fixes and idk

This commit is contained in:
billy 2025-08-17 08:20:04 -04:00
parent 69ce4bc763
commit d01a743c45
3 changed files with 92 additions and 71 deletions

View File

@ -75,58 +75,38 @@ class MusicTable(QTableView):
focusEnterSignal = pyqtSignal() focusEnterSignal = pyqtSignal()
focusLeaveSignal = pyqtSignal() focusLeaveSignal = pyqtSignal()
def focusInEvent(self, e):
"""
Event filter: when self is focused
"""
self.focusEnterSignal.emit()
# arrow keys act normal
super().focusInEvent(e)
def focusOutEvent(self, e):
"""
Event filter: when self becomes unfocused
"""
self.focusLeaveSignal.emit()
def __init__(self, parent=None, application_window=None): def __init__(self, parent=None, application_window=None):
super().__init__(parent) super().__init__(parent)
# why do i need this? # why do i need this?
self.application_window = application_window self.application_window = application_window
self.config = ConfigParser()
# NOTE: wtf is actually going on here with the models?
# Create QStandardItemModel
# Create QSortFilterProxyModel
# Set QSortFilterProxyModel source to QStandardItemModel
# Set QTableView model to the Proxy model
# so it looks like this, i guess:
# QTableView model2 = QSortFilterProxyModel(QStandardItemModel)
# need a standard item model to do actions on cells
self.model2: QStandardItemModel = QStandardItemModel()
# proxy model for sorting i guess?
self.proxymodel = QSortFilterProxyModel()
self.proxymodel.setSourceModel(self.model2)
self.setModel(self.proxymodel)
self.setSortingEnabled(True)
self.search_string = None
# Config # Config
cfg_file = ( cfg_file = (
Path(user_config_dir(appname="musicpom", appauthor="billypom")) Path(user_config_dir(appname="musicpom", appauthor="billypom"))
/ "config.ini" / "config.ini"
) )
self.config = ConfigParser()
self.config.read(cfg_file) self.config.read(cfg_file)
debug(f"music table config: {self.config}") debug(f"music table config: {self.config}")
# Threads # NOTE:
self.threadpool = QThreadPool # QTableView model2 = QSortFilterProxyModel(QStandardItemModel)
# headers class thing #
self.headers = HeaderTags() # wtf is actually going on here with the models?
# Create QStandardItemModel
# Create QSortFilterProxyModel
# Set QSortFilterProxyModel source to QStandardItemModel
# Set QTableView model to the Proxy model
# so it looks like that, i guess
# need a QStandardItemModel to do actions on cells
self.model2: QStandardItemModel = QStandardItemModel()
self.proxymodel = QSortFilterProxyModel()
self.search_string = None
self.threadpool = QThreadPool
self.headers = HeaderTags()
# db names of headers # db names of headers
self.database_columns = str(self.config["table"]["columns"]).split(",") self.database_columns: list[str] = str(
self.config["table"]["columns"]).split(",")
self.vertical_scroll_position = 0 self.vertical_scroll_position = 0
self.selected_song_filepath = "" self.selected_song_filepath = ""
self.selected_song_qmodel_index: QModelIndex self.selected_song_qmodel_index: QModelIndex
@ -135,6 +115,11 @@ class MusicTable(QTableView):
self.current_song_qmodel_index: QModelIndex self.current_song_qmodel_index: QModelIndex
self.selected_playlist_id: int | None = None self.selected_playlist_id: int | None = None
# proxy model for sorting i guess?
self.proxymodel.setSourceModel(self.model2)
self.setModel(self.proxymodel)
self.setSortingEnabled(True)
# Properties # Properties
self.setAcceptDrops(True) self.setAcceptDrops(True)
self.setHorizontalScrollBarPolicy( self.setHorizontalScrollBarPolicy(
@ -175,6 +160,20 @@ class MusicTable(QTableView):
# | | # | |
# |_________________| # |_________________|
def focusInEvent(self, e):
"""
Event filter: when self is focused
"""
self.focusEnterSignal.emit()
# arrow keys act normal
super().focusInEvent(e)
def focusOutEvent(self, e):
"""
Event filter: when self becomes unfocused
"""
self.focusLeaveSignal.emit()
def resizeEvent(self, e: typing.Optional[QResizeEvent]) -> None: def resizeEvent(self, e: typing.Optional[QResizeEvent]) -> None:
"""Do something when the QTableView is resized""" """Do something when the QTableView is resized"""
if e is None: if e is None:
@ -490,7 +489,8 @@ class MusicTable(QTableView):
""" """
worker = Worker(add_files_to_database, files) worker = Worker(add_files_to_database, files)
_ = worker.signals.signal_progress.connect(self.qapp.handle_progress) _ = worker.signals.signal_progress.connect(self.qapp.handle_progress)
_ = worker.signals.signal_result.connect(self.on_add_files_to_database_finished) _ = worker.signals.signal_result.connect(
self.on_add_files_to_database_finished)
worker.signals.signal_finished.connect(self.load_music_table) worker.signals.signal_finished.connect(self.load_music_table)
if self.qapp: if self.qapp:
threadpool = self.qapp.threadpool threadpool = self.qapp.threadpool
@ -660,7 +660,7 @@ class MusicTable(QTableView):
def reorganize_files(self, filepaths, progress_callback=None): def reorganize_files(self, filepaths, progress_callback=None):
""" """
Reorganizes files into Artist/Album/Song, Reorganizes files into Artist/Album/Song,
based on self.config['directories'][reorganize_destination'] based on self.config['settings'][reorganize_destination']
""" """
debug("reorganizing files") debug("reorganizing files")
# FIXME: batch update, instead of doing 1 file at a time # FIXME: batch update, instead of doing 1 file at a time
@ -671,7 +671,7 @@ class MusicTable(QTableView):
# FIXME: change reorganize location in config, try to reorganize, failed - old reference # FIXME: change reorganize location in config, try to reorganize, failed - old reference
# Get target directory # Get target directory
target_dir = str(self.config["directories"]["reorganize_destination"]) target_dir = str(self.config["settings"]["reorganize_destination"])
for filepath in filepaths: for filepath in filepaths:
# Read file metadata # Read file metadata
artist, album = get_reorganize_vars(filepath) artist, album = get_reorganize_vars(filepath)
@ -737,7 +737,8 @@ class MusicTable(QTableView):
self.selected_playlist_id = playlist_id[0] self.selected_playlist_id = playlist_id[0]
try: try:
with DBA.DBAccess() as db: with DBA.DBAccess() as db:
query = f"SELECT id, {fields} FROM song JOIN song_playlist sp ON id = sp.song_id WHERE sp.playlist_id = ?" query = f"SELECT id, {
fields} FROM song JOIN song_playlist sp ON id = sp.song_id WHERE sp.playlist_id = ?"
# fulltext search # fulltext search
if self.search_string: if self.search_string:
# params = 3 * [self.search_string] # params = 3 * [self.search_string]
@ -821,7 +822,8 @@ class MusicTable(QTableView):
db_name: str = self.config.get("settings", "db").split("/").pop() db_name: str = self.config.get("settings", "db").split("/").pop()
db_filename = self.config.get("settings", "db") db_filename = self.config.get("settings", "db")
self.playlistStatsSignal.emit( self.playlistStatsSignal.emit(
f"Songs: {row_count} | Total time: {total_time} | {db_name} | {db_filename}" f"Songs: {row_count} | Total time: {
total_time} | {db_name} | {db_filename}"
) )
self.loadMusicTableSignal.emit() self.loadMusicTableSignal.emit()
self.connect_data_changed() self.connect_data_changed()

