Indeks: ffprobe-strømme og bitrate pr. video, ryd .playlists med tomme kanaler
Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG
This commit is contained in:
@@ -16,6 +16,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
import subprocess
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
@@ -375,6 +376,8 @@ def _ryd_tomme_mapper(saeson_dir: str) -> None:
|
|||||||
for f in KANAL_FILER:
|
for f in KANAL_FILER:
|
||||||
if os.path.exists(os.path.join(kanal, f)):
|
if os.path.exists(os.path.join(kanal, f)):
|
||||||
os.remove(os.path.join(kanal, f))
|
os.remove(os.path.join(kanal, f))
|
||||||
|
# Playlisternes miniaturer (skrevet af opdater_playlister) hører også til kanalen.
|
||||||
|
shutil.rmtree(os.path.join(kanal, ".playlists"), ignore_errors=True)
|
||||||
for d in os.listdir(kanal):
|
for d in os.listdir(kanal):
|
||||||
dd = os.path.join(kanal, d)
|
dd = os.path.join(kanal, d)
|
||||||
if d.startswith("Season ") and os.path.isdir(dd) and not os.listdir(dd):
|
if d.startswith("Season ") and os.path.isdir(dd) and not os.listdir(dd):
|
||||||
@@ -440,6 +443,62 @@ def _varighed_mp4(sti: str, mtime: float):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
FFPROBE_PATH = config.DATA / "yt_ffprobe.json"
|
||||||
|
_ffprobe = {"data": None, "aendret": False}
|
||||||
|
|
||||||
|
|
||||||
|
def _brok(v):
|
||||||
|
try:
|
||||||
|
t, n = str(v).split("/")
|
||||||
|
return round(float(t) / float(n), 3) if float(n) else None
|
||||||
|
except (ValueError, ZeroDivisionError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _heltal(v):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _stroemme(sti: str, st_) -> dict:
|
||||||
|
"""Video-/lydspor (codec, opløsning, bitrate) via ffprobe, så Emby-plugin'et kan
|
||||||
|
sige til apps at filen kan afspilles direkte. Caches pr. sti+mtime+størrelse."""
|
||||||
|
if _ffprobe["data"] is None:
|
||||||
|
_ffprobe["data"] = _load(FFPROBE_PATH, {}) or {}
|
||||||
|
noegle = f"{sti}|{int(st_.st_mtime)}|{st_.st_size}"
|
||||||
|
if noegle in _ffprobe["data"]:
|
||||||
|
return _ffprobe["data"][noegle]
|
||||||
|
ud = {"bitrate": None, "streams": []}
|
||||||
|
try:
|
||||||
|
r = subprocess.run(
|
||||||
|
[shutil.which("ffprobe") or "ffprobe", "-v", "error", "-of", "json", "-show_entries",
|
||||||
|
"stream=codec_type,codec_name,profile,width,height,pix_fmt,avg_frame_rate,bit_rate,"
|
||||||
|
"channels,sample_rate:stream_disposition=attached_pic:stream_tags=language:format=bit_rate", sti],
|
||||||
|
capture_output=True, text=True, timeout=60)
|
||||||
|
d = json.loads(r.stdout or "{}")
|
||||||
|
ud["bitrate"] = _heltal((d.get("format") or {}).get("bit_rate"))
|
||||||
|
for s_ in d.get("streams", []):
|
||||||
|
if (s_.get("disposition") or {}).get("attached_pic"):
|
||||||
|
continue # det indlejrede coverbillede er ikke et videospor
|
||||||
|
if s_.get("codec_type") == "video":
|
||||||
|
ud["streams"].append({"type": "video", "codec": s_.get("codec_name"), "profile": s_.get("profile"),
|
||||||
|
"width": s_.get("width"), "height": s_.get("height"),
|
||||||
|
"fps": _brok(s_.get("avg_frame_rate")), "bitrate": _heltal(s_.get("bit_rate")),
|
||||||
|
"pix_fmt": s_.get("pix_fmt")})
|
||||||
|
elif s_.get("codec_type") == "audio":
|
||||||
|
ud["streams"].append({"type": "audio", "codec": s_.get("codec_name"), "profile": s_.get("profile"),
|
||||||
|
"channels": s_.get("channels"), "sample_rate": _heltal(s_.get("sample_rate")),
|
||||||
|
"bitrate": _heltal(s_.get("bit_rate")),
|
||||||
|
"language": (s_.get("tags") or {}).get("language")})
|
||||||
|
except Exception: # noqa: BLE001
|
||||||
|
return ud # prøv igen næste gang
|
||||||
|
_ffprobe["data"][noegle] = ud
|
||||||
|
_ffprobe["aendret"] = True
|
||||||
|
return ud
|
||||||
|
|
||||||
|
|
||||||
def _laes_playlister() -> dict:
|
def _laes_playlister() -> dict:
|
||||||
return _load(PLAYLISTER_PATH, {}) or {}
|
return _load(PLAYLISTER_PATH, {}) or {}
|
||||||
|
|
||||||
@@ -486,7 +545,7 @@ def skriv_indeks() -> dict:
|
|||||||
"description": nfo.get("plot") or "",
|
"description": nfo.get("plot") or "",
|
||||||
"upload_date": nfo.get("aired") or nfo.get("premiered") or "",
|
"upload_date": nfo.get("aired") or nfo.get("premiered") or "",
|
||||||
"duration": _varighed_mp4(sti, st_.st_mtime), "added": int(st_.st_mtime),
|
"duration": _varighed_mp4(sti, st_.st_mtime), "added": int(st_.st_mtime),
|
||||||
"subtitles": subs}
|
"subtitles": subs, **_stroemme(sti, st_)}
|
||||||
mine.append(vid)
|
mine.append(vid)
|
||||||
if not mine:
|
if not mine:
|
||||||
continue
|
continue
|
||||||
@@ -517,6 +576,11 @@ def skriv_indeks() -> dict:
|
|||||||
json.dump(data, f, ensure_ascii=False, indent=1)
|
json.dump(data, f, ensure_ascii=False, indent=1)
|
||||||
os.chmod(tmp, 0o644)
|
os.chmod(tmp, 0o644)
|
||||||
os.replace(tmp, sti)
|
os.replace(tmp, sti)
|
||||||
|
if _ffprobe["aendret"] and _ffprobe["data"] is not None:
|
||||||
|
stier = {v["path"] for v in videoer.values()}
|
||||||
|
_ffprobe["data"] = {k: v for k, v in _ffprobe["data"].items() if k.split("|", 1)[0] in stier}
|
||||||
|
_save(FFPROBE_PATH, _ffprobe["data"])
|
||||||
|
_ffprobe["aendret"] = False
|
||||||
return {"channels": len(kanaler), "videos": len(videoer),
|
return {"channels": len(kanaler), "videos": len(videoer),
|
||||||
"playlists": sum(len(k["playlists"]) for k in kanaler)}
|
"playlists": sum(len(k["playlists"]) for k in kanaler)}
|
||||||
|
|
||||||
@@ -1092,8 +1156,9 @@ def _marked_played(ud: dict) -> bool:
|
|||||||
async def _kanal_brugerdata(c, users: list, merged: dict) -> None:
|
async def _kanal_brugerdata(c, users: list, merged: dict) -> None:
|
||||||
"""Læg set/favorit fra Emby-kanalen "YouTube" (kanal-plugin'et) oven i
|
"""Læg set/favorit fra Emby-kanalen "YouTube" (kanal-plugin'et) oven i
|
||||||
biblioteket for samme fil, så slettereglen og fredningen virker uanset hvor
|
biblioteket for samme fil, så slettereglen og fredningen virker uanset hvor
|
||||||
videoen blev set. Kanalens elementer bærer video-id'et i ProviderIds["YouTube"];
|
videoen blev set. Kanalens elementer bærer video-id'et i ProviderIds["youtube"];
|
||||||
stien findes via indekset. Mangler kanalen eller indekset, sker der intet."""
|
stien findes via indekset. Mangler kanalen eller indekset, sker der intet.
|
||||||
|
(Plugin'et synkroniserer selv kanal og bibliotek; det her er et sikkerhedsnet.)"""
|
||||||
try:
|
try:
|
||||||
with open(indeks_sti(), encoding="utf-8") as f:
|
with open(indeks_sti(), encoding="utf-8") as f:
|
||||||
stier = {vid: os.path.realpath(v["path"]) for vid, v in json.load(f)["videos"].items()}
|
stier = {vid: os.path.realpath(v["path"]) for vid, v in json.load(f)["videos"].items()}
|
||||||
|
|||||||
Reference in New Issue
Block a user