+import { Job } from 'bull'
import * as ffmpeg from 'fluent-ffmpeg'
import { readFile, remove, writeFile } from 'fs-extra'
import { dirname, join } from 'path'
resolution: VideoResolution
isPortraitMode?: boolean
+
+ job?: Job
}
interface HLSTranscodeOptions extends BaseTranscodeOptions {
interface HLSFromTSTranscodeOptions extends BaseTranscodeOptions {
type: 'hls-from-ts'
+ isAAC: boolean
+
hlsPlaylist: {
videoFilename: string
}
command = await builders[options.type](command, options)
- await runCommand(command)
+ await runCommand(command, options.job)
await fixHLSPlaylistIfNeeded(options)
}
const input = rtmpUrl
const command = getFFmpeg(input, 'live')
- command.inputOption('-fflags nobuffer')
const varStreamMap: string[] = []
])
command.outputOption('-preset superfast')
+ command.outputOption('-sc_threshold 0')
addDefaultEncoderGlobalParams({ command })
function getLiveMuxingCommand (rtmpUrl: string, outPath: string) {
const command = getFFmpeg(rtmpUrl, 'live')
- command.inputOption('-fflags nobuffer')
command.outputOption('-c:v copy')
command.outputOption('-c:a copy')
return base
}
-// ---------------------------------------------------------------------------
-
-export {
- getLiveTranscodingCommand,
- getLiveMuxingCommand,
- buildStreamSuffix,
- convertWebPToJPG,
- processGIF,
- generateImageFromVideoFile,
- TranscodeOptions,
- TranscodeOptionsType,
- transcode
-}
-
-// ---------------------------------------------------------------------------
-
// ---------------------------------------------------------------------------
// Default options
// ---------------------------------------------------------------------------
function addDefaultLiveHLSParams (command: ffmpeg.FfmpegCommand, outPath: string) {
command.outputOption('-hls_time ' + VIDEO_LIVE.SEGMENT_TIME_SECONDS)
command.outputOption('-hls_list_size ' + VIDEO_LIVE.SEGMENTS_LIST_SIZE)
- command.outputOption('-hls_flags delete_segments')
+ command.outputOption('-hls_flags delete_segments+independent_segments')
command.outputOption(`-hls_segment_filename ${join(outPath, '%v-%06d.ts')}`)
command.outputOption('-master_pl_name master.m3u8')
command.outputOption(`-f hls`)
async function buildHLSVODFromTSCommand (command: ffmpeg.FfmpegCommand, options: HLSFromTSTranscodeOptions) {
const videoPath = getHLSVideoPath(options)
- command.inputOption('-safe 0')
- command.inputOption('-f concat')
+ command.outputOption('-c copy')
- command.outputOption('-c:v copy')
- command.audioFilter('aresample=async=1:first_pts=0')
+ 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)
function getFFmpeg (input: string, type: 'live' | 'vod') {
// We set cwd explicitly because ffmpeg appears to create temporary files when trancoding which fails in read-only file systems
- const command = ffmpeg(input, { niceness: FFMPEG_NICE.TRANSCODING, cwd: CONFIG.STORAGE.TMP_DIR })
+ const command = ffmpeg(input, {
+ niceness: type === 'live' ? FFMPEG_NICE.LIVE : FFMPEG_NICE.VOD,
+ cwd: CONFIG.STORAGE.TMP_DIR
+ })
const threads = type === 'live'
? CONFIG.LIVE.TRANSCODING.THREADS
return command
}
-async function runCommand (command: ffmpeg.FfmpegCommand, onEnd?: Function) {
+async function runCommand (command: ffmpeg.FfmpegCommand, job?: Job) {
return new Promise<void>((res, rej) => {
command.on('error', (err, stdout, stderr) => {
- if (onEnd) onEnd()
-
logger.error('Error in transcoding job.', { stdout, stderr })
rej(err)
})
- command.on('end', () => {
- if (onEnd) onEnd()
+ command.on('end', (stdout, stderr) => {
+ logger.debug('FFmpeg command ended.', { stdout, stderr })
res()
})
+ if (job) {
+ command.on('progress', progress => {
+ if (!progress.percent) return
+
+ job.progress(Math.round(progress.percent))
+ .catch(err => logger.warn('Cannot set ffmpeg job progress.', { err }))
+ })
+ }
+
command.run()
})
}
+
+// ---------------------------------------------------------------------------
+
+export {
+ getLiveTranscodingCommand,
+ getLiveMuxingCommand,
+ buildStreamSuffix,
+ convertWebPToJPG,
+ processGIF,
+ generateImageFromVideoFile,
+ TranscodeOptions,
+ TranscodeOptionsType,
+ transcode,
+ runCommand,
+
+ // builders
+ buildx264VODCommand
+}