Skip to content

Commit 0935d8e

Browse files
committed
add archive methods and tests
1 parent 3123abe commit 0935d8e

11 files changed

+587
-33
lines changed

video/src/vonage_video/errors.py

+4
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,7 @@ class LayoutStylesheetError(VideoError):
3131

3232
class LayoutScreenshareTypeError(VideoError):
3333
"""Error with the `screenshare_type` property when setting a layout."""
34+
35+
36+
class InvalidArchiveStateError(VideoError):
37+
"""The archive state was invalid for the specified operation."""

video/src/vonage_video/models/archive.py

+73-21
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class ListArchivesFilter(BaseModel):
2626
"""
2727

2828
offset: Optional[int] = None
29-
page_size: Optional[int] = Field(1000, serialization_alias='count')
29+
page_size: Optional[int] = Field(100, serialization_alias='count')
3030
session_id: Optional[str] = None
3131

3232

@@ -44,54 +44,81 @@ class ArchiveStream(BaseModel):
4444
has_video: Optional[bool] = Field(None, validation_alias='hasVideo')
4545

4646

47+
class Transcription(BaseModel):
48+
"""Model for transcription options for an archive.
49+
50+
Args:
51+
status (str, Optional): The status of the transcription.
52+
reason (str, Optional): May give a brief reason for the transcription status.
53+
"""
54+
55+
status: Optional[str] = None
56+
reason: Optional[str] = None
57+
58+
4759
class Archive(BaseModel):
4860
"""Model for an archive.
4961
5062
Args:
63+
id (str, Optional): The unique archive ID.
64+
status (ArchiveStatus, Optional): The status of the archive.
65+
name (str, Optional): The name of the archive.
66+
reason (str, Optional): May give a brief reason for the archive status.
67+
session_id (str, Optional): The session ID of the Vonage Video session.
68+
application_id (str, Optional): The Vonage application ID.
5169
created_at (int, Optional): The timestamp when the archive when the archive
5270
started recording, expressed in milliseconds since the Unix epoch.
71+
size (int, Optional): The size of the archive.
5372
duration (int, Optional): The duration of the archive in seconds.
5473
For archives that have are being recorded, this value is set to 0.
74+
output_mode (OutputMode, Optional): The output mode of the archive.
75+
stream_mode (StreamMode, Optional): Whether streams included in the archive
76+
are selected automatically (`auto`, the default) or manually (`manual`).
5577
has_audio (bool, Optional): Whether the archive will record audio.
5678
has_video (bool, Optional): Whether the archive will record video.
57-
id (str, Optional): The unique archive ID.
79+
has_transcription (bool, Optional): Whether audio will be transcribed.
80+
sha256_sum (str, Optional): The SHA-256 hash of the archive.
81+
password (str, Optional): The password for the archive.
82+
updated_at (int, Optional): The timestamp when the archive was last updated,
83+
expressed in milliseconds since the Unix epoch.
5884
multi_archive_tag (str, Optional): Set this to support recording multiple
5985
archives for the same session simultaneously. Set this to a unique string
6086
for each simultaneous archive of an ongoing session.
61-
name (str, Optional): The name of the archive.
62-
application_id (str, Optional): The Vonage application ID.
63-
reason (str, Optional): This is set when the `status` is `stopped` or `failed`.
87+
event (str, Optional): The event that triggered the response.
6488
resolution (VideoResolution, Optional): The resolution of the archive.
65-
session_id (str, Optional): The session ID of the Vonage Video session.
66-
size (int, Optional): The size of the archive.
67-
status (ArchiveStatus, Optional): The status of the archive.
68-
stream_mode (StreamMode, Optional): Whether streams included in the archive
69-
are selected automatically (`auto`, the default) or manually (`manual`).
7089
streams (List[ArchiveStream], Optional): The streams in the archive.
7190
url (str, Optional): The download URL of the available archive file.
7291
This is only set for an archive with the status set to `available`.
92+
transcription (Transcription, Optional): Transcription options for the archive.
7393
"""
7494

75-
created_at: Optional[int] = Field(None, validation_alias='createdAt')
76-
duration: Optional[int] = None
77-
has_audio: Optional[bool] = Field(None, validation_alias='hasAudio')
78-
has_video: Optional[bool] = Field(None, validation_alias='hasVideo')
7995
id: Optional[str] = None
80-
multi_archive_tag: Optional[str] = Field(None, validation_alias='multiArchiveTag')
96+
status: Optional[ArchiveStatus] = None
8197
name: Optional[str] = None
82-
application_id: Optional[str] = Field(None, validation_alias='applicationId')
8398
reason: Optional[str] = None
84-
resolution: Optional[VideoResolution] = None
8599
session_id: Optional[str] = Field(None, validation_alias='sessionId')
100+
application_id: Optional[str] = Field(None, validation_alias='applicationId')
101+
created_at: Optional[int] = Field(None, validation_alias='createdAt')
86102
size: Optional[int] = None
87-
status: Optional[ArchiveStatus] = None
103+
duration: Optional[int] = None
104+
output_mode: Optional[OutputMode] = Field(None, validation_alias='outputMode')
88105
stream_mode: Optional[StreamMode] = Field(None, validation_alias='streamMode')
106+
has_audio: Optional[bool] = Field(None, validation_alias='hasAudio')
107+
has_video: Optional[bool] = Field(None, validation_alias='hasVideo')
108+
has_transcription: Optional[bool] = Field(None, validation_alias='hasTranscription')
109+
sha256_sum: Optional[str] = Field(None, validation_alias='sha256sum')
110+
password: Optional[str] = None
111+
updated_at: Optional[int] = Field(None, validation_alias='updatedAt')
112+
multi_archive_tag: Optional[str] = Field(None, validation_alias='multiArchiveTag')
113+
event: Optional[str] = None
114+
resolution: Optional[VideoResolution] = None
89115
streams: Optional[List[ArchiveStream]] = None
90116
url: Optional[str] = None
117+
transcription: Optional[Transcription] = None
91118

92119

93-
class Layout(BaseModel):
94-
"""Model for layout options for an archive.
120+
class ComposedLayout(BaseModel):
121+
"""Model for layout options for a composed archive.
95122
96123
Args:
97124
type (str): Specify this to assign the initial layout type for the archive.
@@ -154,7 +181,10 @@ class CreateArchiveRequest(BaseModel):
154181
session_id: str = Field(..., serialization_alias='sessionId')
155182
has_audio: Optional[bool] = Field(None, serialization_alias='hasAudio')
156183
has_video: Optional[bool] = Field(None, serialization_alias='hasVideo')
157-
layout: Optional[Layout] = None
184+
has_transcription: Optional[bool] = Field(
185+
None, serialization_alias='hasTranscription'
186+
)
187+
layout: Optional[ComposedLayout] = None
158188
multi_archive_tag: Optional[str] = Field(None, serialization_alias='multiArchiveTag')
159189
name: Optional[str] = None
160190
output_mode: Optional[OutputMode] = Field(None, serialization_alias='outputMode')
@@ -180,3 +210,25 @@ def no_layout_or_resolution_for_individual_archives(self):
180210
'The `layout` property cannot be set for `archive_mode: \'individual\'`.'
181211
)
182212
return self
213+
214+
@model_validator(mode='after')
215+
def transcription_only_for_individual_archives(self):
216+
if self.output_mode == OutputMode.COMPOSED and self.has_transcription is True:
217+
raise IndividualArchivePropertyError(
218+
'The `has_transcription` property can only be set for `archive_mode: \'individual\'`.'
219+
)
220+
return self
221+
222+
223+
class AddStreamRequest(BaseModel):
224+
"""Model for adding a stream to an archive.
225+
226+
Args:
227+
stream_id (str): ID of the stream to add to the archive.
228+
has_audio (bool, Optional): Whether the stream has audio.
229+
has_video (bool, Optional): Whether the stream has video.
230+
"""
231+
232+
stream_id: str = Field(..., serialization_alias='addStream')
233+
has_audio: Optional[bool] = Field(None, serialization_alias='hasAudio')
234+
has_video: Optional[bool] = Field(None, serialization_alias='hasVideo')

video/src/vonage_video/models/enums.py

+1
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,4 @@ class ArchiveStatus(str, Enum):
8686
STARTED = 'started'
8787
STOPPED = 'stopped'
8888
UPLOADED = 'uploaded'
89+
DELETED = 'deleted'

video/src/vonage_video/models/experience_composer.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,4 @@ class ListExperienceComposersFilter(BaseModel):
7878
"""
7979

