recs 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- recs/__main__.py +27 -4
- recs/audio/block.py +2 -5
- recs/audio/channel_writer.py +94 -78
- recs/audio/file_opener.py +14 -15
- recs/base/_query_device.py +21 -0
- recs/base/cfg_raw.py +16 -14
- recs/base/prefix_dict.py +10 -11
- recs/base/state.py +73 -0
- recs/base/times.py +3 -62
- recs/base/type_conversions.py +5 -5
- recs/base/types.py +2 -7
- recs/cfg/__init__.py +11 -2
- recs/cfg/aliases.py +32 -21
- recs/cfg/app.py +87 -0
- recs/cfg/cfg.py +36 -60
- recs/cfg/cli.py +161 -100
- recs/cfg/device.py +37 -43
- recs/{base → cfg}/metadata.py +23 -2
- recs/cfg/path_pattern.py +156 -0
- recs/cfg/run_cli.py +43 -0
- recs/cfg/time_settings.py +61 -0
- recs/cfg/track.py +4 -4
- recs/misc/contexts.py +10 -0
- recs/misc/counter.py +9 -8
- recs/misc/legal_filename.py +1 -1
- recs/misc/log.py +43 -0
- recs/ui/__init__.py +0 -1
- recs/ui/device_process.py +37 -0
- recs/ui/device_recorder.py +66 -82
- recs/ui/device_tracks.py +9 -1
- recs/ui/full_state.py +50 -0
- recs/ui/live.py +28 -16
- recs/ui/recorder.py +52 -71
- recs/ui/table.py +1 -1
- {recs-0.2.0.dist-info → recs-0.3.0.dist-info}/METADATA +59 -17
- recs-0.3.0.dist-info/RECORD +47 -0
- {recs-0.2.0.dist-info → recs-0.3.0.dist-info}/WHEEL +1 -1
- recs/cfg/run.py +0 -32
- recs/misc/recording_path.py +0 -47
- recs/ui/channel_recorder.py +0 -51
- recs-0.2.0.dist-info/RECORD +0 -40
- /recs/{misc → cfg}/hash_cmp.py +0 -0
- {recs-0.2.0.dist-info → recs-0.3.0.dist-info}/LICENSE +0 -0
- {recs-0.2.0.dist-info → recs-0.3.0.dist-info}/entry_points.txt +0 -0
recs/ui/recorder.py
CHANGED
|
@@ -1,93 +1,74 @@
|
|
|
1
|
-
import contextlib
|
|
2
1
|
import typing as t
|
|
2
|
+
from multiprocessing import connection
|
|
3
3
|
|
|
4
|
-
from
|
|
5
|
-
from threa import HasThread, Runnable
|
|
4
|
+
from threa import HasThread, Runnables, IsThread
|
|
6
5
|
|
|
7
|
-
from recs.
|
|
8
|
-
from recs.cfg import device
|
|
6
|
+
from recs.cfg import Cfg, device
|
|
9
7
|
|
|
10
|
-
from ..cfg import Cfg
|
|
11
8
|
from . import live
|
|
12
|
-
from .
|
|
9
|
+
from .device_process import DeviceProcess
|
|
10
|
+
from .device_recorder import POLL_TIMEOUT
|
|
13
11
|
from .device_tracks import device_tracks
|
|
12
|
+
from .full_state import FullState
|
|
14
13
|
|
|
15
|
-
InputDevice = device.InputDevice
|
|
16
|
-
TableMaker = t.Callable[[], Table]
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
class Recorder(Runnable):
|
|
15
|
+
class Recorder(Runnables):
|
|
20
16
|
def __init__(self, cfg: Cfg) -> None:
|
|
21
17
|
super().__init__()
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
if not self.device_tracks:
|
|
25
|
-
raise RecsError('No devices or channels selected')
|
|
26
|
-
|
|
19
|
+
tracks = device_tracks(cfg)
|
|
27
20
|
self.cfg = cfg
|
|
28
21
|
self.live = live.Live(self.rows, cfg)
|
|
29
|
-
self.
|
|
22
|
+
self.state = FullState(tracks)
|
|
23
|
+
self.device_names = DeviceNames(cfg.sleep_time_device)
|
|
30
24
|
|
|
31
|
-
|
|
32
|
-
self.
|
|
25
|
+
processes = tuple(DeviceProcess(cfg, t) for t in tracks.values())
|
|
26
|
+
self.connections = {p.connection: p for p in processes}
|
|
27
|
+
ui_time = 1 / self.cfg.ui_refresh_rate
|
|
28
|
+
live_thread = HasThread(
|
|
29
|
+
self.live.update, looping=True, name='LiveUpdate', pre_delay=ui_time
|
|
30
|
+
)
|
|
33
31
|
|
|
34
|
-
|
|
35
|
-
d.stopped.on_set.append(self.on_stopped)
|
|
32
|
+
self.runnables = self.device_names, *processes, live_thread, self.live
|
|
36
33
|
|
|
37
|
-
|
|
34
|
+
def run_recorder(self) -> None:
|
|
35
|
+
with self:
|
|
36
|
+
while self.running:
|
|
37
|
+
self.receive()
|
|
38
38
|
|
|
39
|
-
def
|
|
40
|
-
self.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
with contextlib.ExitStack() as stack:
|
|
44
|
-
for d in self.device_recorders:
|
|
45
|
-
stack.enter_context(d)
|
|
46
|
-
stack.enter_context(self.live)
|
|
39
|
+
def finish(self) -> None:
|
|
40
|
+
for r in reversed(self.runnables):
|
|
41
|
+
r.finish()
|
|
42
|
+
self.stop()
|
|
47
43
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
def rows(self) -> t.Iterator[dict[str, t.Any]]:
|
|
45
|
+
yield from self.state.rows(self.device_names.names)
|
|
46
|
+
|
|
47
|
+
def receive(self) -> None:
|
|
48
|
+
for conn in connection.wait(list(self.connections), timeout=POLL_TIMEOUT):
|
|
49
|
+
c = t.cast(connection.Connection, conn)
|
|
50
|
+
for device_name, msg in c.recv().items():
|
|
51
|
+
if msg.get('_exit'):
|
|
52
|
+
# Not called
|
|
53
|
+
device_process = self.connections[c]
|
|
54
|
+
device_process.set_sent()
|
|
55
|
+
print('Recorder _exit', device_process.device_name)
|
|
56
|
+
self.running = False
|
|
57
|
+
else:
|
|
58
|
+
self.state.update({device_name: msg})
|
|
59
|
+
|
|
60
|
+
if (rt := self.cfg.total_run_time) and rt <= self.state.elapsed_time:
|
|
51
61
|
self.stop()
|
|
52
62
|
|
|
53
|
-
@property
|
|
54
|
-
def elapsed_time(self) -> float:
|
|
55
|
-
return times.time() - self.start_time
|
|
56
63
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
yield from v.rows()
|
|
66
|
-
|
|
67
|
-
@property
|
|
68
|
-
def file_count(self) -> int:
|
|
69
|
-
return sum(d.file_count for d in self.device_recorders)
|
|
70
|
-
|
|
71
|
-
@property
|
|
72
|
-
def file_size(self) -> int:
|
|
73
|
-
return sum(d.file_size for d in self.device_recorders)
|
|
74
|
-
|
|
75
|
-
@property
|
|
76
|
-
def recorded_time(self) -> float:
|
|
77
|
-
return sum(d.recorded_time for d in self.device_recorders)
|
|
78
|
-
|
|
79
|
-
def on_stopped(self) -> None:
|
|
80
|
-
if self.running and all(d.stopped for d in self.device_recorders):
|
|
81
|
-
self.stop()
|
|
64
|
+
class DeviceNames(IsThread):
|
|
65
|
+
daemon = True
|
|
66
|
+
looping = True
|
|
67
|
+
|
|
68
|
+
def __init__(self, pre_delay: float) -> None:
|
|
69
|
+
self.pre_delay = pre_delay
|
|
70
|
+
super().__init__()
|
|
71
|
+
self.callback()
|
|
82
72
|
|
|
83
|
-
def
|
|
84
|
-
self.
|
|
85
|
-
self.live_thread.stop()
|
|
86
|
-
for d in self.device_recorders:
|
|
87
|
-
d.stop()
|
|
88
|
-
self.stopped.set()
|
|
89
|
-
|
|
90
|
-
def _live_thread(self):
|
|
91
|
-
while self.running:
|
|
92
|
-
times.sleep(self.cfg.sleep_time)
|
|
93
|
-
self.live.update()
|
|
73
|
+
def callback(self) -> None:
|
|
74
|
+
self.names = device.input_names()
|
recs/ui/table.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: recs
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: 🎙 recs: the Universal Recorder 🎙
|
|
5
5
|
License: MIT
|
|
6
6
|
Author: Tom Ritchford
|
|
@@ -10,9 +10,10 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
14
|
Requires-Dist: coverage
|
|
14
15
|
Requires-Dist: dtyper
|
|
15
|
-
Requires-Dist: humanfriendly
|
|
16
|
+
Requires-Dist: humanfriendly
|
|
16
17
|
Requires-Dist: impall
|
|
17
18
|
Requires-Dist: numpy
|
|
18
19
|
Requires-Dist: overrides
|
|
@@ -20,9 +21,9 @@ Requires-Dist: pyaudio
|
|
|
20
21
|
Requires-Dist: rich
|
|
21
22
|
Requires-Dist: sounddevice
|
|
22
23
|
Requires-Dist: soundfile
|
|
23
|
-
Requires-Dist: strenum
|
|
24
|
-
Requires-Dist: threa
|
|
25
|
-
Requires-Dist: tomli
|
|
24
|
+
Requires-Dist: strenum
|
|
25
|
+
Requires-Dist: threa (>=1.9.0)
|
|
26
|
+
Requires-Dist: tomli
|
|
26
27
|
Requires-Dist: typer
|
|
27
28
|
Description-Content-Type: text/markdown
|
|
28
29
|
|
|
@@ -60,13 +61,13 @@ has been used to mitigate and minimize this through software. See Appendix A.
|
|
|
60
61
|
|
|
61
62
|
### Universal?
|
|
62
63
|
|
|
63
|
-
It is a "Universal Recorder" because
|
|
64
|
-
|
|
64
|
+
It is a "Universal Recorder" because the plan to be able to record all streams of data:
|
|
65
|
+
audio is simply the start.
|
|
65
66
|
|
|
66
|
-
I have already [written code](https://github.com/rec/litoid) to do this for
|
|
67
|
-
|
|
68
|
-
and most of the value in this first step is the audio, so I
|
|
69
|
-
for this first release!
|
|
67
|
+
I have already [written code](https://github.com/rec/litoid) to do this for MIDI and DMX
|
|
68
|
+
- it works well but it isn't productionized, and I'll be folding that in in due time,
|
|
69
|
+
but most of the difficulty and most of the value in this first step is the audio, so I
|
|
70
|
+
have focused on just audio for this first release!
|
|
70
71
|
|
|
71
72
|
It might be that video is also incorporated in the far future, but the tooling is just
|
|
72
73
|
not there for Python yet, and it would be much too heavy to sit in the background all
|
|
@@ -75,12 +76,53 @@ Recorder if you liked.
|
|
|
75
76
|
|
|
76
77
|
### Installation
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
`recs` is a standard PyPi package - use `poetry add recs` or `pip install recs` or your
|
|
80
|
+
favorite package manager.
|
|
81
|
+
|
|
82
|
+
To test, type `recs --info`, which prints JSON describing the input devices
|
|
83
|
+
you have. Here's a snippet from my machine:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
[
|
|
87
|
+
{
|
|
88
|
+
"name": "FLOW 8 (Recording)",
|
|
89
|
+
"index": 1,
|
|
90
|
+
"hostapi": 0,
|
|
91
|
+
"max_input_channels": 10,
|
|
92
|
+
"max_output_channels": 4,
|
|
93
|
+
"default_low_input_latency": 0.01,
|
|
94
|
+
"default_low_output_latency": 0.004354166666666667,
|
|
95
|
+
"default_high_input_latency": 0.1,
|
|
96
|
+
"default_high_output_latency": 0.0136875,
|
|
97
|
+
"default_samplerate": 48000.0
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"name": "USB PnP Sound Device",
|
|
101
|
+
"index": 2,
|
|
102
|
+
...
|
|
103
|
+
},
|
|
104
|
+
...
|
|
105
|
+
]
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Basic Usage
|
|
109
|
+
|
|
110
|
+
Pick your nicest terminal program, go to a favorite directory with some free space, and
|
|
111
|
+
type:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
recs
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`recs` will start recording all the active audio channels into your current directory
|
|
118
|
+
and display the results in the terminal.
|
|
119
|
+
|
|
120
|
+
What "active"means can be customized rather a lot, but by default when a channel becomes
|
|
121
|
+
too quiet for more than a short time, it stops recording, and will start a new recording
|
|
122
|
+
automatically when the channel receives a signal.
|
|
123
|
+
|
|
124
|
+
Some care is taken to preserve the quiet before the start or after the end of a
|
|
125
|
+
recording to prevent abrupt transitions.
|
|
84
126
|
|
|
85
127
|
|
|
86
128
|
#### Appendix A: Failure modes
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
recs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
recs/__main__.py,sha256=FjhOKF__Z0Bm-L7_DAGcG9WfhxH0A20GLI9w8oLySbY,526
|
|
3
|
+
recs/audio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
recs/audio/block.py,sha256=EWBgpqWvHKgL5hoGOJodAf5WSREt3BBWR7vOT3m1Dk4,2606
|
|
5
|
+
recs/audio/channel_writer.py,sha256=5anwYbkU8GwaCVRCcni6nZA3M4nzrXlEwLzsVQv3CXU,6213
|
|
6
|
+
recs/audio/file_opener.py,sha256=VjGbjtghFf6HWUODJNPaQh6P6L9nrEBLOpBoBev_aDw,1404
|
|
7
|
+
recs/audio/header_size.py,sha256=9sq6iSygdcA1WoH4xIKsKYhIKte6Ah0yQFjiXbW5kIs,474
|
|
8
|
+
recs/base/__init__.py,sha256=B6x8s10ZCO9a4VrMNCzAxNdyKEhQ3YgV7d3FUgDL9PE,120
|
|
9
|
+
recs/base/_query_device.py,sha256=USFXbvFL3iDUvgtQAcfSKpiWMS2AWsSn6uf3C1WU0I0,437
|
|
10
|
+
recs/base/cfg_raw.py,sha256=dlN3kute6oCzNcyspKYTh9b7qlJeDt_Er3w9ml-QL7k,1323
|
|
11
|
+
recs/base/prefix_dict.py,sha256=hCLvFyfgd8ZaMbN4U9RH3CwxuXng1mIycAoc3n_2JiM,640
|
|
12
|
+
recs/base/pyproject.py,sha256=gs2cu18HmGgxliUum6AaB0RHwpVIgVgJG_8XDbPCeCc,386
|
|
13
|
+
recs/base/state.py,sha256=ksEsiqEeUwV5KIlmg-5pcIKB_urE9DiWgQktZ9fRDws,1851
|
|
14
|
+
recs/base/times.py,sha256=zyj9ufCXUBHtCLBlWsaxuN-e-QL2FRAQpVbbX2JN0dY,1013
|
|
15
|
+
recs/base/type_conversions.py,sha256=2M24uABZ6iZCNel4QNLmg2zSDd-n4XC7ELtzKYhgRgc,1121
|
|
16
|
+
recs/base/types.py,sha256=EKjKWBoNrlhZz_XmmqY8_BfTErcNSs0PAGdBEL1KOfY,1489
|
|
17
|
+
recs/cfg/__init__.py,sha256=h8m9SnpGMStThetoYPJeQR2WhJYd_Y01YJXH1a5wJf4,303
|
|
18
|
+
recs/cfg/aliases.py,sha256=VJwEbJZilk-Ey22Y7quKMz85JOXJ_zMGb-F5L487Kl0,2792
|
|
19
|
+
recs/cfg/app.py,sha256=iAr43yiQOAv0Gj5bzz6E7hF7UK6x8W6nv5BwtXMmdzM,2103
|
|
20
|
+
recs/cfg/cfg.py,sha256=y2ljOgzlaXgP7Ei_6pc5xh4m-TITFfzaqG7irY9yJxo,2656
|
|
21
|
+
recs/cfg/cli.py,sha256=Rd08RHw8wvj70leB7k38P8KDKb9-r9Z-0isLgc4_4PE,6761
|
|
22
|
+
recs/cfg/device.py,sha256=CYxbEAxmjXrhjB4yi3JJgUvmSvT1srrao14nymfHy1c,2530
|
|
23
|
+
recs/cfg/hash_cmp.py,sha256=Kqk0d4DDB2upgiug36lTGWZ07o8ehnCTiWXBwjVPWbk,443
|
|
24
|
+
recs/cfg/metadata.py,sha256=V8GdBzcYyIzJ_hXJjoV4EaKaS5W60rXkRGptXclfG0E,1519
|
|
25
|
+
recs/cfg/path_pattern.py,sha256=y_kyaumKG_QM58vJ1HanpNyw9HmHA2D2QNBo8WtTvu4,4058
|
|
26
|
+
recs/cfg/run_cli.py,sha256=4-l0fWDIm7s7mdVIQSkNsC0aowlyQcU4YL-42vBIFO0,1065
|
|
27
|
+
recs/cfg/time_settings.py,sha256=sfg62TBxZfLZiEur9S45KOKG1lLwwn03rPDzUrw0Zo8,1755
|
|
28
|
+
recs/cfg/track.py,sha256=ydC3vhOmnytSGaevmVZ_hM7o3nKF7z2DDGhmGTCJ2rM,1829
|
|
29
|
+
recs/misc/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
30
|
+
recs/misc/contexts.py,sha256=HIu6HrOiBon0ooihH3yqe0jHEFLkAAY-jtqKfQ0vFKU,251
|
|
31
|
+
recs/misc/counter.py,sha256=nCynFN8dyC1htPXmOu5N4h0xx5SCPp5vnIPGZlr8Mxc,1768
|
|
32
|
+
recs/misc/file_list.py,sha256=51dRCDLPNtHpfnep28TvtW61xoJ8MNL5G7sCvY0u6v8,705
|
|
33
|
+
recs/misc/legal_filename.py,sha256=7FnaiI3b0S0kkfMq6AiMPWE0MbWesKtP-INJAZ0pwso,413
|
|
34
|
+
recs/misc/log.py,sha256=pqsEEP5M86F74fsEmkl6WCFuKQqK_oCwlOCadcqoVFs,889
|
|
35
|
+
recs/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
|
+
recs/ui/device_process.py,sha256=Dimm0e16nHAU1AUl-bTjCsDgYQNMkc8178FohJaREaY,1000
|
|
37
|
+
recs/ui/device_recorder.py,sha256=zAQE7gGVbHiLMjO82c3TM-tsc2d54OvwqOyl35XyPxk,2819
|
|
38
|
+
recs/ui/device_tracks.py,sha256=W7mjnzeI2lSEMX0tvg8fVhvhXtW9twzmCcbYom2GMv8,1864
|
|
39
|
+
recs/ui/full_state.py,sha256=02zEIw3XfzDHRHycf_bJe8GWY1UE37K71nnQkbCb4HA,2075
|
|
40
|
+
recs/ui/live.py,sha256=x0A1HPHCYs6y2FBxKRENza8vdfpF_Zl_OuPPmAeZR5c,2621
|
|
41
|
+
recs/ui/recorder.py,sha256=WoXXINH9cdHC498l4A_nkhwtpAPWXSmEqGSA4y9ZNjU,2336
|
|
42
|
+
recs/ui/table.py,sha256=kKVppK709O7kgNfLkwpN3aWCgJsyqUCBQNcPTkTilsw,844
|
|
43
|
+
recs-0.3.0.dist-info/LICENSE,sha256=FJEsMutCaHNtDuTPby0OKGzqOVkVTVwG12itDolfAR8,1067
|
|
44
|
+
recs-0.3.0.dist-info/METADATA,sha256=3DJbmsdHPj72u9Umw6l5EXYHlT13JEE7NpY_Z1WZUx8,4747
|
|
45
|
+
recs-0.3.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
46
|
+
recs-0.3.0.dist-info/entry_points.txt,sha256=a7C0wMHWQVhh4lMNShv6MYnhoD8yiUswBsq8XKkBzLA,42
|
|
47
|
+
recs-0.3.0.dist-info/RECORD,,
|
recs/cfg/run.py
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
|
|
3
|
-
import soundfile as sf
|
|
4
|
-
|
|
5
|
-
from recs.base.types import Format, SdType
|
|
6
|
-
from recs.cfg import device
|
|
7
|
-
from recs.ui.recorder import Recorder
|
|
8
|
-
|
|
9
|
-
from . import Cfg
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def run(cfg: Cfg) -> None:
|
|
13
|
-
if cfg.info:
|
|
14
|
-
_info()
|
|
15
|
-
|
|
16
|
-
elif cfg.list_types:
|
|
17
|
-
avail = sf.available_formats()
|
|
18
|
-
fmts = [f.upper() for f in Format]
|
|
19
|
-
formats = {f: [avail[f], sf.available_subtypes(f)] for f in fmts}
|
|
20
|
-
sdtypes = [str(s) for s in SdType]
|
|
21
|
-
d = {'formats': formats, 'sdtypes': sdtypes}
|
|
22
|
-
|
|
23
|
-
print(json.dumps(d, indent=4))
|
|
24
|
-
|
|
25
|
-
else:
|
|
26
|
-
Recorder(cfg).run()
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def _info():
|
|
30
|
-
info = device.query_devices()
|
|
31
|
-
info = [i for i in info if i['max_input_channels']]
|
|
32
|
-
print(json.dumps(info, indent=4))
|
recs/misc/recording_path.py
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import typing as t
|
|
2
|
-
from datetime import datetime
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
|
|
5
|
-
from recs.base import types
|
|
6
|
-
from recs.cfg import Aliases, InputDevice, Track
|
|
7
|
-
|
|
8
|
-
from .legal_filename import legal_filename
|
|
9
|
-
|
|
10
|
-
NAME_JOINER = ' + '
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def recording_path(
|
|
14
|
-
track: Track,
|
|
15
|
-
aliases: Aliases,
|
|
16
|
-
subdirectories: t.Sequence[types.Subdirectory],
|
|
17
|
-
timestamp: float,
|
|
18
|
-
) -> tuple[Path, str]:
|
|
19
|
-
def display_name(x: InputDevice | Track) -> str:
|
|
20
|
-
s = aliases.display_name(x)
|
|
21
|
-
return legal_filename(s)
|
|
22
|
-
|
|
23
|
-
ts = datetime.fromtimestamp(timestamp)
|
|
24
|
-
time = ts.strftime('%Y/%m/%d')
|
|
25
|
-
hms = ts.strftime('%H%M%S')
|
|
26
|
-
|
|
27
|
-
pieces: dict[str, str] = {
|
|
28
|
-
'device': display_name(track.device),
|
|
29
|
-
'channel': display_name(track),
|
|
30
|
-
'time': time,
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
paths: list[str] = []
|
|
34
|
-
|
|
35
|
-
for s in subdirectories:
|
|
36
|
-
paths.append(pieces.pop(s))
|
|
37
|
-
|
|
38
|
-
file_parts = []
|
|
39
|
-
for k, v in pieces.items():
|
|
40
|
-
if k == 'time':
|
|
41
|
-
date = v.replace('/', '')
|
|
42
|
-
hms = f'{date}-{hms}'
|
|
43
|
-
else:
|
|
44
|
-
file_parts.append(v)
|
|
45
|
-
|
|
46
|
-
filename = NAME_JOINER.join([*file_parts, hms])
|
|
47
|
-
return Path(*paths), filename
|
recs/ui/channel_recorder.py
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import typing as t
|
|
2
|
-
|
|
3
|
-
import numpy as np
|
|
4
|
-
|
|
5
|
-
from recs.audio import block, channel_writer
|
|
6
|
-
from recs.base import times
|
|
7
|
-
from recs.cfg import Cfg, Track
|
|
8
|
-
from recs.misc import counter
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class ChannelRecorder:
|
|
12
|
-
block_count: int = 0
|
|
13
|
-
block_size: int = 0
|
|
14
|
-
|
|
15
|
-
def __init__(self, cfg: Cfg, times: times.TimeSettings[int], track: Track) -> None:
|
|
16
|
-
self.track = track
|
|
17
|
-
self.writer = channel_writer.ChannelWriter(cfg, times, track)
|
|
18
|
-
self.volume = counter.MovingBlock(times.moving_average_time)
|
|
19
|
-
|
|
20
|
-
self.writer.start()
|
|
21
|
-
self.stop = self.writer.stop
|
|
22
|
-
|
|
23
|
-
def callback(self, array: np.ndarray, time: float) -> None:
|
|
24
|
-
b = block.Block(array[:, self.track.slice])
|
|
25
|
-
self.writer.write(b, time)
|
|
26
|
-
|
|
27
|
-
self.block_size = len(b)
|
|
28
|
-
self.block_count += 1
|
|
29
|
-
self.volume(b)
|
|
30
|
-
|
|
31
|
-
@property
|
|
32
|
-
def file_count(self) -> int:
|
|
33
|
-
return len(self.writer.files_written)
|
|
34
|
-
|
|
35
|
-
@property
|
|
36
|
-
def file_size(self) -> int:
|
|
37
|
-
return self.writer.files_written.total_size
|
|
38
|
-
|
|
39
|
-
@property
|
|
40
|
-
def recorded_time(self) -> float:
|
|
41
|
-
return self.writer.frames_written / self.track.device.samplerate
|
|
42
|
-
|
|
43
|
-
def rows(self) -> t.Iterator[dict[str, t.Any]]:
|
|
44
|
-
yield {
|
|
45
|
-
'channel': self.track.channels_name,
|
|
46
|
-
'on': self.writer.active,
|
|
47
|
-
'recorded': self.recorded_time,
|
|
48
|
-
'file_size': self.file_size,
|
|
49
|
-
'file_count': self.file_count,
|
|
50
|
-
'volume': self.volume.mean(),
|
|
51
|
-
}
|
recs-0.2.0.dist-info/RECORD
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
recs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
recs/__main__.py,sha256=ExgCYfYfOKv0oPtqfJ23yWHgjG7Hf-yZ8Pd3G9r3UaU,97
|
|
3
|
-
recs/audio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
recs/audio/block.py,sha256=A6BNmuhyj-vyThEn7rfydmgTN8aN_pO0jIMRrjAHWZA,2667
|
|
5
|
-
recs/audio/channel_writer.py,sha256=PmZSmrpARZg9KO-TM8VcdKkmqDhlA_wIUmWhkRedagE,5395
|
|
6
|
-
recs/audio/file_opener.py,sha256=Px2cX_OaoQCoAfJM_WatwsUIiXD2ccojtTc_XgwlX20,1413
|
|
7
|
-
recs/audio/header_size.py,sha256=9sq6iSygdcA1WoH4xIKsKYhIKte6Ah0yQFjiXbW5kIs,474
|
|
8
|
-
recs/base/__init__.py,sha256=B6x8s10ZCO9a4VrMNCzAxNdyKEhQ3YgV7d3FUgDL9PE,120
|
|
9
|
-
recs/base/cfg_raw.py,sha256=hHdzCD0uqJoyy5vR0h2hrPPulJ6tvmU_3camODMGIrA,1332
|
|
10
|
-
recs/base/metadata.py,sha256=wgl9OmsUN7dGiHTsaxChpGLfoyXXrjxqFrviNjU5y8M,1160
|
|
11
|
-
recs/base/prefix_dict.py,sha256=2AeskezTAQ7CbvMvdP4TZVjcjEDiFqKvQWnncd_vqIo,700
|
|
12
|
-
recs/base/pyproject.py,sha256=gs2cu18HmGgxliUum6AaB0RHwpVIgVgJG_8XDbPCeCc,386
|
|
13
|
-
recs/base/times.py,sha256=yrHZynlrjG4Nsci0GDvXraP0kQ8-Tc34KER5enlMQMw,2730
|
|
14
|
-
recs/base/type_conversions.py,sha256=gL6FawyHlu2ZI82Tpk0sBjdpKfAm1hXcdmBZ7oXQGDI,1097
|
|
15
|
-
recs/base/types.py,sha256=COvcC5rhfLC7M7MMwnHwX5unf8MTFFDTBF2BFOvzlzg,1551
|
|
16
|
-
recs/cfg/__init__.py,sha256=gmA9rYea-supLkV22-wAOFqgwL1VHVZdHk49dKKZcQI,189
|
|
17
|
-
recs/cfg/aliases.py,sha256=Y8u4Rq8FiFHvz2UX6-LFiaDIb-prLt_95iBdAsdlgv0,2458
|
|
18
|
-
recs/cfg/cfg.py,sha256=xElx2ktSaMI5TVKVtj1xVA-pNuB4cYQy4lxO5Ki_7UI,3543
|
|
19
|
-
recs/cfg/cli.py,sha256=B4HmGwZRiNPrjuKs01vFDwDgbXP6U-fzoulvz14mb_A,5741
|
|
20
|
-
recs/cfg/device.py,sha256=D9ythqh_nCXtahH_NLXS33XO0-wozSqRD-lA6_4FSWg,3144
|
|
21
|
-
recs/cfg/run.py,sha256=sL5-4cgdII_kCWe7kfoPJ11cuJVCDMNGDI4Hv7tFUMs,727
|
|
22
|
-
recs/cfg/track.py,sha256=R7HSckho2yrASY1Y-oiaLZzCAeeJVZKcetasFAx95_E,1864
|
|
23
|
-
recs/misc/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
24
|
-
recs/misc/counter.py,sha256=1APBY4bQfmdnYEZVAKr0zUJ92t4EFdusoD_jAo2oZVo,1689
|
|
25
|
-
recs/misc/file_list.py,sha256=51dRCDLPNtHpfnep28TvtW61xoJ8MNL5G7sCvY0u6v8,705
|
|
26
|
-
recs/misc/hash_cmp.py,sha256=Kqk0d4DDB2upgiug36lTGWZ07o8ehnCTiWXBwjVPWbk,443
|
|
27
|
-
recs/misc/legal_filename.py,sha256=z4W1AN5BdI8EcHMoswkjDews5AFNtRenW7ZfULDXC-A,412
|
|
28
|
-
recs/misc/recording_path.py,sha256=OCqdi4Pza2YhH03xjLHwjGEj96_kduezBNDk40__D40,1131
|
|
29
|
-
recs/ui/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
30
|
-
recs/ui/channel_recorder.py,sha256=CN024p8ZAYBtVIejyalNYjbfnYU_8CApnRuEmYGXO7g,1464
|
|
31
|
-
recs/ui/device_recorder.py,sha256=Iagwd-k13xwQRwt96Xog8e7rXUoTi-rb7AezREbeZ8g,3098
|
|
32
|
-
recs/ui/device_tracks.py,sha256=QA8o8W__c0UdnFFbBLOAUUcGGILcnkChCXTymXJWdsM,1660
|
|
33
|
-
recs/ui/live.py,sha256=9oMwh9-Nomd0U_XdFCLb-A5AuDTsnQWdO9Ik0te45TU,2399
|
|
34
|
-
recs/ui/recorder.py,sha256=E1nj97UMI9v8bH1WXs9Pu5csTJjgcUOV2gV1PfXXYlI,2614
|
|
35
|
-
recs/ui/table.py,sha256=5-1UJVyBXRWT_isBSMYEgJRmzuORKSyXAP8YY31cBxU,836
|
|
36
|
-
recs-0.2.0.dist-info/LICENSE,sha256=FJEsMutCaHNtDuTPby0OKGzqOVkVTVwG12itDolfAR8,1067
|
|
37
|
-
recs-0.2.0.dist-info/METADATA,sha256=Sd7EPFpOo-KaWbTDKj63BmxZrIAw1rBZbsPmZivARoU,3360
|
|
38
|
-
recs-0.2.0.dist-info/WHEEL,sha256=WGfLGfLX43Ei_YORXSnT54hxFygu34kMpcQdmgmEwCQ,88
|
|
39
|
-
recs-0.2.0.dist-info/entry_points.txt,sha256=a7C0wMHWQVhh4lMNShv6MYnhoD8yiUswBsq8XKkBzLA,42
|
|
40
|
-
recs-0.2.0.dist-info/RECORD,,
|
/recs/{misc → cfg}/hash_cmp.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|