diff --git a/app/config.py b/app/config.py index fc494f9..029519f 100644 --- a/app/config.py +++ b/app/config.py @@ -8,6 +8,8 @@ class Config(object): DBFS_SILENCE = -50 DISPLAY_SQL = False ERRORS_TO = ['kae@midnighthax.com'] + FADE_STEPS = 20 + FADE_TIME = 5000 LOG_LEVEL_STDERR = logging.DEBUG LOG_LEVEL_SYSLOG = logging.DEBUG LOG_NAME = "musicmuster" diff --git a/app/log.py b/app/log.py old mode 100755 new mode 100644 index a5527f0..271afb1 --- a/app/log.py +++ b/app/log.py @@ -35,9 +35,10 @@ syslog.addFilter(filter) stderr.addFilter(filter) # create formatter and add it to the handlers -formatter = logging.Formatter('[%(name)s] %(leveltag)s: %(message)s') -stderr.setFormatter(formatter) -syslog.setFormatter(formatter) +stderr_fmt = logging.Formatter('%(leveltag)s: %(message)s') +syslog_fmt = logging.Formatter('[%(name)s] %(leveltag)s: %(message)s') +stderr.setFormatter(stderr_fmt) +syslog.setFormatter(syslog_fmt) # add the handlers to the log log.addHandler(stderr) diff --git a/app/model.py b/app/model.py index d5bc719..89607d4 100644 --- a/app/model.py +++ b/app/model.py @@ -17,11 +17,8 @@ from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.orm import relationship, sessionmaker from config import Config - from log import DEBUG, ERROR, INFO -INFO("Starting") - # Create session at the global level as per # https://docs.sqlalchemy.org/en/13/orm/session_basics.html @@ -138,8 +135,10 @@ class Playlists(Base): def get_only_playlist(cls): return session.query(Playlists).filter(Playlists.id == 1).one() - def add_track(self, track): - self.tracks.append(track) + def add_track(self, track, position): + glue = PlaylistTracks(sort=position) + glue.track_id = track.id + self.tracks.append(glue) def get_tracks(self): return [a.tracks for a in self.tracks] @@ -181,10 +180,9 @@ class Tracks(Base): @staticmethod def get_path(id): try: - path = session.query(Tracks.path).filter(Tracks.id == id).one()[0] - return os.path.join(Config.ROOT, path) + return session.query(Tracks.path).filter(Tracks.id == id).one()[0] except NoResultFound: - print(f"Can't find track id {id}") + ERROR(f"Can't find track id {id}") return None @staticmethod @@ -210,6 +208,9 @@ class Tracks(Base): return session.query(Tracks).filter( Tracks.id == id).one() + def update_lastplayed(self): + self.lastplayed = datetime.now() + class Playdates(Base): __tablename__ = 'playdates' @@ -226,4 +227,5 @@ class Playdates(Base): pd.lastplayed = datetime.now() pd.track_id = track.id session.add(pd) + track.update_lastplayed() session.commit() diff --git a/app/musicmuster.py b/app/musicmuster.py index 0666653..6b8a3e2 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -33,6 +33,7 @@ class Music: "player": None, "meta": None } + self.fading = False def get_current_artist(self): try: @@ -101,16 +102,27 @@ class Music: return "" def fade_current(self): + if not self.playing(): + return thread = threading.Thread(target=self._fade_current) thread.start() def _fade_current(self): + fade_time = Config.FADE_TIME / 1000 + sleep_time = fade_time / Config.FADE_STEPS + step_percent = int((100 / Config.FADE_STEPS) * -1) player = self.current_track['player'] position = player.get_position() - for i in range(100, 0, -10): + for i in range(100, 0, step_percent): player.audio_set_volume(i) - sleep(0.2) - player.pause() + sleep(sleep_time) + # If the user clicks "fade" and then, before the track has + # finished fading, click "play next", the "play next" will also + # call fade_current. When that second call to fade_current gets + # to the player.pause() line below, it will unpause it. So, we + # only pause if we're acutally playing. + if player.is_playing(): + player.pause() player.audio_set_volume(100) player.set_position(position) @@ -118,9 +130,11 @@ class Music: if self.previous_track['player']: self.previous_track['player'].release() if self.current_track['player']: - self.current_track['player'].stop() + self.fade_current() self.previous_track = self.current_track self.current_track = self.next_track + # Next in case player was left in odd (ie, silenced) state + self.current_track['player'].audio_set_volume(100) self.current_track['player'].play() Playdates.add_playdate(self.current_track['meta']) @@ -210,7 +224,11 @@ class Window(QMainWindow, Ui_MainWindow): self.actionPlay_next.triggered.connect(self.play_next) self.actionPlay_selected.triggered.connect(self.play_next) self.actionSearch_database.triggered.connect(self.selectFromDatabase) + self.btnSearchDatabase.clicked.connect(self.selectFromDatabase) + self.btnSkipNext.clicked.connect(self.play_next) + self.btnStop.clicked.connect(self.fade) self.playlist.itemSelectionChanged.connect(self.set_next_track) + self.timer.timeout.connect(self.tick) def selectFromDatabase(self): @@ -400,7 +418,8 @@ class DbDialog(QDialog): # Store in current playlist in database db_playlist = Playlists.get_only_playlist() - db_playlist.add_track(track) + # TODO: hack position to be at end of list + db_playlist.add_track(track, 99999) # Add to on-screen playlist self.parent().add_to_playlist(track) diff --git a/app/songdb.py b/app/songdb.py old mode 100755 new mode 100644 index aaa871f..0f04a9b --- a/app/songdb.py +++ b/app/songdb.py @@ -9,12 +9,12 @@ from model import Tracks, session from pydub import AudioSegment from tinytag import TinyTag -INFO("Starting") - def main(): "Main loop" + INFO("Starting") + # Parse command line p = argparse.ArgumentParser() p.add_argument('-u', '--update', @@ -125,9 +125,9 @@ def update_db(): session.commit() elif ext not in [".jpg"]: - print(f"Unrecognised file type: {path}") + INFO(f"Unrecognised file type: {path}") - print(f"{count} files processed") + INFO(f"{count} files processed") if __name__ == '__main__': diff --git a/app/ui/main_window.ui b/app/ui/main_window.ui index b3f1e82..bf70a0e 100644 --- a/app/ui/main_window.ui +++ b/app/ui/main_window.ui @@ -16,153 +16,257 @@ - - - - 0 - 0 - - - - - 230 - 16777215 - - - - - Sans - 20 - - - - background-color: #f8d7da; + + + + + + + + 0 + 0 + + + + + 230 + 16777215 + + + + + Sans + 20 + + + + background-color: #f8d7da; border: 1px solid rgb(85, 87, 83); - - - Last track: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - Sans - 20 - - - - background-color: #f8d7da; + + + Last track: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 230 + 16777215 + + + + + Sans + 20 + + + + background-color: #d4edda; border: 1px solid rgb(85, 87, 83); - - - - - + + + Current track: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 230 + 16777215 + + + + + Sans + 20 + + + + background-color: #fff3cd; +border: 1px solid rgb(85, 87, 83); + + + Next track: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + Sans + 20 + + + + background-color: #f8d7da; +border: 1px solid rgb(85, 87, 83); + + + + + + + + + + + Sans + 20 + + + + background-color: #d4edda; +border: 1px solid rgb(85, 87, 83); + + + + + + + + + + + Sans + 20 + + + + background-color: #fff3cd; +border: 1px solid rgb(85, 87, 83); + + + + + + + + + + + + + + + 35 + + + + 00:00:00 + + + Qt::AlignCenter + + + + + + + + + + 30 + 30 + + + + + + + + previous.pngprevious.png + + + + 41 + 41 + + + + + + + + + 30 + 30 + + + + + + + + stop.pngstop.png + + + + 41 + 41 + + + + + + + + + 30 + 30 + + + + + + + + next.pngnext.png + + + + 41 + 41 + + + + + + + + + - - - - 0 - 0 - - - - - 230 - 16777215 - - - - - Sans - 20 - - - - background-color: #d4edda; -border: 1px solid rgb(85, 87, 83); - - - Current track: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - Sans - 20 - - - - background-color: #d4edda; -border: 1px solid rgb(85, 87, 83); - - - - - - - - - - - 0 - 0 - - - - - 230 - 16777215 - - - - - Sans - 20 - - - - background-color: #fff3cd; -border: 1px solid rgb(85, 87, 83); - - - Next track: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - Sans - 20 - - - - background-color: #fff3cd; -border: 1px solid rgb(85, 87, 83); - - - - - - - QAbstractItemView::NoEditTriggers @@ -230,11 +334,8 @@ border: 1px solid rgb(85, 87, 83); - + - - 5 - @@ -332,8 +433,8 @@ border: 1px solid rgb(85, 87, 83); - 20 - 20 + 13 + 60 @@ -388,8 +489,8 @@ border: 1px solid rgb(85, 87, 83); - 20 - 20 + 13 + 60 @@ -444,8 +545,8 @@ border: 1px solid rgb(85, 87, 83); - 20 - 20 + 13 + 60 @@ -500,8 +601,8 @@ border: 1px solid rgb(85, 87, 83); - 20 - 20 + 13 + 60 @@ -562,16 +663,49 @@ border: 1px solid rgb(85, 87, 83); + + + + + + + + Add &file... + + + + + + + Search &database + + + + + + + + + + + Set &next track + + + + + + + Track &info + + + + + + + - playlist - current_track - previous_track - next_track - current_track_2 - next_track_2 - previous_track_2 @@ -610,36 +744,6 @@ border: 1px solid rgb(85, 87, 83); background-color: rgb(211, 215, 207); - - - toolBar - - - TopToolBarArea - - - false - - - - - - - - - - - - - toolBar_2 - - - TopToolBarArea - - - false - - diff --git a/app/ui/next.png b/app/ui/next.png new file mode 100644 index 0000000..25d76a3 Binary files /dev/null and b/app/ui/next.png differ diff --git a/app/ui/previous.png b/app/ui/previous.png new file mode 100644 index 0000000..8045b09 Binary files /dev/null and b/app/ui/previous.png differ diff --git a/app/ui/stop.png b/app/ui/stop.png new file mode 100644 index 0000000..3935df8 Binary files /dev/null and b/app/ui/stop.png differ