Skip to content

Commit e165e55

Browse files
authored
Merge pull request #8 from video-db/ar/add-image-support
Ar/add image support
2 parents 7588bc4 + 63290e4 commit e165e55

File tree

8 files changed

+149
-16
lines changed

8 files changed

+149
-16
lines changed

videodb/__init__.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,14 @@
55

66
from typing import Optional
77
from videodb._utils._video import play_stream
8-
from videodb._constants import VIDEO_DB_API, MediaType, SearchType
8+
from videodb._constants import (
9+
VIDEO_DB_API,
10+
MediaType,
11+
SearchType,
12+
SubtitleAlignment,
13+
SubtitleBorderStyle,
14+
SubtitleStyle,
15+
)
916
from videodb.client import Connection
1017
from videodb.exceptions import (
1118
VideodbError,
@@ -27,6 +34,9 @@
2734
"play_stream",
2835
"MediaType",
2936
"SearchType",
37+
"SubtitleAlignment",
38+
"SubtitleBorderStyle",
39+
"SubtitleStyle",
3040
]
3141

3242

videodb/_constants.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
"""Constants used in the videodb package."""
22

3+
from dataclasses import dataclass
34

45
VIDEO_DB_API: str = "https://api.videodb.io"
56

67

78
class MediaType:
89
video = "video"
910
audio = "audio"
11+
image = "image"
1012

1113

1214
class SearchType:
@@ -32,6 +34,7 @@ class ApiPath:
3234
upload = "upload"
3335
video = "video"
3436
audio = "audio"
37+
image = "image"
3538
stream = "stream"
3639
thumbnail = "thumbnail"
3740
upload_url = "upload_url"
@@ -57,3 +60,46 @@ class HttpClientDefaultValues:
5760

5861
class MaxSupported:
5962
fade_duration = 5
63+
64+
65+
class SubtitleBorderStyle:
66+
no_border = 1
67+
opaque_box = 3
68+
outline = 4
69+
70+
71+
class SubtitleAlignment:
72+
bottom_left = 1
73+
bottom_center = 2
74+
bottom_right = 3
75+
middle_left = 4
76+
middle_center = 5
77+
middle_right = 6
78+
top_left = 7
79+
top_center = 8
80+
top_right = 9
81+
82+
83+
@dataclass
84+
class SubtitleStyle:
85+
font_name: str = "Arial"
86+
font_size: float = 18
87+
primary_colour: str = "&H00FFFFFF" # white
88+
secondary_colour: str = "&H000000FF" # blue
89+
outline_colour: str = "&H00000000" # black
90+
back_colour: str = "&H00000000" # black
91+
bold: bool = False
92+
italic: bool = False
93+
underline: bool = False
94+
strike_out: bool = False
95+
scale_x: float = 1.0
96+
scale_y: float = 1.0
97+
spacing: float = 0
98+
angle: float = 0
99+
border_style: int = SubtitleBorderStyle.outline
100+
outline: float = 1.0
101+
shadow: float = 0.0
102+
alignment: int = SubtitleAlignment.bottom_center
103+
margin_l: int = 10
104+
margin_r: int = 10
105+
margin_v: int = 10

videodb/asset.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,35 @@ def __repr__(self) -> str:
8585
f"fade_in_duration={self.fade_in_duration}, "
8686
f"fade_out_duration={self.fade_out_duration})"
8787
)
88+
89+
90+
class ImageAsset(MediaAsset):
91+
def __init__(
92+
self,
93+
asset_id: str,
94+
width: Union[int, str] = 100,
95+
height: Union[int, str] = 100,
96+
x: Union[int, str] = 80,
97+
y: Union[int, str] = 20,
98+
duration: Optional[int] = None,
99+
) -> None:
100+
super().__init__(asset_id)
101+
self.width = width
102+
self.height = height
103+
self.x = x
104+
self.y = y
105+
self.duration = duration
106+
107+
def to_json(self) -> dict:
108+
return copy.deepcopy(self.__dict__)
109+
110+
def __repr__(self) -> str:
111+
return (
112+
f"ImageAsset("
113+
f"asset_id={self.asset_id}, "
114+
f"width={self.width}, "
115+
f"height={self.height}, "
116+
f"x={self.x}, "
117+
f"y={self.y}, "
118+
f"duration={self.duration})"
119+
)

videodb/client.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from videodb._utils._http_client import HttpClient
1414
from videodb.video import Video
1515
from videodb.audio import Audio
16+
from videodb.image import Image
1617

1718
from videodb._upload import (
1819
upload,
@@ -46,7 +47,7 @@ def upload(
4647
name: Optional[str] = None,
4748
description: Optional[str] = None,
4849
callback_url: Optional[str] = None,
49-
) -> Union[Video, Audio, None]:
50+
) -> Union[Video, Audio, Image, None]:
5051
upload_data = upload(
5152
self,
5253
file_path,
@@ -56,7 +57,10 @@ def upload(
5657
description,
5758
callback_url,
5859
)
59-
if upload_data.get("id").startswith("m-"):
60-
return Video(self, **upload_data) if upload_data else None
61-
elif upload_data.get("id").startswith("a-"):
62-
return Audio(self, **upload_data) if upload_data else None
60+
media_id = upload_data.get("id", "")
61+
if media_id.startswith("m-"):
62+
return Video(self, **upload_data)
63+
elif media_id.startswith("a-"):
64+
return Audio(self, **upload_data)
65+
elif media_id.startswith("img-"):
66+
return Image(self, **upload_data)

videodb/collection.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
)
1414
from videodb.video import Video
1515
from videodb.audio import Audio
16+
from videodb.image import Image
1617
from videodb.search import SearchFactory, SearchResult
1718

