Skip to content
Open
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
2 changes: 2 additions & 0 deletions videodb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from videodb._utils._video import play_stream
from videodb._constants import (
VIDEO_DB_API,
ClipContentType,
IndexType,
SceneExtractionType,
MediaType,
Expand Down Expand Up @@ -36,6 +37,7 @@
"VideodbError",
"AuthenticationError",
"InvalidRequestError",
"ClipContentType",
"IndexType",
"SearchError",
"play_stream",
Expand Down
7 changes: 7 additions & 0 deletions videodb/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ class ReframeMode:
smart = "smart"


class ClipContentType:
spoken = "spoken"
visual = "visual"
multimodal = "multimodal"


class SemanticSearchDefaultValues:
result_threshold = 5
score_threshold = 0.2
Expand Down Expand Up @@ -91,6 +97,7 @@ class ApiPath:
record = "record"
editor = "editor"
reframe = "reframe"
clip = "clip"


class Status:
Expand Down
62 changes: 62 additions & 0 deletions videodb/video.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from videodb._utils._video import play_stream
from videodb._constants import (
ApiPath,
ClipContentType,
IndexType,
ReframeMode,
SceneExtractionType,
Expand Down Expand Up @@ -724,3 +725,64 @@ def download(self, name: Optional[str] = None) -> dict:

download_name = name or self.name or f"video_{self.id}"
return self._connection.download(self.stream_url, download_name)

def clip(
self,
prompt: str,
content_type: str = ClipContentType.spoken,
model_name: str = "basic",
scene_index_id: Optional[str] = None,
callback_url: Optional[str] = None,
) -> List[Shot]:
"""Generate video clips based on a text prompt.

Uses AI to analyze the video content (transcript, scenes, or both) and
identify segments that match the given prompt.

:param str prompt: Text describing what clips to find (e.g., "find funny moments",
"create a clip about the introduction")
:param str content_type: Type of content to analyze. One of:
- "spoken": Analyze transcript/spoken words (default)
- "visual": Analyze scene descriptions
- "multimodal": Analyze both transcript and scenes together
:param str model_name: LLM tier to use - "basic", "pro", or "ultra" (default: "basic")
:param str scene_index_id: (optional) Specific scene index to use for visual/multimodal analysis
:param str callback_url: (optional) URL to receive callback when processing completes
:raises InvalidRequestError: If the clip generation fails
:return: List of :class:`Shot <Shot>` objects representing the matched clips
:rtype: List[:class:`videodb.shot.Shot`]
"""
clip_data = self._connection.post(
path=f"{ApiPath.video}/{self.id}/{ApiPath.clip}",
data={
"prompt": prompt,
"content_type": content_type,
"model_name": model_name,
"scene_index_id": scene_index_id,
"callback_url": callback_url,
},
)

if callback_url:
return clip_data

shots = []
results = clip_data.get("results", [])
for result in results:
video_id = result.get("video_id", self.id)
video_length = result.get("length", self.length)
video_title = result.get("title", self.name)
for doc in result.get("docs", []):
shot = Shot(
self._connection,
video_id=video_id,
video_length=video_length,
video_title=video_title,
start=doc.get("start"),
end=doc.get("end"),
text=doc.get("text"),
search_score=doc.get("score"),
)
shots.append(shot)

return shots