Files
weircon-front/yt_job.py
T
agw ff76660910
CD / deploy (push) Successful in 20s
CI / check (push) Successful in 30s
fix(yt): én hentning ad gangen, nye forsøg ved 403, genoptag afbrudte
Tre fejl, der forstærkede hinanden:
- Hver tilføjelse startede sin egen kø-tømning, så flere hentninger kørte
  samtidig (og natlig timer kunne køre oveni). Mange samtidige hentninger fra
  samme IP er netop det YouTube svarer 403 på. Køen har nu en fil-lås
  (yt_koe.lock), så kun én kørsel henter, også på tværs af processer.
- yt-dlp gentager ikke selv en 403. Midlertidige fejl (403/429/timeout) prøves
  nu igen op til to gange med pause og en anden YouTube-klient
  (tv,web_safari og mweb). Permanente fejl prøves ikke igen.
- Et job der stod som "running", da portalen genstartede, blev hængende for
  altid. Med låsen ved vi, at et sådant job er afbrudt, og det lægges i kø
  igen ved næste kørsel.

Fejlede job har en "Prøv igen"-knap (og "Prøv alle fejlede igen"), og den
natlige kørsel opdaterer yt-dlp, før den henter.

Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG
2026-10-06 13:54:07 +02:00

68 lines
2.5 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)
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())