From c1457b6dad4640b17679dd802e372bd46a13d2a5 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 29 Jul 2017 13:08:28 -0400
Subject: starting work on Waveform component
split Video class out of Video component for reuse in Waveform
---
src/toolkit/common.py | 37 ++++++++++++++++++++++++++++---------
1 file changed, 28 insertions(+), 9 deletions(-)
(limited to 'src/toolkit/common.py')
diff --git a/src/toolkit/common.py b/src/toolkit/common.py
index 251a2c1..128ed08 100644
--- a/src/toolkit/common.py
+++ b/src/toolkit/common.py
@@ -6,9 +6,22 @@ import string
import os
import sys
import subprocess
+import signal
+import math
from collections import OrderedDict
+def scale(scale, width, height, returntype=None):
+ width = (float(width) / 100.0) * float(scale)
+ height = (float(height) / 100.0) * float(scale)
+ if returntype == str:
+ return (str(math.ceil(width)), str(math.ceil(height)))
+ elif returntype == int:
+ return (math.ceil(width), math.ceil(height))
+ else:
+ return (width, height)
+
+
def badName(name):
'''Returns whether a name contains non-alphanumeric chars'''
return any([letter in string.punctuation for letter in name])
@@ -34,29 +47,35 @@ def appendUppercase(lst):
lst.append(form.upper())
return lst
-
-def hideCmdWin(func):
- ''' Stops CMD window from appearing on Windows.
- Adapted from here: http://code.activestate.com/recipes/409002/
- '''
- def decorator(commandList, **kwargs):
+def pipeWrapper(func):
+ '''A decorator to insert proper kwargs into Popen objects.'''
+ def pipeWrapper(commandList, **kwargs):
if sys.platform == 'win32':
+ # Stop CMD window from appearing on Windows
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
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 decorator
+ return pipeWrapper
-@hideCmdWin
+@pipeWrapper
def checkOutput(commandList, **kwargs):
return subprocess.check_output(commandList, **kwargs)
-@hideCmdWin
+@pipeWrapper
def openPipe(commandList, **kwargs):
return subprocess.Popen(commandList, **kwargs)
+def closePipe(pipe):
+ pipe.stdout.close()
+ pipe.send_signal(signal.SIGINT)
def disableWhenEncoding(func):
def decorator(self, *args, **kwargs):
--
cgit v1.2.3
From 1297af61c9ce00b6dd76f8ec690baedf5bf887c7 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 29 Jul 2017 20:27:46 -0400
Subject: waveform component is working, preview is glitchy
---
src/components/original.py | 3 +
src/components/video.py | 10 ++--
src/components/waveform.py | 134 +++++++++++++++++++++++++++++++--------------
src/components/waveform.ui | 95 +++++++++++++++++++++++++++++++-
src/toolkit/common.py | 21 -------
src/toolkit/ffmpeg.py | 30 ++++++++--
src/toolkit/frame.py | 12 ++++
src/video_thread.py | 38 +++++++++----
8 files changed, 256 insertions(+), 87 deletions(-)
(limited to 'src/toolkit/common.py')
diff --git a/src/components/original.py b/src/components/original.py
index 3d1a574..621af6f 100644
--- a/src/components/original.py
+++ b/src/components/original.py
@@ -18,6 +18,9 @@ class Component(Component):
def names(*args):
return ['Original Audio Visualization']
+ def properties(self):
+ return ['pcm']
+
def widget(self, *args):
self.visColor = (255, 255, 255)
self.scale = 20
diff --git a/src/components/video.py b/src/components/video.py
index d3460ff..6cd16e5 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -4,10 +4,10 @@ import os
import math
import subprocess
-from component import Component, ComponentError
-from toolkit.frame import BlankFrame
-from toolkit.ffmpeg import testAudioStream, FfmpegVideo
-from toolkit import openPipe, closePipe, checkOutput, scale
+from component import Component
+from toolkit.frame import BlankFrame, scale
+from toolkit.ffmpeg import openPipe, closePipe, testAudioStream, FfmpegVideo
+from toolkit import checkOutput
class Component(Component):
@@ -132,7 +132,7 @@ class Component(Component):
]
command.extend(self.makeFfmpegFilter())
command.extend([
- '-vcodec', 'rawvideo', '-',
+ '-codec:v', 'rawvideo', '-',
'-ss', '90',
'-frames:v', '1',
])
diff --git a/src/components/waveform.py b/src/components/waveform.py
index 487a3bb..375b3fc 100644
--- a/src/components/waveform.py
+++ b/src/components/waveform.py
@@ -5,10 +5,10 @@ import os
import math
import subprocess
-from component import Component, ComponentError
-from toolkit.frame import BlankFrame
-from toolkit import openPipe, checkOutput, rgbFromString
-from toolkit.ffmpeg import FfmpegVideo
+from component import Component
+from toolkit.frame import BlankFrame, scale
+from toolkit import checkOutput, rgbFromString, pickColor
+from toolkit.ffmpeg import openPipe, closePipe, getAudioDuration, FfmpegVideo
class Component(Component):
@@ -21,17 +21,27 @@ class Component(Component):
self.page.lineEdit_color.setText('%s,%s,%s' % self.color)
btnStyle = "QPushButton { background-color : %s; outline: none; }" \
- % QColor(*self.color1).name()
- self.page.lineEdit_color.setStylesheet(btnStyle)
+ % QColor(*self.color).name()
+ self.page.pushButton_color.setStyleSheet(btnStyle)
self.page.pushButton_color.clicked.connect(lambda: self.pickColor())
+ self.page.spinBox_scale.valueChanged.connect(self.updateChunksize)
+
+ if hasattr(self.parent, 'window'):
+ self.parent.window.lineEdit_audioFile.textChanged.connect(
+ self.update
+ )
self.trackWidgets(
{
'mode': self.page.comboBox_mode,
+ 'amplitude': self.page.comboBox_amplitude,
'x': self.page.spinBox_x,
'y': self.page.spinBox_y,
'mirror': self.page.checkBox_mirror,
'scale': self.page.spinBox_scale,
+ 'opacity': self.page.spinBox_opacity,
+ 'compress': self.page.checkBox_compress,
+ 'mono': self.page.checkBox_mono,
}
)
@@ -42,6 +52,26 @@ class Component(Component):
self.page.pushButton_color.setStyleSheet(btnStyle)
super().update()
+ def loadPreset(self, pr, *args):
+ super().loadPreset(pr, *args)
+
+ self.page.lineEdit_color.setText('%s,%s,%s' % pr['color'])
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" \
+ % QColor(*pr['color']).name()
+ self.page.pushButton_color.setStyleSheet(btnStyle)
+
+ def savePreset(self):
+ saveValueStore = super().savePreset()
+ saveValueStore['color'] = self.color
+ return saveValueStore
+
+ def pickColor(self):
+ RGBstring, btnStyle = pickColor()
+ if not RGBstring:
+ return
+ self.page.lineEdit_color.setText(RGBstring)
+ self.page.pushButton_color.setStyleSheet(btnStyle)
+
def previewRender(self):
self.updateChunksize()
frame = self.getPreviewFrame(self.width, self.height)
@@ -53,10 +83,11 @@ class Component(Component):
def preFrameRender(self, **kwargs):
super().preFrameRender(**kwargs)
self.updateChunksize()
+ w, h = scale(self.scale, self.width, self.height, str)
self.video = FfmpegVideo(
inputPath=self.audioFile,
- filter_=makeFfmpegFilter(),
- width=self.width, height=self.height,
+ filter_=self.makeFfmpegFilter(),
+ width=w, height=h,
chunkSize=self.chunkSize,
frameRate=int(self.settings.value("outputFrameRate")),
parent=self.parent, component=self,
@@ -65,7 +96,7 @@ class Component(Component):
def frameRender(self, frameNo):
if FfmpegVideo.threadError is not None:
raise FfmpegVideo.threadError
- return finalizeFrame(self.video.frame(frameNo))
+ return self.finalizeFrame(self.video.frame(frameNo))
def postFrameRender(self):
closePipe(self.video.pipe)
@@ -74,18 +105,25 @@ class Component(Component):
inputFile = self.parent.window.lineEdit_audioFile.text()
if not inputFile or not os.path.exists(inputFile):
return
+ duration = getAudioDuration(inputFile)
+ if not duration:
+ return
+ startPt = duration / 3
command = [
self.core.FFMPEG_BIN,
'-thread_queue_size', '512',
+ '-r', self.settings.value("outputFrameRate"),
+ '-ss', "{0:.3f}".format(startPt),
'-i', inputFile,
'-f', 'image2pipe',
'-pix_fmt', 'rgba',
]
- command.extend(self.makeFfmpegFilter())
+ command.extend(self.makeFfmpegFilter(preview=True, startPt=startPt))
command.extend([
- '-vcodec', 'rawvideo', '-',
- '-ss', '90',
+ '-an',
+ '-s:v', '%sx%s' % scale(self.scale, self.width, self.height, str),
+ '-codec:v', 'rawvideo', '-',
'-frames:v', '1',
])
pipe = openPipe(
@@ -95,45 +133,57 @@ class Component(Component):
byteFrame = pipe.stdout.read(self.chunkSize)
closePipe(pipe)
- frame = finalizeFrame(self, byteFrame, width, height)
+ frame = self.finalizeFrame(byteFrame)
return frame
- def makeFfmpegFilter(self):
+ def makeFfmpegFilter(self, preview=False, startPt=0):
w, h = scale(self.scale, self.width, self.height, str)
+ if self.amplitude == 0:
+ amplitude = 'lin'
+ elif self.amplitude == 1:
+ amplitude = 'log'
+ elif self.amplitude == 2:
+ amplitude = 'sqrt'
+ elif self.amplitude == 3:
+ amplitude = 'cbrt'
+ hexcolor = QColor(*self.color).name()
+ opacity = "{0:.1f}".format(self.opacity / 100)
+
return [
'-filter_complex',
- '[0:a] showwaves=s=%sx%s:mode=%s,format=rgba [v]' % (
- w, h, self.mode,
+ '[0:a] %s%s'
+ 'showwaves=r=30:s=%sx%s:mode=%s:colors=%s@%s:scale=%s%s%s [v1]; '
+ '[v1] scale=%s:%s%s [v]' % (
+ 'compand=gain=2,' if self.compress else '',
+ 'aformat=channel_layouts=mono,' if self.mono else '',
+ self.settings.value("outputWidth"),
+ self.settings.value("outputHeight"),
+ str(self.page.comboBox_mode.currentText()).lower(),
+ hexcolor, opacity, amplitude,
+ ', drawbox=x=(iw-w)/2:y=(ih-h)/2:w=iw:h=4:color=%s@%s' % (
+ hexcolor, opacity
+ ) if self.mode < 2 else '',
+ ', hflip' if self.mirror else'',
+ w, h,
+ ', trim=duration=%s' % "{0:.3f}".format(startPt + 1) if preview else '',
),
'-map', '[v]',
- '-map', '0:a',
]
def updateChunksize(self):
- if self.scale != 100:
- width, height = scale(self.scale, self.width, self.height, int)
- else:
- width, height = self.width, self.height
+ width, height = scale(self.scale, self.width, self.height, int)
self.chunkSize = 4 * width * height
-
-def scale(scale, width, height, returntype=None):
- width = (float(width) / 100.0) * float(scale)
- height = (float(height) / 100.0) * float(scale)
- if returntype == str:
- return (str(math.ceil(width)), str(math.ceil(height)))
- elif returntype == int:
- return (math.ceil(width), math.ceil(height))
- else:
- return (width, height)
-
-
-def finalizeFrame(self, imageData, width, height):
- # frombytes goes here
- if self.scale != 100 \
- or self.x != 0 or self.y != 0:
- frame = BlankFrame(width, height)
- frame.paste(image, box=(self.x, self.y))
- else:
- frame = image
- return frame
+ def finalizeFrame(self, imageData):
+ image = Image.frombytes(
+ 'RGBA',
+ scale(self.scale, self.width, self.height, int),
+ imageData
+ )
+ if self.scale != 100 \
+ or self.x != 0 or self.y != 0:
+ frame = BlankFrame(self.width, self.height)
+ frame.paste(image, box=(self.x, self.y))
+ else:
+ frame = image
+ return frame
diff --git a/src/components/waveform.ui b/src/components/waveform.ui
index 5d62150..0e40380 100644
--- a/src/components/waveform.ui
+++ b/src/components/waveform.ui
@@ -226,9 +226,31 @@
-
-
+
- Mirror
+ Opacity
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ QAbstractSpinBox::UpDownArrows
+
+
+ %
+
+
+ 10
+
+
+ 400
+
+
+ 100
@@ -263,6 +285,75 @@
+ -
+
+
-
+
+
+ Compress
+
+
+
+ -
+
+
+ Mono
+
+
+
+ -
+
+
+ Mirror
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Amplitude
+
+
+
+ -
+
+
-
+
+ Linear
+
+
+ -
+
+ Logarithmic
+
+
+ -
+
+ Square root
+
+
+ -
+
+ Cubic root
+
+
+
+
+
+
-
diff --git a/src/toolkit/common.py b/src/toolkit/common.py
index 128ed08..5d424e0 100644
--- a/src/toolkit/common.py
+++ b/src/toolkit/common.py
@@ -6,22 +6,9 @@ import string
import os
import sys
import subprocess
-import signal
-import math
from collections import OrderedDict
-def scale(scale, width, height, returntype=None):
- width = (float(width) / 100.0) * float(scale)
- height = (float(height) / 100.0) * float(scale)
- if returntype == str:
- return (str(math.ceil(width)), str(math.ceil(height)))
- elif returntype == int:
- return (math.ceil(width), math.ceil(height))
- else:
- return (width, height)
-
-
def badName(name):
'''Returns whether a name contains non-alphanumeric chars'''
return any([letter in string.punctuation for letter in name])
@@ -69,14 +56,6 @@ def checkOutput(commandList, **kwargs):
return subprocess.check_output(commandList, **kwargs)
-@pipeWrapper
-def openPipe(commandList, **kwargs):
- return subprocess.Popen(commandList, **kwargs)
-
-def closePipe(pipe):
- pipe.stdout.close()
- pipe.send_signal(signal.SIGINT)
-
def disableWhenEncoding(func):
def decorator(self, *args, **kwargs):
if self.encoding:
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index fea9d4e..e37282f 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -6,10 +6,12 @@ import sys
import os
import subprocess
import threading
+import signal
from queue import PriorityQueue
import core
-from toolkit.common import checkOutput, openPipe
+from toolkit.common import checkOutput, pipeWrapper
+from component import ComponentError
class FfmpegVideo:
@@ -60,7 +62,8 @@ class FfmpegVideo:
kwargs['filter_']
)
self.command.extend([
- '-vcodec', 'rawvideo', '-',
+ '-s:v', '%sx%s' % (self.width, self.height),
+ '-codec:v', 'rawvideo', '-',
])
self.frameBuffer = PriorityQueue()
@@ -85,9 +88,11 @@ class FfmpegVideo:
self.frameBuffer.task_done()
def fillBuffer(self):
+ import sys
+ print(self.command)
self.pipe = openPipe(
self.command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
- stderr=subprocess.DEVNULL, bufsize=10**8
+ stderr=sys.__stdout__, bufsize=10**8
)
while True:
if self.parent.canceled:
@@ -100,7 +105,7 @@ class FfmpegVideo:
self.frameBuffer.put((self.frameNo-1, self.lastFrame))
continue
except AttributeError:
- Video.threadError = ComponentError(self.component, 'video')
+ FfmpegVideo.threadError = ComponentError(self.component, 'video')
break
self.currentFrame = self.pipe.stdout.read(self.chunkSize)
@@ -109,6 +114,16 @@ class FfmpegVideo:
self.lastFrame = self.currentFrame
+@pipeWrapper
+def openPipe(commandList, **kwargs):
+ return subprocess.Popen(commandList, **kwargs)
+
+
+def closePipe(pipe):
+ pipe.stdout.close()
+ pipe.send_signal(signal.SIGINT)
+
+
def findFfmpeg():
if getattr(sys, 'frozen', False):
# The application is frozen
@@ -347,7 +362,12 @@ def getAudioDuration(filename):
except subprocess.CalledProcessError as ex:
fileInfo = ex.output
- info = fileInfo.decode("utf-8").split('\n')
+ try:
+ info = fileInfo.decode("utf-8").split('\n')
+ except UnicodeDecodeError as e:
+ print('Unicode error:', str(e))
+ return False
+
for line in info:
if 'Duration' in line:
d = line.split(',')[0]
diff --git a/src/toolkit/frame.py b/src/toolkit/frame.py
index b66e037..f42d4c9 100644
--- a/src/toolkit/frame.py
+++ b/src/toolkit/frame.py
@@ -6,6 +6,7 @@ from PIL import Image
from PIL.ImageQt import ImageQt
import sys
import os
+import math
import core
@@ -41,6 +42,17 @@ class PaintColor(QtGui.QColor):
super().__init__(b, g, r, a)
+def scale(scale, width, height, returntype=None):
+ width = (float(width) / 100.0) * float(scale)
+ height = (float(height) / 100.0) * float(scale)
+ if returntype == str:
+ return (str(math.ceil(width)), str(math.ceil(height)))
+ elif returntype == int:
+ return (math.ceil(width), math.ceil(height))
+ else:
+ return (width, height)
+
+
def defaultSize(framefunc):
'''Makes width/height arguments optional'''
def decorator(*args):
diff --git a/src/video_thread.py b/src/video_thread.py
index f27ec21..5963def 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -19,9 +19,11 @@ import time
import signal
from component import ComponentError
-from toolkit import openPipe
-from toolkit.ffmpeg import readAudioFile, createFfmpegCommand
from toolkit.frame import Checkerboard
+from toolkit.ffmpeg import (
+ openPipe, readAudioFile,
+ getAudioDuration, createFfmpegCommand
+)
class Worker(QtCore.QObject):
@@ -132,15 +134,24 @@ class Worker(QtCore.QObject):
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
# READ AUDIO, INITIALIZE COMPONENTS, OPEN A PIPE TO FFMPEG
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
-
- self.progressBarSetText.emit("Loading audio file...")
- audioFileTraits = readAudioFile(
- self.inputFile, self
- )
- if audioFileTraits is None:
- self.cancelExport()
- return
- self.completeAudioArray, duration = audioFileTraits
+ if any([
+ True if 'pcm' in comp.properties() else False
+ for comp in self.components
+ ]):
+ self.progressBarSetText.emit("Loading audio file...")
+ audioFileTraits = readAudioFile(
+ self.inputFile, self
+ )
+ if audioFileTraits is None:
+ self.cancelExport()
+ return
+ self.completeAudioArray, duration = audioFileTraits
+ else:
+ duration = getAudioDuration(self.inputFile)
+ class FakeList:
+ def __len__(self):
+ return int((duration * 44100) + 44100) - 1470
+ self.completeAudioArray = FakeList()
self.progressBarUpdate.emit(0)
self.progressBarSetText.emit("Starting components...")
@@ -284,7 +295,10 @@ class Worker(QtCore.QObject):
numpy.seterr(all='print')
- self.out_pipe.stdin.close()
+ try:
+ self.out_pipe.stdin.close()
+ except BrokenPipeError:
+ print('Broken pipe to ffmpeg!')
if self.out_pipe.stderr is not None:
print(self.out_pipe.stderr.read())
self.out_pipe.stderr.close()
--
cgit v1.2.3
From b6b45d12702f18f041acf65b0d5e34714835ecb4 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 30 Jul 2017 13:04:02 -0400
Subject: added Spectrum component with many options
tweaked Waveform, added some ffmpeg logging, made generic widget functions
---
src/component.py | 54 ++---
src/components/spectrum.py | 239 +++++++++++++++++++
src/components/spectrum.ui | 582 +++++++++++++++++++++++++++++++++++++++++++++
src/components/waveform.py | 48 ++--
src/components/waveform.ui | 21 +-
src/mainwindow.py | 2 +-
src/toolkit/common.py | 43 ++++
src/toolkit/ffmpeg.py | 41 ++--
8 files changed, 959 insertions(+), 71 deletions(-)
create mode 100644 src/components/spectrum.py
create mode 100644 src/components/spectrum.ui
(limited to 'src/toolkit/common.py')
diff --git a/src/component.py b/src/component.py
index 6d49406..1a5a5a4 100644
--- a/src/component.py
+++ b/src/component.py
@@ -4,9 +4,11 @@
'''
from PyQt5 import uic, QtCore, QtWidgets
import os
+import sys
import time
from toolkit.frame import BlankFrame
+from toolkit import getWidgetValue, setWidgetValue, connectWidget
class ComponentMetaclass(type(QtCore.QObject)):
@@ -273,14 +275,9 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
widgets['spinBox'].extend(
self.page.findChildren(QtWidgets.QDoubleSpinBox)
)
- for widget in widgets['lineEdit']:
- widget.textChanged.connect(self.update)
- for widget in widgets['checkBox']:
- 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)
+ for widgetList in widgets.values():
+ for widget in widgetList:
+ connectWidget(widget, self.update)
def update(self):
'''
@@ -289,15 +286,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
Call super() at the END if you need to subclass this.
'''
for attr, widget in self._trackedWidgets.items():
- if type(widget) == QtWidgets.QLineEdit:
- setattr(self, attr, widget.text())
- elif type(widget) == QtWidgets.QSpinBox \
- or type(widget) == QtWidgets.QDoubleSpinBox:
- setattr(self, attr, widget.value())
- elif type(widget) == QtWidgets.QCheckBox:
- setattr(self, attr, widget.isChecked())
- elif type(widget) == QtWidgets.QComboBox:
- setattr(self, attr, widget.currentIndex())
+ setattr(self, attr, getWidgetValue(widget))
if not self.core.openingProject:
self.parent.drawPreview()
saveValueStore = self.savePreset()
@@ -313,19 +302,10 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
self.currentPreset = presetName \
if presetName is not None else presetDict['preset']
for attr, widget in self._trackedWidgets.items():
- val = presetDict[
- attr if attr not in self._presetNames
+ key = attr if attr not in self._presetNames \
else self._presetNames[attr]
- ]
- 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)
+ val = presetDict[key]
+ setWidgetValue(widget, val)
def savePreset(self):
saveValueStore = {}
@@ -420,24 +400,30 @@ class ComponentError(RuntimeError):
prevErrors = []
lastTime = time.time()
- def __init__(self, caller, name):
- print('##### ComponentError by %s: %s' % (caller.name, name))
+ def __init__(self, caller, name, msg=None):
+ 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:
ComponentError.prevErrors.pop()
ComponentError.prevErrors.insert(0, name)
curTime = time.time()
if name in ComponentError.prevErrors[1:] \
and curTime - ComponentError.lastTime < 1.0:
- # Don't create multiple windows for quickly repeated messages
return
ComponentError.lastTime = time.time()
from toolkit import formatTraceback
- import sys
if sys.exc_info()[0] is not None:
string = (
- "%s component's %s encountered %s %s: %s" % (
+ "%s component (#%s): %s encountered %s %s: %s" % (
caller.__class__.name,
+ str(caller.compPos),
name,
'an' if any([
sys.exc_info()[0].__name__.startswith(vowel)
diff --git a/src/components/spectrum.py b/src/components/spectrum.py
new file mode 100644
index 0000000..261d9cc
--- /dev/null
+++ b/src/components/spectrum.py
@@ -0,0 +1,239 @@
+from PIL import Image
+from PyQt5 import QtGui, QtCore, QtWidgets
+from PyQt5.QtGui import QColor
+import os
+import math
+import subprocess
+import time
+
+from component import Component
+from toolkit.frame import BlankFrame, scale
+from toolkit import checkOutput, rgbFromString, pickColor, connectWidget
+from toolkit.ffmpeg import (
+ openPipe, closePipe, getAudioDuration, FfmpegVideo, exampleSound
+)
+
+
+class Component(Component):
+ name = 'Spectrum'
+ version = '1.0.0'
+
+ def widget(self, *args):
+ self.color = (255, 255, 255)
+ self.previewFrame = None
+ super().widget(*args)
+ 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,
+ '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,
+ 'color': self.page.comboBox_color,
+ 'compress': self.page.checkBox_compress,
+ 'mono': self.page.checkBox_mono,
+ }
+ )
+ for widget in self._trackedWidgets.values():
+ connectWidget(widget, lambda: self.changed())
+
+ def changed(self):
+ self.changedOptions = True
+
+ def update(self):
+ count = self.page.stackedWidget.count()
+ i = self.page.comboBox_filterType.currentIndex()
+ self.page.stackedWidget.setCurrentIndex(i if i < count else count - 1)
+ 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)
+ 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'
+ color = self.page.comboBox_color.currentText().lower()
+ genericPreview = self.settings.value("pref_genericPreview")
+
+ if self.filterType == 0: # Spectrum
+ filter_ = (
+ 'showspectrum=s=%sx%s:slide=scroll:win_func=%s:'
+ 'color=%s:scale=%s' % (
+ self.settings.value("outputWidth"),
+ self.settings.value("outputHeight"),
+ self.page.comboBox_window.currentText(),
+ color, amplitude,
+ )
+ )
+ elif self.filterType == 1: # Histogram
+ filter_ = (
+ 'ahistogram=r=%s:s=%sx%s:dmode=separate' % (
+ self.settings.value("outputFrameRate"),
+ self.settings.value("outputWidth"),
+ self.settings.value("outputHeight"),
+ )
+ )
+ elif self.filterType == 2: # Vector Scope
+ filter_ = (
+ 'avectorscope=s=%sx%s:draw=line:m=polar:scale=log' % (
+ self.settings.value("outputWidth"),
+ self.settings.value("outputHeight"),
+ )
+ )
+ elif self.filterType == 3: # Musical Scale
+ filter_ = (
+ 'showcqt=r=%s:s=%sx%s:count=30:text=0' % (
+ self.settings.value("outputFrameRate"),
+ self.settings.value("outputWidth"),
+ self.settings.value("outputHeight"),
+ )
+ )
+ elif self.filterType == 4: # Phase
+ filter_ = (
+ 'aphasemeter=r=%s:s=%sx%s:mpc=white:video=1[atrash][vtmp]; '
+ '[atrash] anullsink; [vtmp] null' % (
+ self.settings.value("outputFrameRate"),
+ self.settings.value("outputWidth"),
+ self.settings.value("outputHeight"),
+ )
+ )
+
+ return [
+ '-filter_complex',
+ '%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 else '',
+ filter_,
+ ', hflip' if self.mirror else'',
+ w, h,
+ ', trim=start=%s:end=%s' % (
+ "{0:.3f}".format(startPt + 15),
+ "{0:.3f}".format(startPt + 15.5)
+ ) if preview 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):
+ image = Image.frombytes(
+ 'RGBA',
+ scale(self.scale, self.width, self.height, int),
+ imageData
+ )
+ if self.scale != 100 \
+ or self.x != 0 or self.y != 0:
+ frame = BlankFrame(self.width, self.height)
+ frame.paste(image, box=(self.x, self.y))
+ else:
+ frame = image
+ return frame
diff --git a/src/components/spectrum.ui b/src/components/spectrum.ui
new file mode 100644
index 0000000..59ca0b8
--- /dev/null
+++ b/src/components/spectrum.ui
@@ -0,0 +1,582 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 586
+ 197
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 197
+
+
+
+ Form
+
+
+
-
+
+
+ 4
+
+
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Type
+
+
+
+ -
+
+
-
+
+ Spectrum
+
+
+ -
+
+ Histogram
+
+
+ -
+
+ Vector Scope
+
+
+ -
+
+ Musical Scale
+
+
+ -
+
+ Phase
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ X
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+ -10000
+
+
+ 10000
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Y
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 0
+ 0
+
+
+
+ -10000
+
+
+ 10000
+
+
+ 0
+
+
+
+
+
+ -
+
+
-
+
+
+ Compress
+
+
+
+ -
+
+
+ Mono
+
+
+
+ -
+
+
+ Mirror
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Scale
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ QAbstractSpinBox::UpDownArrows
+
+
+ %
+
+
+ 10
+
+
+ 400
+
+
+ 100
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ false
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Plain
+
+
+ 0
+
+
+
+
+
+ 0
+ 0
+ 561
+ 72
+
+
+
+
+ QLayout::SetMaximumSize
+
+
+ 0
+
+
-
+
+
+ QLayout::SetDefaultConstraint
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 31
+ 0
+
+
+
+ Window
+
+
+ 4
+
+
+
+ -
+
+
-
+
+ hann
+
+
+ -
+
+ gauss
+
+
+ -
+
+ tukey
+
+
+ -
+
+ dolph
+
+
+ -
+
+ cauchy
+
+
+ -
+
+ parzen
+
+
+ -
+
+ poisson
+
+
+ -
+
+ rect
+
+
+ -
+
+ bartlett
+
+
+ -
+
+ hanning
+
+
+ -
+
+ hamming
+
+
+ -
+
+ blackman
+
+
+ -
+
+ welch
+
+
+ -
+
+ flattop
+
+
+ -
+
+ bharris
+
+
+ -
+
+ bnuttall
+
+
+ -
+
+ lanczos
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Amplitude
+
+
+ 4
+
+
+
+ -
+
+
-
+
+ Square root
+
+
+ -
+
+ Cubic root
+
+
+ -
+
+ 4thrt
+
+
+ -
+
+ 5thrt
+
+
+ -
+
+ Linear
+
+
+ -
+
+ Logarithmic
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::MinimumExpanding
+
+
+
+ 10
+ 20
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Color
+
+
+ 4
+
+
+
+ -
+
+
-
+
+ Channel
+
+
+ -
+
+ Intensity
+
+
+ -
+
+ Rainbow
+
+
+ -
+
+ Moreland
+
+
+ -
+
+ Nebulae
+
+
+ -
+
+ Fire
+
+
+ -
+
+ Fiery
+
+
+ -
+
+ Fruit
+
+
+ -
+
+ Cool
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::MinimumExpanding
+
+
+
+ 10
+ 20
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::Fixed
+
+
+
+ 20
+ 10
+
+
+
+
+
+
+
+
+
diff --git a/src/components/waveform.py b/src/components/waveform.py
index b4b19e9..6c5133d 100644
--- a/src/components/waveform.py
+++ b/src/components/waveform.py
@@ -8,7 +8,9 @@ import subprocess
from component import Component
from toolkit.frame import BlankFrame, scale
from toolkit import checkOutput, rgbFromString, pickColor
-from toolkit.ffmpeg import openPipe, closePipe, getAudioDuration, FfmpegVideo
+from toolkit.ffmpeg import (
+ openPipe, closePipe, getAudioDuration, FfmpegVideo, exampleSound
+)
class Component(Component):
@@ -112,6 +114,8 @@ class Component(Component):
if not duration:
return
startPt = duration / 3
+ if startPt + 3 > duration:
+ startPt += startPt - 3
command = [
self.core.FFMPEG_BIN,
@@ -154,29 +158,43 @@ class Component(Component):
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'
- 'showwaves=r=30:s=%sx%s:mode=%s:colors=%s@%s:scale=%s%s%s [v1]; '
- '[v1] scale=%s:%s%s,setpts=2.0*PTS [v]' % (
- 'aevalsrc=sin(1*2*PI*t)*sin(880*2*PI*t),'
- if preview and genericPreview else '[0:a] ',
- 'compand=.3|.3:1|1:-90/-60|-60/-40|-40/-30|-20/-20:6:0:-90:0.2'
- ',' if self.compress and not preview else (
- 'compand=gain=5,' if self.compress else ''
- ),
- 'aformat=channel_layouts=mono,' if self.mono else '',
- self.settings.value("outputWidth"),
- self.settings.value("outputHeight"),
- str(self.page.comboBox_mode.currentText()).lower(),
- hexcolor, opacity, amplitude,
+ '%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 + 1)
+ ', trim=duration=%s' % "{0:.3f}".format(startPt + 3)
if preview else '',
),
'-map', '[v]',
diff --git a/src/components/waveform.ui b/src/components/waveform.ui
index 0e40380..5473f33 100644
--- a/src/components/waveform.ui
+++ b/src/components/waveform.ui
@@ -66,12 +66,17 @@
-
- P2p
+ Point
-
- Point
+ Frequency Bar
+
+
+ -
+
+ Frequency Line
@@ -180,12 +185,16 @@
-
- Wave Color
+ Color
-
-
+
+
+ Qt::ImhNone
+
+
-
@@ -244,10 +253,10 @@
%
- 10
+ 0
- 400
+ 100
100
diff --git a/src/mainwindow.py b/src/mainwindow.py
index a97081e..d9e95e2 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -581,7 +581,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.showMessage(
msg=msg,
detail=detail,
- icon='Warning',
+ icon='Critical',
)
def changeEncodingStatus(self, status):
diff --git a/src/toolkit/common.py b/src/toolkit/common.py
index 5d424e0..db278c0 100644
--- a/src/toolkit/common.py
+++ b/src/toolkit/common.py
@@ -113,3 +113,46 @@ def formatTraceback(tb=None):
import sys
tb = sys.exc_info()[2]
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()
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index 4ea2863..3421049 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -37,7 +37,6 @@ class FfmpegVideo:
self.frameNo = -1
self.currentFrame = 'None'
self.map_ = None
- self.debug = False
if 'loopVideo' in kwargs and kwargs['loopVideo']:
self.loopValue = '-1'
@@ -48,8 +47,6 @@ class FfmpegVideo:
kwargs['filter_'].insert(0, '-filter_complex')
else:
kwargs['filter_'] = None
- if 'debug' in kwargs:
- self.debug = True
self.command = [
core.Core.FFMPEG_BIN,
@@ -90,16 +87,15 @@ class FfmpegVideo:
self.frameBuffer.task_done()
def fillBuffer(self):
- if self.debug:
- print(" ".join([word for word in self.command]))
- err = sys.__stdout__
- else:
- err = subprocess.DEVNULL
-
- self.pipe = openPipe(
- self.command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
- stderr=err, bufsize=10**8
- )
+ 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
@@ -111,10 +107,18 @@ class FfmpegVideo:
self.frameBuffer.put((self.frameNo-1, self.lastFrame))
continue
except AttributeError:
- FfmpegVideo.threadError = ComponentError(self.component, 'video')
+ FfmpegVideo.threadError = ComponentError(
+ self.component, 'video',
+ "Video seemed playable but wasn't."
+ )
break
- self.currentFrame = self.pipe.stdout.read(self.chunkSize)
+ 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
@@ -446,3 +450,10 @@ def readAudioFile(filename, videoWorker):
completeAudioArray = completeAudioArrayCopy
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,'
+ )
--
cgit v1.2.3
From 3c1b52205f183e9a2c943c5f666ed2c01db3aaf5 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 1 Aug 2017 17:57:39 -0400
Subject: component class now tracks colorwidgets
so adding new color-selection widgets is now simple
---
setup.py | 2 +-
src/component.py | 73 +++++++++++++++++++++++++++++++++++++++++-----
src/components/color.py | 58 +++++-------------------------------
src/components/original.py | 35 +++-------------------
src/components/text.py | 27 ++---------------
src/components/waveform.py | 40 ++++---------------------
src/toolkit/common.py | 19 ------------
src/toolkit/frame.py | 6 ++--
8 files changed, 90 insertions(+), 170 deletions(-)
(limited to 'src/toolkit/common.py')
diff --git a/setup.py b/setup.py
index d4f226b..4a4511f 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@ from setuptools import setup
import os
-__version__ = '2.0.0.rc2'
+__version__ = '2.0.0.rc3'
def package_files(directory):
diff --git a/src/component.py b/src/component.py
index 36ad9d3..d47aeae 100644
--- a/src/component.py
+++ b/src/component.py
@@ -3,18 +3,20 @@
on making a valid component.
'''
from PyQt5 import uic, QtCore, QtWidgets
+from PyQt5.QtGui import QColor
import os
import sys
import time
from toolkit.frame import BlankFrame
-from toolkit import getWidgetValue, setWidgetValue, connectWidget
+from toolkit import (
+ getWidgetValue, setWidgetValue, connectWidget, rgbFromString
+)
class ComponentMetaclass(type(QtCore.QObject)):
'''
- Checks the validity of each Component class imported, and
- mutates some attributes for easier use by the core program.
+ Checks the validity of each Component class and mutates some attrs.
E.g., takes only major version from version string & decorates methods
'''
@@ -173,6 +175,8 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
self._trackedWidgets = {}
self._presetNames = {}
self._commandArgs = {}
+ self._colorWidgets = {}
+ self._relativeWidgets = {}
self._lockedProperties = None
self._lockedError = None
@@ -188,7 +192,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
)
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
- # Critical Methods
+ # Render Methods
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
def previewRender(self):
@@ -286,7 +290,17 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
Call super() at the END if you need to subclass this.
'''
for attr, widget in self._trackedWidgets.items():
- setattr(self, attr, getWidgetValue(widget))
+ if attr in self._colorWidgets:
+ rgbTuple = rgbFromString(widget.text())
+ setattr(self, attr, rgbTuple)
+ btnStyle = (
+ "QPushButton { background-color : %s; outline: none; }"
+ % QColor(*rgbTuple).name()
+ )
+ self._colorWidgets[attr].setStyleSheet(btnStyle)
+ else:
+ setattr(self, attr, getWidgetValue(widget))
+
if not self.core.openingProject:
self.parent.drawPreview()
saveValueStore = self.savePreset()
@@ -305,7 +319,16 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
key = attr if attr not in self._presetNames \
else self._presetNames[attr]
val = presetDict[key]
- setWidgetValue(widget, val)
+
+ if attr in self._colorWidgets:
+ widget.setText('%s,%s,%s' % val)
+ btnStyle = (
+ "QPushButton { background-color : %s; outline: none; }"
+ % QColor(*val).name()
+ )
+ self._colorWidgets[attr].setStyleSheet(btnStyle)
+ else:
+ setWidgetValue(widget, val)
def savePreset(self):
saveValueStore = {}
@@ -352,7 +375,12 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
self._trackedWidgets = trackDict
for kwarg in kwargs:
try:
- if kwarg in ('presetNames', 'commandArgs'):
+ if kwarg in (
+ 'presetNames',
+ 'commandArgs',
+ 'colorWidgets',
+ 'relativeWidgets',
+ ):
setattr(self, '_%s' % kwarg, kwargs[kwarg])
else:
raise ComponentError(
@@ -360,6 +388,37 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
except ComponentError:
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; }"
+ )
+
+ 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):
self._lockedProperties = propList
diff --git a/src/components/color.py b/src/components/color.py
index 2abd79a..d6fffc6 100644
--- a/src/components/color.py
+++ b/src/components/color.py
@@ -6,7 +6,6 @@ import os
from component import Component
from toolkit.frame import BlankFrame, FloodFrame, FramePainter, PaintColor
-from toolkit import rgbFromString, pickColor
class Component(Component):
@@ -14,25 +13,12 @@ class Component(Component):
version = '1.0.0'
def widget(self, *args):
- self.color1 = (0, 0, 0)
- self.color2 = (133, 133, 133)
self.x = 0
self.y = 0
super().widget(*args)
- self.page.lineEdit_color1.setText('%s,%s,%s' % self.color1)
- self.page.lineEdit_color2.setText('%s,%s,%s' % self.color2)
-
- btnStyle1 = "QPushButton { background-color : %s; outline: none; }" \
- % QColor(*self.color1).name()
-
- 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))
+ self.page.lineEdit_color1.setText('0,0,0')
+ self.page.lineEdit_color2.setText('133,133,133')
# disable color #2 until non-default 'fill' option gets changed
self.page.lineEdit_color2.setDisabled(True)
@@ -66,16 +52,18 @@ class Component(Component):
'LG_end': self.page.spinBox_linearGradient_end,
'RG_centre': self.page.spinBox_radialGradient_spread,
'fillType': self.page.comboBox_fill,
+ 'color1': self.page.lineEdit_color1,
+ 'color2': self.page.lineEdit_color2,
}, presetNames={
'sizeWidth': 'width',
'sizeHeight': 'height',
- }
+ }, colorWidgets={
+ 'color1': self.page.pushButton_color1,
+ 'color2': self.page.pushButton_color2,
+ },
)
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()
if fillType == 0:
self.page.lineEdit_color2.setEnabled(False)
@@ -161,36 +149,6 @@ class Component(Component):
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):
print('Specify a color:\n color=255,255,255')
diff --git a/src/components/original.py b/src/components/original.py
index 621af6f..950ac7b 100644
--- a/src/components/original.py
+++ b/src/components/original.py
@@ -8,7 +8,6 @@ from copy import copy
from component import Component
from toolkit.frame import BlankFrame
-from toolkit import rgbFromString, pickColor
class Component(Component):
@@ -22,7 +21,6 @@ class Component(Component):
return ['pcm']
def widget(self, *args):
- self.visColor = (255, 255, 255)
self.scale = 20
self.y = 0
super().widget(*args)
@@ -33,35 +31,17 @@ class Component(Component):
self.page.comboBox_visLayout.addItem("Top")
self.page.comboBox_visLayout.setCurrentIndex(0)
- self.page.lineEdit_visColor.setText('%s,%s,%s' % self.visColor)
- self.page.pushButton_visColor.clicked.connect(lambda: self.pickColor())
- btnStyle = "QPushButton { background-color : %s; outline: none; }" \
- % QColor(*self.visColor).name()
- self.page.pushButton_visColor.setStyleSheet(btnStyle)
+ self.page.lineEdit_visColor.setText('255,255,255')
self.trackWidgets({
+ 'visColor': self.page.lineEdit_visColor,
'layout': self.page.comboBox_visLayout,
'scale': self.page.spinBox_scale,
'y': self.page.spinBox_y,
+ }, colorWidgets={
+ 'visColor': self.page.pushButton_visColor,
})
- def update(self):
- self.visColor = rgbFromString(self.page.lineEdit_visColor.text())
- 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):
spectrum = numpy.fromfunction(
lambda x: float(self.scale)/2500*(x-128)**2, (255,), dtype="int16")
@@ -99,13 +79,6 @@ class Component(Component):
self.spectrumArray[arrayNo],
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(
self, i, completeAudioArray, sampleSize,
smoothConstantDown, smoothConstantUp, lastSpectrum):
diff --git a/src/components/text.py b/src/components/text.py
index 8a302ff..1fe3467 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -5,7 +5,6 @@ import os
from component import Component
from toolkit.frame import FramePainter
-from toolkit import rgbFromString, pickColor
class Component(Component):
@@ -33,11 +32,6 @@ class Component(Component):
self.page.comboBox_textAlign.addItem("Right")
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))
@@ -48,21 +42,18 @@ class Component(Component):
self.update
)
self.trackWidgets({
+ 'textColor': self.page.lineEdit_textColor,
'title': self.page.lineEdit_title,
'alignment': self.page.comboBox_textAlign,
'fontSize': self.page.spinBox_fontSize,
'xPosition': self.page.spinBox_xTextAlign,
'yPosition': self.page.spinBox_yTextAlign,
+ }, colorWidgets={
+ 'textColor': self.page.pushButton_textColor,
})
def update(self):
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()
def getXY(self):
@@ -86,15 +77,10 @@ class Component(Component):
font = QFont()
font.fromString(pr['titleFont'])
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):
saveValueStore = super().savePreset()
saveValueStore['titleFont'] = self.titleFont.toString()
- saveValueStore['textColor'] = self.textColor
return saveValueStore
def previewRender(self):
@@ -122,13 +108,6 @@ class Component(Component):
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):
print('Enter a string to use as centred white text:')
print(' "title=User Error"')
diff --git a/src/components/waveform.py b/src/components/waveform.py
index 6c5133d..9c3cf86 100644
--- a/src/components/waveform.py
+++ b/src/components/waveform.py
@@ -7,7 +7,7 @@ import subprocess
from component import Component
from toolkit.frame import BlankFrame, scale
-from toolkit import checkOutput, rgbFromString, pickColor
+from toolkit import checkOutput
from toolkit.ffmpeg import (
openPipe, closePipe, getAudioDuration, FfmpegVideo, exampleSound
)
@@ -18,15 +18,9 @@ class Component(Component):
version = '1.0.0'
def widget(self, *args):
- self.color = (255, 255, 255)
super().widget(*args)
- self.page.lineEdit_color.setText('%s,%s,%s' % self.color)
- btnStyle = "QPushButton { background-color : %s; outline: none; }" \
- % QColor(*self.color).name()
- self.page.pushButton_color.setStyleSheet(btnStyle)
- self.page.pushButton_color.clicked.connect(lambda: self.pickColor())
- self.page.spinBox_scale.valueChanged.connect(self.updateChunksize)
+ self.page.lineEdit_color.setText('255,255,255')
if hasattr(self.parent, 'window'):
self.parent.window.lineEdit_audioFile.textChanged.connect(
@@ -35,6 +29,7 @@ class Component(Component):
self.trackWidgets(
{
+ 'color': self.page.lineEdit_color,
'mode': self.page.comboBox_mode,
'amplitude': self.page.comboBox_amplitude,
'x': self.page.spinBox_x,
@@ -44,36 +39,11 @@ class Component(Component):
'opacity': self.page.spinBox_opacity,
'compress': self.page.checkBox_compress,
'mono': self.page.checkBox_mono,
+ }, colorWidgets={
+ 'color': self.page.pushButton_color,
}
)
- def update(self):
- self.color = rgbFromString(self.page.lineEdit_color.text())
- btnStyle = "QPushButton { background-color : %s; outline: none; }" \
- % QColor(*self.color).name()
- self.page.pushButton_color.setStyleSheet(btnStyle)
- super().update()
-
- def loadPreset(self, pr, *args):
- super().loadPreset(pr, *args)
-
- self.page.lineEdit_color.setText('%s,%s,%s' % pr['color'])
- btnStyle = "QPushButton { background-color : %s; outline: none; }" \
- % QColor(*pr['color']).name()
- self.page.pushButton_color.setStyleSheet(btnStyle)
-
- def savePreset(self):
- saveValueStore = super().savePreset()
- saveValueStore['color'] = self.color
- return saveValueStore
-
- def pickColor(self):
- RGBstring, btnStyle = pickColor()
- if not RGBstring:
- return
- self.page.lineEdit_color.setText(RGBstring)
- self.page.pushButton_color.setStyleSheet(btnStyle)
-
def previewRender(self):
self.updateChunksize()
frame = self.getPreviewFrame(self.width, self.height)
diff --git a/src/toolkit/common.py b/src/toolkit/common.py
index db278c0..eba57d9 100644
--- a/src/toolkit/common.py
+++ b/src/toolkit/common.py
@@ -74,25 +74,6 @@ def disableWhenOpeningProject(func):
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):
'''Turns an RGB string like "255, 255, 255" into a tuple'''
try:
diff --git a/src/toolkit/frame.py b/src/toolkit/frame.py
index f42d4c9..c007188 100644
--- a/src/toolkit/frame.py
+++ b/src/toolkit/frame.py
@@ -42,9 +42,9 @@ class PaintColor(QtGui.QColor):
super().__init__(b, g, r, a)
-def scale(scale, width, height, returntype=None):
- width = (float(width) / 100.0) * float(scale)
- height = (float(height) / 100.0) * float(scale)
+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:
--
cgit v1.2.3