Nommage Plex SxxExx des fichiers téléchargés
Le scanner Plex ignorait les épisodes 1-9 : le pattern « Saison 1 - E9 » n'est reconnu qu'avec un numéro d'épisode à 2 chiffres. Les titres « Série - Saison N - EX (VF) » sont désormais renommés en « Série - S01E09 (VF) » à l'ajout dans la file (plex_filename).
This commit is contained in:
@@ -76,6 +76,33 @@ def series_dirname(title: str) -> str | None:
|
|||||||
base = sanitize_filename(match.group(1).strip()) if match else ""
|
base = sanitize_filename(match.group(1).strip()) if match else ""
|
||||||
return base or None
|
return base or None
|
||||||
|
|
||||||
|
|
||||||
|
_EPISODE_RE = re.compile(
|
||||||
|
r"^(?P<series>.+?)\s*-\s*(?:Saison\s+(?P<season>\d+)\s*-\s*)?"
|
||||||
|
r"E(?P<ep>\d+(?:[.,]\d+)?)(?P<tail>\s*\([^)]*\))?\s*$",
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def plex_filename(title: str, extension: str) -> str | None:
|
||||||
|
"""Nom de fichier compatible Plex (« S01E09 ») pour un titre d'épisode.
|
||||||
|
|
||||||
|
Les scrapers produisent « Série - Saison 1 - E9 (VF) » ; le scanner Plex
|
||||||
|
exige « Série - S01E09 (VF) » (les épisodes 1-9 sont sinon ignorés).
|
||||||
|
None si le titre ne suit pas le format épisode → nommage inchangé.
|
||||||
|
"""
|
||||||
|
match = _EPISODE_RE.match(title)
|
||||||
|
if not match:
|
||||||
|
return None
|
||||||
|
series = match.group("series")
|
||||||
|
season = int(match.group("season") or 1)
|
||||||
|
ep_raw = match.group("ep").replace(",", ".")
|
||||||
|
ep = float(ep_raw)
|
||||||
|
ep_label = f"{int(ep):02d}" if ep.is_integer() else ep_raw
|
||||||
|
tail = match.group("tail") or ""
|
||||||
|
return sanitize_filename(f"{series} - S{season:02d}E{ep_label}{tail}") + extension
|
||||||
|
|
||||||
|
|
||||||
class DownloadManager:
|
class DownloadManager:
|
||||||
"""File d'attente de téléchargements, injectée dans les routes via app.state."""
|
"""File d'attente de téléchargements, injectée dans les routes via app.state."""
|
||||||
|
|
||||||
@@ -174,7 +201,8 @@ class DownloadManager:
|
|||||||
logger.info("Anti-doublon : %s déjà en file (id=%s)", title, existing["id"])
|
logger.info("Anti-doublon : %s déjà en file (id=%s)", title, existing["id"])
|
||||||
return self._to_dict(existing, duplicate=True)
|
return self._to_dict(existing, duplicate=True)
|
||||||
|
|
||||||
filename = sanitize_filename(title) + self._guess_extension(video_url)
|
extension = self._guess_extension(video_url)
|
||||||
|
filename = plex_filename(title, extension) or sanitize_filename(title) + extension
|
||||||
series = series_dirname(title)
|
series = series_dirname(title)
|
||||||
file_path = f"{series}/{filename}" if series else filename
|
file_path = f"{series}/{filename}" if series else filename
|
||||||
cursor = await db.execute(
|
cursor = await db.execute(
|
||||||
|
|||||||
+27
-1
@@ -10,7 +10,7 @@ from app.scrapers.base import (
|
|||||||
import_all_scrapers,
|
import_all_scrapers,
|
||||||
resolve_hoster,
|
resolve_hoster,
|
||||||
)
|
)
|
||||||
from app.services.downloads import sanitize_filename
|
from app.services.downloads import plex_filename, sanitize_filename
|
||||||
|
|
||||||
# ------------------------------------------------------------ format interne
|
# ------------------------------------------------------------ format interne
|
||||||
|
|
||||||
@@ -52,6 +52,32 @@ def test_sanitize_no_traversal():
|
|||||||
assert sanitize_filename("../../etc/passwd") == "etc passwd"
|
assert sanitize_filename("../../etc/passwd") == "etc passwd"
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------ nommage Plex
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("title", "expected"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"The Eminence in Shadow - Saison 1 - E9 (VF)",
|
||||||
|
"The Eminence in Shadow - S01E09 (VF).mp4",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"The Eminence in Shadow - Saison 1 - E10 (VF)",
|
||||||
|
"The Eminence in Shadow - S01E10 (VF).mp4",
|
||||||
|
),
|
||||||
|
("Anime X - E1 (VOSTFR)", "Anime X - S01E01 (VOSTFR).mp4"),
|
||||||
|
("One Piece - E1122", "One Piece - S01E1122.mp4"),
|
||||||
|
("Série - saison 2 - e3 (vf)", "Série - S02E03 (vf).mp4"),
|
||||||
|
("Episode Test 1", None),
|
||||||
|
("Mon Titre", None),
|
||||||
|
("Series S01E05 VOSTFR WEB-DL", None),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_plex_filename(title, expected):
|
||||||
|
assert plex_filename(title, ".mp4") == expected
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------ auth
|
# ------------------------------------------------------------ auth
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ async def test_series_subfolder_and_scan(file_server, manager):
|
|||||||
d = await dm.enqueue(f"{file_server}/video.mp4", "http://page", "Anime Z - E7 (VOSTFR)")
|
d = await dm.enqueue(f"{file_server}/video.mp4", "http://page", "Anime Z - E7 (VOSTFR)")
|
||||||
data = await _wait_status(d["id"], {"done", "failed"})
|
data = await _wait_status(d["id"], {"done", "failed"})
|
||||||
assert data["status"] == "done", data.get("error")
|
assert data["status"] == "done", data.get("error")
|
||||||
assert data["file_path"] == "Anime Z/Anime Z - E7 (VOSTFR).mp4"
|
assert data["file_path"] == "Anime Z/Anime Z - S01E07 (VOSTFR).mp4"
|
||||||
assert (get_settings().download_dir / data["file_path"]).read_bytes() == PAYLOAD
|
assert (get_settings().download_dir / data["file_path"]).read_bytes() == PAYLOAD
|
||||||
|
|
||||||
# DB vidée + redémarrage → le fichier en sous-dossier est restauré tel quel
|
# DB vidée + redémarrage → le fichier en sous-dossier est restauré tel quel
|
||||||
@@ -148,4 +148,4 @@ async def test_series_subfolder_and_scan(file_server, manager):
|
|||||||
await dm.start()
|
await dm.start()
|
||||||
items = await dm.list_all()
|
items = await dm.list_all()
|
||||||
paths = [i["file_path"] for i in items] # d'autres fichiers de tests peuvent trainer
|
paths = [i["file_path"] for i in items] # d'autres fichiers de tests peuvent trainer
|
||||||
assert "Anime Z/Anime Z - E7 (VOSTFR).mp4" in paths
|
assert "Anime Z/Anime Z - S01E07 (VOSTFR).mp4" in paths
|
||||||
|
|||||||
Reference in New Issue
Block a user