Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion videodb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@

from typing import Optional
from videodb._utils._video import play_stream
from videodb._constants import VIDEO_DB_API, MediaType, SearchType
from videodb._constants import (
VIDEO_DB_API,
MediaType,
SearchType,
SubtitleAlignment,
SubtitleBorderStyle,
SubtitleStyle,
)
from videodb.client import Connection
from videodb.exceptions import (
VideodbError,
Expand All @@ -27,6 +34,9 @@
"play_stream",
"MediaType",
"SearchType",
"SubtitleAlignment",
"SubtitleBorderStyle",
"SubtitleStyle",
]


Expand Down
46 changes: 46 additions & 0 deletions videodb/_constants.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
"""Constants used in the videodb package."""

from dataclasses import dataclass

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


class MediaType:
video = "video"
audio = "audio"
image = "image"


class SearchType:
Expand All @@ -32,6 +34,7 @@ class ApiPath:
upload = "upload"
video = "video"
audio = "audio"
image = "image"
stream = "stream"
thumbnail = "thumbnail"
upload_url = "upload_url"
Expand All @@ -57,3 +60,46 @@ class HttpClientDefaultValues:

class MaxSupported:
fade_duration = 5


class SubtitleBorderStyle:
no_border = 1
opaque_box = 3
outline = 4


class SubtitleAlignment:
bottom_left = 1
bottom_center = 2
bottom_right = 3
middle_left = 4
middle_center = 5
middle_right = 6
top_left = 7
top_center = 8
top_right = 9


@dataclass
class SubtitleStyle:
font_name: str = "Arial"
font_size: float = 18
primary_colour: str = "&H00FFFFFF" # white
secondary_colour: str = "&H000000FF" # blue
outline_colour: str = "&H00000000" # black
back_colour: str = "&H00000000" # black
bold: bool = False
italic: bool = False
underline: bool = False
strike_out: bool = False
scale_x: float = 1.0
scale_y: float = 1.0
spacing: float = 0
angle: float = 0
border_style: int = SubtitleBorderStyle.outline
outline: float = 1.0
shadow: float = 0.0
alignment: int = SubtitleAlignment.bottom_center
margin_l: int = 10
margin_r: int = 10
margin_v: int = 10
32 changes: 32 additions & 0 deletions videodb/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,35 @@ def __repr__(self) -> str:
f"fade_in_duration={self.fade_in_duration}, "
f"fade_out_duration={self.fade_out_duration})"
)


class ImageAsset(MediaAsset):
def __init__(
self,
asset_id: str,
width: Union[int, str] = 100,
height: Union[int, str] = 100,
x: Union[int, str] = 80,
y: Union[int, str] = 20,
duration: Optional[int] = None,
) -> None:
super().__init__(asset_id)
self.width = width
self.height = height
self.x = x
self.y = y
self.duration = duration

def to_json(self) -> dict:
return copy.deepcopy(self.__dict__)

def __repr__(self) -> str:
return (
f"ImageAsset("
f"asset_id={self.asset_id}, "
f"width={self.width}, "
f"height={self.height}, "
f"x={self.x}, "
f"y={self.y}, "
f"duration={self.duration})"
)
14 changes: 9 additions & 5 deletions videodb/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from videodb._utils._http_client import HttpClient
from videodb.video import Video
from videodb.audio import Audio
from videodb.image import Image

from videodb._upload import (
upload,
Expand Down Expand Up @@ -46,7 +47,7 @@ def upload(
name: Optional[str] = None,
description: Optional[str] = None,
callback_url: Optional[str] = None,
) -> Union[Video, Audio, None]:
) -> Union[Video, Audio, Image, None]:
upload_data = upload(
self,
file_path,
Expand All @@ -56,7 +57,10 @@ def upload(
description,
callback_url,
)
if upload_data.get("id").startswith("m-"):
return Video(self, **upload_data) if upload_data else None
elif upload_data.get("id").startswith("a-"):
return Audio(self, **upload_data) if upload_data else None
media_id = upload_data.get("id", "")
if media_id.startswith("m-"):
return Video(self, **upload_data)
elif media_id.startswith("a-"):
return Audio(self, **upload_data)
elif media_id.startswith("img-"):
return Image(self, **upload_data)
25 changes: 20 additions & 5 deletions videodb/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
)
from videodb.video import Video
from videodb.audio import Audio
from videodb.image import Image
from videodb.search import SearchFactory, SearchResult

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

def get_images(self) -> list[Image]:
images_data = self._connection.get(path=f"{ApiPath.image}")
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}")
return Image(self._connection, **image_data)

def delete_image(self, image_id: str) -> None:
return self._connection.delete(path=f"{ApiPath.image}/{image_id}")

def search(
self,
query: str,
Expand All @@ -79,7 +91,7 @@ def upload(
name: Optional[str] = None,
description: Optional[str] = None,
callback_url: Optional[str] = None,
) -> Union[Video, Audio, None]:
) -> Union[Video, Audio, Image, None]:
upload_data = upload(
self._connection,
file_path,
Expand All @@ -89,7 +101,10 @@ def upload(
description,
callback_url,
)
if upload_data.get("id").startswith("m-"):
return Video(self._connection, **upload_data) if upload_data else None
elif upload_data.get("id").startswith("a-"):
return Audio(self._connection, **upload_data) if upload_data else None
media_id = upload_data.get("id", "")
if media_id.startswith("m-"):
return Video(self, **upload_data)
elif media_id.startswith("a-"):
return Audio(self, **upload_data)
elif media_id.startswith("img-"):
return Image(self, **upload_data)
22 changes: 22 additions & 0 deletions videodb/image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from videodb._constants import (
ApiPath,
)


class Image:
def __init__(self, _connection, id: str, collection_id: str, **kwargs) -> None:
self._connection = _connection
self.id = id
self.collection_id = collection_id
self.name = kwargs.get("name", None)

def __repr__(self) -> str:
return (
f"Image("
f"id={self.id}, "
f"collection_id={self.collection_id}, "
f"name={self.name})"
)

def delete(self) -> None:
self._connection.delete(f"{ApiPath.image}/{self.id}")
8 changes: 4 additions & 4 deletions videodb/timeline.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Union

from videodb._constants import ApiPath
from videodb.asset import VideoAsset, AudioAsset
from videodb.asset import VideoAsset, AudioAsset, ImageAsset


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

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

def generate_stream(self) -> str:
Expand Down
6 changes: 5 additions & 1 deletion videodb/video.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
SearchType,
IndexType,
Workflows,
SubtitleStyle,
)
from videodb.search import SearchFactory, SearchResult
from videodb.shot import Shot
Expand Down Expand Up @@ -129,11 +130,14 @@ def index_spoken_words(self) -> None:
},
)

def add_subtitle(self) -> str:
def add_subtitle(self, style: SubtitleStyle = SubtitleStyle()) -> str:
if not isinstance(style, SubtitleStyle):
raise ValueError("style must be of type SubtitleStyle")
subtitle_data = self._connection.post(
path=f"{ApiPath.video}/{self.id}/{ApiPath.workflow}",
data={
"type": Workflows.add_subtitles,
"subtitle_style": style.__dict__,
},
)
return subtitle_data.get("stream_url", None)
Expand Down