piwave 2.1.9__py3-none-any.whl → 2.1.11__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.
piwave/backends/base.py CHANGED
@@ -6,10 +6,11 @@ from abc import ABC, abstractmethod
6
6
  from typing import Optional
7
7
  import subprocess
8
8
  import os
9
- import signal
10
9
  import shutil
11
10
  from pathlib import Path
12
11
 
12
+ from ..logger import Log
13
+
13
14
  class BackendError(Exception):
14
15
  pass
15
16
 
@@ -63,18 +64,25 @@ class Backend(ABC):
63
64
 
64
65
  def _find_executable(self) -> str:
65
66
  # Check cache first
67
+ Log.debug(f"Checking cache at {self.cache_file}")
68
+
66
69
  if self.cache_file.exists():
67
70
  try:
68
71
  cached_path = self.cache_file.read_text().strip()
69
72
  if self._is_valid_executable(cached_path):
73
+ Log.debug(f"Cache hit: {cached_path}")
70
74
  return cached_path
71
75
  else:
72
76
  self.cache_file.unlink()
73
77
  except Exception:
74
78
  self.cache_file.unlink(missing_ok=True)
79
+
80
+ Log.debug(f"Cache miss, searching filesystem...")
75
81
 
76
82
  # Only then search for it
77
83
  found_path = self._search_executable()
84
+ Log.debug(f"Found executable at: {found_path}")
85
+
78
86
  if found_path:
79
87
  try:
80
88
  self.cache_file.write_text(found_path)
@@ -82,6 +90,8 @@ class Backend(ABC):
82
90
  pass
83
91
  return found_path
84
92
 
93
+ Log.debug(f"Failed to find {self.name} on the filesystem.")
94
+
85
95
  raise BackendError(f"Could not find path for {self.name}. Please manually add one with python3 -m piwave add {self.name} <path>")
86
96
 
87
97
  @abstractmethod
@@ -230,12 +240,16 @@ class Backend(ABC):
230
240
  raise BackendError(f"{self.name} supports {min_freq}-{max_freq}MHz, got {self.frequency}MHz")
231
241
 
232
242
  cmd = self.build_command(wav_file, loop)
243
+ Log.debug(f"Build command: {' '.join(cmd)}")
233
244
  self.current_process = subprocess.Popen(
234
245
  cmd,
235
246
  stdout=subprocess.DEVNULL,
236
247
  stderr=subprocess.DEVNULL,
237
248
  stdin=subprocess.DEVNULL
238
249
  )
250
+
251
+ Log.debug(f"Process PID: {self.current_process.pid}")
252
+
239
253
  return self.current_process
240
254
 
241
255
  def stop(self):
piwave/logger.py CHANGED
@@ -12,7 +12,8 @@ class Logger(DLogger):
12
12
  'warning': 'WARN',
13
13
  'info': 'INFO',
14
14
  'file': 'FILE',
15
- 'broadcast': 'BCAST'
15
+ 'broadcast': 'BCAST',
16
+ 'debug': 'DEBUG'
16
17
  }
17
18
 
18
19
  STYLES = {
@@ -21,10 +22,12 @@ class Logger(DLogger):
21
22
  'warning': 'bright_yellow',
22
23
  'info': 'bright_cyan',
23
24
  'file': 'yellow',
24
- 'broadcast': 'bright_magenta'
25
+ 'broadcast': 'bright_magenta',
26
+ 'debug': 'orange'
25
27
  }
26
28
 
27
29
  SILENT = False
30
+ DEBUG = False
28
31
 
29
32
  def __init__(self):
30
33
  # Initialize with prebuilt icons & styles and silent support.
@@ -35,12 +38,17 @@ class Logger(DLogger):
35
38
  )
36
39
 
37
40
  @classmethod
38
- def config(self, silent: bool = False):
41
+ def config(self, silent: bool = False, debug: bool = False):
39
42
  self.SILENT = silent
43
+ self.DEBUG = debug
40
44
 
41
45
  def print(self, message: str, style: str = '', icon: str = '', end: str = '\n'):
42
46
  if self.SILENT:
43
47
  return
48
+
49
+ if icon == "DEBUG" and not self.DEBUG:
50
+ return
51
+
44
52
  super().print(message, style, icon, end)
45
53
 
46
54
  Log = Logger()
