Skip to content
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
2 changes: 1 addition & 1 deletion video/src/vonage_video/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.4.0'
__version__ = '1.5.0'
21 changes: 20 additions & 1 deletion video/src/vonage_video/models/archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ class Archive(BaseModel):
transcription (Transcription, Optional): Transcription options for the archive.
max_bitrate (int, Optional): The maximum video bitrate of the archive, in bits per
second. This is only valid for composed archives.
quantization_parameter (int, Optional): Quantization parameter (QP) for video encoding,
smaller values generate higher quality and larger archives, larger values generate
lower quality and smaller archives. Range: 15-40. Only valid for composed archives.
"""

id: Optional[str] = None
Expand All @@ -97,6 +100,9 @@ class Archive(BaseModel):
url: Optional[str] = None
transcription: Optional[Transcription] = None
max_bitrate: Optional[int] = Field(None, validation_alias='maxBitrate')
quantization_parameter: Optional[int] = Field(
None, validation_alias='quantizationParameter'
)


class CreateArchiveRequest(BaseModel):
Expand All @@ -119,9 +125,12 @@ class CreateArchiveRequest(BaseModel):
automatically ("auto", the default) or manually ("manual").
max_bitrate (int, Optional): The maximum video bitrate of the archive, in bits per
second. This is only valid for composed archives.
quantization_parameter (int, Optional): Quantization parameter (QP) for video encoding,
smaller values generate higher quality and larger archives, larger values generate
lower quality and smaller archives. Range: 15-40. Only valid for composed archives.
Raises:
NoAudioOrVideoError: If neither `has_audio` nor `has_video` is set.
IndividualArchivePropertyError: If `resolution` or `layout` is set for individual archives
IndividualArchivePropertyError: If `resolution`, `layout`, or `quantization_parameter` is set for individual archives
or if `has_transcription` is set for composed archives.
"""

Expand All @@ -140,6 +149,9 @@ class CreateArchiveRequest(BaseModel):
max_bitrate: Optional[int] = Field(
None, ge=100_000, le=6_000_000, serialization_alias='maxBitrate'
)
quantization_parameter: Optional[int] = Field(
None, ge=15, le=40, serialization_alias='quantizationParameter'
)

@model_validator(mode='after')
def validate_audio_or_video(self):
Expand All @@ -159,6 +171,13 @@ def no_layout_or_resolution_for_individual_archives(self):
raise IndividualArchivePropertyError(
'The `layout` property cannot be set for `archive_mode: \'individual\'`.'
)
if (
self.output_mode == OutputMode.INDIVIDUAL
and self.quantization_parameter is not None
):
raise IndividualArchivePropertyError(
'The `quantization_parameter` property cannot be set for `archive_mode: \'individual\'`.'
)
return self

@model_validator(mode='after')
Expand Down
3 changes: 2 additions & 1 deletion video/tests/data/archive.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@
"event": "archive",
"resolution": "1280x720",
"url": null,
"maxBitrate": 2000000
"maxBitrate": 2000000,
"quantizationParameter": 25
}
83 changes: 83 additions & 0 deletions video/tests/test_archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,87 @@ def test_create_archive_request_composed_output_mode_with_transcription_error():
)


def test_create_archive_request_valid_quantization_parameter():
"""Test that quantization_parameter is accepted for composed archives with valid
values."""
request = CreateArchiveRequest(
session_id="1_MX40NTY3NjYzMn5-MTQ4MTY3NjYzMn5",
has_audio=True,
has_video=True,
output_mode=OutputMode.COMPOSED,
quantization_parameter=25,
)
assert request.quantization_parameter == 25


def test_create_archive_request_quantization_parameter_boundary_values():
"""Test that quantization_parameter accepts boundary values (15 and 40)."""
# Test minimum value
request_min = CreateArchiveRequest(
session_id="1_MX40NTY3NjYzMn5-MTQ4MTY3NjYzMn5",
has_audio=True,
quantization_parameter=15,
)
assert request_min.quantization_parameter == 15

# Test maximum value
request_max = CreateArchiveRequest(
session_id="1_MX40NTY3NjYzMn5-MTQ4MTY3NjYzMn5",
has_audio=True,
quantization_parameter=40,
)
assert request_max.quantization_parameter == 40


def test_create_archive_request_quantization_parameter_invalid_low():
"""Test that quantization_parameter rejects values below 15."""
with raises(ValueError):
CreateArchiveRequest(
session_id="1_MX40NTY3NjYzMn5-MTQ4MTY3NjYzMn5",
has_audio=True,
quantization_parameter=14,
)


def test_create_archive_request_quantization_parameter_invalid_high():
"""Test that quantization_parameter rejects values above 40."""
with raises(ValueError):
CreateArchiveRequest(
session_id="1_MX40NTY3NjYzMn5-MTQ4MTY3NjYzMn5",
has_audio=True,
quantization_parameter=41,
)


def test_create_archive_request_individual_output_mode_with_quantization_parameter():
"""Test that quantization_parameter is rejected for individual archives."""
with raises(IndividualArchivePropertyError):
CreateArchiveRequest(
session_id="1_MX40NTY3NjYzMn5-MTQ4MTY3NjYzMn5",
has_audio=True,
output_mode=OutputMode.INDIVIDUAL,
quantization_parameter=25,
)


def test_create_archive_request_serialization_with_quantization_parameter():
"""Test that quantization_parameter is properly serialized with the correct alias."""
request = CreateArchiveRequest(
session_id="1_MX40NTY3NjYzMn5-MTQ4MTY3NjYzMn5",
has_audio=True,
has_video=True,
output_mode=OutputMode.COMPOSED,
quantization_parameter=30,
)

serialized = request.model_dump(by_alias=True, exclude_unset=True)
assert 'quantizationParameter' in serialized
assert serialized['quantizationParameter'] == 30
assert (
'quantization_parameter' not in serialized
) # Ensure Python field name is not used


def test_layout_custom_without_stylesheet():
with raises(LayoutStylesheetError):
ComposedLayout(type=LayoutType.CUSTOM)
Expand Down Expand Up @@ -194,6 +275,7 @@ def test_start_archive():
assert archive.name == 'first archive test'
assert archive.resolution == '1280x720'
assert archive.max_bitrate == 2_000_000
assert archive.quantization_parameter == 25


@responses.activate
Expand All @@ -215,6 +297,7 @@ def test_get_archive():
assert archive.status == 'started'
assert archive.name == 'first archive test'
assert archive.resolution == '1280x720'
assert archive.quantization_parameter == 25


@responses.activate
Expand Down