From f975144f25d34f97329b2d4e52891061573cea08 Mon Sep 17 00:00:00 2001
From: Aeliton G. Silva
Date: Mon, 12 Jan 2026 22:39:55 -0300
Subject: Use pyproject.toml + uv_build
This replaces setup.py by a modern pyproject.toml using uv_build
backend.
Dependencies are being also managed by uv, so to install dependencies
and run the project one can execute:
```
uv sync
uv run pytest # optional
python -m avp
```
To build the both source and binary (wheel) distribution package run:
```
uv build
```
Uv can be installed with `pip install uv`.
The directory structure has been changed to reflect best practices.
- src/* -> src/avp/
- src/tests -> ../tests
---
src/components/__init__.py | 1 -
src/components/__template__.ui | 119 ------
src/components/color.py | 176 --------
src/components/color.ui | 666 -----------------------------
src/components/image.py | 129 ------
src/components/image.ui | 388 -----------------
src/components/life.py | 520 ----------------------
src/components/life.ui | 405 ------------------
src/components/original.py | 243 -----------
src/components/original.ui | 243 -----------
src/components/sound.py | 77 ----
src/components/sound.ui | 172 --------
src/components/spectrum.py | 368 ----------------
src/components/spectrum.ui | 946 -----------------------------------------
src/components/text.py | 218 ----------
src/components/text.ui | 671 -----------------------------
src/components/video.py | 254 -----------
src/components/video.ui | 328 --------------
src/components/waveform.py | 230 ----------
src/components/waveform.ui | 383 -----------------
20 files changed, 6537 deletions(-)
delete mode 100644 src/components/__init__.py
delete mode 100644 src/components/__template__.ui
delete mode 100644 src/components/color.py
delete mode 100644 src/components/color.ui
delete mode 100644 src/components/image.py
delete mode 100644 src/components/image.ui
delete mode 100644 src/components/life.py
delete mode 100644 src/components/life.ui
delete mode 100644 src/components/original.py
delete mode 100644 src/components/original.ui
delete mode 100644 src/components/sound.py
delete mode 100644 src/components/sound.ui
delete mode 100644 src/components/spectrum.py
delete mode 100644 src/components/spectrum.ui
delete mode 100644 src/components/text.py
delete mode 100644 src/components/text.ui
delete mode 100644 src/components/video.py
delete mode 100644 src/components/video.ui
delete mode 100644 src/components/waveform.py
delete mode 100644 src/components/waveform.ui
(limited to 'src/components')
diff --git a/src/components/__init__.py b/src/components/__init__.py
deleted file mode 100644
index 8b13789..0000000
--- a/src/components/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/components/__template__.ui b/src/components/__template__.ui
deleted file mode 100644
index 301a2b7..0000000
--- a/src/components/__template__.ui
+++ /dev/null
@@ -1,119 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 586
- 197
-
-
-
- Form
-
-
- -
-
-
-
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
- -
-
-
-
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
- -
-
-
-
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
- -
-
-
-
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
- -
-
-
-
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
-
-
diff --git a/src/components/color.py b/src/components/color.py
deleted file mode 100644
index 1f32c23..0000000
--- a/src/components/color.py
+++ /dev/null
@@ -1,176 +0,0 @@
-from PyQt6 import QtGui
-import logging
-
-from ..component import Component
-from ..toolkit.frame import BlankFrame, FloodFrame, FramePainter, PaintColor
-
-
-log = logging.getLogger("AVP.Components.Color")
-
-
-class Component(Component):
- name = "Color"
- version = "1.0.0"
-
- def widget(self, *args):
- self.x = 0
- self.y = 0
- super().widget(*args)
-
- # disable color #2 until non-default 'fill' option gets changed
- self.page.lineEdit_color2.setDisabled(True)
- self.page.pushButton_color2.setDisabled(True)
- self.page.spinBox_width.setValue(int(self.settings.value("outputWidth")))
- self.page.spinBox_height.setValue(int(self.settings.value("outputHeight")))
-
- self.fillLabels = [
- "Solid",
- "Linear Gradient",
- "Radial Gradient",
- ]
- for label in self.fillLabels:
- self.page.comboBox_fill.addItem(label)
- self.page.comboBox_fill.setCurrentIndex(0)
-
- self.trackWidgets(
- {
- "x": self.page.spinBox_x,
- "y": self.page.spinBox_y,
- "sizeWidth": self.page.spinBox_width,
- "sizeHeight": self.page.spinBox_height,
- "trans": self.page.checkBox_trans,
- "spread": self.page.comboBox_spread,
- "stretch": self.page.checkBox_stretch,
- "RG_start": self.page.spinBox_radialGradient_start,
- "LG_start": self.page.spinBox_linearGradient_start,
- "RG_end": self.page.spinBox_radialGradient_end,
- "LG_end": self.page.spinBox_linearGradient_end,
- "RG_centre": self.page.spinBox_radialGradient_spread,
- "fillType": self.page.comboBox_fill,
- "color1": self.page.lineEdit_color1,
- "color2": self.page.lineEdit_color2,
- },
- presetNames={
- "sizeWidth": "width",
- "sizeHeight": "height",
- },
- colorWidgets={
- "color1": self.page.pushButton_color1,
- "color2": self.page.pushButton_color2,
- },
- relativeWidgets=[
- "x",
- "y",
- "sizeWidth",
- "sizeHeight",
- "LG_start",
- "LG_end",
- "RG_start",
- "RG_end",
- "RG_centre",
- ],
- )
-
- def update(self):
- fillType = self.page.comboBox_fill.currentIndex()
- if fillType == 0:
- self.page.lineEdit_color2.setEnabled(False)
- self.page.pushButton_color2.setEnabled(False)
- self.page.checkBox_trans.setEnabled(False)
- self.page.checkBox_stretch.setEnabled(False)
- self.page.comboBox_spread.setEnabled(False)
- else:
- self.page.lineEdit_color2.setEnabled(True)
- self.page.pushButton_color2.setEnabled(True)
- self.page.checkBox_trans.setEnabled(True)
- self.page.checkBox_stretch.setEnabled(True)
- self.page.comboBox_spread.setEnabled(True)
- if self.page.checkBox_trans.isChecked():
- self.page.lineEdit_color2.setEnabled(False)
- self.page.pushButton_color2.setEnabled(False)
- self.page.fillWidget.setCurrentIndex(fillType)
-
- def previewRender(self):
- return self.drawFrame(self.width, self.height)
-
- def properties(self):
- return ["static"]
-
- def frameRender(self, frameNo):
- log.debug("Color component is drawing frame #%s", frameNo)
- return self.drawFrame(self.width, self.height)
-
- def drawFrame(self, width, height):
- r, g, b = self.color1
- shapeSize = (self.sizeWidth, self.sizeHeight)
- # in default state, skip all this logic and return a plain fill
- if (
- self.fillType == 0
- and shapeSize == (width, height)
- and self.x == 0
- and self.y == 0
- ):
- return FloodFrame(width, height, (r, g, b, 255))
-
- # Return a solid image at x, y
- if self.fillType == 0:
- frame = BlankFrame(width, height)
- image = FloodFrame(self.sizeWidth, self.sizeHeight, (r, g, b, 255))
- frame.paste(image, box=(self.x, self.y))
- return frame
-
- # Now fills that require using Qt...
- elif self.fillType > 0:
- image = FramePainter(width, height)
-
- if self.stretch:
- w = width
- h = height
- else:
- w = self.sizeWidth
- h = self.sizeWidth
-
- if self.fillType == 1: # Linear Gradient
- brush = QtGui.QLinearGradient(
- float(self.LG_start),
- float(self.LG_start),
- float(self.LG_end + width / 3),
- float(self.LG_end),
- )
-
- elif self.fillType == 2: # Radial Gradient
- brush = QtGui.QRadialGradient(
- float(self.RG_start),
- float(self.RG_end),
- float(w),
- float(h),
- float(self.RG_centre),
- )
- spread = QtGui.QGradient.Spread.PadSpread
- if self.spread == 1:
- spread = QtGui.QGradient.Spread.ReflectSpread
- elif self.spread == 2:
- spread = QtGui.QGradient.Spread.RepeatSpread
- brush.setSpread(spread)
- brush.setColorAt(0.0, PaintColor(*self.color1))
- if self.trans:
- brush.setColorAt(1.0, PaintColor(0, 0, 0, 0))
- elif self.fillType == 1 and self.stretch:
- brush.setColorAt(0.2, PaintColor(*self.color2))
- else:
- brush.setColorAt(1.0, PaintColor(*self.color2))
- image.setBrush(brush)
- image.drawRect(self.x, self.y, self.sizeWidth, self.sizeHeight)
-
- return image.finalize()
-
- def commandHelp(self):
- print("Specify a color:\n color=255,255,255")
-
- def command(self, arg):
- if "=" in arg:
- key, arg = arg.split("=", 1)
- if key == "color":
- self.page.lineEdit_color1.setText(arg)
- return
- super().command(arg)
diff --git a/src/components/color.ui b/src/components/color.ui
deleted file mode 100644
index c1713fb..0000000
--- a/src/components/color.ui
+++ /dev/null
@@ -1,666 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 586
- 197
-
-
-
- Form
-
-
- -
-
-
- 4
-
-
-
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 31
- 0
-
-
-
- Color #1
-
-
-
- -
-
-
-
- 32
- 32
-
-
-
-
-
-
-
- 32
- 32
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 1
- 0
-
-
-
- 0,0,0
-
-
- 12
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 31
- 0
-
-
-
- Color #2
-
-
-
- -
-
-
-
- 32
- 32
-
-
-
-
-
-
-
- 32
- 32
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 1
- 0
-
-
-
- 133,133,133
-
-
- 12
-
-
-
-
-
- -
-
-
- 0
-
-
-
-
-
-
- 0
- 0
-
-
-
- Width
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
-
- 0
- 0
-
-
-
- 0
-
-
- 19200
-
-
- 0
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Height
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
- 10800
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- X
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
-
- 0
- 0
-
-
-
- -10000
-
-
- 10000
-
-
- 0
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Y
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
- -10000
-
-
- 10000
-
-
-
-
-
- -
-
-
- 0
-
-
-
-
-
-
- 0
- 0
-
-
-
- Fill
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- -1
-
-
- QComboBox::AdjustToContentsOnFirstShow
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Transparent
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Stretch
-
-
-
- -
-
-
-
-
- Pad
-
-
- -
-
- Reflect
-
-
- -
-
- Repeat
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Minimum
-
-
-
- 40
- 20
-
-
-
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
- 0
-
-
- 2
-
-
-
-
-
-
- -1
- 0
- 561
- 31
-
-
-
-
-
-
-
-
- 0
- 0
-
-
-
- Start
-
-
-
- -
-
-
- -10000
-
-
- 10000
-
-
- 10
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- End
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- -10000
-
-
- 10000
-
-
- 10
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
-
-
-
-
-
- -1
- -1
- 561
- 31
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Start
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- -10000
-
-
- 10000
-
-
- 10
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- End
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- -10000
-
-
- 10000
-
-
- 10
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Centre
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- QAbstractSpinBox::PlusMinus
-
-
- -10000
-
-
- 10000
-
-
- 3
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/components/image.py b/src/components/image.py
deleted file mode 100644
index 2393611..0000000
--- a/src/components/image.py
+++ /dev/null
@@ -1,129 +0,0 @@
-from PIL import Image, ImageDraw, ImageEnhance
-from PyQt6 import QtGui, QtCore, QtWidgets
-import os
-
-from ..component import Component
-from ..toolkit.frame import BlankFrame
-
-
-class Component(Component):
- name = "Image"
- version = "1.0.1"
-
- def widget(self, *args):
- super().widget(*args)
- self.page.pushButton_image.clicked.connect(self.pickImage)
- self.trackWidgets(
- {
- "imagePath": self.page.lineEdit_image,
- "scale": self.page.spinBox_scale,
- "stretchScale": self.page.spinBox_scale_stretch,
- "rotate": self.page.spinBox_rotate,
- "color": self.page.spinBox_color,
- "xPosition": self.page.spinBox_x,
- "yPosition": self.page.spinBox_y,
- "stretched": self.page.checkBox_stretch,
- "mirror": self.page.checkBox_mirror,
- },
- presetNames={
- "imagePath": "image",
- "xPosition": "x",
- "yPosition": "y",
- },
- relativeWidgets=["xPosition", "yPosition", "scale"],
- )
-
- def previewRender(self):
- return self.drawFrame(self.width, self.height)
-
- def properties(self):
- props = ["static"]
- if not os.path.exists(self.imagePath):
- props.append("error")
- return props
-
- def error(self):
- if not self.imagePath:
- return "There is no image selected."
- if not os.path.exists(self.imagePath):
- return "The image selected does not exist!"
-
- def frameRender(self, frameNo):
- return self.drawFrame(self.width, self.height)
-
- def drawFrame(self, width, height):
- frame = BlankFrame(width, height)
- if self.imagePath and os.path.exists(self.imagePath):
- scale = self.scale if not self.stretched else self.stretchScale
- image = Image.open(self.imagePath)
-
- # Modify image's appearance
- if self.color != 100:
- image = ImageEnhance.Color(image).enhance(float(self.color / 100))
- if self.mirror:
- image = image.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
- if self.stretched and image.size != (width, height):
- image = image.resize((width, height), Image.Resampling.LANCZOS)
- if scale != 100:
- newHeight = int((image.height / 100) * scale)
- newWidth = int((image.width / 100) * scale)
- image = image.resize((newWidth, newHeight), Image.Resampling.LANCZOS)
-
- # Paste image at correct position
- frame.paste(image, box=(self.xPosition, self.yPosition))
- if self.rotate != 0:
- frame = frame.rotate(self.rotate)
-
- return frame
-
- def pickImage(self):
- imgDir = self.settings.value("componentDir", os.path.expanduser("~"))
- filename, _ = QtWidgets.QFileDialog.getOpenFileName(
- self.page,
- "Choose Image",
- imgDir,
- "Image Files (%s)" % " ".join(self.core.imageFormats),
- )
- if filename:
- self.settings.setValue("componentDir", os.path.dirname(filename))
- self.mergeUndo = False
- self.page.lineEdit_image.setText(filename)
- self.mergeUndo = True
-
- def command(self, arg):
- if "=" in arg:
- key, arg = arg.split("=", 1)
- if key == "path" and os.path.exists(arg):
- try:
- Image.open(arg)
- self.page.lineEdit_image.setText(arg)
- self.page.checkBox_stretch.setChecked(True)
- return
- except OSError as e:
- print("Not a supported image format")
- quit(1)
- super().command(arg)
-
- def commandHelp(self):
- print("Load an image:\n path=/filepath/to/image.png")
-
- def savePreset(self):
- # Maintain the illusion that the scale spinbox is one widget
- scaleBox = self.page.spinBox_scale
- stretchScaleBox = self.page.spinBox_scale_stretch
- if self.page.checkBox_stretch.isChecked():
- scaleBox.setValue(stretchScaleBox.value())
- else:
- stretchScaleBox.setValue(scaleBox.value())
- return super().savePreset()
-
- def update(self):
- # Maintain the illusion that the scale spinbox is one widget
- scaleBox = self.page.spinBox_scale
- stretchScaleBox = self.page.spinBox_scale_stretch
- if self.page.checkBox_stretch.isChecked():
- scaleBox.setVisible(False)
- stretchScaleBox.setVisible(True)
- else:
- scaleBox.setVisible(True)
- stretchScaleBox.setVisible(False)
diff --git a/src/components/image.ui b/src/components/image.ui
deleted file mode 100644
index 2dad127..0000000
--- a/src/components/image.ui
+++ /dev/null
@@ -1,388 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 586
- 197
-
-
-
- Form
-
-
- -
-
-
- 4
-
-
-
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 31
- 0
-
-
-
- Image
-
-
-
- -
-
-
-
- 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
-
-
-
- -1000
-
-
- 1000
-
-
- 0
-
-
-
-
-
- -
-
-
-
-
-
- Stretch
-
-
- false
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
- Mirror
-
-
-
- -
-
-
- Rotate
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- QAbstractSpinBox::UpDownArrows
-
-
- °
-
-
- 0
-
-
- 359
-
-
- 0
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 10
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Scale
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- QAbstractSpinBox::UpDownArrows
-
-
- %
-
-
- 10
-
-
- 400
-
-
- 100
-
-
-
- -
-
-
- %
-
-
- 10
-
-
- 400
-
-
- 100
-
-
-
-
-
- -
-
-
-
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Color
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- QAbstractSpinBox::UpDownArrows
-
-
- %
-
-
- 0
-
-
- 999
-
-
- 1
-
-
- 100
-
-
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
-
-
diff --git a/src/components/life.py b/src/components/life.py
deleted file mode 100644
index 5b719d1..0000000
--- a/src/components/life.py
+++ /dev/null
@@ -1,520 +0,0 @@
-from PyQt6 import QtGui, QtCore, QtWidgets
-from PyQt6.QtGui import QUndoCommand
-from PIL import Image, ImageDraw, ImageEnhance, ImageChops, ImageFilter
-import os
-import math
-import logging
-
-
-from ..component import Component
-from ..toolkit.frame import BlankFrame, scale
-
-
-log = logging.getLogger("AVP.Component.Life")
-
-
-class Component(Component):
- name = "Conway's Game of Life"
- version = "1.0.0"
-
- def widget(self, *args):
- super().widget(*args)
- self.scale = 32
- self.updateGridSize()
- # The initial grid: a "Queen Bee Shuttle"
- # 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),
- (10, 9),
- (11, 6),
- (11, 7),
- (11, 8),
- (12, 4),
- (12, 5),
- (12, 9),
- (12, 10),
- (23, 6),
- (23, 7),
- (24, 6),
- (24, 7),
- ]
- )
-
- # Amount of 'bleed' (off-canvas coordinates) on each side of the grid
- self.bleedSize = 40
-
- self.page.pushButton_pickImage.clicked.connect(self.pickImage)
- self.trackWidgets(
- {
- "tickRate": self.page.spinBox_tickRate,
- "scale": self.page.spinBox_scale,
- "color": self.page.lineEdit_color,
- "shapeType": self.page.comboBox_shapeType,
- "shadow": self.page.checkBox_shadow,
- "customImg": self.page.checkBox_customImg,
- "showGrid": self.page.checkBox_showGrid,
- "image": self.page.lineEdit_image,
- },
- colorWidgets={
- "color": self.page.pushButton_color,
- },
- )
- self.shiftButtons = (
- self.page.toolButton_up,
- self.page.toolButton_down,
- self.page.toolButton_left,
- self.page.toolButton_right,
- )
-
- def shiftFunc(i):
- def shift():
- self.shiftGrid(i)
-
- return shift
-
- shiftFuncs = [shiftFunc(i) for i in range(len(self.shiftButtons))]
- for i, widget in enumerate(self.shiftButtons):
- widget.clicked.connect(shiftFuncs[i])
- self.page.spinBox_scale.setValue(self.scale)
- self.page.spinBox_scale.valueChanged.connect(self.updateGridSize)
-
- def pickImage(self):
- imgDir = self.settings.value("componentDir", os.path.expanduser("~"))
- filename, _ = QtWidgets.QFileDialog.getOpenFileName(
- self.page,
- "Choose Image",
- imgDir,
- "Image Files (%s)" % " ".join(self.core.imageFormats),
- )
- if filename:
- self.settings.setValue("componentDir", os.path.dirname(filename))
- self.mergeUndo = False
- self.page.lineEdit_image.setText(filename)
- self.mergeUndo = True
-
- def shiftGrid(self, d):
- action = ShiftGrid(self, d)
- self.parent.undoStack.push(action)
-
- def update(self):
- self.updateGridSize()
- if self.page.checkBox_customImg.isChecked():
- self.page.label_color.setVisible(False)
- self.page.lineEdit_color.setVisible(False)
- self.page.pushButton_color.setVisible(False)
- self.page.label_shape.setVisible(False)
- self.page.comboBox_shapeType.setVisible(False)
- self.page.label_image.setVisible(True)
- self.page.lineEdit_image.setVisible(True)
- self.page.pushButton_pickImage.setVisible(True)
- else:
- self.page.label_color.setVisible(True)
- self.page.lineEdit_color.setVisible(True)
- self.page.pushButton_color.setVisible(True)
- self.page.label_shape.setVisible(True)
- self.page.comboBox_shapeType.setVisible(True)
- self.page.label_image.setVisible(False)
- self.page.lineEdit_image.setVisible(False)
- self.page.pushButton_pickImage.setVisible(False)
- enabled = len(self.startingGrid) > 0
- for widget in self.shiftButtons:
- widget.setEnabled(enabled)
-
- def previewClickEvent(self, pos, size, button):
- pos = (
- math.ceil((pos[0] / size[0]) * self.gridWidth) - 1,
- math.ceil((pos[1] / size[1]) * self.gridHeight) - 1,
- )
- action = ClickGrid(self, pos, button)
- self.parent.undoStack.push(action)
-
- def updateGridSize(self):
- w, h = self.core.resolutions[-1].split("x")
- self.gridWidth = int(int(w) / self.scale)
- self.gridHeight = int(int(h) / self.scale)
- self.pxWidth = math.ceil(self.width / self.gridWidth)
- self.pxHeight = math.ceil(self.height / self.gridHeight)
-
- def previewRender(self):
- return self.drawGrid(self.startingGrid)
-
- def preFrameRender(self, *args, **kwargs):
- super().preFrameRender(*args, **kwargs)
- self.tickGrids = {0: self.startingGrid}
-
- def properties(self):
- if self.customImg and (not self.image or not os.path.exists(self.image)):
- return ["error"]
- return []
-
- def error(self):
- return "No image selected to represent life."
-
- def frameRender(self, frameNo):
- tick = math.floor(frameNo / self.tickRate)
-
- # Compute grid evolution on this frame if it hasn't been computed yet
- if tick not in self.tickGrids:
- self.tickGrids[tick] = self.gridForTick(tick)
- grid = self.tickGrids[tick]
-
- # Delete old evolution data which we shouldn't need anymore
- if tick - 60 in self.tickGrids:
- del self.tickGrids[tick - 60]
- return self.drawGrid(grid)
-
- def drawGrid(self, grid):
- frame = BlankFrame(self.width, self.height)
-
- def drawCustomImg():
- try:
- img = Image.open(self.image)
- except Exception:
- return
- img = img.resize((self.pxWidth, self.pxHeight), Image.Resampling.LANCZOS)
- frame.paste(img, box=(drawPtX, drawPtY))
-
- def drawShape():
- drawer = ImageDraw.Draw(frame)
- rect = (
- (drawPtX, drawPtY),
- (drawPtX + self.pxWidth, drawPtY + self.pxHeight),
- )
- shape = self.page.comboBox_shapeType.currentText().lower()
-
- # Rectangle
- if shape == "rectangle":
- drawer.rectangle(rect, fill=self.color)
-
- # Elliptical
- elif shape == "elliptical":
- drawer.ellipse(rect, fill=self.color)
-
- tenthX, tenthY = scale(10, self.pxWidth, self.pxHeight, int)
- smallerShape = (
- (
- drawPtX + tenthX + int(tenthX / 4),
- drawPtY + tenthY + int(tenthY / 2),
- ),
- (
- drawPtX + self.pxWidth - tenthX - int(tenthX / 4),
- drawPtY + self.pxHeight - (tenthY + int(tenthY / 2)),
- ),
- )
- outlineShape = (
- (drawPtX + int(tenthX / 4), drawPtY + int(tenthY / 2)),
- (
- drawPtX + self.pxWidth - int(tenthX / 4),
- drawPtY + self.pxHeight - int(tenthY / 2),
- ),
- )
- # Circle
- if shape == "circle":
- drawer.ellipse(outlineShape, fill=self.color)
- drawer.ellipse(smallerShape, fill=(0, 0, 0, 0))
-
- # Lilypad
- elif shape == "lilypad":
- drawer.pieslice(smallerShape, 290, 250, fill=self.color)
-
- # Pie
- elif shape == "pie":
- drawer.pieslice(outlineShape, 35, 320, fill=self.color)
-
- hX, hY = scale(50, self.pxWidth, self.pxHeight, int) # halfline
- tX, tY = scale(33, self.pxWidth, self.pxHeight, int) # thirdline
- qX, qY = scale(20, self.pxWidth, self.pxHeight, int) # quarterline
-
- # Path
- if shape == "path":
- drawer.ellipse(rect, fill=self.color)
- rects = {
- direction: False
- for direction in (
- "up",
- "down",
- "left",
- "right",
- )
- }
- for cell in self.nearbyCoords(x, y):
- if cell not in grid:
- continue
- if cell[0] == x:
- if cell[1] < y:
- rects["up"] = True
- if cell[1] > y:
- rects["down"] = True
- if cell[1] == y:
- if cell[0] < x:
- rects["left"] = True
- if cell[0] > x:
- rects["right"] = True
-
- for direction, rect in rects.items():
- if rect:
- if direction == "up":
- sect = (
- (drawPtX, drawPtY),
- (drawPtX + self.pxWidth, drawPtY + hY),
- )
- elif direction == "down":
- sect = (
- (drawPtX, drawPtY + hY),
- (
- drawPtX + self.pxWidth,
- drawPtY + self.pxHeight,
- ),
- )
- elif direction == "left":
- sect = (
- (drawPtX, drawPtY),
- (drawPtX + hX, drawPtY + self.pxHeight),
- )
- elif direction == "right":
- sect = (
- (drawPtX + hX, drawPtY),
- (
- drawPtX + self.pxWidth,
- drawPtY + self.pxHeight,
- ),
- )
- drawer.rectangle(sect, fill=self.color)
-
- # Duck
- elif shape == "duck":
- duckHead = (
- (drawPtX + qX, drawPtY + qY),
- (drawPtX + int(qX * 3), drawPtY + int(tY * 2)),
- )
- duckBeak = (
- (drawPtX + hX, drawPtY + qY),
- (drawPtX + self.pxWidth + qX, drawPtY + int(qY * 3)),
- )
- duckWing = ((drawPtX, drawPtY + hY), rect[1])
- duckBody = (
- (drawPtX + int(qX / 4), drawPtY + int(qY * 3)),
- (drawPtX + int(tX * 2), drawPtY + self.pxHeight),
- )
- drawer.ellipse(duckBody, fill=self.color)
- drawer.ellipse(duckHead, fill=self.color)
- drawer.pieslice(duckWing, 130, 200, fill=self.color)
- drawer.pieslice(duckBeak, 145, 200, fill=self.color)
-
- # Peace
- elif shape == "peace":
- line = (
- (
- drawPtX + hX - int(tenthX / 2),
- drawPtY + int(tenthY / 2),
- ),
- (
- drawPtX + hX + int(tenthX / 2),
- drawPtY + self.pxHeight - int(tenthY / 2),
- ),
- )
- drawer.ellipse(outlineShape, fill=self.color)
- drawer.ellipse(smallerShape, fill=(0, 0, 0, 0))
- drawer.rectangle(line, fill=self.color)
-
- def slantLine(difference):
- return (
- (drawPtX + difference),
- (drawPtY + self.pxHeight - qY),
- ), (
- (drawPtX + hX),
- (drawPtY + hY),
- )
-
- drawer.line(slantLine(qX), fill=self.color, width=tenthX)
- drawer.line(slantLine(self.pxWidth - qX), fill=self.color, width=tenthX)
-
- for x, y in grid:
- drawPtX = x * self.pxWidth
- if drawPtX > self.width:
- continue
- drawPtY = y * self.pxHeight
- if drawPtY > self.height:
- continue
-
- if self.customImg:
- drawCustomImg()
- else:
- drawShape()
-
- if self.shadow:
- 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
- if self.showGrid:
- drawer = ImageDraw.Draw(frame)
- w, h = scale(0.05, self.width, self.height, int)
- for x in range(self.pxWidth, self.width, self.pxWidth):
- drawer.rectangle(
- ((x, 0), (x + w, self.height)),
- fill=self.color,
- )
- for y in range(self.pxHeight, self.height, self.pxHeight):
- drawer.rectangle(
- ((0, y), (self.width, y + h)),
- fill=self.color,
- )
-
- return frame
-
- def gridForTick(self, tick):
- """
- Given a tick number over 0, returns a new grid (a set of tuples).
- This must compute the previous ticks' grids if not already computed
- """
- if tick - 1 not in self.tickGrids:
- self.tickGrids[tick - 1] = self.gridForTick(tick - 1)
-
- lastGrid = self.tickGrids[tick - 1]
-
- def neighbours(x, y):
- return {cell for cell in self.nearbyCoords(x, y) if cell in lastGrid}
-
- newGrid = set()
- # Copy cells from the previous grid if they have 2 or 3 neighbouring cells
- # and if they are within the grid or its bleed area (off-canvas area)
- for x, y in lastGrid:
- if (
- -self.bleedSize > x > self.gridWidth + self.bleedSize
- or -self.bleedSize > y > self.gridHeight + self.bleedSize
- ):
- continue
- surrounding = len(neighbours(x, y))
- if surrounding == 2 or surrounding == 3:
- newGrid.add((x, y))
-
- # Find positions around living cells which must be checked for reproduction
- potentialNewCells = {
- coordTup
- for origin in lastGrid
- for coordTup in list(self.nearbyCoords(*origin))
- }
- # Check for reproduction
- for x, y in potentialNewCells:
- if (x, y) in newGrid:
- # Ignore non-empty cell
- continue
- surrounding = len(neighbours(x, y))
- if surrounding == 3:
- newGrid.add((x, y))
-
- return newGrid
-
- def savePreset(self):
- pr = super().savePreset()
- pr["GRID"] = sorted(self.startingGrid)
- return pr
-
- def loadPreset(self, pr, *args):
- self.startingGrid = set(pr["GRID"])
- if self.startingGrid:
- for widget in self.shiftButtons:
- widget.setEnabled(True)
- super().loadPreset(pr, *args)
-
- def nearbyCoords(self, x, y):
- yield x + 1, y + 1
- yield x + 1, y - 1
- yield x - 1, y + 1
- yield x - 1, y - 1
- yield x, y + 1
- yield x, y - 1
- yield x + 1, y
- yield x - 1, y
-
-
-class ClickGrid(QUndoCommand):
- def __init__(self, comp, pos, button):
- super().__init__("click %s component #%s" % (comp.name, comp.compPos))
- self.comp = comp
- self.pos = [pos]
- if button == QtCore.Qt.MouseButton.RightButton:
- self.button = 2
- else:
- self.button = 1
-
- def id(self):
- return self.button
-
- def mergeWith(self, other):
- self.pos.extend(other.pos)
- return True
-
- def add(self):
- for pos in self.pos[:]:
- self.comp.startingGrid.add(pos)
- self.comp.update(auto=True)
-
- def remove(self):
- for pos in self.pos[:]:
- self.comp.startingGrid.discard(pos)
- self.comp.update(auto=True)
-
- def redo(self):
- if self.button == 1: # Left-click
- self.add()
- elif self.button == 2: # Right-click
- self.remove()
-
- def undo(self):
- if self.button == 1: # Left-click
- self.remove()
- elif self.button == 2: # Right-click
- self.add()
-
-
-class ShiftGrid(QUndoCommand):
- def __init__(self, comp, direction):
- super().__init__("change %s component #%s" % (comp.name, comp.compPos))
- self.comp = comp
- self.direction = direction
- self.distance = 1
-
- def id(self):
- return self.direction
-
- def mergeWith(self, other):
- self.distance += other.distance
- return True
-
- def newGrid(self, Xchange, Ychange):
- return {(x + Xchange, y + Ychange) for x, y in self.comp.startingGrid}
-
- def redo(self):
- if self.direction == 0:
- newGrid = self.newGrid(0, -self.distance)
- elif self.direction == 1:
- newGrid = self.newGrid(0, self.distance)
- elif self.direction == 2:
- newGrid = self.newGrid(-self.distance, 0)
- elif self.direction == 3:
- newGrid = self.newGrid(self.distance, 0)
- self.comp.startingGrid = newGrid
- self.comp._sendUpdateSignal()
-
- def undo(self):
- if self.direction == 0:
- newGrid = self.newGrid(0, self.distance)
- elif self.direction == 1:
- newGrid = self.newGrid(0, -self.distance)
- elif self.direction == 2:
- newGrid = self.newGrid(self.distance, 0)
- elif self.direction == 3:
- newGrid = self.newGrid(-self.distance, 0)
- self.comp.startingGrid = newGrid
- self.comp._sendUpdateSignal()
diff --git a/src/components/life.ui b/src/components/life.ui
deleted file mode 100644
index 30cf9d0..0000000
--- a/src/components/life.ui
+++ /dev/null
@@ -1,405 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 586
- 197
-
-
-
- Form
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
- Simulation Speed
-
-
-
- -
-
-
- frames per tick
-
-
- 1
-
-
- 30
-
-
- 5
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
-
- 0
- 16777215
-
-
-
- 255,255,255
-
-
-
-
-
- -
-
-
-
-
-
- Grid Scale
-
-
-
- -
-
-
- 22
-
-
- 128
-
-
- 32
-
-
-
- -
-
-
- Custom Image
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
- -
-
-
-
-
-
- Image
-
-
-
- -
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 32
- 32
-
-
-
- ...
-
-
-
- -
-
-
- Color
-
-
-
- -
-
-
-
- 0
- 16777215
-
-
-
- 0,0,0
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 32
- 32
-
-
-
-
-
-
- false
-
-
- false
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
- Shape
-
-
-
- -
-
-
-
-
- Path
-
-
- -
-
- Rectangle
-
-
- -
-
- Elliptical
-
-
- -
-
- Circle
-
-
- -
-
- Lilypad
-
-
- -
-
- Pie
-
-
- -
-
- Duck
-
-
- -
-
- Peace
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
- -
-
-
-
-
-
- Shadow
-
-
-
- -
-
-
- Show Grid
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
- -
-
-
-
-
-
- Up
-
-
- Qt::UpArrow
-
-
-
- -
-
-
- Down
-
-
- Qt::DownArrow
-
-
-
- -
-
-
- Left
-
-
- Qt::LeftArrow
-
-
-
- -
-
-
- Right
-
-
- Qt::RightArrow
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
-
-
- -
-
-
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
-<html><head><meta name="qrichtext" content="1" /><style type="text/css">
-p, li { white-space: pre-wrap; }
-</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;">
-<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Click the preview window to place a cell. Right-click to remove.</span></p>
-<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">- A cell with less than 2 neighbours will die from underpopulation</p>
-<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">- A cell with more than 3 neighbours will die from overpopulation.</p>
-<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">- An empty space surrounded by 3 live cells will cause reproduction.</p></body></html>
-
-
- 80
-
-
- Qt::NoTextInteraction
-
-
- false
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
-
-
diff --git a/src/components/original.py b/src/components/original.py
deleted file mode 100644
index fad797b..0000000
--- a/src/components/original.py
+++ /dev/null
@@ -1,243 +0,0 @@
-import numpy
-from PIL import Image, ImageDraw
-from copy import copy
-
-from ..component import Component
-from ..toolkit.frame import BlankFrame
-
-
-class Component(Component):
- name = "Classic Visualizer"
- version = "1.0.0"
-
- def names(*args):
- return ["Original Audio Visualization"]
-
- def properties(self):
- return ["pcm"]
-
- def widget(self, *args):
- self.scale = 20
- self.y = 0
- super().widget(*args)
-
- self.page.comboBox_visLayout.addItem("Classic")
- self.page.comboBox_visLayout.addItem("Split")
- self.page.comboBox_visLayout.addItem("Bottom")
- 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,
- },
- colorWidgets={
- "visColor": self.page.pushButton_visColor,
- },
- relativeWidgets=[
- "y",
- ],
- )
-
- def previewRender(self):
- spectrum = numpy.fromfunction(
- lambda x: float(self.scale) / 2500 * (x - 128) ** 2,
- (255,),
- dtype="int16",
- )
- return self.drawBars(
- self.width, self.height, spectrum, self.visColor, self.layout
- )
-
- def preFrameRender(self, **kwargs):
- super().preFrameRender(**kwargs)
- self.smoothConstantDown = 0.08 + 0 if not self.smooth else self.smooth / 15
- self.smoothConstantUp = 0.8 - 0 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,
- self.smoothConstantDown,
- self.smoothConstantUp,
- self.lastSpectrum,
- )
- 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))
-
- def frameRender(self, frameNo):
- arrayNo = frameNo * self.sampleSize
- return self.drawBars(
- self.width,
- self.height,
- self.spectrumArray[arrayNo],
- self.visColor,
- self.layout,
- )
-
- def transformData(
- self,
- i,
- completeAudioArray,
- sampleSize,
- smoothConstantDown,
- smoothConstantUp,
- lastSpectrum,
- ):
- 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
-
- y = self.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):
- vH = height - height / 8
- bF = width / 64
- bH = bF / 2
- bQ = bF / 4
- imTop = BlankFrame(width, height)
- draw = ImageDraw.Draw(imTop)
- r, g, b = color
- color2 = (r, g, b, 125)
-
- bP = height / 1200
-
- for j in range(0, 63):
- x0 = bH + j * bF
- y0 = vH + bQ
- y1 = vH + bQ - spectrum[j * 4] * bP - bH
- x1 = bH + j * bF + bF
- draw.rectangle(
- (
- x0,
- y0 if y0 < y1 else y1,
- x1 if x1 > x0 else x0,
- y1 if y0 < y1 else y0,
- ),
- fill=color2,
- )
-
- x0 = bH + bQ + j * bF
- y0 = vH
- x1 = bH + bQ + j * bF + bH
- y1 = vH - spectrum[j * 4] * bP
- draw.rectangle(
- (
- x0,
- y0 if y0 < y1 else y1,
- x1 if x1 > x0 else x0,
- y1 if y0 < y1 else y0,
- ),
- fill=color,
- )
-
- imBottom = imTop.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
-
- im = BlankFrame(width, height)
-
- if layout == 0: # Classic
- y = self.y - int(height / 100 * 43)
- im.paste(imTop, (0, y), mask=imTop)
- y = self.y + int(height / 100 * 43)
- im.paste(imBottom, (0, y), mask=imBottom)
-
- if layout == 1: # Split
- y = self.y + int(height / 100 * 10)
- im.paste(imTop, (0, y), mask=imTop)
- y = self.y - int(height / 100 * 10)
- im.paste(imBottom, (0, y), mask=imBottom)
-
- if layout == 2: # Bottom
- y = self.y + int(height / 100 * 10)
- im.paste(imTop, (0, y), mask=imTop)
-
- if layout == 3: # Top
- y = self.y - int(height / 100 * 10)
- im.paste(imBottom, (0, y), mask=imBottom)
-
- return im
-
- def command(self, arg):
- if "=" in arg:
- key, arg = arg.split("=", 1)
- try:
- if key == "color":
- self.page.lineEdit_visColor.setText(arg)
- return
- elif key == "layout":
- if arg == "classic":
- self.page.comboBox_visLayout.setCurrentIndex(0)
- elif arg == "split":
- self.page.comboBox_visLayout.setCurrentIndex(1)
- elif arg == "bottom":
- self.page.comboBox_visLayout.setCurrentIndex(2)
- elif arg == "top":
- self.page.comboBox_visLayout.setCurrentIndex(3)
- return
- elif key == "scale":
- arg = int(arg)
- self.page.spinBox_scale.setValue(arg)
- return
- elif key == "y":
- arg = int(arg)
- self.page.spinBox_y.setValue(arg)
- return
- except ValueError:
- print("You must enter a number.")
- quit(1)
- super().command(arg)
-
- def commandHelp(self):
- print("Give a layout name:\n layout=[classic/split/bottom/top]")
- print("Specify a color:\n color=255,255,255")
- print("Visualizer scale (20 is default):\n scale=number")
- print("Y position:\n y=number")
diff --git a/src/components/original.ui b/src/components/original.ui
deleted file mode 100644
index c7b7e22..0000000
--- a/src/components/original.ui
+++ /dev/null
@@ -1,243 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 586
- 178
-
-
-
-
- 180
- 0
-
-
-
- Form
-
-
- -
-
-
- 4
-
-
-
-
-
-
- 0
- 0
-
-
-
- Layout
-
-
-
- -
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
- Color
-
-
-
- -
-
-
-
- 32
- 32
-
-
-
-
-
-
-
- 32
- 32
-
-
-
-
- -
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
- Y
-
-
-
- -
-
-
- QAbstractSpinBox::UpDownArrows
-
-
- -5000
-
-
- 5000
-
-
- 10
-
-
- 0
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
- -
-
-
- 4
-
-
-
-
-
- Scale
-
-
-
- -
-
-
- QAbstractSpinBox::PlusMinus
-
-
- 1
-
-
- 20
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Expanding
-
-
-
- 40
- 20
-
-
-
-
-
-
- -
-
-
- QLayout::SetDefaultConstraint
-
-
- 4
-
-
-
-
-
- Sensitivity
-
-
-
- -
-
-
- 5
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
-
-
diff --git a/src/components/sound.py b/src/components/sound.py
deleted file mode 100644
index 2df8e38..0000000
--- a/src/components/sound.py
+++ /dev/null
@@ -1,77 +0,0 @@
-from PyQt6 import QtGui, QtCore, QtWidgets
-import os
-
-from ..component import Component
-from ..toolkit.frame import BlankFrame
-
-
-class Component(Component):
- name = "Sound"
- version = "1.0.0"
-
- def widget(self, *args):
- super().widget(*args)
- self.page.pushButton_sound.clicked.connect(self.pickSound)
- self.trackWidgets(
- {
- "sound": self.page.lineEdit_sound,
- "chorus": self.page.checkBox_chorus,
- "delay": self.page.spinBox_delay,
- "volume": self.page.spinBox_volume,
- },
- commandArgs={
- "sound": None,
- },
- )
-
- def properties(self):
- props = ["static", "audio"]
- if not os.path.exists(self.sound):
- props.append("error")
- return props
-
- def error(self):
- if not self.sound:
- return "No audio file selected."
- if not os.path.exists(self.sound):
- return "The audio file selected no longer exists!"
-
- def audio(self):
- params = {}
- if self.delay != 0.0:
- params["adelay"] = "=%s" % str(int(self.delay * 1000.00))
- if self.chorus:
- params["chorus"] = "=0.5:0.9:50|60|40:0.4|0.32|0.3:0.25|0.4|0.3:2|2.3|1.3"
- if self.volume != 1.0:
- params["volume"] = "=%s:replaygain_noclip=0" % str(self.volume)
-
- return (self.sound, params)
-
- def pickSound(self):
- sndDir = self.settings.value("componentDir", os.path.expanduser("~"))
- filename, _ = QtWidgets.QFileDialog.getOpenFileName(
- self.page,
- "Choose Sound",
- sndDir,
- "Audio Files (%s)" % " ".join(self.core.audioFormats),
- )
- if filename:
- self.settings.setValue("componentDir", os.path.dirname(filename))
- self.mergeUndo = False
- self.page.lineEdit_sound.setText(filename)
- self.mergeUndo = True
-
- def commandHelp(self):
- print("Path to audio file:\n path=/filepath/to/sound.ogg")
-
- def command(self, arg):
- if "=" in arg:
- key, arg = arg.split("=", 1)
- if key == "path":
- if "*%s" % os.path.splitext(arg)[1] not in self.core.audioFormats:
- print("Not a supported audio format")
- quit(1)
- self.page.lineEdit_sound.setText(arg)
- return
-
- super().command(arg)
diff --git a/src/components/sound.ui b/src/components/sound.ui
deleted file mode 100644
index 4c11332..0000000
--- a/src/components/sound.ui
+++ /dev/null
@@ -1,172 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 586
- 197
-
-
-
- Form
-
-
- -
-
-
- 4
-
-
-
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 31
- 0
-
-
-
- Audio File
-
-
-
- -
-
-
-
- 1
- 0
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 1
- 0
-
-
-
-
- 32
- 32
-
-
-
- ...
-
-
-
- 32
- 32
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
- Volume
-
-
-
- -
-
-
- x
-
-
- 10.000000000000000
-
-
- 0.100000000000000
-
-
- 1.000000000000000
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
- Delay
-
-
-
- -
-
-
- s
-
-
- 9999999.990000000223517
-
-
- 0.500000000000000
-
-
-
- -
-
-
- Chorus
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
-
-
diff --git a/src/components/spectrum.py b/src/components/spectrum.py
deleted file mode 100644
index 062ebc7..0000000
--- a/src/components/spectrum.py
+++ /dev/null
@@ -1,368 +0,0 @@
-from PIL import Image
-from PyQt6 import QtGui, QtCore, QtWidgets
-import os
-import math
-import subprocess
-import time
-import logging
-
-from ..component import Component
-from ..toolkit.frame import BlankFrame, scale
-from ..toolkit import checkOutput, connectWidget
-from ..toolkit.ffmpeg import (
- openPipe,
- closePipe,
- getAudioDuration,
- FfmpegVideo,
- exampleSound,
-)
-
-
-log = logging.getLogger("AVP.Components.Spectrum")
-
-
-class Component(Component):
- name = "Spectrum"
- version = "1.0.1"
-
- def widget(self, *args):
- self.previewFrame = None
- super().widget(*args)
- self._image = BlankFrame(self.width, self.height)
- self.chunkSize = 4 * self.width * self.height
- self.changedOptions = True
- self.previewSize = (214, 120)
- self.previewPipe = None
-
- if hasattr(self.parent, "lineEdit_audioFile"):
- # update preview when audio file changes (if genericPreview is off)
- self.parent.lineEdit_audioFile.textChanged.connect(self.update)
-
- self.trackWidgets(
- {
- "filterType": self.page.comboBox_filterType,
- "window": self.page.comboBox_window,
- "mode": self.page.comboBox_mode,
- "amplitude": self.page.comboBox_amplitude0,
- "amplitude1": self.page.comboBox_amplitude1,
- "amplitude2": self.page.comboBox_amplitude2,
- "display": self.page.comboBox_display,
- "zoom": self.page.spinBox_zoom,
- "tc": self.page.spinBox_tc,
- "x": self.page.spinBox_x,
- "y": self.page.spinBox_y,
- "mirror": self.page.checkBox_mirror,
- "draw": self.page.checkBox_draw,
- "scale": self.page.spinBox_scale,
- "color": self.page.comboBox_color,
- "compress": self.page.checkBox_compress,
- "mono": self.page.checkBox_mono,
- "hue": self.page.spinBox_hue,
- },
- relativeWidgets=[
- "x",
- "y",
- ],
- )
- for widget in self._trackedWidgets.values():
- connectWidget(widget, lambda: self.changed())
-
- def changed(self):
- self.changedOptions = True
-
- def update(self):
- filterType = self.page.comboBox_filterType.currentIndex()
- self.page.stackedWidget.setCurrentIndex(filterType)
- if filterType == 3:
- self.page.spinBox_hue.setEnabled(False)
- else:
- self.page.spinBox_hue.setEnabled(True)
- if filterType == 2 or filterType == 4:
- self.page.checkBox_mono.setEnabled(False)
- else:
- self.page.checkBox_mono.setEnabled(True)
-
- def previewRender(self):
- changedSize = self.updateChunksize()
- if (
- not changedSize
- and not self.changedOptions
- and self.previewFrame is not None
- ):
- log.debug("Spectrum #%s is reusing old preview frame" % self.compPos)
- return self.previewFrame
-
- frame = self.getPreviewFrame()
- self.changedOptions = False
- if not frame:
- log.warning("Spectrum #%s failed to create a preview frame" % self.compPos)
- self.previewFrame = None
- return BlankFrame(self.width, self.height)
- else:
- self.previewFrame = frame
- return frame
-
- def preFrameRender(self, **kwargs):
- super().preFrameRender(**kwargs)
- if self.previewPipe is not None:
- self.previewPipe.wait()
- self.updateChunksize()
- w, h = scale(self.scale, self.width, self.height, str)
- self.video = FfmpegVideo(
- inputPath=self.audioFile,
- filter_=self.makeFfmpegFilter(),
- width=w,
- height=h,
- chunkSize=self.chunkSize,
- frameRate=int(self.settings.value("outputFrameRate")),
- parent=self.parent,
- component=self,
- )
-
- def frameRender(self, frameNo):
- if FfmpegVideo.threadError is not None:
- raise FfmpegVideo.threadError
- return self.finalizeFrame(self.video.frame(frameNo))
-
- def postFrameRender(self):
- closePipe(self.video.pipe)
-
- def getPreviewFrame(self):
- genericPreview = self.settings.value("pref_genericPreview")
- startPt = 0
- if not genericPreview:
- inputFile = self.parent.lineEdit_audioFile.text()
- if not inputFile or not os.path.exists(inputFile):
- return
- duration = getAudioDuration(inputFile)
- if not duration:
- return
- startPt = duration / 3
-
- command = [
- self.core.FFMPEG_BIN,
- "-thread_queue_size",
- "512",
- "-r",
- str(self.settings.value("outputFrameRate")),
- "-ss",
- "{0:.3f}".format(startPt),
- "-i",
- self.core.junkStream if genericPreview else inputFile,
- "-f",
- "image2pipe",
- "-pix_fmt",
- "rgba",
- ]
- command.extend(self.makeFfmpegFilter(preview=True, startPt=startPt))
- command.extend(
- [
- "-an",
- "-s:v",
- "%sx%s" % scale(self.scale, self.width, self.height, str),
- "-codec:v",
- "rawvideo",
- "-",
- "-frames:v",
- "1",
- ]
- )
-
- if self.core.logEnabled:
- logFilename = os.path.join(
- self.core.logDir, "preview_%s.log" % str(self.compPos)
- )
- log.debug("Creating FFmpeg process (log at %s)" % logFilename)
- with open(logFilename, "w") as logf:
- logf.write(" ".join(command) + "\n\n")
- with open(logFilename, "a") as logf:
- self.previewPipe = openPipe(
- command,
- stdin=subprocess.DEVNULL,
- stdout=subprocess.PIPE,
- stderr=logf,
- bufsize=10**8,
- )
- else:
- self.previewPipe = openPipe(
- command,
- stdin=subprocess.DEVNULL,
- stdout=subprocess.PIPE,
- stderr=subprocess.DEVNULL,
- bufsize=10**8,
- )
- byteFrame = self.previewPipe.stdout.read(self.chunkSize)
- closePipe(self.previewPipe)
-
- frame = self.finalizeFrame(byteFrame)
- return frame
-
- def makeFfmpegFilter(self, preview=False, startPt=0):
- """Makes final FFmpeg filter command"""
-
- def getFilterComplexCommand():
- """Inner function that creates the final, complex part of the filter command"""
- nonlocal self
- genericPreview = self.settings.value("pref_genericPreview")
-
- def getFilterComplexCommandForType():
- """Determine portion of filter command that changes depending on selected type"""
- nonlocal self
- if preview:
- w, h = self.previewSize
- else:
- w, h = (self.width, self.height)
- color = self.page.comboBox_color.currentText().lower()
-
- if self.filterType == 0: # Spectrum
- if self.amplitude == 0:
- amplitude = "sqrt"
- elif self.amplitude == 1:
- amplitude = "cbrt"
- elif self.amplitude == 2:
- amplitude = "4thrt"
- elif self.amplitude == 3:
- amplitude = "5thrt"
- elif self.amplitude == 4:
- amplitude = "lin"
- elif self.amplitude == 5:
- amplitude = "log"
- filter_ = (
- f"showspectrum=s={w}x{h}:"
- "slide=scroll:"
- f"win_func={self.page.comboBox_window.currentText()}:"
- f"color={color}:"
- f"scale={amplitude},"
- "colorkey=color=black:"
- "similarity=0.1:blend=0.5"
- )
- elif self.filterType == 1: # Histogram
- if self.amplitude1 == 0:
- amplitude = "log"
- elif self.amplitude1 == 1:
- amplitude = "lin"
- if self.display == 0:
- display = "log"
- elif self.display == 1:
- display = "sqrt"
- elif self.display == 2:
- display = "cbrt"
- elif self.display == 3:
- display = "lin"
- elif self.display == 4:
- display = "rlog"
- filter_ = (
- f'ahistogram=r={str(self.settings.value("outputFrameRate"))}:'
- f"s={w}x{h}:"
- "dmode=separate:"
- f"ascale={amplitude}:"
- f"scale={display}"
- )
- elif self.filterType == 2: # Vector Scope
- if self.amplitude2 == 0:
- amplitude = "log"
- elif self.amplitude2 == 1:
- amplitude = "sqrt"
- elif self.amplitude2 == 2:
- amplitude = "cbrt"
- elif self.amplitude2 == 3:
- amplitude = "lin"
- m = self.page.comboBox_mode.currentText()
- filter_ = (
- f"avectorscope=s={w}x{h}:"
- f'draw={"line" if self.draw else "dot"}:'
- f"m={m}:"
- f"scale={amplitude}:"
- f"zoom={str(self.zoom)}"
- )
- elif self.filterType == 3: # Musical Scale
- filter_ = (
- f'showcqt=r={str(self.settings.value("outputFrameRate"))}:'
- f"s={w}x{h}:"
- "count=30:"
- "text=0:"
- f"tc={str(self.tc)},"
- "colorkey=color=black:"
- "similarity=0.1:blend=0.5"
- )
- elif self.filterType == 4: # Phase
- filter_ = (
- f'aphasemeter=r={str(self.settings.value("outputFrameRate"))}:'
- f"s={w}x{h}:"
- "video=1 [atrash][vtmp1]; "
- "[atrash] anullsink; "
- "[vtmp1] colorkey=color=black:"
- "similarity=0.1:blend=0.5, "
- "crop=in_w/8:in_h:(in_w/8)*7:0 "
- )
- return filter_
-
- if self.filterType < 2:
- exampleSnd = exampleSound("freq")
- elif self.filterType == 2 or self.filterType == 4:
- exampleSnd = exampleSound("stereo")
- elif self.filterType == 3:
- exampleSnd = exampleSound("white")
- compression = "compand=gain=4," if self.compress else ""
- aformat = (
- "aformat=channel_layouts=mono,"
- if self.mono and self.filterType not in (2, 4)
- else ""
- )
- filter_ = getFilterComplexCommandForType()
- hflip = "hflip, " if self.mirror else ""
- trim = (
- "trim=start=%s:end=%s, "
- % (
- "{0:.3f}".format(startPt + 12),
- "{0:.3f}".format(startPt + 12.5),
- )
- if preview
- else ""
- )
- scale_ = "scale=%sx%s" % scale(self.scale, self.width, self.height, str)
- hue = (
- ", hue=h=%s:s=10" % str(self.hue)
- if self.hue > 0 and self.filterType != 3
- else ""
- )
- convolution = (
- ", convolution=-2 -1 0 -1 1 1 0 1 2:-2 -1 0 -1 1 1 0 1 2:-2 -1 0 -1 1 1 0 1 2:-2 -1 0 -1 1 1 0 1 2"
- if self.filterType == 3
- else ""
- )
-
- return (
- f"{exampleSnd if preview and genericPreview else '[0:a] '}"
- f"{compression}{aformat}{filter_} [v1]; "
- f"[v1] {hflip}{trim}{scale_}{hue}{convolution} [v]"
- )
-
- return [
- "-filter_complex",
- getFilterComplexCommand(),
- "-map",
- "[v]",
- ]
-
- def updateChunksize(self):
- width, height = scale(self.scale, self.width, self.height, int)
- oldChunkSize = int(self.chunkSize)
- self.chunkSize = 4 * width * height
- changed = self.chunkSize != oldChunkSize
- return changed
-
- def finalizeFrame(self, imageData):
- try:
- image = Image.frombytes(
- "RGBA",
- scale(self.scale, self.width, self.height, int),
- imageData,
- )
- self._image = image
- except ValueError:
- image = self._image
-
- frame = BlankFrame(self.width, self.height)
- frame.paste(image, box=(self.x, self.y))
- return frame
diff --git a/src/components/spectrum.ui b/src/components/spectrum.ui
deleted file mode 100644
index c6a8a15..0000000
--- a/src/components/spectrum.ui
+++ /dev/null
@@ -1,946 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 586
- 197
-
-
-
-
- 0
- 0
-
-
-
-
- 0
- 197
-
-
-
- Form
-
-
- -
-
-
- 4
-
-
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
- Type
-
-
-
- -
-
-
-
-
- Spectrum
-
-
- -
-
- Histogram
-
-
- -
-
- Vector Scope
-
-
- -
-
- Musical Scale
-
-
- -
-
- Phase
-
-
-
-
- -
-
-
- 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
-
-
-
-
-
- -
-
-
-
-
-
- Compress
-
-
-
- -
-
-
- Mono
-
-
-
- -
-
-
- Mirror
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
- Hue
-
-
- 4
-
-
-
- -
-
-
- °
-
-
- 359
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Scale
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- QAbstractSpinBox::UpDownArrows
-
-
- %
-
-
- 10
-
-
- 400
-
-
- 100
-
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- false
-
-
- QFrame::NoFrame
-
-
- QFrame::Plain
-
-
- 0
-
-
-
-
-
- 0
- 0
- 561
- 66
-
-
-
-
- QLayout::SetMaximumSize
-
-
- 0
-
-
-
-
-
- QLayout::SetDefaultConstraint
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 31
- 0
-
-
-
- Window
-
-
- 4
-
-
-
- -
-
-
-
-
- hann
-
-
- -
-
- gauss
-
-
- -
-
- tukey
-
-
- -
-
- dolph
-
-
- -
-
- cauchy
-
-
- -
-
- parzen
-
-
- -
-
- poisson
-
-
- -
-
- rect
-
-
- -
-
- bartlett
-
-
- -
-
- hanning
-
-
- -
-
- hamming
-
-
- -
-
- blackman
-
-
- -
-
- welch
-
-
- -
-
- flattop
-
-
- -
-
- bharris
-
-
- -
-
- bnuttall
-
-
- -
-
- lanczos
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Amplitude
-
-
- 4
-
-
-
- -
-
-
-
-
- Square root
-
-
- -
-
- Cubic root
-
-
- -
-
- 4thrt
-
-
- -
-
- 5thrt
-
-
- -
-
- Linear
-
-
- -
-
- Logarithmic
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::MinimumExpanding
-
-
-
- 10
- 20
-
-
-
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
- Color
-
-
- 4
-
-
-
- -
-
-
-
-
- Channel
-
-
- -
-
- Intensity
-
-
- -
-
- Rainbow
-
-
- -
-
- Moreland
-
-
- -
-
- Nebulae
-
-
- -
-
- Fire
-
-
- -
-
- Fiery
-
-
- -
-
- Fruit
-
-
- -
-
- Cool
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::MinimumExpanding
-
-
-
- 10
- 20
-
-
-
-
-
-
-
-
-
-
-
-
-
- -1
- -1
- 561
- 31
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
- Display Scale
-
-
- 4
-
-
-
- -
-
-
-
-
- Logarithmic
-
-
- -
-
- Square root
-
-
- -
-
- Cubic root
-
-
- -
-
- Linear
-
-
- -
-
- Reverse Log
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Amplitude
-
-
- 4
-
-
-
- -
-
-
-
-
- Logarithmic
-
-
- -
-
- Linear
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Minimum
-
-
-
- 40
- 20
-
-
-
-
-
-
-
-
-
-
-
-
-
- -1
- -1
- 585
- 64
-
-
-
- -
-
-
-
-
-
- Mode
-
-
-
- -
-
-
-
-
- lissajous
-
-
- -
-
- lissajous_xy
-
-
- -
-
- polar
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Amplitude
-
-
- 4
-
-
-
- -
-
-
-
-
- Linear
-
-
- -
-
- Square root
-
-
- -
-
- Cubic root
-
-
- -
-
- Logarithmic
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
- Zoom
-
-
- 4
-
-
-
- -
-
-
- 1
-
-
- 10
-
-
-
- -
-
-
- Line
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
-
-
-
-
-
-
-
- 0
- 0
- 561
- 31
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
- Timeclamp
-
-
- 4
-
-
-
- -
-
-
- s
-
-
- 3
-
-
- 0.002000000000000
-
-
- 1.000000000000000
-
-
- 0.010000000000000
-
-
- 0.017000000000000
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
-
-
-
-
-
-
-
- 0
- 0
- 551
- 31
-
-
-
- -
-
-
-
-
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
- QSizePolicy::Fixed
-
-
-
- 20
- 10
-
-
-
-
-
-
-
-
-
diff --git a/src/components/text.py b/src/components/text.py
deleted file mode 100644
index 40c981a..0000000
--- a/src/components/text.py
+++ /dev/null
@@ -1,218 +0,0 @@
-from PIL import ImageEnhance, ImageFilter, ImageChops
-from PyQt6.QtGui import QColor, QFont
-from PyQt6 import QtGui, QtCore, QtWidgets
-import os
-import logging
-
-from ..component import Component
-from ..toolkit.frame import FramePainter, PaintColor
-
-log = logging.getLogger("AVP.Components.Text")
-
-
-class Component(Component):
- name = "Title Text"
- version = "1.0.1"
-
- def widget(self, *args):
- super().widget(*args)
- self.title = "Text"
- self.alignment = 1
- self.titleFont = QFont()
- self.fontSize = self.height / 13.5
-
- self.page.comboBox_textAlign.addItem("Left")
- self.page.comboBox_textAlign.addItem("Middle")
- 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(
- self._sendUpdateSignal
- )
- # 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
-
- self.trackWidgets(
- {
- "textColor": self.page.lineEdit_textColor,
- "title": self.page.lineEdit_title,
- "alignment": self.page.comboBox_textAlign,
- "fontSize": self.page.spinBox_fontSize,
- "xPosition": self.page.spinBox_xTextAlign,
- "yPosition": self.page.spinBox_yTextAlign,
- "fontStyle": self.page.comboBox_fontStyle,
- "stroke": self.page.spinBox_stroke,
- "strokeColor": self.page.lineEdit_strokeColor,
- "shadow": self.page.checkBox_shadow,
- "shadX": self.page.spinBox_shadX,
- "shadY": self.page.spinBox_shadY,
- "shadBlur": self.page.spinBox_shadBlur,
- },
- colorWidgets={
- "textColor": self.page.pushButton_textColor,
- "strokeColor": self.page.pushButton_strokeColor,
- },
- relativeWidgets=[
- "xPosition",
- "yPosition",
- "fontSize",
- "stroke",
- "shadX",
- "shadY",
- "shadBlur",
- ],
- )
- self.centerXY()
-
- def update(self):
- self.titleFont = self.page.fontComboBox_titleFont.currentFont()
- if self.page.checkBox_shadow.isChecked():
- self.page.label_shadX.setHidden(False)
- self.page.spinBox_shadX.setHidden(False)
- self.page.spinBox_shadY.setHidden(False)
- self.page.label_shadBlur.setHidden(False)
- self.page.spinBox_shadBlur.setHidden(False)
- else:
- self.page.label_shadX.setHidden(True)
- self.page.spinBox_shadX.setHidden(True)
- self.page.spinBox_shadY.setHidden(True)
- self.page.label_shadBlur.setHidden(True)
- self.page.spinBox_shadBlur.setHidden(True)
-
- def centerXY(self):
- self.setRelativeWidget("xPosition", 0.5)
- self.setRelativeWidget("yPosition", 0.521)
-
- def getXY(self):
- """Returns true x, y after considering alignment settings"""
- fm = QtGui.QFontMetrics(self.titleFont)
- text_width = fm.boundingRect(self.title).width()
- x = self.pixelValForAttr("xPosition")
-
- if self.alignment == 1: # Middle
- offset = int(text_width / 2)
- elif self.alignment == 2: # Right
- offset = text_width
- else:
- raise ValueError(f"Alignment value {self.alignment} unknown")
-
- x -= offset
-
- return x, self.yPosition
-
- def loadPreset(self, pr, *args):
- super().loadPreset(pr, *args)
-
- font = QFont()
- font.fromString(pr["titleFont"])
- self.page.fontComboBox_titleFont.setCurrentFont(font)
-
- def savePreset(self):
- saveValueStore = super().savePreset()
- saveValueStore["titleFont"] = self.titleFont.toString()
- return saveValueStore
-
- def previewRender(self):
- return self.addText(self.width, self.height)
-
- def properties(self):
- props = ["static"]
- if not self.title:
- props.append("error")
- return props
-
- def error(self):
- return "No text provided."
-
- def frameRender(self, frameNo):
- return self.addText(self.width, self.height)
-
- def addText(self, width, height):
- font = self.titleFont
- font.setPixelSize(self.fontSize)
- font.setStyle(QFont.Style.StyleNormal)
- font.setWeight(QFont.Weight.Normal)
- font.setCapitalization(QFont.Capitalization.MixedCase)
- if self.fontStyle == 1:
- font.setWeight(QFont.Weight.DemiBold)
- if self.fontStyle == 2:
- font.setWeight(QFont.Weight.Bold)
- elif self.fontStyle == 3:
- font.setStyle(QFont.Style.StyleItalic)
- elif self.fontStyle == 4:
- font.setWeight(QFont.Weight.Bold)
- font.setStyle(QFont.Style.StyleItalic)
- elif self.fontStyle == 5:
- font.setStyle(QFont.Style.StyleOblique)
- elif self.fontStyle == 6:
- font.setCapitalization(QFont.Capitalization.SmallCaps)
-
- image = FramePainter(width, height)
- x, y = self.getXY()
- log.debug("Text position translates to %s, %s", x, y)
- if self.stroke > 0:
- outliner = QtGui.QPainterPathStroker()
- outliner.setWidth(self.stroke)
- path = QtGui.QPainterPath()
- if self.fontStyle == 6:
- # PathStroker ignores smallcaps so we need this weird hack
- path.addText(x, y, font, self.title[0])
- fm = QtGui.QFontMetrics(font)
- newX = x + fm.boundingRect(self.title[0]).width()
- strokeFont = self.page.fontComboBox_titleFont.currentFont()
- strokeFont.setCapitalization(QFont.Capitalization.SmallCaps)
- strokeFont.setPixelSize(int((self.fontSize / 7) * 5))
- strokeFont.setLetterSpacing(QFont.SpacingType.PercentageSpacing, 139)
- path.addText(newX, y, strokeFont, self.title[1:])
- else:
- path.addText(x, y, font, self.title)
- path = outliner.createStroke(path)
- image.setPen(QtCore.Qt.PenStyle.NoPen)
- image.setBrush(PaintColor(*self.strokeColor))
- image.drawPath(path)
-
- image.setFont(font)
- image.setPen(self.textColor)
- image.drawText(x, y, self.title)
-
- # 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
-
- return frame
-
- def commandHelp(self):
- print("Enter a string to use as centred white text:")
- print(' "title=User Error"')
- print("Specify a text color:\n color=255,255,255")
- print("Set custom x, y position:\n x=500 y=500")
-
- def command(self, arg):
- if "=" in arg:
- key, arg = arg.split("=", 1)
- if key == "color":
- self.page.lineEdit_textColor.setText(arg)
- return
- elif key == "size":
- self.page.spinBox_fontSize.setValue(int(arg))
- return
- elif key == "x":
- self.page.spinBox_xTextAlign.setValue(int(arg))
- return
- elif key == "y":
- self.page.spinBox_yTextAlign.setValue(int(arg))
- return
- elif key == "title":
- self.page.lineEdit_title.setText(arg)
- return
- super().command(arg)
diff --git a/src/components/text.ui b/src/components/text.ui
deleted file mode 100644
index b62e0ed..0000000
--- a/src/components/text.ui
+++ /dev/null
@@ -1,671 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 586
- 197
-
-
-
- Form
-
-
- -
-
-
- 6
-
-
- QLayout::SetDefaultConstraint
-
-
- 4
-
-
-
-
-
-
-
-
- Title
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 0
-
-
-
- Testing New GUI
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Font
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 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
-
-
-
-
-
- -
-
-
-
-
-
-
- 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
-
-
-
-
- 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
-
-
-
- Shadow
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Shadow Offset
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- -1000
-
-
- 1000
-
-
- -4
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- -1000
-
-
- 1000
-
-
- 8
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Shadow Blur
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- 99.000000000000000
-
-
- 0.100000000000000
-
-
- 5.000000000000000
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Minimum
-
-
-
- 40
- 20
-
-
-
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
-
-
diff --git a/src/components/video.py b/src/components/video.py
deleted file mode 100644
index 65a05af..0000000
--- a/src/components/video.py
+++ /dev/null
@@ -1,254 +0,0 @@
-from PIL import Image
-from PyQt6 import QtGui, QtCore, QtWidgets
-import os
-import math
-import subprocess
-import logging
-
-from ..component import Component
-from ..toolkit.frame import BlankFrame, scale
-from ..toolkit.ffmpeg import openPipe, closePipe, testAudioStream, FfmpegVideo
-from ..toolkit import checkOutput
-
-
-log = logging.getLogger("AVP.Components.Video")
-
-
-class Component(Component):
- name = "Video"
- version = "1.0.0"
-
- def widget(self, *args):
- self.videoPath = ""
- self.badAudio = False
- self.x = 0
- self.y = 0
- self.loopVideo = False
- super().widget(*args)
- self._image = BlankFrame(self.width, self.height)
- self.page.pushButton_video.clicked.connect(self.pickVideo)
- self.trackWidgets(
- {
- "videoPath": self.page.lineEdit_video,
- "loopVideo": self.page.checkBox_loop,
- "useAudio": self.page.checkBox_useAudio,
- "distort": self.page.checkBox_distort,
- "scale": self.page.spinBox_scale,
- "volume": self.page.spinBox_volume,
- "xPosition": self.page.spinBox_x,
- "yPosition": self.page.spinBox_y,
- },
- presetNames={
- "videoPath": "video",
- "loopVideo": "loop",
- "xPosition": "x",
- "yPosition": "y",
- },
- relativeWidgets=[
- "xPosition",
- "yPosition",
- ],
- )
-
- def update(self):
- if self.page.checkBox_useAudio.isChecked():
- self.page.label_volume.setEnabled(True)
- self.page.spinBox_volume.setEnabled(True)
- else:
- self.page.label_volume.setEnabled(False)
- self.page.spinBox_volume.setEnabled(False)
-
- def previewRender(self):
- self.updateChunksize()
- frame = self.getPreviewFrame(self.width, self.height)
- if not frame:
- return BlankFrame(self.width, self.height)
- else:
- return frame
-
- def properties(self):
- props = []
- outputFile = None
- if hasattr(self.parent, "lineEdit_outputFile"):
- # check only happens in GUI mode
- outputFile = self.parent.lineEdit_outputFile.text()
-
- if not self.videoPath:
- self.lockError("There is no video selected.")
- elif not os.path.exists(self.videoPath):
- self.lockError("The video selected does not exist!")
- elif outputFile and os.path.realpath(self.videoPath) == os.path.realpath(
- outputFile
- ):
- self.lockError("Input and output paths match.")
-
- if self.useAudio:
- props.append("audio")
- if not testAudioStream(self.videoPath) and self.error() is None:
- self.lockError("Could not identify an audio stream in this video.")
-
- return props
-
- def audio(self):
- params = {}
- if self.volume != 1.0:
- params["volume"] = "=%s:replaygain_noclip=0" % str(self.volume)
- return (self.videoPath, params)
-
- def preFrameRender(self, **kwargs):
- super().preFrameRender(**kwargs)
- self.updateChunksize()
- self.video = (
- FfmpegVideo(
- inputPath=self.videoPath,
- filter_=self.makeFfmpegFilter(),
- width=self.width,
- height=self.height,
- chunkSize=self.chunkSize,
- frameRate=int(self.settings.value("outputFrameRate")),
- parent=self.parent,
- loopVideo=self.loopVideo,
- component=self,
- )
- if os.path.exists(self.videoPath)
- else None
- )
-
- def frameRender(self, frameNo):
- if FfmpegVideo.threadError is not None:
- raise FfmpegVideo.threadError
- return self.finalizeFrame(self.video.frame(frameNo))
-
- def postFrameRender(self):
- closePipe(self.video.pipe)
-
- def pickVideo(self):
- imgDir = self.settings.value("componentDir", os.path.expanduser("~"))
- filename, _ = QtWidgets.QFileDialog.getOpenFileName(
- self.page,
- "Choose Video",
- imgDir,
- "Video Files (%s)" % " ".join(self.core.videoFormats),
- )
- if filename:
- self.settings.setValue("componentDir", os.path.dirname(filename))
- self.mergeUndo = False
- self.page.lineEdit_video.setText(filename)
- self.mergeUndo = True
-
- def getPreviewFrame(self, width, height):
- if not self.videoPath or not os.path.exists(self.videoPath):
- return
-
- command = [
- self.core.FFMPEG_BIN,
- "-thread_queue_size",
- "512",
- "-i",
- self.videoPath,
- "-f",
- "image2pipe",
- "-pix_fmt",
- "rgba",
- ]
- command.extend(self.makeFfmpegFilter())
- command.extend(
- [
- "-codec:v",
- "rawvideo",
- "-",
- "-ss",
- "90",
- "-frames:v",
- "1",
- ]
- )
-
- if self.core.logEnabled:
- logFilename = os.path.join(
- self.core.logDir, "preview_%s.log" % str(self.compPos)
- )
- log.debug("Creating ffmpeg process (log at %s)" % logFilename)
- with open(logFilename, "w") as logf:
- logf.write(" ".join(command) + "\n\n")
- with open(logFilename, "a") as logf:
- pipe = openPipe(
- command,
- stdin=subprocess.DEVNULL,
- stdout=subprocess.PIPE,
- stderr=logf,
- bufsize=10**8,
- )
- else:
- pipe = openPipe(
- command,
- stdin=subprocess.DEVNULL,
- stdout=subprocess.PIPE,
- stderr=subprocess.DEVNULL,
- bufsize=10**8,
- )
-
- byteFrame = pipe.stdout.read(self.chunkSize)
- closePipe(pipe)
-
- frame = self.finalizeFrame(byteFrame)
- return frame
-
- def makeFfmpegFilter(self):
- return [
- "-filter_complex",
- "[0:v] scale=%s:%s" % scale(self.scale, self.width, self.height, str),
- ]
-
- def updateChunksize(self):
- if self.scale != 100 and not self.distort:
- width, height = scale(self.scale, self.width, self.height, int)
- else:
- width, height = self.width, self.height
- self.chunkSize = 4 * width * height
-
- def command(self, arg):
- if "=" in arg:
- key, arg = arg.split("=", 1)
- if key == "path" and os.path.exists(arg):
- if "*%s" % os.path.splitext(arg)[1] in self.core.videoFormats:
- self.page.lineEdit_video.setText(arg)
- self.page.spinBox_scale.setValue(100)
- self.page.checkBox_loop.setChecked(True)
- return
- else:
- print("Not a supported video format")
- quit(1)
- elif arg == "audio":
- if not self.page.lineEdit_video.text():
- print("'audio' option must follow a video selection")
- quit(1)
- self.page.checkBox_useAudio.setChecked(True)
- return
- super().command(arg)
-
- def commandHelp(self):
- print("Load a video:\n path=/filepath/to/video.mp4")
- print("Using audio:\n path=/filepath/to/video.mp4 audio")
-
- def finalizeFrame(self, imageData):
- try:
- if self.distort:
- image = Image.frombytes("RGBA", (self.width, self.height), imageData)
- else:
- image = Image.frombytes(
- "RGBA",
- scale(self.scale, self.width, self.height, int),
- imageData,
- )
- self._image = image
- except ValueError:
- # use last good frame
- image = self._image
-
- if self.scale != 100 or self.xPosition != 0 or self.yPosition != 0:
- frame = BlankFrame(self.width, self.height)
- frame.paste(image, box=(self.xPosition, self.yPosition))
- else:
- frame = image
- return frame
diff --git a/src/components/video.ui b/src/components/video.ui
deleted file mode 100644
index 08d15d3..0000000
--- a/src/components/video.ui
+++ /dev/null
@@ -1,328 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 586
- 197
-
-
-
-
- 0
- 0
-
-
-
-
- 0
- 197
-
-
-
- Form
-
-
- -
-
-
- 4
-
-
-
-
-
-
-
-
-
- 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
-
-
-
-
-
-
-
- -
-
-
-
-
-
- Loop
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
- Distort by scale
-
-
-
- -
-
-
- Scale
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- QAbstractSpinBox::UpDownArrows
-
-
- %
-
-
- 10
-
-
- 400
-
-
- 100
-
-
-
-
-
- -
-
-
-
-
-
- Use Audio
-
-
-
- -
-
-
- Volume
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- x
-
-
- 0.000000000000000
-
-
- 10.000000000000000
-
-
- 0.100000000000000
-
-
- 1.000000000000000
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
-
-
diff --git a/src/components/waveform.py b/src/components/waveform.py
deleted file mode 100644
index 7dc0b99..0000000
--- a/src/components/waveform.py
+++ /dev/null
@@ -1,230 +0,0 @@
-from PIL import Image
-from PyQt6 import QtGui, QtCore, QtWidgets
-from PyQt6.QtGui import QColor
-import os
-import math
-import subprocess
-import logging
-
-from ..component import Component
-from ..toolkit.frame import BlankFrame, scale
-from ..toolkit import checkOutput
-from ..toolkit.ffmpeg import (
- openPipe,
- closePipe,
- getAudioDuration,
- FfmpegVideo,
- exampleSound,
-)
-
-
-log = logging.getLogger("AVP.Components.Waveform")
-
-
-class Component(Component):
- name = "Waveform"
- version = "1.0.0"
-
- 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)
-
- self.trackWidgets(
- {
- "color": self.page.lineEdit_color,
- "mode": self.page.comboBox_mode,
- "amplitude": self.page.comboBox_amplitude,
- "x": self.page.spinBox_x,
- "y": self.page.spinBox_y,
- "mirror": self.page.checkBox_mirror,
- "scale": self.page.spinBox_scale,
- "opacity": self.page.spinBox_opacity,
- "compress": self.page.checkBox_compress,
- "mono": self.page.checkBox_mono,
- },
- colorWidgets={
- "color": self.page.pushButton_color,
- },
- relativeWidgets=[
- "x",
- "y",
- ],
- )
-
- def previewRender(self):
- self.updateChunksize()
- frame = self.getPreviewFrame(self.width, self.height)
- if not frame:
- return BlankFrame(self.width, self.height)
- else:
- return frame
-
- def preFrameRender(self, **kwargs):
- super().preFrameRender(**kwargs)
- self.updateChunksize()
- w, h = scale(self.scale, self.width, self.height, str)
- self.video = FfmpegVideo(
- inputPath=self.audioFile,
- filter_=self.makeFfmpegFilter(),
- width=w,
- height=h,
- chunkSize=self.chunkSize,
- frameRate=int(self.settings.value("outputFrameRate")),
- parent=self.parent,
- component=self,
- debug=True,
- )
-
- def frameRender(self, frameNo):
- if FfmpegVideo.threadError is not None:
- raise FfmpegVideo.threadError
- return self.finalizeFrame(self.video.frame(frameNo))
-
- def postFrameRender(self):
- closePipe(self.video.pipe)
-
- def getPreviewFrame(self, width, height):
- genericPreview = self.settings.value("pref_genericPreview")
- startPt = 0
- if not genericPreview:
- inputFile = self.parent.lineEdit_audioFile.text()
- if not inputFile or not os.path.exists(inputFile):
- return
- duration = getAudioDuration(inputFile)
- if not duration:
- return
- startPt = duration / 3
- if startPt + 3 > duration:
- startPt += startPt - 3
-
- command = [
- self.core.FFMPEG_BIN,
- "-thread_queue_size",
- "512",
- "-r",
- str(self.settings.value("outputFrameRate")),
- "-ss",
- "{0:.3f}".format(startPt),
- "-i",
- self.core.junkStream if genericPreview else inputFile,
- "-f",
- "image2pipe",
- "-pix_fmt",
- "rgba",
- ]
- command.extend(self.makeFfmpegFilter(preview=True, startPt=startPt))
- command.extend(
- [
- "-an",
- "-s:v",
- "%sx%s" % scale(self.scale, self.width, self.height, str),
- "-codec:v",
- "rawvideo",
- "-",
- "-frames:v",
- "1",
- ]
- )
- if self.core.logEnabled:
- logFilename = os.path.join(
- self.core.logDir, "preview_%s.log" % str(self.compPos)
- )
- log.debug("Creating ffmpeg log at %s", logFilename)
- with open(logFilename, "w") as logf:
- logf.write(" ".join(command) + "\n\n")
- with open(logFilename, "a") as logf:
- pipe = openPipe(
- command,
- stdin=subprocess.DEVNULL,
- stdout=subprocess.PIPE,
- stderr=logf,
- bufsize=10**8,
- )
- else:
- pipe = openPipe(
- command,
- stdin=subprocess.DEVNULL,
- stdout=subprocess.PIPE,
- stderr=subprocess.DEVNULL,
- bufsize=10**8,
- )
- byteFrame = pipe.stdout.read(self.chunkSize)
- closePipe(pipe)
-
- frame = self.finalizeFrame(byteFrame)
- return frame
-
- def makeFfmpegFilter(self, preview=False, startPt=0):
- w, h = scale(self.scale, self.width, self.height, str)
- if self.amplitude == 0:
- amplitude = "lin"
- elif self.amplitude == 1:
- amplitude = "log"
- elif self.amplitude == 2:
- amplitude = "sqrt"
- elif self.amplitude == 3:
- amplitude = "cbrt"
- hexcolor = QColor(*self.color).name()
- opacity = "{0:.1f}".format(self.opacity / 100)
- genericPreview = self.settings.value("pref_genericPreview")
- if self.mode < 3:
- filter_ = (
- "showwaves="
- f'r={str(self.settings.value("outputFrameRate"))}:'
- f's={self.settings.value("outputWidth")}x{self.settings.value("outputHeight")}:'
- f'mode={self.page.comboBox_mode.currentText().lower() if self.mode != 3 else "p2p"}:'
- f"colors={hexcolor}@{opacity}:scale={amplitude}"
- )
- 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"colors={hexcolor}@{opacity}"
- f":ascale={amplitude}:fscale={'log' if self.mono else 'lin'}"
- )
-
- baselineHeight = int(self.height * (4 / 1080))
- return [
- "-filter_complex",
- f"{exampleSound('wave', extra='') if preview and genericPreview else '[0:a] '}"
- f"{'compand=gain=4,' if self.compress else ''}"
- f"{'aformat=channel_layouts=mono,' if self.mono and self.mode < 3 else ''}"
- f"{filter_}"
- f"{', drawbox=x=(iw-w)/2:y=(ih-h)/2:w=iw:h=%s:color=%s@%s' % (baselineHeight, hexcolor, opacity) if self.mode < 2 else ''}"
- f"{', hflip' if self.mirror else''}"
- " [v1]; "
- "[v1] scale=%s:%s%s [v]"
- % (
- w,
- h,
- ", trim=duration=%s" % "{0:.3f}".format(startPt + 3) if preview else "",
- ),
- "-map",
- "[v]",
- ]
-
- def updateChunksize(self):
- width, height = scale(self.scale, self.width, self.height, int)
- self.chunkSize = 4 * width * height
-
- def finalizeFrame(self, imageData):
- try:
- image = Image.frombytes(
- "RGBA",
- scale(self.scale, self.width, self.height, int),
- imageData,
- )
- self._image = image
- except ValueError:
- image = self._image
- if self.scale != 100 or self.x != 0 or self.y != 0:
- frame = BlankFrame(self.width, self.height)
- frame.paste(image, box=(self.x, self.y))
- else:
- frame = image
- return frame
diff --git a/src/components/waveform.ui b/src/components/waveform.ui
deleted file mode 100644
index 5473f33..0000000
--- a/src/components/waveform.ui
+++ /dev/null
@@ -1,383 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 586
- 197
-
-
-
-
- 0
- 0
-
-
-
-
- 0
- 197
-
-
-
- Form
-
-
- -
-
-
- 4
-
-
-
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 31
- 0
-
-
-
- Mode
-
-
-
- -
-
-
-
-
- Cline
-
-
- -
-
- Line
-
-
- -
-
- Point
-
-
- -
-
- Frequency Bar
-
-
- -
-
- Frequency Line
-
-
-
-
- -
-
-
- 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
-
-
-
-
-
-
-
- -
-
-
-
-
-
- Color
-
-
-
- -
-
-
- Qt::ImhNone
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 32
- 32
-
-
-
-
-
-
- false
-
-
- false
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
- Opacity
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- QAbstractSpinBox::UpDownArrows
-
-
- %
-
-
- 0
-
-
- 100
-
-
- 100
-
-
-
- -
-
-
- Scale
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- QAbstractSpinBox::UpDownArrows
-
-
- %
-
-
- 10
-
-
- 400
-
-
- 100
-
-
-
-
-
- -
-
-
-
-
-
- Compress
-
-
-
- -
-
-
- Mono
-
-
-
- -
-
-
- Mirror
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
- Amplitude
-
-
-
- -
-
-
-
-
- Linear
-
-
- -
-
- Logarithmic
-
-
- -
-
- Square root
-
-
- -
-
- Cubic root
-
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
-
-
--
cgit v1.2.3