mirror of
https://github.com/Chocobozzz/PeerTube.git
synced 2025-10-03 01:39:37 +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:
parent
445866967f
commit
29a88c0dde
2 changed files with 28 additions and 56 deletions
|
@ -2,7 +2,7 @@ import { getAverageTheoreticalBitrate, getMaxTheoreticalBitrate, getMinTheoretic
|
||||||
import {
|
import {
|
||||||
buildStreamSuffix,
|
buildStreamSuffix,
|
||||||
getAudioStream,
|
getAudioStream,
|
||||||
getMaxAudioBitrate,
|
getMaxAudioKBitrate,
|
||||||
getVideoStream,
|
getVideoStream,
|
||||||
getVideoStreamBitrate,
|
getVideoStreamBitrate,
|
||||||
getVideoStreamDimensionsInfo,
|
getVideoStreamDimensionsInfo,
|
||||||
|
@ -42,7 +42,7 @@ const defaultX264LiveOptionsBuilder: EncoderOptionsBuilder = (options: EncoderOp
|
||||||
|
|
||||||
const defaultAACOptionsBuilder: EncoderOptionsBuilder = async ({ input, streamNum, canCopyAudio, inputProbe }) => {
|
const defaultAACOptionsBuilder: EncoderOptionsBuilder = async ({ input, streamNum, canCopyAudio, inputProbe }) => {
|
||||||
if (canCopyAudio && await canDoQuickAudioTranscode(input, inputProbe)) {
|
if (canCopyAudio && await canDoQuickAudioTranscode(input, inputProbe)) {
|
||||||
return { copy: true, outputOptions: [ ] }
|
return { copy: true, outputOptions: [] }
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedAudio = await getAudioStream(input, inputProbe)
|
const parsedAudio = await getAudioStream(input, inputProbe)
|
||||||
|
@ -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']
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue