diff options
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/color.py | 99 | ||||
| -rw-r--r-- | src/components/image.py | 33 | ||||
| -rw-r--r-- | src/components/original.py | 42 | ||||
| -rw-r--r-- | src/components/spectrum.py | 284 | ||||
| -rw-r--r-- | src/components/spectrum.ui | 946 | ||||
| -rw-r--r-- | src/components/text.py | 53 | ||||
| -rw-r--r-- | src/components/text.ui | 117 | ||||
| -rw-r--r-- | src/components/video.py | 239 | ||||
| -rw-r--r-- | src/components/waveform.py | 194 | ||||
| -rw-r--r-- | src/components/waveform.ui | 383 |
10 files changed, 2020 insertions, 370 deletions
diff --git a/src/components/color.py b/src/components/color.py index 2abd79a..5d1233e 100644 --- a/src/components/color.py +++ b/src/components/color.py @@ -6,7 +6,6 @@ import os from component import Component from toolkit.frame import BlankFrame, FloodFrame, FramePainter, PaintColor -from toolkit import rgbFromString, pickColor class Component(Component): @@ -14,25 +13,12 @@ class Component(Component): version = '1.0.0' def widget(self, *args): - self.color1 = (0, 0, 0) - self.color2 = (133, 133, 133) self.x = 0 self.y = 0 super().widget(*args) - self.page.lineEdit_color1.setText('%s,%s,%s' % self.color1) - self.page.lineEdit_color2.setText('%s,%s,%s' % self.color2) - - btnStyle1 = "QPushButton { background-color : %s; outline: none; }" \ - % QColor(*self.color1).name() - - btnStyle2 = "QPushButton { background-color : %s; outline: none; }" \ - % QColor(*self.color2).name() - - self.page.pushButton_color1.setStyleSheet(btnStyle1) - self.page.pushButton_color2.setStyleSheet(btnStyle2) - self.page.pushButton_color1.clicked.connect(lambda: self.pickColor(1)) - self.page.pushButton_color2.clicked.connect(lambda: self.pickColor(2)) + self.page.lineEdit_color1.setText('0,0,0') + self.page.lineEdit_color2.setText('133,133,133') # disable color #2 until non-default 'fill' option gets changed self.page.lineEdit_color2.setDisabled(True) @@ -51,31 +37,36 @@ class Component(Component): 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, - }, presetNames={ - 'sizeWidth': 'width', - 'sizeHeight': 'height', - } - ) + 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): - self.color1 = rgbFromString(self.page.lineEdit_color1.text()) - self.color2 = rgbFromString(self.page.lineEdit_color2.text()) - fillType = self.page.comboBox_fill.currentIndex() if fillType == 0: self.page.lineEdit_color2.setEnabled(False) @@ -161,36 +152,6 @@ class Component(Component): return image.finalize() - def loadPreset(self, pr, *args): - super().loadPreset(pr, *args) - - self.page.lineEdit_color1.setText('%s,%s,%s' % pr['color1']) - self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2']) - - btnStyle1 = "QPushButton { background-color : %s; outline: none; }" \ - % QColor(*pr['color1']).name() - btnStyle2 = "QPushButton { background-color : %s; outline: none; }" \ - % QColor(*pr['color2']).name() - self.page.pushButton_color1.setStyleSheet(btnStyle1) - self.page.pushButton_color2.setStyleSheet(btnStyle2) - - def savePreset(self): - saveValueStore = super().savePreset() - saveValueStore['color1'] = self.color1 - saveValueStore['color2'] = self.color2 - return saveValueStore - - def pickColor(self, num): - RGBstring, btnStyle = pickColor() - if not RGBstring: - return - if num == 1: - self.page.lineEdit_color1.setText(RGBstring) - self.page.pushButton_color1.setStyleSheet(btnStyle) - else: - self.page.lineEdit_color2.setText(RGBstring) - self.page.pushButton_color2.setStyleSheet(btnStyle) - def commandHelp(self): print('Specify a color:\n color=255,255,255') diff --git a/src/components/image.py b/src/components/image.py index a96f127..1555541 100644 --- a/src/components/image.py +++ b/src/components/image.py @@ -13,23 +13,22 @@ class Component(Component): 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, - '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', - }, - ) + self.trackWidgets({ + 'imagePath': self.page.lineEdit_image, + 'scale': self.page.spinBox_scale, + '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) diff --git a/src/components/original.py b/src/components/original.py index 3d1a574..f886374 100644 --- a/src/components/original.py +++ b/src/components/original.py @@ -8,7 +8,6 @@ from copy import copy from component import Component from toolkit.frame import BlankFrame -from toolkit import rgbFromString, pickColor class Component(Component): @@ -18,8 +17,10 @@ class Component(Component): def names(*args): return ['Original Audio Visualization'] + def properties(self): + return ['pcm'] + def widget(self, *args): - self.visColor = (255, 255, 255) self.scale = 20 self.y = 0 super().widget(*args) @@ -30,34 +31,18 @@ class Component(Component): self.page.comboBox_visLayout.addItem("Top") self.page.comboBox_visLayout.setCurrentIndex(0) - self.page.lineEdit_visColor.setText('%s,%s,%s' % self.visColor) - self.page.pushButton_visColor.clicked.connect(lambda: self.pickColor()) - btnStyle = "QPushButton { background-color : %s; outline: none; }" \ - % QColor(*self.visColor).name() - self.page.pushButton_visColor.setStyleSheet(btnStyle) + 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, - }) - - def update(self): - self.visColor = rgbFromString(self.page.lineEdit_visColor.text()) - super().update() - - def loadPreset(self, pr, *args): - super().loadPreset(pr, *args) - - self.page.lineEdit_visColor.setText('%s,%s,%s' % pr['visColor']) - btnStyle = "QPushButton { background-color : %s; outline: none; }" \ - % QColor(*pr['visColor']).name() - self.page.pushButton_visColor.setStyleSheet(btnStyle) - - def savePreset(self): - saveValueStore = super().savePreset() - saveValueStore['visColor'] = self.visColor - return saveValueStore + }, colorWidgets={ + 'visColor': self.page.pushButton_visColor, + }, relativeWidgets=[ + 'y', + ]) def previewRender(self): spectrum = numpy.fromfunction( @@ -96,13 +81,6 @@ class Component(Component): self.spectrumArray[arrayNo], self.visColor, self.layout) - def pickColor(self): - RGBstring, btnStyle = pickColor() - if not RGBstring: - return - self.page.lineEdit_visColor.setText(RGBstring) - self.page.pushButton_visColor.setStyleSheet(btnStyle) - def transformData( self, i, completeAudioArray, sampleSize, smoothConstantDown, smoothConstantUp, lastSpectrum): diff --git a/src/components/spectrum.py b/src/components/spectrum.py new file mode 100644 index 0000000..666e20a --- /dev/null +++ b/src/components/spectrum.py @@ -0,0 +1,284 @@ +from PIL import Image +from PyQt5 import QtGui, QtCore, QtWidgets +import os +import math +import subprocess +import time + +from component import Component +from toolkit.frame import BlankFrame, scale +from toolkit import checkOutput, connectWidget +from toolkit.ffmpeg import ( + openPipe, closePipe, getAudioDuration, FfmpegVideo, exampleSound +) + + +class Component(Component): + name = 'Spectrum' + version = '1.0.0' + + def widget(self, *args): + self.previewFrame = None + super().widget(*args) + self._image = BlankFrame(self.width, self.height) + self.chunkSize = 4 * self.width * self.height + self.changedOptions = True + + if hasattr(self.parent, 'window'): + # update preview when audio file changes (if genericPreview is off) + self.parent.window.lineEdit_audioFile.textChanged.connect( + self.update + ) + + self.trackWidgets({ + 'filterType': self.page.comboBox_filterType, + 'window': self.page.comboBox_window, + 'mode': self.page.comboBox_mode, + 'amplitude': self.page.comboBox_amplitude0, + 'amplitude1': self.page.comboBox_amplitude1, + 'amplitude2': self.page.comboBox_amplitude2, + 'display': self.page.comboBox_display, + 'zoom': self.page.spinBox_zoom, + 'tc': self.page.spinBox_tc, + 'x': self.page.spinBox_x, + 'y': self.page.spinBox_y, + 'mirror': self.page.checkBox_mirror, + 'draw': self.page.checkBox_draw, + 'scale': self.page.spinBox_scale, + 'color': self.page.comboBox_color, + 'compress': self.page.checkBox_compress, + 'mono': self.page.checkBox_mono, + 'hue': self.page.spinBox_hue, + }, relativeWidgets=[ + 'x', 'y', + ]) + for widget in self._trackedWidgets.values(): + connectWidget(widget, lambda: self.changed()) + + def changed(self): + self.changedOptions = True + + def update(self): + self.page.stackedWidget.setCurrentIndex( + self.page.comboBox_filterType.currentIndex()) + super().update() + + def previewRender(self): + changedSize = self.updateChunksize() + if not changedSize \ + and not self.changedOptions \ + and self.previewFrame is not None: + return self.previewFrame + + frame = self.getPreviewFrame() + self.changedOptions = False + if not frame: + self.previewFrame = None + return BlankFrame(self.width, self.height) + else: + self.previewFrame = frame + return frame + + def preFrameRender(self, **kwargs): + super().preFrameRender(**kwargs) + self.updateChunksize() + w, h = scale(self.scale, self.width, self.height, str) + self.video = FfmpegVideo( + inputPath=self.audioFile, + filter_=self.makeFfmpegFilter(), + width=w, height=h, + chunkSize=self.chunkSize, + frameRate=int(self.settings.value("outputFrameRate")), + parent=self.parent, component=self, + ) + + def frameRender(self, frameNo): + if FfmpegVideo.threadError is not None: + raise FfmpegVideo.threadError + return self.finalizeFrame(self.video.frame(frameNo)) + + def postFrameRender(self): + closePipe(self.video.pipe) + + def getPreviewFrame(self): + genericPreview = self.settings.value("pref_genericPreview") + startPt = 0 + if not genericPreview: + inputFile = self.parent.window.lineEdit_audioFile.text() + if not inputFile or not os.path.exists(inputFile): + return + duration = getAudioDuration(inputFile) + if not duration: + return + startPt = duration / 3 + + command = [ + self.core.FFMPEG_BIN, + '-thread_queue_size', '512', + '-r', self.settings.value("outputFrameRate"), + '-ss', "{0:.3f}".format(startPt), + '-i', + os.path.join(self.core.wd, 'background.png') + 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', + ]) + logFilename = os.path.join( + self.core.dataDir, 'preview_%s.log' % str(self.compPos)) + with open(logFilename, 'w') as log: + log.write(" ".join(command) + '\n\n') + with open(logFilename, 'a') as log: + pipe = openPipe( + command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, + stderr=log, bufsize=10**8 + ) + byteFrame = pipe.stdout.read(self.chunkSize) + closePipe(pipe) + + frame = self.finalizeFrame(byteFrame) + return frame + + def makeFfmpegFilter(self, preview=False, startPt=0): + w, h = scale(self.scale, self.width, self.height, str) + color = self.page.comboBox_color.currentText().lower() + genericPreview = self.settings.value("pref_genericPreview") + + if self.filterType == 0: # Spectrum + if self.amplitude == 0: + amplitude = 'sqrt' + elif self.amplitude == 1: + amplitude = 'cbrt' + elif self.amplitude == 2: + amplitude = '4thrt' + elif self.amplitude == 3: + amplitude = '5thrt' + elif self.amplitude == 4: + amplitude = 'lin' + elif self.amplitude == 5: + amplitude = 'log' + filter_ = ( + 'showspectrum=s=%sx%s:slide=scroll:win_func=%s:' + 'color=%s:scale=%s,' + 'colorkey=color=black:similarity=0.1:blend=0.5' % ( + self.settings.value("outputWidth"), + self.settings.value("outputHeight"), + self.page.comboBox_window.currentText(), + color, amplitude, + ) + ) + elif self.filterType == 1: # Histogram + if self.amplitude1 == 0: + amplitude = 'log' + elif self.amplitude1 == 1: + amplitude = 'lin' + if self.display == 0: + display = 'log' + elif self.display == 1: + display = 'sqrt' + elif self.display == 2: + display = 'cbrt' + elif self.display == 3: + display = 'lin' + elif self.display == 4: + display = 'rlog' + filter_ = ( + 'ahistogram=r=%s:s=%sx%s:dmode=separate:ascale=%s:scale=%s' % ( + self.settings.value("outputFrameRate"), + self.settings.value("outputWidth"), + self.settings.value("outputHeight"), + amplitude, display + ) + ) + elif self.filterType == 2: # Vector Scope + if self.amplitude2 == 0: + amplitude = 'log' + elif self.amplitude2 == 1: + amplitude = 'sqrt' + elif self.amplitude2 == 2: + amplitude = 'cbrt' + elif self.amplitude2 == 3: + amplitude = 'lin' + m = self.page.comboBox_mode.currentText() + filter_ = ( + 'avectorscope=s=%sx%s:draw=%s:m=%s:scale=%s:zoom=%s' % ( + self.settings.value("outputWidth"), + self.settings.value("outputHeight"), + 'line'if self.draw else 'dot', + m, amplitude, str(self.zoom), + ) + ) + elif self.filterType == 3: # Musical Scale + filter_ = ( + 'showcqt=r=%s:s=%sx%s:count=30:text=0:tc=%s,' + 'colorkey=color=black:similarity=0.1:blend=0.5 ' % ( + self.settings.value("outputFrameRate"), + self.settings.value("outputWidth"), + self.settings.value("outputHeight"), + str(self.tc), + ) + ) + elif self.filterType == 4: # Phase + filter_ = ( + 'aphasemeter=r=%s:s=%sx%s: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 '% ( + self.settings.value("outputFrameRate"), + self.settings.value("outputWidth"), + self.settings.value("outputHeight"), + ) + ) + + return [ + '-filter_complex', + '%s%s%s%s [v1]; ' + '[v1] %sscale=%s:%s%s%s%s [v]' % ( + exampleSound() if preview and genericPreview else '[0:a] ', + 'compand=gain=4,' if self.compress else '', + 'aformat=channel_layouts=mono,' if self.mono else '', + filter_, + 'hflip, ' if self.mirror else '', + w, h, + ', hue=h=%s:s=10' % str(self.hue) if self.hue > 0 else '', + ', trim=start=%s:end=%s' % ( + "{0:.3f}".format(startPt + 12), + "{0:.3f}".format(startPt + 12.5) + ) if preview else '', + ', 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 '' + ), + '-map', '[v]', + ] + + def updateChunksize(self): + width, height = scale(self.scale, self.width, self.height, int) + oldChunkSize = int(self.chunkSize) + self.chunkSize = 4 * width * height + changed = self.chunkSize != oldChunkSize + return changed + + def finalizeFrame(self, imageData): + try: + image = Image.frombytes( + 'RGBA', + scale(self.scale, self.width, self.height, int), + imageData + ) + self._image = image + except ValueError: + image = self._image + if self.scale != 100 \ + or self.x != 0 or self.y != 0: + frame = BlankFrame(self.width, self.height) + frame.paste(image, box=(self.x, self.y)) + else: + frame = image + return frame diff --git a/src/components/spectrum.ui b/src/components/spectrum.ui new file mode 100644 index 0000000..c6a8a15 --- /dev/null +++ b/src/components/spectrum.ui @@ -0,0 +1,946 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Form</class> + <widget class="QWidget" name="Form"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>586</width> + <height>197</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>197</height> + </size> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="leftMargin"> + <number>4</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_5"/> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_8"> + <item> + <widget class="QLabel" name="label_4"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Type</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboBox_filterType"> + <item> + <property name="text"> + <string>Spectrum</string> + </property> + </item> + <item> + <property name="text"> + <string>Histogram</string> + </property> + </item> + <item> + <property name="text"> + <string>Vector Scope</string> + </property> + </item> + <item> + <property name="text"> + <string>Musical Scale</string> + </property> + </item> + <item> + <property name="text"> + <string>Phase</string> + </property> + </item> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_9"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>5</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label_xTitleAlign"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>X</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="spinBox_x"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>80</width> + <height>16777215</height> + </size> + </property> + <property name="minimum"> + <number>-10000</number> + </property> + <property name="maximum"> + <number>10000</number> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_yTitleAlign"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Y</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="spinBox_y"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>80</width> + <height>16777215</height> + </size> + </property> + <property name="baseSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="minimum"> + <number>-10000</number> + </property> + <property name="maximum"> + <number>10000</number> + </property> + <property name="value"> + <number>0</number> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_10"> + <item> + <widget class="QCheckBox" name="checkBox_compress"> + <property name="text"> + <string>Compress</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="checkBox_mono"> + <property name="text"> + <string>Mono</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="checkBox_mirror"> + <property name="text"> + <string>Mirror</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label_11"> + <property name="text"> + <string>Hue</string> + </property> + <property name="margin"> + <number>4</number> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="spinBox_hue"> + <property name="suffix"> + <string>° </string> + </property> + <property name="maximum"> + <number>359</number> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Scale</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="spinBox_scale"> + <property name="buttonSymbols"> + <enum>QAbstractSpinBox::UpDownArrows</enum> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>10</number> + </property> + <property name="maximum"> + <number>400</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QStackedWidget" name="stackedWidget"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="autoFillBackground"> + <bool>false</bool> + </property> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Plain</enum> + </property> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="QWidget" name="page"> + <widget class="QWidget" name="verticalLayoutWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>561</width> + <height>66</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="sizeConstraint"> + <enum>QLayout::SetMaximumSize</enum> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_9"> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <item> + <widget class="QLabel" name="label_textColor"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>31</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Window</string> + </property> + <property name="margin"> + <number>4</number> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboBox_window"> + <item> + <property name="text"> + <string>hann</string> + </property> + </item> + <item> + <property name="text"> + <string>gauss</string> + </property> + </item> + <item> + <property name="text"> + <string>tukey</string> + </property> + </item> + <item> + <property name="text"> + <string>dolph</string> + </property> + </item> + <item> + <property name="text"> + <string>cauchy</string> + </property> + </item> + <item> + <property name="text"> + <string>parzen</string> + </property> + </item> + <item> + <property name="text"> + <string>poisson</string> + </property> + </item> + <item> + <property name="text"> + <string>rect</string> + </property> + </item> + <item> + <property name="text"> + <string>bartlett</string> + </property> + </item> + <item> + <property name="text"> + <string>hanning</string> + </property> + </item> + <item> + <property name="text"> + <string>hamming</string> + </property> + </item> + <item> + <property name="text"> + <string>blackman</string> + </property> + </item> + <item> + <property name="text"> + <string>welch</string> + </property> + </item> + <item> + <property name="text"> + <string>flattop</string> + </property> + </item> + <item> + <property name="text"> + <string>bharris</string> + </property> + </item> + <item> + <property name="text"> + <string>bnuttall</string> + </property> + </item> + <item> + <property name="text"> + <string>lanczos</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QLabel" name="label_3"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Amplitude</string> + </property> + <property name="margin"> + <number>4</number> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboBox_amplitude0"> + <item> + <property name="text"> + <string>Square root</string> + </property> + </item> + <item> + <property name="text"> + <string>Cubic root</string> + </property> + </item> + <item> + <property name="text"> + <string>4thrt</string> + </property> + </item> + <item> + <property name="text"> + <string>5thrt</string> + </property> + </item> + <item> + <property name="text"> + <string>Linear</string> + </property> + </item> + <item> + <property name="text"> + <string>Logarithmic</string> + </property> + </item> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::MinimumExpanding</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>10</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Color </string> + </property> + <property name="margin"> + <number>4</number> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboBox_color"> + <item> + <property name="text"> + <string>Channel</string> + </property> + </item> + <item> + <property name="text"> + <string>Intensity</string> + </property> + </item> + <item> + <property name="text"> + <string>Rainbow</string> + </property> + </item> + <item> + <property name="text"> + <string>Moreland</string> + </property> + </item> + <item> + <property name="text"> + <string>Nebulae</string> + </property> + </item> + <item> + <property name="text"> + <string>Fire</string> + </property> + </item> + <item> + <property name="text"> + <string>Fiery</string> + </property> + </item> + <item> + <property name="text"> + <string>Fruit</string> + </property> + </item> + <item> + <property name="text"> + <string>Cool</string> + </property> + </item> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::MinimumExpanding</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>10</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + </widget> + <widget class="QWidget" name="page_2"> + <widget class="QWidget" name="verticalLayoutWidget_2"> + <property name="geometry"> + <rect> + <x>-1</x> + <y>-1</y> + <width>561</width> + <height>31</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="label_6"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Display Scale</string> + </property> + <property name="margin"> + <number>4</number> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboBox_display"> + <item> + <property name="text"> + <string>Logarithmic</string> + </property> + </item> + <item> + <property name="text"> + <string>Square root</string> + </property> + </item> + <item> + <property name="text"> + <string>Cubic root</string> + </property> + </item> + <item> + <property name="text"> + <string>Linear</string> + </property> + </item> + <item> + <property name="text"> + <string>Reverse Log</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QLabel" name="label_5"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Amplitude</string> + </property> + <property name="margin"> + <number>4</number> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboBox_amplitude1"> + <item> + <property name="text"> + <string>Logarithmic</string> + </property> + </item> + <item> + <property name="text"> + <string>Linear</string> + </property> + </item> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Minimum</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + </widget> + <widget class="QWidget" name="page_3"> + <widget class="QWidget" name="verticalLayoutWidget_3"> + <property name="geometry"> + <rect> + <x>-1</x> + <y>-1</y> + <width>585</width> + <height>64</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QLabel" name="label_9"> + <property name="text"> + <string>Mode</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboBox_mode"> + <item> + <property name="text"> + <string>lissajous</string> + </property> + </item> + <item> + <property name="text"> + <string>lissajous_xy</string> + </property> + </item> + <item> + <property name="text"> + <string>polar</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QLabel" name="label_7"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Amplitude</string> + </property> + <property name="margin"> + <number>4</number> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboBox_amplitude2"> + <item> + <property name="text"> + <string>Linear</string> + </property> + </item> + <item> + <property name="text"> + <string>Square root</string> + </property> + </item> + <item> + <property name="text"> + <string>Cubic root</string> + </property> + </item> + <item> + <property name="text"> + <string>Logarithmic</string> + </property> + </item> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_5"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <item> + <widget class="QLabel" name="label_8"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Zoom</string> + </property> + <property name="margin"> + <number>4</number> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="spinBox_zoom"> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>10</number> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="checkBox_draw"> + <property name="text"> + <string>Line</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_6"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + </widget> + <widget class="QWidget" name="page_4"> + <widget class="QWidget" name="verticalLayoutWidget_4"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>561</width> + <height>31</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout_6"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_7"> + <item> + <widget class="QLabel" name="label_10"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Timeclamp</string> + </property> + <property name="margin"> + <number>4</number> + </property> + </widget> + </item> + <item> + <widget class="QDoubleSpinBox" name="spinBox_tc"> + <property name="suffix"> + <string>s</string> + </property> + <property name="decimals"> + <number>3</number> + </property> + <property name="minimum"> + <double>0.002000000000000</double> + </property> + <property name="maximum"> + <double>1.000000000000000</double> + </property> + <property name="singleStep"> + <double>0.010000000000000</double> + </property> + <property name="value"> + <double>0.017000000000000</double> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_7"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + </widget> + <widget class="QWidget" name="page_5"> + <widget class="QWidget" name="verticalLayoutWidget_5"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>551</width> + <height>31</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout_7"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_11"/> + </item> + </layout> + </widget> + </widget> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>10</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/components/text.py b/src/components/text.py index 8a302ff..c3f3bdc 100644 --- a/src/components/text.py +++ b/src/components/text.py @@ -5,12 +5,11 @@ import os from component import Component from toolkit.frame import FramePainter -from toolkit import rgbFromString, pickColor class Component(Component): name = 'Title Text' - version = '1.0.0' + version = '1.0.1' def __init__(self, *args): super().__init__(*args) @@ -18,53 +17,47 @@ class Component(Component): def widget(self, *args): super().widget(*args) - height = int(self.settings.value('outputHeight')) - width = int(self.settings.value('outputWidth')) self.textColor = (255, 255, 255) self.title = 'Text' self.alignment = 1 - self.fontSize = height / 13.5 - fm = QtGui.QFontMetrics(self.titleFont) - self.xPosition = width / 2 - fm.width(self.title)/2 - self.yPosition = height / 2 * 1.036 + self.fontSize = self.height / 13.5 self.page.comboBox_textAlign.addItem("Left") self.page.comboBox_textAlign.addItem("Middle") self.page.comboBox_textAlign.addItem("Right") + self.page.comboBox_textAlign.setCurrentIndex(int(self.alignment)) self.page.lineEdit_textColor.setText('%s,%s,%s' % self.textColor) - self.page.pushButton_textColor.clicked.connect(self.pickColor) - btnStyle = "QPushButton { background-color : %s; outline: none; }" \ - % QColor(*self.textColor).name() - self.page.pushButton_textColor.setStyleSheet(btnStyle) - - self.page.lineEdit_title.setText(self.title) - self.page.comboBox_textAlign.setCurrentIndex(int(self.alignment)) self.page.spinBox_fontSize.setValue(int(self.fontSize)) - self.page.spinBox_xTextAlign.setValue(int(self.xPosition)) - self.page.spinBox_yTextAlign.setValue(int(self.yPosition)) + self.page.lineEdit_title.setText(self.title) + self.page.pushButton_center.clicked.connect(self.centerXY) self.page.fontComboBox_titleFont.currentFontChanged.connect( self.update ) + 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, - }) + }, colorWidgets={ + 'textColor': self.page.pushButton_textColor, + }, relativeWidgets=[ + 'xPosition', 'yPosition', 'fontSize', + ]) + self.centerXY() def update(self): self.titleFont = self.page.fontComboBox_titleFont.currentFont() - self.textColor = rgbFromString( - self.page.lineEdit_textColor.text()) - btnStyle = "QPushButton { background-color : %s; outline: none; }" \ - % QColor(*self.textColor).name() - self.page.pushButton_textColor.setStyleSheet(btnStyle) - super().update() + def centerXY(self): + self.setRelativeWidget('xPosition', 0.5) + self.setRelativeWidget('yPosition', 0.5) + def getXY(self): '''Returns true x, y after considering alignment settings''' fm = QtGui.QFontMetrics(self.titleFont) @@ -86,15 +79,10 @@ class Component(Component): font = QFont() font.fromString(pr['titleFont']) self.page.fontComboBox_titleFont.setCurrentFont(font) - self.page.lineEdit_textColor.setText('%s,%s,%s' % pr['textColor']) - btnStyle = "QPushButton { background-color : %s; outline: none; }" \ - % QColor(*pr['textColor']).name() - self.page.pushButton_textColor.setStyleSheet(btnStyle) def savePreset(self): saveValueStore = super().savePreset() saveValueStore['titleFont'] = self.titleFont.toString() - saveValueStore['textColor'] = self.textColor return saveValueStore def previewRender(self): @@ -122,13 +110,6 @@ class Component(Component): return image.finalize() - def pickColor(self): - RGBstring, btnStyle = pickColor() - if not RGBstring: - return - self.page.lineEdit_textColor.setText(RGBstring) - self.page.pushButton_textColor.setStyleSheet(btnStyle) - def commandHelp(self): print('Enter a string to use as centred white text:') print(' "title=User Error"') diff --git a/src/components/text.ui b/src/components/text.ui index 05e7f8e..f76979c 100644 --- a/src/components/text.ui +++ b/src/components/text.ui @@ -20,6 +20,36 @@ <number>4</number> </property> <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label_title"> + <property name="text"> + <string>Title</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="lineEdit_title"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Testing New GUI</string> + </property> + </widget> + </item> + </layout> + </item> + <item> <layout class="QHBoxLayout" name="horizontalLayout_8"> <item> <widget class="QLabel" name="label"> @@ -81,6 +111,9 @@ </item> <item> <widget class="QSpinBox" name="spinBox_fontSize"> + <property name="minimum"> + <number>1</number> + </property> <property name="maximum"> <number>500</number> </property> @@ -91,38 +124,6 @@ <item> <layout class="QHBoxLayout" name="horizontalLayout_12"> <item> - <widget class="QLabel" name="label_textLayout"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="text"> - <string>Text Layout</string> - </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="comboBox_textAlign"/> - </item> - <item> - <spacer name="horizontalSpacer_2"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeType"> - <enum>QSizePolicy::Fixed</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>5</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> <widget class="QLabel" name="label_textColor"> <property name="text"> <string>Text Color</string> @@ -130,6 +131,9 @@ </widget> </item> <item> + <widget class="QLineEdit" name="lineEdit_textColor"/> + </item> + <item> <widget class="QPushButton" name="pushButton_textColor"> <property name="maximumSize"> <size> @@ -149,7 +153,17 @@ </widget> </item> <item> - <widget class="QLineEdit" name="lineEdit_textColor"/> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> </item> </layout> </item> @@ -159,28 +173,41 @@ <number>0</number> </property> <item> - <widget class="QLabel" name="label_title"> + <widget class="QLabel" name="label_textLayout"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> <property name="text"> - <string>Title</string> + <string>Text Layout</string> </property> </widget> </item> <item> - <widget class="QLineEdit" name="lineEdit_title"> - <property name="sizePolicy"> - <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> + <widget class="QComboBox" name="comboBox_textAlign"/> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> </property> - <property name="minimumSize"> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> <size> - <width>0</width> - <height>0</height> + <width>5</width> + <height>20</height> </size> </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="pushButton_center"> <property name="text"> - <string>Testing New GUI</string> + <string>Center</string> </property> </widget> </item> diff --git a/src/components/video.py b/src/components/video.py index b2487c1..b6bdd52 100644 --- a/src/components/video.py +++ b/src/components/video.py @@ -1,103 +1,13 @@ -from PIL import Image, ImageDraw +from PIL import Image from PyQt5 import QtGui, QtCore, QtWidgets import os import math import subprocess -import signal -import threading -from queue import PriorityQueue -from component import Component, ComponentError -from toolkit.frame import BlankFrame -from toolkit.ffmpeg import testAudioStream -from toolkit import openPipe, checkOutput - - -class Video: - '''Opens a pipe to ffmpeg and stores a buffer of raw video frames.''' - - # error from the thread used to fill the buffer - threadError = None - - def __init__(self, **kwargs): - mandatoryArgs = [ - 'ffmpeg', # path to ffmpeg, usually self.core.FFMPEG_BIN - 'videoPath', - 'width', - 'height', - 'scale', # percentage scale - 'frameRate', # frames per second - 'chunkSize', # number of bytes in one frame - 'parent', # mainwindow object - 'component', # component object - ] - for arg in mandatoryArgs: - setattr(self, arg, kwargs[arg]) - - self.frameNo = -1 - self.currentFrame = 'None' - if 'loopVideo' in kwargs and kwargs['loopVideo']: - self.loopValue = '-1' - else: - self.loopValue = '0' - self.command = [ - self.ffmpeg, - '-thread_queue_size', '512', - '-r', str(self.frameRate), - '-stream_loop', self.loopValue, - '-i', self.videoPath, - '-f', 'image2pipe', - '-pix_fmt', 'rgba', - '-filter_complex', '[0:v] scale=%s:%s' % scale( - self.scale, self.width, self.height, str), - '-vcodec', 'rawvideo', '-', - ] - - self.frameBuffer = PriorityQueue() - self.frameBuffer.maxsize = self.frameRate - self.finishedFrames = {} - - self.thread = threading.Thread( - target=self.fillBuffer, - name='Video Frame-Fetcher' - ) - self.thread.daemon = True - self.thread.start() - - def frame(self, num): - while True: - if num in self.finishedFrames: - image = self.finishedFrames.pop(num) - return finalizeFrame( - self.component, image, self.width, self.height) - - i, image = self.frameBuffer.get() - self.finishedFrames[i] = image - self.frameBuffer.task_done() - - def fillBuffer(self): - self.pipe = openPipe( - self.command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL, bufsize=10**8 - ) - while True: - if self.parent.canceled: - break - self.frameNo += 1 - - # If we run out of frames, use the last good frame and loop. - try: - if len(self.currentFrame) == 0: - self.frameBuffer.put((self.frameNo-1, self.lastFrame)) - continue - except AttributeError: - Video.threadError = ComponentError(self.component, 'video') - break - - self.currentFrame = self.pipe.stdout.read(self.chunkSize) - if len(self.currentFrame) != 0: - self.frameBuffer.put((self.frameNo, self.currentFrame)) - self.lastFrame = self.currentFrame +from component import Component +from toolkit.frame import BlankFrame, scale +from toolkit.ffmpeg import openPipe, closePipe, testAudioStream, FfmpegVideo +from toolkit import checkOutput class Component(Component): @@ -106,30 +16,30 @@ class Component(Component): def widget(self, *args): self.videoPath = '' - self.badVideo = False self.badAudio = False self.x = 0 self.y = 0 self.loopVideo = False super().widget(*args) + self._image = BlankFrame(self.width, self.height) self.page.pushButton_video.clicked.connect(self.pickVideo) - self.trackWidgets( - { - 'videoPath': self.page.lineEdit_video, - 'loopVideo': self.page.checkBox_loop, - 'useAudio': self.page.checkBox_useAudio, - 'distort': self.page.checkBox_distort, - 'scale': self.page.spinBox_scale, - 'volume': self.page.spinBox_volume, - 'xPosition': self.page.spinBox_x, - 'yPosition': self.page.spinBox_y, - }, presetNames={ - 'videoPath': 'video', - 'loopVideo': 'loop', - 'xPosition': 'x', - 'yPosition': 'y', - } - ) + 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(): @@ -157,8 +67,6 @@ class Component(Component): if not self.videoPath: self.lockError("There is no video selected.") - elif self.badVideo: - self.lockError("Could not identify an audio stream in this video.") elif not os.path.exists(self.videoPath): self.lockError("The video selected does not exist!") elif os.path.realpath(self.videoPath) == os.path.realpath(outputFile): @@ -182,22 +90,21 @@ class Component(Component): def preFrameRender(self, **kwargs): super().preFrameRender(**kwargs) self.updateChunksize() - self.video = Video( - ffmpeg=self.core.FFMPEG_BIN, videoPath=self.videoPath, + 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, scale=self.scale + component=self ) if os.path.exists(self.videoPath) else None def frameRender(self, frameNo): - if Video.threadError is not None: - raise Video.threadError - return self.video.frame(frameNo) + if FfmpegVideo.threadError is not None: + raise FfmpegVideo.threadError + return self.finalizeFrame(self.video.frame(frameNo)) def postFrameRender(self): - self.video.pipe.stdout.close() - self.video.pipe.send_signal(signal.SIGINT) + closePipe(self.video.pipe) def pickVideo(self): imgDir = self.settings.value("componentDir", os.path.expanduser("~")) @@ -220,23 +127,30 @@ class Component(Component): '-i', self.videoPath, '-f', 'image2pipe', '-pix_fmt', 'rgba', - '-filter_complex', '[0:v] scale=%s:%s' % scale( - self.scale, width, height, str), - '-vcodec', 'rawvideo', '-', - '-ss', '90', - '-vframes', '1', ] + command.extend(self.makeFfmpegFilter()) + command.extend([ + '-codec:v', 'rawvideo', '-', + '-ss', '90', + '-frames:v', '1', + ]) pipe = openPipe( command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, bufsize=10**8 ) byteFrame = pipe.stdout.read(self.chunkSize) - pipe.stdout.close() - pipe.send_signal(signal.SIGINT) + closePipe(pipe) - frame = finalizeFrame(self, byteFrame, width, height) + frame = self.finalizeFrame(byteFrame) return frame + def makeFfmpegFilter(self): + return [ + '-filter_complex', + '[0:v] scale=%s:%s' % scale( + self.scale, self.width, self.height, str), + ] + def updateChunksize(self): if self.scale != 100 and not self.distort: width, height = scale(self.scale, self.width, self.height, int) @@ -268,44 +182,27 @@ class Component(Component): print('Load a video:\n path=/filepath/to/video.mp4') print('Using audio:\n path=/filepath/to/video.mp4 audio') - -def scale(scale, width, height, returntype=None): - width = (float(width) / 100.0) * float(scale) - height = (float(height) / 100.0) * float(scale) - if returntype == str: - return (str(math.ceil(width)), str(math.ceil(height))) - elif returntype == int: - return (math.ceil(width), math.ceil(height)) - else: - return (width, height) - - -def finalizeFrame(self, imageData, width, height): - try: - if self.distort: - image = Image.frombytes( - 'RGBA', - (width, height), - imageData) + def finalizeFrame(self, imageData): + try: + if self.distort: + image = Image.frombytes( + 'RGBA', + (self.width, self.height), + imageData) + else: + image = Image.frombytes( + 'RGBA', + scale(self.scale, self.width, self.height, int), + imageData) + self._image = image + except ValueError: + # use last good frame + image = self._image + + if self.scale != 100 \ + or self.xPosition != 0 or self.yPosition != 0: + frame = BlankFrame(self.width, self.height) + frame.paste(image, box=(self.xPosition, self.yPosition)) else: - image = Image.frombytes( - 'RGBA', - scale(self.scale, width, height, int), - imageData) - - except ValueError: - print( - '### BAD VIDEO SELECTED ###\n' - 'Video will not export with these settings' - ) - self.badVideo = True - return BlankFrame(width, height) - - if self.scale != 100 \ - or self.xPosition != 0 or self.yPosition != 0: - frame = BlankFrame(width, height) - frame.paste(image, box=(self.xPosition, self.yPosition)) - else: - frame = image - self.badVideo = False - return frame + frame = image + return frame diff --git a/src/components/waveform.py b/src/components/waveform.py new file mode 100644 index 0000000..71cbcac --- /dev/null +++ b/src/components/waveform.py @@ -0,0 +1,194 @@ +from PIL import Image +from PyQt5 import QtGui, QtCore, QtWidgets +from PyQt5.QtGui import QColor +import os +import math +import subprocess + +from component import Component +from toolkit.frame import BlankFrame, scale +from toolkit import checkOutput +from toolkit.ffmpeg import ( + openPipe, closePipe, getAudioDuration, FfmpegVideo, exampleSound +) + + +class Component(Component): + name = 'Waveform' + version = '1.0.0' + + def widget(self, *args): + super().widget(*args) + self._image = BlankFrame(self.width, self.height) + + self.page.lineEdit_color.setText('255,255,255') + + if hasattr(self.parent, 'window'): + self.parent.window.lineEdit_audioFile.textChanged.connect( + self.update + ) + + self.trackWidgets({ + 'color': self.page.lineEdit_color, + 'mode': self.page.comboBox_mode, + 'amplitude': self.page.comboBox_amplitude, + 'x': self.page.spinBox_x, + 'y': self.page.spinBox_y, + 'mirror': self.page.checkBox_mirror, + 'scale': self.page.spinBox_scale, + 'opacity': self.page.spinBox_opacity, + 'compress': self.page.checkBox_compress, + 'mono': self.page.checkBox_mono, + }, colorWidgets={ + 'color': self.page.pushButton_color, + }, relativeWidgets=[ + 'x', 'y', + ]) + + def previewRender(self): + self.updateChunksize() + frame = self.getPreviewFrame(self.width, self.height) + if not frame: + return BlankFrame(self.width, self.height) + else: + return frame + + def preFrameRender(self, **kwargs): + super().preFrameRender(**kwargs) + self.updateChunksize() + w, h = scale(self.scale, self.width, self.height, str) + self.video = FfmpegVideo( + inputPath=self.audioFile, + filter_=self.makeFfmpegFilter(), + width=w, height=h, + chunkSize=self.chunkSize, + frameRate=int(self.settings.value("outputFrameRate")), + parent=self.parent, component=self, debug=True, + ) + + def frameRender(self, frameNo): + if FfmpegVideo.threadError is not None: + raise FfmpegVideo.threadError + return self.finalizeFrame(self.video.frame(frameNo)) + + def postFrameRender(self): + closePipe(self.video.pipe) + + def getPreviewFrame(self, width, height): + genericPreview = self.settings.value("pref_genericPreview") + startPt = 0 + if not genericPreview: + inputFile = self.parent.window.lineEdit_audioFile.text() + if not inputFile or not os.path.exists(inputFile): + return + duration = getAudioDuration(inputFile) + if not duration: + return + startPt = duration / 3 + if startPt + 3 > duration: + startPt += startPt - 3 + + command = [ + self.core.FFMPEG_BIN, + '-thread_queue_size', '512', + '-r', self.settings.value("outputFrameRate"), + '-ss', "{0:.3f}".format(startPt), + '-i', + os.path.join(self.core.wd, 'background.png') + 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', + ]) + pipe = openPipe( + command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, bufsize=10**8 + ) + byteFrame = pipe.stdout.read(self.chunkSize) + closePipe(pipe) + + frame = self.finalizeFrame(byteFrame) + return frame + + def makeFfmpegFilter(self, preview=False, startPt=0): + w, h = scale(self.scale, self.width, self.height, str) + if self.amplitude == 0: + amplitude = 'lin' + elif self.amplitude == 1: + amplitude = 'log' + elif self.amplitude == 2: + amplitude = 'sqrt' + elif self.amplitude == 3: + amplitude = 'cbrt' + hexcolor = QColor(*self.color).name() + opacity = "{0:.1f}".format(self.opacity / 100) + genericPreview = self.settings.value("pref_genericPreview") + if self.mode < 3: + filter_ = 'showwaves=r=%s:s=%sx%s:mode=%s:colors=%s@%s:scale=%s' % ( + self.settings.value("outputFrameRate"), + self.settings.value("outputWidth"), + self.settings.value("outputHeight"), + self.page.comboBox_mode.currentText().lower() + if self.mode != 3 else 'p2p', + hexcolor, opacity, amplitude, + ) + elif self.mode > 2: + filter_ = ( + 'showfreqs=s=%sx%s:mode=%s:colors=%s@%s' + ':ascale=%s:fscale=%s' % ( + self.settings.value("outputWidth"), + self.settings.value("outputHeight"), + 'line' if self.mode == 4 else 'bar', + hexcolor, opacity, amplitude, + 'log' if self.mono else 'lin' + ) + ) + + return [ + '-filter_complex', + '%s%s%s' + '%s%s%s [v1]; ' + '[v1] scale=%s:%s%s [v]' % ( + exampleSound() if preview and genericPreview else '[0:a] ', + 'compand=gain=4,' if self.compress else '', + 'aformat=channel_layouts=mono,' + if self.mono and self.mode < 3 else '', + filter_, + ', drawbox=x=(iw-w)/2:y=(ih-h)/2:w=iw:h=4:color=%s@%s' % ( + hexcolor, opacity + ) if self.mode < 2 else '', + ', hflip' if self.mirror else'', + w, h, + ', trim=duration=%s' % "{0:.3f}".format(startPt + 3) + if preview else '', + ), + '-map', '[v]', + ] + + def updateChunksize(self): + width, height = scale(self.scale, self.width, self.height, int) + self.chunkSize = 4 * width * height + + def finalizeFrame(self, imageData): + try: + image = Image.frombytes( + 'RGBA', + scale(self.scale, self.width, self.height, int), + imageData + ) + self._image = image + except ValueError: + image = self._image + if self.scale != 100 \ + or self.x != 0 or self.y != 0: + frame = BlankFrame(self.width, self.height) + frame.paste(image, box=(self.x, self.y)) + else: + frame = image + return frame diff --git a/src/components/waveform.ui b/src/components/waveform.ui new file mode 100644 index 0000000..5473f33 --- /dev/null +++ b/src/components/waveform.ui @@ -0,0 +1,383 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Form</class> + <widget class="QWidget" name="Form"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>586</width> + <height>197</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>197</height> + </size> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="leftMargin"> + <number>4</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_8"> + <item> + <widget class="QLabel" name="label_textColor"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>31</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Mode</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboBox_mode"> + <item> + <property name="text"> + <string>Cline</string> + </property> + </item> + <item> + <property name="text"> + <string>Line</string> + </property> + </item> + <item> + <property name="text"> + <string>Point</string> + </property> + </item> + <item> + <property name="text"> + <string>Frequency Bar</string> + </property> + </item> + <item> + <property name="text"> + <string>Frequency Line</string> + </property> + </item> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_9"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>5</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label_xTitleAlign"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>X</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="spinBox_x"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>80</width> + <height>16777215</height> + </size> + </property> + <property name="minimum"> + <number>-10000</number> + </property> + <property name="maximum"> + <number>10000</number> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_yTitleAlign"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Y</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="spinBox_y"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>80</width> + <height>16777215</height> + </size> + </property> + <property name="baseSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="minimum"> + <number>-10000</number> + </property> + <property name="maximum"> + <number>10000</number> + </property> + <property name="value"> + <number>0</number> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_9"> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Color</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="lineEdit_color"> + <property name="inputMethodHints"> + <set>Qt::ImhNone</set> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="pushButton_color"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="text"> + <string/> + </property> + <property name="default"> + <bool>false</bool> + </property> + <property name="flat"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Opacity</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="spinBox_opacity"> + <property name="buttonSymbols"> + <enum>QAbstractSpinBox::UpDownArrows</enum> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>0</number> + </property> + <property name="maximum"> + <number>100</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Scale</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="spinBox_scale"> + <property name="buttonSymbols"> + <enum>QAbstractSpinBox::UpDownArrows</enum> + </property> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>10</number> + </property> + <property name="maximum"> + <number>400</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_10"> + <item> + <widget class="QCheckBox" name="checkBox_compress"> + <property name="text"> + <string>Compress</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="checkBox_mono"> + <property name="text"> + <string>Mono</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="checkBox_mirror"> + <property name="text"> + <string>Mirror</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Amplitude</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="comboBox_amplitude"> + <item> + <property name="text"> + <string>Linear</string> + </property> + </item> + <item> + <property name="text"> + <string>Logarithmic</string> + </property> + </item> + <item> + <property name="text"> + <string>Square root</string> + </property> + </item> + <item> + <property name="text"> + <string>Cubic root</string> + </property> + </item> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> |