70
main.py
View File

@ -57,6 +57,8 @@ from utils import (
add_files_to_database, add_files_to_database,
set_album_art, set_album_art,
id3_remap, id3_remap,
get_album_art,
Worker
) )
from components import ( from components import (
HeaderTags, HeaderTags,
@ -68,9 +70,6 @@ from components import (
ExportPlaylistWindow, ExportPlaylistWindow,
SearchLineEdit, SearchLineEdit,
) )
from utils.get_album_art import get_album_art
# from utils.Worker import Worker
from utils import Worker
# good help with signals slots in threads # good help with signals slots in threads
# https://stackoverflow.com/questions/52993677/how-do-i-setup-signals-and-slots-in-pyqt-with-qthreads-in-both-directions # https://stackoverflow.com/questions/52993677/how-do-i-setup-signals-and-slots-in-pyqt-with-qthreads-in-both-directions
@ -162,18 +161,23 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
self.playbackSlider.sliderReleased.connect( self.playbackSlider.sliderReleased.connect(
lambda: self.player.setPosition(self.playbackSlider.value()) lambda: self.player.setPosition(self.playbackSlider.value())
) # sliderReleased works better than sliderMoved ) # sliderReleased works better than sliderMoved
self.volumeSlider.sliderMoved[int].connect(lambda: self.on_volume_changed()) self.volumeSlider.sliderMoved[int].connect(
lambda: self.on_volume_changed())
self.speedSlider.sliderMoved.connect( self.speedSlider.sliderMoved.connect(
lambda: self.on_speed_changed(self.speedSlider.value()) lambda: self.on_speed_changed(self.speedSlider.value())
) )
# self.speedSlider.doubleClicked.connect(lambda: self.on_speed_changed(1)) # self.speedSlider.doubleClicked.connect(lambda: self.on_speed_changed(1))
self.playButton.clicked.connect(self.on_play_clicked) # Click to play/pause self.playButton.clicked.connect(
self.on_play_clicked) # Click to play/pause
self.previousButton.clicked.connect(self.on_prev_clicked) self.previousButton.clicked.connect(self.on_prev_clicked)
self.nextButton.clicked.connect(self.on_next_clicked) # Click to next song self.nextButton.clicked.connect(
self.on_next_clicked) # Click to next song
# FILE MENU # FILE MENU
self.actionOpenFiles.triggered.connect(self.open_files) # Open files window self.actionOpenFiles.triggered.connect(
self.actionNewPlaylist.triggered.connect(self.playlistTreeView.create_playlist) self.open_files) # Open files window
self.actionNewPlaylist.triggered.connect(
self.playlistTreeView.create_playlist)
self.actionExportPlaylist.triggered.connect(self.export_playlist) self.actionExportPlaylist.triggered.connect(self.export_playlist)
# EDIT MENU # EDIT MENU
@ -195,14 +199,16 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
self.lineEditSearch: QLineEdit self.lineEditSearch: QLineEdit
# CONNECTIONS # CONNECTIONS
self.lineEditSearch.textTypedSignal.connect(self.handle_search_box_text) self.lineEditSearch.textTypedSignal.connect(
self.handle_search_box_text)
# tableView # tableView
self.tableView.playSignal.connect(self.play_audio_file) self.tableView.playSignal.connect(self.play_audio_file)
self.tableView.playPauseSignal.connect( self.tableView.playPauseSignal.connect(
self.on_play_clicked self.on_play_clicked
) # Spacebar toggle play/pause signal ) # Spacebar toggle play/pause signal
self.tableView.handleProgressSignal.connect(self.handle_progress) self.tableView.handleProgressSignal.connect(self.handle_progress)
self.tableView.searchBoxSignal.connect(self.handle_search_box_visibility) self.tableView.searchBoxSignal.connect(
self.handle_search_box_visibility)
self.tableView.playlistStatsSignal.connect( self.tableView.playlistStatsSignal.connect(
self.set_permanent_status_bar_message self.set_permanent_status_bar_message
) )
@ -213,7 +219,8 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
self.playlistTreeView.playlistChoiceSignal.connect( self.playlistTreeView.playlistChoiceSignal.connect(
self.tableView.load_music_table self.tableView.load_music_table
) )
self.playlistTreeView.allSongsSignal.connect(self.tableView.load_music_table) self.playlistTreeView.allSongsSignal.connect(
self.tableView.load_music_table)
# albumGraphicsView # albumGraphicsView
self.albumGraphicsView.albumArtDropped.connect( self.albumGraphicsView.albumArtDropped.connect(
@ -315,7 +322,7 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
current_real_index = self.tableView.current_song_qmodel_index current_real_index = self.tableView.current_song_qmodel_index
try: try:
index = self.tableView.proxymodel.mapFromSource(current_real_index) index = self.tableView.proxymodel.mapFromSource(current_real_index)
except: except Exception:
return return
row: int = index.row() row: int = index.row()
prev_row: int = row - 1 prev_row: int = row - 1
@ -392,7 +399,8 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
def load_config(self) -> None: def load_config(self) -> None:
"""does what it says""" """does what it says"""
cfg_file = ( cfg_file = (
Path(user_config_dir(appname="musicpom", appauthor="billypom")) / "config.ini" Path(user_config_dir(appname="musicpom",
appauthor="billypom")) / "config.ini"
) )
self.config.read(cfg_file) self.config.read(cfg_file)
debug("load_config()") debug("load_config()")
@ -470,7 +478,8 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
selected_songs = self.tableView.get_selected_songs_filepaths() selected_songs = self.tableView.get_selected_songs_filepaths()
for song in selected_songs: for song in selected_songs:
debug( debug(
f"main.py set_album_art_for_selected_songs() | updating album art for {song}" f"main.py set_album_art_for_selected_songs() | updating album art for {
song}"
) )
set_album_art(song, album_art_path) set_album_art(song, album_art_path)
@ -497,7 +506,8 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
self.playbackSlider.setMaximum(self.player.duration()) self.playbackSlider.setMaximum(self.player.duration())
slider_position = self.player.position() slider_position = self.player.position()
self.playbackSlider.setValue(slider_position) self.playbackSlider.setValue(slider_position)
current_minutes, current_seconds = divmod(slider_position / 1000, 60) current_minutes, current_seconds = divmod(
slider_position / 1000, 60)
duration_minutes, duration_seconds = divmod( duration_minutes, duration_seconds = divmod(
self.player.duration() / 1000, 60 self.player.duration() / 1000, 60
) )
@ -548,7 +558,8 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
def create_playlist(self) -> None: def create_playlist(self) -> None:
"""Creates a database record for a playlist, given a name""" """Creates a database record for a playlist, given a name"""
window = CreatePlaylistWindow(self.playlistCreatedSignal) window = CreatePlaylistWindow(self.playlistCreatedSignal)
window.playlistCreatedSignal.connect(self.add_latest_playlist_to_tree) # type: ignore window.playlistCreatedSignal.connect(
self.add_latest_playlist_to_tree) # type: ignore
window.exec_() window.exec_()
def import_playlist(self) -> None: def import_playlist(self) -> None:
@ -574,8 +585,10 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
preferences_window = PreferencesWindow( preferences_window = PreferencesWindow(
self.reloadConfigSignal, self.reloadDatabaseSignal self.reloadConfigSignal, self.reloadDatabaseSignal
) )
preferences_window.reloadConfigSignal.connect(self.load_config) # type: ignore preferences_window.reloadConfigSignal.connect(
preferences_window.reloadDatabaseSignal.connect(self.tableView.load_music_table) # type: ignore self.load_config) # type: ignore
preferences_window.reloadDatabaseSignal.connect(
self.tableView.load_music_table) # type: ignore
preferences_window.exec_() # Display the preferences window modally preferences_window.exec_() # Display the preferences window modally
# Quick Actions # Quick Actions
@ -609,9 +622,11 @@ def update_database_file() -> bool:
Reads the database file (specified by config file) Reads the database file (specified by config file)
""" """
cfg_file = ( cfg_file = (
Path(user_config_dir(appname="musicpom", appauthor="billypom")) / "config.ini" Path(user_config_dir(appname="musicpom",
appauthor="billypom")) / "config.ini"
) )
cfg_path = str(Path(user_config_dir(appname="musicpom", appauthor="billypom"))) cfg_path = str(Path(user_config_dir(
appname="musicpom", appauthor="billypom")))
config = ConfigParser() config = ConfigParser()
config.read(cfg_file) config.read(cfg_file)
db_filepath: str = config.get("settings", "db") db_filepath: str = config.get("settings", "db")
@ -620,9 +635,10 @@ def update_database_file() -> bool:
if not db_filepath.startswith(cfg_path): if not db_filepath.startswith(cfg_path):
new_path = f"{cfg_path}/{db_filepath}" new_path = f"{cfg_path}/{db_filepath}"
debug( debug(
f"Set new config [db] database path: \n> Current: {db_filepath}\n> New:{new_path}" f"Set new config [db] database path: \n> Current: {
db_filepath}\n> New:{new_path}"
) )
config["db"]["database"] = new_path config["settings"]["db"] = new_path
# Save the config # Save the config
with open(cfg_file, "w") as configfile: with open(cfg_file, "w") as configfile:
config.write(configfile) config.write(configfile)
@ -654,9 +670,11 @@ def update_config_file() -> ConfigParser:
If the user config file is not up to date, update it with examples from sample config If the user config file is not up to date, update it with examples from sample config
""" """
cfg_file = ( cfg_file = (
Path(user_config_dir(appname="musicpom", appauthor="billypom")) / "config.ini" Path(user_config_dir(appname="musicpom",
appauthor="billypom")) / "config.ini"
) )
cfg_path = str(Path(user_config_dir(appname="musicpom", appauthor="billypom"))) cfg_path = str(Path(user_config_dir(
appname="musicpom", appauthor="billypom")))
# If config path doesn't exist, create it # If config path doesn't exist, create it
if not os.path.exists(cfg_path): if not os.path.exists(cfg_path):
@ -720,8 +738,8 @@ if __name__ == "__main__":
app = QApplication(sys.argv) app = QApplication(sys.argv)
clipboard = app.clipboard() clipboard = app.clipboard()
# Dark theme >:3 # Dark theme >:3
qdarktheme.setup_theme() # qdarktheme.setup_theme()
# qdarktheme.setup_theme("auto") # this is supposed to work but doesnt qdarktheme.setup_theme("auto") # this is supposed to work but doesnt
# Show the UI # Show the UI
ui = ApplicationWindow(clipboard) ui = ApplicationWindow(clipboard)
# window size # window size

View File

@ -3,7 +3,8 @@ matplotlib
appdirs appdirs
pyqt5 pyqt5
pydub pydub
pyqtdarktheme-fork; python_version > '3.11' audioop-lts
pyqtdarktheme; python_version < '3.12' pyqtdarktheme-fork; python_version < '3.11'
pyqtdarktheme; python_version > '3.12'
pyqtgraph pyqtgraph
scipy scipy