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
+
@@ -354,6 +385,11 @@
QGraphicsView
+
+ LeftPane
+ QTreeView
+
+
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 = []