piwave/piwave.py CHANGED
@@ -84,11 +84,12 @@ class PiWave:
84
84
  self.live_thread: Optional[threading.Thread] = None
85
85
  self.audio_queue: Optional[queue.Queue] = None
86
86
 
87
- Log.config(silent=silent)
87
+ Log.config(silent=silent, debug=debug)
88
88
 
89
+ Log.debug(f"Validating environment...")
89
90
  self._validate_environment()
90
- atexit.register(self._stop_curproc)
91
91
 
92
+ Log.debug(f"Discovering backends...")
92
93
  discover_backends()
93
94
 
94
95
  self.backend_use = used_for
@@ -119,18 +120,18 @@ class PiWave:
119
120
  pi=self.pi
120
121
  )
121
122
 
123
+ Log.debug(f"Selected backend: {backend_name}")
124
+
125
+
122
126
 
123
127
  min_freq, max_freq = self.backend.frequency_range
124
128
  rds_support = "with RDS" if self.backend.supports_rds else "no RDS"
125
129
  Log.info(f"Using {self.backend.name} backend ({min_freq}-{max_freq}MHz, {rds_support})")
130
+
131
+ atexit.register(self._stop_curproc)
126
132
 
127
133
  Log.info(f"PiWave initialized - Frequency: {frequency}MHz, PS: {ps}, Loop: {loop}")
128
134
 
129
- def _log_debug(self, message: str):
130
- if self.debug:
131
- Log.print(f"[DEBUG] {message}", 'bright_cyan')
132
-
133
-
134
135
  def _validate_environment(self):
135
136
 
136
137
  #validate that we're running on a Raspberry Pi as root
@@ -152,57 +153,6 @@ class PiWave:
152
153
  def _is_root(self) -> bool:
153
154
  return os.geteuid() == 0
154
155
 
155
- def _find_pi_fm_rds_path(self) -> str:
156
- current_dir = Path(__file__).parent
157
- cache_file = current_dir / "pi_fm_rds_path"
158
-
159
- if cache_file.exists():
160
- try:
161
- cached_path = cache_file.read_text().strip()
162
- if self._is_valid_executable(cached_path):
163
- return cached_path
164
- else:
165
- cache_file.unlink()
166
- except Exception as e:
167
- Log.warning(f"Error reading cache file: {e}")
168
- cache_file.unlink(missing_ok=True)
169
-
170
- search_paths = ["/opt", "/usr/local/bin", "/usr/bin", "/bin", "/home"]
171
-
172
- for search_path in search_paths:
173
- if not Path(search_path).exists():
174
- continue
175
-
176
- try:
177
- for root, dirs, files in os.walk(search_path):
178
- if "pi_fm_rds" in files:
179
- executable_path = Path(root) / "pi_fm_rds"
180
- if self._is_valid_executable(str(executable_path)):
181
- cache_file.write_text(str(executable_path))
182
- return str(executable_path)
183
- except (PermissionError, OSError):
184
- continue
185
-
186
- print("Could not automatically find `pi_fm_rds`. Please enter the full path manually.")
187
- user_path = input("Enter the path to `pi_fm_rds`: ").strip()
188
-
189
- if self._is_valid_executable(user_path):
190
- cache_file.write_text(user_path)
191
- return user_path
192
-
193
- raise PiWaveError("Invalid pi_fm_rds path provided")
194
-
195
- def _is_valid_executable(self, path: str) -> bool:
196
- try:
197
- result = subprocess.run(
198
- [path, "--help"],
199
- stdout=subprocess.PIPE,
200
- stderr=subprocess.PIPE,
201
- timeout=5
202
- )
203
- return result.returncode == 0
204
- except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError):
205
- return False
206
156
 
207
157
  def _is_wav_file(self, filepath: str) -> bool:
208
158
  return filepath.lower().endswith('.wav')
@@ -210,6 +160,7 @@ class PiWave:
210
160
 
211
161
  def _convert_to_wav(self, filepath: str) -> Optional[str]:
212
162
  if self._is_wav_file(filepath):
163
+ Log.debug(f"File is already WAV, skipping conversion")
213
164
  return filepath
214
165
 
215
166
  Log.file(f"Converting {filepath} to WAV")
@@ -220,8 +171,15 @@ class PiWave:
220
171
  'ffmpeg', '-i', filepath, '-acodec', 'pcm_s16le',
221
172
  '-ar', '44100', '-ac', '2', '-y', output_file
