piwave 2.1.2__tar.gz → 2.1.4__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.
- {piwave-2.1.2 → piwave-2.1.4}/PKG-INFO +14 -3
- {piwave-2.1.2 → piwave-2.1.4}/README.md +12 -2
- {piwave-2.1.2 → piwave-2.1.4}/piwave/__main__.py +9 -0
- {piwave-2.1.2 → piwave-2.1.4}/piwave/backends/base.py +8 -3
- {piwave-2.1.2 → piwave-2.1.4}/piwave/backends/fm_transmitter.py +5 -1
- {piwave-2.1.2 → piwave-2.1.4}/piwave/backends/pi_fm_rds.py +6 -2
- piwave-2.1.4/piwave/logger.py +46 -0
- {piwave-2.1.2 → piwave-2.1.4}/piwave/piwave.py +24 -35
- {piwave-2.1.2 → piwave-2.1.4}/piwave.egg-info/PKG-INFO +14 -3
- {piwave-2.1.2 → piwave-2.1.4}/piwave.egg-info/requires.txt +1 -0
- {piwave-2.1.2 → piwave-2.1.4}/setup.cfg +2 -1
- piwave-2.1.2/piwave/logger.py +0 -99
- {piwave-2.1.2 → piwave-2.1.4}/LICENSE +0 -0
- {piwave-2.1.2 → piwave-2.1.4}/piwave/__init__.py +0 -0
- {piwave-2.1.2 → piwave-2.1.4}/piwave/backends/__init__.py +0 -0
- {piwave-2.1.2 → piwave-2.1.4}/piwave.egg-info/SOURCES.txt +0 -0
- {piwave-2.1.2 → piwave-2.1.4}/piwave.egg-info/dependency_links.txt +0 -0
- {piwave-2.1.2 → piwave-2.1.4}/piwave.egg-info/top_level.txt +0 -0
- {piwave-2.1.2 → piwave-2.1.4}/pyproject.toml +0 -0
- {piwave-2.1.2 → piwave-2.1.4}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: piwave
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.4
|
|
4
4
|
Summary: A python module to broadcast radio waves with your Raspberry Pi.
|
|
5
5
|
Home-page: https://github.com/douxxtech/piwave
|
|
6
6
|
Author: Douxx
|
|
@@ -18,6 +18,7 @@ Classifier: Operating System :: POSIX :: Linux
|
|
|
18
18
|
Requires-Python: >=3.7
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
|
+
Requires-Dist: dlogger==1.0.1
|
|
21
22
|
Provides-Extra: dev
|
|
22
23
|
Requires-Dist: pytest>=6.0; extra == "dev"
|
|
23
24
|
Requires-Dist: pytest-cov>=2.0; extra == "dev"
|
|
@@ -518,8 +519,11 @@ status = pw.get_status()
|
|
|
518
519
|
##### `cleanup()`
|
|
519
520
|
Clean up resources and temporary files.
|
|
520
521
|
|
|
522
|
+
> [!WARNING]
|
|
523
|
+
> Always clean up behind you! Dropped support of auto-cleanup on version > 2.1.2
|
|
524
|
+
|
|
521
525
|
```python
|
|
522
|
-
pw.cleanup()
|
|
526
|
+
pw.cleanup()
|
|
523
527
|
```
|
|
524
528
|
|
|
525
529
|
### Backend Management
|
|
@@ -611,6 +615,10 @@ class CustomBackend(Backend):
|
|
|
611
615
|
@property
|
|
612
616
|
def supports_rds(self):
|
|
613
617
|
return True # RDS capability
|
|
618
|
+
|
|
619
|
+
@property
|
|
620
|
+
def supports_loop(self):
|
|
621
|
+
return True # loop support capability
|
|
614
622
|
|
|
615
623
|
def _get_executable_name(self):
|
|
616
624
|
return "my_transmitter"
|
|
@@ -618,10 +626,13 @@ class CustomBackend(Backend):
|
|
|
618
626
|
def _get_search_paths(self):
|
|
619
627
|
return ["/opt", "/usr/local/bin", "/usr/bin"]
|
|
620
628
|
|
|
621
|
-
def build_command(self, wav_file: str):
|
|
629
|
+
def build_command(self, wav_file: str, loop: bool):
|
|
622
630
|
cmd = ['sudo', self.required_executable, '-f', str(self.frequency)]
|
|
623
631
|
if self.supports_rds and self.ps:
|
|
624
632
|
cmd.extend(['-ps', self.ps])
|
|
633
|
+
|
|
634
|
+
if self.supports_rds and loop:
|
|
635
|
+
cmd.extend(['-loop'])
|
|
625
636
|
cmd.append(wav_file)
|
|
626
637
|
return cmd
|
|
627
638
|
```
|
|
@@ -491,8 +491,11 @@ status = pw.get_status()
|
|
|
491
491
|
##### `cleanup()`
|
|
492
492
|
Clean up resources and temporary files.
|
|
493
493
|
|
|
494
|
+
> [!WARNING]
|
|
495
|
+
> Always clean up behind you! Dropped support of auto-cleanup on version > 2.1.2
|
|
496
|
+
|
|
494
497
|
```python
|
|
495
|
-
pw.cleanup()
|
|
498
|
+
pw.cleanup()
|
|
496
499
|
```
|
|
497
500
|
|
|
498
501
|
### Backend Management
|
|
@@ -584,6 +587,10 @@ class CustomBackend(Backend):
|
|
|
584
587
|
@property
|
|
585
588
|
def supports_rds(self):
|
|
586
589
|
return True # RDS capability
|
|
590
|
+
|
|
591
|
+
@property
|
|
592
|
+
def supports_loop(self):
|
|
593
|
+
return True # loop support capability
|
|
587
594
|
|
|
588
595
|
def _get_executable_name(self):
|
|
589
596
|
return "my_transmitter"
|
|
@@ -591,10 +598,13 @@ class CustomBackend(Backend):
|
|
|
591
598
|
def _get_search_paths(self):
|
|
592
599
|
return ["/opt", "/usr/local/bin", "/usr/bin"]
|
|
593
600
|
|
|
594
|
-
def build_command(self, wav_file: str):
|
|
601
|
+
def build_command(self, wav_file: str, loop: bool):
|
|
595
602
|
cmd = ['sudo', self.required_executable, '-f', str(self.frequency)]
|
|
596
603
|
if self.supports_rds and self.ps:
|
|
597
604
|
cmd.extend(['-ps', self.ps])
|
|
605
|
+
|
|
606
|
+
if self.supports_rds and loop:
|
|
607
|
+
cmd.extend(['-loop'])
|
|
598
608
|
cmd.append(wav_file)
|
|
599
609
|
return cmd
|
|
600
610
|
```
|
|
@@ -72,6 +72,7 @@ def main():
|
|
|
72
72
|
|
|
73
73
|
args = parser.parse_args(sys.argv[2:])
|
|
74
74
|
|
|
75
|
+
pw = None
|
|
75
76
|
try:
|
|
76
77
|
pw = PiWave(
|
|
77
78
|
frequency=args.frequency,
|
|
@@ -92,9 +93,17 @@ def main():
|
|
|
92
93
|
except KeyboardInterrupt:
|
|
93
94
|
pw.stop()
|
|
94
95
|
Log.info("Broadcast stopped")
|
|
96
|
+
raise
|
|
97
|
+
|
|
95
98
|
except PiWaveError as e:
|
|
96
99
|
Log.error(f"PiWaveError: {e}")
|
|
97
100
|
sys.exit(1)
|
|
101
|
+
except Exception:
|
|
102
|
+
pass
|
|
103
|
+
finally:
|
|
104
|
+
if pw is not None:
|
|
105
|
+
pw.cleanup()
|
|
106
|
+
|
|
98
107
|
|
|
99
108
|
else:
|
|
100
109
|
Log.error(f"Unknown command: {cmd}")
|
|
@@ -43,6 +43,11 @@ class Backend(ABC):
|
|
|
43
43
|
@abstractmethod
|
|
44
44
|
def supports_live_streaming(self):
|
|
45
45
|
pass
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
@abstractmethod
|
|
49
|
+
def supports_loop(self):
|
|
50
|
+
pass
|
|
46
51
|
|
|
47
52
|
@property
|
|
48
53
|
def cache_file(self):
|
|
@@ -208,7 +213,7 @@ class Backend(ABC):
|
|
|
208
213
|
return False
|
|
209
214
|
|
|
210
215
|
@abstractmethod
|
|
211
|
-
def build_command(self, wav_file: str):
|
|
216
|
+
def build_command(self, wav_file: str, loop: bool):
|
|
212
217
|
pass
|
|
213
218
|
|
|
214
219
|
@abstractmethod
|
|
@@ -219,12 +224,12 @@ class Backend(ABC):
|
|
|
219
224
|
min_freq, max_freq = self.frequency_range
|
|
220
225
|
return min_freq <= self.frequency <= max_freq
|
|
221
226
|
|
|
222
|
-
def play_file(self, wav_file: str) -> subprocess.Popen:
|
|
227
|
+
def play_file(self, wav_file: str, loop: bool) -> subprocess.Popen:
|
|
223
228
|
if not self.validate_settings():
|
|
224
229
|
min_freq, max_freq = self.frequency_range
|
|
225
230
|
raise BackendError(f"{self.name} supports {min_freq}-{max_freq}MHz, got {self.frequency}MHz")
|
|
226
231
|
|
|
227
|
-
cmd = self.build_command(wav_file)
|
|
232
|
+
cmd = self.build_command(wav_file, loop)
|
|
228
233
|
self.current_process = subprocess.Popen(
|
|
229
234
|
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
230
235
|
preexec_fn=os.setsid
|
|
@@ -23,13 +23,17 @@ class FmTransmitterBackend(Backend):
|
|
|
23
23
|
def supports_live_streaming(self):
|
|
24
24
|
return True
|
|
25
25
|
|
|
26
|
+
@property
|
|
27
|
+
def supports_loop(self):
|
|
28
|
+
return False
|
|
29
|
+
|
|
26
30
|
def _get_executable_name(self):
|
|
27
31
|
return "fm_transmitter"
|
|
28
32
|
|
|
29
33
|
def _get_search_paths(self):
|
|
30
34
|
return ["/opt/PiWave/fm_transmitter", "/opt", "/usr/local/bin", "/usr/bin", "/bin", "/home"]
|
|
31
35
|
|
|
32
|
-
def build_command(self, wav_file: str):
|
|
36
|
+
def build_command(self, wav_file: str, loop: bool):
|
|
33
37
|
return [
|
|
34
38
|
'sudo', self.required_executable,
|
|
35
39
|
'-f', str(self.frequency),
|
|
@@ -13,7 +13,7 @@ class PiFmRdsBackend(Backend):
|
|
|
13
13
|
|
|
14
14
|
@property
|
|
15
15
|
def frequency_range(self):
|
|
16
|
-
return (
|
|
16
|
+
return (76.0, 108.0) # taken from pi_fm_rds.c
|
|
17
17
|
|
|
18
18
|
@property
|
|
19
19
|
def supports_rds(self):
|
|
@@ -23,13 +23,17 @@ class PiFmRdsBackend(Backend):
|
|
|
23
23
|
def supports_live_streaming(self):
|
|
24
24
|
return False
|
|
25
25
|
|
|
26
|
+
@property
|
|
27
|
+
def supports_loop(self):
|
|
28
|
+
return False
|
|
29
|
+
|
|
26
30
|
def _get_executable_name(self):
|
|
27
31
|
return "pi_fm_rds"
|
|
28
32
|
|
|
29
33
|
def _get_search_paths(self):
|
|
30
34
|
return ["/opt/PiWave/PiFmRds", "/opt", "/usr/local/bin", "/usr/bin", "/bin", "/home"]
|
|
31
35
|
|
|
32
|
-
def build_command(self, wav_file: str) -> list:
|
|
36
|
+
def build_command(self, wav_file: str, loop: bool) -> list:
|
|
33
37
|
cmd = [
|
|
34
38
|
'sudo', self.required_executable,
|
|
35
39
|
'-freq', str(self.frequency),
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# PiWave is available at https://piwave.xyz
|
|
2
|
+
# Licensed under GPLv3.0, main GitHub repository at https://github.com/douxxtech/piwave/
|
|
3
|
+
# piwave/Logger.py : Main logging manage
|
|
4
|
+
|
|
5
|
+
from dlogger import DLogger
|
|
6
|
+
|
|
7
|
+
class Logger(DLogger):
|
|
8
|
+
|
|
9
|
+
ICONS = {
|
|
10
|
+
'success': 'OK',
|
|
11
|
+
'error': 'ERR',
|
|
12
|
+
'warning': 'WARN',
|
|
13
|
+
'info': 'INFO',
|
|
14
|
+
'file': 'FILE',
|
|
15
|
+
'broadcast': 'BCAST'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
STYLES = {
|
|
19
|
+
'success': 'bright_green',
|
|
20
|
+
'error': 'bright_red',
|
|
21
|
+
'warning': 'bright_yellow',
|
|
22
|
+
'info': 'bright_cyan',
|
|
23
|
+
'file': 'yellow',
|
|
24
|
+
'broadcast': 'bright_magenta'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
SILENT = False
|
|
28
|
+
|
|
29
|
+
def __init__(self):
|
|
30
|
+
# Initialize with prebuilt icons & styles and silent support.
|
|
31
|
+
|
|
32
|
+
super().__init__(
|
|
33
|
+
icons=self.ICONS,
|
|
34
|
+
styles=self.STYLES
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def config(self, silent: bool = False):
|
|
39
|
+
self.SILENT = silent
|
|
40
|
+
|
|
41
|
+
def print(self, message: str, style: str = '', icon: str = '', end: str = '\n'):
|
|
42
|
+
if self.SILENT:
|
|
43
|
+
return
|
|
44
|
+
super().print(message, style, icon, end)
|
|
45
|
+
|
|
46
|
+
Log = Logger()
|
|
@@ -13,10 +13,8 @@ import shutil
|
|
|
13
13
|
import queue
|
|
14
14
|
from typing import Optional, Callable
|
|
15
15
|
from pathlib import Path
|
|
16
|
-
from urllib.parse import urlparse
|
|
17
|
-
import atexit
|
|
18
16
|
|
|
19
|
-
from .backends import discover_backends, backends, get_best_backend
|
|
17
|
+
from .backends import discover_backends, backends, get_best_backend
|
|
20
18
|
from .logger import Log
|
|
21
19
|
|
|
22
20
|
class PiWaveError(Exception):
|
|
@@ -90,7 +88,6 @@ class PiWave:
|
|
|
90
88
|
Log.config(silent=silent)
|
|
91
89
|
|
|
92
90
|
self._validate_environment()
|
|
93
|
-
atexit.register(self.cleanup)
|
|
94
91
|
|
|
95
92
|
discover_backends()
|
|
96
93
|
|
|
@@ -215,7 +212,7 @@ class PiWave:
|
|
|
215
212
|
if self._is_wav_file(filepath):
|
|
216
213
|
return filepath
|
|
217
214
|
|
|
218
|
-
Log.
|
|
215
|
+
Log.file(f"Converting {filepath} to WAV")
|
|
219
216
|
|
|
220
217
|
output_file = f"{os.path.splitext(filepath)[0]}_converted.wav"
|
|
221
218
|
|
|
@@ -268,35 +265,37 @@ class PiWave:
|
|
|
268
265
|
def _play_file(self, wav_file: str) -> bool:
|
|
269
266
|
if self.stop_event.is_set():
|
|
270
267
|
return False
|
|
271
|
-
|
|
268
|
+
|
|
272
269
|
duration = self._get_file_duration(wav_file)
|
|
270
|
+
|
|
273
271
|
if duration <= 0:
|
|
274
272
|
Log.error(f"Could not determine duration for {wav_file}")
|
|
275
273
|
return False
|
|
276
|
-
|
|
274
|
+
|
|
277
275
|
try:
|
|
278
276
|
# update settings
|
|
279
277
|
self.backend.frequency = self.frequency
|
|
280
278
|
self.backend.ps = self.ps
|
|
281
279
|
self.backend.rt = self.rt
|
|
282
280
|
self.backend.pi = self.pi
|
|
283
|
-
|
|
281
|
+
|
|
284
282
|
# validate frequency
|
|
285
283
|
min_freq, max_freq = self.backend.frequency_range
|
|
286
284
|
if not (min_freq <= self.frequency <= max_freq):
|
|
287
285
|
raise PiWaveError(f"Current backend '{self.backend.name}' doesn't support {self.frequency}MHz (supports {min_freq}-{max_freq}MHz). Use update() to change backend or frequency.")
|
|
288
|
-
|
|
289
|
-
|
|
286
|
+
|
|
290
287
|
loop_status = "looping" if self.loop else f"Duration: {duration:.1f}s"
|
|
291
288
|
rds_info = f" (PS: {self.ps})" if self.backend.supports_rds and self.ps else ""
|
|
292
|
-
Log.
|
|
293
|
-
|
|
294
|
-
self.current_process = self.backend.play_file(wav_file)
|
|
289
|
+
Log.broadcast(f"Playing {wav_file} ({loop_status}) at {self.frequency}MHz{rds_info}")
|
|
290
|
+
|
|
291
|
+
self.current_process = self.backend.play_file(wav_file, self.loop)
|
|
295
292
|
|
|
296
293
|
if self.on_track_change:
|
|
297
294
|
self.on_track_change(wav_file)
|
|
298
295
|
|
|
299
|
-
if self.loop:
|
|
296
|
+
if self.loop and not self.backend.supports_loop:
|
|
297
|
+
|
|
298
|
+
# Only manually loop if the backend does not support it
|
|
300
299
|
while not self.stop_event.is_set():
|
|
301
300
|
if self.stop_event.wait(timeout=0.1):
|
|
302
301
|
self._stop_current_process()
|
|
@@ -305,26 +304,24 @@ class PiWave:
|
|
|
305
304
|
if self.current_process.poll() is not None:
|
|
306
305
|
Log.error("Process ended unexpectedly while looping")
|
|
307
306
|
return False
|
|
307
|
+
|
|
308
308
|
else:
|
|
309
|
-
|
|
310
|
-
while
|
|
309
|
+
# fi backend supports looping or we are not looping, just wait for the process to finish
|
|
310
|
+
while not self.stop_event.is_set():
|
|
311
311
|
if self.stop_event.wait(timeout=0.1):
|
|
312
312
|
self._stop_current_process()
|
|
313
313
|
return False
|
|
314
|
-
|
|
315
|
-
elapsed = time.time() - start_time
|
|
316
|
-
if elapsed >= duration:
|
|
317
|
-
self._stop_current_process()
|
|
314
|
+
if not self.loop and self.current_process.poll() is not None:
|
|
318
315
|
break
|
|
319
|
-
|
|
320
316
|
return True
|
|
321
|
-
|
|
317
|
+
|
|
322
318
|
except Exception as e:
|
|
323
319
|
Log.error(f"Error playing {wav_file}: {e}")
|
|
324
320
|
if self.on_error:
|
|
325
321
|
self.on_error(e)
|
|
326
322
|
self._stop_current_process()
|
|
327
323
|
return False
|
|
324
|
+
|
|
328
325
|
|
|
329
326
|
def _playback_worker_wrapper(self):
|
|
330
327
|
# wrapper for non-blocking playback
|
|
@@ -398,7 +395,7 @@ class PiWave:
|
|
|
398
395
|
consumer_thread.daemon = True
|
|
399
396
|
consumer_thread.start()
|
|
400
397
|
|
|
401
|
-
Log.
|
|
398
|
+
Log.broadcast(f"Live streaming at {self.frequency}MHz ({sample_rate}Hz, {channels}ch)")
|
|
402
399
|
return True
|
|
403
400
|
|
|
404
401
|
def _live_producer_worker(self, audio_source, chunk_size: int):
|
|
@@ -465,11 +462,9 @@ class PiWave:
|
|
|
465
462
|
def _stop_current_process(self):
|
|
466
463
|
if self.current_process:
|
|
467
464
|
try:
|
|
468
|
-
Log.info("Stopping current process...")
|
|
469
465
|
os.killpg(os.getpgid(self.current_process.pid), signal.SIGTERM)
|
|
470
466
|
self.current_process.wait(timeout=5)
|
|
471
467
|
except (ProcessLookupError, subprocess.TimeoutExpired):
|
|
472
|
-
Log.warning("Forcing kill of current process")
|
|
473
468
|
try:
|
|
474
469
|
os.killpg(os.getpgid(self.current_process.pid), signal.SIGKILL)
|
|
475
470
|
except ProcessLookupError:
|
|
@@ -505,11 +500,6 @@ class PiWave:
|
|
|
505
500
|
self._log_debug("Playback worker finished")
|
|
506
501
|
|
|
507
502
|
|
|
508
|
-
def _handle_interrupt(self, signum, frame):
|
|
509
|
-
Log.warning("Interrupt received, stopping playback...")
|
|
510
|
-
self.stop()
|
|
511
|
-
os._exit(0)
|
|
512
|
-
|
|
513
503
|
def play(self, source, sample_rate: int = 44100, channels: int = 2, chunk_size: int = 4096, blocking: bool = False):
|
|
514
504
|
"""Play audio from file or live source.
|
|
515
505
|
|
|
@@ -559,7 +549,8 @@ class PiWave:
|
|
|
559
549
|
Example:
|
|
560
550
|
>>> pw.stop()
|
|
561
551
|
"""
|
|
562
|
-
|
|
552
|
+
|
|
553
|
+
if not self.is_playing and not self.is_live_streaming and not self.current_process:
|
|
563
554
|
return
|
|
564
555
|
|
|
565
556
|
Log.warning("Stopping...")
|
|
@@ -748,7 +739,7 @@ class PiWave:
|
|
|
748
739
|
>>> pw.set_frequency(101.5)
|
|
749
740
|
"""
|
|
750
741
|
self.frequency = frequency
|
|
751
|
-
Log.
|
|
742
|
+
Log.broadcast(f"Frequency changed to {frequency}MHz. Will update on next file's broadcast.")
|
|
752
743
|
|
|
753
744
|
def set_loop(self, loop: bool):
|
|
754
745
|
"""Enable or disable looping for the current track.
|
|
@@ -765,7 +756,7 @@ class PiWave:
|
|
|
765
756
|
"""
|
|
766
757
|
self.loop = loop
|
|
767
758
|
loop_status = "enabled" if loop else "disabled"
|
|
768
|
-
Log.
|
|
759
|
+
Log.broadcast(f"Looping {loop_status}. Will update on next file's broadcast.")
|
|
769
760
|
|
|
770
761
|
def get_status(self) -> dict:
|
|
771
762
|
"""Get current status information.
|
|
@@ -825,8 +816,6 @@ class PiWave:
|
|
|
825
816
|
|
|
826
817
|
Log.info("Cleanup completed")
|
|
827
818
|
|
|
828
|
-
def __del__(self):
|
|
829
|
-
self.cleanup()
|
|
830
819
|
|
|
831
820
|
def send(self, file_path: str):
|
|
832
821
|
"""Alias for the play method.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: piwave
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.4
|
|
4
4
|
Summary: A python module to broadcast radio waves with your Raspberry Pi.
|
|
5
5
|
Home-page: https://github.com/douxxtech/piwave
|
|
6
6
|
Author: Douxx
|
|
@@ -18,6 +18,7 @@ Classifier: Operating System :: POSIX :: Linux
|
|
|
18
18
|
Requires-Python: >=3.7
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
|
+
Requires-Dist: dlogger==1.0.1
|
|
21
22
|
Provides-Extra: dev
|
|
22
23
|
Requires-Dist: pytest>=6.0; extra == "dev"
|
|
23
24
|
Requires-Dist: pytest-cov>=2.0; extra == "dev"
|
|
@@ -518,8 +519,11 @@ status = pw.get_status()
|
|
|
518
519
|
##### `cleanup()`
|
|
519
520
|
Clean up resources and temporary files.
|
|
520
521
|
|
|
522
|
+
> [!WARNING]
|
|
523
|
+
> Always clean up behind you! Dropped support of auto-cleanup on version > 2.1.2
|
|
524
|
+
|
|
521
525
|
```python
|
|
522
|
-
pw.cleanup()
|
|
526
|
+
pw.cleanup()
|
|
523
527
|
```
|
|
524
528
|
|
|
525
529
|
### Backend Management
|
|
@@ -611,6 +615,10 @@ class CustomBackend(Backend):
|
|
|
611
615
|
@property
|
|
612
616
|
def supports_rds(self):
|
|
613
617
|
return True # RDS capability
|
|
618
|
+
|
|
619
|
+
@property
|
|
620
|
+
def supports_loop(self):
|
|
621
|
+
return True # loop support capability
|
|
614
622
|
|
|
615
623
|
def _get_executable_name(self):
|
|
616
624
|
return "my_transmitter"
|
|
@@ -618,10 +626,13 @@ class CustomBackend(Backend):
|
|
|
618
626
|
def _get_search_paths(self):
|
|
619
627
|
return ["/opt", "/usr/local/bin", "/usr/bin"]
|
|
620
628
|
|
|
621
|
-
def build_command(self, wav_file: str):
|
|
629
|
+
def build_command(self, wav_file: str, loop: bool):
|
|
622
630
|
cmd = ['sudo', self.required_executable, '-f', str(self.frequency)]
|
|
623
631
|
if self.supports_rds and self.ps:
|
|
624
632
|
cmd.extend(['-ps', self.ps])
|
|
633
|
+
|
|
634
|
+
if self.supports_rds and loop:
|
|
635
|
+
cmd.extend(['-loop'])
|
|
625
636
|
cmd.append(wav_file)
|
|
626
637
|
return cmd
|
|
627
638
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[metadata]
|
|
2
2
|
name = piwave
|
|
3
|
-
version = 2.1.
|
|
3
|
+
version = 2.1.4
|
|
4
4
|
description = A python module to broadcast radio waves with your Raspberry Pi.
|
|
5
5
|
long_description = file: README.md
|
|
6
6
|
long_description_content_type = text/markdown
|
|
@@ -24,6 +24,7 @@ project_urls =
|
|
|
24
24
|
packages = find:
|
|
25
25
|
include_package_data = True
|
|
26
26
|
install_requires =
|
|
27
|
+
dlogger==1.0.1
|
|
27
28
|
python_requires = >=3.7
|
|
28
29
|
|
|
29
30
|
[options.extras_require]
|
piwave-2.1.2/piwave/logger.py
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
# PiWave is available at https://piwave.xyz
|
|
2
|
-
# Licensed under GPLv3.0, main GitHub repository at https://github.com/douxxtech/piwave/
|
|
3
|
-
# piwave/Logger.py : Main logging manager
|
|
4
|
-
|
|
5
|
-
import sys
|
|
6
|
-
|
|
7
|
-
class Log:
|
|
8
|
-
COLORS = { # absolutely not taken from stackoverflow trust
|
|
9
|
-
'reset': '\033[0m',
|
|
10
|
-
'bold': '\033[1m',
|
|
11
|
-
'underline': '\033[4m',
|
|
12
|
-
'red': '\033[31m',
|
|
13
|
-
'green': '\033[32m',
|
|
14
|
-
'yellow': '\033[33m',
|
|
15
|
-
'blue': '\033[34m',
|
|
16
|
-
'magenta': '\033[35m',
|
|
17
|
-
'cyan': '\033[36m',
|
|
18
|
-
'white': '\033[37m',
|
|
19
|
-
'bright_red': '\033[91m',
|
|
20
|
-
'bright_green': '\033[92m',
|
|
21
|
-
'bright_yellow': '\033[93m',
|
|
22
|
-
'bright_blue': '\033[94m',
|
|
23
|
-
'bright_magenta': '\033[95m',
|
|
24
|
-
'bright_cyan': '\033[96m',
|
|
25
|
-
'bright_white': '\033[97m',
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
ICONS = {
|
|
29
|
-
'success': 'OK',
|
|
30
|
-
'error': 'ERR',
|
|
31
|
-
'warning': 'WARN',
|
|
32
|
-
'info': 'INFO',
|
|
33
|
-
'client': 'CLIENT',
|
|
34
|
-
'server': 'SERVER',
|
|
35
|
-
'file': 'FILE',
|
|
36
|
-
'broadcast': 'BCAST',
|
|
37
|
-
'version': 'VER',
|
|
38
|
-
'update': 'UPD',
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
SILENT = False
|
|
42
|
-
|
|
43
|
-
@classmethod
|
|
44
|
-
def config(cls, silent: bool = False):
|
|
45
|
-
cls.SILENT = silent
|
|
46
|
-
|
|
47
|
-
@classmethod
|
|
48
|
-
def print(cls, message: str, style: str = '', icon: str = '', end: str = '\n'):
|
|
49
|
-
|
|
50
|
-
if cls.SILENT: return
|
|
51
|
-
|
|
52
|
-
color = cls.COLORS.get(style, '')
|
|
53
|
-
icon_char = cls.ICONS.get(icon, '')
|
|
54
|
-
if icon_char:
|
|
55
|
-
if color:
|
|
56
|
-
print(f"{color}[{icon_char}]\033[0m {message}", end=end)
|
|
57
|
-
else:
|
|
58
|
-
print(f"[{icon_char}] {message}", end=end)
|
|
59
|
-
else:
|
|
60
|
-
if color:
|
|
61
|
-
print(f"{color}{message}\033[0m", end=end)
|
|
62
|
-
else:
|
|
63
|
-
print(f"{message}", end=end)
|
|
64
|
-
sys.stdout.flush()
|
|
65
|
-
|
|
66
|
-
@classmethod
|
|
67
|
-
def header(cls, text: str):
|
|
68
|
-
cls.print(text, 'bright_blue', end='\n\n')
|
|
69
|
-
sys.stdout.flush()
|
|
70
|
-
|
|
71
|
-
@classmethod
|
|
72
|
-
def section(cls, text: str):
|
|
73
|
-
cls.print(f" {text} ", 'bright_blue', end='')
|
|
74
|
-
cls.print("─" * (len(text) + 2), 'blue', end='\n\n')
|
|
75
|
-
sys.stdout.flush()
|
|
76
|
-
|
|
77
|
-
@classmethod
|
|
78
|
-
def success(cls, message: str):
|
|
79
|
-
cls.print(message, 'bright_green', 'success')
|
|
80
|
-
|
|
81
|
-
@classmethod
|
|
82
|
-
def error(cls, message: str):
|
|
83
|
-
cls.print(message, 'bright_red', 'error')
|
|
84
|
-
|
|
85
|
-
@classmethod
|
|
86
|
-
def warning(cls, message: str):
|
|
87
|
-
cls.print(message, 'bright_yellow', 'warning')
|
|
88
|
-
|
|
89
|
-
@classmethod
|
|
90
|
-
def info(cls, message: str):
|
|
91
|
-
cls.print(message, 'bright_cyan', 'info')
|
|
92
|
-
|
|
93
|
-
@classmethod
|
|
94
|
-
def file_message(cls, message: str):
|
|
95
|
-
cls.print(message, 'yellow', 'file')
|
|
96
|
-
|
|
97
|
-
@classmethod
|
|
98
|
-
def broadcast_message(cls, message: str):
|
|
99
|
-
cls.print(message, 'bright_magenta', 'broadcast')
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|