recs 0.3.1__py3-none-any.whl → 0.10.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/.DS_Store +0 -0
- recs/audio/.DS_Store +0 -0
- recs/audio/block.py +6 -4
- recs/audio/channel_writer.py +98 -54
- recs/audio/file_opener.py +22 -20
- recs/audio/header_size.py +7 -7
- recs/base/.DS_Store +0 -0
- recs/base/_query_device.py +3 -3
- recs/base/cfg_raw.py +5 -4
- recs/base/pyproject.py +1 -1
- recs/base/type_conversions.py +0 -9
- recs/base/types.py +2 -34
- recs/cfg/.DS_Store +0 -0
- recs/cfg/__init__.py +4 -11
- recs/cfg/aliases.py +13 -17
- recs/cfg/app.py +7 -5
- recs/cfg/cfg.py +24 -12
- recs/cfg/cli.py +37 -18
- recs/cfg/device.py +30 -42
- recs/cfg/file_source.py +61 -0
- recs/cfg/hash_cmp.py +2 -2
- recs/cfg/metadata.py +2 -5
- recs/cfg/path_pattern.py +13 -5
- recs/cfg/run_cli.py +8 -17
- recs/cfg/source.py +42 -0
- recs/cfg/time_settings.py +4 -1
- recs/cfg/track.py +12 -9
- recs/misc/.DS_Store +0 -0
- recs/misc/__init__.py +0 -1
- recs/misc/contexts.py +1 -1
- recs/misc/counter.py +12 -4
- recs/misc/file_list.py +1 -1
- recs/misc/log.py +5 -5
- recs/ui/.DS_Store +0 -0
- recs/ui/full_state.py +11 -6
- recs/ui/live.py +9 -9
- recs/ui/recorder.py +39 -48
- recs/ui/source_recorder.py +76 -0
- recs/ui/{device_tracks.py → source_tracks.py} +19 -17
- recs/ui/table.py +2 -2
- {recs-0.3.1.dist-info → recs-0.10.0.dist-info}/METADATA +14 -14
- recs-0.10.0.dist-info/RECORD +53 -0
- {recs-0.3.1.dist-info → recs-0.10.0.dist-info}/WHEEL +1 -1
- recs-0.10.0.dist-info/entry_points.txt +3 -0
- recs/ui/device_process.py +0 -37
- recs/ui/device_recorder.py +0 -83
- recs-0.3.1.dist-info/LICENSE +0 -21
- recs-0.3.1.dist-info/RECORD +0 -47
- recs-0.3.1.dist-info/entry_points.txt +0 -3
recs/.DS_Store
ADDED
|
Binary file
|
recs/audio/.DS_Store
ADDED
|
Binary file
|
recs/audio/block.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# mypy: disable-error-code="no-any-return, type-arg"
|
|
2
|
+
|
|
1
3
|
import dataclasses as dc
|
|
2
4
|
import numbers
|
|
3
5
|
import typing as t
|
|
@@ -5,6 +7,8 @@ from functools import cached_property
|
|
|
5
7
|
|
|
6
8
|
import numpy as np
|
|
7
9
|
|
|
10
|
+
from recs.cfg.source import to_matrix
|
|
11
|
+
|
|
8
12
|
_EMPTY_SEEN = False
|
|
9
13
|
|
|
10
14
|
|
|
@@ -15,9 +19,7 @@ class Block:
|
|
|
15
19
|
def __post_init__(self) -> None:
|
|
16
20
|
if not self.block.size:
|
|
17
21
|
raise ValueError('Empty block')
|
|
18
|
-
|
|
19
|
-
if len(self.block.shape) == 1:
|
|
20
|
-
self.__dict__['block'] = self.block.reshape(*self.block.shape, 1)
|
|
22
|
+
self.__dict__['block'] = to_matrix(self.block)
|
|
21
23
|
|
|
22
24
|
def __len__(self) -> int:
|
|
23
25
|
return self.block.shape[0]
|
|
@@ -26,7 +28,7 @@ class Block:
|
|
|
26
28
|
return Block(self.block[index])
|
|
27
29
|
|
|
28
30
|
@cached_property
|
|
29
|
-
def is_float(self):
|
|
31
|
+
def is_float(self) -> bool:
|
|
30
32
|
return not issubclass(self.block.dtype.type, numbers.Integral)
|
|
31
33
|
|
|
32
34
|
@cached_property
|
recs/audio/channel_writer.py
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import contextlib
|
|
2
|
+
import sys
|
|
2
3
|
import typing as t
|
|
3
4
|
from datetime import datetime
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
from threading import Lock
|
|
6
7
|
|
|
8
|
+
from numpy.typing import NDArray
|
|
7
9
|
from overrides import override
|
|
8
10
|
from soundfile import SoundFile
|
|
9
11
|
from threa import Runnable
|
|
10
12
|
|
|
11
13
|
from recs.base.state import ChannelState
|
|
14
|
+
from recs.base.type_conversions import SUBTYPE_TO_SDTYPE
|
|
12
15
|
from recs.base.types import SDTYPE, Active, Format, SdType
|
|
13
|
-
from recs.cfg import Cfg, Track,
|
|
16
|
+
from recs.cfg import Cfg, Track, time_settings
|
|
14
17
|
from recs.misc import counter, file_list
|
|
15
18
|
|
|
16
19
|
from .block import Block, Blocks
|
|
@@ -19,11 +22,8 @@ from .header_size import header_size
|
|
|
19
22
|
|
|
20
23
|
URL = 'https://github.com/rec/recs'
|
|
21
24
|
|
|
22
|
-
BUFFER =
|
|
23
|
-
|
|
24
|
-
Format.aiff: 0x8000_0000,
|
|
25
|
-
Format.wav: 0x1_0000_0000,
|
|
26
|
-
}
|
|
25
|
+
BUFFER = 0x80
|
|
26
|
+
MAX_WAV_SIZE = 0x1_0000_0000 - BUFFER
|
|
27
27
|
|
|
28
28
|
ITEMSIZE = {
|
|
29
29
|
SdType.float32: 4,
|
|
@@ -35,9 +35,9 @@ BLOCK_FUZZ = 2
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
class ChannelWriter(Runnable):
|
|
38
|
-
|
|
38
|
+
bytes_in_file: int = 0
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
frames_in_file: int = 0
|
|
41
41
|
frames_written: int = 0 # Used elsewhere
|
|
42
42
|
|
|
43
43
|
largest_file_size: int = 0
|
|
@@ -45,19 +45,19 @@ class ChannelWriter(Runnable):
|
|
|
45
45
|
|
|
46
46
|
timestamp: float = 0
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
_sfs: t.Sequence[SoundFile] = ()
|
|
49
49
|
|
|
50
50
|
@property
|
|
51
51
|
def active(self) -> Active:
|
|
52
|
-
return Active.active if self.
|
|
52
|
+
return Active.active if self._sfs else Active.inactive
|
|
53
53
|
|
|
54
54
|
def __init__(
|
|
55
55
|
self, cfg: Cfg, times: time_settings.TimeSettings[int], track: Track
|
|
56
56
|
) -> None:
|
|
57
57
|
super().__init__()
|
|
58
58
|
|
|
59
|
+
self.cfg = cfg
|
|
59
60
|
self.do_not_record = cfg.dry_run or cfg.calibrate
|
|
60
|
-
self.format = cfg.format
|
|
61
61
|
self.metadata = cfg.metadata
|
|
62
62
|
self.times = times
|
|
63
63
|
self.track = track
|
|
@@ -65,20 +65,58 @@ class ChannelWriter(Runnable):
|
|
|
65
65
|
self._blocks = Blocks()
|
|
66
66
|
self._lock = Lock()
|
|
67
67
|
|
|
68
|
+
if track.source.format is None or cfg.cfg.formats:
|
|
69
|
+
self.formats = cfg.formats
|
|
70
|
+
else:
|
|
71
|
+
self.formats = [track.source.format]
|
|
72
|
+
|
|
73
|
+
if track.source.subtype is None or cfg.cfg.subtype:
|
|
74
|
+
subtype = cfg.subtype
|
|
75
|
+
else:
|
|
76
|
+
subtype = track.source.subtype
|
|
77
|
+
|
|
78
|
+
if track.source.subtype is None or cfg.cfg.sdtype:
|
|
79
|
+
sdtype = cfg.sdtype or SDTYPE
|
|
80
|
+
else:
|
|
81
|
+
sdtype = SUBTYPE_TO_SDTYPE[track.source.subtype]
|
|
82
|
+
|
|
68
83
|
self.files_written = file_list.FileList()
|
|
69
|
-
self.frame_size = ITEMSIZE[
|
|
84
|
+
self.frame_size = ITEMSIZE[sdtype] * len(track.channels)
|
|
70
85
|
self.longest_file_frames = times.longest_file_time
|
|
71
|
-
|
|
86
|
+
|
|
87
|
+
self.openers = [
|
|
88
|
+
FileOpener(
|
|
89
|
+
channels=len(track.channels),
|
|
90
|
+
format=f,
|
|
91
|
+
samplerate=track.source.samplerate,
|
|
92
|
+
subtype=subtype,
|
|
93
|
+
)
|
|
94
|
+
for f in self.formats
|
|
95
|
+
]
|
|
72
96
|
self._volume = counter.MovingBlock(times.moving_average_time)
|
|
73
97
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
98
|
+
def size(f: str) -> int:
|
|
99
|
+
return (
|
|
100
|
+
MAX_WAV_SIZE if f == Format.wav and not self.cfg.infinite_length else 0
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
self.largest_file_size = max(0, *(size(f) for f in cfg.formats))
|
|
104
|
+
|
|
105
|
+
def to_block(self, array: NDArray) -> Block:
|
|
106
|
+
return Block(array[:, self.track.slice])
|
|
77
107
|
|
|
78
|
-
def
|
|
79
|
-
block =
|
|
108
|
+
def receive_update(
|
|
109
|
+
self, block: Block, timestamp: float, should_record: bool = False
|
|
110
|
+
) -> ChannelState:
|
|
80
111
|
with self._lock:
|
|
81
|
-
|
|
112
|
+
should_record = should_record or self.should_record(block)
|
|
113
|
+
return self._receive_block(block, timestamp, should_record)
|
|
114
|
+
|
|
115
|
+
def should_record(self, block: Block) -> bool:
|
|
116
|
+
return (
|
|
117
|
+
self.times.record_everything
|
|
118
|
+
or block.volume >= self.times.noise_floor_amplitude
|
|
119
|
+
)
|
|
82
120
|
|
|
83
121
|
@override
|
|
84
122
|
def stop(self) -> None:
|
|
@@ -88,32 +126,37 @@ class ChannelWriter(Runnable):
|
|
|
88
126
|
self.stopped = True
|
|
89
127
|
|
|
90
128
|
def _close(self) -> None:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
sf.
|
|
94
|
-
|
|
95
|
-
with contextlib.suppress(Exception):
|
|
129
|
+
sfs, self._sfs = self._sfs, ()
|
|
130
|
+
for sf in sfs:
|
|
131
|
+
if sf.frames and sf.frames >= self.times.shortest_file_time:
|
|
132
|
+
print(sf.name, file=sys.stderr)
|
|
96
133
|
sf.close()
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
134
|
+
else:
|
|
135
|
+
with contextlib.suppress(Exception):
|
|
136
|
+
sf.close()
|
|
137
|
+
with contextlib.suppress(Exception):
|
|
138
|
+
Path(sf.name).unlink()
|
|
139
|
+
|
|
140
|
+
def _open(self, offset: int) -> t.Sequence[SoundFile]:
|
|
141
|
+
timestamp = self.timestamp - offset / self.track.source.samplerate
|
|
142
|
+
date = datetime.fromtimestamp(timestamp).isoformat()
|
|
104
143
|
index = 1 + len(self.files_written)
|
|
105
|
-
|
|
106
|
-
metadata = {'date': ts.isoformat(), 'software': URL, 'tracknumber': str(index)}
|
|
144
|
+
metadata = {'date': date, 'software': URL, 'tracknumber': str(index)}
|
|
107
145
|
metadata |= self.metadata
|
|
108
146
|
|
|
109
|
-
self.
|
|
110
|
-
self.
|
|
147
|
+
self.bytes_in_file = max(header_size(metadata, f) for f in self.cfg.formats)
|
|
148
|
+
self.frames_in_file = 0
|
|
111
149
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
150
|
+
path = self.cfg.output_directory.make_path(
|
|
151
|
+
self.track, self.cfg.aliases, timestamp, index
|
|
152
|
+
)
|
|
153
|
+
sfs = [o.create(metadata, path) for o in self.openers]
|
|
154
|
+
self.files_written.extend(Path(sf.name) for sf in sfs)
|
|
155
|
+
return sfs
|
|
115
156
|
|
|
116
|
-
def _receive_block(
|
|
157
|
+
def _receive_block(
|
|
158
|
+
self, block: Block, timestamp: float, should_record: bool
|
|
159
|
+
) -> ChannelState:
|
|
117
160
|
saved_state = self._state(
|
|
118
161
|
max_amp=max(block.max) / block.scale,
|
|
119
162
|
min_amp=min(block.min) / block.scale,
|
|
@@ -123,16 +166,16 @@ class ChannelWriter(Runnable):
|
|
|
123
166
|
self.timestamp = timestamp
|
|
124
167
|
self._volume(block)
|
|
125
168
|
|
|
126
|
-
if not self.do_not_record and (self.
|
|
127
|
-
expected_dt = len(block) / self.track.
|
|
169
|
+
if not self.do_not_record and (self._sfs or not self.stopped):
|
|
170
|
+
expected_dt = len(block) / self.track.source.samplerate
|
|
128
171
|
|
|
129
172
|
if dt > expected_dt * BLOCK_FUZZ: # We were asleep, or otherwise lost time
|
|
130
173
|
self._write_and_close()
|
|
131
174
|
|
|
132
175
|
self._blocks.append(block)
|
|
133
176
|
|
|
134
|
-
if
|
|
135
|
-
if not self.
|
|
177
|
+
if should_record:
|
|
178
|
+
if not self._sfs: # Record some quiet before the first block
|
|
136
179
|
length = self.times.quiet_before_start + len(self._blocks[-1])
|
|
137
180
|
self._blocks.clip(length, from_start=True)
|
|
138
181
|
|
|
@@ -144,12 +187,12 @@ class ChannelWriter(Runnable):
|
|
|
144
187
|
|
|
145
188
|
return self._state() - saved_state
|
|
146
189
|
|
|
147
|
-
def _state(self, **kwargs) -> ChannelState:
|
|
190
|
+
def _state(self, **kwargs: t.Any) -> ChannelState:
|
|
148
191
|
return ChannelState(
|
|
149
192
|
file_count=len(self.files_written),
|
|
150
193
|
file_size=self.files_written.total_size,
|
|
151
|
-
is_active=bool(self.
|
|
152
|
-
recorded_time=self.frames_written / self.track.
|
|
194
|
+
is_active=bool(self._sfs),
|
|
195
|
+
recorded_time=self.frames_written / self.track.source.samplerate,
|
|
153
196
|
timestamp=self.timestamp,
|
|
154
197
|
volume=tuple(self._volume.mean()),
|
|
155
198
|
**kwargs,
|
|
@@ -159,7 +202,7 @@ class ChannelWriter(Runnable):
|
|
|
159
202
|
# Record some quiet after the last block
|
|
160
203
|
removed = self._blocks.clip(self.times.quiet_after_end, from_start=False)
|
|
161
204
|
|
|
162
|
-
if self.
|
|
205
|
+
if self._sfs and removed:
|
|
163
206
|
self._write_blocks(reversed(removed))
|
|
164
207
|
|
|
165
208
|
self._close()
|
|
@@ -176,19 +219,20 @@ class ChannelWriter(Runnable):
|
|
|
176
219
|
remains: list[int] = []
|
|
177
220
|
|
|
178
221
|
if self.longest_file_frames:
|
|
179
|
-
remains.append(self.longest_file_frames - self.
|
|
222
|
+
remains.append(self.longest_file_frames - self.frames_in_file)
|
|
180
223
|
|
|
181
|
-
if self.
|
|
182
|
-
file_bytes = self.largest_file_size - self.
|
|
224
|
+
if self._sfs and self.largest_file_size:
|
|
225
|
+
file_bytes = self.largest_file_size - self.bytes_in_file
|
|
183
226
|
remains.append(file_bytes // self.frame_size)
|
|
184
227
|
|
|
185
228
|
if remains and min(remains) <= len(b):
|
|
186
229
|
self._close()
|
|
187
230
|
|
|
188
|
-
self.
|
|
189
|
-
self.
|
|
231
|
+
self._sfs = self._sfs or self._open(offset)
|
|
232
|
+
for sf in self._sfs:
|
|
233
|
+
sf.write(b.block)
|
|
190
234
|
offset += len(b)
|
|
191
235
|
|
|
192
|
-
self.
|
|
236
|
+
self.frames_in_file += len(b)
|
|
193
237
|
self.frames_written += len(b)
|
|
194
|
-
self.
|
|
238
|
+
self.bytes_in_file += len(b) * self.frame_size
|
recs/audio/file_opener.py
CHANGED
|
@@ -1,48 +1,50 @@
|
|
|
1
|
+
import dataclasses as dc
|
|
1
2
|
import itertools
|
|
3
|
+
import typing as t
|
|
2
4
|
from pathlib import Path
|
|
3
5
|
|
|
4
|
-
import soundfile
|
|
6
|
+
import soundfile
|
|
5
7
|
|
|
6
|
-
from recs.
|
|
8
|
+
from recs.base.types import Format, Subtype
|
|
7
9
|
from recs.cfg.metadata import ALLOWS_METADATA
|
|
8
10
|
|
|
9
11
|
|
|
12
|
+
@dc.dataclass
|
|
10
13
|
class FileOpener:
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
format: Format
|
|
15
|
+
channels: int = 1
|
|
16
|
+
samplerate: int = 48_000
|
|
17
|
+
subtype: Subtype | None = None
|
|
14
18
|
|
|
15
19
|
def open(
|
|
16
|
-
self, path: Path | str, metadata:
|
|
17
|
-
) ->
|
|
18
|
-
path = Path(path).with_suffix('.' + self.
|
|
20
|
+
self, path: Path | str, metadata: t.Mapping[str, str], overwrite: bool = False
|
|
21
|
+
) -> soundfile.SoundFile:
|
|
22
|
+
path = Path(path).with_suffix('.' + self.format)
|
|
19
23
|
if not overwrite and path.exists():
|
|
20
24
|
raise FileExistsError(str(path))
|
|
21
25
|
|
|
22
|
-
fp =
|
|
23
|
-
channels=
|
|
26
|
+
fp = soundfile.SoundFile(
|
|
27
|
+
channels=self.channels,
|
|
24
28
|
file=path,
|
|
25
|
-
format=self.
|
|
29
|
+
format=self.format,
|
|
26
30
|
mode='w',
|
|
27
|
-
samplerate=self.
|
|
28
|
-
subtype=self.
|
|
31
|
+
samplerate=self.samplerate,
|
|
32
|
+
subtype=self.subtype,
|
|
29
33
|
)
|
|
30
34
|
|
|
31
|
-
if self.
|
|
35
|
+
if self.format in ALLOWS_METADATA:
|
|
32
36
|
for k, v in metadata.items():
|
|
33
37
|
setattr(fp, k, v)
|
|
34
38
|
|
|
35
39
|
return fp
|
|
36
40
|
|
|
37
|
-
def create(
|
|
38
|
-
|
|
39
|
-
) -> sf.SoundFile:
|
|
40
|
-
p = Path(self.cfg.path.evaluate(self.track, self.cfg.aliases, timestamp, index))
|
|
41
|
-
p.parent.mkdir(exist_ok=True, parents=True)
|
|
41
|
+
def create(self, metadata: t.Mapping[str, str], path: Path) -> soundfile.SoundFile:
|
|
42
|
+
path.parent.mkdir(exist_ok=True, parents=True)
|
|
42
43
|
|
|
43
44
|
for i in itertools.count():
|
|
44
|
-
f =
|
|
45
|
+
f = path.parent / (path.name + bool(i) * f'_{i}')
|
|
45
46
|
try:
|
|
46
47
|
return self.open(f, metadata)
|
|
47
48
|
except FileExistsError:
|
|
48
49
|
pass
|
|
50
|
+
raise FileNotFoundError
|
recs/audio/header_size.py
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
+
import typing as t
|
|
2
|
+
|
|
1
3
|
from recs.base.types import Format
|
|
2
4
|
|
|
3
5
|
|
|
4
|
-
def header_size(metadata:
|
|
5
|
-
|
|
6
|
-
if format == Format.aiff:
|
|
7
|
-
base, first, software = 54, 0, 22
|
|
8
|
-
elif format == Format.wav:
|
|
9
|
-
base, first, software = 44, 12, 19
|
|
10
|
-
else:
|
|
6
|
+
def header_size(metadata: t.Mapping[str, str], format: Format) -> int:
|
|
7
|
+
if format != Format.wav:
|
|
11
8
|
return 0
|
|
12
9
|
|
|
10
|
+
tag = 9
|
|
11
|
+
base, first, software = 44, 12, 19
|
|
12
|
+
|
|
13
13
|
values = (software * (k == 'software') + tag + len(v) for k, v in metadata.items())
|
|
14
14
|
values = (v + v % 2 for v in values)
|
|
15
15
|
|
recs/base/.DS_Store
ADDED
|
Binary file
|
recs/base/_query_device.py
CHANGED
|
@@ -8,14 +8,14 @@ import json
|
|
|
8
8
|
import typing as t
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
def
|
|
11
|
+
def _query_devices() -> t.Any:
|
|
12
12
|
try:
|
|
13
13
|
import sounddevice
|
|
14
14
|
|
|
15
15
|
return sounddevice.query_devices()
|
|
16
|
-
except
|
|
16
|
+
except BaseException:
|
|
17
17
|
return []
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
if __name__ == '__main__':
|
|
21
|
-
print(json.dumps(
|
|
21
|
+
print(json.dumps(_query_devices(), indent=4))
|
recs/base/cfg_raw.py
CHANGED
|
@@ -2,8 +2,6 @@ import dataclasses as dc
|
|
|
2
2
|
import typing as t
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
|
-
from .types import Format
|
|
6
|
-
|
|
7
5
|
|
|
8
6
|
@dc.dataclass
|
|
9
7
|
class CfgRaw:
|
|
@@ -11,7 +9,8 @@ class CfgRaw:
|
|
|
11
9
|
#
|
|
12
10
|
# Directory settings
|
|
13
11
|
#
|
|
14
|
-
|
|
12
|
+
files: list[str] = dc.field(default_factory=list)
|
|
13
|
+
output_directory: str = ''
|
|
15
14
|
#
|
|
16
15
|
# General purpose settings
|
|
17
16
|
#
|
|
@@ -33,7 +32,7 @@ class CfgRaw:
|
|
|
33
32
|
#
|
|
34
33
|
# Audio file format and subtype
|
|
35
34
|
#
|
|
36
|
-
|
|
35
|
+
formats: t.Sequence[str] = ()
|
|
37
36
|
metadata: t.Sequence[str] = ()
|
|
38
37
|
sdtype: str = ''
|
|
39
38
|
subtype: str = ''
|
|
@@ -47,10 +46,12 @@ class CfgRaw:
|
|
|
47
46
|
#
|
|
48
47
|
# Settings relating to times
|
|
49
48
|
#
|
|
49
|
+
band_mode: bool = True
|
|
50
50
|
infinite_length: bool = False
|
|
51
51
|
longest_file_time: float = 0.0
|
|
52
52
|
moving_average_time: float = 1.0
|
|
53
53
|
noise_floor: float = 70.0
|
|
54
|
+
record_everything: bool = False
|
|
54
55
|
shortest_file_time: float = 1.0
|
|
55
56
|
quiet_after_end: float = 2.0
|
|
56
57
|
quiet_before_start: float = 1.0
|
recs/base/pyproject.py
CHANGED
recs/base/type_conversions.py
CHANGED
|
@@ -11,16 +11,7 @@ SUBTYPE_TO_SDTYPE = {
|
|
|
11
11
|
Subtype.alac_24: SdType.int32,
|
|
12
12
|
Subtype.alac_32: SdType.int32,
|
|
13
13
|
Subtype.double: SdType.float32,
|
|
14
|
-
Subtype.dpcm_16: SdType.int16,
|
|
15
|
-
Subtype.dpcm_8: SdType.int16,
|
|
16
|
-
Subtype.dwvw_12: SdType.int16,
|
|
17
|
-
Subtype.dwvw_16: SdType.int16,
|
|
18
|
-
Subtype.dwvw_24: SdType.int32,
|
|
19
14
|
Subtype.float: SdType.float32,
|
|
20
|
-
Subtype.ms_adpcm: SdType.int32,
|
|
21
|
-
Subtype.nms_adpcm_16: SdType.int16,
|
|
22
|
-
Subtype.nms_adpcm_24: SdType.int32,
|
|
23
|
-
Subtype.nms_adpcm_32: SdType.int32,
|
|
24
15
|
Subtype.pcm_16: SdType.int16,
|
|
25
16
|
Subtype.pcm_24: SdType.int32,
|
|
26
17
|
Subtype.pcm_32: SdType.int32,
|
recs/base/types.py
CHANGED
|
@@ -3,7 +3,6 @@ from enum import auto
|
|
|
3
3
|
|
|
4
4
|
from strenum import StrEnum
|
|
5
5
|
|
|
6
|
-
DeviceDict = dict[str, float | int | str]
|
|
7
6
|
Stop = t.Callable[[], None]
|
|
8
7
|
|
|
9
8
|
|
|
@@ -23,27 +22,14 @@ SDTYPE = SdType.float32
|
|
|
23
22
|
|
|
24
23
|
|
|
25
24
|
class Format(StrEnum):
|
|
26
|
-
aiff = auto()
|
|
27
|
-
au = auto()
|
|
28
|
-
avr = auto()
|
|
29
|
-
caf = auto()
|
|
30
25
|
flac = auto()
|
|
31
|
-
ircam = auto()
|
|
32
|
-
mat4 = auto()
|
|
33
|
-
mat5 = auto()
|
|
34
26
|
mp3 = auto()
|
|
35
|
-
mpc2k = auto()
|
|
36
|
-
nist = auto()
|
|
37
27
|
ogg = auto()
|
|
38
|
-
paf = auto()
|
|
39
|
-
pvf = auto()
|
|
40
28
|
raw = auto()
|
|
41
29
|
rf64 = auto()
|
|
42
|
-
sd2 = auto()
|
|
43
|
-
voc = auto()
|
|
44
|
-
w64 = auto()
|
|
45
30
|
wav = auto()
|
|
46
|
-
|
|
31
|
+
|
|
32
|
+
_default = wav
|
|
47
33
|
|
|
48
34
|
|
|
49
35
|
class Subtype(StrEnum):
|
|
@@ -51,33 +37,15 @@ class Subtype(StrEnum):
|
|
|
51
37
|
alac_20 = auto()
|
|
52
38
|
alac_24 = auto()
|
|
53
39
|
alac_32 = auto()
|
|
54
|
-
alaw = auto()
|
|
55
40
|
double = auto()
|
|
56
|
-
dpcm_16 = auto()
|
|
57
|
-
dpcm_8 = auto()
|
|
58
|
-
dwvw_12 = auto()
|
|
59
|
-
dwvw_16 = auto()
|
|
60
|
-
dwvw_24 = auto()
|
|
61
|
-
dwvw_n = auto()
|
|
62
41
|
float = auto()
|
|
63
|
-
g721_32 = auto()
|
|
64
|
-
g723_24 = auto()
|
|
65
|
-
g723_40 = auto()
|
|
66
|
-
gsm610 = auto()
|
|
67
|
-
ima_adpcm = auto()
|
|
68
42
|
mpeg_layer_i = auto()
|
|
69
43
|
mpeg_layer_ii = auto()
|
|
70
44
|
mpeg_layer_iii = auto()
|
|
71
|
-
ms_adpcm = auto()
|
|
72
|
-
nms_adpcm_16 = auto()
|
|
73
|
-
nms_adpcm_24 = auto()
|
|
74
|
-
nms_adpcm_32 = auto()
|
|
75
45
|
opus = auto()
|
|
76
46
|
pcm_16 = auto()
|
|
77
47
|
pcm_24 = auto()
|
|
78
48
|
pcm_32 = auto()
|
|
79
49
|
pcm_s8 = auto()
|
|
80
50
|
pcm_u8 = auto()
|
|
81
|
-
ulaw = auto()
|
|
82
51
|
vorbis = auto()
|
|
83
|
-
vox_adpcm = auto()
|
recs/cfg/.DS_Store
ADDED
|
Binary file
|
recs/cfg/__init__.py
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
from .aliases import Aliases
|
|
2
2
|
from .cfg import Cfg
|
|
3
|
-
from .device import InputDevice
|
|
4
|
-
from .
|
|
3
|
+
from .device import InputDevice
|
|
4
|
+
from .file_source import FileSource
|
|
5
|
+
from .source import Source
|
|
5
6
|
from .track import Track
|
|
6
7
|
|
|
7
|
-
__all__ =
|
|
8
|
-
'Aliases',
|
|
9
|
-
'Cfg',
|
|
10
|
-
'InputDevice',
|
|
11
|
-
'InputDevices',
|
|
12
|
-
'InputStream',
|
|
13
|
-
'PathPattern',
|
|
14
|
-
'Track',
|
|
15
|
-
)
|
|
8
|
+
__all__ = 'Aliases', 'Cfg', 'FileSource', 'InputDevice', 'Source', 'Track'
|
recs/cfg/aliases.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import typing as t
|
|
2
2
|
|
|
3
|
+
from recs.base import RecsError, prefix_dict
|
|
3
4
|
from recs.base.prefix_dict import PrefixDict
|
|
4
|
-
from recs.base import prefix_dict, RecsError
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
from .
|
|
6
|
+
from .device import InputDevices
|
|
7
|
+
from .source import Source
|
|
8
8
|
from .track import Track
|
|
9
9
|
|
|
10
10
|
CHANNEL_SPLITTER = '+'
|
|
@@ -15,11 +15,9 @@ class Aliases:
|
|
|
15
15
|
|
|
16
16
|
def __init__(self, aliases: t.Sequence[str], devices: InputDevices) -> None:
|
|
17
17
|
self.tracks = PrefixDict()
|
|
18
|
-
|
|
19
|
-
assert devices
|
|
20
18
|
self.devices = devices
|
|
21
19
|
|
|
22
|
-
if not aliases:
|
|
20
|
+
if not (aliases and devices):
|
|
23
21
|
self.inv = {}
|
|
24
22
|
return
|
|
25
23
|
|
|
@@ -27,11 +25,11 @@ class Aliases:
|
|
|
27
25
|
alias, sep, value = (n.strip() for n in name.partition('='))
|
|
28
26
|
return alias, (value or alias)
|
|
29
27
|
|
|
30
|
-
names, values = zip(*(split(n) for n in aliases))
|
|
28
|
+
names, values = zip(*(split(n) for n in aliases), strict=False)
|
|
31
29
|
if len(set(names)) < len(names):
|
|
32
30
|
raise RecsError(f'Duplicate aliases: {aliases}')
|
|
33
31
|
|
|
34
|
-
self.tracks.update(sorted(zip(names, self.to_tracks(values))))
|
|
32
|
+
self.tracks.update(sorted(zip(names, self.to_tracks(values), strict=False)))
|
|
35
33
|
|
|
36
34
|
inv: dict[Track, list[str]] = {}
|
|
37
35
|
for k, v in self.tracks.items():
|
|
@@ -56,7 +54,7 @@ class Aliases:
|
|
|
56
54
|
if not errors:
|
|
57
55
|
return result
|
|
58
56
|
|
|
59
|
-
def err(k, v) -> str:
|
|
57
|
+
def err(k: str, v: list[str]) -> str:
|
|
60
58
|
s = 's' * (len(v) != 1)
|
|
61
59
|
return f'{k.capitalize()} device name{s}: {", ".join(v)}'
|
|
62
60
|
|
|
@@ -66,8 +64,8 @@ class Aliases:
|
|
|
66
64
|
|
|
67
65
|
raise RecsError('\n'.join([*errs, devices]))
|
|
68
66
|
|
|
69
|
-
def display_name(self, x:
|
|
70
|
-
if isinstance(x,
|
|
67
|
+
def display_name(self, x: Source | Track, short: bool = True) -> str:
|
|
68
|
+
if isinstance(x, Source):
|
|
71
69
|
return self.inv.get(Track(x), x.name)
|
|
72
70
|
|
|
73
71
|
default = x.name if short else str(x)
|
|
@@ -83,10 +81,8 @@ class Aliases:
|
|
|
83
81
|
try:
|
|
84
82
|
track = self.tracks[name]
|
|
85
83
|
except KeyError:
|
|
86
|
-
|
|
87
|
-
else:
|
|
88
|
-
if track.channels:
|
|
89
|
-
raise KeyError(track_name, 'impossible')
|
|
90
|
-
device = track.device
|
|
84
|
+
return Track(self.devices[name], channels)
|
|
91
85
|
|
|
92
|
-
|
|
86
|
+
if track.channels:
|
|
87
|
+
raise KeyError(track_name, 'impossible')
|
|
88
|
+
return Track(track.source, channels)
|