222
173
  ]
174
+
175
+ if self.debug:
176
+ cmd.extend(['-v', 'debug'])
177
+ else:
178
+ cmd.extend(['-v', 'quiet'])
223
179
 
224
180
  try:
181
+ Log.debug(f"Starting FFmpeg conversion: {' '.join(cmd)}")
182
+
225
183
  subprocess.run(
226
184
  cmd,
227
185
  stdout=subprocess.PIPE,
@@ -230,7 +188,7 @@ class PiWave:
230
188
  check=True
231
189
  )
232
190
 
233
- self._log_debug(f"FFmpeg conversion successful for {filepath}")
191
+ Log.debug(f"Conversion completed: {output_file}")
234
192
 
235
193
  return output_file
236
194
 
@@ -245,11 +203,15 @@ class PiWave:
245
203
  return None
246
204
 
247
205
  def _get_file_duration(self, wav_file: str) -> float:
248
- cmd = [
249
- 'ffprobe', '-i', wav_file, '-show_entries', 'format=duration',
250
- '-v', 'quiet', '-of', 'csv=p=0'
251
- ]
206
+ cmd = ['ffprobe', '-i', wav_file, '-show_entries', 'format=duration', '-of', 'csv=p=0']
252
207
 
208
+ if self.debug:
209
+ cmd.extend(['-v', 'debug'])
210
+ else:
211
+ cmd.extend(['-v', 'quiet'])
212
+
213
+ Log.debug(f"Running command: {' '.join(cmd)}")
214
+
253
215
  try:
