Skip to content

Commit 953d07b

Browse files
authored
Merge pull request #46 from video-db/release-0-2-16
feat: unified `upload()` API, meeting recording flow, custom headers & gen-AI text (v0.2.16)
2 parents 5416008 + 8a710e3 commit 953d07b

File tree

10 files changed

+385
-35
lines changed

10 files changed

+385
-35
lines changed

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,15 @@ conn = videodb.connect(api_key="YOUR_API_KEY")
7474
Now that you have established a connection to VideoDB, you can upload your videos using `conn.upload()`.
7575
You can directly upload from `youtube`, `any public url`, `S3 bucket` or a `local file path`. A default collection is created when you create your first connection.
7676

77-
`upload` method returns a `Video` object.
77+
`upload` method returns a `Video` object. You can simply pass a single string
78+
representing either a local file path or a URL.
7879

7980
```python
8081
# Upload a video by url
81-
video = conn.upload(url="https://www.youtube.com/watch?v=WDv4AWk0J3U")
82+
video = conn.upload("https://www.youtube.com/watch?v=WDv4AWk0J3U")
8283

8384
# Upload a video from file system
84-
video_f = conn.upload(file_path="./my_video.mp4")
85+
video_f = conn.upload("./my_video.mp4")
8586

8687
```
8788

@@ -147,9 +148,9 @@ In the future you'll be able to index videos using:
147148
coll = conn.get_collection()
148149

