trackfetch 2.0.0__py3-none-any.whl

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.
@@ -0,0 +1,333 @@
1
+ Metadata-Version: 2.4
2
+ Name: trackfetch
3
+ Version: 2.0.0
4
+ Summary: Turn a text file of songs into tagged MP3s: Spotify metadata and cover art, best-match YouTube audio via yt-dlp.
5
+ Author: ByteMe6
6
+ License-Expression: GPL-3.0-or-later
7
+ Project-URL: Homepage, https://github.com/ByteMe6/trackfetch
8
+ Project-URL: Issues, https://github.com/ByteMe6/trackfetch/issues
9
+ Project-URL: Changelog, https://github.com/ByteMe6/trackfetch/blob/master/CHANGELOG.md
10
+ Keywords: music,downloader,spotify,youtube,yt-dlp,mp3,id3,metadata,cli
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: End Users/Desktop
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Topic :: Multimedia :: Sound/Audio
18
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Conversion
19
+ Classifier: Topic :: Utilities
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: mutagen>=1.47
24
+ Requires-Dist: requests>=2.31
25
+ Requires-Dist: spotipy>=2.23
26
+ Requires-Dist: yt-dlp>=2025.11.12
27
+ Provides-Extra: test
28
+ Requires-Dist: pytest>=8; extra == "test"
29
+ Requires-Dist: pytest-cov>=5; extra == "test"
30
+ Dynamic: license-file
31
+
32
+ <picture>
33
+ <source media="(prefers-color-scheme: dark)" srcset=".github/assets/banner-dark.svg">
34
+ <img alt="trackfetch: a text file of songs in, tagged MP3s with cover art out" src=".github/assets/banner-light.svg" width="100%">
35
+ </picture>
36
+
37
+ <p align="center">
38
+ <a href="https://github.com/ByteMe6/trackfetch/releases/latest"><img alt="Latest release" src="https://img.shields.io/github/v/release/ByteMe6/trackfetch?style=flat-square&label=release&labelColor=15122B&color=FFB547"></a>
39
+ <a href="https://github.com/ByteMe6/trackfetch/actions/workflows/release.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/ByteMe6/trackfetch/release.yml?branch=master&style=flat-square&label=build&labelColor=15122B"></a>
40
+ <a href="#install"><img alt="Python 3.10+" src="https://img.shields.io/badge/python-3.10%2B-FFB547?style=flat-square&labelColor=15122B"></a>
41
+ <a href="#standalone-binary"><img alt="Linux, macOS and Windows on x86_64 and ARM64" src="https://img.shields.io/badge/linux%20%C2%B7%20macos%20%C2%B7%20windows-x86__64%20%2B%20arm64-FFB547?style=flat-square&labelColor=15122B"></a>
42
+ <a href="LICENSE"><img alt="License: GPL-3.0-or-later" src="https://img.shields.io/badge/license-GPL--3.0--or--later-FFB547?style=flat-square&labelColor=15122B"></a>
43
+ </p>
44
+
45
+ <p align="center">
46
+ <b>English</b> · <a href="README.uk.md">Українська</a> · <a href="README.ru.md">Русский</a>
47
+ </p>
48
+
49
+ <p align="center">
50
+ <a href="#install">Install</a> ·
51
+ <a href="#quick-start">Quick start</a> ·
52
+ <a href="#playlists-from-streaming-services">Playlists from streaming services</a> ·
53
+ <a href="#usage">Usage</a> ·
54
+ <a href="#how-it-works">How it works</a> ·
55
+ <a href="#troubleshooting">Troubleshooting</a>
56
+ </p>
57
+
58
+ <br>
59
+
60
+ <img alt="Terminal recording: trackfetch skips two songs that are already downloaded, finds Daft Punk - One More Time on Spotify, scores five YouTube results, downloads the best one and saves a tagged MP3" src=".github/assets/demo.svg" width="100%">
61
+
62
+ <br>
63
+
64
+ trackfetch reads a plain text file with one `Artist - Title` per line. For every song it looks up the official metadata and cover art on Spotify, picks the best-matching audio on YouTube, and saves an MP3 with complete ID3 tags. Runs are resumable, so you can stop a 1,000-song list at any point and pick it up later.
65
+
66
+ - **Exact metadata.** The title, every credited artist, album, album artist, track number and release date come from Spotify, not from a YouTube video title.
67
+ - **Album art.** The largest Spotify cover is embedded in every file.
68
+ - **The right upload.** trackfetch scores five YouTube results per song. Official audio wins; covers, karaoke, nightcore, sped-up, slowed, remix and reaction videos lose.
69
+ - **Best quality.** yt-dlp extracts the audio as the highest-quality VBR MP3.
70
+ - **Resumable.** Finished songs are logged in `done.txt` and skipped next time. Failures go to `failed.txt` with the reason.
71
+ - **Plays everywhere.** Tags are written as ID3v2.3, which Windows Explorer, Apple Music, Android, car stereos and most other players read.
72
+ - **Any alphabet.** Cyrillic and other non-Latin titles are matched correctly and kept in filenames. Characters that are illegal in filenames are replaced.
73
+ - **No Python needed.** Standalone binaries for Linux, macOS and Windows on x86_64 and ARM64.
74
+
75
+ ## Install
76
+
77
+ trackfetch needs these tools on your `PATH`, plus a free Spotify API key ([set it up below](#spotify-credentials)):
78
+
79
+ | Tool | Used for | Install |
80
+ | --- | --- | --- |
81
+ | [yt-dlp](https://github.com/yt-dlp/yt-dlp) | Searching and downloading from YouTube | `pipx install yt-dlp` · `brew install yt-dlp` · `winget install yt-dlp` |
82
+ | [FFmpeg](https://ffmpeg.org/) | Converting the audio to MP3 | `sudo apt install ffmpeg` · `brew install ffmpeg` · `winget install ffmpeg` |
83
+ | [Deno](https://deno.com/) | JavaScript runtime that yt-dlp needs for YouTube | `curl -fsSL https://deno.land/install.sh \| sh` · `brew install deno` · `winget install DenoLand.Deno` |
84
+
85
+ ### Standalone binary
86
+
87
+ No Python required. Download the file for your system from the [latest release](https://github.com/ByteMe6/trackfetch/releases/latest):
88
+
89
+ | | x86_64 | ARM64 |
90
+ | --- | --- | --- |
91
+ | **Linux** | [`trackfetch-linux-x86_64`](https://github.com/ByteMe6/trackfetch/releases/latest/download/trackfetch-linux-x86_64) | [`trackfetch-linux-arm64`](https://github.com/ByteMe6/trackfetch/releases/latest/download/trackfetch-linux-arm64) |
92
+ | **macOS** | [`trackfetch-macos-x86_64`](https://github.com/ByteMe6/trackfetch/releases/latest/download/trackfetch-macos-x86_64) (Intel) | [`trackfetch-macos-arm64`](https://github.com/ByteMe6/trackfetch/releases/latest/download/trackfetch-macos-arm64) (Apple Silicon) |
93
+ | **Windows** | [`trackfetch-windows-x86_64.exe`](https://github.com/ByteMe6/trackfetch/releases/latest/download/trackfetch-windows-x86_64.exe) | [`trackfetch-windows-arm64.exe`](https://github.com/ByteMe6/trackfetch/releases/latest/download/trackfetch-windows-arm64.exe) |
94
+
95
+ On Linux and macOS, make it executable and put it on your `PATH`:
96
+
97
+ ```bash
98
+ chmod +x trackfetch-linux-x86_64
99
+ sudo mv trackfetch-linux-x86_64 /usr/local/bin/trackfetch
100
+ ```
101
+
102
+ On macOS, if Gatekeeper blocks the unsigned binary, run `xattr -d com.apple.quarantine trackfetch-macos-*` once. Checksums are in `SHA256SUMS.txt` on each release.
103
+
104
+ ### pipx
105
+
106
+ ```bash
107
+ pipx install git+https://github.com/ByteMe6/trackfetch.git
108
+ ```
109
+
110
+ ### From source
111
+
112
+ ```bash
113
+ git clone https://github.com/ByteMe6/trackfetch.git
114
+ cd trackfetch
115
+ python -m venv .venv && source .venv/bin/activate
116
+ pip install -e .
117
+ ```
118
+
119
+ ## Spotify credentials
120
+
121
+ trackfetch uses Spotify's Client Credentials flow: no Spotify login, no access to your account.
122
+
123
+ 1. Open the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard) and click **Create app**.
124
+ 2. Enter any name and description. For the redirect URI, enter `http://127.0.0.1:8888/callback`. trackfetch never uses it, but the form requires one.
125
+ 3. Open the app's **Settings** and copy the **Client ID** and **Client Secret**.
126
+ 4. Set them as environment variables:
127
+
128
+ <details open>
129
+ <summary><b>bash / zsh</b></summary>
130
+
131
+ ```bash
132
+ export SPOTIFY_CLIENT_ID='your-client-id'
133
+ export SPOTIFY_CLIENT_SECRET='your-client-secret'
134
+ ```
135
+
136
+ </details>
137
+
138
+ <details>
139
+ <summary><b>fish</b></summary>
140
+
141
+ ```fish
142
+ set -Ux SPOTIFY_CLIENT_ID 'your-client-id'
143
+ set -Ux SPOTIFY_CLIENT_SECRET 'your-client-secret'
144
+ ```
145
+
146
+ </details>
147
+
148
+ <details>
149
+ <summary><b>PowerShell</b></summary>
150
+
151
+ ```powershell
152
+ [Environment]::SetEnvironmentVariable('SPOTIFY_CLIENT_ID', 'your-client-id', 'User')
153
+ [Environment]::SetEnvironmentVariable('SPOTIFY_CLIENT_SECRET', 'your-client-secret', 'User')
154
+ ```
155
+
156
+ </details>
157
+
158
+ > [!CAUTION]
159
+ > Spotipy caches the access token in a `.cache` file in the current directory. Don't commit it or share it.
160
+
161
+ ## Quick start
162
+
163
+ ```bash
164
+ cat > songs.txt <<'EOF'
165
+ # My playlist
166
+ Daft Punk - One More Time
167
+ Radiohead - Paranoid Android
168
+ Korol i Shut - Мёртвый Анархист
169
+ EOF
170
+
171
+ trackfetch songs.txt
172
+ ```
173
+
174
+ The files land in `~/Music/trackfetch/`.
175
+
176
+ ## Playlists from streaming services
177
+
178
+ You don't have to type the list by hand. [TuneMyMusic](https://www.tunemymusic.com/) exports playlists from Spotify, Apple Music, YouTube Music, Deezer, Tidal, SoundCloud and other services to a text file that trackfetch reads as is:
179
+
180
+ 1. On [tunemymusic.com](https://www.tunemymusic.com/), choose the service your playlist is on as the source.
181
+ 2. Select the playlists you want.
182
+ 3. Choose **Export to file** as the destination and save it as **TXT**.
183
+ 4. Run trackfetch on the exported file:
184
+
185
+ ```bash
186
+ trackfetch "My Playlist.txt" -o ~/Music/"My Playlist"
187
+ ```
188
+
189
+ ## Usage
190
+
191
+ ```text
192
+ trackfetch [-h] [-o OUTPUT] [--title-only] [--delay DELAY] input
193
+ ```
194
+
195
+ | Option | Default | What it does |
196
+ | --- | --- | --- |
197
+ | `input` | required | Text file with one `Artist - Title` per line |
198
+ | `-o`, `--output` | `~/Music/trackfetch` | Folder to save the MP3s in. It's created if it doesn't exist. |
199
+ | `--title-only` | off | Name files `Title.mp3` instead of `Artist - Title.mp3` |
200
+ | `--delay` | `1.0` | Seconds to wait between songs. Raise it for long lists. |
201
+
202
+ ```bash
203
+ trackfetch songs.txt -o ~/Music/RoadTrip # save to a specific folder
204
+ trackfetch songs.txt -o /media/usb --title-only # short names for a car stereo
205
+ trackfetch big-list.txt --delay 3 # go easy on rate limits
206
+ ```
207
+
208
+ ### Input file
209
+
210
+ ```text
211
+ # Lines starting with "#" are comments. Blank lines are ignored.
212
+ Daft Punk - One More Time
213
+ Sufjan Stevens - Mystery of Love - Remastered ← only the first " - " separates artist and title
214
+ ```
215
+
216
+ The separator is space, hyphen, space (` - `). Lines without it are skipped. See [`examples/songs.txt`](examples/songs.txt).
217
+
218
+ ### Output folder
219
+
220
+ ```text
221
+ ~/Music/trackfetch/
222
+ ├── Daft Punk - One More Time.mp3
223
+ ├── Radiohead - Paranoid Android.mp3
224
+ ├── done.txt ← finished songs, skipped on the next run
225
+ └── failed.txt ← "Artist - Title | reason" for each song that failed
226
+ ```
227
+
228
+ **Retry failures** by stripping the reasons from `failed.txt` and running it again:
229
+
230
+ ```bash
231
+ cut -d '|' -f 1 ~/Music/trackfetch/failed.txt > retry.txt
232
+ trackfetch retry.txt
233
+ ```
234
+
235
+ **Re-download a song** by deleting its MP3 and its line in `done.txt`.
236
+
237
+ ### Cheat sheet
238
+
239
+ A [tldr](https://tldr.sh/) page is in [`docs/tldr/trackfetch.md`](docs/tldr/trackfetch.md). To use it with [tealdeer](https://github.com/tealdeer-rs/tealdeer), copy it into your custom pages folder as `trackfetch.page.md`.
240
+
241
+ ## How it works
242
+
243
+ 1. **Find the song on Spotify.** trackfetch searches for `artist:"…" track:"…"` and falls back to a looser search if nothing comes back. Every result is scored by fuzzy similarity, 70% title and 30% artist, and the best one supplies the official spelling, all credited artists, the album details and the cover.
244
+ 2. **Find the audio on YouTube.** It searches YouTube for the official artist and title and scores the top five results:
245
+
246
+ | Signal | Effect on the score |
247
+ | --- | --- |
248
+ | Similarity to the song title | × 0.70 |
249
+ | Similarity to the artist | × 0.30 |
250
+ | `official audio` in the video title | +0.08 |
251
+ | `audio` in the video title | +0.03 |
252
+ | `cover`, `karaoke`, `караоке`, `nightcore`, `sped up`, `slowed`, `remix`, `reaction` | −0.20 each |
253
+
254
+ 3. **Download.** yt-dlp downloads only the winning video and converts it to MP3.
255
+ 4. **Tag.** trackfetch replaces any existing tags with these ID3v2.3 frames:
256
+
257
+ | Frame | Content |
258
+ | --- | --- |
259
+ | `TIT2` | Title |
260
+ | `TPE1` | Artists |
261
+ | `TALB` | Album |
262
+ | `TPE2` | Album artist |
263
+ | `TRCK` | Track number |
264
+ | `TDRC` | Release date |
265
+ | `APIC` | Front cover, up to 640×640 |
266
+
267
+ If Spotify has no match, the song is still downloaded and tagged with the artist and title from your file.
268
+
269
+ ## Troubleshooting
270
+
271
+ <details>
272
+ <summary><b><code>ERROR: Spotify credentials not found.</code></b></summary>
273
+
274
+ `SPOTIFY_CLIENT_ID` and `SPOTIFY_CLIENT_SECRET` aren't set in this terminal. See [Spotify credentials](#spotify-credentials).
275
+
276
+ </details>
277
+
278
+ <details>
279
+ <summary><b>Every song fails with <code>no YouTube result</code> or <code>yt-dlp download failed</code></b></summary>
280
+
281
+ YouTube changes often. Update yt-dlp first with `pipx upgrade yt-dlp` or `yt-dlp -U`. Recent versions of yt-dlp also need Deno: check that `deno --version` works in the same terminal.
282
+
283
+ </details>
284
+
285
+ <details>
286
+ <summary><b><code>ERROR: Postprocessing: ffprobe and ffmpeg not found</code></b></summary>
287
+
288
+ Install FFmpeg and check that `ffmpeg -version` works in the same terminal.
289
+
290
+ </details>
291
+
292
+ <details>
293
+ <summary><b>The wrong version of a song was downloaded</b></summary>
294
+
295
+ Make the line in your file more specific, for example by using the exact title as Spotify spells it. Then delete the MP3 and its line in `done.txt` and run trackfetch again. If it keeps picking the wrong upload, [open an issue](https://github.com/ByteMe6/trackfetch/issues/new?template=bug_report.yml) with the `YouTube score` lines from the output.
296
+
297
+ </details>
298
+
299
+ <details>
300
+ <summary><b>HTTP 429 or other rate-limit errors</b></summary>
301
+
302
+ Raise `--delay`, for example to `3`. Stopped runs continue where they left off.
303
+
304
+ </details>
305
+
306
+ ## Development
307
+
308
+ ```bash
309
+ pip install -e ".[test]" ruff
310
+ ruff check .
311
+ pytest --cov=trackfetch
312
+ ```
313
+
314
+ The tests stub every network and subprocess call, so they run offline in under a second. To build a standalone binary yourself, run `pip install pyinstaller && pyinstaller trackfetch.spec`; it ends up in `dist/`.
315
+
316
+ See [CONTRIBUTING.md](CONTRIBUTING.md) before opening a pull request.
317
+
318
+ ### Releasing
319
+
320
+ Every push to `master` runs the tests on Linux, macOS and Windows. When `version` in [`pyproject.toml`](pyproject.toml) has no matching `vX.Y.Z` tag yet, the [Build & Release](.github/workflows/release.yml) workflow also builds all six binaries and publishes a release, using that version's section of [`CHANGELOG.md`](CHANGELOG.md) as the notes.
321
+
322
+ To release, move the changes under `[Unreleased]` in the changelog to a new `## [X.Y.Z] - YYYY-MM-DD` section, bump `version`, and push.
323
+
324
+ > [!IMPORTANT]
325
+ > Bump the version in a commit that doesn't edit `.github/workflows/`. GitHub doesn't let the workflow token tag a commit that changes workflow files, so the release step would fail with a 403.
326
+
327
+ ## Disclaimer
328
+
329
+ trackfetch is for personal use with content you have the right to download. You are responsible for following copyright law where you live and the terms of service of YouTube and Spotify. trackfetch isn't affiliated with or endorsed by Spotify, YouTube or TuneMyMusic. If you can, support the artists you listen to.
330
+
331
+ ## License
332
+
333
+ trackfetch is free software, released under the [GNU General Public License v3.0 or later](LICENSE). You can use, study, change and share it. If you distribute a modified version, it must stay under the same license.
@@ -0,0 +1,7 @@
1
+ trackfetch.py,sha256=4cwNuM6PAM5V8cjpZ4C8dqagYH6T684b_C2CSEj7VXA,14434
2
+ trackfetch-2.0.0.dist-info/licenses/LICENSE,sha256=5X8cMguM-HmKfS_4Om-eBqM6A1hfbgZf6pfx2G24QFI,35150
3
+ trackfetch-2.0.0.dist-info/METADATA,sha256=59hX_Mj2otQHC5VIuvtC7ZLup1-iohOi92Qzj9a4uKc,15102
4
+ trackfetch-2.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
5
+ trackfetch-2.0.0.dist-info/entry_points.txt,sha256=0EeZ4BZo2rFGRhlBXIO-Kn7lopCyOHYcmmNan8Ahq6s,47
6
+ trackfetch-2.0.0.dist-info/top_level.txt,sha256=4H2NAQF02sDvFbU43i0sFzgnINT5VraX1V36sbqlxMY,11
7
+ trackfetch-2.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ trackfetch = trackfetch:main