diff options
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/color.py | 136 | ||||
| -rw-r--r-- | src/components/image.py | 71 | ||||
| -rw-r--r-- | src/components/life.py | 276 | ||||
| -rw-r--r-- | src/components/life.ui | 2 | ||||
| -rw-r--r-- | src/components/original.py | 189 | ||||
| -rw-r--r-- | src/components/sound.py | 54 | ||||
| -rw-r--r-- | src/components/spectrum.py | 300 | ||||
| -rw-r--r-- | src/components/text.py | 148 | ||||
| -rw-r--r-- | src/components/video.py | 178 | ||||
| -rw-r--r-- | src/components/waveform.py | 170 |
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; } <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></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: |
