Метки rutracker: «Перевод:» без значения и список дорожек ниже
docker / build (push) Successful in 2m21s
docker / build (push) Successful in 2m21s
Парсер меток (rt_parse) требовал текст после «Перевод:» на той же строке, поэтому раздачи с пустым «Перевод:» и списком «Профессиональный (…) | Студия» по строке на дорожку (t=6919056, 6927571) не получали меток, релиз не сохранялся, а сегментатор учился на их дорожках как на негативах. Теперь rt_bare_list берёт подряд идущие строки со словом типа (через пустые строки; строки «тип: определение» — глоссарий — не берутся). То же правило в html_extract.extract_segments. На data/raw: +252 раздачи получают метки, в 12 добавляются недостающие дорожки. PARSE_VERSION 3 -> 4 (переразбор при «Переиндексировать data/raw»). Тест rt_parse на 6919056; 276 ok. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b3ceefda4f
commit
e9c85c4132
4 files changed
+49
-12
No files matched your search
+1
-1
@@ -133,7 +133,7 @@ def extract_segments(text: str) -> list[str]:
|
|||||||
k = i
|
k = i
|
||||||
while k < len(lines) and not lines[k].strip():
|
while k < len(lines) and not lines[k].strip():
|
||||||
k += 1
|
k += 1
|
||||||
if k >= len(lines) or not RE_TYPE_START.match(lines[k]):
|
if k >= len(lines) or not RE_TYPE_START.match(lines[k]) or ":" in lines[k]: # «тип: определение» — глоссарий
|
||||||
break
|
break
|
||||||
body += " ; " + lines[k].strip()
|
body += " ; " + lines[k].strip()
|
||||||
i = k + 1
|
i = k + 1
|
||||||
|
|||||||
@@ -823,6 +823,15 @@ check("метки rt_parse: кодек и битрейт из строки «А
|
|||||||
all(r["codec"] == "AC3" and r["kbps"] == "192" for r in _hr), str(_hr))
|
all(r["codec"] == "AC3" and r["kbps"] == "192" for r in _hr), str(_hr))
|
||||||
check("метки rt_parse: 5 студий + 1 доп. 18+", len(_rows) == 6, str(len(_rows)))
|
check("метки rt_parse: 5 студий + 1 доп. 18+", len(_rows) == 6, str(len(_rows)))
|
||||||
|
|
||||||
|
_bare = ("Перевод:\nПрофессиональный (дублированный) | Cinema Tone Production\n\nПрофессиональный (псевдодублированный) | HDRezka Studio\n"
|
||||||
|
"Профессиональный (многоголосый закадровый) | HDRezka Studio\nПрофессиональный (многоголосый закадровый) | TVShows\n"
|
||||||
|
"\nОригинальная аудиодорожка: Английская\nСубтитры: Русские, английские\n")
|
||||||
|
_br, _ = _hc.rt_parse({"id": "6919056", "title": "Астрал 6 [2026, WEB-DL 2160p]", "post": _bare})
|
||||||
|
check("метки rt_parse: «Перевод:» без значения + список строк (6919056)",
|
||||||
|
[(r["translation_type"], r["studio"]) for r in _br] == [
|
||||||
|
("проф. дубляж", "Cinema Tone Production"), ("люб. дубляж", "HDrezka"), # псевдодубляж = «люб. дубляж»
|
||||||
|
("проф. многоголосый", "HDrezka"), ("проф. многоголосый", "TVShows")], str([(r["translation_type"], r["studio"]) for r in _br]))
|
||||||
|
|
||||||
_sf = __import__("src.release", fromlist=["x"]).series_info("Сериал / Сезон 1 / Серии 1-8 из 8 [2026]", [
|
_sf = __import__("src.release", fromlist=["x"]).series_info("Сериал / Сезон 1 / Серии 1-8 из 8 [2026]", [
|
||||||
{"studio": "LostFilm", "episodes": "1-4", "episode_ranges": [[1, 4]]},
|
{"studio": "LostFilm", "episodes": "1-4", "episode_ranges": [[1, 4]]},
|
||||||
{"studio": "HDrezka", "episodes": None, "episode_ranges": []}])
|
{"studio": "HDrezka", "episodes": None, "episode_ranges": []}])
|
||||||
|
|||||||
+1
-1
@@ -1028,7 +1028,7 @@ def cmd_build(rewrite: bool = False, relabel: bool = False, runner: "Runner | No
|
|||||||
|
|
||||||
# Версия разбора: поднимите при правке парсеров (harvest_core, html_to_text) — тогда
|
# Версия разбора: поднимите при правке парсеров (harvest_core, html_to_text) — тогда
|
||||||
# кэш «уже разобрано» сбросится сам и данные пересоберутся один раз.
|
# кэш «уже разобрано» сбросится сам и данные пересоберутся один раз.
|
||||||
PARSE_VERSION = 3
|
PARSE_VERSION = 4
|
||||||
|
|
||||||
|
|
||||||
def load_json(path: Path) -> dict:
|
def load_json(path: Path) -> dict:
|
||||||
|
|||||||
@@ -1288,6 +1288,10 @@ def _kz_parse_core(rec: dict) -> tuple[list[dict], dict]:
|
|||||||
# ---- rutracker -----------------------------------------------------
|
# ---- rutracker -----------------------------------------------------
|
||||||
RT_RE_FIELD = re.compile(
|
RT_RE_FIELD = re.compile(
|
||||||
r"^\s*(?:Перевод|Озвучка|Озвучивание|Озвучено|Звук)\s*(?:#\s*\d+|\d+)?\s*:\s*(?P<body>.+)$", re.I)
|
r"^\s*(?:Перевод|Озвучка|Озвучивание|Озвучено|Звук)\s*(?:#\s*\d+|\d+)?\s*:\s*(?P<body>.+)$", re.I)
|
||||||
|
RT_RE_BARE = re.compile(
|
||||||
|
r"^\s*(?:Перевод|Озвучка|Озвучивание|Озвучено|Звук)\s*(?:#\s*\d+|\d+)?\s*:\s*$", re.I)
|
||||||
|
RT_RE_TYPE_START = re.compile(
|
||||||
|
r"^\s*(?:Профессиональн|Любительск|Авторск|Дублирован|Многоголос|Двухголос|Одноголос)", re.I)
|
||||||
RT_RE_SUBS = re.compile(r"^\s*Субтитры\s*:\s*(?P<body>.+)$", re.I)
|
RT_RE_SUBS = re.compile(r"^\s*Субтитры\s*:\s*(?P<body>.+)$", re.I)
|
||||||
RT_RE_BRACKET = re.compile(r"\[(?P<body>[^\]]*)\]")
|
RT_RE_BRACKET = re.compile(r"\[(?P<body>[^\]]*)\]")
|
||||||
RT_RE_SEP = re.compile(r"\s*(?:\+|;|/)\s*(?=Проф|Люб|Авт|Дубл|Многоголос|Двухголос|Одноголос)", re.I)
|
RT_RE_SEP = re.compile(r"\s*(?:\+|;|/)\s*(?=Проф|Люб|Авт|Дубл|Многоголос|Двухголос|Одноголос)", re.I)
|
||||||
@@ -1366,20 +1370,44 @@ def audio18_rows(rows: list[dict], post: str) -> list[dict]:
|
|||||||
return rows + extra
|
return rows + extra
|
||||||
|
|
||||||
|
|
||||||
|
def rt_bare_list(lines: list[str], i: int, taken: set[int]) -> list[str]:
|
||||||
|
"""«Перевод:» без значения: дорожки списком ниже, по строке на дорожку.
|
||||||
|
|
||||||
|
Берутся подряд идущие строки со словом типа озвучки; между ними могут стоять
|
||||||
|
пустые (в HTML после <br> идёт ещё и перевод строки). Номера взятых строк
|
||||||
|
кладутся в taken, чтобы их не разбирали второй раз.
|
||||||
|
"""
|
||||||
|
out, j = [], i + 1
|
||||||
|
while True:
|
||||||
|
k = j
|
||||||
|
while k < len(lines) and not lines[k].strip():
|
||||||
|
k += 1
|
||||||
|
if k >= len(lines) or not RT_RE_TYPE_START.match(lines[k]) or ":" in lines[k]: # «тип: определение» — глоссарий
|
||||||
|
return out
|
||||||
|
out.append(lines[k])
|
||||||
|
taken.add(k)
|
||||||
|
j = k + 1
|
||||||
|
|
||||||
|
|
||||||
def _rt_parse_core(rec: dict) -> tuple[list[dict], dict]:
|
def _rt_parse_core(rec: dict) -> tuple[list[dict], dict]:
|
||||||
post = rec.get("post") or ""
|
post = rec.get("post") or ""
|
||||||
info = rt_parse_title(rec.get("page_title") or rec.get("title") or "")
|
info = rt_parse_title(rec.get("page_title") or rec.get("title") or "")
|
||||||
entries, subs = [], ""
|
entries, subs, taken = [], "", set()
|
||||||
for line in post.splitlines():
|
lines = post.splitlines()
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if i in taken:
|
||||||
|
continue
|
||||||
m = RT_RE_FIELD.match(line)
|
m = RT_RE_FIELD.match(line)
|
||||||
if m:
|
bodies = [m.group("body")] if m else (rt_bare_list(lines, i, taken) if RT_RE_BARE.match(line) else [])
|
||||||
for part in rt_split_entries(m.group("body")):
|
if bodies:
|
||||||
e = rt_parse_entry(part)
|
for body in bodies:
|
||||||
if e["type"] in ("оригинал", "субтитры"):
|
for part in rt_split_entries(body):
|
||||||
continue
|
e = rt_parse_entry(part)
|
||||||
if not e["studio"] and e["type"] == "неизвестно":
|
if e["type"] in ("оригинал", "субтитры"):
|
||||||
continue
|
continue
|
||||||
entries.append(e)
|
if not e["studio"] and e["type"] == "неизвестно":
|
||||||
|
continue
|
||||||
|
entries.append(e)
|
||||||
continue
|
continue
|
||||||
m = RT_RE_SUBS.match(line)
|
m = RT_RE_SUBS.match(line)
|
||||||
if m and not subs:
|
if m and not subs:
|
||||||
|
|||||||
Reference in new issue
Block a user