commit 62bf5da7255fcfd73c23361290f68ff1b66b0f85
parent 13d19026950ae1694c7ac227eff88f25a14d88d8
Author: Hunter
Date: Tue, 13 Oct 2026 00:12:54 -0400
allow mix/icon.png to override default icon
Diffstat:
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: