feat: nommage UNFR -ReMoRa automatique après téléchargement
Docker / docker (push) Has been cancelled
Docker / docker (push) Has been cancelled
Format : Title.Event.Year.FRENCH.Resolution.WEBRip.x264|HEVC.AAC-ReMoRa.mp4 - build_release_name() : slugify avec strip accents, apostrophe→point, déduplique l'année si présente dans le titre ET passée séparément, détecte la résolution et le codec depuis les infos yt-dlp - enqueue() : reçoit subtitle + year depuis l'API - _run() : renomme le fichier après download, met à jour le filename en DB - DownloadRequest : subtitle + year ajoutés - app.js : extrait l'année du subtitle via regex avant d'envoyer la requête Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+80
-6
@@ -1,5 +1,7 @@
|
||||
import re
|
||||
import sqlite3
|
||||
import threading
|
||||
import unicodedata
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
@@ -17,6 +19,67 @@ def _db():
|
||||
return conn
|
||||
|
||||
|
||||
# ── Release naming ─────────────────────────────────────────────────────────────
|
||||
|
||||
def _slugify(s: str) -> str:
|
||||
"""Normalize a string to dot-separated scene-style slug."""
|
||||
# Strip accents (NFD decompose then drop combining marks)
|
||||
s = unicodedata.normalize("NFD", s)
|
||||
s = "".join(c for c in s if unicodedata.category(c) != "Mn")
|
||||
# Apostrophe before letter → .Letter (L'Amour → .L.Amour)
|
||||
s = re.sub(r"['’]([A-Za-z])", lambda m: "." + m.group(1).upper(), s)
|
||||
# Spaces / underscores → dot
|
||||
s = re.sub(r"[\s_]+", ".", s)
|
||||
# Keep only alphanumeric, dot, hyphen
|
||||
s = re.sub(r"[^A-Za-z0-9.\-]", "", s)
|
||||
# Collapse multiple dots
|
||||
s = re.sub(r"\.{2,}", ".", s)
|
||||
return s.strip(".")
|
||||
|
||||
|
||||
def build_release_name(title: str, subtitle: str, year: int | None, info: dict) -> str:
|
||||
"""
|
||||
Build a proper UNFR/scene release name.
|
||||
Format: Title.Event.Year.FRENCH.Resolution.WEBRip.x264.AAC-ReMoRa.mp4
|
||||
"""
|
||||
# Strip year from both title and subtitle to avoid duplication
|
||||
t = re.sub(r"\b" + str(year) + r"\b", "", title).strip() if year else title
|
||||
name = _slugify(t)
|
||||
|
||||
sub = subtitle or ""
|
||||
if year:
|
||||
sub = re.sub(r"\b" + str(year) + r"\b", "", sub).strip()
|
||||
sub_slug = _slugify(sub)
|
||||
if sub_slug:
|
||||
name = f"{name}.{sub_slug}"
|
||||
|
||||
year_str = str(year) if year else ""
|
||||
|
||||
# Resolution from yt-dlp info
|
||||
height = info.get("height") or 0
|
||||
if height >= 2160:
|
||||
res = "2160p"
|
||||
elif height >= 1080:
|
||||
res = "1080p"
|
||||
elif height >= 720:
|
||||
res = "720p"
|
||||
else:
|
||||
res = f"{height}p" if height else "1080p"
|
||||
|
||||
# Video codec (avc1 = H.264, hev1/hvc1/hevc = H.265)
|
||||
vcodec = (info.get("vcodec") or "").lower()
|
||||
if "hevc" in vcodec or "h265" in vcodec or "hev1" in vcodec or "hvc1" in vcodec:
|
||||
vc = "HEVC"
|
||||
elif "avc" in vcodec or "h264" in vcodec:
|
||||
vc = "x264"
|
||||
else:
|
||||
vc = "x264"
|
||||
|
||||
parts = [name, year_str, "FRENCH", res, "WEBRip", vc, "AAC"]
|
||||
base = ".".join(p for p in parts if p)
|
||||
return f"{base}-ReMoRa.mp4"
|
||||
|
||||
|
||||
class DownloadManager:
|
||||
def __init__(self):
|
||||
self._active: dict[str, dict] = {}
|
||||
@@ -43,7 +106,8 @@ class DownloadManager:
|
||||
|
||||
# ------------------------------------------------------------------ public
|
||||
|
||||
def enqueue(self, url: str, title: str, bg: BackgroundTasks) -> str:
|
||||
def enqueue(self, url: str, title: str, subtitle: str, year: int | None,
|
||||
bg: BackgroundTasks) -> str:
|
||||
dl_id = str(uuid.uuid4())
|
||||
now = datetime.now().isoformat()
|
||||
with _db() as conn:
|
||||
@@ -53,7 +117,7 @@ class DownloadManager:
|
||||
)
|
||||
with self._lock:
|
||||
self._active[dl_id] = {"state": "queued", "progress": 0, "title": title}
|
||||
bg.add_task(self._run, dl_id, url)
|
||||
bg.add_task(self._run, dl_id, url, title, subtitle, year)
|
||||
return dl_id
|
||||
|
||||
def status(self, dl_id: str) -> dict:
|
||||
@@ -80,15 +144,15 @@ class DownloadManager:
|
||||
with self._lock:
|
||||
self._active.setdefault(dl_id, {}).update(kw)
|
||||
|
||||
def _run(self, dl_id: str, url: str):
|
||||
def _run(self, dl_id: str, url: str, title: str, subtitle: str, year: int | None):
|
||||
Path(OUTPUT_DIR).mkdir(parents=True, exist_ok=True)
|
||||
self._set(dl_id, state="downloading")
|
||||
with _db() as conn:
|
||||
conn.execute("UPDATE downloads SET state='downloading' WHERE id=?", (dl_id,))
|
||||
|
||||
# For HLS, yt-dlp downloads video then audio separately.
|
||||
# After the first stream finishes, stay in "processing" — don't reset
|
||||
# to "downloading" when the audio stream starts.
|
||||
# After the first stream finishes, stay in "processing" to avoid
|
||||
# resetting progress to 0% when the audio stream starts.
|
||||
finished_once = [False]
|
||||
|
||||
def hook(d):
|
||||
@@ -119,7 +183,17 @@ class DownloadManager:
|
||||
try:
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
info = ydl.extract_info(url, download=True)
|
||||
filename = ydl.prepare_filename(info)
|
||||
orig_path = Path(ydl.prepare_filename(info))
|
||||
|
||||
# Rename to proper release name
|
||||
release_name = build_release_name(title, subtitle, year, info)
|
||||
dest_path = orig_path.parent / release_name
|
||||
if orig_path.exists() and orig_path != dest_path:
|
||||
if dest_path.exists():
|
||||
dest_path.unlink()
|
||||
orig_path.rename(dest_path)
|
||||
filename = str(dest_path)
|
||||
|
||||
self._set(dl_id, state="done", progress=100)
|
||||
with _db() as conn:
|
||||
conn.execute(
|
||||
|
||||
Reference in New Issue
Block a user