diff --git a/freeze.py b/freeze.py index 3281cad..520b445 100644 --- a/freeze.py +++ b/freeze.py @@ -2,7 +2,7 @@ from cx_Freeze import setup, Executable import sys import os -from setup import VERSION +from setup import __version__ deps = [os.path.join('src', p) for p in os.listdir('src') if p] @@ -52,7 +52,7 @@ executables = [ setup( name='audio-visualizer-python', - version=VERSION, + version=__version__, description='GUI tool to render visualization videos of audio files', options=dict(build_exe=buildOptions), executables=executables diff --git a/setup.py b/setup.py index 5abb976..a2d8495 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup import os -VERSION = '2.0.0.rc1' +__version__ = '2.0.0.rc1' def package_files(directory): @@ -15,7 +15,7 @@ def package_files(directory): setup( name='audio_visualizer_python', - version=VERSION, + version=__version__, url='https://github.com/djfun/audio-visualizer-python/tree/feature-newgui', license='MIT', description='Create audio visualization videos from a GUI or commandline', diff --git a/src/__init__.py b/src/__init__.py index e69de29..8b13789 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -0,0 +1 @@ + diff --git a/src/components/video.py b/src/components/video.py index b35c2e5..8758b12 100644 --- a/src/components/video.py +++ b/src/components/video.py @@ -16,7 +16,7 @@ class Video: '''Video Component Frame-Fetcher''' def __init__(self, **kwargs): mandatoryArgs = [ - 'ffmpeg', # path to ffmpeg, usually core.FFMPEG_BIN + 'ffmpeg', # path to ffmpeg, usually Core.FFMPEG_BIN 'videoPath', 'width', 'height', @@ -28,7 +28,7 @@ class Video: ] for arg in mandatoryArgs: try: - exec('self.%s = kwargs[arg]' % arg) + setattr(self, arg, kwargs[arg]) except KeyError: raise BadComponentInit(arg, self.__doc__) diff --git a/src/core.py b/src/core.py index dd2ef18..f6cf5eb 100644 --- a/src/core.py +++ b/src/core.py @@ -15,16 +15,14 @@ import video_thread class Core: ''' MainWindow and Command module both use an instance of this class - to store the main program state. This object tracks the components - as an instance, has methods for managing the components and for - opening/creating project files and presets. + to store the core program state. This object tracks the components, + talks to the components and handles opening/creating project files + and presets. The class also stores constants as class variables. ''' @classmethod def storeSettings(cls): - ''' - Stores settings/paths to directories as class variables - ''' + '''Store settings/paths to directories as class variables.''' if getattr(sys, 'frozen', False): # frozen wd = os.path.dirname(sys.executable) diff --git a/src/mainwindow.py b/src/mainwindow.py index 9944d1a..2d598ae 100644 --- a/src/mainwindow.py +++ b/src/mainwindow.py @@ -178,7 +178,6 @@ class MainWindow(QtWidgets.QMainWindow): # Make component buttons self.compMenu = QMenu() - self.compActions = [] for i, comp in enumerate(self.core.modules): action = self.compMenu.addAction(comp.Component.name) action.triggered.connect( @@ -191,6 +190,9 @@ class MainWindow(QtWidgets.QMainWindow): componentList.itemSelectionChanged.connect( self.changeComponentWidget ) + componentList.itemSelectionChanged.connect( + self.presetManager.clearPresetListSelection + ) self.window.pushButton_removeComponent.clicked.connect( lambda: self.removeComponent() ) @@ -313,22 +315,23 @@ class MainWindow(QtWidgets.QMainWindow): ) self.settings.setValue("ffmpegMsgShown", True) - # Setup Hotkeys + # 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) - QtWidgets.QShortcut( - "Ctrl+Alt+Shift+R", self.window, self.drawPreview - ) - QtWidgets.QShortcut( - "Ctrl+Alt+Shift+F", self.window, self.showFfmpegCommand - ) - QtWidgets.QShortcut( - "Ctrl+T", self.window, - activated=lambda: self.window.pushButton_addComponent.click() - ) + # 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() @@ -342,22 +345,29 @@ class MainWindow(QtWidgets.QMainWindow): ) QtWidgets.QShortcut( - "Ctrl+Up", self.window, + "Ctrl+Up", self.window.listWidget_componentList, activated=lambda: self.moveComponent(-1) ) QtWidgets.QShortcut( - "Ctrl+Down", self.window, + "Ctrl+Down", self.window.listWidget_componentList, activated=lambda: self.moveComponent(1) ) QtWidgets.QShortcut( - "Ctrl+Home", self.window, + "Ctrl+Home", self.window.listWidget_componentList, activated=lambda: self.moveComponent('top') ) QtWidgets.QShortcut( - "Ctrl+End", self.window, + "Ctrl+End", self.window.listWidget_componentList, activated=lambda: self.moveComponent('bottom') ) - QtWidgets.QShortcut("Ctrl+r", self.window, self.removeComponent) + + # 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): @@ -677,9 +687,7 @@ class MainWindow(QtWidgets.QMainWindow): stackedWidget.setCurrentIndex(newRow) self.drawPreview() - @disableWhenEncoding - def dragComponent(self, event): - '''Used as Qt drop event for the component listwidget''' + def getComponentListRects(self): componentList = self.window.listWidget_componentList modelIndexes = [ @@ -690,6 +698,13 @@ class MainWindow(QtWidgets.QMainWindow): componentList.visualRect(modelIndex) for modelIndex in modelIndexes ] + return rects + + @disableWhenEncoding + def dragComponent(self, event): + '''Used as Qt drop event for the component listwidget''' + componentList = self.window.listWidget_componentList + rects = self.getComponentListRects() rowPos = [rect.contains(event.pos()) for rect in rects] if not any(rowPos): @@ -826,47 +841,63 @@ class MainWindow(QtWidgets.QMainWindow): @disableWhenEncoding def componentContextMenu(self, QPos): - '''Appears when right-clicking a component in the list''' + '''Appears when right-clicking the component list''' componentList = self.window.listWidget_componentList - if not componentList.selectedItems(): - return - - # don't show menu if clicking empty space - parentPosition = componentList.mapToGlobal(QtCore.QPoint(0, 0)) index = componentList.currentRow() - modelIndex = componentList.model().index(index) - if not componentList.visualRect(modelIndex).contains(QPos): - return - self.presetManager.findPresets() self.menu = QMenu() - menuItem = self.menu.addAction("Save Preset") - menuItem.triggered.connect( - self.presetManager.openSavePresetDialog - ) + parentPosition = componentList.mapToGlobal(QtCore.QPoint(0, 0)) - # submenu for opening presets - try: - presets = self.presetManager.presets[ - str(self.core.selectedComponents[index]) - ] - self.submenu = QMenu("Open Preset") - self.menu.addMenu(self.submenu) + rects = self.getComponentListRects() + rowPos = [rect.contains(QPos) for rect in rects] + if not any(rowPos): + # Insert components at the top if clicking nothing + rowPos = 0 + else: + rowPos = rowPos.index(True) - for version, presetName in presets: - menuItem = self.submenu.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") + if index == rowPos: + # Show preset menu if clicking a component + self.presetManager.findPresets() + menuItem = self.menu.addAction("Save Preset") menuItem.triggered.connect( - self.presetManager.clearPreset + 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) + for i, comp in enumerate(self.core.modules): + menuItem = self.submenu.addAction(comp.Component.name) + menuItem.triggered.connect( + lambda _, item=i: self.core.insertComponent( + rowPos, item, self + ) + ) + self.menu.move(parentPosition + QPos) self.menu.show() diff --git a/src/presetmanager.py b/src/presetmanager.py index 825fdee..64e2203 100644 --- a/src/presetmanager.py +++ b/src/presetmanager.py @@ -245,11 +245,25 @@ class PresetManager(QtWidgets.QDialog): def openRenamePresetDialog(self): # TODO: maintain consistency by changing this to call createNewPreset() presetList = self.window.listWidget_presets - if presetList.currentRow() == -1: - return + index = presetList.currentRow() + if index == -1: + # check if component selected in MainWindow has preset loaded + componentList = self.parent.window.listWidget_componentList + compIndex = componentList.currentRow() + if compIndex == -1: + return + preset = self.core.selectedComponents[compIndex].currentPreset + if not preset: + return + else: + for i, tup in enumerate(self.presetRows): + if preset == tup[2]: + index = i + break + else: + return while True: - index = presetList.currentRow() newName, OK = QtWidgets.QInputDialog.getText( self.window, 'Preset Manager', @@ -321,3 +335,6 @@ class PresetManager(QtWidgets.QDialog): parent=self.window ) self.settings.setValue("presetDir", os.path.dirname(filename)) + + def clearPresetListSelection(self): + self.window.listWidget_presets.setCurrentRow(-1) diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py index 89d4e9d..cc59a6c 100644 --- a/src/toolkit/ffmpeg.py +++ b/src/toolkit/ffmpeg.py @@ -113,7 +113,7 @@ def createFfmpegCommand(inputFile, outputFile, components, duration=-1): '-t', safeDuration, # Tell ffmpeg about shorter clips (seemingly not needed) # streamDuration = getAudioDuration(extraInputFile) - # if streamDuration > float(safeDuration) + # if streamDuration and streamDuration > float(safeDuration) # else "{0:.3f}".format(streamDuration), '-i', extraInputFile ]) @@ -228,11 +228,18 @@ def getAudioDuration(filename): d = d.split(' ')[3] d = d.split(':') duration = float(d[0])*3600 + float(d[1])*60 + float(d[2]) + break + else: + # String not found in output + return False return duration def readAudioFile(filename, parent): duration = getAudioDuration(filename) + if not duration: + print('Audio file doesn\'t exist or unreadable.') + return command = [ Core.FFMPEG_BIN,