Improve track creation in database

Pass all arguments to Tracks.__init__ on track creation
Smarten up metadata collecting
Reformat code
Reinstate stackprinter, but with more sensible settings (mostly
defaults, oddly enough)
This commit is contained in:
Keith Edmunds 2023-10-16 19:44:51 +01:00
parent 8cd8f80883
commit 3e2293195a
5 changed files with 99 additions and 69 deletions

View File

@ -148,6 +148,35 @@ def get_relative_date(
return f"{weeks} {weeks_str}, {days} {days_str} ago"
def get_file_metadata(filepath: str) -> dict:
"""Return track metadata"""
# Get title, artist, bitrate, duration, path
metadata: Dict[str, str | int | float] = get_tags(filepath)
metadata['mtime'] = os.path.getmtime(filepath)
# Set start_gap, fade_at and silence_at
audio = get_audio_segment(filepath)
if not audio:
audio_values = dict(
start_gap=0,
fade_at=0,
silence_at=0
)
else:
audio_values = dict(
start_gap=leading_silence(audio),
fade_at=int(round(fade_point(audio) / 1000, Config.MILLISECOND_SIGFIGS) * 1000),
silence_at=int(
round(trailing_silence(audio) / 1000, Config.MILLISECOND_SIGFIGS) * 1000
)
)
metadata |= audio_values
return metadata
def leading_silence(
audio_segment: AudioSegment,
silence_threshold: int = Config.DBFS_SILENCE,
@ -331,27 +360,13 @@ def open_in_audacity(path: str) -> bool:
return True
def set_track_metadata(session, track):
def set_track_metadata(track):
"""Set/update track metadata in database"""
t = get_tags(track.path)
audio = get_audio_segment(track.path)
metadata = get_file_metadata(track.path)
track.title = t["title"]
track.artist = t["artist"]
track.bitrate = t["bitrate"]
if not audio:
return
track.duration = len(audio)
track.start_gap = leading_silence(audio)
track.fade_at = round(fade_point(audio) / 1000, Config.MILLISECOND_SIGFIGS) * 1000
track.silence_at = (
round(trailing_silence(audio) / 1000, Config.MILLISECOND_SIGFIGS) * 1000
)
track.mtime = os.path.getmtime(track.path)
session.commit()
for key in metadata:
setattr(track, key, metadata[key])
def show_OK(parent: QMainWindow, title: str, msg: str) -> None:

View File

@ -3,7 +3,7 @@
import logging
import logging.handlers
import os
import stackprinter # type: ignore
import stackprinter # type: ignore
import sys
import traceback
@ -44,7 +44,7 @@ stderr = logging.StreamHandler()
stderr.setLevel(Config.LOG_LEVEL_STDERR)
# syslog
syslog = logging.handlers.SysLogHandler(address='/dev/log')
syslog = logging.handlers.SysLogHandler(address="/dev/log")
syslog.setLevel(Config.LOG_LEVEL_SYSLOG)
# Filter
@ -56,10 +56,11 @@ syslog.addFilter(local_filter)
stderr.addFilter(local_filter)
stderr.addFilter(debug_filter)
stderr_fmt = logging.Formatter('[%(asctime)s] %(leveltag)s: %(message)s',
datefmt='%H:%M:%S')
stderr_fmt = logging.Formatter(
"[%(asctime)s] %(leveltag)s: %(message)s", datefmt="%H:%M:%S"
)
syslog_fmt = logging.Formatter(
'[%(name)s] %(module)s.%(funcName)s - %(leveltag)s: %(message)s'
"[%(name)s] %(module)s.%(funcName)s - %(leveltag)s: %(message)s"
)
stderr.setFormatter(stderr_fmt)
syslog.setFormatter(syslog_fmt)
@ -69,18 +70,17 @@ log.addHandler(syslog)
def log_uncaught_exceptions(_ex_cls, ex, tb):
from helpers import send_mail
print("\033[1;31;47m")
logging.critical(''.join(traceback.format_tb(tb)))
logging.critical("".join(traceback.format_tb(tb)))
print("\033[1;37;40m")
# print(stackprinter.format(ex, show_vals="all", add_summary=True,
# style="darkbg"))
print(stackprinter.format(ex, style="darkbg"))
if os.environ["MM_ENV"] == "PRODUCTION":
msg = stackprinter.format(ex)
send_mail(Config.ERRORS_TO, Config.ERRORS_FROM,
"Exception from musicmuster", msg)
send_mail(
Config.ERRORS_TO, Config.ERRORS_FROM, "Exception from musicmuster", msg
)
sys.excepthook = log_uncaught_exceptions

View File

@ -360,7 +360,9 @@ class PlaylistRows(Base):
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
plr_rownum: Mapped[int]
note: Mapped[str] = mapped_column(String(2048), index=False, default="", nullable=False)
note: Mapped[str] = mapped_column(
String(2048), index=False, default="", nullable=False
)
playlist_id: Mapped[int] = mapped_column(ForeignKey("playlists.id"))
playlist: Mapped[Playlists] = relationship(back_populates="rows")
track_id: Mapped[Optional[int]] = mapped_column(ForeignKey("tracks.id"))
@ -368,7 +370,9 @@ class PlaylistRows(Base):
"Tracks",
back_populates="playlistrows",
)
played: Mapped[bool] = mapped_column(Boolean, nullable=False, index=False, default=False)
played: Mapped[bool] = mapped_column(
Boolean, nullable=False, index=False, default=False
)
def __repr__(self) -> str:
return (
@ -436,7 +440,9 @@ class PlaylistRows(Base):
session.flush()
@classmethod
def deep_rows(cls, session: scoped_session, playlist_id: int) -> Sequence["PlaylistRows"]:
def deep_rows(
cls, session: scoped_session, playlist_id: int
) -> Sequence["PlaylistRows"]:
"""
Return a list of playlist rows that include full track and lastplayed data for
given playlist_id., Sequence
@ -667,13 +673,13 @@ class Tracks(Base):
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
title: Mapped[str] = mapped_column(String(256), index=True)
artist: Mapped[str] = mapped_column(String(256), index=True)
duration: Mapped[int] = mapped_column(index=True)
start_gap: Mapped[int] = mapped_column(index=False)
fade_at: Mapped[int] = mapped_column(index=False)
silence_at: Mapped[int] = mapped_column(index=False)
path: Mapped[str] = mapped_column(String(2048), index=False, unique=True)
mtime: Mapped[float] = mapped_column(index=True)
bitrate: Mapped[Optional[int]] = mapped_column(default=None)
duration: Mapped[int] = mapped_column(index=True)
fade_at: Mapped[int] = mapped_column(index=False)
mtime: Mapped[float] = mapped_column(index=True)
path: Mapped[str] = mapped_column(String(2048), index=False, unique=True)
silence_at: Mapped[int] = mapped_column(index=False)
start_gap: Mapped[int] = mapped_column(index=False)
playlistrows: Mapped[List[PlaylistRows]] = relationship(
"PlaylistRows", back_populates="track"
)
@ -694,34 +700,31 @@ class Tracks(Base):
self,
session: scoped_session,
path: str,
title: Optional[str] = None,
artist: Optional[str] = None,
duration: int = 0,
start_gap: int = 0,
fade_at: Optional[int] = None,
silence_at: Optional[int] = None,
mtime: Optional[float] = None,
lastplayed: Optional[datetime] = None,
) -> None:
title: str,
artist: str,
duration: int,
start_gap: int,
fade_at: int,
silence_at: int,
mtime: int,
bitrate: int
):
self.path = path
self.title = title
self.artist = artist
self.bitrate = bitrate
self.duration = duration
self.start_gap = start_gap
self.fade_at = fade_at
self.silence_at = silence_at
self.mtime = mtime
self.lastplayed = lastplayed
try:
session.add(self)
session.commit()
except IntegrityError as error:
session.rollback()
log.error(
f"Error importing track ({title=}, "
f"{title=}, {artist=}, {path=}, {error=})"
)
log.error(f"Error ({error=}) importing track ({path=})")
raise ValueError
@classmethod

View File

@ -199,27 +199,31 @@ class ImportTrack(QObject):
importing = pyqtSignal(str)
finished = pyqtSignal(PlaylistTab)
def __init__(self, playlist: PlaylistTab, filenames: list) -> None:
def __init__(self, playlist: PlaylistTab, filenames: list, row: int) -> None:
super().__init__()
self.filenames = filenames
self.playlist = playlist
self.row = row
def run(self):
"""
Create track objects from passed files and add to visible playlist
"""
target_row = self.row
with Session() as session:
for fname in self.filenames:
self.importing.emit(f"Importing {basename(fname)}")
metadata = helpers.get_file_metadata(fname)
try:
track = Tracks(session, fname)
except ValueError:
self.import_error.emit(basename(fname))
continue
helpers.set_track_metadata(session, track)
track = Tracks(session, **metadata)
except Exception as e:
print(e)
return
helpers.normalise_track(track.path)
self.playlist.insert_track(session, track)
self.playlist.insert_track(session, track, target_row)
# Insert next row under this one
target_row += 1
# We're importing potentially multiple tracks in a loop.
# If there's an error adding the track to the Tracks
# table, the session will rollback, thus losing any
@ -958,7 +962,6 @@ class Window(QMainWindow, Ui_MainWindow):
for fname in dlg.selectedFiles():
txt = ""
tags = helpers.get_tags(fname)
new_tracks.append(fname)
title = tags["title"]
artist = tags["artist"]
count = 0
@ -984,11 +987,16 @@ class Window(QMainWindow, Ui_MainWindow):
QMessageBox.StandardButton.Cancel,
)
if result == QMessageBox.StandardButton.Cancel:
return
continue
new_tracks.append(fname)
# Import in separate thread
self.import_thread = QThread()
self.worker = ImportTrack(self.visible_playlist_tab(), new_tracks)
self.worker = ImportTrack(
self.visible_playlist_tab(),
new_tracks,
self.visible_playlist_tab().get_new_row_number(),
)
self.worker.moveToThread(self.import_thread)
self.import_thread.started.connect(self.worker.run)
self.worker.finished.connect(self.import_thread.quit)
@ -1113,7 +1121,7 @@ class Window(QMainWindow, Ui_MainWindow):
visible_tab.save_playlist(session)
# Disable sort undo
self.sort_undo = []
self.sort_undo: List[int] = []
# Update destination playlist_tab if visible (if not visible, it
# will be re-populated when it is opened)
@ -2170,7 +2178,7 @@ if __name__ == "__main__":
"Exception from musicmuster",
msg,
)
# print("\033[1;31;47mUnhandled exception starts")
# stackprinter.show(style="darkbg")
# print("Unhandled exception ends\033[1;37;40m")
else:
print("\033[1;31;47mUnhandled exception starts")
print(stackprinter.format(exc, style="darkbg"))
print("Unhandled exception ends\033[1;37;40m")

View File

@ -642,6 +642,7 @@ class PlaylistTab(QTableWidget):
track: Tracks,
note: str = "",
repaint: bool = True,
target_row: Optional[int] = None,
) -> None:
"""
Insert track into playlist tab.
@ -660,7 +661,10 @@ class PlaylistTab(QTableWidget):
)
return
row_number = self.get_new_row_number()
if target_row:
row_number = target_row
else:
row_number = self.get_new_row_number()
# Check to see whether track is already in playlist
existing_plr = PlaylistRows.get_track_plr(session, track.id, self.playlist_id)
@ -1713,7 +1717,7 @@ class PlaylistTab(QTableWidget):
self._set_row_colour_unreadable(row_number)
else:
self._set_row_colour_default(row_number)
set_track_metadata(session, track)
set_track_metadata(track)
self._update_row_track_info(session, row_number, track)
else:
_ = self._set_row_track_id(row_number, 0)