diff --git a/conserver/links/wtf_transcribe/__init__.py b/conserver/links/wtf_transcribe/__init__.py index 7323a35..685e83a 100644 --- a/conserver/links/wtf_transcribe/__init__.py +++ b/conserver/links/wtf_transcribe/__init__.py @@ -25,6 +25,7 @@ import json import logging +import mimetypes import requests from typing import Optional, Dict, Any @@ -180,8 +181,33 @@ def dialog_to_audio_binary(dialog, url_timeout): def dialog_filename(dialog, dialog_index): return dialog.get("filename", f"audio_{dialog_index}.wav") -def dialog_mimetype(dialog): - return dialog.get("mimetype", "audio/wav") +def dialog_mediatype(dialog): + """Resolve a dialog's media type. + + Prefers the modern ``mediatype`` field, then falls back to the legacy + ``mimetype`` field (in case a caller passes a pre-compat dialog), then + derives it from the dialog's filename extension, and finally defaults + to audio/wav. + """ + mediatype = dialog.get("mediatype") + if mediatype: + return mediatype + + mimetype = dialog.get("mimetype") + if mimetype: + return mimetype + + filename = dialog.get("filename") + if filename: + guessed_type, _ = mimetypes.guess_type(filename) + if guessed_type and (guessed_type.startswith("audio/") or guessed_type.startswith("video/")): + return guessed_type + + return "audio/wav" + + +# Backwards-compatible alias; prefer dialog_mediatype for new code. +dialog_mimetype = dialog_mediatype def build_vfun_headers(api_key): headers = {} @@ -202,8 +228,8 @@ def maybe_decode_double_encoded_json(response_json): def send_audio_to_vfun(audio_binary, dialog, dialog_index, vfun_server_url, api_key, diarize, language, vfun_timeout): filename = dialog_filename(dialog, dialog_index) - mimetype = dialog_mimetype(dialog) - files = {"file-binary": (filename, audio_binary, mimetype)} + mediatype = dialog_mediatype(dialog) + files = {"file-binary": (filename, audio_binary, mediatype)} headers = build_vfun_headers(api_key) data = build_vfun_data(diarize, language) response = requests.post( diff --git a/conserver/links/wtf_transcribe/test_wtf_transcribe_unit.py b/conserver/links/wtf_transcribe/test_wtf_transcribe_unit.py index 85e3e02..2102ca6 100644 --- a/conserver/links/wtf_transcribe/test_wtf_transcribe_unit.py +++ b/conserver/links/wtf_transcribe/test_wtf_transcribe_unit.py @@ -10,7 +10,7 @@ analysis_dialog_index, analysis_is_wtf_transcription, dialog_filename, - dialog_mimetype, + dialog_mediatype, dialog_to_audio_binary, dialog_to_binary, dialog_to_index, @@ -63,7 +63,27 @@ def test_analysis_and_dialog_helpers_cover_transcribed_and_recording_checks(): def test_file_and_remote_loaders_handle_success_and_failure(): assert is_file_url("file:///tmp/audio.wav") is True assert dialog_filename({}, 3) == "audio_3.wav" - assert dialog_mimetype({}) == "audio/wav" + assert dialog_mediatype({}) == "audio/wav" + + +def test_dialog_mediatype_resolution_order(): + # Modern adapters write "mediatype"; it takes priority. + assert dialog_mediatype({"mediatype": "audio/mpeg"}) == "audio/mpeg" + + # Legacy "mimetype" is honoured when "mediatype" is absent. + assert dialog_mediatype({"mimetype": "audio/mpeg"}) == "audio/mpeg" + + # "mediatype" wins over a legacy "mimetype" when both are present. + assert dialog_mediatype({"mediatype": "audio/mpeg", "mimetype": "audio/wav"}) == "audio/mpeg" + + # Neither field present: derive from the filename extension. + assert dialog_mediatype({"filename": "clip.ogg"}) == "audio/ogg" + + # A non-audio/video guess from the filename doesn't count; fall through to default. + assert dialog_mediatype({"filename": "clip.txt"}) == "audio/wav" + + # Nothing at all gives the audio/wav default. + assert dialog_mediatype({}) == "audio/wav" with patch("builtins.open", mock_open(read_data=b"file-bytes")): assert maybe_load_file_url("file:///tmp/audio.wav") == b"file-bytes"