summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrianna Rainey2026-01-28 17:49:58 -0500
committerGitHub2026-01-28 17:49:58 -0500
commitf66eb99465c61232a7f649e66bee59504bb0e52c (patch)
tree40d4f2e4e7cea033e4a68da025c7d91295e71cfb
parent864898419e810055b51e3a32fccb00a62aab9a6b (diff)
v2.2.1 - fix #74, fix #92, add optional 64th bar to Classic Visualizer, improve Conway default (#93)
* update gitignore ignore profiling and coverage data * F1 opens help window, create appName variable, move undostack class * fix kaleidoscope effect, increase default Y values by +4 the increased y values allow the cells to continue animating for more than 60 minutes instead of 30 (at default 60f/t) * update version number * add minimumWidth to undo history window * Classic Visualizer: option to include 64th bar * Waveform component: fix #74 - new animation speed option * move shared visualizer code into toolkit * Waveform component: compress audio by default * Waveform component: fix 100% animation speed * new components receive random color * update to Qt 6 * fix pushbutton stylesheet * fix #92: replace ok/cancel with save/discard/cancel * remove obsolete PaintColor subclass * mv common shadow code into addShadow func * add 3rd option of ok/cancel back to showMessage the 3 options are: - ok - ok/cancel - save/discard/cancel * Image component: add shadow option * small test of rgbFromString * fix color tuple string * test another way to get comp names from CLI * rename component tests, add some more * Image component: scale shadow based on resolution * catch AttributeError if previewRender returns None * Text component: fix blur radius only able to increase the relativeWidgets system causes QDoubleSpinbox to only allow increases, because it really only works with integeres, so I changed the blur radius into a normal QSpinBox. I noted where the problem exists within component.py for future reference. This commit also removes an unneeded VerticalLayout from the ui file * remove unnecessary QVBoxLayout * paste shadow at x,y instead of using offset method * fix tests due to shadow change * don't print warning in connectWidget due to QFontComboBox
-rw-r--r--.gitignore3
-rw-r--r--pyproject.toml2
-rw-r--r--src/avp/__init__.py2
-rw-r--r--src/avp/component.py12
-rw-r--r--src/avp/components/color.py10
-rw-r--r--src/avp/components/color.ui36
-rw-r--r--src/avp/components/image.py51
-rw-r--r--src/avp/components/image.ui10
-rw-r--r--src/avp/components/life.py103
-rw-r--r--src/avp/components/life.ui2
-rw-r--r--src/avp/components/original.py96
-rw-r--r--src/avp/components/original.ui58
-rw-r--r--src/avp/components/text.py13
-rw-r--r--src/avp/components/text.ui1213
-rw-r--r--src/avp/components/video.ui325
-rw-r--r--src/avp/components/waveform.py92
-rw-r--r--src/avp/components/waveform.ui340
-rw-r--r--src/avp/core.py2
-rw-r--r--src/avp/gui/mainwindow.py80
-rw-r--r--src/avp/gui/presetmanager.py4
-rw-r--r--src/avp/gui/preview_thread.py13
-rw-r--r--src/avp/gui/undostack.py16
-rw-r--r--src/avp/toolkit/common.py18
-rw-r--r--src/avp/toolkit/frame.py26
-rw-r--r--src/avp/toolkit/visualizer.py87
-rw-r--r--tests/test_commandline_parser.py7
-rw-r--r--tests/test_comp_color.py22
-rw-r--r--tests/test_comp_image.py (renamed from tests/test_image_comp.py)0
-rw-r--r--tests/test_comp_life.py23
-rw-r--r--tests/test_comp_original.py (renamed from tests/test_classic_visualizer.py)9
-rw-r--r--tests/test_comp_spectrum.py20
-rw-r--r--tests/test_comp_text.py45
-rw-r--r--tests/test_comp_waveform.py17
-rw-r--r--tests/test_text_comp.py34
-rw-r--r--tests/test_toolkit_common.py33
-rw-r--r--uv.lock2
36 files changed, 1556 insertions, 1270 deletions
diff --git a/.gitignore b/.gitignore
index 6fe019a..9800ef6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@ __pycache__
5build/ 5build/
6dist/ 6dist/
7env/ 7env/
8prof/
8.venv/ 9.venv/
9.env/ 10.env/
10.vscode/ 11.vscode/
@@ -24,3 +25,5 @@ ffmpeg
24*.goutput* 25*.goutput*
25*.kate-swp 26*.kate-swp
26*.code-workspace 27*.code-workspace
28.coverage
29htmlcov/ \ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index a245e09..2f0647c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,7 +6,7 @@ build-backend = "uv_build"
6name = "audio-visualizer-python" 6name = "audio-visualizer-python"
7description = "Create audio visualization videos from a GUI or commandline" 7description = "Create audio visualization videos from a GUI or commandline"
8readme = "README.md" 8readme = "README.md"
9version = "2.2.0" 9version = "2.2.1"
10requires-python = ">= 3.12" 10requires-python = ">= 3.12"
11license = "MIT" 11license = "MIT"
12classifiers=[ 12classifiers=[
diff --git a/src/avp/__init__.py b/src/avp/__init__.py
index ea32f26..a88bf10 100644
--- a/src/avp/__init__.py
+++ b/src/avp/__init__.py
@@ -3,7 +3,7 @@ import os
3import logging 3import logging
4 4
5 5
6__version__ = "2.2.0" 6__version__ = "2.2.1"
7 7
8 8
9class Logger(logging.getLoggerClass()): 9class Logger(logging.getLoggerClass()):
diff --git a/src/avp/component.py b/src/avp/component.py
index 6c5e381..5906ab1 100644
--- a/src/avp/component.py
+++ b/src/avp/component.py
@@ -18,6 +18,7 @@ from .toolkit import (
18 setWidgetValue, 18 setWidgetValue,
19 connectWidget, 19 connectWidget,
20 rgbFromString, 20 rgbFromString,
21 randomColor,
21 blockSignals, 22 blockSignals,
22) 23)
23 24
@@ -635,9 +636,17 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
635 636
636 self._colorFuncs = {attr: makeColorFunc(attr) for attr in kwargs[kwarg]} 637 self._colorFuncs = {attr: makeColorFunc(attr) for attr in kwargs[kwarg]}
637 for attr, func in self._colorFuncs.items(): 638 for attr, func in self._colorFuncs.items():
639 colorText = self._trackedWidgets[attr].text()
640 if colorText == "":
641 rndColor = randomColor()
642 self._trackedWidgets[attr].setText(str(rndColor)[1:-1])
638 self._colorWidgets[attr].clicked.connect(func) 643 self._colorWidgets[attr].clicked.connect(func)
639 self._colorWidgets[attr].setStyleSheet( 644 self._colorWidgets[attr].setStyleSheet(
640 "QPushButton {" "background-color : #FFFFFF; outline: none; }" 645 "QPushButton {"
646 "background-color : %s; outline: none; }"
647 % QColor(
648 *rgbFromString(colorText) if colorText else rndColor
649 ).name()
641 ) 650 )
642 651
643 if kwarg == "relativeWidgets": 652 if kwarg == "relativeWidgets":
@@ -798,6 +807,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
798 if oldUserValue == newUserValue and oldRelativeVal != newRelativeVal: 807 if oldUserValue == newUserValue and oldRelativeVal != newRelativeVal:
799 # Float changed without pixel value changing, which 808 # Float changed without pixel value changing, which
800 # means the pixel value needs to be updated 809 # means the pixel value needs to be updated
810 # TODO QDoubleSpinBox doesn't work with relativeWidgets because of this
801 log.debug( 811 log.debug(
802 "Updating %s #%s's relative widget: %s", 812 "Updating %s #%s's relative widget: %s",
803 self.__class__.name, 813 self.__class__.name,
diff --git a/src/avp/components/color.py b/src/avp/components/color.py
index 1f32c23..cb0960a 100644
--- a/src/avp/components/color.py
+++ b/src/avp/components/color.py
@@ -2,7 +2,7 @@ from PyQt6 import QtGui
2import logging 2import logging
3 3
4from ..component import Component 4from ..component import Component
5from ..toolkit.frame import BlankFrame, FloodFrame, FramePainter, PaintColor 5from ..toolkit.frame import BlankFrame, FloodFrame, FramePainter
6 6
7 7
8log = logging.getLogger("AVP.Components.Color") 8log = logging.getLogger("AVP.Components.Color")
@@ -152,13 +152,13 @@ class Component(Component):
152 elif self.spread == 2: 152 elif self.spread == 2:
153 spread = QtGui.QGradient.Spread.RepeatSpread 153 spread = QtGui.QGradient.Spread.RepeatSpread
154 brush.setSpread(spread) 154 brush.setSpread(spread)
155 brush.setColorAt(0.0, PaintColor(*self.color1)) 155 brush.setColorAt(0.0, QtGui.QColor(*self.color1))
156 if self.trans: 156 if self.trans:
157 brush.setColorAt(1.0, PaintColor(0, 0, 0, 0)) 157 brush.setColorAt(1.0, QtGui.QColor(0, 0, 0, 0))
158 elif self.fillType == 1 and self.stretch: 158 elif self.fillType == 1 and self.stretch:
159 brush.setColorAt(0.2, PaintColor(*self.color2)) 159 brush.setColorAt(0.2, QtGui.QColor(*self.color2))
160 else: 160 else:
161 brush.setColorAt(1.0, PaintColor(*self.color2)) 161 brush.setColorAt(1.0, QtGui.QColor(*self.color2))
162 image.setBrush(brush) 162 image.setBrush(brush)
163 image.drawRect(self.x, self.y, self.sizeWidth, self.sizeHeight) 163 image.drawRect(self.x, self.y, self.sizeWidth, self.sizeHeight)
164 164
diff --git a/src/avp/components/color.ui b/src/avp/components/color.ui
index c1713fb..c36bdd8 100644
--- a/src/avp/components/color.ui
+++ b/src/avp/components/color.ui
@@ -74,7 +74,7 @@
74 </size> 74 </size>
75 </property> 75 </property>
76 <property name="text"> 76 <property name="text">
77 <string>0,0,0</string> 77 <string/>
78 </property> 78 </property>
79 <property name="maxLength"> 79 <property name="maxLength">
80 <number>12</number> 80 <number>12</number>
@@ -84,10 +84,10 @@
84 <item> 84 <item>
85 <spacer name="horizontalSpacer_9"> 85 <spacer name="horizontalSpacer_9">
86 <property name="orientation"> 86 <property name="orientation">
87 <enum>Qt::Horizontal</enum> 87 <enum>Qt::Orientation::Horizontal</enum>
88 </property> 88 </property>
89 <property name="sizeType"> 89 <property name="sizeType">
90 <enum>QSizePolicy::Fixed</enum> 90 <enum>QSizePolicy::Policy::Fixed</enum>
91 </property> 91 </property>
92 <property name="sizeHint" stdset="0"> 92 <property name="sizeHint" stdset="0">
93 <size> 93 <size>
@@ -176,7 +176,7 @@
176 <string>Width</string> 176 <string>Width</string>
177 </property> 177 </property>
178 <property name="alignment"> 178 <property name="alignment">
179 <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> 179 <set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter</set>
180 </property> 180 </property>
181 </widget> 181 </widget>
182 </item> 182 </item>
@@ -246,10 +246,10 @@
246 <item> 246 <item>
247 <spacer name="horizontalSpacer_7"> 247 <spacer name="horizontalSpacer_7">
248 <property name="orientation"> 248 <property name="orientation">
249 <enum>Qt::Horizontal</enum> 249 <enum>Qt::Orientation::Horizontal</enum>
250 </property> 250 </property>
251 <property name="sizeType"> 251 <property name="sizeType">
252 <enum>QSizePolicy::Fixed</enum> 252 <enum>QSizePolicy::Policy::Fixed</enum>
253 </property> 253 </property>
254 <property name="sizeHint" stdset="0"> 254 <property name="sizeHint" stdset="0">
255 <size> 255 <size>
@@ -370,7 +370,7 @@
370 <number>-1</number> 370 <number>-1</number>
371 </property> 371 </property>
372 <property name="sizeAdjustPolicy"> 372 <property name="sizeAdjustPolicy">
373 <enum>QComboBox::AdjustToContentsOnFirstShow</enum> 373 <enum>QComboBox::SizeAdjustPolicy::AdjustToContentsOnFirstShow</enum>
374 </property> 374 </property>
375 </widget> 375 </widget>
376 </item> 376 </item>
@@ -422,10 +422,10 @@
422 <item> 422 <item>
423 <spacer name="horizontalSpacer_2"> 423 <spacer name="horizontalSpacer_2">
424 <property name="orientation"> 424 <property name="orientation">
425 <enum>Qt::Horizontal</enum> 425 <enum>Qt::Orientation::Horizontal</enum>
426 </property> 426 </property>
427 <property name="sizeType"> 427 <property name="sizeType">
428 <enum>QSizePolicy::Minimum</enum> 428 <enum>QSizePolicy::Policy::Minimum</enum>
429 </property> 429 </property>
430 <property name="sizeHint" stdset="0"> 430 <property name="sizeHint" stdset="0">
431 <size> 431 <size>
@@ -461,7 +461,7 @@
461 <x>-1</x> 461 <x>-1</x>
462 <y>0</y> 462 <y>0</y>
463 <width>561</width> 463 <width>561</width>
464 <height>31</height> 464 <height>34</height>
465 </rect> 465 </rect>
466 </property> 466 </property>
467 <layout class="QHBoxLayout" name="horizontalLayout"> 467 <layout class="QHBoxLayout" name="horizontalLayout">
@@ -503,7 +503,7 @@
503 <string>End</string> 503 <string>End</string>
504 </property> 504 </property>
505 <property name="alignment"> 505 <property name="alignment">
506 <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> 506 <set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
507 </property> 507 </property>
508 </widget> 508 </widget>
509 </item> 509 </item>
@@ -523,7 +523,7 @@
523 <item> 523 <item>
524 <spacer name="horizontalSpacer"> 524 <spacer name="horizontalSpacer">
525 <property name="orientation"> 525 <property name="orientation">
526 <enum>Qt::Horizontal</enum> 526 <enum>Qt::Orientation::Horizontal</enum>
527 </property> 527 </property>
528 <property name="sizeHint" stdset="0"> 528 <property name="sizeHint" stdset="0">
529 <size> 529 <size>
@@ -543,7 +543,7 @@
543 <x>-1</x> 543 <x>-1</x>
544 <y>-1</y> 544 <y>-1</y>
545 <width>561</width> 545 <width>561</width>
546 <height>31</height> 546 <height>34</height>
547 </rect> 547 </rect>
548 </property> 548 </property>
549 <layout class="QHBoxLayout" name="horizontalLayout_3"> 549 <layout class="QHBoxLayout" name="horizontalLayout_3">
@@ -559,7 +559,7 @@
559 <string>Start</string> 559 <string>Start</string>
560 </property> 560 </property>
561 <property name="alignment"> 561 <property name="alignment">
562 <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> 562 <set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
563 </property> 563 </property>
564 </widget> 564 </widget>
565 </item> 565 </item>
@@ -588,7 +588,7 @@
588 <string>End</string> 588 <string>End</string>
589 </property> 589 </property>
590 <property name="alignment"> 590 <property name="alignment">
591 <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> 591 <set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
592 </property> 592 </property>
593 </widget> 593 </widget>
594 </item> 594 </item>
@@ -617,14 +617,14 @@
617 <string>Centre</string> 617 <string>Centre</string>
618 </property> 618 </property>
619 <property name="alignment"> 619 <property name="alignment">
620 <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> 620 <set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
621 </property> 621 </property>
622 </widget> 622 </widget>
623 </item> 623 </item>
624 <item> 624 <item>
625 <widget class="QSpinBox" name="spinBox_radialGradient_spread"> 625 <widget class="QSpinBox" name="spinBox_radialGradient_spread">
626 <property name="buttonSymbols"> 626 <property name="buttonSymbols">
627 <enum>QAbstractSpinBox::PlusMinus</enum> 627 <enum>QAbstractSpinBox::ButtonSymbols::PlusMinus</enum>
628 </property> 628 </property>
629 <property name="minimum"> 629 <property name="minimum">
630 <number>-10000</number> 630 <number>-10000</number>
@@ -640,7 +640,7 @@
640 <item> 640 <item>
641 <spacer name="horizontalSpacer_3"> 641 <spacer name="horizontalSpacer_3">
642 <property name="orientation"> 642 <property name="orientation">
643 <enum>Qt::Horizontal</enum> 643 <enum>Qt::Orientation::Horizontal</enum>
644 </property> 644 </property>
645 <property name="sizeHint" stdset="0"> 645 <property name="sizeHint" stdset="0">
646 <size> 646 <size>
diff --git a/src/avp/components/image.py b/src/avp/components/image.py
index bada15f..e012cec 100644
--- a/src/avp/components/image.py
+++ b/src/avp/components/image.py
@@ -4,13 +4,13 @@ import os
4from copy import copy 4from copy import copy
5 5
6from ..component import Component 6from ..component import Component
7from ..toolkit.frame import BlankFrame 7from ..toolkit.frame import BlankFrame, addShadow
8from .original import Component as Visualizer 8from ..toolkit.visualizer import createSpectrumArray
9 9
10 10
11class Component(Component): 11class Component(Component):
12 name = "Image" 12 name = "Image"
13 version = "2.0.0" 13 version = "2.1.0"
14 14
15 def widget(self, *args): 15 def widget(self, *args):
16 super().widget(*args) 16 super().widget(*args)
@@ -35,6 +35,7 @@ class Component(Component):
35 "mirror": self.page.checkBox_mirror, 35 "mirror": self.page.checkBox_mirror,
36 "respondToAudio": self.page.checkBox_respondToAudio, 36 "respondToAudio": self.page.checkBox_respondToAudio,
37 "sensitivity": self.page.spinBox_sensitivity, 37 "sensitivity": self.page.spinBox_sensitivity,
38 "shadow": self.page.checkBox_shadow,
38 }, 39 },
39 presetNames={ 40 presetNames={
40 "imagePath": "image", 41 "imagePath": "image",
@@ -75,31 +76,16 @@ class Component(Component):
75 # Trigger creation of new base image 76 # Trigger creation of new base image
76 self.existingImage = None 77 self.existingImage = None
77 78
78 smoothConstantDown = 0.08 + 0 79 self.spectrumArray = createSpectrumArray(
79 smoothConstantUp = 0.8 - 0 80 self,
80 self.lastSpectrum = None 81 self.completeAudioArray,
81 self.spectrumArray = {} 82 self.sampleSize,
82 83 0.08,
83 for i in range(0, len(self.completeAudioArray), self.sampleSize): 84 0.8,
84 if self.canceled: 85 self.sensitivity,
85 break 86 self.progressBarUpdate,
86 self.lastSpectrum = Visualizer.transformData( 87 self.progressBarSetText,
87 i, 88 )
88 self.completeAudioArray,
89 self.sampleSize,
90 smoothConstantDown,
91 smoothConstantUp,
92 self.lastSpectrum,
93 self.sensitivity,
94 )
95 self.spectrumArray[i] = copy(self.lastSpectrum)
96
97 progress = int(100 * (i / len(self.completeAudioArray)))
98 if progress >= 100:
99 progress = 100
100 pStr = "Analyzing audio: " + str(progress) + "%"
101 self.progressBarSetText.emit(pStr)
102 self.progressBarUpdate.emit(int(progress))
103 89
104 def frameRender(self, frameNo): 90 def frameRender(self, frameNo):
105 return self.drawFrame( 91 return self.drawFrame(
@@ -139,9 +125,16 @@ class Component(Component):
139 self.existingImage = image 125 self.existingImage = image
140 126
141 # Respond to audio 127 # Respond to audio
128 resolutionFactor = height / 1080
129 shadX = int(resolutionFactor * 1)
130 shadY = int(resolutionFactor * -1)
131 shadBlur = resolutionFactor * 3.50
142 scale = 0 132 scale = 0
143 if dynamicScale is not None: 133 if dynamicScale is not None:
144 scale = dynamicScale[36 * 4] / 4 134 scale = dynamicScale[36 * 4] / 4
135 shadX += int((scale / 4) * resolutionFactor)
136 shadY += int((scale / 2) * resolutionFactor)
137 shadBlur += (scale / 8) * resolutionFactor
145 image = ImageOps.contain( 138 image = ImageOps.contain(
146 image, 139 image,
147 ( 140 (
@@ -161,6 +154,8 @@ class Component(Component):
161 ) 154 )
162 if self.rotate != 0: 155 if self.rotate != 0:
163 frame = frame.rotate(self.rotate) 156 frame = frame.rotate(self.rotate)
157 if self.shadow:
158 frame = addShadow(frame, shadBlur, shadX, shadY)
164 159
165 return frame 160 return frame
166 161
diff --git a/src/avp/components/image.ui b/src/avp/components/image.ui
index 72593a3..b53c4b0 100644
--- a/src/avp/components/image.ui
+++ b/src/avp/components/image.ui
@@ -307,6 +307,16 @@
307 </spacer> 307 </spacer>
308 </item> 308 </item>
309 <item> 309 <item>
310 <widget class="QCheckBox" name="checkBox_shadow">
311 <property name="layoutDirection">
312 <enum>Qt::LayoutDirection::RightToLeft</enum>
313 </property>
314 <property name="text">
315 <string>Shadow</string>
316 </property>
317 </widget>
318 </item>
319 <item>
310 <widget class="QLabel" name="label_3"> 320 <widget class="QLabel" name="label_3">
311 <property name="sizePolicy"> 321 <property name="sizePolicy">
312 <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> 322 <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
diff --git a/src/avp/components/life.py b/src/avp/components/life.py
index 9e5e202..a062617 100644
--- a/src/avp/components/life.py
+++ b/src/avp/components/life.py
@@ -8,8 +8,8 @@ import logging
8 8
9 9
10from ..component import Component 10from ..component import Component
11from ..toolkit.frame import BlankFrame, scale 11from ..toolkit.frame import BlankFrame, scale, addShadow
12from .original import Component as Visualizer 12from ..toolkit.visualizer import createSpectrumArray
13 13
14 14
15log = logging.getLogger("AVP.Component.Life") 15log = logging.getLogger("AVP.Component.Life")
@@ -17,7 +17,7 @@ log = logging.getLogger("AVP.Component.Life")
17 17
18class Component(Component): 18class Component(Component):
19 name = "Conway's Game of Life" 19 name = "Conway's Game of Life"
20 version = "2.0.0" 20 version = "2.0.1"
21 21
22 def widget(self, *args): 22 def widget(self, *args):
23 super().widget(*args) 23 super().widget(*args)
@@ -27,26 +27,26 @@ class Component(Component):
27 # https://conwaylife.com/wiki/Queen_bee_shuttle 27 # https://conwaylife.com/wiki/Queen_bee_shuttle
28 self.startingGrid = set( 28 self.startingGrid = set(
29 [ 29 [
30 (3, 7), 30 (3, 11),
31 (3, 8), 31 (3, 12),
32 (4, 7), 32 (4, 11),
33 (4, 8), 33 (4, 12),
34 (8, 7), 34 (8, 11),
35 (9, 6), 35 (9, 10),
36 (9, 8), 36 (9, 12),
37 (10, 5),
38 (10, 9), 37 (10, 9),
39 (11, 6), 38 (10, 13),
40 (11, 7), 39 (11, 10),
41 (11, 8), 40 (11, 11),
42 (12, 4), 41 (11, 12),
43 (12, 5), 42 (12, 8),
44 (12, 9), 43 (12, 9),
45 (12, 10), 44 (12, 13),
46 (23, 6), 45 (12, 14),
47 (23, 7), 46 (23, 10),
48 (24, 6), 47 (23, 11),
49 (24, 7), 48 (24, 10),
49 (24, 11),
50 ] 50 ]
51 ) 51 )
52 52
@@ -163,41 +163,27 @@ class Component(Component):
163 def previewRender(self): 163 def previewRender(self):
164 image = self.drawGrid(self.startingGrid, self.color) 164 image = self.drawGrid(self.startingGrid, self.color)
165 image = self.addKaleidoscopeEffect(image) 165 image = self.addKaleidoscopeEffect(image)
166 image = self.addShadow(image) 166 if self.shadow:
167 image = addShadow(image, 5.00, -2, 2)
167 image = self.addGridLines(image) 168 image = self.addGridLines(image)
168 return image 169 return image
169 170
170 def preFrameRender(self, *args, **kwargs): 171 def preFrameRender(self, *args, **kwargs):
171 super().preFrameRender(*args, **kwargs) 172 super().preFrameRender(*args, **kwargs)
172 self.tickGrids = {0: self.startingGrid} 173 self.tickGrids = {0: self.startingGrid}
173
174 smoothConstantDown = 0.08 + 0
175 smoothConstantUp = 0.8 - 0
176 self.lastSpectrum = None
177 self.spectrumArray = {}
178 if self.sensitivity == 0: 174 if self.sensitivity == 0:
179 return 175 return
180 176
181 for i in range(0, len(self.completeAudioArray), self.sampleSize): 177 self.spectrumArray = createSpectrumArray(
182 if self.canceled: 178 self,
183 break 179 self.completeAudioArray,
184 self.lastSpectrum = Visualizer.transformData( 180 self.sampleSize,
185 i, 181 0.08,
186 self.completeAudioArray, 182 0.8,
187 self.sampleSize, 183 20,
188 smoothConstantDown, 184 self.progressBarUpdate,
189 smoothConstantUp, 185 self.progressBarSetText,
190 self.lastSpectrum, 186 )
191 self.sensitivity,
192 )
193 self.spectrumArray[i] = copy(self.lastSpectrum)
194
195 progress = int(100 * (i / len(self.completeAudioArray)))
196 if progress >= 100:
197 progress = 100
198 pStr = "Analyzing audio: " + str(progress) + "%"
199 self.progressBarSetText.emit(pStr)
200 self.progressBarUpdate.emit(int(progress))
201 187
202 def properties(self): 188 def properties(self):
203 if self.customImg and (not self.image or not os.path.exists(self.image)): 189 if self.customImg and (not self.image or not os.path.exists(self.image)):
@@ -256,21 +242,11 @@ class Component(Component):
256 if not self.customImg: 242 if not self.customImg:
257 image = Image.alpha_composite(previousGridImage, image) 243 image = Image.alpha_composite(previousGridImage, image)
258 image = self.addKaleidoscopeEffect(image) 244 image = self.addKaleidoscopeEffect(image)
259 image = self.addShadow(image) 245 if self.shadow:
246 image = addShadow(image, 5.00, -2, 2)
260 image = self.addGridLines(image) 247 image = self.addGridLines(image)
261 return image 248 return image
262 249
263 def addShadow(self, frame):
264 if not self.shadow:
265 return frame
266
267 shadImg = ImageEnhance.Contrast(frame).enhance(0.0)
268 shadImg = shadImg.filter(ImageFilter.GaussianBlur(5.00))
269 shadImg = ImageChops.offset(shadImg, -2, 2)
270 shadImg.paste(frame, box=(0, 0), mask=frame)
271 frame = shadImg
272 return frame
273
274 def addGridLines(self, frame): 250 def addGridLines(self, frame):
275 if not self.showGrid: 251 if not self.showGrid:
276 return frame 252 return frame
@@ -299,11 +275,6 @@ class Component(Component):
299 flippedImage = frame.transpose(Image.Transpose.FLIP_LEFT_RIGHT) 275 flippedImage = frame.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
300 frame.paste(flippedImage, (0, 0), mask=flippedImage) 276 frame.paste(flippedImage, (0, 0), mask=flippedImage)
301 277
302 flippedImage = frame.transpose(Image.Transpose.ROTATE_90)
303 frame.paste(flippedImage, (0, 0), mask=flippedImage)
304
305 flippedImage = frame.transpose(Image.Transpose.ROTATE_270)
306 frame.paste(flippedImage, (0, 0), mask=flippedImage)
307 return frame 278 return frame
308 279
309 def drawGrid(self, grid, color, spectrumData=None, didntChange=None): 280 def drawGrid(self, grid, color, spectrumData=None, didntChange=None):
@@ -506,10 +477,10 @@ class Component(Component):
506 477
507 for x, y in grid: 478 for x, y in grid:
508 drawPtX = x * self.pxWidth 479 drawPtX = x * self.pxWidth
509 if drawPtX > self.width: 480 if drawPtX > self.width or drawPtX + self.pxWidth < 0:
510 continue 481 continue
511 drawPtY = y * self.pxHeight 482 drawPtY = y * self.pxHeight
512 if drawPtY > self.height: 483 if drawPtY > self.height or drawPtY + self.pxHeight < 0:
513 continue 484 continue
514 485
515 audioMorphWidth = ( 486 audioMorphWidth = (
diff --git a/src/avp/components/life.ui b/src/avp/components/life.ui
index a0c8999..0070fa4 100644
--- a/src/avp/components/life.ui
+++ b/src/avp/components/life.ui
@@ -68,7 +68,7 @@
68 </size> 68 </size>
69 </property> 69 </property>
70 <property name="text"> 70 <property name="text">
71 <string>255,255,255</string> 71 <string></string>
72 </property> 72 </property>
73 </widget> 73 </widget>
74 </item> 74 </item>
diff --git a/src/avp/components/original.py b/src/avp/components/original.py
index 64eba4d..0da78dc 100644
--- a/src/avp/components/original.py
+++ b/src/avp/components/original.py
@@ -4,11 +4,12 @@ from copy import copy
4 4
5from ..component import Component 5from ..component import Component
6from ..toolkit.frame import BlankFrame 6from ..toolkit.frame import BlankFrame
7from ..toolkit.visualizer import createSpectrumArray
7 8
8 9
9class Component(Component): 10class Component(Component):
10 name = "Classic Visualizer" 11 name = "Classic Visualizer"
11 version = "1.0.0" 12 version = "1.1.0"
12 13
13 def names(*args): 14 def names(*args):
14 return ["Original Audio Visualization"] 15 return ["Original Audio Visualization"]
@@ -18,6 +19,7 @@ class Component(Component):
18 19
19 def widget(self, *args): 20 def widget(self, *args):
20 self.scale = 20 21 self.scale = 20
22 self.bars = 63
21 self.y = 0 23 self.y = 0
22 super().widget(*args) 24 super().widget(*args)
23 25
@@ -27,15 +29,14 @@ class Component(Component):
27 self.page.comboBox_visLayout.addItem("Top") 29 self.page.comboBox_visLayout.addItem("Top")
28 self.page.comboBox_visLayout.setCurrentIndex(0) 30 self.page.comboBox_visLayout.setCurrentIndex(0)
29 31
30 self.page.lineEdit_visColor.setText("255,255,255")
31
32 self.trackWidgets( 32 self.trackWidgets(
33 { 33 {
34 "visColor": self.page.lineEdit_visColor, 34 "visColor": self.page.lineEdit_visColor,
35 "layout": self.page.comboBox_visLayout, 35 "layout": self.page.comboBox_visLayout,
36 "scale": self.page.spinBox_scale, 36 "scale": self.page.spinBox_scale,
37 "y": self.page.spinBox_y, 37 "y": self.page.spinBox_y,
38 "smooth": self.page.spinBox_smooth, 38 "smooth": self.page.spinBox_sensitivity,
39 "bars": self.page.spinBox_bars,
39 }, 40 },
40 colorWidgets={ 41 colorWidgets={
41 "visColor": self.page.pushButton_visColor, 42 "visColor": self.page.pushButton_visColor,
@@ -59,29 +60,16 @@ class Component(Component):
59 super().preFrameRender(**kwargs) 60 super().preFrameRender(**kwargs)
60 smoothConstantDown = 0.08 if not self.smooth else self.smooth / 15 61 smoothConstantDown = 0.08 if not self.smooth else self.smooth / 15
61 smoothConstantUp = 0.8 if not self.smooth else self.smooth / 15 62 smoothConstantUp = 0.8 if not self.smooth else self.smooth / 15
62 self.lastSpectrum = None 63 self.spectrumArray = createSpectrumArray(
63 self.spectrumArray = {} 64 self,
64 65 self.completeAudioArray,
65 for i in range(0, len(self.completeAudioArray), self.sampleSize): 66 self.sampleSize,
66 if self.canceled: 67 smoothConstantDown,
67 break 68 smoothConstantUp,
68 self.lastSpectrum = self.transformData( 69 self.scale,
69 i, 70 self.progressBarUpdate,
70 self.completeAudioArray, 71 self.progressBarSetText,
71 self.sampleSize, 72 )
72 smoothConstantDown,
73 smoothConstantUp,
74 self.lastSpectrum,
75 self.scale,
76 )
77 self.spectrumArray[i] = copy(self.lastSpectrum)
78
79 progress = int(100 * (i / len(self.completeAudioArray)))
80 if progress >= 100:
81 progress = 100
82 pStr = "Analyzing audio: " + str(progress) + "%"
83 self.progressBarSetText.emit(pStr)
84 self.progressBarUpdate.emit(int(progress))
85 73
86 def frameRender(self, frameNo): 74 def frameRender(self, frameNo):
87 arrayNo = frameNo * self.sampleSize 75 arrayNo = frameNo * self.sampleSize
@@ -93,60 +81,10 @@ class Component(Component):
93 self.layout, 81 self.layout,
94 ) 82 )
95 83
96 @staticmethod
97 def transformData(
98 i,
99 completeAudioArray,
100 sampleSize,
101 smoothConstantDown,
102 smoothConstantUp,
103 lastSpectrum,
104 scale,
105 ):
106 if len(completeAudioArray) < (i + sampleSize):
107 sampleSize = len(completeAudioArray) - i
108
109 window = numpy.hanning(sampleSize)
110 data = completeAudioArray[i : i + sampleSize][::1] * window
111 paddedSampleSize = 2048
112 paddedData = numpy.pad(data, (0, paddedSampleSize - sampleSize), "constant")
113 spectrum = numpy.fft.fft(paddedData)
114 sample_rate = 44100
115 frequencies = numpy.fft.fftfreq(len(spectrum), 1.0 / sample_rate)
116
117 y = abs(spectrum[0 : int(paddedSampleSize / 2) - 1])
118
119 # filter the noise away
120 # y[y<80] = 0
121
122 with numpy.errstate(divide="ignore"):
123 y = scale * numpy.log10(y)
124
125 y[numpy.isinf(y)] = 0
126
127 if lastSpectrum is not None:
128 lastSpectrum[y < lastSpectrum] = y[
129 y < lastSpectrum
130 ] * smoothConstantDown + lastSpectrum[y < lastSpectrum] * (
131 1 - smoothConstantDown
132 )
133
134 lastSpectrum[y >= lastSpectrum] = y[
135 y >= lastSpectrum
136 ] * smoothConstantUp + lastSpectrum[y >= lastSpectrum] * (
137 1 - smoothConstantUp
138 )
139 else:
140 lastSpectrum = y
141
142 x = frequencies[0 : int(paddedSampleSize / 2) - 1]
143
144 return lastSpectrum
145
146 def drawBars(self, width, height, spectrum, color, layout): 84 def drawBars(self, width, height, spectrum, color, layout):
147 bigYCoord = height - height / 8 85 bigYCoord = height - height / 8
148 smallYCoord = height / 1200 86 smallYCoord = height / 1200
149 bigXCoord = width / 64 87 bigXCoord = width / (self.bars + 1)
150 middleXCoord = bigXCoord / 2 88 middleXCoord = bigXCoord / 2
151 smallXCoord = bigXCoord / 4 89 smallXCoord = bigXCoord / 4
152 90
@@ -155,7 +93,7 @@ class Component(Component):
155 r, g, b = color 93 r, g, b = color
156 color2 = (r, g, b, 125) 94 color2 = (r, g, b, 125)
157 95
158 for i in range(0, 63): 96 for i in range(self.bars):
159 x0 = middleXCoord + i * bigXCoord 97 x0 = middleXCoord + i * bigXCoord
160 y0 = bigYCoord + smallXCoord 98 y0 = bigYCoord + smallXCoord
161 y1 = bigYCoord + smallXCoord - spectrum[i * 4] * smallYCoord - middleXCoord 99 y1 = bigYCoord + smallXCoord - spectrum[i * 4] * smallYCoord - middleXCoord
diff --git a/src/avp/components/original.ui b/src/avp/components/original.ui
index c7b7e22..8dbdaa2 100644
--- a/src/avp/components/original.ui
+++ b/src/avp/components/original.ui
@@ -44,10 +44,10 @@
44 <item> 44 <item>
45 <spacer name="horizontalSpacer_5"> 45 <spacer name="horizontalSpacer_5">
46 <property name="orientation"> 46 <property name="orientation">
47 <enum>Qt::Horizontal</enum> 47 <enum>Qt::Orientation::Horizontal</enum>
48 </property> 48 </property>
49 <property name="sizeType"> 49 <property name="sizeType">
50 <enum>QSizePolicy::Fixed</enum> 50 <enum>QSizePolicy::Policy::Fixed</enum>
51 </property> 51 </property>
52 <property name="sizeHint" stdset="0"> 52 <property name="sizeHint" stdset="0">
53 <size> 53 <size>
@@ -84,15 +84,19 @@
84 </widget> 84 </widget>
85 </item> 85 </item>
86 <item> 86 <item>
87 <widget class="QLineEdit" name="lineEdit_visColor"/> 87 <widget class="QLineEdit" name="lineEdit_visColor">
88 <property name="text">
89 <string></string>
90 </property>
91 </widget>
88 </item> 92 </item>
89 <item> 93 <item>
90 <spacer name="horizontalSpacer_4"> 94 <spacer name="horizontalSpacer_4">
91 <property name="orientation"> 95 <property name="orientation">
92 <enum>Qt::Horizontal</enum> 96 <enum>Qt::Orientation::Horizontal</enum>
93 </property> 97 </property>
94 <property name="sizeType"> 98 <property name="sizeType">
95 <enum>QSizePolicy::Fixed</enum> 99 <enum>QSizePolicy::Policy::Fixed</enum>
96 </property> 100 </property>
97 <property name="sizeHint" stdset="0"> 101 <property name="sizeHint" stdset="0">
98 <size> 102 <size>
@@ -112,7 +116,7 @@
112 <item> 116 <item>
113 <widget class="QSpinBox" name="spinBox_y"> 117 <widget class="QSpinBox" name="spinBox_y">
114 <property name="buttonSymbols"> 118 <property name="buttonSymbols">
115 <enum>QAbstractSpinBox::UpDownArrows</enum> 119 <enum>QAbstractSpinBox::ButtonSymbols::UpDownArrows</enum>
116 </property> 120 </property>
117 <property name="minimum"> 121 <property name="minimum">
118 <number>-5000</number> 122 <number>-5000</number>
@@ -131,7 +135,7 @@
131 <item> 135 <item>
132 <spacer name="horizontalSpacer_2"> 136 <spacer name="horizontalSpacer_2">
133 <property name="orientation"> 137 <property name="orientation">
134 <enum>Qt::Horizontal</enum> 138 <enum>Qt::Orientation::Horizontal</enum>
135 </property> 139 </property>
136 <property name="sizeHint" stdset="0"> 140 <property name="sizeHint" stdset="0">
137 <size> 141 <size>
@@ -158,7 +162,7 @@
158 <item> 162 <item>
159 <widget class="QSpinBox" name="spinBox_scale"> 163 <widget class="QSpinBox" name="spinBox_scale">
160 <property name="buttonSymbols"> 164 <property name="buttonSymbols">
161 <enum>QAbstractSpinBox::PlusMinus</enum> 165 <enum>QAbstractSpinBox::ButtonSymbols::PlusMinus</enum>
162 </property> 166 </property>
163 <property name="minimum"> 167 <property name="minimum">
164 <number>1</number> 168 <number>1</number>
@@ -169,12 +173,26 @@
169 </widget> 173 </widget>
170 </item> 174 </item>
171 <item> 175 <item>
176 <widget class="QLabel" name="label_sensitivity">
177 <property name="text">
178 <string>Sensitivity</string>
179 </property>
180 </widget>
181 </item>
182 <item>
183 <widget class="QSpinBox" name="spinBox_sensitivity">
184 <property name="maximum">
185 <number>5</number>
186 </property>
187 </widget>
188 </item>
189 <item>
172 <spacer name="horizontalSpacer_3"> 190 <spacer name="horizontalSpacer_3">
173 <property name="orientation"> 191 <property name="orientation">
174 <enum>Qt::Horizontal</enum> 192 <enum>Qt::Orientation::Horizontal</enum>
175 </property> 193 </property>
176 <property name="sizeType"> 194 <property name="sizeType">
177 <enum>QSizePolicy::Expanding</enum> 195 <enum>QSizePolicy::Policy::Expanding</enum>
178 </property> 196 </property>
179 <property name="sizeHint" stdset="0"> 197 <property name="sizeHint" stdset="0">
180 <size> 198 <size>
@@ -189,29 +207,35 @@
189 <item> 207 <item>
190 <layout class="QHBoxLayout" name="horizontalLayout"> 208 <layout class="QHBoxLayout" name="horizontalLayout">
191 <property name="sizeConstraint"> 209 <property name="sizeConstraint">
192 <enum>QLayout::SetDefaultConstraint</enum> 210 <enum>QLayout::SizeConstraint::SetDefaultConstraint</enum>
193 </property> 211 </property>
194 <property name="leftMargin"> 212 <property name="leftMargin">
195 <number>4</number> 213 <number>4</number>
196 </property> 214 </property>
197 <item> 215 <item>
198 <widget class="QLabel" name="label_smooth"> 216 <widget class="QLabel" name="label_bars">
199 <property name="text"> 217 <property name="text">
200 <string>Sensitivity</string> 218 <string>Bars</string>
201 </property> 219 </property>
202 </widget> 220 </widget>
203 </item> 221 </item>
204 <item> 222 <item>
205 <widget class="QSpinBox" name="spinBox_smooth"> 223 <widget class="QSpinBox" name="spinBox_bars">
224 <property name="minimum">
225 <number>63</number>
226 </property>
206 <property name="maximum"> 227 <property name="maximum">
207 <number>5</number> 228 <number>64</number>
229 </property>
230 <property name="value">
231 <number>63</number>
208 </property> 232 </property>
209 </widget> 233 </widget>
210 </item> 234 </item>
211 <item> 235 <item>
212 <spacer name="horizontalSpacer"> 236 <spacer name="horizontalSpacer">
213 <property name="orientation"> 237 <property name="orientation">
214 <enum>Qt::Horizontal</enum> 238 <enum>Qt::Orientation::Horizontal</enum>
215 </property> 239 </property>
216 <property name="sizeHint" stdset="0"> 240 <property name="sizeHint" stdset="0">
217 <size> 241 <size>
@@ -226,7 +250,7 @@
226 <item> 250 <item>
227 <spacer name="verticalSpacer"> 251 <spacer name="verticalSpacer">
228 <property name="orientation"> 252 <property name="orientation">
229 <enum>Qt::Vertical</enum> 253 <enum>Qt::Orientation::Vertical</enum>
230 </property> 254 </property>
231 <property name="sizeHint" stdset="0"> 255 <property name="sizeHint" stdset="0">
232 <size> 256 <size>
diff --git a/src/avp/components/text.py b/src/avp/components/text.py
index 40c981a..bee117e 100644
--- a/src/avp/components/text.py
+++ b/src/avp/components/text.py
@@ -5,7 +5,7 @@ import os
5import logging 5import logging
6 6
7from ..component import Component 7from ..component import Component
8from ..toolkit.frame import FramePainter, PaintColor 8from ..toolkit.frame import FramePainter, addShadow
9 9
10log = logging.getLogger("AVP.Components.Text") 10log = logging.getLogger("AVP.Components.Text")
11 11
@@ -26,7 +26,6 @@ class Component(Component):
26 self.page.comboBox_textAlign.addItem("Right") 26 self.page.comboBox_textAlign.addItem("Right")
27 self.page.comboBox_textAlign.setCurrentIndex(int(self.alignment)) 27 self.page.comboBox_textAlign.setCurrentIndex(int(self.alignment))
28 self.page.spinBox_fontSize.setValue(int(self.fontSize)) 28 self.page.spinBox_fontSize.setValue(int(self.fontSize))
29 self.page.lineEdit_title.setText(self.title)
30 self.page.pushButton_center.clicked.connect(self.centerXY) 29 self.page.pushButton_center.clicked.connect(self.centerXY)
31 30
32 self.page.fontComboBox_titleFont.currentFontChanged.connect( 31 self.page.fontComboBox_titleFont.currentFontChanged.connect(
@@ -35,7 +34,7 @@ class Component(Component):
35 # The QFontComboBox must be connected directly to the Qt Signal 34 # The QFontComboBox must be connected directly to the Qt Signal
36 # which triggers the preview to update. 35 # which triggers the preview to update.
37 # This unfortunately makes changing the font into a non-undoable action. 36 # This unfortunately makes changing the font into a non-undoable action.
38 # Must be something broken in the conversion to a ComponentAction 37 # Fix requires updating ComponentAction to handle fonts
39 38
40 self.trackWidgets( 39 self.trackWidgets(
41 { 40 {
@@ -173,7 +172,7 @@ class Component(Component):
173 path.addText(x, y, font, self.title) 172 path.addText(x, y, font, self.title)
174 path = outliner.createStroke(path) 173 path = outliner.createStroke(path)
175 image.setPen(QtCore.Qt.PenStyle.NoPen) 174 image.setPen(QtCore.Qt.PenStyle.NoPen)
176 image.setBrush(PaintColor(*self.strokeColor)) 175 image.setBrush(QtGui.QColor(*self.strokeColor))
177 image.drawPath(path) 176 image.drawPath(path)
178 177
179 image.setFont(font) 178 image.setFont(font)
@@ -183,11 +182,7 @@ class Component(Component):
183 # turn QImage into Pillow frame 182 # turn QImage into Pillow frame
184 frame = image.finalize() 183 frame = image.finalize()
185 if self.shadow: 184 if self.shadow:
186 shadImg = ImageEnhance.Contrast(frame).enhance(0.0) 185 frame = addShadow(frame, self.shadBlur / 10, self.shadX, self.shadY)
187 shadImg = shadImg.filter(ImageFilter.GaussianBlur(self.shadBlur))
188 shadImg = ImageChops.offset(shadImg, self.shadX, self.shadY)
189 shadImg.paste(frame, box=(0, 0), mask=frame)
190 frame = shadImg
191 186
192 return frame 187 return frame
193 188
diff --git a/src/avp/components/text.ui b/src/avp/components/text.ui
index b62e0ed..ce961fe 100644
--- a/src/avp/components/text.ui
+++ b/src/avp/components/text.ui
@@ -15,646 +15,645 @@
15 </property> 15 </property>
16 <layout class="QVBoxLayout" name="verticalLayout_2"> 16 <layout class="QVBoxLayout" name="verticalLayout_2">
17 <item> 17 <item>
18 <layout class="QVBoxLayout" name="verticalLayout"> 18 <layout class="QHBoxLayout" name="horizontalLayout">
19 <property name="spacing"> 19 <item>
20 <number>6</number> 20 <widget class="QLabel" name="label_title">
21 </property> 21 <property name="text">
22 <property name="sizeConstraint"> 22 <string>Title</string>
23 <enum>QLayout::SetDefaultConstraint</enum> 23 </property>
24 </property> 24 </widget>
25 </item>
26 <item>
27 <widget class="QLineEdit" name="lineEdit_title">
28 <property name="sizePolicy">
29 <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
30 <horstretch>0</horstretch>
31 <verstretch>0</verstretch>
32 </sizepolicy>
33 </property>
34 <property name="minimumSize">
35 <size>
36 <width>0</width>
37 <height>0</height>
38 </size>
39 </property>
40 <property name="text">
41 <string>Text</string>
42 </property>
43 </widget>
44 </item>
45 <item>
46 <widget class="QLabel" name="label">
47 <property name="sizePolicy">
48 <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
49 <horstretch>0</horstretch>
50 <verstretch>0</verstretch>
51 </sizepolicy>
52 </property>
53 <property name="text">
54 <string>Font</string>
55 </property>
56 </widget>
57 </item>
58 <item>
59 <widget class="QFontComboBox" name="fontComboBox_titleFont">
60 <property name="sizePolicy">
61 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
62 <horstretch>0</horstretch>
63 <verstretch>0</verstretch>
64 </sizepolicy>
65 </property>
66 <property name="minimumSize">
67 <size>
68 <width>0</width>
69 <height>0</height>
70 </size>
71 </property>
72 </widget>
73 </item>
74 </layout>
75 </item>
76 <item>
77 <layout class="QHBoxLayout" name="horizontalLayout_7">
25 <property name="leftMargin"> 78 <property name="leftMargin">
26 <number>4</number> 79 <number>0</number>
27 </property> 80 </property>
28 <item> 81 <item>
29 <layout class="QHBoxLayout" name="horizontalLayout"> 82 <widget class="QLabel" name="label_textLayout">
30 <item> 83 <property name="sizePolicy">
31 <widget class="QLabel" name="label_title"> 84 <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
32 <property name="text"> 85 <horstretch>0</horstretch>
33 <string>Title</string> 86 <verstretch>0</verstretch>
34 </property> 87 </sizepolicy>
35 </widget> 88 </property>
36 </item> 89 <property name="text">
37 <item> 90 <string>Text Layout</string>
38 <widget class="QLineEdit" name="lineEdit_title"> 91 </property>
39 <property name="sizePolicy"> 92 </widget>
40 <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> 93 </item>
41 <horstretch>0</horstretch> 94 <item>
42 <verstretch>0</verstretch> 95 <widget class="QComboBox" name="comboBox_textAlign">
43 </sizepolicy> 96 <property name="sizePolicy">
44 </property> 97 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
45 <property name="minimumSize"> 98 <horstretch>0</horstretch>
46 <size> 99 <verstretch>0</verstretch>
47 <width>0</width> 100 </sizepolicy>
48 <height>0</height> 101 </property>
49 </size> 102 <property name="maximumSize">
50 </property> 103 <size>
51 <property name="text"> 104 <width>100</width>
52 <string>Testing New GUI</string> 105 <height>16777215</height>
53 </property> 106 </size>
54 </widget> 107 </property>
55 </item> 108 </widget>
56 <item> 109 </item>
57 <widget class="QLabel" name="label"> 110 <item>
58 <property name="sizePolicy"> 111 <spacer name="horizontalSpacer_2">
59 <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> 112 <property name="orientation">
60 <horstretch>0</horstretch> 113 <enum>Qt::Orientation::Horizontal</enum>
61 <verstretch>0</verstretch> 114 </property>
62 </sizepolicy> 115 <property name="sizeType">
63 </property> 116 <enum>QSizePolicy::Policy::Fixed</enum>
64 <property name="text"> 117 </property>
65 <string>Font</string> 118 <property name="sizeHint" stdset="0">
66 </property> 119 <size>
67 </widget> 120 <width>5</width>
68 </item> 121 <height>20</height>
69 <item> 122 </size>
70 <widget class="QFontComboBox" name="fontComboBox_titleFont"> 123 </property>
71 <property name="sizePolicy"> 124 </spacer>
72 <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> 125 </item>
73 <horstretch>0</horstretch> 126 <item>
74 <verstretch>0</verstretch> 127 <widget class="QPushButton" name="pushButton_center">
75 </sizepolicy> 128 <property name="sizePolicy">
76 </property> 129 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
77 <property name="minimumSize"> 130 <horstretch>0</horstretch>
78 <size> 131 <verstretch>0</verstretch>
79 <width>0</width> 132 </sizepolicy>
80 <height>0</height> 133 </property>
81 </size> 134 <property name="text">
82 </property> 135 <string>Center Text</string>
83 </widget> 136 </property>
84 </item> 137 </widget>
85 </layout>
86 </item> 138 </item>
87 <item> 139 <item>
88 <layout class="QHBoxLayout" name="horizontalLayout_7"> 140 <spacer name="horizontalSpacer_6">
89 <property name="leftMargin"> 141 <property name="orientation">
142 <enum>Qt::Orientation::Horizontal</enum>
143 </property>
144 <property name="sizeType">
145 <enum>QSizePolicy::Policy::Fixed</enum>
146 </property>
147 <property name="sizeHint" stdset="0">
148 <size>
149 <width>5</width>
150 <height>20</height>
151 </size>
152 </property>
153 </spacer>
154 </item>
155 <item>
156 <widget class="QLabel" name="label_xTitleAlign">
157 <property name="sizePolicy">
158 <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
159 <horstretch>0</horstretch>
160 <verstretch>0</verstretch>
161 </sizepolicy>
162 </property>
163 <property name="text">
164 <string>X</string>
165 </property>
166 </widget>
167 </item>
168 <item>
169 <widget class="QSpinBox" name="spinBox_xTextAlign">
170 <property name="sizePolicy">
171 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
172 <horstretch>0</horstretch>
173 <verstretch>0</verstretch>
174 </sizepolicy>
175 </property>
176 <property name="maximumSize">
177 <size>
178 <width>50</width>
179 <height>16777215</height>
180 </size>
181 </property>
182 <property name="baseSize">
183 <size>
184 <width>0</width>
185 <height>0</height>
186 </size>
187 </property>
188 <property name="minimum">
90 <number>0</number> 189 <number>0</number>
91 </property> 190 </property>
92 <item> 191 <property name="maximum">
93 <widget class="QLabel" name="label_textLayout"> 192 <number>999999999</number>
94 <property name="sizePolicy"> 193 </property>
95 <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> 194 <property name="value">
96 <horstretch>0</horstretch> 195 <number>0</number>
97 <verstretch>0</verstretch> 196 </property>
98 </sizepolicy> 197 </widget>
99 </property>
100 <property name="text">
101 <string>Text Layout</string>
102 </property>
103 </widget>
104 </item>
105 <item>
106 <widget class="QComboBox" name="comboBox_textAlign">
107 <property name="sizePolicy">
108 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
109 <horstretch>0</horstretch>
110 <verstretch>0</verstretch>
111 </sizepolicy>
112 </property>
113 <property name="maximumSize">
114 <size>
115 <width>100</width>
116 <height>16777215</height>
117 </size>
118 </property>
119 </widget>
120 </item>
121 <item>
122 <spacer name="horizontalSpacer_2">
123 <property name="orientation">
124 <enum>Qt::Horizontal</enum>
125 </property>
126 <property name="sizeType">
127 <enum>QSizePolicy::Fixed</enum>
128 </property>
129 <property name="sizeHint" stdset="0">
130 <size>
131 <width>5</width>
132 <height>20</height>
133 </size>
134 </property>
135 </spacer>
136 </item>
137 <item>
138 <widget class="QPushButton" name="pushButton_center">
139 <property name="sizePolicy">
140 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
141 <horstretch>0</horstretch>
142 <verstretch>0</verstretch>
143 </sizepolicy>
144 </property>
145 <property name="text">
146 <string>Center Text</string>
147 </property>
148 </widget>
149 </item>
150 <item>
151 <spacer name="horizontalSpacer_6">
152 <property name="orientation">
153 <enum>Qt::Horizontal</enum>
154 </property>
155 <property name="sizeType">
156 <enum>QSizePolicy::Fixed</enum>
157 </property>
158 <property name="sizeHint" stdset="0">
159 <size>
160 <width>5</width>
161 <height>20</height>
162 </size>
163 </property>
164 </spacer>
165 </item>
166 <item>
167 <widget class="QLabel" name="label_xTitleAlign">
168 <property name="sizePolicy">
169 <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
170 <horstretch>0</horstretch>
171 <verstretch>0</verstretch>
172 </sizepolicy>
173 </property>
174 <property name="text">
175 <string>X</string>
176 </property>
177 </widget>
178 </item>
179 <item>
180 <widget class="QSpinBox" name="spinBox_xTextAlign">
181 <property name="sizePolicy">
182 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
183 <horstretch>0</horstretch>
184 <verstretch>0</verstretch>
185 </sizepolicy>
186 </property>
187 <property name="maximumSize">
188 <size>
189 <width>50</width>
190 <height>16777215</height>
191 </size>
192 </property>
193 <property name="baseSize">
194 <size>
195 <width>0</width>
196 <height>0</height>
197 </size>
198 </property>
199 <property name="minimum">
200 <number>0</number>
201 </property>
202 <property name="maximum">
203 <number>999999999</number>
204 </property>
205 <property name="value">
206 <number>0</number>
207 </property>
208 </widget>
209 </item>
210 <item>
211 <widget class="QLabel" name="label_yTitleAlign">
212 <property name="sizePolicy">
213 <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
214 <horstretch>0</horstretch>
215 <verstretch>0</verstretch>
216 </sizepolicy>
217 </property>
218 <property name="text">
219 <string>Y</string>
220 </property>
221 </widget>
222 </item>
223 <item>
224 <widget class="QSpinBox" name="spinBox_yTextAlign">
225 <property name="sizePolicy">
226 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
227 <horstretch>0</horstretch>
228 <verstretch>0</verstretch>
229 </sizepolicy>
230 </property>
231 <property name="maximumSize">
232 <size>
233 <width>50</width>
234 <height>16777215</height>
235 </size>
236 </property>
237 <property name="maximum">
238 <number>999999999</number>
239 </property>
240 </widget>
241 </item>
242 </layout>
243 </item> 198 </item>
244 <item> 199 <item>
245 <layout class="QHBoxLayout" name="horizontalLayout_8"> 200 <widget class="QLabel" name="label_yTitleAlign">
246 <item> 201 <property name="sizePolicy">
247 <widget class="QLabel" name="label_textColor"> 202 <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
248 <property name="sizePolicy"> 203 <horstretch>0</horstretch>
249 <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> 204 <verstretch>0</verstretch>
250 <horstretch>0</horstretch> 205 </sizepolicy>
251 <verstretch>0</verstretch> 206 </property>
252 </sizepolicy> 207 <property name="text">
253 </property> 208 <string>Y</string>
254 <property name="maximumSize"> 209 </property>
255 <size> 210 </widget>
256 <width>16777215</width>
257 <height>16777215</height>
258 </size>
259 </property>
260 <property name="text">
261 <string>Text Color</string>
262 </property>
263 </widget>
264 </item>
265 <item>
266 <widget class="QPushButton" name="pushButton_textColor">
267 <property name="sizePolicy">
268 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
269 <horstretch>0</horstretch>
270 <verstretch>0</verstretch>
271 </sizepolicy>
272 </property>
273 <property name="maximumSize">
274 <size>
275 <width>32</width>
276 <height>32</height>
277 </size>
278 </property>
279 <property name="text">
280 <string/>
281 </property>
282 <property name="MaximumSize" stdset="0">
283 <size>
284 <width>32</width>
285 <height>32</height>
286 </size>
287 </property>
288 </widget>
289 </item>
290 <item>
291 <spacer name="horizontalSpacer_8">
292 <property name="orientation">
293 <enum>Qt::Horizontal</enum>
294 </property>
295 <property name="sizeType">
296 <enum>QSizePolicy::Fixed</enum>
297 </property>
298 <property name="sizeHint" stdset="0">
299 <size>
300 <width>5</width>
301 <height>20</height>
302 </size>
303 </property>
304 </spacer>
305 </item>
306 <item>
307 <widget class="QLabel" name="label_fontSize">
308 <property name="sizePolicy">
309 <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
310 <horstretch>0</horstretch>
311 <verstretch>0</verstretch>
312 </sizepolicy>
313 </property>
314 <property name="text">
315 <string>Font Size</string>
316 </property>
317 </widget>
318 </item>
319 <item>
320 <widget class="QSpinBox" name="spinBox_fontSize">
321 <property name="sizePolicy">
322 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
323 <horstretch>0</horstretch>
324 <verstretch>0</verstretch>
325 </sizepolicy>
326 </property>
327 <property name="suffix">
328 <string/>
329 </property>
330 <property name="prefix">
331 <string/>
332 </property>
333 <property name="minimum">
334 <number>1</number>
335 </property>
336 <property name="maximum">
337 <number>500</number>
338 </property>
339 </widget>
340 </item>
341 <item>
342 <spacer name="horizontalSpacer_7">
343 <property name="orientation">
344 <enum>Qt::Horizontal</enum>
345 </property>
346 <property name="sizeType">
347 <enum>QSizePolicy::Fixed</enum>
348 </property>
349 <property name="sizeHint" stdset="0">
350 <size>
351 <width>5</width>
352 <height>20</height>
353 </size>
354 </property>
355 </spacer>
356 </item>
357 <item>
358 <widget class="QLabel" name="label_3">
359 <property name="sizePolicy">
360 <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
361 <horstretch>0</horstretch>
362 <verstretch>0</verstretch>
363 </sizepolicy>
364 </property>
365 <property name="text">
366 <string>Font Style</string>
367 </property>
368 </widget>
369 </item>
370 <item>
371 <widget class="QComboBox" name="comboBox_fontStyle">
372 <item>
373 <property name="text">
374 <string>Normal</string>
375 </property>
376 </item>
377 <item>
378 <property name="text">
379 <string>Semi-Bold</string>
380 </property>
381 </item>
382 <item>
383 <property name="text">
384 <string>Bold</string>
385 </property>
386 </item>
387 <item>
388 <property name="text">
389 <string>Italic</string>
390 </property>
391 </item>
392 <item>
393 <property name="text">
394 <string>Bold Italic</string>
395 </property>
396 </item>
397 <item>
398 <property name="text">
399 <string>Faux Italic</string>
400 </property>
401 </item>
402 <item>
403 <property name="text">
404 <string>Small Caps</string>
405 </property>
406 </item>
407 </widget>
408 </item>
409 </layout>
410 </item> 211 </item>
411 <item> 212 <item>
412 <layout class="QHBoxLayout" name="horizontalLayout_12"> 213 <widget class="QSpinBox" name="spinBox_yTextAlign">
413 <item> 214 <property name="sizePolicy">
414 <widget class="QLineEdit" name="lineEdit_textColor"> 215 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
415 <property name="sizePolicy"> 216 <horstretch>0</horstretch>
416 <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> 217 <verstretch>0</verstretch>
417 <horstretch>0</horstretch> 218 </sizepolicy>
418 <verstretch>0</verstretch> 219 </property>
419 </sizepolicy> 220 <property name="maximumSize">
420 </property> 221 <size>
421 <property name="maximumSize"> 222 <width>50</width>
422 <size> 223 <height>16777215</height>
423 <width>0</width> 224 </size>
424 <height>16777215</height> 225 </property>
425 </size> 226 <property name="maximum">
426 </property> 227 <number>999999999</number>
427 <property name="focusPolicy"> 228 </property>
428 <enum>Qt::NoFocus</enum> 229 </widget>
429 </property> 230 </item>
430 <property name="text"> 231 </layout>
431 <string>255,255,255</string> 232 </item>
432 </property> 233 <item>
433 </widget> 234 <layout class="QHBoxLayout" name="horizontalLayout_8">
434 </item> 235 <item>
435 <item> 236 <widget class="QLabel" name="label_textColor">
436 <widget class="QLabel" name="label_2"> 237 <property name="sizePolicy">
437 <property name="sizePolicy"> 238 <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
438 <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> 239 <horstretch>0</horstretch>
439 <horstretch>0</horstretch> 240 <verstretch>0</verstretch>
440 <verstretch>0</verstretch> 241 </sizepolicy>
441 </sizepolicy> 242 </property>
442 </property> 243 <property name="maximumSize">
443 <property name="text"> 244 <size>
444 <string>Stroke</string> 245 <width>16777215</width>
445 </property> 246 <height>16777215</height>
446 </widget> 247 </size>
447 </item> 248 </property>
448 <item> 249 <property name="text">
449 <widget class="QSpinBox" name="spinBox_stroke"> 250 <string>Text Color</string>
450 <property name="sizePolicy"> 251 </property>
451 <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> 252 </widget>
452 <horstretch>0</horstretch> 253 </item>
453 <verstretch>0</verstretch> 254 <item>
454 </sizepolicy> 255 <widget class="QPushButton" name="pushButton_textColor">
455 </property> 256 <property name="sizePolicy">
456 <property name="suffix"> 257 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
457 <string>px</string> 258 <horstretch>0</horstretch>
458 </property> 259 <verstretch>0</verstretch>
459 </widget> 260 </sizepolicy>
460 </item> 261 </property>
461 <item> 262 <property name="maximumSize">
462 <widget class="QLabel" name="label_5"> 263 <size>
463 <property name="sizePolicy"> 264 <width>32</width>
464 <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> 265 <height>32</height>
465 <horstretch>0</horstretch> 266 </size>
466 <verstretch>0</verstretch> 267 </property>
467 </sizepolicy> 268 <property name="text">
468 </property> 269 <string/>
469 <property name="text"> 270 </property>
470 <string>Stroke Color</string> 271 <property name="MaximumSize" stdset="0">
471 </property> 272 <size>
472 </widget> 273 <width>32</width>
473 </item> 274 <height>32</height>
474 <item> 275 </size>
475 <widget class="QLineEdit" name="lineEdit_strokeColor"> 276 </property>
476 <property name="sizePolicy"> 277 </widget>
477 <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> 278 </item>
478 <horstretch>0</horstretch> 279 <item>
479 <verstretch>0</verstretch> 280 <spacer name="horizontalSpacer_8">
480 </sizepolicy> 281 <property name="orientation">
481 </property> 282 <enum>Qt::Orientation::Horizontal</enum>
482 <property name="maximumSize"> 283 </property>
483 <size> 284 <property name="sizeType">
484 <width>0</width> 285 <enum>QSizePolicy::Policy::Fixed</enum>
485 <height>16777215</height> 286 </property>
486 </size> 287 <property name="sizeHint" stdset="0">
487 </property> 288 <size>
488 <property name="focusPolicy"> 289 <width>5</width>
489 <enum>Qt::NoFocus</enum> 290 <height>20</height>
490 </property> 291 </size>
491 <property name="text"> 292 </property>
492 <string>0,0,0</string> 293 </spacer>
493 </property> 294 </item>
494 </widget> 295 <item>
495 </item> 296 <widget class="QLabel" name="label_fontSize">
496 <item> 297 <property name="sizePolicy">
497 <widget class="QPushButton" name="pushButton_strokeColor"> 298 <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
498 <property name="sizePolicy"> 299 <horstretch>0</horstretch>
499 <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> 300 <verstretch>0</verstretch>
500 <horstretch>0</horstretch> 301 </sizepolicy>
501 <verstretch>0</verstretch> 302 </property>
502 </sizepolicy> 303 <property name="text">
503 </property> 304 <string>Font Size</string>
504 <property name="maximumSize"> 305 </property>
505 <size> 306 </widget>
506 <width>32</width> 307 </item>
507 <height>32</height> 308 <item>
508 </size> 309 <widget class="QSpinBox" name="spinBox_fontSize">
509 </property> 310 <property name="sizePolicy">
510 <property name="text"> 311 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
511 <string/> 312 <horstretch>0</horstretch>
512 </property> 313 <verstretch>0</verstretch>
513 <property name="MaximumSize" stdset="0"> 314 </sizepolicy>
514 <size> 315 </property>
515 <width>32</width> 316 <property name="suffix">
516 <height>32</height> 317 <string/>
517 </size> 318 </property>
518 </property> 319 <property name="prefix">
519 </widget> 320 <string/>
520 </item> 321 </property>
521 <item> 322 <property name="minimum">
522 <spacer name="horizontalSpacer"> 323 <number>1</number>
523 <property name="orientation"> 324 </property>
524 <enum>Qt::Horizontal</enum> 325 <property name="maximum">
525 </property> 326 <number>500</number>
526 <property name="sizeHint" stdset="0"> 327 </property>
527 <size> 328 </widget>
528 <width>40</width> 329 </item>
529 <height>20</height> 330 <item>
530 </size> 331 <spacer name="horizontalSpacer_7">
531 </property> 332 <property name="orientation">
532 </spacer> 333 <enum>Qt::Orientation::Horizontal</enum>
533 </item> 334 </property>
534 </layout> 335 <property name="sizeType">
336 <enum>QSizePolicy::Policy::Fixed</enum>
337 </property>
338 <property name="sizeHint" stdset="0">
339 <size>
340 <width>5</width>
341 <height>20</height>
342 </size>
343 </property>
344 </spacer>
345 </item>
346 <item>
347 <widget class="QLabel" name="label_3">
348 <property name="sizePolicy">
349 <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
350 <horstretch>0</horstretch>
351 <verstretch>0</verstretch>
352 </sizepolicy>
353 </property>
354 <property name="text">
355 <string>Font Style</string>
356 </property>
357 </widget>
535 </item> 358 </item>
536 <item> 359 <item>
537 <layout class="QHBoxLayout" name="horizontalLayout_2"> 360 <widget class="QComboBox" name="comboBox_fontStyle">
538 <item> 361 <item>
539 <widget class="QCheckBox" name="checkBox_shadow"> 362 <property name="text">
540 <property name="sizePolicy"> 363 <string>Normal</string>
541 <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> 364 </property>
542 <horstretch>0</horstretch>
543 <verstretch>0</verstretch>
544 </sizepolicy>
545 </property>
546 <property name="text">
547 <string>Shadow</string>
548 </property>
549 </widget>
550 </item> 365 </item>
551 <item> 366 <item>
552 <widget class="QLabel" name="label_shadX"> 367 <property name="text">
553 <property name="sizePolicy"> 368 <string>Semi-Bold</string>
554 <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> 369 </property>
555 <horstretch>0</horstretch>
556 <verstretch>0</verstretch>
557 </sizepolicy>
558 </property>
559 <property name="text">
560 <string>Shadow Offset</string>
561 </property>
562 </widget>
563 </item> 370 </item>
564 <item> 371 <item>
565 <widget class="QSpinBox" name="spinBox_shadX"> 372 <property name="text">
566 <property name="sizePolicy"> 373 <string>Bold</string>
567 <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> 374 </property>
568 <horstretch>0</horstretch>
569 <verstretch>0</verstretch>
570 </sizepolicy>
571 </property>
572 <property name="minimum">
573 <number>-1000</number>
574 </property>
575 <property name="maximum">
576 <number>1000</number>
577 </property>
578 <property name="value">
579 <number>-4</number>
580 </property>
581 </widget>
582 </item> 375 </item>
583 <item> 376 <item>
584 <widget class="QSpinBox" name="spinBox_shadY"> 377 <property name="text">
585 <property name="sizePolicy"> 378 <string>Italic</string>
586 <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> 379 </property>
587 <horstretch>0</horstretch>
588 <verstretch>0</verstretch>
589 </sizepolicy>
590 </property>
591 <property name="minimum">
592 <number>-1000</number>
593 </property>
594 <property name="maximum">
595 <number>1000</number>
596 </property>
597 <property name="value">
598 <number>8</number>
599 </property>
600 </widget>
601 </item> 380 </item>
602 <item> 381 <item>
603 <widget class="QLabel" name="label_shadBlur"> 382 <property name="text">
604 <property name="sizePolicy"> 383 <string>Bold Italic</string>
605 <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> 384 </property>
606 <horstretch>0</horstretch>
607 <verstretch>0</verstretch>
608 </sizepolicy>
609 </property>
610 <property name="text">
611 <string>Shadow Blur</string>
612 </property>
613 </widget>
614 </item> 385 </item>
615 <item> 386 <item>
616 <widget class="QDoubleSpinBox" name="spinBox_shadBlur"> 387 <property name="text">
617 <property name="sizePolicy"> 388 <string>Faux Italic</string>
618 <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> 389 </property>
619 <horstretch>0</horstretch>
620 <verstretch>0</verstretch>
621 </sizepolicy>
622 </property>
623 <property name="maximum">
624 <double>99.000000000000000</double>
625 </property>
626 <property name="singleStep">
627 <double>0.100000000000000</double>
628 </property>
629 <property name="value">
630 <double>5.000000000000000</double>
631 </property>
632 </widget>
633 </item> 390 </item>
634 <item> 391 <item>
635 <spacer name="horizontalSpacer_3"> 392 <property name="text">
636 <property name="orientation"> 393 <string>Small Caps</string>
637 <enum>Qt::Horizontal</enum> 394 </property>
638 </property>
639 <property name="sizeType">
640 <enum>QSizePolicy::Minimum</enum>
641 </property>
642 <property name="sizeHint" stdset="0">
643 <size>
644 <width>40</width>
645 <height>20</height>
646 </size>
647 </property>
648 </spacer>
649 </item> 395 </item>
650 </layout> 396 </widget>
397 </item>
398 </layout>
399 </item>
400 <item>
401 <layout class="QHBoxLayout" name="horizontalLayout_12">
402 <item>
403 <widget class="QLineEdit" name="lineEdit_textColor">
404 <property name="sizePolicy">
405 <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
406 <horstretch>0</horstretch>
407 <verstretch>0</verstretch>
408 </sizepolicy>
409 </property>
410 <property name="maximumSize">
411 <size>
412 <width>0</width>
413 <height>16777215</height>
414 </size>
415 </property>
416 <property name="focusPolicy">
417 <enum>Qt::FocusPolicy::NoFocus</enum>
418 </property>
419 <property name="text">
420 <string/>
421 </property>
422 </widget>
423 </item>
424 <item>
425 <widget class="QLabel" name="label_2">
426 <property name="sizePolicy">
427 <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
428 <horstretch>0</horstretch>
429 <verstretch>0</verstretch>
430 </sizepolicy>
431 </property>
432 <property name="text">
433 <string>Stroke</string>
434 </property>
435 </widget>
436 </item>
437 <item>
438 <widget class="QSpinBox" name="spinBox_stroke">
439 <property name="sizePolicy">
440 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
441 <horstretch>0</horstretch>
442 <verstretch>0</verstretch>
443 </sizepolicy>
444 </property>
445 <property name="suffix">
446 <string>px</string>
447 </property>
448 </widget>
449 </item>
450 <item>
451 <widget class="QLabel" name="label_5">
452 <property name="sizePolicy">
453 <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
454 <horstretch>0</horstretch>
455 <verstretch>0</verstretch>
456 </sizepolicy>
457 </property>
458 <property name="text">
459 <string>Stroke Color</string>
460 </property>
461 </widget>
462 </item>
463 <item>
464 <widget class="QLineEdit" name="lineEdit_strokeColor">
465 <property name="sizePolicy">
466 <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
467 <horstretch>0</horstretch>
468 <verstretch>0</verstretch>
469 </sizepolicy>
470 </property>
471 <property name="maximumSize">
472 <size>
473 <width>0</width>
474 <height>16777215</height>
475 </size>
476 </property>
477 <property name="focusPolicy">
478 <enum>Qt::FocusPolicy::NoFocus</enum>
479 </property>
480 <property name="text">
481 <string>0,0,0</string>
482 </property>
483 </widget>
484 </item>
485 <item>
486 <widget class="QPushButton" name="pushButton_strokeColor">
487 <property name="sizePolicy">
488 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
489 <horstretch>0</horstretch>
490 <verstretch>0</verstretch>
491 </sizepolicy>
492 </property>
493 <property name="maximumSize">
494 <size>
495 <width>32</width>
496 <height>32</height>
497 </size>
498 </property>
499 <property name="text">
500 <string/>
501 </property>
502 <property name="MaximumSize" stdset="0">
503 <size>
504 <width>32</width>
505 <height>32</height>
506 </size>
507 </property>
508 </widget>
509 </item>
510 <item>
511 <spacer name="horizontalSpacer">
512 <property name="orientation">
513 <enum>Qt::Orientation::Horizontal</enum>
514 </property>
515 <property name="sizeType">
516 <enum>QSizePolicy::Policy::MinimumExpanding</enum>
517 </property>
518 <property name="sizeHint" stdset="0">
519 <size>
520 <width>40</width>
521 <height>20</height>
522 </size>
523 </property>
524 </spacer>
525 </item>
526 </layout>
527 </item>
528 <item>
529 <layout class="QHBoxLayout" name="horizontalLayout_2">
530 <item>
531 <widget class="QCheckBox" name="checkBox_shadow">
532 <property name="sizePolicy">
533 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
534 <horstretch>0</horstretch>
535 <verstretch>0</verstretch>
536 </sizepolicy>
537 </property>
538 <property name="text">
539 <string>Shadow</string>
540 </property>
541 </widget>
542 </item>
543 <item>
544 <spacer name="horizontalSpacer_3">
545 <property name="orientation">
546 <enum>Qt::Orientation::Horizontal</enum>
547 </property>
548 <property name="sizeType">
549 <enum>QSizePolicy::Policy::Preferred</enum>
550 </property>
551 <property name="sizeHint" stdset="0">
552 <size>
553 <width>40</width>
554 <height>20</height>
555 </size>
556 </property>
557 </spacer>
558 </item>
559 <item>
560 <widget class="QLabel" name="label_shadX">
561 <property name="sizePolicy">
562 <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
563 <horstretch>0</horstretch>
564 <verstretch>0</verstretch>
565 </sizepolicy>
566 </property>
567 <property name="text">
568 <string>Shadow Offset</string>
569 </property>
570 </widget>
571 </item>
572 <item>
573 <widget class="QSpinBox" name="spinBox_shadX">
574 <property name="sizePolicy">
575 <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
576 <horstretch>0</horstretch>
577 <verstretch>0</verstretch>
578 </sizepolicy>
579 </property>
580 <property name="minimum">
581 <number>-1000</number>
582 </property>
583 <property name="maximum">
584 <number>1000</number>
585 </property>
586 <property name="value">
587 <number>2</number>
588 </property>
589 </widget>
590 </item>
591 <item>
592 <widget class="QSpinBox" name="spinBox_shadY">
593 <property name="sizePolicy">
594 <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
595 <horstretch>0</horstretch>
596 <verstretch>0</verstretch>
597 </sizepolicy>
598 </property>
599 <property name="minimum">
600 <number>-1000</number>
601 </property>
602 <property name="maximum">
603 <number>1000</number>
604 </property>
605 <property name="value">
606 <number>-2</number>
607 </property>
608 </widget>
609 </item>
610 <item>
611 <widget class="QLabel" name="label_shadBlur">
612 <property name="sizePolicy">
613 <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
614 <horstretch>0</horstretch>
615 <verstretch>0</verstretch>
616 </sizepolicy>
617 </property>
618 <property name="text">
619 <string>Shadow Blur</string>
620 </property>
621 </widget>
622 </item>
623 <item>
624 <widget class="QSpinBox" name="spinBox_shadBlur">
625 <property name="sizePolicy">
626 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
627 <horstretch>0</horstretch>
628 <verstretch>0</verstretch>
629 </sizepolicy>
630 </property>
631 <property name="buttonSymbols">
632 <enum>QAbstractSpinBox::ButtonSymbols::PlusMinus</enum>
633 </property>
634 <property name="correctionMode">
635 <enum>QAbstractSpinBox::CorrectionMode::CorrectToPreviousValue</enum>
636 </property>
637 <property name="maximum">
638 <number>1000</number>
639 </property>
640 <property name="stepType">
641 <enum>QAbstractSpinBox::StepType::DefaultStepType</enum>
642 </property>
643 <property name="value">
644 <number>35</number>
645 </property>
646 <property name="displayIntegerBase">
647 <number>10</number>
648 </property>
649 </widget>
651 </item> 650 </item>
652 </layout> 651 </layout>
653 </item> 652 </item>
654 <item> 653 <item>
655 <spacer name="verticalSpacer"> 654 <spacer name="verticalSpacer">
656 <property name="orientation"> 655 <property name="orientation">
657 <enum>Qt::Vertical</enum> 656 <enum>Qt::Orientation::Vertical</enum>
658 </property> 657 </property>
659 <property name="sizeHint" stdset="0"> 658 <property name="sizeHint" stdset="0">
660 <size> 659 <size>
diff --git a/src/avp/components/video.ui b/src/avp/components/video.ui
index 08d15d3..72ecb0c 100644
--- a/src/avp/components/video.ui
+++ b/src/avp/components/video.ui
@@ -27,168 +27,161 @@
27 </property> 27 </property>
28 <layout class="QVBoxLayout" name="verticalLayout_2"> 28 <layout class="QVBoxLayout" name="verticalLayout_2">
29 <item> 29 <item>
30 <layout class="QVBoxLayout" name="verticalLayout"> 30 <layout class="QHBoxLayout" name="horizontalLayout_8">
31 <property name="leftMargin"> 31 <item>
32 <number>4</number> 32 <widget class="QLabel" name="label_textColor">
33 </property> 33 <property name="sizePolicy">
34 <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
35 <horstretch>0</horstretch>
36 <verstretch>0</verstretch>
37 </sizepolicy>
38 </property>
39 <property name="minimumSize">
40 <size>
41 <width>31</width>
42 <height>0</height>
43 </size>
44 </property>
45 <property name="text">
46 <string>Video</string>
47 </property>
48 </widget>
49 </item>
50 <item>
51 <widget class="QLineEdit" name="lineEdit_video">
52 <property name="minimumSize">
53 <size>
54 <width>1</width>
55 <height>0</height>
56 </size>
57 </property>
58 </widget>
59 </item>
60 <item>
61 <widget class="QPushButton" name="pushButton_video">
62 <property name="sizePolicy">
63 <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
64 <horstretch>0</horstretch>
65 <verstretch>0</verstretch>
66 </sizepolicy>
67 </property>
68 <property name="minimumSize">
69 <size>
70 <width>1</width>
71 <height>0</height>
72 </size>
73 </property>
74 <property name="maximumSize">
75 <size>
76 <width>32</width>
77 <height>32</height>
78 </size>
79 </property>
80 <property name="text">
81 <string>...</string>
82 </property>
83 <property name="MaximumSize" stdset="0">
84 <size>
85 <width>32</width>
86 <height>32</height>
87 </size>
88 </property>
89 </widget>
90 </item>
91 <item>
92 <spacer name="horizontalSpacer_9">
93 <property name="orientation">
94 <enum>Qt::Orientation::Horizontal</enum>
95 </property>
96 <property name="sizeType">
97 <enum>QSizePolicy::Policy::Fixed</enum>
98 </property>
99 <property name="sizeHint" stdset="0">
100 <size>
101 <width>5</width>
102 <height>20</height>
103 </size>
104 </property>
105 </spacer>
106 </item>
107 <item>
108 <widget class="QLabel" name="label_xTitleAlign">
109 <property name="sizePolicy">
110 <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
111 <horstretch>0</horstretch>
112 <verstretch>0</verstretch>
113 </sizepolicy>
114 </property>
115 <property name="text">
116 <string>X</string>
117 </property>
118 </widget>
119 </item>
34 <item> 120 <item>
35 <layout class="QHBoxLayout" name="horizontalLayout_8"> 121 <widget class="QSpinBox" name="spinBox_x">
36 <item> 122 <property name="sizePolicy">
37 <widget class="QLabel" name="label_textColor"> 123 <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
38 <property name="sizePolicy"> 124 <horstretch>0</horstretch>
39 <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> 125 <verstretch>0</verstretch>
40 <horstretch>0</horstretch> 126 </sizepolicy>
41 <verstretch>0</verstretch> 127 </property>
42 </sizepolicy> 128 <property name="maximumSize">
43 </property> 129 <size>
44 <property name="minimumSize"> 130 <width>80</width>
45 <size> 131 <height>16777215</height>
46 <width>31</width> 132 </size>
47 <height>0</height> 133 </property>
48 </size> 134 <property name="minimum">
49 </property> 135 <number>-10000</number>
50 <property name="text"> 136 </property>
51 <string>Video</string> 137 <property name="maximum">
52 </property> 138 <number>10000</number>
53 </widget> 139 </property>
54 </item> 140 </widget>
55 <item> 141 </item>
56 <widget class="QLineEdit" name="lineEdit_video"> 142 <item>
57 <property name="minimumSize"> 143 <widget class="QLabel" name="label_yTitleAlign">
58 <size> 144 <property name="sizePolicy">
59 <width>1</width> 145 <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
60 <height>0</height> 146 <horstretch>0</horstretch>
61 </size> 147 <verstretch>0</verstretch>
62 </property> 148 </sizepolicy>
63 </widget> 149 </property>
64 </item> 150 <property name="text">
65 <item> 151 <string>Y</string>
66 <widget class="QPushButton" name="pushButton_video"> 152 </property>
67 <property name="sizePolicy"> 153 </widget>
68 <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> 154 </item>
69 <horstretch>0</horstretch> 155 <item>
70 <verstretch>0</verstretch> 156 <widget class="QSpinBox" name="spinBox_y">
71 </sizepolicy> 157 <property name="sizePolicy">
72 </property> 158 <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
73 <property name="minimumSize"> 159 <horstretch>0</horstretch>
74 <size> 160 <verstretch>0</verstretch>
75 <width>1</width> 161 </sizepolicy>
76 <height>0</height> 162 </property>
77 </size> 163 <property name="maximumSize">
78 </property> 164 <size>
79 <property name="maximumSize"> 165 <width>80</width>
80 <size> 166 <height>16777215</height>
81 <width>32</width> 167 </size>
82 <height>32</height> 168 </property>
83 </size> 169 <property name="baseSize">
84 </property> 170 <size>
85 <property name="text"> 171 <width>0</width>
86 <string>...</string> 172 <height>0</height>
87 </property> 173 </size>
88 <property name="MaximumSize" stdset="0"> 174 </property>
89 <size> 175 <property name="minimum">
90 <width>32</width> 176 <number>-10000</number>
91 <height>32</height> 177 </property>
92 </size> 178 <property name="maximum">
93 </property> 179 <number>10000</number>
94 </widget> 180 </property>
95 </item> 181 <property name="value">
96 <item> 182 <number>0</number>
97 <spacer name="horizontalSpacer_9"> 183 </property>
98 <property name="orientation"> 184 </widget>
99 <enum>Qt::Horizontal</enum>
100 </property>
101 <property name="sizeType">
102 <enum>QSizePolicy::Fixed</enum>
103 </property>
104 <property name="sizeHint" stdset="0">
105 <size>
106 <width>5</width>
107 <height>20</height>
108 </size>
109 </property>
110 </spacer>
111 </item>
112 <item>
113 <widget class="QLabel" name="label_xTitleAlign">
114 <property name="sizePolicy">
115 <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
116 <horstretch>0</horstretch>
117 <verstretch>0</verstretch>
118 </sizepolicy>
119 </property>
120 <property name="text">
121 <string>X</string>
122 </property>
123 </widget>
124 </item>
125 <item>
126 <widget class="QSpinBox" name="spinBox_x">
127 <property name="sizePolicy">
128 <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
129 <horstretch>0</horstretch>
130 <verstretch>0</verstretch>
131 </sizepolicy>
132 </property>
133 <property name="maximumSize">
134 <size>
135 <width>80</width>
136 <height>16777215</height>
137 </size>
138 </property>
139 <property name="minimum">
140 <number>-10000</number>
141 </property>
142 <property name="maximum">
143 <number>10000</number>
144 </property>
145 </widget>
146 </item>
147 <item>
148 <widget class="QLabel" name="label_yTitleAlign">
149 <property name="sizePolicy">
150 <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
151 <horstretch>0</horstretch>
152 <verstretch>0</verstretch>
153 </sizepolicy>
154 </property>
155 <property name="text">
156 <string>Y</string>
157 </property>
158 </widget>
159 </item>
160 <item>
161 <widget class="QSpinBox" name="spinBox_y">
162 <property name="sizePolicy">
163 <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
164 <horstretch>0</horstretch>
165 <verstretch>0</verstretch>
166 </sizepolicy>
167 </property>
168 <property name="maximumSize">
169 <size>
170 <width>80</width>
171 <height>16777215</height>
172 </size>
173 </property>
174 <property name="baseSize">
175 <size>
176 <width>0</width>
177 <height>0</height>
178 </size>
179 </property>
180 <property name="minimum">
181 <number>-10000</number>
182 </property>
183 <property name="maximum">
184 <number>10000</number>
185 </property>
186 <property name="value">
187 <number>0</number>
188 </property>
189 </widget>
190 </item>
191 </layout>
192 </item> 185 </item>
193 </layout> 186 </layout>
194 </item> 187 </item>
@@ -204,7 +197,7 @@
204 <item> 197 <item>
205 <spacer name="horizontalSpacer"> 198 <spacer name="horizontalSpacer">
206 <property name="orientation"> 199 <property name="orientation">
207 <enum>Qt::Horizontal</enum> 200 <enum>Qt::Orientation::Horizontal</enum>
208 </property> 201 </property>
209 <property name="sizeHint" stdset="0"> 202 <property name="sizeHint" stdset="0">
210 <size> 203 <size>
@@ -227,14 +220,14 @@
227 <string>Scale</string> 220 <string>Scale</string>
228 </property> 221 </property>
229 <property name="alignment"> 222 <property name="alignment">
230 <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> 223 <set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
231 </property> 224 </property>
232 </widget> 225 </widget>
233 </item> 226 </item>
234 <item> 227 <item>
235 <widget class="QSpinBox" name="spinBox_scale"> 228 <widget class="QSpinBox" name="spinBox_scale">
236 <property name="buttonSymbols"> 229 <property name="buttonSymbols">
237 <enum>QAbstractSpinBox::UpDownArrows</enum> 230 <enum>QAbstractSpinBox::ButtonSymbols::UpDownArrows</enum>
238 </property> 231 </property>
239 <property name="suffix"> 232 <property name="suffix">
240 <string>%</string> 233 <string>%</string>
@@ -296,7 +289,7 @@
296 <item> 289 <item>
297 <spacer name="horizontalSpacer_2"> 290 <spacer name="horizontalSpacer_2">
298 <property name="orientation"> 291 <property name="orientation">
299 <enum>Qt::Horizontal</enum> 292 <enum>Qt::Orientation::Horizontal</enum>
300 </property> 293 </property>
301 <property name="sizeHint" stdset="0"> 294 <property name="sizeHint" stdset="0">
302 <size> 295 <size>
@@ -311,7 +304,7 @@
311 <item> 304 <item>
312 <spacer name="verticalSpacer"> 305 <spacer name="verticalSpacer">
313 <property name="orientation"> 306 <property name="orientation">
314 <enum>Qt::Vertical</enum> 307 <enum>Qt::Orientation::Vertical</enum>
315 </property> 308 </property>
316 <property name="sizeHint" stdset="0"> 309 <property name="sizeHint" stdset="0">
317 <size> 310 <size>
diff --git a/src/avp/components/waveform.py b/src/avp/components/waveform.py
index 7dc0b99..e10dec2 100644
--- a/src/avp/components/waveform.py
+++ b/src/avp/components/waveform.py
@@ -1,12 +1,12 @@
1from PIL import Image 1from PIL import Image, ImageChops
2from PyQt6 import QtGui, QtCore, QtWidgets
3from PyQt6.QtGui import QColor 2from PyQt6.QtGui import QColor
4import os 3import os
5import math
6import subprocess 4import subprocess
7import logging 5import logging
6from copy import copy
8 7
9from ..component import Component 8from ..component import Component
9from ..toolkit.visualizer import transformData, createSpectrumArray
10from ..toolkit.frame import BlankFrame, scale 10from ..toolkit.frame import BlankFrame, scale
11from ..toolkit import checkOutput 11from ..toolkit import checkOutput
12from ..toolkit.ffmpeg import ( 12from ..toolkit.ffmpeg import (
@@ -23,14 +23,20 @@ log = logging.getLogger("AVP.Components.Waveform")
23 23
24class Component(Component): 24class Component(Component):
25 name = "Waveform" 25 name = "Waveform"
26 version = "1.0.0" 26 version = "2.0.0"
27
28 @property
29 def updateInterval(self):
30 """How many frames from FFmpeg are ignored between each final frame"""
31 return 100 - self.speed
32
33 def properties(self):
34 return [] if self.speed == 100 else ["pcm"]
27 35
28 def widget(self, *args): 36 def widget(self, *args):
29 super().widget(*args) 37 super().widget(*args)
30 self._image = BlankFrame(self.width, self.height) 38 self._image = BlankFrame(self.width, self.height)
31 39
32 self.page.lineEdit_color.setText("255,255,255")
33
34 if hasattr(self.parent, "lineEdit_audioFile"): 40 if hasattr(self.parent, "lineEdit_audioFile"):
35 self.parent.lineEdit_audioFile.textChanged.connect(self.update) 41 self.parent.lineEdit_audioFile.textChanged.connect(self.update)
36 42
@@ -46,6 +52,7 @@ class Component(Component):
46 "opacity": self.page.spinBox_opacity, 52 "opacity": self.page.spinBox_opacity,
47 "compress": self.page.checkBox_compress, 53 "compress": self.page.checkBox_compress,
48 "mono": self.page.checkBox_mono, 54 "mono": self.page.checkBox_mono,
55 "speed": self.page.spinBox_speed,
49 }, 56 },
50 colorWidgets={ 57 colorWidgets={
51 "color": self.page.pushButton_color, 58 "color": self.page.pushButton_color,
@@ -65,6 +72,10 @@ class Component(Component):
65 return frame 72 return frame
66 73
67 def preFrameRender(self, **kwargs): 74 def preFrameRender(self, **kwargs):
75 self._fadingImage = None
76 self._prevImage = None
77 self._currImage = None
78 self._lastUpdatedFrame = 0
68 super().preFrameRender(**kwargs) 79 super().preFrameRender(**kwargs)
69 self.updateChunksize() 80 self.updateChunksize()
70 w, h = scale(self.scale, self.width, self.height, str) 81 w, h = scale(self.scale, self.width, self.height, str)
@@ -79,11 +90,64 @@ class Component(Component):
79 component=self, 90 component=self,
80 debug=True, 91 debug=True,
81 ) 92 )
93 if self.speed == 100:
94 return
95 self.spectrumArray = createSpectrumArray(
96 self,
97 self.completeAudioArray,
98 self.sampleSize,
99 0.08,
100 0.8,
101 20,
102 self.progressBarUpdate,
103 self.progressBarSetText,
104 )
82 105
83 def frameRender(self, frameNo): 106 def frameRender(self, frameNo):
84 if FfmpegVideo.threadError is not None: 107 if FfmpegVideo.threadError is not None:
85 raise FfmpegVideo.threadError 108 raise FfmpegVideo.threadError
86 return self.finalizeFrame(self.video.frame(frameNo)) 109 newFrame = self.finalizeFrame(self.video.frame(frameNo))
110 if self.speed == 100:
111 return newFrame
112 frameDiff = 0 if frameNo == 0 else frameNo % self.updateInterval
113 peaks = [
114 self.spectrumArray[frameNo * self.sampleSize][i * 4] for i in range(64)
115 ]
116 peakValue = 70 - (max(*peaks) - min(*peaks))
117 isValidPeak = (
118 peakValue > 27
119 and frameNo - self._lastUpdatedFrame > self.updateInterval / 2
120 )
121 if frameDiff == 0 or isValidPeak:
122 self._lastUpdatedFrame = frameNo
123 self._fadingImage = self._prevImage
124 self._prevImage = self._image
125 self._currImage = newFrame
126 usualAlpha = 0.0 + (1 / self.updateInterval) * frameDiff
127 alpha = max(
128 0.1
129 + (
130 1
131 / max(
132 10,
133 peakValue,
134 )
135 ),
136 usualAlpha,
137 )
138 baseImage = self._prevImage
139 if self._fadingImage is not None:
140 # fade away the old previous frame from ages ago
141 baseImage = ImageChops.blend(
142 self._prevImage, self._fadingImage, max(0.0, 0.9 - usualAlpha)
143 )
144 blendedImage = ImageChops.blend(
145 baseImage,
146 ImageChops.lighter(self._prevImage, newFrame),
147 alpha,
148 )
149 baseImage.paste(blendedImage, (0, 0), mask=blendedImage)
150 return Image.alpha_composite(self._currImage, baseImage)
87 151
88 def postFrameRender(self): 152 def postFrameRender(self):
89 closePipe(self.video.pipe) 153 closePipe(self.video.pipe)
@@ -162,17 +226,17 @@ class Component(Component):
162 def makeFfmpegFilter(self, preview=False, startPt=0): 226 def makeFfmpegFilter(self, preview=False, startPt=0):
163 w, h = scale(self.scale, self.width, self.height, str) 227 w, h = scale(self.scale, self.width, self.height, str)
164 if self.amplitude == 0: 228 if self.amplitude == 0:
165 amplitude = "lin"
166 elif self.amplitude == 1:
167 amplitude = "log" 229 amplitude = "log"
168 elif self.amplitude == 2: 230 elif self.amplitude == 1:
169 amplitude = "sqrt" 231 amplitude = "sqrt"
170 elif self.amplitude == 3: 232 elif self.amplitude == 2:
171 amplitude = "cbrt" 233 amplitude = "cbrt"
234 elif self.amplitude == 3:
235 amplitude = "lin"
172 hexcolor = QColor(*self.color).name() 236 hexcolor = QColor(*self.color).name()
173 opacity = "{0:.1f}".format(self.opacity / 100) 237 opacity = "{0:.1f}".format(self.opacity / 100)
174 genericPreview = self.settings.value("pref_genericPreview") 238 genericPreview = self.settings.value("pref_genericPreview")
175 if self.mode < 3: 239 if self.mode > 1:
176 filter_ = ( 240 filter_ = (
177 "showwaves=" 241 "showwaves="
178 f'r={str(self.settings.value("outputFrameRate"))}:' 242 f'r={str(self.settings.value("outputFrameRate"))}:'
@@ -180,10 +244,10 @@ class Component(Component):
180 f'mode={self.page.comboBox_mode.currentText().lower() if self.mode != 3 else "p2p"}:' 244 f'mode={self.page.comboBox_mode.currentText().lower() if self.mode != 3 else "p2p"}:'
181 f"colors={hexcolor}@{opacity}:scale={amplitude}" 245 f"colors={hexcolor}@{opacity}:scale={amplitude}"
182 ) 246 )
183 elif self.mode > 2: 247 elif self.mode < 2:
184 filter_ = ( 248 filter_ = (
185 f'showfreqs=s={str(self.settings.value("outputWidth"))}x{str(self.settings.value("outputHeight"))}:' 249 f'showfreqs=s={str(self.settings.value("outputWidth"))}x{str(self.settings.value("outputHeight"))}:'
186 f'mode={"line" if self.mode == 4 else "bar"}:' 250 f'mode={"line" if self.mode == 0 else "bar"}:'
187 f"colors={hexcolor}@{opacity}" 251 f"colors={hexcolor}@{opacity}"
188 f":ascale={amplitude}:fscale={'log' if self.mono else 'lin'}" 252 f":ascale={amplitude}:fscale={'log' if self.mono else 'lin'}"
189 ) 253 )
diff --git a/src/avp/components/waveform.ui b/src/avp/components/waveform.ui
index 5473f33..434ba62 100644
--- a/src/avp/components/waveform.ui
+++ b/src/avp/components/waveform.ui
@@ -27,156 +27,144 @@
27 </property> 27 </property>
28 <layout class="QVBoxLayout" name="verticalLayout_2"> 28 <layout class="QVBoxLayout" name="verticalLayout_2">
29 <item> 29 <item>
30 <layout class="QVBoxLayout" name="verticalLayout"> 30 <layout class="QHBoxLayout" name="horizontalLayout_8">
31 <property name="leftMargin">
32 <number>4</number>
33 </property>
34 <item> 31 <item>
35 <layout class="QHBoxLayout" name="horizontalLayout_8"> 32 <widget class="QLabel" name="label_textColor">
36 <item> 33 <property name="sizePolicy">
37 <widget class="QLabel" name="label_textColor"> 34 <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
38 <property name="sizePolicy"> 35 <horstretch>0</horstretch>
39 <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> 36 <verstretch>0</verstretch>
40 <horstretch>0</horstretch> 37 </sizepolicy>
41 <verstretch>0</verstretch> 38 </property>
42 </sizepolicy> 39 <property name="minimumSize">
43 </property> 40 <size>
44 <property name="minimumSize"> 41 <width>31</width>
45 <size> 42 <height>0</height>
46 <width>31</width> 43 </size>
47 <height>0</height> 44 </property>
48 </size> 45 <property name="text">
49 </property> 46 <string>Mode</string>
50 <property name="text"> 47 </property>
51 <string>Mode</string> 48 </widget>
52 </property> 49 </item>
53 </widget> 50 <item>
54 </item> 51 <widget class="QComboBox" name="comboBox_mode">
55 <item>
56 <widget class="QComboBox" name="comboBox_mode">
57 <item>
58 <property name="text">
59 <string>Cline</string>
60 </property>
61 </item>
62 <item>
63 <property name="text">
64 <string>Line</string>
65 </property>
66 </item>
67 <item>
68 <property name="text">
69 <string>Point</string>
70 </property>
71 </item>
72 <item>
73 <property name="text">
74 <string>Frequency Bar</string>
75 </property>
76 </item>
77 <item>
78 <property name="text">
79 <string>Frequency Line</string>
80 </property>
81 </item>
82 </widget>
83 </item>
84 <item>
85 <spacer name="horizontalSpacer_9">
86 <property name="orientation">
87 <enum>Qt::Horizontal</enum>
88 </property>
89 <property name="sizeType">
90 <enum>QSizePolicy::Fixed</enum>
91 </property>
92 <property name="sizeHint" stdset="0">
93 <size>
94 <width>5</width>
95 <height>20</height>
96 </size>
97 </property>
98 </spacer>
99 </item>
100 <item> 52 <item>
101 <widget class="QLabel" name="label_xTitleAlign"> 53 <property name="text">
102 <property name="sizePolicy"> 54 <string>Frequency Line</string>
103 <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> 55 </property>
104 <horstretch>0</horstretch>
105 <verstretch>0</verstretch>
106 </sizepolicy>
107 </property>
108 <property name="text">
109 <string>X</string>
110 </property>
111 </widget>
112 </item> 56 </item>
113 <item> 57 <item>
114 <widget class="QSpinBox" name="spinBox_x"> 58 <property name="text">
115 <property name="sizePolicy"> 59 <string>Frequency Bar</string>
116 <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> 60 </property>
117 <horstretch>0</horstretch>
118 <verstretch>0</verstretch>
119 </sizepolicy>
120 </property>
121 <property name="maximumSize">
122 <size>
123 <width>80</width>
124 <height>16777215</height>
125 </size>
126 </property>
127 <property name="minimum">
128 <number>-10000</number>
129 </property>
130 <property name="maximum">
131 <number>10000</number>
132 </property>
133 </widget>
134 </item> 61 </item>
135 <item> 62 <item>
136 <widget class="QLabel" name="label_yTitleAlign"> 63 <property name="text">
137 <property name="sizePolicy"> 64 <string>Cline</string>
138 <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> 65 </property>
139 <horstretch>0</horstretch>
140 <verstretch>0</verstretch>
141 </sizepolicy>
142 </property>
143 <property name="text">
144 <string>Y</string>
145 </property>
146 </widget>
147 </item> 66 </item>
148 <item> 67 <item>
149 <widget class="QSpinBox" name="spinBox_y"> 68 <property name="text">
150 <property name="sizePolicy"> 69 <string>Line</string>
151 <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> 70 </property>
152 <horstretch>0</horstretch>
153 <verstretch>0</verstretch>
154 </sizepolicy>
155 </property>
156 <property name="maximumSize">
157 <size>
158 <width>80</width>
159 <height>16777215</height>
160 </size>
161 </property>
162 <property name="baseSize">
163 <size>
164 <width>0</width>
165 <height>0</height>
166 </size>
167 </property>
168 <property name="minimum">
169 <number>-10000</number>
170 </property>
171 <property name="maximum">
172 <number>10000</number>
173 </property>
174 <property name="value">
175 <number>0</number>
176 </property>
177 </widget>
178 </item> 71 </item>
179 </layout> 72 </widget>
73 </item>
74 <item>
75 <spacer name="horizontalSpacer_9">
76 <property name="orientation">
77 <enum>Qt::Orientation::Horizontal</enum>
78 </property>
79 <property name="sizeType">
80 <enum>QSizePolicy::Policy::Fixed</enum>
81 </property>
82 <property name="sizeHint" stdset="0">
83 <size>
84 <width>5</width>
85 <height>20</height>
86 </size>
87 </property>
88 </spacer>
89 </item>
90 <item>
91 <widget class="QLabel" name="label_xTitleAlign">
92 <property name="sizePolicy">
93 <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
94 <horstretch>0</horstretch>
95 <verstretch>0</verstretch>
96 </sizepolicy>
97 </property>
98 <property name="text">
99 <string>X</string>
100 </property>
101 </widget>
102 </item>
103 <item>
104 <widget class="QSpinBox" name="spinBox_x">
105 <property name="sizePolicy">
106 <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
107 <horstretch>0</horstretch>
108 <verstretch>0</verstretch>
109 </sizepolicy>
110 </property>
111 <property name="maximumSize">
112 <size>
113 <width>80</width>
114 <height>16777215</height>
115 </size>
116 </property>
117 <property name="minimum">
118 <number>-10000</number>
119 </property>
120 <property name="maximum">
121 <number>10000</number>
122 </property>
123 </widget>
124 </item>
125 <item>
126 <widget class="QLabel" name="label_yTitleAlign">
127 <property name="sizePolicy">
128 <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
129 <horstretch>0</horstretch>
130 <verstretch>0</verstretch>
131 </sizepolicy>
132 </property>
133 <property name="text">
134 <string>Y</string>
135 </property>
136 </widget>
137 </item>
138 <item>
139 <widget class="QSpinBox" name="spinBox_y">
140 <property name="sizePolicy">
141 <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
142 <horstretch>0</horstretch>
143 <verstretch>0</verstretch>
144 </sizepolicy>
145 </property>
146 <property name="maximumSize">
147 <size>
148 <width>80</width>
149 <height>16777215</height>
150 </size>
151 </property>
152 <property name="baseSize">
153 <size>
154 <width>0</width>
155 <height>0</height>
156 </size>
157 </property>
158 <property name="minimum">
159 <number>-10000</number>
160 </property>
161 <property name="maximum">
162 <number>10000</number>
163 </property>
164 <property name="value">
165 <number>0</number>
166 </property>
167 </widget>
180 </item> 168 </item>
181 </layout> 169 </layout>
182 </item> 170 </item>
@@ -192,7 +180,7 @@
192 <item> 180 <item>
193 <widget class="QLineEdit" name="lineEdit_color"> 181 <widget class="QLineEdit" name="lineEdit_color">
194 <property name="inputMethodHints"> 182 <property name="inputMethodHints">
195 <set>Qt::ImhNone</set> 183 <set>Qt::InputMethodHint::ImhNone</set>
196 </property> 184 </property>
197 </widget> 185 </widget>
198 </item> 186 </item>
@@ -224,7 +212,7 @@
224 <item> 212 <item>
225 <spacer name="horizontalSpacer"> 213 <spacer name="horizontalSpacer">
226 <property name="orientation"> 214 <property name="orientation">
227 <enum>Qt::Horizontal</enum> 215 <enum>Qt::Orientation::Horizontal</enum>
228 </property> 216 </property>
229 <property name="sizeHint" stdset="0"> 217 <property name="sizeHint" stdset="0">
230 <size> 218 <size>
@@ -240,14 +228,14 @@
240 <string>Opacity</string> 228 <string>Opacity</string>
241 </property> 229 </property>
242 <property name="alignment"> 230 <property name="alignment">
243 <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> 231 <set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
244 </property> 232 </property>
245 </widget> 233 </widget>
246 </item> 234 </item>
247 <item> 235 <item>
248 <widget class="QSpinBox" name="spinBox_opacity"> 236 <widget class="QSpinBox" name="spinBox_opacity">
249 <property name="buttonSymbols"> 237 <property name="buttonSymbols">
250 <enum>QAbstractSpinBox::UpDownArrows</enum> 238 <enum>QAbstractSpinBox::ButtonSymbols::UpDownArrows</enum>
251 </property> 239 </property>
252 <property name="suffix"> 240 <property name="suffix">
253 <string>%</string> 241 <string>%</string>
@@ -269,14 +257,14 @@
269 <string>Scale</string> 257 <string>Scale</string>
270 </property> 258 </property>
271 <property name="alignment"> 259 <property name="alignment">
272 <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> 260 <set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
273 </property> 261 </property>
274 </widget> 262 </widget>
275 </item> 263 </item>
276 <item> 264 <item>
277 <widget class="QSpinBox" name="spinBox_scale"> 265 <widget class="QSpinBox" name="spinBox_scale">
278 <property name="buttonSymbols"> 266 <property name="buttonSymbols">
279 <enum>QAbstractSpinBox::UpDownArrows</enum> 267 <enum>QAbstractSpinBox::ButtonSymbols::UpDownArrows</enum>
280 </property> 268 </property>
281 <property name="suffix"> 269 <property name="suffix">
282 <string>%</string> 270 <string>%</string>
@@ -301,6 +289,9 @@
301 <property name="text"> 289 <property name="text">
302 <string>Compress</string> 290 <string>Compress</string>
303 </property> 291 </property>
292 <property name="checked">
293 <bool>true</bool>
294 </property>
304 </widget> 295 </widget>
305 </item> 296 </item>
306 <item> 297 <item>
@@ -320,7 +311,7 @@
320 <item> 311 <item>
321 <spacer name="horizontalSpacer_2"> 312 <spacer name="horizontalSpacer_2">
322 <property name="orientation"> 313 <property name="orientation">
323 <enum>Qt::Horizontal</enum> 314 <enum>Qt::Orientation::Horizontal</enum>
324 </property> 315 </property>
325 <property name="sizeHint" stdset="0"> 316 <property name="sizeHint" stdset="0">
326 <size> 317 <size>
@@ -341,22 +332,22 @@
341 <widget class="QComboBox" name="comboBox_amplitude"> 332 <widget class="QComboBox" name="comboBox_amplitude">
342 <item> 333 <item>
343 <property name="text"> 334 <property name="text">
344 <string>Linear</string> 335 <string>Logarithmic</string>
345 </property> 336 </property>
346 </item> 337 </item>
347 <item> 338 <item>
348 <property name="text"> 339 <property name="text">
349 <string>Logarithmic</string> 340 <string>Square root</string>
350 </property> 341 </property>
351 </item> 342 </item>
352 <item> 343 <item>
353 <property name="text"> 344 <property name="text">
354 <string>Square root</string> 345 <string>Cubic root</string>
355 </property> 346 </property>
356 </item> 347 </item>
357 <item> 348 <item>
358 <property name="text"> 349 <property name="text">
359 <string>Cubic root</string> 350 <string>Linear</string>
360 </property> 351 </property>
361 </item> 352 </item>
362 </widget> 353 </widget>
@@ -364,9 +355,52 @@
364 </layout> 355 </layout>
365 </item> 356 </item>
366 <item> 357 <item>
358 <layout class="QHBoxLayout" name="horizontalLayout">
359 <item>
360 <widget class="QLabel" name="label_speed">
361 <property name="text">
362 <string>Animation Speed</string>
363 </property>
364 </widget>
365 </item>
366 <item>
367 <widget class="QSpinBox" name="spinBox_speed">
368 <property name="suffix">
369 <string>%</string>
370 </property>
371 <property name="minimum">
372 <number>10</number>
373 </property>
374 <property name="maximum">
375 <number>100</number>
376 </property>
377 <property name="singleStep">
378 <number>10</number>
379 </property>
380 <property name="value">
381 <number>50</number>
382 </property>
383 </widget>
384 </item>
385 <item>
386 <spacer name="horizontalSpacer_3">
387 <property name="orientation">
388 <enum>Qt::Orientation::Horizontal</enum>
389 </property>
390 <property name="sizeHint" stdset="0">
391 <size>
392 <width>40</width>
393 <height>20</height>
394 </size>
395 </property>
396 </spacer>
397 </item>
398 </layout>
399 </item>
400 <item>
367 <spacer name="verticalSpacer"> 401 <spacer name="verticalSpacer">
368 <property name="orientation"> 402 <property name="orientation">
369 <enum>Qt::Vertical</enum> 403 <enum>Qt::Orientation::Vertical</enum>
370 </property> 404 </property>
371 <property name="sizeHint" stdset="0"> 405 <property name="sizeHint" stdset="0">
372 <size> 406 <size>
diff --git a/src/avp/core.py b/src/avp/core.py
index 196cd7d..099b0b4 100644
--- a/src/avp/core.py
+++ b/src/avp/core.py
@@ -12,7 +12,7 @@ import logging
12 12
13from . import toolkit 13from . import toolkit
14 14
15 15appName = "Audio Visualizer Python"
16log = logging.getLogger("AVP.Core") 16log = logging.getLogger("AVP.Core")
17STDOUT_LOGLVL = logging.WARNING 17STDOUT_LOGLVL = logging.WARNING
18 18
diff --git a/src/avp/gui/mainwindow.py b/src/avp/gui/mainwindow.py
index e7a5fe3..5a051fd 100644
--- a/src/avp/gui/mainwindow.py
+++ b/src/avp/gui/mainwindow.py
@@ -7,7 +7,7 @@ projects and exporting the video at a later time.
7 7
8from PyQt6 import QtCore, QtWidgets, uic 8from PyQt6 import QtCore, QtWidgets, uic
9import PyQt6.QtWidgets as QtWidgets 9import PyQt6.QtWidgets as QtWidgets
10from PyQt6.QtGui import QUndoStack, QShortcut 10from PyQt6.QtGui import QShortcut
11from PIL import Image 11from PIL import Image
12from queue import Queue 12from queue import Queue
13import sys 13import sys
@@ -16,12 +16,16 @@ import signal
16import filecmp 16import filecmp
17import time 17import time
18import logging 18import logging
19from textwrap import wrap
19 20
20from ..core import Core 21from ..__init__ import __version__
22from ..core import Core, appName
23from .undostack import UndoStack
21from . import preview_thread 24from . import preview_thread
22from .preview_win import PreviewWindow 25from .preview_win import PreviewWindow
23from .presetmanager import PresetManager 26from .presetmanager import PresetManager
24from .actions import * 27from .actions import *
28from ..toolkit.ffmpeg import createFfmpegCommand
25from ..toolkit import ( 29from ..toolkit import (
26 disableWhenEncoding, 30 disableWhenEncoding,
27 disableWhenOpeningProject, 31 disableWhenOpeningProject,
@@ -30,25 +34,9 @@ from ..toolkit import (
30) 34)
31 35
32 36
33appName = "Audio Visualizer"
34log = logging.getLogger("AVP.Gui.MainWindow") 37log = logging.getLogger("AVP.Gui.MainWindow")
35 38
36 39
37class MyQUndoStack(QUndoStack):
38 # FIXME move this class
39 @property
40 def encoding(self):
41 return self.parent().encoding
42
43 @disableWhenEncoding
44 def undo(self, *args, **kwargs):
45 super().undo(*args, **kwargs)
46
47 @disableWhenEncoding
48 def redo(self, *args, **kwargs):
49 super().redo(*args, **kwargs)
50
51
52class MainWindow(QtWidgets.QMainWindow): 40class MainWindow(QtWidgets.QMainWindow):
53 """ 41 """
54 The MainWindow wraps many Core methods in order to update the GUI 42 The MainWindow wraps many Core methods in order to update the GUI
@@ -91,7 +79,7 @@ class MainWindow(QtWidgets.QMainWindow):
91 self.settings = Core.settings 79 self.settings = Core.settings
92 80
93 # Create stack of undoable user actions 81 # Create stack of undoable user actions
94 self.undoStack = MyQUndoStack(self) 82 self.undoStack = UndoStack(self)
95 undoLimit = self.settings.value("pref_undoLimit") 83 undoLimit = self.settings.value("pref_undoLimit")
96 self.undoStack.setUndoLimit(undoLimit) 84 self.undoStack.setUndoLimit(undoLimit)
97 85
@@ -102,6 +90,7 @@ class MainWindow(QtWidgets.QMainWindow):
102 layout = QtWidgets.QVBoxLayout() 90 layout = QtWidgets.QVBoxLayout()
103 layout.addWidget(undoView) 91 layout.addWidget(undoView)
104 self.undoDialog.setLayout(layout) 92 self.undoDialog.setLayout(layout)
93 self.undoDialog.setMinimumWidth(int(self.width() / 2))
105 94
106 # Create Preset Manager 95 # Create Preset Manager
107 self.presetManager = PresetManager(self) 96 self.presetManager = PresetManager(self)
@@ -325,7 +314,11 @@ class MainWindow(QtWidgets.QMainWindow):
325 self.drawPreview(True) 314 self.drawPreview(True)
326 315
327 log.info("Pillow version %s", Image.__version__) 316 log.info("Pillow version %s", Image.__version__)
328 log.info("PyQt version %s (Qt version %s)", QtCore.PYQT_VERSION_STR, QtCore.QT_VERSION_STR) 317 log.info(
318 "PyQt version %s (Qt version %s)",
319 QtCore.PYQT_VERSION_STR,
320 QtCore.QT_VERSION_STR,
321 )
329 322
330 # verify Ffmpeg version 323 # verify Ffmpeg version
331 if not self.core.FFMPEG_BIN: 324 if not self.core.FFMPEG_BIN:
@@ -408,6 +401,7 @@ class MainWindow(QtWidgets.QMainWindow):
408 activated=lambda: self.moveComponent("bottom"), 401 activated=lambda: self.moveComponent("bottom"),
409 ) 402 )
410 403
404 QShortcut("F1", self, self.showHelpWindow)
411 QShortcut("Ctrl+Shift+F", self, self.showFfmpegCommand) 405 QShortcut("Ctrl+Shift+F", self, self.showFfmpegCommand)
412 QShortcut("Ctrl+Shift+U", self, self.showUndoStack) 406 QShortcut("Ctrl+Shift+U", self, self.showUndoStack)
413 407
@@ -422,6 +416,12 @@ class MainWindow(QtWidgets.QMainWindow):
422 if not self.core.selectedComponents: 416 if not self.core.selectedComponents:
423 self.core.insertComponent(0, 0, self) 417 self.core.insertComponent(0, 0, self)
424 self.core.insertComponent(1, 1, self) 418 self.core.insertComponent(1, 1, self)
419 # set colors to white and black to match classic appearance of program
420 self.core.selectedComponents[0].page.lineEdit_visColor.setText(
421 "255,255,255"
422 )
423 self.core.selectedComponents[1].page.lineEdit_color1.setText("0,0,0")
424 self.undoStack.clear()
425 425
426 def __repr__(self): 426 def __repr__(self):
427 return ( 427 return (
@@ -762,10 +762,10 @@ class MainWindow(QtWidgets.QMainWindow):
762 def showUndoStack(self): 762 def showUndoStack(self):
763 self.undoDialog.show() 763 self.undoDialog.show()
764 764
765 def showFfmpegCommand(self): 765 def showHelpWindow(self):
766 from textwrap import wrap 766 self.showMessage(msg=f"{appName} v{__version__}")
767 from ..toolkit.ffmpeg import createFfmpegCommand
768 767
768 def showFfmpegCommand(self):
769 command = createFfmpegCommand( 769 command = createFfmpegCommand(
770 self.lineEdit_audioFile.text(), 770 self.lineEdit_audioFile.text(),
771 self.lineEdit_outputFile.text(), 771 self.lineEdit_outputFile.text(),
@@ -899,7 +899,9 @@ class MainWindow(QtWidgets.QMainWindow):
899 @disableWhenEncoding 899 @disableWhenEncoding
900 def createNewProject(self, prompt=True): 900 def createNewProject(self, prompt=True):
901 if prompt: 901 if prompt:
902 self.openSaveChangesDialog("starting a new project") 902 ch = self.openSaveChangesDialog("starting a new project")
903 if ch is None:
904 return
903 905
904 self.clear() 906 self.clear()
905 self.currentProject = None 907 self.currentProject = None
@@ -919,18 +921,19 @@ class MainWindow(QtWidgets.QMainWindow):
919 921
920 def openSaveChangesDialog(self, phrase): 922 def openSaveChangesDialog(self, phrase):
921 success = True 923 success = True
924 ch = True
922 if self.autosaveExists(identical=False): 925 if self.autosaveExists(identical=False):
923 ch = self.showMessage( 926 ch = self.showMessage(
924 msg="You have unsaved changes in project '%s'. " 927 msg="You have unsaved changes in project '%s'. "
925 "Save before %s?" 928 "Save before %s?"
926 % (os.path.basename(self.currentProject)[:-4], phrase), 929 % (os.path.basename(self.currentProject)[:-4], phrase),
927 showCancel=True, 930 showDiscard=True,
928 ) 931 )
929 if ch: 932 if ch:
930 success = self.saveProjectChanges() 933 success = self.saveProjectChanges()
931 934 if ch is not None and success and os.path.exists(self.autosavePath):
932 if success and os.path.exists(self.autosavePath):
933 os.remove(self.autosavePath) 935 os.remove(self.autosavePath)
936 return success and ch
934 937
935 def openSaveProjectDialog(self): 938 def openSaveProjectDialog(self):
936 filename, _ = QtWidgets.QFileDialog.getSaveFileName( 939 filename, _ = QtWidgets.QFileDialog.getSaveFileName(
@@ -967,10 +970,12 @@ class MainWindow(QtWidgets.QMainWindow):
967 ): 970 ):
968 return 971 return
969 972
970 self.clear()
971 # ask to save any changes that are about to get deleted 973 # ask to save any changes that are about to get deleted
972 if prompt: 974 if prompt:
973 self.openSaveChangesDialog("opening another project") 975 ch = self.openSaveChangesDialog("opening another project")
976 if ch is None:
977 return
978 self.clear()
974 979
975 self.currentProject = filepath 980 self.currentProject = filepath
976 self.settings.setValue("currentProject", filepath) 981 self.settings.setValue("currentProject", filepath)
@@ -992,7 +997,13 @@ class MainWindow(QtWidgets.QMainWindow):
992 else QtWidgets.QMessageBox.Icon.Information 997 else QtWidgets.QMessageBox.Icon.Information
993 ) 998 )
994 msg.setDetailedText(kwargs["detail"] if "detail" in kwargs else None) 999 msg.setDetailedText(kwargs["detail"] if "detail" in kwargs else None)
995 if "showCancel" in kwargs and kwargs["showCancel"]: 1000 if "showDiscard" in kwargs and kwargs["showDiscard"]:
1001 msg.setStandardButtons(
1002 QtWidgets.QMessageBox.StandardButton.Save
1003 | QtWidgets.QMessageBox.StandardButton.Discard
1004 | QtWidgets.QMessageBox.StandardButton.Cancel
1005 )
1006 elif "showCancel" in kwargs and kwargs["showCancel"]:
996 msg.setStandardButtons( 1007 msg.setStandardButtons(
997 QtWidgets.QMessageBox.StandardButton.Ok 1008 QtWidgets.QMessageBox.StandardButton.Ok
998 | QtWidgets.QMessageBox.StandardButton.Cancel 1009 | QtWidgets.QMessageBox.StandardButton.Cancel
@@ -1000,9 +1011,14 @@ class MainWindow(QtWidgets.QMainWindow):
1000 else: 1011 else:
1001 msg.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Ok) 1012 msg.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Ok)
1002 ch = msg.exec() 1013 ch = msg.exec()
1003 if ch == 1024: 1014 if ch == 1024 or ch == 2048:
1015 # OK or Save
1004 return True 1016 return True
1005 return False 1017 elif ch > 8000000:
1018 # Discard
1019 return False
1020 # Cancel
1021 return None
1006 1022
1007 @disableWhenEncoding 1023 @disableWhenEncoding
1008 def componentContextMenu(self, QPos): 1024 def componentContextMenu(self, QPos):
diff --git a/src/avp/gui/presetmanager.py b/src/avp/gui/presetmanager.py
index 980a969..ca0029d 100644
--- a/src/avp/gui/presetmanager.py
+++ b/src/avp/gui/presetmanager.py
@@ -9,7 +9,7 @@ import os
9import logging 9import logging
10 10
11from ..toolkit import badName 11from ..toolkit import badName
12from ..core import Core 12from ..core import Core, appName
13from .actions import * 13from .actions import *
14 14
15 15
@@ -137,7 +137,7 @@ class PresetManager(QtWidgets.QDialog):
137 currentPreset = selectedComponents[index].currentPreset 137 currentPreset = selectedComponents[index].currentPreset
138 newName, OK = QtWidgets.QInputDialog.getText( 138 newName, OK = QtWidgets.QInputDialog.getText(
139 self.parent, 139 self.parent,
140 "Audio Visualizer", 140 appName,
141 "New Preset Name:", 141 "New Preset Name:",
142 QtWidgets.QLineEdit.EchoMode.Normal, 142 QtWidgets.QLineEdit.EchoMode.Normal,
143 currentPreset, 143 currentPreset,
diff --git a/src/avp/gui/preview_thread.py b/src/avp/gui/preview_thread.py
index 1d78516..a59652a 100644
--- a/src/avp/gui/preview_thread.py
+++ b/src/avp/gui/preview_thread.py
@@ -65,17 +65,18 @@ class Worker(QtCore.QObject):
65 component.unlockSize() 65 component.unlockSize()
66 frame = Image.alpha_composite(frame, newFrame) 66 frame = Image.alpha_composite(frame, newFrame)
67 67
68 except ValueError as e: 68 except (AttributeError, ValueError) as e:
69 errMsg = ( 69 errMsg = (
70 "Bad frame returned by %s's preview renderer. " 70 "Bad frame returned by %s's preview renderer. "
71 "%s. New frame size was %s*%s; should be %s*%s." 71 "%s. New frame %s."
72 % ( 72 % (
73 str(component), 73 str(component),
74 str(e).capitalize(), 74 str(e).capitalize(),
75 newFrame.width, 75 "is None" if newFrame is None else "size was %s*%s; should be %s*%s" % (
76 newFrame.height, 76 newFrame.width,
77 width, 77 newFrame.height,
78 height, 78 width,
79 height),
79 ) 80 )
80 ) 81 )
81 log.critical(errMsg) 82 log.critical(errMsg)
diff --git a/src/avp/gui/undostack.py b/src/avp/gui/undostack.py
new file mode 100644
index 0000000..fd1a3e9
--- /dev/null
+++ b/src/avp/gui/undostack.py
@@ -0,0 +1,16 @@
1from PyQt6.QtGui import QUndoStack
2from ..toolkit.common import disableWhenEncoding
3
4
5class UndoStack(QUndoStack):
6 @property
7 def encoding(self):
8 return self.parent().encoding
9
10 @disableWhenEncoding
11 def undo(self, *args, **kwargs):
12 super().undo(*args, **kwargs)
13
14 @disableWhenEncoding
15 def redo(self, *args, **kwargs):
16 super().redo(*args, **kwargs)
diff --git a/src/avp/toolkit/common.py b/src/avp/toolkit/common.py
index e35aba2..a6195ed 100644
--- a/src/avp/toolkit/common.py
+++ b/src/avp/toolkit/common.py
@@ -4,7 +4,7 @@ Common functions
4 4
5from PyQt6 import QtWidgets 5from PyQt6 import QtWidgets
6import string 6import string
7import os 7import random
8import sys 8import sys
9import subprocess 9import subprocess
10import logging 10import logging
@@ -135,7 +135,12 @@ def rgbFromString(string):
135 if i > 255 or i < 0: 135 if i > 255 or i < 0:
136 raise ValueError 136 raise ValueError
137 return tup 137 return tup
138 except: 138 except Exception as e:
139 log.warning(
140 "Could not parse '%s' as a color (encountered %s).",
141 string,
142 type(e).__name__,
143 )
139 return (255, 255, 255) 144 return (255, 255, 255)
140 145
141 146
@@ -150,6 +155,7 @@ def formatTraceback(tb=None):
150 155
151 156
152def connectWidget(widget, func): 157def connectWidget(widget, func):
158 unsupportedWidgets = ["QtWidgets.QFontComboBox"]
153 if type(widget) == QtWidgets.QLineEdit: 159 if type(widget) == QtWidgets.QLineEdit:
154 widget.textChanged.connect(func) 160 widget.textChanged.connect(func)
155 elif type(widget) == QtWidgets.QSpinBox or type(widget) == QtWidgets.QDoubleSpinBox: 161 elif type(widget) == QtWidgets.QSpinBox or type(widget) == QtWidgets.QDoubleSpinBox:
@@ -158,6 +164,10 @@ def connectWidget(widget, func):
158 widget.stateChanged.connect(func) 164 widget.stateChanged.connect(func)
159 elif type(widget) == QtWidgets.QComboBox: 165 elif type(widget) == QtWidgets.QComboBox:
160 widget.currentIndexChanged.connect(func) 166 widget.currentIndexChanged.connect(func)
167 elif type(widget) in unsupportedWidgets:
168 log.info(
169 "Could not connect %s using connectWidget()", str(widget.__class__.__name__)
170 )
161 else: 171 else:
162 log.warning("Failed to connect %s ", str(widget.__class__.__name__)) 172 log.warning("Failed to connect %s ", str(widget.__class__.__name__))
163 return False 173 return False
@@ -190,3 +200,7 @@ def getWidgetValue(widget):
190 return widget.isChecked() 200 return widget.isChecked()
191 elif type(widget) == QtWidgets.QComboBox: 201 elif type(widget) == QtWidgets.QComboBox:
192 return widget.currentIndex() 202 return widget.currentIndex()
203
204
205def randomColor():
206 return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
diff --git a/src/avp/toolkit/frame.py b/src/avp/toolkit/frame.py
index 94537a6..829b05b 100644
--- a/src/avp/toolkit/frame.py
+++ b/src/avp/toolkit/frame.py
@@ -3,7 +3,7 @@ Common tools for drawing compatible frames in a Component's frameRender()
3""" 3"""
4 4
5from PyQt6 import QtGui 5from PyQt6 import QtGui
6from PIL import Image 6from PIL import Image, ImageEnhance, ImageChops, ImageFilter
7from PIL.ImageQt import ImageQt 7from PIL.ImageQt import ImageQt
8from PyQt6 import QtCore 8from PyQt6 import QtCore
9import sys 9import sys
@@ -30,7 +30,7 @@ class FramePainter(QtGui.QPainter):
30 30
31 def setPen(self, penStyle): 31 def setPen(self, penStyle):
32 if type(penStyle) is tuple: 32 if type(penStyle) is tuple:
33 super().setPen(PaintColor(*penStyle)) 33 super().setPen(QtGui.QColor(*penStyle))
34 else: 34 else:
35 super().setPen(penStyle) 35 super().setPen(penStyle)
36 36
@@ -45,24 +45,14 @@ class FramePainter(QtGui.QPainter):
45 buffer.close() 45 buffer.close()
46 self.end() 46 self.end()
47 return frame 47 return frame
48 imBytes = self.image.bits().asstring(self.image.byteCount())
49 frame = Image.frombytes(
50 "RGBA", (self.image.width(), self.image.height()), imBytes
51 )
52 self.end()
53 return frame
54
55 48
56class PaintColor(QtGui.QColor):
57 """
58 Subclass of QtGui.QColor with an added scale() method
59 Previously this class reversed the painter colour to solve
60 hardware issues related to endianness,
61 but Qt appears to deal with this itself nowadays
62 """
63 49
64 def __init__(self, r, g, b, a=255): 50def addShadow(frame, blurRadius, blurOffsetX, blurOffsetY):
65 super().__init__(r, g, b, a) 51 shadImg = ImageEnhance.Contrast(frame).enhance(0.0)
52 shadImg = shadImg.filter(ImageFilter.GaussianBlur(blurRadius))
53 frame = shadImg.paste(frame, box=(-blurOffsetX, -blurOffsetY), mask=frame)
54 frame = shadImg
55 return frame
66 56
67 57
68def scale(scalePercent, width, height, returntype=None): 58def scale(scalePercent, width, height, returntype=None):
diff --git a/src/avp/toolkit/visualizer.py b/src/avp/toolkit/visualizer.py
new file mode 100644
index 0000000..c55a3f3
--- /dev/null
+++ b/src/avp/toolkit/visualizer.py
@@ -0,0 +1,87 @@
1"""Functions used to transform and manipulate audio for use by visualizers"""
2
3from copy import copy
4import numpy
5
6
7def createSpectrumArray(
8 component,
9 completeAudioArray,
10 sampleSize,
11 smoothConstantDown,
12 smoothConstantUp,
13 scale,
14 progressBarUpdate,
15 progressBarSetText,
16):
17 lastSpectrum = None
18 spectrumArray = {}
19 for i in range(0, len(completeAudioArray), sampleSize):
20 if component.canceled:
21 break
22 lastSpectrum = transformData(
23 i,
24 completeAudioArray,
25 sampleSize,
26 smoothConstantDown,
27 smoothConstantUp,
28 lastSpectrum,
29 scale,
30 )
31 spectrumArray[i] = copy(lastSpectrum)
32
33 progress = int(100 * (i / len(completeAudioArray)))
34 if progress >= 100:
35 progress = 100
36 progressText = f"Analyzing audio: {str(progress)}%"
37 progressBarSetText.emit(progressText)
38 progressBarUpdate.emit(int(progress))
39 return spectrumArray
40
41
42def transformData(
43 i,
44 completeAudioArray,
45 sampleSize,
46 smoothConstantDown,
47 smoothConstantUp,
48 lastSpectrum,
49 scale,
50):
51 if len(completeAudioArray) < (i + sampleSize):
52 sampleSize = len(completeAudioArray) - i
53
54 window = numpy.hanning(sampleSize)
55 data = completeAudioArray[i : i + sampleSize][::1] * window
56 paddedSampleSize = 2048
57 paddedData = numpy.pad(data, (0, paddedSampleSize - sampleSize), "constant")
58 spectrum = numpy.fft.fft(paddedData)
59 sample_rate = 44100
60 frequencies = numpy.fft.fftfreq(len(spectrum), 1.0 / sample_rate)
61
62 y = abs(spectrum[0 : int(paddedSampleSize / 2) - 1])
63
64 # filter the noise away
65 # y[y<80] = 0
66
67 with numpy.errstate(divide="ignore"):
68 y = scale * numpy.log10(y)
69
70 y[numpy.isinf(y)] = 0
71
72 if lastSpectrum is not None:
73 lastSpectrum[y < lastSpectrum] = y[
74 y < lastSpectrum
75 ] * smoothConstantDown + lastSpectrum[y < lastSpectrum] * (
76 1 - smoothConstantDown
77 )
78
79 lastSpectrum[y >= lastSpectrum] = y[
80 y >= lastSpectrum
81 ] * smoothConstantUp + lastSpectrum[y >= lastSpectrum] * (1 - smoothConstantUp)
82 else:
83 lastSpectrum = y
84
85 x = frequencies[0 : int(paddedSampleSize / 2) - 1]
86
87 return lastSpectrum
diff --git a/tests/test_commandline_parser.py b/tests/test_commandline_parser.py
index d092072..77836ce 100644
--- a/tests/test_commandline_parser.py
+++ b/tests/test_commandline_parser.py
@@ -51,6 +51,11 @@ def test_commandline_parses_classic_by_alias(qtbot):
51 assert command.parseCompName("original") == "Classic Visualizer" 51 assert command.parseCompName("original") == "Classic Visualizer"
52 52
53 53
54def test_commandline_parses_conway_by_name(qtbot): 54def test_commandline_parses_conway_by_short_name(qtbot):
55 command = Command() 55 command = Command()
56 assert command.parseCompName("conway") == "Conway's Game of Life" 56 assert command.parseCompName("conway") == "Conway's Game of Life"
57
58
59def test_commandline_parses_image_by_name(qtbot):
60 command = Command()
61 assert command.parseCompName("image") == "Image"
diff --git a/tests/test_comp_color.py b/tests/test_comp_color.py
new file mode 100644
index 0000000..6b82e4c
--- /dev/null
+++ b/tests/test_comp_color.py
@@ -0,0 +1,22 @@
1from avp.command import Command
2from pytestqt import qtbot
3from pytest import fixture
4from . import imageDataSum
5
6
7@fixture
8def coreWithColorComp(qtbot):
9 """Fixture providing a Command object with Color component added"""
10 command = Command()
11 command.settings.setValue("outputHeight", 1080)
12 command.settings.setValue("outputWidth", 1920)
13 command.core.insertComponent(0, command.core.moduleIndexFor("Color"), command)
14 yield command.core
15
16
17def test_comp_color_set_color(coreWithColorComp):
18 "Set imagePath of Image component"
19 comp = coreWithColorComp.selectedComponents[0]
20 comp.page.lineEdit_color1.setText("111,111,111")
21 image = comp.previewRender()
22 assert imageDataSum(image) == 1219276800
diff --git a/tests/test_image_comp.py b/tests/test_comp_image.py
index a4f05e1..a4f05e1 100644
--- a/tests/test_image_comp.py
+++ b/tests/test_comp_image.py
diff --git a/tests/test_comp_life.py b/tests/test_comp_life.py
new file mode 100644
index 0000000..ad78e52
--- /dev/null
+++ b/tests/test_comp_life.py
@@ -0,0 +1,23 @@
1from avp.command import Command
2from pytestqt import qtbot
3from pytest import fixture
4from . import imageDataSum
5
6
7@fixture
8def coreWithLifeComp(qtbot):
9 """Fixture providing a Command object with Waveform component added"""
10 command = Command()
11 command.settings.setValue("outputHeight", 1080)
12 command.settings.setValue("outputWidth", 1920)
13 command.core.insertComponent(
14 0, command.core.moduleIndexFor("Conway's Game of Life"), command
15 )
16 yield command.core
17
18
19def test_comp_life_previewRender(coreWithLifeComp):
20 comp = coreWithLifeComp.selectedComponents[0]
21 comp.page.lineEdit_color.setText("111,111,111")
22 image = comp.previewRender()
23 assert imageDataSum(image) == 339785512
diff --git a/tests/test_classic_visualizer.py b/tests/test_comp_original.py
index e301263..6264644 100644
--- a/tests/test_classic_visualizer.py
+++ b/tests/test_comp_original.py
@@ -1,4 +1,5 @@
1from avp.command import Command 1from avp.command import Command
2from avp.toolkit.visualizer import transformData
2from pytestqt import qtbot 3from pytestqt import qtbot
3from pytest import fixture 4from pytest import fixture
4from . import audioData, MockSignal, imageDataSum 5from . import audioData, MockSignal, imageDataSum
@@ -31,13 +32,9 @@ def test_comp_classic_removed(coreWithClassicComp):
31def test_comp_classic_drawBars(coreWithClassicComp, audioData): 32def test_comp_classic_drawBars(coreWithClassicComp, audioData):
32 """Call drawBars after creating audio spectrum data manually.""" 33 """Call drawBars after creating audio spectrum data manually."""
33 34
34 spectrumArray = { 35 spectrumArray = {0: transformData(0, audioData[0], sampleSize, 0.08, 0.8, None, 20)}
35 0: coreWithClassicComp.selectedComponents[0].transformData(
36 0, audioData[0], sampleSize, 0.08, 0.8, None, 20
37 )
38 }
39 for i in range(sampleSize, len(audioData[0]), sampleSize): 36 for i in range(sampleSize, len(audioData[0]), sampleSize):
40 spectrumArray[i] = coreWithClassicComp.selectedComponents[0].transformData( 37 spectrumArray[i] = transformData(
41 i, 38 i,
42 audioData[0], 39 audioData[0],
43 sampleSize, 40 sampleSize,
diff --git a/tests/test_comp_spectrum.py b/tests/test_comp_spectrum.py
new file mode 100644
index 0000000..44fb257
--- /dev/null
+++ b/tests/test_comp_spectrum.py
@@ -0,0 +1,20 @@
1from avp.command import Command
2from pytestqt import qtbot
3from pytest import fixture
4from . import imageDataSum
5
6
7@fixture
8def coreWithSpectrumComp(qtbot):
9 """Fixture providing a Command object with Spectrum component added"""
10 command = Command()
11 command.settings.setValue("outputHeight", 1080)
12 command.settings.setValue("outputWidth", 1920)
13 command.core.insertComponent(0, command.core.moduleIndexFor("Spectrum"), command)
14 yield command.core
15
16
17def test_comp_waveform_previewRender(coreWithSpectrumComp):
18 comp = coreWithSpectrumComp.selectedComponents[0]
19 image = comp.previewRender()
20 assert imageDataSum(image) == 71992628
diff --git a/tests/test_comp_text.py b/tests/test_comp_text.py
new file mode 100644
index 0000000..e389ff9
--- /dev/null
+++ b/tests/test_comp_text.py
@@ -0,0 +1,45 @@
1from avp.command import Command
2from PyQt6.QtGui import QFont
3from pytestqt import qtbot
4from pytest import fixture, mark
5from . import audioData, MockSignal, imageDataSum
6
7
8@fixture
9def coreWithTextComp(qtbot):
10 """Fixture providing a Command object with Title Text component added"""
11 command = Command()
12 command.core.insertComponent(0, command.core.moduleIndexFor("Title Text"), command)
13 yield command.core
14
15
16def setTextSettings(comp):
17 comp.page.spinBox_fontSize.setValue(40)
18 comp.page.checkBox_shadow.setChecked(True)
19 comp.page.spinBox_shadBlur.setValue(0)
20 comp.page.spinBox_shadX.setValue(2)
21 comp.page.spinBox_shadY.setValue(-2)
22 comp.page.fontComboBox_titleFont.setCurrentFont(QFont("Noto Sans"))
23 comp.page.lineEdit_textColor.setText("255,255,255")
24
25
26@mark.parametrize(
27 "width, height",
28 ((1920, 1080), (1280, 720)),
29)
30def test_comp_text_renderFrame(coreWithTextComp, width, height):
31 """Call renderFrame of Title Text component added to Command object."""
32 comp = coreWithTextComp.selectedComponents[0]
33 comp.parent.settings.setValue("outputWidth", width)
34 comp.parent.settings.setValue("outputHeight", height)
35 setTextSettings(comp)
36 comp.centerXY()
37 image = comp.frameRender(0)
38 assert comp.titleFont.family() == "Noto Sans"
39 assert comp.xPosition == width / 2
40 assert image.width == width
41 assert comp.fontSize == 40
42 assert comp.shadX == 2
43 assert comp.shadY == -2
44 assert comp.shadBlur == 0
45 assert imageDataSum(image) == 727403 or 738586
diff --git a/tests/test_comp_waveform.py b/tests/test_comp_waveform.py
new file mode 100644
index 0000000..a71040b
--- /dev/null
+++ b/tests/test_comp_waveform.py
@@ -0,0 +1,17 @@
1from avp.command import Command
2from pytestqt import qtbot
3from pytest import fixture
4
5
6@fixture
7def coreWithWaveformComp(qtbot):
8 """Fixture providing a Command object with Waveform component added"""
9 command = Command()
10 command.core.insertComponent(0, command.core.moduleIndexFor("Waveform"), command)
11 yield command.core
12
13
14def test_comp_waveform_setColor(coreWithWaveformComp):
15 comp = coreWithWaveformComp.selectedComponents[0]
16 comp.page.lineEdit_color.setText("255,255,255")
17 assert comp.color == (255, 255, 255)
diff --git a/tests/test_text_comp.py b/tests/test_text_comp.py
deleted file mode 100644
index 23dd1fd..0000000
--- a/tests/test_text_comp.py
+++ /dev/null
@@ -1,34 +0,0 @@
1from avp.command import Command
2from PyQt6.QtGui import QFont
3from pytestqt import qtbot
4from pytest import fixture
5from . import audioData, MockSignal, imageDataSum
6
7
8@fixture
9def coreWithTextComp(qtbot):
10 """Fixture providing a Command object with Title Text component added"""
11 command = Command()
12 command.core.insertComponent(0, command.core.moduleIndexFor("Title Text"), command)
13 yield command.core
14
15
16def test_comp_text_renderFrame_resize(coreWithTextComp):
17 """Call renderFrame of Title Text component added to Command object."""
18 comp = coreWithTextComp.selectedComponents[0]
19 comp.parent.settings.setValue("outputWidth", 1920)
20 comp.parent.settings.setValue("outputHeight", 1080)
21 comp.parent.core.updateComponent(0)
22 comp.titleFont = QFont("Noto Sans")
23 image = comp.frameRender(0)
24 assert imageDataSum(image) == 2957069
25
26
27def test_comp_text_renderFrame(coreWithTextComp):
28 """Call renderFrame of Title Text component added to Command object."""
29 comp = coreWithTextComp.selectedComponents[0]
30 comp.parent.settings.setValue("outputWidth", 1280)
31 comp.parent.settings.setValue("outputHeight", 720)
32 comp.parent.core.updateComponent(0)
33 image = comp.frameRender(0)
34 assert imageDataSum(image) == 1412293 or 1379298
diff --git a/tests/test_toolkit_common.py b/tests/test_toolkit_common.py
index d903842..8e9dca2 100644
--- a/tests/test_toolkit_common.py
+++ b/tests/test_toolkit_common.py
@@ -1,6 +1,27 @@
1from pytest import fixture
1from pytestqt import qtbot 2from pytestqt import qtbot
2from avp.command import Command 3from avp.command import Command
3from avp.toolkit import blockSignals 4from avp.toolkit import blockSignals, rgbFromString
5
6
7@fixture
8def gotWarning():
9 """Check if a function called log.warning"""
10 import avp.toolkit.common as tk
11 warning = False
12 def gotWarning():
13 nonlocal warning
14 return warning
15 class log:
16 def warning(self, *args):
17 nonlocal warning
18 warning = True
19 oldLog = tk.log
20 tk.log = log()
21 try:
22 yield gotWarning
23 finally:
24 tk.log = oldLog
4 25
5 26
6def test_blockSignals(qtbot): 27def test_blockSignals(qtbot):
@@ -11,3 +32,13 @@ def test_blockSignals(qtbot):
11 with blockSignals(comp.page.spinBox_scale): 32 with blockSignals(comp.page.spinBox_scale):
12 assert comp.page.spinBox_scale.signalsBlocked() == True 33 assert comp.page.spinBox_scale.signalsBlocked() == True
13 assert comp.page.spinBox_scale.signalsBlocked() == False 34 assert comp.page.spinBox_scale.signalsBlocked() == False
35
36
37def test_rgbFromString(gotWarning):
38 assert rgbFromString("255,255,255") == (255, 255, 255)
39 assert not gotWarning()
40
41
42def test_rgbFromString_error(gotWarning):
43 assert rgbFromString("255,255,256") == (255, 255, 255)
44 assert gotWarning() \ No newline at end of file
diff --git a/uv.lock b/uv.lock
index a3d6bfb..edc9406 100644
--- a/uv.lock
+++ b/uv.lock
@@ -4,7 +4,7 @@ requires-python = ">=3.12"
4 4
5[[package]] 5[[package]]
6name = "audio-visualizer-python" 6name = "audio-visualizer-python"
7version = "2.2.0" 7version = "2.2.1"
8source = { editable = "." } 8source = { editable = "." }
9dependencies = [ 9dependencies = [
10 { name = "numpy" }, 10 { name = "numpy" },