stashify-cli 1.3.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 (36) hide show
  1. stashify_cli-1.3.0/LICENSE +21 -0
  2. stashify_cli-1.3.0/PKG-INFO +488 -0
  3. stashify_cli-1.3.0/README.md +454 -0
  4. stashify_cli-1.3.0/pyproject.toml +61 -0
  5. stashify_cli-1.3.0/setup.cfg +4 -0
  6. stashify_cli-1.3.0/src/stashify/__init__.py +3 -0
  7. stashify_cli-1.3.0/src/stashify/__main__.py +6 -0
  8. stashify_cli-1.3.0/src/stashify/cli.py +372 -0
  9. stashify_cli-1.3.0/src/stashify/config.py +108 -0
  10. stashify_cli-1.3.0/src/stashify/download.py +104 -0
  11. stashify_cli-1.3.0/src/stashify/embed.py +271 -0
  12. stashify_cli-1.3.0/src/stashify/errors.py +21 -0
  13. stashify_cli-1.3.0/src/stashify/lyrics.py +36 -0
  14. stashify_cli-1.3.0/src/stashify/models.py +62 -0
  15. stashify_cli-1.3.0/src/stashify/output.py +59 -0
  16. stashify_cli-1.3.0/src/stashify/pipeline.py +179 -0
  17. stashify_cli-1.3.0/src/stashify/sources.py +130 -0
  18. stashify_cli-1.3.0/src/stashify/spotify.py +256 -0
  19. stashify_cli-1.3.0/src/stashify/sync.py +93 -0
  20. stashify_cli-1.3.0/src/stashify/tagger.py +243 -0
  21. stashify_cli-1.3.0/src/stashify_cli.egg-info/PKG-INFO +488 -0
  22. stashify_cli-1.3.0/src/stashify_cli.egg-info/SOURCES.txt +34 -0
  23. stashify_cli-1.3.0/src/stashify_cli.egg-info/dependency_links.txt +1 -0
  24. stashify_cli-1.3.0/src/stashify_cli.egg-info/entry_points.txt +2 -0
  25. stashify_cli-1.3.0/src/stashify_cli.egg-info/requires.txt +7 -0
  26. stashify_cli-1.3.0/src/stashify_cli.egg-info/top_level.txt +1 -0
  27. stashify_cli-1.3.0/tests/test_cli.py +82 -0
  28. stashify_cli-1.3.0/tests/test_config.py +114 -0
  29. stashify_cli-1.3.0/tests/test_download.py +21 -0
  30. stashify_cli-1.3.0/tests/test_embed.py +190 -0
  31. stashify_cli-1.3.0/tests/test_models.py +52 -0
  32. stashify_cli-1.3.0/tests/test_output.py +61 -0
  33. stashify_cli-1.3.0/tests/test_pipeline.py +79 -0
  34. stashify_cli-1.3.0/tests/test_spotify.py +102 -0
  35. stashify_cli-1.3.0/tests/test_sync.py +102 -0
  36. stashify_cli-1.3.0/tests/test_tagger.py +168 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nicolas Dwi Dharma
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,488 @@
1
+ Metadata-Version: 2.4
2
+ Name: stashify-cli
3
+ Version: 1.3.0
4
+ Summary: Find music from your Spotify library on YouTube and save it locally with cover art, lyrics and full metadata.
5
+ Author: Nicolas Dwi Dharma
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/nekonaru/stashify
8
+ Project-URL: Repository, https://github.com/nekonaru/stashify
9
+ Project-URL: Issues, https://github.com/nekonaru/stashify/issues
10
+ Project-URL: Changelog, https://github.com/nekonaru/stashify/blob/main/CHANGELOG.md
11
+ Project-URL: Security, https://github.com/nekonaru/stashify/security/advisories
12
+ Keywords: music,spotify,youtube,downloader,cli,spotify-playlist,mp3
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: End Users/Desktop
16
+ Classifier: Natural Language :: English
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Multimedia :: Sound/Audio
23
+ Classifier: Topic :: Utilities
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: requests>=2.31
28
+ Requires-Dist: yt-dlp>=2024.8.6
29
+ Requires-Dist: mutagen>=1.46
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=8.0; extra == "dev"
32
+ Requires-Dist: ruff>=0.6; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ <div align="center">
36
+ <img src="https://raw.githubusercontent.com/nekonaru/stashify/main/assets/stashify-mark.png" alt="Stashify logo" width="120" />
37
+ <h1>Stashify: Spotify Music Downloader</h1>
38
+ <p><b>Find any track, album or playlist from Spotify on YouTube and save it locally<br/>
39
+ with cover art, lyrics and full metadata.</b></p>
40
+
41
+ <a href="https://github.com/nekonaru/stashify/releases"><img src="https://img.shields.io/github/v/release/nekonaru/stashify?style=flat-square&color=blue" alt="Release" /></a>
42
+ <a href="https://github.com/nekonaru/stashify/actions/workflows/ci.yml"><img src="https://github.com/nekonaru/stashify/actions/workflows/ci.yml/badge.svg" alt="CI" /></a>
43
+ <img src="https://img.shields.io/badge/Python-3.10+-3776AB?style=flat-square&logo=python&logoColor=white" alt="Python" />
44
+ <img src="https://img.shields.io/badge/Platform-Windows%20%7C%20macOS%20%7C%20Linux-555555?style=flat-square" alt="Platform" />
45
+ <img src="https://img.shields.io/badge/License-MIT-green?style=flat-square" alt="License" />
46
+ <img src="https://img.shields.io/badge/Status-Active-brightgreen?style=flat-square" alt="Status" />
47
+ </div>
48
+
49
+ <br />
50
+
51
+ **English** | [Bahasa Indonesia](https://github.com/nekonaru/stashify/blob/main/README.id.md)
52
+
53
+ > [!NOTE]
54
+ > Stashify reads public Spotify pages, no account or login required. It can only see what is public: tracks, albums, artists and public playlists.
55
+
56
+ ---
57
+
58
+ ## Table of Contents
59
+
60
+ - [About](#about)
61
+ - [Features](#features)
62
+ - [Requirements](#requirements)
63
+ - [Installation](#installation)
64
+ - [Usage](#usage)
65
+ - [Configuration](#configuration)
66
+ - [Data Sources](#data-sources)
67
+ - [Project Structure](#project-structure)
68
+ - [Development](#development)
69
+ - [Testing](#testing)
70
+ - [Continuous Integration](#continuous-integration)
71
+ - [FAQ](#faq)
72
+ - [Author](#author)
73
+ - [Disclaimer](#disclaimer)
74
+ - [License](#license)
75
+
76
+ ---
77
+
78
+ ## About
79
+
80
+ **Stashify** is a command line tool that turns a Spotify link into a properly tagged audio file on your machine. It resolves the link against Spotify's public pages, finds the best matching source for each track on YouTube, converts it with FFmpeg and tags it with title, artists, album, track number, release year, cover art and lyrics.
81
+
82
+ > _"Your Spotify library, stashed as real files."_
83
+
84
+ ---
85
+
86
+ ## Features
87
+
88
+ ### Download
89
+
90
+ | Feature | Description |
91
+ |---------|-------------|
92
+ | **Track, album, playlist, artist** | Public Spotify links resolve to a track list and every track downloads (artist links fetch the artist's top 10 tracks, public playlists show up to 50 tracks) |
93
+ | **No account needed** | Metadata comes from Spotify's public embed pages, nothing to create or configure |
94
+ | **Best-match source** | YouTube results are scored on duration and word overlap, the strongest match wins |
95
+ | **Audio formats** | MP3, M4A, Opus, FLAC or WAV, converted through FFmpeg |
96
+ | **Bitrate control** | Choose the bitrate for lossy formats, default `192k` |
97
+ | **Skip existing** | Files that already exist are skipped, `--overwrite` forces a re-download |
98
+ | **Parallel downloads** | N tracks at a time with `--jobs` (default 2, max 8), faster on large playlists |
99
+ | **Dry run** | `--dry-run` prints the matched source per track without downloading anything |
100
+
101
+ ### Metadata
102
+
103
+ | Feature | Description |
104
+ |---------|-------------|
105
+ | **Full tagging** | Title, artists, album (when known), album artist (when known), track number (albums), disc numbers, release year, cover art |
106
+ | **Cover art** | Original cover embedded, per track for playlists and artists, album cover for albums |
107
+ | **Lyrics** | Plain-text lyrics fetched from a community lyrics API and embedded when found |
108
+ | **Retag existing files** | `stashify meta` writes the same metadata into files you already have |
109
+ | **Path templates** | `{artist} - {title}` style templates, subfolders included, safe for any OS |
110
+ | **Save as JSON** | `stashify save` dumps the resolved metadata without downloading audio |
111
+
112
+ ### Sync
113
+
114
+ | Feature | Description |
115
+ |---------|-------------|
116
+ | **Playlist sync** | `stashify sync` keeps a folder in sync with a playlist: new songs are downloaded, removed songs are deleted |
117
+ | **Self-contained state** | A small `stashify.sync.json` state file tracks which files Stashify downloaded, it never touches files it did not create |
118
+
119
+ ---
120
+
121
+ ## Requirements
122
+
123
+ | Dependency | Minimum Version | Notes |
124
+ |------------|-----------------|-------|
125
+ | **Python** | 3.10 | Required |
126
+ | **FFmpeg** | 6 | Required for audio conversion |
127
+
128
+ > [!TIP]
129
+ > No Spotify account is required. Only public content (tracks, albums, artists and public playlists) can be resolved; private playlists are not reachable without an official API entitlement.
130
+
131
+ ### Never used a terminal? Start here
132
+
133
+ <details>
134
+ <summary><b>How to open a terminal on your OS</b></summary>
135
+
136
+ **Windows**
137
+
138
+ 1. Press the **Windows** key, type `PowerShell`, then press **Enter**
139
+ 2. A blue window opens. That is where you type commands
140
+
141
+ **macOS**
142
+
143
+ 1. Press **Command** + **Space**, type `Terminal`, then press **Enter**
144
+ 2. A window with a prompt opens
145
+
146
+ **Linux**
147
+
148
+ 1. Open your app launcher and type `terminal`, then press **Enter**
149
+ 2. A window with a prompt opens
150
+
151
+ Every `stashify ...` command in this guide is typed in that window, followed by **Enter**.
152
+
153
+ </details>
154
+
155
+ ---
156
+
157
+ ## Installation
158
+
159
+ ### Option A: Install from PyPI (easiest)
160
+
161
+ 1. Make sure Python 3.10 or newer is installed (`python --version` should answer)
162
+ 2. Make sure FFmpeg is installed (`ffmpeg -version` should answer). If not:
163
+ - Windows: download from [ffmpeg.org](https://ffmpeg.org/download.html) or `winget install ffmpeg`
164
+ - macOS: `brew install ffmpeg`
165
+ - Linux: `sudo apt install ffmpeg` (or your distribution's package manager)
166
+ 3. Install Stashify from PyPI:
167
+
168
+ ```bash
169
+ pip install stashify-cli
170
+ ```
171
+
172
+ 4. Verify the install:
173
+
174
+ ```bash
175
+ stashify --version
176
+ ```
177
+
178
+ > [!NOTE]
179
+ > The PyPI package is `stashify-cli`; the command it installs is `stashify`. If pip cannot find the package on PyPI yet, use Option B.
180
+
181
+ ### Option B: Install directly from GitHub
182
+
183
+ ```bash
184
+ pip install git+https://github.com/nekonaru/stashify.git
185
+ stashify --version
186
+ ```
187
+
188
+ Needs Python 3.10+ and FFmpeg, same as above.
189
+
190
+ ### Option C: Build from source (for development / to read or change the code)
191
+
192
+ #### 1. Get the project code
193
+
194
+ <details>
195
+ <summary><b>I have Git installed</b></summary>
196
+
197
+ ```bash
198
+ git clone https://github.com/nekonaru/stashify.git
199
+ cd stashify
200
+ ```
201
+
202
+ </details>
203
+
204
+ <details>
205
+ <summary><b>I don't have Git</b></summary>
206
+
207
+ 1. Open the repository page on GitHub
208
+ 2. Click the green **`Code`** button, choose **`Download ZIP`**
209
+ 3. Extract the ZIP to a folder of your choice
210
+ 4. In the terminal, go into the extracted folder, for example:
211
+
212
+ ```bash
213
+ cd Downloads/stashify-main
214
+ ```
215
+
216
+ </details>
217
+
218
+ #### 2. Install the dependencies and verify
219
+
220
+ ```bash
221
+ python -m pip install -e .[dev]
222
+ python -m pytest -q
223
+ ```
224
+
225
+ > [!IMPORTANT]
226
+ > The test suite must pass before you build or publish anything.
227
+
228
+ #### 3. Build and check
229
+
230
+ ```bash
231
+ python -m ruff check src tests
232
+ ```
233
+
234
+ Stashify is a pure Python package. There is no binary to compile; installation is the packaging step.
235
+
236
+ ---
237
+
238
+ ## Usage
239
+
240
+ ### First launch
241
+
242
+ Stashify needs no account, no login and no API keys. Install it, then download:
243
+
244
+ 1. Find any Spotify link for a track, album, playlist or artist (public content only)
245
+ 2. Run:
246
+
247
+ ```bash
248
+ stashify download "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M"
249
+ ```
250
+
251
+ > [!NOTE]
252
+ > The download works for public content only. Private playlists are not reachable: Spotify requires an official API entitlement for that, which Development Mode apps no longer get.
253
+
254
+ ### Everyday use
255
+
256
+ - Download a single track, an album, a playlist or an artist's top tracks by passing their links to `stashify download`; files land in your Downloads folder by default
257
+ - Pick the audio format with `-f mp3|m4a|opus|flac|wav` (default `mp3`)
258
+ - Change the output folder with `-o "D:\Music"` and the naming scheme with `-t "{album}/{track_number} {title}"`
259
+ - Download up to 8 tracks at once with `--jobs 4` (default 2)
260
+ - Skip cover art or lyrics with `--no-cover` / `--no-lyrics`, or re-fetch everything with `--overwrite`
261
+ - Preview the match without downloading with `--dry-run`
262
+ - Keep a folder in sync with a playlist: `stashify sync "PLAYLIST_URL" -o "D:\Music\MyList"`
263
+ - Retag files you already have: `stashify meta file1.mp3 file2.mp3 --url "ALBUM_URL"`
264
+ - Export the resolved metadata as JSON: `stashify save "ALBUM_URL" --save-file album.json`
265
+ - Print the source URL that would be used per track: `stashify url "PLAYLIST_URL"`
266
+
267
+ The path template supports `{artist}`, `{artists}`, `{title}`, `{album}`, `{album_artist}`, `{track_number}`, `{disc_number}` and `{release_year}`. Use `/` to create subfolders. Illegal file name characters are removed automatically.
268
+
269
+ ---
270
+
271
+ ## Configuration
272
+
273
+ Preferences can be stored in a config file; secrets cannot. The default provider needs none.
274
+
275
+ Config file locations: `%APPDATA%\stashify\config.json` (Windows), `~/.config/stashify/config.json` (macOS and Linux).
276
+
277
+ | Key | Type | Default | Effect |
278
+ |-----|------|---------|--------|
279
+ | `output_dir` | string | your Downloads folder | Output directory for downloads (Windows: `%USERPROFILE%\Downloads`, macOS and Linux: `~/Downloads` or `XDG_DOWNLOAD_DIR`) |
280
+ | `jobs` | integer | `2` | Parallel downloads, between 1 and 8 |
281
+ | `audio_format` | string | `mp3` | Default audio format |
282
+ | `bitrate` | string | `192k` | Bitrate for lossy formats |
283
+ | `template` | string | `{artist} - {title}` | Output path template |
284
+ | `market` | string | `US` | Two-letter market code (only used with `--provider api`) |
285
+ | `provider` | string | `embed` | `embed` (default, no account) or `api` (official Web API, needs a Premium-entitled developer app) |
286
+ | `detailed` | boolean | `true` | Fetch per-track details (artists, cover, release year) for playlists |
287
+ | `overwrite` | boolean | `false` | Re-download files that already exist |
288
+ | `no_cover` | boolean | `false` | Never embed cover art |
289
+ | `no_lyrics` | boolean | `false` | Never embed lyrics |
290
+
291
+ Command line flags always win over the config file. The default output directory is your Downloads folder; set the `STASHIFY_DOWNLOAD_DIR` environment variable, the `-o` flag or the `output_dir` key to change it.
292
+
293
+ Use `--provider api` with `--client-id` and `--client-secret` (or `SPOTIFY_CLIENT_ID` / `SPOTIFY_CLIENT_SECRET` environment variables) if you have an official Web API entitlement; it adds ISRC and album metadata for tracks and albums.
294
+
295
+ ---
296
+
297
+ ## Data Sources
298
+
299
+ Stashify is online only when it resolves metadata, searches for a source or fetches lyrics. Audio is sourced from YouTube.
300
+
301
+ | Purpose | Source |
302
+ |---------|--------|
303
+ | Track, album, playlist and artist metadata | Spotify's public embed pages (default) or the Spotify Web API (`--provider api`) |
304
+ | Cover art | Spotify image CDN (embedded into files) |
305
+ | Audio source | YouTube, through yt-dlp |
306
+ | Lyrics | lyrics.ovh (no key required) |
307
+
308
+ > [!NOTE]
309
+ > Some tracks have no usable match on YouTube. Stashify reports the best candidate it found and skips tracks whose score is too low instead of downloading the wrong song.
310
+
311
+ ---
312
+
313
+ ## Project Structure
314
+
315
+ ```
316
+ stashify/
317
+ ├── src/
318
+ │ └── stashify/
319
+ │ ├── __init__.py # package version
320
+ │ ├── __main__.py # python -m stashify entry point
321
+ │ ├── cli.py # argument parsing and commands
322
+ │ ├── pipeline.py # shared download pipeline
323
+ │ ├── spotify.py # Spotify Web API client (optional, --provider api)
324
+ │ ├── embed.py # public embed page resolver (default provider)
325
+ │ ├── models.py # Track data model
326
+ │ ├── sources.py # YouTube search and scoring
327
+ │ ├── download.py # yt-dlp download and FFmpeg conversion
328
+ │ ├── tagger.py # MP3, M4A, FLAC, Opus and WAV tagging
329
+ │ ├── lyrics.py # lyrics.ovh client
330
+ │ ├── output.py # path templates and file naming
331
+ │ ├── sync.py # playlist sync state handling
332
+ │ ├── config.py # config file loading
333
+ │ └── errors.py # error types
334
+ ├── tests/ # pytest suite (offline, network mocked)
335
+ ├── assets/ # logo and icons
336
+ ├── .github/
337
+ │ └── workflows/
338
+ │ ├── ci.yml # lint and tests on every push
339
+ │ ├── publish.yml # publish to PyPI on demand (OIDC)
340
+ │ └── release.yml # draft release from changelog on tags
341
+ ├── pyproject.toml # packaging and tool configuration
342
+ ├── SECURITY.md # vulnerability reporting policy
343
+ ├── LICENSE
344
+ ├── README.md # main documentation (English)
345
+ └── README.id.md # documentation (Bahasa Indonesia)
346
+ ```
347
+
348
+ ---
349
+
350
+ ## Development
351
+
352
+ ### Publishing to PyPI
353
+
354
+ One-time setup, done by the maintainer on PyPI:
355
+
356
+ 1. Open [pypi.org](https://pypi.org), sign in, go to **Account → Publishing**
357
+ 2. Add a pending publisher with these exact values:
358
+ - Project name: `stashify-cli`
359
+ - Owner: `nekonaru`
360
+ - Repository: `stashify`
361
+ - Workflow name: `publish.yml`
362
+ - Environment name: leave empty
363
+ 3. Push a release tag, then run **Publish** from the Actions tab on the tag you want to release
364
+
365
+ The workflow builds the wheel and sdist, checks them with `twine check`, smoke tests the wheel and publishes via OIDC. No API token is stored anywhere.
366
+
367
+ > [!WARNING]
368
+ > Stashify is distributed on PyPI as `stashify-cli` because the name `stashify` already belongs to someone else. Do not try to claim it.
369
+
370
+ ### Working on the code
371
+
372
+ 1. Install the package in editable mode with the dev extras: `python -m pip install -e .[dev]`
373
+ 2. Make your change
374
+ 3. Run the checks: `python -m ruff check src tests` and `python -m pytest -q`
375
+
376
+ ### Adding a new supported audio format
377
+
378
+ 1. Add the format to `AUDIO_FORMATS` in `src/stashify/download.py` and to `_CODEC_MAP`
379
+ 2. Add a tagging path in `src/stashify/tagger.py` (see `_tag_flac` for an example)
380
+ 3. Add a test that renders and retags a short file with FFmpeg, see `tests/test_tagger.py`
381
+
382
+ > [!WARNING]
383
+ > `assets/` is committed. Do not commit downloaded audio, cover art from real releases, or your config file. Anything that looks like personal media stays out of the repository.
384
+
385
+ ---
386
+
387
+ ## Testing
388
+
389
+ Verification before every push, both must be clean:
390
+
391
+ ```bash
392
+ python -m ruff check src tests
393
+ python -m pytest -q
394
+ ```
395
+
396
+ | Check | Requirement |
397
+ |-------|-------------|
398
+ | `python -m ruff check src tests` | Must report **All checks passed** (zero errors, zero warnings) |
399
+ | `python -m pytest -q` | Must pass the full suite |
400
+
401
+ Lint rules are enforced through the `[tool.ruff]` section in `pyproject.toml`.
402
+
403
+ ---
404
+
405
+ ## Continuous Integration
406
+
407
+ | Workflow | Purpose |
408
+ |----------|---------|
409
+ | `ci.yml` | Ruff and the test suite on Python 3.10 and 3.12, on every push and pull request |
410
+ | `release.yml` | Test suite plus a draft GitHub release built from the changelogs, on tags `v*` |
411
+
412
+ ---
413
+
414
+ ## FAQ
415
+
416
+ <details>
417
+ <summary><b>Do I need a Spotify account, login or developer app?</b></summary>
418
+
419
+ No. The default provider reads Spotify's public pages, exactly what a browser sees without logging in. Nothing to create, nothing to log into, nothing to leak.
420
+
421
+ </details>
422
+
423
+ <details>
424
+ <summary><b>Why does my private playlist fail?</b></summary>
425
+
426
+ Public pages only exist for public content. If the playlist is private, the embed page shows a not-found page and Stashify reports it. Making the playlist public fixes it. Alternatively, official API access to your own playlists requires a Premium-entitled developer app, then `stashify ... --provider api` with your own credentials.
427
+
428
+ </details>
429
+
430
+ <details>
431
+ <summary><b>Why is some metadata missing (ISRC, album name)?</b></summary>
432
+
433
+ Public pages do not carry every field. Stashify tags what is available: title, artists, duration, release year, cover art, album name and track numbers for albums. ISRC is only available through the official Web API.
434
+
435
+ </details>
436
+
437
+ <details>
438
+ <summary><b>Some songs are skipped or fail to download. Why?</b></summary>
439
+
440
+ Either there is no usable match on YouTube (unreleased tracks, very obscure remixes, private uploads) or yt-dlp needs an update, because YouTube changes frequently. Run `pip install --upgrade yt-dlp` and try again.
441
+
442
+ </details>
443
+
444
+ <details>
445
+ <summary><b>The wrong version of a song gets downloaded. What can I do?</b></summary>
446
+
447
+ The scorer strongly prefers duration-matched audio with "official" in the title and rejects karaoke, covers and live versions unless you ask for them. Use `--dry-run` first; if the match keeps coming out wrong, open an issue with the Spotify link.
448
+
449
+ </details>
450
+
451
+ <details>
452
+ <summary><b>A file already exists. Will it be overwritten?</b></summary>
453
+
454
+ No. Existing files are skipped. Pass `--overwrite` to re-download them.
455
+
456
+ </details>
457
+
458
+ <details>
459
+ <summary><b>Can I keep my library folder in sync with a Spotify playlist?</b></summary>
460
+
461
+ Yes. `stashify sync "PLAYLIST_URL" -o "D:\Music\MyList"` downloads new tracks and deletes only the files that Stashify downloaded and that have since disappeared from the playlist. Everything else in the folder is untouched.
462
+
463
+ </details>
464
+
465
+ ---
466
+
467
+ ## Author
468
+
469
+ <div align="center">
470
+
471
+ | [![Nicolas Dwi Dharma](https://github.com/nekonaru.png?size=100)](https://github.com/nekonaru) |
472
+ |:---:|
473
+ | **Nicolas Dwi Dharma** |
474
+ | [github.com/nekonaru](https://github.com/nekonaru) |
475
+
476
+ </div>
477
+
478
+ ---
479
+
480
+ ## Disclaimer
481
+
482
+ Stashify is an independent open-source project and is not affiliated with or endorsed by Spotify, Google, YouTube or the operators of lyrics.ovh. The default provider parses Spotify's public embed pages; this is not a documented API and can change or break at any time. Downloading music may violate the terms of service of those platforms and may infringe copyright in your jurisdiction. Stashify is provided for personal, lawful use only; you are responsible for how you use it.
483
+
484
+ ---
485
+
486
+ ## License
487
+
488
+ Distributed under the **MIT License**. See [`LICENSE`](https://github.com/nekonaru/stashify/blob/main/LICENSE) for more information.