piwave 2.1.10__tar.gz → 2.1.12__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: piwave
3
- Version: 2.1.10
3
+ Version: 2.1.12
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)
@@ -3,9 +3,6 @@
3
3
  <h1>PiWave</h1>
4
4
  </div>
5
5
 
6
- > [!CAUTION]
7
- > `piwave==2.1.6` is broken ! Please use `piwave==2.1.5`
8
-
9
6
  **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.
10
7
 
11
8
  ## Features
@@ -733,4 +730,5 @@ Please submit pull requests or open issues on [GitHub](https://github.com/douxxt
733
730
 
734
731
  **PiWave** - FM Broadcasting module for Raspberry Pi
735
732
 
736
- ![Made by Douxx](https://madeby.douxx.tech)
733
+
734
+ ![Made by Douxx](https://madeby.douxx.tech)
@@ -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):
@@ -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()
@@ -84,10 +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
91
 
92
+ Log.debug(f"Discovering backends...")
91
93
  discover_backends()
92
94
 
93
95
  self.backend_use = used_for
@@ -118,6 +120,9 @@ class PiWave:
118
120
  pi=self.pi
119
121
  )
120
122
 
123
+ Log.debug(f"Selected backend: {backend_name}")
124
+
125
+
121
126
 
122
127
  min_freq, max_freq = self.backend.frequency_range
123
128
  rds_support = "with RDS" if self.backend.supports_rds else "no RDS"
@@ -127,11 +132,6 @@ class PiWave:
127
132
 
128
133
  Log.info(f"PiWave initialized - Frequency: {frequency}MHz, PS: {ps}, Loop: {loop}")
129
134
 
130
- def _log_debug(self, message: str):
131
- if self.debug:
132
- Log.print(f"[DEBUG] {message}", 'bright_cyan')
133
-
134
-
135
135
  def _validate_environment(self):
136
136
 
137
137
  #validate that we're running on a Raspberry Pi as root
@@ -153,57 +153,6 @@ class PiWave:
153
153
  def _is_root(self) -> bool:
154
154
  return os.geteuid() == 0
155
155
 
156
- def _find_pi_fm_rds_path(self) -> str:
157
- current_dir = Path(__file__).parent
158
- cache_file = current_dir / "pi_fm_rds_path"
159
-
160
- if cache_file.exists():
161
- try:
162
- cached_path = cache_file.read_text().strip()
163
- if self._is_valid_executable(cached_path):
164
- return cached_path
165
- else:
166
- cache_file.unlink()
167
- except Exception as e:
168
- Log.warning(f"Error reading cache file: {e}")
169
- cache_file.unlink(missing_ok=True)
170
-
171
- search_paths = ["/opt", "/usr/local/bin", "/usr/bin", "/bin", "/home"]
172
-
173
- for search_path in search_paths:
174
- if not Path(search_path).exists():
175
- continue
176
-
177
- try:
178
- for root, dirs, files in os.walk(search_path):
179
- if "pi_fm_rds" in files:
180
- executable_path = Path(root) / "pi_fm_rds"
181
- if self._is_valid_executable(str(executable_path)):
182
- cache_file.write_text(str(executable_path))
183
- return str(executable_path)
184
- except (PermissionError, OSError):
185
- continue
186
-
187
- print("Could not automatically find `pi_fm_rds`. Please enter the full path manually.")
188
- user_path = input("Enter the path to `pi_fm_rds`: ").strip()
189
-
190
- if self._is_valid_executable(user_path):
191
- cache_file.write_text(user_path)
192
- return user_path
193
-
194
- raise PiWaveError("Invalid pi_fm_rds path provided")
195
-
196
- def _is_valid_executable(self, path: str) -> bool:
197
- try:
198
- result = subprocess.run(
199
- [path, "--help"],
200
- stdout=subprocess.PIPE,
201
- stderr=subprocess.PIPE,
202
- timeout=5
203
- )
204
- return result.returncode == 0
205
- except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError):
206
- return False
207
156
 
208
157
  def _is_wav_file(self, filepath: str) -> bool:
209
158
  return filepath.lower().endswith('.wav')
@@ -211,6 +160,7 @@ class PiWave:
211
160
 
212
161
  def _convert_to_wav(self, filepath: str) -> Optional[str]:
213
162
  if self._is_wav_file(filepath):
163
+ Log.debug(f"File is already WAV, skipping conversion")
214
164
  return filepath
215
165
 
216
166
  Log.file(f"Converting {filepath} to WAV")
@@ -221,8 +171,15 @@ class PiWave:
221
171
  'ffmpeg', '-i', filepath, '-acodec', 'pcm_s16le',
