diff options
| author | Brianna | 2017-06-15 23:21:34 -0400 |
|---|---|---|
| committer | GitHub | 2017-06-15 23:21:34 -0400 |
| commit | fc7ee6d8e5b5a96ec06918f9b5dd9310cdf814de (patch) | |
| tree | 99fd013c1c87f6bb1d2ed22859b56b0bced081bc /components | |
| parent | 02795503d09743b5225eed7e7b7112208dfc28d0 (diff) | |
| parent | ee8031925fcd93d7bedceff6e98a06f3806426b3 (diff) | |
Redesigned preset UI + video & image component scaling/positioning
Added preset manager
Diffstat (limited to 'components')
| -rw-r--r-- | components/__base__.py | 47 | ||||
| -rw-r--r-- | components/color.py | 27 | ||||
| -rw-r--r-- | components/image.py | 40 | ||||
| -rw-r--r-- | components/image.ui | 68 | ||||
| -rw-r--r-- | components/original.py | 16 | ||||
| -rw-r--r-- | components/text.py | 17 | ||||
| -rw-r--r-- | components/video.py | 119 | ||||
| -rw-r--r-- | components/video.ui | 50 |
8 files changed, 323 insertions, 61 deletions
diff --git a/components/__base__.py b/components/__base__.py index 4fdf31f..88f22d4 100644 --- a/components/__base__.py +++ b/components/__base__.py @@ -1,7 +1,18 @@ -from PyQt4 import QtGui +from PyQt4 import QtGui, QtCore +from PIL import Image -class Component: +class Component(QtCore.QObject): + '''A base class for components to inherit from''' + + # modified = QtCore.pyqtSignal(int, bool) + + def __init__(self, moduleIndex, compPos): + super().__init__() + self.currentPreset = None + self.moduleIndex = moduleIndex + self.compPos = compPos + def __str__(self): return self.__doc__ @@ -10,18 +21,38 @@ class Component: return 1 def cancel(self): - # make sure your component responds to these variables in frameRender() + # please 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() + + def loadPreset(self, presetDict, presetName): + '''Children should take (presetDict, presetName=None) as args''' + + # Use super().loadPreset(presetDict, presetName) + # Then update your widgets using the preset dict + self.currentPreset = presetName \ + if presetName != None else presetDict['preset'] + ''' + def savePreset(self): + return {} + ''' def preFrameRender(self, **kwargs): for var, value in kwargs.items(): exec('self.%s = value' % var) + def blankFrame(self, width, height): + return Image.new("RGBA", (width, height), (0, 0, 0, 0)) + def pickColor(self): - color = QtGui.QColorDialog.getColor() + dialog = QtGui.QColorDialog() + dialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True) + color = dialog.getColor() if color.isValid(): RGBstring = '%s,%s,%s' % ( str(color.red()), str(color.green()), str(color.blue())) @@ -57,7 +88,7 @@ class Component: return page def update(self): - # read widget values + super().update() self.parent.drawPreview() def previewRender(self, previewWorker): @@ -72,12 +103,6 @@ class Component: image = Image.new("RGBA", (width, height), (0,0,0,0)) return image - def loadPreset(self, presetDict): - # update widgets using a preset dict - - def savePreset(self): - return {} - def cancel(self): self.canceled = True diff --git a/components/color.py b/components/color.py index b050fbd..36f3906 100644 --- a/components/color.py +++ b/components/color.py @@ -7,6 +7,9 @@ from . import __base__ class Component(__base__.Component): '''Color''' + + modified = QtCore.pyqtSignal(int, dict) + def widget(self, parent): self.parent = parent page = uic.loadUi(os.path.join( @@ -20,14 +23,14 @@ class Component(__base__.Component): page.lineEdit_color1.setText('%s,%s,%s' % self.color1) page.lineEdit_color2.setText('%s,%s,%s' % self.color2) - btnStyle = "QPushButton { background-color : %s; outline: none; }" \ + btnStyle1 = "QPushButton { background-color : %s; outline: none; }" \ % QColor(*self.color1).name() - btnStyle = "QPushButton { background-color : %s; outline: none; }" \ + btnStyle2 = "QPushButton { background-color : %s; outline: none; }" \ % QColor(*self.color2).name() - page.pushButton_color1.setStyleSheet(btnStyle) - page.pushButton_color2.setStyleSheet(btnStyle) + page.pushButton_color1.setStyleSheet(btnStyle1) + page.pushButton_color2.setStyleSheet(btnStyle2) page.pushButton_color1.clicked.connect(lambda: self.pickColor(1)) page.pushButton_color2.clicked.connect(lambda: self.pickColor(2)) @@ -50,6 +53,7 @@ class Component(__base__.Component): self.x = self.page.spinBox_x.value() self.y = self.page.spinBox_y.value() self.parent.drawPreview() + super().update() def previewRender(self, previewWorker): width = int(previewWorker.core.settings.value('outputWidth')) @@ -67,23 +71,26 @@ class Component(__base__.Component): def drawFrame(self, width, height): r, g, b = self.color1 - return Image.new("RGBA", (width, height), (r, g, b, 255)) + return self.blankFrame(width, height) + + def loadPreset(self, pr, presetName=None): + super().loadPreset(pr, presetName) - def loadPreset(self, pr): self.page.lineEdit_color1.setText('%s,%s,%s' % pr['color1']) self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2']) - btnStyle = "QPushButton { background-color : %s; outline: none; }" \ + btnStyle1 = "QPushButton { background-color : %s; outline: none; }" \ % QColor(*pr['color1']).name() - btnStyle = "QPushButton { background-color : %s; outline: none; }" \ + btnStyle2 = "QPushButton { background-color : %s; outline: none; }" \ % QColor(*pr['color2']).name() - self.page.pushButton_color1.setStyleSheet(btnStyle) - self.page.pushButton_color2.setStyleSheet(btnStyle) + self.page.pushButton_color1.setStyleSheet(btnStyle1) + self.page.pushButton_color2.setStyleSheet(btnStyle2) def savePreset(self): return { + 'preset': self.currentPreset, 'color1': self.color1, 'color2': self.color2, } diff --git a/components/image.py b/components/image.py index f9a92ca..b6aa29b 100644 --- a/components/image.py +++ b/components/image.py @@ -6,6 +6,9 @@ from . import __base__ class Component(__base__.Component): '''Image''' + + modified = QtCore.pyqtSignal(int, dict) + def widget(self, parent): self.parent = parent self.settings = parent.settings @@ -17,15 +20,25 @@ class Component(__base__.Component): page.lineEdit_image.textChanged.connect(self.update) page.pushButton_image.clicked.connect(self.pickImage) + page.spinBox_scale.valueChanged.connect(self.update) + page.checkBox_stretch.stateChanged.connect(self.update) + page.spinBox_x.valueChanged.connect(self.update) + page.spinBox_y.valueChanged.connect(self.update) self.page = page return page def update(self): self.imagePath = self.page.lineEdit_image.text() + self.scale = self.page.spinBox_scale.value() + self.xPosition = self.page.spinBox_x.value() + self.yPosition = self.page.spinBox_y.value() + self.stretched = self.page.checkBox_stretch.isChecked() self.parent.drawPreview() + super().update() def previewRender(self, previewWorker): + self.imageFormats = previewWorker.core.imageFormats width = int(previewWorker.core.settings.value('outputWidth')) height = int(previewWorker.core.settings.value('outputHeight')) return self.drawFrame(width, height) @@ -40,27 +53,42 @@ class Component(__base__.Component): return self.drawFrame(width, height) def drawFrame(self, width, height): - frame = Image.new("RGBA", (width, height), (0, 0, 0, 0)) + frame = self.blankFrame(width, height) if self.imagePath and os.path.exists(self.imagePath): image = Image.open(self.imagePath) - if image.size != (width, height): + if self.stretched and image.size != (width, height): image = image.resize((width, height), Image.ANTIALIAS) - frame.paste(image) + if self.scale != 100: + newHeight = int((image.height / 100) * self.scale) + newWidth = int((image.width / 100) * self.scale) + image = image.resize((newWidth, newHeight), Image.ANTIALIAS) + frame.paste(image, box=(self.xPosition, self.yPosition)) return frame - def loadPreset(self, pr): + def loadPreset(self, pr, presetName=None): + super().loadPreset(pr, presetName) self.page.lineEdit_image.setText(pr['image']) + self.page.spinBox_scale.setValue(pr['scale']) + self.page.spinBox_x.setValue(pr['x']) + self.page.spinBox_y.setValue(pr['y']) + self.page.checkBox_stretch.setChecked(pr['stretched']) def savePreset(self): return { + 'preset': self.currentPreset, 'image': self.imagePath, + 'scale': self.scale, + 'stretched': self.stretched, + 'x': self.xPosition, + 'y': self.yPosition, } def pickImage(self): imgDir = self.settings.value("backgroundDir", os.path.expanduser("~")) filename = QtGui.QFileDialog.getOpenFileName( - self.page, "Choose Image", imgDir, "Image Files (*.jpg *.png)") - if filename: + self.page, "Choose Image", imgDir, + "Image Files (%s)" % " ".join(self.imageFormats)) + if filename: self.settings.setValue("backgroundDir", os.path.dirname(filename)) self.page.lineEdit_image.setText(filename) self.update() diff --git a/components/image.ui b/components/image.ui index 3cd5b1b..6df03a5 100644 --- a/components/image.ui +++ b/components/image.ui @@ -124,8 +124,11 @@ <height>16777215</height> </size> </property> + <property name="minimum"> + <number>-10000</number> + </property> <property name="maximum"> - <number>999999999</number> + <number>10000</number> </property> </widget> </item> @@ -163,10 +166,10 @@ </size> </property> <property name="minimum"> - <number>0</number> + <number>-1000</number> </property> <property name="maximum"> - <number>999999999</number> + <number>1000</number> </property> <property name="value"> <number>0</number> @@ -178,6 +181,65 @@ </layout> </item> <item> + <layout class="QHBoxLayout" name="horizontalLayout_9"> + <item> + <widget class="QCheckBox" name="checkBox_stretch"> + <property name="text"> + <string>Stretch</string> + </property> + <property name="checked"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_10"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>5</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Scale</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="spinBox_scale"> + <property name="buttonSymbols"> + <enum>QAbstractSpinBox::UpDownArrows</enum> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>10</number> + </property> + <property name="maximum"> + <number>400</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> + </item> + <item> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> diff --git a/components/original.py b/components/original.py index 4d0e83b..5e2f9d4 100644 --- a/components/original.py +++ b/components/original.py @@ -1,9 +1,8 @@ import numpy from PIL import Image, ImageDraw -from PyQt4 import uic, QtGui +from PyQt4 import uic, QtGui, QtCore from PyQt4.QtGui import QColor import os -import random from . import __base__ import time from copy import copy @@ -11,6 +10,9 @@ from copy import copy class Component(__base__.Component): '''Original Audio Visualization''' + + modified = QtCore.pyqtSignal(int, dict) + def widget(self, parent): self.parent = parent self.visColor = (255, 255, 255) @@ -36,8 +38,11 @@ class Component(__base__.Component): self.layout = self.page.comboBox_visLayout.currentIndex() self.visColor = self.RGBFromString(self.page.lineEdit_visColor.text()) self.parent.drawPreview() + super().update() + + def loadPreset(self, pr, presetName=None): + super().loadPreset(pr, presetName) - def loadPreset(self, pr): self.page.lineEdit_visColor.setText('%s,%s,%s' % pr['visColor']) btnStyle = "QPushButton { background-color : %s; outline: none; }" \ % QColor(*pr['visColor']).name() @@ -46,6 +51,7 @@ class Component(__base__.Component): def savePreset(self): return { + 'preset': self.currentPreset, 'layout': self.layout, 'visColor': self.visColor, } @@ -139,7 +145,7 @@ class Component(__base__.Component): bF = width / 64 bH = bF / 2 bQ = bF / 4 - imTop = Image.new("RGBA", (width, height), (0, 0, 0, 0)) + imTop = self.blankFrame(width, height) draw = ImageDraw.Draw(imTop) r, g, b = color color2 = (r, g, b, 125) @@ -157,7 +163,7 @@ class Component(__base__.Component): imBottom = imTop.transpose(Image.FLIP_TOP_BOTTOM) - im = Image.new("RGBA", (width, height), (0, 0, 0, 0)) + im = self.blankFrame(width, height) if layout == 0: y = 0 - int(height/100*43) diff --git a/components/text.py b/components/text.py index 6cdc0dd..f8ef7b3 100644 --- a/components/text.py +++ b/components/text.py @@ -9,8 +9,11 @@ from . import __base__ class Component(__base__.Component): '''Title Text''' - def __init__(self): - super().__init__() + + modified = QtCore.pyqtSignal(int, dict) + + def __init__(self, *args): + super().__init__(*args) self.titleFont = QFont() def widget(self, parent): @@ -31,7 +34,7 @@ class Component(__base__.Component): page.comboBox_textAlign.addItem("Right") page.lineEdit_textColor.setText('%s,%s,%s' % self.textColor) - page.pushButton_textColor.clicked.connect(lambda: self.pickColor()) + page.pushButton_textColor.clicked.connect(self.pickColor) btnStyle = "QPushButton { background-color : %s; outline: none; }" \ % QColor(*self.textColor).name() page.pushButton_textColor.setStyleSheet(btnStyle) @@ -62,6 +65,7 @@ class Component(__base__.Component): self.textColor = self.RGBFromString( self.page.lineEdit_textColor.text()) self.parent.drawPreview() + super().update() def getXY(self): '''Returns true x, y after considering alignment settings''' @@ -78,7 +82,9 @@ class Component(__base__.Component): x = self.xPosition - offset return x, self.yPosition - def loadPreset(self, pr): + def loadPreset(self, pr, presetName=None): + super().loadPreset(pr, presetName) + self.page.lineEdit_title.setText(pr['title']) font = QFont() font.fromString(pr['titleFont']) @@ -94,6 +100,7 @@ class Component(__base__.Component): def savePreset(self): return { + 'preset': self.currentPreset, 'title': self.title, 'titleFont': self.titleFont.toString(), 'alignment': self.alignment, @@ -119,7 +126,7 @@ class Component(__base__.Component): def addText(self, width, height): x, y = self.getXY() - im = Image.new("RGBA", (width, height), (0, 0, 0, 0)) + im = self.blankFrame(width, height) image = ImageQt(im) painter = QPainter(image) diff --git a/components/video.py b/components/video.py index b28b81e..3d43a18 100644 --- a/components/video.py +++ b/components/video.py @@ -5,12 +5,22 @@ import subprocess import threading from queue import PriorityQueue from . import __base__ - + + class Video: '''Video Component Frame-Fetcher''' def __init__(self, **kwargs): - mandatoryArgs = ['ffmpeg', 'videoPath', 'width', 'height', - 'frameRate', 'chunkSize', 'parent'] + mandatoryArgs = [ + 'ffmpeg', # path to ffmpeg, usually 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: try: exec('self.%s = kwargs[arg]' % arg) @@ -31,7 +41,8 @@ class Video: '-i', self.videoPath, '-f', 'image2pipe', '-pix_fmt', 'rgba', - '-filter:v', 'scale='+str(self.width)+':'+str(self.height), + '-filter:v', 'scale=%s:%s' % + scale(self.scale, self.width, self.height, str), '-vcodec', 'rawvideo', '-', ] @@ -50,7 +61,9 @@ class Video: while True: if num in self.finishedFrames: image = self.finishedFrames.pop(num) - return Image.frombytes('RGBA', (self.width, self.height), image) + return finalizeFrame( + self.component, image, self.width, self.height) + i, image = self.frameBuffer.get() self.finishedFrames[i] = image self.frameBuffer.task_done() @@ -78,6 +91,9 @@ class Video: class Component(__base__.Component): '''Video''' + + modified = QtCore.pyqtSignal(int, dict) + def widget(self, parent): self.parent = parent self.settings = parent.settings @@ -93,6 +109,10 @@ class Component(__base__.Component): page.lineEdit_video.textChanged.connect(self.update) page.pushButton_video.clicked.connect(self.pickVideo) page.checkBox_loop.stateChanged.connect(self.update) + page.checkBox_distort.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) self.page = page return page @@ -100,15 +120,21 @@ class Component(__base__.Component): def update(self): self.videoPath = self.page.lineEdit_video.text() self.loopVideo = self.page.checkBox_loop.isChecked() + self.distort = self.page.checkBox_distort.isChecked() + 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): + self.videoFormats = previewWorker.core.videoFormats width = int(previewWorker.core.settings.value('outputWidth')) height = int(previewWorker.core.settings.value('outputHeight')) - self.chunkSize = 4*width*height + self.updateChunksize(width, height) frame = self.getPreviewFrame(width, height) if not frame: - return Image.new("RGBA", (width, height), (0, 0, 0, 0)) + return self.blankFrame(width, height) else: return frame @@ -116,32 +142,49 @@ class Component(__base__.Component): super().preFrameRender(**kwargs) width = int(self.worker.core.settings.value('outputWidth')) height = int(self.worker.core.settings.value('outputHeight')) - self.chunkSize = 4*width*height + self.blankFrame_ = self.blankFrame(width, height) + self.updateChunksize(width, height) self.video = Video( ffmpeg=self.parent.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 - ) + parent=self.parent, loopVideo=self.loopVideo, + component=self, scale=self.scale + ) if os.path.exists(self.videoPath) else None def frameRender(self, moduleNo, arrayNo, frameNo): - return self.video.frame(frameNo) + if self.video: + return self.video.frame(frameNo) + else: + return self.blankFrame_ - def loadPreset(self, pr): + def loadPreset(self, pr, presetName=None): + super().loadPreset(pr, presetName) self.page.lineEdit_video.setText(pr['video']) + self.page.checkBox_loop.setChecked(pr['loop']) + self.page.checkBox_distort.setChecked(pr['distort']) + self.page.spinBox_scale.setValue(pr['scale']) + self.page.spinBox_x.setValue(pr['x']) + self.page.spinBox_y.setValue(pr['y']) def savePreset(self): return { + 'preset': self.currentPreset, 'video': self.videoPath, + 'loop': self.loopVideo, + 'distort': self.distort, + 'scale': self.scale, + 'x': self.xPosition, + 'y': self.yPosition, } def pickVideo(self): imgDir = self.settings.value("backgroundDir", os.path.expanduser("~")) filename = QtGui.QFileDialog.getOpenFileName( self.page, "Choose Video", - imgDir, "Video Files (*.mp4 *.mov)" + imgDir, "Video Files (%s)" % " ".join(self.videoFormats) ) - if filename: + if filename: self.settings.setValue("backgroundDir", os.path.dirname(filename)) self.page.lineEdit_video.setText(filename) self.update() @@ -149,13 +192,15 @@ class Component(__base__.Component): def getPreviewFrame(self, width, height): if not self.videoPath or not os.path.exists(self.videoPath): return + command = [ self.parent.core.FFMPEG_BIN, '-thread_queue_size', '512', '-i', self.videoPath, '-f', 'image2pipe', '-pix_fmt', 'rgba', - '-filter:v', 'scale='+str(width)+':'+str(height), + '-filter:v', 'scale=%s:%s' % + scale(self.scale, width, height, str), '-vcodec', 'rawvideo', '-', '-ss', '90', '-vframes', '1', @@ -165,7 +210,47 @@ class Component(__base__.Component): stderr=subprocess.DEVNULL, bufsize=10**8 ) byteFrame = pipe.stdout.read(self.chunkSize) - image = Image.frombytes('RGBA', (width, height), byteFrame) + frame = finalizeFrame(self, byteFrame, width, height) pipe.stdout.close() pipe.kill() - return image + + return frame + + def updateChunksize(self, width, height): + if self.scale != 100 and not self.distort: + width, height = scale(self.scale, width, 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(int(width)), str(int(height))) + elif returntype == int: + return (int(width), int(height)) + else: + return (width, height) + +def finalizeFrame(self, imageData, width, height): + if self.distort: + try: + image = Image.frombytes( + 'RGBA', + (width, height), + imageData) + except ValueError: + print('#### ignored invalid data caused by distortion ####') + image = self.blankFrame(width, height) + else: + image = Image.frombytes( + 'RGBA', + scale(self.scale, width, height, int), + imageData) + + if self.scale != 100 \ + or self.xPosition != 0 or self.yPosition != 0: + frame = self.blankFrame(width, height) + frame.paste(image, box=(self.xPosition, self.yPosition)) + else: + frame = image + return frame diff --git a/components/video.ui b/components/video.ui index 6a01368..f05e8a5 100644 --- a/components/video.ui +++ b/components/video.ui @@ -111,7 +111,7 @@ </widget> </item> <item> - <widget class="QSpinBox" name="spinBox_x_2"> + <widget class="QSpinBox" name="spinBox_x"> <property name="sizePolicy"> <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> <horstretch>0</horstretch> @@ -124,8 +124,11 @@ <height>16777215</height> </size> </property> + <property name="minimum"> + <number>-10000</number> + </property> <property name="maximum"> - <number>999999999</number> + <number>10000</number> </property> </widget> </item> @@ -163,10 +166,10 @@ </size> </property> <property name="minimum"> - <number>0</number> + <number>-10000</number> </property> <property name="maximum"> - <number>999999999</number> + <number>10000</number> </property> <property name="value"> <number>0</number> @@ -202,6 +205,42 @@ </property> </spacer> </item> + <item> + <widget class="QCheckBox" name="checkBox_distort"> + <property name="text"> + <string>Distort by scale</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Scale</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="spinBox_scale"> + <property name="buttonSymbols"> + <enum>QAbstractSpinBox::UpDownArrows</enum> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>10</number> + </property> + <property name="maximum"> + <number>400</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> </layout> </item> <item> @@ -217,6 +256,9 @@ </property> </spacer> </item> + <item> + <widget class="QWidget" name="widget" native="true"/> + </item> </layout> </widget> <resources/> |
