yoinktui 1.0.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.
- yoinktui-1.0.0/LICENSE +21 -0
- yoinktui-1.0.0/PKG-INFO +398 -0
- yoinktui-1.0.0/README.md +367 -0
- yoinktui-1.0.0/pyproject.toml +93 -0
- yoinktui-1.0.0/pyproject.toml.orig +80 -0
- yoinktui-1.0.0/src/yoinktui/__init__.py +12 -0
- yoinktui-1.0.0/src/yoinktui/__main__.py +368 -0
- yoinktui-1.0.0/src/yoinktui/app.py +192 -0
- yoinktui-1.0.0/src/yoinktui/assets/logo-dark.png +0 -0
- yoinktui-1.0.0/src/yoinktui/assets/logo-light.png +0 -0
- yoinktui-1.0.0/src/yoinktui/backend/__init__.py +95 -0
- yoinktui-1.0.0/src/yoinktui/backend/config.py +666 -0
- yoinktui-1.0.0/src/yoinktui/backend/errors.py +184 -0
- yoinktui-1.0.0/src/yoinktui/backend/library.py +260 -0
- yoinktui-1.0.0/src/yoinktui/backend/models.py +219 -0
- yoinktui-1.0.0/src/yoinktui/backend/presets.py +108 -0
- yoinktui-1.0.0/src/yoinktui/backend/queue.py +448 -0
- yoinktui-1.0.0/src/yoinktui/backend/ytdlp.py +1116 -0
- yoinktui-1.0.0/src/yoinktui/cli.py +585 -0
- yoinktui-1.0.0/src/yoinktui/tui/__init__.py +41 -0
- yoinktui-1.0.0/src/yoinktui/tui/app.tcss +745 -0
- yoinktui-1.0.0/src/yoinktui/tui/markup.py +15 -0
- yoinktui-1.0.0/src/yoinktui/tui/screens.py +2324 -0
- yoinktui-1.0.0/src/yoinktui/tui/widgets.py +1078 -0
yoinktui-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zakariya Zahid
|
|
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.
|
yoinktui-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: yoinktui
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Steam-style TUI download manager for YouTube and 1000+ sites via yt-dlp + ffmpeg
|
|
5
|
+
Keywords: youtube,download,tui,yt-dlp,terminal,textual
|
|
6
|
+
Author: Zakariya Zahid
|
|
7
|
+
Author-email: Zakariya Zahid <zakryiabukhari@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
13
|
+
Classifier: Operating System :: MacOS
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Multimedia :: Video
|
|
19
|
+
Classifier: Topic :: Utilities
|
|
20
|
+
Requires-Dist: httpx>=0.28.1
|
|
21
|
+
Requires-Dist: pillow>=13.0.0
|
|
22
|
+
Requires-Dist: rich>=15.0.0
|
|
23
|
+
Requires-Dist: textual>=8.2.8
|
|
24
|
+
Requires-Dist: textual-image>=0.14.1
|
|
25
|
+
Requires-Python: >=3.12
|
|
26
|
+
Project-URL: Homepage, https://github.com/Zakariya-Zahid/YoinkTUI
|
|
27
|
+
Project-URL: Repository, https://github.com/Zakariya-Zahid/YoinkTUI
|
|
28
|
+
Project-URL: Issues, https://github.com/Zakariya-Zahid/YoinkTUI/issues
|
|
29
|
+
Project-URL: Changelog, https://github.com/Zakariya-Zahid/YoinkTUI/blob/main/CHANGELOG.md
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
<p align="center">
|
|
33
|
+
<picture>
|
|
34
|
+
<source media="(prefers-color-scheme: dark)" srcset="src/yoinktui/assets/logo-dark.png">
|
|
35
|
+
<img src="src/yoinktui/assets/logo-light.png" alt="YoinkTUI" width="420">
|
|
36
|
+
</picture>
|
|
37
|
+
</p>
|
|
38
|
+
|
|
39
|
+
# YoinkTUI
|
|
40
|
+
|
|
41
|
+
TUI download manager for YouTube (and 1000+ sites) via **yt-dlp** + **ffmpeg**.
|
|
42
|
+
|
|
43
|
+
> Yoink it. Keep it.
|
|
44
|
+
|
|
45
|
+
## Features
|
|
46
|
+
|
|
47
|
+
- **Paste URL → See formats → Download** — clean three-screen flow
|
|
48
|
+
- **Clip downloads** — pick a start/end range to grab just the useful part of a long podcast or tutorial
|
|
49
|
+
- **Steam-style download manager** — per-stream queue, live speed/ETA/peak/disk tiles,
|
|
50
|
+
overall progress, pause/resume, cancel, and a timestamped activity log
|
|
51
|
+
- **Persistent queue** — add several items, review/retry/remove them, clear the
|
|
52
|
+
whole queue at once, resume interrupted or failed downloads after a restart,
|
|
53
|
+
and multi-select rows for bulk remove/start actions
|
|
54
|
+
- **Playlist support** — preview a playlist, choose Video+Audio, Video Only, or
|
|
55
|
+
Audio Only, pick a quality cap, and queue every entry sequentially; queue a
|
|
56
|
+
selected subset with multi-select
|
|
57
|
+
- **History & library** — SQLite-backed history with search, details, open
|
|
58
|
+
file/folder, copy URL, re-download, and delete
|
|
59
|
+
- **Presets** — built-in Music, Archive, and Mobile profiles plus your own
|
|
60
|
+
`[presets.<name>]` bundles, selectable from the format screen
|
|
61
|
+
- **Config file** — quality cap, container, audio format, fragment concurrency,
|
|
62
|
+
rate limit, metadata/thumbnail embedding, output directory, and defaults
|
|
63
|
+
- **Trustworthy failures** — typed errors with hints, startup dependency checks,
|
|
64
|
+
and a `yoinktui --doctor` pre-flight report
|
|
65
|
+
- **Live thumbnail** — renders via kitty graphics / sixel / Unicode fallback (`textual-image`)
|
|
66
|
+
- **Format picker** — resolution, codec, size, and bitrate at a glance; one
|
|
67
|
+
format maps to one download, so the format table itself stays single-select
|
|
68
|
+
- **Video + Audio / Video Only / Audio Only** modes
|
|
69
|
+
- **Real pause & resume** — suspends the whole yt-dlp process group (POSIX)
|
|
70
|
+
|
|
71
|
+
## Live Demo
|
|
72
|
+
<p align="center">
|
|
73
|
+
<a href="https://github.com/user-attachments/assets/4d92a270-7695-44be-a0ad-ab9ccb74ca62">Watch the Demo</a>
|
|
74
|
+
</p>
|
|
75
|
+
|
|
76
|
+
## Screenshots
|
|
77
|
+
|
|
78
|
+
<p align="center">
|
|
79
|
+
<img src="assets/main.png" alt="YoinkTUI URL screen" width="100%">
|
|
80
|
+
<br><br>
|
|
81
|
+
<img src="assets/Video_Url.png" alt="YoinkTUI format picker" width="100%">
|
|
82
|
+
<br><br>
|
|
83
|
+
<img src="assets/Dashboard.png" alt="YoinkTUI download dashboard" width="100%">
|
|
84
|
+
<br><br>
|
|
85
|
+
<img src="assets/Download_Section.png" alt="YoinkTUI download in progress" width="100%">
|
|
86
|
+
</p>
|
|
87
|
+
|
|
88
|
+
## Install
|
|
89
|
+
|
|
90
|
+
Requires Python 3.12+, `yt-dlp`, and `ffmpeg` on your `PATH`.
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# Via uv tool (recommended)
|
|
94
|
+
uv tool install yoinktui
|
|
95
|
+
|
|
96
|
+
# Via pipx
|
|
97
|
+
pipx install yoinktui
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Or install from source:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
git clone https://github.com/Zakariya-Zahid/YoinkTUI.git
|
|
104
|
+
cd YoinkTUI
|
|
105
|
+
uv sync
|
|
106
|
+
uv tool install -e .
|
|
107
|
+
yoinktui --doctor
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Run
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
uv run yoinktui
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Check the environment before the first download:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
uv run yoinktui --doctor
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`--doctor` verifies yt-dlp and ffmpeg, prints their versions, and checks that
|
|
123
|
+
the download directory is writable. Missing dependencies open a friendly
|
|
124
|
+
startup screen instead of a traceback.
|
|
125
|
+
|
|
126
|
+
## Usage
|
|
127
|
+
|
|
128
|
+
1. **Paste a URL** — YouTube, Twitter/X, Vimeo, Twitch, TikTok, etc.
|
|
129
|
+
2. **Pick a format** — Table shows video & audio streams; arrow keys navigate, Enter selects
|
|
130
|
+
3. **Choose mode** — `Video+Audio` (default), `Video Only`, `Audio Only`
|
|
131
|
+
4. **Optional preset** — open the preset modal to apply Music, Archive, Mobile, or a user-defined bundle
|
|
132
|
+
5. **Optional clip** — press `C`, enter a start/end range, then Apply. Leave end blank to go to the end of the video.
|
|
133
|
+
6. **Download or queue** — start immediately, or add the item to the persistent queue. Playlist URLs can queue every entry as video+audio, video only, or audio only, with a quality cap such as 1080p or Best available. Playlist entries can also be queued as a multi-select subset.
|
|
134
|
+
7. **Resume / browse** — pending, interrupted, and failed queue items are offered on startup; the Library screen searches and acts on history.
|
|
135
|
+
|
|
136
|
+
### Multi-select actions
|
|
137
|
+
|
|
138
|
+
Queue and playlist tables support row selection:
|
|
139
|
+
|
|
140
|
+
- `Space` / `Enter` toggles the highlighted row.
|
|
141
|
+
- `Ctrl+A` selects all rows.
|
|
142
|
+
- `Ctrl+Shift+A` clears the selection.
|
|
143
|
+
|
|
144
|
+
Actions then operate on the selection:
|
|
145
|
+
|
|
146
|
+
- Queue screen: remove selected rows, start selected rows, or clear the selection.
|
|
147
|
+
- Playlist screen: queue selected entries, or clear the selection.
|
|
148
|
+
|
|
149
|
+
The format picker remains intentionally single-select because one chosen format
|
|
150
|
+
maps to one download; bulk format selection is not a queue action.
|
|
151
|
+
|
|
152
|
+
## Configuration
|
|
153
|
+
|
|
154
|
+
YoinkTUI reads `$XDG_CONFIG_HOME/yoinktui/config.toml` (usually
|
|
155
|
+
`~/.config/yoinktui/config.toml`). Precedence, highest first:
|
|
156
|
+
|
|
157
|
+
1. CLI flags
|
|
158
|
+
2. `YOINKTUI_*` environment variables
|
|
159
|
+
3. the config file
|
|
160
|
+
4. built-in defaults
|
|
161
|
+
|
|
162
|
+
Create a default file with the current defaults:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
yoinktui --write-default-config
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Or write one by hand:
|
|
169
|
+
|
|
170
|
+
```toml
|
|
171
|
+
[general]
|
|
172
|
+
theme = "dark" # dark, light, or a Textual theme name
|
|
173
|
+
default_mode = "video" # video | vonly | audio
|
|
174
|
+
|
|
175
|
+
[download]
|
|
176
|
+
dir = "~/Videos/yoinktui"
|
|
177
|
+
quality_cap = 1080 # 0 = best available
|
|
178
|
+
container = "mkv" # mkv (preferred) | mp4 | auto
|
|
179
|
+
audio_format = "mp3" # mp3 | m4a | opus | flac | best
|
|
180
|
+
audio_quality = "0"
|
|
181
|
+
concurrent_fragments = 0 # 0 = yt-dlp default
|
|
182
|
+
rate_limit = "" # e.g. "5M"
|
|
183
|
+
write_subtitles = false
|
|
184
|
+
embed_metadata = true
|
|
185
|
+
embed_thumbnail = false
|
|
186
|
+
keep_original = false
|
|
187
|
+
|
|
188
|
+
[network]
|
|
189
|
+
proxy = ""
|
|
190
|
+
cookies_from_browser = "" # browser name, e.g. "firefox" or "chrome:PROFILE"
|
|
191
|
+
cookies_file = "" # exported Netscape cookies.txt, e.g. "~/cookies.txt"
|
|
192
|
+
impersonate = "" # client to impersonate, e.g. "chrome"
|
|
193
|
+
|
|
194
|
+
[ui]
|
|
195
|
+
show_preview = true
|
|
196
|
+
ascii = false
|
|
197
|
+
no_color = false
|
|
198
|
+
|
|
199
|
+
# Built-ins are music, archive, and mobile. Override only the keys you want,
|
|
200
|
+
# or add a new preset name.
|
|
201
|
+
[presets.phone]
|
|
202
|
+
mode = "video"
|
|
203
|
+
quality_cap = 480
|
|
204
|
+
container = "mp4"
|
|
205
|
+
audio_format = "m4a"
|
|
206
|
+
description = "Small MP4 for an older phone"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Presets bundle mode, quality cap, container, audio format, and post-processing
|
|
210
|
+
switches. Apply one from the format screen; the action bar shows the active
|
|
211
|
+
profile. Built-ins can be overridden with `[presets.music]`,
|
|
212
|
+
`[presets.archive]`, or `[presets.mobile]`.
|
|
213
|
+
|
|
214
|
+
Environment overrides use the same names with a `YOINKTUI_` prefix:
|
|
215
|
+
`YOINKTUI_DOWNLOAD_DIR`, `YOINKTUI_QUALITY_CAP`, `YOINKTUI_CONTAINER`,
|
|
216
|
+
`YOINKTUI_AUDIO_FORMAT`, `YOINKTUI_PROXY`, and so on.
|
|
217
|
+
|
|
218
|
+
Useful commands:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
yoinktui --print-config # show the merged result
|
|
222
|
+
yoinktui --config ./test.toml # use a different file
|
|
223
|
+
yoinktui --doctor # check dependencies + paths
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Download policy can also be set per run:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
yoinktui --quality-cap 720 --container mp4 --audio-format m4a
|
|
230
|
+
yoinktui --concurrent-fragments 4 --rate-limit 5M
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Defaults: 1080p cap, `mkv`, `mp3`, `~/Videos/yoinktui`.
|
|
234
|
+
|
|
235
|
+
### Data and resume
|
|
236
|
+
|
|
237
|
+
Runtime data lives under `$XDG_DATA_HOME/yoinktui/` (usually
|
|
238
|
+
`~/.local/share/yoinktui/`):
|
|
239
|
+
|
|
240
|
+
- `queue.json` — persistent download queue; pending, interrupted, and failed
|
|
241
|
+
items are offered for resume on startup.
|
|
242
|
+
- `library.db` — SQLite download history used by the Library screen.
|
|
243
|
+
|
|
244
|
+
Downloads use yt-dlp `--continue`, so a partially written `.part` file resumes
|
|
245
|
+
where it left off. Quitting mid-download records the active item as
|
|
246
|
+
`interrupted` in history; queued downloads are offered for resume on the next
|
|
247
|
+
launch.
|
|
248
|
+
|
|
249
|
+
### Headless CLI & Automation
|
|
250
|
+
|
|
251
|
+
YoinkTUI can run headlessly for scripts, cron jobs, and terminal automation without launching the TUI:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
# Direct download using your config defaults
|
|
255
|
+
yoinktui "https://www.youtube.com/watch?v=..."
|
|
256
|
+
|
|
257
|
+
# Custom output directory, format selector, or mode
|
|
258
|
+
yoinktui URL -o ~/Downloads -f bestvideo+bestaudio
|
|
259
|
+
yoinktui URL -m audio --preset music
|
|
260
|
+
|
|
261
|
+
# Automation: stream progress as NDJSON or run quietly
|
|
262
|
+
yoinktui URL --json
|
|
263
|
+
yoinktui URL --quiet
|
|
264
|
+
|
|
265
|
+
# Inspection and command preview
|
|
266
|
+
yoinktui URL --dry-run
|
|
267
|
+
yoinktui URL --dry-run --print-command
|
|
268
|
+
|
|
269
|
+
# Batch downloads from a file or stdin
|
|
270
|
+
yoinktui --batch-file urls.txt
|
|
271
|
+
cat urls.txt | yoinktui -
|
|
272
|
+
|
|
273
|
+
# Playlists
|
|
274
|
+
yoinktui "https://www.youtube.com/playlist?list=..." --yes-playlist --playlist-items 1-5
|
|
275
|
+
|
|
276
|
+
# Force launching the TUI with a prefilled URL
|
|
277
|
+
yoinktui URL --tui
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Structured exit codes are returned for scripting (`0`: success, `10`: network, `11`: auth/cookies, `12`: geo, `13`: not found, `14`: disk, `15`: dependency missing, `16`: unsupported URL, `17`: no streams, `130`: cancelled).
|
|
281
|
+
|
|
282
|
+
### Cookies and proxy
|
|
283
|
+
|
|
284
|
+
Some sites need a logged-in browser session or a different network:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
# Read cookies directly from a logged-in browser profile
|
|
288
|
+
yoinktui --cookies-from-browser firefox
|
|
289
|
+
yoinktui --cookies-from-browser "firefox:/path/to/profile" \
|
|
290
|
+
--proxy socks5://127.0.0.1:1080
|
|
291
|
+
|
|
292
|
+
# Or use a Netscape cookies.txt exported by a browser extension
|
|
293
|
+
yoinktui --cookies ~/cookies.txt
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
The same values can live in `[network]`. `cookies_from_browser` accepts
|
|
297
|
+
yt-dlp browser names: `chrome`, `firefox`, `edge`, `brave`, or
|
|
298
|
+
`browser:profile`. A `cookies.txt` file must go in `cookies_file` (or
|
|
299
|
+
`--cookies`), **not** in `cookies_from_browser`; pointing the browser option at
|
|
300
|
+
a file makes yt-dlp reject every URL. Extra yt-dlp flags can be passed with
|
|
301
|
+
`--yt-dlp-arg=...` (repeatable).
|
|
302
|
+
|
|
303
|
+
yt-dlp is updated frequently to keep up with site changes. If downloads start
|
|
304
|
+
failing across many sites, update it first (`yt-dlp -U`, or
|
|
305
|
+
`uv tool install --upgrade yt-dlp`).
|
|
306
|
+
|
|
307
|
+
### Udemy courses and lectures
|
|
308
|
+
|
|
309
|
+
Udemy needs a logged-in session and usually browser impersonation. The course
|
|
310
|
+
is the playlist, while individual lectures are the videos. See
|
|
311
|
+
[docs/guides/udemy.md](docs/guides/udemy.md) for cookie export, URL forms,
|
|
312
|
+
whole-course queueing, audio-only mode, and DRM limitations.
|
|
313
|
+
|
|
314
|
+
### TikTok / HTTP 403
|
|
315
|
+
|
|
316
|
+
TikTok's anti-bot layer sometimes rejects yt-dlp with
|
|
317
|
+
`HTTP Error 403: Forbidden` before it can read the video. That is a
|
|
318
|
+
TikTok-side block, not a YoinkTUI site limit. Log into TikTok in your
|
|
319
|
+
browser and use `--cookies-from-browser`, or try a VPN/proxy. The same URL
|
|
320
|
+
may work later without changes.
|
|
321
|
+
|
|
322
|
+
## Keyboard Shortcuts
|
|
323
|
+
|
|
324
|
+
| Key | Action |
|
|
325
|
+
|-----|--------|
|
|
326
|
+
| `Enter` | Submit URL / Select format |
|
|
327
|
+
| `↑/↓` | Navigate format table |
|
|
328
|
+
| `D` | Download the selected format |
|
|
329
|
+
| `Q` | Add the selected format to the queue |
|
|
330
|
+
| `C` | Set a clip start/end range |
|
|
331
|
+
| `V` / `O` / `A` | Video+Audio / Video Only / Audio Only |
|
|
332
|
+
| `Esc` | Back, cancel download, or quit from the URL screen |
|
|
333
|
+
| `P` / `Space` | Pause / resume download |
|
|
334
|
+
| `Ctrl+Q` | Quit app |
|
|
335
|
+
| `Ctrl+D` | Toggle dark/light theme |
|
|
336
|
+
|
|
337
|
+
In the queue and playlist tables, `Space` / `Enter` toggle row selection, `Ctrl+A`
|
|
338
|
+
selects all rows, and `Ctrl+Shift+A` clears the selection. The download screen keeps
|
|
339
|
+
its own `Space` pause/resume behavior.
|
|
340
|
+
|
|
341
|
+
## Architecture
|
|
342
|
+
|
|
343
|
+
```
|
|
344
|
+
assets/ # README screenshots + demo video
|
|
345
|
+
src/yoinktui/
|
|
346
|
+
├── app.py # App shell, per-instance config/state, dependency gate
|
|
347
|
+
├── __main__.py # CLI flags, config precedence, --doctor
|
|
348
|
+
├── assets/ # App logo (light & dark variants)
|
|
349
|
+
├── backend/
|
|
350
|
+
│ ├── config.py # TOML schema, loader, XDG paths, last-URL state
|
|
351
|
+
│ ├── presets.py # built-in Music/Archive/Mobile bundles
|
|
352
|
+
│ ├── queue.py # DownloadQueue, QueueItem, atomic JSON persistence
|
|
353
|
+
│ ├── library.py # SQLite history, search, stats, interrupted recovery
|
|
354
|
+
│ ├── errors.py # DownloadError + ErrorReason taxonomy
|
|
355
|
+
│ ├── models.py # MediaInfo, FormatInfo, numeric coercions
|
|
356
|
+
│ └── ytdlp.py # probe/playlist probe/download, process control
|
|
357
|
+
└── tui/
|
|
358
|
+
├── app.tcss # All styles (Textual 8 compatible)
|
|
359
|
+
├── widgets.py # FormatTable, InfoPanel, DownloadDashboard, queue rows
|
|
360
|
+
└── screens.py # URL/format/download/queue/playlist/library + modals
|
|
361
|
+
tests/
|
|
362
|
+
├── backend/ # parser, models, config, queue, history, presets
|
|
363
|
+
└── tui/ # smoke, flow, URL validation, power-user flows, snapshots
|
|
364
|
+
docs/adr/ # architecture decisions (history store)
|
|
365
|
+
docs/contracts/ # frozen progress, selection, and navigation contracts
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
- **Backend** runs yt-dlp in a thread (`@work(thread=True)`) so the UI stays responsive
|
|
369
|
+
- **Progress** is parsed from yt-dlp's `--progress-template` stdout stream;
|
|
370
|
+
the field order is frozen in [`docs/contracts/progress.md`](docs/contracts/progress.md)
|
|
371
|
+
- **Control** uses POSIX `SIGSTOP`/`SIGCONT` for real pause/resume and
|
|
372
|
+
terminates the whole process group on cancel
|
|
373
|
+
- **Failures** are classified into network/geo/auth/not-found/disk/ffmpeg/
|
|
374
|
+
unsupported buckets, each with a hint and suggested next step
|
|
375
|
+
- **Thumbnails** are fetched via `httpx` and rendered by `textual-image.AutoImage`
|
|
376
|
+
|
|
377
|
+
## Development
|
|
378
|
+
|
|
379
|
+
```bash
|
|
380
|
+
uv sync --dev
|
|
381
|
+
uv run pytest # unit + parser + TUI + snapshots
|
|
382
|
+
uv run pytest --snapshot-update # accept intentional UI changes
|
|
383
|
+
uv run ruff check .
|
|
384
|
+
uv run ruff format --check .
|
|
385
|
+
uv run mypy src
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
CI runs the same checks on Linux and macOS for Python 3.12 and 3.13
|
|
389
|
+
([`.github/workflows/ci.yml`](.github/workflows/ci.yml)). The release
|
|
390
|
+
checklist lives in [`docs/RELEASING.md`](docs/RELEASING.md).
|
|
391
|
+
|
|
392
|
+
## Roadmap
|
|
393
|
+
|
|
394
|
+
See [ROADMAP.md](ROADMAP.md) and the per-module plans in [docs/plans/](docs/plans/).
|
|
395
|
+
|
|
396
|
+
## License
|
|
397
|
+
|
|
398
|
+
MIT — see [LICENSE](LICENSE).
|