recs 0.3.1__tar.gz → 0.10.0__tar.gz

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.
Files changed (61) hide show
  1. {recs-0.3.1 → recs-0.10.0}/PKG-INFO +14 -14
  2. recs-0.10.0/pyproject.toml +67 -0
  3. recs-0.10.0/recs/.DS_Store +0 -0
  4. recs-0.10.0/recs/audio/.DS_Store +0 -0
  5. {recs-0.3.1 → recs-0.10.0}/recs/audio/block.py +6 -4
  6. recs-0.10.0/recs/audio/channel_writer.py +238 -0
  7. recs-0.10.0/recs/audio/file_opener.py +50 -0
  8. {recs-0.3.1 → recs-0.10.0}/recs/audio/header_size.py +7 -7
  9. recs-0.10.0/recs/base/.DS_Store +0 -0
  10. {recs-0.3.1 → recs-0.10.0}/recs/base/_query_device.py +3 -3
  11. {recs-0.3.1 → recs-0.10.0}/recs/base/cfg_raw.py +5 -4
  12. {recs-0.3.1 → recs-0.10.0}/recs/base/pyproject.py +1 -1
  13. {recs-0.3.1 → recs-0.10.0}/recs/base/type_conversions.py +0 -9
  14. {recs-0.3.1 → recs-0.10.0}/recs/base/types.py +2 -34
  15. recs-0.10.0/recs/cfg/.DS_Store +0 -0
  16. recs-0.10.0/recs/cfg/__init__.py +8 -0
  17. {recs-0.3.1 → recs-0.10.0}/recs/cfg/aliases.py +13 -17
  18. {recs-0.3.1 → recs-0.10.0}/recs/cfg/app.py +7 -5
  19. {recs-0.3.1 → recs-0.10.0}/recs/cfg/cfg.py +24 -12
  20. {recs-0.3.1 → recs-0.10.0}/recs/cfg/cli.py +37 -18
  21. {recs-0.3.1 → recs-0.10.0}/recs/cfg/device.py +30 -42
  22. recs-0.10.0/recs/cfg/file_source.py +61 -0
  23. {recs-0.3.1 → recs-0.10.0}/recs/cfg/hash_cmp.py +2 -2
  24. {recs-0.3.1 → recs-0.10.0}/recs/cfg/metadata.py +2 -5
  25. {recs-0.3.1 → recs-0.10.0}/recs/cfg/path_pattern.py +13 -5
  26. {recs-0.3.1 → recs-0.10.0}/recs/cfg/run_cli.py +8 -17
  27. recs-0.10.0/recs/cfg/source.py +42 -0
  28. {recs-0.3.1 → recs-0.10.0}/recs/cfg/time_settings.py +4 -1
  29. {recs-0.3.1 → recs-0.10.0}/recs/cfg/track.py +12 -9
  30. recs-0.10.0/recs/misc/.DS_Store +0 -0
  31. {recs-0.3.1 → recs-0.10.0}/recs/misc/contexts.py +1 -1
  32. {recs-0.3.1 → recs-0.10.0}/recs/misc/counter.py +12 -4
  33. {recs-0.3.1 → recs-0.10.0}/recs/misc/file_list.py +1 -1
  34. {recs-0.3.1 → recs-0.10.0}/recs/misc/log.py +5 -5
  35. recs-0.10.0/recs/ui/.DS_Store +0 -0
  36. recs-0.10.0/recs/ui/__init__.py +0 -0
  37. {recs-0.3.1 → recs-0.10.0}/recs/ui/full_state.py +11 -6
  38. {recs-0.3.1 → recs-0.10.0}/recs/ui/live.py +9 -9
  39. recs-0.10.0/recs/ui/recorder.py +65 -0
  40. recs-0.10.0/recs/ui/source_recorder.py +76 -0
  41. recs-0.3.1/recs/ui/device_tracks.py → recs-0.10.0/recs/ui/source_tracks.py +19 -17
  42. {recs-0.3.1 → recs-0.10.0}/recs/ui/table.py +2 -2
  43. recs-0.3.1/LICENSE +0 -21
  44. recs-0.3.1/pyproject.toml +0 -77
  45. recs-0.3.1/recs/audio/channel_writer.py +0 -194
  46. recs-0.3.1/recs/audio/file_opener.py +0 -48
  47. recs-0.3.1/recs/cfg/__init__.py +0 -15
  48. recs-0.3.1/recs/misc/__init__.py +0 -1
  49. recs-0.3.1/recs/ui/device_process.py +0 -37
  50. recs-0.3.1/recs/ui/device_recorder.py +0 -83
  51. recs-0.3.1/recs/ui/recorder.py +0 -74
  52. {recs-0.3.1 → recs-0.10.0}/README.md +0 -0
  53. {recs-0.3.1 → recs-0.10.0}/recs/__init__.py +0 -0
  54. {recs-0.3.1 → recs-0.10.0}/recs/__main__.py +0 -0
  55. {recs-0.3.1 → recs-0.10.0}/recs/audio/__init__.py +0 -0
  56. {recs-0.3.1 → recs-0.10.0}/recs/base/__init__.py +0 -0
  57. {recs-0.3.1 → recs-0.10.0}/recs/base/prefix_dict.py +0 -0
  58. {recs-0.3.1 → recs-0.10.0}/recs/base/state.py +0 -0
  59. {recs-0.3.1 → recs-0.10.0}/recs/base/times.py +0 -0
  60. {recs-0.3.1/recs/ui → recs-0.10.0/recs/misc}/__init__.py +0 -0
  61. {recs-0.3.1 → recs-0.10.0}/recs/misc/legal_filename.py +0 -0
