]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/lib/transcoding/shared/job-builders/transcoding-runner-job-builder.ts
Support studio transcoding in peertube runner
[github/Chocobozzz/PeerTube.git] / server / lib / transcoding / shared / job-builders / transcoding-runner-job-builder.ts
CommitLineData
0c9668f7
C
1import { computeOutputFPS } from '@server/helpers/ffmpeg'
2import { logger, loggerTagsFactory } from '@server/helpers/logger'
3import { CONFIG } from '@server/initializers/config'
4import { DEFAULT_AUDIO_RESOLUTION, VIDEO_TRANSCODING_FPS } from '@server/initializers/constants'
5import { Hooks } from '@server/lib/plugins/hooks'
6import { VODAudioMergeTranscodingJobHandler, VODHLSTranscodingJobHandler, VODWebVideoTranscodingJobHandler } from '@server/lib/runners'
7import { VideoPathManager } from '@server/lib/video-path-manager'
8import { MUserId, MVideoFile, MVideoFullLight, MVideoWithFileThumbnail } from '@server/types/models'
9import { MRunnerJob } from '@server/types/models/runners'
10import { ffprobePromise, getVideoStreamDimensionsInfo, getVideoStreamFPS, hasAudioStream, isAudioFile } from '@shared/ffmpeg'
5e47f6ab 11import { getTranscodingJobPriority } from '../../transcoding-priority'
0c9668f7
C
12import { computeResolutionsToTranscode } from '../../transcoding-resolutions'
13import { AbstractJobBuilder } from './abstract-job-builder'
14
15/**
16 *
17 * Class to build transcoding job in the local job queue
18 *
19 */
20
21const lTags = loggerTagsFactory('transcoding')
22
23export 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}