videopiper 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
+ MIT License
2
+
3
+ Copyright (c) 2026 PodWorks3D LLC
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,226 @@
1
+ Metadata-Version: 2.4
2
+ Name: videopiper
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for the Video Piper API: upload, import, edit and stream videos, with webhook verification.
5
+ Author: PodWorks3D LLC
6
+ License: MIT
7
+ Project-URL: Homepage, https://videopiper.com/docs/
8
+ Keywords: video,api,sdk,video-api,transcoding,hls,webhooks,live-streaming
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.9
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Dynamic: license-file
21
+
22
+ # videopiper
23
+
24
+ The official Python SDK for the [Video Piper](https://videopiper.com/) API: upload or import videos, cut and join them, stream live, and check webhooks. Python 3.9 or newer, no dependencies (it uses the standard library), type hints included.
25
+
26
+ ```sh
27
+ pip install videopiper
28
+ ```
29
+
30
+ Keep your API key on your server. To let a browser upload, record or edit, make a short-lived token with the SDK and give the page that (see [Tokens for browsers](#tokens-for-browsers)).
31
+
32
+ ## Quickstart
33
+
34
+ Upload a file, wait for it to process, and get the download link:
35
+
36
+ ```python
37
+ from videopiper import VideoPiper
38
+
39
+ client = VideoPiper() # reads VIDEO_PIPER_API_KEY
40
+
41
+ upload = client.videos.upload_file("lesson.mp4", title="Lesson 1")
42
+ video = client.videos.wait_until_done(upload["videoId"])
43
+
44
+ if video["status"] == "SUCCEEDED":
45
+ print(video["downloadUrl"]) # an MP4 link, valid for 15 minutes
46
+ else:
47
+ print("Processing ended with", video["status"])
48
+ ```
49
+
50
+ `upload_file` creates the video, then for each part asks for its upload URL just before sending that part (the URLs expire after 15 minutes), sends the bytes with no `Authorization` header, and completes the upload. Any file size works; parts go one at a time and only one part is held in memory.
51
+
52
+ ## Setup
53
+
54
+ ```python
55
+ client = VideoPiper(
56
+ api_key="vpk_...", # default: the VIDEO_PIPER_API_KEY environment variable
57
+ base_url="https://api.videopiper.dev/v1", # default: VIDEO_PIPER_API_URL, then this
58
+ timeout=60, # seconds, per request
59
+ max_retries=3, # see Retries
60
+ )
61
+ ```
62
+
63
+ Other options: `upload_timeout` (per part, default 10 minutes), `retry_base_delay`, `max_retry_delay`, `requests_per_second` (default 5, the API's limit per key; 0 turns it off) and `transport` (an object with `send(method, url, headers, body, timeout)`, to use instead of `urllib`).
64
+
65
+ Arguments are Python names (`file_name`, `max_height`) and are sent with the API's camelCase names. Objects inside a request (`trim`, `redactions`, `clips`, `metadata`) are sent exactly as the [API docs](https://videopiper.com/docs/) show them. Responses are plain dicts with the API's keys; `videopiper.types` has `TypedDict`s for the main ones.
66
+
67
+ ## Videos
68
+
69
+ ```python
70
+ # Upload from disk, from bytes, or from a file-like object
71
+ client.videos.upload_file("lesson.mp4", max_height=720, captions=True, on_progress=print)
72
+ client.videos.upload_bytes(data, "clip.mp4", metadata={"lessonId": "123"})
73
+ client.videos.upload_stream(stream, size_bytes, "recording.webm")
74
+
75
+ # Have the API download a public HTTPS URL
76
+ video = client.videos.import_url("https://example.com/tutorial.mp4", max_height=1080)
77
+
78
+ # Status, and the links once it has finished
79
+ status = client.videos.get(video["videoId"]) # playbackUrl, streamUrl, downloadUrl, posterUrl
80
+ done = client.videos.wait_until_done(video["videoId"], timeout=600, on_status=lambda s: print(s["status"]))
81
+
82
+ # Lists: one page, or every video
83
+ page = client.videos.list(limit=25, metadata={"lessonId": "123"})
84
+ for item in client.videos.list_all(limit=100):
85
+ print(item["videoId"], item.get("outcome"))
86
+
87
+ client.videos.update(video_id, title="New title", metadata={"archived": None}) # None clears
88
+ client.videos.reprocess(video_id, max_height=2160, trim={"start": 30}) # a new video from the same upload
89
+ client.videos.cancel(video_id) # stop processing
90
+ client.videos.delete(video_id)
91
+ ```
92
+
93
+ `wait_until_done` checks the status with a growing pause (3 s up to 15 s) until the video reaches a final status and returns it; `FAILED`, `TIMED_OUT` and `ABORTED` are returned, not raised, so look at `["status"]`. It raises `WaitTimeoutError` after `timeout` seconds (default 30 minutes). In production prefer [webhooks](#webhooks) to polling.
94
+
95
+ The steps behind `upload_file` are public too, for streaming uploads and other special cases: `create`, `get_part_urls`, `complete`, `abort` and `heartbeat`.
96
+
97
+ Also on `client.videos`: `list_thumbnails`, `choose_thumbnail`, `get_chapters`, `set_chapters`, `delete_chapters` and `set_embed`.
98
+
99
+ ## Edits
100
+
101
+ Make a new video from clips of finished videos: trim, stitch and splice.
102
+
103
+ ```python
104
+ edit = client.edits.create(
105
+ [
106
+ {"videoId": intro_id, "end": 60},
107
+ {"videoId": lesson_id, "start": 90, "transition": {"type": "fade", "seconds": 0.75}},
108
+ ],
109
+ file_name="Lesson 1 (final)",
110
+ )
111
+ ```
112
+
113
+ ## Dry runs and idempotency
114
+
115
+ `dry_run=True` checks a request without creating or starting anything (it doesn't count toward your daily limit) and returns `{"valid": True, "dryRun": True, ...}`; an edit also reports its length. It works on `videos.create`, `import_url`, `complete`, `reprocess`, `edits.create` and `live_streams.create`.
116
+
117
+ ```python
118
+ check = client.edits.create(clips, dry_run=True)
119
+ print(check["durationSeconds"])
120
+ ```
121
+
122
+ An `idempotency_key` makes a retry safe: the same key with the same request returns the first response again (kept 24 hours) instead of creating a second video.
123
+
124
+ ```python
125
+ client.videos.import_url(url, idempotency_key=f"import-{lesson_id}")
126
+ client.videos.upload_file(path, idempotency_key=f"upload-{lesson_id}") # complete uses "<key>:complete"
127
+ ```
128
+
129
+ ## Tokens for browsers
130
+
131
+ ```python
132
+ upload = client.upload_tokens.create(max_seconds=600, metadata={"userId": "u_1"})
133
+ # <video-piper-recorder upload-token="{upload['uploadToken']}">
134
+ edit = client.edit_tokens.create([a, b], max_seconds=300)
135
+ thumb = client.thumbnail_tokens.create(video_id)
136
+ ```
137
+
138
+ These go with the web components in [`@videopiper/elements`](https://www.npmjs.com/package/@videopiper/elements).
139
+
140
+ ## Usage and account
141
+
142
+ ```python
143
+ usage = client.usage.get("2026-10") # processing, storage and delivery against the plan
144
+ account = client.account.get()
145
+ ```
146
+
147
+ ## Live streams (Pro and Scale)
148
+
149
+ ```python
150
+ stream = client.live_streams.create(title="Product demo", record=True, max_duration_minutes=60)
151
+ print(stream["ingest"]["rtmpsUrl"], stream["ingest"]["streamKey"]) # for OBS: give the key only to the broadcaster
152
+ print(stream["watch"]["url"]) # for viewers
153
+ client.live_streams.end(stream["liveStreamId"]) # the recording becomes a normal video
154
+ ```
155
+
156
+ Also `get`, `list`, `list_all`, `update`, `get_ingest` and `delete`.
157
+
158
+ ## Webhooks
159
+
160
+ Set the endpoint, and keep the signing secret it returns:
161
+
162
+ ```python
163
+ config = client.webhooks.set("https://example.com/hooks/video-piper", events=["video.ready", "video.failed"])
164
+ # config["secret"] is "whsec_..."
165
+ ```
166
+
167
+ Check every delivery with `verify_webhook` on the **raw** body (before JSON parsing). It returns the parsed event, or raises `WebhookVerificationError` if a header is missing, no signature matches (it accepts several, separated by spaces), or the timestamp is more than 5 minutes off (change it with `tolerance_seconds`).
168
+
169
+ ```python
170
+ from flask import Flask, request
171
+ from videopiper import WebhookVerificationError, verify_webhook
172
+
173
+ app = Flask(__name__)
174
+
175
+ @app.post("/hooks/video-piper")
176
+ def video_piper_hook():
177
+ try:
178
+ event = verify_webhook(WEBHOOK_SECRET, request.get_data(), request.headers)
179
+ except WebhookVerificationError:
180
+ return "", 401
181
+ if event["type"] == "video.ready":
182
+ print("ready", event["data"]["videoId"])
183
+ return "", 204
184
+ ```
185
+
186
+ Deliveries are at-least-once: deduplicate on `event["id"]` (the `webhook-id` header). Also on `client.webhooks`: `get`, `delete`, `rotate_secret`, `test` and `deliveries`.
187
+
188
+ ## Errors
189
+
190
+ Every failed call raises `VideoPiperError`:
191
+
192
+ ```python
193
+ from videopiper import VideoPiperError
194
+
195
+ try:
196
+ client.videos.upload_file("lesson.mp4")
197
+ except VideoPiperError as error:
198
+ print(error.status, error.code, error.message, error.request_id)
199
+ if error.code == "PLAN_LIMIT_REACHED":
200
+ ... # out of Free minutes
201
+ for problem in error.details or []: # INVALID_REQUEST lists every problem
202
+ print(problem["field"], problem["message"])
203
+ raise
204
+ ```
205
+
206
+ Program against `code` (`PLAN_LIMIT_REACHED`, `DAILY_LIMIT`, `RATE_LIMITED`, `NOT_FOUND`, ...), not the message. Quote `request_id` when asking for help. A request that got no answer raises `VideoPiperConnectionError` (`status` 0, `code` `CONNECTION_ERROR`); `wait_until_done` running out of time raises `WaitTimeoutError`. All three are `VideoPiperError`s.
207
+
208
+ ## Retries and rate limits
209
+
210
+ Failed requests are sent again, up to `max_retries` times with exponential backoff and jitter, when it is safe: network errors, `RATE_LIMITED` (429) and 500, 502, 503 or 504, on `GET`, `PUT`, `PATCH` and `DELETE`, on the idempotent `POST`s (part URLs, `complete`, `cancel`, `abort`) and on any `POST` that carries an `idempotency_key`. Daily, quota and plan limits are not retried. A `Retry-After` header is honoured (a wait longer than `max_retry_delay`, 60 s by default, raises instead).
211
+
212
+ The client sends at most 5 requests a second (the API's limit per key); lower or raise it with `requests_per_second`.
213
+
214
+ ## Test mode
215
+
216
+ A test key (`vp_test_...`, made in the app under Account, API keys, Create test key) works the same way and is free: it only sees test videos, which are one MP4 of at most 60 seconds and 480p, marked TEST, and deleted after 24 hours. Use it in tests and CI. A test key can't read the account or change the webhook, and `usage.get` is always empty for it.
217
+
218
+ ```python
219
+ client = VideoPiper(os.environ["VIDEO_PIPER_TEST_KEY"])
220
+ ```
221
+
222
+ Test webhook events carry `livemode: false`.
223
+
224
+ ## License
225
+
226
+ MIT. Docs: <https://videopiper.com/docs/>.
@@ -0,0 +1,205 @@
1
+ # videopiper
2
+
3
+ The official Python SDK for the [Video Piper](https://videopiper.com/) API: upload or import videos, cut and join them, stream live, and check webhooks. Python 3.9 or newer, no dependencies (it uses the standard library), type hints included.
4
+
5
+ ```sh
6
+ pip install videopiper
7
+ ```
8
+
9
+ Keep your API key on your server. To let a browser upload, record or edit, make a short-lived token with the SDK and give the page that (see [Tokens for browsers](#tokens-for-browsers)).
10
+
11
+ ## Quickstart
12
+
13
+ Upload a file, wait for it to process, and get the download link:
14
+
15
+ ```python
16
+ from videopiper import VideoPiper
17
+
18
+ client = VideoPiper() # reads VIDEO_PIPER_API_KEY
19
+
20
+ upload = client.videos.upload_file("lesson.mp4", title="Lesson 1")
21
+ video = client.videos.wait_until_done(upload["videoId"])
22
+
23
+ if video["status"] == "SUCCEEDED":
24
+ print(video["downloadUrl"]) # an MP4 link, valid for 15 minutes
25
+ else:
26
+ print("Processing ended with", video["status"])
27
+ ```
28
+
29
+ `upload_file` creates the video, then for each part asks for its upload URL just before sending that part (the URLs expire after 15 minutes), sends the bytes with no `Authorization` header, and completes the upload. Any file size works; parts go one at a time and only one part is held in memory.
30
+
31
+ ## Setup
32
+
33
+ ```python
34
+ client = VideoPiper(
35
+ api_key="vpk_...", # default: the VIDEO_PIPER_API_KEY environment variable
36
+ base_url="https://api.videopiper.dev/v1", # default: VIDEO_PIPER_API_URL, then this
37
+ timeout=60, # seconds, per request
38
+ max_retries=3, # see Retries
39
+ )
40
+ ```
41
+
42
+ Other options: `upload_timeout` (per part, default 10 minutes), `retry_base_delay`, `max_retry_delay`, `requests_per_second` (default 5, the API's limit per key; 0 turns it off) and `transport` (an object with `send(method, url, headers, body, timeout)`, to use instead of `urllib`).
43
+
44
+ Arguments are Python names (`file_name`, `max_height`) and are sent with the API's camelCase names. Objects inside a request (`trim`, `redactions`, `clips`, `metadata`) are sent exactly as the [API docs](https://videopiper.com/docs/) show them. Responses are plain dicts with the API's keys; `videopiper.types` has `TypedDict`s for the main ones.
45
+
46
+ ## Videos
47
+
48
+ ```python
49
+ # Upload from disk, from bytes, or from a file-like object
50
+ client.videos.upload_file("lesson.mp4", max_height=720, captions=True, on_progress=print)
51
+ client.videos.upload_bytes(data, "clip.mp4", metadata={"lessonId": "123"})
52
+ client.videos.upload_stream(stream, size_bytes, "recording.webm")
53
+
54
+ # Have the API download a public HTTPS URL
55
+ video = client.videos.import_url("https://example.com/tutorial.mp4", max_height=1080)
56
+
57
+ # Status, and the links once it has finished
58
+ status = client.videos.get(video["videoId"]) # playbackUrl, streamUrl, downloadUrl, posterUrl
59
+ done = client.videos.wait_until_done(video["videoId"], timeout=600, on_status=lambda s: print(s["status"]))
60
+
61
+ # Lists: one page, or every video
62
+ page = client.videos.list(limit=25, metadata={"lessonId": "123"})
63
+ for item in client.videos.list_all(limit=100):
64
+ print(item["videoId"], item.get("outcome"))
65
+
66
+ client.videos.update(video_id, title="New title", metadata={"archived": None}) # None clears
67
+ client.videos.reprocess(video_id, max_height=2160, trim={"start": 30}) # a new video from the same upload
68
+ client.videos.cancel(video_id) # stop processing
69
+ client.videos.delete(video_id)
70
+ ```
71
+
72
+ `wait_until_done` checks the status with a growing pause (3 s up to 15 s) until the video reaches a final status and returns it; `FAILED`, `TIMED_OUT` and `ABORTED` are returned, not raised, so look at `["status"]`. It raises `WaitTimeoutError` after `timeout` seconds (default 30 minutes). In production prefer [webhooks](#webhooks) to polling.
73
+
74
+ The steps behind `upload_file` are public too, for streaming uploads and other special cases: `create`, `get_part_urls`, `complete`, `abort` and `heartbeat`.
75
+
76
+ Also on `client.videos`: `list_thumbnails`, `choose_thumbnail`, `get_chapters`, `set_chapters`, `delete_chapters` and `set_embed`.
77
+
78
+ ## Edits
79
+
80
+ Make a new video from clips of finished videos: trim, stitch and splice.
81
+
82
+ ```python
83
+ edit = client.edits.create(
84
+ [
85
+ {"videoId": intro_id, "end": 60},
86
+ {"videoId": lesson_id, "start": 90, "transition": {"type": "fade", "seconds": 0.75}},
87
+ ],
88
+ file_name="Lesson 1 (final)",
89
+ )
90
+ ```
91
+
92
+ ## Dry runs and idempotency
93
+
94
+ `dry_run=True` checks a request without creating or starting anything (it doesn't count toward your daily limit) and returns `{"valid": True, "dryRun": True, ...}`; an edit also reports its length. It works on `videos.create`, `import_url`, `complete`, `reprocess`, `edits.create` and `live_streams.create`.
95
+
96
+ ```python
97
+ check = client.edits.create(clips, dry_run=True)
98
+ print(check["durationSeconds"])
99
+ ```
100
+
101
+ An `idempotency_key` makes a retry safe: the same key with the same request returns the first response again (kept 24 hours) instead of creating a second video.
102
+
103
+ ```python
104
+ client.videos.import_url(url, idempotency_key=f"import-{lesson_id}")
105
+ client.videos.upload_file(path, idempotency_key=f"upload-{lesson_id}") # complete uses "<key>:complete"
106
+ ```
107
+
108
+ ## Tokens for browsers
109
+
110
+ ```python
111
+ upload = client.upload_tokens.create(max_seconds=600, metadata={"userId": "u_1"})
112
+ # <video-piper-recorder upload-token="{upload['uploadToken']}">
113
+ edit = client.edit_tokens.create([a, b], max_seconds=300)
114
+ thumb = client.thumbnail_tokens.create(video_id)
115
+ ```
116
+
117
+ These go with the web components in [`@videopiper/elements`](https://www.npmjs.com/package/@videopiper/elements).
118
+
119
+ ## Usage and account
120
+
121
+ ```python
122
+ usage = client.usage.get("2026-10") # processing, storage and delivery against the plan
123
+ account = client.account.get()
124
+ ```
125
+
126
+ ## Live streams (Pro and Scale)
127
+
128
+ ```python
129
+ stream = client.live_streams.create(title="Product demo", record=True, max_duration_minutes=60)
130
+ print(stream["ingest"]["rtmpsUrl"], stream["ingest"]["streamKey"]) # for OBS: give the key only to the broadcaster
131
+ print(stream["watch"]["url"]) # for viewers
132
+ client.live_streams.end(stream["liveStreamId"]) # the recording becomes a normal video
133
+ ```
134
+
135
+ Also `get`, `list`, `list_all`, `update`, `get_ingest` and `delete`.
136
+
137
+ ## Webhooks
138
+
139
+ Set the endpoint, and keep the signing secret it returns:
140
+
141
+ ```python
142
+ config = client.webhooks.set("https://example.com/hooks/video-piper", events=["video.ready", "video.failed"])
143
+ # config["secret"] is "whsec_..."
144
+ ```
145
+
146
+ Check every delivery with `verify_webhook` on the **raw** body (before JSON parsing). It returns the parsed event, or raises `WebhookVerificationError` if a header is missing, no signature matches (it accepts several, separated by spaces), or the timestamp is more than 5 minutes off (change it with `tolerance_seconds`).
147
+
148
+ ```python
149
+ from flask import Flask, request
150
+ from videopiper import WebhookVerificationError, verify_webhook
151
+
152
+ app = Flask(__name__)
153
+
154
+ @app.post("/hooks/video-piper")
155
+ def video_piper_hook():
156
+ try:
157
+ event = verify_webhook(WEBHOOK_SECRET, request.get_data(), request.headers)
158
+ except WebhookVerificationError:
159
+ return "", 401
160
+ if event["type"] == "video.ready":
161
+ print("ready", event["data"]["videoId"])
162
+ return "", 204
163
+ ```
164
+
165
+ Deliveries are at-least-once: deduplicate on `event["id"]` (the `webhook-id` header). Also on `client.webhooks`: `get`, `delete`, `rotate_secret`, `test` and `deliveries`.
166
+
167
+ ## Errors
168
+
169
+ Every failed call raises `VideoPiperError`:
170
+
171
+ ```python
172
+ from videopiper import VideoPiperError
173
+
174
+ try:
175
+ client.videos.upload_file("lesson.mp4")
176
+ except VideoPiperError as error:
177
+ print(error.status, error.code, error.message, error.request_id)
178
+ if error.code == "PLAN_LIMIT_REACHED":
179
+ ... # out of Free minutes
180
+ for problem in error.details or []: # INVALID_REQUEST lists every problem
181
+ print(problem["field"], problem["message"])
182
+ raise
183
+ ```
184
+
185
+ Program against `code` (`PLAN_LIMIT_REACHED`, `DAILY_LIMIT`, `RATE_LIMITED`, `NOT_FOUND`, ...), not the message. Quote `request_id` when asking for help. A request that got no answer raises `VideoPiperConnectionError` (`status` 0, `code` `CONNECTION_ERROR`); `wait_until_done` running out of time raises `WaitTimeoutError`. All three are `VideoPiperError`s.
186
+
187
+ ## Retries and rate limits
188
+
189
+ Failed requests are sent again, up to `max_retries` times with exponential backoff and jitter, when it is safe: network errors, `RATE_LIMITED` (429) and 500, 502, 503 or 504, on `GET`, `PUT`, `PATCH` and `DELETE`, on the idempotent `POST`s (part URLs, `complete`, `cancel`, `abort`) and on any `POST` that carries an `idempotency_key`. Daily, quota and plan limits are not retried. A `Retry-After` header is honoured (a wait longer than `max_retry_delay`, 60 s by default, raises instead).
190
+
191
+ The client sends at most 5 requests a second (the API's limit per key); lower or raise it with `requests_per_second`.
192
+
193
+ ## Test mode
194
+
195
+ A test key (`vp_test_...`, made in the app under Account, API keys, Create test key) works the same way and is free: it only sees test videos, which are one MP4 of at most 60 seconds and 480p, marked TEST, and deleted after 24 hours. Use it in tests and CI. A test key can't read the account or change the webhook, and `usage.get` is always empty for it.
196
+
197
+ ```python
198
+ client = VideoPiper(os.environ["VIDEO_PIPER_TEST_KEY"])
199
+ ```
200
+
201
+ Test webhook events carry `livemode: false`.
202
+
203
+ ## License
204
+
205
+ MIT. Docs: <https://videopiper.com/docs/>.
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "videopiper"
7
+ version = "0.1.0"
8
+ description = "Official Python SDK for the Video Piper API: upload, import, edit and stream videos, with webhook verification."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ authors = [{ name = "PodWorks3D LLC" }]
12
+ requires-python = ">=3.9"
13
+ dependencies = []
14
+ keywords = ["video", "api", "sdk", "video-api", "transcoding", "hls", "webhooks", "live-streaming"]
15
+ classifiers = [
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.9",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Typing :: Typed",
24
+ ]
25
+
26
+ [project.urls]
27
+ Homepage = "https://videopiper.com/docs/"
28
+
29
+ [tool.setuptools]
30
+ packages = ["videopiper"]
31
+
32
+ [tool.setuptools.package-data]
33
+ videopiper = ["py.typed"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+