mirror of
https://github.com/mistergibson/radio.git
synced 2026-09-08 22:09:51 -07:00
Minor tweaks
This commit is contained in:
parent
0c5f864007
commit
952c9ae5a9
9 changed files with 1408 additions and 2024 deletions
|
|
@ -1,26 +1,162 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Regenerate per-show playlist files based on playback history."""
|
||||
"""
|
||||
update_playlists.py - Regenerate per-show .pls playlists based on playback history.
|
||||
Runs via cron hourly at :30.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import sqlite3
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
AUDIO_ROOT = Path(__file__).resolve().parent
|
||||
PLAYLISTS_DIR = AUDIO_ROOT / "playlists"
|
||||
STATE_DB = AUDIO_ROOT / "state" / "played.db"
|
||||
SUBS_DB = AUDIO_ROOT / "state" / "subscriptions.db"
|
||||
SHOWS_DIR = AUDIO_ROOT / "podcasts"
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
STATE_DIR = ROOT / "state"
|
||||
SUBS_DB = STATE_DIR / "subscriptions.db"
|
||||
PLAYED_DB = STATE_DIR / "played.db"
|
||||
PODCASTS_DIR = ROOT / "podcasts"
|
||||
PLAYLISTS_DIR = ROOT / "playlists"
|
||||
LOGS_DIR = ROOT / "logs"
|
||||
|
||||
AUDIO_EXTS = {".mp3", ".m4a"}
|
||||
|
||||
def get_played_db():
|
||||
STATE_DB.parent.mkdir(parents=True, exist_ok=True)
|
||||
conn = sqlite3.connect(str(STATE_DB))
|
||||
conn.execute("""CREATE TABLE IF NOT EXISTS played (
|
||||
filename TEXT PRIMARY KEY,
|
||||
show TEXT,
|
||||
played_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)""")
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s [%(levelname)s] %(message)s",
|
||||
handlers=[
|
||||
logging.FileHandler(LOGS_DIR / "update.log"),
|
||||
logging.StreamHandler(sys.stdout),
|
||||
],
|
||||
)
|
||||
log = logging.getLogger("update_playlists")
|
||||
|
||||
def open_subs_db():
|
||||
conn = sqlite3.connect(SUBS_DB)
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
def open_played_db():
|
||||
conn = sqlite3.connect(PLAYED_DB)
|
||||
conn.row_factory = sqlite3.Row
|
||||
conn.execute("""
|
||||
CREATE TABLE IF NOT EXISTS episodes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
show_slug TEXT NOT NULL,
|
||||
guid TEXT NOT NULL,
|
||||
title TEXT,
|
||||
file_path TEXT,
|
||||
duration_seconds INTEGER,
|
||||
played_at TEXT,
|
||||
UNIQUE(show_slug, guid)
|
||||
)
|
||||
""")
|
||||
conn.commit()
|
||||
return conn
|
||||
|
||||
def find_audio_files(directory):
|
||||
"""Recursively scan for .mp3/.m4a files."""
|
||||
results = []
|
||||
if not directory.exists():
|
||||
return results
|
||||
for p in sorted(directory.rglob("*")):
|
||||
if p.is_file() and p.suffix.lower() in AUDIO_EXTS:
|
||||
results.append(str(p))
|
||||
return results
|
||||
|
||||
def select_unplayed_episode(slug, played_db):
|
||||
"""Pick one unplayed episode for the show, falling back to least-recently-played."""
|
||||
files = find_audio_files(PODCASTS_DIR / slug)
|
||||
if not files:
|
||||
return None
|
||||
|
||||
played_rows = played_db.execute(
|
||||
"SELECT file_path, played_at FROM episodes WHERE show_slug = ? AND played_at IS NOT NULL",
|
||||
(slug,),
|
||||
).fetchall()
|
||||
played_paths = {row["file_path"]: row["played_at"] for row in played_rows}
|
||||
|
||||
unplayed = [f for f in files if f not in played_paths]
|
||||
if unplayed:
|
||||
return unplayed[0]
|
||||
|
||||
# All played: pick least recently played
|
||||
if played_paths:
|
||||
return min(played_paths.items(), key=lambda kv: (kv[1] or ""))[0]
|
||||
return files[0]
|
||||
|
||||
def write_pls(filepath, out_path):
|
||||
"""Write a .pls playlist pointing at a single file."""
|
||||
abs_path = str(Path(filepath).resolve())
|
||||
content = f"[playlist]\nFile1={abs_path}\nTitle1=Radio Episode\nLength1=-1\nNumberOfEntries=1\nVersion=2\n"
|
||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
out_path.write_text(content)
|
||||
|
||||
def mark_as_played(slug, filepath, duration, played_db):
|
||||
played_db.execute(
|
||||
"UPDATE episodes SET played_at = datetime('now') WHERE show_slug = ? AND file_path = ?",
|
||||
(slug, filepath),
|
||||
)
|
||||
played_db.commit()
|
||||
|
||||
def update_all():
|
||||
subs_db = open_subs_db()
|
||||
played_db = open_played_db()
|
||||
shows = subs_db.execute("SELECT slug, name FROM shows ORDER BY name").fetchall()
|
||||
|
||||
for show in shows:
|
||||
slug = show["slug"]
|
||||
selected = select_unplayed_episode(slug, played_db)
|
||||
if selected is None:
|
||||
log.info("%s: no audio files found, skipping.", slug)
|
||||
continue
|
||||
|
||||
duration_row = played_db.execute(
|
||||
"SELECT duration_seconds FROM episodes WHERE show_slug = ? AND file_path = ?",
|
||||
(slug, selected),
|
||||
).fetchone()
|
||||
duration = duration_row["duration_seconds"] if duration_row else None
|
||||
|
||||
out_pls = PLAYLISTS_DIR / f"{slug}.pls"
|
||||
write_pls(selected, out_pls)
|
||||
mark_as_played(slug, selected, duration, played_db)
|
||||
log.info("%s: queued %s", slug, Path(selected).name)
|
||||
|
||||
subs_db.close()
|
||||
played_db.close()
|
||||
|
||||
def json_summary():
|
||||
subs_db = open_subs_db()
|
||||
played_db = open_played_db()
|
||||
shows = subs_db.execute("SELECT slug FROM shows ORDER BY name").fetchall()
|
||||
summary = {}
|
||||
for show in shows:
|
||||
slug = show["slug"]
|
||||
files = find_audio_files(PODCASTS_DIR / slug)
|
||||
played_count = played_db.execute(
|
||||
"SELECT COUNT(*) as c FROM episodes WHERE show_slug = ? AND played_at IS NOT NULL",
|
||||
(slug,),
|
||||
).fetchone()["c"]
|
||||
summary[slug] = {"total_files": len(files), "played_count": played_count}
|
||||
subs_db.close()
|
||||
played_db.close()
|
||||
print(json.dumps(summary, indent=2))
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Playlist updater for radio automation")
|
||||
parser.add_argument("--json", action="store_true", help="Emit JSON summary and exit")
|
||||
args = parser.parse_args()
|
||||
|
||||
STATE_DIR.mkdir(exist_ok=True)
|
||||
LOGS_DIR.mkdir(exist_ok=True)
|
||||
PLAYLISTS_DIR.mkdir(exist_ok=True)
|
||||
|
||||
if args.json:
|
||||
json_summary()
|
||||
else:
|
||||
update_all()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue