commit b0e84160096f6e89a6fed3c3beb43feab28d72d0
parent e3af3d0b2ff9175ffbb383f8b731db4bfe91382a
Author: Hunter
Date:   Mon,  6 Apr 2026 16:06:47 -0400

build.py -> catalog.py; generate_manifests.py -> build.py

Diffstat:
Mbuild.py | 462++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Cbuild.py -> catalog.py | 0
Dgenerate_manifests.py | 319-------------------------------------------------------------------------------
Mmix/readme.md | 2+-
Mreadme.md | 6+++---
Mrip.py | 2+-
6 files changed, 300 insertions(+), 491 deletions(-)

diff --git a/build.py b/build.py @@ -1,191 +1,319 @@ #!/usr/bin/env python3 """ -Scans /mix directory and populates tracks.json with metadata -Supports MP3, M4A, OGG, FLAC, and WAV formats -Automatically manages a virtual environment for dependencies +Creates manifest.json and service-worker.js +(PWA requirements) based on the contents of tracks.json """ -import os -import sys -import subprocess import json +import re from pathlib import Path -SCRIPT_DIR = Path(__file__).parent.absolute() -VENV_DIR = SCRIPT_DIR / "venv" -MIX_DIR = SCRIPT_DIR / "mix" -OUTPUT_FILE = MIX_DIR / "tracks.json" - - -def setup_venv(): - """Create and set up virtual environment if it doesn't exist""" - # Determine the path to pip and python in the venv - if sys.platform == "win32": - pip_path = VENV_DIR / "Scripts" / "pip" - python_path = VENV_DIR / "Scripts" / "python" +def get_configuration(): + """Prompt user for configuration values""" + print("=" * 60) + print("PWA Configuration") + print("=" * 60) + print() + + # Get app name + app_name = input("Enter a name for your mixapp: ").strip() + if not app_name: + print("Error: App name is required") + exit(1) + + # Get base path with smart default + default_path = app_name.lower().replace(" ", "_") + print() + print(f"Enter the deployment path (or press Return/Enter for default)") + print(f"Default: /{default_path}/") + base_path_input = input("Path: ").strip() + + if base_path_input: + # User provided a path - ensure it has leading/trailing slashes + base_path = base_path_input + if not base_path.startswith("/"): + base_path = "/" + base_path + if not base_path.endswith("/"): + base_path = base_path + "/" else: - pip_path = VENV_DIR / "bin" / "pip" - python_path = VENV_DIR / "bin" / "python3" - - # Check if venv needs to be created or recreated - if not VENV_DIR.exists() or not python_path.exists(): - if VENV_DIR.exists(): - print("Virtual environment incomplete, recreating...") - import shutil - shutil.rmtree(VENV_DIR) - else: - print("Creating virtual environment...") - - try: - subprocess.check_call([sys.executable, "-m", "venv", str(VENV_DIR)]) - print("Virtual environment created successfully.") - except subprocess.CalledProcessError as e: - print(f"Error creating virtual environment: {e}") - sys.exit(1) - - # Ensure pip is available (sometimes venv doesn't include it) - if not pip_path.exists(): - print("Installing pip in virtual environment...") - try: - subprocess.check_call([str(python_path), "-m", "ensurepip", "--upgrade"]) - except subprocess.CalledProcessError as e: - print(f"Error ensuring pip: {e}") - sys.exit(1) - - check = subprocess.run( - [str(python_path), "-c", "import mutagen"], - capture_output=True - ) - if check.returncode != 0: - try: - subprocess.check_call([str(python_path), "-m", "pip", "install", "-q", "mutagen"]) - except subprocess.CalledProcessError: - print("Note: Could not install mutagen (offline?). Metadata will be derived from filenames.\n") - - return python_path + # Use default + base_path = f"/{default_path}/" + print(f"Using default path: {base_path}") + print() + print(f"Configuration:") + print(f" App Name: {app_name}") + print(f" Base Path: {base_path}") + print() -def run_in_venv(): - """Re-run this script in the virtual environment""" - python_path = setup_venv() - - # Re-run this script with the venv Python - print("Running scanner in virtual environment...\n") - subprocess.check_call([str(python_path), __file__, "--in-venv"]) - sys.exit(0) + return app_name, base_path +# File paths (no need to edit these) +SCRIPT_DIR = Path(__file__).parent.absolute() +TRACKS_JSON = SCRIPT_DIR / "mix" / "tracks.json" +STYLES_CSS = SCRIPT_DIR / "resources" / "styles.css" -def scan_tracks(): - """Main function to scan audio files and generate tracks.json""" - # Import mutagen here (only after venv is active) - try: - from mutagen import File as MutagenFile - has_mutagen = True - except ImportError: - MutagenFile = None - has_mutagen = False - print("Mutagen not available. Metadata will be derived from filenames.\n") - # Supported audio formats - SUPPORTED_EXTENSIONS = ('.mp3', '.m4a', '.ogg', '.flac', '.wav') +def get_background_color(): + """Extract the --background CSS variable from styles.css""" + if not STYLES_CSS.exists(): + print("Warning: styles.css not found. Using default color.") + return "#080a0c" - # Check if tracks directory exists, create if it doesn't - if not MIX_DIR.exists(): - print(f"Creating {MIX_DIR.name} directory...") - MIX_DIR.mkdir(parents=True, exist_ok=True) - print(f"✓ {MIX_DIR.name} directory created.") - print(f"\nAdd audio files to the {MIX_DIR.name} directory and run this script again.") - print(f"Supported formats: {', '.join(SUPPORTED_EXTENSIONS)}") - sys.exit(0) + with open(STYLES_CSS, 'r', encoding='utf-8') as f: + content = f.read() - # Find all supported audio files - audio_files = [f for f in MIX_DIR.iterdir() if f.suffix.lower() in SUPPORTED_EXTENSIONS] + # Look for --background: <color>; pattern + match = re.search( + r'--background:\s*([#a-zA-Z0-9(),.\s]+?)\s*;', + content + ) + if match: + color = match.group(1).strip() + print(f"Found background color in styles.css: {color}") + return color - if not audio_files: - print(f"No audio files found in {MIX_DIR}") - print(f"\nPlease add audio files to the {MIX_DIR.name} directory and run this script again.") - print(f"Supported formats: {', '.join(SUPPORTED_EXTENSIONS)}") - sys.exit(0) + print("Warning: --background not found in styles.css. Using default color.") + return "#080a0c" - print(f"Found {len(audio_files)} audio file(s). Extracting metadata...\n") - tracks = [] +def _next_cache_name(app_name, manifest_path): + """Determine the next cache name by incrementing the version in an existing manifest. - for audio_file in sorted(audio_files): + If manifest.json doesn't exist or has no versioned cache_name, returns "{app_name}-v1". + If it already has e.g. "heaven-v1", returns "heaven-v1.1". + If it already has e.g. "heaven-v1.3", returns "heaven-v1.4". + """ + if manifest_path.exists(): try: - title = None - artist = None - - if has_mutagen: - audio = MutagenFile(audio_file, easy=True) - if audio and audio.tags: - title = audio.tags.get('title', [None])[0] - artist = audio.tags.get('artist', [None])[0] - - # Fallback to filename for title if not found - if not title: - title = audio_file.stem # filename without extension - - # Fallback to "Unknown Artist" if not found - if not artist: - artist = "Unknown Artist" - - track_info = { - "title": title, - "artist": artist, - "filename": audio_file.name + with open(manifest_path, 'r', encoding='utf-8') as f: + existing = json.load(f) + old_cache = existing.get("cache_name", "") + # Match pattern like "appname-v1" or "appname-v1.3" + match = re.match(r'^(.+)-v(\d+)(?:\.(\d+))?$', old_cache) + if match: + prefix = match.group(1) + major = int(match.group(2)) + minor = int(match.group(3)) if match.group(3) is not None else 0 + # If app was renamed, start fresh with new name + if prefix != app_name: + return f"{app_name}-v1" + return f"{app_name}-v{major}.{minor + 1}" + except (json.JSONDecodeError, KeyError): + pass + return f"{app_name}-v1" + + +def generate_pwa_manifests(app_name=None, base_path=None): + """Generate PWA manifest files based on tracks.json + + Args: + app_name: Name of the app. If None, will be prompted via get_configuration() + base_path: Base path for the app. If None, will be prompted via get_configuration() + """ + # Get configuration if not provided + if app_name is None or base_path is None: + app_name, base_path = get_configuration() + + # Derived values + short_name = app_name + cache_name = _next_cache_name(app_name, SCRIPT_DIR / "manifest.json") + app_description = f"{app_name}" + + print("Generating PWA manifests...") + print(f" Cache name: {cache_name}") + + # Load tracks.json + if not TRACKS_JSON.exists(): + print("Error: tracks.json not found. Run catalog.py first.") + return + + with open(TRACKS_JSON, 'r', encoding='utf-8') as f: + tracks = json.load(f) + + # Get background color from styles.css + background_color = get_background_color() + + # Generate manifest.json + manifest = { + "id": base_path, + "name": app_name, + "short_name": short_name, + "description": app_description, + "start_url": base_path, + "scope": base_path, + "display": "standalone", + "background_color": background_color, + "theme_color": background_color, + "cache_name": cache_name, # Custom field for script.js to use + "icons": [ + { + "src": f"{base_path}resources/icon.png", + "sizes": "640x640", + "type": "image/png", + "purpose": "any maskable" } - - tracks.append(track_info) - print(f"✓ {track_info['artist']} - {track_info['title']}") - - except Exception as e: - print(f"✗ Error reading {audio_file.name}: {e}") - continue - - # Check if ALL titles start with numbers - # If so, strip the leading numbers from all titles - import re - all_have_leading_numbers = all( - re.match(r'^\d+\s*[-.]?\s*', track['title']) - for track in tracks - ) - - if all_have_leading_numbers and tracks: - print("\nDetected track numbers in all titles. Stripping them...") - for track in tracks: - original_title = track['title'] - # Remove leading number pattern - cleaned_title = re.sub(r'^\d+\s*[-.]?\s*', '', original_title) - if cleaned_title: # Only update if something remains - track['title'] = cleaned_title - if cleaned_title != original_title: - print(f" {original_title} → {cleaned_title}") - - if not tracks: - print("\nNo valid audio files could be processed.") - sys.exit(1) - - # Write to tracks.json - try: - with open(OUTPUT_FILE, 'w', encoding='utf-8') as f: - json.dump(tracks, f, indent="\t", ensure_ascii=False) - - print(f"\n✓ Successfully generated {OUTPUT_FILE.name} with {len(tracks)} track(s).") - - except Exception as e: - print(f"\nError writing {OUTPUT_FILE.name}: {e}") - sys.exit(1) - - -def main(): - """Main entry point""" - # Check if we're already running in venv - if "--in-venv" not in sys.argv: - run_in_venv() - else: - scan_tracks() + ] + } + + with open(SCRIPT_DIR / "manifest.json", 'w', encoding='utf-8') as f: + json.dump(manifest, f, indent=2) + print("✓ Generated manifest.json") + + # Build static files list for service worker + static_files = [ + "./", + "index.html", + "manifest.json", + "resources/styles.css", + "resources/script.js", + "mix/tracks.json", + "resources/icon.png", + "resources/play.svg", + "resources/pause.svg", + "resources/prev.svg", + "resources/next.svg", + "resources/repeat.svg", + ] + + # Conditionally include optional files if they exist + for optional_file in ["album_art.jpg", "custom.css", "custom.js"]: + if (SCRIPT_DIR / "mix" / optional_file).exists(): + static_files.append(f"mix/{optional_file}") + + static_files_js = json.dumps(static_files) + + # Generate service-worker.js + service_worker_content = f'''// Auto-generated service worker for {app_name} PWA +const CACHE_NAME = '{cache_name}'; + +// Get the base path from the service worker location +const getBasePath = () => {{ + const swPath = self.location.pathname; + return swPath.substring(0, swPath.lastIndexOf('/') + 1); +}}; + +const basePath = getBasePath(); + +// Static files to cache on install +const STATIC_FILES = {static_files_js}; + +// Install event - cache static resources +// Audio files will be cached by the main app's blob preloading system +self.addEventListener('install', (event) => {{ + console.log('Service Worker installing...', 'Base path:', basePath); + event.waitUntil( + caches.open(CACHE_NAME).then(cache => {{ + // Make URLs absolute relative to service worker location + const absoluteUrls = STATIC_FILES.map(url => {{ + if (url === './') return basePath; + return new URL(url, basePath + 'index.html').href; + }}); + console.log('Caching', absoluteUrls.length, 'static resources'); + + // Cache files individually with better error handling + // Using Promise.allSettled to continue even if some fail + return Promise.allSettled( + absoluteUrls.map(url => + fetch(url, {{ cache: 'no-cache' }}) + .then(response => {{ + if (!response.ok) {{ + throw new Error(`HTTP error! status: ${{response.status}}`); + }} + return cache.put(url, response); + }}) + .then(() => console.log('✓ Cached:', url)) + .catch(err => {{ + console.error('✗ Failed to cache:', url, err); + throw err; + }}) + ) + ).then(results => {{ + const failed = results.filter(r => r.status === 'rejected'); + const succeeded = results.filter(r => r.status === 'fulfilled'); + console.log(`Cached ${{succeeded.length}}/${{results.length}} static resources`); + if (failed.length > 0) {{ + console.warn(`Failed to cache ${{failed.length}} resources`); + }} + }}); + }}).then(() => {{ + console.log('Service Worker installation complete'); + return self.skipWaiting(); + }}).catch(error => {{ + console.error('Service Worker installation failed:', error); + }}) + ); +}}); + +// Activate event - clean up old caches +self.addEventListener('activate', (event) => {{ + console.log('Service Worker activating...'); + event.waitUntil( + caches.keys().then((cacheNames) => {{ + return Promise.all( + cacheNames.map((cacheName) => {{ + if (cacheName !== CACHE_NAME) {{ + console.log('Deleting old cache:', cacheName); + return caches.delete(cacheName); + }} + }}) + ); + }}).then(() => self.clients.claim()) + ); +}}); + +// Fetch event - cache first, network fallback +self.addEventListener('fetch', (event) => {{ + // Ignore non-http(s) requests like blob: URLs, data: URLs, chrome-extension:, etc. + if (!event.request.url.startsWith('http')) {{ + return; + }} + + event.respondWith( + caches.match(event.request) + .then((cachedResponse) => {{ + if (cachedResponse) {{ + console.log('✓ Serving from cache:', event.request.url); + return cachedResponse; + }} + + // Not in cache - try network + console.log('⟳ Fetching from network:', event.request.url); + return fetch(event.request) + .then((networkResponse) => {{ + // Check if valid response + if (!networkResponse || networkResponse.status !== 200 || networkResponse.type === 'error') {{ + return networkResponse; + }} + + // Clone and cache for future offline use + const responseToCache = networkResponse.clone(); + caches.open(CACHE_NAME) + .then((cache) => {{ + cache.put(event.request, responseToCache); + console.log('✓ Cached from network:', event.request.url); + }}) + .catch(err => console.error('Failed to cache:', err)); + + return networkResponse; + }}) + .catch((error) => {{ + console.error('✗ Network fetch failed for:', event.request.url, error); + throw error; + }}); + }}) + ); +}}); +''' + + with open(SCRIPT_DIR / "service-worker.js", 'w', encoding='utf-8') as f: + f.write(service_worker_content) + print("✓ Generated service-worker.js") + print() + print("PWA manifests generated successfully!") if __name__ == "__main__": - main() + # When run directly, get configuration and generate manifests + app_name, base_path = get_configuration() + generate_pwa_manifests(app_name, base_path) diff --git a/build.py b/catalog.py diff --git a/generate_manifests.py b/generate_manifests.py @@ -1,319 +0,0 @@ -#!/usr/bin/env python3 -""" -Creates manifest.json and service-worker.js -based on the contents of tracks.json -""" - -import json -import re -from pathlib import Path - -def get_configuration(): - """Prompt user for configuration values""" - print("=" * 60) - print("PWA Configuration") - print("=" * 60) - print() - - # Get app name - app_name = input("Enter a name for your mixapp: ").strip() - if not app_name: - print("Error: App name is required") - exit(1) - - # Get base path with smart default - default_path = app_name.lower().replace(" ", "_") - print() - print(f"Enter the deployment path (or press Return/Enter for default)") - print(f"Default: /{default_path}/") - base_path_input = input("Path: ").strip() - - if base_path_input: - # User provided a path - ensure it has leading/trailing slashes - base_path = base_path_input - if not base_path.startswith("/"): - base_path = "/" + base_path - if not base_path.endswith("/"): - base_path = base_path + "/" - else: - # Use default - base_path = f"/{default_path}/" - print(f"Using default path: {base_path}") - - print() - print(f"Configuration:") - print(f" App Name: {app_name}") - print(f" Base Path: {base_path}") - print() - - return app_name, base_path - -# File paths (no need to edit these) -SCRIPT_DIR = Path(__file__).parent.absolute() -TRACKS_JSON = SCRIPT_DIR / "mix" / "tracks.json" -STYLES_CSS = SCRIPT_DIR / "resources" / "styles.css" - - -def get_background_color(): - """Extract the --background CSS variable from styles.css""" - if not STYLES_CSS.exists(): - print("Warning: styles.css not found. Using default color.") - return "#080a0c" - - with open(STYLES_CSS, 'r', encoding='utf-8') as f: - content = f.read() - - # Look for --background: <color>; pattern - match = re.search( - r'--background:\s*([#a-zA-Z0-9(),.\s]+?)\s*;', - content - ) - if match: - color = match.group(1).strip() - print(f"Found background color in styles.css: {color}") - return color - - print("Warning: --background not found in styles.css. Using default color.") - return "#080a0c" - - -def _next_cache_name(app_name, manifest_path): - """Determine the next cache name by incrementing the version in an existing manifest. - - If manifest.json doesn't exist or has no versioned cache_name, returns "{app_name}-v1". - If it already has e.g. "heaven-v1", returns "heaven-v1.1". - If it already has e.g. "heaven-v1.3", returns "heaven-v1.4". - """ - if manifest_path.exists(): - try: - with open(manifest_path, 'r', encoding='utf-8') as f: - existing = json.load(f) - old_cache = existing.get("cache_name", "") - # Match pattern like "appname-v1" or "appname-v1.3" - match = re.match(r'^(.+)-v(\d+)(?:\.(\d+))?$', old_cache) - if match: - prefix = match.group(1) - major = int(match.group(2)) - minor = int(match.group(3)) if match.group(3) is not None else 0 - # If app was renamed, start fresh with new name - if prefix != app_name: - return f"{app_name}-v1" - return f"{app_name}-v{major}.{minor + 1}" - except (json.JSONDecodeError, KeyError): - pass - return f"{app_name}-v1" - - -def generate_pwa_manifests(app_name=None, base_path=None): - """Generate PWA manifest files based on tracks.json - - Args: - app_name: Name of the app. If None, will be prompted via get_configuration() - base_path: Base path for the app. If None, will be prompted via get_configuration() - """ - # Get configuration if not provided - if app_name is None or base_path is None: - app_name, base_path = get_configuration() - - # Derived values - short_name = app_name - cache_name = _next_cache_name(app_name, SCRIPT_DIR / "manifest.json") - app_description = f"{app_name}" - - print("Generating PWA manifests...") - print(f" Cache name: {cache_name}") - - # Load tracks.json - if not TRACKS_JSON.exists(): - print("Error: tracks.json not found. Run build.py first.") - return - - with open(TRACKS_JSON, 'r', encoding='utf-8') as f: - tracks = json.load(f) - - # Get background color from styles.css - background_color = get_background_color() - - # Generate manifest.json - manifest = { - "id": base_path, - "name": app_name, - "short_name": short_name, - "description": app_description, - "start_url": base_path, - "scope": base_path, - "display": "standalone", - "background_color": background_color, - "theme_color": background_color, - "cache_name": cache_name, # Custom field for script.js to use - "icons": [ - { - "src": f"{base_path}resources/icon.png", - "sizes": "640x640", - "type": "image/png", - "purpose": "any maskable" - } - ] - } - - with open(SCRIPT_DIR / "manifest.json", 'w', encoding='utf-8') as f: - json.dump(manifest, f, indent=2) - print("✓ Generated manifest.json") - - # Build static files list for service worker - static_files = [ - "./", - "index.html", - "manifest.json", - "resources/styles.css", - "resources/script.js", - "mix/tracks.json", - "resources/icon.png", - "resources/play.svg", - "resources/pause.svg", - "resources/prev.svg", - "resources/next.svg", - "resources/repeat.svg", - ] - - # Conditionally include optional files if they exist - for optional_file in ["album_art.jpg", "custom.css", "custom.js"]: - if (SCRIPT_DIR / "mix" / optional_file).exists(): - static_files.append(f"mix/{optional_file}") - - static_files_js = json.dumps(static_files) - - # Generate service-worker.js - service_worker_content = f'''// Auto-generated service worker for {app_name} PWA -const CACHE_NAME = '{cache_name}'; - -// Get the base path from the service worker location -const getBasePath = () => {{ - const swPath = self.location.pathname; - return swPath.substring(0, swPath.lastIndexOf('/') + 1); -}}; - -const basePath = getBasePath(); - -// Static files to cache on install -const STATIC_FILES = {static_files_js}; - -// Install event - cache static resources -// Audio files will be cached by the main app's blob preloading system -self.addEventListener('install', (event) => {{ - console.log('Service Worker installing...', 'Base path:', basePath); - event.waitUntil( - caches.open(CACHE_NAME).then(cache => {{ - // Make URLs absolute relative to service worker location - const absoluteUrls = STATIC_FILES.map(url => {{ - if (url === './') return basePath; - return new URL(url, basePath + 'index.html').href; - }}); - console.log('Caching', absoluteUrls.length, 'static resources'); - - // Cache files individually with better error handling - // Using Promise.allSettled to continue even if some fail - return Promise.allSettled( - absoluteUrls.map(url => - fetch(url, {{ cache: 'no-cache' }}) - .then(response => {{ - if (!response.ok) {{ - throw new Error(`HTTP error! status: ${{response.status}}`); - }} - return cache.put(url, response); - }}) - .then(() => console.log('✓ Cached:', url)) - .catch(err => {{ - console.error('✗ Failed to cache:', url, err); - throw err; - }}) - ) - ).then(results => {{ - const failed = results.filter(r => r.status === 'rejected'); - const succeeded = results.filter(r => r.status === 'fulfilled'); - console.log(`Cached ${{succeeded.length}}/${{results.length}} static resources`); - if (failed.length > 0) {{ - console.warn(`Failed to cache ${{failed.length}} resources`); - }} - }}); - }}).then(() => {{ - console.log('Service Worker installation complete'); - return self.skipWaiting(); - }}).catch(error => {{ - console.error('Service Worker installation failed:', error); - }}) - ); -}}); - -// Activate event - clean up old caches -self.addEventListener('activate', (event) => {{ - console.log('Service Worker activating...'); - event.waitUntil( - caches.keys().then((cacheNames) => {{ - return Promise.all( - cacheNames.map((cacheName) => {{ - if (cacheName !== CACHE_NAME) {{ - console.log('Deleting old cache:', cacheName); - return caches.delete(cacheName); - }} - }}) - ); - }}).then(() => self.clients.claim()) - ); -}}); - -// Fetch event - cache first, network fallback -self.addEventListener('fetch', (event) => {{ - // Ignore non-http(s) requests like blob: URLs, data: URLs, chrome-extension:, etc. - if (!event.request.url.startsWith('http')) {{ - return; - }} - - event.respondWith( - caches.match(event.request) - .then((cachedResponse) => {{ - if (cachedResponse) {{ - console.log('✓ Serving from cache:', event.request.url); - return cachedResponse; - }} - - // Not in cache - try network - console.log('⟳ Fetching from network:', event.request.url); - return fetch(event.request) - .then((networkResponse) => {{ - // Check if valid response - if (!networkResponse || networkResponse.status !== 200 || networkResponse.type === 'error') {{ - return networkResponse; - }} - - // Clone and cache for future offline use - const responseToCache = networkResponse.clone(); - caches.open(CACHE_NAME) - .then((cache) => {{ - cache.put(event.request, responseToCache); - console.log('✓ Cached from network:', event.request.url); - }}) - .catch(err => console.error('Failed to cache:', err)); - - return networkResponse; - }}) - .catch((error) => {{ - console.error('✗ Network fetch failed for:', event.request.url, error); - throw error; - }}); - }}) - ); -}}); -''' - - with open(SCRIPT_DIR / "service-worker.js", 'w', encoding='utf-8') as f: - f.write(service_worker_content) - print("✓ Generated service-worker.js") - print() - print("PWA manifests generated successfully!") - - -if __name__ == "__main__": - # When run directly, get configuration and generate manifests - app_name, base_path = get_configuration() - generate_pwa_manifests(app_name, base_path) diff --git a/mix/readme.md b/mix/readme.md @@ -1,6 +1,6 @@ # /mix -add your audio files here, then run `build.py` to create `tracks.json`. +add your audio files here, then run `catalog.py` to create `tracks.json`. supported formats: `.mp3`, `.m4a`, `.ogg`, `.flac`, `.wav` diff --git a/readme.md b/readme.md @@ -40,7 +40,7 @@ hits different, right?<br><br> - add your audio files to the `/mix` directory, or use: - `./rip.py` to rip tracks from a physical CD - `./buy.py` to search for tracks to purchase (opens in iTunes on MacOS, <a href="https://song.link/i/1651294855">song.link</a> otherwise) - - run `./build.py` to parse `/mix` and populate `tracks.json`, which defines the tracks available to the player. after running `./build.py` once, you can manually edit `tracks.json` to refine your mix. + - run `./catalog.py` to parse `/mix` and populate `tracks.json`, which defines the tracks available to the player. after running `./catalog.py` once, you can manually edit `tracks.json` to refine your mix. - optionally, add an `album_art.jpg` to `/mix` to set the cover art for your mix. - supported audio formats: `.mp3`, `.m4a`, `.ogg`, `.flac`, `.wav` @@ -48,8 +48,8 @@ hits different, right?<br><br> - run `./host.py` to start a local HTTP server for testing. you can scan the QR code printed to the terminal to test the app from any device on your local network. 3. **manifesting** - - run `./generate_manifests.py` and follow the interactive prompts to specify an app name and the remote server path where your app will be hosted. - - this creates the config files that enable offline functionality: `manifest.json` and `service-worker.js`. + - run `./build.py` and follow the interactive prompts to specify an app name and the remote server path where your mixapp will be hosted. + - this creates the config files that enable offline functionality: `manifest.json` and `service-worker.js` 4. **ship it** - upload the entire project directory to any web host with HTTPS support (GitHub Pages, AWS S3, etc.) diff --git a/rip.py b/rip.py @@ -363,7 +363,7 @@ def rip_cd(): if success_count > 0: print(f"\nTracks saved to: {MIX_DIR}") print("\nNext steps:") - print(" 1. Run build.py to generate tracks.json with metadata") + print(" 1. Run catalog.py to generate tracks.json with metadata") print(" 2. Run host.py to test your mixtape locally") # Eject the CD