Skip to content

Commit 9d9c279

Browse files
committed
listeners
1 parent cbef3aa commit 9d9c279

7 files changed

Lines changed: 125 additions & 54 deletions

File tree

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
.vscode
2-
output/
2+
recordings/
33

4-
vao-recorder.yaml
4+
python-recorder.yaml
55

66
# Byte-compiled / optimized / DLL files
77
__pycache__/

localhost-vao-recorder.py

Lines changed: 0 additions & 41 deletions
This file was deleted.

pyproject.toml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,17 @@ typer = {extras = ["all"], version = "^0.4.0"}
1616
PyInquirer = "^1.0.3"
1717
PyYAML = "^5.1"
1818
numpy = "^1.22.2"
19-
sounddevice = {version = "^0.4.4", optional = true}
19+
sounddevice = "^0.4.4"
2020
pyrealsense2 = {version = "^2.50.0", optional = true}
21-
Flask = {version = "^2.1.1", optional = true}
21+
Flask = "^2.1.1"
2222

2323
[tool.poetry.dev-dependencies]
2424
pytest = "^7.1.1"
25+
yapf = "^0.32.0"
2526

2627
[tool.poetry.extras]
2728
realsense = ["pyrealsense2"]
28-
microphone = ["sounddevice"]
29-
localhost = ["Flask"]
30-
all = ["pyrealsense2", "sounddevice", "Flask"]
29+
all = ["pyrealsense2"]
3130

3231
[build-system]
3332
requires = ["poetry>=0.12"]

recorder/__main__.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from recorder.config import DEFAULT_CONFIG_PATH, DEFAULT_OUTPUT_FOLDER
22
from recorder.device import Device, DEVICE_CLASSES
3+
from recorder.listener import Listener, LISTENER_CLASSES
34
from recorder.io import yaml_dump
45
from recorder import Recorder, Config
56

@@ -25,7 +26,6 @@
2526
DEFAULT_CONFIG_PATH,
2627
help="Path to a `yaml` config file. Run `config` command to generate one."
2728
)
28-
2929
DEVICE_CLASS_ARGUMENT = typer.Argument(
3030
None,
3131
metavar='DEVICE_CLASS',
@@ -41,6 +41,14 @@
4141
"Numerical id of the device to use. Use the show command to see the available devices for each device class."
4242
)
4343
)
44+
LISTENER_CLASS_ARGUMENT = typer.Argument(
45+
'localhost',
46+
metavar='LISTENER_CLASS',
47+
help=(
48+
"Case independent name of the listener class to use. Available "
49+
f"options: {[d for d in DEVICE_CLASSES.keys()]}."
50+
)
51+
)
4452
VERBOSE_OPTION = typer.Option(False, "--verbose", "-v", help="Verbose output.")
4553

4654

@@ -58,7 +66,7 @@ def choose(message: str, choices: list):
5866
})['choice']
5967

6068

61-
def get_device_class(name: str):
69+
def get_device_class(name: str) -> Device:
6270
try:
6371
return DEVICE_CLASSES[name.lower()]
6472
except KeyError:
@@ -68,6 +76,16 @@ def get_device_class(name: str):
6876
)
6977

7078

