media player controls working

This commit is contained in:
billypom on debian 2025-04-19 13:43:19 -04:00
parent c3945c51b8
commit 466b38ce2e
5 changed files with 87 additions and 48 deletions

View File

@ -75,6 +75,7 @@ player = new QMediaPlayer;
player->setMedia(QUrl("gst-pipeline: videotestsrc ! autovideosink"));
player->play();
```
QMultimedia.EncodingMode / Encoding quality...
##### misc
- database playlist autoexporting
- .wav, .ogg, .flac convertor

View File

@ -1,19 +1,18 @@
from PyQt5.QtCore import QObject, QUrl
from PyQt5.QtCore import QObject, QUrl, pyqtSignal
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QMediaPlaylist
from PyQt5.QtWidgets import QApplication
import sys
class MediaPlayer(QMediaPlayer):
playlistNextSignal = pyqtSignal()
def __init__(self):
super().__init__()
# Connect mediaStatusChanged signal to our custom function
self.mediaStatusChanged.connect(self.on_media_status_changed)
# def play(self, file_path):
# media_content = QMediaContent(QUrl.fromLocalFile(file_path))
# self.player.setMedia(media_content)
# self.player.play()
def play(self):
super().play()
def on_media_status_changed(self, status):
if status == QMediaPlayer.MediaStatus.EndOfMedia:
@ -21,6 +20,6 @@ class MediaPlayer(QMediaPlayer):
self.on_song_ended()
def on_song_ended(self):
# Your custom logic when the song ends
self.playlistNextSignal.emit()
print("Custom function executed after song ended!")

View File

@ -67,6 +67,7 @@ from configparser import ConfigParser
class MusicTable(QTableView):
playlistStatsSignal = pyqtSignal(str)
loadMusicTableSignal = pyqtSignal()
sortSignal = pyqtSignal()
playPauseSignal = pyqtSignal()
playSignal = pyqtSignal(str)
enterKey = pyqtSignal()
@ -352,12 +353,14 @@ class MusicTable(QTableView):
self.set_selected_song_qmodel_index(selected_qmodel_index)
self.set_current_song_qmodel_index(current_qmodel_index)
self.jump_to_selected_song()
self.sortSignal.emit()
def on_cell_clicked(self, index):
"""
When a cell is clicked, do some stuff :)
- this func also runs when double click happens, fyi
"""
print(index.row(), index.column())
self.set_selected_song_filepath()
self.set_selected_song_qmodel_index()
self.viewport().update() # type: ignore
@ -457,6 +460,7 @@ class MusicTable(QTableView):
Sets the current song filepath
Emits a signal that the current song should start playback
"""
self.set_current_song_qmodel_index()
self.set_current_song_filepath()
self.playSignal.emit(self.current_song_filepath)
@ -653,6 +657,7 @@ class MusicTable(QTableView):
def toggle_play_pause(self):
"""Toggles the currently playing song by emitting a Signal"""
if not self.current_song_filepath:
self.set_current_song_qmodel_index()
self.set_current_song_filepath()
self.playPauseSignal.emit()
@ -847,10 +852,6 @@ class MusicTable(QTableView):
"""Returns the selected song's ID3 tags"""
return id3_remap(get_tags(self.selected_song_filepath)[0])
def get_current_song_album_art(self) -> bytes:
"""Returns the APIC data (album art lol) for the currently playing song"""
return get_album_art(self.current_song_filepath)
def set_selected_song_filepath(self) -> None:
"""Sets the filepath of the currently selected song"""
try:
@ -864,13 +865,11 @@ class MusicTable(QTableView):
filepath = db.query('SELECT filepath FROM song WHERE id = ?', (id,))[0][0]
self.selected_song_filepath = filepath
def set_current_song_filepath(self) -> None:
def set_current_song_filepath(self, filepath=None) -> None:
"""
- Sets the current song filepath to the value in column 'path'
from the current selected row index
- Store the QModelIndex for navigation
"""
self.set_current_song_qmodel_index()
# update the filepath
self.current_song_filepath: str = (
self.current_song_qmodel_index.siblingAtColumn(
@ -879,18 +878,28 @@ class MusicTable(QTableView):
)
def set_current_song_qmodel_index(self, index=None):
"""
Takes in the proxy model index for current song - QModelIndex
converts to model2 index
stores it
"""
if index is None:
index = self.currentIndex()
# map proxy (sortable) model to the original model (used for interactions)
index = self.proxymodel.mapToSource(self.currentIndex())
# set the proxy model index
self.current_song_qmodel_index: QModelIndex = index
model_index: QModelIndex = self.proxymodel.mapToSource(index)
self.current_song_qmodel_index: QModelIndex = model_index
def set_selected_song_qmodel_index(self, index=None):
"""
Takes in the proxy model index for current song - QModelIndex
converts to model2 index
stores it
"""
if index is None:
index = self.currentIndex()
# map proxy (sortable) model to the original model (used for interactions)
index = self.proxymodel.mapToSource(self.currentIndex())
# set the proxy model index
self.selected_song_qmodel_index: QModelIndex = index
model_index: QModelIndex = self.proxymodel.mapToSource(index)
self.selected_song_qmodel_index: QModelIndex = model_index
def load_qapp(self, qapp) -> None:
"""Necessary for using members and methods of main application window"""

72
main.py
View File

@ -26,8 +26,10 @@ from PyQt5.QtWidgets import (
QPushButton,
QStatusBar,
QStyle,
QTableView,
)
from PyQt5.QtCore import (
QModelIndex,
QSize,
QThread,
QUrl,
@ -39,7 +41,7 @@ from PyQt5.QtCore import (
QThreadPool,
QRunnable,
)
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QAudioProbe, QMediaPlaylist
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QAudioProbe, QMediaPlaylist, QMultimedia
from PyQt5.QtGui import QClipboard, QCloseEvent, QFont, QPixmap, QResizeEvent
from utils import (
delete_album_art,
@ -48,9 +50,12 @@ from utils import (
initialize_db,
add_files_to_database,
set_album_art,
id3_remap
)
from components import (
HeaderTags,
MediaPlayer,
MusicTable,
PreferencesWindow,
AudioVisualizer,
CreatePlaylistWindow,
@ -168,10 +173,10 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
self.current_song_album_art: bytes | None = None
# widget bits
self.tableView: MusicTable
self.album_art_scene: QGraphicsScene = QGraphicsScene()
# self.player: QMediaPlayer = QMediaPlayer() # Audio player object
self.player: QMediaPlayer = MediaPlayer()
self.playlist: QMediaPlaylist = QMediaPlaylist()
# set index on choose song
# index is the model2's row number? i guess?
self.probe: QAudioProbe = QAudioProbe() # Gets audio buffer data
@ -200,6 +205,7 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
# sharing functions with other classes and that
self.tableView.load_qapp(self)
self.albumGraphicsView.load_qapp(self)
self.headers = HeaderTags()
# Settings init
self.current_volume: int = int(self.config["settings"]["volume"])
@ -224,7 +230,7 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
)
# self.speedSlider.doubleClicked.connect(lambda: self.on_speed_changed(1))
self.playButton.clicked.connect(self.on_play_clicked) # Click to play/pause
self.previousButton.clicked.connect(self.on_previous_clicked)
self.previousButton.clicked.connect(self.on_prev_clicked)
self.nextButton.clicked.connect(self.on_next_clicked) # Click to next song
# FILE MENU
@ -257,8 +263,8 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
self.tableView.playlistStatsSignal.connect(
self.set_permanent_status_bar_message
)
self.tableView.loadMusicTableSignal.connect(self.load_media_playlist)
self.tableView.load_music_table()
self.player.playlistNextSignal.connect(self.on_next_clicked)
# playlistTreeView
self.playlistTreeView.playlistChoiceSignal.connect(
@ -316,6 +322,14 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
# | |
# |____________________|
def on_playlist_media_changed(self, media: QMediaContent):
"""Update stuff when the song changes"""
if not media.isNull():
file_url = media.canonicalUrl().toLocalFile()
metadata = id3_remap(get_tags(file_url)[0])
if metadata is not None:
self.set_ui_metadata(metadata["title"], metadata["artist"], metadata["album"])
def on_volume_changed(self) -> None:
"""Handles volume changes"""
try:
@ -351,15 +365,29 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
else:
self.playButton.setText("👽")
def on_previous_clicked(self) -> None:
""""""
# TODO: implement this
debug("main.py on_previous_clicked()")
def on_prev_clicked(self) -> None:
"""click previous - go to previous song"""
current_real_index = self.tableView.current_song_qmodel_index
index = self.tableView.proxymodel.mapFromSource(current_real_index)
row: int = index.row()
prev_row: int = row - 1
prev_index: QModelIndex = self.tableView.proxymodel.index(prev_row, index.column())
prev_filepath = prev_index.siblingAtColumn(self.headers.user_headers.index("filepath")).data()
self.play_audio_file(prev_filepath)
self.tableView.set_current_song_qmodel_index(prev_index)
def on_next_clicked(self) -> None:
""""""
# TODO: implement this
debug("main.py on_next_clicked()")
"""click next (or song ended) - go to next song"""
current_real_index = self.tableView.current_song_qmodel_index
index = self.tableView.proxymodel.mapFromSource(current_real_index)
row: int = index.row()
next_row: int = row + 1
next_index: QModelIndex = self.tableView.proxymodel.index(next_row, index.column())
next_filepath = next_index.siblingAtColumn(self.headers.user_headers.index("filepath")).data()
self.play_audio_file(next_filepath)
self.tableView.set_current_song_qmodel_index(next_index)
# ____________________
# | |
@ -368,11 +396,6 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
# | |
# |____________________|
def load_media_playlist(self):
self.proxymodel.row
pass
# self.playlist.
def setup_fonts(self):
"""Initializes font sizes and behaviors for various UI components"""
font: QFont = QFont()
@ -437,8 +460,8 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
"""
if not filepath:
filepath = self.tableView.get_selected_song_filepath()
# get metadata
metadata = get_tags(filepath)[0]
file_url = QUrl.fromLocalFile(filepath)
metadata = id3_remap(get_tags(filepath)[0])
# read the file
url = QUrl.fromLocalFile(filepath)
# load the audio content
@ -451,14 +474,19 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
# assign "now playing" labels & album artwork
if metadata is not None:
artist = metadata["TPE1"][0] if "TPE1" in metadata else None
album = metadata["TALB"][0] if "TALB" in metadata else None
title = metadata["TIT2"][0] if "TIT2" in metadata else None
self.set_ui_metadata(metadata["title"], metadata["artist"], metadata["album"], filepath)
def set_ui_metadata(self, title, artist, album, filepath):
"""
Loads metadata into UI, presumably for current song
But you could pass any text here i guess
album art will always try to be current song
"""
self.artistLabel.setText(artist)
self.albumLabel.setText(album)
self.titleLabel.setText(title)
# set album artwork
album_art_data = self.tableView.get_current_song_album_art()
album_art_data = get_album_art(filepath)
self.albumGraphicsView.load_album_art(album_art_data)
def set_album_art_for_selected_songs(self, album_art_path: str) -> None:

View File

@ -9,6 +9,7 @@ def get_album_art(file: str | None) -> bytes:
# Returns
bytes for album art or placeholder artwork
"""
debug(f'try album art: {file}')
default_image_path = "./assets/default_album_art.jpg"
if file:
try:
@ -20,6 +21,7 @@ def get_album_art(file: str | None) -> bytes:
return audio.getall("APIC")[0].data
except Exception as e:
error(f"Error retrieving album art: {e}")
return bytes()
with open(default_image_path, "rb") as f:
debug("loading placeholder album art")
return f.read()