222
172
  '-ar', '44100', '-ac', '2', '-y', output_file
223
173
  ]
174
+
175
+ if self.debug:
176
+ cmd.extend(['-v', 'debug'])
177
+ else:
178
+ cmd.extend(['-v', 'quiet'])
224
179
 
225
180
  try:
181
+ Log.debug(f"Starting FFmpeg conversion: {' '.join(cmd)}")
182
+
226
183
  subprocess.run(
227
184
  cmd,
228
185
  stdout=subprocess.PIPE,
@@ -231,7 +188,7 @@ class PiWave:
231
188
  check=True
232
189
  )
233
190
 
234
- self._log_debug(f"FFmpeg conversion successful for {filepath}")
191
+ Log.debug(f"Conversion completed: {output_file}")
235
192
 
236
193
  return output_file
237
194
 
@@ -246,11 +203,15 @@ class PiWave:
246
203
  return None
247
204
 
248
205
  def _get_file_duration(self, wav_file: str) -> float:
249
- cmd = [
250
- 'ffprobe', '-i', wav_file, '-show_entries', 'format=duration',
251
- '-v', 'quiet', '-of', 'csv=p=0'
252
- ]
206
+ cmd = ['ffprobe', '-i', wav_file, '-show_entries', 'format=duration', '-of', 'csv=p=0']
253
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
+
254
215
  try:
255
216
  result = subprocess.run(
256
217
  cmd,
@@ -407,6 +368,7 @@ class PiWave:
407
368
  if self.stop_event.is_set():
408
369
  break
409
370
  if chunk:
371
+ Log.debug(f"Producer: sending chunk of {len(chunk)} bytes")
410
372
  self.audio_queue.put(chunk, timeout=1)
411
373
 
412
374
  elif callable(audio_source):
@@ -414,6 +376,7 @@ class PiWave:
414
376
  chunk = audio_source()
415
377
  if not chunk:
416
378
  break
379
+ Log.debug(f"Producer: sending chunk of {len(chunk)} bytes")
417
380
  self.audio_queue.put(chunk, timeout=1)
418
381
 
419
382
  elif hasattr(audio_source, 'read'):
@@ -421,6 +384,7 @@ class PiWave:
421
384
  chunk = audio_source.read(chunk_size)
422
385
  if not chunk:
423
386
  break
387
+ Log.debug(f"Producer: sending chunk of {len(chunk)} bytes")
424
388
  self.audio_queue.put(chunk, timeout=1)
425
389
 
426
390
  except Exception as e:
@@ -438,6 +402,8 @@ class PiWave:
438
402
  chunk = self.audio_queue.get(timeout=0.1)
439
403
  if chunk is None:
440
404
  break
405
+
406
+ Log.debug(f"Consumer: queue size = {self.audio_queue.qsize()}")
441
407
 
442
408
  if self.current_process and self.current_process.stdin:
443
409
  self.current_process.stdin.write(chunk)
@@ -460,7 +426,7 @@ class PiWave:
460
426
  self.is_live_streaming = False
461
427
 
462
428
  def _playback_worker(self):
463
- self._log_debug("Playback worker started")
429
+ Log.debug("Playback worker started")
464
430
 
465
431
  if not self.current_file:
466
432
  Log.error("No file specified for playback")
@@ -483,7 +449,7 @@ class PiWave:
483
449
  Log.error(f"Playback failed for {wav_file}")
484
450
 
485
451
  self.is_playing = False
486
- self._log_debug("Playback worker finished")
452
+ Log.debug("Playback worker finished")
487
453
 
488
454
 
489
455
  def play(self, source, sample_rate: int = 44100, channels: int = 2, chunk_size: int = 4096, blocking: bool = False):
@@ -703,11 +669,11 @@ class PiWave:
703
669
  updated_settings.append(f"PI: {self.pi}")
704
670
 
705
671
  if debug is not None:
706
- self.debug = debug
672
+ Log.config(silent=Log.SILENT, debug=debug)
707
673
  updated_settings.append(f"debug: {debug}")
708
674
 
709
675
  if silent is not None:
710
- Log.config(silent=silent)
676
+ Log.config(silent=silent, debug=Log.DEBUG)
711
677
  updated_settings.append(f"silent: {silent}")
712
678
 
713
679
  if loop is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: piwave
3
- Version: 2.1.10
3
+ Version: 2.1.12
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)
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = piwave
3
- version = 2.1.10
3
+ version = 2.1.12
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
File without changes
File without changes
File without changes
File without changes
File without changes