new Waveform & Spectrum component + relative coords for everything
NOTE: projects and presets saved from old versions will not load correctly anymore
This commit is contained in:
commit
20905230fe
|
@ -11,3 +11,5 @@ env/*
|
||||||
*.tar.*
|
*.tar.*
|
||||||
*.exe
|
*.exe
|
||||||
ffmpeg
|
ffmpeg
|
||||||
|
*.bak
|
||||||
|
*~
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -2,7 +2,7 @@ from setuptools import setup
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
__version__ = '2.0.0.rc2'
|
__version__ = '2.0.0.rc3'
|
||||||
|
|
||||||
|
|
||||||
def package_files(directory):
|
def package_files(directory):
|
||||||
|
|
248
src/component.py
248
src/component.py
|
@ -3,16 +3,21 @@
|
||||||
on making a valid component.
|
on making a valid component.
|
||||||
'''
|
'''
|
||||||
from PyQt5 import uic, QtCore, QtWidgets
|
from PyQt5 import uic, QtCore, QtWidgets
|
||||||
|
from PyQt5.QtGui import QColor
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
import math
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from toolkit.frame import BlankFrame
|
from toolkit.frame import BlankFrame
|
||||||
|
from toolkit import (
|
||||||
|
getWidgetValue, setWidgetValue, connectWidget, rgbFromString
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ComponentMetaclass(type(QtCore.QObject)):
|
class ComponentMetaclass(type(QtCore.QObject)):
|
||||||
'''
|
'''
|
||||||
Checks the validity of each Component class imported, and
|
Checks the validity of each Component class and mutates some attrs.
|
||||||
mutates some attributes for easier use by the core program.
|
|
||||||
E.g., takes only major version from version string & decorates methods
|
E.g., takes only major version from version string & decorates methods
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
@ -171,8 +176,17 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
|
||||||
self._trackedWidgets = {}
|
self._trackedWidgets = {}
|
||||||
self._presetNames = {}
|
self._presetNames = {}
|
||||||
self._commandArgs = {}
|
self._commandArgs = {}
|
||||||
|
self._colorWidgets = {}
|
||||||
|
self._colorFuncs = {}
|
||||||
|
self._relativeWidgets = {}
|
||||||
|
# pixel values stored as floats
|
||||||
|
self._relativeValues = {}
|
||||||
|
# maximum values of spinBoxes at 1080p (Core.resolutions[0])
|
||||||
|
self._relativeMaximums = {}
|
||||||
|
|
||||||
self._lockedProperties = None
|
self._lockedProperties = None
|
||||||
self._lockedError = None
|
self._lockedError = None
|
||||||
|
self._lockedSize = None
|
||||||
|
|
||||||
# Stop lengthy processes in response to this variable
|
# Stop lengthy processes in response to this variable
|
||||||
self.canceled = False
|
self.canceled = False
|
||||||
|
@ -181,12 +195,16 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
|
||||||
return self.__class__.name
|
return self.__class__.name
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
try:
|
||||||
|
preset = self.savePreset()
|
||||||
|
except Exception as e:
|
||||||
|
preset = '%s occured while saving preset' % str(e)
|
||||||
return '%s\n%s\n%s' % (
|
return '%s\n%s\n%s' % (
|
||||||
self.__class__.name, str(self.__class__.version), self.savePreset()
|
self.__class__.name, str(self.__class__.version), preset
|
||||||
)
|
)
|
||||||
|
|
||||||
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
|
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
|
||||||
# Critical Methods
|
# Render Methods
|
||||||
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
|
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
|
||||||
|
|
||||||
def previewRender(self):
|
def previewRender(self):
|
||||||
|
@ -197,7 +215,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
|
||||||
'''
|
'''
|
||||||
Must call super() when subclassing
|
Must call super() when subclassing
|
||||||
Triggered only before a video is exported (video_thread.py)
|
Triggered only before a video is exported (video_thread.py)
|
||||||
self.worker = the video thread worker
|
self.audioFile = filepath to the main input audio file
|
||||||
self.completeAudioArray = a list of audio samples
|
self.completeAudioArray = a list of audio samples
|
||||||
self.sampleSize = number of audio samples per video frame
|
self.sampleSize = number of audio samples per video frame
|
||||||
self.progressBarUpdate = signal to set progress bar number
|
self.progressBarUpdate = signal to set progress bar number
|
||||||
|
@ -273,14 +291,9 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
|
||||||
widgets['spinBox'].extend(
|
widgets['spinBox'].extend(
|
||||||
self.page.findChildren(QtWidgets.QDoubleSpinBox)
|
self.page.findChildren(QtWidgets.QDoubleSpinBox)
|
||||||
)
|
)
|
||||||
for widget in widgets['lineEdit']:
|
for widgetList in widgets.values():
|
||||||
widget.textChanged.connect(self.update)
|
for widget in widgetList:
|
||||||
for widget in widgets['checkBox']:
|
connectWidget(widget, self.update)
|
||||||
widget.stateChanged.connect(self.update)
|
|
||||||
for widget in widgets['spinBox']:
|
|
||||||
widget.valueChanged.connect(self.update)
|
|
||||||
for widget in widgets['comboBox']:
|
|
||||||
widget.currentIndexChanged.connect(self.update)
|
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
'''
|
'''
|
||||||
|
@ -289,15 +302,24 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
|
||||||
Call super() at the END if you need to subclass this.
|
Call super() at the END if you need to subclass this.
|
||||||
'''
|
'''
|
||||||
for attr, widget in self._trackedWidgets.items():
|
for attr, widget in self._trackedWidgets.items():
|
||||||
if type(widget) == QtWidgets.QLineEdit:
|
if attr in self._colorWidgets:
|
||||||
setattr(self, attr, widget.text())
|
# Color Widgets: text stored as tuple & update the button color
|
||||||
elif type(widget) == QtWidgets.QSpinBox \
|
rgbTuple = rgbFromString(widget.text())
|
||||||
or type(widget) == QtWidgets.QDoubleSpinBox:
|
btnStyle = (
|
||||||
setattr(self, attr, widget.value())
|
"QPushButton { background-color : %s; outline: none; }"
|
||||||
elif type(widget) == QtWidgets.QCheckBox:
|
% QColor(*rgbTuple).name())
|
||||||
setattr(self, attr, widget.isChecked())
|
self._colorWidgets[attr].setStyleSheet(btnStyle)
|
||||||
elif type(widget) == QtWidgets.QComboBox:
|
setattr(self, attr, rgbTuple)
|
||||||
setattr(self, attr, widget.currentIndex())
|
|
||||||
|
elif attr in self._relativeWidgets:
|
||||||
|
# Relative widgets: number scales to fit export resolution
|
||||||
|
self.updateRelativeWidget(attr)
|
||||||
|
setattr(self, attr, self._trackedWidgets[attr].value())
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Normal tracked widget
|
||||||
|
setattr(self, attr, getWidgetValue(widget))
|
||||||
|
|
||||||
if not self.core.openingProject:
|
if not self.core.openingProject:
|
||||||
self.parent.drawPreview()
|
self.parent.drawPreview()
|
||||||
saveValueStore = self.savePreset()
|
saveValueStore = self.savePreset()
|
||||||
|
@ -313,27 +335,40 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
|
||||||
self.currentPreset = presetName \
|
self.currentPreset = presetName \
|
||||||
if presetName is not None else presetDict['preset']
|
if presetName is not None else presetDict['preset']
|
||||||
for attr, widget in self._trackedWidgets.items():
|
for attr, widget in self._trackedWidgets.items():
|
||||||
val = presetDict[
|
key = attr if attr not in self._presetNames \
|
||||||
attr if attr not in self._presetNames
|
|
||||||
else self._presetNames[attr]
|
else self._presetNames[attr]
|
||||||
]
|
val = presetDict[key]
|
||||||
if type(widget) == QtWidgets.QLineEdit:
|
|
||||||
widget.setText(val)
|
if attr in self._colorWidgets:
|
||||||
elif type(widget) == QtWidgets.QSpinBox \
|
widget.setText('%s,%s,%s' % val)
|
||||||
or type(widget) == QtWidgets.QDoubleSpinBox:
|
btnStyle = (
|
||||||
widget.setValue(val)
|
"QPushButton { background-color : %s; outline: none; }"
|
||||||
elif type(widget) == QtWidgets.QCheckBox:
|
% QColor(*val).name()
|
||||||
widget.setChecked(val)
|
)
|
||||||
elif type(widget) == QtWidgets.QComboBox:
|
self._colorWidgets[attr].setStyleSheet(btnStyle)
|
||||||
widget.setCurrentIndex(val)
|
elif attr in self._relativeWidgets:
|
||||||
|
self._relativeValues[attr] = val
|
||||||
|
pixelVal = self.pixelValForAttr(attr, val)
|
||||||
|
setWidgetValue(widget, pixelVal)
|
||||||
|
else:
|
||||||
|
setWidgetValue(widget, val)
|
||||||
|
|
||||||
def savePreset(self):
|
def savePreset(self):
|
||||||
saveValueStore = {}
|
saveValueStore = {}
|
||||||
for attr, widget in self._trackedWidgets.items():
|
for attr, widget in self._trackedWidgets.items():
|
||||||
saveValueStore[
|
presetAttrName = (
|
||||||
attr if attr not in self._presetNames
|
attr if attr not in self._presetNames
|
||||||
else self._presetNames[attr]
|
else self._presetNames[attr]
|
||||||
] = getattr(self, attr)
|
)
|
||||||
|
if attr in self._relativeWidgets:
|
||||||
|
try:
|
||||||
|
val = self._relativeValues[attr]
|
||||||
|
except AttributeError:
|
||||||
|
val = self.floatValForAttr(attr)
|
||||||
|
else:
|
||||||
|
val = getattr(self, attr)
|
||||||
|
|
||||||
|
saveValueStore[presetAttrName] = val
|
||||||
return saveValueStore
|
return saveValueStore
|
||||||
|
|
||||||
def commandHelp(self):
|
def commandHelp(self):
|
||||||
|
@ -372,7 +407,12 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
|
||||||
self._trackedWidgets = trackDict
|
self._trackedWidgets = trackDict
|
||||||
for kwarg in kwargs:
|
for kwarg in kwargs:
|
||||||
try:
|
try:
|
||||||
if kwarg in ('presetNames', 'commandArgs'):
|
if kwarg in (
|
||||||
|
'presetNames',
|
||||||
|
'commandArgs',
|
||||||
|
'colorWidgets',
|
||||||
|
'relativeWidgets',
|
||||||
|
):
|
||||||
setattr(self, '_%s' % kwarg, kwargs[kwarg])
|
setattr(self, '_%s' % kwarg, kwargs[kwarg])
|
||||||
else:
|
else:
|
||||||
raise ComponentError(
|
raise ComponentError(
|
||||||
|
@ -380,29 +420,79 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
|
||||||
except ComponentError:
|
except ComponentError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if kwarg == 'colorWidgets':
|
||||||
|
def makeColorFunc(attr):
|
||||||
|
def pickColor_():
|
||||||
|
self.pickColor(
|
||||||
|
self._trackedWidgets[attr],
|
||||||
|
self._colorWidgets[attr]
|
||||||
|
)
|
||||||
|
return pickColor_
|
||||||
|
self._colorFuncs = {
|
||||||
|
attr: makeColorFunc(attr) for attr in kwargs[kwarg]
|
||||||
|
}
|
||||||
|
for attr, func in self._colorFuncs.items():
|
||||||
|
self._colorWidgets[attr].clicked.connect(func)
|
||||||
|
self._colorWidgets[attr].setStyleSheet(
|
||||||
|
"QPushButton {"
|
||||||
|
"background-color : #FFFFFF; outline: none; }"
|
||||||
|
)
|
||||||
|
|
||||||
|
if kwarg == 'relativeWidgets':
|
||||||
|
# store maximum values of spinBoxes to be scaled appropriately
|
||||||
|
for attr in kwargs[kwarg]:
|
||||||
|
self._relativeMaximums[attr] = \
|
||||||
|
self._trackedWidgets[attr].maximum()
|
||||||
|
self.updateRelativeWidgetMaximum(attr)
|
||||||
|
|
||||||
|
def pickColor(self, textWidget, button):
|
||||||
|
'''Use color picker to get color input from the user.'''
|
||||||
|
dialog = QtWidgets.QColorDialog()
|
||||||
|
dialog.setOption(QtWidgets.QColorDialog.ShowAlphaChannel, True)
|
||||||
|
color = dialog.getColor()
|
||||||
|
if color.isValid():
|
||||||
|
RGBstring = '%s,%s,%s' % (
|
||||||
|
str(color.red()), str(color.green()), str(color.blue()))
|
||||||
|
btnStyle = "QPushButton{background-color: %s; outline: none;}" \
|
||||||
|
% color.name()
|
||||||
|
textWidget.setText(RGBstring)
|
||||||
|
button.setStyleSheet(btnStyle)
|
||||||
|
|
||||||
def lockProperties(self, propList):
|
def lockProperties(self, propList):
|
||||||
self._lockedProperties = propList
|
self._lockedProperties = propList
|
||||||
|
|
||||||
def lockError(self, msg):
|
def lockError(self, msg):
|
||||||
self._lockedError = msg
|
self._lockedError = msg
|
||||||
|
|
||||||
|
def lockSize(self, w, h):
|
||||||
|
self._lockedSize = (w, h)
|
||||||
|
|
||||||
def unlockProperties(self):
|
def unlockProperties(self):
|
||||||
self._lockedProperties = None
|
self._lockedProperties = None
|
||||||
|
|
||||||
def unlockError(self):
|
def unlockError(self):
|
||||||
self._lockedError = None
|
self._lockedError = None
|
||||||
|
|
||||||
|
def unlockSize(self):
|
||||||
|
self._lockedSize = None
|
||||||
|
|
||||||
def loadUi(self, filename):
|
def loadUi(self, filename):
|
||||||
'''Load a Qt Designer ui file to use for this component's widget'''
|
'''Load a Qt Designer ui file to use for this component's widget'''
|
||||||
return uic.loadUi(os.path.join(self.core.componentsPath, filename))
|
return uic.loadUi(os.path.join(self.core.componentsPath, filename))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def width(self):
|
def width(self):
|
||||||
|
if self._lockedSize is None:
|
||||||
return int(self.settings.value('outputWidth'))
|
return int(self.settings.value('outputWidth'))
|
||||||
|
else:
|
||||||
|
return self._lockedSize[0]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def height(self):
|
def height(self):
|
||||||
|
if self._lockedSize is None:
|
||||||
return int(self.settings.value('outputHeight'))
|
return int(self.settings.value('outputHeight'))
|
||||||
|
else:
|
||||||
|
return self._lockedSize[1]
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self):
|
||||||
'''Stop any lengthy process in response to this variable.'''
|
'''Stop any lengthy process in response to this variable.'''
|
||||||
|
@ -413,6 +503,67 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
|
||||||
self.unlockProperties()
|
self.unlockProperties()
|
||||||
self.unlockError()
|
self.unlockError()
|
||||||
|
|
||||||
|
def relativeWidgetAxis(func):
|
||||||
|
def relativeWidgetAxis(self, attr, *args, **kwargs):
|
||||||
|
if 'axis' not in kwargs:
|
||||||
|
axis = self.width
|
||||||
|
if 'height' in attr.lower() \
|
||||||
|
or 'ypos' in attr.lower() or attr == 'y':
|
||||||
|
axis = self.height
|
||||||
|
kwargs['axis'] = axis
|
||||||
|
return func(self, attr, *args, **kwargs)
|
||||||
|
return relativeWidgetAxis
|
||||||
|
|
||||||
|
@relativeWidgetAxis
|
||||||
|
def pixelValForAttr(self, attr, val=None, **kwargs):
|
||||||
|
if val is None:
|
||||||
|
val = self._relativeValues[attr]
|
||||||
|
return math.ceil(kwargs['axis'] * val)
|
||||||
|
|
||||||
|
@relativeWidgetAxis
|
||||||
|
def floatValForAttr(self, attr, val=None, **kwargs):
|
||||||
|
if val is None:
|
||||||
|
val = self._trackedWidgets[attr].value()
|
||||||
|
return val / kwargs['axis']
|
||||||
|
|
||||||
|
def setRelativeWidget(self, attr, floatVal):
|
||||||
|
'''Set a relative widget using a float'''
|
||||||
|
pixelVal = self.pixelValForAttr(attr, floatVal)
|
||||||
|
self._trackedWidgets[attr].setValue(pixelVal)
|
||||||
|
|
||||||
|
|
||||||
|
def updateRelativeWidget(self, attr):
|
||||||
|
try:
|
||||||
|
oldUserValue = getattr(self, attr)
|
||||||
|
except AttributeError:
|
||||||
|
oldUserValue = self._trackedWidgets[attr].value()
|
||||||
|
newUserValue = self._trackedWidgets[attr].value()
|
||||||
|
newRelativeVal = self.floatValForAttr(attr, newUserValue)
|
||||||
|
|
||||||
|
if attr in self._relativeValues:
|
||||||
|
oldRelativeVal = self._relativeValues[attr]
|
||||||
|
if oldUserValue == newUserValue \
|
||||||
|
and oldRelativeVal != newRelativeVal:
|
||||||
|
# Float changed without pixel value changing, which
|
||||||
|
# means the pixel value needs to be updated
|
||||||
|
self._trackedWidgets[attr].blockSignals(True)
|
||||||
|
self.updateRelativeWidgetMaximum(attr)
|
||||||
|
pixelVal = self.pixelValForAttr(attr, oldRelativeVal)
|
||||||
|
self._trackedWidgets[attr].setValue(pixelVal)
|
||||||
|
self._trackedWidgets[attr].blockSignals(False)
|
||||||
|
|
||||||
|
if attr not in self._relativeValues \
|
||||||
|
or oldUserValue != newUserValue:
|
||||||
|
self._relativeValues[attr] = newRelativeVal
|
||||||
|
|
||||||
|
def updateRelativeWidgetMaximum(self, attr):
|
||||||
|
maxRes = int(self.core.resolutions[0].split('x')[0])
|
||||||
|
newMaximumValue = self.width * (
|
||||||
|
self._relativeMaximums[attr] /
|
||||||
|
maxRes
|
||||||
|
)
|
||||||
|
self._trackedWidgets[attr].setMaximum(int(newMaximumValue))
|
||||||
|
|
||||||
|
|
||||||
class ComponentError(RuntimeError):
|
class ComponentError(RuntimeError):
|
||||||
'''Gives the MainWindow a traceback to display, and cancels the export.'''
|
'''Gives the MainWindow a traceback to display, and cancels the export.'''
|
||||||
|
@ -420,36 +571,43 @@ class ComponentError(RuntimeError):
|
||||||
prevErrors = []
|
prevErrors = []
|
||||||
lastTime = time.time()
|
lastTime = time.time()
|
||||||
|
|
||||||
def __init__(self, caller, name):
|
def __init__(self, caller, name, msg=None):
|
||||||
print('##### ComponentError by %s: %s' % (caller.name, name))
|
if msg is None and sys.exc_info()[0] is not None:
|
||||||
|
msg = str(sys.exc_info()[1])
|
||||||
|
else:
|
||||||
|
msg = 'Unknown error.'
|
||||||
|
print("##### ComponentError by %s's %s: %s" % (
|
||||||
|
caller.name, name, msg))
|
||||||
|
|
||||||
|
# Don't create multiple windows for quickly repeated messages
|
||||||
if len(ComponentError.prevErrors) > 1:
|
if len(ComponentError.prevErrors) > 1:
|
||||||
ComponentError.prevErrors.pop()
|
ComponentError.prevErrors.pop()
|
||||||
ComponentError.prevErrors.insert(0, name)
|
ComponentError.prevErrors.insert(0, name)
|
||||||
curTime = time.time()
|
curTime = time.time()
|
||||||
if name in ComponentError.prevErrors[1:] \
|
if name in ComponentError.prevErrors[1:] \
|
||||||
and curTime - ComponentError.lastTime < 0.2:
|
and curTime - ComponentError.lastTime < 1.0:
|
||||||
# Don't create multiple windows for quickly repeated messages
|
|
||||||
return
|
return
|
||||||
ComponentError.lastTime = time.time()
|
ComponentError.lastTime = time.time()
|
||||||
|
|
||||||
from toolkit import formatTraceback
|
from toolkit import formatTraceback
|
||||||
import sys
|
|
||||||
if sys.exc_info()[0] is not None:
|
if sys.exc_info()[0] is not None:
|
||||||
string = (
|
string = (
|
||||||
"%s component's %s encountered %s %s." % (
|
"%s component (#%s): %s encountered %s %s: %s" % (
|
||||||
caller.__class__.name,
|
caller.__class__.name,
|
||||||
|
str(caller.compPos),
|
||||||
name,
|
name,
|
||||||
'an' if any([
|
'an' if any([
|
||||||
sys.exc_info()[0].__name__.startswith(vowel)
|
sys.exc_info()[0].__name__.startswith(vowel)
|
||||||
for vowel in ('A', 'I')
|
for vowel in ('A', 'I', 'U', 'O', 'E')
|
||||||
]) else 'a',
|
]) else 'a',
|
||||||
sys.exc_info()[0].__name__,
|
sys.exc_info()[0].__name__,
|
||||||
|
str(sys.exc_info()[1])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
detail = formatTraceback(sys.exc_info()[2])
|
detail = formatTraceback(sys.exc_info()[2])
|
||||||
else:
|
else:
|
||||||
string = name
|
string = name
|
||||||
detail = "Methods:\n%s" % (
|
detail = "Attributes:\n%s" % (
|
||||||
"\n".join(
|
"\n".join(
|
||||||
[m for m in dir(caller) if not m.startswith('_')]
|
[m for m in dir(caller) if not m.startswith('_')]
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,7 +6,6 @@ import os
|
||||||
|
|
||||||
from component import Component
|
from component import Component
|
||||||
from toolkit.frame import BlankFrame, FloodFrame, FramePainter, PaintColor
|
from toolkit.frame import BlankFrame, FloodFrame, FramePainter, PaintColor
|
||||||
from toolkit import rgbFromString, pickColor
|
|
||||||
|
|
||||||
|
|
||||||
class Component(Component):
|
class Component(Component):
|
||||||
|
@ -14,25 +13,12 @@ class Component(Component):
|
||||||
version = '1.0.0'
|
version = '1.0.0'
|
||||||
|
|
||||||
def widget(self, *args):
|
def widget(self, *args):
|
||||||
self.color1 = (0, 0, 0)
|
|
||||||
self.color2 = (133, 133, 133)
|
|
||||||
self.x = 0
|
self.x = 0
|
||||||
self.y = 0
|
self.y = 0
|
||||||
super().widget(*args)
|
super().widget(*args)
|
||||||
|
|
||||||
self.page.lineEdit_color1.setText('%s,%s,%s' % self.color1)
|
self.page.lineEdit_color1.setText('0,0,0')
|
||||||
self.page.lineEdit_color2.setText('%s,%s,%s' % self.color2)
|
self.page.lineEdit_color2.setText('133,133,133')
|
||||||
|
|
||||||
btnStyle1 = "QPushButton { background-color : %s; outline: none; }" \
|
|
||||||
% QColor(*self.color1).name()
|
|
||||||
|
|
||||||
btnStyle2 = "QPushButton { background-color : %s; outline: none; }" \
|
|
||||||
% QColor(*self.color2).name()
|
|
||||||
|
|
||||||
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
|
# disable color #2 until non-default 'fill' option gets changed
|
||||||
self.page.lineEdit_color2.setDisabled(True)
|
self.page.lineEdit_color2.setDisabled(True)
|
||||||
|
@ -51,8 +37,7 @@ class Component(Component):
|
||||||
self.page.comboBox_fill.addItem(label)
|
self.page.comboBox_fill.addItem(label)
|
||||||
self.page.comboBox_fill.setCurrentIndex(0)
|
self.page.comboBox_fill.setCurrentIndex(0)
|
||||||
|
|
||||||
self.trackWidgets(
|
self.trackWidgets({
|
||||||
{
|
|
||||||
'x': self.page.spinBox_x,
|
'x': self.page.spinBox_x,
|
||||||
'y': self.page.spinBox_y,
|
'y': self.page.spinBox_y,
|
||||||
'sizeWidth': self.page.spinBox_width,
|
'sizeWidth': self.page.spinBox_width,
|
||||||
|
@ -66,16 +51,22 @@ class Component(Component):
|
||||||
'LG_end': self.page.spinBox_linearGradient_end,
|
'LG_end': self.page.spinBox_linearGradient_end,
|
||||||
'RG_centre': self.page.spinBox_radialGradient_spread,
|
'RG_centre': self.page.spinBox_radialGradient_spread,
|
||||||
'fillType': self.page.comboBox_fill,
|
'fillType': self.page.comboBox_fill,
|
||||||
|
'color1': self.page.lineEdit_color1,
|
||||||
|
'color2': self.page.lineEdit_color2,
|
||||||
}, presetNames={
|
}, presetNames={
|
||||||
'sizeWidth': 'width',
|
'sizeWidth': 'width',
|
||||||
'sizeHeight': 'height',
|
'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):
|
def update(self):
|
||||||
self.color1 = rgbFromString(self.page.lineEdit_color1.text())
|
|
||||||
self.color2 = rgbFromString(self.page.lineEdit_color2.text())
|
|
||||||
|
|
||||||
fillType = self.page.comboBox_fill.currentIndex()
|
fillType = self.page.comboBox_fill.currentIndex()
|
||||||
if fillType == 0:
|
if fillType == 0:
|
||||||
self.page.lineEdit_color2.setEnabled(False)
|
self.page.lineEdit_color2.setEnabled(False)
|
||||||
|
@ -161,36 +152,6 @@ class Component(Component):
|
||||||
|
|
||||||
return image.finalize()
|
return image.finalize()
|
||||||
|
|
||||||
def loadPreset(self, pr, *args):
|
|
||||||
super().loadPreset(pr, *args)
|
|
||||||
|
|
||||||
self.page.lineEdit_color1.setText('%s,%s,%s' % pr['color1'])
|
|
||||||
self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2'])
|
|
||||||
|
|
||||||
btnStyle1 = "QPushButton { background-color : %s; outline: none; }" \
|
|
||||||
% QColor(*pr['color1']).name()
|
|
||||||
btnStyle2 = "QPushButton { background-color : %s; outline: none; }" \
|
|
||||||
% QColor(*pr['color2']).name()
|
|
||||||
self.page.pushButton_color1.setStyleSheet(btnStyle1)
|
|
||||||
self.page.pushButton_color2.setStyleSheet(btnStyle2)
|
|
||||||
|
|
||||||
def savePreset(self):
|
|
||||||
saveValueStore = super().savePreset()
|
|
||||||
saveValueStore['color1'] = self.color1
|
|
||||||
saveValueStore['color2'] = self.color2
|
|
||||||
return saveValueStore
|
|
||||||
|
|
||||||
def pickColor(self, num):
|
|
||||||
RGBstring, btnStyle = pickColor()
|
|
||||||
if not RGBstring:
|
|
||||||
return
|
|
||||||
if num == 1:
|
|
||||||
self.page.lineEdit_color1.setText(RGBstring)
|
|
||||||
self.page.pushButton_color1.setStyleSheet(btnStyle)
|
|
||||||
else:
|
|
||||||
self.page.lineEdit_color2.setText(RGBstring)
|
|
||||||
self.page.pushButton_color2.setStyleSheet(btnStyle)
|
|
||||||
|
|
||||||
def commandHelp(self):
|
def commandHelp(self):
|
||||||
print('Specify a color:\n color=255,255,255')
|
print('Specify a color:\n color=255,255,255')
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,7 @@ class Component(Component):
|
||||||
def widget(self, *args):
|
def widget(self, *args):
|
||||||
super().widget(*args)
|
super().widget(*args)
|
||||||
self.page.pushButton_image.clicked.connect(self.pickImage)
|
self.page.pushButton_image.clicked.connect(self.pickImage)
|
||||||
self.trackWidgets(
|
self.trackWidgets({
|
||||||
{
|
|
||||||
'imagePath': self.page.lineEdit_image,
|
'imagePath': self.page.lineEdit_image,
|
||||||
'scale': self.page.spinBox_scale,
|
'scale': self.page.spinBox_scale,
|
||||||
'rotate': self.page.spinBox_rotate,
|
'rotate': self.page.spinBox_rotate,
|
||||||
|
@ -23,13 +22,13 @@ class Component(Component):
|
||||||
'yPosition': self.page.spinBox_y,
|
'yPosition': self.page.spinBox_y,
|
||||||
'stretched': self.page.checkBox_stretch,
|
'stretched': self.page.checkBox_stretch,
|
||||||
'mirror': self.page.checkBox_mirror,
|
'mirror': self.page.checkBox_mirror,
|
||||||
},
|
}, presetNames={
|
||||||
presetNames={
|
|
||||||
'imagePath': 'image',
|
'imagePath': 'image',
|
||||||
'xPosition': 'x',
|
'xPosition': 'x',
|
||||||
'yPosition': 'y',
|
'yPosition': 'y',
|
||||||
},
|
}, relativeWidgets=[
|
||||||
)
|
'xPosition', 'yPosition', 'scale'
|
||||||
|
])
|
||||||
|
|
||||||
def previewRender(self):
|
def previewRender(self):
|
||||||
return self.drawFrame(self.width, self.height)
|
return self.drawFrame(self.width, self.height)
|
||||||
|
|
|
@ -8,7 +8,6 @@ from copy import copy
|
||||||
|
|
||||||
from component import Component
|
from component import Component
|
||||||
from toolkit.frame import BlankFrame
|
from toolkit.frame import BlankFrame
|
||||||
from toolkit import rgbFromString, pickColor
|
|
||||||
|
|
||||||
|
|
||||||
class Component(Component):
|
class Component(Component):
|
||||||
|
@ -18,8 +17,10 @@ class Component(Component):
|
||||||
def names(*args):
|
def names(*args):
|
||||||
return ['Original Audio Visualization']
|
return ['Original Audio Visualization']
|
||||||
|
|
||||||
|
def properties(self):
|
||||||
|
return ['pcm']
|
||||||
|
|
||||||
def widget(self, *args):
|
def widget(self, *args):
|
||||||
self.visColor = (255, 255, 255)
|
|
||||||
self.scale = 20
|
self.scale = 20
|
||||||
self.y = 0
|
self.y = 0
|
||||||
super().widget(*args)
|
super().widget(*args)
|
||||||
|
@ -30,34 +31,18 @@ class Component(Component):
|
||||||
self.page.comboBox_visLayout.addItem("Top")
|
self.page.comboBox_visLayout.addItem("Top")
|
||||||
self.page.comboBox_visLayout.setCurrentIndex(0)
|
self.page.comboBox_visLayout.setCurrentIndex(0)
|
||||||
|
|
||||||
self.page.lineEdit_visColor.setText('%s,%s,%s' % self.visColor)
|
self.page.lineEdit_visColor.setText('255,255,255')
|
||||||
self.page.pushButton_visColor.clicked.connect(lambda: self.pickColor())
|
|
||||||
btnStyle = "QPushButton { background-color : %s; outline: none; }" \
|
|
||||||
% QColor(*self.visColor).name()
|
|
||||||
self.page.pushButton_visColor.setStyleSheet(btnStyle)
|
|
||||||
|
|
||||||
self.trackWidgets({
|
self.trackWidgets({
|
||||||
|
'visColor': self.page.lineEdit_visColor,
|
||||||
'layout': self.page.comboBox_visLayout,
|
'layout': self.page.comboBox_visLayout,
|
||||||
'scale': self.page.spinBox_scale,
|
'scale': self.page.spinBox_scale,
|
||||||
'y': self.page.spinBox_y,
|
'y': self.page.spinBox_y,
|
||||||
})
|
}, colorWidgets={
|
||||||
|
'visColor': self.page.pushButton_visColor,
|
||||||
def update(self):
|
}, relativeWidgets=[
|
||||||
self.visColor = rgbFromString(self.page.lineEdit_visColor.text())
|
'y',
|
||||||
super().update()
|
])
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
def savePreset(self):
|
|
||||||
saveValueStore = super().savePreset()
|
|
||||||
saveValueStore['visColor'] = self.visColor
|
|
||||||
return saveValueStore
|
|
||||||
|
|
||||||
def previewRender(self):
|
def previewRender(self):
|
||||||
spectrum = numpy.fromfunction(
|
spectrum = numpy.fromfunction(
|
||||||
|
@ -96,13 +81,6 @@ class Component(Component):
|
||||||
self.spectrumArray[arrayNo],
|
self.spectrumArray[arrayNo],
|
||||||
self.visColor, self.layout)
|
self.visColor, self.layout)
|
||||||
|
|
||||||
def pickColor(self):
|
|
||||||
RGBstring, btnStyle = pickColor()
|
|
||||||
if not RGBstring:
|
|
||||||
return
|
|
||||||
self.page.lineEdit_visColor.setText(RGBstring)
|
|
||||||
self.page.pushButton_visColor.setStyleSheet(btnStyle)
|
|
||||||
|
|
||||||
def transformData(
|
def transformData(
|
||||||
self, i, completeAudioArray, sampleSize,
|
self, i, completeAudioArray, sampleSize,
|
||||||
smoothConstantDown, smoothConstantUp, lastSpectrum):
|
smoothConstantDown, smoothConstantUp, lastSpectrum):
|
||||||
|
|
|
@ -0,0 +1,284 @@
|
||||||
|
from PIL import Image
|
||||||
|
from PyQt5 import QtGui, QtCore, QtWidgets
|
||||||
|
import os
|
||||||
|
import math
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
|
||||||
|
from component import Component
|
||||||
|
from toolkit.frame import BlankFrame, scale
|
||||||
|
from toolkit import checkOutput, connectWidget
|
||||||
|
from toolkit.ffmpeg import (
|
||||||
|
openPipe, closePipe, getAudioDuration, FfmpegVideo, exampleSound
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Component(Component):
|
||||||
|
name = 'Spectrum'
|
||||||
|
version = '1.0.0'
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
if hasattr(self.parent, 'window'):
|
||||||
|
# update preview when audio file changes (if genericPreview is off)
|
||||||
|
self.parent.window.lineEdit_audioFile.textChanged.connect(
|
||||||
|
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', 'y',
|
||||||
|
])
|
||||||
|
for widget in self._trackedWidgets.values():
|
||||||
|
connectWidget(widget, lambda: self.changed())
|
||||||
|
|
||||||
|
def changed(self):
|
||||||
|
self.changedOptions = True
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.page.stackedWidget.setCurrentIndex(
|
||||||
|
self.page.comboBox_filterType.currentIndex())
|
||||||
|
super().update()
|
||||||
|
|
||||||
|
def previewRender(self):
|
||||||
|
changedSize = self.updateChunksize()
|
||||||
|
if not changedSize \
|
||||||
|
and not self.changedOptions \
|
||||||
|
and self.previewFrame is not None:
|
||||||
|
return self.previewFrame
|
||||||
|
|
||||||
|
frame = self.getPreviewFrame()
|
||||||
|
self.changedOptions = False
|
||||||
|
if not frame:
|
||||||
|
self.previewFrame = None
|
||||||
|
return BlankFrame(self.width, self.height)
|
||||||
|
else:
|
||||||
|
self.previewFrame = frame
|
||||||
|
return frame
|
||||||
|
|
||||||
|
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_=self.makeFfmpegFilter(),
|
||||||
|
width=w, height=h,
|
||||||
|
chunkSize=self.chunkSize,
|
||||||
|
frameRate=int(self.settings.value("outputFrameRate")),
|
||||||
|
parent=self.parent, component=self,
|
||||||
|
)
|
||||||
|
|
||||||
|
def frameRender(self, frameNo):
|
||||||
|
if FfmpegVideo.threadError is not None:
|
||||||
|
raise FfmpegVideo.threadError
|
||||||
|
return self.finalizeFrame(self.video.frame(frameNo))
|
||||||
|
|
||||||
|
def postFrameRender(self):
|
||||||
|
closePipe(self.video.pipe)
|
||||||
|
|
||||||
|
def getPreviewFrame(self):
|
||||||
|
genericPreview = self.settings.value("pref_genericPreview")
|
||||||
|
startPt = 0
|
||||||
|
if not genericPreview:
|
||||||
|
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',
|
||||||
|
os.path.join(self.core.wd, 'background.png')
|
||||||
|
if genericPreview else inputFile,
|
||||||
|
'-f', 'image2pipe',
|
||||||
|
'-pix_fmt', 'rgba',
|
||||||
|
]
|
||||||
|
command.extend(self.makeFfmpegFilter(preview=True, startPt=startPt))
|
||||||
|
command.extend([
|
||||||
|
'-an',
|
||||||
|
'-s:v', '%sx%s' % scale(self.scale, self.width, self.height, str),
|
||||||
|
'-codec:v', 'rawvideo', '-',
|
||||||
|
'-frames:v', '1',
|
||||||
|
])
|
||||||
|
logFilename = os.path.join(
|
||||||
|
self.core.dataDir, 'preview_%s.log' % str(self.compPos))
|
||||||
|
with open(logFilename, 'w') as log:
|
||||||
|
log.write(" ".join(command) + '\n\n')
|
||||||
|
with open(logFilename, 'a') as log:
|
||||||
|
pipe = openPipe(
|
||||||
|
command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
|
||||||
|
stderr=log, bufsize=10**8
|
||||||
|
)
|
||||||
|
byteFrame = pipe.stdout.read(self.chunkSize)
|
||||||
|
closePipe(pipe)
|
||||||
|
|
||||||
|
frame = self.finalizeFrame(byteFrame)
|
||||||
|
return frame
|
||||||
|
|
||||||
|
def makeFfmpegFilter(self, preview=False, startPt=0):
|
||||||
|
w, h = scale(self.scale, self.width, self.height, str)
|
||||||
|
color = self.page.comboBox_color.currentText().lower()
|
||||||
|
genericPreview = self.settings.value("pref_genericPreview")
|
||||||
|
|
||||||
|
if self.filterType == 0: # Spectrum
|
||||||
|
if self.amplitude == 0:
|
||||||
|
amplitude = 'sqrt'
|
||||||
|
elif self.amplitude == 1:
|
||||||
|
amplitude = 'cbrt'
|
||||||
|
elif self.amplitude == 2:
|
||||||
|
amplitude = '4thrt'
|
||||||
|
elif self.amplitude == 3:
|
||||||
|
amplitude = '5thrt'
|
||||||
|
elif self.amplitude == 4:
|
||||||
|
amplitude = 'lin'
|
||||||
|
elif self.amplitude == 5:
|
||||||
|
amplitude = 'log'
|
||||||
|
filter_ = (
|
||||||
|
'showspectrum=s=%sx%s:slide=scroll:win_func=%s:'
|
||||||
|
'color=%s:scale=%s,'
|
||||||
|
'colorkey=color=black:similarity=0.1:blend=0.5' % (
|
||||||
|
self.settings.value("outputWidth"),
|
||||||
|
self.settings.value("outputHeight"),
|
||||||
|
self.page.comboBox_window.currentText(),
|
||||||
|
color, amplitude,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif self.filterType == 1: # Histogram
|
||||||
|
if self.amplitude1 == 0:
|
||||||
|
amplitude = 'log'
|
||||||
|
elif self.amplitude1 == 1:
|
||||||
|
amplitude = 'lin'
|
||||||
|
if self.display == 0:
|
||||||
|
display = 'log'
|
||||||
|
elif self.display == 1:
|
||||||
|
display = 'sqrt'
|
||||||
|
elif self.display == 2:
|
||||||
|
display = 'cbrt'
|
||||||
|
elif self.display == 3:
|
||||||
|
display = 'lin'
|
||||||
|
elif self.display == 4:
|
||||||
|
display = 'rlog'
|
||||||
|
filter_ = (
|
||||||
|
'ahistogram=r=%s:s=%sx%s:dmode=separate:ascale=%s:scale=%s' % (
|
||||||
|
self.settings.value("outputFrameRate"),
|
||||||
|
self.settings.value("outputWidth"),
|
||||||
|
self.settings.value("outputHeight"),
|
||||||
|
amplitude, display
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif self.filterType == 2: # Vector Scope
|
||||||
|
if self.amplitude2 == 0:
|
||||||
|
amplitude = 'log'
|
||||||
|
elif self.amplitude2 == 1:
|
||||||
|
amplitude = 'sqrt'
|
||||||
|
elif self.amplitude2 == 2:
|
||||||
|
amplitude = 'cbrt'
|
||||||
|
elif self.amplitude2 == 3:
|
||||||
|
amplitude = 'lin'
|
||||||
|
m = self.page.comboBox_mode.currentText()
|
||||||
|
filter_ = (
|
||||||
|
'avectorscope=s=%sx%s:draw=%s:m=%s:scale=%s:zoom=%s' % (
|
||||||
|
self.settings.value("outputWidth"),
|
||||||
|
self.settings.value("outputHeight"),
|
||||||
|
'line'if self.draw else 'dot',
|
||||||
|
m, amplitude, str(self.zoom),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif self.filterType == 3: # Musical Scale
|
||||||
|
filter_ = (
|
||||||
|
'showcqt=r=%s:s=%sx%s:count=30:text=0:tc=%s,'
|
||||||
|
'colorkey=color=black:similarity=0.1:blend=0.5 ' % (
|
||||||
|
self.settings.value("outputFrameRate"),
|
||||||
|
self.settings.value("outputWidth"),
|
||||||
|
self.settings.value("outputHeight"),
|
||||||
|
str(self.tc),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif self.filterType == 4: # Phase
|
||||||
|
filter_ = (
|
||||||
|
'aphasemeter=r=%s:s=%sx%s:video=1 [atrash][vtmp1]; '
|
||||||
|
'[atrash] anullsink; '
|
||||||
|
'[vtmp1] colorkey=color=black:similarity=0.1:blend=0.5, '
|
||||||
|
'crop=in_w/8:in_h:(in_w/8)*7:0 '% (
|
||||||
|
self.settings.value("outputFrameRate"),
|
||||||
|
self.settings.value("outputWidth"),
|
||||||
|
self.settings.value("outputHeight"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return [
|
||||||
|
'-filter_complex',
|
||||||
|
'%s%s%s%s [v1]; '
|
||||||
|
'[v1] %sscale=%s:%s%s%s%s [v]' % (
|
||||||
|
exampleSound() if preview and genericPreview else '[0:a] ',
|
||||||
|
'compand=gain=4,' if self.compress else '',
|
||||||
|
'aformat=channel_layouts=mono,' if self.mono else '',
|
||||||
|
filter_,
|
||||||
|
'hflip, ' if self.mirror else '',
|
||||||
|
w, h,
|
||||||
|
', hue=h=%s:s=10' % str(self.hue) if self.hue > 0 else '',
|
||||||
|
', trim=start=%s:end=%s' % (
|
||||||
|
"{0:.3f}".format(startPt + 12),
|
||||||
|
"{0:.3f}".format(startPt + 12.5)
|
||||||
|
) if preview else '',
|
||||||
|
', convolution=-2 -1 0 -1 1 1 0 1 2:-2 -1 0 -1 1 1 0 1 2:-2 '
|
||||||
|
'-1 0 -1 1 1 0 1 2:-2 -1 0 -1 1 1 0 1 2'
|
||||||
|
if self.filterType == 3 else ''
|
||||||
|
),
|
||||||
|
'-map', '[v]',
|
||||||
|
]
|
||||||
|
|
||||||
|
def updateChunksize(self):
|
||||||
|
width, height = scale(self.scale, self.width, self.height, int)
|
||||||
|
oldChunkSize = int(self.chunkSize)
|
||||||
|
self.chunkSize = 4 * width * height
|
||||||
|
changed = self.chunkSize != oldChunkSize
|
||||||
|
return changed
|
||||||
|
|
||||||
|
def finalizeFrame(self, 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)
|
||||||
|
frame.paste(image, box=(self.x, self.y))
|
||||||
|
else:
|
||||||
|
frame = image
|
||||||
|
return frame
|
|
@ -0,0 +1,946 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>Form</class>
|
||||||
|
<widget class="QWidget" name="Form">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>586</width>
|
||||||
|
<height>197</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>197</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_5"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_8">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Type</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="comboBox_filterType">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Spectrum</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Histogram</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Vector Scope</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Musical Scale</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Phase</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_9">
|
||||||
|
<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_xTitleAlign">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>X</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="spinBox_x">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>80</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<number>-10000</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>10000</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_yTitleAlign">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Y</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="spinBox_y">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>80</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="baseSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<number>-10000</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>10000</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_10">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkBox_compress">
|
||||||
|
<property name="text">
|
||||||
|
<string>Compress</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkBox_mono">
|
||||||
|
<property name="text">
|
||||||
|
<string>Mono</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkBox_mirror">
|
||||||
|
<property name="text">
|
||||||
|
<string>Mirror</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_11">
|
||||||
|
<property name="text">
|
||||||
|
<string>Hue</string>
|
||||||
|
</property>
|
||||||
|
<property name="margin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="spinBox_hue">
|
||||||
|
<property name="suffix">
|
||||||
|
<string>° </string>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>359</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<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>
|
||||||
|
<widget class="QStackedWidget" name="stackedWidget">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="autoFillBackground">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::NoFrame</enum>
|
||||||
|
</property>
|
||||||
|
<property name="frameShadow">
|
||||||
|
<enum>QFrame::Plain</enum>
|
||||||
|
</property>
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="page">
|
||||||
|
<widget class="QWidget" name="verticalLayoutWidget">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>561</width>
|
||||||
|
<height>66</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<property name="sizeConstraint">
|
||||||
|
<enum>QLayout::SetMaximumSize</enum>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_9">
|
||||||
|
<property name="sizeConstraint">
|
||||||
|
<enum>QLayout::SetDefaultConstraint</enum>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_textColor">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>31</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Window</string>
|
||||||
|
</property>
|
||||||
|
<property name="margin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="comboBox_window">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>hann</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>gauss</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>tukey</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>dolph</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>cauchy</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>parzen</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>poisson</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>rect</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>bartlett</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>hanning</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>hamming</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>blackman</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>welch</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>flattop</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>bharris</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>bnuttall</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>lanczos</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Amplitude</string>
|
||||||
|
</property>
|
||||||
|
<property name="margin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="comboBox_amplitude0">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Square root</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Cubic root</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>4thrt</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>5thrt</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Linear</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Logarithmic</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_4">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::MinimumExpanding</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>10</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Color </string>
|
||||||
|
</property>
|
||||||
|
<property name="margin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="comboBox_color">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Channel</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Intensity</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Rainbow</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Moreland</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Nebulae</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Fire</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Fiery</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Fruit</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Cool</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_3">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::MinimumExpanding</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>10</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="page_2">
|
||||||
|
<widget class="QWidget" name="verticalLayoutWidget_2">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>-1</x>
|
||||||
|
<y>-1</y>
|
||||||
|
<width>561</width>
|
||||||
|
<height>31</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_6">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Display Scale</string>
|
||||||
|
</property>
|
||||||
|
<property name="margin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="comboBox_display">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Logarithmic</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Square root</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Cubic root</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Linear</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Reverse Log</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_5">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Amplitude</string>
|
||||||
|
</property>
|
||||||
|
<property name="margin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="comboBox_amplitude1">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Logarithmic</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Linear</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Minimum</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="page_3">
|
||||||
|
<widget class="QWidget" name="verticalLayoutWidget_3">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>-1</x>
|
||||||
|
<y>-1</y>
|
||||||
|
<width>585</width>
|
||||||
|
<height>64</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_9">
|
||||||
|
<property name="text">
|
||||||
|
<string>Mode</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="comboBox_mode">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>lissajous</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>lissajous_xy</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>polar</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_7">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Amplitude</string>
|
||||||
|
</property>
|
||||||
|
<property name="margin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="comboBox_amplitude2">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Linear</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Square root</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Cubic root</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Logarithmic</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_5">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_8">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Zoom</string>
|
||||||
|
</property>
|
||||||
|
<property name="margin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="spinBox_zoom">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkBox_draw">
|
||||||
|
<property name="text">
|
||||||
|
<string>Line</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_6">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="page_4">
|
||||||
|
<widget class="QWidget" name="verticalLayoutWidget_4">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>561</width>
|
||||||
|
<height>31</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_10">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Timeclamp</string>
|
||||||
|
</property>
|
||||||
|
<property name="margin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDoubleSpinBox" name="spinBox_tc">
|
||||||
|
<property name="suffix">
|
||||||
|
<string>s</string>
|
||||||
|
</property>
|
||||||
|
<property name="decimals">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<double>0.002000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<double>1.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<double>0.010000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<double>0.017000000000000</double>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_7">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="page_5">
|
||||||
|
<widget class="QWidget" name="verticalLayoutWidget_5">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>551</width>
|
||||||
|
<height>31</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_11"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>10</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -5,12 +5,11 @@ import os
|
||||||
|
|
||||||
from component import Component
|
from component import Component
|
||||||
from toolkit.frame import FramePainter
|
from toolkit.frame import FramePainter
|
||||||
from toolkit import rgbFromString, pickColor
|
|
||||||
|
|
||||||
|
|
||||||
class Component(Component):
|
class Component(Component):
|
||||||
name = 'Title Text'
|
name = 'Title Text'
|
||||||
version = '1.0.0'
|
version = '1.0.1'
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
super().__init__(*args)
|
super().__init__(*args)
|
||||||
|
@ -18,53 +17,47 @@ class Component(Component):
|
||||||
|
|
||||||
def widget(self, *args):
|
def widget(self, *args):
|
||||||
super().widget(*args)
|
super().widget(*args)
|
||||||
height = int(self.settings.value('outputHeight'))
|
|
||||||
width = int(self.settings.value('outputWidth'))
|
|
||||||
self.textColor = (255, 255, 255)
|
self.textColor = (255, 255, 255)
|
||||||
self.title = 'Text'
|
self.title = 'Text'
|
||||||
self.alignment = 1
|
self.alignment = 1
|
||||||
self.fontSize = height / 13.5
|
self.fontSize = self.height / 13.5
|
||||||
fm = QtGui.QFontMetrics(self.titleFont)
|
|
||||||
self.xPosition = width / 2 - fm.width(self.title)/2
|
|
||||||
self.yPosition = height / 2 * 1.036
|
|
||||||
|
|
||||||
self.page.comboBox_textAlign.addItem("Left")
|
self.page.comboBox_textAlign.addItem("Left")
|
||||||
self.page.comboBox_textAlign.addItem("Middle")
|
self.page.comboBox_textAlign.addItem("Middle")
|
||||||
self.page.comboBox_textAlign.addItem("Right")
|
self.page.comboBox_textAlign.addItem("Right")
|
||||||
|
self.page.comboBox_textAlign.setCurrentIndex(int(self.alignment))
|
||||||
|
|
||||||
self.page.lineEdit_textColor.setText('%s,%s,%s' % self.textColor)
|
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()
|
|
||||||
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_fontSize.setValue(int(self.fontSize))
|
||||||
self.page.spinBox_xTextAlign.setValue(int(self.xPosition))
|
self.page.lineEdit_title.setText(self.title)
|
||||||
self.page.spinBox_yTextAlign.setValue(int(self.yPosition))
|
|
||||||
|
|
||||||
|
self.page.pushButton_center.clicked.connect(self.centerXY)
|
||||||
self.page.fontComboBox_titleFont.currentFontChanged.connect(
|
self.page.fontComboBox_titleFont.currentFontChanged.connect(
|
||||||
self.update
|
self.update
|
||||||
)
|
)
|
||||||
|
|
||||||
self.trackWidgets({
|
self.trackWidgets({
|
||||||
|
'textColor': self.page.lineEdit_textColor,
|
||||||
'title': self.page.lineEdit_title,
|
'title': self.page.lineEdit_title,
|
||||||
'alignment': self.page.comboBox_textAlign,
|
'alignment': self.page.comboBox_textAlign,
|
||||||
'fontSize': self.page.spinBox_fontSize,
|
'fontSize': self.page.spinBox_fontSize,
|
||||||
'xPosition': self.page.spinBox_xTextAlign,
|
'xPosition': self.page.spinBox_xTextAlign,
|
||||||
'yPosition': self.page.spinBox_yTextAlign,
|
'yPosition': self.page.spinBox_yTextAlign,
|
||||||
})
|
}, colorWidgets={
|
||||||
|
'textColor': self.page.pushButton_textColor,
|
||||||
|
}, relativeWidgets=[
|
||||||
|
'xPosition', 'yPosition', 'fontSize',
|
||||||
|
])
|
||||||
|
self.centerXY()
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
self.titleFont = self.page.fontComboBox_titleFont.currentFont()
|
self.titleFont = self.page.fontComboBox_titleFont.currentFont()
|
||||||
self.textColor = rgbFromString(
|
|
||||||
self.page.lineEdit_textColor.text())
|
|
||||||
btnStyle = "QPushButton { background-color : %s; outline: none; }" \
|
|
||||||
% QColor(*self.textColor).name()
|
|
||||||
self.page.pushButton_textColor.setStyleSheet(btnStyle)
|
|
||||||
|
|
||||||
super().update()
|
super().update()
|
||||||
|
|
||||||
|
def centerXY(self):
|
||||||
|
self.setRelativeWidget('xPosition', 0.5)
|
||||||
|
self.setRelativeWidget('yPosition', 0.5)
|
||||||
|
|
||||||
def getXY(self):
|
def getXY(self):
|
||||||
'''Returns true x, y after considering alignment settings'''
|
'''Returns true x, y after considering alignment settings'''
|
||||||
fm = QtGui.QFontMetrics(self.titleFont)
|
fm = QtGui.QFontMetrics(self.titleFont)
|
||||||
|
@ -86,15 +79,10 @@ class Component(Component):
|
||||||
font = QFont()
|
font = QFont()
|
||||||
font.fromString(pr['titleFont'])
|
font.fromString(pr['titleFont'])
|
||||||
self.page.fontComboBox_titleFont.setCurrentFont(font)
|
self.page.fontComboBox_titleFont.setCurrentFont(font)
|
||||||
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):
|
def savePreset(self):
|
||||||
saveValueStore = super().savePreset()
|
saveValueStore = super().savePreset()
|
||||||
saveValueStore['titleFont'] = self.titleFont.toString()
|
saveValueStore['titleFont'] = self.titleFont.toString()
|
||||||
saveValueStore['textColor'] = self.textColor
|
|
||||||
return saveValueStore
|
return saveValueStore
|
||||||
|
|
||||||
def previewRender(self):
|
def previewRender(self):
|
||||||
|
@ -122,13 +110,6 @@ class Component(Component):
|
||||||
|
|
||||||
return image.finalize()
|
return image.finalize()
|
||||||
|
|
||||||
def pickColor(self):
|
|
||||||
RGBstring, btnStyle = pickColor()
|
|
||||||
if not RGBstring:
|
|
||||||
return
|
|
||||||
self.page.lineEdit_textColor.setText(RGBstring)
|
|
||||||
self.page.pushButton_textColor.setStyleSheet(btnStyle)
|
|
||||||
|
|
||||||
def commandHelp(self):
|
def commandHelp(self):
|
||||||
print('Enter a string to use as centred white text:')
|
print('Enter a string to use as centred white text:')
|
||||||
print(' "title=User Error"')
|
print(' "title=User Error"')
|
||||||
|
|
|
@ -19,6 +19,36 @@
|
||||||
<property name="leftMargin">
|
<property name="leftMargin">
|
||||||
<number>4</number>
|
<number>4</number>
|
||||||
</property>
|
</property>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_title">
|
||||||
|
<property name="text">
|
||||||
|
<string>Title</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="lineEdit_title">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Testing New GUI</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_8">
|
<layout class="QHBoxLayout" name="horizontalLayout_8">
|
||||||
<item>
|
<item>
|
||||||
|
@ -81,6 +111,9 @@
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QSpinBox" name="spinBox_fontSize">
|
<widget class="QSpinBox" name="spinBox_fontSize">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
<property name="maximum">
|
<property name="maximum">
|
||||||
<number>500</number>
|
<number>500</number>
|
||||||
</property>
|
</property>
|
||||||
|
@ -90,6 +123,55 @@
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_12">
|
<layout class="QHBoxLayout" name="horizontalLayout_12">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_textColor">
|
||||||
|
<property name="text">
|
||||||
|
<string>Text Color</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="lineEdit_textColor"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="pushButton_textColor">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>32</width>
|
||||||
|
<height>32</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="MaximumSize" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>32</width>
|
||||||
|
<height>32</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label_textLayout">
|
<widget class="QLabel" name="label_textLayout">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
|
@ -123,64 +205,9 @@
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label_textColor">
|
<widget class="QPushButton" name="pushButton_center">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Text Color</string>
|
<string>Center</string>
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="pushButton_textColor">
|
|
||||||
<property name="maximumSize">
|
|
||||||
<size>
|
|
||||||
<width>32</width>
|
|
||||||
<height>32</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
<property name="MaximumSize" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>32</width>
|
|
||||||
<height>32</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLineEdit" name="lineEdit_textColor"/>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
|
||||||
<property name="leftMargin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_title">
|
|
||||||
<property name="text">
|
|
||||||
<string>Title</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLineEdit" name="lineEdit_title">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>0</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Testing New GUI</string>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
|
@ -1,103 +1,13 @@
|
||||||
from PIL import Image, ImageDraw
|
from PIL import Image
|
||||||
from PyQt5 import QtGui, QtCore, QtWidgets
|
from PyQt5 import QtGui, QtCore, QtWidgets
|
||||||
import os
|
import os
|
||||||
import math
|
import math
|
||||||
import subprocess
|
import subprocess
|
||||||
import signal
|
|
||||||
import threading
|
|
||||||
from queue import PriorityQueue
|
|
||||||
|
|
||||||
from component import Component, ComponentError
|
from component import Component
|
||||||
from toolkit.frame import BlankFrame
|
from toolkit.frame import BlankFrame, scale
|
||||||
from toolkit.ffmpeg import testAudioStream
|
from toolkit.ffmpeg import openPipe, closePipe, testAudioStream, FfmpegVideo
|
||||||
from toolkit import openPipe, checkOutput
|
from toolkit import 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
|
|
||||||
|
|
||||||
|
|
||||||
class Component(Component):
|
class Component(Component):
|
||||||
|
@ -106,15 +16,14 @@ class Component(Component):
|
||||||
|
|
||||||
def widget(self, *args):
|
def widget(self, *args):
|
||||||
self.videoPath = ''
|
self.videoPath = ''
|
||||||
self.badVideo = False
|
|
||||||
self.badAudio = False
|
self.badAudio = False
|
||||||
self.x = 0
|
self.x = 0
|
||||||
self.y = 0
|
self.y = 0
|
||||||
self.loopVideo = False
|
self.loopVideo = False
|
||||||
super().widget(*args)
|
super().widget(*args)
|
||||||
|
self._image = BlankFrame(self.width, self.height)
|
||||||
self.page.pushButton_video.clicked.connect(self.pickVideo)
|
self.page.pushButton_video.clicked.connect(self.pickVideo)
|
||||||
self.trackWidgets(
|
self.trackWidgets({
|
||||||
{
|
|
||||||
'videoPath': self.page.lineEdit_video,
|
'videoPath': self.page.lineEdit_video,
|
||||||
'loopVideo': self.page.checkBox_loop,
|
'loopVideo': self.page.checkBox_loop,
|
||||||
'useAudio': self.page.checkBox_useAudio,
|
'useAudio': self.page.checkBox_useAudio,
|
||||||
|
@ -128,8 +37,9 @@ class Component(Component):
|
||||||
'loopVideo': 'loop',
|
'loopVideo': 'loop',
|
||||||
'xPosition': 'x',
|
'xPosition': 'x',
|
||||||
'yPosition': 'y',
|
'yPosition': 'y',
|
||||||
}
|
}, relativeWidgets=[
|
||||||
)
|
'xPosition', 'yPosition',
|
||||||
|
])
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
if self.page.checkBox_useAudio.isChecked():
|
if self.page.checkBox_useAudio.isChecked():
|
||||||
|
@ -157,8 +67,6 @@ class Component(Component):
|
||||||
|
|
||||||
if not self.videoPath:
|
if not self.videoPath:
|
||||||
self.lockError("There is no video selected.")
|
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):
|
elif not os.path.exists(self.videoPath):
|
||||||
self.lockError("The video selected does not exist!")
|
self.lockError("The video selected does not exist!")
|
||||||
elif os.path.realpath(self.videoPath) == os.path.realpath(outputFile):
|
elif os.path.realpath(self.videoPath) == os.path.realpath(outputFile):
|
||||||
|
@ -182,22 +90,21 @@ class Component(Component):
|
||||||
def preFrameRender(self, **kwargs):
|
def preFrameRender(self, **kwargs):
|
||||||
super().preFrameRender(**kwargs)
|
super().preFrameRender(**kwargs)
|
||||||
self.updateChunksize()
|
self.updateChunksize()
|
||||||
self.video = Video(
|
self.video = FfmpegVideo(
|
||||||
ffmpeg=self.core.FFMPEG_BIN, videoPath=self.videoPath,
|
inputPath=self.videoPath, filter_=self.makeFfmpegFilter(),
|
||||||
width=self.width, height=self.height, chunkSize=self.chunkSize,
|
width=self.width, height=self.height, chunkSize=self.chunkSize,
|
||||||
frameRate=int(self.settings.value("outputFrameRate")),
|
frameRate=int(self.settings.value("outputFrameRate")),
|
||||||
parent=self.parent, loopVideo=self.loopVideo,
|
parent=self.parent, loopVideo=self.loopVideo,
|
||||||
component=self, scale=self.scale
|
component=self
|
||||||
) if os.path.exists(self.videoPath) else None
|
) if os.path.exists(self.videoPath) else None
|
||||||
|
|
||||||
def frameRender(self, frameNo):
|
def frameRender(self, frameNo):
|
||||||
if Video.threadError is not None:
|
if FfmpegVideo.threadError is not None:
|
||||||
raise Video.threadError
|
raise FfmpegVideo.threadError
|
||||||
return self.video.frame(frameNo)
|
return self.finalizeFrame(self.video.frame(frameNo))
|
||||||
|
|
||||||
def postFrameRender(self):
|
def postFrameRender(self):
|
||||||
self.video.pipe.stdout.close()
|
closePipe(self.video.pipe)
|
||||||
self.video.pipe.send_signal(signal.SIGINT)
|
|
||||||
|
|
||||||
def pickVideo(self):
|
def pickVideo(self):
|
||||||
imgDir = self.settings.value("componentDir", os.path.expanduser("~"))
|
imgDir = self.settings.value("componentDir", os.path.expanduser("~"))
|
||||||
|
@ -220,23 +127,30 @@ class Component(Component):
|
||||||
'-i', self.videoPath,
|
'-i', self.videoPath,
|
||||||
'-f', 'image2pipe',
|
'-f', 'image2pipe',
|
||||||
'-pix_fmt', 'rgba',
|
'-pix_fmt', 'rgba',
|
||||||
'-filter_complex', '[0:v] scale=%s:%s' % scale(
|
|
||||||
self.scale, width, height, str),
|
|
||||||
'-vcodec', 'rawvideo', '-',
|
|
||||||
'-ss', '90',
|
|
||||||
'-vframes', '1',
|
|
||||||
]
|
]
|
||||||
|
command.extend(self.makeFfmpegFilter())
|
||||||
|
command.extend([
|
||||||
|
'-codec:v', 'rawvideo', '-',
|
||||||
|
'-ss', '90',
|
||||||
|
'-frames:v', '1',
|
||||||
|
])
|
||||||
pipe = openPipe(
|
pipe = openPipe(
|
||||||
command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
|
command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.DEVNULL, bufsize=10**8
|
stderr=subprocess.DEVNULL, bufsize=10**8
|
||||||
)
|
)
|
||||||
byteFrame = pipe.stdout.read(self.chunkSize)
|
byteFrame = pipe.stdout.read(self.chunkSize)
|
||||||
pipe.stdout.close()
|
closePipe(pipe)
|
||||||
pipe.send_signal(signal.SIGINT)
|
|
||||||
|
|
||||||
frame = finalizeFrame(self, byteFrame, width, height)
|
frame = self.finalizeFrame(byteFrame)
|
||||||
return frame
|
return frame
|
||||||
|
|
||||||
|
def makeFfmpegFilter(self):
|
||||||
|
return [
|
||||||
|
'-filter_complex',
|
||||||
|
'[0:v] scale=%s:%s' % scale(
|
||||||
|
self.scale, self.width, self.height, str),
|
||||||
|
]
|
||||||
|
|
||||||
def updateChunksize(self):
|
def updateChunksize(self):
|
||||||
if self.scale != 100 and not self.distort:
|
if self.scale != 100 and not self.distort:
|
||||||
width, height = scale(self.scale, self.width, self.height, int)
|
width, height = scale(self.scale, self.width, self.height, int)
|
||||||
|
@ -268,44 +182,27 @@ class Component(Component):
|
||||||
print('Load a video:\n path=/filepath/to/video.mp4')
|
print('Load a video:\n path=/filepath/to/video.mp4')
|
||||||
print('Using audio:\n path=/filepath/to/video.mp4 audio')
|
print('Using audio:\n path=/filepath/to/video.mp4 audio')
|
||||||
|
|
||||||
|
def finalizeFrame(self, imageData):
|
||||||
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:
|
try:
|
||||||
if self.distort:
|
if self.distort:
|
||||||
image = Image.frombytes(
|
image = Image.frombytes(
|
||||||
'RGBA',
|
'RGBA',
|
||||||
(width, height),
|
(self.width, self.height),
|
||||||
imageData)
|
imageData)
|
||||||
else:
|
else:
|
||||||
image = Image.frombytes(
|
image = Image.frombytes(
|
||||||
'RGBA',
|
'RGBA',
|
||||||
scale(self.scale, width, height, int),
|
scale(self.scale, self.width, self.height, int),
|
||||||
imageData)
|
imageData)
|
||||||
|
self._image = image
|
||||||
except ValueError:
|
except ValueError:
|
||||||
print(
|
# use last good frame
|
||||||
'### BAD VIDEO SELECTED ###\n'
|
image = self._image
|
||||||
'Video will not export with these settings'
|
|
||||||
)
|
|
||||||
self.badVideo = True
|
|
||||||
return BlankFrame(width, height)
|
|
||||||
|
|
||||||
if self.scale != 100 \
|
if self.scale != 100 \
|
||||||
or self.xPosition != 0 or self.yPosition != 0:
|
or self.xPosition != 0 or self.yPosition != 0:
|
||||||
frame = BlankFrame(width, height)
|
frame = BlankFrame(self.width, self.height)
|
||||||
frame.paste(image, box=(self.xPosition, self.yPosition))
|
frame.paste(image, box=(self.xPosition, self.yPosition))
|
||||||
else:
|
else:
|
||||||
frame = image
|
frame = image
|
||||||
self.badVideo = False
|
|
||||||
return frame
|
return frame
|
||||||
|
|
|
@ -0,0 +1,194 @@
|
||||||
|
from PIL import Image
|
||||||
|
from PyQt5 import QtGui, QtCore, QtWidgets
|
||||||
|
from PyQt5.QtGui import QColor
|
||||||
|
import os
|
||||||
|
import math
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from component import Component
|
||||||
|
from toolkit.frame import BlankFrame, scale
|
||||||
|
from toolkit import checkOutput
|
||||||
|
from toolkit.ffmpeg import (
|
||||||
|
openPipe, closePipe, getAudioDuration, FfmpegVideo, exampleSound
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Component(Component):
|
||||||
|
name = 'Waveform'
|
||||||
|
version = '1.0.0'
|
||||||
|
|
||||||
|
def widget(self, *args):
|
||||||
|
super().widget(*args)
|
||||||
|
self._image = BlankFrame(self.width, self.height)
|
||||||
|
|
||||||
|
self.page.lineEdit_color.setText('255,255,255')
|
||||||
|
|
||||||
|
if hasattr(self.parent, 'window'):
|
||||||
|
self.parent.window.lineEdit_audioFile.textChanged.connect(
|
||||||
|
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', 'y',
|
||||||
|
])
|
||||||
|
|
||||||
|
def previewRender(self):
|
||||||
|
self.updateChunksize()
|
||||||
|
frame = self.getPreviewFrame(self.width, self.height)
|
||||||
|
if not frame:
|
||||||
|
return BlankFrame(self.width, self.height)
|
||||||
|
else:
|
||||||
|
return frame
|
||||||
|
|
||||||
|
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_=self.makeFfmpegFilter(),
|
||||||
|
width=w, height=h,
|
||||||
|
chunkSize=self.chunkSize,
|
||||||
|
frameRate=int(self.settings.value("outputFrameRate")),
|
||||||
|
parent=self.parent, component=self, debug=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def frameRender(self, frameNo):
|
||||||
|
if FfmpegVideo.threadError is not None:
|
||||||
|
raise FfmpegVideo.threadError
|
||||||
|
return self.finalizeFrame(self.video.frame(frameNo))
|
||||||
|
|
||||||
|
def postFrameRender(self):
|
||||||
|
closePipe(self.video.pipe)
|
||||||
|
|
||||||
|
def getPreviewFrame(self, width, height):
|
||||||
|
genericPreview = self.settings.value("pref_genericPreview")
|
||||||
|
startPt = 0
|
||||||
|
if not genericPreview:
|
||||||
|
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
|
||||||
|
if startPt + 3 > duration:
|
||||||
|
startPt += startPt - 3
|
||||||
|
|
||||||
|
command = [
|
||||||
|
self.core.FFMPEG_BIN,
|
||||||
|
'-thread_queue_size', '512',
|
||||||
|
'-r', self.settings.value("outputFrameRate"),
|
||||||
|
'-ss', "{0:.3f}".format(startPt),
|
||||||
|
'-i',
|
||||||
|
os.path.join(self.core.wd, 'background.png')
|
||||||
|
if genericPreview else inputFile,
|
||||||
|
'-f', 'image2pipe',
|
||||||
|
'-pix_fmt', 'rgba',
|
||||||
|
]
|
||||||
|
command.extend(self.makeFfmpegFilter(preview=True, startPt=startPt))
|
||||||
|
command.extend([
|
||||||
|
'-an',
|
||||||
|
'-s:v', '%sx%s' % scale(self.scale, self.width, self.height, str),
|
||||||
|
'-codec:v', 'rawvideo', '-',
|
||||||
|
'-frames:v', '1',
|
||||||
|
])
|
||||||
|
pipe = openPipe(
|
||||||
|
command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.DEVNULL, bufsize=10**8
|
||||||
|
)
|
||||||
|
byteFrame = pipe.stdout.read(self.chunkSize)
|
||||||
|
closePipe(pipe)
|
||||||
|
|
||||||
|
frame = self.finalizeFrame(byteFrame)
|
||||||
|
return frame
|
||||||
|
|
||||||
|
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)
|
||||||
|
genericPreview = self.settings.value("pref_genericPreview")
|
||||||
|
if self.mode < 3:
|
||||||
|
filter_ = 'showwaves=r=%s:s=%sx%s:mode=%s:colors=%s@%s:scale=%s' % (
|
||||||
|
self.settings.value("outputFrameRate"),
|
||||||
|
self.settings.value("outputWidth"),
|
||||||
|
self.settings.value("outputHeight"),
|
||||||
|
self.page.comboBox_mode.currentText().lower()
|
||||||
|
if self.mode != 3 else 'p2p',
|
||||||
|
hexcolor, opacity, amplitude,
|
||||||
|
)
|
||||||
|
elif self.mode > 2:
|
||||||
|
filter_ = (
|
||||||
|
'showfreqs=s=%sx%s:mode=%s:colors=%s@%s'
|
||||||
|
':ascale=%s:fscale=%s' % (
|
||||||
|
self.settings.value("outputWidth"),
|
||||||
|
self.settings.value("outputHeight"),
|
||||||
|
'line' if self.mode == 4 else 'bar',
|
||||||
|
hexcolor, opacity, amplitude,
|
||||||
|
'log' if self.mono else 'lin'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return [
|
||||||
|
'-filter_complex',
|
||||||
|
'%s%s%s'
|
||||||
|
'%s%s%s [v1]; '
|
||||||
|
'[v1] scale=%s:%s%s [v]' % (
|
||||||
|
exampleSound() if preview and genericPreview else '[0:a] ',
|
||||||
|
'compand=gain=4,' if self.compress else '',
|
||||||
|
'aformat=channel_layouts=mono,'
|
||||||
|
if self.mono and self.mode < 3 else '',
|
||||||
|
filter_,
|
||||||
|
', 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 + 3)
|
||||||
|
if preview else '',
|
||||||
|
),
|
||||||
|
'-map', '[v]',
|
||||||
|
]
|
||||||
|
|
||||||
|
def updateChunksize(self):
|
||||||
|
width, height = scale(self.scale, self.width, self.height, int)
|
||||||
|
self.chunkSize = 4 * width * height
|
||||||
|
|
||||||
|
def finalizeFrame(self, 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)
|
||||||
|
frame.paste(image, box=(self.x, self.y))
|
||||||
|
else:
|
||||||
|
frame = image
|
||||||
|
return frame
|
|
@ -0,0 +1,383 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>Form</class>
|
||||||
|
<widget class="QWidget" name="Form">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>586</width>
|
||||||
|
<height>197</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>197</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_8">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_textColor">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>31</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Mode</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="comboBox_mode">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Cline</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Line</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Point</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Frequency Bar</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Frequency Line</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_9">
|
||||||
|
<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_xTitleAlign">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>X</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="spinBox_x">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>80</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<number>-10000</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>10000</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_yTitleAlign">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Y</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="spinBox_y">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>80</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="baseSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<number>-10000</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>10000</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_9">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Color</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="lineEdit_color">
|
||||||
|
<property name="inputMethodHints">
|
||||||
|
<set>Qt::ImhNone</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="pushButton_color">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>32</width>
|
||||||
|
<height>32</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="default">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="flat">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="text">
|
||||||
|
<string>Opacity</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="spinBox_opacity">
|
||||||
|
<property name="buttonSymbols">
|
||||||
|
<enum>QAbstractSpinBox::UpDownArrows</enum>
|
||||||
|
</property>
|
||||||
|
<property name="suffix">
|
||||||
|
<string>%</string>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>100</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>100</number>
|
||||||
|
</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>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_10">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkBox_compress">
|
||||||
|
<property name="text">
|
||||||
|
<string>Compress</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkBox_mono">
|
||||||
|
<property name="text">
|
||||||
|
<string>Mono</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkBox_mirror">
|
||||||
|
<property name="text">
|
||||||
|
<string>Mirror</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Amplitude</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="comboBox_amplitude">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Linear</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Logarithmic</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Square root</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Cubic root</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -161,7 +161,7 @@ class Core:
|
||||||
for widget, value in data['WindowFields']:
|
for widget, value in data['WindowFields']:
|
||||||
widget = eval('loader.window.%s' % widget)
|
widget = eval('loader.window.%s' % widget)
|
||||||
widget.blockSignals(True)
|
widget.blockSignals(True)
|
||||||
widget.setText(value)
|
toolkit.setWidgetValue(widget, value)
|
||||||
widget.blockSignals(False)
|
widget.blockSignals(False)
|
||||||
|
|
||||||
for key, value in data['Settings']:
|
for key, value in data['Settings']:
|
||||||
|
@ -451,8 +451,8 @@ class Core:
|
||||||
'1280x720',
|
'1280x720',
|
||||||
'854x480',
|
'854x480',
|
||||||
],
|
],
|
||||||
'windowHasFocus': False,
|
|
||||||
'FFMPEG_BIN': findFfmpeg(),
|
'FFMPEG_BIN': findFfmpeg(),
|
||||||
|
'windowHasFocus': False,
|
||||||
'canceled': False,
|
'canceled': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -492,7 +492,7 @@ class Core:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def loadDefaultSettings(cls):
|
def loadDefaultSettings(cls):
|
||||||
defaultSettings = {
|
cls.defaultSettings = {
|
||||||
"outputWidth": 1280,
|
"outputWidth": 1280,
|
||||||
"outputHeight": 720,
|
"outputHeight": 720,
|
||||||
"outputFrameRate": 30,
|
"outputFrameRate": 30,
|
||||||
|
@ -506,9 +506,10 @@ class Core:
|
||||||
"outputContainer": "MP4",
|
"outputContainer": "MP4",
|
||||||
"projectDir": os.path.join(cls.dataDir, 'projects'),
|
"projectDir": os.path.join(cls.dataDir, 'projects'),
|
||||||
"pref_insertCompAtTop": True,
|
"pref_insertCompAtTop": True,
|
||||||
|
"pref_genericPreview": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
for parm, value in defaultSettings.items():
|
for parm, value in cls.defaultSettings.items():
|
||||||
if cls.settings.value(parm) is None:
|
if cls.settings.value(parm) is None:
|
||||||
cls.settings.setValue(parm, value)
|
cls.settings.setValue(parm, value)
|
||||||
|
|
||||||
|
|
|
@ -581,7 +581,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
self.showMessage(
|
self.showMessage(
|
||||||
msg=msg,
|
msg=msg,
|
||||||
detail=detail,
|
detail=detail,
|
||||||
icon='Warning',
|
icon='Critical',
|
||||||
)
|
)
|
||||||
|
|
||||||
def changeEncodingStatus(self, status):
|
def changeEncodingStatus(self, status):
|
||||||
|
@ -644,9 +644,12 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
def updateResolution(self):
|
def updateResolution(self):
|
||||||
resIndex = int(self.window.comboBox_resolution.currentIndex())
|
resIndex = int(self.window.comboBox_resolution.currentIndex())
|
||||||
res = Core.resolutions[resIndex].split('x')
|
res = Core.resolutions[resIndex].split('x')
|
||||||
|
changed = res[0] != self.settings.value("outputWidth")
|
||||||
self.settings.setValue('outputWidth', res[0])
|
self.settings.setValue('outputWidth', res[0])
|
||||||
self.settings.setValue('outputHeight', res[1])
|
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):
|
def drawPreview(self, force=False, **kwargs):
|
||||||
'''Use autosave keyword arg to force saving or not saving if needed'''
|
'''Use autosave keyword arg to force saving or not saving if needed'''
|
||||||
|
@ -791,6 +794,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
field.blockSignals(True)
|
field.blockSignals(True)
|
||||||
field.setText('')
|
field.setText('')
|
||||||
field.blockSignals(False)
|
field.blockSignals(False)
|
||||||
|
self.progressBarUpdated(0)
|
||||||
|
self.progressBarSetText('')
|
||||||
|
|
||||||
@disableWhenEncoding
|
@disableWhenEncoding
|
||||||
def createNewProject(self, prompt=True):
|
def createNewProject(self, prompt=True):
|
||||||
|
|
|
@ -59,7 +59,9 @@ class Worker(QtCore.QObject):
|
||||||
components = nextPreviewInformation["components"]
|
components = nextPreviewInformation["components"]
|
||||||
for component in reversed(components):
|
for component in reversed(components):
|
||||||
try:
|
try:
|
||||||
|
component.lockSize(width, height)
|
||||||
newFrame = component.previewRender()
|
newFrame = component.previewRender()
|
||||||
|
component.unlockSize()
|
||||||
frame = Image.alpha_composite(
|
frame = Image.alpha_composite(
|
||||||
frame, newFrame
|
frame, newFrame
|
||||||
)
|
)
|
||||||
|
|
|
@ -34,30 +34,28 @@ def appendUppercase(lst):
|
||||||
lst.append(form.upper())
|
lst.append(form.upper())
|
||||||
return lst
|
return lst
|
||||||
|
|
||||||
|
def pipeWrapper(func):
|
||||||
def hideCmdWin(func):
|
'''A decorator to insert proper kwargs into Popen objects.'''
|
||||||
''' Stops CMD window from appearing on Windows.
|
def pipeWrapper(commandList, **kwargs):
|
||||||
Adapted from here: http://code.activestate.com/recipes/409002/
|
|
||||||
'''
|
|
||||||
def decorator(commandList, **kwargs):
|
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
|
# Stop CMD window from appearing on Windows
|
||||||
startupinfo = subprocess.STARTUPINFO()
|
startupinfo = subprocess.STARTUPINFO()
|
||||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||||
kwargs['startupinfo'] = startupinfo
|
kwargs['startupinfo'] = startupinfo
|
||||||
|
|
||||||
|
if 'bufsize' not in kwargs:
|
||||||
|
kwargs['bufsize'] = 10**8
|
||||||
|
if 'stdin' not in kwargs:
|
||||||
|
kwargs['stdin'] = subprocess.DEVNULL
|
||||||
return func(commandList, **kwargs)
|
return func(commandList, **kwargs)
|
||||||
return decorator
|
return pipeWrapper
|
||||||
|
|
||||||
|
|
||||||
@hideCmdWin
|
@pipeWrapper
|
||||||
def checkOutput(commandList, **kwargs):
|
def checkOutput(commandList, **kwargs):
|
||||||
return subprocess.check_output(commandList, **kwargs)
|
return subprocess.check_output(commandList, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@hideCmdWin
|
|
||||||
def openPipe(commandList, **kwargs):
|
|
||||||
return subprocess.Popen(commandList, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def disableWhenEncoding(func):
|
def disableWhenEncoding(func):
|
||||||
def decorator(self, *args, **kwargs):
|
def decorator(self, *args, **kwargs):
|
||||||
if self.encoding:
|
if self.encoding:
|
||||||
|
@ -76,25 +74,6 @@ def disableWhenOpeningProject(func):
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def pickColor():
|
|
||||||
'''
|
|
||||||
Use color picker to get color input from the user,
|
|
||||||
and return this as an RGB string and QPushButton stylesheet.
|
|
||||||
In a subclass apply stylesheet to any color selection widgets
|
|
||||||
'''
|
|
||||||
dialog = QtWidgets.QColorDialog()
|
|
||||||
dialog.setOption(QtWidgets.QColorDialog.ShowAlphaChannel, True)
|
|
||||||
color = dialog.getColor()
|
|
||||||
if color.isValid():
|
|
||||||
RGBstring = '%s,%s,%s' % (
|
|
||||||
str(color.red()), str(color.green()), str(color.blue()))
|
|
||||||
btnStyle = "QPushButton{background-color: %s; outline: none;}" \
|
|
||||||
% color.name()
|
|
||||||
return RGBstring, btnStyle
|
|
||||||
else:
|
|
||||||
return None, None
|
|
||||||
|
|
||||||
|
|
||||||
def rgbFromString(string):
|
def rgbFromString(string):
|
||||||
'''Turns an RGB string like "255, 255, 255" into a tuple'''
|
'''Turns an RGB string like "255, 255, 255" into a tuple'''
|
||||||
try:
|
try:
|
||||||
|
@ -115,3 +94,46 @@ def formatTraceback(tb=None):
|
||||||
import sys
|
import sys
|
||||||
tb = sys.exc_info()[2]
|
tb = sys.exc_info()[2]
|
||||||
return 'Traceback:\n%s' % "\n".join(traceback.format_tb(tb))
|
return 'Traceback:\n%s' % "\n".join(traceback.format_tb(tb))
|
||||||
|
|
||||||
|
|
||||||
|
def connectWidget(widget, func):
|
||||||
|
if type(widget) == QtWidgets.QLineEdit:
|
||||||
|
widget.textChanged.connect(func)
|
||||||
|
elif type(widget) == QtWidgets.QSpinBox \
|
||||||
|
or type(widget) == QtWidgets.QDoubleSpinBox:
|
||||||
|
widget.valueChanged.connect(func)
|
||||||
|
elif type(widget) == QtWidgets.QCheckBox:
|
||||||
|
widget.stateChanged.connect(func)
|
||||||
|
elif type(widget) == QtWidgets.QComboBox:
|
||||||
|
widget.currentIndexChanged.connect(func)
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def setWidgetValue(widget, val):
|
||||||
|
'''Generic setValue method for use with any typical QtWidget'''
|
||||||
|
if type(widget) == QtWidgets.QLineEdit:
|
||||||
|
widget.setText(val)
|
||||||
|
elif type(widget) == QtWidgets.QSpinBox \
|
||||||
|
or type(widget) == QtWidgets.QDoubleSpinBox:
|
||||||
|
widget.setValue(val)
|
||||||
|
elif type(widget) == QtWidgets.QCheckBox:
|
||||||
|
widget.setChecked(val)
|
||||||
|
elif type(widget) == QtWidgets.QComboBox:
|
||||||
|
widget.setCurrentIndex(val)
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def getWidgetValue(widget):
|
||||||
|
if type(widget) == QtWidgets.QLineEdit:
|
||||||
|
return widget.text()
|
||||||
|
elif type(widget) == QtWidgets.QSpinBox \
|
||||||
|
or type(widget) == QtWidgets.QDoubleSpinBox:
|
||||||
|
return widget.value()
|
||||||
|
elif type(widget) == QtWidgets.QCheckBox:
|
||||||
|
return widget.isChecked()
|
||||||
|
elif type(widget) == QtWidgets.QComboBox:
|
||||||
|
return widget.currentIndex()
|
||||||
|
|
|
@ -5,9 +5,133 @@ import numpy
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import threading
|
||||||
|
import signal
|
||||||
|
from queue import PriorityQueue
|
||||||
|
|
||||||
import core
|
import core
|
||||||
from toolkit.common import checkOutput, openPipe
|
from toolkit.common import checkOutput, pipeWrapper
|
||||||
|
from component import ComponentError
|
||||||
|
|
||||||
|
|
||||||
|
class FfmpegVideo:
|
||||||
|
'''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 = [
|
||||||
|
'inputPath',
|
||||||
|
'filter_',
|
||||||
|
'width',
|
||||||
|
'height',
|
||||||
|
'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'
|
||||||
|
self.map_ = None
|
||||||
|
|
||||||
|
if 'loopVideo' in kwargs and kwargs['loopVideo']:
|
||||||
|
self.loopValue = '-1'
|
||||||
|
else:
|
||||||
|
self.loopValue = '0'
|
||||||
|
if 'filter_' in kwargs:
|
||||||
|
if kwargs['filter_'][0] != '-filter_complex':
|
||||||
|
kwargs['filter_'].insert(0, '-filter_complex')
|
||||||
|
else:
|
||||||
|
kwargs['filter_'] = None
|
||||||
|
|
||||||
|
self.command = [
|
||||||
|
core.Core.FFMPEG_BIN,
|
||||||
|
'-thread_queue_size', '512',
|
||||||
|
'-r', str(self.frameRate),
|
||||||
|
'-stream_loop', self.loopValue,
|
||||||
|
'-i', self.inputPath,
|
||||||
|
'-f', 'image2pipe',
|
||||||
|
'-pix_fmt', 'rgba',
|
||||||
|
]
|
||||||
|
if type(kwargs['filter_']) is list:
|
||||||
|
self.command.extend(
|
||||||
|
kwargs['filter_']
|
||||||
|
)
|
||||||
|
self.command.extend([
|
||||||
|
'-codec:v', 'rawvideo', '-',
|
||||||
|
])
|
||||||
|
|
||||||
|
self.frameBuffer = PriorityQueue()
|
||||||
|
self.frameBuffer.maxsize = self.frameRate
|
||||||
|
self.finishedFrames = {}
|
||||||
|
|
||||||
|
self.thread = threading.Thread(
|
||||||
|
target=self.fillBuffer,
|
||||||
|
name='FFmpeg 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 image
|
||||||
|
|
||||||
|
i, image = self.frameBuffer.get()
|
||||||
|
self.finishedFrames[i] = image
|
||||||
|
self.frameBuffer.task_done()
|
||||||
|
|
||||||
|
def fillBuffer(self):
|
||||||
|
logFilename = os.path.join(
|
||||||
|
core.Core.dataDir, 'extra_%s.log' % str(self.component.compPos))
|
||||||
|
with open(logFilename, 'w') as log:
|
||||||
|
log.write(" ".join(self.command) + '\n\n')
|
||||||
|
with open(logFilename, 'a') as log:
|
||||||
|
self.pipe = openPipe(
|
||||||
|
self.command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
|
||||||
|
stderr=log, 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:
|
||||||
|
FfmpegVideo.threadError = ComponentError(
|
||||||
|
self.component, 'video',
|
||||||
|
"Video seemed playable but wasn't."
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.currentFrame = self.pipe.stdout.read(self.chunkSize)
|
||||||
|
except ValueError:
|
||||||
|
FfmpegVideo.threadError = ComponentError(
|
||||||
|
self.component, 'video')
|
||||||
|
|
||||||
|
if len(self.currentFrame) != 0:
|
||||||
|
self.frameBuffer.put((self.frameNo, self.currentFrame))
|
||||||
|
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():
|
def findFfmpeg():
|
||||||
|
@ -248,7 +372,12 @@ def getAudioDuration(filename):
|
||||||
except subprocess.CalledProcessError as ex:
|
except subprocess.CalledProcessError as ex:
|
||||||
fileInfo = ex.output
|
fileInfo = ex.output
|
||||||
|
|
||||||
|
try:
|
||||||
info = fileInfo.decode("utf-8").split('\n')
|
info = fileInfo.decode("utf-8").split('\n')
|
||||||
|
except UnicodeDecodeError as e:
|
||||||
|
print('Unicode error:', str(e))
|
||||||
|
return False
|
||||||
|
|
||||||
for line in info:
|
for line in info:
|
||||||
if 'Duration' in line:
|
if 'Duration' in line:
|
||||||
d = line.split(',')[0]
|
d = line.split(',')[0]
|
||||||
|
@ -321,3 +450,10 @@ def readAudioFile(filename, videoWorker):
|
||||||
completeAudioArray = completeAudioArrayCopy
|
completeAudioArray = completeAudioArrayCopy
|
||||||
|
|
||||||
return (completeAudioArray, duration)
|
return (completeAudioArray, duration)
|
||||||
|
|
||||||
|
|
||||||
|
def exampleSound():
|
||||||
|
return (
|
||||||
|
'aevalsrc=tan(random(1)*PI*t)*sin(random(0)*2*PI*t),'
|
||||||
|
'apulsator=offset_l=0.5:offset_r=0.5,'
|
||||||
|
)
|
||||||
|
|
|
@ -6,6 +6,7 @@ from PIL import Image
|
||||||
from PIL.ImageQt import ImageQt
|
from PIL.ImageQt import ImageQt
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import math
|
||||||
|
|
||||||
import core
|
import core
|
||||||
|
|
||||||
|
@ -41,6 +42,17 @@ class PaintColor(QtGui.QColor):
|
||||||
super().__init__(b, g, r, a)
|
super().__init__(b, g, r, a)
|
||||||
|
|
||||||
|
|
||||||
|
def scale(scalePercent, width, height, returntype=None):
|
||||||
|
width = (float(width) / 100.0) * float(scalePercent)
|
||||||
|
height = (float(height) / 100.0) * float(scalePercent)
|
||||||
|
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):
|
def defaultSize(framefunc):
|
||||||
'''Makes width/height arguments optional'''
|
'''Makes width/height arguments optional'''
|
||||||
def decorator(*args):
|
def decorator(*args):
|
||||||
|
|
|
@ -19,9 +19,11 @@ import time
|
||||||
import signal
|
import signal
|
||||||
|
|
||||||
from component import ComponentError
|
from component import ComponentError
|
||||||
from toolkit import openPipe
|
|
||||||
from toolkit.ffmpeg import readAudioFile, createFfmpegCommand
|
|
||||||
from toolkit.frame import Checkerboard
|
from toolkit.frame import Checkerboard
|
||||||
|
from toolkit.ffmpeg import (
|
||||||
|
openPipe, readAudioFile,
|
||||||
|
getAudioDuration, createFfmpegCommand
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Worker(QtCore.QObject):
|
class Worker(QtCore.QObject):
|
||||||
|
@ -132,7 +134,10 @@ class Worker(QtCore.QObject):
|
||||||
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
|
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
|
||||||
# READ AUDIO, INITIALIZE COMPONENTS, OPEN A PIPE TO FFMPEG
|
# READ AUDIO, INITIALIZE COMPONENTS, OPEN A PIPE TO FFMPEG
|
||||||
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
|
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
|
||||||
|
if any([
|
||||||
|
True if 'pcm' in comp.properties() else False
|
||||||
|
for comp in self.components
|
||||||
|
]):
|
||||||
self.progressBarSetText.emit("Loading audio file...")
|
self.progressBarSetText.emit("Loading audio file...")
|
||||||
audioFileTraits = readAudioFile(
|
audioFileTraits = readAudioFile(
|
||||||
self.inputFile, self
|
self.inputFile, self
|
||||||
|
@ -141,6 +146,12 @@ class Worker(QtCore.QObject):
|
||||||
self.cancelExport()
|
self.cancelExport()
|
||||||
return
|
return
|
||||||
self.completeAudioArray, duration = audioFileTraits
|
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.progressBarUpdate.emit(0)
|
||||||
self.progressBarSetText.emit("Starting components...")
|
self.progressBarSetText.emit("Starting components...")
|
||||||
|
@ -153,7 +164,7 @@ class Worker(QtCore.QObject):
|
||||||
for compNo, comp in enumerate(reversed(self.components)):
|
for compNo, comp in enumerate(reversed(self.components)):
|
||||||
try:
|
try:
|
||||||
comp.preFrameRender(
|
comp.preFrameRender(
|
||||||
worker=self,
|
audioFile=self.inputFile,
|
||||||
completeAudioArray=self.completeAudioArray,
|
completeAudioArray=self.completeAudioArray,
|
||||||
sampleSize=self.sampleSize,
|
sampleSize=self.sampleSize,
|
||||||
progressBarUpdate=self.progressBarUpdate,
|
progressBarUpdate=self.progressBarUpdate,
|
||||||
|
@ -284,7 +295,10 @@ class Worker(QtCore.QObject):
|
||||||
|
|
||||||
numpy.seterr(all='print')
|
numpy.seterr(all='print')
|
||||||
|
|
||||||
|
try:
|
||||||
self.out_pipe.stdin.close()
|
self.out_pipe.stdin.close()
|
||||||
|
except BrokenPipeError:
|
||||||
|
print('Broken pipe to ffmpeg!')
|
||||||
if self.out_pipe.stderr is not None:
|
if self.out_pipe.stderr is not None:
|
||||||
print(self.out_pipe.stderr.read())
|
print(self.out_pipe.stderr.read())
|
||||||
self.out_pipe.stderr.close()
|
self.out_pipe.stderr.close()
|
||||||
|
|
Reference in New Issue