From 0c9668f77901e7540e2c7045eb0f2974a4842a69 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 21 Apr 2023 14:55:10 +0200 Subject: Implement remote runner jobs in server Move ffmpeg functions to @shared --- server/helpers/ffmpeg/ffmpeg-vod.ts | 267 ------------------------------------ 1 file changed, 267 deletions(-) delete mode 100644 server/helpers/ffmpeg/ffmpeg-vod.ts (limited to 'server/helpers/ffmpeg/ffmpeg-vod.ts') diff --git a/server/helpers/ffmpeg/ffmpeg-vod.ts b/server/helpers/ffmpeg/ffmpeg-vod.ts deleted file mode 100644 index d84703eb9..000000000 --- a/server/helpers/ffmpeg/ffmpeg-vod.ts +++ /dev/null @@ -1,267 +0,0 @@ -import { MutexInterface } from 'async-mutex' -import { Job } from 'bullmq' -import { FfmpegCommand } from 'fluent-ffmpeg' -import { readFile, writeFile } from 'fs-extra' -import { dirname } from 'path' -import { VIDEO_TRANSCODING_FPS } from '@server/initializers/constants' -import { pick } from '@shared/core-utils' -import { AvailableEncoders, VideoResolution } from '@shared/models' -import { logger, loggerTagsFactory } from '../logger' -import { getFFmpeg, runCommand } from './ffmpeg-commons' -import { presetCopy, presetOnlyAudio, presetVOD } from './ffmpeg-presets' -import { computeFPS, ffprobePromise, getVideoStreamDimensionsInfo, getVideoStreamFPS } from './ffprobe-utils' - -const lTags = loggerTagsFactory('ffmpeg') - -// --------------------------------------------------------------------------- - -type TranscodeVODOptionsType = 'hls' | 'hls-from-ts' | 'quick-transcode' | 'video' | 'merge-audio' | 'only-audio' - -interface BaseTranscodeVODOptions { - type: TranscodeVODOptionsType - - inputPath: string - outputPath: string - - // Will be released after the ffmpeg started - // To prevent a bug where the input file does not exist anymore when running ffmpeg - inputFileMutexReleaser: MutexInterface.Releaser - - availableEncoders: AvailableEncoders - profile: string - - resolution: number - - job?: Job -} - -interface HLSTranscodeOptions extends BaseTranscodeVODOptions { - type: 'hls' - copyCodecs: boolean - hlsPlaylist: { - videoFilename: string - } -} - -interface HLSFromTSTranscodeOptions extends BaseTranscodeVODOptions { - type: 'hls-from-ts' - - isAAC: boolean - - hlsPlaylist: { - videoFilename: string - } -} - -interface QuickTranscodeOptions extends BaseTranscodeVODOptions { - type: 'quick-transcode' -} - -interface VideoTranscodeOptions extends BaseTranscodeVODOptions { - type: 'video' -} - -interface MergeAudioTranscodeOptions extends BaseTranscodeVODOptions { - type: 'merge-audio' - audioPath: string -} - -interface OnlyAudioTranscodeOptions extends BaseTranscodeVODOptions { - type: 'only-audio' -} - -type TranscodeVODOptions = - HLSTranscodeOptions - | HLSFromTSTranscodeOptions - | VideoTranscodeOptions - | MergeAudioTranscodeOptions - | OnlyAudioTranscodeOptions - | QuickTranscodeOptions - -// --------------------------------------------------------------------------- - -const builders: { - [ type in TranscodeVODOptionsType ]: (c: FfmpegCommand, o?: TranscodeVODOptions) => Promise | FfmpegCommand -} = { - 'quick-transcode': buildQuickTranscodeCommand, - 'hls': buildHLSVODCommand, - 'hls-from-ts': buildHLSVODFromTSCommand, - 'merge-audio': buildAudioMergeCommand, - 'only-audio': buildOnlyAudioCommand, - 'video': buildVODCommand -} - -async function transcodeVOD (options: TranscodeVODOptions) { - logger.debug('Will run transcode.', { options, ...lTags() }) - - let command = getFFmpeg(options.inputPath, 'vod') - .output(options.outputPath) - - command = await builders[options.type](command, options) - - command.on('start', () => { - setTimeout(() => { - options.inputFileMutexReleaser() - }, 1000) - }) - - await runCommand({ command, job: options.job }) - - await fixHLSPlaylistIfNeeded(options) -} - -// --------------------------------------------------------------------------- - -export { - transcodeVOD, - - buildVODCommand, - - TranscodeVODOptions, - TranscodeVODOptionsType -} - -// --------------------------------------------------------------------------- - -async function buildVODCommand (command: FfmpegCommand, options: TranscodeVODOptions) { - const probe = await ffprobePromise(options.inputPath) - - let fps = await getVideoStreamFPS(options.inputPath, probe) - fps = computeFPS(fps, options.resolution) - - let scaleFilterValue: string - - if (options.resolution !== undefined) { - const videoStreamInfo = await getVideoStreamDimensionsInfo(options.inputPath, probe) - - scaleFilterValue = videoStreamInfo?.isPortraitMode === true - ? `w=${options.resolution}:h=-2` - : `w=-2:h=${options.resolution}` - } - - command = await presetVOD({ - ...pick(options, [ 'resolution', 'availableEncoders', 'profile' ]), - - command, - input: options.inputPath, - canCopyAudio: true, - canCopyVideo: true, - fps, - scaleFilterValue - }) - - return command -} - -function buildQuickTranscodeCommand (command: FfmpegCommand) { - command = presetCopy(command) - - command = command.outputOption('-map_metadata -1') // strip all metadata - .outputOption('-movflags faststart') - - return command -} - -// --------------------------------------------------------------------------- -// Audio transcoding -// --------------------------------------------------------------------------- - -async function buildAudioMergeCommand (command: FfmpegCommand, options: MergeAudioTranscodeOptions) { - command = command.loop(undefined) - - const scaleFilterValue = getMergeAudioScaleFilterValue() - command = await presetVOD({ - ...pick(options, [ 'resolution', 'availableEncoders', 'profile' ]), - - command, - input: options.audioPath, - canCopyAudio: true, - canCopyVideo: true, - fps: VIDEO_TRANSCODING_FPS.AUDIO_MERGE, - scaleFilterValue - }) - - command.outputOption('-preset:v veryfast') - - command = command.input(options.audioPath) - .outputOption('-tune stillimage') - .outputOption('-shortest') - - return command -} - -function buildOnlyAudioCommand (command: FfmpegCommand, _options: OnlyAudioTranscodeOptions) { - command = presetOnlyAudio(command) - - return command -} - -// --------------------------------------------------------------------------- -// HLS transcoding -// --------------------------------------------------------------------------- - -async function buildHLSVODCommand (command: FfmpegCommand, options: HLSTranscodeOptions) { - const videoPath = getHLSVideoPath(options) - - if (options.copyCodecs) command = presetCopy(command) - else if (options.resolution === VideoResolution.H_NOVIDEO) command = presetOnlyAudio(command) - else command = await buildVODCommand(command, options) - - addCommonHLSVODCommandOptions(command, videoPath) - - return command -} - -function buildHLSVODFromTSCommand (command: FfmpegCommand, options: HLSFromTSTranscodeOptions) { - const videoPath = getHLSVideoPath(options) - - command.outputOption('-c copy') - - if (options.isAAC) { - // Required for example when copying an AAC stream from an MPEG-TS - // Since it's a bitstream filter, we don't need to reencode the audio - command.outputOption('-bsf:a aac_adtstoasc') - } - - addCommonHLSVODCommandOptions(command, videoPath) - - return command -} - -function addCommonHLSVODCommandOptions (command: FfmpegCommand, outputPath: string) { - return command.outputOption('-hls_time 4') - .outputOption('-hls_list_size 0') - .outputOption('-hls_playlist_type vod') - .outputOption('-hls_segment_filename ' + outputPath) - .outputOption('-hls_segment_type fmp4') - .outputOption('-f hls') - .outputOption('-hls_flags single_file') -} - -async function fixHLSPlaylistIfNeeded (options: TranscodeVODOptions) { - if (options.type !== 'hls' && options.type !== 'hls-from-ts') return - - const fileContent = await readFile(options.outputPath) - - const videoFileName = options.hlsPlaylist.videoFilename - const videoFilePath = getHLSVideoPath(options) - - // Fix wrong mapping with some ffmpeg versions - const newContent = fileContent.toString() - .replace(`#EXT-X-MAP:URI="${videoFilePath}",`, `#EXT-X-MAP:URI="${videoFileName}",`) - - await writeFile(options.outputPath, newContent) -} - -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- - -function getHLSVideoPath (options: HLSTranscodeOptions | HLSFromTSTranscodeOptions) { - return `${dirname(options.outputPath)}/${options.hlsPlaylist.videoFilename}` -} - -// Avoid "height not divisible by 2" error -function getMergeAudioScaleFilterValue () { - return 'trunc(iw/2)*2:trunc(ih/2)*2' -} -- cgit v1.2.3