feat: integrated audio player + animated transcript follower
- Video detail (done videos): added 'Download audio' button; removed the
manual 'Thumbnail' button (thumbnails auto-download now, so it was redundant).
- New 'GET /api/videos/{id}/audio' endpoint (also serves HEAD for probing),
serving data/audio/<video_id>.mp3. Audio job outtmpl switched to %(id)s so
files are addressable per video.
- Integrated <audio> player appears in the detail view once the MP3 is present
(probed via HEAD on openVideo; polled after a download job until ready).
- Synced transcript follower: as audio plays, the matching segment is
highlighted (seg-active) and auto-scrolled into view like a karaoke/lyrics
tracker. Clicking any segment timestamp or chapter seeks the audio to that
point (falls back to scroll when no audio). Playback indicator pulses while
playing.
This commit is contained in:
@@ -575,6 +575,22 @@ class Store:
|
||||
cur.execute("SELECT * FROM scrape_jobs ORDER BY started_at DESC LIMIT ?", (limit,))
|
||||
return [_row_to_jobrow(r) for r in cur.fetchall()]
|
||||
|
||||
TERMINAL_STATUSES = ("done", "error", "cancelled")
|
||||
|
||||
def delete_job(self, job_id: str) -> bool:
|
||||
with self._cursor() as cur:
|
||||
cur.execute("DELETE FROM scrape_jobs WHERE id = ?", (job_id,))
|
||||
return cur.rowcount > 0
|
||||
|
||||
def delete_terminal_jobs(self) -> int:
|
||||
placeholders = ",".join("?" for _ in self.TERMINAL_STATUSES)
|
||||
with self._cursor() as cur:
|
||||
cur.execute(
|
||||
f"DELETE FROM scrape_jobs WHERE status IN ({placeholders})",
|
||||
list(self.TERMINAL_STATUSES),
|
||||
)
|
||||
return cur.rowcount
|
||||
|
||||
# ------------------------------------------------------------------ aggregates
|
||||
|
||||
def stats(self, channel_id: str | None = None) -> dict[str, int]:
|
||||
|
||||
Reference in New Issue
Block a user