Nyt admin-modul /admin/yt: indsæt en YouTube-URL, og videoen hentes med yt-dlp til Emby-biblioteket "YouTube" (/media/yt-offline) — klar til offline-brug. Oprydningen er bevidst modsat mediaclean.py: dér tæller selv delvis afspilning som "set" for at frede alt der er rørt, her slettes kun videoer der er eksplicit markeret som set (Emby UserData.Played). Halvt sete videoer bliver liggende. Favoritter slettes aldrig, og en favoritering i karensperioden freder igen. Sikkerhedsnet: oprydningen rører kun filer portalen selv har hentet (registreret med fuld sti i yt_state.json). Det eksisterende YT-bibliotek er urørligt, og download-træet er lagt uden for det, så Emby ikke indekserer dobbelt. Standardvalg: - H.264/AAC foretrækkes frem for AV1. Pascal-GPU'en i CT100 kan ikke afkode AV1, og mange afspillere kan det slet ikke offline. Koster ~1,8x diskplads. - SponsorBlock klipper sponsorater ud og beholder resten som kapitler. - Undertekster hentes hvis de findes, men er aldrig et krav: YouTube rate-limiter undertekst-endpointet uafhængigt af videoen, så en 429 dér ville ellers vælte hele hentningen. Fald-tilbage henter videoen uden undertekster. - Metadata, miniature og kapitler indlejres, så Emby har noget at vise. Alt kan redigeres i UI'et. Natlig timer (yt-offline.timer) tømmer køen og rydder op. Claude-Session: https://claude.ai/code/session_011WxG8pbLj61jYU1MMaD7VC
123 lines
6.5 KiB
HTML
123 lines
6.5 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Admin — YouTube offline{% endblock %}
|
|
{% block body %}
|
|
<h1>YouTube offline</h1>
|
|
<p class="lead">Indsæt en YouTube-URL, så hentes videoen til Emby-biblioteket
|
|
<strong>{{ settings.library_name }}</strong> — klar til at se uden net, fx i et fly.
|
|
Når du har <strong>markeret den som set</strong> i Emby, slettes den efter
|
|
{{ settings.grace_days }} dage. <strong>Favoritter slettes aldrig.</strong>
|
|
Halvt sete videoer rører oprydningen ikke.</p>
|
|
{% if saved %}<div class="flash">{{ saved }}</div>{% endif %}
|
|
|
|
{% if unconfigured %}
|
|
<div class="err">Ikke konfigureret endnu. Kræver <code>EMBY_URL/EMBY_API_KEY</code> i
|
|
<code>/etc/portal.env</code>, <code>yt-dlp</code> installeret, og at download-mappen
|
|
<code>{{ download_dir }}</code> findes i containeren.</div>
|
|
{% else %}
|
|
|
|
{% if inv_error %}<div class="err">⚠ Emby: {{ inv_error }} — oprydningen står stille indtil den kan læse se-data (sikkerhed).</div>{% endif %}
|
|
|
|
{# ---- hent ny ---- #}
|
|
<form method="post" action="/admin/yt/add" style="margin-top:1.4rem;display:flex;gap:.6rem;flex-wrap:wrap;align-items:center">
|
|
<input name="url" placeholder="https://www.youtube.com/watch?v=…" required
|
|
style="flex:1 1 26rem;min-width:16rem">
|
|
<label class="sw" style="flex:0 0 auto"><input type="checkbox" name="playlist"> <span>Hele playlisten</span></label>
|
|
<button class="btn" type="submit">Hent</button>
|
|
</form>
|
|
|
|
{# ---- topkort ---- #}
|
|
<div class="grid" style="grid-template-columns:repeat(auto-fill,minmax(14rem,1fr));margin-top:1.4rem">
|
|
<div class="stat"><div class="muted" style="font-size:.8rem;text-transform:uppercase;letter-spacing:.08em">I biblioteket</div>
|
|
<div class="n">{{ ours|length }}</div>
|
|
<div class="muted" style="font-size:.85rem;margin-top:.4rem">hentet af portalen · {{ human(ours_size) }}</div></div>
|
|
<div class="stat"><div class="muted" style="font-size:.8rem;text-transform:uppercase;letter-spacing:.08em">På vej ud</div>
|
|
<div class="n">{{ leaving|length }}</div>
|
|
<div class="muted" style="font-size:.85rem;margin-top:.4rem">set · i karens</div></div>
|
|
<div class="stat"><div class="muted" style="font-size:.8rem;text-transform:uppercase;letter-spacing:.08em">I kø</div>
|
|
<div class="n">{{ queued }}</div>
|
|
<div class="muted" style="font-size:.85rem;margin-top:.4rem">{% if running %}1 i gang{% else %}ingen i gang{% endif %}</div></div>
|
|
<div class="stat"><div class="muted" style="font-size:.8rem;text-transform:uppercase;letter-spacing:.08em">Sletning</div>
|
|
<div class="n" style="font-size:1.4rem;margin-top:.4rem">{% if settings.enabled %}<span style="color:#3ecf8e">AKTIV</span>{% else %}<span style="color:#f5a623">fra</span>{% endif %}</div>
|
|
<div class="muted" style="font-size:.85rem;margin-top:.4rem">{{ settings.grace_days }} dages karens</div></div>
|
|
</div>
|
|
|
|
{# ---- indstillinger ---- #}
|
|
<details style="margin-top:1.6rem">
|
|
<summary style="cursor:pointer;font-weight:600">⚙️ Indstillinger</summary>
|
|
<form method="post" action="/admin/yt/settings" style="margin-top:1rem">
|
|
<div class="setgrid">
|
|
<label class="sw"><input type="checkbox" name="enabled" {% if settings.enabled %}checked{% endif %}> <span>Slet sete videoer automatisk</span></label>
|
|
<label class="sw"><input type="checkbox" name="sponsorblock" {% if settings.sponsorblock %}checked{% endif %}> <span>SponsorBlock — klip sponsorater ud</span></label>
|
|
<label class="sw"><input type="checkbox" name="subtitles" {% if settings.subtitles %}checked{% endif %}> <span>Undertekster hvis de findes</span></label>
|
|
<label class="sw"><input type="checkbox" name="metadata" {% if settings.metadata %}checked{% endif %}> <span>Metadata, miniature & kapitler</span></label>
|
|
</div>
|
|
<div class="setgrid" style="margin-top:.8rem">
|
|
<label class="fld"><span>Karens efter set (dage)</span><input type="number" name="grace_days" min="0" max="365" value="{{ settings.grace_days }}"></label>
|
|
<label class="fld"><span>Undertekst-sprog</span><input name="sub_langs" value="{{ settings.sub_langs }}"></label>
|
|
<label class="fld"><span>Emby-bibliotek</span><input name="library_name" value="{{ settings.library_name }}"></label>
|
|
<label class="fld"><span>Kvalitet (yt-dlp -f)</span><input name="quality" value="{{ settings.quality }}"></label>
|
|
</div>
|
|
<p style="margin-top:1rem;display:flex;gap:.6rem;flex-wrap:wrap">
|
|
<button class="btn" type="submit">Gem indstillinger</button>
|
|
<button class="btn secondary" type="submit" formaction="/admin/yt/run" formmethod="post">Kør oprydning nu</button>
|
|
</p>
|
|
</form>
|
|
</details>
|
|
|
|
{# ---- vores videoer ---- #}
|
|
<h2 style="margin-top:2rem">Hentede videoer</h2>
|
|
{% if not ours %}<p class="muted">Ingen endnu — indsæt en URL ovenfor.</p>{% else %}
|
|
<table class="wt-table" style="margin-top:.8rem">
|
|
<thead><tr><th>Titel</th><th style="width:7rem">Størrelse</th><th style="width:9rem">Status</th><th style="width:11rem">Slettes</th></tr></thead>
|
|
<tbody>
|
|
{% for v in ours %}
|
|
<tr>
|
|
<td>{{ v.name }}</td>
|
|
<td class="muted">{{ human(v.size) }}</td>
|
|
<td>
|
|
{% if v.favorite %}<span style="color:#3ecf8e">★ favorit — fredet</span>
|
|
{% elif v.played %}<span style="color:#f5a623">set</span>
|
|
{% else %}<span class="muted">ikke set</span>{% endif %}
|
|
</td>
|
|
<td class="muted">
|
|
{% if v.favorite %}aldrig
|
|
{% elif v.delete_after %}om {{ ((v.delete_after - now) / 86400) | round(0, 'ceil') | int }} dage
|
|
{% else %}—{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% endif %}
|
|
|
|
{# ---- kø & historik ---- #}
|
|
{% if jobs %}
|
|
<h2 style="margin-top:2rem">Kø & historik</h2>
|
|
<table class="wt-table" style="margin-top:.8rem">
|
|
<thead><tr><th>URL / titel</th><th style="width:8rem">Status</th><th>Besked</th></tr></thead>
|
|
<tbody>
|
|
{% for j in jobs %}
|
|
<tr>
|
|
<td>{{ j.title or j.url }}</td>
|
|
<td>{% if j.status == 'done' %}<span style="color:#3ecf8e">hentet</span>
|
|
{% elif j.status == 'error' %}<span style="color:#f87171">fejl</span>
|
|
{% elif j.status == 'running' %}i gang…
|
|
{% elif j.status == 'deleted' %}<span class="muted">slettet</span>
|
|
{% else %}i kø{% endif %}</td>
|
|
<td class="muted">{{ j.error }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% endif %}
|
|
|
|
{% if log %}
|
|
<h2 style="margin-top:2rem">Log</h2>
|
|
<ul class="muted" style="margin-top:.6rem;line-height:1.7">
|
|
{% for l in log %}<li>{{ l.text }}</li>{% endfor %}
|
|
</ul>
|
|
{% endif %}
|
|
|
|
{% endif %}
|
|
{% endblock %}
|