From 4c3920e6309b4e67e3d8d809dd0b5b6cd245fd0c Mon Sep 17 00:00:00 2001 From: tassaron Date: Sun, 9 Jul 2017 21:27:29 -0400 Subject: separated creation of ffmpeg command for future use to sllow editing the command before starting the export --- src/components/text.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/components/text.py') diff --git a/src/components/text.py b/src/components/text.py index 2b1884f..fb6a90e 100644 --- a/src/components/text.py +++ b/src/components/text.py @@ -119,8 +119,7 @@ class Component(Component): height = int(previewWorker.core.settings.value('outputHeight')) return self.addText(width, height) - def preFrameRender(self, **kwargs): - super().preFrameRender(**kwargs) + def properties(self): return ['static'] def frameRender(self, layerNo, frameNo): -- cgit v1.2.3 From 2e37dafd7036973a315b525f131850a6fb6d0b35 Mon Sep 17 00:00:00 2001 From: tassaron Date: Tue, 11 Jul 2017 06:06:22 -0400 Subject: fixed various bugs --- src/component.py | 9 ++++++++- src/components/image.py | 10 +++++++++- src/components/sound.py | 8 ++++---- src/components/text.py | 10 +++++----- src/components/video.py | 21 +++++++++++++++++++++ src/components/video.ui | 17 +++++++++-------- src/core.py | 4 ++-- src/mainwindow.py | 4 ++++ src/preview_thread.py | 26 ++++++++++++-------------- src/video_thread.py | 9 +++++++++ 10 files changed, 83 insertions(+), 35 deletions(-) (limited to 'src/components/text.py') diff --git a/src/component.py b/src/component.py index 7c2f753..eea82d7 100644 --- a/src/component.py +++ b/src/component.py @@ -30,10 +30,17 @@ class Component(QtCore.QObject): def properties(self): ''' Return a list of properties to signify if your component is - non-animated ('static') or returns sound ('audio'). + non-animated ('static'), returns sound ('audio'), or has + encountered an error in configuration ('error'). ''' return [] + def error(self): + ''' + Return a string containing an error message, or None for a default. + ''' + return + def cancel(self): # please stop any lengthy process in response to this variable self.canceled = True diff --git a/src/components/image.py b/src/components/image.py index 94dcb83..07abc3f 100644 --- a/src/components/image.py +++ b/src/components/image.py @@ -47,7 +47,15 @@ class Component(Component): return self.drawFrame(width, height) def properties(self): - return ['static'] + props = ['static'] + if not os.path.exists(self.imagePath): + props.append('error') + return props + + def error(self): + if not os.path.exists(self.imagePath): + return "The image path selected on " \ + "layer %s no longer exists!" % str(self.compPos) def frameRender(self, layerNo, frameNo): width = int(self.worker.core.settings.value('outputWidth')) diff --git a/src/components/sound.py b/src/components/sound.py index 1f43c83..9c114a8 100644 --- a/src/components/sound.py +++ b/src/components/sound.py @@ -28,7 +28,7 @@ class Component(Component): def previewRender(self, previewWorker): width = int(previewWorker.core.settings.value('outputWidth')) height = int(previewWorker.core.settings.value('outputHeight')) - return self.frameRender(self.compPos, 0) + return BlankFrame(width, height) def preFrameRender(self, **kwargs): pass @@ -37,7 +37,7 @@ class Component(Component): return ['static', 'audio'] def audio(self): - return self.sound + return (self.sound, {}) def pickSound(self): sndDir = self.settings.value("componentDir", os.path.expanduser("~")) @@ -50,8 +50,8 @@ class Component(Component): self.update() def frameRender(self, layerNo, frameNo): - width = int(self.core.settings.value('outputWidth')) - height = int(self.core.settings.value('outputHeight')) + width = int(self.settings.value('outputWidth')) + height = int(self.settings.value('outputHeight')) return BlankFrame(width, height) def loadPreset(self, pr, presetName=None): diff --git a/src/components/text.py b/src/components/text.py index fb6a90e..ed50064 100644 --- a/src/components/text.py +++ b/src/components/text.py @@ -75,15 +75,15 @@ class Component(Component): '''Returns true x, y after considering alignment settings''' fm = QtGui.QFontMetrics(self.titleFont) if self.alignment == 0: # Left - x = self.xPosition + x = int(self.xPosition) if self.alignment == 1: # Middle offset = fm.width(self.title)/2 - x = self.xPosition - offset + x = int(self.xPosition - offset) if self.alignment == 2: # Right offset = fm.width(self.title) - x = self.xPosition - offset + x = int(self.xPosition - offset) return x, self.yPosition def loadPreset(self, pr, presetName=None): @@ -128,12 +128,12 @@ class Component(Component): return self.addText(width, height) def addText(self, width, height): - x, y = self.getXY() - image = FramePainter(width, height) + image = FramePainter(width, height) self.titleFont.setPixelSize(self.fontSize) image.setFont(self.titleFont) image.setPen(self.textColor) + x, y = self.getXY() image.drawText(x, y, self.title) return image.finalize() diff --git a/src/components/video.py b/src/components/video.py index e6890e0..5303e3a 100644 --- a/src/components/video.py +++ b/src/components/video.py @@ -123,6 +123,7 @@ class Component(Component): page.pushButton_video.clicked.connect(self.pickVideo) page.checkBox_loop.stateChanged.connect(self.update) page.checkBox_distort.stateChanged.connect(self.update) + page.checkBox_useAudio.stateChanged.connect(self.update) page.spinBox_scale.valueChanged.connect(self.update) page.spinBox_x.valueChanged.connect(self.update) page.spinBox_y.valueChanged.connect(self.update) @@ -133,6 +134,7 @@ class Component(Component): def update(self): self.videoPath = self.page.lineEdit_video.text() self.loopVideo = self.page.checkBox_loop.isChecked() + self.useAudio = self.page.checkBox_useAudio.isChecked() self.distort = self.page.checkBox_distort.isChecked() self.scale = self.page.spinBox_scale.value() self.xPosition = self.page.spinBox_x.value() @@ -151,6 +153,23 @@ class Component(Component): else: return frame + def properties(self): + props = [] + if self.useAudio: + # props.append('audio') + pass + if not os.path.exists(self.videoPath): + props.append('error') + return props + + def error(self): + if not os.path.exists(self.videoPath): + return "The video path selected on " \ + "layer %s no longer exists!" % str(self.compPos) + + def audio(self): + return (self.videoPath, {}) + def preFrameRender(self, **kwargs): super().preFrameRender(**kwargs) width = int(self.worker.core.settings.value('outputWidth')) @@ -175,6 +194,7 @@ class Component(Component): super().loadPreset(pr, presetName) self.page.lineEdit_video.setText(pr['video']) self.page.checkBox_loop.setChecked(pr['loop']) + self.page.checkBox_useAudio.setChecked(pr['useAudio']) self.page.checkBox_distort.setChecked(pr['distort']) self.page.spinBox_scale.setValue(pr['scale']) self.page.spinBox_x.setValue(pr['x']) @@ -185,6 +205,7 @@ class Component(Component): 'preset': self.currentPreset, 'video': self.videoPath, 'loop': self.loopVideo, + 'useAudio': self.useAudio, 'distort': self.distort, 'scale': self.scale, 'x': self.xPosition, diff --git a/src/components/video.ui b/src/components/video.ui index f05e8a5..97b7d6f 100644 --- a/src/components/video.ui +++ b/src/components/video.ui @@ -190,16 +190,20 @@ - + + + Use Audio + + + + + Qt::Horizontal - - QSizePolicy::Fixed - - 5 + 40 20 @@ -256,9 +260,6 @@ - - - diff --git a/src/core.py b/src/core.py index 3d64c3b..450e43b 100644 --- a/src/core.py +++ b/src/core.py @@ -524,7 +524,7 @@ class Core: if 'audio' in comp.properties() ] if extraAudio: - for extraInputFile in extraAudio: + for extraInputFile, params in extraAudio: ffmpegCommand.extend([ '-i', extraInputFile ]) @@ -532,7 +532,7 @@ class Core: '-filter_complex', 'amix=inputs=%s:duration=longest:dropout_transition=3' % str( len(extraAudio) + 1 - ) + ), ]) ffmpegCommand.extend([ diff --git a/src/mainwindow.py b/src/mainwindow.py index 3cd45d6..d21ba0a 100644 --- a/src/mainwindow.py +++ b/src/mainwindow.py @@ -713,6 +713,10 @@ class MainWindow(QtWidgets.QMainWindow): 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() diff --git a/src/preview_thread.py b/src/preview_thread.py index a72845b..fb3b792 100644 --- a/src/preview_thread.py +++ b/src/preview_thread.py @@ -25,8 +25,8 @@ class Worker(QtCore.QObject): self.parent = parent self.core = self.parent.core self.queue = queue - self.core.settings = parent.settings - self.stackedWidget = parent.window.stackedWidget + self.width = int(self.core.settings.value('outputWidth')) + self.height = int(self.core.settings.value('outputHeight')) # create checkerboard background to represent transparency self.background = FloodFrame(1920, 1080, (0, 0, 0, 0)) @@ -50,10 +50,10 @@ class Worker(QtCore.QObject): except Empty: continue - width = int(self.core.settings.value('outputWidth')) - height = int(self.core.settings.value('outputHeight')) + if self.background.width != self.width: + self.background = self.background.resize( + (self.width, self.height)) frame = self.background.copy() - frame = frame.resize((width, height)) components = nextPreviewInformation["components"] for component in reversed(components): @@ -63,23 +63,21 @@ class Worker(QtCore.QObject): ) except ValueError as e: + errMsg = "Bad frame returned by %s's preview renderer. " \ + "%s. This is a fatal error." % ( + str(component), str(e).capitalize() + ) + print(errMsg) self.parent.showMessage( - msg="Bad frame returned by %s's previewRender method. " - "This is a fatal error." % - str(component), + msg=errMsg, detail=str(e), icon='Warning', parent=None # MainWindow is in a different thread ) - self.imageCreated.emit( - QtGui.QImage(ImageQt( - FloodFrame(width, height, (0, 0, 0, 0)) - )) - ) self.error.emit() break else: - self.imageCreated.emit(ImageQt(frame)) + self.imageCreated.emit(QtGui.QImage(ImageQt(frame))) except Empty: True diff --git a/src/video_thread.py b/src/video_thread.py index dde71da..b00d512 100644 --- a/src/video_thread.py +++ b/src/video_thread.py @@ -151,6 +151,15 @@ class Worker(QtCore.QObject): progressBarSetText=self.progressBarSetText ) + if 'error' in comp.properties(): + self.canceled = True + errMsg = "Component #%s encountered an error!" % compNo \ + if comp.error() is None else comp.error() + self.parent.showMessage( + msg=errMsg, + icon='Warning', + parent=None # MainWindow is in a different thread + ) if 'static' in comp.properties(): self.staticComponents[compNo] = \ comp.frameRender(compNo, 0).copy() -- cgit v1.2.3 From 8811b699a9c2d6b78af1e2a332d3031aef73aec4 Mon Sep 17 00:00:00 2001 From: tassaron Date: Thu, 13 Jul 2017 00:05:11 -0400 Subject: merge consecutive static components --- src/components/color.py | 13 +++++++------ src/components/image.py | 16 ++++++++-------- src/components/original.py | 9 +++++---- src/components/text.py | 21 +++++++++++---------- src/components/video.py | 6 +++--- src/core.py | 2 ++ src/frame.py | 21 ++++++++++++++++++++- src/mainwindow.py | 2 ++ src/preview_thread.py | 38 +++++++++++++++++++++++--------------- src/video_thread.py | 27 +++++++++++++++++++-------- 10 files changed, 100 insertions(+), 55 deletions(-) (limited to 'src/components/text.py') diff --git a/src/components/color.py b/src/components/color.py index 82b45b3..da3bcf9 100644 --- a/src/components/color.py +++ b/src/components/color.py @@ -15,6 +15,7 @@ class Component(Component): def widget(self, parent): self.parent = parent + self.settings = self.parent.core.settings page = self.loadUi('color.ui') self.color1 = (0, 0, 0) @@ -42,9 +43,9 @@ class Component(Component): page.spinBox_x.valueChanged.connect(self.update) page.spinBox_y.valueChanged.connect(self.update) page.spinBox_width.setValue( - int(parent.settings.value("outputWidth"))) + int(self.settings.value("outputWidth"))) page.spinBox_height.setValue( - int(parent.settings.value("outputHeight"))) + int(self.settings.value("outputHeight"))) page.lineEdit_color1.textChanged.connect(self.update) page.lineEdit_color2.textChanged.connect(self.update) @@ -113,16 +114,16 @@ class Component(Component): super().update() def previewRender(self, previewWorker): - width = int(previewWorker.core.settings.value('outputWidth')) - height = int(previewWorker.core.settings.value('outputHeight')) + width = int(self.settings.value('outputWidth')) + height = int(self.settings.value('outputHeight')) return self.drawFrame(width, height) def properties(self): return ['static'] def frameRender(self, layerNo, frameNo): - width = int(self.worker.core.settings.value('outputWidth')) - height = int(self.worker.core.settings.value('outputHeight')) + width = int(self.settings.value('outputWidth')) + height = int(self.settings.value('outputHeight')) return self.drawFrame(width, height) def drawFrame(self, width, height): diff --git a/src/components/image.py b/src/components/image.py index 07abc3f..6465bc9 100644 --- a/src/components/image.py +++ b/src/components/image.py @@ -13,7 +13,7 @@ class Component(Component): def widget(self, parent): self.parent = parent - self.settings = parent.settings + self.settings = self.parent.core.settings page = self.loadUi('image.ui') page.lineEdit_image.textChanged.connect(self.update) @@ -42,24 +42,24 @@ class Component(Component): super().update() def previewRender(self, previewWorker): - width = int(previewWorker.core.settings.value('outputWidth')) - height = int(previewWorker.core.settings.value('outputHeight')) + width = int(self.settings.value('outputWidth')) + height = int(self.settings.value('outputHeight')) return self.drawFrame(width, height) def properties(self): props = ['static'] - if not os.path.exists(self.imagePath): + if self.imagePath and not os.path.exists(self.imagePath): props.append('error') return props def error(self): if not os.path.exists(self.imagePath): - return "The image path selected on " \ - "layer %s no longer exists!" % str(self.compPos) + return "The image selected on " \ + "layer %s does not exist!" % str(self.compPos) def frameRender(self, layerNo, frameNo): - width = int(self.worker.core.settings.value('outputWidth')) - height = int(self.worker.core.settings.value('outputHeight')) + width = int(self.settings.value('outputWidth')) + height = int(self.settings.value('outputHeight')) return self.drawFrame(width, height) def drawFrame(self, width, height): diff --git a/src/components/original.py b/src/components/original.py index 638095d..3599c30 100644 --- a/src/components/original.py +++ b/src/components/original.py @@ -21,6 +21,7 @@ class Component(Component): def widget(self, parent): self.parent = parent + self.settings = self.parent.core.settings self.visColor = (255, 255, 255) self.scale = 20 self.y = 0 @@ -76,8 +77,8 @@ class Component(Component): def previewRender(self, previewWorker): spectrum = numpy.fromfunction( lambda x: float(self.scale)/2500*(x-128)**2, (255,), dtype="int16") - width = int(previewWorker.core.settings.value('outputWidth')) - height = int(previewWorker.core.settings.value('outputHeight')) + width = int(self.settings.value('outputWidth')) + height = int(self.settings.value('outputHeight')) return self.drawBars( width, height, spectrum, self.visColor, self.layout ) @@ -88,8 +89,8 @@ class Component(Component): self.smoothConstantUp = 0.8 self.lastSpectrum = None self.spectrumArray = {} - self.width = int(self.worker.core.settings.value('outputWidth')) - self.height = int(self.worker.core.settings.value('outputHeight')) + self.width = int(self.settings.value('outputWidth')) + self.height = int(self.settings.value('outputHeight')) for i in range(0, len(self.completeAudioArray), self.sampleSize): if self.canceled: diff --git a/src/components/text.py b/src/components/text.py index ed50064..4435b80 100644 --- a/src/components/text.py +++ b/src/components/text.py @@ -17,10 +17,11 @@ class Component(Component): self.titleFont = QFont() def widget(self, parent): - height = int(parent.settings.value('outputHeight')) - width = int(parent.settings.value('outputWidth')) - self.parent = parent + self.settings = self.parent.core.settings + height = int(self.settings.value('outputHeight')) + width = int(self.settings.value('outputWidth')) + self.textColor = (255, 255, 255) self.title = 'Text' self.alignment = 1 @@ -78,12 +79,12 @@ class Component(Component): x = int(self.xPosition) if self.alignment == 1: # Middle - offset = fm.width(self.title)/2 - x = int(self.xPosition - offset) + offset = int(fm.width(self.title)/2) + x = self.xPosition - offset if self.alignment == 2: # Right offset = fm.width(self.title) - x = int(self.xPosition - offset) + x = self.xPosition - offset return x, self.yPosition def loadPreset(self, pr, presetName=None): @@ -115,16 +116,16 @@ class Component(Component): } def previewRender(self, previewWorker): - width = int(previewWorker.core.settings.value('outputWidth')) - height = int(previewWorker.core.settings.value('outputHeight')) + width = int(self.settings.value('outputWidth')) + height = int(self.settings.value('outputHeight')) return self.addText(width, height) def properties(self): return ['static'] def frameRender(self, layerNo, frameNo): - width = int(self.worker.core.settings.value('outputWidth')) - height = int(self.worker.core.settings.value('outputHeight')) + width = int(self.settings.value('outputWidth')) + height = int(self.settings.value('outputHeight')) return self.addText(width, height) def addText(self, width, height): diff --git a/src/components/video.py b/src/components/video.py index 5303e3a..49bd145 100644 --- a/src/components/video.py +++ b/src/components/video.py @@ -158,14 +158,14 @@ class Component(Component): if self.useAudio: # props.append('audio') pass - if not os.path.exists(self.videoPath): + if self.videoPath and not os.path.exists(self.videoPath): props.append('error') return props def error(self): if not os.path.exists(self.videoPath): - return "The video path selected on " \ - "layer %s no longer exists!" % str(self.compPos) + return "The video selected on " \ + "layer %s does not exist!" % str(self.compPos) def audio(self): return (self.videoPath, {}) diff --git a/src/core.py b/src/core.py index 450e43b..64f55eb 100644 --- a/src/core.py +++ b/src/core.py @@ -11,6 +11,7 @@ from importlib import import_module from PyQt5.QtCore import QStandardPaths import toolkit +from frame import Frame class Core: @@ -20,6 +21,7 @@ class Core: opens projects and presets, and stores settings/paths to data. ''' def __init__(self): + Frame.core = self self.dataDir = QStandardPaths.writableLocation( QStandardPaths.AppConfigLocation ) diff --git a/src/frame.py b/src/frame.py index c066cdb..cddb611 100644 --- a/src/frame.py +++ b/src/frame.py @@ -5,6 +5,11 @@ from PyQt5 import QtGui from PIL import Image from PIL.ImageQt import ImageQt import sys +import os + + +class Frame: + '''Controller class for all frames.''' class FramePainter(QtGui.QPainter): @@ -43,5 +48,19 @@ def FloodFrame(width, height, RgbaTuple): def BlankFrame(width, height): - '''The base frame used by each component to start drawing''' + '''The base frame used by each component to start drawing.''' return FloodFrame(width, height, (0, 0, 0, 0)) + + +def Checkerboard(width, height): + ''' + A checkerboard to represent transparency to the user. + TODO: Would be cool to generate this image with numpy instead. + ''' + image = FloodFrame(1920, 1080, (0, 0, 0, 0)) + image.paste(Image.open( + os.path.join(Frame.core.wd, "background.png")), + (0, 0) + ) + image = image.resize((width, height)) + return image diff --git a/src/mainwindow.py b/src/mainwindow.py index d21ba0a..771b6b8 100644 --- a/src/mainwindow.py +++ b/src/mainwindow.py @@ -306,6 +306,7 @@ class MainWindow(QtWidgets.QMainWindow): 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+T", self.window, @@ -585,6 +586,7 @@ class MainWindow(QtWidgets.QMainWindow): self.autosave(force) self.updateWindowTitle() + @QtCore.pyqtSlot(QtGui.QImage) def showPreviewImage(self, image): self.previewWindow.changePixmap(image) diff --git a/src/preview_thread.py b/src/preview_thread.py index fb3b792..4ffb7f6 100644 --- a/src/preview_thread.py +++ b/src/preview_thread.py @@ -10,12 +10,12 @@ import core from queue import Queue, Empty import os -from frame import FloodFrame +from frame import Checkerboard class Worker(QtCore.QObject): - imageCreated = pyqtSignal(['QImage']) + imageCreated = pyqtSignal(QtGui.QImage) error = pyqtSignal() def __init__(self, parent=None, queue=None): @@ -24,14 +24,12 @@ class Worker(QtCore.QObject): parent.processTask.connect(self.process) self.parent = parent self.core = self.parent.core + self.settings = self.parent.core.settings self.queue = queue - self.width = int(self.core.settings.value('outputWidth')) - self.height = int(self.core.settings.value('outputHeight')) - # create checkerboard background to represent transparency - self.background = FloodFrame(1920, 1080, (0, 0, 0, 0)) - self.background.paste(Image.open(os.path.join( - self.core.wd, "background.png"))) + width = int(self.settings.value('outputWidth')) + height = int(self.settings.value('outputHeight')) + self.background = Checkerboard(width, height) @pyqtSlot(list) def createPreviewImage(self, components): @@ -42,6 +40,8 @@ class Worker(QtCore.QObject): @pyqtSlot() def process(self): + width = int(self.settings.value('outputWidth')) + height = int(self.settings.value('outputHeight')) try: nextPreviewInformation = self.queue.get(block=False) while self.queue.qsize() >= 2: @@ -50,22 +50,27 @@ class Worker(QtCore.QObject): except Empty: continue - if self.background.width != self.width: - self.background = self.background.resize( - (self.width, self.height)) + if self.background.width != width \ + or self.background.height != height: + self.background = Checkerboard(width, height) + frame = self.background.copy() components = nextPreviewInformation["components"] for component in reversed(components): try: + newFrame = component.previewRender(self) frame = Image.alpha_composite( - frame, component.previewRender(self) + frame, newFrame ) except ValueError as e: errMsg = "Bad frame returned by %s's preview renderer. " \ - "%s. This is a fatal error." % ( - str(component), str(e).capitalize() + "%s. New frame size was %s*%s; should be %s*%s. " \ + "This is a fatal error." % ( + str(component), str(e).capitalize(), + newFrame.width, newFrame.height, + width, height ) print(errMsg) self.parent.showMessage( @@ -76,8 +81,11 @@ class Worker(QtCore.QObject): ) self.error.emit() break + except RuntimeError as e: + print(e) else: - self.imageCreated.emit(QtGui.QImage(ImageQt(frame))) + self.frame = ImageQt(frame) + self.imageCreated.emit(QtGui.QImage(self.frame)) except Empty: True diff --git a/src/video_thread.py b/src/video_thread.py index b00d512..f736013 100644 --- a/src/video_thread.py +++ b/src/video_thread.py @@ -20,7 +20,7 @@ import signal import core from toolkit import openPipe, checkOutput -from frame import FloodFrame +from frame import Checkerboard class Worker(QtCore.QObject): @@ -56,8 +56,10 @@ class Worker(QtCore.QObject): frame = None for compNo, comp in reversed(list(enumerate(self.components))): - if compNo in self.staticComponents and \ - self.staticComponents[compNo] is not None: + if compNo in self.staticComponents: + if self.staticComponents[compNo] is None: + # this layer was merged into a following layer + continue # static component if frame is None: # bottom-most layer frame = self.staticComponents[compNo] @@ -93,10 +95,7 @@ class Worker(QtCore.QObject): Grabs frames from the previewQueue, adds them to the checkerboard and emits a final QImage to the MainWindow for the live preview ''' - background = FloodFrame(1920, 1080, (0, 0, 0, 0)) - background.paste(Image.open(os.path.join( - self.core.wd, "background.png"))) - background = background.resize((self.width, self.height)) + background = Checkerboard(self.width, self.height) while not self.stopped: audioI, frame = self.previewQueue.get() @@ -164,8 +163,20 @@ class Worker(QtCore.QObject): self.staticComponents[compNo] = \ comp.frameRender(compNo, 0).copy() + # Merge consecutive static component frames together + for compNo in range(len(self.components), 0, -1): + if compNo not in self.staticComponents \ + or compNo - 1 not in self.staticComponents: + continue + self.staticComponents[compNo - 1] = Image.alpha_composite( + self.staticComponents.pop(compNo), + self.staticComponents[compNo - 1] + ) + self.staticComponents[compNo] = None + ffmpegCommand = self.core.createFfmpegCommand(inputFile, outputFile) - print(ffmpegCommand) + print('###### FFMPEG COMMAND ######\n %s' % " ".join(ffmpegCommand)) + print('###### -------------- ######') self.out_pipe = openPipe( ffmpegCommand, stdin=sp.PIPE, stdout=sys.stdout, stderr=sys.stdout ) -- cgit v1.2.3 From 06c27a48bc3f52e15c15445d822e8a6f523ab98f Mon Sep 17 00:00:00 2001 From: tassaron Date: Thu, 13 Jul 2017 17:03:25 -0400 Subject: more error messages for blank components --- src/components/image.py | 7 ++++--- src/components/sound.py | 11 ++++++++++- src/components/text.py | 8 +++++++- src/components/video.py | 13 ++++++++++--- src/video_thread.py | 28 ++++++++++++++++++++++------ 5 files changed, 53 insertions(+), 14 deletions(-) (limited to 'src/components/text.py') diff --git a/src/components/image.py b/src/components/image.py index 6465bc9..6a70424 100644 --- a/src/components/image.py +++ b/src/components/image.py @@ -48,14 +48,15 @@ class Component(Component): def properties(self): props = ['static'] - if self.imagePath and not os.path.exists(self.imagePath): + if not os.path.exists(self.imagePath): props.append('error') return props def error(self): + if not self.imagePath: + return "There is no image selected." if not os.path.exists(self.imagePath): - return "The image selected on " \ - "layer %s does not exist!" % str(self.compPos) + return "The image selected does not exist!" def frameRender(self, layerNo, frameNo): width = int(self.settings.value('outputWidth')) diff --git a/src/components/sound.py b/src/components/sound.py index 9c114a8..2ffb682 100644 --- a/src/components/sound.py +++ b/src/components/sound.py @@ -34,7 +34,16 @@ class Component(Component): pass def properties(self): - return ['static', 'audio'] + props = ['static', 'audio'] + if not os.path.exists(self.sound): + props.append('error') + return props + + def error(self): + if not self.sound: + return "No audio file selected." + if not os.path.exists(self.sound): + return "The audio file selected no longer exists!" def audio(self): return (self.sound, {}) diff --git a/src/components/text.py b/src/components/text.py index 4435b80..c52bdc5 100644 --- a/src/components/text.py +++ b/src/components/text.py @@ -121,7 +121,13 @@ class Component(Component): return self.addText(width, height) def properties(self): - return ['static'] + props = ['static'] + if not self.title: + props.append('error') + return props + + def error(self): + return "No text provided." def frameRender(self, layerNo, frameNo): width = int(self.settings.value('outputWidth')) diff --git a/src/components/video.py b/src/components/video.py index 53487b1..8861d70 100644 --- a/src/components/video.py +++ b/src/components/video.py @@ -115,6 +115,7 @@ class Component(Component): self.settings = parent.settings page = self.loadUi('video.ui') self.videoPath = '' + self.badVideo = False self.x = 0 self.y = 0 self.loopVideo = False @@ -156,14 +157,18 @@ class Component(Component): props = [] if self.useAudio: props.append('audio') - if self.videoPath and not os.path.exists(self.videoPath): + if not self.videoPath or self.badVideo \ + or not os.path.exists(self.videoPath): props.append('error') return props def error(self): + if not self.videoPath: + return "There is no video selected." if not os.path.exists(self.videoPath): - return "The video selected on " \ - "layer %s does not exist!" % str(self.compPos) + return "The video selected does not exist!" + if self.badVideo: + return "The video selected is corrupt!" def audio(self): return (self.videoPath, {'map': '-v'}) @@ -300,6 +305,7 @@ def finalizeFrame(self, imageData, width, height): '### BAD VIDEO SELECTED ###\n' 'Video will not export with these settings' ) + self.badVideo = True return BlankFrame(width, height) if self.scale != 100 \ @@ -308,4 +314,5 @@ def finalizeFrame(self, imageData, width, height): frame.paste(image, box=(self.xPosition, self.yPosition)) else: frame = image + self.badVideo = False return frame diff --git a/src/video_thread.py b/src/video_thread.py index bfb0cc4..9ce9cc8 100644 --- a/src/video_thread.py +++ b/src/video_thread.py @@ -141,7 +141,7 @@ class Worker(QtCore.QObject): ])) self.staticComponents = {} numComps = len(self.components) - for compNo, comp in enumerate(self.components): + for compNo, comp in enumerate(reversed(self.components)): comp.preFrameRender( worker=self, completeAudioArray=self.completeAudioArray, @@ -151,26 +151,41 @@ class Worker(QtCore.QObject): ) if 'error' in comp.properties(): + self.cancel() self.canceled = True errMsg = "Component #%s encountered an error!" % compNo \ - if comp.error() is None else comp.error() + if comp.error() is None else 'Component #%s (%s): %s' % ( + str(compNo), + str(comp), + comp.error() + ) self.parent.showMessage( msg=errMsg, icon='Warning', parent=None # MainWindow is in a different thread ) + break if 'static' in comp.properties(): self.staticComponents[compNo] = \ comp.frameRender(compNo, 0).copy() + if self.canceled: + print('Export cancelled by component #%s (%s): %s' % ( + compNo, str(comp), comp.error() + )) + self.progressBarSetText.emit('Export Canceled') + self.encoding.emit(False) + self.videoCreated.emit() + return + # Merge consecutive static component frames together - for compNo in range(len(self.components), 0, -1): + for compNo in range(len(self.components)): if compNo not in self.staticComponents \ - or compNo - 1 not in self.staticComponents: + or compNo + 1 not in self.staticComponents: continue - self.staticComponents[compNo - 1] = Image.alpha_composite( + self.staticComponents[compNo + 1] = Image.alpha_composite( self.staticComponents.pop(compNo), - self.staticComponents[compNo - 1] + self.staticComponents[compNo + 1] ) self.staticComponents[compNo] = None @@ -278,6 +293,7 @@ class Worker(QtCore.QObject): def cancel(self): self.canceled = True + self.stopped = True self.core.cancel() for comp in self.components: -- cgit v1.2.3 From cbbb7876155cdb057b0d779cb8ab7bc1f31116b0 Mon Sep 17 00:00:00 2001 From: tassaron Date: Thu, 13 Jul 2017 21:59:23 -0400 Subject: components automatically drawPreview & save currentPreset this makes a Component easier to program. also more comments --- src/component.py | 36 ++++++++++++++++++++++-------------- src/components/color.py | 1 - src/components/image.py | 2 +- src/components/original.py | 2 +- src/components/sound.py | 1 - src/components/text.py | 2 +- src/components/video.py | 2 +- src/core.py | 1 + src/presetmanager.py | 1 + 9 files changed, 28 insertions(+), 20 deletions(-) (limited to 'src/components/text.py') diff --git a/src/component.py b/src/component.py index eea82d7..2b297d1 100644 --- a/src/component.py +++ b/src/component.py @@ -24,7 +24,9 @@ class Component(QtCore.QObject): return self.__doc__ def version(self): - # change this number to identify new versions of a component + ''' + Change this number to identify new versions of a component + ''' return 1 def properties(self): @@ -42,15 +44,22 @@ class Component(QtCore.QObject): return def cancel(self): - # please stop any lengthy process in response to this variable + ''' + Stop any lengthy process in response to this variable + ''' self.canceled = True def reset(self): self.canceled = False def update(self): - self.modified.emit(self.compPos, self.savePreset()) - # read your widget values, then call super().update() + ''' + Read your widget values from self.page, then call super().update() + ''' + self.parent.drawPreview() + saveValueStore = self.savePreset() + saveValueStore['preset'] = self.currentPreset + self.modified.emit(self.compPos, saveValueStore) def loadPreset(self, presetDict, presetName): ''' @@ -72,8 +81,8 @@ class Component(QtCore.QObject): Use the latter two signals to update the MainWindow if needed for a long initialization procedure (i.e., for a visualizer) ''' - for var, value in kwargs.items(): - exec('self.%s = value' % var) + for key, value in kwargs.items(): + setattr(self, key, value) def command(self, arg): ''' @@ -143,16 +152,11 @@ class Component(QtCore.QObject): def widget(self, parent): self.parent = parent - page = uic.loadUi(os.path.join( - os.path.dirname(os.path.realpath(__file__)), 'example.ui')) + page = self.loadUi('example.ui') # --- connect widget signals here --- self.page = page return page - def update(self): - self.parent.drawPreview() - super().update() - def previewRender(self, previewWorker): width = int(previewWorker.core.settings.value('outputWidth')) height = int(previewWorker.core.settings.value('outputHeight')) @@ -170,8 +174,12 @@ class Component(QtCore.QObject): def audio(self): \''' - Return audio to mix into master as a string (path to audio file), - or an object that returns raw audio data [future feature]. + Return audio to mix into master as a tuple with two elements: + The first element can be: + - A string (path to audio file), + - Or an object that returns audio data through a pipe + The second element must be a dictionary of ffmpeg parameters + to apply to the input stream. \''' @classmethod diff --git a/src/components/color.py b/src/components/color.py index da3bcf9..ef4dd95 100644 --- a/src/components/color.py +++ b/src/components/color.py @@ -110,7 +110,6 @@ class Component(Component): self.page.pushButton_color2.setEnabled(False) self.page.fillWidget.setCurrentIndex(self.fillType) - self.parent.drawPreview() super().update() def previewRender(self, previewWorker): diff --git a/src/components/image.py b/src/components/image.py index 6a70424..c0d1c0d 100644 --- a/src/components/image.py +++ b/src/components/image.py @@ -38,7 +38,7 @@ class Component(Component): self.yPosition = self.page.spinBox_y.value() self.stretched = self.page.checkBox_stretch.isChecked() self.mirror = self.page.checkBox_mirror.isChecked() - self.parent.drawPreview() + super().update() def previewRender(self, previewWorker): diff --git a/src/components/original.py b/src/components/original.py index 3599c30..f5776a4 100644 --- a/src/components/original.py +++ b/src/components/original.py @@ -51,7 +51,7 @@ class Component(Component): self.visColor = self.RGBFromString(self.page.lineEdit_visColor.text()) self.scale = self.page.spinBox_scale.value() self.y = self.page.spinBox_y.value() - self.parent.drawPreview() + super().update() def loadPreset(self, pr, presetName=None): diff --git a/src/components/sound.py b/src/components/sound.py index 2ffb682..fedc32b 100644 --- a/src/components/sound.py +++ b/src/components/sound.py @@ -69,7 +69,6 @@ class Component(Component): def savePreset(self): return { - 'preset': self.currentPreset, 'sound': self.sound, } diff --git a/src/components/text.py b/src/components/text.py index c52bdc5..19460e5 100644 --- a/src/components/text.py +++ b/src/components/text.py @@ -69,7 +69,7 @@ class Component(Component): btnStyle = "QPushButton { background-color : %s; outline: none; }" \ % QColor(*self.textColor).name() self.page.pushButton_textColor.setStyleSheet(btnStyle) - self.parent.drawPreview() + super().update() def getXY(self): diff --git a/src/components/video.py b/src/components/video.py index 8861d70..8aa1420 100644 --- a/src/components/video.py +++ b/src/components/video.py @@ -140,7 +140,7 @@ class Component(Component): self.scale = self.page.spinBox_scale.value() self.xPosition = self.page.spinBox_x.value() self.yPosition = self.page.spinBox_y.value() - self.parent.drawPreview() + super().update() def previewRender(self, previewWorker): diff --git a/src/core.py b/src/core.py index 3f0a6ad..2500fa6 100644 --- a/src/core.py +++ b/src/core.py @@ -414,6 +414,7 @@ class Core: f.write('[Components]\n') for comp in self.selectedComponents: saveValueStore = comp.savePreset() + saveValueStore['preset'] = comp.currentPreset f.write('%s\n' % str(comp)) f.write('%s\n' % str(comp.version())) f.write('%s\n' % toolkit.presetToString(saveValueStore)) diff --git a/src/presetmanager.py b/src/presetmanager.py index 40aa73f..0028203 100644 --- a/src/presetmanager.py +++ b/src/presetmanager.py @@ -160,6 +160,7 @@ class PresetManager(QtWidgets.QDialog): selectedComponents[index].currentPreset = newName saveValueStore = \ selectedComponents[index].savePreset() + saveValueStore['preset'] = newName componentName = str(selectedComponents[index]).strip() vers = selectedComponents[index].version() self.createNewPreset( -- cgit v1.2.3