8000 feat: add player by ankit-v2-3 · Pull Request #2 · video-db/videodb-python · GitHub
[go: up one dir, main page]

Skip to content

feat: add player #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Dec 28, 2023
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
1 change: 1 addition & 0 deletions .gitignore
8000
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ dist/*
venv/
.vscode/*
example.ipynb
example.py
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ video = conn.upload(url="https://www.youtube.com/")
video = conn.upload(file_path="path/to/video.mp4")

# get the stream url for the video
stream_url = video.get_stream()
stream_url = video.generate_stream()

```

Expand Down Expand Up @@ -113,7 +113,7 @@ collection = conn.get_collection()
# get the video from the collection
video = collection.get_video("video_id")

# index the video for symantic search
# index the video for semantic search
video.index_spoken_words()

# search relevant moment in video and stream resultant video clip instantly.
Expand All @@ -139,7 +139,7 @@ stream_url = result.compile()
# get shots of the result returns a list of Shot objects
shots = result.get_shots()
# get stream url of the shot
short_stream_url = shots[0].get_stream()
short_stream_url = shots[0].generate_stream()

```

Expand All @@ -155,10 +155,10 @@ video = collection.get_video("video_id")
# get the stream url of the dynamically curated video based on the given timeline sequence
# optional parameters:
# - timeline: Optional[list[tuple[int, int]] to specify the start and end time of the video
stream_url = video.get_stream(timeline=[(0, 10), (30, 40)])
stream_url = video.generate_stream(timeline=[(0, 10), (30, 40)])

# get thumbnail of the video
thumbnail = video.get_thumbnail()
# get thumbnail url of the video
thumbnail_url = video.generate_thumbnail()

# get transcript of the video
# optional parameters:
Expand Down
2 changes: 2 additions & 0 deletions videodb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import logging

from typing import Optional
from videodb._utils._video import play_stream
from videodb._constants import VIDEO_DB_API
from videodb.client import Connection
from videodb.exceptions import (
Expand All @@ -23,6 +24,7 @@
"AuthenticationError",
"InvalidRequestError",
"SearchError",
"play_stream",
]


Expand Down
1 change: 1 addition & 0 deletions videodb/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@


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


class SearchType:
Expand Down
2 changes: 1 addition & 1 deletion videodb/_utils/_http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def _handle_request_error(self, e: requests.exceptions.RequestException) -> None
f"Invalid request: {str(e)}", e.response
) from None

@backoff.on_exception(backoff.expo, Exception, max_time=500)
@backoff.on_exception(backoff.expo, Exception, max_time=500, logger=None)
def _get_output(self, url: str):
"""Get the output from an async request"""
response_json = self.session.get(url).json()
Expand Down
23 changes: 23 additions & 0 deletions videodb/_utils/_video.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import webbrowser as web

from videodb._constants import PLAYER_URL


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

:param str url: The url of the stream
:return: The player url if the stream is opened in the browser or the iframe if the stream is opened in the notebook
"""
player = f"{PLAYER_URL}?url={url}"
opend = web.open(player)
if not opend:
try:
from IPython.display import IFrame

player_width = 800
player_height = 400
return IFrame(player, player_width, player_height)
except ImportError:
return player
return player
36 changes: 28 additions & 8 deletions videodb/search.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from abc import ABC, abstractmethod
from videodb._utils._video import play_stream
from videodb._constants import (
SearchType,
ApiPath,
Expand All @@ -15,8 +16,8 @@ class SearchResult:
def __init__(self, _connection, **kwargs):
self._connection = _connection
self.shots = []
self.text_summary = None
self.stream = None
self.stream_url = None
self.player_url = None
self.collection_id = "default"
self._results = kwargs.get("results", [])
self._format_results()
Expand All @@ -38,18 +39,27 @@ def _format_results(self):
)
)

def __repr__(self) -> str:
return (
f"SearchResult("
f"collection_id={self.collection_id}, "
f"stream_url={self.stream_url}, "
f"player_url={self.player_url}, "
f"shots={self.shots})"
)

def get_shots(self) -> List[Shot]:
return self.shots

def compile(self) -> str:
"""Compile the search result shots into a stream link
"""Compile the search result shots into a stream url

:raises SearchError: If no shots are found in the search results
:return: The stream link
:return: The stream url
:rtype: str
"""
if self.stream:
return self.stream
if self.stream_url:
return self.stream_url
elif self.shots:
compile_data = self._connection.post(
path=f"{ApiPath.compile}",
Expand All @@ -62,12 +72,22 @@ def compile(self) -> str:
for shot in self.shots
],
)
self.stream = compile_data.get("stream_link")
return self.stream
self.stream_url = compile_data.get("stream_url")
self.player_url = compile_data.get("player_url")
return self.stream_url

else:
raise SearchError("No shots found in search results to compile")

def play(self) -> str:
"""Generate a stream url for the shot and open it in the default browser

:return: The stream url
:rtype: str
"""
self.compile()
return play_stream(self.stream_url)


class Search(ABC):
"""Search interface inside video or collection"""
Expand Down
31 changes: 22 additions & 9 deletions videodb/shot.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@


from typing import Optional
from videodb._utils._video import play_stream
from videodb._constants import (
ApiPath,
)
Expand Down Expand Up @@ -29,7 +30,8 @@ def __init__(
self.end = end
self.text = text
self.search_score = search_score
self.stream = None
self.stream_url = None
self.player_url = None

def __repr__(self) -> str:
return (
Expand All @@ -40,22 +42,23 @@ def __repr__(self) -> str:
f"end={self.end}, "
f"text={self.text}, "
f"search_score={self.search_score}, "
f"stream={self.stream})"
f"stream_url={self.stream_url}, "
f"player_url={self.player_url})"
)

def __getitem__(self, key):
"""Get an item from the shot object"""
return self.__dict__[key]

def get_stream(self) -> str:
"""Get the shot into a stream link
def generate_stream(self) -> str:
"""Generate a stream url for the shot

:return: The stream link
:return: The stream url
:rtype: str
"""

if self.stream:
return self.stream
if self.stream_url:
return self.stream_url
else:
stream_data = self._connection.post(
path=f"{ApiPath.video}/{self.video_id}/{ApiPath.stream}",
Expand All @@ -64,5 +67,15 @@ def get_stream(self) -> str:
"length": self.video_length,
},
)
self.stream = stream_data.get("stream_link")
return self.stream
self.stream_url = stream_data.get("stream_url")
self.player_url = stream_data.get("player_url")
return self.stream_url

def play(self) -> str:
"""Generate a stream url for the shot and open it in the default browser/ notebook

:return: The stream url
:rtype: str
"""
self.generate_stream()
return play_stream(self.stream_url)
52 changes: 31 additions & 21 deletions videodb/video.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Optional
from videodb._utils._video import play_stream
from videodb._constants import (
ApiPath,
SearchType,
Expand All @@ -6,18 +8,18 @@
)
from videodb.search import SearchFactory, SearchResult
from videodb.shot import Shot
from typing import Optional


class Video:
def __init__(self, _connection, id: str, collection_id: str, **kwargs) -> None:
self._connection = _connection
self.id = id
self.collection_id = collection_id
self.stream_link = kwargs.get("stream_link", None)
self.stream_url = kwargs.get("stream_url", None)
self.player_url = kwargs.get("player_url", None)
self.name = kwargs.get("name", None)
self.description = kwargs.get("description", None)
self.thumbnail = kwargs.get("thumbnail", None)
self.thumbnail_url = kwargs.get("thumbnail_url", None)
self.length = float(kwargs.get("length", 0.0))
self.transcript = kwargs.get("transcript", None)
self.transcript_text = kwargs.get("transcript_text", None)
Expand All @@ -27,10 +29,11 @@ def __repr__(self) -> str:
f"Video("
f"id={self.id}, "
f"collection_id={self.collection_id}, "
f"stream_link={self.stream_link}, "
f"stream_url={self.stream_url}, "
f"player_url={self.player_url}, "
f"name={self.name}, "
f"description={self.description}, "
f"thumbnail={self.thumbnail}, "
f"thumbnail_url={self.thumbnail_url}, "
f"length={self.length})"
)

Expand Down Expand Up @@ -63,16 +66,16 @@ def delete(self) -> None:
"""
self._connection.delete(path=f"{ApiPath.video}/{self.id}")

def get_stream(self, timeline: Optional[list[tuple[int, int]]] = None) -> str:
"""Get the stream link of the video
def generate_stream(self, timeline: Optional[list[tuple[int, int]]] = None) -> str:
"""Generate the stream url of the video

:param list timeline: The timeline of the video to be streamed. Defaults to None.
:raises InvalidRequestError: If the get_stream fails
:return: The stream link of the video
:return: The stream url of the video
:rtype: str
"""
if not timeline and self.stream_link:
return self.stream_link
if not timeline and self.stream_url:
return self.stream_url

stream_data = self._connection.post(
path=f"{ApiPath.video}/{self.id}/{ApiPath.stream}",
Expand All @@ -81,16 +84,16 @@ def get_stream(self, timeline: Optional[list[tuple[int, int]]] = None) -> str:
"length": self.length,
},
)
return stream_data.get("stream_link")
return stream_data.get("stream_url", None)

def get_thumbnail(self):
if self.thumbnail:
return self.thumbnail
def generate_thumbnail(self):
if self.thumbnail_url:
return self.thumbnail_url
thumbnail_data = self._connection.get(
path=f"{ApiPath.video}/{self.id}/{ApiPath.thumbnail}"
)
self.thumbnail = thumbnail_data.get("thumbnail")
return self.thumbnail
self.thumbnail_url = thumbnail_data.get("thumbnail_url")
return self.thumbnail_url

def _fetch_transcript(self, force: bool = False) -> None:
if self.transcript and not force:
Expand All @@ -111,7 +114,7 @@ def get_transcript_text(self, force: bool = False) -> str:
return self.transcript_text

def index_spoken_words(self) -> None:
"""Symantic indexing of spoken words in the video
"""Semantic indexing of spoken words in the video

:raises InvalidRequestError: If the video is already indexed
:return: None if the indexing is successful
Expand All @@ -132,15 +135,15 @@ def add_subtitle(self) -> str:
"type": Workflows.add_subtitles,
},
)
return subtitle_data.get("stream_link")
return subtitle_data.get("stream_url", None)

def insert_video(self, video, timestamp: float) -> str:
"""Insert a video into another video

:param Video video: The video to be inserted
:param float timestamp: The timestamp where the video should be inserted
:raises InvalidRequestError: If the insert fails
:return: The stream link of the inserted video
:return: The stream url of the inserted video
:rtype: str
"""
if timestamp > float(self.length):
Expand Down Expand Up @@ -171,5 +174,12 @@ def insert_video(self, video, timestamp: float) -> str:
for shot in all_shots
],
)
stream_link = compile_data.get("stream_link")
return stream_link
return compile_data.get("stream_url", None)

def play(self) -> str:
"""Open the player url in the browser/iframe and return the stream url

:return: The stream url
:rtype: str
"""
return play_stream(self.stream_url)
0