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):
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")
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):
@ -223,7 +227,7 @@ class CodecSuffixValueTests(unittest.TestCase):
title="t",
url="http://example.com/v",
quality="best",
download_type="video",
download_type=kwargs.pop("download_type", "video"),
codec=kwargs.pop("codec", "auto"),
format="any",
folder="",
@ -250,6 +254,10 @@ class CodecSuffixValueTests(unittest.TestCase):
info = self._download_info(entry={"vcodec": "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):
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:
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':
return '%(height)sp'
if re.fullmatch(r'\d+', quality):
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
def _codec_suffix_value(dl: DownloadInfo) -> str:
if getattr(dl, 'download_type', '') != 'video':
return ''
codec_value = str(getattr(dl, 'codec', '') or '')
if codec_value.lower() != 'auto':
return codec_value

View file

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

View file

@ -273,7 +273,8 @@
[(ngModel)]="format"
(change)="formatChanged()"
[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) {
<option [ngValue]="f.id">{{ f.text }}</option>
}
@ -291,13 +292,14 @@
(change)="subtitleLanguageChanged()"
[disabled]="addInProgress || subscribeInProgress || downloads.loading"
placeholder="e.g. en, es, zh-Hans"
ngbTooltip="Subtitle language (you can type any language code)">
<datalist id="subtitleLanguageOptions">
@for (lang of subtitleLanguages; track lang.id) {
<option [value]="lang.id">{{ lang.text }}</option>
}
</datalist>
ngbTooltip="Subtitle language (you can type any language code)"
container="body">
</div>
<datalist id="subtitleLanguageOptions">
@for (lang of subtitleLanguages; track lang.id) {
<option [value]="lang.id">{{ lang.text }}</option>
}
</datalist>
</div>
<div class="col-12 col-md-6 col-lg-3">
<div class="input-group">
@ -307,7 +309,8 @@
[(ngModel)]="subtitleMode"
(change)="subtitleModeChanged()"
[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) {
<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)
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
box-shadow: 0 6px 16px color-mix(in srgb, var(--bs-primary) 28%, transparent)