@@ -1,30 +1,31 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: recs
3
- Version: 0.3.1
4
- Summary: 🎙 recs: the Universal Recorder 🎙
5
- License: MIT
3
+ Version: 0.10.0
4
+ Summary: 🎙 The Universal Recorder 🎙
6
5
  Author: Tom Ritchford
7
- Author-email: tom@swirly.com
8
- Requires-Python: >=3.10
9
- Classifier: License :: OSI Approved :: MIT License
6
+ Author-email: Tom Ritchford <tom@swirly.com>
7
+ License-Expression: MIT
10
8
  Classifier: Programming Language :: Python :: 3
11
9
  Classifier: Programming Language :: Python :: 3.10
12
10
  Classifier: Programming Language :: Python :: 3.11
13
11
  Classifier: Programming Language :: Python :: 3.12
14
- Requires-Dist: coverage
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
15
14
  Requires-Dist: dtyper
16
- Requires-Dist: humanfriendly
17
- Requires-Dist: impall
18
15
  Requires-Dist: numpy
19
- Requires-Dist: overrides
20
16
  Requires-Dist: pyaudio
21
17
  Requires-Dist: rich
22
18
  Requires-Dist: sounddevice
23
19
  Requires-Dist: soundfile
20
+ Requires-Dist: threa>=1.9.0
21
+ Requires-Dist: typer
22
+ Requires-Dist: impall
23
+ Requires-Dist: overrides
24
+ Requires-Dist: coverage
24
25
  Requires-Dist: strenum
25
- Requires-Dist: threa (>=1.9.0)
26
+ Requires-Dist: humanfriendly
26
27
  Requires-Dist: tomli
27
- Requires-Dist: typer
28
+ Requires-Python: >=3.10
28
29
  Description-Content-Type: text/markdown
29
30
 
30
31
  # 🎬 recs: the Universal Recorder 🎬
@@ -138,4 +139,3 @@ detect if a device goes offline and report it.
138
139
  The holy grail is reconnecting to a device that comes back online: this is an
