feat(downloads): ASCII-slug names, universal browser playability, force re-download
Mini-epic "Downloads correctness & UX" (S1–S3), on top of the iOS-AV1 + 720p fixes.
S1 — naming & titles:
- ASCII-fold on-disk dir/file names AND the served (Content-Disposition) filename: no
spaces (underscore-joined), no accents (á→a, ő→o, ß→ss), pure ASCII — portable and free
of the `?` mojibake accented names show in non-UTF-8 tools. (Forward-only; existing files
clean up via re-download.)
- Strip a leading social engagement prefix ("1.6M views · 66K reactions | …") from titles.
S2 — universal browser playability (generalizes the iOS re-encode):
- ensure_browser_playable guarantees the stored file is H.264 + AAC/MP3 in mp4 — the only
combo every browser decodes. Re-encode just the offending stream(s) (video for AV1/VP9/HEVC,
audio for Opus/etc), or remux when only the container is wrong. Gated on the mp4 compat
profile (an explicit vcodec / non-mp4 custom profile opts out).
S3 — force re-download (preserves share links):
- POST /downloads/{job_id}/redownload keeps the job row so its public DownloadLink survives
and resolves to the freshly-downloaded file; deletes the old file and re-fetches under the
current rules. A failed re-download leaves the job in error (the intended broken-link case).
- Library "Re-download" action (icon + confirm), api method, hu/en strings.
Tests: naming/title-strip (test_naming), browser_transcode_flags matrix; existing
download_filename expectation updated for the underscore policy.
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
"""ASCII-slug naming (no spaces, no accents) for on-disk paths and served filenames, and the
|
||||
social engagement-prefix strip in title normalization."""
|
||||
from app.downloads import storage
|
||||
from app.titles import normalize_title
|
||||
|
||||
|
||||
def _is_ascii_slug(s: str) -> bool:
|
||||
return s.isascii() and " " not in s
|
||||
|
||||
|
||||
def test_sanitize_folds_accents_to_ascii():
|
||||
assert storage.sanitize("Barnabás Skriván") == "Barnabas_Skrivan"
|
||||
assert storage.sanitize("Comedy Central Magyarország") == "Comedy_Central_Magyarorszag"
|
||||
assert storage.sanitize("Marco Mobili Bútoráruház Veszprém") == "Marco_Mobili_Butoraruhaz_Veszprem"
|
||||
# Hungarian double-acute (ő/ű) and German ß.
|
||||
assert storage.sanitize("Előző Műsor") == "Elozo_Musor"
|
||||
assert storage.sanitize("Straße") == "Strasse"
|
||||
|
||||
|
||||
def test_sanitize_output_is_pure_ascii_without_spaces():
|
||||
for name in ("Poénbomba", "Mágáné Zsuzsanna", "café déjà vu", "naïve œuvre"):
|
||||
out = storage.sanitize(name)
|
||||
assert _is_ascii_slug(out), out
|
||||
|
||||
|
||||
def test_sanitize_non_latin_falls_back_not_crashes():
|
||||
# A title with no ASCII base collapses to the untitled fallback rather than an empty name.
|
||||
assert storage.sanitize("日本語") == "untitled"
|
||||
|
||||
|
||||
def test_display_filename_uses_underscores_and_ascii():
|
||||
out = storage.display_filename("Amikor a férfi éhesen ér haza")
|
||||
assert out == "Amikor_a_ferfi_ehesen_er_haza"
|
||||
assert _is_ascii_slug(out)
|
||||
|
||||
|
||||
def test_download_filename_appends_single_extension():
|
||||
from pathlib import Path
|
||||
|
||||
assert storage.download_filename("Szép Videó", "mp4", Path("x.mp4")) == "Szep_Video.mp4"
|
||||
|
||||
|
||||
def test_rel_path_is_ascii_everywhere():
|
||||
meta = storage.MediaMeta(
|
||||
video_id="abc123", title="Áristom Ödön", uploader="Hajdú Balázs", upload_date=None
|
||||
)
|
||||
rel = storage.rel_path(meta, "mp4", "plex", asset_id=7)
|
||||
assert _is_ascii_slug(rel.replace("/", ""))
|
||||
assert "Hajdu_Balazs" in rel and "Aristom_Odon" in rel
|
||||
|
||||
|
||||
def test_engagement_prefix_stripped():
|
||||
assert normalize_title(
|
||||
"1.6M views · 66K reactions | Amikor a férfi éhesen ér haza"
|
||||
) == "Amikor a férfi éhesen ér haza"
|
||||
assert normalize_title("13M views · 162K reactions |Valami cím") == "Valami cím"
|
||||
assert normalize_title("500 views | Rövid") == "Rövid"
|
||||
|
||||
|
||||
def test_engagement_prefix_does_not_touch_normal_titles():
|
||||
# A pipe that isn't an engagement prefix must survive.
|
||||
assert normalize_title("Vlog 12 | A nagy nap") == "Vlog 12 | A nagy nap"
|
||||
assert normalize_title("How I built a boat") == "How I built a boat"
|
||||
Reference in New Issue
Block a user