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/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 rich.table import Table
5
- from threa import HasThread, Runnable
4
+ from threa import HasThread, Runnables, IsThread
6
5
 
7
- from recs.base import RecsError, times
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 .device_recorder import DeviceRecorder
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
- self.device_tracks = device_tracks(cfg)
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.start_time = times.time()
22
+ self.state = FullState(tracks)
23
+ self.device_names = DeviceNames(cfg.sleep_time_device)
30
24
 
31
- tracks = self.device_tracks.values()
32
- self.device_recorders = tuple(DeviceRecorder(cfg, t) for t in tracks)
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
- for d in self.device_recorders:
35
- d.stopped.on_set.append(self.on_stopped)
32
+ self.runnables = self.device_names, *processes, live_thread, self.live
36
33
 
37
- self.live_thread = HasThread(self._live_thread)
34
+ def run_recorder(self) -> None:
35
+ with self:
36
+ while self.running:
37
+ self.receive()
38
38
 
39
- def run(self) -> None:
40
- self.start()
41
- self.live_thread.start()
42
- try:
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
- while self.running:
49
- times.sleep(self.cfg.sleep_time)
50
- finally:
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
- def rows(self) -> t.Iterator[dict[str, t.Any]]:
58
- yield {
59
- 'time': self.elapsed_time,
60
- 'recorded': self.recorded_time,
61
- 'file_size': self.file_size,
62
- 'file_count': self.file_count,
63
- }
64
- for v in self.device_recorders:
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 stop(self) -> None:
84
- self.running.clear()
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
@@ -8,7 +8,7 @@ def to_str(x: t.Any) -> str:
8
8
  if isinstance(x, str):
9
9
  return x
10
10
 
11
- assert isinstance(x, numbers.Real)
11
+ assert isinstance(x, numbers.Real), str(x)
12
12
  return f'{x:6.1%}'
13
13
 
14
14
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: recs
3
- Version: 0.2.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 (>=10.0,<11.0)
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 (>=0.4.15,<0.5.0)
24
- Requires-Dist: threa
25
- Requires-Dist: tomli (>=2.0.1,<3.0.0)
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 I plan to be able to record all streams of data in
64
- some coherent way.
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
- MIDI and DMX and I'll be folding that in in due time, but most of the difficulty
68
- and most of the value in this first step is the audio, so I have focused on just audio
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
- Use
79
-
80
- ### Usage
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.6.0
2
+ Generator: poetry-core 1.8.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
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))
@@ -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
@@ -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
- }
@@ -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,,
File without changes
File without changes