79+
def get_listener_class(name: str) -> Listener:
80+
try:
81+
return LISTENER_CLASSES[name.lower()]
82+
except KeyError:
83+
raise RuntimeError(
84+
f"Invalid listener class `{name}`. Available options: "
85+
f"{list(LISTENER_CLASSES.keys())}"
86+
)
87+
88+
7189
@app.command(help="Display the available devices")
7290
def show(
7391
device_class: Optional[str] = DEVICE_CLASS_ARGUMENT,
@@ -138,6 +156,7 @@ def record(
138156
seconds: int = typer.Argument(None, help="Number of seconds to record"),
139157
config_path: Optional[Path] = DEFAULT_CONFIG_PATH_OPTION,
140158
output_folder: Optional[Path] = DEFAULT_OUTPUT_FOLDER_OPTION,
159+
# TODO pass kwargs to listener. Or make subcommands for each listener
141160
):
142161
config = get_config(config_path)
143162
recorder = Recorder(config, output_folder)
@@ -146,6 +165,18 @@ def record(
146165
typer.echo(f"Recording finished, data saved to {output_folder}")
147166

148167

168+
@app.command(help='Listen to an external command to start recording')
169+
def listen(
170+
listener_class: str = LISTENER_CLASS_ARGUMENT,
171+
config_path: Optional[Path] = DEFAULT_CONFIG_PATH_OPTION,
172+
output_folder: Optional[Path] = DEFAULT_OUTPUT_FOLDER_OPTION,
173+
):
174+
config = get_config(config_path)
175+
recorder = Recorder(config, output_folder)
176+
listener = get_listener_class(listener_class)(recorder)
177+
listener.listen()
178+
179+
149180
@app.command(help="Test device recording")
150181
def test(
151182
device_class: Optional[str] = DEVICE_CLASS_ARGUMENT,
@@ -184,9 +215,6 @@ def test(
184215
recorder = Recorder(config, output_folder)
185216
output_folder = recorder(seconds=5)
186217

187-
@app.command(help='Listen to an external command to start recording')
188-
def listen(to: str):
189-
raise NotImplementedError(f"Listen to {to} is not implemented.")
190218

191219
def main():
192220
# Launch the command line application

recorder/listener/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,10 @@
1-
LISTENER_CLASSES = []
1+
from .listener import Listener
2+
3+
try:
4+
from .localhost import Localhost
5+
except ImportError as e:
6+
class Localhost:
7+
def __init__(self, *args, **kwargs) -> None:
8+
raise e
9+
10+
LISTENER_CLASSES = {repr(cls): cls for cls in Listener.__subclasses__()}

recorder/listener/listener.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from recorder import Recorder
2+
from abc import ABC, ABCMeta, abstractmethod
3+
4+
5+
class MetaListener(ABCMeta):
6+
7+
def __str__(cls) -> str:
8+
return cls.__name__
9+
10+
def __repr__(cls) -> str:
11+
return cls.__name__.lower()
12+
13+
class Listener(ABC, metaclass=MetaListener):
14+
15+
def __init__(self, recorder: Recorder) -> None:
16+
self.recorder = recorder
17+
18+
@abstractmethod
19+
def listen(self) -> None:
20+
pass

recorder/listener/localhost.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from recorder.listener import Listener
2+
3+
import socket
4+
5+
from flask import Flask, request, Response
6+
7+
# Troubleshooting:
8+
# https://stackoverflow.com/questions/62705271/connect-to-flask-server-from-other-devices-on-same-network
9+
10+
11+
class EndpointAction(object):
12+
13+
def __init__(self, action):
14+
self.action = action
15+
16+
def __call__(self, *args):
17+
# Perform the action
18+
answer = self.action()
19+
# Create the answer (bundle it in a correctly formatted HTTP answer)
20+
self.response = Response(answer, status=200, headers={})
21+
# Send it
22+
return self.response
23+
24+
25+
class Localhost(Listener):
26+
27+
def __init__(self, *args, **kwargs) -> None:
28+
super().__init__(*args, **kwargs)
29+
self.app = Flask('python-recorder')
30+
self.app.add_url_rule(
31+
'/setup', '/setup', EndpointAction(self.setup), methods=['GET']
32+
)
33+
self.app.add_url_rule(
34+
'/start', '/start', EndpointAction(self.start), methods=['GET']
35+
)
36+
self.app.add_url_rule(
37+
'/stop', '/stop', EndpointAction(self.stop), methods=['GET']
38+
)
39+
40+
def setup(self):
41+
name = request.args.get('name', None)
42+
self.recorder.setup(name=name)
43+
return str(self.recorder.next_output.name)
44+
45+
def start(self):
46+
self.recorder.start()
47+
return 'Recording started'
48+
49+
def stop(self):
50+
return str(self.recorder.stop().name)
51+
52+
def listen(self):
53+
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
54+
s.connect(("8.8.8.8", 80))
55+
host = s.getsockname()[0]
56+
self.app.run(debug=True, host=host)

0 commit comments

Comments
 (0)