From 409de2fe2f0c4f62a8c4d9c907699deac00a069b Mon Sep 17 00:00:00 2001 From: SWAPI03 Date: Fri, 4 Sep 2026 09:41:05 +0530 Subject: [PATCH] fix: label .webm as audio/webm in audio transcription process_audio_file only ever handles audio for transcription, but it derived the MIME type from the file extension via get_file_mime_type_from_extension, which returns the video-biased "video/webm". Vertex Gemini transcription then sends inlineData.mimeType: video/webm and returns an empty transcript for audio-only webm uploads (browser MediaRecorder output). Override the webm extension to audio/webm in this audio-only helper. Other audio containers (ogg, m4a) already resolve to audio/*. Fixes #38963 --- .../litellm_core_utils/audio_utils/utils.py | 13 +++++++++++- .../audio_utils/test_utils.py | 20 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 tests/test_litellm/litellm_core_utils/audio_utils/test_utils.py diff --git a/litellm/litellm_core_utils/audio_utils/utils.py b/litellm/litellm_core_utils/audio_utils/utils.py index dab3e48f91a..ee1903db7fc 100644 --- a/litellm/litellm_core_utils/audio_utils/utils.py +++ b/litellm/litellm_core_utils/audio_utils/utils.py @@ -4,7 +4,9 @@ Utils used for litellm.transcription() and litellm.atranscription() import hashlib import os +from collections.abc import Mapping from dataclasses import dataclass +from types import MappingProxyType from typing import Final from litellm.types.files import ( @@ -16,6 +18,13 @@ from litellm.types.files import ( ) from litellm.types.utils import FileTypes +# webm is an audio/video container; get_file_mime_type_from_extension returns +# "video/webm", but this helper only ever handles audio for transcription, so a +# .webm upload is audio. Labeling it video/webm makes Vertex Gemini +# transcription return an empty transcript. +# https://github.com/BerriAI/litellm/issues/38963 +_AUDIO_CONTAINER_MIME_OVERRIDES: Final[Mapping[str, str]] = MappingProxyType({"webm": "audio/webm"}) + @dataclass class ProcessedAudioFile: @@ -121,7 +130,9 @@ def process_audio_file(audio_file: FileTypes) -> ProcessedAudioFile: try: # Extract extension from filename extension: Final = filename.split(".")[-1].lower() if "." in filename else "wav" - content_type = get_file_mime_type_from_extension(extension) + content_type = _AUDIO_CONTAINER_MIME_OVERRIDES.get(extension) or get_file_mime_type_from_extension( + extension + ) except ValueError: # If extension is not recognized, fallback to audio/wav content_type = "audio/wav" diff --git a/tests/test_litellm/litellm_core_utils/audio_utils/test_utils.py b/tests/test_litellm/litellm_core_utils/audio_utils/test_utils.py new file mode 100644 index 00000000000..5da85d83d4b --- /dev/null +++ b/tests/test_litellm/litellm_core_utils/audio_utils/test_utils.py @@ -0,0 +1,20 @@ +from litellm.litellm_core_utils.audio_utils.utils import process_audio_file + + +def test_process_audio_file_labels_webm_as_audio(): + # Regression for https://github.com/BerriAI/litellm/issues/38963: webm is an + # audio/video container and process_audio_file is audio-only, so a .webm + # upload must be audio/webm. video/webm makes Vertex Gemini transcription + # return an empty transcript + processed = process_audio_file(("speech.webm", b"\x1aE\xdf\xa3")) + assert processed.content_type == "audio/webm" + + +def test_process_audio_file_keeps_known_audio_extension(): + processed = process_audio_file(("speech.wav", b"RIFF")) + assert processed.content_type == "audio/wav" + + +def test_process_audio_file_unknown_extension_falls_back_to_wav(): + processed = process_audio_file(("recording.unknownext", b"x")) + assert processed.content_type == "audio/wav"