working on stuff and fixing nvim config testing stuff

This commit is contained in:
billy 2025-08-24 13:56:39 -04:00
parent d01a743c45
commit dbfab01b53
4 changed files with 116 additions and 130 deletions

View File

@ -1,4 +1,3 @@
from mutagen.id3 import ID3
import DBA import DBA
from PyQt5.QtGui import ( from PyQt5.QtGui import (
QColor, QColor,
@ -14,6 +13,7 @@ from PyQt5.QtGui import (
) )
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAction, QAction,
QApplication,
QHeaderView, QHeaderView,
QMenu, QMenu,
QTableView, QTableView,
@ -26,11 +26,9 @@ from PyQt5.QtCore import (
QSortFilterProxyModel, QSortFilterProxyModel,
Qt, Qt,
QModelIndex, QModelIndex,
QThreadPool,
pyqtSignal, pyqtSignal,
) )
from components.DebugWindow import DebugWindow from components.DebugWindow import DebugWindow
from components.ErrorDialog import ErrorDialog
from components.LyricsWindow import LyricsWindow from components.LyricsWindow import LyricsWindow
from components.AddToPlaylistWindow import AddToPlaylistWindow from components.AddToPlaylistWindow import AddToPlaylistWindow
from components.MetadataWindow import MetadataWindow from components.MetadataWindow import MetadataWindow
@ -40,11 +38,9 @@ from components.HeaderTags import HeaderTags
from utils import ( from utils import (
batch_delete_filepaths_from_database, batch_delete_filepaths_from_database,
batch_delete_filepaths_from_playlist, batch_delete_filepaths_from_playlist,
delete_song_id_from_database,
add_files_to_database, add_files_to_database,
get_reorganize_vars, get_reorganize_vars,
update_song_in_database, update_song_in_database,
get_album_art,
id3_remap, id3_remap,
get_tags, get_tags,
set_tag, set_tag,
@ -59,21 +55,20 @@ from pathlib import Path
from appdirs import user_config_dir from appdirs import user_config_dir
from configparser import ConfigParser from configparser import ConfigParser
class MusicTable(QTableView): class MusicTable(QTableView):
playlistStatsSignal = pyqtSignal(str) playlistStatsSignal: pyqtSignal = pyqtSignal(str)
loadMusicTableSignal = pyqtSignal() loadMusicTableSignal: pyqtSignal = pyqtSignal()
sortSignal = pyqtSignal() sortSignal: pyqtSignal = pyqtSignal()
playPauseSignal = pyqtSignal() playPauseSignal: pyqtSignal = pyqtSignal()
playSignal = pyqtSignal(str) playSignal: pyqtSignal = pyqtSignal(str)
enterKey = pyqtSignal() enterKey: pyqtSignal = pyqtSignal()
deleteKey = pyqtSignal() deleteKey: pyqtSignal = pyqtSignal()
refreshMusicTableSignal = pyqtSignal() refreshMusicTableSignal: pyqtSignal = pyqtSignal()
handleProgressSignal = pyqtSignal(str) handleProgressSignal: pyqtSignal = pyqtSignal(str)
getThreadPoolSignal = pyqtSignal() getThreadPoolSignal: pyqtSignal = pyqtSignal()
searchBoxSignal = pyqtSignal() searchBoxSignal: pyqtSignal = pyqtSignal()
focusEnterSignal = pyqtSignal() focusEnterSignal: pyqtSignal = pyqtSignal()
focusLeaveSignal = pyqtSignal() focusLeaveSignal: pyqtSignal = pyqtSignal()
def __init__(self, parent=None, application_window=None): def __init__(self, parent=None, application_window=None):
super().__init__(parent) super().__init__(parent)
@ -85,7 +80,7 @@ class MusicTable(QTableView):
Path(user_config_dir(appname="musicpom", appauthor="billypom")) Path(user_config_dir(appname="musicpom", appauthor="billypom"))
/ "config.ini" / "config.ini"
) )
self.config.read(cfg_file) _ = self.config.read(cfg_file)
debug(f"music table config: {self.config}") debug(f"music table config: {self.config}")
# NOTE: # NOTE:
@ -96,13 +91,12 @@ class MusicTable(QTableView):
# Create QSortFilterProxyModel # Create QSortFilterProxyModel
# Set QSortFilterProxyModel source to QStandardItemModel # Set QSortFilterProxyModel source to QStandardItemModel
# Set QTableView model to the Proxy model # Set QTableView model to the Proxy model
# so it looks like that, i guess # so it looks like the above note, i guess
# need a QStandardItemModel to do actions on cells # need a QStandardItemModel to do actions on cells
self.model2: QStandardItemModel = QStandardItemModel() self.model2: QStandardItemModel = QStandardItemModel()
self.proxymodel = QSortFilterProxyModel() self.proxymodel: QSortFilterProxyModel = QSortFilterProxyModel()
self.search_string = None self.search_string: str | None = None
self.threadpool = QThreadPool
self.headers = HeaderTags() self.headers = HeaderTags()
# db names of headers # db names of headers
self.database_columns: list[str] = str( self.database_columns: list[str] = str(
@ -144,8 +138,7 @@ class MusicTable(QTableView):
self.deleteKey.connect(self.delete_songs) self.deleteKey.connect(self.delete_songs)
self.doubleClicked.connect(self.play_selected_audio_file) self.doubleClicked.connect(self.play_selected_audio_file)
self.enterKey.connect(self.play_selected_audio_file) self.enterKey.connect(self.play_selected_audio_file)
self.model2.dataChanged.connect( self.model2.dataChanged.connect(self.on_cell_data_changed) # editing cells
self.on_cell_data_changed) # editing cells
# self.model2.layoutChanged.connect(self.restore_scroll_position) # self.model2.layoutChanged.connect(self.restore_scroll_position)
self.horizontal_header.sectionResized.connect(self.on_header_resized) self.horizontal_header.sectionResized.connect(self.on_header_resized)
# Final actions # Final actions
@ -202,35 +195,32 @@ class MusicTable(QTableView):
"""Right-click context menu""" """Right-click context menu"""
menu = QMenu(self) menu = QMenu(self)
add_to_playlist_action = QAction("Add to playlist", self) add_to_playlist_action = QAction("Add to playlist", self)
add_to_playlist_action.triggered.connect( _ = add_to_playlist_action.triggered.connect(self.add_selected_files_to_playlist)
self.add_selected_files_to_playlist)
menu.addAction(add_to_playlist_action) menu.addAction(add_to_playlist_action)
# edit metadata # edit metadata
edit_metadata_action = QAction("Edit metadata", self) edit_metadata_action = QAction("Edit metadata", self)
edit_metadata_action.triggered.connect( _ = edit_metadata_action.triggered.connect(self.edit_selected_files_metadata)
self.edit_selected_files_metadata)
menu.addAction(edit_metadata_action) menu.addAction(edit_metadata_action)
# edit lyrics # edit lyrics
edit_lyrics_action = QAction("Lyrics (View/Edit)", self) edit_lyrics_action = QAction("Lyrics (View/Edit)", self)
edit_lyrics_action.triggered.connect(self.show_lyrics_menu) _ = edit_lyrics_action.triggered.connect(self.show_lyrics_menu)
menu.addAction(edit_lyrics_action) menu.addAction(edit_lyrics_action)
# jump to current song in table # jump to current song in table
jump_to_current_song_action = QAction("Jump to current song", self) jump_to_current_song_action = QAction("Jump to current song", self)
jump_to_current_song_action.triggered.connect( _ = jump_to_current_song_action.triggered.connect(self.jump_to_current_song)
self.jump_to_current_song)
menu.addAction(jump_to_current_song_action) menu.addAction(jump_to_current_song_action)
# open in file explorer # open in file explorer
open_containing_folder_action = QAction( open_containing_folder_action = QAction(
"Open in system file manager", self) "Open in system file manager", self)
open_containing_folder_action.triggered.connect(self.open_directory) _ = open_containing_folder_action.triggered.connect(self.open_directory)
menu.addAction(open_containing_folder_action) menu.addAction(open_containing_folder_action)
# view id3 tags (debug) # view id3 tags (debug)
view_id3_tags_debug = QAction("View ID3 tags (debug)", self) view_id3_tags_debug = QAction("View ID3 tags (debug)", self)
view_id3_tags_debug.triggered.connect(self.view_id3_tags_debug_menu) _ = view_id3_tags_debug.triggered.connect(self.view_id3_tags_debug_menu)
menu.addAction(view_id3_tags_debug) menu.addAction(view_id3_tags_debug)
# delete song # delete song
delete_action = QAction("Delete", self) delete_action = QAction("Delete", self)
delete_action.triggered.connect(self.delete_songs) _ = delete_action.triggered.connect(self.delete_songs)
menu.addAction(delete_action) menu.addAction(delete_action)
# show # show
self.set_selected_song_filepath() self.set_selected_song_filepath()
@ -261,8 +251,8 @@ class MusicTable(QTableView):
data = e.mimeData() data = e.mimeData()
debug("dropEvent") debug("dropEvent")
if data and data.hasUrls(): if data and data.hasUrls():
directories = [] directories: list[str] = []
files = [] files: list[str] = []
for url in data.urls(): for url in data.urls():
if url.isLocalFile(): if url.isLocalFile():
path = url.toLocalFile() path = url.toLocalFile()
@ -275,11 +265,9 @@ class MusicTable(QTableView):
e.accept() e.accept()
if directories: if directories:
worker = Worker(self.get_audio_files_recursively, directories) worker = Worker(self.get_audio_files_recursively, directories)
worker.signals.signal_progress.connect(self.handle_progress) _ = worker.signals.signal_progress.connect(self.handle_progress)
worker.signals.signal_result.connect( _ = worker.signals.signal_result.connect(self.on_get_audio_files_recursively_finished)
self.on_get_audio_files_recursively_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
threadpool.start(worker) threadpool.start(worker)
@ -301,9 +289,8 @@ class MusicTable(QTableView):
index = self.currentIndex() index = self.currentIndex()
new_index = self.model2.index(index.row(), index.column() + 1) new_index = self.model2.index(index.row(), index.column() + 1)
if new_index.isValid(): if new_index.isValid():
# print(f"right -> ({new_index.row()},{new_index.column()})")
self.setCurrentIndex(new_index) self.setCurrentIndex(new_index)
self.viewport().update() # type: ignore self.viewport().update()
super().keyPressEvent(e) super().keyPressEvent(e)
return return
@ -311,9 +298,8 @@ class MusicTable(QTableView):
index = self.currentIndex() index = self.currentIndex()
new_index = self.model2.index(index.row(), index.column() - 1) new_index = self.model2.index(index.row(), index.column() - 1)
if new_index.isValid(): if new_index.isValid():
# print(f"left -> ({new_index.row()},{new_index.column()})")
self.setCurrentIndex(new_index) self.setCurrentIndex(new_index)
self.viewport().update() # type: ignore self.viewport().update()
super().keyPressEvent(e) super().keyPressEvent(e)
return return
@ -321,9 +307,8 @@ class MusicTable(QTableView):
index = self.currentIndex() index = self.currentIndex()
new_index = self.model2.index(index.row() - 1, index.column()) new_index = self.model2.index(index.row() - 1, index.column())
if new_index.isValid(): if new_index.isValid():
# print(f"up -> ({new_index.row()},{new_index.column()})")
self.setCurrentIndex(new_index) self.setCurrentIndex(new_index)
self.viewport().update() # type: ignore self.viewport().update()
super().keyPressEvent(e) super().keyPressEvent(e)
return return
@ -331,15 +316,14 @@ class MusicTable(QTableView):
index = self.currentIndex() index = self.currentIndex()
new_index = self.model2.index(index.row() + 1, index.column()) new_index = self.model2.index(index.row() + 1, index.column())
if new_index.isValid(): if new_index.isValid():
# print(f"down -> ({new_index.row()},{new_index.column()})")
self.setCurrentIndex(new_index) self.setCurrentIndex(new_index)
self.viewport().update() # type: ignore self.viewport().update()
super().keyPressEvent(e) super().keyPressEvent(e)
return return
elif key in (Qt.Key.Key_Return, Qt.Key.Key_Enter): elif key in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
if self.state() != QAbstractItemView.EditingState: if self.state() != QAbstractItemView.EditingState:
self.enterKey.emit() # Enter key detected self.enterKey.emit()
else: else:
super().keyPressEvent(e) super().keyPressEvent(e)
else: # Default behavior else: # Default behavior
@ -450,8 +434,10 @@ class MusicTable(QTableView):
- data returned from the original worker process function are returned here - data returned from the original worker process function are returned here
as the first item in a tuple as the first item in a tuple
""" """
_, details = args[0][:2] print('hello?')
print(args)
try: try:
_, details = args[0][:2]
details = dict(tuple(details)[0]) details = dict(tuple(details)[0])
if details: if details:
window = DebugWindow(details) window = DebugWindow(details)
@ -484,14 +470,15 @@ class MusicTable(QTableView):
def add_files_to_library(self, files: list[str]) -> None: def add_files_to_library(self, files: list[str]) -> None:
""" """
Spawns a worker thread - adds a list of filepaths to the library Spawns a worker thread - adds a list of filepaths to the library
- Drag & Drop song(s) on tableView - Drag & Drop song(s) on tableView
- File > Open > List of song(s) - File > Open > List of song(s)
""" """
worker = Worker(add_files_to_database, files) debug('add_files_to_library()')
worker = Worker(add_files_to_database, files, None)
_ = worker.signals.signal_progress.connect(self.qapp.handle_progress) _ = worker.signals.signal_progress.connect(self.qapp.handle_progress)
_ = worker.signals.signal_result.connect( _ = worker.signals.signal_result.connect(self.on_add_files_to_database_finished)
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
threadpool.start(worker) threadpool.start(worker)
@ -711,7 +698,7 @@ class MusicTable(QTableView):
self.set_current_song_filepath() self.set_current_song_filepath()
self.playPauseSignal.emit() self.playPauseSignal.emit()
def load_music_table(self, *playlist_id): def load_music_table(self, *playlist_id: int):
""" """
Loads data into self (QTableView) Loads data into self (QTableView)
Loads all songs in library, by default Loads all songs in library, by default
@ -721,7 +708,7 @@ class MusicTable(QTableView):
""" """
self.disconnect_data_changed() self.disconnect_data_changed()
self.disconnect_layout_changed() self.disconnect_layout_changed()
self.vertical_scroll_position = self.verticalScrollBar().value() # type: ignore self.vertical_scroll_position = self.verticalScrollBar().value()
self.model2.clear() self.model2.clear()
self.model2.setHorizontalHeaderLabels( self.model2.setHorizontalHeaderLabels(
self.headers.get_user_gui_headers()) self.headers.get_user_gui_headers())
@ -845,10 +832,8 @@ class MusicTable(QTableView):
Sorts the data in QTableView (self) by multiple columns Sorts the data in QTableView (self) by multiple columns
as defined in config.ini as defined in config.ini
""" """
# TODO: Rewrite this function to use self.load_music_table() with dynamic SQL queries
# in order to sort the data more effectively & have more control over UI refreshes.
# Disconnect these signals to prevent unnecessary loads # Disconnect these signals to prevent unnecessary reloads
debug("sort_table_by_multiple_columns()") debug("sort_table_by_multiple_columns()")
self.disconnect_data_changed() self.disconnect_data_changed()
self.disconnect_layout_changed() self.disconnect_layout_changed()
@ -879,6 +864,8 @@ class MusicTable(QTableView):
self.connect_data_changed() self.connect_data_changed()
self.connect_layout_changed() self.connect_layout_changed()
# self.model2.layoutChanged.emit() # self.model2.layoutChanged.emit()
# TODO: Rewrite this function to use self.load_music_table() with dynamic SQL queries
# in order to sort the data more effectively & have more control over UI refreshes.
def restore_scroll_position(self) -> None: def restore_scroll_position(self) -> None:
"""Restores the scroll position""" """Restores the scroll position"""
@ -888,10 +875,10 @@ class MusicTable(QTableView):
# lambda: self.verticalScrollBar().setValue(self.vertical_scroll_position), # lambda: self.verticalScrollBar().setValue(self.vertical_scroll_position),
# ) # )
def get_audio_files_recursively(self, directories, progress_callback=None): def get_audio_files_recursively(self, directories: list[str], progress_callback=None) -> list[str]:
"""Scans a directories for files""" """Scans a directories for files"""
extensions = self.config.get("settings", "extensions").split(",") extensions = self.config.get("settings", "extensions").split(",")
audio_files = [] audio_files: list[str] = []
for directory in directories: for directory in directories:
for root, _, files in os.walk(directory): for root, _, files in os.walk(directory):
for file in files: for file in files:
@ -965,9 +952,7 @@ class MusicTable(QTableView):
self.proxymodel.index(row, 0), Qt.ItemDataRole.UserRole self.proxymodel.index(row, 0), Qt.ItemDataRole.UserRole
) )
with DBA.DBAccess() as db: with DBA.DBAccess() as db:
filepath = db.query("SELECT filepath FROM song WHERE id = ?", (id,))[0][ filepath = db.query("SELECT filepath FROM song WHERE id = ?", (id,))[0][0]
0
]
self.selected_song_filepath = filepath self.selected_song_filepath = filepath
def set_current_song_filepath(self, filepath=None) -> None: def set_current_song_filepath(self, filepath=None) -> None:
@ -984,7 +969,7 @@ class MusicTable(QTableView):
else: else:
self.current_song_filepath = filepath self.current_song_filepath = filepath
def set_current_song_qmodel_index(self, index=None): def set_current_song_qmodel_index(self, index: QModelIndex | None = None):
""" """
Takes in the proxy model index for current song - QModelIndex Takes in the proxy model index for current song - QModelIndex
converts to model2 index converts to model2 index
@ -994,9 +979,9 @@ class MusicTable(QTableView):
index = self.currentIndex() index = self.currentIndex()
# map proxy (sortable) model to the original model (used for interactions) # map proxy (sortable) model to the original model (used for interactions)
real_index: QModelIndex = self.proxymodel.mapToSource(index) real_index: QModelIndex = self.proxymodel.mapToSource(index)
self.current_song_qmodel_index: QModelIndex = real_index self.current_song_qmodel_index = real_index
def set_selected_song_qmodel_index(self, index=None): def set_selected_song_qmodel_index(self, index: QModelIndex | None = None):
""" """
Takes in the proxy model index for current song - QModelIndex Takes in the proxy model index for current song - QModelIndex
converts to model2 index converts to model2 index
@ -1006,15 +991,15 @@ class MusicTable(QTableView):
index = self.currentIndex() index = self.currentIndex()
# map proxy (sortable) model to the original model (used for interactions) # map proxy (sortable) model to the original model (used for interactions)
real_index: QModelIndex = self.proxymodel.mapToSource(index) real_index: QModelIndex = self.proxymodel.mapToSource(index)
self.selected_song_qmodel_index: QModelIndex = real_index self.selected_song_qmodel_index = real_index
def set_search_string(self, text: str): def set_search_string(self, text: str):
"""set the search string""" """set the search string"""
self.search_string = text self.search_string = text
def load_qapp(self, qapp) -> None: def load_qapp(self, qapp: QApplication) -> None:
"""Necessary for using members and methods of main application window""" """Necessary for using members and methods of main application window"""
self.qapp = qapp self.qapp: QApplication = qapp
# ____________________ # ____________________
# | | # | |
@ -1033,7 +1018,7 @@ class MusicTable(QTableView):
def connect_data_changed(self): def connect_data_changed(self):
"""Connects the dataChanged signal from QTableView.model""" """Connects the dataChanged signal from QTableView.model"""
try: try:
self.model2.dataChanged.connect(self.on_cell_data_changed) _ = self.model2.dataChanged.connect(self.on_cell_data_changed)
except Exception: except Exception:
pass pass
@ -1047,7 +1032,7 @@ class MusicTable(QTableView):
def connect_layout_changed(self): def connect_layout_changed(self):
"""Connects the layoutChanged signal from QTableView.model""" """Connects the layoutChanged signal from QTableView.model"""
try: try:
self.model2.layoutChanged.connect(self.restore_scroll_position) _ = self.model2.layoutChanged.connect(self.restore_scroll_position)
except Exception: except Exception:
pass pass

52
main.py
View File

@ -12,7 +12,7 @@ from mutagen.id3 import ID3
from configparser import ConfigParser from configparser import ConfigParser
from pathlib import Path from pathlib import Path
from appdirs import user_config_dir from appdirs import user_config_dir
from logging import debug, error, warning, basicConfig, INFO, DEBUG from logging import debug, error, basicConfig, DEBUG
from ui import Ui_MainWindow from ui import Ui_MainWindow
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QFileDialog, QFileDialog,
@ -21,34 +21,25 @@ from PyQt5.QtWidgets import (
QMainWindow, QMainWindow,
QApplication, QApplication,
QGraphicsScene, QGraphicsScene,
QGraphicsPixmapItem,
QMessageBox, QMessageBox,
QPushButton, QPushButton,
QStatusBar, QStatusBar,
QStyle, QStyle,
QTableView,
) )
from PyQt5.QtCore import ( from PyQt5.QtCore import (
QModelIndex, QModelIndex,
QSize, QSize,
QThread,
QUrl, QUrl,
QTimer, QTimer,
Qt,
pyqtSignal, pyqtSignal,
QObject,
pyqtSlot,
QThreadPool, QThreadPool,
QRunnable,
) )
from PyQt5.QtMultimedia import ( from PyQt5.QtMultimedia import (
QMediaPlayer, QMediaPlayer,
QMediaContent, QMediaContent,
QAudioProbe, QAudioProbe,
QMediaPlaylist,
QMultimedia,
) )
from PyQt5.QtGui import QClipboard, QCloseEvent, QFont, QPixmap, QResizeEvent from PyQt5.QtGui import QCloseEvent, QFont, QResizeEvent
from utils import ( from utils import (
delete_album_art, delete_album_art,
get_tags, get_tags,
@ -68,7 +59,6 @@ from components import (
AudioVisualizer, AudioVisualizer,
CreatePlaylistWindow, CreatePlaylistWindow,
ExportPlaylistWindow, ExportPlaylistWindow,
SearchLineEdit,
) )
# good help with signals slots in threads # good help with signals slots in threads
@ -79,14 +69,14 @@ from components import (
class ApplicationWindow(QMainWindow, Ui_MainWindow): class ApplicationWindow(QMainWindow, Ui_MainWindow):
reloadConfigSignal = pyqtSignal() reloadConfigSignal: pyqtSignal = pyqtSignal()
reloadDatabaseSignal = pyqtSignal() reloadDatabaseSignal: pyqtSignal = pyqtSignal()
def __init__(self, clipboard): def __init__(self, clipboard):
super(ApplicationWindow, self).__init__() super(ApplicationWindow, self).__init__()
self.clipboard = clipboard self.clipboard = clipboard
self.config: ConfigParser = ConfigParser() self.config: ConfigParser = ConfigParser()
self.cfg_file = ( self.cfg_file: Path = (
Path(user_config_dir(appname="musicpom", appauthor="billypom")) Path(user_config_dir(appname="musicpom", appauthor="billypom"))
/ "config.ini" / "config.ini"
) )
@ -119,14 +109,15 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
self.audio_visualizer: AudioVisualizer = AudioVisualizer( self.audio_visualizer: AudioVisualizer = AudioVisualizer(
self.player, self.probe, self.PlotWidget self.player, self.probe, self.PlotWidget
) )
self.timer = QTimer(self) # for playback slider and such self.timer: QTimer = QTimer(parent=self) # for playback slider and such
# Button styles # Button styles
style: QStyle | None
if not self.style(): if not self.style():
style = QStyle() style = QStyle()
else: else:
style = self.style() style = self.style()
assert style is not None # i hate linting errors assert style is not None
pixmapi = QStyle.StandardPixmap.SP_MediaSkipForward pixmapi = QStyle.StandardPixmap.SP_MediaSkipForward
icon = style.standardIcon(pixmapi) icon = style.standardIcon(pixmapi)
self.nextButton.setIcon(icon) self.nextButton.setIcon(icon)
@ -380,7 +371,6 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
QtCore.Qt.TextInteractionFlag.TextSelectableByMouse QtCore.Qt.TextInteractionFlag.TextSelectableByMouse
) )
font: QFont = QFont()
font.setPointSize(12) font.setPointSize(12)
font.setBold(False) font.setBold(False)
self.titleLabel.setFont(font) self.titleLabel.setFont(font)
@ -388,7 +378,6 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
QtCore.Qt.TextInteractionFlag.TextSelectableByMouse QtCore.Qt.TextInteractionFlag.TextSelectableByMouse
) )
font: QFont = QFont()
font.setPointSize(12) font.setPointSize(12)
font.setItalic(True) font.setItalic(True)
self.albumLabel.setFont(font) self.albumLabel.setFont(font)
@ -585,10 +574,8 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
preferences_window = PreferencesWindow( preferences_window = PreferencesWindow(
self.reloadConfigSignal, self.reloadDatabaseSignal self.reloadConfigSignal, self.reloadDatabaseSignal
) )
preferences_window.reloadConfigSignal.connect( preferences_window.reloadConfigSignal.connect(self.load_config)
self.load_config) # type: ignore preferences_window.reloadDatabaseSignal.connect(self.tableView.load_music_table)
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
@ -606,11 +593,11 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
def delete_database(self) -> None: def delete_database(self) -> None:
"""Deletes the entire database""" """Deletes the entire database"""
reply = QMessageBox.question( reply = QMessageBox.question(
self, parent=self,
"Confirmation", title="Confirmation",
"Delete database?", text="Delete database?",
QMessageBox.Yes | QMessageBox.No, buttons=QMessageBox.Yes | QMessageBox.No,
QMessageBox.Yes, defaultButton=QMessageBox.Yes,
) )
if reply == QMessageBox.Yes: if reply == QMessageBox.Yes:
initialize_db() initialize_db()
@ -629,7 +616,8 @@ def update_database_file() -> bool:
appname="musicpom", appauthor="billypom"))) 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
db_filepath= config.get("settings", "db")
# If the database location isnt set at the config location, move it # If the database location isnt set at the config location, move it
if not db_filepath.startswith(cfg_path): if not db_filepath.startswith(cfg_path):
@ -643,7 +631,7 @@ def update_database_file() -> bool:
with open(cfg_file, "w") as configfile: with open(cfg_file, "w") as configfile:
config.write(configfile) config.write(configfile)
config.read(cfg_file) config.read(cfg_file)
db_filepath: str = config.get("settings", "db") db_filepath = config.get("settings", "db")
db_path = db_filepath.split("/") db_path = db_filepath.split("/")
db_path.pop() db_path.pop()
@ -722,8 +710,8 @@ if __name__ == "__main__":
format="{%(filename)s:%(lineno)d} %(levelname)s - %(message)s", format="{%(filename)s:%(lineno)d} %(levelname)s - %(message)s",
handlers=handlers, handlers=handlers,
) )
debug(f'--------- musicpom debug started') debug('--------- musicpom debug started')
debug(f'---------------------| ') debug('---------------------| ')
debug(f'----------------------> {handlers} ') debug(f'----------------------> {handlers} ')
# Initialization # Initialization
config: ConfigParser = update_config_file() config: ConfigParser = update_config_file()

