#!/usr/bin/python3 import vlc import sys from datetime import datetime, timedelta from log import DEBUG, ERROR from PyQt5.QtCore import Qt, QTimer from PyQt5.QtWidgets import QApplication, QDialog, QMainWindow from PyQt5.QtWidgets import QTableWidgetItem, QFileDialog, QListWidgetItem from ui.main_window_ui import Ui_MainWindow from ui.dlg_search_database_ui import Ui_Dialog from config import Config from model import Settings, Tracks class Music: def __init__(self): self.current_track = { "player": None, "meta": None } self.next_track = { "player": None, "meta": None } self.previous_track = { "player": None, "meta": None } def get_current_artist(self): return self.current_track['meta'].artist def get_current_duration(self): return self.current_track['meta'].duration def get_current_fade_at(self): return self.current_track['meta'].fade_at def get_current_playtime(self): return self.current_track['player'].get_time() def get_current_silence_at(self): return self.current_track['meta'].silence_at def get_current_title(self): return self.current_track['meta'].title def get_last_artist(self): return self.last_track['meta'].artist def get_last_title(self): return self.last_track['meta'].title def get_next_artist(self): return self.next_track['meta'].artist def get_next_title(self): return self.next_track['meta'].title def play_next(self): if self.previous_track['player']: self.previous_track['player'].release() if self.current_track['player']: self.current_track['player'].stop() self.previous_track = self.current_track self.current_track = self.next_track self.current_track['player'].play() # Tidy up self.next_track = { "player": None, "meta": None } def playing(self): if self.current_track['player']: return self.current_track['player'].is_playing() else: return False def resume_last(self): pass def set_next_track(self, 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['meta'] = track return track.id class Window(QMainWindow, Ui_MainWindow): def __init__(self, parent=None): super().__init__(parent) self.setupUi(self) self.timer = QTimer() self.connectSignalsSlots() self.music = Music() record = Settings.get_int("mainwindow_x") x = record.f_int or 1 record = Settings.get_int("mainwindow_y") y = record.f_int or 1 record = Settings.get_int("mainwindow_width") width = record.f_int or 1599 record = Settings.get_int("mainwindow_height") height = record.f_int or 981 self.setGeometry(x, y, width, height) for column in range(self.playlist.columnCount()): name = f"playlist_col_{str(column)}_width" record = Settings.get_int(name) if record.f_int is not None: self.playlist.setColumnWidth(column, record.f_int) self.timer.start(Config.TIMER_MS) def __del__(self): for column in range(self.playlist.columnCount()): name = f"playlist_col_{str(column)}_width" record = Settings.get_int(name) if record.f_int != self.playlist.columnWidth(column): record.update({'f_int': self.playlist.columnWidth(column)}) record = Settings.get_int("mainwindow_height") if record.f_int != self.height(): record.update({'f_int': self.height()}) record = Settings.get_int("mainwindow_width") if record.f_int != self.width(): record.update({'f_int': self.width()}) record = Settings.get_int("mainwindow_x") if record.f_int != self.x(): record.update({'f_int': self.x()}) record = Settings.get_int("mainwindow_y") if record.f_int != self.y(): record.update({'f_int': self.y()}) def connectSignalsSlots(self): self.fileButton.clicked.connect(self.selectFile) self.databaseButton.clicked.connect(self.selectFromDatabase) self.actionPlay_selected.triggered.connect(self.play_next) self.actionPlay_next.triggered.connect(self.play_next) self.playlist.itemSelectionChanged.connect(self.set_next) self.timer.timeout.connect(self.tick) def selectFromDatabase(self): dlg = DbDialog(self) dlg.exec() def play_next(self): self.music.play_next() # Set time clocks now = datetime.now() self.label_start_tod.setText(now.strftime("%H:%M:%S")) fade_time = now + timedelta( milliseconds=self.music.get_current_fade_at()) self.label_fade_tod.setText(fade_time.strftime("%H:%M:%S")) silence_time = now + timedelta( milliseconds=self.music.get_current_silence_at()) self.label_silent_tod.setText(silence_time.strftime("%H:%M:%S")) end_time = now + timedelta( milliseconds=self.music.get_current_duration()) self.label_end_tod.setText(end_time.strftime("%H:%M:%S")) def play_selected(self): if self.playlist.selectionModel().hasSelection(): row = self.playlist.currentRow() track_id = int(self.playlist.item(row, 0).text()) DEBUG(f"play_selected: track_id={track_id}") # TODO: get_path may raise exception track_path = Tracks.get_path(track_id) if track_path: DEBUG(f"play_selected: track_path={track_path}") player = vlc.MediaPlayer(track_path) player.play() def selectFile(self): dlg = QFileDialog() dlg.setFileMode(QFileDialog.ExistingFile) dlg.setViewMode(QFileDialog.Detail) dlg.setDirectory(Config.ROOT) dlg.setNameFilter("Music files (*.flac *.mp3)") if dlg.exec_(): pass # TODO Add to database # for fname in dlg.selectedFiles(): # track = Track(fname) # self.add_to_playlist(track) def set_next(self): if self.playlist.selectionModel().hasSelection(): row = self.playlist.currentRow() track_id = int(self.playlist.item(row, 0).text()) DEBUG(f"set_next: track_id={track_id}") if self.music.set_next_track(track_id) != track_id: ERROR("Can't set next track") def tick(self): now = datetime.now() self.current_time.setText(now.strftime("%H:%M:%S")) if self.music.playing(): playtime = self.music.get_current_playtime() self.label_elapsed_timer.setText(ms_to_mmss(playtime)) self.label_fade_timer.setText( ms_to_mmss(self.music.get_current_fade_at() - playtime)) self.label_silent_timer.setText( ms_to_mmss(self.music.get_current_silence_at() - playtime)) self.label_end_timer.setText( ms_to_mmss(self.music.get_current_duration() - playtime)) def add_to_playlist(self, track): """ Add track to playlist track is an instance of Track """ DEBUG(f"add_to_playlist: track.id={track.id}") pl = self.playlist row = pl.rowCount() pl.insertRow(row) item = QTableWidgetItem(str(track.id)) pl.setItem(row, 0, item) item = QTableWidgetItem(str(track.start_gap)) pl.setItem(row, 1, item) item = QTableWidgetItem(track.title) pl.setItem(row, 2, item) item = QTableWidgetItem(track.artist) pl.setItem(row, 3, item) item = QTableWidgetItem(ms_to_mmss(track.duration)) pl.setItem(row, 4, item) item = QTableWidgetItem(track.path) pl.setItem(row, 7, item) class DbDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_Dialog() self.ui.setupUi(self) self.ui.searchString.textEdited.connect(self.chars_typed) self.ui.matchList.itemDoubleClicked.connect(self.listdclick) record = Settings.get_int("dbdialog_width") width = record.f_int or 800 record = Settings.get_int("dbdialog_height") height = record.f_int or 600 self.resize(width, height) def __del__(self): record = Settings.get_int("dbdialog_height") if record.f_int != self.height(): record.update({'f_int': self.height()}) record = Settings.get_int("dbdialog_width") if record.f_int != self.width(): record.update({'f_int': self.width()}) def chars_typed(self, s): if len(s) >= 3: matches = Tracks.search_titles(s) self.ui.matchList.clear() if matches: for track in matches: t = QListWidgetItem() t.setText( f"{track.title} - {track.artist} " f"[{ms_to_mmss(track.duration)}]" ) t.setData(Qt.UserRole, track.id) self.ui.matchList.addItem(t) def listdclick(self, entry): track_id = entry.data(Qt.UserRole) track = Tracks.track_from_id(track_id) self.parent().add_to_playlist(track) def ms_to_mmss(ms, decimals=0, negative=False): if not ms: return "-" sign = "" if ms < 0: if negative: sign = "-" else: ms = 0 minutes, remainder = divmod(ms, 60 * 1000) seconds = remainder / 1000 return f"{sign}{minutes:.0f}:{seconds:02.{decimals}f}" def main(): app = QApplication(sys.argv) win = Window() win.show() sys.exit(app.exec()) if __name__ == "__main__": main()