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"
|
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(
|
def leading_silence(
|
||||||
audio_segment: AudioSegment,
|
audio_segment: AudioSegment,
|
||||||
silence_threshold: int = Config.DBFS_SILENCE,
|
silence_threshold: int = Config.DBFS_SILENCE,
|
||||||
@ -331,27 +360,13 @@ def open_in_audacity(path: str) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def set_track_metadata(session, track):
|
def set_track_metadata(track):
|
||||||
"""Set/update track metadata in database"""
|
"""Set/update track metadata in database"""
|
||||||
|
|
||||||
t = get_tags(track.path)
|
metadata = get_file_metadata(track.path)
|
||||||
audio = get_audio_segment(track.path)
|
|
||||||
|
|
||||||
track.title = t["title"]
|
for key in metadata:
|
||||||
track.artist = t["artist"]
|
setattr(track, key, metadata[key])
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
||||||
def show_OK(parent: QMainWindow, title: str, msg: str) -> None:
|
def show_OK(parent: QMainWindow, title: str, msg: str) -> None:
|
||||||
|
|||||||
20
app/log.py
20
app/log.py
@ -44,7 +44,7 @@ stderr = logging.StreamHandler()
|
|||||||
stderr.setLevel(Config.LOG_LEVEL_STDERR)
|
stderr.setLevel(Config.LOG_LEVEL_STDERR)
|
||||||
|
|
||||||
# syslog
|
# syslog
|
||||||
syslog = logging.handlers.SysLogHandler(address='/dev/log')
|
syslog = logging.handlers.SysLogHandler(address="/dev/log")
|
||||||
syslog.setLevel(Config.LOG_LEVEL_SYSLOG)
|
syslog.setLevel(Config.LOG_LEVEL_SYSLOG)
|
||||||
|
|
||||||
# Filter
|
# Filter
|
||||||
@ -56,10 +56,11 @@ syslog.addFilter(local_filter)
|
|||||||
stderr.addFilter(local_filter)
|
stderr.addFilter(local_filter)
|
||||||
stderr.addFilter(debug_filter)
|
stderr.addFilter(debug_filter)
|
||||||
|
|
||||||
stderr_fmt = logging.Formatter('[%(asctime)s] %(leveltag)s: %(message)s',
|
stderr_fmt = logging.Formatter(
|
||||||
datefmt='%H:%M:%S')
|
"[%(asctime)s] %(leveltag)s: %(message)s", datefmt="%H:%M:%S"
|
||||||
|
)
|
||||||
syslog_fmt = logging.Formatter(
|
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)
|
stderr.setFormatter(stderr_fmt)
|
||||||
syslog.setFormatter(syslog_fmt)
|
syslog.setFormatter(syslog_fmt)
|
||||||
@ -69,18 +70,17 @@ log.addHandler(syslog)
|
|||||||
|
|
||||||
|
|
||||||
def log_uncaught_exceptions(_ex_cls, ex, tb):
|
def log_uncaught_exceptions(_ex_cls, ex, tb):
|
||||||
|
|
||||||
from helpers import send_mail
|
from helpers import send_mail
|
||||||
|
|
||||||
print("\033[1;31;47m")
|
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("\033[1;37;40m")
|
||||||
# print(stackprinter.format(ex, show_vals="all", add_summary=True,
|
print(stackprinter.format(ex, style="darkbg"))
|
||||||
# style="darkbg"))
|
|
||||||
if os.environ["MM_ENV"] == "PRODUCTION":
|
if os.environ["MM_ENV"] == "PRODUCTION":
|
||||||
msg = stackprinter.format(ex)
|
msg = stackprinter.format(ex)
|
||||||
send_mail(Config.ERRORS_TO, Config.ERRORS_FROM,
|
send_mail(
|
||||||
"Exception from musicmuster", msg)
|
Config.ERRORS_TO, Config.ERRORS_FROM, "Exception from musicmuster", msg
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
sys.excepthook = log_uncaught_exceptions
|
sys.excepthook = log_uncaught_exceptions
|
||||||
|
|||||||
@ -360,7 +360,9 @@ class PlaylistRows(Base):
|
|||||||
|
|
||||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||||
plr_rownum: Mapped[int]
|
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_id: Mapped[int] = mapped_column(ForeignKey("playlists.id"))
|
||||||
playlist: Mapped[Playlists] = relationship(back_populates="rows")
|
playlist: Mapped[Playlists] = relationship(back_populates="rows")
|
||||||
track_id: Mapped[Optional[int]] = mapped_column(ForeignKey("tracks.id"))
|
track_id: Mapped[Optional[int]] = mapped_column(ForeignKey("tracks.id"))
|
||||||
@ -368,7 +370,9 @@ class PlaylistRows(Base):
|
|||||||
"Tracks",
|
"Tracks",
|
||||||
back_populates="playlistrows",
|
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:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
@ -436,7 +440,9 @@ class PlaylistRows(Base):
|
|||||||
session.flush()
|
session.flush()
|
||||||
|
|
||||||
@classmethod
|
@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
|
Return a list of playlist rows that include full track and lastplayed data for
|
||||||
given playlist_id., Sequence
|
given playlist_id., Sequence
|
||||||
@ -667,13 +673,13 @@ class Tracks(Base):
|
|||||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||||
title: Mapped[str] = mapped_column(String(256), index=True)
|
title: Mapped[str] = mapped_column(String(256), index=True)
|
||||||
artist: 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)
|
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: Mapped[List[PlaylistRows]] = relationship(
|
||||||
"PlaylistRows", back_populates="track"
|
"PlaylistRows", back_populates="track"
|
||||||
)
|
)
|
||||||
@ -694,34 +700,31 @@ class Tracks(Base):
|
|||||||
self,
|
self,
|
||||||
session: scoped_session,
|
session: scoped_session,
|
||||||
path: str,
|
path: str,
|
||||||
title: Optional[str] = None,
|
title: str,
|
||||||
artist: Optional[str] = None,
|
artist: str,
|
||||||
duration: int = 0,
|
duration: int,
|
||||||
start_gap: int = 0,
|
start_gap: int,
|
||||||
fade_at: Optional[int] = None,
|
fade_at: int,
|
||||||
silence_at: Optional[int] = None,
|
silence_at: int,
|
||||||
mtime: Optional[float] = None,
|
mtime: int,
|
||||||
lastplayed: Optional[datetime] = None,
|
bitrate: int
|
||||||
) -> None:
|
):
|
||||||
self.path = path
|
self.path = path
|
||||||
self.title = title
|
self.title = title
|
||||||
self.artist = artist
|
self.artist = artist
|
||||||
|
self.bitrate = bitrate
|
||||||
self.duration = duration
|
self.duration = duration
|
||||||
self.start_gap = start_gap
|
self.start_gap = start_gap
|
||||||
self.fade_at = fade_at
|
self.fade_at = fade_at
|
||||||
self.silence_at = silence_at
|
self.silence_at = silence_at
|
||||||
self.mtime = mtime
|
self.mtime = mtime
|
||||||
self.lastplayed = lastplayed
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
session.add(self)
|
session.add(self)
|
||||||
session.commit()
|
session.commit()
|
||||||
except IntegrityError as error:
|
except IntegrityError as error:
|
||||||
session.rollback()
|
session.rollback()
|
||||||
log.error(
|
log.error(f"Error ({error=}) importing track ({path=})")
|
||||||
f"Error importing track ({title=}, "
|
|
||||||
f"{title=}, {artist=}, {path=}, {error=})"
|
|
||||||
)
|
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@ -199,27 +199,31 @@ class ImportTrack(QObject):
|
|||||||
importing = pyqtSignal(str)
|
importing = pyqtSignal(str)
|
||||||
finished = pyqtSignal(PlaylistTab)
|
finished = pyqtSignal(PlaylistTab)
|
||||||
|
|
||||||
def __init__(self, playlist: PlaylistTab, filenames: list) -> None:
|
def __init__(self, playlist: PlaylistTab, filenames: list, row: int) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.filenames = filenames
|
self.filenames = filenames
|
||||||
self.playlist = playlist
|
self.playlist = playlist
|
||||||
|
self.row = row
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
Create track objects from passed files and add to visible playlist
|
Create track objects from passed files and add to visible playlist
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
target_row = self.row
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
for fname in self.filenames:
|
for fname in self.filenames:
|
||||||
self.importing.emit(f"Importing {basename(fname)}")
|
self.importing.emit(f"Importing {basename(fname)}")
|
||||||
|
metadata = helpers.get_file_metadata(fname)
|
||||||
try:
|
try:
|
||||||
track = Tracks(session, fname)
|
track = Tracks(session, **metadata)
|
||||||
except ValueError:
|
except Exception as e:
|
||||||
self.import_error.emit(basename(fname))
|
print(e)
|
||||||
continue
|
return
|
||||||
helpers.set_track_metadata(session, track)
|
|
||||||
helpers.normalise_track(track.path)
|
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.
|
# We're importing potentially multiple tracks in a loop.
|
||||||
# If there's an error adding the track to the Tracks
|
# If there's an error adding the track to the Tracks
|
||||||
# table, the session will rollback, thus losing any
|
# table, the session will rollback, thus losing any
|
||||||
@ -958,7 +962,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
for fname in dlg.selectedFiles():
|
for fname in dlg.selectedFiles():
|
||||||
txt = ""
|
txt = ""
|
||||||
tags = helpers.get_tags(fname)
|
tags = helpers.get_tags(fname)
|
||||||
new_tracks.append(fname)
|
|
||||||
title = tags["title"]
|
title = tags["title"]
|
||||||
artist = tags["artist"]
|
artist = tags["artist"]
|
||||||
count = 0
|
count = 0
|
||||||
@ -984,11 +987,16 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
QMessageBox.StandardButton.Cancel,
|
QMessageBox.StandardButton.Cancel,
|
||||||
)
|
)
|
||||||
if result == QMessageBox.StandardButton.Cancel:
|
if result == QMessageBox.StandardButton.Cancel:
|
||||||
return
|
continue
|
||||||
|
new_tracks.append(fname)
|
||||||
|
|
||||||
# Import in separate thread
|
# Import in separate thread
|
||||||
self.import_thread = QThread()
|
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.worker.moveToThread(self.import_thread)
|
||||||
self.import_thread.started.connect(self.worker.run)
|
self.import_thread.started.connect(self.worker.run)
|
||||||
self.worker.finished.connect(self.import_thread.quit)
|
self.worker.finished.connect(self.import_thread.quit)
|
||||||
@ -1113,7 +1121,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
visible_tab.save_playlist(session)
|
visible_tab.save_playlist(session)
|
||||||
|
|
||||||
# Disable sort undo
|
# Disable sort undo
|
||||||
self.sort_undo = []
|
self.sort_undo: List[int] = []
|
||||||
|
|
||||||
# Update destination playlist_tab if visible (if not visible, it
|
# Update destination playlist_tab if visible (if not visible, it
|
||||||
# will be re-populated when it is opened)
|
# will be re-populated when it is opened)
|
||||||
@ -2170,7 +2178,7 @@ if __name__ == "__main__":
|
|||||||
"Exception from musicmuster",
|
"Exception from musicmuster",
|
||||||
msg,
|
msg,
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
# print("\033[1;31;47mUnhandled exception starts")
|
print("\033[1;31;47mUnhandled exception starts")
|
||||||
# stackprinter.show(style="darkbg")
|
print(stackprinter.format(exc, style="darkbg"))
|
||||||
# print("Unhandled exception ends\033[1;37;40m")
|
print("Unhandled exception ends\033[1;37;40m")
|
||||||
|
|||||||
@ -642,6 +642,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
track: Tracks,
|
track: Tracks,
|
||||||
note: str = "",
|
note: str = "",
|
||||||
repaint: bool = True,
|
repaint: bool = True,
|
||||||
|
target_row: Optional[int] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Insert track into playlist tab.
|
Insert track into playlist tab.
|
||||||
@ -660,6 +661,9 @@ class PlaylistTab(QTableWidget):
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if target_row:
|
||||||
|
row_number = target_row
|
||||||
|
else:
|
||||||
row_number = self.get_new_row_number()
|
row_number = self.get_new_row_number()
|
||||||
|
|
||||||
# Check to see whether track is already in playlist
|
# Check to see whether track is already in playlist
|
||||||
@ -1713,7 +1717,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
self._set_row_colour_unreadable(row_number)
|
self._set_row_colour_unreadable(row_number)
|
||||||
else:
|
else:
|
||||||
self._set_row_colour_default(row_number)
|
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)
|
self._update_row_track_info(session, row_number, track)
|
||||||
else:
|
else:
|
||||||
_ = self._set_row_track_id(row_number, 0)
|
_ = self._set_row_track_id(row_number, 0)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user