254
216
  result = subprocess.run(
255
217
  cmd,
@@ -421,6 +383,8 @@ class PiWave:
421
383
  if not chunk:
422
384
  break
423
385
  self.audio_queue.put(chunk, timeout=1)
386
+
387
+ Log.debug(f"Producer: sending chunk of {len(chunk)} bytes")
424
388
 
425
389
  except Exception as e:
426
390
  Log.error(f"Producer error: {e}")
@@ -437,6 +401,8 @@ class PiWave:
437
401
  chunk = self.audio_queue.get(timeout=0.1)
438
402
  if chunk is None:
439
403
  break
404
+
405
+ Log.debug(f"Consumer: queue size = {self.audio_queue.qsize()}")
440
406
 
441
407
  if self.current_process and self.current_process.stdin:
442
408
  self.current_process.stdin.write(chunk)
@@ -459,7 +425,7 @@ class PiWave:
459
425
  self.is_live_streaming = False
460
426
 
461
427
  def _playback_worker(self):
462
- self._log_debug("Playback worker started")
428
+ Log.debug("Playback worker started")
463
429
 
464
430
  if not self.current_file:
465
431
  Log.error("No file specified for playback")
@@ -482,7 +448,7 @@ class PiWave:
482
448
  Log.error(f"Playback failed for {wav_file}")
483
449
 
484
450
  self.is_playing = False
485
- self._log_debug("Playback worker finished")
451
+ Log.debug("Playback worker finished")
486
452
 
487
453
 
488
454
  def play(self, source, sample_rate: int = 44100, channels: int = 2, chunk_size: int = 4096, blocking: bool = False):
@@ -562,6 +528,9 @@ class PiWave:
562
528
  Log.success("Stopped")
563
529
 
564
530
  def _stop_curproc(self):
531
+ if not hasattr(self, 'backend'):
532
+ return
533
+
565
534
  if self.backend.current_process:
566
535
  self.backend.stop()
567
536
  elif self.current_process:
@@ -699,11 +668,11 @@ class PiWave:
699
668
  updated_settings.append(f"PI: {self.pi}")
700
669
 
701
670
  if debug is not None:
702
- self.debug = debug
671
+ Log.config(silent=Log.SILENT, debug=debug)
703
672
  updated_settings.append(f"debug: {debug}")
704
673
 
705
674
  if silent is not None:
706
- Log.config(silent=silent)
675
+ Log.config(silent=silent, debug=Log.DEBUG)
707
676
  updated_settings.append(f"silent: {silent}")
708
677
 
709
678
  if loop is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: piwave
3
- Version: 2.1.9
3
+ Version: 2.1.11
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
@@ -31,9 +31,6 @@ Dynamic: license-file
31
31
  <h1>PiWave</h1>
32
32
  </div>
33
33
 
34
- > [!CAUTION]
35
- > `piwave==2.1.6` is broken ! Please use `piwave==2.1.5`
36
-
37
34
  **PiWave** is a Python module designed to manage and control your Raspberry Pi radio using multiple FM transmission backends. It provides a unified interface for broadcasting audio files with multiple backends support and RDS (Radio Data System) support.
38
35
 
39
36
  ## Features
@@ -761,4 +758,5 @@ Please submit pull requests or open issues on [GitHub](https://github.com/douxxt
761
758
 
762
759
  **PiWave** - FM Broadcasting module for Raspberry Pi
763
760
 
761
+
764
762
  ![Made by Douxx](https://madeby.douxx.tech)
@@ -0,0 +1,13 @@
1
+ piwave/__init__.py,sha256=jz2r-qclltKTxJjlGnqhAwIlUgjvRTR31f4hjTHP5NA,230
2
+ piwave/__main__.py,sha256=-Z3RI7TiicE5UwWc4dZaHX82-MpwVXWkfMZXOC457_Q,4089
3
+ piwave/logger.py,sha256=jpsM3tTPTxuhojtSJbfLq4nDFxOjojSSOrtYDGl_oTw,1353
4
+ piwave/piwave.py,sha256=Uu6KH03jOtsrgogswjxgPwvQtB51R_oJ539UunNedEQ,30873
5
+ piwave/backends/__init__.py,sha256=DUbdyYf2V2XcDB05vmFWEkuJ292YTNiNJjzh1raJ5Cg,3756
6
+ piwave/backends/base.py,sha256=GI8IBinGii09F3AAuy9n9t59LlSEJAIkRiwwTcupWtM,8258
7
+ piwave/backends/fm_transmitter.py,sha256=Wzsoyi5hqLLZF5eNPmZ7WFvP27OMs2ywJlwrJVut-8w,1403
8
+ piwave/backends/pi_fm_rds.py,sha256=VjcbFeje8LHVr4cBjlL36IcvA3WRfI1hHSGG2ui8Pb0,1467
9
+ piwave-2.1.11.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
10
+ piwave-2.1.11.dist-info/METADATA,sha256=EMd5DXvCMkjPCn9cl34y_PXJLLJrGYtNXFt2ndCjEh4,20821
11
+ piwave-2.1.11.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
12
+ piwave-2.1.11.dist-info/top_level.txt,sha256=xUbZ7Rk6OymSdDxmb9bfO8N-avJ9VYxP41GnXfwKYi8,7
13
+ piwave-2.1.11.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,13 +0,0 @@
1
- piwave/__init__.py,sha256=jz2r-qclltKTxJjlGnqhAwIlUgjvRTR31f4hjTHP5NA,230
2
- piwave/__main__.py,sha256=-Z3RI7TiicE5UwWc4dZaHX82-MpwVXWkfMZXOC457_Q,4089
3
- piwave/logger.py,sha256=ipWzdNcEYob0xjXP8bbTq9enA3nryU-GKzAOKayRqlg,1149
4
- piwave/piwave.py,sha256=6ns1VmQ8hZFwrI_8HTLwvSD_CwMCzzUWGqms0u1MKeQ,32293
5
- piwave/backends/__init__.py,sha256=DUbdyYf2V2XcDB05vmFWEkuJ292YTNiNJjzh1raJ5Cg,3756
6
- piwave/backends/base.py,sha256=PJNXEEXZ8wgc343SO5FJ1oGOop--Y-7t01PUWSa5GuE,7818
7
- piwave/backends/fm_transmitter.py,sha256=Wzsoyi5hqLLZF5eNPmZ7WFvP27OMs2ywJlwrJVut-8w,1403
8
- piwave/backends/pi_fm_rds.py,sha256=VjcbFeje8LHVr4cBjlL36IcvA3WRfI1hHSGG2ui8Pb0,1467
9
- piwave-2.1.9.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
10
- piwave-2.1.9.dist-info/METADATA,sha256=0iHcdTApVbFqDrlJ4wurc-1d8O25T4R2ZzdiQxJz2jQ,20890
11
- piwave-2.1.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
- piwave-2.1.9.dist-info/top_level.txt,sha256=xUbZ7Rk6OymSdDxmb9bfO8N-avJ9VYxP41GnXfwKYi8,7
13
- piwave-2.1.9.dist-info/RECORD,,