update
This commit is contained in:
@@ -28,6 +28,13 @@ CREATE TABLE IF NOT EXISTS clips (
|
||||
datei TEXT,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- Der Film einer Aufnahme, vom Host gesetzt. Ohne Eintrag gilt der Abend
|
||||
-- mit dem gleichen Datum — oder keiner.
|
||||
CREATE TABLE IF NOT EXISTS aufnahme_film (
|
||||
start TEXT PRIMARY KEY,
|
||||
movie_id INTEGER NOT NULL
|
||||
);
|
||||
"""
|
||||
|
||||
STREAM_PFAD = "filmabend"
|
||||
@@ -223,33 +230,9 @@ async def _segmente(von, bis):
|
||||
return resp.json()
|
||||
|
||||
|
||||
async def tage_mit_aufnahme():
|
||||
"""An welchen Abenden es Aufnahmen gibt — als Datum, wobei der Abend
|
||||
bis zum nächsten Mittag reicht. Für die Auswahl im Clips-Reiter."""
|
||||
segmente = await _segmente(datetime(2000, 1, 1, tzinfo=timezone.utc), _jetzt())
|
||||
tage = set()
|
||||
for s in segmente:
|
||||
start = datetime.fromisoformat(s["start"].replace("Z", "+00:00"))
|
||||
tage.add((start - timedelta(hours=12)).strftime("%Y-%m-%d"))
|
||||
return sorted(tage)
|
||||
|
||||
|
||||
async def aufnahmen_des_abends(watched_at):
|
||||
"""Die Aufnahmen zu einem Abend: alles vom Mittag des Tages bis zum
|
||||
nächsten Mittag — so gehört auch die Nacht nach Mitternacht dazu.
|
||||
Wurde der Stream zwischendurch beendet, sind es mehrere Stücke,
|
||||
nach Beginn geordnet."""
|
||||
tag = datetime.fromisoformat(watched_at[:10]).replace(tzinfo=timezone.utc)
|
||||
mittag = tag + timedelta(hours=12)
|
||||
segmente = await _segmente(mittag, mittag + timedelta(days=1))
|
||||
stuecke = [
|
||||
{
|
||||
"start": _iso(datetime.fromisoformat(s["start"].replace("Z", "+00:00"))),
|
||||
"dauer": round(s["duration"], 1),
|
||||
}
|
||||
for s in segmente
|
||||
]
|
||||
return sorted(stuecke, key=lambda s: s["start"])
|
||||
# ── Aufnahmen: die Stücke aus MediaMTX, jedes mit seinem Film ──────────
|
||||
# Eine Aufnahme hängt an keinem Abend. Der Host weist ihr einen Film zu;
|
||||
# ohne Zuweisung gilt der Abend mit dem gleichen Datum, sonst keiner.
|
||||
|
||||
|
||||
def _stueck(s):
|
||||
@@ -259,10 +242,83 @@ def _stueck(s):
|
||||
}
|
||||
|
||||
|
||||
def _abendtag(iso):
|
||||
"""Der Abend reicht bis zum nächsten Mittag — die Nacht gehört dazu."""
|
||||
zeit = datetime.fromisoformat(iso.replace("Z", "+00:00"))
|
||||
return (zeit - timedelta(hours=12)).strftime("%Y-%m-%d")
|
||||
|
||||
|
||||
def film_zuweisen(start, movie_id):
|
||||
"""Der Host hängt einen Film an eine Aufnahme — oder nimmt ihn weg."""
|
||||
with connect() as conn:
|
||||
if movie_id is None:
|
||||
conn.execute("DELETE FROM aufnahme_film WHERE start = ?", (start,))
|
||||
else:
|
||||
conn.execute(
|
||||
"INSERT OR REPLACE INTO aufnahme_film (start, movie_id) VALUES (?, ?)",
|
||||
(start, movie_id),
|
||||
)
|
||||
|
||||
|
||||
def _filme(conn):
|
||||
"""Zuweisungen je Start und Abende je Tag — um Aufnahmen ihren Film zu geben."""
|
||||
zugewiesen = {
|
||||
r["start"]: {"id": r["movie_id"], "title": r["title"], "poster_path": r["poster_path"]}
|
||||
for r in conn.execute(
|
||||
"SELECT a.start, a.movie_id, m.title, m.poster_path FROM aufnahme_film a "
|
||||
"LEFT JOIN movies m ON m.id = a.movie_id"
|
||||
)
|
||||
}
|
||||
abende = {}
|
||||
for w in conn.execute(
|
||||
"SELECT movie_id, title, poster_path, watched_at FROM watched ORDER BY id"
|
||||
):
|
||||
abende.setdefault(
|
||||
_abendtag(w["watched_at"]),
|
||||
{"id": w["movie_id"], "title": w["title"], "poster_path": w["poster_path"]},
|
||||
)
|
||||
return zugewiesen, abende
|
||||
|
||||
|
||||
def _mit_film(stueck, zugewiesen, abende):
|
||||
film = zugewiesen.get(stueck["start"])
|
||||
return {
|
||||
**stueck,
|
||||
"film": film or abende.get(_abendtag(stueck["start"])),
|
||||
"zugewiesen": film is not None,
|
||||
}
|
||||
|
||||
|
||||
async def aufnahmen_alle():
|
||||
"""Alle Aufnahmen, neueste zuerst — der Host räumt damit auf."""
|
||||
"""Alle Aufnahmen, neueste zuerst, jede mit ihrem Film."""
|
||||
segmente = await _segmente(datetime(2000, 1, 1, tzinfo=timezone.utc), _jetzt())
|
||||
return sorted((_stueck(s) for s in segmente), key=lambda s: s["start"], reverse=True)
|
||||
with connect() as conn:
|
||||
zugewiesen, abende = _filme(conn)
|
||||
stuecke = [_mit_film(_stueck(s), zugewiesen, abende) for s in segmente]
|
||||
return sorted(stuecke, key=lambda s: s["start"], reverse=True)
|
||||
|
||||
|
||||
def film_der_aufnahme(start):
|
||||
"""Nur die id — für neue Marken."""
|
||||
with connect() as conn:
|
||||
zugewiesen, abende = _filme(conn)
|
||||
film = _mit_film({"start": start}, zugewiesen, abende)["film"]
|
||||
return film["id"] if film else None
|
||||
|
||||
|
||||
async def film_live():
|
||||
"""Der Film der gerade laufenden Aufnahme — für die Taste C. None,
|
||||
wenn nichts aufgenommen wird oder MediaMTX nicht antwortet."""
|
||||
try:
|
||||
segmente = await _segmente(_jetzt() - timedelta(hours=6), _jetzt())
|
||||
except httpx.HTTPError:
|
||||
return None
|
||||
for s in segmente:
|
||||
stueck = _stueck(s)
|
||||
ende = datetime.fromisoformat(stueck["start"]) + timedelta(seconds=stueck["dauer"])
|
||||
if ende >= _jetzt() - timedelta(seconds=90):
|
||||
return film_der_aufnahme(stueck["start"])
|
||||
return None
|
||||
|
||||
|
||||
def _im_stueck(segmente, start, dauer):
|
||||
|
||||
@@ -307,15 +307,17 @@ def clip_oder_404(clip):
|
||||
|
||||
|
||||
@app.post("/api/clips")
|
||||
def marke_setzen(response: Response, filmabend_sid: str | None = Cookie(None)):
|
||||
"""Taste C: der Moment wird gemerkt, mehr nicht. Der Film ist der
|
||||
zuletzt gezogene, sonst der neueste Abend unter Gesehen."""
|
||||
async def marke_setzen(response: Response, filmabend_sid: str | None = Cookie(None)):
|
||||
"""Taste C: der Moment wird gemerkt, mehr nicht. Der Film ist der der
|
||||
laufenden Aufnahme, sonst der zuletzt gezogene, sonst der neueste
|
||||
Abend unter Gesehen."""
|
||||
sid = sitzung(response, filmabend_sid)
|
||||
mitglied(sid)
|
||||
movie_id = await clips.film_live()
|
||||
gezogen = letzter_dreh["ergebnis"]
|
||||
if gezogen:
|
||||
if movie_id is None and gezogen:
|
||||
movie_id = gezogen["gewinner"]["movie_id"]
|
||||
else:
|
||||
if movie_id is None:
|
||||
abend = watched.juengster()
|
||||
movie_id = abend["movie_id"] if abend else None
|
||||
clip = clips.markieren(sid, movie_id)
|
||||
@@ -323,27 +325,34 @@ def marke_setzen(response: Response, filmabend_sid: str | None = Cookie(None)):
|
||||
return clip
|
||||
|
||||
|
||||
@app.get("/api/clips/aufnahmen")
|
||||
async def clip_aufnahmen():
|
||||
"""Zu welchen Abenden es eine Aufnahme gibt."""
|
||||
try:
|
||||
return {"tage": await clips.tage_mit_aufnahme()}
|
||||
except httpx.HTTPError:
|
||||
return {"tage": []}
|
||||
|
||||
|
||||
@app.get("/api/aufnahmen")
|
||||
async def aufnahmen_liste(response: Response, filmabend_sid: str | None = Cookie(None)):
|
||||
"""Alle Stücke der Aufnahme, neueste zuerst — für die Verwaltung."""
|
||||
sid = sitzung(response, filmabend_sid)
|
||||
if not users.ist_host(sid):
|
||||
raise HTTPException(status_code=403, detail="Nur für den Host")
|
||||
async def aufnahmen_liste():
|
||||
"""Alle Aufnahmen, neueste zuerst, jede mit ihrem Film — für
|
||||
„Clip erstellen" und die Verwaltung."""
|
||||
try:
|
||||
return {"stuecke": await clips.aufnahmen_alle()}
|
||||
except httpx.HTTPError:
|
||||
raise HTTPException(status_code=502, detail="Die Aufnahmen sind gerade nicht erreichbar.")
|
||||
|
||||
|
||||
@app.put("/api/aufnahmen/film")
|
||||
async def aufnahme_film_setzen(
|
||||
response: Response,
|
||||
start: str = Body(..., embed=True),
|
||||
movie_id: int | None = Body(None, embed=True),
|
||||
filmabend_sid: str | None = Cookie(None),
|
||||
):
|
||||
"""Der Host sagt, welcher Film in einer Aufnahme lief — oder nimmt es zurück."""
|
||||
sid = sitzung(response, filmabend_sid)
|
||||
if not users.ist_host(sid):
|
||||
raise HTTPException(status_code=403, detail="Nur für den Host")
|
||||
if movie_id is not None:
|
||||
# Suchtreffer stehen nicht im Katalog — Titel und Poster müssen aber her
|
||||
await film_holen(movie_id)
|
||||
clips.film_zuweisen(start, movie_id)
|
||||
return {"zugewiesen": movie_id is not None}
|
||||
|
||||
|
||||
@app.delete("/api/aufnahmen")
|
||||
async def aufnahme_loeschen(
|
||||
response: Response, start: str, dauer: float, filmabend_sid: str | None = Cookie(None)
|
||||
@@ -360,51 +369,25 @@ async def aufnahme_loeschen(
|
||||
)
|
||||
except httpx.HTTPError:
|
||||
raise HTTPException(status_code=502, detail="Die MediaMTX-API ist nicht erreichbar.")
|
||||
clips.film_zuweisen(start, None)
|
||||
return {"segmente": anzahl}
|
||||
|
||||
|
||||
@app.get("/api/clips/von-film/{movie_id}")
|
||||
async def clips_stuecke(
|
||||
movie_id: int, response: Response, filmabend_sid: str | None = Cookie(None)
|
||||
):
|
||||
"""Die Aufnahmen zu einem Abend — mehrere, wenn der Stream neu
|
||||
gestartet wurde. Daraus wählt man das Stück für den Clip."""
|
||||
sid = sitzung(response, filmabend_sid)
|
||||
mitglied(sid)
|
||||
abend = watched.von_film(movie_id)
|
||||
if not abend:
|
||||
raise HTTPException(status_code=404, detail="Dieser Film steht nicht unter Gesehen.")
|
||||
try:
|
||||
return {"stuecke": await clips.aufnahmen_des_abends(abend["watched_at"])}
|
||||
except httpx.HTTPError:
|
||||
return {"stuecke": []}
|
||||
|
||||
|
||||
@app.post("/api/clips/von-film")
|
||||
async def clip_von_film(
|
||||
@app.post("/api/clips/von-aufnahme")
|
||||
def clip_von_aufnahme(
|
||||
response: Response,
|
||||
movie_id: int = Body(..., embed=True),
|
||||
start: str | None = Body(None, embed=True),
|
||||
start: str = Body(..., embed=True),
|
||||
filmabend_sid: str | None = Cookie(None),
|
||||
):
|
||||
"""Ein Clip zu einem alten Abend: die Marke kommt an den Anfang des
|
||||
gewählten Stücks — ohne Wahl an den des ersten. Das Fenster schiebt
|
||||
man dann im Editor hin."""
|
||||
"""Ein Clip aus einer alten Aufnahme: die Marke kommt kurz nach deren
|
||||
Anfang, das Fenster schiebt man dann im Editor hin. Den Film bringt
|
||||
die Aufnahme mit — oder keinen."""
|
||||
sid = sitzung(response, filmabend_sid)
|
||||
mitglied(sid)
|
||||
abend = watched.von_film(movie_id)
|
||||
if not abend:
|
||||
raise HTTPException(status_code=404, detail="Dieser Film steht nicht unter Gesehen.")
|
||||
if start is None:
|
||||
try:
|
||||
stuecke = await clips.aufnahmen_des_abends(abend["watched_at"])
|
||||
except httpx.HTTPError:
|
||||
stuecke = []
|
||||
if not stuecke:
|
||||
raise HTTPException(status_code=404, detail="Zu diesem Abend gibt es keine Aufnahme.")
|
||||
start = stuecke[0]["start"]
|
||||
beginn = datetime.fromisoformat(start)
|
||||
clip = clips.markieren(sid, movie_id, beginn + timedelta(seconds=clips.VORHER + 30))
|
||||
clip = clips.markieren(
|
||||
sid, clips.film_der_aufnahme(start), beginn + timedelta(seconds=clips.VORHER + 30)
|
||||
)
|
||||
events.senden("clips")
|
||||
return clip
|
||||
|
||||
|
||||
Reference in New Issue
Block a user