From b476db188f8a7f7967d10af42c0cccf62317d815 Mon Sep 17 00:00:00 2001 From: Keith Edmunds Date: Fri, 30 Dec 2022 21:43:47 +0000 Subject: [PATCH] Implement PlaylistTrack object --- app/musicmuster.py | 257 +++++++++++++++++++++++++-------------------- app/playlists.py | 43 ++++---- 2 files changed, 168 insertions(+), 132 deletions(-) diff --git a/app/musicmuster.py b/app/musicmuster.py index 13fafa0..64cf6a5 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -111,24 +111,74 @@ class CartButton(QPushButton): self.pgb.setGeometry(0, 0, self.width(), 10) -class TrackData: - def __init__(self, track): - self.id = track.id - self.title = track.title - self.artist = track.artist - self.duration = track.duration - self.start_gap = track.start_gap - self.fade_at = track.fade_at - self.silence_at = track.silence_at - self.path = track.path - self.mtime = track.mtime +class PlaylistTrack: + """ + Used to provide a single reference point for specific playlist tracks, + typicall the previous, current and next track. + """ + + def __init__(self) -> None: + """ + Only initialises data structure. Call set_plr to populate. + """ + + self.artist = None + self.duration = None + self.end_time = None + self.fade_at = None + self.fade_length = None + self.path = None + self.playlist_id = None + self.playlist_tab = None + self.plr_id = None + self.row_number = None + self.silence_at = None + self.start_gap = None + self.start_time = None + self.title = None + self.track_id = None def __repr__(self) -> str: return ( - f"" + f"" ) + def set_plr(self, session: Session, plr: PlaylistRows, + tab: PlaylistTab) -> None: + """ + Update with new plr information + """ + + self.playlist_tab = tab + + session.add(plr) + track = plr.track + + self.artist = track.artist + self.duration = track.duration + self.end_time = None + self.fade_at = track.fade_at + self.fade_length = track.silence_at - track.fade_at + self.path = track.path + self.playlist_id = plr.playlist_id + self.plr_id = plr.id + self.row_number = plr.row_number + self.silence_at = track.silence_at + self.start_gap = track.start_gap + self.start_time = None + self.title = track.title + self.track_id = track.id + + def start(self) -> None: + """ + Called when track starts playing + """ + + self.start_time = datetime.now() + self.end_time = self.start_time + timedelta(milliseconds=self.duration) + class Window(QMainWindow, Ui_MainWindow): def __init__(self, parent=None) -> None: @@ -137,13 +187,14 @@ class Window(QMainWindow, Ui_MainWindow): self.timer: QTimer = QTimer() self.even_tick: bool = True + + self.music: music.Music = music.Music() self.playing: bool = False - self.current_plr: Optional[PlaylistRows] = None - self.current_track_playlist_tab: Optional[PlaylistTab] = None - self.next_plr: Optional[PlaylistRows] = None - self.next_track: Optional[TrackData] = None - self.previous_plr: Optional[PlaylistRows] = None + self.current_track = PlaylistTrack() + self.next_track = PlaylistTrack() + self.previous_track = PlaylistTrack() + self.previous_track_position: Optional[int] = None self.selected_plrs = None @@ -309,7 +360,7 @@ class Window(QMainWindow, Ui_MainWindow): """Handle attempt to close main window""" # Don't allow window to close when a track is playing - if self.music.player and self.music.player.is_playing(): + if self.playing: event.ignore() helpers.show_warning( "Track playing", @@ -374,7 +425,7 @@ class Window(QMainWindow, Ui_MainWindow): return # Don't close next track playlist - if self.tabPlaylist.widget(tab_index) == self.next_track_playlist_tab: + if self.tabPlaylist.widget(tab_index) == self.next_track.playlist_tab: self.statusbar.showMessage( "Can't close next track playlist", 5000) return @@ -560,17 +611,14 @@ class Window(QMainWindow, Ui_MainWindow): # doesn't see player=None and kick off end-of-track actions self.playing = False - # Reset current track - if self.current_plr: - self.previous_plr = self.current_plr - self.current_plr = None + # Tell playlist_tab track has finished + if self.current_track.playlist_tab: + self.current_track.playlist_tab.play_stopped() - # Tell playlist_tab track has finished and - # reset current playlist_tab - if self.current_track_playlist_tab: - self.previous_track_playlist_tab = self.current_track_playlist_tab - self.current_track_playlist_tab.play_stopped() - self.current_track_playlist_tab = None + # Reset PlaylistTrack objects + if self.current_track.track_id: + self.previous_track = self.current_track + self.current_track = PlaylistTrack() # Reset clocks self.frame_fade.setStyleSheet("") @@ -582,7 +630,7 @@ class Window(QMainWindow, Ui_MainWindow): self.label_start_time.setText("00:00:00") self.label_end_time.setText("00:00:00") - if self.next_track: + if self.next_track.track_id: self.label_track_length.setText( helpers.ms_to_mmss(self.next_track.duration) ) @@ -994,7 +1042,7 @@ class Window(QMainWindow, Ui_MainWindow): """ # If there is no next track set, return. - if not self.next_track: + if not self.next_track.track_id: log.debug("musicmuster.play_next(): no next track selected") return @@ -1002,37 +1050,33 @@ class Window(QMainWindow, Ui_MainWindow): # If there's currently a track playing, fade it. self.stop_playing(fade=True) - # Move next track to current track. - self.current_plr = self.next_plr - self.next_plr = None - # Ensure playlist tabs are the correct colour - # If current track on different playlist_tab to last, reset - # last track playlist_tab colour - if self.current_track_playlist_tab != self.next_track_playlist_tab: - self.set_tab_colour(self.current_track_playlist_tab, + # If next track is on a different playlist_tab to the + # current track, reset the current track playlist_tab colour + if self.current_track.playlist_tab != self.next_track.playlist_tab: + self.set_tab_colour(self.current_track.playlist_tab, QColor(Config.COLOUR_NORMAL_TAB)) - # # Update record of current track playlist_tab - self.current_track_playlist_tab = self.next_track_playlist_tab - self.next_track_playlist_tab = None - # Set current track playlist_tab colour - self.set_tab_colour(self.current_track_playlist_tab, - QColor(Config.COLOUR_CURRENT_TAB)) + # Set current track playlist_tab colour + self.set_tab_colour(self.current_track.playlist_tab, + QColor(Config.COLOUR_CURRENT_TAB)) + + # Move next track to current track. + self.current_track = self.next_track + self.next_track = PlaylistTrack() # Restore volume if -3dB active if self.btnDrop3db.isChecked(): self.btnDrop3db.setChecked(False) # Play (new) current track - start_at = datetime.now() - session.add(self.current_plr.track.path) - self.music.play(self.current_plr.track.path, position) + self.current_track.start() + self.music.play(self.current_track.path, position) # Tell database to record it as played - Playdates(session, self.current_plr.track.id) + Playdates(session, self.current_track.track_id) # Tell playlist track is now playing - self.current_track_playlist_tab.play_started(session) + self.current_track.playlist_tab.play_started(session) # Note that track is now playing self.playing = True @@ -1045,18 +1089,15 @@ class Window(QMainWindow, Ui_MainWindow): # Update clocks self.label_track_length.setText( - helpers.ms_to_mmss(self.current_plr.track.duration) + helpers.ms_to_mmss(self.current_track.duration) ) - fade_at = self.current_plr.track.fade_at - silence_at = self.current_plr.track.silence_at self.label_fade_length.setText( - helpers.ms_to_mmss(silence_at - fade_at)) + helpers.ms_to_mmss(self.current_track.fade_length)) self.label_start_time.setText( - start_at.strftime(Config.TRACK_TIME_FORMAT)) - self.current_track_end_time = start_at + timedelta( - milliseconds=self.current_plr.track.duration) + self.current_track.start_time.strftime( + Config.TRACK_TIME_FORMAT)) self.label_end_time.setText( - self.current_track_end_time.strftime(Config.TRACK_TIME_FORMAT)) + self.current_track.end_time.strftime(Config.TRACK_TIME_FORMAT)) def resume(self) -> None: """ @@ -1078,24 +1119,23 @@ class Window(QMainWindow, Ui_MainWindow): # Note resume point resume_from = self.previous_track_position - # Remember current next track - original_next_track = self.next_track - original_next_track_playlist_tab = self.next_track_playlist_tab + # Remember what was to have been the next track + original_next_plr_id = self.next_track.plr_id + original_next_plr_playlist_tab = self.next_track.playlist_tab with Session() as session: - # Set last track to be next track - self.this_is_the_next_track(session, - self.previous_track_playlist_tab, - self.previous_track) + # Set next track to be the last one played + self.next_track = self.previous_track + self.previous_track = PlaylistTrack() # Resume last track self.play_next(resume_from) # Reset next track if there was one - if original_next_track: - self.this_is_the_next_track(session, - original_next_track_playlist_tab, - original_next_track) + if original_next_plr_id: + next_plr = Session.get(PlaylistRows, original_next_plr_id) + self.this_is_the_next_playlist_row( + session, next_plr, original_next_plr_playlist_tab) def save_as_template(self) -> None: """Save current playlist as template""" @@ -1193,15 +1233,15 @@ class Window(QMainWindow, Ui_MainWindow): def show_current(self) -> None: """Scroll to show current track""" - if self.current_track_playlist_tab != self.visible_playlist_tab(): - self.tabPlaylist.setCurrentWidget(self.current_track_playlist_tab) + if self.current_track.playlist_tab != self.visible_playlist_tab(): + self.tabPlaylist.setCurrentWidget(self.current.track_playlist_tab) self.tabPlaylist.currentWidget().scroll_current_to_top() def show_next(self) -> None: """Scroll to show next track""" - if self.next_track_playlist_tab != self.visible_playlist_tab(): - self.tabPlaylist.setCurrentWidget(self.next_track_playlist_tab) + if self.next_track.playlist_tab != self.visible_playlist_tab(): + self.tabPlaylist.setCurrentWidget(self.next_track.playlist_tab) self.tabPlaylist.currentWidget().scroll_next_to_top() def solicit_playlist_name(self) -> Optional[str]: @@ -1245,11 +1285,11 @@ class Window(QMainWindow, Ui_MainWindow): self.music.stop() # Reset playlist_tab colour - if self.current_track_playlist_tab == self.next_track_playlist_tab: - self.set_tab_colour(self.current_track_playlist_tab, + if self.current_track.playlist_tab == self.next_track.playlist_tab: + self.set_tab_colour(self.current_track.playlist_tab, QColor(Config.COLOUR_NEXT_TAB)) else: - self.set_tab_colour(self.current_track_playlist_tab, + self.set_tab_colour(self.current_track.playlist_tab, QColor(Config.COLOUR_NORMAL_TAB)) # Run end-of-track actions @@ -1264,12 +1304,12 @@ class Window(QMainWindow, Ui_MainWindow): # May also be called when last tab is closed pass - def this_is_the_next_track(self, session: Session, - playlist_tab: PlaylistTab, - track: Tracks) -> None: + def this_is_the_next_playlist_row(self, session: Session, + plr: PlaylistRows, + playlist_tab: PlaylistTab) -> None: """ This is notification from a playlist tab that it holds the next - track to be played. + playlist row to be played. Actions required: - Clear next track if on other tab @@ -1283,47 +1323,44 @@ class Window(QMainWindow, Ui_MainWindow): """ # Clear next track if on another tab - if self.next_track_playlist_tab != playlist_tab: + if self.next_track.playlist_tab != playlist_tab: # We need to reset the ex-next-track playlist - if self.next_track_playlist_tab: - self.next_track_playlist_tab.clear_next(session) + if self.next_track.playlist_tab: + self.next_track.playlist_tab.clear_next(session) # Reset tab colour if on other tab - if (self.next_track_playlist_tab != - self.current_track_playlist_tab): + if (self.next_track.playlist_tab != + self.current.track_playlist_tab): self.set_tab_colour( - self.next_track_playlist_tab, + self.next_track.playlist_tab, QColor(Config.COLOUR_NORMAL_TAB)) # Note next playlist tab - self.next_track_playlist_tab = playlist_tab + self.next_track = PlaylistTrack() + self.next_track.set_plr(session, plr, playlist_tab) # Set next playlist_tab tab colour if it isn't the # currently-playing tab - if (self.next_track_playlist_tab != - self.current_track_playlist_tab): + if (self.next_track.playlist_tab != + self.current_track.playlist_tab): self.set_tab_colour( - self.next_track_playlist_tab, + self.next_track.playlist_tab, QColor(Config.COLOUR_NEXT_TAB)) - # Note next track - self.next_track = TrackData(track) - # Populate footer if we're not currently playing - - if not self.playing and self.next_track: + if not self.playing and self.next_track.track_id: self.label_track_length.setText( helpers.ms_to_mmss(self.next_track.duration) ) self.label_fade_length.setText(helpers.ms_to_mmss( - self.next_track.silence_at - self.next_track.fade_at)) + self.next_track.fade_length)) # Update headers self.update_headers() # Populate 'info' tabs with Wikipedia info, but queue it because # it isn't quick - track_title = track.title + track_title = self.next_track.title QTimer.singleShot( 1, lambda: self.tabInfolist.open_in_wikipedia(track_title) ) @@ -1363,10 +1400,10 @@ class Window(QMainWindow, Ui_MainWindow): # If track is playing, update track clocks time and colours if self.music.player and self.music.player.is_playing(): playtime = self.music.get_playtime() - time_to_fade = (self.current_plr.track.fade_at - playtime) + time_to_fade = (self.current_track.fade_at - playtime) time_to_silence = ( - self.current_plr.track.silence_at - playtime) - time_to_end = (self.current_plr.track.duration - playtime) + self.current_track.silence_at - playtime) + time_to_end = (self.current_track.duration - playtime) # Elapsed time self.label_elapsed_timer.setText(helpers.ms_to_mmss(playtime)) @@ -1408,12 +1445,6 @@ class Window(QMainWindow, Ui_MainWindow): if self.playing: self.stop_playing() - def update_current_track(self, track): - """Update current track with passed details""" - - self.current_track = TrackData(track) - self.update_headers() - def update_next_track(self, track): """Update next track with passed details""" @@ -1425,23 +1456,23 @@ class Window(QMainWindow, Ui_MainWindow): Update last / current / next track headers """ - try: + if self.previous_track.title: self.hdrPreviousTrack.setText( f"{self.previous_track.title} - {self.previous_track.artist}") - except AttributeError: + else: self.hdrPreviousTrack.setText("") - try: + if self.current_track.title: self.hdrCurrentTrack.setText( f"{self.current_track.title} - {self.current_track.artist}") - except AttributeError: + else: self.hdrCurrentTrack.setText("") - try: + if self.next_track.title: self.hdrNextTrack.setText( f"{self.next_track.title} - {self.next_track.artist}" ) - except AttributeError: + else: self.hdrNextTrack.setText("") diff --git a/app/playlists.py b/app/playlists.py index 391feee..c973f46 100644 --- a/app/playlists.py +++ b/app/playlists.py @@ -419,12 +419,16 @@ class PlaylistTab(QTableWidget): # Determin cell type changed with Session() as session: - if self.edit_cell_type == ROW_NOTES: - # Get playlistrow object - plr_id = self._get_playlistrow_id(row) - plr_item = session.get(PlaylistRows, plr_id) - plr_item.note = new_text + # Get playlistrow object + plr_id = self._get_playlistrow_id(row) + plr_item = session.get(PlaylistRows, plr_id) + # Note any updates needed to PlaylistTrack objects + update_current = self.musicmuster.current_track.plr_id == plr_id + update_next = self.musicmuster.next_track.plr_id == plr_id + + if self.edit_cell_type == ROW_NOTES: + plr_item.note = new_text # Set/clear row start time accordingly start_time = self._get_note_text_time(new_text) if start_time: @@ -436,23 +440,23 @@ class PlaylistTab(QTableWidget): if track_id: track = session.get(Tracks, track_id) if track: - update_current = row == self._get_current_track_row() - update_next = row == self._get_next_track_row() if self.edit_cell_type == TITLE: log.debug(f"KAE: _cell_changed:440, {new_text=}") track.title = new_text + if update_current: + self.musicmuster.current_track.title = new_text + if update_next: + self.musicmuster.next_track.title = new_text elif self.edit_cell_type == ARTIST: track.artist = new_text - if update_current: - self.musicmuster.update_current_track(track) - elif update_next: - self.musicmuster.update_next_track(track) + if update_current: + self.musicmuster.current_track.artist = \ + new_text + if update_next: + self.musicmuster.next_track.artist = new_text - # Headers will be incorrect if the edited track is - # previous / current / next TODO: this will require - # the stored data in musicmuster to be updated, - # which currently it isn't). - self.musicmuster.update_headers() + if update_next or update_current: + self.musicmuster.update_headers() def closeEditor(self, editor: QWidget, @@ -1098,7 +1102,7 @@ class PlaylistTab(QTableWidget): # if there's a track playing, set start time from # that. It may be on a different tab, so we get # start time from musicmuster. - start_time = self.musicmuster.current_track_end_time + start_time = self.musicmuster.current_track.end_time if start_time is None: # No current track to base from, but don't change # time if it's already set @@ -1813,7 +1817,7 @@ class PlaylistTab(QTableWidget): def _set_next(self, session: Session, row_number: int) -> None: """ - Set passed row as next track to play. + Set passed row as next playlist row to play. Actions required: - Check row has a track @@ -1847,7 +1851,8 @@ class PlaylistTab(QTableWidget): self.update_display(session) # Notify musicmuster - self.musicmuster.this_is_the_next_track(session, self, track) + plr = session.get(PlaylistRows, self._get_playlistrow_id(row_number)) + self.musicmuster.this_is_the_next_playlist_row(session, plr, self) def _set_next_track_row(self, row: int) -> None: """Mark this row as next track"""