mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-29 18:46:59 +00:00
fix(signal): detect M4A-branded voice notes so iOS audio reaches STT
iOS Signal delivers voice notes as MP4-container AAC carrying an audio
ftyp brand ("M4A "). `_guess_extension()` returned ".mp4" for every
`ftyp` file regardless of brand, so those attachments were cached as
documents instead of audio and STT rejected the upload:
API error: Error code: 400 - Invalid file format.
Supported formats: ['flac','m4a','mp3','mp4','mpeg','mpga','oga',
'ogg','wav','webm']
Read the 4-byte brand at offset 8 and return ".m4a" for audio brands
("M4A ", "M4B ") so they satisfy `_is_audio_ext()` and route to
`cache_audio_from_bytes()`. Video brands (isom/mp42/avc1/qt) still
return ".mp4".
This mirrors the existing brand/form-type disambiguation already used
in this function for RIFF (WEBP vs WAVE) and for ADTS AAC vs MP3.
Verified against a real iOS Signal voice note: pre-fix the upload was
rejected with the 400 above; post-fix the same bytes transcribe
successfully.
This commit is contained in:
parent
38b0b7ae3f
commit
6210642297
2 changed files with 28 additions and 0 deletions
|
|
@ -106,6 +106,14 @@ def _guess_extension(data: bytes) -> str:
|
|||
if data[:4] == b"%PDF":
|
||||
return ".pdf"
|
||||
if len(data) >= 8 and data[4:8] == b"ftyp":
|
||||
# iOS Signal delivers voice notes as MP4-container AAC carrying an
|
||||
# audio ftyp brand ("M4A ", "M4B "). Returning ".mp4" for those made
|
||||
# them cache as documents and STT reject them ("Invalid file
|
||||
# format") even though the bytes are valid audio. Read the brand so
|
||||
# audio-branded files land as ".m4a" and route to the audio cache.
|
||||
brand = data[8:12].lower() if len(data) >= 12 else b""
|
||||
if brand in (b"m4a ", b"m4b "):
|
||||
return ".m4a"
|
||||
return ".mp4"
|
||||
if data[:4] == b"OggS":
|
||||
return ".ogg"
|
||||
|
|
|
|||
|
|
@ -201,6 +201,26 @@ class TestSignalHelpers:
|
|||
webp = b"RIFF\x24\x08\x00\x00WEBPVP8 " + b"\x00" * 100
|
||||
assert _guess_extension(webp) == ".webp"
|
||||
|
||||
def test_guess_extension_m4a_audio_brand(self):
|
||||
"""iOS Signal voice notes are MP4-container AAC with an M4A ftyp brand.
|
||||
|
||||
Classifying them as ``.mp4`` sent them to the document cache and made
|
||||
STT reject the upload ("Invalid file format") even though the bytes
|
||||
were valid audio. Audio brands must resolve to ``.m4a``.
|
||||
"""
|
||||
from gateway.platforms.signal import _guess_extension, _is_audio_ext
|
||||
for brand in (b"M4A ", b"M4B ", b"m4a "):
|
||||
data = b"\x00\x00\x00\x1cftyp" + brand + b"\x00" * 100
|
||||
assert _guess_extension(data) == ".m4a", brand
|
||||
assert _is_audio_ext(_guess_extension(data)) is True
|
||||
|
||||
def test_guess_extension_video_brands_stay_mp4(self):
|
||||
"""Real video ftyp brands must not be swept up by the M4A fix."""
|
||||
from gateway.platforms.signal import _guess_extension
|
||||
for brand in (b"isom", b"mp42", b"avc1", b"qt "):
|
||||
data = b"\x00\x00\x00\x1cftyp" + brand + b"\x00" * 100
|
||||
assert _guess_extension(data) == ".mp4", brand
|
||||
|
||||
def test_guess_extension_aac_adts_unprotected(self):
|
||||
"""ADTS AAC, MPEG-4, no CRC (the canonical Android Signal voice note).
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue