diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..27f5a90 --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +# Copy to `.env` and fill in, then `source .env` before running. +# Get these from https://developer.spotify.com/dashboard (your app's settings). +# Register `http://127.0.0.1:8888` as a Redirect URI in that app. +export SPOTIFY_CLIENT_ID=your_client_id_here +export SPOTIFY_CLIENT_SECRET=your_client_secret_here + +# Optional overrides: +# export MUSIC_LIBRARY_PATH=~/Music +# export WORK_DIR=./work_dir/new diff --git a/.gitignore b/.gitignore index 880f9c3..7b75b45 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ prefetched* __pycache__ .idea secret +.env +.venv work_dir old tmp \ No newline at end of file diff --git a/Env.py b/Env.py index 6549c6b..c609347 100644 --- a/Env.py +++ b/Env.py @@ -1,7 +1,13 @@ +import os from pathlib import Path -local_library_path = Path("/home/auser/Music/") -work_dir = Path("./work_dir/new") +# Local config. Paths can be overridden via environment variables. +local_library_path = Path(os.environ.get("MUSIC_LIBRARY_PATH", "~/Music")).expanduser() +work_dir = Path(os.environ.get("WORK_DIR", "./work_dir/new")) -client_id = '***REDACTED***' -client_secret = "***REDACTED***" +# Spotify app credentials. NEVER hardcode these here — set them in the +# environment (see .env.example) so they don't end up in version control. +# export SPOTIFY_CLIENT_ID=... +# export SPOTIFY_CLIENT_SECRET=... +client_id = os.environ.get("SPOTIFY_CLIENT_ID") +client_secret = os.environ.get("SPOTIFY_CLIENT_SECRET") diff --git a/README.md b/README.md index 68b9ba5..62d8bd7 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,100 @@ -# spotifyFetcher -loads all data through web api
-client secret - ***REDACTED*** +# MusicIndexer + +Pull your Spotify library and listening stats via the Spotify Web API, index a +local music library (audio tags + iTunes XML), and match the two so you can see +which tracks you own, what's missing, and your top artists/tracks/playlists — in +a Streamlit GUI. + +## Features + +- **Spotify backend** — OAuth (Authorization Code) login, then fetch the user + profile, liked tracks, all playlists and their tracks, long-term top tracks + and top artists, and playlist/profile cover art. Resilient paginated fetching + (follows `next`, retries dropped connections, honours `429` rate limits) with + progress bars. +- **Local backend** — index a folder of audio files via `mutagen` tags. +- **iTunes backend** — parse an iTunes Library XML export. +- **Matching** — map local tracks to Spotify tracks (fuzzy matcher; ISRC/MBID + matching planned). +- **Stats & lyrics** — generate listening stats and fetch lyrics. +- **GUI** — browse libraries as artist → album → track tables. + +## Setup + +Requires Python 3.13+. + +```bash +python -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt +``` + +### Credentials + +Create an app at https://developer.spotify.com/dashboard and add +`http://127.0.0.1:8888` as a **Redirect URI**. Credentials are read from the +environment — never commit them. + +```bash +cp .env.example .env +# edit .env with your client id/secret +source .env +``` + +`Env.py` reads `SPOTIFY_CLIENT_ID` / `SPOTIFY_CLIENT_SECRET` (plus optional +`MUSIC_LIBRARY_PATH` and `WORK_DIR`) from the environment. + +## Usage + +Edit `main.py` to uncomment the steps you want, then run: + +```bash +python main.py +``` + +The flow steps (see `Flow.py`) include: + +| Step | What it does | +|------|--------------| +| `fetch_spotify()` | OAuth login + download all Spotify data into `work_dir/spotify/` | +| `fetch_local()` | index local audio files into `work_dir/local/` | +| `parse_spotify_library()` / `parse_itunes_library()` / `parse_local_library()` | build normalized libraries | +| `load_libraries()` / `save_libraries()` | (de)serialize libraries to `work_dir` | +| `map_local_to_spotify()` | match local tracks against the Spotify library | +| `fetch_and_save_lyrics()` | fetch lyrics for tracks | +| `log_stats()` | generate stats | + +On first `fetch_spotify()` a browser opens for the Spotify login; a local server +on `127.0.0.1:8888` catches the redirect and exchanges the code for a token. + +### GUI + +```bash +./gui.sh # runs: .venv/bin/streamlit run Gui.py +``` + +## Layout + +``` +main.py entry point (CLI flow) +Gui.py / gui.sh Streamlit GUI +Flow.py orchestrates fetch / parse / map / stats +Env.py config (reads credentials from the environment) +src/ + Library.py data model (Library / Artist / Album / Track) + LibrarySerializer.py load/save libraries + backends/ + spotify/ Web API client, OAuth, parser + local/ local-file indexer, parser, playlist generator + itunes/ iTunes XML parser + navidrome/ Navidrome backend + mappers/ local↔Spotify matching (fuzzy / search) + lyrics/ lyrics fetching +work_dir/ fetched data and serialized libraries (gitignored) +``` + +## Notes + +- Data is cached as JSON under `work_dir/` (gitignored); re-running reuses it. +- The deprecated Spotify endpoints (audio-features, recommendations, etc.) are + not used — only currently-supported endpoints.