refactor: rename Source.prefer_audio_track to audio_track

Per review: 'prefer_audio_track' reads like the prefer_60fps/prefer_hdr
booleans, but this is a pick-one choice. Renamed to audio_track and
regenerated the migration (as 0038_source_audio_track) - it had drifted
after the switch to single-letter values: default='original' and the long
choices under max_length=1 would fail the AddField back-fill on Postgres.

Also addresses the lint findings in the touched files:
- matching.py: collapse nested prefer_60fps/prefer_hdr ifs (SIM102),
  fold matched_resolution and the video fallback branches (SIM114)
- test_frontend.py: str.removeprefix, dict.values(), and a DTZ001 noqa
  on the deliberately naive datetime that make_aware() wraps
This commit is contained in:
masterdot
2026-09-08 16:23:16 +02:00
parent 9a0c8d9e3a
commit b6ed2a1894
8 changed files with 61 additions and 83 deletions

View File

@@ -19,7 +19,7 @@ SourceForm = forms.modelform_factory(
'source_type', 'key', 'name', 'directory', 'filter_text', 'filter_text_invert', 'filter_seconds', 'filter_seconds_min',
'media_format', 'target_schedule', 'index_schedule', 'index_videos', 'index_streams', 'download_media',
'download_cap', 'delete_old_media', 'days_to_keep', 'source_resolution', 'source_vcodec', 'source_acodec',
'prefer_60fps', 'prefer_hdr', 'prefer_audio_track', 'fallback', 'delete_removed_media', 'delete_files_on_disk', 'copy_channel_images',
'prefer_60fps', 'prefer_hdr', 'audio_track', 'fallback', 'delete_removed_media', 'delete_files_on_disk', 'copy_channel_images',
'copy_thumbnails', 'write_nfo', 'write_json', 'embed_metadata', 'embed_thumbnail',
'enable_sponsorblock', 'sponsorblock_categories', 'write_subtitles', 'auto_subtitles', 'sub_langs',
),

View File

@@ -71,13 +71,11 @@ def get_best_combined_format(media):
if media.source.source_acodec != fmt['acodec']:
continue
# if the source prefers 60fps, check for it
if media.source.prefer_60fps:
if not fmt['is_60fps']:
continue
if media.source.prefer_60fps and not fmt['is_60fps']:
continue
# If the source prefers HDR, check for it
if media.source.prefer_hdr:
if not fmt['is_hdr']:
continue
if media.source.prefer_hdr and not fmt['is_hdr']:
continue
# If we reach here, we have a combined match!
matches.add(fmt['id'])
by_fmt_id[fmt['id']] = fmt
@@ -92,7 +90,7 @@ def get_best_combined_format(media):
return False, False
# honour the source's audio-track preference
prefer = Val(media.source.prefer_audio_track)
prefer = Val(media.source.audio_track)
preferred = by_fmt_id.get(f'~{prefer}~')
if preferred and 'id' in preferred:
return True, preferred['id']
@@ -133,7 +131,7 @@ def get_best_audio_format(media):
# Media has no audio formats at all
return False, False
source_acodec = media.source.source_acodec
prefer = Val(media.source.prefer_audio_track)
prefer = Val(media.source.audio_track)
# Among formats with the requested codec, honour the audio-track preference.
# A codec match still beats the track preference: if the source wants OPUS
# and only the "original" track is MP4A, the OPUS "default" track wins.
@@ -171,11 +169,10 @@ def get_best_video_format(media):
media.source.fallback != Val(Fallback.REQUIRE_CODEC)
)
def matched_resolution(fmt):
if fmt['format'] == source_resolution:
return True
elif fmt['height'] == source_resolution_height:
return True
return False
return (
fmt['format'] == source_resolution or
fmt['height'] == source_resolution_height
)
# Filter video-only formats by resolution that matches the source
video_formats = []
sort_keys = [('height', False), ('vcodec', True), ('vbr', False)] # key, reverse
@@ -514,13 +511,14 @@ def get_best_video_format(media):
return True, best_match['id']
elif media.source.can_fallback:
# Allow the fallback if it meets requirements
if (media.source.fallback == Val(Fallback.REQUIRE_HD) and
best_match['height'] >= fallback_hd_cutoff):
return False, best_match['id']
elif (media.source.fallback == Val(Fallback.REQUIRE_CODEC) and
source_vcodec == best_match['vcodec']):
return False, best_match['id']
elif media.source.fallback == Val(Fallback.NEXT_BEST_RESOLUTION):
accept_fallback = (
(media.source.fallback == Val(Fallback.REQUIRE_HD) and
best_match['height'] >= fallback_hd_cutoff) or
(media.source.fallback == Val(Fallback.REQUIRE_CODEC) and
source_vcodec == best_match['vcodec']) or
(media.source.fallback == Val(Fallback.NEXT_BEST_RESOLUTION))
)
if accept_fallback:
return False, best_match['id']
# Nope, failed to find match
return False, False

