diff --git a/src/lib/converters/ffmpeg/ffmpeg.svelte.ts b/src/lib/converters/ffmpeg/ffmpeg.svelte.ts index 9bb5970..d0f617b 100644 --- a/src/lib/converters/ffmpeg/ffmpeg.svelte.ts +++ b/src/lib/converters/ffmpeg/ffmpeg.svelte.ts @@ -420,7 +420,105 @@ export class FFmpegConverter extends Converter { let channelsArgs: string[] = []; let tracksArgs: string[] = []; let metadataArgs: string[] = []; - let m4aArgs: string[] = []; + const extraArgs: string[] = []; + + const checkSettings = () => { + /* + * sanity check settings for certain formats, to prevent conversion failure + */ + const settingsChanged: SettingChange[] = []; + + console.log( + `checking settings for compatibility with output format ${to}...`, + ); + + if (to === ".opus") { + // work around browser stereo+libopus wasm crash + // no idea why its broken like this in the browser only lol + if (settings.channels >= 2) { + channelsArgs = ["-ac", "1"]; + settingsChanged.push({ + setting: "channels", + oldValue: settings.channels, + newValue: 1, + file: input.name, + }); + } + + // TODO: surely better way to do this as well :sob: + if ( + audioBitrateArgs[1] && + parseInt(audioBitrateArgs[1], 10) > 256 + ) { + audioBitrateArgs = ["-b:a", "256k"]; + settingsChanged.push({ + setting: "bitrate", + oldValue: settings.bitrate, + newValue: 256, + file: input.name, + }); + } + } else if (to === ".amv") { + // amv requires mono audio, 32kbps, and 22050 sample rate + // block size is 22050 (sample rate) / fps. max is 8192, so we have to force a higher fps + const amvFps = 3; + const amvBlockSize = Math.floor(22050 / amvFps); + + audioBitrateArgs = ["-b:a", "32k"]; + sampleRateArgs = ["-ar", "22050"]; + channelsArgs = ["-ac", "1"]; + extraArgs.push( + "-r", + String(amvFps), + "-block_size", + String(amvBlockSize), + ); + settingsChanged.push({ + setting: "sampleRate", + oldValue: settings.sampleRate, + newValue: 22050, + file: input.name, + }); + settingsChanged.push({ + setting: "channels", + oldValue: settings.channels, + newValue: 1, + file: input.name, + }); + settingsChanged.push({ + setting: "bitrate", + oldValue: settings.bitrate, + newValue: 32, + file: input.name, + }); + } else if (to === ".mpg" || to === ".mpeg" || to === ".vob") { + // these formats need to have its bitrate to the power of 2 to convert correctly + const bitrate = parseInt(audioBitrateArgs[1], 10); + const newBitrate = Math.pow(2, Math.round(Math.log2(bitrate))); + audioBitrateArgs = ["-b:a", `${newBitrate}k`]; + settingsChanged.push({ + setting: "bitrate", + oldValue: settings.bitrate, + newValue: newBitrate, + file: input.name, + }); + } + + for (const change of settingsChanged) { + this.log( + `changed setting "${change.setting}" from "${change.oldValue}" to "${change.newValue}" for file ${change.file} to prevent conversion failure`, + ); + ToastManager.add({ + type: "warning", + message: m["workers.warnings.settings_change"]({ + setting: change.setting, + oldValue: change.oldValue, + newValue: change.newValue, + file: change.file, + }), + }); + } + }; this.log(`keep metadata: ${keepMetadata}`); if (!keepMetadata) { @@ -522,6 +620,7 @@ export class FFmpegConverter extends Converter { // video to audio if (videoFormats.includes(inputFormat)) { this.log(`Converting video ${input.from} to audio ${to}`); + checkSettings(); return [ "-i", "input", @@ -532,6 +631,7 @@ export class FFmpegConverter extends Converter { ...sampleRateArgs, ...channelsArgs, ...tracksArgs, + ...extraArgs, "output" + to, ]; } @@ -545,6 +645,8 @@ export class FFmpegConverter extends Converter { : false; const codecArgs = toArgs(to, isAlac); + checkSettings(); + if (hasAlbumArt) { this.log("Using album art as video background"); return avWithArt( @@ -554,6 +656,7 @@ export class FFmpegConverter extends Converter { audioBitrateArgs, sampleRateArgs, channelsArgs, + extraArgs, ); } else { this.log("Using solid color background"); @@ -564,6 +667,7 @@ export class FFmpegConverter extends Converter { audioBitrateArgs, sampleRateArgs, channelsArgs, + extraArgs, ); } } @@ -571,60 +675,13 @@ export class FFmpegConverter extends Converter { // audio to audio this.log(`Converting audio ${input.from} to audio ${to}`); const { audio: audioCodec } = getCodecs(to, isAlac); - if (m4a && keepMetadata) m4aArgs = ["-c:v", "copy"]; // for album art + if (m4a && keepMetadata) extraArgs.push("-c:v", "copy"); // for album art - /* - * sanity check settings for certain formats, to prevent conversion failure - */ - const settingsChanged: SettingChange[] = []; - - if (to === ".opus") { - // work around browser stereo+libopus wasm crash - // no idea why its broken like this in the browser only lol - if (settings.channels >= 2) { - channelsArgs = ["-ac", "1"]; - settingsChanged.push({ - setting: "channels", - oldValue: settings.channels, - newValue: 1, - file: input.name, - }); - } - - // TODO: surely better way to do this as well :sob: - if ( - audioBitrateArgs[1] && - parseInt(audioBitrateArgs[1], 10) > 256 - ) { - audioBitrateArgs = ["-b:a", "256k"]; - settingsChanged.push({ - setting: "bitrate", - oldValue: settings.bitrate, - newValue: 256, - file: input.name, - }); - } - } - - for (const change of settingsChanged) { - this.log( - `changed setting "${change.setting}" from "${change.oldValue}" to "${change.newValue}" for file ${change.file} to prevent conversion failure`, - ); - ToastManager.add({ - type: "warning", - message: m["workers.warnings.settings_change"]({ - setting: change.setting, - oldValue: change.oldValue, - newValue: change.newValue, - file: change.file, - }), - }); - } + checkSettings(); return [ "-i", "input", - ...m4aArgs, "-c:a", audioCodec, ...metadataArgs, @@ -632,6 +689,7 @@ export class FFmpegConverter extends Converter { ...sampleRateArgs, ...channelsArgs, ...tracksArgs, + ...extraArgs, "-strict", "experimental", "output" + to, @@ -724,7 +782,6 @@ const handleSpecialOutput = async ( ".qoa", ); } - // if (whatever other formats need special parsing) return null; diff --git a/src/lib/converters/ffmpeg/utils/ffmpeg.ts b/src/lib/converters/ffmpeg/utils/ffmpeg.ts index 199e7db..997966b 100644 --- a/src/lib/converters/ffmpeg/utils/ffmpeg.ts +++ b/src/lib/converters/ffmpeg/utils/ffmpeg.ts @@ -56,6 +56,7 @@ export const avWithArt = ( audioBitrateArgs: string[], sampleRateArgs: string[], channelsArgs: string[], + extraArgs: string[], ): string[] => { return [ "-loop", @@ -80,6 +81,7 @@ export const avWithArt = ( ...audioBitrateArgs, ...sampleRateArgs, ...channelsArgs, + ...extraArgs, "output" + to, ]; }; @@ -92,6 +94,7 @@ export const avWithBg = ( audioBitrateArgs: string[], sampleRateArgs: string[], channelsArgs: string[], + extraArgs: string[], ): string[] => { return [ "-f", @@ -114,6 +117,7 @@ export const avWithBg = ( ...audioBitrateArgs, ...sampleRateArgs, ...channelsArgs, + ...extraArgs, "output" + to, ]; }; diff --git a/src/lib/converters/ffmpeg/utils/ffprobe.ts b/src/lib/converters/ffmpeg/utils/ffprobe.ts index 9810d86..737c8f0 100644 --- a/src/lib/converters/ffmpeg/utils/ffprobe.ts +++ b/src/lib/converters/ffmpeg/utils/ffprobe.ts @@ -44,7 +44,7 @@ export const detectAudioBitrate = async ( return await ffprobeValue(ffmpeg, args, (s) => { const n = parseInt(s, 10); - return Number.isFinite(n) ? Math.round(n / 1000) : null; + return Number.isFinite(n) ? Math.floor(n / 1000) : null; }); };