1
0
Fork 0
mirror of https://github.com/Chocobozzz/PeerTube.git synced 2025-10-03 09:49:20 +02:00

Improve audio transcoding

ffmpeg aac encoder is not very good so prefer to keep the same bitrate
as mp3
we also use the max available bitrate with a flac input that has an
unknown bitrate
This commit is contained in:
Chocobozzz 2025-07-24 16:49:28 +02:00
parent 445866967f
commit 29a88c0dde
No known key found for this signature in database
GPG key ID: 583A612D890159BE
2 changed files with 28 additions and 56 deletions

View file

@ -2,7 +2,7 @@ import { getAverageTheoreticalBitrate, getMaxTheoreticalBitrate, getMinTheoretic
import { import {
buildStreamSuffix, buildStreamSuffix,
getAudioStream, getAudioStream,
getMaxAudioBitrate, getMaxAudioKBitrate,
getVideoStream, getVideoStream,
getVideoStreamBitrate, getVideoStreamBitrate,
getVideoStreamDimensionsInfo, getVideoStreamDimensionsInfo,
@ -52,7 +52,7 @@ const defaultAACOptionsBuilder: EncoderOptionsBuilder = async ({ input, streamNu
const audioCodecName = parsedAudio.audioStream['codec_name'] const audioCodecName = parsedAudio.audioStream['codec_name']
const bitrate = getMaxAudioBitrate(audioCodecName, parsedAudio.bitrate) const bitrate = getMaxAudioKBitrate(audioCodecName, parsedAudio.bitrate)
// Force stereo as it causes some issues with HLS playback in Chrome // Force stereo as it causes some issues with HLS playback in Chrome
const base = [ '-channel_layout', 'stereo' ] const base = [ '-channel_layout', 'stereo' ]
@ -116,7 +116,7 @@ export async function canDoQuickAudioTranscode (path: string, probe?: FfprobeDat
const audioBitrate = parsedAudio.bitrate const audioBitrate = parsedAudio.bitrate
if (!audioBitrate) return false if (!audioBitrate) return false
const maxAudioBitrate = getMaxAudioBitrate('aac', audioBitrate) const maxAudioBitrate = getMaxAudioKBitrate('aac', audioBitrate)
if (maxAudioBitrate !== -1 && audioBitrate > maxAudioBitrate) return false if (maxAudioBitrate !== -1 && audioBitrate > maxAudioBitrate) return false
const channelLayout = parsedAudio.audioStream['channel_layout'] const channelLayout = parsedAudio.audioStream['channel_layout']

View file

@ -6,7 +6,7 @@ import ffmpeg, { FfprobeData } from 'fluent-ffmpeg'
* Helpers to run ffprobe and extract data from the JSON output * Helpers to run ffprobe and extract data from the JSON output
*/ */
function ffprobePromise (path: string) { export function ffprobePromise (path: string) {
return new Promise<FfprobeData>((res, rej) => { return new Promise<FfprobeData>((res, rej) => {
ffmpeg.ffprobe(path, [ '-show_chapters' ], (err, data) => { ffmpeg.ffprobe(path, [ '-show_chapters' ], (err, data) => {
if (err) return rej(err) if (err) return rej(err)
@ -62,7 +62,7 @@ const imageCodecs = new Set([
'xwd' 'xwd'
]) ])
async function isAudioFile (path: string, existingProbe?: FfprobeData) { export async function isAudioFile (path: string, existingProbe?: FfprobeData) {
const videoStream = await getVideoStream(path, existingProbe) const videoStream = await getVideoStream(path, existingProbe)
if (!videoStream) return true if (!videoStream) return true
@ -71,13 +71,13 @@ async function isAudioFile (path: string, existingProbe?: FfprobeData) {
return false return false
} }
async function hasAudioStream (path: string, existingProbe?: FfprobeData) { export async function hasAudioStream (path: string, existingProbe?: FfprobeData) {
const { audioStream } = await getAudioStream(path, existingProbe) const { audioStream } = await getAudioStream(path, existingProbe)
return !!audioStream return !!audioStream
} }
async function getAudioStream (videoPath: string, existingProbe?: FfprobeData) { export async function getAudioStream (videoPath: string, existingProbe?: FfprobeData) {
const data = existingProbe || await ffprobePromise(videoPath) const data = existingProbe || await ffprobePromise(videoPath)
if (Array.isArray(data.streams)) { if (Array.isArray(data.streams)) {
@ -95,45 +95,34 @@ async function getAudioStream (videoPath: string, existingProbe?: FfprobeData) {
return { absolutePath: data.format.filename } return { absolutePath: data.format.filename }
} }
function getMaxAudioBitrate (type: string, bitrate: number) { export function getMaxAudioKBitrate (type: string, bitrate: number) {
const maxKBitrate = 384 const maxKBitrate = 384
const kToBits = (kbits: number) => kbits * 1000
// If we did not manage to get the bitrate, use an average value // If we did not manage to get the bitrate, use an average value
if (!bitrate) return 256 if (!bitrate) {
// We expect uploader wants a high quality audio if the input is in FLAC format
if (type === 'flac') return maxKBitrate
return 256
}
const kBitrate = Math.round(bitrate / 1000)
if (type === 'aac') { if (type === 'aac') {
switch (true) { if (kBitrate > maxKBitrate) return maxKBitrate
case bitrate > kToBits(maxKBitrate):
return maxKBitrate
default: // We interpret it as a signal to copy the audio stream as is
return -1 // we interpret it as a signal to copy the audio stream as is return -1
}
} }
/* return Math.min(maxKBitrate, kBitrate)
a 192kbit/sec mp3 doesn't hold as much information as a 192kbit/sec aac.
That's why, when using aac, we can go to lower kbit/sec. The equivalences
made here are not made to be accurate, especially with good mp3 encoders.
*/
switch (true) {
case bitrate <= kToBits(192):
return 128
case bitrate <= kToBits(384):
return 256
default:
return maxKBitrate
}
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Video // Video
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
async function getVideoStreamDimensionsInfo (path: string, existingProbe?: FfprobeData) { export async function getVideoStreamDimensionsInfo (path: string, existingProbe?: FfprobeData) {
const videoStream = await getVideoStream(path, existingProbe) const videoStream = await getVideoStream(path, existingProbe)
if (!videoStream) { if (!videoStream) {
return { return {
@ -164,7 +153,7 @@ async function getVideoStreamDimensionsInfo (path: string, existingProbe?: Ffpro
} }
} }
async function getVideoStreamFPS (path: string, existingProbe?: FfprobeData) { export async function getVideoStreamFPS (path: string, existingProbe?: FfprobeData) {
const videoStream = await getVideoStream(path, existingProbe) const videoStream = await getVideoStream(path, existingProbe)
if (!videoStream) return 0 if (!videoStream) return 0
@ -182,7 +171,7 @@ async function getVideoStreamFPS (path: string, existingProbe?: FfprobeData) {
return 0 return 0
} }
async function getVideoStreamBitrate (path: string, existingProbe?: FfprobeData): Promise<number> { export async function getVideoStreamBitrate (path: string, existingProbe?: FfprobeData): Promise<number> {
const metadata = existingProbe || await ffprobePromise(path) const metadata = existingProbe || await ffprobePromise(path)
let bitrate = metadata.format.bit_rate let bitrate = metadata.format.bit_rate
@ -197,19 +186,19 @@ async function getVideoStreamBitrate (path: string, existingProbe?: FfprobeData)
return undefined return undefined
} }
async function getVideoStreamDuration (path: string, existingProbe?: FfprobeData) { export async function getVideoStreamDuration (path: string, existingProbe?: FfprobeData) {
const metadata = existingProbe || await ffprobePromise(path) const metadata = existingProbe || await ffprobePromise(path)
return Math.round(metadata.format.duration) return Math.round(metadata.format.duration)
} }
async function getVideoStream (path: string, existingProbe?: FfprobeData) { export async function getVideoStream (path: string, existingProbe?: FfprobeData) {
const metadata = existingProbe || await ffprobePromise(path) const metadata = existingProbe || await ffprobePromise(path)
return metadata.streams.find(s => s.codec_type === 'video') return metadata.streams.find(s => s.codec_type === 'video')
} }
async function hasVideoStream (path: string, existingProbe?: FfprobeData) { export async function hasVideoStream (path: string, existingProbe?: FfprobeData) {
const videoStream = await getVideoStream(path, existingProbe) const videoStream = await getVideoStream(path, existingProbe)
return !!videoStream return !!videoStream
@ -219,7 +208,7 @@ async function hasVideoStream (path: string, existingProbe?: FfprobeData) {
// Chapters // Chapters
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
async function getChaptersFromContainer (options: { export async function getChaptersFromContainer (options: {
path: string path: string
maxTitleLength: number maxTitleLength: number
ffprobe?: FfprobeData ffprobe?: FfprobeData
@ -236,20 +225,3 @@ async function getChaptersFromContainer (options: {
title: (c['TAG:title'] || '').slice(0, maxTitleLength) title: (c['TAG:title'] || '').slice(0, maxTitleLength)
})) }))
} }
// ---------------------------------------------------------------------------
export {
ffprobePromise,
getAudioStream,
getChaptersFromContainer,
getMaxAudioBitrate,
getVideoStream,
getVideoStreamBitrate,
getVideoStreamDimensionsInfo,
getVideoStreamDuration,
getVideoStreamFPS,
hasAudioStream,
hasVideoStream,
isAudioFile
}