working on making id3 tags easier to work with
This commit is contained in:
parent
d3313db6ab
commit
554b06a667
@ -63,6 +63,7 @@ from configparser import ConfigParser
|
||||
|
||||
|
||||
class MusicTable(QTableView):
|
||||
playlistStatsSignal = pyqtSignal(str)
|
||||
playPauseSignal = pyqtSignal()
|
||||
playSignal = pyqtSignal(str)
|
||||
enterKey = pyqtSignal()
|
||||
@ -162,7 +163,7 @@ class MusicTable(QTableView):
|
||||
self.model2.layoutChanged.connect(self.restore_scroll_position)
|
||||
self.horizontal_header.sectionResized.connect(self.on_header_resized)
|
||||
# Final actions
|
||||
self.load_music_table()
|
||||
# self.load_music_table()
|
||||
self.setup_keyboard_shortcuts()
|
||||
self.load_header_widths()
|
||||
|
||||
@ -455,7 +456,7 @@ class MusicTable(QTableView):
|
||||
except IndexError:
|
||||
pass
|
||||
except Exception as e:
|
||||
debug(f'on_add_files_to_database_finished() | Something went wrong: {e}')
|
||||
debug(f"on_add_files_to_database_finished() | Something went wrong: {e}")
|
||||
|
||||
# ____________________
|
||||
# | |
|
||||
@ -690,9 +691,6 @@ class MusicTable(QTableView):
|
||||
# Fetch playlist data
|
||||
selected_playlist_id = playlist_id[0]
|
||||
try:
|
||||
debug(
|
||||
f"load_music_table() | selected_playlist_id: {selected_playlist_id}"
|
||||
)
|
||||
with DBA.DBAccess() as db:
|
||||
data = db.query(
|
||||
"SELECT s.id, s.title, s.artist, s.album, s.track_number, s.genre, s.codec, s.album_date, s.filepath FROM song s JOIN song_playlist sp ON s.id = sp.song_id WHERE sp.playlist_id = ?",
|
||||
@ -712,7 +710,10 @@ class MusicTable(QTableView):
|
||||
error(f"load_music_table() | Unhandled exception: {e}")
|
||||
return
|
||||
# Populate the model
|
||||
row_count: int = 0
|
||||
total_time: int = 0 # total time of all songs in seconds
|
||||
for row_data in data:
|
||||
row_count += 1
|
||||
id, *rest_of_data = row_data
|
||||
# handle different datatypes
|
||||
items = []
|
||||
@ -731,6 +732,7 @@ class MusicTable(QTableView):
|
||||
for item in items:
|
||||
item.setData(id, Qt.ItemDataRole.UserRole)
|
||||
self.model2.layoutChanged.emit() # emits a signal that the view should be updated
|
||||
self.playlistStatsSignal.emit(f"Songs: {row_count} | Total time: {total_time}")
|
||||
self.connect_data_changed()
|
||||
self.connect_layout_changed()
|
||||
|
||||
@ -739,7 +741,7 @@ class MusicTable(QTableView):
|
||||
Loads the header widths from the last application close.
|
||||
"""
|
||||
table_view_column_widths = str(self.config["table"]["column_widths"]).split(",")
|
||||
debug(f'loaded header widths: {table_view_column_widths}')
|
||||
debug(f"loaded header widths: {table_view_column_widths}")
|
||||
if not isinstance(table_view_column_widths, list):
|
||||
for i in range(self.model2.columnCount() - 1):
|
||||
self.setColumnWidth(i, int(table_view_column_widths[i]))
|
||||
@ -830,10 +832,6 @@ class MusicTable(QTableView):
|
||||
"""Returns the selected songs filepath"""
|
||||
return self.selected_song_filepath
|
||||
|
||||
def get_selected_song_metadata(self) -> ID3 | dict:
|
||||
"""Returns the selected song's ID3 tags"""
|
||||
return get_id3_tags(self.selected_song_filepath)[0]
|
||||
|
||||
def get_selected_songs_db_ids(self) -> list:
|
||||
"""Returns a list of id's for the selected songs"""
|
||||
indexes = self.selectedIndexes()
|
||||
@ -854,6 +852,10 @@ class MusicTable(QTableView):
|
||||
"""Returns the currently playing song's ID3 tags"""
|
||||
return get_id3_tags(self.current_song_filepath)[0]
|
||||
|
||||
def get_selected_song_metadata(self) -> ID3 | dict:
|
||||
"""Returns the selected song's ID3 tags"""
|
||||
return get_id3_tags(self.selected_song_filepath)[0]
|
||||
|
||||
def get_current_song_album_art(self) -> bytes:
|
||||
"""Returns the APIC data (album art lol) for the currently playing song"""
|
||||
return get_album_art(self.current_song_filepath)
|
||||
|
||||
21
main.py
21
main.py
@ -37,7 +37,7 @@ from PyQt5.QtCore import (
|
||||
QThreadPool,
|
||||
QRunnable,
|
||||
)
|
||||
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QAudioProbe
|
||||
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QAudioProbe, QMediaPlaylist
|
||||
from PyQt5.QtGui import QClipboard, QCloseEvent, QFont, QPixmap, QResizeEvent
|
||||
from utils import (
|
||||
delete_album_art,
|
||||
@ -162,7 +162,7 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
|
||||
|
||||
# self.vLayoutAlbumArt.SetFixedSize()
|
||||
self.status_bar = QStatusBar()
|
||||
self.permanent_status_label = QLabel("Status...")
|
||||
self.permanent_status_label = QLabel("")
|
||||
self.status_bar.addPermanentWidget(self.permanent_status_label)
|
||||
self.setStatusBar(self.status_bar)
|
||||
|
||||
@ -174,6 +174,7 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
|
||||
# widget bits
|
||||
self.album_art_scene: QGraphicsScene = QGraphicsScene()
|
||||
self.player: QMediaPlayer = QMediaPlayer() # Audio player object
|
||||
self.playlist: QMediaPlaylist = QMediaPlaylist()
|
||||
self.probe: QAudioProbe = QAudioProbe() # Gets audio buffer data
|
||||
self.audio_visualizer: AudioVisualizer = AudioVisualizer(
|
||||
self.player, self.probe, self.PlotWidget
|
||||
@ -248,13 +249,15 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
|
||||
|
||||
## CONNECTIONS
|
||||
# tableView
|
||||
# self.tableView.doubleClicked.connect(self.play_audio_file)
|
||||
# self.tableView.enterKey.connect(self.play_audio_file)
|
||||
self.tableView.playSignal.connect(self.play_audio_file)
|
||||
self.tableView.playPauseSignal.connect(
|
||||
self.on_play_clicked
|
||||
) # Spacebar toggle play/pause signal
|
||||
self.tableView.handleProgressSignal.connect(self.handle_progress)
|
||||
self.tableView.playlistStatsSignal.connect(
|
||||
self.set_permanent_status_bar_message
|
||||
)
|
||||
self.tableView.load_music_table()
|
||||
|
||||
# playlistTreeView
|
||||
self.playlistTreeView.playlistChoiceSignal.connect(
|
||||
@ -289,7 +292,7 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
|
||||
for i in range(self.tableView.model2.columnCount()):
|
||||
list_of_column_widths.append(str(self.tableView.columnWidth(i)))
|
||||
column_widths_as_string = ",".join(list_of_column_widths)
|
||||
debug(f'saving column widths: {column_widths_as_string}')
|
||||
debug(f"saving column widths: {column_widths_as_string}")
|
||||
self.config["table"]["column_widths"] = column_widths_as_string
|
||||
self.config["settings"]["volume"] = str(self.current_volume)
|
||||
self.config["settings"]["window_size"] = (
|
||||
@ -301,7 +304,7 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
|
||||
with open(self.cfg_file, "w") as configfile:
|
||||
self.config.write(configfile)
|
||||
except Exception as e:
|
||||
debug(f'wtf man {e}')
|
||||
debug(f"wtf man {e}")
|
||||
if a0 is not None:
|
||||
super().closeEvent(a0)
|
||||
|
||||
@ -327,7 +330,10 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
|
||||
self.speedLabel.setText("{:.2f}".format(rate / 50))
|
||||
|
||||
def on_play_clicked(self) -> None:
|
||||
"""Updates the Play & Pause buttons when clicked"""
|
||||
"""
|
||||
Plays & pauses the song
|
||||
Updates the button icons
|
||||
"""
|
||||
pixmapi = QStyle.StandardPixmap.SP_MediaPlay
|
||||
play_icon = self.style().standardIcon(pixmapi) # type: ignore
|
||||
pixmapi = QStyle.StandardPixmap.SP_MediaPause
|
||||
@ -405,7 +411,6 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
|
||||
"""
|
||||
Sets the permanent message label in the status bar
|
||||
"""
|
||||
# what does this do?
|
||||
self.permanent_status_label.setText(message)
|
||||
|
||||
def show_status_bar_message(self, message: str, timeout: int | None = None) -> None:
|
||||
|
||||
@ -3,7 +3,7 @@ from .convert_id3_timestamp_to_datetime import convert_id3_timestamp_to_datetime
|
||||
from .initialize_db import initialize_db
|
||||
from .safe_get import safe_get
|
||||
from .get_album_art import get_album_art
|
||||
from .get_id3_tags import get_id3_tags
|
||||
from .get_id3_tags import get_id3_tags, id3_remap
|
||||
from .get_reorganize_vars import get_reorganize_vars
|
||||
from .set_id3_tag import set_id3_tag
|
||||
from .delete_song_id_from_database import delete_song_id_from_database
|
||||
|
||||
@ -2,7 +2,7 @@ from PyQt5.QtWidgets import QMessageBox
|
||||
from mutagen.id3 import ID3
|
||||
import DBA
|
||||
from logging import debug
|
||||
from utils import get_id3_tags, convert_id3_timestamp_to_datetime
|
||||
from utils import get_id3_tags, convert_id3_timestamp_to_datetime, id3_remap
|
||||
from configparser import ConfigParser
|
||||
from pathlib import Path
|
||||
from appdirs import user_config_dir
|
||||
@ -14,8 +14,12 @@ def add_files_to_database(files, progress_callback=None):
|
||||
Args:
|
||||
files: list() of fully qualified paths to audio file(s)
|
||||
progress_callback: emit data for user feedback
|
||||
Returns:
|
||||
True on success, else False
|
||||
|
||||
Returns a tuple where the first value is the success state
|
||||
and the second value is a list of failed to add items
|
||||
```
|
||||
(True, {"filename.mp3":"failed because i said so"})
|
||||
```
|
||||
"""
|
||||
config = ConfigParser()
|
||||
cfg_file = (
|
||||
@ -23,7 +27,7 @@ def add_files_to_database(files, progress_callback=None):
|
||||
)
|
||||
config.read(cfg_file)
|
||||
if not files:
|
||||
return False, {'Failure': 'All operations failed in add_files_to_database()'}
|
||||
return False, {"Failure": "All operations failed in add_files_to_database()"}
|
||||
extensions = config.get("settings", "extensions").split(",")
|
||||
failed_dict = {}
|
||||
insert_data = [] # To store data for batch insert
|
||||
@ -33,55 +37,24 @@ def add_files_to_database(files, progress_callback=None):
|
||||
progress_callback.emit(filepath)
|
||||
filename = filepath.split("/")[-1]
|
||||
|
||||
audio, details = get_id3_tags(filepath)
|
||||
# print('got id3 tags')
|
||||
# print(type(audio))
|
||||
# print(audio)
|
||||
if not isinstance(audio, ID3):
|
||||
tags, details = get_id3_tags(filepath)
|
||||
if details:
|
||||
failed_dict[filepath] = details
|
||||
continue
|
||||
|
||||
try:
|
||||
title = audio["TIT2"].text[0]
|
||||
except KeyError:
|
||||
title = filename
|
||||
try:
|
||||
artist = audio["TPE1"].text[0]
|
||||
except KeyError:
|
||||
artist = ""
|
||||
try:
|
||||
album = audio["TALB"].text[0]
|
||||
except KeyError:
|
||||
album = ""
|
||||
try:
|
||||
track_number = audio["TRCK"].text[0]
|
||||
except KeyError:
|
||||
track_number = None
|
||||
try:
|
||||
genre = audio["TCON"].text[0]
|
||||
except KeyError:
|
||||
genre = ""
|
||||
try:
|
||||
date = convert_id3_timestamp_to_datetime(audio["TDRC"].text[0])
|
||||
except KeyError:
|
||||
date = ""
|
||||
try:
|
||||
bitrate = audio["TBIT"].text[0]
|
||||
except KeyError:
|
||||
bitrate = ""
|
||||
audio = id3_remap(tags)
|
||||
|
||||
# Append data tuple to insert_data list
|
||||
insert_data.append(
|
||||
(
|
||||
filepath,
|
||||
title,
|
||||
album,
|
||||
artist,
|
||||
track_number,
|
||||
genre,
|
||||
audio["title"],
|
||||
audio["album"],
|
||||
audio["artist"],
|
||||
audio["track_number"],
|
||||
audio["genre"],
|
||||
filename.split(".")[-1],
|
||||
date,
|
||||
bitrate,
|
||||
audio["date"],
|
||||
audio["bitrate"],
|
||||
)
|
||||
)
|
||||
# Check if batch size is reached
|
||||
|
||||
@ -1,31 +1,29 @@
|
||||
import os
|
||||
from logging import debug, error
|
||||
from mutagen.id3 import ID3
|
||||
from mutagen.mp3 import MP3
|
||||
from mutagen.flac import FLAC
|
||||
from mutagen.id3._frames import TIT2
|
||||
from mutagen.id3._util import ID3NoHeaderError
|
||||
|
||||
def get_mp3_tags(filename: str) -> tuple[ID3 | dict, str]:
|
||||
from utils import convert_id3_timestamp_to_datetime
|
||||
|
||||
|
||||
def get_mp3_tags(filename: str) -> tuple[MP3 | ID3 | FLAC, str]:
|
||||
"""Get ID3 tags for mp3 file"""
|
||||
try:
|
||||
# Open the MP3 file and read its content
|
||||
audio = ID3(filename)
|
||||
audio = MP3(filename)
|
||||
except ID3NoHeaderError:
|
||||
audio = ID3()
|
||||
audio = MP3()
|
||||
|
||||
try:
|
||||
if os.path.exists(filename):
|
||||
audio.save(os.path.abspath(filename))
|
||||
|
||||
# NOTE: If 'TIT2' tag is not set, we add it with a default value
|
||||
# title = filename without extension
|
||||
|
||||
title = os.path.splitext(os.path.basename(filename))[0]
|
||||
if "TIT2" in list(audio.keys()):
|
||||
audio.save()
|
||||
return audio, ""
|
||||
else:
|
||||
# if title tag doesnt exist,
|
||||
# create it and return
|
||||
if "TIT2" not in list(audio.keys()):
|
||||
# if title tag doesnt exist, create it - filename
|
||||
tit2_tag = TIT2(encoding=3, text=[title])
|
||||
audio["TIT2"] = tit2_tag
|
||||
# Save the updated tags
|
||||
@ -33,9 +31,26 @@ def get_mp3_tags(filename: str) -> tuple[ID3 | dict, str]:
|
||||
return audio, ""
|
||||
|
||||
except Exception as e:
|
||||
return {}, f"Could not assign ID3 tag to file: {e}"
|
||||
return MP3(), f"Could not assign ID3 tag to file: {e}"
|
||||
|
||||
def get_id3_tags(filename: str) -> tuple[ID3 | dict, str]:
|
||||
|
||||
def id3_remap(audio: MP3 | ID3 | FLAC) -> dict:
|
||||
"""
|
||||
Turns an ID3 dict into a normal dict that I the human can use.
|
||||
with words...
|
||||
"""
|
||||
return {
|
||||
"title": audio["TIT2"].text[0],
|
||||
"artist": audio["TPE1"].text[0],
|
||||
"album": audio["TALB"].text[0],
|
||||
"track_number": audio["TRCK"].text[0],
|
||||
"genre": audio["TCON"].text[0],
|
||||
"date": convert_id3_timestamp_to_datetime(audio["TDRC"].text[0]),
|
||||
"bitrate": audio["TBIT"].text[0],
|
||||
}
|
||||
|
||||
|
||||
def get_id3_tags(filename: str) -> tuple[MP3 | ID3 | FLAC, str]:
|
||||
"""
|
||||
Get the ID3 tags for an audio file
|
||||
Returns a tuple of:
|
||||
@ -52,7 +67,5 @@ def get_id3_tags(filename: str) -> tuple[ID3 | dict, str]:
|
||||
if filename.endswith(".mp3"):
|
||||
tags, details = get_mp3_tags(filename)
|
||||
else:
|
||||
tags, details = {}, "non mp3 file"
|
||||
tags, details = ID3(), "non mp3 file"
|
||||
return tags, details
|
||||
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ CREATE TABLE song(
|
||||
album varchar(255),
|
||||
artist varchar(255),
|
||||
track_number integer,
|
||||
length_seconds integer,
|
||||
genre varchar(255),
|
||||
codec varchar(15),
|
||||
album_date date,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user