1+ from recorder .settings import DEFAULT_CONFIG_PATH , DEFAULT_OUTPUT_FOLDER
2+ from recorder .device import Device
3+ from recorder .io import yaml_dump , yaml_load
4+
5+ import typer
6+
7+ from pathlib import Path
8+ from typing import Optional
9+ from PyInquirer import prompt
10+
11+ # Command line application
12+ app = typer .Typer (
13+ add_completion = False ,
14+ help = __doc__ ,
15+ )
16+
17+ DEFAULT_OUTPUT_FOLDER_OPTION = typer .Option (
18+ DEFAULT_OUTPUT_FOLDER ,
19+ help = """
20+ Path to a folder where the recorded data and the recording configuration
21+ will be stored."""
22+ )
23+ DEFAULT_CONFIG_PATH_OPTION = typer .Option (
24+ DEFAULT_CONFIG_PATH ,
25+ help = "Path to a `yaml` config file. Run `config` command to generate one."
26+ )
27+
28+ DEVICE_CLASSES = {cls .__name__ .lower (): cls for cls in Device .__subclasses__ ()}
29+ DEVICE_CLASS_ARGUMENT = typer .Argument (
30+ None ,
31+ metavar = 'DEVICE_CLASS' ,
32+ help = (
33+ "Case independent name of the device class to use. Available options: "
34+ f"{ [d for d in DEVICE_CLASSES .keys ()]} ."
35+ )
36+ )
37+ DEVICE_ID_ARGUMENT = typer .Argument (
38+ None ,
39+ metavar = 'DEVICE_ID' ,
40+ help = (
41+ "Numerical id of the device to use. Use the show command to see the available devices for each device class."
42+ )
43+ )
44+ VERBOSE_OPTION = typer .Option (False , "--verbose" , "-v" , help = "Verbose output." )
45+
46+
47+ def typer_warn (message : str ):
48+ return typer .secho (message , bg = 'black' , fg = 'yellow' )
49+
50+
51+ def choose (message : str , choices : list ):
52+ # Utility function that asks the user to configure the devices
53+ return prompt ({
54+ 'type' : 'list' ,
55+ 'name' : 'choice' ,
56+ 'message' : message ,
57+ 'choices' : choices ,
58+ })['choice' ]
59+
60+
61+ def wait (seconds : int = 4 , label : str = 'Recording...' , ** kwargs ):
62+ with typer .progressbar (range (seconds * 10 ), label = label , ** kwargs ) as p :
63+ for _ in p :
64+ sd .sleep (100 )
65+
66+
67+ @app .command (help = 'Display the available devices' )
68+ def show (
69+ device_class : Optional [str ] = DEVICE_CLASS_ARGUMENT ,
70+ verbose : bool = VERBOSE_OPTION
71+ ):
72+ if device_class :
73+ try :
74+ device_classes = [DEVICE_CLASSES [device_class .lower ()]]
75+ except KeyError :
76+ raise RuntimeError (
77+ f'Invalid device class `{ device_class } `. Available options: '
78+ f'{ list (DEVICE_CLASSES .keys ())} '
79+ )
80+ else :
81+ device_classes = DEVICE_CLASSES .values ()
82+ for cls in device_classes :
83+ devices = cls .find ()
84+ if devices :
85+ if not verbose :
86+ devices = {i : d ['name' ] for i , d in devices .items ()}
87+ typer .echo (f'{ cls } :\n ' + yaml_dump (devices ))
88+ else :
89+ typer_warn (f"Could not find { cls } devices" )
90+
91+
92+ def config_device_class (device : Device ) -> dict :
93+ choices = []
94+ devices = device .find ()
95+ if not devices :
96+ typer_warn (f"Could not find { device } devices" )
97+ return choices
98+ while typer .confirm (
99+ f"Add { 'another' if choices else 'a' } { device } device?"
100+ ):
101+ _id = choose (
102+ message = f"Select the { device } to add to the configuration:" ,
103+ choices = [{
104+ 'name' : f"{ _id } { d ['name' ]} " ,
105+ 'value' : _id
106+ } for _id , d in devices .items ()]
107+ )
108+ choices .append (devices [_id ])
109+ return {i : c for i , c in enumerate (choices )}
110+
111+
112+ @app .command (help = 'Create a configuration `yaml` file.' )
113+ def config (output : Path = DEFAULT_CONFIG_PATH_OPTION ) -> dict :
114+ config = {}
115+ for name , device_class in DEVICE_CLASSES .items ():
116+ config [name ] = config_device_class (device_class )
117+ yaml_dump (config , to_file = output )
118+ typer .echo (
119+ f"Configuration file written to { output } . Remember that it can be "
120+ "edited manually. Check the repository for an explained example file "
121+ "https://github.com/AcousticOdometry/VAO-recorder/blob/main/example-config.yaml"
122+ )
123+ return config
124+
125+
126+ def get_config (path : Path = DEFAULT_CONFIG_PATH ) -> dict :
127+ _config = None
128+ if path .exists ():
129+ _config = yaml_load (path )
130+ if not _config :
131+ typer_warn (f"No configuration found in { path } . Generate one." )
132+ _config = config (path )
133+ return _config
134+
135+
136+ def record ():
137+ pass
138+
139+
140+ @app .command (help = 'Test the available devices of a certain [device_class]' )
141+ def test (
142+ device_class : Optional [str ] = DEVICE_CLASS_ARGUMENT ,
143+ device_id : Optional [int ] = DEVICE_ID_ARGUMENT
144+ ):
145+ if not device_id :
146+ config = get_config ()
147+ if not device_class :
148+ pass
149+ else :
150+ # Both device_class and device_id are given
151+ pass
152+
153+
154+ if __name__ == '__main__' :
155+ # Launch the command line application
156+ try :
157+ app ()
158+ except RuntimeError as e :
159+ typer .secho (str (e ), fg = 'red' )
0 commit comments