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