|
- import { buildAspectRatio } from '@peertube/peertube-core-utils'
- import { getVideoStreamDuration } from '@peertube/peertube-ffmpeg'
- import { VideoStudioEditionPayload, VideoStudioTask, VideoStudioTaskPayload } from '@peertube/peertube-models'
- import { logger, loggerTagsFactory } from '@server/helpers/logger.js'
- import { createTorrentAndSetInfoHashFromPath } from '@server/helpers/webtorrent.js'
- import { CONFIG } from '@server/initializers/config.js'
- import { VideoCaptionModel } from '@server/models/video/video-caption.js'
- import { MUser, MVideo, MVideoFile, MVideoFullLight, MVideoWithAllFiles } from '@server/types/models/index.js'
- import { move, remove } from 'fs-extra/esm'
- import { join } from 'path'
- import { JobQueue } from './job-queue/index.js'
- import { VideoStudioTranscodingJobHandler } from './runners/index.js'
- import { getTranscodingJobPriority } from './transcoding/transcoding-priority.js'
- import { createTranscriptionTaskIfNeeded } from './video-captions.js'
- import { buildNewFile, removeHLSPlaylist, removeWebVideoFile } from './video-file.js'
- import { buildStoryboardJobIfNeeded } from './video-jobs.js'
- import { VideoPathManager } from './video-path-manager.js'
-
- const lTags = loggerTagsFactory('video-studio')
-
- export function buildTaskFileFieldname (indice: number, fieldName = 'file') {
- return `tasks[${indice}][options][${fieldName}]`
- }
-
- export function getTaskFileFromReq (files: Express.Multer.File[], indice: number, fieldName = 'file') {
- return files.find(f => f.fieldname === buildTaskFileFieldname(indice, fieldName))
- }
-
- export function getStudioTaskFilePath (filename: string) {
- return join(CONFIG.STORAGE.TMP_PERSISTENT_DIR, filename)
- }
-
- export async function safeCleanupStudioTMPFiles (tasks: VideoStudioTaskPayload[]) {
- logger.info('Removing studio task files', { tasks, ...lTags() })
-
- for (const task of tasks) {
- try {
- if (task.name === 'add-intro' || task.name === 'add-outro') {
- await remove(task.options.file)
- } else if (task.name === 'add-watermark') {
- await remove(task.options.file)
- }
- } catch (err) {
- logger.error('Cannot remove studio file', { err })
- }
- }
- }
-
- // ---------------------------------------------------------------------------
-
- export async function approximateIntroOutroAdditionalSize (
- video: MVideoFullLight,
- tasks: VideoStudioTask[],
- fileFinder: (i: number) => string
- ) {
- let additionalDuration = 0
-
- for (let i = 0; i < tasks.length; i++) {
- const task = tasks[i]
-
- if (task.name !== 'add-intro' && task.name !== 'add-outro') continue
-
- const filePath = fileFinder(i)
- additionalDuration += await getVideoStreamDuration(filePath)
- }
-
- return (video.getMaxQualityFile().size / video.duration) * additionalDuration
- }
-
- // ---------------------------------------------------------------------------
-
- export async function createVideoStudioJob (options: {
- video: MVideo
- user: MUser
- payload: VideoStudioEditionPayload
- }) {
- const { video, user, payload } = options
-
- const priority = await getTranscodingJobPriority({ user, type: 'studio', fallback: 0 })
-
- if (CONFIG.VIDEO_STUDIO.REMOTE_RUNNERS.ENABLED) {
- await new VideoStudioTranscodingJobHandler().create({ video, tasks: payload.tasks, priority })
- return
- }
-
- await JobQueue.Instance.createJob({ type: 'video-studio-edition', payload, priority })
- }
-
- export async function onVideoStudioEnded (options: {
- editionResultPath: string
- tasks: VideoStudioTaskPayload[]
- video: MVideoFullLight
- }) {
- const { video, tasks, editionResultPath } = options
-
- const newFile = await buildNewFile({ path: editionResultPath, mode: 'web-video' })
- newFile.videoId = video.id
-
- const outputPath = VideoPathManager.Instance.getFSVideoFileOutputPath(video, newFile)
- await move(editionResultPath, outputPath)
-
- await safeCleanupStudioTMPFiles(tasks)
-
- await createTorrentAndSetInfoHashFromPath(video, newFile, outputPath)
- await removeAllFiles(video, newFile)
-
- await newFile.save()
-
- video.duration = await getVideoStreamDuration(outputPath)
- video.aspectRatio = buildAspectRatio({ width: newFile.width, height: newFile.height })
- await video.save()
-
- await JobQueue.Instance.createSequentialJobFlow(
- buildStoryboardJobIfNeeded({ video, federate: false }),
-
- {
- type: 'federate-video' as 'federate-video',
- payload: {
- videoUUID: video.uuid,
- isNewVideoForFederation: false
- }
- },
-
- {
- type: 'transcoding-job-builder' as 'transcoding-job-builder',
- payload: {
- videoUUID: video.uuid,
- optimizeJob: {
- isNewVideo: false
- }
- }
- }
- )
-
- if (video.language && CONFIG.VIDEO_TRANSCRIPTION.ENABLED) {
- const caption = await VideoCaptionModel.loadByVideoIdAndLanguage(video.id, video.language)
-
- if (caption?.automaticallyGenerated) {
- await createTranscriptionTaskIfNeeded(video)
- }
- }
- }
-
- // ---------------------------------------------------------------------------
- // Private
- // ---------------------------------------------------------------------------
-
- async function removeAllFiles (video: MVideoWithAllFiles, webVideoFileException: MVideoFile) {
- await removeHLSPlaylist(video)
-
- for (const file of video.VideoFiles) {
- if (file.id === webVideoFileException.id) continue
-
- await removeWebVideoFile(video, file.id)
- }
- }
|