diff --git a/components/HeaderTags.py b/components/HeaderTags.py index 891313b..c08898a 100644 --- a/components/HeaderTags.py +++ b/components/HeaderTags.py @@ -4,15 +4,31 @@ from appdirs import user_config_dir from dataclasses import dataclass, asdict from typing import Optional + @dataclass -class SQLiteMap(): - title: str - artist: str +class SQLiteMap: + title: Optional[str] = None + artist: Optional[str] = None album: Optional[str] = None + album_artist: Optional[str] = None + track_number: Optional[str] = None + genre: Optional[str] = None + length_seconds: Optional[str] = None + album_date: Optional[str] = None + codec: Optional[str] = None + filepath: Optional[str] = None +""" +db names are called FIELDS (e.g., title, track_number, length_seconds) +gui names are called HEADERS (e.g., title, track, length, year) +id3 names are called TAGS (e.g., TIT2, TPE1, TALB) -class HeaderTags(): +is dataclasses rly worth it? +""" + + +class HeaderTags: """ Utility class to converting between different "standards" for tags (headers, id3, etc) @@ -20,8 +36,8 @@ class HeaderTags(): `gui`: dict = "db name": "gui string" `id3`: dict = "db name": "id3 tag string" `id3_keys`: dict = "id3 tag string": "db name" - `editable_db_tags`: list = "list of db names that are user editable" - `user_headers`: list = "list of db headers that the user has chosen to see in gui" + `editable_fields`: list = "list of db names that are user editable" + `user_fields`: list = "list of db headers that the user has chosen to see in gui" """ def __init__(self): @@ -31,7 +47,43 @@ class HeaderTags(): ) self.config = ConfigParser() self.config.read(cfg_file) - self.user_headers: list = str(self.config["table"]["columns"]).split(",") + print("header tag config") + print(self.config) + self.user_fields: list = str(self.config["table"]["columns"]).split(",") + self.editable_fields: list = [ + "title", + "artist", + "album_artist", + "album", + "track_number", + "genre", + "album_date", + ] + self.fields = SQLiteMap() + self.headers = SQLiteMap( + title="title", + artist="artist", + album="album", + album_artist="alb artist", + track_number="track", + genre="genre", + codec="codec", + length_seconds="length", + album_date="year", + filepath="path", + ) + # self.id3 = SQLiteMap( + # title = "TIT2", + # artist = "TPE1", + # album = "TALB", + # album_artist = "TPE2", + # track_number = "TRCK", + # genre = "TCON", + # length_seconds = "TLEN", + # album_date = "TDRC", + # codec = None, + # filepath = None + # ) self.db: dict = { "title": "title", "artist": "artist", @@ -74,20 +126,10 @@ class HeaderTags(): if v is not None: self.id3_keys[v] = k - self.editable_db_tags: list = [ - "title", - "artist", - "album_artist", - "album", - "track_number", - "genre", - "album_date", - ] - def get_user_gui_headers(self) -> list: """Returns a list of headers for the GUI""" gui_headers = [] - for db, gui in self.gui.items(): - if db in self.user_headers: + for db, gui in asdict(self.headers).items(): + if db in self.user_fields: gui_headers.append(gui) return gui_headers diff --git a/components/MetadataWindow.py b/components/MetadataWindow.py index e5af1a5..09a5569 100644 --- a/components/MetadataWindow.py +++ b/components/MetadataWindow.py @@ -56,9 +56,10 @@ class MetadataWindow(QDialog): layout.addWidget(category_label) layout.addWidget(h_separator) - tag_sets: dict = {} + tag_sets: dict[str, list] = {} # Get a dict of all tags for all songs # e.g., { "TIT2": ["song_title1", "song_title2"], ... } + # e.g., { "title": ["song_title1", "song_title2"], ... } for song in self.songs: song_data = get_tags(song[0])[0] if not song_data: @@ -71,7 +72,7 @@ class MetadataWindow(QDialog): ) return for key, tag in self.headers.id3.items(): - if key not in self.headers.editable_db_tags: + if key not in self.headers.editable_fields: continue if tag is not None: try: diff --git a/components/MusicTable.py b/components/MusicTable.py index 9df428c..10a0a58 100644 --- a/components/MusicTable.py +++ b/components/MusicTable.py @@ -1,7 +1,5 @@ from mutagen.id3 import ID3 -from json import load as jsonload import DBA -from pprint import pprint from PyQt5.QtGui import ( QColor, QDragMoveEvent, @@ -18,12 +16,10 @@ from PyQt5.QtWidgets import ( QAction, QHeaderView, QMenu, - QPlainTextEdit, QTableView, QShortcut, QMessageBox, QAbstractItemView, - QVBoxLayout, ) from PyQt5.QtCore import ( QItemSelectionModel, @@ -32,7 +28,6 @@ from PyQt5.QtCore import ( QModelIndex, QThreadPool, pyqtSignal, - QTimer, ) from components.DebugWindow import DebugWindow from components.ErrorDialog import ErrorDialog @@ -104,6 +99,8 @@ class MusicTable(QTableView): ) self.config = ConfigParser() self.config.read(cfg_file) + print("music table config:") + print(self.config) # Threads self.threadpool = QThreadPool @@ -127,8 +124,6 @@ class MusicTable(QTableView): self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSelectionBehavior(QAbstractItemView.SelectRows) # header - # FIXME: table headers being resized and going out window bounds - # causing some recursion errors... self.horizontal_header: QHeaderView = self.horizontalHeader() assert self.horizontal_header is not None # i hate look at linting errors self.horizontal_header.setStretchLastSection(True) @@ -342,7 +337,7 @@ class MusicTable(QTableView): def on_sort(self): debug("on_sort") - search_col_num = self.headers.user_headers.index("filepath") + search_col_num = self.headers.user_fields.index("filepath") selected_qmodel_index = self.find_qmodel_index_by_value( self.proxymodel, search_col_num, self.selected_song_filepath ) @@ -397,18 +392,14 @@ class MusicTable(QTableView): if isinstance(self.model2, QStandardItemModel): debug("on_cell_data_changed") # get the ID of the row that was edited - id_index = self.model2.index(topLeft.row(), 0) # ID is column 0, always + id_index = self.model2.index(topLeft.row(), 0) # get the db song_id from the row song_id = self.model2.data(id_index, Qt.ItemDataRole.UserRole) - # get the filepath through a series of steps... - # NOTE: filepath is always the last column - filepath_column_idx = self.model2.columnCount() - 1 - filepath_index = self.model2.index(topLeft.row(), filepath_column_idx) - filepath = self.model2.data(filepath_index) + user_index = self.headers.user_fields.index("filepath") + filepath = self.currentIndex().siblingAtColumn(user_index).data() # update the ID3 information user_input_data = topLeft.data() - # edited_column_name = self.database_columns[topLeft.column()] - edited_column_name = self.headers.user_headers[topLeft.column()] + edited_column_name = self.headers.user_fields[topLeft.column()] debug(f"on_cell_data_changed | edited column name: {edited_column_name}") response = set_tag(filepath, edited_column_name, user_input_data) if response: @@ -677,7 +668,7 @@ class MusicTable(QTableView): self.vertical_scroll_position = self.verticalScrollBar().value() # type: ignore self.model2.clear() self.model2.setHorizontalHeaderLabels(self.headers.get_user_gui_headers()) - fields = ", ".join(self.headers.user_headers) + fields = ", ".join(self.headers.user_fields) if playlist_id: # Load a playlist # Fetch playlist data selected_playlist_id = playlist_id[0] @@ -730,13 +721,15 @@ class MusicTable(QTableView): # reloading the model destroys and makes new indexes # so we look for the new index of the current song on load current_song_filepath = self.get_current_song_filepath() - print(f'load music table current filepath: {current_song_filepath}') + print(f"load music table current filepath: {current_song_filepath}") for row in range(self.model2.rowCount()): - real_index = self.model2.index(row, self.headers.user_headers.index("filepath")) + real_index = self.model2.index( + row, self.headers.user_fields.index("filepath") + ) if real_index.data() == current_song_filepath: - print('is it true?') - print(f'{real_index.data()} == {current_song_filepath}') - print('load music table real index:') + print("is it true?") + print(f"{real_index.data()} == {current_song_filepath}") + print("load music table real index:") print(real_index) self.current_song_qmodel_index = real_index self.model2.layoutChanged.emit() # emits a signal that the view should be updated @@ -833,9 +826,7 @@ class MusicTable(QTableView): selected_rows = self.get_selected_rows() filepaths = [] for row in selected_rows: - idx = self.proxymodel.index( - row, self.headers.user_headers.index("filepath") - ) + idx = self.proxymodel.index(row, self.headers.user_fields.index("filepath")) filepaths.append(idx.data()) return filepaths @@ -850,7 +841,9 @@ class MusicTable(QTableView): return [] selected_rows = set(index.row() for index in indexes) id_list = [ - self.proxymodel.data(self.proxymodel.index(row, 0), Qt.ItemDataRole.UserRole) + self.proxymodel.data( + self.proxymodel.index(row, 0), Qt.ItemDataRole.UserRole + ) for row in selected_rows ] return id_list @@ -870,14 +863,18 @@ class MusicTable(QTableView): def set_selected_song_filepath(self) -> None: """Sets the filepath of the currently selected song""" try: - table_index = self.headers.user_headers.index("filepath") - filepath = self.currentIndex().siblingAtColumn(table_index).data() + user_index = self.headers.user_fields.index("filepath") + filepath = self.currentIndex().siblingAtColumn(user_index).data() except ValueError: # if the user doesnt have filepath selected as a header, retrieve the file from db row = self.currentIndex().row() - id = self.proxymodel.data(self.proxymodel.index(row, 0), Qt.ItemDataRole.UserRole) + id = self.proxymodel.data( + self.proxymodel.index(row, 0), Qt.ItemDataRole.UserRole + ) with DBA.DBAccess() as db: - filepath = db.query('SELECT filepath FROM song WHERE id = ?', (id,))[0][0] + filepath = db.query("SELECT filepath FROM song WHERE id = ?", (id,))[0][ + 0 + ] self.selected_song_filepath = filepath def set_current_song_filepath(self, filepath=None) -> None: @@ -887,7 +884,9 @@ class MusicTable(QTableView): """ # update the filepath if not filepath: - path = self.current_song_qmodel_index.siblingAtColumn(self.headers.user_headers.index("filepath")).data() + path = self.current_song_qmodel_index.siblingAtColumn( + self.headers.user_fields.index("filepath") + ).data() self.current_song_filepath: str = path else: self.current_song_filepath = filepath diff --git a/main.py b/main.py index 973622d..d92e006 100644 --- a/main.py +++ b/main.py @@ -41,7 +41,13 @@ from PyQt5.QtCore import ( QThreadPool, QRunnable, ) -from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QAudioProbe, QMediaPlaylist, QMultimedia +from PyQt5.QtMultimedia import ( + QMediaPlayer, + QMediaContent, + QAudioProbe, + QMediaPlaylist, + QMultimedia, +) from PyQt5.QtGui import QClipboard, QCloseEvent, QFont, QPixmap, QResizeEvent from utils import ( delete_album_art, @@ -50,7 +56,7 @@ from utils import ( initialize_db, add_files_to_database, set_album_art, - id3_remap + id3_remap, ) from components import ( HeaderTags, @@ -155,6 +161,8 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow): / "config.ini" ) self.config.read(self.cfg_file) + print("main config:") + print(self.config) self.threadpool: QThreadPool = QThreadPool() # UI self.setupUi(self) @@ -328,7 +336,9 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow): file_url = media.canonicalUrl().toLocalFile() metadata = id3_remap(get_tags(file_url)[0]) if metadata is not None: - self.set_ui_metadata(metadata["title"], metadata["artist"], metadata["album"], file_url) + self.set_ui_metadata( + metadata["title"], metadata["artist"], metadata["album"], file_url + ) def on_volume_changed(self) -> None: """Handles volume changes""" @@ -374,8 +384,12 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow): return row: int = index.row() prev_row: int = row - 1 - prev_index: QModelIndex = self.tableView.proxymodel.index(prev_row, index.column()) - prev_filepath = prev_index.siblingAtColumn(self.headers.user_headers.index("filepath")).data() + prev_index: QModelIndex = self.tableView.proxymodel.index( + prev_row, index.column() + ) + prev_filepath = prev_index.siblingAtColumn( + self.headers.user_fields.index("filepath") + ).data() if prev_filepath is None: return @@ -394,8 +408,12 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow): return row: int = index.row() next_row: int = row + 1 - next_index: QModelIndex = self.tableView.proxymodel.index(next_row, index.column()) - next_filepath = next_index.siblingAtColumn(self.headers.user_headers.index("filepath")).data() + next_index: QModelIndex = self.tableView.proxymodel.index( + next_row, index.column() + ) + next_filepath = next_index.siblingAtColumn( + self.headers.user_fields.index("filepath") + ).data() if next_filepath is None: return self.play_audio_file(next_filepath) @@ -471,7 +489,7 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow): filepath default value = `tableView.current_song_filepath` """ - print('play audio file') + print("play audio file") if not filepath: filepath = self.tableView.get_selected_song_filepath() metadata = id3_remap(get_tags(filepath)[0]) @@ -487,7 +505,9 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow): # assign "now playing" labels & album artwork if metadata is not None: - self.set_ui_metadata(metadata["title"], metadata["artist"], metadata["album"], filepath) + self.set_ui_metadata( + metadata["title"], metadata["artist"], metadata["album"], filepath + ) def set_ui_metadata(self, title, artist, album, filepath): """ diff --git a/ui.ui b/ui.ui index 625aec1..3a82b2a 100644 --- a/ui.ui +++ b/ui.ui @@ -6,8 +6,8 @@ 0 0 - 1152 - 894 + 953 + 739 @@ -310,7 +310,7 @@ 0 0 - 1152 + 953 24 diff --git a/utils/set_tag.py b/utils/set_tag.py index 32bdfe1..067f7b9 100644 --- a/utils/set_tag.py +++ b/utils/set_tag.py @@ -107,6 +107,7 @@ def set_tag(filepath: str, tag_name: str, value: str): # if tdat_tag: # # update TDAT if we have it # audio_file.add(tdat_tag) + # Lyrics if tag_name == "lyrics" or tag_name == "USLT": try: