from PyQt5.QtGui import * from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtMultimedia import * from PyQt5.QtMultimediaWidgets import * from MainWindow import Ui_MainWindow import midi class PlaylistModel(QAbstractListModel): def __init__(self, playlist, *args, **kwargs): super(PlaylistModel, self).__init__(*args, **kwargs) self.playlist = playlist def data(self, index, role): if role == Qt.DisplayRole: media = self.playlist.media(index.row()) return media.canonicalUrl().fileName() def rowCount(self, index): return self.playlist.mediaCount() class AbListModel(QAbstractListModel): def __init__(self, *args, **kwargs): super(AbListModel, self).__init__(*args, **kwargs) self.abList = list() def data(self, index, role): if role == Qt.DisplayRole: ab = self.abList[index.row()] return f"{hhmmss(ab[0])} - {hhmmss(ab[1])}" def rowCount(self, index): return len(self.abList) def hhmmss(ms): # s = 1000 # m = 60000 # h = 360000 h, r = divmod(ms, 36000) m, r = divmod(r, 60000) s, _ = divmod(r, 1000) return ("%d:%02d:%02d" % (h,m,s)) if h else ("%d:%02d" % (m,s)) class MainWindow(QMainWindow, Ui_MainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.setupUi(self) self.player = QMediaPlayer() # Setup the playlist. self.playlist = QMediaPlaylist() self.player.setPlaylist(self.playlist) self.playlistModel = PlaylistModel(self.playlist) self.songListView.setModel(self.playlistModel) self.playlist.currentIndexChanged.connect(self.playlistPositionChanged) selection_model = self.songListView.selectionModel() selection_model.selectionChanged.connect(self.playlistSelectionChanged) # set button context menu policy self.songListView.customContextMenuRequested.connect(self.onContextMenu) # create context menu self.songListMenu = QMenu(self) self.addSongAction.triggered.connect(self.openSoundFile) self.songListMenu.addAction(self.addSongAction) self.playPauseButton.pressed.connect(self.playPause) self.player.durationChanged.connect(self.updateDuration) self.player.positionChanged.connect(self.updatePosition) self.songSlider.valueChanged.connect(self.player.setPosition) self.player.positionChanged.connect(self.positionChanged) self.abListModel = AbListModel() self.abListView.setModel(self.abListModel) self.abListView.selectionModel().selectionChanged.connect(self.abListSelectionChanged) self.saveAbButton.pressed.connect(self.addAb) self.internalState = dict() self.saveSessionButton.pressed.connect(self.saveSession) self.loadSessionButton.pressed.connect(self.loadSession) self.abRepeatButton.clicked.connect(self.abRepeatToggleClick) self.initMidiButton.pressed.connect(self.initMidi) self.midiEnabled = False self.show() def playlistSelectionChanged(self, ix): i = ix.indexes()[0].row() self.playlist.setCurrentIndex(i) path = self.playlist.currentMedia().canonicalUrl().path() self.abListModel.abList = self.internalState[path] self.abListView.selectionModel().clearSelection() self.abListModel.layoutChanged.emit() self.aSlider.setValue(0) self.bSlider.setValue(0) def playlistPositionChanged(self, i): if i > -1: ix = self.playlistModel.index(i) self.songListView.setCurrentIndex(ix) def abListSelectionChanged(self, ix): if len(ix.indexes()) > 0: i = ix.indexes()[0].row() ab = self.abListModel.abList[i] self.aSlider.setValue(ab[0]) self.bSlider.setValue(ab[1]) def addAb(self, song=None, a=None, b=None): currentSong = song or self.playlist.currentMedia().canonicalUrl().path() abState = [a or self.aSlider.value(), b or self.bSlider.value()] self.abListModel.abList.append(abState) self.abListModel.layoutChanged.emit() def playPause(self): if self.player.state() == QMediaPlayer.PlayingState: self.player.pause() else: self.player.play() if self.midiEnabled: midi.button_on(midi.lp_key[0][1], (midi.GREEN if self.player.state() == QMediaPlayer.PlayingState else midi.RED)) def updateDuration(self, duration): self.songSlider.setMaximum(duration) self.aSlider.setMaximum(duration) self.bSlider.setMaximum(duration) def updatePosition(self, position): # Disable the events to prevent updating triggering a setPosition event (can cause stuttering). self.songSlider.blockSignals(True) self.songSlider.setValue(position) self.songSlider.blockSignals(False) def positionChanged(self, position): if self.abRepeatButton.isChecked() and position > self.bSlider.value(): self.player.setPosition(self.aSlider.value()) def onContextMenu(self, point): self.songListMenu.exec_(self.songListView.mapToGlobal(point)) def openSoundFile(self): path, _ = QFileDialog.getOpenFileName(self, "Open file", "", "mp3 Audio (*.mp3);FLAC audio (*.flac);All files (*.*)") if path: self.addSong(path) def addSong(self, path): self.playlist.addMedia(QMediaContent(QUrl.fromLocalFile(path))) self.internalState[path] = list() self.playlistModel.layoutChanged.emit() def loadSession(self): path, _ = QFileDialog.getOpenFileName(self, "Open file", "", "session (*.json)") if path: import json with open(path, "r") as f: session = json.load(f) self.reset() for song in session: self.addSong(song) self.abListModel.abList = self.internalState[song] for ab in session[song]: self.addAb(song=song, a=ab[0], b=ab[1]) def saveSession(self): path, _ = QFileDialog.getSaveFileName(self, "Save file", "", "session (*.json)") if path: import json with open(path, "w") as f: json.dump(self.internalState, f) def abRepeatToggleClick(self): midi.button_on(midi.lp_key[1][1], (midi.GREEN if self.abRepeatButton.isChecked() else midi.RED)) def initMidi(self): if self.midiEnabled: return try: midi.midi_init() except e: print(e) return self.midiEnabled = True # play pause midi.button_on(midi.lp_key[0][1], (midi.GREEN if self.player.state() == QMediaPlayer.PlayingState else midi.RED)) midi.on_press(midi.lp_key[0][1], self.playPauseButton.click) # next song midi.button_on(midi.lp_key[0][2], midi.YELLOW) midi.on_press(midi.lp_key[0][2], self.playlist.next) # previous song midi.button_on(midi.lp_key[0][0], midi.YELLOW) midi.on_press(midi.lp_key[0][0], self.playlist.previous) # a/b repeat midi.button_on(midi.lp_key[1][1], (midi.GREEN if self.abRepeatButton.isChecked() else midi.RED)) midi.on_press(midi.lp_key[1][1], self.abRepeatButton.click) # next ab # TODO continue here midi.button_on(midi.lp_key[1][2], midi.YELLOW) #midi.on_press(midi.lp_key[1][2], nextAb) def reset(self): self.playlist.clear() self.playlistModel.layoutChanged.emit() self.abListModel.abList.clear() self.abListModel.layoutChanged.emit() if __name__ == '__main__': app = QApplication([]) app.setApplicationName("Solo Tool") app.setStyle("Fusion") # Fusion dark palette from https://gist.github.com/QuantumCD/6245215. palette = QPalette() palette.setColor(QPalette.Window, QColor(53, 53, 53)) palette.setColor(QPalette.WindowText, Qt.white) palette.setColor(QPalette.Base, QColor(25, 25, 25)) palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53)) palette.setColor(QPalette.ToolTipBase, Qt.white) palette.setColor(QPalette.ToolTipText, Qt.white) palette.setColor(QPalette.Text, Qt.white) palette.setColor(QPalette.Button, QColor(53, 53, 53)) palette.setColor(QPalette.ButtonText, Qt.white) palette.setColor(QPalette.BrightText, Qt.red) palette.setColor(QPalette.Link, QColor(42, 130, 218)) palette.setColor(QPalette.Highlight, QColor(42, 130, 218)) palette.setColor(QPalette.HighlightedText, Qt.black) app.setPalette(palette) app.setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }") window = MainWindow() app.exec_()