refactor: ffmpeg customFormats/containerFormats are now an object

This commit is contained in:
Daniel 2026-03-12 15:24:54 +00:00 committed by GitHub
parent 14817a3314
commit c865b21bf5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 19 additions and 44 deletions

View file

@ -1,13 +0,0 @@
// Re-exports for backwards compatibility canonical source is ffmpegFormats.ts
export {
type ProgressEvent,
type CustomFormat,
type ContainerFormat,
customFormats,
containerFormats,
isCustomFormat,
getCustomFormat,
getContainerFormat,
transcodeWithCustomFormat,
transcodeWithContainerFormat,
} from './ffmpegFormats';

View file

@ -12,8 +12,6 @@ export interface ProgressEvent {
export interface CustomFormat { export interface CustomFormat {
/** Human-readable label shown in the UI */ /** Human-readable label shown in the UI */
displayName: string; displayName: string;
/** Internal identifier, must start with `FFMPEG_` */
internalName: string;
/** Arguments passed to ffmpeg (excluding input/output file args) */ /** Arguments passed to ffmpeg (excluding input/output file args) */
ffmpegArgs: string[]; ffmpegArgs: string[];
/** Output filename used when calling ffmpeg */ /** Output filename used when calling ffmpeg */
@ -40,37 +38,33 @@ export interface ContainerFormat extends Omit<CustomFormat, 'category'> {
needsTranscode: (blob: Blob) => Promise<boolean>; needsTranscode: (blob: Blob) => Promise<boolean>;
} }
export const customFormats: CustomFormat[] = [ export const customFormats: Record<string, CustomFormat> = {
{ FFMPEG_MP3_320: {
displayName: 'MP3 320kbps', displayName: 'MP3 320kbps',
internalName: 'FFMPEG_MP3_320',
ffmpegArgs: ['-map_metadata', '-1', '-c:a', 'libmp3lame', '-b:a', '320k', '-ar', '44100'], ffmpegArgs: ['-map_metadata', '-1', '-c:a', 'libmp3lame', '-b:a', '320k', '-ar', '44100'],
outputFilename: 'output.mp3', outputFilename: 'output.mp3',
outputMime: 'audio/mpeg', outputMime: 'audio/mpeg',
extension: 'mp3', extension: 'mp3',
category: 'MP3', category: 'MP3',
}, },
{ FFMPEG_MP3_256: {
displayName: 'MP3 256kbps', displayName: 'MP3 256kbps',
internalName: 'FFMPEG_MP3_256',
ffmpegArgs: ['-map_metadata', '-1', '-c:a', 'libmp3lame', '-b:a', '256k', '-ar', '44100'], ffmpegArgs: ['-map_metadata', '-1', '-c:a', 'libmp3lame', '-b:a', '256k', '-ar', '44100'],
outputFilename: 'output.mp3', outputFilename: 'output.mp3',
outputMime: 'audio/mpeg', outputMime: 'audio/mpeg',
extension: 'mp3', extension: 'mp3',
category: 'MP3', category: 'MP3',
}, },
{ FFMPEG_MP3_128: {
displayName: 'MP3 128kbps', displayName: 'MP3 128kbps',
internalName: 'FFMPEG_MP3_128',
ffmpegArgs: ['-map_metadata', '-1', '-c:a', 'libmp3lame', '-b:a', '128k', '-ar', '44100'], ffmpegArgs: ['-map_metadata', '-1', '-c:a', 'libmp3lame', '-b:a', '128k', '-ar', '44100'],
outputFilename: 'output.mp3', outputFilename: 'output.mp3',
outputMime: 'audio/mpeg', outputMime: 'audio/mpeg',
extension: 'mp3', extension: 'mp3',
category: 'MP3', category: 'MP3',
}, },
{ FFMPEG_OGG_320: {
displayName: 'OGG 320kbps', displayName: 'OGG 320kbps',
internalName: 'FFMPEG_OGG_320',
ffmpegArgs: [ ffmpegArgs: [
'-map_metadata', '-map_metadata',
'-1', '-1',
@ -88,9 +82,8 @@ export const customFormats: CustomFormat[] = [
extension: 'ogg', extension: 'ogg',
category: 'OGG', category: 'OGG',
}, },
{ FFMPEG_OGG_256: {
displayName: 'OGG 256kbps', displayName: 'OGG 256kbps',
internalName: 'FFMPEG_OGG_256',
ffmpegArgs: [ ffmpegArgs: [
'-map_metadata', '-map_metadata',
'-1', '-1',
@ -108,9 +101,8 @@ export const customFormats: CustomFormat[] = [
extension: 'ogg', extension: 'ogg',
category: 'OGG', category: 'OGG',
}, },
{ FFMPEG_OGG_128: {
displayName: 'OGG 128kbps', displayName: 'OGG 128kbps',
internalName: 'FFMPEG_OGG_128',
ffmpegArgs: [ ffmpegArgs: [
'-map_metadata', '-map_metadata',
'-1', '-1',
@ -128,16 +120,15 @@ export const customFormats: CustomFormat[] = [
extension: 'ogg', extension: 'ogg',
category: 'OGG', category: 'OGG',
}, },
{ FFMPEG_AAC_256: {
displayName: 'AAC 256kbps', displayName: 'AAC 256kbps',
internalName: 'FFMPEG_AAC_256',
ffmpegArgs: ['-map_metadata', '-1', '-c:a', 'aac', '-b:a', '256k'], ffmpegArgs: ['-map_metadata', '-1', '-c:a', 'aac', '-b:a', '256k'],
outputFilename: 'output.m4a', outputFilename: 'output.m4a',
outputMime: 'audio/mp4', outputMime: 'audio/mp4',
extension: 'm4a', extension: 'm4a',
category: 'AAC', category: 'AAC',
}, },
]; };
/** /**
* Container format definitions for lossless re-muxing. Each entry describes * Container format definitions for lossless re-muxing. Each entry describes
@ -145,10 +136,9 @@ export const customFormats: CustomFormat[] = [
* `needsTranscode` predicate so callers can skip the ffmpeg step when the * `needsTranscode` predicate so callers can skip the ffmpeg step when the
* source is already in the correct container. * source is already in the correct container.
*/ */
export const containerFormats: ContainerFormat[] = [ export const containerFormats: Record<string, ContainerFormat> = {
{ flac: {
displayName: 'FLAC', displayName: 'FLAC',
internalName: 'flac',
ffmpegArgs: ['-vn', '-map_metadata', '-1', '-map', '0:a', '-c:a', 'flac'], ffmpegArgs: ['-vn', '-map_metadata', '-1', '-map', '0:a', '-c:a', 'flac'],
outputFilename: 'output.flac', outputFilename: 'output.flac',
outputMime: 'audio/flac', outputMime: 'audio/flac',
@ -156,25 +146,23 @@ export const containerFormats: ContainerFormat[] = [
// Only transcode when the source is NOT already a FLAC file. // Only transcode when the source is NOT already a FLAC file.
needsTranscode: async (blob) => (await getExtensionFromBlob(blob)) !== 'flac', needsTranscode: async (blob) => (await getExtensionFromBlob(blob)) !== 'flac',
}, },
{ alac: {
displayName: 'Apple Lossless', displayName: 'Apple Lossless',
internalName: 'alac',
ffmpegArgs: ['-c:a', 'alac'], ffmpegArgs: ['-c:a', 'alac'],
outputFilename: 'output.m4a', outputFilename: 'output.m4a',
outputMime: 'audio/mp4', outputMime: 'audio/mp4',
extension: 'm4a', extension: 'm4a',
needsTranscode: async () => true, needsTranscode: async () => true,
}, },
{ nochange: {
displayName: "Don't change", displayName: "Don't change",
internalName: 'nochange',
ffmpegArgs: ['-c:a', 'copy', '-strict', '-2'], ffmpegArgs: ['-c:a', 'copy', '-strict', '-2'],
outputFilename: 'output.mp4', outputFilename: 'output.mp4',
outputMime: 'audio/mp4', outputMime: 'audio/mp4',
extension: 'mp4', extension: 'mp4',
needsTranscode: async (blob) => (await getExtensionFromBlob(blob)) == 'm4a', needsTranscode: async (blob) => (await getExtensionFromBlob(blob)) == 'm4a',
}, },
]; };
/** Returns true if the quality string identifies a known custom ffmpeg-transcoded format */ /** Returns true if the quality string identifies a known custom ffmpeg-transcoded format */
export function isCustomFormat(quality: string): boolean { export function isCustomFormat(quality: string): boolean {
@ -183,12 +171,12 @@ export function isCustomFormat(quality: string): boolean {
/** Looks up a custom format by its internal name, or returns undefined */ /** Looks up a custom format by its internal name, or returns undefined */
export function getCustomFormat(internalName: string): CustomFormat | undefined { export function getCustomFormat(internalName: string): CustomFormat | undefined {
return customFormats.find((f) => f.internalName === internalName); return customFormats[internalName];
} }
/** Looks up a container format by its internal name, or returns undefined */ /** Looks up a container format by its internal name, or returns undefined */
export function getContainerFormat(internalName: string): ContainerFormat | undefined { export function getContainerFormat(internalName: string): ContainerFormat | undefined {
return containerFormats.find((f) => f.internalName === internalName); return containerFormats[internalName];
} }
/** /**

View file

@ -816,8 +816,8 @@ export function initializeSettings(scrobbler, player, api, ui) {
})); }));
// Append custom (ffmpeg-transcoded) format options // Append custom (ffmpeg-transcoded) format options
for (const fmt of customFormats) { for (const [key, fmt] of Object.entries(customFormats)) {
allOptions.push({ value: fmt.internalName, text: fmt.displayName, category: fmt.category }); allOptions.push({ value: key, text: fmt.displayName, category: fmt.category });
} }
// Sort by category order first, then by bitrate descending within each category // Sort by category order first, then by bitrate descending within each category
@ -877,7 +877,7 @@ export function initializeSettings(scrobbler, player, api, ui) {
} }
if (losslessContainerSetting) { if (losslessContainerSetting) {
for (const { internalName, displayName } of containerFormats) { for (const [internalName, { displayName }] of Object.entries(containerFormats)) {
const option = document.createElement('option'); const option = document.createElement('option');
option.value = internalName; option.value = internalName;
option.textContent = displayName; option.textContent = displayName;