A new isolated Calendar page (/api/video/calendar) — every upcoming episode for your owned shows across a real 7-day week (today first), as art cards sorted by air time with a per-cell breathing colour glow. - Air times: enrich shows with TVDB airsTime (new shows.airs_time column + migration); cells show + sort by time, streaming (untimed/00:00) = "Anytime". One-time background backfill re-queues already-matched shows for the time. - Click an episode → styled modal (show backdrop hero, episode still/synopsis, air date+time, owned badge, genres, "about the show"), with an explicit "Open full show page" action instead of navigating on click. - Isolated: reads only video_library.db, writes nothing to the music side.
62 lines
2.3 KiB
Python
62 lines
2.3 KiB
Python
"""Video Calendar — upcoming TV episodes for OWNED shows.
|
|
|
|
GET /api/video/calendar?days=N → episodes airing from today through today+N-1,
|
|
grouped client-side into the agenda view. Isolated: reads only video_library.db
|
|
via VideoDatabase, writes nothing, never touches the music side.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import date, timedelta
|
|
|
|
from flask import jsonify, request
|
|
|
|
from utils.logging_config import get_logger
|
|
|
|
logger = get_logger("video.calendar")
|
|
|
|
|
|
def register_routes(bp):
|
|
@bp.route("/calendar", methods=["GET"])
|
|
def video_calendar():
|
|
from . import get_video_db
|
|
try:
|
|
days = request.args.get("days", default=7, type=int) or 7
|
|
days = max(1, min(days, 90)) # clamp: today .. +90d
|
|
start = date.today()
|
|
end = start + timedelta(days=days - 1)
|
|
db = get_video_db()
|
|
|
|
# One-time backfill: existing shows matched TVDB before air time was
|
|
# captured, so re-queue them once (background, only those missing it).
|
|
try:
|
|
if (db.get_setting("airs_time_backfill") or "") != "1":
|
|
n = db.requeue_shows_for_airtime()
|
|
db.set_setting("airs_time_backfill", "1")
|
|
if n:
|
|
logger.info("calendar: queued %d shows for TVDB air-time backfill", n)
|
|
except Exception:
|
|
logger.exception("airs_time backfill queue failed")
|
|
|
|
eps = db.calendar_upcoming(start.isoformat(), end.isoformat())
|
|
|
|
# Per-date counts drive the day-strip dots without a second query.
|
|
counts: dict[str, int] = {}
|
|
owned = 0
|
|
for e in eps:
|
|
counts[e["air_date"]] = counts.get(e["air_date"], 0) + 1
|
|
if e.get("has_file"):
|
|
owned += 1
|
|
return jsonify({
|
|
"today": start.isoformat(),
|
|
"start": start.isoformat(),
|
|
"end": end.isoformat(),
|
|
"days": days,
|
|
"counts_by_date": counts,
|
|
"total": len(eps),
|
|
"owned": owned,
|
|
"episodes": eps,
|
|
})
|
|
except Exception:
|
|
logger.exception("video calendar failed")
|
|
return jsonify({"error": "calendar failed"}), 500
|