139
140
  [unsolved problem](https://github.com/spatialaudio/python-sounddevice/issues/382)
140
141
  in Python, I believe, but I am on my way to solving it.
141
-
@@ -0,0 +1,67 @@
1
+ [project]
2
+ name = "recs"
3
+ version = "0.10.0"
4
+ description = "🎙 The Universal Recorder 🎙"
5
+ authors = [{ name = "Tom Ritchford", email = "tom@swirly.com" }]
6
+ requires-python = ">=3.10"
7
+ readme = "README.md"
8
+ license = "MIT"
9
+ classifiers = ["Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14"]
10
+ dependencies = [
11
+ "dtyper",
12
+ "numpy",
13
+ "pyaudio",
14
+ "rich",
15
+ "sounddevice",
16
+ "soundfile",
17
+ "threa>=1.9.0",
18
+ "typer",
19
+ "impall",
20
+ "overrides",
21
+ "coverage",
22
+ "strenum",
23
+ "humanfriendly",
24
+ "tomli",
25
+ ]
26
+
27
+ [project.scripts]
28
+ recs = "recs.base.cli:run"
29
+
30
+ [dependency-groups]
31
+ dev = [
32
+ "coverage>=7.4.1",
33
+ "pytest",
34
+ "pyupgrade>=3.21.2",
35
+ "ruff",
36
+ "tdir",
37
+ "ty>=0.0.14",
38
+ ]
39
+
40
+ [tool.uv]
41
+
42
+ [tool.uv.build-backend]
43
+ module-root = ""
44
+
45
+ [build-system]
46
+ requires = ["uv_build>=0.9.0,<0.10.0"]
47
+ build-backend = "uv_build"
48
+
49
+ [tool.coverage.run]
50
+ branch = true
51
+ source = ["recs"]
52
+ omit = [
53
+ "recs/__main__.py",
54
+ "recs/cfg/cli.py",
55
+ "recs/cfg/app.py",
56
+ ]
57
+
58
+ [tool.coverage.report]
59
+ fail_under = 91
60
+ skip_covered = true
61
+ exclude_lines = ["pragma: no cover", "if False:", "if __name__ == .__main__.:", "raise NotImplementedError"]
62
+
63
+ [tool.ruff]
64
+ line-length = 88
65
+
66
+ [tool.ruff.format]
67
+ quote-style = "single"
Binary file
Binary file
@@ -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
@@ -0,0 +1,238 @@
1
+ import contextlib
2
+ import sys
3
+ import typing as t
4
+ from datetime import datetime
5
+ from pathlib import Path
6
+ from threading import Lock
7
+
8
+ from numpy.typing import NDArray
9
+ from overrides import override
10
+ from soundfile import SoundFile
11
+ from threa import Runnable
12
+
13
+ from recs.base.state import ChannelState
14
+ from recs.base.type_conversions import SUBTYPE_TO_SDTYPE
15
+ from recs.base.types import SDTYPE, Active, Format, SdType
16
+ from recs.cfg import Cfg, Track, time_settings
17
+ from recs.misc import counter, file_list
18
+
19
+ from .block import Block, Blocks
20
+ from .file_opener import FileOpener
21
+ from .header_size import header_size
22
+
23
+ URL = 'https://github.com/rec/recs'
24
+
25
+ BUFFER = 0x80
26
+ MAX_WAV_SIZE = 0x1_0000_0000 - BUFFER
27
+
28
+ ITEMSIZE = {
29
+ SdType.float32: 4,
30
+ SdType.int16: 2,
31
+ SdType.int32: 4,
32
+ }
33
+
34
+ BLOCK_FUZZ = 2
35
+
36
+
37
+ class ChannelWriter(Runnable):
38
+ bytes_in_file: int = 0
39
+
40
+ frames_in_file: int = 0
41
+ frames_written: int = 0 # Used elsewhere
42
+
43
+ largest_file_size: int = 0
44
+ longest_file_frames: int = 0
45
+
46
+ timestamp: float = 0
47
+
48
+ _sfs: t.Sequence[SoundFile] = ()
49
+
50
+ @property
51
+ def active(self) -> Active:
52
+ return Active.active if self._sfs else Active.inactive
53
+
54
+ def __init__(
55
+ self, cfg: Cfg, times: time_settings.TimeSettings[int], track: Track
56
+ ) -> None:
57
+ super().__init__()
58
+
59
+ self.cfg = cfg
60
+ self.do_not_record = cfg.dry_run or cfg.calibrate
61
+ self.metadata = cfg.metadata
62
+ self.times = times
63
+ self.track = track
64
+
65
+ self._blocks = Blocks()
66
+ self._lock = Lock()
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
+
83
+ self.files_written = file_list.FileList()
84
+ self.frame_size = ITEMSIZE[sdtype] * len(track.channels)
85
+ self.longest_file_frames = times.longest_file_time
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
+ ]
96
+ self._volume = counter.MovingBlock(times.moving_average_time)
97
+
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])
107
+
108
+ def receive_update(
109
+ self, block: Block, timestamp: float, should_record: bool = False
110
+ ) -> ChannelState:
111
+ with self._lock:
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
+ )
120
+
121
+ @override
122
+ def stop(self) -> None:
123
+ with self._lock:
124
+ self.running = False
125
+ self._write_and_close()
126
+ self.stopped = True
127
+
128
+ def _close(self) -> None:
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)
133
+ sf.close()
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()
143
+ index = 1 + len(self.files_written)
144
+ metadata = {'date': date, 'software': URL, 'tracknumber': str(index)}
145
+ metadata |= self.metadata
146
+
147
+ self.bytes_in_file = max(header_size(metadata, f) for f in self.cfg.formats)
148
+ self.frames_in_file = 0
149
+
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
156
+
157
+ def _receive_block(
158
+ self, block: Block, timestamp: float, should_record: bool
159
+ ) -> ChannelState:
160
+ saved_state = self._state(
161
+ max_amp=max(block.max) / block.scale,
162
+ min_amp=min(block.min) / block.scale,
163
+ )
164
+
165
+ dt = self.timestamp - timestamp
166
+ self.timestamp = timestamp
167
+ self._volume(block)
168
+
169
+ if not self.do_not_record and (self._sfs or not self.stopped):
170
+ expected_dt = len(block) / self.track.source.samplerate
171
+
172
+ if dt > expected_dt * BLOCK_FUZZ: # We were asleep, or otherwise lost time
173
+ self._write_and_close()
174
+
175
+ self._blocks.append(block)
176
+
177
+ if should_record:
178
+ if not self._sfs: # Record some quiet before the first block
179
+ length = self.times.quiet_before_start + len(self._blocks[-1])
180
+ self._blocks.clip(length, from_start=True)
181
+
182
+ self._write_blocks(self._blocks)
183
+ self._blocks.clear()
184
+
185
+ if self.stopped or self._blocks.duration > self.times.stop_after_quiet:
186
+ self._write_and_close()
187
+
188
+ return self._state() - saved_state
189
+
190
+ def _state(self, **kwargs: t.Any) -> ChannelState:
191
+ return ChannelState(
192
+ file_count=len(self.files_written),
193
+ file_size=self.files_written.total_size,
194
+ is_active=bool(self._sfs),
195
+ recorded_time=self.frames_written / self.track.source.samplerate,
196
+ timestamp=self.timestamp,
197
+ volume=tuple(self._volume.mean()),
198
+ **kwargs,
199
+ )
200
+
201
+ def _write_and_close(self) -> None:
202
+ # Record some quiet after the last block
203
+ removed = self._blocks.clip(self.times.quiet_after_end, from_start=False)
204
+
205
+ if self._sfs and removed:
206
+ self._write_blocks(reversed(removed))
207
+
208
+ self._close()
209
+
210
+ def _write_blocks(self, blox: t.Iterable[Block]) -> None:
211
+ blocks = list(blox)
212
+
213
+ # The last block in the list ends at self.timestamp so
214
+ # we keep track of the sample offset before that
215
+ offset = -sum(len(b) for b in blocks)
216
+
217
+ for b in blocks:
218
+ # Check if this block will overrun the file size or length
219
+ remains: list[int] = []
220
+
221
+ if self.longest_file_frames:
222
+ remains.append(self.longest_file_frames - self.frames_in_file)
223
+
224
+ if self._sfs and self.largest_file_size:
225
+ file_bytes = self.largest_file_size - self.bytes_in_file
226
+ remains.append(file_bytes // self.frame_size)
227
+
228
+ if remains and min(remains) <= len(b):
229
+ self._close()
230
+
231
+ self._sfs = self._sfs or self._open(offset)
232
+ for sf in self._sfs:
233
+ sf.write(b.block)
234
+ offset += len(b)
235
+
236
+ self.frames_in_file += len(b)
237
+ self.frames_written += len(b)
238
+ self.bytes_in_file += len(b) * self.frame_size
@@ -0,0 +1,50 @@
1
+ import dataclasses as dc
2
+ import itertools
3
+ import typing as t
4
+ from pathlib import Path
5
+
6
+ import soundfile
7
+
8
+ from recs.base.types import Format, Subtype
9
+ from recs.cfg.metadata import ALLOWS_METADATA
10
+
11
+
12
+ @dc.dataclass
13
+ class FileOpener:
14
+ format: Format
15
+ channels: int = 1
16
+ samplerate: int = 48_000
17
+ subtype: Subtype | None = None
18
+
19
+ def open(
20
+ self, path: Path | str, metadata: t.Mapping[str, str], overwrite: bool = False
21
+ ) -> soundfile.SoundFile:
22
+ path = Path(path).with_suffix('.' + self.format)
23
+ if not overwrite and path.exists():
24
+ raise FileExistsError(str(path))
25
+
26
+ fp = soundfile.SoundFile(
27
+ channels=self.channels,
28
+ file=path,
29
+ format=self.format,
30
+ mode='w',
31
+ samplerate=self.samplerate,
32
+ subtype=self.subtype,
33
+ )
34
+
35
+ if self.format in ALLOWS_METADATA:
36
+ for k, v in metadata.items():
37
+ setattr(fp, k, v)
38
+
39
+ return fp
40
+
41
+ def create(self, metadata: t.Mapping[str, str], path: Path) -> soundfile.SoundFile:
42
+ path.parent.mkdir(exist_ok=True, parents=True)
43
+
44
+ for i in itertools.count():
45
+ f = path.parent / (path.name + bool(i) * f'_{i}')
46
+ try:
47
+ return self.open(f, metadata)
48
+ except FileExistsError:
49
+ pass
50
+ raise FileNotFoundError
@@ -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: dict[str, str], format: Format) -> int:
5
- tag = 9
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
 
