playlist pane, return to designer

This commit is contained in:
billypom on debian 2024-08-04 14:16:23 -04:00
parent 68590deea2
commit 97e335ba00
9 changed files with 1044 additions and 974 deletions

434
bak.main
View File

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

668
bak.main2 Normal file
View File

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

36
components/LeftPane.py Normal file
View File

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

View File

@ -19,11 +19,11 @@ from PyQt5.QtWidgets import (
from PyQt5.QtCore import QAbstractItemModel, QModelIndex, Qt, pyqtSignal, QTimer from PyQt5.QtCore import QAbstractItemModel, QModelIndex, Qt, pyqtSignal, QTimer
from components.LyricsWindow import LyricsWindow from components.LyricsWindow import LyricsWindow
from components.AddToPlaylistWindow import AddToPlaylistWindow from components.AddToPlaylistWindow import AddToPlaylistWindow
from utils import delete_song_id_from_database from utils.delete_song_id_from_database import delete_song_id_from_database
from utils import add_files_to_library from utils.add_files_to_library import add_files_to_library
from utils import update_song_in_library from utils.update_song_in_library import update_song_in_library
from utils import get_id3_tags from utils.get_id3_tags import get_id3_tags
from utils import get_album_art from utils.get_album_art import get_album_art
from utils import set_id3_tag from utils import set_id3_tag
from subprocess import Popen from subprocess import Popen
import logging import logging

View File

@ -6,3 +6,4 @@ from .ErrorDialog import ErrorDialog
from .LyricsWindow import LyricsWindow from .LyricsWindow import LyricsWindow
from .AddToPlaylistWindow import AddToPlaylistWindow from .AddToPlaylistWindow import AddToPlaylistWindow
from .CreatePlaylistWindow import CreatePlaylistWindow from .CreatePlaylistWindow import CreatePlaylistWindow
from .LeftPane import LeftPane

421
main.py
View File

@ -4,16 +4,15 @@ import sys
import logging import logging
from subprocess import run from subprocess import run
import qdarktheme import qdarktheme
from pyqtgraph import PlotWidget
from pyqtgraph import mkBrush from pyqtgraph import mkBrush
from mutagen.id3 import ID3 from mutagen.id3 import ID3
from mutagen.id3._frames import APIC from mutagen.id3._frames import APIC
from configparser import ConfigParser from configparser import ConfigParser
import DBA import DBA
from PyQt5 import QtCore, QtGui, QtWidgets from ui import Ui_MainWindow
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QFileDialog, QFileDialog,
QInputDialog,
QMainWindow, QMainWindow,
QApplication, QApplication,
QGraphicsScene, QGraphicsScene,
@ -28,18 +27,18 @@ from utils import scan_for_music, delete_and_create_library_database, initialize
from components import ( from components import (
PreferencesWindow, PreferencesWindow,
AudioVisualizer, AudioVisualizer,
AlbumArtGraphicsView,
MusicTable,
CreatePlaylistWindow, CreatePlaylistWindow,
) )
# Create ui.py file from Qt Designer
# pyuic5 ui.ui -o ui.py
class MainWindow(QMainWindow):
def __init__(self): class ApplicationWindow(QMainWindow, Ui_MainWindow):
super(MainWindow, self).__init__() def __init__(self, qapp):
super(ApplicationWindow, self).__init__()
global stopped global stopped
stopped = False stopped = False
# Vars
self.setupUi(self) self.setupUi(self)
self.setWindowTitle("MusicPom") self.setWindowTitle("MusicPom")
self.selected_song_filepath: str | None = None self.selected_song_filepath: str | None = None
@ -52,71 +51,73 @@ class MainWindow(QMainWindow):
self.probe: QAudioProbe = QAudioProbe() # Gets audio data self.probe: QAudioProbe = QAudioProbe() # Gets audio data
self.audio_visualizer: AudioVisualizer = AudioVisualizer(self.player) self.audio_visualizer: AudioVisualizer = AudioVisualizer(self.player)
self.current_volume: int = 50 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") self.config.read("config.ini")
# Initialization
self.timer = QTimer(self) # Audio timing things
self.player.setVolume(self.current_volume) self.player.setVolume(self.current_volume)
# Audio probe for processing audio signal in real time # Audio probe for processing audio signal in real time
self.probe.setSource(self.player) self.probe.setSource(self.player)
self.probe.audioBufferProbed.connect(self.process_probe) self.probe.audioBufferProbed.connect(self.process_probe)
# Slider Timer (realtime playback feedback horizontal bar) # Slider Timer (realtime playback feedback horizontal bar)
self.timer: QTimer = QTimer(self) # Audio timing things self.timer.start(100)
self.timer.start(
150
) # 150ms update interval solved problem with drag seeking halting playback
self.timer.timeout.connect(self.move_slider) self.timer.timeout.connect(self.move_slider)
# Graphics plot # Graphics plot
self.PlotWidget.setXRange(0, 100, padding=0) # x axis range self.PlotWidget.setXRange(0, 100, padding=0) # x axis range
self.PlotWidget.setYRange(0, 0.8, padding=0) # y axis range self.PlotWidget.setYRange(0, 0.3, 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").setTicks([]) # Remove x-axis ticks
self.PlotWidget.getAxis("bottom").setLabel("") # Remove x-axis label 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").setTicks([]) # Remove y-axis ticks
self.PlotWidget.getAxis("left").setLabel("") # Remove y-axis label self.PlotWidget.getAxis("left").setLabel("") # Remove y-axis label
# _____________ # Playlist left-pane
# | | self.playlistTreeView
# | CONNECTIONS |
# | CONNECTIONS |
# | CONNECTIONS |
# |_____________|
# FIXME: moving the slider while playing is happening frequently causes playback to halt - the pyqtgraph is also affected by this # Connections
# 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( self.playbackSlider.sliderReleased.connect(
lambda: self.player.setPosition(self.playbackSlider.value()) lambda: self.player.setPosition(self.playbackSlider.value())
) # maybe sliderReleased works better than sliderMoved ) # maybe sliderReleased works better than sliderMoved
self.volumeSlider.sliderMoved[int].connect( self.volumeSlider.sliderMoved[int].connect(
lambda: self.volume_changed() lambda: self.volume_changed()
) # Move slider to adjust volume ) # Move slider to adjust volume
# Playback controls self.playButton.clicked.connect(self.on_play_clicked) # Click to play/pause
self.playButton.clicked.connect(self.on_play_clicked) self.previousButton.clicked.connect(
self.prevButton.clicked.connect(self.on_previous_clicked) self.on_previous_clicked
self.nextButton.clicked.connect(self.on_next_clicked) ) # Click to previous song
self.nextButton.clicked.connect(self.on_next_clicked) # Click to next song
# FILE MENU # FILE MENU
self.actionOpenFiles.triggered.connect(self.open_files) # Open files window self.actionOpenFiles.triggered.connect(self.open_files) # Open files window
self.actionNewPlaylist.triggered.connect(self.create_playlist) self.actionNewPlaylist.triggered.connect(self.create_playlist)
# EDIT MENU # EDIT MENU
# VIEW MENU
self.actionPreferences.triggered.connect( self.actionPreferences.triggered.connect(
self.open_preferences self.open_preferences
) # Open preferences menu ) # Open preferences menu
# VIEW MENU
# QUICK ACTIONS MENU # QUICK ACTIONS MENU
self.actionScanLibraries.triggered.connect(self.scan_libraries) self.actionScanLibraries.triggered.connect(self.scan_libraries)
self.actionDeleteLibrary.triggered.connect(self.clear_database) self.actionDeleteLibrary.triggered.connect(self.clear_database)
self.actionDeleteDatabase.triggered.connect(self.delete_database) self.actionDeleteDatabase.triggered.connect(self.delete_database)
## Music Table | self.tableView triggers
# Listens for the double click event, then plays the song ## tableView triggers
self.tableView.doubleClicked.connect(self.play_audio_file) self.tableView.doubleClicked.connect(
# Listens for the enter key event, then plays the song self.play_audio_file
self.tableView.enterKey.connect(self.play_audio_file) ) # Listens for the double click event, then plays the song
# Spacebar for toggle play/pause self.tableView.enterKey.connect(
self.tableView.playPauseSignal.connect(self.on_play_clicked) self.play_audio_file
# Album Art | self.albumGraphicsView ) # 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.albumGraphicsView.albumArtDropped.connect(
self.set_album_art_for_selected_songs self.set_album_art_for_selected_songs
) )
@ -134,233 +135,6 @@ class MainWindow(QMainWindow):
self.tableView.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.tableView.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.tableView.horizontalHeader().setStretchLastSection(False) 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: def closeEvent(self, a0: QCloseEvent | None) -> None:
"""Save settings when closing the application""" """Save settings when closing the application"""
# MusicTable/tableView column widths # MusicTable/tableView column widths
@ -375,6 +149,39 @@ class MainWindow(QMainWindow):
self.config.write(configfile) self.config.write(configfile)
super().closeEvent(a0) 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: def load_album_art(self, album_art_data) -> None:
"""Displays the album art for the currently playing track in the GraphicsView""" """Displays the album art for the currently playing track in the GraphicsView"""
if self.current_song_album_art: if self.current_song_album_art:
@ -464,56 +271,6 @@ class MainWindow(QMainWindow):
except Exception as e: except Exception as e:
print(f"Error processing {file}: {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: def update_audio_visualization(self) -> None:
"""Handles upading points on the pyqtgraph visual""" """Handles upading points on the pyqtgraph visual"""
self.clear_audio_visualization() self.clear_audio_visualization()
@ -530,9 +287,6 @@ class MainWindow(QMainWindow):
if stopped: if stopped:
return return
else: else:
if self.playbackSlider.isSliderDown():
# Prevents slider from updating when dragging
return
# Update the slider # Update the slider
if self.player.state() == QMediaPlayer.State.PlayingState: if self.player.state() == QMediaPlayer.State.PlayingState:
self.playbackSlider.setMinimum(0) self.playbackSlider.setMinimum(0)
@ -558,6 +312,26 @@ class MainWindow(QMainWindow):
except Exception as e: except Exception as e:
print(f"Changing volume error: {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: def open_files(self) -> None:
"""Opens the open files window""" """Opens the open files window"""
open_files_window = QFileDialog( open_files_window = QFileDialog(
@ -567,8 +341,6 @@ class MainWindow(QMainWindow):
open_files_window.setFileMode(QFileDialog.ExistingFiles) open_files_window.setFileMode(QFileDialog.ExistingFiles)
open_files_window.exec_() open_files_window.exec_()
filenames = open_files_window.selectedFiles() filenames = open_files_window.selectedFiles()
print("main.py open_files() | file names chosen")
print(filenames)
self.tableView.add_files(filenames) self.tableView.add_files(filenames)
def create_playlist(self) -> None: def create_playlist(self) -> None:
@ -651,7 +423,6 @@ if __name__ == "__main__":
for statement in lines.split(";"): for statement in lines.split(";"):
print(f"executing [{statement}]") print(f"executing [{statement}]")
db.execute(statement, ()) db.execute(statement, ())
# logging setup # logging setup
logging.basicConfig(filename="musicpom.log", encoding="utf-8", level=logging.DEBUG) logging.basicConfig(filename="musicpom.log", encoding="utf-8", level=logging.DEBUG)
# Allow for dynamic imports of my custom classes and utilities # Allow for dynamic imports of my custom classes and utilities
@ -663,6 +434,6 @@ if __name__ == "__main__":
# Dark theme >:3 # Dark theme >:3
qdarktheme.setup_theme() qdarktheme.setup_theme()
# Show the UI # Show the UI
ui = MainWindow() ui = ApplicationWindow(app)
ui.show() ui.show()
sys.exit(app.exec_()) sys.exit(app.exec_())

64
ui.py
View File

@ -20,36 +20,29 @@ class Ui_MainWindow(object):
self.centralwidget.setObjectName("centralwidget") self.centralwidget.setObjectName("centralwidget")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.centralwidget) self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout_3.setObjectName("verticalLayout_3") 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 = QtWidgets.QHBoxLayout()
self.hLayoutHead.setObjectName("hLayoutHead") self.hLayoutHead.setObjectName("hLayoutHead")
self.vlayoutAlbumArt = QtWidgets.QVBoxLayout() self.vlayoutAlbumArt = QtWidgets.QVBoxLayout()
self.vlayoutAlbumArt.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) self.vlayoutAlbumArt.setSizeConstraint(QtWidgets.QLayout.SetFixedSize)
self.vlayoutAlbumArt.setObjectName("vlayoutAlbumArt") self.vlayoutAlbumArt.setObjectName("vlayoutAlbumArt")
self.albumGraphicsView = AlbumArtGraphicsView(self.centralwidget) self.albumGraphicsView = AlbumArtGraphicsView(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy( sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum
)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth( sizePolicy.setHeightForWidth(self.albumGraphicsView.sizePolicy().hasHeightForWidth())
self.albumGraphicsView.sizePolicy().hasHeightForWidth()
)
self.albumGraphicsView.setSizePolicy(sizePolicy) self.albumGraphicsView.setSizePolicy(sizePolicy)
self.albumGraphicsView.setMinimumSize(QtCore.QSize(200, 200)) self.albumGraphicsView.setMinimumSize(QtCore.QSize(200, 200))
self.albumGraphicsView.setMaximumSize(QtCore.QSize(16777215, 16777215)) self.albumGraphicsView.setMaximumSize(QtCore.QSize(16777215, 16777215))
self.albumGraphicsView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.albumGraphicsView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.albumGraphicsView.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.albumGraphicsView.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.albumGraphicsView.setHorizontalScrollBarPolicy( self.albumGraphicsView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
QtCore.Qt.ScrollBarAlwaysOff self.albumGraphicsView.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustIgnored)
)
self.albumGraphicsView.setSizeAdjustPolicy(
QtWidgets.QAbstractScrollArea.AdjustIgnored
)
self.albumGraphicsView.setInteractive(False) self.albumGraphicsView.setInteractive(False)
self.albumGraphicsView.setResizeAnchor(QtWidgets.QGraphicsView.AnchorViewCenter) self.albumGraphicsView.setResizeAnchor(QtWidgets.QGraphicsView.AnchorViewCenter)
self.albumGraphicsView.setViewportUpdateMode( self.albumGraphicsView.setViewportUpdateMode(QtWidgets.QGraphicsView.FullViewportUpdate)
QtWidgets.QGraphicsView.FullViewportUpdate
)
self.albumGraphicsView.setObjectName("albumGraphicsView") self.albumGraphicsView.setObjectName("albumGraphicsView")
self.vlayoutAlbumArt.addWidget(self.albumGraphicsView) self.vlayoutAlbumArt.addWidget(self.albumGraphicsView)
self.hLayoutHead.addLayout(self.vlayoutAlbumArt) self.hLayoutHead.addLayout(self.vlayoutAlbumArt)
@ -104,13 +97,15 @@ class Ui_MainWindow(object):
self.hLayoutHead.setStretch(0, 1) self.hLayoutHead.setStretch(0, 1)
self.hLayoutHead.setStretch(1, 4) self.hLayoutHead.setStretch(1, 4)
self.hLayoutHead.setStretch(2, 6) self.hLayoutHead.setStretch(2, 6)
self.verticalLayout_3.addLayout(self.hLayoutHead) self.verticalLayout.addLayout(self.hLayoutHead)
self.hLayoutMusicTable = QtWidgets.QHBoxLayout() self.hLayoutMusicTable = QtWidgets.QHBoxLayout()
self.hLayoutMusicTable.setContentsMargins(0, -1, 0, -1)
self.hLayoutMusicTable.setObjectName("hLayoutMusicTable") self.hLayoutMusicTable.setObjectName("hLayoutMusicTable")
self.playlistTreeView = LeftPane(self.centralwidget)
self.playlistTreeView.setObjectName("playlistTreeView")
self.hLayoutMusicTable.addWidget(self.playlistTreeView)
self.tableView = MusicTable(self.centralwidget) self.tableView = MusicTable(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy( sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum
)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(1) sizePolicy.setVerticalStretch(1)
sizePolicy.setHeightForWidth(self.tableView.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.tableView.sizePolicy().hasHeightForWidth())
@ -118,13 +113,8 @@ class Ui_MainWindow(object):
self.tableView.setMaximumSize(QtCore.QSize(32000, 32000)) self.tableView.setMaximumSize(QtCore.QSize(32000, 32000))
self.tableView.setAcceptDrops(True) self.tableView.setAcceptDrops(True)
self.tableView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.tableView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.tableView.setSizeAdjustPolicy( self.tableView.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
QtWidgets.QAbstractScrollArea.AdjustToContents self.tableView.setEditTriggers(QtWidgets.QAbstractItemView.AnyKeyPressed|QtWidgets.QAbstractItemView.EditKeyPressed)
)
self.tableView.setEditTriggers(
QtWidgets.QAbstractItemView.AnyKeyPressed
| QtWidgets.QAbstractItemView.EditKeyPressed
)
self.tableView.setAlternatingRowColors(True) self.tableView.setAlternatingRowColors(True)
self.tableView.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) self.tableView.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.tableView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) self.tableView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
@ -134,7 +124,10 @@ class Ui_MainWindow(object):
self.tableView.horizontalHeader().setStretchLastSection(True) self.tableView.horizontalHeader().setStretchLastSection(True)
self.tableView.verticalHeader().setVisible(False) self.tableView.verticalHeader().setVisible(False)
self.hLayoutMusicTable.addWidget(self.tableView) 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 = QtWidgets.QHBoxLayout()
self.hLayoutControls.setObjectName("hLayoutControls") self.hLayoutControls.setObjectName("hLayoutControls")
self.previousButton = QtWidgets.QPushButton(self.centralwidget) self.previousButton = QtWidgets.QPushButton(self.centralwidget)
@ -165,10 +158,9 @@ class Ui_MainWindow(object):
self.volumeSlider.setObjectName("volumeSlider") self.volumeSlider.setObjectName("volumeSlider")
self.hLayoutControls2.addWidget(self.volumeSlider) self.hLayoutControls2.addWidget(self.volumeSlider)
self.verticalLayout_3.addLayout(self.hLayoutControls2) self.verticalLayout_3.addLayout(self.hLayoutControls2)
self.verticalLayout_3.setStretch(0, 3) self.verticalLayout_3.setStretch(0, 10)
self.verticalLayout_3.setStretch(1, 8) self.verticalLayout_3.setStretch(1, 1)
self.verticalLayout_3.setStretch(2, 1) self.verticalLayout_3.setStretch(2, 1)
self.verticalLayout_3.setStretch(3, 1)
MainWindow.setCentralWidget(self.centralwidget) MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 1152, 41)) self.menubar.setGeometry(QtCore.QRect(0, 0, 1152, 41))
@ -195,7 +187,10 @@ class Ui_MainWindow(object):
self.actionOpenFiles.setObjectName("actionOpenFiles") self.actionOpenFiles.setObjectName("actionOpenFiles")
self.actionDeleteDatabase = QtWidgets.QAction(MainWindow) self.actionDeleteDatabase = QtWidgets.QAction(MainWindow)
self.actionDeleteDatabase.setObjectName("actionDeleteDatabase") self.actionDeleteDatabase.setObjectName("actionDeleteDatabase")
self.actionNewPlaylist = QtWidgets.QAction(MainWindow)
self.actionNewPlaylist.setObjectName("actionNewPlaylist")
self.menuFile.addAction(self.actionOpenFiles) self.menuFile.addAction(self.actionOpenFiles)
self.menuFile.addAction(self.actionNewPlaylist)
self.menuEdit.addAction(self.actionPreferences) self.menuEdit.addAction(self.actionPreferences)
self.menuQuick_Actions.addAction(self.actionScanLibraries) self.menuQuick_Actions.addAction(self.actionScanLibraries)
self.menuQuick_Actions.addAction(self.actionDeleteLibrary) self.menuQuick_Actions.addAction(self.actionDeleteLibrary)
@ -225,14 +220,11 @@ class Ui_MainWindow(object):
self.menuView.setTitle(_translate("MainWindow", "View")) self.menuView.setTitle(_translate("MainWindow", "View"))
self.menuQuick_Actions.setTitle(_translate("MainWindow", "Quick-Actions")) self.menuQuick_Actions.setTitle(_translate("MainWindow", "Quick-Actions"))
self.actionPreferences.setText(_translate("MainWindow", "Preferences")) self.actionPreferences.setText(_translate("MainWindow", "Preferences"))
self.actionPreferences.setStatusTip( self.actionPreferences.setStatusTip(_translate("MainWindow", "Open preferences"))
_translate("MainWindow", "Open preferences")
)
self.actionScanLibraries.setText(_translate("MainWindow", "Scan libraries")) self.actionScanLibraries.setText(_translate("MainWindow", "Scan libraries"))
self.actionDeleteLibrary.setText(_translate("MainWindow", "Delete Library")) self.actionDeleteLibrary.setText(_translate("MainWindow", "Delete Library"))
self.actionOpenFiles.setText(_translate("MainWindow", "Open file(s)")) self.actionOpenFiles.setText(_translate("MainWindow", "Open file(s)"))
self.actionDeleteDatabase.setText(_translate("MainWindow", "Delete Database")) self.actionDeleteDatabase.setText(_translate("MainWindow", "Delete Database"))
self.actionNewPlaylist.setText(_translate("MainWindow", "New playlist"))
from components import AlbumArtGraphicsView, LeftPane, MusicTable
from components import AlbumArtGraphicsView, MusicTable
from pyqtgraph import PlotWidget from pyqtgraph import PlotWidget

