Spotify: read the whole playlist so one wrong song shows WRONG PLAYLIST; refresh hourly and on unknown songs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -120,7 +120,7 @@ The **page number key** on every page follows the ALL button. It's green when ev
|
|||||||
| RESI | decoder (RossTalk connection and ping, 10.1.10.203); **encoder** (ping and its web page, 10.1.10.202, LAOEncoder); resi-studio-cues; Resi cloud reachable. Only shows they are on the network, not that the encoder is streaming. |
|
| RESI | decoder (RossTalk connection and ping, 10.1.10.203); **encoder** (ping and its web page, 10.1.10.202, LAOEncoder); resi-studio-cues; Resi cloud reachable. Only shows they are on the network, not that the encoder is streaming. |
|
||||||
| NET | NA Ingest's link; gateway 10.1.0.1 pings; **4+ connections from 3+ groups dropping within 10 s** (what happened Sunday 10/5 at 10:21). A blip **flashes amber** for 5 minutes, then goes by itself; press NET to stop the flashing, hold NET 2 s to clear it now. |
|
| NET | NA Ingest's link; gateway 10.1.0.1 pings; **4+ connections from 3+ groups dropping within 10 s** (what happened Sunday 10/5 at 10:21). A blip **flashes amber** for 5 minutes, then goes by itself; press NET to stop the flashing, hold NET 2 s to clear it now. |
|
||||||
| INGEST | the bridge (makes ProPresenter's stage screen count down to the next Resi cue), lobby captions server and caption screen, **ProdCom** (transcription, answering on :24480), ProdDash (running and its page on :24500); free disk |
|
| INGEST | the bridge (makes ProPresenter's stage screen count down to the next Resi cue), lobby captions server and caption screen, **ProdCom** (transcription, answering on :24480), ProdDash (running and its page on :24500); free disk |
|
||||||
| SPOTIFY | Spotify on NA Ingest must be playing the **Christian EDM 2026** playlist (`spotify:playlist:2uTpJVYQLFnKF1WnSOuco8`): **SPOTIFY CLOSED**, **NOT PLAYING**, or **WRONG PLAYLIST** (the song list comes from Spotify's public playlist page, refreshed every 6 h; it shows the first 100 of the playlist's songs, so the alarm needs 3 songs in a row that are not on it). Hold 2 s (and Sunday auto-fix) plays the playlist, opening Spotify first if needed. |
|
| SPOTIFY | Spotify on NA Ingest must be playing the **Christian EDM 2026** playlist (`spotify:playlist:2uTpJVYQLFnKF1WnSOuco8`): **SPOTIFY CLOSED**, **NOT PLAYING**, or **WRONG PLAYLIST** (it reads all of the playlist's songs from Spotify every hour, and again whenever a song plays that isn't on the list, since the playlist keeps getting new songs). Hold 2 s (and Sunday auto-fix) plays the playlist, opening Spotify first if needed. |
|
||||||
| RF (group) | Shure connections; battery ≤1 bar red, 2 bars amber; RF interference (only while that transmitter is on); PSM RF muted; each receiver pinged every 3 s ("AXIENT 1 NO PING" in about 6–9 s; Companion's Shure modules take 18–27 s to notice). The address is the one set on its Companion connection, read from Companion's settings every minute, so changing it there is enough; `"host"` in `config.json` is only the fallback. |
|
| RF (group) | Shure connections; battery ≤1 bar red, 2 bars amber; RF interference (only while that transmitter is on); PSM RF muted; each receiver pinged every 3 s ("AXIENT 1 NO PING" in about 6–9 s; Companion's Shure modules take 18–27 s to notice). The address is the one set on its Companion connection, read from Companion's settings every minute, so changing it there is enough; `"host"` in `config.json` is only the fallback. |
|
||||||
|
|
||||||
A problem has to last 3 seconds before it shows, so one bad reading doesn't trigger an alarm. Recovery shows immediately.
|
A problem has to last 3 seconds before it shows, so one bad reading doesn't trigger an alarm. Recovery shows immediately.
|
||||||
|
|||||||
+21
-9
@@ -1595,29 +1595,39 @@ SPOTIFY_STATE = ('tell application "Spotify" to return (player state as text) &
|
|||||||
|
|
||||||
|
|
||||||
def playlist_tracks(playlist_uri):
|
def playlist_tracks(playlist_uri):
|
||||||
"""(set of track uris, complete?) from Spotify's public pages: the embed preview lists up to
|
"""(set of track uris, complete?) for a public playlist. The embed preview page lists the first
|
||||||
100 songs, the playlist page says how many there are."""
|
100 songs and carries an anonymous token, which reads the whole list from Spotify's playlist service."""
|
||||||
pid = playlist_uri.split(":")[-1]
|
pid = playlist_uri.split(":")[-1]
|
||||||
req = lambda url: urllib.request.urlopen(urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}),
|
req = lambda url, **h: urllib.request.urlopen(urllib.request.Request(url, headers=dict({"User-Agent": "Mozilla/5.0"}, **h)),
|
||||||
timeout=10, context=_UNVERIFIED).read().decode("utf-8", "replace")
|
timeout=10, context=_UNVERIFIED).read().decode("utf-8", "replace")
|
||||||
m = re.search(r'<script id="__NEXT_DATA__" type="application/json">(.*?)</script>',
|
m = re.search(r'<script id="__NEXT_DATA__" type="application/json">(.*?)</script>',
|
||||||
req("https://open.spotify.com/embed/playlist/" + pid))
|
req("https://open.spotify.com/embed/playlist/" + pid))
|
||||||
entity = json.loads(m.group(1))["props"]["pageProps"]["state"]["data"]["entity"]
|
state = json.loads(m.group(1))["props"]["pageProps"]["state"]
|
||||||
tracks = {t["uri"] for t in entity.get("trackList", []) if t.get("uri")}
|
tracks = {t["uri"] for t in state["data"]["entity"].get("trackList", []) if t.get("uri")}
|
||||||
n = re.search(r'music:song_count" content="(\d+)"', req("https://open.spotify.com/playlist/" + pid))
|
try:
|
||||||
return tracks, bool(n) and len(tracks) >= int(n.group(1))
|
token = state["settings"]["session"]["accessToken"]
|
||||||
|
full = json.loads(req("https://spclient.wg.spotify.com/playlist/v2/playlist/%s?from=0&length=1000" % pid,
|
||||||
|
Authorization="Bearer " + token, Accept="application/json"))
|
||||||
|
items = {i["uri"] for i in full.get("contents", {}).get("items", []) if i.get("uri")}
|
||||||
|
if items and len(items) >= int(full.get("length") or 0):
|
||||||
|
return items, True
|
||||||
|
except (OSError, ValueError, KeyError, TypeError) as e:
|
||||||
|
log.warning("Spotify full playlist: %s (using the first %d songs)", e, len(tracks))
|
||||||
|
return tracks, False
|
||||||
|
|
||||||
|
|
||||||
def poll_spotify(snap, cfg):
|
def poll_spotify(snap, cfg):
|
||||||
checks = [c for t in cfg["tiles"] for c in t.get("checks", []) if c["type"] == "spotify"]
|
checks = [c for t in cfg["tiles"] for c in t.get("checks", []) if c["type"] == "spotify"]
|
||||||
if not checks:
|
if not checks:
|
||||||
return
|
return
|
||||||
if time.time() - snap.spotify_tracks_time > 6 * 3600: # the playlist changes weekly
|
def refresh():
|
||||||
try:
|
try:
|
||||||
snap.spotify_tracks, snap.spotify_complete = playlist_tracks(checks[0]["playlist"])
|
snap.spotify_tracks, snap.spotify_complete = playlist_tracks(checks[0]["playlist"])
|
||||||
except (OSError, ValueError, KeyError, AttributeError, TypeError) as e:
|
except (OSError, ValueError, KeyError, AttributeError, TypeError) as e:
|
||||||
log.warning("Spotify playlist list: %s", e)
|
log.warning("Spotify playlist list: %s", e)
|
||||||
snap.spotify_tracks_time = time.time()
|
snap.spotify_tracks_time = time.time()
|
||||||
|
if time.time() - snap.spotify_tracks_time > 3600:
|
||||||
|
refresh()
|
||||||
# Never "tell" Spotify unless it is running: that would open it.
|
# Never "tell" Spotify unless it is running: that would open it.
|
||||||
running = subprocess.run(["/usr/bin/pgrep", "-xq", "Spotify"], timeout=5).returncode == 0
|
running = subprocess.run(["/usr/bin/pgrep", "-xq", "Spotify"], timeout=5).returncode == 0
|
||||||
if not running:
|
if not running:
|
||||||
@@ -1630,6 +1640,8 @@ def poll_spotify(snap, cfg):
|
|||||||
parts = (r.stdout.strip().split("|") + ["", "", "", ""])[:4]
|
parts = (r.stdout.strip().split("|") + ["", "", "", ""])[:4]
|
||||||
snap.spotify = {"running": True, "state": parts[0], "track": parts[1], "name": parts[2], "artist": parts[3]}
|
snap.spotify = {"running": True, "state": parts[0], "track": parts[1], "name": parts[2], "artist": parts[3]}
|
||||||
if parts[1] and (not snap.spotify_recent or snap.spotify_recent[-1] != parts[1]):
|
if parts[1] and (not snap.spotify_recent or snap.spotify_recent[-1] != parts[1]):
|
||||||
|
if parts[1] not in snap.spotify_tracks and time.time() - snap.spotify_tracks_time > 60:
|
||||||
|
refresh() # a song just added to the playlist?
|
||||||
snap.spotify_recent.append(parts[1])
|
snap.spotify_recent.append(parts[1])
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user