Rotate the random zoom spot over time, not just per-track
Random zoom position was previously picked once per track (stable via subsonic_id) and held for that track's entire runtime. Changed it to rotate on a ~10s cadence based on wall-clock time, still offset by a per-track hash so different tracks don't all land on the same spot in the same window. This lets a single long track show off different sections of its own artwork over time, rather than being stuck on one spot until the next track starts.
This commit is contained in:
1 parent
d8add24c5e
commit
f6893bdcdf
2 files changed
+43
-21
No files matched your search
@@ -24,8 +24,12 @@ app's config when adding it to a device.
|
|||||||
- **Album art zoom** — how far the album art's breathing zoom effect goes
|
- **Album art zoom** — how far the album art's breathing zoom effect goes
|
||||||
in: Off, Subtle, Moderate (default), or Strong.
|
in: Off, Subtle, Moderate (default), or Strong.
|
||||||
- **Zoom position** — where the zoom centers on: Center (default), or
|
- **Zoom position** — where the zoom centers on: Center (default), or
|
||||||
Random (a handful of preset off-center spots, picked per-track so a
|
Random (a handful of preset off-center spots). Rotates on a ~10s cadence
|
||||||
still-playing track doesn't jump between spots on every refresh).
|
based on wall-clock time, offset per-track so different tracks don't all
|
||||||
|
land on the same spot at the same moment — meaning it can change on
|
||||||
|
*every* refresh, not just when the track changes, so a long track gets
|
||||||
|
to show off different sections of its art over time instead of being
|
||||||
|
stuck on one spot for its whole runtime.
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
@@ -55,13 +59,19 @@ app's config when adding it to a device.
|
|||||||
came from testing it set to its own default value and comparing the
|
came from testing it set to its own default value and comparing the
|
||||||
result against itself, which is of course identical either way). The
|
result against itself, which is of course identical either way). The
|
||||||
"Random" zoom position picks from a handful of preset off-center spots
|
"Random" zoom position picks from a handful of preset off-center spots
|
||||||
(`ZOOM_RANDOM_SPOTS`), chosen per-track from `subsonic_id` the same way
|
(`ZOOM_RANDOM_SPOTS`), kept modest (0.3-0.7 of the image's own bounds,
|
||||||
the old corner-selection logic worked, so a still-playing track doesn't
|
not the full 0.0-1.0 range) and verified safe (no black border, even at
|
||||||
jump between spots on every `NOWPLAYING_TTL` refresh. Those spots are
|
the strongest 1.7x zoom) via the calibration tool; more extreme corners
|
||||||
kept modest (0.3-0.7 of the image's own bounds, not the full 0.0-1.0
|
weren't tested and may not be safe. Which spot is picked rotates on a
|
||||||
range) and verified safe (no black border, even at the strongest 1.7x
|
`ZOOM_SPOT_ROTATE_SECONDS` (10s) cadence based on wall-clock time
|
||||||
zoom) via the calibration tool; more extreme corners weren't tested and
|
(`time.now().unix`), offset by a hash of `subsonic_id` so different
|
||||||
may not be safe.
|
tracks don't all land on the same spot in the same 10s window. This was
|
||||||
|
deliberately *not* made purely per-track-stable (i.e. the same spot for
|
||||||
|
a track's entire runtime, the way the old corner-selection logic for
|
||||||
|
the since-removed Translate approach worked): the ask was to see
|
||||||
|
different sections of a single piece of art over time, which requires
|
||||||
|
the spot to be able to change mid-playback, not just when the track
|
||||||
|
changes.
|
||||||
|
|
||||||
An earlier version tried to fix a *different* problem — `animation.Scale`
|
An earlier version tried to fix a *different* problem — `animation.Scale`
|
||||||
zooming in asymmetrically toward one corner — by pairing Scale with a
|
zooming in asymmetrically toward one corner — by pairing Scale with a
|
||||||
|
|||||||
+24
-12
@@ -104,13 +104,22 @@ DEFAULT_ZOOM_LEVEL = "moderate"
|
|||||||
# own 36x36 bounds. Kept modest (0.3-0.7, not full 0.0-1.0) and verified
|
# own 36x36 bounds. Kept modest (0.3-0.7, not full 0.0-1.0) and verified
|
||||||
# safe (no black border, even at the strongest 1.7x zoom) via the
|
# safe (no black border, even at the strongest 1.7x zoom) via the
|
||||||
# calibration tool -- more extreme corners weren't tested and may not be.
|
# calibration tool -- more extreme corners weren't tested and may not be.
|
||||||
# Picked per-track (stable via subsonic_id, like the old corner-selection
|
# Only used when the "Zoom position" schema field is set to "random" --
|
||||||
# logic) rather than per-render, so a still-playing track doesn't jump
|
# "center" always zooms on the image's own center, exposed as a config
|
||||||
# between spots on every NOWPLAYING_TTL refresh. Only used when the
|
# choice since some covers (e.g. ones with a centered logo or face) read
|
||||||
# "Zoom position" schema field is set to "random" -- "center" always zooms
|
# better with a predictable center zoom than a random one.
|
||||||
# on the image's own center, exposed as a config choice since some covers
|
#
|
||||||
# (e.g. ones with a centered logo or face) read better with a predictable
|
# Rotates on a ~10s cadence (matching ART_ZOOM_DURATION below) based on
|
||||||
# center zoom than a per-track random one.
|
# wall-clock time, not just per-track: a time bucket picks the spot, offset
|
||||||
|
# by a per-track hash so different tracks don't all land on the same spot
|
||||||
|
# at the same moment. This means the spot can change on *every* refresh,
|
||||||
|
# including mid-playback of a single still-running track -- not just when
|
||||||
|
# the track itself changes -- so a long track gets to show off different
|
||||||
|
# sections of its art over time, which is the whole point of "random"
|
||||||
|
# rather than "center". One real limitation: a one-shot `pixlet render`
|
||||||
|
# CLI call reads the system clock same as the live server would, so this
|
||||||
|
# is still deterministic for a given moment -- it just varies *which*
|
||||||
|
# moment you rendered at, same as the live progress bar in _track_progress.
|
||||||
ZOOM_CENTER_SPOT = (0.5, 0.5)
|
ZOOM_CENTER_SPOT = (0.5, 0.5)
|
||||||
ZOOM_RANDOM_SPOTS = [
|
ZOOM_RANDOM_SPOTS = [
|
||||||
(0.5, 0.5),
|
(0.5, 0.5),
|
||||||
@@ -119,16 +128,19 @@ ZOOM_RANDOM_SPOTS = [
|
|||||||
(0.3, 0.7),
|
(0.3, 0.7),
|
||||||
(0.7, 0.3),
|
(0.7, 0.3),
|
||||||
]
|
]
|
||||||
|
ZOOM_SPOT_ROTATE_SECONDS = 10
|
||||||
DEFAULT_ZOOM_POSITION = "center"
|
DEFAULT_ZOOM_POSITION = "center"
|
||||||
|
|
||||||
def _zoom_spot_for_track(zoom_position, subsonic_id):
|
def _zoom_spot(zoom_position, subsonic_id):
|
||||||
if zoom_position != "random":
|
if zoom_position != "random":
|
||||||
return ZOOM_CENTER_SPOT
|
return ZOOM_CENTER_SPOT
|
||||||
|
|
||||||
total = 0
|
track_offset = 0
|
||||||
for ch in subsonic_id.elems():
|
for ch in subsonic_id.elems():
|
||||||
total += ord(ch)
|
track_offset += ord(ch)
|
||||||
return ZOOM_RANDOM_SPOTS[total % len(ZOOM_RANDOM_SPOTS)]
|
|
||||||
|
time_bucket = int(time.now().unix / ZOOM_SPOT_ROTATE_SECONDS)
|
||||||
|
return ZOOM_RANDOM_SPOTS[(time_bucket + track_offset) % len(ZOOM_RANDOM_SPOTS)]
|
||||||
|
|
||||||
def main(config):
|
def main(config):
|
||||||
station_url = config.get("station_url", DEFAULT_STATION_URL)
|
station_url = config.get("station_url", DEFAULT_STATION_URL)
|
||||||
@@ -266,7 +278,7 @@ def _art_widget(art_bytes, zoom_scale, zoom_position, subsonic_id):
|
|||||||
# toward the bottom-right -- the opposite of the bug it was meant to
|
# toward the bottom-right -- the opposite of the bug it was meant to
|
||||||
# fix. Since `origin` already does directly and correctly what that
|
# fix. Since `origin` already does directly and correctly what that
|
||||||
# Translate was clumsily approximating, this uses `origin` alone.
|
# Translate was clumsily approximating, this uses `origin` alone.
|
||||||
origin_x, origin_y = _zoom_spot_for_track(zoom_position, subsonic_id)
|
origin_x, origin_y = _zoom_spot(zoom_position, subsonic_id)
|
||||||
top_scale = max(zoom_scale, 1.0)
|
top_scale = max(zoom_scale, 1.0)
|
||||||
zoom = animation.Transformation(
|
zoom = animation.Transformation(
|
||||||
child = art,
|
child = art,
|
||||||
|
|||||||