diff --git a/litellm/litellm_core_utils/prompt_templates/common_utils.py b/litellm/litellm_core_utils/prompt_templates/common_utils.py index 2485896184e..47211067c77 100644 --- a/litellm/litellm_core_utils/prompt_templates/common_utils.py +++ b/litellm/litellm_core_utils/prompt_templates/common_utils.py @@ -1565,6 +1565,15 @@ def infer_content_type_from_url_and_content( "png": "image/png", "gif": "image/gif", "webp": "image/webp", + "mp4": "video/mp4", + "mov": "video/mov", + "mkv": "video/mkv", + "webm": "video/webm", + "flv": "video/flv", + "mpeg": "video/mpeg", + "mpg": "video/mpg", + "wmv": "video/wmv", + "3gp": "video/3gp", # Document formats "pdf": "application/pdf", "csv": "text/csv", diff --git a/litellm/litellm_core_utils/prompt_templates/factory.py b/litellm/litellm_core_utils/prompt_templates/factory.py index 67d78c0cb8f..493f19faf5b 100644 --- a/litellm/litellm_core_utils/prompt_templates/factory.py +++ b/litellm/litellm_core_utils/prompt_templates/factory.py @@ -4372,9 +4372,6 @@ class BedrockConverseMessagesProcessor: ) _parts.append(_part) elif element["type"] == "video_url": - # see the sync path: video shares the image - # processor, which keys the block type off the - # mime type. video_element = element["video_url"] if isinstance(video_element, dict): video_url = video_element["url"] @@ -4760,10 +4757,6 @@ def _bedrock_converse_messages_pt( ) _parts.append(_part) elif element["type"] == "video_url": - # OpenAI `video_url` parts must reach Converse as - # video blocks too. `process_image_sync` picks the - # block type from the mime type (video/* -> video, - # image/* -> image), so a video shares this path. video_element = element["video_url"] if isinstance(video_element, dict): video_url = video_element["url"] diff --git a/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_common_utils.py b/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_common_utils.py index b5890d1a5b0..b3b5b08e8da 100644 --- a/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_common_utils.py +++ b/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_common_utils.py @@ -19,6 +19,7 @@ from litellm.litellm_core_utils.prompt_templates.common_utils import ( get_format_from_file_id, handle_any_messages_to_chat_completion_str_messages_conversion, hoist_images_from_tool_messages, + infer_content_type_from_url_and_content, is_encrypted_reasoning_block, responses_reasoning_items_from_thinking_blocks, split_concatenated_json_objects, @@ -1813,3 +1814,35 @@ class TestEncryptedReasoningReplay: strip_encrypted_reasoning_from_messages(messages) assert messages == before + + +@pytest.mark.parametrize( + "url,expected", + [ + ("https://example.com/clip.mp4", "video/mp4"), + ("https://example.com/clip.webm?X-Amz-Signature=abc123", "video/webm"), + ("https://example.com/clip.mov", "video/mov"), + ("https://example.com/clip.mkv", "video/mkv"), + ("https://example.com/clip.3gp", "video/3gp"), + ], +) +def test_infer_video_content_type_from_url_extension(url, expected): + assert ( + infer_content_type_from_url_and_content( + url=url, + content=b"\x00\x00\x00\x18ftypmp42", + current_content_type="binary/octet-stream", + ) + == expected + ) + + +def test_infer_video_content_type_from_url_extension_without_header(): + assert ( + infer_content_type_from_url_and_content( + url="https://example.com/clip.mp4", + content=b"\x00\x00\x00\x18ftypmp42", + current_content_type=None, + ) + == "video/mp4" + )