X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Flib%2Fjob-queue%2Fhandlers%2Fvideo-transcoding.ts;h=95ee6b384d9902f50a4ca39738e78c3a6b2dfe1f;hb=e722fb5923ddf11d72e48cec9788abc64327c22f;hp=5a93c4ed1aeaa0347c105953710d0bc8770e1f8f;hpb=5a298a5a3d000f94c944d3711d4b0226a888df04;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts index 5a93c4ed1..95ee6b384 100644 --- a/server/lib/job-queue/handlers/video-transcoding.ts +++ b/server/lib/job-queue/handlers/video-transcoding.ts @@ -1,20 +1,22 @@ -import * as Bull from 'bull' -import { TranscodeOptionsType } from '@server/helpers/ffmpeg-utils' +import { Job } from 'bull' +import { TranscodeVODOptionsType } from '@server/helpers/ffmpeg' import { addTranscodingJob, getTranscodingJobPriority } from '@server/lib/video' import { VideoPathManager } from '@server/lib/video-path-manager' -import { moveToNextState } from '@server/lib/video-state' +import { moveToFailedTranscodingState, moveToNextState } from '@server/lib/video-state' import { UserModel } from '@server/models/user/user' import { VideoJobInfoModel } from '@server/models/video/video-job-info' import { MUser, MUserId, MVideo, MVideoFullLight, MVideoWithFile } from '@server/types/models' +import { pick } from '@shared/core-utils' import { HLSTranscodingPayload, MergeAudioTranscodingPayload, - NewResolutionTranscodingPayload, + NewWebTorrentResolutionTranscodingPayload, OptimizeTranscodingPayload, + VideoResolution, VideoTranscodingPayload -} from '../../../../shared' +} from '@shared/models' import { retryTransactionWrapper } from '../../../helpers/database-utils' -import { computeResolutionsToTranscode } from '../../../helpers/ffprobe-utils' +import { computeLowerResolutionsToTranscode } from '../../../helpers/ffmpeg' import { logger, loggerTagsFactory } from '../../../helpers/logger' import { CONFIG } from '../../../initializers/config' import { VideoModel } from '../../../models/video/video' @@ -23,9 +25,9 @@ import { mergeAudioVideofile, optimizeOriginalVideofile, transcodeNewWebTorrentResolution -} from '../../transcoding/video-transcoding' +} from '../../transcoding/transcoding' -type HandlerFunction = (job: Bull.Job, payload: VideoTranscodingPayload, video: MVideoFullLight, user: MUser) => Promise +type HandlerFunction = (job: Job, payload: VideoTranscodingPayload, video: MVideoFullLight, user: MUser) => Promise const handlers: { [ id in VideoTranscodingPayload['type'] ]: HandlerFunction } = { 'new-resolution-to-hls': handleHLSJob, @@ -36,7 +38,7 @@ const handlers: { [ id in VideoTranscodingPayload['type'] ]: HandlerFunction } = const lTags = loggerTagsFactory('transcoding') -async function processVideoTranscoding (job: Bull.Job) { +async function processVideoTranscoding (job: Job) { const payload = job.data as VideoTranscodingPayload logger.info('Processing transcoding job %d.', job.id, lTags(payload.videoUUID)) @@ -52,19 +54,36 @@ async function processVideoTranscoding (job: Bull.Job) { const handler = handlers[payload.type] if (!handler) { + await moveToFailedTranscodingState(video) + await VideoJobInfoModel.decrease(video.uuid, 'pendingTranscode') + throw new Error('Cannot find transcoding handler for ' + payload.type) } - await handler(job, payload, video, user) + try { + await handler(job, payload, video, user) + } catch (error) { + await moveToFailedTranscodingState(video) + + await VideoJobInfoModel.decrease(video.uuid, 'pendingTranscode') + + throw error + } return video } +// --------------------------------------------------------------------------- + +export { + processVideoTranscoding +} + // --------------------------------------------------------------------------- // Job handlers // --------------------------------------------------------------------------- -async function handleHLSJob (job: Bull.Job, payload: HLSTranscodingPayload, video: MVideoFullLight, user: MUser) { +async function handleHLSJob (job: Job, payload: HLSTranscodingPayload, video: MVideoFullLight, user: MUser) { logger.info('Handling HLS transcoding job for %s.', video.uuid, lTags(video.uuid)) const videoFileInput = payload.copyCodecs @@ -73,7 +92,7 @@ async function handleHLSJob (job: Bull.Job, payload: HLSTranscodingPayload, vide const videoOrStreamingPlaylist = videoFileInput.getVideoOrStreamingPlaylist() - await VideoPathManager.Instance.makeAvailableVideoFile(videoOrStreamingPlaylist, videoFileInput, videoInputPath => { + await VideoPathManager.Instance.makeAvailableVideoFile(videoFileInput.withVideoOrPlaylist(videoOrStreamingPlaylist), videoInputPath => { return generateHlsPlaylistResolution({ video, videoInputPath, @@ -86,12 +105,12 @@ async function handleHLSJob (job: Bull.Job, payload: HLSTranscodingPayload, vide logger.info('HLS transcoding job for %s ended.', video.uuid, lTags(video.uuid)) - await retryTransactionWrapper(onHlsPlaylistGeneration, video, user, payload) + await onHlsPlaylistGeneration(video, user, payload) } async function handleNewWebTorrentResolutionJob ( - job: Bull.Job, - payload: NewResolutionTranscodingPayload, + job: Job, + payload: NewWebTorrentResolutionTranscodingPayload, video: MVideoFullLight, user: MUserId ) { @@ -101,36 +120,36 @@ async function handleNewWebTorrentResolutionJob ( logger.info('WebTorrent transcoding job for %s ended.', video.uuid, lTags(video.uuid)) - await retryTransactionWrapper(onNewWebTorrentFileResolution, video, user, payload) + await onNewWebTorrentFileResolution(video, user, payload) } -async function handleWebTorrentMergeAudioJob (job: Bull.Job, payload: MergeAudioTranscodingPayload, video: MVideoFullLight, user: MUserId) { +async function handleWebTorrentMergeAudioJob (job: Job, payload: MergeAudioTranscodingPayload, video: MVideoFullLight, user: MUserId) { logger.info('Handling merge audio transcoding job for %s.', video.uuid, lTags(video.uuid)) await mergeAudioVideofile(video, payload.resolution, job) logger.info('Merge audio transcoding job for %s ended.', video.uuid, lTags(video.uuid)) - await retryTransactionWrapper(onVideoFileOptimizer, video, payload, 'video', user) + await onVideoFirstWebTorrentTranscoding(video, payload, 'video', user) } -async function handleWebTorrentOptimizeJob (job: Bull.Job, payload: OptimizeTranscodingPayload, video: MVideoFullLight, user: MUserId) { +async function handleWebTorrentOptimizeJob (job: Job, payload: OptimizeTranscodingPayload, video: MVideoFullLight, user: MUserId) { logger.info('Handling optimize transcoding job for %s.', video.uuid, lTags(video.uuid)) const { transcodeType } = await optimizeOriginalVideofile(video, video.getMaxQualityFile(), job) logger.info('Optimize transcoding job for %s ended.', video.uuid, lTags(video.uuid)) - await retryTransactionWrapper(onVideoFileOptimizer, video, payload, transcodeType, user) + await onVideoFirstWebTorrentTranscoding(video, payload, transcodeType, user) } // --------------------------------------------------------------------------- async function onHlsPlaylistGeneration (video: MVideoFullLight, user: MUser, payload: HLSTranscodingPayload) { - if (payload.isMaxQuality && CONFIG.TRANSCODING.WEBTORRENT.ENABLED === false) { + if (payload.isMaxQuality && payload.autoDeleteWebTorrentIfNeeded && CONFIG.TRANSCODING.WEBTORRENT.ENABLED === false) { // Remove webtorrent files if not enabled for (const file of video.VideoFiles) { - await video.removeFileAndTorrent(file) + await video.removeWebTorrentFileAndTorrent(file) await file.destroy() } @@ -142,22 +161,23 @@ async function onHlsPlaylistGeneration (video: MVideoFullLight, user: MUser, pay user, videoFileResolution: payload.resolution, isPortraitMode: payload.isPortraitMode, + hasAudio: payload.hasAudio, isNewVideo: payload.isNewVideo ?? true, type: 'hls' }) } await VideoJobInfoModel.decrease(video.uuid, 'pendingTranscode') - await moveToNextState(video, payload.isNewVideo) + await retryTransactionWrapper(moveToNextState, { video, isNewVideo: payload.isNewVideo }) } -async function onVideoFileOptimizer ( +async function onVideoFirstWebTorrentTranscoding ( videoArg: MVideoWithFile, payload: OptimizeTranscodingPayload | MergeAudioTranscodingPayload, - transcodeType: TranscodeOptionsType, + transcodeType: TranscodeVODOptionsType, user: MUserId ) { - const { resolution, isPortraitMode } = await videoArg.getMaxQualityResolution() + const { resolution, isPortraitMode, audioStream } = await videoArg.probeMaxQualityFile() // Maybe the video changed in database, refresh it const videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoArg.uuid) @@ -169,6 +189,7 @@ async function onVideoFileOptimizer ( ...payload, isPortraitMode, + hasAudio: !!audioStream, resolution: videoDatabase.getMaxQualityFile().resolution, // If we quick transcoded original file, force transcoding for HLS to avoid some weird playback issues copyCodecs: transcodeType !== 'quick-transcode', @@ -179,6 +200,7 @@ async function onVideoFileOptimizer ( video: videoDatabase, user, videoFileResolution: resolution, + hasAudio: !!audioStream, isPortraitMode, type: 'webtorrent', isNewVideo: payload.isNewVideo ?? true @@ -188,24 +210,30 @@ async function onVideoFileOptimizer ( // Move to next state if there are no other resolutions to generate if (!hasHls && !hasNewResolutions) { - await moveToNextState(videoDatabase, payload.isNewVideo) + await retryTransactionWrapper(moveToNextState, { video: videoDatabase, isNewVideo: payload.isNewVideo }) } } async function onNewWebTorrentFileResolution ( video: MVideo, user: MUserId, - payload: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload + payload: NewWebTorrentResolutionTranscodingPayload | MergeAudioTranscodingPayload ) { - await createHlsJobIfEnabled(user, { ...payload, copyCodecs: true, isMaxQuality: false }) + if (payload.createHLSIfNeeded) { + await createHlsJobIfEnabled(user, { hasAudio: true, copyCodecs: true, isMaxQuality: false, ...payload }) + } + await VideoJobInfoModel.decrease(video.uuid, 'pendingTranscode') - await moveToNextState(video, payload.isNewVideo) + await retryTransactionWrapper(moveToNextState, { video, isNewVideo: payload.isNewVideo }) } +// --------------------------------------------------------------------------- + async function createHlsJobIfEnabled (user: MUserId, payload: { videoUUID: string resolution: number + hasAudio: boolean isPortraitMode?: boolean copyCodecs: boolean isMaxQuality: boolean @@ -219,12 +247,9 @@ async function createHlsJobIfEnabled (user: MUserId, payload: { const hlsTranscodingPayload: HLSTranscodingPayload = { type: 'new-resolution-to-hls', - videoUUID: payload.videoUUID, - resolution: payload.resolution, - isPortraitMode: payload.isPortraitMode, - copyCodecs: payload.copyCodecs, - isMaxQuality: payload.isMaxQuality, - isNewVideo: payload.isNewVideo + autoDeleteWebTorrentIfNeeded: true, + + ...pick(payload, [ 'videoUUID', 'resolution', 'isPortraitMode', 'copyCodecs', 'isMaxQuality', 'isNewVideo', 'hasAudio' ]) } await addTranscodingJob(hlsTranscodingPayload, jobOptions) @@ -232,31 +257,24 @@ async function createHlsJobIfEnabled (user: MUserId, payload: { return true } -// --------------------------------------------------------------------------- - -export { - processVideoTranscoding, - createHlsJobIfEnabled, - onNewWebTorrentFileResolution -} - -// --------------------------------------------------------------------------- - async function createLowerResolutionsJobs (options: { video: MVideoFullLight user: MUserId videoFileResolution: number isPortraitMode: boolean + hasAudio: boolean isNewVideo: boolean type: 'hls' | 'webtorrent' }) { - const { video, user, videoFileResolution, isPortraitMode, isNewVideo, type } = options + const { video, user, videoFileResolution, isPortraitMode, isNewVideo, hasAudio, type } = options // Create transcoding jobs if there are enabled resolutions - const resolutionsEnabled = computeResolutionsToTranscode(videoFileResolution, 'vod') + const resolutionsEnabled = computeLowerResolutionsToTranscode(videoFileResolution, 'vod') const resolutionCreated: string[] = [] for (const resolution of resolutionsEnabled) { + if (resolution === VideoResolution.H_NOVIDEO && hasAudio === false) continue + let dataInput: VideoTranscodingPayload if (CONFIG.TRANSCODING.WEBTORRENT.ENABLED && type === 'webtorrent') { @@ -266,6 +284,8 @@ async function createLowerResolutionsJobs (options: { videoUUID: video.uuid, resolution, isPortraitMode, + hasAudio, + createHLSIfNeeded: true, isNewVideo } @@ -278,8 +298,10 @@ async function createLowerResolutionsJobs (options: { videoUUID: video.uuid, resolution, isPortraitMode, + hasAudio, copyCodecs: false, isMaxQuality: false, + autoDeleteWebTorrentIfNeeded: true, isNewVideo }