]>
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 { | |
39 | await VideoPathManager.Instance.makeAvailableVideoFile(videoFile.withVideoOrPlaylist(video), async videoFilePath => { | |
40 | const probe = await ffprobePromise(videoFilePath) | |
41 | ||
42 | const { resolution } = await getVideoStreamDimensionsInfo(videoFilePath, probe) | |
43 | const hasAudio = await hasAudioStream(videoFilePath, probe) | |
44 | const inputFPS = videoFile.isAudio() | |
45 | ? VIDEO_TRANSCODING_FPS.AUDIO_MERGE // The first transcoding job will transcode to this FPS value | |
46 | : await getVideoStreamFPS(videoFilePath, probe) | |
47 | ||
48 | const maxResolution = await isAudioFile(videoFilePath, probe) | |
49 | ? DEFAULT_AUDIO_RESOLUTION | |
50 | : resolution | |
51 | ||
52 | const fps = computeOutputFPS({ inputFPS, resolution: maxResolution }) | |
5e47f6ab | 53 | const priority = await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 }) |
0c9668f7 C |
54 | |
55 | const mainRunnerJob = videoFile.isAudio() | |
56 | ? await new VODAudioMergeTranscodingJobHandler().create({ video, resolution: maxResolution, fps, isNewVideo, priority }) | |
57 | : await new VODWebVideoTranscodingJobHandler().create({ video, resolution: maxResolution, fps, isNewVideo, priority }) | |
58 | ||
59 | if (CONFIG.TRANSCODING.HLS.ENABLED === true) { | |
60 | await new VODHLSTranscodingJobHandler().create({ | |
61 | video, | |
62 | deleteWebVideoFiles: CONFIG.TRANSCODING.WEBTORRENT.ENABLED === false, | |
63 | resolution: maxResolution, | |
64 | fps, | |
65 | isNewVideo, | |
66 | dependsOnRunnerJob: mainRunnerJob, | |
5e47f6ab | 67 | priority: await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 }) |
0c9668f7 C |
68 | }) |
69 | } | |
70 | ||
71 | await this.buildLowerResolutionJobPayloads({ | |
72 | video, | |
73 | inputVideoResolution: maxResolution, | |
74 | inputVideoFPS: inputFPS, | |
75 | hasAudio, | |
76 | isNewVideo, | |
77 | mainRunnerJob, | |
78 | user | |
79 | }) | |
80 | }) | |
81 | } finally { | |
82 | mutexReleaser() | |
83 | } | |
84 | } | |
85 | ||
86 | // --------------------------------------------------------------------------- | |
87 | ||
88 | async createTranscodingJobs (options: { | |
89 | transcodingType: 'hls' | 'webtorrent' | |
90 | video: MVideoFullLight | |
91 | resolutions: number[] | |
92 | isNewVideo: boolean | |
93 | user: MUserId | null | |
94 | }) { | |
95 | const { video, transcodingType, resolutions, isNewVideo, user } = options | |
96 | ||
97 | const maxResolution = Math.max(...resolutions) | |
98 | const { fps: inputFPS } = await video.probeMaxQualityFile() | |
99 | const maxFPS = computeOutputFPS({ inputFPS, resolution: maxResolution }) | |
5e47f6ab | 100 | const priority = await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 }) |
0c9668f7 C |
101 | |
102 | const childrenResolutions = resolutions.filter(r => r !== maxResolution) | |
103 | ||
104 | logger.info('Manually creating transcoding jobs for %s.', transcodingType, { childrenResolutions, maxResolution }) | |
105 | ||
106 | // Process the last resolution before the other ones to prevent concurrency issue | |
107 | // Because low resolutions use the biggest one as ffmpeg input | |
108 | const mainJob = transcodingType === 'hls' | |
109 | // eslint-disable-next-line max-len | |
110 | ? await new VODHLSTranscodingJobHandler().create({ video, resolution: maxResolution, fps: maxFPS, isNewVideo, deleteWebVideoFiles: false, priority }) | |
111 | : await new VODWebVideoTranscodingJobHandler().create({ video, resolution: maxResolution, fps: maxFPS, isNewVideo, priority }) | |
112 | ||
113 | for (const resolution of childrenResolutions) { | |
114 | const dependsOnRunnerJob = mainJob | |
115 | const fps = computeOutputFPS({ inputFPS, resolution: maxResolution }) | |
116 | ||
117 | if (transcodingType === 'hls') { | |
118 | await new VODHLSTranscodingJobHandler().create({ | |
119 | video, | |
120 | resolution, | |
121 | fps, | |
122 | isNewVideo, | |
123 | deleteWebVideoFiles: false, | |
124 | dependsOnRunnerJob, | |
5e47f6ab | 125 | priority: await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 }) |
0c9668f7 C |
126 | }) |
127 | continue | |
128 | } | |
129 | ||
130 | if (transcodingType === 'webtorrent') { | |
131 | await new VODWebVideoTranscodingJobHandler().create({ | |
132 | video, | |
133 | resolution, | |
134 | fps, | |
135 | isNewVideo, | |
136 | dependsOnRunnerJob, | |
5e47f6ab | 137 | priority: await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 }) |
0c9668f7 C |
138 | }) |
139 | continue | |
140 | } | |
141 | ||
142 | throw new Error('Unknown transcoding type') | |
143 | } | |
144 | } | |
145 | ||
146 | private async buildLowerResolutionJobPayloads (options: { | |
147 | mainRunnerJob: MRunnerJob | |
148 | video: MVideoWithFileThumbnail | |
149 | inputVideoResolution: number | |
150 | inputVideoFPS: number | |
151 | hasAudio: boolean | |
152 | isNewVideo: boolean | |
153 | user: MUserId | |
154 | }) { | |
155 | const { video, inputVideoResolution, inputVideoFPS, isNewVideo, hasAudio, mainRunnerJob, user } = options | |
156 | ||
157 | // Create transcoding jobs if there are enabled resolutions | |
158 | const resolutionsEnabled = await Hooks.wrapObject( | |
159 | computeResolutionsToTranscode({ input: inputVideoResolution, type: 'vod', includeInput: false, strictLower: true, hasAudio }), | |
160 | 'filter:transcoding.auto.resolutions-to-transcode.result', | |
161 | options | |
162 | ) | |
163 | ||
164 | logger.debug('Lower resolutions build for %s.', video.uuid, { resolutionsEnabled, ...lTags(video.uuid) }) | |
165 | ||
166 | for (const resolution of resolutionsEnabled) { | |
167 | const fps = computeOutputFPS({ inputFPS: inputVideoFPS, resolution }) | |
168 | ||
169 | if (CONFIG.TRANSCODING.WEBTORRENT.ENABLED) { | |
170 | await new VODWebVideoTranscodingJobHandler().create({ | |
171 | video, | |
172 | resolution, | |
173 | fps, | |
174 | isNewVideo, | |
175 | dependsOnRunnerJob: mainRunnerJob, | |
5e47f6ab | 176 | priority: await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 }) |
0c9668f7 C |
177 | }) |
178 | } | |
179 | ||
180 | if (CONFIG.TRANSCODING.HLS.ENABLED) { | |
181 | await new VODHLSTranscodingJobHandler().create({ | |
182 | video, | |
183 | resolution, | |
184 | fps, | |
185 | isNewVideo, | |
186 | deleteWebVideoFiles: false, | |
187 | dependsOnRunnerJob: mainRunnerJob, | |
5e47f6ab | 188 | priority: await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 }) |
0c9668f7 C |
189 | }) |
190 | } | |
191 | } | |
192 | } | |
193 | } |