video search: scene/p2p release-title parser (Sonarr-style)

core/video/release_parse.py (pure, isolated): parse_release(title) → resolution,
source (remux/bluray/web-dl/webrip/hdtv/dvd/cam/screener/workprint), codec, HDR,
audio, group, repack/proper/3d, and crucially the SCOPE — single episode (SxxExx)
vs season pack (Sxx / Season N) vs complete-series pack (S01-S05 / COMPLETE). The
search layer uses this to validate that a hit matches what was searched. 9 tests,
isolation guard + ruff clean. First piece of the live search pipeline.
This commit is contained in:
BoulderBadgeDad 2026-06-19 13:28:37 -07:00
parent 8803c1bd0c
commit 9b1d76b4ff
2 changed files with 183 additions and 0 deletions

118
core/video/release_parse.py Normal file
View file

@ -0,0 +1,118 @@
"""Parse a scene/p2p release title into structured quality + scope fields.
This is the video equivalent of Sonarr/Radarr's release parser: given a raw name
like ``The Wire S02 1080p BluRay x265 DDP5.1-GROUP`` it pulls out resolution,
source, codec, HDR, audio, group, repack/proper, and the SEASON/EPISODE scope
(single episode vs season pack vs complete-series pack). The download engine and
the search UI both rely on this to validate that a hit actually matches what was
searched (a season search must return the *whole* season, etc.).
Pure + regex-only (no DB, no network) so it's unit-tested in isolation. Isolated —
imports only re/typing; the music side never imports it.
"""
from __future__ import annotations
import re
from typing import Any
# Resolution — first match wins (most specific first).
_RES = [
(re.compile(r"\b(2160p|4k|uhd)\b", re.I), "2160p"),
(re.compile(r"\b1080[pi]\b", re.I), "1080p"),
(re.compile(r"\b720[pi]\b", re.I), "720p"),
(re.compile(r"\b(480[pi]|576[pi])\b", re.I), "480p"),
]
# Source — order matters (remux before bluray; web-dl before webrip).
_SOURCE = [
(re.compile(r"\bremux\b", re.I), "remux"),
(re.compile(r"\b(blu-?ray|bdrip|brrip|bd25|bd50)\b", re.I), "bluray"),
(re.compile(r"\b(web-?dl|web\.?dl|amzn|nf|dsnp|hmax|atvp)\b", re.I), "web-dl"),
(re.compile(r"\bweb-?rip\b", re.I), "webrip"),
(re.compile(r"\bhdtv\b", re.I), "hdtv"),
(re.compile(r"\b(dvdrip|dvd)\b", re.I), "dvd"),
(re.compile(r"\b(cam|hdcam|ts|telesync|hdts)\b", re.I), "cam"),
(re.compile(r"\b(scr|screener|dvdscr|bdscr)\b", re.I), "screener"),
(re.compile(r"\bworkprint\b", re.I), "workprint"),
]
_CODEC = [
(re.compile(r"\b(x265|h\.?265|hevc)\b", re.I), "hevc"),
(re.compile(r"\b(x264|h\.?264|avc)\b", re.I), "x264"),
(re.compile(r"\bav1\b", re.I), "av1"),
]
_HDR = [
(re.compile(r"\b(dolby\s?vision|do?vi|\bdv\b)\b", re.I), "dv"),
(re.compile(r"\bhdr10\+\b", re.I), "hdr10"),
(re.compile(r"\bhdr10\b", re.I), "hdr10"),
(re.compile(r"\bhdr\b", re.I), "hdr"),
]
_AUDIO = [
(re.compile(r"\batmos\b", re.I), "atmos"),
(re.compile(r"\b(truehd|true-hd)\b", re.I), "truehd"),
(re.compile(r"\b(dts-?hd|dts-?x)\b", re.I), "dts-hd"),
(re.compile(r"\bdts\b", re.I), "dts"),
(re.compile(r"\b(ddp|eac3|dd\+)\b", re.I), "eac3"),
(re.compile(r"\b(ac3|dd5\.?1|dd2\.?0)\b", re.I), "ac3"),
(re.compile(r"\baac\b", re.I), "aac"),
]
_RANGE = re.compile(r"\bS(\d{1,2})\s*[-]\s*S?(\d{1,2})\b", re.I) # S01-S05
_SXXEXX = re.compile(r"\bS(\d{1,2})[\s.]?E(\d{1,3})\b", re.I) # S02E03
_SXX = re.compile(r"\bS(\d{1,2})\b", re.I) # S02 (pack)
_SEASON_WORD = re.compile(r"\bseason[\s.]?(\d{1,2})\b", re.I) # Season 2
_COMPLETE = re.compile(r"\b(complete|collection|all\s?seasons)\b", re.I)
_GROUP = re.compile(r"-([A-Za-z0-9]{2,})\s*$")
def _first(table, text) -> Any:
for rx, val in table:
if rx.search(text):
return val
return None
def parse_release(title: Any) -> dict:
"""Parse a release name into quality + scope fields. Never raises."""
t = str(title or "")
out = {
"title": t,
"resolution": _first(_RES, t),
"source": _first(_SOURCE, t),
"codec": _first(_CODEC, t),
"hdr": _first(_HDR, t),
"audio": _first(_AUDIO, t),
"group": None,
"repack": bool(re.search(r"\brepack\b", t, re.I)),
"proper": bool(re.search(r"\bproper\b", t, re.I)),
"three_d": bool(re.search(r"\b3d\b", t, re.I)),
"season": None,
"season_end": None,
"episode": None,
"is_season_pack": False,
"is_series_pack": False,
}
g = _GROUP.search(t)
if g:
out["group"] = g.group(1)
rng = _RANGE.search(t)
m = _SXXEXX.search(t)
if rng:
out["season"] = int(rng.group(1))
out["season_end"] = int(rng.group(2))
out["is_series_pack"] = True
elif m:
out["season"] = int(m.group(1))
out["episode"] = int(m.group(2))
else:
sm = _SXX.search(t) or _SEASON_WORD.search(t)
if sm:
out["season"] = int(sm.group(1))
out["is_season_pack"] = True
if _COMPLETE.search(t):
out["is_series_pack"] = True
out["is_season_pack"] = False
return out
__all__ = ["parse_release"]

