From f6fbc8d2423ac5ae683a7613b53648db3e02e323 Mon Sep 17 00:00:00 2001 From: tassaron Date: Sun, 9 Jul 2017 14:31:19 -0400 Subject: a basic Sound component for mixing sounds to be greatly expanded... --- src/mainwindow.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/mainwindow.py') diff --git a/src/mainwindow.py b/src/mainwindow.py index 165b5bd..3cd45d6 100644 --- a/src/mainwindow.py +++ b/src/mainwindow.py @@ -557,9 +557,11 @@ class MainWindow(QtWidgets.QMainWindow): 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) -- 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/mainwindow.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/mainwindow.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 62ab09e3f36dcaf6c1a4680dc6c4d048fb2e165c Mon Sep 17 00:00:00 2001 From: tassaron Date: Sat, 15 Jul 2017 01:00:03 -0400 Subject: Video comp verifies audio streams, videoThread moved into Core off-by-1 bug fixed in exporting, & use fewer threads for fewer CPUs --- src/command.py | 22 ++++++++-------------- src/components/video.py | 25 ++++++++++++++++++++----- src/core.py | 16 ++++++++++++++++ src/mainwindow.py | 22 ++++++---------------- src/video_thread.py | 35 +++++++++++++++++++++++------------ 5 files changed, 73 insertions(+), 47 deletions(-) (limited to 'src/mainwindow.py') diff --git a/src/command.py b/src/command.py index be194d8..41618f8 100644 --- a/src/command.py +++ b/src/command.py @@ -9,13 +9,12 @@ import os import sys import core -import video_thread from toolkit import LoadDefaultSettings class Command(QtCore.QObject): - videoTask = QtCore.pyqtSignal(str, str, list) + createVideo = QtCore.pyqtSignal() def __init__(self): QtCore.QObject.__init__(self) @@ -112,21 +111,16 @@ class Command(QtCore.QObject): quit(1) def createAudioVisualisation(self, input, output): - self.videoThread = QtCore.QThread(self) - self.videoWorker = video_thread.Worker(self) - self.videoWorker.moveToThread(self.videoThread) - self.videoWorker.videoCreated.connect(self.videoCreated) - - self.videoThread.start() - self.videoTask.emit( - input, - output, - list(reversed(self.core.selectedComponents)) + self.core.selectedComponents = list( + reversed(self.core.selectedComponents)) + self.core.componentListChanged() + self.worker = self.core.newVideoWorker( + self, input, output ) + self.worker.videoCreated.connect(self.videoCreated) + self.createVideo.emit() def videoCreated(self): - self.videoThread.quit() - self.videoThread.wait() quit(0) def showMessage(self, **kwargs): diff --git a/src/components/video.py b/src/components/video.py index 8aa1420..b3b6a59 100644 --- a/src/components/video.py +++ b/src/components/video.py @@ -8,7 +8,7 @@ from queue import PriorityQueue from component import Component, BadComponentInit from frame import BlankFrame -from toolkit import openPipe +from toolkit import openPipe, checkOutput class Video: @@ -155,14 +155,29 @@ class Component(Component): def properties(self): props = [] - if self.useAudio: - props.append('audio') if not self.videoPath or self.badVideo \ or not os.path.exists(self.videoPath): - props.append('error') + return ['error'] + + if self.useAudio: + props.append('audio') + # test if an audio stream really exists + audioTestCommand = [ + self.core.FFMPEG_BIN, + '-i', self.videoPath, + '-vn', '-f', 'null', '-' + ] + try: + checkOutput(audioTestCommand, stderr=subprocess.DEVNULL) + except subprocess.CalledProcessError: + self.badAudio = True + return ['error'] + return props def error(self): + if hasattr(self, 'badAudio'): + return "Could not identify an audio stream in this video." if not self.videoPath: return "There is no video selected." if not os.path.exists(self.videoPath): @@ -180,7 +195,7 @@ class Component(Component): self.blankFrame_ = BlankFrame(width, height) self.updateChunksize(width, height) self.video = Video( - ffmpeg=self.parent.core.FFMPEG_BIN, videoPath=self.videoPath, + ffmpeg=self.core.FFMPEG_BIN, videoPath=self.videoPath, width=width, height=height, chunkSize=self.chunkSize, frameRate=int(self.settings.value("outputFrameRate")), parent=self.parent, loopVideo=self.loopVideo, diff --git a/src/core.py b/src/core.py index 2500fa6..55bf261 100644 --- a/src/core.py +++ b/src/core.py @@ -12,6 +12,7 @@ from PyQt5.QtCore import QStandardPaths import toolkit from frame import Frame +import video_thread class Core: @@ -633,6 +634,21 @@ class Core: return completeAudioArray + def newVideoWorker(self, loader, audioFile, outputPath): + self.videoThread = QtCore.QThread(loader) + videoWorker = video_thread.Worker( + loader, audioFile, outputPath, self.selectedComponents + ) + videoWorker.moveToThread(self.videoThread) + videoWorker.videoCreated.connect(self.videoCreated) + + self.videoThread.start() + return videoWorker + + def videoCreated(self): + self.videoThread.quit() + self.videoThread.wait() + def cancel(self): self.canceled = True diff --git a/src/mainwindow.py b/src/mainwindow.py index 771b6b8..76ed179 100644 --- a/src/mainwindow.py +++ b/src/mainwindow.py @@ -16,7 +16,6 @@ import time import core import preview_thread -import video_thread from presetmanager import PresetManager from toolkit import LoadDefaultSettings, disableWhenEncoding, checkOutput @@ -49,9 +48,9 @@ class PreviewWindow(QtWidgets.QLabel): class MainWindow(QtWidgets.QMainWindow): - newTask = QtCore.pyqtSignal(list) + createVideo = QtCore.pyqtSignal() + newTask = QtCore.pyqtSignal(list) # for the preview window processTask = QtCore.pyqtSignal() - videoTask = QtCore.pyqtSignal(str, str, list) def __init__(self, window, project): QtWidgets.QMainWindow.__init__(self) @@ -497,20 +496,15 @@ class MainWindow(QtWidgets.QMainWindow): self.canceled = False self.progressBarUpdated(-1) - self.videoThread = QtCore.QThread(self) - self.videoWorker = video_thread.Worker(self) - self.videoWorker.moveToThread(self.videoThread) - self.videoWorker.videoCreated.connect(self.videoCreated) + 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.videoThread.start() - self.videoTask.emit( - audioFile, - outputPath, - self.core.selectedComponents) + self.createVideo.emit() def changeEncodingStatus(self, status): self.encoding = status @@ -569,10 +563,6 @@ class MainWindow(QtWidgets.QMainWindow): else: self.window.progressBar_createVideo.setFormat(value) - def videoCreated(self): - self.videoThread.quit() - self.videoThread.wait() - def updateResolution(self): resIndex = int(self.window.comboBox_resolution.currentIndex()) res = self.resolutions[resIndex].split('x') diff --git a/src/video_thread.py b/src/video_thread.py index b0562db..5295a3b 100644 --- a/src/video_thread.py +++ b/src/video_thread.py @@ -19,7 +19,7 @@ import time import signal import core -from toolkit import openPipe, checkOutput +from toolkit import openPipe from frame import Checkerboard @@ -31,13 +31,19 @@ class Worker(QtCore.QObject): progressBarSetText = pyqtSignal(str) encoding = pyqtSignal(bool) - def __init__(self, parent=None): + + def __init__(self, parent, inputFile, outputFile, components): QtCore.QObject.__init__(self) self.core = parent.core self.settings = parent.core.settings self.modules = parent.core.modules + parent.createVideo.connect(self.createVideo) + self.parent = parent - parent.videoTask.connect(self.createVideo) + self.components = components + self.outputFile = outputFile + self.inputFile = inputFile + self.sampleSize = 1470 # 44100 / 30 = 1470 self.canceled = False self.error = False @@ -55,7 +61,7 @@ class Worker(QtCore.QObject): bgI = int(audioI / self.sampleSize) frame = None for compNo, comp in reversed(list(enumerate(self.components))): - layerNo = len(self.components) - compNo + layerNo = len(self.components) - compNo - 1 if layerNo in self.staticComponents: if self.staticComponents[layerNo] is None: # this layer was merged into a following layer @@ -106,12 +112,10 @@ class Worker(QtCore.QObject): self.previewQueue.task_done() - @pyqtSlot(str, str, list) - def createVideo(self, inputFile, outputFile, components): + @pyqtSlot() + def createVideo(self): numpy.seterr(divide='ignore') self.encoding.emit(True) - self.components = components - self.outputFile = outputFile self.extraAudio = [] self.width = int(self.settings.value('outputWidth')) self.height = int(self.settings.value('outputHeight')) @@ -131,7 +135,7 @@ class Worker(QtCore.QObject): # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~ self.progressBarSetText.emit("Loading audio file...") - self.completeAudioArray = self.core.readAudioFile(inputFile, self) + self.completeAudioArray = self.core.readAudioFile(self.inputFile, self) self.progressBarUpdate.emit(0) self.progressBarSetText.emit("Starting components...") @@ -189,7 +193,9 @@ class Worker(QtCore.QObject): ) self.staticComponents[compNo] = None - ffmpegCommand = self.core.createFfmpegCommand(inputFile, outputFile) + ffmpegCommand = self.core.createFfmpegCommand( + self.inputFile, self.outputFile + ) print('###### FFMPEG COMMAND ######\n%s' % " ".join(ffmpegCommand)) print('############################') self.out_pipe = openPipe( @@ -200,9 +206,14 @@ class Worker(QtCore.QObject): # START CREATING THE VIDEO # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~ - # Make three renderNodes in new threads to create the frames + # Make 2 or 3 renderNodes in new threads to create the frames self.renderThreads = [] - for i in range(3): + try: + numCpus = len(os.sched_getaffinity(0)) + except: + numCpus = os.cpu_count() + + for i in range(2 if numCpus <= 2 else 3): self.renderThreads.append( Thread(target=self.renderNode, name="Render Thread")) self.renderThreads[i].daemon = True -- cgit v1.2.3