diff --git a/new.py b/main.bak similarity index 50% rename from new.py rename to main.bak index 89e2a2c..7eeac76 100644 --- a/new.py +++ b/main.bak @@ -4,13 +4,13 @@ import sys import logging from subprocess import run import qdarktheme -from pyqtgraph import PlotWidget + from pyqtgraph import mkBrush from mutagen.id3 import ID3 from mutagen.id3._frames import APIC from configparser import ConfigParser import DBA -from PyQt5 import QtCore, QtGui, QtWidgets +from ui import Ui_MainWindow from PyQt5.QtWidgets import ( QFileDialog, QMainWindow, @@ -24,40 +24,48 @@ from PyQt5.QtCore import QUrl, QTimer, Qt from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QAudioProbe from PyQt5.QtGui import QCloseEvent, QPixmap from utils import scan_for_music, delete_and_create_library_database, initialize_db -from components import ( - PreferencesWindow, - AudioVisualizer, - AlbumArtGraphicsView, - MusicTable, -) +from components import PreferencesWindow, AudioVisualizer + +# Create ui.py file from Qt Designer +# pyuic5 ui.ui -o ui.py -class MainWindow(QMainWindow): - def __init__(self): - super(MainWindow, self).__init__() - global stopped - stopped = False - # Vars +class ApplicationWindow(QMainWindow, Ui_MainWindow): + def __init__(self, qapp): + super(ApplicationWindow, self).__init__() self.setupUi(self) self.setWindowTitle("MusicPom") - self.selected_song_filepath: str | None = None - self.current_song_filepath: str | None = None - self.current_song_metadata: ID3 | dict | None = None - self.current_song_album_art: bytes | None = None - self.album_art_scene: QGraphicsScene = QGraphicsScene() - self.config: ConfigParser = configparser.ConfigParser() - self.player: QMediaPlayer = QMediaPlayer() # Audio player object - self.probe: QAudioProbe = QAudioProbe() # Gets audio data - self.audio_visualizer: AudioVisualizer = AudioVisualizer(self.player) - self.current_volume: int = 50 - # Initialization + self.selected_song_filepath = None + self.current_song_filepath = None + self.current_song_metadata = None + self.current_song_album_art = None + self.album_art_scene = QGraphicsScene() + self.qapp = qapp + # print(f'ApplicationWindow self.qapp: {self.qapp}') + self.tableView.load_qapp(self.qapp) + self.albumGraphicsView.load_qapp(self.qapp) + # print(f'tableView type: {type(self.tableView)}') + self.config = configparser.ConfigParser() self.config.read("config.ini") + global stopped + stopped = False + # Initialization + self.player = QMediaPlayer() # Audio player object + print(f"QMediaPlayer() = {self.player}") + self.probe = QAudioProbe() # Gets audio data + print(f"QAudioProbe() = {self.probe}") + self.timer = QTimer(self) # Audio timing things + print(f"QTimer() = {self.timer}") + # self.music_table_model = QStandardItemModel(self) # Table library listing + self.audio_visualizer = AudioVisualizer(self.player) + self.current_volume = 50 self.player.setVolume(self.current_volume) # Audio probe for processing audio signal in real time + # Provides faster updates than move_slider self.probe.setSource(self.player) self.probe.audioBufferProbed.connect(self.process_probe) + # Slider Timer (realtime playback feedback horizontal bar) - self.timer: QTimer = QTimer(self) # Audio timing things self.timer.start( 150 ) # 150ms update interval solved problem with drag seeking halting playback @@ -73,47 +81,41 @@ class MainWindow(QMainWindow): self.PlotWidget.getAxis("left").setTicks([]) # Remove y-axis ticks self.PlotWidget.getAxis("left").setLabel("") # Remove y-axis label - # _____________ - # | | - # | CONNECTIONS | - # | CONNECTIONS | - # | CONNECTIONS | - # |_____________| - - # FIXME: moving the slider while playing is happening frequently causes playback to halt - the pyqtgraph is also affected by this - # so it must be affecting our QMediaPlayer as well - # self.playbackSlider.sliderMoved[int].connect( - # lambda: self.player.setPosition(self.playbackSlider.value()) - # ) - self.playbackSlider.sliderReleased.connect( + # Connections + # ! FIXME moving the slider while playing is happening frequently causes playback to halt - the pyqtgraph is also affected by this + self.playbackSlider.sliderMoved[int].connect( lambda: self.player.setPosition(self.playbackSlider.value()) - ) # maybe sliderReleased works better than sliderMoved + ) # Move slidet to adjust playback time self.volumeSlider.sliderMoved[int].connect( lambda: self.volume_changed() ) # Move slider to adjust volume - # Playback controls - self.playButton.clicked.connect(self.on_play_clicked) - self.prevButton.clicked.connect(self.on_previous_clicked) - self.nextButton.clicked.connect(self.on_next_clicked) + self.playButton.clicked.connect(self.on_play_clicked) # Click to play/pause + self.previousButton.clicked.connect( + self.on_previous_clicked + ) # Click to previous song + self.nextButton.clicked.connect(self.on_next_clicked) # Click to next song # FILE MENU self.actionOpenFiles.triggered.connect(self.open_files) # Open files window # EDIT MENU + # VIEW MENU self.actionPreferences.triggered.connect( self.open_preferences ) # Open preferences menu - # VIEW MENU # QUICK ACTIONS MENU self.actionScanLibraries.triggered.connect(self.scan_libraries) self.actionDeleteLibrary.triggered.connect(self.clear_database) self.actionDeleteDatabase.triggered.connect(self.delete_database) - ## Music Table | self.tableView triggers - # Listens for the double click event, then plays the song - self.tableView.doubleClicked.connect(self.play_audio_file) - # Listens for the enter key event, then plays the song - self.tableView.enterKey.connect(self.play_audio_file) - # Spacebar for toggle play/pause - self.tableView.playPauseSignal.connect(self.on_play_clicked) - # Album Art | self.albumGraphicsView + ## tableView triggers + self.tableView.doubleClicked.connect( + self.play_audio_file + ) # Listens for the double click event, then plays the song + self.tableView.enterKey.connect( + self.play_audio_file + ) # Listens for the enter key event, then plays the song + self.tableView.playPauseSignal.connect( + self.on_play_clicked + ) # Spacebar toggle play/pause signal + # albumGraphicsView self.albumGraphicsView.albumArtDropped.connect( self.set_album_art_for_selected_songs ) @@ -131,229 +133,6 @@ class MainWindow(QMainWindow): self.tableView.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.tableView.horizontalHeader().setStretchLastSection(False) - def setupUi(self, MainWindow): - MainWindow.setObjectName("MainWindow") - MainWindow.resize(1152, 894) - MainWindow.setStatusTip("") - # Main - self.centralwidget = QtWidgets.QWidget(MainWindow) - self.centralwidget.setObjectName("centralwidget") - # - self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.centralwidget) - self.verticalLayout_3.setObjectName("verticalLayout_3") - self.hLayoutHead = QtWidgets.QHBoxLayout() - self.hLayoutHead.setObjectName("hLayoutHead") - self.vlayoutAlbumArt = QtWidgets.QVBoxLayout() - self.vlayoutAlbumArt.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) - self.vlayoutAlbumArt.setObjectName("vlayoutAlbumArt") - self.albumGraphicsView = AlbumArtGraphicsView(self.centralwidget) - sizePolicy = QtWidgets.QSizePolicy( - QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum - ) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth( - self.albumGraphicsView.sizePolicy().hasHeightForWidth() - ) - self.albumGraphicsView.setSizePolicy(sizePolicy) - self.albumGraphicsView.setMinimumSize(QtCore.QSize(200, 200)) - self.albumGraphicsView.setMaximumSize(QtCore.QSize(16777215, 16777215)) - self.albumGraphicsView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) - self.albumGraphicsView.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.albumGraphicsView.setHorizontalScrollBarPolicy( - QtCore.Qt.ScrollBarAlwaysOff - ) - self.albumGraphicsView.setSizeAdjustPolicy( - QtWidgets.QAbstractScrollArea.AdjustIgnored - ) - self.albumGraphicsView.setInteractive(False) - self.albumGraphicsView.setResizeAnchor(QtWidgets.QGraphicsView.AnchorViewCenter) - self.albumGraphicsView.setViewportUpdateMode( - QtWidgets.QGraphicsView.FullViewportUpdate - ) - self.albumGraphicsView.setObjectName("albumGraphicsView") - self.vlayoutAlbumArt.addWidget(self.albumGraphicsView) - self.hLayoutHead.addLayout(self.vlayoutAlbumArt) - self.vLayoutSongDetails = QtWidgets.QVBoxLayout() - self.vLayoutSongDetails.setObjectName("vLayoutSongDetails") - self.artistLabel = QtWidgets.QLabel(self.centralwidget) - font = QtGui.QFont() - font.setPointSize(24) - font.setBold(True) - font.setWeight(75) - self.artistLabel.setFont(font) - self.artistLabel.setObjectName("artistLabel") - self.vLayoutSongDetails.addWidget(self.artistLabel) - self.titleLabel = QtWidgets.QLabel(self.centralwidget) - font = QtGui.QFont() - font.setPointSize(18) - self.titleLabel.setFont(font) - self.titleLabel.setObjectName("titleLabel") - self.vLayoutSongDetails.addWidget(self.titleLabel) - self.albumLabel = QtWidgets.QLabel(self.centralwidget) - font = QtGui.QFont() - font.setPointSize(16) - font.setBold(False) - font.setItalic(True) - font.setWeight(50) - self.albumLabel.setFont(font) - self.albumLabel.setObjectName("albumLabel") - self.vLayoutSongDetails.addWidget(self.albumLabel) - self.hLayoutHead.addLayout(self.vLayoutSongDetails) - self.vLayoutPlaybackVisuals = QtWidgets.QVBoxLayout() - self.vLayoutPlaybackVisuals.setObjectName("vLayoutPlaybackVisuals") - self.horizontalLayout = QtWidgets.QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") - self.playbackSlider = QtWidgets.QSlider(self.centralwidget) - self.playbackSlider.setOrientation(QtCore.Qt.Horizontal) - self.playbackSlider.setObjectName("playbackSlider") - self.horizontalLayout.addWidget(self.playbackSlider) - self.startTimeLabel = QtWidgets.QLabel(self.centralwidget) - self.startTimeLabel.setObjectName("startTimeLabel") - self.horizontalLayout.addWidget(self.startTimeLabel) - self.slashLabel = QtWidgets.QLabel(self.centralwidget) - self.slashLabel.setObjectName("slashLabel") - self.horizontalLayout.addWidget(self.slashLabel) - self.endTimeLabel = QtWidgets.QLabel(self.centralwidget) - self.endTimeLabel.setObjectName("endTimeLabel") - self.horizontalLayout.addWidget(self.endTimeLabel) - self.vLayoutPlaybackVisuals.addLayout(self.horizontalLayout) - self.PlotWidget = PlotWidget(self.centralwidget) - self.PlotWidget.setObjectName("PlotWidget") - self.vLayoutPlaybackVisuals.addWidget(self.PlotWidget) - self.hLayoutHead.addLayout(self.vLayoutPlaybackVisuals) - self.hLayoutHead.setStretch(0, 1) - self.hLayoutHead.setStretch(1, 4) - self.hLayoutHead.setStretch(2, 6) - self.verticalLayout_3.addLayout(self.hLayoutHead) - self.hLayoutMusicTable = QtWidgets.QHBoxLayout() - self.hLayoutMusicTable.setObjectName("hLayoutMusicTable") - self.tableView = MusicTable(self.centralwidget) - sizePolicy = QtWidgets.QSizePolicy( - QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum - ) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth(self.tableView.sizePolicy().hasHeightForWidth()) - self.tableView.setSizePolicy(sizePolicy) - self.tableView.setMaximumSize(QtCore.QSize(32000, 32000)) - self.tableView.setAcceptDrops(True) - self.tableView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.tableView.setSizeAdjustPolicy( - QtWidgets.QAbstractScrollArea.AdjustToContents - ) - self.tableView.setEditTriggers( - QtWidgets.QAbstractItemView.AnyKeyPressed - | QtWidgets.QAbstractItemView.EditKeyPressed - ) - self.tableView.setAlternatingRowColors(True) - self.tableView.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) - self.tableView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) - self.tableView.setSortingEnabled(True) - self.tableView.setObjectName("tableView") - self.tableView.horizontalHeader().setCascadingSectionResizes(True) - self.tableView.horizontalHeader().setStretchLastSection(True) - self.tableView.verticalHeader().setVisible(False) - self.hLayoutMusicTable.addWidget(self.tableView) - self.verticalLayout_3.addLayout(self.hLayoutMusicTable) - self.hLayoutControls = QtWidgets.QHBoxLayout() - self.hLayoutControls.setObjectName("hLayoutControls") - self.prevButton = QtWidgets.QPushButton(self.centralwidget) - font = QtGui.QFont() - font.setPointSize(28) - self.prevButton.setFont(font) - self.prevButton.setObjectName("prevButton") - self.hLayoutControls.addWidget(self.prevButton) - self.playButton = QtWidgets.QPushButton(self.centralwidget) - font = QtGui.QFont() - font.setPointSize(28) - self.playButton.setFont(font) - self.playButton.setObjectName("playButton") - self.hLayoutControls.addWidget(self.playButton) - self.nextButton = QtWidgets.QPushButton(self.centralwidget) - font = QtGui.QFont() - font.setPointSize(28) - self.nextButton.setFont(font) - self.nextButton.setObjectName("nextButton") - self.hLayoutControls.addWidget(self.nextButton) - self.verticalLayout_3.addLayout(self.hLayoutControls) - self.hLayoutControls2 = QtWidgets.QHBoxLayout() - self.hLayoutControls2.setObjectName("hLayoutControls2") - self.volumeSlider = QtWidgets.QSlider(self.centralwidget) - self.volumeSlider.setMaximum(100) - self.volumeSlider.setProperty("value", 50) - self.volumeSlider.setOrientation(QtCore.Qt.Horizontal) - self.volumeSlider.setObjectName("volumeSlider") - self.hLayoutControls2.addWidget(self.volumeSlider) - self.verticalLayout_3.addLayout(self.hLayoutControls2) - self.verticalLayout_3.setStretch(0, 3) - self.verticalLayout_3.setStretch(1, 8) - self.verticalLayout_3.setStretch(2, 1) - self.verticalLayout_3.setStretch(3, 1) - MainWindow.setCentralWidget(self.centralwidget) - self.menubar = QtWidgets.QMenuBar(MainWindow) - self.menubar.setGeometry(QtCore.QRect(0, 0, 1152, 41)) - self.menubar.setObjectName("menubar") - self.menuFile = QtWidgets.QMenu(self.menubar) - self.menuFile.setObjectName("menuFile") - self.menuEdit = QtWidgets.QMenu(self.menubar) - self.menuEdit.setObjectName("menuEdit") - self.menuView = QtWidgets.QMenu(self.menubar) - self.menuView.setObjectName("menuView") - self.menuQuick_Actions = QtWidgets.QMenu(self.menubar) - self.menuQuick_Actions.setObjectName("menuQuick_Actions") - MainWindow.setMenuBar(self.menubar) - self.statusbar = QtWidgets.QStatusBar(MainWindow) - self.statusbar.setObjectName("statusbar") - MainWindow.setStatusBar(self.statusbar) - self.actionPreferences = QtWidgets.QAction(MainWindow) - self.actionPreferences.setObjectName("actionPreferences") - self.actionScanLibraries = QtWidgets.QAction(MainWindow) - self.actionScanLibraries.setObjectName("actionScanLibraries") - self.actionDeleteLibrary = QtWidgets.QAction(MainWindow) - self.actionDeleteLibrary.setObjectName("actionDeleteLibrary") - self.actionOpenFiles = QtWidgets.QAction(MainWindow) - self.actionOpenFiles.setObjectName("actionOpenFiles") - self.actionDeleteDatabase = QtWidgets.QAction(MainWindow) - self.actionDeleteDatabase.setObjectName("actionDeleteDatabase") - self.menuFile.addAction(self.actionOpenFiles) - self.menuEdit.addAction(self.actionPreferences) - self.menuQuick_Actions.addAction(self.actionScanLibraries) - self.menuQuick_Actions.addAction(self.actionDeleteLibrary) - self.menuQuick_Actions.addAction(self.actionDeleteDatabase) - self.menubar.addAction(self.menuFile.menuAction()) - self.menubar.addAction(self.menuEdit.menuAction()) - self.menubar.addAction(self.menuView.menuAction()) - self.menubar.addAction(self.menuQuick_Actions.menuAction()) - - self.retranslateUi(MainWindow) - QtCore.QMetaObject.connectSlotsByName(MainWindow) - - def retranslateUi(self, MainWindow): - _translate = QtCore.QCoreApplication.translate - MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) - self.artistLabel.setText(_translate("MainWindow", "artist")) - self.titleLabel.setText(_translate("MainWindow", "song title")) - self.albumLabel.setText(_translate("MainWindow", "album")) - self.startTimeLabel.setText(_translate("MainWindow", "00:00")) - self.slashLabel.setText(_translate("MainWindow", "/")) - self.endTimeLabel.setText(_translate("MainWindow", "00:00")) - self.prevButton.setText(_translate("MainWindow", "⏮️")) - self.playButton.setText(_translate("MainWindow", "▶️")) - self.nextButton.setText(_translate("MainWindow", "⏭️")) - self.menuFile.setTitle(_translate("MainWindow", "File")) - self.menuEdit.setTitle(_translate("MainWindow", "Edit")) - self.menuView.setTitle(_translate("MainWindow", "View")) - self.menuQuick_Actions.setTitle(_translate("MainWindow", "Quick-Actions")) - self.actionPreferences.setText(_translate("MainWindow", "Preferences")) - self.actionPreferences.setStatusTip( - _translate("MainWindow", "Open preferences") - ) - self.actionScanLibraries.setText(_translate("MainWindow", "Scan libraries")) - self.actionDeleteLibrary.setText(_translate("MainWindow", "Delete Library")) - self.actionOpenFiles.setText(_translate("MainWindow", "Open file(s)")) - self.actionDeleteDatabase.setText(_translate("MainWindow", "Delete Database")) - def closeEvent(self, a0: QCloseEvent | None) -> None: """Save settings when closing the application""" # MusicTable/tableView column widths @@ -368,6 +147,39 @@ class MainWindow(QMainWindow): self.config.write(configfile) super().closeEvent(a0) + def play_audio_file(self) -> None: + """Start playback of tableView.current_song_filepath track & moves playback slider""" + self.current_song_metadata = ( + self.tableView.get_current_song_metadata() + ) # get metadata + self.current_song_album_art = self.tableView.get_current_song_album_art() + url = QUrl.fromLocalFile( + self.tableView.get_current_song_filepath() + ) # read the file + content = QMediaContent(url) # load the audio content + self.player.setMedia(content) # what content to play + self.player.play() # play + self.move_slider() # mover + + # assign metadata + artist = ( + self.current_song_metadata["TPE1"][0] + if "artist" in self.current_song_metadata + else None + ) + album = ( + self.current_song_metadata["TALB"][0] + if "album" in self.current_song_metadata + else None + ) + title = self.current_song_metadata["TIT2"][0] + # edit labels + self.artistLabel.setText(artist) + self.albumLabel.setText(album) + self.titleLabel.setText(title) + # set album artwork + self.load_album_art(self.current_song_album_art) + def load_album_art(self, album_art_data) -> None: """Displays the album art for the currently playing track in the GraphicsView""" if self.current_song_album_art: @@ -457,56 +269,6 @@ class MainWindow(QMainWindow): except Exception as e: print(f"Error processing {file}: {e}") - def play_audio_file(self) -> None: - """Start playback of tableView.current_song_filepath track & moves playback slider""" - self.current_song_metadata = self.tableView.get_current_song_metadata() - self.current_song_album_art = self.tableView.get_current_song_album_art() - # read the file - url = QUrl.fromLocalFile(self.tableView.get_current_song_filepath()) - content = QMediaContent(url) # load the audio content - self.player.setMedia(content) # what content to play - self.player.play() # play - self.move_slider() # mover - - # assign metadata - artist = ( - self.current_song_metadata["TPE1"][0] - if "artist" in self.current_song_metadata - else None - ) - album = ( - self.current_song_metadata["TALB"][0] - if "album" in self.current_song_metadata - else None - ) - title = self.current_song_metadata["TIT2"][0] - # edit labels - self.artistLabel.setText(artist) - self.albumLabel.setText(album) - self.titleLabel.setText(title) - # set album artwork - self.load_album_art(self.current_song_album_art) - - def on_play_clicked(self) -> None: - """Updates the Play & Pause buttons when clicked""" - if self.player.state() == QMediaPlayer.State.PlayingState: - self.player.pause() - self.playButton.setText("▶️") - else: - if self.player.state() == QMediaPlayer.State.PausedState: - self.player.play() - self.playButton.setText("⏸️") - else: - self.play_audio_file() - self.playButton.setText("⏸️") - - def on_previous_clicked(self) -> None: - """""" - print("previous") - - def on_next_clicked(self) -> None: - print("next") - def update_audio_visualization(self) -> None: """Handles upading points on the pyqtgraph visual""" self.clear_audio_visualization() @@ -523,9 +285,6 @@ class MainWindow(QMainWindow): if stopped: return else: - if self.playbackSlider.isSliderDown(): - # Prevents slider from updating when dragging - return # Update the slider if self.player.state() == QMediaPlayer.State.PlayingState: self.playbackSlider.setMinimum(0) @@ -551,6 +310,26 @@ class MainWindow(QMainWindow): except Exception as e: print(f"Changing volume error: {e}") + def on_play_clicked(self) -> None: + """Updates the Play & Pause buttons when clicked""" + if self.player.state() == QMediaPlayer.State.PlayingState: + self.player.pause() + self.playButton.setText("▶️") + else: + if self.player.state() == QMediaPlayer.State.PausedState: + self.player.play() + self.playButton.setText("⏸️") + else: + self.play_audio_file() + self.playButton.setText("⏸️") + + def on_previous_clicked(self) -> None: + """""" + print("previous") + + def on_next_clicked(self) -> None: + print("next") + def open_files(self) -> None: """Opens the open files window""" open_files_window = QFileDialog( @@ -650,6 +429,6 @@ if __name__ == "__main__": # Dark theme >:3 qdarktheme.setup_theme() # Show the UI - ui = MainWindow() + ui = ApplicationWindow(app) ui.show() sys.exit(app.exec_()) diff --git a/main.py b/main.py index 7eeac76..89e2a2c 100644 --- a/main.py +++ b/main.py @@ -4,13 +4,13 @@ import sys import logging from subprocess import run import qdarktheme - +from pyqtgraph import PlotWidget from pyqtgraph import mkBrush from mutagen.id3 import ID3 from mutagen.id3._frames import APIC from configparser import ConfigParser import DBA -from ui import Ui_MainWindow +from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtWidgets import ( QFileDialog, QMainWindow, @@ -24,48 +24,40 @@ from PyQt5.QtCore import QUrl, QTimer, Qt from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QAudioProbe from PyQt5.QtGui import QCloseEvent, QPixmap from utils import scan_for_music, delete_and_create_library_database, initialize_db -from components import PreferencesWindow, AudioVisualizer - -# Create ui.py file from Qt Designer -# pyuic5 ui.ui -o ui.py +from components import ( + PreferencesWindow, + AudioVisualizer, + AlbumArtGraphicsView, + MusicTable, +) -class ApplicationWindow(QMainWindow, Ui_MainWindow): - def __init__(self, qapp): - super(ApplicationWindow, self).__init__() - self.setupUi(self) - self.setWindowTitle("MusicPom") - self.selected_song_filepath = None - self.current_song_filepath = None - self.current_song_metadata = None - self.current_song_album_art = None - self.album_art_scene = QGraphicsScene() - self.qapp = qapp - # print(f'ApplicationWindow self.qapp: {self.qapp}') - self.tableView.load_qapp(self.qapp) - self.albumGraphicsView.load_qapp(self.qapp) - # print(f'tableView type: {type(self.tableView)}') - self.config = configparser.ConfigParser() - self.config.read("config.ini") +class MainWindow(QMainWindow): + def __init__(self): + super(MainWindow, self).__init__() global stopped stopped = False + # Vars + self.setupUi(self) + self.setWindowTitle("MusicPom") + self.selected_song_filepath: str | None = None + self.current_song_filepath: str | None = None + self.current_song_metadata: ID3 | dict | None = None + self.current_song_album_art: bytes | None = None + self.album_art_scene: QGraphicsScene = QGraphicsScene() + self.config: ConfigParser = configparser.ConfigParser() + self.player: QMediaPlayer = QMediaPlayer() # Audio player object + self.probe: QAudioProbe = QAudioProbe() # Gets audio data + self.audio_visualizer: AudioVisualizer = AudioVisualizer(self.player) + self.current_volume: int = 50 # Initialization - self.player = QMediaPlayer() # Audio player object - print(f"QMediaPlayer() = {self.player}") - self.probe = QAudioProbe() # Gets audio data - print(f"QAudioProbe() = {self.probe}") - self.timer = QTimer(self) # Audio timing things - print(f"QTimer() = {self.timer}") - # self.music_table_model = QStandardItemModel(self) # Table library listing - self.audio_visualizer = AudioVisualizer(self.player) - self.current_volume = 50 + self.config.read("config.ini") self.player.setVolume(self.current_volume) # Audio probe for processing audio signal in real time - # Provides faster updates than move_slider self.probe.setSource(self.player) self.probe.audioBufferProbed.connect(self.process_probe) - # Slider Timer (realtime playback feedback horizontal bar) + self.timer: QTimer = QTimer(self) # Audio timing things self.timer.start( 150 ) # 150ms update interval solved problem with drag seeking halting playback @@ -81,41 +73,47 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow): self.PlotWidget.getAxis("left").setTicks([]) # Remove y-axis ticks self.PlotWidget.getAxis("left").setLabel("") # Remove y-axis label - # Connections - # ! FIXME moving the slider while playing is happening frequently causes playback to halt - the pyqtgraph is also affected by this - self.playbackSlider.sliderMoved[int].connect( + # _____________ + # | | + # | CONNECTIONS | + # | CONNECTIONS | + # | CONNECTIONS | + # |_____________| + + # FIXME: moving the slider while playing is happening frequently causes playback to halt - the pyqtgraph is also affected by this + # so it must be affecting our QMediaPlayer as well + # self.playbackSlider.sliderMoved[int].connect( + # lambda: self.player.setPosition(self.playbackSlider.value()) + # ) + self.playbackSlider.sliderReleased.connect( lambda: self.player.setPosition(self.playbackSlider.value()) - ) # Move slidet to adjust playback time + ) # maybe sliderReleased works better than sliderMoved self.volumeSlider.sliderMoved[int].connect( lambda: self.volume_changed() ) # Move slider to adjust volume - self.playButton.clicked.connect(self.on_play_clicked) # Click to play/pause - self.previousButton.clicked.connect( - self.on_previous_clicked - ) # Click to previous song - self.nextButton.clicked.connect(self.on_next_clicked) # Click to next song + # Playback controls + self.playButton.clicked.connect(self.on_play_clicked) + self.prevButton.clicked.connect(self.on_previous_clicked) + self.nextButton.clicked.connect(self.on_next_clicked) # FILE MENU self.actionOpenFiles.triggered.connect(self.open_files) # Open files window # EDIT MENU - # VIEW MENU self.actionPreferences.triggered.connect( self.open_preferences ) # Open preferences menu + # VIEW MENU # QUICK ACTIONS MENU self.actionScanLibraries.triggered.connect(self.scan_libraries) self.actionDeleteLibrary.triggered.connect(self.clear_database) self.actionDeleteDatabase.triggered.connect(self.delete_database) - ## tableView triggers - self.tableView.doubleClicked.connect( - self.play_audio_file - ) # Listens for the double click event, then plays the song - self.tableView.enterKey.connect( - self.play_audio_file - ) # Listens for the enter key event, then plays the song - self.tableView.playPauseSignal.connect( - self.on_play_clicked - ) # Spacebar toggle play/pause signal - # albumGraphicsView + ## Music Table | self.tableView triggers + # Listens for the double click event, then plays the song + self.tableView.doubleClicked.connect(self.play_audio_file) + # Listens for the enter key event, then plays the song + self.tableView.enterKey.connect(self.play_audio_file) + # Spacebar for toggle play/pause + self.tableView.playPauseSignal.connect(self.on_play_clicked) + # Album Art | self.albumGraphicsView self.albumGraphicsView.albumArtDropped.connect( self.set_album_art_for_selected_songs ) @@ -133,6 +131,229 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow): self.tableView.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.tableView.horizontalHeader().setStretchLastSection(False) + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(1152, 894) + MainWindow.setStatusTip("") + # Main + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + # + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.centralwidget) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.hLayoutHead = QtWidgets.QHBoxLayout() + self.hLayoutHead.setObjectName("hLayoutHead") + self.vlayoutAlbumArt = QtWidgets.QVBoxLayout() + self.vlayoutAlbumArt.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) + self.vlayoutAlbumArt.setObjectName("vlayoutAlbumArt") + self.albumGraphicsView = AlbumArtGraphicsView(self.centralwidget) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum + ) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth( + self.albumGraphicsView.sizePolicy().hasHeightForWidth() + ) + self.albumGraphicsView.setSizePolicy(sizePolicy) + self.albumGraphicsView.setMinimumSize(QtCore.QSize(200, 200)) + self.albumGraphicsView.setMaximumSize(QtCore.QSize(16777215, 16777215)) + self.albumGraphicsView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.albumGraphicsView.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.albumGraphicsView.setHorizontalScrollBarPolicy( + QtCore.Qt.ScrollBarAlwaysOff + ) + self.albumGraphicsView.setSizeAdjustPolicy( + QtWidgets.QAbstractScrollArea.AdjustIgnored + ) + self.albumGraphicsView.setInteractive(False) + self.albumGraphicsView.setResizeAnchor(QtWidgets.QGraphicsView.AnchorViewCenter) + self.albumGraphicsView.setViewportUpdateMode( + QtWidgets.QGraphicsView.FullViewportUpdate + ) + self.albumGraphicsView.setObjectName("albumGraphicsView") + self.vlayoutAlbumArt.addWidget(self.albumGraphicsView) + self.hLayoutHead.addLayout(self.vlayoutAlbumArt) + self.vLayoutSongDetails = QtWidgets.QVBoxLayout() + self.vLayoutSongDetails.setObjectName("vLayoutSongDetails") + self.artistLabel = QtWidgets.QLabel(self.centralwidget) + font = QtGui.QFont() + font.setPointSize(24) + font.setBold(True) + font.setWeight(75) + self.artistLabel.setFont(font) + self.artistLabel.setObjectName("artistLabel") + self.vLayoutSongDetails.addWidget(self.artistLabel) + self.titleLabel = QtWidgets.QLabel(self.centralwidget) + font = QtGui.QFont() + font.setPointSize(18) + self.titleLabel.setFont(font) + self.titleLabel.setObjectName("titleLabel") + self.vLayoutSongDetails.addWidget(self.titleLabel) + self.albumLabel = QtWidgets.QLabel(self.centralwidget) + font = QtGui.QFont() + font.setPointSize(16) + font.setBold(False) + font.setItalic(True) + font.setWeight(50) + self.albumLabel.setFont(font) + self.albumLabel.setObjectName("albumLabel") + self.vLayoutSongDetails.addWidget(self.albumLabel) + self.hLayoutHead.addLayout(self.vLayoutSongDetails) + self.vLayoutPlaybackVisuals = QtWidgets.QVBoxLayout() + self.vLayoutPlaybackVisuals.setObjectName("vLayoutPlaybackVisuals") + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.playbackSlider = QtWidgets.QSlider(self.centralwidget) + self.playbackSlider.setOrientation(QtCore.Qt.Horizontal) + self.playbackSlider.setObjectName("playbackSlider") + self.horizontalLayout.addWidget(self.playbackSlider) + self.startTimeLabel = QtWidgets.QLabel(self.centralwidget) + self.startTimeLabel.setObjectName("startTimeLabel") + self.horizontalLayout.addWidget(self.startTimeLabel) + self.slashLabel = QtWidgets.QLabel(self.centralwidget) + self.slashLabel.setObjectName("slashLabel") + self.horizontalLayout.addWidget(self.slashLabel) + self.endTimeLabel = QtWidgets.QLabel(self.centralwidget) + self.endTimeLabel.setObjectName("endTimeLabel") + self.horizontalLayout.addWidget(self.endTimeLabel) + self.vLayoutPlaybackVisuals.addLayout(self.horizontalLayout) + self.PlotWidget = PlotWidget(self.centralwidget) + self.PlotWidget.setObjectName("PlotWidget") + self.vLayoutPlaybackVisuals.addWidget(self.PlotWidget) + self.hLayoutHead.addLayout(self.vLayoutPlaybackVisuals) + self.hLayoutHead.setStretch(0, 1) + self.hLayoutHead.setStretch(1, 4) + self.hLayoutHead.setStretch(2, 6) + self.verticalLayout_3.addLayout(self.hLayoutHead) + self.hLayoutMusicTable = QtWidgets.QHBoxLayout() + self.hLayoutMusicTable.setObjectName("hLayoutMusicTable") + self.tableView = MusicTable(self.centralwidget) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum + ) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(1) + sizePolicy.setHeightForWidth(self.tableView.sizePolicy().hasHeightForWidth()) + self.tableView.setSizePolicy(sizePolicy) + self.tableView.setMaximumSize(QtCore.QSize(32000, 32000)) + self.tableView.setAcceptDrops(True) + self.tableView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.tableView.setSizeAdjustPolicy( + QtWidgets.QAbstractScrollArea.AdjustToContents + ) + self.tableView.setEditTriggers( + QtWidgets.QAbstractItemView.AnyKeyPressed + | QtWidgets.QAbstractItemView.EditKeyPressed + ) + self.tableView.setAlternatingRowColors(True) + self.tableView.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + self.tableView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + self.tableView.setSortingEnabled(True) + self.tableView.setObjectName("tableView") + self.tableView.horizontalHeader().setCascadingSectionResizes(True) + self.tableView.horizontalHeader().setStretchLastSection(True) + self.tableView.verticalHeader().setVisible(False) + self.hLayoutMusicTable.addWidget(self.tableView) + self.verticalLayout_3.addLayout(self.hLayoutMusicTable) + self.hLayoutControls = QtWidgets.QHBoxLayout() + self.hLayoutControls.setObjectName("hLayoutControls") + self.prevButton = QtWidgets.QPushButton(self.centralwidget) + font = QtGui.QFont() + font.setPointSize(28) + self.prevButton.setFont(font) + self.prevButton.setObjectName("prevButton") + self.hLayoutControls.addWidget(self.prevButton) + self.playButton = QtWidgets.QPushButton(self.centralwidget) + font = QtGui.QFont() + font.setPointSize(28) + self.playButton.setFont(font) + self.playButton.setObjectName("playButton") + self.hLayoutControls.addWidget(self.playButton) + self.nextButton = QtWidgets.QPushButton(self.centralwidget) + font = QtGui.QFont() + font.setPointSize(28) + self.nextButton.setFont(font) + self.nextButton.setObjectName("nextButton") + self.hLayoutControls.addWidget(self.nextButton) + self.verticalLayout_3.addLayout(self.hLayoutControls) + self.hLayoutControls2 = QtWidgets.QHBoxLayout() + self.hLayoutControls2.setObjectName("hLayoutControls2") + self.volumeSlider = QtWidgets.QSlider(self.centralwidget) + self.volumeSlider.setMaximum(100) + self.volumeSlider.setProperty("value", 50) + self.volumeSlider.setOrientation(QtCore.Qt.Horizontal) + self.volumeSlider.setObjectName("volumeSlider") + self.hLayoutControls2.addWidget(self.volumeSlider) + self.verticalLayout_3.addLayout(self.hLayoutControls2) + self.verticalLayout_3.setStretch(0, 3) + self.verticalLayout_3.setStretch(1, 8) + self.verticalLayout_3.setStretch(2, 1) + self.verticalLayout_3.setStretch(3, 1) + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 1152, 41)) + self.menubar.setObjectName("menubar") + self.menuFile = QtWidgets.QMenu(self.menubar) + self.menuFile.setObjectName("menuFile") + self.menuEdit = QtWidgets.QMenu(self.menubar) + self.menuEdit.setObjectName("menuEdit") + self.menuView = QtWidgets.QMenu(self.menubar) + self.menuView.setObjectName("menuView") + self.menuQuick_Actions = QtWidgets.QMenu(self.menubar) + self.menuQuick_Actions.setObjectName("menuQuick_Actions") + MainWindow.setMenuBar(self.menubar) + self.statusbar = QtWidgets.QStatusBar(MainWindow) + self.statusbar.setObjectName("statusbar") + MainWindow.setStatusBar(self.statusbar) + self.actionPreferences = QtWidgets.QAction(MainWindow) + self.actionPreferences.setObjectName("actionPreferences") + self.actionScanLibraries = QtWidgets.QAction(MainWindow) + self.actionScanLibraries.setObjectName("actionScanLibraries") + self.actionDeleteLibrary = QtWidgets.QAction(MainWindow) + self.actionDeleteLibrary.setObjectName("actionDeleteLibrary") + self.actionOpenFiles = QtWidgets.QAction(MainWindow) + self.actionOpenFiles.setObjectName("actionOpenFiles") + self.actionDeleteDatabase = QtWidgets.QAction(MainWindow) + self.actionDeleteDatabase.setObjectName("actionDeleteDatabase") + self.menuFile.addAction(self.actionOpenFiles) + self.menuEdit.addAction(self.actionPreferences) + self.menuQuick_Actions.addAction(self.actionScanLibraries) + self.menuQuick_Actions.addAction(self.actionDeleteLibrary) + self.menuQuick_Actions.addAction(self.actionDeleteDatabase) + self.menubar.addAction(self.menuFile.menuAction()) + self.menubar.addAction(self.menuEdit.menuAction()) + self.menubar.addAction(self.menuView.menuAction()) + self.menubar.addAction(self.menuQuick_Actions.menuAction()) + + self.retranslateUi(MainWindow) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) + self.artistLabel.setText(_translate("MainWindow", "artist")) + self.titleLabel.setText(_translate("MainWindow", "song title")) + self.albumLabel.setText(_translate("MainWindow", "album")) + self.startTimeLabel.setText(_translate("MainWindow", "00:00")) + self.slashLabel.setText(_translate("MainWindow", "/")) + self.endTimeLabel.setText(_translate("MainWindow", "00:00")) + self.prevButton.setText(_translate("MainWindow", "⏮️")) + self.playButton.setText(_translate("MainWindow", "▶️")) + self.nextButton.setText(_translate("MainWindow", "⏭️")) + self.menuFile.setTitle(_translate("MainWindow", "File")) + self.menuEdit.setTitle(_translate("MainWindow", "Edit")) + self.menuView.setTitle(_translate("MainWindow", "View")) + self.menuQuick_Actions.setTitle(_translate("MainWindow", "Quick-Actions")) + self.actionPreferences.setText(_translate("MainWindow", "Preferences")) + self.actionPreferences.setStatusTip( + _translate("MainWindow", "Open preferences") + ) + self.actionScanLibraries.setText(_translate("MainWindow", "Scan libraries")) + self.actionDeleteLibrary.setText(_translate("MainWindow", "Delete Library")) + self.actionOpenFiles.setText(_translate("MainWindow", "Open file(s)")) + self.actionDeleteDatabase.setText(_translate("MainWindow", "Delete Database")) + def closeEvent(self, a0: QCloseEvent | None) -> None: """Save settings when closing the application""" # MusicTable/tableView column widths @@ -147,39 +368,6 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow): self.config.write(configfile) super().closeEvent(a0) - def play_audio_file(self) -> None: - """Start playback of tableView.current_song_filepath track & moves playback slider""" - self.current_song_metadata = ( - self.tableView.get_current_song_metadata() - ) # get metadata - self.current_song_album_art = self.tableView.get_current_song_album_art() - url = QUrl.fromLocalFile( - self.tableView.get_current_song_filepath() - ) # read the file - content = QMediaContent(url) # load the audio content - self.player.setMedia(content) # what content to play - self.player.play() # play - self.move_slider() # mover - - # assign metadata - artist = ( - self.current_song_metadata["TPE1"][0] - if "artist" in self.current_song_metadata - else None - ) - album = ( - self.current_song_metadata["TALB"][0] - if "album" in self.current_song_metadata - else None - ) - title = self.current_song_metadata["TIT2"][0] - # edit labels - self.artistLabel.setText(artist) - self.albumLabel.setText(album) - self.titleLabel.setText(title) - # set album artwork - self.load_album_art(self.current_song_album_art) - def load_album_art(self, album_art_data) -> None: """Displays the album art for the currently playing track in the GraphicsView""" if self.current_song_album_art: @@ -269,6 +457,56 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow): except Exception as e: print(f"Error processing {file}: {e}") + def play_audio_file(self) -> None: + """Start playback of tableView.current_song_filepath track & moves playback slider""" + self.current_song_metadata = self.tableView.get_current_song_metadata() + self.current_song_album_art = self.tableView.get_current_song_album_art() + # read the file + url = QUrl.fromLocalFile(self.tableView.get_current_song_filepath()) + content = QMediaContent(url) # load the audio content + self.player.setMedia(content) # what content to play + self.player.play() # play + self.move_slider() # mover + + # assign metadata + artist = ( + self.current_song_metadata["TPE1"][0] + if "artist" in self.current_song_metadata + else None + ) + album = ( + self.current_song_metadata["TALB"][0] + if "album" in self.current_song_metadata + else None + ) + title = self.current_song_metadata["TIT2"][0] + # edit labels + self.artistLabel.setText(artist) + self.albumLabel.setText(album) + self.titleLabel.setText(title) + # set album artwork + self.load_album_art(self.current_song_album_art) + + def on_play_clicked(self) -> None: + """Updates the Play & Pause buttons when clicked""" + if self.player.state() == QMediaPlayer.State.PlayingState: + self.player.pause() + self.playButton.setText("▶️") + else: + if self.player.state() == QMediaPlayer.State.PausedState: + self.player.play() + self.playButton.setText("⏸️") + else: + self.play_audio_file() + self.playButton.setText("⏸️") + + def on_previous_clicked(self) -> None: + """""" + print("previous") + + def on_next_clicked(self) -> None: + print("next") + def update_audio_visualization(self) -> None: """Handles upading points on the pyqtgraph visual""" self.clear_audio_visualization() @@ -285,6 +523,9 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow): if stopped: return else: + if self.playbackSlider.isSliderDown(): + # Prevents slider from updating when dragging + return # Update the slider if self.player.state() == QMediaPlayer.State.PlayingState: self.playbackSlider.setMinimum(0) @@ -310,26 +551,6 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow): except Exception as e: print(f"Changing volume error: {e}") - def on_play_clicked(self) -> None: - """Updates the Play & Pause buttons when clicked""" - if self.player.state() == QMediaPlayer.State.PlayingState: - self.player.pause() - self.playButton.setText("▶️") - else: - if self.player.state() == QMediaPlayer.State.PausedState: - self.player.play() - self.playButton.setText("⏸️") - else: - self.play_audio_file() - self.playButton.setText("⏸️") - - def on_previous_clicked(self) -> None: - """""" - print("previous") - - def on_next_clicked(self) -> None: - print("next") - def open_files(self) -> None: """Opens the open files window""" open_files_window = QFileDialog( @@ -429,6 +650,6 @@ if __name__ == "__main__": # Dark theme >:3 qdarktheme.setup_theme() # Show the UI - ui = ApplicationWindow(app) + ui = MainWindow() ui.show() sys.exit(app.exec_())