MetadataWindow update to DB and id3 tags complete
This commit is contained in:
parent
3f8d632de8
commit
63c54f9de7
@ -8,13 +8,19 @@ from PyQt5.QtWidgets import (
|
|||||||
QPushButton,
|
QPushButton,
|
||||||
)
|
)
|
||||||
from PyQt5.QtGui import QFont
|
from PyQt5.QtGui import QFont
|
||||||
|
from PyQt5.QtCore import pyqtSignal
|
||||||
from mutagen.id3 import ID3
|
from mutagen.id3 import ID3
|
||||||
|
from components.ErrorDialog import ErrorDialog
|
||||||
from utils.get_id3_tags import get_id3_tags
|
from utils.get_id3_tags import get_id3_tags
|
||||||
from utils.set_id3_tag import set_id3_tag
|
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):
|
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
|
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
|
- list of strings, absolute paths to mp3 files
|
||||||
"""
|
"""
|
||||||
super(MetadataWindow, self).__init__()
|
super(MetadataWindow, self).__init__()
|
||||||
self.songs = songs
|
self.refreshMusicTableSignal = refreshMusicTableSignal
|
||||||
self.id3_tag_mapping = {
|
self.songs = list(zip(songs, ids))
|
||||||
"TIT2": "title",
|
self.id3_tag_mapping = id3_tag_mapping
|
||||||
"TPE1": "artist",
|
|
||||||
"TALB": "album",
|
|
||||||
"TPE2": "album_artist",
|
|
||||||
"TCON": "genre",
|
|
||||||
"TRCK": "track_number",
|
|
||||||
"APIC": "album_cover",
|
|
||||||
"TCOP": "copyright",
|
|
||||||
}
|
|
||||||
# Keep a dictionary of the input fields for save function
|
# Keep a dictionary of the input fields for save function
|
||||||
self.input_fields = {}
|
self.input_fields = {}
|
||||||
self.setWindowTitle("Edit metadata")
|
self.setWindowTitle("Edit metadata")
|
||||||
@ -52,7 +50,7 @@ class MetadataWindow(QDialog):
|
|||||||
# Get a dict of all tags for all songs
|
# Get a dict of all tags for all songs
|
||||||
# e.g., { "TIT2": ["song_title1", "song_title2"], ... }
|
# e.g., { "TIT2": ["song_title1", "song_title2"], ... }
|
||||||
for song in self.songs:
|
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:
|
for tag in self.id3_tag_mapping:
|
||||||
try:
|
try:
|
||||||
_ = tag_sets[tag]
|
_ = tag_sets[tag]
|
||||||
@ -107,5 +105,22 @@ class MetadataWindow(QDialog):
|
|||||||
"""Save changes made to metadata for each song in dict"""
|
"""Save changes made to metadata for each song in dict"""
|
||||||
for song in self.songs:
|
for song in self.songs:
|
||||||
for tag, field in self.input_fields.items():
|
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()
|
self.close()
|
||||||
|
|||||||
@ -23,7 +23,7 @@ from components.AddToPlaylistWindow import AddToPlaylistWindow
|
|||||||
from components.MetadataWindow import MetadataWindow
|
from components.MetadataWindow import MetadataWindow
|
||||||
from utils.delete_song_id_from_database import delete_song_id_from_database
|
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.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_id3_tags import get_id3_tags
|
||||||
from utils.get_album_art import get_album_art
|
from utils.get_album_art import get_album_art
|
||||||
from utils import set_id3_tag
|
from utils import set_id3_tag
|
||||||
@ -38,12 +38,15 @@ class MusicTable(QTableView):
|
|||||||
playPauseSignal = pyqtSignal()
|
playPauseSignal = pyqtSignal()
|
||||||
enterKey = pyqtSignal()
|
enterKey = pyqtSignal()
|
||||||
deleteKey = pyqtSignal()
|
deleteKey = pyqtSignal()
|
||||||
|
refreshMusicTable = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self: QTableView, parent=None):
|
def __init__(self: QTableView, parent=None):
|
||||||
# QTableView.__init__(self, parent)
|
# QTableView.__init__(self, parent)
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
# Necessary for actions related to cell values
|
# Necessary for actions related to cell values
|
||||||
|
# FIXME: why do these give me pyright errors
|
||||||
self.model = QStandardItemModel(self)
|
self.model = QStandardItemModel(self)
|
||||||
|
# self.model = QAbstractItemModel(self)
|
||||||
self.setModel(self.model)
|
self.setModel(self.model)
|
||||||
|
|
||||||
# Config
|
# Config
|
||||||
@ -165,7 +168,9 @@ class MusicTable(QTableView):
|
|||||||
# FIXME:
|
# FIXME:
|
||||||
"""Opens a form with metadata from the selected audio files"""
|
"""Opens a form with metadata from the selected audio files"""
|
||||||
files = self.get_selected_songs_filepaths()
|
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
|
window.exec_() # Display the preferences window modally
|
||||||
|
|
||||||
def add_selected_files_to_playlist(self):
|
def add_selected_files_to_playlist(self):
|
||||||
@ -231,9 +236,11 @@ class MusicTable(QTableView):
|
|||||||
else:
|
else:
|
||||||
e.ignore()
|
e.ignore()
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
def keyPressEvent(self, e):
|
||||||
"""Press a key. Do a thing"""
|
"""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
|
if key == Qt.Key_Space: # Spacebar to play/pause
|
||||||
self.toggle_play_pause()
|
self.toggle_play_pause()
|
||||||
elif key == Qt.Key_Up: # Arrow key navigation
|
elif key == Qt.Key_Up: # Arrow key navigation
|
||||||
@ -254,9 +261,9 @@ class MusicTable(QTableView):
|
|||||||
if self.state() != QAbstractItemView.EditingState:
|
if self.state() != QAbstractItemView.EditingState:
|
||||||
self.enterKey.emit() # Enter key detected
|
self.enterKey.emit() # Enter key detected
|
||||||
else:
|
else:
|
||||||
super().keyPressEvent(event)
|
super().keyPressEvent(e)
|
||||||
else: # Default behavior
|
else: # Default behavior
|
||||||
super().keyPressEvent(event)
|
super().keyPressEvent(e)
|
||||||
|
|
||||||
def setup_keyboard_shortcuts(self):
|
def setup_keyboard_shortcuts(self):
|
||||||
"""Setup shortcuts here"""
|
"""Setup shortcuts here"""
|
||||||
@ -267,7 +274,7 @@ class MusicTable(QTableView):
|
|||||||
"""Handles updating ID3 tags when data changes in a cell"""
|
"""Handles updating ID3 tags when data changes in a cell"""
|
||||||
print("on_cell_data_changed")
|
print("on_cell_data_changed")
|
||||||
id_index = self.model.index(topLeft.row(), 0) # ID is column 0, always
|
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 is always the last column
|
||||||
filepath_column_idx = self.model.columnCount() - 1
|
filepath_column_idx = self.model.columnCount() - 1
|
||||||
filepath_index = self.model.index(topLeft.row(), filepath_column_idx)
|
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)
|
response = set_id3_tag(filepath, edited_column_name, user_input_data)
|
||||||
if response:
|
if response:
|
||||||
# Update the library with new metadata
|
# 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):
|
def reorganize_selected_files(self):
|
||||||
"""Ctrl+Shift+R = Reorganize"""
|
"""Ctrl+Shift+R = Reorganize"""
|
||||||
@ -411,6 +418,7 @@ class MusicTable(QTableView):
|
|||||||
self.model.layoutChanged.emit() # emits a signal that the view should be updated
|
self.model.layoutChanged.emit() # emits a signal that the view should be updated
|
||||||
try:
|
try:
|
||||||
self.model.dataChanged.connect(self.on_cell_data_changed)
|
self.model.dataChanged.connect(self.on_cell_data_changed)
|
||||||
|
self.restore_scroll_position()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
2
main.py
2
main.py
@ -440,7 +440,7 @@ if __name__ == "__main__":
|
|||||||
sys.path.append(project_root)
|
sys.path.append(project_root)
|
||||||
# Start the app
|
# Start the app
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
print(f"main.py app: {app}")
|
# print(f"main.py app: {app}")
|
||||||
# Dark theme >:3
|
# Dark theme >:3
|
||||||
qdarktheme.setup_theme()
|
qdarktheme.setup_theme()
|
||||||
# Show the UI
|
# Show the UI
|
||||||
|
|||||||
@ -7,7 +7,7 @@ from .get_id3_tags import get_id3_tags
|
|||||||
from .set_id3_tag import set_id3_tag
|
from .set_id3_tag import set_id3_tag
|
||||||
from .delete_song_id_from_database import delete_song_id_from_database
|
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 .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 .scan_for_music import scan_for_music
|
||||||
from .add_files_to_library import add_files_to_library
|
from .add_files_to_library import add_files_to_library
|
||||||
from .handle_year_and_date_id3_tag import handle_year_and_date_id3_tag
|
from .handle_year_and_date_id3_tag import handle_year_and_date_id3_tag
|
||||||
|
|||||||
10
utils/id3_tag_mapping.py
Normal file
10
utils/id3_tag_mapping.py
Normal file
@ -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",
|
||||||
|
}
|
||||||
@ -40,7 +40,7 @@ from mutagen.id3._frames import (
|
|||||||
WPUB,
|
WPUB,
|
||||||
)
|
)
|
||||||
|
|
||||||
id3_tag_mapping = {
|
mutagen_id3_tag_mapping = {
|
||||||
"title": TIT2, # Title/song name/content description
|
"title": TIT2, # Title/song name/content description
|
||||||
"artist": TPE1, # Lead performer(s)/Soloist(s)
|
"artist": TPE1, # Lead performer(s)/Soloist(s)
|
||||||
"album": TALB, # Album/Movie/Show title
|
"album": TALB, # Album/Movie/Show title
|
||||||
@ -119,8 +119,8 @@ def set_id3_tag(filepath: str, tag_name: str, value: str):
|
|||||||
audio.save()
|
audio.save()
|
||||||
return True
|
return True
|
||||||
# Other
|
# Other
|
||||||
elif tag_name in id3_tag_mapping: # Tag accounted for
|
elif tag_name in mutagen_id3_tag_mapping: # Tag accounted for
|
||||||
tag_class = id3_tag_mapping[tag_name]
|
tag_class = mutagen_id3_tag_mapping[tag_name]
|
||||||
if issubclass(tag_class, Frame):
|
if issubclass(tag_class, Frame):
|
||||||
frame = tag_class(encoding=3, text=[value])
|
frame = tag_class(encoding=3, text=[value])
|
||||||
audio_file.add(frame) # Add the tag
|
audio_file.add(frame) # Add the tag
|
||||||
|
|||||||
@ -2,13 +2,13 @@ import DBA
|
|||||||
from components.ErrorDialog import ErrorDialog
|
from components.ErrorDialog import ErrorDialog
|
||||||
|
|
||||||
|
|
||||||
def update_song_in_library(
|
def update_song_in_database(
|
||||||
library_id: int, edited_column_name: str, user_input_data: str
|
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:
|
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
|
edited_column_name: the name of the database column
|
||||||
user_input_data: the data to input
|
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
|
# 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(
|
db.execute(
|
||||||
f"UPDATE song SET {edited_column_name} = ? WHERE id = ?",
|
f"UPDATE song SET {edited_column_name} = ? WHERE id = ?",
|
||||||
(user_input_data, library_id),
|
(user_input_data, song_id),
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
dialog = ErrorDialog(
|
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_()
|
dialog.exec_()
|
||||||
return False
|
return False
|
||||||
Loading…
x
Reference in New Issue
Block a user