Binary file
@@ -8,14 +8,14 @@ import json
8
8
  import typing as t
9
9
 
10
10
 
11
- def query_devices() -> list[dict[str, t.Any]]:
11
+ def _query_devices() -> t.Any:
12
12
  try:
13
13
  import sounddevice
14
14
 
15
15
  return sounddevice.query_devices()
16
- except Exception:
16
+ except BaseException:
17
17
  return []
18
18
 
19
19
 
20
20
  if __name__ == '__main__':
21
- print(json.dumps(query_devices(), indent=4))
21
+ print(json.dumps(_query_devices(), indent=4))
@@ -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
- path: str = ''
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
- format: str = Format.flac
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
@@ -7,7 +7,7 @@ assert PYPROJECT.exists()
7
7
 
8
8
 
9
9
  def message() -> str:
10
- p = tomli.loads(PYPROJECT.read_text())['tool']['poetry']
10
+ p = tomli.loads(PYPROJECT.read_text())['project']
11
11
  desc, name = p['description'], p['name']
12
12
 
13
13
  icon, *d, icon2 = desc.split()
@@ -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,
@@ -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
- wavex = auto()
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()
Binary file
@@ -0,0 +1,8 @@
1
+ from .aliases import Aliases
2
+ from .cfg import Cfg
3
+ from .device import InputDevice
4
+ from .file_source import FileSource
5
+ from .source import Source
6
+ from .track import Track
7
+
8
+ __all__ = 'Aliases', 'Cfg', 'FileSource', 'InputDevice', 'Source', 'Track'