1819
logger = logging.getLogger(__name__)
@@ -54,6 +55,17 @@ def get_audio(self, audio_id: str) -> Audio:
5455
def delete_audio(self, audio_id: str) -> None:
5556
return self._connection.delete(path=f"{ApiPath.audio}/{audio_id}")
5657

58+
def get_images(self) -> list[Image]:
59+
images_data = self._connection.get(path=f"{ApiPath.image}")
60+
return [Image(self._connection, **image) for image in images_data.get("images")]
61+
62+
def get_image(self, image_id: str) -> Image:
63+
image_data = self._connection.get(path=f"{ApiPath.image}/{image_id}")
64+
return Image(self._connection, **image_data)
65+
66+
def delete_image(self, image_id: str) -> None:
67+
return self._connection.delete(path=f"{ApiPath.image}/{image_id}")
68+
5769
def search(
5870
self,
5971
query: str,
@@ -79,7 +91,7 @@ def upload(
7991
name: Optional[str] = None,
8092
description: Optional[str] = None,
8193
callback_url: Optional[str] = None,
82-
) -> Union[Video, Audio, None]:
94+
) -> Union[Video, Audio, Image, None]:
8395
upload_data = upload(
8496
self._connection,
8597
file_path,
@@ -89,7 +101,10 @@ def upload(
89101
description,
90102
callback_url,
91103
)
92-
if upload_data.get("id").startswith("m-"):
93-
return Video(self._connection, **upload_data) if upload_data else None
94-
elif upload_data.get("id").startswith("a-"):
95-
return Audio(self._connection, **upload_data) if upload_data else None
104+
media_id = upload_data.get("id", "")
105+
if media_id.startswith("m-"):
106+
return Video(self, **upload_data)
107+
elif media_id.startswith("a-"):
108+
return Audio(self, **upload_data)
109+
elif media_id.startswith("img-"):
110+
return Image(self, **upload_data)

videodb/image.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from videodb._constants import (
2+
ApiPath,
3+
)
4+
5+
6+
class Image:
7+
def __init__(self, _connection, id: str, collection_id: str, **kwargs) -> None:
8+
self._connection = _connection
9+
self.id = id
10+
self.collection_id = collection_id
11+
self.name = kwargs.get("name", None)
12+
13+
def __repr__(self) -> str:
14+
return (
15+
f"Image("
16+
f"id={self.id}, "
17+
f"collection_id={self.collection_id}, "
18+
f"name={self.name})"
19+
)
20+
21+
def delete(self) -> None:
22+
self._connection.delete(f"{ApiPath.image}/{self.id}")

videodb/timeline.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from typing import Union
22

33
from videodb._constants import ApiPath
4-
from videodb.asset import VideoAsset, AudioAsset
4+
from videodb.asset import VideoAsset, AudioAsset, ImageAsset
55

66

77
class Timeline(object):
@@ -28,9 +28,9 @@ def add_inline(self, asset: Union[VideoAsset]) -> None:
2828
raise ValueError("asset must be of type VideoAsset")
2929
self._timeline.append(asset)
3030

31-
def add_overlay(self, start: int, asset: Union[AudioAsset]) -> None:
32-
if not isinstance(asset, AudioAsset):
33-
raise ValueError("asset must be of type AudioAsset")
31+
def add_overlay(self, start: int, asset: Union[AudioAsset, ImageAsset]) -> None:
32+
if not isinstance(asset, AudioAsset) and not isinstance(asset, ImageAsset):
33+
raise ValueError("asset must be of type AudioAsset or ImageAsset")
3434
self._timeline.append((start, asset))
3535

3636
def generate_stream(self) -> str:

videodb/video.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
SearchType,
66
IndexType,
77
Workflows,
8+
SubtitleStyle,
89
)
910
from videodb.search import SearchFactory, SearchResult
1011
from videodb.shot import Shot
@@ -129,11 +130,14 @@ def index_spoken_words(self) -> None:
129130
},
130131
)
131132

132-
def add_subtitle(self) -> str:
133+
def add_subtitle(self, style: SubtitleStyle = SubtitleStyle()) -> str:
134+
if not isinstance(style, SubtitleStyle):
135+
raise ValueError("style must be of type SubtitleStyle")
133136
subtitle_data = self._connection.post(
134137
path=f"{ApiPath.video}/{self.id}/{ApiPath.workflow}",
135138
data={
136139
"type": Workflows.add_subtitles,
140+
"subtitle_style": style.__dict__,
137141
},
138142
)
139143
return subtitle_data.get("stream_url", None)

0 commit comments

Comments
 (0)