diff options
| author | Brianna Rainey | 2026-01-28 17:49:58 -0500 |
|---|---|---|
| committer | GitHub | 2026-01-28 17:49:58 -0500 |
| commit | f66eb99465c61232a7f649e66bee59504bb0e52c (patch) | |
| tree | 40d4f2e4e7cea033e4a68da025c7d91295e71cfb /src/avp/components/waveform.py | |
| parent | 864898419e810055b51e3a32fccb00a62aab9a6b (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
Diffstat (limited to 'src/avp/components/waveform.py')
| -rw-r--r-- | src/avp/components/waveform.py | 92 |
1 files changed, 78 insertions, 14 deletions
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'}" ) |
