Skip to content

Commit f97c289

Browse files
Add preload endpoint (#203)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
1 parent 1178e90 commit f97c289

File tree

6 files changed

+239
-5
lines changed

6 files changed

+239
-5
lines changed

go2rtc_client/models.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,10 @@ class WebRTCSdpAnswer(WebRTCSdp):
8181
"""WebRTC SDP answer model."""
8282

8383
type: Literal["answer"] = field(default="answer", init=False)
84+
85+
86+
@dataclass
87+
class Preload(DataClassORJSONMixin):
88+
"""Preload model."""
89+
90+
query: str

go2rtc_client/rest.py

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@
1414
from yarl import URL
1515

1616
from .exceptions import Go2RtcVersionError, handle_error
17-
from .models import ApplicationInfo, Stream, WebRTCSdpAnswer, WebRTCSdpOffer
17+
from .models import ApplicationInfo, Preload, Stream, WebRTCSdpAnswer, WebRTCSdpOffer
1818

1919
if TYPE_CHECKING:
2020
from collections.abc import Mapping
2121

2222
_LOGGER = logging.getLogger(__name__)
2323

2424
_API_PREFIX = "/api"
25-
_MIN_VERSION_SUPPORTED: Final = AwesomeVersion("1.9.12")
25+
_MIN_VERSION_SUPPORTED: Final = AwesomeVersion("1.9.13")
2626
_MIN_VERSION_UNSUPPORTED: Final = AwesomeVersion("2.0.0")
2727

2828

@@ -42,7 +42,7 @@ def __init__(self, websession: ClientSession, server_url: str) -> None:
4242

4343
async def request(
4444
self,
45-
method: Literal["GET", "PUT", "POST"],
45+
method: Literal["GET", "PUT", "POST", "DELETE"],
4646
path: str,
4747
*,
4848
params: Mapping[str, Any] | None = None,
@@ -156,13 +156,61 @@ async def list(self) -> set[str]:
156156
return self._DECODER.decode(await resp.json())
157157

158158

159+
class _PreloadClient:
160+
PATH: Final = f"{_API_PREFIX}/preload"
161+
_DECODER = BasicDecoder(dict[str, Preload])
162+
163+
def __init__(self, client: _BaseClient) -> None:
164+
"""Initialize Client."""
165+
self._client = client
166+
167+
@handle_error
168+
async def enable(
169+
self,
170+
source: str,
171+
*,
172+
video_codec_filter: list[str] | None = None,
173+
audio_codec_filter: list[str] | None = None,
174+
microphone_codec_filter: list[str] | None = None,
175+
) -> None:
176+
"""Enable preload for a stream."""
177+
params = {"src": source}
178+
if video_codec_filter:
179+
params["video_codec_filter"] = ",".join(video_codec_filter)
180+
if audio_codec_filter:
181+
params["audio_codec_filter"] = ",".join(audio_codec_filter)
182+
if microphone_codec_filter:
183+
params["microphone_codec_filter"] = ",".join(microphone_codec_filter)
184+
await self._client.request(
185+
"PUT",
186+
self.PATH,
187+
params=params,
188+
)
189+
190+
@handle_error
191+
async def disable(self, source: str) -> None:
192+
"""Disable preload for a stream."""
193+
await self._client.request(
194+
"DELETE",
195+
self.PATH,
196+
params={"src": source},
197+
)
198+
199+
@handle_error
200+
async def list(self) -> dict[str, Preload]:
201+
"""List all preloaded streams."""
202+
resp = await self._client.request("GET", self.PATH)
203+
return self._DECODER.decode(await resp.json())
204+
205+
159206
class Go2RtcRestClient:
160207
"""Rest client for go2rtc server."""
161208

162209
def __init__(self, websession: ClientSession, server_url: str) -> None:
163210
"""Initialize Client."""
164211
self._client = _BaseClient(websession, server_url)
165212
self.application: Final = _ApplicationClient(self._client)
213+
self.preload: Final = _PreloadClient(self._client)
166214
self.schemes: Final = _SchemesClient(self._client)
167215
self.streams: Final = _StreamClient(self._client)
168216
self.webrtc: Final = _WebRTCClient(self._client)

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ disable = [
140140
"too-few-public-methods",
141141
"too-many-instance-attributes",
142142
"too-many-arguments",
143+
"too-many-positional-arguments",
143144
"too-many-public-methods",
144145
"wrong-import-order",
145146
]

tests/__snapshots__/test_rest.ambr

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@
99
'version': '1.9.4',
1010
})
1111
# ---
12+
# name: test_preload_list[no stream preloaded]
13+
dict({
14+
})
15+
# ---
16+
# name: test_preload_list[one stream preloaded]
17+
dict({
18+
'camera.haus_eingang_fluent_2': dict({
19+
'query': 'video&audio',
20+
}),
21+
})
22+
# ---
1223
# name: test_schemes
1324
set({
1425
'exec',
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"camera.haus_eingang_fluent_2": {
3+
"consumer": {
4+
"id": 1,
5+
"format_name": "preload",
6+
"medias": ["video, sendonly, ANY", "audio, sendonly, ANY"],
7+
"senders": [
8+
{
9+
"id": 4,
10+
"codec": {
11+
"codec_name": "h264",
12+
"codec_type": "video"
13+
},
14+
"parent": 3,
15+
"bytes": 18828335,
16+
"packets": 19268
17+
},
18+
{
19+
"id": 6,
20+
"codec": {
21+
"codec_name": "aac",
22+
"codec_type": "audio",
23+
"sample_rate": 16000
24+
},
25+
"parent": 5,
26+
"bytes": 1890224,
27+
"packets": 7471
28+
}
29+
],
30+
"bytes_send": 20702970
31+
},
32+
"query": "video&audio"
33+
}
34+
}

tests/test_rest.py

Lines changed: 135 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from go2rtc_client.rest import (
1717
_API_PREFIX,
1818
_ApplicationClient,
19+
_PreloadClient,
1920
_SchemesClient,
2021
_StreamClient,
2122
_WebRTCClient,
@@ -135,7 +136,7 @@ async def test_streams_add_str(
135136
)
136137

137138

138-
VERSION_ERR = "server version '{}' not >= 1.9.12 and < 2.0.0"
139+
VERSION_ERR = "server version '{}' not >= 1.9.13 and < 2.0.0"
139140

140141

141142
@pytest.mark.parametrize(
@@ -144,8 +145,8 @@ async def test_streams_add_str(
144145
("0.0.0", pytest.raises(Go2RtcVersionError, match=VERSION_ERR.format("0.0.0"))),
145146
("1.9.5", pytest.raises(Go2RtcVersionError, match=VERSION_ERR.format("1.9.5"))),
146147
("1.9.6", pytest.raises(Go2RtcVersionError, match=VERSION_ERR.format("1.9.6"))),
147-
("1.9.12", does_not_raise()),
148148
("1.9.13", does_not_raise()),
149+
("1.9.14", does_not_raise()),
149150
("2.0.0", pytest.raises(Go2RtcVersionError, match=VERSION_ERR.format("2.0.0"))),
150151
("BLAH", pytest.raises(Go2RtcVersionError, match=VERSION_ERR.format("BLAH"))),
151152
],
@@ -237,3 +238,135 @@ async def test_schemes(
237238
)
238239
resp = await rest_client.schemes.list()
239240
assert resp == snapshot
241+
242+
243+
@pytest.mark.parametrize(
244+
"filename",
245+
["preload_list_one.json", "streams_none.json"],
246+
ids=[
247+
"one stream preloaded",
248+
"no stream preloaded",
249+
],
250+
)
251+
async def test_preload_list(
252+
responses: aioresponses,
253+
rest_client: Go2RtcRestClient,
254+
snapshot: SnapshotAssertion,
255+
filename: str,
256+
) -> None:
257+
"""Test preload list."""
258+
responses.get(
259+
f"{URL}{_PreloadClient.PATH}",
260+
status=200,
261+
body=load_fixture_str(filename),
262+
)
263+
resp = await rest_client.preload.list()
264+
assert resp == snapshot
265+
266+
267+
async def test_preload_enable_no_filters(
268+
responses: aioresponses,
269+
rest_client: Go2RtcRestClient,
270+
) -> None:
271+
"""Test enable preload without codec filters."""
272+
url = f"{URL}{_PreloadClient.PATH}"
273+
camera = "camera.12mp_fluent"
274+
params = {"src": camera}
275+
responses.put(url + f"?src={camera}", status=200)
276+
await rest_client.preload.enable(camera)
277+
278+
responses.assert_called_once_with(
279+
url, method=METH_PUT, params=params, timeout=ClientTimeout(total=10)
280+
)
281+
282+
283+
@pytest.mark.parametrize(
284+
(
285+
"video_codecs",
286+
"audio_codecs",
287+
"microphone_codecs",
288+
"query_string",
289+
"expected_params",
290+
),
291+
[
292+
(
293+
["h264"],
294+
None,
295+
None,
296+
"?src=camera.12mp_fluent&video_codec_filter=h264",
297+
{"src": "camera.12mp_fluent", "video_codec_filter": "h264"},
298+
),
299+
(
300+
None,
301+
["opus"],
302+
None,
303+
"?src=camera.12mp_fluent&audio_codec_filter=opus",
304+
{"src": "camera.12mp_fluent", "audio_codec_filter": "opus"},
305+
),
306+
(
307+
None,
308+
None,
309+
["pcmu"],
310+
"?src=camera.12mp_fluent&microphone_codec_filter=pcmu",
311+
{"src": "camera.12mp_fluent", "microphone_codec_filter": "pcmu"},
312+
),
313+
(
314+
["h264", "h265"],
315+
["opus", "pcma"],
316+
["pcmu"],
317+
"?src=camera.12mp_fluent&video_codec_filter=h264%2Ch265&audio_codec_filter=opus%2Cpcma&microphone_codec_filter=pcmu",
318+
{
319+
"src": "camera.12mp_fluent",
320+
"video_codec_filter": "h264,h265",
321+
"audio_codec_filter": "opus,pcma",
322+
"microphone_codec_filter": "pcmu",
323+
},
324+
),
325+
],
326+
ids=[
327+
"video filter only",
328+
"audio filter only",
329+
"microphone filter only",
330+
"all filters",
331+
],
332+
)
333+
async def test_preload_enable_with_filters(
334+
responses: aioresponses,
335+
rest_client: Go2RtcRestClient,
336+
video_codecs: list[str] | None,
337+
audio_codecs: list[str] | None,
338+
microphone_codecs: list[str] | None,
339+
query_string: str,
340+
expected_params: dict[str, str],
341+
) -> None:
342+
"""Test enable preload with codec filters."""
343+
url = f"{URL}{_PreloadClient.PATH}"
344+
camera = "camera.12mp_fluent"
345+
346+
responses.put(url + query_string, status=200)
347+
await rest_client.preload.enable(
348+
camera,
349+
video_codec_filter=video_codecs,
350+
audio_codec_filter=audio_codecs,
351+
microphone_codec_filter=microphone_codecs,
352+
)
353+
354+
responses.assert_called_once_with(
355+
url, method=METH_PUT, params=expected_params, timeout=ClientTimeout(total=10)
356+
)
357+
358+
359+
async def test_preload_disable(
360+
responses: aioresponses,
361+
rest_client: Go2RtcRestClient,
362+
) -> None:
363+
"""Test disable preload."""
364+
url = f"{URL}{_PreloadClient.PATH}"
365+
camera = "camera.12mp_fluent"
366+
params = {"src": camera}
367+
responses.delete(url + f"?src={camera}", status=200)
368+
await rest_client.preload.disable(camera)
369+
370+
responses.assert_called_once_with(
371+
url, method="DELETE", params=params, timeout=ClientTimeout(total=10)
372+
)

0 commit comments

Comments
 (0)