diff --git a/videodb/__init__.py b/videodb/__init__.py index 51225eb..e61c91a 100644 --- a/videodb/__init__.py +++ b/videodb/__init__.py @@ -24,7 +24,7 @@ logger: logging.Logger = logging.getLogger("videodb") -__version__ = "0.1.0" +__version__ = "0.1.1" __author__ = "videodb" __all__ = [ diff --git a/videodb/_constants.py b/videodb/_constants.py index 50fe068..b7e0503 100644 --- a/videodb/_constants.py +++ b/videodb/_constants.py @@ -39,6 +39,7 @@ class ApiPath: image = "image" stream = "stream" thumbnail = "thumbnail" + thumbnails = "thumbnails" upload_url = "upload_url" transcription = "transcription" index = "index" diff --git a/videodb/_utils/_http_client.py b/videodb/_utils/_http_client.py index 4555411..35eb5c5 100644 --- a/videodb/_utils/_http_client.py +++ b/videodb/_utils/_http_client.py @@ -87,7 +87,7 @@ def _make_request( def _handle_request_error(self, e: requests.exceptions.RequestException) -> None: """Handle request errors""" - + self.show_progress = False if isinstance(e, requests.exceptions.HTTPError): try: error_message = e.response.json().get("message", "Unknown error") @@ -198,8 +198,11 @@ def get( self.show_progress = show_progress return self._make_request(method=self.session.get, path=path, **kwargs) - def post(self, path: str, data=None, **kwargs) -> requests.Response: + def post( + self, path: str, data=None, show_progress: Optional[bool] = False, **kwargs + ) -> requests.Response: """Make a post request""" + self.show_progress = show_progress return self._make_request(self.session.post, path, json=data, **kwargs) def put(self, path: str, data=None, **kwargs) -> requests.Response: diff --git a/videodb/client.py b/videodb/client.py index fd823f9..643807b 100644 --- a/videodb/client.py +++ b/videodb/client.py @@ -3,6 +3,7 @@ from typing import ( Optional, Union, + List, ) from videodb._constants import ( @@ -39,6 +40,50 @@ def get_collection(self, collection_id: Optional[str] = "default") -> Collection collection_data.get("description"), ) + def get_collections(self) -> List[Collection]: + collections_data = self.get(path=ApiPath.collection) + return [ + Collection( + self, + collection.get("id"), + collection.get("name"), + collection.get("description"), + ) + for collection in collections_data.get("collections") + ] + + def create_collection(self, name: str, description: str) -> Collection: + collection_data = self.post( + path=ApiPath.collection, + data={ + "name": name, + "description": description, + }, + ) + self.collection_id = collection_data.get("id", "default") + return Collection( + self, + collection_data.get("id"), + collection_data.get("name"), + collection_data.get("description"), + ) + + def update_collection(self, id: str, name: str, description: str) -> Collection: + collection_data = self.patch( + path=f"{ApiPath.collection}/{id}", + data={ + "name": name, + "description": description, + }, + ) + self.collection_id = collection_data.get("id", "default") + return Collection( + self, + collection_data.get("id"), + collection_data.get("name"), + collection_data.get("description"), + ) + def upload( self, file_path: str = None, diff --git a/videodb/collection.py b/videodb/collection.py index 8073ec9..fcafa4d 100644 --- a/videodb/collection.py +++ b/videodb/collection.py @@ -27,12 +27,25 @@ def __init__(self, _connection, id: str, name: str = None, description: str = No self.name = name self.description = description + def __repr__(self) -> str: + return ( + f"Collection(" + f"id={self.id}, " + f"name={self.name}, " + f"description={self.description})" + ) + def get_videos(self) -> List[Video]: - videos_data = self._connection.get(path=f"{ApiPath.video}") + videos_data = self._connection.get( + path=f"{ApiPath.video}", + params={"collection_id": self.id}, + ) return [Video(self._connection, **video) for video in videos_data.get("videos")] def get_video(self, video_id: str) -> Video: - video_data = self._connection.get(path=f"{ApiPath.video}/{video_id}") + video_data = self._connection.get( + path=f"{ApiPath.video}/{video_id}", params={"collection_id": self.id} + ) return Video(self._connection, **video_data) def delete_video(self, video_id: str) -> None: @@ -43,29 +56,45 @@ def delete_video(self, video_id: str) -> None: :return: None if the delete is successful :rtype: None """ - return self._connection.delete(path=f"{ApiPath.video}/{video_id}") + return self._connection.delete( + path=f"{ApiPath.video}/{video_id}", params={"collection_id": self.id} + ) def get_audios(self) -> List[Audio]: - audios_data = self._connection.get(path=f"{ApiPath.audio}") + audios_data = self._connection.get( + path=f"{ApiPath.audio}", + params={"collection_id": self.id}, + ) return [Audio(self._connection, **audio) for audio in audios_data.get("audios")] def get_audio(self, audio_id: str) -> Audio: - audio_data = self._connection.get(path=f"{ApiPath.audio}/{audio_id}") + audio_data = self._connection.get( + path=f"{ApiPath.audio}/{audio_id}", params={"collection_id": self.id} + ) return Audio(self._connection, **audio_data) def delete_audio(self, audio_id: str) -> None: - return self._connection.delete(path=f"{ApiPath.audio}/{audio_id}") + return self._connection.delete( + path=f"{ApiPath.audio}/{audio_id}", params={"collection_id": self.id} + ) def get_images(self) -> List[Image]: - images_data = self._connection.get(path=f"{ApiPath.image}") + images_data = self._connection.get( + path=f"{ApiPath.image}", + params={"collection_id": self.id}, + ) return [Image(self._connection, **image) for image in images_data.get("images")] def get_image(self, image_id: str) -> Image: - image_data = self._connection.get(path=f"{ApiPath.image}/{image_id}") + image_data = self._connection.get( + path=f"{ApiPath.image}/{image_id}", params={"collection_id": self.id} + ) return Image(self._connection, **image_data) def delete_image(self, image_id: str) -> None: - return self._connection.delete(path=f"{ApiPath.image}/{image_id}") + return self._connection.delete( + path=f"{ApiPath.image}/{image_id}", params={"collection_id": self.id} + ) def search( self, diff --git a/videodb/image.py b/videodb/image.py index 69e0ec3..d7e5e0c 100644 --- a/videodb/image.py +++ b/videodb/image.py @@ -9,13 +9,15 @@ def __init__(self, _connection, id: str, collection_id: str, **kwargs) -> None: self.id = id self.collection_id = collection_id self.name = kwargs.get("name", None) + self.url = kwargs.get("url", None) def __repr__(self) -> str: return ( f"Image(" f"id={self.id}, " f"collection_id={self.collection_id}, " - f"name={self.name})" + f"name={self.name}, " + f"url={self.url})" ) def delete(self) -> None: diff --git a/videodb/video.py b/videodb/video.py index a2bcbab..183a7b9 100644 --- a/videodb/video.py +++ b/videodb/video.py @@ -7,6 +7,7 @@ SubtitleStyle, Workflows, ) +from videodb.image import Image from videodb.search import SearchFactory, SearchResult from videodb.shot import Shot @@ -88,15 +89,31 @@ def generate_stream(self, timeline: Optional[List[Tuple[int, int]]] = None) -> s ) return stream_data.get("stream_url", None) - def generate_thumbnail(self): - if self.thumbnail_url: + def generate_thumbnail(self, time: Optional[float] = None) -> Union[str, Image]: + if self.thumbnail_url and not time: return self.thumbnail_url + + if time: + thumbnail_data = self._connection.post( + path=f"{ApiPath.video}/{self.id}/{ApiPath.thumbnail}", + data={ + "time": time, + }, + ) + return Image(self._connection, **thumbnail_data) + thumbnail_data = self._connection.get( path=f"{ApiPath.video}/{self.id}/{ApiPath.thumbnail}" ) self.thumbnail_url = thumbnail_data.get("thumbnail_url") return self.thumbnail_url + def get_thumbnails(self) -> List[Image]: + thumbnails_data = self._connection.get( + path=f"{ApiPath.video}/{self.id}/{ApiPath.thumbnails}" + ) + return [Image(self._connection, **thumbnail) for thumbnail in thumbnails_data] + def _fetch_transcript(self, force: bool = False) -> None: if self.transcript and not force: return @@ -116,19 +133,21 @@ def get_transcript_text(self, force: bool = False) -> str: self._fetch_transcript(force) return self.transcript_text - def index_spoken_words(self) -> None: + def index_spoken_words(self, force: bool = False, callback_url: str = None) -> None: """Semantic indexing of spoken words in the video :raises InvalidRequestError: If the video is already indexed :return: None if the indexing is successful :rtype: None """ - self._fetch_transcript() self._connection.post( path=f"{ApiPath.video}/{self.id}/{ApiPath.index}", data={ "index_type": IndexType.semantic, + "force": force, + "callback_url": callback_url, }, + show_progress=True, ) def index_scenes(