lots of fixes for normal activity
This commit is contained in:
parent
84221f92ae
commit
df9399403a
@ -16,7 +16,7 @@ class EditPlaylistOptionsWindow(QDialog):
|
|||||||
# super(EditPlaylistOptionsWindow, self).__init__()
|
# super(EditPlaylistOptionsWindow, self).__init__()
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.setWindowTitle("Playlist options")
|
self.setWindowTitle("Playlist options")
|
||||||
# self.setMinimumSize(800, 200)
|
self.setMinimumSize(400, 300)
|
||||||
self.playlist_id = playlist_id
|
self.playlist_id = playlist_id
|
||||||
# self.playlist_path_prefix: str = self.config.get(
|
# self.playlist_path_prefix: str = self.config.get(
|
||||||
# "settings", "playlist_path_prefix"
|
# "settings", "playlist_path_prefix"
|
||||||
@ -61,7 +61,7 @@ class EditPlaylistOptionsWindow(QDialog):
|
|||||||
label.setFont(QFont("Sans", weight=QFont.Bold))
|
label.setFont(QFont("Sans", weight=QFont.Bold))
|
||||||
layout.addWidget(label)
|
layout.addWidget(label)
|
||||||
label = QLabel('i.e.: ../music/playlists/my-playlist.m3u')
|
label = QLabel('i.e.: ../music/playlists/my-playlist.m3u')
|
||||||
label.setFont(QFont("Serif", weight=QFont.Style.StyleItalic)) # bold category
|
label.setFont(QFont("Sans", italic=True))
|
||||||
layout.addWidget(label)
|
layout.addWidget(label)
|
||||||
|
|
||||||
# Relative export path line edit widget
|
# Relative export path line edit widget
|
||||||
@ -73,7 +73,7 @@ class EditPlaylistOptionsWindow(QDialog):
|
|||||||
label.setFont(QFont("Sans", weight=QFont.Bold))
|
label.setFont(QFont("Sans", weight=QFont.Bold))
|
||||||
layout.addWidget(label)
|
layout.addWidget(label)
|
||||||
label = QLabel("i.e.: /prefix/song.mp3")
|
label = QLabel("i.e.: /prefix/song.mp3")
|
||||||
label.setFont(QFont("Serif", weight=QFont.Style.StyleItalic)) # bold category
|
label.setFont(QFont("Sans", italic=True))
|
||||||
layout.addWidget(label)
|
layout.addWidget(label)
|
||||||
|
|
||||||
# Playlist file save path line edit widget
|
# Playlist file save path line edit widget
|
||||||
|
|||||||
@ -46,7 +46,7 @@ class LyricsWindow(QDialog):
|
|||||||
value=self.input_field.toPlainText(),
|
value=self.input_field.toPlainText(),
|
||||||
)
|
)
|
||||||
if success:
|
if success:
|
||||||
debug("lyrical success! yay")
|
debug("Lyrics saved successfully")
|
||||||
else:
|
else:
|
||||||
error_dialog = ErrorDialog("Could not save lyrics :( sad")
|
error_dialog = ErrorDialog("Could not save lyrics :( sad")
|
||||||
error_dialog.exec()
|
error_dialog.exec()
|
||||||
|
|||||||
@ -38,6 +38,7 @@ from components.QuestionBoxDetails import QuestionBoxDetails
|
|||||||
from components.HeaderTags import HeaderTags2
|
from components.HeaderTags import HeaderTags2
|
||||||
|
|
||||||
from utils import (
|
from utils import (
|
||||||
|
batch_delete_filepaths_from_filesystem,
|
||||||
batch_delete_filepaths_from_database,
|
batch_delete_filepaths_from_database,
|
||||||
batch_delete_filepaths_from_playlist,
|
batch_delete_filepaths_from_playlist,
|
||||||
add_files_to_database,
|
add_files_to_database,
|
||||||
@ -315,7 +316,7 @@ class MusicTable(QTableView):
|
|||||||
if directories:
|
if directories:
|
||||||
debug('Spawning worker thread for directories')
|
debug('Spawning worker thread for directories')
|
||||||
worker = Worker(self.get_audio_files_recursively, directories)
|
worker = Worker(self.get_audio_files_recursively, directories)
|
||||||
_ = worker.signals.signal_progress.connect(self.handle_progress)
|
_ = worker.signals.signal_progress.connect(self.qapp.handle_progress)
|
||||||
_ = worker.signals.signal_result.connect(
|
_ = worker.signals.signal_result.connect(
|
||||||
self.on_get_audio_files_recursively_finished
|
self.on_get_audio_files_recursively_finished
|
||||||
)
|
)
|
||||||
@ -645,7 +646,7 @@ class MusicTable(QTableView):
|
|||||||
- Drag & Drop song(s) on tableView
|
- Drag & Drop song(s) on tableView
|
||||||
- File > Open > List of song(s)
|
- File > Open > List of song(s)
|
||||||
"""
|
"""
|
||||||
debug(f'add_files_to_library() files={files}')
|
# debug(f'add_files_to_library() files={files}')
|
||||||
worker = Worker(add_files_to_database, files, None)
|
worker = Worker(add_files_to_database, files, None)
|
||||||
_ = worker.signals.signal_progress.connect(self.qapp.handle_progress) # type: ignore
|
_ = worker.signals.signal_progress.connect(self.qapp.handle_progress) # type: ignore
|
||||||
_ = worker.signals.signal_result.connect(self.on_add_files_to_database_finished)
|
_ = worker.signals.signal_result.connect(self.on_add_files_to_database_finished)
|
||||||
@ -678,16 +679,17 @@ class MusicTable(QTableView):
|
|||||||
"""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?
|
||||||
# | Delete from playlist & lib | Delete from playlist only | Cancel |
|
# | Delete from playlist & lib | Delete from playlist only | Cancel |
|
||||||
# Currently, this just has 2 options [the playlist], or [the main lib & all playlists]
|
# Currently, this just has 2 options [the playlist, cancel], or [the main lib & all playlists, cancel]
|
||||||
selected_filepaths = self.get_selected_songs_filepaths()
|
selected_filepaths = self.get_selected_songs_filepaths()
|
||||||
if self.selected_playlist_id:
|
if self.selected_playlist_id:
|
||||||
question_dialog = QuestionBoxDetails(
|
question_dialog = QuestionBoxDetails(
|
||||||
title="Delete songs",
|
title="Delete songs",
|
||||||
description="Remove these songs from the playlist?",
|
description="Remove these songs from the playlist?",
|
||||||
|
choices=[(1, 'Confirm'), (0, 'Cancel')],
|
||||||
data=selected_filepaths,
|
data=selected_filepaths,
|
||||||
)
|
)
|
||||||
reply = question_dialog.execute()
|
reply = question_dialog.execute()
|
||||||
if reply:
|
if reply == 1:
|
||||||
worker = Worker(
|
worker = Worker(
|
||||||
batch_delete_filepaths_from_playlist,
|
batch_delete_filepaths_from_playlist,
|
||||||
selected_filepaths,
|
selected_filepaths,
|
||||||
@ -701,11 +703,12 @@ class MusicTable(QTableView):
|
|||||||
else:
|
else:
|
||||||
question_dialog = QuestionBoxDetails(
|
question_dialog = QuestionBoxDetails(
|
||||||
title="Delete songs",
|
title="Delete songs",
|
||||||
description="Remove these songs from the library?\n(This will remove the songs from all playlists)",
|
description="Delete these songs from the library?\n(This will remove the songs from all playlists)",
|
||||||
|
choices=[(1, 'Delete'), (2, 'Delete from filesystem too'), (0, 'Cancel')],
|
||||||
data=selected_filepaths,
|
data=selected_filepaths,
|
||||||
)
|
)
|
||||||
reply = question_dialog.execute()
|
reply = question_dialog.execute()
|
||||||
if reply:
|
if reply == 1 or reply == 2:
|
||||||
worker = Worker(
|
worker = Worker(
|
||||||
batch_delete_filepaths_from_database, selected_filepaths
|
batch_delete_filepaths_from_database, selected_filepaths
|
||||||
)
|
)
|
||||||
@ -714,21 +717,18 @@ class MusicTable(QTableView):
|
|||||||
if self.qapp:
|
if self.qapp:
|
||||||
threadpool = self.qapp.threadpool # type: ignore
|
threadpool = self.qapp.threadpool # type: ignore
|
||||||
threadpool.start(worker)
|
threadpool.start(worker)
|
||||||
|
if reply == 2:
|
||||||
|
def finished():
|
||||||
|
self.qapp.handle_progress('Files delete from system.')
|
||||||
|
worker = Worker(
|
||||||
|
batch_delete_filepaths_from_filesystem, selected_filepaths
|
||||||
|
)
|
||||||
|
worker.signals.signal_progress.connect(self.qapp.handle_progress) # type: ignore
|
||||||
|
worker.signals.signal_finished.connect(finished)
|
||||||
|
if self.qapp:
|
||||||
|
threadpool = self.qapp.threadpool # type: ignore
|
||||||
|
threadpool.start(worker)
|
||||||
|
|
||||||
# def delete_selected_row_indices(self):
|
|
||||||
# """
|
|
||||||
# Removes rows from the QTableView based on a list of indices
|
|
||||||
# and then reload the table
|
|
||||||
# """
|
|
||||||
# debug('delete_selected_row_indices')
|
|
||||||
# selected_indices = self.get_selected_rows()
|
|
||||||
# for index in selected_indices:
|
|
||||||
# try:
|
|
||||||
# self.model2.removeRow(index)
|
|
||||||
# except Exception as e:
|
|
||||||
# debug(f"delete_selected_row_indices() failed | {e}")
|
|
||||||
# self.model2.layoutChanged.emit() # emits a signal that the view should be updated
|
|
||||||
# # self.viewport().update()
|
|
||||||
|
|
||||||
def delete_selected_row_indices(self):
|
def delete_selected_row_indices(self):
|
||||||
"""
|
"""
|
||||||
@ -845,7 +845,7 @@ class MusicTable(QTableView):
|
|||||||
)
|
)
|
||||||
if reply == QMessageBox.Yes:
|
if reply == QMessageBox.Yes:
|
||||||
worker = Worker(self.reorganize_files, filepaths)
|
worker = Worker(self.reorganize_files, filepaths)
|
||||||
worker.signals.signal_progress.connect(self.handle_progress)
|
worker.signals.signal_progress.connect(self.qapp.handle_progress)
|
||||||
worker.signals.signal_finished.connect(self.load_music_table)
|
worker.signals.signal_finished.connect(self.load_music_table)
|
||||||
self.qapp.threadpool.start(worker) # type: ignore
|
self.qapp.threadpool.start(worker) # type: ignore
|
||||||
|
|
||||||
@ -1128,7 +1128,6 @@ class MusicTable(QTableView):
|
|||||||
audio_files.append(os.path.join(root, file))
|
audio_files.append(os.path.join(root, file))
|
||||||
if progress_callback:
|
if progress_callback:
|
||||||
progress_callback.emit(f"Scanning {file}")
|
progress_callback.emit(f"Scanning {file}")
|
||||||
debug(f'get_audio_files_recursively() return: {audio_files}')
|
|
||||||
return audio_files
|
return audio_files
|
||||||
|
|
||||||
def get_selected_rows(self) -> list[int]:
|
def get_selected_rows(self) -> list[int]:
|
||||||
|
|||||||
@ -15,15 +15,30 @@ from pprint import pformat
|
|||||||
|
|
||||||
|
|
||||||
class QuestionBoxDetails(QDialog):
|
class QuestionBoxDetails(QDialog):
|
||||||
def __init__(self, title: str, description: str, data):
|
"""
|
||||||
|
Dialog box primitive that displays arbitrary `data`, and prompts user for a `reply` as a QPushButton
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- `reply`: An integer representing a user choice based on key of `choices`
|
||||||
|
- `choices`: A list of tuples, where each tuple represents a valid reply
|
||||||
|
e.g. [(reply: int, name: str),]
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
You may supply an arbitrary number of choices, but here are my suggestions:
|
||||||
|
2 = The alternative
|
||||||
|
1 = The default
|
||||||
|
0 = Cancel
|
||||||
|
-1 = Cancel action (non-response)
|
||||||
|
"""
|
||||||
|
def __init__(self, title: str, description: str, data: str | list | dict, choices: list[tuple[int, str]]):
|
||||||
super(QuestionBoxDetails, self).__init__()
|
super(QuestionBoxDetails, self).__init__()
|
||||||
self.title: str = title
|
self.title: str = title
|
||||||
self.description: str = description
|
self.description: str = description
|
||||||
self.data: str = data
|
self.data: str | list | dict = data
|
||||||
self.reply: bool = False
|
self.reply: int = -1
|
||||||
self.setWindowTitle(title)
|
self.setWindowTitle(title)
|
||||||
self.setMinimumSize(400, 400)
|
self.setMinimumSize(600, 400)
|
||||||
self.setMaximumSize(600,1000)
|
self.setMaximumSize(600, 400)
|
||||||
layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
h_layout = QHBoxLayout()
|
h_layout = QHBoxLayout()
|
||||||
|
|
||||||
@ -37,10 +52,9 @@ class QuestionBoxDetails(QDialog):
|
|||||||
else:
|
else:
|
||||||
table: QTableWidget = QTableWidget()
|
table: QTableWidget = QTableWidget()
|
||||||
table.setSizeAdjustPolicy(QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents)
|
table.setSizeAdjustPolicy(QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents)
|
||||||
table.horizontalHeader().setStretchLastSection(True)
|
table.horizontalHeader().setStretchLastSection(True) # type: ignore
|
||||||
table.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
|
table.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive) # type: ignore
|
||||||
if isinstance(self.data, list):
|
if isinstance(self.data, list):
|
||||||
# big ol column
|
|
||||||
table.setRowCount(len(data))
|
table.setRowCount(len(data))
|
||||||
table.setColumnCount(1)
|
table.setColumnCount(1)
|
||||||
for i, item in enumerate(self.data):
|
for i, item in enumerate(self.data):
|
||||||
@ -50,10 +64,10 @@ class QuestionBoxDetails(QDialog):
|
|||||||
try:
|
try:
|
||||||
# | TIT2 | title goes here |
|
# | TIT2 | title goes here |
|
||||||
# | TDRC | 2025-05-05 |
|
# | TDRC | 2025-05-05 |
|
||||||
table.setRowCount(len(data.keys()))
|
table.setRowCount(len(data.keys())) # type: ignore
|
||||||
table.setColumnCount(2)
|
table.setColumnCount(2)
|
||||||
table.setHorizontalHeaderLabels(['Tag', 'Value'])
|
table.setHorizontalHeaderLabels(['Key', 'Value'])
|
||||||
for i, (k, v) in enumerate(data.items()):
|
for i, (k, v) in enumerate(data.items()): # type: ignore
|
||||||
table.setItem(i, 0, QTableWidgetItem(str(k)))
|
table.setItem(i, 0, QTableWidgetItem(str(k)))
|
||||||
table.setItem(i, 1, QTableWidgetItem(str(v)))
|
table.setItem(i, 1, QTableWidgetItem(str(v)))
|
||||||
layout.addWidget(table)
|
layout.addWidget(table)
|
||||||
@ -62,21 +76,39 @@ class QuestionBoxDetails(QDialog):
|
|||||||
self.input_field = QPlainTextEdit(pformat(data + "\n\n" + str(e)))
|
self.input_field = QPlainTextEdit(pformat(data + "\n\n" + str(e)))
|
||||||
layout.addWidget(self.input_field)
|
layout.addWidget(self.input_field)
|
||||||
error(f'Tried to load self.data as dict but could not. {e}')
|
error(f'Tried to load self.data as dict but could not. {e}')
|
||||||
# ok
|
|
||||||
ok_button = QPushButton("Confirm")
|
# dynamically load the button choices
|
||||||
ok_button.clicked.connect(self.ok)
|
def create_button(choice):
|
||||||
h_layout.addWidget(ok_button)
|
def func():
|
||||||
# cancel
|
self.reply = choice[0]
|
||||||
cancel_button = QPushButton("no")
|
self.close()
|
||||||
cancel_button.clicked.connect(self.cancel)
|
button = QPushButton(choice[1])
|
||||||
h_layout.addWidget(cancel_button)
|
button.clicked.connect(func)
|
||||||
|
return button
|
||||||
|
|
||||||
|
for idx, choice in enumerate(choices):
|
||||||
|
button = create_button(choice)
|
||||||
|
h_layout.addWidget(button)
|
||||||
|
if idx == 0:
|
||||||
|
button.setFocus()
|
||||||
|
|
||||||
|
|
||||||
|
# # ok
|
||||||
|
# ok_button = QPushButton("Confirm")
|
||||||
|
# ok_button.clicked.connect(self.ok)
|
||||||
|
# h_layout.addWidget(ok_button)
|
||||||
|
# # cancel
|
||||||
|
# cancel_button = QPushButton("no")
|
||||||
|
# cancel_button.clicked.connect(self.cancel)
|
||||||
|
# h_layout.addWidget(cancel_button)
|
||||||
|
|
||||||
layout.addLayout(h_layout)
|
layout.addLayout(h_layout)
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
|
|
||||||
ok_button.setFocus()
|
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
|
"""
|
||||||
|
Returns when `self` is closed
|
||||||
|
"""
|
||||||
self.exec_()
|
self.exec_()
|
||||||
return self.reply
|
return self.reply
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
from .fft_analyser import FFTAnalyser
|
from .fft_analyser import FFTAnalyser
|
||||||
from .convert_id3_timestamp_to_datetime import convert_id3_timestamp_to_datetime
|
from .handle_date_tag import handle_date_tag
|
||||||
from .initialize_db import initialize_db
|
from .initialize_db import initialize_db
|
||||||
from .safe_get import safe_get
|
from .safe_get import safe_get
|
||||||
from .get_album_art import get_album_art
|
from .get_album_art import get_album_art
|
||||||
@ -7,6 +7,7 @@ from .get_tags import get_tags, id3_remap
|
|||||||
from .get_reorganize_vars import get_reorganize_vars, parse_artist_album
|
from .get_reorganize_vars import get_reorganize_vars, parse_artist_album
|
||||||
from .set_tag import set_tag
|
from .set_tag import set_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 .batch_delete_filepaths_from_filesystem import batch_delete_filepaths_from_filesystem
|
||||||
from .batch_delete_filepaths_from_database import batch_delete_filepaths_from_database
|
from .batch_delete_filepaths_from_database import batch_delete_filepaths_from_database
|
||||||
from .batch_delete_filepaths_from_playlist import batch_delete_filepaths_from_playlist
|
from .batch_delete_filepaths_from_playlist import batch_delete_filepaths_from_playlist
|
||||||
from .delete_and_create_library_database import delete_and_create_library_database
|
from .delete_and_create_library_database import delete_and_create_library_database
|
||||||
|
|||||||
@ -85,7 +85,7 @@ def add_files_to_database(files: list[str], playlist_id: int | None = None, prog
|
|||||||
insert_data = [] # Reset the insert_data list
|
insert_data = [] # Reset the insert_data list
|
||||||
# Insert any remaining data after reading every file
|
# Insert any remaining data after reading every file
|
||||||
if insert_data:
|
if insert_data:
|
||||||
debug(f'inserting the rest of the songs | insert_data={insert_data}')
|
# debug(f'inserting the rest of the songs | insert_data={insert_data}')
|
||||||
with DBA.DBAccess() as db:
|
with DBA.DBAccess() as db:
|
||||||
db.executemany(
|
db.executemany(
|
||||||
"INSERT OR IGNORE INTO song (filepath, title, album, artist, track_number, genre, codec, album_date, bitrate, length_ms) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);",
|
"INSERT OR IGNORE INTO song (filepath, title, album, artist, track_number, genre, codec, album_date, bitrate, length_ms) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);",
|
||||||
|
|||||||
34
utils/batch_delete_filepaths_from_filesystem.py
Normal file
34
utils/batch_delete_filepaths_from_filesystem.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from components.ErrorDialog import ErrorDialog
|
||||||
|
|
||||||
|
|
||||||
|
def batch_delete_filepaths_from_filesystem(
|
||||||
|
files: list[str], progress_callback=None
|
||||||
|
) -> bool:
|
||||||
|
"""
|
||||||
|
Handles deleting song files from filesystem
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- files: a list of absolute filepaths to songs
|
||||||
|
- progress_callback: emit this signal for user feedback
|
||||||
|
|
||||||
|
Returns True on success
|
||||||
|
False on failure/error
|
||||||
|
"""
|
||||||
|
# Get song IDs from filepaths
|
||||||
|
try:
|
||||||
|
for file in files:
|
||||||
|
os.remove(file)
|
||||||
|
if progress_callback:
|
||||||
|
progress_callback.emit(f'Removing {file}')
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(
|
||||||
|
f"An error occurred during batch processing: {e}"
|
||||||
|
)
|
||||||
|
dialog = ErrorDialog(
|
||||||
|
f"An error occurred during batch processing: {e}"
|
||||||
|
)
|
||||||
|
dialog.exec_()
|
||||||
|
return False
|
||||||
|
return True
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import datetime
|
|
||||||
from mutagen.id3._specs import ID3TimeStamp
|
|
||||||
|
|
||||||
|
|
||||||
def convert_id3_timestamp_to_datetime(timestamp):
|
|
||||||
"""Turns a mutagen ID3TimeStamp into a format that SQLite can use for Date field"""
|
|
||||||
if timestamp is None:
|
|
||||||
return
|
|
||||||
if not isinstance(timestamp, ID3TimeStamp):
|
|
||||||
return
|
|
||||||
if len(timestamp.text) == 4: # If only year is provided
|
|
||||||
datetime_obj = datetime.datetime.strptime(timestamp.text, "%Y")
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
datetime_obj = datetime.datetime.strptime(timestamp.text, "%Y-%m-%d")
|
|
||||||
except ValueError:
|
|
||||||
datetime_obj = datetime.datetime.strptime(timestamp.text, "%Y%m%d")
|
|
||||||
return datetime_obj.strftime("%Y-%m-%d")
|
|
||||||
@ -16,7 +16,7 @@ def delete_album_art(file: str) -> bool:
|
|||||||
try:
|
try:
|
||||||
debug("Deleting album art")
|
debug("Deleting album art")
|
||||||
audio = ID3(file)
|
audio = ID3(file)
|
||||||
debug(audio)
|
# debug(audio)
|
||||||
if "APIC:" in audio:
|
if "APIC:" in audio:
|
||||||
del audio["APIC:"]
|
del audio["APIC:"]
|
||||||
debug("Deleting album art for real this time")
|
debug("Deleting album art for real this time")
|
||||||
|
|||||||
@ -62,12 +62,10 @@ def export_playlist_by_id(playlist_db_id: int, progress_callback=None) -> bool:
|
|||||||
artist, album = parse_artist_album(song)
|
artist, album = parse_artist_album(song)
|
||||||
write_path = Path(path_prefix) / artist / album / song.name
|
write_path = Path(path_prefix) / artist / album / song.name
|
||||||
write_paths.append(str(write_path) + "\n")
|
write_paths.append(str(write_path) + "\n")
|
||||||
# write_to_playlist_file(write_paths, auto_export_path)
|
worker = Worker(write_to_playlist_file, write_paths, auto_export_path)
|
||||||
|
# worker.signals.signal_finished.connect(None)
|
||||||
worker = Worker(write_to_playlist_file, write_paths, auto_export_path)
|
# worker.signals.signal_progress.connect()
|
||||||
# worker.signals.signal_finished.connect(None)
|
threadpool.start(worker)
|
||||||
# worker.signals.signal_progress.connect()
|
|
||||||
threadpool.start(worker)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -57,6 +57,9 @@ def decode_text_frame(data):
|
|||||||
|
|
||||||
|
|
||||||
def parse_artist_album(filepath) -> tuple[str, str]:
|
def parse_artist_album(filepath) -> tuple[str, str]:
|
||||||
|
"""
|
||||||
|
ai slop plus riley
|
||||||
|
"""
|
||||||
with open(filepath, "rb") as f:
|
with open(filepath, "rb") as f:
|
||||||
header = f.read(10)
|
header = f.read(10)
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@ from mutagen.flac import FLAC
|
|||||||
from mutagen.id3._frames import TIT2
|
from mutagen.id3._frames import TIT2
|
||||||
from mutagen.id3._util import ID3NoHeaderError
|
from mutagen.id3._util import ID3NoHeaderError
|
||||||
|
|
||||||
from utils import convert_id3_timestamp_to_datetime
|
from utils import handle_date_tag
|
||||||
|
|
||||||
|
|
||||||
def get_mp3_tags(filename: str) -> tuple[MP3 | ID3 | FLAC, str]:
|
def get_mp3_tags(filename: str) -> tuple[MP3 | ID3 | FLAC, str]:
|
||||||
@ -52,7 +52,7 @@ def id3_remap(audio: MP3 | ID3 | FLAC) -> dict[str, str | int | None]:
|
|||||||
"album": audio.get("TALB"),
|
"album": audio.get("TALB"),
|
||||||
"track_number": audio.get("TRCK"),
|
"track_number": audio.get("TRCK"),
|
||||||
"genre": audio.get("TCON"),
|
"genre": audio.get("TCON"),
|
||||||
"date": convert_id3_timestamp_to_datetime(audio.get("TDRC")),
|
"date": handle_date_tag(audio.get("TDRC")),
|
||||||
"bitrate": audio.get("TBIT"),
|
"bitrate": audio.get("TBIT"),
|
||||||
"lyrics": lyrics,
|
"lyrics": lyrics,
|
||||||
"length": int(round(audio.info.length, 0)),
|
"length": int(round(audio.info.length, 0)),
|
||||||
@ -88,7 +88,10 @@ def get_tags(filename: str) -> tuple[MP3 | ID3 | FLAC, str]:
|
|||||||
"""
|
"""
|
||||||
# FIXME: this is where i implement other filetypes
|
# FIXME: this is where i implement other filetypes
|
||||||
if filename.lower().endswith(".mp3"):
|
if filename.lower().endswith(".mp3"):
|
||||||
tags, details = get_mp3_tags(filename)
|
try:
|
||||||
|
tags, details = get_mp3_tags(filename)
|
||||||
|
except Exception as e:
|
||||||
|
tags, details = ID3(), str(e)
|
||||||
else:
|
else:
|
||||||
tags, details = ID3(), "non mp3 file"
|
tags, details = ID3(), "non mp3 file"
|
||||||
return tags, details
|
return tags, details
|
||||||
|
|||||||
25
utils/handle_date_tag.py
Normal file
25
utils/handle_date_tag.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import datetime
|
||||||
|
from mutagen.id3._specs import ID3TimeStamp
|
||||||
|
from logging import debug, error
|
||||||
|
import dateutil.parser
|
||||||
|
|
||||||
|
def handle_date_tag(tag) -> str | None:
|
||||||
|
"""
|
||||||
|
Handles TDRC ID3 tag and what not
|
||||||
|
# {handle_date_tag.py:9} DEBUG - tag = 2025-11-05
|
||||||
|
# {handle_date_tag.py:10} DEBUG - tag type = <class 'mutagen.id3.TDRC'>
|
||||||
|
# {handle_date_tag.py:17} DEBUG - tag.text[0] = 2025-11-05
|
||||||
|
# {handle_date_tag.py:18} DEBUG - tag.text[0] type = <class 'mutagen.id3._specs.ID3TimeStamp'>
|
||||||
|
"""
|
||||||
|
if tag is None:
|
||||||
|
return None
|
||||||
|
if isinstance(tag, ID3TimeStamp):
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
datetime = dateutil.parser.parse(str(tag))
|
||||||
|
formatted_date = datetime.strftime("%Y-%m-%d")
|
||||||
|
return formatted_date
|
||||||
|
except ValueError as e:
|
||||||
|
error(f'date tag is incomprehensible. | tag = {tag} | error: {e}')
|
||||||
|
return None
|
||||||
@ -19,7 +19,7 @@ def set_tag(filepath: str, db_column: str, value: str):
|
|||||||
True / False
|
True / False
|
||||||
"""
|
"""
|
||||||
headers = HeaderTags2()
|
headers = HeaderTags2()
|
||||||
debug(f"filepath: {filepath} | db_column: {db_column} | value: {value}")
|
# debug(f"filepath: {filepath} | db_column: {db_column} | value: {value}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
try: # Load existing tags
|
try: # Load existing tags
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user