X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fhelpers%2Fffprobe-utils.ts;h=595112bce602b321a9a1c19ced49c5d81ad9d183;hb=75b7117f078461d2507572ba9da6527894e1b734;hp=ef2aa3f890e0a36856ca9aa87b300e30f0a513cd;hpb=20213fbd2a366dffc35aa7dddad71323893f8d62;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/helpers/ffprobe-utils.ts b/server/helpers/ffprobe-utils.ts index ef2aa3f89..595112bce 100644 --- a/server/helpers/ffprobe-utils.ts +++ b/server/helpers/ffprobe-utils.ts @@ -1,8 +1,22 @@ -import * as ffmpeg from 'fluent-ffmpeg' -import { getMaxBitrate, VideoFileMetadata, VideoResolution } from '../../shared/models/videos' +import { FfprobeData } from 'fluent-ffmpeg' +import { getMaxBitrate } from '@shared/core-utils' +import { VideoResolution, VideoTranscodingFPS } from '../../shared/models/videos' import { CONFIG } from '../initializers/config' import { VIDEO_TRANSCODING_FPS } from '../initializers/constants' import { logger } from './logger' +import { + canDoQuickAudioTranscode, + ffprobePromise, + getDurationFromVideoFile, + getAudioStream, + getMaxAudioBitrate, + getMetadataFromFile, + getVideoFileBitrate, + getVideoFileFPS, + getVideoFileResolution, + getVideoStreamFromFile, + getVideoStreamSize +} from '@shared/extra-utils/ffprobe' /** * @@ -10,79 +24,6 @@ import { logger } from './logger' * */ -function ffprobePromise (path: string) { - return new Promise((res, rej) => { - ffmpeg.ffprobe(path, (err, data) => { - if (err) return rej(err) - - return res(data) - }) - }) -} - -async function getAudioStream (videoPath: string, existingProbe?: ffmpeg.FfprobeData) { - // 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 - const data = existingProbe || await ffprobePromise(videoPath) - - if (Array.isArray(data.streams)) { - const audioStream = data.streams.find(stream => stream['codec_type'] === 'audio') - - if (audioStream) { - return { - absolutePath: data.format.filename, - audioStream, - bitrate: parseInt(audioStream['bit_rate'] + '', 10) - } - } - } - - return { absolutePath: data.format.filename } -} - -function getMaxAudioBitrate (type: 'aac' | 'mp3' | string, bitrate: number) { - const maxKBitrate = 384 - const kToBits = (kbits: number) => kbits * 1000 - - // If we did not manage to get the bitrate, use an average value - if (!bitrate) return 256 - - if (type === 'aac') { - switch (true) { - case bitrate > kToBits(maxKBitrate): - return maxKBitrate - - default: - return -1 // we interpret it as a signal to copy the audio stream as is - } - } - - /* - a 192kbit/sec mp3 doesn't hold as much information as a 192kbit/sec aac. - That's why, when using aac, we can go to lower kbit/sec. The equivalences - made here are not made to be accurate, especially with good mp3 encoders. - */ - switch (true) { - case bitrate <= kToBits(192): - return 128 - - case bitrate <= kToBits(384): - return 256 - - default: - return maxKBitrate - } -} - -async function getVideoStreamSize (path: string, existingProbe?: ffmpeg.FfprobeData) { - const videoStream = await getVideoStreamFromFile(path, existingProbe) - - return videoStream === null - ? { width: 0, height: 0 } - : { width: videoStream.width, height: videoStream.height } -} - async function getVideoStreamCodec (path: string) { const videoStream = await getVideoStreamFromFile(path) @@ -126,7 +67,7 @@ async function getVideoStreamCodec (path: string) { return `${videoCodec}.${baseProfile}${level}` } -async function getAudioStreamCodec (path: string, existingProbe?: ffmpeg.FfprobeData) { +async function getAudioStreamCodec (path: string, existingProbe?: FfprobeData) { const { audioStream } = await getAudioStream(path, existingProbe) if (!audioStream) return '' @@ -142,58 +83,7 @@ async function getAudioStreamCodec (path: string, existingProbe?: ffmpeg.Ffprobe return 'mp4a.40.2' // Fallback } -async function getVideoFileResolution (path: string, existingProbe?: ffmpeg.FfprobeData) { - const size = await getVideoStreamSize(path, existingProbe) - - return { - videoFileResolution: Math.min(size.height, size.width), - isPortraitMode: size.height > size.width - } -} - -async function getVideoFileFPS (path: string, existingProbe?: ffmpeg.FfprobeData) { - const videoStream = await getVideoStreamFromFile(path, existingProbe) - if (videoStream === null) return 0 - - for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) { - const valuesText: string = videoStream[key] - if (!valuesText) continue - - const [ frames, seconds ] = valuesText.split('/') - if (!frames || !seconds) continue - - const result = parseInt(frames, 10) / parseInt(seconds, 10) - if (result > 0) return Math.round(result) - } - - return 0 -} - -async function getMetadataFromFile (path: string, existingProbe?: ffmpeg.FfprobeData) { - const metadata = existingProbe || await ffprobePromise(path) - - return new VideoFileMetadata(metadata) -} - -async function getVideoFileBitrate (path: string, existingProbe?: ffmpeg.FfprobeData) { - const metadata = await getMetadataFromFile(path, existingProbe) - - return metadata.format.bit_rate as number -} - -async function getDurationFromVideoFile (path: string, existingProbe?: ffmpeg.FfprobeData) { - const metadata = await getMetadataFromFile(path, existingProbe) - - return Math.round(metadata.format.duration) -} - -async function getVideoStreamFromFile (path: string, existingProbe?: ffmpeg.FfprobeData) { - const metadata = await getMetadataFromFile(path, existingProbe) - - return metadata.streams.find(s => s.codec_type === 'video') || null -} - -function computeResolutionsToTranscode (videoFileResolution: number, type: 'vod' | 'live') { +function computeLowerResolutionsToTranscode (videoFileResolution: number, type: 'vod' | 'live') { const configResolutions = type === 'vod' ? CONFIG.TRANSCODING.RESOLUTIONS : CONFIG.LIVE.TRANSCODING.RESOLUTIONS @@ -201,12 +91,13 @@ function computeResolutionsToTranscode (videoFileResolution: number, type: 'vod' const resolutionsEnabled: number[] = [] // Put in the order we want to proceed jobs - const resolutions = [ + const resolutions: VideoResolution[] = [ VideoResolution.H_NOVIDEO, VideoResolution.H_480P, VideoResolution.H_360P, VideoResolution.H_720P, VideoResolution.H_240P, + VideoResolution.H_144P, VideoResolution.H_1080P, VideoResolution.H_1440P, VideoResolution.H_4K @@ -230,11 +121,11 @@ async function canDoQuickTranscode (path: string): Promise { await canDoQuickAudioTranscode(path, probe) } -async function canDoQuickVideoTranscode (path: string, probe?: ffmpeg.FfprobeData): Promise { +async function canDoQuickVideoTranscode (path: string, probe?: FfprobeData): Promise { const videoStream = await getVideoStreamFromFile(path, probe) const fps = await getVideoFileFPS(path, probe) const bitRate = await getVideoFileBitrate(path, probe) - const resolution = await getVideoFileResolution(path, probe) + const resolutionData = await getVideoFileResolution(path, probe) // If ffprobe did not manage to guess the bitrate if (!bitRate) return false @@ -244,32 +135,12 @@ async function canDoQuickVideoTranscode (path: string, probe?: ffmpeg.FfprobeDat 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 - - return true -} - -async function canDoQuickAudioTranscode (path: string, probe?: ffmpeg.FfprobeData): Promise { - const parsedAudio = await getAudioStream(path, probe) - - if (!parsedAudio.audioStream) return true - - if (parsedAudio.audioStream['codec_name'] !== 'aac') return false - - const audioBitrate = parsedAudio.bitrate - if (!audioBitrate) return false - - const maxAudioBitrate = getMaxAudioBitrate('aac', audioBitrate) - if (maxAudioBitrate !== -1 && audioBitrate > maxAudioBitrate) return false - - const channelLayout = parsedAudio.audioStream['channel_layout'] - // Causes playback issues with Chrome - if (!channelLayout || channelLayout === 'unknown') return false + if (bitRate > getMaxBitrate({ ...resolutionData, fps })) return false return true } -function getClosestFramerateStandard (fps: number, type: 'HD_STANDARD' | 'STANDARD'): number { +function getClosestFramerateStandard > (fps: number, type: K) { return VIDEO_TRANSCODING_FPS[type].slice(0) .sort((a, b) => fps % a - fps % b)[0] } @@ -289,7 +160,10 @@ function computeFPS (fpsArg: number, resolution: VideoResolution) { // Hard FPS limits if (fps > VIDEO_TRANSCODING_FPS.MAX) fps = getClosestFramerateStandard(fps, 'HD_STANDARD') - else if (fps < VIDEO_TRANSCODING_FPS.MIN) fps = VIDEO_TRANSCODING_FPS.MIN + + if (fps < VIDEO_TRANSCODING_FPS.MIN) { + throw new Error(`Cannot compute FPS because ${fps} is lower than our minimum value ${VIDEO_TRANSCODING_FPS.MIN}`) + } return fps } @@ -310,7 +184,7 @@ export { getVideoFileFPS, ffprobePromise, getClosestFramerateStandard, - computeResolutionsToTranscode, + computeLowerResolutionsToTranscode, getVideoFileBitrate, canDoQuickTranscode, canDoQuickVideoTranscode,