mixarchive 0.2.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 (34) hide show
  1. mixarchive-0.2.0/LICENSE +21 -0
  2. mixarchive-0.2.0/PKG-INFO +487 -0
  3. mixarchive-0.2.0/README.md +460 -0
  4. mixarchive-0.2.0/mixarchive/__init__.py +2 -0
  5. mixarchive-0.2.0/mixarchive/__main__.py +3 -0
  6. mixarchive-0.2.0/mixarchive/art.py +159 -0
  7. mixarchive-0.2.0/mixarchive/cli.py +1214 -0
  8. mixarchive-0.2.0/mixarchive/collection.py +162 -0
  9. mixarchive-0.2.0/mixarchive/gui.html +739 -0
  10. mixarchive-0.2.0/mixarchive/gui.py +339 -0
  11. mixarchive-0.2.0/mixarchive/gui_hybrid.html +262 -0
  12. mixarchive-0.2.0/mixarchive/gui_music.html +481 -0
  13. mixarchive-0.2.0/mixarchive/gui_panel.html +248 -0
  14. mixarchive-0.2.0/mixarchive/gui_progress.js +81 -0
  15. mixarchive-0.2.0/mixarchive/gui_radio.html +250 -0
  16. mixarchive-0.2.0/mixarchive/gui_skin.js +313 -0
  17. mixarchive-0.2.0/mixarchive/modland.py +166 -0
  18. mixarchive-0.2.0/mixarchive/rbdb.py +413 -0
  19. mixarchive-0.2.0/mixarchive/rbfiles.py +1006 -0
  20. mixarchive-0.2.0/mixarchive/rbmerge.py +389 -0
  21. mixarchive-0.2.0/mixarchive/rbxml.py +116 -0
  22. mixarchive-0.2.0/mixarchive/singles.py +232 -0
  23. mixarchive-0.2.0/mixarchive/split.py +262 -0
  24. mixarchive-0.2.0/mixarchive/tracklist.py +231 -0
  25. mixarchive-0.2.0/mixarchive/util.py +241 -0
  26. mixarchive-0.2.0/mixarchive/yt.py +233 -0
  27. mixarchive-0.2.0/mixarchive.egg-info/PKG-INFO +487 -0
  28. mixarchive-0.2.0/mixarchive.egg-info/SOURCES.txt +32 -0
  29. mixarchive-0.2.0/mixarchive.egg-info/dependency_links.txt +1 -0
  30. mixarchive-0.2.0/mixarchive.egg-info/entry_points.txt +5 -0
  31. mixarchive-0.2.0/mixarchive.egg-info/requires.txt +10 -0
  32. mixarchive-0.2.0/mixarchive.egg-info/top_level.txt +1 -0
  33. mixarchive-0.2.0/pyproject.toml +42 -0
  34. mixarchive-0.2.0/setup.cfg +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 wolfybird
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,487 @@
1
+ Metadata-Version: 2.4
2
+ Name: mixarchive
3
+ Version: 0.2.0
4
+ Summary: Archive YouTube channels and long mixes as tagged tracks, and sync them into rekordbox for CDJ/XDJ
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/wolfybird/mixarchive
7
+ Project-URL: Issues, https://github.com/wolfybird/mixarchive/issues
8
+ Keywords: youtube,rekordbox,dj,mixes,tracklists,yt-dlp,cdj,xdj
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: End Users/Desktop
11
+ Classifier: Operating System :: Microsoft :: Windows
12
+ Classifier: Operating System :: MacOS
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Multimedia :: Sound/Audio
15
+ Requires-Python: >=3.11
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: yt-dlp[default]
19
+ Requires-Dist: deno
20
+ Requires-Dist: mutagen
21
+ Requires-Dist: pillow
22
+ Provides-Extra: app
23
+ Requires-Dist: pywebview>=6; extra == "app"
24
+ Provides-Extra: rekordbox
25
+ Requires-Dist: pyrekordbox==0.5.0; extra == "rekordbox"
26
+ Dynamic: license-file
27
+
28
+ # mixarchive
29
+
30
+ Archive YouTube channels, playlists and long mixes (DJ sets, compilations, mixed CDs) as
31
+ tagged, level-matched, cover-art-equipped tracks, and put them straight into rekordbox as
32
+ playlists, ready to analyze and export to a USB stick for Pioneer DJ players.
33
+
34
+ New here? Start with [`docs/guide.html`](docs/guide.html): how the pieces fit together
35
+ from YouTube to the decks, setup, the day-to-day steps with screenshots, where the backups
36
+ are and how to undo things. This README is the reference.
37
+
38
+ > mixarchive is an independent project. It is not made, endorsed or supported by
39
+ > AlphaTheta or Pioneer DJ; rekordbox, CDJ and XDJ are trademarks of their owners. It
40
+ > writes to rekordbox's database only while rekordbox is closed, after saving a copy, but
41
+ > keep your own backups. Download and play only what you have the right to: YouTube's
42
+ > terms limit downloading, and playing music in public needs the licences that apply where
43
+ > you play.
44
+
45
+ Built and tested on Windows 10 with rekordbox 7; runs on macOS too.
46
+
47
+ ## Install
48
+
49
+ You need Python 3.11 or newer ([python.org](https://www.python.org/downloads/), or
50
+ `winget install Python.Python.3.14` on Windows) and ffmpeg (`winget install ffmpeg` on
51
+ Windows, `brew install ffmpeg` on macOS). Then, in a terminal:
52
+
53
+ ```
54
+ python -m pip install --user pipx
55
+ python -m pipx ensurepath
56
+ ```
57
+
58
+ open a new terminal (so the commands are on PATH), and:
59
+
60
+ ```
61
+ pipx install "mixarchive[app,rekordbox]"
62
+ mixarchive-app
63
+ ```
64
+
65
+ `mixarchive-app` opens mixarchive in a window of its own (see [GUI](#gui)); `mixarchive`
66
+ is the command line. pipx gives mixarchive an environment of its own; `pip install` into
67
+ any virtual environment works too. The extras: `app` is the window (pywebview), and
68
+ `rekordbox` is what writes to rekordbox's database: `rbsync`, `files` and `merge`
69
+ (pyrekordbox). Without them you still get the command line and `mixarchive gui` in the
70
+ browser.
71
+
72
+ Until the first release is on PyPI, install from GitHub instead:
73
+ `pipx install "mixarchive[app,rekordbox] @ git+https://github.com/wolfybird/mixarchive"`.
74
+
75
+ Your collections go in `Music\mixarchive` in your home folder unless you pick another
76
+ **music folder** before adding the first one (on the Add screen, or `library_root` in
77
+ `settings.json`). mixarchive's own files - settings, the list of collections, caches - are in
78
+ `%APPDATA%\mixarchive` on Windows, `~/Library/Application Support/mixarchive` on macOS and
79
+ `~/.local/share/mixarchive` on Linux; set `MIXARCHIVE_HOME` to use another folder.
80
+
81
+ **Updating:** `pipx upgrade mixarchive`. When YouTube changes something and downloads
82
+ start failing, a newer yt-dlp is usually the fix, before a new mixarchive is out:
83
+ `pipx runpip mixarchive install -U yt-dlp`.
84
+
85
+ ## Day to day
86
+
87
+ ```
88
+ mixarchive add https://www.youtube.com/@somelabel --label "Some Label" --genre House
89
+ mixarchive add https://youtu.be/VIDEOID https://youtu.be/OTHERID # -> YouTube picks
90
+ mixarchive update # every collection: new uploads -> download -> split -> rbsync
91
+ ```
92
+
93
+ then in rekordbox: *mixarchive › Not analyzed yet*, Ctrl+A, right-click › Analyze Track;
94
+ then export the playlists you want to the USB stick. `update` leaves rekordbox alone while
95
+ it's open (it says so) - close it and run `mixarchive rbsync`.
96
+
97
+ `mixarchive` here is the command pip installs; in a clone (below) it's `mixarchive.bat`
98
+ (or `venv\Scripts\python -I -m mixarchive`), on macOS `./mixarchive.sh`.
99
+
100
+ ## GUI
101
+
102
+ `mixarchive gui` opens the workflow in the browser (served on 127.0.0.1 only; the address
103
+ carries a random token, so other pages can't drive it - keep its window open while you use
104
+ it). `mixarchive app` (or `mixarchive-app`) opens it in a window of its own instead
105
+ (pywebview, from the `app` extra: Edge WebView2 on Windows, WebKit on macOS). The window
106
+ won't close while a command is running - press Stop first.
107
+
108
+ Both open on **hybrid**. The **Design** menu at the top switches between the designs, and
109
+ `--page NAME` opens on another one:
110
+
111
+ | design | |
112
+ |---|---|
113
+ | `hybrid` (default) | the radio header over the panel body |
114
+ | `radio` | black, thin rules, condensed caps; NOW and NEXT rows under the menu |
115
+ | `panel` | light grey, lowercase; the five steps as hardware-style keys beside a status screen |
116
+ | `music` | dark sidebar, album-style collection cards, the running command in a player bar |
117
+ | `classic` | the original tabbed page, with skins (below) |
118
+
119
+ Hybrid, radio and panel are built around the pipeline: **1 Add, 2 Update, 3 Sync,
120
+ 4 Analyze, 5 USB**, each with a live count, and *Next* points at the first step with work
121
+ waiting ("Update · 5 mixes to fetch", "Sync · close rekordbox first"). The classic page's
122
+ tabs are **Collections**, **Add sources**, **rekordbox** (including Merge playlists) and
123
+ **Library files** (the `files` tool below); merging and the files tool are only there for
124
+ now.
125
+
126
+ Every design runs the CLI's own commands one at a time, with their output alongside;
127
+ anything that writes to rekordbox is disabled while rekordbox is open. While a command runs,
128
+ a progress bar shows the collection, the step and its count, with an estimate of the time
129
+ left once the step reports a count.
130
+
131
+ The classic page picks a **skin** in its header, kept between runs: **Bare** (the default:
132
+ black and white with one acid accent, light or dark with the system), **Burrow** (wood
133
+ cheeks, candy keys, a small OLED readout), **Field** (grey panel, numbered keys, a green
134
+ LCD) and **Classic** (the original look). `?skin=NAME` in the address overrides it for one
135
+ visit.
136
+
137
+ To start the window with a double-click:
138
+ - **Windows:** `mixarchive-app.exe` starts it without a console window. pipx puts it in
139
+ `%USERPROFILE%\.local\bin`, a clone in `venv\Scripts`; make a desktop or Start menu
140
+ shortcut to it.
141
+ - **macOS, from a clone:** `./make_mac_app.sh` builds `~/Applications/mixarchive.app`,
142
+ which runs the clone's venv, so code changes need no rebuild (rebuild it if you move the
143
+ folder). It opens on hybrid; `./make_mac_app.sh radio` picks another design.
144
+
145
+ ## Setup from a clone
146
+
147
+ For working on mixarchive itself, or running it from a folder of its own:
148
+
149
+ 1. Python 3.11+:
150
+ `py -3 -m venv venv` then `venv\Scripts\python -m pip install -e ".[app,rekordbox]"`.
151
+ On macOS: `python3 -m venv venv` then `venv/bin/python -m pip install -e ".[app,rekordbox]"`;
152
+ paths below that say `venv\Scripts\` are `venv/bin/` there.
153
+ 2. ffmpeg, as in [Install](#install).
154
+ 3. Run from a clone, mixarchive keeps its own files (`settings.json`, `library.json`,
155
+ `cache`, `tools`) in the clone rather than the app-data folder. For settings, copy
156
+ `settings.example.json` to `settings.json` and adjust it. Every key is optional:
157
+
158
+ | key | default | |
159
+ |---|---|---|
160
+ | `ffmpeg`, `ffprobe` | on PATH, or where winget / Homebrew put them | full paths if they're elsewhere (a path that doesn't exist falls back to the search) |
161
+ | `ytdlp` | the yt-dlp installed with mixarchive | |
162
+ | `rbpython` | `rbvenv\Scripts\python.exe` if there is one, else mixarchive's own Python | the Python with pyrekordbox |
163
+ | `library_root` | `Music\mixarchive` in your home folder | where a new library's collections go |
164
+ | `usb_mirror` | none | a backup copy of your USB stick, if you keep one: `files` flags tracks rekordbox plays from inside it |
165
+ | `openmpt123` | `tools\openmpt\openmpt123.exe` | only for MOD renders |
166
+ | `library` | `library.json` | the list of collections (written by `add` / `init`) |
167
+
168
+ An `rbvenv` (a second environment with pyrekordbox, from when it needed a newer Python
169
+ than the rest) is still used if it's there; a new setup doesn't need one.
170
+ 4. The first collection decides where the library lives: by default `library_root`, or give
171
+ it `--root`, e.g.
172
+ `mixarchive add https://www.youtube.com/@somelabel --label "Some Label" --root "D:\DJ\Library\mixarchive\Some Label"`.
173
+ Later collections go next to it (label collections in a `labels\` folder beside it).
174
+
175
+ yt-dlp needs deno for YouTube's JavaScript (installed with the package). Some videos
176
+ answer 403 unless the request carries a PO token; see yt-dlp's
177
+ [PO Token Guide](https://github.com/yt-dlp/yt-dlp/wiki/PO-Token-Guide). If a bgutil
178
+ provider is installed in `tools\bgutil-ytdlp-pot-provider` in mixarchive's own folder (see that project's own
179
+ instructions), mixarchive starts it on 127.0.0.1 when a download needs one. Videos YouTube
180
+ age-restricts need a signed-in account; mixarchive never signs in, so they're left out.
181
+
182
+ ## Where things live
183
+
184
+ All collections live under one library root, e.g.
185
+
186
+ ```
187
+ D:\DJ\Library\mixarchive\ <- one folder per collection; labels\<Label>\ for labels
188
+ D:\DJ\Library\_rekordbox backups\ <- copies of rekordbox's database before each write
189
+ ```
190
+
191
+ `library.json` maps collection names to their folders; `move NAME NEWROOT` relocates one
192
+ (copy, size check, re-register; `--delete-old`). Keep the library off the USB stick and out
193
+ of any backup copy of the stick: rekordbox's USB export re-files tracks as
194
+ `Contents\<Artist>\<Album>\`, and the next copy of the stick would bring them back as
195
+ second copies.
196
+
197
+ ## Conventions
198
+
199
+ Every collection is one folder:
200
+
201
+ ```
202
+ <collection>\
203
+ originals\<title> [<id>].opus best YouTube audio, never re-encoded
204
+ _mixarchive\ state (small, safe to sync)
205
+ collection.json settings (below)
206
+ sources.json which YouTube upload each mix maps to (+ alternates)
207
+ info\<id>.info.json yt-dlp metadata incl. top comments
208
+ tracklists\<id>.txt EDITABLE tracklist per mix - the source of truth for splits
209
+ tracks.csv EDITABLE singles list (label channels)
210
+ art\ square 600x600 covers (<id>_main.jpg, <id>_<NN>.jpg)
211
+ modules.csv, modules\, renders\ (opt-in MOD renders)
212
+ split\
213
+ MP3 320\<Mix>\NN - Artist - Title.mp3 tracks
214
+ MP3 320\_full mixes\<Mix>.mp3 whole mixes (tracklist in comments)
215
+ MP3 320\tracklist.csv manifest incl. loudness + gain applied
216
+ MOD renders\<Mix>\NN - Artist - Title (MOD render).mp3
217
+ ```
218
+
219
+ - **Audio source**: YouTube Opus 251 (full band to ~20 kHz); AAC 140 (16 kHz low-pass) only
220
+ when no Opus exists. YouTube serves nothing lossless.
221
+ - **Output**: MP3 320 CBR (plays on every CDJ/XDJ). Add `flac` to `formats` for 16-bit FLAC.
222
+ - **Normalization** (default on): each track gets one linear gain to **-14 LUFS**, capped so the
223
+ true peak stays <= **-1 dBTP** - no limiter, dynamics untouched. Full mixes get one gain
224
+ for the whole mix. YouTube decodes often peak above 0 dBFS, so this also stops clipping.
225
+ Very dynamic tracks can land a few dB under -14 (peak-capped) rather than be squashed;
226
+ rekordbox Auto Gain evens those out. `tracklist.csv` records measured LUFS / peak / gain.
227
+ - **Tags**: artist, title, album (= mix), album artist (DJ / channel), track n/total, genre,
228
+ year, label (TPUB = rekordbox "Label"), comment with a YouTube link at the track's start time,
229
+ square cover art.
230
+
231
+ ## Label channels and playlists (singles + mixes)
232
+
233
+ `init NAME --root DIR --playlist URL [--playlist URL2] --label "Label" --genre G` takes
234
+ playlists and channel pages (`https://www.youtube.com/@channel/videos`). `resolve` sorts
235
+ every video:
236
+
237
+ - **single track** (shorter than `mix_min_sec`, 15 min) -> a row in `_mixarchive\tracks.csv`
238
+ - **mix** (longer) -> the usual tracklist file and split
239
+ - **skip**: unavailable, under 60 s, over 12 h (livestreams), a title matching `skip_titles`
240
+ (trailer, teaser, preview, announcement, interview, ...), or a video another collection
241
+ already archives (`skip_known`)
242
+
243
+ `tracks.csv` is the singles' source of truth (UTF-8 CSV, opens in Excel). Edit freely,
244
+ then re-run `split`: renamed rows are re-exported and the old file removed.
245
+
246
+ | column | |
247
+ |---|---|
248
+ | status | `ok`, `check` (no artist found), `dup` (same artist + title as a better upload: official audio > plain > video, then longest), `skip`; set `manual` to protect a hand-edited row from `resolve --force` |
249
+ | artist, title, album | parsed from the title: `Artist - Title`, `Artist 'Title'`, catalog numbers (`ABC039`, `- XYZ030`, `[LB007]`), `(Label, 1993)`, `[Official Audio]`, `\| Series` |
250
+ | year, year_src | `title` / `youtube` (music metadata) / `description` (the (P) line) / `upload` (fallback; off with `--no-upload-year` for archive labels whose uploads are decades after release) |
251
+ | part, start, end | a short video with real chapters becomes one row per track |
252
+
253
+ Singles export whole (one gain to -14 LUFS / -1 dBTP) to `split\MP3 320\<Label>\Artist - Title.mp3`
254
+ with album = label (or the series), rekordbox Label = label, and the YouTube title, link,
255
+ catalog number and upload date in the comment. Covers: when the thumbnail is a sleeve on a
256
+ flat backdrop or in black bars, the sleeve is cropped out; otherwise the frame is letterboxed.
257
+
258
+ `--kind track` / `--kind mix` limits any step to singles or mixes (e.g. fetch only singles).
259
+
260
+ ## Adding sources
261
+
262
+ `add LINK...` takes channel pages (`youtube.com/@name`, `/channel/UC...`; any tab becomes
263
+ `/videos`), playlists (`playlist?list=`) and single videos (`youtu.be/ID`, `watch?v=`,
264
+ `shorts/`, or a bare id; a watch link inside a playlist counts as the video):
265
+
266
+ - a channel / playlist becomes a new collection named after it, in the library root.
267
+ `--label L` makes it a label collection in `labels\L` (rekordbox Label = L); without one,
268
+ a channel is a DJ's (album artist = the channel) and the rekordbox playlist is named
269
+ after the channel / playlist. `--name`, `--genre`, `--album-artist`, `--root`,
270
+ `--no-upload-year` as for `init`
271
+ - single videos go to the **youtube-picks** collection (`YouTube picks` in the library root,
272
+ rekordbox playlist "YouTube picks"; created on first use). Short ones become singles
273
+ (album artist = their own artist), long ones mixes with tracklists, as anywhere else.
274
+ `--name X` with only videos makes / extends a picks collection called X instead
275
+ - `--to NAME` puts everything into an existing collection instead
276
+ - `--run` downloads + splits straight away; otherwise `update` does it later
277
+
278
+ Videos already archived in another collection are skipped as usual (`skip_known`).
279
+ Single videos live in `collection.json` as `source.videos`.
280
+
281
+ ## Tracklist files
282
+
283
+ `_mixarchive\tracklists\<video id>.txt` - edit freely, then re-run `split`:
284
+
285
+ ```
286
+ @album: Some DJ - Studio Mix (1996)
287
+ @album_artist: Some DJ
288
+ @year: 1996
289
+ 00:00:00 First Artist - First Track {1995} {label: First Label}
290
+ 00:07:51 Second Artist - Second Track (Some Remix) {1996} {label: Second Label}
291
+ --:--:-- Some Artist - Untimed Track <- blocks splitting until given a time
292
+ ```
293
+
294
+ Mixes with any `--:--:--` line get only a full-mix file until every line has a time.
295
+ `tracklists --force` refreshes the track lines but keeps your `@album/@album_artist/@year/@genre`.
296
+
297
+ Channels that write titles in styled Unicode letters (`𝗟𝗢𝗦𝗧 𝗢𝗖𝗘𝗔𝗡 沈殿`) show up blank or
298
+ as boxes on many DJ players. `"plain_titles": true` in collection.json makes new tracklists,
299
+ files and tags use plain letters instead (`LOST OCEAN 沈殿`); accents and kanji are kept.
300
+ Set it before a collection's first `split`: on one already split, the next `split` writes
301
+ plain-named copies next to the old files.
302
+
303
+ Where tracklists come from, best first: YouTube chapters -> timestamps in the description ->
304
+ per-track **lengths** whose total matches the mix (CD listings, also borrowed from sibling
305
+ uploads in the same collection, +-15 s and >=50% track overlap) -> timestamped top comments ->
306
+ silent gaps (unmixed CDs: clustered silences within 30 s merge, longest wins; used only when
307
+ there are exactly N-1 gaps) -> untimed lists.
308
+
309
+ Downloads retry YouTube's intermittent 403s; a mix that still fails is skipped and listed so
310
+ the rest of the batch finishes - just re-run `fetch`. As a last resort a download falls back
311
+ to the HLS copy of AAC 128k (format 234: same audio as 140, 16 kHz low-pass).
312
+
313
+ ## Commands
314
+
315
+ | command | does |
316
+ |---|---|
317
+ | `init NAME --root DIR (--playlist URL... \| --local DIR [--exclude RE]) [--genre G] [--album-artist A] [--label L] [--no-upload-year] [--modrender] [--frames-art]` | create a collection (`--playlist` repeats; channel `/videos` pages work) |
318
+ | `resolve NAME [--pick "substring=VIDEOID"]` | find each mix's upload (search + duration match for local files), pick the one with the best tracklist |
319
+ | `tracklists NAME [--years CSV] [--force]` | write the editable tracklists |
320
+ | `fetch NAME` | download originals; move old local files to `_old 128k` |
321
+ | `art NAME` | square covers (thumbnail letterboxed; `frames` mode crops each track's on-screen info box) |
322
+ | `split NAME [--only X] [--formats mp3,flac] [--jobs N]` | cut + normalize + tag |
323
+ | `modmatch NAME` / `modrender NAME` | opt-in tracker music: Modland match, libopenmpt render |
324
+ | `status NAME` | per-mix progress |
325
+ | `verify NAME [--sample N]` | every split decodes, has the right length, tags + cover; re-measures a sample's loudness |
326
+ | `run NAME` | everything above in order |
327
+ | `add LINK... [--to NAME] [--label L] [--genre G] [--name N] [--root DIR] [--run]` | add channels / playlists (new collections) or videos (youtube-picks) |
328
+ | `update [NAME...] [--no-rbsync]` | `run` for every collection (or those named), then `rbsync` if rekordbox is closed |
329
+ | `files status \| audit \| verify \| relocate \| move \| flac \| undo \| flaccopy \| silence ...` | the files behind rekordbox's whole collection (below) |
330
+ | `gui [--port N] [--page NAME]` | the browser GUI (design: hybrid, radio, panel, music or classic) |
331
+ | `app [--port N] [--page NAME]` | the same GUI in a window of its own (needs the `app` extra: pywebview) |
332
+ | `list` | known collections |
333
+ | `move NAME NEWROOT [--delete-old]` | relocate a collection (copy, check sizes, re-register) |
334
+ | `rbsync [NAME...] [--dry-run]` | put tracks + playlists straight into rekordbox's database (rekordbox closed); default: all collections |
335
+ | `rbxml [NAME...] [--out PATH]` | the same playlists as a rekordbox XML file, for a manual import instead |
336
+ | `merge SOURCE --into TARGET [--apply] [--keep-source]` | merge one rekordbox playlist into another (below; rekordbox closed) |
337
+
338
+ All steps skip finished work; `--force` redoes it, `--only a,b` limits to matching mixes.
339
+
340
+ ## Into rekordbox and onto the USB
341
+
342
+ Close rekordbox, then `mixarchive rbsync`. It writes into rekordbox's own database
343
+ (`%APPDATA%\Pioneer\rekordbox\master.db`, via pyrekordbox):
344
+
345
+ - a `mixarchive` playlist folder with one playlist per collection. Each holds the
346
+ collection's splits (mix tracks + singles); a mix without a timed tracklist contributes
347
+ its full-mix file instead, so a USB export never carries a mix alongside its own tracks.
348
+ MOD renders get their own playlist. Two collection.json settings change that:
349
+ `"full_mixes": true` lists every mix by its full mix instead (for when its splits live in
350
+ playlists of your own), and `"mix_playlists": {"<mix>": "<playlist>"}` moves a mix to a
351
+ playlist of its own, its splits then its full mix. Playlists are rewritten to match on every run, except
352
+ that tracks you dragged into one yourself stay (after the collection's own, in your order;
353
+ rbsync remembers what it put there in `cache\rbsync_written.json` and only ever removes
354
+ those). Other playlists, including your own inside that folder, are never touched
355
+ - new files are added to the collection with their tags, label and cover
356
+ - files a re-split changed (size differs) get tags, cover and size refreshed, like Reload
357
+ Tag; cues, beat grids and analysis stay
358
+ - files a tracklist / tracks.csv edit renamed are followed: the existing rekordbox entry
359
+ (matched by the YouTube link + start time in its comment) is pointed at the new name,
360
+ so its cues aren't lost to a fresh copy
361
+ - `mixarchive / Not analyzed yet` lists every synced track rekordbox hasn't analyzed. Open
362
+ it, Ctrl+A, right-click > Analyze Track. It disappears once it's empty
363
+
364
+ `master.db` and `masterPlaylists6.xml` are copied to `_rekordbox backups\<date> rbsync\`
365
+ (next to the library root) before each write; the last 10 are kept. To undo a sync, close
366
+ rekordbox and copy them back. A run with nothing to do writes nothing. `--dry-run` shows
367
+ what would change. Entries under the library whose file mixarchive removed (a single
368
+ that became a dup or skip) are reported, not deleted; they show as missing in rekordbox.
369
+
370
+ `rbxml` remains for a manual route: it writes the same playlists as `rekordbox.xml` in the
371
+ library root for rekordbox xml > Import Playlist. Use one route or the other; importing the
372
+ XML after `rbsync` adds a second set of playlists.
373
+
374
+ Both print each playlist's size, for planning what fits on the USB stick.
375
+
376
+ rekordbox has no way to start an analysis from outside, so analyzing stays a click in
377
+ rekordbox. Closing rekordbox stops an analysis midway; the next `rbsync` shrinks *Not
378
+ analyzed yet* to what's left. When a re-split changes the audio behind a file rekordbox
379
+ already analyzed (same name, or a followed rename, but a different length), `rbsync` adds
380
+ it to *mixarchive › Re-analyze (audio changed)*: analyze those again, then delete that
381
+ playlist. Old split files that no longer exist show as missing in rekordbox - remove them
382
+ with *File › Display All Missing Files*.
383
+
384
+ mixarchive never writes to the USB stick; rekordbox's export does.
385
+
386
+ ## Merging playlists (`merge`)
387
+
388
+ `mixarchive merge "Some Label (old)" --into "mixarchive / Some Label"` (or the rekordbox tab
389
+ of the GUI) merges one rekordbox playlist into another (rbmerge.py, with pyrekordbox like rbsync).
390
+ Name a playlist by its name, or by its path with ` / ` when the name is used twice.
391
+
392
+ - the target keeps its tracks in its order; the source's follow in theirs; a track listed
393
+ twice is listed once
394
+ - copies of the same recording (same words in the name, similar length, then confirmed
395
+ by ear: the first 4 minutes correlated) keep only the best copy: WAV / AIFF, then FLAC,
396
+ then the highest bitrate. Tracks rbsync put in a mixarchive target always stay; a copy
397
+ of yours stays next to them only if it's lossless or at least 320 kbps (a bought file),
398
+ so lower-bitrate rips of what mixarchive has go
399
+ - the copies that go leave the playlist, not the collection (play histories keep them);
400
+ their cues and hot cues are copied to the copy that stays, shifted by the measured
401
+ offset (not where it already has one there, or the hot cue pad is taken)
402
+ - the source playlist is deleted (`--keep-source` keeps it). One of mixarchive's own
403
+ playlists can't be the source: Sync would bring it back. Tracks merged into a mixarchive
404
+ playlist stay there on later Syncs (after mixarchive's own)
405
+
406
+ Without `--apply` it only reports, with every copy it found and where its cues go. With
407
+ it, master.db and masterPlaylists6.xml are copied to `_rekordbox backups\<date> merge\` first
408
+ with `plan.csv` (every track's fate); to undo, close rekordbox and copy them back. The USB
409
+ stick isn't touched: export the merged playlist again and delete the old one from the stick.
410
+
411
+ ## rekordbox's files (`files`)
412
+
413
+ `mixarchive files COMMAND` works on rekordbox's whole collection, not only mixarchive's
414
+ (rbfiles.py, with pyrekordbox like rbsync). Filters for every command: `--ext wav,aif,aiff`,
415
+ `--under DIR`, `--playlist NAME` (a folder includes what's below it), `--missing`.
416
+
417
+ | command | does |
418
+ |---|---|
419
+ | `status` | rekordbox open?, tracks, not analyzed, missing; mixarchive playlists; tracks / GB per location and format |
420
+ | `audit [--list] [--csv PATH]` | the same per location for the filtered tracks; `--list` prints each file + its playlists |
421
+ | `verify [--decode]` | each file exists with the size rekordbox recorded; `--decode` decodes them all (ffmpeg) |
422
+ | `relocate --search DIR...` | missing files: same name (+ size) found under DIR -> rekordbox points there |
423
+ | `move --to DIR [--layout keep\|flat\|playlist\|artist] [--copy]` | move (or copy) the files and repoint rekordbox; `playlist` files each under the smallest playlist holding it, `artist` under artist / album, `keep` keeps the folders below `--under` |
424
+ | `flac --to DIR [--layout ...]` | WAV / AIFF -> FLAC under DIR, rekordbox repointed (type FLAC, bitrate 0, new size, as rekordbox records FLACs). Each FLAC is decoded and compared sample for sample with its original first; tags + cover art come along, with rekordbox's title / artist / album / genre on top. Originals stay; 32-bit float files (FLAC can't hold them) are copied as they are. Older Pioneer players don't play FLAC: if you use them, keep WAV for rekordbox and use `flaccopy` for the backup instead |
425
+ | `undo JOURNAL.csv [--keep]` | reverse a relocate / move / flac; `--keep` points rekordbox back at the originals but leaves the copies / FLACs |
426
+ | `flaccopy --from DIR --to DIR` | keep `--to` a FLAC copy of the WAV / AIFF under `--from` (checked the same way; float files copied as they are; copies whose source is gone are deleted). Files only, rekordbox untouched - `backup_library.bat` runs it for `<library>\WAV` |
427
+ | `silence [--min-gap S] [--list] [--csv PATH] [--jobs N] [--cues --apply]` | silent gaps inside tracks, such as dropouts in a rip (below) |
428
+
429
+ `files silence` decodes each track (mono 8 kHz, ffmpeg, 8 at a time; about 4,000x realtime
430
+ on a desktop) and measures its level every 0.1 s over 0.4 s. A gap is a stretch of
431
+ `--min-gap` seconds (default 2) at least 30 dB under the track's typical level and under
432
+ -35 dBFS, between where the music first gets within 10 dB of typical and where it last is,
433
+ so quiet intros, fade-ins and endings don't count. Short blips inside a gap don't end it.
434
+ A gap is **dropout-like** when the level falls 20 dB or more into it, or half of it is digital
435
+ silence; otherwise it's a **quiet passage** (breakdowns, dub drops). Files over 15 minutes
436
+ (mixes, tapes) and acapellas are reported apart. Results are cached in
437
+ `cache\silence_scan.json`, so a re-run scans only new or changed files. With `--cues --apply`
438
+ (rekordbox closed, master.db backed up first) it puts a memory cue named "gap N s" at each
439
+ dropout-like gap at least 20 s from either end, so you see it on the deck; a gap that
440
+ already has a memory cue within 1 s is skipped, so a re-run adds nothing twice.
441
+
442
+ relocate / move / flac / undo only report until `--apply`, and write only with rekordbox
443
+ closed. Cues, beat grids and analysis stay (pyrekordbox's `update_content_path` also rewrites
444
+ the path inside the analysis files, and rbfiles updates the size + md5 rekordbox keeps of
445
+ each analysis file in `contentFile`). Before writing, master.db, masterPlaylists6.xml and the
446
+ touched tracks' analysis files go to `_rekordbox backups\<date> files-<command>\` with a
447
+ `journal.csv` of every change - the input for `undo`. If anything fails midway the files are
448
+ put back, the analysis files restored and the database left as it was. Moves on one drive
449
+ are instant renames; to another drive the files are copied, checked, and the originals
450
+ deleted only after the database is committed.
451
+
452
+ `status` flags tracks that play from inside a backup copy of the USB stick (`usb_mirror`
453
+ in settings): that folder is a copy of the stick, not a library.
454
+
455
+ ## Backup
456
+
457
+ `backup_library.bat LIBRARY BACKUP`, e.g. `backup_library.bat "D:\DJ\Library" "B:\DJ Backup"`,
458
+ mirrors the library to `BACKUP\Library` (robocopy /MIR, so files you deleted from the
459
+ library go from the backup too) and, while rekordbox is closed, rekordbox's own database
460
+ and analysis files (`%APPDATA%\Pioneer\rekordbox`) to `BACKUP\rekordbox`. Log:
461
+ `cache\backup_library.log`.
462
+
463
+ If the library has a `WAV` folder (WAV / AIFF for rekordbox), the backup leaves it out and
464
+ carries `WAV - FLAC backup` instead: the same audio as FLAC at about half the size,
465
+ refreshed by `files flaccopy` at the start of every backup. To restore a WAV from it:
466
+ `ffmpeg -i "x.flac" -c:a pcm_s16le "x.wav"` - `pcm_s24le` for 24-bit files (ffprobe shows
467
+ `bits_per_raw_sample`); left to its default ffmpeg would write 24-bit audio as 16-bit.
468
+
469
+ ## MOD renders (tracker collections, opt-in)
470
+
471
+ `modmatch` fuzzy-matches artist/title against the Modland index (cached monthly), with scene
472
+ aliases in `aliases.json` (e.g. `{"xsm": ["xtra spicy mike"]}`). `modules.csv` statuses:
473
+ `auto` (title + artist match), `check` (needs a look), `none`, and the hand-set
474
+ `manual` / `skip` (never overwritten). `modrender` renders with libopenmpt (A1200 filter
475
+ emulation, 50% stereo, 48 kHz; needs `openmpt123`) and **verifies each render's length
476
+ against the mix's track** (+-12% / 20 s), trying alternates and marking `mismatch` instead
477
+ of exporting a same-named different song.
478
+
479
+ ## Licence
480
+
481
+ mixarchive's own code is under the MIT licence (see [LICENSE](LICENSE)). It installs and
482
+ uses other projects under their own licences, notably yt-dlp (Unlicense), pyrekordbox (MIT),
483
+ mutagen (GPL-2.0-or-later), Pillow (MIT-CMU), SQLAlchemy (MIT), sqlcipher3 (zlib) and, for
484
+ `mixarchive app`, pywebview (BSD-3-Clause); none of them is bundled here. The GUI's fonts
485
+ (Inter, Inter Tight, JetBrains Mono, Space Mono, Fredoka, VT323; SIL Open Font Licence) load
486
+ from Google Fonts, with system fonts as the fallback offline. Thanks to their authors, and to pyrekordbox in particular for making
487
+ rekordbox's database reachable.