149150
# Upload Videos to a collection
150-
coll.upload(url="https://www.youtube.com/watch?v=lsODSDmY4CY")
151-
coll.upload(url="https://www.youtube.com/watch?v=vZ4kOr38JhY")
152-
coll.upload(url="https://www.youtube.com/watch?v=uak_dXHh6s4")
151+
coll.upload("https://www.youtube.com/watch?v=lsODSDmY4CY")
152+
coll.upload("https://www.youtube.com/watch?v=vZ4kOr38JhY")
153+
coll.upload("https://www.youtube.com/watch?v=uak_dXHh6s4")
153154
```
154155

155156
- `conn.get_collection()` : Returns a Collection object; the default collection.

videodb/__about__.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
""" About information for videodb sdk"""
1+
"""About information for videodb sdk"""
22

3-
4-
5-
__version__ = "0.2.15"
3+
__version__ = "0.2.16"
64
__title__ = "videodb"
75
__author__ = "videodb"
86
__email__ = "contact@videodb.io"

videodb/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ def connect(
5858
api_key: str = None,
5959
base_url: Optional[str] = VIDEO_DB_API,
6060
log_level: Optional[int] = logging.INFO,
61+
**kwargs,
6162
) -> Connection:
6263
"""A client for interacting with a videodb via REST API
6364
@@ -76,4 +77,4 @@ def connect(
7677
"No API key provided. Set an API key either as an environment variable (VIDEO_DB_API_KEY) or pass it as an argument."
7778
)
7879

79-
return Connection(api_key, base_url)
80+
return Connection(api_key, base_url, **kwargs)

videodb/_constants.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,17 +77,26 @@ class ApiPath:
7777
alert = "alert"
7878
generate_url = "generate_url"
7979
generate = "generate"
80+
text = "text"
8081
web = "web"
8182
translate = "translate"
8283
dub = "dub"
8384
transcode = "transcode"
85+
meeting = "meeting"
86+
record = "record"
8487

8588

8689
class Status:
8790
processing = "processing"
8891
in_progress = "in progress"
8992

9093

94+
class MeetingStatus:
95+
initializing = "initializing"
96+
processing = "processing"
97+
done = "done"
98+
99+
91100
class HttpClientDefaultValues:
92101
max_retries = 1
93102
timeout = 30

videodb/_upload.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import requests
22

33
from typing import Optional
4+
from urllib.parse import urlparse
45
from requests import HTTPError
6+
import os
57

68

79
from videodb._constants import (
@@ -13,15 +15,50 @@
1315
)
1416

1517

18+
def _is_url(path: str) -> bool:
19+
parsed = urlparse(path)
20+
return all([parsed.scheme in ("http", "https"), parsed.netloc])
21+
22+
1623
def upload(
1724
_connection,
18-
file_path: str = None,
19-
url: str = None,
25+
source: Optional[str] = None,
2026
media_type: Optional[str] = None,
2127
name: Optional[str] = None,
2228
description: Optional[str] = None,
2329
callback_url: Optional[str] = None,
30+
file_path: Optional[str] = None,
31+
url: Optional[str] = None,
2432
) -> dict:
33+
"""Upload a file or URL.
34+
35+
:param _connection: Connection object for API calls
36+
:param str source: Local path or URL of the file to be uploaded
37+
:param str media_type: MediaType object (optional)
38+
:param str name: Name of the file (optional)
39+
:param str description: Description of the file (optional)
40+
:param str callback_url: URL to receive the callback (optional)
41+
:param str file_path: Path to the file to be uploaded
42+
:param str url: URL of the file to be uploaded
43+
:return: Dictionary containing upload response data
44+
:rtype: dict
45+
"""
46+
if source and (file_path or url):
47+
raise VideodbError("source cannot be used with file_path or url")
48+
49+
if source and not file_path and not url:
50+
if _is_url(source):
51+
url = source
52+
else:
53+
file_path = source
54+
if file_path and not url and _is_url(file_path):
55+
url = file_path
56+
file_path = None
57+
58+
if not file_path and url and not _is_url(url) and os.path.exists(url):
59+
file_path = url
60+
url = None
61+
2562
if not file_path and not url:
2663
raise VideodbError("Either file_path or url is required")
2764
if file_path and url:

videodb/_utils/_http_client.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def __init__(
3434
base_url: str,
3535
version: str,
3636
max_retries: Optional[int] = HttpClientDefaultValues.max_retries,
37+
**kwargs,
3738
) -> None:
3839
"""Create a new http client instance
3940
@@ -52,11 +53,13 @@ def __init__(
5253
self.session.mount("http://", adapter)
5354
self.session.mount("https://", adapter)
5455
self.version = version
56+
kwargs = self._format_headers(kwargs)
5557
self.session.headers.update(
5658
{
5759
"x-access-token": api_key,
5860
"x-videodb-client": f"videodb-python/{self.version}",
5961
"Content-Type": "application/json",
62+
**kwargs,
6063
}
6164
)
6265
self.base_url = base_url
@@ -198,6 +201,14 @@ def _parse_response(self, response: requests.Response):
198201
f"Invalid request: {response.text}", response
199202
) from None
200203

204+
def _format_headers(self, headers: dict):
205+
"""Format the headers"""
206+
formatted_headers = {}
207+
for key, value in headers.items():
208+
key = key.lower().replace("_", "-")
209+
formatted_headers[f"x-{key}"] = value
210+
return formatted_headers
211+
201212
def get(
202213
self, path: str, show_progress: Optional[bool] = False, **kwargs
203214
) -> requests.Response:

videodb/client.py

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from videodb.video import Video
1919
from videodb.audio import Audio
2020
from videodb.image import Image
21+
from videodb.meeting import Meeting
2122

2223
from videodb._upload import (
2324
upload,
@@ -29,7 +30,7 @@
2930
class Connection(HttpClient):
3031
"""Connection class to interact with the VideoDB"""
3132

32-
def __init__(self, api_key: str, base_url: str) -> "Connection":
33+
def __init__(self, api_key: str, base_url: str, **kwargs) -> "Connection":
3334
"""Initializes a new instance of the Connection class with specified API credentials.
3435
3536
Note: Users should not initialize this class directly.
@@ -44,7 +45,9 @@ def __init__(self, api_key: str, base_url: str) -> "Connection":
4445
self.api_key = api_key
4546
self.base_url = base_url
4647
self.collection_id = "default"
47-
super().__init__(api_key=api_key, base_url=base_url, version=__version__)
48+
super().__init__(
49+
api_key=api_key, base_url=base_url, version=__version__, **kwargs
50+
)
4851

4952
def get_collection(self, collection_id: Optional[str] = "default") -> Collection:
5053
"""Get a collection object by its ID.
@@ -256,32 +259,35 @@ def get_transcode_details(self, job_id: str) -> dict:
256259

257260
def upload(
258261
self,
259-
file_path: str = None,
260-
url: str = None,
262+
source: Optional[str] = None,
261263
media_type: Optional[str] = None,
262264
name: Optional[str] = None,
263265
description: Optional[str] = None,
264266
callback_url: Optional[str] = None,
267+
file_path: Optional[str] = None,
268+
url: Optional[str] = None,
265269
) -> Union[Video, Audio, Image, None]:
266270
"""Upload a file.
267271
268-
:param str file_path: Path to the file to upload (optional)
269-
:param str url: URL of the file to upload (optional)
272+
:param str source: Local path or URL of the file to upload (optional)
270273
:param MediaType media_type: MediaType object (optional)
271274
:param str name: Name of the file (optional)
272275
:param str description: Description of the file (optional)
273276
:param str callback_url: URL to receive the callback (optional)
277+
:param str file_path: Path to the file to upload (optional)
278+
:param str url: URL of the file to upload (optional)
274279
:return: :class:`Video <Video>`, or :class:`Audio <Audio>`, or :class:`Image <Image>` object
275280
:rtype: Union[ :class:`videodb.video.Video`, :class:`videodb.audio.Audio`, :class:`videodb.image.Image`]
276281
"""
277282
upload_data = upload(
278283
self,
279-
file_path,
280-
url,
281-
media_type,
282-
name,
283-
description,
284-
callback_url,
284+
source,
285+
media_type=media_type,
286+
name=name,
287+
description=description,
288+
callback_url=callback_url,
289+
file_path=file_path,
290+
url=url,
285291
)
286292
media_id = upload_data.get("id", "")
287293
if media_id.startswith("m-"):
@@ -290,3 +296,54 @@ def upload(
290296
return Audio(self, **upload_data)
291297
elif media_id.startswith("img-"):
292298
return Image(self, **upload_data)
299+
300+
def record_meeting(
301+
self,
302+
meeting_url: str,
303+
bot_name: str = None,
304+
bot_image_url: str = None,
305+
meeting_title: str = None,
306+
callback_url: str = None,
307+
callback_data: Optional[dict] = None,
308+
time_zone: str = "UTC",
309+
) -> Meeting:
310+
"""Record a meeting and upload it to the default collection.
311+
312+
:param str meeting_url: Meeting url
313+
:param str bot_name: Name of the recorder bot
314+
:param str bot_image_url: URL of the recorder bot image
315+
:param str meeting_title: Name of the meeting
316+
:param str callback_url: URL to receive callback once recording is done
317+
:param dict callback_data: Data to be sent in the callback (optional)
318+
:param str time_zone: Time zone for the meeting (default ``UTC``)
319+
:return: :class:`Meeting <Meeting>` object representing the recording bot
320+
:rtype: :class:`videodb.meeting.Meeting`
321+
"""
322+
if callback_data is None:
323+
callback_data = {}
324+
325+
response = self.post(
326+
path=f"{ApiPath.collection}/default/{ApiPath.meeting}/{ApiPath.record}",
327+
data={
328+
"meeting_url": meeting_url,
329+
"bot_name": bot_name,
330+
"bot_image_url": bot_image_url,
331+
"meeting_title": meeting_title,
332+
"callback_url": callback_url,
333+
"callback_data": callback_data,
334+
"time_zone": time_zone,
335+
},
336+
)
337+
meeting_id = response.get("meeting_id")
338+
return Meeting(self, id=meeting_id, collection_id="default", **response)
339+
340+
def get_meeting(self, meeting_id: str) -> Meeting:
341+
"""Get a meeting by its ID.
342+
343+
:param str meeting_id: ID of the meeting
344+
:return: :class:`Meeting <Meeting>` object
345+
:rtype: :class:`videodb.meeting.Meeting`
346+
"""
347+
meeting = Meeting(self, id=meeting_id, collection_id="default")
348+
meeting.refresh()
349+
return meeting

0 commit comments

Comments
 (0)