1 import { FfprobeData } from 'fluent-ffmpeg'
2 import { getMaxBitrate } from '@shared/core-utils'
3 import { VideoResolution, VideoTranscodingFPS } from '../../shared/models/videos'
4 import { CONFIG } from '../initializers/config'
5 import { VIDEO_TRANSCODING_FPS } from '../initializers/constants'
6 import { logger } from './logger'
8 canDoQuickAudioTranscode,
10 getDurationFromVideoFile,
16 getVideoFileResolution,
17 getVideoStreamFromFile,
19 } from '@shared/extra-utils/ffprobe'
23 * Helpers to run ffprobe and extract data from the JSON output
27 async function getVideoStreamCodec (path: string) {
28 const videoStream = await getVideoStreamFromFile(path)
30 if (!videoStream) return ''
32 const videoCodec = videoStream.codec_tag_string
34 if (videoCodec === 'vp09') return 'vp09.00.50.08'
35 if (videoCodec === 'hev1') return 'hev1.1.6.L93.B0'
37 const baseProfileMatrix = {
50 let baseProfile = baseProfileMatrix[videoCodec][videoStream.profile]
52 logger.warn('Cannot get video profile codec of %s.', path, { videoStream })
53 baseProfile = baseProfileMatrix[videoCodec]['High'] // Fallback
56 if (videoCodec === 'av01') {
57 const level = videoStream.level
59 // Guess the tier indicator and bit depth
60 return `${videoCodec}.${baseProfile}.${level}M.08`
63 // Default, h264 codec
64 let level = videoStream.level.toString(16)
65 if (level.length === 1) level = `0${level}`
67 return `${videoCodec}.${baseProfile}${level}`
70 async function getAudioStreamCodec (path: string, existingProbe?: FfprobeData) {
71 const { audioStream } = await getAudioStream(path, existingProbe)
73 if (!audioStream) return ''
75 const audioCodecName = audioStream.codec_name
77 if (audioCodecName === 'opus') return 'opus'
78 if (audioCodecName === 'vorbis') return 'vorbis'
79 if (audioCodecName === 'aac') return 'mp4a.40.2'
81 logger.warn('Cannot get audio codec of %s.', path, { audioStream })
83 return 'mp4a.40.2' // Fallback
86 function computeLowerResolutionsToTranscode (videoFileResolution: number, type: 'vod' | 'live') {
87 const configResolutions = type === 'vod'
88 ? CONFIG.TRANSCODING.RESOLUTIONS
89 : CONFIG.LIVE.TRANSCODING.RESOLUTIONS
91 const resolutionsEnabled: number[] = []
93 // Put in the order we want to proceed jobs
94 const resolutions: VideoResolution[] = [
95 VideoResolution.H_NOVIDEO,
96 VideoResolution.H_480P,
97 VideoResolution.H_360P,
98 VideoResolution.H_720P,
99 VideoResolution.H_240P,
100 VideoResolution.H_144P,
101 VideoResolution.H_1080P,
102 VideoResolution.H_1440P,
106 for (const resolution of resolutions) {
107 if (configResolutions[resolution + 'p'] === true && videoFileResolution > resolution) {
108 resolutionsEnabled.push(resolution)
112 return resolutionsEnabled
115 async function canDoQuickTranscode (path: string): Promise<boolean> {
116 if (CONFIG.TRANSCODING.PROFILE !== 'default') return false
118 const probe = await ffprobePromise(path)
120 return await canDoQuickVideoTranscode(path, probe) &&
121 await canDoQuickAudioTranscode(path, probe)
124 async function canDoQuickVideoTranscode (path: string, probe?: FfprobeData): Promise<boolean> {
125 const videoStream = await getVideoStreamFromFile(path, probe)
126 const fps = await getVideoFileFPS(path, probe)
127 const bitRate = await getVideoFileBitrate(path, probe)
128 const resolutionData = await getVideoFileResolution(path, probe)
130 // If ffprobe did not manage to guess the bitrate
131 if (!bitRate) return false
133 // check video params
134 if (videoStream == null) return false
135 if (videoStream['codec_name'] !== 'h264') return false
136 if (videoStream['pix_fmt'] !== 'yuv420p') return false
137 if (fps < VIDEO_TRANSCODING_FPS.MIN || fps > VIDEO_TRANSCODING_FPS.MAX) return false
138 if (bitRate > getMaxBitrate({ ...resolutionData, fps })) return false
143 function getClosestFramerateStandard <K extends keyof Pick<VideoTranscodingFPS, 'HD_STANDARD' | 'STANDARD'>> (fps: number, type: K) {
144 return VIDEO_TRANSCODING_FPS[type].slice(0)
145 .sort((a, b) => fps % a - fps % b)[0]
148 function computeFPS (fpsArg: number, resolution: VideoResolution) {
152 // On small/medium resolutions, limit FPS
153 resolution !== undefined &&
154 resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN &&
155 fps > VIDEO_TRANSCODING_FPS.AVERAGE
157 // Get closest standard framerate by modulo: downsampling has to be done to a divisor of the nominal fps value
158 fps = getClosestFramerateStandard(fps, 'STANDARD')
162 if (fps > VIDEO_TRANSCODING_FPS.MAX) fps = getClosestFramerateStandard(fps, 'HD_STANDARD')
164 if (fps < VIDEO_TRANSCODING_FPS.MIN) {
165 throw new Error(`Cannot compute FPS because ${fps} is lower than our minimum value ${VIDEO_TRANSCODING_FPS.MIN}`)
171 // ---------------------------------------------------------------------------
177 getVideoFileResolution,
180 getVideoStreamFromFile,
181 getDurationFromVideoFile,
186 getClosestFramerateStandard,
187 computeLowerResolutionsToTranscode,
190 canDoQuickVideoTranscode,
191 canDoQuickAudioTranscode