View File

@@ -0,0 +1,18 @@
# Generated by Django 6.0.8 on 2026-09-08 14:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sync', '0037_alter_source_fallback'),
]
operations = [
migrations.AddField(
model_name='source',
name='audio_track',
field=models.CharField(choices=[('o', 'Original audio (recorded language)'), ('d', "Publisher's default audio (often an auto-dub)")], db_index=True, default='o', help_text='Which audio track to download when the source publishes more than one. "Original audio" is the track in the language the video was actually recorded in. "Publisher\'s default audio" is whichever track the uploader set as the default for viewers, which for many channels is an automatic dubbed translation. This setting has no effect when a video only offers a single audio track.', max_length=1, verbose_name='audio track'),
),
]

View File

@@ -1,35 +0,0 @@
# Generated by Django 5.2.11 on 2026-09-07 00:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sync', '0037_alter_source_fallback'),
]
operations = [
migrations.AddField(
model_name='source',
name='prefer_audio_track',
field=models.CharField(
choices=[
('original', 'Original audio (recorded language)'),
('default', "Publisher's default audio (often an auto-dub)"),
],
db_index=True,
default='original',
help_text=(
'Which audio track to download when the source publishes more than one. '
'"Original audio" is the track in the language the video was actually '
'recorded in. "Publisher\'s default audio" is whichever track the uploader '
'set as the default for viewers, which for many channels is an automatic '
'dubbed translation. This setting has no effect when a video only offers a '
'single audio track.'
),
max_length=1,
verbose_name='prefer audio track',
),
),
]

View File

@@ -252,8 +252,8 @@ class Source(db.models.Model):
default=False,
help_text=_('Where possible, prefer HDR media for this source'),
)
prefer_audio_track = db.models.CharField(
_('prefer audio track'),
audio_track = db.models.CharField(
_('audio track'),
max_length=1,
db_index=True,
choices=AudioTrack.choices,
@@ -483,7 +483,7 @@ class Source(db.models.Model):
ac = self.source_acodec
f = ' 60FPS' if self.is_video and self.prefer_60fps else ''
h = ' HDR' if self.is_video and self.prefer_hdr else ''
a = '' if self.prefer_audio_track == Val(AudioTrack.ORIGINAL) else ' DEFAULT-AUDIO'
a = '' if self.audio_track == Val(AudioTrack.ORIGINAL) else ' DEFAULT-AUDIO'
return f'{self.source_resolution} (video:{vc}, audio:{ac}){f}{h}{a}'.strip()
@property

View File

@@ -120,8 +120,8 @@
<td><span class="hide-on-med-and-up">Prefer HDR?<br></span><strong>{% if source.prefer_hdr %}<i class="fas fa-check"></i>{% else %}<i class="fas fa-times"></i>{% endif %}</strong></td>
</tr>
<tr title="Which audio track to download when a video has more than one">
<td class="hide-on-small-only">Preferred audio track</td>
<td><span class="hide-on-med-and-up">Preferred audio track<br></span><strong>{{ source.get_prefer_audio_track_display }}</strong></td>
<td class="hide-on-small-only">Audio track</td>
<td><span class="hide-on-med-and-up">Audio track<br></span><strong>{{ source.get_audio_track_display }}</strong></td>
</tr>
<tr title="Output file extension">
<td class="hide-on-small-only">Output extension</td>

View File

@@ -337,18 +337,18 @@ class FormatMatchingTestCase(TestCase):
self.media.metadata = all_test_metadata['multi_audio']
self.media.save()
expected_matches = {
# (acodec, prefer_audio_track): (match_type, code)
# (acodec, 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
acodec, audio_track = params
self.source.source_acodec = acodec
self.source.prefer_audio_track = prefer_audio_track
self.source.audio_track = audio_track
self.assertEqual(self.media.get_best_audio_format(), expected,
msg=f'{acodec} / {prefer_audio_track}')
msg=f'{acodec} / {audio_track}')
def test_audio_track_preference_combined(self):
self.source.fallback = Val(Fallback.FAIL)
@@ -359,18 +359,18 @@ class FormatMatchingTestCase(TestCase):
self.media.metadata = all_test_metadata['multi_audio']
self.media.save()
expected_matches = {
# (resolution, prefer_audio_track): (match_type, code)
# (resolution, 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
resolution, audio_track = params
self.source.source_resolution = resolution
self.source.prefer_audio_track = prefer_audio_track
self.source.audio_track = audio_track
self.assertEqual(self.media.get_best_combined_format(), expected,
msg=f'{resolution} / {prefer_audio_track}')
msg=f'{resolution} / {audio_track}')
def test_audio_track_preference_split_video_plus_audio(self):
self.source.fallback = Val(Fallback.NEXT_BEST_RESOLUTION)
@@ -379,9 +379,9 @@ class FormatMatchingTestCase(TestCase):
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.source.audio_track = Val(AudioTrack.ORIGINAL)
self.assertEqual(self.media.get_format_str(), '248+251-en')
self.source.prefer_audio_track = Val(AudioTrack.DEFAULT)
self.source.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):
@@ -391,7 +391,7 @@ class FormatMatchingTestCase(TestCase):
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.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)

