Commit Graph

5 Commits

Author SHA1 Message Date
andrew d756aab7fc 0.6.8: Pin sldl skip index + fix dedup scan blinded by the path rewrite
Two bugs conspired to fill the library with .1.flac duplicates on the
night of 2026-07-15/16:

1. sldl derives its skip-index folder from the input: the Spotify
   playlist display name before 0.6.2, the CSV filename after. The
   switch stranded every playlist's download history in the old
   emoji-named subfolder, so sldl started from an empty index and
   re-downloaded every playlist in full. Fix: pin index-path in
   _template.conf, seed the pinned file from all stranded indexes
   (merge-sldl-indexes.py, called from run-playlist.sh whenever the
   pinned index is missing or older than a stranded one). Recovered
   rows keep the CSV-era identity fields but are marked downloaded, so
   tracks that failed last night yet exist in the library since months
   ago are not fetched again.

2. The safety net that should have flagged the flood, dedup-library.sh,
   has silently reported 0 groups since the 2026-07-08 path rewrite:
   host_path() still assumed /music/... display paths and prepended the
   library dir to already-correct /data/music/Library/... paths, so
   every candidate failed the -f check. Fix: pass real paths through
   unchanged, keep the /music prefix swap only for legacy entries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 09:23:53 -06:00
andrew 3fbf580b88 0.6.2: Bypass sldl's Spotify client -- fetch the playlist ourselves, feed sldl a CSV
sldl's vendored Spotify client still calls GET /playlists/{id}/tracks, which
Spotify removed in its February 2026 API changes. Grandfathered apps still get
a pass; apps created after the change get a hard 403 there no matter how they
authenticate (client-credentials or a correctly-scoped OAuth user token), so
sldl can never load a playlist for a new app regardless of what we hand it.

Instead of depending on sldl's Spotify client at all, run-playlist.sh now reads
the playlist itself via the still-working /items endpoint (new
spotify-playlist-csv.py, creds sourced from _spotify.env) and invokes sldl with
the CSV + --input-type csv, which override the conf's input lines while -c still
supplies Soulseek login, paths, and quality settings (verified live). The same
CSV format upgrade-mp3-to-flac.sh already feeds sldl. All artists are
comma-joined in the CSV so multi-artist tracks search no worse than before, and
a 403/404 on the fetch prints the account-visibility hint instead of a bare
traceback.

Since sldl no longer talks to Spotify, playlist .confs no longer carry
spotify-id/spotify-secret/spotify-refresh: _template.conf drops them,
render_playlist_confs stops injecting them, and a Spotify credential save no
longer re-renders confs (only _spotify.env). The retag step in run-playlist.sh
reuses the sourced _spotify.env creds instead of scraping the conf lines that
no longer exist. Confs are also re-rendered once at app startup so
already-deployed confs converge on upgrade (and shed the stale secret lines)
without waiting for a playlist or credential change. Playlist delete now also
removes the generated .csv.

Tests updated: conf rendering must NOT contain Spotify creds but must keep the
input URL line; new coverage for _spotify.env rendering (quoting, refresh-token
presence/blank, 0600).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 08:48:30 -06:00
andrew e5d9c7f043 0.6: Spotify OAuth connect flow, AzuraCast base URL, fingerprint/buy-url fixes
- Add "Connect Spotify" OAuth flow (/connect/spotify) so newly created Spotify
  apps can read playlists: Spotify now requires a user token for playlist
  reads, which client-credentials alone can no longer provide. The stored
  refresh token is rendered as spotify-refresh into each playlist's sldl
  .conf -- sldl's own docs confirm supplying it skips its interactive login
  flow, which is what was causing multi-hour hangs on a stuck sync.
  Grandfathered apps keep working unchanged if no account is connected.
- Fix the connect flow's redirect_uri: request.url_for() reflected the raw
  connection scheme (http) rather than what the reverse proxy actually
  served over, which Spotify's exact-match redirect_uri check rejected.
  Force https rather than trust the unproxied scheme.
- Add an AzuraCast base URL credential field alongside the API key, with a
  keyfile fallback so the scheduled enrich-buy-url.py job can actually reach
  AzuraCast (previously it had no way to receive a base URL at all when run
  from the scheduler, so the reprocess call was silently always skipped).
- Fix build-fingerprint-index.py exiting non-zero on any single fingerprint
  failure instead of only when nothing was written.
- Guard enrich-buy-url.py's AzuraCast reprocess against an empty
  --azuracast-base (raised ValueError since PD2 removed the hardcoded base).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-14 11:16:55 -06:00
andrew a9e1263b01 Render playlist configs directly in-app; retire regen.sh and playlists.json (R3)
The app owns every input to a playlist's sldl .conf (playlists in its DB,
credentials in its encrypted store), so the old
DB -> playlists.json -> regen.sh subprocess -> regex credential patch-back
chain was three serialization hops for no reason. Collapse it:

- credential_service.render_playlist_confs(db) renders each <playlist>.conf
  from _template.conf in one pass: path placeholders substituted as literal
  text, then the four credential lines set, written 0600. This is now the
  single source of .conf rendering.
- playlist_service loses _write_playlists_json and regenerate_confs; _sync_to_disk
  just calls render_playlist_confs and syncs the scheduler. No subprocess, no
  intermediate JSON file.
- render_scope for spotify/soulseek re-renders confs via the same function
  (spotify still also writes _spotify.env for the python scripts). The dead
  _patch_conf_field / _every_playlist_conf helpers are removed.
- Delete pipeline/configs/regen.sh and drop it from the Dockerfile chmod;
  update _template.conf's comments.

Nothing outside playlist_service consumed regen.sh or playlists.json (verified).
Also closes the last remnants of the S2/S3 injection surface: names are
re-validated and path/credential values are substituted as literals, never
through sed or a shell.

Verified: end-to-end render in a throwaway DB (paths, creds incl. a password
with shell metacharacters, 0600, bad-name rejection) and a live re-render of
all 17 playlist configs with credentials preserved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 15:34:53 -06:00
andrew 68cb007e4c Stage 0: migrate pipeline scripts from /opt/sldl, scaffold repo
Moves all ~30 pipeline scripts, configs, and the vendored sldl binary into
this repo (source /opt/sldl left untouched). Removes all docker exec/docker
compose dependencies now that beets and sldl run in-process/as a subprocess
of this container instead of via soulbeet/on-demand sldl containers.
Replaces hardcoded host paths, Navidrome credentials, and Spotify credential
sourcing with env-var-driven paths and shared credential loaders. Adds
Dockerfile, entrypoint.sh, requirements.txt, docker-compose.snippet.yml, and
the initial app DB schema.
2026-07-08 13:10:55 -06:00