]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/lib/transcoding/shared/job-builders/transcoding-runner-job-builder.ts
Implement remote runner jobs in server
[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'
11import { computeResolutionsToTranscode } from '../../transcoding-resolutions'
12import { AbstractJobBuilder } from './abstract-job-builder'
13
14/**
15 *
16 * Class to build transcoding job in the local job queue
17 *
18 */
19
20const lTags = loggerTagsFactory('transcoding')
21
22export 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}