From 63c54f9de792abcf7bb8e1abf504c6a952f4e0bc Mon Sep 17 00:00:00 2001 From: tsi-billypom Date: Tue, 13 Aug 2024 14:18:03 -0400 Subject: [PATCH] MetadataWindow update to DB and id3 tags complete --- components/MetadataWindow.py | 43 +++++++++++++------ components/MusicTable.py | 24 +++++++---- main.py | 2 +- utils/__init__.py | 2 +- utils/id3_tag_mapping.py | 10 +++++ utils/set_id3_tag.py | 6 +-- ..._library.py => update_song_in_database.py} | 12 +++--- 7 files changed, 66 insertions(+), 33 deletions(-) create mode 100644 utils/id3_tag_mapping.py rename utils/{update_song_in_library.py => update_song_in_database.py} (70%) diff --git a/components/MetadataWindow.py b/components/MetadataWindow.py index 6d75fe1..cfd26ea 100644 --- a/components/MetadataWindow.py +++ b/components/MetadataWindow.py @@ -8,13 +8,19 @@ from PyQt5.QtWidgets import ( QPushButton, ) from PyQt5.QtGui import QFont +from PyQt5.QtCore import pyqtSignal from mutagen.id3 import ID3 +from components.ErrorDialog import ErrorDialog from utils.get_id3_tags import get_id3_tags from utils.set_id3_tag import set_id3_tag +from utils.update_song_in_database import update_song_in_database +from utils.id3_tag_mapping import id3_tag_mapping class MetadataWindow(QDialog): - def __init__(self, songs: list): + refreshMusicTableSignal = pyqtSignal() + + def __init__(self, refreshMusicTableSignal, songs: list, ids: list): """ Window that allows batch editing of metadata for multiple files @@ -22,17 +28,9 @@ class MetadataWindow(QDialog): - list of strings, absolute paths to mp3 files """ super(MetadataWindow, self).__init__() - self.songs = songs - self.id3_tag_mapping = { - "TIT2": "title", - "TPE1": "artist", - "TALB": "album", - "TPE2": "album_artist", - "TCON": "genre", - "TRCK": "track_number", - "APIC": "album_cover", - "TCOP": "copyright", - } + self.refreshMusicTableSignal = refreshMusicTableSignal + self.songs = list(zip(songs, ids)) + self.id3_tag_mapping = id3_tag_mapping # Keep a dictionary of the input fields for save function self.input_fields = {} self.setWindowTitle("Edit metadata") @@ -52,7 +50,7 @@ class MetadataWindow(QDialog): # Get a dict of all tags for all songs # e.g., { "TIT2": ["song_title1", "song_title2"], ... } for song in self.songs: - song_data = get_id3_tags(song) + song_data = get_id3_tags(song[0]) for tag in self.id3_tag_mapping: try: _ = tag_sets[tag] @@ -107,5 +105,22 @@ class MetadataWindow(QDialog): """Save changes made to metadata for each song in dict""" for song in self.songs: for tag, field in self.input_fields.items(): - set_id3_tag(filepath=song, tag_name=tag, value=field) + if field.text() is not None and field.text() != "": + # Update the ID3 tag if not blank + success = set_id3_tag( + filepath=song[0], tag_name=tag, value=field.text() + ) + if success: + update_song_in_database( + song[1], + edited_column_name=self.id3_tag_mapping[tag], + user_input_data=field.text(), + ) + else: + error_dialog = ErrorDialog( + f"Could not update metadata for {song}. Exiting early" + ) + error_dialog.exec() + self.close() + self.refreshMusicTableSignal.emit() self.close() diff --git a/components/MusicTable.py b/components/MusicTable.py index cde7132..11c25e5 100644 --- a/components/MusicTable.py +++ b/components/MusicTable.py @@ -23,7 +23,7 @@ from components.AddToPlaylistWindow import AddToPlaylistWindow from components.MetadataWindow import MetadataWindow 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.update_song_in_database import update_song_in_database from utils.get_id3_tags import get_id3_tags from utils.get_album_art import get_album_art from utils import set_id3_tag @@ -38,12 +38,15 @@ class MusicTable(QTableView): playPauseSignal = pyqtSignal() enterKey = pyqtSignal() deleteKey = pyqtSignal() + refreshMusicTable = pyqtSignal() def __init__(self: QTableView, parent=None): # QTableView.__init__(self, parent) super().__init__(parent) # Necessary for actions related to cell values + # FIXME: why do these give me pyright errors self.model = QStandardItemModel(self) + # self.model = QAbstractItemModel(self) self.setModel(self.model) # Config @@ -165,7 +168,9 @@ class MusicTable(QTableView): # FIXME: """Opens a form with metadata from the selected audio files""" files = self.get_selected_songs_filepaths() - window = MetadataWindow(files) + song_ids = self.get_selected_songs_db_ids() + window = MetadataWindow(self.refreshMusicTable, files, song_ids) + window.refreshMusicTableSignal.connect(self.load_music_table) window.exec_() # Display the preferences window modally def add_selected_files_to_playlist(self): @@ -231,9 +236,11 @@ class MusicTable(QTableView): else: e.ignore() - def keyPressEvent(self, event): + def keyPressEvent(self, e): """Press a key. Do a thing""" - key = event.key() + if not e: + return + key = e.key() if key == Qt.Key_Space: # Spacebar to play/pause self.toggle_play_pause() elif key == Qt.Key_Up: # Arrow key navigation @@ -254,9 +261,9 @@ class MusicTable(QTableView): if self.state() != QAbstractItemView.EditingState: self.enterKey.emit() # Enter key detected else: - super().keyPressEvent(event) + super().keyPressEvent(e) else: # Default behavior - super().keyPressEvent(event) + super().keyPressEvent(e) def setup_keyboard_shortcuts(self): """Setup shortcuts here""" @@ -267,7 +274,7 @@ class MusicTable(QTableView): """Handles updating ID3 tags when data changes in a cell""" print("on_cell_data_changed") id_index = self.model.index(topLeft.row(), 0) # ID is column 0, always - library_id = self.model.data(id_index, Qt.UserRole) + song_id = self.model.data(id_index, Qt.UserRole) # filepath is always the last column filepath_column_idx = self.model.columnCount() - 1 filepath_index = self.model.index(topLeft.row(), filepath_column_idx) @@ -280,7 +287,7 @@ class MusicTable(QTableView): response = set_id3_tag(filepath, edited_column_name, user_input_data) if response: # Update the library with new metadata - update_song_in_library(library_id, edited_column_name, user_input_data) + update_song_in_database(song_id, edited_column_name, user_input_data) def reorganize_selected_files(self): """Ctrl+Shift+R = Reorganize""" @@ -411,6 +418,7 @@ class MusicTable(QTableView): self.model.layoutChanged.emit() # emits a signal that the view should be updated try: self.model.dataChanged.connect(self.on_cell_data_changed) + self.restore_scroll_position() except Exception: pass diff --git a/main.py b/main.py index 60bbd27..c899c08 100644 --- a/main.py +++ b/main.py @@ -440,7 +440,7 @@ if __name__ == "__main__": sys.path.append(project_root) # Start the app app = QApplication(sys.argv) - print(f"main.py app: {app}") + # print(f"main.py app: {app}") # Dark theme >:3 qdarktheme.setup_theme() # Show the UI diff --git a/utils/__init__.py b/utils/__init__.py index e1618e1..345eaae 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -7,7 +7,7 @@ from .get_id3_tags import get_id3_tags from .set_id3_tag import set_id3_tag from .delete_song_id_from_database import delete_song_id_from_database from .delete_and_create_library_database import delete_and_create_library_database -from .update_song_in_library import update_song_in_library +from .update_song_in_database import update_song_in_database from .scan_for_music import scan_for_music from .add_files_to_library import add_files_to_library from .handle_year_and_date_id3_tag import handle_year_and_date_id3_tag diff --git a/utils/id3_tag_mapping.py b/utils/id3_tag_mapping.py new file mode 100644 index 0000000..df9915f --- /dev/null +++ b/utils/id3_tag_mapping.py @@ -0,0 +1,10 @@ +id3_tag_mapping = { + "TIT2": "title", + "TPE1": "artist", + "TALB": "album", + "TPE2": "album_artist", + "TCON": "genre", + "TRCK": "track_number", + # "APIC": "album_cover", + "TCOP": "copyright", +} diff --git a/utils/set_id3_tag.py b/utils/set_id3_tag.py index 420b541..c23cbed 100644 --- a/utils/set_id3_tag.py +++ b/utils/set_id3_tag.py @@ -40,7 +40,7 @@ from mutagen.id3._frames import ( WPUB, ) -id3_tag_mapping = { +mutagen_id3_tag_mapping = { "title": TIT2, # Title/song name/content description "artist": TPE1, # Lead performer(s)/Soloist(s) "album": TALB, # Album/Movie/Show title @@ -119,8 +119,8 @@ def set_id3_tag(filepath: str, tag_name: str, value: str): audio.save() return True # Other - elif tag_name in id3_tag_mapping: # Tag accounted for - tag_class = id3_tag_mapping[tag_name] + elif tag_name in mutagen_id3_tag_mapping: # Tag accounted for + tag_class = mutagen_id3_tag_mapping[tag_name] if issubclass(tag_class, Frame): frame = tag_class(encoding=3, text=[value]) audio_file.add(frame) # Add the tag diff --git a/utils/update_song_in_library.py b/utils/update_song_in_database.py similarity index 70% rename from utils/update_song_in_library.py rename to utils/update_song_in_database.py index fc310cb..43584b4 100644 --- a/utils/update_song_in_library.py +++ b/utils/update_song_in_database.py @@ -2,13 +2,13 @@ import DBA from components.ErrorDialog import ErrorDialog -def update_song_in_library( - library_id: int, edited_column_name: str, user_input_data: str +def update_song_in_database( + song_id: int, edited_column_name: str, user_input_data: str ): - """Updates a field in the library database based on an ID + """Updates a field in the database based on an ID Args: - library_id: the database ID of the song + song_id: the database ID of the song edited_column_name: the name of the database column user_input_data: the data to input @@ -19,11 +19,11 @@ def update_song_in_library( # yeah yeah this is bad... the column names are defined in the program by me so im ok with it because it works db.execute( f"UPDATE song SET {edited_column_name} = ? WHERE id = ?", - (user_input_data, library_id), + (user_input_data, song_id), ) except Exception as e: dialog = ErrorDialog( - f"Unable to update [{edited_column_name}] to [{user_input_data}]. ID: {library_id} | {e}" + f"Unable to update [{edited_column_name}] to [{user_input_data}]. ID: {song_id} | {e}" ) dialog.exec_() return False