diff options
| author | Brianna | 2017-07-27 22:47:40 -0400 |
|---|---|---|
| committer | GitHub | 2017-07-27 22:47:40 -0400 |
| commit | ae2af28808816d67e964b63bee1b5dbc18073672 (patch) | |
| tree | 93818b30c43761288e0414e620e27871ac32cc8d /src/components | |
| parent | c799305eff66432d1d42ff3a8d1b7fd24448e1b6 (diff) | |
| parent | 6ecb6df23628de65c9efd8cac4810fdf74238c3d (diff) | |
Merge pull request #49 from djfun/toolkit
Code reorganization, more readable component code, better error messages
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/color.py | 164 | ||||
| -rw-r--r-- | src/components/image.py | 98 | ||||
| -rw-r--r-- | src/components/original.py | 87 | ||||
| -rw-r--r-- | src/components/sound.py | 70 | ||||
| -rw-r--r-- | src/components/text.py | 109 | ||||
| -rw-r--r-- | src/components/video.py | 212 |
6 files changed, 262 insertions, 478 deletions
diff --git a/src/components/color.py b/src/components/color.py index ef4dd95..2abd79a 100644 --- a/src/components/color.py +++ b/src/components/color.py @@ -5,26 +5,23 @@ from PIL.ImageQt import ImageQt import os from component import Component -from frame import BlankFrame, FloodFrame, FramePainter, PaintColor +from toolkit.frame import BlankFrame, FloodFrame, FramePainter, PaintColor +from toolkit import rgbFromString, pickColor class Component(Component): - '''Color''' - - modified = QtCore.pyqtSignal(int, dict) - - def widget(self, parent): - self.parent = parent - self.settings = self.parent.core.settings - page = self.loadUi('color.ui') + name = 'Color' + version = '1.0.0' + def widget(self, *args): self.color1 = (0, 0, 0) self.color2 = (133, 133, 133) self.x = 0 self.y = 0 + super().widget(*args) - page.lineEdit_color1.setText('%s,%s,%s' % self.color1) - page.lineEdit_color2.setText('%s,%s,%s' % self.color2) + self.page.lineEdit_color1.setText('%s,%s,%s' % self.color1) + self.page.lineEdit_color2.setText('%s,%s,%s' % self.color2) btnStyle1 = "QPushButton { background-color : %s; outline: none; }" \ % QColor(*self.color1).name() @@ -32,68 +29,55 @@ class Component(Component): btnStyle2 = "QPushButton { background-color : %s; outline: none; }" \ % QColor(*self.color2).name() - 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)) + self.page.pushButton_color1.setStyleSheet(btnStyle1) + self.page.pushButton_color2.setStyleSheet(btnStyle2) + self.page.pushButton_color1.clicked.connect(lambda: self.pickColor(1)) + self.page.pushButton_color2.clicked.connect(lambda: self.pickColor(2)) # disable color #2 until non-default 'fill' option gets changed - page.lineEdit_color2.setDisabled(True) - page.pushButton_color2.setDisabled(True) - page.spinBox_x.valueChanged.connect(self.update) - page.spinBox_y.valueChanged.connect(self.update) - page.spinBox_width.setValue( + self.page.lineEdit_color2.setDisabled(True) + self.page.pushButton_color2.setDisabled(True) + self.page.spinBox_width.setValue( int(self.settings.value("outputWidth"))) - page.spinBox_height.setValue( + self.page.spinBox_height.setValue( int(self.settings.value("outputHeight"))) - page.lineEdit_color1.textChanged.connect(self.update) - page.lineEdit_color2.textChanged.connect(self.update) - page.spinBox_x.valueChanged.connect(self.update) - page.spinBox_y.valueChanged.connect(self.update) - page.spinBox_width.valueChanged.connect(self.update) - page.spinBox_height.valueChanged.connect(self.update) - page.checkBox_trans.stateChanged.connect(self.update) - self.fillLabels = [ 'Solid', 'Linear Gradient', 'Radial Gradient', ] for label in self.fillLabels: - page.comboBox_fill.addItem(label) - page.comboBox_fill.setCurrentIndex(0) - page.comboBox_fill.currentIndexChanged.connect(self.update) - page.comboBox_spread.currentIndexChanged.connect(self.update) - page.spinBox_radialGradient_end.valueChanged.connect(self.update) - page.spinBox_radialGradient_start.valueChanged.connect(self.update) - page.spinBox_radialGradient_spread.valueChanged.connect(self.update) - page.spinBox_linearGradient_end.valueChanged.connect(self.update) - page.spinBox_linearGradient_start.valueChanged.connect(self.update) - page.checkBox_stretch.stateChanged.connect(self.update) - - self.page = page - return page + 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, + }, presetNames={ + 'sizeWidth': 'width', + 'sizeHeight': 'height', + } + ) def update(self): - self.color1 = self.RGBFromString(self.page.lineEdit_color1.text()) - self.color2 = self.RGBFromString(self.page.lineEdit_color2.text()) - self.x = self.page.spinBox_x.value() - self.y = self.page.spinBox_y.value() - self.sizeWidth = self.page.spinBox_width.value() - self.sizeHeight = self.page.spinBox_height.value() - self.trans = self.page.checkBox_trans.isChecked() - self.spread = self.page.comboBox_spread.currentIndex() - - self.RG_start = self.page.spinBox_radialGradient_start.value() - self.RG_end = self.page.spinBox_radialGradient_end.value() - self.RG_centre = self.page.spinBox_radialGradient_spread.value() - self.stretch = self.page.checkBox_stretch.isChecked() - self.LG_start = self.page.spinBox_linearGradient_start.value() - self.LG_end = self.page.spinBox_linearGradient_end.value() - - self.fillType = self.page.comboBox_fill.currentIndex() - if self.fillType == 0: + self.color1 = rgbFromString(self.page.lineEdit_color1.text()) + self.color2 = rgbFromString(self.page.lineEdit_color2.text()) + + fillType = self.page.comboBox_fill.currentIndex() + if fillType == 0: self.page.lineEdit_color2.setEnabled(False) self.page.pushButton_color2.setEnabled(False) self.page.checkBox_trans.setEnabled(False) @@ -105,25 +89,21 @@ class Component(Component): self.page.checkBox_trans.setEnabled(True) self.page.checkBox_stretch.setEnabled(True) self.page.comboBox_spread.setEnabled(True) - if self.trans: + if self.page.checkBox_trans.isChecked(): self.page.lineEdit_color2.setEnabled(False) self.page.pushButton_color2.setEnabled(False) - self.page.fillWidget.setCurrentIndex(self.fillType) + self.page.fillWidget.setCurrentIndex(fillType) super().update() - def previewRender(self, previewWorker): - width = int(self.settings.value('outputWidth')) - height = int(self.settings.value('outputHeight')) - return self.drawFrame(width, height) + def previewRender(self): + return self.drawFrame(self.width, self.height) def properties(self): return ['static'] - def frameRender(self, layerNo, frameNo): - width = int(self.settings.value('outputWidth')) - height = int(self.settings.value('outputHeight')) - return self.drawFrame(width, height) + def frameRender(self, frameNo): + return self.drawFrame(self.width, self.height) def drawFrame(self, width, height): r, g, b = self.color1 @@ -181,25 +161,11 @@ class Component(Component): return image.finalize() - def loadPreset(self, pr, presetName=None): - super().loadPreset(pr, presetName) + def loadPreset(self, pr, *args): + super().loadPreset(pr, *args) - self.page.comboBox_fill.setCurrentIndex(pr['fillType']) self.page.lineEdit_color1.setText('%s,%s,%s' % pr['color1']) self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2']) - self.page.spinBox_x.setValue(pr['x']) - self.page.spinBox_y.setValue(pr['y']) - self.page.spinBox_width.setValue(pr['width']) - self.page.spinBox_height.setValue(pr['height']) - self.page.checkBox_trans.setChecked(pr['trans']) - - self.page.spinBox_radialGradient_start.setValue(pr['RG_start']) - self.page.spinBox_radialGradient_end.setValue(pr['RG_end']) - self.page.spinBox_radialGradient_spread.setValue(pr['RG_centre']) - self.page.spinBox_linearGradient_start.setValue(pr['LG_start']) - self.page.spinBox_linearGradient_end.setValue(pr['LG_end']) - self.page.checkBox_stretch.setChecked(pr['stretch']) - self.page.comboBox_spread.setCurrentIndex(pr['spread']) btnStyle1 = "QPushButton { background-color : %s; outline: none; }" \ % QColor(*pr['color1']).name() @@ -209,27 +175,13 @@ class Component(Component): self.page.pushButton_color2.setStyleSheet(btnStyle2) def savePreset(self): - return { - 'preset': self.currentPreset, - 'color1': self.color1, - 'color2': self.color2, - 'x': self.x, - 'y': self.y, - 'fillType': self.fillType, - 'width': self.sizeWidth, - 'height': self.sizeHeight, - 'trans': self.trans, - 'stretch': self.stretch, - 'spread': self.spread, - 'RG_start': self.RG_start, - 'RG_end': self.RG_end, - 'RG_centre': self.RG_centre, - 'LG_start': self.LG_start, - 'LG_end': self.LG_end, - } + saveValueStore = super().savePreset() + saveValueStore['color1'] = self.color1 + saveValueStore['color2'] = self.color2 + return saveValueStore def pickColor(self, num): - RGBstring, btnStyle = super().pickColor() + RGBstring, btnStyle = pickColor() if not RGBstring: return if num == 1: @@ -243,7 +195,7 @@ class Component(Component): print('Specify a color:\n color=255,255,255') def command(self, arg): - if not arg.startswith('preset=') and '=' in arg: + if '=' in arg: key, arg = arg.split('=', 1) if key == 'color': self.page.lineEdit_color1.setText(arg) diff --git a/src/components/image.py b/src/components/image.py index c0d1c0d..a96f127 100644 --- a/src/components/image.py +++ b/src/components/image.py @@ -3,48 +3,36 @@ from PyQt5 import QtGui, QtCore, QtWidgets import os from component import Component -from frame import BlankFrame +from toolkit.frame import BlankFrame class Component(Component): - '''Image''' - - modified = QtCore.pyqtSignal(int, dict) - - def widget(self, parent): - self.parent = parent - self.settings = self.parent.core.settings - page = self.loadUi('image.ui') - - page.lineEdit_image.textChanged.connect(self.update) - page.pushButton_image.clicked.connect(self.pickImage) - page.spinBox_scale.valueChanged.connect(self.update) - page.spinBox_rotate.valueChanged.connect(self.update) - page.spinBox_color.valueChanged.connect(self.update) - page.checkBox_stretch.stateChanged.connect(self.update) - page.checkBox_mirror.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.rotate = self.page.spinBox_rotate.value() - self.color = self.page.spinBox_color.value() - self.xPosition = self.page.spinBox_x.value() - self.yPosition = self.page.spinBox_y.value() - self.stretched = self.page.checkBox_stretch.isChecked() - self.mirror = self.page.checkBox_mirror.isChecked() - - super().update() - - def previewRender(self, previewWorker): - width = int(self.settings.value('outputWidth')) - height = int(self.settings.value('outputHeight')) - return self.drawFrame(width, height) + name = 'Image' + version = '1.0.0' + + 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', + }, + ) + + def previewRender(self): + return self.drawFrame(self.width, self.height) def properties(self): props = ['static'] @@ -58,10 +46,8 @@ class Component(Component): if not os.path.exists(self.imagePath): return "The image selected does not exist!" - def frameRender(self, layerNo, frameNo): - width = int(self.settings.value('outputWidth')) - height = int(self.settings.value('outputHeight')) - return self.drawFrame(width, height) + def frameRender(self, frameNo): + return self.drawFrame(self.width, self.height) def drawFrame(self, width, height): frame = BlankFrame(width, height) @@ -89,30 +75,6 @@ class Component(Component): return frame - 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_color.setValue(pr['color']) - self.page.spinBox_rotate.setValue(pr['rotate']) - self.page.spinBox_x.setValue(pr['x']) - self.page.spinBox_y.setValue(pr['y']) - self.page.checkBox_stretch.setChecked(pr['stretched']) - self.page.checkBox_mirror.setChecked(pr['mirror']) - - def savePreset(self): - return { - 'preset': self.currentPreset, - 'image': self.imagePath, - 'scale': self.scale, - 'color': self.color, - 'rotate': self.rotate, - 'stretched': self.stretched, - 'mirror': self.mirror, - 'x': self.xPosition, - 'y': self.yPosition, - } - def pickImage(self): imgDir = self.settings.value("componentDir", os.path.expanduser("~")) filename, _ = QtWidgets.QFileDialog.getOpenFileName( @@ -124,7 +86,7 @@ class Component(Component): self.update() def command(self, arg): - if not arg.startswith('preset=') and '=' in arg: + if '=' in arg: key, arg = arg.split('=', 1) if key == 'path' and os.path.exists(arg): try: diff --git a/src/components/original.py b/src/components/original.py index f5776a4..3d1a574 100644 --- a/src/components/original.py +++ b/src/components/original.py @@ -7,80 +7,63 @@ import time from copy import copy from component import Component -from frame import BlankFrame +from toolkit.frame import BlankFrame +from toolkit import rgbFromString, pickColor class Component(Component): - '''Classic Visualizer''' + name = 'Classic Visualizer' + version = '1.0.0' - modified = QtCore.pyqtSignal(int, dict) - - @classmethod - def names(cls): + def names(*args): return ['Original Audio Visualization'] - def widget(self, parent): - self.parent = parent - self.settings = self.parent.core.settings + def widget(self, *args): self.visColor = (255, 255, 255) self.scale = 20 self.y = 0 - self.canceled = False - - page = self.loadUi('original.ui') - page.comboBox_visLayout.addItem("Classic") - page.comboBox_visLayout.addItem("Split") - page.comboBox_visLayout.addItem("Bottom") - page.comboBox_visLayout.addItem("Top") - page.comboBox_visLayout.setCurrentIndex(0) - page.comboBox_visLayout.currentIndexChanged.connect(self.update) - page.lineEdit_visColor.setText('%s,%s,%s' % self.visColor) - page.pushButton_visColor.clicked.connect(lambda: self.pickColor()) + super().widget(*args) + + self.page.comboBox_visLayout.addItem("Classic") + self.page.comboBox_visLayout.addItem("Split") + self.page.comboBox_visLayout.addItem("Bottom") + self.page.comboBox_visLayout.addItem("Top") + self.page.comboBox_visLayout.setCurrentIndex(0) + + self.page.lineEdit_visColor.setText('%s,%s,%s' % self.visColor) + self.page.pushButton_visColor.clicked.connect(lambda: self.pickColor()) btnStyle = "QPushButton { background-color : %s; outline: none; }" \ % QColor(*self.visColor).name() - page.pushButton_visColor.setStyleSheet(btnStyle) - page.lineEdit_visColor.textChanged.connect(self.update) - page.spinBox_scale.valueChanged.connect(self.update) - page.spinBox_y.valueChanged.connect(self.update) + self.page.pushButton_visColor.setStyleSheet(btnStyle) - self.page = page - return page + self.trackWidgets({ + 'layout': self.page.comboBox_visLayout, + 'scale': self.page.spinBox_scale, + 'y': self.page.spinBox_y, + }) def update(self): - self.layout = self.page.comboBox_visLayout.currentIndex() - self.visColor = self.RGBFromString(self.page.lineEdit_visColor.text()) - self.scale = self.page.spinBox_scale.value() - self.y = self.page.spinBox_y.value() - + self.visColor = rgbFromString(self.page.lineEdit_visColor.text()) super().update() - def loadPreset(self, pr, presetName=None): - super().loadPreset(pr, presetName) + def loadPreset(self, pr, *args): + super().loadPreset(pr, *args) self.page.lineEdit_visColor.setText('%s,%s,%s' % pr['visColor']) btnStyle = "QPushButton { background-color : %s; outline: none; }" \ % QColor(*pr['visColor']).name() self.page.pushButton_visColor.setStyleSheet(btnStyle) - self.page.comboBox_visLayout.setCurrentIndex(pr['layout']) - self.page.spinBox_scale.setValue(pr['scale']) - self.page.spinBox_y.setValue(pr['y']) def savePreset(self): - return { - 'preset': self.currentPreset, - 'layout': self.layout, - 'visColor': self.visColor, - 'scale': self.scale, - 'y': self.y, - } - - def previewRender(self, previewWorker): + saveValueStore = super().savePreset() + saveValueStore['visColor'] = self.visColor + return saveValueStore + + def previewRender(self): spectrum = numpy.fromfunction( lambda x: float(self.scale)/2500*(x-128)**2, (255,), dtype="int16") - width = int(self.settings.value('outputWidth')) - height = int(self.settings.value('outputHeight')) return self.drawBars( - width, height, spectrum, self.visColor, self.layout + self.width, self.height, spectrum, self.visColor, self.layout ) def preFrameRender(self, **kwargs): @@ -89,8 +72,6 @@ class Component(Component): self.smoothConstantUp = 0.8 self.lastSpectrum = None self.spectrumArray = {} - 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: @@ -108,7 +89,7 @@ class Component(Component): self.progressBarSetText.emit(pStr) self.progressBarUpdate.emit(int(progress)) - def frameRender(self, layerNo, frameNo): + def frameRender(self, frameNo): arrayNo = frameNo * self.sampleSize return self.drawBars( self.width, self.height, @@ -116,7 +97,7 @@ class Component(Component): self.visColor, self.layout) def pickColor(self): - RGBstring, btnStyle = super().pickColor() + RGBstring, btnStyle = pickColor() if not RGBstring: return self.page.lineEdit_visColor.setText(RGBstring) @@ -208,7 +189,7 @@ class Component(Component): return im def command(self, arg): - if not arg.startswith('preset=') and '=' in arg: + if '=' in arg: key, arg = arg.split('=', 1) try: if key == 'color': diff --git a/src/components/sound.py b/src/components/sound.py index bd7d002..26ecf93 100644 --- a/src/components/sound.py +++ b/src/components/sound.py @@ -2,42 +2,24 @@ from PyQt5 import QtGui, QtCore, QtWidgets import os from component import Component -from frame import BlankFrame +from toolkit.frame import BlankFrame class Component(Component): - '''Sound''' - - modified = QtCore.pyqtSignal(int, dict) - - def widget(self, parent): - self.parent = parent - self.settings = parent.settings - page = self.loadUi('sound.ui') - - page.lineEdit_sound.textChanged.connect(self.update) - page.pushButton_sound.clicked.connect(self.pickSound) - page.checkBox_chorus.stateChanged.connect(self.update) - page.spinBox_delay.valueChanged.connect(self.update) - page.spinBox_volume.valueChanged.connect(self.update) - - self.page = page - return page - - def update(self): - self.sound = self.page.lineEdit_sound.text() - self.delay = self.page.spinBox_delay.value() - self.volume = self.page.spinBox_volume.value() - self.chorus = self.page.checkBox_chorus.isChecked() - super().update() - - def previewRender(self, previewWorker): - width = int(previewWorker.core.settings.value('outputWidth')) - height = int(previewWorker.core.settings.value('outputHeight')) - return BlankFrame(width, height) - - def preFrameRender(self, **kwargs): - pass + name = 'Sound' + version = '1.0.0' + + def widget(self, *args): + super().widget(*args) + self.page.pushButton_sound.clicked.connect(self.pickSound) + self.trackWidgets({ + 'sound': self.page.lineEdit_sound, + 'chorus': self.page.checkBox_chorus, + 'delay': self.page.spinBox_delay, + 'volume': self.page.spinBox_volume, + }, commandArgs={ + 'sound': None, + }) def properties(self): props = ['static', 'audio'] @@ -73,31 +55,11 @@ class Component(Component): self.page.lineEdit_sound.setText(filename) self.update() - def frameRender(self, layerNo, frameNo): - width = int(self.settings.value('outputWidth')) - height = int(self.settings.value('outputHeight')) - return BlankFrame(width, height) - - def loadPreset(self, pr, presetName=None): - super().loadPreset(pr, presetName) - self.page.lineEdit_sound.setText(pr['sound']) - self.page.checkBox_chorus.setChecked(pr['chorus']) - self.page.spinBox_delay.setValue(pr['delay']) - self.page.spinBox_volume.setValue(pr['volume']) - - def savePreset(self): - return { - 'sound': self.sound, - 'chorus': self.chorus, - 'delay': self.delay, - 'volume': self.volume, - } - def commandHelp(self): print('Path to audio file:\n path=/filepath/to/sound.ogg') def command(self, arg): - if not arg.startswith('preset=') and '=' in arg: + if '=' in arg: key, arg = arg.split('=', 1) if key == 'path': if '*%s' % os.path.splitext(arg)[1] \ diff --git a/src/components/text.py b/src/components/text.py index 19460e5..8a302ff 100644 --- a/src/components/text.py +++ b/src/components/text.py @@ -4,24 +4,22 @@ from PyQt5 import QtGui, QtCore, QtWidgets import os from component import Component -from frame import FramePainter +from toolkit.frame import FramePainter +from toolkit import rgbFromString, pickColor class Component(Component): - '''Title Text''' - - modified = QtCore.pyqtSignal(int, dict) + name = 'Title Text' + version = '1.0.0' def __init__(self, *args): super().__init__(*args) self.titleFont = QFont() - def widget(self, parent): - self.parent = parent - self.settings = self.parent.core.settings + 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 @@ -30,41 +28,36 @@ class Component(Component): self.xPosition = width / 2 - fm.width(self.title)/2 self.yPosition = height / 2 * 1.036 - page = self.loadUi('text.ui') - page.comboBox_textAlign.addItem("Left") - page.comboBox_textAlign.addItem("Middle") - page.comboBox_textAlign.addItem("Right") + self.page.comboBox_textAlign.addItem("Left") + self.page.comboBox_textAlign.addItem("Middle") + self.page.comboBox_textAlign.addItem("Right") - page.lineEdit_textColor.setText('%s,%s,%s' % self.textColor) - page.pushButton_textColor.clicked.connect(self.pickColor) + self.page.lineEdit_textColor.setText('%s,%s,%s' % self.textColor) + self.page.pushButton_textColor.clicked.connect(self.pickColor) btnStyle = "QPushButton { background-color : %s; outline: none; }" \ % QColor(*self.textColor).name() - page.pushButton_textColor.setStyleSheet(btnStyle) - - page.lineEdit_title.setText(self.title) - page.comboBox_textAlign.setCurrentIndex(int(self.alignment)) - page.spinBox_fontSize.setValue(int(self.fontSize)) - page.spinBox_xTextAlign.setValue(int(self.xPosition)) - page.spinBox_yTextAlign.setValue(int(self.yPosition)) - - page.fontComboBox_titleFont.currentFontChanged.connect(self.update) - page.lineEdit_title.textChanged.connect(self.update) - page.comboBox_textAlign.currentIndexChanged.connect(self.update) - page.spinBox_xTextAlign.valueChanged.connect(self.update) - page.spinBox_yTextAlign.valueChanged.connect(self.update) - page.spinBox_fontSize.valueChanged.connect(self.update) - page.lineEdit_textColor.textChanged.connect(self.update) - self.page = page - return page + self.page.pushButton_textColor.setStyleSheet(btnStyle) + + 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)) + + self.page.fontComboBox_titleFont.currentFontChanged.connect( + self.update + ) + self.trackWidgets({ + 'title': self.page.lineEdit_title, + 'alignment': self.page.comboBox_textAlign, + 'fontSize': self.page.spinBox_fontSize, + 'xPosition': self.page.spinBox_xTextAlign, + 'yPosition': self.page.spinBox_yTextAlign, + }) def update(self): - self.title = self.page.lineEdit_title.text() - self.alignment = self.page.comboBox_textAlign.currentIndex() self.titleFont = self.page.fontComboBox_titleFont.currentFont() - self.fontSize = self.page.spinBox_fontSize.value() - self.xPosition = self.page.spinBox_xTextAlign.value() - self.yPosition = self.page.spinBox_yTextAlign.value() - self.textColor = self.RGBFromString( + self.textColor = rgbFromString( self.page.lineEdit_textColor.text()) btnStyle = "QPushButton { background-color : %s; outline: none; }" \ % QColor(*self.textColor).name() @@ -87,38 +80,25 @@ class Component(Component): x = self.xPosition - offset return x, self.yPosition - def loadPreset(self, pr, presetName=None): - super().loadPreset(pr, presetName) + def loadPreset(self, pr, *args): + super().loadPreset(pr, *args) - self.page.lineEdit_title.setText(pr['title']) font = QFont() font.fromString(pr['titleFont']) self.page.fontComboBox_titleFont.setCurrentFont(font) - self.page.spinBox_fontSize.setValue(pr['fontSize']) - self.page.comboBox_textAlign.setCurrentIndex(pr['alignment']) - self.page.spinBox_xTextAlign.setValue(pr['xPosition']) - self.page.spinBox_yTextAlign.setValue(pr['yPosition']) self.page.lineEdit_textColor.setText('%s,%s,%s' % pr['textColor']) btnStyle = "QPushButton { background-color : %s; outline: none; }" \ % QColor(*pr['textColor']).name() self.page.pushButton_textColor.setStyleSheet(btnStyle) def savePreset(self): - return { - 'preset': self.currentPreset, - 'title': self.title, - 'titleFont': self.titleFont.toString(), - 'alignment': self.alignment, - 'fontSize': self.fontSize, - 'xPosition': self.xPosition, - 'yPosition': self.yPosition, - 'textColor': self.textColor - } - - def previewRender(self, previewWorker): - width = int(self.settings.value('outputWidth')) - height = int(self.settings.value('outputHeight')) - return self.addText(width, height) + saveValueStore = super().savePreset() + saveValueStore['titleFont'] = self.titleFont.toString() + saveValueStore['textColor'] = self.textColor + return saveValueStore + + def previewRender(self): + return self.addText(self.width, self.height) def properties(self): props = ['static'] @@ -129,13 +109,10 @@ class Component(Component): def error(self): return "No text provided." - def frameRender(self, layerNo, frameNo): - width = int(self.settings.value('outputWidth')) - height = int(self.settings.value('outputHeight')) - return self.addText(width, height) + def frameRender(self, frameNo): + return self.addText(self.width, self.height) def addText(self, width, height): - image = FramePainter(width, height) self.titleFont.setPixelSize(self.fontSize) image.setFont(self.titleFont) @@ -146,7 +123,7 @@ class Component(Component): return image.finalize() def pickColor(self): - RGBstring, btnStyle = super().pickColor() + RGBstring, btnStyle = pickColor() if not RGBstring: return self.page.lineEdit_textColor.setText(RGBstring) @@ -159,7 +136,7 @@ class Component(Component): print('Set custom x, y position:\n x=500 y=500') def command(self, arg): - if not arg.startswith('preset=') and '=' in arg: + if '=' in arg: key, arg = arg.split('=', 1) if key == 'color': self.page.lineEdit_textColor.setText(arg) diff --git a/src/components/video.py b/src/components/video.py index 9e3db30..b2487c1 100644 --- a/src/components/video.py +++ b/src/components/video.py @@ -3,19 +3,25 @@ from PyQt5 import QtGui, QtCore, QtWidgets import os import math import subprocess +import signal import threading from queue import PriorityQueue -from component import Component, BadComponentInit -from frame import BlankFrame +from component import Component, ComponentError +from toolkit.frame import BlankFrame +from toolkit.ffmpeg import testAudioStream from toolkit import openPipe, checkOutput class Video: - '''Video Component Frame-Fetcher''' + '''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 core.FFMPEG_BIN + 'ffmpeg', # path to ffmpeg, usually self.core.FFMPEG_BIN 'videoPath', 'width', 'height', @@ -26,10 +32,7 @@ class Video: 'component', # component object ] for arg in mandatoryArgs: - try: - exec('self.%s = kwargs[arg]' % arg) - except KeyError: - raise BadComponentInit(arg, self.__doc__) + setattr(self, arg, kwargs[arg]) self.frameNo = -1 self.currentFrame = 'None' @@ -56,7 +59,7 @@ class Video: self.thread = threading.Thread( target=self.fillBuffer, - name=self.__doc__ + name='Video Frame-Fetcher' ) self.thread.daemon = True self.thread.start() @@ -73,8 +76,8 @@ class Video: self.frameBuffer.task_done() def fillBuffer(self): - pipe = openPipe( - self.command, stdout=subprocess.PIPE, + self.pipe = openPipe( + self.command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, bufsize=10**8 ) while True: @@ -87,120 +90,89 @@ class Video: if len(self.currentFrame) == 0: self.frameBuffer.put((self.frameNo-1, self.lastFrame)) continue - except AttributeError as e: - self.parent.showMessage( - msg='%s couldn\'t be loaded. ' - 'This is a fatal error.' % os.path.basename( - self.videoPath - ), - detail=str(e), - icon='Warning' - ) - self.parent.stopVideo() + except AttributeError: + Video.threadError = ComponentError(self.component, 'video') break - self.currentFrame = pipe.stdout.read(self.chunkSize) + self.currentFrame = self.pipe.stdout.read(self.chunkSize) if len(self.currentFrame) != 0: self.frameBuffer.put((self.frameNo, self.currentFrame)) self.lastFrame = self.currentFrame class Component(Component): - '''Video''' + name = 'Video' + version = '1.0.0' - modified = QtCore.pyqtSignal(int, dict) - - def widget(self, parent): - self.parent = parent - self.settings = parent.settings - page = self.loadUi('video.ui') + def widget(self, *args): self.videoPath = '' self.badVideo = False self.badAudio = False self.x = 0 self.y = 0 self.loopVideo = False - - 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.checkBox_useAudio.stateChanged.connect(self.update) - page.spinBox_scale.valueChanged.connect(self.update) - page.spinBox_volume.valueChanged.connect(self.update) - page.spinBox_x.valueChanged.connect(self.update) - page.spinBox_y.valueChanged.connect(self.update) - - self.page = page - return page + super().widget(*args) + 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', + } + ) 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.volume = self.page.spinBox_volume.value() - self.xPosition = self.page.spinBox_x.value() - self.yPosition = self.page.spinBox_y.value() - - if self.useAudio: + if self.page.checkBox_useAudio.isChecked(): self.page.label_volume.setEnabled(True) self.page.spinBox_volume.setEnabled(True) else: self.page.label_volume.setEnabled(False) self.page.spinBox_volume.setEnabled(False) - super().update() - def previewRender(self, previewWorker): - width = int(previewWorker.core.settings.value('outputWidth')) - height = int(previewWorker.core.settings.value('outputHeight')) - self.updateChunksize(width, height) - frame = self.getPreviewFrame(width, height) + def previewRender(self): + self.updateChunksize() + frame = self.getPreviewFrame(self.width, self.height) if not frame: - return BlankFrame(width, height) + return BlankFrame(self.width, self.height) else: return frame def properties(self): props = [] - if not self.videoPath or self.badVideo \ - or not os.path.exists(self.videoPath): - return ['error'] + if hasattr(self.parent, 'window'): + outputFile = self.parent.window.lineEdit_outputFile.text() + else: + outputFile = str(self.parent.args.output) + + 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): + self.lockError("Input and output paths match.") if self.useAudio: props.append('audio') - self.testAudioStream() - if self.badAudio: - return ['error'] + if not testAudioStream(self.videoPath) \ + and self.error() is None: + self.lockError( + "Could not identify an audio stream in this video.") return props - def error(self): - if 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): - return "The video selected does not exist!" - if self.badVideo: - return "The video selected is corrupt!" - - def testAudioStream(self): - # 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 - else: - self.badAudio = False - def audio(self): params = {} if self.volume != 1.0: @@ -209,47 +181,23 @@ class Component(Component): def preFrameRender(self, **kwargs): super().preFrameRender(**kwargs) - width = int(self.worker.core.settings.value('outputWidth')) - height = int(self.worker.core.settings.value('outputHeight')) - self.blankFrame_ = BlankFrame(width, height) - self.updateChunksize(width, height) + self.updateChunksize() self.video = Video( ffmpeg=self.core.FFMPEG_BIN, videoPath=self.videoPath, - width=width, height=height, chunkSize=self.chunkSize, + 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 ) if os.path.exists(self.videoPath) else None - def frameRender(self, layerNo, frameNo): - if self.video: - return self.video.frame(frameNo) - else: - return self.blankFrame_ - - 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_useAudio.setChecked(pr['useAudio']) - self.page.checkBox_distort.setChecked(pr['distort']) - self.page.spinBox_scale.setValue(pr['scale']) - self.page.spinBox_volume.setValue(pr['volume']) - 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, - 'useAudio': self.useAudio, - 'distort': self.distort, - 'scale': self.scale, - 'volume': self.volume, - 'x': self.xPosition, - 'y': self.yPosition, - } + def frameRender(self, frameNo): + if Video.threadError is not None: + raise Video.threadError + return self.video.frame(frameNo) + + def postFrameRender(self): + self.video.pipe.stdout.close() + self.video.pipe.send_signal(signal.SIGINT) def pickVideo(self): imgDir = self.settings.value("componentDir", os.path.expanduser("~")) @@ -267,7 +215,7 @@ class Component(Component): return command = [ - self.parent.core.FFMPEG_BIN, + self.core.FFMPEG_BIN, '-thread_queue_size', '512', '-i', self.videoPath, '-f', 'image2pipe', @@ -279,23 +227,25 @@ class Component(Component): '-vframes', '1', ] pipe = openPipe( - command, stdout=subprocess.PIPE, + command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, bufsize=10**8 ) byteFrame = pipe.stdout.read(self.chunkSize) - frame = finalizeFrame(self, byteFrame, width, height) pipe.stdout.close() - pipe.kill() + pipe.send_signal(signal.SIGINT) + frame = finalizeFrame(self, byteFrame, width, height) return frame - def updateChunksize(self, width, height): + def updateChunksize(self): if self.scale != 100 and not self.distort: - width, height = scale(self.scale, width, height, int) - self.chunkSize = 4*width*height + width, height = scale(self.scale, self.width, self.height, int) + else: + width, height = self.width, self.height + self.chunkSize = 4 * width * height def command(self, arg): - if not arg.startswith('preset=') and '=' in arg: + if '=' in arg: key, arg = arg.split('=', 1) if key == 'path' and os.path.exists(arg): if '*%s' % os.path.splitext(arg)[1] in self.core.videoFormats: |
