Skip to content

Commit 2cf018f

Browse files
feat: Add quantization parameter (#257)
* Added quantizationParameter to acrhives * Fixed bumpconfig * Bump version: 3.12.0 → 3.13.0
1 parent 723e3a0 commit 2cf018f

File tree

7 files changed

+315
-2
lines changed

7 files changed

+315
-2
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 3.11.1
2+
current_version = 3.13.0
33
commit = True
44
tag = True
55

.requirements.txt

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
astroid==3.3.10
2+
black==25.1.0
3+
bump2version==1.0.1
4+
certifi==2025.4.26
5+
cffi==1.17.1
6+
charset-normalizer==3.4.2
7+
click==8.2.1
8+
coverage==7.9.0
9+
cryptography==45.0.4
10+
dill==0.4.0
11+
docutils==0.21.2
12+
expects==0.9.0
13+
httpretty==1.1.4
14+
id==1.5.0
15+
idna==3.10
16+
iniconfig==2.1.0
17+
isort==6.0.1
18+
jaraco.classes==3.4.0
19+
jaraco.context==6.0.1
20+
jaraco.functools==4.1.0
21+
keyring==25.6.0
22+
markdown-it-py==3.0.0
23+
mccabe==0.7.0
24+
mdurl==0.1.2
25+
mock==5.2.0
26+
more-itertools==10.7.0
27+
mypy_extensions==1.1.0
28+
nh3==0.2.21
29+
-e git+ssh://[email protected]/opentok/Opentok-Python-SDK.git@1ad6e1763b04f4897c1bd1a77ff884cf85e63caf#egg=opentok
30+
packaging==25.0
31+
pathspec==0.12.1
32+
platformdirs==4.3.8
33+
pluggy==1.6.0
34+
pyasn1==0.6.1
35+
pycparser==2.22
36+
Pygments==2.19.1
37+
PyJWT==2.10.1
38+
pylint==3.3.7
39+
pytest==8.4.0
40+
pytest-cov==6.2.1
41+
pytz==2025.2
42+
readme_renderer==44.0
43+
requests==2.32.4
44+
requests-toolbelt==1.0.0
45+
rfc3986==2.0.0
46+
rich==14.0.0
47+
rsa==4.9.1
48+
setuptools==80.9.0
49+
six==1.17.0
50+
sure==2.0.1
51+
tomlkit==0.13.3
52+
twine==6.1.0
53+
urllib3==2.4.0
54+
wheel==0.45.1

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Release 3.12.0
2+
- Add new `quantization_parameter` option for archives to control video encoding quality. Values between 15-40, where smaller values generate higher quality and larger archives, larger values generate lower quality and smaller archives. QP uses variable bitrate (VBR).
3+
14
# Release 3.11.0
25
- OpenTok SDK now accepts Vonage credentials so it's possible to use the existing OpenTok SDK with the Vonage Video API
36
- Add additional headers to some requests

opentok/archives.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ class Archive(object):
112112
10 minutes. To generate a new URL, call the Archive.listArchives() or OpenTok.getArchive() method.
113113
114114
:ivar max_bitrate: The maximum video bitrate for the archive, in bits per second. The minimum value is 100,000 and the maximum is 6,000,000.
115+
116+
:ivar quantization_parameter: The quantization parameter (QP) for video encoding quality. Values between 15-40, where smaller values generate higher quality and larger archives.
115117
"""
116118

117119
def __init__(self, sdk, values):
@@ -139,6 +141,7 @@ def __init__(self, sdk, values):
139141
self.url = values.get("url")
140142
self.resolution = values.get("resolution")
141143
self.max_bitrate = values.get("maxBitrate")
144+
self.quantization_parameter = values.get("quantizationParameter")
142145

143146
def stop(self):
144147
"""

opentok/opentok.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,7 @@ def start_archive(
648648
layout=None,
649649
multi_archive_tag=None,
650650
max_bitrate=None,
651+
quantization_parameter=None,
651652
):
652653
"""
653654
Starts archiving an OpenTok session.
@@ -708,6 +709,8 @@ def start_archive(
708709
709710
:param String max_bitrate (Optional): The maximum video bitrate for the archive, in bits per second. The minimum value is 100,000 and the maximum is 6,000,000.
710711
712+
:param Number quantization_parameter (Optional): The quantization parameter (QP) for video encoding quality. Values between 15-40, where smaller values generate higher quality and larger archives, larger values generate lower quality and smaller archives. QP uses variable bitrate (VBR).
713+
711714
:rtype: The Archive object, which includes properties defining the archive,
712715
including the archive ID.
713716
"""
@@ -725,6 +728,16 @@ def start_archive(
725728
)
726729
)
727730

