diff --git a/api/src/config.js b/api/src/config.js index 9792729e..a989148e 100644 --- a/api/src/config.js +++ b/api/src/config.js @@ -1,12 +1,5 @@ const genericUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"; const supportedAudio = ["mp3", "ogg", "wav", "opus"]; -const ffmpegArgs = { - webm: ["-c:v", "copy", "-c:a", "copy"], - mp4: ["-c:v", "copy", "-c:a", "copy", "-movflags", "faststart+frag_keyframe+empty_moov"], - audio: ["-ar", "48000", "-ac", "2", "-b:a", "320k"], - m4a: ["-movflags", "frag_keyframe+empty_moov"], - gif: ["-vf", "scale=-1:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse", "-loop", "0"] -} const env = { apiURL: process.env.API_URL || '', @@ -42,5 +35,4 @@ export { genericUserAgent, supportedAudio, - ffmpegArgs, } diff --git a/api/src/processing/match-action.js b/api/src/processing/match-action.js index f14d57dc..91a46af9 100644 --- a/api/src/processing/match-action.js +++ b/api/src/processing/match-action.js @@ -5,7 +5,7 @@ import { createResponse } from "./request.js"; import { createStream } from "../stream/manage.js"; import { audioIgnore, services } from "./service-config.js"; -export default function({ r, host, audioFormat, isAudioOnly, isAudioMuted, disableMetadata, filenameStyle, twitterGif, requestIP }) { +export default function({ r, host, audioFormat, isAudioOnly, isAudioMuted, disableMetadata, filenameStyle, twitterGif, requestIP, audioBitrate }) { let action, responseType = "stream", defaultParams = { @@ -193,8 +193,10 @@ export default function({ r, host, audioFormat, isAudioOnly, isAudioMuted, disab params = { type: processType, u: Array.isArray(r.urls) ? r.urls[1] : r.urls, - audioFormat: audioFormat, - copy: copy + + audioBitrate, + audioCopy: copy, + audioFormat, } break; } diff --git a/api/src/processing/match.js b/api/src/processing/match.js index 50ca4c23..09d4cffb 100644 --- a/api/src/processing/match.js +++ b/api/src/processing/match.js @@ -261,7 +261,8 @@ export default async function(host, patternMatch, obj) { disableMetadata, filenameStyle: obj.filenameStyle, twitterGif: obj.twitterGif, - requestIP + requestIP, + audioBitrate: obj.audioBitrate, }) } catch { return createResponse("error", { diff --git a/api/src/processing/schema.js b/api/src/processing/schema.js index 65659b64..ed234e16 100644 --- a/api/src/processing/schema.js +++ b/api/src/processing/schema.js @@ -8,6 +8,10 @@ export const apiSchema = z.object({ .min(1) .transform((url) => normalizeURL(decodeURIComponent(url))), + audioBitrate: z.enum( + ["320", "256", "128", "96", "64"] + ).default("256"), + audioFormat: z.enum( ["best", "mp3", "ogg", "wav", "opus"] ).default("mp3"), diff --git a/api/src/stream/manage.js b/api/src/stream/manage.js index 90a341bb..fcac78ac 100644 --- a/api/src/stream/manage.js +++ b/api/src/stream/manage.js @@ -37,11 +37,15 @@ export function createStream(obj) { urls: obj.u, service: obj.service, filename: obj.filename, - audioFormat: obj.audioFormat, + + requestIP: obj.requestIP, headers: obj.headers, - copy: !!obj.copy, + metadata: obj.fileMetadata || false, - requestIP: obj.requestIP + + audioBitrate: obj.audioBitrate, + audioCopy: !!obj.audioCopy, + audioFormat: obj.audioFormat, }; streamCache.set( diff --git a/api/src/stream/types.js b/api/src/stream/types.js index 575e522d..b08c2b05 100644 --- a/api/src/stream/types.js +++ b/api/src/stream/types.js @@ -3,12 +3,19 @@ import ffmpeg from "ffmpeg-static"; import { spawn } from "child_process"; import { create as contentDisposition } from "content-disposition-header"; -import { env, ffmpegArgs } from "../config.js"; +import { env } from "../config.js"; import { metadataManager } from "../misc/utils.js"; import { destroyInternalStream } from "./manage.js"; import { hlsExceptions } from "../processing/service-config.js"; import { getHeaders, closeRequest, closeResponse, pipe } from "./shared.js"; +const ffmpegArgs = { + webm: ["-c:v", "copy", "-c:a", "copy"], + mp4: ["-c:v", "copy", "-c:a", "copy", "-movflags", "faststart+frag_keyframe+empty_moov"], + m4a: ["-movflags", "frag_keyframe+empty_moov"], + gif: ["-vf", "scale=-1:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse", "-loop", "0"] +} + const toRawHeaders = (headers) => { return Object.entries(headers) .map(([key, value]) => `${key}: ${value}\r\n`) @@ -209,18 +216,24 @@ const convertAudio = (streamInfo, res) => { '-vn' ) - if (streamInfo.metadata) { - args = args.concat(metadataManager(streamInfo.metadata)) + if (streamInfo.audioCopy) { + args.push("-c:a", "copy") + } else { + args.push("-b:a", `${streamInfo.audioBitrate}k`) } - args = args.concat( - streamInfo.copy ? ["-c:a", "copy"] : ffmpegArgs.audio - ); + if (streamInfo.audioFormat === "opus") { + args.push("-vbr", "off") + } if (ffmpegArgs[streamInfo.audioFormat]) { args = args.concat(ffmpegArgs[streamInfo.audioFormat]) } + if (streamInfo.metadata) { + args = args.concat(metadataManager(streamInfo.metadata)) + } + args.push('-f', streamInfo.audioFormat === "m4a" ? "ipod" : streamInfo.audioFormat, 'pipe:3'); process = spawn(...getCommand(args), { @@ -257,7 +270,7 @@ const convertGif = (streamInfo, res) => { } args.push('-i', streamInfo.urls); - args = args.concat(ffmpegArgs["gif"]); + args = args.concat(ffmpegArgs.gif); args.push('-f', "gif", 'pipe:3'); process = spawn(...getCommand(args), {