]>
Commit | Line | Data |
---|---|---|
0c9668f7 C |
1 | import { computeOutputFPS } from '@server/helpers/ffmpeg' |
2 | import { logger, loggerTagsFactory } from '@server/helpers/logger' | |
3 | import { CONFIG } from '@server/initializers/config' | |
4 | import { DEFAULT_AUDIO_RESOLUTION, VIDEO_TRANSCODING_FPS } from '@server/initializers/constants' | |
5 | import { Hooks } from '@server/lib/plugins/hooks' | |
6 | import { VODAudioMergeTranscodingJobHandler, VODHLSTranscodingJobHandler, VODWebVideoTranscodingJobHandler } from '@server/lib/runners' | |
7 | import { VideoPathManager } from '@server/lib/video-path-manager' | |
8 | import { MUserId, MVideoFile, MVideoFullLight, MVideoWithFileThumbnail } from '@server/types/models' | |
9 | import { MRunnerJob } from '@server/types/models/runners' | |
10 | import { ffprobePromise, getVideoStreamDimensionsInfo, getVideoStreamFPS, hasAudioStream, isAudioFile } from '@shared/ffmpeg' | |
5e47f6ab | 11 | import { getTranscodingJobPriority } from '../../transcoding-priority' |
0c9668f7 C |
12 | import { computeResolutionsToTranscode } from '../../transcoding-resolutions' |
13 | import { AbstractJobBuilder } from './abstract-job-builder' | |
14 | ||
15 | /** | |
16 | * | |
17 | * Class to build transcoding job in the local job queue | |
18 | * | |
19 | */ | |
20 | ||
21 | const lTags = loggerTagsFactory('transcoding') | |
22 | ||
23 | export class TranscodingRunnerJobBuilder extends AbstractJobBuilder { | |
24 | ||
25 | async createOptimizeOrMergeAudioJobs (options: { | |
26 | video: MVideoFullLight | |
27 | videoFile: MVideoFile | |
28 | isNewVideo: boolean | |
29 | user: MUserId | |
9a3db678 | 30 | videoFileAlreadyLocked: boolean |
0c9668f7 | 31 | }) { |
9a3db678 | 32 | const { video, videoFile, isNewVideo, user, videoFileAlreadyLocked } = options |
0c9668f7 | 33 | |
9a3db678 C |
34 | const mutexReleaser = videoFileAlreadyLocked |
35 | ? () => {} | |
36 | : await VideoPathManager.Instance.lockFiles(video.uuid) | |
0c9668f7 C |
37 | |
38 | try { | |
a687879e C |
39 | await video.reload() |
40 | await videoFile.reload() | |
41 | ||
0c9668f7 C |
42 | await VideoPathManager.Instance.makeAvailableVideoFile(videoFile.withVideoOrPlaylist(video), async videoFilePath => { |
43 | const probe = await ffprobePromise(videoFilePath) | |
44 | ||
45 | const { resolution } = await getVideoStreamDimensionsInfo(videoFilePath, probe) | |
46 | const hasAudio = await hasAudioStream(videoFilePath, probe) | |
47 | const inputFPS = videoFile.isAudio() | |
48 | ? VIDEO_TRANSCODING_FPS.AUDIO_MERGE // The first transcoding job will transcode to this FPS value | |
49 | : await getVideoStreamFPS(videoFilePath, probe) | |
50 | ||
51 | const maxResolution = await isAudioFile(videoFilePath, probe) | |
52 | ? DEFAULT_AUDIO_RESOLUTION | |
53 | : resolution | |
54 | ||
55 | const fps = computeOutputFPS({ inputFPS, resolution: maxResolution }) | |
5e47f6ab | 56 | const priority = await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 }) |
0c9668f7 C |
57 | |
58 | const mainRunnerJob = videoFile.isAudio() | |
59 | ? await new VODAudioMergeTranscodingJobHandler().create({ video, resolution: maxResolution, fps, isNewVideo, priority }) | |
60 | : await new VODWebVideoTranscodingJobHandler().create({ video, resolution: maxResolution, fps, isNewVideo, priority }) | |
61 | ||
62 | if (CONFIG.TRANSCODING.HLS.ENABLED === true) { | |
63 | await new VODHLSTranscodingJobHandler().create({ | |
64 | video, | |
65 | deleteWebVideoFiles: CONFIG.TRANSCODING.WEBTORRENT.ENABLED === false, | |
66 | resolution: maxResolution, | |
67 | fps, | |
68 | isNewVideo, | |
69 | dependsOnRunnerJob: mainRunnerJob, | |
5e47f6ab | 70 | priority: await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 }) |
0c9668f7 C |
71 | }) |
72 | } | |
73 | ||
74 | await this.buildLowerResolutionJobPayloads({ | |
75 | video, | |
76 | inputVideoResolution: maxResolution, | |
77 | inputVideoFPS: inputFPS, | |
78 | hasAudio, | |
79 | isNewVideo, | |
80 | mainRunnerJob, | |
81 | user | |
82 | }) | |
83 | }) | |
84 | } finally { | |
85 | mutexReleaser() | |
86 | } | |
87 | } | |
88 | ||
89 | // --------------------------------------------------------------------------- | |
90 | ||
91 | async createTranscodingJobs (options: { | |
92 | transcodingType: 'hls' | 'webtorrent' | |
93 | video: MVideoFullLight | |
94 | resolutions: number[] | |
95 | isNewVideo: boolean | |
96 | user: MUserId | null | |
97 | }) { | |
98 | const { video, transcodingType, resolutions, isNewVideo, user } = options | |
99 | ||
100 | const maxResolution = Math.max(...resolutions) | |
101 | const { fps: inputFPS } = await video.probeMaxQualityFile() | |
102 | const maxFPS = computeOutputFPS({ inputFPS, resolution: maxResolution }) | |
5e47f6ab | 103 | const priority = await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 }) |
0c9668f7 C |
104 | |
105 | const childrenResolutions = resolutions.filter(r => r !== maxResolution) | |
106 | ||
107 | logger.info('Manually creating transcoding jobs for %s.', transcodingType, { childrenResolutions, maxResolution }) | |
108 | ||
109 | // Process the last resolution before the other ones to prevent concurrency issue | |
110 | // Because low resolutions use the biggest one as ffmpeg input | |
111 | const mainJob = transcodingType === 'hls' | |
112 | // eslint-disable-next-line max-len | |
113 | ? await new VODHLSTranscodingJobHandler().create({ video, resolution: maxResolution, fps: maxFPS, isNewVideo, deleteWebVideoFiles: false, priority }) | |
114 | : await new VODWebVideoTranscodingJobHandler().create({ video, resolution: maxResolution, fps: maxFPS, isNewVideo, priority }) | |
115 | ||
116 | for (const resolution of childrenResolutions) { | |
117 | const dependsOnRunnerJob = mainJob | |
d98909f6 | 118 | const fps = computeOutputFPS({ inputFPS, resolution }) |
0c9668f7 C |
119 | |
120 | if (transcodingType === 'hls') { | |
121 | await new VODHLSTranscodingJobHandler().create({ | |
122 | video, | |
123 | resolution, | |
124 | fps, | |
125 | isNewVideo, | |
126 | deleteWebVideoFiles: false, | |
127 | dependsOnRunnerJob, | |
5e47f6ab | 128 | priority: await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 }) |
0c9668f7 C |
129 | }) |
130 | continue | |
131 | } | |
132 | ||
133 | if (transcodingType === 'webtorrent') { | |
134 | await new VODWebVideoTranscodingJobHandler().create({ | |
135 | video, | |
136 | resolution, | |
137 | fps, | |
138 | isNewVideo, | |
139 | dependsOnRunnerJob, | |
5e47f6ab | 140 | priority: await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 }) |
0c9668f7 C |
141 | }) |
142 | continue | |
143 | } | |
144 | ||
145 | throw new Error('Unknown transcoding type') | |
146 | } | |
147 | } | |
148 | ||
149 | private async buildLowerResolutionJobPayloads (options: { | |
150 | mainRunnerJob: MRunnerJob | |
151 | video: MVideoWithFileThumbnail | |
152 | inputVideoResolution: number | |
153 | inputVideoFPS: number | |
154 | hasAudio: boolean | |
155 | isNewVideo: boolean | |
156 | user: MUserId | |
157 | }) { | |
158 | const { video, inputVideoResolution, inputVideoFPS, isNewVideo, hasAudio, mainRunnerJob, user } = options | |
159 | ||
160 | // Create transcoding jobs if there are enabled resolutions | |
161 | const resolutionsEnabled = await Hooks.wrapObject( | |
162 | computeResolutionsToTranscode({ input: inputVideoResolution, type: 'vod', includeInput: false, strictLower: true, hasAudio }), | |
163 | 'filter:transcoding.auto.resolutions-to-transcode.result', | |
164 | options | |
165 | ) | |
166 | ||
167 | logger.debug('Lower resolutions build for %s.', video.uuid, { resolutionsEnabled, ...lTags(video.uuid) }) | |
168 | ||
169 | for (const resolution of resolutionsEnabled) { | |
170 | const fps = computeOutputFPS({ inputFPS: inputVideoFPS, resolution }) | |
171 | ||
172 | if (CONFIG.TRANSCODING.WEBTORRENT.ENABLED) { | |
173 | await new VODWebVideoTranscodingJobHandler().create({ | |
174 | video, | |
175 | resolution, | |
176 | fps, | |
177 | isNewVideo, | |
178 | dependsOnRunnerJob: mainRunnerJob, | |
5e47f6ab | 179 | priority: await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 }) |
0c9668f7 C |
180 | }) |
181 | } | |
182 | ||
183 | if (CONFIG.TRANSCODING.HLS.ENABLED) { | |
184 | await new VODHLSTranscodingJobHandler().create({ | |
185 | video, | |
186 | resolution, | |
187 | fps, | |
188 | isNewVideo, | |
189 | deleteWebVideoFiles: false, | |
190 | dependsOnRunnerJob: mainRunnerJob, | |
5e47f6ab | 191 | priority: await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 }) |
0c9668f7 C |
192 | }) |
193 | } | |
194 | } | |
195 | } | |
196 | } |