multithreading working with add files to library

This commit is contained in:
tsi-billypom 2024-09-06 11:41:29 -04:00
parent 3bd2c539cf
commit d351f3515e
7 changed files with 122 additions and 154 deletions

1
.gitignore vendored
View File

@ -2,6 +2,7 @@
/references /references
/db /db
config.ini config.ini
log
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/

View File

@ -31,6 +31,7 @@ from components.AddToPlaylistWindow import AddToPlaylistWindow
from components.MetadataWindow import MetadataWindow from components.MetadataWindow import MetadataWindow
# from main import WorkerThread # from main import WorkerThread
from main import Worker
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.get_reorganize_vars import get_reorganize_vars from utils.get_reorganize_vars import get_reorganize_vars
@ -45,29 +46,6 @@ import os
import shutil import shutil
class DropAddFilesThread(QThread):
signalStarted = pyqtSignal()
signalProgress = pyqtSignal(str)
signalFinished = pyqtSignal()
def __init__(self, files, parent=None):
QThread.__init__(self, parent)
self.files = files
def run(self) -> None:
self.add_files()
return
def add_files(self) -> None:
"""When song(s) added to the library, update the tableview model
- Drag & Drop song(s) on tableView
- File > Open > List of song(s)
"""
add_files_to_library(self.files)
self.signalFinished.emit()
return
class MusicTable(QTableView): class MusicTable(QTableView):
playPauseSignal = pyqtSignal() playPauseSignal = pyqtSignal()
enterKey = pyqtSignal() enterKey = pyqtSignal()
@ -191,7 +169,7 @@ class MusicTable(QTableView):
try: try:
model.removeRow(index) model.removeRow(index)
except Exception as e: except Exception as e:
logging.info(f"MusicTable.py delete_songs() failed | {e}") logging.info(f" delete_songs() failed | {e}")
self.load_music_table() self.load_music_table()
self.model.dataChanged.connect(self.on_cell_data_changed) self.model.dataChanged.connect(self.on_cell_data_changed)
@ -237,7 +215,7 @@ class MusicTable(QTableView):
else: else:
raise RuntimeError("No USLT tags found in song metadata") raise RuntimeError("No USLT tags found in song metadata")
except Exception as e: except Exception as e:
print(f"MusicTable.py | show_lyrics_menu | could not retrieve lyrics | {e}") logging.error(f"show_lyrics_menu() | could not retrieve lyrics | {e}")
lyrics = "" lyrics = ""
lyrics_window = LyricsWindow(selected_song_filepath, lyrics) lyrics_window = LyricsWindow(selected_song_filepath, lyrics)
lyrics_window.exec_() lyrics_window.exec_()
@ -261,6 +239,7 @@ class MusicTable(QTableView):
e.ignore() e.ignore()
def dropEvent(self, e: QDropEvent | None): def dropEvent(self, e: QDropEvent | None):
self.model.dataChanged.disconnect(self.on_cell_data_changed)
if e is None: if e is None:
return return
data = e.mimeData() data = e.mimeData()
@ -270,14 +249,13 @@ class MusicTable(QTableView):
if url.isLocalFile(): if url.isLocalFile():
files.append(url.path()) files.append(url.path())
e.accept() e.accept()
self.worker = DropAddFilesThread(files=files) worker = Worker(add_files_to_library, files)
# self.model.dataChanged.disconnect(self.on_cell_data_changed) worker.signals.signal_progress.connect(self.handle_progress)
# self.model.dataChanged.connect(self.on_cell_data_changed) worker.signals.signal_finished.connect(self.load_music_table)
self.worker.signalFinished.connect(self.load_music_table) self.threadpool.start(worker)
self.worker.start()
# self.add_files(files)
else: else:
e.ignore() e.ignore()
self.model.dataChanged.connect(self.on_cell_data_changed)
def keyPressEvent(self, e): def keyPressEvent(self, e):
"""Press a key. Do a thing""" """Press a key. Do a thing"""
@ -315,7 +293,7 @@ class MusicTable(QTableView):
def on_cell_data_changed(self, topLeft: QModelIndex, bottomRight: QModelIndex): def on_cell_data_changed(self, topLeft: QModelIndex, bottomRight: QModelIndex):
"""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") logging.info("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
song_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
@ -326,7 +304,7 @@ class MusicTable(QTableView):
# update the ID3 information # update the ID3 information
user_input_data = topLeft.data() user_input_data = topLeft.data()
edited_column_name = self.database_columns[topLeft.column()] edited_column_name = self.database_columns[topLeft.column()]
print(f"edited column name: {edited_column_name}") logging.info(f"edited column name: {edited_column_name}")
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
@ -366,13 +344,12 @@ class MusicTable(QTableView):
"UPDATE song SET filepath = ? WHERE filepath = ?", "UPDATE song SET filepath = ? WHERE filepath = ?",
(new_path, filepath), (new_path, filepath),
) )
print(f"Moved: {filepath} -> {new_path}") logging.info(
f"reorganize_selected_files() | Moved: {filepath} -> {new_path}"
)
except Exception as e: except Exception as e:
logging.warning( logging.warning(
f"MusicTable.py reorganize_selected_files() | Error moving file: {filepath} | {e}" f"reorganize_selected_files() | Error moving file: {filepath} | {e}"
)
print(
f"MusicTable.py reorganize_selected_files() | Error moving file: {filepath} | {e}"
) )
# Draw the rest of the owl # Draw the rest of the owl
# self.model.dataChanged.disconnect(self.on_cell_data_changed) # self.model.dataChanged.disconnect(self.on_cell_data_changed)
@ -388,6 +365,14 @@ class MusicTable(QTableView):
self.set_current_song_filepath() self.set_current_song_filepath()
self.playPauseSignal.emit() self.playPauseSignal.emit()
def add_files(self, files, progress_callback) -> None:
"""When song(s) added to the library, update the tableview model
- Drag & Drop song(s) on tableView
- File > Open > List of song(s)
"""
add_files_to_library(files, progress_callback)
return
def load_music_table(self, *playlist_id): def load_music_table(self, *playlist_id):
""" """
Loads data into self (QTableView) Loads data into self (QTableView)
@ -401,8 +386,8 @@ class MusicTable(QTableView):
# then re-enable after we are done loading # then re-enable after we are done loading
self.model.dataChanged.disconnect(self.on_cell_data_changed) self.model.dataChanged.disconnect(self.on_cell_data_changed)
except Exception as e: except Exception as e:
print( logging.info(
f"MusicTable.py load_music_table() | could not disconnect on_cell_data_changed trigger: {e}" f"load_music_table() | could not disconnect on_cell_data_changed trigger: {e}"
) )
pass pass
self.vertical_scroll_position = ( self.vertical_scroll_position = (
@ -413,7 +398,9 @@ class MusicTable(QTableView):
self.model.setHorizontalHeaderLabels(self.table_headers) self.model.setHorizontalHeaderLabels(self.table_headers)
if playlist_id: if playlist_id:
selected_playlist_id = playlist_id[0] selected_playlist_id = playlist_id[0]
print(f"selected_playlist_id: {selected_playlist_id}") logging.info(
f"load_music_table() | selected_playlist_id: {selected_playlist_id}"
)
# Fetch playlist data # Fetch playlist data
try: try:
with DBA.DBAccess() as db: with DBA.DBAccess() as db:
@ -422,10 +409,7 @@ class MusicTable(QTableView):
(selected_playlist_id,), (selected_playlist_id,),
) )
except Exception as e: except Exception as e:
logging.warning( logging.warning(f"load_music_table() | Unhandled exception: {e}")
f"MusicTable.py | load_music_table | Unhandled exception: {e}"
)
print(f"MusicTable.py | load_music_table | Unhandled exception: {e}")
return return
else: else:
# Fetch library data # Fetch library data
@ -436,9 +420,7 @@ class MusicTable(QTableView):
(), (),
) )
except Exception as e: except Exception as e:
logging.warning( logging.warning(f"load_music_table() | Unhandled exception: {e}")
f"MusicTable.py | load_music_table | Unhandled exception: {e}"
)
return return
# Populate the model # Populate the model
for row_data in data: for row_data in data:
@ -458,7 +440,7 @@ class MusicTable(QTableView):
def restore_scroll_position(self) -> None: def restore_scroll_position(self) -> None:
"""Restores the scroll position""" """Restores the scroll position"""
print("restore_scroll_position") logging.info("restore_scroll_position")
QTimer.singleShot( QTimer.singleShot(
100, 100,
lambda: self.verticalScrollBar().setValue(self.vertical_scroll_position), lambda: self.verticalScrollBar().setValue(self.vertical_scroll_position),
@ -520,7 +502,7 @@ class MusicTable(QTableView):
self.selected_song_filepath = ( self.selected_song_filepath = (
self.currentIndex().siblingAtColumn(self.table_headers.index("path")).data() self.currentIndex().siblingAtColumn(self.table_headers.index("path")).data()
) )
print(self.selected_song_filepath) logging.info(self.selected_song_filepath)
def set_current_song_filepath(self) -> None: def set_current_song_filepath(self) -> None:
"""Sets the filepath of the currently playing song""" """Sets the filepath of the currently playing song"""

140
main.py
View File

@ -9,6 +9,7 @@ from pyqtgraph import mkBrush
from mutagen.id3 import ID3 from mutagen.id3 import ID3
from mutagen.id3._frames import APIC from mutagen.id3._frames import APIC
from configparser import ConfigParser from configparser import ConfigParser
import traceback
import DBA import DBA
from ui import Ui_MainWindow from ui import Ui_MainWindow
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
@ -28,6 +29,7 @@ from PyQt5.QtCore import (
Qt, Qt,
pyqtSignal, pyqtSignal,
QObject, QObject,
QThread,
pyqtSlot, pyqtSlot,
QThreadPool, QThreadPool,
QRunnable, QRunnable,
@ -35,7 +37,12 @@ from PyQt5.QtCore import (
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QAudioProbe from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QAudioProbe
from PyQt5.QtGui import QCloseEvent, QPixmap from PyQt5.QtGui import QCloseEvent, QPixmap
from uuid import uuid4, UUID from uuid import uuid4, UUID
from utils import scan_for_music, delete_and_create_library_database, initialize_db from utils import (
scan_for_music,
delete_and_create_library_database,
initialize_db,
add_files_to_library,
)
from components import ( from components import (
PreferencesWindow, PreferencesWindow,
AudioVisualizer, AudioVisualizer,
@ -49,6 +56,11 @@ from components import (
# good help with signals slots in threads # good help with signals slots in threads
# https://stackoverflow.com/questions/52993677/how-do-i-setup-signals-and-slots-in-pyqt-with-qthreads-in-both-directions # https://stackoverflow.com/questions/52993677/how-do-i-setup-signals-and-slots-in-pyqt-with-qthreads-in-both-directions
# GOOD
# https://www.pythonguis.com/tutorials/multithreading-pyqt-applications-qthreadpool/
class WorkerSignals(QObject): class WorkerSignals(QObject):
""" """
How to use signals for a QRunnable class; unlike most cases where signals How to use signals for a QRunnable class; unlike most cases where signals
@ -67,31 +79,52 @@ class WorkerSignals(QObject):
# pandas DataFrames or numpy arrays. Instead, pass the minimum amount of information needed # pandas DataFrames or numpy arrays. Instead, pass the minimum amount of information needed
# (i.e. lists of filepaths) # (i.e. lists of filepaths)
signal_started = pyqtSignal(list) signal_started = pyqtSignal()
signal_finished = pyqtSignal(UUID) signal_finished = pyqtSignal()
signal_progress = pyqtSignal(str) signal_progress = pyqtSignal(str)
class WorkerThread(QRunnable): class Worker(QRunnable):
""" """
This is the thread that is going to do the work so that the This is the thread that is going to do the work so that the
application doesn't freeze application doesn't freeze
Inherits from QRunnable to handle worker thread setup, signals, and tear down
:param callback: the function callback to run on this worker thread. Supplied
arg and kwargs will be passed through to the runner
:type callback: function
:param args: Arguments to pass to the callback function
:param kwargs: Keywords to pass to the callback function
""" """
def __init__(self, worker_id: UUID) -> None: def __init__(self, fn, *args, **kwargs):
super().__init__() super(Worker, self).__init__()
# allow for signals to be used # Store constructor arguments (re-used for processing)
self.worker_id = worker_id self.fn = fn
self.args = args
self.kwargs = kwargs
self.signals: WorkerSignals = WorkerSignals() self.signals: WorkerSignals = WorkerSignals()
# Add a callback to our kwargs
self.kwargs["progress_callback"] = self.signals.signal_progress
@pyqtSlot()
def run(self): def run(self):
""" """
This is where the work is done. This is where the work is done.
MUST be called run() in order for QRunnable to work MUST be called run() in order for QRunnable to work
Initialize the runner function with passed args & kwargs
""" """
self.signals.signal_started.emit("start worker") self.signals.signal_started.emit()
self.signals.signal_progress.emit("worker progress") try:
self.signals.signal_finished.emit(self.worker_id) self.fn(*self.args, **self.kwargs)
except Exception as e:
traceback.print_exc()
exctype, value = sys.exc_info()[:2]
self.signals.signal_finished.emit((exctype, value, traceback.format_exc()))
finally:
self.signals.signal_finished.emit()
class ApplicationWindow(QMainWindow, Ui_MainWindow): class ApplicationWindow(QMainWindow, Ui_MainWindow):
@ -122,7 +155,6 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
self.audio_visualizer: AudioVisualizer = AudioVisualizer(self.player) self.audio_visualizer: AudioVisualizer = AudioVisualizer(self.player)
self.current_volume: int = 50 self.current_volume: int = 50
self.qapp = qapp self.qapp = qapp
# print(f'ApplicationWindow self.qapp: {self.qapp}')
self.tableView.load_qapp(self.qapp) self.tableView.load_qapp(self.qapp)
self.albumGraphicsView.load_qapp(self.qapp) self.albumGraphicsView.load_qapp(self.qapp)
self.config.read("config.ini") self.config.read("config.ini")
@ -154,7 +186,6 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
self.playbackSlider.sliderReleased.connect( self.playbackSlider.sliderReleased.connect(
lambda: self.player.setPosition(self.playbackSlider.value()) lambda: self.player.setPosition(self.playbackSlider.value())
) # maybe sliderReleased works better than sliderMoved ) # maybe sliderReleased works better than sliderMoved
self
self.volumeSlider.sliderMoved[int].connect( self.volumeSlider.sliderMoved[int].connect(
lambda: self.volume_changed() lambda: self.volume_changed()
) # Move slider to adjust volume ) # Move slider to adjust volume
@ -213,45 +244,6 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
self.tableView.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.tableView.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.tableView.horizontalHeader().setStretchLastSection(False) self.tableView.horizontalHeader().setStretchLastSection(False)
def start_worker(self):
worker_id = uuid4() # generate an unique id for the worker
worker = WorkerThread(worker_id=worker_id)
# Connect the signals to the slots
worker.signals.signal_started.connect(self.handle_started)
worker.signals.signal_progress.connect(self.handle_progress)
worker.signals.signal_finished.connect(self.handle_finished)
# Store the worker in a dict so we can keep track of it
self.workers[worker_id] = worker
# Start the worker
self.threadpool.start(worker)
# Slots should have decorators whose arguments match the signals they are connected to
@pyqtSlot(list)
def handle_files_from_child_window(self, files: list[str]):
self.files_to_process = files
print(f"Files to process: {self.files_to_process}")
self.show_status_bar_message(f"Files to process: {self.files_to_process}")
@pyqtSlot(int)
def handle_progress(self, n):
print(f"progress: {n}%")
self.show_status_bar_message(f"progress: {n}%")
@pyqtSlot(UUID)
def handle_finished(self, worker_id):
print(f"worker {worker_id} finished")
self.show_status_bar_message(f"worker {worker_id} finished")
# remove the worker from the dict
self.workers.pop(worker_id)
assert worker_id not in self.workers
@pyqtSlot(list)
def handle_started(self, files: list[str]):
print(f"Started processing files: {files}")
def closeEvent(self, a0: QCloseEvent | None) -> None: def closeEvent(self, a0: QCloseEvent | None) -> None:
"""Save settings when closing the application""" """Save settings when closing the application"""
# MusicTable/tableView column widths # MusicTable/tableView column widths
@ -355,7 +347,9 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
"""Sets the ID3 tag APIC (album art) for all selected song filepaths""" """Sets the ID3 tag APIC (album art) for all selected song filepaths"""
selected_songs = self.tableView.get_selected_songs_filepaths() selected_songs = self.tableView.get_selected_songs_filepaths()
for song in selected_songs: for song in selected_songs:
print(f"updating album art for {song}") logging.info(
f"main.py set_album_art_for_selected_songs() | updating album art for {song}"
)
self.update_album_art_for_song(song, album_art_path) self.update_album_art_for_song(song, album_art_path)
def update_album_art_for_song( def update_album_art_for_song(
@ -401,7 +395,9 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
del audio["APIC"] del audio["APIC"]
audio.save() audio.save()
except Exception as e: except Exception as e:
print(f"Error processing {file}: {e}") logging.error(
f"main.py delete_album_art_for_selected_songs() | Error processing {file}: {e}"
)
def update_audio_visualization(self) -> None: def update_audio_visualization(self) -> None:
"""Handles upading points on the pyqtgraph visual""" """Handles upading points on the pyqtgraph visual"""
@ -445,7 +441,7 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
self.current_volume = self.volumeSlider.value() self.current_volume = self.volumeSlider.value()
self.player.setVolume(self.current_volume) self.player.setVolume(self.current_volume)
except Exception as e: except Exception as e:
print(f"Changing volume error: {e}") logging.error(f"main.py volume_changed() | Changing volume error: {e}")
def on_play_clicked(self) -> None: def on_play_clicked(self) -> None:
"""Updates the Play & Pause buttons when clicked""" """Updates the Play & Pause buttons when clicked"""
@ -462,17 +458,20 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
def on_previous_clicked(self) -> None: def on_previous_clicked(self) -> None:
"""""" """"""
print("previous") logging.info("main.py on_previous_clicked()")
def on_next_clicked(self) -> None: def on_next_clicked(self) -> None:
print("next") logging.info("main.py on_next_clicked()")
def add_latest_playlist_to_tree(self) -> None: def add_latest_playlist_to_tree(self) -> None:
"""Refreshes the playlist tree""" """Refreshes the playlist tree"""
self.playlistTreeView.add_latest_playlist_to_tree() self.playlistTreeView.add_latest_playlist_to_tree()
def open_files(self) -> None: def open_files(self) -> None:
"""Opens the open files window""" """
Opens the open files window
- File > Open > List of song(s)
"""
open_files_window = QFileDialog( open_files_window = QFileDialog(
self, "Open file(s)", ".", "Audio files (*.mp3)" self, "Open file(s)", ".", "Audio files (*.mp3)"
) )
@ -480,7 +479,17 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
open_files_window.setFileMode(QFileDialog.ExistingFiles) open_files_window.setFileMode(QFileDialog.ExistingFiles)
open_files_window.exec_() open_files_window.exec_()
filenames = open_files_window.selectedFiles() filenames = open_files_window.selectedFiles()
self.tableView.add_files(filenames) # Adds files to the library in a new thread
worker = Worker(add_files_to_library, filenames)
worker.signals.signal_progress.connect(self.handle_progress)
worker.signals.signal_finished.connect(self.tableView.load_music_table)
self.threadpool.start(worker)
def handle_progress(self, data):
"""
updates the status bar when progress is emitted
"""
self.show_status_bar_message(data)
# File # File
@ -584,16 +593,23 @@ if __name__ == "__main__":
with open("utils/init.sql", "r") as file: with open("utils/init.sql", "r") as file:
lines = file.read() lines = file.read()
for statement in lines.split(";"): for statement in lines.split(";"):
print(f"executing [{statement}]") logging.info(f"executing [{statement}]")
db.execute(statement, ()) db.execute(statement, ())
# logging setup # logging setup
logging.basicConfig(filename="musicpom.log", encoding="utf-8", level=logging.DEBUG) file_handler = logging.FileHandler(filename="log", encoding="utf-8")
stdout_handler = logging.StreamHandler(stream=sys.stdout)
handlers = [file_handler, stdout_handler]
# logging.basicConfig(filename="log", encoding="utf-8", level=logging.DEBUG)
logging.basicConfig(
level=logging.DEBUG,
format="[%(asctime)s] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s",
handlers=handlers,
)
# Allow for dynamic imports of my custom classes and utilities # Allow for dynamic imports of my custom classes and utilities
project_root = os.path.abspath(os.path.dirname(__file__)) project_root = os.path.abspath(os.path.dirname(__file__))
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}")
# Dark theme >:3 # Dark theme >:3
qdarktheme.setup_theme() qdarktheme.setup_theme()
# Show the UI # Show the UI

View File

@ -1,6 +1,7 @@
import sys import sys
from mutagen.id3 import ID3, APIC from mutagen.id3 import ID3, APIC
import os import os
import logging
def print_id3_tags(file_path): def print_id3_tags(file_path):

View File

@ -8,19 +8,20 @@ config = ConfigParser()
config.read("config.ini") config.read("config.ini")
def add_files_to_library(files): def add_files_to_library(files, progress_callback):
"""Adds audio file(s) to the sqllite db """Adds audio file(s) to the sqllite db
files = list() of fully qualified paths to audio file(s) files = list() of fully qualified paths to audio file(s)
Returns a list of dictionaries of metadata Returns a list of dictionaries of metadata
""" """
logging.info("started function")
# print("Running add_files_to_library.py")
if not files: if not files:
return [] return []
extensions = config.get("settings", "extensions").split(",") extensions = config.get("settings", "extensions").split(",")
insert_data = [] # To store data for batch insert insert_data = [] # To store data for batch insert
for filepath in files: for filepath in files:
if any(filepath.lower().endswith(ext) for ext in extensions): if any(filepath.lower().endswith(ext) for ext in extensions):
if progress_callback:
progress_callback.emit(filepath)
filename = filepath.split("/")[-1] filename = filepath.split("/")[-1]
audio = get_id3_tags(filepath) audio = get_id3_tags(filepath)

View File

@ -1,40 +1,11 @@
# from mutagen.id3 import ID3
# from mutagen.id3._frames import TIT2
# import os
#
#
# def get_id3_tags(filename):
# """Get the ID3 tags for an audio file
#
# # Parameters
# `file` | str | Fully qualified path to file
#
# # Returns
# dict of all id3 tags
# at minimum we will get the filename as a title.[ID3:TIT2]
# """
#
# # Check if all tags are empty
# # tags_are_empty = all(not values for values in audio.values())
# audio = ID3()
# try:
# if not audio["TIT2"] or audio["TIT2"].text[0] == "":
# title = os.path.splitext(os.path.basename(filename))[0]
# frame = TIT2(encoding=3, text=[title])
# audio["TIT2"] = frame
# audio.save() # type: ignore
# except Exception as e:
# print(f"get_id3_tags.py | Could not assign file ID3 tag: {e}")
# return audio
import os import os
import logging
from mutagen.id3 import ID3, TIT2, ID3NoHeaderError from mutagen.id3 import ID3, TIT2, ID3NoHeaderError
def get_id3_tags(filename): def get_id3_tags(filename):
"""Get the ID3 tags for an audio file""" """Get the ID3 tags for an audio file"""
print(f"get id3 tags filename: {filename}") logging.info(f"filename: {filename}")
try: try:
# Open the MP3 file and read its content # Open the MP3 file and read its content
@ -59,6 +30,6 @@ def get_id3_tags(filename):
audio.save() audio.save()
except Exception as e: except Exception as e:
print(f"get_id3_tags.py | Could not assign file ID3 tag: {e}") logging.error(f"Could not assign file ID3 tag: {e}")
return audio return audio