View File

@@ -118,7 +118,7 @@ class FrontEndTestCase(TestCase):
c = Client()
response = c.get('/source-validate')
self.assertEqual(response.status_code, 200)
for (source_type, tests) in test_sources.items():
for tests in test_sources.values():
for test, urls in tests.items():
for url in urls:
data = {'source_url': url}
@@ -185,7 +185,7 @@ class FrontEndTestCase(TestCase):
'source_acodec': 'OPUS',
'prefer_60fps': False,
'prefer_hdr': False,
'prefer_audio_track': 'o',
'audio_track': 'o',
'fallback': 'f',
'sponsorblock_categories': data_categories,
'sub_langs': 'en',
@@ -194,8 +194,7 @@ class FrontEndTestCase(TestCase):
self.assertEqual(response.status_code, 302)
url_parts = urlsplit(response.url)
url_path = str(url_parts.path).strip()
if url_path.startswith('/'):
url_path = url_path[1:]
url_path = url_path.removeprefix('/')
path_parts = url_path.split('/')
self.assertEqual(path_parts[0], 'source')
source_uuid = path_parts[1]
@@ -247,7 +246,7 @@ class FrontEndTestCase(TestCase):
'source_acodec': Val(YouTube_AudioCodec.OPUS),
'prefer_60fps': False,
'prefer_hdr': False,
'prefer_audio_track': 'o',
'audio_track': 'o',
'fallback': Val(Fallback.FAIL),
'sponsorblock_categories': data_categories,
'sub_langs': 'en',
@@ -256,8 +255,7 @@ class FrontEndTestCase(TestCase):
self.assertEqual(response.status_code, 302)
url_parts = urlsplit(response.url)
url_path = str(url_parts.path).strip()
if url_path.startswith('/'):
url_path = url_path[1:]
url_path = url_path.removeprefix('/')
path_parts = url_path.split('/')
self.assertEqual(path_parts[0], 'source')
source_uuid = path_parts[1]
@@ -285,7 +283,7 @@ class FrontEndTestCase(TestCase):
'source_acodec': Val(YouTube_AudioCodec.OPUS),
'prefer_60fps': False,
'prefer_hdr': False,
'prefer_audio_track': 'o',
'audio_track': 'o',
'fallback': Val(Fallback.FAIL),
'sponsorblock_categories': data_categories,
'sub_langs': 'en',
@@ -294,8 +292,7 @@ class FrontEndTestCase(TestCase):
self.assertEqual(response.status_code, 302)
url_parts = urlsplit(response.url)
url_path = str(url_parts.path).strip()
if url_path.startswith('/'):
url_path = url_path[1:]
url_path = url_path.removeprefix('/')
path_parts = url_path.split('/')
self.assertEqual(path_parts[0], 'source')
source_uuid = path_parts[1]
@@ -352,7 +349,7 @@ class FrontEndTestCase(TestCase):
# Add some media
test_minimal_metadata = all_test_metadata['minimal']
before_dt = timezone.now()
past_date = timezone.make_aware(datetime(year=2000, month=1, day=1))
past_date = timezone.make_aware(datetime(year=2000, month=1, day=1)) # noqa: DTZ001
test_media1 = Media.objects.create(
key='mediakey1',
source=test_source,