From 0dc72124c8d05f6e6cb54f766d6165539e0b3c71 Mon Sep 17 00:00:00 2001 From: Ankit raj <113342181+ankit-v2-3@users.noreply.github.com> Date: Tue, 11 Jun 2024 19:40:13 +0530 Subject: [PATCH 01/26] feat: add scene scene extractor --- videodb/_constants.py | 7 ++++ videodb/image.py | 32 ++++++++++++++++ videodb/scene.py | 89 +++++++++++++++++++++++++++++++++++++++++++ videodb/video.py | 86 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 videodb/scene.py diff --git a/videodb/_constants.py b/videodb/_constants.py index dd4afcc..c46f357 100644 --- a/videodb/_constants.py +++ b/videodb/_constants.py @@ -22,6 +22,12 @@ class IndexType: scene = "scene" +class SceneExtractionType: + scene = "scene" + time_based = "time_based" + compression_based = "compression_based" + + class Workflows: add_subtitles = "add_subtitles" @@ -51,6 +57,7 @@ class ApiPath: billing = "billing" usage = "usage" invoices = "invoices" + scenes = "scenes" class Status: diff --git a/videodb/image.py b/videodb/image.py index d7e5e0c..c0a2984 100644 --- a/videodb/image.py +++ b/videodb/image.py @@ -22,3 +22,35 @@ def __repr__(self) -> str: def delete(self) -> None: self._connection.delete(f"{ApiPath.image}/{self.id}") + + +class Frame(Image): + def __init__( + self, + _connection, + id: str, + video_id: str, + scene_id: str, + url: str, + frame_no: int, + frame_time: float, + description: str, + ): + super().__init__(_connection=_connection, id=id, collection_id=None, url=url) + self.scene_id = scene_id + self.video_id = video_id + self.frame_no = frame_no + self.frame_time = frame_time + self.description = description + + def __repr__(self) -> str: + return ( + f"Frame(" + f"id={self.id}, " + f"video_id={self.video_id}, " + f"scene_id={self.scene_id}, " + f"url={self.url}, " + f"frame_no={self.frame_no}, " + f"frame_time={self.frame_time}, " + f"description={self.description})" + ) diff --git a/videodb/scene.py b/videodb/scene.py new file mode 100644 index 0000000..6b29483 --- /dev/null +++ b/videodb/scene.py @@ -0,0 +1,89 @@ +from typing import List + +from videodb._constants import ApiPath + +from videodb.image import Frame + + +class SceneExtractionConfig: + def __init__( + self, + time: int = 5, + threshold: int = 20, + frame_count: int = 1, + select_frame: str = "first", + ): + self.time = time + self.threshold = threshold + self.frame_count = frame_count + self.select_frame = select_frame + + def __repr__(self) -> str: + return ( + f"SceneExtractionConfig(" + f"time={self.time}, " + f"threshold={self.threshold}, " + f"frame_count={self.frame_count}, " + f"select_frame={self.select_frame})" + ) + + +class Scene: + def __init__( + self, + _connection, + id: str, + video_id: str, + start: float, + end: float, + frames: List[Frame], + description: str, + ): + self._connection = _connection + self.id = id + self.video_id = video_id + self.start = start + self.end = end + self.frames: List[Frame] = frames + self.description = description + + def __repr__(self) -> str: + return ( + f"Scene(" + f"id={self.id}, " + f"video_id={self.video_id}, " + f"start={self.start}, " + f"end={self.end}, " + f"frames={self.frames}, " + f"description={self.description})" + ) + + +class SceneCollection: + def __init__( + self, + _connection, + id: str, + video_id: str, + config: SceneExtractionConfig, + scenes: List[Scene], + ) -> None: + self._connection = _connection + self.id = id + self.video_id = video_id + self.config: SceneExtractionConfig = config + self.scenes: List[Scene] = scenes + + def __repr__(self) -> str: + return ( + f"SceneCollection(" + f"id={self.id}, " + f"video_id={self.video_id}, " + f"config={self.config.__dict__}, " + f"scenes={self.scenes})" + ) + + def delete(self) -> None: + self._connection.delete( + path=f"{ApiPath.video}/{self.video_id}/{ApiPath.scenes}/{self.id}" + ) diff --git a/videodb/video.py b/videodb/video.py index 685f8a5..a1d2b89 100644 --- a/videodb/video.py +++ b/videodb/video.py @@ -3,11 +3,13 @@ from videodb._constants import ( ApiPath, IndexType, + SceneExtractionType, SearchType, SubtitleStyle, Workflows, ) -from videodb.image import Image +from videodb.image import Image, Frame +from videodb.scene import SceneExtractionConfig, Scene, SceneCollection from videodb.search import SearchFactory, SearchResult from videodb.shot import Shot @@ -26,6 +28,7 @@ def __init__(self, _connection, id: str, collection_id: str, **kwargs) -> None: self.transcript = kwargs.get("transcript", None) self.transcript_text = kwargs.get("transcript_text", None) self.scenes = kwargs.get("scenes", None) + self.scene_collections = kwargs.get("scene_collections", None) def __repr__(self) -> str: return ( @@ -184,6 +187,87 @@ def get_scenes(self) -> Union[list, None]: self.scenes = scene_data return scene_data if scene_data else None + def _format_scene_collection(self, collection_data: dict) -> SceneCollection: + scenes = [] + for scene in collection_data.get("scenes", []): + frames = [] + for frame in scene.get("frames", []): + frame = Frame( + self._connection, + frame.get("frame_id"), + self.id, + scene.get("scene_id"), + frame.get("url"), + frame.get("frame_no"), + frame.get("frame_time"), + frame.get("description"), + ) + frames.append(frame) + scene = Scene( + self._connection, + scene.get("scene_id"), + self.id, + scene.get("start"), + scene.get("end"), + frames, + scene.get("description"), + ) + scenes.append(scene) + + config = collection_data.get("config", {}) + + return SceneCollection( + self._connection, + collection_data.get("scenes_collection_id"), + self.id, + SceneExtractionConfig( + config.get("time"), + config.get("threshold"), + config.get("frame_count"), + config.get("select_frame"), + ), + scenes, + ) + + def extract_scenes( + self, + extraction_type: SceneExtractionType = SceneExtractionType.scene, + extraction_config: SceneExtractionConfig = SceneExtractionConfig(), + force: bool = False, + callback_url: str = None, + ): + scenes_data = self._connection.post( + path=f"{ApiPath.video}/{self.id}/{ApiPath.scenes}", + data={ + "index_type": IndexType.scene, + "extraction_type": extraction_type, + "extraction_config": extraction_config.__dict__, + "force": force, + "callback_url": callback_url, + }, + ) + return self._format_scene_collection(scenes_data.get("scenes_collection")) + + def get_scene_collection(self, collection_id: str): + scenes_data = self._connection.get( + path=f"{ApiPath.video}/{self.id}/{ApiPath.scenes}/{collection_id}" + ) + return self._format_scene_collection(scenes_data.get("scenes_collection")) + + def get_scene_collections(self): + scene_collections_data = self._connection.get( + path=f"{ApiPath.video}/{self.id}/{ApiPath.scenes}" + ) + scene_collections = [] + for collection in scene_collections_data.get("scenes_collections", []): + scene_collections.append(self._format_scene_collection(collection)) + return scene_collections + + def delete_scene_collection(self, collection_id: str) -> None: + self._connection.delete( + path=f"{ApiPath.video}/{self.id}/{ApiPath.scenes}/{collection_id}" + ) + def delete_scene_index(self) -> None: self._connection.post( path=f"{ApiPath.video}/{self.id}/{ApiPath.index}/{ApiPath.delete}", From 854055ed2808eeb609461f3b8e62f3001f4f9de5 Mon Sep 17 00:00:00 2001 From: Ankit raj <113342181+ankit-v2-3@users.noreply.github.com> Date: Tue, 11 Jun 2024 20:24:32 +0530 Subject: [PATCH 02/26] feat: add create scene index --- videodb/_constants.py | 1 + videodb/image.py | 11 +++++++++++ videodb/scene.py | 12 ++++++++++-- videodb/video.py | 28 +++++++++++++++++++++++++++- 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/videodb/_constants.py b/videodb/_constants.py index c46f357..bef5b15 100644 --- a/videodb/_constants.py +++ b/videodb/_constants.py @@ -58,6 +58,7 @@ class ApiPath: usage = "usage" invoices = "invoices" scenes = "scenes" + scene = "scene" class Status: diff --git a/videodb/image.py b/videodb/image.py index c0a2984..aef37e4 100644 --- a/videodb/image.py +++ b/videodb/image.py @@ -54,3 +54,14 @@ def __repr__(self) -> str: f"frame_time={self.frame_time}, " f"description={self.description})" ) + + def to_json(self): + return { + "id": self.id, + "video_id": self.video_id, + "scene_id": self.scene_id, + "url": self.url, + "frame_no": self.frame_no, + "frame_time": self.frame_time, + "description": self.description, + } diff --git a/videodb/scene.py b/videodb/scene.py index 6b29483..b8cebf7 100644 --- a/videodb/scene.py +++ b/videodb/scene.py @@ -31,7 +31,6 @@ def __repr__(self) -> str: class Scene: def __init__( self, - _connection, id: str, video_id: str, start: float, @@ -39,7 +38,6 @@ def __init__( frames: List[Frame], description: str, ): - self._connection = _connection self.id = id self.video_id = video_id self.start = start @@ -58,6 +56,16 @@ def __repr__(self) -> str: f"description={self.description})" ) + def to_json(self): + return { + "id": self.id, + "video_id": self.video_id, + "start": self.start, + "end": self.end, + "frames": [frame.to_json() for frame in self.frames], + "description": self.description, + } + class SceneCollection: def __init__( diff --git a/videodb/video.py b/videodb/video.py index a1d2b89..c2834dd 100644 --- a/videodb/video.py +++ b/videodb/video.py @@ -204,7 +204,6 @@ def _format_scene_collection(self, collection_data: dict) -> SceneCollection: ) frames.append(frame) scene = Scene( - self._connection, scene.get("scene_id"), self.id, scene.get("start"), @@ -268,6 +267,33 @@ def delete_scene_collection(self, collection_id: str) -> None: path=f"{ApiPath.video}/{self.id}/{ApiPath.scenes}/{collection_id}" ) + def create_scene_index( + self, scenes: List[Scene], callback_url: str = None + ) -> List[Scene]: + scenes_data = self._connection.post( + path=f"{ApiPath.video}/{self.id}/{ApiPath.index}/{ApiPath.scene}", + data={ + "scenes": [scene.to_json() for scene in scenes], + "callback_url": callback_url, + }, + ) + return [ + Scene( + scene.get("scene_id"), + self.id, + scene.get("start"), + scene.get("end"), + [], + scene.get("description"), + ) + for scene in scenes_data.get("scene_index_records", []) + ] + + def delete_scene_index(self) -> None: + self._connection.delete( + path=f"{ApiPath.video}/{self.id}/{ApiPath.index}/{ApiPath.scene}" + ) + def delete_scene_index(self) -> None: self._connection.post( path=f"{ApiPath.video}/{self.id}/{ApiPath.index}/{ApiPath.delete}", From 9411c696ad0456d6bd26e433d6dcfac89c701eb7 Mon Sep 17 00:00:00 2001 From: Ankit raj <113342181+ankit-v2-3@users.noreply.github.com> Date: Thu, 13 Jun 2024 10:21:00 +0530 Subject: [PATCH 03/26] fix: response data --- videodb/video.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/videodb/video.py b/videodb/video.py index c2834dd..3b6bd06 100644 --- a/videodb/video.py +++ b/videodb/video.py @@ -257,10 +257,7 @@ def get_scene_collections(self): scene_collections_data = self._connection.get( path=f"{ApiPath.video}/{self.id}/{ApiPath.scenes}" ) - scene_collections = [] - for collection in scene_collections_data.get("scenes_collections", []): - scene_collections.append(self._format_scene_collection(collection)) - return scene_collections + return scene_collections_data.get("scenes_collections", []) def delete_scene_collection(self, collection_id: str) -> None: self._connection.delete( @@ -289,12 +286,25 @@ def create_scene_index( for scene in scenes_data.get("scene_index_records", []) ] + def get_scene_indexes(self) -> List: + index_data = self._connection.get( + path=f"{ApiPath.video}/{self.id}/{ApiPath.index}/{ApiPath.scene}" + ) + + return index_data.get("scene_indexes", []) + + def get_scene_index(self, scene_index_id: str) -> Scene: + index_data = self._connection.get( + path=f"{ApiPath.video}/{self.id}/{ApiPath.index}/{ApiPath.scene}/{scene_index_id}" + ) + return index_data.get("scene_index_records", []) + def delete_scene_index(self) -> None: self._connection.delete( path=f"{ApiPath.video}/{self.id}/{ApiPath.index}/{ApiPath.scene}" ) - def delete_scene_index(self) -> None: + def delete_index(self) -> None: self._connection.post( path=f"{ApiPath.video}/{self.id}/{ApiPath.index}/{ApiPath.delete}", data={ From e25bb07aab2f2cf43790edce694963a4f21df118 Mon Sep 17 00:00:00 2001 From: Ankit raj <113342181+ankit-v2-3@users.noreply.github.com> Date: Thu, 13 Jun 2024 10:35:21 +0530 Subject: [PATCH 04/26] fix: create_scene_index --- videodb/video.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/videodb/video.py b/videodb/video.py index 3b6bd06..15f081e 100644 --- a/videodb/video.py +++ b/videodb/video.py @@ -265,12 +265,20 @@ def delete_scene_collection(self, collection_id: str) -> None: ) def create_scene_index( - self, scenes: List[Scene], callback_url: str = None + self, + scenes: List[Scene] = None, + extraction_type: SceneExtractionType = None, + extraction_config: SceneExtractionConfig = None, + force: bool = False, + callback_url: str = None, ) -> List[Scene]: scenes_data = self._connection.post( path=f"{ApiPath.video}/{self.id}/{ApiPath.index}/{ApiPath.scene}", data={ "scenes": [scene.to_json() for scene in scenes], + "extraction_type": extraction_type, + "extraction_config": extraction_config.__dict__, + "force": force, "callback_url": callback_url, }, ) From 9f261a001bf16b74c9840b2d57d3af2c736323e2 Mon Sep 17 00:00:00 2001 From: Ankit raj <113342181+ankit-v2-3@users.noreply.github.com> Date: Thu, 13 Jun 2024 10:37:51 +0530 Subject: [PATCH 05/26] fix: create_scene_index --- videodb/video.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/videodb/video.py b/videodb/video.py index 15f081e..becbc4b 100644 --- a/videodb/video.py +++ b/videodb/video.py @@ -266,9 +266,9 @@ def delete_scene_collection(self, collection_id: str) -> None: def create_scene_index( self, + extraction_type: SceneExtractionType = SceneExtractionType.scene, + extraction_config: SceneExtractionConfig = SceneExtractionConfig(), scenes: List[Scene] = None, - extraction_type: SceneExtractionType = None, - extraction_config: SceneExtractionConfig = None, force: bool = False, callback_url: str = None, ) -> List[Scene]: From 08b6d5f6add718b87ac9185be900fab7c3ed69cc Mon Sep 17 00:00:00 2001 From: Ankit raj <113342181+ankit-v2-3@users.noreply.github.com> Date: Thu, 13 Jun 2024 10:50:47 +0530 Subject: [PATCH 06/26] fix: scene index --- videodb/video.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/videodb/video.py b/videodb/video.py index becbc4b..76864a8 100644 --- a/videodb/video.py +++ b/videodb/video.py @@ -268,7 +268,7 @@ def create_scene_index( self, extraction_type: SceneExtractionType = SceneExtractionType.scene, extraction_config: SceneExtractionConfig = SceneExtractionConfig(), - scenes: List[Scene] = None, + scenes: List[Scene] = [], force: bool = False, callback_url: str = None, ) -> List[Scene]: @@ -307,12 +307,12 @@ def get_scene_index(self, scene_index_id: str) -> Scene: ) return index_data.get("scene_index_records", []) - def delete_scene_index(self) -> None: + def delete_index(self, scene_index_id: str) -> None: self._connection.delete( - path=f"{ApiPath.video}/{self.id}/{ApiPath.index}/{ApiPath.scene}" + path=f"{ApiPath.video}/{self.id}/{ApiPath.index}/{ApiPath.scene}/{scene_index_id}" ) - def delete_index(self) -> None: + def delete_scene_index(self) -> None: self._connection.post( path=f"{ApiPath.video}/{self.id}/{ApiPath.index}/{ApiPath.delete}", data={ From ba4f07ec80a02a150b7a5c7a9fbc8dac73dce9cc Mon Sep 17 00:00:00 2001 From: Ankit raj <113342181+ankit-v2-3@users.noreply.github.com> Date: Thu, 13 Jun 2024 15:35:38 +0530 Subject: [PATCH 07/26] fix: remove SceneExtractionConfig --- videodb/scene.py | 29 +++-------------------------- videodb/video.py | 19 ++++++------------- 2 files changed, 9 insertions(+), 39 deletions(-) diff --git a/videodb/scene.py b/videodb/scene.py index b8cebf7..26b9cff 100644 --- a/videodb/scene.py +++ b/videodb/scene.py @@ -5,29 +5,6 @@ from videodb.image import Frame -class SceneExtractionConfig: - def __init__( - self, - time: int = 5, - threshold: int = 20, - frame_count: int = 1, - select_frame: str = "first", - ): - self.time = time - self.threshold = threshold - self.frame_count = frame_count - self.select_frame = select_frame - - def __repr__(self) -> str: - return ( - f"SceneExtractionConfig(" - f"time={self.time}, " - f"threshold={self.threshold}, " - f"frame_count={self.frame_count}, " - f"select_frame={self.select_frame})" - ) - - class Scene: def __init__( self, @@ -73,13 +50,13 @@ def __init__( _connection, id: str, video_id: str, - config: SceneExtractionConfig, + config: dict, scenes: List[Scene], ) -> None: self._connection = _connection self.id = id self.video_id = video_id - self.config: SceneExtractionConfig = config + self.config: dict = config self.scenes: List[Scene] = scenes def __repr__(self) -> str: @@ -87,7 +64,7 @@ def __repr__(self) -> str: f"SceneCollection(" f"id={self.id}, " f"video_id={self.video_id}, " - f"config={self.config.__dict__}, " + f"config={self.config}, " f"scenes={self.scenes})" ) diff --git a/videodb/video.py b/videodb/video.py index 76864a8..4a282b9 100644 --- a/videodb/video.py +++ b/videodb/video.py @@ -9,7 +9,7 @@ Workflows, ) from videodb.image import Image, Frame -from videodb.scene import SceneExtractionConfig, Scene, SceneCollection +from videodb.scene import Scene, SceneCollection from videodb.search import SearchFactory, SearchResult from videodb.shot import Shot @@ -213,25 +213,18 @@ def _format_scene_collection(self, collection_data: dict) -> SceneCollection: ) scenes.append(scene) - config = collection_data.get("config", {}) - return SceneCollection( self._connection, collection_data.get("scenes_collection_id"), self.id, - SceneExtractionConfig( - config.get("time"), - config.get("threshold"), - config.get("frame_count"), - config.get("select_frame"), - ), + collection_data.get("config", {}), scenes, ) def extract_scenes( self, extraction_type: SceneExtractionType = SceneExtractionType.scene, - extraction_config: SceneExtractionConfig = SceneExtractionConfig(), + extraction_config: dict = {}, force: bool = False, callback_url: str = None, ): @@ -240,7 +233,7 @@ def extract_scenes( data={ "index_type": IndexType.scene, "extraction_type": extraction_type, - "extraction_config": extraction_config.__dict__, + "extraction_config": extraction_config, "force": force, "callback_url": callback_url, }, @@ -267,7 +260,7 @@ def delete_scene_collection(self, collection_id: str) -> None: def create_scene_index( self, extraction_type: SceneExtractionType = SceneExtractionType.scene, - extraction_config: SceneExtractionConfig = SceneExtractionConfig(), + extraction_config: dict = {}, scenes: List[Scene] = [], force: bool = False, callback_url: str = None, @@ -277,7 +270,7 @@ def create_scene_index( data={ "scenes": [scene.to_json() for scene in scenes], "extraction_type": extraction_type, - "extraction_config": extraction_config.__dict__, + "extraction_config": extraction_config, "force": force, "callback_url": callback_url, }, From 577c7788c0259bbf5e9552b892453be74404f990 Mon Sep 17 00:00:00 2001 From: Ankit raj <113342181+ankit-v2-3@users.noreply.github.com> Date: Thu, 13 Jun 2024 16:31:00 +0530 Subject: [PATCH 08/26] feat: add timeout exception --- videodb/_utils/_http_client.py | 5 +++-- videodb/exceptions.py | 10 ++++++++++ videodb/video.py | 12 +----------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/videodb/_utils/_http_client.py b/videodb/_utils/_http_client.py index 35eb5c5..0b4ae80 100644 --- a/videodb/_utils/_http_client.py +++ b/videodb/_utils/_http_client.py @@ -19,6 +19,7 @@ from videodb.exceptions import ( AuthenticationError, InvalidRequestError, + RequestTimeoutError, ) logger = logging.getLogger(__name__) @@ -109,8 +110,8 @@ def _handle_request_error(self, e: requests.exceptions.RequestException) -> None ) from None elif isinstance(e, requests.exceptions.Timeout): - raise InvalidRequestError( - "Invalid request: Request timed out", e.response + raise RequestTimeoutError( + "Timeout error: Request timed out", e.response ) from None elif isinstance(e, requests.exceptions.ConnectionError): diff --git a/videodb/exceptions.py b/videodb/exceptions.py index 16749b4..2b70008 100644 --- a/videodb/exceptions.py +++ b/videodb/exceptions.py @@ -37,6 +37,16 @@ def __init__(self, message, response=None): self.response = response +class RequestTimeoutError(VideodbError): + """ + Raised when a request times out. + """ + + def __init__(self, message, response=None): + super(RequestTimeoutError, self).__init__(message) + self.response = response + + class SearchError(VideodbError): """ Raised when a search is invalid. diff --git a/videodb/video.py b/videodb/video.py index 4a282b9..469c526 100644 --- a/videodb/video.py +++ b/videodb/video.py @@ -275,17 +275,7 @@ def create_scene_index( "callback_url": callback_url, }, ) - return [ - Scene( - scene.get("scene_id"), - self.id, - scene.get("start"), - scene.get("end"), - [], - scene.get("description"), - ) - for scene in scenes_data.get("scene_index_records", []) - ] + return scenes_data.get("scene_index_records", []) def get_scene_indexes(self) -> List: index_data = self._connection.get( From 55fe2fd83bc1a2b89b54dd8bfa8d639b631453fd Mon Sep 17 00:00:00 2001 From: Ankit raj <113342181+ankit-v2-3@users.noreply.github.com> Date: Thu, 13 Jun 2024 19:25:56 +0530 Subject: [PATCH 09/26] feat: add **kwargs in search --- videodb/search.py | 1 + videodb/video.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/videodb/search.py b/videodb/search.py index 81ff27b..cab4b6d 100644 --- a/videodb/search.py +++ b/videodb/search.py @@ -198,6 +198,7 @@ def search_inside_video( "query": query, "score_threshold": score_threshold, "result_threshold": result_threshold, + **kwargs, }, ) return SearchResult(self._connection, **search_data) diff --git a/videodb/video.py b/videodb/video.py index 469c526..06401fb 100644 --- a/videodb/video.py +++ b/videodb/video.py @@ -53,6 +53,7 @@ def search( result_threshold: Optional[int] = None, score_threshold: Optional[int] = None, dynamic_score_percentage: Optional[int] = None, + **kwargs, ) -> SearchResult: search = SearchFactory(self._connection).get_search(search_type) return search.search_inside_video( @@ -61,6 +62,7 @@ def search( result_threshold=result_threshold, score_threshold=score_threshold, dynamic_score_percentage=dynamic_score_percentage, + **kwargs, ) def delete(self) -> None: From c4a922e023617671804ca77deff052bca20d5786 Mon Sep 17 00:00:00 2001 From: Ankit raj <113342181+ankit-v2-3@users.noreply.github.com> Date: Fri, 14 Jun 2024 11:54:00 +0530 Subject: [PATCH 10/26] fix: scene and frame class --- videodb/image.py | 4 ---- videodb/scene.py | 4 ++-- videodb/video.py | 13 ++++++------- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/videodb/image.py b/videodb/image.py index aef37e4..c1bd2ef 100644 --- a/videodb/image.py +++ b/videodb/image.py @@ -32,14 +32,12 @@ def __init__( video_id: str, scene_id: str, url: str, - frame_no: int, frame_time: float, description: str, ): super().__init__(_connection=_connection, id=id, collection_id=None, url=url) self.scene_id = scene_id self.video_id = video_id - self.frame_no = frame_no self.frame_time = frame_time self.description = description @@ -50,7 +48,6 @@ def __repr__(self) -> str: f"video_id={self.video_id}, " f"scene_id={self.scene_id}, " f"url={self.url}, " - f"frame_no={self.frame_no}, " f"frame_time={self.frame_time}, " f"description={self.description})" ) @@ -61,7 +58,6 @@ def to_json(self): "video_id": self.video_id, "scene_id": self.scene_id, "url": self.url, - "frame_no": self.frame_no, "frame_time": self.frame_time, "description": self.description, } diff --git a/videodb/scene.py b/videodb/scene.py index 26b9cff..182bfe8 100644 --- a/videodb/scene.py +++ b/videodb/scene.py @@ -8,12 +8,12 @@ class Scene: def __init__( self, - id: str, video_id: str, start: float, end: float, - frames: List[Frame], description: str, + id: str = None, + frames: List[Frame] = [], ): self.id = id self.video_id = video_id diff --git a/videodb/video.py b/videodb/video.py index 06401fb..219022c 100644 --- a/videodb/video.py +++ b/videodb/video.py @@ -200,18 +200,17 @@ def _format_scene_collection(self, collection_data: dict) -> SceneCollection: self.id, scene.get("scene_id"), frame.get("url"), - frame.get("frame_no"), frame.get("frame_time"), frame.get("description"), ) frames.append(frame) scene = Scene( - scene.get("scene_id"), - self.id, - scene.get("start"), - scene.get("end"), - frames, - scene.get("description"), + video_id=self.id, + start=scene.get("start"), + end=scene.get("end"), + description=scene.get("description"), + id=scene.get("scene_id"), + frames=frames, ) scenes.append(scene) From 7036ece226726a9897ff9a5eb42aa755ffc7d62a Mon Sep 17 00:00:00 2001 From: ashish-spext Date: Fri, 14 Jun 2024 15:15:34 +0530 Subject: [PATCH 11/26] Index Interface Updates - Deleted old scene_index and replaced with renaming new create_scene_index - Added missing prompt - Synced name of scene_collection[s] with server - Replaced delete_scene_index with renaming new delete_index --- videodb/video.py | 40 ++++++++-------------------------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/videodb/video.py b/videodb/video.py index 219022c..facb2bc 100644 --- a/videodb/video.py +++ b/videodb/video.py @@ -161,22 +161,6 @@ def index_spoken_words( show_progress=True, ) - def index_scenes( - self, - force: bool = False, - prompt: str = None, - callback_url: str = None, - ) -> None: - self._connection.post( - path=f"{ApiPath.video}/{self.id}/{ApiPath.index}", - data={ - "index_type": IndexType.scene, - "force": force, - "prompt": prompt, - "callback_url": callback_url, - }, - ) - def get_scenes(self) -> Union[list, None]: if self.scenes: return self.scenes @@ -216,7 +200,7 @@ def _format_scene_collection(self, collection_data: dict) -> SceneCollection: return SceneCollection( self._connection, - collection_data.get("scenes_collection_id"), + collection_data.get("scene_collection_id"), self.id, collection_data.get("config", {}), scenes, @@ -232,36 +216,36 @@ def extract_scenes( scenes_data = self._connection.post( path=f"{ApiPath.video}/{self.id}/{ApiPath.scenes}", data={ - "index_type": IndexType.scene, "extraction_type": extraction_type, "extraction_config": extraction_config, "force": force, "callback_url": callback_url, }, ) - return self._format_scene_collection(scenes_data.get("scenes_collection")) + return self._format_scene_collection(scenes_data.get("scene_collection")) def get_scene_collection(self, collection_id: str): scenes_data = self._connection.get( path=f"{ApiPath.video}/{self.id}/{ApiPath.scenes}/{collection_id}" ) - return self._format_scene_collection(scenes_data.get("scenes_collection")) + return self._format_scene_collection(scenes_data.get("scene_collection")) def get_scene_collections(self): scene_collections_data = self._connection.get( path=f"{ApiPath.video}/{self.id}/{ApiPath.scenes}" ) - return scene_collections_data.get("scenes_collections", []) + return scene_collections_data.get("scene_collections", []) def delete_scene_collection(self, collection_id: str) -> None: self._connection.delete( path=f"{ApiPath.video}/{self.id}/{ApiPath.scenes}/{collection_id}" ) - def create_scene_index( + def index_scenes( self, extraction_type: SceneExtractionType = SceneExtractionType.scene, extraction_config: dict = {}, + prompt: str = None, scenes: List[Scene] = [], force: bool = False, callback_url: str = None, @@ -272,6 +256,7 @@ def create_scene_index( "scenes": [scene.to_json() for scene in scenes], "extraction_type": extraction_type, "extraction_config": extraction_config, + "prompt": prompt, "force": force, "callback_url": callback_url, }, @@ -291,20 +276,11 @@ def get_scene_index(self, scene_index_id: str) -> Scene: ) return index_data.get("scene_index_records", []) - def delete_index(self, scene_index_id: str) -> None: + def delete_scene_index(self, scene_index_id: str) -> None: self._connection.delete( path=f"{ApiPath.video}/{self.id}/{ApiPath.index}/{ApiPath.scene}/{scene_index_id}" ) - def delete_scene_index(self) -> None: - self._connection.post( - path=f"{ApiPath.video}/{self.id}/{ApiPath.index}/{ApiPath.delete}", - data={ - "index_type": IndexType.scene, - }, - ) - self.scenes = None - def add_subtitle(self, style: SubtitleStyle = SubtitleStyle()) -> str: if not isinstance(style, SubtitleStyle): raise ValueError("style must be of type SubtitleStyle") From cc0cbefbc1d8d99df1c7b6cb483ad0262fbe771f Mon Sep 17 00:00:00 2001 From: Ankit raj <113342181+ankit-v2-3@users.noreply.github.com> Date: Fri, 14 Jun 2024 15:51:32 +0530 Subject: [PATCH 12/26] fix: SceneExtractionType --- videodb/__init__.py | 2 ++ videodb/_constants.py | 5 ++--- videodb/video.py | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/videodb/__init__.py b/videodb/__init__.py index 1657f43..22fbad6 100644 --- a/videodb/__init__.py +++ b/videodb/__init__.py @@ -7,6 +7,7 @@ from videodb._utils._video import play_stream from videodb._constants import ( VIDEO_DB_API, + SceneExtractionType, MediaType, SearchType, SubtitleAlignment, @@ -39,6 +40,7 @@ "SubtitleBorderStyle", "SubtitleStyle", "TextStyle", + "SceneExtractionType", ] diff --git a/videodb/_constants.py b/videodb/_constants.py index bef5b15..e0ed017 100644 --- a/videodb/_constants.py +++ b/videodb/_constants.py @@ -23,9 +23,8 @@ class IndexType: class SceneExtractionType: - scene = "scene" - time_based = "time_based" - compression_based = "compression_based" + scene_based = "scene" + time_based = "time" class Workflows: diff --git a/videodb/video.py b/videodb/video.py index facb2bc..b69de4e 100644 --- a/videodb/video.py +++ b/videodb/video.py @@ -208,7 +208,7 @@ def _format_scene_collection(self, collection_data: dict) -> SceneCollection: def extract_scenes( self, - extraction_type: SceneExtractionType = SceneExtractionType.scene, + extraction_type: SceneExtractionType = SceneExtractionType.scene_based, extraction_config: dict = {}, force: bool = False, callback_url: str = None, @@ -243,7 +243,7 @@ def delete_scene_collection(self, collection_id: str) -> None: def index_scenes( self, - extraction_type: SceneExtractionType = SceneExtractionType.scene, + extraction_type: SceneExtractionType = SceneExtractionType.scene_based, extraction_config: dict = {}, prompt: str = None, scenes: List[Scene] = [], From b1110e9d620f504ce254855c09a58927152f64d6 Mon Sep 17 00:00:00 2001 From: Ankit raj <113342181+ankit-v2-3@users.noreply.github.com> Date: Mon, 17 Jun 2024 15:41:08 +0530 Subject: [PATCH 13/26] fix: semantic search --- videodb/collection.py | 4 ++-- videodb/search.py | 21 +++++++++++++-------- videodb/video.py | 13 ++++++------- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/videodb/collection.py b/videodb/collection.py index fcafa4d..040979f 100644 --- a/videodb/collection.py +++ b/videodb/collection.py @@ -101,8 +101,8 @@ def search( query: str, search_type: Optional[str] = SearchType.semantic, result_threshold: Optional[int] = None, - score_threshold: Optional[int] = None, - dynamic_score_percentage: Optional[int] = None, + score_threshold: Optional[float] = None, + dynamic_score_percentage: Optional[float] = None, ) -> SearchResult: search = SearchFactory(self._connection).get_search(search_type) return search.search_inside_collection( diff --git a/videodb/search.py b/videodb/search.py index cab4b6d..e9e6942 100644 --- a/videodb/search.py +++ b/videodb/search.py @@ -110,8 +110,8 @@ def search_inside_video( video_id: str, query: str, result_threshold: Optional[int] = None, - score_threshold: Optional[int] = None, - dynamic_score_percentage: Optional[int] = None, + score_threshold: Optional[float] = None, + dynamic_score_percentage: Optional[float] = None, **kwargs, ): search_data = self._connection.post( @@ -123,6 +123,8 @@ def search_inside_video( or SemanticSearchDefaultValues.score_threshold, "result_threshold": result_threshold or SemanticSearchDefaultValues.result_threshold, + "dynamic_score_percentage": dynamic_score_percentage, + **kwargs, }, ) return SearchResult(self._connection, **search_data) @@ -132,8 +134,8 @@ def search_inside_collection( collection_id: str, query: str, result_threshold: Optional[int] = None, - score_threshold: Optional[int] = None, - dynamic_score_percentage: Optional[int] = None, + score_threshold: Optional[float] = None, + dynamic_score_percentage: Optional[float] = None, **kwargs, ): search_data = self._connection.post( @@ -145,6 +147,8 @@ def search_inside_collection( or SemanticSearchDefaultValues.score_threshold, "result_threshold": result_threshold or SemanticSearchDefaultValues.result_threshold, + "dynamic_score_percentage": dynamic_score_percentage, + **kwargs, }, ) return SearchResult(self._connection, **search_data) @@ -159,8 +163,8 @@ def search_inside_video( video_id: str, query: str, result_threshold: Optional[int] = None, - score_threshold: Optional[int] = None, - dynamic_score_percentage: Optional[int] = None, + score_threshold: Optional[float] = None, + dynamic_score_percentage: Optional[float] = None, **kwargs, ): search_data = self._connection.post( @@ -187,8 +191,8 @@ def search_inside_video( video_id: str, query: str, result_threshold: Optional[int] = None, - score_threshold: Optional[int] = None, - dynamic_score_percentage: Optional[int] = None, + score_threshold: Optional[float] = None, + dynamic_score_percentage: Optional[float] = None, **kwargs, ): search_data = self._connection.post( @@ -198,6 +202,7 @@ def search_inside_video( "query": query, "score_threshold": score_threshold, "result_threshold": result_threshold, + "dynamic_score_percentage": dynamic_score_percentage, **kwargs, }, ) diff --git a/videodb/video.py b/videodb/video.py index b69de4e..1331b97 100644 --- a/videodb/video.py +++ b/videodb/video.py @@ -28,7 +28,6 @@ def __init__(self, _connection, id: str, collection_id: str, **kwargs) -> None: self.transcript = kwargs.get("transcript", None) self.transcript_text = kwargs.get("transcript_text", None) self.scenes = kwargs.get("scenes", None) - self.scene_collections = kwargs.get("scene_collections", None) def __repr__(self) -> str: return ( @@ -51,8 +50,8 @@ def search( query: str, search_type: Optional[str] = SearchType.semantic, result_threshold: Optional[int] = None, - score_threshold: Optional[int] = None, - dynamic_score_percentage: Optional[int] = None, + score_threshold: Optional[float] = None, + dynamic_score_percentage: Optional[float] = None, **kwargs, ) -> SearchResult: search = SearchFactory(self._connection).get_search(search_type) @@ -173,9 +172,9 @@ def get_scenes(self) -> Union[list, None]: self.scenes = scene_data return scene_data if scene_data else None - def _format_scene_collection(self, collection_data: dict) -> SceneCollection: + def _format_scene_collection(self, scene_collection_data: dict) -> SceneCollection: scenes = [] - for scene in collection_data.get("scenes", []): + for scene in scene_collection_data.get("scenes", []): frames = [] for frame in scene.get("frames", []): frame = Frame( @@ -200,9 +199,9 @@ def _format_scene_collection(self, collection_data: dict) -> SceneCollection: return SceneCollection( self._connection, - collection_data.get("scene_collection_id"), + scene_collection_data.get("scene_collection_id"), self.id, - collection_data.get("config", {}), + scene_collection_data.get("config", {}), scenes, ) From ed56178292762eba85f275ea7dedee3b19473c1d Mon Sep 17 00:00:00 2001 From: Ankit raj <113342181+ankit-v2-3@users.noreply.github.com> Date: Tue, 18 Jun 2024 18:25:30 +0530 Subject: [PATCH 14/26] feat: return Scene object --- videodb/scene.py | 3 +++ videodb/video.py | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/videodb/scene.py b/videodb/scene.py index 182bfe8..03086c0 100644 --- a/videodb/scene.py +++ b/videodb/scene.py @@ -13,6 +13,7 @@ def __init__( end: float, description: str, id: str = None, + index_id: str = None, frames: List[Frame] = [], ): self.id = id @@ -20,6 +21,7 @@ def __init__( self.start = start self.end = end self.frames: List[Frame] = frames + self.index_id = index_id self.description = description def __repr__(self) -> str: @@ -30,6 +32,7 @@ def __repr__(self) -> str: f"start={self.start}, " f"end={self.end}, " f"frames={self.frames}, " + f"index_id={self.index_id}, " f"description={self.description})" ) diff --git a/videodb/video.py b/videodb/video.py index 1331b97..5889443 100644 --- a/videodb/video.py +++ b/videodb/video.py @@ -194,6 +194,7 @@ def _format_scene_collection(self, scene_collection_data: dict) -> SceneCollecti description=scene.get("description"), id=scene.get("scene_id"), frames=frames, + index_id=scene.get("index_id"), ) scenes.append(scene) @@ -221,6 +222,8 @@ def extract_scenes( "callback_url": callback_url, }, ) + if not scenes_data: + return None return self._format_scene_collection(scenes_data.get("scene_collection")) def get_scene_collection(self, collection_id: str): @@ -248,7 +251,7 @@ def index_scenes( scenes: List[Scene] = [], force: bool = False, callback_url: str = None, - ) -> List[Scene]: + ) -> List[Scene] or None: scenes_data = self._connection.post( path=f"{ApiPath.video}/{self.id}/{ApiPath.index}/{ApiPath.scene}", data={ @@ -260,7 +263,18 @@ def index_scenes( "callback_url": callback_url, }, ) - return scenes_data.get("scene_index_records", []) + if not scenes_data: + return None + return [ + Scene( + video_id=self.id, + start=scene.get("start"), + end=scene.get("end"), + index_id=scene.get("scene_index_id"), + description=scene.get("description"), + ) + for scene in scenes_data.get("scene_index_records", []) + ] def get_scene_indexes(self) -> List: index_data = self._connection.get( @@ -269,11 +283,23 @@ def get_scene_indexes(self) -> List: return index_data.get("scene_indexes", []) - def get_scene_index(self, scene_index_id: str) -> Scene: + def get_scene_index(self, scene_index_id: str) -> List[Scene] or None: index_data = self._connection.get( path=f"{ApiPath.video}/{self.id}/{ApiPath.index}/{ApiPath.scene}/{scene_index_id}" ) - return index_data.get("scene_index_records", []) + index_records = index_data.get("scene_index_records", []) + if not index_records: + return None + return [ + Scene( + video_id=self.id, + start=scene.get("start"), + end=scene.get("end"), + index_id=scene.get("scene_index_id"), + description=scene.get("description"), + ) + for scene in index_records + ] def delete_scene_index(self, scene_index_id: str) -> None: self._connection.delete( From 09df3559f20093f566c0e518b43b057e90e9a15a Mon Sep 17 00:00:00 2001 From: Ankit raj <113342181+ankit-v2-3@users.noreply.github.com> Date: Wed, 19 Jun 2024 17:48:54 +0530 Subject: [PATCH 15/26] refactor: scene class, method names --- videodb/scene.py | 3 --- videodb/video.py | 17 +++-------------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/videodb/scene.py b/videodb/scene.py index 03086c0..182bfe8 100644 --- a/videodb/scene.py +++ b/videodb/scene.py @@ -13,7 +13,6 @@ def __init__( end: float, description: str, id: str = None, - index_id: str = None, frames: List[Frame] = [], ): self.id = id @@ -21,7 +20,6 @@ def __init__( self.start = start self.end = end self.frames: List[Frame] = frames - self.index_id = index_id self.description = description def __repr__(self) -> str: @@ -32,7 +30,6 @@ def __repr__(self) -> str: f"start={self.start}, " f"end={self.end}, " f"frames={self.frames}, " - f"index_id={self.index_id}, " f"description={self.description})" ) diff --git a/videodb/video.py b/videodb/video.py index 5889443..dad299f 100644 --- a/videodb/video.py +++ b/videodb/video.py @@ -194,7 +194,6 @@ def _format_scene_collection(self, scene_collection_data: dict) -> SceneCollecti description=scene.get("description"), id=scene.get("scene_id"), frames=frames, - index_id=scene.get("index_id"), ) scenes.append(scene) @@ -232,7 +231,7 @@ def get_scene_collection(self, collection_id: str): ) return self._format_scene_collection(scenes_data.get("scene_collection")) - def get_scene_collections(self): + def list_scene_collection(self): scene_collections_data = self._connection.get( path=f"{ApiPath.video}/{self.id}/{ApiPath.scenes}" ) @@ -265,18 +264,9 @@ def index_scenes( ) if not scenes_data: return None - return [ - Scene( - video_id=self.id, - start=scene.get("start"), - end=scene.get("end"), - index_id=scene.get("scene_index_id"), - description=scene.get("description"), - ) - for scene in scenes_data.get("scene_index_records", []) - ] + return scenes_data.get("scene_index_records", []) - def get_scene_indexes(self) -> List: + def list_scene_index(self) -> List: index_data = self._connection.get( path=f"{ApiPath.video}/{self.id}/{ApiPath.index}/{ApiPath.scene}" ) @@ -295,7 +285,6 @@ def get_scene_index(self, scene_index_id: str) -> List[Scene] or None: video_id=self.id, start=scene.get("start"), end=scene.get("end"), - index_id=scene.get("scene_index_id"), description=scene.get("description"), ) for scene in index_records From 892b6c97d02eb78ee28a44b610ad932927a94017 Mon Sep 17 00:00:00 2001 From: Ankit raj <113342181+ankit-v2-3@users.noreply.github.com> Date: Thu, 20 Jun 2024 15:02:46 +0530 Subject: [PATCH 16/26] fix: get scene index --- videodb/video.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/videodb/video.py b/videodb/video.py index dad299f..0360637 100644 --- a/videodb/video.py +++ b/videodb/video.py @@ -277,18 +277,9 @@ def get_scene_index(self, scene_index_id: str) -> List[Scene] or None: index_data = self._connection.get( path=f"{ApiPath.video}/{self.id}/{ApiPath.index}/{ApiPath.scene}/{scene_index_id}" ) - index_records = index_data.get("scene_index_records", []) - if not index_records: + if not index_data: return None - return [ - Scene( - video_id=self.id, - start=scene.get("start"), - end=scene.get("end"), - description=scene.get("description"), - ) - for scene in index_records - ] + return index_data.get("scene_index_records", []) def delete_scene_index(self, scene_index_id: str) -> None: self._connection.delete( From c0a7f70cecb3f9cbf438552532ac1d71fa64241e Mon Sep 17 00:00:00 2001 From: Ankit raj <113342181+ankit-v2-3@users.noreply.github.com> Date: Thu, 20 Jun 2024 15:58:07 +0530 Subject: [PATCH 17/26] fix: index scenes --- videodb/video.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/videodb/video.py b/videodb/video.py index 0360637..0b277c7 100644 --- a/videodb/video.py +++ b/videodb/video.py @@ -245,20 +245,26 @@ def delete_scene_collection(self, collection_id: str) -> None: def index_scenes( self, extraction_type: SceneExtractionType = SceneExtractionType.scene_based, - extraction_config: dict = {}, - prompt: str = None, - scenes: List[Scene] = [], - force: bool = False, - callback_url: str = None, - ) -> List[Scene] or None: + extraction_config: Dict = {}, + prompt: Optional[str] = None, + model: Optional[str] = None, + model_config: Optional[Dict] = None, + name: Optional[str] = None, + scenes: Optional[List[Scene]] = None, + force: Optional[bool] = False, + callback_url: Optional[str] = None, + ) -> Optional[List]: scenes_data = self._connection.post( path=f"{ApiPath.video}/{self.id}/{ApiPath.index}/{ApiPath.scene}", data={ - "scenes": [scene.to_json() for scene in scenes], "extraction_type": extraction_type, "extraction_config": extraction_config, "prompt": prompt, + "model": model, + "model_config": model_config, + "name": name, "force": force, + "scenes": [scene.to_json() for scene in scenes] if scenes else None, "callback_url": callback_url, }, ) @@ -273,7 +279,7 @@ def list_scene_index(self) -> List: return index_data.get("scene_indexes", []) - def get_scene_index(self, scene_index_id: str) -> List[Scene] or None: + def get_scene_index(self, scene_index_id: str) -> Optional[List]: index_data = self._connection.get( path=f"{ApiPath.video}/{self.id}/{ApiPath.index}/{ApiPath.scene}/{scene_index_id}" ) From cff83a2e5db6c08d963d95645cda4a1c6297559c Mon Sep 17 00:00:00 2001 From: Ankit raj <113342181+ankit-v2-3@users.noreply.github.com> Date: Fri, 21 Jun 2024 15:34:13 +0530 Subject: [PATCH 18/26] build: add client header --- setup.py | 22 +++++++++++----------- videodb/__about__.py | 8 ++++++++ videodb/__init__.py | 1 + videodb/_utils/_http_client.py | 8 +++++++- videodb/client.py | 4 ++-- videodb/video.py | 1 - 6 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 videodb/__about__.py diff --git a/setup.py b/setup.py index bdb8b05..b97aad2 100644 --- a/setup.py +++ b/setup.py @@ -2,16 +2,16 @@ import os from setuptools import setup, find_packages -ROOT = os.path.dirname(__file__) +ROOT = os.path.dirname(os.path.abspath(__file__)) # Read in the package version per recommendations from: # https://packaging.python.org/guides/single-sourcing-package-version/ -def get_version(): - with open(os.path.join(ROOT, "videodb", "__init__.py")) as f: - for line in f.readlines(): - if line.startswith("__version__"): - return line.split("=")[1].strip().strip('''"''') + +about_path = os.path.join(ROOT, "videodb", "__about__.py") +about = {} +with open(about_path) as fp: + exec(fp.read(), about) # read the contents of README file @@ -19,14 +19,14 @@ def get_version(): setup( - name="videodb", - version=get_version(), - author="videodb", - author_email="contact@videodb.io", + name=about["__title__"], + version=about["__version__"], + author=about["__author__"], + author_email=about["__email__"], description="VideoDB Python SDK", long_description=long_description, long_description_content_type="text/markdown", - url="https://github.com/video-db/videodb-python", + url=about["__url__"], packages=find_packages(exclude=["tests", "tests.*"]), python_requires=">=3.8", install_requires=[ diff --git a/videodb/__about__.py b/videodb/__about__.py new file mode 100644 index 0000000..d71b052 --- /dev/null +++ b/videodb/__about__.py @@ -0,0 +1,8 @@ +""" About information for videodb sdk""" + + +__version__ = "0.2.0" +__title__ = "videodb" +__author__ = "videodb" +__email__ = "contact@videodb.io" +__url__ = "https://github.com/video-db/videodb-python" diff --git a/videodb/__init__.py b/videodb/__init__.py index 22fbad6..82e2500 100644 --- a/videodb/__init__.py +++ b/videodb/__init__.py @@ -4,6 +4,7 @@ import logging from typing import Optional +from videodb.__about__ import __version__ from videodb._utils._video import play_stream from videodb._constants import ( VIDEO_DB_API, diff --git a/videodb/_utils/_http_client.py b/videodb/_utils/_http_client.py index 0b4ae80..8633ebb 100644 --- a/videodb/_utils/_http_client.py +++ b/videodb/_utils/_http_client.py @@ -32,6 +32,7 @@ def __init__( self, api_key: str, base_url: str, + version: str, max_retries: Optional[int] = HttpClientDefaultValues.max_retries, ) -> None: """Create a new http client instance @@ -50,8 +51,13 @@ def __init__( adapter = HTTPAdapter(max_retries=retries) self.session.mount("http://", adapter) self.session.mount("https://", adapter) + self.version = version self.session.headers.update( - {"x-access-token": api_key, "Content-Type": "application/json"} + { + "x-access-token": api_key, + "x-videodb-client": f"videodb-python/{self.version}", + "Content-Type": "application/json", + } ) self.base_url = base_url self.show_progress = False diff --git a/videodb/client.py b/videodb/client.py index 39bf38b..19d9c1d 100644 --- a/videodb/client.py +++ b/videodb/client.py @@ -5,7 +5,7 @@ Union, List, ) - +from videodb import __version__ from videodb._constants import ( ApiPath, ) @@ -28,7 +28,7 @@ def __init__(self, api_key: str, base_url: str) -> None: self.api_key = api_key self.base_url = base_url self.collection_id = "default" - super().__init__(api_key, base_url) + super().__init__(api_key=api_key, base_url=base_url, version=__version__) def get_collection(self, collection_id: Optional[str] = "default") -> Collection: collection_data = self.get(path=f"{ApiPath.collection}/{collection_id}") diff --git a/videodb/video.py b/videodb/video.py index 0b277c7..845e32d 100644 --- a/videodb/video.py +++ b/videodb/video.py @@ -276,7 +276,6 @@ def list_scene_index(self) -> List: index_data = self._connection.get( path=f"{ApiPath.video}/{self.id}/{ApiPath.index}/{ApiPath.scene}" ) - return index_data.get("scene_indexes", []) def get_scene_index(self, scene_index_id: str) -> Optional[List]: From c23d3ca831853c2af224663506d9ea6bc9804bff Mon Sep 17 00:00:00 2001 From: Ankit raj <113342181+ankit-v2-3@users.noreply.github.com> Date: Fri, 21 Jun 2024 15:56:42 +0530 Subject: [PATCH 19/26] fix: linter --- videodb/__init__.py | 3 --- videodb/client.py | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/videodb/__init__.py b/videodb/__init__.py index 82e2500..b96558d 100644 --- a/videodb/__init__.py +++ b/videodb/__init__.py @@ -4,7 +4,6 @@ import logging from typing import Optional -from videodb.__about__ import __version__ from videodb._utils._video import play_stream from videodb._constants import ( VIDEO_DB_API, @@ -26,8 +25,6 @@ logger: logging.Logger = logging.getLogger("videodb") -__version__ = "0.1.2" -__author__ = "videodb" __all__ = [ "VideodbError", diff --git a/videodb/client.py b/videodb/client.py index 19d9c1d..a118f57 100644 --- a/videodb/client.py +++ b/videodb/client.py @@ -5,7 +5,7 @@ Union, List, ) -from videodb import __version__ +from videodb.__about__ import __version__ from videodb._constants import ( ApiPath, ) From 49fa4914541f6018cab310d8fc6a0ad77c6b0bd2 Mon Sep 17 00:00:00 2001 From: Ankit raj <113342181+ankit-v2-3@users.noreply.github.com> Date: Wed, 26 Jun 2024 20:41:06 +0530 Subject: [PATCH 20/26] fix: search and vision interface --- videodb/__init__.py | 2 ++ videodb/_constants.py | 4 ++-- videodb/collection.py | 4 ++++ videodb/search.py | 21 +++++++++++++++++---- videodb/video.py | 25 ++++++++++++++++++------- 5 files changed, 43 insertions(+), 13 deletions(-) diff --git a/videodb/__init__.py b/videodb/__init__.py index b96558d..7aa45a7 100644 --- a/videodb/__init__.py +++ b/videodb/__init__.py @@ -7,6 +7,7 @@ from videodb._utils._video import play_stream from videodb._constants import ( VIDEO_DB_API, + IndexType, SceneExtractionType, MediaType, SearchType, @@ -30,6 +31,7 @@ "VideodbError", "AuthenticationError", "InvalidRequestError", + "IndexType", "SearchError", "play_stream", "MediaType", diff --git a/videodb/_constants.py b/videodb/_constants.py index e0ed017..f45141f 100644 --- a/videodb/_constants.py +++ b/videodb/_constants.py @@ -18,12 +18,12 @@ class SearchType: class IndexType: - semantic = "semantic" + spoken = "spoken" scene = "scene" class SceneExtractionType: - scene_based = "scene" + shot_based = "shot" time_based = "time" diff --git a/videodb/collection.py b/videodb/collection.py index 040979f..25bcb47 100644 --- a/videodb/collection.py +++ b/videodb/collection.py @@ -10,6 +10,7 @@ ) from videodb._constants import ( ApiPath, + IndexType, SearchType, ) from videodb.video import Video @@ -100,6 +101,7 @@ def search( self, query: str, search_type: Optional[str] = SearchType.semantic, + index_type: Optional[str] = IndexType.spoken, result_threshold: Optional[int] = None, score_threshold: Optional[float] = None, dynamic_score_percentage: Optional[float] = None, @@ -108,6 +110,8 @@ def search( return search.search_inside_collection( collection_id=self.id, query=query, + search_type=search_type, + index_type=index_type, result_threshold=result_threshold, score_threshold=score_threshold, dynamic_score_percentage=dynamic_score_percentage, diff --git a/videodb/search.py b/videodb/search.py index e9e6942..168d10f 100644 --- a/videodb/search.py +++ b/videodb/search.py @@ -1,6 +1,7 @@ from abc import ABC, abstractmethod from videodb._utils._video import play_stream from videodb._constants import ( + IndexType, SearchType, ApiPath, SemanticSearchDefaultValues, @@ -109,6 +110,8 @@ def search_inside_video( self, video_id: str, query: str, + search_type: str, + index_type: str, result_threshold: Optional[int] = None, score_threshold: Optional[float] = None, dynamic_score_percentage: Optional[float] = None, @@ -117,7 +120,8 @@ def search_inside_video( search_data = self._connection.post( path=f"{ApiPath.video}/{video_id}/{ApiPath.search}", data={ - "index_type": SearchType.semantic, + "search_type": search_type, + "index_type": index_type, "query": query, "score_threshold": score_threshold or SemanticSearchDefaultValues.score_threshold, @@ -133,6 +137,8 @@ def search_inside_collection( self, collection_id: str, query: str, + search_type: str, + index_type: str, result_threshold: Optional[int] = None, score_threshold: Optional[float] = None, dynamic_score_percentage: Optional[float] = None, @@ -141,7 +147,8 @@ def search_inside_collection( search_data = self._connection.post( path=f"{ApiPath.collection}/{collection_id}/{ApiPath.search}", data={ - "index_type": SearchType.semantic, + "search_type": search_type, + "index_type": index_type, "query": query, "score_threshold": score_threshold or SemanticSearchDefaultValues.score_threshold, @@ -162,6 +169,8 @@ def search_inside_video( self, video_id: str, query: str, + search_type: str, + index_type: str, result_threshold: Optional[int] = None, score_threshold: Optional[float] = None, dynamic_score_percentage: Optional[float] = None, @@ -170,7 +179,8 @@ def search_inside_video( search_data = self._connection.post( path=f"{ApiPath.video}/{video_id}/{ApiPath.search}", data={ - "index_type": SearchType.keyword, + "search_type": search_type, + "index_type": index_type, "query": query, "score_threshold": score_threshold, "result_threshold": result_threshold, @@ -190,6 +200,8 @@ def search_inside_video( self, video_id: str, query: str, + search_type: str, + index_type: str, result_threshold: Optional[int] = None, score_threshold: Optional[float] = None, dynamic_score_percentage: Optional[float] = None, @@ -198,7 +210,8 @@ def search_inside_video( search_data = self._connection.post( path=f"{ApiPath.video}/{video_id}/{ApiPath.search}", data={ - "index_type": SearchType.scene, + "search_type": search_type, + "index_type": IndexType.scene, "query": query, "score_threshold": score_threshold, "result_threshold": result_threshold, diff --git a/videodb/video.py b/videodb/video.py index 845e32d..6c4fb65 100644 --- a/videodb/video.py +++ b/videodb/video.py @@ -49,6 +49,7 @@ def search( self, query: str, search_type: Optional[str] = SearchType.semantic, + index_type: Optional[str] = IndexType.spoken, result_threshold: Optional[int] = None, score_threshold: Optional[float] = None, dynamic_score_percentage: Optional[float] = None, @@ -58,6 +59,8 @@ def search( return search.search_inside_video( video_id=self.id, query=query, + search_type=search_type, + index_type=index_type, result_threshold=result_threshold, score_threshold=score_threshold, dynamic_score_percentage=dynamic_score_percentage, @@ -152,7 +155,7 @@ def index_spoken_words( self._connection.post( path=f"{ApiPath.video}/{self.id}/{ApiPath.index}", data={ - "index_type": IndexType.semantic, + "index_type": IndexType.spoken, "language_code": language_code, "force": force, "callback_url": callback_url, @@ -207,11 +210,11 @@ def _format_scene_collection(self, scene_collection_data: dict) -> SceneCollecti def extract_scenes( self, - extraction_type: SceneExtractionType = SceneExtractionType.scene_based, + extraction_type: SceneExtractionType = SceneExtractionType.shot_based, extraction_config: dict = {}, force: bool = False, callback_url: str = None, - ): + ) -> Optional[SceneCollection]: scenes_data = self._connection.post( path=f"{ApiPath.video}/{self.id}/{ApiPath.scenes}", data={ @@ -225,10 +228,14 @@ def extract_scenes( return None return self._format_scene_collection(scenes_data.get("scene_collection")) - def get_scene_collection(self, collection_id: str): + def get_scene_collection(self, collection_id: str) -> Optional[SceneCollection]: + if not collection_id: + raise ValueError("collection_id is required") scenes_data = self._connection.get( path=f"{ApiPath.video}/{self.id}/{ApiPath.scenes}/{collection_id}" ) + if not scenes_data: + return None return self._format_scene_collection(scenes_data.get("scene_collection")) def list_scene_collection(self): @@ -238,13 +245,15 @@ def list_scene_collection(self): return scene_collections_data.get("scene_collections", []) def delete_scene_collection(self, collection_id: str) -> None: + if not collection_id: + raise ValueError("collection_id is required") self._connection.delete( path=f"{ApiPath.video}/{self.id}/{ApiPath.scenes}/{collection_id}" ) def index_scenes( self, - extraction_type: SceneExtractionType = SceneExtractionType.scene_based, + extraction_type: SceneExtractionType = SceneExtractionType.shot_based, extraction_config: Dict = {}, prompt: Optional[str] = None, model: Optional[str] = None, @@ -253,7 +262,7 @@ def index_scenes( scenes: Optional[List[Scene]] = None, force: Optional[bool] = False, callback_url: Optional[str] = None, - ) -> Optional[List]: + ) -> Optional[str]: scenes_data = self._connection.post( path=f"{ApiPath.video}/{self.id}/{ApiPath.index}/{ApiPath.scene}", data={ @@ -270,7 +279,7 @@ def index_scenes( ) if not scenes_data: return None - return scenes_data.get("scene_index_records", []) + return scenes_data.get("scene_index_id") def list_scene_index(self) -> List: index_data = self._connection.get( @@ -287,6 +296,8 @@ def get_scene_index(self, scene_index_id: str) -> Optional[List]: return index_data.get("scene_index_records", []) def delete_scene_index(self, scene_index_id: str) -> None: + if not scene_index_id: + raise ValueError("scene_index_id is required") self._connection.delete( path=f"{ApiPath.video}/{self.id}/{ApiPath.index}/{ApiPath.scene}/{scene_index_id}" ) From 125cf89371e2de0176c1ac576c92108c3dc07d1d Mon Sep 17 00:00:00 2001 From: Ankit raj <113342181+ankit-v2-3@users.noreply.github.com> Date: Wed, 26 Jun 2024 20:46:00 +0530 Subject: [PATCH 21/26] fix: index type --- videodb/_constants.py | 2 +- videodb/collection.py | 2 +- videodb/video.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/videodb/_constants.py b/videodb/_constants.py index f45141f..0d9a02b 100644 --- a/videodb/_constants.py +++ b/videodb/_constants.py @@ -18,7 +18,7 @@ class SearchType: class IndexType: - spoken = "spoken" + spoken_word = "spoken_word" scene = "scene" diff --git a/videodb/collection.py b/videodb/collection.py index 25bcb47..b610009 100644 --- a/videodb/collection.py +++ b/videodb/collection.py @@ -101,7 +101,7 @@ def search( self, query: str, search_type: Optional[str] = SearchType.semantic, - index_type: Optional[str] = IndexType.spoken, + index_type: Optional[str] = IndexType.spoken_word, result_threshold: Optional[int] = None, score_threshold: Optional[float] = None, dynamic_score_percentage: Optional[float] = None, diff --git a/videodb/video.py b/videodb/video.py index 6c4fb65..5e713ab 100644 --- a/videodb/video.py +++ b/videodb/video.py @@ -49,7 +49,7 @@ def search( self, query: str, search_type: Optional[str] = SearchType.semantic, - index_type: Optional[str] = IndexType.spoken, + index_type: Optional[str] = IndexType.spoken_word, result_threshold: Optional[int] = None, score_threshold: Optional[float] = None, dynamic_score_percentage: Optional[float] = None, @@ -155,7 +155,7 @@ def index_spoken_words( self._connection.post( path=f"{ApiPath.video}/{self.id}/{ApiPath.index}", data={ - "index_type": IndexType.spoken, + "index_type": IndexType.spoken_word, "language_code": language_code, "force": force, "callback_url": callback_url, From 3841880d86fd77e444b6766a94841acb2a702e72 Mon Sep 17 00:00:00 2001 From: Ankit raj <113342181+ankit-v2-3@users.noreply.github.com> Date: Thu, 27 Jun 2024 16:48:14 +0530 Subject: [PATCH 22/26] feat: add scene and frame describe --- videodb/_constants.py | 6 ++++++ videodb/image.py | 9 +++++++++ videodb/scene.py | 14 +++++++++++++- videodb/video.py | 1 + 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/videodb/_constants.py b/videodb/_constants.py index 0d9a02b..400850e 100644 --- a/videodb/_constants.py +++ b/videodb/_constants.py @@ -27,6 +27,10 @@ class SceneExtractionType: time_based = "time" +class SceneModels: + gpt4_o = "gpt4-o" + + class Workflows: add_subtitles = "add_subtitles" @@ -58,6 +62,8 @@ class ApiPath: invoices = "invoices" scenes = "scenes" scene = "scene" + frame = "frame" + describe = "describe" class Status: diff --git a/videodb/image.py b/videodb/image.py index c1bd2ef..bc427dd 100644 --- a/videodb/image.py +++ b/videodb/image.py @@ -1,5 +1,6 @@ from videodb._constants import ( ApiPath, + SceneModels, ) @@ -61,3 +62,11 @@ def to_json(self): "frame_time": self.frame_time, "description": self.description, } + + def describe(self, prompt: str = None, model_name=SceneModels.gpt4_o): + description_data = self._connection.post( + path=f"{ApiPath.video}/{self.video_id}/{ApiPath.frame}/{self.id}/{ApiPath.describe}", + data={"prompt": prompt, "model_name": model_name}, + ) + self.description = description_data.get("description", None) + return self.description diff --git a/videodb/scene.py b/videodb/scene.py index 182bfe8..c17ca16 100644 --- a/videodb/scene.py +++ b/videodb/scene.py @@ -1,6 +1,6 @@ from typing import List -from videodb._constants import ApiPath +from videodb._constants import ApiPath, SceneModels from videodb.image import Frame @@ -14,6 +14,7 @@ def __init__( description: str, id: str = None, frames: List[Frame] = [], + connection=None, ): self.id = id self.video_id = video_id @@ -21,6 +22,7 @@ def __init__( self.end = end self.frames: List[Frame] = frames self.description = description + self._connection = connection def __repr__(self) -> str: return ( @@ -43,6 +45,16 @@ def to_json(self): "description": self.description, } + def describe(self, prompt: str = None, model_name=SceneModels.gpt4_o) -> None: + if self._connection is None: + raise ValueError("Connection is required to describe a scene") + description_data = self._connection.post( + path=f"{ApiPath.video}/{self.video_id}/{ApiPath.scene}/{self.id}/{ApiPath.describe}", + data={"prompt": prompt, "model_name": model_name}, + ) + self.description = description_data.get("description", None) + return self.description + class SceneCollection: def __init__( diff --git a/videodb/video.py b/videodb/video.py index 5e713ab..f2e2687 100644 --- a/videodb/video.py +++ b/videodb/video.py @@ -197,6 +197,7 @@ def _format_scene_collection(self, scene_collection_data: dict) -> SceneCollecti description=scene.get("description"), id=scene.get("scene_id"), frames=frames, + connection=self._connection, ) scenes.append(scene) From 43001635aa46c9aefee0f61723604aa19f59cb9e Mon Sep 17 00:00:00 2001 From: Ankit raj <113342181+ankit-v2-3@users.noreply.github.com> Date: Thu, 27 Jun 2024 17:05:31 +0530 Subject: [PATCH 23/26] refactor: model to model_name --- videodb/_constants.py | 4 ---- videodb/image.py | 3 +-- videodb/scene.py | 4 ++-- videodb/video.py | 4 ++-- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/videodb/_constants.py b/videodb/_constants.py index 400850e..7f12586 100644 --- a/videodb/_constants.py +++ b/videodb/_constants.py @@ -27,10 +27,6 @@ class SceneExtractionType: time_based = "time" -class SceneModels: - gpt4_o = "gpt4-o" - - class Workflows: add_subtitles = "add_subtitles" diff --git a/videodb/image.py b/videodb/image.py index bc427dd..5a97b87 100644 --- a/videodb/image.py +++ b/videodb/image.py @@ -1,6 +1,5 @@ from videodb._constants import ( ApiPath, - SceneModels, ) @@ -63,7 +62,7 @@ def to_json(self): "description": self.description, } - def describe(self, prompt: str = None, model_name=SceneModels.gpt4_o): + def describe(self, prompt: str = None, model_name=None): description_data = self._connection.post( path=f"{ApiPath.video}/{self.video_id}/{ApiPath.frame}/{self.id}/{ApiPath.describe}", data={"prompt": prompt, "model_name": model_name}, diff --git a/videodb/scene.py b/videodb/scene.py index c17ca16..3bb1028 100644 --- a/videodb/scene.py +++ b/videodb/scene.py @@ -1,6 +1,6 @@ from typing import List -from videodb._constants import ApiPath, SceneModels +from videodb._constants import ApiPath from videodb.image import Frame @@ -45,7 +45,7 @@ def to_json(self): "description": self.description, } - def describe(self, prompt: str = None, model_name=SceneModels.gpt4_o) -> None: + def describe(self, prompt: str = None, model_name=None) -> None: if self._connection is None: raise ValueError("Connection is required to describe a scene") description_data = self._connection.post( diff --git a/videodb/video.py b/videodb/video.py index f2e2687..5a0e291 100644 --- a/videodb/video.py +++ b/videodb/video.py @@ -257,7 +257,7 @@ def index_scenes( extraction_type: SceneExtractionType = SceneExtractionType.shot_based, extraction_config: Dict = {}, prompt: Optional[str] = None, - model: Optional[str] = None, + model_name: Optional[str] = None, model_config: Optional[Dict] = None, name: Optional[str] = None, scenes: Optional[List[Scene]] = None, @@ -270,7 +270,7 @@ def index_scenes( "extraction_type": extraction_type, "extraction_config": extraction_config, "prompt": prompt, - "model": model, + "model_name": model_name, "model_config": model_config, "name": name, "force": force, From 157b439fb80085653888fdc858de3db6a5ec0b31 Mon Sep 17 00:00:00 2001 From: Ankit raj <113342181+ankit-v2-3@users.noreply.github.com> Date: Thu, 27 Jun 2024 17:41:34 +0530 Subject: [PATCH 24/26] build: upgrade version and add license --- setup.py | 1 + videodb/__about__.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b97aad2..db2f955 100644 --- a/setup.py +++ b/setup.py @@ -23,6 +23,7 @@ version=about["__version__"], author=about["__author__"], author_email=about["__email__"], + license=about["__license__"], description="VideoDB Python SDK", long_description=long_description, long_description_content_type="text/markdown", diff --git a/videodb/__about__.py b/videodb/__about__.py index d71b052..aa144bc 100644 --- a/videodb/__about__.py +++ b/videodb/__about__.py @@ -1,8 +1,9 @@ """ About information for videodb sdk""" -__version__ = "0.2.0" +__version__ = "0.2.1" __title__ = "videodb" __author__ = "videodb" __email__ = "contact@videodb.io" __url__ = "https://github.com/video-db/videodb-python" +__license__ = "Apache License 2.0" From a28842cb56cf0b4e82a1db69a7d1a521ddae8152 Mon Sep 17 00:00:00 2001 From: Ankit raj <113342181+ankit-v2-3@users.noreply.github.com> Date: Thu, 27 Jun 2024 17:54:54 +0530 Subject: [PATCH 25/26] build: upgrade twine --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index bf8db06..07fee5c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,4 @@ ruff==0.1.7 pytest==7.4.3 -twine==4.0.2 +twine==5.1.1 wheel==0.42.0 From a94693a2569ffa7b8df9a7b25cff14d279812a50 Mon Sep 17 00:00:00 2001 From: Ankit raj <113342181+ankit-v2-3@users.noreply.github.com> Date: Thu, 27 Jun 2024 20:53:49 +0530 Subject: [PATCH 26/26] fix: remove force --- videodb/video.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/videodb/video.py b/videodb/video.py index 5a0e291..5098cbf 100644 --- a/videodb/video.py +++ b/videodb/video.py @@ -261,7 +261,6 @@ def index_scenes( model_config: Optional[Dict] = None, name: Optional[str] = None, scenes: Optional[List[Scene]] = None, - force: Optional[bool] = False, callback_url: Optional[str] = None, ) -> Optional[str]: scenes_data = self._connection.post( @@ -273,7 +272,6 @@ def index_scenes( "model_name": model_name, "model_config": model_config, "name": name, - "force": force, "scenes": [scene.to_json() for scene in scenes] if scenes else None, "callback_url": callback_url, },