aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/component.py54
-rw-r--r--src/components/spectrum.py239
-rw-r--r--src/components/spectrum.ui582
-rw-r--r--src/components/waveform.py48
-rw-r--r--src/components/waveform.ui21
-rw-r--r--src/mainwindow.py2
-rw-r--r--src/toolkit/common.py43
-rw-r--r--src/toolkit/ffmpeg.py41
8 files changed, 959 insertions, 71 deletions
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 @@
+<?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_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">
+ <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>72</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_amplitude">
+ <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>
+ </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>
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 @@
</item>
<item>
<property name="text">
- <string>P2p</string>
+ <string>Point</string>
</property>
</item>
<item>
<property name="text">
- <string>Point</string>
+ <string>Frequency Bar</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Frequency Line</string>
</property>
</item>
</widget>
@@ -180,12 +185,16 @@
<item>
<widget class="QLabel" name="label_2">
<property name="text">
- <string>Wave Color</string>
+ <string>Color</string>
</property>
</widget>
</item>
<item>
- <widget class="QLineEdit" name="lineEdit_color"/>
+ <widget class="QLineEdit" name="lineEdit_color">
+ <property name="inputMethodHints">
+ <set>Qt::ImhNone</set>
+ </property>
+ </widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_color">
@@ -244,10 +253,10 @@
<string>%</string>
</property>
<property name="minimum">
- <number>10</number>
+ <number>0</number>
</property>
<property name="maximum">
- <number>400</number>
+ <number>100</number>
</property>
<property name="value">
<number>100</number>
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,'
+ )