commit 62bf5da7255fcfd73c23361290f68ff1b66b0f85
parent 13d19026950ae1694c7ac227eff88f25a14d88d8
Author: Hunter
Date:   Tue, 13 Oct 2026 00:12:54 -0400

allow mix/icon.png to override default icon

Diffstat:
Mbuild.py | 20+++++++++++++++-----
Mindex.html | 1-
Mmix/readme.md | 5+++--
Mreadme.md | 2+-
Mrip.py | 19++++++++++++++++++-
5 files changed, 37 insertions(+), 10 deletions(-)

diff --git a/build.py b/build.py @@ -4,6 +4,7 @@ Creates manifest.json and service-worker.js (PWA requirements) based on the contents of tracks.json """ +import hashlib import json import re from pathlib import Path @@ -121,11 +122,20 @@ def build_pwa(app_name=None, base_path=None): album_art_path = "mix/album_art.jpg" if custom_album_art.exists() else "resources/album_art.jpg" # append a content hash so the URL changes when the image changes if album_art_file.exists(): - import hashlib art_hash = hashlib.md5(album_art_file.read_bytes()).hexdigest()[:8] album_art_path = f"{album_art_path}?v={art_hash}" print(f" Album art: {album_art_path}") + # mix/icon.png overrides the default + custom_icon = SCRIPT_DIR / "mix" / "icon.png" + icon_file = custom_icon if custom_icon.exists() else SCRIPT_DIR / "resources" / "icon.png" + icon_path = "mix/icon.png" if custom_icon.exists() else "resources/icon.png" + # append a content hash so the URL changes when the image changes + if icon_file.exists(): + icon_hash = hashlib.md5(icon_file.read_bytes()).hexdigest()[:8] + icon_path = f"{icon_path}?v={icon_hash}" + print(f" Icon: {icon_path}") + # Generate manifest.json manifest = { "id": base_path, @@ -141,7 +151,7 @@ def build_pwa(app_name=None, base_path=None): "album_art": album_art_path, # Custom field for script.js to use "icons": [ { - "src": f"{base_path}resources/icon.png", + "src": f"{base_path}{icon_path}", "sizes": "640x640", "type": "image/png", "purpose": "any maskable" @@ -161,7 +171,7 @@ def build_pwa(app_name=None, base_path=None): "resources/styles.css", "resources/script.js", "mix/tracks.json", - "resources/icon.png", + icon_path, album_art_path, "resources/play.svg", "resources/pause.svg", @@ -172,8 +182,8 @@ def build_pwa(app_name=None, base_path=None): ] AUDIO_EXTS = {".mp3", ".m4a", ".ogg", ".flac", ".wav"} - # album_art.jpg added explicitly above with a hash; skip to avoid a bare dupe - SKIP_NAMES = {"tracks.json", "readme.md", "album_art.jpg"} + # album_art.jpg and icon.png added explicitly above with a hash; skip to avoid a bare dupe + SKIP_NAMES = {"tracks.json", "readme.md", "album_art.jpg", "icon.png"} for path in sorted((SCRIPT_DIR / "mix").iterdir()): if not path.is_file(): continue diff --git a/index.html b/index.html @@ -7,7 +7,6 @@ <meta name="theme-color" content="#080a0c"> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> - <link rel="apple-touch-icon" href="resources/icon.png"> <link rel="manifest" href="manifest.json"> <title>my mixapp</title> <link rel="stylesheet" href="resources/styles.css"> diff --git a/mix/readme.md b/mix/readme.md @@ -7,4 +7,5 @@ supported formats: `.mp3`, `.m4a`, `.ogg`, `.flac`, `.wav` ## optional files - `custom.css` - custom styles to override the default theme - `custom.js` - custom scripts for additional functionality -- `album_art.jpg` - cover art for your mix (overrides the default in `/resources`) -\ No newline at end of file +- `album_art.jpg` - cover art for your mix (overrides the default in `/resources`) +- `icon.png` - home screen icon for your mix (overrides the default in `/resources`) +\ No newline at end of file diff --git a/readme.md b/readme.md @@ -72,7 +72,7 @@ once you install one, it's yours.<br><br> (pictured: integration with iOS lockscreen controls) ## customization -add your own `custom.css`, `custom.js`, and/or `album_art.jpg` to `mix/` to customize your mixapp's appearance and behavior. +add `custom.css`, `custom.js`, `album_art.jpg`, and/or `icon.png` to `mix/` to customize your mixapp's appearance and behavior. ## local network installation diff --git a/rip.py b/rip.py @@ -10,7 +10,8 @@ Usage: ./rip.py [track_number] ./rip.py 3 rips only track 3 If /mix holds only tracks from this CD, ripping the whole disc also -downloads its cover art to mix/album_art.jpg and runs build.py +downloads its cover art to mix/album_art.jpg, scales it to mix/icon.png +and runs build.py """ import os @@ -279,6 +280,20 @@ def download_album_art(release_id, release_group_id): print("✓ Saved album art to mix/album_art.jpg") +def make_icon(): + """Scale mix/album_art.jpg to a 1000x1000 mix/icon.png using ffmpeg""" + icon = MIX_DIR / "icon.png" + result = subprocess.run(['ffmpeg', '-loglevel', 'error', '-y', '-i', str(MIX_DIR / "album_art.jpg"), + '-vf', 'scale=1000:1000', '-frames:v', '1', str(icon)], + capture_output=True, text=True) + if result.returncode != 0: + icon.unlink(missing_ok=True) + err_tail = "\n".join(result.stderr.strip().splitlines()[-5:]) + print(f" Could not create icon:\n{err_tail}") + return + print("✓ Saved icon to mix/icon.png") + + def mix_tracks(): """Names of the audio files currently in /mix""" return {f.name for f in MIX_DIR.iterdir() if f.suffix.lower() in scan.SUPPORTED_EXTENSIONS} @@ -683,6 +698,8 @@ def rip_cd(only_track=None): if only_this_cd and whole_cd_in_mix and album: if release_id and not (MIX_DIR / "album_art.jpg").exists(): download_album_art(release_id, release_group_id) + if (MIX_DIR / "album_art.jpg").exists() and not (MIX_DIR / "icon.png").exists(): + make_icon() build_mixapp(album) print("\nRun https_serve.py to install your mixapp.") elif success_count > 0: