Skip to content
This repository was archived by the owner on Nov 20, 2024. It is now read-only.

Commit b3321ff

Browse files
osodem4reko
andauthored
Feature: Getters for scenevideo and gazedata urls (#64)
* Add getters for scenevideo and gazedata urls * Add cSpell words * Add tests for scenevideo and gazedata url getters * Add aiohttp requirement * Add aiohttp dependency in pyproject.toml * Remove comments * Improve error handling * Move InvalidResponseError to g3pylib folder * Change context encapsulation Co-authored-by: Markus Wesslén <mw2013@tobii.com>
1 parent 0142d08 commit b3321ff

8 files changed

Lines changed: 66 additions & 12 deletions

File tree

.vscode/settings.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,29 @@
55
"python.languageServer": "Pylance",
66
"python.sortImports.path": "isort",
77
"cSpell.words": [
8+
"aiohttp",
89
"asyncio",
910
"autouse",
1011
"coro",
1112
"Docstrings",
1213
"dotenv",
14+
"gazedata",
1315
"isort",
1416
"keepalive",
17+
"pylib",
1518
"pyright",
1619
"pytest",
1720
"PYTHONDEVMODE",
1821
"rtsp",
22+
"scenecamera",
23+
"scenevideo",
1924
"setuptools",
2025
"subprotocol",
2126
"subprotocols",
2227
"tobii",
2328
"unsubscription",
2429
"websockets",
2530
"zeroconf"
26-
],
31+
],
2732
"cSpell.language": "en,sv-SE",
2833
}

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ dependencies = [
2222
"websockets ~= 10.3",
2323
"zeroconf ~= 0.38.7",
2424
"aiortsp @ git+https://github.com/m4reko/aiortsp@master",
25-
"av ~= 9.2.0"
25+
"av ~= 9.2.0",
26+
"aiohttp ~= 3.8.1"
2627
]
2728
dynamic = ["version", "description"]
2829

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ websockets ~= 10.3
22
zeroconf ~= 0.38.7
33
aiortsp @ git+https://github.com/m4reko/aiortsp@master
44
av ~= 9.2.0
5+
aiohttp ~= 3.8.1

src/g3pylib/exceptions.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class InvalidResponseError(Exception):
2+
"""Raised when the server responds with an invalid message."""

src/g3pylib/recordings/recording.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
import json
2+
import logging
13
from datetime import datetime, timedelta
24
from typing import List, Optional, cast
35

6+
import aiohttp
7+
48
from g3pylib._utils import APIComponent, EndpointKind
9+
from g3pylib.exceptions import InvalidResponseError
510
from g3pylib.g3typing import URI
611
from g3pylib.websocket import G3WebSocketClientProtocol
712

@@ -12,6 +17,7 @@ def __init__(
1217
):
1318
self._connection = connection
1419
self._uuid = uuid
20+
self.logger: logging.Logger = logging.getLogger(__name__)
1521
super().__init__(URI(f"{api_base_uri}/{uuid}"))
1622

1723
async def get_created(self) -> datetime:
@@ -159,3 +165,35 @@ async def move(self, folder: str) -> bool:
159165
def uuid(self) -> str:
160166
"""The uuid of the recording."""
161167
return self._uuid
168+
169+
async def get_scenevideo_url(self) -> str:
170+
"""Returns a URL to the recording's video file."""
171+
host_address = self._connection.remote_address[0]
172+
data_url = f"http://{host_address}{await self.get_http_path()}"
173+
async with aiohttp.ClientSession() as session:
174+
async with session.get(data_url) as response:
175+
data = json.loads(await response.text())
176+
try:
177+
scenevideo_file_name = data["scenecamera"]["file"]
178+
except KeyError:
179+
self.logger.warning(
180+
f"Could not retrieve file name for recording from recording data collected from {data_url}."
181+
)
182+
raise InvalidResponseError
183+
return f"{data_url}/{scenevideo_file_name}"
184+
185+
async def get_gazedata_url(self) -> str:
186+
"""Returns a URL to the recording's decompressed gaze data file."""
187+
host_address = self._connection.remote_address[0]
188+
data_url = f"http://{host_address}{await self.get_http_path()}"
189+
async with aiohttp.ClientSession() as session:
190+
async with session.get(data_url) as response:
191+
data = json.loads(await response.text())
192+
try:
193+
gaze_file_name = data["gaze"]["file"]
194+
except KeyError:
195+
self.logger.warning(
196+
f"Could not retrieve file name for gaze data from recording data collected from {data_url}."
197+
)
198+
raise InvalidResponseError
199+
return f"{data_url}/{gaze_file_name}?use-content-encoding=true"

src/g3pylib/websocket/__init__.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from websockets.typing import Subprotocol
1515

1616
from g3pylib import _utils
17+
from g3pylib.exceptions import InvalidResponseError
1718
from g3pylib.g3typing import (
1819
URI,
1920
JSONDict,
@@ -23,12 +24,7 @@
2324
SignalId,
2425
SubscriptionId,
2526
)
26-
from g3pylib.websocket.exceptions import (
27-
GlassesError,
28-
InvalidResponseError,
29-
SubscribeError,
30-
UnsubscribeError,
31-
)
27+
from g3pylib.websocket.exceptions import GlassesError, SubscribeError, UnsubscribeError
3228

3329

3430
def connect(ws_url: str) -> websockets.legacy.client.Connect:

src/g3pylib/websocket/exceptions.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,6 @@ class UnsubscribeError(Exception):
66
"""Raised when unsubscribing to a signal is unsuccessful."""
77

88

9-
class InvalidResponseError(Exception):
10-
"""Raised when the server responds with an invalid message."""
11-
12-
139
class GlassesError(Exception):
1410
"""Raised when the glasses responds with an error websocket message."""
1511

tests/api_components/test_recording.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from datetime import datetime, timedelta
22
from typing import cast
33

4+
import aiohttp
45
import pytest
56

67
from g3pylib import Glasses3
@@ -96,3 +97,17 @@ async def test_meta_data(recording: Recording):
9697
assert meta_keys == ["RuVersion", "HuSerial", "RuSerial", "key1"]
9798
non_existing_message = await recording.meta_lookup("key3")
9899
assert non_existing_message == None
100+
101+
102+
async def test_get_scenevideo_url(recording: Recording):
103+
url = await recording.get_scenevideo_url()
104+
async with aiohttp.ClientSession() as session:
105+
async with session.get(url) as response:
106+
assert response.status == 200
107+
108+
109+
async def test_get_gazedata_url(recording: Recording):
110+
url = await recording.get_gazedata_url()
111+
async with aiohttp.ClientSession() as session:
112+
async with session.get(url) as response:
113+
assert response.status == 200

0 commit comments

Comments
 (0)