]>
Commit | Line | Data |
---|---|---|
5e47f6ab C |
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' | |
0c9668f7 | 8 | import { getVideoStreamDuration } from '@shared/ffmpeg' |
5e47f6ab C |
9 | import { VideoStudioEditionPayload, VideoStudioTask, VideoStudioTaskPayload } from '@shared/models' |
10 | import { federateVideoIfNeeded } from './activitypub/videos' | |
11 | import { JobQueue } from './job-queue' | |
12 | import { VideoEditionTranscodingJobHandler } 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' | |
c729caf6 | 17 | |
5e47f6ab C |
18 | const lTags = loggerTagsFactory('video-edition') |
19 | ||
20 | export function buildTaskFileFieldname (indice: number, fieldName = 'file') { | |
c729caf6 C |
21 | return `tasks[${indice}][options][${fieldName}]` |
22 | } | |
23 | ||
5e47f6ab | 24 | export function getTaskFileFromReq (files: Express.Multer.File[], indice: number, fieldName = 'file') { |
c729caf6 C |
25 | return files.find(f => f.fieldname === buildTaskFileFieldname(indice, fieldName)) |
26 | } | |
27 | ||
5e47f6ab C |
28 | export function getStudioTaskFilePath (filename: string) { |
29 | return join(CONFIG.STORAGE.TMP_PERSISTENT_DIR, filename) | |
30 | } | |
31 | ||
32 | export async function safeCleanupStudioTMPFiles (tasks: VideoStudioTaskPayload[]) { | |
33 | logger.info('Removing studio task files', { tasks, ...lTags() }) | |
34 | ||
35 | for (const task of tasks) { | |
6a490560 C |
36 | try { |
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) | |
41 | } | |
42 | } catch (err) { | |
43 | logger.error('Cannot remove studio file', { err }) | |
44 | } | |
45 | } | |
46 | } | |
47 | ||
5e47f6ab C |
48 | // --------------------------------------------------------------------------- |
49 | ||
50 | export async function approximateIntroOutroAdditionalSize ( | |
51 | video: MVideoFullLight, | |
52 | tasks: VideoStudioTask[], | |
53 | fileFinder: (i: number) => string | |
54 | ) { | |
c729caf6 C |
55 | let additionalDuration = 0 |
56 | ||
57 | for (let i = 0; i < tasks.length; i++) { | |
58 | const task = tasks[i] | |
59 | ||
60 | if (task.name !== 'add-intro' && task.name !== 'add-outro') continue | |
61 | ||
62 | const filePath = fileFinder(i) | |
63 | additionalDuration += await getVideoStreamDuration(filePath) | |
64 | } | |
65 | ||
66 | return (video.getMaxQualityFile().size / video.duration) * additionalDuration | |
67 | } | |
68 | ||
5e47f6ab C |
69 | // --------------------------------------------------------------------------- |
70 | ||
71 | export async function createVideoStudioJob (options: { | |
72 | video: MVideo | |
73 | user: MUser | |
74 | payload: VideoStudioEditionPayload | |
75 | }) { | |
76 | const { video, user, payload } = options | |
77 | ||
78 | const priority = await getTranscodingJobPriority({ user, type: 'studio', fallback: 0 }) | |
79 | ||
80 | if (CONFIG.VIDEO_STUDIO.REMOTE_RUNNERS.ENABLED) { | |
81 | await new VideoEditionTranscodingJobHandler().create({ video, tasks: payload.tasks, priority }) | |
82 | return | |
83 | } | |
84 | ||
85 | await JobQueue.Instance.createJob({ type: 'video-studio-edition', payload, priority }) | |
86 | } | |
87 | ||
88 | export async function onVideoEditionEnded (options: { | |
89 | editionResultPath: string | |
90 | tasks: VideoStudioTaskPayload[] | |
91 | video: MVideoFullLight | |
92 | }) { | |
93 | const { video, tasks, editionResultPath } = options | |
94 | ||
95 | const newFile = await buildNewFile({ path: editionResultPath, mode: 'web-video' }) | |
96 | newFile.videoId = video.id | |
97 | ||
98 | const outputPath = VideoPathManager.Instance.getFSVideoFileOutputPath(video, newFile) | |
99 | await move(editionResultPath, outputPath) | |
100 | ||
101 | await safeCleanupStudioTMPFiles(tasks) | |
102 | ||
103 | await createTorrentAndSetInfoHashFromPath(video, newFile, outputPath) | |
104 | await removeAllFiles(video, newFile) | |
105 | ||
106 | await newFile.save() | |
107 | ||
108 | video.duration = await getVideoStreamDuration(outputPath) | |
109 | await video.save() | |
110 | ||
111 | await federateVideoIfNeeded(video, false, undefined) | |
112 | ||
113 | const user = await UserModel.loadByVideoId(video.id) | |
114 | ||
115 | await createOptimizeOrMergeAudioJobs({ video, videoFile: newFile, isNewVideo: false, user, videoFileAlreadyLocked: false }) | |
116 | } | |
117 | ||
118 | // --------------------------------------------------------------------------- | |
119 | // Private | |
120 | // --------------------------------------------------------------------------- | |
121 | ||
122 | async function removeAllFiles (video: MVideoWithAllFiles, webTorrentFileException: MVideoFile) { | |
123 | await removeHLSPlaylist(video) | |
124 | ||
125 | for (const file of video.VideoFiles) { | |
126 | if (file.id === webTorrentFileException.id) continue | |
127 | ||
128 | await removeWebTorrentFile(video, file.id) | |
129 | } | |
c729caf6 | 130 | } |