feat: add external subtitle file detection with language parsing and cache invalidation on subtitle changes
Add EXTERNAL_SUBTITLE_EXTENSIONS set with common subtitle formats (.srt, .ass, .ssa, .vtt, .sub, .idx, .sup). Add external_subtitle_files() to glob subtitle files matching media stem and external_subtitle_languages() to parse language codes from filenames (de/deu/ger/german/deutsch, en/eng/english, fr/fra/fre/french/französisch, es/spa/spanish/spanisch, it/ita/italian/italienisch). Update run_ffprobe() to merge external subtitle
This commit is contained in:
@@ -180,6 +180,29 @@ def map_path(path, remote, local):
|
||||
return str(Path(local) / path[len(remote):].lstrip("/"))
|
||||
return path
|
||||
|
||||
EXTERNAL_SUBTITLE_EXTENSIONS = {".srt", ".ass", ".ssa", ".vtt", ".sub", ".idx", ".sup"}
|
||||
|
||||
def external_subtitle_files(path):
|
||||
media = Path(path)
|
||||
return sorted(candidate for candidate in media.parent.glob(f"{media.stem}.*") if candidate.suffix.lower() in EXTERNAL_SUBTITLE_EXTENSIONS)
|
||||
|
||||
def external_subtitle_languages(path):
|
||||
media = Path(path)
|
||||
languages = []
|
||||
for candidate in external_subtitle_files(path):
|
||||
tokens = {token for token in re.split(r"[. _()\[\]-]+", candidate.stem.lower()) if token}
|
||||
if tokens & {"de", "deu", "ger", "german", "deutsch"}:
|
||||
languages.append(norm_lang("de"))
|
||||
if tokens & {"en", "eng", "english"}:
|
||||
languages.append(norm_lang("en"))
|
||||
if tokens & {"fr", "fra", "fre", "french", "französisch"}:
|
||||
languages.append(norm_lang("fr"))
|
||||
if tokens & {"es", "spa", "spanish", "spanisch"}:
|
||||
languages.append(norm_lang("es"))
|
||||
if tokens & {"it", "ita", "italian", "italienisch"}:
|
||||
languages.append(norm_lang("it"))
|
||||
return unique_langs(languages)
|
||||
|
||||
def run_ffprobe(path):
|
||||
proc = subprocess.run(["ffprobe", "-v", "error", "-show_entries", "format=size:stream=index,codec_type,codec_name,channels,channel_layout:stream_tags=language,title", "-of", "json", path], capture_output=True, text=True, timeout=90)
|
||||
if proc.returncode != 0: raise RuntimeError(proc.stderr.strip() or "ffprobe fehlgeschlagen")
|
||||
@@ -190,13 +213,18 @@ def run_ffprobe(path):
|
||||
elif kind == "audio":
|
||||
audios.append(lang); details.append({"language": lang, "codec": stream.get("codec_name"), "channels": stream.get("channels"), "layout": stream.get("channel_layout"), "title": tags.get("title")})
|
||||
elif kind == "subtitle": subs.append(lang)
|
||||
return {"audio_languages": unique_langs(audios), "subtitle_languages": unique_langs(subs), "audio_details": details, "video_codec": video or "—", "size": int((raw.get("format") or {}).get("size") or 0)}
|
||||
subtitles = unique_langs(subs + external_subtitle_languages(path))
|
||||
return {"audio_languages": unique_langs(audios), "subtitle_languages": subtitles, "external_subtitle_files": [str(file) for file in external_subtitle_files(path)], "audio_details": details, "video_codec": video or "—", "size": int((raw.get("format") or {}).get("size") or 0)}
|
||||
|
||||
def cached(path):
|
||||
p = Path(path)
|
||||
if not p.exists(): return {"error": f"Datei nicht gefunden: {p}", "pending": True}
|
||||
mtime = p.stat().st_mtime; con = db(); row = con.execute("SELECT mtime,data FROM media_cache WHERE path=?", (str(p),)).fetchone(); con.close()
|
||||
if row and float(row["mtime"]) == float(mtime): return json.loads(row["data"])
|
||||
if row and float(row["mtime"]) == float(mtime):
|
||||
data = json.loads(row["data"])
|
||||
current_subtitles = [str(file) for file in external_subtitle_files(path)]
|
||||
if "external_subtitle_files" in data and data["external_subtitle_files"] == current_subtitles:
|
||||
return data
|
||||
return None
|
||||
|
||||
def scan_one(path):
|
||||
|
||||
Reference in New Issue
Block a user