]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/helpers/ffprobe-utils.ts
Try to fix ARM docker builds
[github/Chocobozzz/PeerTube.git] / server / helpers / ffprobe-utils.ts
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'
7 import {
8 canDoQuickAudioTranscode,
9 ffprobePromise,
10 getDurationFromVideoFile,
11 getAudioStream,
12 getMaxAudioBitrate,
13 getMetadataFromFile,
14 getVideoFileBitrate,
15 getVideoFileFPS,
16 getVideoFileResolution,
17 getVideoStreamFromFile,
18 getVideoStreamSize
19 } from '@shared/extra-utils/ffprobe'
20
21 /**
22 *
23 * Helpers to run ffprobe and extract data from the JSON output
24 *
25 */
26
27 async function getVideoStreamCodec (path: string) {
28 const videoStream = await getVideoStreamFromFile(path)
29
30 if (!videoStream) return ''
31
32 const videoCodec = videoStream.codec_tag_string
33
34 if (videoCodec === 'vp09') return 'vp09.00.50.08'
35 if (videoCodec === 'hev1') return 'hev1.1.6.L93.B0'
36
37 const baseProfileMatrix = {
38 avc1: {
39 High: '6400',
40 Main: '4D40',
41 Baseline: '42E0'
42 },
43 av01: {
44 High: '1',
45 Main: '0',
46 Professional: '2'
47 }
48 }
49
50 let baseProfile = baseProfileMatrix[videoCodec][videoStream.profile]
51 if (!baseProfile) {
52 logger.warn('Cannot get video profile codec of %s.', path, { videoStream })
53 baseProfile = baseProfileMatrix[videoCodec]['High'] // Fallback
54 }
55
56 if (videoCodec === 'av01') {
57 const level = videoStream.level
58
59 // Guess the tier indicator and bit depth
60 return `${videoCodec}.${baseProfile}.${level}M.08`
61 }
62
63 // Default, h264 codec
64 let level = videoStream.level.toString(16)
65 if (level.length === 1) level = `0${level}`
66
67 return `${videoCodec}.${baseProfile}${level}`
68 }
69
70 async function getAudioStreamCodec (path: string, existingProbe?: FfprobeData) {
71 const { audioStream } = await getAudioStream(path, existingProbe)
72
73 if (!audioStream) return ''
74
75 const audioCodecName = audioStream.codec_name
76
77 if (audioCodecName === 'opus') return 'opus'
78 if (audioCodecName === 'vorbis') return 'vorbis'
79 if (audioCodecName === 'aac') return 'mp4a.40.2'
80
81 logger.warn('Cannot get audio codec of %s.', path, { audioStream })
82
83 return 'mp4a.40.2' // Fallback
84 }
85
86 function computeLowerResolutionsToTranscode (videoFileResolution: number, type: 'vod' | 'live') {
87 const configResolutions = type === 'vod'
88 ? CONFIG.TRANSCODING.RESOLUTIONS
89 : CONFIG.LIVE.TRANSCODING.RESOLUTIONS
90
91 const resolutionsEnabled: number[] = []
92
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,
103 VideoResolution.H_4K
104 ]
105
106 for (const resolution of resolutions) {
107 if (configResolutions[resolution + 'p'] === true && videoFileResolution > resolution) {
108 resolutionsEnabled.push(resolution)
109 }
110 }
111
112 return resolutionsEnabled
113 }
114
115 async function canDoQuickTranscode (path: string): Promise<boolean> {
116 if (CONFIG.TRANSCODING.PROFILE !== 'default') return false
117
118 const probe = await ffprobePromise(path)
119
120 return await canDoQuickVideoTranscode(path, probe) &&
121 await canDoQuickAudioTranscode(path, probe)
122 }
123
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)
129
130 // If ffprobe did not manage to guess the bitrate
131 if (!bitRate) return false
132
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
139
140 return true
141 }
142
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]
146 }
147
148 function computeFPS (fpsArg: number, resolution: VideoResolution) {
149 let fps = fpsArg
150
151 if (
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
156 ) {
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')
159 }
160
161 // Hard FPS limits
162 if (fps > VIDEO_TRANSCODING_FPS.MAX) fps = getClosestFramerateStandard(fps, 'HD_STANDARD')
163
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}`)
166 }
167
168 return fps
169 }
170
171 // ---------------------------------------------------------------------------
172
173 export {
174 getVideoStreamCodec,
175 getAudioStreamCodec,
176 getVideoStreamSize,
177 getVideoFileResolution,
178 getMetadataFromFile,
179 getMaxAudioBitrate,
180 getVideoStreamFromFile,
181 getDurationFromVideoFile,
182 getAudioStream,
183 computeFPS,
184 getVideoFileFPS,
185 ffprobePromise,
186 getClosestFramerateStandard,
187 computeLowerResolutionsToTranscode,
188 getVideoFileBitrate,
189 canDoQuickTranscode,
190 canDoQuickVideoTranscode,
191 canDoQuickAudioTranscode
192 }