fix: Fixed audio file suffix when quality is 'best' and minor UI fixes

This commit is contained in:
TonyBlur 2026-05-07 07:25:54 +08:00
parent 8723ab0afe
commit 0311df0b06
5 changed files with 83 additions and 58 deletions

View file

@ -211,9 +211,13 @@ class QualitySuffixValueTests(unittest.TestCase):
def test_video_numeric_quality_gets_p_suffix(self): def test_video_numeric_quality_gets_p_suffix(self):
self.assertEqual(_quality_suffix_value(self._download_info(quality="2160")), "2160p") self.assertEqual(_quality_suffix_value(self._download_info(quality="2160")), "2160p")
def test_audio_best_stays_best(self): def test_audio_best_uses_actual_bitrate_template(self):
info = self._download_info(download_type="audio", quality="best") info = self._download_info(download_type="audio", quality="best")
self.assertEqual(_quality_suffix_value(info), "best") self.assertEqual(_quality_suffix_value(info), "%(abr).0fkbps")
def test_audio_numeric_quality_gets_kbps_suffix(self):
info = self._download_info(download_type="audio", quality="192")
self.assertEqual(_quality_suffix_value(info), "192kbps")
class CodecSuffixValueTests(unittest.TestCase): class CodecSuffixValueTests(unittest.TestCase):
@ -223,7 +227,7 @@ class CodecSuffixValueTests(unittest.TestCase):
title="t", title="t",
url="http://example.com/v", url="http://example.com/v",
quality="best", quality="best",
download_type="video", download_type=kwargs.pop("download_type", "video"),
codec=kwargs.pop("codec", "auto"), codec=kwargs.pop("codec", "auto"),
format="any", format="any",
folder="", folder="",
@ -250,6 +254,10 @@ class CodecSuffixValueTests(unittest.TestCase):
info = self._download_info(entry={"vcodec": "some codec/name"}) info = self._download_info(entry={"vcodec": "some codec/name"})
self.assertEqual(_codec_suffix_value(info), "some_codec_name") self.assertEqual(_codec_suffix_value(info), "some_codec_name")
def test_audio_has_no_codec_suffix(self):
info = self._download_info(download_type="audio", codec="auto", entry={"acodec": "mp4a.40.2"})
self.assertEqual(_codec_suffix_value(info), "")
class DownloadInfoSetstateTests(unittest.TestCase): class DownloadInfoSetstateTests(unittest.TestCase):
def _base_state(self, **kwargs): def _base_state(self, **kwargs):

View file

@ -377,15 +377,24 @@ def _download_info_from_record(record: dict[str, Any]) -> DownloadInfo:
def _quality_suffix_value(dl: DownloadInfo) -> str: def _quality_suffix_value(dl: DownloadInfo) -> str:
quality = str(getattr(dl, 'quality', '') or '') quality = str(getattr(dl, 'quality', '') or '')
if getattr(dl, 'download_type', '') == 'video': download_type = getattr(dl, 'download_type', '')
if download_type == 'video':
if quality == 'best': if quality == 'best':
return '%(height)sp' return '%(height)sp'
if re.fullmatch(r'\d+', quality): if re.fullmatch(r'\d+', quality):
return f'{quality}p' return f'{quality}p'
if download_type == 'audio':
if quality == 'best':
return '%(abr).0fkbps'
if re.fullmatch(r'\d+', quality):
return f'{quality}kbps'
return quality return quality
def _codec_suffix_value(dl: DownloadInfo) -> str: def _codec_suffix_value(dl: DownloadInfo) -> str:
if getattr(dl, 'download_type', '') != 'video':
return ''
codec_value = str(getattr(dl, 'codec', '') or '') codec_value = str(getattr(dl, 'codec', '') or '')
if codec_value.lower() != 'auto': if codec_value.lower() != 'auto':
return codec_value return codec_value

View file

