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
3 changes: 2 additions & 1 deletion videodb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import logging

from typing import Optional
from videodb._utils._video import play_stream
from videodb._utils._video import play_stream, build_iframe_embed_code
from videodb._constants import (
VIDEO_DB_API,
IndexType,
Expand Down Expand Up @@ -55,6 +55,7 @@
"IndexType",
"SearchError",
"play_stream",
"build_iframe_embed_code",
"MediaType",
"SearchType",
"SubtitleAlignment",
Expand Down
67 changes: 67 additions & 0 deletions videodb/_utils/_video.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,74 @@
import webbrowser as web
from urllib.parse import urlparse, urlunparse, parse_qs, urlencode

PLAYER_URL: str = "https://console.videodb.io/player"


def player_url_to_embed_url(player_url: str) -> str:
"""Convert a /watch player URL to an /embed URL.

:param str player_url: The player URL (e.g., https://player.videodb.io/watch?v=slug)
:return: The embed URL (e.g., https://player.videodb.io/embed?v=slug)
:rtype: str
:raises ValueError: If the URL format is invalid or missing the 'v' parameter
"""
if not player_url:
raise ValueError("player_url is required to generate embed URL")

parsed = urlparse(player_url)
query_params = parse_qs(parsed.query)

if "v" not in query_params:
raise ValueError("player_url must contain a 'v' query parameter")

embed_params = {"v": query_params["v"][0]}

embed_url = urlunparse((
parsed.scheme,
parsed.netloc,
"/embed",
"",
urlencode(embed_params),
"",
))

return embed_url


def build_iframe_embed_code(
player_url: str,
width: str = "100%",
height: int = 405,
title: str = "VideoDB Player",
allow_fullscreen: bool = True,
) -> str:
"""Build an iframe embed HTML string from a player URL.

:param str player_url: The player URL to embed
:param str width: Width of the iframe (default: "100%")
:param int height: Height of the iframe in pixels (default: 405)
:param str title: Title attribute for the iframe (default: "VideoDB Player")
:param bool allow_fullscreen: Whether to allow fullscreen (default: True)
:return: HTML iframe string
:rtype: str
:raises ValueError: If player_url is empty or height is not positive
"""
if not player_url:
raise ValueError("player_url is required to generate embed code")

if height <= 0:
raise ValueError("height must be a positive integer")

embed_url = player_url_to_embed_url(player_url)
fullscreen_attr = " allowfullscreen" if allow_fullscreen else ""

return (
f'<iframe src="{embed_url}" '
f'width="{width}" height="{height}" '
f'title="{title}" frameborder="0"{fullscreen_attr}></iframe>'
)


def play_stream(url: str):
"""Play a stream url in the browser/ notebook

Expand Down
36 changes: 36 additions & 0 deletions videodb/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from enum import Enum

from videodb._constants import ApiPath
from videodb._utils._video import build_iframe_embed_code
from videodb.exceptions import InvalidRequestError


Expand Down Expand Up @@ -1164,3 +1165,38 @@ def download_stream(self, stream_url: str) -> dict:
return self.connection.post(
path=f"{ApiPath.editor}/{ApiPath.download}", data={"stream_url": stream_url}
)

def get_embed_code(
self,
width: str = "100%",
height: int = 405,
title: str = "VideoDB Player",
allow_fullscreen: bool = True,
auto_generate: bool = True,
) -> str:
"""Generate an HTML iframe embed code for the timeline.

:param str width: Width of the iframe (default: "100%")
:param int height: Height of the iframe in pixels (default: 405)
:param str title: Title attribute for the iframe (default: "VideoDB Player")
:param bool allow_fullscreen: Whether to allow fullscreen (default: True)
:param bool auto_generate: If True and player_url is missing, auto-generate it (default: True)
:return: HTML iframe string
:rtype: str
:raises ValueError: If player_url is not available
"""
if not self.player_url and auto_generate:
self.generate_stream()

if not self.player_url:
raise ValueError(
"player_url not available. Call generate_stream() first or set auto_generate=True."
)

return build_iframe_embed_code(
player_url=self.player_url,
width=width,
height=height,
title=title,
allow_fullscreen=allow_fullscreen,
)
101 changes: 100 additions & 1 deletion videodb/rtstream.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
SceneExtractionType,
Segmenter,
)
from videodb._utils._video import play_stream
from videodb._utils._video import play_stream, build_iframe_embed_code


class RTStreamSearchResult:
Expand Down Expand Up @@ -71,6 +71,36 @@ def __repr__(self) -> str:
f"duration={self.duration})"
)

def get_embed_code(
self,
width: str = "100%",
height: int = 405,
title: str = "VideoDB Player",
allow_fullscreen: bool = True,
) -> str:
"""Generate an HTML iframe embed code for the exported recording.

:param str width: Width of the iframe (default: "100%")
:param int height: Height of the iframe in pixels (default: 405)
:param str title: Title attribute for the iframe (default: "VideoDB Player")
:param bool allow_fullscreen: Whether to allow fullscreen (default: True)
:return: HTML iframe string
:rtype: str
:raises ValueError: If player_url is not available
"""
if not self.player_url:
raise ValueError(
"player_url not available. Export may have failed or returned audio-only content."
)

return build_iframe_embed_code(
player_url=self.player_url,
width=width,
height=height,
title=title,
allow_fullscreen=allow_fullscreen,
)


class RTStreamShot:
"""RTStreamShot class for rtstream search results
Expand Down Expand Up @@ -159,6 +189,41 @@ def play(self) -> str:
self.generate_stream()
return play_stream(self.stream_url)

def get_embed_code(
self,
width: str = "100%",
height: int = 405,
title: str = "VideoDB Player",
allow_fullscreen: bool = True,
auto_generate: bool = True,
) -> str:
"""Generate an HTML iframe embed code for the rtstream shot.

:param str width: Width of the iframe (default: "100%")
:param int height: Height of the iframe in pixels (default: 405)
:param str title: Title attribute for the iframe (default: "VideoDB Player")
:param bool allow_fullscreen: Whether to allow fullscreen (default: True)
:param bool auto_generate: If True and player_url is missing, auto-generate it (default: True)
:return: HTML iframe string
:rtype: str
:raises ValueError: If player_url is not available
"""
if not self.player_url and auto_generate:
self.generate_stream()

if not self.player_url:
raise ValueError(
"player_url not available. Call generate_stream() first or set auto_generate=True."
)

return build_iframe_embed_code(
player_url=self.player_url,
width=width,
height=height,
title=title,
allow_fullscreen=allow_fullscreen,
)


class RTStreamSceneIndex:
"""RTStreamSceneIndex class to interact with the rtstream scene index
Expand Down Expand Up @@ -463,6 +528,40 @@ def generate_stream(
self.player_url = stream_data.get("player_url")
return self.player_url

def get_embed_code(
self,
width: str = "100%",
height: int = 405,
title: str = "VideoDB Player",
allow_fullscreen: bool = True,
) -> str:
"""Generate an HTML iframe embed code for the rtstream.

Note: Unlike other objects, RTStream does not support auto_generate
because generate_stream() requires start and end parameters.
Call generate_stream(start, end) first to populate player_url.

:param str width: Width of the iframe (default: "100%")
:param int height: Height of the iframe in pixels (default: 405)
:param str title: Title attribute for the iframe (default: "VideoDB Player")
:param bool allow_fullscreen: Whether to allow fullscreen (default: True)
:return: HTML iframe string
:rtype: str
:raises ValueError: If player_url is not available
"""
if not self.player_url:
raise ValueError(
"player_url not available. Call generate_stream(start, end) first to generate a stream."
)

return build_iframe_embed_code(
player_url=self.player_url,
width=width,
height=height,
title=title,
allow_fullscreen=allow_fullscreen,
)

def index_scenes(
self,
extraction_type=SceneExtractionType.time_based,
Expand Down
37 changes: 36 additions & 1 deletion videodb/search.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from abc import ABC, abstractmethod
from videodb._utils._video import play_stream
from videodb._utils._video import play_stream, build_iframe_embed_code
from videodb._constants import (
IndexType,
SearchType,
Expand Down Expand Up @@ -100,6 +100,41 @@ def play(self) -> str:
self.compile()
return play_stream(self.stream_url)

def get_embed_code(
self,
width: str = "100%",
height: int = 405,
title: str = "VideoDB Player",
allow_fullscreen: bool = True,
auto_generate: bool = True,
) -> str:
"""Generate an HTML iframe embed code for the search result.

:param str width: Width of the iframe (default: "100%")
:param int height: Height of the iframe in pixels (default: 405)
:param str title: Title attribute for the iframe (default: "VideoDB Player")
:param bool allow_fullscreen: Whether to allow fullscreen (default: True)
:param bool auto_generate: If True and player_url is missing, auto-compile it (default: True)
:return: HTML iframe string
:rtype: str
:raises ValueError: If player_url is not available
"""
if not self.player_url and auto_generate:
self.compile()

if not self.player_url:
raise ValueError(
"player_url not available. Call compile() first or set auto_generate=True."
)

return build_iframe_embed_code(
player_url=self.player_url,
width=width,
height=height,
title=title,
allow_fullscreen=allow_fullscreen,
)


class Search(ABC):
"""Search interface inside video or collection"""
Expand Down
37 changes: 36 additions & 1 deletion videodb/shot.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from typing import Optional
from videodb._utils._video import play_stream
from videodb._utils._video import play_stream, build_iframe_embed_code
from videodb._constants import (
ApiPath,
)
Expand Down Expand Up @@ -105,3 +105,38 @@ def play(self) -> str:
"""
self.generate_stream()
return play_stream(self.stream_url)

def get_embed_code(
self,
width: str = "100%",
height: int = 405,
title: str = "VideoDB Player",
allow_fullscreen: bool = True,
auto_generate: bool = True,
) -> str:
"""Generate an HTML iframe embed code for the shot.

:param str width: Width of the iframe (default: "100%")
:param int height: Height of the iframe in pixels (default: 405)
:param str title: Title attribute for the iframe (default: "VideoDB Player")
:param bool allow_fullscreen: Whether to allow fullscreen (default: True)
:param bool auto_generate: If True and player_url is missing, auto-generate it (default: True)
:return: HTML iframe string
:rtype: str
:raises ValueError: If player_url is not available
"""
if not self.player_url and auto_generate:
self.generate_stream()

if not self.player_url:
raise ValueError(
"player_url not available. Call generate_stream() first or set auto_generate=True."
)

return build_iframe_embed_code(
player_url=self.player_url,
width=width,
height=height,
title=title,
allow_fullscreen=allow_fullscreen,
)
Loading
Loading