From 37e450ab22aaade2356c67f2d65184d8f995cb0a Mon Sep 17 00:00:00 2001 From: Keith Edmunds Date: Fri, 10 May 2024 11:48:40 +0100 Subject: [PATCH 1/3] Bugfix replace files Fixes #243 --- app/classes.py | 3 ++- app/dialogs.py | 45 ++++++++++++++++++++++++++++----------------- app/musicmuster.py | 33 +++++++++++++++++++++------------ 3 files changed, 51 insertions(+), 30 deletions(-) diff --git a/app/classes.py b/app/classes.py index 25d8102..fda81b0 100644 --- a/app/classes.py +++ b/app/classes.py @@ -209,9 +209,10 @@ class TrackFileData: Simple class to track details changes to a track file """ - source_file_path: str + new_file_path: str track_id: int = 0 track_path: Optional[str] = None + obsolete_path: Optional[str] = None tags: dict[str, Any] = field(default_factory=dict) audio_metadata: dict[str, str | int | float] = field(default_factory=dict) diff --git a/app/dialogs.py b/app/dialogs.py index 75ff953..36ec626 100644 --- a/app/dialogs.py +++ b/app/dialogs.py @@ -78,18 +78,18 @@ class ReplaceFilesDialog(QDialog): # Work through new files source_dir = self.ui.lblSourceDirectory.text() with db.Session() as session: - for new_basename in os.listdir(source_dir): - new_path = os.path.join(source_dir, new_basename) - if not os.path.isfile(new_path): + for new_file_basename in os.listdir(source_dir): + new_file_path = os.path.join(source_dir, new_file_basename) + if not os.path.isfile(new_file_path): continue - rf = TrackFileData(source_file_path=new_path) - rf.tags = get_tags(new_path) + rf = TrackFileData(new_file_path=new_file_path) + rf.tags = get_tags(new_file_path) if not rf.tags['title'] or not rf.tags['artist']: show_warning( parent=self.main_window, title="Error", msg=( - f"File {new_path} missing tags\n\n:" + f"File {new_file_path} missing tags\n\n:" f"Title={rf.tags['title']}\n" f"Artist={rf.tags['artist']}\n" ), @@ -98,41 +98,52 @@ class ReplaceFilesDialog(QDialog): # Check for same filename match_track = self.check_by_basename( - session, new_path, rf.tags['artist'], rf.tags['title'] + session, new_file_path, rf.tags['artist'], rf.tags['title'] ) if not match_track: match_track = self.check_by_title( - session, new_path, rf.tags['artist'], rf.tags['title'] + session, new_file_path, rf.tags['artist'], rf.tags['title'] ) + if not match_track: - match_track = self.get_fuzzy_match(session, new_basename) + match_track = self.get_fuzzy_match(session, new_file_basename) # Build summary - rf.track_path = os.path.join(Config.REPLACE_FILES_DEFAULT_DESTINATION, - new_basename) if match_track: + # We will store new file in the same directory as the + # existing file but with the new file name + rf.track_path = os.path.join( + os.path.dirname(match_track.path), + new_file_basename + ) + + # We will remove existing track file + rf.obsolete_path = match_track.path + rf.track_id = match_track.id match_basename = os.path.basename(match_track.path) - if match_basename == new_basename: - path_text = " " + new_basename + " (no change)" + if match_basename == new_file_basename: + path_text = " " + new_file_basename + " (no change)" else: - path_text = f" {match_basename} →\n {new_basename}" + path_text = f" {match_basename} →\n {new_file_basename} (replace)" filename_item = QTableWidgetItem(path_text) if match_track.title == rf.tags['title']: title_text = " " + rf.tags['title'] + " (no change)" else: - title_text = f" {match_track.title} →\n {rf.tags['title']}" + title_text = f" {match_track.title} →\n {rf.tags['title']} (update)" title_item = QTableWidgetItem(title_text) if match_track.artist == rf.tags['artist']: artist_text = " " + rf.tags['artist'] + " (no change)" else: - artist_text = f" {match_track.artist} →\n {rf.tags['artist']}" + artist_text = f" {match_track.artist} →\n {rf.tags['artist']} (update)" artist_item = QTableWidgetItem(artist_text) else: - filename_item = QTableWidgetItem(" " + new_basename + " (new)") + rf.track_path = os.path.join(Config.REPLACE_FILES_DEFAULT_DESTINATION, + new_file_basename) + filename_item = QTableWidgetItem(" " + new_file_basename + " (new)") title_item = QTableWidgetItem(" " + rf.tags['title']) artist_item = QTableWidgetItem(" " + rf.tags['artist']) diff --git a/app/musicmuster.py b/app/musicmuster.py index 32d068a..04d5db6 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -165,13 +165,13 @@ class ImportTrack(QObject): # Sanity check for tf in track_files: if not tf.tags: - raise Exception(f"ImportTrack: no tags for {tf.source_file_path}") + raise Exception(f"ImportTrack: no tags for {tf.new_file_path}") if not tf.audio_metadata: raise Exception( - f"ImportTrack: no audio_metadata for {tf.source_file_path}" + f"ImportTrack: no audio_metadata for {tf.new_file_path}" ) if tf.track_path is None: - raise Exception(f"ImportTrack: no track_path for {tf.source_file_path}") + raise Exception(f"ImportTrack: no track_path for {tf.new_file_path}") def run(self): """ @@ -181,19 +181,19 @@ class ImportTrack(QObject): with db.Session() as session: for tf in self.track_files: self.signals.status_message_signal.emit( - f"Importing {basename(tf.source_file_path)}", 5000 + f"Importing {basename(tf.new_file_path)}", 5000 ) # Sanity check - if not os.path.exists(tf.source_file_path): - log.error(f"ImportTrack: file not found: {tf.source_file_path=}") + if not os.path.exists(tf.new_file_path): + log.error(f"ImportTrack: file not found: {tf.new_file_path=}") continue # Move the track file. Check that we're not importing a # file that's already in its final destination. - if os.path.exists(tf.track_path) and tf.track_path != tf.source_file_path: + if os.path.exists(tf.track_path) and tf.track_path != tf.new_file_path: os.unlink(tf.track_path) - shutil.move(tf.source_file_path, tf.track_path) + shutil.move(tf.new_file_path, tf.track_path) # Import track try: @@ -1305,10 +1305,18 @@ class Window(QMainWindow, Ui_MainWindow): for rf in dlg.replacement_files: if rf.track_id: # We're updating an existing track + # If the filename has changed, remove the + # existing file + if rf.obsolete_path is not None: + if os.path.exists(rf.obsolete_path): + os.unlink(rf.obsolete_path) + else: + log.error(f"replace_files: could not unlink {rf.obsolete_path=}") + continue if rf.track_path: if os.path.exists(rf.track_path): os.unlink(rf.track_path) - shutil.move(rf.source_file_path, rf.track_path) + shutil.move(rf.new_file_path, rf.track_path) track = session.get(Tracks, rf.track_id) if not track: raise Exception( @@ -1327,16 +1335,17 @@ class Window(QMainWindow, Ui_MainWindow): track.path = "DUMMY" session.commit() track.path = rf.track_path - session.commit() + else: + session.commit() else: # We're importing a new track do_import = self.ok_to_import( session, - os.path.basename(rf.source_file_path), + os.path.basename(rf.new_file_path), rf.tags ) if do_import: - rf.audio_metadata = helpers.get_audio_metadata(rf.source_file_path) + rf.audio_metadata = helpers.get_audio_metadata(rf.new_file_path) import_files.append(rf) # self.import_filenames(dlg.replacement_files) From f825304de44883ca84e7e791d42e428c543cfb85 Mon Sep 17 00:00:00 2001 From: Keith Edmunds Date: Fri, 10 May 2024 12:06:04 +0100 Subject: [PATCH 2/3] Update track times after rescan Fixes #242 --- app/playlistmodel.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/playlistmodel.py b/app/playlistmodel.py index 75bce7e..4d16088 100644 --- a/app/playlistmodel.py +++ b/app/playlistmodel.py @@ -1122,6 +1122,7 @@ class PlaylistModel(QAbstractTableModel): track = session.get(Tracks, track_id) set_track_metadata(track) self.refresh_row(session, row_number) + self.update_track_times() self.invalidate_row(row_number) self.signals.resize_rows_signal.emit(self.playlist_id) session.commit() From a7932adfe4dd380d06e6ecbd3a83907e5a67d862 Mon Sep 17 00:00:00 2001 From: Keith Edmunds Date: Fri, 10 May 2024 12:48:39 +0100 Subject: [PATCH 3/3] Add more protection against hitting return twice --- app/config.py | 1 + app/musicmuster.py | 25 +++++++++++++++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/app/config.py b/app/config.py index 1d94487..48f4706 100644 --- a/app/config.py +++ b/app/config.py @@ -78,6 +78,7 @@ class Config(object): OBS_HOST = "localhost" OBS_PASSWORD = "auster" OBS_PORT = 4455 + PLAY_NEXT_GUARD_MS = 10000 PLAY_SETTLE = 500000 REPLACE_FILES_DEFAULT_SOURCE = "/home/kae/music/Singles/tmp" RETURN_KEY_DEBOUNCE_MS = 500 diff --git a/app/musicmuster.py b/app/musicmuster.py index 04d5db6..310378f 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -1164,10 +1164,21 @@ class Window(QMainWindow, Ui_MainWindow): > dt.datetime.now() ): return + # If return is pressed during first PLAY_NEXT_GUARD_MS then + # default to NOT playing the next track, else default to + # playing it. + default_yes: bool = ( + track_sequence.now.start_time is not None + and ( + (dt.datetime.now() - track_sequence.now.start_time).total_seconds() + * 1000 + > Config.PLAY_NEXT_GUARD_MS + ) + ) if not helpers.ask_yes_no( "Track playing", "Really play next track now?", - default_yes=True, + default_yes=default_yes, parent=self, ): return @@ -1311,7 +1322,9 @@ class Window(QMainWindow, Ui_MainWindow): if os.path.exists(rf.obsolete_path): os.unlink(rf.obsolete_path) else: - log.error(f"replace_files: could not unlink {rf.obsolete_path=}") + log.error( + f"replace_files: could not unlink {rf.obsolete_path=}" + ) continue if rf.track_path: if os.path.exists(rf.track_path): @@ -1340,12 +1353,12 @@ class Window(QMainWindow, Ui_MainWindow): else: # We're importing a new track do_import = self.ok_to_import( - session, - os.path.basename(rf.new_file_path), - rf.tags + session, os.path.basename(rf.new_file_path), rf.tags ) if do_import: - rf.audio_metadata = helpers.get_audio_metadata(rf.new_file_path) + rf.audio_metadata = helpers.get_audio_metadata( + rf.new_file_path + ) import_files.append(rf) # self.import_filenames(dlg.replacement_files)