diff --git a/assets/gptcrap.md b/assets/gptcrap.md new file mode 100644 index 0000000..1e3891f --- /dev/null +++ b/assets/gptcrap.md @@ -0,0 +1,167 @@ +✅ 1. Use Pitch Correction with pitch Element +Normally, speeding up audio raises its pitch. To maintain pitch (i.e., keep voices from sounding chipmunky), you can use the pitch plugin (part of the soundtouch or rubberband plugins). + +Example GStreamer Pipeline (CLI): +```bash +gst-launch-1.0 filesrc location=yourfile.mp3 ! decodebin ! audioconvert ! pitch pitch=1.0 rate=1.5 ! autoaudiosink +``` + +pitch=1.0 keeps the pitch constant +rate=1.5 increases playback speed to 1.5x +pitch plugin is from gst-plugins-bad + +✅ 2. Use Higher-Quality Resampling +When changing playback rate, audio might need resampling. Use audioresample with better quality settings. +```bash +gst-launch-1.0 filesrc location=yourfile.mp3 ! decodebin ! audioresample quality=10 ! pitch pitch=1.0 rate=1.5 ! autoaudiosink +quality=10 gives better audio fidelity (default is 4) +``` + +✅ 3. Adjust Buffering to Prevent Glitches +Higher playback speeds can stress the pipeline. Use queue elements or increase buffer sizes to smooth things out. + +```bash +... ! queue max-size-buffers=0 max-size-bytes=0 max-size-time=500000000 ! ... +``` +✅ 4. Use playbin with Custom Filters (in Code) +If you're working in Qt and using GStreamer under the hood, you can’t easily insert these elements into the pipeline via QMediaPlayer. But you can build a custom pipeline using GStreamer directly in C++ or Python. + +Example GStreamer code snippet in Python: + +```python +pipeline = Gst.parse_launch( + "playbin uri=file:///path/to/file.mp3 audio-filter='pitch pitch=1.0 rate=1.5'" +) +``` +In Qt, you'd need to bypass QMediaPlayer and integrate GStreamer directly, or subclass QMediaPlayer and access the backend (advanced). + +✅ 5. Ensure You Have the Right Plugins Installed +Make sure you have: + +gstreamer1.0-plugins-bad (for pitch) +gstreamer1.0-plugins-base +gstreamer1.0-plugins-good +gstreamer1.0-plugins-ugly (if needed) + +Install on Debian/Ubuntu: + +```bash +sudo apt install gstreamer1.0-plugins-{bad,base,good,ugly} +``` + + +```bash +pip install PyQt5 +sudo apt install python3-gi gir1.2-gst-1.0 gstreamer1.0-plugins-{bad,good,base} +``` +✅ Custom Media Player with Rate & Pitch Control +```python +import sys +from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout, QFileDialog, QLabel, QSlider +from PyQt5.QtCore import Qt +import gi + +gi.require_version('Gst', '1.0') +from gi.repository import Gst, GObject + +# Initialize GStreamer +Gst.init(None) + +class GStreamerPlayer(QWidget): + def __init__(self): + super().__init__() + self.setWindowTitle("Custom GStreamer Audio Player with Pitch Control") + self.setGeometry(300, 300, 400, 200) + + # UI + self.play_button = QPushButton("Play") + self.pause_button = QPushButton("Pause") + self.open_button = QPushButton("Open File") + self.label = QLabel("Playback Rate: 1.0x") + self.slider = QSlider(Qt.Horizontal) + self.slider.setMinimum(50) + self.slider.setMaximum(200) + self.slider.setValue(100) + + layout = QVBoxLayout() + layout.addWidget(self.open_button) + layout.addWidget(self.play_button) + layout.addWidget(self.pause_button) + layout.addWidget(self.label) + layout.addWidget(self.slider) + self.setLayout(layout) + + # Signals + self.open_button.clicked.connect(self.open_file) + self.play_button.clicked.connect(self.play) + self.pause_button.clicked.connect(self.pause) + self.slider.valueChanged.connect(self.set_playback_rate) + + # GStreamer pipeline + self.pipeline = Gst.ElementFactory.make("playbin", "player") + self.pipeline.set_property("audio-filter", self.build_pitch_filter(1.0)) + + self.uri = None + + def build_pitch_filter(self, rate): + pitch = Gst.ElementFactory.make("pitch", "pitch") + pitch.set_property("pitch", 1.0) + pitch.set_property("rate", rate) + + # Build filter bin + bin = Gst.Bin.new("filter_bin") + conv = Gst.ElementFactory.make("audioconvert", "conv") + resample = Gst.ElementFactory.make("audioresample", "resample") + bin.add(conv) + bin.add(resample) + bin.add(pitch) + + conv.link(resample) + resample.link(pitch) + + # Ghost pad to connect to playbin + pad = pitch.get_static_pad("src") + ghost = Gst.GhostPad.new("src", pad) + bin.add_pad(ghost) + + conv_pad = conv.get_static_pad("sink") + ghost_sink = Gst.GhostPad.new("sink", conv_pad) + bin.add_pad(ghost_sink) + + return bin + + def open_file(self): + file_path, _ = QFileDialog.getOpenFileName(self, "Open Audio File") + if file_path: + self.uri = Gst.filename_to_uri(file_path) + self.pipeline.set_property("uri", self.uri) + + def play(self): + if self.uri: + self.pipeline.set_state(Gst.State.PLAYING) + + def pause(self): + self.pipeline.set_state(Gst.State.PAUSED) + + def set_playback_rate(self, value): + rate = value / 100.0 + self.label.setText(f"Playback Rate: {rate:.1f}x") + # Replace the audio-filter with a new one using the updated rate + self.pipeline.set_state(Gst.State.NULL) + self.pipeline.set_property("audio-filter", self.build_pitch_filter(rate)) + if self.uri: + self.pipeline.set_property("uri", self.uri) + self.pipeline.set_state(Gst.State.PLAYING) + + def closeEvent(self, event): + self.pipeline.set_state(Gst.State.NULL) + event.accept() + + +if __name__ == "__main__": + GObject.threads_init() + app = QApplication(sys.argv) + player = GStreamerPlayer() + player.show() + sys.exit(app.exec_()) +``` diff --git a/components/PMediaPlayer.py b/components/PMediaPlayer.py new file mode 100644 index 0000000..dd70e81 --- /dev/null +++ b/components/PMediaPlayer.py @@ -0,0 +1,104 @@ +import gi +import os +from gi.repository import Gst, GObject + +gi.require_version("Gst", "1.0") + +Gst.init(None) + + +class PMediaPlayer: + def __init__(self): + self.pipeline = Gst.ElementFactory.make("playbin", "player") + self.uri = None + self.rate = 1.0 + + # Set initial audio filter + self.pipeline.set_property("audio-filter", self._build_pitch_filter(self.rate)) + + def setSource(self, file_path: str): + if not os.path.isfile(file_path): + raise FileNotFoundError(f"File not found: {file_path}") + self.uri = Gst.filename_to_uri(file_path) + self.pipeline.set_property("uri", self.uri) + + def play(self): + if not self.uri: + raise RuntimeError("No media source set.") + self.pipeline.set_state(Gst.State.PLAYING) + + def pause(self): + self.pipeline.set_state(Gst.State.PAUSED) + + def stop(self): + self.pipeline.set_state(Gst.State.NULL) + + def setPlaybackRate(self, rate: float): + if rate <= 0: + raise ValueError("Playback rate must be greater than 0.") + + self.rate = rate + # Fully rebuild the pitch filter with new rate + self.pipeline.set_state(Gst.State.NULL) + self.pipeline.set_property("audio-filter", self._build_pitch_filter(rate)) + if self.uri: + self.pipeline.set_property("uri", self.uri) + self.pipeline.set_state(Gst.State.PLAYING) + + def _build_pitch_filter(self, rate: float): + pitch = Gst.ElementFactory.make("pitch", "pitch") + pitch.set_property("pitch", 1.0) # Keep pitch constant + pitch.set_property("rate", rate) + + bin = Gst.Bin.new("filter_bin") + conv = Gst.ElementFactory.make("audioconvert", "conv") + resample = Gst.ElementFactory.make("audioresample", "resample") + + bin.add(conv) + bin.add(resample) + bin.add(pitch) + conv.link(resample) + resample.link(pitch) + + # Ghost pads + sink_pad = conv.get_static_pad("sink") + src_pad = pitch.get_static_pad("src") + bin.add_pad(Gst.GhostPad.new("sink", sink_pad)) + bin.add_pad(Gst.GhostPad.new("src", src_pad)) + + return bin + + def cleanup(self): + self.pipeline.set_state(Gst.State.NULL) + + +# Example usage: +if __name__ == "__main__": + import time + + GObject.threads_init() + player = PMediaPlayer() + + try: + player.setSource("/path/to/your/audio/file.mp3") + player.play() + time.sleep(2) + + print("Speeding up to 1.5x...") + player.setPlaybackRate(1.5) + time.sleep(5) + + print("Slowing down to 0.75x...") + player.setPlaybackRate(0.75) + time.sleep(5) + + print("Pausing...") + player.pause() + time.sleep(2) + + print("Resuming...") + player.play() + time.sleep(5) + + finally: + player.cleanup() diff --git a/components/__init__.py b/components/__init__.py index 57ba057..b8dc6b5 100644 --- a/components/__init__.py +++ b/components/__init__.py @@ -9,3 +9,4 @@ from .AddToPlaylistWindow import AddToPlaylistWindow from .CreatePlaylistWindow import CreatePlaylistWindow from .PlaylistsPane import PlaylistsPane from .ExportPlaylistWindow import ExportPlaylistWindow +from .PMediaPlayer import PMediaPlayer diff --git a/main.py b/main.py index a264092..1598648 100644 --- a/main.py +++ b/main.py @@ -50,6 +50,7 @@ from components import ( AudioVisualizer, CreatePlaylistWindow, ExportPlaylistWindow, + PMediaPlayer, ) from utils.get_album_art import get_album_art @@ -171,9 +172,12 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow): # widget bits self.album_art_scene: QGraphicsScene = QGraphicsScene() - self.player: QMediaPlayer = QMediaPlayer() # Audio player object + # self.player: QMediaPlayer = QMediaPlayer() # Audio player object + self.player: PMediaPlayer = PMediaPlayer() self.probe: QAudioProbe = QAudioProbe() # Gets audio buffer data - self.audio_visualizer: AudioVisualizer = AudioVisualizer(self.player, self.probe, self.PlotWidget) + self.audio_visualizer: AudioVisualizer = AudioVisualizer( + self.player, self.probe, self.PlotWidget + ) self.timer = QTimer(self) # for playback slider and such # sharing functions with other classes and that