diff --git a/app/config.py b/app/config.py index 655bdbe..ddb94a1 100644 --- a/app/config.py +++ b/app/config.py @@ -7,11 +7,13 @@ class Config(object): COLOUR_CURRENT_HEADER = "#d4edda" COLOUR_CURRENT_PLAYLIST = "#28a745" COLOUR_ODD_PLAYLIST = "#f2f2f2" + COLOUR_ENDING_TIMER = "#dc3545" COLOUR_EVEN_PLAYLIST = "#d9d9d9" COLOUR_NEXT_HEADER = "#fff3cd" COLOUR_NEXT_PLAYLIST = "#ffc107" - COLOUR_NOTES_PLAYLIST = "#802020" + COLOUR_NOTES_PLAYLIST = "#dc3545" COLOUR_PREVIOUS_HEADER = "#f8d7da" + COLOUR_WARNING_TIMER = "#ffc107" DBFS_FADE = -12 DBFS_SILENCE = -50 DISPLAY_SQL = False diff --git a/app/music.py b/app/music.py index a672fd7..92f2b29 100644 --- a/app/music.py +++ b/app/music.py @@ -22,7 +22,7 @@ class Music: """ Fade the currently playing track. - Return the current track path and position. + Return the current track position. The actual management of fading runs in its own thread so as not to hold up the UI during the fade. @@ -31,15 +31,14 @@ class Music: DEBUG("fade()") if not self.playing(): - return (None, None) + return None - path = self.track_path position = self.player.get_position() thread = threading.Thread(target=self._fade) thread.start() - return (path, position) + return position def _fade(self): """ diff --git a/app/musicmuster.py b/app/musicmuster.py index f7b4476..262332a 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -31,6 +31,7 @@ class Window(QMainWindow, Ui_MainWindow): self.timer = QTimer() self.even_tick = True self.music = music.Music() + self.playing = False self.playlist.music = self.music self.connect_signals_slots() self.disable_play_next_controls() @@ -47,6 +48,7 @@ class Window(QMainWindow, Ui_MainWindow): # Hard code to the only playlist we have for now if self.playlist.load_playlist("Default"): + self.update_headers() self.enable_play_next_controls() self.timer.start(Config.TIMER_MS) @@ -91,7 +93,7 @@ class Window(QMainWindow, Ui_MainWindow): self.actionSearch_database.triggered.connect(self.search_database) self.btnPrevious.clicked.connect(self.play_previous) self.btnSearchDatabase.clicked.connect(self.search_database) - self.btnSetNextTrack.clicked.connect(self.playlist.set_next_track) + self.btnSetNextTrack.clicked.connect(self.set_next_track) self.btnSkipNext.clicked.connect(self.play_next) self.btnStop.clicked.connect(self.fade) @@ -109,18 +111,7 @@ class Window(QMainWindow, Ui_MainWindow): def play_next(self): self.playlist.play_next() self.disable_play_next_controls() - self.previous_track.setText( - f"{self.playlist.get_previous_title()} - " - f"{self.playlist.get_previous_artist()}" - ) - self.current_track.setText( - f"{self.playlist.get_current_title()} - " - f"{self.playlist.get_current_artist()}" - ) - self.next_track.setText( - f"{self.playlist.get_next_title()} - " - f"{self.playlist.get_next_artist()}" - ) + self.update_headers() # Set time clocks now = datetime.now() @@ -142,8 +133,10 @@ class Window(QMainWindow, Ui_MainWindow): dlg.exec() def set_next_track(self): - # TODO - pass + "Set selected track as next" + + self.playlist.set_selected_as_next() + self.update_headers() def tick(self): """ @@ -166,28 +159,69 @@ class Window(QMainWindow, Ui_MainWindow): return if self.music.playing(): + self.playing = True playtime = self.music.get_playtime() - self.label_elapsed_timer.setText(helpers.ms_to_mmss(playtime)) - self.label_fade_timer.setText( - helpers.ms_to_mmss( - self.playlist.get_current_fade_at() - playtime) - ) + time_to_fade = (self.playlist.get_current_fade_at() - playtime) time_to_silence = ( self.playlist.get_current_silence_at() - playtime ) - if time_to_silence < 500: - self.label_silent_timer.setText("00:00") + time_to_end = (self.playlist.get_current_duration() - playtime) + + # Elapsed time + if time_to_end < 500: + self.label_elapsed_timer.setText( + helpers.ms_to_mmss(self.playlist.get_current_duration()) + ) + else: + self.label_elapsed_timer.setText(helpers.ms_to_mmss(playtime)) + + # Time to fade + self.label_fade_timer.setText(helpers.ms_to_mmss(time_to_fade)) + + # Time to silence + if time_to_silence < 5000: + self.frame_silent.setStyleSheet( + f"background: {Config.COLOUR_ENDING_TIMER}" + ) + self.enable_play_next_controls() + elif time_to_fade < 500: + self.frame_silent.setStyleSheet( + f"background: {Config.COLOUR_WARNING_TIMER}" + ) self.enable_play_next_controls() else: - self.label_silent_timer.setText( - helpers.ms_to_mmss(time_to_silence) - ) - self.label_end_timer.setText( - helpers.ms_to_mmss( - self.playlist.get_current_duration() - playtime)) + self.frame_silent.setStyleSheet("") + + self.label_silent_timer.setText( + helpers.ms_to_mmss(time_to_silence) + ) + + # Time to end + self.label_end_timer.setText(helpers.ms_to_mmss(time_to_end)) + else: - # When music ends, update playlist display - self.playlist.update_playlist_colours() + if self.playing: + self.label_end_timer.setText("00:00") + self.frame_silent.setStyleSheet("") + self.playing = False + self.playlist.music_ended() + self.update_headers() + + def update_headers(self): + "Update last / current / next track headers" + + self.previous_track.setText( + f"{self.playlist.get_previous_title()} - " + f"{self.playlist.get_previous_artist()}" + ) + self.current_track.setText( + f"{self.playlist.get_current_title()} - " + f"{self.playlist.get_current_artist()}" + ) + self.next_track.setText( + f"{self.playlist.get_next_title()} - " + f"{self.playlist.get_next_artist()}" + ) class DbDialog(QDialog): diff --git a/app/playlists.py b/app/playlists.py index b2748d2..0209ccd 100644 --- a/app/playlists.py +++ b/app/playlists.py @@ -42,7 +42,7 @@ class Playlist(QTableWidget): self.current_track = None self.next_track = None - self.previous_track_path = None + self.previous_track = None self.previous_track_position = None self.played_tracks = [] @@ -66,10 +66,11 @@ class Playlist(QTableWidget): "Save column widths" for column in range(self.columnCount()): + width = self.columnWidth(column) name = f"playlist_col_{str(column)}_width" record = Settings.get_int(name) if record.f_int != self.columnWidth(column): - record.update({'f_int': self.columnWidth(column)}) + record.update({'f_int': width}) def add_to_playlist(self, track): """ @@ -133,7 +134,7 @@ class Playlist(QTableWidget): DEBUG(f"Moved row(s) {rows} to become row {drop_row}") self.clearSelection() - self.update_playlist_colours() + self.repaint() def fade(self): self.music.fade() @@ -225,21 +226,22 @@ class Playlist(QTableWidget): Return True if successful else False. """ + DEBUG(f"load_playlist({name})") + for track in Playlists.get_playlist_by_name(name).get_tracks(): self.add_to_playlist(track) # Set the first playable track as next to play for row in range(self.rowCount()): - if self.set_row_as_next_track(row): + if self.item(row, self.COL_INDEX): self.meta_set_next(row) + self.tracks_changed() return True - self.update_playlist_colours() - return False def meta_clear(self, row): - "Clear metad ata for row" + "Clear metadata for row" self.meta_set(row, None) @@ -295,11 +297,21 @@ class Playlist(QTableWidget): def meta_set_current(self, row): "Mark row as current track" + DEBUG(f"meta_set_current({row})") + + old_current = self.meta_get_current() + if old_current is not None: + self.meta_clear(old_current) self.meta_set(row, "current") def meta_set_next(self, row): "Mark row as next track" + DEBUG(f"meta_set_next({row})") + + old_next = self.meta_get_next() + if old_next is not None: + self.meta_clear(old_next) self.meta_set(row, "next") def meta_set_note(self, row): @@ -322,10 +334,9 @@ class Playlist(QTableWidget): Play (new) current. Update playlist "current track" metadata Cue up next track in playlist if there is one. - Update playlist "next track" metadata Tell database to record it as played Remember it was played for this session - Update playlist appearance. + Update metadata and headers, and repaint """ # If there is no next track set, return. @@ -334,43 +345,30 @@ class Playlist(QTableWidget): # If there's currently a track playing, fade it. if self.music.playing(): - path, position = self.music.fade() - self.previous_track_path = path - self.previous_track_position = position + self.previous_track_position = self.music.fade() + self.previous_track = self.current_track - # Move next track to current track. + # Shuffle tracks along self.current_track = self.next_track + self.next_track = None # Play (new) current. self.music.play(self.current_track.path) - # Update playlist "current track" metadata - old_current = self.meta_get_current() - if old_current is not None: - self.meta_clear(old_current) - current = self.meta_get_next() - if current is not None: - self.meta_set_current(current) + # Update metadata + self.meta_set_current(self.meta_get_next()) - # Cue up next track in playlist if there is one. - track_id = 0 - next_row = 0 - if current is not None: - start = current + 1 + # Set up metadata for next track in playlist if there is one. + current_row = self.meta_get_current() + if current_row is not None: + start = current_row + 1 else: start = 0 for row in range(start, self.rowCount()): if self.item(row, self.COL_INDEX): if int(self.item(row, self.COL_INDEX).text()) > 0: - track_id = int(self.item(row, self.COL_INDEX).text()) - next_row = row + self.meta_set_next(row) break - if track_id: - self.next_track = Tracks.get_track(track_id) - - # Update playlist "next track" metadata - if next_row: - self.meta_set_next(next_row) # Tell database to record it as played self.current_track.update_lastplayed() @@ -379,33 +377,15 @@ class Playlist(QTableWidget): # Remember it was played for this session self.played_tracks.append(self.current_track.id) - # Update playlist appearance. - self.update_playlist_colours() + # Update display + self.tracks_changed() - def set_next_track(self): + def set_selected_as_next(self): """ Sets the selected track as the next track. """ - # TODO - pass - - # if self.selectionModel().hasSelection(): - # self.set_next_track_row(self.playlist.currentRow()) - # if self.selectionModel().hasSelection(): - # row = self.currentRow() - # track_id = int(self.item(row, 0).text()) - # DEBUG(f"play_selected: track_id={track_id}") - # # TODO: get_path may raise exception - # music.play_track(track_id) - # track = Tracks.get_track(id) - # if not track: - # ERROR(f"set_next_track({id}): can't find track") - # return None - - # # self.next_track['player'] = vlc.MediaPlayer(track.path) - # self.next_track = track - # return track.id + self.set_row_as_next_track(self.currentRow()) def set_row_as_next_track(self, row): """ @@ -415,16 +395,21 @@ class Playlist(QTableWidget): """ if self.item(row, self.COL_INDEX): - track_id = int(self.item(row, self.COL_INDEX).text()) - DEBUG(f"set_row_as_next_track: track_id={track_id}") - self.next_track = Tracks.get_track(track_id) - # Mark row as next track - self.meta_set_next(row) + self.meta_set_current(row) + self.tracks_changed() return True return False - def update_playlist_colours(self): + def music_ended(self): + "Update display" + + self.previous_track = self.current_track + self.previous_track_position = 0 + self.meta_clear(self.meta_get_current()) + self.tracks_changed() + + def repaint(self): "Set row colours, fonts, etc" self.clearSelection() @@ -481,6 +466,50 @@ class Playlist(QTableWidget): if self.item(row, j): self.item(row, j).setFont(normal) + def tracks_changed(self): + """ + One or more of current or next track has changed. + + The row metadata is definitive because the same track may appear + more than once in the playlist, but only one track may be marked + as current or next. + + Update self.current_track and self.next_track. + """ + + # Update instance variables + current_row = self.meta_get_current() + if current_row is not None: + track_id = int(self.item(current_row, self.COL_INDEX).text()) + if not self.current_track or self.current_track.id != track_id: + self.current_track = Tracks.get_track(track_id) + else: + self.current_track = None + + next_row = self.meta_get_next() + if next_row is not None: + track_id = int(self.item(next_row, self.COL_INDEX).text()) + if not self.next_track or self.next_track.id != track_id: + self.next_track = Tracks.get_track(track_id) + else: + self.next_track = None + + try: + DEBUG(f"tracks_changed() previous={self.previous_track.id}") + except AttributeError: + DEBUG("tracks_changed() previous=None") + try: + DEBUG(f"tracks_changed() current={self.current_track.id}") + except AttributeError: + DEBUG("tracks_changed() current=None") + try: + DEBUG(f"tracks_changed() next={self.next_track.id}") + except AttributeError: + DEBUG("tracks_changed() next=None") + + # Update display + self.repaint() + class Window(QWidget): def __init__(self): diff --git a/app/ui/main_window.ui b/app/ui/main_window.ui index 646aeff..3735fc9 100644 --- a/app/ui/main_window.ui +++ b/app/ui/main_window.ui @@ -491,7 +491,7 @@ border: 1px solid rgb(85, 87, 83); - + @@ -547,7 +547,7 @@ border: 1px solid rgb(85, 87, 83); - + @@ -603,7 +603,7 @@ border: 1px solid rgb(85, 87, 83); - +