Skip to content

Commit e45ebac

Browse files
fix(media): m4a/AAC audio files from non-Apple sources are misidentified as video/mp4 and excluded from audio workflows (#111177)
* 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 <[email protected]> --------- Co-authored-by: Peter Steinberger <[email protected]>
1 parent bdf71d7 commit e45ebac

2 files changed

Lines changed: 57 additions & 6 deletions

File tree

packages/media-core/src/mime.test.ts

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ async function makeOoxmlZip(opts: { mainMime: string; partPath: string }): Promi
2626
return await zip.generateAsync({ type: "nodebuffer" });
2727
}
2828

29+
// file-type classifies this generic ISO-BMFF brand as video/mp4 without track metadata.
30+
const ISOM_BRAND_BUFFER = Buffer.from(
31+
"0000001c6674797069736f6d0000000069736f6d0000000000000000",
32+
"hex",
33+
);
34+
2935
describe("mime detection", () => {
3036
async function expectDetectedMime(params: {
3137
input: Parameters<typeof detectMime>[0];
@@ -221,10 +227,51 @@ describe("mime detection", () => {
221227
});
222228
});
223229

224-
it("preserves audio metadata when an MP4 extension cannot identify track kind", async () => {
225-
await expectDetectedMime({
226-
input: { filePath: "voice.mp4", headerMime: "audio/mp4" },
230+
it.each([
231+
{
232+
name: "audio/mp4 header",
233+
filePath: "voice.mp4",
234+
headerMime: "audio/mp4",
227235
expected: "audio/mp4",
236+
},
237+
{
238+
name: "audio/x-m4a header",
239+
filePath: "voice.m4a",
240+
headerMime: "audio/x-m4a",
241+
expected: "audio/x-m4a",
242+
},
243+
{
244+
name: "audio/m4a header",
245+
filePath: "voice.m4a",
246+
headerMime: "audio/m4a",
247+
expected: "audio/m4a",
248+
},
249+
{
250+
name: "m4a extension",
251+
filePath: "voice.m4a",
252+
headerMime: undefined,
253+
expected: "audio/x-m4a",
254+
},
255+
{
256+
name: "mp4 extension without an audio hint",
257+
filePath: "clip.mp4",
258+
headerMime: undefined,
259+
expected: "video/mp4",
260+
},
261+
{
262+
name: "audio/aac elementary-stream metadata",
263+
filePath: "voice.aac",
264+
headerMime: "audio/aac",
265+
expected: "video/mp4",
266+
},
267+
] as const)("resolves ambiguous isom-brand bytes from $name", async (testCase) => {
268+
await expectDetectedMime({
269+
input: {
270+
buffer: ISOM_BRAND_BUFFER,
271+
filePath: testCase.filePath,
272+
headerMime: testCase.headerMime,
273+
},
274+
expected: testCase.expected,
228275
});
229276
});
230277

@@ -381,6 +428,7 @@ describe("extensionForMime", () => {
381428
{ mime: "audio/x-wav", expected: ".wav" },
382429
{ mime: "audio/webm", expected: ".webm" },
383430
{ mime: "audio/x-m4a", expected: ".m4a" },
431+
{ mime: "audio/m4a", expected: ".m4a" },
384432
{ mime: "audio/mp4", expected: ".m4a" },
385433
{ mime: "video/x-msvideo", expected: ".avi" },
386434
{ mime: "video/mp4", expected: ".mp4" },

packages/media-core/src/mime.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const EXT_BY_MIME: Record<string, string> = {
2828
"audio/opus": ".opus",
2929
"audio/webm": ".webm",
3030
"audio/x-m4a": ".m4a",
31+
"audio/m4a": ".m4a",
3132
"audio/mp4": ".m4a",
3233
"audio/x-caf": ".caf",
3334
"video/x-msvideo": ".avi",
@@ -88,6 +89,8 @@ const MIME_BY_EXT: Record<string, string> = {
8889

8990
const AMBIGUOUS_VIDEO_MIME_BY_AUDIO_MIME: Readonly<Record<string, string>> = {
9091
"audio/mp4": "video/mp4",
92+
"audio/x-m4a": "video/mp4",
93+
"audio/m4a": "video/mp4",
9194
"audio/webm": "video/webm",
9295
};
9396

@@ -243,9 +246,9 @@ export async function detectMime(opts: {
243246
: (sniffed ?? extMime);
244247
// file-type defaults these containers to video without parsing their tracks.
245248
// Preserve a concrete audio hint only for those documented ambiguous results.
246-
const audioContainerHint = mimeHints.find(
247-
(mime) => AMBIGUOUS_VIDEO_MIME_BY_AUDIO_MIME[mime] === inferred,
248-
);
249+
const audioContainerHint =
250+
mimeHints.find((mime) => AMBIGUOUS_VIDEO_MIME_BY_AUDIO_MIME[mime] === inferred) ??
251+
(extMime && AMBIGUOUS_VIDEO_MIME_BY_AUDIO_MIME[extMime] === inferred ? extMime : undefined);
249252
if (audioContainerHint) {
250253
return audioContainerHint;
251254
}

0 commit comments

Comments
 (0)