Skip to content

Commit e5c31e1

Browse files
committed
add captioning, start audio connector
1 parent 29a1e49 commit e5c31e1

File tree

9 files changed

+197
-9
lines changed

9 files changed

+197
-9
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from typing import Optional
2+
3+
from pydantic import BaseModel, Field
4+
from vonage_video.models.enums import AudioSampleRate
5+
6+
7+
class AudioConnectorWebsocket(BaseModel):
8+
"""The audio connector websocket options.
9+
10+
Args:
11+
uri (str): The URI.
12+
streams (list): The streams.
13+
headers (dict): The headers.
14+
audio_rate (AudioSampleRate): The audio sample rate.
15+
"""
16+
17+
uri: str
18+
streams: Optional[list] = None
19+
headers: Optional[dict] = None
20+
audio_rate: Optional[AudioSampleRate] = Field(None, serialization_alias='audioRate')
21+
22+
23+
class AudioConnectorOptions(BaseModel):
24+
"""Options for the audio connector.
25+
26+
Args:
27+
session_id (str): The session ID.
28+
token (str): The token.
29+
websocket (AudioConnectorWebsocket): The audio connector websocket.
30+
"""
31+
32+
session_id: str = Field(..., serialization_alias='sessionId')
33+
token: str
34+
websocket: AudioConnectorWebsocket
35+
36+
37+
class AudioConnectorData(BaseModel):
38+
"""Class containing audio connector ID and audio captioning session ID."""
39+
40+
id: Optional[str] = None
41+
captions_id: Optional[str] = Field(None, serialization_alias='captionsId')

video/src/vonage_video/models/captions.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class CaptionsOptions(BaseModel):
1010
1111
Args:
1212
session_id (str): The session ID.
13-
token (str): The token.
13+
token (str): A valid token with moderation privileges.
1414
language_code (LanguageCode, Optional): The language code.
1515
max_duration (int, Optional): The maximum duration.
1616
partial_captions (bool, Optional): The partial captions.
@@ -23,9 +23,19 @@ class CaptionsOptions(BaseModel):
2323
None, serialization_alias='languageCode'
2424
)
2525
max_duration: Optional[int] = Field(
26-
None, le=300, ge=14400, serialization_alias='maxDuration'
26+
None, ge=300, le=14400, serialization_alias='maxDuration'
2727
)
2828
partial_captions: Optional[bool] = Field(None, serialization_alias='partialCaptions')
2929
status_callback_url: Optional[str] = Field(
3030
None, min_length=15, max_length=2048, serialization_alias='statusCallbackUrl'
3131
)
32+
33+
34+
class CaptionsData(BaseModel):
35+
"""Class containing captions ID.
36+
37+
Args:
38+
captions_id (str): The captions ID.
39+
"""
40+
41+
captions_id: str = Field(..., serialization_alias='captionsId')

video/src/vonage_video/models/enums.py

+5
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,8 @@ class LanguageCode(str, Enum):
3737
KO_KR = 'ko-KR'
3838
PT_BR = 'pt-BR'
3939
TH_TH = 'th-TH'
40+
41+
42+
class AudioSampleRate(str, Enum):
43+
KHZ_8 = 8000
44+
KHZ_16 = 16000

video/src/vonage_video/models/session.py

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ def set_p2p_preference_if_archive_mode_set(self):
4141

4242

4343
class VideoSession(BaseModel):
44+
"""The new session ID and options specified in the request."""
45+
4446
session_id: str
4547
archive_mode: Optional[ArchiveMode] = None
4648
media_mode: Optional[MediaMode] = None

video/src/vonage_video/models/stream.py

+18
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@
44

55

66
class StreamInfo(BaseModel):
7+
"""The stream information.
8+
9+
Args:
10+
id (str): The stream ID.
11+
video_type (str): The video type.
12+
name (str): The name.
13+
layout_class_list (list(str)): The layout class list.
14+
"""
15+
716
id: Optional[str] = Field(None, validation_alias='id')
817
video_type: Optional[str] = Field(None, validation_alias='videoType')
918
name: Optional[str] = Field(None, validation_alias='name')
@@ -13,9 +22,18 @@ class StreamInfo(BaseModel):
1322

1423

1524
class StreamLayout(BaseModel):
25+
"""The stream layout.
26+
27+
Args:
28+
id (str): The stream ID.
29+
layout_class_list (list): The layout class list.
30+
"""
31+
1632
id: str
1733
layout_class_list: List[str] = Field(..., serialization_alias='layoutClassList')
1834

1935

2036
class StreamLayoutOptions(BaseModel):
37+
"""The options for the stream layout."""
38+
2139
items: List[StreamLayout]

video/src/vonage_video/video.py

+35-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from pydantic import validate_call
44
from vonage_http_client.http_client import HttpClient
5-
from vonage_video.models.captions import CaptionsOptions
5+
from vonage_video.models.captions import CaptionsData, CaptionsOptions
66
from vonage_video.models.session import SessionOptions, VideoSession
77
from vonage_video.models.signal import SignalData
88
from vonage_video.models.stream import StreamInfo, StreamLayoutOptions
@@ -207,19 +207,49 @@ def _toggle_mute_all_streams(self, session_id: str, params: dict) -> None:
207207
)
208208

