Files
drive/caption_parser.py
T
2026-10-18 23:48:22 +05:30

109 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Extract structured metadata from a Telegram video caption.
Expected format (fields may appear in any order, with extra blank lines, emoji or
bullet decorations, and varying whitespace)::
Index: 001
Index: 002
Title: Class 01 Aman sir.mp4
Topic: Misc• Grammar Practice
Batch: English ( SSC MAINS )
Extracted By: @PUTINxINDIA
Parsing is line based, so the value of one field can never swallow the next field.
If a single line holds several known fields (captions flattened by a forwarder),
the line is split at each known label.
"""
from __future__ import annotations
import re
from models import ParsedCaption
REQUIRED_FIELDS = ("title", "topic")
_LABEL = r"(?P<label>index|title|topic|batch|extracted[ \t_]*by)"
# Optional decoration (emoji, bullets, markdown markers) before the label, then the
# label, optional spaces and an ASCII or full-width colon.
_LINE_FIELD = re.compile(rf"^[^\w@]*{_LABEL}[ \t]*[::]", re.IGNORECASE)
_INLINE_FIELD = re.compile(rf"(?<=\s)[^\w\s@]*{_LABEL}[ \t]*[::]", re.IGNORECASE)
_GENERIC_FIELD = re.compile(r"^(?P<key>[^::]{1,40}?)[ \t]*[::][ \t]*(?P<value>.*)$")
_LABEL_KEYS = {
"index": "index",
"title": "title",
"topic": "topic",
"batch": "batch",
"extractedby": "extracted_by",
}
_DISPLAY = {"title": "Title", "topic": "Topic", "batch": "Batch", "extracted_by": "Extracted By", "index": "Index"}
def _field_key(label: str) -> str:
return _LABEL_KEYS[re.sub(r"[\s_]+", "", label.lower())]
def _split_fields(line: str) -> list[tuple[str, str]] | None:
"""Return ``[(field, value), ...]`` if the line starts with a known label."""
first = _LINE_FIELD.match(line)
if not first:
return None
marks = [(first.end(), first.group("label"))]
starts = [first.start("label")]
for inline in _INLINE_FIELD.finditer(line, first.end()):
starts.append(inline.start())
marks.append((inline.end(), inline.group("label")))
result = []
for i, (value_start, label) in enumerate(marks):
is_last = i + 1 == len(marks)
value = line[value_start : len(line) if is_last else starts[i + 1]].strip()
if not is_last:
value = value.rstrip(" \t|,;").strip()
result.append((_field_key(label), value))
return result
def parse_caption(text: str | None) -> ParsedCaption:
raw = text or ""
parsed = ParsedCaption(raw=raw)
if not raw.strip():
parsed.warnings.append("caption is empty")
parsed.missing = list(REQUIRED_FIELDS)
return parsed
for line in raw.replace("\r\n", "\n").replace("\r", "\n").split("\n"):
line = line.strip()
if not line:
continue
fields = _split_fields(line)
if fields is None:
generic = _GENERIC_FIELD.match(line)
if generic and re.search(r"\w", generic.group("key")):
parsed.extra.setdefault(generic.group("key").strip(), generic.group("value").strip())
continue
for name, value in fields:
_assign(parsed, name, value)
parsed.missing = [name for name in REQUIRED_FIELDS if not getattr(parsed, name)]
for name in parsed.missing:
parsed.warnings.append(f"{_DISPLAY[name]} field is missing")
if not parsed.batch:
parsed.warnings.append("Batch field is missing")
return parsed
def _assign(parsed: ParsedCaption, name: str, value: str) -> None:
if name == "index":
if value:
parsed.indexes.append(value)
else:
parsed.warnings.append("empty Index field ignored")
return
if not value:
parsed.warnings.append(f"{_DISPLAY[name]} field is empty")
return
current = getattr(parsed, name)
if current is None:
setattr(parsed, name, value)
elif current != value:
parsed.warnings.append(f"duplicate {_DISPLAY[name]} field {value!r} ignored; kept {current!r}")