382
ui.ui
View File

@ -17,200 +17,222 @@
<string/> <string/>
</property> </property>
<widget class="QWidget" name="centralwidget"> <widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout_3" stretch="3,8,1,1"> <layout class="QVBoxLayout" name="verticalLayout_3" stretch="10,1,1">
<item> <item>
<layout class="QHBoxLayout" name="hLayoutHead" stretch="1,4,6"> <layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<item> <item>
<layout class="QVBoxLayout" name="vlayoutAlbumArt"> <layout class="QHBoxLayout" name="hLayoutHead" stretch="1,4,6">
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<item> <item>
<widget class="AlbumArtGraphicsView" name="albumGraphicsView"> <layout class="QVBoxLayout" name="vlayoutAlbumArt">
<property name="sizePolicy"> <property name="sizeConstraint">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum"> <enum>QLayout::SetFixedSize</enum>
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property> </property>
<property name="minimumSize">
<size>
<width>200</width>
<height>200</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustIgnored</enum>
</property>
<property name="interactive">
<bool>false</bool>
</property>
<property name="resizeAnchor">
<enum>QGraphicsView::AnchorViewCenter</enum>
</property>
<property name="viewportUpdateMode">
<enum>QGraphicsView::FullViewportUpdate</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="vLayoutSongDetails">
<item>
<widget class="QLabel" name="artistLabel">
<property name="font">
<font>
<pointsize>24</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>artist</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="titleLabel">
<property name="font">
<font>
<pointsize>18</pointsize>
</font>
</property>
<property name="text">
<string>song title</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="albumLabel">
<property name="font">
<font>
<pointsize>16</pointsize>
<weight>50</weight>
<italic>true</italic>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>album</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="vLayoutPlaybackVisuals">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<widget class="QSlider" name="playbackSlider"> <widget class="AlbumArtGraphicsView" name="albumGraphicsView">
<property name="orientation"> <property name="sizePolicy">
<enum>Qt::Horizontal</enum> <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property> </property>
</widget> <property name="minimumSize">
</item> <size>
<item> <width>200</width>
<widget class="QLabel" name="startTimeLabel"> <height>200</height>
<property name="text"> </size>
<string>00:00</string>
</property> </property>
</widget> <property name="maximumSize">
</item> <size>
<item> <width>16777215</width>
<widget class="QLabel" name="slashLabel"> <height>16777215</height>
<property name="text"> </size>
<string>/</string>
</property> </property>
</widget> <property name="contextMenuPolicy">
</item> <enum>Qt::CustomContextMenu</enum>
<item> </property>
<widget class="QLabel" name="endTimeLabel"> <property name="verticalScrollBarPolicy">
<property name="text"> <enum>Qt::ScrollBarAlwaysOff</enum>
<string>00:00</string> </property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustIgnored</enum>
</property>
<property name="interactive">
<bool>false</bool>
</property>
<property name="resizeAnchor">
<enum>QGraphicsView::AnchorViewCenter</enum>
</property>
<property name="viewportUpdateMode">
<enum>QGraphicsView::FullViewportUpdate</enum>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
<item> <item>
<widget class="PlotWidget" name="PlotWidget" native="true"/> <layout class="QVBoxLayout" name="vLayoutSongDetails">
<item>
<widget class="QLabel" name="artistLabel">
<property name="font">
<font>
<pointsize>24</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>artist</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="titleLabel">
<property name="font">
<font>
<pointsize>18</pointsize>
</font>
</property>
<property name="text">
<string>song title</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="albumLabel">
<property name="font">
<font>
<pointsize>16</pointsize>
<weight>50</weight>
<italic>true</italic>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>album</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="vLayoutPlaybackVisuals">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QSlider" name="playbackSlider">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="startTimeLabel">
<property name="text">
<string>00:00</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="slashLabel">
<property name="text">
<string>/</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="endTimeLabel">
<property name="text">
<string>00:00</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="PlotWidget" name="PlotWidget" native="true"/>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="hLayoutMusicTable" stretch="2,10">
<property name="leftMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<item>
<widget class="LeftPane" name="playlistTreeView"/>
</item>
<item>
<widget class="MusicTable" name="tableView">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>32000</width>
<height>32000</height>
</size>
</property>
<property name="acceptDrops">
<bool>true</bool>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::AnyKeyPressed|QAbstractItemView::EditKeyPressed</set>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<attribute name="horizontalHeaderCascadingSectionResizes">
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item> </item>
</layout> </layout>
</item> </item>
</layout> </layout>
</item> </item>
<item>
<layout class="QHBoxLayout" name="hLayoutMusicTable">
<item>
<widget class="MusicTable" name="tableView">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>32000</width>
<height>32000</height>
</size>
</property>
<property name="acceptDrops">
<bool>true</bool>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::AnyKeyPressed|QAbstractItemView::EditKeyPressed</set>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<attribute name="horizontalHeaderCascadingSectionResizes">
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
</item>
<item> <item>
<layout class="QHBoxLayout" name="hLayoutControls"> <layout class="QHBoxLayout" name="hLayoutControls">
<property name="spacing">
<number>6</number>
</property>
<item> <item>
<widget class="QPushButton" name="previousButton"> <widget class="QPushButton" name="previousButton">
<property name="font"> <property name="font">
@ -251,6 +273,9 @@
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="hLayoutControls2"> <layout class="QHBoxLayout" name="hLayoutControls2">
<property name="spacing">
<number>6</number>
</property>
<item> <item>
<widget class="QSlider" name="volumeSlider"> <widget class="QSlider" name="volumeSlider">
<property name="maximum"> <property name="maximum">
@ -282,6 +307,7 @@
<string>File</string> <string>File</string>
</property> </property>
<addaction name="actionOpenFiles"/> <addaction name="actionOpenFiles"/>
<addaction name="actionNewPlaylist"/>
</widget> </widget>
<widget class="QMenu" name="menuEdit"> <widget class="QMenu" name="menuEdit">
<property name="title"> <property name="title">
@ -336,6 +362,11 @@
<string>Delete Database</string> <string>Delete Database</string>
</property> </property>
</action> </action>
<action name="actionNewPlaylist">
<property name="text">
<string>New playlist</string>
</property>
</action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>
@ -354,6 +385,11 @@
<extends>QGraphicsView</extends> <extends>QGraphicsView</extends>
<header>components</header> <header>components</header>
</customwidget> </customwidget>
<customwidget>
<class>LeftPane</class>
<extends>QTreeView</extends>
<header>components</header>
</customwidget>
</customwidgets> </customwidgets>
<resources/> <resources/>
<connections/> <connections/>

View File

@ -57,7 +57,7 @@ class FFTAnalyser(QtCore.QThread):
freq = np.fft.fftfreq(fourier.size, d=0.05) freq = np.fft.fftfreq(fourier.size, d=0.05)
amps = 2 / v_sample.size * np.abs(fourier) amps = 2 / v_sample.size * np.abs(fourier)
data = np.array([freq, amps]).T data = np.array([freq, amps]).T
print(data) # print(data)
point_range = 1 / self.resolution point_range = 1 / self.resolution
point_samples = [] point_samples = []