diff options
author | Chocobozzz <me@florianbigard.com> | 2022-02-11 10:51:33 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2022-02-28 10:42:19 +0100 |
commit | c729caf6cc34630877a0e5a1bda1719384cd0c8a (patch) | |
tree | 1d2e13722e518c73d2c9e6f0969615e29d51cf8c /server/helpers/ffprobe-utils.ts | |
parent | a24bf4dc659cebb65d887862bf21d7a35e9ec791 (diff) | |
download | PeerTube-c729caf6cc34630877a0e5a1bda1719384cd0c8a.tar.gz PeerTube-c729caf6cc34630877a0e5a1bda1719384cd0c8a.tar.zst PeerTube-c729caf6cc34630877a0e5a1bda1719384cd0c8a.zip |
Add basic video editor support
Diffstat (limited to 'server/helpers/ffprobe-utils.ts')
-rw-r--r-- | server/helpers/ffprobe-utils.ts | 192 |
1 files changed, 0 insertions, 192 deletions
diff --git a/server/helpers/ffprobe-utils.ts b/server/helpers/ffprobe-utils.ts deleted file mode 100644 index 595112bce..000000000 --- a/server/helpers/ffprobe-utils.ts +++ /dev/null | |||
@@ -1,192 +0,0 @@ | |||
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 | } | ||