sorting works now. mostly... also lyrics window small fix
This commit is contained in:
parent
c4e5105f91
commit
8420b31057
@ -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
|
||||||
|
|||||||
@ -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,68 +993,38 @@ 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.sort_config
|
||||||
|
if not raw_sort:
|
||||||
raw_sort = self.config["table"].get("sort_order", "")
|
raw_sort = self.config["table"].get("sort_order", "")
|
||||||
if not raw_sort:
|
if not raw_sort:
|
||||||
return []
|
return []
|
||||||
@ -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):
|
||||||
|
|||||||
7
main.py
7
main.py
@ -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:
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user