backpack-backcrack 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 (32) hide show
  1. backpack_backcrack-0.2.0/LICENSE +21 -0
  2. backpack_backcrack-0.2.0/PKG-INFO +360 -0
  3. backpack_backcrack-0.2.0/README.md +341 -0
  4. backpack_backcrack-0.2.0/backcrack/__init__.py +0 -0
  5. backpack_backcrack-0.2.0/backcrack/__main__.py +3 -0
  6. backpack_backcrack-0.2.0/backcrack/cli.py +84 -0
  7. backpack_backcrack-0.2.0/backcrack/common.py +99 -0
  8. backpack_backcrack-0.2.0/backcrack/config.py +286 -0
  9. backpack_backcrack-0.2.0/backcrack/deps.py +39 -0
  10. backpack_backcrack-0.2.0/backcrack/disc.py +178 -0
  11. backpack_backcrack-0.2.0/backcrack/diskspeed.py +129 -0
  12. backpack_backcrack-0.2.0/backcrack/encd.py +42 -0
  13. backpack_backcrack-0.2.0/backcrack/encode.py +198 -0
  14. backpack_backcrack-0.2.0/backcrack/namer.py +178 -0
  15. backpack_backcrack-0.2.0/backcrack/pattern.py +135 -0
  16. backpack_backcrack-0.2.0/backcrack/rip.py +266 -0
  17. backpack_backcrack-0.2.0/backcrack/ripd.py +83 -0
  18. backpack_backcrack-0.2.0/backcrack/sort.py +146 -0
  19. backpack_backcrack-0.2.0/backcrack/sortd.py +39 -0
  20. backpack_backcrack-0.2.0/backcrack/status.py +59 -0
  21. backpack_backcrack-0.2.0/backcrack/swapd.py +69 -0
  22. backpack_backcrack-0.2.0/backcrack/titles.py +58 -0
  23. backpack_backcrack-0.2.0/backcrack/watch.py +418 -0
  24. backpack_backcrack-0.2.0/backpack_backcrack.egg-info/PKG-INFO +360 -0
  25. backpack_backcrack-0.2.0/backpack_backcrack.egg-info/SOURCES.txt +30 -0
  26. backpack_backcrack-0.2.0/backpack_backcrack.egg-info/dependency_links.txt +1 -0
  27. backpack_backcrack-0.2.0/backpack_backcrack.egg-info/entry_points.txt +2 -0
  28. backpack_backcrack-0.2.0/backpack_backcrack.egg-info/requires.txt +1 -0
  29. backpack_backcrack-0.2.0/backpack_backcrack.egg-info/top_level.txt +1 -0
  30. backpack_backcrack-0.2.0/pyproject.toml +32 -0
  31. backpack_backcrack-0.2.0/setup.cfg +4 -0
  32. backpack_backcrack-0.2.0/tests/test_disc_toc.py +51 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 RedEraRrow
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,360 @@
1
+ Metadata-Version: 2.4
2
+ Name: backpack-backcrack
3
+ Version: 0.2.0
4
+ Summary: CD/DVD/Blu-ray rip pipeline - MakeMKV/HandBrake for video, cdparanoia for audio, dynamic %token% library patterning.
5
+ License: MIT
6
+ Project-URL: Homepage, https://github.com/RedEraRrow/backcrack
7
+ Project-URL: Issues, https://github.com/RedEraRrow/backcrack/issues
8
+ Classifier: Environment :: Console :: Curses
9
+ Classifier: Topic :: Multimedia :: Sound/Audio :: CD Audio :: CD Ripping
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: MacOS
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: backpack-backbone<0.3,>=0.2
18
+ Dynamic: license-file
19
+
20
+ # backcrack
21
+
22
+ Insert discs, get pinged, swap discs. Encoding happens on its own in the
23
+ background. Works on CDs, DVDs, and Blu-rays - the disc in the drive tells
24
+ the pipeline which path to take.
25
+
26
+ Python, stdlib only apart from `backbone`, the library the back* tools share
27
+ (`watch`'s screen, process and notification helpers). Requires Python 3.10+
28
+ (`python3 --version` to check).
29
+
30
+ ## Install
31
+
32
+ macOS only (it drives the drives through diskutil and drutil):
33
+
34
+ brew install pipx
35
+ pipx install backpack-backcrack
36
+ backcrack doctor
37
+
38
+ `backcrack doctor` lists the rippers and encoders it drives, found or how to
39
+ install each. For everything: `brew install --cask makemkv` and
40
+ `brew install handbrake cdparanoia flac cd-discid`. Paths it can't find
41
+ itself go in `settings.env` (`MKVCON`, `HBCLI`, …).
42
+
43
+ Every tool is a subcommand of the one command, `backcrack` (`backcrack
44
+ watch`, `backcrack status`), so it never takes a common word like `watch`
45
+ on your PATH.
46
+
47
+ To work on it from a checkout, install backbone's checkout and this one
48
+ editable (backbone first), so edits take effect with no reinstall:
49
+
50
+ pip3 install -e ../backbone -e .
51
+
52
+ Every command also runs from the checkout as `python3 -m backcrack <command>`.
53
+
54
+ ## Before the first disc
55
+
56
+ `LIBRARY` is the folder finished discs land in, `~/Media/rips` unless you
57
+ set it: from `watch`'s settings screen (`s`), in `settings.env`, or as an
58
+ environment variable (see [Settings](#settings)).
59
+
60
+ For a push on every eject, set `NTFY_TOPIC` to a topic name of your own, and
61
+ `NTFY_SERVER` too if you don't use the public `https://ntfy.sh`. Anyone who
62
+ knows a topic name can read it on the public server, so pick one nobody will
63
+ guess. With `NTFY_TOPIC` unset, nothing is sent.
64
+
65
+ ## Morning start
66
+
67
+ One command:
68
+
69
+ backcrack
70
+
71
+ starts `ripd` (the ripper), `encd` (the encoder) and `sortd` (which files
72
+ discs out of `UNSORTED/`) in the background, then opens `watch`, the live
73
+ view. Any of them already running is left alone. Their output goes to
74
+ `$LIBRARY/.ripstate/<name>.out`. `LAUNCH_RIPD`, `LAUNCH_ENCD`,
75
+ `LAUNCH_SORTD` and `LAUNCH_WATCH` (all on by default) turn each part off, to
76
+ run it by hand in its own tab instead:
77
+
78
+ backcrack ripd
79
+ backcrack encd # start it once and leave it; idle is normal
80
+ backcrack sortd
81
+
82
+ Then load both drives. Every eject sends an ntfy push. Insert the next discs.
83
+
84
+ Check progress any time:
85
+
86
+ backcrack status
87
+ backcrack watch # live view
88
+
89
+ In `watch`, `q` quits (and offers to stop ripd, encd and sortd with it) and
90
+ `s` opens the settings screen - see [Settings](#settings). Both keys, and those of every
91
+ list, can be changed under Key bindings on that screen.
92
+
93
+ ## What it detects
94
+
95
+ - **DVD / Blu-ray** - MakeMKV rips each title to a lossless MKV, HandBrake
96
+ encodes the ones matching a configured duration class - see
97
+ `DURATION_CLASSES` below.
98
+ - **Audio CD** - cdparanoia extracts each track losslessly
99
+ (`track01.cdda.wav`...), then each is compressed to `AUDIO_FORMAT` (flac
100
+ by default) as `track01.flac`...
101
+
102
+ Both land in the same shape: `source/` (lossless rip) and `encoded/`
103
+ (compressed, ready to use); video also gets `extras/` for anything shorter
104
+ than the main window.
105
+
106
+ A video disc is known by its volume label. An audio CD has no useful label,
107
+ so it is named `AudioCD-<n>tracks-<id>`, where the id is cd-discid's disc id
108
+ (or, without cd-discid, a hash of cdparanoia's track table). That name is
109
+ what shows in the log and in `UNSORTED/`, and what a `labels.map` line for
110
+ the CD has to use.
111
+
112
+ ### Rip mode
113
+
114
+ `RIP_MODE` picks how a video disc is ripped:
115
+
116
+ - `titles` (default): MakeMKV rips each title to its own MKV in `source/`.
117
+ Works for DVD and Blu-ray. A rip whose MakeMKV log reports "N titles
118
+ saved, M failed" is marked failed even when makemkvcon exits 0.
119
+ - `video_ts`: a full decrypted backup of the disc, menus included, written
120
+ to `VIDEO_TS/` instead of `source/`. DVD only. The partial-rip check
121
+ above doesn't apply; a rip counts as done when makemkvcon exits 0 and
122
+ `VIDEO_TS/` exists.
123
+
124
+ ## Layout produced
125
+
126
+ $LIBRARY/<pattern-resolved path>/source/... lossless rip (VIDEO_TS/ in video_ts mode)
127
+ $LIBRARY/<pattern-resolved path>/encoded/... compressed, ready to use (default "main" class)
128
+ $LIBRARY/<pattern-resolved path>/extras/... video only (default "extra" class)
129
+ $LIBRARY/.ripstate/ logs, queue, progress
130
+
131
+ For video, `encoded` and `extras` are just the stock `DURATION_CLASSES`
132
+ folder names; a class's `folder` field is whatever you set it to. Audio
133
+ always goes to `encoded/`.
134
+
135
+ The path itself is built from `PATTERN_VIDEO` / `PATTERN_AUDIO` by
136
+ substituting %tokens%. Default is `Season %season%/Disc %disc%` for video
137
+ and `%artist%/%album%` for audio; change these for a movie shelf, a mixed
138
+ CD/DVD pile, whatever you're ripping this run. See `docs/pattern-tokens.md`.
139
+
140
+ Discs whose tokens can't be resolved go to `UNSORTED/<label>/`, and you get
141
+ a high-priority push. Add a line to `labels.map` (in your config directory,
142
+ see [Settings](#settings)) and, with `sortd` running, it files itself within
143
+ a few seconds.
144
+
145
+ ## Before trusting a whole shelf of discs
146
+
147
+ Once the first disc of a kind has ripped, check what HandBrake saw:
148
+
149
+ backcrack titles "$LIBRARY/Season 1/Disc 1"
150
+
151
+ Every title in your target runtime should say `main`; menus, featurettes,
152
+ and any "play all" duplicate should say `extra` or `skip`. Adjust
153
+ `DURATION_CLASSES` if not - a 22-minute sitcom and a 150-minute film need
154
+ very different windows.
155
+
156
+ `DURATION_CLASSES` is a comma-separated list of
157
+ `name:min-max:folder:quality[:dedup]` entries, e.g. the default:
158
+
159
+ DURATION_CLASSES=extra:180-1199:extras:22,main:1200-10800:encoded:20
160
+
161
+ A title's duration must fall in a class's `min-max` (seconds) to match;
162
+ first match wins, and anything matching none of them isn't encoded at all.
163
+ `folder` is where it lands under a disc's destination, `quality` is
164
+ HandBrake's `--quality` (RF) for that class, and the optional `dedup` flag
165
+ drops a second title with that exact duration.
166
+
167
+ `dedup` is off by default, and worth turning on only for a disc that
168
+ really does list the same film twice ("Play Movie" plus a separate menu
169
+ entry of identical content). It is wrong for TV: episodes on one disc
170
+ naturally cluster within seconds of each other - four real titles at 2530,
171
+ 2530, 2537 and 2533 seconds, two of them bit-for-bit different files - so
172
+ matching on duration alone silently drops a distinct episode. Check your own
173
+ discs before setting it. Add as many classes as you want: a `commentary` or
174
+ `featurette` tier with its own folder and quality, tighter windows for a
175
+ mixed sitcom/movie shelf, whatever your discs need.
176
+
177
+ `MIN_TITLE_S` is the separate rip-time floor (MakeMKV's `--minlength`) below
178
+ which a title is never even ripped; it defaults to the shortest configured
179
+ class's minimum, so set it explicitly only if you want to rip shorter junk
180
+ than you keep.
181
+
182
+ Then watch one encoded file and scrub a fast camera move frame by frame. If
183
+ you see combing, set `DEINTERLACE_ARGS` to HandBrake's deinterlace flags,
184
+ e.g. `DEINTERLACE_ARGS=--comb-detect --decomb`. Leave it empty for
185
+ progressive sources.
186
+
187
+ For audio, `cdparanoia -Q -d <device>` lists a disc's tracks directly if you
188
+ want to sanity-check before ripping.
189
+
190
+ ## Encoding throughput
191
+
192
+ `ENCODE_JOBS` is how many HandBrake jobs run at once, `ENCODER_PRESET` is the
193
+ x264 preset, and `ENCODE_THREADS` caps the threads any one job may use.
194
+
195
+ The defaults (4 jobs, `fast`, threads capped at cores/jobs) were measured on
196
+ an M1 Pro with 8 performance cores: `medium` saturates it at 2 concurrent
197
+ jobs, while `fast` keeps scaling through 4, for roughly 33% more aggregate
198
+ throughput at about 3.6% bigger files at the same RF. Those numbers are
199
+ specific to that CPU and preset. Re-benchmark if you change any of it: encode
200
+ one short clip alone, then N of them at once, and compare the wall time.
201
+
202
+ The thread cap matters more than it looks. Jobs in a batch don't finish
203
+ together - a 168-minute title far outlasts a 42-minute one - so a job can end
204
+ up running alone, and an uncapped x264 then takes every core it can see. That
205
+ starved two concurrent MakeMKV rips of scheduling time for over ten minutes
206
+ (both sat at about 3% CPU with nothing written). The cap keeps a fixed
207
+ ceiling however many siblings are still going, so ripping always has room.
208
+
209
+ ## Resuming
210
+
211
+ Everything is resumable. Stop any daemon with Ctrl-C whenever you like.
212
+ (`watch`'s live view is the exception - Ctrl-C is disabled there; press `q`
213
+ and it'll ask whether to also stop the daemons.)
214
+
215
+ - A disc already ripped is ejected immediately instead of redone.
216
+ - A failed rip is left in the drive and ripped again, up to `MAX_RETRIES`
217
+ attempts in all (3 by default). After the last one it is ejected and
218
+ `$LIBRARY/.ripstate/gaveup-<label>` is written; re-inserting it then just
219
+ ejects it again. To try it again, delete that file and re-insert the
220
+ disc: it gets a fresh `MAX_RETRIES` attempts.
221
+ - A file already encoded is skipped. An encode is written as
222
+ `<name>.part.mkv` and renamed when HandBrake finishes, so an interrupted
223
+ one is redone rather than kept half-written.
224
+
225
+ Before finishing, check for failures:
226
+
227
+ grep FAIL "$LIBRARY/.ripstate/rip.log" "$LIBRARY/.ripstate/encode.log"
228
+ grep -i WARN "$LIBRARY/.ripstate/rip.log"
229
+
230
+ `WARN` lines are worth reading. A disc routing to a folder that already holds
231
+ finished output is ripped alongside it as `<name> (<label>)/` rather than over
232
+ it, and says so there - reconcile those by hand.
233
+
234
+ Every rip also keeps MakeMKV's own output at
235
+ `$LIBRARY/.ripstate/logs/<label>.mkv.log`, and every encode HandBrake's at
236
+ `$LIBRARY/.ripstate/logs/<file>.hb.log`. That is where a read error, a
237
+ retried sector or a title MakeMKV gave up on shows.
238
+
239
+ ## Upgrading ripd mid-run
240
+
241
+ `swapd` swaps in a new `ripd.py` without interrupting a rip: save the new
242
+ version as `backcrack/ripd.py.new` beside `backcrack/ripd.py` (in a checkout) and run
243
+ `backcrack swapd`. It waits until no
244
+ drive is ripping (giving up after 4 hours), swaps the file in, restarts
245
+ `ripd` and exits. `swap.log` in `.ripstate` records what it did.
246
+
247
+ ## Settings
248
+
249
+ Each setting is read, in order, from an environment variable, then
250
+ `settings.env` in your config directory, then the default in
251
+ `backcrack/config.py`. So a one-off `LIBRARY=... ripd` overrides
252
+ everything, and `settings.env` holds what you want every time.
253
+
254
+ `s` in `watch` opens a settings screen over most of them and writes your
255
+ changes to `settings.env`. The tool paths below aren't on it; set those in
256
+ `settings.env` or the environment. The daemons read their settings at
257
+ startup, so restart `ripd`, `encd` or `sortd` to pick a change up.
258
+
259
+ The ones not covered elsewhere in this README:
260
+
261
+ | Setting | Default | What it does |
262
+ |---|---|---|
263
+ | `MKVCON`, `HBCLI`, `CDPARANOIA`, `CD_DISCID`, `FLAC`, `FFMPEG` | found on PATH (MakeMKV at `/Applications/MakeMKV.app`) | tool paths |
264
+ | `NOTIFY_ENCODES` | `0` | also push when a disc finishes encoding |
265
+ | `ACCENT` | `green` | the accent colour: `green`, `red`, `yellow`, `blue`, `magenta`, `cyan` (your terminal's own), `amber`, `coral`, `rose`, `lavender`, `sky`, `mint`, or `#RRGGBB` |
266
+ | `WATCH_INTERVAL` | `1` | seconds between `watch` refreshes |
267
+ | `SORT_INTERVAL` | `5` | seconds between `sortd` passes |
268
+ | `TOTAL_DISCS` | `0` | discs in this run; shows a progress bar and ETA in `watch` (0 hides them) |
269
+ | `WINDOW`, `ACTIVE_S`, `FALLBACK_KB` | `20`, `90`, `7340032` | `watch`: seconds behind its MB/s figure, how long an idle rip stays listed, disc size (KB) assumed until the real one is known |
270
+ | `READ_MB`, `SKIP_MB`, `STALL_S`, `DISC_BYTES`, `RESULTS` | `600`, `1000`, `60`, `7000000000`, `~/diskspeed.txt` | `diskspeed`: MB read per drive, MB skipped first, seconds without progress before giving up, disc size behind its minutes-per-disc estimate, results file |
271
+
272
+ An older `INTERVAL` still works as a fallback for both `WATCH_INTERVAL` and
273
+ `SORT_INTERVAL`.
274
+
275
+ `ONESHOT=1 watch` (print one frame and exit), `ONCE=1 sortd` (one pass) and
276
+ `DRYRUN=1 sortd` (say what it would do, change nothing) are per-run switches,
277
+ read from the environment only.
278
+
279
+ ### Your files live outside the checkout
280
+
281
+ Everything personal (your saved settings, your disc overrides, your episode
282
+ titles) lives in a config directory, not in this repo. The checkout stays
283
+ code-only, and nothing of yours needs gitignoring or risks being committed.
284
+
285
+ | File | What it is |
286
+ |---|---|
287
+ | `settings.env` | saved settings, one `NAME=value` per line; the `watch` settings screen writes it |
288
+ | `labels.map` | manual disc → %token% overrides |
289
+ | `episodes.map` | per-season episode titles, for `namer` |
290
+
291
+ The directory is `$BACKCRACK_CONFIG_DIR` if set, else
292
+ `$XDG_CONFIG_HOME/backcrack`, else `~/.config/backcrack`. It is created on
293
+ first run. Point `BACKCRACK_CONFIG_DIR` somewhere else to keep separate sets
294
+ of overrides for separate shelves.
295
+
296
+ The same screen has an entry for adding a `labels.map` line, which is the
297
+ manual escape hatch for a disc whose %tokens% couldn't be resolved - pick it
298
+ out of `UNSORTED/`, fill in the tokens the active pattern needs, and `sortd`
299
+ files it within a few seconds.
300
+
301
+ ## Finishing a season
302
+
303
+ Once a season's discs are all ripped and encoded, `namer` lays them out the
304
+ way Jellyfin and Kodi expect:
305
+
306
+ backcrack namer every season episodes.map covers
307
+ backcrack namer "Season 4" just one
308
+
309
+ Each disc's encoded titles are renamed to `SxxExx Title.mkv` using the titles
310
+ in `episodes.map`, moved up into the season folder itself, and every disc's
311
+ extras are merged into one season-level `featurettes/` - a recognised extra
312
+ type, which a bare `extras/` is not.
313
+
314
+ **namer then deletes each disc's `source/` folder, the lossless rip**, once
315
+ every encoded episode from that disc has moved into the season. A disc
316
+ whose episodes didn't all move (a name collision, or a disc with no
317
+ encoded episodes) keeps its `source/`, and namer says which it kept and
318
+ which it deleted. Empty disc folders are removed; anything it doesn't
319
+ recognise is left where it is.
320
+
321
+ `episodes.map` lives in your config directory (see [Settings](#settings)) and
322
+ is one block per season: a `# Season N` header, then one title per line in
323
+ broadcast order. Renaming assumes disc and title order matches
324
+ broadcast order, which is the normal convention for a season box set.
325
+
326
+ A season is refused outright, with nothing touched, if its encoded-file
327
+ count doesn't exactly match its title count, or if any encoded file is empty
328
+ or still being written. A mismatch means a disc is still ripping, a title
329
+ never encoded, or one was wrongly deduped, and guessing would mislabel every
330
+ episode after the gap.
331
+
332
+ namer only understands the default layout: season folders named
333
+ `Season N` directly under `LIBRARY`, holding one folder per disc (the
334
+ default `PATTERN_VIDEO`, `Season %season%/Disc %disc%`), with episodes in
335
+ `encoded/` and extras in `extras/` (the default `DURATION_CLASSES` folder
336
+ names). With another pattern or other folder names, don't use it.
337
+
338
+ ## Tools it needs
339
+
340
+ - **MakeMKV** (`makemkvcon`) - for DVD/Blu-ray. `brew install --cask makemkv`.
341
+ - **HandBrakeCLI** - to encode video. `brew install handbrake`.
342
+ - **cdparanoia** - for audio CDs. `brew install cdparanoia`.
343
+ - **flac** (or ffmpeg) - to compress ripped audio. `brew install flac`.
344
+ With neither, tracks are copied to `encoded/` as `.wav`.
345
+ - **cd-discid** - optional, for automatic artist/album lookup on audio CDs.
346
+ `brew install cd-discid`. Without it, every CD goes to `UNSORTED/` until
347
+ you add a `labels.map` line.
348
+
349
+ `backcrack doctor` checks all of them. A missing tool also gets a warning at
350
+ startup rather than a silent failure: `ripd` warns about makemkvcon,
351
+ cdparanoia and cd-discid, and `encd` about HandBrakeCLI and flac/ffmpeg. The
352
+ daemon still runs; that disc kind just won't rip or encode until the tool is
353
+ installed.
354
+
355
+ ## Storage
356
+
357
+ Video eats far more space than audio. `namer` deletes a finished TV
358
+ season's `source/` folders for you (see above). For anything `namer` doesn't
359
+ handle (audio, films, another pattern), delete a disc's `source/` by hand
360
+ once you've checked its encoded output.