booru-dl 2.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.
- booru_dl-2.1.0/LICENSE +21 -0
- booru_dl-2.1.0/PKG-INFO +216 -0
- booru_dl-2.1.0/README.md +181 -0
- booru_dl-2.1.0/bdl/__init__.py +27 -0
- booru_dl-2.1.0/bdl/__main__.py +8 -0
- booru_dl-2.1.0/bdl/blacklist.py +74 -0
- booru_dl-2.1.0/bdl/cli.py +849 -0
- booru_dl-2.1.0/bdl/config.py +187 -0
- booru_dl-2.1.0/bdl/db.py +355 -0
- booru_dl-2.1.0/bdl/downloader.py +913 -0
- booru_dl-2.1.0/bdl/filters.py +184 -0
- booru_dl-2.1.0/bdl/i18n/__init__.py +126 -0
- booru_dl-2.1.0/bdl/i18n/en.json +60 -0
- booru_dl-2.1.0/bdl/i18n/ru.json +60 -0
- booru_dl-2.1.0/bdl/media.py +77 -0
- booru_dl-2.1.0/bdl/meta.py +83 -0
- booru_dl-2.1.0/bdl/models.py +27 -0
- booru_dl-2.1.0/bdl/net/__init__.py +0 -0
- booru_dl-2.1.0/bdl/net/doh.py +103 -0
- booru_dl-2.1.0/bdl/notifier.py +31 -0
- booru_dl-2.1.0/bdl/perceptual.py +50 -0
- booru_dl-2.1.0/bdl/preview.py +232 -0
- booru_dl-2.1.0/bdl/providers/__init__.py +246 -0
- booru_dl-2.1.0/bdl/providers/base.py +232 -0
- booru_dl-2.1.0/bdl/providers/client.py +244 -0
- booru_dl-2.1.0/bdl/tags/__init__.py +0 -0
- booru_dl-2.1.0/bdl/tags/catalog.py +205 -0
- booru_dl-2.1.0/bdl/tags/commands.py +83 -0
- booru_dl-2.1.0/bdl/tags/families.py +126 -0
- booru_dl-2.1.0/bdl/tags/library.py +879 -0
- booru_dl-2.1.0/bdl/tags/store.py +309 -0
- booru_dl-2.1.0/bdl/tags/suggest.py +202 -0
- booru_dl-2.1.0/bdl/tui/__init__.py +12 -0
- booru_dl-2.1.0/bdl/tui/style.py +70 -0
- booru_dl-2.1.0/bdl/tui/wizard.py +453 -0
- booru_dl-2.1.0/booru_dl.egg-info/PKG-INFO +216 -0
- booru_dl-2.1.0/booru_dl.egg-info/SOURCES.txt +43 -0
- booru_dl-2.1.0/booru_dl.egg-info/dependency_links.txt +1 -0
- booru_dl-2.1.0/booru_dl.egg-info/entry_points.txt +2 -0
- booru_dl-2.1.0/booru_dl.egg-info/requires.txt +15 -0
- booru_dl-2.1.0/booru_dl.egg-info/top_level.txt +1 -0
- booru_dl-2.1.0/pyproject.toml +64 -0
- booru_dl-2.1.0/setup.cfg +4 -0
- booru_dl-2.1.0/tests/test_dl.py +1301 -0
- booru_dl-2.1.0/tests/test_tui.py +165 -0
booru_dl-2.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wexqk-boo
|
|
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.
|
booru_dl-2.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: booru-dl
|
|
3
|
+
Version: 2.1.0
|
|
4
|
+
Summary: Async batch downloader and sync tool for booru boards
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/wexqk-boo/Booru-Downloader-py
|
|
7
|
+
Project-URL: Issues, https://github.com/wexqk-boo/Booru-Downloader-py/issues
|
|
8
|
+
Keywords: booru,downloader,gelbooru,rule34,danbooru,asyncio,cli
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
18
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: aiohttp<4,>=3.10
|
|
23
|
+
Requires-Dist: aiofiles>=24.1
|
|
24
|
+
Requires-Dist: aiosqlite>=0.20
|
|
25
|
+
Requires-Dist: imagehash>=4.3
|
|
26
|
+
Requires-Dist: pillow>=10
|
|
27
|
+
Requires-Dist: rapidfuzz>=3
|
|
28
|
+
Requires-Dist: rich>=13
|
|
29
|
+
Requires-Dist: questionary>=2.0
|
|
30
|
+
Requires-Dist: uvloop>=0.21; sys_platform != "win32"
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
33
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# bdl
|
|
37
|
+
|
|
38
|
+
Async batch downloader and sync tool for booru boards (Gelbooru, Rule34,
|
|
39
|
+
Xbooru, Safebooru, Danbooru, Konachan, Yande.re, Tbib, e621, e926).
|
|
40
|
+
|
|
41
|
+
## Features
|
|
42
|
+
|
|
43
|
+
- **Async streaming downloads** — `asyncio` + `aiohttp`, files streamed to disk
|
|
44
|
+
in chunks, concurrency bounded by `asyncio.Semaphore`.
|
|
45
|
+
- **Four-level deduplication** — `uid` in SQLite, the file already present on
|
|
46
|
+
disk, an optional sha256 check, and a perceptual pHash so the same artwork
|
|
47
|
+
reposted to another board under a new id is skipped too. The first three
|
|
48
|
+
levels are free; the pHash has to see the bytes, so a repost is downloaded,
|
|
49
|
+
recognised and then deleted instead of being kept.
|
|
50
|
+
- **Aspect-ratio filtering** — `--wallpapers`, `--avatars`, or a custom
|
|
51
|
+
`--aspect-ratio MIN:MAX`, applied after the API response so nothing extra is
|
|
52
|
+
downloaded. Boards that omit image dimensions are kept by default; pass
|
|
53
|
+
`--aspect-unknown skip` to drop them instead.
|
|
54
|
+
- **Tag-family blacklist** — a root tag pulls in its related sub-tags from
|
|
55
|
+
`~/.config/bdl/blacklist.txt`, plus a per-run `--blacklist` override.
|
|
56
|
+
- **DNS-over-HTTPS and proxy support** — a built-in `DoHResolver` (Cloudflare)
|
|
57
|
+
works around local DNS blocks on image CDNs, and an upstream proxy can be set.
|
|
58
|
+
- **uvloop when available** — the event loop switches to uvloop on every
|
|
59
|
+
platform but Windows, which speeds up a batch of many small transfers.
|
|
60
|
+
- **Typo tolerance** — a tag that is not in the dictionary is matched against
|
|
61
|
+
the popular tags and corrected before the query goes out (`mku` → `miku`).
|
|
62
|
+
- **Image previews** — inside Kitty, finished pictures are drawn once the batch
|
|
63
|
+
ends as a compact gallery of thumbnail rows rather than a column running off
|
|
64
|
+
the screen; `--no-preview` turns them off.
|
|
65
|
+
- **Interactive wizard** — `bdl` on a terminal (or `bdl tui`) walks through the
|
|
66
|
+
download: boards with checkboxes, tags with live tag suggestions, limit, sort
|
|
67
|
+
order, rating, geometry and an optional list of tags to exclude, then prints a
|
|
68
|
+
summary before starting. The interface ships in Russian (default) and English;
|
|
69
|
+
`questionary` supplies the arrow-key menus and `rich` the section headers.
|
|
70
|
+
- **Machine-readable output** — `--json` on `search` and `status`.
|
|
71
|
+
|
|
72
|
+
## Installation
|
|
73
|
+
|
|
74
|
+
Requires Python 3.11+. The repository is **Booru-Downloader-py**; the console
|
|
75
|
+
command it installs is **`bdl`**.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
git clone https://github.com/wexqk-boo/Booru-Downloader-py.git
|
|
79
|
+
cd Booru-Downloader-py
|
|
80
|
+
python3 -m venv .venv
|
|
81
|
+
./.venv/bin/pip install -e . # or: pip install -e '.[dev]' for tests/ruff
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The package installs a single entry point: `bdl`.
|
|
85
|
+
|
|
86
|
+
To put it on `PATH`:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
./install.sh # symlink the entry point into ~/.local/bin
|
|
90
|
+
./install.sh --copy # copy it instead
|
|
91
|
+
./install.sh --remove # uninstall
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Run `bdl` on a terminal to open the wizard. Without a terminal it prints usage
|
|
95
|
+
and exits `2`, so scripts never block on a prompt. If an old install left a
|
|
96
|
+
fish function named `bdl`, it shadows the binary — remove it
|
|
97
|
+
(`rm ~/.config/fish/functions/bdl.fish`) and run `functions -e bdl` once;
|
|
98
|
+
`./install.sh --remove` cleans that up together with the stale helper.
|
|
99
|
+
|
|
100
|
+
`rapidfuzz` powers tag suggestions and typo correction; without it the code
|
|
101
|
+
falls back to the standard-library `difflib`. `imagehash` and `pillow` enable
|
|
102
|
+
perceptual dedup, and `uvloop` speeds up the event loop everywhere but Windows.
|
|
103
|
+
|
|
104
|
+
## Usage
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# Download every source, tag "miku", wallpaper aspect ratio only
|
|
108
|
+
bdl dl -s all -t "miku" --wallpapers
|
|
109
|
+
|
|
110
|
+
# Preview results without downloading
|
|
111
|
+
bdl search -s gelbooru -t "school_uniform" --limit 20
|
|
112
|
+
|
|
113
|
+
# Download statistics from the SQLite state
|
|
114
|
+
bdl status
|
|
115
|
+
|
|
116
|
+
# Daemon-style sync: repeated passes, already-downloaded posts skipped
|
|
117
|
+
bdl sync -s all -t "rating:general" --interval 900
|
|
118
|
+
|
|
119
|
+
# Blacklist management
|
|
120
|
+
bdl blacklist list
|
|
121
|
+
bdl blacklist add loli
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Other subcommands: `tui` (interactive wizard), `prune-stale` (forget DB rows
|
|
125
|
+
whose files are gone), `config` (`get` / `set` / `show`), and `tags` (tag
|
|
126
|
+
suggestions).
|
|
127
|
+
|
|
128
|
+
`prune-stale` only inspects paths under the current output root, so a run
|
|
129
|
+
against a temporary `--out` cannot forget the real library. Use `--all` to
|
|
130
|
+
check every recorded path, or `--out DIR` to pick a different scope.
|
|
131
|
+
|
|
132
|
+
### Wizard
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
bdl tui # or bare `bdl` on a terminal, which reaches the same wizard
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Rating choices are Safe / Suggestive / NSFW / All (`--safe`, `--suggestive`,
|
|
139
|
+
`--nsfw` on the command line), and geometry offers Wallpapers, Avatars, a
|
|
140
|
+
custom `MIN:MAX` range, or no filter. `MIN:MAX` accepts `1.5:1.8`, `1,5:1,8`,
|
|
141
|
+
`1.5/1.8` and width:height shorthands such as `16:9` or `3:4`.
|
|
142
|
+
|
|
143
|
+
The whole wizard is five screens and takes well under a minute: boards, the tag
|
|
144
|
+
query, limit and geometry, an optional line of tags to exclude, then the
|
|
145
|
+
summary. It never asks about concurrency — 8 parallel transfers keep a 100 Mbit
|
|
146
|
+
line busy — and the JSON/sha256/re-download switches stay off unless you pass
|
|
147
|
+
them to `dl` yourself.
|
|
148
|
+
|
|
149
|
+
The wizard asks for the output folder only until one is known: the first answer
|
|
150
|
+
is saved to the config and later runs reuse it silently, unless `BDL_OUT` is
|
|
151
|
+
set, which wins outright and is never written back. The exclusion step is a
|
|
152
|
+
single comma-separated field — a root tag such as `ai` pulls in its whole family,
|
|
153
|
+
an empty answer excludes nothing. Tag fields complete from the local Danbooru
|
|
154
|
+
database as you type, and the run ends with a summary before the final
|
|
155
|
+
confirmation.
|
|
156
|
+
|
|
157
|
+
Common flags for `dl` / `sync`: `--limit`, `--per-page`, `--out`,
|
|
158
|
+
`--concurrency`, `--sidecar`, `--checksum`, `--force`, `--min-score`,
|
|
159
|
+
`--rating-include`, `--max-size-mb`, `--blacklist`, `--no-progress`,
|
|
160
|
+
`--no-preview`, `--no-notify`, `--timeout`.
|
|
161
|
+
|
|
162
|
+
## Directory layout
|
|
163
|
+
|
|
164
|
+
Files are written as `<out_root>/<source>/<bucket>/[videos/]/<filename>`, with
|
|
165
|
+
all sidecar JSON collected in a `json/` folder:
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
<out_root>/<source>/
|
|
169
|
+
├── safe/ # ratings: safe, s, general, g
|
|
170
|
+
│ ├── <file>.jpg
|
|
171
|
+
│ ├── videos/ # mp4, webm, mov, avi, mkv, flv, m4v, wmv
|
|
172
|
+
│ └── json/ # <file>.jpg.json sidecars (with --sidecar)
|
|
173
|
+
├── suggestive/ # ratings: questionable, q, sensitive
|
|
174
|
+
└── nsfw/ # ratings: explicit, e
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
The default `out_root` comes from `BDL_OUT`, then the `out` key in the config
|
|
178
|
+
file, then `~/Pictures/booru`. Unknown or missing ratings land in `nsfw/`.
|
|
179
|
+
|
|
180
|
+
## Configuration
|
|
181
|
+
|
|
182
|
+
- Config file: `~/.config/bdl/config.json`
|
|
183
|
+
- Download state (SQLite): `~/.local/share/bdl/downloads.sqlite3`
|
|
184
|
+
- Tag blacklist: `~/.config/bdl/blacklist.txt`
|
|
185
|
+
- Tag database cache: `~/.cache/bdl/`
|
|
186
|
+
- Log: `~/.local/state/bdl/bdl.log`
|
|
187
|
+
|
|
188
|
+
Environment variables:
|
|
189
|
+
|
|
190
|
+
- `BDL_OUT` — override the output root (`BOORU_DL_OUT` still works).
|
|
191
|
+
- `BDL_LOG_FILE` — duplicate logs to a file (`BOORU_DL_LOG_FILE` still works).
|
|
192
|
+
- `BDL_LANG` — wizard language, `ru` (default) or `en`. Also settable through
|
|
193
|
+
`bdl config set lang en`.
|
|
194
|
+
- `PROXY_URL` — route requests through a proxy.
|
|
195
|
+
|
|
196
|
+
### Board blocks
|
|
197
|
+
|
|
198
|
+
Xbooru's DAPI needs no API key, but Cloudflare fronts the site and sometimes
|
|
199
|
+
challenges an IP with HTTP 403. Requests to Xbooru therefore carry a full set of
|
|
200
|
+
browser headers. If the IP is still challenged, the affected files are counted
|
|
201
|
+
as `blocked` instead of filling the screen with warnings, and the reason is
|
|
202
|
+
reported once in the batch summary at the end. Setting `PROXY_URL` is the usual
|
|
203
|
+
way out.
|
|
204
|
+
- `bdl config set out ~/Pictures/booru` — default output root for every command
|
|
205
|
+
that takes `--out` (`BDL_OUT` still wins over it).
|
|
206
|
+
|
|
207
|
+
## Development
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
./.venv/bin/python -m pytest tests -q # unit tests, no network
|
|
211
|
+
./.venv/bin/ruff check bdl tests
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## License
|
|
215
|
+
|
|
216
|
+
MIT
|
booru_dl-2.1.0/README.md
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# bdl
|
|
2
|
+
|
|
3
|
+
Async batch downloader and sync tool for booru boards (Gelbooru, Rule34,
|
|
4
|
+
Xbooru, Safebooru, Danbooru, Konachan, Yande.re, Tbib, e621, e926).
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
|
|
8
|
+
- **Async streaming downloads** — `asyncio` + `aiohttp`, files streamed to disk
|
|
9
|
+
in chunks, concurrency bounded by `asyncio.Semaphore`.
|
|
10
|
+
- **Four-level deduplication** — `uid` in SQLite, the file already present on
|
|
11
|
+
disk, an optional sha256 check, and a perceptual pHash so the same artwork
|
|
12
|
+
reposted to another board under a new id is skipped too. The first three
|
|
13
|
+
levels are free; the pHash has to see the bytes, so a repost is downloaded,
|
|
14
|
+
recognised and then deleted instead of being kept.
|
|
15
|
+
- **Aspect-ratio filtering** — `--wallpapers`, `--avatars`, or a custom
|
|
16
|
+
`--aspect-ratio MIN:MAX`, applied after the API response so nothing extra is
|
|
17
|
+
downloaded. Boards that omit image dimensions are kept by default; pass
|
|
18
|
+
`--aspect-unknown skip` to drop them instead.
|
|
19
|
+
- **Tag-family blacklist** — a root tag pulls in its related sub-tags from
|
|
20
|
+
`~/.config/bdl/blacklist.txt`, plus a per-run `--blacklist` override.
|
|
21
|
+
- **DNS-over-HTTPS and proxy support** — a built-in `DoHResolver` (Cloudflare)
|
|
22
|
+
works around local DNS blocks on image CDNs, and an upstream proxy can be set.
|
|
23
|
+
- **uvloop when available** — the event loop switches to uvloop on every
|
|
24
|
+
platform but Windows, which speeds up a batch of many small transfers.
|
|
25
|
+
- **Typo tolerance** — a tag that is not in the dictionary is matched against
|
|
26
|
+
the popular tags and corrected before the query goes out (`mku` → `miku`).
|
|
27
|
+
- **Image previews** — inside Kitty, finished pictures are drawn once the batch
|
|
28
|
+
ends as a compact gallery of thumbnail rows rather than a column running off
|
|
29
|
+
the screen; `--no-preview` turns them off.
|
|
30
|
+
- **Interactive wizard** — `bdl` on a terminal (or `bdl tui`) walks through the
|
|
31
|
+
download: boards with checkboxes, tags with live tag suggestions, limit, sort
|
|
32
|
+
order, rating, geometry and an optional list of tags to exclude, then prints a
|
|
33
|
+
summary before starting. The interface ships in Russian (default) and English;
|
|
34
|
+
`questionary` supplies the arrow-key menus and `rich` the section headers.
|
|
35
|
+
- **Machine-readable output** — `--json` on `search` and `status`.
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
Requires Python 3.11+. The repository is **Booru-Downloader-py**; the console
|
|
40
|
+
command it installs is **`bdl`**.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
git clone https://github.com/wexqk-boo/Booru-Downloader-py.git
|
|
44
|
+
cd Booru-Downloader-py
|
|
45
|
+
python3 -m venv .venv
|
|
46
|
+
./.venv/bin/pip install -e . # or: pip install -e '.[dev]' for tests/ruff
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The package installs a single entry point: `bdl`.
|
|
50
|
+
|
|
51
|
+
To put it on `PATH`:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
./install.sh # symlink the entry point into ~/.local/bin
|
|
55
|
+
./install.sh --copy # copy it instead
|
|
56
|
+
./install.sh --remove # uninstall
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Run `bdl` on a terminal to open the wizard. Without a terminal it prints usage
|
|
60
|
+
and exits `2`, so scripts never block on a prompt. If an old install left a
|
|
61
|
+
fish function named `bdl`, it shadows the binary — remove it
|
|
62
|
+
(`rm ~/.config/fish/functions/bdl.fish`) and run `functions -e bdl` once;
|
|
63
|
+
`./install.sh --remove` cleans that up together with the stale helper.
|
|
64
|
+
|
|
65
|
+
`rapidfuzz` powers tag suggestions and typo correction; without it the code
|
|
66
|
+
falls back to the standard-library `difflib`. `imagehash` and `pillow` enable
|
|
67
|
+
perceptual dedup, and `uvloop` speeds up the event loop everywhere but Windows.
|
|
68
|
+
|
|
69
|
+
## Usage
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Download every source, tag "miku", wallpaper aspect ratio only
|
|
73
|
+
bdl dl -s all -t "miku" --wallpapers
|
|
74
|
+
|
|
75
|
+
# Preview results without downloading
|
|
76
|
+
bdl search -s gelbooru -t "school_uniform" --limit 20
|
|
77
|
+
|
|
78
|
+
# Download statistics from the SQLite state
|
|
79
|
+
bdl status
|
|
80
|
+
|
|
81
|
+
# Daemon-style sync: repeated passes, already-downloaded posts skipped
|
|
82
|
+
bdl sync -s all -t "rating:general" --interval 900
|
|
83
|
+
|
|
84
|
+
# Blacklist management
|
|
85
|
+
bdl blacklist list
|
|
86
|
+
bdl blacklist add loli
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Other subcommands: `tui` (interactive wizard), `prune-stale` (forget DB rows
|
|
90
|
+
whose files are gone), `config` (`get` / `set` / `show`), and `tags` (tag
|
|
91
|
+
suggestions).
|
|
92
|
+
|
|
93
|
+
`prune-stale` only inspects paths under the current output root, so a run
|
|
94
|
+
against a temporary `--out` cannot forget the real library. Use `--all` to
|
|
95
|
+
check every recorded path, or `--out DIR` to pick a different scope.
|
|
96
|
+
|
|
97
|
+
### Wizard
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
bdl tui # or bare `bdl` on a terminal, which reaches the same wizard
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Rating choices are Safe / Suggestive / NSFW / All (`--safe`, `--suggestive`,
|
|
104
|
+
`--nsfw` on the command line), and geometry offers Wallpapers, Avatars, a
|
|
105
|
+
custom `MIN:MAX` range, or no filter. `MIN:MAX` accepts `1.5:1.8`, `1,5:1,8`,
|
|
106
|
+
`1.5/1.8` and width:height shorthands such as `16:9` or `3:4`.
|
|
107
|
+
|
|
108
|
+
The whole wizard is five screens and takes well under a minute: boards, the tag
|
|
109
|
+
query, limit and geometry, an optional line of tags to exclude, then the
|
|
110
|
+
summary. It never asks about concurrency — 8 parallel transfers keep a 100 Mbit
|
|
111
|
+
line busy — and the JSON/sha256/re-download switches stay off unless you pass
|
|
112
|
+
them to `dl` yourself.
|
|
113
|
+
|
|
114
|
+
The wizard asks for the output folder only until one is known: the first answer
|
|
115
|
+
is saved to the config and later runs reuse it silently, unless `BDL_OUT` is
|
|
116
|
+
set, which wins outright and is never written back. The exclusion step is a
|
|
117
|
+
single comma-separated field — a root tag such as `ai` pulls in its whole family,
|
|
118
|
+
an empty answer excludes nothing. Tag fields complete from the local Danbooru
|
|
119
|
+
database as you type, and the run ends with a summary before the final
|
|
120
|
+
confirmation.
|
|
121
|
+
|
|
122
|
+
Common flags for `dl` / `sync`: `--limit`, `--per-page`, `--out`,
|
|
123
|
+
`--concurrency`, `--sidecar`, `--checksum`, `--force`, `--min-score`,
|
|
124
|
+
`--rating-include`, `--max-size-mb`, `--blacklist`, `--no-progress`,
|
|
125
|
+
`--no-preview`, `--no-notify`, `--timeout`.
|
|
126
|
+
|
|
127
|
+
## Directory layout
|
|
128
|
+
|
|
129
|
+
Files are written as `<out_root>/<source>/<bucket>/[videos/]/<filename>`, with
|
|
130
|
+
all sidecar JSON collected in a `json/` folder:
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
<out_root>/<source>/
|
|
134
|
+
├── safe/ # ratings: safe, s, general, g
|
|
135
|
+
│ ├── <file>.jpg
|
|
136
|
+
│ ├── videos/ # mp4, webm, mov, avi, mkv, flv, m4v, wmv
|
|
137
|
+
│ └── json/ # <file>.jpg.json sidecars (with --sidecar)
|
|
138
|
+
├── suggestive/ # ratings: questionable, q, sensitive
|
|
139
|
+
└── nsfw/ # ratings: explicit, e
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The default `out_root` comes from `BDL_OUT`, then the `out` key in the config
|
|
143
|
+
file, then `~/Pictures/booru`. Unknown or missing ratings land in `nsfw/`.
|
|
144
|
+
|
|
145
|
+
## Configuration
|
|
146
|
+
|
|
147
|
+
- Config file: `~/.config/bdl/config.json`
|
|
148
|
+
- Download state (SQLite): `~/.local/share/bdl/downloads.sqlite3`
|
|
149
|
+
- Tag blacklist: `~/.config/bdl/blacklist.txt`
|
|
150
|
+
- Tag database cache: `~/.cache/bdl/`
|
|
151
|
+
- Log: `~/.local/state/bdl/bdl.log`
|
|
152
|
+
|
|
153
|
+
Environment variables:
|
|
154
|
+
|
|
155
|
+
- `BDL_OUT` — override the output root (`BOORU_DL_OUT` still works).
|
|
156
|
+
- `BDL_LOG_FILE` — duplicate logs to a file (`BOORU_DL_LOG_FILE` still works).
|
|
157
|
+
- `BDL_LANG` — wizard language, `ru` (default) or `en`. Also settable through
|
|
158
|
+
`bdl config set lang en`.
|
|
159
|
+
- `PROXY_URL` — route requests through a proxy.
|
|
160
|
+
|
|
161
|
+
### Board blocks
|
|
162
|
+
|
|
163
|
+
Xbooru's DAPI needs no API key, but Cloudflare fronts the site and sometimes
|
|
164
|
+
challenges an IP with HTTP 403. Requests to Xbooru therefore carry a full set of
|
|
165
|
+
browser headers. If the IP is still challenged, the affected files are counted
|
|
166
|
+
as `blocked` instead of filling the screen with warnings, and the reason is
|
|
167
|
+
reported once in the batch summary at the end. Setting `PROXY_URL` is the usual
|
|
168
|
+
way out.
|
|
169
|
+
- `bdl config set out ~/Pictures/booru` — default output root for every command
|
|
170
|
+
that takes `--out` (`BDL_OUT` still wins over it).
|
|
171
|
+
|
|
172
|
+
## Development
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
./.venv/bin/python -m pytest tests -q # unit tests, no network
|
|
176
|
+
./.venv/bin/ruff check bdl tests
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## License
|
|
180
|
+
|
|
181
|
+
MIT
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Standalone CLI for batch downloading and syncing content from Booru boards.
|
|
2
|
+
|
|
3
|
+
Self-contained: own providers, DoH resolver, models, dedup database and tag
|
|
4
|
+
dictionaries. Runs as ``python -m bdl`` or via the ``bdl`` console script.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "2.1.0"
|
|
8
|
+
|
|
9
|
+
#: Repository the project is published from.
|
|
10
|
+
REPO_SLUG = "github.com/wexqk-boo/Booru-Downloader-py"
|
|
11
|
+
|
|
12
|
+
#: User-Agent for normal outgoing API and media requests.
|
|
13
|
+
USER_AGENT = f"bdl/{__version__} ({REPO_SLUG})"
|
|
14
|
+
|
|
15
|
+
#: Xbooru has an open DAPI with no keys, but its Cloudflare edge challenges a
|
|
16
|
+
#: client that announces itself as a script, so both the API call and the media
|
|
17
|
+
#: fetch present a full set of browser headers instead.
|
|
18
|
+
XBOORU_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64; rv:132.0) Gecko/20100101 Firefox/132.0"
|
|
19
|
+
|
|
20
|
+
XBOORU_HEADERS: dict[str, str] = {
|
|
21
|
+
"User-Agent": XBOORU_USER_AGENT,
|
|
22
|
+
"Referer": "https://xbooru.com/index.php?page=post&s=list",
|
|
23
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
|
24
|
+
"Accept-Language": "en-US,en;q=0.5",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
__all__ = ["REPO_SLUG", "USER_AGENT", "XBOORU_HEADERS", "XBOORU_USER_AGENT", "__version__"]
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Tag blacklist: persistent file and management helpers.
|
|
2
|
+
|
|
3
|
+
Default file: ``~/.config/bdl/blacklist.txt`` (one tag per line, ``#`` starts a
|
|
4
|
+
comment). Posts matching any of these tags are skipped during download, and a
|
|
5
|
+
base tag such as ``ai`` also pulls in its whole family.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from .config import xdg_config_home
|
|
13
|
+
from .tags.families import expand_blacklist_tags
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def default_blacklist_path() -> Path:
|
|
17
|
+
return xdg_config_home() / "bdl" / "blacklist.txt"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def load_blacklist(path: Path | str | None = None) -> set[str]:
|
|
21
|
+
"""Read the tag list in lowercase; a missing file yields an empty set."""
|
|
22
|
+
file_path = Path(path).expanduser() if path else default_blacklist_path()
|
|
23
|
+
if not file_path.is_file():
|
|
24
|
+
return set()
|
|
25
|
+
tags: set[str] = set()
|
|
26
|
+
for line in file_path.read_text(encoding="utf-8").splitlines():
|
|
27
|
+
tag = line.strip().lower()
|
|
28
|
+
if tag and not tag.startswith("#"):
|
|
29
|
+
tags.add(tag)
|
|
30
|
+
return tags
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def save_blacklist(tags: set[str], path: Path | str | None = None) -> Path:
|
|
34
|
+
"""Write the sorted tag list and return the file path."""
|
|
35
|
+
file_path = Path(path).expanduser() if path else default_blacklist_path()
|
|
36
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
37
|
+
lines = [f"{tag}\n" for tag in sorted(tags)]
|
|
38
|
+
file_path.write_text("".join(lines), encoding="utf-8")
|
|
39
|
+
return file_path
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def expand_tag(tag: str) -> set[str]:
|
|
43
|
+
"""Tag family: the tag itself plus related sub-tags (ai/furry/guro/...)."""
|
|
44
|
+
clean = tag.strip().lower()
|
|
45
|
+
if not clean:
|
|
46
|
+
return set()
|
|
47
|
+
expanded = set(expand_blacklist_tags(clean))
|
|
48
|
+
expanded.add(clean)
|
|
49
|
+
return expanded
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def add_tag(tag: str, path: Path | str | None = None) -> int:
|
|
53
|
+
"""Add a tag and its family; return how many entries were added."""
|
|
54
|
+
expanded = expand_tag(tag)
|
|
55
|
+
if not expanded:
|
|
56
|
+
return 0
|
|
57
|
+
tags = load_blacklist(path)
|
|
58
|
+
before = len(tags)
|
|
59
|
+
tags |= expanded
|
|
60
|
+
if len(tags) != before:
|
|
61
|
+
save_blacklist(tags, path)
|
|
62
|
+
return len(tags) - before
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def remove_tag(tag: str, path: Path | str | None = None) -> int:
|
|
66
|
+
"""Remove a tag and its family if present; return how many entries were removed."""
|
|
67
|
+
clean = tag.strip().lower()
|
|
68
|
+
tags = load_blacklist(path)
|
|
69
|
+
to_remove = expand_tag(clean) & tags
|
|
70
|
+
if not to_remove:
|
|
71
|
+
return 0
|
|
72
|
+
tags -= to_remove
|
|
73
|
+
save_blacklist(tags, path)
|
|
74
|
+
return len(to_remove)
|