Skip to content

Commit 62a7bc5

Browse files
authored
Merge pull request #19 from video-db/feat/add-vision-interface
Feat/add vision interface
2 parents 01e52b1 + c23d3ca commit 62a7bc5

12 files changed

+301
-52
lines changed

setup.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,31 @@
22
import os
33
from setuptools import setup, find_packages
44

5-
ROOT = os.path.dirname(__file__)
5+
ROOT = os.path.dirname(os.path.abspath(__file__))
66

77

88
# Read in the package version per recommendations from:
99
# https://packaging.python.org/guides/single-sourcing-package-version/
10-
def get_version():
11-
with open(os.path.join(ROOT, "videodb", "__init__.py")) as f:
12-
for line in f.readlines():
13-
if line.startswith("__version__"):
14-
return line.split("=")[1].strip().strip('''"''')
10+
11+
about_path = os.path.join(ROOT, "videodb", "__about__.py")
12+
about = {}
13+
with open(about_path) as fp:
14+
exec(fp.read(), about)
1515

1616

1717
# read the contents of README file
1818
long_description = open(os.path.join(ROOT, "README.md"), "r", encoding="utf-8").read()
1919

2020

2121
setup(
22-
name="videodb",
23-
version=get_version(),
24-
author="videodb",
25-
author_email="[email protected]",
22+
name=about["__title__"],
23+
version=about["__version__"],
24+
author=about["__author__"],
25+
author_email=about["__email__"],
2626
description="VideoDB Python SDK",
2727
long_description=long_description,
2828
long_description_content_type="text/markdown",
29-
url="https://github.com/video-db/videodb-python",
29+
url=about["__url__"],
3030
packages=find_packages(exclude=["tests", "tests.*"]),
3131
python_requires=">=3.8",
3232
install_requires=[

videodb/__about__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
""" About information for videodb sdk"""
2+
3+
4+
__version__ = "0.2.0"
5+
__title__ = "videodb"
6+
__author__ = "videodb"
7+
__email__ = "[email protected]"
8+
__url__ = "https://github.com/video-db/videodb-python"

videodb/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from videodb._utils._video import play_stream
88
from videodb._constants import (
99
VIDEO_DB_API,
10+
SceneExtractionType,
1011
MediaType,
1112
SearchType,
1213
SubtitleAlignment,
@@ -24,8 +25,6 @@
2425

2526
logger: logging.Logger = logging.getLogger("videodb")
2627

27-
__version__ = "0.1.2"
28-
__author__ = "videodb"
2928

3029
__all__ = [
3130
"VideodbError",
@@ -39,6 +38,7 @@
3938
"SubtitleBorderStyle",
4039
"SubtitleStyle",
4140
"TextStyle",
41+
"SceneExtractionType",
4242
]
4343

4444

videodb/_constants.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ class IndexType:
2222
scene = "scene"
2323

2424

25+
class SceneExtractionType:
26+
scene_based = "scene"
27+
time_based = "time"
28+
29+
2530
class Workflows:
2631
add_subtitles = "add_subtitles"
2732

@@ -51,6 +56,8 @@ class ApiPath:
5156
billing = "billing"
5257
usage = "usage"
5358
invoices = "invoices"
59+
scenes = "scenes"
60+
scene = "scene"
5461

5562

5663
class Status:

videodb/_utils/_http_client.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from videodb.exceptions import (
2020
AuthenticationError,
2121
InvalidRequestError,
22+
RequestTimeoutError,
2223
)
2324

2425
logger = logging.getLogger(__name__)
@@ -31,6 +32,7 @@ def __init__(
3132
self,
3233
api_key: str,
3334
base_url: str,
35+
version: str,
3436
max_retries: Optional[int] = HttpClientDefaultValues.max_retries,
3537
) -> None:
3638
"""Create a new http client instance
@@ -49,8 +51,13 @@ def __init__(
4951
adapter = HTTPAdapter(max_retries=retries)
5052
self.session.mount("http://", adapter)
5153
self.session.mount("https://", adapter)
54+
self.version = version
5255
self.session.headers.update(
53-
{"x-access-token": api_key, "Content-Type": "application/json"}
56+
{
57+
"x-access-token": api_key,
58+
"x-videodb-client": f"videodb-python/{self.version}",
59+
"Content-Type": "application/json",
60+
}
5461
)
5562
self.base_url = base_url
5663
self.show_progress = False
@@ -109,8 +116,8 @@ def _handle_request_error(self, e: requests.exceptions.RequestException) -> None
109116
) from None
110117

111118
elif isinstance(e, requests.exceptions.Timeout):
112-
raise InvalidRequestError(
113-
"Invalid request: Request timed out", e.response
119+
raise RequestTimeoutError(
120+
"Timeout error: Request timed out", e.response
114121
) from None
115122

116123
elif isinstance(e, requests.exceptions.ConnectionError):

videodb/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
Union,
66
List,
77
)
8-
8+
from videodb.__about__ import __version__
99
from videodb._constants import (
1010
ApiPath,
1111
)
@@ -28,7 +28,7 @@ def __init__(self, api_key: str, base_url: str) -> None:
2828
self.api_key = api_key
2929
self.base_url = base_url
3030
self.collection_id = "default"
31-
super().__init__(api_key, base_url)
31+
super().__init__(api_key=api_key, base_url=base_url, version=__version__)
3232

3333
def get_collection(self, collection_id: Optional[str] = "default") -> Collection:
3434
collection_data = self.get(path=f"{ApiPath.collection}/{collection_id}")

videodb/collection.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ def search(
101101
query: str,
102102
search_type: Optional[str] = SearchType.semantic,
103103
result_threshold: Optional[int] = None,
104-
score_threshold: Optional[int] = None,
105-
dynamic_score_percentage: Optional[int] = None,
104+
score_threshold: Optional[float] = None,
105+
dynamic_score_percentage: Optional[float] = None,
106106
) -> SearchResult:
107107
search = SearchFactory(self._connection).get_search(search_type)
108108
return search.search_inside_collection(

videodb/exceptions.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@ def __init__(self, message, response=None):
3737
self.response = response
3838

3939

40+
class RequestTimeoutError(VideodbError):
41+
"""
42+
Raised when a request times out.
43+
"""
44+
45+
def __init__(self, message, response=None):
46+
super(RequestTimeoutError, self).__init__(message)
47+
self.response = response
48+
49+
4050
class SearchError(VideodbError):
4151
"""
4252
Raised when a search is invalid.

videodb/image.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,42 @@ def __repr__(self) -> str:
2222

2323
def delete(self) -> None:
2424
self._connection.delete(f"{ApiPath.image}/{self.id}")
25+
26+
27+
class Frame(Image):
28+
def __init__(
29+
self,
30+
_connection,
31+
id: str,
32+
video_id: str,
33+
scene_id: str,
34+
url: str,
35+
frame_time: float,
36+
description: str,
37+
):
38+
super().__init__(_connection=_connection, id=id, collection_id=None, url=url)
39+
self.scene_id = scene_id
40+
self.video_id = video_id
41+
self.frame_time = frame_time
42+
self.description = description
43+
44+
def __repr__(self) -> str:
45+
return (
46+
f"Frame("
47+
f"id={self.id}, "
48+
f"video_id={self.video_id}, "
49+
f"scene_id={self.scene_id}, "
50+
f"url={self.url}, "
51+
f"frame_time={self.frame_time}, "
52+
f"description={self.description})"
53+
)
54+
55+
def to_json(self):
56+
return {
57+
"id": self.id,
58+
"video_id": self.video_id,
59+
"scene_id": self.scene_id,
60+
"url": self.url,
61+
"frame_time": self.frame_time,
62+
"description": self.description,
63+
}

videodb/scene.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from typing import List
2+
3+
from videodb._constants import ApiPath
4+
5+
from videodb.image import Frame
6+
7+
8+
class Scene:
9+
def __init__(
10+
self,
11+
video_id: str,
12+
start: float,
13+
end: float,
14+
description: str,
15+
id: str = None,
16+
frames: List[Frame] = [],
17+
):
18+
self.id = id
19+
self.video_id = video_id
20+
self.start = start
21+
self.end = end
22+
self.frames: List[Frame] = frames
23+
self.description = description
24+
25+
def __repr__(self) -> str:
26+
return (
27+
f"Scene("
28+
f"id={self.id}, "
29+
f"video_id={self.video_id}, "
30+
f"start={self.start}, "
31+
f"end={self.end}, "
32+
f"frames={self.frames}, "
33+
f"description={self.description})"
34+
)
35+
36+
def to_json(self):
37+
return {
38+
"id": self.id,
39+
"video_id": self.video_id,
40+
"start": self.start,
41+
"end": self.end,
42+
"frames": [frame.to_json() for frame in self.frames],
43+
"description": self.description,
44+
}
45+
46+
47+
class SceneCollection:
48+
def __init__(
49+
self,
50+
_connection,
51+
id: str,
52+
video_id: str,
53+
config: dict,
54+
scenes: List[Scene],
55+
) -> None:
56+
self._connection = _connection
57+
self.id = id
58+
self.video_id = video_id
59+
self.config: dict = config
60+
self.scenes: List[Scene] = scenes
61+
62+
def __repr__(self) -> str:
63+
return (
64+
f"SceneCollection("
65+
f"id={self.id}, "
66+
f"video_id={self.video_id}, "
67+
f"config={self.config}, "
68+
f"scenes={self.scenes})"
69+
)
70+
71+
def delete(self) -> None:
72+
self._connection.delete(
73+
path=f"{ApiPath.video}/{self.video_id}/{ApiPath.scenes}/{self.id}"
74+
)

videodb/search.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ def search_inside_video(
110110
video_id: str,
111111
query: str,
112112
result_threshold: Optional[int] = None,
113-
score_threshold: Optional[int] = None,
114-
dynamic_score_percentage: Optional[int] = None,
113+
score_threshold: Optional[float] = None,
114+
dynamic_score_percentage: Optional[float] = None,
115115
**kwargs,
116116
):
117117
search_data = self._connection.post(
@@ -123,6 +123,8 @@ def search_inside_video(
123123
or SemanticSearchDefaultValues.score_threshold,
124124
"result_threshold": result_threshold
125125
or SemanticSearchDefaultValues.result_threshold,
126+
"dynamic_score_percentage": dynamic_score_percentage,
127+
**kwargs,
126128
},
127129
)
128130
return SearchResult(self._connection, **search_data)
@@ -132,8 +134,8 @@ def search_inside_collection(
132134
collection_id: str,
133135
query: str,
134136
result_threshold: Optional[int] = None,
135-
score_threshold: Optional[int] = None,
136-
dynamic_score_percentage: Optional[int] = None,
137+
score_threshold: Optional[float] = None,
138+
dynamic_score_percentage: Optional[float] = None,
137139
**kwargs,
138140
):
139141
search_data = self._connection.post(
@@ -145,6 +147,8 @@ def search_inside_collection(
145147
or SemanticSearchDefaultValues.score_threshold,
146148
"result_threshold": result_threshold
147149
or SemanticSearchDefaultValues.result_threshold,
150+
"dynamic_score_percentage": dynamic_score_percentage,
151+
**kwargs,
148152
},
149153
)
150154
return SearchResult(self._connection, **search_data)
@@ -159,8 +163,8 @@ def search_inside_video(
159163
video_id: str,
160164
query: str,
161165
result_threshold: Optional[int] = None,
162-
score_threshold: Optional[int] = None,
163-
dynamic_score_percentage: Optional[int] = None,
166+
score_threshold: Optional[float] = None,
167+
dynamic_score_percentage: Optional[float] = None,
164168
**kwargs,
165169
):
166170
search_data = self._connection.post(
@@ -187,8 +191,8 @@ def search_inside_video(
187191
video_id: str,
188192
query: str,
189193
result_threshold: Optional[int] = None,
190-
score_threshold: Optional[int] = None,
191-
dynamic_score_percentage: Optional[int] = None,
194+
score_threshold: Optional[float] = None,
195+
dynamic_score_percentage: Optional[float] = None,
192196
**kwargs,
193197
):
194198
search_data = self._connection.post(
@@ -198,6 +202,8 @@ def search_inside_video(
198202
"query": query,
199203
"score_threshold": score_threshold,
200204
"result_threshold": result_threshold,
205+
"dynamic_score_percentage": dynamic_score_percentage,
206+
**kwargs,
201207
},
202208
)
203209
return SearchResult(self._connection, **search_data)

0 commit comments

Comments
 (0)