View File

@ -92,9 +92,7 @@ def set_id3_tag(filepath: str, tag_name: str, value: str):
Returns: Returns:
True / False""" True / False"""
print( logging.info(f"filepath: {filepath} | tag_name: {tag_name} | value: {value}")
f"set_id3_tag.py | filepath: {filepath} | tag_name: {tag_name} | value: {value}"
)
try: try:
try: # Load existing tags try: # Load existing tags
@ -114,9 +112,7 @@ def set_id3_tag(filepath: str, tag_name: str, value: str):
try: try:
audio = ID3(filepath) audio = ID3(filepath)
except Exception as e: except Exception as e:
logging.debug( logging.error(f"ran into an exception: {e}")
f"set_id3_tag.py set_id3_tag() ran into an exception: {e}"
)
audio = ID3() audio = ID3()
audio.delall("USLT") audio.delall("USLT")
frame = USLT(encoding=3, text=value) frame = USLT(encoding=3, text=value)
@ -128,7 +124,7 @@ def set_id3_tag(filepath: str, tag_name: str, value: str):
tag_name = id3_tag_mapping[tag_name] tag_name = id3_tag_mapping[tag_name]
# Other # Other
if tag_name in mutagen_id3_tag_mapping: # Tag accounted for if tag_name in mutagen_id3_tag_mapping: # Tag accounted for
print(f"set_id3_tag.py | tag_name = {tag_name}") logging.info(f"tag_name = {tag_name}")
tag_class = mutagen_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])