Skip to content

Commit 0029122

Browse files
fix(media): correct ambiguous M4A aliases
Co-authored-by: 潘晓波0668000512 <[email protected]>
1 parent a69a3de commit 0029122

2 files changed

Lines changed: 50 additions & 29 deletions

File tree

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

Lines changed: 49 additions & 28 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,37 +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",
228-
});
229-
});
230-
231-
it("preserves audio/x-m4a header for isom-brand ISO-BMFF bytes (Android/ffmpeg m4a)", async () => {
232-
// Non-Apple m4a files use "isom" as the ftyp major brand;
233-
// file-type sniffs those as video/mp4 regardless of audio content.
234-
const isomBrand = Buffer.from(
235-
// 28-byte ftyp box: size=28, "ftyp", major="isom", minor=0, compat=["isom"]
236-
"0000001c6674797069736f6d0000000069736f6d0000000000000000",
237-
"hex",
238-
);
239-
240-
await expectDetectedMime({
241-
input: { buffer: isomBrand, filePath: "voice.m4a", headerMime: "audio/x-m4a" },
236+
},
237+
{
238+
name: "audio/x-m4a header",
239+
filePath: "voice.m4a",
240+
headerMime: "audio/x-m4a",
242241
expected: "audio/x-m4a",
243-
});
244-
});
245-
246-
it("uses the file extension to rescue isom-brand bytes when no header exists", async () => {
247-
const isomBrand = Buffer.from(
248-
"0000001c6674797069736f6d0000000069736f6d0000000000000000",
249-
"hex",
250-
);
251-
252-
await expectDetectedMime({
253-
input: { buffer: isomBrand, filePath: "voice.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,
254253
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,
255275
});
256276
});
257277

@@ -408,6 +428,7 @@ describe("extensionForMime", () => {
408428
{ mime: "audio/x-wav", expected: ".wav" },
409429
{ mime: "audio/webm", expected: ".webm" },
410430
{ mime: "audio/x-m4a", expected: ".m4a" },
431+
{ mime: "audio/m4a", expected: ".m4a" },
411432
{ mime: "audio/mp4", expected: ".m4a" },
412433
{ mime: "video/x-msvideo", expected: ".avi" },
413434
{ mime: "video/mp4", expected: ".mp4" },

packages/media-core/src/mime.ts

Lines changed: 1 addition & 1 deletion
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",
@@ -90,7 +91,6 @@ const AMBIGUOUS_VIDEO_MIME_BY_AUDIO_MIME: Readonly<Record<string, string>> = {
9091
"audio/mp4": "video/mp4",
9192
"audio/x-m4a": "video/mp4",
9293
"audio/m4a": "video/mp4",
93-
"audio/aac": "video/mp4",
9494
"audio/webm": "video/webm",
9595
};
9696

0 commit comments

Comments
 (0)