vidkraken 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.
@@ -0,0 +1,21 @@
1
+ # Local virtualenv
2
+ .venv/
3
+ .venv-*/
4
+ .uv-cache/
5
+ .uv-python/
6
+ venv/
7
+
8
+ # Build artifacts
9
+ dist/
10
+ build/
11
+ *.egg-info/
12
+ *.whl
13
+
14
+ # Caches
15
+ __pycache__/
16
+ *.py[cod]
17
+ .pytest_cache/
18
+ .mypy_cache/
19
+ .ruff_cache/
20
+ .coverage
21
+ htmlcov/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vid Kraken
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,297 @@
1
+ Metadata-Version: 2.5
2
+ Name: vidkraken
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK and CLI for the Vid Kraken YouTube download API
5
+ Project-URL: Homepage, https://vidkraken.com
6
+ Project-URL: Documentation, https://vidkraken.com/docs
7
+ Project-URL: Source, https://github.com/vidkraken/vidkraken
8
+ Project-URL: Issues, https://github.com/vidkraken/vidkraken/issues
9
+ Author-email: Vid Kraken <hello@vidkraken.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: api,audio,cli,download,mp3,sdk,video,vidkraken,youtube,youtube-downloader
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Topic :: Internet :: WWW/HTTP
26
+ Classifier: Topic :: Multimedia :: Video
27
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
+ Classifier: Typing :: Typed
29
+ Requires-Python: >=3.9
30
+ Requires-Dist: httpx>=0.25
31
+ Provides-Extra: dev
32
+ Requires-Dist: build>=1; extra == 'dev'
33
+ Requires-Dist: mypy>=1.8; extra == 'dev'
34
+ Requires-Dist: pytest>=7; extra == 'dev'
35
+ Requires-Dist: twine>=5; extra == 'dev'
36
+ Description-Content-Type: text/markdown
37
+
38
+ # Vid Kraken Python SDK and CLI
39
+
40
+ The official Python client for [Vid Kraken](https://vidkraken.com), a REST API
41
+ that downloads YouTube videos and audio on its servers and returns a CDN link
42
+ to the finished file. You pay for bandwidth only, and failed downloads are never
43
+ charged.
44
+
45
+ One package gives you:
46
+
47
+ - `VidKraken` and `AsyncVidKraken`: typed sync and async clients built on [httpx](https://www.python-httpx.org/)
48
+ - a `vidkraken` command-line tool for scripts, shells and AI agents
49
+
50
+ > Use only with content you have the right to download.
51
+
52
+ ## Install
53
+
54
+ ```bash
55
+ pip install vidkraken
56
+ ```
57
+
58
+ Requires Python 3.9 or newer. The only runtime dependency is `httpx`.
59
+
60
+ ## Get an API key
61
+
62
+ Sign in at **https://vidkraken.com/login** and copy your key from the dashboard.
63
+ Then either export it:
64
+
65
+ ```bash
66
+ export VIDKRAKEN_API_KEY="vk_..."
67
+ ```
68
+
69
+ or pass it to the client with `VidKraken(api_key="vk_...")`.
70
+
71
+ ## Quickstart (sync)
72
+
73
+ ```python
74
+ from vidkraken import VidKraken
75
+
76
+ with VidKraken() as vk: # reads VIDKRAKEN_API_KEY
77
+ done = vk.download_and_wait(
78
+ "https://youtu.be/dQw4w9WgXcQ",
79
+ format="720", # "1080", "720", "480", "360", "audio", "audio-aac", "audio-mp3"
80
+ on_progress=lambda s: print(s.status, s.progress and s.progress.percent),
81
+ )
82
+ print(done.download_url, done.actual_format, done.file_size)
83
+ path = vk.save_file(done, "downloads/") # named after the video title
84
+ print("saved to", path)
85
+ ```
86
+
87
+ Step by step, if you'd rather poll yourself or use a webhook:
88
+
89
+ ```python
90
+ job = vk.download("https://youtu.be/dQw4w9WgXcQ", format="audio-mp3", start_time=30, end_time=90)
91
+ print(job.job_id, job.title, job.rate_limit.remaining)
92
+
93
+ status = vk.get_download(job.job_id) # IN_QUEUE / IN_PROGRESS / COMPLETED / FAILED
94
+ status = vk.wait_for_download(job.job_id, poll_interval=3.0, timeout=600)
95
+ ```
96
+
97
+ Pass `webhook_url="https://example.com/hooks/vidkraken"` to `download()` and Vid
98
+ Kraken POSTs the final status to that URL. The body is the same as `get_download()`
99
+ returns, so `DownloadStatus.from_dict(payload)` parses it.
100
+
101
+ ## Quickstart (async)
102
+
103
+ `AsyncVidKraken` has the same methods. Each one is a coroutine.
104
+
105
+ ```python
106
+ import asyncio
107
+ from vidkraken import AsyncVidKraken
108
+
109
+ async def main() -> None:
110
+ async with AsyncVidKraken() as vk:
111
+ urls = ["https://youtu.be/dQw4w9WgXcQ", "https://youtu.be/9bZkp7q19f0"]
112
+ results = await asyncio.gather(*(vk.download_and_wait(u, format="audio") for u in urls))
113
+ for done in results:
114
+ await vk.save_file(done, "downloads/")
115
+
116
+ asyncio.run(main())
117
+ ```
118
+
119
+ ## Video info
120
+
121
+ ```python
122
+ info = vk.info_and_wait("https://youtu.be/dQw4w9WgXcQ", include_audio_tracks=True)
123
+ print(info.title, info.duration)
124
+ for track in info.audio or []:
125
+ print(track.language, track.type) # e.g. "en original", "es dubbed"
126
+ ```
127
+
128
+ `info()`, `get_info()` and `wait_for_info()` are the separate steps behind `info_and_wait()`.
129
+
130
+ ## Account and channel listing
131
+
132
+ ```python
133
+ me = vk.me()
134
+ print(me.plan.id, me.balances["bandwidth_mb"].remaining)
135
+
136
+ listing = vk.list_channel("https://www.youtube.com/@RickAstleyYT", max_videos=25) # paid add-on
137
+ for entry in listing.entries:
138
+ print(entry["id"], entry["title"])
139
+ ```
140
+
141
+ ## Saving files
142
+
143
+ `save_file(source, path=None)` streams a finished download to disk in chunks.
144
+
145
+ - `source` is a completed `DownloadStatus` or a raw `downloadUrl` string.
146
+ - `path` is a file path, a directory, or `None` for the current directory. When
147
+ you give a directory, the file is named `<title>.<ext>`: `.mp4` for video,
148
+ `.mp3` for `audio-mp3`, `.m4a` for `audio-aac`, and the CDN's own extension
149
+ for `audio`.
150
+ - The file is written to `<name>.part` and renamed once it is complete.
151
+ - Your API key is never sent to the file host.
152
+
153
+ ## Responses
154
+
155
+ Responses are frozen dataclasses with snake_case attributes, for example
156
+ `status.download_url` and `status.progress.eta_seconds`. The original JSON is kept
157
+ on `.raw`, and `obj["camelCaseKey"]` / `obj.get(...)` read from it, so fields
158
+ added to the API later are still reachable.
159
+
160
+ ## Errors
161
+
162
+ Every error inherits from `vidkraken.VidKrakenError`, which has `.status`, `.code`,
163
+ `.message`, `.details` and `.retry_after`. Branch on `.code`, not on the message.
164
+
165
+ | Exception | When | `code` |
166
+ |---|---|---|
167
+ | `BadRequestError` | 400 | `INVALID_REQUEST` |
168
+ | `AuthenticationError` | 401 | `UNAUTHORIZED` |
169
+ | `PermissionDeniedError` | 403 | `LIMIT_REACHED` |
170
+ | `NotFoundError` | 404 | `NOT_FOUND`, `VIDEO_NOT_FOUND` |
171
+ | `RateLimitError` | 429 | `TOO_MANY_PENDING`, `RATE_LIMITED` |
172
+ | `ServerError` / `ServiceUnavailableError` | 5xx / 503 | `INTERNAL_ERROR` / `UNAVAILABLE` |
173
+ | `APIError` | any other HTTP error (base class of the above) | as sent |
174
+ | `APIConnectionError` / `APITimeoutError` | no HTTP response | `CONNECTION_ERROR` / `TIMEOUT` |
175
+ | `JobFailedError` | the job ended in `FAILED` / `failed` | `.error_code`, e.g. `AGE_RESTRICTED` |
176
+ | `WaitTimeoutError` | `wait_for_*(timeout=...)` ran out of time | `WAIT_TIMEOUT` |
177
+ | `MissingAPIKeyError` | no key given and `VIDKRAKEN_API_KEY` is unset | `MISSING_API_KEY` |
178
+
179
+ If the API sends a code this SDK doesn't know, the exception class is picked from
180
+ the HTTP status.
181
+
182
+ ```python
183
+ from vidkraken import JobFailedError, RateLimitError, VidKrakenError
184
+
185
+ try:
186
+ done = vk.download_and_wait(url, format="1080")
187
+ except JobFailedError as e:
188
+ print(e.job_id, e.error_code, e.message)
189
+ if e.retryable: # only NETWORK_ERROR is worth resubmitting
190
+ ...
191
+ except RateLimitError as e:
192
+ print("retry in", e.retry_after, "seconds")
193
+ except VidKrakenError as e:
194
+ print(e.status, e.code, e.message)
195
+ ```
196
+
197
+ ## Retries
198
+
199
+ The API has **no idempotency key**. Retrying a `POST /download` after a timeout
200
+ or a 5xx could therefore start, and bill, a second download. The client only
201
+ retries when that can't happen:
202
+
203
+ | Request | Retried on |
204
+ |---|---|
205
+ | Any `GET` (status polls, `/me`, file download) | network errors, timeouts, 429, 500, 502, 503, 504 |
206
+ | `POST /download`, `/info`, `/list-channel` | 429 and 503 `UNAVAILABLE` only, because in both cases nothing was queued |
207
+
208
+ It never retries a POST on 500, 502, 504, a timeout or a network error. In those
209
+ cases you get the error and can check before resubmitting.
210
+
211
+ - Retries use exponential backoff with jitter (0.5s, 1s, 2s and so on, capped at 8s).
212
+ - A `Retry-After` header is honored if it is 60s or less. If it is longer, the
213
+ error is raised so you can decide what to do.
214
+ - `max_retries` defaults to 2. Set it to 0 to turn retries off:
215
+
216
+ ```python
217
+ vk = VidKraken(max_retries=5, timeout=30.0)
218
+ vk = VidKraken(max_retries=0)
219
+ ```
220
+
221
+ ## Rate limits
222
+
223
+ Each account can have at most 100 downloads queued or in progress at once.
224
+ Every `download()` result carries the headroom parsed from the IETF `RateLimit`
225
+ headers:
226
+
227
+ ```python
228
+ job = vk.download(url)
229
+ job.rate_limit.limit # 100: how many may be pending at once
230
+ job.rate_limit.remaining # how many more you can queue right now
231
+ ```
232
+
233
+ Past the cap, `download()` raises `RateLimitError` with code `TOO_MANY_PENDING`
234
+ and `.retry_after` set. The error also has `.rate_limit`.
235
+
236
+ ## Bring your own httpx client
237
+
238
+ ```python
239
+ import httpx
240
+ vk = VidKraken(http_client=httpx.Client(proxy="http://proxy:8080")) # proxy= needs httpx>=0.26
241
+ ```
242
+
243
+ The client must be an `httpx.Client` for `VidKraken` or an `httpx.AsyncClient`
244
+ for `AsyncVidKraken`. A client you pass in is not closed by `close()`.
245
+
246
+ ## CLI
247
+
248
+ ```bash
249
+ vidkraken download https://youtu.be/dQw4w9WgXcQ -f 1080 # waits, saves, prints the path
250
+ vidkraken download https://youtu.be/dQw4w9WgXcQ -f audio-mp3 -o music/
251
+ vidkraken download https://youtu.be/dQw4w9WgXcQ --start 30 --end 90 -o clip.mp4 -f 720
252
+ vidkraken download https://youtu.be/dQw4w9WgXcQ --no-wait # prints the job id
253
+ vidkraken download https://youtu.be/dQw4w9WgXcQ --webhook https://example.com/hook --no-wait
254
+ vidkraken status <job_id>
255
+ vidkraken info https://youtu.be/dQw4w9WgXcQ --audio-tracks
256
+ vidkraken me
257
+ vidkraken --version
258
+ python -m vidkraken --help
259
+ ```
260
+
261
+ | Option | Applies to | Effect |
262
+ |---|---|---|
263
+ | `-f/--format` | `download` | one of `1080`, `720`, `480`, `360`, `audio` (default), `audio-aac`, `audio-mp3` |
264
+ | `--start S` / `--end S` | `download` | trim the clip, in seconds |
265
+ | `--language LANG` | `download` | preferred audio track |
266
+ | `-o/--output PATH` | `download` | output file or directory |
267
+ | `--no-wait` | `download` | print the job id and exit |
268
+ | `--webhook URL` | `download` | URL to POST the final status to |
269
+ | `--timeout S`, `--poll-interval S` | `download` | how long to wait and how often to poll |
270
+ | `-q/--quiet` | `download` | no progress output |
271
+ | `--json` | every command | print one JSON document on stdout |
272
+ | `--api-key KEY` | every command | overrides `VIDKRAKEN_API_KEY` |
273
+
274
+ The CLI is designed to be scripted and driven by agents:
275
+
276
+ - **stdout** carries only the result: a path, a job id, a plain report, or one
277
+ JSON document with `--json`. On failure with `--json`, stdout gets
278
+ `{"error", "code", ...}`.
279
+ - **stderr** carries progress and errors such as
280
+ `vidkraken: error: TOO_MANY_PENDING: ...`.
281
+ - It never prompts. Color appears only when stderr is a TTY and `NO_COLOR` is unset.
282
+ - **Exit codes:**
283
+ - `0`: success
284
+ - `1`: API or job error. `status` also exits 1 for a `FAILED` job.
285
+ - `2`: usage error, including a missing API key
286
+ - `130`: interrupted
287
+
288
+ ## Links
289
+
290
+ - Documentation: https://vidkraken.com/docs
291
+ - LLM-friendly reference: https://vidkraken.com/llms.txt
292
+ - OpenAPI spec: https://vidkraken.com/openapi.yaml
293
+ - Support: hello@vidkraken.com
294
+
295
+ ## License
296
+
297
+ MIT © Omnivision Solutions
@@ -0,0 +1,260 @@
1
+ # Vid Kraken Python SDK and CLI
2
+
3
+ The official Python client for [Vid Kraken](https://vidkraken.com), a REST API
4
+ that downloads YouTube videos and audio on its servers and returns a CDN link
5
+ to the finished file. You pay for bandwidth only, and failed downloads are never
6
+ charged.
7
+
8
+ One package gives you:
9
+
10
+ - `VidKraken` and `AsyncVidKraken`: typed sync and async clients built on [httpx](https://www.python-httpx.org/)
11
+ - a `vidkraken` command-line tool for scripts, shells and AI agents
12
+
13
+ > Use only with content you have the right to download.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ pip install vidkraken
19
+ ```
20
+
21
+ Requires Python 3.9 or newer. The only runtime dependency is `httpx`.
22
+
23
+ ## Get an API key
24
+
25
+ Sign in at **https://vidkraken.com/login** and copy your key from the dashboard.
26
+ Then either export it:
27
+
28
+ ```bash
29
+ export VIDKRAKEN_API_KEY="vk_..."
30
+ ```
31
+
32
+ or pass it to the client with `VidKraken(api_key="vk_...")`.
33
+
34
+ ## Quickstart (sync)
35
+
36
+ ```python
37
+ from vidkraken import VidKraken
38
+
39
+ with VidKraken() as vk: # reads VIDKRAKEN_API_KEY
40
+ done = vk.download_and_wait(
41
+ "https://youtu.be/dQw4w9WgXcQ",
42
+ format="720", # "1080", "720", "480", "360", "audio", "audio-aac", "audio-mp3"
43
+ on_progress=lambda s: print(s.status, s.progress and s.progress.percent),
44
+ )
45
+ print(done.download_url, done.actual_format, done.file_size)
46
+ path = vk.save_file(done, "downloads/") # named after the video title
47
+ print("saved to", path)
48
+ ```
49
+
50
+ Step by step, if you'd rather poll yourself or use a webhook:
51
+
52
+ ```python
53
+ job = vk.download("https://youtu.be/dQw4w9WgXcQ", format="audio-mp3", start_time=30, end_time=90)
54
+ print(job.job_id, job.title, job.rate_limit.remaining)
55
+
56
+ status = vk.get_download(job.job_id) # IN_QUEUE / IN_PROGRESS / COMPLETED / FAILED
57
+ status = vk.wait_for_download(job.job_id, poll_interval=3.0, timeout=600)
58
+ ```
59
+
60
+ Pass `webhook_url="https://example.com/hooks/vidkraken"` to `download()` and Vid
61
+ Kraken POSTs the final status to that URL. The body is the same as `get_download()`
62
+ returns, so `DownloadStatus.from_dict(payload)` parses it.
63
+
64
+ ## Quickstart (async)
65
+
66
+ `AsyncVidKraken` has the same methods. Each one is a coroutine.
67
+
68
+ ```python
69
+ import asyncio
70
+ from vidkraken import AsyncVidKraken
71
+
72
+ async def main() -> None:
73
+ async with AsyncVidKraken() as vk:
74
+ urls = ["https://youtu.be/dQw4w9WgXcQ", "https://youtu.be/9bZkp7q19f0"]
75
+ results = await asyncio.gather(*(vk.download_and_wait(u, format="audio") for u in urls))
76
+ for done in results:
77
+ await vk.save_file(done, "downloads/")
78
+
79
+ asyncio.run(main())
80
+ ```
81
+
82
+ ## Video info
83
+
84
+ ```python
85
+ info = vk.info_and_wait("https://youtu.be/dQw4w9WgXcQ", include_audio_tracks=True)
86
+ print(info.title, info.duration)
87
+ for track in info.audio or []:
88
+ print(track.language, track.type) # e.g. "en original", "es dubbed"
89
+ ```
90
+
91
+ `info()`, `get_info()` and `wait_for_info()` are the separate steps behind `info_and_wait()`.
92
+
93
+ ## Account and channel listing
94
+
95
+ ```python
96
+ me = vk.me()
97
+ print(me.plan.id, me.balances["bandwidth_mb"].remaining)
98
+
99
+ listing = vk.list_channel("https://www.youtube.com/@RickAstleyYT", max_videos=25) # paid add-on
100
+ for entry in listing.entries:
101
+ print(entry["id"], entry["title"])
102
+ ```
103
+
104
+ ## Saving files
105
+
106
+ `save_file(source, path=None)` streams a finished download to disk in chunks.
107
+
108
+ - `source` is a completed `DownloadStatus` or a raw `downloadUrl` string.
109
+ - `path` is a file path, a directory, or `None` for the current directory. When
110
+ you give a directory, the file is named `<title>.<ext>`: `.mp4` for video,
111
+ `.mp3` for `audio-mp3`, `.m4a` for `audio-aac`, and the CDN's own extension
112
+ for `audio`.
113
+ - The file is written to `<name>.part` and renamed once it is complete.
114
+ - Your API key is never sent to the file host.
115
+
116
+ ## Responses
117
+
118
+ Responses are frozen dataclasses with snake_case attributes, for example
119
+ `status.download_url` and `status.progress.eta_seconds`. The original JSON is kept
120
+ on `.raw`, and `obj["camelCaseKey"]` / `obj.get(...)` read from it, so fields
121
+ added to the API later are still reachable.
122
+
123
+ ## Errors
124
+
125
+ Every error inherits from `vidkraken.VidKrakenError`, which has `.status`, `.code`,
126
+ `.message`, `.details` and `.retry_after`. Branch on `.code`, not on the message.
127
+
128
+ | Exception | When | `code` |
129
+ |---|---|---|
130
+ | `BadRequestError` | 400 | `INVALID_REQUEST` |
131
+ | `AuthenticationError` | 401 | `UNAUTHORIZED` |
132
+ | `PermissionDeniedError` | 403 | `LIMIT_REACHED` |
133
+ | `NotFoundError` | 404 | `NOT_FOUND`, `VIDEO_NOT_FOUND` |
134
+ | `RateLimitError` | 429 | `TOO_MANY_PENDING`, `RATE_LIMITED` |
135
+ | `ServerError` / `ServiceUnavailableError` | 5xx / 503 | `INTERNAL_ERROR` / `UNAVAILABLE` |
136
+ | `APIError` | any other HTTP error (base class of the above) | as sent |
137
+ | `APIConnectionError` / `APITimeoutError` | no HTTP response | `CONNECTION_ERROR` / `TIMEOUT` |
138
+ | `JobFailedError` | the job ended in `FAILED` / `failed` | `.error_code`, e.g. `AGE_RESTRICTED` |
139
+ | `WaitTimeoutError` | `wait_for_*(timeout=...)` ran out of time | `WAIT_TIMEOUT` |
140
+ | `MissingAPIKeyError` | no key given and `VIDKRAKEN_API_KEY` is unset | `MISSING_API_KEY` |
141
+
142
+ If the API sends a code this SDK doesn't know, the exception class is picked from
143
+ the HTTP status.
144
+
145
+ ```python
146
+ from vidkraken import JobFailedError, RateLimitError, VidKrakenError
147
+
148
+ try:
149
+ done = vk.download_and_wait(url, format="1080")
150
+ except JobFailedError as e:
151
+ print(e.job_id, e.error_code, e.message)
152
+ if e.retryable: # only NETWORK_ERROR is worth resubmitting
153
+ ...
154
+ except RateLimitError as e:
155
+ print("retry in", e.retry_after, "seconds")
156
+ except VidKrakenError as e:
157
+ print(e.status, e.code, e.message)
158
+ ```
159
+
160
+ ## Retries
161
+
162
+ The API has **no idempotency key**. Retrying a `POST /download` after a timeout
163
+ or a 5xx could therefore start, and bill, a second download. The client only
164
+ retries when that can't happen:
165
+
166
+ | Request | Retried on |
167
+ |---|---|
168
+ | Any `GET` (status polls, `/me`, file download) | network errors, timeouts, 429, 500, 502, 503, 504 |
169
+ | `POST /download`, `/info`, `/list-channel` | 429 and 503 `UNAVAILABLE` only, because in both cases nothing was queued |
170
+
171
+ It never retries a POST on 500, 502, 504, a timeout or a network error. In those
172
+ cases you get the error and can check before resubmitting.
173
+
174
+ - Retries use exponential backoff with jitter (0.5s, 1s, 2s and so on, capped at 8s).
175
+ - A `Retry-After` header is honored if it is 60s or less. If it is longer, the
176
+ error is raised so you can decide what to do.
177
+ - `max_retries` defaults to 2. Set it to 0 to turn retries off:
178
+
179
+ ```python
180
+ vk = VidKraken(max_retries=5, timeout=30.0)
181
+ vk = VidKraken(max_retries=0)
182
+ ```
183
+
184
+ ## Rate limits
185
+
186
+ Each account can have at most 100 downloads queued or in progress at once.
187
+ Every `download()` result carries the headroom parsed from the IETF `RateLimit`
188
+ headers:
189
+
190
+ ```python
191
+ job = vk.download(url)
192
+ job.rate_limit.limit # 100: how many may be pending at once
193
+ job.rate_limit.remaining # how many more you can queue right now
194
+ ```
195
+
196
+ Past the cap, `download()` raises `RateLimitError` with code `TOO_MANY_PENDING`
197
+ and `.retry_after` set. The error also has `.rate_limit`.
198
+
199
+ ## Bring your own httpx client
200
+
201
+ ```python
202
+ import httpx
203
+ vk = VidKraken(http_client=httpx.Client(proxy="http://proxy:8080")) # proxy= needs httpx>=0.26
204
+ ```
205
+
206
+ The client must be an `httpx.Client` for `VidKraken` or an `httpx.AsyncClient`
207
+ for `AsyncVidKraken`. A client you pass in is not closed by `close()`.
208
+
209
+ ## CLI
210
+
211
+ ```bash
212
+ vidkraken download https://youtu.be/dQw4w9WgXcQ -f 1080 # waits, saves, prints the path
213
+ vidkraken download https://youtu.be/dQw4w9WgXcQ -f audio-mp3 -o music/
214
+ vidkraken download https://youtu.be/dQw4w9WgXcQ --start 30 --end 90 -o clip.mp4 -f 720
215
+ vidkraken download https://youtu.be/dQw4w9WgXcQ --no-wait # prints the job id
216
+ vidkraken download https://youtu.be/dQw4w9WgXcQ --webhook https://example.com/hook --no-wait
217
+ vidkraken status <job_id>
218
+ vidkraken info https://youtu.be/dQw4w9WgXcQ --audio-tracks
219
+ vidkraken me
220
+ vidkraken --version
221
+ python -m vidkraken --help
222
+ ```
223
+
224
+ | Option | Applies to | Effect |
225
+ |---|---|---|
226
+ | `-f/--format` | `download` | one of `1080`, `720`, `480`, `360`, `audio` (default), `audio-aac`, `audio-mp3` |
227
+ | `--start S` / `--end S` | `download` | trim the clip, in seconds |
228
+ | `--language LANG` | `download` | preferred audio track |
229
+ | `-o/--output PATH` | `download` | output file or directory |
230
+ | `--no-wait` | `download` | print the job id and exit |
231
+ | `--webhook URL` | `download` | URL to POST the final status to |
232
+ | `--timeout S`, `--poll-interval S` | `download` | how long to wait and how often to poll |
233
+ | `-q/--quiet` | `download` | no progress output |
234
+ | `--json` | every command | print one JSON document on stdout |
235
+ | `--api-key KEY` | every command | overrides `VIDKRAKEN_API_KEY` |
236
+
237
+ The CLI is designed to be scripted and driven by agents:
238
+
239
+ - **stdout** carries only the result: a path, a job id, a plain report, or one
240
+ JSON document with `--json`. On failure with `--json`, stdout gets
241
+ `{"error", "code", ...}`.
242
+ - **stderr** carries progress and errors such as
243
+ `vidkraken: error: TOO_MANY_PENDING: ...`.
244
+ - It never prompts. Color appears only when stderr is a TTY and `NO_COLOR` is unset.
245
+ - **Exit codes:**
246
+ - `0`: success
247
+ - `1`: API or job error. `status` also exits 1 for a `FAILED` job.
248
+ - `2`: usage error, including a missing API key
249
+ - `130`: interrupted
250
+
251
+ ## Links
252
+
253
+ - Documentation: https://vidkraken.com/docs
254
+ - LLM-friendly reference: https://vidkraken.com/llms.txt
255
+ - OpenAPI spec: https://vidkraken.com/openapi.yaml
256
+ - Support: hello@vidkraken.com
257
+
258
+ ## License
259
+
260
+ MIT © Omnivision Solutions
@@ -0,0 +1,72 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "vidkraken"
7
+ dynamic = ["version"]
8
+ description = "Official Python SDK and CLI for the Vid Kraken YouTube download API"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Vid Kraken", email = "hello@vidkraken.com" }]
14
+ keywords = [
15
+ "vidkraken",
16
+ "youtube",
17
+ "youtube-downloader",
18
+ "video",
19
+ "audio",
20
+ "mp3",
21
+ "download",
22
+ "api",
23
+ "sdk",
24
+ "cli",
25
+ ]
26
+ classifiers = [
27
+ "Development Status :: 4 - Beta",
28
+ "Environment :: Console",
29
+ "Intended Audience :: Developers",
30
+ "Operating System :: OS Independent",
31
+ "Programming Language :: Python",
32
+ "Programming Language :: Python :: 3",
33
+ "Programming Language :: Python :: 3 :: Only",
34
+ "Programming Language :: Python :: 3.9",
35
+ "Programming Language :: Python :: 3.10",
36
+ "Programming Language :: Python :: 3.11",
37
+ "Programming Language :: Python :: 3.12",
38
+ "Programming Language :: Python :: 3.13",
39
+ "Topic :: Internet :: WWW/HTTP",
40
+ "Topic :: Multimedia :: Video",
41
+ "Topic :: Software Development :: Libraries :: Python Modules",
42
+ "Typing :: Typed",
43
+ ]
44
+ dependencies = ["httpx>=0.25"]
45
+
46
+ [project.optional-dependencies]
47
+ dev = ["pytest>=7", "build>=1", "twine>=5", "mypy>=1.8"]
48
+
49
+ [project.urls]
50
+ Homepage = "https://vidkraken.com"
51
+ Documentation = "https://vidkraken.com/docs"
52
+ Source = "https://github.com/vidkraken/vidkraken"
53
+ Issues = "https://github.com/vidkraken/vidkraken/issues"
54
+
55
+ [project.scripts]
56
+ vidkraken = "vidkraken.cli:main"
57
+
58
+ [tool.hatch.version]
59
+ path = "src/vidkraken/_version.py"
60
+
61
+ [tool.hatch.build.targets.wheel]
62
+ packages = ["src/vidkraken"]
63
+
64
+ [tool.hatch.build.targets.sdist]
65
+ include = ["src/vidkraken", "tests", "README.md", "LICENSE", "pyproject.toml"]
66
+
67
+ [tool.pytest.ini_options]
68
+ testpaths = ["tests"]
69
+
70
+ [tool.mypy]
71
+ strict = true
72
+ files = ["src/vidkraken"]