209209
@validate_call
210-
def enable_captions(self, options: CaptionsOptions) -> str:
210+
def start_captions(self, options: CaptionsOptions) -> CaptionsData:
211211
"""Enables captions in a session using the Vonage Video API.
212212
213213
Args:
214214
options (CaptionsOptions): Options for the captions.
215215
216216
Returns:
217-
str: The captions stream ID.
217+
CaptionsData: Class containing captions ID.
218218
"""
219219
response = self._http_client.post(
220220
self._http_client.video_host,
221221
f'/v2/project/{self._http_client.auth.application_id}/captions',
222-
options.model_dump(exclude_none=True),
222+
options.model_dump(exclude_none=True, by_alias=True),
223223
)
224224

225-
return response['captionsId']
225+
return CaptionsData(captions_id=response['captionsId'])
226+
227+
@validate_call
228+
def stop_captions(self, captions: CaptionsData) -> None:
229+
"""Disables captions in a session using the Vonage Video API.
230+
231+
Args:
232+
captions (CaptionsData): The captions data.
233+
"""
234+
self._http_client.post(
235+
self._http_client.video_host,
236+
f'/v2/project/{self._http_client.auth.application_id}/captions/{captions.captions_id}/stop',
237+
)
238+
239+
@validate_call
240+
def start_audio_connector(self, options: AudioConnectorOptions) -> AudioConnectorData:
241+
"""Starts an audio connector in a session using the Vonage Video API.
242+
243+
Args:
244+
options (AudioConnectorOptions): Options for the audio connector.
245+
246+
Returns:
247+
AudioConnectorData: Class containing audio connector ID.
248+
"""
249+
response = self._http_client.post(
250+
self._http_client.video_host,
251+
f'/v2/project/{self._http_client.auth.application_id}/connect',
252+
options.model_dump(exclude_none=True, by_alias=True),
253+
)
254+
255+
return AudioConnectorData(audio_connector_id=response['audioConnectorId'])
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"code": 60003,
3+
"message": "Audio captioning is already enabled",
4+
"description": "Audio captioning is already enabled"
5+
}

video/tests/data/start_captions.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"captionsId": "bc01a6b7-0e8e-4aa0-bb4e-2390f7cb18a1"
3+
}

video/tests/test_captions.py

+76-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
from os.path import abspath
22

33
import responses
4+
from pytest import raises
45
from vonage_http_client import HttpClient
5-
from vonage_video.models.captions import CaptionsOptions
6+
from vonage_http_client.errors import HttpRequestError
7+
from vonage_video.models.captions import CaptionsData, CaptionsOptions
8+
from vonage_video.models.enums import LanguageCode, TokenRole
9+
from vonage_video.models.token import TokenOptions
610
from vonage_video.video import Video
711

812
from testutils import build_response, get_mock_jwt_auth
@@ -13,6 +17,26 @@
1317
video = Video(HttpClient(get_mock_jwt_auth()))
1418

1519

20+
def test_captions_options_model():
21+
options = CaptionsOptions(
22+
session_id='test_session_id',
23+
token='test_token',
24+
language_code=LanguageCode.EN_GB,
25+
max_duration=300,
26+
partial_captions=True,
27+
status_callback_url='example.com/status',
28+
)
29+
30+
assert options.model_dump(by_alias=True) == {
31+
'sessionId': 'test_session_id',
32+
'token': 'test_token',
33+
'languageCode': 'en-GB',
34+
'maxDuration': 300,
35+
'partialCaptions': True,
36+
'statusCallbackUrl': 'example.com/status',
37+
}
38+
39+
1640
@responses.activate
1741
def test_start_captions():
1842
build_response(
@@ -23,4 +47,54 @@ def test_start_captions():
2347
202,
2448
)
2549

26-
options = CaptionsOptions()
50+
session_id = 'test_session_id'
51+
options = CaptionsOptions(
52+
session_id=session_id,
53+
token=video.generate_client_token(
54+
TokenOptions(session_id=session_id, role=TokenRole.MODERATOR)
55+
),
56+
language_code=LanguageCode.EN_GB,
57+
max_duration=300,
58+
partial_captions=True,
59+
status_callback_url='https://example.com/status',
60+
)
61+
captions = video.start_captions(options)
62+
63+
assert captions.captions_id == 'bc01a6b7-0e8e-4aa0-bb4e-2390f7cb18a1'
64+
65+
66+
@responses.activate
67+
def test_start_captions_error_already_enabled():
68+
build_response(
69+
path,
70+
'POST',
71+
'https://video.api.vonage.com/v2/project/test_application_id/captions',
72+
'captions_error_already_enabled.json',
73+
409,
74+
)
75+
76+
session_id = 'test_session_id'
77+
options = CaptionsOptions(
78+
session_id=session_id,
79+
token=video.generate_client_token(
80+
TokenOptions(session_id=session_id, role=TokenRole.MODERATOR)
81+
),
82+
)
83+
84+
with raises(HttpRequestError) as e:
85+
video.start_captions(options)
86+
assert 'Audio captioning is already enabled' in e.value.message
87+
88+
89+
@responses.activate
90+
def test_stop_captions():
91+
build_response(
92+
path,
93+
'POST',
94+
'https://video.api.vonage.com/v2/project/test_application_id/captions/test_captions_id/stop',
95+
status_code=202,
96+
)
97+
98+
video.stop_captions(CaptionsData(captions_id='test_captions_id'))
99+
100+
assert responses.calls[0].response.status_code == 202

0 commit comments

Comments
 (0)