update for flac metadata on iphone/rpi

This commit is contained in:
2026-05-17 12:29:13 -07:00
parent 38e825cf28
commit 7b0224a45a
4 changed files with 80 additions and 397 deletions
+45 -27
View File
@@ -26,6 +26,8 @@ const AUDIO_METADATA_EXTENSIONS = new Set([
let ffmpegInstance: FFmpeg | null = null
let ffmpegLoadPromise: Promise<FFmpeg> | null = null
let ffmpegOperationPromise: Promise<void> = Promise.resolve()
let ffmpegTempFileCounter = 0
function sanitizeExtension(extension?: string | null) {
return (extension || '').trim().replace(/^\./, '').toLowerCase()
@@ -54,6 +56,17 @@ async function getFFmpeg() {
}
}
function createTempFileStem() {
ffmpegTempFileCounter += 1
return `metadata-${Date.now()}-${ffmpegTempFileCounter}`
}
function queueFFmpegOperation<T>(operation: () => Promise<T>) {
const pending = ffmpegOperationPromise.then(operation, operation)
ffmpegOperationPromise = pending.then(() => undefined, () => undefined)
return pending
}
function buildMetadataArgs(metadata: AudioTagMetadata) {
const args: string[] = []
if (metadata.title) args.push('-metadata', `title=${metadata.title}`)
@@ -74,37 +87,42 @@ export function supportsContainerMetadataEmbedding(extension?: string | null, mi
export async function embedContainerAudioMetadata(blob: Blob, extension: string | undefined, metadata: AudioTagMetadata) {
if (!metadata.title && !metadata.artist && !metadata.album && !metadata.trackNumber) return blob
const normalizedExtension = sanitizeExtension(extension) || 'audio'
const ffmpeg = await getFFmpeg()
const { fetchFile } = await import('@ffmpeg/util')
const inputPath = `input.${normalizedExtension}`
const outputPath = `output.${normalizedExtension}`
return queueFFmpegOperation(async () => {
const normalizedExtension = sanitizeExtension(extension) || 'audio'
const ffmpeg = await getFFmpeg()
const { fetchFile } = await import('@ffmpeg/util')
const tempStem = createTempFileStem()
const inputPath = `${tempStem}-input.${normalizedExtension}`
const outputPath = `${tempStem}-output.${normalizedExtension}`
await ffmpeg.writeFile(inputPath, await fetchFile(blob))
await ffmpeg.writeFile(inputPath, await fetchFile(blob))
try {
const exitCode = await ffmpeg.exec([
'-i', inputPath,
'-map', '0',
'-map_metadata', '-1',
'-c', 'copy',
...buildMetadataArgs(metadata),
outputPath,
])
try {
const exitCode = await ffmpeg.exec([
'-nostdin',
'-y',
'-i', inputPath,
'-map', '0',
'-map_metadata', '-1',
'-c', 'copy',
...buildMetadataArgs(metadata),
outputPath,
])
if (exitCode !== 0) {
throw new Error(`FFmpeg metadata update failed (${exitCode})`)
if (exitCode !== 0) {
throw new Error(`FFmpeg metadata update failed (${exitCode})`)
}
const outputData = await ffmpeg.readFile(outputPath)
const outputBytes = outputData instanceof Uint8Array ? outputData : new Uint8Array(new TextEncoder().encode(String(outputData)))
const outputCopy = new Uint8Array(outputBytes.byteLength)
outputCopy.set(outputBytes)
return new Blob([outputCopy], { type: blob.type || 'application/octet-stream' })
} finally {
try { await ffmpeg.deleteFile(inputPath) } catch {}
try { await ffmpeg.deleteFile(outputPath) } catch {}
}
const outputData = await ffmpeg.readFile(outputPath)
const outputBytes = outputData instanceof Uint8Array ? outputData : new Uint8Array(new TextEncoder().encode(String(outputData)))
const outputCopy = new Uint8Array(outputBytes.byteLength)
outputCopy.set(outputBytes)
return new Blob([outputCopy], { type: blob.type || 'application/octet-stream' })
} finally {
try { await ffmpeg.deleteFile(inputPath) } catch {}
try { await ffmpeg.deleteFile(outputPath) } catch {}
}
})
}
export type { AudioTagMetadata }