From 719f0bf46ae19f868ab604582fc1e04afbfb85fa Mon Sep 17 00:00:00 2001 From: wimby Date: Fri, 25 Sep 2026 20:34:08 +0200 Subject: [PATCH] fix: derive media content type from container --- tubesync/sync/models/media.py | 49 ++++++++++++++++++------------- tubesync/sync/tests/test_media.py | 18 ++++++++++++ 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/tubesync/sync/models/media.py b/tubesync/sync/models/media.py index 5f98ea1e..e27a8a6e 100644 --- a/tubesync/sync/models/media.py +++ b/tubesync/sync/models/media.py @@ -928,26 +928,35 @@ class Media(models.Model): @property def content_type(self): - if not self.downloaded: - return 'video/mp4' - vcodec = self.downloaded_video_codec - if vcodec is None: - acodec = self.downloaded_audio_codec - if acodec is None: - raise TypeError() # nothing here. - acodec = acodec.upper() - if acodec == Val(YouTube_AudioCodec.MP4A): - return "audio/mp4" - elif acodec == Val(YouTube_AudioCodec.OPUS): - return "audio/opus" - else: - # fall-fall-back. - return 'audio/ogg' - vcodec = vcodec.upper() - if vcodec == Val(YouTube_VideoCodec.AVC1): - return 'video/mp4' - else: - return 'video/matroska' + """Return a MIME type without requiring downloaded codec metadata. + + Imported media can be marked downloaded while codec fields are empty. + MIME type is determined primarily by the container/file extension. + """ + container = (self.downloaded_container or '').strip().lower() + if not container and self.media_file: + container = Path(self.media_file.name).suffix.lstrip('.').lower() + + if self.source.is_audio: + return { + 'm4a': 'audio/mp4', + 'mp4': 'audio/mp4', + 'ogg': 'audio/ogg', + 'oga': 'audio/ogg', + 'opus': 'audio/ogg', + 'webm': 'audio/webm', + 'mp3': 'audio/mpeg', + }.get(container, 'audio/ogg') + + return { + 'mp4': 'video/mp4', + 'm4v': 'video/mp4', + 'mov': 'video/quicktime', + 'mkv': 'video/x-matroska', + 'matroska': 'video/x-matroska', + 'webm': 'video/webm', + 'avi': 'video/x-msvideo', + }.get(container, 'video/mp4') @property def nfoxml(self): diff --git a/tubesync/sync/tests/test_media.py b/tubesync/sync/tests/test_media.py index 24e7a1d4..ce8353d3 100644 --- a/tubesync/sync/tests/test_media.py +++ b/tubesync/sync/tests/test_media.py @@ -48,6 +48,24 @@ class MediaTestCase(TestCase): self.media.created = datetime(year=2020, month=1, day=1, hour=1, minute=1, second=1) + def test_content_type_uses_video_container_without_codec_metadata(self): + self.media.downloaded = True + self.media.downloaded_container = 'mkv' + self.media.downloaded_video_codec = None + self.media.downloaded_audio_codec = None + + self.assertEqual(self.media.content_type, 'video/x-matroska') + + def test_content_type_uses_audio_container_without_codec_metadata(self): + self.source.source_resolution = Val(SourceResolution.AUDIO) + self.source.save(update_fields=['source_resolution']) + self.media.downloaded = True + self.media.downloaded_container = 'mp3' + self.media.downloaded_video_codec = None + self.media.downloaded_audio_codec = None + + self.assertEqual(self.media.content_type, 'audio/mpeg') + def test_nfo(self): expected_tree = ElementTree.fromstring(all_test_metadata['expected_nfo']) nfo_tree = ElementTree.fromstring(self.media.nfoxml)