From f66eb99465c61232a7f649e66bee59504bb0e52c Mon Sep 17 00:00:00 2001 From: Brianna Rainey Date: Wed, 28 Jan 2026 17:49:58 -0500 Subject: 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--- src/avp/components/color.py | 10 +- src/avp/components/color.ui | 36 +- src/avp/components/image.py | 51 +- src/avp/components/image.ui | 10 + src/avp/components/life.py | 103 ++-- src/avp/components/life.ui | 2 +- src/avp/components/original.py | 96 +--- src/avp/components/original.ui | 58 +- src/avp/components/text.py | 13 +- src/avp/components/text.ui | 1213 ++++++++++++++++++++-------------------- src/avp/components/video.ui | 325 ++++++----- src/avp/components/waveform.py | 92 ++- src/avp/components/waveform.ui | 340 ++++++----- 13 files changed, 1186 insertions(+), 1163 deletions(-) (limited to 'src/avp/components') 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 import logging from ..component import Component -from ..toolkit.frame import BlankFrame, FloodFrame, FramePainter, PaintColor +from ..toolkit.frame import BlankFrame, FloodFrame, FramePainter log = logging.getLogger("AVP.Components.Color") @@ -152,13 +152,13 @@ class Component(Component): elif self.spread == 2: spread = QtGui.QGradient.Spread.RepeatSpread brush.setSpread(spread) - brush.setColorAt(0.0, PaintColor(*self.color1)) + brush.setColorAt(0.0, QtGui.QColor(*self.color1)) if self.trans: - brush.setColorAt(1.0, PaintColor(0, 0, 0, 0)) + brush.setColorAt(1.0, QtGui.QColor(0, 0, 0, 0)) elif self.fillType == 1 and self.stretch: - brush.setColorAt(0.2, PaintColor(*self.color2)) + brush.setColorAt(0.2, QtGui.QColor(*self.color2)) else: - brush.setColorAt(1.0, PaintColor(*self.color2)) + brush.setColorAt(1.0, QtGui.QColor(*self.color2)) image.setBrush(brush) image.drawRect(self.x, self.y, self.sizeWidth, self.sizeHeight) 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 @@ - 0,0,0 + 12 @@ -84,10 +84,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QSizePolicy::Fixed + QSizePolicy::Policy::Fixed @@ -176,7 +176,7 @@ Width - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter @@ -246,10 +246,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QSizePolicy::Fixed + QSizePolicy::Policy::Fixed @@ -370,7 +370,7 @@ -1 - QComboBox::AdjustToContentsOnFirstShow + QComboBox::SizeAdjustPolicy::AdjustToContentsOnFirstShow @@ -422,10 +422,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QSizePolicy::Minimum + QSizePolicy::Policy::Minimum @@ -461,7 +461,7 @@ -1 0 561 - 31 + 34 @@ -503,7 +503,7 @@ End - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter @@ -523,7 +523,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -543,7 +543,7 @@ -1 -1 561 - 31 + 34 @@ -559,7 +559,7 @@ Start - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter @@ -588,7 +588,7 @@ End - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter @@ -617,14 +617,14 @@ Centre - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter - QAbstractSpinBox::PlusMinus + QAbstractSpinBox::ButtonSymbols::PlusMinus -10000 @@ -640,7 +640,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal 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 from copy import copy from ..component import Component -from ..toolkit.frame import BlankFrame -from .original import Component as Visualizer +from ..toolkit.frame import BlankFrame, addShadow +from ..toolkit.visualizer import createSpectrumArray class Component(Component): name = "Image" - version = "2.0.0" + version = "2.1.0" def widget(self, *args): super().widget(*args) @@ -35,6 +35,7 @@ class Component(Component): "mirror": self.page.checkBox_mirror, "respondToAudio": self.page.checkBox_respondToAudio, "sensitivity": self.page.spinBox_sensitivity, + "shadow": self.page.checkBox_shadow, }, presetNames={ "imagePath": "image", @@ -75,31 +76,16 @@ class Component(Component): # Trigger creation of new base image self.existingImage = None - smoothConstantDown = 0.08 + 0 - smoothConstantUp = 0.8 - 0 - self.lastSpectrum = None - self.spectrumArray = {} - - for i in range(0, len(self.completeAudioArray), self.sampleSize): - if self.canceled: - break - self.lastSpectrum = Visualizer.transformData( - i, - self.completeAudioArray, - self.sampleSize, - smoothConstantDown, - smoothConstantUp, - self.lastSpectrum, - self.sensitivity, - ) - self.spectrumArray[i] = copy(self.lastSpectrum) - - progress = int(100 * (i / len(self.completeAudioArray))) - if progress >= 100: - progress = 100 - pStr = "Analyzing audio: " + str(progress) + "%" - self.progressBarSetText.emit(pStr) - self.progressBarUpdate.emit(int(progress)) + self.spectrumArray = createSpectrumArray( + self, + self.completeAudioArray, + self.sampleSize, + 0.08, + 0.8, + self.sensitivity, + self.progressBarUpdate, + self.progressBarSetText, + ) def frameRender(self, frameNo): return self.drawFrame( @@ -139,9 +125,16 @@ class Component(Component): self.existingImage = image # Respond to audio + resolutionFactor = height / 1080 + shadX = int(resolutionFactor * 1) + shadY = int(resolutionFactor * -1) + shadBlur = resolutionFactor * 3.50 scale = 0 if dynamicScale is not None: scale = dynamicScale[36 * 4] / 4 + shadX += int((scale / 4) * resolutionFactor) + shadY += int((scale / 2) * resolutionFactor) + shadBlur += (scale / 8) * resolutionFactor image = ImageOps.contain( image, ( @@ -161,6 +154,8 @@ class Component(Component): ) if self.rotate != 0: frame = frame.rotate(self.rotate) + if self.shadow: + frame = addShadow(frame, shadBlur, shadX, shadY) return frame 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 @@ -306,6 +306,16 @@ + + + + Qt::LayoutDirection::RightToLeft + + + Shadow + + + 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 from ..component import Component -from ..toolkit.frame import BlankFrame, scale -from .original import Component as Visualizer +from ..toolkit.frame import BlankFrame, scale, addShadow +from ..toolkit.visualizer import createSpectrumArray log = logging.getLogger("AVP.Component.Life") @@ -17,7 +17,7 @@ log = logging.getLogger("AVP.Component.Life") class Component(Component): name = "Conway's Game of Life" - version = "2.0.0" + version = "2.0.1" def widget(self, *args): super().widget(*args) @@ -27,26 +27,26 @@ class Component(Component): # https://conwaylife.com/wiki/Queen_bee_shuttle self.startingGrid = set( [ - (3, 7), - (3, 8), - (4, 7), - (4, 8), - (8, 7), - (9, 6), - (9, 8), - (10, 5), + (3, 11), + (3, 12), + (4, 11), + (4, 12), + (8, 11), + (9, 10), + (9, 12), (10, 9), - (11, 6), - (11, 7), - (11, 8), - (12, 4), - (12, 5), + (10, 13), + (11, 10), + (11, 11), + (11, 12), + (12, 8), (12, 9), - (12, 10), - (23, 6), - (23, 7), - (24, 6), - (24, 7), + (12, 13), + (12, 14), + (23, 10), + (23, 11), + (24, 10), + (24, 11), ] ) @@ -163,41 +163,27 @@ class Component(Component): def previewRender(self): image = self.drawGrid(self.startingGrid, self.color) image = self.addKaleidoscopeEffect(image) - image = self.addShadow(image) + if self.shadow: + image = addShadow(image, 5.00, -2, 2) image = self.addGridLines(image) return image def preFrameRender(self, *args, **kwargs): super().preFrameRender(*args, **kwargs) self.tickGrids = {0: self.startingGrid} - - smoothConstantDown = 0.08 + 0 - smoothConstantUp = 0.8 - 0 - self.lastSpectrum = None - self.spectrumArray = {} if self.sensitivity == 0: return - for i in range(0, len(self.completeAudioArray), self.sampleSize): - if self.canceled: - break - self.lastSpectrum = Visualizer.transformData( - i, - self.completeAudioArray, - self.sampleSize, - smoothConstantDown, - smoothConstantUp, - self.lastSpectrum, - self.sensitivity, - ) - self.spectrumArray[i] = copy(self.lastSpectrum) - - progress = int(100 * (i / len(self.completeAudioArray))) - if progress >= 100: - progress = 100 - pStr = "Analyzing audio: " + str(progress) + "%" - self.progressBarSetText.emit(pStr) - self.progressBarUpdate.emit(int(progress)) + self.spectrumArray = createSpectrumArray( + self, + self.completeAudioArray, + self.sampleSize, + 0.08, + 0.8, + 20, + self.progressBarUpdate, + self.progressBarSetText, + ) def properties(self): if self.customImg and (not self.image or not os.path.exists(self.image)): @@ -256,21 +242,11 @@ class Component(Component): if not self.customImg: image = Image.alpha_composite(previousGridImage, image) image = self.addKaleidoscopeEffect(image) - image = self.addShadow(image) + if self.shadow: + image = addShadow(image, 5.00, -2, 2) image = self.addGridLines(image) return image - def addShadow(self, frame): - if not self.shadow: - return frame - - shadImg = ImageEnhance.Contrast(frame).enhance(0.0) - shadImg = shadImg.filter(ImageFilter.GaussianBlur(5.00)) - shadImg = ImageChops.offset(shadImg, -2, 2) - shadImg.paste(frame, box=(0, 0), mask=frame) - frame = shadImg - return frame - def addGridLines(self, frame): if not self.showGrid: return frame @@ -299,11 +275,6 @@ class Component(Component): flippedImage = frame.transpose(Image.Transpose.FLIP_LEFT_RIGHT) frame.paste(flippedImage, (0, 0), mask=flippedImage) - flippedImage = frame.transpose(Image.Transpose.ROTATE_90) - frame.paste(flippedImage, (0, 0), mask=flippedImage) - - flippedImage = frame.transpose(Image.Transpose.ROTATE_270) - frame.paste(flippedImage, (0, 0), mask=flippedImage) return frame def drawGrid(self, grid, color, spectrumData=None, didntChange=None): @@ -506,10 +477,10 @@ class Component(Component): for x, y in grid: drawPtX = x * self.pxWidth - if drawPtX > self.width: + if drawPtX > self.width or drawPtX + self.pxWidth < 0: continue drawPtY = y * self.pxHeight - if drawPtY > self.height: + if drawPtY > self.height or drawPtY + self.pxHeight < 0: continue 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 @@ - 255,255,255 + 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 from ..component import Component from ..toolkit.frame import BlankFrame +from ..toolkit.visualizer import createSpectrumArray class Component(Component): name = "Classic Visualizer" - version = "1.0.0" + version = "1.1.0" def names(*args): return ["Original Audio Visualization"] @@ -18,6 +19,7 @@ class Component(Component): def widget(self, *args): self.scale = 20 + self.bars = 63 self.y = 0 super().widget(*args) @@ -27,15 +29,14 @@ class Component(Component): self.page.comboBox_visLayout.addItem("Top") self.page.comboBox_visLayout.setCurrentIndex(0) - self.page.lineEdit_visColor.setText("255,255,255") - self.trackWidgets( { "visColor": self.page.lineEdit_visColor, "layout": self.page.comboBox_visLayout, "scale": self.page.spinBox_scale, "y": self.page.spinBox_y, - "smooth": self.page.spinBox_smooth, + "smooth": self.page.spinBox_sensitivity, + "bars": self.page.spinBox_bars, }, colorWidgets={ "visColor": self.page.pushButton_visColor, @@ -59,29 +60,16 @@ class Component(Component): super().preFrameRender(**kwargs) smoothConstantDown = 0.08 if not self.smooth else self.smooth / 15 smoothConstantUp = 0.8 if not self.smooth else self.smooth / 15 - self.lastSpectrum = None - self.spectrumArray = {} - - for i in range(0, len(self.completeAudioArray), self.sampleSize): - if self.canceled: - break - self.lastSpectrum = self.transformData( - i, - self.completeAudioArray, - self.sampleSize, - smoothConstantDown, - smoothConstantUp, - self.lastSpectrum, - self.scale, - ) - self.spectrumArray[i] = copy(self.lastSpectrum) - - progress = int(100 * (i / len(self.completeAudioArray))) - if progress >= 100: - progress = 100 - pStr = "Analyzing audio: " + str(progress) + "%" - self.progressBarSetText.emit(pStr) - self.progressBarUpdate.emit(int(progress)) + self.spectrumArray = createSpectrumArray( + self, + self.completeAudioArray, + self.sampleSize, + smoothConstantDown, + smoothConstantUp, + self.scale, + self.progressBarUpdate, + self.progressBarSetText, + ) def frameRender(self, frameNo): arrayNo = frameNo * self.sampleSize @@ -93,60 +81,10 @@ class Component(Component): self.layout, ) - @staticmethod - def transformData( - i, - completeAudioArray, - sampleSize, - smoothConstantDown, - smoothConstantUp, - lastSpectrum, - scale, - ): - if len(completeAudioArray) < (i + sampleSize): - sampleSize = len(completeAudioArray) - i - - window = numpy.hanning(sampleSize) - data = completeAudioArray[i : i + sampleSize][::1] * window - paddedSampleSize = 2048 - paddedData = numpy.pad(data, (0, paddedSampleSize - sampleSize), "constant") - spectrum = numpy.fft.fft(paddedData) - sample_rate = 44100 - frequencies = numpy.fft.fftfreq(len(spectrum), 1.0 / sample_rate) - - y = abs(spectrum[0 : int(paddedSampleSize / 2) - 1]) - - # filter the noise away - # y[y<80] = 0 - - with numpy.errstate(divide="ignore"): - y = scale * numpy.log10(y) - - y[numpy.isinf(y)] = 0 - - if lastSpectrum is not None: - lastSpectrum[y < lastSpectrum] = y[ - y < lastSpectrum - ] * smoothConstantDown + lastSpectrum[y < lastSpectrum] * ( - 1 - smoothConstantDown - ) - - lastSpectrum[y >= lastSpectrum] = y[ - y >= lastSpectrum - ] * smoothConstantUp + lastSpectrum[y >= lastSpectrum] * ( - 1 - smoothConstantUp - ) - else: - lastSpectrum = y - - x = frequencies[0 : int(paddedSampleSize / 2) - 1] - - return lastSpectrum - def drawBars(self, width, height, spectrum, color, layout): bigYCoord = height - height / 8 smallYCoord = height / 1200 - bigXCoord = width / 64 + bigXCoord = width / (self.bars + 1) middleXCoord = bigXCoord / 2 smallXCoord = bigXCoord / 4 @@ -155,7 +93,7 @@ class Component(Component): r, g, b = color color2 = (r, g, b, 125) - for i in range(0, 63): + for i in range(self.bars): x0 = middleXCoord + i * bigXCoord y0 = bigYCoord + smallXCoord 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 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QSizePolicy::Fixed + QSizePolicy::Policy::Fixed @@ -84,15 +84,19 @@ - + + + + + - Qt::Horizontal + Qt::Orientation::Horizontal - QSizePolicy::Fixed + QSizePolicy::Policy::Fixed @@ -112,7 +116,7 @@ - QAbstractSpinBox::UpDownArrows + QAbstractSpinBox::ButtonSymbols::UpDownArrows -5000 @@ -131,7 +135,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -158,7 +162,7 @@ - QAbstractSpinBox::PlusMinus + QAbstractSpinBox::ButtonSymbols::PlusMinus 1 @@ -168,13 +172,27 @@ + + + + Sensitivity + + + + + + + 5 + + + - Qt::Horizontal + Qt::Orientation::Horizontal - QSizePolicy::Expanding + QSizePolicy::Policy::Expanding @@ -189,29 +207,35 @@ - QLayout::SetDefaultConstraint + QLayout::SizeConstraint::SetDefaultConstraint 4 - + - Sensitivity + Bars - + + + 63 + - 5 + 64 + + + 63 - Qt::Horizontal + Qt::Orientation::Horizontal @@ -226,7 +250,7 @@ - Qt::Vertical + Qt::Orientation::Vertical 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 import logging from ..component import Component -from ..toolkit.frame import FramePainter, PaintColor +from ..toolkit.frame import FramePainter, addShadow log = logging.getLogger("AVP.Components.Text") @@ -26,7 +26,6 @@ class Component(Component): self.page.comboBox_textAlign.addItem("Right") self.page.comboBox_textAlign.setCurrentIndex(int(self.alignment)) self.page.spinBox_fontSize.setValue(int(self.fontSize)) - self.page.lineEdit_title.setText(self.title) self.page.pushButton_center.clicked.connect(self.centerXY) self.page.fontComboBox_titleFont.currentFontChanged.connect( @@ -35,7 +34,7 @@ class Component(Component): # The QFontComboBox must be connected directly to the Qt Signal # which triggers the preview to update. # This unfortunately makes changing the font into a non-undoable action. - # Must be something broken in the conversion to a ComponentAction + # Fix requires updating ComponentAction to handle fonts self.trackWidgets( { @@ -173,7 +172,7 @@ class Component(Component): path.addText(x, y, font, self.title) path = outliner.createStroke(path) image.setPen(QtCore.Qt.PenStyle.NoPen) - image.setBrush(PaintColor(*self.strokeColor)) + image.setBrush(QtGui.QColor(*self.strokeColor)) image.drawPath(path) image.setFont(font) @@ -183,11 +182,7 @@ class Component(Component): # turn QImage into Pillow frame frame = image.finalize() if self.shadow: - shadImg = ImageEnhance.Contrast(frame).enhance(0.0) - shadImg = shadImg.filter(ImageFilter.GaussianBlur(self.shadBlur)) - shadImg = ImageChops.offset(shadImg, self.shadX, self.shadY) - shadImg.paste(frame, box=(0, 0), mask=frame) - frame = shadImg + frame = addShadow(frame, self.shadBlur / 10, self.shadX, self.shadY) return frame 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 @@ - - - 6 - - - QLayout::SetDefaultConstraint - + + + + + Title + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Text + + + + + + + + 0 + 0 + + + + Font + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + + + + - 4 + 0 - - - - - Title - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - Testing New GUI - - - - - - - - 0 - 0 - - - - Font - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - + + + + 0 + 0 + + + + Text Layout + + + + + + + + 0 + 0 + + + + + 100 + 16777215 + + + + + + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::Fixed + + + + 5 + 20 + + + + + + + + + 0 + 0 + + + + Center Text + + - - + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::Fixed + + + + 5 + 20 + + + + + + + + + 0 + 0 + + + + X + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + + 0 + 0 + + + 0 - - - - - 0 - 0 - - - - Text Layout - - - - - - - - 0 - 0 - - - - - 100 - 16777215 - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 5 - 20 - - - - - - - - - 0 - 0 - - - - Center Text - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 5 - 20 - - - - - - - - - 0 - 0 - - - - X - - - - - - - - 0 - 0 - - - - - 50 - 16777215 - - - - - 0 - 0 - - - - 0 - - - 999999999 - - - 0 - - - - - - - - 0 - 0 - - - - Y - - - - - - - - 0 - 0 - - - - - 50 - 16777215 - - - - 999999999 - - - - + + 999999999 + + + 0 + + - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Text Color - - - - - - - - 0 - 0 - - - - - 32 - 32 - - - - - - - - 32 - 32 - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 5 - 20 - - - - - - - - - 0 - 0 - - - - Font Size - - - - - - - - 0 - 0 - - - - - - - - - - 1 - - - 500 - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 5 - 20 - - - - - - - - - 0 - 0 - - - - Font Style - - - - - - - - Normal - - - - - Semi-Bold - - - - - Bold - - - - - Italic - - - - - Bold Italic - - - - - Faux Italic - - - - - Small Caps - - - - - + + + + 0 + 0 + + + + Y + + - - - - - - 0 - 0 - - - - - 0 - 16777215 - - - - Qt::NoFocus - - - 255,255,255 - - - - - - - - 0 - 0 - - - - Stroke - - - - - - - - 0 - 0 - - - - px - - - - - - - - 0 - 0 - - - - Stroke Color - - - - - - - - 0 - 0 - - - - - 0 - 16777215 - - - - Qt::NoFocus - - - 0,0,0 - - - - - - - - 0 - 0 - - - - - 32 - 32 - - - - - - - - 32 - 32 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + + + + 0 + 0 + + + + + 50 + 16777215 + + + + 999999999 + + + + + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Text Color + + + + + + + + 0 + 0 + + + + + 32 + 32 + + + + + + + + 32 + 32 + + + + + + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::Fixed + + + + 5 + 20 + + + + + + + + + 0 + 0 + + + + Font Size + + + + + + + + 0 + 0 + + + + + + + + + + 1 + + + 500 + + + + + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::Fixed + + + + 5 + 20 + + + + + + + + + 0 + 0 + + + + Font Style + + - + - - - - 0 - 0 - - - - Shadow - - + + Normal + - - - - 0 - 0 - - - - Shadow Offset - - + + Semi-Bold + - - - - 0 - 0 - - - - -1000 - - - 1000 - - - -4 - - + + Bold + - - - - 0 - 0 - - - - -1000 - - - 1000 - - - 8 - - + + Italic + - - - - 0 - 0 - - - - Shadow Blur - - + + Bold Italic + - - - - 0 - 0 - - - - 99.000000000000000 - - - 0.100000000000000 - - - 5.000000000000000 - - + + Faux Italic + - - - Qt::Horizontal - - - QSizePolicy::Minimum - - - - 40 - 20 - - - + + Small Caps + - + + + + + + + + + + + 0 + 0 + + + + + 0 + 16777215 + + + + Qt::FocusPolicy::NoFocus + + + + + + + + + + + 0 + 0 + + + + Stroke + + + + + + + + 0 + 0 + + + + px + + + + + + + + 0 + 0 + + + + Stroke Color + + + + + + + + 0 + 0 + + + + + 0 + 16777215 + + + + Qt::FocusPolicy::NoFocus + + + 0,0,0 + + + + + + + + 0 + 0 + + + + + 32 + 32 + + + + + + + + 32 + 32 + + + + + + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::MinimumExpanding + + + + 40 + 20 + + + + + + + + + + + + + 0 + 0 + + + + Shadow + + + + + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::Preferred + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Shadow Offset + + + + + + + + 0 + 0 + + + + -1000 + + + 1000 + + + 2 + + + + + + + + 0 + 0 + + + + -1000 + + + 1000 + + + -2 + + + + + + + + 0 + 0 + + + + Shadow Blur + + + + + + + + 0 + 0 + + + + QAbstractSpinBox::ButtonSymbols::PlusMinus + + + QAbstractSpinBox::CorrectionMode::CorrectToPreviousValue + + + 1000 + + + QAbstractSpinBox::StepType::DefaultStepType + + + 35 + + + 10 + + - Qt::Vertical + Qt::Orientation::Vertical 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 @@ - - - 4 - + + + + + + 0 + 0 + + + + + 31 + 0 + + + + Video + + + + + + + + 1 + 0 + + + + + + + + + 0 + 0 + + + + + 1 + 0 + + + + + 32 + 32 + + + + ... + + + + 32 + 32 + + + + + + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::Fixed + + + + 5 + 20 + + + + + + + + + 0 + 0 + + + + X + + + - - - - - - 0 - 0 - - - - - 31 - 0 - - - - Video - - - - - - - - 1 - 0 - - - - - - - - - 0 - 0 - - - - - 1 - 0 - - - - - 32 - 32 - - - - ... - - - - 32 - 32 - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 5 - 20 - - - - - - - - - 0 - 0 - - - - X - - - - - - - - 0 - 0 - - - - - 80 - 16777215 - - - - -10000 - - - 10000 - - - - - - - - 0 - 0 - - - - Y - - - - - - - - 0 - 0 - - - - - 80 - 16777215 - - - - - 0 - 0 - - - - -10000 - - - 10000 - - - 0 - - - - + + + + 0 + 0 + + + + + 80 + 16777215 + + + + -10000 + + + 10000 + + + + + + + + 0 + 0 + + + + Y + + + + + + + + 0 + 0 + + + + + 80 + 16777215 + + + + + 0 + 0 + + + + -10000 + + + 10000 + + + 0 + + @@ -204,7 +197,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -227,14 +220,14 @@ Scale - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter - QAbstractSpinBox::UpDownArrows + QAbstractSpinBox::ButtonSymbols::UpDownArrows % @@ -296,7 +289,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -311,7 +304,7 @@ - Qt::Vertical + Qt::Orientation::Vertical 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 @@ -from PIL import Image -from PyQt6 import QtGui, QtCore, QtWidgets +from PIL import Image, ImageChops from PyQt6.QtGui import QColor import os -import math import subprocess import logging +from copy import copy from ..component import Component +from ..toolkit.visualizer import transformData, createSpectrumArray from ..toolkit.frame import BlankFrame, scale from ..toolkit import checkOutput from ..toolkit.ffmpeg import ( @@ -23,14 +23,20 @@ log = logging.getLogger("AVP.Components.Waveform") class Component(Component): name = "Waveform" - version = "1.0.0" + version = "2.0.0" + + @property + def updateInterval(self): + """How many frames from FFmpeg are ignored between each final frame""" + return 100 - self.speed + + def properties(self): + return [] if self.speed == 100 else ["pcm"] def widget(self, *args): super().widget(*args) self._image = BlankFrame(self.width, self.height) - self.page.lineEdit_color.setText("255,255,255") - if hasattr(self.parent, "lineEdit_audioFile"): self.parent.lineEdit_audioFile.textChanged.connect(self.update) @@ -46,6 +52,7 @@ class Component(Component): "opacity": self.page.spinBox_opacity, "compress": self.page.checkBox_compress, "mono": self.page.checkBox_mono, + "speed": self.page.spinBox_speed, }, colorWidgets={ "color": self.page.pushButton_color, @@ -65,6 +72,10 @@ class Component(Component): return frame def preFrameRender(self, **kwargs): + self._fadingImage = None + self._prevImage = None + self._currImage = None + self._lastUpdatedFrame = 0 super().preFrameRender(**kwargs) self.updateChunksize() w, h = scale(self.scale, self.width, self.height, str) @@ -79,11 +90,64 @@ class Component(Component): component=self, debug=True, ) + if self.speed == 100: + return + self.spectrumArray = createSpectrumArray( + self, + self.completeAudioArray, + self.sampleSize, + 0.08, + 0.8, + 20, + self.progressBarUpdate, + self.progressBarSetText, + ) def frameRender(self, frameNo): if FfmpegVideo.threadError is not None: raise FfmpegVideo.threadError - return self.finalizeFrame(self.video.frame(frameNo)) + newFrame = self.finalizeFrame(self.video.frame(frameNo)) + if self.speed == 100: + return newFrame + frameDiff = 0 if frameNo == 0 else frameNo % self.updateInterval + peaks = [ + self.spectrumArray[frameNo * self.sampleSize][i * 4] for i in range(64) + ] + peakValue = 70 - (max(*peaks) - min(*peaks)) + isValidPeak = ( + peakValue > 27 + and frameNo - self._lastUpdatedFrame > self.updateInterval / 2 + ) + if frameDiff == 0 or isValidPeak: + self._lastUpdatedFrame = frameNo + self._fadingImage = self._prevImage + self._prevImage = self._image + self._currImage = newFrame + usualAlpha = 0.0 + (1 / self.updateInterval) * frameDiff + alpha = max( + 0.1 + + ( + 1 + / max( + 10, + peakValue, + ) + ), + usualAlpha, + ) + baseImage = self._prevImage + if self._fadingImage is not None: + # fade away the old previous frame from ages ago + baseImage = ImageChops.blend( + self._prevImage, self._fadingImage, max(0.0, 0.9 - usualAlpha) + ) + blendedImage = ImageChops.blend( + baseImage, + ImageChops.lighter(self._prevImage, newFrame), + alpha, + ) + baseImage.paste(blendedImage, (0, 0), mask=blendedImage) + return Image.alpha_composite(self._currImage, baseImage) def postFrameRender(self): closePipe(self.video.pipe) @@ -162,17 +226,17 @@ class Component(Component): def makeFfmpegFilter(self, preview=False, startPt=0): w, h = scale(self.scale, self.width, self.height, str) if self.amplitude == 0: - amplitude = "lin" - elif self.amplitude == 1: amplitude = "log" - elif self.amplitude == 2: + elif self.amplitude == 1: amplitude = "sqrt" - elif self.amplitude == 3: + elif self.amplitude == 2: amplitude = "cbrt" + elif self.amplitude == 3: + amplitude = "lin" hexcolor = QColor(*self.color).name() opacity = "{0:.1f}".format(self.opacity / 100) genericPreview = self.settings.value("pref_genericPreview") - if self.mode < 3: + if self.mode > 1: filter_ = ( "showwaves=" f'r={str(self.settings.value("outputFrameRate"))}:' @@ -180,10 +244,10 @@ class Component(Component): f'mode={self.page.comboBox_mode.currentText().lower() if self.mode != 3 else "p2p"}:' f"colors={hexcolor}@{opacity}:scale={amplitude}" ) - elif self.mode > 2: + elif self.mode < 2: filter_ = ( f'showfreqs=s={str(self.settings.value("outputWidth"))}x{str(self.settings.value("outputHeight"))}:' - f'mode={"line" if self.mode == 4 else "bar"}:' + f'mode={"line" if self.mode == 0 else "bar"}:' f"colors={hexcolor}@{opacity}" f":ascale={amplitude}:fscale={'log' if self.mono else 'lin'}" ) 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 @@ - - - 4 - + - - - - - - 0 - 0 - - - - - 31 - 0 - - - - Mode - - - - - - - - Cline - - - - - Line - - - - - Point - - - - - Frequency Bar - - - - - Frequency Line - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 5 - 20 - - - - + + + + 0 + 0 + + + + + 31 + 0 + + + + Mode + + + + + - - - - 0 - 0 - - - - X - - + + Frequency Line + - - - - 0 - 0 - - - - - 80 - 16777215 - - - - -10000 - - - 10000 - - + + Frequency Bar + - - - - 0 - 0 - - - - Y - - + + Cline + - - - - 0 - 0 - - - - - 80 - 16777215 - - - - - 0 - 0 - - - - -10000 - - - 10000 - - - 0 - - + + Line + - + + + + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::Fixed + + + + 5 + 20 + + + + + + + + + 0 + 0 + + + + X + + + + + + + + 0 + 0 + + + + + 80 + 16777215 + + + + -10000 + + + 10000 + + + + + + + + 0 + 0 + + + + Y + + + + + + + + 0 + 0 + + + + + 80 + 16777215 + + + + + 0 + 0 + + + + -10000 + + + 10000 + + + 0 + + @@ -192,7 +180,7 @@ - Qt::ImhNone + Qt::InputMethodHint::ImhNone @@ -224,7 +212,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -240,14 +228,14 @@ Opacity - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter - QAbstractSpinBox::UpDownArrows + QAbstractSpinBox::ButtonSymbols::UpDownArrows % @@ -269,14 +257,14 @@ Scale - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter - QAbstractSpinBox::UpDownArrows + QAbstractSpinBox::ButtonSymbols::UpDownArrows % @@ -301,6 +289,9 @@ Compress + + true + @@ -320,7 +311,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -341,32 +332,75 @@ - Linear + Logarithmic - Logarithmic + Square root - Square root + Cubic root - Cubic root + Linear + + + + + + Animation Speed + + + + + + + % + + + 10 + + + 100 + + + 10 + + + 50 + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + - Qt::Vertical + Qt::Orientation::Vertical -- cgit v1.2.3