mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-01 12:30:21 +00:00
fix(line): land #31151 M4A voice MIME detection (@scoootscooob)
Landed from contributor PR #31151 by @scoootscooob. Co-authored-by: scoootscooob <scoootscooob@users.noreply.github.com>
This commit is contained in:
@@ -66,4 +66,54 @@ describe("downloadLineMedia", () => {
|
||||
await expect(downloadLineMedia("mid", "token", 7)).rejects.toThrow(/Media exceeds/i);
|
||||
expect(writeSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("detects M4A audio from ftyp major brand (#29751)", async () => {
|
||||
// Real M4A magic bytes: size(4) + "ftyp" + "M4A "
|
||||
const m4a = Buffer.from([
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x1c, // box size
|
||||
0x66,
|
||||
0x74,
|
||||
0x79,
|
||||
0x70, // "ftyp"
|
||||
0x4d,
|
||||
0x34,
|
||||
0x41,
|
||||
0x20, // "M4A " major brand
|
||||
]);
|
||||
getMessageContentMock.mockResolvedValueOnce(chunks([m4a]));
|
||||
vi.spyOn(fs.promises, "writeFile").mockResolvedValueOnce(undefined);
|
||||
|
||||
const result = await downloadLineMedia("mid-m4a", "token");
|
||||
|
||||
expect(result.contentType).toBe("audio/mp4");
|
||||
expect(result.path).toMatch(/\.m4a$/);
|
||||
});
|
||||
|
||||
it("detects MP4 video from ftyp major brand (isom)", async () => {
|
||||
// MP4 video magic bytes: size(4) + "ftyp" + "isom"
|
||||
const mp4 = Buffer.from([
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x1c,
|
||||
0x66,
|
||||
0x74,
|
||||
0x79,
|
||||
0x70,
|
||||
0x69,
|
||||
0x73,
|
||||
0x6f,
|
||||
0x6d, // "isom" major brand
|
||||
]);
|
||||
getMessageContentMock.mockResolvedValueOnce(chunks([mp4]));
|
||||
vi.spyOn(fs.promises, "writeFile").mockResolvedValueOnce(undefined);
|
||||
|
||||
const result = await downloadLineMedia("mid-mp4", "token");
|
||||
|
||||
expect(result.contentType).toBe("video/mp4");
|
||||
expect(result.path).toMatch(/\.mp4$/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -80,15 +80,20 @@ function detectContentType(buffer: Buffer): string {
|
||||
) {
|
||||
return "image/webp";
|
||||
}
|
||||
// MP4
|
||||
if (buffer[4] === 0x66 && buffer[5] === 0x74 && buffer[6] === 0x79 && buffer[7] === 0x70) {
|
||||
return "video/mp4";
|
||||
}
|
||||
// M4A/AAC
|
||||
if (buffer[0] === 0x00 && buffer[1] === 0x00 && buffer[2] === 0x00) {
|
||||
if (buffer[4] === 0x66 && buffer[5] === 0x74 && buffer[6] === 0x79 && buffer[7] === 0x70) {
|
||||
// MPEG-4 container (ftyp box) — distinguish audio (M4A) from video (MP4)
|
||||
// by checking the major brand at bytes 8-11.
|
||||
if (
|
||||
buffer.length >= 12 &&
|
||||
buffer[4] === 0x66 &&
|
||||
buffer[5] === 0x74 &&
|
||||
buffer[6] === 0x79 &&
|
||||
buffer[7] === 0x70
|
||||
) {
|
||||
const brand = String.fromCharCode(buffer[8], buffer[9], buffer[10], buffer[11]);
|
||||
if (brand === "M4A " || brand === "M4B ") {
|
||||
return "audio/mp4";
|
||||
}
|
||||
return "video/mp4";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user