Emby kan kun vise serie, sæson og afsnit, men på YouTube har en kanal både videoer og playlister, og en video kan ligge i flere playlister. Kanal-plugin'et (agw/emby-yt-kanaler) viser Kanal: Alle videoer og Playlister, og det læser dette indeks, som portalen skriver. - skriv_indeks(): bygger DOWNLOAD_DIR/.weircon-index.json (version 1) ud fra NFO'er, billeder og undertekster på disken og playliste-cachen. Kun hentede videoer kommer med i playlisterne, i YouTubes rækkefølge. Skrives atomisk og efter hver hentning og sletning (via refresh_library). - opdater_playlister(): henter kanalernes playlister med yt-dlp (højst ugentligt pr. kanal) plus portalens egne playliste-job og -abonnementer, og playlistens miniature til <Kanal>/.playlists/<id>.jpg. Køres natligt. - emby_yt_items(): lægger set/favorit fra Emby-kanalen "YouTube" oven i biblioteket for samme fil, så slettereglen og fredningen virker, uanset hvor videoen blev set. Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG
82 lines
3.2 KiB
Python
82 lines
3.2 KiB
Python
"""Natlig YouTube-kørsel (systemd-timer).
|
|
|
|
Rækkefølgen er ikke tilfældig:
|
|
1. sync_subs — find nye videoer i playliste-/kanal-abonnementer
|
|
2. process_queue — hent alt i køen (også jobs der hang efter en genstart)
|
|
3. enforce_cycles — hold hver kanal på sine X nyeste (favoritter tæller ikke med)
|
|
4. run_cycle — slet videoer der er markeret som set, efter karensen
|
|
|
|
Cyklussen håndhæves EFTER hentningen, ellers ville den se et halvt bibliotek.
|
|
"""
|
|
import asyncio
|
|
|
|
import ytdl
|
|
|
|
|
|
async def opdater_ytdlp():
|
|
"""YouTube ændrer ofte noget, der giver 403, indtil yt-dlp er opdateret.
|
|
Derfor opdateres den hver nat, før der hentes."""
|
|
try:
|
|
proc = await asyncio.create_subprocess_exec(
|
|
ytdl.config.YT_DLP_BIN, "-U",
|
|
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT)
|
|
out, _ = await asyncio.wait_for(proc.communicate(), timeout=180)
|
|
sidste = (out.decode("utf-8", "replace").strip().splitlines() or [""])[-1]
|
|
print(f"yt_job: yt-dlp -U: {sidste[:160]}")
|
|
except Exception as e: # noqa: BLE001
|
|
print(f"yt_job: yt-dlp kunne ikke opdateres: {e}")
|
|
|
|
|
|
async def main():
|
|
if not ytdl.configured():
|
|
print("yt_job: ikke konfigureret (mangler EMBY-env, yt-dlp eller download-mappe)")
|
|
return
|
|
|
|
await opdater_ytdlp()
|
|
|
|
sub = await ytdl.sync_subs()
|
|
if sub["checked"] or sub["errors"]:
|
|
print(f"yt_job: abonnementer tjekket={sub['checked']} nye_i_koe={sub['queued']}")
|
|
for e in sub["errors"]:
|
|
print(" abonnement-fejl:", e)
|
|
|
|
q = await ytdl.process_queue()
|
|
if q["done"] or q["failed"]:
|
|
print(f"yt_job: hentet={q['done']} fejlet={q['failed']}")
|
|
|
|
cyc = await ytdl.enforce_cycles()
|
|
if cyc["evicted"] or cyc["errors"]:
|
|
print(f"yt_job: ude_af_cyklus={len(cyc['evicted'])} "
|
|
f"frigjort={ytdl._human(cyc['freed'])}")
|
|
for e in cyc["errors"]:
|
|
print(" cyklus-fejl:", e)
|
|
|
|
# Kanal-plugin'ets indeks: playlister (højst ugentligt pr. kanal) og så indekset.
|
|
try:
|
|
pl = await ytdl.opdater_playlister()
|
|
print(f"yt_job: playlister opdateret for {pl['kanaler']} kanaler, {pl['playlister']} i alt")
|
|
for e in pl["fejl"][:10]:
|
|
print(" playliste-fejl:", e)
|
|
except Exception as e: # noqa: BLE001
|
|
print(f"yt_job: playlister kunne ikke opdateres: {e}")
|
|
try:
|
|
ix = ytdl.skriv_indeks()
|
|
print(f"yt_job: indeks: {ix['channels']} kanaler, {ix['videos']} videoer, {ix['playlists']} playlister")
|
|
except Exception as e: # noqa: BLE001
|
|
print(f"yt_job: indekset kunne ikke skrives: {e}")
|
|
|
|
res = await ytdl.run_cycle(dry_run=False)
|
|
if res.get("error"):
|
|
print("yt_job FEJL:", res["error"])
|
|
return
|
|
print(f"yt_job: vores videoer={res['seen']} nye_paa_vej_ud={len(res['new'])} "
|
|
f"fredet={len(res['spared'])} slettet={len(res['deleted'])} "
|
|
f"frigjort={ytdl._human(res['freed'])} "
|
|
f"sletning={'til' if res['enabled'] else 'fra'}")
|
|
for e in res.get("errors", []):
|
|
print(" fejl:", e)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|