navidrome-scripts 0.14.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. navidrome_scripts-0.14.0/PKG-INFO +485 -0
  2. navidrome_scripts-0.14.0/README.md +469 -0
  3. navidrome_scripts-0.14.0/navidrome_scripts/__init__.py +1 -0
  4. navidrome_scripts-0.14.0/navidrome_scripts/artist_ratings.py +424 -0
  5. navidrome_scripts-0.14.0/navidrome_scripts/catalog.py +45 -0
  6. navidrome_scripts-0.14.0/navidrome_scripts/date_evidence.py +265 -0
  7. navidrome_scripts-0.14.0/navidrome_scripts/dates.py +369 -0
  8. navidrome_scripts-0.14.0/navidrome_scripts/duplicates.py +356 -0
  9. navidrome_scripts-0.14.0/navidrome_scripts/indexed_duplicates.py +370 -0
  10. navidrome_scripts-0.14.0/navidrome_scripts/library_review.py +281 -0
  11. navidrome_scripts-0.14.0/navidrome_scripts/lidarr.py +43 -0
  12. navidrome_scripts-0.14.0/navidrome_scripts/locking.py +88 -0
  13. navidrome_scripts-0.14.0/navidrome_scripts/maintenance.py +308 -0
  14. navidrome_scripts-0.14.0/navidrome_scripts/metadata.py +46 -0
  15. navidrome_scripts-0.14.0/navidrome_scripts/navidrome_guard.py +202 -0
  16. navidrome_scripts-0.14.0/navidrome_scripts/playlists.py +313 -0
  17. navidrome_scripts-0.14.0/navidrome_scripts/provider_review.py +122 -0
  18. navidrome_scripts-0.14.0/navidrome_scripts/reconciliation.py +210 -0
  19. navidrome_scripts-0.14.0/navidrome_scripts/rename_rollback.py +273 -0
  20. navidrome_scripts-0.14.0/navidrome_scripts/renames.py +360 -0
  21. navidrome_scripts-0.14.0/navidrome_scripts/spotify.py +131 -0
  22. navidrome_scripts-0.14.0/navidrome_scripts/subsonic.py +166 -0
  23. navidrome_scripts-0.14.0/navidrome_scripts/tag_repair.py +382 -0
  24. navidrome_scripts-0.14.0/navidrome_scripts/unmapped.py +317 -0
  25. navidrome_scripts-0.14.0/navidrome_scripts.egg-info/PKG-INFO +485 -0
  26. navidrome_scripts-0.14.0/navidrome_scripts.egg-info/SOURCES.txt +46 -0
  27. navidrome_scripts-0.14.0/navidrome_scripts.egg-info/dependency_links.txt +1 -0
  28. navidrome_scripts-0.14.0/navidrome_scripts.egg-info/entry_points.txt +11 -0
  29. navidrome_scripts-0.14.0/navidrome_scripts.egg-info/requires.txt +12 -0
  30. navidrome_scripts-0.14.0/navidrome_scripts.egg-info/top_level.txt +1 -0
  31. navidrome_scripts-0.14.0/pyproject.toml +31 -0
  32. navidrome_scripts-0.14.0/setup.cfg +4 -0
  33. navidrome_scripts-0.14.0/tests/test_date_evidence.py +170 -0
  34. navidrome_scripts-0.14.0/tests/test_dates.py +133 -0
  35. navidrome_scripts-0.14.0/tests/test_duplicates.py +257 -0
  36. navidrome_scripts-0.14.0/tests/test_filesystem_api.py +98 -0
  37. navidrome_scripts-0.14.0/tests/test_indexed_duplicates.py +239 -0
  38. navidrome_scripts-0.14.0/tests/test_library_review.py +85 -0
  39. navidrome_scripts-0.14.0/tests/test_maintenance.py +149 -0
  40. navidrome_scripts-0.14.0/tests/test_package.py +12 -0
  41. navidrome_scripts-0.14.0/tests/test_playlists.py +312 -0
  42. navidrome_scripts-0.14.0/tests/test_provider_review.py +96 -0
  43. navidrome_scripts-0.14.0/tests/test_reconciliation.py +149 -0
  44. navidrome_scripts-0.14.0/tests/test_rename_rollback.py +181 -0
  45. navidrome_scripts-0.14.0/tests/test_renames.py +265 -0
  46. navidrome_scripts-0.14.0/tests/test_scan_barrier.py +58 -0
  47. navidrome_scripts-0.14.0/tests/test_tag_repair.py +234 -0
  48. navidrome_scripts-0.14.0/tests/test_unmapped.py +245 -0
