diff --git a/app/config.py b/app/config.py index 39cefa7..9692eca 100644 --- a/app/config.py +++ b/app/config.py @@ -52,7 +52,6 @@ class Config(object): HEADER_TITLE = "Title" HIDE_AFTER_PLAYING_OFFSET = 5000 INFO_TAB_TITLE_LENGTH = 15 - INTRO_END_GAP_MS = 1000 INTRO_SECONDS_FORMAT = ".1f" INTRO_SECONDS_WARNING_MS = 3000 LAST_PLAYED_TODAY_STRING = "Today" @@ -78,6 +77,7 @@ class Config(object): PLAY_SETTLE = 500000 PREVIEW_ADVANCE_MS = 5000 PREVIEW_BACK_MS = 5000 + PREVIEW_END_BUFFER_MS = 1000 REPLACE_FILES_DEFAULT_SOURCE = "/home/kae/music/Singles/tmp" RETURN_KEY_DEBOUNCE_MS = 500 ROOT = os.environ.get("ROOT") or "/home/kae/music" diff --git a/app/musicmuster.py b/app/musicmuster.py index 4dcc93e..5f435f2 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -1062,22 +1062,30 @@ class Window(QMainWindow, Ui_MainWindow): if self.btnPreview.isChecked(): # Get track_id for first selected track if there is one - track_id = self.active_tab().get_selected_row_track_id() - if not track_id: - # Otherwise get path to next track to play + row_number_and_track_id = self.active_tab().get_selected_row_and_track_id() + if row_number_and_track_id: + row_number, track_id = row_number_and_track_id + else: + # Otherwise get track_id to next track to play if track_sequence.next: track_id = track_sequence.next.track_id - if not track_id: - self.btnPreview.setChecked(False) - return + row_number = track_sequence.next.row_number + if not track_id or row_number is None: + self.btnPreview.setChecked(False) + return + with db.Session() as session: - self.preview_track_player = PreviewTrackManager(session, track_id) + self.preview_track_player = PreviewTrackManager( + session=session, track_id=track_id, row_number=row_number + ) self.preview_track_player.play() else: if self.preview_track_player: - self.preview_track_player.stop_playing() + self.preview_track_player.stop() + self.preview_track_player = None self.label_intro_timer.setText("0.0") + self.label_intro_timer.setStyleSheet("") self.btnPreviewMark.setEnabled(False) self.btnPreviewArm.setChecked(False) @@ -1089,51 +1097,45 @@ class Window(QMainWindow, Ui_MainWindow): def preview_back(self) -> None: """Wind back preview file""" - self.preview_track_player.move_back(Config.PREVIEW_BACK_MS) + if self.preview_track_player: + self.preview_track_player.move_back() def preview_end(self) -> None: - """Advance preview file to just before end of intro""" + """Advance preview file to Config.PREVIEW_END_BUFFER_MS before end of intro""" - return - - # preview_track_path = self.preview_player.path - # if not preview_track_path: - # return - - # with Session() as session: - # preview_track = Tracks.get_by_path(session, preview_track_path) - # if not preview_track or not preview_track.intro: - # return - - # new_position = max(0, preview_track.intro - Config.INTRO_END_GAP_MS) - # self.preview_player.set_position(new_position) + if self.preview_track_player: + self.preview_track_player.move_to_intro_end() def preview_fwd(self) -> None: """Advance preview file""" - self.preview_player.move_forward(Config.PREVIEW_ADVANCE_MS) + if self.preview_track_player: + self.preview_track_player.move_forward() def preview_mark(self) -> None: """Set intro time""" - track_id = self.active_tab().get_selected_row_track_id() - row_number = self.active_tab().get_selected_row() - if track_id: + if self.preview_track_player: + track_id = self.preview_track_player.track_id + row_number = self.preview_track_player.row_number with db.Session() as session: track = session.get(Tracks, track_id) if track: # Save intro as millisends rounded to nearest 0.1 # second because editor spinbox only resolves to 0.1 # seconds - track.intro = round(self.preview_player.get_playtime() / 100) * 100 + intro = round(self.preview_track_player.time_playing() / 100) * 100 + track.intro = intro session.commit() + self.preview_track_player.intro = intro self.active_tab().source_model.refresh_row(session, row_number) self.active_tab().source_model.invalidate_row(row_number) def preview_start(self) -> None: - """Advance preview file""" + """Restart preview""" - self.preview_track_player.set_position(0) + if self.preview_track_player: + self.preview_track_player.restart() def rename_playlist(self) -> None: """ @@ -1504,6 +1506,24 @@ class Window(QMainWindow, Ui_MainWindow): # currnent track ended during servicing tick pass + # Ensure preview button is reset if preview finishes playing + if self.preview_track_player: + self.btnPreview.setChecked(self.preview_track_player.is_playing()) + + # Update preview timer + if self.preview_track_player.is_playing(): + playtime = self.preview_track_player.time_playing() + self.label_intro_timer.setText(f"{playtime / 1000:.1f}") + if self.preview_track_player.time_remaining_intro() <= 50: + self.label_intro_timer.setStyleSheet( + f"background: {Config.COLOUR_WARNING_TIMER}" + ) + else: + self.label_intro_timer.setStyleSheet("") + else: + self.label_intro_timer.setText("0.0") + self.label_intro_timer.setStyleSheet("") + self.btnPreview.setChecked(False) def tick_1000ms(self) -> None: """ diff --git a/app/playlists.py b/app/playlists.py index 222fce0..1a117f2 100644 --- a/app/playlists.py +++ b/app/playlists.py @@ -631,21 +631,23 @@ class PlaylistTab(QTableView): self.source_model.delete_rows(self.selected_model_row_numbers()) self.clear_selection() - def get_selected_row_track_id(self) -> Optional[int]: + def get_selected_row_and_track_id(self) -> Optional[tuple[int, int]]: """ - Return the track_id of the selected row. If no row selected or selected + Return the (row_number, track_id) of the selected row. If no row selected or selected row does not have a track, return None. """ - log.debug("get_selected_row_track_id() called") - - model_row_number = self.source_model_selected_row_number() - if model_row_number is None: + row_number = self.source_model_selected_row_number() + if row_number is None: result = None else: - result = self.source_model.get_row_track_id(model_row_number) + track_id = self.source_model.get_row_track_id(row_number) + if not track_id: + result = None + else: + result = (row_number, track_id) - log.debug(f"get_selected_row_track_id() returned: {result=}") + log.debug(f"get_selected_row_and_track_id() returned: {result=}") return result diff --git a/app/trackmanager.py b/app/trackmanager.py index 974a3cb..b458653 100644 --- a/app/trackmanager.py +++ b/app/trackmanager.py @@ -170,7 +170,7 @@ class _Music: self.max_volume = Config.VLC_VOLUME_DEFAULT self.start_dt: Optional[dt.datetime] = None - def _adjust_by_ms(self, ms: int) -> None: + def adjust_by_ms(self, ms: int) -> None: """Move player position by ms milliseconds""" if not self.player: @@ -275,20 +275,6 @@ class _Music: < dt.timedelta(microseconds=Config.PLAY_SETTLE) ) - def move_back(self, ms: int) -> None: - """ - Rewind player by ms milliseconds - """ - - self._adjust_by_ms(ms * -1) - - def move_forward(self, ms: int) -> None: - """ - Rewind player by ms milliseconds - """ - - self._adjust_by_ms(ms) - def play( self, path: str, @@ -304,7 +290,7 @@ class _Music: the start time is the same """ - log.info(f"Music[{self.name}].play({path=}, {position=}") + log.debug(f"Music[{self.name}].play({path=}, {position=}") if file_is_unreadable(path): log.error(f"play({path}): path not readable") @@ -374,7 +360,7 @@ class _TrackManager: typically the previous, current and next track. """ - def __init__(self, session: db.Session, player_name: str, track_id: int) -> None: + def __init__(self, session: db.Session, player_name: str, track_id: int, row_number: int) -> None: """ Initialises data structure. Define a player. @@ -385,6 +371,7 @@ class _TrackManager: if not track: raise ValueError(f"_TrackPlayer: unable to retreived {track_id=}") self.player_name = player_name + self.row_number = row_number self.artist = track.artist self.bitrate = track.bitrate @@ -462,6 +449,33 @@ class _TrackManager: return self.player.is_playing() + def move_back(self, ms: int = Config.PREVIEW_BACK_MS) -> None: + """ + Rewind player by ms milliseconds + """ + + self.player.adjust_by_ms(ms * -1) + + def move_forward(self, ms: int = Config.PREVIEW_ADVANCE_MS) -> None: + """ + Rewind player by ms milliseconds + """ + + self.player.adjust_by_ms(ms) + + def move_to_intro_end(self, buffer: int = Config.PREVIEW_END_BUFFER_MS) -> None: + """ + Move play position to 'buffer' milliseconds before end of intro. + + If no intro defined, do nothing. + """ + + if self.intro is None: + return + + new_position = max(0, self.intro - Config.PREVIEW_END_BUFFER_MS) + self.player.adjust_by_ms(new_position - self.time_playing()) + def play(self, position: Optional[float] = None) -> None: """Play track""" @@ -481,6 +495,13 @@ class _TrackManager: milliseconds=update_graph_at_ms ) + def restart(self) -> None: + """ + Restart player + """ + + self.player.adjust_by_ms(self.time_playing() * -1) + def stop(self, fade_seconds: int = 0) -> None: """ Stop this track playing @@ -575,12 +596,12 @@ class MainTrackManager(_TrackManager): session=session, player_name=Config.VLC_MAIN_PLAYER_NAME, track_id=self.track_id, + row_number=plr.plr_rownum ) # Save non-track plr info self.plr_id: int = plr.id self.playlist_id: int = plr.playlist_id - self.row_number: int = plr.plr_rownum def __repr__(self) -> str: return ( @@ -594,11 +615,12 @@ class PreviewTrackManager(_TrackManager): Manage previewing tracks """ - def __init__(self, session: db.Session, track_id: int) -> None: + def __init__(self, session: db.Session, track_id: int, row_number: int) -> None: super().__init__( session=session, player_name=Config.VLC_PREVIEW_PLAYER_NAME, track_id=track_id, + row_number=row_number ) def __repr__(self) -> str: