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