feat(yt): slet-knap på hentede videoer
Hver række under "Hentede videoer" på /admin/yt har nu en Slet-knap (med bekræftelse; nævner det, hvis videoen er favorit). Sletningen bruger samme sikkerhedsnet som oprydningen: kun filer portalen selv har hentet, og undertekster/miniature slettes med. Er det jobbets sidste fil, markeres jobbet som slettet, og Emby-biblioteket opdateres. Video-id'et bliver i abonnementets "seen", så en overvåget kanal ikke henter den igen. Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG
This commit is contained in:
@@ -1069,6 +1069,20 @@ async def admin_yt_add(request: Request, url: str = Form(""), playlist: str = Fo
|
||||
return RedirectResponse("/admin/yt?saved=Lagt i kø — hentningen er i gang", status_code=303)
|
||||
|
||||
|
||||
@app.post("/admin/yt/slet")
|
||||
async def admin_yt_delete(request: Request):
|
||||
require_admin(request)
|
||||
f = await request.form()
|
||||
res = yt.delete_video(f.get("path") or "")
|
||||
if not res["ok"]:
|
||||
return RedirectResponse(f"/admin/yt?saved={urllib.parse.quote(res['error'])}", status_code=303)
|
||||
cache.clear("yt:")
|
||||
await yt.refresh_library()
|
||||
return RedirectResponse(
|
||||
"/admin/yt?saved=" + urllib.parse.quote(f"Slettet: {res['title']} ({yt._human(res['size'])})"),
|
||||
status_code=303)
|
||||
|
||||
|
||||
@app.post("/admin/yt/settings")
|
||||
async def admin_yt_settings(request: Request):
|
||||
require_admin(request)
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
<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>
|
||||
<thead><tr><th>Titel</th><th style="width:7rem">Størrelse</th><th style="width:9rem">Status</th><th style="width:11rem">Slettes</th><th style="width:5.5rem"></th></tr></thead>
|
||||
<tbody>
|
||||
{% for v in ours %}
|
||||
<tr>
|
||||
@@ -142,6 +142,13 @@
|
||||
{% elif v.delete_after %}om {{ ((v.delete_after - now) / 86400) | round(0, 'ceil') | int }} dage
|
||||
{% else %}—{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<form method="post" action="/admin/yt/slet" style="margin:0"
|
||||
onsubmit="return confirm({{ ('Slet „' ~ v.name ~ '“ nu?' ~ (' Den er markeret som favorit.' if v.favorite else '')) | tojson | forceescape }})">
|
||||
<input type="hidden" name="path" value="{{ v.path }}">
|
||||
<button class="wt-btn" type="submit">Slet</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
@@ -488,6 +488,44 @@ async def enforce_cycles() -> dict:
|
||||
return res
|
||||
|
||||
|
||||
def delete_video(path: str) -> dict:
|
||||
"""Slet én video manuelt (knappen på /admin/yt), med samme sikkerhedsnet som
|
||||
oprydningen: KUN filer portalen selv har hentet, og sidecars med.
|
||||
|
||||
Video-id'et bliver stående i abonnementets `seen`, så en overvåget kanal ikke
|
||||
henter den igen. Er det jobbets sidste fil, markeres jobbet som slettet.
|
||||
"""
|
||||
real = os.path.realpath(path or "")
|
||||
if real not in owned_paths():
|
||||
return {"ok": False, "error": "Den video har portalen ikke selv hentet, så den røres ikke"}
|
||||
st = get_state()
|
||||
try:
|
||||
size = file_size(real)
|
||||
for side in _sidecars(real):
|
||||
os.remove(side)
|
||||
if os.path.exists(real):
|
||||
os.remove(real)
|
||||
except OSError as e:
|
||||
return {"ok": False, "error": f"Kunne ikke slette: {e}"}
|
||||
st["leaving"].pop(real, None)
|
||||
titel = os.path.basename(real)
|
||||
for j in st["jobs"].values():
|
||||
paths = [p for p in (j.get("paths") or ([j["path"]] if j.get("path") else [])) if p]
|
||||
if real not in {os.path.realpath(p) for p in paths}:
|
||||
continue
|
||||
titel = j.get("title") or titel
|
||||
rest = [p for p in paths if os.path.realpath(p) != real]
|
||||
if j.get("paths") is not None:
|
||||
j["paths"] = rest
|
||||
if j.get("videos"):
|
||||
j["videos"] = [v for v in j["videos"] if os.path.realpath(v.get("path") or "") != real]
|
||||
if not rest:
|
||||
j["status"] = "deleted"
|
||||
_log(st, f"Slettet manuelt: {titel} ({_human(size)})")
|
||||
save_state(st)
|
||||
return {"ok": True, "title": titel, "size": size}
|
||||
|
||||
|
||||
# ---------------- Emby ----------------
|
||||
async def _emby(client, path, **params):
|
||||
params["api_key"] = config.EMBY_API_KEY
|
||||
|
||||
Reference in New Issue
Block a user