piwave 2.0.3__py3-none-any.whl → 2.0.5__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/__init__.py +3 -1
- piwave/piwave.py +182 -7
- {piwave-2.0.3.dist-info → piwave-2.0.5.dist-info}/METADATA +6 -20
- piwave-2.0.5.dist-info/RECORD +7 -0
- piwave-2.0.3.dist-info/RECORD +0 -7
- {piwave-2.0.3.dist-info → piwave-2.0.5.dist-info}/WHEEL +0 -0
- {piwave-2.0.3.dist-info → piwave-2.0.5.dist-info}/licenses/LICENSE +0 -0
- {piwave-2.0.3.dist-info → piwave-2.0.5.dist-info}/top_level.txt +0 -0
piwave/__init__.py
CHANGED
piwave/piwave.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# piwave/
|
|
1
|
+
# piwave/pw.py
|
|
2
2
|
# pi_fm_rds is required !!! Check https://github.com/ChristopheJacquet/PiFmRds
|
|
3
3
|
|
|
4
4
|
import os
|
|
@@ -112,6 +112,30 @@ class PiWave:
|
|
|
112
112
|
debug: bool = False,
|
|
113
113
|
on_track_change: Optional[Callable] = None,
|
|
114
114
|
on_error: Optional[Callable] = None):
|
|
115
|
+
"""Initialize PiWave FM transmitter.
|
|
116
|
+
|
|
117
|
+
:param frequency: FM frequency to broadcast on (80.0-108.0 MHz)
|
|
118
|
+
:type frequency: float
|
|
119
|
+
:param ps: Program Service name (max 8 characters)
|
|
120
|
+
:type ps: str
|
|
121
|
+
:param rt: Radio Text message (max 64 characters)
|
|
122
|
+
:type rt: str
|
|
123
|
+
:param pi: Program Identification code (4 hex digits)
|
|
124
|
+
:type pi: str
|
|
125
|
+
:param loop: Whether to loop the playlist when it ends
|
|
126
|
+
:type loop: bool
|
|
127
|
+
:param debug: Enable debug logging
|
|
128
|
+
:type debug: bool
|
|
129
|
+
:param on_track_change: Callback function called when track changes
|
|
130
|
+
:type on_track_change: Optional[Callable]
|
|
131
|
+
:param on_error: Callback function called when an error occurs
|
|
132
|
+
:type on_error: Optional[Callable]
|
|
133
|
+
:raises PiWaveError: If not running on Raspberry Pi or without root privileges
|
|
134
|
+
|
|
135
|
+
.. note::
|
|
136
|
+
This class requires pi_fm_rds to be installed and accessible.
|
|
137
|
+
Must be run on a Raspberry Pi with root privileges.
|
|
138
|
+
"""
|
|
115
139
|
|
|
116
140
|
self.debug = debug
|
|
117
141
|
self.frequency = frequency
|
|
@@ -422,6 +446,21 @@ class PiWave:
|
|
|
422
446
|
os._exit(0)
|
|
423
447
|
|
|
424
448
|
def add_files(self, files: List[str]) -> bool:
|
|
449
|
+
"""Add audio files to the playlist.
|
|
450
|
+
|
|
451
|
+
:param files: List of file paths or URLs to add to the playlist
|
|
452
|
+
:type files: List[str]
|
|
453
|
+
:return: True if at least one file was successfully added, False otherwise
|
|
454
|
+
:rtype: bool
|
|
455
|
+
|
|
456
|
+
.. note::
|
|
457
|
+
Files are automatically converted to WAV format if needed.
|
|
458
|
+
URLs are supported for streaming audio.
|
|
459
|
+
|
|
460
|
+
Example:
|
|
461
|
+
>>> pw.add_files(['song1.mp3', 'song2.wav', 'http://stream.url'])
|
|
462
|
+
"""
|
|
463
|
+
|
|
425
464
|
converted_files = []
|
|
426
465
|
|
|
427
466
|
for file_path in files:
|
|
@@ -442,6 +481,21 @@ class PiWave:
|
|
|
442
481
|
return False
|
|
443
482
|
|
|
444
483
|
def play(self, files: Optional[List[str]] = None) -> bool:
|
|
484
|
+
"""Start playing the playlist or specified files.
|
|
485
|
+
|
|
486
|
+
:param files: Optional list of files to play. If provided, replaces current playlist
|
|
487
|
+
:type files: Optional[List[str]]
|
|
488
|
+
:return: True if playback started successfully, False otherwise
|
|
489
|
+
:rtype: bool
|
|
490
|
+
|
|
491
|
+
.. note::
|
|
492
|
+
If no files are specified, plays the current playlist.
|
|
493
|
+
Automatically stops any current playback before starting.
|
|
494
|
+
|
|
495
|
+
Example:
|
|
496
|
+
>>> pw.play(['song1.mp3', 'song2.wav']) # Play specific files
|
|
497
|
+
>>> pw.play() # Play current playlist
|
|
498
|
+
"""
|
|
445
499
|
if files:
|
|
446
500
|
self.playlist.clear()
|
|
447
501
|
self.current_index = 0
|
|
@@ -467,45 +521,129 @@ class PiWave:
|
|
|
467
521
|
return True
|
|
468
522
|
|
|
469
523
|
def stop(self):
|
|
524
|
+
"""Stop all playback and streaming.
|
|
525
|
+
|
|
526
|
+
Stops the current playback, kills all related processes, and resets the player state.
|
|
527
|
+
This method is safe to call multiple times.
|
|
528
|
+
|
|
529
|
+
Example:
|
|
530
|
+
>>> pw.stop()
|
|
531
|
+
"""
|
|
470
532
|
if not self.is_playing:
|
|
471
533
|
return
|
|
472
534
|
|
|
473
|
-
Log.warning("Stopping playback")
|
|
535
|
+
Log.warning("Stopping playback...")
|
|
536
|
+
|
|
474
537
|
self.is_stopped = True
|
|
475
538
|
self.stop_event.set()
|
|
476
|
-
|
|
477
|
-
self.
|
|
478
|
-
|
|
539
|
+
|
|
540
|
+
if self.current_process:
|
|
541
|
+
try:
|
|
542
|
+
os.killpg(os.getpgid(self.current_process.pid), signal.SIGTERM)
|
|
543
|
+
self.current_process.wait(timeout=5)
|
|
544
|
+
except Exception:
|
|
545
|
+
pass
|
|
546
|
+
finally:
|
|
547
|
+
self.current_process = None
|
|
548
|
+
|
|
549
|
+
if self.stream_process:
|
|
550
|
+
try:
|
|
551
|
+
os.killpg(os.getpgid(self.stream_process.pid), signal.SIGTERM)
|
|
552
|
+
self.stream_process.wait(timeout=5)
|
|
553
|
+
except Exception:
|
|
554
|
+
pass
|
|
555
|
+
finally:
|
|
556
|
+
self.stream_process = None
|
|
557
|
+
|
|
479
558
|
if self.playback_thread and self.playback_thread.is_alive():
|
|
480
559
|
self.playback_thread.join(timeout=5)
|
|
481
|
-
|
|
560
|
+
|
|
482
561
|
self.is_playing = False
|
|
483
562
|
Log.success("Playback stopped")
|
|
484
563
|
|
|
485
564
|
def pause(self):
|
|
565
|
+
"""Pause the current playback.
|
|
566
|
+
|
|
567
|
+
Stops the current track but maintains the playlist position.
|
|
568
|
+
Use :meth:`resume` to continue playback.
|
|
569
|
+
|
|
570
|
+
Example:
|
|
571
|
+
>>> pw.pause()
|
|
572
|
+
"""
|
|
486
573
|
if self.is_playing:
|
|
487
574
|
self._stop_current_process()
|
|
488
575
|
Log.info("Playback paused")
|
|
489
576
|
|
|
490
577
|
def resume(self):
|
|
578
|
+
"""Resume playback from the current position.
|
|
579
|
+
|
|
580
|
+
Continues playback from where it was paused or stopped.
|
|
581
|
+
|
|
582
|
+
Example:
|
|
583
|
+
>>> pw.resume()
|
|
584
|
+
"""
|
|
491
585
|
if not self.is_playing and self.playlist:
|
|
492
586
|
self.play()
|
|
493
587
|
|
|
494
588
|
def next_track(self):
|
|
589
|
+
"""Skip to the next track in the playlist.
|
|
590
|
+
|
|
591
|
+
If currently playing, stops the current track and advances to the next one.
|
|
592
|
+
|
|
593
|
+
Example:
|
|
594
|
+
>>> pw.next_track()
|
|
595
|
+
"""
|
|
495
596
|
if self.is_playing:
|
|
496
597
|
self._stop_current_process()
|
|
497
598
|
self.current_index += 1
|
|
498
599
|
|
|
499
600
|
def previous_track(self):
|
|
601
|
+
"""Go back to the previous track in the playlist.
|
|
602
|
+
|
|
603
|
+
If currently playing, stops the current track and goes to the previous one.
|
|
604
|
+
Cannot go before the first track.
|
|
605
|
+
|
|
606
|
+
Example:
|
|
607
|
+
>>> pw.previous_track()
|
|
608
|
+
"""
|
|
500
609
|
if self.is_playing:
|
|
501
610
|
self._stop_current_process()
|
|
502
611
|
self.current_index = max(0, self.current_index - 1)
|
|
503
612
|
|
|
504
613
|
def set_frequency(self, frequency: float):
|
|
614
|
+
"""Change the FM broadcast frequency.
|
|
615
|
+
|
|
616
|
+
:param frequency: New frequency in MHz (typically 88.0-108.0)
|
|
617
|
+
:type frequency: float
|
|
618
|
+
|
|
619
|
+
.. note::
|
|
620
|
+
The frequency change will take effect on the next track or broadcast.
|
|
621
|
+
|
|
622
|
+
Example:
|
|
623
|
+
>>> pw.set_frequency(101.5)
|
|
624
|
+
"""
|
|
505
625
|
self.frequency = frequency
|
|
506
626
|
Log.broadcast_message(f"Frequency changed to {frequency}MHz. Will update on next file's broadcast.")
|
|
507
627
|
|
|
508
628
|
def get_status(self) -> dict:
|
|
629
|
+
"""Get current status information.
|
|
630
|
+
|
|
631
|
+
:return: Dictionary containing current player status
|
|
632
|
+
:rtype: dict
|
|
633
|
+
|
|
634
|
+
The returned dictionary contains:
|
|
635
|
+
|
|
636
|
+
- **is_playing** (bool): Whether playback is active
|
|
637
|
+
- **current_index** (int): Current position in playlist
|
|
638
|
+
- **playlist_length** (int): Total number of items in playlist
|
|
639
|
+
- **frequency** (float): Current broadcast frequency
|
|
640
|
+
- **current_file** (str|None): Path of currently playing file
|
|
641
|
+
|
|
642
|
+
Example:
|
|
643
|
+
>>> status = pw.get_status()
|
|
644
|
+
>>> print(f"Playing: {status['is_playing']}")
|
|
645
|
+
>>> print(f"Track {status['current_index'] + 1} of {status['playlist_length']}")
|
|
646
|
+
"""
|
|
509
647
|
return {
|
|
510
648
|
'is_playing': self.is_playing,
|
|
511
649
|
'current_index': self.current_index,
|
|
@@ -515,6 +653,14 @@ class PiWave:
|
|
|
515
653
|
}
|
|
516
654
|
|
|
517
655
|
def cleanup(self):
|
|
656
|
+
"""Clean up resources and temporary files.
|
|
657
|
+
|
|
658
|
+
Stops all playback, removes temporary files, and cleans up system resources.
|
|
659
|
+
This method is automatically called when the object is destroyed.
|
|
660
|
+
|
|
661
|
+
Example:
|
|
662
|
+
>>> pw.cleanup()
|
|
663
|
+
"""
|
|
518
664
|
self.stop()
|
|
519
665
|
|
|
520
666
|
if os.path.exists(self.temp_dir):
|
|
@@ -526,9 +672,38 @@ class PiWave:
|
|
|
526
672
|
self.cleanup()
|
|
527
673
|
|
|
528
674
|
def send(self, files: List[str]):
|
|
675
|
+
"""Alias for the play method.
|
|
676
|
+
|
|
677
|
+
:param files: List of file paths or URLs to play
|
|
678
|
+
:type files: List[str]
|
|
679
|
+
:return: True if playback started successfully, False otherwise
|
|
680
|
+
:rtype: bool
|
|
681
|
+
|
|
682
|
+
.. note::
|
|
683
|
+
This is an alias for :meth:`play` for backward compatibility.
|
|
684
|
+
|
|
685
|
+
Example:
|
|
686
|
+
>>> pw.send(['song1.mp3', 'song2.wav'])
|
|
687
|
+
"""
|
|
529
688
|
return self.play(files)
|
|
530
689
|
|
|
531
690
|
def restart(self):
|
|
691
|
+
"""Restart playback from the beginning of the playlist.
|
|
692
|
+
|
|
693
|
+
Resets the current position to the first track and starts playback.
|
|
694
|
+
Only works if there are files in the playlist.
|
|
695
|
+
|
|
696
|
+
Example:
|
|
697
|
+
>>> pw.restart()
|
|
698
|
+
"""
|
|
532
699
|
if self.playlist:
|
|
533
700
|
self.current_index = 0
|
|
534
|
-
self.play()
|
|
701
|
+
self.play()
|
|
702
|
+
|
|
703
|
+
if __name__ == "__main__":
|
|
704
|
+
Log.header("PiWave Radio Module")
|
|
705
|
+
Log.info("This module is designed to run on a Raspberry Pi with root privileges.")
|
|
706
|
+
Log.info("Please import this module in your main application to use its features.")
|
|
707
|
+
Log.info("Exiting PiWave module")
|
|
708
|
+
|
|
709
|
+
__all__ = ["PiWave"]
|
|
@@ -1,38 +1,29 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: piwave
|
|
3
|
-
Version: 2.0.
|
|
4
|
-
Summary: A python module to
|
|
3
|
+
Version: 2.0.5
|
|
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
|
|
7
7
|
Author-email: douxx@douxx.tech
|
|
8
|
+
License: GPL-3.0-or-later
|
|
8
9
|
Project-URL: Bug Reports, https://github.com/douxxtech/piwave/issues
|
|
9
10
|
Project-URL: Source, https://github.com/douxxtech/piwave
|
|
10
11
|
Keywords: raspberry pi,radio,fm,rds,streaming,audio,broadcast
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
11
15
|
Classifier: Programming Language :: Python :: 3
|
|
12
16
|
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
13
17
|
Classifier: Operating System :: POSIX :: Linux
|
|
14
18
|
Requires-Python: >=3.7
|
|
15
19
|
Description-Content-Type: text/markdown
|
|
16
20
|
License-File: LICENSE
|
|
17
|
-
Requires-Dist: pathlib
|
|
18
21
|
Provides-Extra: dev
|
|
19
22
|
Requires-Dist: pytest>=6.0; extra == "dev"
|
|
20
23
|
Requires-Dist: pytest-cov>=2.0; extra == "dev"
|
|
21
24
|
Requires-Dist: black>=22.0; extra == "dev"
|
|
22
25
|
Requires-Dist: flake8>=4.0; extra == "dev"
|
|
23
|
-
Dynamic: author
|
|
24
|
-
Dynamic: author-email
|
|
25
|
-
Dynamic: classifier
|
|
26
|
-
Dynamic: description
|
|
27
|
-
Dynamic: description-content-type
|
|
28
|
-
Dynamic: home-page
|
|
29
|
-
Dynamic: keywords
|
|
30
26
|
Dynamic: license-file
|
|
31
|
-
Dynamic: project-url
|
|
32
|
-
Dynamic: provides-extra
|
|
33
|
-
Dynamic: requires-dist
|
|
34
|
-
Dynamic: requires-python
|
|
35
|
-
Dynamic: summary
|
|
36
27
|
|
|
37
28
|
<div align=center>
|
|
38
29
|
<img alt="PiWave image" src="https://piwave.xyz/static/img/logo.png"/>
|
|
@@ -51,11 +42,6 @@ Dynamic: summary
|
|
|
51
42
|
- Supports streaming from URLs.
|
|
52
43
|
- Better error handling and event callbacks.
|
|
53
44
|
- Non-blocking playback with threading.
|
|
54
|
-
- Temporary file management for streamed content.
|
|
55
|
-
|
|
56
|
-
> [!NOTE]
|
|
57
|
-
> Are you looking for a minimal GUI for PiWave?
|
|
58
|
-
> Take a look at the [PiWave WebGUI](https://github.com/douxxtech/piwave-webgui)
|
|
59
45
|
|
|
60
46
|
## Hardware Installation
|
|
61
47
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
piwave/__init__.py,sha256=tAmruZvneieh6fgkf7chKzOX9Q6fEB-5Jt9FJ7Fl5xQ,74
|
|
2
|
+
piwave/piwave.py,sha256=NzSCOGaHCEquWYQvxlaYvnn4zRfx13vuCWBX6ETPEXA,24378
|
|
3
|
+
piwave-2.0.5.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
|
|
4
|
+
piwave-2.0.5.dist-info/METADATA,sha256=ANndDO8Kds5p9NZZD1ZMEVTBzxs3ZU2o59-QCBhf_YQ,6768
|
|
5
|
+
piwave-2.0.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
6
|
+
piwave-2.0.5.dist-info/top_level.txt,sha256=xUbZ7Rk6OymSdDxmb9bfO8N-avJ9VYxP41GnXfwKYi8,7
|
|
7
|
+
piwave-2.0.5.dist-info/RECORD,,
|
piwave-2.0.3.dist-info/RECORD
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
piwave/__init__.py,sha256=lYEG0hZ7Khm0mMbVjVNLatI9YnKrHo_P5SWkN8eXmBk,50
|
|
2
|
-
piwave/piwave.py,sha256=oyxm0ggPtiHuca2TdsEjHN4hFAzRTVAnYEjaea0iQ-c,18031
|
|
3
|
-
piwave-2.0.3.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
|
|
4
|
-
piwave-2.0.3.dist-info/METADATA,sha256=G8Z7lH8sSlwaIAiVwljZJrRwJx66m66FbZrhweBQ_vw,7041
|
|
5
|
-
piwave-2.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
6
|
-
piwave-2.0.3.dist-info/top_level.txt,sha256=xUbZ7Rk6OymSdDxmb9bfO8N-avJ9VYxP41GnXfwKYi8,7
|
|
7
|
-
piwave-2.0.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|