From 97e335ba0076a68e90e00c9cedae7900b3f1646d Mon Sep 17 00:00:00 2001 From: billypom on debian Date: Sun, 4 Aug 2024 14:16:23 -0400 Subject: [PATCH] playlist pane, return to designer --- bak.main | 434 ------------------------- bak.main2 | 668 +++++++++++++++++++++++++++++++++++++++ components/LeftPane.py | 36 +++ components/MusicTable.py | 10 +- components/__init__.py | 1 + main.py | 421 ++++++------------------ ui.py | 64 ++-- ui.ui | 382 ++++++++++++---------- utils/fft_analyser.py | 2 +- 9 files changed, 1044 insertions(+), 974 deletions(-) delete mode 100644 bak.main create mode 100644 bak.main2 create mode 100644 components/LeftPane.py diff --git a/bak.main b/bak.main deleted file mode 100644 index 7eeac76..0000000 --- a/bak.main +++ /dev/null @@ -1,434 +0,0 @@ -import os -import configparser -import sys -import logging -from subprocess import run -import qdarktheme - -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.QtWidgets import ( - QFileDialog, - QMainWindow, - QApplication, - QGraphicsScene, - QHeaderView, - QGraphicsPixmapItem, - QMessageBox, -) -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 - - -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") - 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.start( - 150 - ) # 150ms update interval solved problem with drag seeking halting playback - self.timer.timeout.connect(self.move_slider) - - # Graphics plot - self.PlotWidget.setXRange(0, 100, padding=0) # x axis range - self.PlotWidget.setYRange(0, 0.3, padding=0) # y axis range - self.PlotWidget.getAxis("bottom").setTicks([]) # Remove x-axis ticks - self.PlotWidget.getAxis("bottom").setLabel("") # Remove x-axis label - self.PlotWidget.setLogMode(False, False) - # Remove y-axis labels and decorations - 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( - lambda: self.player.setPosition(self.playbackSlider.value()) - ) # Move slidet to adjust playback time - 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 - # 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 - # 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 - self.albumGraphicsView.albumArtDropped.connect( - self.set_album_art_for_selected_songs - ) - self.albumGraphicsView.albumArtDeleted.connect( - self.delete_album_art_for_selected_songs - ) - self.tableView.viewport().installEventFilter( - self - ) # for drag & drop functionality - # set column widths - table_view_column_widths = str(self.config["table"]["column_widths"]).split(",") - for i in range(self.tableView.model.columnCount()): - self.tableView.setColumnWidth(i, int(table_view_column_widths[i])) - # dont extend last column past table view border - self.tableView.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) - self.tableView.horizontalHeader().setStretchLastSection(False) - - def closeEvent(self, a0: QCloseEvent | None) -> None: - """Save settings when closing the application""" - # MusicTable/tableView column widths - list_of_column_widths = [] - for i in range(self.tableView.model.columnCount()): - list_of_column_widths.append(str(self.tableView.columnWidth(i))) - column_widths_as_string = ",".join(list_of_column_widths) - self.config["table"]["column_widths"] = column_widths_as_string - - # Save the config - with open("config.ini", "w") as configfile: - 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: - # Clear the scene - try: - self.album_art_scene.clear() - except Exception: - pass - # Reset the scene - self.albumGraphicsView.setScene(None) - # Create pixmap for album art - pixmap = QPixmap() - pixmap.loadFromData(album_art_data) - # Create a QGraphicsPixmapItem for more control over pic - pixmapItem = QGraphicsPixmapItem(pixmap) - pixmapItem.setTransformationMode( - Qt.SmoothTransformation - ) # For better quality scaling - # Add pixmap item to the scene - self.album_art_scene.addItem(pixmapItem) - # Set the scene - self.albumGraphicsView.setScene(self.album_art_scene) - # Adjust the album art scaling - self.adjustPixmapScaling(pixmapItem) - - def adjustPixmapScaling(self, pixmapItem) -> None: - """Adjust the scaling of the pixmap item to fit the QGraphicsView, maintaining aspect ratio""" - viewWidth = self.albumGraphicsView.width() - viewHeight = self.albumGraphicsView.height() - pixmapSize = pixmapItem.pixmap().size() - # Calculate scaling factor while maintaining aspect ratio - scaleX = viewWidth / pixmapSize.width() - scaleY = viewHeight / pixmapSize.height() - scaleFactor = min(scaleX, scaleY) - # Apply scaling to the pixmap item - pixmapItem.setScale(scaleFactor) - - def set_album_art_for_selected_songs(self, album_art_path: str) -> None: - """Sets the ID3 tag APIC (album art) for all selected song filepaths""" - selected_songs = self.tableView.get_selected_songs_filepaths() - for song in selected_songs: - print(f"updating album art for {song}") - self.update_album_art_for_song(song, album_art_path) - - def update_album_art_for_song( - self, song_file_path: str, album_art_path: str - ) -> None: - """Updates the ID3 tag APIC (album art) for 1 song""" - # audio = MP3(song_file_path, ID3=ID3) - audio = ID3(song_file_path) - # Remove existing APIC Frames (album art) - audio.delall("APIC") - # Add the album art - with open(album_art_path, "rb") as album_art_file: - if album_art_path.endswith(".jpg") or album_art_path.endswith(".jpeg"): - audio.add( - APIC( - encoding=3, # 3 = utf-8 - mime="image/jpeg", - type=3, # 3 = cover image - desc="Cover", - data=album_art_file.read(), - ) - ) - elif album_art_path.endswith(".png"): - audio.add( - APIC( - encoding=3, # 3 = utf-8 - mime="image/png", - type=3, # 3 = cover image - desc="Cover", - data=album_art_file.read(), - ) - ) - audio.save() - - def delete_album_art_for_selected_songs(self) -> None: - """Handles deleting the ID3 tag APIC (album art) for all selected songs""" - filepaths = self.tableView.get_selected_songs_filepaths() - for file in filepaths: - # delete APIC data - try: - audio = ID3(file) - if "APIC:" in audio: - del audio["APIC"] - audio.save() - except Exception as e: - print(f"Error processing {file}: {e}") - - def update_audio_visualization(self) -> None: - """Handles upading points on the pyqtgraph visual""" - self.clear_audio_visualization() - y = self.audio_visualizer.get_amplitudes() - x = [i for i in range(len(y))] - self.PlotWidget.plot(x, y, fillLevel=0, fillBrush=mkBrush("b")) - self.PlotWidget.show() - - def clear_audio_visualization(self) -> None: - self.PlotWidget.clear() - - def move_slider(self) -> None: - """Handles moving the playback slider""" - if stopped: - return - else: - # Update the slider - if self.player.state() == QMediaPlayer.State.PlayingState: - self.playbackSlider.setMinimum(0) - self.playbackSlider.setMaximum(self.player.duration()) - slider_position = self.player.position() - self.playbackSlider.setValue(slider_position) - current_minutes, current_seconds = divmod(slider_position / 1000, 60) - duration_minutes, duration_seconds = divmod( - self.player.duration() / 1000, 60 - ) - self.startTimeLabel.setText( - f"{int(current_minutes):02d}:{int(current_seconds):02d}" - ) - self.endTimeLabel.setText( - f"{int(duration_minutes):02d}:{int(duration_seconds):02d}" - ) - - def volume_changed(self) -> None: - """Handles volume changes""" - try: - self.current_volume = self.volumeSlider.value() - self.player.setVolume(self.current_volume) - 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( - self, "Open file(s)", ".", "Audio files (*.mp3)" - ) - # QFileDialog.FileMode enum { AnyFile, ExistingFile, Directory, ExistingFiles } - open_files_window.setFileMode(QFileDialog.ExistingFiles) - open_files_window.exec_() - filenames = open_files_window.selectedFiles() - print("file names chosen") - print(filenames) - self.tableView.add_files(filenames) - - def open_preferences(self) -> None: - """Opens the preferences window""" - preferences_window = PreferencesWindow(self.config) - preferences_window.exec_() # Display the preferences window modally - - def scan_libraries(self) -> None: - """Scans for new files in the configured library folder - Refreshes the datagridview""" - scan_for_music() - self.tableView.fetch_library() - - def clear_database(self) -> None: - """Clears all songs from the database""" - reply = QMessageBox.question( - self, - "Confirmation", - "Clear all songs from database?", - QMessageBox.Yes | QMessageBox.No, - QMessageBox.Yes, - ) - if reply: - delete_and_create_library_database() - self.tableView.fetch_library() - - def delete_database(self) -> None: - """Deletes the entire database""" - reply = QMessageBox.question( - self, - "Confirmation", - "Delete database?", - QMessageBox.Yes | QMessageBox.No, - QMessageBox.Yes, - ) - if reply: - initialize_db() - self.tableView.fetch_library() - - def reinitialize_database(self) -> None: - """Clears all tables in database and recreates""" - reply = QMessageBox.question( - self, - "Confirmation", - "Recreate the database?", - QMessageBox.Yes | QMessageBox.No, - QMessageBox.Yes, - ) - if reply: - initialize_db() - self.tableView.fetch_library() - - def process_probe(self, buff) -> None: - buff.startTime() - self.update_audio_visualization() - - -if __name__ == "__main__": - # First run initialization - if not os.path.exists("config.ini"): - # Create config file from sample - run(["cp", "sample_config.ini", "config.ini"]) - config = ConfigParser() - config.read("config.ini") - db_name = config.get("db", "database") - db_path = db_name.split("/") - db_path.pop() - path_as_string = "/".join(db_path) - if not os.path.exists(path_as_string): - os.makedirs(path_as_string) - # Create database on first run - with DBA.DBAccess() as db: - with open("utils/init.sql", "r") as file: - lines = file.read() - for statement in lines.split(";"): - print(f"executing [{statement}]") - db.execute(statement, ()) - # logging setup - logging.basicConfig(filename="musicpom.log", encoding="utf-8", level=logging.DEBUG) - # Allow for dynamic imports of my custom classes and utilities - project_root = os.path.abspath(os.path.dirname(__file__)) - sys.path.append(project_root) - # Start the app - app = QApplication(sys.argv) - print(f"main.py app: {app}") - # Dark theme >:3 - qdarktheme.setup_theme() - # Show the UI - ui = ApplicationWindow(app) - ui.show() - sys.exit(app.exec_()) diff --git a/bak.main2 b/bak.main2 new file mode 100644 index 0000000..23a67e4 --- /dev/null +++ b/bak.main2 @@ -0,0 +1,668 @@ +import os +import configparser +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 PyQt5.QtWidgets import ( + QFileDialog, + QInputDialog, + QMainWindow, + QApplication, + QGraphicsScene, + QHeaderView, + QGraphicsPixmapItem, + QMessageBox, +) +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, + CreatePlaylistWindow, +) + + +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.config.read("config.ini") + self.player.setVolume(self.current_volume) + # Audio probe for processing audio signal in real time + 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 + self.timer.timeout.connect(self.move_slider) + + # Graphics plot + self.PlotWidget.setXRange(0, 100, padding=0) # x axis range + self.PlotWidget.setYRange(0, 0.8, padding=0) # y axis range + # Remove axis labels and decorations + # self.PlotWidget.setLogMode(False, False) + self.PlotWidget.getAxis("bottom").setTicks([]) # Remove x-axis ticks + self.PlotWidget.getAxis("bottom").setLabel("") # Remove x-axis label + 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( + lambda: self.player.setPosition(self.playbackSlider.value()) + ) # maybe sliderReleased works better than sliderMoved + 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) + # FILE MENU + self.actionOpenFiles.triggered.connect(self.open_files) # Open files window + self.actionNewPlaylist.triggered.connect(self.create_playlist) + # EDIT 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 + self.albumGraphicsView.albumArtDropped.connect( + self.set_album_art_for_selected_songs + ) + self.albumGraphicsView.albumArtDeleted.connect( + self.delete_album_art_for_selected_songs + ) + self.tableView.viewport().installEventFilter( + self + ) # for drag & drop functionality + # set column widths + table_view_column_widths = str(self.config["table"]["column_widths"]).split(",") + for i in range(self.tableView.model.columnCount()): + self.tableView.setColumnWidth(i, int(table_view_column_widths[i])) + # dont extend last column past table view border + 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.actionNewPlaylist = QtWidgets.QAction(MainWindow) + self.actionNewPlaylist.setObjectName("actionNewPlaylist") + self.actionDeleteDatabase = QtWidgets.QAction(MainWindow) + self.actionDeleteDatabase.setObjectName("actionDeleteDatabase") + self.menuFile.addAction(self.actionOpenFiles) + self.menuFile.addAction(self.actionNewPlaylist) + 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.actionNewPlaylist.setText(_translate("MainWindow", "New playlist")) + self.actionDeleteDatabase.setText(_translate("MainWindow", "Delete Database")) + + def closeEvent(self, a0: QCloseEvent | None) -> None: + """Save settings when closing the application""" + # MusicTable/tableView column widths + list_of_column_widths = [] + for i in range(self.tableView.model.columnCount()): + list_of_column_widths.append(str(self.tableView.columnWidth(i))) + column_widths_as_string = ",".join(list_of_column_widths) + self.config["table"]["column_widths"] = column_widths_as_string + + # Save the config + with open("config.ini", "w") as configfile: + self.config.write(configfile) + super().closeEvent(a0) + + 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: + # Clear the scene + try: + self.album_art_scene.clear() + except Exception: + pass + # Reset the scene + self.albumGraphicsView.setScene(None) + # Create pixmap for album art + pixmap = QPixmap() + pixmap.loadFromData(album_art_data) + # Create a QGraphicsPixmapItem for more control over pic + pixmapItem = QGraphicsPixmapItem(pixmap) + pixmapItem.setTransformationMode( + Qt.SmoothTransformation + ) # For better quality scaling + # Add pixmap item to the scene + self.album_art_scene.addItem(pixmapItem) + # Set the scene + self.albumGraphicsView.setScene(self.album_art_scene) + # Adjust the album art scaling + self.adjustPixmapScaling(pixmapItem) + + def adjustPixmapScaling(self, pixmapItem) -> None: + """Adjust the scaling of the pixmap item to fit the QGraphicsView, maintaining aspect ratio""" + viewWidth = self.albumGraphicsView.width() + viewHeight = self.albumGraphicsView.height() + pixmapSize = pixmapItem.pixmap().size() + # Calculate scaling factor while maintaining aspect ratio + scaleX = viewWidth / pixmapSize.width() + scaleY = viewHeight / pixmapSize.height() + scaleFactor = min(scaleX, scaleY) + # Apply scaling to the pixmap item + pixmapItem.setScale(scaleFactor) + + def set_album_art_for_selected_songs(self, album_art_path: str) -> None: + """Sets the ID3 tag APIC (album art) for all selected song filepaths""" + selected_songs = self.tableView.get_selected_songs_filepaths() + for song in selected_songs: + print(f"updating album art for {song}") + self.update_album_art_for_song(song, album_art_path) + + def update_album_art_for_song( + self, song_file_path: str, album_art_path: str + ) -> None: + """Updates the ID3 tag APIC (album art) for 1 song""" + # audio = MP3(song_file_path, ID3=ID3) + audio = ID3(song_file_path) + # Remove existing APIC Frames (album art) + audio.delall("APIC") + # Add the album art + with open(album_art_path, "rb") as album_art_file: + if album_art_path.endswith(".jpg") or album_art_path.endswith(".jpeg"): + audio.add( + APIC( + encoding=3, # 3 = utf-8 + mime="image/jpeg", + type=3, # 3 = cover image + desc="Cover", + data=album_art_file.read(), + ) + ) + elif album_art_path.endswith(".png"): + audio.add( + APIC( + encoding=3, # 3 = utf-8 + mime="image/png", + type=3, # 3 = cover image + desc="Cover", + data=album_art_file.read(), + ) + ) + audio.save() + + def delete_album_art_for_selected_songs(self) -> None: + """Handles deleting the ID3 tag APIC (album art) for all selected songs""" + filepaths = self.tableView.get_selected_songs_filepaths() + for file in filepaths: + # delete APIC data + try: + audio = ID3(file) + if "APIC:" in audio: + del audio["APIC"] + audio.save() + 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() + y = self.audio_visualizer.get_amplitudes() + x = [i for i in range(len(y))] + self.PlotWidget.plot(x, y, fillLevel=0, fillBrush=mkBrush("b")) + self.PlotWidget.show() + + def clear_audio_visualization(self) -> None: + self.PlotWidget.clear() + + def move_slider(self) -> None: + """Handles moving the playback slider""" + 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) + self.playbackSlider.setMaximum(self.player.duration()) + slider_position = self.player.position() + self.playbackSlider.setValue(slider_position) + current_minutes, current_seconds = divmod(slider_position / 1000, 60) + duration_minutes, duration_seconds = divmod( + self.player.duration() / 1000, 60 + ) + self.startTimeLabel.setText( + f"{int(current_minutes):02d}:{int(current_seconds):02d}" + ) + self.endTimeLabel.setText( + f"{int(duration_minutes):02d}:{int(duration_seconds):02d}" + ) + + def volume_changed(self) -> None: + """Handles volume changes""" + try: + self.current_volume = self.volumeSlider.value() + self.player.setVolume(self.current_volume) + except Exception as e: + print(f"Changing volume error: {e}") + + def open_files(self) -> None: + """Opens the open files window""" + open_files_window = QFileDialog( + self, "Open file(s)", ".", "Audio files (*.mp3)" + ) + # QFileDialog.FileMode enum { AnyFile, ExistingFile, Directory, ExistingFiles } + open_files_window.setFileMode(QFileDialog.ExistingFiles) + open_files_window.exec_() + filenames = open_files_window.selectedFiles() + print("main.py open_files() | file names chosen") + print(filenames) + self.tableView.add_files(filenames) + + def create_playlist(self) -> None: + """Creates a database record for a playlist, given a name""" + create_playlist_window = CreatePlaylistWindow() + create_playlist_window.exec_() + + def open_preferences(self) -> None: + """Opens the preferences window""" + preferences_window = PreferencesWindow(self.config) + preferences_window.exec_() # Display the preferences window modally + + def scan_libraries(self) -> None: + """Scans for new files in the configured library folder + Refreshes the datagridview""" + scan_for_music() + self.tableView.fetch_library() + + def clear_database(self) -> None: + """Clears all songs from the database""" + reply = QMessageBox.question( + self, + "Confirmation", + "Clear all songs from database?", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.Yes, + ) + if reply: + delete_and_create_library_database() + self.tableView.fetch_library() + + def delete_database(self) -> None: + """Deletes the entire database""" + reply = QMessageBox.question( + self, + "Confirmation", + "Delete database?", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.Yes, + ) + if reply: + initialize_db() + self.tableView.fetch_library() + + def reinitialize_database(self) -> None: + """Clears all tables in database and recreates""" + reply = QMessageBox.question( + self, + "Confirmation", + "Recreate the database?", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.Yes, + ) + if reply: + initialize_db() + self.tableView.fetch_library() + + def process_probe(self, buff) -> None: + buff.startTime() + self.update_audio_visualization() + + +if __name__ == "__main__": + # First run initialization + if not os.path.exists("config.ini"): + # Create config file from sample + run(["cp", "sample_config.ini", "config.ini"]) + config = ConfigParser() + config.read("config.ini") + db_name = config.get("db", "database") + db_path = db_name.split("/") + db_path.pop() + path_as_string = "/".join(db_path) + if not os.path.exists(path_as_string): + os.makedirs(path_as_string) + # Create database on first run + with DBA.DBAccess() as db: + with open("utils/init.sql", "r") as file: + lines = file.read() + for statement in lines.split(";"): + print(f"executing [{statement}]") + db.execute(statement, ()) + + # logging setup + logging.basicConfig(filename="musicpom.log", encoding="utf-8", level=logging.DEBUG) + # Allow for dynamic imports of my custom classes and utilities + project_root = os.path.abspath(os.path.dirname(__file__)) + sys.path.append(project_root) + # Start the app + app = QApplication(sys.argv) + print(f"main.py app: {app}") + # Dark theme >:3 + qdarktheme.setup_theme() + # Show the UI + ui = MainWindow() + ui.show() + sys.exit(app.exec_()) diff --git a/components/LeftPane.py b/components/LeftPane.py new file mode 100644 index 0000000..14d606f --- /dev/null +++ b/components/LeftPane.py @@ -0,0 +1,36 @@ +from PyQt5.QtWidgets import QListWidget, QTreeWidget, QTreeWidgetItem +import DBA + + +class PlaylistWidgetItem(QTreeWidgetItem): + def __init__(self, parent, id, name): + super().__init__([name], 0) + self.id = id + + +class LeftPane(QTreeWidget): + def __init__(self: QTreeWidget, parent=None): + super().__init__(parent) + library_root = QTreeWidgetItem(["Library"]) + self.addTopLevelItem(library_root) + all_songs_branch = QTreeWidgetItem(["All Songs"]) + library_root.addChild(all_songs_branch) + + playlists_root = QTreeWidgetItem(["Playlists"]) + self.addTopLevelItem(playlists_root) + with DBA.DBAccess() as db: + playlists = db.query("SELECT id, name FROM playlist;", ()) + for playlist in playlists: + branch = PlaylistWidgetItem(self, playlist[0], playlist[1]) + playlists_root.addChild(branch) + + self.currentItemChanged.connect(self.playlist_clicked) + + def playlist_clicked(self, item): + if isinstance(item, PlaylistWidgetItem): + print(f"ID: {item.id}, name: {item.text(0)}") + elif item.text(0).lower() == "all songs": + self.all_songs_selected() + + def all_songs_selected(self): + print("all songs") diff --git a/components/MusicTable.py b/components/MusicTable.py index b50f74b..e99d5d2 100644 --- a/components/MusicTable.py +++ b/components/MusicTable.py @@ -19,11 +19,11 @@ from PyQt5.QtWidgets import ( from PyQt5.QtCore import QAbstractItemModel, QModelIndex, Qt, pyqtSignal, QTimer from components.LyricsWindow import LyricsWindow from components.AddToPlaylistWindow import AddToPlaylistWindow -from utils import delete_song_id_from_database -from utils import add_files_to_library -from utils import update_song_in_library -from utils import get_id3_tags -from utils import get_album_art +from utils.delete_song_id_from_database import delete_song_id_from_database +from utils.add_files_to_library import add_files_to_library +from utils.update_song_in_library import update_song_in_library +from utils.get_id3_tags import get_id3_tags +from utils.get_album_art import get_album_art from utils import set_id3_tag from subprocess import Popen import logging diff --git a/components/__init__.py b/components/__init__.py index cedb3fe..55054ab 100644 --- a/components/__init__.py +++ b/components/__init__.py @@ -6,3 +6,4 @@ from .ErrorDialog import ErrorDialog from .LyricsWindow import LyricsWindow from .AddToPlaylistWindow import AddToPlaylistWindow from .CreatePlaylistWindow import CreatePlaylistWindow +from .LeftPane import LeftPane diff --git a/main.py b/main.py index 23a67e4..c2961a3 100644 --- a/main.py +++ b/main.py @@ -4,16 +4,15 @@ 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, - QInputDialog, QMainWindow, QApplication, QGraphicsScene, @@ -28,18 +27,18 @@ from utils import scan_for_music, delete_and_create_library_database, initialize from components import ( PreferencesWindow, AudioVisualizer, - AlbumArtGraphicsView, - MusicTable, CreatePlaylistWindow, ) +# Create ui.py file from Qt Designer +# pyuic5 ui.ui -o ui.py -class MainWindow(QMainWindow): - def __init__(self): - super(MainWindow, self).__init__() + +class ApplicationWindow(QMainWindow, Ui_MainWindow): + def __init__(self, qapp): + super(ApplicationWindow, self).__init__() global stopped stopped = False - # Vars self.setupUi(self) self.setWindowTitle("MusicPom") self.selected_song_filepath: str | None = None @@ -52,71 +51,73 @@ class MainWindow(QMainWindow): self.probe: QAudioProbe = QAudioProbe() # Gets audio data self.audio_visualizer: AudioVisualizer = AudioVisualizer(self.player) self.current_volume: int = 50 - # Initialization + self.qapp = qapp + # print(f'ApplicationWindow self.qapp: {self.qapp}') + self.tableView.load_qapp(self.qapp) + self.albumGraphicsView.load_qapp(self.qapp) self.config.read("config.ini") + # Initialization + self.timer = QTimer(self) # Audio timing things self.player.setVolume(self.current_volume) # Audio probe for processing audio signal in real time 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 + self.timer.start(100) self.timer.timeout.connect(self.move_slider) # Graphics plot self.PlotWidget.setXRange(0, 100, padding=0) # x axis range - self.PlotWidget.setYRange(0, 0.8, padding=0) # y axis range - # Remove axis labels and decorations - # self.PlotWidget.setLogMode(False, False) + self.PlotWidget.setYRange(0, 0.3, padding=0) # y axis range self.PlotWidget.getAxis("bottom").setTicks([]) # Remove x-axis ticks self.PlotWidget.getAxis("bottom").setLabel("") # Remove x-axis label + self.PlotWidget.setLogMode(False, False) + # Remove y-axis labels and decorations self.PlotWidget.getAxis("left").setTicks([]) # Remove y-axis ticks self.PlotWidget.getAxis("left").setLabel("") # Remove y-axis label - # _____________ - # | | - # | CONNECTIONS | - # | CONNECTIONS | - # | CONNECTIONS | - # |_____________| + # Playlist left-pane + self.playlistTreeView - # 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()) - # ) + # Connections self.playbackSlider.sliderReleased.connect( lambda: self.player.setPosition(self.playbackSlider.value()) ) # maybe sliderReleased works better than sliderMoved 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 self.actionNewPlaylist.triggered.connect(self.create_playlist) # 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 ) @@ -134,233 +135,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.actionNewPlaylist = QtWidgets.QAction(MainWindow) - self.actionNewPlaylist.setObjectName("actionNewPlaylist") - self.actionDeleteDatabase = QtWidgets.QAction(MainWindow) - self.actionDeleteDatabase.setObjectName("actionDeleteDatabase") - self.menuFile.addAction(self.actionOpenFiles) - self.menuFile.addAction(self.actionNewPlaylist) - 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.actionNewPlaylist.setText(_translate("MainWindow", "New playlist")) - self.actionDeleteDatabase.setText(_translate("MainWindow", "Delete Database")) - def closeEvent(self, a0: QCloseEvent | None) -> None: """Save settings when closing the application""" # MusicTable/tableView column widths @@ -375,6 +149,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: @@ -464,56 +271,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() @@ -530,9 +287,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) @@ -558,6 +312,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( @@ -567,8 +341,6 @@ class MainWindow(QMainWindow): open_files_window.setFileMode(QFileDialog.ExistingFiles) open_files_window.exec_() filenames = open_files_window.selectedFiles() - print("main.py open_files() | file names chosen") - print(filenames) self.tableView.add_files(filenames) def create_playlist(self) -> None: @@ -651,7 +423,6 @@ if __name__ == "__main__": for statement in lines.split(";"): print(f"executing [{statement}]") db.execute(statement, ()) - # logging setup logging.basicConfig(filename="musicpom.log", encoding="utf-8", level=logging.DEBUG) # Allow for dynamic imports of my custom classes and utilities @@ -663,6 +434,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/ui.py b/ui.py index 6c6ff13..fa5b705 100644 --- a/ui.py +++ b/ui.py @@ -20,36 +20,29 @@ class Ui_MainWindow(object): self.centralwidget.setObjectName("centralwidget") self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.centralwidget) self.verticalLayout_3.setObjectName("verticalLayout_3") + self.verticalLayout = QtWidgets.QVBoxLayout() + self.verticalLayout.setContentsMargins(-1, -1, 10, -1) + self.verticalLayout.setObjectName("verticalLayout") 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 = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth( - self.albumGraphicsView.sizePolicy().hasHeightForWidth() - ) + 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.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.setViewportUpdateMode(QtWidgets.QGraphicsView.FullViewportUpdate) self.albumGraphicsView.setObjectName("albumGraphicsView") self.vlayoutAlbumArt.addWidget(self.albumGraphicsView) self.hLayoutHead.addLayout(self.vlayoutAlbumArt) @@ -104,13 +97,15 @@ class Ui_MainWindow(object): self.hLayoutHead.setStretch(0, 1) self.hLayoutHead.setStretch(1, 4) self.hLayoutHead.setStretch(2, 6) - self.verticalLayout_3.addLayout(self.hLayoutHead) + self.verticalLayout.addLayout(self.hLayoutHead) self.hLayoutMusicTable = QtWidgets.QHBoxLayout() + self.hLayoutMusicTable.setContentsMargins(0, -1, 0, -1) self.hLayoutMusicTable.setObjectName("hLayoutMusicTable") + self.playlistTreeView = LeftPane(self.centralwidget) + self.playlistTreeView.setObjectName("playlistTreeView") + self.hLayoutMusicTable.addWidget(self.playlistTreeView) self.tableView = MusicTable(self.centralwidget) - sizePolicy = QtWidgets.QSizePolicy( - QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum - ) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.tableView.sizePolicy().hasHeightForWidth()) @@ -118,13 +113,8 @@ class Ui_MainWindow(object): 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.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) @@ -134,7 +124,10 @@ class Ui_MainWindow(object): self.tableView.horizontalHeader().setStretchLastSection(True) self.tableView.verticalHeader().setVisible(False) self.hLayoutMusicTable.addWidget(self.tableView) - self.verticalLayout_3.addLayout(self.hLayoutMusicTable) + self.hLayoutMusicTable.setStretch(0, 2) + self.hLayoutMusicTable.setStretch(1, 10) + self.verticalLayout.addLayout(self.hLayoutMusicTable) + self.verticalLayout_3.addLayout(self.verticalLayout) self.hLayoutControls = QtWidgets.QHBoxLayout() self.hLayoutControls.setObjectName("hLayoutControls") self.previousButton = QtWidgets.QPushButton(self.centralwidget) @@ -165,10 +158,9 @@ class Ui_MainWindow(object): 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(0, 10) + self.verticalLayout_3.setStretch(1, 1) 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)) @@ -195,7 +187,10 @@ class Ui_MainWindow(object): self.actionOpenFiles.setObjectName("actionOpenFiles") self.actionDeleteDatabase = QtWidgets.QAction(MainWindow) self.actionDeleteDatabase.setObjectName("actionDeleteDatabase") + self.actionNewPlaylist = QtWidgets.QAction(MainWindow) + self.actionNewPlaylist.setObjectName("actionNewPlaylist") self.menuFile.addAction(self.actionOpenFiles) + self.menuFile.addAction(self.actionNewPlaylist) self.menuEdit.addAction(self.actionPreferences) self.menuQuick_Actions.addAction(self.actionScanLibraries) self.menuQuick_Actions.addAction(self.actionDeleteLibrary) @@ -225,14 +220,11 @@ class Ui_MainWindow(object): 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.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")) - - -from components import AlbumArtGraphicsView, MusicTable + self.actionNewPlaylist.setText(_translate("MainWindow", "New playlist")) +from components import AlbumArtGraphicsView, LeftPane, MusicTable from pyqtgraph import PlotWidget diff --git a/ui.ui b/ui.ui index b8c8cef..0cb5076 100644 --- a/ui.ui +++ b/ui.ui @@ -17,200 +17,222 @@ - + - + + + 6 + + + 0 + - - - QLayout::SetFixedSize - + - - - - 0 - 0 - + + + QLayout::SetFixedSize - - - 200 - 200 - - - - - 16777215 - 16777215 - - - - Qt::CustomContextMenu - - - Qt::ScrollBarAlwaysOff - - - Qt::ScrollBarAlwaysOff - - - QAbstractScrollArea::AdjustIgnored - - - false - - - QGraphicsView::AnchorViewCenter - - - QGraphicsView::FullViewportUpdate - - - - - - - - - - - - 24 - 75 - true - - - - artist - - - - - - - - 18 - - - - song title - - - - - - - - 16 - 50 - true - false - - - - album - - - - - - - - - - - - Qt::Horizontal + + + + 0 + 0 + - - - - - - 00:00 + + + 200 + 200 + - - - - - - / + + + 16777215 + 16777215 + - - - - - - 00:00 + + Qt::CustomContextMenu + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + QAbstractScrollArea::AdjustIgnored + + + false + + + QGraphicsView::AnchorViewCenter + + + QGraphicsView::FullViewportUpdate - + + + + + + 24 + 75 + true + + + + artist + + + + + + + + 18 + + + + song title + + + + + + + + 16 + 50 + true + false + + + + album + + + + + + + + + + + + + Qt::Horizontal + + + + + + + 00:00 + + + + + + + / + + + + + + + 00:00 + + + + + + + + + + + + + + + + 0 + + + 0 + + + + + + + + + 0 + 1 + + + + + 32000 + 32000 + + + + true + + + Qt::ScrollBarAlwaysOff + + + QAbstractScrollArea::AdjustToContents + + + QAbstractItemView::AnyKeyPressed|QAbstractItemView::EditKeyPressed + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + true + + + true + + + true + + + false + + - - - - - - - 0 - 1 - - - - - 32000 - 32000 - - - - true - - - Qt::ScrollBarAlwaysOff - - - QAbstractScrollArea::AdjustToContents - - - QAbstractItemView::AnyKeyPressed|QAbstractItemView::EditKeyPressed - - - true - - - QAbstractItemView::ExtendedSelection - - - QAbstractItemView::SelectRows - - - true - - - true - - - true - - - false - - - - - + + 6 + @@ -251,6 +273,9 @@ + + 6 + @@ -282,6 +307,7 @@ File + @@ -336,6 +362,11 @@ Delete Database + + + New playlist + + @@ -354,6 +385,11 @@ QGraphicsView
components
+ + LeftPane + QTreeView +
components
+
diff --git a/utils/fft_analyser.py b/utils/fft_analyser.py index 65ba47a..99f3e4e 100644 --- a/utils/fft_analyser.py +++ b/utils/fft_analyser.py @@ -57,7 +57,7 @@ class FFTAnalyser(QtCore.QThread): freq = np.fft.fftfreq(fourier.size, d=0.05) amps = 2 / v_sample.size * np.abs(fourier) data = np.array([freq, amps]).T - print(data) + # print(data) point_range = 1 / self.resolution point_samples = []