731+
if quantization_parameter is not None:
732+
if not isinstance(quantization_parameter, (int, float)):
733+
raise OpenTokException(
734+
u("quantization_parameter must be a number")
735+
)
736+
if quantization_parameter < 15 or quantization_parameter > 40:
737+
raise OpenTokException(
738+
u("quantization_parameter must be between 15 and 40")
739+
)
740+
728741
payload = {
729742
"name": name,
730743
"sessionId": session_id,
@@ -737,6 +750,9 @@ def start_archive(
737750
"maxBitrate": max_bitrate,
738751
}
739752

753+
if quantization_parameter is not None:
754+
payload["quantizationParameter"] = quantization_parameter
755+
740756
if layout is not None:
741757
payload["layout"] = layout
742758

opentok/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# see: http://legacy.python.org/dev/peps/pep-0440/#public-version-identifiers
2-
__version__ = "3.12.0"
2+
__version__ = "3.13.0"
33

tests/test_archive_api.py

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1552,6 +1552,243 @@ def test_start_archive_with_streammode_manual(self):
15521552
response.json().should.equal({"streamMode": "manual"})
15531553
response.headers["Content-Type"].should.equal("application/json")
15541554

1555+
@httpretty.activate
1556+
def test_start_archive_with_quantization_parameter(self):
1557+
"""Test start archive with quantization parameter"""
1558+
httpretty.register_uri(
1559+
httpretty.POST,
1560+
u("https://api.opentok.com/v2/project/{0}/archive").format(self.api_key),
1561+
body=textwrap.dedent(
1562+
u(
1563+
"""\
1564+
{
1565+
"createdAt" : 1395183243556,
1566+
"duration" : 0,
1567+
"id" : "30b3ebf1-ba36-4f5b-8def-6f70d9986fe9",
1568+
"name" : "ARCHIVE NAME",
1569+
"partnerId" : 123456,
1570+
"reason" : "",
1571+
"sessionId" : "SESSIONID",
1572+
"size" : 0,
1573+
"status" : "started",
1574+
"hasAudio": true,
1575+
"hasVideo": true,
1576+
"outputMode": "composed",
1577+
"url" : null,
1578+
"quantizationParameter": 25
1579+
}
1580+
"""
1581+
)
1582+
),
1583+
status=200,
1584+
content_type=u("application/json"),
1585+
)
1586+
1587+
archive = self.opentok.start_archive(
1588+
self.session_id, name=u("ARCHIVE NAME"), quantization_parameter=25
1589+
)
1590+
1591+
validate_jwt_header(self, httpretty.last_request().headers[u("x-opentok-auth")])
1592+
expect(httpretty.last_request().headers[u("user-agent")]).to(
1593+
contain(u("OpenTok-Python-SDK/") + __version__)
1594+
)
1595+
expect(httpretty.last_request().headers[u("content-type")]).to(
1596+
equal(u("application/json"))
1597+
)
1598+
# non-deterministic json encoding. have to decode to test it properly
1599+
if PY2:
1600+
body = json.loads(httpretty.last_request().body)
1601+
if PY3:
1602+
body = json.loads(httpretty.last_request().body.decode("utf-8"))
1603+
expect(body).to(have_key(u("sessionId"), u("SESSIONID")))
1604+
expect(body).to(have_key(u("name"), u("ARCHIVE NAME")))
1605+
expect(body).to(have_key(u("quantizationParameter"), 25))
1606+
expect(archive).to(be_an(Archive))
1607+
expect(archive).to(
1608+
have_property(u("id"), u("30b3ebf1-ba36-4f5b-8def-6f70d9986fe9"))
1609+
)
1610+
expect(archive).to(have_property(u("name"), ("ARCHIVE NAME")))
1611+
expect(archive).to(have_property(u("quantization_parameter"), 25))
1612+
1613+
def test_start_archive_with_invalid_quantization_parameter_type(self):
1614+
"""Test start archive with invalid quantization parameter type"""
1615+
with pytest.raises(OpenTokException) as excinfo:
1616+
self.opentok.start_archive(
1617+
self.session_id, quantization_parameter="invalid"
1618+
)
1619+
expect(str(excinfo.value)).to(contain("quantization_parameter must be a number"))
1620+
1621+
def test_start_archive_with_quantization_parameter_too_low(self):
1622+
"""Test start archive with quantization parameter below minimum"""
1623+
with pytest.raises(OpenTokException) as excinfo:
1624+
self.opentok.start_archive(
1625+
self.session_id, quantization_parameter=14
1626+
)
1627+
expect(str(excinfo.value)).to(contain("quantization_parameter must be between 15 and 40"))
1628+
1629+
def test_start_archive_with_quantization_parameter_too_high(self):
1630+
"""Test start archive with quantization parameter above maximum"""
1631+
with pytest.raises(OpenTokException) as excinfo:
1632+
self.opentok.start_archive(
1633+
self.session_id, quantization_parameter=41
1634+
)
1635+
expect(str(excinfo.value)).to(contain("quantization_parameter must be between 15 and 40"))
1636+
1637+
@httpretty.activate
1638+
def test_start_archive_with_quantization_parameter_boundary_values(self):
1639+
"""Test start archive with quantization parameter boundary values"""
1640+
httpretty.register_uri(
1641+
httpretty.POST,
1642+
u("https://api.opentok.com/v2/project/{0}/archive").format(self.api_key),
1643+
body=textwrap.dedent(
1644+
u(
1645+
"""\
1646+
{
1647+
"createdAt" : 1395183243556,
1648+
"duration" : 0,
1649+
"id" : "30b3ebf1-ba36-4f5b-8def-6f70d9986fe9",
1650+
"name" : "",
1651+
"partnerId" : 123456,
1652+
"reason" : "",
1653+
"sessionId" : "SESSIONID",
1654+
"size" : 0,
1655+
"status" : "started",
1656+
"hasAudio": true,
1657+
"hasVideo": true,
1658+
"outputMode": "composed",
1659+
"url" : null,
1660+
"quantizationParameter": 15
1661+
}
1662+
"""
1663+
)
1664+
),
1665+
status=200,
1666+
content_type=u("application/json"),
1667+
)
1668+
1669+
# Test minimum value (15)
1670+
archive = self.opentok.start_archive(self.session_id, quantization_parameter=15)
1671+
expect(archive).to(be_an(Archive))
1672+
expect(archive).to(have_property(u("quantization_parameter"), 15))
1673+
1674+
# Test maximum value (40)
1675+
httpretty.reset()
1676+
httpretty.register_uri(
1677+
httpretty.POST,
1678+
u("https://api.opentok.com/v2/project/{0}/archive").format(self.api_key),
1679+
body=textwrap.dedent(
1680+
u(
1681+
"""\
1682+
{
1683+
"createdAt" : 1395183243556,
1684+
"duration" : 0,
1685+
"id" : "30b3ebf1-ba36-4f5b-8def-6f70d9986fe9",
1686+
"name" : "",
1687+
"partnerId" : 123456,
1688+
"reason" : "",
1689+
"sessionId" : "SESSIONID",
1690+
"size" : 0,
1691+
"status" : "started",
1692+
"hasAudio": true,
1693+
"hasVideo": true,
1694+
"outputMode": "composed",
1695+
"url" : null,
1696+
"quantizationParameter": 40
1697+
}
1698+
"""
1699+
)
1700+
),
1701+
status=200,
1702+
content_type=u("application/json"),
1703+
)
1704+
1705+
archive = self.opentok.start_archive(self.session_id, quantization_parameter=40)
1706+
expect(archive).to(be_an(Archive))
1707+
expect(archive).to(have_property(u("quantization_parameter"), 40))
1708+
1709+
@httpretty.activate
1710+
def test_start_archive_with_float_quantization_parameter(self):
1711+
"""Test start archive with float quantization parameter"""
1712+
httpretty.register_uri(
1713+
httpretty.POST,
1714+
u("https://api.opentok.com/v2/project/{0}/archive").format(self.api_key),
1715+
body=textwrap.dedent(
1716+
u(
1717+
"""\
1718+
{
1719+
"createdAt" : 1395183243556,
1720+
"duration" : 0,
1721+
"id" : "30b3ebf1-ba36-4f5b-8def-6f70d9986fe9",
1722+
"name" : "",
1723+
"partnerId" : 123456,
1724+
"reason" : "",
1725+
"sessionId" : "SESSIONID",
1726+
"size" : 0,
1727+
"status" : "started",
1728+
"hasAudio": true,
1729+
"hasVideo": true,
1730+
"outputMode": "composed",
1731+
"url" : null,
1732+
"quantizationParameter": 25.5
1733+
}
1734+
"""
1735+
)
1736+
),
1737+
status=200,
1738+
content_type=u("application/json"),
1739+
)
1740+
1741+
archive = self.opentok.start_archive(self.session_id, quantization_parameter=25.5)
1742+
1743+
if PY2:
1744+
body = json.loads(httpretty.last_request().body)
1745+
if PY3:
1746+
body = json.loads(httpretty.last_request().body.decode("utf-8"))
1747+
expect(body).to(have_key(u("quantizationParameter"), 25.5))
1748+
expect(archive).to(be_an(Archive))
1749+
expect(archive).to(have_property(u("quantization_parameter"), 25.5))
1750+
1751+
@httpretty.activate
1752+
def test_start_archive_without_quantization_parameter(self):
1753+
"""Test start archive without quantization parameter (should not include in payload)"""
1754+
httpretty.register_uri(
1755+
httpretty.POST,
1756+
u("https://api.opentok.com/v2/project/{0}/archive").format(self.api_key),
1757+
body=textwrap.dedent(
1758+
u(
1759+
"""\
1760+
{
1761+
"createdAt" : 1395183243556,
1762+
"duration" : 0,
1763+
"id" : "30b3ebf1-ba36-4f5b-8def-6f70d9986fe9",
1764+
"name" : "",
1765+
"partnerId" : 123456,
1766+
"reason" : "",
1767+
"sessionId" : "SESSIONID",
1768+
"size" : 0,
1769+
"status" : "started",
1770+
"hasAudio": true,
1771+
"hasVideo": true,
1772+
"outputMode": "composed",
1773+
"url" : null
1774+
}
1775+
"""
1776+
)
1777+
),
1778+
status=200,
1779+
content_type=u("application/json"),
1780+
)
1781+
1782+
archive = self.opentok.start_archive(self.session_id)
1783+
1784+
if PY2:
1785+
body = json.loads(httpretty.last_request().body)
1786+
if PY3:
1787+
body = json.loads(httpretty.last_request().body.decode("utf-8"))
1788+
expect(body).to_not(have_key(u("quantizationParameter")))
1789+
expect(archive).to(be_an(Archive))
1790+
expect(archive).to(have_property(u("quantization_parameter"), None))
1791+
15551792
@httpretty.activate
15561793
def test_set_archive_layout_throws_exception(self):
15571794
"""Test invalid request in set archive layout"""

0 commit comments

Comments
 (0)