diff --git a/journeyfm/config_store.py b/journeyfm/config_store.py index fd0d790..c193086 100644 --- a/journeyfm/config_store.py +++ b/journeyfm/config_store.py @@ -24,6 +24,7 @@ NON_SECRET_KEYS = { "SERVER_IP", "PLAYLIST_NAME", "PLAYLIST_NAMES", + "PLAYLISTS", "AUTO_UPDATE", "UPDATE_INTERVAL", "UPDATE_UNIT", diff --git a/journeyfm/update_service.py b/journeyfm/update_service.py index b1f4561..12a6e2f 100644 --- a/journeyfm/update_service.py +++ b/journeyfm/update_service.py @@ -100,18 +100,35 @@ def run_update_job(config=None, dry_run=False, persist_history=True, write_buy_l "skipped_songs": [], } + import json as _json + + # station_key → display name used in song["source"] + STATION_SOURCE = {"journey_fm": "Journey FM", "spirit_fm": "Spirit FM", "klove": "K-LOVE"} + 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()] + # Build playlist configs: [{name, stations}] + playlists_raw = config.get("PLAYLISTS") + if playlists_raw: + try: + playlists = _json.loads(playlists_raw) if isinstance(playlists_raw, str) else playlists_raw + except Exception: + playlists = [] 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"] + playlists = [] + + if not playlists: + # Fall back to flat PLAYLIST_NAMES / PLAYLIST_NAME + SELECTED_STATIONS + raw_names = config.get("PLAYLIST_NAMES") or config.get("PLAYLIST_NAME", "Journey FM Recently Played") + if isinstance(raw_names, list): + names = raw_names + else: + names = [n.strip() for n in str(raw_names).split(",") if n.strip()] + stations = config.get("SELECTED_STATIONS", ["journey_fm"]) + if isinstance(stations, str): + stations = [s.strip() for s in stations.split(",") if s.strip()] + playlists = [{"name": n, "stations": list(stations)} for n in (names or ["Journey FM Recently Played"])] if not token or not server_ip: result["status"] = "error" @@ -121,18 +138,21 @@ def run_update_job(config=None, dry_run=False, persist_history=True, write_buy_l if persist_history: init_history_db() - scrape_result = scrape_recently_played(selected_stations) - songs = scrape_result["songs"] + # Scrape the union of all stations across all playlists (no duplicate scrapes) + all_station_keys = list({s for pl in playlists for s in pl.get("stations", [])}) + scrape_result = scrape_recently_played(all_station_keys) + all_songs = scrape_result["songs"] result["station_breakdown"] = scrape_result["station_results"] - result["scraped_count"] = len(songs) - result["scraped_songs"] = songs + result["scraped_count"] = len(all_songs) + result["scraped_songs"] = all_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) + for pl in playlists: + pl_sources = {STATION_SOURCE.get(s, s) for s in pl.get("stations", [])} + pl_songs = [s for s in all_songs if s.get("source", "") in pl_sources] if pl_sources else all_songs + pr = create_or_update_playlist(plex, pl_songs, pl["name"], dry_run=dry_run) if not merged: merged = pr else: diff --git a/journeyfm/web_service.py b/journeyfm/web_service.py index 684a173..0299223 100644 --- a/journeyfm/web_service.py +++ b/journeyfm/web_service.py @@ -187,39 +187,49 @@ def dedup_buy_list(buy_list_path=None): def get_web_config(): from journeyfm.config_store import _read_config_file, DEFAULT_CONFIG, _normalize_selected_stations - # Read config.json directly so the form shows/saves the file values, - # not the env-var-merged runtime config (env vars still win at sync time). + # Read config.json directly so the form is consistent in container environments + # where env vars override the runtime config (env vars still win at sync time). file_cfg = _read_config_file() config = dict(DEFAULT_CONFIG) config.update({k: v for k, v in file_cfg.items() if k != "PLEX_TOKEN"}) config["_has_token"] = bool(get_secret("PLEX_TOKEN")) config["PLEX_TOKEN"] = "" - config["SELECTED_STATIONS"] = _normalize_selected_stations(config.get("SELECTED_STATIONS", [])) - # Normalise playlist names to a list - raw = config.get("PLAYLIST_NAMES") or config.get("PLAYLIST_NAME", "") - if isinstance(raw, list): - playlists = [n.strip() for n in raw if str(n).strip()] + # Parse PLAYLISTS JSON → list of {name, stations} + playlists_raw = config.get("PLAYLISTS") + if playlists_raw: + try: + playlists = json.loads(playlists_raw) if isinstance(playlists_raw, str) else playlists_raw + except Exception: + playlists = [] else: - playlists = [n.strip() for n in str(raw).split(",") if n.strip()] - config["PLAYLIST_NAMES"] = playlists + playlists = [] + if not playlists: + # Build from flat keys for backward compat + raw_names = config.get("PLAYLIST_NAMES") or config.get("PLAYLIST_NAME", "") + if isinstance(raw_names, list): + names = raw_names + else: + names = [n.strip() for n in str(raw_names).split(",") if n.strip()] + stations = list(_normalize_selected_stations(config.get("SELECTED_STATIONS", []))) + playlists = [{"name": n, "stations": stations} for n in names] if names else [] + config["PLAYLISTS"] = playlists return config def save_web_config(data): config = {} config["SERVER_IP"] = str(data.get("SERVER_IP", "")).strip() - # Accept PLAYLIST_NAMES as list or comma string; store as comma string - raw = data.get("PLAYLIST_NAMES", data.get("PLAYLIST_NAME", "")) - if isinstance(raw, list): - playlists = [n.strip() for n in raw if str(n).strip()] + # PLAYLISTS: list of {name, stations} + playlists = data.get("PLAYLISTS", []) + if isinstance(playlists, list): + clean = [{"name": str(p.get("name", "")).strip(), "stations": list(p.get("stations", []))} + for p in playlists if str(p.get("name", "")).strip()] else: - playlists = [n.strip() for n in str(raw).split(",") if n.strip()] - config["PLAYLIST_NAMES"] = ",".join(playlists) - config["PLAYLIST_NAME"] = playlists[0] if playlists else "" - stations = data.get("SELECTED_STATIONS", []) - if isinstance(stations, str): - stations = [s.strip() for s in stations.split(",") if s.strip()] - config["SELECTED_STATIONS"] = stations + clean = [] + config["PLAYLISTS"] = json.dumps(clean) + config["PLAYLIST_NAME"] = clean[0]["name"] if clean else "" + all_stations = list({s for p in clean for s in p.get("stations", [])}) + config["SELECTED_STATIONS"] = all_stations config["AUTO_UPDATE"] = bool(data.get("AUTO_UPDATE", False)) try: config["UPDATE_INTERVAL"] = int(data.get("UPDATE_INTERVAL", 15)) @@ -597,21 +607,11 @@ tr:last-child td{border-bottom:none}tr:hover td{background:var(--sf2)}