@@ -0,0 +1,485 @@
1
+ Metadata-Version: 2.4
2
+ Name: navidrome-scripts
3
+ Version: 0.14.0
4
+ Summary: Navidrome ratings and music library review tools
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: requests>=2.32
8
+ Requires-Dist: python-dotenv>=1.0
9
+ Requires-Dist: mutagen>=1.47
10
+ Provides-Extra: tags
11
+ Requires-Dist: mediafile>=0.17; extra == "tags"
12
+ Provides-Extra: matching
13
+ Requires-Dist: beets<3,>=2.14.1; extra == "matching"
14
+ Provides-Extra: test
15
+ Requires-Dist: imageio-ffmpeg>=0.6; extra == "test"
16
+
17
+ # navidrome-scripts
18
+
19
+ Tools for Navidrome ratings and music library review.
20
+
21
+ | Command | Purpose |
22
+ | --- | --- |
23
+ | `navidrome-artist-ratings` | Change one artist's tracks from a selected rating to another through the Subsonic API. |
24
+ | `navidrome-library-review` | Review Lidarr unmapped files and optional invalid-date exports without changing the library. |
25
+ | `navidrome-dates` | Audit embedded dates and review reversible date repairs. |
26
+ | `navidrome-playlists` | Match Spotify playlist tracks to your library and update a reviewed Navidrome playlist or export M3U8. |
27
+ | `navidrome-unmapped` | Plan and verify scoped, in-place Lidarr matching; separately review missing catalog additions. |
28
+ | `navidrome-maintenance` | Schedule read-only reviews and process explicitly approved metadata handoffs. |
29
+ | `navidrome-duplicates` | Review duplicate copies, quarantine extras and preserve indexed history. |
30
+ | `navidrome-guard` | Snapshot song identities and personal data, then verify preservation after a new full scan. |
31
+ | `navidrome-renames` | Review album renames and rollback, with backups and Navidrome history checks. |
32
+ | `navidrome-tags` | Review and reversibly fill missing core metadata from an exact Beets/catalog match. |
33
+
34
+ ## Install
35
+
36
+ From this checkout:
37
+
38
+ ```bash
39
+ python3 -m venv .venv
40
+ source .venv/bin/activate
41
+ pip install -e .
42
+ cp .env.example .env
43
+ ```
44
+
45
+ Edit `.env` for the command you want to use. All commands support `--help`.
46
+ Filesystem checks also use Navidrome's native API with the same credentials;
47
+ the base URL must expose `/auth` and `/api`, as well as `/rest`.
48
+ For automation such as arr-scripts, install a released package version and invoke
49
+ its console command; this package does not depend on the other music helpers.
50
+ It has not been published to PyPI yet.
51
+
52
+ ## Artist track ratings
53
+
54
+ Accepts an artist name or Navidrome artist ID (`--artist-id` forces ID mode).
55
+ Preview first, then remove `--dry-run` to write ratings:
56
+
57
+ ```bash
58
+ navidrome-artist-ratings --env-file .env --dry-run
59
+ navidrome-artist-ratings --env-file .env --batch-size 100
60
+ ```
61
+
62
+ Use `--max-albums 2 --batch-size 3` with an artist ID for a small preview.
63
+ Updates are batched; rerunning skips tracks that no longer match `FROM_RATING`.
64
+ Each run writes a timestamped log under `logs/`.
65
+
66
+ ## Library review
67
+
68
+ Set `LIDARR_URL` and `LIDARR_API_KEY` in `.env`, then give the local path that
69
+ corresponds to Lidarr's `/music`:
70
+
71
+ ```bash
72
+ navidrome-library-review --library-root /path/to/Music-Library
73
+ ```
74
+
75
+ Reports are saved as HTML, CSV and JSON under `logs/library-review/`.
76
+ Use `--check-catalog 10` to search MusicBrainz and Apple Music for the first ten
77
+ album groups. Spotify links are manual searches. Existing MusicBrainz IDs and
78
+ search results are candidates to review, not proof of the correct edition.
79
+
80
+ For offline input use `--unmapped-json files.json`. Optional `--date-seeds dates.json`
81
+ includes an offline date-audit export; this command never opens a service database.
82
+ It does not change tags, move files, import albums or submit catalog edits.
83
+ For confirmed MusicBrainz gaps, the separately installed musicbrainz-helper's
84
+ `musicbrainz-new-artist APPLE_ALBUM_URL` prepares editor forms for manual review.
85
+ Ambiguous catalog/import decisions and indexed-copy history migration still require manual review.
86
+
87
+ ## Duplicate review
88
+
89
+ Start with one album folder; audit produces JSON plus a readable HTML comparison:
90
+
91
+ ```bash
92
+ navidrome-duplicates --library-root /music audit --path "Artist/Album" --output logs/duplicates.json
93
+ ```
94
+
95
+ Recording IDs alone do not establish duplicate files. Different encodes, masters,
96
+ editions and tag variations remain for review. No keeper is automatically chosen.
97
+ After reviewing byte-identical copies, choose paths relative to the music root:
98
+
99
+ ```bash
100
+ navidrome-duplicates --library-root /music plan --audit logs/duplicates.json \\
101
+ --keep "Artist/Album/song.flac" --quarantine-file "Artist/Album/copy.flac" \\
102
+ --quarantine-dir /quarantine --navidrome-prefix Music-Library --output logs/quarantine-plan.json
103
+ ```
104
+
105
+ Use the prefix shown at the start of Navidrome's API paths; omit it when its paths
106
+ are already relative to your music root. Planning needs Lidarr and Navidrome
107
+ credentials. A complete administrator index is checked in pages, retaining only
108
+ selected paths. Active scans, incomplete coverage, multiple libraries, indexed
109
+ copies, mapped duplicates, hardlinks, unique lyrics and local playlist/cue files
110
+ require separate review. The keeper must already be mapped and indexed.
111
+
112
+ Run changes on the server with native library paths while scans/tag writers are
113
+ idle. Review the plan, then quarantine or restore:
114
+
115
+ ```bash
116
+ navidrome-duplicates --library-root /music apply --plan logs/quarantine-plan.json --journal logs/quarantine-run.json
117
+ navidrome-duplicates --library-root /music restore --plan logs/quarantine-plan.json --journal logs/quarantine-run.json
118
+ ```
119
+
120
+ Quarantine stays outside the library. Copies are hash-verified before their
121
+ original paths are removed; no service records, directories, artwork or sidecars
122
+ are deleted. A changed Navidrome state triggers restoration of original paths when
123
+ possible, with unresolved cases retained for review. Restore never overwrites new
124
+ content and retains quarantine backups. Lidarr may retain missing unmapped rows
125
+ until its next scoped artist rescan. This does not merge indexed song histories.
126
+
127
+ For **already indexed copies**, use the separate reviewed workflow below. It needs
128
+ Navidrome's native `missing fix` command (tested on 0.64.2) and a local/native backup
129
+ directory mapping. `roots.json` maps Navidrome library IDs to local filesystem roots,
130
+ for example `{"1":"/music"}`. `native-command.json` contains an argument array for
131
+ the official CLI, such as `["docker","exec","navidrome","/app/navidrome","--datafolder","/data"]`.
132
+
133
+ ```bash
134
+ navidrome-duplicates --library-root /music indexed-plan --roots roots.json --keep-id KEEP_AUDIO_ID --quarantine-id OTHER_ID --native-command native-command.json --backup-root /path/to/navidrome/backups --native-backup-root /data/backups --quarantine-dir /quarantine/reviewed-pair-1 --output logs/indexed-plan.json
135
+ navidrome-duplicates --library-root /music indexed-apply --plan logs/indexed-plan.json --journal logs/indexed-run.json
136
+ ```
137
+
138
+ Review `logs/indexed-plan.html` first. The selected audio is kept; the other ID is
139
+ retained by Navidrome's remap and both copies' playlist references follow it.
140
+ Overlapping user annotations/bookmarks and file-backed/smart playlists stay in
141
+ review. Different bytes require `--allow-alternate-encode`; different editions or
142
+ unverified track identities are refused. Backups include all users' private data.
143
+ Keep backups and quarantine outside every music library and source control.
144
+
145
+ After quarantine, complete a full Navidrome scan and repeat `indexed-apply`.
146
+ After remapping, complete another full scan and run `indexed-verify` with the same
147
+ plan/journal. `indexed-restore-files` restores quarantined audio without overwriting
148
+ new files and prints the separate **offline** database-recovery command. It never
149
+ restores the database while Navidrome is running.
150
+
151
+ ## Verify Navidrome preservation
152
+
153
+ Capture a baseline before a reviewed repair, using your Navidrome song IDs:
154
+
155
+ ```bash
156
+ navidrome-guard capture --song-id SONG_ID --output logs/navidrome-before.json
157
+ # After the repair and a completed full Navidrome scan:
158
+ navidrome-guard verify --snapshot logs/navidrome-before.json
159
+ ```
160
+
161
+ This checks the same IDs/paths, ratings/stars, nondecreasing play history and visible
162
+ playlist IDs/ordered membership. It checks only the authenticated account; repeat
163
+ for other users when validating their data. A stale or still-running scan cannot
164
+ prove preservation. For a separately reviewed rename, `--path-map FILE.json` supplies
165
+ an explicit old-path to new-path mapping; the original song IDs must still survive.
166
+
167
+ ## Match unmapped files
168
+
169
+ Start with one album folder. These commands use `LIDARR_URL` and `LIDARR_API_KEY`:
170
+
171
+ ```bash
172
+ navidrome-unmapped --library-root /path/to/Music-Library plan --path "/music/Artist/Album" --output logs/import-plan.json
173
+ ```
174
+
175
+ Review the plan. `ready` files match the exact tagged release, position, title,
176
+ duration and available recording IDs. Conflicting artists/editions, existing track
177
+ files and duplicate copies remain for review. Files must already be in the correct
178
+ Lidarr artist folder. This command will not move files into place.
179
+
180
+ Apply reviewed matches and verify their assignment, original bytes and timestamps:
181
+
182
+ ```bash
183
+ navidrome-unmapped --library-root /path/to/Music-Library apply --plan logs/import-plan.json --journal logs/import-run.json
184
+ ```
185
+
186
+ Queued commands can be checked by repeating the same command; imports are not
187
+ blindly reissued. Keep the plan and journal. Lidarr's File Date must be `None` and
188
+ tag writing must be `Never` or `For new downloads only`; existing files stay untouched.
189
+
190
+ For `catalog_missing` rows that include a `catalog_proposal`, explicitly run
191
+ `add-catalog --plan logs/import-plan.json --journal logs/catalog-run.json` with the
192
+ same `--library-root`. This only adds an unmonitored album to an existing artist,
193
+ without searching for downloads. After metadata refresh, generate a fresh import
194
+ plan. Excluded catalog entries and mismatched artist IDs are refused.
195
+
196
+ After verified mapping, a `.metadata-queue.json` handoff is saved beside the import
197
+ journal. Existing-file imports do not trigger ordinary download notifications;
198
+ the optional arr-scripts worker processes this handoff only after approval.
199
+
200
+ ## Optional maintenance with arr-scripts
201
+
202
+ After publishing/installing the matching package release, set
203
+ `enableLibraryMaintenance="true"` and `navidromeScriptsVersion="X.Y.Z"` in
204
+ arr-scripts' `extended.conf`, then rerun container setup. The default is disabled;
205
+ no unreleased version is automatically installed or enabled.
206
+
207
+ The service generates a read-only unmapped-file report every seven days and
208
+ processes approved jobs one album at a time. Optional full date audits are off by
209
+ default. Review and import one album, then approve its metadata handoff:
210
+
211
+ ```bash
212
+ bash /config/extended/LibraryMaintenance.bash --plan "/music/Artist/Album" /config/logs/import-plan.json
213
+ bash /config/extended/LibraryMaintenance.bash --import /config/logs/import-plan.json /config/logs/import-run.json
214
+ bash /config/extended/LibraryMaintenance.bash --approve /config/logs/import-run.metadata-queue.json
215
+ ```
216
+
217
+ The worker checks the whole album folder for extra/unmapped audio before work.
218
+ Beets runs as a read-only exact-release preview with a separate scratch library;
219
+ use the separate reviewed `navidrome-tags` workflow below to apply tag changes. Missing
220
+ ReplayGain is repaired and verified, then missing lyrics are enqueued. Lyrics
221
+ completion remains in the lyrics worker; queueing does not mean embedding finished.
222
+ Retries preserve completed steps; settings changes pause incompatible approvals.
223
+
224
+ Logs: `/config/logs/library-maintenance.log`. Reports, approvals, step logs and
225
+ status files: `/config/extended/library-maintenance/`. This does not enable
226
+ MusicBrainz submissions, date repair, file moves or duplicate deletion. Stop other
227
+ tag writers while approving/processing a repair album.
228
+
229
+ ## Missing metadata
230
+
231
+ After correcting an existing catalog entry, refresh that album's Lidarr metadata separately:
232
+
233
+ ```bash
234
+ navidrome-unmapped --library-root /path/to/Music-Library refresh-plan --album-id 123 --output logs/refresh.json
235
+ # Review refresh.html, then:
236
+ navidrome-unmapped --library-root /path/to/Music-Library refresh --plan logs/refresh.json --journal logs/refresh-journal.json
237
+ ```
238
+
239
+ This checks artist/edition identity and backs up mapped audio and sidecars before
240
+ submitting one metadata refresh. Repeat the same command to check a queued result.
241
+ Unknown or stale upstream dates remain for review; nothing invents a date or
242
+ automatically renames files. Lidarr tag writing must be `Never` and File Date `None`.
243
+
244
+ From this checkout, install `pip install -e '.[tags,matching]'`, then preview one mapped
245
+ album. Lidarr audio-tag writing must be `Never` and File Date must be `None`:
246
+
247
+ ```bash
248
+ navidrome-tags --library-root /path/to/Music-Library plan --album-id 123 --output logs/tags.json
249
+ ```
250
+
251
+ Open `logs/tags.html`. This checks Beets' exact-edition match against MusicBrainz
252
+ track identities and durations, then proposes missing titles, artist credits,
253
+ album names, track/disc numbers and MusicBrainz IDs. Existing values are retained;
254
+ conflicts remain for review. Dates use the separate date workflow below.
255
+
256
+ After review, with other tag writers stopped:
257
+
258
+ ```bash
259
+ navidrome-tags --library-root /path/to/Music-Library apply --plan logs/tags.json --journal logs/tags-repair.jsonl
260
+ ```
261
+
262
+ Every selected format is tested on a temporary copy before originals are touched.
263
+ Full-file backups and staging need roughly twice the selected files' size in free
264
+ space. Lyrics, artwork, ReplayGain, unrelated tags, paths and modification times
265
+ are checked. Repeat the same command to resume; altered files require review.
266
+ Supported formats are MP3, FLAC, M4A, Opus and Ogg Vorbis.
267
+
268
+ To restore unchanged repaired files exactly:
269
+
270
+ ```bash
271
+ navidrome-tags --library-root /path/to/Music-Library rollback --journal logs/tags-repair.jsonl
272
+ ```
273
+
274
+ Complete a full Navidrome scan after tags change and before planning any rename.
275
+ Use `navidrome-guard` snapshots to check personal data across the scan.
276
+
277
+ ## Invalid dates
278
+
279
+ Audit without changing files, then generate and review a repair plan:
280
+
281
+ ```bash
282
+ mkdir -p logs
283
+ navidrome-dates --library-root /path/to/Music-Library audit --output logs/dates.jsonl
284
+ navidrome-dates --library-root /path/to/Music-Library plan --lookup-dates --audit logs/dates.jsonl --output logs/date-plan.json
285
+ ```
286
+
287
+ The audit records progress, errors and elapsed time. Review `date-plan.html` beside
288
+ the JSON plan. Exact release and track identities must match before MusicBrainz
289
+ dates can replace invalid values. Edition and original dates stay separate, and
290
+ year-only dates stay year-only. Missing or conflicting evidence remains for review;
291
+ invalid placeholders may be removed without inventing replacements. Omit
292
+ `--lookup-dates` for a removal-only plan. Folder renaming remains separate.
293
+
294
+ Add `--apple-review` to the lookup plan for storefront dates, track-list comparisons
295
+ and links to review the existing MusicBrainz entry (`--country us` selects the
296
+ storefront). These results are context for manual catalog corrections; they never
297
+ authorize a tag replacement themselves. Lookup failures and differing/incomplete
298
+ track lists remain visible. Storefront requests are paced and cached per release.
299
+
300
+ Install `pip install 'navidrome-scripts[tags]'` for replacement dates, then apply the
301
+ reviewed plan while other tag writers are stopped:
302
+
303
+ ```bash
304
+ navidrome-dates --library-root /path/to/Music-Library apply --plan logs/date-plan.json --journal logs/date-repair.jsonl
305
+ ```
306
+
307
+ This rechecks source evidence, verifies file hashes, tests replacement writes on
308
+ temporary copies, saves full-file backups alongside the journal, checks unrelated
309
+ tags, and preserves modification times. Allow roughly twice the selected files'
310
+ size for backups and staging. Hardlinked files require separate review. Repairs
311
+ share a library lock; repeating the same command skips completed files. An
312
+ interruption during an unconfirmed write requires backup review before continuing.
313
+
314
+ To restore the exact originals, provided files have not changed since repair:
315
+
316
+ ```bash
317
+ navidrome-dates --library-root /path/to/Music-Library rollback --journal logs/date-repair.jsonl
318
+ ```
319
+
320
+ Keep the plan, journal and backups together. Reconcile incorrect source metadata
321
+ before running another tagger, or it could write the placeholders again. File moves
322
+ and date corrections remain separate operations.
323
+
324
+ ## Album folder and filename repairs
325
+
326
+ Correct the album's current Lidarr metadata first. Finish tag repairs and a full
327
+ Navidrome scan before changing paths. Then review one album's proposed names:
328
+
329
+ ```bash
330
+ navidrome-renames --library-root /path/to/Music-Library plan --album-id 123 --navidrome-prefix Music-Library --output logs/rename-plan.json
331
+ ```
332
+
333
+ Use the prefix shown in Navidrome's native filesystem paths; omit it if paths start directly with
334
+ the artist. Open `logs/rename-plan.html` to review audio, artwork and lyric paths.
335
+ Existing destination folders, extra unmapped audio and local playlist/cue files
336
+ require separate review. Both services' credentials belong in `.env`.
337
+ Use the same library root for repairs and renaming. The helper records completed
338
+ tag writes separately from file mtimes and refuses an older full scan; resume or
339
+ roll back an interrupted repair before renaming.
340
+
341
+ Apply only the reviewed plan, with other writers stopped and enough space for
342
+ full-file backups:
343
+
344
+ ```bash
345
+ navidrome-renames --library-root /path/to/Music-Library apply --plan logs/rename-plan.json --journal logs/rename.json
346
+ ```
347
+
348
+ This submits one scoped Lidarr command. Repeat that exact command to check a queued
349
+ or interrupted result; it does not submit another rename. Artwork and lyrics follow
350
+ the reviewed paths. Changed contents or timestamps stop the workflow for review.
351
+ Original bytes remain in `rename.json.backups`; the plan records their old paths.
352
+ After a full Navidrome scan, verify the forward rename:
353
+
354
+ ```bash
355
+ navidrome-renames --library-root /path/to/Music-Library verify --plan logs/rename-plan.json --journal logs/rename.json
356
+ ```
357
+
358
+ To undo a rename, review a rollback plan before restoring paths:
359
+
360
+ ```bash
361
+ navidrome-renames --library-root /path/to/Music-Library rollback-plan --plan logs/rename-plan.json --journal logs/rename.json --output logs/rollback-plan.json
362
+ navidrome-renames --library-root /path/to/Music-Library rollback --plan logs/rollback-plan.json --journal logs/rollback.json
363
+ ```
364
+
365
+ Open `logs/rollback-plan.html` first. Stop other writers and wait for Lidarr jobs
366
+ to finish. Rollback retains the backups, restores original paths and verifies a
367
+ scoped Lidarr rescan. Changed files stop for review. Repeat the same command to
368
+ resume a queued or interrupted rollback. After a new full Navidrome scan, run:
369
+
370
+ ```bash
371
+ navidrome-renames --library-root /path/to/Music-Library rollback-verify --plan logs/rollback-plan.json --journal logs/rollback.json
372
+ ```
373
+
374
+ Preservation checks cover the configured account. Other users need their own
375
+ `navidrome-guard` snapshots. A failed verification remains unresolved in the journal.
376
+
377
+ ## Spotify playlists
378
+
379
+ Set `NAVIDROME_URL`, `NAVIDROME_USERNAME`, and `NAVIDROME_PASSWORD` in `.env`.
380
+ For Spotify, use a user OAuth access token (`SPOTIFY_ACCESS_TOKEN`) or
381
+ `SPOTIFY_CLIENT_ID` and `SPOTIFY_REFRESH_TOKEN` (plus `SPOTIFY_CLIENT_SECRET`
382
+ for a confidential client). Include `playlist-read-private` for private lists.
383
+ Client credentials alone do not provide user playlist access. Spotify currently
384
+ restricts playlist-item access to lists you own or collaborate on; see the
385
+ [Spotify API documentation](https://developer.spotify.com/documentation/web-api/reference/get-playlists-items).
386
+
387
+ ```bash
388
+ navidrome-playlists plan --spotify-playlist PLAYLIST_URL --output logs/playlist-plan.json
389
+ # Review the JSON matches and unresolved items before applying:
390
+ navidrome-playlists apply --plan logs/playlist-plan.json
391
+ ```
392
+
393
+ Repeat `plan` with a new output filename and `apply` to sync changes. The saved
394
+ state keeps the same Navidrome playlist ID. Order and repeated tracks are preserved;
395
+ ambiguous copies, unavailable items and missing songs require review. `--allow-partial`
396
+ explicitly permits omitting unresolved items; lookup errors always block updates.
397
+ To update an existing playlist, pass its `--playlist-id` when planning.
398
+
399
+ State, prior playlist contents and refreshed Spotify tokens are stored under
400
+ `logs/spotify-playlists/`; retain this directory between runs and keep it private.
401
+ `--state FILE` and `SPOTIFY_TOKEN_FILE` select separate locations for other accounts.
402
+ Unknown write outcomes remain pending until verified; rerun the same apply command
403
+ to recover rather than deleting state or creating another playlist.
404
+
405
+ For a local playlist, use `navidrome-playlists export --plan logs/playlist-plan.json
406
+ --output playlist.m3u8` (one command). Paths are relative to your music root; place
407
+ the resulting M3U8 there. Export reads Navidrome's native API using the same
408
+ credentials to resolve real filenames; virtual Subsonic paths are not exported.
409
+ `--source-json FILE` can replace `--spotify-playlist` when
410
+ planning from a complete API export. `restore --plan FILE` restores a previously
411
+ existing playlist's contents if nobody has edited it since synchronization.
412
+
413
+ ## Validation
414
+
415
+ Install test dependencies with `pip install -e '.[tags,test]'`, then run
416
+ `python -m unittest discover -s tests`. The opt-in `tests/integration_navidrome.py`
417
+ also exercises real scans against an isolated local server: set
418
+ `NAVIDROME_TEST_EXE` to a trusted Navidrome binary and run the script with this
419
+ checkout on `PYTHONPATH`. It generates audio and a temporary database, checks
420
+ ratings/playlists across separate tag and move scans, and stops its own server.
421
+ Production music, accounts and databases are never used by this test.
422
+
423
+ With Docker running, `python tests/run_lidarr_sandbox.py` exercises the full
424
+ Lidarr/Navidrome workflow with generated audio in disposable containers. Results
425
+ go under `logs/nav-fixture-*`; the script removes its test services afterward.
426
+
427
+ ## Run as Python modules
428
+
429
+ From the repository root, the same implementations run without console commands
430
+ once their dependencies are installed:
431
+
432
+ ```bash
433
+ python -m navidrome_scripts.artist_ratings --help
434
+ python -m navidrome_scripts.library_review --help
435
+ python -m navidrome_scripts.dates --help
436
+ python -m navidrome_scripts.playlists --help
437
+ python -m navidrome_scripts.unmapped --help
438
+ python -m navidrome_scripts.maintenance --help
439
+ python -m navidrome_scripts.duplicates --help
440
+ python -m navidrome_scripts.navidrome_guard --help
441
+ python -m navidrome_scripts.renames --help
442
+ python -m navidrome_scripts.tag_repair --help
443
+ ```
444
+
445
+ ## Docker
446
+
447
+ ```bash
448
+ docker build -t navidrome-scripts .
449
+ mkdir -p logs
450
+ docker run --rm --env-file .env \
451
+ -v /path/to/Music-Library:/music:ro -v "$PWD/logs:/work/logs" \
452
+ navidrome-scripts --library-root /music
453
+ ```
454
+
455
+ Use a Lidarr URL reachable from the container; `localhost` refers to the container.
456
+ To run the ratings command instead, use `--entrypoint navidrome-artist-ratings`
457
+ before the image name, followed by `--dry-run` or other ratings options.
458
+
459
+ ## .env support and auth methods
460
+
461
+ The script can load defaults from a `.env` file. Either pass `--env-file` or
462
+ place a `.env` in the working directory. CLI args override environment values.
463
+
464
+ Supported variables:
465
+
466
+ - `BASE_URL` - Navidrome base URL
467
+ - `USERNAME` - Navidrome/Subsonic username
468
+ - `PASSWORD` - Navidrome/Subsonic password
469
+ - `TOKEN` or `API_TOKEN` - optional Subsonic JWT token if your setup exposes one
470
+ - `ARTIST` - artist name or internal artist id
471
+ - `FROM_RATING` - default source rating (default 5)
472
+ - `TO_RATING` - default destination rating (default 2)
473
+
474
+ Example `.env`:
475
+
476
+ ```env
477
+ BASE_URL=http://localhost:4533
478
+ USERNAME=your_username
479
+ PASSWORD=your_password
480
+ ARTIST=3Lu5a2hhygk22VnEcfKkwY
481
+ FROM_RATING=5
482
+ TO_RATING=2
483
+ ```
484
+
485
+ The script prefers `PASSWORD` when both password and token are provided.