test(matching): cover original vs default audio-track selection
New metadata_multi_audio fixture (English original + Spanish default tracks) drives tests for get_best_audio_format, get_best_combined_format and the video+audio split path under both preference values, plus a regression guard that marker-free metadata is unaffected and a unit test for the new parse_media_format flags. Refs #1468
This commit is contained in:
174
tubesync/sync/testdata/metadata_multi_audio.json
vendored
Normal file
174
tubesync/sync/testdata/metadata_multi_audio.json
vendored
Normal file
@@ -0,0 +1,174 @@
|
||||
{
|
||||
"id": "multiaudio",
|
||||
"channel_id": "testchannelid",
|
||||
"title": "multi audio track title",
|
||||
"upload_date": "20260101",
|
||||
"duration": 300.0,
|
||||
"uploader": "test uploader",
|
||||
"playlist_title": "test playlist",
|
||||
"thumbnail": "https://example.com/maxresdefault.webp",
|
||||
"subtitles": {},
|
||||
"automatic_captions": {},
|
||||
"formats": [
|
||||
{
|
||||
"format_id": "251-en",
|
||||
"ext": "webm",
|
||||
"vcodec": "none",
|
||||
"acodec": "opus",
|
||||
"abr": 160,
|
||||
"asr": 48000,
|
||||
"fps": null,
|
||||
"height": null,
|
||||
"width": null,
|
||||
"tbr": 156.0,
|
||||
"language": "en",
|
||||
"format_note": "English original, medium (original)",
|
||||
"format": "251-en - audio only (English original, medium) (original)",
|
||||
"protocol": "https"
|
||||
},
|
||||
{
|
||||
"format_id": "251-es",
|
||||
"ext": "webm",
|
||||
"vcodec": "none",
|
||||
"acodec": "opus",
|
||||
"abr": 160,
|
||||
"asr": 48000,
|
||||
"fps": null,
|
||||
"height": null,
|
||||
"width": null,
|
||||
"tbr": 155.0,
|
||||
"language": "es",
|
||||
"format_note": "Spanish (Spain) (default), medium (default)",
|
||||
"format": "251-es - audio only (Spanish (Spain) (default), medium) (default)",
|
||||
"protocol": "https"
|
||||
},
|
||||
{
|
||||
"format_id": "140-en",
|
||||
"ext": "m4a",
|
||||
"vcodec": "none",
|
||||
"acodec": "mp4a.40.2",
|
||||
"abr": 128,
|
||||
"asr": 44100,
|
||||
"fps": null,
|
||||
"height": null,
|
||||
"width": null,
|
||||
"tbr": 128.0,
|
||||
"language": "en",
|
||||
"format_note": "English original, low (original)",
|
||||
"format": "140-en - audio only (English original, low) (original)",
|
||||
"protocol": "https"
|
||||
},
|
||||
{
|
||||
"format_id": "140-es",
|
||||
"ext": "m4a",
|
||||
"vcodec": "none",
|
||||
"acodec": "mp4a.40.2",
|
||||
"abr": 128,
|
||||
"asr": 44100,
|
||||
"fps": null,
|
||||
"height": null,
|
||||
"width": null,
|
||||
"tbr": 127.0,
|
||||
"language": "es",
|
||||
"format_note": "Spanish (Spain) (default), low (default)",
|
||||
"format": "140-es - audio only (Spanish (Spain) (default), low) (default)",
|
||||
"protocol": "https"
|
||||
},
|
||||
{
|
||||
"format_id": "18-en",
|
||||
"ext": "mp4",
|
||||
"vcodec": "avc1.42001E",
|
||||
"acodec": "mp4a.40.2",
|
||||
"abr": 96,
|
||||
"asr": 44100,
|
||||
"fps": 30,
|
||||
"height": 360,
|
||||
"width": 640,
|
||||
"tbr": 620.0,
|
||||
"language": "en",
|
||||
"format_note": "English original, 360p (original)",
|
||||
"format": "18-en - 640x360 (360p) (original)",
|
||||
"protocol": "https"
|
||||
},
|
||||
{
|
||||
"format_id": "18-es",
|
||||
"ext": "mp4",
|
||||
"vcodec": "avc1.42001E",
|
||||
"acodec": "mp4a.40.2",
|
||||
"abr": 96,
|
||||
"asr": 44100,
|
||||
"fps": 30,
|
||||
"height": 360,
|
||||
"width": 640,
|
||||
"tbr": 618.0,
|
||||
"language": "es",
|
||||
"format_note": "Spanish (Spain) (default), 360p (default)",
|
||||
"format": "18-es - 640x360 (360p) (default)",
|
||||
"protocol": "https"
|
||||
},
|
||||
{
|
||||
"format_id": "22-en",
|
||||
"ext": "mp4",
|
||||
"vcodec": "avc1.64001F",
|
||||
"acodec": "mp4a.40.2",
|
||||
"abr": 192,
|
||||
"asr": 44100,
|
||||
"fps": 30,
|
||||
"height": 720,
|
||||
"width": 1280,
|
||||
"tbr": 2100.0,
|
||||
"language": "en",
|
||||
"format_note": "English original, 720p (original)",
|
||||
"format": "22-en - 1280x720 (720p) (original)",
|
||||
"protocol": "https"
|
||||
},
|
||||
{
|
||||
"format_id": "22-es",
|
||||
"ext": "mp4",
|
||||
"vcodec": "avc1.64001F",
|
||||
"acodec": "mp4a.40.2",
|
||||
"abr": 192,
|
||||
"asr": 44100,
|
||||
"fps": 30,
|
||||
"height": 720,
|
||||
"width": 1280,
|
||||
"tbr": 2098.0,
|
||||
"language": "es",
|
||||
"format_note": "Spanish (Spain) (default), 720p (default)",
|
||||
"format": "22-es - 1280x720 (720p) (default)",
|
||||
"protocol": "https"
|
||||
},
|
||||
{
|
||||
"format_id": "137",
|
||||
"ext": "mp4",
|
||||
"vcodec": "avc1.640028",
|
||||
"acodec": "none",
|
||||
"abr": null,
|
||||
"asr": null,
|
||||
"fps": 30,
|
||||
"height": 1080,
|
||||
"width": 1920,
|
||||
"tbr": 4000.0,
|
||||
"language": null,
|
||||
"format_note": "1080p",
|
||||
"format": "137 - 1920x1080 (1080p)",
|
||||
"protocol": "https"
|
||||
},
|
||||
{
|
||||
"format_id": "248",
|
||||
"ext": "webm",
|
||||
"vcodec": "vp9",
|
||||
"acodec": "none",
|
||||
"abr": null,
|
||||
"asr": null,
|
||||
"fps": 30,
|
||||
"height": 1080,
|
||||
"width": 1920,
|
||||
"tbr": 3000.0,
|
||||
"language": null,
|
||||
"format_note": "1080p",
|
||||
"format": "248 - 1920x1080 (1080p)",
|
||||
"protocol": "https"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -16,6 +16,7 @@ _TESTDATA_FILES = {
|
||||
"60fps": "metadata_60fps.json",
|
||||
"60fps+hdr": "metadata_60fps_hdr.json",
|
||||
"20230629": "metadata_2023-06-29.json",
|
||||
"multi_audio": "metadata_multi_audio.json",
|
||||
"issue499_1080p50": "metadata_issue_499_1080p50.json",
|
||||
"issue499_premium": "metadata_issue_499_premium.json",
|
||||
"expected_nfo": "expected_nfo.xml",
|
||||
|
||||
@@ -2,10 +2,11 @@ import logging
|
||||
from django.test import TestCase
|
||||
from sync.models import Source, Media
|
||||
from sync.choices import (
|
||||
Val, Fallback, SourceResolution,
|
||||
Val, AudioTrack, Fallback, SourceResolution,
|
||||
YouTube_AudioCodec, YouTube_VideoCodec,
|
||||
YouTube_SourceType,
|
||||
)
|
||||
from sync.utils import parse_media_format
|
||||
|
||||
from .fixtures import all_test_metadata
|
||||
|
||||
@@ -313,6 +314,96 @@ class FormatMatchingTestCase(TestCase):
|
||||
self.assertEqual(format_code, expected_format_code)
|
||||
self.assertEqual(match_type, expected_match_type)
|
||||
|
||||
def test_parse_media_format_original_default_flags(self):
|
||||
original = parse_media_format({
|
||||
'format_id': 'a', 'format_note': 'English original, medium (original)',
|
||||
'vcodec': 'none', 'acodec': 'opus',
|
||||
})
|
||||
default = parse_media_format({
|
||||
'format_id': 'b', 'format_note': 'Spanish (Spain) (default), medium (default)',
|
||||
'vcodec': 'none', 'acodec': 'opus',
|
||||
})
|
||||
plain = parse_media_format({
|
||||
'format_id': 'c', 'format_note': 'medium',
|
||||
'vcodec': 'none', 'acodec': 'opus',
|
||||
})
|
||||
self.assertEqual((original['is_original'], original['is_default']), (True, False))
|
||||
self.assertEqual((default['is_original'], default['is_default']), (False, True))
|
||||
self.assertEqual((plain['is_original'], plain['is_default']), (False, False))
|
||||
|
||||
def test_audio_track_preference_audio_only(self):
|
||||
self.source.fallback = Val(Fallback.FAIL)
|
||||
self.source.source_resolution = Val(SourceResolution.AUDIO)
|
||||
self.media.metadata = all_test_metadata['multi_audio']
|
||||
self.media.save()
|
||||
expected_matches = {
|
||||
# (acodec, prefer_audio_track): (match_type, code)
|
||||
(Val(YouTube_AudioCodec.OPUS), Val(AudioTrack.ORIGINAL)): (True, '251-en'),
|
||||
(Val(YouTube_AudioCodec.OPUS), Val(AudioTrack.DEFAULT)): (True, '251-es'),
|
||||
(Val(YouTube_AudioCodec.MP4A), Val(AudioTrack.ORIGINAL)): (True, '140-en'),
|
||||
(Val(YouTube_AudioCodec.MP4A), Val(AudioTrack.DEFAULT)): (True, '140-es'),
|
||||
}
|
||||
for params, expected in expected_matches.items():
|
||||
acodec, prefer_audio_track = params
|
||||
self.source.source_acodec = acodec
|
||||
self.source.prefer_audio_track = prefer_audio_track
|
||||
self.assertEqual(self.media.get_best_audio_format(), expected,
|
||||
msg=f'{acodec} / {prefer_audio_track}')
|
||||
|
||||
def test_audio_track_preference_combined(self):
|
||||
self.source.fallback = Val(Fallback.FAIL)
|
||||
self.source.source_vcodec = Val(YouTube_VideoCodec.AVC1)
|
||||
self.source.source_acodec = Val(YouTube_AudioCodec.MP4A)
|
||||
self.source.prefer_60fps = False
|
||||
self.source.prefer_hdr = False
|
||||
self.media.metadata = all_test_metadata['multi_audio']
|
||||
self.media.save()
|
||||
expected_matches = {
|
||||
# (resolution, prefer_audio_track): (match_type, code)
|
||||
('360p', Val(AudioTrack.ORIGINAL)): (True, '18-en'),
|
||||
('360p', Val(AudioTrack.DEFAULT)): (True, '18-es'),
|
||||
('720p', Val(AudioTrack.ORIGINAL)): (True, '22-en'),
|
||||
('720p', Val(AudioTrack.DEFAULT)): (True, '22-es'),
|
||||
}
|
||||
for params, expected in expected_matches.items():
|
||||
resolution, prefer_audio_track = params
|
||||
self.source.source_resolution = resolution
|
||||
self.source.prefer_audio_track = prefer_audio_track
|
||||
self.assertEqual(self.media.get_best_combined_format(), expected,
|
||||
msg=f'{resolution} / {prefer_audio_track}')
|
||||
|
||||
def test_audio_track_preference_split_video_plus_audio(self):
|
||||
self.source.fallback = Val(Fallback.NEXT_BEST_RESOLUTION)
|
||||
self.source.source_resolution = Val(SourceResolution.VIDEO_1080P)
|
||||
self.source.source_vcodec = Val(YouTube_VideoCodec.VP9)
|
||||
self.source.source_acodec = Val(YouTube_AudioCodec.OPUS)
|
||||
self.media.metadata = all_test_metadata['multi_audio']
|
||||
self.media.save()
|
||||
self.source.prefer_audio_track = Val(AudioTrack.ORIGINAL)
|
||||
self.assertEqual(self.media.get_format_str(), '248+251-en')
|
||||
self.source.prefer_audio_track = Val(AudioTrack.DEFAULT)
|
||||
self.assertEqual(self.media.get_format_str(), '248+251-es')
|
||||
|
||||
def test_audio_track_preference_no_marker_is_noop(self):
|
||||
# 'boring' metadata carries no (original)/(default) markers, so the
|
||||
# preference must not change what gets matched
|
||||
self.source.fallback = Val(Fallback.FAIL)
|
||||
self.media.metadata = all_test_metadata['boring']
|
||||
self.media.save()
|
||||
for prefer in (Val(AudioTrack.ORIGINAL), Val(AudioTrack.DEFAULT)):
|
||||
self.source.prefer_audio_track = prefer
|
||||
self.source.source_resolution = Val(SourceResolution.AUDIO)
|
||||
self.source.source_acodec = Val(YouTube_AudioCodec.OPUS)
|
||||
self.assertEqual(self.media.get_best_audio_format(), (True, '251'), msg=prefer)
|
||||
self.source.source_acodec = Val(YouTube_AudioCodec.MP4A)
|
||||
self.assertEqual(self.media.get_best_audio_format(), (True, '140'), msg=prefer)
|
||||
self.source.source_resolution = Val(SourceResolution.VIDEO_720P)
|
||||
self.source.source_vcodec = Val(YouTube_VideoCodec.AVC1)
|
||||
self.source.source_acodec = Val(YouTube_AudioCodec.MP4A)
|
||||
self.source.prefer_60fps = False
|
||||
self.source.prefer_hdr = False
|
||||
self.assertEqual(self.media.get_best_combined_format(), (True, '22'), msg=prefer)
|
||||
|
||||
def test_video_exact_format_matching(self):
|
||||
self.source.fallback = Val(Fallback.FAIL)
|
||||
# Test no 60fps, no HDR metadata
|
||||
|
||||
@@ -185,6 +185,7 @@ class FrontEndTestCase(TestCase):
|
||||
'source_acodec': 'OPUS',
|
||||
'prefer_60fps': False,
|
||||
'prefer_hdr': False,
|
||||
'prefer_audio_track': 'original',
|
||||
'fallback': 'f',
|
||||
'sponsorblock_categories': data_categories,
|
||||
'sub_langs': 'en',
|
||||
@@ -246,6 +247,7 @@ class FrontEndTestCase(TestCase):
|
||||
'source_acodec': Val(YouTube_AudioCodec.OPUS),
|
||||
'prefer_60fps': False,
|
||||
'prefer_hdr': False,
|
||||
'prefer_audio_track': 'original',
|
||||
'fallback': Val(Fallback.FAIL),
|
||||
'sponsorblock_categories': data_categories,
|
||||
'sub_langs': 'en',
|
||||
@@ -283,6 +285,7 @@ class FrontEndTestCase(TestCase):
|
||||
'source_acodec': Val(YouTube_AudioCodec.OPUS),
|
||||
'prefer_60fps': False,
|
||||
'prefer_hdr': False,
|
||||
'prefer_audio_track': 'original',
|
||||
'fallback': Val(Fallback.FAIL),
|
||||
'sponsorblock_categories': data_categories,
|
||||
'sub_langs': 'en',
|
||||
|
||||
Reference in New Issue
Block a user