From 044fddfa9c5063f61e4a97993efe7cd5b2bae066 Mon Sep 17 00:00:00 2001 From: tassaron Date: Sun, 18 Jun 2017 14:46:08 -0400 Subject: basic commandline functionality using 3 args needs more args so components can be modified without gui --- mainwindow.py | 76 +++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 42 insertions(+), 34 deletions(-) (limited to 'mainwindow.py') diff --git a/mainwindow.py b/mainwindow.py index f722158..2a8762d 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -45,7 +45,7 @@ class MainWindow(QtCore.QObject): processTask = QtCore.pyqtSignal() videoTask = QtCore.pyqtSignal(str, str, list) - def __init__(self, window): + def __init__(self, window, project): QtCore.QObject.__init__(self) # print('main thread id: {}'.format(QtCore.QThread.currentThreadId())) @@ -151,7 +151,7 @@ class MainWindow(QtCore.QObject): for i, comp in enumerate(self.core.modules): action = self.compMenu.addAction(comp.Component.__doc__) action.triggered[()].connect( - lambda item=i: self.insertComponent(item)) + lambda item=i: self.core.insertComponent(0, item, self)) self.window.pushButton_addComponent.setMenu(self.compMenu) @@ -211,24 +211,36 @@ class MainWindow(QtCore.QObject): self.openPresetManager ) - # Show the window and load current project window.show() - self.currentProject = self.settings.value("currentProject") - if self.autosaveExists(identical=True): - # delete autosave if it's identical to the project - 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: + if project and project != self.autosavePath: + # open a project from the commandline + if not os.path.dirname(project): + project = os.path.join(os.path.expanduser('~'), 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() + self.drawPreview(True) def cleanUp(self): self.timer.stop() @@ -240,7 +252,8 @@ class MainWindow(QtCore.QObject): appName = 'Audio Visualizer' if self.currentProject: appName += ' - %s' % \ - os.path.basename(self.currentProject)[:-4] + os.path.splitext( + os.path.basename(self.currentProject))[0] self.window.setWindowTitle(appName) @QtCore.pyqtSlot(int, dict) @@ -252,7 +265,6 @@ class MainWindow(QtCore.QObject): else: modified = (presetStore != self.core.savedPresets[name]) else: - print(pos, presetStore) modified = bool(presetStore) if pos < 0: pos = len(self.core.selectedComponents)-1 @@ -306,10 +318,14 @@ class MainWindow(QtCore.QObject): self.lastAutosave = time.time() def autosaveExists(self, identical=True): - if self.currentProject and os.path.exists(self.autosavePath) \ - and filecmp.cmp( - self.autosavePath, self.currentProject) == identical: - return True + 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): @@ -411,6 +427,7 @@ class MainWindow(QtCore.QObject): self.window.listWidget_componentList.setEnabled(True) self.window.menuButton_newProject.setEnabled(True) self.window.menuButton_openProject.setEnabled(True) + self.drawPreview(True) def progressBarUpdated(self, value): self.window.progressBar_createVideo.setValue(value) @@ -437,19 +454,11 @@ class MainWindow(QtCore.QObject): def showPreviewImage(self, image): self.previewWindow.changePixmap(image) - def insertComponent(self, moduleIndex, compPos=0): + def insertComponent(self, index): componentList = self.window.listWidget_componentList stackedWidget = self.window.stackedWidget - if compPos < 0: - compPos = componentList.count() - - index = self.core.insertComponent( - compPos, moduleIndex) - if index == None: - self.showMessage(msg="Too many components!") - return None - row = componentList.insertItem( + componentList.insertItem( index, self.core.selectedComponents[index].__doc__) componentList.setCurrentRow(index) @@ -458,11 +467,10 @@ class MainWindow(QtCore.QObject): self.core.selectedComponents[index].modified.connect( self.updateComponentTitle) - self.pages.insert(index, self.core.selectedComponents[index].widget(self)) + self.pages.insert(index, self.core.selectedComponents[index].page) stackedWidget.insertWidget(index, self.pages[index]) stackedWidget.setCurrentIndex(index) - self.core.updateComponent(index) return index def removeComponent(self): -- cgit v1.2.3 From 5c74d496a960042ed4a4279328dc81e23dfdc1d9 Mon Sep 17 00:00:00 2001 From: tassaron Date: Thu, 22 Jun 2017 18:40:34 -0400 Subject: preset-loading and basic args from commandline also made some docstrings more informative --- command.py | 30 ++++++++++++---------- components/__base__.py | 69 +++++++++++++++++++++++++++++++++++--------------- components/image.py | 17 +++++++++++++ components/original.py | 12 +++++++++ components/text.py | 10 ++++++++ components/video.py | 16 ++++++++++++ core.py | 22 ++++++++++------ main.py | 12 ++++----- mainwindow.py | 1 - video_thread.py | 29 +++++++++++---------- 10 files changed, 156 insertions(+), 62 deletions(-) (limited to 'mainwindow.py') diff --git a/command.py b/command.py index d56c64b..97eddd2 100644 --- a/command.py +++ b/command.py @@ -36,7 +36,7 @@ class Command(QtCore.QObject): help='open a project file (.avp)', nargs='?') self.parser.add_argument( '-c', '--comp', metavar=('LAYER', 'NAME', 'ARG'), - help='create/edit component NAME at LAYER.' + help='create component NAME at LAYER.' '"help" for information about possible args', nargs=3, action='append') @@ -80,12 +80,18 @@ class Command(QtCore.QObject): if self.args.comp: for comp in self.args.comp: pos, name, arg = comp + try: + pos = int(pos) + except ValueError: + print(pos, 'is not a layer number.') + quit(1) realName = self.parseCompName(name) if not realName: print(name, 'is not a valid component name.') - quit() + quit(1) modI = self.core.moduleIndexFor(realName) - self.core.insertComponent(int(pos), modI, self) + i = self.core.insertComponent(pos, modI, self) + self.core.selectedComponents[i].command(arg) self.createAudioVisualisation() @@ -99,12 +105,12 @@ class Command(QtCore.QObject): self.videoTask.emit( self.args.input, self.args.output, - self.core.selectedComponents) + list(reversed(self.core.selectedComponents)) + ) def videoCreated(self): self.videoThread.quit() self.videoThread.wait() - self.cleanUp() def showMessage(self, **kwargs): print(kwargs['msg']) @@ -114,22 +120,20 @@ class Command(QtCore.QObject): def drawPreview(self, *args): pass - def cleanUp(self, *args): - pass - def parseCompName(self, name): '''Deduces a proper component name out of a commandline arg''' - compFileNames = [ \ - os.path.splitext(os.path.basename( - mod.__file__))[0] \ - for mod in self.core.modules \ - ] if name.title() in self.core.compNames: return name.title() for compName in self.core.compNames: if name.capitalize() in compName: return compName + + compFileNames = [ \ + os.path.splitext(os.path.basename( + mod.__file__))[0] \ + for mod in self.core.modules \ + ] for i, compFileName in enumerate(compFileNames): if name.lower() in compFileName: return self.core.compNames[i] diff --git a/components/__base__.py b/components/__base__.py index 88f22d4..e43a517 100644 --- a/components/__base__.py +++ b/components/__base__.py @@ -1,5 +1,6 @@ from PyQt4 import QtGui, QtCore from PIL import Image +import os class Component(QtCore.QObject): @@ -7,11 +8,12 @@ class Component(QtCore.QObject): # modified = QtCore.pyqtSignal(int, bool) - def __init__(self, moduleIndex, compPos): + def __init__(self, moduleIndex, compPos, core): super().__init__() self.currentPreset = None self.moduleIndex = moduleIndex self.compPos = compPos + self.core = core def __str__(self): return self.__doc__ @@ -32,24 +34,59 @@ class Component(QtCore.QObject): # read your widget values, then call super().update() def loadPreset(self, presetDict, presetName): - '''Children should take (presetDict, presetName=None) as args''' - - # Use super().loadPreset(presetDict, presetName) - # Then update your widgets using the preset dict + '''Subclasses take (presetDict, presetName=None) as args. + Must use super().loadPreset(presetDict, presetName) first, + then update self.page widgets using the preset dict. + ''' self.currentPreset = presetName \ if presetName != None else presetDict['preset'] - ''' - def savePreset(self): - return {} - ''' + def preFrameRender(self, **kwargs): + '''Triggered only before a video is exported (video_thread.py) + self.worker = the video thread worker + self.completeAudioArray = a list of audio samples + self.sampleSize = number of audio samples per video frame + self.progressBarUpdate = signal to set progress bar number + self.progressBarSetText = signal to set progress bar text + Use the latter two signals to update the MainProgram if needed + for a long initialization procedure (i.e., for a visualizer) + ''' for var, value in kwargs.items(): exec('self.%s = value' % var) + def command(self, arg): + '''Configure a component using argument from the commandline. + Use super().command(arg) at the end of a subclass's method, + if no arguments are found in that method first + ''' + if arg.startswith('preset='): + _, preset = arg.split('=', 1) + path = os.path.join(self.core.getPresetDir(self), preset) + if not os.path.exists(path): + print('Couldn\'t locate preset "%s"' % preset) + quit(1) + else: + print('Opening "%s" preset on layer %s' % \ + (preset, self.compPos)) + self.core.openPreset(path, self.compPos, preset) + else: + print( + 'To open a preset for this component:\n' + ' "preset=Preset Name"\n') + self.commandHelp() + quit(0) + + def commandHelp(self): + '''Print help text for this Component's commandline arguments''' + def blankFrame(self, width, height): return Image.new("RGBA", (width, height), (0, 0, 0, 0)) def pickColor(self): + '''Use color picker to get color input from the user, + and return this as an RGB string and QPushButton stylesheet. + In a subclass apply stylesheet to any color selection widgets + ''' dialog = QtGui.QColorDialog() dialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True) color = dialog.getColor() @@ -63,7 +100,7 @@ class Component(QtCore.QObject): return None, None def RGBFromString(self, string): - ''' turns an RGB string like "255, 255, 255" into a tuple ''' + ''' Turns an RGB string like "255, 255, 255" into a tuple ''' try: tup = tuple([int(i) for i in string.split(',')]) if len(tup) != 3: @@ -83,14 +120,10 @@ class Component(QtCore.QObject): self.parent = parent page = uic.loadUi(os.path.join( os.path.dirname(os.path.realpath(__file__)), 'example.ui')) - # connect widgets signals + # --- connect widget signals here --- self.page = page return page - def update(self): - super().update() - self.parent.drawPreview() - def previewRender(self, previewWorker): width = int(previewWorker.core.settings.value('outputWidth')) height = int(previewWorker.core.settings.value('outputHeight')) @@ -102,12 +135,6 @@ class Component(QtCore.QObject): height = int(self.worker.core.settings.value('outputHeight')) image = Image.new("RGBA", (width, height), (0,0,0,0)) return image - - def cancel(self): - self.canceled = True - - def reset(self): - self.canceled = False ''' class BadComponentInit(Exception): diff --git a/components/image.py b/components/image.py index b6aa29b..d0e1894 100644 --- a/components/image.py +++ b/components/image.py @@ -92,3 +92,20 @@ class Component(__base__.Component): self.settings.setValue("backgroundDir", os.path.dirname(filename)) self.page.lineEdit_image.setText(filename) self.update() + + def command(self, arg): + if not arg.startswith('preset='): + if os.path.exists(arg): + try: + Image.open(arg) + self.imagePath = arg + self.stretched = True + return True + except OSError as e: + print("Not a supported image format") + quit(1) + super().command(arg) + + def commandHelp(self): + print('Give a complete filepath to an image to load that ' + 'image with default settings.') diff --git a/components/original.py b/components/original.py index 5e2f9d4..328d64f 100644 --- a/components/original.py +++ b/components/original.py @@ -183,3 +183,15 @@ class Component(__base__.Component): return im + def command(self, arg): + if not arg.startswith('preset='): + if arg == 'classic': + self.layout = 0; return + elif arg == 'split': + self.layout = 1; return + elif arg == 'bottom': + self.layout = 2; return + super().command(arg) + + def commandHelp(self): + print('Give a layout name: classic, split, or bottom') diff --git a/components/text.py b/components/text.py index f8ef7b3..6c465b1 100644 --- a/components/text.py +++ b/components/text.py @@ -146,3 +146,13 @@ class Component(__base__.Component): return self.page.lineEdit_textColor.setText(RGBstring) self.page.pushButton_textColor.setStyleSheet(btnStyle) + + def commandHelp(self, arg): + print('Enter a string to use as centred white text. ' + 'Use quotes around the string to escape spaces.') + + def command(self, arg): + if not arg.startswith('preset='): + self.title = arg + return True + super().command(arg) diff --git a/components/video.py b/components/video.py index 3d43a18..dd385b4 100644 --- a/components/video.py +++ b/components/video.py @@ -221,6 +221,22 @@ class Component(__base__.Component): width, height = scale(self.scale, width, height, int) self.chunkSize = 4*width*height + def command(self, arg): + if not arg.startswith('preset='): + if os.path.exists(arg): + if os.path.splitext(arg)[1] in self.core.videoFormats: + self.videoPath = arg + self.scale = 100 + return True + else: + print("Not a supported video format") + quit(1) + super().command(arg) + + def commandHelp(self): + print('Give a complete filepath to a video to load that ' + 'video with default settings.') + def scale(scale, width, height, returntype=None): width = (float(width) / 100.0) * float(scale) height = (float(height) / 100.0) * float(scale) diff --git a/core.py b/core.py index 2dde464..42eb44e 100644 --- a/core.py +++ b/core.py @@ -86,7 +86,7 @@ class Core(): return None component = self.modules[moduleIndex].Component( - moduleIndex, compPos) + moduleIndex, compPos, self) self.selectedComponents.insert( compPos, component) @@ -142,6 +142,10 @@ class Core(): self.savedPresets[presetName] = dict(saveValueStore) return True + def getPresetDir(self, comp): + return os.path.join( + self.presetDir, str(comp), str(comp.version())) + def getPreset(self, filepath): '''Returns the preset dict stored at this filepath''' if not os.path.exists(filepath): @@ -153,10 +157,11 @@ class Core(): return saveValueStore def openProject(self, loader, filepath): - '''loader is the object calling this method which must have - its own showMessage(**kwargs) method for displaying errors''' + ''' loader is the object calling this method which must have + its own showMessage(**kwargs) method for displaying errors. + ''' errcode, data = self.parseAvFile(filepath) - print(data) + #print(data) if errcode == 0: try: for i, tup in enumerate(data['Components']): @@ -224,12 +229,14 @@ class Core(): def parseAvFile(self, filepath): '''Parses an avp (project) or avl (preset package) file. - Returns data usable by another method.''' + Returns dictionary with section names as the keys, each one + contains a list of tuples: (compName, version, compPresetDict) + ''' data = {} try: with open(filepath, 'r') as f: def parseLine(line): - '''Decides if a given avp or avl line is a section header''' + '''Decides if a file line is a section header''' validSections = ('Components') line = line.strip() newSection = '' @@ -313,8 +320,7 @@ class Core(): def createPresetFile( self, compName, vers, presetName, saveValueStore, filepath=''): '''Create a preset file (.avl) at filepath using args. - Or if filepath is empty, create an internal preset using - the args for the filepath.''' + Or if filepath is empty, create an internal preset using args''' if not filepath: dirname = os.path.join(self.presetDir, compName, str(vers)) if not os.path.exists(dirname): diff --git a/main.py b/main.py index 140392c..e04d002 100644 --- a/main.py +++ b/main.py @@ -1,8 +1,6 @@ from PyQt4 import QtGui, uic import sys import os -import atexit -import signal import core import preview_thread @@ -32,7 +30,7 @@ def LoadDefaultSettings(self): } for parm, value in default.items(): - print(parm, self.settings.value(parm)) + #print(parm, self.settings.value(parm)) if self.settings.value(parm) is None: self.settings.setValue(parm, value) @@ -62,6 +60,8 @@ if __name__ == "__main__": elif mode == 'gui': from mainwindow import * + import atexit + import signal window = uic.loadUi(os.path.join( os.path.dirname(os.path.realpath(__file__)), "mainwindow.ui")) @@ -73,10 +73,10 @@ if __name__ == "__main__": window.resize(window.width() * (dpi / 96), window.height() * (dpi / 96)) # window.verticalLayout_2.setContentsMargins(0, topMargin, 0, 0) + signal.signal(signal.SIGINT, main.cleanUp) + atexit.register(main.cleanUp) + main = MainWindow(window, proj) # applicable to both modes - signal.signal(signal.SIGINT, main.cleanUp) - atexit.register(main.cleanUp) - sys.exit(app.exec_()) diff --git a/mainwindow.py b/mainwindow.py index 2a8762d..6023831 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -597,7 +597,6 @@ class MainWindow(QtCore.QObject): self.openProject(filename) def openProject(self, filepath, prompt=True): - print('opening', filepath) if not filepath or not os.path.exists(filepath) \ or not filepath.endswith('.avp'): self.updateWindowTitle() diff --git a/video_thread.py b/video_thread.py index 2255259..e6c6531 100644 --- a/video_thread.py +++ b/video_thread.py @@ -29,7 +29,7 @@ class Worker(QtCore.QObject): self.modules = parent.core.modules self.parent = parent parent.videoTask.connect(self.createVideo) - self.sampleSize = 1470 + self.sampleSize = 1470 # 44100 / 30 = 1470 self.canceled = False self.error = False self.stopped = False @@ -99,7 +99,8 @@ class Worker(QtCore.QObject): # test if user has libfdk_aac encoders = sp.check_output( - self.core.FFMPEG_BIN + " -encoders -hide_banner", shell=True) + self.core.FFMPEG_BIN + " -encoders -hide_banner", + shell=True) encoders = encoders.decode("utf-8") @@ -120,15 +121,15 @@ class Worker(QtCore.QObject): vencoders = options['video-codecs'][vcodec] aencoders = options['audio-codecs'][acodec] - print(encoders) + #print(encoders) for encoder in vencoders: - print(encoder) + #print(encoder) if encoder in encoders: vencoder = encoder break for encoder in aencoders: - print(encoder) + #print(encoder) if encoder in encoders: aencoder = encoder break @@ -161,16 +162,15 @@ class Worker(QtCore.QObject): ffmpegCommand.append('-2') ffmpegCommand.append(outputFile) - self.out_pipe = sp.Popen( - ffmpegCommand, stdin=sp.PIPE, stdout=sys.stdout, stderr=sys.stdout) - # create video for output + # ### Now start creating video for output ### numpy.seterr(divide='ignore') - # initialize components - print('loaded components:', - ["%s%s" % (num, str(component)) for num, - component in enumerate(self.components)]) + # Call preFrameRender on all components + print('Loaded Components:', ", ".join( + ["%s) %s" % (num, str(component)) \ + for num, component in enumerate(reversed(self.components)) + ])) self.staticComponents = {} numComps = len(self.components) for compNo, comp in enumerate(self.components): @@ -190,14 +190,17 @@ class Worker(QtCore.QObject): comp.frameRender(compNo, 0, 0)) self.progressBarUpdate.emit(100) + # Create ffmpeg pipe and queues for frames + self.out_pipe = sp.Popen( + ffmpegCommand, stdin=sp.PIPE, stdout=sys.stdout, stderr=sys.stdout) self.compositeQueue = Queue() self.compositeQueue.maxsize = 20 self.renderQueue = PriorityQueue() self.renderQueue.maxsize = 20 self.previewQueue = PriorityQueue() - self.renderThreads = [] # Threads to render frames and send them back here for piping out + self.renderThreads = [] for i in range(3): self.renderThreads.append( Thread(target=self.renderNode, name="Render Thread")) -- cgit v1.2.3 From 407ba57e4eff4b7dfc760ab0cd390a4555294f24 Mon Sep 17 00:00:00 2001 From: tassaron Date: Fri, 23 Jun 2017 07:41:11 -0400 Subject: fixed crash after creating a project on commandline because it didn't check for errors while saving --- mainwindow.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'mainwindow.py') diff --git a/mainwindow.py b/mainwindow.py index f3d45e6..e86abe6 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -212,6 +212,8 @@ class MainWindow(QtGui.QMainWindow): 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(os.path.expanduser('~'), project) @@ -350,8 +352,15 @@ class MainWindow(QtGui.QMainWindow): return False def saveProjectChanges(self): - os.remove(self.currentProject) - os.rename(self.autosavePath, self.currentProject) + 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("~")) @@ -592,6 +601,7 @@ class MainWindow(QtGui.QMainWindow): self.openSaveProjectDialog() def openSaveChangesDialog(self, phrase): + success = True if self.autosaveExists(identical=False): ch = self.showMessage( msg="You have unsaved changes in project '%s'. " @@ -600,9 +610,9 @@ class MainWindow(QtGui.QMainWindow): phrase), showCancel=True) if ch: - self.saveProjectChanges() + success = self.saveProjectChanges() - if os.path.exists(self.autosavePath): + if success and os.path.exists(self.autosavePath): os.remove(self.autosavePath) def openSaveProjectDialog(self): -- cgit v1.2.3