This commit is contained in:
tsi-billypom 2025-04-11 15:35:11 -04:00
parent 67c922ac10
commit 9d94540d0a
4 changed files with 278 additions and 2 deletions

167
assets/gptcrap.md Normal file
View File

@ -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 cant 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_())
```

104
components/PMediaPlayer.py Normal file
View File

@ -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()

View File

@ -9,3 +9,4 @@ from .AddToPlaylistWindow import AddToPlaylistWindow
from .CreatePlaylistWindow import CreatePlaylistWindow
from .PlaylistsPane import PlaylistsPane
from .ExportPlaylistWindow import ExportPlaylistWindow
from .PMediaPlayer import PMediaPlayer

View File

@ -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