diff --git a/.gitignore b/.gitignore index 7cec615..916c6c1 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ env/* ffmpeg *.bak *~ +*.goutput* \ No newline at end of file diff --git a/src/.goutputstream-67IS4Y b/src/.goutputstream-67IS4Y deleted file mode 100644 index 789a6e7..0000000 --- a/src/.goutputstream-67IS4Y +++ /dev/null @@ -1,976 +0,0 @@ -''' - When using GUI mode, this module's object (the main window) takes - user input to construct a program state (stored in the Core object). - This shows a preview of the video being created and allows for saving - projects and exporting the video at a later time. -''' -from PyQt5 import QtCore, QtGui, uic, QtWidgets -from PyQt5.QtWidgets import QMenu, QShortcut -from PIL import Image -from queue import Queue -import sys -import os -import signal -import filecmp -import time - -from core import Core -import preview_thread -from presetmanager import PresetManager -from toolkit import disableWhenEncoding, disableWhenOpeningProject, checkOutput - - -class PreviewWindow(QtWidgets.QLabel): - ''' - Paints the preview QLabel and maintains the aspect ratio when the - window is resized. - ''' - - def __init__(self, parent, img): - super(PreviewWindow, self).__init__() - self.parent = parent - self.setFrameStyle(QtWidgets.QFrame.StyledPanel) - self.pixmap = QtGui.QPixmap(img) - - def paintEvent(self, event): - size = self.size() - painter = QtGui.QPainter(self) - point = QtCore.QPoint(0, 0) - scaledPix = self.pixmap.scaled( - size, - QtCore.Qt.KeepAspectRatio, - transformMode=QtCore.Qt.SmoothTransformation) - - # start painting the label from left upper corner - point.setX((size.width() - scaledPix.width())/2) - point.setY((size.height() - scaledPix.height())/2) - painter.drawPixmap(point, scaledPix) - - def changePixmap(self, img): - self.pixmap = QtGui.QPixmap(img) - self.repaint() - - def mousePressEvent(self, event): - if self.parent.encoding: - return - - i = self.parent.window.listWidget_componentList.currentRow() - if i >= 0: - component = self.parent.core.selectedComponents[i] - if not hasattr(component, 'previewClickEvent'): - return - pos = (event.x(), event.y()) - size = (self.width(), self.height()) - component.previewClickEvent( - pos, size, event.button() - ) - self.parent.core.updateComponent(i) - - @QtCore.pyqtSlot(str) - def threadError(self, msg): - self.parent.showMessage( - msg=msg, - icon='Critical', - parent=self - ) - - -class MainWindow(QtWidgets.QMainWindow): - ''' - The MainWindow wraps many Core methods in order to update the GUI - accordingly. E.g., instead of self.core.openProject(), it will use - self.openProject() and update the window titlebar within the wrapper. - - MainWindow manages the autosave feature, although Core has the - primary functions for opening and creating project files. - ''' - - createVideo = QtCore.pyqtSignal() - newTask = QtCore.pyqtSignal(list) # for the preview window - processTask = QtCore.pyqtSignal() - - def __init__(self, window, project): - QtWidgets.QMainWindow.__init__(self) - # print('main thread id: {}'.format(QtCore.QThread.currentThreadId())) - self.window = window - self.core = Core() - - # widgets of component settings - self.pages = [] - self.lastAutosave = time.time() - # list of previous five autosave times, used to reduce update spam - self.autosaveTimes = [] - self.autosaveCooldown = 0.2 - self.encoding = False - - # Create data directory, load/create settings - self.dataDir = Core.dataDir - self.presetDir = Core.presetDir - self.autosavePath = os.path.join(self.dataDir, 'autosave.avp') - self.settings = Core.settings - self.presetManager = PresetManager( - uic.loadUi( - os.path.join(Core.wd, 'presetmanager.ui')), self) - - if not os.path.exists(self.dataDir): - os.makedirs(self.dataDir) - for neededDirectory in ( - self.presetDir, self.settings.value("projectDir")): - if not os.path.exists(neededDirectory): - os.mkdir(neededDirectory) - - # Create the preview window and its thread, queues, and timers - self.previewWindow = PreviewWindow(self, os.path.join( - Core.wd, "background.png")) - window.verticalLayout_previewWrapper.addWidget(self.previewWindow) - - self.previewQueue = Queue() - self.previewThread = QtCore.QThread(self) - self.previewWorker = preview_thread.Worker(self, self.previewQueue) - self.previewWorker.error.connect(self.previewWindow.threadError) - self.previewWorker.moveToThread(self.previewThread) - self.previewWorker.imageCreated.connect(self.showPreviewImage) - self.previewThread.start() - - self.timer = QtCore.QTimer(self) - self.timer.timeout.connect(self.processTask.emit) - self.timer.start(500) - - # Begin decorating the window and connecting events - self.window.installEventFilter(self) - componentList = self.window.listWidget_componentList - - if sys.platform == 'darwin': - window.progressBar_createVideo.setTextVisible(False) - else: - window.progressLabel.setHidden(True) - - window.toolButton_selectAudioFile.clicked.connect( - self.openInputFileDialog) - - window.toolButton_selectOutputFile.clicked.connect( - self.openOutputFileDialog) - - def changedField(): - self.autosave() - self.updateWindowTitle() - - window.lineEdit_audioFile.textChanged.connect(changedField) - window.lineEdit_outputFile.textChanged.connect(changedField) - - window.progressBar_createVideo.setValue(0) - - window.pushButton_createVideo.clicked.connect( - self.createAudioVisualisation) - - window.pushButton_Cancel.clicked.connect(self.stopVideo) - - for i, container in enumerate(Core.encoderOptions['containers']): - window.comboBox_videoContainer.addItem(container['name']) - if container['name'] == self.settings.value('outputContainer'): - selectedContainer = i - - window.comboBox_videoContainer.setCurrentIndex(selectedContainer) - window.comboBox_videoContainer.currentIndexChanged.connect( - self.updateCodecs - ) - - self.updateCodecs() - - for i in range(window.comboBox_videoCodec.count()): - codec = window.comboBox_videoCodec.itemText(i) - if codec == self.settings.value('outputVideoCodec'): - window.comboBox_videoCodec.setCurrentIndex(i) - - for i in range(window.comboBox_audioCodec.count()): - codec = window.comboBox_audioCodec.itemText(i) - if codec == self.settings.value('outputAudioCodec'): - window.comboBox_audioCodec.setCurrentIndex(i) - - window.comboBox_videoCodec.currentIndexChanged.connect( - self.updateCodecSettings - ) - - window.comboBox_audioCodec.currentIndexChanged.connect( - self.updateCodecSettings - ) - - vBitrate = int(self.settings.value('outputVideoBitrate')) - aBitrate = int(self.settings.value('outputAudioBitrate')) - - window.spinBox_vBitrate.setValue(vBitrate) - window.spinBox_aBitrate.setValue(aBitrate) - window.spinBox_vBitrate.valueChanged.connect(self.updateCodecSettings) - window.spinBox_aBitrate.valueChanged.connect(self.updateCodecSettings) - - # Make component buttons - self.compMenu = QMenu() - for i, comp in enumerate(self.core.modules): - action = self.compMenu.addAction(comp.Component.name) - action.triggered.connect( - lambda _, item=i: self.core.insertComponent(0, item, self) - ) - - self.window.pushButton_addComponent.setMenu(self.compMenu) - - componentList.dropEvent = self.dragComponent - componentList.itemSelectionChanged.connect( - self.changeComponentWidget - ) - componentList.itemSelectionChanged.connect( - self.presetManager.clearPresetListSelection - ) - self.window.pushButton_removeComponent.clicked.connect( - lambda: self.removeComponent() - ) - - componentList.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) - componentList.customContextMenuRequested.connect( - self.componentContextMenu - ) - - currentRes = str(self.settings.value('outputWidth'))+'x' + \ - str(self.settings.value('outputHeight')) - for i, res in enumerate(Core.resolutions): - window.comboBox_resolution.addItem(res) - if res == currentRes: - currentRes = i - window.comboBox_resolution.setCurrentIndex(currentRes) - window.comboBox_resolution.currentIndexChanged.connect( - self.updateResolution - ) - - self.window.pushButton_listMoveUp.clicked.connect( - lambda: self.moveComponent(-1) - ) - self.window.pushButton_listMoveDown.clicked.connect( - lambda: self.moveComponent(1) - ) - - # Configure the Projects Menu - self.projectMenu = QMenu() - self.window.menuButton_newProject = self.projectMenu.addAction( - "New Project" - ) - self.window.menuButton_newProject.triggered.connect( - lambda: self.createNewProject() - ) - self.window.menuButton_openProject = self.projectMenu.addAction( - "Open Project" - ) - self.window.menuButton_openProject.triggered.connect( - lambda: self.openOpenProjectDialog() - ) - - action = self.projectMenu.addAction("Save Project") - action.triggered.connect(self.saveCurrentProject) - - action = self.projectMenu.addAction("Save Project As") - action.triggered.connect(self.openSaveProjectDialog) - - self.window.pushButton_projects.setMenu(self.projectMenu) - - # Configure the Presets Button - self.window.pushButton_presets.clicked.connect( - self.openPresetManager - ) - - self.updateWindowTitle() - window.show() - - if project and project != self.autosavePath: - if not project.endswith('.avp'): - project += '.avp' - # open a project from the commandline - if not os.path.dirname(project): - project = os.path.join( - self.settings.value("projectDir"), project - ) - self.currentProject = project - self.settings.setValue("currentProject", project) - if os.path.exists(self.autosavePath): - os.remove(self.autosavePath) - else: - # open the last currentProject from settings - self.currentProject = self.settings.value("currentProject") - - # delete autosave if it's identical to this project - if self.autosaveExists(identical=True): - os.remove(self.autosavePath) - - if self.currentProject and os.path.exists(self.autosavePath): - ch = self.showMessage( - msg="Restore unsaved changes in project '%s'?" - % os.path.basename(self.currentProject)[:-4], - showCancel=True) - if ch: - self.saveProjectChanges() - else: - os.remove(self.autosavePath) - - self.openProject(self.currentProject, prompt=False) - self.drawPreview(True) - - # verify Pillow version - if not self.settings.value("pilMsgShown") \ - and 'post' not in Image.PILLOW_VERSION: - self.showMessage( - msg="You are using the standard version of the " - "Python imaging library (Pillow %s). Upgrade " - "to the Pillow-SIMD fork to enable hardware accelerations " - "and export videos faster." % Image.PILLOW_VERSION - ) - self.settings.setValue("pilMsgShown", True) - - # verify Ffmpeg version - if not self.settings.value("ffmpegMsgShown"): - try: - with open(os.devnull, "w") as f: - ffmpegVers = checkOutput( - ['ffmpeg', '-version'], stderr=f - ) - goodVersion = str(ffmpegVers).split()[2].startswith('3') - except Exception: - goodVersion = False - else: - goodVersion = True - - if not goodVersion: - self.showMessage( - msg="You're using an old version of Ffmpeg. " - "Some features may not work as expected." - ) - self.settings.setValue("ffmpegMsgShown", True) - - # Hotkeys for projects - QtWidgets.QShortcut("Ctrl+S", self.window, self.saveCurrentProject) - QtWidgets.QShortcut("Ctrl+A", self.window, self.openSaveProjectDialog) - QtWidgets.QShortcut("Ctrl+O", self.window, self.openOpenProjectDialog) - QtWidgets.QShortcut("Ctrl+N", self.window, self.createNewProject) - - # Hotkeys for component list - for inskey in ("Ctrl+T", QtCore.Qt.Key_Insert): - QtWidgets.QShortcut( - inskey, self.window, - activated=lambda: self.window.pushButton_addComponent.click() - ) - for delkey in ("Ctrl+R", QtCore.Qt.Key_Delete): - QtWidgets.QShortcut( - delkey, self.window.listWidget_componentList, - self.removeComponent - ) - QtWidgets.QShortcut( - "Ctrl+Space", self.window, - activated=lambda: self.window.listWidget_componentList.setFocus() - ) - QtWidgets.QShortcut( - "Ctrl+Shift+S", self.window, - self.presetManager.openSavePresetDialog - ) - QtWidgets.QShortcut( - "Ctrl+Shift+C", self.window, self.presetManager.clearPreset - ) - - QtWidgets.QShortcut( - "Ctrl+Up", self.window.listWidget_componentList, - activated=lambda: self.moveComponent(-1) - ) - QtWidgets.QShortcut( - "Ctrl+Down", self.window.listWidget_componentList, - activated=lambda: self.moveComponent(1) - ) - QtWidgets.QShortcut( - "Ctrl+Home", self.window.listWidget_componentList, - activated=lambda: self.moveComponent('top') - ) - QtWidgets.QShortcut( - "Ctrl+End", self.window.listWidget_componentList, - activated=lambda: self.moveComponent('bottom') - ) - - # Debug Hotkeys - QtWidgets.QShortcut( - "Ctrl+Alt+Shift+R", self.window, self.drawPreview - ) - QtWidgets.QShortcut( - "Ctrl+Alt+Shift+F", self.window, self.showFfmpegCommand - ) - - @QtCore.pyqtSlot() - def cleanUp(self, *args): - self.timer.stop() - self.previewThread.quit() - self.previewThread.wait() - - @disableWhenOpeningProject - def updateWindowTitle(self): - appName = 'Audio Visualizer' - try: - if self.currentProject: - appName += ' - %s' % \ - os.path.splitext( - os.path.basename(self.currentProject))[0] - if self.autosaveExists(identical=False): - appName += '*' - except AttributeError: - pass - self.window.setWindowTitle(appName) - - @QtCore.pyqtSlot(int, dict) - def updateComponentTitle(self, pos, presetStore=False): - if type(presetStore) == dict: - name = presetStore['preset'] - if name is None or name not in self.core.savedPresets: - modified = False - else: - modified = (presetStore != self.core.savedPresets[name]) - else: - modified = bool(presetStore) - if pos < 0: - pos = len(self.core.selectedComponents)-1 - title = str(self.core.selectedComponents[pos]) - if self.core.selectedComponents[pos].currentPreset: - title += ' - %s' % self.core.selectedComponents[pos].currentPreset - if modified: - title += '*' - self.window.listWidget_componentList.item(pos).setText(title) - - def updateCodecs(self): - containerWidget = self.window.comboBox_videoContainer - vCodecWidget = self.window.comboBox_videoCodec - aCodecWidget = self.window.comboBox_audioCodec - index = containerWidget.currentIndex() - name = containerWidget.itemText(index) - self.settings.setValue('outputContainer', name) - - vCodecWidget.clear() - aCodecWidget.clear() - - for container in Core.encoderOptions['containers']: - if container['name'] == name: - for vCodec in container['video-codecs']: - vCodecWidget.addItem(vCodec) - for aCodec in container['audio-codecs']: - aCodecWidget.addItem(aCodec) - - def updateCodecSettings(self): - '''Updates settings.ini to match encoder option widgets''' - vCodecWidget = self.window.comboBox_videoCodec - vBitrateWidget = self.window.spinBox_vBitrate - aBitrateWidget = self.window.spinBox_aBitrate - aCodecWidget = self.window.comboBox_audioCodec - currentVideoCodec = vCodecWidget.currentIndex() - currentVideoCodec = vCodecWidget.itemText(currentVideoCodec) - currentVideoBitrate = vBitrateWidget.value() - currentAudioCodec = aCodecWidget.currentIndex() - currentAudioCodec = aCodecWidget.itemText(currentAudioCodec) - currentAudioBitrate = aBitrateWidget.value() - self.settings.setValue('outputVideoCodec', currentVideoCodec) - self.settings.setValue('outputAudioCodec', currentAudioCodec) - self.settings.setValue('outputVideoBitrate', currentVideoBitrate) - self.settings.setValue('outputAudioBitrate', currentAudioBitrate) - - @disableWhenOpeningProject - def autosave(self, force=False): - if not self.currentProject: - if os.path.exists(self.autosavePath): - os.remove(self.autosavePath) - elif force or time.time() - self.lastAutosave >= self.autosaveCooldown: - self.core.createProjectFile(self.autosavePath, self.window) - self.lastAutosave = time.time() - if len(self.autosaveTimes) >= 5: - # Do some math to reduce autosave spam. This gives a smooth - # curve up to 5 seconds cooldown and maintains that for 30 secs - # if a component is continuously updated - timeDiff = self.lastAutosave - self.autosaveTimes.pop() - if not force and timeDiff >= 1.0 \ - and timeDiff <= 10.0: - if self.autosaveCooldown / 4.0 < 0.5: - self.autosaveCooldown += 1.0 - self.autosaveCooldown = ( - 5.0 * (self.autosaveCooldown / 5.0) - ) + (self.autosaveCooldown / 5.0) * 2 - elif force or timeDiff >= self.autosaveCooldown * 5: - self.autosaveCooldown = 0.2 - self.autosaveTimes.insert(0, self.lastAutosave) - - def autosaveExists(self, identical=True): - '''Determines if creating the autosave should be blocked.''' - try: - if self.currentProject and os.path.exists(self.autosavePath) \ - and filecmp.cmp( - self.autosavePath, self.currentProject) == identical: - return True - except FileNotFoundError: - print('project file couldn\'t be located:', self.currentProject) - return identical - return False - - def saveProjectChanges(self): - '''Overwrites project file with autosave file''' - try: - os.remove(self.currentProject) - os.rename(self.autosavePath, self.currentProject) - return True - except (FileNotFoundError, IsADirectoryError) as e: - self.showMessage( - msg='Project file couldn\'t be saved.', - detail=str(e)) - return False - - def openInputFileDialog(self): - inputDir = self.settings.value("inputDir", os.path.expanduser("~")) - - fileName, _ = QtWidgets.QFileDialog.getOpenFileName( - self.window, "Open Audio File", - inputDir, "Audio Files (%s)" % " ".join(Core.audioFormats)) - - if fileName: - self.settings.setValue("inputDir", os.path.dirname(fileName)) - self.window.lineEdit_audioFile.setText(fileName) - - def openOutputFileDialog(self): - outputDir = self.settings.value("outputDir", os.path.expanduser("~")) - - fileName, _ = QtWidgets.QFileDialog.getSaveFileName( - self.window, "Set Output Video File", - outputDir, - "Video Files (%s);; All Files (*)" % " ".join( - Core.videoFormats)) - - if fileName: - self.settings.setValue("outputDir", os.path.dirname(fileName)) - self.window.lineEdit_outputFile.setText(fileName) - - def stopVideo(self): - print('stop') - self.videoWorker.cancel() - self.canceled = True - - def createAudioVisualisation(self): - # create output video if mandatory settings are filled in - audioFile = self.window.lineEdit_audioFile.text() - outputPath = self.window.lineEdit_outputFile.text() - - if audioFile and outputPath and self.core.selectedComponents: - if not os.path.dirname(outputPath): - outputPath = os.path.join( - os.path.expanduser("~"), outputPath) - if outputPath and os.path.isdir(outputPath): - self.showMessage( - msg='Chosen filename matches a directory, which ' - 'cannot be overwritten. Please choose a different ' - 'filename or move the directory.', - icon='Warning', - ) - return - else: - if not audioFile or not outputPath: - self.showMessage( - msg="You must select an audio file and output filename." - ) - elif not self.core.selectedComponents: - self.showMessage( - msg="Not enough components." - ) - return - - self.canceled = False - self.progressBarUpdated(-1) - self.videoWorker = self.core.newVideoWorker( - self, audioFile, outputPath - ) - self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated) - self.videoWorker.progressBarSetText.connect( - self.progressBarSetText) - self.videoWorker.imageCreated.connect(self.showPreviewImage) - self.videoWorker.encoding.connect(self.changeEncodingStatus) - self.createVideo.emit() - - @QtCore.pyqtSlot(str, str) - def videoThreadError(self, msg, detail): - try: - self.stopVideo() - except AttributeError as e: - if 'videoWorker' not in str(e): - raise - self.showMessage( - msg=msg, - detail=detail, - icon='Critical', - ) - - def changeEncodingStatus(self, status): - self.encoding = status - if status: - self.window.pushButton_createVideo.setEnabled(False) - self.window.pushButton_Cancel.setEnabled(True) - self.window.comboBox_resolution.setEnabled(False) - self.window.stackedWidget.setEnabled(False) - self.window.tab_encoderSettings.setEnabled(False) - self.window.label_audioFile.setEnabled(False) - self.window.toolButton_selectAudioFile.setEnabled(False) - self.window.label_outputFile.setEnabled(False) - self.window.toolButton_selectOutputFile.setEnabled(False) - self.window.lineEdit_audioFile.setEnabled(False) - self.window.lineEdit_outputFile.setEnabled(False) - self.window.pushButton_addComponent.setEnabled(False) - self.window.pushButton_removeComponent.setEnabled(False) - self.window.pushButton_listMoveDown.setEnabled(False) - self.window.pushButton_listMoveUp.setEnabled(False) - self.window.menuButton_newProject.setEnabled(False) - self.window.menuButton_openProject.setEnabled(False) - if sys.platform == 'darwin': - self.window.progressLabel.setHidden(False) - else: - self.window.listWidget_componentList.setEnabled(False) - else: - self.window.pushButton_createVideo.setEnabled(True) - self.window.pushButton_Cancel.setEnabled(False) - self.window.comboBox_resolution.setEnabled(True) - self.window.stackedWidget.setEnabled(True) - self.window.tab_encoderSettings.setEnabled(True) - self.window.label_audioFile.setEnabled(True) - self.window.toolButton_selectAudioFile.setEnabled(True) - self.window.lineEdit_audioFile.setEnabled(True) - self.window.label_outputFile.setEnabled(True) - self.window.toolButton_selectOutputFile.setEnabled(True) - self.window.lineEdit_outputFile.setEnabled(True) - self.window.pushButton_addComponent.setEnabled(True) - self.window.pushButton_removeComponent.setEnabled(True) - self.window.pushButton_listMoveDown.setEnabled(True) - self.window.pushButton_listMoveUp.setEnabled(True) - self.window.menuButton_newProject.setEnabled(True) - self.window.menuButton_openProject.setEnabled(True) - self.window.listWidget_componentList.setEnabled(True) - self.window.progressLabel.setHidden(True) - self.drawPreview(True) - - @QtCore.pyqtSlot(int) - def progressBarUpdated(self, value): - self.window.progressBar_createVideo.setValue(value) - - @QtCore.pyqtSlot(str) - def progressBarSetText(self, value): - if sys.platform == 'darwin': - self.window.progressLabel.setText(value) - else: - self.window.progressBar_createVideo.setFormat(value) - - def updateResolution(self): - resIndex = int(self.window.comboBox_resolution.currentIndex()) - res = Core.resolutions[resIndex].split('x') - changed = res[0] != self.settings.value("outputWidth") - self.settings.setValue('outputWidth', res[0]) - self.settings.setValue('outputHeight', res[1]) - if changed: - for i in range(len(self.core.selectedComponents)): - self.core.updateComponent(i) - - def drawPreview(self, force=False, **kwargs): - '''Use autosave keyword arg to force saving or not saving if needed''' - self.newTask.emit(self.core.selectedComponents) - # self.processTask.emit() - if force or 'autosave' in kwargs: - if force or kwargs['autosave']: - self.autosave(True) - else: - self.autosave() - self.updateWindowTitle() - - @QtCore.pyqtSlot(QtGui.QImage) - def showPreviewImage(self, image): - self.previewWindow.changePixmap(image) - - def showFfmpegCommand(self): - from textwrap import wrap - from toolkit.ffmpeg import createFfmpegCommand - command = createFfmpegCommand( - self.window.lineEdit_audioFile.text(), - self.window.lineEdit_outputFile.text(), - self.core.selectedComponents - ) - lines = wrap(" ".join(command), 49) - self.showMessage( - msg="Current FFmpeg command:\n\n %s" % " ".join(lines) - ) - - def insertComponent(self, index): - componentList = self.window.listWidget_componentList - stackedWidget = self.window.stackedWidget - - componentList.insertItem( - index, - self.core.selectedComponents[index].name) - componentList.setCurrentRow(index) - - # connect to signal that adds an asterisk when modified - self.core.selectedComponents[index].modified.connect( - self.updateComponentTitle) - - self.pages.insert(index, self.core.selectedComponents[index].page) - stackedWidget.insertWidget(index, self.pages[index]) - stackedWidget.setCurrentIndex(index) - - return index - - def removeComponent(self): - componentList = self.window.listWidget_componentList - - for selected in componentList.selectedItems(): - index = componentList.row(selected) - self.window.stackedWidget.removeWidget(self.pages[index]) - componentList.takeItem(index) - self.core.removeComponent(index) - self.pages.pop(index) - self.changeComponentWidget() - self.drawPreview() - - @disableWhenEncoding - def moveComponent(self, change): - '''Moves a component relatively from its current position''' - componentList = self.window.listWidget_componentList - if change == 'top': - change = -componentList.currentRow() - elif change == 'bottom': - change = len(componentList)-componentList.currentRow()-1 - stackedWidget = self.window.stackedWidget - - row = componentList.currentRow() - newRow = row + change - if newRow > -1 and newRow < componentList.count(): - self.core.moveComponent(row, newRow) - - # update widgets - page = self.pages.pop(row) - self.pages.insert(newRow, page) - item = componentList.takeItem(row) - newItem = componentList.insertItem(newRow, item) - widget = stackedWidget.removeWidget(page) - stackedWidget.insertWidget(newRow, page) - componentList.setCurrentRow(newRow) - stackedWidget.setCurrentIndex(newRow) - self.drawPreview(True) - - def getComponentListMousePos(self, position): - ''' - Given a QPos, returns the component index under the mouse cursor - or -1 if no component is there. - ''' - componentList = self.window.listWidget_componentList - - modelIndexes = [ - componentList.model().index(i) - for i in range(componentList.count()) - ] - rects = [ - componentList.visualRect(modelIndex) - for modelIndex in modelIndexes - ] - mousePos = [rect.contains(position) for rect in rects] - if not any(mousePos): - # Not clicking a component - mousePos = -1 - else: - mousePos = mousePos.index(True) - return mousePos - - @disableWhenEncoding - def dragComponent(self, event): - '''Used as Qt drop event for the component listwidget''' - componentList = self.window.listWidget_componentList - mousePos = self.getComponentListMousePos(event.pos()) - if mousePos > -1: - change = (componentList.currentRow() - mousePos) * -1 - else: - change = (componentList.count() - componentList.currentRow() - 1) - self.moveComponent(change) - - def changeComponentWidget(self): - selected = self.window.listWidget_componentList.selectedItems() - if selected: - index = self.window.listWidget_componentList.row(selected[0]) - self.window.stackedWidget.setCurrentIndex(index) - - def openPresetManager(self): - '''Preset manager for importing, exporting, renaming, deleting''' - self.presetManager.show() - - def clear(self): - '''Get a blank slate''' - self.core.clearComponents() - self.window.listWidget_componentList.clear() - for widget in self.pages: - self.window.stackedWidget.removeWidget(widget) - self.pages = [] - for field in ( - self.window.lineEdit_audioFile, - self.window.lineEdit_outputFile - ): - field.blockSignals(True) - field.setText('') - field.blockSignals(False) - self.progressBarUpdated(0) - self.progressBarSetText('') - - @disableWhenEncoding - def createNewProject(self, prompt=True): - if prompt: - self.openSaveChangesDialog('starting a new project') - - self.clear() - self.currentProject = None - self.settings.setValue("currentProject", None) - self.drawPreview(True) - - def saveCurrentProject(self): - if self.currentProject: - self.core.createProjectFile(self.currentProject, self.window) - try: - os.remove(self.autosavePath) - except FileNotFoundError: - pass - self.updateWindowTitle() - else: - self.openSaveProjectDialog() - - def openSaveChangesDialog(self, phrase): - success = True - if self.autosaveExists(identical=False): - ch = self.showMessage( - msg="You have unsaved changes in project '%s'. " - "Save before %s?" % ( - os.path.basename(self.currentProject)[:-4], - phrase - ), - showCancel=True) - if ch: - success = self.saveProjectChanges() - - if success and os.path.exists(self.autosavePath): - os.remove(self.autosavePath) - - def openSaveProjectDialog(self): - filename, _ = QtWidgets.QFileDialog.getSaveFileName( - self.window, "Create Project File", - self.settings.value("projectDir"), - "Project Files (*.avp)") - if not filename: - return - if not filename.endswith(".avp"): - filename += '.avp' - self.settings.setValue("projectDir", os.path.dirname(filename)) - self.settings.setValue("currentProject", filename) - self.currentProject = filename - self.core.createProjectFile(filename, self.window) - self.updateWindowTitle() - - @disableWhenEncoding - def openOpenProjectDialog(self): - filename, _ = QtWidgets.QFileDialog.getOpenFileName( - self.window, "Open Project File", - self.settings.value("projectDir"), - "Project Files (*.avp)") - self.openProject(filename) - - def openProject(self, filepath, prompt=True): - if not filepath or not os.path.exists(filepath) \ - or not filepath.endswith('.avp'): - return - - self.clear() - # ask to save any changes that are about to get deleted - if prompt: - self.openSaveChangesDialog('opening another project') - - self.currentProject = filepath - self.settings.setValue("currentProject", filepath) - self.settings.setValue("projectDir", os.path.dirname(filepath)) - # actually load the project using core method - self.core.openProject(self, filepath) - self.drawPreview(autosave=False) - self.updateWindowTitle() - - def showMessage(self, **kwargs): - parent = kwargs['parent'] if 'parent' in kwargs else self.window - msg = QtWidgets.QMessageBox(parent) - msg.setModal(True) - msg.setText(kwargs['msg']) - msg.setIcon( - eval('QtWidgets.QMessageBox.%s' % kwargs['icon']) - if 'icon' in kwargs else QtWidgets.QMessageBox.Information - ) - msg.setDetailedText(kwargs['detail'] if 'detail' in kwargs else None) - if 'showCancel'in kwargs and kwargs['showCancel']: - msg.setStandardButtons( - QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) - else: - msg.setStandardButtons(QtWidgets.QMessageBox.Ok) - ch = msg.exec_() - if ch == 1024: - return True - return False - - @disableWhenEncoding - def componentContextMenu(self, QPos): - '''Appears when right-clicking the component list''' - componentList = self.window.listWidget_componentList - self.menu = QMenu() - parentPosition = componentList.mapToGlobal(QtCore.QPoint(0, 0)) - - index = self.getComponentListMousePos(QPos) - if index > -1: - # Show preset menu if clicking a component - self.presetManager.findPresets() - menuItem = self.menu.addAction("Save Preset") - menuItem.triggered.connect( - self.presetManager.openSavePresetDialog - ) - - # submenu for opening presets - try: - presets = self.presetManager.presets[ - str(self.core.selectedComponents[index]) - ] - self.presetSubmenu = QMenu("Open Preset") - self.menu.addMenu(self.presetSubmenu) - - for version, presetName in presets: - menuItem = self.presetSubmenu.addAction(presetName) - menuItem.triggered.connect( - lambda _, presetName=presetName: - self.presetManager.openPreset(presetName) - ) - except KeyError: - pass - - if self.core.selectedComponents[index].currentPreset: - menuItem = self.menu.addAction("Clear Preset") - menuItem.triggered.connect( - self.presetManager.clearPreset - ) - self.menu.addSeparator() - - # "Add Component" submenu - self.submenu = QMenu("Add") - self.menu.addMenu(self.submenu) - insertCompAtTop = self.settings.value("pref_insertCompAtTop") - for i, comp in enumerate(self.core.modules): - menuItem = self.submenu.addAction(comp.Component.name) - menuItem.triggered.connect( - lambda _, item=i: self.core.insertComponent( - 0 if insertCompAtTop else index, item, self - ) - ) - - self.menu.move(parentPosition + QPos) - self.menu.show() - - def eventFilter(self, object, event): - if event.type() == QtCore.QEvent.WindowActivate \ - or event.type() == QtCore.QEvent.FocusIn: - Core.windowHasFocus = True - elif event.type() == QtCore.QEvent.WindowDeactivate \ - or event.type() == QtCore.QEvent.FocusOut: - Core.windowHasFocus = False - return False