8080
offset: Optional[int] = None
81-
page_size: Optional[int] = Field(1000, serialization_alias='count')
81+
page_size: Optional[int] = Field(100, serialization_alias='count')

video/src/vonage_video/video.py

+170-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
from typing import List, Optional, Tuple
22

33
from pydantic import validate_call
4+
from vonage_http_client.errors import HttpRequestError
45
from vonage_http_client.http_client import HttpClient
5-
from vonage_video.models.archive import Archive, CreateArchiveRequest, ListArchivesFilter
6+
from vonage_video.errors import InvalidArchiveStateError
7+
from vonage_video.models.archive import (
8+
AddStreamRequest,
9+
Archive,
10+
ComposedLayout,
11+
CreateArchiveRequest,
12+
ListArchivesFilter,
13+
)
614
from vonage_video.models.audio_connector import AudioConnectorData, AudioConnectorOptions
715
from vonage_video.models.captions import CaptionsData, CaptionsOptions
816
from vonage_video.models.experience_composer import (
@@ -296,7 +304,7 @@ def list_experience_composers(
296304
composer objects, the total count of Experience Composers and the required offset value
297305
for the next page, if applicable.
298306
i.e.
299-
experience_composers: List[ExperienceComposer], count: int, next_page_index: Optional[int]
307+
experience_composers: List[ExperienceComposer], count: int, next_page_offset: Optional[int]
300308
"""
301309
response = self._http_client.get(
302310
self._http_client.video_host,
@@ -352,8 +360,165 @@ def stop_experience_composer(self, experience_composer_id: str) -> None:
352360
def list_archives(
353361
self, filter: ListArchivesFilter
354362
) -> Tuple[List[Archive], int, Optional[int]]:
355-
pass
363+
"""Lists archives associated with a Vonage Application.
364+
365+
Args:
366+
filter (ListArchivesFilter): The filters for the archives.
367+
368+
Returns:
369+
Tuple[List[Archive], int, Optional[int]]: A tuple containing a list of archive objects,
370+
the total count of archives and the required offset value for the next page, if applicable.
371+
i.e.
372+
archives: List[Archive], count: int, next_page_offset: Optional[int]
373+
"""
374+
response = self._http_client.get(
375+
self._http_client.video_host,
376+
f'/v2/project/{self._http_client.auth.application_id}/archive',
377+
filter.model_dump(exclude_none=True, by_alias=True),
378+
)
379+
380+
index = filter.offset + 1 or 1
381+
page_size = filter.page_size
382+
archives = []
383+
384+
try:
385+
for archive in response['items']:
386+
archives.append(Archive(**archive))
387+
except KeyError:
388+
return [], 0, None
389+
390+
count = response['count']
391+
if count > page_size * (index):
392+
return archives, count, index
393+
return archives, count, None
356394

357395
@validate_call
358-
def create_archive(self, options: CreateArchiveRequest) -> Archive:
359-
pass
396+
def start_archive(self, options: CreateArchiveRequest) -> Archive:
397+
"""Starts an archive in a Vonage Video API session.
398+
399+
Args:
400+
options (CreateArchiveRequest): The options for the archive.
401+
402+
Returns:
403+
Archive: The archive object.
404+
"""
405+
response = self._http_client.post(
406+
self._http_client.video_host,
407+
f'/v2/project/{self._http_client.auth.application_id}/archive',
408+
options.model_dump(exclude_none=True, by_alias=True),
409+
)
410+
411+
return Archive(**response)
412+
413+
@validate_call
414+
def get_archive(self, archive_id: str) -> Archive:
415+
"""Gets an archive from the Vonage Video API.
416+
417+
Args:
418+
archive_id (str): The archive ID.
419+
420+
Returns:
421+
Archive: The archive object.
422+
"""
423+
response = self._http_client.get(
424+
self._http_client.video_host,
425+
f'/v2/project/{self._http_client.auth.application_id}/archive/{archive_id}',
426+
)
427+
428+
return Archive(**response)
429+
430+
@validate_call
431+
def delete_archive(self, archive_id: str) -> None:
432+
"""Deletes an archive from the Vonage Video API.
433+
434+
Args:
435+
archive_id (str): The archive ID.
436+
437+
Raises:
438+
InvalidArchiveStateError: If the archive has a status other than `available`, `uploaded`, or `deleted`.
439+
"""
440+
try:
441+
self._http_client.delete(
442+
self._http_client.video_host,
443+
f'/v2/project/{self._http_client.auth.application_id}/archive/{archive_id}',
444+
)
445+
except HttpRequestError as e:
446+
if e.response.status_code == 409:
447+
raise InvalidArchiveStateError(
448+
'You can only delete an archive that has one of the following statuses: `available` OR `uploaded` OR `deleted`.'
449+
)
450+
raise e
451+
452+
@validate_call
453+
def add_stream_to_archive(self, archive_id: str, params: AddStreamRequest) -> None:
454+
"""Adds a stream to an archive in the Vonage Video API. Use this method to change the
455+
streams included in a composed archive that was started with the streamMode set to "manual".
456+
457+
Args:
458+
archive_id (str): The archive ID.
459+
params (AddStreamRequest): Params for adding a stream to an archive.
460+
"""
461+
self._http_client.patch(
462+
self._http_client.video_host,
463+
f'/v2/project/{self._http_client.auth.application_id}/archive/{archive_id}/streams',
464+
params.model_dump(exclude_none=True, by_alias=True),
465+
)
466+
467+
@validate_call
468+
def remove_stream_from_archive(self, archive_id: str, stream_id: str) -> None:
469+
"""Removes a stream from an archive in the Vonage Video API.
470+
471+
Args:
472+
archive_id (str): The archive ID.
473+
stream_id (str): ID of the stream to remove.
474+
"""
475+
self._http_client.patch(
476+
self._http_client.video_host,
477+
f'/v2/project/{self._http_client.auth.application_id}/archive/{archive_id}/streams',
478+
params={'removeStream': stream_id},
479+
)
480+
481+
@validate_call
482+
def stop_archive(self, archive_id: str) -> Archive:
483+
"""Stops a Vonage Video API archive.
484+
485+
Args:
486+
archive_id (str): The archive ID.
487+
488+
Returns:
489+
Archive: The archive object.
490+
491+
Raises:
492+
InvalidArchiveStateError: If the archive is not being recorded.
493+
"""
494+
try:
495+
response = self._http_client.post(
496+
self._http_client.video_host,
497+
f'/v2/project/{self._http_client.auth.application_id}/archive/{archive_id}/stop',
498+
)
499+
except HttpRequestError as e:
500+
if e.response.status_code == 409:
501+
raise InvalidArchiveStateError(
502+
'You can only stop an archive that is being recorded.'
503+
)
504+
raise e
505+
return Archive(**response)
506+
507+
@validate_call
508+
def change_archive_layout(self, archive_id: str, layout: ComposedLayout) -> Archive:
509+
"""Changes the layout of an archive in the Vonage Video API.
510+
511+
Args:
512+
archive_id (str): The archive ID.
513+
layout (ComposedLayout): The layout to change to.
514+
515+
Returns:
516+
Archive: The archive object.
517+
"""
518+
response = self._http_client.put(
519+
self._http_client.video_host,
520+
f'/v2/project/{self._http_client.auth.application_id}/archive/{archive_id}/layout',
521+
layout.model_dump(exclude_none=True, by_alias=True),
522+
)
523+
524+
return Archive(**response)

video/tests/data/archive.json

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"id": "5b1521e6-115f-4efd-bed9-e527b87f0699",
3+
"status": "started",
4+
"name": "first archive test",
5+
"reason": "",
6+
"sessionId": "test_session_id",
7+
"applicationId": "test_application_id",
8+
"createdAt": 1727870434974,
9+
"size": 0,
10+
"duration": 0,
11+
"outputMode": "composed",
12+
"streamMode": "manual",
13+
"hasAudio": true,
14+
"hasVideo": true,
15+
"hasTranscription": false,
16+
"sha256sum": "",
17+
"password": "",
18+
"updatedAt": 1727870434977,
19+
"multiArchiveTag": "my-multi-archive",
20+
"event": "archive",
21+
"resolution": "1280x720",
22+
"url": null
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"code": 15004,
3+
"message": "You can only delete an archive that has one of the following statuses: available OR uploaded OR deleted",
4+
"description": "You can only delete an archive that has one of the following statuses: available OR uploaded OR deleted"
5+
}

0 commit comments

Comments
 (0)