Files
weircon-front/yt_job.py
T
agw 2c146b0711 feat(yt): overvåg playlister og kanaler, med cycle X på kanaler
Playliste-abonnement henter nye videoer efterhånden som de lægges i playlisten.
Kanal-abonnement holder en rullende buffer: cycle X = de X nyeste videoer ligger
klar, og kommer der en ny, ryger den ældste ud.

X tæller kun kanal-abonnementets egne hentninger. Manuelle hentninger,
playliste-abonnementer og favoritter tæller ikke med — og favoritter slettes
aldrig, så en favoritering frigør en plads i cyklussen til næste video.

Tre ting der måtte designes udenom:

1. `seen` husker hvert video-id abonnementet nogensinde har hentet, også efter
   videoen er slettet igen. Uden det ville set-reglen slette en video efter 7
   dage, hvorefter kanalen stadig har den blandt sine nyeste og henter den igen
   — i en uendelig løkke.
2. Playliste og kanal primer forskelligt ved oprettelse. En playliste kan have
   hundredvis af videoer, og ønsket var NYE videoer, så første tjek markerer det
   eksisterende som kendt uden at hente. En kanal henter derimod de X nyeste med
   det samme; det er hele pointen med cycle X. Resten af bagkataloget markeres
   som kendt, så den aldrig begynder at hente gammelt indhold.
3. enforce_cycles rører intet hvis Emby ikke svarer. Uden favorit-data kunne den
   komme til at slette noget fredet — samme konservative holdning som mediaclean.

Downloads gemmer nu video-id og upload-dato, så cyklussen kan sortere efter hvor
ny videoen ER, ikke hvornår vi tilfældigvis hentede den.

Timeren kører i rækkefølgen: tjek abonnementer -> hent -> håndhæv cyklus ->
slet sete. Cyklussen skal håndhæves efter hentningen, ellers ser den et halvt
bibliotek.

Claude-Session: https://claude.ai/code/session_011WxG8pbLj61jYU1MMaD7VC
2026-09-19 22:32:40 +02:00

52 lines
1.8 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 main():
if not ytdl.configured():
print("yt_job: ikke konfigureret (mangler EMBY-env, yt-dlp eller download-mappe)")
return
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())