sorting works now. mostly... also lyrics window small fix

This commit is contained in:
billy@pom 2026-01-11 12:00:11 -05:00
parent c4e5105f91
commit 8420b31057
5 changed files with 83 additions and 130 deletions

View File

@ -2,10 +2,8 @@ from PyQt5.QtWidgets import (
QDialog, QDialog,
QPlainTextEdit, QPlainTextEdit,
QVBoxLayout, QVBoxLayout,
QLabel,
QPushButton, QPushButton,
) )
from PyQt5.QtGui import QFont
from components.ErrorDialog import ErrorDialog from components.ErrorDialog import ErrorDialog
from utils import set_tag from utils import set_tag
from logging import debug from logging import debug

View File

@ -83,6 +83,8 @@ class MusicTable(QTableView):
/ "config.ini" / "config.ini"
) )
_ = self.config.read(self.cfg_file) _ = self.config.read(self.cfg_file)
self.sort_config: str = self.config.get('table', 'sort_order')
self.column_sort_order: list[tuple[str, Qt.SortOrder | None]] = []
font: QFont = QFont() font: QFont = QFont()
font.setPointSize(11) font.setPointSize(11)
@ -363,18 +365,12 @@ class MusicTable(QTableView):
# | | # | |
# |____________________| # |____________________|
def on_sort(self):
debug('on_sort')
self.find_current_and_selected_bits()
debug('on_sort(): done finding current and selected bits')
self.jump_to_selected_song()
# self.sortSignal.emit()
# debug('sortSignal emitted')
def on_header_clicked(self, logicalIndex: QModelIndex): def on_header_clicked(self, logicalIndex: QModelIndex):
debug('on_header_clicked()') """
debug(f'- logicalIndex: {logicalIndex}') self.horizontal_header.sectionClicked.connect(self.on_header_clicked)
self.on_sort() """
self.find_current_and_selected_bits()
self.jump_to_selected_song()
def on_cell_clicked(self, index): def on_cell_clicked(self, index):
@ -382,7 +378,6 @@ class MusicTable(QTableView):
When a cell is clicked, do some stuff :) When a cell is clicked, do some stuff :)
- this func also runs when double click happens, fyi - this func also runs when double click happens, fyi
""" """
# print(index.row(), index.column())
self.set_selected_song_filepath() self.set_selected_song_filepath()
self.set_selected_song_qmodel_index() self.set_selected_song_qmodel_index()
self.viewport().update() # type: ignore self.viewport().update() # type: ignore
@ -413,9 +408,6 @@ class MusicTable(QTableView):
error('ERROR: response failed') error('ERROR: response failed')
return return
def handle_progress(self, data: object):
"""Emits data to main"""
self.handleProgressSignal.emit(data)
def on_get_audio_files_recursively_finished(self, result: list[str]): def on_get_audio_files_recursively_finished(self, result: list[str]):
"""file search completion handler""" """file search completion handler"""
@ -452,6 +444,10 @@ class MusicTable(QTableView):
# |____________________| # |____________________|
def handle_progress(self, data: object):
"""Emits data to main"""
self.handleProgressSignal.emit(data)
def show_header_context_menu(self, position): def show_header_context_menu(self, position):
""" """
Show context menu on right-click in the horizontal header. Show context menu on right-click in the horizontal header.
@ -465,11 +461,8 @@ class MusicTable(QTableView):
# i do not care and i did not ask # i do not care and i did not ask
# TODO: right click menu for sort by asc/desc, per column # TODO: right click menu for sort by asc/desc, per column
debug(f'show_header_context_menu(position={position})')
logical_index = self.horizontal_header.logicalIndexAt(position) logical_index = self.horizontal_header.logicalIndexAt(position)
debug(f'- logicalIndexAt(position) = {logical_index}')
column_name: str = self.headers.db_list[logical_index] column_name: str = self.headers.db_list[logical_index]
debug(f'- column_name = {column_name}')
menu = QMenu() menu = QMenu()
clear_action = QAction("Clear all sorts", self) clear_action = QAction("Clear all sorts", self)
clear_action.triggered.connect(self.clear_all_sorts) clear_action.triggered.connect(self.clear_all_sorts)
@ -498,16 +491,21 @@ class MusicTable(QTableView):
def mark_column_sort(self, logical_index, column_name, sort_order): def mark_column_sort(self, logical_index, column_name, sort_order):
""" """
Marks a column as sorted in a particular direction
- logical_index: called from `show_header_context_menu()`
- column_name: database column name
- sort_order: 0 = asc, 1 = desc
""" """
debug(f'mark_column_sort(logical_index={logical_index}, column_name={column_name}, sort_order={sort_order})')
# TODO: implement this # TODO: implement this
# write field name and the sort direction into config # write field name and the sort direction into config
# then run sort by logical fields or something to sort the config as is # then run sort by logical fields or something to sort the config as is
raw = self.config["table"].get(option="sort_order", fallback="")
debug(f'- self.config[table][sort_order] = {raw}') debug(f'mark_column_sort(logical_index={logical_index}, column_name={column_name}, sort_order={sort_order})')
sort_list = [] raw = self.sort_config
# newest sort at the beginning bcus we sort in reverse order # Clear the sort order list
sort_list.append((column_name, sort_order)) self.column_sort_order = []
# Ensure newest sort at the beginning bcus we sort in reverse order
self.column_sort_order.append((column_name, sort_order))
# Parse current sort_order from config # Parse current sort_order from config
if raw: if raw:
for item in raw.split(","): for item in raw.split(","):
@ -515,7 +513,7 @@ class MusicTable(QTableView):
continue continue
field, dir_str = item.strip().split(":") field, dir_str = item.strip().split(":")
if field == column_name: if field == column_name:
# skip newest sort # skip newest marked sort
continue continue
direction = int(dir_str) direction = int(dir_str)
if dir_str == "0": if dir_str == "0":
@ -524,10 +522,9 @@ class MusicTable(QTableView):
direction = Qt.SortOrder.DescendingOrder direction = Qt.SortOrder.DescendingOrder
else: else:
direction = None direction = None
sort_list.append((field, direction)) self.column_sort_order.append((field, direction))
debug(f'- sort list updated (field, direction): {sort_list}') debug(f'- sort list updated (field, direction): {self.column_sort_order}')
# Save back to config self.sort_config = ",".join(f'{field}:{order}' for field, order in self.column_sort_order)
self.save_sort_config(sort_list)
# Re-apply the updated sort order # Re-apply the updated sort order
self.sort_by_logical_fields() self.sort_by_logical_fields()
@ -536,13 +533,9 @@ class MusicTable(QTableView):
""" """
Clears all stored sort orders and refreshes the table view. Clears all stored sort orders and refreshes the table view.
""" """
debug('clear_all_sorts()')
self.config["table"]["sort_order"] = ""
with open(self.cfg_file, "w") as f:
self.config.write(f)
# Clear sort visually # Clear sort visually
self.horizontal_header.setSortIndicator(-1, Qt.SortOrder.AscendingOrder) self.horizontal_header.setSortIndicator(-1, Qt.SortOrder.AscendingOrder)
debug('clear_all_sorts() - setSortIndicator at index -1 to AscendingOrder')
def find_qmodel_index_by_value(self, model, column: int, value) -> QModelIndex: def find_qmodel_index_by_value(self, model, column: int, value) -> QModelIndex:
""" """
@ -550,7 +543,7 @@ class MusicTable(QTableView):
Returns index at (0,0) if params are invalid/out of bounds Returns index at (0,0) if params are invalid/out of bounds
Returns empty QModelIndex object if model is invalid Returns empty QModelIndex object if model is invalid
""" """
debug(f'find_qmodel_index_by_value(model={model}, column={column}, value={value})') # debug(f'find_qmodel_index_by_value(model={model}, column={column}, value={value})')
if not model: if not model:
return QModelIndex() return QModelIndex()
@ -574,13 +567,14 @@ class MusicTable(QTableView):
column_ratios = [] column_ratios = []
for i in range(self.model2.columnCount()): for i in range(self.model2.columnCount()):
# for i in range(self.model2.columnCount() - 1): # for i in range(self.model2.columnCount() - 1):
debug(f'column count: {self.model2.columnCount()}') # debug(f'column count: {self.model2.columnCount()}')
column_width = self.columnWidth(i) column_width = self.columnWidth(i)
ratio = column_width / total_table_width ratio = column_width / total_table_width
column_ratios.append(str(round(ratio, 4))) column_ratios.append(str(round(ratio, 4)))
debug(f'get_current_header_width_ratios = {column_ratios}') # debug(f'get_current_header_width_ratios = {column_ratios}')
return column_ratios return column_ratios
def save_header_ratios(self): def save_header_ratios(self):
""" """
Saves the current header widths to memory and file, as ratios Saves the current header widths to memory and file, as ratios
@ -596,7 +590,8 @@ class MusicTable(QTableView):
self.config.write(configfile) self.config.write(configfile)
except Exception as e: except Exception as e:
debug(f"wtf man {e}") debug(f"wtf man {e}")
debug(f"Saved column ratios: {self.saved_column_ratios}") # debug(f"Saved column ratios: {self.saved_column_ratios}")
def load_header_widths(self, ratios: list[str] | None = None): def load_header_widths(self, ratios: list[str] | None = None):
""" """
@ -621,6 +616,7 @@ class MusicTable(QTableView):
def set_qmodel_index(self, index: QModelIndex): def set_qmodel_index(self, index: QModelIndex):
self.current_song_qmodel_index = index self.current_song_qmodel_index = index
def play_selected_audio_file(self): def play_selected_audio_file(self):
""" """
Sets the current song filepath Sets the current song filepath
@ -630,6 +626,7 @@ class MusicTable(QTableView):
self.set_current_song_filepath() self.set_current_song_filepath()
self.playSignal.emit(self.current_song_filepath) self.playSignal.emit(self.current_song_filepath)
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
@ -648,12 +645,14 @@ class MusicTable(QTableView):
else: else:
error("Application window could not be found") error("Application window could not be found")
def add_selected_files_to_playlist(self): def add_selected_files_to_playlist(self):
"""Opens a playlist choice menu and adds the currently selected files to the chosen playlist""" """Opens a playlist choice menu and adds the currently selected files to the chosen playlist"""
playlist_choice_window = AddToPlaylistWindow( playlist_choice_window = AddToPlaylistWindow(
self.get_selected_songs_db_ids()) self.get_selected_songs_db_ids())
playlist_choice_window.exec_() playlist_choice_window.exec_()
def delete_songs(self): def delete_songs(self):
"""Asks to delete the currently selected songs from the db and music table (not the filesystem)""" """Asks to delete the currently selected songs from the db and music table (not the filesystem)"""
# NOTE: provide extra questionbox option? # NOTE: provide extra questionbox option?
@ -952,10 +951,8 @@ class MusicTable(QTableView):
# cache the data # cache the data
# self.data_cache[self.selected_playlist_id] = data # self.data_cache[self.selected_playlist_id] = data
self.populate_model(data) self.populate_model(data)
# self.sort_table_by_multiple_columns() self.sort_by_logical_fields()
self.current_playlist_id = self.selected_playlist_id self.current_playlist_id = self.selected_playlist_id
# self.model2.layoutChanged.emit() # emits a signal that the view should be updated
self.proxymodel.invalidateFilter()
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(f"Songs: {self.model2.rowCount()} | {db_name} | {db_filename}") self.playlistStatsSignal.emit(f"Songs: {self.model2.rowCount()} | {db_name} | {db_filename}")
@ -968,14 +965,9 @@ class MusicTable(QTableView):
When data changes in the model view, its nice to re-grab the current song index information When data changes in the model view, its nice to re-grab the current song index information
might as well get the selected song too i guess? though nothing should be selected when reloading the table data might as well get the selected song too i guess? though nothing should be selected when reloading the table data
""" """
debug('find_current_and_selected_bits')
search_col_num = self.headers.db_list.index("filepath") search_col_num = self.headers.db_list.index("filepath")
debug('find_current_and_selected_bits - searching for column number of `filepath`')
debug(f'find_current_and_selected_bits - search_col_num: {search_col_num}')
selected_qmodel_index = self.find_qmodel_index_by_value(self.proxymodel, search_col_num, self.selected_song_filepath) selected_qmodel_index = self.find_qmodel_index_by_value(self.proxymodel, search_col_num, self.selected_song_filepath)
debug(f'find_current_and_selected_bits - selected_qmodel_index: {selected_qmodel_index}')
current_qmodel_index = self.find_qmodel_index_by_value(self.proxymodel, search_col_num, self.current_song_filepath) current_qmodel_index = self.find_qmodel_index_by_value(self.proxymodel, search_col_num, self.current_song_filepath)
debug(f'find_current_and_selected_bits - current_qmodel_index: {current_qmodel_index}')
# Update the 2 proxy QModelIndexes that we track # Update the 2 proxy QModelIndexes that we track
self.set_selected_song_qmodel_index(selected_qmodel_index) self.set_selected_song_qmodel_index(selected_qmodel_index)
self.set_current_song_qmodel_index(current_qmodel_index) self.set_current_song_qmodel_index(current_qmodel_index)
@ -1001,71 +993,41 @@ class MusicTable(QTableView):
for item in items: for item in items:
item.setData(id, Qt.ItemDataRole.UserRole) item.setData(id, Qt.ItemDataRole.UserRole)
self.model2.appendRow(items) self.model2.appendRow(items)
# self.proxymodel.setSourceModel(self.model2)
# self.setModel(self.proxymodel)
def sort_table_by_multiple_columns(self):
"""
Sorts the data in QTableView (self) by multiple columns
as defined in config.ini
"""
# NOTE: STOP USING THIS
debug('sort_table_by_multiple_columns')
# self.horizontal_header.sortIndicatorChanged.disconnect()
sort_orders = []
config_sort_orders: list[int] = [
int(x) for x in self.config["table"]["sort_orders"].split(",")
]
debug(f'sort_table_by_multiple_columns() - config_sort_orders = {config_sort_orders}')
for order in config_sort_orders:
if order == 0:
sort_orders.append(None)
elif order == 1:
sort_orders.append(Qt.SortOrder.AscendingOrder)
elif order == 2:
sort_orders.append(Qt.SortOrder.DescendingOrder)
debug(f'sort_table_by_multiple_columns() - determined sort_orders = {sort_orders}')
# QTableView sorts need to happen in reverse order
# The primary sort column is the last column sorted.
for i in reversed(range(len(sort_orders))):
if sort_orders[i] is not None:
debug(f"sorting column {i} by {sort_orders[i]}")
self.sortByColumn(i, sort_orders[i])
# WARNING:
# sortByColumn calls a SELECT statement,
# and will do this for as many sorts that are needed
# maybe not a huge deal for a small music application...?
# `len(config_sort_orders)` number of SELECTs
# self.on_sort()
# self.horizontal_header.sortIndicatorChanged.connect(self.on_user_sort_change)
# self.model2.layoutChanged.emit()
self.proxymodel.invalidateFilter()
def save_sort_config(self, fields: list[tuple[str, Qt.SortOrder]]): def get_sort_config_by_name(self) -> list[tuple[str, Qt.SortOrder]]:
""" debug('get_sort_config_by_name()')
Save sort config to ini file. sort_config = []
fields: List of tuples like [('artist', Qt.AscendingOrder), ('album', Qt.DescendingOrder)] raw_sort = self.sort_config
""" if not raw_sort:
debug('save_sort_config()') raw_sort = self.config["table"].get("sort_order", "")
# raw = ",".join(f"{field}:{1 if order == Qt.SortOrder.AscendingOrder else 2}" for field, order in fields) if not raw_sort:
raw = ",".join(f"{field}:{order}" for field, order in fields) return []
debug(f'- saving config [table][sort_order] = {raw}')
self.config["table"]["sort_order"] = raw
debug(f'- self.cfg_file = {self.cfg_file}')
with open(self.cfg_file, "w") as f:
debug('- writing to config file')
self.config.write(f)
debug(f'- self.config["table"]["sort_order"] = {self.config["table"]["sort_order"]}')
def get_sort_config(self) -> list[tuple[int, Qt.SortOrder]]: try:
sort_fields = [x.strip() for x in raw_sort.split(",")]
for entry in sort_fields:
if ":" not in entry:
continue
db_field, order_str = entry.split(":")
db_field = db_field.strip()
order = int(order_str.strip())
sort_config.append((db_field, order))
except Exception as e:
error(f"- Failed to parse sort config: {e}")
return sort_config
def get_sort_config_by_index(self) -> list[tuple[int, Qt.SortOrder]]:
""" """
Returns a list of (column_index, Qt.SortOrder) tuples based on config and headers Returns a list of (column_index, Qt.SortOrder) tuples based on config and headers
""" """
debug('get_sort_config()') debug('get_sort_config_by_index()')
sort_config = [] sort_config = []
raw_sort = self.config["table"].get("sort_order", "") raw_sort = self.sort_config
if not raw_sort: if not raw_sort:
return [] raw_sort = self.config["table"].get("sort_order", "")
if not raw_sort:
return []
try: try:
sort_fields = [x.strip() for x in raw_sort.split(",")] sort_fields = [x.strip() for x in raw_sort.split(",")]
@ -1090,8 +1052,7 @@ class MusicTable(QTableView):
Sorts the table using logical field names defined in config. Sorts the table using logical field names defined in config.
""" """
debug('sort_by_logical_fields()') debug('sort_by_logical_fields()')
sort_config = self.get_sort_config() sort_config = self.get_sort_config_by_index()
debug(f'- retrieved sort_config: {sort_config}')
# Sort in reverse order (primary sort last) # Sort in reverse order (primary sort last)
for col_index, order in reversed(sort_config): for col_index, order in reversed(sort_config):
if order == 0: if order == 0:
@ -1102,11 +1063,8 @@ class MusicTable(QTableView):
continue continue
self.sortByColumn(col_index, new_order) self.sortByColumn(col_index, new_order)
debug(f'- sorted column index {col_index} by {new_order}') debug(f'- sorted column index {col_index} by {new_order}')
# self.model2.layoutChanged.emit()
# debug('- emitted layoutChanged signal')
self.proxymodel.invalidateFilter() self.proxymodel.invalidateFilter()
debug('- proxymodel invalidateFilter()')
# self.on_sort()
def get_audio_files_recursively(self, directories: list[str], progress_callback=None) -> list[str]: def get_audio_files_recursively(self, directories: list[str], progress_callback=None) -> list[str]:
"""Scans a directories for files""" """Scans a directories for files"""
@ -1200,14 +1158,10 @@ class MusicTable(QTableView):
converts to model2 index converts to model2 index
stores it stores it
""" """
debug('set_current_song_qmodel_index')
debug(f'got index: {index}')
if index is None: if index is None:
index = self.currentIndex() index = self.currentIndex()
debug(f'new index: {index}')
# 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)
debug(f'map index: {real_index}')
self.current_song_qmodel_index = real_index self.current_song_qmodel_index = real_index
def set_selected_song_qmodel_index(self, index: QModelIndex | None = None): def set_selected_song_qmodel_index(self, index: QModelIndex | None = None):
@ -1216,14 +1170,10 @@ class MusicTable(QTableView):
converts to model2 index converts to model2 index
stores it stores it
""" """
debug('set_selected_song_qmodel_index')
debug(f'got index: {index}')
if index is None: if index is None:
index = self.currentIndex() index = self.currentIndex()
debug(f'new index: {index}')
# 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)
debug(f'map index: {real_index}')
self.selected_song_qmodel_index = real_index self.selected_song_qmodel_index = real_index
def set_search_string(self, text: str): def set_search_string(self, text: str):