@ -1,8 +1,6 @@
version: '3.8'
services: services:
mytube: mytube:
image: mytube image: tonyblu/mytube:latest
container_name: mytube container_name: mytube
restart: unless-stopped restart: unless-stopped
ports: ports:
@ -28,64 +26,66 @@ services:
# - /path/to/robots.txt:/etc/robots.txt:ro # - /path/to/robots.txt:/etc/robots.txt:ro
environment: environment:
ENV_FILE: ""
# === Container Setup === # === Container Setup ===
# - PUID=1000 # PUID: "1000"
# - PGID=1000 # PGID: "1000"
# - UMASK=022 # UMASK: "022"
# - DEFAULT_THEME=auto # DEFAULT_THEME: auto
# - LOGLEVEL=INFO # LOGLEVEL: INFO
# - ENABLE_ACCESSLOG=false # ENABLE_ACCESSLOG: "false"
# === Directory Configuration === # === Directory Configuration ===
# - DOWNLOAD_DIR=/downloads # DOWNLOAD_DIR: /downloads
# - STATE_DIR=/downloads/.metube # STATE_DIR: /downloads/.metube
# - TEMP_DIR=/downloads # TEMP_DIR: /downloads
# - AUDIO_DOWNLOAD_DIR=/audio # AUDIO_DOWNLOAD_DIR: /audio
# === Web Server === # === Web Server ===
# - HOST=0.0.0.0 # HOST: 0.0.0.0
# - PORT=8081 # PORT: "8081"
- PUBLIC_MODE=true # PUBLIC_MODE: "false"
# - URL_PREFIX=/mytube/ # URL_PREFIX: /mytube/
# - PUBLIC_HOST_URL=download/ # PUBLIC_HOST_URL: download/
# - PUBLIC_HOST_AUDIO_URL=audio_download/ # PUBLIC_HOST_AUDIO_URL: audio_download/
# - HTTPS=false # HTTPS: "false"
# - CERTFILE=/ssl/crt.pem # CERTFILE: /ssl/crt.pem
# - KEYFILE=/ssl/key.pem # KEYFILE: /ssl/key.pem
# - CORS_ALLOWED_ORIGINS= # CORS_ALLOWED_ORIGINS: ""
# - ROBOTS_TXT= # ROBOTS_TXT: ""
# === Download Behavior === # === Download Behavior ===
# - MAX_CONCURRENT_DOWNLOADS=3 # MAX_CONCURRENT_DOWNLOADS: "3"
# - DELETE_FILE_ON_TRASHCAN=false # DELETE_FILE_ON_TRASHCAN: "false"
# - DEFAULT_OPTION_PLAYLIST_ITEM_LIMIT=0 # DEFAULT_OPTION_PLAYLIST_ITEM_LIMIT: "0"
# - SUBSCRIPTION_DEFAULT_CHECK_INTERVAL=60 # SUBSCRIPTION_DEFAULT_CHECK_INTERVAL: "60"
# - SUBSCRIPTION_SCAN_PLAYLIST_END=50 # SUBSCRIPTION_SCAN_PLAYLIST_END: "50"
# - SUBSCRIPTION_MAX_SEEN_IDS=50000 # SUBSCRIPTION_MAX_SEEN_IDS: "50000"
# - CLEAR_COMPLETED_AFTER=0 # CLEAR_COMPLETED_AFTER: "0"
# === File Naming & Output === # === File Naming & Output ===
# - OUTPUT_TEMPLATE=%(title)s.%(ext)s # OUTPUT_TEMPLATE: "%(title)s.%(ext)s"
# - OUTPUT_TEMPLATE_CHAPTER=%(title)s - %(section_number)02d - %(section_title)s.%(ext)s # OUTPUT_TEMPLATE_CHAPTER: "%(title)s - %(section_number)02d - %(section_title)s.%(ext)s"
# - OUTPUT_TEMPLATE_PLAYLIST=%(playlist_title)s/%(title)s.%(ext)s # OUTPUT_TEMPLATE_PLAYLIST: "%(playlist_title)s/%(title)s.%(ext)s"
# - OUTPUT_TEMPLATE_CHANNEL=%(channel)s/%(title)s.%(ext)s # OUTPUT_TEMPLATE_CHANNEL: "%(channel)s/%(title)s.%(ext)s"
# === yt-dlp Options === # === yt-dlp Options ===
# - YTDL_OPTIONS={} # YTDL_OPTIONS: "{}"
# - YTDL_OPTIONS_FILE=/config/ytdl-options.json # YTDL_OPTIONS_FILE: /config/ytdl-options.json
# - YTDL_OPTIONS_PRESETS={} # YTDL_OPTIONS_PRESETS: "{}"
# - YTDL_OPTIONS_PRESETS_FILE=/config/ytdl-presets.json # YTDL_OPTIONS_PRESETS_FILE: /config/ytdl-presets.json
# - ALLOW_YTDL_OPTIONS_OVERRIDES=false # ALLOW_YTDL_OPTIONS_OVERRIDES: "false"
# === Directory Features === # === Directory Features ===
# - CUSTOM_DIRS=true # CUSTOM_DIRS: "true"
# - CREATE_CUSTOM_DIRS=true # CREATE_CUSTOM_DIRS: "true"
# - DOWNLOAD_DIRS_INDEXABLE=false # DOWNLOAD_DIRS_INDEXABLE: "false"
# - CUSTOM_DIRS_EXCLUDE_REGEX=(^|/)[.@].*$ # CUSTOM_DIRS_EXCLUDE_REGEX: "(^|/)[.@].*$"
# - CHOWN_DIRS=true # CHOWN_DIRS: "true"
# === Custom Environment File === # === Custom Environment File ===
# - ENV_FILE=/path/to/custom.env # ENV_FILE: /path/to/custom.env
healthcheck: healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:${PORT:-8081}/"] test: ["CMD", "curl", "-fsS", "http://localhost:${PORT:-8081}/"]

