]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/lib/transcoding/shared/job-builders/transcoding-runner-job-builder.ts
Fix import video file lock
[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
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}