diff --git a/video/src/vonage_video/models/captions.py b/video/src/vonage_video/models/captions.py new file mode 100644 index 00000000..3dd4ae77 --- /dev/null +++ b/video/src/vonage_video/models/captions.py @@ -0,0 +1,31 @@ +from typing import Optional + +from pydantic import BaseModel, Field + +from .enums import LanguageCode + + +class CaptionsOptions(BaseModel): + """The Options to send captions. + + Args: + session_id (str): The session ID. + token (str): The token. + language_code (LanguageCode, Optional): The language code. + max_duration (int, Optional): The maximum duration. + partial_captions (bool, Optional): The partial captions. + status_callback_url (str, Optional): The status callback URL. + """ + + session_id: str = Field(..., serialization_alias='sessionId') + token: str + language_code: Optional[LanguageCode] = Field( + None, serialization_alias='languageCode' + ) + max_duration: Optional[int] = Field( + None, le=300, ge=14400, serialization_alias='maxDuration' + ) + partial_captions: Optional[bool] = Field(None, serialization_alias='partialCaptions') + status_callback_url: Optional[str] = Field( + None, min_length=15, max_length=2048, serialization_alias='statusCallbackUrl' + ) diff --git a/video/src/vonage_video/models/enums.py b/video/src/vonage_video/models/enums.py index f1199e53..7abb6659 100644 --- a/video/src/vonage_video/models/enums.py +++ b/video/src/vonage_video/models/enums.py @@ -21,3 +21,19 @@ class MediaMode(str, Enum): class P2pPreference(str, Enum): DISABLED = 'disabled' ALWAYS = 'always' + + +class LanguageCode(str, Enum): + EN_US = 'en-US' + EN_AU = 'en-AU' + EN_GB = 'en-GB' + ZH_CN = 'zh-CN' + FR_FR = 'fr-FR' + FR_CA = 'fr-CA' + DE_DE = 'de-DE' + HI_IN = 'hi-IN' + IT_IT = 'it-IT' + JA_JP = 'ja-JP' + KO_KR = 'ko-KR' + PT_BR = 'pt-BR' + TH_TH = 'th-TH' diff --git a/video/src/vonage_video/models/signal.py b/video/src/vonage_video/models/signal.py index c39bf637..7edafda3 100644 --- a/video/src/vonage_video/models/signal.py +++ b/video/src/vonage_video/models/signal.py @@ -4,5 +4,5 @@ class SignalData(BaseModel): """The data to send in a signal.""" - type: str - data: str = Field(None, max_length=8192) + type: str = Field(..., max_length=128) + data: str = Field(..., max_length=8192) diff --git a/video/src/vonage_video/video.py b/video/src/vonage_video/video.py index 17f4c101..2a55943a 100644 --- a/video/src/vonage_video/video.py +++ b/video/src/vonage_video/video.py @@ -2,6 +2,7 @@ from pydantic import validate_call from vonage_http_client.http_client import HttpClient +from vonage_video.models.captions import CaptionsOptions from vonage_video.models.session import SessionOptions, VideoSession from vonage_video.models.signal import SignalData from vonage_video.models.stream import StreamInfo, StreamLayoutOptions @@ -131,15 +132,94 @@ def send_signal( Args: session_id (str): The session ID. data (SignalData): The data to send in the signal. - connection_id (str, Optional): The connection ID to send the signal to. + connection_id (str, Optional): The connection ID to send the signal to. If not provided, + the signal will be sent to all connections in the session. """ if connection_id is not None: url = f'/v2/project/{self._http_client.auth.application_id}/session/{session_id}/connection/{connection_id}/signal' else: - url = ( - f'/v2/project/{self._http_client.auth.application_id}/session/{session_id}/signal', - ) + url = f'/v2/project/{self._http_client.auth.application_id}/session/{session_id}/signal' - self._http_client.post( - self._http_client.video_host, url, data.model_dump(exclude_none=True) - ) + self._http_client.post( + self._http_client.video_host, url, data.model_dump(exclude_none=True) + ) + + @validate_call + def disconnect_client(self, session_id: str, connection_id: str) -> None: + """Disconnects a client from a session in the Vonage Video API. + + Args: + session_id (str): The session ID. + connection_id (str): The connection ID of the client to disconnect. + """ + self._http_client.delete( + self._http_client.video_host, + f'/v2/project/{self._http_client.auth.application_id}/session/{session_id}/connection/{connection_id}', + ) + + @validate_call + def mute_stream(self, session_id: str, stream_id: str) -> None: + """Mutes a stream in a session using the Vonage Video API. + + Args: + session_id (str): The session ID. + stream_id (str): The stream ID. + """ + self._http_client.post( + self._http_client.video_host, + f'/v2/project/{self._http_client.auth.application_id}/session/{session_id}/stream/{stream_id}/mute', + ) + + @validate_call + def mute_all_streams( + self, session_id: str, excluded_stream_ids: List[str] = None + ) -> None: + """Mutes all streams in a session using the Vonage Video API. + + Args: + session_id (str): The session ID. + excluded_stream_ids (List[str], Optional): The stream IDs to exclude from muting. + """ + params = {'active': True, 'excludedStreamIds': excluded_stream_ids} + self._toggle_mute_all_streams(session_id, params) + + @validate_call + def disable_mute_all_streams(self, session_id: str) -> None: + """Disables muting all streams in a session using the Vonage Video API. + + Args: + session_id (str): The session ID. + """ + self._toggle_mute_all_streams(session_id, {'active': False}) + + @validate_call + def _toggle_mute_all_streams(self, session_id: str, params: dict) -> None: + """Mutes all streams in a session using the Vonage Video API. + + Args: + session_id (str): The session ID. + params (dict): The parameters to send in the request. + """ + self._http_client.post( + self._http_client.video_host, + f'/v2/project/{self._http_client.auth.application_id}/session/{session_id}/mute', + params, + ) + + @validate_call + def enable_captions(self, options: CaptionsOptions) -> str: + """Enables captions in a session using the Vonage Video API. + + Args: + options (CaptionsOptions): Options for the captions. + + Returns: + str: The captions stream ID. + """ + response = self._http_client.post( + self._http_client.video_host, + f'/v2/project/{self._http_client.auth.application_id}/captions', + options.model_dump(exclude_none=True), + ) + + return response['captionsId'] diff --git a/video/tests/test_captions.py b/video/tests/test_captions.py new file mode 100644 index 00000000..ded1a130 --- /dev/null +++ b/video/tests/test_captions.py @@ -0,0 +1,26 @@ +from os.path import abspath + +import responses +from vonage_http_client import HttpClient +from vonage_video.models.captions import CaptionsOptions +from vonage_video.video import Video + +from testutils import build_response, get_mock_jwt_auth + +path = abspath(__file__) + + +video = Video(HttpClient(get_mock_jwt_auth())) + + +@responses.activate +def test_start_captions(): + build_response( + path, + 'POST', + 'https://video.api.vonage.com/v2/project/test_application_id/captions', + 'start_captions.json', + 202, + ) + + options = CaptionsOptions() diff --git a/video/tests/test_moderation.py b/video/tests/test_moderation.py new file mode 100644 index 00000000..e7748d70 --- /dev/null +++ b/video/tests/test_moderation.py @@ -0,0 +1,70 @@ +from os.path import abspath + +import responses +from vonage_http_client import HttpClient +from vonage_video.video import Video + +from testutils import build_response, get_mock_jwt_auth + +path = abspath(__file__) + + +video = Video(HttpClient(get_mock_jwt_auth())) + + +@responses.activate +def test_disconnect_client(): + build_response( + path, + 'DELETE', + 'https://video.api.vonage.com/v2/project/test_application_id/session/test_session_id/connection/test_connection_id', + status_code=204, + ) + + video.disconnect_client( + session_id='test_session_id', connection_id='test_connection_id' + ) + + assert responses.calls[0].response.status_code == 204 + + +@responses.activate +def test_mute_stream(): + build_response( + path, + 'POST', + 'https://video.api.vonage.com/v2/project/test_application_id/session/test_session_id/stream/test_stream_id/mute', + ) + + video.mute_stream(session_id='test_session_id', stream_id='test_stream_id') + + assert responses.calls[0].response.status_code == 200 + + +@responses.activate +def test_mute_all_streams(): + build_response( + path, + 'POST', + 'https://video.api.vonage.com/v2/project/test_application_id/session/test_session_id/mute', + ) + + video.mute_all_streams(session_id='test_session_id') + assert responses.calls[0].response.status_code == 200 + + video.disable_mute_all_streams(session_id='test_session_id') + assert responses.calls[1].response.status_code == 200 + + +@responses.activate +def test_mute_all_streams_excluded_stream_ids(): + build_response( + path, + 'POST', + 'https://video.api.vonage.com/v2/project/test_application_id/session/test_session_id/mute', + ) + + video.mute_all_streams( + session_id='test_session_id', excluded_stream_ids=['test_stream_id'] + ) + assert responses.calls[0].response.status_code == 200 diff --git a/video/tests/test_signal.py b/video/tests/test_signal.py new file mode 100644 index 00000000..dacdadff --- /dev/null +++ b/video/tests/test_signal.py @@ -0,0 +1,47 @@ +from os.path import abspath + +import responses +from vonage_http_client import HttpClient +from vonage_video.models.signal import SignalData +from vonage_video.video import Video + +from testutils import build_response, get_mock_jwt_auth + +path = abspath(__file__) + + +video = Video(HttpClient(get_mock_jwt_auth())) + + +@responses.activate +def test_send_signal_all(): + build_response( + path, + 'POST', + 'https://video.api.vonage.com/v2/project/test_application_id/session/test_session_id/signal', + status_code=204, + ) + + video.send_signal( + session_id='test_session_id', data=SignalData(type='msg', data='Hello, World!') + ) + + assert responses.calls[0].response.status_code == 204 + + +@responses.activate +def test_send_signal_to_connection_id(): + build_response( + path, + 'POST', + 'https://video.api.vonage.com/v2/project/test_application_id/session/test_session_id/connection/test_connection_id/signal', + status_code=204, + ) + + video.send_signal( + session_id='test_session_id', + data=SignalData(type='msg', data='Hello, World!'), + connection_id='test_connection_id', + ) + + assert responses.calls[0].response.status_code == 204