Метки 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
|
||||
while k < len(lines) and not lines[k].strip():
|
||||
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
|
||||
body += " ; " + lines[k].strip()
|
||||
i = k + 1
|
||||
|
||||
@@ -823,6 +823,15 @@ check("метки rt_parse: кодек и битрейт из строки «А
|
||||
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)))
|
||||
|
||||
_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]", [
|
||||
{"studio": "LostFilm", "episodes": "1-4", "episode_ranges": [[1, 4]]},
|
||||
{"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) — тогда
|
||||
# кэш «уже разобрано» сбросится сам и данные пересоберутся один раз.
|
||||
PARSE_VERSION = 3
|
||||
PARSE_VERSION = 4
|
||||
|
||||
|
||||
def load_json(path: Path) -> dict:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user