]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - shared/extra-utils/ffprobe.ts
Translated using Weblate (Chinese (Simplified))
[github/Chocobozzz/PeerTube.git] / shared / extra-utils / ffprobe.ts
CommitLineData
06aad801 1import { ffprobe, FfprobeData } from 'fluent-ffmpeg'
2import { VideoFileMetadata } from '@shared/models/videos'
3
4/**
5 *
6 * Helpers to run ffprobe and extract data from the JSON output
7 *
8 */
9
10function ffprobePromise (path: string) {
11 return new Promise<FfprobeData>((res, rej) => {
12 ffprobe(path, (err, data) => {
13 if (err) return rej(err)
14
15 return res(data)
16 })
17 })
18}
19
482b2623
C
20async function isAudioFile (path: string, existingProbe?: FfprobeData) {
21 const videoStream = await getVideoStreamFromFile(path, existingProbe)
22
23 return !videoStream
24}
25
06aad801 26async function getAudioStream (videoPath: string, existingProbe?: FfprobeData) {
27 // without position, ffprobe considers the last input only
28 // we make it consider the first input only
29 // if you pass a file path to pos, then ffprobe acts on that file directly
30 const data = existingProbe || await ffprobePromise(videoPath)
31
32 if (Array.isArray(data.streams)) {
33 const audioStream = data.streams.find(stream => stream['codec_type'] === 'audio')
34
35 if (audioStream) {
36 return {
37 absolutePath: data.format.filename,
38 audioStream,
39 bitrate: parseInt(audioStream['bit_rate'] + '', 10)
40 }
41 }
42 }
43
44 return { absolutePath: data.format.filename }
45}
46
47function getMaxAudioBitrate (type: 'aac' | 'mp3' | string, bitrate: number) {
48 const maxKBitrate = 384
49 const kToBits = (kbits: number) => kbits * 1000
50
51 // If we did not manage to get the bitrate, use an average value
52 if (!bitrate) return 256
53
54 if (type === 'aac') {
55 switch (true) {
56 case bitrate > kToBits(maxKBitrate):
57 return maxKBitrate
58
59 default:
60 return -1 // we interpret it as a signal to copy the audio stream as is
61 }
62 }
63
64 /*
65 a 192kbit/sec mp3 doesn't hold as much information as a 192kbit/sec aac.
66 That's why, when using aac, we can go to lower kbit/sec. The equivalences
67 made here are not made to be accurate, especially with good mp3 encoders.
68 */
69 switch (true) {
70 case bitrate <= kToBits(192):
71 return 128
72
73 case bitrate <= kToBits(384):
74 return 256
75
76 default:
77 return maxKBitrate
78 }
79}
80
81async function getVideoStreamSize (path: string, existingProbe?: FfprobeData): Promise<{ width: number, height: number }> {
82 const videoStream = await getVideoStreamFromFile(path, existingProbe)
83
84 return videoStream === null
85 ? { width: 0, height: 0 }
86 : { width: videoStream.width, height: videoStream.height }
87}
88
89async function getVideoFileResolution (path: string, existingProbe?: FfprobeData) {
90 const size = await getVideoStreamSize(path, existingProbe)
91
92 return {
93 width: size.width,
94 height: size.height,
95 ratio: Math.max(size.height, size.width) / Math.min(size.height, size.width),
96 resolution: Math.min(size.height, size.width),
97 isPortraitMode: size.height > size.width
98 }
99}
100
101async function getVideoFileFPS (path: string, existingProbe?: FfprobeData) {
102 const videoStream = await getVideoStreamFromFile(path, existingProbe)
103 if (videoStream === null) return 0
104
105 for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) {
106 const valuesText: string = videoStream[key]
107 if (!valuesText) continue
108
109 const [ frames, seconds ] = valuesText.split('/')
110 if (!frames || !seconds) continue
111
112 const result = parseInt(frames, 10) / parseInt(seconds, 10)
113 if (result > 0) return Math.round(result)
114 }
115
116 return 0
117}
118
119async function getMetadataFromFile (path: string, existingProbe?: FfprobeData) {
120 const metadata = existingProbe || await ffprobePromise(path)
121
122 return new VideoFileMetadata(metadata)
123}
124
125async function getVideoFileBitrate (path: string, existingProbe?: FfprobeData): Promise<number> {
126 const metadata = await getMetadataFromFile(path, existingProbe)
127
128 let bitrate = metadata.format.bit_rate as number
129 if (bitrate && !isNaN(bitrate)) return bitrate
130
131 const videoStream = await getVideoStreamFromFile(path, existingProbe)
132 if (!videoStream) return undefined
133
134 bitrate = videoStream?.bit_rate
135 if (bitrate && !isNaN(bitrate)) return bitrate
136
137 return undefined
138}
139
140async function getDurationFromVideoFile (path: string, existingProbe?: FfprobeData) {
141 const metadata = await getMetadataFromFile(path, existingProbe)
142
143 return Math.round(metadata.format.duration)
144}
145
146async function getVideoStreamFromFile (path: string, existingProbe?: FfprobeData) {
147 const metadata = await getMetadataFromFile(path, existingProbe)
148
149 return metadata.streams.find(s => s.codec_type === 'video') || null
150}
151
152async function canDoQuickAudioTranscode (path: string, probe?: FfprobeData): Promise<boolean> {
153 const parsedAudio = await getAudioStream(path, probe)
154
155 if (!parsedAudio.audioStream) return true
156
157 if (parsedAudio.audioStream['codec_name'] !== 'aac') return false
158
159 const audioBitrate = parsedAudio.bitrate
160 if (!audioBitrate) return false
161
162 const maxAudioBitrate = getMaxAudioBitrate('aac', audioBitrate)
163 if (maxAudioBitrate !== -1 && audioBitrate > maxAudioBitrate) return false
164
165 const channelLayout = parsedAudio.audioStream['channel_layout']
166 // Causes playback issues with Chrome
167 if (!channelLayout || channelLayout === 'unknown') return false
168
169 return true
170}
171
172// ---------------------------------------------------------------------------
173
174export {
175 getVideoStreamSize,
176 getVideoFileResolution,
177 getMetadataFromFile,
178 getMaxAudioBitrate,
179 getVideoStreamFromFile,
180 getDurationFromVideoFile,
181 getAudioStream,
182 getVideoFileFPS,
482b2623 183 isAudioFile,
06aad801 184 ffprobePromise,
185 getVideoFileBitrate,
186 canDoQuickAudioTranscode
187}