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:
parent
8cd8f80883
commit
3e2293195a
@ -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:
|
||||
|
||||
22
app/log.py
22
app/log.py
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user