View file

@ -273,7 +273,8 @@
[(ngModel)]="format" [(ngModel)]="format"
(change)="formatChanged()" (change)="formatChanged()"
[disabled]="addInProgress || subscribeInProgress || downloads.loading" [disabled]="addInProgress || subscribeInProgress || downloads.loading"
ngbTooltip="Subtitle output format for captions mode"> ngbTooltip="Subtitle output format for captions mode"
container="body">
@for (f of formatOptions; track f.id) { @for (f of formatOptions; track f.id) {
<option [ngValue]="f.id">{{ f.text }}</option> <option [ngValue]="f.id">{{ f.text }}</option>
} }
@ -291,13 +292,14 @@
(change)="subtitleLanguageChanged()" (change)="subtitleLanguageChanged()"
[disabled]="addInProgress || subscribeInProgress || downloads.loading" [disabled]="addInProgress || subscribeInProgress || downloads.loading"
placeholder="e.g. en, es, zh-Hans" placeholder="e.g. en, es, zh-Hans"
ngbTooltip="Subtitle language (you can type any language code)"> ngbTooltip="Subtitle language (you can type any language code)"
<datalist id="subtitleLanguageOptions"> container="body">
@for (lang of subtitleLanguages; track lang.id) {
<option [value]="lang.id">{{ lang.text }}</option>
}
</datalist>
</div> </div>
<datalist id="subtitleLanguageOptions">
@for (lang of subtitleLanguages; track lang.id) {
<option [value]="lang.id">{{ lang.text }}</option>
}
</datalist>
</div> </div>
<div class="col-12 col-md-6 col-lg-3"> <div class="col-12 col-md-6 col-lg-3">
<div class="input-group"> <div class="input-group">
@ -307,7 +309,8 @@
[(ngModel)]="subtitleMode" [(ngModel)]="subtitleMode"
(change)="subtitleModeChanged()" (change)="subtitleModeChanged()"
[disabled]="addInProgress || subscribeInProgress || downloads.loading" [disabled]="addInProgress || subscribeInProgress || downloads.loading"
ngbTooltip="Choose manual, auto, or fallback preference for captions mode"> ngbTooltip="Choose manual, auto, or fallback preference for captions mode"
container="body">
@for (mode of subtitleModes; track mode.id) { @for (mode of subtitleModes; track mode.id) {
<option [ngValue]="mode.id">{{ mode.text }}</option> <option [ngValue]="mode.id">{{ mode.text }}</option>
} }

View file

@ -82,6 +82,11 @@ main
border-color: color-mix(in srgb, var(--bs-primary) 55%, #fff) border-color: color-mix(in srgb, var(--bs-primary) 55%, #fff)
box-shadow: 0 0 0 .22rem color-mix(in srgb, var(--bs-primary) 20%, transparent) box-shadow: 0 0 0 .22rem color-mix(in srgb, var(--bs-primary) 20%, transparent)
.input-group:not(.has-validation) > .form-control:last-of-type:not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),
.input-group:not(.has-validation) > .form-select:last-of-type:not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating)
border-top-right-radius: var(--bs-border-radius)
border-bottom-right-radius: var(--bs-border-radius)
.btn-primary .btn-primary
box-shadow: 0 6px 16px color-mix(in srgb, var(--bs-primary) 28%, transparent) box-shadow: 0 6px 16px color-mix(in srgb, var(--bs-primary) 28%, transparent)