abs-organize 0.1.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.
- abs_organize-0.1.0/LICENSE +21 -0
- abs_organize-0.1.0/PKG-INFO +224 -0
- abs_organize-0.1.0/README.md +193 -0
- abs_organize-0.1.0/pyproject.toml +55 -0
- abs_organize-0.1.0/setup.cfg +4 -0
- abs_organize-0.1.0/src/abs_organize/__init__.py +3 -0
- abs_organize-0.1.0/src/abs_organize/batch.py +277 -0
- abs_organize-0.1.0/src/abs_organize/cli.py +339 -0
- abs_organize-0.1.0/src/abs_organize/config.py +125 -0
- abs_organize-0.1.0/src/abs_organize/covers.py +144 -0
- abs_organize-0.1.0/src/abs_organize/discovery.py +326 -0
- abs_organize-0.1.0/src/abs_organize/heuristics.py +66 -0
- abs_organize-0.1.0/src/abs_organize/metadata.py +534 -0
- abs_organize-0.1.0/src/abs_organize/naming.py +120 -0
- abs_organize-0.1.0/src/abs_organize/opf.py +159 -0
- abs_organize-0.1.0/src/abs_organize/organize.py +370 -0
- abs_organize-0.1.0/src/abs_organize.egg-info/PKG-INFO +224 -0
- abs_organize-0.1.0/src/abs_organize.egg-info/SOURCES.txt +33 -0
- abs_organize-0.1.0/src/abs_organize.egg-info/dependency_links.txt +1 -0
- abs_organize-0.1.0/src/abs_organize.egg-info/entry_points.txt +2 -0
- abs_organize-0.1.0/src/abs_organize.egg-info/requires.txt +5 -0
- abs_organize-0.1.0/src/abs_organize.egg-info/top_level.txt +1 -0
- abs_organize-0.1.0/tests/test_cli_batch.py +175 -0
- abs_organize-0.1.0/tests/test_cli_config.py +158 -0
- abs_organize-0.1.0/tests/test_cli_json.py +143 -0
- abs_organize-0.1.0/tests/test_config.py +222 -0
- abs_organize-0.1.0/tests/test_discovery.py +306 -0
- abs_organize-0.1.0/tests/test_heuristics.py +87 -0
- abs_organize-0.1.0/tests/test_metadata.py +92 -0
- abs_organize-0.1.0/tests/test_metadata_gap_fill.py +108 -0
- abs_organize-0.1.0/tests/test_metadata_guess.py +59 -0
- abs_organize-0.1.0/tests/test_metadata_majority.py +93 -0
- abs_organize-0.1.0/tests/test_naming.py +117 -0
- abs_organize-0.1.0/tests/test_narrator_normalize.py +40 -0
- abs_organize-0.1.0/tests/test_organize_integration.py +1332 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Trevor Davies
|
|
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,224 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: abs-organize
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Organize downloaded audiobooks into Audiobookshelf library layout
|
|
5
|
+
Author-email: Trevor Davies <me@trevordavies095.com>
|
|
6
|
+
Maintainer-email: Trevor Davies <me@trevordavies095.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/trevordavies095/abs-organize
|
|
9
|
+
Project-URL: Repository, https://github.com/trevordavies095/abs-organize
|
|
10
|
+
Project-URL: Issues, https://github.com/trevordavies095/abs-organize/issues
|
|
11
|
+
Project-URL: Documentation, https://github.com/trevordavies095/abs-organize#readme
|
|
12
|
+
Keywords: audiobooks,audiobookshelf,cli,metadata,library
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
22
|
+
Classifier: Topic :: Utilities
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: mutagen>=1.47
|
|
27
|
+
Requires-Dist: Pillow>=10
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# abs-organize
|
|
33
|
+
|
|
34
|
+
CLI to place downloaded audiobooks into an [Audiobookshelf](https://www.audiobookshelf.org/) library layout from embedded tags (and optional folder-name guesses). **Copy** is the default; use **`--move`** to clear the inbox after a successful run.
|
|
35
|
+
|
|
36
|
+
**Layout:** `{library}/{Author}/[{Series}/]{TitleFolder}/`
|
|
37
|
+
|
|
38
|
+
**Requirements:** Python 3.11+
|
|
39
|
+
|
|
40
|
+
## Quick start
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install abs-organize
|
|
44
|
+
|
|
45
|
+
# One-off (no config file)
|
|
46
|
+
abs-organize ~/Downloads/book.m4b --library ~/Audiobooks --dry-run
|
|
47
|
+
abs-organize ~/Downloads/book.m4b --library ~/Audiobooks
|
|
48
|
+
|
|
49
|
+
# With config (see Configuration)
|
|
50
|
+
abs-organize ~/Downloads/inbox/MyBook.m4b
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Install
|
|
54
|
+
|
|
55
|
+
From PyPI (recommended):
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install abs-organize
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
From a clone:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install -e .
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
For development and tests, see [Development](#development).
|
|
68
|
+
|
|
69
|
+
## Usage
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
abs-organize INPUT [options]
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**INPUT** — one audio file (`.mp3`, `.m4b`, `.m4a`, `.flac`, `.ogg`) or a folder of tracks.
|
|
76
|
+
|
|
77
|
+
| Option | Purpose |
|
|
78
|
+
|--------|---------|
|
|
79
|
+
| `--library PATH` | Library root for this run (overrides config and env) |
|
|
80
|
+
| `--profile NAME` | Named `[libraries.*]` profile (default profile when omitted) |
|
|
81
|
+
| `--dry-run` | Show library, destination, and planned ops; no writes |
|
|
82
|
+
| `--move` | Move into the library instead of copy (rename on same FS) |
|
|
83
|
+
| `--replace` | Delete existing destination title folder, then organize |
|
|
84
|
+
| `--allow-guess` | Guess author/title from folder or file name when tags are missing |
|
|
85
|
+
| `--batch` | Organize every detected book under INPUT (multi-book inbox) |
|
|
86
|
+
| `--continue-on-error` | With `--batch`, keep going after a failure (apply runs only) |
|
|
87
|
+
| `--json` | Success payload on stdout (scripting) |
|
|
88
|
+
| `-v`, `--verbose` | Path sanitization details on stderr |
|
|
89
|
+
|
|
90
|
+
**Metadata overrides** (single-book runs): `--author`, `--title`, `--year`, `--series`, `--sequence`, `--narrator`. With **`--batch`**, `--series`, `--narrator`, and `--year` may gap-fill empty fields per book; `--author`, `--title`, and `--sequence` are rejected.
|
|
91
|
+
|
|
92
|
+
### Preview, copy, and move
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
abs-organize ~/Downloads/inbox/SomeBook --dry-run --library ~/Audiobooks
|
|
96
|
+
abs-organize ~/Downloads/inbox/SomeBook --library ~/Audiobooks
|
|
97
|
+
abs-organize ~/Downloads/inbox/SomeBook --library ~/Audiobooks --move
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`--dry-run` uses the same validation as a real run and prints warnings to stderr, but does not create library paths or transfer files.
|
|
101
|
+
|
|
102
|
+
### Batch inbox
|
|
103
|
+
|
|
104
|
+
If INPUT contains multiple book roots (e.g. several `.m4b` siblings), a plain run fails with a candidate list. Use **`--batch`** to organize all detected books:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
abs-organize ~/Downloads/inbox --batch --library ~/Audiobooks --dry-run
|
|
108
|
+
abs-organize ~/Downloads/inbox --batch --library ~/Audiobooks --move
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Dry-run always reports every book. On apply, batch stops at the first failure unless **`--continue-on-error`** is set.
|
|
112
|
+
|
|
113
|
+
**Discovery (summary):** each `.m4b`/`.m4a` sibling is its own book; `.mp3`/`.flac`/`.ogg` siblings in one folder are one book; `Disc`/`CD`/`Disk` subfolders roll up to one book at the parent.
|
|
114
|
+
|
|
115
|
+
### Metadata and guessing
|
|
116
|
+
|
|
117
|
+
Tags are read with [Mutagen](https://mutagen.readthedocs.io/):
|
|
118
|
+
|
|
119
|
+
| Folder segment | Tags |
|
|
120
|
+
|----------------|------|
|
|
121
|
+
| Author | `albumartist` or `artist` |
|
|
122
|
+
| Title folder | `album` or `title` (+ optional `subtitle` via config) |
|
|
123
|
+
| Series | `grouping`; sequence/year/narrator from tags, movement atoms (`.m4b`/`.m4a`), or OPF when present |
|
|
124
|
+
|
|
125
|
+
Missing **author** or **title** tags exit with an error unless **`--allow-guess`** is set. Guesses use patterns such as `Author - Title` or `Author - Title (YYYY)` on the book folder or file stem; stderr marks them `(confidence: low)`. CLI overrides always win.
|
|
126
|
+
|
|
127
|
+
**Example (series layout):**
|
|
128
|
+
|
|
129
|
+
```text
|
|
130
|
+
{library}/Terry Goodkind/Sword of Truth/Vol 1 - 1994 - Wizards First Rule {Sam Tsoutsouvas}/book.m4b
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Sidecars (`desc.txt`, `reader.txt`, cover images) are copied when present.
|
|
134
|
+
|
|
135
|
+
## Configuration
|
|
136
|
+
|
|
137
|
+
File: `~/.config/abs-organize/config.toml`
|
|
138
|
+
|
|
139
|
+
```toml
|
|
140
|
+
include_subtitle_in_folder = false
|
|
141
|
+
|
|
142
|
+
[libraries.default]
|
|
143
|
+
path = "/Users/you/Audiobooks"
|
|
144
|
+
|
|
145
|
+
[libraries.fiction]
|
|
146
|
+
path = "/Users/you/Audiobooks/Fiction"
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
- **`[libraries.default]`** is required when you omit `--library`.
|
|
150
|
+
- `include_subtitle_in_folder` — append ` - {subtitle}` to the title folder name.
|
|
151
|
+
|
|
152
|
+
**Library path precedence**
|
|
153
|
+
|
|
154
|
+
| Priority | Source |
|
|
155
|
+
|----------|--------|
|
|
156
|
+
| 1 | `--library PATH` |
|
|
157
|
+
| 2 | `ABS_ORGANIZE_LIBRARY` (only when `--profile` is omitted) |
|
|
158
|
+
| 3 | `[libraries.{profile}].path` when `--profile NAME` is set |
|
|
159
|
+
| 4 | `[libraries.default].path` |
|
|
160
|
+
|
|
161
|
+
## Scripting (`--json`)
|
|
162
|
+
|
|
163
|
+
On success, stdout is JSON; errors stay on stderr (plain text). Warnings are in the JSON payload, not duplicated on stderr.
|
|
164
|
+
|
|
165
|
+
**Single book:**
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"destination": "/Users/you/Audiobooks/Jane Author/Book Title/",
|
|
170
|
+
"files": ["book.mp3"],
|
|
171
|
+
"warnings": []
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Batch:**
|
|
176
|
+
|
|
177
|
+
```json
|
|
178
|
+
{
|
|
179
|
+
"books": [
|
|
180
|
+
{
|
|
181
|
+
"source": "/inbox/Book A/",
|
|
182
|
+
"ok": true,
|
|
183
|
+
"destination": "/Audiobooks/Author/Title/",
|
|
184
|
+
"files": ["book.m4b"],
|
|
185
|
+
"warnings": []
|
|
186
|
+
}
|
|
187
|
+
],
|
|
188
|
+
"summary": { "ok": 1, "failed": 0 }
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Unknown top-level keys may be added later; ignore fields you do not need.
|
|
193
|
+
|
|
194
|
+
## Exit codes
|
|
195
|
+
|
|
196
|
+
| Code | Meaning |
|
|
197
|
+
|------|---------|
|
|
198
|
+
| 0 | Success |
|
|
199
|
+
| 1 | User or metadata error (missing tags, invalid paths, config/profile errors) |
|
|
200
|
+
| 2 | I/O error (copy, move, or filesystem failure) |
|
|
201
|
+
|
|
202
|
+
Batch: `0` only if every book succeeded; partial failure uses `1` or `2` if any book hit I/O errors.
|
|
203
|
+
|
|
204
|
+
## Development
|
|
205
|
+
|
|
206
|
+
Release policy: see [`docs/RELEASE.md`](docs/RELEASE.md).
|
|
207
|
+
|
|
208
|
+
**CI:** GitHub Actions runs `pytest` on every push and pull request (`.github/workflows/ci.yml`). Require the **CI** status check to pass before merging to `main` (**Settings → Branches → Branch protection rules**).
|
|
209
|
+
|
|
210
|
+
| Path | Role |
|
|
211
|
+
|------|------|
|
|
212
|
+
| `src/abs_organize/cli.py` | Argument parsing and entry point |
|
|
213
|
+
| `src/abs_organize/organize.py` | Single-book copy/move pipeline |
|
|
214
|
+
| `src/abs_organize/batch.py` | Multi-book inbox orchestration |
|
|
215
|
+
| `src/abs_organize/discovery.py` | Book-root detection |
|
|
216
|
+
| `src/abs_organize/metadata.py` | Tag read, validation, overrides |
|
|
217
|
+
| `src/abs_organize/naming.py` | ABS-style path segments |
|
|
218
|
+
| `tests/` | Pytest suite (`test_data/` for fixtures) |
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
pip install -e ".[dev]"
|
|
222
|
+
pytest
|
|
223
|
+
abs-organize --help
|
|
224
|
+
```
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# abs-organize
|
|
2
|
+
|
|
3
|
+
CLI to place downloaded audiobooks into an [Audiobookshelf](https://www.audiobookshelf.org/) library layout from embedded tags (and optional folder-name guesses). **Copy** is the default; use **`--move`** to clear the inbox after a successful run.
|
|
4
|
+
|
|
5
|
+
**Layout:** `{library}/{Author}/[{Series}/]{TitleFolder}/`
|
|
6
|
+
|
|
7
|
+
**Requirements:** Python 3.11+
|
|
8
|
+
|
|
9
|
+
## Quick start
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install abs-organize
|
|
13
|
+
|
|
14
|
+
# One-off (no config file)
|
|
15
|
+
abs-organize ~/Downloads/book.m4b --library ~/Audiobooks --dry-run
|
|
16
|
+
abs-organize ~/Downloads/book.m4b --library ~/Audiobooks
|
|
17
|
+
|
|
18
|
+
# With config (see Configuration)
|
|
19
|
+
abs-organize ~/Downloads/inbox/MyBook.m4b
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
From PyPI (recommended):
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install abs-organize
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
From a clone:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install -e .
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
For development and tests, see [Development](#development).
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
abs-organize INPUT [options]
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**INPUT** — one audio file (`.mp3`, `.m4b`, `.m4a`, `.flac`, `.ogg`) or a folder of tracks.
|
|
45
|
+
|
|
46
|
+
| Option | Purpose |
|
|
47
|
+
|--------|---------|
|
|
48
|
+
| `--library PATH` | Library root for this run (overrides config and env) |
|
|
49
|
+
| `--profile NAME` | Named `[libraries.*]` profile (default profile when omitted) |
|
|
50
|
+
| `--dry-run` | Show library, destination, and planned ops; no writes |
|
|
51
|
+
| `--move` | Move into the library instead of copy (rename on same FS) |
|
|
52
|
+
| `--replace` | Delete existing destination title folder, then organize |
|
|
53
|
+
| `--allow-guess` | Guess author/title from folder or file name when tags are missing |
|
|
54
|
+
| `--batch` | Organize every detected book under INPUT (multi-book inbox) |
|
|
55
|
+
| `--continue-on-error` | With `--batch`, keep going after a failure (apply runs only) |
|
|
56
|
+
| `--json` | Success payload on stdout (scripting) |
|
|
57
|
+
| `-v`, `--verbose` | Path sanitization details on stderr |
|
|
58
|
+
|
|
59
|
+
**Metadata overrides** (single-book runs): `--author`, `--title`, `--year`, `--series`, `--sequence`, `--narrator`. With **`--batch`**, `--series`, `--narrator`, and `--year` may gap-fill empty fields per book; `--author`, `--title`, and `--sequence` are rejected.
|
|
60
|
+
|
|
61
|
+
### Preview, copy, and move
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
abs-organize ~/Downloads/inbox/SomeBook --dry-run --library ~/Audiobooks
|
|
65
|
+
abs-organize ~/Downloads/inbox/SomeBook --library ~/Audiobooks
|
|
66
|
+
abs-organize ~/Downloads/inbox/SomeBook --library ~/Audiobooks --move
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`--dry-run` uses the same validation as a real run and prints warnings to stderr, but does not create library paths or transfer files.
|
|
70
|
+
|
|
71
|
+
### Batch inbox
|
|
72
|
+
|
|
73
|
+
If INPUT contains multiple book roots (e.g. several `.m4b` siblings), a plain run fails with a candidate list. Use **`--batch`** to organize all detected books:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
abs-organize ~/Downloads/inbox --batch --library ~/Audiobooks --dry-run
|
|
77
|
+
abs-organize ~/Downloads/inbox --batch --library ~/Audiobooks --move
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Dry-run always reports every book. On apply, batch stops at the first failure unless **`--continue-on-error`** is set.
|
|
81
|
+
|
|
82
|
+
**Discovery (summary):** each `.m4b`/`.m4a` sibling is its own book; `.mp3`/`.flac`/`.ogg` siblings in one folder are one book; `Disc`/`CD`/`Disk` subfolders roll up to one book at the parent.
|
|
83
|
+
|
|
84
|
+
### Metadata and guessing
|
|
85
|
+
|
|
86
|
+
Tags are read with [Mutagen](https://mutagen.readthedocs.io/):
|
|
87
|
+
|
|
88
|
+
| Folder segment | Tags |
|
|
89
|
+
|----------------|------|
|
|
90
|
+
| Author | `albumartist` or `artist` |
|
|
91
|
+
| Title folder | `album` or `title` (+ optional `subtitle` via config) |
|
|
92
|
+
| Series | `grouping`; sequence/year/narrator from tags, movement atoms (`.m4b`/`.m4a`), or OPF when present |
|
|
93
|
+
|
|
94
|
+
Missing **author** or **title** tags exit with an error unless **`--allow-guess`** is set. Guesses use patterns such as `Author - Title` or `Author - Title (YYYY)` on the book folder or file stem; stderr marks them `(confidence: low)`. CLI overrides always win.
|
|
95
|
+
|
|
96
|
+
**Example (series layout):**
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
{library}/Terry Goodkind/Sword of Truth/Vol 1 - 1994 - Wizards First Rule {Sam Tsoutsouvas}/book.m4b
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Sidecars (`desc.txt`, `reader.txt`, cover images) are copied when present.
|
|
103
|
+
|
|
104
|
+
## Configuration
|
|
105
|
+
|
|
106
|
+
File: `~/.config/abs-organize/config.toml`
|
|
107
|
+
|
|
108
|
+
```toml
|
|
109
|
+
include_subtitle_in_folder = false
|
|
110
|
+
|
|
111
|
+
[libraries.default]
|
|
112
|
+
path = "/Users/you/Audiobooks"
|
|
113
|
+
|
|
114
|
+
[libraries.fiction]
|
|
115
|
+
path = "/Users/you/Audiobooks/Fiction"
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
- **`[libraries.default]`** is required when you omit `--library`.
|
|
119
|
+
- `include_subtitle_in_folder` — append ` - {subtitle}` to the title folder name.
|
|
120
|
+
|
|
121
|
+
**Library path precedence**
|
|
122
|
+
|
|
123
|
+
| Priority | Source |
|
|
124
|
+
|----------|--------|
|
|
125
|
+
| 1 | `--library PATH` |
|
|
126
|
+
| 2 | `ABS_ORGANIZE_LIBRARY` (only when `--profile` is omitted) |
|
|
127
|
+
| 3 | `[libraries.{profile}].path` when `--profile NAME` is set |
|
|
128
|
+
| 4 | `[libraries.default].path` |
|
|
129
|
+
|
|
130
|
+
## Scripting (`--json`)
|
|
131
|
+
|
|
132
|
+
On success, stdout is JSON; errors stay on stderr (plain text). Warnings are in the JSON payload, not duplicated on stderr.
|
|
133
|
+
|
|
134
|
+
**Single book:**
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"destination": "/Users/you/Audiobooks/Jane Author/Book Title/",
|
|
139
|
+
"files": ["book.mp3"],
|
|
140
|
+
"warnings": []
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Batch:**
|
|
145
|
+
|
|
146
|
+
```json
|
|
147
|
+
{
|
|
148
|
+
"books": [
|
|
149
|
+
{
|
|
150
|
+
"source": "/inbox/Book A/",
|
|
151
|
+
"ok": true,
|
|
152
|
+
"destination": "/Audiobooks/Author/Title/",
|
|
153
|
+
"files": ["book.m4b"],
|
|
154
|
+
"warnings": []
|
|
155
|
+
}
|
|
156
|
+
],
|
|
157
|
+
"summary": { "ok": 1, "failed": 0 }
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Unknown top-level keys may be added later; ignore fields you do not need.
|
|
162
|
+
|
|
163
|
+
## Exit codes
|
|
164
|
+
|
|
165
|
+
| Code | Meaning |
|
|
166
|
+
|------|---------|
|
|
167
|
+
| 0 | Success |
|
|
168
|
+
| 1 | User or metadata error (missing tags, invalid paths, config/profile errors) |
|
|
169
|
+
| 2 | I/O error (copy, move, or filesystem failure) |
|
|
170
|
+
|
|
171
|
+
Batch: `0` only if every book succeeded; partial failure uses `1` or `2` if any book hit I/O errors.
|
|
172
|
+
|
|
173
|
+
## Development
|
|
174
|
+
|
|
175
|
+
Release policy: see [`docs/RELEASE.md`](docs/RELEASE.md).
|
|
176
|
+
|
|
177
|
+
**CI:** GitHub Actions runs `pytest` on every push and pull request (`.github/workflows/ci.yml`). Require the **CI** status check to pass before merging to `main` (**Settings → Branches → Branch protection rules**).
|
|
178
|
+
|
|
179
|
+
| Path | Role |
|
|
180
|
+
|------|------|
|
|
181
|
+
| `src/abs_organize/cli.py` | Argument parsing and entry point |
|
|
182
|
+
| `src/abs_organize/organize.py` | Single-book copy/move pipeline |
|
|
183
|
+
| `src/abs_organize/batch.py` | Multi-book inbox orchestration |
|
|
184
|
+
| `src/abs_organize/discovery.py` | Book-root detection |
|
|
185
|
+
| `src/abs_organize/metadata.py` | Tag read, validation, overrides |
|
|
186
|
+
| `src/abs_organize/naming.py` | ABS-style path segments |
|
|
187
|
+
| `tests/` | Pytest suite (`test_data/` for fixtures) |
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
pip install -e ".[dev]"
|
|
191
|
+
pytest
|
|
192
|
+
abs-organize --help
|
|
193
|
+
```
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "abs-organize"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Organize downloaded audiobooks into Audiobookshelf library layout"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "Trevor Davies", email = "me@trevordavies095.com" },
|
|
15
|
+
]
|
|
16
|
+
maintainers = [
|
|
17
|
+
{ name = "Trevor Davies", email = "me@trevordavies095.com" },
|
|
18
|
+
]
|
|
19
|
+
keywords = ["audiobooks", "audiobookshelf", "cli", "metadata", "library"]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Development Status :: 4 - Beta",
|
|
22
|
+
"Environment :: Console",
|
|
23
|
+
"Intended Audience :: End Users/Desktop",
|
|
24
|
+
"Operating System :: OS Independent",
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
"Programming Language :: Python :: 3.11",
|
|
27
|
+
"Programming Language :: Python :: 3.12",
|
|
28
|
+
"Programming Language :: Python :: 3.13",
|
|
29
|
+
"Topic :: Multimedia :: Sound/Audio",
|
|
30
|
+
"Topic :: Utilities",
|
|
31
|
+
]
|
|
32
|
+
dependencies = [
|
|
33
|
+
"mutagen>=1.47",
|
|
34
|
+
"Pillow>=10",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/trevordavies095/abs-organize"
|
|
39
|
+
Repository = "https://github.com/trevordavies095/abs-organize"
|
|
40
|
+
Issues = "https://github.com/trevordavies095/abs-organize/issues"
|
|
41
|
+
Documentation = "https://github.com/trevordavies095/abs-organize#readme"
|
|
42
|
+
|
|
43
|
+
[project.optional-dependencies]
|
|
44
|
+
dev = [
|
|
45
|
+
"pytest>=8",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
[project.scripts]
|
|
49
|
+
abs-organize = "abs_organize.cli:main"
|
|
50
|
+
|
|
51
|
+
[tool.setuptools.packages.find]
|
|
52
|
+
where = ["src"]
|
|
53
|
+
|
|
54
|
+
[tool.pytest.ini_options]
|
|
55
|
+
testpaths = ["tests"]
|