1 import { move, remove } from 'fs-extra'
2 import { join } from 'path'
3 import { logger, loggerTagsFactory } from '@server/helpers/logger'
4 import { createTorrentAndSetInfoHashFromPath } from '@server/helpers/webtorrent'
5 import { CONFIG } from '@server/initializers/config'
6 import { UserModel } from '@server/models/user/user'
7 import { MUser, MVideo, MVideoFile, MVideoFullLight, MVideoWithAllFiles } from '@server/types/models'
8 import { getVideoStreamDuration } from '@shared/ffmpeg'
9 import { VideoStudioEditionPayload, VideoStudioTask, VideoStudioTaskPayload } from '@shared/models'
10 import { federateVideoIfNeeded } from './activitypub/videos'
11 import { JobQueue } from './job-queue'
12 import { VideoStudioTranscodingJobHandler } from './runners'
13 import { createOptimizeOrMergeAudioJobs } from './transcoding/create-transcoding-job'
14 import { getTranscodingJobPriority } from './transcoding/transcoding-priority'
15 import { buildNewFile, removeHLSPlaylist, removeWebTorrentFile } from './video-file'
16 import { VideoPathManager } from './video-path-manager'
18 const lTags = loggerTagsFactory('video-studio')
20 export function buildTaskFileFieldname (indice: number, fieldName = 'file') {
21 return `tasks[${indice}][options][${fieldName}]`
24 export function getTaskFileFromReq (files: Express.Multer.File[], indice: number, fieldName = 'file') {
25 return files.find(f => f.fieldname === buildTaskFileFieldname(indice, fieldName))
28 export function getStudioTaskFilePath (filename: string) {
29 return join(CONFIG.STORAGE.TMP_PERSISTENT_DIR, filename)
32 export async function safeCleanupStudioTMPFiles (tasks: VideoStudioTaskPayload[]) {
33 logger.info('Removing studio task files', { tasks, ...lTags() })
35 for (const task of tasks) {
37 if (task.name === 'add-intro' || task.name === 'add-outro') {
38 await remove(task.options.file)
39 } else if (task.name === 'add-watermark') {
40 await remove(task.options.file)
43 logger.error('Cannot remove studio file', { err })
48 // ---------------------------------------------------------------------------
50 export async function approximateIntroOutroAdditionalSize (
51 video: MVideoFullLight,
52 tasks: VideoStudioTask[],
53 fileFinder: (i: number) => string
55 let additionalDuration = 0
57 for (let i = 0; i < tasks.length; i++) {
60 if (task.name !== 'add-intro' && task.name !== 'add-outro') continue
62 const filePath = fileFinder(i)
63 additionalDuration += await getVideoStreamDuration(filePath)
66 return (video.getMaxQualityFile().size / video.duration) * additionalDuration
69 // ---------------------------------------------------------------------------
71 export async function createVideoStudioJob (options: {
74 payload: VideoStudioEditionPayload
76 const { video, user, payload } = options
78 const priority = await getTranscodingJobPriority({ user, type: 'studio', fallback: 0 })
80 if (CONFIG.VIDEO_STUDIO.REMOTE_RUNNERS.ENABLED) {
81 await new VideoStudioTranscodingJobHandler().create({ video, tasks: payload.tasks, priority })
85 await JobQueue.Instance.createJob({ type: 'video-studio-edition', payload, priority })
88 export async function onVideoStudioEnded (options: {
89 editionResultPath: string
90 tasks: VideoStudioTaskPayload[]
91 video: MVideoFullLight
93 const { video, tasks, editionResultPath } = options
95 const newFile = await buildNewFile({ path: editionResultPath, mode: 'web-video' })
96 newFile.videoId = video.id
98 const outputPath = VideoPathManager.Instance.getFSVideoFileOutputPath(video, newFile)
99 await move(editionResultPath, outputPath)
101 await safeCleanupStudioTMPFiles(tasks)
103 await createTorrentAndSetInfoHashFromPath(video, newFile, outputPath)
104 await removeAllFiles(video, newFile)
108 video.duration = await getVideoStreamDuration(outputPath)
111 await federateVideoIfNeeded(video, false, undefined)
113 const user = await UserModel.loadByVideoId(video.id)
115 await createOptimizeOrMergeAudioJobs({ video, videoFile: newFile, isNewVideo: false, user, videoFileAlreadyLocked: false })
118 // ---------------------------------------------------------------------------
120 // ---------------------------------------------------------------------------
122 async function removeAllFiles (video: MVideoWithAllFiles, webTorrentFileException: MVideoFile) {
123 await removeHLSPlaylist(video)
125 for (const file of video.VideoFiles) {
126 if (file.id === webTorrentFileException.id) continue
128 await removeWebTorrentFile(video, file.id)