From eaaded770db638de904a3c8437bc4ff59565f7ef Mon Sep 17 00:00:00 2001 From: jim kolpack Date: Mon, 12 Oct 2026 20:12:40 -0400 Subject: [PATCH] Show BPM (left) and track length (right) at 2x, above the progress bar New track_length ("M:SS" from duration) and bpm ("N bpm", rounded) formatted fields on the now-playing fetch. 2x only, tied to the progress bar's own presence (both come from data that's only meaningful on a long-lived server -- see _track_progress's own comment), since the row is meant to sit directly above it rather than float with nothing below it. Right-aligns track_length via a computed left Padding/spacer Box (same estimated-width approach already used for time_temp's centering), not render.Row(expanded=True, main_align="space_between") -- that pattern already caused a real bug once this session (the time_temp centering fix): `expanded` on a Row grabs extra *vertical* space from the enclosing Column (main_align="space_between"), pushing the progress bar out of view. Caught this before it shipped by pixel-sampling the bottom rows and confirming the progress bar's exact "bar_empty" color is still present where expected, not just eyeballing the render. Verified: renders between time_temp and the progress bar at 2x ("136 bpm" / "6:45"), 1x unaffected (no new row, unchanged layout). --- subwave_nowplaying.star | 44 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/subwave_nowplaying.star b/subwave_nowplaying.star index 5b6c64b..8efcac2 100644 --- a/subwave_nowplaying.star +++ b/subwave_nowplaying.star @@ -356,6 +356,34 @@ def main(config): ] + show_lines + [time_temp_line] children = [render.Column(children = top_lines)] + + # BPM (left) / track length (right), 2x only -- same reasoning as the + # other 2x-only additions above: no spare row for it at 1x's tighter + # layout. Tied to the progress bar's own presence (both come from + # _track_progress/duration data that's only meaningful on a + # long-lived server, not a one-shot CLI render -- see _track_progress's + # own comment), since it's meant to sit directly above it, not float + # on its own if there's nothing below it. + # + # Right-aligns track_length via a computed left Padding (same + # estimated-width approach as time_temp's centering), not + # render.Row(expanded=True, main_align="space_between") -- that + # pattern already caused a real bug once this session (the + # time_temp centering fix): `expanded` on a Row grabs extra + # *vertical* space from this same enclosing Column + # (main_align="space_between"), which pushed the progress bar below + # it out of view. A plain Row with a computed Box spacer between the + # two texts can't do that, since nothing in it is flexible/expanding. + if scale == 2 and progress != None: + bpm_width = int(len(list(now["bpm"].codepoints())) * 3.9) + length_width = int(len(list(now["track_length"].codepoints())) * 3.9) + gap = max(0, text_width - bpm_width - length_width) + children.append(render.Row(children = [ + render.Text(now["bpm"], font = "tom-thumb", color = colors["meta"]), + render.Box(width = gap, height = 1), + render.Text(now["track_length"], font = "tom-thumb", color = colors["meta"]), + ])) + if progress != None: children.append(_progress_bar(progress["fraction"], colors, width = text_width, height = 2 * scale)) @@ -1002,6 +1030,8 @@ def _fetch_now_playing(station_url): "host": dj.get("name", ""), "time_temp": _format_time_temp(context.get("clock", {}) or {}, context.get("weather", {}) or {}), "album_year": _format_album_year(np.get("album", ""), np.get("year", None)), + "track_length": _format_track_length(np.get("duration", 0)), + "bpm": _format_bpm(np.get("bpm", None)), "online": bool(data.get("streamOnline", False)), } @@ -1013,6 +1043,20 @@ def _format_album_year(album, year): parts.append(str(int(year))) return " ยท ".join(parts) +def _format_track_length(duration): + if not duration: + return "" + total = int(duration) + minutes = total // 60 + seconds = total % 60 + secs_str = "0" + str(seconds) if seconds < 10 else str(seconds) + return str(minutes) + ":" + secs_str + +def _format_bpm(bpm): + if not bpm: + return "" + return str(int(bpm + 0.5)) + " bpm" + def _format_time_temp(clock, weather): parts = [] display_time = clock.get("display", "")