View file

@ -0,0 +1,65 @@
"""Release-title parser — the Sonarr-style seam that pulls quality + season/episode
scope out of a raw release name. Isolated from music."""
from __future__ import annotations
from core.video.release_parse import parse_release
def test_movie_release():
p = parse_release("The Matrix 1999 2160p UHD BluRay x265 TrueHD Atmos-FraMeSToR")
assert p["resolution"] == "2160p" and p["source"] == "bluray"
assert p["codec"] == "hevc" and p["audio"] == "atmos"
assert p["group"] == "FraMeSToR"
assert p["season"] is None and p["episode"] is None
assert not p["is_season_pack"] and not p["is_series_pack"]
def test_single_episode():
p = parse_release("The.Wire.S02E03.1080p.WEB-DL.x264-GROUP")
assert p["season"] == 2 and p["episode"] == 3
assert p["source"] == "web-dl" and p["codec"] == "x264"
assert not p["is_season_pack"] and not p["is_series_pack"]
def test_season_pack_no_episode():
p = parse_release("The Wire S02 1080p BluRay x265 DDP5.1-GROUP")
assert p["season"] == 2 and p["episode"] is None
assert p["is_season_pack"] is True and p["is_series_pack"] is False
def test_season_word_pack():
p = parse_release("The.Wire.Season.2.COMPLETE.720p.HDTV.x264")
assert p["season"] == 2
# "COMPLETE" + a single season word → treated as a series/complete pack
assert p["is_series_pack"] is True
def test_series_range_pack():
p = parse_release("The Wire S01-S05 COMPLETE 1080p BluRay x265-GROUP")
assert p["season"] == 1 and p["season_end"] == 5
assert p["is_series_pack"] is True
def test_hdr_and_dv():
assert parse_release("Dune 2021 2160p BluRay HDR10 x265")["hdr"] == "hdr10"
assert parse_release("Dune 2021 2160p WEB-DL DV HEVC")["hdr"] == "dv"
assert parse_release("Dune 2021 1080p BluRay x264")["hdr"] is None
def test_junk_sources_detected_for_rejection():
assert parse_release("Some Movie 2024 HDCAM x264")["source"] == "cam"
assert parse_release("Some Movie 2024 DVDScr XviD")["source"] == "screener"
assert parse_release("Some.Movie.2024.1080p.WEB-DL.x264.3D")["three_d"] is True
def test_repack_proper():
p = parse_release("Show.S01E01.REPACK.1080p.WEB-DL.x265-GRP")
assert p["repack"] is True
assert parse_release("Show.S01E01.PROPER.1080p.HDTV.x264")["proper"] is True
def test_garbage_never_raises():
p = parse_release(None)
assert p["resolution"] is None and p["title"] == ""
assert parse_release(12345)["title"] == "12345"