From c1457b6dad4640b17679dd802e372bd46a13d2a5 Mon Sep 17 00:00:00 2001 From: tassaron Date: Sat, 29 Jul 2017 13:08:28 -0400 Subject: starting work on Waveform component split Video class out of Video component for reuse in Waveform --- src/components/video.py | 198 +++++++++++++----------------------------------- 1 file changed, 51 insertions(+), 147 deletions(-) (limited to 'src/components/video.py') diff --git a/src/components/video.py b/src/components/video.py index b2487c1..d3460ff 100644 --- a/src/components/video.py +++ b/src/components/video.py @@ -1,103 +1,13 @@ -from PIL import Image, ImageDraw +from PIL import Image from PyQt5 import QtGui, QtCore, QtWidgets import os import math import subprocess -import signal -import threading -from queue import PriorityQueue from component import Component, ComponentError from toolkit.frame import BlankFrame -from toolkit.ffmpeg import testAudioStream -from toolkit import openPipe, checkOutput - - -class Video: - '''Opens a pipe to ffmpeg and stores a buffer of raw video frames.''' - - # error from the thread used to fill the buffer - threadError = None - - def __init__(self, **kwargs): - mandatoryArgs = [ - 'ffmpeg', # path to ffmpeg, usually self.core.FFMPEG_BIN - 'videoPath', - 'width', - 'height', - 'scale', # percentage scale - 'frameRate', # frames per second - 'chunkSize', # number of bytes in one frame - 'parent', # mainwindow object - 'component', # component object - ] - for arg in mandatoryArgs: - setattr(self, arg, kwargs[arg]) - - self.frameNo = -1 - self.currentFrame = 'None' - if 'loopVideo' in kwargs and kwargs['loopVideo']: - self.loopValue = '-1' - else: - self.loopValue = '0' - self.command = [ - self.ffmpeg, - '-thread_queue_size', '512', - '-r', str(self.frameRate), - '-stream_loop', self.loopValue, - '-i', self.videoPath, - '-f', 'image2pipe', - '-pix_fmt', 'rgba', - '-filter_complex', '[0:v] scale=%s:%s' % scale( - self.scale, self.width, self.height, str), - '-vcodec', 'rawvideo', '-', - ] - - self.frameBuffer = PriorityQueue() - self.frameBuffer.maxsize = self.frameRate - self.finishedFrames = {} - - self.thread = threading.Thread( - target=self.fillBuffer, - name='Video Frame-Fetcher' - ) - self.thread.daemon = True - self.thread.start() - - def frame(self, num): - while True: - if num in self.finishedFrames: - image = self.finishedFrames.pop(num) - return finalizeFrame( - self.component, image, self.width, self.height) - - i, image = self.frameBuffer.get() - self.finishedFrames[i] = image - self.frameBuffer.task_done() - - def fillBuffer(self): - self.pipe = openPipe( - self.command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL, bufsize=10**8 - ) - while True: - if self.parent.canceled: - break - self.frameNo += 1 - - # If we run out of frames, use the last good frame and loop. - try: - if len(self.currentFrame) == 0: - self.frameBuffer.put((self.frameNo-1, self.lastFrame)) - continue - except AttributeError: - Video.threadError = ComponentError(self.component, 'video') - break - - self.currentFrame = self.pipe.stdout.read(self.chunkSize) - if len(self.currentFrame) != 0: - self.frameBuffer.put((self.frameNo, self.currentFrame)) - self.lastFrame = self.currentFrame +from toolkit.ffmpeg import testAudioStream, FfmpegVideo +from toolkit import openPipe, closePipe, checkOutput, scale class Component(Component): @@ -182,22 +92,21 @@ class Component(Component): def preFrameRender(self, **kwargs): super().preFrameRender(**kwargs) self.updateChunksize() - self.video = Video( - ffmpeg=self.core.FFMPEG_BIN, videoPath=self.videoPath, + self.video = FfmpegVideo( + inputPath=self.videoPath, filter_=self.makeFfmpegFilter(), width=self.width, height=self.height, chunkSize=self.chunkSize, frameRate=int(self.settings.value("outputFrameRate")), parent=self.parent, loopVideo=self.loopVideo, - component=self, scale=self.scale + component=self ) if os.path.exists(self.videoPath) else None def frameRender(self, frameNo): - if Video.threadError is not None: - raise Video.threadError - return self.video.frame(frameNo) + if FfmpegVideo.threadError is not None: + raise FfmpegVideo.threadError + return self.finalizeFrame(self.video.frame(frameNo)) def postFrameRender(self): - self.video.pipe.stdout.close() - self.video.pipe.send_signal(signal.SIGINT) + closePipe(self.video.pipe) def pickVideo(self): imgDir = self.settings.value("componentDir", os.path.expanduser("~")) @@ -220,23 +129,30 @@ class Component(Component): '-i', self.videoPath, '-f', 'image2pipe', '-pix_fmt', 'rgba', - '-filter_complex', '[0:v] scale=%s:%s' % scale( - self.scale, width, height, str), + ] + command.extend(self.makeFfmpegFilter()) + command.extend([ '-vcodec', 'rawvideo', '-', '-ss', '90', - '-vframes', '1', - ] + '-frames:v', '1', + ]) pipe = openPipe( command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, bufsize=10**8 ) byteFrame = pipe.stdout.read(self.chunkSize) - pipe.stdout.close() - pipe.send_signal(signal.SIGINT) + closePipe(pipe) - frame = finalizeFrame(self, byteFrame, width, height) + frame = self.finalizeFrame(byteFrame) return frame + def makeFfmpegFilter(self): + return [ + '-filter_complex', + '[0:v] scale=%s:%s' % scale( + self.scale, self.width, self.height, str), + ] + def updateChunksize(self): if self.scale != 100 and not self.distort: width, height = scale(self.scale, self.width, self.height, int) @@ -268,44 +184,32 @@ class Component(Component): print('Load a video:\n path=/filepath/to/video.mp4') print('Using audio:\n path=/filepath/to/video.mp4 audio') + def finalizeFrame(self, imageData): + try: + if self.distort: + image = Image.frombytes( + 'RGBA', + (self.width, self.height), + imageData) + else: + image = Image.frombytes( + 'RGBA', + scale(self.scale, self.width, self.height, int), + imageData) + + except ValueError: + print( + '### BAD VIDEO SELECTED ###\n' + 'Video will not export with these settings' + ) + self.badVideo = True + return BlankFrame(self.width, self.height) -def scale(scale, width, height, returntype=None): - width = (float(width) / 100.0) * float(scale) - height = (float(height) / 100.0) * float(scale) - if returntype == str: - return (str(math.ceil(width)), str(math.ceil(height))) - elif returntype == int: - return (math.ceil(width), math.ceil(height)) - else: - return (width, height) - - -def finalizeFrame(self, imageData, width, height): - try: - if self.distort: - image = Image.frombytes( - 'RGBA', - (width, height), - imageData) + if self.scale != 100 \ + or self.xPosition != 0 or self.yPosition != 0: + frame = BlankFrame(self.width, self.height) + frame.paste(image, box=(self.xPosition, self.yPosition)) else: - image = Image.frombytes( - 'RGBA', - scale(self.scale, width, height, int), - imageData) - - except ValueError: - print( - '### BAD VIDEO SELECTED ###\n' - 'Video will not export with these settings' - ) - self.badVideo = True - return BlankFrame(width, height) - - if self.scale != 100 \ - or self.xPosition != 0 or self.yPosition != 0: - frame = BlankFrame(width, height) - frame.paste(image, box=(self.xPosition, self.yPosition)) - else: - frame = image - self.badVideo = False - return frame + frame = image + self.badVideo = False + return frame -- cgit v1.2.3 From 1297af61c9ce00b6dd76f8ec690baedf5bf887c7 Mon Sep 17 00:00:00 2001 From: tassaron Date: Sat, 29 Jul 2017 20:27:46 -0400 Subject: waveform component is working, preview is glitchy --- src/components/original.py | 3 + src/components/video.py | 10 ++-- src/components/waveform.py | 134 +++++++++++++++++++++++++++++++-------------- src/components/waveform.ui | 95 +++++++++++++++++++++++++++++++- src/toolkit/common.py | 21 ------- src/toolkit/ffmpeg.py | 30 ++++++++-- src/toolkit/frame.py | 12 ++++ src/video_thread.py | 38 +++++++++---- 8 files changed, 256 insertions(+), 87 deletions(-) (limited to 'src/components/video.py') diff --git a/src/components/original.py b/src/components/original.py index 3d1a574..621af6f 100644 --- a/src/components/original.py +++ b/src/components/original.py @@ -18,6 +18,9 @@ class Component(Component): def names(*args): return ['Original Audio Visualization'] + def properties(self): + return ['pcm'] + def widget(self, *args): self.visColor = (255, 255, 255) self.scale = 20 diff --git a/src/components/video.py b/src/components/video.py index d3460ff..6cd16e5 100644 --- a/src/components/video.py +++ b/src/components/video.py @@ -4,10 +4,10 @@ import os import math import subprocess -from component import Component, ComponentError -from toolkit.frame import BlankFrame -from toolkit.ffmpeg import testAudioStream, FfmpegVideo -from toolkit import openPipe, closePipe, checkOutput, scale +from component import Component +from toolkit.frame import BlankFrame, scale +from toolkit.ffmpeg import openPipe, closePipe, testAudioStream, FfmpegVideo +from toolkit import checkOutput class Component(Component): @@ -132,7 +132,7 @@ class Component(Component): ] command.extend(self.makeFfmpegFilter()) command.extend([ - '-vcodec', 'rawvideo', '-', + '-codec:v', 'rawvideo', '-', '-ss', '90', '-frames:v', '1', ]) diff --git a/src/components/waveform.py b/src/components/waveform.py index 487a3bb..375b3fc 100644 --- a/src/components/waveform.py +++ b/src/components/waveform.py @@ -5,10 +5,10 @@ import os import math import subprocess -from component import Component, ComponentError -from toolkit.frame import BlankFrame -from toolkit import openPipe, checkOutput, rgbFromString -from toolkit.ffmpeg import FfmpegVideo +from component import Component +from toolkit.frame import BlankFrame, scale +from toolkit import checkOutput, rgbFromString, pickColor +from toolkit.ffmpeg import openPipe, closePipe, getAudioDuration, FfmpegVideo class Component(Component): @@ -21,17 +21,27 @@ class Component(Component): self.page.lineEdit_color.setText('%s,%s,%s' % self.color) btnStyle = "QPushButton { background-color : %s; outline: none; }" \ - % QColor(*self.color1).name() - self.page.lineEdit_color.setStylesheet(btnStyle) + % QColor(*self.color).name() + self.page.pushButton_color.setStyleSheet(btnStyle) self.page.pushButton_color.clicked.connect(lambda: self.pickColor()) + self.page.spinBox_scale.valueChanged.connect(self.updateChunksize) + + if hasattr(self.parent, 'window'): + self.parent.window.lineEdit_audioFile.textChanged.connect( + self.update + ) self.trackWidgets( { 'mode': self.page.comboBox_mode, + 'amplitude': self.page.comboBox_amplitude, 'x': self.page.spinBox_x, 'y': self.page.spinBox_y, 'mirror': self.page.checkBox_mirror, 'scale': self.page.spinBox_scale, + 'opacity': self.page.spinBox_opacity, + 'compress': self.page.checkBox_compress, + 'mono': self.page.checkBox_mono, } ) @@ -42,6 +52,26 @@ class Component(Component): self.page.pushButton_color.setStyleSheet(btnStyle) super().update() + def loadPreset(self, pr, *args): + super().loadPreset(pr, *args) + + self.page.lineEdit_color.setText('%s,%s,%s' % pr['color']) + btnStyle = "QPushButton { background-color : %s; outline: none; }" \ + % QColor(*pr['color']).name() + self.page.pushButton_color.setStyleSheet(btnStyle) + + def savePreset(self): + saveValueStore = super().savePreset() + saveValueStore['color'] = self.color + return saveValueStore + + def pickColor(self): + RGBstring, btnStyle = pickColor() + if not RGBstring: + return + self.page.lineEdit_color.setText(RGBstring) + self.page.pushButton_color.setStyleSheet(btnStyle) + def previewRender(self): self.updateChunksize() frame = self.getPreviewFrame(self.width, self.height) @@ -53,10 +83,11 @@ class Component(Component): def preFrameRender(self, **kwargs): super().preFrameRender(**kwargs) self.updateChunksize() + w, h = scale(self.scale, self.width, self.height, str) self.video = FfmpegVideo( inputPath=self.audioFile, - filter_=makeFfmpegFilter(), - width=self.width, height=self.height, + filter_=self.makeFfmpegFilter(), + width=w, height=h, chunkSize=self.chunkSize, frameRate=int(self.settings.value("outputFrameRate")), parent=self.parent, component=self, @@ -65,7 +96,7 @@ class Component(Component): def frameRender(self, frameNo): if FfmpegVideo.threadError is not None: raise FfmpegVideo.threadError - return finalizeFrame(self.video.frame(frameNo)) + return self.finalizeFrame(self.video.frame(frameNo)) def postFrameRender(self): closePipe(self.video.pipe) @@ -74,18 +105,25 @@ class Component(Component): inputFile = self.parent.window.lineEdit_audioFile.text() if not inputFile or not os.path.exists(inputFile): return + duration = getAudioDuration(inputFile) + if not duration: + return + startPt = duration / 3 command = [ self.core.FFMPEG_BIN, '-thread_queue_size', '512', + '-r', self.settings.value("outputFrameRate"), + '-ss', "{0:.3f}".format(startPt), '-i', inputFile, '-f', 'image2pipe', '-pix_fmt', 'rgba', ] - command.extend(self.makeFfmpegFilter()) + command.extend(self.makeFfmpegFilter(preview=True, startPt=startPt)) command.extend([ - '-vcodec', 'rawvideo', '-', - '-ss', '90', + '-an', + '-s:v', '%sx%s' % scale(self.scale, self.width, self.height, str), + '-codec:v', 'rawvideo', '-', '-frames:v', '1', ]) pipe = openPipe( @@ -95,45 +133,57 @@ class Component(Component): byteFrame = pipe.stdout.read(self.chunkSize) closePipe(pipe) - frame = finalizeFrame(self, byteFrame, width, height) + frame = self.finalizeFrame(byteFrame) return frame - def makeFfmpegFilter(self): + def makeFfmpegFilter(self, preview=False, startPt=0): w, h = scale(self.scale, self.width, self.height, str) + if self.amplitude == 0: + amplitude = 'lin' + elif self.amplitude == 1: + amplitude = 'log' + elif self.amplitude == 2: + amplitude = 'sqrt' + elif self.amplitude == 3: + amplitude = 'cbrt' + hexcolor = QColor(*self.color).name() + opacity = "{0:.1f}".format(self.opacity / 100) + return [ '-filter_complex', - '[0:a] showwaves=s=%sx%s:mode=%s,format=rgba [v]' % ( - w, h, self.mode, + '[0:a] %s%s' + 'showwaves=r=30:s=%sx%s:mode=%s:colors=%s@%s:scale=%s%s%s [v1]; ' + '[v1] scale=%s:%s%s [v]' % ( + 'compand=gain=2,' if self.compress else '', + 'aformat=channel_layouts=mono,' if self.mono else '', + self.settings.value("outputWidth"), + self.settings.value("outputHeight"), + str(self.page.comboBox_mode.currentText()).lower(), + hexcolor, opacity, amplitude, + ', drawbox=x=(iw-w)/2:y=(ih-h)/2:w=iw:h=4:color=%s@%s' % ( + hexcolor, opacity + ) if self.mode < 2 else '', + ', hflip' if self.mirror else'', + w, h, + ', trim=duration=%s' % "{0:.3f}".format(startPt + 1) if preview else '', ), '-map', '[v]', - '-map', '0:a', ] def updateChunksize(self): - if self.scale != 100: - width, height = scale(self.scale, self.width, self.height, int) - else: - width, height = self.width, self.height + width, height = scale(self.scale, self.width, self.height, int) self.chunkSize = 4 * width * height - -def scale(scale, width, height, returntype=None): - width = (float(width) / 100.0) * float(scale) - height = (float(height) / 100.0) * float(scale) - if returntype == str: - return (str(math.ceil(width)), str(math.ceil(height))) - elif returntype == int: - return (math.ceil(width), math.ceil(height)) - else: - return (width, height) - - -def finalizeFrame(self, imageData, width, height): - # frombytes goes here - if self.scale != 100 \ - or self.x != 0 or self.y != 0: - frame = BlankFrame(width, height) - frame.paste(image, box=(self.x, self.y)) - else: - frame = image - return frame + def finalizeFrame(self, imageData): + image = Image.frombytes( + 'RGBA', + scale(self.scale, self.width, self.height, int), + imageData + ) + if self.scale != 100 \ + or self.x != 0 or self.y != 0: + frame = BlankFrame(self.width, self.height) + frame.paste(image, box=(self.x, self.y)) + else: + frame = image + return frame diff --git a/src/components/waveform.ui b/src/components/waveform.ui index 5d62150..0e40380 100644 --- a/src/components/waveform.ui +++ b/src/components/waveform.ui @@ -226,9 +226,31 @@ - + - Mirror + Opacity + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + QAbstractSpinBox::UpDownArrows + + + % + + + 10 + + + 400 + + + 100 @@ -263,6 +285,75 @@ + + + + + + Compress + + + + + + + Mono + + + + + + + Mirror + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Amplitude + + + + + + + + Linear + + + + + Logarithmic + + + + + Square root + + + + + Cubic root + + + + + + diff --git a/src/toolkit/common.py b/src/toolkit/common.py index 128ed08..5d424e0 100644 --- a/src/toolkit/common.py +++ b/src/toolkit/common.py @@ -6,22 +6,9 @@ import string import os import sys import subprocess -import signal -import math from collections import OrderedDict -def scale(scale, width, height, returntype=None): - width = (float(width) / 100.0) * float(scale) - height = (float(height) / 100.0) * float(scale) - if returntype == str: - return (str(math.ceil(width)), str(math.ceil(height))) - elif returntype == int: - return (math.ceil(width), math.ceil(height)) - else: - return (width, height) - - def badName(name): '''Returns whether a name contains non-alphanumeric chars''' return any([letter in string.punctuation for letter in name]) @@ -69,14 +56,6 @@ def checkOutput(commandList, **kwargs): return subprocess.check_output(commandList, **kwargs) -@pipeWrapper -def openPipe(commandList, **kwargs): - return subprocess.Popen(commandList, **kwargs) - -def closePipe(pipe): - pipe.stdout.close() - pipe.send_signal(signal.SIGINT) - def disableWhenEncoding(func): def decorator(self, *args, **kwargs): if self.encoding: diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py index fea9d4e..e37282f 100644 --- a/src/toolkit/ffmpeg.py +++ b/src/toolkit/ffmpeg.py @@ -6,10 +6,12 @@ import sys import os import subprocess import threading +import signal from queue import PriorityQueue import core -from toolkit.common import checkOutput, openPipe +from toolkit.common import checkOutput, pipeWrapper +from component import ComponentError class FfmpegVideo: @@ -60,7 +62,8 @@ class FfmpegVideo: kwargs['filter_'] ) self.command.extend([ - '-vcodec', 'rawvideo', '-', + '-s:v', '%sx%s' % (self.width, self.height), + '-codec:v', 'rawvideo', '-', ]) self.frameBuffer = PriorityQueue() @@ -85,9 +88,11 @@ class FfmpegVideo: self.frameBuffer.task_done() def fillBuffer(self): + import sys + print(self.command) self.pipe = openPipe( self.command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL, bufsize=10**8 + stderr=sys.__stdout__, bufsize=10**8 ) while True: if self.parent.canceled: @@ -100,7 +105,7 @@ class FfmpegVideo: self.frameBuffer.put((self.frameNo-1, self.lastFrame)) continue except AttributeError: - Video.threadError = ComponentError(self.component, 'video') + FfmpegVideo.threadError = ComponentError(self.component, 'video') break self.currentFrame = self.pipe.stdout.read(self.chunkSize) @@ -109,6 +114,16 @@ class FfmpegVideo: self.lastFrame = self.currentFrame +@pipeWrapper +def openPipe(commandList, **kwargs): + return subprocess.Popen(commandList, **kwargs) + + +def closePipe(pipe): + pipe.stdout.close() + pipe.send_signal(signal.SIGINT) + + def findFfmpeg(): if getattr(sys, 'frozen', False): # The application is frozen @@ -347,7 +362,12 @@ def getAudioDuration(filename): except subprocess.CalledProcessError as ex: fileInfo = ex.output - info = fileInfo.decode("utf-8").split('\n') + try: + info = fileInfo.decode("utf-8").split('\n') + except UnicodeDecodeError as e: + print('Unicode error:', str(e)) + return False + for line in info: if 'Duration' in line: d = line.split(',')[0] diff --git a/src/toolkit/frame.py b/src/toolkit/frame.py index b66e037..f42d4c9 100644 --- a/src/toolkit/frame.py +++ b/src/toolkit/frame.py @@ -6,6 +6,7 @@ from PIL import Image from PIL.ImageQt import ImageQt import sys import os +import math import core @@ -41,6 +42,17 @@ class PaintColor(QtGui.QColor): super().__init__(b, g, r, a) +def scale(scale, width, height, returntype=None): + width = (float(width) / 100.0) * float(scale) + height = (float(height) / 100.0) * float(scale) + if returntype == str: + return (str(math.ceil(width)), str(math.ceil(height))) + elif returntype == int: + return (math.ceil(width), math.ceil(height)) + else: + return (width, height) + + def defaultSize(framefunc): '''Makes width/height arguments optional''' def decorator(*args): diff --git a/src/video_thread.py b/src/video_thread.py index f27ec21..5963def 100644 --- a/src/video_thread.py +++ b/src/video_thread.py @@ -19,9 +19,11 @@ import time import signal from component import ComponentError -from toolkit import openPipe -from toolkit.ffmpeg import readAudioFile, createFfmpegCommand from toolkit.frame import Checkerboard +from toolkit.ffmpeg import ( + openPipe, readAudioFile, + getAudioDuration, createFfmpegCommand +) class Worker(QtCore.QObject): @@ -132,15 +134,24 @@ class Worker(QtCore.QObject): # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~ # READ AUDIO, INITIALIZE COMPONENTS, OPEN A PIPE TO FFMPEG # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~ - - self.progressBarSetText.emit("Loading audio file...") - audioFileTraits = readAudioFile( - self.inputFile, self - ) - if audioFileTraits is None: - self.cancelExport() - return - self.completeAudioArray, duration = audioFileTraits + if any([ + True if 'pcm' in comp.properties() else False + for comp in self.components + ]): + self.progressBarSetText.emit("Loading audio file...") + audioFileTraits = readAudioFile( + self.inputFile, self + ) + if audioFileTraits is None: + self.cancelExport() + return + self.completeAudioArray, duration = audioFileTraits + else: + duration = getAudioDuration(self.inputFile) + class FakeList: + def __len__(self): + return int((duration * 44100) + 44100) - 1470 + self.completeAudioArray = FakeList() self.progressBarUpdate.emit(0) self.progressBarSetText.emit("Starting components...") @@ -284,7 +295,10 @@ class Worker(QtCore.QObject): numpy.seterr(all='print') - self.out_pipe.stdin.close() + try: + self.out_pipe.stdin.close() + except BrokenPipeError: + print('Broken pipe to ffmpeg!') if self.out_pipe.stderr is not None: print(self.out_pipe.stderr.read()) self.out_pipe.stderr.close() -- cgit v1.2.3 From 5784cdbcf87556b61519782cd1fc27065ffbc631 Mon Sep 17 00:00:00 2001 From: tassaron Date: Tue, 1 Aug 2017 21:57:36 -0400 Subject: x/y pixel values update to match output resolution --- src/component.py | 39 ++++++++++++++++++++++++++++++++++++--- src/components/color.py | 3 +++ src/components/image.py | 3 +++ src/components/original.py | 2 ++ src/components/spectrum.py | 3 +++ src/components/text.py | 19 +++++++++++-------- src/components/video.py | 3 +++ src/components/waveform.py | 3 +++ src/mainwindow.py | 5 ++++- 9 files changed, 68 insertions(+), 12 deletions(-) (limited to 'src/components/video.py') diff --git a/src/component.py b/src/component.py index d47aeae..5dfe2ab 100644 --- a/src/component.py +++ b/src/component.py @@ -6,6 +6,7 @@ from PyQt5 import uic, QtCore, QtWidgets from PyQt5.QtGui import QColor import os import sys +import math import time from toolkit.frame import BlankFrame @@ -176,7 +177,9 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): self._presetNames = {} self._commandArgs = {} self._colorWidgets = {} + self._colorFuncs = {} self._relativeWidgets = {} + self._relativeValues = {} self._lockedProperties = None self._lockedError = None @@ -291,14 +294,44 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): ''' for attr, widget in self._trackedWidgets.items(): if attr in self._colorWidgets: + # Color Widgets: text stored as tuple & update the button color rgbTuple = rgbFromString(widget.text()) - setattr(self, attr, rgbTuple) btnStyle = ( "QPushButton { background-color : %s; outline: none; }" - % QColor(*rgbTuple).name() - ) + % QColor(*rgbTuple).name()) self._colorWidgets[attr].setStyleSheet(btnStyle) + setattr(self, attr, rgbTuple) + + elif attr in self._relativeWidgets: + # Relative widgets: number scales to fit export resolution + if self._relativeWidgets[attr] == 'x': + dimension = self.width + else: + dimension = self.height + try: + oldUserValue = getattr(self, attr) + except AttributeError: + oldUserValue = self._trackedWidgets[attr].value() + newUserValue = self._trackedWidgets[attr].value() + newRelativeVal = newUserValue / dimension + + if attr in self._relativeValues: + if oldUserValue == newUserValue: + oldRelativeVal = self._relativeValues[attr] + if oldRelativeVal != newRelativeVal: + # Float changed without pixel value changing, which + # means the pixel value needs to be updated + self._trackedWidgets[attr].blockSignals(True) + self._trackedWidgets[attr].setValue( + math.ceil(dimension * oldRelativeVal)) + self._trackedWidgets[attr].blockSignals(False) + if oldUserValue != newUserValue \ + or attr not in self._relativeValues: + self._relativeValues[attr] = newRelativeVal + setattr(self, attr, self._trackedWidgets[attr].value()) + else: + # Normal tracked widget setattr(self, attr, getWidgetValue(widget)) if not self.core.openingProject: diff --git a/src/components/color.py b/src/components/color.py index d6fffc6..703caca 100644 --- a/src/components/color.py +++ b/src/components/color.py @@ -60,6 +60,9 @@ class Component(Component): }, colorWidgets={ 'color1': self.page.pushButton_color1, 'color2': self.page.pushButton_color2, + }, relativeWidgets={ + 'x': 'x', + 'y': 'y', }, ) diff --git a/src/components/image.py b/src/components/image.py index a96f127..2ffa5a1 100644 --- a/src/components/image.py +++ b/src/components/image.py @@ -28,6 +28,9 @@ class Component(Component): 'imagePath': 'image', 'xPosition': 'x', 'yPosition': 'y', + }, relativeWidgets={ + 'xPosition': 'x', + 'yPosition': 'y', }, ) diff --git a/src/components/original.py b/src/components/original.py index 950ac7b..67e3239 100644 --- a/src/components/original.py +++ b/src/components/original.py @@ -40,6 +40,8 @@ class Component(Component): 'y': self.page.spinBox_y, }, colorWidgets={ 'visColor': self.page.pushButton_visColor, + }, relativeWidgets={ + 'y': 'y', }) def previewRender(self): diff --git a/src/components/spectrum.py b/src/components/spectrum.py index 8ab8404..2cc641d 100644 --- a/src/components/spectrum.py +++ b/src/components/spectrum.py @@ -49,6 +49,9 @@ class Component(Component): 'compress': self.page.checkBox_compress, 'mono': self.page.checkBox_mono, 'hue': self.page.spinBox_hue, + }, relativeWidgets={ + 'x': 'x', + 'y': 'y', } ) for widget in self._trackedWidgets.values(): diff --git a/src/components/text.py b/src/components/text.py index 1fe3467..0f87038 100644 --- a/src/components/text.py +++ b/src/components/text.py @@ -17,15 +17,12 @@ class Component(Component): def widget(self, *args): super().widget(*args) - height = int(self.settings.value('outputHeight')) - width = int(self.settings.value('outputWidth')) + # height = int(self.settings.value('outputHeight')) + # width = int(self.settings.value('outputWidth')) self.textColor = (255, 255, 255) self.title = 'Text' self.alignment = 1 - self.fontSize = height / 13.5 - fm = QtGui.QFontMetrics(self.titleFont) - self.xPosition = width / 2 - fm.width(self.title)/2 - self.yPosition = height / 2 * 1.036 + self.fontSize = self.height / 13.5 self.page.comboBox_textAlign.addItem("Left") self.page.comboBox_textAlign.addItem("Middle") @@ -35,8 +32,11 @@ class Component(Component): self.page.lineEdit_title.setText(self.title) self.page.comboBox_textAlign.setCurrentIndex(int(self.alignment)) self.page.spinBox_fontSize.setValue(int(self.fontSize)) - self.page.spinBox_xTextAlign.setValue(int(self.xPosition)) - self.page.spinBox_yTextAlign.setValue(int(self.yPosition)) + + fm = QtGui.QFontMetrics(self.titleFont) + self.page.spinBox_xTextAlign.setValue( + self.width / 2 - fm.width(self.title)/2) + self.page.spinBox_yTextAlign.setValue(self.height / 2 * 1.036) self.page.fontComboBox_titleFont.currentFontChanged.connect( self.update @@ -50,6 +50,9 @@ class Component(Component): 'yPosition': self.page.spinBox_yTextAlign, }, colorWidgets={ 'textColor': self.page.pushButton_textColor, + }, relativeWidgets={ + 'xPosition': 'x', + 'yPosition': 'y', }) def update(self): diff --git a/src/components/video.py b/src/components/video.py index 6cd16e5..3569d17 100644 --- a/src/components/video.py +++ b/src/components/video.py @@ -38,6 +38,9 @@ class Component(Component): 'loopVideo': 'loop', 'xPosition': 'x', 'yPosition': 'y', + }, relativeWidgets={ + 'xPosition': 'x', + 'yPosition': 'y', } ) diff --git a/src/components/waveform.py b/src/components/waveform.py index 9c3cf86..a25116b 100644 --- a/src/components/waveform.py +++ b/src/components/waveform.py @@ -41,6 +41,9 @@ class Component(Component): 'mono': self.page.checkBox_mono, }, colorWidgets={ 'color': self.page.pushButton_color, + }, relativeWidgets={ + 'x': 'x', + 'y': 'y', } ) diff --git a/src/mainwindow.py b/src/mainwindow.py index d9e95e2..1c8806d 100644 --- a/src/mainwindow.py +++ b/src/mainwindow.py @@ -644,9 +644,12 @@ class MainWindow(QtWidgets.QMainWindow): def updateResolution(self): resIndex = int(self.window.comboBox_resolution.currentIndex()) res = Core.resolutions[resIndex].split('x') + changed = res[0] != self.settings.value("outputWidth") self.settings.setValue('outputWidth', res[0]) self.settings.setValue('outputHeight', res[1]) - self.drawPreview() + if changed: + for i in range(len(self.core.selectedComponents)): + self.core.updateComponent(i) def drawPreview(self, force=False, **kwargs): '''Use autosave keyword arg to force saving or not saving if needed''' -- cgit v1.2.3 From 6611492b30a7daf7bdbe77f6e12f9d322bdd90c1 Mon Sep 17 00:00:00 2001 From: tassaron Date: Thu, 3 Aug 2017 00:44:46 -0400 Subject: relative gradients & last good frame used for preview errors --- src/components/color.py | 5 +++++ src/components/spectrum.py | 15 ++++++++++----- src/components/text.py | 2 -- src/components/video.py | 15 ++++----------- src/components/waveform.py | 15 ++++++++++----- 5 files changed, 29 insertions(+), 23 deletions(-) (limited to 'src/components/video.py') diff --git a/src/components/color.py b/src/components/color.py index 2b100d9..f5d618e 100644 --- a/src/components/color.py +++ b/src/components/color.py @@ -65,6 +65,11 @@ class Component(Component): 'y': 'y', 'sizeWidth': 'x', 'sizeHeight': 'y', + 'RG_start': 'x', + 'LG_start': 'x', + 'RG_end': 'x', + 'LG_end': 'x', + 'RG_centre': 'x', }, ) diff --git a/src/components/spectrum.py b/src/components/spectrum.py index 2cc641d..9a0c59a 100644 --- a/src/components/spectrum.py +++ b/src/components/spectrum.py @@ -20,6 +20,7 @@ class Component(Component): def widget(self, *args): self.previewFrame = None super().widget(*args) + self._image = BlankFrame(self.width, self.height) self.chunkSize = 4 * self.width * self.height self.changedOptions = True @@ -268,11 +269,15 @@ class Component(Component): return changed def finalizeFrame(self, imageData): - image = Image.frombytes( - 'RGBA', - scale(self.scale, self.width, self.height, int), - imageData - ) + try: + image = Image.frombytes( + 'RGBA', + scale(self.scale, self.width, self.height, int), + imageData + ) + self._image = image + except ValueError: + image = self._image if self.scale != 100 \ or self.x != 0 or self.y != 0: frame = BlankFrame(self.width, self.height) diff --git a/src/components/text.py b/src/components/text.py index be4de4a..2a5d433 100644 --- a/src/components/text.py +++ b/src/components/text.py @@ -17,8 +17,6 @@ class Component(Component): def widget(self, *args): super().widget(*args) - # height = int(self.settings.value('outputHeight')) - # width = int(self.settings.value('outputWidth')) self.textColor = (255, 255, 255) self.title = 'Text' self.alignment = 1 diff --git a/src/components/video.py b/src/components/video.py index 3569d17..2cd67c6 100644 --- a/src/components/video.py +++ b/src/components/video.py @@ -16,12 +16,12 @@ class Component(Component): def widget(self, *args): self.videoPath = '' - self.badVideo = False self.badAudio = False self.x = 0 self.y = 0 self.loopVideo = False super().widget(*args) + self._image = BlankFrame(self.width, self.height) self.page.pushButton_video.clicked.connect(self.pickVideo) self.trackWidgets( { @@ -70,8 +70,6 @@ class Component(Component): if not self.videoPath: self.lockError("There is no video selected.") - elif self.badVideo: - self.lockError("Could not identify an audio stream in this video.") elif not os.path.exists(self.videoPath): self.lockError("The video selected does not exist!") elif os.path.realpath(self.videoPath) == os.path.realpath(outputFile): @@ -199,14 +197,10 @@ class Component(Component): 'RGBA', scale(self.scale, self.width, self.height, int), imageData) - + self._image = image except ValueError: - print( - '### BAD VIDEO SELECTED ###\n' - 'Video will not export with these settings' - ) - self.badVideo = True - return BlankFrame(self.width, self.height) + # use last good frame + image = self._image if self.scale != 100 \ or self.xPosition != 0 or self.yPosition != 0: @@ -214,5 +208,4 @@ class Component(Component): frame.paste(image, box=(self.xPosition, self.yPosition)) else: frame = image - self.badVideo = False return frame diff --git a/src/components/waveform.py b/src/components/waveform.py index a25116b..526e6fb 100644 --- a/src/components/waveform.py +++ b/src/components/waveform.py @@ -19,6 +19,7 @@ class Component(Component): def widget(self, *args): super().widget(*args) + self._image = BlankFrame(self.width, self.height) self.page.lineEdit_color.setText('255,255,255') @@ -178,11 +179,15 @@ class Component(Component): self.chunkSize = 4 * width * height def finalizeFrame(self, imageData): - image = Image.frombytes( - 'RGBA', - scale(self.scale, self.width, self.height, int), - imageData - ) + try: + image = Image.frombytes( + 'RGBA', + scale(self.scale, self.width, self.height, int), + imageData + ) + self._image = image + except ValueError: + image = self._image if self.scale != 100 \ or self.x != 0 or self.y != 0: frame = BlankFrame(self.width, self.height) -- cgit v1.2.3 From 219e846984bb10e9674432fa7aeac4157635c743 Mon Sep 17 00:00:00 2001 From: tassaron Date: Thu, 3 Aug 2017 12:16:57 -0400 Subject: relativeWidgets might as well be a list --- src/component.py | 5 +--- src/components/color.py | 63 +++++++++++++++++++++------------------------- src/components/image.py | 36 ++++++++++++-------------- src/components/original.py | 6 ++--- src/components/spectrum.py | 47 ++++++++++++++++------------------ src/components/text.py | 8 +++--- src/components/video.py | 37 +++++++++++++-------------- src/components/waveform.py | 35 ++++++++++++-------------- 8 files changed, 106 insertions(+), 131 deletions(-) (limited to 'src/components/video.py') diff --git a/src/component.py b/src/component.py index 5dfe2ab..c5bc44b 100644 --- a/src/component.py +++ b/src/component.py @@ -304,10 +304,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): elif attr in self._relativeWidgets: # Relative widgets: number scales to fit export resolution - if self._relativeWidgets[attr] == 'x': - dimension = self.width - else: - dimension = self.height + dimension = self.width try: oldUserValue = getattr(self, attr) except AttributeError: diff --git a/src/components/color.py b/src/components/color.py index f5d618e..5d1233e 100644 --- a/src/components/color.py +++ b/src/components/color.py @@ -37,41 +37,34 @@ class Component(Component): self.page.comboBox_fill.addItem(label) self.page.comboBox_fill.setCurrentIndex(0) - self.trackWidgets( - { - 'x': self.page.spinBox_x, - 'y': self.page.spinBox_y, - 'sizeWidth': self.page.spinBox_width, - 'sizeHeight': self.page.spinBox_height, - 'trans': self.page.checkBox_trans, - 'spread': self.page.comboBox_spread, - 'stretch': self.page.checkBox_stretch, - 'RG_start': self.page.spinBox_radialGradient_start, - 'LG_start': self.page.spinBox_linearGradient_start, - 'RG_end': self.page.spinBox_radialGradient_end, - 'LG_end': self.page.spinBox_linearGradient_end, - 'RG_centre': self.page.spinBox_radialGradient_spread, - 'fillType': self.page.comboBox_fill, - 'color1': self.page.lineEdit_color1, - 'color2': self.page.lineEdit_color2, - }, presetNames={ - 'sizeWidth': 'width', - 'sizeHeight': 'height', - }, colorWidgets={ - 'color1': self.page.pushButton_color1, - 'color2': self.page.pushButton_color2, - }, relativeWidgets={ - 'x': 'x', - 'y': 'y', - 'sizeWidth': 'x', - 'sizeHeight': 'y', - 'RG_start': 'x', - 'LG_start': 'x', - 'RG_end': 'x', - 'LG_end': 'x', - 'RG_centre': 'x', - }, - ) + self.trackWidgets({ + 'x': self.page.spinBox_x, + 'y': self.page.spinBox_y, + 'sizeWidth': self.page.spinBox_width, + 'sizeHeight': self.page.spinBox_height, + 'trans': self.page.checkBox_trans, + 'spread': self.page.comboBox_spread, + 'stretch': self.page.checkBox_stretch, + 'RG_start': self.page.spinBox_radialGradient_start, + 'LG_start': self.page.spinBox_linearGradient_start, + 'RG_end': self.page.spinBox_radialGradient_end, + 'LG_end': self.page.spinBox_linearGradient_end, + 'RG_centre': self.page.spinBox_radialGradient_spread, + 'fillType': self.page.comboBox_fill, + 'color1': self.page.lineEdit_color1, + 'color2': self.page.lineEdit_color2, + }, presetNames={ + 'sizeWidth': 'width', + 'sizeHeight': 'height', + }, colorWidgets={ + 'color1': self.page.pushButton_color1, + 'color2': self.page.pushButton_color2, + }, relativeWidgets=[ + 'x', 'y', + 'sizeWidth', 'sizeHeight', + 'LG_start', 'LG_end', + 'RG_start', 'RG_end', 'RG_centre', + ]) def update(self): fillType = self.page.comboBox_fill.currentIndex() diff --git a/src/components/image.py b/src/components/image.py index 2ffa5a1..19c4796 100644 --- a/src/components/image.py +++ b/src/components/image.py @@ -13,26 +13,22 @@ class Component(Component): def widget(self, *args): super().widget(*args) self.page.pushButton_image.clicked.connect(self.pickImage) - self.trackWidgets( - { - 'imagePath': self.page.lineEdit_image, - 'scale': self.page.spinBox_scale, - 'rotate': self.page.spinBox_rotate, - 'color': self.page.spinBox_color, - 'xPosition': self.page.spinBox_x, - 'yPosition': self.page.spinBox_y, - 'stretched': self.page.checkBox_stretch, - 'mirror': self.page.checkBox_mirror, - }, - presetNames={ - 'imagePath': 'image', - 'xPosition': 'x', - 'yPosition': 'y', - }, relativeWidgets={ - 'xPosition': 'x', - 'yPosition': 'y', - }, - ) + self.trackWidgets({ + 'imagePath': self.page.lineEdit_image, + 'scale': self.page.spinBox_scale, + 'rotate': self.page.spinBox_rotate, + 'color': self.page.spinBox_color, + 'xPosition': self.page.spinBox_x, + 'yPosition': self.page.spinBox_y, + 'stretched': self.page.checkBox_stretch, + }, presetNames={ + 'mirror': self.page.checkBox_mirror, + 'imagePath': 'image', + 'xPosition': 'x', + 'yPosition': 'y', + }, relativeWidgets=[ + 'xPosition', 'yPosition', + ]) def previewRender(self): return self.drawFrame(self.width, self.height) diff --git a/src/components/original.py b/src/components/original.py index 67e3239..f886374 100644 --- a/src/components/original.py +++ b/src/components/original.py @@ -40,9 +40,9 @@ class Component(Component): 'y': self.page.spinBox_y, }, colorWidgets={ 'visColor': self.page.pushButton_visColor, - }, relativeWidgets={ - 'y': 'y', - }) + }, relativeWidgets=[ + 'y', + ]) def previewRender(self): spectrum = numpy.fromfunction( diff --git a/src/components/spectrum.py b/src/components/spectrum.py index 9a0c59a..666e20a 100644 --- a/src/components/spectrum.py +++ b/src/components/spectrum.py @@ -30,31 +30,28 @@ class Component(Component): self.update ) - self.trackWidgets( - { - 'filterType': self.page.comboBox_filterType, - 'window': self.page.comboBox_window, - 'mode': self.page.comboBox_mode, - 'amplitude': self.page.comboBox_amplitude0, - 'amplitude1': self.page.comboBox_amplitude1, - 'amplitude2': self.page.comboBox_amplitude2, - 'display': self.page.comboBox_display, - 'zoom': self.page.spinBox_zoom, - 'tc': self.page.spinBox_tc, - 'x': self.page.spinBox_x, - 'y': self.page.spinBox_y, - 'mirror': self.page.checkBox_mirror, - 'draw': self.page.checkBox_draw, - 'scale': self.page.spinBox_scale, - 'color': self.page.comboBox_color, - 'compress': self.page.checkBox_compress, - 'mono': self.page.checkBox_mono, - 'hue': self.page.spinBox_hue, - }, relativeWidgets={ - 'x': 'x', - 'y': 'y', - } - ) + self.trackWidgets({ + 'filterType': self.page.comboBox_filterType, + 'window': self.page.comboBox_window, + 'mode': self.page.comboBox_mode, + 'amplitude': self.page.comboBox_amplitude0, + 'amplitude1': self.page.comboBox_amplitude1, + 'amplitude2': self.page.comboBox_amplitude2, + 'display': self.page.comboBox_display, + 'zoom': self.page.spinBox_zoom, + 'tc': self.page.spinBox_tc, + 'x': self.page.spinBox_x, + 'y': self.page.spinBox_y, + 'mirror': self.page.checkBox_mirror, + 'draw': self.page.checkBox_draw, + 'scale': self.page.spinBox_scale, + 'color': self.page.comboBox_color, + 'compress': self.page.checkBox_compress, + 'mono': self.page.checkBox_mono, + 'hue': self.page.spinBox_hue, + }, relativeWidgets=[ + 'x', 'y', + ]) for widget in self._trackedWidgets.values(): connectWidget(widget, lambda: self.changed()) diff --git a/src/components/text.py b/src/components/text.py index 2a5d433..b7c244e 100644 --- a/src/components/text.py +++ b/src/components/text.py @@ -48,11 +48,9 @@ class Component(Component): 'yPosition': self.page.spinBox_yTextAlign, }, colorWidgets={ 'textColor': self.page.pushButton_textColor, - }, relativeWidgets={ - 'xPosition': 'x', - 'yPosition': 'y', - 'fontSize': 'y', - }) + }, relativeWidgets=[ + 'xPosition', 'yPosition', 'fontSize', + ]) def update(self): self.titleFont = self.page.fontComboBox_titleFont.currentFont() diff --git a/src/components/video.py b/src/components/video.py index 2cd67c6..b6bdd52 100644 --- a/src/components/video.py +++ b/src/components/video.py @@ -23,26 +23,23 @@ class Component(Component): super().widget(*args) self._image = BlankFrame(self.width, self.height) self.page.pushButton_video.clicked.connect(self.pickVideo) - self.trackWidgets( - { - 'videoPath': self.page.lineEdit_video, - 'loopVideo': self.page.checkBox_loop, - 'useAudio': self.page.checkBox_useAudio, - 'distort': self.page.checkBox_distort, - 'scale': self.page.spinBox_scale, - 'volume': self.page.spinBox_volume, - 'xPosition': self.page.spinBox_x, - 'yPosition': self.page.spinBox_y, - }, presetNames={ - 'videoPath': 'video', - 'loopVideo': 'loop', - 'xPosition': 'x', - 'yPosition': 'y', - }, relativeWidgets={ - 'xPosition': 'x', - 'yPosition': 'y', - } - ) + self.trackWidgets({ + 'videoPath': self.page.lineEdit_video, + 'loopVideo': self.page.checkBox_loop, + 'useAudio': self.page.checkBox_useAudio, + 'distort': self.page.checkBox_distort, + 'scale': self.page.spinBox_scale, + 'volume': self.page.spinBox_volume, + 'xPosition': self.page.spinBox_x, + 'yPosition': self.page.spinBox_y, + }, presetNames={ + 'videoPath': 'video', + 'loopVideo': 'loop', + 'xPosition': 'x', + 'yPosition': 'y', + }, relativeWidgets=[ + 'xPosition', 'yPosition', + ]) def update(self): if self.page.checkBox_useAudio.isChecked(): diff --git a/src/components/waveform.py b/src/components/waveform.py index 526e6fb..71cbcac 100644 --- a/src/components/waveform.py +++ b/src/components/waveform.py @@ -28,25 +28,22 @@ class Component(Component): self.update ) - self.trackWidgets( - { - 'color': self.page.lineEdit_color, - 'mode': self.page.comboBox_mode, - 'amplitude': self.page.comboBox_amplitude, - 'x': self.page.spinBox_x, - 'y': self.page.spinBox_y, - 'mirror': self.page.checkBox_mirror, - 'scale': self.page.spinBox_scale, - 'opacity': self.page.spinBox_opacity, - 'compress': self.page.checkBox_compress, - 'mono': self.page.checkBox_mono, - }, colorWidgets={ - 'color': self.page.pushButton_color, - }, relativeWidgets={ - 'x': 'x', - 'y': 'y', - } - ) + self.trackWidgets({ + 'color': self.page.lineEdit_color, + 'mode': self.page.comboBox_mode, + 'amplitude': self.page.comboBox_amplitude, + 'x': self.page.spinBox_x, + 'y': self.page.spinBox_y, + 'mirror': self.page.checkBox_mirror, + 'scale': self.page.spinBox_scale, + 'opacity': self.page.spinBox_opacity, + 'compress': self.page.checkBox_compress, + 'mono': self.page.checkBox_mono, + }, colorWidgets={ + 'color': self.page.pushButton_color, + }, relativeWidgets=[ + 'x', 'y', + ]) def previewRender(self): self.updateChunksize() -- cgit v1.2.3