View File

@ -1,5 +1,3 @@
from PyQt5.QtWidgets import QMessageBox
from mutagen.id3 import ID3
import DBA import DBA
from logging import debug from logging import debug
from utils import get_tags, convert_id3_timestamp_to_datetime, id3_remap from utils import get_tags, convert_id3_timestamp_to_datetime, id3_remap
@ -8,7 +6,7 @@ from pathlib import Path
from appdirs import user_config_dir from appdirs import user_config_dir
def add_files_to_database(files, progress_callback=None): def add_files_to_database(files: list[str], playlist_id: int | None = None, progress_callback=None) -> tuple[bool, dict[str, str]]:
""" """
Adds audio file(s) to the sqllite db "song" table Adds audio file(s) to the sqllite db "song" table
Args: Args:
@ -21,26 +19,40 @@ def add_files_to_database(files, progress_callback=None):
(True, {"filename.mp3":"failed because i said so"}) (True, {"filename.mp3":"failed because i said so"})
``` ```
""" """
# yea
if playlist_id:
pass
config = ConfigParser() config = ConfigParser()
cfg_file = ( cfg_file = (
Path(user_config_dir(appname="musicpom", appauthor="billypom")) / "config.ini" Path(user_config_dir(appname="musicpom", appauthor="billypom")) / "config.ini"
) )
config.read(cfg_file) _ = config.read(cfg_file)
if not files: if not files:
return False, {"Failure": "All operations failed in add_files_to_database()"} return False, {"Failure": "All operations failed in add_files_to_database()"}
failed_dict = {} failed_dict: dict[str, str] = {}
insert_data = [] # To store data for batch insert insert_data: list[tuple[
str,
str | int | None,
str | int | None,
str | int | None,
str | int | None,
str | int | None,
str,
str | int | None,
str | int | None,
str | int | None]] = [] # To store data for batch insert
for filepath in files: for filepath in files:
if progress_callback: if progress_callback:
progress_callback.emit(filepath) progress_callback.emit(filepath)
filename = filepath.split("/")[-1] filename = filepath.split("/")[-1]
tags, fail_reason = get_tags(filepath)
tags, details = get_tags(filepath) if fail_reason:
if details: # if we fail to get audio tags, skip to next song
failed_dict[filepath] = details failed_dict[filepath] = fail_reason
continue continue
audio = id3_remap(tags) # remap tags from ID3 to database tags
audio: dict[str, str | int | None] = id3_remap(tags)
# Append data tuple to insert_data list # Append data tuple to insert_data list
insert_data.append( insert_data.append(
( (
@ -60,21 +72,22 @@ def add_files_to_database(files, progress_callback=None):
if len(insert_data) >= 1000: if len(insert_data) >= 1000:
debug(f"inserting a LOT of songs: {len(insert_data)}") debug(f"inserting a LOT of songs: {len(insert_data)}")
with DBA.DBAccess() as db: with DBA.DBAccess() as db:
db.executemany( result = db.executemany(
"INSERT OR IGNORE INTO song (filepath, title, album, artist, track_number, genre, codec, album_date, bitrate, length_seconds) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", "INSERT OR IGNORE INTO song (filepath, title, album, artist, track_number, genre, codec, album_date, bitrate, length_seconds) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id",
insert_data, insert_data,
) )
debug('IMPORTANT')
debug(f'batch insert result: {result}')
debug('IMPORTANT')
insert_data = [] # Reset the insert_data list insert_data = [] # Reset the insert_data list
else: # Insert any remaining data after reading every file
# continue adding files if we havent reached big length
continue
# Insert any remaining data
debug("i check for insert data")
if insert_data: if insert_data:
debug(f"inserting some songs: {len(insert_data)}")
with DBA.DBAccess() as db: with DBA.DBAccess() as db:
db.executemany( result = db.executemany(
"INSERT OR IGNORE INTO song (filepath, title, album, artist, track_number, genre, codec, album_date, bitrate, length_seconds) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", "INSERT OR IGNORE INTO song (filepath, title, album, artist, track_number, genre, codec, album_date, bitrate, length_seconds) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id",
insert_data, insert_data,
) )
debug('IMPORTANT')
debug(f'batch insert result: {result}')
debug('IMPORTANT')
return True, failed_dict return True, failed_dict

View File

@ -34,7 +34,7 @@ def get_mp3_tags(filename: str) -> tuple[MP3 | ID3 | FLAC, str]:
return MP3(), f"Could not assign ID3 tag to file: {e}" return MP3(), f"Could not assign ID3 tag to file: {e}"
def id3_remap(audio: MP3 | ID3 | FLAC) -> dict: def id3_remap(audio: MP3 | ID3 | FLAC) -> dict[str, str | int | None]:
""" """
Turns the ID3 dict of an audio file into a normal dict that I, the human, can use. Turns the ID3 dict of an audio file into a normal dict that I, the human, can use.
Add extra fields too :D yahooo Add extra fields too :D yahooo