PeerTube/server/helpers/ffprobe-utils.ts

193 lines
5.6 KiB
TypeScript
Raw Normal View History

import { FfprobeData } from 'fluent-ffmpeg'
2021-08-06 13:35:25 +02:00
import { getMaxBitrate } from '@shared/core-utils'
import { VideoResolution, VideoTranscodingFPS } from '../../shared/models/videos'
2020-11-20 17:16:55 +01:00
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'
2020-11-20 17:16:55 +01:00
2020-11-24 16:29:39 +01:00
/**
*
* Helpers to run ffprobe and extract data from the JSON output
*
*/
2020-11-20 17:16:55 +01:00
async function getVideoStreamCodec (path: string) {
const videoStream = await getVideoStreamFromFile(path)
if (!videoStream) return ''
const videoCodec = videoStream.codec_tag_string
2021-01-29 13:57:17 +01:00
if (videoCodec === 'vp09') return 'vp09.00.50.08'
2021-04-21 09:08:14 +02:00
if (videoCodec === 'hev1') return 'hev1.1.6.L93.B0'
2021-01-29 13:57:17 +01:00
2020-11-20 17:16:55 +01:00
const baseProfileMatrix = {
2021-01-29 13:57:17 +01:00
avc1: {
High: '6400',
Main: '4D40',
Baseline: '42E0'
},
av01: {
High: '1',
Main: '0',
Professional: '2'
}
2020-11-20 17:16:55 +01:00
}
2021-01-29 13:57:17 +01:00
let baseProfile = baseProfileMatrix[videoCodec][videoStream.profile]
2020-11-20 17:16:55 +01:00
if (!baseProfile) {
logger.warn('Cannot get video profile codec of %s.', path, { videoStream })
2021-01-29 13:57:17 +01:00
baseProfile = baseProfileMatrix[videoCodec]['High'] // Fallback
}
if (videoCodec === 'av01') {
const level = videoStream.level
// Guess the tier indicator and bit depth
return `${videoCodec}.${baseProfile}.${level}M.08`
2020-11-20 17:16:55 +01:00
}
2021-01-29 13:57:17 +01:00
// Default, h264 codec
2020-11-20 17:16:55 +01:00
let level = videoStream.level.toString(16)
if (level.length === 1) level = `0${level}`
return `${videoCodec}.${baseProfile}${level}`
}
2021-08-27 14:32:44 +02:00
async function getAudioStreamCodec (path: string, existingProbe?: FfprobeData) {
2020-11-20 17:16:55 +01:00
const { audioStream } = await getAudioStream(path, existingProbe)
if (!audioStream) return ''
2021-01-29 13:57:17 +01:00
const audioCodecName = audioStream.codec_name
if (audioCodecName === 'opus') return 'opus'
if (audioCodecName === 'vorbis') return 'vorbis'
if (audioCodecName === 'aac') return 'mp4a.40.2'
2020-11-20 17:16:55 +01:00
logger.warn('Cannot get audio codec of %s.', path, { audioStream })
return 'mp4a.40.2' // Fallback
}
2021-11-18 14:35:08 +01:00
function computeLowerResolutionsToTranscode (videoFileResolution: number, type: 'vod' | 'live') {
2020-11-20 17:16:55 +01:00
const configResolutions = type === 'vod'
? CONFIG.TRANSCODING.RESOLUTIONS
: CONFIG.LIVE.TRANSCODING.RESOLUTIONS
const resolutionsEnabled: number[] = []
// Put in the order we want to proceed jobs
2021-11-18 14:35:08 +01:00
const resolutions: VideoResolution[] = [
2020-11-20 17:16:55 +01:00
VideoResolution.H_NOVIDEO,
VideoResolution.H_480P,
VideoResolution.H_360P,
VideoResolution.H_720P,
VideoResolution.H_240P,
VideoResolution.H_144P,
2020-11-20 17:16:55 +01:00
VideoResolution.H_1080P,
VideoResolution.H_1440P,
2020-11-20 17:16:55 +01:00
VideoResolution.H_4K
]
for (const resolution of resolutions) {
if (configResolutions[resolution + 'p'] === true && videoFileResolution > resolution) {
resolutionsEnabled.push(resolution)
}
}
return resolutionsEnabled
}
async function canDoQuickTranscode (path: string): Promise<boolean> {
if (CONFIG.TRANSCODING.PROFILE !== 'default') return false
2020-11-20 17:16:55 +01:00
const probe = await ffprobePromise(path)
2020-11-24 14:08:23 +01:00
return await canDoQuickVideoTranscode(path, probe) &&
await canDoQuickAudioTranscode(path, probe)
}
2021-08-27 14:32:44 +02:00
async function canDoQuickVideoTranscode (path: string, probe?: FfprobeData): Promise<boolean> {
2020-11-20 17:16:55 +01:00
const videoStream = await getVideoStreamFromFile(path, probe)
const fps = await getVideoFileFPS(path, probe)
const bitRate = await getVideoFileBitrate(path, probe)
2021-08-06 13:35:25 +02:00
const resolutionData = await getVideoFileResolution(path, probe)
2020-11-20 17:16:55 +01:00
2020-11-24 16:24:50 +01:00
// If ffprobe did not manage to guess the bitrate
if (!bitRate) return false
2020-11-20 17:16:55 +01:00
// 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
2021-08-06 13:35:25 +02:00
if (bitRate > getMaxBitrate({ ...resolutionData, fps })) return false
2020-11-20 17:16:55 +01:00
2020-11-24 14:08:23 +01:00
return true
}
2021-08-06 13:35:25 +02:00
function getClosestFramerateStandard <K extends keyof Pick<VideoTranscodingFPS, 'HD_STANDARD' | 'STANDARD'>> (fps: number, type: K) {
2020-11-20 17:16:55 +01:00
return VIDEO_TRANSCODING_FPS[type].slice(0)
.sort((a, b) => fps % a - fps % b)[0]
}
2020-11-26 11:29:50 +01:00
function computeFPS (fpsArg: number, resolution: VideoResolution) {
let fps = fpsArg
if (
// On small/medium resolutions, limit FPS
resolution !== undefined &&
resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN &&
fps > VIDEO_TRANSCODING_FPS.AVERAGE
) {
// Get closest standard framerate by modulo: downsampling has to be done to a divisor of the nominal fps value
fps = getClosestFramerateStandard(fps, 'STANDARD')
}
// Hard FPS limits
if (fps > VIDEO_TRANSCODING_FPS.MAX) fps = getClosestFramerateStandard(fps, 'HD_STANDARD')
if (fps < VIDEO_TRANSCODING_FPS.MIN) {
throw new Error(`Cannot compute FPS because ${fps} is lower than our minimum value ${VIDEO_TRANSCODING_FPS.MIN}`)
}
2020-11-26 11:29:50 +01:00
return fps
}
2020-11-20 17:16:55 +01:00
// ---------------------------------------------------------------------------
export {
getVideoStreamCodec,
getAudioStreamCodec,
getVideoStreamSize,
getVideoFileResolution,
getMetadataFromFile,
getMaxAudioBitrate,
2020-11-24 14:08:23 +01:00
getVideoStreamFromFile,
2020-11-20 17:16:55 +01:00
getDurationFromVideoFile,
getAudioStream,
2020-11-26 11:29:50 +01:00
computeFPS,
2020-11-20 17:16:55 +01:00
getVideoFileFPS,
2020-11-24 14:08:23 +01:00
ffprobePromise,
2020-11-20 17:16:55 +01:00
getClosestFramerateStandard,
2021-11-18 14:35:08 +01:00
computeLowerResolutionsToTranscode,
2020-11-20 17:16:55 +01:00
getVideoFileBitrate,
2020-11-24 14:08:23 +01:00
canDoQuickTranscode,
canDoQuickVideoTranscode,
canDoQuickAudioTranscode
2020-11-20 17:16:55 +01:00
}