playlist pane, return to designer
This commit is contained in:
parent
68590deea2
commit
97e335ba00
434
bak.main
434
bak.main
@ -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
668
bak.main2
Normal 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
36
components/LeftPane.py
Normal 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")
|
||||
@ -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
|
||||
|
||||
@ -6,3 +6,4 @@ from .ErrorDialog import ErrorDialog
|
||||
from .LyricsWindow import LyricsWindow
|
||||
from .AddToPlaylistWindow import AddToPlaylistWindow
|
||||
from .CreatePlaylistWindow import CreatePlaylistWindow
|
||||
from .LeftPane import LeftPane
|
||||
|
||||
421
main.py
421
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_())
|
||||
|
||||
64
ui.py
64
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
|
||||
|
||||
382
ui.ui
382
ui.ui
@ -17,200 +17,222 @@
|
||||
<string/>
|
||||
</property>
|
||||
<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>
|
||||
<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>
|
||||
<layout class="QVBoxLayout" name="vlayoutAlbumArt">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetFixedSize</enum>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="hLayoutHead" stretch="1,4,6">
|
||||
<item>
|
||||
<widget class="AlbumArtGraphicsView" name="albumGraphicsView">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
<layout class="QVBoxLayout" name="vlayoutAlbumArt">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetFixedSize</enum>
|
||||
</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>
|
||||
<widget class="QSlider" name="playbackSlider">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<widget class="AlbumArtGraphicsView" name="albumGraphicsView">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="startTimeLabel">
|
||||
<property name="text">
|
||||
<string>00:00</string>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>200</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="slashLabel">
|
||||
<property name="text">
|
||||
<string>/</string>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="endTimeLabel">
|
||||
<property name="text">
|
||||
<string>00:00</string>
|
||||
<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>
|
||||
<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>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</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>
|
||||
<layout class="QHBoxLayout" name="hLayoutControls">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="previousButton">
|
||||
<property name="font">
|
||||
@ -251,6 +273,9 @@
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="hLayoutControls2">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QSlider" name="volumeSlider">
|
||||
<property name="maximum">
|
||||
@ -282,6 +307,7 @@
|
||||
<string>File</string>
|
||||
</property>
|
||||
<addaction name="actionOpenFiles"/>
|
||||
<addaction name="actionNewPlaylist"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuEdit">
|
||||
<property name="title">
|
||||
@ -336,6 +362,11 @@
|
||||
<string>Delete Database</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionNewPlaylist">
|
||||
<property name="text">
|
||||
<string>New playlist</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
@ -354,6 +385,11 @@
|
||||
<extends>QGraphicsView</extends>
|
||||
<header>components</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>LeftPane</class>
|
||||
<extends>QTreeView</extends>
|
||||
<header>components</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
||||
@ -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 = []
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user