View File

@ -169,7 +169,7 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
# QUICK ACTIONS MENU # QUICK ACTIONS MENU
self.actionScanLibraries.triggered.connect(self.scan_libraries) self.actionScanLibraries.triggered.connect(self.scan_libraries)
self.actionDeleteDatabase.triggered.connect(self.delete_database) self.actionDeleteDatabase.triggered.connect(self.delete_database)
self.actionSortColumns.triggered.connect(self.tableView.sort_table_by_multiple_columns) self.actionSortColumns.triggered.connect(self.nothing)
# QTableView # QTableView
# for drag & drop functionality # for drag & drop functionality
@ -197,6 +197,10 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
self.albumGraphicsView.albumArtDropped.connect(self.set_album_art_for_selected_songs) self.albumGraphicsView.albumArtDropped.connect(self.set_album_art_for_selected_songs)
self.albumGraphicsView.albumArtDeleted.connect(self.delete_album_art_for_current_song) self.albumGraphicsView.albumArtDeleted.connect(self.delete_album_art_for_current_song)
def nothing(self):
pass
# _________________ # _________________
# | | # | |
# | | # | |
@ -214,6 +218,7 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
self.config["settings"]["volume"] = str(self.current_volume) self.config["settings"]["volume"] = str(self.current_volume)
self.config["settings"]["window_size"] = (str(self.width()) + "," + str(self.height())) self.config["settings"]["window_size"] = (str(self.width()) + "," + str(self.height()))
self.config['table']['column_ratios'] = ",".join(self.tableView.get_current_header_width_ratios()) self.config['table']['column_ratios'] = ",".join(self.tableView.get_current_header_width_ratios())
self.config["table"]["sort_order"] = self.tableView.sort_config
# Save the config # Save the config
try: try:
with open(self.cfg_file, "w") as configfile: with open(self.cfg_file, "w") as configfile:

View File

@ -13,5 +13,5 @@ window_size=1152,894
# Music table user options # Music table user options
columns = title,artist,album,track_number,genre,album_date,codec,length_seconds,filepath columns = title,artist,album,track_number,genre,album_date,codec,length_seconds,filepath
column_ratios = 0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01 column_ratios = 0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01
# 0 = no sort, 1 = ascending, 2 = descending # 0 = ascending, 1 = descending
sort_orders = 0,1,1,1,0,0,0,0,0 sort_order = artist:0,track_number:0,album:0

View File

@ -5,20 +5,20 @@ from mutagen.id3 import ID3
from mutagen.id3._util import ID3NoHeaderError from mutagen.id3._util import ID3NoHeaderError
from mutagen.id3._frames import USLT, Frame from mutagen.id3._frames import USLT, Frame
def set_tag(filepath: str, db_column: str, value: str): def set_tag(filepath: str, tag_name: str, value: str):
""" """
Sets the ID3 tag for a file given a filepath, db_column, and a value for the tag Sets the ID3 tag for a file given a filepath, tag_name, and a value for the tag
Args: Args:
filepath: path to the mp3 file filepath: path to the mp3 file
db_column: db column name of the ID3 tag tag_name: db column name of the ID3 tag
value: value to set for the tag value: value to set for the tag
Returns: Returns:
True / False True / False
""" """
headers = HeaderTags2() headers = HeaderTags2()
debug(f"filepath: {filepath} | db_column: {db_column} | value: {value}") debug(f"filepath: {filepath} | tag_name: {tag_name} | value: {value}")
try: try:
try: # Load existing tags try: # Load existing tags
@ -26,7 +26,7 @@ def set_tag(filepath: str, db_column: str, value: str):
except ID3NoHeaderError: # Create new tags if none exist except ID3NoHeaderError: # Create new tags if none exist
audio_file = ID3() audio_file = ID3()
# Lyrics get handled differently # Lyrics get handled differently
if db_column == "lyrics": if tag_name == "lyrics":
try: try:
audio = ID3(filepath) audio = ID3(filepath)
except Exception as e: except Exception as e:
@ -38,14 +38,14 @@ def set_tag(filepath: str, db_column: str, value: str):
audio.save() audio.save()
return True return True
# DB Tag into Mutagen Frame Class # DB Tag into Mutagen Frame Class
if db_column in headers.db: if tag_name in headers.db:
frame_class = headers.db[db_column].frame_class frame_class = headers.db[tag_name].frame_class
assert frame_class is not None # ooo scary assert frame_class is not None # ooo scary
if issubclass(frame_class, Frame): if issubclass(frame_class, Frame):
frame = frame_class(encoding=3, text=[value]) frame = frame_class(encoding=3, text=[value])
audio_file.add(frame) audio_file.add(frame)
else: else:
warning(f'Tag "{db_column}" not found - ID3 tag update skipped') warning(f'Tag "{tag_name}" not found - ID3 tag update skipped')
audio_file.save(filepath) audio_file.save(filepath)
return True return True
except Exception as e: except Exception as e: