diff options
Diffstat (limited to 'server/lib/job-queue/handlers/video-transcoding.ts')
-rw-r--r-- | server/lib/job-queue/handlers/video-transcoding.ts | 128 |
1 files changed, 74 insertions, 54 deletions
diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts index 876d1460c..b3149dde8 100644 --- a/server/lib/job-queue/handlers/video-transcoding.ts +++ b/server/lib/job-queue/handlers/video-transcoding.ts | |||
@@ -1,9 +1,11 @@ | |||
1 | import * as Bull from 'bull' | 1 | import * as Bull from 'bull' |
2 | import { TranscodeOptionsType } from '@server/helpers/ffmpeg-utils' | 2 | import { TranscodeOptionsType } from '@server/helpers/ffmpeg-utils' |
3 | import { getTranscodingJobPriority, publishAndFederateIfNeeded } from '@server/lib/video' | 3 | import { addTranscodingJob, getTranscodingJobPriority } from '@server/lib/video' |
4 | import { getVideoFilePath } from '@server/lib/video-paths' | 4 | import { VideoPathManager } from '@server/lib/video-path-manager' |
5 | import { moveToNextState } from '@server/lib/video-state' | ||
5 | import { UserModel } from '@server/models/user/user' | 6 | import { UserModel } from '@server/models/user/user' |
6 | import { MUser, MUserId, MVideoFullLight, MVideoUUID, MVideoWithFile } from '@server/types/models' | 7 | import { VideoJobInfoModel } from '@server/models/video/video-job-info' |
8 | import { MUser, MUserId, MVideo, MVideoFullLight, MVideoWithFile } from '@server/types/models' | ||
7 | import { | 9 | import { |
8 | HLSTranscodingPayload, | 10 | HLSTranscodingPayload, |
9 | MergeAudioTranscodingPayload, | 11 | MergeAudioTranscodingPayload, |
@@ -16,17 +18,14 @@ import { computeResolutionsToTranscode } from '../../../helpers/ffprobe-utils' | |||
16 | import { logger } from '../../../helpers/logger' | 18 | import { logger } from '../../../helpers/logger' |
17 | import { CONFIG } from '../../../initializers/config' | 19 | import { CONFIG } from '../../../initializers/config' |
18 | import { VideoModel } from '../../../models/video/video' | 20 | import { VideoModel } from '../../../models/video/video' |
19 | import { federateVideoIfNeeded } from '../../activitypub/videos' | ||
20 | import { Notifier } from '../../notifier' | ||
21 | import { | 21 | import { |
22 | generateHlsPlaylistResolution, | 22 | generateHlsPlaylistResolution, |
23 | mergeAudioVideofile, | 23 | mergeAudioVideofile, |
24 | optimizeOriginalVideofile, | 24 | optimizeOriginalVideofile, |
25 | transcodeNewWebTorrentResolution | 25 | transcodeNewWebTorrentResolution |
26 | } from '../../transcoding/video-transcoding' | 26 | } from '../../transcoding/video-transcoding' |
27 | import { JobQueue } from '../job-queue' | ||
28 | 27 | ||
29 | type HandlerFunction = (job: Bull.Job, payload: VideoTranscodingPayload, video: MVideoFullLight, user: MUser) => Promise<any> | 28 | type HandlerFunction = (job: Bull.Job, payload: VideoTranscodingPayload, video: MVideoFullLight, user: MUser) => Promise<void> |
30 | 29 | ||
31 | const handlers: { [ id in VideoTranscodingPayload['type'] ]: HandlerFunction } = { | 30 | const handlers: { [ id in VideoTranscodingPayload['type'] ]: HandlerFunction } = { |
32 | 'new-resolution-to-hls': handleHLSJob, | 31 | 'new-resolution-to-hls': handleHLSJob, |
@@ -69,15 +68,16 @@ async function handleHLSJob (job: Bull.Job, payload: HLSTranscodingPayload, vide | |||
69 | : video.getMaxQualityFile() | 68 | : video.getMaxQualityFile() |
70 | 69 | ||
71 | const videoOrStreamingPlaylist = videoFileInput.getVideoOrStreamingPlaylist() | 70 | const videoOrStreamingPlaylist = videoFileInput.getVideoOrStreamingPlaylist() |
72 | const videoInputPath = getVideoFilePath(videoOrStreamingPlaylist, videoFileInput) | ||
73 | 71 | ||
74 | await generateHlsPlaylistResolution({ | 72 | await VideoPathManager.Instance.makeAvailableVideoFile(videoOrStreamingPlaylist, videoFileInput, videoInputPath => { |
75 | video, | 73 | return generateHlsPlaylistResolution({ |
76 | videoInputPath, | 74 | video, |
77 | resolution: payload.resolution, | 75 | videoInputPath, |
78 | copyCodecs: payload.copyCodecs, | 76 | resolution: payload.resolution, |
79 | isPortraitMode: payload.isPortraitMode || false, | 77 | copyCodecs: payload.copyCodecs, |
80 | job | 78 | isPortraitMode: payload.isPortraitMode || false, |
79 | job | ||
80 | }) | ||
81 | }) | 81 | }) |
82 | 82 | ||
83 | await retryTransactionWrapper(onHlsPlaylistGeneration, video, user, payload) | 83 | await retryTransactionWrapper(onHlsPlaylistGeneration, video, user, payload) |
@@ -101,7 +101,7 @@ async function handleWebTorrentMergeAudioJob (job: Bull.Job, payload: MergeAudio | |||
101 | } | 101 | } |
102 | 102 | ||
103 | async function handleWebTorrentOptimizeJob (job: Bull.Job, payload: OptimizeTranscodingPayload, video: MVideoFullLight, user: MUserId) { | 103 | async function handleWebTorrentOptimizeJob (job: Bull.Job, payload: OptimizeTranscodingPayload, video: MVideoFullLight, user: MUserId) { |
104 | const transcodeType = await optimizeOriginalVideofile(video, video.getMaxQualityFile(), job) | 104 | const { transcodeType } = await optimizeOriginalVideofile(video, video.getMaxQualityFile(), job) |
105 | 105 | ||
106 | await retryTransactionWrapper(onVideoFileOptimizer, video, payload, transcodeType, user) | 106 | await retryTransactionWrapper(onVideoFileOptimizer, video, payload, transcodeType, user) |
107 | } | 107 | } |
@@ -121,10 +121,18 @@ async function onHlsPlaylistGeneration (video: MVideoFullLight, user: MUser, pay | |||
121 | video.VideoFiles = [] | 121 | video.VideoFiles = [] |
122 | 122 | ||
123 | // Create HLS new resolution jobs | 123 | // Create HLS new resolution jobs |
124 | await createLowerResolutionsJobs(video, user, payload.resolution, payload.isPortraitMode, 'hls') | 124 | await createLowerResolutionsJobs({ |
125 | video, | ||
126 | user, | ||
127 | videoFileResolution: payload.resolution, | ||
128 | isPortraitMode: payload.isPortraitMode, | ||
129 | isNewVideo: payload.isNewVideo ?? true, | ||
130 | type: 'hls' | ||
131 | }) | ||
125 | } | 132 | } |
126 | 133 | ||
127 | return publishAndFederateIfNeeded(video) | 134 | await VideoJobInfoModel.decrease(video.uuid, 'pendingTranscode') |
135 | await moveToNextState(video, payload.isNewVideo) | ||
128 | } | 136 | } |
129 | 137 | ||
130 | async function onVideoFileOptimizer ( | 138 | async function onVideoFileOptimizer ( |
@@ -143,58 +151,54 @@ async function onVideoFileOptimizer ( | |||
143 | // Video does not exist anymore | 151 | // Video does not exist anymore |
144 | if (!videoDatabase) return undefined | 152 | if (!videoDatabase) return undefined |
145 | 153 | ||
146 | let videoPublished = false | ||
147 | |||
148 | // Generate HLS version of the original file | 154 | // Generate HLS version of the original file |
149 | const originalFileHLSPayload = Object.assign({}, payload, { | 155 | const originalFileHLSPayload = { |
156 | ...payload, | ||
157 | |||
150 | isPortraitMode, | 158 | isPortraitMode, |
151 | resolution: videoDatabase.getMaxQualityFile().resolution, | 159 | resolution: videoDatabase.getMaxQualityFile().resolution, |
152 | // If we quick transcoded original file, force transcoding for HLS to avoid some weird playback issues | 160 | // If we quick transcoded original file, force transcoding for HLS to avoid some weird playback issues |
153 | copyCodecs: transcodeType !== 'quick-transcode', | 161 | copyCodecs: transcodeType !== 'quick-transcode', |
154 | isMaxQuality: true | 162 | isMaxQuality: true |
155 | }) | 163 | } |
156 | const hasHls = await createHlsJobIfEnabled(user, originalFileHLSPayload) | 164 | const hasHls = await createHlsJobIfEnabled(user, originalFileHLSPayload) |
165 | const hasNewResolutions = await createLowerResolutionsJobs({ | ||
166 | video: videoDatabase, | ||
167 | user, | ||
168 | videoFileResolution: resolution, | ||
169 | isPortraitMode, | ||
170 | type: 'webtorrent', | ||
171 | isNewVideo: payload.isNewVideo ?? true | ||
172 | }) | ||
157 | 173 | ||
158 | const hasNewResolutions = await createLowerResolutionsJobs(videoDatabase, user, resolution, isPortraitMode, 'webtorrent') | 174 | await VideoJobInfoModel.decrease(videoDatabase.uuid, 'pendingTranscode') |
159 | 175 | ||
176 | // Move to next state if there are no other resolutions to generate | ||
160 | if (!hasHls && !hasNewResolutions) { | 177 | if (!hasHls && !hasNewResolutions) { |
161 | // No transcoding to do, it's now published | 178 | await moveToNextState(videoDatabase, payload.isNewVideo) |
162 | videoPublished = await videoDatabase.publishIfNeededAndSave(undefined) | ||
163 | } | 179 | } |
164 | |||
165 | await federateVideoIfNeeded(videoDatabase, payload.isNewVideo) | ||
166 | |||
167 | if (payload.isNewVideo) Notifier.Instance.notifyOnNewVideoIfNeeded(videoDatabase) | ||
168 | if (videoPublished) Notifier.Instance.notifyOnVideoPublishedAfterTranscoding(videoDatabase) | ||
169 | } | 180 | } |
170 | 181 | ||
171 | async function onNewWebTorrentFileResolution ( | 182 | async function onNewWebTorrentFileResolution ( |
172 | video: MVideoUUID, | 183 | video: MVideo, |
173 | user: MUserId, | 184 | user: MUserId, |
174 | payload: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload | 185 | payload: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload |
175 | ) { | 186 | ) { |
176 | await publishAndFederateIfNeeded(video) | 187 | await createHlsJobIfEnabled(user, { ...payload, copyCodecs: true, isMaxQuality: false }) |
188 | await VideoJobInfoModel.decrease(video.uuid, 'pendingTranscode') | ||
177 | 189 | ||
178 | await createHlsJobIfEnabled(user, Object.assign({}, payload, { copyCodecs: true, isMaxQuality: false })) | 190 | await moveToNextState(video, payload.isNewVideo) |
179 | } | 191 | } |
180 | 192 | ||
181 | // --------------------------------------------------------------------------- | ||
182 | |||
183 | export { | ||
184 | processVideoTranscoding, | ||
185 | onNewWebTorrentFileResolution | ||
186 | } | ||
187 | |||
188 | // --------------------------------------------------------------------------- | ||
189 | |||
190 | async function createHlsJobIfEnabled (user: MUserId, payload: { | 193 | async function createHlsJobIfEnabled (user: MUserId, payload: { |
191 | videoUUID: string | 194 | videoUUID: string |
192 | resolution: number | 195 | resolution: number |
193 | isPortraitMode?: boolean | 196 | isPortraitMode?: boolean |
194 | copyCodecs: boolean | 197 | copyCodecs: boolean |
195 | isMaxQuality: boolean | 198 | isMaxQuality: boolean |
199 | isNewVideo?: boolean | ||
196 | }) { | 200 | }) { |
197 | if (!payload || CONFIG.TRANSCODING.HLS.ENABLED !== true) return false | 201 | if (!payload || CONFIG.TRANSCODING.ENABLED !== true || CONFIG.TRANSCODING.HLS.ENABLED !== true) return false |
198 | 202 | ||
199 | const jobOptions = { | 203 | const jobOptions = { |
200 | priority: await getTranscodingJobPriority(user) | 204 | priority: await getTranscodingJobPriority(user) |
@@ -206,21 +210,35 @@ async function createHlsJobIfEnabled (user: MUserId, payload: { | |||
206 | resolution: payload.resolution, | 210 | resolution: payload.resolution, |
207 | isPortraitMode: payload.isPortraitMode, | 211 | isPortraitMode: payload.isPortraitMode, |
208 | copyCodecs: payload.copyCodecs, | 212 | copyCodecs: payload.copyCodecs, |
209 | isMaxQuality: payload.isMaxQuality | 213 | isMaxQuality: payload.isMaxQuality, |
214 | isNewVideo: payload.isNewVideo | ||
210 | } | 215 | } |
211 | 216 | ||
212 | JobQueue.Instance.createJob({ type: 'video-transcoding', payload: hlsTranscodingPayload }, jobOptions) | 217 | await addTranscodingJob(hlsTranscodingPayload, jobOptions) |
213 | 218 | ||
214 | return true | 219 | return true |
215 | } | 220 | } |
216 | 221 | ||
217 | async function createLowerResolutionsJobs ( | 222 | // --------------------------------------------------------------------------- |
218 | video: MVideoFullLight, | 223 | |
219 | user: MUserId, | 224 | export { |
220 | videoFileResolution: number, | 225 | processVideoTranscoding, |
221 | isPortraitMode: boolean, | 226 | createHlsJobIfEnabled, |
227 | onNewWebTorrentFileResolution | ||
228 | } | ||
229 | |||
230 | // --------------------------------------------------------------------------- | ||
231 | |||
232 | async function createLowerResolutionsJobs (options: { | ||
233 | video: MVideoFullLight | ||
234 | user: MUserId | ||
235 | videoFileResolution: number | ||
236 | isPortraitMode: boolean | ||
237 | isNewVideo: boolean | ||
222 | type: 'hls' | 'webtorrent' | 238 | type: 'hls' | 'webtorrent' |
223 | ) { | 239 | }) { |
240 | const { video, user, videoFileResolution, isPortraitMode, isNewVideo, type } = options | ||
241 | |||
224 | // Create transcoding jobs if there are enabled resolutions | 242 | // Create transcoding jobs if there are enabled resolutions |
225 | const resolutionsEnabled = computeResolutionsToTranscode(videoFileResolution, 'vod') | 243 | const resolutionsEnabled = computeResolutionsToTranscode(videoFileResolution, 'vod') |
226 | const resolutionCreated: number[] = [] | 244 | const resolutionCreated: number[] = [] |
@@ -234,7 +252,8 @@ async function createLowerResolutionsJobs ( | |||
234 | type: 'new-resolution-to-webtorrent', | 252 | type: 'new-resolution-to-webtorrent', |
235 | videoUUID: video.uuid, | 253 | videoUUID: video.uuid, |
236 | resolution, | 254 | resolution, |
237 | isPortraitMode | 255 | isPortraitMode, |
256 | isNewVideo | ||
238 | } | 257 | } |
239 | } | 258 | } |
240 | 259 | ||
@@ -245,7 +264,8 @@ async function createLowerResolutionsJobs ( | |||
245 | resolution, | 264 | resolution, |
246 | isPortraitMode, | 265 | isPortraitMode, |
247 | copyCodecs: false, | 266 | copyCodecs: false, |
248 | isMaxQuality: false | 267 | isMaxQuality: false, |
268 | isNewVideo | ||
249 | } | 269 | } |
250 | } | 270 | } |
251 | 271 | ||
@@ -257,7 +277,7 @@ async function createLowerResolutionsJobs ( | |||
257 | priority: await getTranscodingJobPriority(user) | 277 | priority: await getTranscodingJobPriority(user) |
258 | } | 278 | } |
259 | 279 | ||
260 | JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput }, jobOptions) | 280 | await addTranscodingJob(dataInput, jobOptions) |
261 | } | 281 | } |
262 | 282 | ||
263 | if (resolutionCreated.length === 0) { | 283 | if (resolutionCreated.length === 0) { |