aboutsummaryrefslogtreecommitdiff
path: root/src/avp/toolkit
diff options
context:
space:
mode:
Diffstat (limited to 'src/avp/toolkit')
-rw-r--r--src/avp/toolkit/common.py18
-rw-r--r--src/avp/toolkit/frame.py26
-rw-r--r--src/avp/toolkit/visualizer.py87
3 files changed, 111 insertions, 20 deletions
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
from PyQt6 import QtWidgets
import string
-import os
+import random
import sys
import subprocess
import logging
@@ -135,7 +135,12 @@ def rgbFromString(string):
if i > 255 or i < 0:
raise ValueError
return tup
- except:
+ except Exception as e:
+ log.warning(
+ "Could not parse '%s' as a color (encountered %s).",
+ string,
+ type(e).__name__,
+ )
return (255, 255, 255)
@@ -150,6 +155,7 @@ def formatTraceback(tb=None):
def connectWidget(widget, func):
+ unsupportedWidgets = ["QtWidgets.QFontComboBox"]
if type(widget) == QtWidgets.QLineEdit:
widget.textChanged.connect(func)
elif type(widget) == QtWidgets.QSpinBox or type(widget) == QtWidgets.QDoubleSpinBox:
@@ -158,6 +164,10 @@ def connectWidget(widget, func):
widget.stateChanged.connect(func)
elif type(widget) == QtWidgets.QComboBox:
widget.currentIndexChanged.connect(func)
+ elif type(widget) in unsupportedWidgets:
+ log.info(
+ "Could not connect %s using connectWidget()", str(widget.__class__.__name__)
+ )
else:
log.warning("Failed to connect %s ", str(widget.__class__.__name__))
return False
@@ -190,3 +200,7 @@ def getWidgetValue(widget):
return widget.isChecked()
elif type(widget) == QtWidgets.QComboBox:
return widget.currentIndex()
+
+
+def randomColor():
+ 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()
"""
from PyQt6 import QtGui
-from PIL import Image
+from PIL import Image, ImageEnhance, ImageChops, ImageFilter
from PIL.ImageQt import ImageQt
from PyQt6 import QtCore
import sys
@@ -30,7 +30,7 @@ class FramePainter(QtGui.QPainter):
def setPen(self, penStyle):
if type(penStyle) is tuple:
- super().setPen(PaintColor(*penStyle))
+ super().setPen(QtGui.QColor(*penStyle))
else:
super().setPen(penStyle)
@@ -45,24 +45,14 @@ class FramePainter(QtGui.QPainter):
buffer.close()
self.end()
return frame
- imBytes = self.image.bits().asstring(self.image.byteCount())
- frame = Image.frombytes(
- "RGBA", (self.image.width(), self.image.height()), imBytes
- )
- self.end()
- return frame
-
-class PaintColor(QtGui.QColor):
- """
- Subclass of QtGui.QColor with an added scale() method
- Previously this class reversed the painter colour to solve
- hardware issues related to endianness,
- but Qt appears to deal with this itself nowadays
- """
- def __init__(self, r, g, b, a=255):
- super().__init__(r, g, b, a)
+def addShadow(frame, blurRadius, blurOffsetX, blurOffsetY):
+ shadImg = ImageEnhance.Contrast(frame).enhance(0.0)
+ shadImg = shadImg.filter(ImageFilter.GaussianBlur(blurRadius))
+ frame = shadImg.paste(frame, box=(-blurOffsetX, -blurOffsetY), mask=frame)
+ frame = shadImg
+ return frame
def 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 @@
+"""Functions used to transform and manipulate audio for use by visualizers"""
+
+from copy import copy
+import numpy
+
+
+def createSpectrumArray(
+ component,
+ completeAudioArray,
+ sampleSize,
+ smoothConstantDown,
+ smoothConstantUp,
+ scale,
+ progressBarUpdate,
+ progressBarSetText,
+):
+ lastSpectrum = None
+ spectrumArray = {}
+ for i in range(0, len(completeAudioArray), sampleSize):
+ if component.canceled:
+ break
+ lastSpectrum = transformData(
+ i,
+ completeAudioArray,
+ sampleSize,
+ smoothConstantDown,
+ smoothConstantUp,
+ lastSpectrum,
+ scale,
+ )
+ spectrumArray[i] = copy(lastSpectrum)
+
+ progress = int(100 * (i / len(completeAudioArray)))
+ if progress >= 100:
+ progress = 100
+ progressText = f"Analyzing audio: {str(progress)}%"
+ progressBarSetText.emit(progressText)
+ progressBarUpdate.emit(int(progress))
+ return spectrumArray
+
+
+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