From e45ebac97abb516127fd21d6881ca41c91e9dbc4 Mon Sep 17 00:00:00 2001 From: YangManBOBO Date: Mon, 20 Jul 2026 13:18:23 +0800 Subject: [PATCH] fix(media): m4a/AAC audio files from non-Apple sources are misidentified as video/mp4 and excluded from audio workflows (#111177) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(media): preserve audio hints for isom-brand m4a/AAC files misidentified as video/mp4 * fix(media): correct ambiguous M4A aliases Co-authored-by: 潘晓波0668000512 --------- Co-authored-by: Peter Steinberger --- packages/media-core/src/mime.test.ts | 54 ++++++++++++++++++++++++++-- packages/media-core/src/mime.ts | 9 +++-- 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/packages/media-core/src/mime.test.ts b/packages/media-core/src/mime.test.ts index 26abadfa0e3..313af90fc90 100644 --- a/packages/media-core/src/mime.test.ts +++ b/packages/media-core/src/mime.test.ts @@ -26,6 +26,12 @@ async function makeOoxmlZip(opts: { mainMime: string; partPath: string }): Promi return await zip.generateAsync({ type: "nodebuffer" }); } +// file-type classifies this generic ISO-BMFF brand as video/mp4 without track metadata. +const ISOM_BRAND_BUFFER = Buffer.from( + "0000001c6674797069736f6d0000000069736f6d0000000000000000", + "hex", +); + describe("mime detection", () => { async function expectDetectedMime(params: { input: Parameters[0]; @@ -221,10 +227,51 @@ describe("mime detection", () => { }); }); - it("preserves audio metadata when an MP4 extension cannot identify track kind", async () => { - await expectDetectedMime({ - input: { filePath: "voice.mp4", headerMime: "audio/mp4" }, + it.each([ + { + name: "audio/mp4 header", + filePath: "voice.mp4", + headerMime: "audio/mp4", expected: "audio/mp4", + }, + { + name: "audio/x-m4a header", + filePath: "voice.m4a", + headerMime: "audio/x-m4a", + expected: "audio/x-m4a", + }, + { + name: "audio/m4a header", + filePath: "voice.m4a", + headerMime: "audio/m4a", + expected: "audio/m4a", + }, + { + name: "m4a extension", + filePath: "voice.m4a", + headerMime: undefined, + expected: "audio/x-m4a", + }, + { + name: "mp4 extension without an audio hint", + filePath: "clip.mp4", + headerMime: undefined, + expected: "video/mp4", + }, + { + name: "audio/aac elementary-stream metadata", + filePath: "voice.aac", + headerMime: "audio/aac", + expected: "video/mp4", + }, + ] as const)("resolves ambiguous isom-brand bytes from $name", async (testCase) => { + await expectDetectedMime({ + input: { + buffer: ISOM_BRAND_BUFFER, + filePath: testCase.filePath, + headerMime: testCase.headerMime, + }, + expected: testCase.expected, }); }); @@ -381,6 +428,7 @@ describe("extensionForMime", () => { { mime: "audio/x-wav", expected: ".wav" }, { mime: "audio/webm", expected: ".webm" }, { mime: "audio/x-m4a", expected: ".m4a" }, + { mime: "audio/m4a", expected: ".m4a" }, { mime: "audio/mp4", expected: ".m4a" }, { mime: "video/x-msvideo", expected: ".avi" }, { mime: "video/mp4", expected: ".mp4" }, diff --git a/packages/media-core/src/mime.ts b/packages/media-core/src/mime.ts index 046127ac147..1fa8cfaa801 100644 --- a/packages/media-core/src/mime.ts +++ b/packages/media-core/src/mime.ts @@ -28,6 +28,7 @@ const EXT_BY_MIME: Record = { "audio/opus": ".opus", "audio/webm": ".webm", "audio/x-m4a": ".m4a", + "audio/m4a": ".m4a", "audio/mp4": ".m4a", "audio/x-caf": ".caf", "video/x-msvideo": ".avi", @@ -88,6 +89,8 @@ const MIME_BY_EXT: Record = { const AMBIGUOUS_VIDEO_MIME_BY_AUDIO_MIME: Readonly> = { "audio/mp4": "video/mp4", + "audio/x-m4a": "video/mp4", + "audio/m4a": "video/mp4", "audio/webm": "video/webm", }; @@ -243,9 +246,9 @@ export async function detectMime(opts: { : (sniffed ?? extMime); // file-type defaults these containers to video without parsing their tracks. // Preserve a concrete audio hint only for those documented ambiguous results. - const audioContainerHint = mimeHints.find( - (mime) => AMBIGUOUS_VIDEO_MIME_BY_AUDIO_MIME[mime] === inferred, - ); + const audioContainerHint = + mimeHints.find((mime) => AMBIGUOUS_VIDEO_MIME_BY_AUDIO_MIME[mime] === inferred) ?? + (extMime && AMBIGUOUS_VIDEO_MIME_BY_AUDIO_MIME[extMime] === inferred ? extMime : undefined); if (audioContainerHint) { return audioContainerHint; }