Метки rutracker: «Перевод:» без значения и список дорожек ниже
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:
DenozordecandClaude Sonnet 5.5 committed 2026-11-07 16:30:10 +07:00
1 parent b3ceefda4f
commit e9c85c4132
4 files changed
+49 -12

No files matched your search

+1 -1
View File
@@ -1028,7 +1028,7 @@ def cmd_build(rewrite: bool = False, relabel: bool = False, runner: "Runner | No
# Версия разбора: поднимите при правке парсеров (harvest_core, html_to_text) — тогда
# кэш «уже разобрано» сбросится сам и данные пересоберутся один раз.
PARSE_VERSION = 3
PARSE_VERSION = 4
def load_json(path: Path) -> dict:
+38 -10
View File
@@ -1288,6 +1288,10 @@ def _kz_parse_core(rec: dict) -> tuple[list[dict], dict]:
# ---- rutracker -----------------------------------------------------
RT_RE_FIELD = re.compile(
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_BRACKET = re.compile(r"\[(?P<body>[^\]]*)\]")
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
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]:
post = rec.get("post") or ""
info = rt_parse_title(rec.get("page_title") or rec.get("title") or "")
entries, subs = [], ""
for line in post.splitlines():
entries, subs, taken = [], "", set()
lines = post.splitlines()
for i, line in enumerate(lines):
if i in taken:
continue
m = RT_RE_FIELD.match(line)
if m:
for part in rt_split_entries(m.group("body")):
e = rt_parse_entry(part)
if e["type"] in ("оригинал", "субтитры"):
continue
if not e["studio"] and e["type"] == "неизвестно":
continue
entries.append(e)
bodies = [m.group("body")] if m else (rt_bare_list(lines, i, taken) if RT_RE_BARE.match(line) else [])
if bodies:
for body in bodies:
for part in rt_split_entries(body):
e = rt_parse_entry(part)
if e["type"] in ("оригинал", "субтитры"):
continue
if not e["studio"] and e["type"] == "неизвестно":
continue
entries.append(e)
continue
m = RT_RE_SUBS.match(line)
if m and not subs: