// Put in the order we want to proceed jobs
const resolutions = [
+ VideoResolution.H_NOVIDEO,
VideoResolution.H_480P,
VideoResolution.H_360P,
VideoResolution.H_720P,
return resolutionsEnabled
}
-async function getVideoFileSize (path: string) {
+async function getVideoStreamSize (path: string) {
const videoStream = await getVideoStreamFromFile(path)
- return {
- width: videoStream.width,
- height: videoStream.height
+ return videoStream === null
+ ? { width: 0, height: 0 }
+ : { width: videoStream.width, height: videoStream.height }
+}
+
+async function getVideoStreamCodec (path: string) {
+ const videoStream = await getVideoStreamFromFile(path)
+
+ if (!videoStream) return ''
+
+ const videoCodec = videoStream.codec_tag_string
+
+ const baseProfileMatrix = {
+ 'High': '6400',
+ 'Main': '4D40',
+ 'Baseline': '42E0'
}
+
+ let baseProfile = baseProfileMatrix[videoStream.profile]
+ if (!baseProfile) {
+ logger.warn('Cannot get video profile codec of %s.', path, { videoStream })
+ baseProfile = baseProfileMatrix['High'] // Fallback
+ }
+
+ const level = videoStream.level.toString(16)
+
+ return `${videoCodec}.${baseProfile}${level}`
+}
+
+async function getAudioStreamCodec (path: string) {
+ const { audioStream } = await audio.get(path)
+
+ if (!audioStream) return ''
+
+ const audioCodec = audioStream.codec_name
+ if (audioCodec === 'aac') return 'mp4a.40.2'
+
+ logger.warn('Cannot get audio codec of %s.', path, { audioStream })
+
+ return 'mp4a.40.2' // Fallback
}
async function getVideoFileResolution (path: string) {
- const size = await getVideoFileSize(path)
+ const size = await getVideoStreamSize(path)
return {
videoFileResolution: Math.min(size.height, size.width),
async function getVideoFileFPS (path: string) {
const videoStream = await getVideoStreamFromFile(path)
+ if (videoStream === null) return 0
for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) {
- const valuesText: string = videoStream[key]
+ const valuesText: string = videoStream[ key ]
if (!valuesText) continue
const [ frames, seconds ] = valuesText.split('/')
}
}
-type TranscodeOptionsType = 'hls' | 'quick-transcode' | 'video' | 'merge-audio'
+type TranscodeOptionsType = 'hls' | 'quick-transcode' | 'video' | 'merge-audio' | 'only-audio'
interface BaseTranscodeOptions {
type: TranscodeOptionsType
audioPath: string
}
-type TranscodeOptions = HLSTranscodeOptions | VideoTranscodeOptions | MergeAudioTranscodeOptions | QuickTranscodeOptions
+interface OnlyAudioTranscodeOptions extends BaseTranscodeOptions {
+ type: 'only-audio'
+}
+
+type TranscodeOptions = HLSTranscodeOptions
+ | VideoTranscodeOptions
+ | MergeAudioTranscodeOptions
+ | OnlyAudioTranscodeOptions
+ | QuickTranscodeOptions
function transcode (options: TranscodeOptions) {
return new Promise<void>(async (res, rej) => {
command = await buildHLSCommand(command, options)
} else if (options.type === 'merge-audio') {
command = await buildAudioMergeCommand(command, options)
+ } else if (options.type === 'only-audio') {
+ command = await buildOnlyAudioCommand(command, options)
} else {
command = await buildx264Command(command, options)
}
const resolution = await getVideoFileResolution(path)
// check video params
+ if (videoStream == null) return false
if (videoStream[ 'codec_name' ] !== 'h264') return false
if (videoStream[ 'pix_fmt' ] !== 'yuv420p') return false
if (fps < VIDEO_TRANSCODING_FPS.MIN || fps > VIDEO_TRANSCODING_FPS.MAX) return false
if (bitRate > getMaxBitrate(resolution.videoFileResolution, fps, VIDEO_TRANSCODING_FPS)) return false
- // check audio params (if audio stream exists)
+ // check audio params (if audio stream exists)
if (parsedAudio.audioStream) {
if (parsedAudio.audioStream[ 'codec_name' ] !== 'aac') return false
// ---------------------------------------------------------------------------
export {
- getVideoFileSize,
+ getVideoStreamCodec,
+ getAudioStreamCodec,
+ getVideoStreamSize,
getVideoFileResolution,
getDurationFromVideoFile,
generateImageFromVideoFile,
return command
}
+async function buildOnlyAudioCommand (command: ffmpeg.FfmpegCommand, options: OnlyAudioTranscodeOptions) {
+ command = await presetOnlyAudio(command)
+
+ return command
+}
+
async function buildQuickTranscodeCommand (command: ffmpeg.FfmpegCommand) {
command = await presetCopy(command)
if (err) return rej(err)
const videoStream = metadata.streams.find(s => s.codec_type === 'video')
- if (!videoStream) return rej(new Error('Cannot find video stream of ' + path))
-
- return res(videoStream)
+ return res(videoStream || null)
})
})
}
* A toolbox to play with audio
*/
namespace audio {
- export const get = (option: string) => {
+ export const get = (videoPath: string) => {
// without position, ffprobe considers the last input only
// we make it consider the first input only
// if you pass a file path to pos, then ffprobe acts on that file directly
if (err) return rej(err)
if ('streams' in data) {
- const audioStream = data.streams.find(stream => stream['codec_type'] === 'audio')
+ const audioStream = data.streams.find(stream => stream[ 'codec_type' ] === 'audio')
if (audioStream) {
return res({
absolutePath: data.format.filename,
return res({ absolutePath: data.format.filename })
}
- return ffmpeg.ffprobe(option, parseFfprobe)
+ return ffmpeg.ffprobe(videoPath, parseFfprobe)
})
}
let localCommand = command
.format('mp4')
.videoCodec('libx264')
- .outputOption('-level 3.1') // 3.1 is the minimal ressource allocation for our highest supported resolution
- .outputOption('-b_strategy 1') // NOTE: b-strategy 1 - heuristic algorythm, 16 is optimal B-frames for it
+ .outputOption('-level 3.1') // 3.1 is the minimal resource allocation for our highest supported resolution
+ .outputOption('-b_strategy 1') // NOTE: b-strategy 1 - heuristic algorithm, 16 is optimal B-frames for it
.outputOption('-bf 16') // NOTE: Why 16: https://github.com/Chocobozzz/PeerTube/pull/774. b-strategy 2 -> B-frames<16
.outputOption('-pix_fmt yuv420p') // allows import of source material with incompatible pixel formats (e.g. MJPEG video)
.outputOption('-map_metadata -1') // strip all metadata
.videoCodec('copy')
.audioCodec('copy')
}
+
+async function presetOnlyAudio (command: ffmpeg.FfmpegCommand): Promise<ffmpeg.FfmpegCommand> {
+ return command
+ .format('mp4')
+ .audioCodec('copy')
+ .noVideo()
+}