aboutsummaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
authortassaron2026-01-11 14:29:58 -0500
committertassaron2026-01-11 14:29:58 -0500
commit669756b391d26661cf2e2a97a304e73343ef6655 (patch)
tree9cf2d4858c209bdab9f44d5c7f95c2a30b37f7a6 /src/components
parent9d45f7f1a986aaa5d3c084c7ae747442b94a61b1 (diff)
update to Qt 6 and Pillow 12
and yeah, I accidentally ran black on the codebase. I don't want to spend more free time fixing that. All of these changes are simple renames or removals, nothing too major.
Diffstat (limited to 'src/components')
-rw-r--r--src/components/color.py136
-rw-r--r--src/components/image.py71
-rw-r--r--src/components/life.py276
-rw-r--r--src/components/life.ui2
-rw-r--r--src/components/original.py189
-rw-r--r--src/components/sound.py54
-rw-r--r--src/components/spectrum.py300
-rw-r--r--src/components/text.py148
-rw-r--r--src/components/video.py178
-rw-r--r--src/components/waveform.py170
10 files changed, 862 insertions, 662 deletions
diff --git a/src/components/color.py b/src/components/color.py
index 8d0edd2..1f32c23 100644
--- a/src/components/color.py
+++ b/src/components/color.py
@@ -1,16 +1,16 @@
-from PyQt5 import QtGui
+from PyQt6 import QtGui
import logging
from ..component import Component
from ..toolkit.frame import BlankFrame, FloodFrame, FramePainter, PaintColor
-log = logging.getLogger('AVP.Components.Color')
+log = logging.getLogger("AVP.Components.Color")
class Component(Component):
- name = 'Color'
- version = '1.0.0'
+ name = "Color"
+ version = "1.0.0"
def widget(self, *args):
self.x = 0
@@ -20,48 +20,56 @@ class Component(Component):
# 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.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',
+ "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',
- ])
+ 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()
@@ -86,7 +94,7 @@ class Component(Component):
return self.drawFrame(self.width, self.height)
def properties(self):
- return ['static']
+ return ["static"]
def frameRender(self, frameNo):
log.debug("Color component is drawing frame #%s", frameNo)
@@ -96,8 +104,12 @@ class Component(Component):
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:
+ 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
@@ -120,19 +132,26 @@ class Component(Component):
if self.fillType == 1: # Linear Gradient
brush = QtGui.QLinearGradient(
- self.LG_start,
- self.LG_start,
- self.LG_end+width/3,
- self.LG_end)
+ 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(
- self.RG_start,
- self.RG_end,
- w, h,
- self.RG_centre)
-
- brush.setSpread(self.spread)
+ 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))
@@ -141,20 +160,17 @@ class Component(Component):
else:
brush.setColorAt(1.0, PaintColor(*self.color2))
image.setBrush(brush)
- image.drawRect(
- self.x, self.y,
- self.sizeWidth, self.sizeHeight
- )
+ 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')
+ print("Specify a color:\n color=255,255,255")
def command(self, arg):
- if '=' in arg:
- key, arg = arg.split('=', 1)
- if key == 'color':
+ 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/image.py b/src/components/image.py
index 42f9564..2393611 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -1,5 +1,5 @@
from PIL import Image, ImageDraw, ImageEnhance
-from PyQt5 import QtGui, QtCore, QtWidgets
+from PyQt6 import QtGui, QtCore, QtWidgets
import os
from ..component import Component
@@ -7,37 +7,39 @@ from ..toolkit.frame import BlankFrame
class Component(Component):
- name = 'Image'
- version = '1.0.1'
+ 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'
- ])
+ 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']
+ props = ["static"]
if not os.path.exists(self.imagePath):
- props.append('error')
+ props.append("error")
return props
def error(self):
@@ -57,17 +59,15 @@ class Component(Component):
# Modify image's appearance
if self.color != 100:
- image = ImageEnhance.Color(image).enhance(
- float(self.color / 100)
- )
+ image = ImageEnhance.Color(image).enhance(float(self.color / 100))
if self.mirror:
- image = image.transpose(Image.FLIP_LEFT_RIGHT)
+ image = image.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
if self.stretched and image.size != (width, height):
- image = image.resize((width, height), Image.ANTIALIAS)
+ 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.ANTIALIAS)
+ image = image.resize((newWidth, newHeight), Image.Resampling.LANCZOS)
# Paste image at correct position
frame.paste(image, box=(self.xPosition, self.yPosition))
@@ -79,8 +79,11 @@ class Component(Component):
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))
+ 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
@@ -88,9 +91,9 @@ class Component(Component):
self.mergeUndo = True
def command(self, arg):
- if '=' in arg:
- key, arg = arg.split('=', 1)
- if key == 'path' and os.path.exists(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)
@@ -102,7 +105,7 @@ class Component(Component):
super().command(arg)
def commandHelp(self):
- print('Load an image:\n path=/filepath/to/image.png')
+ print("Load an image:\n path=/filepath/to/image.png")
def savePreset(self):
# Maintain the illusion that the scale spinbox is one widget
diff --git a/src/components/life.py b/src/components/life.py
index e19ed36..b3c2c58 100644
--- a/src/components/life.py
+++ b/src/components/life.py
@@ -1,16 +1,21 @@
-from PyQt5 import QtGui, QtCore, QtWidgets
-from PyQt5.QtWidgets import QUndoCommand
+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'
+ name = "Conway's Game of Life"
+ version = "1.0.0"
def widget(self, *args):
super().widget(*args)
@@ -18,34 +23,50 @@ class Component(Component):
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)
- ])
+ 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.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,
@@ -56,7 +77,9 @@ class Component(Component):
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])
@@ -66,8 +89,11 @@ class Component(Component):
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))
+ 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
@@ -98,20 +124,20 @@ class Component(Component):
self.page.label_image.setVisible(False)
self.page.lineEdit_image.setVisible(False)
self.page.pushButton_pickImage.setVisible(False)
- enabled = (len(self.startingGrid) > 0)
+ 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
+ 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')
+ 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)
@@ -125,10 +151,8 @@ class Component(Component):
self.tickGrids = {0: self.startingGrid}
def properties(self):
- if self.customImg and (
- not self.image or not os.path.exists(self.image)
- ):
- return ['error']
+ if self.customImg and (not self.image or not os.path.exists(self.image)):
+ return ["error"]
return []
def error(self):
@@ -162,42 +186,47 @@ class Component(Component):
drawer = ImageDraw.Draw(frame)
rect = (
(drawPtX, drawPtY),
- (drawPtX + self.pxWidth, drawPtY + self.pxHeight)
+ (drawPtX + self.pxWidth, drawPtY + self.pxHeight),
)
shape = self.page.comboBox_shapeType.currentText().lower()
# Rectangle
- if shape == 'rectangle':
+ if shape == "rectangle":
drawer.rectangle(rect, fill=self.color)
# Elliptical
- elif shape == '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)))
+ (
+ 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))
+ (drawPtX + int(tenthX / 4), drawPtY + int(tenthY / 2)),
+ (
+ drawPtX + self.pxWidth - int(tenthX / 4),
+ drawPtY + self.pxHeight - int(tenthY / 2),
+ ),
)
# Circle
- if shape == 'circle':
+ if shape == "circle":
drawer.ellipse(outlineShape, fill=self.color)
drawer.ellipse(smallerShape, fill=(0, 0, 0, 0))
# Lilypad
- elif shape == 'lilypad':
+ elif shape == "lilypad":
drawer.pieslice(smallerShape, 290, 250, fill=self.color)
# Pie
- elif shape == 'pie':
+ elif shape == "pie":
drawer.pieslice(outlineShape, 35, 320, fill=self.color)
hX, hY = scale(50, self.pxWidth, self.pxHeight, int) # halfline
@@ -205,12 +234,15 @@ class Component(Component):
qX, qY = scale(20, self.pxWidth, self.pxHeight, int) # quarterline
# Path
- if shape == 'path':
+ if shape == "path":
drawer.ellipse(rect, fill=self.color)
rects = {
direction: False
for direction in (
- 'up', 'down', 'left', 'right',
+ "up",
+ "down",
+ "left",
+ "right",
)
}
for cell in self.nearbyCoords(x, y):
@@ -218,60 +250,59 @@ class Component(Component):
continue
if cell[0] == x:
if cell[1] < y:
- rects['up'] = True
+ rects["up"] = True
if cell[1] > y:
- rects['down'] = True
+ rects["down"] = True
if cell[1] == y:
if cell[0] < x:
- rects['left'] = True
+ rects["left"] = True
if cell[0] > x:
- rects['right'] = True
+ rects["right"] = True
for direction, rect in rects.items():
if rect:
- if direction == 'up':
+ if direction == "up":
sect = (
(drawPtX, drawPtY),
- (drawPtX + self.pxWidth, drawPtY + hY)
+ (drawPtX + self.pxWidth, drawPtY + hY),
)
- elif direction == 'down':
+ elif direction == "down":
sect = (
(drawPtX, drawPtY + hY),
- (drawPtX + self.pxWidth,
- drawPtY + self.pxHeight)
+ (
+ drawPtX + self.pxWidth,
+ drawPtY + self.pxHeight,
+ ),
)
- elif direction == 'left':
+ elif direction == "left":
sect = (
(drawPtX, drawPtY),
- (drawPtX + hX,
- drawPtY + self.pxHeight)
+ (drawPtX + hX, drawPtY + self.pxHeight),
)
- elif direction == 'right':
+ elif direction == "right":
sect = (
(drawPtX + hX, drawPtY),
- (drawPtX + self.pxWidth,
- drawPtY + self.pxHeight)
+ (
+ drawPtX + self.pxWidth,
+ drawPtY + self.pxHeight,
+ ),
)
drawer.rectangle(sect, fill=self.color)
# Duck
- elif shape == 'duck':
+ elif shape == "duck":
duckHead = (
(drawPtX + qX, drawPtY + qY),
- (drawPtX + int(qX * 3), drawPtY + int(tY * 2))
+ (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]
+ (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)
+ (drawPtX + int(tX * 2), drawPtY + self.pxHeight),
)
drawer.ellipse(duckBody, fill=self.color)
drawer.ellipse(duckHead, fill=self.color)
@@ -279,11 +310,16 @@ class Component(Component):
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))
+ 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))
@@ -291,21 +327,15 @@ class Component(Component):
def slantLine(difference):
return (
- (drawPtX + difference), (drawPtY + self.pxHeight - qY)
+ (drawPtX + difference),
+ (drawPtY + self.pxHeight - qY),
), (
- (drawPtX + hX), (drawPtY + hY)
+ (drawPtX + hX),
+ (drawPtY + hY),
)
- drawer.line(
- slantLine(qX),
- fill=self.color,
- width=tenthX
- )
- drawer.line(
- slantLine(self.pxWidth - qX),
- fill=self.color,
- width=tenthX
- )
+ 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
@@ -331,44 +361,38 @@ class Component(Component):
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)),
+ ((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)),
+ ((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
- }
+ 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
- ):
+ -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:
@@ -376,7 +400,8 @@ class Component(Component):
# Find positions around living cells which must be checked for reproduction
potentialNewCells = {
- coordTup for origin in lastGrid
+ coordTup
+ for origin in lastGrid
for coordTup in list(self.nearbyCoords(*origin))
}
# Check for reproduction
@@ -392,11 +417,11 @@ class Component(Component):
def savePreset(self):
pr = super().savePreset()
- pr['GRID'] = sorted(self.startingGrid)
+ pr["GRID"] = sorted(self.startingGrid)
return pr
def loadPreset(self, pr, *args):
- self.startingGrid = set(pr['GRID'])
+ self.startingGrid = set(pr["GRID"])
if self.startingGrid:
for widget in self.shiftButtons:
widget.setEnabled(True)
@@ -414,15 +439,17 @@ class Component(Component):
class ClickGrid(QUndoCommand):
- def __init__(self, comp, pos, id_):
- super().__init__(
- "click %s component #%s" % (comp.name, comp.compPos))
+ def __init__(self, comp, pos, button):
+ super().__init__("click %s component #%s" % (comp.name, comp.compPos))
self.comp = comp
self.pos = [pos]
- self.id_ = id_
+ if button == QtCore.Qt.MouseButton.RightButton:
+ self.button = 2
+ else:
+ self.button = 1
def id(self):
- return self.id_
+ return self.button
def mergeWith(self, other):
self.pos.extend(other.pos)
@@ -439,21 +466,21 @@ class ClickGrid(QUndoCommand):
self.comp.update(auto=True)
def redo(self):
- if self.id_ == 1: # Left-click
+ if self.button == 1: # Left-click
self.add()
- elif self.id_ == 2: # Right-click
+ elif self.button == 2: # Right-click
self.remove()
def undo(self):
- if self.id_ == 1: # Left-click
+ if self.button == 1: # Left-click
self.remove()
- elif self.id_ == 2: # Right-click
+ 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))
+ super().__init__("change %s component #%s" % (comp.name, comp.compPos))
self.comp = comp
self.direction = direction
self.distance = 1
@@ -466,10 +493,7 @@ class ShiftGrid(QUndoCommand):
return True
def newGrid(self, Xchange, Ychange):
- return {
- (x + Xchange, y + Ychange)
- for x, y in self.comp.startingGrid
- }
+ return {(x + Xchange, y + Ychange) for x, y in self.comp.startingGrid}
def redo(self):
if self.direction == 0:
diff --git a/src/components/life.ui b/src/components/life.ui
index 3d6ad5a..30cf9d0 100644
--- a/src/components/life.ui
+++ b/src/components/life.ui
@@ -372,7 +372,7 @@ p, li { white-space: pre-wrap; }
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;- A cell with more than 3 neighbours will die from overpopulation.&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;- An empty space surrounded by 3 live cells will cause reproduction.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
- <property name="tabStopWidth">
+ <property name="tabStopDistance">
<number>80</number>
</property>
<property name="textInteractionFlags">
diff --git a/src/components/original.py b/src/components/original.py
index 289e982..fad797b 100644
--- a/src/components/original.py
+++ b/src/components/original.py
@@ -1,9 +1,5 @@
import numpy
from PIL import Image, ImageDraw
-from PyQt5 import QtGui, QtCore, QtWidgets
-from PyQt5.QtGui import QColor
-import os
-import time
from copy import copy
from ..component import Component
@@ -11,14 +7,14 @@ from ..toolkit.frame import BlankFrame
class Component(Component):
- name = 'Classic Visualizer'
- version = '1.0.0'
+ name = "Classic Visualizer"
+ version = "1.0.0"
def names(*args):
- return ['Original Audio Visualization']
+ return ["Original Audio Visualization"]
def properties(self):
- return ['pcm']
+ return ["pcm"]
def widget(self, *args):
self.scale = 20
@@ -31,23 +27,30 @@ class Component(Component):
self.page.comboBox_visLayout.addItem("Top")
self.page.comboBox_visLayout.setCurrentIndex(0)
- self.page.lineEdit_visColor.setText('255,255,255')
-
- self.trackWidgets({
- 'visColor': self.page.lineEdit_visColor,
- 'layout': self.page.comboBox_visLayout,
- 'scale': self.page.spinBox_scale,
- 'y': self.page.spinBox_y,
- 'smooth': self.page.spinBox_smooth,
- }, colorWidgets={
- 'visColor': self.page.pushButton_visColor,
- }, relativeWidgets=[
- 'y',
- ])
+ 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")
+ lambda x: float(self.scale) / 2500 * (x - 128) ** 2,
+ (255,),
+ dtype="int16",
+ )
return self.drawBars(
self.width, self.height, spectrum, self.visColor, self.layout
)
@@ -63,41 +66,53 @@ class Component(Component):
if self.canceled:
break
self.lastSpectrum = self.transformData(
- i, self.completeAudioArray, self.sampleSize,
- self.smoothConstantDown, self.smoothConstantUp,
- self.lastSpectrum)
+ i,
+ self.completeAudioArray,
+ self.sampleSize,
+ self.smoothConstantDown,
+ self.smoothConstantUp,
+ self.lastSpectrum,
+ )
self.spectrumArray[i] = copy(self.lastSpectrum)
- progress = int(100*(i/len(self.completeAudioArray)))
+ progress = int(100 * (i / len(self.completeAudioArray)))
if progress >= 100:
progress = 100
- pStr = "Analyzing audio: "+str(progress)+'%'
+ 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.width,
+ self.height,
self.spectrumArray[arrayNo],
- self.visColor, self.layout)
+ self.visColor,
+ self.layout,
+ )
def transformData(
- self, i, completeAudioArray, sampleSize,
- smoothConstantDown, smoothConstantUp, lastSpectrum):
+ 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
+ data = completeAudioArray[i : i + sampleSize][::1] * window
paddedSampleSize = 2048
- paddedData = numpy.pad(
- data, (0, paddedSampleSize - sampleSize), 'constant')
+ paddedData = numpy.pad(data, (0, paddedSampleSize - sampleSize), "constant")
spectrum = numpy.fft.fft(paddedData)
sample_rate = 44100
- frequencies = numpy.fft.fftfreq(len(spectrum), 1./sample_rate)
+ frequencies = numpy.fft.fftfreq(len(spectrum), 1.0 / sample_rate)
- y = abs(spectrum[0:int(paddedSampleSize/2) - 1])
+ y = abs(spectrum[0 : int(paddedSampleSize / 2) - 1])
# filter the noise away
# y[y<80] = 0
@@ -106,22 +121,26 @@ class Component(Component):
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)
+ 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]
+ x = frequencies[0 : int(paddedSampleSize / 2) - 1]
return lastSpectrum
def drawBars(self, width, height, spectrum, color, layout):
- vH = height-height/8
+ vH = height - height / 8
bF = width / 64
bH = bF / 2
bQ = bF / 4
@@ -133,72 +152,92 @@ class Component(Component):
bP = height / 1200
for j in range(0, 63):
- draw.rectangle((
- bH + j * bF, vH+bQ, bH + j * bF + bF, vH + bQ -
- spectrum[j * 4] * bP - bH), fill=color2)
-
- draw.rectangle((
- bH + bQ + j * bF, vH, bH + bQ + j * bF + bH, vH -
- spectrum[j * 4] * bP), fill=color)
-
- imBottom = imTop.transpose(Image.FLIP_TOP_BOTTOM)
+ 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)
+ y = self.y - int(height / 100 * 43)
im.paste(imTop, (0, y), mask=imTop)
- y = self.y + int(height/100*43)
+ 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)
+ y = self.y + int(height / 100 * 10)
im.paste(imTop, (0, y), mask=imTop)
- y = self.y - int(height/100*10)
+ 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)
+ 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)
+ 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)
+ if "=" in arg:
+ key, arg = arg.split("=", 1)
try:
- if key == 'color':
+ if key == "color":
self.page.lineEdit_visColor.setText(arg)
return
- elif key == 'layout':
- if arg == 'classic':
+ elif key == "layout":
+ if arg == "classic":
self.page.comboBox_visLayout.setCurrentIndex(0)
- elif arg == 'split':
+ elif arg == "split":
self.page.comboBox_visLayout.setCurrentIndex(1)
- elif arg == 'bottom':
+ elif arg == "bottom":
self.page.comboBox_visLayout.setCurrentIndex(2)
- elif arg == 'top':
+ elif arg == "top":
self.page.comboBox_visLayout.setCurrentIndex(3)
return
- elif key == 'scale':
+ elif key == "scale":
arg = int(arg)
self.page.spinBox_scale.setValue(arg)
return
- elif key == 'y':
+ elif key == "y":
arg = int(arg)
self.page.spinBox_y.setValue(arg)
return
except ValueError:
- print('You must enter a number.')
+ 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')
+ 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/sound.py b/src/components/sound.py
index 118ea23..2df8e38 100644
--- a/src/components/sound.py
+++ b/src/components/sound.py
@@ -1,4 +1,4 @@
-from PyQt5 import QtGui, QtCore, QtWidgets
+from PyQt6 import QtGui, QtCore, QtWidgets
import os
from ..component import Component
@@ -6,25 +6,28 @@ from ..toolkit.frame import BlankFrame
class Component(Component):
- name = 'Sound'
- version = '1.0.0'
+ 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,
- })
+ 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']
+ props = ["static", "audio"]
if not os.path.exists(self.sound):
- props.append('error')
+ props.append("error")
return props
def error(self):
@@ -36,20 +39,22 @@ class Component(Component):
def audio(self):
params = {}
if self.delay != 0.0:
- params['adelay'] = '=%s' % str(int(self.delay * 1000.00))
+ 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'
+ 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)
+ 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))
+ 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
@@ -57,14 +62,13 @@ class Component(Component):
self.mergeUndo = True
def commandHelp(self):
- print('Path to audio file:\n path=/filepath/to/sound.ogg')
+ 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:
+ 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)
diff --git a/src/components/spectrum.py b/src/components/spectrum.py
index 30d5426..062ebc7 100644
--- a/src/components/spectrum.py
+++ b/src/components/spectrum.py
@@ -1,5 +1,5 @@
from PIL import Image
-from PyQt5 import QtGui, QtCore, QtWidgets
+from PyQt6 import QtGui, QtCore, QtWidgets
import os
import math
import subprocess
@@ -10,16 +10,20 @@ from ..component import Component
from ..toolkit.frame import BlankFrame, scale
from ..toolkit import checkOutput, connectWidget
from ..toolkit.ffmpeg import (
- openPipe, closePipe, getAudioDuration, FfmpegVideo, exampleSound
+ openPipe,
+ closePipe,
+ getAudioDuration,
+ FfmpegVideo,
+ exampleSound,
)
-log = logging.getLogger('AVP.Components.Spectrum')
+log = logging.getLogger("AVP.Components.Spectrum")
class Component(Component):
- name = 'Spectrum'
- version = '1.0.1'
+ name = "Spectrum"
+ version = "1.0.1"
def widget(self, *args):
self.previewFrame = None
@@ -30,34 +34,36 @@ class Component(Component):
self.previewSize = (214, 120)
self.previewPipe = None
- if hasattr(self.parent, 'lineEdit_audioFile'):
+ 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.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',
- ])
+ 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())
@@ -78,18 +84,18 @@ class Component(Component):
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)
+ 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)
+ log.warning("Spectrum #%s failed to create a preview frame" % self.compPos)
self.previewFrame = None
return BlankFrame(self.width, self.height)
else:
@@ -105,10 +111,12 @@ class Component(Component):
self.video = FfmpegVideo(
inputPath=self.audioFile,
filter_=self.makeFfmpegFilter(),
- width=w, height=h,
+ width=w,
+ height=h,
chunkSize=self.chunkSize,
frameRate=int(self.settings.value("outputFrameRate")),
- parent=self.parent, component=self,
+ parent=self.parent,
+ component=self,
)
def frameRender(self, frameNo):
@@ -133,38 +141,55 @@ class Component(Component):
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',
+ "-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',
- ])
+ 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.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
+ 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
+ command,
+ stdin=subprocess.DEVNULL,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.DEVNULL,
+ bufsize=10**8,
)
byteFrame = self.previewPipe.stdout.read(self.chunkSize)
closePipe(self.previewPipe)
@@ -173,132 +198,151 @@ class Component(Component):
return frame
def makeFfmpegFilter(self, preview=False, startPt=0):
- '''Makes final FFmpeg filter command'''
+ """Makes final FFmpeg filter command"""
def getFilterComplexCommand():
- '''Inner function that creates the final, complex part of the filter command'''
+ """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'''
+ """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'
+ amplitude = "sqrt"
elif self.amplitude == 1:
- amplitude = 'cbrt'
+ amplitude = "cbrt"
elif self.amplitude == 2:
- amplitude = '4thrt'
+ amplitude = "4thrt"
elif self.amplitude == 3:
- amplitude = '5thrt'
+ amplitude = "5thrt"
elif self.amplitude == 4:
- amplitude = 'lin'
+ amplitude = "lin"
elif self.amplitude == 5:
- amplitude = 'log'
+ 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'
+ 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'
+ amplitude = "log"
elif self.amplitude1 == 1:
- amplitude = 'lin'
+ amplitude = "lin"
if self.display == 0:
- display = 'log'
+ display = "log"
elif self.display == 1:
- display = 'sqrt'
+ display = "sqrt"
elif self.display == 2:
- display = 'cbrt'
+ display = "cbrt"
elif self.display == 3:
- display = 'lin'
+ display = "lin"
elif self.display == 4:
- display = 'rlog'
+ display = "rlog"
filter_ = (
f'ahistogram=r={str(self.settings.value("outputFrameRate"))}:'
- f's={w}x{h}:'
- 'dmode=separate:'
- f'ascale={amplitude}:'
- f'scale={display}'
+ 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'
+ amplitude = "log"
elif self.amplitude2 == 1:
- amplitude = 'sqrt'
+ amplitude = "sqrt"
elif self.amplitude2 == 2:
- amplitude = 'cbrt'
+ amplitude = "cbrt"
elif self.amplitude2 == 3:
- amplitude = 'lin'
+ amplitude = "lin"
m = self.page.comboBox_mode.currentText()
filter_ = (
- f'avectorscope=s={w}x{h}:'
+ 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)}'
+ 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'
+ 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 '
+ 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')
+ exampleSnd = exampleSound("freq")
elif self.filterType == 2 or self.filterType == 4:
- exampleSnd = exampleSound('stereo')
+ 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 ''
+ 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 ''
-
+ 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',
+ "-filter_complex",
getFilterComplexCommand(),
- '-map', '[v]',
+ "-map",
+ "[v]",
]
def updateChunksize(self):
@@ -311,9 +355,9 @@ class Component(Component):
def finalizeFrame(self, imageData):
try:
image = Image.frombytes(
- 'RGBA',
+ "RGBA",
scale(self.scale, self.width, self.height, int),
- imageData
+ imageData,
)
self._image = image
except ValueError:
diff --git a/src/components/text.py b/src/components/text.py
index 3238d2a..40c981a 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -1,22 +1,22 @@
from PIL import ImageEnhance, ImageFilter, ImageChops
-from PyQt5.QtGui import QColor, QFont
-from PyQt5 import QtGui, QtCore, QtWidgets
+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')
+log = logging.getLogger("AVP.Components.Text")
class Component(Component):
- name = 'Title Text'
- version = '1.0.1'
+ name = "Title Text"
+ version = "1.0.1"
def widget(self, *args):
super().widget(*args)
- self.title = 'Text'
+ self.title = "Text"
self.alignment = 1
self.titleFont = QFont()
self.fontSize = self.height / 13.5
@@ -29,33 +29,44 @@ class Component(Component):
self.page.lineEdit_title.setText(self.title)
self.page.pushButton_center.clicked.connect(self.centerXY)
- self.page.fontComboBox_titleFont.currentFontChanged.connect(self._sendUpdateSignal)
+ 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.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):
@@ -74,20 +85,23 @@ class Component(Component):
self.page.spinBox_shadBlur.setHidden(True)
def centerXY(self):
- self.setRelativeWidget('xPosition', 0.5)
- self.setRelativeWidget('yPosition', 0.521)
+ self.setRelativeWidget("xPosition", 0.5)
+ self.setRelativeWidget("yPosition", 0.521)
def getXY(self):
- '''Returns true x, y after considering alignment settings'''
+ """Returns true x, y after considering alignment settings"""
fm = QtGui.QFontMetrics(self.titleFont)
- x = self.pixelValForAttr('xPosition')
+ text_width = fm.boundingRect(self.title).width()
+ x = self.pixelValForAttr("xPosition")
- if self.alignment == 1: # Middle
- offset = int(fm.width(self.title)/2)
- x -= offset
- if self.alignment == 2: # Right
- offset = fm.width(self.title)
- x -= offset
+ 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
@@ -95,21 +109,21 @@ class Component(Component):
super().loadPreset(pr, *args)
font = QFont()
- font.fromString(pr['titleFont'])
+ font.fromString(pr["titleFont"])
self.page.fontComboBox_titleFont.setCurrentFont(font)
def savePreset(self):
saveValueStore = super().savePreset()
- saveValueStore['titleFont'] = self.titleFont.toString()
+ saveValueStore["titleFont"] = self.titleFont.toString()
return saveValueStore
def previewRender(self):
return self.addText(self.width, self.height)
def properties(self):
- props = ['static']
+ props = ["static"]
if not self.title:
- props.append('error')
+ props.append("error")
return props
def error(self):
@@ -121,26 +135,26 @@ class Component(Component):
def addText(self, width, height):
font = self.titleFont
font.setPixelSize(self.fontSize)
- font.setStyle(QFont.StyleNormal)
- font.setWeight(QFont.Normal)
- font.setCapitalization(QFont.MixedCase)
+ font.setStyle(QFont.Style.StyleNormal)
+ font.setWeight(QFont.Weight.Normal)
+ font.setCapitalization(QFont.Capitalization.MixedCase)
if self.fontStyle == 1:
- font.setWeight(QFont.DemiBold)
+ font.setWeight(QFont.Weight.DemiBold)
if self.fontStyle == 2:
- font.setWeight(QFont.Bold)
+ font.setWeight(QFont.Weight.Bold)
elif self.fontStyle == 3:
- font.setStyle(QFont.StyleItalic)
+ font.setStyle(QFont.Style.StyleItalic)
elif self.fontStyle == 4:
- font.setWeight(QFont.Bold)
- font.setStyle(QFont.StyleItalic)
+ font.setWeight(QFont.Weight.Bold)
+ font.setStyle(QFont.Style.StyleItalic)
elif self.fontStyle == 5:
- font.setStyle(QFont.StyleOblique)
+ font.setStyle(QFont.Style.StyleOblique)
elif self.fontStyle == 6:
- font.setCapitalization(QFont.SmallCaps)
+ font.setCapitalization(QFont.Capitalization.SmallCaps)
image = FramePainter(width, height)
x, y = self.getXY()
- log.debug('Text position translates to %s, %s', x, y)
+ log.debug("Text position translates to %s, %s", x, y)
if self.stroke > 0:
outliner = QtGui.QPainterPathStroker()
outliner.setWidth(self.stroke)
@@ -149,16 +163,16 @@ class Component(Component):
# PathStroker ignores smallcaps so we need this weird hack
path.addText(x, y, font, self.title[0])
fm = QtGui.QFontMetrics(font)
- newX = x + fm.width(self.title[0])
+ newX = x + fm.boundingRect(self.title[0]).width()
strokeFont = self.page.fontComboBox_titleFont.currentFont()
- strokeFont.setCapitalization(QFont.SmallCaps)
+ strokeFont.setCapitalization(QFont.Capitalization.SmallCaps)
strokeFont.setPixelSize(int((self.fontSize / 7) * 5))
- strokeFont.setLetterSpacing(QFont.PercentageSpacing, 139)
+ 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.NoPen)
+ image.setPen(QtCore.Qt.PenStyle.NoPen)
image.setBrush(PaintColor(*self.strokeColor))
image.drawPath(path)
@@ -178,27 +192,27 @@ class Component(Component):
return frame
def commandHelp(self):
- print('Enter a string to use as centred white text:')
+ 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')
+ 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':
+ if "=" in arg:
+ key, arg = arg.split("=", 1)
+ if key == "color":
self.page.lineEdit_textColor.setText(arg)
return
- elif key == 'size':
+ elif key == "size":
self.page.spinBox_fontSize.setValue(int(arg))
return
- elif key == 'x':
+ elif key == "x":
self.page.spinBox_xTextAlign.setValue(int(arg))
return
- elif key == 'y':
+ elif key == "y":
self.page.spinBox_yTextAlign.setValue(int(arg))
return
- elif key == 'title':
+ elif key == "title":
self.page.lineEdit_title.setText(arg)
return
super().command(arg)
diff --git a/src/components/video.py b/src/components/video.py
index 60ca800..65a05af 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -1,5 +1,5 @@
from PIL import Image
-from PyQt5 import QtGui, QtCore, QtWidgets
+from PyQt6 import QtGui, QtCore, QtWidgets
import os
import math
import subprocess
@@ -11,15 +11,15 @@ from ..toolkit.ffmpeg import openPipe, closePipe, testAudioStream, FfmpegVideo
from ..toolkit import checkOutput
-log = logging.getLogger('AVP.Components.Video')
+log = logging.getLogger("AVP.Components.Video")
class Component(Component):
- name = 'Video'
- version = '1.0.0'
+ name = "Video"
+ version = "1.0.0"
def widget(self, *args):
- self.videoPath = ''
+ self.videoPath = ""
self.badAudio = False
self.x = 0
self.y = 0
@@ -27,23 +27,28 @@ class Component(Component):
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',
- ])
+ 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():
@@ -64,7 +69,7 @@ class Component(Component):
def properties(self):
props = []
outputFile = None
- if hasattr(self.parent, 'lineEdit_outputFile'):
+ if hasattr(self.parent, "lineEdit_outputFile"):
# check only happens in GUI mode
outputFile = self.parent.lineEdit_outputFile.text()
@@ -72,34 +77,42 @@ class Component(Component):
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):
+ 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.")
+ 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)
+ 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
+ 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:
@@ -112,8 +125,10 @@ class Component(Component):
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)
+ self.page,
+ "Choose Video",
+ imgDir,
+ "Video Files (%s)" % " ".join(self.core.videoFormats),
)
if filename:
self.settings.setValue("componentDir", os.path.dirname(filename))
@@ -127,33 +142,50 @@ class Component(Component):
command = [
self.core.FFMPEG_BIN,
- '-thread_queue_size', '512',
- '-i', self.videoPath,
- '-f', 'image2pipe',
- '-pix_fmt', 'rgba',
+ "-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',
- ])
+ 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:
+ 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
+ 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
+ command,
+ stdin=subprocess.DEVNULL,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.DEVNULL,
+ bufsize=10**8,
)
byteFrame = pipe.stdout.read(self.chunkSize)
@@ -164,9 +196,8 @@ class Component(Component):
def makeFfmpegFilter(self):
return [
- '-filter_complex',
- '[0:v] scale=%s:%s' % scale(
- self.scale, self.width, self.height, str),
+ "-filter_complex",
+ "[0:v] scale=%s:%s" % scale(self.scale, self.width, self.height, str),
]
def updateChunksize(self):
@@ -177,10 +208,10 @@ class Component(Component):
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:
+ 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)
@@ -188,7 +219,7 @@ class Component(Component):
else:
print("Not a supported video format")
quit(1)
- elif arg == 'audio':
+ elif arg == "audio":
if not self.page.lineEdit_video.text():
print("'audio' option must follow a video selection")
quit(1)
@@ -197,28 +228,25 @@ class Component(Component):
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')
+ 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)
+ image = Image.frombytes("RGBA", (self.width, self.height), imageData)
else:
image = Image.frombytes(
- 'RGBA',
+ "RGBA",
scale(self.scale, self.width, self.height, int),
- imageData)
+ 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:
+ 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:
diff --git a/src/components/waveform.py b/src/components/waveform.py
index eef6de0..7dc0b99 100644
--- a/src/components/waveform.py
+++ b/src/components/waveform.py
@@ -1,6 +1,6 @@
from PIL import Image
-from PyQt5 import QtGui, QtCore, QtWidgets
-from PyQt5.QtGui import QColor
+from PyQt6 import QtGui, QtCore, QtWidgets
+from PyQt6.QtGui import QColor
import os
import math
import subprocess
@@ -10,44 +10,51 @@ from ..component import Component
from ..toolkit.frame import BlankFrame, scale
from ..toolkit import checkOutput
from ..toolkit.ffmpeg import (
- openPipe, closePipe, getAudioDuration, FfmpegVideo, exampleSound
+ openPipe,
+ closePipe,
+ getAudioDuration,
+ FfmpegVideo,
+ exampleSound,
)
-log = logging.getLogger('AVP.Components.Waveform')
+log = logging.getLogger("AVP.Components.Waveform")
class Component(Component):
- name = 'Waveform'
- version = '1.0.0'
+ 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',
- ])
+ 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()
@@ -64,10 +71,13 @@ class Component(Component):
self.video = FfmpegVideo(
inputPath=self.audioFile,
filter_=self.makeFfmpegFilter(),
- width=w, height=h,
+ width=w,
+ height=h,
chunkSize=self.chunkSize,
frameRate=int(self.settings.value("outputFrameRate")),
- parent=self.parent, component=self, debug=True,
+ parent=self.parent,
+ component=self,
+ debug=True,
)
def frameRender(self, frameNo):
@@ -94,37 +104,54 @@ class Component(Component):
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',
+ "-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',
- ])
+ 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:
+ 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
+ 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
+ command,
+ stdin=subprocess.DEVNULL,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.DEVNULL,
+ bufsize=10**8,
)
byteFrame = pipe.stdout.read(self.chunkSize)
closePipe(pipe)
@@ -135,35 +162,35 @@ 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'
+ amplitude = "lin"
elif self.amplitude == 1:
- amplitude = 'log'
+ amplitude = "log"
elif self.amplitude == 2:
- amplitude = 'sqrt'
+ amplitude = "sqrt"
elif self.amplitude == 3:
- amplitude = 'cbrt'
+ 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='
+ "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}'
+ 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"colors={hexcolor}@{opacity}"
f":ascale={amplitude}:fscale={'log' if self.mono else 'lin'}"
)
baselineHeight = int(self.height * (4 / 1080))
return [
- '-filter_complex',
+ "-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 ''}"
@@ -171,12 +198,14 @@ class Component(Component):
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 '',
+ "[v1] scale=%s:%s%s [v]"
+ % (
+ w,
+ h,
+ ", trim=duration=%s" % "{0:.3f}".format(startPt + 3) if preview else "",
),
- '-map', '[v]',
+ "-map",
+ "[v]",
]
def updateChunksize(self):
@@ -186,15 +215,14 @@ class Component(Component):
def finalizeFrame(self, imageData):
try:
image = Image.frombytes(
- 'RGBA',
+ "RGBA",
scale(self.scale, self.width, self.height, int),
- imageData
+ imageData,
)
self._image = image
except ValueError:
image = self._image
- if self.scale != 100 \
- or self.x != 0 or self.y != 0:
+ 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: