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 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.
@@ -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).