Files
journey-fm-playlist/journeyfm/update_service.py
T
nmemmertandClaude Sonnet 4.6 ff174a70ce Add multiple playlist support; fix settings save in container
- PLAYLIST_NAMES config key (comma-separated list); falls back to PLAYLIST_NAME
- run_update_job syncs songs to every configured playlist, merges results
- Settings form: dynamic add/remove playlist rows instead of single input
- get_web_config reads config.json directly so form is consistent in
  container environments where env vars override runtime config
- Playlist tab: dropdown to pick which playlist to browse when multiple exist

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-26 08:57:00 -04:00

157 lines
6.1 KiB
Python

import logging
import os
import urllib.parse
from journeyfm.config_store import load_runtime_config
from journeyfm.history_service import init_history_db, save_history_entry
from journeyfm.paths import data_path
from journeyfm.plex_service import PlexConnectionError, connect_to_plex_server, create_or_update_playlist
from journeyfm.scraper_service import scrape_recently_played
logger = logging.getLogger(__name__)
def update_buy_list(missing_songs, buy_list_path=None):
buy_list_path = buy_list_path or data_path("amazon_buy_list.txt")
if not missing_songs:
logger.info("No new songs to add to buy list.")
return []
existing_songs = set()
if os.path.exists(buy_list_path):
try:
with open(buy_list_path, "r", encoding="utf-8") as file_handle:
lines = file_handle.read().splitlines()
for line in lines:
if line.strip() and not line.startswith("http") and " - " in line:
artist, title = line.split(" - ", 1)
existing_songs.add((artist.strip(), title.strip()))
except Exception:
pass
new_missing = []
for song in missing_songs:
key = (song["artist"], song["title"])
if key in existing_songs:
continue
existing_songs.add(key)
new_missing.append(song)
if not new_missing:
logger.info("No new songs to add to buy list.")
return []
file_exists = os.path.exists(buy_list_path)
file_empty = not file_exists or os.path.getsize(buy_list_path) == 0
with open(buy_list_path, "a", encoding="utf-8") as file_handle:
if file_empty:
file_handle.write("Songs not in your library - Amazon search links:\n\n")
for song in new_missing:
query = urllib.parse.quote(f"{song['artist']} {song['title']}")
file_handle.write(f"{song['artist']} - {song['title']}\n")
file_handle.write(f"https://www.amazon.com/s?k={query}&i=digital-music\n\n")
logger.info("Buy list updated.")
return new_missing
def format_result_summary(result):
station_bits = []
for station in result.get("station_breakdown", []):
if station.get("success"):
pattern = station.get("parse_pattern", "unknown")
payload_bytes = int(station.get("raw_payload_bytes", 0) or 0)
payload_kib = payload_bytes / 1024.0
station_bits.append(
f"{station['display_name']}: {station['scraped_count']} [{pattern}, {payload_kib:.1f} KiB]"
)
else:
station_bits.append(f"{station['display_name']}: failed")
station_summary = ", ".join(station_bits) or "No station data"
lines = [
f"Scraped {result.get('scraped_count', 0)} songs ({station_summary})",
f"Matched {result.get('matched_count', 0)} in Plex",
f"Added {result.get('added_count', 0)} to playlist",
f"Skipped {result.get('skipped_count', 0)} invalid entries",
f"Suppressed {result.get('duplicate_count', 0)} duplicates already in playlist",
f"Missing {result.get('missing_count', 0)} from Plex library",
]
if result.get("status") == "error":
lines.append(f"Error: {result.get('error_message', 'Unknown error')}")
return "\n".join(lines)
def run_update_job(config=None, dry_run=False, persist_history=True, write_buy_list=True):
config = config or load_runtime_config()
result = {
"status": "success",
"error_message": "",
"station_breakdown": [],
"scraped_count": 0,
"matched_count": 0,
"added_count": 0,
"added_songs": [],
"missing_count": 0,
"missing_songs": [],
"duplicate_count": 0,
"duplicate_songs": [],
"skipped_count": 0,
"skipped_songs": [],
}
token = config.get("PLEX_TOKEN", "").strip()
server_ip = config.get("SERVER_IP", "").strip()
selected_stations = config.get("SELECTED_STATIONS", ["journey_fm", "spirit_fm"])
# Support PLAYLIST_NAMES (list) or fall back to single PLAYLIST_NAME
raw_names = config.get("PLAYLIST_NAMES") or config.get("PLAYLIST_NAME", "Journey FM Recently Played")
if isinstance(raw_names, list):
playlist_names = [n.strip() for n in raw_names if str(n).strip()]
else:
playlist_names = [n.strip() for n in str(raw_names).split(",") if n.strip()]
if not playlist_names:
playlist_names = ["Journey FM Recently Played"]
if not token or not server_ip:
result["status"] = "error"
result["error_message"] = "Missing configuration: set Plex token and server IP before running updates"
return result
if persist_history:
init_history_db()
scrape_result = scrape_recently_played(selected_stations)
songs = scrape_result["songs"]
result["station_breakdown"] = scrape_result["station_results"]
result["scraped_count"] = len(songs)
result["scraped_songs"] = songs
try:
plex = connect_to_plex_server(token, server_ip)
# Sync every configured playlist; aggregate results
merged = {}
for playlist_name in playlist_names:
pr = create_or_update_playlist(plex, songs, playlist_name, dry_run=dry_run)
if not merged:
merged = pr
else:
merged["added_count"] = merged.get("added_count", 0) + pr.get("added_count", 0)
merged["added_songs"] = merged.get("added_songs", []) + pr.get("added_songs", [])
merged["duplicate_count"] = merged.get("duplicate_count", 0) + pr.get("duplicate_count", 0)
result.update(merged)
result["missing_count"] = len(result.get("missing_songs", []))
result["skipped_count"] = len(result.get("skipped_songs", []))
if write_buy_list and not dry_run:
update_buy_list(result.get("missing_songs", []))
except PlexConnectionError as exc:
result["status"] = "error"
result["error_message"] = str(exc)
except Exception as exc:
result["status"] = "error"
result["error_message"] = f"Unexpected update failure: {exc}"
if persist_history:
save_history_entry(result)
return result