From eaee0ab233709c18324dbb25f38b59c95c447e3c Mon Sep 17 00:00:00 2001
From: DH4
Date: Fri, 26 May 2017 23:06:47 -0500
Subject: Removed hardcoded parameters. Defaults loaded at runtime.
---
.vscode/launch.json | 204 ++++++++++++++++++++++++++++++++++++++++++++++++++++
main.py | 22 ++++++
video_thread.py | 21 +++---
3 files changed, 237 insertions(+), 10 deletions(-)
create mode 100644 .vscode/launch.json
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..5b2c335
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,204 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Python",
+ "type": "python",
+ "request": "launch",
+ "stopOnEntry": true,
+ "pythonPath": "${config:python.pythonPath}",
+ "program": "${file}",
+ "cwd": "${workspaceRoot}",
+ "env": {},
+ "envFile": "${workspaceRoot}/.env",
+ "debugOptions": [
+ "WaitOnAbnormalExit",
+ "WaitOnNormalExit",
+ "RedirectOutput"
+ ]
+ },
+ {
+ "name": "PySpark",
+ "type": "python",
+ "request": "launch",
+ "stopOnEntry": true,
+ "osx": {
+ "pythonPath": "${env:SPARK_HOME}/bin/spark-submit"
+ },
+ "windows": {
+ "pythonPath": "${env:SPARK_HOME}/bin/spark-submit.cmd"
+ },
+ "linux": {
+ "pythonPath": "${env:SPARK_HOME}/bin/spark-submit"
+ },
+ "program": "${file}",
+ "cwd": "${workspaceRoot}",
+ "env": {},
+ "envFile": "${workspaceRoot}/.env",
+ "debugOptions": [
+ "WaitOnAbnormalExit",
+ "WaitOnNormalExit",
+ "RedirectOutput"
+ ]
+ },
+ {
+ "name": "Python Module",
+ "type": "python",
+ "request": "launch",
+ "stopOnEntry": true,
+ "pythonPath": "${config:python.pythonPath}",
+ "module": "module.name",
+ "cwd": "${workspaceRoot}",
+ "env": {},
+ "envFile": "${workspaceRoot}/.env",
+ "debugOptions": [
+ "WaitOnAbnormalExit",
+ "WaitOnNormalExit",
+ "RedirectOutput"
+ ]
+ },
+ {
+ "name": "Integrated Terminal/Console",
+ "type": "python",
+ "request": "launch",
+ "stopOnEntry": true,
+ "pythonPath": "${config:python.pythonPath}",
+ "program": "${file}",
+ "cwd": "",
+ "console": "integratedTerminal",
+ "env": {},
+ "envFile": "${workspaceRoot}/.env",
+ "debugOptions": [
+ "WaitOnAbnormalExit",
+ "WaitOnNormalExit"
+ ]
+ },
+ {
+ "name": "External Terminal/Console",
+ "type": "python",
+ "request": "launch",
+ "stopOnEntry": true,
+ "pythonPath": "${config:python.pythonPath}",
+ "program": "${file}",
+ "cwd": "",
+ "console": "externalTerminal",
+ "env": {},
+ "envFile": "${workspaceRoot}/.env",
+ "debugOptions": [
+ "WaitOnAbnormalExit",
+ "WaitOnNormalExit"
+ ]
+ },
+ {
+ "name": "Django",
+ "type": "python",
+ "request": "launch",
+ "stopOnEntry": true,
+ "pythonPath": "${config:python.pythonPath}",
+ "program": "${workspaceRoot}/manage.py",
+ "cwd": "${workspaceRoot}",
+ "args": [
+ "runserver",
+ "--noreload"
+ ],
+ "env": {},
+ "envFile": "${workspaceRoot}/.env",
+ "debugOptions": [
+ "WaitOnAbnormalExit",
+ "WaitOnNormalExit",
+ "RedirectOutput",
+ "DjangoDebugging"
+ ]
+ },
+ {
+ "name": "Flask",
+ "type": "python",
+ "request": "launch",
+ "stopOnEntry": false,
+ "pythonPath": "${config:python.pythonPath}",
+ "program": "fully qualified path fo 'flask' executable. Generally located along with python interpreter",
+ "cwd": "${workspaceRoot}",
+ "env": {
+ "FLASK_APP": "${workspaceRoot}/quickstart/app.py"
+ },
+ "args": [
+ "run",
+ "--no-debugger",
+ "--no-reload"
+ ],
+ "envFile": "${workspaceRoot}/.env",
+ "debugOptions": [
+ "WaitOnAbnormalExit",
+ "WaitOnNormalExit",
+ "RedirectOutput"
+ ]
+ },
+ {
+ "name": "Flask (old)",
+ "type": "python",
+ "request": "launch",
+ "stopOnEntry": false,
+ "pythonPath": "${config:python.pythonPath}",
+ "program": "${workspaceRoot}/run.py",
+ "cwd": "${workspaceRoot}",
+ "args": [],
+ "env": {},
+ "envFile": "${workspaceRoot}/.env",
+ "debugOptions": [
+ "WaitOnAbnormalExit",
+ "WaitOnNormalExit",
+ "RedirectOutput"
+ ]
+ },
+ {
+ "name": "Pyramid",
+ "type": "python",
+ "request": "launch",
+ "stopOnEntry": true,
+ "pythonPath": "${config:python.pythonPath}",
+ "cwd": "${workspaceRoot}",
+ "env": {},
+ "envFile": "${workspaceRoot}/.env",
+ "args": [
+ "${workspaceRoot}/development.ini"
+ ],
+ "debugOptions": [
+ "WaitOnAbnormalExit",
+ "WaitOnNormalExit",
+ "RedirectOutput",
+ "Pyramid"
+ ]
+ },
+ {
+ "name": "Watson",
+ "type": "python",
+ "request": "launch",
+ "stopOnEntry": true,
+ "pythonPath": "${config:python.pythonPath}",
+ "program": "${workspaceRoot}/console.py",
+ "cwd": "${workspaceRoot}",
+ "args": [
+ "dev",
+ "runserver",
+ "--noreload=True"
+ ],
+ "env": {},
+ "envFile": "${workspaceRoot}/.env",
+ "debugOptions": [
+ "WaitOnAbnormalExit",
+ "WaitOnNormalExit",
+ "RedirectOutput"
+ ]
+ },
+ {
+ "name": "Attach (Remote Debug)",
+ "type": "python",
+ "request": "attach",
+ "localRoot": "${workspaceRoot}",
+ "remoteRoot": "${workspaceRoot}",
+ "port": 3000,
+ "secret": "my_secret",
+ "host": "localhost"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/main.py b/main.py
index 9f608d8..3915c71 100644
--- a/main.py
+++ b/main.py
@@ -36,6 +36,7 @@ class Command(QtCore.QObject):
self.args = self.parser.parse_args()
self.settings = QSettings('settings.ini', QSettings.IniFormat)
+ LoadDefaultSettings(self)
# load colours as tuples from comma-separated strings
self.textColor = core.Core.RGBFromString(self.settings.value("textColor", '255, 255, 255'))
@@ -106,6 +107,8 @@ class Command(QtCore.QObject):
self.settings.setValue("textColor", '%s,%s,%s' % self.textColor)
sys.exit(0)
+
+
class Main(QtCore.QObject):
newTask = QtCore.pyqtSignal(str, str, QFont, int, int, int, int, tuple, tuple)
@@ -119,6 +122,8 @@ class Main(QtCore.QObject):
self.window = window
self.core = core.Core()
self.settings = QSettings('settings.ini', QSettings.IniFormat)
+
+ LoadDefaultSettings(self)
# load colors as tuples from a comma-separated string
self.textColor = core.Core.RGBFromString(self.settings.value("textColor", '255, 255, 255'))
@@ -314,6 +319,23 @@ class Main(QtCore.QObject):
self.window.lineEdit_visColor.setText(RGBstring)
window.pushButton_visColor.setStyleSheet(btnStyle)
+def LoadDefaultSettings(self):
+ default = {
+ "outputWidth": 1280,
+ "outputHeight": 720,
+ "outputFrameRate": 30,
+ "outputAudioCodec": "aac",
+ "outputAudioBitrate": "192k",
+ "outputVideoCodec": "libx264",
+ "outputVideoFormat": "yuv420p",
+ "outputPreset": "medium",
+ "outputFormat": "mp4"
+ }
+
+ for parm, value in default.items():
+ if self.settings.value(parm) == None:
+ self.settings.setValue(parm,value)
+
if len(sys.argv) > 1:
# command line mode
app = QtGui.QApplication(sys.argv, False)
diff --git a/video_thread.py b/video_thread.py
index 6f71d38..fe1f6f6 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -15,6 +15,7 @@ class Worker(QtCore.QObject):
def __init__(self, parent=None):
QtCore.QObject.__init__(self)
+ self.settings = parent.settings
parent.videoTask.connect(self.createVideo)
self.core = core.Core()
@@ -52,27 +53,27 @@ class Worker(QtCore.QObject):
# test if user has libfdk_aac
encoders = sp.check_output(self.core.FFMPEG_BIN + " -encoders -hide_banner", shell=True)
- if b'libfdk_aac' in encoders:
+ acodec = self.settings.value('outputAudioCodec')
+
+ if b'libfdk_aac' in encoders and acodec == 'aac':
acodec = 'libfdk_aac'
- else:
- acodec = 'aac'
ffmpegCommand = [ self.core.FFMPEG_BIN,
'-y', # (optional) means overwrite the output file if it already exists.
'-f', 'rawvideo',
'-vcodec', 'rawvideo',
- '-s', '1280x720', # size of one frame
+ '-s', self.settings.value('outputWidth')+'x'+self.settings.value('outputHeight'), # size of one frame
'-pix_fmt', 'rgb24',
- '-r', '30', # frames per second
+ '-r', self.settings.value('outputFrameRate'), # frames per second
'-i', '-', # The input comes from a pipe
'-an',
'-i', inputFile,
'-acodec', acodec, # output audio codec
- '-b:a', "192k",
- '-vcodec', "libx264",
- '-pix_fmt', "yuv420p",
- '-preset', "medium",
- '-f', "mp4"]
+ '-b:a', self.settings.value('outputAudioBitrate'),
+ '-vcodec', self.settings.value('outputVideoCodec'),
+ '-pix_fmt', self.settings.value('outputVideoFormat'),
+ '-preset', self.settings.value('outputPreset'),
+ '-f', self.settings.value('outputFormat')]
if acodec == 'aac':
ffmpegCommand.append('-strict')
--
cgit v1.2.3
From f2329e93660780fc261abdbbd9d43884fdcaf722 Mon Sep 17 00:00:00 2001
From: DH4
Date: Sat, 27 May 2017 03:06:17 -0500
Subject: Added automatic scaling of Image and bars. Set title x/y position,
and font size based on scale.
---
core.py | 27 +++++++++++++++++++--------
main.py | 10 ++++------
preview_thread.py | 1 +
video_thread.py | 22 +++++++++++-----------
4 files changed, 35 insertions(+), 25 deletions(-)
diff --git a/core.py b/core.py
index 900a98f..d360ca6 100644
--- a/core.py
+++ b/core.py
@@ -45,7 +45,7 @@ class Core():
def drawBaseImage(self, backgroundFile, titleText, titleFont, fontSize, alignment,\
xOffset, yOffset, textColor, visColor):
if backgroundFile == '':
- im = Image.new("RGB", (1280, 720), "black")
+ im = Image.new("RGB", (int(self.settings.value('outputWidth')), int(self.settings.value('outputHeight'))), "black")
else:
im = Image.open(backgroundFile)
@@ -53,8 +53,8 @@ class Core():
self.lastBackgroundImage = backgroundFile
# resize if necessary
- if not im.size == (1280, 720):
- im = im.resize((1280, 720), Image.ANTIALIAS)
+ if not im.size == (int(self.settings.value('outputWidth')), int(self.settings.value('outputHeight'))):
+ im = im.resize((int(self.settings.value('outputWidth')), int(self.settings.value('outputHeight'))), Image.ANTIALIAS)
self._image = ImageQt(im)
@@ -89,21 +89,32 @@ class Core():
def drawBars(self, spectrum, image, color):
- imTop = Image.new("RGBA", (1280, 360))
+ width = int(self.settings.value('outputWidth'))
+ height = int(int(self.settings.value('outputHeight'))/2)
+
+ imTop = Image.new("RGBA", (width, height))
draw = ImageDraw.Draw(imTop)
r, g, b = color
color2 = (r, g, b, 50)
+
+ vH = height-height/8
+ bF = int(self.settings.value('outputWidth')) / 64
+ bH = bF / 2
+ bQ = bF / 4
+
+ bP = int(self.settings.value('outputHeight')) / 800
+
for j in range(0, 63):
- draw.rectangle((10 + j * 20, 325, 10 + j * 20 + 20, 325 - spectrum[j * 4] * 1 - 10), fill=color2)
- draw.rectangle((15 + j * 20, 320, 15 + j * 20 + 10, 320 - spectrum[j * 4] * 1), fill=color)
+ draw.rectangle((bH + j * bF, vH, bH + j * bF + bF, vH + bQ - spectrum[j * 4] * bP - bH), fill=color2)
+ draw.rectangle((bH + bQ + j * bF, vH - bQ , bH + bQ + j * bF + bH, vH - spectrum[j * 4] * bP), fill=color)
imBottom = imTop.transpose(Image.FLIP_TOP_BOTTOM)
- im = Image.new("RGB", (1280, 720), "black")
+ im = Image.new("RGB", (int(self.settings.value('outputWidth')), int(self.settings.value('outputHeight'))), "black")
im.paste(image, (0, 0))
im.paste(imTop, (0, 0), mask=imTop)
- im.paste(imBottom, (0, 360), mask=imBottom)
+ im.paste(imBottom, (0, int(vH+bF*.7)), mask=imBottom)
return im
diff --git a/main.py b/main.py
index 3915c71..bfa8fbd 100644
--- a/main.py
+++ b/main.py
@@ -107,8 +107,6 @@ class Command(QtCore.QObject):
self.settings.setValue("textColor", '%s,%s,%s' % self.textColor)
sys.exit(0)
-
-
class Main(QtCore.QObject):
newTask = QtCore.pyqtSignal(str, str, QFont, int, int, int, int, tuple, tuple)
@@ -122,7 +120,6 @@ class Main(QtCore.QObject):
self.window = window
self.core = core.Core()
self.settings = QSettings('settings.ini', QSettings.IniFormat)
-
LoadDefaultSettings(self)
# load colors as tuples from a comma-separated string
@@ -168,9 +165,10 @@ class Main(QtCore.QObject):
window.alignmentComboBox.addItem("Left")
window.alignmentComboBox.addItem("Middle")
window.alignmentComboBox.addItem("Right")
- window.fontsizeSpinBox.setValue(35)
- window.textXSpinBox.setValue(70)
- window.textYSpinBox.setValue(375)
+ window.alignmentComboBox.setCurrentIndex(1)
+ window.fontsizeSpinBox.setValue(int(int(self.settings.value("outputHeight")) / 16 ))
+ window.textXSpinBox.setValue(int(int(self.settings.value('outputWidth'))/2))
+ window.textYSpinBox.setValue(int(int(self.settings.value('outputHeight'))/2))
window.lineEdit_textColor.setText('%s,%s,%s' % self.textColor)
window.lineEdit_visColor.setText('%s,%s,%s' % self.visColor)
window.pushButton_textColor.clicked.connect(lambda: self.pickColor('text'))
diff --git a/preview_thread.py b/preview_thread.py
index 041d39e..8195712 100644
--- a/preview_thread.py
+++ b/preview_thread.py
@@ -17,6 +17,7 @@ class Worker(QtCore.QObject):
parent.processTask.connect(self.process)
self.core = core.Core()
self.queue = queue
+ self.core.settings = parent.settings
@pyqtSlot(str, str, QtGui.QFont, int, int, int, int, tuple, tuple)
diff --git a/video_thread.py b/video_thread.py
index fe1f6f6..5b9a896 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -15,10 +15,10 @@ class Worker(QtCore.QObject):
def __init__(self, parent=None):
QtCore.QObject.__init__(self)
- self.settings = parent.settings
- parent.videoTask.connect(self.createVideo)
self.core = core.Core()
-
+ self.core.settings = parent.settings
+ parent.videoTask.connect(self.createVideo)
+
@pyqtSlot(str, str, QtGui.QFont, int, int, int, int, tuple, tuple, str, str)
def createVideo(self, backgroundImage, titleText, titleFont, fontSize, alignment,\
@@ -53,7 +53,7 @@ class Worker(QtCore.QObject):
# test if user has libfdk_aac
encoders = sp.check_output(self.core.FFMPEG_BIN + " -encoders -hide_banner", shell=True)
- acodec = self.settings.value('outputAudioCodec')
+ acodec = self.core.settings.value('outputAudioCodec')
if b'libfdk_aac' in encoders and acodec == 'aac':
acodec = 'libfdk_aac'
@@ -62,18 +62,18 @@ class Worker(QtCore.QObject):
'-y', # (optional) means overwrite the output file if it already exists.
'-f', 'rawvideo',
'-vcodec', 'rawvideo',
- '-s', self.settings.value('outputWidth')+'x'+self.settings.value('outputHeight'), # size of one frame
+ '-s', self.core.settings.value('outputWidth')+'x'+self.core.settings.value('outputHeight'), # size of one frame
'-pix_fmt', 'rgb24',
- '-r', self.settings.value('outputFrameRate'), # frames per second
+ '-r', self.core.settings.value('outputFrameRate'), # frames per second
'-i', '-', # The input comes from a pipe
'-an',
'-i', inputFile,
'-acodec', acodec, # output audio codec
- '-b:a', self.settings.value('outputAudioBitrate'),
- '-vcodec', self.settings.value('outputVideoCodec'),
- '-pix_fmt', self.settings.value('outputVideoFormat'),
- '-preset', self.settings.value('outputPreset'),
- '-f', self.settings.value('outputFormat')]
+ '-b:a', self.core.settings.value('outputAudioBitrate'),
+ '-vcodec', self.core.settings.value('outputVideoCodec'),
+ '-pix_fmt', self.core.settings.value('outputVideoFormat'),
+ '-preset', self.core.settings.value('outputPreset'),
+ '-f', self.core.settings.value('outputFormat')]
if acodec == 'aac':
ffmpegCommand.append('-strict')
--
cgit v1.2.3
From 1a8acdbed09cc751d587e99bfc848c29752104e5 Mon Sep 17 00:00:00 2001
From: DH4
Date: Sat, 27 May 2017 04:49:26 -0500
Subject: Fixed Scaling Bugs
---
core.py | 19 +++++++++----------
main.py | 3 ++-
2 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/core.py b/core.py
index d360ca6..249a373 100644
--- a/core.py
+++ b/core.py
@@ -65,9 +65,9 @@ class Core():
painter.setFont(font)
painter.setPen(QColor(*textColor))
- yPosition = yOffset
-
fm = QtGui.QFontMetrics(font)
+ yPosition = yOffset + fm.height()/6
+
if alignment == 0: #Left
xPosition = xOffset
if alignment == 1: #Middle
@@ -92,21 +92,20 @@ class Core():
width = int(self.settings.value('outputWidth'))
height = int(int(self.settings.value('outputHeight'))/2)
- imTop = Image.new("RGBA", (width, height))
- draw = ImageDraw.Draw(imTop)
- r, g, b = color
- color2 = (r, g, b, 50)
-
vH = height-height/8
bF = int(self.settings.value('outputWidth')) / 64
bH = bF / 2
bQ = bF / 4
+ imTop = Image.new("RGBA", (width, height))
+ draw = ImageDraw.Draw(imTop)
+ r, g, b = color
+ color2 = (r, g, b, 50)
bP = int(self.settings.value('outputHeight')) / 800
for j in range(0, 63):
- draw.rectangle((bH + j * bF, vH, bH + j * bF + bF, vH + bQ - spectrum[j * 4] * bP - bH), fill=color2)
- draw.rectangle((bH + bQ + j * bF, vH - bQ , bH + bQ + j * bF + bH, vH - spectrum[j * 4] * bP), fill=color)
+ 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)
@@ -114,7 +113,7 @@ class Core():
im = Image.new("RGB", (int(self.settings.value('outputWidth')), int(self.settings.value('outputHeight'))), "black")
im.paste(image, (0, 0))
im.paste(imTop, (0, 0), mask=imTop)
- im.paste(imBottom, (0, int(vH+bF*.7)), mask=imBottom)
+ im.paste(imBottom, (0, int(vH+bF*1.8)), mask=imBottom)
return im
diff --git a/main.py b/main.py
index bfa8fbd..a065680 100644
--- a/main.py
+++ b/main.py
@@ -166,9 +166,10 @@ class Main(QtCore.QObject):
window.alignmentComboBox.addItem("Middle")
window.alignmentComboBox.addItem("Right")
window.alignmentComboBox.setCurrentIndex(1)
- window.fontsizeSpinBox.setValue(int(int(self.settings.value("outputHeight")) / 16 ))
+ window.fontsizeSpinBox.setValue(int(int(self.settings.value("outputHeight")) / 14 ))
window.textXSpinBox.setValue(int(int(self.settings.value('outputWidth'))/2))
window.textYSpinBox.setValue(int(int(self.settings.value('outputHeight'))/2))
+
window.lineEdit_textColor.setText('%s,%s,%s' % self.textColor)
window.lineEdit_visColor.setText('%s,%s,%s' % self.visColor)
window.pushButton_textColor.clicked.connect(lambda: self.pickColor('text'))
--
cgit v1.2.3
From 86c6ac87627365e8ea30f84420c3ac97f0a597ea Mon Sep 17 00:00:00 2001
From: DH4
Date: Sat, 27 May 2017 05:40:22 -0500
Subject: Removed .vscode directory and updated .gitignore
---
.gitignore | 3 +-
.vscode/launch.json | 204 ----------------------------------------------------
2 files changed, 2 insertions(+), 205 deletions(-)
delete mode 100644 .vscode/launch.json
diff --git a/.gitignore b/.gitignore
index f4d88c1..24ed791 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
__pycache__
settings.ini
-build/*
\ No newline at end of file
+build/*
+.vscode/*
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
deleted file mode 100644
index 5b2c335..0000000
--- a/.vscode/launch.json
+++ /dev/null
@@ -1,204 +0,0 @@
-{
- "version": "0.2.0",
- "configurations": [
- {
- "name": "Python",
- "type": "python",
- "request": "launch",
- "stopOnEntry": true,
- "pythonPath": "${config:python.pythonPath}",
- "program": "${file}",
- "cwd": "${workspaceRoot}",
- "env": {},
- "envFile": "${workspaceRoot}/.env",
- "debugOptions": [
- "WaitOnAbnormalExit",
- "WaitOnNormalExit",
- "RedirectOutput"
- ]
- },
- {
- "name": "PySpark",
- "type": "python",
- "request": "launch",
- "stopOnEntry": true,
- "osx": {
- "pythonPath": "${env:SPARK_HOME}/bin/spark-submit"
- },
- "windows": {
- "pythonPath": "${env:SPARK_HOME}/bin/spark-submit.cmd"
- },
- "linux": {
- "pythonPath": "${env:SPARK_HOME}/bin/spark-submit"
- },
- "program": "${file}",
- "cwd": "${workspaceRoot}",
- "env": {},
- "envFile": "${workspaceRoot}/.env",
- "debugOptions": [
- "WaitOnAbnormalExit",
- "WaitOnNormalExit",
- "RedirectOutput"
- ]
- },
- {
- "name": "Python Module",
- "type": "python",
- "request": "launch",
- "stopOnEntry": true,
- "pythonPath": "${config:python.pythonPath}",
- "module": "module.name",
- "cwd": "${workspaceRoot}",
- "env": {},
- "envFile": "${workspaceRoot}/.env",
- "debugOptions": [
- "WaitOnAbnormalExit",
- "WaitOnNormalExit",
- "RedirectOutput"
- ]
- },
- {
- "name": "Integrated Terminal/Console",
- "type": "python",
- "request": "launch",
- "stopOnEntry": true,
- "pythonPath": "${config:python.pythonPath}",
- "program": "${file}",
- "cwd": "",
- "console": "integratedTerminal",
- "env": {},
- "envFile": "${workspaceRoot}/.env",
- "debugOptions": [
- "WaitOnAbnormalExit",
- "WaitOnNormalExit"
- ]
- },
- {
- "name": "External Terminal/Console",
- "type": "python",
- "request": "launch",
- "stopOnEntry": true,
- "pythonPath": "${config:python.pythonPath}",
- "program": "${file}",
- "cwd": "",
- "console": "externalTerminal",
- "env": {},
- "envFile": "${workspaceRoot}/.env",
- "debugOptions": [
- "WaitOnAbnormalExit",
- "WaitOnNormalExit"
- ]
- },
- {
- "name": "Django",
- "type": "python",
- "request": "launch",
- "stopOnEntry": true,
- "pythonPath": "${config:python.pythonPath}",
- "program": "${workspaceRoot}/manage.py",
- "cwd": "${workspaceRoot}",
- "args": [
- "runserver",
- "--noreload"
- ],
- "env": {},
- "envFile": "${workspaceRoot}/.env",
- "debugOptions": [
- "WaitOnAbnormalExit",
- "WaitOnNormalExit",
- "RedirectOutput",
- "DjangoDebugging"
- ]
- },
- {
- "name": "Flask",
- "type": "python",
- "request": "launch",
- "stopOnEntry": false,
- "pythonPath": "${config:python.pythonPath}",
- "program": "fully qualified path fo 'flask' executable. Generally located along with python interpreter",
- "cwd": "${workspaceRoot}",
- "env": {
- "FLASK_APP": "${workspaceRoot}/quickstart/app.py"
- },
- "args": [
- "run",
- "--no-debugger",
- "--no-reload"
- ],
- "envFile": "${workspaceRoot}/.env",
- "debugOptions": [
- "WaitOnAbnormalExit",
- "WaitOnNormalExit",
- "RedirectOutput"
- ]
- },
- {
- "name": "Flask (old)",
- "type": "python",
- "request": "launch",
- "stopOnEntry": false,
- "pythonPath": "${config:python.pythonPath}",
- "program": "${workspaceRoot}/run.py",
- "cwd": "${workspaceRoot}",
- "args": [],
- "env": {},
- "envFile": "${workspaceRoot}/.env",
- "debugOptions": [
- "WaitOnAbnormalExit",
- "WaitOnNormalExit",
- "RedirectOutput"
- ]
- },
- {
- "name": "Pyramid",
- "type": "python",
- "request": "launch",
- "stopOnEntry": true,
- "pythonPath": "${config:python.pythonPath}",
- "cwd": "${workspaceRoot}",
- "env": {},
- "envFile": "${workspaceRoot}/.env",
- "args": [
- "${workspaceRoot}/development.ini"
- ],
- "debugOptions": [
- "WaitOnAbnormalExit",
- "WaitOnNormalExit",
- "RedirectOutput",
- "Pyramid"
- ]
- },
- {
- "name": "Watson",
- "type": "python",
- "request": "launch",
- "stopOnEntry": true,
- "pythonPath": "${config:python.pythonPath}",
- "program": "${workspaceRoot}/console.py",
- "cwd": "${workspaceRoot}",
- "args": [
- "dev",
- "runserver",
- "--noreload=True"
- ],
- "env": {},
- "envFile": "${workspaceRoot}/.env",
- "debugOptions": [
- "WaitOnAbnormalExit",
- "WaitOnNormalExit",
- "RedirectOutput"
- ]
- },
- {
- "name": "Attach (Remote Debug)",
- "type": "python",
- "request": "attach",
- "localRoot": "${workspaceRoot}",
- "remoteRoot": "${workspaceRoot}",
- "port": 3000,
- "secret": "my_secret",
- "host": "localhost"
- }
- ]
-}
\ No newline at end of file
--
cgit v1.2.3
From fe13268a841d3b4d45f33359abb81993d990772c Mon Sep 17 00:00:00 2001
From: DH4
Date: Sat, 27 May 2017 14:32:08 -0500
Subject: Created a new UI, several new features to be implemented. FIXME:
Resolution change requires an application restart.
---
core.py | 15 +-
main.py | 151 ++++----
main.ui | 38 --
mainwindow.ui | 1120 +++++++++++++++++++++++++++++++++++++++++++++++++++++
preview_thread.py | 7 +-
5 files changed, 1219 insertions(+), 112 deletions(-)
create mode 100644 mainwindow.ui
diff --git a/core.py b/core.py
index 249a373..8292f5b 100644
--- a/core.py
+++ b/core.py
@@ -112,8 +112,19 @@ class Core():
im = Image.new("RGB", (int(self.settings.value('outputWidth')), int(self.settings.value('outputHeight'))), "black")
im.paste(image, (0, 0))
- im.paste(imTop, (0, 0), mask=imTop)
- im.paste(imBottom, (0, int(vH+bF*1.8)), mask=imBottom)
+
+ layout = int(self.settings.value('visLayout'))
+
+ if layout == 0:
+ im.paste(imTop, (0, 0), mask=imTop)
+ im.paste(imBottom, (0, int(vH+bF*1.8)), mask=imBottom)
+
+ if layout == 1:
+ im.paste(imTop, (0, int(height+bF*1.5)), mask=imTop)
+ im.paste(imBottom, (0, int(0-bF*1.5)), mask=imBottom)
+
+ if layout == 2:
+ im.paste(imTop, (0, int(height+bF*1.5)), mask=imTop)
return im
diff --git a/main.py b/main.py
index a065680..4fe9315 100644
--- a/main.py
+++ b/main.py
@@ -139,36 +139,37 @@ class Main(QtCore.QObject):
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.processTask.emit)
self.timer.start(500)
-
- window.pushButton_selectInput.clicked.connect(self.openInputFileDialog)
- window.pushButton_selectOutput.clicked.connect(self.openOutputFileDialog)
- window.pushButton_createVideo.clicked.connect(self.createAudioVisualisation)
- window.pushButton_selectBackground.clicked.connect(self.openBackgroundFileDialog)
- window.progressBar_create.setValue(0)
+ window.toolButton_selectAudioFile.clicked.connect(self.openInputFileDialog)
+ window.toolButton_selectBackground.clicked.connect(self.openBackgroundFileDialog)
+ window.toolButton_selectOutputFile.clicked.connect(self.openOutputFileDialog)
+ window.progressBar_createVideo.setValue(0)
+ window.pushButton_createVideo.clicked.connect(self.createAudioVisualisation)
window.setWindowTitle("Audio Visualizer")
- window.pushButton_selectInput.setText("Select Input Music File")
- window.pushButton_selectOutput.setText("Select Output Video File")
- window.pushButton_selectBackground.setText("Select Background Image")
- window.label_font.setText("Title Font")
- window.label_alignment.setText("Title Options")
- window.label_colorOptions.setText("Colors")
- window.label_fontsize.setText("Fontsize")
- window.label_title.setText("Title Text")
- window.label_textColor.setText("Text:")
- window.label_visColor.setText("Visualizer:")
- window.pushButton_createVideo.setText("Create Video")
- window.groupBox_create.setTitle("Create")
- window.groupBox_settings.setTitle("Settings")
- window.groupBox_preview.setTitle("Preview")
-
- window.alignmentComboBox.addItem("Left")
- window.alignmentComboBox.addItem("Middle")
- window.alignmentComboBox.addItem("Right")
- window.alignmentComboBox.setCurrentIndex(1)
- window.fontsizeSpinBox.setValue(int(int(self.settings.value("outputHeight")) / 14 ))
- window.textXSpinBox.setValue(int(int(self.settings.value('outputWidth'))/2))
- window.textYSpinBox.setValue(int(int(self.settings.value('outputHeight'))/2))
+ window.comboBox_textAlign.addItem("Left")
+ window.comboBox_textAlign.addItem("Middle")
+ window.comboBox_textAlign.addItem("Right")
+ window.comboBox_textAlign.setCurrentIndex(1)
+
+ window.comboBox_visLayout.addItem("Classic")
+ window.comboBox_visLayout.addItem("Split")
+ window.comboBox_visLayout.addItem("Bottom")
+ visLayoutValue = int(self.settings.value('visLayout'))
+ window.comboBox_visLayout.setCurrentIndex(visLayoutValue)
+
+ currentRes = self.settings.value('outputWidth')+'x'+self.settings.value('outputHeight')
+ for i, res in enumerate(self.resolutions):
+ window.comboBox_resolution.addItem(res)
+ if res == currentRes:
+ currentRes = i
+ window.comboBox_resolution.setCurrentIndex(currentRes)
+ window.comboBox_resolution.currentIndexChanged.connect(self.updateResolution)
+
+ # FIXME This needs to be changed in a future commit.
+ # We should be setting these values somewhere else.
+ window.spinBox_fontSize.setValue(int(int(self.settings.value("outputHeight")) / 14 ))
+ window.spinBox_xTextAlign.setValue(int(int(self.settings.value('outputWidth'))/2))
+ window.spinBox_yTextAlign.setValue(int(int(self.settings.value('outputHeight'))/2))
window.lineEdit_textColor.setText('%s,%s,%s' % self.textColor)
window.lineEdit_visColor.setText('%s,%s,%s' % self.visColor)
@@ -181,30 +182,31 @@ class Main(QtCore.QObject):
titleFont = self.settings.value("titleFont")
if not titleFont == None:
- window.fontComboBox.setCurrentFont(QFont(titleFont))
+ window.fontComboBox_titleFont.setCurrentFont(QFont(titleFont))
alignment = self.settings.value("alignment")
if not alignment == None:
- window.alignmentComboBox.setCurrentIndex(int(alignment))
+ window.comboBox_textAlign.setCurrentIndex(int(alignment))
fontSize = self.settings.value("fontSize")
if not fontSize == None:
- window.fontsizeSpinBox.setValue(int(fontSize))
+ window.spinBox_fontSize.setValue(int(fontSize))
xPosition = self.settings.value("xPosition")
if not xPosition == None:
- window.textXSpinBox.setValue(int(xPosition))
+ window.spinBox_xTextAlign.setValue(int(xPosition))
yPosition = self.settings.value("yPosition")
if not yPosition == None:
- window.textYSpinBox.setValue(int(yPosition))
+ window.spinBox_yTextAlign.setValue(int(yPosition))
- window.fontComboBox.currentFontChanged.connect(self.drawPreview)
+ window.fontComboBox_titleFont.currentFontChanged.connect(self.drawPreview)
window.lineEdit_title.textChanged.connect(self.drawPreview)
- window.alignmentComboBox.currentIndexChanged.connect(self.drawPreview)
- window.textXSpinBox.valueChanged.connect(self.drawPreview)
- window.textYSpinBox.valueChanged.connect(self.drawPreview)
- window.fontsizeSpinBox.valueChanged.connect(self.drawPreview)
+ window.comboBox_textAlign.currentIndexChanged.connect(self.drawPreview)
+ window.comboBox_visLayout.currentIndexChanged.connect(self.drawPreview)
+ window.spinBox_xTextAlign.valueChanged.connect(self.drawPreview)
+ window.spinBox_yTextAlign.valueChanged.connect(self.drawPreview)
+ window.spinBox_fontSize.valueChanged.connect(self.drawPreview)
window.lineEdit_textColor.textChanged.connect(self.drawPreview)
window.lineEdit_visColor.textChanged.connect(self.drawPreview)
-
+
self.drawPreview()
window.show()
@@ -214,11 +216,11 @@ class Main(QtCore.QObject):
self.previewThread.quit()
self.previewThread.wait()
- self.settings.setValue("titleFont", self.window.fontComboBox.currentFont().toString())
- self.settings.setValue("alignment", str(self.window.alignmentComboBox.currentIndex()))
- self.settings.setValue("fontSize", str(self.window.fontsizeSpinBox.value()))
- self.settings.setValue("xPosition", str(self.window.textXSpinBox.value()))
- self.settings.setValue("yPosition", str(self.window.textYSpinBox.value()))
+ self.settings.setValue("titleFont", self.window.fontComboBox_titleFont.currentFont().toString())
+ self.settings.setValue("alignment", str(self.window.comboBox_textAlign.currentIndex()))
+ self.settings.setValue("fontSize", str(self.window.spinBox_fontSize.value()))
+ self.settings.setValue("xPosition", str(self.window.spinBox_xTextAlign.value()))
+ self.settings.setValue("yPosition", str(self.window.spinBox_yTextAlign.value()))
self.settings.setValue("visColor", self.window.lineEdit_visColor.text())
self.settings.setValue("textColor", self.window.lineEdit_textColor.text())
@@ -230,7 +232,7 @@ class Main(QtCore.QObject):
if not fileName == "":
self.settings.setValue("inputDir", os.path.dirname(fileName))
- self.window.label_input.setText(fileName)
+ self.window.lineEdit_audioFile.setText(fileName)
def openOutputFileDialog(self):
outputDir = self.settings.value("outputDir", expanduser("~"))
@@ -240,7 +242,7 @@ class Main(QtCore.QObject):
if not fileName == "":
self.settings.setValue("outputDir", os.path.dirname(fileName))
- self.window.label_output.setText(fileName)
+ self.window.lineEdit_outputFile.setText(fileName)
def openBackgroundFileDialog(self):
backgroundDir = self.settings.value("backgroundDir", expanduser("~"))
@@ -250,7 +252,7 @@ class Main(QtCore.QObject):
if not fileName == "":
self.settings.setValue("backgroundDir", os.path.dirname(fileName))
- self.window.label_background.setText(fileName)
+ self.window.lineEdit_background.setText(fileName)
self.drawPreview()
def createAudioVisualisation(self):
@@ -265,37 +267,45 @@ class Main(QtCore.QObject):
self.videoWorker.progressBarSetText.connect(self.progressBarSetText)
self.videoThread.start()
- self.videoTask.emit(self.window.label_background.text(),
+ self.videoTask.emit(self.window.lineEdit_background.text(),
self.window.lineEdit_title.text(),
- self.window.fontComboBox.currentFont(),
- self.window.fontsizeSpinBox.value(),
- self.window.alignmentComboBox.currentIndex(),
- self.window.textXSpinBox.value(),
- self.window.textYSpinBox.value(),
+ self.window.fontComboBox_titleFont.currentFont(),
+ self.window.spinBox_fontSize.value(),
+ self.window.comboBox_textAlign.currentIndex(),
+ self.window.spinBox_xTextAlign.value(),
+ self.window.spinBox_yTextAlign.value(),
core.Core.RGBFromString(self.window.lineEdit_textColor.text()),
core.Core.RGBFromString(self.window.lineEdit_visColor.text()),
- self.window.label_input.text(),
- self.window.label_output.text())
+ self.window.lineEdit_audioFile.text(),
+ self.window.lineEdit_outputFile.text())
def progressBarUpdated(self, value):
- self.window.progressBar_create.setValue(value)
+ self.window.progressBar_createVideo.setValue(value)
def progressBarSetText(self, value):
- self.window.progressBar_create.setFormat(value)
+ self.window.progressBar_createVideo.setFormat(value)
def videoCreated(self):
self.videoThread.quit()
self.videoThread.wait()
+ def updateResolution(self):
+ resIndex = int(window.comboBox_resolution.currentIndex())
+ res = self.resolutions[resIndex].split('x')
+ self.settings.setValue('outputWidth',res[0])
+ self.settings.setValue('outputHeight',res[1])
+ self.drawPreview
+
def drawPreview(self):
- self.newTask.emit(self.window.label_background.text(),
+ self.settings.setValue('visLayout', self.window.comboBox_visLayout.currentIndex())
+ self.newTask.emit(self.window.lineEdit_background.text(),
self.window.lineEdit_title.text(),
- self.window.fontComboBox.currentFont(),
- self.window.fontsizeSpinBox.value(),
- self.window.alignmentComboBox.currentIndex(),
- self.window.textXSpinBox.value(),
- self.window.textYSpinBox.value(),
+ self.window.fontComboBox_titleFont.currentFont(),
+ self.window.spinBox_fontSize.value(),
+ self.window.comboBox_textAlign.currentIndex(),
+ self.window.spinBox_xTextAlign.value(),
+ self.window.spinBox_yTextAlign.value(),
core.Core.RGBFromString(self.window.lineEdit_textColor.text()),
core.Core.RGBFromString(self.window.lineEdit_visColor.text()))
# self.processTask.emit()
@@ -304,7 +314,7 @@ class Main(QtCore.QObject):
self._scaledPreviewImage = image
self._previewPixmap = QtGui.QPixmap.fromImage(self._scaledPreviewImage)
- self.window.label_preview.setPixmap(self._previewPixmap)
+ self.window.label_previewContainer.setPixmap(self._previewPixmap)
def pickColor(self, colorTarget):
color = QtGui.QColorDialog.getColor()
@@ -319,6 +329,12 @@ class Main(QtCore.QObject):
window.pushButton_visColor.setStyleSheet(btnStyle)
def LoadDefaultSettings(self):
+ self.resolutions = [
+ '1920x1080',
+ '1280x720',
+ '854x480'
+ ]
+
default = {
"outputWidth": 1280,
"outputHeight": 720,
@@ -328,7 +344,8 @@ def LoadDefaultSettings(self):
"outputVideoCodec": "libx264",
"outputVideoFormat": "yuv420p",
"outputPreset": "medium",
- "outputFormat": "mp4"
+ "outputFormat": "mp4",
+ "visLayout": 0
}
for parm, value in default.items():
@@ -345,12 +362,12 @@ else:
# gui mode
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
- window = uic.loadUi("main.ui")
+ window = uic.loadUi("mainwindow.ui")
# window.adjustSize()
desc = QtGui.QDesktopWidget()
dpi = desc.physicalDpiX()
+
topMargin = 0 if (dpi == 96) else int(10 * (dpi / 96))
-
window.resize(window.width() * (dpi / 96), window.height() * (dpi / 96))
window.verticalLayout_2.setContentsMargins(0, topMargin, 0, 0)
diff --git a/main.ui b/main.ui
index c2892c5..5acb7eb 100644
--- a/main.ui
+++ b/main.ui
@@ -373,25 +373,6 @@
- -
-
-
-
- 32
- 32
-
-
-
-
-
-
-
- 32
- 32
-
-
-
-
-
@@ -405,25 +386,6 @@
- -
-
-
-
- 32
- 32
-
-
-
-
-
-
-
- 32
- 32
-
-
-
-
-
diff --git a/mainwindow.ui b/mainwindow.ui
new file mode 100644
index 0000000..3dbb817
--- /dev/null
+++ b/mainwindow.ui
@@ -0,0 +1,1120 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 1139
+ 658
+
+
+
+ MainWindow
+
+
+
+ false
+
+
+
+ 9
+
+
+ 0
+
+ -
+
+
-
+
+
+ 0
+
+
-
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::Minimum
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 356
+ 280
+
+
+
+
+ 0
+ 0
+
+
+
+ false
+
+
+ background-color:rgba(255, 255, 255, 15);
+
+
+
+
+
+
+ true
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::Minimum
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+ -
+
+
+ 3
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 280
+ 200
+
+
+
+
+
+
+ Qt::ScrollBarAlwaysOn
+
+
+ Qt::ScrollBarAlwaysOff
+
+
+
+
+
+ -
+
+
+ 3
+
+
-
+
+
-
+
+
+ Open Project
+
+
+
+ -
+
+
+ Save Project
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::Fixed
+
+
+
+ 20
+ 10
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 280
+ 0
+
+
+
+
+ -
+
+
-
+
+
+ Add Component
+
+
+
+ -
+
+
+ Remove Component
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
-
+
+
+ Open Preset
+
+
+
+ -
+
+
+ Save Preset
+
+
+
+
+
+
+
+
+
+ -
+
+
+ QLayout::SetFixedSize
+
+
+ 4
+
+
-
+
+
+ 8
+
+
+ 8
+
+
-
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 100
+ 0
+
+
+
+
+ 80
+ 0
+
+
+
+ Audio File
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 340
+ 28
+
+
+
+
+ 16777215
+ 28
+
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+
+ 0
+ 28
+
+
+
+
+ 16777215
+ 28
+
+
+
+ ...
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 100
+ 0
+
+
+
+ Background
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 28
+
+
+
+
+ 16777215
+ 28
+
+
+
+
+ -
+
+
+
+ 0
+ 28
+
+
+
+
+ 16777215
+ 28
+
+
+
+ ...
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 100
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+ Output File
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 28
+
+
+
+
+ 16777215
+ 28
+
+
+
+
+ -
+
+
+
+ 0
+ 28
+
+
+
+
+ 16777215
+ 28
+
+
+
+ ...
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 98
+ 0
+
+
+
+ Video Format
+
+
+
+ -
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Video Preset
+
+
+
+ -
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 98
+ 0
+
+
+
+ Video Codec
+
+
+
+ -
+
+
+
+ 150
+ 0
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 5
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Resolution
+
+
+
+ -
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 98
+ 0
+
+
+
+ Audio Codec
+
+
+
+ -
+
+
+
+ 150
+ 0
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 10
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Bitrate
+
+
+
+ -
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::Maximum
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+ 0
+
+
+
+
-
+
+
+ 0
+
+
-
+
+
+ Title
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 300
+ 0
+
+
+
+ Testing New GUI
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ X
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 0
+ 0
+
+
+
+ 0
+
+
+ 999999999
+
+
+ 0
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Y
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+ 999999999
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Font
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 140
+ 0
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Font Size
+
+
+
+ -
+
+
+ 500
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Text Layout
+
+
+
+ -
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+ Text Color
+
+
+
+ -
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Visualizer Layout
+
+
+
+ -
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+ Visulizer Color
+
+
+
+ -
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::Minimum
+
+
+
+ 20
+ 80
+
+
+
+
+
+
+
+ verticalSpacer_4
+
+
+
+
+
+ -
+
+
+ 0
+
+
+ 20
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ 24
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Minimum
+
+
+
+ 10
+ 20
+
+
+
+
+ -
+
+
+ Create video
+
+
+
+ -
+
+
+ Cancel
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/preview_thread.py b/preview_thread.py
index 8195712..593a70f 100644
--- a/preview_thread.py
+++ b/preview_thread.py
@@ -71,10 +71,7 @@ class Worker(QtCore.QObject):
im = self.core.drawBars(spectrum, im, nextPreviewInformation["visColor"])
self._image = ImageQt(im)
- self._previewImage = QtGui.QImage(self._image)
-
- self._scaledPreviewImage = self._previewImage.scaled(320, 180, QtCore.Qt.IgnoreAspectRatio, QtCore.Qt.SmoothTransformation)
-
- self.imageCreated.emit(self._scaledPreviewImage)
+ self.imageCreated.emit(QtGui.QImage(self._image))
+
except Empty:
True
--
cgit v1.2.3
From 75c1c65c9d63515a1488b63e9df9971984e1f7e8 Mon Sep 17 00:00:00 2001
From: DH4
Date: Sun, 28 May 2017 06:34:34 -0500
Subject: Integration with tassaron2 modular design. True Alpha Rendering
added, several bug fixes.
---
components/__init__.py | 1 +
components/original.py | 113 +++++
components/original.ui | 92 ++++
components/text.py | 59 +++
components/text.ui | 329 ++++++++++++++
core.py | 107 +----
main.py | 104 +++--
main.ui | 564 ------------------------
mainwindow.ui | 1120 +++++++++++++++++-------------------------------
preview_thread.py | 42 +-
video_thread.py | 82 ++--
11 files changed, 1107 insertions(+), 1506 deletions(-)
create mode 100644 components/__init__.py
create mode 100644 components/original.py
create mode 100644 components/original.ui
create mode 100644 components/text.py
create mode 100644 components/text.ui
delete mode 100644 main.ui
diff --git a/components/__init__.py b/components/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/components/__init__.py
@@ -0,0 +1 @@
+
diff --git a/components/original.py b/components/original.py
new file mode 100644
index 0000000..d1caa7b
--- /dev/null
+++ b/components/original.py
@@ -0,0 +1,113 @@
+''' Original Audio Visualization '''
+import numpy
+from PIL import Image, ImageDraw
+from PyQt4 import uic
+import os, random
+
+
+class Component:
+ def widget(self,parent):
+ self.parent = parent
+
+ page = uic.loadUi(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'original.ui'))
+ page.comboBox_visLayout.addItem("Classic")
+ page.comboBox_visLayout.addItem("Split")
+ page.comboBox_visLayout.addItem("Bottom")
+ #visLayoutValue = int(self.settings.value('visLayout'))
+ page.comboBox_visLayout.setCurrentIndex(0)
+ page.comboBox_visLayout.currentIndexChanged.connect(self.update)
+
+ return page
+ def update(self):
+ self.layout = self.page.comboBox_visLayout.currentIndex()
+ print(self.layout)
+ self.parent.drawPreview()
+
+ def previewRender(self, previewWorker, widget):
+ spectrum = numpy.fromfunction(lambda x: 0.008*(x-128)**2, (255,), dtype="int16")
+ width = int(previewWorker.core.settings.value('outputWidth'))
+ height = int(previewWorker.core.settings.value('outputHeight'))
+ return drawBars(width, height, spectrum, (255, 255, 255), self.layout)
+
+ def preFrameRender(self, **kwargs):
+ for kwarg, value in kwargs.items():
+ exec('self.%s = value' % kwarg)
+ self.smoothConstantDown = 0.08
+ self.smoothConstantUp = 0.8
+ self.lastSpectrum = None
+
+ def frameRender(self, moduleNo, frameNo):
+ self.lastSpectrum = transformData(frameNo, self.completeAudioArray, self.sampleSize,
+ self.smoothConstantDown, self.smoothConstantUp, self.lastSpectrum)
+ width = int(self.worker.core.settings.value('outputWidth'))
+ height = int(self.worker.core.settings.value('outputHeight'))
+ return drawBars(width, height, self.lastSpectrum, (255,255,255), self.layout)
+
+def transformData(i, completeAudioArray, sampleSize, smoothConstantDown, smoothConstantUp, lastSpectrum):
+ if len(completeAudioArray) < (i + sampleSize):
+ sampleSize = len(completeAudioArray) - i
+ numpy.seterr(divide='ignore')
+ window = numpy.hanning(sampleSize)
+ data = completeAudioArray[i:i+sampleSize][::1] * window
+ paddedSampleSize = 2048
+ paddedData = numpy.pad(data, (0, paddedSampleSize - sampleSize), 'constant')
+ spectrum = numpy.fft.fft(paddedData)
+ sample_rate = 44100
+ frequencies = numpy.fft.fftfreq(len(spectrum), 1./sample_rate)
+
+ y = abs(spectrum[0:int(paddedSampleSize/2) - 1])
+
+ # filter the noise away
+ # y[y<80] = 0
+
+ y = 20 * numpy.log10(y)
+ y[numpy.isinf(y)] = 0
+
+ if lastSpectrum is not None:
+ lastSpectrum[y < lastSpectrum] = y[y < lastSpectrum] * smoothConstantDown + lastSpectrum[y < lastSpectrum] * (1 - smoothConstantDown)
+ lastSpectrum[y >= lastSpectrum] = y[y >= lastSpectrum] * smoothConstantUp + lastSpectrum[y >= lastSpectrum] * (1 - smoothConstantUp)
+ else:
+ lastSpectrum = y
+
+ x = frequencies[0:int(paddedSampleSize/2) - 1]
+
+ return lastSpectrum
+
+def drawBars(width, height, spectrum, color, layout):
+ vH = height-height/8
+ bF = width / 64
+ bH = bF / 2
+ bQ = bF / 4
+ imTop = Image.new("RGBA", (width, height),(0,0,0,0))
+ draw = ImageDraw.Draw(imTop)
+ r, g, b = color
+ color2 = (r, g, b, 125)
+
+ 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)
+
+ im = Image.new("RGBA", (width, height),(0,0,0,0))
+
+ if layout == 0:
+ y = 0 - int(height/100*43)
+ im.paste(imTop, (0, y), mask=imTop)
+ y = 0 + int(height/100*43)
+ im.paste(imBottom, (0, y), mask=imBottom)
+
+ if layout == 1:
+ y = 0 + int(height/100*10)
+ im.paste(imTop, (0, y), mask=imTop)
+ y = 0 - int(height/100*10)
+ im.paste(imBottom, (0, y), mask=imBottom)
+
+ if layout == 2:
+ y = 0 + int(height/100*10)
+ im.paste(imTop, (0, y), mask=imTop)
+
+ return im
diff --git a/components/original.ui b/components/original.ui
new file mode 100644
index 0000000..0e6dd98
--- /dev/null
+++ b/components/original.ui
@@ -0,0 +1,92 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 584
+ 169
+
+
+
+ Form
+
+
+
+
+ 10
+ 10
+ 567
+ 29
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Visualizer Layout
+
+
+
+ -
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+ Visualizer Color
+
+
+
+ -
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+
+
+
+
+
+
diff --git a/components/text.py b/components/text.py
new file mode 100644
index 0000000..68b02fe
--- /dev/null
+++ b/components/text.py
@@ -0,0 +1,59 @@
+''' Title Text '''
+import numpy
+from PIL import Image, ImageDraw
+from PyQt4 import uic
+import os
+
+
+class Component:
+ def widget(self,parent):
+ page = uic.loadUi(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'text.ui'))
+ return page
+ def previewRender(self, previewWorker, widget):
+ width = int(previewWorker.core.settings.value('outputWidth'))
+ height = int(previewWorker.core.settings.value('outputHeight'))
+ im = Image.new("RGBA", (width, height),(0,0,0,0))
+
+ return im
+
+ def preFrameRender(self, **kwargs):
+ pass
+ def frameRender(self, moduleNo, frameNo):
+ width = int(previewWorker.core.settings.value('outputWidth'))
+ height = int(previewWorker.core.settings.value('outputHeight'))
+ im = Image.new("RGBA", (width, height),(0,0,0,0))
+
+ return im
+
+ '''
+ self._image = ImageQt(im)
+
+ self._image1 = QtGui.QImage(self._image)
+ painter = QPainter(self._image1)
+ font = titleFont
+ font.setPixelSize(fontSize)
+ painter.setFont(font)
+ painter.setPen(QColor(*textColor))
+
+ yPosition = yOffset
+
+ fm = QtGui.QFontMetrics(font)
+ if alignment == 0: #Left
+ xPosition = xOffset
+ if alignment == 1: #Middle
+ xPosition = xOffset - fm.width(titleText)/2
+ if alignment == 2: #Right
+ xPosition = xOffset - fm.width(titleText)
+ painter.drawText(xPosition, yPosition, titleText)
+ painter.end()
+
+ buffer = QtCore.QBuffer()
+ buffer.open(QtCore.QIODevice.ReadWrite)
+ self._image1.save(buffer, "PNG")
+
+ strio = io.BytesIO()
+ strio.write(buffer.data())
+ buffer.close()
+ strio.seek(0)
+ return Image.open(strio)
+ '''
diff --git a/components/text.ui b/components/text.ui
new file mode 100644
index 0000000..4431278
--- /dev/null
+++ b/components/text.ui
@@ -0,0 +1,329 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 584
+ 169
+
+
+
+ Form
+
+
+
+
+ 10
+ 20
+ 567
+ 25
+
+
+
+
+ 0
+
+ -
+
+
+ Title
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 300
+ 0
+
+
+
+ Testing New GUI
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ X
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 0
+ 0
+
+
+
+ 0
+
+
+ 999999999
+
+
+ 0
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Y
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+ 999999999
+
+
+
+
+
+
+
+
+ 10
+ 51
+ 567
+ 29
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Font
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 140
+ 0
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Font Size
+
+
+
+ -
+
+
+ 500
+
+
+
+
+
+
+
+
+ 10
+ 86
+ 567
+ 29
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Text Layout
+
+
+
+ -
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+ Text Color
+
+
+
+ -
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+
+
+
+
+
+ 10
+ 121
+ 567
+ 29
+
+
+
+
+
+
+
+
diff --git a/core.py b/core.py
index 8292f5b..c8bfbca 100644
--- a/core.py
+++ b/core.py
@@ -42,8 +42,7 @@ class Core():
else:
return self.getVideoFrames(backgroundImage, preview)
- def drawBaseImage(self, backgroundFile, titleText, titleFont, fontSize, alignment,\
- xOffset, yOffset, textColor, visColor):
+ def drawBaseImage(self, backgroundFile):
if backgroundFile == '':
im = Image.new("RGB", (int(self.settings.value('outputWidth')), int(self.settings.value('outputHeight'))), "black")
else:
@@ -51,81 +50,10 @@ class Core():
if self._image == None or not self.lastBackgroundImage == backgroundFile:
self.lastBackgroundImage = backgroundFile
-
# resize if necessary
if not im.size == (int(self.settings.value('outputWidth')), int(self.settings.value('outputHeight'))):
im = im.resize((int(self.settings.value('outputWidth')), int(self.settings.value('outputHeight'))), Image.ANTIALIAS)
-
- self._image = ImageQt(im)
-
- self._image1 = QtGui.QImage(self._image)
- painter = QPainter(self._image1)
- font = titleFont
- font.setPixelSize(fontSize)
- painter.setFont(font)
- painter.setPen(QColor(*textColor))
-
- fm = QtGui.QFontMetrics(font)
- yPosition = yOffset + fm.height()/6
-
- if alignment == 0: #Left
- xPosition = xOffset
- if alignment == 1: #Middle
- xPosition = xOffset - fm.width(titleText)/2
- if alignment == 2: #Right
- xPosition = xOffset - fm.width(titleText)
- painter.drawText(xPosition, yPosition, titleText)
- painter.end()
-
- buffer = QtCore.QBuffer()
- buffer.open(QtCore.QIODevice.ReadWrite)
- self._image1.save(buffer, "PNG")
-
- strio = io.BytesIO()
- strio.write(buffer.data())
- buffer.close()
- strio.seek(0)
- return Image.open(strio)
-
- def drawBars(self, spectrum, image, color):
-
- width = int(self.settings.value('outputWidth'))
- height = int(int(self.settings.value('outputHeight'))/2)
-
- vH = height-height/8
- bF = int(self.settings.value('outputWidth')) / 64
- bH = bF / 2
- bQ = bF / 4
- imTop = Image.new("RGBA", (width, height))
- draw = ImageDraw.Draw(imTop)
- r, g, b = color
- color2 = (r, g, b, 50)
-
- bP = int(self.settings.value('outputHeight')) / 800
-
- 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)
-
- im = Image.new("RGB", (int(self.settings.value('outputWidth')), int(self.settings.value('outputHeight'))), "black")
- im.paste(image, (0, 0))
-
- layout = int(self.settings.value('visLayout'))
-
- if layout == 0:
- im.paste(imTop, (0, 0), mask=imTop)
- im.paste(imBottom, (0, int(vH+bF*1.8)), mask=imBottom)
-
- if layout == 1:
- im.paste(imTop, (0, int(height+bF*1.5)), mask=imTop)
- im.paste(imBottom, (0, int(0-bF*1.5)), mask=imBottom)
-
- if layout == 2:
- im.paste(imTop, (0, int(height+bF*1.5)), mask=imTop)
-
+
return im
def readAudioFile(self, filename):
@@ -159,41 +87,10 @@ class Core():
return completeAudioArray
- def transformData(self, i, completeAudioArray, sampleSize, smoothConstantDown, smoothConstantUp, lastSpectrum):
- if len(completeAudioArray) < (i + sampleSize):
- sampleSize = len(completeAudioArray) - i
-
- window = numpy.hanning(sampleSize)
- data = completeAudioArray[i:i+sampleSize][::1] * window
- paddedSampleSize = 2048
- paddedData = numpy.pad(data, (0, paddedSampleSize - sampleSize), 'constant')
- spectrum = numpy.fft.fft(paddedData)
- sample_rate = 44100
- frequencies = numpy.fft.fftfreq(len(spectrum), 1./sample_rate)
-
- y = abs(spectrum[0:int(paddedSampleSize/2) - 1])
-
- # filter the noise away
- # y[y<80] = 0
-
- y = 20 * numpy.log10(y)
- y[numpy.isinf(y)] = 0
-
- if lastSpectrum is not None:
- lastSpectrum[y < lastSpectrum] = y[y < lastSpectrum] * smoothConstantDown + lastSpectrum[y < lastSpectrum] * (1 - smoothConstantDown)
- lastSpectrum[y >= lastSpectrum] = y[y >= lastSpectrum] * smoothConstantUp + lastSpectrum[y >= lastSpectrum] * (1 - smoothConstantUp)
- else:
- lastSpectrum = y
-
- x = frequencies[0:int(paddedSampleSize/2) - 1]
-
- return lastSpectrum
-
def deleteTempDir(self):
if self.tempDir and os.path.exists(self.tempDir):
rmtree(self.tempDir)
-
def getVideoFrames(self, videoPath, firstOnly=False):
self.tempDir = os.path.join(tempfile.gettempdir(), 'audio-visualizer-python-data')
# recreate the temporary directory so it is empty
diff --git a/main.py b/main.py
index 4fe9315..b700ad7 100644
--- a/main.py
+++ b/main.py
@@ -10,15 +10,18 @@ import atexit
from queue import Queue
from PyQt4.QtCore import QSettings
import signal
+from importlib import import_module
import preview_thread, core, video_thread
class Command(QtCore.QObject):
- videoTask = QtCore.pyqtSignal(str, str, QFont, int, int, int, int, tuple, tuple, str, str)
+ videoTask = QtCore.pyqtSignal(str, str, QFont, int, int, int, int, tuple, tuple, str, str, list)
def __init__(self):
QtCore.QObject.__init__(self)
+ self.modules = []
+ self.selectedComponents = []
import argparse
self.parser = argparse.ArgumentParser(description='Create a visualization for an audio file')
@@ -90,7 +93,8 @@ class Command(QtCore.QObject):
self.textColor,
self.visColor,
self.args.input,
- self.args.output)
+ self.args.output,
+ self.selectedComponents)
def videoCreated(self):
self.videoThread.quit()
@@ -109,9 +113,9 @@ class Command(QtCore.QObject):
class Main(QtCore.QObject):
- newTask = QtCore.pyqtSignal(str, str, QFont, int, int, int, int, tuple, tuple)
+ newTask = QtCore.pyqtSignal(str, list)
processTask = QtCore.pyqtSignal()
- videoTask = QtCore.pyqtSignal(str, str, QFont, int, int, int, int, tuple, tuple, str, str)
+ videoTask = QtCore.pyqtSignal(str, str, str, list)
def __init__(self, window):
QtCore.QObject.__init__(self)
@@ -121,6 +125,8 @@ class Main(QtCore.QObject):
self.core = core.Core()
self.settings = QSettings('settings.ini', QSettings.IniFormat)
LoadDefaultSettings(self)
+
+ self.pages = []
# load colors as tuples from a comma-separated string
self.textColor = core.Core.RGBFromString(self.settings.value("textColor", '255, 255, 255'))
@@ -140,24 +146,26 @@ class Main(QtCore.QObject):
self.timer.timeout.connect(self.processTask.emit)
self.timer.start(500)
+ # begin decorating the window and connecting events
window.toolButton_selectAudioFile.clicked.connect(self.openInputFileDialog)
window.toolButton_selectBackground.clicked.connect(self.openBackgroundFileDialog)
window.toolButton_selectOutputFile.clicked.connect(self.openOutputFileDialog)
window.progressBar_createVideo.setValue(0)
window.pushButton_createVideo.clicked.connect(self.createAudioVisualisation)
window.setWindowTitle("Audio Visualizer")
- window.comboBox_textAlign.addItem("Left")
- window.comboBox_textAlign.addItem("Middle")
- window.comboBox_textAlign.addItem("Right")
- window.comboBox_textAlign.setCurrentIndex(1)
- window.comboBox_visLayout.addItem("Classic")
- window.comboBox_visLayout.addItem("Split")
- window.comboBox_visLayout.addItem("Bottom")
- visLayoutValue = int(self.settings.value('visLayout'))
- window.comboBox_visLayout.setCurrentIndex(visLayoutValue)
+ self.modules = self.findComponents()
+ for component in self.modules:
+ window.comboBox_componentSelection.addItem(component.__doc__)
+ window.listWidget_componentList.clicked.connect(lambda _: self.changeComponentWidget())
+ self.selectedComponents = []
+
+ self.window.pushButton_addComponent.clicked.connect( \
+ lambda _: self.addComponent(self.window.comboBox_componentSelection.currentIndex())
+ )
+ self.window.pushButton_removeComponent.clicked.connect(lambda _: self.removeComponent())
- currentRes = self.settings.value('outputWidth')+'x'+self.settings.value('outputHeight')
+ currentRes = str(self.settings.value('outputWidth'))+'x'+str(self.settings.value('outputHeight'))
for i, res in enumerate(self.resolutions):
window.comboBox_resolution.addItem(res)
if res == currentRes:
@@ -165,8 +173,12 @@ class Main(QtCore.QObject):
window.comboBox_resolution.setCurrentIndex(currentRes)
window.comboBox_resolution.currentIndexChanged.connect(self.updateResolution)
- # FIXME This needs to be changed in a future commit.
- # We should be setting these values somewhere else.
+ '''
+ window.comboBox_textAlign.addItem("Left")
+ window.comboBox_textAlign.addItem("Middle")
+ window.comboBox_textAlign.addItem("Right")
+ window.comboBox_textAlign.setCurrentIndex(1)
+
window.spinBox_fontSize.setValue(int(int(self.settings.value("outputHeight")) / 14 ))
window.spinBox_xTextAlign.setValue(int(int(self.settings.value('outputWidth'))/2))
window.spinBox_yTextAlign.setValue(int(int(self.settings.value('outputHeight'))/2))
@@ -206,7 +218,7 @@ class Main(QtCore.QObject):
window.spinBox_fontSize.valueChanged.connect(self.drawPreview)
window.lineEdit_textColor.textChanged.connect(self.drawPreview)
window.lineEdit_visColor.textChanged.connect(self.drawPreview)
-
+ '''
self.drawPreview()
window.show()
@@ -268,18 +280,10 @@ class Main(QtCore.QObject):
self.videoThread.start()
self.videoTask.emit(self.window.lineEdit_background.text(),
- self.window.lineEdit_title.text(),
- self.window.fontComboBox_titleFont.currentFont(),
- self.window.spinBox_fontSize.value(),
- self.window.comboBox_textAlign.currentIndex(),
- self.window.spinBox_xTextAlign.value(),
- self.window.spinBox_yTextAlign.value(),
- core.Core.RGBFromString(self.window.lineEdit_textColor.text()),
- core.Core.RGBFromString(self.window.lineEdit_visColor.text()),
self.window.lineEdit_audioFile.text(),
- self.window.lineEdit_outputFile.text())
+ self.window.lineEdit_outputFile.text(),
+ self.selectedComponents)
-
def progressBarUpdated(self, value):
self.window.progressBar_createVideo.setValue(value)
@@ -298,16 +302,8 @@ class Main(QtCore.QObject):
self.drawPreview
def drawPreview(self):
- self.settings.setValue('visLayout', self.window.comboBox_visLayout.currentIndex())
- self.newTask.emit(self.window.lineEdit_background.text(),
- self.window.lineEdit_title.text(),
- self.window.fontComboBox_titleFont.currentFont(),
- self.window.spinBox_fontSize.value(),
- self.window.comboBox_textAlign.currentIndex(),
- self.window.spinBox_xTextAlign.value(),
- self.window.spinBox_yTextAlign.value(),
- core.Core.RGBFromString(self.window.lineEdit_textColor.text()),
- core.Core.RGBFromString(self.window.lineEdit_visColor.text()))
+ #self.settings.setValue('visLayout', self.window.comboBox_visLayout.currentIndex())
+ self.newTask.emit(self.window.lineEdit_background.text(), self.selectedComponents)
# self.processTask.emit()
def showPreviewImage(self, image):
@@ -328,6 +324,40 @@ class Main(QtCore.QObject):
self.window.lineEdit_visColor.setText(RGBstring)
window.pushButton_visColor.setStyleSheet(btnStyle)
+ def findComponents(self):
+ def findComponents():
+ srcPath = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'components')
+ if os.path.exists(srcPath):
+ for f in os.listdir(srcPath):
+ name, ext = os.path.splitext(f)
+ if name.startswith("__"):
+ continue
+ elif ext == '.py':
+ yield name
+ return [import_module('components.%s' % name) for name in findComponents()]
+
+ def addComponent(self, moduleIndex):
+ self.window.listWidget_componentList.addItem(self.modules[moduleIndex].__doc__)
+ self.selectedComponents.append(self.modules[moduleIndex].Component())
+ self.selectedComponents[-1].page = self.selectedComponents[-1].widget(self)
+ self.pages.append(self.selectedComponents[-1].page)
+ self.window.stackedWidget.addWidget(self.pages[-1])
+ self.selectedComponents[-1].update()
+
+ def removeComponent(self):
+ for selected in self.window.listWidget_componentList.selectedItems():
+ index = self.window.listWidget_componentList.row(selected)
+ self.window.stackedWidget.removeWidget(self.pages[index])
+ self.window.listWidget_componentList.takeItem(index)
+ self.selectedComponents.pop(index)
+ print(self.selectedComponents)
+ self.drawPreview()
+
+ def changeComponentWidget(self):
+ selected = self.window.listWidget_componentList.selectedItems()
+ index = self.window.listWidget_componentList.row(selected[0])
+ self.window.stackedWidget.setCurrentIndex(index)
+
def LoadDefaultSettings(self):
self.resolutions = [
'1920x1080',
diff --git a/main.ui b/main.ui
deleted file mode 100644
index 5acb7eb..0000000
--- a/main.ui
+++ /dev/null
@@ -1,564 +0,0 @@
-
-
- MainWindow
-
-
-
- 0
- 0
- 635
- 600
-
-
-
-
- 0
- 0
-
-
-
-
- 635
- 600
-
-
-
- MainWindow
-
-
-
-
- 0
- 0
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 0
- 200
-
-
-
-
- 16777215
- 16777215
-
-
-
- GroupBox
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1
- 0
-
-
-
-
- 200
- 0
-
-
-
-
- 16777215
- 16777215
-
-
-
- PushButton
-
-
-
- -
-
-
-
- 2
- 0
-
-
-
- QFrame::Box
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
- 1
- 0
-
-
-
-
- 200
- 0
-
-
-
-
- 16777215
- 16777215
-
-
-
- PushButton
-
-
-
- -
-
-
-
- 2
- 0
-
-
-
- QFrame::Box
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
- 1
- 0
-
-
-
-
- 200
- 0
-
-
-
-
- 16777215
- 16777215
-
-
-
- PushButton
-
-
-
- -
-
-
-
- 2
- 0
-
-
-
- QFrame::Box
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
- 200
- 0
-
-
-
-
- 200
- 16777215
-
-
-
-
- 200
- 0
-
-
-
- QFrame::NoFrame
-
-
-
-
-
-
- -
-
-
-
-
- -
-
-
-
-
-
-
- 200
- 0
-
-
-
-
- 200
- 16777215
-
-
-
-
- 200
- 0
-
-
-
- QFrame::NoFrame
-
-
-
-
-
-
- -
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
- 999
-
-
-
- -
-
-
- Qt::LeftToRight
-
-
- X
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- -99999
-
-
- 99999
-
-
-
- -
-
-
- Y
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- -99999
-
-
- 99999
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
- 200
- 0
-
-
-
-
- 200
- 16777215
-
-
-
-
- 200
- 0
-
-
-
- QFrame::NoFrame
-
-
-
-
-
-
- -
-
-
-
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- -
-
-
-
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- 200
- 0
-
-
-
-
- 200
- 16777215
-
-
-
-
- 200
- 0
-
-
-
- QFrame::NoFrame
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 220
-
-
-
-
- 16777215
- 220
-
-
-
- GroupBox
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
-
-
-
-
-
-
-
-
- 320
- 180
-
-
-
-
- 320
- 180
-
-
-
- QFrame::Box
-
-
-
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
-
-
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 16777215
- 16777215
-
-
-
- GroupBox
-
-
-
-
-
-
-
-
-
- 24
-
-
- Qt::AlignCenter
-
-
- true
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- PushButton
-
-
-
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 0
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/mainwindow.ui b/mainwindow.ui
index 3dbb817..ce8233e 100644
--- a/mainwindow.ui
+++ b/mainwindow.ui
@@ -6,8 +6,8 @@
0
0
- 1139
- 658
+ 1165
+ 707
@@ -109,337 +109,46 @@
3
-
-
-
-
- 0
- 0
-
-
-
-
- 280
- 200
-
-
-
-
-
-
- Qt::ScrollBarAlwaysOn
-
-
- Qt::ScrollBarAlwaysOff
-
-
-
-
-
- -
-
-
- 3
-
-
-
-
-
-
-
-
- Open Project
-
-
-
- -
-
-
- Save Project
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
- QSizePolicy::Fixed
-
-
-
- 20
- 10
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 280
- 0
-
-
-
-
- -
-
-
-
-
-
- Add Component
-
-
-
- -
-
-
- Remove Component
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
+
+
+ 3
-
-
- -
-
-
-
-
- Open Preset
-
-
-
- -
-
-
- Save Preset
-
-
-
-
-
-
-
-
-
- -
-
-
- QLayout::SetFixedSize
-
-
- 4
-
-
-
-
-
- 8
-
-
- 8
-
-
-
-
-
- 0
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 100
- 0
-
-
-
-
- 80
- 0
-
-
-
- Audio File
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 340
- 28
-
-
-
-
- 16777215
- 28
-
-
-
-
- 0
- 0
-
-
-
-
- -
-
-
-
- 0
- 28
-
-
-
-
- 16777215
- 28
-
-
-
- ...
-
-
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 100
- 0
-
-
-
- Background
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 28
-
-
-
-
- 16777215
- 28
-
-
-
-
- -
-
-
-
- 0
- 28
-
-
-
-
- 16777215
- 28
-
-
-
- ...
-
-
+
+
-
+
+
+ Open Project
+
+
+
+ -
+
+
+ Save Project
+
+
+
+
-
-
- -
-
-
-
-
-
- 0
- 0
-
+
+
+ Qt::Vertical
-
-
- 100
- 0
-
+
+ QSizePolicy::Fixed
-
+
- 0
- 0
+ 20
+ 10
-
- Output File
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
+
-
-
+
0
@@ -448,259 +157,147 @@
- 0
- 28
-
-
-
-
- 16777215
- 28
-
-
-
-
- -
-
-
-
- 0
- 28
-
-
-
-
- 16777215
- 28
-
-
-
- ...
-
-
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 98
+ 280
0
-
- Video Format
-
-
-
- -
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Video Preset
-
-
-
+
+
-
+
+
+ Add Component
+
+
+
+ -
+
+
+ Remove Component
+
+
+
+
-
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 98
- 0
-
-
-
- Video Codec
-
-
-
- -
-
-
-
- 150
- 0
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 5
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Resolution
-
-
-
- -
-
-
-
+
+
+
+ 0
+ 0
+
+
+
-
-
+
-
-
-
-
- 0
- 0
-
-
-
-
- 98
- 0
-
-
+
- Audio Codec
-
-
-
- -
-
-
-
- 150
- 0
-
+ Open Preset
-
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 10
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
+
- Bitrate
-
-
-
- -
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
- QSizePolicy::Maximum
-
-
-
- 20
- 40
-
-
-
+ Save Preset
+
+
+
+
+
+
+ -
+
+
+ QLayout::SetFixedSize
+
+
+ 4
+
+
+ 0
+
-
-
+
-
+
0
0
-
-
+
+
+ 0
+ 180
+
+
+
+
+ 16777215
+ 180
+
+
+
+ QTabWidget::North
+
+
+ QTabWidget::Rounded
0
-
-
+
+
+ Input Settings
+
+
+
+ 10
+
-
-
-
+
+
0
-
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 100
+ 0
+
+
+
+
+ 80
+ 0
+
+
- Title
+ Audio File
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
+
0
@@ -709,158 +306,197 @@
- 300
- 0
+ 340
+ 28
-
- Testing New GUI
+
+
+ 16777215
+ 28
+
+
+
+
+ 0
+ 0
+
-
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
+
+
+
+ 0
+ 28
+
-
+
- 5
- 20
+ 16777215
+ 28
-
+
+ ...
+
+
+
+
+ -
+
-
-
+
0
0
+
+
+ 100
+ 0
+
+
- X
+ Background
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
+
-
+
0
0
-
+
- 80
- 16777215
+ 0
+ 28
-
+
- 0
- 0
+ 16777215
+ 28
-
- 0
-
-
- 999999999
-
-
- 0
-
-
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
+
+
+
+ 0
+ 28
+
-
+
- 5
- 20
+ 16777215
+ 28
-
+
+ ...
+
+
+
+
+
+
+
+
+ Encoder Settings
+
+
+
+ 10
+
+ -
+
-
-
+
0
0
+
+
+ 98
+ 0
+
+
- Y
+ Video Format
-
-
+
+
+ -
+
-
+
0
0
-
-
- 80
- 16777215
-
-
-
- 999999999
+
+ Video Preset
+ -
+
+
-
-
+
-
-
+
0
0
+
+
+ 98
+ 0
+
+
- Font
+ Video Codec
-
-
-
-
- 0
- 0
-
-
+
- 140
+ 150
0
-
-
+
Qt::Horizontal
@@ -870,13 +506,13 @@
5
- 20
+ 5
-
-
+
0
@@ -884,39 +520,48 @@
- Font Size
+ Resolution
-
-
-
- 500
-
-
+
-
-
+
-
-
+
0
0
+
+
+ 98
+ 0
+
+
- Text Layout
+ Audio Codec
-
-
+
+
+
+ 150
+ 0
+
+
+
-
-
+
Qt::Horizontal
@@ -926,186 +571,193 @@
5
- 20
+ 10
-
-
+
+
+
+ 0
+ 0
+
+
- Text Color
+ Bitrate
-
-
-
-
- 32
- 32
-
-
-
-
-
-
-
- 32
- 32
-
-
-
+
+
+
+
+
+
+
+ Export Video
+
+
+
+ 10
+
+ -
+
-
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 100
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+ Output File
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 28
+
+
+
+
+ 16777215
+ 28
+
+
+
+
+ -
+
+
+
+ 0
+ 28
+
+
+
+
+ 16777215
+ 28
+
+
+
+ ...
+
+
+
+
-
-
+
+
+ 0
+
-
-
-
-
- 0
- 0
-
+
+
+
+ 0
+ 0
+
-
- Visualizer Layout
+
+ 24
-
-
-
- -
-
+
Qt::Horizontal
- QSizePolicy::Fixed
+ QSizePolicy::Minimum
- 5
+ 10
20
-
-
+
- Visulizer Color
+ Create video
-
-
-
-
- 32
- 32
-
-
+
-
-
-
-
- 32
- 32
-
+ Cancel
- -
-
-
- -
-
-
- Qt::Vertical
-
-
- QSizePolicy::Minimum
-
-
-
- 20
- 80
-
-
-
-
-
- verticalSpacer_4
-
-
- -
-
-
- 0
-
-
- 20
-
-
- 0
-
-
- 0
-
-
-
+
+
+
+ 0
+ 0
+
+
0
- 0
+ 180
-
- 24
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Minimum
-
-
+
- 10
- 20
+ 16777215
+ 180
-
-
- -
-
-
- Create video
-
-
-
- -
-
-
- Cancel
+
+ -1
diff --git a/preview_thread.py b/preview_thread.py
index 593a70f..e8b2021 100644
--- a/preview_thread.py
+++ b/preview_thread.py
@@ -18,23 +18,17 @@ class Worker(QtCore.QObject):
self.core = core.Core()
self.queue = queue
self.core.settings = parent.settings
+ self.stackedWidget = parent.window.stackedWidget
- @pyqtSlot(str, str, QtGui.QFont, int, int, int, int, tuple, tuple)
- def createPreviewImage(self, backgroundImage, titleText, titleFont, fontSize,\
- alignment, xOffset, yOffset, textColor, visColor):
+ @pyqtSlot(str, list)
+ def createPreviewImage(self, backgroundImage, components):
# print('worker thread id: {}'.format(QtCore.QThread.currentThreadId()))
dic = {
"backgroundImage": backgroundImage,
- "titleText": titleText,
- "titleFont": titleFont,
- "fontSize": fontSize,
- "alignment": alignment,
- "xoffset": xOffset,
- "yoffset": yOffset,
- "textColor" : textColor,
- "visColor" : visColor
+ "components": components,
}
+ print(components)
self.queue.put(dic)
@pyqtSlot()
@@ -56,21 +50,21 @@ class Worker(QtCore.QObject):
else:
bgImage = bgImage[0]
- im = self.core.drawBaseImage(
- bgImage,
- nextPreviewInformation["titleText"],
- nextPreviewInformation["titleFont"],
- nextPreviewInformation["fontSize"],
- nextPreviewInformation["alignment"],
- nextPreviewInformation["xoffset"],
- nextPreviewInformation["yoffset"],
- nextPreviewInformation["textColor"],
- nextPreviewInformation["visColor"])
- spectrum = numpy.fromfunction(lambda x: 0.008*(x-128)**2, (255,), dtype="int16")
+ im = self.core.drawBaseImage(bgImage)
+ frame = Image.new("RGBA", (1280, 720),(0,0,0,255))
+ frame.paste(im)
- im = self.core.drawBars(spectrum, im, nextPreviewInformation["visColor"])
- self._image = ImageQt(im)
+ componentWidgets = [self.stackedWidget.widget(i) for i in range(self.stackedWidget.count())]
+ components = nextPreviewInformation["components"]
+ print(components)
+ print(componentWidgets)
+ for component, componentWidget in zip(components, componentWidgets):
+ print('drawing')
+ newFrame = Image.alpha_composite(frame,component.previewRender(self, componentWidget))
+ frame = Image.alpha_composite(frame,newFrame)
+
+ self._image = ImageQt(frame)
self.imageCreated.emit(QtGui.QImage(self._image))
except Empty:
diff --git a/video_thread.py b/video_thread.py
index 5b9a896..ccb2730 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -17,24 +17,15 @@ class Worker(QtCore.QObject):
QtCore.QObject.__init__(self)
self.core = core.Core()
self.core.settings = parent.settings
+ self.modules = parent.modules
+ self.stackedWidget = parent.window.stackedWidget
parent.videoTask.connect(self.createVideo)
-
- @pyqtSlot(str, str, QtGui.QFont, int, int, int, int, tuple, tuple, str, str)
- def createVideo(self, backgroundImage, titleText, titleFont, fontSize, alignment,\
- xOffset, yOffset, textColor, visColor, inputFile, outputFile):
+ @pyqtSlot(str, str, str, list)
+ def createVideo(self, backgroundImage, inputFile, outputFile, components):
# print('worker thread id: {}'.format(QtCore.QThread.currentThreadId()))
def getBackgroundAtIndex(i):
- return self.core.drawBaseImage(
- backgroundFrames[i],
- titleText,
- titleFont,
- fontSize,
- alignment,
- xOffset,
- yOffset,
- textColor,
- visColor)
+ return self.core.drawBaseImage(backgroundFrames[i])
progressBarValue = 0
self.progressBarUpdate.emit(progressBarValue)
@@ -84,40 +75,47 @@ class Worker(QtCore.QObject):
out_pipe = sp.Popen(ffmpegCommand,
stdin=sp.PIPE,stdout=sys.stdout, stderr=sys.stdout)
- smoothConstantDown = 0.08
- smoothConstantUp = 0.8
- lastSpectrum = None
+ # initialize components
+ componentWidgets = [self.stackedWidget.widget(i) for i in range(self.stackedWidget.count())]
+
+ print('######################## Data')
+ print(components)
+ print(componentWidgets)
sampleSize = 1470
-
+ for component, widget in zip(components, componentWidgets):
+ component.preFrameRender(worker=self, widget=widget, completeAudioArray=completeAudioArray, sampleSize=sampleSize)
+
numpy.seterr(divide='ignore')
+ frame = getBackgroundAtIndex(0)
bgI = 0
+ # create video for output
for i in range(0, len(completeAudioArray), sampleSize):
- # create video for output
- lastSpectrum = self.core.transformData(
- i,
- completeAudioArray,
- sampleSize,
- smoothConstantDown,
- smoothConstantUp,
- lastSpectrum)
- if imBackground != None:
- im = self.core.drawBars(lastSpectrum, imBackground, visColor)
- else:
- im = self.core.drawBars(lastSpectrum, getBackgroundAtIndex(bgI), visColor)
- if bgI < len(backgroundFrames)-1:
- bgI += 1
+ newFrame = Image.new("RGBA", (int(self.core.settings.value('outputWidth')), int(self.core.settings.value('outputHeight'))),(0,0,0,255))
+
+ if imBackground:
+ newFrame.paste(imBackground)
+ else:
+ newFrame.paste(getBackgroundAtIndex(bgI))
+
+ for compNo, comp in enumerate(components):
+ newFrame = Image.alpha_composite(newFrame,comp.frameRender(compNo, i))
+ if not imBackground:
+ if bgI < len(backgroundFrames)-1:
+ bgI += 1
# write to out_pipe
- try:
- out_pipe.stdin.write(im.tobytes())
- finally:
- True
-
- # increase progress bar value
- if progressBarValue + 1 <= (i / len(completeAudioArray)) * 100:
- progressBarValue = numpy.floor((i / len(completeAudioArray)) * 100)
- self.progressBarUpdate.emit(progressBarValue)
- self.progressBarSetText.emit('%s%%' % str(int(progressBarValue)))
+ try:
+ frame = Image.new("RGB", (int(self.core.settings.value('outputWidth')), int(self.core.settings.value('outputHeight'))),(0,0,0))
+ frame.paste(newFrame)
+ out_pipe.stdin.write(frame.tobytes())
+ finally:
+ True
+
+ # increase progress bar value
+ if progressBarValue + 1 <= (i / len(completeAudioArray)) * 100:
+ progressBarValue = numpy.floor((i / len(completeAudioArray)) * 100)
+ self.progressBarUpdate.emit(progressBarValue)
+ self.progressBarSetText.emit('%s%%' % str(int(progressBarValue)))
numpy.seterr(all='print')
--
cgit v1.2.3
From d9a5f2dd34c0bf14bfc99b58183b00d43217d889 Mon Sep 17 00:00:00 2001
From: DH4
Date: Sun, 28 May 2017 07:36:34 -0500
Subject: Fixed Resolution Change in preview. Removed debugging print
statements.
---
main.py | 3 +--
preview_thread.py | 8 +++-----
2 files changed, 4 insertions(+), 7 deletions(-)
diff --git a/main.py b/main.py
index b700ad7..77fc9cc 100644
--- a/main.py
+++ b/main.py
@@ -299,7 +299,7 @@ class Main(QtCore.QObject):
res = self.resolutions[resIndex].split('x')
self.settings.setValue('outputWidth',res[0])
self.settings.setValue('outputHeight',res[1])
- self.drawPreview
+ self.drawPreview()
def drawPreview(self):
#self.settings.setValue('visLayout', self.window.comboBox_visLayout.currentIndex())
@@ -350,7 +350,6 @@ class Main(QtCore.QObject):
self.window.stackedWidget.removeWidget(self.pages[index])
self.window.listWidget_componentList.takeItem(index)
self.selectedComponents.pop(index)
- print(self.selectedComponents)
self.drawPreview()
def changeComponentWidget(self):
diff --git a/preview_thread.py b/preview_thread.py
index e8b2021..7a7e619 100644
--- a/preview_thread.py
+++ b/preview_thread.py
@@ -28,7 +28,6 @@ class Worker(QtCore.QObject):
"backgroundImage": backgroundImage,
"components": components,
}
- print(components)
self.queue.put(dic)
@pyqtSlot()
@@ -51,16 +50,15 @@ class Worker(QtCore.QObject):
bgImage = bgImage[0]
im = self.core.drawBaseImage(bgImage)
- frame = Image.new("RGBA", (1280, 720),(0,0,0,255))
+ width = int(self.core.settings.value('outputWidth'))
+ height = int(self.core.settings.value('outputHeight'))
+ frame = Image.new("RGBA", (width, height),(0,0,0,255))
frame.paste(im)
componentWidgets = [self.stackedWidget.widget(i) for i in range(self.stackedWidget.count())]
components = nextPreviewInformation["components"]
- print(components)
- print(componentWidgets)
for component, componentWidget in zip(components, componentWidgets):
- print('drawing')
newFrame = Image.alpha_composite(frame,component.previewRender(self, componentWidget))
frame = Image.alpha_composite(frame,newFrame)
--
cgit v1.2.3
From e0eed5bff4316910a93937d904f1a913f365a252 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 28 May 2017 14:19:28 -0400
Subject: title text is now a component
plus numerous bugs removed and added
---
components/original.py | 46 +++-
components/text.py | 160 +++++++++----
core.py | 20 +-
main.py | 115 +++-------
main.ui | 602 +++++++++++++++++++++++++++++++++++++++++++++++++
preview_thread.py | 6 +-
video_thread.py | 11 +-
7 files changed, 798 insertions(+), 162 deletions(-)
create mode 100644 main.ui
diff --git a/components/original.py b/components/original.py
index d1caa7b..e901c21 100644
--- a/components/original.py
+++ b/components/original.py
@@ -1,13 +1,18 @@
''' Original Audio Visualization '''
import numpy
from PIL import Image, ImageDraw
-from PyQt4 import uic
+from PyQt4 import uic, QtGui
+from PyQt4.QtGui import QColor
import os, random
class Component:
- def widget(self,parent):
+ def __str__(self):
+ return __doc__
+
+ def widget(self, parent):
self.parent = parent
+ self.visColor = (255,255,255)
page = uic.loadUi(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'original.ui'))
page.comboBox_visLayout.addItem("Classic")
@@ -16,18 +21,24 @@ class Component:
#visLayoutValue = int(self.settings.value('visLayout'))
page.comboBox_visLayout.setCurrentIndex(0)
page.comboBox_visLayout.currentIndexChanged.connect(self.update)
-
+ page.lineEdit_visColor.setText('%s,%s,%s' % self.visColor)
+ page.pushButton_visColor.clicked.connect(lambda: self.pickColor())
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" % QColor(*self.visColor).name()
+ page.pushButton_visColor.setStyleSheet(btnStyle)
+ page.lineEdit_visColor.textChanged.connect(self.update)
+ self.page = page
return page
+
def update(self):
self.layout = self.page.comboBox_visLayout.currentIndex()
- print(self.layout)
+ self.visColor = RGBFromString(self.page.lineEdit_visColor.text())
self.parent.drawPreview()
- def previewRender(self, previewWorker, widget):
+ def previewRender(self, previewWorker):
spectrum = numpy.fromfunction(lambda x: 0.008*(x-128)**2, (255,), dtype="int16")
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
- return drawBars(width, height, spectrum, (255, 255, 255), self.layout)
+ return drawBars(width, height, spectrum, self.visColor, self.layout)
def preFrameRender(self, **kwargs):
for kwarg, value in kwargs.items():
@@ -41,7 +52,15 @@ class Component:
self.smoothConstantDown, self.smoothConstantUp, self.lastSpectrum)
width = int(self.worker.core.settings.value('outputWidth'))
height = int(self.worker.core.settings.value('outputHeight'))
- return drawBars(width, height, self.lastSpectrum, (255,255,255), self.layout)
+ return drawBars(width, height, self.lastSpectrum, self.visColor, self.layout)
+
+ def pickColor(self):
+ color = QtGui.QColorDialog.getColor()
+ if color.isValid():
+ RGBstring = '%s,%s,%s' % (str(color.red()), str(color.green()), str(color.blue()))
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" % color.name()
+ self.page.lineEdit_visColor.setText(RGBstring)
+ self.page.pushButton_visColor.setStyleSheet(btnStyle)
def transformData(i, completeAudioArray, sampleSize, smoothConstantDown, smoothConstantUp, lastSpectrum):
if len(completeAudioArray) < (i + sampleSize):
@@ -111,3 +130,16 @@ def drawBars(width, height, spectrum, color, layout):
im.paste(imTop, (0, y), mask=imTop)
return im
+
+def RGBFromString(string):
+ ''' turns an RGB string like "255, 255, 255" into a tuple '''
+ try:
+ tup = tuple([int(i) for i in string.split(',')])
+ if len(tup) != 3:
+ raise ValueError
+ for i in tup:
+ if i > 255 or i < 0:
+ raise ValueError
+ return tup
+ except:
+ return (255, 255, 255)
diff --git a/components/text.py b/components/text.py
index 68b02fe..814e13f 100644
--- a/components/text.py
+++ b/components/text.py
@@ -1,59 +1,131 @@
''' Title Text '''
-import numpy
from PIL import Image, ImageDraw
-from PyQt4 import uic
-import os
+from PyQt4.QtGui import QPainter, QColor, QFont
+from PyQt4 import uic, QtGui, QtCore
+from PIL.ImageQt import ImageQt
+import os, io
class Component:
- def widget(self,parent):
+ def __str__(self):
+ return __doc__
+
+ def widget(self, parent):
+ height = int(parent.settings.value('outputHeight'))
+ width = int(parent.settings.value('outputWidth'))
+ self.parent = parent
+ self.textColor = (255,255,255)
+ self.title = 'Text'
+ self.titleFont = None
+ self.alignment = 1
+ self.fontSize = height / 16
+ self.xPosition = width / 2
+ self.yPosition = height / 2
+
page = uic.loadUi(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'text.ui'))
+ page.comboBox_textAlign.addItem("Left")
+ page.comboBox_textAlign.addItem("Middle")
+ page.comboBox_textAlign.addItem("Right")
+ page.comboBox_textAlign.setCurrentIndex(1)
+
+ page.spinBox_fontSize.setValue(int(int(parent.settings.value("outputHeight")) / 14 ))
+ page.spinBox_xTextAlign.setValue(int(int(parent.settings.value('outputWidth'))/2))
+ page.spinBox_yTextAlign.setValue(int(int(parent.settings.value('outputHeight'))/2))
+
+ page.lineEdit_textColor.setText('%s,%s,%s' % self.textColor)
+ page.pushButton_textColor.clicked.connect(lambda: self.pickColor())
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" % QColor(*self.textColor).name()
+ page.pushButton_textColor.setStyleSheet(btnStyle)
+
+ page.lineEdit_title.setText(self.title)
+ if not self.titleFont == None:
+ page.fontComboBox_titleFont.setCurrentFont(QFont(self.titleFont))
+ page.comboBox_textAlign.setCurrentIndex(int(self.alignment))
+ page.spinBox_fontSize.setValue(int(self.fontSize))
+ page.spinBox_xTextAlign.setValue(int(self.xPosition))
+ page.spinBox_yTextAlign.setValue(int(self.yPosition))
+
+ page.fontComboBox_titleFont.currentFontChanged.connect(self.update)
+ page.lineEdit_title.textChanged.connect(self.update)
+ page.comboBox_textAlign.currentIndexChanged.connect(self.update)
+ page.spinBox_xTextAlign.valueChanged.connect(self.update)
+ page.spinBox_yTextAlign.valueChanged.connect(self.update)
+ page.spinBox_fontSize.valueChanged.connect(self.update)
+ page.lineEdit_textColor.textChanged.connect(self.update)
+ self.page = page
return page
- def previewRender(self, previewWorker, widget):
+
+ def update(self):
+ self.title = self.page.lineEdit_title.text()
+ self.alignment = self.page.comboBox_textAlign.currentIndex()
+ self.titleFont = self.page.fontComboBox_titleFont.currentFont()
+ self.fontSize = self.page.spinBox_fontSize.value()
+ self.xPosition = self.page.spinBox_xTextAlign.value()
+ self.yPosition = self.page.spinBox_yTextAlign.value()
+ self.textColor = RGBFromString(self.page.lineEdit_textColor.text())
+ self.parent.drawPreview()
+
+ def previewRender(self, previewWorker):
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
- im = Image.new("RGBA", (width, height),(0,0,0,0))
-
- return im
+ return self.addText(width, height)
def preFrameRender(self, **kwargs):
- pass
+ for kwarg, value in kwargs.items():
+ exec('self.%s = value' % kwarg)
+
def frameRender(self, moduleNo, frameNo):
- width = int(previewWorker.core.settings.value('outputWidth'))
- height = int(previewWorker.core.settings.value('outputHeight'))
+ width = int(self.worker.core.settings.value('outputWidth'))
+ height = int(self.worker.core.settings.value('outputHeight'))
+ return self.addText(width, height)
+
+ def addText(self, width, height):
im = Image.new("RGBA", (width, height),(0,0,0,0))
+ image = ImageQt(im)
+
+ image1 = QtGui.QImage(image)
+ painter = QPainter(image1)
+ self.titleFont.setPixelSize(self.fontSize)
+ painter.setFont(self.titleFont)
+ painter.setPen(QColor(*self.textColor))
- return im
+ fm = QtGui.QFontMetrics(self.titleFont)
+ if self.alignment == 0: #Left
+ self.xPosition = self.xPosition
+ if self.alignment == 1: #Middle
+ self.xPosition = self.xPosition - fm.width(self.title)/2
+ if self.alignment == 2: #Right
+ self.xPosition = self.xPosition - fm.width(self.title)
+ painter.drawText(self.xPosition, self.yPosition, self.title)
+ painter.end()
- '''
- self._image = ImageQt(im)
-
- self._image1 = QtGui.QImage(self._image)
- painter = QPainter(self._image1)
- font = titleFont
- font.setPixelSize(fontSize)
- painter.setFont(font)
- painter.setPen(QColor(*textColor))
-
- yPosition = yOffset
-
- fm = QtGui.QFontMetrics(font)
- if alignment == 0: #Left
- xPosition = xOffset
- if alignment == 1: #Middle
- xPosition = xOffset - fm.width(titleText)/2
- if alignment == 2: #Right
- xPosition = xOffset - fm.width(titleText)
- painter.drawText(xPosition, yPosition, titleText)
- painter.end()
-
- buffer = QtCore.QBuffer()
- buffer.open(QtCore.QIODevice.ReadWrite)
- self._image1.save(buffer, "PNG")
-
- strio = io.BytesIO()
- strio.write(buffer.data())
- buffer.close()
- strio.seek(0)
- return Image.open(strio)
- '''
+ buffer = QtCore.QBuffer()
+ buffer.open(QtCore.QIODevice.ReadWrite)
+ image1.save(buffer, "PNG")
+
+ strio = io.BytesIO()
+ strio.write(buffer.data())
+ buffer.close()
+ strio.seek(0)
+ return Image.open(strio)
+
+ def pickColor(self):
+ color = QtGui.QColorDialog.getColor()
+ if color.isValid():
+ RGBstring = '%s,%s,%s' % (str(color.red()), str(color.green()), str(color.blue()))
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" % color.name()
+ self.page.lineEdit_textColor.setText(RGBstring)
+ self.page.pushButton_textColor.setStyleSheet(btnStyle)
+
+def RGBFromString(string):
+ ''' turns an RGB string like "255, 255, 255" into a tuple '''
+ try:
+ tup = tuple([int(i) for i in string.split(',')])
+ if len(tup) != 3:
+ raise ValueError
+ for i in tup:
+ if i > 255 or i < 0:
+ raise ValueError
+ return tup
+ except:
+ return (255, 255, 255)
diff --git a/core.py b/core.py
index c8bfbca..5478f93 100644
--- a/core.py
+++ b/core.py
@@ -1,11 +1,9 @@
import sys, io, os
from PyQt4 import QtCore, QtGui, uic
-from PyQt4.QtGui import QPainter, QColor
from os.path import expanduser
import subprocess as sp
import numpy
-from PIL import Image, ImageDraw, ImageFont
-from PIL.ImageQt import ImageQt
+from PIL import Image
import tempfile
from shutil import rmtree
import atexit
@@ -34,7 +32,7 @@ class Core():
def parseBaseImage(self, backgroundImage, preview=False):
''' determines if the base image is a single frame or list of frames '''
if backgroundImage == "":
- return []
+ return ['']
else:
_, bgExt = os.path.splitext(backgroundImage)
if not bgExt == '.mp4':
@@ -112,17 +110,3 @@ class Core():
shell=True
)
return sorted([os.path.join(self.tempDir, f) for f in os.listdir(self.tempDir)])
-
- @staticmethod
- def RGBFromString(string):
- ''' turns an RGB string like "255, 255, 255" into a tuple '''
- try:
- tup = tuple([int(i) for i in string.split(',')])
- if len(tup) != 3:
- raise ValueError
- for i in tup:
- if i > 255 or i < 0:
- raise ValueError
- return tup
- except:
- return (255, 255, 255)
diff --git a/main.py b/main.py
index 77fc9cc..09d8e46 100644
--- a/main.py
+++ b/main.py
@@ -1,11 +1,6 @@
import sys, io, os
from PyQt4 import QtCore, QtGui, uic
-from PyQt4.QtGui import QPainter, QColor, QFont
from os.path import expanduser
-import subprocess as sp
-import numpy
-from PIL import Image, ImageDraw, ImageFont
-from PIL.ImageQt import ImageQt
import atexit
from queue import Queue
from PyQt4.QtCore import QSettings
@@ -14,9 +9,11 @@ from importlib import import_module
import preview_thread, core, video_thread
+# FIXME: commandline functionality broken until we decide how to implement it
+'''
class Command(QtCore.QObject):
- videoTask = QtCore.pyqtSignal(str, str, QFont, int, int, int, int, tuple, tuple, str, str, list)
+ videoTask = QtCore.pyqtSignal(str, str, str, list)
def __init__(self):
QtCore.QObject.__init__(self)
@@ -110,7 +107,7 @@ class Command(QtCore.QObject):
self.settings.setValue("visColor", '%s,%s,%s' % self.visColor)
self.settings.setValue("textColor", '%s,%s,%s' % self.textColor)
sys.exit(0)
-
+'''
class Main(QtCore.QObject):
newTask = QtCore.pyqtSignal(str, list)
@@ -127,10 +124,6 @@ class Main(QtCore.QObject):
LoadDefaultSettings(self)
self.pages = []
-
- # load colors as tuples from a comma-separated string
- self.textColor = core.Core.RGBFromString(self.settings.value("textColor", '255, 255, 255'))
- self.visColor = core.Core.RGBFromString(self.settings.value("visColor", '255, 255, 255'))
self.previewQueue = Queue()
@@ -174,49 +167,10 @@ class Main(QtCore.QObject):
window.comboBox_resolution.currentIndexChanged.connect(self.updateResolution)
'''
- window.comboBox_textAlign.addItem("Left")
- window.comboBox_textAlign.addItem("Middle")
- window.comboBox_textAlign.addItem("Right")
- window.comboBox_textAlign.setCurrentIndex(1)
-
- window.spinBox_fontSize.setValue(int(int(self.settings.value("outputHeight")) / 14 ))
- window.spinBox_xTextAlign.setValue(int(int(self.settings.value('outputWidth'))/2))
- window.spinBox_yTextAlign.setValue(int(int(self.settings.value('outputHeight'))/2))
-
- window.lineEdit_textColor.setText('%s,%s,%s' % self.textColor)
window.lineEdit_visColor.setText('%s,%s,%s' % self.visColor)
- window.pushButton_textColor.clicked.connect(lambda: self.pickColor('text'))
window.pushButton_visColor.clicked.connect(lambda: self.pickColor('vis'))
- btnStyle = "QPushButton { background-color : %s; outline: none; }" % QColor(*self.textColor).name()
- window.pushButton_textColor.setStyleSheet(btnStyle)
btnStyle = "QPushButton { background-color : %s; outline: none; }" % QColor(*self.visColor).name()
window.pushButton_visColor.setStyleSheet(btnStyle)
-
- titleFont = self.settings.value("titleFont")
- if not titleFont == None:
- window.fontComboBox_titleFont.setCurrentFont(QFont(titleFont))
-
- alignment = self.settings.value("alignment")
- if not alignment == None:
- window.comboBox_textAlign.setCurrentIndex(int(alignment))
- fontSize = self.settings.value("fontSize")
- if not fontSize == None:
- window.spinBox_fontSize.setValue(int(fontSize))
- xPosition = self.settings.value("xPosition")
- if not xPosition == None:
- window.spinBox_xTextAlign.setValue(int(xPosition))
- yPosition = self.settings.value("yPosition")
- if not yPosition == None:
- window.spinBox_yTextAlign.setValue(int(yPosition))
-
- window.fontComboBox_titleFont.currentFontChanged.connect(self.drawPreview)
- window.lineEdit_title.textChanged.connect(self.drawPreview)
- window.comboBox_textAlign.currentIndexChanged.connect(self.drawPreview)
- window.comboBox_visLayout.currentIndexChanged.connect(self.drawPreview)
- window.spinBox_xTextAlign.valueChanged.connect(self.drawPreview)
- window.spinBox_yTextAlign.valueChanged.connect(self.drawPreview)
- window.spinBox_fontSize.valueChanged.connect(self.drawPreview)
- window.lineEdit_textColor.textChanged.connect(self.drawPreview)
window.lineEdit_visColor.textChanged.connect(self.drawPreview)
'''
self.drawPreview()
@@ -227,7 +181,8 @@ class Main(QtCore.QObject):
self.timer.stop()
self.previewThread.quit()
self.previewThread.wait()
-
+ # TODO: replace remembered settings with presets/projects
+ '''
self.settings.setValue("titleFont", self.window.fontComboBox_titleFont.currentFont().toString())
self.settings.setValue("alignment", str(self.window.comboBox_textAlign.currentIndex()))
self.settings.setValue("fontSize", str(self.window.spinBox_fontSize.value()))
@@ -235,6 +190,7 @@ class Main(QtCore.QObject):
self.settings.setValue("yPosition", str(self.window.spinBox_yTextAlign.value()))
self.settings.setValue("visColor", self.window.lineEdit_visColor.text())
self.settings.setValue("textColor", self.window.lineEdit_textColor.text())
+ '''
def openInputFileDialog(self):
inputDir = self.settings.value("inputDir", expanduser("~"))
@@ -268,21 +224,26 @@ class Main(QtCore.QObject):
self.drawPreview()
def createAudioVisualisation(self):
- ffmpeg_cmd = self.settings.value("ffmpeg_cmd", expanduser("~"))
-
- self.videoThread = QtCore.QThread(self)
- self.videoWorker = video_thread.Worker(self)
-
- self.videoWorker.moveToThread(self.videoThread)
- self.videoWorker.videoCreated.connect(self.videoCreated)
- self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated)
- self.videoWorker.progressBarSetText.connect(self.progressBarSetText)
-
- self.videoThread.start()
- self.videoTask.emit(self.window.lineEdit_background.text(),
- self.window.lineEdit_audioFile.text(),
- self.window.lineEdit_outputFile.text(),
- self.selectedComponents)
+ # create output video if mandatory settings are filled in
+ if self.window.lineEdit_audioFile.text() and self.window.lineEdit_outputFile.text():
+ ffmpeg_cmd = self.settings.value("ffmpeg_cmd", expanduser("~"))
+
+ self.videoThread = QtCore.QThread(self)
+ self.videoWorker = video_thread.Worker(self)
+
+ self.videoWorker.moveToThread(self.videoThread)
+ self.videoWorker.videoCreated.connect(self.videoCreated)
+ self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated)
+ self.videoWorker.progressBarSetText.connect(self.progressBarSetText)
+
+ self.videoThread.start()
+ self.videoTask.emit(self.window.lineEdit_background.text(),
+ self.window.lineEdit_audioFile.text(),
+ self.window.lineEdit_outputFile.text(),
+ self.selectedComponents)
+ else:
+ # TODO: use QMessageBox or similar to alert user that fields are empty
+ pass
def progressBarUpdated(self, value):
self.window.progressBar_createVideo.setValue(value)
@@ -312,18 +273,6 @@ class Main(QtCore.QObject):
self.window.label_previewContainer.setPixmap(self._previewPixmap)
- def pickColor(self, colorTarget):
- color = QtGui.QColorDialog.getColor()
- if color.isValid():
- RGBstring = '%s,%s,%s' % (str(color.red()), str(color.green()), str(color.blue()))
- btnStyle = "QPushButton { background-color : %s; outline: none; }" % color.name()
- if colorTarget == 'text':
- self.window.lineEdit_textColor.setText(RGBstring)
- window.pushButton_textColor.setStyleSheet(btnStyle)
- elif colorTarget == 'vis':
- self.window.lineEdit_visColor.setText(RGBstring)
- window.pushButton_visColor.setStyleSheet(btnStyle)
-
def findComponents(self):
def findComponents():
srcPath = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'components')
@@ -339,8 +288,7 @@ class Main(QtCore.QObject):
def addComponent(self, moduleIndex):
self.window.listWidget_componentList.addItem(self.modules[moduleIndex].__doc__)
self.selectedComponents.append(self.modules[moduleIndex].Component())
- self.selectedComponents[-1].page = self.selectedComponents[-1].widget(self)
- self.pages.append(self.selectedComponents[-1].page)
+ self.pages.append(self.selectedComponents[-1].widget(self))
self.window.stackedWidget.addWidget(self.pages[-1])
self.selectedComponents[-1].update()
@@ -381,6 +329,8 @@ def LoadDefaultSettings(self):
if self.settings.value(parm) == None:
self.settings.setValue(parm,value)
+
+''' ####### commandline functionality broken until we decide how to implement it
if len(sys.argv) > 1:
# command line mode
app = QtGui.QApplication(sys.argv, False)
@@ -388,8 +338,9 @@ if len(sys.argv) > 1:
signal.signal(signal.SIGINT, command.cleanUp)
sys.exit(app.exec_())
else:
- # gui mode
- if __name__ == "__main__":
+'''
+# gui mode
+if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = uic.loadUi("mainwindow.ui")
# window.adjustSize()
diff --git a/main.ui b/main.ui
new file mode 100644
index 0000000..c2892c5
--- /dev/null
+++ b/main.ui
@@ -0,0 +1,602 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 635
+ 600
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 635
+ 600
+
+
+
+ MainWindow
+
+
+
+
+ 0
+ 0
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 200
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+ GroupBox
+
+
+
-
+
+
-
+
+
-
+
+
+
+ 1
+ 0
+
+
+
+
+ 200
+ 0
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+ PushButton
+
+
+
+ -
+
+
+
+ 2
+ 0
+
+
+
+ QFrame::Box
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 1
+ 0
+
+
+
+
+ 200
+ 0
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+ PushButton
+
+
+
+ -
+
+
+
+ 2
+ 0
+
+
+
+ QFrame::Box
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 1
+ 0
+
+
+
+
+ 200
+ 0
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+ PushButton
+
+
+
+ -
+
+
+
+ 2
+ 0
+
+
+
+ QFrame::Box
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 200
+ 0
+
+
+
+
+ 200
+ 16777215
+
+
+
+
+ 200
+ 0
+
+
+
+ QFrame::NoFrame
+
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
-
+
+
+
+ 200
+ 0
+
+
+
+
+ 200
+ 16777215
+
+
+
+
+ 200
+ 0
+
+
+
+ QFrame::NoFrame
+
+
+
+
+
+
+ -
+
+
-
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+ 999
+
+
+
+ -
+
+
+ Qt::LeftToRight
+
+
+ X
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ -99999
+
+
+ 99999
+
+
+
+ -
+
+
+ Y
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ -99999
+
+
+ 99999
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 200
+ 0
+
+
+
+
+ 200
+ 16777215
+
+
+
+
+ 200
+ 0
+
+
+
+ QFrame::NoFrame
+
+
+
+
+
+
+ -
+
+
+
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 200
+ 0
+
+
+
+
+ 200
+ 16777215
+
+
+
+
+ 200
+ 0
+
+
+
+ QFrame::NoFrame
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 220
+
+
+
+
+ 16777215
+ 220
+
+
+
+ GroupBox
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+
-
+
+
-
+
+
+
+ 320
+ 180
+
+
+
+
+ 320
+ 180
+
+
+
+ QFrame::Box
+
+
+
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+ GroupBox
+
+
+
-
+
+
-
+
+
+ 24
+
+
+ Qt::AlignCenter
+
+
+ true
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ PushButton
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/preview_thread.py b/preview_thread.py
index 7a7e619..b20e9a1 100644
--- a/preview_thread.py
+++ b/preview_thread.py
@@ -55,11 +55,9 @@ class Worker(QtCore.QObject):
frame = Image.new("RGBA", (width, height),(0,0,0,255))
frame.paste(im)
-
- componentWidgets = [self.stackedWidget.widget(i) for i in range(self.stackedWidget.count())]
components = nextPreviewInformation["components"]
- for component, componentWidget in zip(components, componentWidgets):
- newFrame = Image.alpha_composite(frame,component.previewRender(self, componentWidget))
+ for component in components:
+ newFrame = Image.alpha_composite(frame,component.previewRender(self))
frame = Image.alpha_composite(frame,newFrame)
self._image = ImageQt(frame)
diff --git a/video_thread.py b/video_thread.py
index ccb2730..8bef6ef 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -76,19 +76,16 @@ class Worker(QtCore.QObject):
stdin=sp.PIPE,stdout=sys.stdout, stderr=sys.stdout)
# initialize components
- componentWidgets = [self.stackedWidget.widget(i) for i in range(self.stackedWidget.count())]
-
print('######################## Data')
- print(components)
- print(componentWidgets)
+ print('loaded components: ', [str(component) for component in components])
sampleSize = 1470
- for component, widget in zip(components, componentWidgets):
- component.preFrameRender(worker=self, widget=widget, completeAudioArray=completeAudioArray, sampleSize=sampleSize)
+ for component in components:
+ component.preFrameRender(worker=self, completeAudioArray=completeAudioArray, sampleSize=sampleSize)
+ # create video for output
numpy.seterr(divide='ignore')
frame = getBackgroundAtIndex(0)
bgI = 0
- # create video for output
for i in range(0, len(completeAudioArray), sampleSize):
newFrame = Image.new("RGBA", (int(self.core.settings.value('outputWidth')), int(self.core.settings.value('outputHeight'))),(0,0,0,255))
--
cgit v1.2.3
From 5ed79ff5c62ba704a96c5d96641a4a5733b53c1e Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 28 May 2017 14:20:36 -0400
Subject: rm old ui file
---
main.ui | 602 ----------------------------------------------------------------
1 file changed, 602 deletions(-)
delete mode 100644 main.ui
diff --git a/main.ui b/main.ui
deleted file mode 100644
index c2892c5..0000000
--- a/main.ui
+++ /dev/null
@@ -1,602 +0,0 @@
-
-
- MainWindow
-
-
-
- 0
- 0
- 635
- 600
-
-
-
-
- 0
- 0
-
-
-
-
- 635
- 600
-
-
-
- MainWindow
-
-
-
-
- 0
- 0
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 0
- 200
-
-
-
-
- 16777215
- 16777215
-
-
-
- GroupBox
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1
- 0
-
-
-
-
- 200
- 0
-
-
-
-
- 16777215
- 16777215
-
-
-
- PushButton
-
-
-
- -
-
-
-
- 2
- 0
-
-
-
- QFrame::Box
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
- 1
- 0
-
-
-
-
- 200
- 0
-
-
-
-
- 16777215
- 16777215
-
-
-
- PushButton
-
-
-
- -
-
-
-
- 2
- 0
-
-
-
- QFrame::Box
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
- 1
- 0
-
-
-
-
- 200
- 0
-
-
-
-
- 16777215
- 16777215
-
-
-
- PushButton
-
-
-
- -
-
-
-
- 2
- 0
-
-
-
- QFrame::Box
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
- 200
- 0
-
-
-
-
- 200
- 16777215
-
-
-
-
- 200
- 0
-
-
-
- QFrame::NoFrame
-
-
-
-
-
-
- -
-
-
-
-
- -
-
-
-
-
-
-
- 200
- 0
-
-
-
-
- 200
- 16777215
-
-
-
-
- 200
- 0
-
-
-
- QFrame::NoFrame
-
-
-
-
-
-
- -
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
- 999
-
-
-
- -
-
-
- Qt::LeftToRight
-
-
- X
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- -99999
-
-
- 99999
-
-
-
- -
-
-
- Y
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- -99999
-
-
- 99999
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
- 200
- 0
-
-
-
-
- 200
- 16777215
-
-
-
-
- 200
- 0
-
-
-
- QFrame::NoFrame
-
-
-
-
-
-
- -
-
-
-
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
-
- 32
- 32
-
-
-
-
-
-
-
- 32
- 32
-
-
-
-
- -
-
-
- -
-
-
-
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
-
- 32
- 32
-
-
-
-
-
-
-
- 32
- 32
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- 200
- 0
-
-
-
-
- 200
- 16777215
-
-
-
-
- 200
- 0
-
-
-
- QFrame::NoFrame
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 220
-
-
-
-
- 16777215
- 220
-
-
-
- GroupBox
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
-
-
-
-
-
-
-
-
- 320
- 180
-
-
-
-
- 320
- 180
-
-
-
- QFrame::Box
-
-
-
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
-
-
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 16777215
- 16777215
-
-
-
- GroupBox
-
-
-
-
-
-
-
-
-
- 24
-
-
- Qt::AlignCenter
-
-
- true
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- PushButton
-
-
-
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 0
-
-
-
-
-
-
-
-
-
-
-
-
--
cgit v1.2.3
From 5101b439dfcbedf0dbdba3ee867c11c063cee51a Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 28 May 2017 14:49:35 -0400
Subject: fixed travelling text bug
---
components/text.py | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/components/text.py b/components/text.py
index 814e13f..eab33b2 100644
--- a/components/text.py
+++ b/components/text.py
@@ -63,6 +63,14 @@ class Component:
self.xPosition = self.page.spinBox_xTextAlign.value()
self.yPosition = self.page.spinBox_yTextAlign.value()
self.textColor = RGBFromString(self.page.lineEdit_textColor.text())
+ fm = QtGui.QFontMetrics(self.titleFont)
+ if self.alignment == 0: #Left
+ self.xPosition = self.xPosition
+ if self.alignment == 1: #Middle
+ self.xPosition = self.xPosition - fm.width(self.title)/2
+ if self.alignment == 2: #Right
+ self.xPosition = self.xPosition - fm.width(self.title)
+
self.parent.drawPreview()
def previewRender(self, previewWorker):
@@ -90,12 +98,6 @@ class Component:
painter.setPen(QColor(*self.textColor))
fm = QtGui.QFontMetrics(self.titleFont)
- if self.alignment == 0: #Left
- self.xPosition = self.xPosition
- if self.alignment == 1: #Middle
- self.xPosition = self.xPosition - fm.width(self.title)/2
- if self.alignment == 2: #Right
- self.xPosition = self.xPosition - fm.width(self.title)
painter.drawText(self.xPosition, self.yPosition, self.title)
painter.end()
--
cgit v1.2.3
From e3079f7a67ce8939ebb861b9580c281f81331181 Mon Sep 17 00:00:00 2001
From: DH4
Date: Sun, 28 May 2017 14:19:06 -0500
Subject: Fixed Stack & list sync bug.
---
main.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/main.py b/main.py
index 09d8e46..d165fc5 100644
--- a/main.py
+++ b/main.py
@@ -3,7 +3,7 @@ from PyQt4 import QtCore, QtGui, uic
from os.path import expanduser
import atexit
from queue import Queue
-from PyQt4.QtCore import QSettings
+from PyQt4.QtCore import QSettings, QModelIndex
import signal
from importlib import import_module
@@ -286,9 +286,11 @@ class Main(QtCore.QObject):
return [import_module('components.%s' % name) for name in findComponents()]
def addComponent(self, moduleIndex):
+ index = len(self.pages)
self.window.listWidget_componentList.addItem(self.modules[moduleIndex].__doc__)
self.selectedComponents.append(self.modules[moduleIndex].Component())
self.pages.append(self.selectedComponents[-1].widget(self))
+ self.window.listWidget_componentList.setCurrentRow(index)
self.window.stackedWidget.addWidget(self.pages[-1])
self.selectedComponents[-1].update()
@@ -298,6 +300,7 @@ class Main(QtCore.QObject):
self.window.stackedWidget.removeWidget(self.pages[index])
self.window.listWidget_componentList.takeItem(index)
self.selectedComponents.pop(index)
+ self.pages.pop(index)
self.drawPreview()
def changeComponentWidget(self):
--
cgit v1.2.3
From 719e9a4ddf306b06bce7a5dcf0f3028731db0664 Mon Sep 17 00:00:00 2001
From: DH4
Date: Sun, 28 May 2017 15:05:08 -0500
Subject: Implemented change list order
---
main.py | 31 +++++++++++++++++++++++++++++++
mainwindow.ui | 27 ++++++++++++++++++++++++---
2 files changed, 55 insertions(+), 3 deletions(-)
diff --git a/main.py b/main.py
index d165fc5..8a9ba8c 100644
--- a/main.py
+++ b/main.py
@@ -166,6 +166,9 @@ class Main(QtCore.QObject):
window.comboBox_resolution.setCurrentIndex(currentRes)
window.comboBox_resolution.currentIndexChanged.connect(self.updateResolution)
+ self.window.pushButton_listMoveUp.clicked.connect(self.moveComponentUp)
+ self.window.pushButton_listMoveDown.clicked.connect(self.moveComponentDown)
+
'''
window.lineEdit_visColor.setText('%s,%s,%s' % self.visColor)
window.pushButton_visColor.clicked.connect(lambda: self.pickColor('vis'))
@@ -292,6 +295,7 @@ class Main(QtCore.QObject):
self.pages.append(self.selectedComponents[-1].widget(self))
self.window.listWidget_componentList.setCurrentRow(index)
self.window.stackedWidget.addWidget(self.pages[-1])
+ self.window.stackedWidget.setCurrentIndex(index)
self.selectedComponents[-1].update()
def removeComponent(self):
@@ -308,6 +312,33 @@ class Main(QtCore.QObject):
index = self.window.listWidget_componentList.row(selected[0])
self.window.stackedWidget.setCurrentIndex(index)
+ def moveComponentUp(self):
+ row = self.window.listWidget_componentList.currentRow()
+ if row > 0:
+ item = self.window.listWidget_componentList.takeItem(row)
+ self.window.listWidget_componentList.insertItem(row - 1, item)
+ page = self.pages[row]
+ self.pages.pop(row)
+ self.pages.insert(row - 1, page)
+ widget = self.window.stackedWidget.removeWidget(page)
+ self.window.stackedWidget.insertWidget(row - 1, page)
+ self.window.listWidget_componentList.setCurrentRow(row - 1)
+ self.window.stackedWidget.setCurrentIndex(row -1)
+
+ def moveComponentDown(self):
+ row = self.window.listWidget_componentList.currentRow()
+ if row < len(self.pages):
+ item = self.window.listWidget_componentList.takeItem(row)
+ self.window.listWidget_componentList.insertItem(row + 1, item)
+ page = self.pages[row]
+ self.pages.pop(row)
+ self.pages.insert(row + 1, page)
+ widget = self.window.stackedWidget.removeWidget(page)
+ self.window.stackedWidget.insertWidget(row + 1, page)
+ self.window.listWidget_componentList.setCurrentRow(row + 1)
+ self.window.stackedWidget.setCurrentIndex(row + 1)
+
+
def LoadDefaultSettings(self):
self.resolutions = [
'1920x1080',
diff --git a/mainwindow.ui b/mainwindow.ui
index ce8233e..b15cc8e 100644
--- a/mainwindow.ui
+++ b/mainwindow.ui
@@ -129,6 +129,13 @@
+ -
+
+
+ Save As
+
+
+
-
@@ -142,7 +149,7 @@
20
- 10
+ 20
@@ -168,14 +175,28 @@
-
- Add Component
+ Add
-
- Remove Component
+ Remove
+
+
+
+ -
+
+
+ Down
+
+
+
+ -
+
+
+ Up
--
cgit v1.2.3
From fa89cd38f22051fe6e3afec5e93eb7e1e70351c0 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 28 May 2017 16:26:06 -0400
Subject: slight fixes to component UIs
and adding a component changes the stackedWidget
---
components/original.ui | 13 +-
components/text.ui | 568 ++++++++++++++++++++++++-------------------------
main.py | 1 +
3 files changed, 284 insertions(+), 298 deletions(-)
diff --git a/components/original.ui b/components/original.ui
index 0e6dd98..9cdfac2 100644
--- a/components/original.ui
+++ b/components/original.ui
@@ -16,13 +16,16 @@
- 10
- 10
- 567
- 29
+ 0
+ 0
+ 561
+ 51
+
+ 4
+
-
@@ -86,6 +89,8 @@
+ label_visLayout
+ layoutWidget
diff --git a/components/text.ui b/components/text.ui
index 4431278..9a37215 100644
--- a/components/text.ui
+++ b/components/text.ui
@@ -13,316 +13,296 @@
Form
-
+
- 10
- 20
- 567
- 25
+ 0
+ 0
+ 581
+ 131
-
+
- 0
+ 4
-
-
-
- Title
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 300
- 0
-
-
-
- Testing New GUI
-
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Font
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 140
+ 0
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Font Size
+
+
+
+ -
+
+
+ 500
+
+
+
+
-
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Text Layout
+
+
+
+ -
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+ Text Color
+
+
+
+ -
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+
-
-
-
-
- 0
- 0
-
-
-
- X
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
-
- 0
- 0
-
-
-
+
+
0
-
- 999999999
-
-
- 0
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Y
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
- 999999999
-
-
+
-
+
+
+ Title
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 300
+ 0
+
+
+
+ Testing New GUI
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ X
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 0
+ 0
+
+
+
+ 0
+
+
+ 999999999
+
+
+ 0
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Y
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+ 999999999
+
+
+
+
-
-
-
- 10
- 51
- 567
- 29
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Font
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 140
- 0
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Font Size
-
-
-
- -
-
-
- 500
-
-
-
-
-
-
-
-
- 10
- 86
- 567
- 29
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Text Layout
-
-
-
- -
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
- Text Color
-
-
-
- -
-
-
-
- 32
- 32
-
-
-
-
-
-
-
- 32
- 32
-
-
-
-
- -
-
-
-
-
-
-
-
- 10
- 121
- 567
- 29
-
-
-
-
diff --git a/main.py b/main.py
index d165fc5..5a80fd8 100644
--- a/main.py
+++ b/main.py
@@ -292,6 +292,7 @@ class Main(QtCore.QObject):
self.pages.append(self.selectedComponents[-1].widget(self))
self.window.listWidget_componentList.setCurrentRow(index)
self.window.stackedWidget.addWidget(self.pages[-1])
+ self.window.stackedWidget.setCurrentWidget(self.pages[-1])
self.selectedComponents[-1].update()
def removeComponent(self):
--
cgit v1.2.3
From b2e3716a2920aff875d7ec44ab1b9b1aa521101a Mon Sep 17 00:00:00 2001
From: DH4
Date: Sun, 28 May 2017 15:46:59 -0500
Subject: Fixed component list not affecting render order. FIXME Reverse the
render order
---
main.py | 16 +++++++++++-----
1 file changed, 11 insertions(+), 5 deletions(-)
diff --git a/main.py b/main.py
index 8a9ba8c..7cae950 100644
--- a/main.py
+++ b/main.py
@@ -315,11 +315,14 @@ class Main(QtCore.QObject):
def moveComponentUp(self):
row = self.window.listWidget_componentList.currentRow()
if row > 0:
- item = self.window.listWidget_componentList.takeItem(row)
- self.window.listWidget_componentList.insertItem(row - 1, item)
+ module = self.selectedComponents[row]
+ self.selectedComponents.pop(row)
+ self.selectedComponents.insert(row - 1,module)
page = self.pages[row]
self.pages.pop(row)
self.pages.insert(row - 1, page)
+ item = self.window.listWidget_componentList.takeItem(row)
+ self.window.listWidget_componentList.insertItem(row - 1, item)
widget = self.window.stackedWidget.removeWidget(page)
self.window.stackedWidget.insertWidget(row - 1, page)
self.window.listWidget_componentList.setCurrentRow(row - 1)
@@ -327,12 +330,15 @@ class Main(QtCore.QObject):
def moveComponentDown(self):
row = self.window.listWidget_componentList.currentRow()
- if row < len(self.pages):
- item = self.window.listWidget_componentList.takeItem(row)
- self.window.listWidget_componentList.insertItem(row + 1, item)
+ if row < len(self.pages) + 1:
+ module = self.selectedComponents[row]
+ self.selectedComponents.pop(row)
+ self.selectedComponents.insert(row + 1,module)
page = self.pages[row]
self.pages.pop(row)
self.pages.insert(row + 1, page)
+ item = self.window.listWidget_componentList.takeItem(row)
+ self.window.listWidget_componentList.insertItem(row + 1, item)
widget = self.window.stackedWidget.removeWidget(page)
self.window.stackedWidget.insertWidget(row + 1, page)
self.window.listWidget_componentList.setCurrentRow(row + 1)
--
cgit v1.2.3
From 39944a56a860836c62b5358174be9e65bd66dc66 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 28 May 2017 19:08:50 -0400
Subject: create data directory structure
---
main.py | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/main.py b/main.py
index 7cae950..7ddc4f2 100644
--- a/main.py
+++ b/main.py
@@ -1,11 +1,12 @@
import sys, io, os
-from PyQt4 import QtCore, QtGui, uic
from os.path import expanduser
import atexit
from queue import Queue
-from PyQt4.QtCore import QSettings, QModelIndex
import signal
from importlib import import_module
+from PyQt4 import QtCore, QtGui, uic
+from PyQt4.QtCore import QSettings, QModelIndex
+from PyQt4.QtGui import QDesktopServices
import preview_thread, core, video_thread
@@ -123,6 +124,14 @@ class Main(QtCore.QObject):
self.settings = QSettings('settings.ini', QSettings.IniFormat)
LoadDefaultSettings(self)
+ # create data directory structure if needed
+ dataDir = QDesktopServices.storageLocation(QDesktopServices.DataLocation)
+ if not os.path.exists(dataDir):
+ os.makedirs(dataDir)
+ for neededDirectory in ('projects', 'presets'):
+ if not os.path.exists(os.path.join(dataDir, neededDirectory)):
+ os.mkdir(os.path.join(dataDir, neededDirectory))
+
self.pages = []
self.previewQueue = Queue()
@@ -382,6 +391,8 @@ else:
# gui mode
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
+ app.setApplicationName("audio-visualizer")
+ app.setOrganizationName("audio-visualizer")
window = uic.loadUi("mainwindow.ui")
# window.adjustSize()
desc = QtGui.QDesktopWidget()
--
cgit v1.2.3
From ce414ff96081d1c32fe04503b855fd04a32e82bb Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 28 May 2017 19:50:29 -0400
Subject: turned openPreset button into comboBox to fit a new design
---
main.py | 18 +++++++++++-------
mainwindow.ui | 10 ++++++----
2 files changed, 17 insertions(+), 11 deletions(-)
diff --git a/main.py b/main.py
index 7ddc4f2..d4c12d8 100644
--- a/main.py
+++ b/main.py
@@ -178,13 +178,11 @@ class Main(QtCore.QObject):
self.window.pushButton_listMoveUp.clicked.connect(self.moveComponentUp)
self.window.pushButton_listMoveDown.clicked.connect(self.moveComponentDown)
- '''
- window.lineEdit_visColor.setText('%s,%s,%s' % self.visColor)
- window.pushButton_visColor.clicked.connect(lambda: self.pickColor('vis'))
- btnStyle = "QPushButton { background-color : %s; outline: none; }" % QColor(*self.visColor).name()
- window.pushButton_visColor.setStyleSheet(btnStyle)
- window.lineEdit_visColor.textChanged.connect(self.drawPreview)
- '''
+ self.window.pushButton_savePreset.clicked.connect(self.openSavePresetDialog)
+ self.window.comboBox_openPreset.currentIndexChanged.connect( \
+ lambda _: self.openPreset(self.window.comboBox_openPreset.currentIndex())
+ )
+
self.drawPreview()
window.show()
@@ -353,6 +351,12 @@ class Main(QtCore.QObject):
self.window.listWidget_componentList.setCurrentRow(row + 1)
self.window.stackedWidget.setCurrentIndex(row + 1)
+ def openSavePresetDialog(self):
+ pass
+
+ def openPreset(self, comboBoxIndex):
+ pass
+
def LoadDefaultSettings(self):
self.resolutions = [
diff --git a/mainwindow.ui b/mainwindow.ui
index b15cc8e..0dcce91 100644
--- a/mainwindow.ui
+++ b/mainwindow.ui
@@ -217,10 +217,12 @@
-
-
-
-
- Open Preset
-
+
+
-
+
+ Open Preset
+
+
-
--
cgit v1.2.3
From c0920da4ffa0a78bac3eec7fdadba3a4183a8fed Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 28 May 2017 21:24:51 -0400
Subject: savePreset creates a file
---
components/original.py | 3 +++
components/text.py | 3 +++
main.py | 32 ++++++++++++++++++++++++++------
3 files changed, 32 insertions(+), 6 deletions(-)
diff --git a/components/original.py b/components/original.py
index e901c21..5655867 100644
--- a/components/original.py
+++ b/components/original.py
@@ -34,6 +34,9 @@ class Component:
self.visColor = RGBFromString(self.page.lineEdit_visColor.text())
self.parent.drawPreview()
+ def savePreset(self):
+ return {}
+
def previewRender(self, previewWorker):
spectrum = numpy.fromfunction(lambda x: 0.008*(x-128)**2, (255,), dtype="int16")
width = int(previewWorker.core.settings.value('outputWidth'))
diff --git a/components/text.py b/components/text.py
index eab33b2..e900994 100644
--- a/components/text.py
+++ b/components/text.py
@@ -73,6 +73,9 @@ class Component:
self.parent.drawPreview()
+ def savePreset(self):
+ return {}
+
def previewRender(self, previewWorker):
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
diff --git a/main.py b/main.py
index d4c12d8..fe76e2c 100644
--- a/main.py
+++ b/main.py
@@ -125,12 +125,12 @@ class Main(QtCore.QObject):
LoadDefaultSettings(self)
# create data directory structure if needed
- dataDir = QDesktopServices.storageLocation(QDesktopServices.DataLocation)
- if not os.path.exists(dataDir):
- os.makedirs(dataDir)
+ self.dataDir = QDesktopServices.storageLocation(QDesktopServices.DataLocation)
+ if not os.path.exists(self.dataDir):
+ os.makedirs(self.dataDir)
for neededDirectory in ('projects', 'presets'):
- if not os.path.exists(os.path.join(dataDir, neededDirectory)):
- os.mkdir(os.path.join(dataDir, neededDirectory))
+ if not os.path.exists(os.path.join(self.dataDir, neededDirectory)):
+ os.mkdir(os.path.join(self.dataDir, neededDirectory))
self.pages = []
@@ -352,7 +352,27 @@ class Main(QtCore.QObject):
self.window.stackedWidget.setCurrentIndex(row + 1)
def openSavePresetDialog(self):
- pass
+ if self.window.listWidget_componentList.currentRow() == -1:
+ return
+ newName, OK = QtGui.QInputDialog.getText(QtGui.QWidget(), 'Audio Visualizer', 'New Preset Name:')
+ if OK and newName:
+ index = self.window.listWidget_componentList.currentRow()
+ if index != -1:
+ saveValueStore = self.selectedComponents[index].savePreset()
+ componentName = str(self.selectedComponents[index]).strip()
+ if hasattr(self.selectedComponents[index], 'version'):
+ vers = self.selectedComponents[index].version()
+ else:
+ vers = 1
+ self.createPresetFile(componentName, vers, saveValueStore, newName)
+
+ def createPresetFile(self, componentName, version, saveValueStore, filename):
+ dirname = os.path.join(self.dataDir, 'presets', componentName, str(version))
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+ with open(os.path.join(dirname, filename), 'w') as f:
+ for itemset in saveValueStore.items():
+ f.write('%s=%s' % itemset)
def openPreset(self, comboBoxIndex):
pass
--
cgit v1.2.3
From db7acbf3ea353d6c5b21de44b4f532b43339ac5c Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 28 May 2017 22:58:13 -0400
Subject: save empty presets, comboBox populates with preset names
---
components/original.py | 3 +++
components/text.py | 3 +++
main.py | 18 ++++++++++++++----
3 files changed, 20 insertions(+), 4 deletions(-)
diff --git a/components/original.py b/components/original.py
index 5655867..e543dac 100644
--- a/components/original.py
+++ b/components/original.py
@@ -34,6 +34,9 @@ class Component:
self.visColor = RGBFromString(self.page.lineEdit_visColor.text())
self.parent.drawPreview()
+ def version(self):
+ return 1
+
def savePreset(self):
return {}
diff --git a/components/text.py b/components/text.py
index e900994..334fc80 100644
--- a/components/text.py
+++ b/components/text.py
@@ -55,6 +55,9 @@ class Component:
self.page = page
return page
+ def version(self):
+ return 1
+
def update(self):
self.title = self.page.lineEdit_title.text()
self.alignment = self.page.comboBox_textAlign.currentIndex()
diff --git a/main.py b/main.py
index fe76e2c..5bb10f5 100644
--- a/main.py
+++ b/main.py
@@ -304,6 +304,7 @@ class Main(QtCore.QObject):
self.window.stackedWidget.addWidget(self.pages[-1])
self.window.stackedWidget.setCurrentIndex(index)
self.selectedComponents[-1].update()
+ self.updateOpenPresetComboBox(self.selectedComponents[-1])
def removeComponent(self):
for selected in self.window.listWidget_componentList.selectedItems():
@@ -318,6 +319,7 @@ class Main(QtCore.QObject):
selected = self.window.listWidget_componentList.selectedItems()
index = self.window.listWidget_componentList.row(selected[0])
self.window.stackedWidget.setCurrentIndex(index)
+ self.updateOpenPresetComboBox(self.selectedComponents[index])
def moveComponentUp(self):
row = self.window.listWidget_componentList.currentRow()
@@ -351,6 +353,16 @@ class Main(QtCore.QObject):
self.window.listWidget_componentList.setCurrentRow(row + 1)
self.window.stackedWidget.setCurrentIndex(row + 1)
+ def updateOpenPresetComboBox(self, component):
+ self.window.comboBox_openPreset.clear()
+ self.window.comboBox_openPreset.addItem("Open Preset")
+ destination = os.path.join(self.dataDir, 'presets',
+ str(component).strip(), str(component.version()))
+ if not os.path.exists(destination):
+ os.makedirs(destination)
+ for f in os.listdir(destination):
+ self.window.comboBox_openPreset.addItem(f)
+
def openSavePresetDialog(self):
if self.window.listWidget_componentList.currentRow() == -1:
return
@@ -360,10 +372,7 @@ class Main(QtCore.QObject):
if index != -1:
saveValueStore = self.selectedComponents[index].savePreset()
componentName = str(self.selectedComponents[index]).strip()
- if hasattr(self.selectedComponents[index], 'version'):
- vers = self.selectedComponents[index].version()
- else:
- vers = 1
+ vers = self.selectedComponents[index].version()
self.createPresetFile(componentName, vers, saveValueStore, newName)
def createPresetFile(self, componentName, version, saveValueStore, filename):
@@ -373,6 +382,7 @@ class Main(QtCore.QObject):
with open(os.path.join(dirname, filename), 'w') as f:
for itemset in saveValueStore.items():
f.write('%s=%s' % itemset)
+ self.window.comboBox_openPreset.addItem(filename)
def openPreset(self, comboBoxIndex):
pass
--
cgit v1.2.3
From 75f1e8af76fe0ea01d4dd59d53d6ce3b31a4a706 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Mon, 29 May 2017 17:38:28 -0400
Subject: title text does not need to generate a new image each frame
---
components/text.py | 1 +
test.mkv | Bin 0 -> 1207731 bytes
video_thread.py | 22 +++++++++++++++++-----
3 files changed, 18 insertions(+), 5 deletions(-)
create mode 100644 test.mkv
diff --git a/components/text.py b/components/text.py
index 334fc80..8b7f6be 100644
--- a/components/text.py
+++ b/components/text.py
@@ -87,6 +87,7 @@ class Component:
def preFrameRender(self, **kwargs):
for kwarg, value in kwargs.items():
exec('self.%s = value' % kwarg)
+ return ['static']
def frameRender(self, moduleNo, frameNo):
width = int(self.worker.core.settings.value('outputWidth'))
diff --git a/test.mkv b/test.mkv
new file mode 100644
index 0000000..92780a7
Binary files /dev/null and b/test.mkv differ
diff --git a/video_thread.py b/video_thread.py
index 8bef6ef..93ca7bd 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -77,10 +77,15 @@ class Worker(QtCore.QObject):
# initialize components
print('######################## Data')
- print('loaded components: ', [str(component) for component in components])
+ print('loaded components:',
+ ["%s%s" % (num, str(component)) for num, component in enumerate(components)])
+ staticComponents = {}
sampleSize = 1470
- for component in components:
- component.preFrameRender(worker=self, completeAudioArray=completeAudioArray, sampleSize=sampleSize)
+ for compNo, comp in enumerate(components):
+ properties = None
+ properties = comp.preFrameRender(worker=self, completeAudioArray=completeAudioArray, sampleSize=sampleSize)
+ if properties and 'static' in properties:
+ staticComponents[compNo] = None
# create video for output
numpy.seterr(divide='ignore')
@@ -88,18 +93,25 @@ class Worker(QtCore.QObject):
bgI = 0
for i in range(0, len(completeAudioArray), sampleSize):
newFrame = Image.new("RGBA", (int(self.core.settings.value('outputWidth')), int(self.core.settings.value('outputHeight'))),(0,0,0,255))
-
if imBackground:
newFrame.paste(imBackground)
else:
newFrame.paste(getBackgroundAtIndex(bgI))
+ # composite all frames returned by the components in order
for compNo, comp in enumerate(components):
- newFrame = Image.alpha_composite(newFrame,comp.frameRender(compNo, i))
+ if compNo in staticComponents and staticComponents[compNo] != None:
+ newFrame = Image.alpha_composite(newFrame,staticComponents[compNo])
+ else:
+ newFrame = Image.alpha_composite(newFrame,comp.frameRender(compNo, i))
+ if i == 0 and compNo in staticComponents:
+ staticComponents[compNo] = comp.frameRender(compNo, i)
if not imBackground:
+ # increment background video frame for next iteration
if bgI < len(backgroundFrames)-1:
bgI += 1
+
# write to out_pipe
try:
frame = Image.new("RGB", (int(self.core.settings.value('outputWidth')), int(self.core.settings.value('outputHeight'))),(0,0,0))
--
cgit v1.2.3
From 37fd68fd2bd2ad81bf1b08d923df2a0934bee6b8 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Mon, 29 May 2017 17:40:40 -0400
Subject: remove test video
---
.gitignore | 4 +++-
test.mkv | Bin 1207731 -> 0 bytes
2 files changed, 3 insertions(+), 1 deletion(-)
delete mode 100644 test.mkv
diff --git a/.gitignore b/.gitignore
index 24ed791..0316a98 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,6 @@
__pycache__
settings.ini
build/*
-.vscode/*
\ No newline at end of file
+.vscode/*
+*.mkv
+*.mp4
diff --git a/test.mkv b/test.mkv
deleted file mode 100644
index 92780a7..0000000
Binary files a/test.mkv and /dev/null differ
--
cgit v1.2.3
From 8dd7b7d59ab3ef3caf2bbd69dd0b2a7eb134edc7 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Mon, 29 May 2017 20:39:11 -0400
Subject: added component base class
---
components/__base__.py | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++
components/original.py | 35 +++++++------------------------
components/text.py | 36 +++++++------------------------
main.py | 4 ++--
4 files changed, 74 insertions(+), 58 deletions(-)
create mode 100644 components/__base__.py
diff --git a/components/__base__.py b/components/__base__.py
new file mode 100644
index 0000000..87440bb
--- /dev/null
+++ b/components/__base__.py
@@ -0,0 +1,57 @@
+from PyQt4 import QtGui
+
+class Component:
+ def __str__(self):
+ return self.__doc__
+
+ def preFrameRender(self, **kwargs):
+ for kwarg, value in kwargs.items():
+ exec('self.%s = value' % kwarg)
+
+ def pickColor(self):
+ color = QtGui.QColorDialog.getColor()
+ if color.isValid():
+ RGBstring = '%s,%s,%s' % (str(color.red()), str(color.green()), str(color.blue()))
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" % color.name()
+ return RGBstring, btnStyle
+
+ def RGBFromString(self, string):
+ ''' turns an RGB string like "255, 255, 255" into a tuple '''
+ try:
+ tup = tuple([int(i) for i in string.split(',')])
+ if len(tup) != 3:
+ raise ValueError
+ for i in tup:
+ if i > 255 or i < 0:
+ raise ValueError
+ return tup
+ except:
+ return (255, 255, 255)
+
+ '''
+ ### Reference methods for creating a new component
+ ### (Inherit from this class and define these)
+
+ def widget(self, parent):
+ self.parent = parent
+ page = uic.loadUi(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'example.ui'))
+ # connect widgets signals
+ self.page = page
+ return page
+
+ def update(self):
+ # read widget values
+ self.parent.drawPreview()
+
+ def previewRender(self, previewWorker):
+ width = int(previewWorker.core.settings.value('outputWidth'))
+ height = int(previewWorker.core.settings.value('outputHeight'))
+ image = Image.new("RGBA", (width, height), (0,0,0,0))
+ return image
+
+ def frameRender(self, moduleNo, frameNo):
+ width = int(self.worker.core.settings.value('outputWidth'))
+ height = int(self.worker.core.settings.value('outputHeight'))
+ image = Image.new("RGBA", (width, height), (0,0,0,0))
+ return image
+ '''
diff --git a/components/original.py b/components/original.py
index e901c21..4a149e2 100644
--- a/components/original.py
+++ b/components/original.py
@@ -1,15 +1,13 @@
-''' Original Audio Visualization '''
import numpy
from PIL import Image, ImageDraw
from PyQt4 import uic, QtGui
from PyQt4.QtGui import QColor
import os, random
+from . import __base__
-class Component:
- def __str__(self):
- return __doc__
-
+class Component(__base__.Component):
+ '''Original Audio Visualization'''
def widget(self, parent):
self.parent = parent
self.visColor = (255,255,255)
@@ -31,7 +29,7 @@ class Component:
def update(self):
self.layout = self.page.comboBox_visLayout.currentIndex()
- self.visColor = RGBFromString(self.page.lineEdit_visColor.text())
+ self.visColor = self.RGBFromString(self.page.lineEdit_visColor.text())
self.parent.drawPreview()
def previewRender(self, previewWorker):
@@ -41,8 +39,7 @@ class Component:
return drawBars(width, height, spectrum, self.visColor, self.layout)
def preFrameRender(self, **kwargs):
- for kwarg, value in kwargs.items():
- exec('self.%s = value' % kwarg)
+ super().preFrameRender(**kwargs)
self.smoothConstantDown = 0.08
self.smoothConstantUp = 0.8
self.lastSpectrum = None
@@ -55,12 +52,9 @@ class Component:
return drawBars(width, height, self.lastSpectrum, self.visColor, self.layout)
def pickColor(self):
- color = QtGui.QColorDialog.getColor()
- if color.isValid():
- RGBstring = '%s,%s,%s' % (str(color.red()), str(color.green()), str(color.blue()))
- btnStyle = "QPushButton { background-color : %s; outline: none; }" % color.name()
- self.page.lineEdit_visColor.setText(RGBstring)
- self.page.pushButton_visColor.setStyleSheet(btnStyle)
+ RGBstring, btnStyle = super().pickColor()
+ self.page.lineEdit_visColor.setText(RGBstring)
+ self.page.pushButton_visColor.setStyleSheet(btnStyle)
def transformData(i, completeAudioArray, sampleSize, smoothConstantDown, smoothConstantUp, lastSpectrum):
if len(completeAudioArray) < (i + sampleSize):
@@ -130,16 +124,3 @@ def drawBars(width, height, spectrum, color, layout):
im.paste(imTop, (0, y), mask=imTop)
return im
-
-def RGBFromString(string):
- ''' turns an RGB string like "255, 255, 255" into a tuple '''
- try:
- tup = tuple([int(i) for i in string.split(',')])
- if len(tup) != 3:
- raise ValueError
- for i in tup:
- if i > 255 or i < 0:
- raise ValueError
- return tup
- except:
- return (255, 255, 255)
diff --git a/components/text.py b/components/text.py
index eab33b2..1f5e222 100644
--- a/components/text.py
+++ b/components/text.py
@@ -1,15 +1,13 @@
-''' Title Text '''
from PIL import Image, ImageDraw
from PyQt4.QtGui import QPainter, QColor, QFont
from PyQt4 import uic, QtGui, QtCore
from PIL.ImageQt import ImageQt
import os, io
+from . import __base__
-class Component:
- def __str__(self):
- return __doc__
-
+class Component(__base__.Component):
+ '''Title Text'''
def widget(self, parent):
height = int(parent.settings.value('outputHeight'))
width = int(parent.settings.value('outputWidth'))
@@ -62,7 +60,7 @@ class Component:
self.fontSize = self.page.spinBox_fontSize.value()
self.xPosition = self.page.spinBox_xTextAlign.value()
self.yPosition = self.page.spinBox_yTextAlign.value()
- self.textColor = RGBFromString(self.page.lineEdit_textColor.text())
+ self.textColor = self.RGBFromString(self.page.lineEdit_textColor.text())
fm = QtGui.QFontMetrics(self.titleFont)
if self.alignment == 0: #Left
self.xPosition = self.xPosition
@@ -77,10 +75,6 @@ class Component:
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
return self.addText(width, height)
-
- def preFrameRender(self, **kwargs):
- for kwarg, value in kwargs.items():
- exec('self.%s = value' % kwarg)
def frameRender(self, moduleNo, frameNo):
width = int(self.worker.core.settings.value('outputWidth'))
@@ -112,22 +106,6 @@ class Component:
return Image.open(strio)
def pickColor(self):
- color = QtGui.QColorDialog.getColor()
- if color.isValid():
- RGBstring = '%s,%s,%s' % (str(color.red()), str(color.green()), str(color.blue()))
- btnStyle = "QPushButton { background-color : %s; outline: none; }" % color.name()
- self.page.lineEdit_textColor.setText(RGBstring)
- self.page.pushButton_textColor.setStyleSheet(btnStyle)
-
-def RGBFromString(string):
- ''' turns an RGB string like "255, 255, 255" into a tuple '''
- try:
- tup = tuple([int(i) for i in string.split(',')])
- if len(tup) != 3:
- raise ValueError
- for i in tup:
- if i > 255 or i < 0:
- raise ValueError
- return tup
- except:
- return (255, 255, 255)
+ RGBstring, btnStyle = super().pickColor()
+ self.page.lineEdit_textColor.setText(RGBstring)
+ self.page.pushButton_textColor.setStyleSheet(btnStyle)
diff --git a/main.py b/main.py
index 7cae950..1d22704 100644
--- a/main.py
+++ b/main.py
@@ -149,7 +149,7 @@ class Main(QtCore.QObject):
self.modules = self.findComponents()
for component in self.modules:
- window.comboBox_componentSelection.addItem(component.__doc__)
+ window.comboBox_componentSelection.addItem(component.Component.__doc__)
window.listWidget_componentList.clicked.connect(lambda _: self.changeComponentWidget())
self.selectedComponents = []
@@ -290,8 +290,8 @@ class Main(QtCore.QObject):
def addComponent(self, moduleIndex):
index = len(self.pages)
- self.window.listWidget_componentList.addItem(self.modules[moduleIndex].__doc__)
self.selectedComponents.append(self.modules[moduleIndex].Component())
+ self.window.listWidget_componentList.addItem(self.selectedComponents[-1].__doc__)
self.pages.append(self.selectedComponents[-1].widget(self))
self.window.listWidget_componentList.setCurrentRow(index)
self.window.stackedWidget.addWidget(self.pages[-1])
--
cgit v1.2.3
From ca7e8bdb0dc998088aeb45a77987a78cc4656b34 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 30 May 2017 19:31:10 -0400
Subject: the most simple way of saving dictionaries
---
components/__base__.py | 7 +++++++
components/original.py | 8 +++++---
main.py | 30 +++++++++++++++++++++++-------
3 files changed, 35 insertions(+), 10 deletions(-)
diff --git a/components/__base__.py b/components/__base__.py
index 87440bb..252ad03 100644
--- a/components/__base__.py
+++ b/components/__base__.py
@@ -3,6 +3,9 @@ from PyQt4 import QtGui
class Component:
def __str__(self):
return self.__doc__
+
+ def version(self):
+ return 1
def preFrameRender(self, **kwargs):
for kwarg, value in kwargs.items():
@@ -54,4 +57,8 @@ class Component:
height = int(self.worker.core.settings.value('outputHeight'))
image = Image.new("RGBA", (width, height), (0,0,0,0))
return image
+
+ def version(self):
+ # change this number to identify new versions of your component
+ return 1
'''
diff --git a/components/original.py b/components/original.py
index 47e53b8..40f51eb 100644
--- a/components/original.py
+++ b/components/original.py
@@ -32,11 +32,13 @@ class Component(__base__.Component):
self.visColor = self.RGBFromString(self.page.lineEdit_visColor.text())
self.parent.drawPreview()
- def version(self):
- return 1
+ def loadPreset(self, presetDict):
+ self.preFrameRender(**presetDict)
def savePreset(self):
- return {}
+ return { 'layout' : self.page.comboBox_visLayout.currentIndex(),
+ 'visColor' : self.page.lineEdit_visColor.text(),
+ }
def previewRender(self, previewWorker):
spectrum = numpy.fromfunction(lambda x: 0.008*(x-128)**2, (255,), dtype="int16")
diff --git a/main.py b/main.py
index 474ab29..77b56c3 100644
--- a/main.py
+++ b/main.py
@@ -179,9 +179,7 @@ class Main(QtCore.QObject):
self.window.pushButton_listMoveDown.clicked.connect(self.moveComponentDown)
self.window.pushButton_savePreset.clicked.connect(self.openSavePresetDialog)
- self.window.comboBox_openPreset.currentIndexChanged.connect( \
- lambda _: self.openPreset(self.window.comboBox_openPreset.currentIndex())
- )
+ self.window.comboBox_openPreset.currentIndexChanged.connect(self.openPreset)
self.drawPreview()
@@ -380,12 +378,30 @@ class Main(QtCore.QObject):
if not os.path.exists(dirname):
os.makedirs(dirname)
with open(os.path.join(dirname, filename), 'w') as f:
- for itemset in saveValueStore.items():
- f.write('%s=%s' % itemset)
+ f.write('%s' % repr(saveValueStore))
self.window.comboBox_openPreset.addItem(filename)
- def openPreset(self, comboBoxIndex):
- pass
+ def openPreset(self):
+ if self.window.comboBox_openPreset.currentIndex() < 1:
+ return
+ index = self.window.listWidget_componentList.currentRow()
+ if index == -1:
+ # no component selected
+ return
+ filename = self.window.comboBox_openPreset.itemText(self.window.comboBox_openPreset.currentIndex())
+ componentName = str(self.selectedComponents[index]).strip()
+ version = self.selectedComponents[index].version()
+ dirname = os.path.join(self.dataDir, 'presets', componentName, str(version))
+ filepath = os.path.join(dirname, filename)
+ if not os.path.exists(filepath):
+ self.window.comboBox_openPreset.removeItem(self.window.comboBox_openPreset.currentIndex())
+ return
+ with open(filepath, 'r') as f:
+ for line in f:
+ saveValueStore = eval(line.strip())
+ break
+ print(saveValueStore)
+
def LoadDefaultSettings(self):
--
cgit v1.2.3
From 5295a6d9ae3d73c7dceb286f13c6a1429e55393c Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 30 May 2017 22:05:56 -0400
Subject: presets are working
except for font because it can't be represented as a string
---
components/__base__.py | 21 +++++++++++++--------
components/original.py | 13 +++++++++----
components/text.py | 25 ++++++++++++++++++++-----
main.py | 5 ++---
4 files changed, 44 insertions(+), 20 deletions(-)
diff --git a/components/__base__.py b/components/__base__.py
index 252ad03..05d5cb6 100644
--- a/components/__base__.py
+++ b/components/__base__.py
@@ -5,18 +5,21 @@ class Component:
return self.__doc__
def version(self):
+ # change this number to identify new versions of a component
return 1
def preFrameRender(self, **kwargs):
- for kwarg, value in kwargs.items():
- exec('self.%s = value' % kwarg)
+ for item in kwargs.items():
+ exec('self.%s = %s' % item)
def pickColor(self):
color = QtGui.QColorDialog.getColor()
if color.isValid():
- RGBstring = '%s,%s,%s' % (str(color.red()), str(color.green()), str(color.blue()))
- btnStyle = "QPushButton { background-color : %s; outline: none; }" % color.name()
- return RGBstring, btnStyle
+ RGBstring = '%s,%s,%s' % (str(color.red()), str(color.green()), str(color.blue()))
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" % color.name()
+ return RGBstring, btnStyle
+ else:
+ return None, None
def RGBFromString(self, string):
''' turns an RGB string like "255, 255, 255" into a tuple '''
@@ -58,7 +61,9 @@ class Component:
image = Image.new("RGBA", (width, height), (0,0,0,0))
return image
- def version(self):
- # change this number to identify new versions of your component
- return 1
+ def loadPreset(self, presetDict):
+ # update widgets using a preset dict
+
+ def savePreset(self):
+ return {}
'''
diff --git a/components/original.py b/components/original.py
index 40f51eb..bebfdf2 100644
--- a/components/original.py
+++ b/components/original.py
@@ -32,12 +32,15 @@ class Component(__base__.Component):
self.visColor = self.RGBFromString(self.page.lineEdit_visColor.text())
self.parent.drawPreview()
- def loadPreset(self, presetDict):
- self.preFrameRender(**presetDict)
+ def loadPreset(self, pr):
+ 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)
+ self.page.comboBox_visLayout.setCurrentIndex(pr['layout'])
def savePreset(self):
- return { 'layout' : self.page.comboBox_visLayout.currentIndex(),
- 'visColor' : self.page.lineEdit_visColor.text(),
+ return { 'layout' : self.layout,
+ 'visColor' : self.visColor,
}
def previewRender(self, previewWorker):
@@ -61,6 +64,8 @@ class Component(__base__.Component):
def pickColor(self):
RGBstring, btnStyle = super().pickColor()
+ if not RGBstring:
+ return
self.page.lineEdit_visColor.setText(RGBstring)
self.page.pushButton_visColor.setStyleSheet(btnStyle)
diff --git a/components/text.py b/components/text.py
index c9359f2..23e65eb 100644
--- a/components/text.py
+++ b/components/text.py
@@ -53,9 +53,6 @@ class Component(__base__.Component):
self.page = page
return page
- def version(self):
- return 1
-
def update(self):
self.title = self.page.lineEdit_title.text()
self.alignment = self.page.comboBox_textAlign.currentIndex()
@@ -71,11 +68,27 @@ class Component(__base__.Component):
self.xPosition = self.xPosition - fm.width(self.title)/2
if self.alignment == 2: #Right
self.xPosition = self.xPosition - fm.width(self.title)
-
self.parent.drawPreview()
+
+ def loadPreset(self, pr):
+ self.page.lineEdit_title.setText(pr['title'])
+ self.page.spinBox_fontSize.setValue(pr['fontSize'])
+ self.page.spinBox_xTextAlign.setValue(pr['xPosition'])
+ self.page.spinBox_yTextAlign.setValue(pr['yPosition'])
+ self.page.comboBox_textAlign.setCurrentIndex(pr['alignment'])
+ 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):
- return {}
+ return {
+ 'title' : self.title,
+ 'alignment' : self.alignment,
+ 'fontSize' : self.fontSize,
+ 'xPosition' : self.xPosition,
+ 'yPosition' : self.yPosition,
+ 'textColor' : self.textColor
+ }
def previewRender(self, previewWorker):
width = int(previewWorker.core.settings.value('outputWidth'))
@@ -117,5 +130,7 @@ class Component(__base__.Component):
def pickColor(self):
RGBstring, btnStyle = super().pickColor()
+ if not RGBstring:
+ return
self.page.lineEdit_textColor.setText(RGBstring)
self.page.pushButton_textColor.setStyleSheet(btnStyle)
diff --git a/main.py b/main.py
index 77b56c3..2dbefe7 100644
--- a/main.py
+++ b/main.py
@@ -400,9 +400,8 @@ class Main(QtCore.QObject):
for line in f:
saveValueStore = eval(line.strip())
break
- print(saveValueStore)
-
-
+ self.selectedComponents[index].loadPreset(saveValueStore)
+ self.drawPreview()
def LoadDefaultSettings(self):
self.resolutions = [
--
cgit v1.2.3
From 9be8f742c6a694c3d85eacfedf03808f215295ad Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 30 May 2017 22:34:25 -0400
Subject: get confirmation when overwriting presets
---
main.py | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/main.py b/main.py
index 2dbefe7..d3fa52c 100644
--- a/main.py
+++ b/main.py
@@ -377,7 +377,20 @@ class Main(QtCore.QObject):
dirname = os.path.join(self.dataDir, 'presets', componentName, str(version))
if not os.path.exists(dirname):
os.makedirs(dirname)
- with open(os.path.join(dirname, filename), 'w') as f:
+ filepath = os.path.join(dirname, filename)
+ if os.path.exists(filepath):
+ msg = QtGui.QMessageBox()
+ msg.setIcon(QtGui.QMessageBox.Warning)
+ msg.setText("%s already exists! Overwrite it?" % filename)
+ msg.setStandardButtons(QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel)
+ ch = msg.exec_()
+ if ch != 1024: # 1024 = OK
+ return
+ # remove old copies of the preset
+ for i in range(0, self.windowcomboBox_openPreset.count()):
+ if self.window.comboBox_openPreset.itemText(i) == filename:
+ self.window.comboBox_openPreset.removeItem(i)
+ with open(filepath, 'w') as f:
f.write('%s' % repr(saveValueStore))
self.window.comboBox_openPreset.addItem(filename)
--
cgit v1.2.3
From c21d6f5ea7c6d33e2ded44b823d2dbb5b9384d78 Mon Sep 17 00:00:00 2001
From: DH4
Date: Wed, 31 May 2017 02:15:09 -0500
Subject: New rendering engine partially implemented. Also added a live preview
during rendering. FIXME: spectrum is out of sync / rendering too quickly.
---
components/original.py | 12 +-
main.py | 1 +
video_thread.py | 326 ++++++++++++++++++++++++++++++-------------------
3 files changed, 208 insertions(+), 131 deletions(-)
diff --git a/components/original.py b/components/original.py
index 47e53b8..46e7182 100644
--- a/components/original.py
+++ b/components/original.py
@@ -49,13 +49,17 @@ class Component(__base__.Component):
self.smoothConstantDown = 0.08
self.smoothConstantUp = 0.8
self.lastSpectrum = None
-
+ self.spectrumArray = {}
+
+ for i in range(0, len(self.completeAudioArray), self.sampleSize):
+ spectrum = transformData(i, self.completeAudioArray, self.sampleSize,
+ self.smoothConstantDown, self.smoothConstantUp, self.lastSpectrum)
+ self.spectrumArray[i] = spectrum
+
def frameRender(self, moduleNo, frameNo):
- self.lastSpectrum = transformData(frameNo, self.completeAudioArray, self.sampleSize,
- self.smoothConstantDown, self.smoothConstantUp, self.lastSpectrum)
width = int(self.worker.core.settings.value('outputWidth'))
height = int(self.worker.core.settings.value('outputHeight'))
- return drawBars(width, height, self.lastSpectrum, self.visColor, self.layout)
+ return drawBars(width, height, self.spectrumArray[frameNo], self.visColor, self.layout)
def pickColor(self):
RGBstring, btnStyle = super().pickColor()
diff --git a/main.py b/main.py
index 474ab29..4739465 100644
--- a/main.py
+++ b/main.py
@@ -245,6 +245,7 @@ class Main(QtCore.QObject):
self.videoWorker.videoCreated.connect(self.videoCreated)
self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated)
self.videoWorker.progressBarSetText.connect(self.progressBarSetText)
+ self.videoWorker.imageCreated.connect(self.showPreviewImage)
self.videoThread.start()
self.videoTask.emit(self.window.lineEdit_background.text(),
diff --git a/video_thread.py b/video_thread.py
index 93ca7bd..18b4e3e 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -6,136 +6,208 @@ import core
import numpy
import subprocess as sp
import sys
+from queue import Queue
+from threading import Thread
+import time
class Worker(QtCore.QObject):
- videoCreated = pyqtSignal()
- progressBarUpdate = pyqtSignal(int)
- progressBarSetText = pyqtSignal(str)
-
- def __init__(self, parent=None):
- QtCore.QObject.__init__(self)
- self.core = core.Core()
- self.core.settings = parent.settings
- self.modules = parent.modules
- self.stackedWidget = parent.window.stackedWidget
- parent.videoTask.connect(self.createVideo)
-
- @pyqtSlot(str, str, str, list)
- def createVideo(self, backgroundImage, inputFile, outputFile, components):
- # print('worker thread id: {}'.format(QtCore.QThread.currentThreadId()))
- def getBackgroundAtIndex(i):
- return self.core.drawBaseImage(backgroundFrames[i])
-
- progressBarValue = 0
- self.progressBarUpdate.emit(progressBarValue)
- self.progressBarSetText.emit('Loading background image…')
-
- backgroundFrames = self.core.parseBaseImage(backgroundImage)
- if len(backgroundFrames) < 2:
- # the base image is not a video so we can draw it now
- imBackground = getBackgroundAtIndex(0)
- else:
- # base images will be drawn while drawing the audio bars
- imBackground = None
-
- self.progressBarSetText.emit('Loading audio file…')
- completeAudioArray = self.core.readAudioFile(inputFile)
-
- # test if user has libfdk_aac
- encoders = sp.check_output(self.core.FFMPEG_BIN + " -encoders -hide_banner", shell=True)
- acodec = self.core.settings.value('outputAudioCodec')
-
- if b'libfdk_aac' in encoders and acodec == 'aac':
- acodec = 'libfdk_aac'
-
- ffmpegCommand = [ self.core.FFMPEG_BIN,
- '-y', # (optional) means overwrite the output file if it already exists.
- '-f', 'rawvideo',
- '-vcodec', 'rawvideo',
- '-s', self.core.settings.value('outputWidth')+'x'+self.core.settings.value('outputHeight'), # size of one frame
- '-pix_fmt', 'rgb24',
- '-r', self.core.settings.value('outputFrameRate'), # frames per second
- '-i', '-', # The input comes from a pipe
- '-an',
- '-i', inputFile,
- '-acodec', acodec, # output audio codec
- '-b:a', self.core.settings.value('outputAudioBitrate'),
- '-vcodec', self.core.settings.value('outputVideoCodec'),
- '-pix_fmt', self.core.settings.value('outputVideoFormat'),
- '-preset', self.core.settings.value('outputPreset'),
- '-f', self.core.settings.value('outputFormat')]
-
- if acodec == 'aac':
- ffmpegCommand.append('-strict')
- ffmpegCommand.append('-2')
-
- ffmpegCommand.append(outputFile)
-
- out_pipe = sp.Popen(ffmpegCommand,
- stdin=sp.PIPE,stdout=sys.stdout, stderr=sys.stdout)
-
- # initialize components
- print('######################## Data')
- print('loaded components:',
- ["%s%s" % (num, str(component)) for num, component in enumerate(components)])
- staticComponents = {}
- sampleSize = 1470
- for compNo, comp in enumerate(components):
- properties = None
- properties = comp.preFrameRender(worker=self, completeAudioArray=completeAudioArray, sampleSize=sampleSize)
- if properties and 'static' in properties:
- staticComponents[compNo] = None
-
- # create video for output
- numpy.seterr(divide='ignore')
- frame = getBackgroundAtIndex(0)
- bgI = 0
- for i in range(0, len(completeAudioArray), sampleSize):
- newFrame = Image.new("RGBA", (int(self.core.settings.value('outputWidth')), int(self.core.settings.value('outputHeight'))),(0,0,0,255))
- if imBackground:
- newFrame.paste(imBackground)
- else:
- newFrame.paste(getBackgroundAtIndex(bgI))
-
- # composite all frames returned by the components in order
- for compNo, comp in enumerate(components):
- if compNo in staticComponents and staticComponents[compNo] != None:
- newFrame = Image.alpha_composite(newFrame,staticComponents[compNo])
+ imageCreated = pyqtSignal(['QImage'])
+ videoCreated = pyqtSignal()
+ progressBarUpdate = pyqtSignal(int)
+ progressBarSetText = pyqtSignal(str)
+
+ def __init__(self, parent=None):
+ QtCore.QObject.__init__(self)
+ self.core = core.Core()
+ self.core.settings = parent.settings
+ self.modules = parent.modules
+ self.stackedWidget = parent.window.stackedWidget
+ self.parent = parent
+ parent.videoTask.connect(self.createVideo)
+ self.sampleSize = 1470
+
+ def renderNode(self):
+ while True:
+ i = self.compositeQueue.get()
+
+ frame = Image.new(
+ "RGBA",
+ (self.width, self.height),
+ (0, 0, 0, 255)
+ )
+
+ frame.paste(self.imBackground)
+
+ if self.imBackground is not None:
+ frame.paste(self.imBackground)
else:
- newFrame = Image.alpha_composite(newFrame,comp.frameRender(compNo, i))
- if i == 0 and compNo in staticComponents:
- staticComponents[compNo] = comp.frameRender(compNo, i)
+ frame.paste(self.getBackgroundAtIndex(i[1]))
+
+ for compNo, comp in enumerate(self.components):
+ if compNo in self.staticComponents and self.staticComponents[compNo] != None:
+ frame = Image.alpha_composite(frame, self.staticComponents[compNo])
+ else:
+ frame = Image.alpha_composite(frame, comp.frameRender(compNo, i[0]))
+
+ # frame.paste(compFrame, mask=compFrame)
- if not imBackground:
+ self.renderQueue.put([i[0], frame])
+ self.compositeQueue.task_done()
+
+ def renderDispatch(self):
+ print('Dispatching Frames for Compositing...')
+ if not self.imBackground:
# increment background video frame for next iteration
- if bgI < len(backgroundFrames)-1:
- bgI += 1
-
- # write to out_pipe
- try:
- frame = Image.new("RGB", (int(self.core.settings.value('outputWidth')), int(self.core.settings.value('outputHeight'))),(0,0,0))
- frame.paste(newFrame)
- out_pipe.stdin.write(frame.tobytes())
- finally:
- True
-
- # increase progress bar value
- if progressBarValue + 1 <= (i / len(completeAudioArray)) * 100:
- progressBarValue = numpy.floor((i / len(completeAudioArray)) * 100)
- self.progressBarUpdate.emit(progressBarValue)
- self.progressBarSetText.emit('%s%%' % str(int(progressBarValue)))
-
- numpy.seterr(all='print')
-
- out_pipe.stdin.close()
- if out_pipe.stderr is not None:
- print(out_pipe.stderr.read())
- out_pipe.stderr.close()
- # out_pipe.terminate() # don't terminate ffmpeg too early
- out_pipe.wait()
- print("Video file created")
- self.core.deleteTempDir()
- self.progressBarUpdate.emit(100)
- self.progressBarSetText.emit('100%')
- self.videoCreated.emit()
+ if self.bgI < len(self.backgroundFrames)-1 and i != 0:
+ self.bgI += 1
+
+ for i in range(0, len(self.completeAudioArray), self.sampleSize):
+ self.compositeQueue.put([i, self.bgI])
+ self.compositeQueue.join()
+ print('Compositing Complete.')
+
+ def previewDispatch(self):
+ while True:
+ i = self.previewQueue.get()
+ if time.time() - self.lastPreview >= 0.05 or i[0] == 0:
+ self._image = ImageQt(i[1])
+ self.imageCreated.emit(QtGui.QImage(self._image))
+ lastPreview = time.time()
+
+ self.previewQueue.task_done()
+
+
+ def getBackgroundAtIndex(self, i):
+ return self.core.drawBaseImage(self.backgroundFrames[i])
+
+ @pyqtSlot(str, str, str, list)
+ def createVideo(self, backgroundImage, inputFile, outputFile, components):
+ self.width = int(self.core.settings.value('outputWidth'))
+ self.height = int(self.core.settings.value('outputHeight'))
+ # print('worker thread id: {}'.format(QtCore.QThread.currentThreadId()))
+ self.components = components
+ progressBarValue = 0
+ self.progressBarUpdate.emit(progressBarValue)
+ self.progressBarSetText.emit('Loading background image…')
+
+ self.backgroundImage = backgroundImage
+
+ self.backgroundFrames = self.core.parseBaseImage(backgroundImage)
+ if len(self.backgroundFrames) < 2:
+ # the base image is not a video so we can draw it now
+ self.imBackground = self.getBackgroundAtIndex(0)
+ else:
+ # base images will be drawn while drawing the audio bars
+ self.imBackground = None
+ self.bgI = 0
+
+ self.progressBarSetText.emit('Loading audio file…')
+ self.completeAudioArray = self.core.readAudioFile(inputFile)
+
+ # test if user has libfdk_aac
+ encoders = sp.check_output(self.core.FFMPEG_BIN + " -encoders -hide_banner", shell=True)
+ acodec = self.core.settings.value('outputAudioCodec')
+
+ if b'libfdk_aac' in encoders and acodec == 'aac':
+ acodec = 'libfdk_aac'
+
+ ffmpegCommand = [
+ self.core.FFMPEG_BIN,
+ '-y', # (optional) means overwrite the output file if it already exists.
+ '-f', 'rawvideo',
+ '-vcodec', 'rawvideo',
+ '-s', str(self.width)+'x'+str(self.height), # size of one frame
+ '-pix_fmt', 'rgba',
+ '-r', self.core.settings.value('outputFrameRate'), # frames per second
+ '-i', '-', # The input comes from a pipe
+ '-an',
+ '-i', inputFile,
+ '-acodec', acodec, # output audio codec
+ '-b:a', self.core.settings.value('outputAudioBitrate'),
+ '-vcodec', self.core.settings.value('outputVideoCodec'),
+ '-pix_fmt', self.core.settings.value('outputVideoFormat'),
+ '-preset', self.core.settings.value('outputPreset'),
+ '-f', self.core.settings.value('outputFormat')
+ ]
+
+ if acodec == 'aac':
+ ffmpegCommand.append('-strict')
+ ffmpegCommand.append('-2')
+
+ ffmpegCommand.append(outputFile)
+ out_pipe = sp.Popen(ffmpegCommand, stdin=sp.PIPE,stdout=sys.stdout, stderr=sys.stdout)
+
+ # create video for output
+ numpy.seterr(divide='ignore')
+
+ self.compositeQueue = Queue()
+ self.compositeQueue.maxsize = 20
+ self.renderQueue = Queue()
+ self.renderQueue.maxsize = 20
+ self.previewQueue = Queue()
+
+ for i in range(2):
+ t = Thread(target=self.renderNode)
+ t.daemon = True
+ t.start()
+
+ self.dispatchThread = Thread(target=self.renderDispatch)
+ self.dispatchThread.daemon = True
+ self.dispatchThread.start()
+
+ self.previewDispatch = Thread(target=self.previewDispatch)
+ self.previewDispatch.daemon = True
+ self.previewDispatch.start()
+
+ frameBuffer = {}
+ self.lastPreview = 0.0
+
+ # initialize components
+ print('loaded components:',
+ ["%s%s" % (num, str(component)) for num, component in enumerate(components)])
+ self.staticComponents = {}
+ for compNo, comp in enumerate(components):
+ properties = None
+ properties = comp.preFrameRender(
+ worker=self,
+ completeAudioArray=self.completeAudioArray,
+ sampleSize=self.sampleSize
+ )
+
+ if properties and 'static' in properties:
+ self.staticComponents[compNo] = comp.frameRender(compNo, 0)
+
+ for i in range(0, len(self.completeAudioArray), self.sampleSize):
+ data = self.renderQueue.get()
+ frameBuffer[data[0]] = data[1]
+
+ if i in frameBuffer:
+ try:
+ out_pipe.stdin.write(frameBuffer[i].tobytes())
+ self.previewQueue.put([i, frameBuffer[i]])
+ del frameBuffer[i]
+ finally:
+ True
+ self.renderQueue.task_done()
+
+ # increase progress bar value
+ if progressBarValue + 1 <= (i / len(self.completeAudioArray)) * 100:
+ progressBarValue = numpy.floor((i / len(self.completeAudioArray)) * 100)
+ self.progressBarUpdate.emit(progressBarValue)
+ self.progressBarSetText.emit('%s%%' % str(int(progressBarValue)))
+
+ numpy.seterr(all='print')
+
+ out_pipe.stdin.close()
+ if out_pipe.stderr is not None:
+ print(out_pipe.stderr.read())
+ out_pipe.stderr.close()
+ # out_pipe.terminate() # don't terminate ffmpeg too early
+ out_pipe.wait()
+ print("Video file created")
+ self.parent.drawPreview()
+ self.core.deleteTempDir()
+ self.progressBarUpdate.emit(100)
+ self.progressBarSetText.emit('100%')
+ self.videoCreated.emit()
--
cgit v1.2.3
From 1eeb763dc31343762f827f9610eb464cce4fd7cc Mon Sep 17 00:00:00 2001
From: DH4
Date: Wed, 31 May 2017 04:01:18 -0500
Subject: Fixed frame loop bug.
---
video_thread.py | 35 ++++++++++++++++++-----------------
1 file changed, 18 insertions(+), 17 deletions(-)
diff --git a/video_thread.py b/video_thread.py
index 18b4e3e..c37741d 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -6,7 +6,7 @@ import core
import numpy
import subprocess as sp
import sys
-from queue import Queue
+from queue import Queue, PriorityQueue
from threading import Thread
import time
@@ -37,8 +37,6 @@ class Worker(QtCore.QObject):
(0, 0, 0, 255)
)
- frame.paste(self.imBackground)
-
if self.imBackground is not None:
frame.paste(self.imBackground)
else:
@@ -143,9 +141,9 @@ class Worker(QtCore.QObject):
self.compositeQueue = Queue()
self.compositeQueue.maxsize = 20
- self.renderQueue = Queue()
+ self.renderQueue = PriorityQueue()
self.renderQueue.maxsize = 20
- self.previewQueue = Queue()
+ self.previewQueue = PriorityQueue()
for i in range(2):
t = Thread(target=self.renderNode)
@@ -176,20 +174,23 @@ class Worker(QtCore.QObject):
)
if properties and 'static' in properties:
- self.staticComponents[compNo] = comp.frameRender(compNo, 0)
+ self.staticComponents[compNo] = comp.frameRender(compNo, 0)
for i in range(0, len(self.completeAudioArray), self.sampleSize):
- data = self.renderQueue.get()
- frameBuffer[data[0]] = data[1]
-
- if i in frameBuffer:
- try:
- out_pipe.stdin.write(frameBuffer[i].tobytes())
- self.previewQueue.put([i, frameBuffer[i]])
- del frameBuffer[i]
- finally:
- True
- self.renderQueue.task_done()
+
+ while True:
+ if i in frameBuffer:
+ break
+ data = self.renderQueue.get()
+ frameBuffer[data[0]] = data[1]
+ self.renderQueue.task_done()
+
+ try:
+ out_pipe.stdin.write(frameBuffer[i].tobytes())
+ self.previewQueue.put([i, frameBuffer[i]])
+ del frameBuffer[i]
+ finally:
+ True
# increase progress bar value
if progressBarValue + 1 <= (i / len(self.completeAudioArray)) * 100:
--
cgit v1.2.3
From 0b210fb4f010079ec082233786a77a85495845bc Mon Sep 17 00:00:00 2001
From: tassaron
Date: Wed, 31 May 2017 18:00:10 -0400
Subject: fix syntax error
---
components/__base__.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/components/__base__.py b/components/__base__.py
index 05d5cb6..45512ce 100644
--- a/components/__base__.py
+++ b/components/__base__.py
@@ -9,8 +9,8 @@ class Component:
return 1
def preFrameRender(self, **kwargs):
- for item in kwargs.items():
- exec('self.%s = %s' % item)
+ for var, value in kwargs.items():
+ exec('self.%s = value' % var)
def pickColor(self):
color = QtGui.QColorDialog.getColor()
--
cgit v1.2.3
From 2afdba04fded945f0b47f989e228a8eedd3d50d2 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 1 Jun 2017 09:05:20 -0400
Subject: minor fixes, comments
---
components/text.py | 7 ++-----
video_thread.py | 42 ++++++++++++++++++++++--------------------
2 files changed, 24 insertions(+), 25 deletions(-)
diff --git a/components/text.py b/components/text.py
index c9359f2..c0bb61f 100644
--- a/components/text.py
+++ b/components/text.py
@@ -95,19 +95,16 @@ class Component(__base__.Component):
im = Image.new("RGBA", (width, height),(0,0,0,0))
image = ImageQt(im)
- image1 = QtGui.QImage(image)
- painter = QPainter(image1)
+ painter = QPainter(image)
self.titleFont.setPixelSize(self.fontSize)
painter.setFont(self.titleFont)
painter.setPen(QColor(*self.textColor))
-
- fm = QtGui.QFontMetrics(self.titleFont)
painter.drawText(self.xPosition, self.yPosition, self.title)
painter.end()
buffer = QtCore.QBuffer()
buffer.open(QtCore.QIODevice.ReadWrite)
- image1.save(buffer, "PNG")
+ image.save(buffer, "PNG")
strio = io.BytesIO()
strio.write(buffer.data())
diff --git a/video_thread.py b/video_thread.py
index c37741d..6972be4 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -71,7 +71,7 @@ class Worker(QtCore.QObject):
if time.time() - self.lastPreview >= 0.05 or i[0] == 0:
self._image = ImageQt(i[1])
self.imageCreated.emit(QtGui.QImage(self._image))
- lastPreview = time.time()
+ self.lastPreview = time.time()
self.previewQueue.task_done()
@@ -139,48 +139,50 @@ class Worker(QtCore.QObject):
# create video for output
numpy.seterr(divide='ignore')
+ # initialize components
+ print('loaded components:',
+ ["%s%s" % (num, str(component)) for num, component in enumerate(components)])
+ self.staticComponents = {}
+ for compNo, comp in enumerate(components):
+ properties = None
+ properties = comp.preFrameRender(
+ worker=self,
+ completeAudioArray=self.completeAudioArray,
+ sampleSize=self.sampleSize
+ )
+
+ if properties and 'static' in properties:
+ self.staticComponents[compNo] = comp.frameRender(compNo, 0)
+
self.compositeQueue = Queue()
self.compositeQueue.maxsize = 20
self.renderQueue = PriorityQueue()
self.renderQueue.maxsize = 20
self.previewQueue = PriorityQueue()
+ # create threads to render frames and send them back here for piping out
for i in range(2):
- t = Thread(target=self.renderNode)
+ t = Thread(target=self.renderNode, name="Render Thread")
t.daemon = True
t.start()
- self.dispatchThread = Thread(target=self.renderDispatch)
+ self.dispatchThread = Thread(target=self.renderDispatch, name="Render Dispatch Thread")
self.dispatchThread.daemon = True
self.dispatchThread.start()
- self.previewDispatch = Thread(target=self.previewDispatch)
+ self.previewDispatch = Thread(target=self.previewDispatch, name="Render Dispatch Thread")
self.previewDispatch.daemon = True
self.previewDispatch.start()
frameBuffer = {}
self.lastPreview = 0.0
- # initialize components
- print('loaded components:',
- ["%s%s" % (num, str(component)) for num, component in enumerate(components)])
- self.staticComponents = {}
- for compNo, comp in enumerate(components):
- properties = None
- properties = comp.preFrameRender(
- worker=self,
- completeAudioArray=self.completeAudioArray,
- sampleSize=self.sampleSize
- )
-
- if properties and 'static' in properties:
- self.staticComponents[compNo] = comp.frameRender(compNo, 0)
-
for i in range(0, len(self.completeAudioArray), self.sampleSize):
-
while True:
if i in frameBuffer:
+ # if frame's in buffer, pipe it to ffmpeg
break
+ # else fetch the next frame & add to the buffer
data = self.renderQueue.get()
frameBuffer[data[0]] = data[1]
self.renderQueue.task_done()
--
cgit v1.2.3
From 43073cbd429fe415be3009124bee26b12ec8d2de Mon Sep 17 00:00:00 2001
From: DH4
Date: Thu, 1 Jun 2017 09:52:40 -0500
Subject: Fixed spectrum rendering. Fixed multiple static renders.
---
components/original.py | 52 ++++++++++++++++++++++++++------------------------
video_thread.py | 4 +++-
2 files changed, 30 insertions(+), 26 deletions(-)
diff --git a/components/original.py b/components/original.py
index 46e7182..045ffa0 100644
--- a/components/original.py
+++ b/components/original.py
@@ -4,6 +4,8 @@ from PyQt4 import uic, QtGui
from PyQt4.QtGui import QColor
import os, random
from . import __base__
+import time
+from copy import copy
class Component(__base__.Component):
@@ -52,9 +54,9 @@ class Component(__base__.Component):
self.spectrumArray = {}
for i in range(0, len(self.completeAudioArray), self.sampleSize):
- spectrum = transformData(i, self.completeAudioArray, self.sampleSize,
+ self.lastSpectrum = self.transformData(i, self.completeAudioArray, self.sampleSize,
self.smoothConstantDown, self.smoothConstantUp, self.lastSpectrum)
- self.spectrumArray[i] = spectrum
+ self.spectrumArray[i] = copy(self.lastSpectrum)
def frameRender(self, moduleNo, frameNo):
width = int(self.worker.core.settings.value('outputWidth'))
@@ -66,35 +68,35 @@ class Component(__base__.Component):
self.page.lineEdit_visColor.setText(RGBstring)
self.page.pushButton_visColor.setStyleSheet(btnStyle)
-def transformData(i, completeAudioArray, sampleSize, smoothConstantDown, smoothConstantUp, lastSpectrum):
- if len(completeAudioArray) < (i + sampleSize):
- sampleSize = len(completeAudioArray) - i
- numpy.seterr(divide='ignore')
- window = numpy.hanning(sampleSize)
- data = completeAudioArray[i:i+sampleSize][::1] * window
- paddedSampleSize = 2048
- paddedData = numpy.pad(data, (0, paddedSampleSize - sampleSize), 'constant')
- spectrum = numpy.fft.fft(paddedData)
- sample_rate = 44100
- frequencies = numpy.fft.fftfreq(len(spectrum), 1./sample_rate)
+ def transformData(self, i, completeAudioArray, sampleSize, smoothConstantDown, smoothConstantUp, lastSpectrum):
+ if len(completeAudioArray) < (i + sampleSize):
+ sampleSize = len(completeAudioArray) - i
- y = abs(spectrum[0:int(paddedSampleSize/2) - 1])
+ window = numpy.hanning(sampleSize)
+ data = completeAudioArray[i:i+sampleSize][::1] * window
+ paddedSampleSize = 2048
+ paddedData = numpy.pad(data, (0, paddedSampleSize - sampleSize), 'constant')
+ spectrum = numpy.fft.fft(paddedData)
+ sample_rate = 44100
+ frequencies = numpy.fft.fftfreq(len(spectrum), 1./sample_rate)
- # filter the noise away
- # y[y<80] = 0
+ y = abs(spectrum[0:int(paddedSampleSize/2) - 1])
- y = 20 * numpy.log10(y)
- y[numpy.isinf(y)] = 0
+ # filter the noise away
+ # y[y<80] = 0
- if lastSpectrum is not None:
- lastSpectrum[y < lastSpectrum] = y[y < lastSpectrum] * smoothConstantDown + lastSpectrum[y < lastSpectrum] * (1 - smoothConstantDown)
- lastSpectrum[y >= lastSpectrum] = y[y >= lastSpectrum] * smoothConstantUp + lastSpectrum[y >= lastSpectrum] * (1 - smoothConstantUp)
- else:
- lastSpectrum = y
+ y = 20 * numpy.log10(y)
+ y[numpy.isinf(y)] = 0
- x = frequencies[0:int(paddedSampleSize/2) - 1]
+ if lastSpectrum is not None:
+ lastSpectrum[y < lastSpectrum] = y[y < lastSpectrum] * smoothConstantDown + lastSpectrum[y < lastSpectrum] * (1 - smoothConstantDown)
+ lastSpectrum[y >= lastSpectrum] = y[y >= lastSpectrum] * smoothConstantUp + lastSpectrum[y >= lastSpectrum] * (1 - smoothConstantUp)
+ else:
+ lastSpectrum = y
- return lastSpectrum
+ x = frequencies[0:int(paddedSampleSize/2) - 1]
+
+ return lastSpectrum
def drawBars(width, height, spectrum, color, layout):
vH = height-height/8
diff --git a/video_thread.py b/video_thread.py
index 6972be4..388093a 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -9,6 +9,7 @@ import sys
from queue import Queue, PriorityQueue
from threading import Thread
import time
+from copy import copy
class Worker(QtCore.QObject):
@@ -152,7 +153,8 @@ class Worker(QtCore.QObject):
)
if properties and 'static' in properties:
- self.staticComponents[compNo] = comp.frameRender(compNo, 0)
+ self.staticComponents[compNo] = copy(comp.frameRender(compNo, 0))
+ print('done')
self.compositeQueue = Queue()
self.compositeQueue.maxsize = 20
--
cgit v1.2.3
From fcbe211bf1588d4d3740d5b8ec70aa45bbe8ea69 Mon Sep 17 00:00:00 2001
From: DH4
Date: Thu, 1 Jun 2017 11:01:51 -0500
Subject: Performance boost for static backgrounds. moved drawBars() inside
class.
---
components/original.py | 63 +++++++++++++++++++++++++-------------------------
video_thread.py | 23 +++++++++---------
2 files changed, 42 insertions(+), 44 deletions(-)
diff --git a/components/original.py b/components/original.py
index 045ffa0..9e7da30 100644
--- a/components/original.py
+++ b/components/original.py
@@ -44,7 +44,7 @@ class Component(__base__.Component):
spectrum = numpy.fromfunction(lambda x: 0.008*(x-128)**2, (255,), dtype="int16")
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
- return drawBars(width, height, spectrum, self.visColor, self.layout)
+ return self.drawBars(width, height, spectrum, self.visColor, self.layout)
def preFrameRender(self, **kwargs):
super().preFrameRender(**kwargs)
@@ -61,7 +61,7 @@ class Component(__base__.Component):
def frameRender(self, moduleNo, frameNo):
width = int(self.worker.core.settings.value('outputWidth'))
height = int(self.worker.core.settings.value('outputHeight'))
- return drawBars(width, height, self.spectrumArray[frameNo], self.visColor, self.layout)
+ return self.drawBars(width, height, self.spectrumArray[frameNo], self.visColor, self.layout)
def pickColor(self):
RGBstring, btnStyle = super().pickColor()
@@ -97,42 +97,41 @@ class Component(__base__.Component):
x = frequencies[0:int(paddedSampleSize/2) - 1]
return lastSpectrum
-
-def drawBars(width, height, spectrum, color, layout):
- vH = height-height/8
- bF = width / 64
- bH = bF / 2
- bQ = bF / 4
- imTop = Image.new("RGBA", (width, height),(0,0,0,0))
- draw = ImageDraw.Draw(imTop)
- r, g, b = color
- color2 = (r, g, b, 125)
- bP = height / 1200
+ def drawBars(self, width, height, spectrum, color, layout):
+ vH = height-height/8
+ bF = width / 64
+ bH = bF / 2
+ bQ = bF / 4
+ imTop = Image.new("RGBA", (width, height),(0,0,0,0))
+ draw = ImageDraw.Draw(imTop)
+ r, g, b = color
+ color2 = (r, g, b, 125)
- 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)
+ 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)
+ imBottom = imTop.transpose(Image.FLIP_TOP_BOTTOM)
- im = Image.new("RGBA", (width, height),(0,0,0,0))
+ im = Image.new("RGBA", (width, height),(0,0,0,0))
- if layout == 0:
- y = 0 - int(height/100*43)
- im.paste(imTop, (0, y), mask=imTop)
- y = 0 + int(height/100*43)
- im.paste(imBottom, (0, y), mask=imBottom)
+ if layout == 0:
+ y = 0 - int(height/100*43)
+ im.paste(imTop, (0, y), mask=imTop)
+ y = 0 + int(height/100*43)
+ im.paste(imBottom, (0, y), mask=imBottom)
- if layout == 1:
- y = 0 + int(height/100*10)
- im.paste(imTop, (0, y), mask=imTop)
- y = 0 - int(height/100*10)
- im.paste(imBottom, (0, y), mask=imBottom)
+ if layout == 1:
+ y = 0 + int(height/100*10)
+ im.paste(imTop, (0, y), mask=imTop)
+ y = 0 - int(height/100*10)
+ im.paste(imBottom, (0, y), mask=imBottom)
- if layout == 2:
- y = 0 + int(height/100*10)
- im.paste(imTop, (0, y), mask=imTop)
+ if layout == 2:
+ y = 0 + int(height/100*10)
+ im.paste(imTop, (0, y), mask=imTop)
- return im
+ return im
diff --git a/video_thread.py b/video_thread.py
index 388093a..dee254a 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -32,16 +32,10 @@ class Worker(QtCore.QObject):
while True:
i = self.compositeQueue.get()
- frame = Image.new(
- "RGBA",
- (self.width, self.height),
- (0, 0, 0, 255)
- )
-
if self.imBackground is not None:
- frame.paste(self.imBackground)
+ frame = self.imBackground
else:
- frame.paste(self.getBackgroundAtIndex(i[1]))
+ frame = self.getBackgroundAtIndex(i[1])
for compNo, comp in enumerate(self.components):
if compNo in self.staticComponents and self.staticComponents[compNo] != None:
@@ -75,10 +69,16 @@ class Worker(QtCore.QObject):
self.lastPreview = time.time()
self.previewQueue.task_done()
-
def getBackgroundAtIndex(self, i):
- return self.core.drawBaseImage(self.backgroundFrames[i])
+ background = Image.new(
+ "RGBA",
+ (self.width, self.height),
+ (0, 0, 0, 255)
+ )
+ layer = self.core.drawBaseImage(self.backgroundFrames[i])
+ background.paste(layer)
+ return background
@pyqtSlot(str, str, str, list)
def createVideo(self, backgroundImage, inputFile, outputFile, components):
@@ -154,7 +154,6 @@ class Worker(QtCore.QObject):
if properties and 'static' in properties:
self.staticComponents[compNo] = copy(comp.frameRender(compNo, 0))
- print('done')
self.compositeQueue = Queue()
self.compositeQueue.maxsize = 20
@@ -163,7 +162,7 @@ class Worker(QtCore.QObject):
self.previewQueue = PriorityQueue()
# create threads to render frames and send them back here for piping out
- for i in range(2):
+ for i in range(3):
t = Thread(target=self.renderNode, name="Render Thread")
t.daemon = True
t.start()
--
cgit v1.2.3
From f55d7d120639cc4b83850699b8de57e036977288 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 1 Jun 2017 13:17:36 -0400
Subject: saveable titleFont, xPosition glitches fixed
---
components/text.py | 24 +++++++++++++++++-------
main.py | 4 +++-
2 files changed, 20 insertions(+), 8 deletions(-)
diff --git a/components/text.py b/components/text.py
index 716030b..9237167 100644
--- a/components/text.py
+++ b/components/text.py
@@ -61,21 +61,29 @@ class Component(__base__.Component):
self.xPosition = self.page.spinBox_xTextAlign.value()
self.yPosition = self.page.spinBox_yTextAlign.value()
self.textColor = self.RGBFromString(self.page.lineEdit_textColor.text())
+
+ self.parent.drawPreview()
+
+ def getXY(self):
+ '''Returns true x, y after considering alignment settings'''
fm = QtGui.QFontMetrics(self.titleFont)
if self.alignment == 0: #Left
- self.xPosition = self.xPosition
+ x = self.xPosition
if self.alignment == 1: #Middle
- self.xPosition = self.xPosition - fm.width(self.title)/2
+ x = self.xPosition - fm.width(self.title)/2
if self.alignment == 2: #Right
- self.xPosition = self.xPosition - fm.width(self.title)
- self.parent.drawPreview()
-
+ x = self.xPosition - fm.width(self.title)
+ return x, self.yPosition
+
+
def loadPreset(self, pr):
self.page.lineEdit_title.setText(pr['title'])
+ font = QFont(); font.fromString(pr['titleFont'])
+ self.page.fontComboBox_titleFont.setCurrentFont(font)
self.page.spinBox_fontSize.setValue(pr['fontSize'])
+ self.page.comboBox_textAlign.setCurrentIndex(pr['alignment'])
self.page.spinBox_xTextAlign.setValue(pr['xPosition'])
self.page.spinBox_yTextAlign.setValue(pr['yPosition'])
- self.page.comboBox_textAlign.setCurrentIndex(pr['alignment'])
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)
@@ -83,6 +91,7 @@ class Component(__base__.Component):
def savePreset(self):
return {
'title' : self.title,
+ 'titleFont' : self.titleFont.toString(),
'alignment' : self.alignment,
'fontSize' : self.fontSize,
'xPosition' : self.xPosition,
@@ -105,6 +114,7 @@ class Component(__base__.Component):
return self.addText(width, height)
def addText(self, width, height):
+ x, y = self.getXY()
im = Image.new("RGBA", (width, height),(0,0,0,0))
image = ImageQt(im)
@@ -112,7 +122,7 @@ class Component(__base__.Component):
self.titleFont.setPixelSize(self.fontSize)
painter.setFont(self.titleFont)
painter.setPen(QColor(*self.textColor))
- painter.drawText(self.xPosition, self.yPosition, self.title)
+ painter.drawText(x, y, self.title)
painter.end()
buffer = QtCore.QBuffer()
diff --git a/main.py b/main.py
index 1cbe1db..c8ad4e8 100644
--- a/main.py
+++ b/main.py
@@ -388,12 +388,14 @@ class Main(QtCore.QObject):
if ch != 1024: # 1024 = OK
return
# remove old copies of the preset
- for i in range(0, self.windowcomboBox_openPreset.count()):
+ presetLen = self.window.comboBox_openPreset.count()
+ for i in range(0, presetLen):
if self.window.comboBox_openPreset.itemText(i) == filename:
self.window.comboBox_openPreset.removeItem(i)
with open(filepath, 'w') as f:
f.write('%s' % repr(saveValueStore))
self.window.comboBox_openPreset.addItem(filename)
+ self.window.comboBox_openPreset.setCurrentIndex(presetLen-1)
def openPreset(self):
if self.window.comboBox_openPreset.currentIndex() < 1:
--
cgit v1.2.3
From 1a24922fee9cb86bf8814f57c0f8f2dd77f27a3f Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 1 Jun 2017 16:16:42 -0400
Subject: restrict presets to boring characters
---
main.py | 40 +++++++++++++++++++++++++---------------
1 file changed, 25 insertions(+), 15 deletions(-)
diff --git a/main.py b/main.py
index c8ad4e8..00ba96b 100644
--- a/main.py
+++ b/main.py
@@ -1,8 +1,6 @@
-import sys, io, os
+import sys, io, os, atexit, string, signal
from os.path import expanduser
-import atexit
from queue import Queue
-import signal
from importlib import import_module
from PyQt4 import QtCore, QtGui, uic
from PyQt4.QtCore import QSettings, QModelIndex
@@ -365,14 +363,28 @@ class Main(QtCore.QObject):
def openSavePresetDialog(self):
if self.window.listWidget_componentList.currentRow() == -1:
return
- newName, OK = QtGui.QInputDialog.getText(QtGui.QWidget(), 'Audio Visualizer', 'New Preset Name:')
- if OK and newName:
- index = self.window.listWidget_componentList.currentRow()
- if index != -1:
- saveValueStore = self.selectedComponents[index].savePreset()
- componentName = str(self.selectedComponents[index]).strip()
- vers = self.selectedComponents[index].version()
- self.createPresetFile(componentName, vers, saveValueStore, newName)
+ while True:
+ newName, OK = QtGui.QInputDialog.getText(QtGui.QWidget(), 'Audio Visualizer', 'New Preset Name:')
+ badName = False
+ for letter in newName:
+ if letter in string.punctuation:
+ badName = True
+ if badName:
+ # some filesystems don't like bizarre characters
+ msg = QtGui.QMessageBox()
+ msg.setIcon(QtGui.QMessageBox.Information)
+ msg.setText("Preset names must contain only letters, numbers, and spaces.")
+ msg.setStandardButtons(QtGui.QMessageBox.Ok)
+ msg.exec_()
+ continue
+ if OK and newName:
+ index = self.window.listWidget_componentList.currentRow()
+ if index != -1:
+ saveValueStore = self.selectedComponents[index].savePreset()
+ componentName = str(self.selectedComponents[index]).strip()
+ vers = self.selectedComponents[index].version()
+ self.createPresetFile(componentName, vers, saveValueStore, newName)
+ break
def createPresetFile(self, componentName, version, saveValueStore, filename):
dirname = os.path.join(self.dataDir, 'presets', componentName, str(version))
@@ -388,21 +400,19 @@ class Main(QtCore.QObject):
if ch != 1024: # 1024 = OK
return
# remove old copies of the preset
- presetLen = self.window.comboBox_openPreset.count()
- for i in range(0, presetLen):
+ for i in range(0, self.window.comboBox_openPreset.count()):
if self.window.comboBox_openPreset.itemText(i) == filename:
self.window.comboBox_openPreset.removeItem(i)
with open(filepath, 'w') as f:
f.write('%s' % repr(saveValueStore))
self.window.comboBox_openPreset.addItem(filename)
- self.window.comboBox_openPreset.setCurrentIndex(presetLen-1)
+ self.window.comboBox_openPreset.setCurrentIndex(self.window.comboBox_openPreset.count()-1)
def openPreset(self):
if self.window.comboBox_openPreset.currentIndex() < 1:
return
index = self.window.listWidget_componentList.currentRow()
if index == -1:
- # no component selected
return
filename = self.window.comboBox_openPreset.itemText(self.window.comboBox_openPreset.currentIndex())
componentName = str(self.selectedComponents[index]).strip()
--
cgit v1.2.3
From 907ba33e93b5e8c33be8c74dd787f78e1b3fa109 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 1 Jun 2017 17:34:04 -0400
Subject: a handy showMessage() method
and starting on the project buttons
---
main.py | 48 ++++++++++++++++++++++++++++++++++--------------
1 file changed, 34 insertions(+), 14 deletions(-)
diff --git a/main.py b/main.py
index 00ba96b..dc18179 100644
--- a/main.py
+++ b/main.py
@@ -178,6 +178,8 @@ class Main(QtCore.QObject):
self.window.pushButton_savePreset.clicked.connect(self.openSavePresetDialog)
self.window.comboBox_openPreset.currentIndexChanged.connect(self.openPreset)
+ self.window.pushButton_saveProject.clicked.connect(self.openSaveProjectDialog)
+ #self.window.pushButton_openProject
self.drawPreview()
@@ -249,8 +251,7 @@ class Main(QtCore.QObject):
self.window.lineEdit_outputFile.text(),
self.selectedComponents)
else:
- # TODO: use QMessageBox or similar to alert user that fields are empty
- pass
+ self.showMessage("You must select an audio file and output filename.")
def progressBarUpdated(self, value):
self.window.progressBar_createVideo.setValue(value)
@@ -371,11 +372,7 @@ class Main(QtCore.QObject):
badName = True
if badName:
# some filesystems don't like bizarre characters
- msg = QtGui.QMessageBox()
- msg.setIcon(QtGui.QMessageBox.Information)
- msg.setText("Preset names must contain only letters, numbers, and spaces.")
- msg.setStandardButtons(QtGui.QMessageBox.Ok)
- msg.exec_()
+ self.showMessage("Preset names must contain only letters, numbers, and spaces.")
continue
if OK and newName:
index = self.window.listWidget_componentList.currentRow()
@@ -392,19 +389,15 @@ class Main(QtCore.QObject):
os.makedirs(dirname)
filepath = os.path.join(dirname, filename)
if os.path.exists(filepath):
- msg = QtGui.QMessageBox()
- msg.setIcon(QtGui.QMessageBox.Warning)
- msg.setText("%s already exists! Overwrite it?" % filename)
- msg.setStandardButtons(QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel)
- ch = msg.exec_()
- if ch != 1024: # 1024 = OK
+ ch = self.showMessage("%s already exists! Overwrite it?" % filename, QtGui.QMessageBox.Warning, True)
+ if not ch:
return
# remove old copies of the preset
for i in range(0, self.window.comboBox_openPreset.count()):
if self.window.comboBox_openPreset.itemText(i) == filename:
self.window.comboBox_openPreset.removeItem(i)
with open(filepath, 'w') as f:
- f.write('%s' % repr(saveValueStore))
+ f.write(repr(saveValueStore))
self.window.comboBox_openPreset.addItem(filename)
self.window.comboBox_openPreset.setCurrentIndex(self.window.comboBox_openPreset.count()-1)
@@ -429,6 +422,33 @@ class Main(QtCore.QObject):
self.selectedComponents[index].loadPreset(saveValueStore)
self.drawPreview()
+ def openSaveProjectDialog(self):
+ outputDir = os.path.join(self.dataDir, 'projects')
+ filename = QtGui.QFileDialog.getSaveFileName(self.window,
+ "Create Project File", outputDir)
+ if not filename:
+ return
+ filepath = os.path.join(outputDir, filename)
+ with open(filepath, 'w') as f:
+ for comp in self.selectedComponents:
+ saveValueStore = comp.savePreset()
+ f.write('%s\n' % str(comp))
+ f.write('%s\n' % str(comp.version()))
+ f.write('%s\n' % repr(saveValueStore))
+
+ def showMessage(self, string, icon=QtGui.QMessageBox.Information, showCancel=False):
+ msg = QtGui.QMessageBox()
+ msg.setIcon(icon)
+ msg.setText(string)
+ if showCancel:
+ msg.setStandardButtons(QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel)
+ else:
+ msg.setStandardButtons(QtGui.QMessageBox.Ok)
+ ch = msg.exec_()
+ if ch == 1024:
+ return True
+ return False
+
def LoadDefaultSettings(self):
self.resolutions = [
'1920x1080',
--
cgit v1.2.3
From 00f5c885842c15ef86cae9c0fdabca997016182b Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 1 Jun 2017 18:47:47 -0400
Subject: components can be saved and loaded as projects
---
main.py | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 50 insertions(+), 6 deletions(-)
diff --git a/main.py b/main.py
index dc18179..220d594 100644
--- a/main.py
+++ b/main.py
@@ -121,6 +121,7 @@ class Main(QtCore.QObject):
self.core = core.Core()
self.settings = QSettings('settings.ini', QSettings.IniFormat)
LoadDefaultSettings(self)
+ self.currentProject = None
# create data directory structure if needed
self.dataDir = QDesktopServices.storageLocation(QDesktopServices.DataLocation)
@@ -136,10 +137,8 @@ class Main(QtCore.QObject):
self.previewThread = QtCore.QThread(self)
self.previewWorker = preview_thread.Worker(self, self.previewQueue)
-
self.previewWorker.moveToThread(self.previewThread)
self.previewWorker.imageCreated.connect(self.showPreviewImage)
-
self.previewThread.start()
self.timer = QtCore.QTimer(self)
@@ -178,8 +177,9 @@ class Main(QtCore.QObject):
self.window.pushButton_savePreset.clicked.connect(self.openSavePresetDialog)
self.window.comboBox_openPreset.currentIndexChanged.connect(self.openPreset)
- self.window.pushButton_saveProject.clicked.connect(self.openSaveProjectDialog)
- #self.window.pushButton_openProject
+ self.window.pushButton_saveAs.clicked.connect(self.openSaveProjectDialog)
+ self.window.pushButton_saveProject.clicked.connect(self.saveCurrentProject)
+ self.window.pushButton_openProject.clicked.connect(self.openOpenProjectDialog)
self.drawPreview()
@@ -422,19 +422,55 @@ class Main(QtCore.QObject):
self.selectedComponents[index].loadPreset(saveValueStore)
self.drawPreview()
+ def saveCurrentProject(self):
+ if self.currentProject:
+ self.createProjectFile(self.currentProject)
+ else:
+ self.openSaveProjectDialog()
+
def openSaveProjectDialog(self):
outputDir = os.path.join(self.dataDir, 'projects')
- filename = QtGui.QFileDialog.getSaveFileName(self.window,
- "Create Project File", outputDir)
+ filename = QtGui.QFileDialog.getSaveFileName(self.window, "Create Project File", outputDir)
if not filename:
return
filepath = os.path.join(outputDir, filename)
+ self.currentProject = filepath
+ self.createProjectFile(filepath)
+
+ def createProjectFile(self, filepath):
with open(filepath, 'w') as f:
for comp in self.selectedComponents:
saveValueStore = comp.savePreset()
f.write('%s\n' % str(comp))
f.write('%s\n' % str(comp.version()))
f.write('%s\n' % repr(saveValueStore))
+
+ def openOpenProjectDialog(self):
+ inputDir = os.path.join(self.dataDir, 'projects')
+ filename = QtGui.QFileDialog.getOpenFileName(self.window, "Open Project File", inputDir)
+ if not filename:
+ return
+ filepath = os.path.join(inputDir, filename)
+ self.openProject(filepath)
+
+ def openProject(self, filepath):
+ self.clear()
+ self.currentProject = filepath
+ compNames = [mod.Component.__doc__ for mod in self.modules]
+ with open(filepath, 'r') as f:
+ i = 0
+ for line in f:
+ if i == 0:
+ compIndex = compNames.index(line.strip())
+ self.addComponent(compIndex)
+ i += 1
+ elif i == 1:
+ # version, not used yet
+ i += 1
+ elif i == 2:
+ saveValueStore = eval(line.strip())
+ self.selectedComponents[-1].loadPreset(saveValueStore)
+ i = 0
def showMessage(self, string, icon=QtGui.QMessageBox.Information, showCancel=False):
msg = QtGui.QMessageBox()
@@ -448,6 +484,14 @@ class Main(QtCore.QObject):
if ch == 1024:
return True
return False
+
+ def clear(self):
+ ''' empty out all components and fields, get a blank slate '''
+ self.selectedComponents = []
+ self.window.listWidget_componentList.clear()
+ for widget in self.pages:
+ self.window.stackedWidget.removeWidget(widget)
+ self.pages = []
def LoadDefaultSettings(self):
self.resolutions = [
--
cgit v1.2.3
From 610db2060678b8b848785dc8f309944de2a96e9a Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 1 Jun 2017 19:54:50 -0400
Subject: settings.ini now saved/loaded with projects
---
main.py | 43 +++++++++++++++++++++++--------------------
1 file changed, 23 insertions(+), 20 deletions(-)
diff --git a/main.py b/main.py
index 220d594..470a0a9 100644
--- a/main.py
+++ b/main.py
@@ -1,4 +1,4 @@
-import sys, io, os, atexit, string, signal
+import sys, io, os, shutil, atexit, string, signal
from os.path import expanduser
from queue import Queue
from importlib import import_module
@@ -119,22 +119,21 @@ class Main(QtCore.QObject):
# print('main thread id: {}'.format(QtCore.QThread.currentThreadId()))
self.window = window
self.core = core.Core()
- self.settings = QSettings('settings.ini', QSettings.IniFormat)
- LoadDefaultSettings(self)
self.currentProject = None
+ self.pages = []
+ self.selectedComponents = []
# create data directory structure if needed
self.dataDir = QDesktopServices.storageLocation(QDesktopServices.DataLocation)
if not os.path.exists(self.dataDir):
os.makedirs(self.dataDir)
- for neededDirectory in ('projects', 'presets'):
+ for neededDirectory in ('projects', 'project-settings', 'presets'):
if not os.path.exists(os.path.join(self.dataDir, neededDirectory)):
os.mkdir(os.path.join(self.dataDir, neededDirectory))
-
- self.pages = []
+ self.settings = QSettings(os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat)
+ LoadDefaultSettings(self)
self.previewQueue = Queue()
-
self.previewThread = QtCore.QThread(self)
self.previewWorker = preview_thread.Worker(self, self.previewQueue)
self.previewWorker.moveToThread(self.previewThread)
@@ -152,12 +151,11 @@ class Main(QtCore.QObject):
window.progressBar_createVideo.setValue(0)
window.pushButton_createVideo.clicked.connect(self.createAudioVisualisation)
window.setWindowTitle("Audio Visualizer")
-
+
self.modules = self.findComponents()
for component in self.modules:
window.comboBox_componentSelection.addItem(component.Component.__doc__)
window.listWidget_componentList.clicked.connect(lambda _: self.changeComponentWidget())
- self.selectedComponents = []
self.window.pushButton_addComponent.clicked.connect( \
lambda _: self.addComponent(self.window.comboBox_componentSelection.currentIndex())
@@ -189,16 +187,11 @@ class Main(QtCore.QObject):
self.timer.stop()
self.previewThread.quit()
self.previewThread.wait()
- # TODO: replace remembered settings with presets/projects
- '''
- self.settings.setValue("titleFont", self.window.fontComboBox_titleFont.currentFont().toString())
- self.settings.setValue("alignment", str(self.window.comboBox_textAlign.currentIndex()))
- self.settings.setValue("fontSize", str(self.window.spinBox_fontSize.value()))
- self.settings.setValue("xPosition", str(self.window.spinBox_xTextAlign.value()))
- self.settings.setValue("yPosition", str(self.window.spinBox_yTextAlign.value()))
- self.settings.setValue("visColor", self.window.lineEdit_visColor.text())
- self.settings.setValue("textColor", self.window.lineEdit_textColor.text())
- '''
+ backupPath = os.path.join(self.dataDir, 'settings.ini~')
+ settingsPath = os.path.join(self.dataDir, 'settings.ini')
+ if self.currentProject:
+ os.remove(settingsPath)
+ os.rename(backupPath, settingsPath)
def openInputFileDialog(self):
inputDir = self.settings.value("inputDir", expanduser("~"))
@@ -444,6 +437,8 @@ class Main(QtCore.QObject):
f.write('%s\n' % str(comp))
f.write('%s\n' % str(comp.version()))
f.write('%s\n' % repr(saveValueStore))
+ dir_ = os.path.join(self.dataDir, 'project-settings')
+ shutil.copyfile(self.settings.fileName(), os.path.join(dir_, os.path.basename('%s.ini' % filepath)))
def openOpenProjectDialog(self):
inputDir = os.path.join(self.dataDir, 'projects')
@@ -471,6 +466,15 @@ class Main(QtCore.QObject):
saveValueStore = eval(line.strip())
self.selectedComponents[-1].loadPreset(saveValueStore)
i = 0
+ projSettingsPath = os.path.join(self.dataDir, 'project-settings', '%s.ini' % os.path.basename(filepath))
+ backupPath = os.path.join(self.dataDir, 'settings.ini~')
+ settingsPath = os.path.join(self.dataDir, 'settings.ini')
+ if os.path.exists(backupPath):
+ os.remove(backupPath)
+ os.rename(settingsPath, backupPath)
+ #os.remove(settingsPath)
+ shutil.copyfile(projSettingsPath, settingsPath)
+ self.settings.sync()
def showMessage(self, string, icon=QtGui.QMessageBox.Information, showCancel=False):
msg = QtGui.QMessageBox()
@@ -510,7 +514,6 @@ def LoadDefaultSettings(self):
"outputVideoFormat": "yuv420p",
"outputPreset": "medium",
"outputFormat": "mp4",
- "visLayout": 0
}
for parm, value in default.items():
--
cgit v1.2.3
From d31add0d955f58d1fa375d3166ac519a21f80c6b Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 1 Jun 2017 20:21:26 -0400
Subject: tidying up
---
main.py | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/main.py b/main.py
index 470a0a9..ba62c2c 100644
--- a/main.py
+++ b/main.py
@@ -188,7 +188,7 @@ class Main(QtCore.QObject):
self.previewThread.quit()
self.previewThread.wait()
backupPath = os.path.join(self.dataDir, 'settings.ini~')
- settingsPath = os.path.join(self.dataDir, 'settings.ini')
+ settingsPath = self.settings.fileName()
if self.currentProject:
os.remove(settingsPath)
os.rename(backupPath, settingsPath)
@@ -327,6 +327,7 @@ class Main(QtCore.QObject):
self.window.stackedWidget.insertWidget(row - 1, page)
self.window.listWidget_componentList.setCurrentRow(row - 1)
self.window.stackedWidget.setCurrentIndex(row -1)
+ self.drawPreview()
def moveComponentDown(self):
row = self.window.listWidget_componentList.currentRow()
@@ -343,6 +344,7 @@ class Main(QtCore.QObject):
self.window.stackedWidget.insertWidget(row + 1, page)
self.window.listWidget_componentList.setCurrentRow(row + 1)
self.window.stackedWidget.setCurrentIndex(row + 1)
+ self.drawPreview()
def updateOpenPresetComboBox(self, component):
self.window.comboBox_openPreset.clear()
@@ -437,8 +439,8 @@ class Main(QtCore.QObject):
f.write('%s\n' % str(comp))
f.write('%s\n' % str(comp.version()))
f.write('%s\n' % repr(saveValueStore))
- dir_ = os.path.join(self.dataDir, 'project-settings')
- shutil.copyfile(self.settings.fileName(), os.path.join(dir_, os.path.basename('%s.ini' % filepath)))
+ projSettingsPath = os.path.join(self.dataDir, 'project-settings', os.path.basename('%s.ini' % filepath))
+ shutil.copyfile(self.settings.fileName(), projSettingsPath)
def openOpenProjectDialog(self):
inputDir = os.path.join(self.dataDir, 'projects')
@@ -468,11 +470,10 @@ class Main(QtCore.QObject):
i = 0
projSettingsPath = os.path.join(self.dataDir, 'project-settings', '%s.ini' % os.path.basename(filepath))
backupPath = os.path.join(self.dataDir, 'settings.ini~')
- settingsPath = os.path.join(self.dataDir, 'settings.ini')
+ settingsPath = self.settings.fileName()
if os.path.exists(backupPath):
os.remove(backupPath)
os.rename(settingsPath, backupPath)
- #os.remove(settingsPath)
shutil.copyfile(projSettingsPath, settingsPath)
self.settings.sync()
--
cgit v1.2.3
From 2768084b30da136dcfcb0c4e2e4264ad66083e4e Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 1 Jun 2017 20:31:15 -0400
Subject: resolution comboBox gets updated
---
main.py | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/main.py b/main.py
index ba62c2c..8bbbf08 100644
--- a/main.py
+++ b/main.py
@@ -354,7 +354,7 @@ class Main(QtCore.QObject):
if not os.path.exists(destination):
os.makedirs(destination)
for f in os.listdir(destination):
- self.window.comboBox_openPreset.addItem(f)
+ self.window.comboBox_openPreset.addItem(f)
def openSavePresetDialog(self):
if self.window.listWidget_componentList.currentRow() == -1:
@@ -476,6 +476,11 @@ class Main(QtCore.QObject):
os.rename(settingsPath, backupPath)
shutil.copyfile(projSettingsPath, settingsPath)
self.settings.sync()
+ currentRes = str(self.settings.value('outputWidth'))+'x'+str(self.settings.value('outputHeight'))
+ for i in range(self.window.comboBox_resolution.count()-1):
+ if self.window.comboBox_resolution.itemText(i) == currentRes:
+ self.window.comboBox_resolution.setCurrentIndex(i)
+ break
def showMessage(self, string, icon=QtGui.QMessageBox.Information, showCancel=False):
msg = QtGui.QMessageBox()
--
cgit v1.2.3
From 7d8e9ab3b16546e91144e256e88f9f490abc7ec2 Mon Sep 17 00:00:00 2001
From: DH4
Date: Thu, 1 Jun 2017 22:46:45 -0500
Subject: Added aspect ratio scaling to preview area.
---
background.jpg | Bin 0 -> 14782 bytes
main.py | 35 +++++++++++++++++++----
mainwindow.ui | 86 ++++++++++++++++----------------------------------------
video_thread.py | 2 +-
4 files changed, 55 insertions(+), 68 deletions(-)
create mode 100644 background.jpg
diff --git a/background.jpg b/background.jpg
new file mode 100644
index 0000000..f746432
Binary files /dev/null and b/background.jpg differ
diff --git a/main.py b/main.py
index 8bbbf08..f34cbee 100644
--- a/main.py
+++ b/main.py
@@ -3,7 +3,7 @@ from os.path import expanduser
from queue import Queue
from importlib import import_module
from PyQt4 import QtCore, QtGui, uic
-from PyQt4.QtCore import QSettings, QModelIndex
+from PyQt4.QtCore import QSettings, QModelIndex, Qt
from PyQt4.QtGui import QDesktopServices
import preview_thread, core, video_thread
@@ -107,6 +107,29 @@ class Command(QtCore.QObject):
self.settings.setValue("textColor", '%s,%s,%s' % self.textColor)
sys.exit(0)
'''
+
+class PreviewWindow(QtGui.QLabel):
+ def __init__(self, parent, img):
+ super(PreviewWindow, self).__init__()
+ self.parent = parent
+ self.setFrameStyle(QtGui.QFrame.StyledPanel)
+ self.pixmap = QtGui.QPixmap(img)
+
+ def paintEvent(self, event):
+ size = self.size()
+ painter = QtGui.QPainter(self)
+ point = QtCore.QPoint(0,0)
+ scaledPix = self.pixmap.scaled(size, Qt.KeepAspectRatio, transformMode = Qt.SmoothTransformation)
+ # start painting the label from left upper corner
+ point.setX((size.width() - scaledPix.width())/2)
+ point.setY((size.height() - scaledPix.height())/2)
+ #print point.x(), ' ', point.y()
+ painter.drawPixmap(point, scaledPix)
+
+ def changePixmap(self, img):
+ self.pixmap = QtGui.QPixmap(img)
+ self.repaint()
+
class Main(QtCore.QObject):
newTask = QtCore.pyqtSignal(str, list)
@@ -151,6 +174,9 @@ class Main(QtCore.QObject):
window.progressBar_createVideo.setValue(0)
window.pushButton_createVideo.clicked.connect(self.createAudioVisualisation)
window.setWindowTitle("Audio Visualizer")
+
+ self.previewWindow = PreviewWindow(self, r"background.jpg")
+ window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
self.modules = self.findComponents()
for component in self.modules:
@@ -269,10 +295,7 @@ class Main(QtCore.QObject):
# self.processTask.emit()
def showPreviewImage(self, image):
- self._scaledPreviewImage = image
- self._previewPixmap = QtGui.QPixmap.fromImage(self._scaledPreviewImage)
-
- self.window.label_previewContainer.setPixmap(self._previewPixmap)
+ self.previewWindow.changePixmap(image)
def findComponents(self):
def findComponents():
@@ -548,7 +571,7 @@ if __name__ == "__main__":
topMargin = 0 if (dpi == 96) else int(10 * (dpi / 96))
window.resize(window.width() * (dpi / 96), window.height() * (dpi / 96))
- window.verticalLayout_2.setContentsMargins(0, topMargin, 0, 0)
+ #window.verticalLayout_2.setContentsMargins(0, topMargin, 0, 0)
main = Main(window)
diff --git a/mainwindow.ui b/mainwindow.ui
index 0dcce91..808073b 100644
--- a/mainwindow.ui
+++ b/mainwindow.ui
@@ -27,75 +27,24 @@
-
-
-
+
+
+ QLayout::SetDefaultConstraint
+
0
-
-
-
- Qt::Vertical
-
-
- QSizePolicy::Minimum
-
-
-
- 0
- 0
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 356
- 280
-
-
-
-
- 0
- 0
-
-
-
- false
-
-
- background-color:rgba(255, 255, 255, 15);
-
-
-
-
-
-
- true
-
-
- Qt::AlignCenter
-
-
-
- -
-
+
- Qt::Vertical
+ Qt::Horizontal
- QSizePolicy::Minimum
+ QSizePolicy::MinimumExpanding
- 0
+ 420
0
@@ -105,16 +54,25 @@
-
+
+ QLayout::SetMinimumSize
+
3
-
+
+ QLayout::SetMinimumSize
+
3
-
+
+ QLayout::SetMinimumSize
+
-
@@ -141,10 +99,10 @@
-
- Qt::Vertical
+ Qt::Horizontal
- QSizePolicy::Fixed
+ QSizePolicy::Minimum
@@ -218,6 +176,12 @@
-
+
+
+ 0
+ 0
+
+
-
Open Preset
diff --git a/video_thread.py b/video_thread.py
index dee254a..a7c7ac6 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -63,7 +63,7 @@ class Worker(QtCore.QObject):
def previewDispatch(self):
while True:
i = self.previewQueue.get()
- if time.time() - self.lastPreview >= 0.05 or i[0] == 0:
+ if time.time() - self.lastPreview >= 0.06 or i[0] == 0:
self._image = ImageQt(i[1])
self.imageCreated.emit(QtGui.QImage(self._image))
self.lastPreview = time.time()
--
cgit v1.2.3
From 6bf36d0324ac4b04717a458adbb7172f717ec16a Mon Sep 17 00:00:00 2001
From: DH4
Date: Thu, 1 Jun 2017 23:24:13 -0500
Subject: Added ability to cancel export.
---
main.py | 14 +++++++++++---
video_thread.py | 52 ++++++++++++++++++++++++++++++++++++++++------------
2 files changed, 51 insertions(+), 15 deletions(-)
diff --git a/main.py b/main.py
index f34cbee..bb42d1f 100644
--- a/main.py
+++ b/main.py
@@ -173,6 +173,7 @@ class Main(QtCore.QObject):
window.toolButton_selectOutputFile.clicked.connect(self.openOutputFileDialog)
window.progressBar_createVideo.setValue(0)
window.pushButton_createVideo.clicked.connect(self.createAudioVisualisation)
+ window.pushButton_Cancel.clicked.connect(self.stopVideo)
window.setWindowTitle("Audio Visualizer")
self.previewWindow = PreviewWindow(self, r"background.jpg")
@@ -250,6 +251,13 @@ class Main(QtCore.QObject):
self.window.lineEdit_background.setText(fileName)
self.drawPreview()
+ def stopVideo(self):
+ print('stop')
+ try:
+ self.videoWorker.stopVideo()
+ except:
+ pass
+
def createAudioVisualisation(self):
# create output video if mandatory settings are filled in
if self.window.lineEdit_audioFile.text() and self.window.lineEdit_outputFile.text():
@@ -262,8 +270,8 @@ class Main(QtCore.QObject):
self.videoWorker.videoCreated.connect(self.videoCreated)
self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated)
self.videoWorker.progressBarSetText.connect(self.progressBarSetText)
- self.videoWorker.imageCreated.connect(self.showPreviewImage)
-
+ self.videoWorker.imageCreated.connect(self.showPreviewImage)
+
self.videoThread.start()
self.videoTask.emit(self.window.lineEdit_background.text(),
self.window.lineEdit_audioFile.text(),
@@ -271,7 +279,7 @@ class Main(QtCore.QObject):
self.selectedComponents)
else:
self.showMessage("You must select an audio file and output filename.")
-
+
def progressBarUpdated(self, value):
self.window.progressBar_createVideo.setValue(value)
diff --git a/video_thread.py b/video_thread.py
index a7c7ac6..2e9eb13 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -6,10 +6,12 @@ import core
import numpy
import subprocess as sp
import sys
+import os
from queue import Queue, PriorityQueue
from threading import Thread
import time
from copy import copy
+import signal
class Worker(QtCore.QObject):
@@ -27,6 +29,8 @@ class Worker(QtCore.QObject):
self.parent = parent
parent.videoTask.connect(self.createVideo)
self.sampleSize = 1470
+ self.canceled = False
+ self.error = False
def renderNode(self):
while True:
@@ -80,8 +84,14 @@ class Worker(QtCore.QObject):
background.paste(layer)
return background
+ def stopVideo(self):
+ print('Stop Export')
+ self.canceled = True
+ self.out_pipe.send_signal(signal.SIGINT)
+
@pyqtSlot(str, str, str, list)
def createVideo(self, backgroundImage, inputFile, outputFile, components):
+ self.outputFile = outputFile
self.width = int(self.core.settings.value('outputWidth'))
self.height = int(self.core.settings.value('outputHeight'))
# print('worker thread id: {}'.format(QtCore.QThread.currentThreadId()))
@@ -135,7 +145,9 @@ class Worker(QtCore.QObject):
ffmpegCommand.append('-2')
ffmpegCommand.append(outputFile)
- out_pipe = sp.Popen(ffmpegCommand, stdin=sp.PIPE,stdout=sys.stdout, stderr=sys.stdout)
+ self.out_pipe = sp.Popen(ffmpegCommand, stdin=sp.PIPE,stdout=sys.stdout, stderr=sys.stdout)
+
+
# create video for output
numpy.seterr(divide='ignore')
@@ -189,11 +201,11 @@ class Worker(QtCore.QObject):
self.renderQueue.task_done()
try:
- out_pipe.stdin.write(frameBuffer[i].tobytes())
+ self.out_pipe.stdin.write(frameBuffer[i].tobytes())
self.previewQueue.put([i, frameBuffer[i]])
del frameBuffer[i]
- finally:
- True
+ except:
+ break
# increase progress bar value
if progressBarValue + 1 <= (i / len(self.completeAudioArray)) * 100:
@@ -203,15 +215,31 @@ class Worker(QtCore.QObject):
numpy.seterr(all='print')
- out_pipe.stdin.close()
- if out_pipe.stderr is not None:
- print(out_pipe.stderr.read())
- out_pipe.stderr.close()
+ self.out_pipe.stdin.close()
+ if self.out_pipe.stderr is not None:
+ print(self.out_pipe.stderr.read())
+ self.out_pipe.stderr.close()
+ self.error = True
# out_pipe.terminate() # don't terminate ffmpeg too early
- out_pipe.wait()
- print("Video file created")
+ self.out_pipe.wait()
+ if self.canceled:
+ print("Export Canceled")
+ os.remove(self.outputFile)
+ self.progressBarUpdate.emit(0)
+ self.progressBarSetText.emit('Export Canceled')
+ else:
+ if self.error:
+ print("Export Failed")
+ self.progressBarUpdate.emit(0)
+ self.progressBarSetText.emit('Export Failed')
+ else:
+ print("Export Complete")
+ self.progressBarUpdate.emit(100)
+ self.progressBarSetText.emit('Export Complete')
+
+ self.error = False
+ self.canceled = False
self.parent.drawPreview()
self.core.deleteTempDir()
- self.progressBarUpdate.emit(100)
- self.progressBarSetText.emit('100%')
+
self.videoCreated.emit()
--
cgit v1.2.3
From 73a0492585e238d32869bfa9c53ddc95481ab1c5 Mon Sep 17 00:00:00 2001
From: DH4
Date: Fri, 2 Jun 2017 00:30:44 -0500
Subject: Cancel button stops pre-processing too.
---
components/original.py | 9 ++++++
components/text.py | 6 ++++
core.py | 9 ++++++
main.py | 31 ++++++++++++++++---
mainwindow.ui | 3 ++
video_thread.py | 82 ++++++++++++++++++++++++++++++--------------------
6 files changed, 103 insertions(+), 37 deletions(-)
diff --git a/components/original.py b/components/original.py
index 382c3ab..e1240e9 100644
--- a/components/original.py
+++ b/components/original.py
@@ -27,6 +27,7 @@ class Component(__base__.Component):
page.pushButton_visColor.setStyleSheet(btnStyle)
page.lineEdit_visColor.textChanged.connect(self.update)
self.page = page
+ self.canceled = False
return page
def update(self):
@@ -59,6 +60,8 @@ class Component(__base__.Component):
self.spectrumArray = {}
for i in range(0, len(self.completeAudioArray), self.sampleSize):
+ if self.canceled:
+ break
self.lastSpectrum = self.transformData(i, self.completeAudioArray, self.sampleSize,
self.smoothConstantDown, self.smoothConstantUp, self.lastSpectrum)
self.spectrumArray[i] = copy(self.lastSpectrum)
@@ -142,3 +145,9 @@ class Component(__base__.Component):
im.paste(imTop, (0, y), mask=imTop)
return im
+
+ def cancel(self):
+ self.canceled = True
+
+ def reset(self):
+ self.canceled = False
diff --git a/components/text.py b/components/text.py
index 9237167..da2706b 100644
--- a/components/text.py
+++ b/components/text.py
@@ -141,3 +141,9 @@ class Component(__base__.Component):
return
self.page.lineEdit_textColor.setText(RGBstring)
self.page.pushButton_textColor.setStyleSheet(btnStyle)
+
+ def cancel(self):
+ self.canceled = True
+
+ def reset(self):
+ self.canceled = False
\ No newline at end of file
diff --git a/core.py b/core.py
index 5478f93..0bbe001 100644
--- a/core.py
+++ b/core.py
@@ -7,6 +7,7 @@ from PIL import Image
import tempfile
from shutil import rmtree
import atexit
+import time
class Core():
@@ -67,6 +68,8 @@ class Core():
completeAudioArray = numpy.empty(0, dtype="int16")
while True:
+ if self.canceled:
+ break
# read 2 seconds of audio
raw_audio = in_pipe.stdout.read(88200*4)
if len(raw_audio) == 0:
@@ -110,3 +113,9 @@ class Core():
shell=True
)
return sorted([os.path.join(self.tempDir, f) for f in os.listdir(self.tempDir)])
+
+ def cancel(self):
+ self.canceled = True
+
+ def reset(self):
+ self.canceled = False
diff --git a/main.py b/main.py
index bb42d1f..94020b2 100644
--- a/main.py
+++ b/main.py
@@ -253,14 +253,15 @@ class Main(QtCore.QObject):
def stopVideo(self):
print('stop')
- try:
- self.videoWorker.stopVideo()
- except:
- pass
+ self.videoWorker.cancel()
+ self.canceled = True
def createAudioVisualisation(self):
# create output video if mandatory settings are filled in
if self.window.lineEdit_audioFile.text() and self.window.lineEdit_outputFile.text():
+ self.canceled = False
+ self.startExport = True
+ self.progressBarUpdated(-1)
ffmpeg_cmd = self.settings.value("ffmpeg_cmd", expanduser("~"))
self.videoThread = QtCore.QThread(self)
@@ -281,7 +282,27 @@ class Main(QtCore.QObject):
self.showMessage("You must select an audio file and output filename.")
def progressBarUpdated(self, value):
- self.window.progressBar_createVideo.setValue(value)
+ if value != -1:
+ self.window.progressBar_createVideo.setValue(value)
+
+ if self.canceled:
+ self.window.pushButton_createVideo.setEnabled(True)
+ self.window.pushButton_Cancel.setEnabled(False)
+ self.startExport = False
+ return
+
+ if value == 100 or value == 0:
+ if not self.startExport:
+ self.window.pushButton_createVideo.setEnabled(True)
+ self.window.pushButton_Cancel.setEnabled(False)
+ else:
+ if value == -1:
+ self.startExport = True
+ else:
+ self.startExport = False
+ self.window.pushButton_createVideo.setEnabled(False)
+ self.window.pushButton_Cancel.setEnabled(True)
+
def progressBarSetText(self, value):
self.window.progressBar_createVideo.setFormat(value)
diff --git a/mainwindow.ui b/mainwindow.ui
index 808073b..6119e63 100644
--- a/mainwindow.ui
+++ b/mainwindow.ui
@@ -711,6 +711,9 @@
-
+
+ false
+
Cancel
diff --git a/video_thread.py b/video_thread.py
index 2e9eb13..504102a 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -82,20 +82,16 @@ class Worker(QtCore.QObject):
)
layer = self.core.drawBaseImage(self.backgroundFrames[i])
background.paste(layer)
- return background
-
- def stopVideo(self):
- print('Stop Export')
- self.canceled = True
- self.out_pipe.send_signal(signal.SIGINT)
+ return background
@pyqtSlot(str, str, str, list)
def createVideo(self, backgroundImage, inputFile, outputFile, components):
+ self.components = components
self.outputFile = outputFile
+ self.reset()
self.width = int(self.core.settings.value('outputWidth'))
self.height = int(self.core.settings.value('outputHeight'))
# print('worker thread id: {}'.format(QtCore.QThread.currentThreadId()))
- self.components = components
progressBarValue = 0
self.progressBarUpdate.emit(progressBarValue)
self.progressBarSetText.emit('Loading background image…')
@@ -154,14 +150,14 @@ class Worker(QtCore.QObject):
# initialize components
print('loaded components:',
- ["%s%s" % (num, str(component)) for num, component in enumerate(components)])
+ ["%s%s" % (num, str(component)) for num, component in enumerate(self.components)])
self.staticComponents = {}
- for compNo, comp in enumerate(components):
+ for compNo, comp in enumerate(self.components):
properties = None
properties = comp.preFrameRender(
worker=self,
completeAudioArray=self.completeAudioArray,
- sampleSize=self.sampleSize
+ sampleSize=self.sampleSize,
)
if properties and 'static' in properties:
@@ -189,29 +185,29 @@ class Worker(QtCore.QObject):
frameBuffer = {}
self.lastPreview = 0.0
-
- for i in range(0, len(self.completeAudioArray), self.sampleSize):
- while True:
- if i in frameBuffer:
- # if frame's in buffer, pipe it to ffmpeg
+ if not self.canceled:
+ for i in range(0, len(self.completeAudioArray), self.sampleSize):
+ while True:
+ if i in frameBuffer:
+ # if frame's in buffer, pipe it to ffmpeg
+ break
+ # else fetch the next frame & add to the buffer
+ data = self.renderQueue.get()
+ frameBuffer[data[0]] = data[1]
+ self.renderQueue.task_done()
+
+ try:
+ self.out_pipe.stdin.write(frameBuffer[i].tobytes())
+ self.previewQueue.put([i, frameBuffer[i]])
+ del frameBuffer[i]
+ except:
break
- # else fetch the next frame & add to the buffer
- data = self.renderQueue.get()
- frameBuffer[data[0]] = data[1]
- self.renderQueue.task_done()
-
- try:
- self.out_pipe.stdin.write(frameBuffer[i].tobytes())
- self.previewQueue.put([i, frameBuffer[i]])
- del frameBuffer[i]
- except:
- break
- # increase progress bar value
- if progressBarValue + 1 <= (i / len(self.completeAudioArray)) * 100:
- progressBarValue = numpy.floor((i / len(self.completeAudioArray)) * 100)
- self.progressBarUpdate.emit(progressBarValue)
- self.progressBarSetText.emit('%s%%' % str(int(progressBarValue)))
+ # increase progress bar value
+ if progressBarValue + 1 <= (i / len(self.completeAudioArray)) * 100:
+ progressBarValue = numpy.floor((i / len(self.completeAudioArray)) * 100)
+ self.progressBarUpdate.emit(progressBarValue)
+ self.progressBarSetText.emit('%s%%' % str(int(progressBarValue)))
numpy.seterr(all='print')
@@ -224,7 +220,10 @@ class Worker(QtCore.QObject):
self.out_pipe.wait()
if self.canceled:
print("Export Canceled")
- os.remove(self.outputFile)
+ try:
+ os.remove(self.outputFile)
+ except:
+ pass
self.progressBarUpdate.emit(0)
self.progressBarSetText.emit('Export Canceled')
else:
@@ -243,3 +242,22 @@ class Worker(QtCore.QObject):
self.core.deleteTempDir()
self.videoCreated.emit()
+
+ def cancel(self):
+ self.canceled = True
+ self.core.cancel()
+
+ for comp in self.components:
+ comp.cancel()
+
+ try:
+ self.out_pipe.send_signal(signal.SIGINT)
+ except:
+ pass
+
+ def reset(self):
+ self.core.reset()
+
+ self.canceled = False
+ for comp in self.components:
+ comp.reset()
--
cgit v1.2.3
From 53598f7a85e0238d5c2c42cd248876fb4e06eb16 Mon Sep 17 00:00:00 2001
From: DH4
Date: Fri, 2 Jun 2017 03:30:51 -0500
Subject: Progressbar enhancement.
---
asd | Bin 0 -> 1310087 bytes
asdf | Bin 0 -> 9685175 bytes
components/original.py | 12 ++++++++++++
core.py | 35 +++++++++++++++++++++++++++++++++--
main.py | 23 ++++++-----------------
video_thread.py | 19 +++++++++++++++----
6 files changed, 66 insertions(+), 23 deletions(-)
create mode 100644 asd
create mode 100644 asdf
diff --git a/asd b/asd
new file mode 100644
index 0000000..39f0fa8
Binary files /dev/null and b/asd differ
diff --git a/asdf b/asdf
new file mode 100644
index 0000000..8cccca4
Binary files /dev/null and b/asdf differ
diff --git a/components/original.py b/components/original.py
index e1240e9..6903a5f 100644
--- a/components/original.py
+++ b/components/original.py
@@ -66,6 +66,14 @@ class Component(__base__.Component):
self.smoothConstantDown, self.smoothConstantUp, self.lastSpectrum)
self.spectrumArray[i] = copy(self.lastSpectrum)
+ progress = int(100*(i/len(self.completeAudioArray)))
+ if progress >= 100:
+ progress = 100
+ pStr = "Analyzing audio: "+ str(progress) +'%'
+ self.progressBarSetText.emit(pStr)
+ self.progressBarUpdate.emit(int(progress))
+
+
def frameRender(self, moduleNo, frameNo):
width = int(self.worker.core.settings.value('outputWidth'))
height = int(self.worker.core.settings.value('outputHeight'))
@@ -151,3 +159,7 @@ class Component(__base__.Component):
def reset(self):
self.canceled = False
+
+
+
+
diff --git a/core.py b/core.py
index 0bbe001..96f5670 100644
--- a/core.py
+++ b/core.py
@@ -55,7 +55,24 @@ class Core():
return im
- def readAudioFile(self, filename):
+ def readAudioFile(self, filename, parent):
+ command = [ self.FFMPEG_BIN,
+ '-i', filename]
+
+ try:
+ fileInfo = sp.check_output(command, stderr=sp.STDOUT, shell=False)
+ except sp.CalledProcessError as ex:
+ fileInfo = ex.output
+ pass
+
+ info = fileInfo.decode("utf-8").split('\n')
+ for line in info:
+ if 'Duration' in line:
+ d = line.split(',')[0]
+ d = d.split(' ')[3]
+ d = d.split(':')
+ duration = float(d[0])*3600 + float(d[1])*60 + float(d[2])
+
command = [ self.FFMPEG_BIN,
'-i', filename,
'-f', 's16le',
@@ -67,16 +84,30 @@ class Core():
completeAudioArray = numpy.empty(0, dtype="int16")
+ progress = 0
+ lastPercent = None
while True:
if self.canceled:
break
# read 2 seconds of audio
+ progress = progress + 4
raw_audio = in_pipe.stdout.read(88200*4)
if len(raw_audio) == 0:
break
audio_array = numpy.fromstring(raw_audio, dtype="int16")
completeAudioArray = numpy.append(completeAudioArray, audio_array)
- # print(audio_array)
+
+ percent = int(100*(progress/duration))
+ if percent >= 100:
+ percent = 100
+
+ if lastPercent != percent:
+ string = 'Loading audio file: '+str(percent)+'%'
+ parent.progressBarSetText.emit(string)
+ parent.progressBarUpdate.emit(percent)
+
+ lastPercent = percent
+
in_pipe.kill()
in_pipe.wait()
diff --git a/main.py b/main.py
index 94020b2..f13af7d 100644
--- a/main.py
+++ b/main.py
@@ -260,7 +260,7 @@ class Main(QtCore.QObject):
# create output video if mandatory settings are filled in
if self.window.lineEdit_audioFile.text() and self.window.lineEdit_outputFile.text():
self.canceled = False
- self.startExport = True
+ self.changeEncodingStatus(True)
self.progressBarUpdated(-1)
ffmpeg_cmd = self.settings.value("ffmpeg_cmd", expanduser("~"))
@@ -282,26 +282,15 @@ class Main(QtCore.QObject):
self.showMessage("You must select an audio file and output filename.")
def progressBarUpdated(self, value):
- if value != -1:
self.window.progressBar_createVideo.setValue(value)
-
- if self.canceled:
- self.window.pushButton_createVideo.setEnabled(True)
- self.window.pushButton_Cancel.setEnabled(False)
- self.startExport = False
- return
- if value == 100 or value == 0:
- if not self.startExport:
- self.window.pushButton_createVideo.setEnabled(True)
- self.window.pushButton_Cancel.setEnabled(False)
- else:
- if value == -1:
- self.startExport = True
- else:
- self.startExport = False
+ def changeEncodingStatus(self, status):
+ if status:
self.window.pushButton_createVideo.setEnabled(False)
self.window.pushButton_Cancel.setEnabled(True)
+ else:
+ self.window.pushButton_createVideo.setEnabled(True)
+ self.window.pushButton_Cancel.setEnabled(False)
def progressBarSetText(self, value):
diff --git a/video_thread.py b/video_thread.py
index 504102a..9f3eee2 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -107,8 +107,8 @@ class Worker(QtCore.QObject):
self.imBackground = None
self.bgI = 0
- self.progressBarSetText.emit('Loading audio file…')
- self.completeAudioArray = self.core.readAudioFile(inputFile)
+ self.progressBarSetText.emit('Loading audio file...')
+ self.completeAudioArray = self.core.readAudioFile(inputFile, self)
# test if user has libfdk_aac
encoders = sp.check_output(self.core.FFMPEG_BIN + " -encoders -hide_banner", shell=True)
@@ -152,12 +152,17 @@ class Worker(QtCore.QObject):
print('loaded components:',
["%s%s" % (num, str(component)) for num, component in enumerate(self.components)])
self.staticComponents = {}
+ numComps = len(self.components)
for compNo, comp in enumerate(self.components):
+ pStr = "Analyzing audio..."
+ self.progressBarSetText.emit(pStr)
properties = None
properties = comp.preFrameRender(
worker=self,
completeAudioArray=self.completeAudioArray,
sampleSize=self.sampleSize,
+ progressBarUpdate=self.progressBarUpdate,
+ progressBarSetText=self.progressBarSetText
)
if properties and 'static' in properties:
@@ -207,7 +212,8 @@ class Worker(QtCore.QObject):
if progressBarValue + 1 <= (i / len(self.completeAudioArray)) * 100:
progressBarValue = numpy.floor((i / len(self.completeAudioArray)) * 100)
self.progressBarUpdate.emit(progressBarValue)
- self.progressBarSetText.emit('%s%%' % str(int(progressBarValue)))
+ pStr = "Exporting video: " + str(int(progressBarValue)) + "%"
+ self.progressBarSetText.emit(pStr)
numpy.seterr(all='print')
@@ -226,6 +232,7 @@ class Worker(QtCore.QObject):
pass
self.progressBarUpdate.emit(0)
self.progressBarSetText.emit('Export Canceled')
+
else:
if self.error:
print("Export Failed")
@@ -240,9 +247,13 @@ class Worker(QtCore.QObject):
self.canceled = False
self.parent.drawPreview()
self.core.deleteTempDir()
-
+ self.parent.changeEncodingStatus(False)
self.videoCreated.emit()
+ def updateProgress(self, pStr, pVal):
+ self.progressBarValue.emit(pVal)
+ self.progressBarSetText.emit(pStr)
+
def cancel(self):
self.canceled = True
self.core.cancel()
--
cgit v1.2.3
From e33caa9179e972bc7ffdad0761d020e977559a5d Mon Sep 17 00:00:00 2001
From: DH4
Date: Fri, 2 Jun 2017 08:14:04 -0500
Subject: Threading changes.
---
asd | Bin 1310087 -> 0 bytes
asdf | Bin 9685175 -> 0 bytes
main.py | 4 +---
video_thread.py | 31 ++++++++++++++++++-------------
4 files changed, 19 insertions(+), 16 deletions(-)
delete mode 100644 asd
delete mode 100644 asdf
diff --git a/asd b/asd
deleted file mode 100644
index 39f0fa8..0000000
Binary files a/asd and /dev/null differ
diff --git a/asdf b/asdf
deleted file mode 100644
index 8cccca4..0000000
Binary files a/asdf and /dev/null differ
diff --git a/main.py b/main.py
index f13af7d..cba4ce7 100644
--- a/main.py
+++ b/main.py
@@ -263,16 +263,13 @@ class Main(QtCore.QObject):
self.changeEncodingStatus(True)
self.progressBarUpdated(-1)
ffmpeg_cmd = self.settings.value("ffmpeg_cmd", expanduser("~"))
-
self.videoThread = QtCore.QThread(self)
self.videoWorker = video_thread.Worker(self)
-
self.videoWorker.moveToThread(self.videoThread)
self.videoWorker.videoCreated.connect(self.videoCreated)
self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated)
self.videoWorker.progressBarSetText.connect(self.progressBarSetText)
self.videoWorker.imageCreated.connect(self.showPreviewImage)
-
self.videoThread.start()
self.videoTask.emit(self.window.lineEdit_background.text(),
self.window.lineEdit_audioFile.text(),
@@ -291,6 +288,7 @@ class Main(QtCore.QObject):
else:
self.window.pushButton_createVideo.setEnabled(True)
self.window.pushButton_Cancel.setEnabled(False)
+
def progressBarSetText(self, value):
diff --git a/video_thread.py b/video_thread.py
index 9f3eee2..64bbd5f 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -8,7 +8,7 @@ import subprocess as sp
import sys
import os
from queue import Queue, PriorityQueue
-from threading import Thread
+from threading import Thread, Event
import time
from copy import copy
import signal
@@ -31,9 +31,10 @@ class Worker(QtCore.QObject):
self.sampleSize = 1470
self.canceled = False
self.error = False
+ self.stopped = False
def renderNode(self):
- while True:
+ while not self.stopped:
i = self.compositeQueue.get()
if self.imBackground is not None:
@@ -61,11 +62,9 @@ class Worker(QtCore.QObject):
for i in range(0, len(self.completeAudioArray), self.sampleSize):
self.compositeQueue.put([i, self.bgI])
- self.compositeQueue.join()
- print('Compositing Complete.')
def previewDispatch(self):
- while True:
+ while not self.stopped:
i = self.previewQueue.get()
if time.time() - self.lastPreview >= 0.06 or i[0] == 0:
self._image = ImageQt(i[1])
@@ -82,7 +81,7 @@ class Worker(QtCore.QObject):
)
layer = self.core.drawBaseImage(self.backgroundFrames[i])
background.paste(layer)
- return background
+ return background
@pyqtSlot(str, str, str, list)
def createVideo(self, backgroundImage, inputFile, outputFile, components):
@@ -113,7 +112,7 @@ class Worker(QtCore.QObject):
# test if user has libfdk_aac
encoders = sp.check_output(self.core.FFMPEG_BIN + " -encoders -hide_banner", shell=True)
acodec = self.core.settings.value('outputAudioCodec')
-
+
if b'libfdk_aac' in encoders and acodec == 'aac':
acodec = 'libfdk_aac'
@@ -143,8 +142,6 @@ class Worker(QtCore.QObject):
ffmpegCommand.append(outputFile)
self.out_pipe = sp.Popen(ffmpegCommand, stdin=sp.PIPE,stdout=sys.stdout, stderr=sys.stdout)
-
-
# create video for output
numpy.seterr(divide='ignore')
@@ -167,6 +164,7 @@ class Worker(QtCore.QObject):
if properties and 'static' in properties:
self.staticComponents[compNo] = copy(comp.frameRender(compNo, 0))
+ self.progressBarUpdate.emit(100)
self.compositeQueue = Queue()
self.compositeQueue.maxsize = 20
@@ -174,11 +172,12 @@ class Worker(QtCore.QObject):
self.renderQueue.maxsize = 20
self.previewQueue = PriorityQueue()
+ self.renderThreads = []
# create threads to render frames and send them back here for piping out
for i in range(3):
- t = Thread(target=self.renderNode, name="Render Thread")
- t.daemon = True
- t.start()
+ self.renderThreads.append(Thread(target=self.renderNode, name="Render Thread"))
+ self.renderThreads[i].daemon = True
+ self.renderThreads[i].start()
self.dispatchThread = Thread(target=self.renderDispatch, name="Render Dispatch Thread")
self.dispatchThread.daemon = True
@@ -190,6 +189,9 @@ class Worker(QtCore.QObject):
frameBuffer = {}
self.lastPreview = 0.0
+ self.progressBarUpdate.emit(0)
+ pStr = "Exporting video..."
+ self.progressBarSetText.emit(pStr)
if not self.canceled:
for i in range(0, len(self.completeAudioArray), self.sampleSize):
while True:
@@ -247,8 +249,11 @@ class Worker(QtCore.QObject):
self.canceled = False
self.parent.drawPreview()
self.core.deleteTempDir()
- self.parent.changeEncodingStatus(False)
+ self.stopped = True
self.videoCreated.emit()
+ self.parent.changeEncodingStatus(False)
+
+ return
def updateProgress(self, pStr, pVal):
self.progressBarValue.emit(pVal)
--
cgit v1.2.3
From 4b566601772a00e354e0f144bb3dc76ed043be4f Mon Sep 17 00:00:00 2001
From: DH4
Date: Sat, 3 Jun 2017 00:07:30 -0500
Subject: Changed encoding update to signal/slot.
---
main.py | 4 ++--
mainwindow.ui | 4 ++--
video_thread.py | 7 +++----
3 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/main.py b/main.py
index cba4ce7..e104b2b 100644
--- a/main.py
+++ b/main.py
@@ -260,7 +260,6 @@ class Main(QtCore.QObject):
# create output video if mandatory settings are filled in
if self.window.lineEdit_audioFile.text() and self.window.lineEdit_outputFile.text():
self.canceled = False
- self.changeEncodingStatus(True)
self.progressBarUpdated(-1)
ffmpeg_cmd = self.settings.value("ffmpeg_cmd", expanduser("~"))
self.videoThread = QtCore.QThread(self)
@@ -269,7 +268,8 @@ class Main(QtCore.QObject):
self.videoWorker.videoCreated.connect(self.videoCreated)
self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated)
self.videoWorker.progressBarSetText.connect(self.progressBarSetText)
- self.videoWorker.imageCreated.connect(self.showPreviewImage)
+ self.videoWorker.imageCreated.connect(self.showPreviewImage)
+ self.videoWorker.encoding.connect(self.changeEncodingStatus)
self.videoThread.start()
self.videoTask.emit(self.window.lineEdit_background.text(),
self.window.lineEdit_audioFile.text(),
diff --git a/mainwindow.ui b/mainwindow.ui
index 6119e63..42a22a6 100644
--- a/mainwindow.ui
+++ b/mainwindow.ui
@@ -240,7 +240,7 @@
QTabWidget::Rounded
- 0
+ 2
@@ -712,7 +712,7 @@
-
- false
+ true
Cancel
diff --git a/video_thread.py b/video_thread.py
index 64bbd5f..4032c27 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -19,6 +19,7 @@ class Worker(QtCore.QObject):
videoCreated = pyqtSignal()
progressBarUpdate = pyqtSignal(int)
progressBarSetText = pyqtSignal(str)
+ encoding = pyqtSignal(bool)
def __init__(self, parent=None):
QtCore.QObject.__init__(self)
@@ -85,6 +86,7 @@ class Worker(QtCore.QObject):
@pyqtSlot(str, str, str, list)
def createVideo(self, backgroundImage, inputFile, outputFile, components):
+ self.encoding.emit(True)
self.components = components
self.outputFile = outputFile
self.reset()
@@ -250,10 +252,8 @@ class Worker(QtCore.QObject):
self.parent.drawPreview()
self.core.deleteTempDir()
self.stopped = True
+ self.encoding.emit(False)
self.videoCreated.emit()
- self.parent.changeEncodingStatus(False)
-
- return
def updateProgress(self, pStr, pVal):
self.progressBarValue.emit(pVal)
@@ -273,7 +273,6 @@ class Worker(QtCore.QObject):
def reset(self):
self.core.reset()
-
self.canceled = False
for comp in self.components:
comp.reset()
--
cgit v1.2.3
From fccdee45b291bbb4570650b6c1ff00dd21dbb43f Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 3 Jun 2017 08:46:18 -0400
Subject: absolute path to main ui, bg video fixed
---
main.py | 2 +-
video_thread.py | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/main.py b/main.py
index e104b2b..9fac283 100644
--- a/main.py
+++ b/main.py
@@ -580,7 +580,7 @@ if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
app.setApplicationName("audio-visualizer")
app.setOrganizationName("audio-visualizer")
- window = uic.loadUi("mainwindow.ui")
+ window = uic.loadUi(os.path.join(os.path.dirname(os.path.realpath(__file__)), "mainwindow.ui"))
# window.adjustSize()
desc = QtGui.QDesktopWidget()
dpi = desc.physicalDpiX()
diff --git a/video_thread.py b/video_thread.py
index 4032c27..0d42406 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -56,13 +56,13 @@ class Worker(QtCore.QObject):
def renderDispatch(self):
print('Dispatching Frames for Compositing...')
- if not self.imBackground:
- # increment background video frame for next iteration
- if self.bgI < len(self.backgroundFrames)-1 and i != 0:
- self.bgI += 1
for i in range(0, len(self.completeAudioArray), self.sampleSize):
self.compositeQueue.put([i, self.bgI])
+ if not self.imBackground:
+ # increment background video frame for next iteration
+ if self.bgI < len(self.backgroundFrames)-1:
+ self.bgI += 1
def previewDispatch(self):
while not self.stopped:
--
cgit v1.2.3
From 0bef283f8d4538a6a3cff740a530973e745ad9c8 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 3 Jun 2017 10:01:47 -0400
Subject: saved project dirs
---
components/text.py | 10 +++++---
main.py | 75 ++++++++++++++++++++++++------------------------------
2 files changed, 40 insertions(+), 45 deletions(-)
diff --git a/components/text.py b/components/text.py
index da2706b..bfc9701 100644
--- a/components/text.py
+++ b/components/text.py
@@ -8,6 +8,10 @@ from . import __base__
class Component(__base__.Component):
'''Title Text'''
+ def __init__(self):
+ super().__init__()
+ self.titleFont = QFont()
+
def widget(self, parent):
height = int(parent.settings.value('outputHeight'))
width = int(parent.settings.value('outputWidth'))
@@ -36,8 +40,8 @@ class Component(__base__.Component):
page.pushButton_textColor.setStyleSheet(btnStyle)
page.lineEdit_title.setText(self.title)
- if not self.titleFont == None:
- page.fontComboBox_titleFont.setCurrentFont(QFont(self.titleFont))
+ #if self.titleFont:
+ # page.fontComboBox_titleFont.setCurrentFont(QFont(self.titleFont))
page.comboBox_textAlign.setCurrentIndex(int(self.alignment))
page.spinBox_fontSize.setValue(int(self.fontSize))
page.spinBox_xTextAlign.setValue(int(self.xPosition))
@@ -146,4 +150,4 @@ class Component(__base__.Component):
self.canceled = True
def reset(self):
- self.canceled = False
\ No newline at end of file
+ self.canceled = False
diff --git a/main.py b/main.py
index 9fac283..df9a4f6 100644
--- a/main.py
+++ b/main.py
@@ -146,16 +146,18 @@ class Main(QtCore.QObject):
self.pages = []
self.selectedComponents = []
- # create data directory structure if needed
+ # create data directory, load/create settings
self.dataDir = QDesktopServices.storageLocation(QDesktopServices.DataLocation)
- if not os.path.exists(self.dataDir):
- os.makedirs(self.dataDir)
- for neededDirectory in ('projects', 'project-settings', 'presets'):
- if not os.path.exists(os.path.join(self.dataDir, neededDirectory)):
- os.mkdir(os.path.join(self.dataDir, neededDirectory))
self.settings = QSettings(os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat)
LoadDefaultSettings(self)
+ if not os.path.exists(self.dataDir):
+ os.makedirs(self.dataDir)
+ presetDir = os.path.join(self.dataDir, 'presets')
+ for neededDirectory in (presetDir, self.settings.value("projectDir")):
+ if not os.path.exists(neededDirectory):
+ os.mkdir(neededDirectory)
+ #
self.previewQueue = Queue()
self.previewThread = QtCore.QThread(self)
self.previewWorker = preview_thread.Worker(self, self.previewQueue)
@@ -176,7 +178,7 @@ class Main(QtCore.QObject):
window.pushButton_Cancel.clicked.connect(self.stopVideo)
window.setWindowTitle("Audio Visualizer")
- self.previewWindow = PreviewWindow(self, r"background.jpg")
+ self.previewWindow = PreviewWindow(self, os.path.join(os.path.dirname(os.path.realpath(__file__)), "background.jpg"))
window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
self.modules = self.findComponents()
@@ -206,19 +208,14 @@ class Main(QtCore.QObject):
self.window.pushButton_saveProject.clicked.connect(self.saveCurrentProject)
self.window.pushButton_openProject.clicked.connect(self.openOpenProjectDialog)
+ self.openProject(self.settings.value("lastProject"))
self.drawPreview()
-
window.show()
def cleanUp(self):
self.timer.stop()
self.previewThread.quit()
self.previewThread.wait()
- backupPath = os.path.join(self.dataDir, 'settings.ini~')
- settingsPath = self.settings.fileName()
- if self.currentProject:
- os.remove(settingsPath)
- os.rename(backupPath, settingsPath)
def openInputFileDialog(self):
inputDir = self.settings.value("inputDir", expanduser("~"))
@@ -343,13 +340,15 @@ class Main(QtCore.QObject):
self.window.listWidget_componentList.takeItem(index)
self.selectedComponents.pop(index)
self.pages.pop(index)
+ self.changeComponentWidget()
self.drawPreview()
def changeComponentWidget(self):
selected = self.window.listWidget_componentList.selectedItems()
- index = self.window.listWidget_componentList.row(selected[0])
- self.window.stackedWidget.setCurrentIndex(index)
- self.updateOpenPresetComboBox(self.selectedComponents[index])
+ if selected:
+ index = self.window.listWidget_componentList.row(selected[0])
+ self.window.stackedWidget.setCurrentIndex(index)
+ self.updateOpenPresetComboBox(self.selectedComponents[index])
def moveComponentUp(self):
row = self.window.listWidget_componentList.currentRow()
@@ -463,35 +462,39 @@ class Main(QtCore.QObject):
self.openSaveProjectDialog()
def openSaveProjectDialog(self):
- outputDir = os.path.join(self.dataDir, 'projects')
- filename = QtGui.QFileDialog.getSaveFileName(self.window, "Create Project File", outputDir)
+ filename = QtGui.QFileDialog.getSaveFileName(self.window,
+ "Create Project File", self.settings.value("projectDir"),
+ "Project Files (*.avp)")
if not filename:
return
- filepath = os.path.join(outputDir, filename)
- self.currentProject = filepath
- self.createProjectFile(filepath)
+ self.createProjectFile(filename)
def createProjectFile(self, filepath):
+ if not filepath.endswith(".avp"):
+ filepath += '.avp'
with open(filepath, 'w') as f:
for comp in self.selectedComponents:
saveValueStore = comp.savePreset()
f.write('%s\n' % str(comp))
f.write('%s\n' % str(comp.version()))
f.write('%s\n' % repr(saveValueStore))
- projSettingsPath = os.path.join(self.dataDir, 'project-settings', os.path.basename('%s.ini' % filepath))
- shutil.copyfile(self.settings.fileName(), projSettingsPath)
+ self.settings.setValue("projectDir", os.path.dirname(filepath))
+ self.settings.setValue("lastProject", filepath)
+ self.currentProject = filepath
def openOpenProjectDialog(self):
- inputDir = os.path.join(self.dataDir, 'projects')
- filename = QtGui.QFileDialog.getOpenFileName(self.window, "Open Project File", inputDir)
- if not filename:
- return
- filepath = os.path.join(inputDir, filename)
- self.openProject(filepath)
+ filename = QtGui.QFileDialog.getOpenFileName(self.window,
+ "Open Project File", self.settings.value("projectDir"),
+ "Project Files (*.avp)")
+ self.openProject(filename)
def openProject(self, filepath):
+ if not filepath or not os.path.exists(filepath) or not filepath.endswith('.avp'):
+ return
self.clear()
self.currentProject = filepath
+ self.settings.setValue("lastProject", filepath)
+ self.settings.setValue("projectDir", os.path.dirname(filepath))
compNames = [mod.Component.__doc__ for mod in self.modules]
with open(filepath, 'r') as f:
i = 0
@@ -507,19 +510,6 @@ class Main(QtCore.QObject):
saveValueStore = eval(line.strip())
self.selectedComponents[-1].loadPreset(saveValueStore)
i = 0
- projSettingsPath = os.path.join(self.dataDir, 'project-settings', '%s.ini' % os.path.basename(filepath))
- backupPath = os.path.join(self.dataDir, 'settings.ini~')
- settingsPath = self.settings.fileName()
- if os.path.exists(backupPath):
- os.remove(backupPath)
- os.rename(settingsPath, backupPath)
- shutil.copyfile(projSettingsPath, settingsPath)
- self.settings.sync()
- currentRes = str(self.settings.value('outputWidth'))+'x'+str(self.settings.value('outputHeight'))
- for i in range(self.window.comboBox_resolution.count()-1):
- if self.window.comboBox_resolution.itemText(i) == currentRes:
- self.window.comboBox_resolution.setCurrentIndex(i)
- break
def showMessage(self, string, icon=QtGui.QMessageBox.Information, showCancel=False):
msg = QtGui.QMessageBox()
@@ -559,6 +549,7 @@ def LoadDefaultSettings(self):
"outputVideoFormat": "yuv420p",
"outputPreset": "medium",
"outputFormat": "mp4",
+ "projectDir" : os.path.join(self.dataDir, 'projects'),
}
for parm, value in default.items():
--
cgit v1.2.3
From f0ab2f53d6e5b44fa4e763a204b0c5034808551b Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 3 Jun 2017 11:08:52 -0400
Subject: section structure in avp files
---
main.py | 38 +++++++++++++++++++++++++++-----------
1 file changed, 27 insertions(+), 11 deletions(-)
diff --git a/main.py b/main.py
index df9a4f6..48d7925 100644
--- a/main.py
+++ b/main.py
@@ -473,6 +473,7 @@ class Main(QtCore.QObject):
if not filepath.endswith(".avp"):
filepath += '.avp'
with open(filepath, 'w') as f:
+ f.write('[Components]\n')
for comp in self.selectedComponents:
saveValueStore = comp.savePreset()
f.write('%s\n' % str(comp))
@@ -496,20 +497,35 @@ class Main(QtCore.QObject):
self.settings.setValue("lastProject", filepath)
self.settings.setValue("projectDir", os.path.dirname(filepath))
compNames = [mod.Component.__doc__ for mod in self.modules]
+
with open(filepath, 'r') as f:
+ validSections = ('Components')
+ section = ''
+ def parseLine(line):
+ line = line.strip()
+ newSection = ''
+ if line.startswith('[') and line.endswith(']') and line[1:-1] in validSections:
+ newSection = line[1:-1]
+ return line, newSection
+
i = 0
for line in f:
- if i == 0:
- compIndex = compNames.index(line.strip())
- self.addComponent(compIndex)
- i += 1
- elif i == 1:
- # version, not used yet
- i += 1
- elif i == 2:
- saveValueStore = eval(line.strip())
- self.selectedComponents[-1].loadPreset(saveValueStore)
- i = 0
+ line, newSection = parseLine(line)
+ if newSection:
+ section = str(newSection)
+ continue
+ if line and section == 'Components':
+ if i == 0:
+ compIndex = compNames.index(line.strip())
+ self.addComponent(compIndex)
+ i += 1
+ elif i == 1:
+ # version, not used yet
+ i += 1
+ elif i == 2:
+ saveValueStore = eval(line.strip())
+ self.selectedComponents[-1].loadPreset(saveValueStore)
+ i = 0
def showMessage(self, string, icon=QtGui.QMessageBox.Information, showCancel=False):
msg = QtGui.QMessageBox()
--
cgit v1.2.3
From c008571ae28a5b0309619695ec408febb8757dc4 Mon Sep 17 00:00:00 2001
From: DH4
Date: Sat, 3 Jun 2017 13:35:13 -0500
Subject: Changed UI default to Input Settings
---
mainwindow.ui | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mainwindow.ui b/mainwindow.ui
index 42a22a6..d350367 100644
--- a/mainwindow.ui
+++ b/mainwindow.ui
@@ -240,7 +240,7 @@
QTabWidget::Rounded
- 2
+ 0
--
cgit v1.2.3
From d417833193882acef0023a1c8e1f82b8222f89ae Mon Sep 17 00:00:00 2001
From: DH4
Date: Sat, 3 Jun 2017 14:16:25 -0500
Subject: Layout Fixes
---
components/original.ui | 171 +++++++-------
components/text.ui | 591 +++++++++++++++++++++++++------------------------
mainwindow.ui | 121 +++++++---
3 files changed, 479 insertions(+), 404 deletions(-)
diff --git a/components/original.ui b/components/original.ui
index 9cdfac2..5808653 100644
--- a/components/original.ui
+++ b/components/original.ui
@@ -6,91 +6,102 @@
0
0
- 584
- 169
+ 633
+ 178
+
+
+ 180
+ 0
+
+
Form
-
-
-
- 0
- 0
- 561
- 51
-
-
-
-
- 4
-
- -
-
-
-
- 0
- 0
-
-
-
- Visualizer Layout
-
-
-
- -
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
- Visualizer Color
-
-
-
- -
-
-
-
- 32
- 32
-
-
-
-
-
-
-
- 32
- 32
-
-
-
-
- -
-
-
-
-
- label_visLayout
- layoutWidget
+
+ -
+
+
+ 4
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Visualizer Layout
+
+
+
+ -
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+ Visualizer Color
+
+
+
+ -
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
diff --git a/components/text.ui b/components/text.ui
index 9a37215..05e7f8e 100644
--- a/components/text.ui
+++ b/components/text.ui
@@ -6,303 +6,310 @@
0
0
- 584
- 169
+ 586
+ 197
Form
-
-
-
- 0
- 0
- 581
- 131
-
-
-
-
- 4
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
- Font
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 140
- 0
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Font Size
-
-
-
- -
-
-
- 500
-
-
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
- Text Layout
-
-
-
- -
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
- Text Color
-
-
-
- -
-
-
-
- 32
- 32
-
-
-
-
-
-
-
- 32
- 32
-
-
-
-
- -
-
-
-
-
- -
-
-
- 0
-
-
-
-
-
- Title
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 300
- 0
-
-
-
- Testing New GUI
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- X
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
-
- 0
- 0
-
-
-
- 0
-
-
- 999999999
-
-
- 0
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Y
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
- 999999999
-
-
-
-
-
-
-
+
+ -
+
+
+ 4
+
+
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Font
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Font Size
+
+
+
+ -
+
+
+ 500
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Text Layout
+
+
+
+ -
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+ Text Color
+
+
+
+ -
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+ Title
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+ Testing New GUI
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ X
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 0
+ 0
+
+
+
+ 0
+
+
+ 999999999
+
+
+ 0
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Y
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+ 999999999
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
diff --git a/mainwindow.ui b/mainwindow.ui
index d350367..eda5bb6 100644
--- a/mainwindow.ui
+++ b/mainwindow.ui
@@ -6,14 +6,32 @@
0
0
- 1165
- 707
+ 1008
+ 575
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
MainWindow
+
+
+ 0
+ 0
+
+
false
@@ -26,6 +44,22 @@
-
+
-
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::MinimumExpanding
+
+
+
+ 0
+ 360
+
+
+
+
-
@@ -145,16 +179,16 @@
-
-
+
- Down
+ Up
-
-
+
- Up
+ Down
@@ -216,15 +250,15 @@
-
-
+
0
0
- 0
- 180
+ 500
+ 0
@@ -293,7 +327,7 @@
- 340
+ 0
28
@@ -727,29 +761,52 @@
-
-
-
-
- 0
- 0
-
-
-
-
- 0
- 180
-
-
-
-
- 16777215
- 180
-
-
-
- -1
+
+
+ QLayout::SetDefaultConstraint
-
+
-
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::MinimumExpanding
+
+
+
+ 500
+ 0
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 180
+
+
+
+
+ 16777215
+ 180
+
+
+
+ -1
+
+
+
+
--
cgit v1.2.3
From 2cbae481c5216f9adedb135fec077370969bac36 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 3 Jun 2017 15:24:52 -0400
Subject: autosave to help restore unsaved projects in case of a crash
---
core.py | 6 ++++
main.py | 124 +++++++++++++++++++++++++++++++++++++++-------------------------
2 files changed, 81 insertions(+), 49 deletions(-)
diff --git a/core.py b/core.py
index 96f5670..16ecb35 100644
--- a/core.py
+++ b/core.py
@@ -8,6 +8,7 @@ import tempfile
from shutil import rmtree
import atexit
import time
+from collections import OrderedDict
class Core():
@@ -150,3 +151,8 @@ class Core():
def reset(self):
self.canceled = False
+
+ @staticmethod
+ def sortedStringDict(dictionary):
+ sorted_ = OrderedDict(sorted(dictionary.items(), key=lambda t: t[0]))
+ return repr(sorted_)
diff --git a/main.py b/main.py
index 48d7925..1f3e1ec 100644
--- a/main.py
+++ b/main.py
@@ -1,7 +1,8 @@
-import sys, io, os, shutil, atexit, string, signal
+import sys, io, os, shutil, atexit, string, signal, filecmp
from os.path import expanduser
from queue import Queue
from importlib import import_module
+from collections import OrderedDict
from PyQt4 import QtCore, QtGui, uic
from PyQt4.QtCore import QSettings, QModelIndex, Qt
from PyQt4.QtGui import QDesktopServices
@@ -142,18 +143,18 @@ class Main(QtCore.QObject):
# print('main thread id: {}'.format(QtCore.QThread.currentThreadId()))
self.window = window
self.core = core.Core()
- self.currentProject = None
self.pages = []
self.selectedComponents = []
# create data directory, load/create settings
self.dataDir = QDesktopServices.storageLocation(QDesktopServices.DataLocation)
+ self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
+ self.presetDir = os.path.join(self.dataDir, 'presets')
self.settings = QSettings(os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat)
LoadDefaultSettings(self)
if not os.path.exists(self.dataDir):
os.makedirs(self.dataDir)
- presetDir = os.path.join(self.dataDir, 'presets')
- for neededDirectory in (presetDir, self.settings.value("projectDir")):
+ for neededDirectory in (self.presetDir, self.settings.value("projectDir")):
if not os.path.exists(neededDirectory):
os.mkdir(neededDirectory)
@@ -208,14 +209,35 @@ class Main(QtCore.QObject):
self.window.pushButton_saveProject.clicked.connect(self.saveCurrentProject)
self.window.pushButton_openProject.clicked.connect(self.openOpenProjectDialog)
- self.openProject(self.settings.value("lastProject"))
- self.drawPreview()
+ # show the window and load current project
window.show()
+ self.currentProject = self.settings.value("currentProject")
+ if self.currentProject and os.path.exists(self.autosavePath) \
+ and filecmp.cmp(self.autosavePath, self.currentProject):
+ # delete autosave if it's identical to the project
+ os.remove(self.autosavePath)
+
+ if self.currentProject and os.path.exists(self.autosavePath):
+ ch = self.showMessage("Restore unsaved changes in project '%s'?" % os.path.basename(self.currentProject)[:-4], True)
+ if ch:
+ os.remove(self.currentProject)
+ os.rename(self.autosavePath, self.currentProject)
+ else:
+ os.remove(self.autosavePath)
+
+ self.openProject(self.currentProject)
+ self.drawPreview()
def cleanUp(self):
self.timer.stop()
self.previewThread.quit()
self.previewThread.wait()
+ self.autosave()
+
+ def autosave(self):
+ if os.path.exists(self.autosavePath):
+ os.remove(self.autosavePath)
+ self.createProjectFile(self.autosavePath)
def openInputFileDialog(self):
inputDir = self.settings.value("inputDir", expanduser("~"))
@@ -303,9 +325,9 @@ class Main(QtCore.QObject):
self.drawPreview()
def drawPreview(self):
- #self.settings.setValue('visLayout', self.window.comboBox_visLayout.currentIndex())
self.newTask.emit(self.window.lineEdit_background.text(), self.selectedComponents)
# self.processTask.emit()
+ self.autosave()
def showPreviewImage(self, image):
self.previewWindow.changePixmap(image)
@@ -387,7 +409,7 @@ class Main(QtCore.QObject):
def updateOpenPresetComboBox(self, component):
self.window.comboBox_openPreset.clear()
self.window.comboBox_openPreset.addItem("Open Preset")
- destination = os.path.join(self.dataDir, 'presets',
+ destination = os.path.join(self.presetDir,
str(component).strip(), str(component.version()))
if not os.path.exists(destination):
os.makedirs(destination)
@@ -417,12 +439,12 @@ class Main(QtCore.QObject):
break
def createPresetFile(self, componentName, version, saveValueStore, filename):
- dirname = os.path.join(self.dataDir, 'presets', componentName, str(version))
+ dirname = os.path.join(self.presetDir, componentName, str(version))
if not os.path.exists(dirname):
os.makedirs(dirname)
filepath = os.path.join(dirname, filename)
if os.path.exists(filepath):
- ch = self.showMessage("%s already exists! Overwrite it?" % filename, QtGui.QMessageBox.Warning, True)
+ ch = self.showMessage("%s already exists! Overwrite it?" % filename, True, QtGui.QMessageBox.Warning)
if not ch:
return
# remove old copies of the preset
@@ -430,7 +452,7 @@ class Main(QtCore.QObject):
if self.window.comboBox_openPreset.itemText(i) == filename:
self.window.comboBox_openPreset.removeItem(i)
with open(filepath, 'w') as f:
- f.write(repr(saveValueStore))
+ f.write(core.Core.sortedStringDict(saveValueStore))
self.window.comboBox_openPreset.addItem(filename)
self.window.comboBox_openPreset.setCurrentIndex(self.window.comboBox_openPreset.count()-1)
@@ -443,14 +465,14 @@ class Main(QtCore.QObject):
filename = self.window.comboBox_openPreset.itemText(self.window.comboBox_openPreset.currentIndex())
componentName = str(self.selectedComponents[index]).strip()
version = self.selectedComponents[index].version()
- dirname = os.path.join(self.dataDir, 'presets', componentName, str(version))
+ dirname = os.path.join(self.presetDir, componentName, str(version))
filepath = os.path.join(dirname, filename)
if not os.path.exists(filepath):
self.window.comboBox_openPreset.removeItem(self.window.comboBox_openPreset.currentIndex())
return
with open(filepath, 'r') as f:
for line in f:
- saveValueStore = eval(line.strip())
+ saveValueStore = dict(eval(line.strip()))
break
self.selectedComponents[index].loadPreset(saveValueStore)
self.drawPreview()
@@ -478,10 +500,11 @@ class Main(QtCore.QObject):
saveValueStore = comp.savePreset()
f.write('%s\n' % str(comp))
f.write('%s\n' % str(comp.version()))
- f.write('%s\n' % repr(saveValueStore))
- self.settings.setValue("projectDir", os.path.dirname(filepath))
- self.settings.setValue("lastProject", filepath)
- self.currentProject = filepath
+ f.write('%s\n' % core.Core.sortedStringDict(saveValueStore))
+ if filepath != self.autosavePath:
+ self.settings.setValue("projectDir", os.path.dirname(filepath))
+ self.settings.setValue("currentProject", filepath)
+ self.currentProject = filepath
def openOpenProjectDialog(self):
filename = QtGui.QFileDialog.getOpenFileName(self.window,
@@ -494,40 +517,43 @@ class Main(QtCore.QObject):
return
self.clear()
self.currentProject = filepath
- self.settings.setValue("lastProject", filepath)
+ self.settings.setValue("currentProject", filepath)
self.settings.setValue("projectDir", os.path.dirname(filepath))
compNames = [mod.Component.__doc__ for mod in self.modules]
-
- with open(filepath, 'r') as f:
- validSections = ('Components')
- section = ''
- def parseLine(line):
- line = line.strip()
- newSection = ''
- if line.startswith('[') and line.endswith(']') and line[1:-1] in validSections:
- newSection = line[1:-1]
- return line, newSection
-
- i = 0
- for line in f:
- line, newSection = parseLine(line)
- if newSection:
- section = str(newSection)
- continue
- if line and section == 'Components':
- if i == 0:
- compIndex = compNames.index(line.strip())
- self.addComponent(compIndex)
- i += 1
- elif i == 1:
- # version, not used yet
- i += 1
- elif i == 2:
- saveValueStore = eval(line.strip())
- self.selectedComponents[-1].loadPreset(saveValueStore)
- i = 0
-
- def showMessage(self, string, icon=QtGui.QMessageBox.Information, showCancel=False):
+ try:
+ with open(filepath, 'r') as f:
+ validSections = ('Components')
+ section = ''
+ def parseLine(line):
+ line = line.strip()
+ newSection = ''
+ if line.startswith('[') and line.endswith(']') and line[1:-1] in validSections:
+ newSection = line[1:-1]
+ return line, newSection
+
+ i = 0
+ for line in f:
+ line, newSection = parseLine(line)
+ if newSection:
+ section = str(newSection)
+ continue
+ if line and section == 'Components':
+ if i == 0:
+ compIndex = compNames.index(line)
+ self.addComponent(compIndex)
+ i += 1
+ elif i == 1:
+ # version, not used yet
+ i += 1
+ elif i == 2:
+ saveValueStore = dict(eval(line))
+ self.selectedComponents[-1].loadPreset(saveValueStore)
+ i = 0
+ except:
+ self.clear()
+ self.showMessage("Project file '%s' is corrupted." % filepath)
+
+ def showMessage(self, string, showCancel=False, icon=QtGui.QMessageBox.Information):
msg = QtGui.QMessageBox()
msg.setIcon(icon)
msg.setText(string)
--
cgit v1.2.3
From aae04194a0cebaa156c29fa3d9b3405a51b75092 Mon Sep 17 00:00:00 2001
From: DH4
Date: Sat, 3 Jun 2017 14:38:21 -0500
Subject: Better text defaults
---
components/text.py | 9 ++-------
1 file changed, 2 insertions(+), 7 deletions(-)
diff --git a/components/text.py b/components/text.py
index bfc9701..7efde39 100644
--- a/components/text.py
+++ b/components/text.py
@@ -20,19 +20,14 @@ class Component(__base__.Component):
self.title = 'Text'
self.titleFont = None
self.alignment = 1
- self.fontSize = height / 16
+ self.fontSize = height / 13.5
self.xPosition = width / 2
- self.yPosition = height / 2
+ self.yPosition = height / 2 * 1.036
page = uic.loadUi(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'text.ui'))
page.comboBox_textAlign.addItem("Left")
page.comboBox_textAlign.addItem("Middle")
page.comboBox_textAlign.addItem("Right")
- page.comboBox_textAlign.setCurrentIndex(1)
-
- page.spinBox_fontSize.setValue(int(int(parent.settings.value("outputHeight")) / 14 ))
- page.spinBox_xTextAlign.setValue(int(int(parent.settings.value('outputWidth'))/2))
- page.spinBox_yTextAlign.setValue(int(int(parent.settings.value('outputHeight'))/2))
page.lineEdit_textColor.setText('%s,%s,%s' % self.textColor)
page.pushButton_textColor.clicked.connect(lambda: self.pickColor())
--
cgit v1.2.3
From cf197904b82d6f5769f23c15d047d22a2bd46644 Mon Sep 17 00:00:00 2001
From: DH4
Date: Sat, 3 Jun 2017 16:46:52 -0500
Subject: Add component changed to menu.
---
main.py | 17 +++++-----
mainwindow.ui | 101 +++++++++++++++++++++++++++++++++++++++++++---------------
2 files changed, 84 insertions(+), 34 deletions(-)
diff --git a/main.py b/main.py
index 1f3e1ec..49fe469 100644
--- a/main.py
+++ b/main.py
@@ -5,7 +5,7 @@ from importlib import import_module
from collections import OrderedDict
from PyQt4 import QtCore, QtGui, uic
from PyQt4.QtCore import QSettings, QModelIndex, Qt
-from PyQt4.QtGui import QDesktopServices
+from PyQt4.QtGui import QDesktopServices, QMenu
import preview_thread, core, video_thread
@@ -183,13 +183,14 @@ class Main(QtCore.QObject):
window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
self.modules = self.findComponents()
- for component in self.modules:
- window.comboBox_componentSelection.addItem(component.Component.__doc__)
- window.listWidget_componentList.clicked.connect(lambda _: self.changeComponentWidget())
+ self.compMenu = QMenu()
+ for i, comp in enumerate(self.modules):
+ action = self.compMenu.addAction(comp.Component.__doc__)
+ action.triggered[()].connect( lambda item=i: self.addComponent(item))
- self.window.pushButton_addComponent.clicked.connect( \
- lambda _: self.addComponent(self.window.comboBox_componentSelection.currentIndex())
- )
+ self.window.pushButton_addComponent.setMenu(self.compMenu)
+ window.listWidget_componentList.clicked.connect(lambda _: self.changeComponentWidget())
+
self.window.pushButton_removeComponent.clicked.connect(lambda _: self.removeComponent())
currentRes = str(self.settings.value('outputWidth'))+'x'+str(self.settings.value('outputHeight'))
@@ -408,7 +409,7 @@ class Main(QtCore.QObject):
def updateOpenPresetComboBox(self, component):
self.window.comboBox_openPreset.clear()
- self.window.comboBox_openPreset.addItem("Open Preset")
+ self.window.comboBox_openPreset.addItem("Component Presets")
destination = os.path.join(self.presetDir,
str(component).strip(), str(component.version()))
if not os.path.exists(destination):
diff --git a/mainwindow.ui b/mainwindow.ui
index eda5bb6..5e10028 100644
--- a/mainwindow.ui
+++ b/mainwindow.ui
@@ -141,27 +141,11 @@
20
- 20
+ 15
- -
-
-
-
- 0
- 0
-
-
-
-
- 280
- 0
-
-
-
-
-
-
@@ -197,17 +181,63 @@
-
-
-
-
- 0
- 0
-
+
+
+ 4
-
+
+ 2
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Sunken
+
+
+ 1
+
+
+ false
+
+
+ false
+
+
+ false
+
+
+ QAbstractItemView::NoDragDrop
+
+
+
+
-
+
+ 2
+
-
@@ -216,17 +246,36 @@
0
+
+
+ 180
+ 0
+
+
-
- Open Preset
+ Component Presets
-
+
+
+ 0
+ 0
+
+
+
+ Save
+
+
+
+ -
+
- Save Preset
+ Remove
--
cgit v1.2.3
From a3557cbc4f192b520f7fccd849ebf4f70937cfee Mon Sep 17 00:00:00 2001
From: DH4
Date: Sat, 3 Jun 2017 19:10:27 -0500
Subject: UI Updates, encode lockout, added encoder-options.json. FIXME: Add
encoder options to the UI.
---
encoder-options.json | 191 ++++++++++++++++++++++++++++
main.py | 44 +++++++
mainwindow.ui | 350 ++++++++++++++++++++++++++++-----------------------
3 files changed, 427 insertions(+), 158 deletions(-)
create mode 100644 encoder-options.json
diff --git a/encoder-options.json b/encoder-options.json
new file mode 100644
index 0000000..699ead4
--- /dev/null
+++ b/encoder-options.json
@@ -0,0 +1,191 @@
+{
+ "containers":[
+ {
+ "name": "MP4",
+ "container": "mp4",
+ "default-vcodec": "H264",
+ "default-acodec": "AAC",
+ "video-codecs": [
+ {
+ "name": "H264",
+ "encoders": ["libx264"]
+ },
+ {
+ "name": "H264 (nvenc)",
+ "encoders": ["nvenc_264"]
+ },
+ {
+ "name": "MPEG4",
+ "encoders": ["mpeg4"]
+ }
+ ],
+ "audio-codecs": [
+ {
+ "name": "AAC",
+ "encoders": ["libfdk_aac","aac"]
+ },
+ {
+ "name": "AC3",
+ "encoders": ["ac3"]
+ },
+ {
+ "name": "MP3",
+ "encoders": ["libmp3lame"]
+ }
+ ]
+ },
+ {
+ "name": "MOV",
+ "container": "mov",
+ "default-vcodec": "H264",
+ "default-acodec": "AAC",
+ "video-codecs": [
+ {
+ "name": "H264",
+ "encoders": ["libx264"]
+ },
+ {
+ "name": "H264 (nvenc)",
+ "encoders": ["nvenc_264"]
+ },
+ {
+ "name": "MPEG4",
+ "encoders": ["mpeg4"]
+ },
+ {
+ "name": "XVID",
+ "encoders": ["libxvid"]
+ }
+ ],
+ "audio-codecs": [
+ {
+ "name": "AAC",
+ "encoders": ["libfdk_aac","aac"]
+ },
+ {
+ "name": "AC3",
+ "encoders": ["ac3"]
+ },
+ {
+ "name": "MP3",
+ "encoders": ["libmp3lame"]
+ },
+ {
+ "name": "PCM s16 LE",
+ "encoders": ["pcm_s16le"]
+ }
+ ]
+ },
+ {
+ "name": "AVI",
+ "container": "avi",
+ "default-vcodec": "H264",
+ "default-acodec": "AAC",
+ "video-codecs": [
+ {
+ "name": "H264",
+ "encoders": ["libx264"]
+ },
+ {
+ "name": "H264 (nvenc)",
+ "encoders": ["nvenc_264"]
+ },
+ {
+ "name": "MPEG4",
+ "encoders": ["mpeg4"]
+ },
+ {
+ "name": "MPEG2",
+ "encoders": ["mp2video"]
+ },
+ {
+ "name": "DV",
+ "encoders": ["dvvideo"]
+ },
+ {
+ "name": "WMV",
+ "encoders": ["wmv2"]
+ }
+ ],
+ "audio-codecs": [
+ {
+ "name": "AAC",
+ "encoders": ["libfdk_aac","aac"]
+ },
+ {
+ "name": "AC3",
+ "encoders": ["ac3"]
+ },
+ {
+ "name": "WMA",
+ "encoders": ["wmav2"]
+ },
+ {
+ "name": "MP3",
+ "encoders": ["libmp3lame"]
+ },
+ {
+ "name": "PCM s16 LE",
+ "encoders": ["pcm_s16le"]
+ }
+ ]
+ },
+ {
+ "name": "WEBM",
+ "container": "webm",
+ "default-vcodec": "VP9",
+ "default-acodec": "Vorbis",
+ "video-codecs": [
+ {
+ "name": "VP9",
+ "encoders": ["libvpx-vp9"]
+ },
+ {
+ "name": "VP8",
+ "encoders": ["libvpx"]
+ }
+ ],
+ "audio-codecs": [
+ {
+ "name": "Vorbis",
+ "encoders": ["vorbis"]
+ }
+ ]
+ },
+ {
+ "name": "FLV",
+ "container": "flv",
+ "default-vcodec": "FLV",
+ "default-acodec": "Vorbis",
+ "video-codecs": [
+ {
+ "name": "Sorenson (flv)",
+ "encoders": ["flv"]
+ },
+ {
+ "name": "H264",
+ "encoders": ["libx264"]
+ },
+ {
+ "name": "MPEG4",
+ "encoders": ["mpeg4"]
+ }
+ ],
+ "audio-codecs": [
+ {
+ "name": "MP3",
+ "encoders": ["libmp3lame"]
+ },
+ {
+ "name": "Vorbis",
+ "encoders": ["vorbis"]
+ },
+ {
+ "name": "PCM s16 LE",
+ "encoders": ["pcm_s16le"]
+ }
+ ]
+ }
+ ]
+
+}
\ No newline at end of file
diff --git a/main.py b/main.py
index 49fe469..c5080f3 100644
--- a/main.py
+++ b/main.py
@@ -305,9 +305,53 @@ class Main(QtCore.QObject):
if status:
self.window.pushButton_createVideo.setEnabled(False)
self.window.pushButton_Cancel.setEnabled(True)
+ self.window.comboBox_resolution.setEnabled(False)
+ self.window.stackedWidget.setEnabled(False)
+ self.window.tab_encoderSettings.setEnabled(False)
+ self.window.label_audioFile.setEnabled(False)
+ self.window.toolButton_selectAudioFile.setEnabled(False)
+ self.window.label_outputFile.setEnabled(False)
+ self.window.toolButton_selectOutputFile.setEnabled(False)
+ self.window.lineEdit_audioFile.setEnabled(False)
+ self.window.lineEdit_outputFile.setEnabled(False)
+ self.window.pushButton_addComponent.setEnabled(False)
+ self.window.pushButton_removeComponent.setEnabled(False)
+ self.window.pushButton_listMoveDown.setEnabled(False)
+ self.window.pushButton_listMoveUp.setEnabled(False)
+ self.window.comboBox_openPreset.setEnabled(False)
+ self.window.pushButton_removePreset.setEnabled(False)
+ self.window.pushButton_savePreset.setEnabled(False)
+ self.window.pushButton_openProject.setEnabled(False)
+ self.window.listWidget_componentList.setEnabled(False)
+
+ self.window.label_background.setEnabled(False)
+ self.window.lineEdit_background.setEnabled(False)
+ self.window.toolButton_selectBackground.setEnabled(False)
else:
self.window.pushButton_createVideo.setEnabled(True)
self.window.pushButton_Cancel.setEnabled(False)
+ self.window.comboBox_resolution.setEnabled(True)
+ self.window.stackedWidget.setEnabled(True)
+ self.window.tab_encoderSettings.setEnabled(True)
+ self.window.label_audioFile.setEnabled(True)
+ self.window.toolButton_selectAudioFile.setEnabled(True)
+ self.window.lineEdit_audioFile.setEnabled(True)
+ self.window.label_outputFile.setEnabled(True)
+ self.window.toolButton_selectOutputFile.setEnabled(True)
+ self.window.lineEdit_outputFile.setEnabled(True)
+ self.window.pushButton_addComponent.setEnabled(True)
+ self.window.pushButton_removeComponent.setEnabled(True)
+ self.window.pushButton_listMoveDown.setEnabled(True)
+ self.window.pushButton_listMoveUp.setEnabled(True)
+ self.window.comboBox_openPreset.setEnabled(True)
+ self.window.pushButton_removePreset.setEnabled(True)
+ self.window.pushButton_savePreset.setEnabled(True)
+ self.window.pushButton_openProject.setEnabled(True)
+ self.window.listWidget_componentList.setEnabled(True)
+
+ self.window.label_background.setEnabled(True)
+ self.window.lineEdit_background.setEnabled(True)
+ self.window.toolButton_selectBackground.setEnabled(True)
diff --git a/mainwindow.ui b/mainwindow.ui
index 5e10028..f9e8f5e 100644
--- a/mainwindow.ui
+++ b/mainwindow.ui
@@ -325,11 +325,11 @@
0
-
+
- Input Settings
+ Export Video
-
+
10
@@ -348,10 +348,16 @@
- 100
+ 85
0
+
+
+ 80
+ 16777215
+
+
80
@@ -427,7 +433,7 @@
- 100
+ 85
0
@@ -482,9 +488,142 @@
+ -
+
+
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 85
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+ Output File
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 28
+
+
+
+
+ 16777215
+ 28
+
+
+
+
+ -
+
+
+
+ 0
+ 28
+
+
+
+
+ 16777215
+ 28
+
+
+
+ ...
+
+
+
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ 24
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Minimum
+
+
+
+ 10
+ 20
+
+
+
+
+ -
+
+
+ Create Video
+
+
+
+ -
+
+
+ false
+
+
+ Cancel
+
+
+
+
+
+
+
+
-
+
Encoder Settings
@@ -504,17 +643,40 @@
- 98
+ 85
0
- Video Format
+ Container
+
+
+
+ -
+
+
+
+ 150
+ 0
+
-
-
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Minimum
+
+
+
+ 5
+ 5
+
+
+
-
@@ -525,12 +687,25 @@
- Video Preset
+ Resolution
-
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
@@ -546,7 +721,7 @@
- 98
+ 85
0
@@ -590,12 +765,12 @@
- Resolution
+ Video Bitrate
-
-
+
@@ -611,7 +786,7 @@
- 98
+ 85
0
@@ -655,158 +830,17 @@
- Bitrate
+ Audio Bitrate
-
-
+
-
-
- Export Video
-
-
-
- 10
-
- -
-
-
-
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 100
- 0
-
-
-
-
- 0
- 0
-
-
-
- Output File
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 28
-
-
-
-
- 16777215
- 28
-
-
-
-
- -
-
-
-
- 0
- 28
-
-
-
-
- 16777215
- 28
-
-
-
- ...
-
-
-
-
-
-
-
- -
-
-
- 0
-
-
-
-
-
-
- 0
- 0
-
-
-
- 24
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Minimum
-
-
-
- 10
- 20
-
-
-
-
- -
-
-
- Create video
-
-
-
- -
-
-
- true
-
-
- Cancel
-
-
-
-
-
-
-
-
-
--
cgit v1.2.3
From e6d119769f825a523bc96c3bfe1c87015be54c9f Mon Sep 17 00:00:00 2001
From: DH4
Date: Sat, 3 Jun 2017 19:29:25 -0500
Subject: Render order reversed to match component list.
---
preview_thread.py | 2 +-
video_thread.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/preview_thread.py b/preview_thread.py
index b20e9a1..63d1ac5 100644
--- a/preview_thread.py
+++ b/preview_thread.py
@@ -56,7 +56,7 @@ class Worker(QtCore.QObject):
frame.paste(im)
components = nextPreviewInformation["components"]
- for component in components:
+ for component in reversed(components):
newFrame = Image.alpha_composite(frame,component.previewRender(self))
frame = Image.alpha_composite(frame,newFrame)
diff --git a/video_thread.py b/video_thread.py
index 0d42406..c97cc24 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -43,7 +43,7 @@ class Worker(QtCore.QObject):
else:
frame = self.getBackgroundAtIndex(i[1])
- for compNo, comp in enumerate(self.components):
+ for compNo, comp in reversed(list(enumerate(self.components))):
if compNo in self.staticComponents and self.staticComponents[compNo] != None:
frame = Image.alpha_composite(frame, self.staticComponents[compNo])
else:
--
cgit v1.2.3
From 825b7af6e30deaf85646ce93507d8cb5a0b426ae Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 3 Jun 2017 20:39:32 -0400
Subject: start of background replacement components
---
components/__base__.py | 6 +
components/color.py | 82 +++++++++++++
components/color.ui | 306 +++++++++++++++++++++++++++++++++++++++++++++++++
components/image.py | 42 +++++++
components/image.ui | 200 ++++++++++++++++++++++++++++++++
components/text.py | 3 -
components/video.py | 42 +++++++
components/video.ui | 227 ++++++++++++++++++++++++++++++++++++
8 files changed, 905 insertions(+), 3 deletions(-)
create mode 100644 components/color.py
create mode 100644 components/color.ui
create mode 100644 components/image.py
create mode 100644 components/image.ui
create mode 100644 components/video.py
create mode 100644 components/video.ui
diff --git a/components/__base__.py b/components/__base__.py
index 45512ce..45c148d 100644
--- a/components/__base__.py
+++ b/components/__base__.py
@@ -66,4 +66,10 @@ class Component:
def savePreset(self):
return {}
+
+ def cancel(self):
+ self.canceled = True
+
+ def reset(self):
+ self.canceled = False
'''
diff --git a/components/color.py b/components/color.py
new file mode 100644
index 0000000..d86470c
--- /dev/null
+++ b/components/color.py
@@ -0,0 +1,82 @@
+from PIL import Image, ImageDraw
+from PyQt4 import uic, QtGui, QtCore
+from PyQt4.QtGui import QColor
+import os
+from . import __base__
+
+class Component(__base__.Component):
+ '''Color'''
+ def widget(self, parent):
+ self.parent = parent
+ page = uic.loadUi(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'color.ui'))
+
+ self.color1 = (0,0,0)
+ self.color2 = (133,133,133)
+ self.x = 0
+ self.y = 0
+
+ page.lineEdit_color1.setText('%s,%s,%s' % self.color1)
+ page.lineEdit_color2.setText('%s,%s,%s' % self.color2)
+ page.pushButton_color1.clicked.connect(lambda: self.pickColor(1))
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" % QColor(*self.color1).name()
+ page.pushButton_color1.setStyleSheet(btnStyle)
+ page.pushButton_color2.clicked.connect(lambda: self.pickColor(2))
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" % QColor(*self.color2).name()
+ page.pushButton_color2.setStyleSheet(btnStyle)
+ # disable color #2 until non-default 'fill' option gets changed
+ page.lineEdit_color2.setDisabled(True)
+ page.pushButton_color2.setDisabled(True)
+ page.spinBox_x.setValue(self.x)
+ page.spinBox_x.setValue(self.y)
+
+ page.lineEdit_color1.textChanged.connect(self.update)
+ page.lineEdit_color2.textChanged.connect(self.update)
+ page.spinBox_x.valueChanged.connect(self.update)
+ page.spinBox_y.valueChanged.connect(self.update)
+ self.page = page
+ return page
+
+ def update(self):
+ self.color1 = self.RGBFromString(self.page.lineEdit_color1.text())
+ self.color2 = self.RGBFromString(self.page.lineEdit_color2.text())
+ self.x = self.page.spinBox_x.value()
+ self.y = self.page.spinBox_y.value()
+ self.parent.drawPreview()
+
+ def previewRender(self, previewWorker):
+ width = int(previewWorker.core.settings.value('outputWidth'))
+ height = int(previewWorker.core.settings.value('outputHeight'))
+ return self.drawFrame(width, height)
+
+ def frameRender(self, moduleNo, frameNo):
+ width = int(self.worker.core.settings.value('outputWidth'))
+ height = int(self.worker.core.settings.value('outputHeight'))
+ return self.drawFrame(width, height)
+
+ def drawFrame(self, width, height):
+ r,g,b = self.color1
+ return Image.new("RGBA", (width, height), (r, g, b, 255))
+
+ def loadPreset(self, presetDict):
+ # update widgets using a preset dict
+ pass
+
+ def savePreset(self):
+ return {}
+
+ def pickColor(self, num):
+ RGBstring, btnStyle = super().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 cancel(self):
+ self.canceled = True
+
+ def reset(self):
+ self.canceled = False
diff --git a/components/color.ui b/components/color.ui
new file mode 100644
index 0000000..fd427e6
--- /dev/null
+++ b/components/color.ui
@@ -0,0 +1,306 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 586
+ 197
+
+
+
+ Form
+
+
+
-
+
+
+ 4
+
+
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 31
+ 0
+
+
+
+ Color #1
+
+
+
+ -
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 1
+ 0
+
+
+
+ 12
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 31
+ 0
+
+
+
+ Color #2
+
+
+
+ -
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 1
+ 0
+
+
+
+ 12
+
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Fill
+
+
+
+ -
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ X
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 0
+ 0
+
+
+
+ 0
+
+
+ 999999999
+
+
+ 0
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Y
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+ 999999999
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
diff --git a/components/image.py b/components/image.py
new file mode 100644
index 0000000..3176d7c
--- /dev/null
+++ b/components/image.py
@@ -0,0 +1,42 @@
+from PIL import Image, ImageDraw
+from PyQt4 import uic, QtGui, QtCore
+import os
+from . import __base__
+
+class Component(__base__.Component):
+ '''Image'''
+ def widget(self, parent):
+ self.parent = parent
+ page = uic.loadUi(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'image.ui'))
+ self.page = page
+ return page
+
+ def update(self):
+ # read widget values
+ self.parent.drawPreview()
+
+ def previewRender(self, previewWorker):
+ width = int(previewWorker.core.settings.value('outputWidth'))
+ height = int(previewWorker.core.settings.value('outputHeight'))
+ return self.drawFrame(width, height)
+
+ def frameRender(self, moduleNo, frameNo):
+ width = int(self.worker.core.settings.value('outputWidth'))
+ height = int(self.worker.core.settings.value('outputHeight'))
+ return self.drawFrame(width, height)
+
+ def drawFrame(self, width, height):
+ return Image.new("RGBA", (width, height), (0,0,0,255))
+
+ def loadPreset(self, presetDict):
+ # update widgets using a preset dict
+ pass
+
+ def savePreset(self):
+ return {}
+
+ def cancel(self):
+ self.canceled = True
+
+ def reset(self):
+ self.canceled = False
diff --git a/components/image.ui b/components/image.ui
new file mode 100644
index 0000000..6a24370
--- /dev/null
+++ b/components/image.ui
@@ -0,0 +1,200 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 586
+ 197
+
+
+
+ Form
+
+
+ -
+
+
+ 4
+
+
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 31
+ 0
+
+
+
+ Image
+
+
+
+ -
+
+
+
+ 1
+ 0
+
+
+
+ 12
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 1
+ 0
+
+
+
+
+ 32
+ 32
+
+
+
+ ...
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ X
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+ 999999999
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Y
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 0
+ 0
+
+
+
+ 0
+
+
+ 999999999
+
+
+ 0
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
diff --git a/components/text.py b/components/text.py
index 7efde39..d2e009d 100644
--- a/components/text.py
+++ b/components/text.py
@@ -18,7 +18,6 @@ class Component(__base__.Component):
self.parent = parent
self.textColor = (255,255,255)
self.title = 'Text'
- self.titleFont = None
self.alignment = 1
self.fontSize = height / 13.5
self.xPosition = width / 2
@@ -35,8 +34,6 @@ class Component(__base__.Component):
page.pushButton_textColor.setStyleSheet(btnStyle)
page.lineEdit_title.setText(self.title)
- #if self.titleFont:
- # page.fontComboBox_titleFont.setCurrentFont(QFont(self.titleFont))
page.comboBox_textAlign.setCurrentIndex(int(self.alignment))
page.spinBox_fontSize.setValue(int(self.fontSize))
page.spinBox_xTextAlign.setValue(int(self.xPosition))
diff --git a/components/video.py b/components/video.py
new file mode 100644
index 0000000..1365f34
--- /dev/null
+++ b/components/video.py
@@ -0,0 +1,42 @@
+from PIL import Image, ImageDraw
+from PyQt4 import uic, QtGui, QtCore
+import os
+from . import __base__
+
+class Component(__base__.Component):
+ '''Video'''
+ def widget(self, parent):
+ self.parent = parent
+ page = uic.loadUi(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'video.ui'))
+ self.page = page
+ return page
+
+ def update(self):
+ # read widget values
+ self.parent.drawPreview()
+
+ def previewRender(self, previewWorker):
+ width = int(previewWorker.core.settings.value('outputWidth'))
+ height = int(previewWorker.core.settings.value('outputHeight'))
+ return self.drawFrame(width, height)
+
+ def frameRender(self, moduleNo, frameNo):
+ width = int(self.worker.core.settings.value('outputWidth'))
+ height = int(self.worker.core.settings.value('outputHeight'))
+ return self.drawFrame(width, height)
+
+ def drawFrame(self, width, height):
+ return Image.new("RGBA", (width, height), (0,0,0,255))
+
+ def loadPreset(self, presetDict):
+ # update widgets using a preset dict
+ pass
+
+ def savePreset(self):
+ return {}
+
+ def cancel(self):
+ self.canceled = True
+
+ def reset(self):
+ self.canceled = False
diff --git a/components/video.ui b/components/video.ui
new file mode 100644
index 0000000..73697f3
--- /dev/null
+++ b/components/video.ui
@@ -0,0 +1,227 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 586
+ 197
+
+
+
+ Form
+
+
+ -
+
+
+ 4
+
+
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 31
+ 0
+
+
+
+ Video
+
+
+
+ -
+
+
+
+ 1
+ 0
+
+
+
+ 12
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 1
+ 0
+
+
+
+
+ 32
+ 32
+
+
+
+ ...
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ X
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+ 999999999
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Y
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 0
+ 0
+
+
+
+ 0
+
+
+ 999999999
+
+
+ 0
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Loop
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
--
cgit v1.2.3
From 5b78a26d8069e48ad5ba88f457b563620be72173 Mon Sep 17 00:00:00 2001
From: DH4
Date: Sat, 3 Jun 2017 19:54:53 -0500
Subject: Add component inserts on top.
---
main.py | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/main.py b/main.py
index c5080f3..2aa7fa9 100644
--- a/main.py
+++ b/main.py
@@ -186,7 +186,7 @@ class Main(QtCore.QObject):
self.compMenu = QMenu()
for i, comp in enumerate(self.modules):
action = self.compMenu.addAction(comp.Component.__doc__)
- action.triggered[()].connect( lambda item=i: self.addComponent(item))
+ action.triggered[()].connect( lambda item=i: self.insertComponent(item))
self.window.pushButton_addComponent.setMenu(self.compMenu)
window.listWidget_componentList.clicked.connect(lambda _: self.changeComponentWidget())
@@ -400,6 +400,16 @@ class Main(QtCore.QObject):
self.selectedComponents[-1].update()
self.updateOpenPresetComboBox(self.selectedComponents[-1])
+ def insertComponent(self, moduleIndex):
+ self.selectedComponents.insert(0, self.modules[moduleIndex].Component())
+ self.window.listWidget_componentList.insertItem(0, self.selectedComponents[0].__doc__)
+ self.pages.insert(0, self.selectedComponents[0].widget(self))
+ self.window.listWidget_componentList.setCurrentRow(0)
+ self.window.stackedWidget.insertWidget(0, self.pages[0])
+ self.window.stackedWidget.setCurrentIndex(0)
+ self.selectedComponents[0].update()
+ self.updateOpenPresetComboBox(self.selectedComponents[0])
+
def removeComponent(self):
for selected in self.window.listWidget_componentList.selectedItems():
index = self.window.listWidget_componentList.row(selected)
--
cgit v1.2.3
From cfb8e17b6362719ca736997a23a939bec4975e70 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 3 Jun 2017 22:58:40 -0400
Subject: basic image component
---
components/color.py | 15 +++++++++++----
components/image.py | 36 ++++++++++++++++++++++++++++++------
components/image.ui | 11 ++++-------
components/video.py | 38 ++++++++++++++++++++++++++++++++------
components/video.ui | 13 +++++--------
5 files changed, 82 insertions(+), 31 deletions(-)
diff --git a/components/color.py b/components/color.py
index d86470c..ae818e2 100644
--- a/components/color.py
+++ b/components/color.py
@@ -57,12 +57,19 @@ class Component(__base__.Component):
r,g,b = self.color1
return Image.new("RGBA", (width, height), (r, g, b, 255))
- def loadPreset(self, presetDict):
- # update widgets using a preset dict
- pass
+ def loadPreset(self, pr):
+ self.page.lineEdit_color1.setText('%s,%s,%s' % pr['color1'])
+ self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2'])
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" % QColor(*pr['color1']).name()
+ self.page.pushButton_color1.setStyleSheet(btnStyle)
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" % QColor(*pr['color2']).name()
+ self.page.pushButton_color2.setStyleSheet(btnStyle)
def savePreset(self):
- return {}
+ return {
+ 'color1' : self.color1,
+ 'color2' : self.color2,
+ }
def pickColor(self, num):
RGBstring, btnStyle = super().pickColor()
diff --git a/components/image.py b/components/image.py
index 3176d7c..021bb9e 100644
--- a/components/image.py
+++ b/components/image.py
@@ -7,12 +7,20 @@ class Component(__base__.Component):
'''Image'''
def widget(self, parent):
self.parent = parent
+ self.settings = parent.settings
page = uic.loadUi(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'image.ui'))
+ self.imagePath = ''
+ self.x = 0
+ self.y = 0
+
+ page.lineEdit_image.textChanged.connect(self.update)
+ page.pushButton_image.clicked.connect(self.pickImage)
+
self.page = page
return page
def update(self):
- # read widget values
+ self.imagePath = self.page.lineEdit_image.text()
self.parent.drawPreview()
def previewRender(self, previewWorker):
@@ -26,17 +34,33 @@ class Component(__base__.Component):
return self.drawFrame(width, height)
def drawFrame(self, width, height):
- return Image.new("RGBA", (width, height), (0,0,0,255))
+ frame = Image.new("RGBA", (width, height), (0,0,0,0))
+ if self.imagePath and os.path.exists(self.imagePath):
+ image = Image.open(self.imagePath)
+ if image.size != (width, height):
+ image = image.resize((width, height), Image.ANTIALIAS)
+ frame.paste(image)
+ return frame
- def loadPreset(self, presetDict):
- # update widgets using a preset dict
- pass
+ def loadPreset(self, pr):
+ self.page.lineEdit_image.setText(pr['image'])
def savePreset(self):
- return {}
+ return {
+ 'image' : self.imagePath,
+ }
def cancel(self):
self.canceled = True
def reset(self):
self.canceled = False
+
+ def pickImage(self):
+ imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
+ filename = QtGui.QFileDialog.getOpenFileName(self.page,
+ "Choose Image", imgDir, "Image Files (*.jpg *.png)")
+ if filename:
+ self.settings.setValue("backgroundDir", os.path.dirname(filename))
+ self.page.lineEdit_image.setText(filename)
+ self.update()
diff --git a/components/image.ui b/components/image.ui
index 6a24370..3cd5b1b 100644
--- a/components/image.ui
+++ b/components/image.ui
@@ -41,20 +41,17 @@
-
-
+
1
0
-
- 12
-
-
-
+
0
@@ -114,7 +111,7 @@
-
-
+
0
@@ -146,7 +143,7 @@
-
-
+
0
diff --git a/components/video.py b/components/video.py
index 1365f34..561e40b 100644
--- a/components/video.py
+++ b/components/video.py
@@ -7,12 +7,20 @@ class Component(__base__.Component):
'''Video'''
def widget(self, parent):
self.parent = parent
+ self.settings = parent.settings
page = uic.loadUi(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'video.ui'))
+ self.videoPath = ''
+ self.x = 0
+ self.y = 0
+
+ page.lineEdit_video.textChanged.connect(self.update)
+ page.pushButton_video.clicked.connect(self.pickVideo)
+
self.page = page
return page
def update(self):
- # read widget values
+ self.videoPath = self.page.lineEdit_video.text()
self.parent.drawPreview()
def previewRender(self, previewWorker):
@@ -26,17 +34,35 @@ class Component(__base__.Component):
return self.drawFrame(width, height)
def drawFrame(self, width, height):
- return Image.new("RGBA", (width, height), (0,0,0,255))
+ frame = Image.new("RGBA", (width, height), (0,0,0,0))
+ '''
+ if self.imagePath and os.path.exists(self.imagePath):
+ image = Image.open(self.imagePath)
+ if image.size != (width, height):
+ image = image.resize((width, height), Image.ANTIALIAS)
+ frame.paste(image)
+ '''
+ return frame
- def loadPreset(self, presetDict):
- # update widgets using a preset dict
- pass
+ def loadPreset(self, pr):
+ self.page.lineEdit_video.setText(pr['video'])
def savePreset(self):
- return {}
+ return {
+ 'video' : self.videoPath,
+ }
def cancel(self):
self.canceled = True
def reset(self):
self.canceled = False
+
+ def pickVideo(self):
+ imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
+ filename = QtGui.QFileDialog.getOpenFileName(self.page,
+ "Choose Video", imgDir, "Video Files (*.mp4)")
+ if filename:
+ self.settings.setValue("backgroundDir", os.path.dirname(filename))
+ self.page.lineEdit_video.setText(filename)
+ self.update()
diff --git a/components/video.ui b/components/video.ui
index 73697f3..6a01368 100644
--- a/components/video.ui
+++ b/components/video.ui
@@ -41,20 +41,17 @@
-
-
+
1
0
-
- 12
-
-
-
+
0
@@ -114,7 +111,7 @@
-
-
+
0
@@ -146,7 +143,7 @@
-
-
+
0
@@ -183,7 +180,7 @@
-
-
-
+
Loop
--
cgit v1.2.3
From 443c65455a1cae8ccaea0f0af7cdda3919c709f8 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 4 Jun 2017 00:19:10 -0400
Subject: half-finished video component
will finish tomorrow
---
components/video.py | 67 +++++++++++++++++++++++++++++++++++++++--------------
1 file changed, 49 insertions(+), 18 deletions(-)
diff --git a/components/video.py b/components/video.py
index 561e40b..3d6ba7a 100644
--- a/components/video.py
+++ b/components/video.py
@@ -1,6 +1,6 @@
from PIL import Image, ImageDraw
from PyQt4 import uic, QtGui, QtCore
-import os
+import os, subprocess
from . import __base__
class Component(__base__.Component):
@@ -24,25 +24,27 @@ class Component(__base__.Component):
self.parent.drawPreview()
def previewRender(self, previewWorker):
- width = int(previewWorker.core.settings.value('outputWidth'))
- height = int(previewWorker.core.settings.value('outputHeight'))
- return self.drawFrame(width, height)
+ self.width = int(previewWorker.core.settings.value('outputWidth'))
+ self.height = int(previewWorker.core.settings.value('outputHeight'))
+ frames = self.getVideoFrames(True)
+ if frames:
+ im = Image.open(frames[0])
+ im = self.resize(im)
+ return im
+ else:
+ return Image.new("RGBA", (self.width, self.height), (0, 0, 0, 0))
+
+ def preFrameRender(self, **kwargs):
+ super().__init__(**kwargs)
+ self.width = int(self.worker.core.settings.value('outputWidth'))
+ self.height = int(self.worker.core.settings.value('outputHeight'))
+ self.frames = self.getVideoFrames()
def frameRender(self, moduleNo, frameNo):
- width = int(self.worker.core.settings.value('outputWidth'))
- height = int(self.worker.core.settings.value('outputHeight'))
- return self.drawFrame(width, height)
-
- def drawFrame(self, width, height):
- frame = Image.new("RGBA", (width, height), (0,0,0,0))
- '''
- if self.imagePath and os.path.exists(self.imagePath):
- image = Image.open(self.imagePath)
- if image.size != (width, height):
- image = image.resize((width, height), Image.ANTIALIAS)
- frame.paste(image)
- '''
- return frame
+ i = frameNo if frameNo < len(self.frames)-1 else len(self.frames)-1
+ im = Image.open(self.frames[i])
+ im = self.resize(im)
+ return im
def loadPreset(self, pr):
self.page.lineEdit_video.setText(pr['video'])
@@ -66,3 +68,32 @@ class Component(__base__.Component):
self.settings.setValue("backgroundDir", os.path.dirname(filename))
self.page.lineEdit_video.setText(filename)
self.update()
+
+ def getVideoFrames(self, firstOnly=False):
+ # recreate the temporary directory so it is empty
+ # FIXME: don't dump too many frames at once
+ if not self.videoPath:
+ return
+ self.parent.core.deleteTempDir()
+ os.mkdir(self.parent.core.tempDir)
+ if firstOnly:
+ filename = 'preview%s.jpg' % os.path.basename(self.videoPath).split('.', 1)[0]
+ options = '-ss 10 -vframes 1'
+ else:
+ filename = '$frame%05d.jpg'
+ options = ''
+ subprocess.call( \
+ '%s -i "%s" -y %s "%s"' % ( \
+ self.parent.core.FFMPEG_BIN,
+ self.videoPath,
+ options,
+ os.path.join(self.parent.core.tempDir, filename)
+ ),
+ shell=True
+ )
+ return sorted([os.path.join(self.parent.core.tempDir, f) for f in os.listdir(self.parent.core.tempDir)])
+
+ def resize(self, im):
+ if im.size != (self.width, self.height):
+ im = im.resize((self.width, self.height), Image.ANTIALIAS)
+ return im
--
cgit v1.2.3
From 39e66ffa2d07b87b57ed90b369ab26aedf0a69e8 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 4 Jun 2017 13:00:36 -0400
Subject: video component almost working, rm hardcoded backgrounds
---
background.jpg | Bin 14782 -> 0 bytes
background.png | Bin 0 -> 3711 bytes
components/__base__.py | 7 +++++
components/color.py | 16 +++++------
components/image.py | 14 +++++-----
components/original.py | 20 ++++----------
components/text.py | 8 +-----
components/video.py | 68 +++++++++++++++++++++++++++++-----------------
core.py | 61 +++++------------------------------------
main.py | 50 +++++++++++-----------------------
mainwindow.ui | 72 ++-----------------------------------------------
preview_thread.py | 20 +++-----------
video_thread.py | 46 +++++++++----------------------
13 files changed, 110 insertions(+), 272 deletions(-)
delete mode 100644 background.jpg
create mode 100644 background.png
diff --git a/background.jpg b/background.jpg
deleted file mode 100644
index f746432..0000000
Binary files a/background.jpg and /dev/null differ
diff --git a/background.png b/background.png
new file mode 100644
index 0000000..7a33158
Binary files /dev/null and b/background.png differ
diff --git a/components/__base__.py b/components/__base__.py
index 45c148d..f564aad 100644
--- a/components/__base__.py
+++ b/components/__base__.py
@@ -7,6 +7,13 @@ class Component:
def version(self):
# change this number to identify new versions of a component
return 1
+
+ def cancel(self):
+ # make sure your component responds to these variables in frameRender()
+ self.canceled = True
+
+ def reset(self):
+ self.canceled = False
def preFrameRender(self, **kwargs):
for var, value in kwargs.items():
diff --git a/components/color.py b/components/color.py
index ae818e2..c2a49e2 100644
--- a/components/color.py
+++ b/components/color.py
@@ -42,13 +42,17 @@ class Component(__base__.Component):
self.x = self.page.spinBox_x.value()
self.y = self.page.spinBox_y.value()
self.parent.drawPreview()
-
+
def previewRender(self, previewWorker):
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
return self.drawFrame(width, height)
-
- def frameRender(self, moduleNo, frameNo):
+
+ def preFrameRender(self, **kwargs):
+ super().preFrameRender(**kwargs)
+ return ['static']
+
+ def frameRender(self, moduleNo, arrayNo, frameNo):
width = int(self.worker.core.settings.value('outputWidth'))
height = int(self.worker.core.settings.value('outputHeight'))
return self.drawFrame(width, height)
@@ -81,9 +85,3 @@ class Component(__base__.Component):
else:
self.page.lineEdit_color2.setText(RGBstring)
self.page.pushButton_color2.setStyleSheet(btnStyle)
-
- def cancel(self):
- self.canceled = True
-
- def reset(self):
- self.canceled = False
diff --git a/components/image.py b/components/image.py
index 021bb9e..ffbb117 100644
--- a/components/image.py
+++ b/components/image.py
@@ -27,8 +27,12 @@ class Component(__base__.Component):
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
return self.drawFrame(width, height)
-
- def frameRender(self, moduleNo, frameNo):
+
+ def preFrameRender(self, **kwargs):
+ super().preFrameRender(**kwargs)
+ return ['static']
+
+ def frameRender(self, moduleNo, arrayNo, frameNo):
width = int(self.worker.core.settings.value('outputWidth'))
height = int(self.worker.core.settings.value('outputHeight'))
return self.drawFrame(width, height)
@@ -49,12 +53,6 @@ class Component(__base__.Component):
return {
'image' : self.imagePath,
}
-
- def cancel(self):
- self.canceled = True
-
- def reset(self):
- self.canceled = False
def pickImage(self):
imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
diff --git a/components/original.py b/components/original.py
index 6903a5f..b0a7579 100644
--- a/components/original.py
+++ b/components/original.py
@@ -58,6 +58,8 @@ class Component(__base__.Component):
self.smoothConstantUp = 0.8
self.lastSpectrum = None
self.spectrumArray = {}
+ self.width = int(self.worker.core.settings.value('outputWidth'))
+ self.height = int(self.worker.core.settings.value('outputHeight'))
for i in range(0, len(self.completeAudioArray), self.sampleSize):
if self.canceled:
@@ -72,12 +74,9 @@ class Component(__base__.Component):
pStr = "Analyzing audio: "+ str(progress) +'%'
self.progressBarSetText.emit(pStr)
self.progressBarUpdate.emit(int(progress))
-
-
- def frameRender(self, moduleNo, frameNo):
- width = int(self.worker.core.settings.value('outputWidth'))
- height = int(self.worker.core.settings.value('outputHeight'))
- return self.drawBars(width, height, self.spectrumArray[frameNo], self.visColor, self.layout)
+
+ def frameRender(self, moduleNo, arrayNo, frameNo):
+ return self.drawBars(self.width, self.height, self.spectrumArray[arrayNo], self.visColor, self.layout)
def pickColor(self):
RGBstring, btnStyle = super().pickColor()
@@ -154,12 +153,3 @@ class Component(__base__.Component):
return im
- def cancel(self):
- self.canceled = True
-
- def reset(self):
- self.canceled = False
-
-
-
-
diff --git a/components/text.py b/components/text.py
index d2e009d..2650935 100644
--- a/components/text.py
+++ b/components/text.py
@@ -104,7 +104,7 @@ class Component(__base__.Component):
super().preFrameRender(**kwargs)
return ['static']
- def frameRender(self, moduleNo, frameNo):
+ def frameRender(self, moduleNo, arrayNo, frameNo):
width = int(self.worker.core.settings.value('outputWidth'))
height = int(self.worker.core.settings.value('outputHeight'))
return self.addText(width, height)
@@ -137,9 +137,3 @@ class Component(__base__.Component):
return
self.page.lineEdit_textColor.setText(RGBstring)
self.page.pushButton_textColor.setStyleSheet(btnStyle)
-
- def cancel(self):
- self.canceled = True
-
- def reset(self):
- self.canceled = False
diff --git a/components/video.py b/components/video.py
index 3d6ba7a..1a9f344 100644
--- a/components/video.py
+++ b/components/video.py
@@ -5,6 +5,10 @@ from . import __base__
class Component(__base__.Component):
'''Video'''
+ def __init__(self):
+ super().__init__()
+ self.working = False
+
def widget(self, parent):
self.parent = parent
self.settings = parent.settings
@@ -27,24 +31,35 @@ class Component(__base__.Component):
self.width = int(previewWorker.core.settings.value('outputWidth'))
self.height = int(previewWorker.core.settings.value('outputHeight'))
frames = self.getVideoFrames(True)
- if frames:
- im = Image.open(frames[0])
- im = self.resize(im)
- return im
- else:
- return Image.new("RGBA", (self.width, self.height), (0, 0, 0, 0))
+ if not hasattr(self, 'staticFrame') or not self.working and frames:
+ frame = Image.new("RGBA", (self.width, self.height), (0, 0, 0, 0))
+ if frames:
+ im = Image.open(frames[0])
+ im = self.resize(im)
+ frame.paste(im)
+ if not self.working:
+ self.staticFrame = frame
+ return self.staticFrame
def preFrameRender(self, **kwargs):
- super().__init__(**kwargs)
+ super().preFrameRender(**kwargs)
self.width = int(self.worker.core.settings.value('outputWidth'))
self.height = int(self.worker.core.settings.value('outputHeight'))
self.frames = self.getVideoFrames()
+ self.working = True
- def frameRender(self, moduleNo, frameNo):
- i = frameNo if frameNo < len(self.frames)-1 else len(self.frames)-1
- im = Image.open(self.frames[i])
- im = self.resize(im)
- return im
+ def frameRender(self, moduleNo, arrayNo, frameNo):
+ print(frameNo)
+ try:
+ if frameNo < len(self.frames)-1:
+ self.staticFrame = Image.new("RGBA", (self.width, self.height), (0, 0, 0, 0))
+ im = Image.open(self.frames[frameNo])
+ im = self.resize(im)
+ self.staticFrame.paste(im)
+ except FileNotFoundError:
+ print("Video component encountered an error")
+ self.frames = []
+ return self.staticFrame
def loadPreset(self, pr):
self.page.lineEdit_video.setText(pr['video'])
@@ -53,12 +68,6 @@ class Component(__base__.Component):
return {
'video' : self.videoPath,
}
-
- def cancel(self):
- self.canceled = True
-
- def reset(self):
- self.canceled = False
def pickVideo(self):
imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
@@ -69,19 +78,27 @@ class Component(__base__.Component):
self.page.lineEdit_video.setText(filename)
self.update()
- def getVideoFrames(self, firstOnly=False):
+ def getVideoFrames(self, preview=False):
# recreate the temporary directory so it is empty
- # FIXME: don't dump too many frames at once
+ # FIXME: don't dump all the frames at once, don't dump more than sound length
+ # FIXME: make cancellable, report status to user, etc etc etc
if not self.videoPath:
return
+ name = os.path.basename(self.videoPath).split('.', 1)[0]
+ if preview:
+ filename = 'preview%s.jpg' % name
+ if os.path.exists(os.path.join(self.parent.core.tempDir, filename)):
+ return False
+ else:
+ filename = name+'-frame%05d.jpg'
+
+ # recreate tempDir and dump needed frame(s)
self.parent.core.deleteTempDir()
os.mkdir(self.parent.core.tempDir)
- if firstOnly:
- filename = 'preview%s.jpg' % os.path.basename(self.videoPath).split('.', 1)[0]
- options = '-ss 10 -vframes 1'
+ if preview:
+ options = '-ss 10 -vframes 1'
else:
- filename = '$frame%05d.jpg'
- options = ''
+ options = '' #'-vframes 99999'
subprocess.call( \
'%s -i "%s" -y %s "%s"' % ( \
self.parent.core.FFMPEG_BIN,
@@ -91,6 +108,7 @@ class Component(__base__.Component):
),
shell=True
)
+ print('### Got Preview Frame From %s ###' % name if preview else '### Finished Dumping Frames From %s ###' % name)
return sorted([os.path.join(self.parent.core.tempDir, f) for f in os.listdir(self.parent.core.tempDir)])
def resize(self, im):
diff --git a/core.py b/core.py
index 16ecb35..ecbf12c 100644
--- a/core.py
+++ b/core.py
@@ -13,11 +13,8 @@ from collections import OrderedDict
class Core():
def __init__(self):
- self.lastBackgroundImage = ""
- self._image = None
-
self.FFMPEG_BIN = self.findFfmpeg()
- self.tempDir = None
+ self.tempDir = os.path.join(tempfile.gettempdir(), 'audio-visualizer-python-data')
atexit.register(self.deleteTempDir)
def findFfmpeg(self):
@@ -31,31 +28,6 @@ class Core():
except:
return "avconv"
- def parseBaseImage(self, backgroundImage, preview=False):
- ''' determines if the base image is a single frame or list of frames '''
- if backgroundImage == "":
- return ['']
- else:
- _, bgExt = os.path.splitext(backgroundImage)
- if not bgExt == '.mp4':
- return [backgroundImage]
- else:
- return self.getVideoFrames(backgroundImage, preview)
-
- def drawBaseImage(self, backgroundFile):
- if backgroundFile == '':
- im = Image.new("RGB", (int(self.settings.value('outputWidth')), int(self.settings.value('outputHeight'))), "black")
- else:
- im = Image.open(backgroundFile)
-
- if self._image == None or not self.lastBackgroundImage == backgroundFile:
- self.lastBackgroundImage = backgroundFile
- # resize if necessary
- if not im.size == (int(self.settings.value('outputWidth')), int(self.settings.value('outputHeight'))):
- im = im.resize((int(self.settings.value('outputWidth')), int(self.settings.value('outputHeight'))), Image.ANTIALIAS)
-
- return im
-
def readAudioFile(self, filename, parent):
command = [ self.FFMPEG_BIN,
'-i', filename]
@@ -121,30 +93,11 @@ class Core():
return completeAudioArray
def deleteTempDir(self):
- if self.tempDir and os.path.exists(self.tempDir):
- rmtree(self.tempDir)
-
- def getVideoFrames(self, videoPath, firstOnly=False):
- self.tempDir = os.path.join(tempfile.gettempdir(), 'audio-visualizer-python-data')
- # recreate the temporary directory so it is empty
- self.deleteTempDir()
- os.mkdir(self.tempDir)
- if firstOnly:
- filename = 'preview%s.jpg' % os.path.basename(videoPath).split('.', 1)[0]
- options = '-ss 10 -vframes 1'
- else:
- filename = '$frame%05d.jpg'
- options = ''
- sp.call( \
- '%s -i "%s" -y %s "%s"' % ( \
- self.FFMPEG_BIN,
- videoPath,
- options,
- os.path.join(self.tempDir, filename)
- ),
- shell=True
- )
- return sorted([os.path.join(self.tempDir, f) for f in os.listdir(self.tempDir)])
+ try:
+ if os.path.exists(self.tempDir):
+ rmtree(self.tempDir)
+ except FileNotFoundError:
+ pass
def cancel(self):
self.canceled = True
@@ -153,6 +106,6 @@ class Core():
self.canceled = False
@staticmethod
- def sortedStringDict(dictionary):
+ def stringOrderedDict(dictionary):
sorted_ = OrderedDict(sorted(dictionary.items(), key=lambda t: t[0]))
return repr(sorted_)
diff --git a/main.py b/main.py
index 2aa7fa9..da72b1e 100644
--- a/main.py
+++ b/main.py
@@ -133,9 +133,9 @@ class PreviewWindow(QtGui.QLabel):
class Main(QtCore.QObject):
- newTask = QtCore.pyqtSignal(str, list)
+ newTask = QtCore.pyqtSignal(list)
processTask = QtCore.pyqtSignal()
- videoTask = QtCore.pyqtSignal(str, str, str, list)
+ videoTask = QtCore.pyqtSignal(str, str, list)
def __init__(self, window):
QtCore.QObject.__init__(self)
@@ -172,14 +172,13 @@ class Main(QtCore.QObject):
# begin decorating the window and connecting events
window.toolButton_selectAudioFile.clicked.connect(self.openInputFileDialog)
- window.toolButton_selectBackground.clicked.connect(self.openBackgroundFileDialog)
window.toolButton_selectOutputFile.clicked.connect(self.openOutputFileDialog)
window.progressBar_createVideo.setValue(0)
window.pushButton_createVideo.clicked.connect(self.createAudioVisualisation)
window.pushButton_Cancel.clicked.connect(self.stopVideo)
window.setWindowTitle("Audio Visualizer")
- self.previewWindow = PreviewWindow(self, os.path.join(os.path.dirname(os.path.realpath(__file__)), "background.jpg"))
+ self.previewWindow = PreviewWindow(self, os.path.join(os.path.dirname(os.path.realpath(__file__)), "background.png"))
window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
self.modules = self.findComponents()
@@ -260,17 +259,6 @@ class Main(QtCore.QObject):
self.settings.setValue("outputDir", os.path.dirname(fileName))
self.window.lineEdit_outputFile.setText(fileName)
- def openBackgroundFileDialog(self):
- backgroundDir = self.settings.value("backgroundDir", expanduser("~"))
-
- fileName = QtGui.QFileDialog.getOpenFileName(self.window,
- "Open Background Image", backgroundDir, "Image Files (*.jpg *.png);; Video Files (*.mp4)");
-
- if not fileName == "":
- self.settings.setValue("backgroundDir", os.path.dirname(fileName))
- self.window.lineEdit_background.setText(fileName)
- self.drawPreview()
-
def stopVideo(self):
print('stop')
self.videoWorker.cancel()
@@ -291,8 +279,7 @@ class Main(QtCore.QObject):
self.videoWorker.imageCreated.connect(self.showPreviewImage)
self.videoWorker.encoding.connect(self.changeEncodingStatus)
self.videoThread.start()
- self.videoTask.emit(self.window.lineEdit_background.text(),
- self.window.lineEdit_audioFile.text(),
+ self.videoTask.emit(self.window.lineEdit_audioFile.text(),
self.window.lineEdit_outputFile.text(),
self.selectedComponents)
else:
@@ -323,10 +310,6 @@ class Main(QtCore.QObject):
self.window.pushButton_savePreset.setEnabled(False)
self.window.pushButton_openProject.setEnabled(False)
self.window.listWidget_componentList.setEnabled(False)
-
- self.window.label_background.setEnabled(False)
- self.window.lineEdit_background.setEnabled(False)
- self.window.toolButton_selectBackground.setEnabled(False)
else:
self.window.pushButton_createVideo.setEnabled(True)
self.window.pushButton_Cancel.setEnabled(False)
@@ -349,12 +332,6 @@ class Main(QtCore.QObject):
self.window.pushButton_openProject.setEnabled(True)
self.window.listWidget_componentList.setEnabled(True)
- self.window.label_background.setEnabled(True)
- self.window.lineEdit_background.setEnabled(True)
- self.window.toolButton_selectBackground.setEnabled(True)
-
-
-
def progressBarSetText(self, value):
self.window.progressBar_createVideo.setFormat(value)
@@ -370,7 +347,7 @@ class Main(QtCore.QObject):
self.drawPreview()
def drawPreview(self):
- self.newTask.emit(self.window.lineEdit_background.text(), self.selectedComponents)
+ self.newTask.emit(self.selectedComponents)
# self.processTask.emit()
self.autosave()
@@ -381,7 +358,7 @@ class Main(QtCore.QObject):
def findComponents():
srcPath = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'components')
if os.path.exists(srcPath):
- for f in os.listdir(srcPath):
+ for f in sorted(os.listdir(srcPath)):
name, ext = os.path.splitext(f)
if name.startswith("__"):
continue
@@ -507,7 +484,7 @@ class Main(QtCore.QObject):
if self.window.comboBox_openPreset.itemText(i) == filename:
self.window.comboBox_openPreset.removeItem(i)
with open(filepath, 'w') as f:
- f.write(core.Core.sortedStringDict(saveValueStore))
+ f.write(core.Core.stringOrderedDict(saveValueStore))
self.window.comboBox_openPreset.addItem(filename)
self.window.comboBox_openPreset.setCurrentIndex(self.window.comboBox_openPreset.count()-1)
@@ -550,12 +527,13 @@ class Main(QtCore.QObject):
if not filepath.endswith(".avp"):
filepath += '.avp'
with open(filepath, 'w') as f:
+ print('creating %s' % filepath)
f.write('[Components]\n')
for comp in self.selectedComponents:
saveValueStore = comp.savePreset()
f.write('%s\n' % str(comp))
f.write('%s\n' % str(comp.version()))
- f.write('%s\n' % core.Core.sortedStringDict(saveValueStore))
+ f.write('%s\n' % core.Core.stringOrderedDict(saveValueStore))
if filepath != self.autosavePath:
self.settings.setValue("projectDir", os.path.dirname(filepath))
self.settings.setValue("currentProject", filepath)
@@ -604,14 +582,18 @@ class Main(QtCore.QObject):
saveValueStore = dict(eval(line))
self.selectedComponents[-1].loadPreset(saveValueStore)
i = 0
- except:
+ except (IndexError, ValueError, KeyError, NameError, SyntaxError, AttributeError, TypeError) as e:
self.clear()
- self.showMessage("Project file '%s' is corrupted." % filepath)
+ typ, value, _ = sys.exc_info()
+ msg = '%s: %s' % (typ.__name__, value)
+ self.showMessage("Project file '%s' is corrupted." % filepath, False,
+ QtGui.QMessageBox.Warning, msg)
- def showMessage(self, string, showCancel=False, icon=QtGui.QMessageBox.Information):
+ def showMessage(self, string, showCancel=False, icon=QtGui.QMessageBox.Information, detail=None):
msg = QtGui.QMessageBox()
msg.setIcon(icon)
msg.setText(string)
+ msg.setDetailedText(detail)
if showCancel:
msg.setStandardButtons(QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel)
else:
diff --git a/mainwindow.ui b/mainwindow.ui
index f9e8f5e..b703997 100644
--- a/mainwindow.ui
+++ b/mainwindow.ui
@@ -6,8 +6,8 @@
0
0
- 1008
- 575
+ 1028
+ 592
@@ -421,73 +421,6 @@
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 85
- 0
-
-
-
- Background
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 28
-
-
-
-
- 16777215
- 28
-
-
-
-
- -
-
-
-
- 0
- 28
-
-
-
-
- 16777215
- 28
-
-
-
- ...
-
-
-
-
-
-
-
@@ -621,7 +554,6 @@
-
diff --git a/preview_thread.py b/preview_thread.py
index 63d1ac5..5116707 100644
--- a/preview_thread.py
+++ b/preview_thread.py
@@ -22,10 +22,9 @@ class Worker(QtCore.QObject):
@pyqtSlot(str, list)
- def createPreviewImage(self, backgroundImage, components):
+ def createPreviewImage(self, components):
# print('worker thread id: {}'.format(QtCore.QThread.currentThreadId()))
dic = {
- "backgroundImage": backgroundImage,
"components": components,
}
self.queue.put(dic)
@@ -40,25 +39,14 @@ class Worker(QtCore.QObject):
except Empty:
continue
- bgImage = self.core.parseBaseImage(\
- nextPreviewInformation["backgroundImage"],
- preview=True
- )
- if bgImage == []:
- bgImage = ''
- else:
- bgImage = bgImage[0]
-
- im = self.core.drawBaseImage(bgImage)
width = int(self.core.settings.value('outputWidth'))
height = int(self.core.settings.value('outputHeight'))
- frame = Image.new("RGBA", (width, height),(0,0,0,255))
- frame.paste(im)
+ frame = Image.new("RGBA", (width, height),(0,0,0,0))
components = nextPreviewInformation["components"]
for component in reversed(components):
- newFrame = Image.alpha_composite(frame,component.previewRender(self))
- frame = Image.alpha_composite(frame,newFrame)
+ #newFrame = Image.alpha_composite(frame,)
+ frame = Image.alpha_composite(frame,component.previewRender(self))
self._image = ImageQt(frame)
self.imageCreated.emit(QtGui.QImage(self._image))
diff --git a/video_thread.py b/video_thread.py
index c97cc24..e74fffa 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -38,16 +38,17 @@ class Worker(QtCore.QObject):
while not self.stopped:
i = self.compositeQueue.get()
- if self.imBackground is not None:
- frame = self.imBackground
- else:
- frame = self.getBackgroundAtIndex(i[1])
+ frame = Image.new(
+ "RGBA",
+ (self.width, self.height),
+ (0, 0, 0, 0)
+ )
for compNo, comp in reversed(list(enumerate(self.components))):
if compNo in self.staticComponents and self.staticComponents[compNo] != None:
frame = Image.alpha_composite(frame, self.staticComponents[compNo])
else:
- frame = Image.alpha_composite(frame, comp.frameRender(compNo, i[0]))
+ frame = Image.alpha_composite(frame, comp.frameRender(compNo, i[0], i[1]))
# frame.paste(compFrame, mask=compFrame)
@@ -59,10 +60,8 @@ class Worker(QtCore.QObject):
for i in range(0, len(self.completeAudioArray), self.sampleSize):
self.compositeQueue.put([i, self.bgI])
- if not self.imBackground:
- # increment background video frame for next iteration
- if self.bgI < len(self.backgroundFrames)-1:
- self.bgI += 1
+ # increment tracked video frame for next iteration
+ self.bgI += 1
def previewDispatch(self):
while not self.stopped:
@@ -74,39 +73,18 @@ class Worker(QtCore.QObject):
self.previewQueue.task_done()
- def getBackgroundAtIndex(self, i):
- background = Image.new(
- "RGBA",
- (self.width, self.height),
- (0, 0, 0, 255)
- )
- layer = self.core.drawBaseImage(self.backgroundFrames[i])
- background.paste(layer)
- return background
-
- @pyqtSlot(str, str, str, list)
- def createVideo(self, backgroundImage, inputFile, outputFile, components):
+ @pyqtSlot(str, str, list)
+ def createVideo(self, inputFile, outputFile, components):
self.encoding.emit(True)
self.components = components
self.outputFile = outputFile
+ self.bgI = 0 # tracked video frame
self.reset()
self.width = int(self.core.settings.value('outputWidth'))
self.height = int(self.core.settings.value('outputHeight'))
# print('worker thread id: {}'.format(QtCore.QThread.currentThreadId()))
progressBarValue = 0
self.progressBarUpdate.emit(progressBarValue)
- self.progressBarSetText.emit('Loading background image…')
-
- self.backgroundImage = backgroundImage
-
- self.backgroundFrames = self.core.parseBaseImage(backgroundImage)
- if len(self.backgroundFrames) < 2:
- # the base image is not a video so we can draw it now
- self.imBackground = self.getBackgroundAtIndex(0)
- else:
- # base images will be drawn while drawing the audio bars
- self.imBackground = None
- self.bgI = 0
self.progressBarSetText.emit('Loading audio file...')
self.completeAudioArray = self.core.readAudioFile(inputFile, self)
@@ -165,7 +143,7 @@ class Worker(QtCore.QObject):
)
if properties and 'static' in properties:
- self.staticComponents[compNo] = copy(comp.frameRender(compNo, 0))
+ self.staticComponents[compNo] = copy(comp.frameRender(compNo, 0, 0))
self.progressBarUpdate.emit(100)
self.compositeQueue = Queue()
--
cgit v1.2.3
From 277e86f2795093deaa12f294c29ac2f951ae65cd Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 4 Jun 2017 20:27:43 -0400
Subject: not dumping frames anymore, but not working yet either
will finish later
---
components/video.py | 69 +++++++++++++++++++++++++++++++++--------------------
core.py | 5 ++--
main.py | 2 +-
video_thread.py | 1 -
4 files changed, 47 insertions(+), 30 deletions(-)
diff --git a/components/video.py b/components/video.py
index 1a9f344..97b824c 100644
--- a/components/video.py
+++ b/components/video.py
@@ -30,11 +30,11 @@ class Component(__base__.Component):
def previewRender(self, previewWorker):
self.width = int(previewWorker.core.settings.value('outputWidth'))
self.height = int(previewWorker.core.settings.value('outputHeight'))
- frames = self.getVideoFrames(True)
- if not hasattr(self, 'staticFrame') or not self.working and frames:
+ frame1 = self.getPreviewFrame()
+ if not hasattr(self, 'staticFrame') or not self.working and frame1:
frame = Image.new("RGBA", (self.width, self.height), (0, 0, 0, 0))
- if frames:
- im = Image.open(frames[0])
+ if frame1:
+ im = Image.open(frame1)
im = self.resize(im)
frame.paste(im)
if not self.working:
@@ -77,39 +77,56 @@ class Component(__base__.Component):
self.settings.setValue("backgroundDir", os.path.dirname(filename))
self.page.lineEdit_video.setText(filename)
self.update()
-
- def getVideoFrames(self, preview=False):
- # recreate the temporary directory so it is empty
- # FIXME: don't dump all the frames at once, don't dump more than sound length
- # FIXME: make cancellable, report status to user, etc etc etc
+
+ def getPreviewFrame(self):
if not self.videoPath:
return
name = os.path.basename(self.videoPath).split('.', 1)[0]
- if preview:
- filename = 'preview%s.jpg' % name
- if os.path.exists(os.path.join(self.parent.core.tempDir, filename)):
- return False
- else:
- filename = name+'-frame%05d.jpg'
-
- # recreate tempDir and dump needed frame(s)
- self.parent.core.deleteTempDir()
- os.mkdir(self.parent.core.tempDir)
- if preview:
- options = '-ss 10 -vframes 1'
- else:
- options = '' #'-vframes 99999'
+ filename = 'preview%s.jpg' % name
+ if os.path.exists(os.path.join(self.parent.core.tempDir, filename)):
+ # no, we don't need a new preview frame
+ return False
+
+ # get a preview frame
subprocess.call( \
'%s -i "%s" -y %s "%s"' % ( \
self.parent.core.FFMPEG_BIN,
self.videoPath,
- options,
+ '-ss 10 -vframes 1',
os.path.join(self.parent.core.tempDir, filename)
),
shell=True
)
- print('### Got Preview Frame From %s ###' % name if preview else '### Finished Dumping Frames From %s ###' % name)
- return sorted([os.path.join(self.parent.core.tempDir, f) for f in os.listdir(self.parent.core.tempDir)])
+ print('### Got Preview Frame From %s ###' % name)
+ return os.path.join(self.parent.core.tempDir, filename)
+
+ def getVideoFrames(self):
+ # FIXME: make cancellable, report status to user, etc etc etc
+ if not self.videoPath:
+ return
+
+ command = [
+ self.parent.core.FFMPEG_BIN,
+ '-i', self.videoPath,
+ '-f', 'image2pipe',
+ '-vcodec', 'rawvideo', '-',
+ '-pix_fmt', 'rgba',
+ ]
+
+ # pipe in video frames from ffmpeg
+ in_pipe = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, bufsize=8**10)
+ # maybe bufsize=4*self.width*self.height+100 ?
+ chunk = 4*self.width*self.height
+
+ frames = []
+ while True:
+ byteFrame = in_pipe.stdout.read(chunk)
+ if len(byteFrame) == 0:
+ break
+ img = Image.frombytes('RGBA', (self.width, self.height), byteFrame, 'raw', 'RGBa')
+ frames.append(img)
+
+ return frames
def resize(self, im):
if im.size != (self.width, self.height):
diff --git a/core.py b/core.py
index ecbf12c..99403f1 100644
--- a/core.py
+++ b/core.py
@@ -15,6 +15,8 @@ class Core():
def __init__(self):
self.FFMPEG_BIN = self.findFfmpeg()
self.tempDir = os.path.join(tempfile.gettempdir(), 'audio-visualizer-python-data')
+ if not os.path.exists(self.tempDir):
+ os.makedirs(self.tempDir)
atexit.register(self.deleteTempDir)
def findFfmpeg(self):
@@ -94,8 +96,7 @@ class Core():
def deleteTempDir(self):
try:
- if os.path.exists(self.tempDir):
- rmtree(self.tempDir)
+ rmtree(self.tempDir)
except FileNotFoundError:
pass
diff --git a/main.py b/main.py
index da72b1e..637ece8 100644
--- a/main.py
+++ b/main.py
@@ -253,7 +253,7 @@ class Main(QtCore.QObject):
outputDir = self.settings.value("outputDir", expanduser("~"))
fileName = QtGui.QFileDialog.getSaveFileName(self.window,
- "Set Output Video File", outputDir, "Video Files (*.mkv)");
+ "Set Output Video File", outputDir, "Video Files (*.mkv *.mp4)");
if not fileName == "":
self.settings.setValue("outputDir", os.path.dirname(fileName))
diff --git a/video_thread.py b/video_thread.py
index e74fffa..0542bc2 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -228,7 +228,6 @@ class Worker(QtCore.QObject):
self.error = False
self.canceled = False
self.parent.drawPreview()
- self.core.deleteTempDir()
self.stopped = True
self.encoding.emit(False)
self.videoCreated.emit()
--
cgit v1.2.3
From e58a1d0b2d499aca72af8472ec33c02580d2dffd Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 4 Jun 2017 22:57:19 -0400
Subject: frames are taken straight from the in_pipe
---
components/video.py | 49 ++++++++++++++++++++++++-------------------------
1 file changed, 24 insertions(+), 25 deletions(-)
diff --git a/components/video.py b/components/video.py
index 97b824c..b009baa 100644
--- a/components/video.py
+++ b/components/video.py
@@ -1,6 +1,7 @@
from PIL import Image, ImageDraw
from PyQt4 import uic, QtGui, QtCore
import os, subprocess
+import numpy
from . import __base__
class Component(__base__.Component):
@@ -35,6 +36,7 @@ class Component(__base__.Component):
frame = Image.new("RGBA", (self.width, self.height), (0, 0, 0, 0))
if frame1:
im = Image.open(frame1)
+ self.realSize = im.size
im = self.resize(im)
frame.paste(im)
if not self.working:
@@ -45,20 +47,26 @@ class Component(__base__.Component):
super().preFrameRender(**kwargs)
self.width = int(self.worker.core.settings.value('outputWidth'))
self.height = int(self.worker.core.settings.value('outputHeight'))
- self.frames = self.getVideoFrames()
self.working = True
+ self.frames = self.getVideoFrames()
def frameRender(self, moduleNo, arrayNo, frameNo):
- print(frameNo)
- try:
- if frameNo < len(self.frames)-1:
- self.staticFrame = Image.new("RGBA", (self.width, self.height), (0, 0, 0, 0))
- im = Image.open(self.frames[frameNo])
- im = self.resize(im)
- self.staticFrame.paste(im)
- except FileNotFoundError:
- print("Video component encountered an error")
- self.frames = []
+ # don't make a new frame
+ if not self.working:
+ return self.staticFrame
+ byteFrame = self.frames.stdout.read(self.chunkSize)
+ if len(byteFrame) == 0:
+ self.working = False
+ self.frames.kill()
+ return self.staticFrame
+
+ # make a new frame
+ width, height = self.realSize
+ image = numpy.fromstring(byteFrame, dtype='uint8')
+ image = image.reshape((width, height, 4))
+ image = Image.frombytes('RGBA', (width, height), image, 'raw', 'RGBa')
+ image = self.resize(image)
+ self.staticFrame = image
return self.staticFrame
def loadPreset(self, pr):
@@ -101,7 +109,6 @@ class Component(__base__.Component):
return os.path.join(self.parent.core.tempDir, filename)
def getVideoFrames(self):
- # FIXME: make cancellable, report status to user, etc etc etc
if not self.videoPath:
return
@@ -109,24 +116,16 @@ class Component(__base__.Component):
self.parent.core.FFMPEG_BIN,
'-i', self.videoPath,
'-f', 'image2pipe',
- '-vcodec', 'rawvideo', '-',
'-pix_fmt', 'rgba',
+ '-vcodec', 'rawvideo', '-',
]
# pipe in video frames from ffmpeg
- in_pipe = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, bufsize=8**10)
- # maybe bufsize=4*self.width*self.height+100 ?
- chunk = 4*self.width*self.height
-
- frames = []
- while True:
- byteFrame = in_pipe.stdout.read(chunk)
- if len(byteFrame) == 0:
- break
- img = Image.frombytes('RGBA', (self.width, self.height), byteFrame, 'raw', 'RGBa')
- frames.append(img)
+ in_pipe = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, bufsize=10**8)
+ width, height = self.realSize
+ self.chunkSize = 4*width*height
- return frames
+ return in_pipe
def resize(self, im):
if im.size != (self.width, self.height):
--
cgit v1.2.3
From 0a8a2fdf71aeff8ea22905cdf00b6c315cbb29ee Mon Sep 17 00:00:00 2001
From: DH4
Date: Mon, 5 Jun 2017 02:38:10 -0500
Subject: Font Offset
---
components/text.py | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/components/text.py b/components/text.py
index 7efde39..7b2289d 100644
--- a/components/text.py
+++ b/components/text.py
@@ -69,9 +69,11 @@ class Component(__base__.Component):
if self.alignment == 0: #Left
x = self.xPosition
if self.alignment == 1: #Middle
- x = self.xPosition - fm.width(self.title)/2
+ offset = fm.width(self.title)/2
+ x = self.xPosition - offset
if self.alignment == 2: #Right
- x = self.xPosition - fm.width(self.title)
+ offset = fm.width(self.title)
+ x = self.xPosition - offset
return x, self.yPosition
--
cgit v1.2.3
From be18deece5843ac8d2c7af64704e3fb360a05a25 Mon Sep 17 00:00:00 2001
From: DH4
Date: Mon, 5 Jun 2017 04:54:58 -0500
Subject: Performance Tuning. FIXME: Video component frames are rendered out
of order. Video component creates a severe performance bottleneck.
---
components/video.py | 21 ++++++++++-----------
main.py | 4 ++--
video_thread.py | 20 ++++++++++----------
3 files changed, 22 insertions(+), 23 deletions(-)
diff --git a/components/video.py b/components/video.py
index b009baa..422b952 100644
--- a/components/video.py
+++ b/components/video.py
@@ -36,8 +36,6 @@ class Component(__base__.Component):
frame = Image.new("RGBA", (self.width, self.height), (0, 0, 0, 0))
if frame1:
im = Image.open(frame1)
- self.realSize = im.size
- im = self.resize(im)
frame.paste(im)
if not self.working:
self.staticFrame = frame
@@ -61,11 +59,9 @@ class Component(__base__.Component):
return self.staticFrame
# make a new frame
- width, height = self.realSize
- image = numpy.fromstring(byteFrame, dtype='uint8')
- image = image.reshape((width, height, 4))
- image = Image.frombytes('RGBA', (width, height), image, 'raw', 'RGBa')
- image = self.resize(image)
+ width = self.width
+ height = self.height
+ image = Image.frombytes('RGBA', (width, height), byteFrame)
self.staticFrame = image
return self.staticFrame
@@ -80,7 +76,7 @@ class Component(__base__.Component):
def pickVideo(self):
imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
filename = QtGui.QFileDialog.getOpenFileName(self.page,
- "Choose Video", imgDir, "Video Files (*.mp4)")
+ "Choose Video", imgDir, "Video Files (*.mp4 *.mov)")
if filename:
self.settings.setValue("backgroundDir", os.path.dirname(filename))
self.page.lineEdit_video.setText(filename)
@@ -97,10 +93,11 @@ class Component(__base__.Component):
# get a preview frame
subprocess.call( \
- '%s -i "%s" -y %s "%s"' % ( \
+ '%s -i "%s" -y %s %s "%s"' % ( \
self.parent.core.FFMPEG_BIN,
self.videoPath,
'-ss 10 -vframes 1',
+ '-filter:v scale='+str(self.width)+':'+str(self.height),
os.path.join(self.parent.core.tempDir, filename)
),
shell=True
@@ -114,16 +111,18 @@ class Component(__base__.Component):
command = [
self.parent.core.FFMPEG_BIN,
+ '-thread_queue_size', '512',
'-i', self.videoPath,
'-f', 'image2pipe',
'-pix_fmt', 'rgba',
+ '-filter:v', 'scale='+str(self.width)+':'+str(self.height),
'-vcodec', 'rawvideo', '-',
]
# pipe in video frames from ffmpeg
in_pipe = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, bufsize=10**8)
- width, height = self.realSize
- self.chunkSize = 4*width*height
+ #width, height = self.realSize
+ self.chunkSize = 4*self.width*self.height
return in_pipe
diff --git a/main.py b/main.py
index 637ece8..36fc989 100644
--- a/main.py
+++ b/main.py
@@ -243,7 +243,7 @@ class Main(QtCore.QObject):
inputDir = self.settings.value("inputDir", expanduser("~"))
fileName = QtGui.QFileDialog.getOpenFileName(self.window,
- "Open Music File", inputDir, "Music Files (*.mp3 *.wav *.ogg *.flac)");
+ "Open Music File", inputDir, "Music Files (*.mp3 *.wav *.ogg *.fla *.aac)");
if not fileName == "":
self.settings.setValue("inputDir", os.path.dirname(fileName))
@@ -253,7 +253,7 @@ class Main(QtCore.QObject):
outputDir = self.settings.value("outputDir", expanduser("~"))
fileName = QtGui.QFileDialog.getSaveFileName(self.window,
- "Set Output Video File", outputDir, "Video Files (*.mkv *.mp4)");
+ "Set Output Video File", outputDir, "Video Files (*.mp4 *.mov *.mkv *.avi *.webm *.flv)");
if not fileName == "":
self.settings.setValue("outputDir", os.path.dirname(fileName))
diff --git a/video_thread.py b/video_thread.py
index 0542bc2..ac4162c 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -37,20 +37,19 @@ class Worker(QtCore.QObject):
def renderNode(self):
while not self.stopped:
i = self.compositeQueue.get()
-
- frame = Image.new(
- "RGBA",
- (self.width, self.height),
- (0, 0, 0, 0)
- )
+ frame = None
for compNo, comp in reversed(list(enumerate(self.components))):
if compNo in self.staticComponents and self.staticComponents[compNo] != None:
- frame = Image.alpha_composite(frame, self.staticComponents[compNo])
+ if frame is None:
+ frame = self.staticComponents[compNo]
+ else:
+ frame = Image.alpha_composite(frame, self.staticComponents[compNo])
else:
- frame = Image.alpha_composite(frame, comp.frameRender(compNo, i[0], i[1]))
-
- # frame.paste(compFrame, mask=compFrame)
+ if frame is None:
+ frame = comp.frameRender(compNo, i[0], i[1])
+ else:
+ frame = Image.alpha_composite(frame, comp.frameRender(compNo, i[0], i[1]))
self.renderQueue.put([i[0], frame])
self.compositeQueue.task_done()
@@ -98,6 +97,7 @@ class Worker(QtCore.QObject):
ffmpegCommand = [
self.core.FFMPEG_BIN,
+ '-thread_queue_size', '512',
'-y', # (optional) means overwrite the output file if it already exists.
'-f', 'rawvideo',
'-vcodec', 'rawvideo',
--
cgit v1.2.3
From 47509ae2b19e5a05211ae06114ba4675aa60a793 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 6 Jun 2017 01:40:26 -0400
Subject: added framebuffer to keep frames in order
NOT WORKING: end of video detection
---
components/video.py | 144 +++++++++++++++++++++++++---------------------------
main.py | 13 +++--
2 files changed, 76 insertions(+), 81 deletions(-)
diff --git a/components/video.py b/components/video.py
index 422b952..0ae5348 100644
--- a/components/video.py
+++ b/components/video.py
@@ -1,15 +1,56 @@
from PIL import Image, ImageDraw
from PyQt4 import uic, QtGui, QtCore
-import os, subprocess
-import numpy
+import os, subprocess, threading
+from queue import PriorityQueue
from . import __base__
+class Video:
+ '''Video Component Frame-Fetcher'''
+ def __init__(self, ffmpeg, videoPath, width, height, frameRate, chunkSize, parent):
+ self.parent = parent
+ self.chunkSize = chunkSize
+ self.size = (width, height)
+ self.frameNo = -1
+ self.command = [
+ ffmpeg,
+ '-thread_queue_size', '512',
+ '-r', frameRate,
+ '-i', videoPath,
+ '-f', 'image2pipe',
+ '-pix_fmt', 'rgba',
+ '-filter:v', 'scale='+str(width)+':'+str(height),
+ '-vcodec', 'rawvideo', '-',
+ ]
+
+ self.frameBuffer = PriorityQueue()
+ self.frameBuffer.maxsize = int(frameRate)
+ self.finishedFrames = {}
+
+ self.thread = threading.Thread(target=self.fillBuffer, name=self.__doc__)
+ self.thread.daemon = True
+ self.thread.start()
+
+ def frame(self, num):
+ while True:
+ if num in self.finishedFrames:
+ image = self.finishedFrames.pop(num)
+ return Image.frombytes('RGBA', self.size, image)
+ i, image = self.frameBuffer.get()
+ self.finishedFrames[i] = image
+ self.frameBuffer.task_done()
+
+ def fillBuffer(self):
+ self.pipe = subprocess.Popen(self.command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, bufsize=10**8)
+ while True:
+ if self.parent.canceled:
+ break
+ self.frameNo += 1
+ image = self.pipe.stdout.read(self.chunkSize)
+ print('creating frame #%s' % str(self.frameNo))
+ self.frameBuffer.put((self.frameNo, image))
+
class Component(__base__.Component):
'''Video'''
- def __init__(self):
- super().__init__()
- self.working = False
-
def widget(self, parent):
self.parent = parent
self.settings = parent.settings
@@ -29,41 +70,22 @@ class Component(__base__.Component):
self.parent.drawPreview()
def previewRender(self, previewWorker):
- self.width = int(previewWorker.core.settings.value('outputWidth'))
- self.height = int(previewWorker.core.settings.value('outputHeight'))
- frame1 = self.getPreviewFrame()
- if not hasattr(self, 'staticFrame') or not self.working and frame1:
- frame = Image.new("RGBA", (self.width, self.height), (0, 0, 0, 0))
- if frame1:
- im = Image.open(frame1)
- frame.paste(im)
- if not self.working:
- self.staticFrame = frame
- return self.staticFrame
+ width = int(previewWorker.core.settings.value('outputWidth'))
+ height = int(previewWorker.core.settings.value('outputHeight'))
+ self.chunkSize = 4*width*height
+ return self.getPreviewFrame(width, height)
def preFrameRender(self, **kwargs):
super().preFrameRender(**kwargs)
- self.width = int(self.worker.core.settings.value('outputWidth'))
- self.height = int(self.worker.core.settings.value('outputHeight'))
- self.working = True
- self.frames = self.getVideoFrames()
+ width = int(self.worker.core.settings.value('outputWidth'))
+ height = int(self.worker.core.settings.value('outputHeight'))
+ self.chunkSize = 4*width*height
+ self.video = Video(self.parent.core.FFMPEG_BIN, self.videoPath,
+ width, height, self.settings.value("outputFrameRate"),
+ self.chunkSize, self.parent)
def frameRender(self, moduleNo, arrayNo, frameNo):
- # don't make a new frame
- if not self.working:
- return self.staticFrame
- byteFrame = self.frames.stdout.read(self.chunkSize)
- if len(byteFrame) == 0:
- self.working = False
- self.frames.kill()
- return self.staticFrame
-
- # make a new frame
- width = self.width
- height = self.height
- image = Image.frombytes('RGBA', (width, height), byteFrame)
- self.staticFrame = image
- return self.staticFrame
+ return self.video.frame(frameNo)
def loadPreset(self, pr):
self.page.lineEdit_video.setText(pr['video'])
@@ -82,51 +104,21 @@ class Component(__base__.Component):
self.page.lineEdit_video.setText(filename)
self.update()
- def getPreviewFrame(self):
- if not self.videoPath:
- return
- name = os.path.basename(self.videoPath).split('.', 1)[0]
- filename = 'preview%s.jpg' % name
- if os.path.exists(os.path.join(self.parent.core.tempDir, filename)):
- # no, we don't need a new preview frame
- return False
-
- # get a preview frame
- subprocess.call( \
- '%s -i "%s" -y %s %s "%s"' % ( \
- self.parent.core.FFMPEG_BIN,
- self.videoPath,
- '-ss 10 -vframes 1',
- '-filter:v scale='+str(self.width)+':'+str(self.height),
- os.path.join(self.parent.core.tempDir, filename)
- ),
- shell=True
- )
- print('### Got Preview Frame From %s ###' % name)
- return os.path.join(self.parent.core.tempDir, filename)
-
- def getVideoFrames(self):
- if not self.videoPath:
- return
-
+ def getPreviewFrame(self, width, height):
command = [
self.parent.core.FFMPEG_BIN,
'-thread_queue_size', '512',
'-i', self.videoPath,
'-f', 'image2pipe',
'-pix_fmt', 'rgba',
- '-filter:v', 'scale='+str(self.width)+':'+str(self.height),
+ '-filter:v', 'scale='+str(width)+':'+str(height),
'-vcodec', 'rawvideo', '-',
+ '-ss', '90',
+ '-vframes', '1',
]
-
- # pipe in video frames from ffmpeg
- in_pipe = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, bufsize=10**8)
- #width, height = self.realSize
- self.chunkSize = 4*self.width*self.height
-
- return in_pipe
-
- def resize(self, im):
- if im.size != (self.width, self.height):
- im = im.resize((self.width, self.height), Image.ANTIALIAS)
- return im
+ pipe = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, bufsize=10**8)
+ byteFrame = pipe.stdout.read(self.chunkSize)
+ image = Image.frombytes('RGBA', (width, height), byteFrame)
+ pipe.stdout.close()
+ pipe.kill()
+ return image
diff --git a/main.py b/main.py
index 36fc989..c75a7f7 100644
--- a/main.py
+++ b/main.py
@@ -1,4 +1,4 @@
-import sys, io, os, shutil, atexit, string, signal, filecmp
+import sys, io, os, shutil, atexit, string, signal, filecmp, time
from os.path import expanduser
from queue import Queue
from importlib import import_module
@@ -145,6 +145,7 @@ class Main(QtCore.QObject):
self.core = core.Core()
self.pages = []
self.selectedComponents = []
+ self.lastAutosave = time.time()
# create data directory, load/create settings
self.dataDir = QDesktopServices.storageLocation(QDesktopServices.DataLocation)
@@ -235,9 +236,11 @@ class Main(QtCore.QObject):
self.autosave()
def autosave(self):
- if os.path.exists(self.autosavePath):
- os.remove(self.autosavePath)
- self.createProjectFile(self.autosavePath)
+ if time.time() - self.lastAutosave >= 1.0:
+ if os.path.exists(self.autosavePath):
+ os.remove(self.autosavePath)
+ self.createProjectFile(self.autosavePath)
+ self.lastAutosave = time.time()
def openInputFileDialog(self):
inputDir = self.settings.value("inputDir", expanduser("~"))
@@ -423,7 +426,7 @@ class Main(QtCore.QObject):
def moveComponentDown(self):
row = self.window.listWidget_componentList.currentRow()
- if row < len(self.pages) + 1:
+ if row != -1 and row < len(self.pages)+1:
module = self.selectedComponents[row]
self.selectedComponents.pop(row)
self.selectedComponents.insert(row + 1,module)
--
cgit v1.2.3
From 7946e98f222784e25ea9c6dc00713f431e238609 Mon Sep 17 00:00:00 2001
From: DH4
Date: Tue, 6 Jun 2017 01:57:48 -0500
Subject: When out of frames, send last frame to buffer. Added ability to loop
video.
---
components/video.py | 27 ++++++++++++++++++++++-----
1 file changed, 22 insertions(+), 5 deletions(-)
diff --git a/components/video.py b/components/video.py
index 0ae5348..3162279 100644
--- a/components/video.py
+++ b/components/video.py
@@ -6,15 +6,21 @@ from . import __base__
class Video:
'''Video Component Frame-Fetcher'''
- def __init__(self, ffmpeg, videoPath, width, height, frameRate, chunkSize, parent):
+ def __init__(self, ffmpeg, videoPath, width, height, frameRate, chunkSize, parent, loopVideo):
self.parent = parent
self.chunkSize = chunkSize
self.size = (width, height)
self.frameNo = -1
+ self.currentFrame = 'None'
+ if loopVideo:
+ self.loopValue = '-1'
+ else:
+ self.loopValue = '0'
self.command = [
ffmpeg,
'-thread_queue_size', '512',
'-r', frameRate,
+ '-stream_loop', self.loopValue,
'-i', videoPath,
'-f', 'image2pipe',
'-pix_fmt', 'rgba',
@@ -45,9 +51,17 @@ class Video:
if self.parent.canceled:
break
self.frameNo += 1
- image = self.pipe.stdout.read(self.chunkSize)
- print('creating frame #%s' % str(self.frameNo))
- self.frameBuffer.put((self.frameNo, image))
+
+ # If we run out of frames, use the last good frame and loop.
+ if len(self.currentFrame) == 0:
+ self.frameBuffer.put((self.frameNo-1, self.lastFrame))
+ continue
+
+ self.currentFrame = self.pipe.stdout.read(self.chunkSize)
+ #print('creating frame #%s' % str(self.frameNo))
+ if len(self.currentFrame) != 0:
+ self.frameBuffer.put((self.frameNo, self.currentFrame))
+ self.lastFrame = self.currentFrame
class Component(__base__.Component):
'''Video'''
@@ -58,15 +72,18 @@ class Component(__base__.Component):
self.videoPath = ''
self.x = 0
self.y = 0
+ self.loopVideo = False
page.lineEdit_video.textChanged.connect(self.update)
page.pushButton_video.clicked.connect(self.pickVideo)
+ page.checkBox_loop.stateChanged.connect(self.update)
self.page = page
return page
def update(self):
self.videoPath = self.page.lineEdit_video.text()
+ self.loopVideo = self.page.checkBox_loop.isChecked()
self.parent.drawPreview()
def previewRender(self, previewWorker):
@@ -82,7 +99,7 @@ class Component(__base__.Component):
self.chunkSize = 4*width*height
self.video = Video(self.parent.core.FFMPEG_BIN, self.videoPath,
width, height, self.settings.value("outputFrameRate"),
- self.chunkSize, self.parent)
+ self.chunkSize, self.parent, self.loopVideo)
def frameRender(self, moduleNo, arrayNo, frameNo):
return self.video.frame(frameNo)
--
cgit v1.2.3
From 0d1e7459e1f156eb2ac004ad87636d0bc39fcc6b Mon Sep 17 00:00:00 2001
From: DH4
Date: Tue, 6 Jun 2017 02:08:49 -0500
Subject: Revert default window size.
---
mainwindow.ui | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/mainwindow.ui b/mainwindow.ui
index b703997..d501110 100644
--- a/mainwindow.ui
+++ b/mainwindow.ui
@@ -6,8 +6,8 @@
0
0
- 1028
- 592
+ 1008
+ 575
--
cgit v1.2.3
From 6a1deb9b781890f9a62a6f6ce608856c1fc9e720 Mon Sep 17 00:00:00 2001
From: DH4
Date: Tue, 6 Jun 2017 04:04:42 -0500
Subject: Add checkerboard for transpart frames.
---
background.png | Bin 3711 -> 45367 bytes
preview_thread.py | 8 +++++++-
video_thread.py | 8 +++++++-
3 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/background.png b/background.png
index 7a33158..fb58593 100644
Binary files a/background.png and b/background.png differ
diff --git a/preview_thread.py b/preview_thread.py
index 5116707..04683ae 100644
--- a/preview_thread.py
+++ b/preview_thread.py
@@ -6,6 +6,8 @@ import core
import time
from queue import Queue, Empty
import numpy
+import os
+from copy import copy
class Worker(QtCore.QObject):
@@ -19,6 +21,9 @@ class Worker(QtCore.QObject):
self.queue = queue
self.core.settings = parent.settings
self.stackedWidget = parent.window.stackedWidget
+ self.background = Image.new("RGBA", (1920, 1080),(0,0,0,0))
+ self.background.paste(Image.open(os.path.join(os.path.dirname(os.path.realpath(__file__)),"background.png")))
+
@pyqtSlot(str, list)
@@ -41,7 +46,8 @@ class Worker(QtCore.QObject):
width = int(self.core.settings.value('outputWidth'))
height = int(self.core.settings.value('outputHeight'))
- frame = Image.new("RGBA", (width, height),(0,0,0,0))
+ frame = copy(self.background)
+ frame = frame.resize((width,height))
components = nextPreviewInformation["components"]
for component in reversed(components):
diff --git a/video_thread.py b/video_thread.py
index ac4162c..e880263 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -63,10 +63,16 @@ class Worker(QtCore.QObject):
self.bgI += 1
def previewDispatch(self):
+ background = Image.new("RGBA", (1920, 1080),(0,0,0,0))
+ background.paste(Image.open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "background.png")))
+ background = background.resize((self.width, self.height))
+
while not self.stopped:
i = self.previewQueue.get()
if time.time() - self.lastPreview >= 0.06 or i[0] == 0:
- self._image = ImageQt(i[1])
+ image = copy(background)
+ image = Image.alpha_composite(image, i[1])
+ self._image = ImageQt(image)
self.imageCreated.emit(QtGui.QImage(self._image))
self.lastPreview = time.time()
--
cgit v1.2.3
From 6c78c96d80618598692c121672f6d56e2feade5a Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 6 Jun 2017 08:55:22 -0400
Subject: fixed empty preview frame bug
---
components/video.py | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/components/video.py b/components/video.py
index 3162279..de91264 100644
--- a/components/video.py
+++ b/components/video.py
@@ -90,7 +90,11 @@ class Component(__base__.Component):
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
self.chunkSize = 4*width*height
- return self.getPreviewFrame(width, height)
+ frame = self.getPreviewFrame(width, height)
+ if not frame:
+ return Image.new("RGBA", (width, height),(0,0,0,0))
+ else:
+ return frame
def preFrameRender(self, **kwargs):
super().preFrameRender(**kwargs)
@@ -122,6 +126,8 @@ class Component(__base__.Component):
self.update()
def getPreviewFrame(self, width, height):
+ if not self.videoPath or not os.path.exists(self.videoPath):
+ return
command = [
self.parent.core.FFMPEG_BIN,
'-thread_queue_size', '512',
--
cgit v1.2.3
From 0948afd6e8b0cf29cf4bdf570e48350caa225c0a Mon Sep 17 00:00:00 2001
From: DH4
Date: Tue, 6 Jun 2017 08:53:27 -0500
Subject: Use bytes instead of encoding a PNG in text module.
---
components/text.py | 77 +++++++++++++++++++++++++++---------------------------
1 file changed, 39 insertions(+), 38 deletions(-)
diff --git a/components/text.py b/components/text.py
index d2bd684..56a9502 100644
--- a/components/text.py
+++ b/components/text.py
@@ -2,7 +2,8 @@ from PIL import Image, ImageDraw
from PyQt4.QtGui import QPainter, QColor, QFont
from PyQt4 import uic, QtGui, QtCore
from PIL.ImageQt import ImageQt
-import os, io
+import os
+import io
from . import __base__
@@ -16,21 +17,25 @@ class Component(__base__.Component):
height = int(parent.settings.value('outputHeight'))
width = int(parent.settings.value('outputWidth'))
self.parent = parent
- self.textColor = (255,255,255)
+ self.textColor = (255, 255, 255)
self.title = 'Text'
self.alignment = 1
self.fontSize = height / 13.5
self.xPosition = width / 2
self.yPosition = height / 2 * 1.036
-
- page = uic.loadUi(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'text.ui'))
+
+ page = uic.loadUi(os.path.join(
+ os.path.dirname(os.path.realpath(__file__)),
+ 'text.ui'
+ ))
page.comboBox_textAlign.addItem("Left")
page.comboBox_textAlign.addItem("Middle")
page.comboBox_textAlign.addItem("Right")
page.lineEdit_textColor.setText('%s,%s,%s' % self.textColor)
page.pushButton_textColor.clicked.connect(lambda: self.pickColor())
- btnStyle = "QPushButton { background-color : %s; outline: none; }" % QColor(*self.textColor).name()
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" \
+ % QColor(*self.textColor).name()
page.pushButton_textColor.setStyleSheet(btnStyle)
page.lineEdit_title.setText(self.title)
@@ -57,44 +62,46 @@ class Component(__base__.Component):
self.xPosition = self.page.spinBox_xTextAlign.value()
self.yPosition = self.page.spinBox_yTextAlign.value()
self.textColor = self.RGBFromString(self.page.lineEdit_textColor.text())
-
self.parent.drawPreview()
-
+
def getXY(self):
'''Returns true x, y after considering alignment settings'''
fm = QtGui.QFontMetrics(self.titleFont)
- if self.alignment == 0: #Left
- x = self.xPosition
- if self.alignment == 1: #Middle
- offset = fm.width(self.title)/2
- x = self.xPosition - offset
- if self.alignment == 2: #Right
- offset = fm.width(self.title)
- x = self.xPosition - offset
+ if self.alignment == 0: # Left
+ x = self.xPosition
+
+ if self.alignment == 1: # Middle
+ offset = fm.width(self.title)/2
+ x = self.xPosition - offset
+
+ if self.alignment == 2: # Right
+ offset = fm.width(self.title)
+ x = self.xPosition - offset
return x, self.yPosition
-
-
+
def loadPreset(self, pr):
self.page.lineEdit_title.setText(pr['title'])
- font = QFont(); font.fromString(pr['titleFont'])
+ font = QFont()
+ font.fromString(pr['titleFont'])
self.page.fontComboBox_titleFont.setCurrentFont(font)
self.page.spinBox_fontSize.setValue(pr['fontSize'])
self.page.comboBox_textAlign.setCurrentIndex(pr['alignment'])
self.page.spinBox_xTextAlign.setValue(pr['xPosition'])
self.page.spinBox_yTextAlign.setValue(pr['yPosition'])
self.page.lineEdit_textColor.setText('%s,%s,%s' % pr['textColor'])
- btnStyle = "QPushButton { background-color : %s; outline: none; }" % QColor(*pr['textColor']).name()
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" \
+ % QColor(*pr['textColor']).name()
self.page.pushButton_textColor.setStyleSheet(btnStyle)
-
+
def savePreset(self):
return {
- 'title' : self.title,
- 'titleFont' : self.titleFont.toString(),
- 'alignment' : self.alignment,
- 'fontSize' : self.fontSize,
- 'xPosition' : self.xPosition,
- 'yPosition' : self.yPosition,
- 'textColor' : self.textColor
+ 'title': self.title,
+ 'titleFont': self.titleFont.toString(),
+ 'alignment': self.alignment,
+ 'fontSize': self.fontSize,
+ 'xPosition': self.xPosition,
+ 'yPosition': self.yPosition,
+ 'textColor': self.textColor
}
def previewRender(self, previewWorker):
@@ -105,7 +112,7 @@ class Component(__base__.Component):
def preFrameRender(self, **kwargs):
super().preFrameRender(**kwargs)
return ['static']
-
+
def frameRender(self, moduleNo, arrayNo, frameNo):
width = int(self.worker.core.settings.value('outputWidth'))
height = int(self.worker.core.settings.value('outputHeight'))
@@ -113,9 +120,9 @@ class Component(__base__.Component):
def addText(self, width, height):
x, y = self.getXY()
- im = Image.new("RGBA", (width, height),(0,0,0,0))
+ im = Image.new("RGBA", (width, height), (0, 0, 0, 0))
image = ImageQt(im)
-
+
painter = QPainter(image)
self.titleFont.setPixelSize(self.fontSize)
painter.setFont(self.titleFont)
@@ -123,15 +130,9 @@ class Component(__base__.Component):
painter.drawText(x, y, self.title)
painter.end()
- buffer = QtCore.QBuffer()
- buffer.open(QtCore.QIODevice.ReadWrite)
- image.save(buffer, "PNG")
+ imBytes = image.bits().asstring(image.numBytes())
- strio = io.BytesIO()
- strio.write(buffer.data())
- buffer.close()
- strio.seek(0)
- return Image.open(strio)
+ return Image.frombytes('RGBA', (width, height), imBytes)
def pickColor(self):
RGBstring, btnStyle = super().pickColor()
--
cgit v1.2.3
From 231af74ea2b247bd73fcdfc44657b7fea2ab1620 Mon Sep 17 00:00:00 2001
From: DH4
Date: Tue, 6 Jun 2017 10:14:39 -0500
Subject: Code cleanup
---
command.py | 122 +++++++++
components/__base__.py | 44 ++--
components/color.py | 55 ++--
components/image.py | 24 +-
components/original.py | 78 ++++--
components/text.py | 23 +-
components/video.py | 74 ++++--
core.py | 203 ++++++++-------
main.py | 693 ++++---------------------------------------------
mainwindow.py | 586 +++++++++++++++++++++++++++++++++++++++++
preview_thread.py | 91 ++++---
video_thread.py | 68 +++--
12 files changed, 1126 insertions(+), 935 deletions(-)
create mode 100644 command.py
create mode 100644 mainwindow.py
diff --git a/command.py b/command.py
new file mode 100644
index 0000000..a610d8c
--- /dev/null
+++ b/command.py
@@ -0,0 +1,122 @@
+# FIXME: commandline functionality broken until we decide how to implement it
+'''
+class Command(QtCore.QObject):
+
+ videoTask = QtCore.pyqtSignal(str, str, str, list)
+
+ def __init__(self):
+ QtCore.QObject.__init__(self)
+ self.modules = []
+ self.selectedComponents = []
+
+ import argparse
+ self.parser = argparse.ArgumentParser(
+ description='Create a visualization for an audio file')
+ self.parser.add_argument(
+ '-i', '--input', dest='input', help='input audio file', required=True)
+ self.parser.add_argument(
+ '-o', '--output', dest='output',
+ help='output video file', required=True)
+ self.parser.add_argument(
+ '-b', '--background', dest='bgimage',
+ help='background image file', required=True)
+ self.parser.add_argument(
+ '-t', '--text', dest='text', help='title text', required=True)
+ self.parser.add_argument(
+ '-f', '--font', dest='font', help='title font', required=False)
+ self.parser.add_argument(
+ '-s', '--fontsize', dest='fontsize',
+ help='title font size', required=False)
+ self.parser.add_argument(
+ '-c', '--textcolor', dest='textcolor',
+ help='title text color in r,g,b format', required=False)
+ self.parser.add_argument(
+ '-C', '--viscolor', dest='viscolor',
+ help='visualization color in r,g,b format', required=False)
+ self.parser.add_argument(
+ '-x', '--xposition', dest='xposition',
+ help='x position', required=False)
+ self.parser.add_argument(
+ '-y', '--yposition', dest='yposition',
+ help='y position', required=False)
+ self.parser.add_argument(
+ '-a', '--alignment', dest='alignment',
+ help='title alignment', required=False,
+ type=int, choices=[0, 1, 2])
+ self.args = self.parser.parse_args()
+
+ self.settings = QSettings('settings.ini', QSettings.IniFormat)
+ LoadDefaultSettings(self)
+
+ # load colours as tuples from comma-separated strings
+ self.textColor = core.Core.RGBFromString(
+ self.settings.value("textColor", '255, 255, 255'))
+ self.visColor = core.Core.RGBFromString(
+ self.settings.value("visColor", '255, 255, 255'))
+ if self.args.textcolor:
+ self.textColor = core.Core.RGBFromString(self.args.textcolor)
+ if self.args.viscolor:
+ self.visColor = core.Core.RGBFromString(self.args.viscolor)
+
+ # font settings
+ if self.args.font:
+ self.font = QFont(self.args.font)
+ else:
+ self.font = QFont(self.settings.value("titleFont", QFont()))
+
+ if self.args.fontsize:
+ self.fontsize = int(self.args.fontsize)
+ else:
+ self.fontsize = int(self.settings.value("fontSize", 35))
+ if self.args.alignment:
+ self.alignment = int(self.args.alignment)
+ else:
+ self.alignment = int(self.settings.value("alignment", 0))
+
+ if self.args.xposition:
+ self.textX = int(self.args.xposition)
+ else:
+ self.textX = int(self.settings.value("xPosition", 70))
+
+ if self.args.yposition:
+ self.textY = int(self.args.yposition)
+ else:
+ self.textY = int(self.settings.value("yPosition", 375))
+
+ ffmpeg_cmd = self.settings.value("ffmpeg_cmd", expanduser("~"))
+
+ self.videoThread = QtCore.QThread(self)
+ self.videoWorker = video_thread.Worker(self)
+
+ self.videoWorker.moveToThread(self.videoThread)
+ self.videoWorker.videoCreated.connect(self.videoCreated)
+
+ self.videoThread.start()
+ self.videoTask.emit(self.args.bgimage,
+ self.args.text,
+ self.font,
+ self.fontsize,
+ self.alignment,
+ self.textX,
+ self.textY,
+ self.textColor,
+ self.visColor,
+ self.args.input,
+ self.args.output,
+ self.selectedComponents)
+
+ def videoCreated(self):
+ self.videoThread.quit()
+ self.videoThread.wait()
+ self.cleanUp()
+
+ def cleanUp(self):
+ self.settings.setValue("titleFont", self.font.toString())
+ self.settings.setValue("alignment", str(self.alignment))
+ self.settings.setValue("fontSize", str(self.fontsize))
+ self.settings.setValue("xPosition", str(self.textX))
+ self.settings.setValue("yPosition", str(self.textY))
+ self.settings.setValue("visColor", '%s,%s,%s' % self.visColor)
+ self.settings.setValue("textColor", '%s,%s,%s' % self.textColor)
+ sys.exit(0)
+'''
diff --git a/components/__base__.py b/components/__base__.py
index f564aad..94ac6f2 100644
--- a/components/__base__.py
+++ b/components/__base__.py
@@ -1,5 +1,6 @@
from PyQt4 import QtGui
+
class Component:
def __str__(self):
return self.__doc__
@@ -14,7 +15,7 @@ class Component:
def reset(self):
self.canceled = False
-
+
def preFrameRender(self, **kwargs):
for var, value in kwargs.items():
exec('self.%s = value' % var)
@@ -22,32 +23,35 @@ class Component:
def pickColor(self):
color = QtGui.QColorDialog.getColor()
if color.isValid():
- RGBstring = '%s,%s,%s' % (str(color.red()), str(color.green()), str(color.blue()))
- btnStyle = "QPushButton { background-color : %s; outline: none; }" % color.name()
+ RGBstring = '%s,%s,%s' % (
+ str(color.red()), str(color.green()), str(color.blue()))
+ btnStyle = "QPushButton{background-color: %s; outline: none;}" \
+ % color.name()
return RGBstring, btnStyle
else:
return None, None
def RGBFromString(self, string):
- ''' turns an RGB string like "255, 255, 255" into a tuple '''
- try:
- tup = tuple([int(i) for i in string.split(',')])
- if len(tup) != 3:
- raise ValueError
- for i in tup:
- if i > 255 or i < 0:
- raise ValueError
- return tup
- except:
- return (255, 255, 255)
+ ''' turns an RGB string like "255, 255, 255" into a tuple '''
+ try:
+ tup = tuple([int(i) for i in string.split(',')])
+ if len(tup) != 3:
+ raise ValueError
+ for i in tup:
+ if i > 255 or i < 0:
+ raise ValueError
+ return tup
+ except:
+ return (255, 255, 255)
'''
### Reference methods for creating a new component
### (Inherit from this class and define these)
-
+
def widget(self, parent):
self.parent = parent
- page = uic.loadUi(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'example.ui'))
+ page = uic.loadUi(os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), 'example.ui'))
# connect widgets signals
self.page = page
return page
@@ -55,13 +59,13 @@ class Component:
def update(self):
# read widget values
self.parent.drawPreview()
-
+
def previewRender(self, previewWorker):
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
image = Image.new("RGBA", (width, height), (0,0,0,0))
return image
-
+
def frameRender(self, moduleNo, frameNo):
width = int(self.worker.core.settings.value('outputWidth'))
height = int(self.worker.core.settings.value('outputHeight'))
@@ -70,10 +74,10 @@ class Component:
def loadPreset(self, presetDict):
# update widgets using a preset dict
-
+
def savePreset(self):
return {}
-
+
def cancel(self):
self.canceled = True
diff --git a/components/color.py b/components/color.py
index c2a49e2..b050fbd 100644
--- a/components/color.py
+++ b/components/color.py
@@ -4,31 +4,39 @@ from PyQt4.QtGui import QColor
import os
from . import __base__
+
class Component(__base__.Component):
'''Color'''
def widget(self, parent):
self.parent = parent
- page = uic.loadUi(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'color.ui'))
-
- self.color1 = (0,0,0)
- self.color2 = (133,133,133)
+ page = uic.loadUi(os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), 'color.ui'))
+
+ self.color1 = (0, 0, 0)
+ self.color2 = (133, 133, 133)
self.x = 0
self.y = 0
-
+
page.lineEdit_color1.setText('%s,%s,%s' % self.color1)
page.lineEdit_color2.setText('%s,%s,%s' % self.color2)
- page.pushButton_color1.clicked.connect(lambda: self.pickColor(1))
- btnStyle = "QPushButton { background-color : %s; outline: none; }" % QColor(*self.color1).name()
+
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" \
+ % QColor(*self.color1).name()
+
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" \
+ % QColor(*self.color2).name()
+
page.pushButton_color1.setStyleSheet(btnStyle)
- page.pushButton_color2.clicked.connect(lambda: self.pickColor(2))
- btnStyle = "QPushButton { background-color : %s; outline: none; }" % QColor(*self.color2).name()
page.pushButton_color2.setStyleSheet(btnStyle)
+ page.pushButton_color1.clicked.connect(lambda: self.pickColor(1))
+ page.pushButton_color2.clicked.connect(lambda: self.pickColor(2))
+
# disable color #2 until non-default 'fill' option gets changed
page.lineEdit_color2.setDisabled(True)
page.pushButton_color2.setDisabled(True)
page.spinBox_x.setValue(self.x)
page.spinBox_x.setValue(self.y)
-
+
page.lineEdit_color1.textChanged.connect(self.update)
page.lineEdit_color2.textChanged.connect(self.update)
page.spinBox_x.valueChanged.connect(self.update)
@@ -42,39 +50,44 @@ class Component(__base__.Component):
self.x = self.page.spinBox_x.value()
self.y = self.page.spinBox_y.value()
self.parent.drawPreview()
-
+
def previewRender(self, previewWorker):
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
return self.drawFrame(width, height)
-
+
def preFrameRender(self, **kwargs):
super().preFrameRender(**kwargs)
return ['static']
-
+
def frameRender(self, moduleNo, arrayNo, frameNo):
width = int(self.worker.core.settings.value('outputWidth'))
height = int(self.worker.core.settings.value('outputHeight'))
return self.drawFrame(width, height)
-
+
def drawFrame(self, width, height):
- r,g,b = self.color1
+ r, g, b = self.color1
return Image.new("RGBA", (width, height), (r, g, b, 255))
def loadPreset(self, pr):
self.page.lineEdit_color1.setText('%s,%s,%s' % pr['color1'])
self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2'])
- btnStyle = "QPushButton { background-color : %s; outline: none; }" % QColor(*pr['color1']).name()
+
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" \
+ % QColor(*pr['color1']).name()
+
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" \
+ % QColor(*pr['color2']).name()
+
self.page.pushButton_color1.setStyleSheet(btnStyle)
- btnStyle = "QPushButton { background-color : %s; outline: none; }" % QColor(*pr['color2']).name()
self.page.pushButton_color2.setStyleSheet(btnStyle)
-
+
def savePreset(self):
return {
- 'color1' : self.color1,
- 'color2' : self.color2,
+ 'color1': self.color1,
+ 'color2': self.color2,
}
-
+
def pickColor(self, num):
RGBstring, btnStyle = super().pickColor()
if not RGBstring:
diff --git a/components/image.py b/components/image.py
index ffbb117..f9a92ca 100644
--- a/components/image.py
+++ b/components/image.py
@@ -3,26 +3,28 @@ from PyQt4 import uic, QtGui, QtCore
import os
from . import __base__
+
class Component(__base__.Component):
'''Image'''
def widget(self, parent):
self.parent = parent
self.settings = parent.settings
- page = uic.loadUi(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'image.ui'))
+ page = uic.loadUi(os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), 'image.ui'))
self.imagePath = ''
self.x = 0
self.y = 0
-
+
page.lineEdit_image.textChanged.connect(self.update)
page.pushButton_image.clicked.connect(self.pickImage)
-
+
self.page = page
return page
def update(self):
self.imagePath = self.page.lineEdit_image.text()
self.parent.drawPreview()
-
+
def previewRender(self, previewWorker):
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
@@ -36,9 +38,9 @@ class Component(__base__.Component):
width = int(self.worker.core.settings.value('outputWidth'))
height = int(self.worker.core.settings.value('outputHeight'))
return self.drawFrame(width, height)
-
+
def drawFrame(self, width, height):
- frame = Image.new("RGBA", (width, height), (0,0,0,0))
+ frame = Image.new("RGBA", (width, height), (0, 0, 0, 0))
if self.imagePath and os.path.exists(self.imagePath):
image = Image.open(self.imagePath)
if image.size != (width, height):
@@ -48,16 +50,16 @@ class Component(__base__.Component):
def loadPreset(self, pr):
self.page.lineEdit_image.setText(pr['image'])
-
+
def savePreset(self):
return {
- 'image' : self.imagePath,
+ 'image': self.imagePath,
}
-
+
def pickImage(self):
imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
- filename = QtGui.QFileDialog.getOpenFileName(self.page,
- "Choose Image", imgDir, "Image Files (*.jpg *.png)")
+ filename = QtGui.QFileDialog.getOpenFileName(
+ self.page, "Choose Image", imgDir, "Image Files (*.jpg *.png)")
if filename:
self.settings.setValue("backgroundDir", os.path.dirname(filename))
self.page.lineEdit_image.setText(filename)
diff --git a/components/original.py b/components/original.py
index b0a7579..4d0e83b 100644
--- a/components/original.py
+++ b/components/original.py
@@ -2,7 +2,8 @@ import numpy
from PIL import Image, ImageDraw
from PyQt4 import uic, QtGui
from PyQt4.QtGui import QColor
-import os, random
+import os
+import random
from . import __base__
import time
from copy import copy
@@ -12,24 +13,25 @@ class Component(__base__.Component):
'''Original Audio Visualization'''
def widget(self, parent):
self.parent = parent
- self.visColor = (255,255,255)
+ self.visColor = (255, 255, 255)
- page = uic.loadUi(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'original.ui'))
+ page = uic.loadUi(os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), 'original.ui'))
page.comboBox_visLayout.addItem("Classic")
page.comboBox_visLayout.addItem("Split")
page.comboBox_visLayout.addItem("Bottom")
- #visLayoutValue = int(self.settings.value('visLayout'))
page.comboBox_visLayout.setCurrentIndex(0)
page.comboBox_visLayout.currentIndexChanged.connect(self.update)
page.lineEdit_visColor.setText('%s,%s,%s' % self.visColor)
page.pushButton_visColor.clicked.connect(lambda: self.pickColor())
- btnStyle = "QPushButton { background-color : %s; outline: none; }" % QColor(*self.visColor).name()
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" \
+ % QColor(*self.visColor).name()
page.pushButton_visColor.setStyleSheet(btnStyle)
page.lineEdit_visColor.textChanged.connect(self.update)
self.page = page
self.canceled = False
return page
-
+
def update(self):
self.layout = self.page.comboBox_visLayout.currentIndex()
self.visColor = self.RGBFromString(self.page.lineEdit_visColor.text())
@@ -37,21 +39,25 @@ class Component(__base__.Component):
def loadPreset(self, pr):
self.page.lineEdit_visColor.setText('%s,%s,%s' % pr['visColor'])
- btnStyle = "QPushButton { background-color : %s; outline: none; }" % QColor(*pr['visColor']).name()
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" \
+ % QColor(*pr['visColor']).name()
self.page.pushButton_visColor.setStyleSheet(btnStyle)
self.page.comboBox_visLayout.setCurrentIndex(pr['layout'])
-
+
def savePreset(self):
- return { 'layout' : self.layout,
- 'visColor' : self.visColor,
- }
+ return {
+ 'layout': self.layout,
+ 'visColor': self.visColor,
+ }
def previewRender(self, previewWorker):
- spectrum = numpy.fromfunction(lambda x: 0.008*(x-128)**2, (255,), dtype="int16")
+ spectrum = numpy.fromfunction(
+ lambda x: 0.008*(x-128)**2, (255,), dtype="int16")
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
- return self.drawBars(width, height, spectrum, self.visColor, self.layout)
-
+ return self.drawBars(
+ width, height, spectrum, self.visColor, self.layout)
+
def preFrameRender(self, **kwargs):
super().preFrameRender(**kwargs)
self.smoothConstantDown = 0.08
@@ -64,19 +70,24 @@ class Component(__base__.Component):
for i in range(0, len(self.completeAudioArray), self.sampleSize):
if self.canceled:
break
- self.lastSpectrum = self.transformData(i, self.completeAudioArray, self.sampleSize,
- self.smoothConstantDown, self.smoothConstantUp, self.lastSpectrum)
+ self.lastSpectrum = self.transformData(
+ i, self.completeAudioArray, self.sampleSize,
+ self.smoothConstantDown, self.smoothConstantUp,
+ self.lastSpectrum)
self.spectrumArray[i] = copy(self.lastSpectrum)
progress = int(100*(i/len(self.completeAudioArray)))
if progress >= 100:
progress = 100
- pStr = "Analyzing audio: "+ str(progress) +'%'
+ pStr = "Analyzing audio: "+str(progress)+'%'
self.progressBarSetText.emit(pStr)
self.progressBarUpdate.emit(int(progress))
-
+
def frameRender(self, moduleNo, arrayNo, frameNo):
- return self.drawBars(self.width, self.height, self.spectrumArray[arrayNo], self.visColor, self.layout)
+ return self.drawBars(
+ self.width, self.height,
+ self.spectrumArray[arrayNo],
+ self.visColor, self.layout)
def pickColor(self):
RGBstring, btnStyle = super().pickColor()
@@ -85,14 +96,17 @@ class Component(__base__.Component):
self.page.lineEdit_visColor.setText(RGBstring)
self.page.pushButton_visColor.setStyleSheet(btnStyle)
- def transformData(self, i, completeAudioArray, sampleSize, smoothConstantDown, smoothConstantUp, lastSpectrum):
+ def transformData(
+ self, i, completeAudioArray, sampleSize,
+ smoothConstantDown, smoothConstantUp, lastSpectrum):
if len(completeAudioArray) < (i + sampleSize):
sampleSize = len(completeAudioArray) - i
window = numpy.hanning(sampleSize)
data = completeAudioArray[i:i+sampleSize][::1] * window
paddedSampleSize = 2048
- paddedData = numpy.pad(data, (0, paddedSampleSize - sampleSize), 'constant')
+ 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)
@@ -106,8 +120,13 @@ class Component(__base__.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
@@ -120,7 +139,7 @@ class Component(__base__.Component):
bF = width / 64
bH = bF / 2
bQ = bF / 4
- imTop = Image.new("RGBA", (width, height),(0,0,0,0))
+ imTop = Image.new("RGBA", (width, height), (0, 0, 0, 0))
draw = ImageDraw.Draw(imTop)
r, g, b = color
color2 = (r, g, b, 125)
@@ -128,12 +147,17 @@ class Component(__base__.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)
+ 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)
- im = Image.new("RGBA", (width, height),(0,0,0,0))
+ im = Image.new("RGBA", (width, height), (0, 0, 0, 0))
if layout == 0:
y = 0 - int(height/100*43)
diff --git a/components/text.py b/components/text.py
index 56a9502..6cdc0dd 100644
--- a/components/text.py
+++ b/components/text.py
@@ -25,9 +25,7 @@ class Component(__base__.Component):
self.yPosition = height / 2 * 1.036
page = uic.loadUi(os.path.join(
- os.path.dirname(os.path.realpath(__file__)),
- 'text.ui'
- ))
+ os.path.dirname(os.path.realpath(__file__)), 'text.ui'))
page.comboBox_textAlign.addItem("Left")
page.comboBox_textAlign.addItem("Middle")
page.comboBox_textAlign.addItem("Right")
@@ -61,7 +59,8 @@ class Component(__base__.Component):
self.fontSize = self.page.spinBox_fontSize.value()
self.xPosition = self.page.spinBox_xTextAlign.value()
self.yPosition = self.page.spinBox_yTextAlign.value()
- self.textColor = self.RGBFromString(self.page.lineEdit_textColor.text())
+ self.textColor = self.RGBFromString(
+ self.page.lineEdit_textColor.text())
self.parent.drawPreview()
def getXY(self):
@@ -95,14 +94,14 @@ class Component(__base__.Component):
def savePreset(self):
return {
- 'title': self.title,
- 'titleFont': self.titleFont.toString(),
- 'alignment': self.alignment,
- 'fontSize': self.fontSize,
- 'xPosition': self.xPosition,
- 'yPosition': self.yPosition,
- 'textColor': self.textColor
- }
+ 'title': self.title,
+ 'titleFont': self.titleFont.toString(),
+ 'alignment': self.alignment,
+ 'fontSize': self.fontSize,
+ 'xPosition': self.xPosition,
+ 'yPosition': self.yPosition,
+ 'textColor': self.textColor
+ }
def previewRender(self, previewWorker):
width = int(previewWorker.core.settings.value('outputWidth'))
diff --git a/components/video.py b/components/video.py
index de91264..67a96dd 100644
--- a/components/video.py
+++ b/components/video.py
@@ -1,12 +1,18 @@
from PIL import Image, ImageDraw
from PyQt4 import uic, QtGui, QtCore
-import os, subprocess, threading
+import os
+import subprocess
+import threading
from queue import PriorityQueue
from . import __base__
+
class Video:
'''Video Component Frame-Fetcher'''
- def __init__(self, ffmpeg, videoPath, width, height, frameRate, chunkSize, parent, loopVideo):
+ def __init__(
+ self, ffmpeg, videoPath, width, height,
+ frameRate, chunkSize, parent, loopVideo):
+
self.parent = parent
self.chunkSize = chunkSize
self.size = (width, height)
@@ -27,15 +33,18 @@ class Video:
'-filter:v', 'scale='+str(width)+':'+str(height),
'-vcodec', 'rawvideo', '-',
]
-
+
self.frameBuffer = PriorityQueue()
self.frameBuffer.maxsize = int(frameRate)
self.finishedFrames = {}
-
- self.thread = threading.Thread(target=self.fillBuffer, name=self.__doc__)
+
+ self.thread = threading.Thread(
+ target=self.fillBuffer,
+ name=self.__doc__
+ )
self.thread.daemon = True
self.thread.start()
-
+
def frame(self, num):
while True:
if num in self.finishedFrames:
@@ -44,9 +53,12 @@ class Video:
i, image = self.frameBuffer.get()
self.finishedFrames[i] = image
self.frameBuffer.task_done()
-
- def fillBuffer(self):
- self.pipe = subprocess.Popen(self.command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, bufsize=10**8)
+
+ def fillBuffer(self):
+ self.pipe = subprocess.Popen(
+ self.command, stdout=subprocess.PIPE,
+ stderr=subprocess.DEVNULL, bufsize=10**8
+ )
while True:
if self.parent.canceled:
break
@@ -58,26 +70,29 @@ class Video:
continue
self.currentFrame = self.pipe.stdout.read(self.chunkSize)
- #print('creating frame #%s' % str(self.frameNo))
if len(self.currentFrame) != 0:
self.frameBuffer.put((self.frameNo, self.currentFrame))
self.lastFrame = self.currentFrame
+
class Component(__base__.Component):
'''Video'''
def widget(self, parent):
self.parent = parent
self.settings = parent.settings
- page = uic.loadUi(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'video.ui'))
+ page = uic.loadUi(os.path.join(
+ os.path.dirname(os.path.realpath(__file__)),
+ 'video.ui'
+ ))
self.videoPath = ''
self.x = 0
self.y = 0
self.loopVideo = False
-
+
page.lineEdit_video.textChanged.connect(self.update)
page.pushButton_video.clicked.connect(self.pickVideo)
page.checkBox_loop.stateChanged.connect(self.update)
-
+
self.page = page
return page
@@ -85,46 +100,50 @@ class Component(__base__.Component):
self.videoPath = self.page.lineEdit_video.text()
self.loopVideo = self.page.checkBox_loop.isChecked()
self.parent.drawPreview()
-
+
def previewRender(self, previewWorker):
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
self.chunkSize = 4*width*height
frame = self.getPreviewFrame(width, height)
if not frame:
- return Image.new("RGBA", (width, height),(0,0,0,0))
+ return Image.new("RGBA", (width, height), (0, 0, 0, 0))
else:
return frame
-
+
def preFrameRender(self, **kwargs):
super().preFrameRender(**kwargs)
width = int(self.worker.core.settings.value('outputWidth'))
height = int(self.worker.core.settings.value('outputHeight'))
self.chunkSize = 4*width*height
- self.video = Video(self.parent.core.FFMPEG_BIN, self.videoPath,
+ self.video = Video(
+ self.parent.core.FFMPEG_BIN, self.videoPath,
width, height, self.settings.value("outputFrameRate"),
- self.chunkSize, self.parent, self.loopVideo)
-
+ self.chunkSize, self.parent, self.loopVideo
+ )
+
def frameRender(self, moduleNo, arrayNo, frameNo):
return self.video.frame(frameNo)
def loadPreset(self, pr):
self.page.lineEdit_video.setText(pr['video'])
-
+
def savePreset(self):
return {
- 'video' : self.videoPath,
+ 'video': self.videoPath,
}
-
+
def pickVideo(self):
imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
- filename = QtGui.QFileDialog.getOpenFileName(self.page,
- "Choose Video", imgDir, "Video Files (*.mp4 *.mov)")
+ filename = QtGui.QFileDialog.getOpenFileName(
+ self.page, "Choose Video",
+ imgDir, "Video Files (*.mp4 *.mov)"
+ )
if filename:
self.settings.setValue("backgroundDir", os.path.dirname(filename))
self.page.lineEdit_video.setText(filename)
self.update()
-
+
def getPreviewFrame(self, width, height):
if not self.videoPath or not os.path.exists(self.videoPath):
return
@@ -139,7 +158,10 @@ class Component(__base__.Component):
'-ss', '90',
'-vframes', '1',
]
- pipe = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, bufsize=10**8)
+ pipe = subprocess.Popen(
+ command, stdout=subprocess.PIPE,
+ stderr=subprocess.DEVNULL, bufsize=10**8
+ )
byteFrame = pipe.stdout.read(self.chunkSize)
image = Image.frombytes('RGBA', (width, height), byteFrame)
pipe.stdout.close()
diff --git a/core.py b/core.py
index 99403f1..8ea884b 100644
--- a/core.py
+++ b/core.py
@@ -1,4 +1,6 @@
-import sys, io, os
+import sys
+import io
+import os
from PyQt4 import QtCore, QtGui, uic
from os.path import expanduser
import subprocess as sp
@@ -10,103 +12,106 @@ import atexit
import time
from collections import OrderedDict
+
class Core():
- def __init__(self):
- self.FFMPEG_BIN = self.findFfmpeg()
- self.tempDir = os.path.join(tempfile.gettempdir(), 'audio-visualizer-python-data')
- if not os.path.exists(self.tempDir):
- os.makedirs(self.tempDir)
- atexit.register(self.deleteTempDir)
-
- def findFfmpeg(self):
- if sys.platform == "win32":
- return "ffmpeg.exe"
- else:
- try:
- with open(os.devnull, "w") as f:
- sp.check_call(['ffmpeg', '-version'], stdout=f, stderr=f)
- return "ffmpeg"
- except:
- return "avconv"
-
- def readAudioFile(self, filename, parent):
- command = [ self.FFMPEG_BIN,
- '-i', filename]
-
- try:
- fileInfo = sp.check_output(command, stderr=sp.STDOUT, shell=False)
- except sp.CalledProcessError as ex:
- fileInfo = ex.output
- pass
-
- info = fileInfo.decode("utf-8").split('\n')
- for line in info:
- if 'Duration' in line:
- d = line.split(',')[0]
- d = d.split(' ')[3]
- d = d.split(':')
- duration = float(d[0])*3600 + float(d[1])*60 + float(d[2])
-
- command = [ self.FFMPEG_BIN,
- '-i', filename,
- '-f', 's16le',
- '-acodec', 'pcm_s16le',
- '-ar', '44100', # ouput will have 44100 Hz
- '-ac', '1', # mono (set to '2' for stereo)
- '-']
- in_pipe = sp.Popen(command, stdout=sp.PIPE, stderr=sp.DEVNULL, bufsize=10**8)
-
- completeAudioArray = numpy.empty(0, dtype="int16")
-
- progress = 0
- lastPercent = None
- while True:
- if self.canceled:
- break
- # read 2 seconds of audio
- progress = progress + 4
- raw_audio = in_pipe.stdout.read(88200*4)
- if len(raw_audio) == 0:
- break
- audio_array = numpy.fromstring(raw_audio, dtype="int16")
- completeAudioArray = numpy.append(completeAudioArray, audio_array)
-
- percent = int(100*(progress/duration))
- if percent >= 100:
- percent = 100
-
- if lastPercent != percent:
- string = 'Loading audio file: '+str(percent)+'%'
- parent.progressBarSetText.emit(string)
- parent.progressBarUpdate.emit(percent)
-
- lastPercent = percent
-
-
- in_pipe.kill()
- in_pipe.wait()
-
- # add 0s the end
- completeAudioArrayCopy = numpy.zeros(len(completeAudioArray) + 44100, dtype="int16")
- completeAudioArrayCopy[:len(completeAudioArray)] = completeAudioArray
- completeAudioArray = completeAudioArrayCopy
-
- return completeAudioArray
-
- def deleteTempDir(self):
- try:
- rmtree(self.tempDir)
- except FileNotFoundError:
- pass
-
- def cancel(self):
- self.canceled = True
-
- def reset(self):
- self.canceled = False
-
- @staticmethod
- def stringOrderedDict(dictionary):
- sorted_ = OrderedDict(sorted(dictionary.items(), key=lambda t: t[0]))
- return repr(sorted_)
+ def __init__(self):
+ self.FFMPEG_BIN = self.findFfmpeg()
+ self.tempDir = os.path.join(
+ tempfile.gettempdir(), 'audio-visualizer-python-data')
+ if not os.path.exists(self.tempDir):
+ os.makedirs(self.tempDir)
+ atexit.register(self.deleteTempDir)
+
+ def findFfmpeg(self):
+ if sys.platform == "win32":
+ return "ffmpeg.exe"
+ else:
+ try:
+ with open(os.devnull, "w") as f:
+ sp.check_call(['ffmpeg', '-version'], stdout=f, stderr=f)
+ return "ffmpeg"
+ except:
+ return "avconv"
+
+ def readAudioFile(self, filename, parent):
+ command = [self.FFMPEG_BIN, '-i', filename]
+
+ try:
+ fileInfo = sp.check_output(command, stderr=sp.STDOUT, shell=False)
+ except sp.CalledProcessError as ex:
+ fileInfo = ex.output
+ pass
+
+ info = fileInfo.decode("utf-8").split('\n')
+ for line in info:
+ if 'Duration' in line:
+ d = line.split(',')[0]
+ d = d.split(' ')[3]
+ d = d.split(':')
+ duration = float(d[0])*3600 + float(d[1])*60 + float(d[2])
+
+ command = [
+ self.FFMPEG_BIN,
+ '-i', filename,
+ '-f', 's16le',
+ '-acodec', 'pcm_s16le',
+ '-ar', '44100', # ouput will have 44100 Hz
+ '-ac', '1', # mono (set to '2' for stereo)
+ '-']
+ in_pipe = sp.Popen(
+ command, stdout=sp.PIPE, stderr=sp.DEVNULL, bufsize=10**8)
+
+ completeAudioArray = numpy.empty(0, dtype="int16")
+
+ progress = 0
+ lastPercent = None
+ while True:
+ if self.canceled:
+ break
+ # read 2 seconds of audio
+ progress = progress + 4
+ raw_audio = in_pipe.stdout.read(88200*4)
+ if len(raw_audio) == 0:
+ break
+ audio_array = numpy.fromstring(raw_audio, dtype="int16")
+ completeAudioArray = numpy.append(completeAudioArray, audio_array)
+
+ percent = int(100*(progress/duration))
+ if percent >= 100:
+ percent = 100
+
+ if lastPercent != percent:
+ string = 'Loading audio file: '+str(percent)+'%'
+ parent.progressBarSetText.emit(string)
+ parent.progressBarUpdate.emit(percent)
+
+ lastPercent = percent
+
+ in_pipe.kill()
+ in_pipe.wait()
+
+ # add 0s the end
+ completeAudioArrayCopy = numpy.zeros(
+ len(completeAudioArray) + 44100, dtype="int16")
+ completeAudioArrayCopy[:len(completeAudioArray)] = completeAudioArray
+ completeAudioArray = completeAudioArrayCopy
+
+ return completeAudioArray
+
+ def deleteTempDir(self):
+ try:
+ rmtree(self.tempDir)
+ except FileNotFoundError:
+ pass
+
+ def cancel(self):
+ self.canceled = True
+
+ def reset(self):
+ self.canceled = False
+
+ @staticmethod
+ def stringOrderedDict(dictionary):
+ sorted_ = OrderedDict(sorted(dictionary.items(), key=lambda t: t[0]))
+ return repr(sorted_)
diff --git a/main.py b/main.py
index c75a7f7..78c1d9b 100644
--- a/main.py
+++ b/main.py
@@ -1,668 +1,67 @@
-import sys, io, os, shutil, atexit, string, signal, filecmp, time
-from os.path import expanduser
-from queue import Queue
from importlib import import_module
from collections import OrderedDict
-from PyQt4 import QtCore, QtGui, uic
-from PyQt4.QtCore import QSettings, QModelIndex, Qt
-from PyQt4.QtGui import QDesktopServices, QMenu
+from PyQt4 import QtGui, uic
+from PyQt4.QtCore import Qt
+import sys
+import io
+import os
+import atexit
+import signal
-import preview_thread, core, video_thread
+import core
+import preview_thread
+import video_thread
+from mainwindow import *
-# FIXME: commandline functionality broken until we decide how to implement it
-'''
-class Command(QtCore.QObject):
-
- videoTask = QtCore.pyqtSignal(str, str, str, list)
-
- def __init__(self):
- QtCore.QObject.__init__(self)
- self.modules = []
- self.selectedComponents = []
-
- import argparse
- self.parser = argparse.ArgumentParser(description='Create a visualization for an audio file')
- self.parser.add_argument('-i', '--input', dest='input', help='input audio file', required=True)
- self.parser.add_argument('-o', '--output', dest='output', help='output video file', required=True)
- self.parser.add_argument('-b', '--background', dest='bgimage', help='background image file', required=True)
- self.parser.add_argument('-t', '--text', dest='text', help='title text', required=True)
- self.parser.add_argument('-f', '--font', dest='font', help='title font', required=False)
- self.parser.add_argument('-s', '--fontsize', dest='fontsize', help='title font size', required=False)
- self.parser.add_argument('-c', '--textcolor', dest='textcolor', help='title text color in r,g,b format', required=False)
- self.parser.add_argument('-C', '--viscolor', dest='viscolor', help='visualization color in r,g,b format', required=False)
- self.parser.add_argument('-x', '--xposition', dest='xposition', help='x position', required=False)
- self.parser.add_argument('-y', '--yposition', dest='yposition', help='y position', required=False)
- self.parser.add_argument('-a', '--alignment', dest='alignment', help='title alignment', required=False, type=int, choices=[0, 1, 2])
- self.args = self.parser.parse_args()
-
- self.settings = QSettings('settings.ini', QSettings.IniFormat)
- LoadDefaultSettings(self)
-
- # load colours as tuples from comma-separated strings
- self.textColor = core.Core.RGBFromString(self.settings.value("textColor", '255, 255, 255'))
- self.visColor = core.Core.RGBFromString(self.settings.value("visColor", '255, 255, 255'))
- if self.args.textcolor:
- self.textColor = core.Core.RGBFromString(self.args.textcolor)
- if self.args.viscolor:
- self.visColor = core.Core.RGBFromString(self.args.viscolor)
-
- # font settings
- if self.args.font:
- self.font = QFont(self.args.font)
- else:
- self.font = QFont(self.settings.value("titleFont", QFont()))
-
- if self.args.fontsize:
- self.fontsize = int(self.args.fontsize)
- else:
- self.fontsize = int(self.settings.value("fontSize", 35))
- if self.args.alignment:
- self.alignment = int(self.args.alignment)
- else:
- self.alignment = int(self.settings.value("alignment", 0))
-
- if self.args.xposition:
- self.textX = int(self.args.xposition)
- else:
- self.textX = int(self.settings.value("xPosition", 70))
-
- if self.args.yposition:
- self.textY = int(self.args.yposition)
- else:
- self.textY = int(self.settings.value("yPosition", 375))
-
- ffmpeg_cmd = self.settings.value("ffmpeg_cmd", expanduser("~"))
-
- self.videoThread = QtCore.QThread(self)
- self.videoWorker = video_thread.Worker(self)
-
- self.videoWorker.moveToThread(self.videoThread)
- self.videoWorker.videoCreated.connect(self.videoCreated)
-
- self.videoThread.start()
- self.videoTask.emit(self.args.bgimage,
- self.args.text,
- self.font,
- self.fontsize,
- self.alignment,
- self.textX,
- self.textY,
- self.textColor,
- self.visColor,
- self.args.input,
- self.args.output,
- self.selectedComponents)
-
- def videoCreated(self):
- self.videoThread.quit()
- self.videoThread.wait()
- self.cleanUp()
-
- def cleanUp(self):
- self.settings.setValue("titleFont", self.font.toString())
- self.settings.setValue("alignment", str(self.alignment))
- self.settings.setValue("fontSize", str(self.fontsize))
- self.settings.setValue("xPosition", str(self.textX))
- self.settings.setValue("yPosition", str(self.textY))
- self.settings.setValue("visColor", '%s,%s,%s' % self.visColor)
- self.settings.setValue("textColor", '%s,%s,%s' % self.textColor)
- sys.exit(0)
-'''
-
-class PreviewWindow(QtGui.QLabel):
- def __init__(self, parent, img):
- super(PreviewWindow, self).__init__()
- self.parent = parent
- self.setFrameStyle(QtGui.QFrame.StyledPanel)
- self.pixmap = QtGui.QPixmap(img)
-
- def paintEvent(self, event):
- size = self.size()
- painter = QtGui.QPainter(self)
- point = QtCore.QPoint(0,0)
- scaledPix = self.pixmap.scaled(size, Qt.KeepAspectRatio, transformMode = Qt.SmoothTransformation)
- # start painting the label from left upper corner
- point.setX((size.width() - scaledPix.width())/2)
- point.setY((size.height() - scaledPix.height())/2)
- #print point.x(), ' ', point.y()
- painter.drawPixmap(point, scaledPix)
-
- def changePixmap(self, img):
- self.pixmap = QtGui.QPixmap(img)
- self.repaint()
-
-class Main(QtCore.QObject):
-
- newTask = QtCore.pyqtSignal(list)
- processTask = QtCore.pyqtSignal()
- videoTask = QtCore.pyqtSignal(str, str, list)
-
- def __init__(self, window):
- QtCore.QObject.__init__(self)
-
- # print('main thread id: {}'.format(QtCore.QThread.currentThreadId()))
- self.window = window
- self.core = core.Core()
- self.pages = []
- self.selectedComponents = []
- self.lastAutosave = time.time()
-
- # create data directory, load/create settings
- self.dataDir = QDesktopServices.storageLocation(QDesktopServices.DataLocation)
- self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
- self.presetDir = os.path.join(self.dataDir, 'presets')
- self.settings = QSettings(os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat)
- LoadDefaultSettings(self)
- if not os.path.exists(self.dataDir):
- os.makedirs(self.dataDir)
- for neededDirectory in (self.presetDir, self.settings.value("projectDir")):
- if not os.path.exists(neededDirectory):
- os.mkdir(neededDirectory)
-
- #
- self.previewQueue = Queue()
- self.previewThread = QtCore.QThread(self)
- self.previewWorker = preview_thread.Worker(self, self.previewQueue)
- self.previewWorker.moveToThread(self.previewThread)
- self.previewWorker.imageCreated.connect(self.showPreviewImage)
- self.previewThread.start()
-
- self.timer = QtCore.QTimer(self)
- self.timer.timeout.connect(self.processTask.emit)
- self.timer.start(500)
-
- # begin decorating the window and connecting events
- window.toolButton_selectAudioFile.clicked.connect(self.openInputFileDialog)
- window.toolButton_selectOutputFile.clicked.connect(self.openOutputFileDialog)
- window.progressBar_createVideo.setValue(0)
- window.pushButton_createVideo.clicked.connect(self.createAudioVisualisation)
- window.pushButton_Cancel.clicked.connect(self.stopVideo)
- window.setWindowTitle("Audio Visualizer")
-
- self.previewWindow = PreviewWindow(self, os.path.join(os.path.dirname(os.path.realpath(__file__)), "background.png"))
- window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
-
- self.modules = self.findComponents()
- self.compMenu = QMenu()
- for i, comp in enumerate(self.modules):
- action = self.compMenu.addAction(comp.Component.__doc__)
- action.triggered[()].connect( lambda item=i: self.insertComponent(item))
-
- self.window.pushButton_addComponent.setMenu(self.compMenu)
- window.listWidget_componentList.clicked.connect(lambda _: self.changeComponentWidget())
-
- self.window.pushButton_removeComponent.clicked.connect(lambda _: self.removeComponent())
-
- currentRes = str(self.settings.value('outputWidth'))+'x'+str(self.settings.value('outputHeight'))
- for i, res in enumerate(self.resolutions):
- window.comboBox_resolution.addItem(res)
- if res == currentRes:
- currentRes = i
- window.comboBox_resolution.setCurrentIndex(currentRes)
- window.comboBox_resolution.currentIndexChanged.connect(self.updateResolution)
-
- self.window.pushButton_listMoveUp.clicked.connect(self.moveComponentUp)
- self.window.pushButton_listMoveDown.clicked.connect(self.moveComponentDown)
-
- self.window.pushButton_savePreset.clicked.connect(self.openSavePresetDialog)
- self.window.comboBox_openPreset.currentIndexChanged.connect(self.openPreset)
- self.window.pushButton_saveAs.clicked.connect(self.openSaveProjectDialog)
- self.window.pushButton_saveProject.clicked.connect(self.saveCurrentProject)
- self.window.pushButton_openProject.clicked.connect(self.openOpenProjectDialog)
-
- # show the window and load current project
- window.show()
- self.currentProject = self.settings.value("currentProject")
- if self.currentProject and os.path.exists(self.autosavePath) \
- and filecmp.cmp(self.autosavePath, self.currentProject):
- # delete autosave if it's identical to the project
- os.remove(self.autosavePath)
-
- if self.currentProject and os.path.exists(self.autosavePath):
- ch = self.showMessage("Restore unsaved changes in project '%s'?" % os.path.basename(self.currentProject)[:-4], True)
- if ch:
- os.remove(self.currentProject)
- os.rename(self.autosavePath, self.currentProject)
- else:
- os.remove(self.autosavePath)
-
- self.openProject(self.currentProject)
- self.drawPreview()
-
- def cleanUp(self):
- self.timer.stop()
- self.previewThread.quit()
- self.previewThread.wait()
- self.autosave()
-
- def autosave(self):
- if time.time() - self.lastAutosave >= 1.0:
- if os.path.exists(self.autosavePath):
- os.remove(self.autosavePath)
- self.createProjectFile(self.autosavePath)
- self.lastAutosave = time.time()
-
- def openInputFileDialog(self):
- inputDir = self.settings.value("inputDir", expanduser("~"))
-
- fileName = QtGui.QFileDialog.getOpenFileName(self.window,
- "Open Music File", inputDir, "Music Files (*.mp3 *.wav *.ogg *.fla *.aac)");
-
- if not fileName == "":
- self.settings.setValue("inputDir", os.path.dirname(fileName))
- self.window.lineEdit_audioFile.setText(fileName)
-
- def openOutputFileDialog(self):
- outputDir = self.settings.value("outputDir", expanduser("~"))
-
- fileName = QtGui.QFileDialog.getSaveFileName(self.window,
- "Set Output Video File", outputDir, "Video Files (*.mp4 *.mov *.mkv *.avi *.webm *.flv)");
-
- if not fileName == "":
- self.settings.setValue("outputDir", os.path.dirname(fileName))
- self.window.lineEdit_outputFile.setText(fileName)
-
- def stopVideo(self):
- print('stop')
- self.videoWorker.cancel()
- self.canceled = True
-
- def createAudioVisualisation(self):
- # create output video if mandatory settings are filled in
- if self.window.lineEdit_audioFile.text() and self.window.lineEdit_outputFile.text():
- self.canceled = False
- self.progressBarUpdated(-1)
- ffmpeg_cmd = self.settings.value("ffmpeg_cmd", expanduser("~"))
- self.videoThread = QtCore.QThread(self)
- self.videoWorker = video_thread.Worker(self)
- self.videoWorker.moveToThread(self.videoThread)
- self.videoWorker.videoCreated.connect(self.videoCreated)
- self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated)
- self.videoWorker.progressBarSetText.connect(self.progressBarSetText)
- self.videoWorker.imageCreated.connect(self.showPreviewImage)
- self.videoWorker.encoding.connect(self.changeEncodingStatus)
- self.videoThread.start()
- self.videoTask.emit(self.window.lineEdit_audioFile.text(),
- self.window.lineEdit_outputFile.text(),
- self.selectedComponents)
- else:
- self.showMessage("You must select an audio file and output filename.")
-
- def progressBarUpdated(self, value):
- self.window.progressBar_createVideo.setValue(value)
-
- def changeEncodingStatus(self, status):
- if status:
- self.window.pushButton_createVideo.setEnabled(False)
- self.window.pushButton_Cancel.setEnabled(True)
- self.window.comboBox_resolution.setEnabled(False)
- self.window.stackedWidget.setEnabled(False)
- self.window.tab_encoderSettings.setEnabled(False)
- self.window.label_audioFile.setEnabled(False)
- self.window.toolButton_selectAudioFile.setEnabled(False)
- self.window.label_outputFile.setEnabled(False)
- self.window.toolButton_selectOutputFile.setEnabled(False)
- self.window.lineEdit_audioFile.setEnabled(False)
- self.window.lineEdit_outputFile.setEnabled(False)
- self.window.pushButton_addComponent.setEnabled(False)
- self.window.pushButton_removeComponent.setEnabled(False)
- self.window.pushButton_listMoveDown.setEnabled(False)
- self.window.pushButton_listMoveUp.setEnabled(False)
- self.window.comboBox_openPreset.setEnabled(False)
- self.window.pushButton_removePreset.setEnabled(False)
- self.window.pushButton_savePreset.setEnabled(False)
- self.window.pushButton_openProject.setEnabled(False)
- self.window.listWidget_componentList.setEnabled(False)
- else:
- self.window.pushButton_createVideo.setEnabled(True)
- self.window.pushButton_Cancel.setEnabled(False)
- self.window.comboBox_resolution.setEnabled(True)
- self.window.stackedWidget.setEnabled(True)
- self.window.tab_encoderSettings.setEnabled(True)
- self.window.label_audioFile.setEnabled(True)
- self.window.toolButton_selectAudioFile.setEnabled(True)
- self.window.lineEdit_audioFile.setEnabled(True)
- self.window.label_outputFile.setEnabled(True)
- self.window.toolButton_selectOutputFile.setEnabled(True)
- self.window.lineEdit_outputFile.setEnabled(True)
- self.window.pushButton_addComponent.setEnabled(True)
- self.window.pushButton_removeComponent.setEnabled(True)
- self.window.pushButton_listMoveDown.setEnabled(True)
- self.window.pushButton_listMoveUp.setEnabled(True)
- self.window.comboBox_openPreset.setEnabled(True)
- self.window.pushButton_removePreset.setEnabled(True)
- self.window.pushButton_savePreset.setEnabled(True)
- self.window.pushButton_openProject.setEnabled(True)
- self.window.listWidget_componentList.setEnabled(True)
-
- def progressBarSetText(self, value):
- self.window.progressBar_createVideo.setFormat(value)
-
- def videoCreated(self):
- self.videoThread.quit()
- self.videoThread.wait()
-
- def updateResolution(self):
- resIndex = int(window.comboBox_resolution.currentIndex())
- res = self.resolutions[resIndex].split('x')
- self.settings.setValue('outputWidth',res[0])
- self.settings.setValue('outputHeight',res[1])
- self.drawPreview()
-
- def drawPreview(self):
- self.newTask.emit(self.selectedComponents)
- # self.processTask.emit()
- self.autosave()
-
- def showPreviewImage(self, image):
- self.previewWindow.changePixmap(image)
-
- def findComponents(self):
- def findComponents():
- srcPath = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'components')
- if os.path.exists(srcPath):
- for f in sorted(os.listdir(srcPath)):
- name, ext = os.path.splitext(f)
- if name.startswith("__"):
- continue
- elif ext == '.py':
- yield name
- return [import_module('components.%s' % name) for name in findComponents()]
-
- def addComponent(self, moduleIndex):
- index = len(self.pages)
- self.selectedComponents.append(self.modules[moduleIndex].Component())
- self.window.listWidget_componentList.addItem(self.selectedComponents[-1].__doc__)
- self.pages.append(self.selectedComponents[-1].widget(self))
- self.window.listWidget_componentList.setCurrentRow(index)
- self.window.stackedWidget.addWidget(self.pages[-1])
- self.window.stackedWidget.setCurrentIndex(index)
- self.selectedComponents[-1].update()
- self.updateOpenPresetComboBox(self.selectedComponents[-1])
-
- def insertComponent(self, moduleIndex):
- self.selectedComponents.insert(0, self.modules[moduleIndex].Component())
- self.window.listWidget_componentList.insertItem(0, self.selectedComponents[0].__doc__)
- self.pages.insert(0, self.selectedComponents[0].widget(self))
- self.window.listWidget_componentList.setCurrentRow(0)
- self.window.stackedWidget.insertWidget(0, self.pages[0])
- self.window.stackedWidget.setCurrentIndex(0)
- self.selectedComponents[0].update()
- self.updateOpenPresetComboBox(self.selectedComponents[0])
-
- def removeComponent(self):
- for selected in self.window.listWidget_componentList.selectedItems():
- index = self.window.listWidget_componentList.row(selected)
- self.window.stackedWidget.removeWidget(self.pages[index])
- self.window.listWidget_componentList.takeItem(index)
- self.selectedComponents.pop(index)
- self.pages.pop(index)
- self.changeComponentWidget()
- self.drawPreview()
-
- def changeComponentWidget(self):
- selected = self.window.listWidget_componentList.selectedItems()
- if selected:
- index = self.window.listWidget_componentList.row(selected[0])
- self.window.stackedWidget.setCurrentIndex(index)
- self.updateOpenPresetComboBox(self.selectedComponents[index])
-
- def moveComponentUp(self):
- row = self.window.listWidget_componentList.currentRow()
- if row > 0:
- module = self.selectedComponents[row]
- self.selectedComponents.pop(row)
- self.selectedComponents.insert(row - 1,module)
- page = self.pages[row]
- self.pages.pop(row)
- self.pages.insert(row - 1, page)
- item = self.window.listWidget_componentList.takeItem(row)
- self.window.listWidget_componentList.insertItem(row - 1, item)
- widget = self.window.stackedWidget.removeWidget(page)
- self.window.stackedWidget.insertWidget(row - 1, page)
- self.window.listWidget_componentList.setCurrentRow(row - 1)
- self.window.stackedWidget.setCurrentIndex(row -1)
- self.drawPreview()
-
- def moveComponentDown(self):
- row = self.window.listWidget_componentList.currentRow()
- if row != -1 and row < len(self.pages)+1:
- module = self.selectedComponents[row]
- self.selectedComponents.pop(row)
- self.selectedComponents.insert(row + 1,module)
- page = self.pages[row]
- self.pages.pop(row)
- self.pages.insert(row + 1, page)
- item = self.window.listWidget_componentList.takeItem(row)
- self.window.listWidget_componentList.insertItem(row + 1, item)
- widget = self.window.stackedWidget.removeWidget(page)
- self.window.stackedWidget.insertWidget(row + 1, page)
- self.window.listWidget_componentList.setCurrentRow(row + 1)
- self.window.stackedWidget.setCurrentIndex(row + 1)
- self.drawPreview()
-
- def updateOpenPresetComboBox(self, component):
- self.window.comboBox_openPreset.clear()
- self.window.comboBox_openPreset.addItem("Component Presets")
- destination = os.path.join(self.presetDir,
- str(component).strip(), str(component.version()))
- if not os.path.exists(destination):
- os.makedirs(destination)
- for f in os.listdir(destination):
- self.window.comboBox_openPreset.addItem(f)
-
- def openSavePresetDialog(self):
- if self.window.listWidget_componentList.currentRow() == -1:
- return
- while True:
- newName, OK = QtGui.QInputDialog.getText(QtGui.QWidget(), 'Audio Visualizer', 'New Preset Name:')
- badName = False
- for letter in newName:
- if letter in string.punctuation:
- badName = True
- if badName:
- # some filesystems don't like bizarre characters
- self.showMessage("Preset names must contain only letters, numbers, and spaces.")
- continue
- if OK and newName:
- index = self.window.listWidget_componentList.currentRow()
- if index != -1:
- saveValueStore = self.selectedComponents[index].savePreset()
- componentName = str(self.selectedComponents[index]).strip()
- vers = self.selectedComponents[index].version()
- self.createPresetFile(componentName, vers, saveValueStore, newName)
- break
-
- def createPresetFile(self, componentName, version, saveValueStore, filename):
- dirname = os.path.join(self.presetDir, componentName, str(version))
- if not os.path.exists(dirname):
- os.makedirs(dirname)
- filepath = os.path.join(dirname, filename)
- if os.path.exists(filepath):
- ch = self.showMessage("%s already exists! Overwrite it?" % filename, True, QtGui.QMessageBox.Warning)
- if not ch:
- return
- # remove old copies of the preset
- for i in range(0, self.window.comboBox_openPreset.count()):
- if self.window.comboBox_openPreset.itemText(i) == filename:
- self.window.comboBox_openPreset.removeItem(i)
- with open(filepath, 'w') as f:
- f.write(core.Core.stringOrderedDict(saveValueStore))
- self.window.comboBox_openPreset.addItem(filename)
- self.window.comboBox_openPreset.setCurrentIndex(self.window.comboBox_openPreset.count()-1)
-
- def openPreset(self):
- if self.window.comboBox_openPreset.currentIndex() < 1:
- return
- index = self.window.listWidget_componentList.currentRow()
- if index == -1:
- return
- filename = self.window.comboBox_openPreset.itemText(self.window.comboBox_openPreset.currentIndex())
- componentName = str(self.selectedComponents[index]).strip()
- version = self.selectedComponents[index].version()
- dirname = os.path.join(self.presetDir, componentName, str(version))
- filepath = os.path.join(dirname, filename)
- if not os.path.exists(filepath):
- self.window.comboBox_openPreset.removeItem(self.window.comboBox_openPreset.currentIndex())
- return
- with open(filepath, 'r') as f:
- for line in f:
- saveValueStore = dict(eval(line.strip()))
- break
- self.selectedComponents[index].loadPreset(saveValueStore)
- self.drawPreview()
-
- def saveCurrentProject(self):
- if self.currentProject:
- self.createProjectFile(self.currentProject)
- else:
- self.openSaveProjectDialog()
-
- def openSaveProjectDialog(self):
- filename = QtGui.QFileDialog.getSaveFileName(self.window,
- "Create Project File", self.settings.value("projectDir"),
- "Project Files (*.avp)")
- if not filename:
- return
- self.createProjectFile(filename)
-
- def createProjectFile(self, filepath):
- if not filepath.endswith(".avp"):
- filepath += '.avp'
- with open(filepath, 'w') as f:
- print('creating %s' % filepath)
- f.write('[Components]\n')
- for comp in self.selectedComponents:
- saveValueStore = comp.savePreset()
- f.write('%s\n' % str(comp))
- f.write('%s\n' % str(comp.version()))
- f.write('%s\n' % core.Core.stringOrderedDict(saveValueStore))
- if filepath != self.autosavePath:
- self.settings.setValue("projectDir", os.path.dirname(filepath))
- self.settings.setValue("currentProject", filepath)
- self.currentProject = filepath
-
- def openOpenProjectDialog(self):
- filename = QtGui.QFileDialog.getOpenFileName(self.window,
- "Open Project File", self.settings.value("projectDir"),
- "Project Files (*.avp)")
- self.openProject(filename)
-
- def openProject(self, filepath):
- if not filepath or not os.path.exists(filepath) or not filepath.endswith('.avp'):
- return
- self.clear()
- self.currentProject = filepath
- self.settings.setValue("currentProject", filepath)
- self.settings.setValue("projectDir", os.path.dirname(filepath))
- compNames = [mod.Component.__doc__ for mod in self.modules]
- try:
- with open(filepath, 'r') as f:
- validSections = ('Components')
- section = ''
- def parseLine(line):
- line = line.strip()
- newSection = ''
- if line.startswith('[') and line.endswith(']') and line[1:-1] in validSections:
- newSection = line[1:-1]
- return line, newSection
-
- i = 0
- for line in f:
- line, newSection = parseLine(line)
- if newSection:
- section = str(newSection)
- continue
- if line and section == 'Components':
- if i == 0:
- compIndex = compNames.index(line)
- self.addComponent(compIndex)
- i += 1
- elif i == 1:
- # version, not used yet
- i += 1
- elif i == 2:
- saveValueStore = dict(eval(line))
- self.selectedComponents[-1].loadPreset(saveValueStore)
- i = 0
- except (IndexError, ValueError, KeyError, NameError, SyntaxError, AttributeError, TypeError) as e:
- self.clear()
- typ, value, _ = sys.exc_info()
- msg = '%s: %s' % (typ.__name__, value)
- self.showMessage("Project file '%s' is corrupted." % filepath, False,
- QtGui.QMessageBox.Warning, msg)
-
- def showMessage(self, string, showCancel=False, icon=QtGui.QMessageBox.Information, detail=None):
- msg = QtGui.QMessageBox()
- msg.setIcon(icon)
- msg.setText(string)
- msg.setDetailedText(detail)
- if showCancel:
- msg.setStandardButtons(QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel)
- else:
- msg.setStandardButtons(QtGui.QMessageBox.Ok)
- ch = msg.exec_()
- if ch == 1024:
- return True
- return False
-
- def clear(self):
- ''' empty out all components and fields, get a blank slate '''
- self.selectedComponents = []
- self.window.listWidget_componentList.clear()
- for widget in self.pages:
- self.window.stackedWidget.removeWidget(widget)
- self.pages = []
def LoadDefaultSettings(self):
- self.resolutions = [
- '1920x1080',
- '1280x720',
- '854x480'
+ self.resolutions = [
+ '1920x1080',
+ '1280x720',
+ '854x480'
]
- default = {
- "outputWidth": 1280,
- "outputHeight": 720,
- "outputFrameRate": 30,
- "outputAudioCodec": "aac",
- "outputAudioBitrate": "192k",
- "outputVideoCodec": "libx264",
- "outputVideoFormat": "yuv420p",
- "outputPreset": "medium",
- "outputFormat": "mp4",
- "projectDir" : os.path.join(self.dataDir, 'projects'),
- }
-
- for parm, value in default.items():
- if self.settings.value(parm) == None:
- self.settings.setValue(parm,value)
-
+ default = {
+ "outputWidth": 1280,
+ "outputHeight": 720,
+ "outputFrameRate": 30,
+ "outputAudioCodec": "aac",
+ "outputAudioBitrate": "192k",
+ "outputVideoCodec": "libx264",
+ "outputVideoFormat": "yuv420p",
+ "outputPreset": "medium",
+ "outputFormat": "mp4",
+ "projectDir": os.path.join(self.dataDir, 'projects'),
+ }
+
+ for parm, value in default.items():
+ if self.settings.value(parm) is None:
+ self.settings.setValue(parm, value)
-''' ####### commandline functionality broken until we decide how to implement it
-if len(sys.argv) > 1:
- # command line mode
- app = QtGui.QApplication(sys.argv, False)
- command = Command()
- signal.signal(signal.SIGINT, command.cleanUp)
- sys.exit(app.exec_())
-else:
-'''
-# gui mode
if __name__ == "__main__":
+ ''' FIXME commandline functionality broken until we decide how to implement
+ if len(sys.argv) > 1:
+ # command line mode
+ app = QtGui.QApplication(sys.argv, False)
+ command = Command()
+ signal.signal(signal.SIGINT, command.cleanUp)
+ sys.exit(app.exec_())
+ else:
+ '''
app = QtGui.QApplication(sys.argv)
app.setApplicationName("audio-visualizer")
app.setOrganizationName("audio-visualizer")
- window = uic.loadUi(os.path.join(os.path.dirname(os.path.realpath(__file__)), "mainwindow.ui"))
+ window = uic.loadUi(os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), "mainwindow.ui"))
# window.adjustSize()
desc = QtGui.QDesktopWidget()
dpi = desc.physicalDpiX()
-
+
topMargin = 0 if (dpi == 96) else int(10 * (dpi / 96))
window.resize(window.width() * (dpi / 96), window.height() * (dpi / 96))
- #window.verticalLayout_2.setContentsMargins(0, topMargin, 0, 0)
-
- main = Main(window)
+ # window.verticalLayout_2.setContentsMargins(0, topMargin, 0, 0)
+
+ main = MainWindow(window)
signal.signal(signal.SIGINT, main.cleanUp)
atexit.register(main.cleanUp)
diff --git a/mainwindow.py b/mainwindow.py
new file mode 100644
index 0000000..b779298
--- /dev/null
+++ b/mainwindow.py
@@ -0,0 +1,586 @@
+from os.path import expanduser
+from queue import Queue
+from importlib import import_module
+from collections import OrderedDict
+from PyQt4 import QtCore, QtGui
+from PyQt4.QtCore import QSettings, Qt
+from PyQt4.QtGui import QDesktopServices, QMenu
+import sys
+import io
+import os
+import string
+import signal
+import filecmp
+import time
+
+import core
+import preview_thread
+import video_thread
+from main import LoadDefaultSettings
+
+
+class PreviewWindow(QtGui.QLabel):
+ def __init__(self, parent, img):
+ super(PreviewWindow, self).__init__()
+ self.parent = parent
+ self.setFrameStyle(QtGui.QFrame.StyledPanel)
+ self.pixmap = QtGui.QPixmap(img)
+
+ def paintEvent(self, event):
+ size = self.size()
+ painter = QtGui.QPainter(self)
+ point = QtCore.QPoint(0, 0)
+ scaledPix = self.pixmap.scaled(
+ size, Qt.KeepAspectRatio, transformMode=Qt.SmoothTransformation)
+
+ # start painting the label from left upper corner
+ point.setX((size.width() - scaledPix.width())/2)
+ point.setY((size.height() - scaledPix.height())/2)
+ painter.drawPixmap(point, scaledPix)
+
+ def changePixmap(self, img):
+ self.pixmap = QtGui.QPixmap(img)
+ self.repaint()
+
+
+class MainWindow(QtCore.QObject):
+
+ newTask = QtCore.pyqtSignal(list)
+ processTask = QtCore.pyqtSignal()
+ videoTask = QtCore.pyqtSignal(str, str, list)
+
+ def __init__(self, window):
+ QtCore.QObject.__init__(self)
+
+ # print('main thread id: {}'.format(QtCore.QThread.currentThreadId()))
+ self.window = window
+ self.core = core.Core()
+ self.pages = []
+ self.selectedComponents = []
+ self.lastAutosave = time.time()
+
+ # create data directory, load/create settings
+ self.dataDir = QDesktopServices.storageLocation(
+ QDesktopServices.DataLocation)
+ self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
+ self.presetDir = os.path.join(self.dataDir, 'presets')
+ self.settings = QSettings(
+ os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat)
+ LoadDefaultSettings(self)
+ if not os.path.exists(self.dataDir):
+ os.makedirs(self.dataDir)
+ for neededDirectory in (
+ self.presetDir, self.settings.value("projectDir")):
+ if not os.path.exists(neededDirectory):
+ os.mkdir(neededDirectory)
+
+ #
+ self.previewQueue = Queue()
+ self.previewThread = QtCore.QThread(self)
+ self.previewWorker = preview_thread.Worker(self, self.previewQueue)
+ self.previewWorker.moveToThread(self.previewThread)
+ self.previewWorker.imageCreated.connect(self.showPreviewImage)
+ self.previewThread.start()
+
+ self.timer = QtCore.QTimer(self)
+ self.timer.timeout.connect(self.processTask.emit)
+ self.timer.start(500)
+
+ # begin decorating the window and connecting events
+ window.toolButton_selectAudioFile.clicked.connect(
+ self.openInputFileDialog)
+
+ window.toolButton_selectOutputFile.clicked.connect(
+ self.openOutputFileDialog)
+
+ window.progressBar_createVideo.setValue(0)
+
+ window.pushButton_createVideo.clicked.connect(
+ self.createAudioVisualisation)
+
+ window.pushButton_Cancel.clicked.connect(self.stopVideo)
+ window.setWindowTitle("Audio Visualizer")
+
+ self.previewWindow = PreviewWindow(self, os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), "background.png"))
+ window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
+
+ self.modules = self.findComponents()
+ self.compMenu = QMenu()
+ for i, comp in enumerate(self.modules):
+ action = self.compMenu.addAction(comp.Component.__doc__)
+ action.triggered[()].connect(
+ lambda item=i: self.insertComponent(item))
+
+ self.window.pushButton_addComponent.setMenu(self.compMenu)
+ window.listWidget_componentList.clicked.connect(
+ lambda _: self.changeComponentWidget())
+
+ self.window.pushButton_removeComponent.clicked.connect(
+ lambda _: self.removeComponent())
+
+ currentRes = str(self.settings.value('outputWidth'))+'x' + \
+ str(self.settings.value('outputHeight'))
+ for i, res in enumerate(self.resolutions):
+ window.comboBox_resolution.addItem(res)
+ if res == currentRes:
+ currentRes = i
+ window.comboBox_resolution.setCurrentIndex(currentRes)
+ window.comboBox_resolution.currentIndexChanged.connect(
+ self.updateResolution)
+
+ self.window.pushButton_listMoveUp.clicked.connect(
+ self.moveComponentUp)
+ self.window.pushButton_listMoveDown.clicked.connect(
+ self.moveComponentDown)
+ self.window.pushButton_savePreset.clicked.connect(
+ self.openSavePresetDialog)
+ self.window.comboBox_openPreset.currentIndexChanged.connect(
+ self.openPreset)
+ self.window.pushButton_saveAs.clicked.connect(
+ self.openSaveProjectDialog)
+ self.window.pushButton_saveProject.clicked.connect(
+ self.saveCurrentProject)
+ self.window.pushButton_openProject.clicked.connect(
+ self.openOpenProjectDialog)
+
+ # show the window and load current project
+ window.show()
+ self.currentProject = self.settings.value("currentProject")
+ if self.currentProject and os.path.exists(self.autosavePath) \
+ and filecmp.cmp(self.autosavePath, self.currentProject):
+ # delete autosave if it's identical to the project
+ os.remove(self.autosavePath)
+
+ if self.currentProject and os.path.exists(self.autosavePath):
+ ch = self.showMessage(
+ "Restore unsaved changes in project '%s'?"
+ % os.path.basename(self.currentProject)[:-4], True)
+ if ch:
+ os.remove(self.currentProject)
+ os.rename(self.autosavePath, self.currentProject)
+ else:
+ os.remove(self.autosavePath)
+
+ self.openProject(self.currentProject)
+ self.drawPreview()
+
+ def cleanUp(self):
+ self.timer.stop()
+ self.previewThread.quit()
+ self.previewThread.wait()
+ self.autosave()
+
+ def autosave(self):
+ if time.time() - self.lastAutosave >= 1.0:
+ if os.path.exists(self.autosavePath):
+ os.remove(self.autosavePath)
+ self.createProjectFile(self.autosavePath)
+ self.lastAutosave = time.time()
+
+ def openInputFileDialog(self):
+ inputDir = self.settings.value("inputDir", expanduser("~"))
+
+ fileName = QtGui.QFileDialog.getOpenFileName(
+ self.window, "Open Music File",
+ inputDir, "Music Files (*.mp3 *.wav *.ogg *.fla *.aac)")
+
+ if not fileName == "":
+ self.settings.setValue("inputDir", os.path.dirname(fileName))
+ self.window.lineEdit_audioFile.setText(fileName)
+
+ def openOutputFileDialog(self):
+ outputDir = self.settings.value("outputDir", expanduser("~"))
+
+ fileName = QtGui.QFileDialog.getSaveFileName(
+ self.window, "Set Output Video File",
+ outputDir, "Video Files (*.mp4 *.mov *.mkv *.avi *.webm *.flv)")
+
+ if not fileName == "":
+ self.settings.setValue("outputDir", os.path.dirname(fileName))
+ self.window.lineEdit_outputFile.setText(fileName)
+
+ def stopVideo(self):
+ print('stop')
+ self.videoWorker.cancel()
+ self.canceled = True
+
+ def createAudioVisualisation(self):
+ # create output video if mandatory settings are filled in
+ if self.window.lineEdit_audioFile.text() and \
+ self.window.lineEdit_outputFile.text():
+ self.canceled = False
+ self.progressBarUpdated(-1)
+ ffmpeg_cmd = self.settings.value("ffmpeg_cmd", expanduser("~"))
+ self.videoThread = QtCore.QThread(self)
+ self.videoWorker = video_thread.Worker(self)
+ self.videoWorker.moveToThread(self.videoThread)
+ self.videoWorker.videoCreated.connect(self.videoCreated)
+ self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated)
+ self.videoWorker.progressBarSetText.connect(
+ self.progressBarSetText)
+ self.videoWorker.imageCreated.connect(self.showPreviewImage)
+ self.videoWorker.encoding.connect(self.changeEncodingStatus)
+ self.videoThread.start()
+ self.videoTask.emit(
+ self.window.lineEdit_audioFile.text(),
+ self.window.lineEdit_outputFile.text(),
+ self.selectedComponents)
+ else:
+ self.showMessage(
+ "You must select an audio file and output filename.")
+
+ def progressBarUpdated(self, value):
+ self.window.progressBar_createVideo.setValue(value)
+
+ def changeEncodingStatus(self, status):
+ if status:
+ self.window.pushButton_createVideo.setEnabled(False)
+ self.window.pushButton_Cancel.setEnabled(True)
+ self.window.comboBox_resolution.setEnabled(False)
+ self.window.stackedWidget.setEnabled(False)
+ self.window.tab_encoderSettings.setEnabled(False)
+ self.window.label_audioFile.setEnabled(False)
+ self.window.toolButton_selectAudioFile.setEnabled(False)
+ self.window.label_outputFile.setEnabled(False)
+ self.window.toolButton_selectOutputFile.setEnabled(False)
+ self.window.lineEdit_audioFile.setEnabled(False)
+ self.window.lineEdit_outputFile.setEnabled(False)
+ self.window.pushButton_addComponent.setEnabled(False)
+ self.window.pushButton_removeComponent.setEnabled(False)
+ self.window.pushButton_listMoveDown.setEnabled(False)
+ self.window.pushButton_listMoveUp.setEnabled(False)
+ self.window.comboBox_openPreset.setEnabled(False)
+ self.window.pushButton_removePreset.setEnabled(False)
+ self.window.pushButton_savePreset.setEnabled(False)
+ self.window.pushButton_openProject.setEnabled(False)
+ self.window.listWidget_componentList.setEnabled(False)
+ else:
+ self.window.pushButton_createVideo.setEnabled(True)
+ self.window.pushButton_Cancel.setEnabled(False)
+ self.window.comboBox_resolution.setEnabled(True)
+ self.window.stackedWidget.setEnabled(True)
+ self.window.tab_encoderSettings.setEnabled(True)
+ self.window.label_audioFile.setEnabled(True)
+ self.window.toolButton_selectAudioFile.setEnabled(True)
+ self.window.lineEdit_audioFile.setEnabled(True)
+ self.window.label_outputFile.setEnabled(True)
+ self.window.toolButton_selectOutputFile.setEnabled(True)
+ self.window.lineEdit_outputFile.setEnabled(True)
+ self.window.pushButton_addComponent.setEnabled(True)
+ self.window.pushButton_removeComponent.setEnabled(True)
+ self.window.pushButton_listMoveDown.setEnabled(True)
+ self.window.pushButton_listMoveUp.setEnabled(True)
+ self.window.comboBox_openPreset.setEnabled(True)
+ self.window.pushButton_removePreset.setEnabled(True)
+ self.window.pushButton_savePreset.setEnabled(True)
+ self.window.pushButton_openProject.setEnabled(True)
+ self.window.listWidget_componentList.setEnabled(True)
+
+ def progressBarSetText(self, value):
+ self.window.progressBar_createVideo.setFormat(value)
+
+ def videoCreated(self):
+ self.videoThread.quit()
+ self.videoThread.wait()
+
+ def updateResolution(self):
+ resIndex = int(window.comboBox_resolution.currentIndex())
+ res = self.resolutions[resIndex].split('x')
+ self.settings.setValue('outputWidth', res[0])
+ self.settings.setValue('outputHeight', res[1])
+ self.drawPreview()
+
+ def drawPreview(self):
+ self.newTask.emit(self.selectedComponents)
+ # self.processTask.emit()
+ self.autosave()
+
+ def showPreviewImage(self, image):
+ self.previewWindow.changePixmap(image)
+
+ def findComponents(self):
+ def findComponents():
+ srcPath = os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), 'components')
+ if os.path.exists(srcPath):
+ for f in sorted(os.listdir(srcPath)):
+ name, ext = os.path.splitext(f)
+ if name.startswith("__"):
+ continue
+ elif ext == '.py':
+ yield name
+ return [
+ import_module('components.%s' % name)
+ for name in findComponents()]
+
+ def addComponent(self, moduleIndex):
+ index = len(self.pages)
+ self.selectedComponents.append(self.modules[moduleIndex].Component())
+ self.window.listWidget_componentList.addItem(
+ self.selectedComponents[-1].__doc__)
+ self.pages.append(self.selectedComponents[-1].widget(self))
+ self.window.listWidget_componentList.setCurrentRow(index)
+ self.window.stackedWidget.addWidget(self.pages[-1])
+ self.window.stackedWidget.setCurrentIndex(index)
+ self.selectedComponents[-1].update()
+ self.updateOpenPresetComboBox(self.selectedComponents[-1])
+
+ def insertComponent(self, moduleIndex):
+ self.selectedComponents.insert(
+ 0, self.modules[moduleIndex].Component())
+ self.window.listWidget_componentList.insertItem(
+ 0, self.selectedComponents[0].__doc__)
+ self.pages.insert(0, self.selectedComponents[0].widget(self))
+ self.window.listWidget_componentList.setCurrentRow(0)
+ self.window.stackedWidget.insertWidget(0, self.pages[0])
+ self.window.stackedWidget.setCurrentIndex(0)
+ self.selectedComponents[0].update()
+ self.updateOpenPresetComboBox(self.selectedComponents[0])
+
+ def removeComponent(self):
+ for selected in self.window.listWidget_componentList.selectedItems():
+ index = self.window.listWidget_componentList.row(selected)
+ self.window.stackedWidget.removeWidget(self.pages[index])
+ self.window.listWidget_componentList.takeItem(index)
+ self.selectedComponents.pop(index)
+ self.pages.pop(index)
+ self.changeComponentWidget()
+ self.drawPreview()
+
+ def changeComponentWidget(self):
+ selected = self.window.listWidget_componentList.selectedItems()
+ if selected:
+ index = self.window.listWidget_componentList.row(selected[0])
+ self.window.stackedWidget.setCurrentIndex(index)
+ self.updateOpenPresetComboBox(self.selectedComponents[index])
+
+ def moveComponentUp(self):
+ row = self.window.listWidget_componentList.currentRow()
+ if row > 0:
+ module = self.selectedComponents[row]
+ self.selectedComponents.pop(row)
+ self.selectedComponents.insert(row - 1, module)
+ page = self.pages[row]
+ self.pages.pop(row)
+ self.pages.insert(row - 1, page)
+ item = self.window.listWidget_componentList.takeItem(row)
+ self.window.listWidget_componentList.insertItem(row - 1, item)
+ widget = self.window.stackedWidget.removeWidget(page)
+ self.window.stackedWidget.insertWidget(row - 1, page)
+ self.window.listWidget_componentList.setCurrentRow(row - 1)
+ self.window.stackedWidget.setCurrentIndex(row - 1)
+ self.drawPreview()
+
+ def moveComponentDown(self):
+ row = self.window.listWidget_componentList.currentRow()
+ if row != -1 and row < len(self.pages)+1:
+ module = self.selectedComponents[row]
+ self.selectedComponents.pop(row)
+ self.selectedComponents.insert(row + 1, module)
+ page = self.pages[row]
+ self.pages.pop(row)
+ self.pages.insert(row + 1, page)
+ item = self.window.listWidget_componentList.takeItem(row)
+ self.window.listWidget_componentList.insertItem(row + 1, item)
+ widget = self.window.stackedWidget.removeWidget(page)
+ self.window.stackedWidget.insertWidget(row + 1, page)
+ self.window.listWidget_componentList.setCurrentRow(row + 1)
+ self.window.stackedWidget.setCurrentIndex(row + 1)
+ self.drawPreview()
+
+ def updateOpenPresetComboBox(self, component):
+ self.window.comboBox_openPreset.clear()
+ self.window.comboBox_openPreset.addItem("Component Presets")
+ destination = os.path.join(
+ self.presetDir, str(component).strip(), str(component.version()))
+ if not os.path.exists(destination):
+ os.makedirs(destination)
+ for f in os.listdir(destination):
+ self.window.comboBox_openPreset.addItem(f)
+
+ def openSavePresetDialog(self):
+ if self.window.listWidget_componentList.currentRow() == -1:
+ return
+ while True:
+ newName, OK = QtGui.QInputDialog.getText(
+ QtGui.QWidget(), 'Audio Visualizer', 'New Preset Name:')
+ badName = False
+ for letter in newName:
+ if letter in string.punctuation:
+ badName = True
+ if badName:
+ # some filesystems don't like bizarre characters
+ self.showMessage("Preset names must contain only letters, \
+ numbers, and spaces.")
+ continue
+ if OK and newName:
+ index = self.window.listWidget_componentList.currentRow()
+ if index != -1:
+ saveValueStore = \
+ self.selectedComponents[index].savePreset()
+ componentName = str(self.selectedComponents[index]).strip()
+ vers = self.selectedComponents[index].version()
+ self.createPresetFile(
+ componentName, vers, saveValueStore, newName)
+ break
+
+ def createPresetFile(
+ self, componentName, version, saveValueStore, filename):
+ dirname = os.path.join(self.presetDir, componentName, str(version))
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+ filepath = os.path.join(dirname, filename)
+ if os.path.exists(filepath):
+ ch = self.showMessage(
+ "%s already exists! Overwrite it?" % filename,
+ True, QtGui.QMessageBox.Warning)
+ if not ch:
+ return
+ # remove old copies of the preset
+ for i in range(0, self.window.comboBox_openPreset.count()):
+ if self.window.comboBox_openPreset.itemText(i) == filename:
+ self.window.comboBox_openPreset.removeItem(i)
+ with open(filepath, 'w') as f:
+ f.write(core.Core.stringOrderedDict(saveValueStore))
+ self.window.comboBox_openPreset.addItem(filename)
+ self.window.comboBox_openPreset.setCurrentIndex(
+ self.window.comboBox_openPreset.count()-1)
+
+ def openPreset(self):
+ if self.window.comboBox_openPreset.currentIndex() < 1:
+ return
+ index = self.window.listWidget_componentList.currentRow()
+ if index == -1:
+ return
+ filename = self.window.comboBox_openPreset.itemText(
+ self.window.comboBox_openPreset.currentIndex())
+ componentName = str(self.selectedComponents[index]).strip()
+ version = self.selectedComponents[index].version()
+ dirname = os.path.join(self.presetDir, componentName, str(version))
+ filepath = os.path.join(dirname, filename)
+ if not os.path.exists(filepath):
+ self.window.comboBox_openPreset.removeItem(
+ self.window.comboBox_openPreset.currentIndex())
+ return
+ with open(filepath, 'r') as f:
+ for line in f:
+ saveValueStore = dict(eval(line.strip()))
+ break
+ self.selectedComponents[index].loadPreset(saveValueStore)
+ self.drawPreview()
+
+ def saveCurrentProject(self):
+ if self.currentProject:
+ self.createProjectFile(self.currentProject)
+ else:
+ self.openSaveProjectDialog()
+
+ def openSaveProjectDialog(self):
+ filename = QtGui.QFileDialog.getSaveFileName(
+ self.window, "Create Project File",
+ self.settings.value("projectDir"),
+ "Project Files (*.avp)")
+ if not filename:
+ return
+ self.createProjectFile(filename)
+
+ def createProjectFile(self, filepath):
+ if not filepath.endswith(".avp"):
+ filepath += '.avp'
+ with open(filepath, 'w') as f:
+ print('creating %s' % filepath)
+ f.write('[Components]\n')
+ for comp in self.selectedComponents:
+ saveValueStore = comp.savePreset()
+ f.write('%s\n' % str(comp))
+ f.write('%s\n' % str(comp.version()))
+ f.write('%s\n' % core.Core.stringOrderedDict(saveValueStore))
+ if filepath != self.autosavePath:
+ self.settings.setValue("projectDir", os.path.dirname(filepath))
+ self.settings.setValue("currentProject", filepath)
+ self.currentProject = filepath
+
+ def openOpenProjectDialog(self):
+ filename = QtGui.QFileDialog.getOpenFileName(
+ self.window, "Open Project File",
+ self.settings.value("projectDir"),
+ "Project Files (*.avp)")
+ self.openProject(filename)
+
+ def openProject(self, filepath):
+ if not filepath or not os.path.exists(filepath) \
+ or not filepath.endswith('.avp'):
+ return
+ self.clear()
+ self.currentProject = filepath
+ self.settings.setValue("currentProject", filepath)
+ self.settings.setValue("projectDir", os.path.dirname(filepath))
+ compNames = [mod.Component.__doc__ for mod in self.modules]
+ try:
+ with open(filepath, 'r') as f:
+ validSections = ('Components')
+ section = ''
+
+ def parseLine(line):
+ line = line.strip()
+ newSection = ''
+
+ if line.startswith('[') and line.endswith(']') \
+ and line[1:-1] in validSections:
+ newSection = line[1:-1]
+
+ return line, newSection
+
+ i = 0
+ for line in f:
+ line, newSection = parseLine(line)
+ if newSection:
+ section = str(newSection)
+ continue
+ if line and section == 'Components':
+ if i == 0:
+ compIndex = compNames.index(line)
+ self.addComponent(compIndex)
+ i += 1
+ elif i == 1:
+ # version, not used yet
+ i += 1
+ elif i == 2:
+ saveValueStore = dict(eval(line))
+ self.selectedComponents[-1].loadPreset(
+ saveValueStore)
+ i = 0
+ except (IndexError, ValueError, KeyError, NameError,
+ SyntaxError, AttributeError, TypeError) as e:
+ self.clear()
+ typ, value, _ = sys.exc_info()
+ msg = '%s: %s' % (typ.__name__, value)
+ self.showMessage(
+ "Project file '%s' is corrupted." % filepath, False,
+ QtGui.QMessageBox.Warning, msg)
+
+ def showMessage(
+ self, string, showCancel=False,
+ icon=QtGui.QMessageBox.Information, detail=None):
+ msg = QtGui.QMessageBox()
+ msg.setIcon(icon)
+ msg.setText(string)
+ msg.setDetailedText(detail)
+ if showCancel:
+ msg.setStandardButtons(
+ QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel)
+ else:
+ msg.setStandardButtons(QtGui.QMessageBox.Ok)
+ ch = msg.exec_()
+ if ch == 1024:
+ return True
+ return False
+
+ def clear(self):
+ ''' empty out all components and fields, get a blank slate '''
+ self.selectedComponents = []
+ self.window.listWidget_componentList.clear()
+ for widget in self.pages:
+ self.window.stackedWidget.removeWidget(widget)
+ self.pages = []
diff --git a/preview_thread.py b/preview_thread.py
index 04683ae..d54dba5 100644
--- a/preview_thread.py
+++ b/preview_thread.py
@@ -9,53 +9,52 @@ import numpy
import os
from copy import copy
+
class Worker(QtCore.QObject):
- imageCreated = pyqtSignal(['QImage'])
-
- def __init__(self, parent=None, queue=None):
- QtCore.QObject.__init__(self)
- parent.newTask.connect(self.createPreviewImage)
- parent.processTask.connect(self.process)
- self.core = core.Core()
- self.queue = queue
- self.core.settings = parent.settings
- self.stackedWidget = parent.window.stackedWidget
- self.background = Image.new("RGBA", (1920, 1080),(0,0,0,0))
- self.background.paste(Image.open(os.path.join(os.path.dirname(os.path.realpath(__file__)),"background.png")))
-
-
-
- @pyqtSlot(str, list)
- def createPreviewImage(self, components):
- # print('worker thread id: {}'.format(QtCore.QThread.currentThreadId()))
- dic = {
- "components": components,
- }
- self.queue.put(dic)
-
- @pyqtSlot()
- def process(self):
- try:
- nextPreviewInformation = self.queue.get(block=False)
- while self.queue.qsize() >= 2:
+ imageCreated = pyqtSignal(['QImage'])
+
+ def __init__(self, parent=None, queue=None):
+ QtCore.QObject.__init__(self)
+ parent.newTask.connect(self.createPreviewImage)
+ parent.processTask.connect(self.process)
+ self.core = core.Core()
+ self.queue = queue
+ self.core.settings = parent.settings
+ self.stackedWidget = parent.window.stackedWidget
+ self.background = Image.new("RGBA", (1920, 1080), (0, 0, 0, 0))
+ self.background.paste(Image.open(os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), "background.png")))
+
+ @pyqtSlot(str, list)
+ def createPreviewImage(self, components):
+ dic = {
+ "components": components,
+ }
+ self.queue.put(dic)
+
+ @pyqtSlot()
+ def process(self):
try:
- self.queue.get(block=False)
+ nextPreviewInformation = self.queue.get(block=False)
+ while self.queue.qsize() >= 2:
+ try:
+ self.queue.get(block=False)
+ except Empty:
+ continue
+
+ width = int(self.core.settings.value('outputWidth'))
+ height = int(self.core.settings.value('outputHeight'))
+ frame = copy(self.background)
+ frame = frame.resize((width, height))
+
+ components = nextPreviewInformation["components"]
+ for component in reversed(components):
+ frame = Image.alpha_composite(
+ frame, component.previewRender(self))
+
+ self._image = ImageQt(frame)
+ self.imageCreated.emit(QtGui.QImage(self._image))
+
except Empty:
- continue
-
- width = int(self.core.settings.value('outputWidth'))
- height = int(self.core.settings.value('outputHeight'))
- frame = copy(self.background)
- frame = frame.resize((width,height))
-
- components = nextPreviewInformation["components"]
- for component in reversed(components):
- #newFrame = Image.alpha_composite(frame,)
- frame = Image.alpha_composite(frame,component.previewRender(self))
-
- self._image = ImageQt(frame)
- self.imageCreated.emit(QtGui.QImage(self._image))
-
- except Empty:
- True
+ True
diff --git a/video_thread.py b/video_thread.py
index e880263..5897ff0 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -13,6 +13,7 @@ import time
from copy import copy
import signal
+
class Worker(QtCore.QObject):
imageCreated = pyqtSignal(['QImage'])
@@ -40,16 +41,19 @@ class Worker(QtCore.QObject):
frame = None
for compNo, comp in reversed(list(enumerate(self.components))):
- if compNo in self.staticComponents and self.staticComponents[compNo] != None:
+ if compNo in self.staticComponents and \
+ self.staticComponents[compNo] is not None:
if frame is None:
frame = self.staticComponents[compNo]
else:
- frame = Image.alpha_composite(frame, self.staticComponents[compNo])
+ frame = Image.alpha_composite(
+ frame, self.staticComponents[compNo])
else:
if frame is None:
frame = comp.frameRender(compNo, i[0], i[1])
else:
- frame = Image.alpha_composite(frame, comp.frameRender(compNo, i[0], i[1]))
+ frame = Image.alpha_composite(
+ frame, comp.frameRender(compNo, i[0], i[1]))
self.renderQueue.put([i[0], frame])
self.compositeQueue.task_done()
@@ -63,8 +67,9 @@ class Worker(QtCore.QObject):
self.bgI += 1
def previewDispatch(self):
- background = Image.new("RGBA", (1920, 1080),(0,0,0,0))
- background.paste(Image.open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "background.png")))
+ background = Image.new("RGBA", (1920, 1080), (0, 0, 0, 0))
+ background.paste(Image.open(os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), "background.png")))
background = background.resize((self.width, self.height))
while not self.stopped:
@@ -83,11 +88,10 @@ class Worker(QtCore.QObject):
self.encoding.emit(True)
self.components = components
self.outputFile = outputFile
- self.bgI = 0 # tracked video frame
+ self.bgI = 0 # tracked video frame
self.reset()
self.width = int(self.core.settings.value('outputWidth'))
self.height = int(self.core.settings.value('outputHeight'))
- # print('worker thread id: {}'.format(QtCore.QThread.currentThreadId()))
progressBarValue = 0
self.progressBarUpdate.emit(progressBarValue)
@@ -95,21 +99,24 @@ class Worker(QtCore.QObject):
self.completeAudioArray = self.core.readAudioFile(inputFile, self)
# test if user has libfdk_aac
- encoders = sp.check_output(self.core.FFMPEG_BIN + " -encoders -hide_banner", shell=True)
+ encoders = sp.check_output(
+ self.core.FFMPEG_BIN + " -encoders -hide_banner", shell=True)
acodec = self.core.settings.value('outputAudioCodec')
-
+
if b'libfdk_aac' in encoders and acodec == 'aac':
acodec = 'libfdk_aac'
ffmpegCommand = [
self.core.FFMPEG_BIN,
'-thread_queue_size', '512',
- '-y', # (optional) means overwrite the output file if it already exists.
+ '-y', # overwrite the output file if it already exists.
'-f', 'rawvideo',
'-vcodec', 'rawvideo',
'-s', str(self.width)+'x'+str(self.height), # size of one frame
'-pix_fmt', 'rgba',
- '-r', self.core.settings.value('outputFrameRate'), # frames per second
+
+ # frames per second
+ '-r', self.core.settings.value('outputFrameRate'),
'-i', '-', # The input comes from a pipe
'-an',
'-i', inputFile,
@@ -126,14 +133,16 @@ class Worker(QtCore.QObject):
ffmpegCommand.append('-2')
ffmpegCommand.append(outputFile)
- self.out_pipe = sp.Popen(ffmpegCommand, stdin=sp.PIPE,stdout=sys.stdout, stderr=sys.stdout)
+ self.out_pipe = sp.Popen(
+ ffmpegCommand, stdin=sp.PIPE, stdout=sys.stdout, stderr=sys.stdout)
# create video for output
numpy.seterr(divide='ignore')
# initialize components
print('loaded components:',
- ["%s%s" % (num, str(component)) for num, component in enumerate(self.components)])
+ ["%s%s" % (num, str(component)) for num,
+ component in enumerate(self.components)])
self.staticComponents = {}
numComps = len(self.components)
for compNo, comp in enumerate(self.components):
@@ -149,7 +158,8 @@ class Worker(QtCore.QObject):
)
if properties and 'static' in properties:
- self.staticComponents[compNo] = copy(comp.frameRender(compNo, 0, 0))
+ self.staticComponents[compNo] = copy(
+ comp.frameRender(compNo, 0, 0))
self.progressBarUpdate.emit(100)
self.compositeQueue = Queue()
@@ -159,17 +169,20 @@ class Worker(QtCore.QObject):
self.previewQueue = PriorityQueue()
self.renderThreads = []
- # create threads to render frames and send them back here for piping out
+ # Threads to render frames and send them back here for piping out
for i in range(3):
- self.renderThreads.append(Thread(target=self.renderNode, name="Render Thread"))
+ self.renderThreads.append(
+ Thread(target=self.renderNode, name="Render Thread"))
self.renderThreads[i].daemon = True
self.renderThreads[i].start()
- self.dispatchThread = Thread(target=self.renderDispatch, name="Render Dispatch Thread")
+ self.dispatchThread = Thread(
+ target=self.renderDispatch, name="Render Dispatch Thread")
self.dispatchThread.daemon = True
self.dispatchThread.start()
- self.previewDispatch = Thread(target=self.previewDispatch, name="Render Dispatch Thread")
+ self.previewDispatch = Thread(
+ target=self.previewDispatch, name="Render Dispatch Thread")
self.previewDispatch.daemon = True
self.previewDispatch.start()
@@ -197,10 +210,13 @@ class Worker(QtCore.QObject):
break
# increase progress bar value
- if progressBarValue + 1 <= (i / len(self.completeAudioArray)) * 100:
- progressBarValue = numpy.floor((i / len(self.completeAudioArray)) * 100)
+ if progressBarValue + 1 <= (i / len(self.completeAudioArray)) \
+ * 100:
+ progressBarValue = numpy.floor(
+ (i / len(self.completeAudioArray)) * 100)
self.progressBarUpdate.emit(progressBarValue)
- pStr = "Exporting video: " + str(int(progressBarValue)) + "%"
+ pStr = "Exporting video: " + str(int(progressBarValue)) \
+ + "%"
self.progressBarSetText.emit(pStr)
numpy.seterr(all='print')
@@ -220,7 +236,7 @@ class Worker(QtCore.QObject):
pass
self.progressBarUpdate.emit(0)
self.progressBarSetText.emit('Export Canceled')
-
+
else:
if self.error:
print("Export Failed")
@@ -230,14 +246,14 @@ class Worker(QtCore.QObject):
print("Export Complete")
self.progressBarUpdate.emit(100)
self.progressBarSetText.emit('Export Complete')
-
+
self.error = False
self.canceled = False
self.parent.drawPreview()
self.stopped = True
self.encoding.emit(False)
self.videoCreated.emit()
-
+
def updateProgress(self, pStr, pVal):
self.progressBarValue.emit(pVal)
self.progressBarSetText.emit(pStr)
@@ -245,10 +261,10 @@ class Worker(QtCore.QObject):
def cancel(self):
self.canceled = True
self.core.cancel()
-
+
for comp in self.components:
comp.cancel()
-
+
try:
self.out_pipe.send_signal(signal.SIGINT)
except:
--
cgit v1.2.3
From c946133da99a3bb1bf3565a19a12c641c8f77ed9 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 6 Jun 2017 20:50:53 -0400
Subject: changed video init to use keywords
---
components/__base__.py | 10 ++++++++++
components/video.py | 42 ++++++++++++++++++++++--------------------
2 files changed, 32 insertions(+), 20 deletions(-)
diff --git a/components/__base__.py b/components/__base__.py
index 94ac6f2..4fdf31f 100644
--- a/components/__base__.py
+++ b/components/__base__.py
@@ -84,3 +84,13 @@ class Component:
def reset(self):
self.canceled = False
'''
+
+class BadComponentInit(Exception):
+ def __init__(self, arg, name):
+ string = \
+'''################################
+Mandatory argument "%s" not specified
+ in %s instance initialization
+###################################'''
+ print(string % (arg, name))
+ quit()
diff --git a/components/video.py b/components/video.py
index 67a96dd..b28b81e 100644
--- a/components/video.py
+++ b/components/video.py
@@ -5,37 +5,38 @@ import subprocess
import threading
from queue import PriorityQueue
from . import __base__
-
-
+
class Video:
'''Video Component Frame-Fetcher'''
- def __init__(
- self, ffmpeg, videoPath, width, height,
- frameRate, chunkSize, parent, loopVideo):
+ def __init__(self, **kwargs):
+ mandatoryArgs = ['ffmpeg', 'videoPath', 'width', 'height',
+ 'frameRate', 'chunkSize', 'parent']
+ for arg in mandatoryArgs:
+ try:
+ exec('self.%s = kwargs[arg]' % arg)
+ except KeyError:
+ raise __base__.BadComponentInit(arg, self.__doc__)
- self.parent = parent
- self.chunkSize = chunkSize
- self.size = (width, height)
self.frameNo = -1
self.currentFrame = 'None'
- if loopVideo:
+ if 'loopVideo' in kwargs and kwargs['loopVideo']:
self.loopValue = '-1'
else:
self.loopValue = '0'
self.command = [
- ffmpeg,
+ self.ffmpeg,
'-thread_queue_size', '512',
- '-r', frameRate,
+ '-r', str(self.frameRate),
'-stream_loop', self.loopValue,
- '-i', videoPath,
+ '-i', self.videoPath,
'-f', 'image2pipe',
'-pix_fmt', 'rgba',
- '-filter:v', 'scale='+str(width)+':'+str(height),
+ '-filter:v', 'scale='+str(self.width)+':'+str(self.height),
'-vcodec', 'rawvideo', '-',
]
self.frameBuffer = PriorityQueue()
- self.frameBuffer.maxsize = int(frameRate)
+ self.frameBuffer.maxsize = self.frameRate
self.finishedFrames = {}
self.thread = threading.Thread(
@@ -49,13 +50,13 @@ class Video:
while True:
if num in self.finishedFrames:
image = self.finishedFrames.pop(num)
- return Image.frombytes('RGBA', self.size, image)
+ return Image.frombytes('RGBA', (self.width, self.height), image)
i, image = self.frameBuffer.get()
self.finishedFrames[i] = image
self.frameBuffer.task_done()
def fillBuffer(self):
- self.pipe = subprocess.Popen(
+ pipe = subprocess.Popen(
self.command, stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL, bufsize=10**8
)
@@ -69,7 +70,7 @@ class Video:
self.frameBuffer.put((self.frameNo-1, self.lastFrame))
continue
- self.currentFrame = self.pipe.stdout.read(self.chunkSize)
+ self.currentFrame = pipe.stdout.read(self.chunkSize)
if len(self.currentFrame) != 0:
self.frameBuffer.put((self.frameNo, self.currentFrame))
self.lastFrame = self.currentFrame
@@ -117,9 +118,10 @@ class Component(__base__.Component):
height = int(self.worker.core.settings.value('outputHeight'))
self.chunkSize = 4*width*height
self.video = Video(
- self.parent.core.FFMPEG_BIN, self.videoPath,
- width, height, self.settings.value("outputFrameRate"),
- self.chunkSize, self.parent, self.loopVideo
+ ffmpeg=self.parent.core.FFMPEG_BIN, videoPath=self.videoPath,
+ width=width, height=height, chunkSize=self.chunkSize,
+ frameRate=int(self.settings.value("outputFrameRate")),
+ parent=self.parent, loopVideo=self.loopVideo
)
def frameRender(self, moduleNo, arrayNo, frameNo):
--
cgit v1.2.3
From e6beca94a383ab916baf294ec2d703a47b4117fc Mon Sep 17 00:00:00 2001
From: DH4
Date: Wed, 7 Jun 2017 11:59:59 -0500
Subject: Added Encoder Settings, FIXME: Add bitrate options.
---
core.py | 8 ++
encoder-options.json | 215 ++++++++++++++++++---------------------------------
main.py | 5 +-
mainwindow.py | 71 +++++++++++++++--
video_thread.py | 35 +++++++--
5 files changed, 183 insertions(+), 151 deletions(-)
diff --git a/core.py b/core.py
index 8ea884b..7b3c69a 100644
--- a/core.py
+++ b/core.py
@@ -11,6 +11,7 @@ from shutil import rmtree
import atexit
import time
from collections import OrderedDict
+import json
class Core():
@@ -22,6 +23,13 @@ class Core():
if not os.path.exists(self.tempDir):
os.makedirs(self.tempDir)
atexit.register(self.deleteTempDir)
+ self.wd = os.path.dirname(os.path.realpath(__file__))
+ self.loadEncoderOptions()
+
+ def loadEncoderOptions(self):
+ file_path = os.path.join(self.wd, 'encoder-options.json')
+ with open(file_path) as json_file:
+ self.encoder_options = json.load(json_file)
def findFfmpeg(self):
if sys.platform == "win32":
diff --git a/encoder-options.json b/encoder-options.json
index 699ead4..53aa0e5 100644
--- a/encoder-options.json
+++ b/encoder-options.json
@@ -6,32 +6,14 @@
"default-vcodec": "H264",
"default-acodec": "AAC",
"video-codecs": [
- {
- "name": "H264",
- "encoders": ["libx264"]
- },
- {
- "name": "H264 (nvenc)",
- "encoders": ["nvenc_264"]
- },
- {
- "name": "MPEG4",
- "encoders": ["mpeg4"]
- }
+ "H264",
+ "H264 (nvenc)",
+ "MPEG4"
],
"audio-codecs": [
- {
- "name": "AAC",
- "encoders": ["libfdk_aac","aac"]
- },
- {
- "name": "AC3",
- "encoders": ["ac3"]
- },
- {
- "name": "MP3",
- "encoders": ["libmp3lame"]
- }
+ "AAC",
+ "AC3",
+ "MP3"
]
},
{
@@ -40,40 +22,37 @@
"default-vcodec": "H264",
"default-acodec": "AAC",
"video-codecs": [
- {
- "name": "H264",
- "encoders": ["libx264"]
- },
- {
- "name": "H264 (nvenc)",
- "encoders": ["nvenc_264"]
- },
- {
- "name": "MPEG4",
- "encoders": ["mpeg4"]
- },
- {
- "name": "XVID",
- "encoders": ["libxvid"]
- }
+ "H264",
+ "H264 (nvenc)",
+ "MPEG4",
+ "XVID"
],
"audio-codecs": [
- {
- "name": "AAC",
- "encoders": ["libfdk_aac","aac"]
- },
- {
- "name": "AC3",
- "encoders": ["ac3"]
- },
- {
- "name": "MP3",
- "encoders": ["libmp3lame"]
- },
- {
- "name": "PCM s16 LE",
- "encoders": ["pcm_s16le"]
- }
+ "AAC",
+ "AC3",
+ "MP3",
+ "PCM s16 LE"
+ ]
+ },
+ {
+ "name": "MKV",
+ "container": "matroska",
+ "default-vcodec": "H264",
+ "default-acodec": "AAC",
+ "video-codecs": [
+ "H264",
+ "H264 (nvenc)",
+ "MPEG4",
+ "MPEG2",
+ "DV",
+ "WMV"
+ ],
+ "audio-codecs": [
+ "AAC",
+ "AC3",
+ "MP3",
+ "PCM s16 LE",
+ "WMA"
]
},
{
@@ -82,52 +61,19 @@
"default-vcodec": "H264",
"default-acodec": "AAC",
"video-codecs": [
- {
- "name": "H264",
- "encoders": ["libx264"]
- },
- {
- "name": "H264 (nvenc)",
- "encoders": ["nvenc_264"]
- },
- {
- "name": "MPEG4",
- "encoders": ["mpeg4"]
- },
- {
- "name": "MPEG2",
- "encoders": ["mp2video"]
- },
- {
- "name": "DV",
- "encoders": ["dvvideo"]
- },
- {
- "name": "WMV",
- "encoders": ["wmv2"]
- }
+ "H264",
+ "H264 (nvenc)",
+ "MPEG4",
+ "MPEG2",
+ "DV",
+ "WMV"
],
"audio-codecs": [
- {
- "name": "AAC",
- "encoders": ["libfdk_aac","aac"]
- },
- {
- "name": "AC3",
- "encoders": ["ac3"]
- },
- {
- "name": "WMA",
- "encoders": ["wmav2"]
- },
- {
- "name": "MP3",
- "encoders": ["libmp3lame"]
- },
- {
- "name": "PCM s16 LE",
- "encoders": ["pcm_s16le"]
- }
+ "AAC",
+ "AC3",
+ "MP3",
+ "PCM s16 LE",
+ "WMA"
]
},
{
@@ -136,20 +82,11 @@
"default-vcodec": "VP9",
"default-acodec": "Vorbis",
"video-codecs": [
- {
- "name": "VP9",
- "encoders": ["libvpx-vp9"]
- },
- {
- "name": "VP8",
- "encoders": ["libvpx"]
- }
+ "VP9",
+ "VP8"
],
"audio-codecs": [
- {
- "name": "Vorbis",
- "encoders": ["vorbis"]
- }
+ "Vorbis"
]
},
{
@@ -158,34 +95,36 @@
"default-vcodec": "FLV",
"default-acodec": "Vorbis",
"video-codecs": [
- {
- "name": "Sorenson (flv)",
- "encoders": ["flv"]
- },
- {
- "name": "H264",
- "encoders": ["libx264"]
- },
- {
- "name": "MPEG4",
- "encoders": ["mpeg4"]
- }
+ "Sorenson (flv)",
+ "H264",
+ "H264 (nvenc)",
+ "MPEG4"
],
"audio-codecs": [
- {
- "name": "MP3",
- "encoders": ["libmp3lame"]
- },
- {
- "name": "Vorbis",
- "encoders": ["vorbis"]
- },
- {
- "name": "PCM s16 LE",
- "encoders": ["pcm_s16le"]
- }
+ "MP3",
+ "PCM s16 LE",
+ "Vorbis"
]
}
- ]
-
+ ],
+ "video-codecs":{
+ "H264": ["libx264"],
+ "H264 (nvenc)": ["nvenc_264"],
+ "MPEG4": ["mpeg4"],
+ "VP9": ["libvpx-vp9"],
+ "VP8": ["libvpx"],
+ "XVID": ["libxvid"],
+ "Sorenson (flv)": ["flv"],
+ "MPEG2": ["mp2video"],
+ "DV": ["dvvideo"],
+ "WMV": ["wmv2"]
+ },
+ "audio-codecs": {
+ "AAC": ["libfdk_aac","aac"],
+ "AC3": ["ac3"],
+ "MP3": ["libmp3lame"],
+ "PCM s16 LE": ["pcm_s16le"],
+ "WMA": ["wmav2"],
+ "Vorbis": ["libvorbis"]
+ }
}
\ No newline at end of file
diff --git a/main.py b/main.py
index 78c1d9b..3082e95 100644
--- a/main.py
+++ b/main.py
@@ -25,12 +25,13 @@ def LoadDefaultSettings(self):
"outputWidth": 1280,
"outputHeight": 720,
"outputFrameRate": 30,
- "outputAudioCodec": "aac",
+ "outputAudioCodec": "AAC",
"outputAudioBitrate": "192k",
- "outputVideoCodec": "libx264",
+ "outputVideoCodec": "H264",
"outputVideoFormat": "yuv420p",
"outputPreset": "medium",
"outputFormat": "mp4",
+ "outputContainer": "MP4",
"projectDir": os.path.join(self.dataDir, 'projects'),
}
diff --git a/mainwindow.py b/mainwindow.py
index b779298..6fbc3ed 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -101,6 +101,37 @@ class MainWindow(QtCore.QObject):
window.pushButton_Cancel.clicked.connect(self.stopVideo)
window.setWindowTitle("Audio Visualizer")
+ for i, container in enumerate(self.core.encoder_options['containers']):
+ window.comboBox_videoContainer.addItem(container['name'])
+ if container['name'] == self.settings.value('outputContainer'):
+ selectedContainer = i
+
+ window.comboBox_videoContainer.setCurrentIndex(selectedContainer)
+ window.comboBox_videoContainer.currentIndexChanged.connect(
+ self.updateCodecs
+ )
+
+ self.updateCodecs()
+
+ for i in range(window.comboBox_videoCodec.count()):
+ codec = window.comboBox_videoCodec.itemText(i)
+ if codec == self.settings.value('outputVideoCodec'):
+ window.comboBox_videoCodec.setCurrentIndex(i)
+ print(codec)
+
+ for i in range(window.comboBox_audioCodec.count()):
+ codec = window.comboBox_audioCodec.itemText(i)
+ if codec == self.settings.value('outputAudioCodec'):
+ window.comboBox_audioCodec.setCurrentIndex(i)
+
+ window.comboBox_videoCodec.currentIndexChanged.connect(
+ self.updateCodecSettings
+ )
+
+ window.comboBox_audioCodec.currentIndexChanged.connect(
+ self.updateCodecSettings
+ )
+
self.previewWindow = PreviewWindow(self, os.path.join(
os.path.dirname(os.path.realpath(__file__)), "background.png"))
window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
@@ -123,11 +154,11 @@ class MainWindow(QtCore.QObject):
str(self.settings.value('outputHeight'))
for i, res in enumerate(self.resolutions):
window.comboBox_resolution.addItem(res)
- if res == currentRes:
- currentRes = i
- window.comboBox_resolution.setCurrentIndex(currentRes)
- window.comboBox_resolution.currentIndexChanged.connect(
- self.updateResolution)
+ if res == currentRes:
+ currentRes = i
+ window.comboBox_resolution.setCurrentIndex(currentRes)
+ window.comboBox_resolution.currentIndexChanged.connect(
+ self.updateResolution)
self.window.pushButton_listMoveUp.clicked.connect(
self.moveComponentUp)
@@ -171,6 +202,34 @@ class MainWindow(QtCore.QObject):
self.previewThread.wait()
self.autosave()
+ def updateCodecs(self):
+ containerWidget = self.window.comboBox_videoContainer
+ vCodecWidget = self.window.comboBox_videoCodec
+ aCodecWidget = self.window.comboBox_audioCodec
+ index = containerWidget.currentIndex()
+ name = containerWidget.itemText(index)
+ self.settings.setValue('outputContainer', name)
+
+ vCodecWidget.clear()
+ aCodecWidget.clear()
+
+ for container in self.core.encoder_options['containers']:
+ if container['name'] == name:
+ for vCodec in container['video-codecs']:
+ vCodecWidget.addItem(vCodec)
+ for aCodec in container['audio-codecs']:
+ aCodecWidget.addItem(aCodec)
+
+ def updateCodecSettings(self):
+ vCodecWidget = self.window.comboBox_videoCodec
+ aCodecWidget = self.window.comboBox_audioCodec
+ currentVideoCodec = vCodecWidget.currentIndex()
+ currentVideoCodec = vCodecWidget.itemText(currentVideoCodec)
+ currentAudioCodec = aCodecWidget.currentIndex()
+ currentAudioCodec = aCodecWidget.itemText(currentAudioCodec)
+ self.settings.setValue('outputVideoCodec', currentVideoCodec)
+ self.settings.setValue('outputAudioCodec', currentAudioCodec)
+
def autosave(self):
if time.time() - self.lastAutosave >= 1.0:
if os.path.exists(self.autosavePath):
@@ -285,7 +344,7 @@ class MainWindow(QtCore.QObject):
self.videoThread.wait()
def updateResolution(self):
- resIndex = int(window.comboBox_resolution.currentIndex())
+ resIndex = int(self.window.comboBox_resolution.currentIndex())
res = self.resolutions[resIndex].split('x')
self.settings.setValue('outputWidth', res[0])
self.settings.setValue('outputHeight', res[1])
diff --git a/video_thread.py b/video_thread.py
index 5897ff0..d8694a4 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -101,10 +101,35 @@ class Worker(QtCore.QObject):
# test if user has libfdk_aac
encoders = sp.check_output(
self.core.FFMPEG_BIN + " -encoders -hide_banner", shell=True)
+
+ encoders = encoders.decode("utf-8")
+
+ acodec = self.core.settings.value('outputAudioCodec')
+
+ options = self.core.encoder_options
+ containerName = self.core.settings.value('outputContainer')
+ vcodec = self.core.settings.value('outputVideoCodec')
acodec = self.core.settings.value('outputAudioCodec')
- if b'libfdk_aac' in encoders and acodec == 'aac':
- acodec = 'libfdk_aac'
+ for cont in options['containers']:
+ if cont['name'] == containerName:
+ container = cont['container']
+
+ vencoders = options['video-codecs'][vcodec]
+ aencoders = options['audio-codecs'][acodec]
+
+ print(encoders)
+ for encoder in vencoders:
+ print(encoder)
+ if encoder in encoders:
+ vencoder = encoder
+ break
+
+ for encoder in aencoders:
+ print(encoder)
+ if encoder in encoders:
+ aencoder = encoder
+ break
ffmpegCommand = [
self.core.FFMPEG_BIN,
@@ -120,12 +145,12 @@ class Worker(QtCore.QObject):
'-i', '-', # The input comes from a pipe
'-an',
'-i', inputFile,
- '-acodec', acodec, # output audio codec
+ '-vcodec', vencoder,
+ '-acodec', aencoder, # output audio codec
'-b:a', self.core.settings.value('outputAudioBitrate'),
- '-vcodec', self.core.settings.value('outputVideoCodec'),
'-pix_fmt', self.core.settings.value('outputVideoFormat'),
'-preset', self.core.settings.value('outputPreset'),
- '-f', self.core.settings.value('outputFormat')
+ '-f', container
]
if acodec == 'aac':
--
cgit v1.2.3
From 02795503d09743b5225eed7e7b7112208dfc28d0 Mon Sep 17 00:00:00 2001
From: DH4
Date: Wed, 7 Jun 2017 12:33:22 -0500
Subject: Added Bitrate Selection
---
encoder-options.json | 4 ++--
main.py | 3 ++-
mainwindow.py | 18 ++++++++++++++++++
mainwindow.ui | 16 ++++++++++++----
video_thread.py | 5 ++++-
5 files changed, 38 insertions(+), 8 deletions(-)
diff --git a/encoder-options.json b/encoder-options.json
index 53aa0e5..78bc940 100644
--- a/encoder-options.json
+++ b/encoder-options.json
@@ -109,7 +109,7 @@
],
"video-codecs":{
"H264": ["libx264"],
- "H264 (nvenc)": ["nvenc_264"],
+ "H264 (nvenc)": ["h264_nvenc", "nvenc_h264"],
"MPEG4": ["mpeg4"],
"VP9": ["libvpx-vp9"],
"VP8": ["libvpx"],
@@ -120,7 +120,7 @@
"WMV": ["wmv2"]
},
"audio-codecs": {
- "AAC": ["libfdk_aac","aac"],
+ "AAC": ["libfdk_aac", "aac"],
"AC3": ["ac3"],
"MP3": ["libmp3lame"],
"PCM s16 LE": ["pcm_s16le"],
diff --git a/main.py b/main.py
index 3082e95..c771aca 100644
--- a/main.py
+++ b/main.py
@@ -26,8 +26,9 @@ def LoadDefaultSettings(self):
"outputHeight": 720,
"outputFrameRate": 30,
"outputAudioCodec": "AAC",
- "outputAudioBitrate": "192k",
+ "outputAudioBitrate": "192",
"outputVideoCodec": "H264",
+ "outputVideoBitrate": "2500",
"outputVideoFormat": "yuv420p",
"outputPreset": "medium",
"outputFormat": "mp4",
diff --git a/mainwindow.py b/mainwindow.py
index 6fbc3ed..78809be 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -132,6 +132,16 @@ class MainWindow(QtCore.QObject):
self.updateCodecSettings
)
+ vBitrate = int(self.settings.value('outputVideoBitrate'))
+ aBitrate = int(self.settings.value('outputAudioBitrate'))
+
+ window.spinBox_vBitrate.setValue(vBitrate)
+ window.spinBox_aBitrate.setValue(aBitrate)
+
+ window.spinBox_vBitrate.valueChanged.connect(self.updateCodecSettings)
+ window.spinBox_aBitrate.valueChanged.connect(self.updateCodecSettings)
+
+
self.previewWindow = PreviewWindow(self, os.path.join(
os.path.dirname(os.path.realpath(__file__)), "background.png"))
window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
@@ -159,6 +169,8 @@ class MainWindow(QtCore.QObject):
window.comboBox_resolution.setCurrentIndex(currentRes)
window.comboBox_resolution.currentIndexChanged.connect(
self.updateResolution)
+
+
self.window.pushButton_listMoveUp.clicked.connect(
self.moveComponentUp)
@@ -222,13 +234,19 @@ class MainWindow(QtCore.QObject):
def updateCodecSettings(self):
vCodecWidget = self.window.comboBox_videoCodec
+ vBitrateWidget = self.window.spinBox_vBitrate
+ aBitrateWidget = self.window.spinBox_aBitrate
aCodecWidget = self.window.comboBox_audioCodec
currentVideoCodec = vCodecWidget.currentIndex()
currentVideoCodec = vCodecWidget.itemText(currentVideoCodec)
+ currentVideoBitrate = vBitrateWidget.value()
currentAudioCodec = aCodecWidget.currentIndex()
currentAudioCodec = aCodecWidget.itemText(currentAudioCodec)
+ currentAudioBitrate = aBitrateWidget.value()
self.settings.setValue('outputVideoCodec', currentVideoCodec)
self.settings.setValue('outputAudioCodec', currentAudioCodec)
+ self.settings.setValue('outputVideoBitrate', currentVideoBitrate)
+ self.settings.setValue('outputAudioBitrate', currentAudioBitrate)
def autosave(self):
if time.time() - self.lastAutosave >= 1.0:
diff --git a/mainwindow.ui b/mainwindow.ui
index d501110..c010caf 100644
--- a/mainwindow.ui
+++ b/mainwindow.ui
@@ -697,12 +697,16 @@
- Video Bitrate
+ Video Bitrate (Kbps)
-
-
+
+
+ 99999
+
+
@@ -762,12 +766,16 @@
- Audio Bitrate
+ Audio Bitrate (Kbps)
-
-
+
+
+ 9999
+
+
diff --git a/video_thread.py b/video_thread.py
index d8694a4..f5354be 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -109,7 +109,9 @@ class Worker(QtCore.QObject):
options = self.core.encoder_options
containerName = self.core.settings.value('outputContainer')
vcodec = self.core.settings.value('outputVideoCodec')
+ vbitrate = str(self.core.settings.value('outputVideoBitrate'))+'k'
acodec = self.core.settings.value('outputAudioCodec')
+ abitrate = str(self.core.settings.value('outputAudioBitrate'))+'k'
for cont in options['containers']:
if cont['name'] == containerName:
@@ -147,7 +149,8 @@ class Worker(QtCore.QObject):
'-i', inputFile,
'-vcodec', vencoder,
'-acodec', aencoder, # output audio codec
- '-b:a', self.core.settings.value('outputAudioBitrate'),
+ '-b:v', vbitrate,
+ '-b:a', abitrate,
'-pix_fmt', self.core.settings.value('outputVideoFormat'),
'-preset', self.core.settings.value('outputPreset'),
'-f', container
--
cgit v1.2.3
From acf22900256d75c469c78efbd98139e3bfab0e93 Mon Sep 17 00:00:00 2001
From: DH4
Date: Wed, 7 Jun 2017 14:32:05 -0500
Subject: Created projects and presets button. FIXME: Hookup New Project menu
item. Hookup preset manager.
---
mainwindow.py | 60 ++++++++++++++++-------
mainwindow.ui | 147 ++++++++++++++++++++-----------------------------------
presetmanager.ui | 104 +++++++++++++++++++++++++++++++++++++++
3 files changed, 200 insertions(+), 111 deletions(-)
create mode 100644 presetmanager.ui
diff --git a/mainwindow.py b/mainwindow.py
index 78809be..27c8f6e 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -141,7 +141,6 @@ class MainWindow(QtCore.QObject):
window.spinBox_vBitrate.valueChanged.connect(self.updateCodecSettings)
window.spinBox_aBitrate.valueChanged.connect(self.updateCodecSettings)
-
self.previewWindow = PreviewWindow(self, os.path.join(
os.path.dirname(os.path.realpath(__file__)), "background.png"))
window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
@@ -169,23 +168,44 @@ class MainWindow(QtCore.QObject):
window.comboBox_resolution.setCurrentIndex(currentRes)
window.comboBox_resolution.currentIndexChanged.connect(
self.updateResolution)
-
-
self.window.pushButton_listMoveUp.clicked.connect(
self.moveComponentUp)
self.window.pushButton_listMoveDown.clicked.connect(
self.moveComponentDown)
- self.window.pushButton_savePreset.clicked.connect(
+
+ '''self.window.pushButton_savePreset.clicked.connect(
self.openSavePresetDialog)
self.window.comboBox_openPreset.currentIndexChanged.connect(
- self.openPreset)
- self.window.pushButton_saveAs.clicked.connect(
+ self.openPreset)'''
+
+ # Configure the Projects Menu
+ self.projectMenu = QMenu()
+ action = self.projectMenu.addAction("New Project")
+ action.triggered[()].connect(self.createNewProject)
+
+ action = self.projectMenu.addAction("Open Project")
+ action.triggered[()].connect(self.openOpenProjectDialog)
+
+ action = self.projectMenu.addAction("Save Project")
+ action.triggered[()].connect(self.saveCurrentProject)
+
+ action = self.projectMenu.addAction("Save Project As")
+ action.triggered[()].connect(self.openSaveProjectDialog)
+
+ self.window.pushButton_projects.setMenu(self.projectMenu)
+
+ # Configure the Presets Button
+ self.window.pushButton_presets.clicked.connect(
+ self.openPresetManager
+ )
+
+ '''self.window.pushButton_saveAs.clicked.connect(
self.openSaveProjectDialog)
self.window.pushButton_saveProject.clicked.connect(
self.saveCurrentProject)
self.window.pushButton_openProject.clicked.connect(
- self.openOpenProjectDialog)
+ self.openOpenProjectDialog)'''
# show the window and load current project
window.show()
@@ -327,10 +347,8 @@ class MainWindow(QtCore.QObject):
self.window.pushButton_removeComponent.setEnabled(False)
self.window.pushButton_listMoveDown.setEnabled(False)
self.window.pushButton_listMoveUp.setEnabled(False)
- self.window.comboBox_openPreset.setEnabled(False)
- self.window.pushButton_removePreset.setEnabled(False)
- self.window.pushButton_savePreset.setEnabled(False)
- self.window.pushButton_openProject.setEnabled(False)
+ self.window.comboBox_Presets.setEnabled(False)
+ '''self.window.pushButton_openProject.setEnabled(False)'''
self.window.listWidget_componentList.setEnabled(False)
else:
self.window.pushButton_createVideo.setEnabled(True)
@@ -348,10 +366,8 @@ class MainWindow(QtCore.QObject):
self.window.pushButton_removeComponent.setEnabled(True)
self.window.pushButton_listMoveDown.setEnabled(True)
self.window.pushButton_listMoveUp.setEnabled(True)
- self.window.comboBox_openPreset.setEnabled(True)
- self.window.pushButton_removePreset.setEnabled(True)
- self.window.pushButton_savePreset.setEnabled(True)
- self.window.pushButton_openProject.setEnabled(True)
+ self.window.comboBox_Presets.setEnabled(True)
+ '''self.window.pushButton_openProject.setEnabled(True)'''
self.window.listWidget_componentList.setEnabled(True)
def progressBarSetText(self, value):
@@ -401,7 +417,7 @@ class MainWindow(QtCore.QObject):
self.window.stackedWidget.addWidget(self.pages[-1])
self.window.stackedWidget.setCurrentIndex(index)
self.selectedComponents[-1].update()
- self.updateOpenPresetComboBox(self.selectedComponents[-1])
+ '''self.updateOpenPresetComboBox(self.selectedComponents[-1])'''
def insertComponent(self, moduleIndex):
self.selectedComponents.insert(
@@ -413,7 +429,7 @@ class MainWindow(QtCore.QObject):
self.window.stackedWidget.insertWidget(0, self.pages[0])
self.window.stackedWidget.setCurrentIndex(0)
self.selectedComponents[0].update()
- self.updateOpenPresetComboBox(self.selectedComponents[0])
+ '''self.updateOpenPresetComboBox(self.selectedComponents[0])'''
def removeComponent(self):
for selected in self.window.listWidget_componentList.selectedItems():
@@ -430,7 +446,7 @@ class MainWindow(QtCore.QObject):
if selected:
index = self.window.listWidget_componentList.row(selected[0])
self.window.stackedWidget.setCurrentIndex(index)
- self.updateOpenPresetComboBox(self.selectedComponents[index])
+ '''self.updateOpenPresetComboBox(self.selectedComponents[index])'''
def moveComponentUp(self):
row = self.window.listWidget_componentList.currentRow()
@@ -466,6 +482,11 @@ class MainWindow(QtCore.QObject):
self.window.stackedWidget.setCurrentIndex(row + 1)
self.drawPreview()
+ # Preset manager for importing, exporting, renaming,
+ # and deleting presets.
+ def openPresetManager(self):
+ return
+
def updateOpenPresetComboBox(self, component):
self.window.comboBox_openPreset.clear()
self.window.comboBox_openPreset.addItem("Component Presets")
@@ -547,6 +568,9 @@ class MainWindow(QtCore.QObject):
self.selectedComponents[index].loadPreset(saveValueStore)
self.drawPreview()
+ def createNewProject(self):
+ return
+
def saveCurrentProject(self):
if self.currentProject:
self.createProjectFile(self.currentProject)
diff --git a/mainwindow.ui b/mainwindow.ui
index c010caf..62e0632 100644
--- a/mainwindow.ui
+++ b/mainwindow.ui
@@ -108,23 +108,32 @@
QLayout::SetMinimumSize
-
-
-
- Open Project
+
+
+ Qt::Horizontal
-
+
+ QSizePolicy::Fixed
+
+
+
+ 140
+ 20
+
+
+
-
-
+
- Save Project
+ Projects
-
-
+
- Save As
+ Presets
@@ -141,53 +150,11 @@
20
- 15
+ 2
- -
-
-
-
-
-
- Add
-
-
-
- -
-
-
- Remove
-
-
-
- -
-
-
- Up
-
-
-
- -
-
-
- Down
-
-
-
-
-
-
-
- -
-
-
- 4
-
-
- 2
-
-
@@ -231,54 +198,48 @@
+ -
+
+
-
+
+
+ Add
+
+
+
+ -
+
+
+ Remove
+
+
+
+ -
+
+
+ Up
+
+
+
+ -
+
+
+ Down
+
+
+
+
+
-
-
+
+ 4
+
+
2
-
-
-
-
-
- 0
- 0
-
-
-
-
- 180
- 0
-
-
-
-
-
- Component Presets
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Save
-
-
-
- -
-
-
- Remove
-
-
-
diff --git a/presetmanager.ui b/presetmanager.ui
new file mode 100644
index 0000000..7496169
--- /dev/null
+++ b/presetmanager.ui
@@ -0,0 +1,104 @@
+
+
+ presetmanager
+
+
+
+ 0
+ 0
+ 542
+ 360
+
+
+
+ Preset Manager
+
+
+ -
+
+
-
+
+
+ Search
+
+
+
+ -
+
+
+
+ 200
+ 0
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+ -
+
+
+ QLayout::SetMinimumSize
+
+
-
+
+
+ Import
+
+
+
+ -
+
+
+ Export
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Rename
+
+
+
+ -
+
+
+ Delete
+
+
+
+
+
+
+
+
+
+
--
cgit v1.2.3
From 6f2f02b709d2ea789b39b68f9a21a1a35d421075 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Wed, 7 Jun 2017 17:09:28 -0400
Subject: newProject method & various small fixes
---
components/video.py | 5 ++++-
mainwindow.py | 38 +++++++++++++++++++-------------------
2 files changed, 23 insertions(+), 20 deletions(-)
diff --git a/components/video.py b/components/video.py
index b28b81e..f086cc0 100644
--- a/components/video.py
+++ b/components/video.py
@@ -5,7 +5,8 @@ import subprocess
import threading
from queue import PriorityQueue
from . import __base__
-
+
+
class Video:
'''Video Component Frame-Fetcher'''
def __init__(self, **kwargs):
@@ -129,10 +130,12 @@ class Component(__base__.Component):
def loadPreset(self, pr):
self.page.lineEdit_video.setText(pr['video'])
+ self.page.checkBox_loop.setChecked(pr['loop'])
def savePreset(self):
return {
'video': self.videoPath,
+ 'loop': self.loopVideo,
}
def pickVideo(self):
diff --git a/mainwindow.py b/mainwindow.py
index 27c8f6e..7ea1410 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -133,7 +133,7 @@ class MainWindow(QtCore.QObject):
)
vBitrate = int(self.settings.value('outputVideoBitrate'))
- aBitrate = int(self.settings.value('outputAudioBitrate'))
+ aBitrate = int(self.settings.value('outputAudioBitrate')[:-1])
window.spinBox_vBitrate.setValue(vBitrate)
window.spinBox_aBitrate.setValue(aBitrate)
@@ -269,7 +269,7 @@ class MainWindow(QtCore.QObject):
self.settings.setValue('outputAudioBitrate', currentAudioBitrate)
def autosave(self):
- if time.time() - self.lastAutosave >= 1.0:
+ if time.time() - self.lastAutosave >= 2.0:
if os.path.exists(self.autosavePath):
os.remove(self.autosavePath)
self.createProjectFile(self.autosavePath)
@@ -347,8 +347,7 @@ class MainWindow(QtCore.QObject):
self.window.pushButton_removeComponent.setEnabled(False)
self.window.pushButton_listMoveDown.setEnabled(False)
self.window.pushButton_listMoveUp.setEnabled(False)
- self.window.comboBox_Presets.setEnabled(False)
- '''self.window.pushButton_openProject.setEnabled(False)'''
+ self.window.pushButton_presets.setEnabled(False)
self.window.listWidget_componentList.setEnabled(False)
else:
self.window.pushButton_createVideo.setEnabled(True)
@@ -366,8 +365,7 @@ class MainWindow(QtCore.QObject):
self.window.pushButton_removeComponent.setEnabled(True)
self.window.pushButton_listMoveDown.setEnabled(True)
self.window.pushButton_listMoveUp.setEnabled(True)
- self.window.comboBox_Presets.setEnabled(True)
- '''self.window.pushButton_openProject.setEnabled(True)'''
+ self.window.pushButton_presets.setEnabled(True)
self.window.listWidget_componentList.setEnabled(True)
def progressBarSetText(self, value):
@@ -569,7 +567,14 @@ class MainWindow(QtCore.QObject):
self.drawPreview()
def createNewProject(self):
- return
+ self.currentProject = None
+ self.selectedComponents = []
+ self.window.listWidget_componentList.clear()
+ for widget in self.pages:
+ self.window.stackedWidget.removeWidget(widget)
+ self.pages = []
+ self.settings.setValue("currentProject", None)
+ self.drawPreview()
def saveCurrentProject(self):
if self.currentProject:
@@ -613,7 +618,7 @@ class MainWindow(QtCore.QObject):
if not filepath or not os.path.exists(filepath) \
or not filepath.endswith('.avp'):
return
- self.clear()
+ self.createNewProject()
self.currentProject = filepath
self.settings.setValue("currentProject", filepath)
self.settings.setValue("projectDir", os.path.dirname(filepath))
@@ -652,14 +657,17 @@ class MainWindow(QtCore.QObject):
self.selectedComponents[-1].loadPreset(
saveValueStore)
i = 0
- except (IndexError, ValueError, KeyError, NameError,
- SyntaxError, AttributeError, TypeError) as e:
- self.clear()
+ except (IndexError, ValueError, NameError, SyntaxError,
+ AttributeError, TypeError) as e:
+ self.createNewProject()
typ, value, _ = sys.exc_info()
msg = '%s: %s' % (typ.__name__, value)
self.showMessage(
"Project file '%s' is corrupted." % filepath, False,
QtGui.QMessageBox.Warning, msg)
+ except KeyError as e:
+ # probably just an old version, still loadable
+ print('project file missing value: %s' % e)
def showMessage(
self, string, showCancel=False,
@@ -677,11 +685,3 @@ class MainWindow(QtCore.QObject):
if ch == 1024:
return True
return False
-
- def clear(self):
- ''' empty out all components and fields, get a blank slate '''
- self.selectedComponents = []
- self.window.listWidget_componentList.clear()
- for widget in self.pages:
- self.window.stackedWidget.removeWidget(widget)
- self.pages = []
--
cgit v1.2.3
From 6093e701e151af96464b564e275db4664d828a82 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Wed, 7 Jun 2017 20:30:37 -0400
Subject: laying some foundations for new preset implementation
---
mainwindow.py | 88 +++++++++++++++++++++++++++++++++-----------------------
presetmanager.py | 70 ++++++++++++++++++++++++++++++++++++++++++++
presetmanager.ui | 17 ++++++-----
3 files changed, 132 insertions(+), 43 deletions(-)
create mode 100644 presetmanager.py
diff --git a/mainwindow.py b/mainwindow.py
index 7ea1410..2c1a95a 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -2,7 +2,7 @@ from os.path import expanduser
from queue import Queue
from importlib import import_module
from collections import OrderedDict
-from PyQt4 import QtCore, QtGui
+from PyQt4 import QtCore, QtGui, uic
from PyQt4.QtCore import QSettings, Qt
from PyQt4.QtGui import QDesktopServices, QMenu
import sys
@@ -16,6 +16,7 @@ import time
import core
import preview_thread
import video_thread
+from presetmanager import PresetManager
from main import LoadDefaultSettings
@@ -59,7 +60,7 @@ class MainWindow(QtCore.QObject):
self.selectedComponents = []
self.lastAutosave = time.time()
- # create data directory, load/create settings
+ # Create data directory, load/create settings
self.dataDir = QDesktopServices.storageLocation(
QDesktopServices.DataLocation)
self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
@@ -74,7 +75,13 @@ class MainWindow(QtCore.QObject):
if not os.path.exists(neededDirectory):
os.mkdir(neededDirectory)
- #
+ self.presetManager = PresetManager(
+ uic.loadUi(
+ os.path.join(os.path.dirname(os.path.realpath(__file__)),
+ 'presetmanager.ui')),
+ self)
+
+ # Make queues/timers for the preview thread
self.previewQueue = Queue()
self.previewThread = QtCore.QThread(self)
self.previewWorker = preview_thread.Worker(self, self.previewQueue)
@@ -86,7 +93,7 @@ class MainWindow(QtCore.QObject):
self.timer.timeout.connect(self.processTask.emit)
self.timer.start(500)
- # begin decorating the window and connecting events
+ # Begin decorating the window and connecting events
window.toolButton_selectAudioFile.clicked.connect(
self.openInputFileDialog)
@@ -145,6 +152,7 @@ class MainWindow(QtCore.QObject):
os.path.dirname(os.path.realpath(__file__)), "background.png"))
window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
+ # Make component buttons
self.modules = self.findComponents()
self.compMenu = QMenu()
for i, comp in enumerate(self.modules):
@@ -153,12 +161,20 @@ class MainWindow(QtCore.QObject):
lambda item=i: self.insertComponent(item))
self.window.pushButton_addComponent.setMenu(self.compMenu)
+
window.listWidget_componentList.clicked.connect(
lambda _: self.changeComponentWidget())
self.window.pushButton_removeComponent.clicked.connect(
lambda _: self.removeComponent())
+ self.window.listWidget_componentList.setContextMenuPolicy(
+ QtCore.Qt.CustomContextMenu)
+ self.window.listWidget_componentList.connect(
+ self.window.listWidget_componentList,
+ QtCore.SIGNAL("customContextMenuRequested(QPoint)"),
+ self.componentContextMenu)
+
currentRes = str(self.settings.value('outputWidth'))+'x' + \
str(self.settings.value('outputHeight'))
for i, res in enumerate(self.resolutions):
@@ -174,11 +190,6 @@ class MainWindow(QtCore.QObject):
self.window.pushButton_listMoveDown.clicked.connect(
self.moveComponentDown)
- '''self.window.pushButton_savePreset.clicked.connect(
- self.openSavePresetDialog)
- self.window.comboBox_openPreset.currentIndexChanged.connect(
- self.openPreset)'''
-
# Configure the Projects Menu
self.projectMenu = QMenu()
action = self.projectMenu.addAction("New Project")
@@ -200,14 +211,7 @@ class MainWindow(QtCore.QObject):
self.openPresetManager
)
- '''self.window.pushButton_saveAs.clicked.connect(
- self.openSaveProjectDialog)
- self.window.pushButton_saveProject.clicked.connect(
- self.saveCurrentProject)
- self.window.pushButton_openProject.clicked.connect(
- self.openOpenProjectDialog)'''
-
- # show the window and load current project
+ # Show the window and load current project
window.show()
self.currentProject = self.settings.value("currentProject")
if self.currentProject and os.path.exists(self.autosavePath) \
@@ -217,8 +221,9 @@ class MainWindow(QtCore.QObject):
if self.currentProject and os.path.exists(self.autosavePath):
ch = self.showMessage(
- "Restore unsaved changes in project '%s'?"
- % os.path.basename(self.currentProject)[:-4], True)
+ msg="Restore unsaved changes in project '%s'?"
+ % os.path.basename(self.currentProject)[:-4],
+ showCancel=True)
if ch:
os.remove(self.currentProject)
os.rename(self.autosavePath, self.currentProject)
@@ -325,7 +330,7 @@ class MainWindow(QtCore.QObject):
self.selectedComponents)
else:
self.showMessage(
- "You must select an audio file and output filename.")
+ msg="You must select an audio file and output filename.")
def progressBarUpdated(self, value):
self.window.progressBar_createVideo.setValue(value)
@@ -480,10 +485,9 @@ class MainWindow(QtCore.QObject):
self.window.stackedWidget.setCurrentIndex(row + 1)
self.drawPreview()
- # Preset manager for importing, exporting, renaming,
- # and deleting presets.
def openPresetManager(self):
- return
+ '''Preset manager for importing, exporting, renaming, deleting'''
+ self.presetManager.show()
def updateOpenPresetComboBox(self, component):
self.window.comboBox_openPreset.clear()
@@ -507,8 +511,8 @@ class MainWindow(QtCore.QObject):
badName = True
if badName:
# some filesystems don't like bizarre characters
- self.showMessage("Preset names must contain only letters, \
- numbers, and spaces.")
+ self.showMessage(msg="Preset names must contain only \
+ letters, numbers, and spaces.")
continue
if OK and newName:
index = self.window.listWidget_componentList.currentRow()
@@ -529,8 +533,8 @@ class MainWindow(QtCore.QObject):
filepath = os.path.join(dirname, filename)
if os.path.exists(filepath):
ch = self.showMessage(
- "%s already exists! Overwrite it?" % filename,
- True, QtGui.QMessageBox.Warning)
+ msg="%s already exists! Overwrite it?" % filename,
+ showCancel=True, icon=QtGui.QMessageBox.Warning)
if not ch:
return
# remove old copies of the preset
@@ -663,20 +667,20 @@ class MainWindow(QtCore.QObject):
typ, value, _ = sys.exc_info()
msg = '%s: %s' % (typ.__name__, value)
self.showMessage(
- "Project file '%s' is corrupted." % filepath, False,
- QtGui.QMessageBox.Warning, msg)
+ msg="Project file '%s' is corrupted." % filepath,
+ showCancel=False,
+ icon=QtGui.QMessageBox.Warning,
+ detail=msg)
except KeyError as e:
# probably just an old version, still loadable
print('project file missing value: %s' % e)
- def showMessage(
- self, string, showCancel=False,
- icon=QtGui.QMessageBox.Information, detail=None):
+ def showMessage(self, **kwargs):
msg = QtGui.QMessageBox()
- msg.setIcon(icon)
- msg.setText(string)
- msg.setDetailedText(detail)
- if showCancel:
+ msg.setText(kwargs['msg'])
+ msg.setIcon(kwargs['icon'] if 'icon' in kwargs else QtGui.QMessageBox.Information)
+ msg.setDetailedText(kwargs['detail'] if 'detail' in kwargs else None)
+ if 'showCancel'in kwargs and kwargs['showCancel']:
msg.setStandardButtons(
QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel)
else:
@@ -685,3 +689,15 @@ class MainWindow(QtCore.QObject):
if ch == 1024:
return True
return False
+
+ def componentContextMenu(self, QPos):
+ self.menu = QtGui.QMenu()
+ menuItem = self.menu.addAction("Save Preset")
+ self.connect(menuItem, QtCore.SIGNAL("triggered()"), self.openSavePresetDialog)
+ parentPosition = self.window.listWidget_componentList.mapToGlobal(QtCore.QPoint(0, 0))
+ self.menu.move(parentPosition + QPos)
+ self.menu.show()
+
+ def menuItemClicked(self):
+ currentItemName=str(self.window.listWidget_componentList.currentItem().text() )
+ print(currentItemName)
diff --git a/presetmanager.py b/presetmanager.py
new file mode 100644
index 0000000..cc6c482
--- /dev/null
+++ b/presetmanager.py
@@ -0,0 +1,70 @@
+from PyQt4 import QtGui
+#import sys
+import os
+
+class PresetManager(QtGui.QDialog):
+ def __init__(self, window, parent):
+ super().__init__()
+ self.parent = parent
+ self.presetDir = parent.presetDir
+ self.window = window
+ self.presets = self.findPresets()
+
+ # create filter box and preset list
+ self.drawFilterList()
+ self.window.comboBox_filter.currentIndexChanged.connect(
+ lambda: self.drawPresetList(self.window.comboBox_filter.currentText())
+ )
+ self.drawPresetList('*')
+
+ # make auto-completion for search bar
+ self.autocomplete = QtGui.QStringListModel()
+ completer = QtGui.QCompleter()
+ completer.setModel(self.autocomplete)
+ self.window.lineEdit_search.setCompleter(completer)
+
+ def show(self):
+ presetNames = []
+ for presetList in self.presets.values():
+ for preset in presetList:
+ presetNames.append(preset[1])
+ self.autocomplete.setStringList(presetNames)
+ self.presets = self.findPresets()
+ self.drawFilterList()
+ self.drawPresetList('*')
+ self.window.show()
+
+ def findPresets(self):
+ parseList = []
+ for dirpath, dirnames, filenames in os.walk(self.presetDir):
+ # anything without a subdirectory must be a preset folder
+ if dirnames:
+ continue
+ for preset in filenames:
+ compName = os.path.basename(os.path.dirname(dirpath))
+ compVers = os.path.basename(dirpath)
+ try:
+ parseList.append((compName, int(compVers), preset))
+ except ValueError:
+ continue
+ return { compName : \
+ [ (vers, preset) \
+ for name, vers, preset in parseList \
+ if name == compName \
+ ] \
+ for compName, _, __ in parseList \
+ }
+
+ def drawPresetList(self, filter):
+ self.window.listWidget_presets.clear()
+ for component, presets in self.presets.items():
+ if filter != '*' and component != filter:
+ continue
+ for vers, preset in presets:
+ self.window.listWidget_presets.addItem('%s: %s' % (component, preset))
+
+ def drawFilterList(self):
+ self.window.comboBox_filter.clear()
+ self.window.comboBox_filter.addItem('*')
+ for component in self.presets:
+ self.window.comboBox_filter.addItem(component)
diff --git a/presetmanager.ui b/presetmanager.ui
index 7496169..610de91 100644
--- a/presetmanager.ui
+++ b/presetmanager.ui
@@ -17,14 +17,17 @@
-
-
-
+
+
+
+
Search
-
-
+
200
@@ -38,7 +41,7 @@
-
-
-
+
0
@@ -55,14 +58,14 @@
QLayout::SetMinimumSize
-
-
+
Import
-
-
+
Export
@@ -82,14 +85,14 @@
-
-
+
Rename
-
-
+
Delete
--
cgit v1.2.3
From 292d21c20372634f4d0cabf43611d0e50386bc4c Mon Sep 17 00:00:00 2001
From: tassaron
Date: Wed, 7 Jun 2017 23:22:55 -0400
Subject: added submenu for opening presets, moved code
---
components/__base__.py | 6 ++-
components/color.py | 3 +-
components/image.py | 5 +-
components/original.py | 3 +-
components/text.py | 3 +-
components/video.py | 5 +-
mainwindow.py | 138 +++++++++++++------------------------------------
presetmanager.py | 98 ++++++++++++++++++++++++++++++-----
8 files changed, 140 insertions(+), 121 deletions(-)
diff --git a/components/__base__.py b/components/__base__.py
index 4fdf31f..b95edf4 100644
--- a/components/__base__.py
+++ b/components/__base__.py
@@ -2,6 +2,9 @@ from PyQt4 import QtGui
class Component:
+ def __init__(self):
+ self.currentPreset = None
+
def __str__(self):
return self.__doc__
@@ -72,7 +75,8 @@ class Component:
image = Image.new("RGBA", (width, height), (0,0,0,0))
return image
- def loadPreset(self, presetDict):
+ def loadPreset(self, presetDict, presetName=None):
+ self.currentPreset = presetName
# update widgets using a preset dict
def savePreset(self):
diff --git a/components/color.py b/components/color.py
index b050fbd..b13f54e 100644
--- a/components/color.py
+++ b/components/color.py
@@ -69,7 +69,8 @@ class Component(__base__.Component):
r, g, b = self.color1
return Image.new("RGBA", (width, height), (r, g, b, 255))
- def loadPreset(self, pr):
+ def loadPreset(self, pr, presetName=None):
+ self.currentPreset = presetName
self.page.lineEdit_color1.setText('%s,%s,%s' % pr['color1'])
self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2'])
diff --git a/components/image.py b/components/image.py
index f9a92ca..6ccddb6 100644
--- a/components/image.py
+++ b/components/image.py
@@ -48,7 +48,8 @@ class Component(__base__.Component):
frame.paste(image)
return frame
- def loadPreset(self, pr):
+ def loadPreset(self, pr, presetName=None):
+ self.currentPreset = presetName
self.page.lineEdit_image.setText(pr['image'])
def savePreset(self):
@@ -60,7 +61,7 @@ class Component(__base__.Component):
imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
filename = QtGui.QFileDialog.getOpenFileName(
self.page, "Choose Image", imgDir, "Image Files (*.jpg *.png)")
- if filename:
+ if filename:
self.settings.setValue("backgroundDir", os.path.dirname(filename))
self.page.lineEdit_image.setText(filename)
self.update()
diff --git a/components/original.py b/components/original.py
index 4d0e83b..a2059d1 100644
--- a/components/original.py
+++ b/components/original.py
@@ -37,7 +37,8 @@ class Component(__base__.Component):
self.visColor = self.RGBFromString(self.page.lineEdit_visColor.text())
self.parent.drawPreview()
- def loadPreset(self, pr):
+ def loadPreset(self, pr, presetName=None):
+ self.currentPreset = presetName
self.page.lineEdit_visColor.setText('%s,%s,%s' % pr['visColor'])
btnStyle = "QPushButton { background-color : %s; outline: none; }" \
% QColor(*pr['visColor']).name()
diff --git a/components/text.py b/components/text.py
index 6cdc0dd..1725a41 100644
--- a/components/text.py
+++ b/components/text.py
@@ -78,7 +78,8 @@ class Component(__base__.Component):
x = self.xPosition - offset
return x, self.yPosition
- def loadPreset(self, pr):
+ def loadPreset(self, pr, presetName=None):
+ self.currentPreset = presetName
self.page.lineEdit_title.setText(pr['title'])
font = QFont()
font.fromString(pr['titleFont'])
diff --git a/components/video.py b/components/video.py
index f086cc0..e636224 100644
--- a/components/video.py
+++ b/components/video.py
@@ -128,7 +128,8 @@ class Component(__base__.Component):
def frameRender(self, moduleNo, arrayNo, frameNo):
return self.video.frame(frameNo)
- def loadPreset(self, pr):
+ def loadPreset(self, pr, presetName=None):
+ self.currentPreset = presetName
self.page.lineEdit_video.setText(pr['video'])
self.page.checkBox_loop.setChecked(pr['loop'])
@@ -144,7 +145,7 @@ class Component(__base__.Component):
self.page, "Choose Video",
imgDir, "Video Files (*.mp4 *.mov)"
)
- if filename:
+ if filename:
self.settings.setValue("backgroundDir", os.path.dirname(filename))
self.page.lineEdit_video.setText(filename)
self.update()
diff --git a/mainwindow.py b/mainwindow.py
index 2c1a95a..883d475 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -6,9 +6,7 @@ from PyQt4 import QtCore, QtGui, uic
from PyQt4.QtCore import QSettings, Qt
from PyQt4.QtGui import QDesktopServices, QMenu
import sys
-import io
import os
-import string
import signal
import filecmp
import time
@@ -63,24 +61,22 @@ class MainWindow(QtCore.QObject):
# Create data directory, load/create settings
self.dataDir = QDesktopServices.storageLocation(
QDesktopServices.DataLocation)
+ self.presetManager = PresetManager(
+ uic.loadUi(
+ os.path.join(os.path.dirname(os.path.realpath(__file__)),
+ 'presetmanager.ui')),
+ self)
self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
- self.presetDir = os.path.join(self.dataDir, 'presets')
self.settings = QSettings(
os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat)
LoadDefaultSettings(self)
if not os.path.exists(self.dataDir):
os.makedirs(self.dataDir)
for neededDirectory in (
- self.presetDir, self.settings.value("projectDir")):
+ self.presetManager.presetDir, self.settings.value("projectDir")):
if not os.path.exists(neededDirectory):
os.mkdir(neededDirectory)
- self.presetManager = PresetManager(
- uic.loadUi(
- os.path.join(os.path.dirname(os.path.realpath(__file__)),
- 'presetmanager.ui')),
- self)
-
# Make queues/timers for the preview thread
self.previewQueue = Queue()
self.previewThread = QtCore.QThread(self)
@@ -140,7 +136,7 @@ class MainWindow(QtCore.QObject):
)
vBitrate = int(self.settings.value('outputVideoBitrate'))
- aBitrate = int(self.settings.value('outputAudioBitrate')[:-1])
+ aBitrate = int(self.settings.value('outputAudioBitrate'))
window.spinBox_vBitrate.setValue(vBitrate)
window.spinBox_aBitrate.setValue(aBitrate)
@@ -420,7 +416,6 @@ class MainWindow(QtCore.QObject):
self.window.stackedWidget.addWidget(self.pages[-1])
self.window.stackedWidget.setCurrentIndex(index)
self.selectedComponents[-1].update()
- '''self.updateOpenPresetComboBox(self.selectedComponents[-1])'''
def insertComponent(self, moduleIndex):
self.selectedComponents.insert(
@@ -432,7 +427,6 @@ class MainWindow(QtCore.QObject):
self.window.stackedWidget.insertWidget(0, self.pages[0])
self.window.stackedWidget.setCurrentIndex(0)
self.selectedComponents[0].update()
- '''self.updateOpenPresetComboBox(self.selectedComponents[0])'''
def removeComponent(self):
for selected in self.window.listWidget_componentList.selectedItems():
@@ -449,7 +443,6 @@ class MainWindow(QtCore.QObject):
if selected:
index = self.window.listWidget_componentList.row(selected[0])
self.window.stackedWidget.setCurrentIndex(index)
- '''self.updateOpenPresetComboBox(self.selectedComponents[index])'''
def moveComponentUp(self):
row = self.window.listWidget_componentList.currentRow()
@@ -489,87 +482,6 @@ class MainWindow(QtCore.QObject):
'''Preset manager for importing, exporting, renaming, deleting'''
self.presetManager.show()
- def updateOpenPresetComboBox(self, component):
- self.window.comboBox_openPreset.clear()
- self.window.comboBox_openPreset.addItem("Component Presets")
- destination = os.path.join(
- self.presetDir, str(component).strip(), str(component.version()))
- if not os.path.exists(destination):
- os.makedirs(destination)
- for f in os.listdir(destination):
- self.window.comboBox_openPreset.addItem(f)
-
- def openSavePresetDialog(self):
- if self.window.listWidget_componentList.currentRow() == -1:
- return
- while True:
- newName, OK = QtGui.QInputDialog.getText(
- QtGui.QWidget(), 'Audio Visualizer', 'New Preset Name:')
- badName = False
- for letter in newName:
- if letter in string.punctuation:
- badName = True
- if badName:
- # some filesystems don't like bizarre characters
- self.showMessage(msg="Preset names must contain only \
- letters, numbers, and spaces.")
- continue
- if OK and newName:
- index = self.window.listWidget_componentList.currentRow()
- if index != -1:
- saveValueStore = \
- self.selectedComponents[index].savePreset()
- componentName = str(self.selectedComponents[index]).strip()
- vers = self.selectedComponents[index].version()
- self.createPresetFile(
- componentName, vers, saveValueStore, newName)
- break
-
- def createPresetFile(
- self, componentName, version, saveValueStore, filename):
- dirname = os.path.join(self.presetDir, componentName, str(version))
- if not os.path.exists(dirname):
- os.makedirs(dirname)
- filepath = os.path.join(dirname, filename)
- if os.path.exists(filepath):
- ch = self.showMessage(
- msg="%s already exists! Overwrite it?" % filename,
- showCancel=True, icon=QtGui.QMessageBox.Warning)
- if not ch:
- return
- # remove old copies of the preset
- for i in range(0, self.window.comboBox_openPreset.count()):
- if self.window.comboBox_openPreset.itemText(i) == filename:
- self.window.comboBox_openPreset.removeItem(i)
- with open(filepath, 'w') as f:
- f.write(core.Core.stringOrderedDict(saveValueStore))
- self.window.comboBox_openPreset.addItem(filename)
- self.window.comboBox_openPreset.setCurrentIndex(
- self.window.comboBox_openPreset.count()-1)
-
- def openPreset(self):
- if self.window.comboBox_openPreset.currentIndex() < 1:
- return
- index = self.window.listWidget_componentList.currentRow()
- if index == -1:
- return
- filename = self.window.comboBox_openPreset.itemText(
- self.window.comboBox_openPreset.currentIndex())
- componentName = str(self.selectedComponents[index]).strip()
- version = self.selectedComponents[index].version()
- dirname = os.path.join(self.presetDir, componentName, str(version))
- filepath = os.path.join(dirname, filename)
- if not os.path.exists(filepath):
- self.window.comboBox_openPreset.removeItem(
- self.window.comboBox_openPreset.currentIndex())
- return
- with open(filepath, 'r') as f:
- for line in f:
- saveValueStore = dict(eval(line.strip()))
- break
- self.selectedComponents[index].loadPreset(saveValueStore)
- self.drawPreview()
-
def createNewProject(self):
self.currentProject = None
self.selectedComponents = []
@@ -678,7 +590,8 @@ class MainWindow(QtCore.QObject):
def showMessage(self, **kwargs):
msg = QtGui.QMessageBox()
msg.setText(kwargs['msg'])
- msg.setIcon(kwargs['icon'] if 'icon' in kwargs else QtGui.QMessageBox.Information)
+ msg.setIcon(
+ kwargs['icon'] if 'icon' in kwargs else QtGui.QMessageBox.Information)
msg.setDetailedText(kwargs['detail'] if 'detail' in kwargs else None)
if 'showCancel'in kwargs and kwargs['showCancel']:
msg.setStandardButtons(
@@ -691,13 +604,36 @@ class MainWindow(QtCore.QObject):
return False
def componentContextMenu(self, QPos):
+ '''Appears when right-clicking a component in the list'''
+ if not self.window.listWidget_componentList.selectedItems():
+ return
+
+ self.presetManager.findPresets()
self.menu = QtGui.QMenu()
menuItem = self.menu.addAction("Save Preset")
- self.connect(menuItem, QtCore.SIGNAL("triggered()"), self.openSavePresetDialog)
+ self.connect(
+ menuItem,
+ QtCore.SIGNAL("triggered()"),
+ self.presetManager.openSavePresetDialog
+ )
+
+ # submenu for opening presets
+ index = self.window.listWidget_componentList.currentRow()
+ try:
+ presets = self.presetManager.presets[str(self.selectedComponents[index])]
+ self.submenu = QtGui.QMenu("Open Preset")
+ self.menu.addMenu(self.submenu)
+
+ for version, presetName in presets:
+ menuItem = self.submenu.addAction(presetName)
+ self.connect(
+ menuItem,
+ QtCore.SIGNAL("triggered()"),
+ lambda presetName=presetName:
+ self.presetManager.openPreset(presetName)
+ )
+ except KeyError as e:
+ print(e)
parentPosition = self.window.listWidget_componentList.mapToGlobal(QtCore.QPoint(0, 0))
self.menu.move(parentPosition + QPos)
self.menu.show()
-
- def menuItemClicked(self):
- currentItemName=str(self.window.listWidget_componentList.currentItem().text() )
- print(currentItemName)
diff --git a/presetmanager.py b/presetmanager.py
index cc6c482..f67dbb9 100644
--- a/presetmanager.py
+++ b/presetmanager.py
@@ -1,14 +1,19 @@
from PyQt4 import QtGui
-#import sys
+from collections import OrderedDict
+import string
import os
+import core
+
+
class PresetManager(QtGui.QDialog):
def __init__(self, window, parent):
super().__init__()
self.parent = parent
- self.presetDir = parent.presetDir
+ self.presetDir = os.path.join(self.parent.dataDir, 'presets')
self.window = window
- self.presets = self.findPresets()
+ self.findPresets()
+ self.lastFilter = '*'
# create filter box and preset list
self.drawFilterList()
@@ -29,7 +34,7 @@ class PresetManager(QtGui.QDialog):
for preset in presetList:
presetNames.append(preset[1])
self.autocomplete.setStringList(presetNames)
- self.presets = self.findPresets()
+ self.findPresets()
self.drawFilterList()
self.drawPresetList('*')
self.window.show()
@@ -47,16 +52,23 @@ class PresetManager(QtGui.QDialog):
parseList.append((compName, int(compVers), preset))
except ValueError:
continue
- return { compName : \
- [ (vers, preset) \
- for name, vers, preset in parseList \
- if name == compName \
- ] \
- for compName, _, __ in parseList \
- }
+ self.presets =\
+ {
+ compName : \
+ [
+ (vers, preset) \
+ for name, vers, preset in parseList \
+ if name == compName \
+ ] \
+ for compName, _, __ in parseList \
+ }
- def drawPresetList(self, filter):
+ def drawPresetList(self, filter=None):
self.window.listWidget_presets.clear()
+ if filter:
+ self.lastFilter = str(filter)
+ else:
+ filter = str(self.lastFilter)
for component, presets in self.presets.items():
if filter != '*' and component != filter:
continue
@@ -68,3 +80,65 @@ class PresetManager(QtGui.QDialog):
self.window.comboBox_filter.addItem('*')
for component in self.presets:
self.window.comboBox_filter.addItem(component)
+
+ def openSavePresetDialog(self):
+ window = self.parent.window
+ if window.listWidget_componentList.currentRow() == -1:
+ return
+ while True:
+ dialog = QtGui.QInputDialog(
+ QtGui.QWidget(), 'Audio Visualizer', 'New Preset Name:')
+ dialog.setTextValue()
+ newName, OK = dialog.getText()
+ badName = False
+ for letter in newName:
+ if letter in string.punctuation:
+ badName = True
+ if badName:
+ # some filesystems don't like bizarre characters
+ self.parent.showMessage(msg=\
+'''Preset names must contain only letters, numbers, and spaces.''')
+ continue
+ if OK and newName:
+ index = window.listWidget_componentList.currentRow()
+ if index != -1:
+ saveValueStore = \
+ self.parent.selectedComponents[index].savePreset()
+ componentName = str(self.parent.selectedComponents[index]).strip()
+ vers = self.parent.selectedComponents[index].version()
+ self.createPresetFile(
+ componentName, vers, saveValueStore, newName)
+ break
+
+ def createPresetFile(self, compName, vers, saveValueStore, filename):
+ dirname = os.path.join(self.presetDir, compName, str(vers))
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+ filepath = os.path.join(dirname, filename)
+ if os.path.exists(filepath):
+ ch = self.parent.showMessage(
+ msg="%s already exists! Overwrite it?" % filename,
+ showCancel=True, icon=QtGui.QMessageBox.Warning)
+ if not ch:
+ return
+ with open(filepath, 'w') as f:
+ f.write(core.Core.stringOrderedDict(saveValueStore))
+ self.drawPresetList()
+
+ def openPreset(self, presetName):
+ index = self.parent.window.listWidget_componentList.currentRow()
+ if index == -1:
+ return
+ componentName = str(self.parent.selectedComponents[index]).strip()
+ version = self.parent.selectedComponents[index].version()
+ dirname = os.path.join(self.presetDir, componentName, str(version))
+ filepath = os.path.join(dirname, presetName)
+ if not os.path.exists(filepath):
+ return
+ with open(filepath, 'r') as f:
+ for line in f:
+ saveValueStore = dict(eval(line.strip()))
+ break
+ self.parent.selectedComponents[index].loadPreset(saveValueStore)
+ self.parent.drawPreview()
+
--
cgit v1.2.3
From 6079c4fd24aecf2ecfed0528c1427a74d596993f Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 8 Jun 2017 09:56:57 -0400
Subject: drag'n'drop componentList, move component code to core.py
FIXME: finish implementing drag'n'drop, Down button
---
core.py | 32 ++++++++++++++++
mainwindow.py | 114 ++++++++++++++++++++++++-------------------------------
mainwindow.ui | 11 ++++--
presetmanager.py | 56 ++++++++++++++++-----------
4 files changed, 122 insertions(+), 91 deletions(-)
diff --git a/core.py b/core.py
index 7b3c69a..0fa5ec5 100644
--- a/core.py
+++ b/core.py
@@ -12,6 +12,7 @@ import atexit
import time
from collections import OrderedDict
import json
+from importlib import import_module
class Core():
@@ -26,6 +27,37 @@ class Core():
self.wd = os.path.dirname(os.path.realpath(__file__))
self.loadEncoderOptions()
+ self.modules = self.findComponents()
+ self.selectedComponents = []
+
+ def findComponents(self):
+ def findComponents():
+ srcPath = os.path.join(self.wd, 'components')
+ if os.path.exists(srcPath):
+ for f in sorted(os.listdir(srcPath)):
+ name, ext = os.path.splitext(f)
+ if name.startswith("__"):
+ continue
+ elif ext == '.py':
+ yield name
+ return [
+ import_module('components.%s' % name)
+ for name in findComponents()]
+
+ def insertComponent(self, compPos, moduleIndex):
+ self.selectedComponents.insert(
+ compPos,
+ self.modules[moduleIndex].Component())
+ return compPos #if compPos > -1 else len(self.selectedComponents)-1
+
+ def moveComponent(self, startI, endI):
+ comp = self.selectedComponents.pop(startI)
+ i = self.selectedComponents.insert(endI, comp)
+ return i
+
+ def updateComponent(self, i):
+ self.selectedComponents[i].update()
+
def loadEncoderOptions(self):
file_path = os.path.join(self.wd, 'encoder-options.json')
with open(file_path) as json_file:
diff --git a/mainwindow.py b/mainwindow.py
index 883d475..f24fc66 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -1,6 +1,5 @@
from os.path import expanduser
from queue import Queue
-from importlib import import_module
from collections import OrderedDict
from PyQt4 import QtCore, QtGui, uic
from PyQt4.QtCore import QSettings, Qt
@@ -54,8 +53,9 @@ class MainWindow(QtCore.QObject):
# print('main thread id: {}'.format(QtCore.QThread.currentThreadId()))
self.window = window
self.core = core.Core()
- self.pages = []
- self.selectedComponents = []
+
+ self.pages = [] # widgets of component settings
+ self.componentRows = {} # QListWidgetItems
self.lastAutosave = time.time()
# Create data directory, load/create settings
@@ -149,16 +149,16 @@ class MainWindow(QtCore.QObject):
window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
# Make component buttons
- self.modules = self.findComponents()
self.compMenu = QMenu()
- for i, comp in enumerate(self.modules):
+ for i, comp in enumerate(self.core.modules):
action = self.compMenu.addAction(comp.Component.__doc__)
action.triggered[()].connect(
lambda item=i: self.insertComponent(item))
self.window.pushButton_addComponent.setMenu(self.compMenu)
- window.listWidget_componentList.clicked.connect(
+ self.window.listWidget_componentList.dropEvent = self.componentMoved
+ self.window.listWidget_componentList.clicked.connect(
lambda _: self.changeComponentWidget())
self.window.pushButton_removeComponent.clicked.connect(
@@ -183,8 +183,8 @@ class MainWindow(QtCore.QObject):
self.window.pushButton_listMoveUp.clicked.connect(
self.moveComponentUp)
- self.window.pushButton_listMoveDown.clicked.connect(
- self.moveComponentDown)
+ #self.window.pushButton_listMoveDown.clicked.connect(
+ # self.moveComponentDown)
# Configure the Projects Menu
self.projectMenu = QMenu()
@@ -323,7 +323,7 @@ class MainWindow(QtCore.QObject):
self.videoTask.emit(
self.window.lineEdit_audioFile.text(),
self.window.lineEdit_outputFile.text(),
- self.selectedComponents)
+ self.core.selectedComponents)
else:
self.showMessage(
msg="You must select an audio file and output filename.")
@@ -384,56 +384,35 @@ class MainWindow(QtCore.QObject):
self.drawPreview()
def drawPreview(self):
- self.newTask.emit(self.selectedComponents)
+ self.newTask.emit(self.core.selectedComponents)
# self.processTask.emit()
self.autosave()
def showPreviewImage(self, image):
self.previewWindow.changePixmap(image)
- def findComponents(self):
- def findComponents():
- srcPath = os.path.join(
- os.path.dirname(os.path.realpath(__file__)), 'components')
- if os.path.exists(srcPath):
- for f in sorted(os.listdir(srcPath)):
- name, ext = os.path.splitext(f)
- if name.startswith("__"):
- continue
- elif ext == '.py':
- yield name
- return [
- import_module('components.%s' % name)
- for name in findComponents()]
-
- def addComponent(self, moduleIndex):
- index = len(self.pages)
- self.selectedComponents.append(self.modules[moduleIndex].Component())
- self.window.listWidget_componentList.addItem(
- self.selectedComponents[-1].__doc__)
- self.pages.append(self.selectedComponents[-1].widget(self))
- self.window.listWidget_componentList.setCurrentRow(index)
- self.window.stackedWidget.addWidget(self.pages[-1])
+ def insertComponent(self, moduleIndex, compPos=0):
+ componentList = self.window.listWidget_componentList
+
+ index = self.core.insertComponent(
+ compPos, moduleIndex)
+ row = componentList.insertItem(
+ index,
+ self.core.selectedComponents[index].__doc__)
+ self.componentRows[index] = componentList.row(row)
+ componentList.setCurrentRow(index)
+
+ self.pages.insert(index, self.core.selectedComponents[index].widget(self))
+ self.window.stackedWidget.insertWidget(index, self.pages[index])
self.window.stackedWidget.setCurrentIndex(index)
- self.selectedComponents[-1].update()
-
- def insertComponent(self, moduleIndex):
- self.selectedComponents.insert(
- 0, self.modules[moduleIndex].Component())
- self.window.listWidget_componentList.insertItem(
- 0, self.selectedComponents[0].__doc__)
- self.pages.insert(0, self.selectedComponents[0].widget(self))
- self.window.listWidget_componentList.setCurrentRow(0)
- self.window.stackedWidget.insertWidget(0, self.pages[0])
- self.window.stackedWidget.setCurrentIndex(0)
- self.selectedComponents[0].update()
+ self.core.updateComponent(index)
def removeComponent(self):
for selected in self.window.listWidget_componentList.selectedItems():
index = self.window.listWidget_componentList.row(selected)
self.window.stackedWidget.removeWidget(self.pages[index])
self.window.listWidget_componentList.takeItem(index)
- self.selectedComponents.pop(index)
+ self.core.selectedComponents.pop(index)
self.pages.pop(index)
self.changeComponentWidget()
self.drawPreview()
@@ -447,20 +426,21 @@ class MainWindow(QtCore.QObject):
def moveComponentUp(self):
row = self.window.listWidget_componentList.currentRow()
if row > 0:
- module = self.selectedComponents[row]
- self.selectedComponents.pop(row)
- self.selectedComponents.insert(row - 1, module)
- page = self.pages[row]
- self.pages.pop(row)
+ self.core.moveComponent(row, row - 1)
+ page = self.pages.pop(row)
self.pages.insert(row - 1, page)
- item = self.window.listWidget_componentList.takeItem(row)
- self.window.listWidget_componentList.insertItem(row - 1, item)
- widget = self.window.stackedWidget.removeWidget(page)
- self.window.stackedWidget.insertWidget(row - 1, page)
- self.window.listWidget_componentList.setCurrentRow(row - 1)
- self.window.stackedWidget.setCurrentIndex(row - 1)
- self.drawPreview()
+ # update widgets
+ componentList = self.window.listWidget_componentList
+ stackedWidget = self.window.stackedWidget
+ item = componentList.takeItem(row)
+ componentList.insertItem(row - 1, item)
+ widget = stackedWidget.removeWidget(page)
+ stackedWidget.insertWidget(row - 1, page)
+ componentList.setCurrentRow(row - 1)
+ stackedWidget.setCurrentIndex(row - 1)
+ self.drawPreview()
+ '''
def moveComponentDown(self):
row = self.window.listWidget_componentList.currentRow()
if row != -1 and row < len(self.pages)+1:
@@ -477,6 +457,12 @@ class MainWindow(QtCore.QObject):
self.window.listWidget_componentList.setCurrentRow(row + 1)
self.window.stackedWidget.setCurrentIndex(row + 1)
self.drawPreview()
+ '''
+ def componentMoved(self, event):
+ widget = self.window.listWidget_componentList
+ for i in range(widget.count()):
+ pass
+ #print(widget.item(i) == self.componentRows[i])
def openPresetManager(self):
'''Preset manager for importing, exporting, renaming, deleting'''
@@ -484,7 +470,7 @@ class MainWindow(QtCore.QObject):
def createNewProject(self):
self.currentProject = None
- self.selectedComponents = []
+ self.core.selectedComponents = []
self.window.listWidget_componentList.clear()
for widget in self.pages:
self.window.stackedWidget.removeWidget(widget)
@@ -513,7 +499,7 @@ class MainWindow(QtCore.QObject):
with open(filepath, 'w') as f:
print('creating %s' % filepath)
f.write('[Components]\n')
- for comp in self.selectedComponents:
+ for comp in self.core.selectedComponents:
saveValueStore = comp.savePreset()
f.write('%s\n' % str(comp))
f.write('%s\n' % str(comp.version()))
@@ -538,7 +524,7 @@ class MainWindow(QtCore.QObject):
self.currentProject = filepath
self.settings.setValue("currentProject", filepath)
self.settings.setValue("projectDir", os.path.dirname(filepath))
- compNames = [mod.Component.__doc__ for mod in self.modules]
+ compNames = [mod.Component.__doc__ for mod in self.core.modules]
try:
with open(filepath, 'r') as f:
validSections = ('Components')
@@ -563,14 +549,14 @@ class MainWindow(QtCore.QObject):
if line and section == 'Components':
if i == 0:
compIndex = compNames.index(line)
- self.addComponent(compIndex)
+ self.insertComponent(compIndex, -1)
i += 1
elif i == 1:
# version, not used yet
i += 1
elif i == 2:
saveValueStore = dict(eval(line))
- self.selectedComponents[-1].loadPreset(
+ self.core.selectedComponents[-1].loadPreset(
saveValueStore)
i = 0
except (IndexError, ValueError, NameError, SyntaxError,
@@ -620,7 +606,7 @@ class MainWindow(QtCore.QObject):
# submenu for opening presets
index = self.window.listWidget_componentList.currentRow()
try:
- presets = self.presetManager.presets[str(self.selectedComponents[index])]
+ presets = self.presetManager.presets[str(self.core.selectedComponents[index])]
self.submenu = QtGui.QMenu("Open Preset")
self.menu.addMenu(self.submenu)
diff --git a/mainwindow.ui b/mainwindow.ui
index 62e0632..e809ee8 100644
--- a/mainwindow.ui
+++ b/mainwindow.ui
@@ -184,17 +184,20 @@
1
-
- false
+
+ true
- false
+ true
false
- QAbstractItemView::NoDragDrop
+ QAbstractItemView::InternalMove
+
+
+ Qt::MoveAction
diff --git a/presetmanager.py b/presetmanager.py
index f67dbb9..50efd8d 100644
--- a/presetmanager.py
+++ b/presetmanager.py
@@ -86,28 +86,35 @@ class PresetManager(QtGui.QDialog):
if window.listWidget_componentList.currentRow() == -1:
return
while True:
- dialog = QtGui.QInputDialog(
- QtGui.QWidget(), 'Audio Visualizer', 'New Preset Name:')
- dialog.setTextValue()
- newName, OK = dialog.getText()
- badName = False
- for letter in newName:
- if letter in string.punctuation:
- badName = True
- if badName:
- # some filesystems don't like bizarre characters
- self.parent.showMessage(msg=\
-'''Preset names must contain only letters, numbers, and spaces.''')
- continue
- if OK and newName:
- index = window.listWidget_componentList.currentRow()
- if index != -1:
- saveValueStore = \
- self.parent.selectedComponents[index].savePreset()
- componentName = str(self.parent.selectedComponents[index]).strip()
- vers = self.parent.selectedComponents[index].version()
- self.createPresetFile(
- componentName, vers, saveValueStore, newName)
+ index = window.listWidget_componentList.currentRow()
+ currentPreset = self.parent.selectedComponents[index].currentPreset
+ newName, OK = QtGui.QInputDialog.getText(
+ self.parent.window,
+ 'Audio Visualizer',
+ 'New Preset Name:',
+ QtGui.QLineEdit.Normal,
+ currentPreset
+ )
+ if OK:
+ badName = False
+ for letter in newName:
+ if letter in string.punctuation:
+ badName = True
+ if badName:
+ # some filesystems don't like bizarre characters
+ self.parent.showMessage(
+ msg='Preset names must contain only letters,'
+ 'numbers, and spaces.')
+ continue
+ if newName:
+ if index != -1:
+ saveValueStore = \
+ self.parent.selectedComponents[index].savePreset()
+ componentName = str(self.parent.selectedComponents[index]).strip()
+ vers = self.parent.selectedComponents[index].version()
+ self.createPresetFile(
+ componentName, vers, saveValueStore, newName)
+ self.parent.selectedComponents[index].currentPreset = newName
break
def createPresetFile(self, compName, vers, saveValueStore, filename):
@@ -139,6 +146,9 @@ class PresetManager(QtGui.QDialog):
for line in f:
saveValueStore = dict(eval(line.strip()))
break
- self.parent.selectedComponents[index].loadPreset(saveValueStore)
+ self.parent.selectedComponents[index].loadPreset(
+ saveValueStore,
+ presetName
+ )
self.parent.drawPreview()
--
cgit v1.2.3
From bb1e54b31eb6157ef041764cfccd60484a0e02d8 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 8 Jun 2017 16:50:48 -0400
Subject: saved preset titles, code clean-ups
componentList drag'n'drop disabled for now; will work on it in another branch
---
components/color.py | 3 +-
components/image.py | 3 +-
components/original.py | 3 +-
components/text.py | 3 +-
components/video.py | 3 +-
core.py | 58 +++++++++++++-----
mainwindow.py | 159 +++++++++++++++++++++++++++++--------------------
mainwindow.ui | 2 +-
presetmanager.py | 42 +++++++------
9 files changed, 169 insertions(+), 107 deletions(-)
diff --git a/components/color.py b/components/color.py
index b13f54e..a86927b 100644
--- a/components/color.py
+++ b/components/color.py
@@ -70,7 +70,7 @@ class Component(__base__.Component):
return Image.new("RGBA", (width, height), (r, g, b, 255))
def loadPreset(self, pr, presetName=None):
- self.currentPreset = presetName
+ self.currentPreset = presetName if presetName else pr['preset']
self.page.lineEdit_color1.setText('%s,%s,%s' % pr['color1'])
self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2'])
@@ -85,6 +85,7 @@ class Component(__base__.Component):
def savePreset(self):
return {
+ 'preset': self.currentPreset,
'color1': self.color1,
'color2': self.color2,
}
diff --git a/components/image.py b/components/image.py
index 6ccddb6..441e0e1 100644
--- a/components/image.py
+++ b/components/image.py
@@ -49,11 +49,12 @@ class Component(__base__.Component):
return frame
def loadPreset(self, pr, presetName=None):
- self.currentPreset = presetName
+ self.currentPreset = presetName if presetName else pr['preset']
self.page.lineEdit_image.setText(pr['image'])
def savePreset(self):
return {
+ 'preset': self.currentPreset,
'image': self.imagePath,
}
diff --git a/components/original.py b/components/original.py
index a2059d1..7873f43 100644
--- a/components/original.py
+++ b/components/original.py
@@ -38,7 +38,7 @@ class Component(__base__.Component):
self.parent.drawPreview()
def loadPreset(self, pr, presetName=None):
- self.currentPreset = presetName
+ self.currentPreset = presetName if presetName else pr['preset']
self.page.lineEdit_visColor.setText('%s,%s,%s' % pr['visColor'])
btnStyle = "QPushButton { background-color : %s; outline: none; }" \
% QColor(*pr['visColor']).name()
@@ -47,6 +47,7 @@ class Component(__base__.Component):
def savePreset(self):
return {
+ 'preset': self.currentPreset,
'layout': self.layout,
'visColor': self.visColor,
}
diff --git a/components/text.py b/components/text.py
index 1725a41..68cffca 100644
--- a/components/text.py
+++ b/components/text.py
@@ -79,7 +79,7 @@ class Component(__base__.Component):
return x, self.yPosition
def loadPreset(self, pr, presetName=None):
- self.currentPreset = presetName
+ self.currentPreset = presetName if presetName else pr['preset']
self.page.lineEdit_title.setText(pr['title'])
font = QFont()
font.fromString(pr['titleFont'])
@@ -95,6 +95,7 @@ class Component(__base__.Component):
def savePreset(self):
return {
+ 'preset': self.currentPreset,
'title': self.title,
'titleFont': self.titleFont.toString(),
'alignment': self.alignment,
diff --git a/components/video.py b/components/video.py
index e636224..c529658 100644
--- a/components/video.py
+++ b/components/video.py
@@ -129,12 +129,13 @@ class Component(__base__.Component):
return self.video.frame(frameNo)
def loadPreset(self, pr, presetName=None):
- self.currentPreset = presetName
+ self.currentPreset = presetName if presetName else pr['preset']
self.page.lineEdit_video.setText(pr['video'])
self.page.checkBox_loop.setChecked(pr['loop'])
def savePreset(self):
return {
+ 'preset': self.currentPreset,
'video': self.videoPath,
'loop': self.loopVideo,
}
diff --git a/core.py b/core.py
index 0fa5ec5..797749d 100644
--- a/core.py
+++ b/core.py
@@ -6,29 +6,34 @@ from os.path import expanduser
import subprocess as sp
import numpy
from PIL import Image
-import tempfile
+#import tempfile
from shutil import rmtree
-import atexit
+#import atexit
import time
from collections import OrderedDict
import json
from importlib import import_module
+from PyQt4.QtGui import QDesktopServices
class Core():
def __init__(self):
self.FFMPEG_BIN = self.findFfmpeg()
- self.tempDir = os.path.join(
- tempfile.gettempdir(), 'audio-visualizer-python-data')
- if not os.path.exists(self.tempDir):
- os.makedirs(self.tempDir)
- atexit.register(self.deleteTempDir)
+ #self.tempDir = os.path.join(
+ # tempfile.gettempdir(), 'audio-visualizer-python-data')
+ #if not os.path.exists(self.tempDir):
+ # os.makedirs(self.tempDir)
+ #atexit.register(self.deleteTempDir)
+ self.dataDir = QDesktopServices.storageLocation(
+ QDesktopServices.DataLocation)
+ self.presetDir = os.path.join(self.dataDir, 'presets')
self.wd = os.path.dirname(os.path.realpath(__file__))
self.loadEncoderOptions()
self.modules = self.findComponents()
self.selectedComponents = []
+ self.selectedModules = []
def findComponents(self):
def findComponents():
@@ -45,19 +50,40 @@ class Core():
for name in findComponents()]
def insertComponent(self, compPos, moduleIndex):
+ if compPos < 0:
+ compPos = len(self.selectedComponents) -1
self.selectedComponents.insert(
compPos,
- self.modules[moduleIndex].Component())
- return compPos #if compPos > -1 else len(self.selectedComponents)-1
+ self.modules[moduleIndex].Component()
+ )
+ self.selectedModules.insert(
+ compPos,
+ moduleIndex
+ )
+ return compPos
def moveComponent(self, startI, endI):
comp = self.selectedComponents.pop(startI)
- i = self.selectedComponents.insert(endI, comp)
- return i
+ self.selectedComponents.insert(endI, comp)
+ i = self.selectedModules.pop(startI)
+ self.selectedModules.insert(endI, i)
+ return endI
def updateComponent(self, i):
+ print('updating %s' % self.selectedComponents[i])
self.selectedComponents[i].update()
+ def moduleIndexFor(self, compIndex):
+ return self.selectedModules[compIndex]
+
+ def createPresetFile(self, compName, vers, saveValueStore, filename):
+ dirname = os.path.join(self.presetDir, compName, str(vers))
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+ filepath = os.path.join(dirname, filename)
+ with open(filepath, 'w') as f:
+ f.write(Core.stringOrderedDict(saveValueStore))
+
def loadEncoderOptions(self):
file_path = os.path.join(self.wd, 'encoder-options.json')
with open(file_path) as json_file:
@@ -139,11 +165,11 @@ class Core():
return completeAudioArray
- def deleteTempDir(self):
- try:
- rmtree(self.tempDir)
- except FileNotFoundError:
- pass
+ #def deleteTempDir(self):
+ # try:
+ # rmtree(self.tempDir)
+ # except FileNotFoundError:
+ # pass
def cancel(self):
self.canceled = True
diff --git a/mainwindow.py b/mainwindow.py
index f24fc66..8812e7f 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -3,7 +3,7 @@ from queue import Queue
from collections import OrderedDict
from PyQt4 import QtCore, QtGui, uic
from PyQt4.QtCore import QSettings, Qt
-from PyQt4.QtGui import QDesktopServices, QMenu
+from PyQt4.QtGui import QMenu
import sys
import os
import signal
@@ -55,12 +55,11 @@ class MainWindow(QtCore.QObject):
self.core = core.Core()
self.pages = [] # widgets of component settings
- self.componentRows = {} # QListWidgetItems
+ self.componentRows = [] # (moduleIndex, QListWidgetItem) tuples
self.lastAutosave = time.time()
# Create data directory, load/create settings
- self.dataDir = QDesktopServices.storageLocation(
- QDesktopServices.DataLocation)
+ self.dataDir = self.core.dataDir
self.presetManager = PresetManager(
uic.loadUi(
os.path.join(os.path.dirname(os.path.realpath(__file__)),
@@ -73,7 +72,7 @@ class MainWindow(QtCore.QObject):
if not os.path.exists(self.dataDir):
os.makedirs(self.dataDir)
for neededDirectory in (
- self.presetManager.presetDir, self.settings.value("projectDir")):
+ self.core.presetDir, self.settings.value("projectDir")):
if not os.path.exists(neededDirectory):
os.mkdir(neededDirectory)
@@ -90,6 +89,8 @@ class MainWindow(QtCore.QObject):
self.timer.start(500)
# Begin decorating the window and connecting events
+ componentList = self.window.listWidget_componentList
+
window.toolButton_selectAudioFile.clicked.connect(
self.openInputFileDialog)
@@ -120,7 +121,7 @@ class MainWindow(QtCore.QObject):
codec = window.comboBox_videoCodec.itemText(i)
if codec == self.settings.value('outputVideoCodec'):
window.comboBox_videoCodec.setCurrentIndex(i)
- print(codec)
+ #print(codec)
for i in range(window.comboBox_audioCodec.count()):
codec = window.comboBox_audioCodec.itemText(i)
@@ -157,17 +158,17 @@ class MainWindow(QtCore.QObject):
self.window.pushButton_addComponent.setMenu(self.compMenu)
- self.window.listWidget_componentList.dropEvent = self.componentMoved
- self.window.listWidget_componentList.clicked.connect(
+ componentList.dropEvent = self.componentListChanged
+ componentList.clicked.connect(
lambda _: self.changeComponentWidget())
self.window.pushButton_removeComponent.clicked.connect(
lambda _: self.removeComponent())
- self.window.listWidget_componentList.setContextMenuPolicy(
+ componentList.setContextMenuPolicy(
QtCore.Qt.CustomContextMenu)
- self.window.listWidget_componentList.connect(
- self.window.listWidget_componentList,
+ componentList.connect(
+ componentList,
QtCore.SIGNAL("customContextMenuRequested(QPoint)"),
self.componentContextMenu)
@@ -182,9 +183,11 @@ class MainWindow(QtCore.QObject):
self.updateResolution)
self.window.pushButton_listMoveUp.clicked.connect(
- self.moveComponentUp)
- #self.window.pushButton_listMoveDown.clicked.connect(
- # self.moveComponentDown)
+ lambda: self.moveComponent(-1)
+ )
+ self.window.pushButton_listMoveDown.clicked.connect(
+ lambda: self.moveComponent(1)
+ )
# Configure the Projects Menu
self.projectMenu = QMenu()
@@ -210,8 +213,7 @@ class MainWindow(QtCore.QObject):
# Show the window and load current project
window.show()
self.currentProject = self.settings.value("currentProject")
- if self.currentProject and os.path.exists(self.autosavePath) \
- and filecmp.cmp(self.autosavePath, self.currentProject):
+ if self.autosaveExists():
# delete autosave if it's identical to the project
os.remove(self.autosavePath)
@@ -235,6 +237,14 @@ class MainWindow(QtCore.QObject):
self.previewThread.wait()
self.autosave()
+ def updateComponentTitle(self, pos):
+ if pos < 0:
+ pos = len(self.core.selectedComponents)-1
+ title = str(self.core.selectedComponents[pos])
+ if self.core.selectedComponents[pos].currentPreset:
+ title += ' - %s' % self.core.selectedComponents[pos].currentPreset
+ self.window.listWidget_componentList.item(pos).setText(title)
+
def updateCodecs(self):
containerWidget = self.window.comboBox_videoContainer
vCodecWidget = self.window.comboBox_videoCodec
@@ -270,12 +280,20 @@ class MainWindow(QtCore.QObject):
self.settings.setValue('outputAudioBitrate', currentAudioBitrate)
def autosave(self):
- if time.time() - self.lastAutosave >= 2.0:
+ if not self.currentProject:
if os.path.exists(self.autosavePath):
os.remove(self.autosavePath)
+ elif time.time() - self.lastAutosave >= 2.0:
self.createProjectFile(self.autosavePath)
self.lastAutosave = time.time()
+ def autosaveExists(self):
+ if self.currentProject and os.path.exists(self.autosavePath) \
+ and filecmp.cmp(self.autosavePath, self.currentProject):
+ return True
+ else:
+ return False
+
def openInputFileDialog(self):
inputDir = self.settings.value("inputDir", expanduser("~"))
@@ -393,88 +411,94 @@ class MainWindow(QtCore.QObject):
def insertComponent(self, moduleIndex, compPos=0):
componentList = self.window.listWidget_componentList
+ stackedWidget = self.window.stackedWidget
+ if compPos < 0:
+ compPos = componentList.count()
index = self.core.insertComponent(
compPos, moduleIndex)
row = componentList.insertItem(
index,
self.core.selectedComponents[index].__doc__)
- self.componentRows[index] = componentList.row(row)
+ self.componentRows.insert(compPos, (moduleIndex, row))
componentList.setCurrentRow(index)
self.pages.insert(index, self.core.selectedComponents[index].widget(self))
- self.window.stackedWidget.insertWidget(index, self.pages[index])
- self.window.stackedWidget.setCurrentIndex(index)
+ stackedWidget.insertWidget(index, self.pages[index])
+ stackedWidget.setCurrentIndex(index)
+
self.core.updateComponent(index)
def removeComponent(self):
- for selected in self.window.listWidget_componentList.selectedItems():
- index = self.window.listWidget_componentList.row(selected)
+ componentList = self.window.listWidget_componentList
+
+ for selected in componentList.selectedItems():
+ index = componentList.row(selected)
self.window.stackedWidget.removeWidget(self.pages[index])
- self.window.listWidget_componentList.takeItem(index)
+ componentList.takeItem(index)
+ self.componentRows.pop(index)
self.core.selectedComponents.pop(index)
self.pages.pop(index)
self.changeComponentWidget()
self.drawPreview()
- def changeComponentWidget(self):
- selected = self.window.listWidget_componentList.selectedItems()
- if selected:
- index = self.window.listWidget_componentList.row(selected[0])
- self.window.stackedWidget.setCurrentIndex(index)
+ def moveComponent(self, change):
+ '''Moves a component relatively from its current position'''
+ componentList = self.window.listWidget_componentList
+ stackedWidget = self.window.stackedWidget
- def moveComponentUp(self):
- row = self.window.listWidget_componentList.currentRow()
- if row > 0:
- self.core.moveComponent(row, row - 1)
- page = self.pages.pop(row)
- self.pages.insert(row - 1, page)
+ row = componentList.currentRow()
+ newRow = row + change
+ if newRow > -1 and newRow < componentList.count():
+ self.core.moveComponent(row, newRow)
# update widgets
- componentList = self.window.listWidget_componentList
- stackedWidget = self.window.stackedWidget
+ page = self.pages.pop(row)
+ self.pages.insert(newRow, page)
item = componentList.takeItem(row)
- componentList.insertItem(row - 1, item)
+ newItem = componentList.insertItem(newRow, item)
widget = stackedWidget.removeWidget(page)
- stackedWidget.insertWidget(row - 1, page)
- componentList.setCurrentRow(row - 1)
- stackedWidget.setCurrentIndex(row - 1)
- self.drawPreview()
- '''
- def moveComponentDown(self):
- row = self.window.listWidget_componentList.currentRow()
- if row != -1 and row < len(self.pages)+1:
- module = self.selectedComponents[row]
- self.selectedComponents.pop(row)
- self.selectedComponents.insert(row + 1, module)
- page = self.pages[row]
- self.pages.pop(row)
- self.pages.insert(row + 1, page)
- item = self.window.listWidget_componentList.takeItem(row)
- self.window.listWidget_componentList.insertItem(row + 1, item)
- widget = self.window.stackedWidget.removeWidget(page)
- self.window.stackedWidget.insertWidget(row + 1, page)
- self.window.listWidget_componentList.setCurrentRow(row + 1)
- self.window.stackedWidget.setCurrentIndex(row + 1)
+ stackedWidget.insertWidget(newRow, page)
+ componentList.setCurrentRow(newRow)
+ stackedWidget.setCurrentIndex(newRow)
+ self.componentRows.pop(row)
+ self.componentRows.insert(newRow, (self.core.moduleIndexFor(row), newItem))
self.drawPreview()
- '''
- def componentMoved(self, event):
- widget = self.window.listWidget_componentList
- for i in range(widget.count()):
- pass
- #print(widget.item(i) == self.componentRows[i])
+
+ def componentListChanged(self, *args):
+ '''Update all our tracking variables to match the widget'''
+ pass
+
+ def changeComponentWidget(self):
+ selected = self.window.listWidget_componentList.selectedItems()
+ if selected:
+ index = self.window.listWidget_componentList.row(selected[0])
+ self.window.stackedWidget.setCurrentIndex(index)
def openPresetManager(self):
'''Preset manager for importing, exporting, renaming, deleting'''
self.presetManager.show()
- def createNewProject(self):
- self.currentProject = None
+ def clear(self):
+ '''Get a blank slate'''
self.core.selectedComponents = []
self.window.listWidget_componentList.clear()
for widget in self.pages:
self.window.stackedWidget.removeWidget(widget)
self.pages = []
+
+ def createNewProject(self):
+ if self.autosaveExists():
+ ch = self.showMessage(
+ msg="You have unsaved changes in project '%s'. "
+ "Save before starting a new project?"
+ % os.path.basename(self.currentProject)[:-4],
+ showCancel=True)
+ if ch:
+ self.saveCurrentProject()
+
+ self.clear()
+ self.currentProject = None
self.settings.setValue("currentProject", None)
self.drawPreview()
@@ -496,6 +520,8 @@ class MainWindow(QtCore.QObject):
def createProjectFile(self, filepath):
if not filepath.endswith(".avp"):
filepath += '.avp'
+ if os.path.exists(filepath):
+ os.remove(filepath)
with open(filepath, 'w') as f:
print('creating %s' % filepath)
f.write('[Components]\n')
@@ -520,7 +546,7 @@ class MainWindow(QtCore.QObject):
if not filepath or not os.path.exists(filepath) \
or not filepath.endswith('.avp'):
return
- self.createNewProject()
+ self.clear()
self.currentProject = filepath
self.settings.setValue("currentProject", filepath)
self.settings.setValue("projectDir", os.path.dirname(filepath))
@@ -558,6 +584,7 @@ class MainWindow(QtCore.QObject):
saveValueStore = dict(eval(line))
self.core.selectedComponents[-1].loadPreset(
saveValueStore)
+ self.updateComponentTitle(-1)
i = 0
except (IndexError, ValueError, NameError, SyntaxError,
AttributeError, TypeError) as e:
diff --git a/mainwindow.ui b/mainwindow.ui
index e809ee8..af47cee 100644
--- a/mainwindow.ui
+++ b/mainwindow.ui
@@ -188,7 +188,7 @@
true
- true
+ false
false
diff --git a/presetmanager.py b/presetmanager.py
index 50efd8d..268754e 100644
--- a/presetmanager.py
+++ b/presetmanager.py
@@ -10,7 +10,8 @@ class PresetManager(QtGui.QDialog):
def __init__(self, window, parent):
super().__init__()
self.parent = parent
- self.presetDir = os.path.join(self.parent.dataDir, 'presets')
+ self.core = self.parent.core
+ self.presetDir = self.core.presetDir
self.window = window
self.findPresets()
self.lastFilter = '*'
@@ -83,11 +84,14 @@ class PresetManager(QtGui.QDialog):
def openSavePresetDialog(self):
window = self.parent.window
- if window.listWidget_componentList.currentRow() == -1:
+ self.selectedComponents = self.parent.core.selectedComponents
+ componentList = window.listWidget_componentList
+
+ if componentList.currentRow() == -1:
return
while True:
- index = window.listWidget_componentList.currentRow()
- currentPreset = self.parent.selectedComponents[index].currentPreset
+ index = componentList.currentRow()
+ currentPreset = self.selectedComponents[index].currentPreset
newName, OK = QtGui.QInputDialog.getText(
self.parent.window,
'Audio Visualizer',
@@ -109,35 +113,34 @@ class PresetManager(QtGui.QDialog):
if newName:
if index != -1:
saveValueStore = \
- self.parent.selectedComponents[index].savePreset()
- componentName = str(self.parent.selectedComponents[index]).strip()
- vers = self.parent.selectedComponents[index].version()
+ self.selectedComponents[index].savePreset()
+ componentName = str(self.selectedComponents[index]).strip()
+ vers = self.selectedComponents[index].version()
self.createPresetFile(
componentName, vers, saveValueStore, newName)
- self.parent.selectedComponents[index].currentPreset = newName
+ self.selectedComponents[index].currentPreset = newName
break
def createPresetFile(self, compName, vers, saveValueStore, filename):
- dirname = os.path.join(self.presetDir, compName, str(vers))
- if not os.path.exists(dirname):
- os.makedirs(dirname)
- filepath = os.path.join(dirname, filename)
- if os.path.exists(filepath):
+ path = os.path.join(self.presetDir, compName, str(vers), filename)
+ if os.path.exists(path):
ch = self.parent.showMessage(
msg="%s already exists! Overwrite it?" % filename,
showCancel=True, icon=QtGui.QMessageBox.Warning)
if not ch:
return
- with open(filepath, 'w') as f:
- f.write(core.Core.stringOrderedDict(saveValueStore))
+ self.core.createPresetFile(compName, vers, saveValueStore, filename)
self.drawPresetList()
def openPreset(self, presetName):
- index = self.parent.window.listWidget_componentList.currentRow()
+ componentList = self.parent.window.listWidget_componentList
+ selectedComponents = self.parent.core.selectedComponents
+
+ index = componentList.currentRow()
if index == -1:
return
- componentName = str(self.parent.selectedComponents[index]).strip()
- version = self.parent.selectedComponents[index].version()
+ componentName = str(selectedComponents[index]).strip()
+ version = selectedComponents[index].version()
dirname = os.path.join(self.presetDir, componentName, str(version))
filepath = os.path.join(dirname, presetName)
if not os.path.exists(filepath):
@@ -146,9 +149,10 @@ class PresetManager(QtGui.QDialog):
for line in f:
saveValueStore = dict(eval(line.strip()))
break
- self.parent.selectedComponents[index].loadPreset(
+ selectedComponents[index].loadPreset(
saveValueStore,
presetName
)
+ self.parent.updateComponentTitle(index)
self.parent.drawPreview()
--
cgit v1.2.3
From 4fc73f1e094289b50f828f0a3128d710e1d9ec4c Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 8 Jun 2017 20:32:25 -0400
Subject: rename and delete buttons in preset manager
---
components/color.py | 16 ++++----
core.py | 10 +++++
presetmanager.py | 116 ++++++++++++++++++++++++++++++++++++++++++++--------
3 files changed, 118 insertions(+), 24 deletions(-)
diff --git a/components/color.py b/components/color.py
index a86927b..11c1b19 100644
--- a/components/color.py
+++ b/components/color.py
@@ -20,14 +20,14 @@ class Component(__base__.Component):
page.lineEdit_color1.setText('%s,%s,%s' % self.color1)
page.lineEdit_color2.setText('%s,%s,%s' % self.color2)
- btnStyle = "QPushButton { background-color : %s; outline: none; }" \
+ btnStyle1 = "QPushButton { background-color : %s; outline: none; }" \
% QColor(*self.color1).name()
- btnStyle = "QPushButton { background-color : %s; outline: none; }" \
+ btnStyle2 = "QPushButton { background-color : %s; outline: none; }" \
% QColor(*self.color2).name()
- page.pushButton_color1.setStyleSheet(btnStyle)
- page.pushButton_color2.setStyleSheet(btnStyle)
+ page.pushButton_color1.setStyleSheet(btnStyle1)
+ page.pushButton_color2.setStyleSheet(btnStyle2)
page.pushButton_color1.clicked.connect(lambda: self.pickColor(1))
page.pushButton_color2.clicked.connect(lambda: self.pickColor(2))
@@ -74,14 +74,14 @@ class Component(__base__.Component):
self.page.lineEdit_color1.setText('%s,%s,%s' % pr['color1'])
self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2'])
- btnStyle = "QPushButton { background-color : %s; outline: none; }" \
+ btnStyle1 = "QPushButton { background-color : %s; outline: none; }" \
% QColor(*pr['color1']).name()
- btnStyle = "QPushButton { background-color : %s; outline: none; }" \
+ btnStyle2 = "QPushButton { background-color : %s; outline: none; }" \
% QColor(*pr['color2']).name()
- self.page.pushButton_color1.setStyleSheet(btnStyle)
- self.page.pushButton_color2.setStyleSheet(btnStyle)
+ self.page.pushButton_color1.setStyleSheet(btnStyle1)
+ self.page.pushButton_color2.setStyleSheet(btnStyle2)
def savePreset(self):
return {
diff --git a/core.py b/core.py
index 797749d..06367cf 100644
--- a/core.py
+++ b/core.py
@@ -14,6 +14,7 @@ from collections import OrderedDict
import json
from importlib import import_module
from PyQt4.QtGui import QDesktopServices
+import string
class Core():
@@ -177,6 +178,15 @@ class Core():
def reset(self):
self.canceled = False
+ @staticmethod
+ def badName(name):
+ '''Returns whether a name contains non-alphanumeric chars'''
+ badName = False
+ for letter in name:
+ if letter in string.punctuation:
+ badName = True
+ return badName
+
@staticmethod
def stringOrderedDict(dictionary):
sorted_ = OrderedDict(sorted(dictionary.items(), key=lambda t: t[0]))
diff --git a/presetmanager.py b/presetmanager.py
index 268754e..d7189b1 100644
--- a/presetmanager.py
+++ b/presetmanager.py
@@ -15,6 +15,11 @@ class PresetManager(QtGui.QDialog):
self.window = window
self.findPresets()
self.lastFilter = '*'
+ self.presetRows = [] # list of (comp, vers, name) tuples
+
+ # connect button signals
+ self.window.pushButton_delete.clicked.connect(self.openDeletePresetDialog)
+ self.window.pushButton_rename.clicked.connect(self.openRenamePresetDialog)
# create filter box and preset list
self.drawFilterList()
@@ -30,6 +35,7 @@ class PresetManager(QtGui.QDialog):
self.window.lineEdit_search.setCompleter(completer)
def show(self):
+ '''Open a new preset manager window from the mainwindow'''
presetNames = []
for presetList in self.presets.values():
for preset in presetList:
@@ -70,11 +76,13 @@ class PresetManager(QtGui.QDialog):
self.lastFilter = str(filter)
else:
filter = str(self.lastFilter)
+ self.presetRows = []
for component, presets in self.presets.items():
if filter != '*' and component != filter:
continue
for vers, preset in presets:
self.window.listWidget_presets.addItem('%s: %s' % (component, preset))
+ self.presetRows.append((component, vers, preset))
def drawFilterList(self):
self.window.comboBox_filter.clear()
@@ -83,9 +91,10 @@ class PresetManager(QtGui.QDialog):
self.window.comboBox_filter.addItem(component)
def openSavePresetDialog(self):
+ '''Functions on mainwindow level from the context menu'''
window = self.parent.window
self.selectedComponents = self.parent.core.selectedComponents
- componentList = window.listWidget_componentList
+ componentList = self.parent.window.listWidget_componentList
if componentList.currentRow() == -1:
return
@@ -100,15 +109,8 @@ class PresetManager(QtGui.QDialog):
currentPreset
)
if OK:
- badName = False
- for letter in newName:
- if letter in string.punctuation:
- badName = True
- if badName:
- # some filesystems don't like bizarre characters
- self.parent.showMessage(
- msg='Preset names must contain only letters,'
- 'numbers, and spaces.')
+ if core.Core.badName(newName):
+ self.warnMessage()
continue
if newName:
if index != -1:
@@ -116,21 +118,30 @@ class PresetManager(QtGui.QDialog):
self.selectedComponents[index].savePreset()
componentName = str(self.selectedComponents[index]).strip()
vers = self.selectedComponents[index].version()
- self.createPresetFile(
+ self.createNewPreset(
componentName, vers, saveValueStore, newName)
self.selectedComponents[index].currentPreset = newName
+ self.findPresets()
+ self.drawPresetList()
break
- def createPresetFile(self, compName, vers, saveValueStore, filename):
+ def createNewPreset(self, compName, vers, saveValueStore, filename):
path = os.path.join(self.presetDir, compName, str(vers), filename)
+ if self.presetExists(path):
+ return
+ self.core.createPresetFile(compName, vers, saveValueStore, filename)
+
+ def presetExists(self, path):
if os.path.exists(path):
ch = self.parent.showMessage(
- msg="%s already exists! Overwrite it?" % filename,
+ msg="%s already exists! Overwrite it?" %
+ os.path.basename(path),
showCancel=True, icon=QtGui.QMessageBox.Warning)
if not ch:
- return
- self.core.createPresetFile(compName, vers, saveValueStore, filename)
- self.drawPresetList()
+ # user clicked cancel
+ return True
+
+ return False
def openPreset(self, presetName):
componentList = self.parent.window.listWidget_componentList
@@ -156,3 +167,76 @@ class PresetManager(QtGui.QDialog):
self.parent.updateComponentTitle(index)
self.parent.drawPreview()
+ def openDeletePresetDialog(self):
+ selected = self.window.listWidget_presets.selectedItems()
+ if not selected:
+ return
+ row = self.window.listWidget_presets.row(selected[0])
+ comp, vers, name = self.presetRows[row]
+ ch = self.parent.showMessage(
+ msg='Really delete %s?' % name,
+ showCancel=True, icon=QtGui.QMessageBox.Warning
+ )
+ if not ch:
+ return
+ self.deletePreset(comp, vers, name)
+ self.findPresets()
+ self.drawPresetList()
+
+ def deletePreset(self, comp, vers, name):
+ filepath = os.path.join(self.presetDir, comp, str(vers), name)
+ os.remove(filepath)
+
+ def warnMessage(self):
+ self.parent.showMessage(
+ msg='Preset names must contain only letters, '
+ 'numbers, and spaces.')
+
+ def openRenamePresetDialog(self):
+ presetList = self.window.listWidget_presets
+ if presetList.currentRow() == -1:
+ return
+
+ while True:
+ index = presetList.currentRow()
+ newName, OK = QtGui.QInputDialog.getText(
+ self.window,
+ 'Preset Manager',
+ 'Rename Preset:',
+ QtGui.QLineEdit.Normal,
+ self.presetRows[index][2]
+ )
+ if OK:
+ if core.Core.badName(newName):
+ self.warnMessage()
+ continue
+ if newName:
+ comp, vers, oldName = self.presetRows[index]
+ path = os.path.join(
+ self.presetDir, comp, str(vers))
+ newPath = os.path.join(path, newName)
+ oldPath = os.path.join(path, oldName)
+ if self.presetExists(newPath):
+ return
+ if os.path.exists(newPath):
+ os.remove(newPath)
+ os.rename(oldPath, newPath)
+ self.findPresets()
+ self.drawPresetList()
+ break
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--
cgit v1.2.3
From c51d86dd74c0548a0e81725534b78e23f6b6acaa Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 8 Jun 2017 22:31:02 -0400
Subject: preset searchbar works, ui experimentally changed
closebutton where I keep expecting it to be
---
presetmanager.py | 53 ++++++++++++++++++++++-------------------------------
presetmanager.ui | 26 +++++++++++++++++++++++++-
video_thread.py | 2 +-
3 files changed, 48 insertions(+), 33 deletions(-)
diff --git a/presetmanager.py b/presetmanager.py
index d7189b1..ff50444 100644
--- a/presetmanager.py
+++ b/presetmanager.py
@@ -8,7 +8,7 @@ import core
class PresetManager(QtGui.QDialog):
def __init__(self, window, parent):
- super().__init__()
+ super().__init__(parent.window)
self.parent = parent
self.core = self.parent.core
self.presetDir = self.core.presetDir
@@ -20,27 +20,30 @@ class PresetManager(QtGui.QDialog):
# connect button signals
self.window.pushButton_delete.clicked.connect(self.openDeletePresetDialog)
self.window.pushButton_rename.clicked.connect(self.openRenamePresetDialog)
+ self.window.pushButton_close.clicked.connect(self.close)
# create filter box and preset list
self.drawFilterList()
self.window.comboBox_filter.currentIndexChanged.connect(
- lambda: self.drawPresetList(self.window.comboBox_filter.currentText())
+ lambda: self.drawPresetList(
+ self.window.comboBox_filter.currentText(), self.window.lineEdit_search.text()
+ )
)
- self.drawPresetList('*')
# make auto-completion for search bar
self.autocomplete = QtGui.QStringListModel()
completer = QtGui.QCompleter()
completer.setModel(self.autocomplete)
self.window.lineEdit_search.setCompleter(completer)
+ self.window.lineEdit_search.textChanged.connect(
+ lambda: self.drawPresetList(
+ self.window.comboBox_filter.currentText(), self.window.lineEdit_search.text()
+ )
+ )
+ self.drawPresetList('*')
def show(self):
'''Open a new preset manager window from the mainwindow'''
- presetNames = []
- for presetList in self.presets.values():
- for preset in presetList:
- presetNames.append(preset[1])
- self.autocomplete.setStringList(presetNames)
self.findPresets()
self.drawFilterList()
self.drawPresetList('*')
@@ -70,19 +73,23 @@ class PresetManager(QtGui.QDialog):
for compName, _, __ in parseList \
}
- def drawPresetList(self, filter=None):
+ def drawPresetList(self, compFilter=None, presetFilter=''):
self.window.listWidget_presets.clear()
- if filter:
- self.lastFilter = str(filter)
+ if compFilter:
+ self.lastFilter = str(compFilter)
else:
- filter = str(self.lastFilter)
+ compFilter = str(self.lastFilter)
self.presetRows = []
+ presetNames = []
for component, presets in self.presets.items():
- if filter != '*' and component != filter:
+ if compFilter != '*' and component != compFilter:
continue
for vers, preset in presets:
- self.window.listWidget_presets.addItem('%s: %s' % (component, preset))
- self.presetRows.append((component, vers, preset))
+ if not presetFilter or presetFilter in preset:
+ self.window.listWidget_presets.addItem('%s: %s' % (component, preset))
+ self.presetRows.append((component, vers, preset))
+ presetNames.append(preset)
+ self.autocomplete.setStringList(presetNames)
def drawFilterList(self):
self.window.comboBox_filter.clear()
@@ -224,19 +231,3 @@ class PresetManager(QtGui.QDialog):
self.findPresets()
self.drawPresetList()
break
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/presetmanager.ui b/presetmanager.ui
index 610de91..a7ef15f 100644
--- a/presetmanager.ui
+++ b/presetmanager.ui
@@ -2,6 +2,9 @@
presetmanager
+
+ Qt::ApplicationModal
+
0
@@ -22,7 +25,7 @@
- Search
+ Filter by name
@@ -48,6 +51,9 @@
0
+
+ true
+
@@ -98,6 +104,24 @@
+ -
+
+
+ Close
+
+
+
+
+
+ -
+
+
-
+
+
+ <html><head/><body><p><span style=" font-style:italic;">Right-click components in the list to create presets.</span></p></body></html>
+
+
+
diff --git a/video_thread.py b/video_thread.py
index f5354be..fc877bd 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -26,7 +26,7 @@ class Worker(QtCore.QObject):
QtCore.QObject.__init__(self)
self.core = core.Core()
self.core.settings = parent.settings
- self.modules = parent.modules
+ self.modules = parent.core.modules
self.stackedWidget = parent.window.stackedWidget
self.parent = parent
parent.videoTask.connect(self.createVideo)
--
cgit v1.2.3
From d3f979ef2461a3de701df0d5add545b80dfe23ad Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 8 Jun 2017 22:56:33 -0400
Subject: start connecting import/export buttons
---
core.py | 6 ++++++
mainwindow.py | 8 ++++----
presetmanager.py | 23 ++++++++++++++++++++++-
presetmanager.ui | 29 +++++++++++++++++++++--------
4 files changed, 53 insertions(+), 13 deletions(-)
diff --git a/core.py b/core.py
index 06367cf..776af55 100644
--- a/core.py
+++ b/core.py
@@ -85,6 +85,12 @@ class Core():
with open(filepath, 'w') as f:
f.write(Core.stringOrderedDict(saveValueStore))
+ def importPreset(self, filepath):
+ print(filepath)
+
+ def exportPreset(self, exportName, compName, vers, origName):
+ pass
+
def loadEncoderOptions(self):
file_path = os.path.join(self.wd, 'encoder-options.json')
with open(file_path) as json_file:
diff --git a/mainwindow.py b/mainwindow.py
index 8812e7f..45262f7 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -60,15 +60,15 @@ class MainWindow(QtCore.QObject):
# Create data directory, load/create settings
self.dataDir = self.core.dataDir
+ self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
+ self.settings = QSettings(
+ os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat)
+ LoadDefaultSettings(self)
self.presetManager = PresetManager(
uic.loadUi(
os.path.join(os.path.dirname(os.path.realpath(__file__)),
'presetmanager.ui')),
self)
- self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
- self.settings = QSettings(
- os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat)
- LoadDefaultSettings(self)
if not os.path.exists(self.dataDir):
os.makedirs(self.dataDir)
for neededDirectory in (
diff --git a/presetmanager.py b/presetmanager.py
index ff50444..73a2431 100644
--- a/presetmanager.py
+++ b/presetmanager.py
@@ -10,7 +10,8 @@ class PresetManager(QtGui.QDialog):
def __init__(self, window, parent):
super().__init__(parent.window)
self.parent = parent
- self.core = self.parent.core
+ self.core = parent.core
+ self.settings = parent.settings
self.presetDir = self.core.presetDir
self.window = window
self.findPresets()
@@ -21,6 +22,8 @@ class PresetManager(QtGui.QDialog):
self.window.pushButton_delete.clicked.connect(self.openDeletePresetDialog)
self.window.pushButton_rename.clicked.connect(self.openRenamePresetDialog)
self.window.pushButton_close.clicked.connect(self.close)
+ self.window.pushButton_import.clicked.connect(self.openImportDialog)
+ self.window.pushButton_export.clicked.connect(self.openExportDialog)
# create filter box and preset list
self.drawFilterList()
@@ -231,3 +234,21 @@ class PresetManager(QtGui.QDialog):
self.findPresets()
self.drawPresetList()
break
+
+ def openImportDialog(self):
+ filename = QtGui.QFileDialog.getOpenFileName(
+ self.window, "Import Preset File",
+ self.settings.value("projectDir"),
+ "Preset Files (*.avl)")
+ if filename:
+ self.core.importPreset(filename)
+
+ def openExportDialog(self):
+ filename = QtGui.QFileDialog.getSaveFileName(
+ self.window, "Export Preset",
+ self.settings.value("projectDir"),
+ "Preset Files (*.avl)")
+ if filename:
+ index = self.window.listWidget_presets.currentRow()
+ comp, vers, name = self.presetRows[index]
+ self.core.exportPreset(filename, comp, vers, name)
diff --git a/presetmanager.ui b/presetmanager.ui
index a7ef15f..47568fb 100644
--- a/presetmanager.ui
+++ b/presetmanager.ui
@@ -9,7 +9,7 @@
0
0
- 542
+ 475
360
@@ -104,13 +104,6 @@
- -
-
-
- Close
-
-
-
-
@@ -122,6 +115,26 @@
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Close
+
+
+
--
cgit v1.2.3
From b048312882f80d43e3d8b1573814badf70fe3b92 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 10 Jun 2017 12:10:05 -0400
Subject: close button works, dialogs properly parented
hint text wording changed by IamDH4's suggestion
---
mainwindow.py | 4 +++-
presetmanager.py | 18 ++++++++++++------
presetmanager.ui | 6 +++---
3 files changed, 18 insertions(+), 10 deletions(-)
diff --git a/mainwindow.py b/mainwindow.py
index 45262f7..3e49ab2 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -601,7 +601,9 @@ class MainWindow(QtCore.QObject):
print('project file missing value: %s' % e)
def showMessage(self, **kwargs):
- msg = QtGui.QMessageBox()
+ parent = kwargs['parent'] if 'parent' in kwargs else self.window
+ msg = QtGui.QMessageBox(parent)
+ msg.setModal(True)
msg.setText(kwargs['msg'])
msg.setIcon(
kwargs['icon'] if 'icon' in kwargs else QtGui.QMessageBox.Information)
diff --git a/presetmanager.py b/presetmanager.py
index 73a2431..04a9042 100644
--- a/presetmanager.py
+++ b/presetmanager.py
@@ -21,9 +21,9 @@ class PresetManager(QtGui.QDialog):
# connect button signals
self.window.pushButton_delete.clicked.connect(self.openDeletePresetDialog)
self.window.pushButton_rename.clicked.connect(self.openRenamePresetDialog)
- self.window.pushButton_close.clicked.connect(self.close)
self.window.pushButton_import.clicked.connect(self.openImportDialog)
self.window.pushButton_export.clicked.connect(self.openExportDialog)
+ self.window.pushButton_close.clicked.connect(self.window.close)
# create filter box and preset list
self.drawFilterList()
@@ -91,7 +91,8 @@ class PresetManager(QtGui.QDialog):
if not presetFilter or presetFilter in preset:
self.window.listWidget_presets.addItem('%s: %s' % (component, preset))
self.presetRows.append((component, vers, preset))
- presetNames.append(preset)
+ if preset not in presetNames:
+ presetNames.append(preset)
self.autocomplete.setStringList(presetNames)
def drawFilterList(self):
@@ -120,7 +121,7 @@ class PresetManager(QtGui.QDialog):
)
if OK:
if core.Core.badName(newName):
- self.warnMessage()
+ self.warnMessage(self.parent.window)
continue
if newName:
if index != -1:
@@ -185,7 +186,9 @@ class PresetManager(QtGui.QDialog):
comp, vers, name = self.presetRows[row]
ch = self.parent.showMessage(
msg='Really delete %s?' % name,
- showCancel=True, icon=QtGui.QMessageBox.Warning
+ showCancel=True,
+ icon=QtGui.QMessageBox.Warning,
+ parent=self.window
)
if not ch:
return
@@ -197,10 +200,11 @@ class PresetManager(QtGui.QDialog):
filepath = os.path.join(self.presetDir, comp, str(vers), name)
os.remove(filepath)
- def warnMessage(self):
+ def warnMessage(self, window=None):
self.parent.showMessage(
msg='Preset names must contain only letters, '
- 'numbers, and spaces.')
+ 'numbers, and spaces.',
+ parent=window if window else self.window)
def openRenamePresetDialog(self):
presetList = self.window.listWidget_presets
@@ -242,6 +246,8 @@ class PresetManager(QtGui.QDialog):
"Preset Files (*.avl)")
if filename:
self.core.importPreset(filename)
+ self.findPresets()
+ self.drawPresetList()
def openExportDialog(self):
filename = QtGui.QFileDialog.getSaveFileName(
diff --git a/presetmanager.ui b/presetmanager.ui
index 47568fb..b3c25fe 100644
--- a/presetmanager.ui
+++ b/presetmanager.ui
@@ -9,8 +9,8 @@
0
0
- 475
- 360
+ 497
+ 377
@@ -111,7 +111,7 @@
-
- <html><head/><body><p><span style=" font-style:italic;">Right-click components in the list to create presets.</span></p></body></html>
+ <html><head/><body><p><span style=" font-size:10pt; font-style:italic;">Right-click components in the main window to create presets</span></p></body></html>
--
cgit v1.2.3
From 59c2c090ab9275bc1146329536d43855a46d34f4 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 10 Jun 2017 14:52:01 -0400
Subject: made basic export function, moved more code into core
---
core.py | 70 ++++++++++++++++++++++++++++++++++++++++++++++++--------
mainwindow.py | 28 ++++++-----------------
presetmanager.py | 12 ++++++----
presetmanager.ui | 8 ++++++-
4 files changed, 81 insertions(+), 37 deletions(-)
diff --git a/core.py b/core.py
index 776af55..c50918f 100644
--- a/core.py
+++ b/core.py
@@ -77,19 +77,69 @@ class Core():
def moduleIndexFor(self, compIndex):
return self.selectedModules[compIndex]
- def createPresetFile(self, compName, vers, saveValueStore, filename):
- dirname = os.path.join(self.presetDir, compName, str(vers))
- if not os.path.exists(dirname):
- os.makedirs(dirname)
- filepath = os.path.join(dirname, filename)
- with open(filepath, 'w') as f:
- f.write(Core.stringOrderedDict(saveValueStore))
-
def importPreset(self, filepath):
print(filepath)
- def exportPreset(self, exportName, compName, vers, origName):
- pass
+ def exportPreset(self, exportPath, compName, vers, origName):
+ internalPath = os.path.join(self.presetDir, compName, str(vers), origName)
+ if not os.path.exists(internalPath):
+ return
+ if os.path.exists(exportPath):
+ os.remove(exportPath)
+ with open(internalPath, 'r') as f:
+ internalData = [line for line in f]
+ try:
+ saveValueStore = dict(eval(internalData[0].strip()))
+ self.createPresetFile(
+ compName, vers,
+ origName, saveValueStore,
+ exportPath
+ )
+ except:
+ # TODO: add proper warning message
+ print('couldn\'t export %s' % exportPath)
+
+ def createPresetFile(
+ self, compName, vers, presetName, saveValueStore, filepath=''):
+ '''Create a preset file (.avl) at filepath using args.
+ Or if filepath is empty, create an internal preset using
+ the args for the filepath.'''
+ if not filepath:
+ dirname = os.path.join(self.presetDir, compName, str(vers))
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+ filepath = os.path.join(dirname, presetName)
+ internal = True
+ else:
+ if not filepath.endswith('.avl'):
+ filepath += '.avl'
+ internal = False
+
+ with open(filepath, 'w') as f:
+ if not internal:
+ f.write('[Components]\n')
+ f.write('%s\n' % compName)
+ f.write('%s\n' % str(vers))
+ f.write(Core.stringOrderedDict(saveValueStore))
+
+ def createProjectFile(self, filepath):
+ '''Create a project file (.avp) using the current program state'''
+ try:
+ if not filepath.endswith(".avp"):
+ filepath += '.avp'
+ if os.path.exists(filepath):
+ os.remove(filepath)
+ with open(filepath, 'w') as f:
+ print('creating %s' % filepath)
+ f.write('[Components]\n')
+ for comp in self.selectedComponents:
+ saveValueStore = comp.savePreset()
+ f.write('%s\n' % str(comp))
+ f.write('%s\n' % str(comp.version()))
+ f.write('%s\n' % Core.stringOrderedDict(saveValueStore))
+ return True
+ except:
+ return False
def loadEncoderOptions(self):
file_path = os.path.join(self.wd, 'encoder-options.json')
diff --git a/mainwindow.py b/mainwindow.py
index 3e49ab2..5c929c3 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -284,7 +284,7 @@ class MainWindow(QtCore.QObject):
if os.path.exists(self.autosavePath):
os.remove(self.autosavePath)
elif time.time() - self.lastAutosave >= 2.0:
- self.createProjectFile(self.autosavePath)
+ self.core.createProjectFile(self.autosavePath)
self.lastAutosave = time.time()
def autosaveExists(self):
@@ -504,7 +504,7 @@ class MainWindow(QtCore.QObject):
def saveCurrentProject(self):
if self.currentProject:
- self.createProjectFile(self.currentProject)
+ self.core.createProjectFile(self.currentProject)
else:
self.openSaveProjectDialog()
@@ -515,25 +515,11 @@ class MainWindow(QtCore.QObject):
"Project Files (*.avp)")
if not filename:
return
- self.createProjectFile(filename)
-
- def createProjectFile(self, filepath):
- if not filepath.endswith(".avp"):
- filepath += '.avp'
- if os.path.exists(filepath):
- os.remove(filepath)
- with open(filepath, 'w') as f:
- print('creating %s' % filepath)
- f.write('[Components]\n')
- for comp in self.core.selectedComponents:
- saveValueStore = comp.savePreset()
- f.write('%s\n' % str(comp))
- f.write('%s\n' % str(comp.version()))
- f.write('%s\n' % core.Core.stringOrderedDict(saveValueStore))
- if filepath != self.autosavePath:
- self.settings.setValue("projectDir", os.path.dirname(filepath))
- self.settings.setValue("currentProject", filepath)
- self.currentProject = filepath
+ self.settings.setValue("projectDir", os.path.dirname(filename))
+ self.settings.setValue("currentProject", filename)
+ self.currentProject = filename
+
+ self.core.createProjectFile(filename)
def openOpenProjectDialog(self):
filename = QtGui.QFileDialog.getOpenFileName(
diff --git a/presetmanager.py b/presetmanager.py
index 04a9042..7e4efbb 100644
--- a/presetmanager.py
+++ b/presetmanager.py
@@ -1,4 +1,4 @@
-from PyQt4 import QtGui
+from PyQt4 import QtGui, QtCore
from collections import OrderedDict
import string
import os
@@ -13,10 +13,12 @@ class PresetManager(QtGui.QDialog):
self.core = parent.core
self.settings = parent.settings
self.presetDir = self.core.presetDir
- self.window = window
self.findPresets()
+
self.lastFilter = '*'
self.presetRows = [] # list of (comp, vers, name) tuples
+ self.window = window
+ self.window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
# connect button signals
self.window.pushButton_delete.clicked.connect(self.openDeletePresetDialog)
@@ -130,17 +132,17 @@ class PresetManager(QtGui.QDialog):
componentName = str(self.selectedComponents[index]).strip()
vers = self.selectedComponents[index].version()
self.createNewPreset(
- componentName, vers, saveValueStore, newName)
+ componentName, vers, newName, saveValueStore)
self.selectedComponents[index].currentPreset = newName
self.findPresets()
self.drawPresetList()
break
- def createNewPreset(self, compName, vers, saveValueStore, filename):
+ def createNewPreset(self, compName, vers, filename, saveValueStore):
path = os.path.join(self.presetDir, compName, str(vers), filename)
if self.presetExists(path):
return
- self.core.createPresetFile(compName, vers, saveValueStore, filename)
+ self.core.createPresetFile(compName, vers, filename, saveValueStore)
def presetExists(self, path):
if os.path.exists(path):
diff --git a/presetmanager.ui b/presetmanager.ui
index b3c25fe..5257b1c 100644
--- a/presetmanager.ui
+++ b/presetmanager.ui
@@ -3,7 +3,10 @@
presetmanager
- Qt::ApplicationModal
+ Qt::NonModal
+
+
+ true
@@ -92,6 +95,9 @@
-
+
+ true
+
Rename
--
cgit v1.2.3
From be5d47f8634d29d58b9811657ede815814ffde18 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 11 Jun 2017 12:52:29 -0400
Subject: can't right-click empty space + color eyedropper
---
components/__base__.py | 4 +++-
components/text.py | 2 +-
core.py | 4 ++--
mainwindow.py | 27 +++++++++++++++------------
presetmanager.py | 6 +++++-
5 files changed, 26 insertions(+), 17 deletions(-)
diff --git a/components/__base__.py b/components/__base__.py
index b95edf4..3ccb5dc 100644
--- a/components/__base__.py
+++ b/components/__base__.py
@@ -24,7 +24,9 @@ class Component:
exec('self.%s = value' % var)
def pickColor(self):
- color = QtGui.QColorDialog.getColor()
+ dialog = QtGui.QColorDialog()
+ dialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True)
+ color = dialog.getColor()
if color.isValid():
RGBstring = '%s,%s,%s' % (
str(color.red()), str(color.green()), str(color.blue()))
diff --git a/components/text.py b/components/text.py
index 68cffca..e8fb3d5 100644
--- a/components/text.py
+++ b/components/text.py
@@ -31,7 +31,7 @@ class Component(__base__.Component):
page.comboBox_textAlign.addItem("Right")
page.lineEdit_textColor.setText('%s,%s,%s' % self.textColor)
- page.pushButton_textColor.clicked.connect(lambda: self.pickColor())
+ page.pushButton_textColor.clicked.connect(self.pickColor)
btnStyle = "QPushButton { background-color : %s; outline: none; }" \
% QColor(*self.textColor).name()
page.pushButton_textColor.setStyleSheet(btnStyle)
diff --git a/core.py b/core.py
index c50918f..e69de50 100644
--- a/core.py
+++ b/core.py
@@ -95,9 +95,9 @@ class Core():
origName, saveValueStore,
exportPath
)
+ return True
except:
- # TODO: add proper warning message
- print('couldn\'t export %s' % exportPath)
+ return False
def createPresetFile(
self, compName, vers, presetName, saveValueStore, filepath=''):
diff --git a/mainwindow.py b/mainwindow.py
index 5c929c3..2f04559 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -606,20 +606,25 @@ class MainWindow(QtCore.QObject):
def componentContextMenu(self, QPos):
'''Appears when right-clicking a component in the list'''
- if not self.window.listWidget_componentList.selectedItems():
+ componentList = self.window.listWidget_componentList
+ if not componentList.selectedItems():
+ return
+
+ # don't show menu if clicking empty space
+ parentPosition = componentList.mapToGlobal(QtCore.QPoint(0, 0))
+ index = componentList.currentRow()
+ modelIndex = componentList.model().index(index)
+ if not componentList.visualRect(modelIndex).contains(QPos):
return
self.presetManager.findPresets()
self.menu = QtGui.QMenu()
menuItem = self.menu.addAction("Save Preset")
- self.connect(
- menuItem,
- QtCore.SIGNAL("triggered()"),
+ menuItem.triggered.connect(
self.presetManager.openSavePresetDialog
)
# submenu for opening presets
- index = self.window.listWidget_componentList.currentRow()
try:
presets = self.presetManager.presets[str(self.core.selectedComponents[index])]
self.submenu = QtGui.QMenu("Open Preset")
@@ -627,14 +632,12 @@ class MainWindow(QtCore.QObject):
for version, presetName in presets:
menuItem = self.submenu.addAction(presetName)
- self.connect(
- menuItem,
- QtCore.SIGNAL("triggered()"),
- lambda presetName=presetName:
+ menuItem.triggered.connect(
+ lambda _, presetName=presetName:
self.presetManager.openPreset(presetName)
)
- except KeyError as e:
- print(e)
- parentPosition = self.window.listWidget_componentList.mapToGlobal(QtCore.QPoint(0, 0))
+ except KeyError:
+ pass
+
self.menu.move(parentPosition + QPos)
self.menu.show()
diff --git a/presetmanager.py b/presetmanager.py
index 7e4efbb..3036f7a 100644
--- a/presetmanager.py
+++ b/presetmanager.py
@@ -259,4 +259,8 @@ class PresetManager(QtGui.QDialog):
if filename:
index = self.window.listWidget_presets.currentRow()
comp, vers, name = self.presetRows[index]
- self.core.exportPreset(filename, comp, vers, name)
+ if not self.core.exportPreset(filename, comp, vers, name):
+ self.parent.showMessage(
+ msg='Couldn\'t export %s.' % filename,
+ parent=self.window
+ )
--
cgit v1.2.3
From 28f07272cc174efe8e219abed683d22f72f18d94 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 11 Jun 2017 17:03:40 -0400
Subject: using tab in component list updates widget
and more understandable function names for writing/reading presets
---
components/video.py | 11 +++++++++--
core.py | 14 +++++++++-----
main.py | 1 -
mainwindow.py | 7 +++----
presetmanager.py | 3 +--
5 files changed, 22 insertions(+), 14 deletions(-)
diff --git a/components/video.py b/components/video.py
index c529658..bd1bf96 100644
--- a/components/video.py
+++ b/components/video.py
@@ -10,8 +10,15 @@ from . import __base__
class Video:
'''Video Component Frame-Fetcher'''
def __init__(self, **kwargs):
- mandatoryArgs = ['ffmpeg', 'videoPath', 'width', 'height',
- 'frameRate', 'chunkSize', 'parent']
+ mandatoryArgs = [
+ 'ffmpeg', # path to ffmpeg, usually core.FFMPEG_BIN
+ 'videoPath',
+ 'width',
+ 'height',
+ 'frameRate', # frames per second
+ 'chunkSize', # number of bytes in one frame
+ 'parent'
+ ]
for arg in mandatoryArgs:
try:
exec('self.%s = kwargs[arg]' % arg)
diff --git a/core.py b/core.py
index e69de50..4f30973 100644
--- a/core.py
+++ b/core.py
@@ -71,7 +71,7 @@ class Core():
return endI
def updateComponent(self, i):
- print('updating %s' % self.selectedComponents[i])
+ # print('updating %s' % self.selectedComponents[i])
self.selectedComponents[i].update()
def moduleIndexFor(self, compIndex):
@@ -89,7 +89,7 @@ class Core():
with open(internalPath, 'r') as f:
internalData = [line for line in f]
try:
- saveValueStore = dict(eval(internalData[0].strip()))
+ saveValueStore = Core.presetFromString(internalData[0].strip())
self.createPresetFile(
compName, vers,
origName, saveValueStore,
@@ -120,7 +120,7 @@ class Core():
f.write('[Components]\n')
f.write('%s\n' % compName)
f.write('%s\n' % str(vers))
- f.write(Core.stringOrderedDict(saveValueStore))
+ f.write(Core.presetToString(saveValueStore))
def createProjectFile(self, filepath):
'''Create a project file (.avp) using the current program state'''
@@ -136,7 +136,7 @@ class Core():
saveValueStore = comp.savePreset()
f.write('%s\n' % str(comp))
f.write('%s\n' % str(comp.version()))
- f.write('%s\n' % Core.stringOrderedDict(saveValueStore))
+ f.write('%s\n' % Core.presetToString(saveValueStore))
return True
except:
return False
@@ -244,6 +244,10 @@ class Core():
return badName
@staticmethod
- def stringOrderedDict(dictionary):
+ def presetToString(dictionary):
sorted_ = OrderedDict(sorted(dictionary.items(), key=lambda t: t[0]))
return repr(sorted_)
+
+ @staticmethod
+ def presetFromString(string):
+ return dict(eval(string))
diff --git a/main.py b/main.py
index c771aca..7c0727b 100644
--- a/main.py
+++ b/main.py
@@ -1,5 +1,4 @@
from importlib import import_module
-from collections import OrderedDict
from PyQt4 import QtGui, uic
from PyQt4.QtCore import Qt
import sys
diff --git a/mainwindow.py b/mainwindow.py
index 2f04559..dbbc631 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -1,6 +1,5 @@
from os.path import expanduser
from queue import Queue
-from collections import OrderedDict
from PyQt4 import QtCore, QtGui, uic
from PyQt4.QtCore import QSettings, Qt
from PyQt4.QtGui import QMenu
@@ -159,8 +158,8 @@ class MainWindow(QtCore.QObject):
self.window.pushButton_addComponent.setMenu(self.compMenu)
componentList.dropEvent = self.componentListChanged
- componentList.clicked.connect(
- lambda _: self.changeComponentWidget())
+ componentList.itemSelectionChanged.connect(
+ self.changeComponentWidget)
self.window.pushButton_removeComponent.clicked.connect(
lambda _: self.removeComponent())
@@ -567,7 +566,7 @@ class MainWindow(QtCore.QObject):
# version, not used yet
i += 1
elif i == 2:
- saveValueStore = dict(eval(line))
+ saveValueStore = core.Core.presetFromString(line)
self.core.selectedComponents[-1].loadPreset(
saveValueStore)
self.updateComponentTitle(-1)
diff --git a/presetmanager.py b/presetmanager.py
index 3036f7a..6708d11 100644
--- a/presetmanager.py
+++ b/presetmanager.py
@@ -1,5 +1,4 @@
from PyQt4 import QtGui, QtCore
-from collections import OrderedDict
import string
import os
@@ -171,7 +170,7 @@ class PresetManager(QtGui.QDialog):
return
with open(filepath, 'r') as f:
for line in f:
- saveValueStore = dict(eval(line.strip()))
+ saveValueStore = core.Core.presetFromString(line.strip())
break
selectedComponents[index].loadPreset(
saveValueStore,
--
cgit v1.2.3
From dbbefbf70ec9fad1735ee7199992ca6123bd54be Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 11 Jun 2017 23:29:13 -0400
Subject: split up openProject code for use in importPreset
---
core.py | 121 ++++++++++++++++++++++++++++++++++++++++++++++---------
mainwindow.py | 60 +++------------------------
presetmanager.py | 18 ++++++++-
3 files changed, 124 insertions(+), 75 deletions(-)
diff --git a/core.py b/core.py
index 4f30973..c47068c 100644
--- a/core.py
+++ b/core.py
@@ -32,9 +32,8 @@ class Core():
self.wd = os.path.dirname(os.path.realpath(__file__))
self.loadEncoderOptions()
- self.modules = self.findComponents()
+ self.findComponents()
self.selectedComponents = []
- self.selectedModules = []
def findComponents(self):
def findComponents():
@@ -46,9 +45,10 @@ class Core():
continue
elif ext == '.py':
yield name
- return [
+ self.modules = [
import_module('components.%s' % name)
for name in findComponents()]
+ self.moduleIndexes = [i for i in range(len(self.modules))]
def insertComponent(self, compPos, moduleIndex):
if compPos < 0:
@@ -57,28 +57,114 @@ class Core():
compPos,
self.modules[moduleIndex].Component()
)
- self.selectedModules.insert(
- compPos,
- moduleIndex
- )
return compPos
def moveComponent(self, startI, endI):
comp = self.selectedComponents.pop(startI)
self.selectedComponents.insert(endI, comp)
- i = self.selectedModules.pop(startI)
- self.selectedModules.insert(endI, i)
return endI
def updateComponent(self, i):
# print('updating %s' % self.selectedComponents[i])
self.selectedComponents[i].update()
- def moduleIndexFor(self, compIndex):
- return self.selectedModules[compIndex]
+ def moduleIndexFor(self, compName):
+ compNames = [mod.Component.__doc__ for mod in self.modules]
+ index = compNames.index(compName)
+ return self.moduleIndexes[index]
+
+ def openProject(self, loader, filepath):
+ '''loader is the object calling this method (mainwindow/command)
+ which implements an insertComponent method'''
+ errcode, data = self.parseAvFile(filepath)
+ if errcode == 0:
+ for name, vers, preset in data['Components']:
+ loader.insertComponent(
+ self.moduleIndexFor(name), -1)
+ self.selectedComponents[-1].loadPreset(
+ preset)
+ elif errcode == 1:
+ typ, value, _ = data
+ if typ.__name__ == KeyError:
+ # probably just an old version, still loadable
+ print('file missing value: %s' % value)
+ return
+ loader.createNewProject()
+ msg = '%s: %s' % (typ.__name__, value)
+ loader.showMessage(
+ msg="Project file '%s' is corrupted." % filepath,
+ showCancel=False,
+ icon=QtGui.QMessageBox.Warning,
+ detail=msg)
+
+ def parseAvFile(self, filepath):
+ '''Parses an avp (project) or avl (preset package) file.
+ Returns data usable by another method.'''
+ data = {}
+ try:
+ with open(filepath, 'r') as f:
+ def parseLine(line):
+ '''Decides if a given avp or avl line is a section header'''
+ validSections = ('Components')
+ line = line.strip()
+ newSection = ''
+
+ if line.startswith('[') and line.endswith(']') \
+ and line[1:-1] in validSections:
+ newSection = line[1:-1]
+
+ return line, newSection
+
+ section = ''
+ i = 0
+ for line in f:
+ line, newSection = parseLine(line)
+ if newSection:
+ section = str(newSection)
+ data[section] = []
+ continue
+ if line and section == 'Components':
+ if i == 0:
+ lastCompName = str(line)
+ i += 1
+ elif i == 1:
+ lastCompVers = str(line)
+ i += 1
+ elif i == 2:
+ lastCompPreset = Core.presetFromString(line)
+ data[section].append(
+ (lastCompName,
+ lastCompVers,
+ lastCompPreset)
+ )
+ i = 0
+ return 0, data
+ except:
+ return 1, sys.exc_info()
def importPreset(self, filepath):
- print(filepath)
+ errcode, data = self.parseAvFile(filepath)
+ returnList = []
+ if errcode == 0:
+ name, vers, preset = data['Components'][0]
+ presetName = preset['preset'] \
+ if preset['preset'] else os.path.basename(filepath)[:-4]
+ newPath = os.path.join(
+ self.presetDir,
+ name,
+ vers,
+ presetName
+ )
+ if os.path.exists(newPath):
+ return False, newPath
+ preset['preset'] = presetName
+ self.createPresetFile(
+ name, vers, presetName, preset
+ )
+ return True, presetName
+ elif errcode == 1:
+ # TODO: an error message
+ return False, ''
def exportPreset(self, exportPath, compName, vers, origName):
internalPath = os.path.join(self.presetDir, compName, str(vers), origName)
@@ -237,17 +323,14 @@ class Core():
@staticmethod
def badName(name):
'''Returns whether a name contains non-alphanumeric chars'''
- badName = False
- for letter in name:
- if letter in string.punctuation:
- badName = True
- return badName
+ return any([letter in string.punctuation for letter in name])
@staticmethod
def presetToString(dictionary):
- sorted_ = OrderedDict(sorted(dictionary.items(), key=lambda t: t[0]))
- return repr(sorted_)
+ '''Alphabetizes a dict into OrderedDict & returns string repr'''
+ return repr(OrderedDict(sorted(dictionary.items(), key=lambda t: t[0])))
@staticmethod
def presetFromString(string):
+ '''Turns a string repr of OrderedDict into a regular dict'''
return dict(eval(string))
diff --git a/mainwindow.py b/mainwindow.py
index dbbc631..42105d1 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -54,7 +54,6 @@ class MainWindow(QtCore.QObject):
self.core = core.Core()
self.pages = [] # widgets of component settings
- self.componentRows = [] # (moduleIndex, QListWidgetItem) tuples
self.lastAutosave = time.time()
# Create data directory, load/create settings
@@ -419,7 +418,6 @@ class MainWindow(QtCore.QObject):
row = componentList.insertItem(
index,
self.core.selectedComponents[index].__doc__)
- self.componentRows.insert(compPos, (moduleIndex, row))
componentList.setCurrentRow(index)
self.pages.insert(index, self.core.selectedComponents[index].widget(self))
@@ -435,7 +433,6 @@ class MainWindow(QtCore.QObject):
index = componentList.row(selected)
self.window.stackedWidget.removeWidget(self.pages[index])
componentList.takeItem(index)
- self.componentRows.pop(index)
self.core.selectedComponents.pop(index)
self.pages.pop(index)
self.changeComponentWidget()
@@ -460,8 +457,6 @@ class MainWindow(QtCore.QObject):
stackedWidget.insertWidget(newRow, page)
componentList.setCurrentRow(newRow)
stackedWidget.setCurrentIndex(newRow)
- self.componentRows.pop(row)
- self.componentRows.insert(newRow, (self.core.moduleIndexFor(row), newItem))
self.drawPreview()
def componentListChanged(self, *args):
@@ -535,55 +530,12 @@ class MainWindow(QtCore.QObject):
self.currentProject = filepath
self.settings.setValue("currentProject", filepath)
self.settings.setValue("projectDir", os.path.dirname(filepath))
- compNames = [mod.Component.__doc__ for mod in self.core.modules]
- try:
- with open(filepath, 'r') as f:
- validSections = ('Components')
- section = ''
-
- def parseLine(line):
- line = line.strip()
- newSection = ''
-
- if line.startswith('[') and line.endswith(']') \
- and line[1:-1] in validSections:
- newSection = line[1:-1]
-
- return line, newSection
-
- i = 0
- for line in f:
- line, newSection = parseLine(line)
- if newSection:
- section = str(newSection)
- continue
- if line and section == 'Components':
- if i == 0:
- compIndex = compNames.index(line)
- self.insertComponent(compIndex, -1)
- i += 1
- elif i == 1:
- # version, not used yet
- i += 1
- elif i == 2:
- saveValueStore = core.Core.presetFromString(line)
- self.core.selectedComponents[-1].loadPreset(
- saveValueStore)
- self.updateComponentTitle(-1)
- i = 0
- except (IndexError, ValueError, NameError, SyntaxError,
- AttributeError, TypeError) as e:
- self.createNewProject()
- typ, value, _ = sys.exc_info()
- msg = '%s: %s' % (typ.__name__, value)
- self.showMessage(
- msg="Project file '%s' is corrupted." % filepath,
- showCancel=False,
- icon=QtGui.QMessageBox.Warning,
- detail=msg)
- except KeyError as e:
- # probably just an old version, still loadable
- print('project file missing value: %s' % e)
+ # actually load the project using core method
+ self.core.openProject(self, filepath)
+
+ for i in range(self.window.listWidget_componentList.count()):
+ # update listwidget titles to indicate loaded presets
+ self.updateComponentTitle(i)
def showMessage(self, **kwargs):
parent = kwargs['parent'] if 'parent' in kwargs else self.window
diff --git a/presetmanager.py b/presetmanager.py
index 6708d11..91dc373 100644
--- a/presetmanager.py
+++ b/presetmanager.py
@@ -148,7 +148,9 @@ class PresetManager(QtGui.QDialog):
ch = self.parent.showMessage(
msg="%s already exists! Overwrite it?" %
os.path.basename(path),
- showCancel=True, icon=QtGui.QMessageBox.Warning)
+ showCancel=True,
+ icon=QtGui.QMessageBox.Warning,
+ parent=self.window)
if not ch:
# user clicked cancel
return True
@@ -246,11 +248,23 @@ class PresetManager(QtGui.QDialog):
self.settings.value("projectDir"),
"Preset Files (*.avl)")
if filename:
- self.core.importPreset(filename)
+ path = ''
+ while True:
+ if path:
+ if self.presetExists(path):
+ break
+ else:
+ if os.path.exists(path):
+ os.remove(path)
+ success, path = self.core.importPreset(filename)
+ if success:
+ break
self.findPresets()
self.drawPresetList()
def openExportDialog(self):
+ if not self.window.listWidget_presets.selectedItems():
+ return
filename = QtGui.QFileDialog.getSaveFileName(
self.window, "Export Preset",
self.settings.value("projectDir"),
--
cgit v1.2.3
From 307d499f9ae2729c790fe9258d88aca72331cdf6 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Mon, 12 Jun 2017 22:34:37 -0400
Subject: adding an asterisk to modified, unsaved presets
flags for unsaved changes saved in project files
---
components/__base__.py | 38 +++++++++++++++-------
components/color.py | 7 +++-
components/image.py | 6 +++-
components/original.py | 10 ++++--
components/text.py | 11 +++++--
components/video.py | 6 +++-
core.py | 87 +++++++++++++++++++++++++++++++++++++++++++++++---
mainwindow.py | 26 +++++++++------
presetmanager.py | 32 +++++++++++--------
9 files changed, 175 insertions(+), 48 deletions(-)
diff --git a/components/__base__.py b/components/__base__.py
index 3ccb5dc..b32c120 100644
--- a/components/__base__.py
+++ b/components/__base__.py
@@ -1,9 +1,15 @@
-from PyQt4 import QtGui
+from PyQt4 import QtGui, QtCore
+class Component(QtCore.QObject):
+ '''A base class for components to inherit from'''
-class Component:
- def __init__(self):
+ # modified = QtCore.pyqtSignal(int, bool)
+
+ def __init__(self, moduleIndex, compPos):
+ super().__init__()
self.currentPreset = None
+ self.moduleIndex = moduleIndex
+ self.compPos = compPos
def __str__(self):
return self.__doc__
@@ -13,12 +19,27 @@ class Component:
return 1
def cancel(self):
- # make sure your component responds to these variables in frameRender()
+ # please stop any lengthy process in response to this variable
self.canceled = True
def reset(self):
self.canceled = False
+ def update(self):
+ self.modified.emit(self.compPos, True)
+ # use super().update() then read your widget values here
+
+ def loadPreset(self, presetDict, presetName):
+ '''Children should take (presetDict, presetName=None) as args'''
+
+ # Use super().loadPreset(presetDict, presetName)
+ # Then update your widgets using the preset dict
+ self.currentPreset = presetName \
+ if presetName != None else presetDict['preset']
+
+ def savePreset(self):
+ return {}
+
def preFrameRender(self, **kwargs):
for var, value in kwargs.items():
exec('self.%s = value' % var)
@@ -62,7 +83,7 @@ class Component:
return page
def update(self):
- # read widget values
+ super().update()
self.parent.drawPreview()
def previewRender(self, previewWorker):
@@ -77,13 +98,6 @@ class Component:
image = Image.new("RGBA", (width, height), (0,0,0,0))
return image
- def loadPreset(self, presetDict, presetName=None):
- self.currentPreset = presetName
- # update widgets using a preset dict
-
- def savePreset(self):
- return {}
-
def cancel(self):
self.canceled = True
diff --git a/components/color.py b/components/color.py
index 11c1b19..b6105c8 100644
--- a/components/color.py
+++ b/components/color.py
@@ -7,6 +7,9 @@ from . import __base__
class Component(__base__.Component):
'''Color'''
+
+ modified = QtCore.pyqtSignal(int, bool)
+
def widget(self, parent):
self.parent = parent
page = uic.loadUi(os.path.join(
@@ -45,6 +48,7 @@ class Component(__base__.Component):
return page
def update(self):
+ super().update()
self.color1 = self.RGBFromString(self.page.lineEdit_color1.text())
self.color2 = self.RGBFromString(self.page.lineEdit_color2.text())
self.x = self.page.spinBox_x.value()
@@ -70,7 +74,8 @@ class Component(__base__.Component):
return Image.new("RGBA", (width, height), (r, g, b, 255))
def loadPreset(self, pr, presetName=None):
- self.currentPreset = presetName if presetName else pr['preset']
+ super().loadPreset(pr, presetName)
+
self.page.lineEdit_color1.setText('%s,%s,%s' % pr['color1'])
self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2'])
diff --git a/components/image.py b/components/image.py
index 441e0e1..ed5f243 100644
--- a/components/image.py
+++ b/components/image.py
@@ -6,6 +6,9 @@ from . import __base__
class Component(__base__.Component):
'''Image'''
+
+ modified = QtCore.pyqtSignal(int, bool)
+
def widget(self, parent):
self.parent = parent
self.settings = parent.settings
@@ -22,6 +25,7 @@ class Component(__base__.Component):
return page
def update(self):
+ super().update()
self.imagePath = self.page.lineEdit_image.text()
self.parent.drawPreview()
@@ -49,7 +53,7 @@ class Component(__base__.Component):
return frame
def loadPreset(self, pr, presetName=None):
- self.currentPreset = presetName if presetName else pr['preset']
+ super().loadPreset(pr, presetName)
self.page.lineEdit_image.setText(pr['image'])
def savePreset(self):
diff --git a/components/original.py b/components/original.py
index 7873f43..f3f578d 100644
--- a/components/original.py
+++ b/components/original.py
@@ -1,9 +1,8 @@
import numpy
from PIL import Image, ImageDraw
-from PyQt4 import uic, QtGui
+from PyQt4 import uic, QtGui, QtCore
from PyQt4.QtGui import QColor
import os
-import random
from . import __base__
import time
from copy import copy
@@ -11,6 +10,9 @@ from copy import copy
class Component(__base__.Component):
'''Original Audio Visualization'''
+
+ modified = QtCore.pyqtSignal(int, bool)
+
def widget(self, parent):
self.parent = parent
self.visColor = (255, 255, 255)
@@ -33,12 +35,14 @@ class Component(__base__.Component):
return page
def update(self):
+ super().update()
self.layout = self.page.comboBox_visLayout.currentIndex()
self.visColor = self.RGBFromString(self.page.lineEdit_visColor.text())
self.parent.drawPreview()
def loadPreset(self, pr, presetName=None):
- self.currentPreset = presetName if presetName else pr['preset']
+ super().loadPreset(pr, presetName)
+
self.page.lineEdit_visColor.setText('%s,%s,%s' % pr['visColor'])
btnStyle = "QPushButton { background-color : %s; outline: none; }" \
% QColor(*pr['visColor']).name()
diff --git a/components/text.py b/components/text.py
index e8fb3d5..a40e2a9 100644
--- a/components/text.py
+++ b/components/text.py
@@ -9,8 +9,11 @@ from . import __base__
class Component(__base__.Component):
'''Title Text'''
- def __init__(self):
- super().__init__()
+
+ modified = QtCore.pyqtSignal(int, bool)
+
+ def __init__(self, *args):
+ super().__init__(*args)
self.titleFont = QFont()
def widget(self, parent):
@@ -53,6 +56,7 @@ class Component(__base__.Component):
return page
def update(self):
+ super().update()
self.title = self.page.lineEdit_title.text()
self.alignment = self.page.comboBox_textAlign.currentIndex()
self.titleFont = self.page.fontComboBox_titleFont.currentFont()
@@ -79,7 +83,8 @@ class Component(__base__.Component):
return x, self.yPosition
def loadPreset(self, pr, presetName=None):
- self.currentPreset = presetName if presetName else pr['preset']
+ super().loadPreset(pr, presetName)
+
self.page.lineEdit_title.setText(pr['title'])
font = QFont()
font.fromString(pr['titleFont'])
diff --git a/components/video.py b/components/video.py
index bd1bf96..ff06329 100644
--- a/components/video.py
+++ b/components/video.py
@@ -86,6 +86,9 @@ class Video:
class Component(__base__.Component):
'''Video'''
+
+ modified = QtCore.pyqtSignal(int, bool)
+
def widget(self, parent):
self.parent = parent
self.settings = parent.settings
@@ -106,6 +109,7 @@ class Component(__base__.Component):
return page
def update(self):
+ super().update()
self.videoPath = self.page.lineEdit_video.text()
self.loopVideo = self.page.checkBox_loop.isChecked()
self.parent.drawPreview()
@@ -136,7 +140,7 @@ class Component(__base__.Component):
return self.video.frame(frameNo)
def loadPreset(self, pr, presetName=None):
- self.currentPreset = presetName if presetName else pr['preset']
+ super().loadPreset(pr, presetName)
self.page.lineEdit_video.setText(pr['video'])
self.page.checkBox_loop.setChecked(pr['loop'])
diff --git a/core.py b/core.py
index c47068c..5617c81 100644
--- a/core.py
+++ b/core.py
@@ -34,6 +34,7 @@ class Core():
self.findComponents()
self.selectedComponents = []
+ self.modifiedComponents = {}
def findComponents(self):
def findComponents():
@@ -50,24 +51,86 @@ class Core():
for name in findComponents()]
self.moduleIndexes = [i for i in range(len(self.modules))]
+ def componentListChanged(self):
+ for i, component in enumerate(self.selectedComponents):
+ component.compPos = i
+ # print('Modified Components: ', self.modifiedComponents)
+
def insertComponent(self, compPos, moduleIndex):
if compPos < 0:
compPos = len(self.selectedComponents) -1
+
+ component = self.modules[moduleIndex].Component(
+ moduleIndex, compPos)
self.selectedComponents.insert(
compPos,
- self.modules[moduleIndex].Component()
- )
+ component)
+
+ newDict = {}
+ for i, val in self.modifiedComponents.items():
+ if i >= compPos:
+ newDict[i+1] = bool(val)
+ else:
+ newDict[i] = bool(val)
+ self.modifiedComponents.clear()
+ self.modifiedComponents = newDict
+
+ self.componentListChanged()
return compPos
def moveComponent(self, startI, endI):
+ def insert(target, comp):
+ self.selectedComponents.insert(target, comp)
+
comp = self.selectedComponents.pop(startI)
- self.selectedComponents.insert(endI, comp)
+ insert(endI, comp)
+
+ try:
+ oldModified = self.modifiedComponents.pop(startI)
+ if endI in self.modifiedComponents:
+ self.modifiedComponents[startI] = \
+ self.modifiedComponents.pop(endI)
+ self.modifiedComponents[endI] = oldModified
+ except KeyError:
+ pass
+
+ self.componentListChanged()
return endI
+ def removeComponent(self, i):
+ self.selectedComponents.pop(i)
+ try:
+ self.modifiedComponents.pop(i)
+ except KeyError:
+ pass
+
+ newDict = {}
+ for index, val in self.modifiedComponents.items():
+ if index >= i:
+ newDict[index-1] = bool(val)
+ else:
+ newDict[index] = bool(val)
+ self.modifiedComponents.clear()
+ self.modifiedComponents = newDict
+
+ self.componentListChanged()
+
def updateComponent(self, i):
# print('updating %s' % self.selectedComponents[i])
self.selectedComponents[i].update()
+ def componentModified(self, i):
+ '''Triggered by mainwindow.updateComponentTitle()
+ Tracks temporary state of whether components are modified or not
+ for retrieval upon loading a project file'''
+ self.modifiedComponents[i] = True
+
+ def componentUnmodified(self, i):
+ try:
+ self.modifiedComponents.pop(i)
+ except KeyError:
+ pass
+
def moduleIndexFor(self, compName):
compNames = [mod.Component.__doc__ for mod in self.modules]
index = compNames.index(compName)
@@ -78,11 +141,17 @@ class Core():
which implements an insertComponent method'''
errcode, data = self.parseAvFile(filepath)
if errcode == 0:
- for name, vers, preset in data['Components']:
+ for i, tup in enumerate(data['Components']):
+ name, vers, preset = tup
loader.insertComponent(
self.moduleIndexFor(name), -1)
self.selectedComponents[-1].loadPreset(
preset)
+ if data['Modified'][i] == True:
+ self.componentModified(i)
+ loader.updateComponentTitle(i, True)
+ else:
+ loader.updateComponentTitle(i)
elif errcode == 1:
typ, value, _ = data
if typ.__name__ == KeyError:
@@ -105,7 +174,7 @@ class Core():
with open(filepath, 'r') as f:
def parseLine(line):
'''Decides if a given avp or avl line is a section header'''
- validSections = ('Components')
+ validSections = ('Components', 'Modified')
line = line.strip()
newSection = ''
@@ -138,6 +207,8 @@ class Core():
lastCompPreset)
)
i = 0
+ if line and section == 'Modified':
+ data[section].append(eval(line))
return 0, data
except:
return 1, sys.exc_info()
@@ -223,6 +294,12 @@ class Core():
f.write('%s\n' % str(comp))
f.write('%s\n' % str(comp.version()))
f.write('%s\n' % Core.presetToString(saveValueStore))
+ f.write('[Modified]\n')
+ for i in range(len(self.selectedComponents)):
+ if i in self.modifiedComponents:
+ f.write('%s\n' % repr(True))
+ else:
+ f.write('%s\n' % repr(False))
return True
except:
return False
diff --git a/mainwindow.py b/mainwindow.py
index 42105d1..2a04a4a 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -235,13 +235,21 @@ class MainWindow(QtCore.QObject):
self.previewThread.wait()
self.autosave()
- def updateComponentTitle(self, pos):
+ @QtCore.pyqtSlot(int, bool)
+ def updateComponentTitle(self, pos, modified=False):
+ #print(pos, modified)
if pos < 0:
pos = len(self.core.selectedComponents)-1
title = str(self.core.selectedComponents[pos])
if self.core.selectedComponents[pos].currentPreset:
title += ' - %s' % self.core.selectedComponents[pos].currentPreset
+ if modified:
+ title += '*'
self.window.listWidget_componentList.item(pos).setText(title)
+ if modified:
+ self.core.componentModified(pos)
+ else:
+ self.core.componentUnmodified(pos)
def updateCodecs(self):
containerWidget = self.window.comboBox_videoContainer
@@ -344,9 +352,6 @@ class MainWindow(QtCore.QObject):
self.showMessage(
msg="You must select an audio file and output filename.")
- def progressBarUpdated(self, value):
- self.window.progressBar_createVideo.setValue(value)
-
def changeEncodingStatus(self, status):
if status:
self.window.pushButton_createVideo.setEnabled(False)
@@ -385,6 +390,9 @@ class MainWindow(QtCore.QObject):
self.window.pushButton_presets.setEnabled(True)
self.window.listWidget_componentList.setEnabled(True)
+ def progressBarUpdated(self, value):
+ self.window.progressBar_createVideo.setValue(value)
+
def progressBarSetText(self, value):
self.window.progressBar_createVideo.setFormat(value)
@@ -420,6 +428,10 @@ class MainWindow(QtCore.QObject):
self.core.selectedComponents[index].__doc__)
componentList.setCurrentRow(index)
+ # connect to signal that adds an asterisk when modified
+ self.core.selectedComponents[index].modified.connect(
+ self.updateComponentTitle)
+
self.pages.insert(index, self.core.selectedComponents[index].widget(self))
stackedWidget.insertWidget(index, self.pages[index])
stackedWidget.setCurrentIndex(index)
@@ -433,7 +445,7 @@ class MainWindow(QtCore.QObject):
index = componentList.row(selected)
self.window.stackedWidget.removeWidget(self.pages[index])
componentList.takeItem(index)
- self.core.selectedComponents.pop(index)
+ self.core.removeComponent(index)
self.pages.pop(index)
self.changeComponentWidget()
self.drawPreview()
@@ -533,10 +545,6 @@ class MainWindow(QtCore.QObject):
# actually load the project using core method
self.core.openProject(self, filepath)
- for i in range(self.window.listWidget_componentList.count()):
- # update listwidget titles to indicate loaded presets
- self.updateComponentTitle(i)
-
def showMessage(self, **kwargs):
parent = kwargs['parent'] if 'parent' in kwargs else self.window
msg = QtGui.QMessageBox(parent)
diff --git a/presetmanager.py b/presetmanager.py
index 91dc373..4300ce1 100644
--- a/presetmanager.py
+++ b/presetmanager.py
@@ -105,14 +105,14 @@ class PresetManager(QtGui.QDialog):
def openSavePresetDialog(self):
'''Functions on mainwindow level from the context menu'''
window = self.parent.window
- self.selectedComponents = self.parent.core.selectedComponents
+ selectedComponents = self.parent.core.selectedComponents
componentList = self.parent.window.listWidget_componentList
if componentList.currentRow() == -1:
return
while True:
index = componentList.currentRow()
- currentPreset = self.selectedComponents[index].currentPreset
+ currentPreset = selectedComponents[index].currentPreset
newName, OK = QtGui.QInputDialog.getText(
self.parent.window,
'Audio Visualizer',
@@ -127,30 +127,35 @@ class PresetManager(QtGui.QDialog):
if newName:
if index != -1:
saveValueStore = \
- self.selectedComponents[index].savePreset()
- componentName = str(self.selectedComponents[index]).strip()
- vers = self.selectedComponents[index].version()
+ selectedComponents[index].savePreset()
+ componentName = str(selectedComponents[index]).strip()
+ vers = selectedComponents[index].version()
self.createNewPreset(
- componentName, vers, newName, saveValueStore)
- self.selectedComponents[index].currentPreset = newName
- self.findPresets()
- self.drawPresetList()
+ componentName, vers, newName,
+ saveValueStore, window=self.parent.window)
+ selectedComponents[index].currentPreset = newName
+ #self.findPresets()
+ #self.drawPresetList()
+ self.parent.updateComponentTitle(index)
break
- def createNewPreset(self, compName, vers, filename, saveValueStore):
+ def createNewPreset(
+ self, compName, vers, filename, saveValueStore, **kwargs):
path = os.path.join(self.presetDir, compName, str(vers), filename)
- if self.presetExists(path):
+ if self.presetExists(path, **kwargs):
return
self.core.createPresetFile(compName, vers, filename, saveValueStore)
- def presetExists(self, path):
+ def presetExists(self, path, **kwargs):
if os.path.exists(path):
+ window = self.window \
+ if 'window' not in kwargs else kwargs['window']
ch = self.parent.showMessage(
msg="%s already exists! Overwrite it?" %
os.path.basename(path),
showCancel=True,
icon=QtGui.QMessageBox.Warning,
- parent=self.window)
+ parent=window)
if not ch:
# user clicked cancel
return True
@@ -204,6 +209,7 @@ class PresetManager(QtGui.QDialog):
os.remove(filepath)
def warnMessage(self, window=None):
+ print(window)
self.parent.showMessage(
msg='Preset names must contain only letters, '
'numbers, and spaces.',
--
cgit v1.2.3
From 2ad14b7d6ca9216bcdc72c5e13937fcbccc887a3 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 13 Jun 2017 22:47:18 -0400
Subject: asterisk next to modified preset is more accurate
hopefully
---
components/__base__.py | 9 ++--
components/color.py | 4 +-
components/image.py | 4 +-
components/original.py | 4 +-
components/text.py | 4 +-
components/video.py | 4 +-
core.py | 125 +++++++++++++++++++------------------------------
mainwindow.py | 20 +++++---
presetmanager.py | 19 ++------
9 files changed, 80 insertions(+), 113 deletions(-)
diff --git a/components/__base__.py b/components/__base__.py
index b32c120..bc6644b 100644
--- a/components/__base__.py
+++ b/components/__base__.py
@@ -1,5 +1,6 @@
from PyQt4 import QtGui, QtCore
+
class Component(QtCore.QObject):
'''A base class for components to inherit from'''
@@ -26,8 +27,8 @@ class Component(QtCore.QObject):
self.canceled = False
def update(self):
- self.modified.emit(self.compPos, True)
- # use super().update() then read your widget values here
+ self.modified.emit(self.compPos, self.savePreset())
+ # read your widget values, then call super().update()
def loadPreset(self, presetDict, presetName):
'''Children should take (presetDict, presetName=None) as args'''
@@ -36,10 +37,10 @@ class Component(QtCore.QObject):
# Then update your widgets using the preset dict
self.currentPreset = presetName \
if presetName != None else presetDict['preset']
-
+ '''
def savePreset(self):
return {}
-
+ '''
def preFrameRender(self, **kwargs):
for var, value in kwargs.items():
exec('self.%s = value' % var)
diff --git a/components/color.py b/components/color.py
index b6105c8..5912bfa 100644
--- a/components/color.py
+++ b/components/color.py
@@ -8,7 +8,7 @@ from . import __base__
class Component(__base__.Component):
'''Color'''
- modified = QtCore.pyqtSignal(int, bool)
+ modified = QtCore.pyqtSignal(int, dict)
def widget(self, parent):
self.parent = parent
@@ -48,12 +48,12 @@ class Component(__base__.Component):
return page
def update(self):
- super().update()
self.color1 = self.RGBFromString(self.page.lineEdit_color1.text())
self.color2 = self.RGBFromString(self.page.lineEdit_color2.text())
self.x = self.page.spinBox_x.value()
self.y = self.page.spinBox_y.value()
self.parent.drawPreview()
+ super().update()
def previewRender(self, previewWorker):
width = int(previewWorker.core.settings.value('outputWidth'))
diff --git a/components/image.py b/components/image.py
index ed5f243..cdf10c7 100644
--- a/components/image.py
+++ b/components/image.py
@@ -7,7 +7,7 @@ from . import __base__
class Component(__base__.Component):
'''Image'''
- modified = QtCore.pyqtSignal(int, bool)
+ modified = QtCore.pyqtSignal(int, dict)
def widget(self, parent):
self.parent = parent
@@ -25,9 +25,9 @@ class Component(__base__.Component):
return page
def update(self):
- super().update()
self.imagePath = self.page.lineEdit_image.text()
self.parent.drawPreview()
+ super().update()
def previewRender(self, previewWorker):
width = int(previewWorker.core.settings.value('outputWidth'))
diff --git a/components/original.py b/components/original.py
index f3f578d..9df2815 100644
--- a/components/original.py
+++ b/components/original.py
@@ -11,7 +11,7 @@ from copy import copy
class Component(__base__.Component):
'''Original Audio Visualization'''
- modified = QtCore.pyqtSignal(int, bool)
+ modified = QtCore.pyqtSignal(int, dict)
def widget(self, parent):
self.parent = parent
@@ -35,10 +35,10 @@ class Component(__base__.Component):
return page
def update(self):
- super().update()
self.layout = self.page.comboBox_visLayout.currentIndex()
self.visColor = self.RGBFromString(self.page.lineEdit_visColor.text())
self.parent.drawPreview()
+ super().update()
def loadPreset(self, pr, presetName=None):
super().loadPreset(pr, presetName)
diff --git a/components/text.py b/components/text.py
index a40e2a9..165a093 100644
--- a/components/text.py
+++ b/components/text.py
@@ -10,7 +10,7 @@ from . import __base__
class Component(__base__.Component):
'''Title Text'''
- modified = QtCore.pyqtSignal(int, bool)
+ modified = QtCore.pyqtSignal(int, dict)
def __init__(self, *args):
super().__init__(*args)
@@ -56,7 +56,6 @@ class Component(__base__.Component):
return page
def update(self):
- super().update()
self.title = self.page.lineEdit_title.text()
self.alignment = self.page.comboBox_textAlign.currentIndex()
self.titleFont = self.page.fontComboBox_titleFont.currentFont()
@@ -66,6 +65,7 @@ class Component(__base__.Component):
self.textColor = self.RGBFromString(
self.page.lineEdit_textColor.text())
self.parent.drawPreview()
+ super().update()
def getXY(self):
'''Returns true x, y after considering alignment settings'''
diff --git a/components/video.py b/components/video.py
index ff06329..841f77b 100644
--- a/components/video.py
+++ b/components/video.py
@@ -87,7 +87,7 @@ class Video:
class Component(__base__.Component):
'''Video'''
- modified = QtCore.pyqtSignal(int, bool)
+ modified = QtCore.pyqtSignal(int, dict)
def widget(self, parent):
self.parent = parent
@@ -109,10 +109,10 @@ class Component(__base__.Component):
return page
def update(self):
- super().update()
self.videoPath = self.page.lineEdit_video.text()
self.loopVideo = self.page.checkBox_loop.isChecked()
self.parent.drawPreview()
+ super().update()
def previewRender(self, previewWorker):
width = int(previewWorker.core.settings.value('outputWidth'))
diff --git a/core.py b/core.py
index 5617c81..ef369c9 100644
--- a/core.py
+++ b/core.py
@@ -6,9 +6,7 @@ from os.path import expanduser
import subprocess as sp
import numpy
from PIL import Image
-#import tempfile
from shutil import rmtree
-#import atexit
import time
from collections import OrderedDict
import json
@@ -21,11 +19,6 @@ class Core():
def __init__(self):
self.FFMPEG_BIN = self.findFfmpeg()
- #self.tempDir = os.path.join(
- # tempfile.gettempdir(), 'audio-visualizer-python-data')
- #if not os.path.exists(self.tempDir):
- # os.makedirs(self.tempDir)
- #atexit.register(self.deleteTempDir)
self.dataDir = QDesktopServices.storageLocation(
QDesktopServices.DataLocation)
self.presetDir = os.path.join(self.dataDir, 'presets')
@@ -34,7 +27,8 @@ class Core():
self.findComponents()
self.selectedComponents = []
- self.modifiedComponents = {}
+ # copies of named presets to detect modification
+ self.savedPresets = {}
def findComponents(self):
def findComponents():
@@ -54,7 +48,6 @@ class Core():
def componentListChanged(self):
for i, component in enumerate(self.selectedComponents):
component.compPos = i
- # print('Modified Components: ', self.modifiedComponents)
def insertComponent(self, compPos, moduleIndex):
if compPos < 0:
@@ -66,76 +59,52 @@ class Core():
compPos,
component)
- newDict = {}
- for i, val in self.modifiedComponents.items():
- if i >= compPos:
- newDict[i+1] = bool(val)
- else:
- newDict[i] = bool(val)
- self.modifiedComponents.clear()
- self.modifiedComponents = newDict
-
self.componentListChanged()
return compPos
def moveComponent(self, startI, endI):
- def insert(target, comp):
- self.selectedComponents.insert(target, comp)
-
comp = self.selectedComponents.pop(startI)
- insert(endI, comp)
-
- try:
- oldModified = self.modifiedComponents.pop(startI)
- if endI in self.modifiedComponents:
- self.modifiedComponents[startI] = \
- self.modifiedComponents.pop(endI)
- self.modifiedComponents[endI] = oldModified
- except KeyError:
- pass
+ self.selectedComponents.insert(endI, comp)
self.componentListChanged()
return endI
def removeComponent(self, i):
self.selectedComponents.pop(i)
- try:
- self.modifiedComponents.pop(i)
- except KeyError:
- pass
-
- newDict = {}
- for index, val in self.modifiedComponents.items():
- if index >= i:
- newDict[index-1] = bool(val)
- else:
- newDict[index] = bool(val)
- self.modifiedComponents.clear()
- self.modifiedComponents = newDict
-
self.componentListChanged()
def updateComponent(self, i):
# print('updating %s' % self.selectedComponents[i])
self.selectedComponents[i].update()
- def componentModified(self, i):
- '''Triggered by mainwindow.updateComponentTitle()
- Tracks temporary state of whether components are modified or not
- for retrieval upon loading a project file'''
- self.modifiedComponents[i] = True
-
- def componentUnmodified(self, i):
- try:
- self.modifiedComponents.pop(i)
- except KeyError:
- pass
-
def moduleIndexFor(self, compName):
compNames = [mod.Component.__doc__ for mod in self.modules]
index = compNames.index(compName)
return self.moduleIndexes[index]
+ def openPreset(self, filepath, compIndex, presetName):
+ '''Applies a preset to a specific component'''
+ saveValueStore = self.getPreset(filepath)
+ if not saveValueStore:
+ return False
+
+ self.selectedComponents[compIndex].loadPreset(
+ saveValueStore,
+ presetName
+ )
+ self.savedPresets[presetName] = dict(saveValueStore)
+ return True
+
+ def getPreset(self, filepath):
+ '''Returns the preset dict stored at this filepath'''
+ if not os.path.exists(filepath):
+ return False
+ with open(filepath, 'r') as f:
+ for line in f:
+ saveValueStore = Core.presetFromString(line.strip())
+ break
+ return saveValueStore
+
def openProject(self, loader, filepath):
'''loader is the object calling this method (mainwindow/command)
which implements an insertComponent method'''
@@ -143,15 +112,29 @@ class Core():
if errcode == 0:
for i, tup in enumerate(data['Components']):
name, vers, preset = tup
+
+ # add loaded named presets to savedPresets dict
+ if 'preset' in preset and preset['preset'] != None:
+ nam = preset['preset']
+ filepath2 = os.path.join(
+ self.presetDir, name, str(vers), nam)
+ origSaveValueStore = self.getPreset(filepath2)
+ self.savedPresets[nam] = dict(origSaveValueStore)
+
+ # insert component into the loader
loader.insertComponent(
self.moduleIndexFor(name), -1)
- self.selectedComponents[-1].loadPreset(
- preset)
- if data['Modified'][i] == True:
- self.componentModified(i)
- loader.updateComponentTitle(i, True)
+
+ if 'preset' in preset and preset['preset'] != None:
+ self.selectedComponents[-1].loadPreset(
+ preset
+ )
else:
- loader.updateComponentTitle(i)
+ self.selectedComponents[-1].loadPreset(
+ preset,
+ preset['preset']
+ )
+
elif errcode == 1:
typ, value, _ = data
if typ.__name__ == KeyError:
@@ -174,7 +157,7 @@ class Core():
with open(filepath, 'r') as f:
def parseLine(line):
'''Decides if a given avp or avl line is a section header'''
- validSections = ('Components', 'Modified')
+ validSections = ('Components')
line = line.strip()
newSection = ''
@@ -207,8 +190,6 @@ class Core():
lastCompPreset)
)
i = 0
- if line and section == 'Modified':
- data[section].append(eval(line))
return 0, data
except:
return 1, sys.exc_info()
@@ -294,12 +275,6 @@ class Core():
f.write('%s\n' % str(comp))
f.write('%s\n' % str(comp.version()))
f.write('%s\n' % Core.presetToString(saveValueStore))
- f.write('[Modified]\n')
- for i in range(len(self.selectedComponents)):
- if i in self.modifiedComponents:
- f.write('%s\n' % repr(True))
- else:
- f.write('%s\n' % repr(False))
return True
except:
return False
@@ -385,12 +360,6 @@ class Core():
return completeAudioArray
- #def deleteTempDir(self):
- # try:
- # rmtree(self.tempDir)
- # except FileNotFoundError:
- # pass
-
def cancel(self):
self.canceled = True
diff --git a/mainwindow.py b/mainwindow.py
index 2a04a4a..3ea4f58 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -235,9 +235,17 @@ class MainWindow(QtCore.QObject):
self.previewThread.wait()
self.autosave()
- @QtCore.pyqtSlot(int, bool)
- def updateComponentTitle(self, pos, modified=False):
- #print(pos, modified)
+ @QtCore.pyqtSlot(int, dict)
+ def updateComponentTitle(self, pos, presetStore=False):
+ if type(presetStore) == dict:
+ name = presetStore['preset']
+ if name == None:
+ modified = False
+ else:
+ modified = (presetStore != self.core.savedPresets[name])
+ else:
+ print(pos, presetStore)
+ modified = bool(presetStore)
if pos < 0:
pos = len(self.core.selectedComponents)-1
title = str(self.core.selectedComponents[pos])
@@ -246,10 +254,6 @@ class MainWindow(QtCore.QObject):
if modified:
title += '*'
self.window.listWidget_componentList.item(pos).setText(title)
- if modified:
- self.core.componentModified(pos)
- else:
- self.core.componentUnmodified(pos)
def updateCodecs(self):
containerWidget = self.window.comboBox_videoContainer
@@ -521,6 +525,8 @@ class MainWindow(QtCore.QObject):
"Project Files (*.avp)")
if not filename:
return
+ if not filename.endswith(".avp"):
+ filename += '.avp'
self.settings.setValue("projectDir", os.path.dirname(filename))
self.settings.setValue("currentProject", filename)
self.currentProject = filename
diff --git a/presetmanager.py b/presetmanager.py
index 4300ce1..2083483 100644
--- a/presetmanager.py
+++ b/presetmanager.py
@@ -14,6 +14,7 @@ class PresetManager(QtGui.QDialog):
self.presetDir = self.core.presetDir
self.findPresets()
+ # window
self.lastFilter = '*'
self.presetRows = [] # list of (comp, vers, name) tuples
self.window = window
@@ -126,6 +127,7 @@ class PresetManager(QtGui.QDialog):
continue
if newName:
if index != -1:
+ selectedComponents[index].currentPreset = newName
saveValueStore = \
selectedComponents[index].savePreset()
componentName = str(selectedComponents[index]).strip()
@@ -133,10 +135,7 @@ class PresetManager(QtGui.QDialog):
self.createNewPreset(
componentName, vers, newName,
saveValueStore, window=self.parent.window)
- selectedComponents[index].currentPreset = newName
- #self.findPresets()
- #self.drawPresetList()
- self.parent.updateComponentTitle(index)
+ self.openPreset(newName)
break
def createNewPreset(
@@ -173,16 +172,8 @@ class PresetManager(QtGui.QDialog):
version = selectedComponents[index].version()
dirname = os.path.join(self.presetDir, componentName, str(version))
filepath = os.path.join(dirname, presetName)
- if not os.path.exists(filepath):
- return
- with open(filepath, 'r') as f:
- for line in f:
- saveValueStore = core.Core.presetFromString(line.strip())
- break
- selectedComponents[index].loadPreset(
- saveValueStore,
- presetName
- )
+ self.core.openPreset(filepath, index, presetName)
+
self.parent.updateComponentTitle(index)
self.parent.drawPreview()
--
cgit v1.2.3
From 807e37bddd16cb8fa195a220d415cb4bedb1364b Mon Sep 17 00:00:00 2001
From: tassaron
Date: Wed, 14 Jun 2017 17:36:46 -0400
Subject: no keyerror when opening new preset
---
mainwindow.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mainwindow.py b/mainwindow.py
index 3ea4f58..4b2d567 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -239,7 +239,7 @@ class MainWindow(QtCore.QObject):
def updateComponentTitle(self, pos, presetStore=False):
if type(presetStore) == dict:
name = presetStore['preset']
- if name == None:
+ if name == None or name not in self.core.savedPresets:
modified = False
else:
modified = (presetStore != self.core.savedPresets[name])
--
cgit v1.2.3
From 8846af57ba9635fe4a1c44778dc468f22277e538 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Wed, 14 Jun 2017 19:37:47 -0400
Subject: image component stretch/scale/x/y options
---
components/image.py | 24 +++++++++++++++++--
components/image.ui | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++---
components/video.ui | 43 +++++++++++++++++++++++++++++----
core.py | 23 ++++++++++--------
4 files changed, 139 insertions(+), 19 deletions(-)
diff --git a/components/image.py b/components/image.py
index cdf10c7..a2f0521 100644
--- a/components/image.py
+++ b/components/image.py
@@ -20,12 +20,20 @@ class Component(__base__.Component):
page.lineEdit_image.textChanged.connect(self.update)
page.pushButton_image.clicked.connect(self.pickImage)
+ page.spinBox_scale.valueChanged.connect(self.update)
+ page.checkBox_stretch.stateChanged.connect(self.update)
+ page.spinBox_x.valueChanged.connect(self.update)
+ page.spinBox_y.valueChanged.connect(self.update)
self.page = page
return page
def update(self):
self.imagePath = self.page.lineEdit_image.text()
+ self.scale = self.page.spinBox_scale.value()
+ self.xPosition = self.page.spinBox_x.value()
+ self.yPosition = self.page.spinBox_y.value()
+ self.stretched = self.page.checkBox_stretch.isChecked()
self.parent.drawPreview()
super().update()
@@ -47,19 +55,31 @@ class Component(__base__.Component):
frame = Image.new("RGBA", (width, height), (0, 0, 0, 0))
if self.imagePath and os.path.exists(self.imagePath):
image = Image.open(self.imagePath)
- if image.size != (width, height):
+ if self.stretched and image.size != (width, height):
image = image.resize((width, height), Image.ANTIALIAS)
- frame.paste(image)
+ if self.scale != 100:
+ newHeight = int((image.height / 100) * self.scale)
+ newWidth = int((image.width / 100) * self.scale)
+ image = image.resize((newWidth, newHeight), Image.ANTIALIAS)
+ frame.paste(image, box=(self.xPosition, self.yPosition))
return frame
def loadPreset(self, pr, presetName=None):
super().loadPreset(pr, presetName)
self.page.lineEdit_image.setText(pr['image'])
+ self.page.spinBox_scale.setValue(pr['scale'])
+ self.page.spinBox_x.setValue(pr['x'])
+ self.page.spinBox_y.setValue(pr['y'])
+ self.page.checkBox_stretch.setChecked(pr['stretched'])
def savePreset(self):
return {
'preset': self.currentPreset,
'image': self.imagePath,
+ 'scale': self.scale,
+ 'stretched': self.stretched,
+ 'x': self.xPosition,
+ 'y': self.yPosition,
}
def pickImage(self):
diff --git a/components/image.ui b/components/image.ui
index 3cd5b1b..685e997 100644
--- a/components/image.ui
+++ b/components/image.ui
@@ -124,8 +124,11 @@
16777215
+
+ -10000
+
- 999999999
+ 10000
@@ -163,10 +166,10 @@
- 0
+ -1000
- 999999999
+ 1000
0
@@ -177,6 +180,65 @@
+ -
+
+
-
+
+
+ Stretch
+
+
+ false
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+ Scale
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ QAbstractSpinBox::UpDownArrows
+
+
+ %
+
+
+ 10
+
+
+ 200
+
+
+ 100
+
+
+
+
+
-
diff --git a/components/video.ui b/components/video.ui
index 6a01368..aca46b4 100644
--- a/components/video.ui
+++ b/components/video.ui
@@ -111,7 +111,7 @@
-
-
+
0
@@ -124,8 +124,11 @@
16777215
+
+ -10000
+
- 999999999
+ 10000
@@ -163,10 +166,10 @@
- 0
+ -10000
- 999999999
+ 10000
0
@@ -202,6 +205,35 @@
+ -
+
+
+ Scale
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ QAbstractSpinBox::UpDownArrows
+
+
+ %
+
+
+ 10
+
+
+ 200
+
+
+ 100
+
+
+
-
@@ -217,6 +249,9 @@
+ -
+
+
diff --git a/core.py b/core.py
index ef369c9..9276f4a 100644
--- a/core.py
+++ b/core.py
@@ -124,16 +124,19 @@ class Core():
# insert component into the loader
loader.insertComponent(
self.moduleIndexFor(name), -1)
-
- if 'preset' in preset and preset['preset'] != None:
- self.selectedComponents[-1].loadPreset(
- preset
- )
- else:
- self.selectedComponents[-1].loadPreset(
- preset,
- preset['preset']
- )
+ try:
+ if 'preset' in preset and preset['preset'] != None:
+ self.selectedComponents[-1].loadPreset(
+ preset
+ )
+ else:
+ self.selectedComponents[-1].loadPreset(
+ preset,
+ preset['preset']
+ )
+ except KeyError as e:
+ print('%s missing value %s' %
+ (self.selectedComponents[-1], e))
elif errcode == 1:
typ, value, _ = data
--
cgit v1.2.3
From cb639e5c7ccf42e654f1dda1b75b082478cf73d9 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 15 Jun 2017 11:36:26 -0400
Subject: clear preset button, disable New Project during export
enable preset manager during export, and clear deleted presets from project files when opened
---
core.py | 17 ++++++++++++++++-
mainwindow.py | 20 ++++++++++++++------
presetmanager.py | 21 ++++++++++++++++++---
3 files changed, 48 insertions(+), 10 deletions(-)
diff --git a/core.py b/core.py
index 9276f4a..8eb7d16 100644
--- a/core.py
+++ b/core.py
@@ -82,6 +82,12 @@ class Core():
index = compNames.index(compName)
return self.moduleIndexes[index]
+ def clearPreset(self, compIndex, loader=None):
+ '''Clears a preset from a component'''
+ self.selectedComponents[compIndex].currentPreset = None
+ if loader:
+ loader.updateComponentTitle(compIndex)
+
def openPreset(self, filepath, compIndex, presetName):
'''Applies a preset to a specific component'''
saveValueStore = self.getPreset(filepath)
@@ -112,6 +118,7 @@ class Core():
if errcode == 0:
for i, tup in enumerate(data['Components']):
name, vers, preset = tup
+ clearThis = False
# add loaded named presets to savedPresets dict
if 'preset' in preset and preset['preset'] != None:
@@ -119,7 +126,11 @@ class Core():
filepath2 = os.path.join(
self.presetDir, name, str(vers), nam)
origSaveValueStore = self.getPreset(filepath2)
- self.savedPresets[nam] = dict(origSaveValueStore)
+ if origSaveValueStore:
+ self.savedPresets[nam] = dict(origSaveValueStore)
+ else:
+ # saved preset was renamed or deleted
+ clearThis = True
# insert component into the loader
loader.insertComponent(
@@ -138,6 +149,10 @@ class Core():
print('%s missing value %s' %
(self.selectedComponents[-1], e))
+ if clearThis:
+ self.clearPreset(-1, loader)
+
+
elif errcode == 1:
typ, value, _ = data
if typ.__name__ == KeyError:
diff --git a/mainwindow.py b/mainwindow.py
index 4b2d567..5004bf6 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -67,6 +67,7 @@ class MainWindow(QtCore.QObject):
os.path.join(os.path.dirname(os.path.realpath(__file__)),
'presetmanager.ui')),
self)
+
if not os.path.exists(self.dataDir):
os.makedirs(self.dataDir)
for neededDirectory in (
@@ -189,11 +190,11 @@ class MainWindow(QtCore.QObject):
# Configure the Projects Menu
self.projectMenu = QMenu()
- action = self.projectMenu.addAction("New Project")
- action.triggered[()].connect(self.createNewProject)
+ self.ui_newProject = self.projectMenu.addAction("New Project")
+ self.ui_newProject.triggered[()].connect(self.createNewProject)
- action = self.projectMenu.addAction("Open Project")
- action.triggered[()].connect(self.openOpenProjectDialog)
+ self.ui_openProject = self.projectMenu.addAction("Open Project")
+ self.ui_openProject.triggered[()].connect(self.openOpenProjectDialog)
action = self.projectMenu.addAction("Save Project")
action.triggered[()].connect(self.saveCurrentProject)
@@ -373,8 +374,9 @@ class MainWindow(QtCore.QObject):
self.window.pushButton_removeComponent.setEnabled(False)
self.window.pushButton_listMoveDown.setEnabled(False)
self.window.pushButton_listMoveUp.setEnabled(False)
- self.window.pushButton_presets.setEnabled(False)
self.window.listWidget_componentList.setEnabled(False)
+ self.ui_newProject.setEnabled(False)
+ self.ui_openProject.setEnabled(False)
else:
self.window.pushButton_createVideo.setEnabled(True)
self.window.pushButton_Cancel.setEnabled(False)
@@ -391,8 +393,9 @@ class MainWindow(QtCore.QObject):
self.window.pushButton_removeComponent.setEnabled(True)
self.window.pushButton_listMoveDown.setEnabled(True)
self.window.pushButton_listMoveUp.setEnabled(True)
- self.window.pushButton_presets.setEnabled(True)
self.window.listWidget_componentList.setEnabled(True)
+ self.ui_newProject.setEnabled(True)
+ self.ui_openProject.setEnabled(True)
def progressBarUpdated(self, value):
self.window.progressBar_createVideo.setValue(value)
@@ -604,5 +607,10 @@ class MainWindow(QtCore.QObject):
except KeyError:
pass
+ menuItem = self.menu.addAction("Clear Preset")
+ menuItem.triggered.connect(
+ self.presetManager.clearPreset
+ )
+
self.menu.move(parentPosition + QPos)
self.menu.show()
diff --git a/presetmanager.py b/presetmanager.py
index 2083483..49a6336 100644
--- a/presetmanager.py
+++ b/presetmanager.py
@@ -12,6 +12,11 @@ class PresetManager(QtGui.QDialog):
self.core = parent.core
self.settings = parent.settings
self.presetDir = self.core.presetDir
+ if not self.settings.value('presetDir'):
+ self.settings.setValue(
+ "presetDir",
+ os.path.join(self.core.dataDir, 'projects'))
+
self.findPresets()
# window
@@ -103,10 +108,16 @@ class PresetManager(QtGui.QDialog):
for component in self.presets:
self.window.comboBox_filter.addItem(component)
+ def clearPreset(self, compI=None):
+ '''Functions on mainwindow level from the context menu'''
+ compI = self.parent.window.listWidget_componentList.currentRow()
+ self.core.clearPreset(compI, self)
+
+
def openSavePresetDialog(self):
'''Functions on mainwindow level from the context menu'''
window = self.parent.window
- selectedComponents = self.parent.core.selectedComponents
+ selectedComponents = self.core.selectedComponents
componentList = self.parent.window.listWidget_componentList
if componentList.currentRow() == -1:
@@ -242,9 +253,10 @@ class PresetManager(QtGui.QDialog):
def openImportDialog(self):
filename = QtGui.QFileDialog.getOpenFileName(
self.window, "Import Preset File",
- self.settings.value("projectDir"),
+ self.settings.value("presetDir"),
"Preset Files (*.avl)")
if filename:
+ # get installed path & ask user to overwrite if needed
path = ''
while True:
if path:
@@ -256,15 +268,17 @@ class PresetManager(QtGui.QDialog):
success, path = self.core.importPreset(filename)
if success:
break
+
self.findPresets()
self.drawPresetList()
+ self.settings.setValue("presetDir", os.path.dirname(filename))
def openExportDialog(self):
if not self.window.listWidget_presets.selectedItems():
return
filename = QtGui.QFileDialog.getSaveFileName(
self.window, "Export Preset",
- self.settings.value("projectDir"),
+ self.settings.value("presetDir"),
"Preset Files (*.avl)")
if filename:
index = self.window.listWidget_presets.currentRow()
@@ -274,3 +288,4 @@ class PresetManager(QtGui.QDialog):
msg='Couldn\'t export %s.' % filename,
parent=self.window
)
+ self.settings.setValue("presetDir", os.path.dirname(filename))
--
cgit v1.2.3
From 8603fa12e3d63705e3cbc202bd0635e5ac977225 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 15 Jun 2017 15:09:45 -0400
Subject: video scaling, position and distortion
---
components/video.py | 80 ++++++++++++++++++++++++++++++++++++++++++++++-------
components/video.ui | 7 +++++
mainwindow.py | 3 +-
3 files changed, 79 insertions(+), 11 deletions(-)
diff --git a/components/video.py b/components/video.py
index 841f77b..5f55211 100644
--- a/components/video.py
+++ b/components/video.py
@@ -11,13 +11,15 @@ class Video:
'''Video Component Frame-Fetcher'''
def __init__(self, **kwargs):
mandatoryArgs = [
- 'ffmpeg', # path to ffmpeg, usually core.FFMPEG_BIN
+ 'ffmpeg', # path to ffmpeg, usually core.FFMPEG_BIN
'videoPath',
'width',
'height',
+ 'scale', # percentage scale
'frameRate', # frames per second
'chunkSize', # number of bytes in one frame
- 'parent'
+ 'parent', # mainwindow object
+ 'component', # component object
]
for arg in mandatoryArgs:
try:
@@ -39,7 +41,8 @@ class Video:
'-i', self.videoPath,
'-f', 'image2pipe',
'-pix_fmt', 'rgba',
- '-filter:v', 'scale='+str(self.width)+':'+str(self.height),
+ '-filter:v', 'scale=%s:%s' %
+ scale(self.scale, self.width, self.height, str),
'-vcodec', 'rawvideo', '-',
]
@@ -58,7 +61,9 @@ class Video:
while True:
if num in self.finishedFrames:
image = self.finishedFrames.pop(num)
- return Image.frombytes('RGBA', (self.width, self.height), image)
+ return finalizeFrame(
+ self.component, image, self.width, self.height)
+
i, image = self.frameBuffer.get()
self.finishedFrames[i] = image
self.frameBuffer.task_done()
@@ -104,6 +109,10 @@ class Component(__base__.Component):
page.lineEdit_video.textChanged.connect(self.update)
page.pushButton_video.clicked.connect(self.pickVideo)
page.checkBox_loop.stateChanged.connect(self.update)
+ page.checkBox_distort.stateChanged.connect(self.update)
+ page.spinBox_scale.valueChanged.connect(self.update)
+ page.spinBox_x.valueChanged.connect(self.update)
+ page.spinBox_y.valueChanged.connect(self.update)
self.page = page
return page
@@ -111,13 +120,17 @@ class Component(__base__.Component):
def update(self):
self.videoPath = self.page.lineEdit_video.text()
self.loopVideo = self.page.checkBox_loop.isChecked()
+ self.distort = self.page.checkBox_distort.isChecked()
+ self.scale = self.page.spinBox_scale.value()
+ self.xPosition = self.page.spinBox_x.value()
+ self.yPosition = self.page.spinBox_y.value()
self.parent.drawPreview()
super().update()
def previewRender(self, previewWorker):
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
- self.chunkSize = 4*width*height
+ self.updateChunksize(width, height)
frame = self.getPreviewFrame(width, height)
if not frame:
return Image.new("RGBA", (width, height), (0, 0, 0, 0))
@@ -128,12 +141,13 @@ class Component(__base__.Component):
super().preFrameRender(**kwargs)
width = int(self.worker.core.settings.value('outputWidth'))
height = int(self.worker.core.settings.value('outputHeight'))
- self.chunkSize = 4*width*height
+ self.updateChunksize(width, height)
self.video = Video(
ffmpeg=self.parent.core.FFMPEG_BIN, videoPath=self.videoPath,
width=width, height=height, chunkSize=self.chunkSize,
frameRate=int(self.settings.value("outputFrameRate")),
- parent=self.parent, loopVideo=self.loopVideo
+ parent=self.parent, loopVideo=self.loopVideo,
+ component=self, scale=self.scale
)
def frameRender(self, moduleNo, arrayNo, frameNo):
@@ -143,12 +157,20 @@ class Component(__base__.Component):
super().loadPreset(pr, presetName)
self.page.lineEdit_video.setText(pr['video'])
self.page.checkBox_loop.setChecked(pr['loop'])
+ self.page.checkBox_distort.setChecked(pr['distort'])
+ self.page.spinBox_scale.setValue(pr['scale'])
+ self.page.spinBox_x.setValue(pr['x'])
+ self.page.spinBox_y.setValue(pr['y'])
def savePreset(self):
return {
'preset': self.currentPreset,
'video': self.videoPath,
'loop': self.loopVideo,
+ 'distort': self.distort,
+ 'scale': self.scale,
+ 'x': self.xPosition,
+ 'y': self.yPosition,
}
def pickVideo(self):
@@ -165,13 +187,15 @@ class Component(__base__.Component):
def getPreviewFrame(self, width, height):
if not self.videoPath or not os.path.exists(self.videoPath):
return
+
command = [
self.parent.core.FFMPEG_BIN,
'-thread_queue_size', '512',
'-i', self.videoPath,
'-f', 'image2pipe',
'-pix_fmt', 'rgba',
- '-filter:v', 'scale='+str(width)+':'+str(height),
+ '-filter:v', 'scale=%s:%s' %
+ scale(self.scale, width, height, str),
'-vcodec', 'rawvideo', '-',
'-ss', '90',
'-vframes', '1',
@@ -181,7 +205,43 @@ class Component(__base__.Component):
stderr=subprocess.DEVNULL, bufsize=10**8
)
byteFrame = pipe.stdout.read(self.chunkSize)
- image = Image.frombytes('RGBA', (width, height), byteFrame)
+ frame = finalizeFrame(self, byteFrame, width, height)
pipe.stdout.close()
pipe.kill()
- return image
+
+ return frame
+
+ def updateChunksize(self, width, height):
+ if self.scale != 100 and not self.distort:
+ width, height = scale(self.scale, width, height, int)
+ self.chunkSize = 4*width*height
+
+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(int(width)), str(int(height)))
+ elif returntype == int:
+ return (int(width), int(height))
+ else:
+ return (width, height)
+
+def finalizeFrame(self, imageData, width, height):
+ if self.distort:
+ image = Image.frombytes(
+ 'RGBA',
+ (width, height),
+ imageData)
+ else:
+ image = Image.frombytes(
+ 'RGBA',
+ scale(self.scale, width, height, int),
+ imageData)
+
+ if self.scale != 100 \
+ or self.xPosition != 0 or self.yPosition != 0:
+ frame = Image.new("RGBA", (width, height), (0, 0, 0, 0))
+ frame.paste(image, box=(self.xPosition, self.yPosition))
+ else:
+ frame = image
+ return frame
diff --git a/components/video.ui b/components/video.ui
index aca46b4..fa088fa 100644
--- a/components/video.ui
+++ b/components/video.ui
@@ -205,6 +205,13 @@
+ -
+
+
+ Distort by scale
+
+
+
-
diff --git a/mainwindow.py b/mainwindow.py
index 5004bf6..ad1df10 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -321,7 +321,8 @@ class MainWindow(QtCore.QObject):
fileName = QtGui.QFileDialog.getSaveFileName(
self.window, "Set Output Video File",
- outputDir, "Video Files (*.mp4 *.mov *.mkv *.avi *.webm *.flv)")
+ outputDir,
+ "Video Files (*.mp4 *.mov *.mkv *.avi *.webm *.flv);; All Files (*)")
if not fileName == "":
self.settings.setValue("outputDir", os.path.dirname(fileName))
--
cgit v1.2.3
From c05efc73ee069fe2eb8776a27b503ada2adb4af6 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 15 Jun 2017 22:15:03 -0400
Subject: various bugfixes, blankFrame method for components
don't crash from broken project files or nonexistent videopaths, and shareable common paths in core.py
---
components/__base__.py | 4 ++
components/color.py | 2 +-
components/image.py | 6 ++-
components/image.ui | 2 +-
components/original.py | 4 +-
components/text.py | 2 +-
components/video.py | 27 ++++++++-----
components/video.ui | 2 +-
core.py | 106 +++++++++++++++++++++++++++++++++----------------
mainwindow.py | 4 +-
preview_thread.py | 5 +--
11 files changed, 107 insertions(+), 57 deletions(-)
diff --git a/components/__base__.py b/components/__base__.py
index bc6644b..88f22d4 100644
--- a/components/__base__.py
+++ b/components/__base__.py
@@ -1,4 +1,5 @@
from PyQt4 import QtGui, QtCore
+from PIL import Image
class Component(QtCore.QObject):
@@ -45,6 +46,9 @@ class Component(QtCore.QObject):
for var, value in kwargs.items():
exec('self.%s = value' % var)
+ def blankFrame(self, width, height):
+ return Image.new("RGBA", (width, height), (0, 0, 0, 0))
+
def pickColor(self):
dialog = QtGui.QColorDialog()
dialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True)
diff --git a/components/color.py b/components/color.py
index 5912bfa..36f3906 100644
--- a/components/color.py
+++ b/components/color.py
@@ -71,7 +71,7 @@ class Component(__base__.Component):
def drawFrame(self, width, height):
r, g, b = self.color1
- return Image.new("RGBA", (width, height), (r, g, b, 255))
+ return self.blankFrame(width, height)
def loadPreset(self, pr, presetName=None):
super().loadPreset(pr, presetName)
diff --git a/components/image.py b/components/image.py
index a2f0521..b6aa29b 100644
--- a/components/image.py
+++ b/components/image.py
@@ -38,6 +38,7 @@ class Component(__base__.Component):
super().update()
def previewRender(self, previewWorker):
+ self.imageFormats = previewWorker.core.imageFormats
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
return self.drawFrame(width, height)
@@ -52,7 +53,7 @@ class Component(__base__.Component):
return self.drawFrame(width, height)
def drawFrame(self, width, height):
- frame = Image.new("RGBA", (width, height), (0, 0, 0, 0))
+ frame = self.blankFrame(width, height)
if self.imagePath and os.path.exists(self.imagePath):
image = Image.open(self.imagePath)
if self.stretched and image.size != (width, height):
@@ -85,7 +86,8 @@ class Component(__base__.Component):
def pickImage(self):
imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
filename = QtGui.QFileDialog.getOpenFileName(
- self.page, "Choose Image", imgDir, "Image Files (*.jpg *.png)")
+ self.page, "Choose Image", imgDir,
+ "Image Files (%s)" % " ".join(self.imageFormats))
if filename:
self.settings.setValue("backgroundDir", os.path.dirname(filename))
self.page.lineEdit_image.setText(filename)
diff --git a/components/image.ui b/components/image.ui
index 685e997..6df03a5 100644
--- a/components/image.ui
+++ b/components/image.ui
@@ -230,7 +230,7 @@
10
- 200
+ 400
100
diff --git a/components/original.py b/components/original.py
index 9df2815..5e2f9d4 100644
--- a/components/original.py
+++ b/components/original.py
@@ -145,7 +145,7 @@ class Component(__base__.Component):
bF = width / 64
bH = bF / 2
bQ = bF / 4
- imTop = Image.new("RGBA", (width, height), (0, 0, 0, 0))
+ imTop = self.blankFrame(width, height)
draw = ImageDraw.Draw(imTop)
r, g, b = color
color2 = (r, g, b, 125)
@@ -163,7 +163,7 @@ class Component(__base__.Component):
imBottom = imTop.transpose(Image.FLIP_TOP_BOTTOM)
- im = Image.new("RGBA", (width, height), (0, 0, 0, 0))
+ im = self.blankFrame(width, height)
if layout == 0:
y = 0 - int(height/100*43)
diff --git a/components/text.py b/components/text.py
index 165a093..f8ef7b3 100644
--- a/components/text.py
+++ b/components/text.py
@@ -126,7 +126,7 @@ class Component(__base__.Component):
def addText(self, width, height):
x, y = self.getXY()
- im = Image.new("RGBA", (width, height), (0, 0, 0, 0))
+ im = self.blankFrame(width, height)
image = ImageQt(im)
painter = QPainter(image)
diff --git a/components/video.py b/components/video.py
index 5f55211..3d43a18 100644
--- a/components/video.py
+++ b/components/video.py
@@ -128,12 +128,13 @@ class Component(__base__.Component):
super().update()
def previewRender(self, previewWorker):
+ self.videoFormats = previewWorker.core.videoFormats
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
self.updateChunksize(width, height)
frame = self.getPreviewFrame(width, height)
if not frame:
- return Image.new("RGBA", (width, height), (0, 0, 0, 0))
+ return self.blankFrame(width, height)
else:
return frame
@@ -141,6 +142,7 @@ class Component(__base__.Component):
super().preFrameRender(**kwargs)
width = int(self.worker.core.settings.value('outputWidth'))
height = int(self.worker.core.settings.value('outputHeight'))
+ self.blankFrame_ = self.blankFrame(width, height)
self.updateChunksize(width, height)
self.video = Video(
ffmpeg=self.parent.core.FFMPEG_BIN, videoPath=self.videoPath,
@@ -148,10 +150,13 @@ class Component(__base__.Component):
frameRate=int(self.settings.value("outputFrameRate")),
parent=self.parent, loopVideo=self.loopVideo,
component=self, scale=self.scale
- )
+ ) if os.path.exists(self.videoPath) else None
def frameRender(self, moduleNo, arrayNo, frameNo):
- return self.video.frame(frameNo)
+ if self.video:
+ return self.video.frame(frameNo)
+ else:
+ return self.blankFrame_
def loadPreset(self, pr, presetName=None):
super().loadPreset(pr, presetName)
@@ -177,7 +182,7 @@ class Component(__base__.Component):
imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
filename = QtGui.QFileDialog.getOpenFileName(
self.page, "Choose Video",
- imgDir, "Video Files (*.mp4 *.mov)"
+ imgDir, "Video Files (%s)" % " ".join(self.videoFormats)
)
if filename:
self.settings.setValue("backgroundDir", os.path.dirname(filename))
@@ -228,10 +233,14 @@ def scale(scale, width, height, returntype=None):
def finalizeFrame(self, imageData, width, height):
if self.distort:
- image = Image.frombytes(
- 'RGBA',
- (width, height),
- imageData)
+ try:
+ image = Image.frombytes(
+ 'RGBA',
+ (width, height),
+ imageData)
+ except ValueError:
+ print('#### ignored invalid data caused by distortion ####')
+ image = self.blankFrame(width, height)
else:
image = Image.frombytes(
'RGBA',
@@ -240,7 +249,7 @@ def finalizeFrame(self, imageData, width, height):
if self.scale != 100 \
or self.xPosition != 0 or self.yPosition != 0:
- frame = Image.new("RGBA", (width, height), (0, 0, 0, 0))
+ frame = self.blankFrame(width, height)
frame.paste(image, box=(self.xPosition, self.yPosition))
else:
frame = image
diff --git a/components/video.ui b/components/video.ui
index fa088fa..f05e8a5 100644
--- a/components/video.ui
+++ b/components/video.ui
@@ -234,7 +234,7 @@
10
- 200
+ 400
100
diff --git a/core.py b/core.py
index 8eb7d16..3fca7bf 100644
--- a/core.py
+++ b/core.py
@@ -24,6 +24,32 @@ class Core():
self.presetDir = os.path.join(self.dataDir, 'presets')
self.wd = os.path.dirname(os.path.realpath(__file__))
self.loadEncoderOptions()
+ self.videoFormats = Core.appendUppercase([
+ '*.mp4',
+ '*.mov',
+ '*.mkv',
+ '*.avi',
+ '*.webm',
+ '*.flv',
+ ])
+ self.audioFormats = Core.appendUppercase([
+ '*.mp3',
+ '*.wav',
+ '*.ogg',
+ '*.fla',
+ '*.aac',
+ ])
+ self.imageFormats = Core.appendUppercase([
+ '*.png',
+ '*.jpg',
+ '*.tif',
+ '*.tiff',
+ '*.gif',
+ '*.bmp',
+ '*.ico',
+ '*.xbm',
+ '*.xpm',
+ ])
self.findComponents()
self.selectedComponents = []
@@ -116,44 +142,48 @@ class Core():
which implements an insertComponent method'''
errcode, data = self.parseAvFile(filepath)
if errcode == 0:
- for i, tup in enumerate(data['Components']):
- name, vers, preset = tup
- clearThis = False
-
- # add loaded named presets to savedPresets dict
- if 'preset' in preset and preset['preset'] != None:
- nam = preset['preset']
- filepath2 = os.path.join(
- self.presetDir, name, str(vers), nam)
- origSaveValueStore = self.getPreset(filepath2)
- if origSaveValueStore:
- self.savedPresets[nam] = dict(origSaveValueStore)
- else:
- # saved preset was renamed or deleted
- clearThis = True
-
- # insert component into the loader
- loader.insertComponent(
- self.moduleIndexFor(name), -1)
- try:
+ try:
+ for i, tup in enumerate(data['Components']):
+ name, vers, preset = tup
+ clearThis = False
+
+ # add loaded named presets to savedPresets dict
if 'preset' in preset and preset['preset'] != None:
- self.selectedComponents[-1].loadPreset(
- preset
- )
- else:
- self.selectedComponents[-1].loadPreset(
- preset,
- preset['preset']
- )
- except KeyError as e:
- print('%s missing value %s' %
- (self.selectedComponents[-1], e))
-
- if clearThis:
- self.clearPreset(-1, loader)
+ nam = preset['preset']
+ filepath2 = os.path.join(
+ self.presetDir, name, str(vers), nam)
+ origSaveValueStore = self.getPreset(filepath2)
+ if origSaveValueStore:
+ self.savedPresets[nam] = dict(origSaveValueStore)
+ else:
+ # saved preset was renamed or deleted
+ clearThis = True
+
+ # insert component into the loader
+ loader.insertComponent(
+ self.moduleIndexFor(name), -1)
+ try:
+ if 'preset' in preset and preset['preset'] != None:
+ self.selectedComponents[-1].loadPreset(
+ preset
+ )
+ else:
+ self.selectedComponents[-1].loadPreset(
+ preset,
+ preset['preset']
+ )
+ except KeyError as e:
+ print('%s missing value %s' %
+ (self.selectedComponents[-1], e))
+ if clearThis:
+ self.clearPreset(-1, loader)
+ except:
+ errcode = 1
+ data = sys.exc_info()
- elif errcode == 1:
+
+ if errcode == 1:
typ, value, _ = data
if typ.__name__ == KeyError:
# probably just an old version, still loadable
@@ -398,3 +428,9 @@ class Core():
def presetFromString(string):
'''Turns a string repr of OrderedDict into a regular dict'''
return dict(eval(string))
+
+ @staticmethod
+ def appendUppercase(lst):
+ for form, i in zip(lst, range(len(lst))):
+ lst.append(form.upper())
+ return lst
diff --git a/mainwindow.py b/mainwindow.py
index ad1df10..f1959cb 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -310,7 +310,7 @@ class MainWindow(QtCore.QObject):
fileName = QtGui.QFileDialog.getOpenFileName(
self.window, "Open Music File",
- inputDir, "Music Files (*.mp3 *.wav *.ogg *.fla *.aac)")
+ inputDir, "Music Files (%s)" % " ".join(self.core.audioFormats))
if not fileName == "":
self.settings.setValue("inputDir", os.path.dirname(fileName))
@@ -322,7 +322,7 @@ class MainWindow(QtCore.QObject):
fileName = QtGui.QFileDialog.getSaveFileName(
self.window, "Set Output Video File",
outputDir,
- "Video Files (*.mp4 *.mov *.mkv *.avi *.webm *.flv);; All Files (*)")
+ "Video Files (%s);; All Files (*)" % " ".join(self.core.videoFormats))
if not fileName == "":
self.settings.setValue("outputDir", os.path.dirname(fileName))
diff --git a/preview_thread.py b/preview_thread.py
index d54dba5..e3e8279 100644
--- a/preview_thread.py
+++ b/preview_thread.py
@@ -1,11 +1,9 @@
from PyQt4 import QtCore, QtGui, uic
from PyQt4.QtCore import pyqtSignal, pyqtSlot
-from PIL import Image, ImageDraw, ImageFont
+from PIL import Image
from PIL.ImageQt import ImageQt
import core
-import time
from queue import Queue, Empty
-import numpy
import os
from copy import copy
@@ -18,6 +16,7 @@ class Worker(QtCore.QObject):
QtCore.QObject.__init__(self)
parent.newTask.connect(self.createPreviewImage)
parent.processTask.connect(self.process)
+ self.parent = parent
self.core = core.Core()
self.queue = queue
self.core.settings = parent.settings
--
cgit v1.2.3
From ee8031925fcd93d7bedceff6e98a06f3806426b3 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 15 Jun 2017 23:13:36 -0400
Subject: drag events for component list now working!
---
core.py | 14 +++++++++-----
mainwindow.py | 34 ++++++++++++++++++++++++++--------
mainwindow.ui | 9 ++++++---
presetmanager.py | 3 +--
4 files changed, 42 insertions(+), 18 deletions(-)
diff --git a/core.py b/core.py
index 3fca7bf..dcea783 100644
--- a/core.py
+++ b/core.py
@@ -68,7 +68,8 @@ class Core():
yield name
self.modules = [
import_module('components.%s' % name)
- for name in findComponents()]
+ for name in findComponents()
+ ]
self.moduleIndexes = [i for i in range(len(self.modules))]
def componentListChanged(self):
@@ -119,11 +120,14 @@ class Core():
saveValueStore = self.getPreset(filepath)
if not saveValueStore:
return False
+ try:
+ self.selectedComponents[compIndex].loadPreset(
+ saveValueStore,
+ presetName
+ )
+ except KeyError as e:
+ print('preset missing value: %s' % e)
- self.selectedComponents[compIndex].loadPreset(
- saveValueStore,
- presetName
- )
self.savedPresets[presetName] = dict(saveValueStore)
return True
diff --git a/mainwindow.py b/mainwindow.py
index f1959cb..fb9ebfd 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -157,7 +157,7 @@ class MainWindow(QtCore.QObject):
self.window.pushButton_addComponent.setMenu(self.compMenu)
- componentList.dropEvent = self.componentListChanged
+ componentList.dropEvent = self.dragComponent
componentList.itemSelectionChanged.connect(
self.changeComponentWidget)
@@ -479,9 +479,26 @@ class MainWindow(QtCore.QObject):
stackedWidget.setCurrentIndex(newRow)
self.drawPreview()
- def componentListChanged(self, *args):
- '''Update all our tracking variables to match the widget'''
- pass
+ def dragComponent(self, event):
+ '''Drop event for the component listwidget'''
+ componentList = self.window.listWidget_componentList
+
+ modelIndexes = [ \
+ componentList.model().index(i) \
+ for i in range(componentList.count()) \
+ ]
+ rects = [ \
+ componentList.visualRect(modelIndex) \
+ for modelIndex in modelIndexes \
+ ]
+
+ rowPos = [rect.contains(event.pos()) for rect in rects]
+ if not any(rowPos):
+ return
+
+ i = rowPos.index(True)
+ change = (componentList.currentRow() - i) * -1
+ self.moveComponent(change)
def changeComponentWidget(self):
selected = self.window.listWidget_componentList.selectedItems()
@@ -608,10 +625,11 @@ class MainWindow(QtCore.QObject):
except KeyError:
pass
- menuItem = self.menu.addAction("Clear Preset")
- menuItem.triggered.connect(
- self.presetManager.clearPreset
- )
+ if self.core.selectedComponents[index].currentPreset:
+ menuItem = self.menu.addAction("Clear Preset")
+ menuItem.triggered.connect(
+ self.presetManager.clearPreset
+ )
self.menu.move(parentPosition + QPos)
self.menu.show()
diff --git a/mainwindow.ui b/mainwindow.ui
index af47cee..e892959 100644
--- a/mainwindow.ui
+++ b/mainwindow.ui
@@ -6,8 +6,8 @@
0
0
- 1008
- 575
+ 1028
+ 592
@@ -175,6 +175,9 @@
16777215
+
+ true
+
QFrame::StyledPanel
@@ -188,7 +191,7 @@
true
- false
+ true
false
diff --git a/presetmanager.py b/presetmanager.py
index 49a6336..3b02714 100644
--- a/presetmanager.py
+++ b/presetmanager.py
@@ -111,8 +111,7 @@ class PresetManager(QtGui.QDialog):
def clearPreset(self, compI=None):
'''Functions on mainwindow level from the context menu'''
compI = self.parent.window.listWidget_componentList.currentRow()
- self.core.clearPreset(compI, self)
-
+ self.core.clearPreset(compI, self.parent)
def openSavePresetDialog(self):
'''Functions on mainwindow level from the context menu'''
--
cgit v1.2.3
From 4b1058781d39ea3e43197c559dd3631bae42b66b Mon Sep 17 00:00:00 2001
From: tassaron
Date: Fri, 16 Jun 2017 00:01:34 -0400
Subject: added width and height to color.ui
---
components/color.py | 15 ++++--
components/color.ui | 140 +++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 151 insertions(+), 4 deletions(-)
diff --git a/components/color.py b/components/color.py
index 36f3906..fda7376 100644
--- a/components/color.py
+++ b/components/color.py
@@ -37,8 +37,12 @@ class Component(__base__.Component):
# disable color #2 until non-default 'fill' option gets changed
page.lineEdit_color2.setDisabled(True)
page.pushButton_color2.setDisabled(True)
- page.spinBox_x.setValue(self.x)
- page.spinBox_x.setValue(self.y)
+ page.spinBox_x.valueChanged.connect(self.update)
+ page.spinBox_y.valueChanged.connect(self.update)
+ page.spinBox_width.setValue(
+ int(parent.settings.value("outputWidth")))
+ page.spinBox_height.setValue(
+ int(parent.settings.value("outputHeight")))
page.lineEdit_color1.textChanged.connect(self.update)
page.lineEdit_color2.textChanged.connect(self.update)
@@ -71,13 +75,16 @@ class Component(__base__.Component):
def drawFrame(self, width, height):
r, g, b = self.color1
- return self.blankFrame(width, height)
+ return Image.new("RGBA", (width, height), (r, g, b, 255))
def loadPreset(self, pr, presetName=None):
super().loadPreset(pr, presetName)
self.page.lineEdit_color1.setText('%s,%s,%s' % pr['color1'])
self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2'])
+ self.page.spinBox_x.setValue(pr['x'])
+ self.page.spinBox_y.setValue(pr['y'])
+ self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2'])
btnStyle1 = "QPushButton { background-color : %s; outline: none; }" \
% QColor(*pr['color1']).name()
@@ -93,6 +100,8 @@ class Component(__base__.Component):
'preset': self.currentPreset,
'color1': self.color1,
'color2': self.color2,
+ 'x': self.x,
+ 'y': self.y,
}
def pickColor(self, num):
diff --git a/components/color.ui b/components/color.ui
index fd427e6..9ba0649 100644
--- a/components/color.ui
+++ b/components/color.ui
@@ -172,7 +172,14 @@
-
-
+
+
+
+ 0
+ 0
+
+
+
-
@@ -286,6 +293,137 @@
+ -
+
+
+ 0
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Width
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 0
+ 0
+
+
+
+ 0
+
+
+ 999999999
+
+
+ 0
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Height
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+ 999999999
+
+
+
+
+
-
--
cgit v1.2.3
From 4de39ebe0782fbd68b5fcc31e0466e76e145e712 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Fri, 16 Jun 2017 20:01:27 -0400
Subject: color component size, position, and gradient options
---
components/color.py | 117 +++++++++++-
components/color.ui | 521 ++++++++++++++++++++++++++++++++++++----------------
mainwindow.py | 4 +-
mainwindow.ui | 4 +-
4 files changed, 477 insertions(+), 169 deletions(-)
diff --git a/components/color.py b/components/color.py
index fda7376..bb08b53 100644
--- a/components/color.py
+++ b/components/color.py
@@ -1,6 +1,7 @@
from PIL import Image, ImageDraw
from PyQt4 import uic, QtGui, QtCore
from PyQt4.QtGui import QColor
+from PIL.ImageQt import ImageQt
import os
from . import __base__
@@ -48,6 +49,26 @@ class Component(__base__.Component):
page.lineEdit_color2.textChanged.connect(self.update)
page.spinBox_x.valueChanged.connect(self.update)
page.spinBox_y.valueChanged.connect(self.update)
+ page.spinBox_width.valueChanged.connect(self.update)
+ page.spinBox_height.valueChanged.connect(self.update)
+ page.checkBox_trans.stateChanged.connect(self.update)
+
+ self.fillLabels = [ \
+ 'Solid',
+ 'Linear Gradient',
+ 'Radial Gradient',
+ ]
+ for label in self.fillLabels:
+ page.comboBox_fill.addItem(label)
+ page.comboBox_fill.setCurrentIndex(0)
+ page.comboBox_fill.currentIndexChanged.connect(self.update)
+ page.spinBox_radialGradient_end.valueChanged.connect(self.update)
+ page.spinBox_radialGradient_start.valueChanged.connect(self.update)
+ page.spinBox_radialGradient_spread.valueChanged.connect(self.update)
+ page.spinBox_linearGradient_end.valueChanged.connect(self.update)
+ page.spinBox_linearGradient_start.valueChanged.connect(self.update)
+ page.checkBox_stretch.stateChanged.connect(self.update)
+
self.page = page
return page
@@ -56,6 +77,30 @@ class Component(__base__.Component):
self.color2 = self.RGBFromString(self.page.lineEdit_color2.text())
self.x = self.page.spinBox_x.value()
self.y = self.page.spinBox_y.value()
+ self.sizeWidth = self.page.spinBox_width.value()
+ self.sizeHeight = self.page.spinBox_height.value()
+ self.trans = self.page.checkBox_trans.isChecked()
+
+ self.RG_start = self.page.spinBox_radialGradient_start.value()
+ self.RG_end = self.page.spinBox_radialGradient_end.value()
+ self.RG_spread = self.page.spinBox_radialGradient_spread.value()
+ self.stretch = self.page.checkBox_stretch.isChecked()
+ self.LG_start = self.page.spinBox_linearGradient_start.value()
+ self.LG_end = self.page.spinBox_linearGradient_end.value()
+
+ self.fillType = self.page.comboBox_fill.currentIndex()
+ if self.fillType == 0:
+ self.page.lineEdit_color2.setEnabled(False)
+ self.page.pushButton_color2.setEnabled(False)
+ self.page.checkBox_trans.setEnabled(False)
+ self.page.checkBox_stretch.setEnabled(False)
+ else:
+ self.page.lineEdit_color2.setEnabled(True)
+ self.page.pushButton_color2.setEnabled(True)
+ self.page.checkBox_trans.setEnabled(True)
+ self.page.checkBox_stretch.setEnabled(True)
+ self.page.fillWidget.setCurrentIndex(self.fillType)
+
self.parent.drawPreview()
super().update()
@@ -75,23 +120,77 @@ class Component(__base__.Component):
def drawFrame(self, width, height):
r, g, b = self.color1
- return Image.new("RGBA", (width, height), (r, g, b, 255))
+ shapeSize = (self.sizeWidth, self.sizeHeight)
+ # in default state, skip all this logic and return a plain fill
+ if self.fillType==0 and shapeSize == (width, height) \
+ and self.x == 0 and self.y == 0:
+ return Image.new("RGBA", (width, height), (r, g, b, 255))
+
+ frame = self.blankFrame(width, height)
+
+ # Return a solid image at x, y
+ if self.fillType == 0:
+ image = Image.new("RGBA", shapeSize, (r, g, b, 255))
+ frame.paste(image, box=(self.x, self.y))
+ return frame
+
+ # Now fills that require using Qt...
+ elif self.fillType > 0:
+ image = ImageQt(frame)
+ painter = QtGui.QPainter(image)
+ if self.stretch:
+ w = width; h = height
+ else:
+ w = self.sizeWidth; h = self.sizeWidth
+
+ if self.fillType == 1: # Linear Gradient
+ brush = QtGui.QLinearGradient(
+ self.LG_start,
+ self.LG_end,
+ w, h)
+
+ elif self.fillType == 2: # Radial Gradient
+ brush = QtGui.QRadialGradient(
+ self.RG_start,
+ self.RG_end,
+ w, h,
+ self.RG_spread)
+
+ brush.setColorAt(0.0, QColor(*self.color1))
+ if self.trans:
+ brush.setColorAt(1.0, QColor(0, 0, 0, 0))
+ else:
+ brush.setColorAt(1.0, QColor(*self.color2))
+ painter.setBrush(brush)
+ painter.drawRect(self.x, self.y,
+ self.sizeWidth, self.sizeHeight)
+ painter.end()
+ imBytes = image.bits().asstring(image.numBytes())
+ return Image.frombytes('RGBA', (width, height), imBytes)
def loadPreset(self, pr, presetName=None):
super().loadPreset(pr, presetName)
+ self.page.comboBox_fill.setCurrentIndex(pr['fillType'])
self.page.lineEdit_color1.setText('%s,%s,%s' % pr['color1'])
self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2'])
self.page.spinBox_x.setValue(pr['x'])
self.page.spinBox_y.setValue(pr['y'])
- self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2'])
+ self.page.spinBox_width.setValue(pr['width'])
+ self.page.spinBox_height.setValue(pr['height'])
+ self.page.checkBox_trans.setChecked(pr['trans'])
+
+ self.page.spinBox_radialGradient_start.setValue(pr['RG_start'])
+ self.page.spinBox_radialGradient_end.setValue(pr['RG_end'])
+ self.page.spinBox_radialGradient_spread.setValue(pr['RG_spread'])
+ self.page.spinBox_linearGradient_start.setValue(pr['LG_start'])
+ self.page.spinBox_linearGradient_end.setValue(pr['LG_end'])
+ self.page.checkBox_stretch.setChecked(pr['stretch'])
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)
@@ -102,6 +201,16 @@ class Component(__base__.Component):
'color2': self.color2,
'x': self.x,
'y': self.y,
+ 'fillType': self.fillType,
+ 'width': self.sizeWidth,
+ 'height': self.sizeHeight,
+ 'trans': self.trans,
+ 'RG_start': self.RG_start,
+ 'RG_end': self.RG_end,
+ 'RG_spread': self.RG_spread,
+ 'stretch': self.stretch,
+ 'LG_start': self.LG_start,
+ 'LG_end': self.LG_end,
}
def pickColor(self, num):
diff --git a/components/color.ui b/components/color.ui
index 9ba0649..89375d3 100644
--- a/components/color.ui
+++ b/components/color.ui
@@ -159,7 +159,7 @@
0
-
-
+
0
@@ -167,22 +167,78 @@
- Fill
+ Width
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 0
+ 0
+
+
+
+ 0
+
+
+ 999999999
+
+
+ 0
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Height
+
+
+
+ -
+
-
+
0
0
+
+
+ 80
+ 16777215
+
+
+
+ 999999999
+
-
-
+
Qt::Horizontal
@@ -231,32 +287,16 @@
- 0
+ -10000
- 999999999
+ 10000
0
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
-
@@ -284,159 +324,316 @@
16777215
+
+ -10000
+
- 999999999
+ 10000
-
-
- -
-
-
- 0
-
-
-
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Width
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
-
- 0
- 0
-
-
-
- 0
-
-
- 999999999
-
-
+
+
0
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Height
-
-
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Fill
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ -1
+
+
+ QComboBox::AdjustToContentsOnFirstShow
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Transparent
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Stretch
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Minimum
+
+
+
+ 40
+ 20
+
+
+
+
+
-
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
- 999999999
-
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ 0
+
+
+ 2
+
+
+
+
+
+
+ -1
+ 0
+ 561
+ 31
+
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Start
+
+
+
+ -
+
+
+ -10000
+
+
+ 10000
+
+
+ 10
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ End
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ -10000
+
+
+ 10000
+
+
+ 10
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
+
+
+ -1
+ -1
+ 561
+ 31
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Start
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ -10000
+
+
+ 10000
+
+
+ 10
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ End
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ -10000
+
+
+ 10000
+
+
+ 10
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Spread
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ QAbstractSpinBox::PlusMinus
+
+
+ 1
+
+
+ 20
+
+
+ 3
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
+
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
diff --git a/mainwindow.py b/mainwindow.py
index fb9ebfd..0d6b130 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -301,7 +301,9 @@ class MainWindow(QtCore.QObject):
def autosaveExists(self):
if self.currentProject and os.path.exists(self.autosavePath) \
and filecmp.cmp(self.autosavePath, self.currentProject):
- return True
+ with open(self.autosavePath, 'r') as f:
+ p = [line for line in f]
+ return True if len(p) > 1 else False
else:
return False
diff --git a/mainwindow.ui b/mainwindow.ui
index e892959..4a12fd5 100644
--- a/mainwindow.ui
+++ b/mainwindow.ui
@@ -6,8 +6,8 @@
0
0
- 1028
- 592
+ 1008
+ 575
--
cgit v1.2.3
From aa9926590b6fce4099eb75c0c84b4fd886dce0d9 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Fri, 16 Jun 2017 20:43:40 -0400
Subject: spread option for gradients
---
components/color.py | 22 +++++++++++++++-------
components/color.ui | 25 ++++++++++++++++++++++---
2 files changed, 37 insertions(+), 10 deletions(-)
diff --git a/components/color.py b/components/color.py
index bb08b53..22b57c0 100644
--- a/components/color.py
+++ b/components/color.py
@@ -62,6 +62,7 @@ class Component(__base__.Component):
page.comboBox_fill.addItem(label)
page.comboBox_fill.setCurrentIndex(0)
page.comboBox_fill.currentIndexChanged.connect(self.update)
+ page.comboBox_spread.currentIndexChanged.connect(self.update)
page.spinBox_radialGradient_end.valueChanged.connect(self.update)
page.spinBox_radialGradient_start.valueChanged.connect(self.update)
page.spinBox_radialGradient_spread.valueChanged.connect(self.update)
@@ -80,10 +81,11 @@ class Component(__base__.Component):
self.sizeWidth = self.page.spinBox_width.value()
self.sizeHeight = self.page.spinBox_height.value()
self.trans = self.page.checkBox_trans.isChecked()
+ self.spread = self.page.comboBox_spread.currentIndex()
self.RG_start = self.page.spinBox_radialGradient_start.value()
self.RG_end = self.page.spinBox_radialGradient_end.value()
- self.RG_spread = self.page.spinBox_radialGradient_spread.value()
+ self.RG_centre = self.page.spinBox_radialGradient_spread.value()
self.stretch = self.page.checkBox_stretch.isChecked()
self.LG_start = self.page.spinBox_linearGradient_start.value()
self.LG_end = self.page.spinBox_linearGradient_end.value()
@@ -146,19 +148,23 @@ class Component(__base__.Component):
if self.fillType == 1: # Linear Gradient
brush = QtGui.QLinearGradient(
self.LG_start,
- self.LG_end,
- w, h)
+ self.LG_start,
+ self.LG_start+width/3,
+ self.LG_end)
elif self.fillType == 2: # Radial Gradient
brush = QtGui.QRadialGradient(
self.RG_start,
self.RG_end,
w, h,
- self.RG_spread)
+ self.RG_centre)
+ brush.setSpread(self.spread)
brush.setColorAt(0.0, QColor(*self.color1))
if self.trans:
brush.setColorAt(1.0, QColor(0, 0, 0, 0))
+ elif self.fillType == 1 and self.stretch:
+ brush.setColorAt(0.2, QColor(*self.color2))
else:
brush.setColorAt(1.0, QColor(*self.color2))
painter.setBrush(brush)
@@ -182,10 +188,11 @@ class Component(__base__.Component):
self.page.spinBox_radialGradient_start.setValue(pr['RG_start'])
self.page.spinBox_radialGradient_end.setValue(pr['RG_end'])
- self.page.spinBox_radialGradient_spread.setValue(pr['RG_spread'])
+ self.page.spinBox_radialGradient_spread.setValue(pr['RG_centre'])
self.page.spinBox_linearGradient_start.setValue(pr['LG_start'])
self.page.spinBox_linearGradient_end.setValue(pr['LG_end'])
self.page.checkBox_stretch.setChecked(pr['stretch'])
+ self.page.comboBox_spread.setCurrentIndex(pr['spread'])
btnStyle1 = "QPushButton { background-color : %s; outline: none; }" \
% QColor(*pr['color1']).name()
@@ -205,10 +212,11 @@ class Component(__base__.Component):
'width': self.sizeWidth,
'height': self.sizeHeight,
'trans': self.trans,
+ 'stretch': self.stretch,
+ 'spread': self.spread,
'RG_start': self.RG_start,
'RG_end': self.RG_end,
- 'RG_spread': self.RG_spread,
- 'stretch': self.stretch,
+ 'RG_centre': self.RG_centre,
'LG_start': self.LG_start,
'LG_end': self.LG_end,
}
diff --git a/components/color.ui b/components/color.ui
index 89375d3..a9dacea 100644
--- a/components/color.ui
+++ b/components/color.ui
@@ -394,6 +394,25 @@
+ -
+
+
-
+
+ Pad
+
+
+ -
+
+ Reflect
+
+
+ -
+
+ Repeat
+
+
+
+
-
@@ -589,7 +608,7 @@
- Spread
+ Centre
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
@@ -602,10 +621,10 @@
QAbstractSpinBox::PlusMinus
- 1
+ -10000
- 20
+ 10000
3
--
cgit v1.2.3
From ffc5966042e1490930396dd541e4c9497a6b7a73 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 17 Jun 2017 11:15:24 -0400
Subject: ask to save changes before changing current project
also limited total # of components to 50
---
components/color.py | 2 ++
core.py | 19 +++++++++---
mainwindow.py | 88 +++++++++++++++++++++++++++++++++++------------------
3 files changed, 75 insertions(+), 34 deletions(-)
diff --git a/components/color.py b/components/color.py
index 22b57c0..cb75839 100644
--- a/components/color.py
+++ b/components/color.py
@@ -96,11 +96,13 @@ class Component(__base__.Component):
self.page.pushButton_color2.setEnabled(False)
self.page.checkBox_trans.setEnabled(False)
self.page.checkBox_stretch.setEnabled(False)
+ self.page.comboBox_spread.setEnabled(False)
else:
self.page.lineEdit_color2.setEnabled(True)
self.page.pushButton_color2.setEnabled(True)
self.page.checkBox_trans.setEnabled(True)
self.page.checkBox_stretch.setEnabled(True)
+ self.page.comboBox_spread.setEnabled(True)
self.page.fillWidget.setCurrentIndex(self.fillType)
self.parent.drawPreview()
diff --git a/core.py b/core.py
index dcea783..e4a7a6c 100644
--- a/core.py
+++ b/core.py
@@ -79,6 +79,8 @@ class Core():
def insertComponent(self, compPos, moduleIndex):
if compPos < 0:
compPos = len(self.selectedComponents) -1
+ if len(self.selectedComponents) > 50:
+ return None
component = self.modules[moduleIndex].Component(
moduleIndex, compPos)
@@ -100,6 +102,10 @@ class Core():
self.selectedComponents.pop(i)
self.componentListChanged()
+ def clearComponents(self):
+ self.selectedComponents = list()
+ self.componentListChanged()
+
def updateComponent(self, i):
# print('updating %s' % self.selectedComponents[i])
self.selectedComponents[i].update()
@@ -164,24 +170,27 @@ class Core():
clearThis = True
# insert component into the loader
- loader.insertComponent(
+ i = loader.insertComponent(
self.moduleIndexFor(name), -1)
+ if i == None:
+ break
+
try:
if 'preset' in preset and preset['preset'] != None:
- self.selectedComponents[-1].loadPreset(
+ self.selectedComponents[i].loadPreset(
preset
)
else:
- self.selectedComponents[-1].loadPreset(
+ self.selectedComponents[i].loadPreset(
preset,
preset['preset']
)
except KeyError as e:
print('%s missing value %s' %
- (self.selectedComponents[-1], e))
+ (self.selectedComponents[i], e))
if clearThis:
- self.clearPreset(-1, loader)
+ self.clearPreset(i, loader)
except:
errcode = 1
data = sys.exc_info()
diff --git a/mainwindow.py b/mainwindow.py
index 0d6b130..78a54be 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -102,7 +102,6 @@ class MainWindow(QtCore.QObject):
self.createAudioVisualisation)
window.pushButton_Cancel.clicked.connect(self.stopVideo)
- window.setWindowTitle("Audio Visualizer")
for i, container in enumerate(self.core.encoder_options['containers']):
window.comboBox_videoContainer.addItem(container['name'])
@@ -212,7 +211,7 @@ class MainWindow(QtCore.QObject):
# Show the window and load current project
window.show()
self.currentProject = self.settings.value("currentProject")
- if self.autosaveExists():
+ if self.autosaveExists(identical=True):
# delete autosave if it's identical to the project
os.remove(self.autosavePath)
@@ -222,12 +221,10 @@ class MainWindow(QtCore.QObject):
% os.path.basename(self.currentProject)[:-4],
showCancel=True)
if ch:
- os.remove(self.currentProject)
- os.rename(self.autosavePath, self.currentProject)
+ self.saveProjectChanges()
else:
os.remove(self.autosavePath)
-
- self.openProject(self.currentProject)
+ self.openProject(self.currentProject, prompt=False)
self.drawPreview()
def cleanUp(self):
@@ -236,6 +233,13 @@ class MainWindow(QtCore.QObject):
self.previewThread.wait()
self.autosave()
+ def updateWindowTitle(self):
+ appName = 'Audio Visualizer'
+ if self.currentProject:
+ appName += ' - %s' % \
+ os.path.basename(self.currentProject)[:-4]
+ self.window.setWindowTitle(appName)
+
@QtCore.pyqtSlot(int, dict)
def updateComponentTitle(self, pos, presetStore=False):
if type(presetStore) == dict:
@@ -290,22 +294,24 @@ class MainWindow(QtCore.QObject):
self.settings.setValue('outputVideoBitrate', currentVideoBitrate)
self.settings.setValue('outputAudioBitrate', currentAudioBitrate)
- def autosave(self):
+ def autosave(self, force=False):
if not self.currentProject:
if os.path.exists(self.autosavePath):
os.remove(self.autosavePath)
- elif time.time() - self.lastAutosave >= 2.0:
+ elif force or time.time() - self.lastAutosave >= 2.0:
self.core.createProjectFile(self.autosavePath)
self.lastAutosave = time.time()
- def autosaveExists(self):
+ def autosaveExists(self, identical=True):
if self.currentProject and os.path.exists(self.autosavePath) \
- and filecmp.cmp(self.autosavePath, self.currentProject):
- with open(self.autosavePath, 'r') as f:
- p = [line for line in f]
- return True if len(p) > 1 else False
- else:
- return False
+ and filecmp.cmp(
+ self.autosavePath, self.currentProject) == identical:
+ return True
+ return False
+
+ def saveProjectChanges(self):
+ os.remove(self.currentProject)
+ os.rename(self.autosavePath, self.currentProject)
def openInputFileDialog(self):
inputDir = self.settings.value("inputDir", expanduser("~"))
@@ -417,10 +423,10 @@ class MainWindow(QtCore.QObject):
self.settings.setValue('outputHeight', res[1])
self.drawPreview()
- def drawPreview(self):
+ def drawPreview(self, force=False):
self.newTask.emit(self.core.selectedComponents)
# self.processTask.emit()
- self.autosave()
+ self.autosave(force)
def showPreviewImage(self, image):
self.previewWindow.changePixmap(image)
@@ -433,6 +439,10 @@ class MainWindow(QtCore.QObject):
index = self.core.insertComponent(
compPos, moduleIndex)
+ if index == None:
+ self.showMessage(msg="Too many components!")
+ return None
+
row = componentList.insertItem(
index,
self.core.selectedComponents[index].__doc__)
@@ -447,6 +457,7 @@ class MainWindow(QtCore.QObject):
stackedWidget.setCurrentIndex(index)
self.core.updateComponent(index)
+ return index
def removeComponent(self):
componentList = self.window.listWidget_componentList
@@ -514,26 +525,20 @@ class MainWindow(QtCore.QObject):
def clear(self):
'''Get a blank slate'''
- self.core.selectedComponents = []
+ self.core.clearComponents()
self.window.listWidget_componentList.clear()
for widget in self.pages:
self.window.stackedWidget.removeWidget(widget)
self.pages = []
def createNewProject(self):
- if self.autosaveExists():
- ch = self.showMessage(
- msg="You have unsaved changes in project '%s'. "
- "Save before starting a new project?"
- % os.path.basename(self.currentProject)[:-4],
- showCancel=True)
- if ch:
- self.saveCurrentProject()
+ self.openSaveChangesDialog('starting a new project')
self.clear()
self.currentProject = None
self.settings.setValue("currentProject", None)
- self.drawPreview()
+ self.drawPreview(True)
+ self.updateWindowTitle()
def saveCurrentProject(self):
if self.currentProject:
@@ -541,6 +546,20 @@ class MainWindow(QtCore.QObject):
else:
self.openSaveProjectDialog()
+ def openSaveChangesDialog(self, phrase):
+ if self.autosaveExists(identical=False):
+ ch = self.showMessage(
+ msg="You have unsaved changes in project '%s'. "
+ "Save before %s?" % \
+ (os.path.basename(self.currentProject)[:-4],
+ phrase),
+ showCancel=True)
+ if ch:
+ self.saveProjectChanges()
+
+ if os.path.exists(self.autosavePath):
+ os.remove(self.autosavePath)
+
def openSaveProjectDialog(self):
filename = QtGui.QFileDialog.getSaveFileName(
self.window, "Create Project File",
@@ -553,7 +572,7 @@ class MainWindow(QtCore.QObject):
self.settings.setValue("projectDir", os.path.dirname(filename))
self.settings.setValue("currentProject", filename)
self.currentProject = filename
-
+ self.updateWindowTitle()
self.core.createProjectFile(filename)
def openOpenProjectDialog(self):
@@ -563,16 +582,27 @@ class MainWindow(QtCore.QObject):
"Project Files (*.avp)")
self.openProject(filename)
- def openProject(self, filepath):
+ def openProject(self, filepath, prompt=True):
+ print('opening', filepath)
if not filepath or not os.path.exists(filepath) \
or not filepath.endswith('.avp'):
+ self.updateWindowTitle()
return
+
self.clear()
+ # ask to save any changes that are about to get deleted
+ if prompt:
+ self.openSaveChangesDialog('opening another project')
+
self.currentProject = filepath
+ self.updateWindowTitle()
self.settings.setValue("currentProject", filepath)
self.settings.setValue("projectDir", os.path.dirname(filepath))
# actually load the project using core method
self.core.openProject(self, filepath)
+ if self.window.listWidget_componentList.count() == 0:
+ self.drawPreview()
+ self.autosave(True)
def showMessage(self, **kwargs):
parent = kwargs['parent'] if 'parent' in kwargs else self.window
--
cgit v1.2.3
From 2c63b053769ecdc2791af3b35392f0ff0b19ad76 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 17 Jun 2017 19:08:18 -0400
Subject: exports to ~ if no dir given, fix infinite loop when cancelling
---
mainwindow.py | 36 +++++++++++++++++++++---------------
video_thread.py | 5 ++++-
2 files changed, 25 insertions(+), 16 deletions(-)
diff --git a/mainwindow.py b/mainwindow.py
index 78a54be..f722158 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -1,4 +1,3 @@
-from os.path import expanduser
from queue import Queue
from PyQt4 import QtCore, QtGui, uic
from PyQt4.QtCore import QSettings, Qt
@@ -189,11 +188,15 @@ class MainWindow(QtCore.QObject):
# Configure the Projects Menu
self.projectMenu = QMenu()
- self.ui_newProject = self.projectMenu.addAction("New Project")
- self.ui_newProject.triggered[()].connect(self.createNewProject)
+ self.window.menuButton_newProject = self.projectMenu.addAction(
+ "New Project")
+ self.window.menuButton_newProject.triggered[()].connect(
+ self.createNewProject)
- self.ui_openProject = self.projectMenu.addAction("Open Project")
- self.ui_openProject.triggered[()].connect(self.openOpenProjectDialog)
+ self.window.menuButton_openProject = self.projectMenu.addAction(
+ "Open Project")
+ self.window.menuButton_openProject.triggered[()].connect(
+ self.openOpenProjectDialog)
action = self.projectMenu.addAction("Save Project")
action.triggered[()].connect(self.saveCurrentProject)
@@ -314,18 +317,18 @@ class MainWindow(QtCore.QObject):
os.rename(self.autosavePath, self.currentProject)
def openInputFileDialog(self):
- inputDir = self.settings.value("inputDir", expanduser("~"))
+ inputDir = self.settings.value("inputDir", os.path.expanduser("~"))
fileName = QtGui.QFileDialog.getOpenFileName(
- self.window, "Open Music File",
- inputDir, "Music Files (%s)" % " ".join(self.core.audioFormats))
+ self.window, "Open Audio File",
+ inputDir, "Audio Files (%s)" % " ".join(self.core.audioFormats))
if not fileName == "":
self.settings.setValue("inputDir", os.path.dirname(fileName))
self.window.lineEdit_audioFile.setText(fileName)
def openOutputFileDialog(self):
- outputDir = self.settings.value("outputDir", expanduser("~"))
+ outputDir = self.settings.value("outputDir", os.path.expanduser("~"))
fileName = QtGui.QFileDialog.getSaveFileName(
self.window, "Set Output Video File",
@@ -347,7 +350,6 @@ class MainWindow(QtCore.QObject):
self.window.lineEdit_outputFile.text():
self.canceled = False
self.progressBarUpdated(-1)
- ffmpeg_cmd = self.settings.value("ffmpeg_cmd", expanduser("~"))
self.videoThread = QtCore.QThread(self)
self.videoWorker = video_thread.Worker(self)
self.videoWorker.moveToThread(self.videoThread)
@@ -358,9 +360,13 @@ class MainWindow(QtCore.QObject):
self.videoWorker.imageCreated.connect(self.showPreviewImage)
self.videoWorker.encoding.connect(self.changeEncodingStatus)
self.videoThread.start()
+ outputPath = self.window.lineEdit_outputFile.text()
+ if not os.path.dirname(outputPath):
+ outputPath = os.path.join(
+ os.path.expanduser("~"), outputPath)
self.videoTask.emit(
self.window.lineEdit_audioFile.text(),
- self.window.lineEdit_outputFile.text(),
+ outputPath,
self.core.selectedComponents)
else:
self.showMessage(
@@ -384,8 +390,8 @@ class MainWindow(QtCore.QObject):
self.window.pushButton_listMoveDown.setEnabled(False)
self.window.pushButton_listMoveUp.setEnabled(False)
self.window.listWidget_componentList.setEnabled(False)
- self.ui_newProject.setEnabled(False)
- self.ui_openProject.setEnabled(False)
+ self.window.menuButton_newProject.setEnabled(False)
+ self.window.menuButton_openProject.setEnabled(False)
else:
self.window.pushButton_createVideo.setEnabled(True)
self.window.pushButton_Cancel.setEnabled(False)
@@ -403,8 +409,8 @@ class MainWindow(QtCore.QObject):
self.window.pushButton_listMoveDown.setEnabled(True)
self.window.pushButton_listMoveUp.setEnabled(True)
self.window.listWidget_componentList.setEnabled(True)
- self.ui_newProject.setEnabled(True)
- self.ui_openProject.setEnabled(True)
+ self.window.menuButton_newProject.setEnabled(True)
+ self.window.menuButton_openProject.setEnabled(True)
def progressBarUpdated(self, value):
self.window.progressBar_createVideo.setValue(value)
diff --git a/video_thread.py b/video_thread.py
index fc877bd..d7220f1 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -116,6 +116,7 @@ class Worker(QtCore.QObject):
for cont in options['containers']:
if cont['name'] == containerName:
container = cont['container']
+ break
vencoders = options['video-codecs'][vcodec]
aencoders = options['audio-codecs'][acodec]
@@ -222,13 +223,15 @@ class Worker(QtCore.QObject):
if not self.canceled:
for i in range(0, len(self.completeAudioArray), self.sampleSize):
while True:
- if i in frameBuffer:
+ if i in frameBuffer or self.canceled:
# if frame's in buffer, pipe it to ffmpeg
break
# else fetch the next frame & add to the buffer
data = self.renderQueue.get()
frameBuffer[data[0]] = data[1]
self.renderQueue.task_done()
+ if self.canceled:
+ break
try:
self.out_pipe.stdin.write(frameBuffer[i].tobytes())
--
cgit v1.2.3
From 044fddfa9c5063f61e4a97993efe7cd5b2bae066 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 18 Jun 2017 14:46:08 -0400
Subject: basic commandline functionality using 3 args
needs more args so components can be modified without gui
---
command.py | 215 +++++++++++++++++++++++++-------------------------------
core.py | 40 +++++++----
main.py | 54 ++++++++------
mainwindow.py | 76 +++++++++++---------
video_thread.py | 2 -
5 files changed, 196 insertions(+), 191 deletions(-)
diff --git a/command.py b/command.py
index a610d8c..1b07afc 100644
--- a/command.py
+++ b/command.py
@@ -1,122 +1,97 @@
-# FIXME: commandline functionality broken until we decide how to implement it
-'''
+from PyQt4 import QtCore
+from PyQt4.QtCore import QSettings
+import argparse
+import os
+
+import core
+import video_thread
+from main import LoadDefaultSettings
+
+
class Command(QtCore.QObject):
- videoTask = QtCore.pyqtSignal(str, str, str, list)
-
- def __init__(self):
- QtCore.QObject.__init__(self)
- self.modules = []
- self.selectedComponents = []
-
- import argparse
- self.parser = argparse.ArgumentParser(
- description='Create a visualization for an audio file')
- self.parser.add_argument(
- '-i', '--input', dest='input', help='input audio file', required=True)
- self.parser.add_argument(
- '-o', '--output', dest='output',
- help='output video file', required=True)
- self.parser.add_argument(
- '-b', '--background', dest='bgimage',
- help='background image file', required=True)
- self.parser.add_argument(
- '-t', '--text', dest='text', help='title text', required=True)
- self.parser.add_argument(
- '-f', '--font', dest='font', help='title font', required=False)
- self.parser.add_argument(
- '-s', '--fontsize', dest='fontsize',
- help='title font size', required=False)
- self.parser.add_argument(
- '-c', '--textcolor', dest='textcolor',
- help='title text color in r,g,b format', required=False)
- self.parser.add_argument(
- '-C', '--viscolor', dest='viscolor',
- help='visualization color in r,g,b format', required=False)
- self.parser.add_argument(
- '-x', '--xposition', dest='xposition',
- help='x position', required=False)
- self.parser.add_argument(
- '-y', '--yposition', dest='yposition',
- help='y position', required=False)
- self.parser.add_argument(
- '-a', '--alignment', dest='alignment',
- help='title alignment', required=False,
- type=int, choices=[0, 1, 2])
- self.args = self.parser.parse_args()
-
- self.settings = QSettings('settings.ini', QSettings.IniFormat)
- LoadDefaultSettings(self)
-
- # load colours as tuples from comma-separated strings
- self.textColor = core.Core.RGBFromString(
- self.settings.value("textColor", '255, 255, 255'))
- self.visColor = core.Core.RGBFromString(
- self.settings.value("visColor", '255, 255, 255'))
- if self.args.textcolor:
- self.textColor = core.Core.RGBFromString(self.args.textcolor)
- if self.args.viscolor:
- self.visColor = core.Core.RGBFromString(self.args.viscolor)
-
- # font settings
- if self.args.font:
- self.font = QFont(self.args.font)
- else:
- self.font = QFont(self.settings.value("titleFont", QFont()))
-
- if self.args.fontsize:
- self.fontsize = int(self.args.fontsize)
- else:
- self.fontsize = int(self.settings.value("fontSize", 35))
- if self.args.alignment:
- self.alignment = int(self.args.alignment)
- else:
- self.alignment = int(self.settings.value("alignment", 0))
-
- if self.args.xposition:
- self.textX = int(self.args.xposition)
- else:
- self.textX = int(self.settings.value("xPosition", 70))
-
- if self.args.yposition:
- self.textY = int(self.args.yposition)
- else:
- self.textY = int(self.settings.value("yPosition", 375))
-
- ffmpeg_cmd = self.settings.value("ffmpeg_cmd", expanduser("~"))
-
- self.videoThread = QtCore.QThread(self)
- self.videoWorker = video_thread.Worker(self)
-
- self.videoWorker.moveToThread(self.videoThread)
- self.videoWorker.videoCreated.connect(self.videoCreated)
-
- self.videoThread.start()
- self.videoTask.emit(self.args.bgimage,
- self.args.text,
- self.font,
- self.fontsize,
- self.alignment,
- self.textX,
- self.textY,
- self.textColor,
- self.visColor,
- self.args.input,
- self.args.output,
- self.selectedComponents)
-
- def videoCreated(self):
- self.videoThread.quit()
- self.videoThread.wait()
- self.cleanUp()
-
- def cleanUp(self):
- self.settings.setValue("titleFont", self.font.toString())
- self.settings.setValue("alignment", str(self.alignment))
- self.settings.setValue("fontSize", str(self.fontsize))
- self.settings.setValue("xPosition", str(self.textX))
- self.settings.setValue("yPosition", str(self.textY))
- self.settings.setValue("visColor", '%s,%s,%s' % self.visColor)
- self.settings.setValue("textColor", '%s,%s,%s' % self.textColor)
- sys.exit(0)
-'''
+ videoTask = QtCore.pyqtSignal(str, str, list)
+
+ def __init__(self):
+ QtCore.QObject.__init__(self)
+ self.core = core.Core()
+ self.dataDir = self.core.dataDir
+
+ self.parser = argparse.ArgumentParser(
+ description='Create a visualization for an audio file')
+ self.parser.add_argument(
+ '-i', '--input', help='input audio file', required=True)
+ self.parser.add_argument(
+ '-o', '--output', help='output video file', required=True)
+
+ # optional arguments
+ self.parser.add_argument(
+ 'projpath', metavar='path-to-project',
+ help='open a project file (.avp)', nargs='?')
+
+ '''
+ self.parser.add_argument(
+ '-b', '--background', dest='bgimage',
+ help='background image file', required=True)
+ self.parser.add_argument(
+ '-t', '--text', dest='text', help='title text', required=True)
+ self.parser.add_argument(
+ '-f', '--font', dest='font', help='title font', required=False)
+ self.parser.add_argument(
+ '-s', '--fontsize', dest='fontsize',
+ help='title font size', required=False)
+ self.parser.add_argument(
+ '-c', '--textcolor', dest='textcolor',
+ help='title text color in r,g,b format', required=False)
+ self.parser.add_argument(
+ '-C', '--viscolor', dest='viscolor',
+ help='visualization color in r,g,b format', required=False)
+ self.parser.add_argument(
+ '-x', '--xposition', dest='xposition',
+ help='x position', required=False)
+ self.parser.add_argument(
+ '-y', '--yposition', dest='yposition',
+ help='y position', required=False)
+ self.parser.add_argument(
+ '-a', '--alignment', dest='alignment',
+ help='title alignment', required=False,
+ type=int, choices=[0, 1, 2])
+ '''
+
+ self.args = self.parser.parse_args()
+ self.settings = QSettings(
+ os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat)
+ LoadDefaultSettings(self)
+
+ if self.args.projpath:
+ self.core.openProject(self, self.args.projpath)
+
+ self.createAudioVisualisation()
+
+ def createAudioVisualisation(self):
+ self.videoThread = QtCore.QThread(self)
+ self.videoWorker = video_thread.Worker(self)
+ self.videoWorker.moveToThread(self.videoThread)
+ self.videoWorker.videoCreated.connect(self.videoCreated)
+
+ self.videoThread.start()
+ self.videoTask.emit(
+ self.args.input,
+ self.args.output,
+ self.core.selectedComponents)
+
+ def videoCreated(self):
+ self.videoThread.quit()
+ self.videoThread.wait()
+ self.cleanUp()
+
+ def showMessage(self, **kwargs):
+ print(kwargs['msg'])
+ if 'detail' in kwargs:
+ print(kwargs['detail'])
+
+ def drawPreview(self, *args):
+ pass
+
+ def cleanUp(self, *args):
+ pass
diff --git a/core.py b/core.py
index e4a7a6c..5e4071a 100644
--- a/core.py
+++ b/core.py
@@ -37,6 +37,7 @@ class Core():
'*.wav',
'*.ogg',
'*.fla',
+ '*.flac',
'*.aac',
])
self.imageFormats = Core.appendUppercase([
@@ -76,9 +77,10 @@ class Core():
for i, component in enumerate(self.selectedComponents):
component.compPos = i
- def insertComponent(self, compPos, moduleIndex):
+ def insertComponent(self, compPos, moduleIndex, loader):
+ '''Creates a new component'''
if compPos < 0:
- compPos = len(self.selectedComponents) -1
+ compPos = len(self.selectedComponents)
if len(self.selectedComponents) > 50:
return None
@@ -87,8 +89,14 @@ class Core():
self.selectedComponents.insert(
compPos,
component)
-
self.componentListChanged()
+
+ # init component's widget for loading/saving presets
+ self.selectedComponents[compPos].widget(loader)
+ self.updateComponent(compPos)
+
+ if hasattr(loader, 'insertComponent'):
+ loader.insertComponent(compPos)
return compPos
def moveComponent(self, startI, endI):
@@ -115,11 +123,8 @@ class Core():
index = compNames.index(compName)
return self.moduleIndexes[index]
- def clearPreset(self, compIndex, loader=None):
- '''Clears a preset from a component'''
+ def clearPreset(self, compIndex):
self.selectedComponents[compIndex].currentPreset = None
- if loader:
- loader.updateComponentTitle(compIndex)
def openPreset(self, filepath, compIndex, presetName):
'''Applies a preset to a specific component'''
@@ -148,9 +153,10 @@ class Core():
return saveValueStore
def openProject(self, loader, filepath):
- '''loader is the object calling this method (mainwindow/command)
- which implements an insertComponent method'''
+ '''loader is the object calling this method which must have
+ its own showMessage(**kwargs) method for displaying errors'''
errcode, data = self.parseAvFile(filepath)
+ print(data)
if errcode == 0:
try:
for i, tup in enumerate(data['Components']):
@@ -169,10 +175,13 @@ class Core():
# saved preset was renamed or deleted
clearThis = True
- # insert component into the loader
- i = loader.insertComponent(
- self.moduleIndexFor(name), -1)
+ # create the actual component object & get its index
+ i = self.insertComponent(
+ -1,
+ self.moduleIndexFor(name),
+ loader)
if i == None:
+ loader.showMessage(msg="Too many components!")
break
try:
@@ -190,7 +199,9 @@ class Core():
(self.selectedComponents[i], e))
if clearThis:
- self.clearPreset(i, loader)
+ self.clearPreset(i)
+ if hasattr(loader, 'updateComponentTitle'):
+ loader.updateComponentTitle(i)
except:
errcode = 1
data = sys.exc_info()
@@ -202,7 +213,8 @@ class Core():
# probably just an old version, still loadable
print('file missing value: %s' % value)
return
- loader.createNewProject()
+ if hasattr(loader, 'createNewProject'):
+ loader.createNewProject()
msg = '%s: %s' % (typ.__name__, value)
loader.showMessage(
msg="Project file '%s' is corrupted." % filepath,
diff --git a/main.py b/main.py
index 7c0727b..140392c 100644
--- a/main.py
+++ b/main.py
@@ -1,8 +1,5 @@
-from importlib import import_module
from PyQt4 import QtGui, uic
-from PyQt4.QtCore import Qt
import sys
-import io
import os
import atexit
import signal
@@ -10,7 +7,6 @@ import signal
import core
import preview_thread
import video_thread
-from mainwindow import *
def LoadDefaultSettings(self):
@@ -36,34 +32,50 @@ def LoadDefaultSettings(self):
}
for parm, value in default.items():
+ print(parm, self.settings.value(parm))
if self.settings.value(parm) is None:
self.settings.setValue(parm, value)
if __name__ == "__main__":
- ''' FIXME commandline functionality broken until we decide how to implement
- if len(sys.argv) > 1:
- # command line mode
- app = QtGui.QApplication(sys.argv, False)
- command = Command()
- signal.signal(signal.SIGINT, command.cleanUp)
- sys.exit(app.exec_())
+ mode = 'gui'
+ if len(sys.argv) > 2:
+ mode = 'cmd'
+
+ elif len(sys.argv) == 2:
+ if sys.argv[1].startswith('-'):
+ mode = 'cmd'
+ else:
+ # opening a project file with gui
+ proj = sys.argv[1]
else:
- '''
+ # normal gui launch
+ proj = None
+
app = QtGui.QApplication(sys.argv)
app.setApplicationName("audio-visualizer")
app.setOrganizationName("audio-visualizer")
- window = uic.loadUi(os.path.join(
- os.path.dirname(os.path.realpath(__file__)), "mainwindow.ui"))
- # window.adjustSize()
- desc = QtGui.QDesktopWidget()
- dpi = desc.physicalDpiX()
- topMargin = 0 if (dpi == 96) else int(10 * (dpi / 96))
- window.resize(window.width() * (dpi / 96), window.height() * (dpi / 96))
- # window.verticalLayout_2.setContentsMargins(0, topMargin, 0, 0)
+ if mode == 'cmd':
+ from command import *
+
+ main = Command()
+
+ elif mode == 'gui':
+ from mainwindow import *
+
+ window = uic.loadUi(os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), "mainwindow.ui"))
+ # window.adjustSize()
+ desc = QtGui.QDesktopWidget()
+ dpi = desc.physicalDpiX()
+
+ topMargin = 0 if (dpi == 96) else int(10 * (dpi / 96))
+ window.resize(window.width() * (dpi / 96), window.height() * (dpi / 96))
+ # window.verticalLayout_2.setContentsMargins(0, topMargin, 0, 0)
- main = MainWindow(window)
+ main = MainWindow(window, proj)
+ # applicable to both modes
signal.signal(signal.SIGINT, main.cleanUp)
atexit.register(main.cleanUp)
diff --git a/mainwindow.py b/mainwindow.py
index f722158..2a8762d 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -45,7 +45,7 @@ class MainWindow(QtCore.QObject):
processTask = QtCore.pyqtSignal()
videoTask = QtCore.pyqtSignal(str, str, list)
- def __init__(self, window):
+ def __init__(self, window, project):
QtCore.QObject.__init__(self)
# print('main thread id: {}'.format(QtCore.QThread.currentThreadId()))
@@ -151,7 +151,7 @@ class MainWindow(QtCore.QObject):
for i, comp in enumerate(self.core.modules):
action = self.compMenu.addAction(comp.Component.__doc__)
action.triggered[()].connect(
- lambda item=i: self.insertComponent(item))
+ lambda item=i: self.core.insertComponent(0, item, self))
self.window.pushButton_addComponent.setMenu(self.compMenu)
@@ -211,24 +211,36 @@ class MainWindow(QtCore.QObject):
self.openPresetManager
)
- # Show the window and load current project
window.show()
- self.currentProject = self.settings.value("currentProject")
- if self.autosaveExists(identical=True):
- # delete autosave if it's identical to the project
- os.remove(self.autosavePath)
- if self.currentProject and os.path.exists(self.autosavePath):
- ch = self.showMessage(
- msg="Restore unsaved changes in project '%s'?"
- % os.path.basename(self.currentProject)[:-4],
- showCancel=True)
- if ch:
- self.saveProjectChanges()
- else:
+ if project and project != self.autosavePath:
+ # open a project from the commandline
+ if not os.path.dirname(project):
+ project = os.path.join(os.path.expanduser('~'), project)
+ self.currentProject = project
+ self.settings.setValue("currentProject", project)
+ if os.path.exists(self.autosavePath):
os.remove(self.autosavePath)
+ else:
+ # open the last currentProject from settings
+ self.currentProject = self.settings.value("currentProject")
+
+ # delete autosave if it's identical to this project
+ if self.autosaveExists(identical=True):
+ os.remove(self.autosavePath)
+
+ if self.currentProject and os.path.exists(self.autosavePath):
+ ch = self.showMessage(
+ msg="Restore unsaved changes in project '%s'?"
+ % os.path.basename(self.currentProject)[:-4],
+ showCancel=True)
+ if ch:
+ self.saveProjectChanges()
+ else:
+ os.remove(self.autosavePath)
+
self.openProject(self.currentProject, prompt=False)
- self.drawPreview()
+ self.drawPreview(True)
def cleanUp(self):
self.timer.stop()
@@ -240,7 +252,8 @@ class MainWindow(QtCore.QObject):
appName = 'Audio Visualizer'
if self.currentProject:
appName += ' - %s' % \
- os.path.basename(self.currentProject)[:-4]
+ os.path.splitext(
+ os.path.basename(self.currentProject))[0]
self.window.setWindowTitle(appName)
@QtCore.pyqtSlot(int, dict)
@@ -252,7 +265,6 @@ class MainWindow(QtCore.QObject):
else:
modified = (presetStore != self.core.savedPresets[name])
else:
- print(pos, presetStore)
modified = bool(presetStore)
if pos < 0:
pos = len(self.core.selectedComponents)-1
@@ -306,10 +318,14 @@ class MainWindow(QtCore.QObject):
self.lastAutosave = time.time()
def autosaveExists(self, identical=True):
- if self.currentProject and os.path.exists(self.autosavePath) \
- and filecmp.cmp(
- self.autosavePath, self.currentProject) == identical:
- return True
+ try:
+ if self.currentProject and os.path.exists(self.autosavePath) \
+ and filecmp.cmp(
+ self.autosavePath, self.currentProject) == identical:
+ return True
+ except FileNotFoundError:
+ print('project file couldn\'t be located:', self.currentProject)
+ return identical
return False
def saveProjectChanges(self):
@@ -411,6 +427,7 @@ class MainWindow(QtCore.QObject):
self.window.listWidget_componentList.setEnabled(True)
self.window.menuButton_newProject.setEnabled(True)
self.window.menuButton_openProject.setEnabled(True)
+ self.drawPreview(True)
def progressBarUpdated(self, value):
self.window.progressBar_createVideo.setValue(value)
@@ -437,19 +454,11 @@ class MainWindow(QtCore.QObject):
def showPreviewImage(self, image):
self.previewWindow.changePixmap(image)
- def insertComponent(self, moduleIndex, compPos=0):
+ def insertComponent(self, index):
componentList = self.window.listWidget_componentList
stackedWidget = self.window.stackedWidget
- if compPos < 0:
- compPos = componentList.count()
-
- index = self.core.insertComponent(
- compPos, moduleIndex)
- if index == None:
- self.showMessage(msg="Too many components!")
- return None
- row = componentList.insertItem(
+ componentList.insertItem(
index,
self.core.selectedComponents[index].__doc__)
componentList.setCurrentRow(index)
@@ -458,11 +467,10 @@ class MainWindow(QtCore.QObject):
self.core.selectedComponents[index].modified.connect(
self.updateComponentTitle)
- self.pages.insert(index, self.core.selectedComponents[index].widget(self))
+ self.pages.insert(index, self.core.selectedComponents[index].page)
stackedWidget.insertWidget(index, self.pages[index])
stackedWidget.setCurrentIndex(index)
- self.core.updateComponent(index)
return index
def removeComponent(self):
diff --git a/video_thread.py b/video_thread.py
index d7220f1..2255259 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -27,7 +27,6 @@ class Worker(QtCore.QObject):
self.core = core.Core()
self.core.settings = parent.settings
self.modules = parent.core.modules
- self.stackedWidget = parent.window.stackedWidget
self.parent = parent
parent.videoTask.connect(self.createVideo)
self.sampleSize = 1470
@@ -280,7 +279,6 @@ class Worker(QtCore.QObject):
self.error = False
self.canceled = False
- self.parent.drawPreview()
self.stopped = True
self.encoding.emit(False)
self.videoCreated.emit()
--
cgit v1.2.3
From 82011de966f95afa88ec9e11e0ce86cbd04d5fc0 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 18 Jun 2017 21:49:00 -0400
Subject: able to create components from commandline
TODO: make components respond to argument
---
command.py | 47 ++++++++++++++++++++++++++++++++++++++++++++---
core.py | 4 ++--
2 files changed, 46 insertions(+), 5 deletions(-)
diff --git a/command.py b/command.py
index 1b07afc..d56c64b 100644
--- a/command.py
+++ b/command.py
@@ -18,16 +18,27 @@ class Command(QtCore.QObject):
self.dataDir = self.core.dataDir
self.parser = argparse.ArgumentParser(
- description='Create a visualization for an audio file')
+ description='Create a visualization for an audio file',
+ epilog='EXAMPLE COMMAND: main.py myvideotemplate.avp '
+ '-i ~/Music/song.mp3 -o ~/video.mp4 '
+ '-c 0 image ~/Pictures/thisWeeksPicture.jpg '
+ '-c 1 vis classic')
self.parser.add_argument(
- '-i', '--input', help='input audio file', required=True)
+ '-i', '--input', metavar='SOUND',
+ help='input audio file', required=True)
self.parser.add_argument(
- '-o', '--output', help='output video file', required=True)
+ '-o', '--output', metavar='OUTPUT',
+ help='output video file', required=True)
# optional arguments
self.parser.add_argument(
'projpath', metavar='path-to-project',
help='open a project file (.avp)', nargs='?')
+ self.parser.add_argument(
+ '-c', '--comp', metavar=('LAYER', 'NAME', 'ARG'),
+ help='create/edit component NAME at LAYER.'
+ '"help" for information about possible args', nargs=3,
+ action='append')
'''
self.parser.add_argument(
@@ -66,6 +77,16 @@ class Command(QtCore.QObject):
if self.args.projpath:
self.core.openProject(self, self.args.projpath)
+ if self.args.comp:
+ for comp in self.args.comp:
+ pos, name, arg = comp
+ realName = self.parseCompName(name)
+ if not realName:
+ print(name, 'is not a valid component name.')
+ quit()
+ modI = self.core.moduleIndexFor(realName)
+ self.core.insertComponent(int(pos), modI, self)
+
self.createAudioVisualisation()
def createAudioVisualisation(self):
@@ -95,3 +116,23 @@ class Command(QtCore.QObject):
def cleanUp(self, *args):
pass
+
+ def parseCompName(self, name):
+ '''Deduces a proper component name out of a commandline arg'''
+ compFileNames = [ \
+ os.path.splitext(os.path.basename(
+ mod.__file__))[0] \
+ for mod in self.core.modules \
+ ]
+
+ if name.title() in self.core.compNames:
+ return name.title()
+ for compName in self.core.compNames:
+ if name.capitalize() in compName:
+ return compName
+ for i, compFileName in enumerate(compFileNames):
+ if name.lower() in compFileName:
+ return self.core.compNames[i]
+ return
+
+ return None
diff --git a/core.py b/core.py
index 5e4071a..2dde464 100644
--- a/core.py
+++ b/core.py
@@ -72,6 +72,7 @@ class Core():
for name in findComponents()
]
self.moduleIndexes = [i for i in range(len(self.modules))]
+ self.compNames = [mod.Component.__doc__ for mod in self.modules]
def componentListChanged(self):
for i, component in enumerate(self.selectedComponents):
@@ -119,8 +120,7 @@ class Core():
self.selectedComponents[i].update()
def moduleIndexFor(self, compName):
- compNames = [mod.Component.__doc__ for mod in self.modules]
- index = compNames.index(compName)
+ index = self.compNames.index(compName)
return self.moduleIndexes[index]
def clearPreset(self, compIndex):
--
cgit v1.2.3
From 5c74d496a960042ed4a4279328dc81e23dfdc1d9 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 22 Jun 2017 18:40:34 -0400
Subject: preset-loading and basic args from commandline
also made some docstrings more informative
---
command.py | 30 ++++++++++++----------
components/__base__.py | 69 +++++++++++++++++++++++++++++++++++---------------
components/image.py | 17 +++++++++++++
components/original.py | 12 +++++++++
components/text.py | 10 ++++++++
components/video.py | 16 ++++++++++++
core.py | 22 ++++++++++------
main.py | 12 ++++-----
mainwindow.py | 1 -
video_thread.py | 29 +++++++++++----------
10 files changed, 156 insertions(+), 62 deletions(-)
diff --git a/command.py b/command.py
index d56c64b..97eddd2 100644
--- a/command.py
+++ b/command.py
@@ -36,7 +36,7 @@ class Command(QtCore.QObject):
help='open a project file (.avp)', nargs='?')
self.parser.add_argument(
'-c', '--comp', metavar=('LAYER', 'NAME', 'ARG'),
- help='create/edit component NAME at LAYER.'
+ help='create component NAME at LAYER.'
'"help" for information about possible args', nargs=3,
action='append')
@@ -80,12 +80,18 @@ class Command(QtCore.QObject):
if self.args.comp:
for comp in self.args.comp:
pos, name, arg = comp
+ try:
+ pos = int(pos)
+ except ValueError:
+ print(pos, 'is not a layer number.')
+ quit(1)
realName = self.parseCompName(name)
if not realName:
print(name, 'is not a valid component name.')
- quit()
+ quit(1)
modI = self.core.moduleIndexFor(realName)
- self.core.insertComponent(int(pos), modI, self)
+ i = self.core.insertComponent(pos, modI, self)
+ self.core.selectedComponents[i].command(arg)
self.createAudioVisualisation()
@@ -99,12 +105,12 @@ class Command(QtCore.QObject):
self.videoTask.emit(
self.args.input,
self.args.output,
- self.core.selectedComponents)
+ list(reversed(self.core.selectedComponents))
+ )
def videoCreated(self):
self.videoThread.quit()
self.videoThread.wait()
- self.cleanUp()
def showMessage(self, **kwargs):
print(kwargs['msg'])
@@ -114,22 +120,20 @@ class Command(QtCore.QObject):
def drawPreview(self, *args):
pass
- def cleanUp(self, *args):
- pass
-
def parseCompName(self, name):
'''Deduces a proper component name out of a commandline arg'''
- compFileNames = [ \
- os.path.splitext(os.path.basename(
- mod.__file__))[0] \
- for mod in self.core.modules \
- ]
if name.title() in self.core.compNames:
return name.title()
for compName in self.core.compNames:
if name.capitalize() in compName:
return compName
+
+ compFileNames = [ \
+ os.path.splitext(os.path.basename(
+ mod.__file__))[0] \
+ for mod in self.core.modules \
+ ]
for i, compFileName in enumerate(compFileNames):
if name.lower() in compFileName:
return self.core.compNames[i]
diff --git a/components/__base__.py b/components/__base__.py
index 88f22d4..e43a517 100644
--- a/components/__base__.py
+++ b/components/__base__.py
@@ -1,5 +1,6 @@
from PyQt4 import QtGui, QtCore
from PIL import Image
+import os
class Component(QtCore.QObject):
@@ -7,11 +8,12 @@ class Component(QtCore.QObject):
# modified = QtCore.pyqtSignal(int, bool)
- def __init__(self, moduleIndex, compPos):
+ def __init__(self, moduleIndex, compPos, core):
super().__init__()
self.currentPreset = None
self.moduleIndex = moduleIndex
self.compPos = compPos
+ self.core = core
def __str__(self):
return self.__doc__
@@ -32,24 +34,59 @@ class Component(QtCore.QObject):
# read your widget values, then call super().update()
def loadPreset(self, presetDict, presetName):
- '''Children should take (presetDict, presetName=None) as args'''
-
- # Use super().loadPreset(presetDict, presetName)
- # Then update your widgets using the preset dict
+ '''Subclasses take (presetDict, presetName=None) as args.
+ Must use super().loadPreset(presetDict, presetName) first,
+ then update self.page widgets using the preset dict.
+ '''
self.currentPreset = presetName \
if presetName != None else presetDict['preset']
- '''
- def savePreset(self):
- return {}
- '''
+
def preFrameRender(self, **kwargs):
+ '''Triggered only before a video is exported (video_thread.py)
+ self.worker = the video thread worker
+ self.completeAudioArray = a list of audio samples
+ self.sampleSize = number of audio samples per video frame
+ self.progressBarUpdate = signal to set progress bar number
+ self.progressBarSetText = signal to set progress bar text
+ Use the latter two signals to update the MainProgram if needed
+ for a long initialization procedure (i.e., for a visualizer)
+ '''
for var, value in kwargs.items():
exec('self.%s = value' % var)
+ def command(self, arg):
+ '''Configure a component using argument from the commandline.
+ Use super().command(arg) at the end of a subclass's method,
+ if no arguments are found in that method first
+ '''
+ if arg.startswith('preset='):
+ _, preset = arg.split('=', 1)
+ path = os.path.join(self.core.getPresetDir(self), preset)
+ if not os.path.exists(path):
+ print('Couldn\'t locate preset "%s"' % preset)
+ quit(1)
+ else:
+ print('Opening "%s" preset on layer %s' % \
+ (preset, self.compPos))
+ self.core.openPreset(path, self.compPos, preset)
+ else:
+ print(
+ 'To open a preset for this component:\n'
+ ' "preset=Preset Name"\n')
+ self.commandHelp()
+ quit(0)
+
+ def commandHelp(self):
+ '''Print help text for this Component's commandline arguments'''
+
def blankFrame(self, width, height):
return Image.new("RGBA", (width, height), (0, 0, 0, 0))
def pickColor(self):
+ '''Use color picker to get color input from the user,
+ and return this as an RGB string and QPushButton stylesheet.
+ In a subclass apply stylesheet to any color selection widgets
+ '''
dialog = QtGui.QColorDialog()
dialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True)
color = dialog.getColor()
@@ -63,7 +100,7 @@ class Component(QtCore.QObject):
return None, None
def RGBFromString(self, string):
- ''' turns an RGB string like "255, 255, 255" into a tuple '''
+ ''' Turns an RGB string like "255, 255, 255" into a tuple '''
try:
tup = tuple([int(i) for i in string.split(',')])
if len(tup) != 3:
@@ -83,14 +120,10 @@ class Component(QtCore.QObject):
self.parent = parent
page = uic.loadUi(os.path.join(
os.path.dirname(os.path.realpath(__file__)), 'example.ui'))
- # connect widgets signals
+ # --- connect widget signals here ---
self.page = page
return page
- def update(self):
- super().update()
- self.parent.drawPreview()
-
def previewRender(self, previewWorker):
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
@@ -102,12 +135,6 @@ class Component(QtCore.QObject):
height = int(self.worker.core.settings.value('outputHeight'))
image = Image.new("RGBA", (width, height), (0,0,0,0))
return image
-
- def cancel(self):
- self.canceled = True
-
- def reset(self):
- self.canceled = False
'''
class BadComponentInit(Exception):
diff --git a/components/image.py b/components/image.py
index b6aa29b..d0e1894 100644
--- a/components/image.py
+++ b/components/image.py
@@ -92,3 +92,20 @@ class Component(__base__.Component):
self.settings.setValue("backgroundDir", os.path.dirname(filename))
self.page.lineEdit_image.setText(filename)
self.update()
+
+ def command(self, arg):
+ if not arg.startswith('preset='):
+ if os.path.exists(arg):
+ try:
+ Image.open(arg)
+ self.imagePath = arg
+ self.stretched = True
+ return True
+ except OSError as e:
+ print("Not a supported image format")
+ quit(1)
+ super().command(arg)
+
+ def commandHelp(self):
+ print('Give a complete filepath to an image to load that '
+ 'image with default settings.')
diff --git a/components/original.py b/components/original.py
index 5e2f9d4..328d64f 100644
--- a/components/original.py
+++ b/components/original.py
@@ -183,3 +183,15 @@ class Component(__base__.Component):
return im
+ def command(self, arg):
+ if not arg.startswith('preset='):
+ if arg == 'classic':
+ self.layout = 0; return
+ elif arg == 'split':
+ self.layout = 1; return
+ elif arg == 'bottom':
+ self.layout = 2; return
+ super().command(arg)
+
+ def commandHelp(self):
+ print('Give a layout name: classic, split, or bottom')
diff --git a/components/text.py b/components/text.py
index f8ef7b3..6c465b1 100644
--- a/components/text.py
+++ b/components/text.py
@@ -146,3 +146,13 @@ class Component(__base__.Component):
return
self.page.lineEdit_textColor.setText(RGBstring)
self.page.pushButton_textColor.setStyleSheet(btnStyle)
+
+ def commandHelp(self, arg):
+ print('Enter a string to use as centred white text. '
+ 'Use quotes around the string to escape spaces.')
+
+ def command(self, arg):
+ if not arg.startswith('preset='):
+ self.title = arg
+ return True
+ super().command(arg)
diff --git a/components/video.py b/components/video.py
index 3d43a18..dd385b4 100644
--- a/components/video.py
+++ b/components/video.py
@@ -221,6 +221,22 @@ class Component(__base__.Component):
width, height = scale(self.scale, width, height, int)
self.chunkSize = 4*width*height
+ def command(self, arg):
+ if not arg.startswith('preset='):
+ if os.path.exists(arg):
+ if os.path.splitext(arg)[1] in self.core.videoFormats:
+ self.videoPath = arg
+ self.scale = 100
+ return True
+ else:
+ print("Not a supported video format")
+ quit(1)
+ super().command(arg)
+
+ def commandHelp(self):
+ print('Give a complete filepath to a video to load that '
+ 'video with default settings.')
+
def scale(scale, width, height, returntype=None):
width = (float(width) / 100.0) * float(scale)
height = (float(height) / 100.0) * float(scale)
diff --git a/core.py b/core.py
index 2dde464..42eb44e 100644
--- a/core.py
+++ b/core.py
@@ -86,7 +86,7 @@ class Core():
return None
component = self.modules[moduleIndex].Component(
- moduleIndex, compPos)
+ moduleIndex, compPos, self)
self.selectedComponents.insert(
compPos,
component)
@@ -142,6 +142,10 @@ class Core():
self.savedPresets[presetName] = dict(saveValueStore)
return True
+ def getPresetDir(self, comp):
+ return os.path.join(
+ self.presetDir, str(comp), str(comp.version()))
+
def getPreset(self, filepath):
'''Returns the preset dict stored at this filepath'''
if not os.path.exists(filepath):
@@ -153,10 +157,11 @@ class Core():
return saveValueStore
def openProject(self, loader, filepath):
- '''loader is the object calling this method which must have
- its own showMessage(**kwargs) method for displaying errors'''
+ ''' loader is the object calling this method which must have
+ its own showMessage(**kwargs) method for displaying errors.
+ '''
errcode, data = self.parseAvFile(filepath)
- print(data)
+ #print(data)
if errcode == 0:
try:
for i, tup in enumerate(data['Components']):
@@ -224,12 +229,14 @@ class Core():
def parseAvFile(self, filepath):
'''Parses an avp (project) or avl (preset package) file.
- Returns data usable by another method.'''
+ Returns dictionary with section names as the keys, each one
+ contains a list of tuples: (compName, version, compPresetDict)
+ '''
data = {}
try:
with open(filepath, 'r') as f:
def parseLine(line):
- '''Decides if a given avp or avl line is a section header'''
+ '''Decides if a file line is a section header'''
validSections = ('Components')
line = line.strip()
newSection = ''
@@ -313,8 +320,7 @@ class Core():
def createPresetFile(
self, compName, vers, presetName, saveValueStore, filepath=''):
'''Create a preset file (.avl) at filepath using args.
- Or if filepath is empty, create an internal preset using
- the args for the filepath.'''
+ Or if filepath is empty, create an internal preset using args'''
if not filepath:
dirname = os.path.join(self.presetDir, compName, str(vers))
if not os.path.exists(dirname):
diff --git a/main.py b/main.py
index 140392c..e04d002 100644
--- a/main.py
+++ b/main.py
@@ -1,8 +1,6 @@
from PyQt4 import QtGui, uic
import sys
import os
-import atexit
-import signal
import core
import preview_thread
@@ -32,7 +30,7 @@ def LoadDefaultSettings(self):
}
for parm, value in default.items():
- print(parm, self.settings.value(parm))
+ #print(parm, self.settings.value(parm))
if self.settings.value(parm) is None:
self.settings.setValue(parm, value)
@@ -62,6 +60,8 @@ if __name__ == "__main__":
elif mode == 'gui':
from mainwindow import *
+ import atexit
+ import signal
window = uic.loadUi(os.path.join(
os.path.dirname(os.path.realpath(__file__)), "mainwindow.ui"))
@@ -73,10 +73,10 @@ if __name__ == "__main__":
window.resize(window.width() * (dpi / 96), window.height() * (dpi / 96))
# window.verticalLayout_2.setContentsMargins(0, topMargin, 0, 0)
+ signal.signal(signal.SIGINT, main.cleanUp)
+ atexit.register(main.cleanUp)
+
main = MainWindow(window, proj)
# applicable to both modes
- signal.signal(signal.SIGINT, main.cleanUp)
- atexit.register(main.cleanUp)
-
sys.exit(app.exec_())
diff --git a/mainwindow.py b/mainwindow.py
index 2a8762d..6023831 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -597,7 +597,6 @@ class MainWindow(QtCore.QObject):
self.openProject(filename)
def openProject(self, filepath, prompt=True):
- print('opening', filepath)
if not filepath or not os.path.exists(filepath) \
or not filepath.endswith('.avp'):
self.updateWindowTitle()
diff --git a/video_thread.py b/video_thread.py
index 2255259..e6c6531 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -29,7 +29,7 @@ class Worker(QtCore.QObject):
self.modules = parent.core.modules
self.parent = parent
parent.videoTask.connect(self.createVideo)
- self.sampleSize = 1470
+ self.sampleSize = 1470 # 44100 / 30 = 1470
self.canceled = False
self.error = False
self.stopped = False
@@ -99,7 +99,8 @@ class Worker(QtCore.QObject):
# test if user has libfdk_aac
encoders = sp.check_output(
- self.core.FFMPEG_BIN + " -encoders -hide_banner", shell=True)
+ self.core.FFMPEG_BIN + " -encoders -hide_banner",
+ shell=True)
encoders = encoders.decode("utf-8")
@@ -120,15 +121,15 @@ class Worker(QtCore.QObject):
vencoders = options['video-codecs'][vcodec]
aencoders = options['audio-codecs'][acodec]
- print(encoders)
+ #print(encoders)
for encoder in vencoders:
- print(encoder)
+ #print(encoder)
if encoder in encoders:
vencoder = encoder
break
for encoder in aencoders:
- print(encoder)
+ #print(encoder)
if encoder in encoders:
aencoder = encoder
break
@@ -161,16 +162,15 @@ class Worker(QtCore.QObject):
ffmpegCommand.append('-2')
ffmpegCommand.append(outputFile)
- self.out_pipe = sp.Popen(
- ffmpegCommand, stdin=sp.PIPE, stdout=sys.stdout, stderr=sys.stdout)
- # create video for output
+ # ### Now start creating video for output ###
numpy.seterr(divide='ignore')
- # initialize components
- print('loaded components:',
- ["%s%s" % (num, str(component)) for num,
- component in enumerate(self.components)])
+ # Call preFrameRender on all components
+ print('Loaded Components:', ", ".join(
+ ["%s) %s" % (num, str(component)) \
+ for num, component in enumerate(reversed(self.components))
+ ]))
self.staticComponents = {}
numComps = len(self.components)
for compNo, comp in enumerate(self.components):
@@ -190,14 +190,17 @@ class Worker(QtCore.QObject):
comp.frameRender(compNo, 0, 0))
self.progressBarUpdate.emit(100)
+ # Create ffmpeg pipe and queues for frames
+ self.out_pipe = sp.Popen(
+ ffmpegCommand, stdin=sp.PIPE, stdout=sys.stdout, stderr=sys.stdout)
self.compositeQueue = Queue()
self.compositeQueue.maxsize = 20
self.renderQueue = PriorityQueue()
self.renderQueue.maxsize = 20
self.previewQueue = PriorityQueue()
- self.renderThreads = []
# Threads to render frames and send them back here for piping out
+ self.renderThreads = []
for i in range(3):
self.renderThreads.append(
Thread(target=self.renderNode, name="Render Thread"))
--
cgit v1.2.3
From b21a953dda4ec54d494c813af8f687d53d3675d9 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 22 Jun 2017 19:59:31 -0400
Subject: bugfixes
---
command.py | 36 ++++++------------------------------
components/__base__.py | 4 ++++
components/text.py | 4 +++-
components/video.py | 1 +
core.py | 2 +-
main.py | 4 ++--
6 files changed, 17 insertions(+), 34 deletions(-)
diff --git a/command.py b/command.py
index 97eddd2..65fe782 100644
--- a/command.py
+++ b/command.py
@@ -16,13 +16,14 @@ class Command(QtCore.QObject):
QtCore.QObject.__init__(self)
self.core = core.Core()
self.dataDir = self.core.dataDir
+ self.canceled = False
self.parser = argparse.ArgumentParser(
description='Create a visualization for an audio file',
epilog='EXAMPLE COMMAND: main.py myvideotemplate.avp '
'-i ~/Music/song.mp3 -o ~/video.mp4 '
'-c 0 image ~/Pictures/thisWeeksPicture.jpg '
- '-c 1 vis classic')
+ '-c 1 video "preset=My Logo" -c 2 vis classic')
self.parser.add_argument(
'-i', '--input', metavar='SOUND',
help='input audio file', required=True)
@@ -40,35 +41,6 @@ class Command(QtCore.QObject):
'"help" for information about possible args', nargs=3,
action='append')
- '''
- self.parser.add_argument(
- '-b', '--background', dest='bgimage',
- help='background image file', required=True)
- self.parser.add_argument(
- '-t', '--text', dest='text', help='title text', required=True)
- self.parser.add_argument(
- '-f', '--font', dest='font', help='title font', required=False)
- self.parser.add_argument(
- '-s', '--fontsize', dest='fontsize',
- help='title font size', required=False)
- self.parser.add_argument(
- '-c', '--textcolor', dest='textcolor',
- help='title text color in r,g,b format', required=False)
- self.parser.add_argument(
- '-C', '--viscolor', dest='viscolor',
- help='visualization color in r,g,b format', required=False)
- self.parser.add_argument(
- '-x', '--xposition', dest='xposition',
- help='x position', required=False)
- self.parser.add_argument(
- '-y', '--yposition', dest='yposition',
- help='y position', required=False)
- self.parser.add_argument(
- '-a', '--alignment', dest='alignment',
- help='title alignment', required=False,
- type=int, choices=[0, 1, 2])
- '''
-
self.args = self.parser.parse_args()
self.settings = QSettings(
os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat)
@@ -76,6 +48,9 @@ class Command(QtCore.QObject):
if self.args.projpath:
self.core.openProject(self, self.args.projpath)
+ self.core.selectedComponents = list(
+ reversed(self.core.selectedComponents))
+ self.core.componentListChanged()
if self.args.comp:
for comp in self.args.comp:
@@ -111,6 +86,7 @@ class Command(QtCore.QObject):
def videoCreated(self):
self.videoThread.quit()
self.videoThread.wait()
+ quit(0)
def showMessage(self, **kwargs):
print(kwargs['msg'])
diff --git a/components/__base__.py b/components/__base__.py
index e43a517..bdf6fdd 100644
--- a/components/__base__.py
+++ b/components/__base__.py
@@ -124,6 +124,10 @@ class Component(QtCore.QObject):
self.page = page
return page
+ def update(self):
+ super().update()
+ self.parent.drawPreview()
+
def previewRender(self, previewWorker):
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
diff --git a/components/text.py b/components/text.py
index 6c465b1..536a9ba 100644
--- a/components/text.py
+++ b/components/text.py
@@ -19,12 +19,14 @@ class Component(__base__.Component):
def widget(self, parent):
height = int(parent.settings.value('outputHeight'))
width = int(parent.settings.value('outputWidth'))
+
self.parent = parent
self.textColor = (255, 255, 255)
self.title = 'Text'
self.alignment = 1
self.fontSize = height / 13.5
- self.xPosition = width / 2
+ fm = QtGui.QFontMetrics(self.titleFont)
+ self.xPosition = width / 2 - fm.width(self.title)/2
self.yPosition = height / 2 * 1.036
page = uic.loadUi(os.path.join(
diff --git a/components/video.py b/components/video.py
index dd385b4..66c98ce 100644
--- a/components/video.py
+++ b/components/video.py
@@ -227,6 +227,7 @@ class Component(__base__.Component):
if os.path.splitext(arg)[1] in self.core.videoFormats:
self.videoPath = arg
self.scale = 100
+ self.loopVideo = True
return True
else:
print("Not a supported video format")
diff --git a/core.py b/core.py
index 42eb44e..2177071 100644
--- a/core.py
+++ b/core.py
@@ -80,7 +80,7 @@ class Core():
def insertComponent(self, compPos, moduleIndex, loader):
'''Creates a new component'''
- if compPos < 0:
+ if compPos < 0 or compPos > len(self.selectedComponents):
compPos = len(self.selectedComponents)
if len(self.selectedComponents) > 50:
return None
diff --git a/main.py b/main.py
index e04d002..3fd4234 100644
--- a/main.py
+++ b/main.py
@@ -73,10 +73,10 @@ if __name__ == "__main__":
window.resize(window.width() * (dpi / 96), window.height() * (dpi / 96))
# window.verticalLayout_2.setContentsMargins(0, topMargin, 0, 0)
+ main = MainWindow(window, proj)
+
signal.signal(signal.SIGINT, main.cleanUp)
atexit.register(main.cleanUp)
- main = MainWindow(window, proj)
-
# applicable to both modes
sys.exit(app.exec_())
--
cgit v1.2.3
From 49cda1bf3aa1800459d1085496291bec90ae6a5a Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 22 Jun 2017 20:31:04 -0400
Subject: can send multiple arguments to a component
---
command.py | 15 +++++++++------
components/__base__.py | 3 ++-
core.py | 5 ++++-
3 files changed, 15 insertions(+), 8 deletions(-)
diff --git a/command.py b/command.py
index 65fe782..9012ca4 100644
--- a/command.py
+++ b/command.py
@@ -36,10 +36,10 @@ class Command(QtCore.QObject):
'projpath', metavar='path-to-project',
help='open a project file (.avp)', nargs='?')
self.parser.add_argument(
- '-c', '--comp', metavar=('LAYER', 'NAME', 'ARG'),
- help='create component NAME at LAYER.'
- '"help" for information about possible args', nargs=3,
- action='append')
+ '-c', '--comp', metavar=('LAYER', 'ARG'),
+ help='first arg must be component NAME to insert at LAYER.'
+ '"help" for information about possible args for a component.',
+ nargs='*', action='append')
self.args = self.parser.parse_args()
self.settings = QSettings(
@@ -54,7 +54,9 @@ class Command(QtCore.QObject):
if self.args.comp:
for comp in self.args.comp:
- pos, name, arg = comp
+ pos = comp[0]
+ name = comp[1]
+ args = comp[2:]
try:
pos = int(pos)
except ValueError:
@@ -66,7 +68,8 @@ class Command(QtCore.QObject):
quit(1)
modI = self.core.moduleIndexFor(realName)
i = self.core.insertComponent(pos, modI, self)
- self.core.selectedComponents[i].command(arg)
+ for arg in args:
+ self.core.selectedComponents[i].command(arg)
self.createAudioVisualisation()
diff --git a/components/__base__.py b/components/__base__.py
index bdf6fdd..5c8865d 100644
--- a/components/__base__.py
+++ b/components/__base__.py
@@ -71,7 +71,8 @@ class Component(QtCore.QObject):
self.core.openPreset(path, self.compPos, preset)
else:
print(
- 'To open a preset for this component:\n'
+ self.__doc__, 'Usage:\n'
+ 'Open a preset for this component:\n'
' "preset=Preset Name"\n')
self.commandHelp()
quit(0)
diff --git a/core.py b/core.py
index 2177071..ba71b82 100644
--- a/core.py
+++ b/core.py
@@ -160,8 +160,11 @@ class Core():
''' loader is the object calling this method which must have
its own showMessage(**kwargs) method for displaying errors.
'''
+ if not os.path.exists(filepath):
+ loader.showMessage(msg='Project file not found')
+ return
+
errcode, data = self.parseAvFile(filepath)
- #print(data)
if errcode == 0:
try:
for i, tup in enumerate(data['Components']):
--
cgit v1.2.3
From 3c903794e3588560f2b9d342214009d55a675d5a Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 22 Jun 2017 22:23:04 -0400
Subject: more commandline component options
commandline options that existed before the redesign are now back
---
command.py | 15 ++++++++++-----
components/__base__.py | 2 +-
components/color.py | 11 +++++++++++
components/image.py | 14 +++++++-------
components/original.py | 23 +++++++++++++++--------
components/text.py | 28 ++++++++++++++++++++++------
components/video.py | 16 ++++++++--------
7 files changed, 74 insertions(+), 35 deletions(-)
diff --git a/command.py b/command.py
index 9012ca4..1a1e810 100644
--- a/command.py
+++ b/command.py
@@ -2,6 +2,7 @@ from PyQt4 import QtCore
from PyQt4.QtCore import QSettings
import argparse
import os
+import sys
import core
import video_thread
@@ -22,14 +23,14 @@ class Command(QtCore.QObject):
description='Create a visualization for an audio file',
epilog='EXAMPLE COMMAND: main.py myvideotemplate.avp '
'-i ~/Music/song.mp3 -o ~/video.mp4 '
- '-c 0 image ~/Pictures/thisWeeksPicture.jpg '
- '-c 1 video "preset=My Logo" -c 2 vis classic')
+ '-c 0 image path=~/Pictures/thisWeeksPicture.jpg '
+ '-c 1 video "preset=My Logo" -c 2 vis layout=classic')
self.parser.add_argument(
'-i', '--input', metavar='SOUND',
- help='input audio file', required=True)
+ help='input audio file')
self.parser.add_argument(
'-o', '--output', metavar='OUTPUT',
- help='output video file', required=True)
+ help='output video file')
# optional arguments
self.parser.add_argument(
@@ -71,7 +72,11 @@ class Command(QtCore.QObject):
for arg in args:
self.core.selectedComponents[i].command(arg)
- self.createAudioVisualisation()
+ if self.args.input and self.args.output:
+ self.createAudioVisualisation()
+ elif 'help' not in sys.argv:
+ self.parser.print_help()
+ quit(1)
def createAudioVisualisation(self):
self.videoThread = QtCore.QThread(self)
diff --git a/components/__base__.py b/components/__base__.py
index 5c8865d..bef7f0e 100644
--- a/components/__base__.py
+++ b/components/__base__.py
@@ -73,7 +73,7 @@ class Component(QtCore.QObject):
print(
self.__doc__, 'Usage:\n'
'Open a preset for this component:\n'
- ' "preset=Preset Name"\n')
+ ' "preset=Preset Name"')
self.commandHelp()
quit(0)
diff --git a/components/color.py b/components/color.py
index cb75839..5ffcdea 100644
--- a/components/color.py
+++ b/components/color.py
@@ -233,3 +233,14 @@ class Component(__base__.Component):
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')
+
+ def command(self, arg):
+ if not arg.startswith('preset=') and '=' in arg:
+ key, arg = arg.split('=', 1)
+ if key == 'color':
+ self.page.lineEdit_color1.setText(arg)
+ return
+ super().command(arg)
diff --git a/components/image.py b/components/image.py
index d0e1894..f8ae64e 100644
--- a/components/image.py
+++ b/components/image.py
@@ -94,18 +94,18 @@ class Component(__base__.Component):
self.update()
def command(self, arg):
- if not arg.startswith('preset='):
- if os.path.exists(arg):
+ if not arg.startswith('preset=') and '=' in arg:
+ key, arg = arg.split('=', 1)
+ if key == 'path' and os.path.exists(arg):
try:
Image.open(arg)
- self.imagePath = arg
- self.stretched = True
- return True
+ self.page.lineEdit_image.setText(arg)
+ self.page.checkBox_stretch.setChecked(True)
+ return
except OSError as e:
print("Not a supported image format")
quit(1)
super().command(arg)
def commandHelp(self):
- print('Give a complete filepath to an image to load that '
- 'image with default settings.')
+ print('Load an image:\n path=/filepath/to/image.png')
diff --git a/components/original.py b/components/original.py
index 328d64f..6222157 100644
--- a/components/original.py
+++ b/components/original.py
@@ -184,14 +184,21 @@ class Component(__base__.Component):
return im
def command(self, arg):
- if not arg.startswith('preset='):
- if arg == 'classic':
- self.layout = 0; return
- elif arg == 'split':
- self.layout = 1; return
- elif arg == 'bottom':
- self.layout = 2; return
+ if not arg.startswith('preset=') and '=' in arg:
+ key, arg = arg.split('=', 1)
+ if key == 'color':
+ self.page.lineEdit_visColor.setText(arg)
+ return
+ elif key == 'layout':
+ if arg == 'classic':
+ self.page.comboBox_visLayout.setCurrentIndex(0)
+ elif arg == 'split':
+ self.page.comboBox_visLayout.setCurrentIndex(1)
+ elif arg == 'bottom':
+ self.page.comboBox_visLayout.setCurrentIndex(2)
+ return
super().command(arg)
def commandHelp(self):
- print('Give a layout name: classic, split, or bottom')
+ print('Give a layout name:\n layout=[classic/split/bottom]')
+ print('Specify a color:\n color=255,255,255')
diff --git a/components/text.py b/components/text.py
index 536a9ba..2375dcd 100644
--- a/components/text.py
+++ b/components/text.py
@@ -149,12 +149,28 @@ class Component(__base__.Component):
self.page.lineEdit_textColor.setText(RGBstring)
self.page.pushButton_textColor.setStyleSheet(btnStyle)
- def commandHelp(self, arg):
- print('Enter a string to use as centred white text. '
- 'Use quotes around the string to escape spaces.')
+ def commandHelp(self):
+ print('Enter a string to use as centred white text:')
+ print(' "title=User Error"')
+ print('Specify a text color:\n color=255,255,255')
+ print('Set custom x, y position:\n x=500 y=500')
def command(self, arg):
- if not arg.startswith('preset='):
- self.title = arg
- return True
+ if not arg.startswith('preset=') and '=' in arg:
+ key, arg = arg.split('=', 1)
+ if key == 'color':
+ self.page.lineEdit_textColor.setText(arg)
+ return
+ elif key == 'size':
+ self.page.spinBox_fontSize.setValue(int(arg))
+ return
+ elif key == 'x':
+ self.page.spinBox_xTextAlign.setValue(int(arg))
+ return
+ elif key == 'y':
+ self.page.spinBox_yTextAlign.setValue(int(arg))
+ return
+ elif key == 'title':
+ self.page.lineEdit_title.setText(arg)
+ return
super().command(arg)
diff --git a/components/video.py b/components/video.py
index 66c98ce..1d250bd 100644
--- a/components/video.py
+++ b/components/video.py
@@ -222,21 +222,21 @@ class Component(__base__.Component):
self.chunkSize = 4*width*height
def command(self, arg):
- if not arg.startswith('preset='):
- if os.path.exists(arg):
+ if not arg.startswith('preset=') and '=' in arg:
+ key, arg = arg.split('=', 1)
+ if key == 'path' and os.path.exists(arg):
if os.path.splitext(arg)[1] in self.core.videoFormats:
- self.videoPath = arg
- self.scale = 100
- self.loopVideo = True
- return True
+ self.page.lineEdit_video.setText(arg)
+ self.page.spinBox_scale.setValue(100)
+ self.page.checkBox_loop.setChecked(True)
+ return
else:
print("Not a supported video format")
quit(1)
super().command(arg)
def commandHelp(self):
- print('Give a complete filepath to a video to load that '
- 'video with default settings.')
+ print('Load a video:\n path=/filepath/to/video.mp4')
def scale(scale, width, height, returntype=None):
width = (float(width) / 100.0) * float(scale)
--
cgit v1.2.3
From 60d62599f785167f4c976a92fd8f19fbee4a363d Mon Sep 17 00:00:00 2001
From: DH4
Date: Fri, 23 Jun 2017 01:20:59 -0500
Subject: Implement Hotkeys
---
mainwindow.py | 39 ++++++++++++++++++++++++++++++++++++---
1 file changed, 36 insertions(+), 3 deletions(-)
diff --git a/mainwindow.py b/mainwindow.py
index f722158..e1553f6 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -1,7 +1,7 @@
from queue import Queue
from PyQt4 import QtCore, QtGui, uic
from PyQt4.QtCore import QSettings, Qt
-from PyQt4.QtGui import QMenu
+from PyQt4.QtGui import QMenu, QShortcut
import sys
import os
import signal
@@ -39,14 +39,14 @@ class PreviewWindow(QtGui.QLabel):
self.repaint()
-class MainWindow(QtCore.QObject):
+class MainWindow(QtGui.QMainWindow):
newTask = QtCore.pyqtSignal(list)
processTask = QtCore.pyqtSignal()
videoTask = QtCore.pyqtSignal(str, str, list)
def __init__(self, window):
- QtCore.QObject.__init__(self)
+ QtGui.QMainWindow.__init__(self)
# print('main thread id: {}'.format(QtCore.QThread.currentThreadId()))
self.window = window
@@ -230,6 +230,29 @@ class MainWindow(QtCore.QObject):
self.openProject(self.currentProject, prompt=False)
self.drawPreview()
+ # Setup Hotkeys
+ QtGui.QShortcut("Ctrl+S", self.window, self.saveCurrentProject)
+ QtGui.QShortcut("Ctrl+A", self.window, self.openSaveProjectDialog)
+ QtGui.QShortcut("Ctrl+O", self.window, self.openOpenProjectDialog)
+ QtGui.QShortcut("Ctrl+N", self.window, self.createNewProject)
+
+ QtGui.QShortcut("Ctrl+T", self.window, activated=lambda:
+ self.window.pushButton_addComponent.click())
+ QtGui.QShortcut("Ctrl+Space", self.window, activated=lambda:
+ self.window.listWidget_componentList.setFocus())
+ QtGui.QShortcut("Ctrl+Shift+S", self.window,
+ self.presetManager.openSavePresetDialog)
+ QtGui.QShortcut("Ctrl+Shift+C", self.window,
+ self.presetManager.clearPreset)
+
+ QtGui.QShortcut("Ctrl+Up", self.window,
+ activated=lambda: self.moveComponent(-1))
+ QtGui.QShortcut("Ctrl+Down", self.window,
+ activated=lambda: self.moveComponent(1))
+ QtGui.QShortcut("Ctrl+Home", self.window, self.moveComponentTop)
+ QtGui.QShortcut("Ctrl+End", self.window, self.moveComponentBottom)
+ QtGui.QShortcut("Ctrl+r", self.window, self.removeComponent)
+
def cleanUp(self):
self.timer.stop()
self.previewThread.quit()
@@ -498,6 +521,16 @@ class MainWindow(QtCore.QObject):
stackedWidget.setCurrentIndex(newRow)
self.drawPreview()
+ def moveComponentTop(self):
+ componentList = self.window.listWidget_componentList
+ row = -componentList.currentRow()
+ self.moveComponent(row)
+
+ def moveComponentBottom(self):
+ componentList = self.window.listWidget_componentList
+ row = len(componentList)-1
+ self.moveComponent(row)
+
def dragComponent(self, event):
'''Drop event for the component listwidget'''
componentList = self.window.listWidget_componentList
--
cgit v1.2.3
From 8c9914850e9987d4f05e8b88dedb058ffbb4f53f Mon Sep 17 00:00:00 2001
From: DH4
Date: Fri, 23 Jun 2017 02:39:56 -0500
Subject: cx_freeze Path Updates
---
core.py | 8 ++++++-
main.py | 11 ++++++++--
mainwindow.py | 6 ++----
preview_thread.py | 2 +-
setup.py | 63 ++++++++++++++++++++++++++++++++++++-------------------
video_thread.py | 2 +-
6 files changed, 62 insertions(+), 30 deletions(-)
diff --git a/core.py b/core.py
index e4a7a6c..e5a9b70 100644
--- a/core.py
+++ b/core.py
@@ -22,7 +22,13 @@ class Core():
self.dataDir = QDesktopServices.storageLocation(
QDesktopServices.DataLocation)
self.presetDir = os.path.join(self.dataDir, 'presets')
- self.wd = os.path.dirname(os.path.realpath(__file__))
+ if getattr(sys, 'frozen', False):
+ # frozen
+ self.wd = os.path.dirname(sys.executable)
+ else:
+ # unfrozen
+ self.wd = os.path.dirname(os.path.realpath(__file__))
+
self.loadEncoderOptions()
self.videoFormats = Core.appendUppercase([
'*.mp4',
diff --git a/main.py b/main.py
index 7c0727b..08add50 100644
--- a/main.py
+++ b/main.py
@@ -52,8 +52,15 @@ if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
app.setApplicationName("audio-visualizer")
app.setOrganizationName("audio-visualizer")
- window = uic.loadUi(os.path.join(
- os.path.dirname(os.path.realpath(__file__)), "mainwindow.ui"))
+
+ if getattr(sys, 'frozen', False):
+ # frozen
+ wd = os.path.dirname(sys.executable)
+ else:
+ # unfrozen
+ wd = os.path.dirname(os.path.realpath(__file__))
+
+ window = uic.loadUi(os.path.join(wd, "mainwindow.ui"))
# window.adjustSize()
desc = QtGui.QDesktopWidget()
dpi = desc.physicalDpiX()
diff --git a/mainwindow.py b/mainwindow.py
index e1553f6..d21ca49 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -63,9 +63,7 @@ class MainWindow(QtGui.QMainWindow):
LoadDefaultSettings(self)
self.presetManager = PresetManager(
uic.loadUi(
- os.path.join(os.path.dirname(os.path.realpath(__file__)),
- 'presetmanager.ui')),
- self)
+ os.path.join(self.core.wd, 'presetmanager.ui')), self)
if not os.path.exists(self.dataDir):
os.makedirs(self.dataDir)
@@ -143,7 +141,7 @@ class MainWindow(QtGui.QMainWindow):
window.spinBox_aBitrate.valueChanged.connect(self.updateCodecSettings)
self.previewWindow = PreviewWindow(self, os.path.join(
- os.path.dirname(os.path.realpath(__file__)), "background.png"))
+ self.core.wd, "background.png"))
window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
# Make component buttons
diff --git a/preview_thread.py b/preview_thread.py
index e3e8279..eabf715 100644
--- a/preview_thread.py
+++ b/preview_thread.py
@@ -23,7 +23,7 @@ class Worker(QtCore.QObject):
self.stackedWidget = parent.window.stackedWidget
self.background = Image.new("RGBA", (1920, 1080), (0, 0, 0, 0))
self.background.paste(Image.open(os.path.join(
- os.path.dirname(os.path.realpath(__file__)), "background.png")))
+ self.core.wd, "background.png")))
@pyqtSlot(str, list)
def createPreviewImage(self, components):
diff --git a/setup.py b/setup.py
index 0d9cbc4..48034dc 100644
--- a/setup.py
+++ b/setup.py
@@ -1,30 +1,51 @@
from cx_Freeze import setup, Executable
+import sys
# Dependencies are automatically detected, but it might need
# fine tuning.
-buildOptions = dict(packages = [], excludes = [
- "apport",
- "apt",
- "ctypes",
- "curses",
- "distutils",
- "email",
- "html",
- "http",
- "json",
- "xmlrpc",
- "nose"
- ], include_files = ["main.ui"])
-import sys
-base = 'Win32GUI' if sys.platform=='win32' else None
+buildOptions = dict(
+ packages=[],
+ excludes=[
+ "apport",
+ "apt",
+ "curses",
+ "distutils",
+ "email",
+ "html",
+ "http",
+ "xmlrpc",
+ "nose"
+ ],
+ include_files=[
+ "mainwindow.ui",
+ "presetmanager.ui",
+ "background.png",
+ "encoder-options.json",
+ "components/"
+ ],
+ includes=[
+ 'numpy.core._methods',
+ 'numpy.lib.format'
+ ]
+)
+
+
+base = 'Win32GUI' if sys.platform == 'win32' else None
executables = [
- Executable('main.py', base=base, targetName = 'audio-visualizer-python')
+ Executable(
+ 'main.py',
+ base=base,
+ targetName='audio-visualizer-python'
+ )
]
-setup(name='audio-visualizer-python',
- version = '1.0',
- description = 'a little GUI tool to render visualization videos of audio files',
- options = dict(build_exe = buildOptions),
- executables = executables)
+
+setup(
+ name='audio-visualizer-python',
+ version='1.0',
+ description='GUI tool to render visualization videos of audio files',
+ options=dict(build_exe=buildOptions),
+ executables=executables
+)
diff --git a/video_thread.py b/video_thread.py
index d7220f1..9740641 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -69,7 +69,7 @@ class Worker(QtCore.QObject):
def previewDispatch(self):
background = Image.new("RGBA", (1920, 1080), (0, 0, 0, 0))
background.paste(Image.open(os.path.join(
- os.path.dirname(os.path.realpath(__file__)), "background.png")))
+ self.core.wd, "background.png")))
background = background.resize((self.width, self.height))
while not self.stopped:
--
cgit v1.2.3
From 407ba57e4eff4b7dfc760ab0cd390a4555294f24 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Fri, 23 Jun 2017 07:41:11 -0400
Subject: fixed crash after creating a project on commandline
because it didn't check for errors while saving
---
mainwindow.py | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/mainwindow.py b/mainwindow.py
index f3d45e6..e86abe6 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -212,6 +212,8 @@ class MainWindow(QtGui.QMainWindow):
window.show()
if project and project != self.autosavePath:
+ if not project.endswith('.avp'):
+ project += '.avp'
# open a project from the commandline
if not os.path.dirname(project):
project = os.path.join(os.path.expanduser('~'), project)
@@ -350,8 +352,15 @@ class MainWindow(QtGui.QMainWindow):
return False
def saveProjectChanges(self):
- os.remove(self.currentProject)
- os.rename(self.autosavePath, self.currentProject)
+ try:
+ os.remove(self.currentProject)
+ os.rename(self.autosavePath, self.currentProject)
+ return True
+ except (FileNotFoundError, IsADirectoryError) as e:
+ self.showMessage(
+ msg='Project file couldn\'t be saved.',
+ detail=str(e))
+ return False
def openInputFileDialog(self):
inputDir = self.settings.value("inputDir", os.path.expanduser("~"))
@@ -592,6 +601,7 @@ class MainWindow(QtGui.QMainWindow):
self.openSaveProjectDialog()
def openSaveChangesDialog(self, phrase):
+ success = True
if self.autosaveExists(identical=False):
ch = self.showMessage(
msg="You have unsaved changes in project '%s'. "
@@ -600,9 +610,9 @@ class MainWindow(QtGui.QMainWindow):
phrase),
showCancel=True)
if ch:
- self.saveProjectChanges()
+ success = self.saveProjectChanges()
- if os.path.exists(self.autosavePath):
+ if success and os.path.exists(self.autosavePath):
os.remove(self.autosavePath)
def openSaveProjectDialog(self):
--
cgit v1.2.3
From 84ceff7f5490ac5f7e1256a16d82b3b8520cb03a Mon Sep 17 00:00:00 2001
From: DH4
Date: Fri, 23 Jun 2017 10:46:32 -0500
Subject: Fixed Ctrl+End Hotkey
---
mainwindow.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mainwindow.py b/mainwindow.py
index d21ca49..778e79a 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -526,7 +526,7 @@ class MainWindow(QtGui.QMainWindow):
def moveComponentBottom(self):
componentList = self.window.listWidget_componentList
- row = len(componentList)-1
+ row = len(componentList)-componentList.currentRow()-1
self.moveComponent(row)
def dragComponent(self, event):
--
cgit v1.2.3
From 68ac0cf755c6c3dbcef4abbb934cd1ead2d713c5 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Fri, 23 Jun 2017 17:14:39 -0400
Subject: pep8 cleanup
---
components/__base__.py | 11 ++++++-----
components/color.py | 18 ++++++++++-------
components/video.py | 12 +++++++-----
core.py | 33 +++++++++++++++++++-------------
main.py | 7 +++++--
presetmanager.py | 52 +++++++++++++++++++++++++++++++-------------------
video_thread.py | 11 ++++-------
7 files changed, 85 insertions(+), 59 deletions(-)
diff --git a/components/__base__.py b/components/__base__.py
index bef7f0e..a29d775 100644
--- a/components/__base__.py
+++ b/components/__base__.py
@@ -39,7 +39,7 @@ class Component(QtCore.QObject):
then update self.page widgets using the preset dict.
'''
self.currentPreset = presetName \
- if presetName != None else presetDict['preset']
+ if presetName is not None else presetDict['preset']
def preFrameRender(self, **kwargs):
'''Triggered only before a video is exported (video_thread.py)
@@ -66,8 +66,9 @@ class Component(QtCore.QObject):
print('Couldn\'t locate preset "%s"' % preset)
quit(1)
else:
- print('Opening "%s" preset on layer %s' % \
- (preset, self.compPos))
+ print('Opening "%s" preset on layer %s' % (
+ preset, self.compPos)
+ )
self.core.openPreset(path, self.compPos, preset)
else:
print(
@@ -142,10 +143,10 @@ class Component(QtCore.QObject):
return image
'''
+
class BadComponentInit(Exception):
def __init__(self, arg, name):
- string = \
-'''################################
+ string = '''################################
Mandatory argument "%s" not specified
in %s instance initialization
###################################'''
diff --git a/components/color.py b/components/color.py
index 5ffcdea..3b73458 100644
--- a/components/color.py
+++ b/components/color.py
@@ -53,7 +53,7 @@ class Component(__base__.Component):
page.spinBox_height.valueChanged.connect(self.update)
page.checkBox_trans.stateChanged.connect(self.update)
- self.fillLabels = [ \
+ self.fillLabels = [
'Solid',
'Linear Gradient',
'Radial Gradient',
@@ -126,8 +126,8 @@ class Component(__base__.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 Image.new("RGBA", (width, height), (r, g, b, 255))
frame = self.blankFrame(width, height)
@@ -143,9 +143,11 @@ class Component(__base__.Component):
image = ImageQt(frame)
painter = QtGui.QPainter(image)
if self.stretch:
- w = width; h = height
+ w = width
+ h = height
else:
- w = self.sizeWidth; h = self.sizeWidth
+ w = self.sizeWidth
+ h = self.sizeWidth
if self.fillType == 1: # Linear Gradient
brush = QtGui.QLinearGradient(
@@ -170,8 +172,10 @@ class Component(__base__.Component):
else:
brush.setColorAt(1.0, QColor(*self.color2))
painter.setBrush(brush)
- painter.drawRect(self.x, self.y,
- self.sizeWidth, self.sizeHeight)
+ painter.drawRect(
+ self.x, self.y,
+ self.sizeWidth, self.sizeHeight
+ )
painter.end()
imBytes = image.bits().asstring(image.numBytes())
return Image.frombytes('RGBA', (width, height), imBytes)
diff --git a/components/video.py b/components/video.py
index 1d250bd..4fced4e 100644
--- a/components/video.py
+++ b/components/video.py
@@ -41,8 +41,8 @@ class Video:
'-i', self.videoPath,
'-f', 'image2pipe',
'-pix_fmt', 'rgba',
- '-filter:v', 'scale=%s:%s' %
- scale(self.scale, self.width, self.height, str),
+ '-filter:v', 'scale=%s:%s' % scale(
+ self.scale, self.width, self.height, str),
'-vcodec', 'rawvideo', '-',
]
@@ -199,8 +199,8 @@ class Component(__base__.Component):
'-i', self.videoPath,
'-f', 'image2pipe',
'-pix_fmt', 'rgba',
- '-filter:v', 'scale=%s:%s' %
- scale(self.scale, width, height, str),
+ '-filter:v', 'scale=%s:%s' % scale(
+ self.scale, width, height, str),
'-vcodec', 'rawvideo', '-',
'-ss', '90',
'-vframes', '1',
@@ -238,6 +238,7 @@ class Component(__base__.Component):
def commandHelp(self):
print('Load a video:\n path=/filepath/to/video.mp4')
+
def scale(scale, width, height, returntype=None):
width = (float(width) / 100.0) * float(scale)
height = (float(height) / 100.0) * float(scale)
@@ -248,6 +249,7 @@ def scale(scale, width, height, returntype=None):
else:
return (width, height)
+
def finalizeFrame(self, imageData, width, height):
if self.distort:
try:
@@ -265,7 +267,7 @@ def finalizeFrame(self, imageData, width, height):
imageData)
if self.scale != 100 \
- or self.xPosition != 0 or self.yPosition != 0:
+ or self.xPosition != 0 or self.yPosition != 0:
frame = self.blankFrame(width, height)
frame.paste(image, box=(self.xPosition, self.yPosition))
else:
diff --git a/core.py b/core.py
index de6ed99..b51cf98 100644
--- a/core.py
+++ b/core.py
@@ -178,7 +178,8 @@ class Core():
clearThis = False
# add loaded named presets to savedPresets dict
- if 'preset' in preset and preset['preset'] != None:
+ if 'preset' in preset and \
+ preset['preset'] is not None:
nam = preset['preset']
filepath2 = os.path.join(
self.presetDir, name, str(vers), nam)
@@ -194,12 +195,13 @@ class Core():
-1,
self.moduleIndexFor(name),
loader)
- if i == None:
+ if i is None:
loader.showMessage(msg="Too many components!")
break
try:
- if 'preset' in preset and preset['preset'] != None:
+ if 'preset' in preset and \
+ preset['preset'] is not None:
self.selectedComponents[i].loadPreset(
preset
)
@@ -209,8 +211,9 @@ class Core():
preset['preset']
)
except KeyError as e:
- print('%s missing value %s' %
- (self.selectedComponents[i], e))
+ print('%s missing value %s' % (
+ self.selectedComponents[i], e)
+ )
if clearThis:
self.clearPreset(i)
@@ -220,7 +223,6 @@ class Core():
errcode = 1
data = sys.exc_info()
-
if errcode == 1:
typ, value, _ = data
if typ.__name__ == KeyError:
@@ -273,11 +275,11 @@ class Core():
i += 1
elif i == 2:
lastCompPreset = Core.presetFromString(line)
- data[section].append(
- (lastCompName,
+ data[section].append((
+ lastCompName,
lastCompVers,
- lastCompPreset)
- )
+ lastCompPreset
+ ))
i = 0
return 0, data
except:
@@ -308,7 +310,9 @@ class Core():
return False, ''
def exportPreset(self, exportPath, compName, vers, origName):
- internalPath = os.path.join(self.presetDir, compName, str(vers), origName)
+ internalPath = os.path.join(
+ self.presetDir, compName, str(vers), origName
+ )
if not os.path.exists(internalPath):
return
if os.path.exists(exportPath):
@@ -327,7 +331,8 @@ class Core():
return False
def createPresetFile(
- self, compName, vers, presetName, saveValueStore, filepath=''):
+ self, compName, vers, presetName,
+ saveValueStore, filepath=''):
'''Create a preset file (.avl) at filepath using args.
Or if filepath is empty, create an internal preset using args'''
if not filepath:
@@ -462,7 +467,9 @@ class Core():
@staticmethod
def presetToString(dictionary):
'''Alphabetizes a dict into OrderedDict & returns string repr'''
- return repr(OrderedDict(sorted(dictionary.items(), key=lambda t: t[0])))
+ return repr(
+ OrderedDict(sorted(dictionary.items(), key=lambda t: t[0]))
+ )
@staticmethod
def presetFromString(string):
diff --git a/main.py b/main.py
index 106bd29..715e433 100644
--- a/main.py
+++ b/main.py
@@ -30,7 +30,6 @@ def LoadDefaultSettings(self):
}
for parm, value in default.items():
- #print(parm, self.settings.value(parm))
if self.settings.value(parm) is None:
self.settings.setValue(parm, value)
@@ -76,7 +75,11 @@ if __name__ == "__main__":
dpi = desc.physicalDpiX()
topMargin = 0 if (dpi == 96) else int(10 * (dpi / 96))
- window.resize(window.width() * (dpi / 96), window.height() * (dpi / 96))
+ window.resize(
+ window.width() *
+ (dpi / 96), window.height() *
+ (dpi / 96)
+ )
# window.verticalLayout_2.setContentsMargins(0, topMargin, 0, 0)
main = MainWindow(window, proj)
diff --git a/presetmanager.py b/presetmanager.py
index 3b02714..1720b5c 100644
--- a/presetmanager.py
+++ b/presetmanager.py
@@ -21,22 +21,33 @@ class PresetManager(QtGui.QDialog):
# window
self.lastFilter = '*'
- self.presetRows = [] # list of (comp, vers, name) tuples
+ self.presetRows = [] # list of (comp, vers, name) tuples
self.window = window
self.window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
# connect button signals
- self.window.pushButton_delete.clicked.connect(self.openDeletePresetDialog)
- self.window.pushButton_rename.clicked.connect(self.openRenamePresetDialog)
- self.window.pushButton_import.clicked.connect(self.openImportDialog)
- self.window.pushButton_export.clicked.connect(self.openExportDialog)
- self.window.pushButton_close.clicked.connect(self.window.close)
+ self.window.pushButton_delete.clicked.connect(
+ self.openDeletePresetDialog
+ )
+ self.window.pushButton_rename.clicked.connect(
+ self.openRenamePresetDialog
+ )
+ self.window.pushButton_import.clicked.connect(
+ self.openImportDialog
+ )
+ self.window.pushButton_export.clicked.connect(
+ self.openExportDialog
+ )
+ self.window.pushButton_close.clicked.connect(
+ self.window.close
+ )
# create filter box and preset list
self.drawFilterList()
self.window.comboBox_filter.currentIndexChanged.connect(
lambda: self.drawPresetList(
- self.window.comboBox_filter.currentText(), self.window.lineEdit_search.text()
+ self.window.comboBox_filter.currentText(),
+ self.window.lineEdit_search.text()
)
)
@@ -47,7 +58,8 @@ class PresetManager(QtGui.QDialog):
self.window.lineEdit_search.setCompleter(completer)
self.window.lineEdit_search.textChanged.connect(
lambda: self.drawPresetList(
- self.window.comboBox_filter.currentText(), self.window.lineEdit_search.text()
+ self.window.comboBox_filter.currentText(),
+ self.window.lineEdit_search.text()
)
)
self.drawPresetList('*')
@@ -72,16 +84,14 @@ class PresetManager(QtGui.QDialog):
parseList.append((compName, int(compVers), preset))
except ValueError:
continue
- self.presets =\
- {
- compName : \
- [
- (vers, preset) \
- for name, vers, preset in parseList \
- if name == compName \
- ] \
- for compName, _, __ in parseList \
- }
+ self.presets = {
+ compName: [
+ (vers, preset)
+ for name, vers, preset in parseList
+ if name == compName
+ ]
+ for compName, _, __ in parseList
+ }
def drawPresetList(self, compFilter=None, presetFilter=''):
self.window.listWidget_presets.clear()
@@ -96,7 +106,9 @@ class PresetManager(QtGui.QDialog):
continue
for vers, preset in presets:
if not presetFilter or presetFilter in preset:
- self.window.listWidget_presets.addItem('%s: %s' % (component, preset))
+ self.window.listWidget_presets.addItem(
+ '%s: %s' % (component, preset)
+ )
self.presetRows.append((component, vers, preset))
if preset not in presetNames:
presetNames.append(preset)
@@ -149,7 +161,7 @@ class PresetManager(QtGui.QDialog):
break
def createNewPreset(
- self, compName, vers, filename, saveValueStore, **kwargs):
+ self, compName, vers, filename, saveValueStore, **kwargs):
path = os.path.join(self.presetDir, compName, str(vers), filename)
if self.presetExists(path, **kwargs):
return
diff --git a/video_thread.py b/video_thread.py
index 265feee..8266083 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -121,15 +121,12 @@ class Worker(QtCore.QObject):
vencoders = options['video-codecs'][vcodec]
aencoders = options['audio-codecs'][acodec]
- #print(encoders)
for encoder in vencoders:
- #print(encoder)
if encoder in encoders:
vencoder = encoder
break
for encoder in aencoders:
- #print(encoder)
if encoder in encoders:
aencoder = encoder
break
@@ -167,10 +164,10 @@ class Worker(QtCore.QObject):
numpy.seterr(divide='ignore')
# Call preFrameRender on all components
- print('Loaded Components:', ", ".join(
- ["%s) %s" % (num, str(component)) \
- for num, component in enumerate(reversed(self.components))
- ]))
+ print('Loaded Components:', ", ".join([
+ "%s) %s" % (num, str(component))
+ for num, component in enumerate(reversed(self.components))
+ ]))
self.staticComponents = {}
numComps = len(self.components)
for compNo, comp in enumerate(self.components):
--
cgit v1.2.3
From e92e9d79f95ad67e83074ef318278c3486601eac Mon Sep 17 00:00:00 2001
From: DH4
Date: Fri, 23 Jun 2017 17:38:05 -0500
Subject: QT5 Conversion + Directory Structure
---
background.png | Bin 45367 -> 0 bytes
command.py | 126 -------
components/__base__.py | 153 ---------
components/__init__.py | 1 -
components/color.py | 246 --------------
components/color.ui | 660 ------------------------------------
components/image.py | 111 -------
components/image.ui | 259 ---------------
components/original.py | 204 ------------
components/original.ui | 108 ------
components/text.py | 176 ----------
components/text.ui | 316 ------------------
components/video.py | 273 ---------------
components/video.ui | 266 ---------------
core.py | 476 --------------------------
encoder-options.json | 130 --------
freeze.py | 51 +++
main.py | 88 -----
mainwindow.py | 721 ----------------------------------------
mainwindow.ui | 809 ---------------------------------------------
presetmanager.py | 290 ----------------
presetmanager.ui | 150 ---------
preview_thread.py | 59 ----
setup.py | 70 ++--
src/background.png | Bin 0 -> 45367 bytes
src/command.py | 126 +++++++
src/components/__base__.py | 153 +++++++++
src/components/__init__.py | 1 +
src/components/color.py | 246 ++++++++++++++
src/components/color.ui | 660 ++++++++++++++++++++++++++++++++++++
src/components/image.py | 111 +++++++
src/components/image.ui | 259 +++++++++++++++
src/components/original.py | 204 ++++++++++++
src/components/original.ui | 108 ++++++
src/components/text.py | 176 ++++++++++
src/components/text.ui | 316 ++++++++++++++++++
src/components/video.py | 273 +++++++++++++++
src/components/video.ui | 266 +++++++++++++++
src/core.py | 477 ++++++++++++++++++++++++++
src/encoder-options.json | 130 ++++++++
src/main.py | 88 +++++
src/mainwindow.py | 718 ++++++++++++++++++++++++++++++++++++++++
src/mainwindow.ui | 809 +++++++++++++++++++++++++++++++++++++++++++++
src/presetmanager.py | 290 ++++++++++++++++
src/presetmanager.ui | 150 +++++++++
src/preview_thread.py | 59 ++++
src/video_thread.py | 309 +++++++++++++++++
video_thread.py | 309 -----------------
48 files changed, 5999 insertions(+), 5982 deletions(-)
delete mode 100644 background.png
delete mode 100644 command.py
delete mode 100644 components/__base__.py
delete mode 100644 components/__init__.py
delete mode 100644 components/color.py
delete mode 100644 components/color.ui
delete mode 100644 components/image.py
delete mode 100644 components/image.ui
delete mode 100644 components/original.py
delete mode 100644 components/original.ui
delete mode 100644 components/text.py
delete mode 100644 components/text.ui
delete mode 100644 components/video.py
delete mode 100644 components/video.ui
delete mode 100644 core.py
delete mode 100644 encoder-options.json
create mode 100644 freeze.py
delete mode 100644 main.py
delete mode 100644 mainwindow.py
delete mode 100644 mainwindow.ui
delete mode 100644 presetmanager.py
delete mode 100644 presetmanager.ui
delete mode 100644 preview_thread.py
create mode 100644 src/background.png
create mode 100644 src/command.py
create mode 100644 src/components/__base__.py
create mode 100644 src/components/__init__.py
create mode 100644 src/components/color.py
create mode 100644 src/components/color.ui
create mode 100644 src/components/image.py
create mode 100644 src/components/image.ui
create mode 100644 src/components/original.py
create mode 100644 src/components/original.ui
create mode 100644 src/components/text.py
create mode 100644 src/components/text.ui
create mode 100644 src/components/video.py
create mode 100644 src/components/video.ui
create mode 100644 src/core.py
create mode 100644 src/encoder-options.json
create mode 100644 src/main.py
create mode 100644 src/mainwindow.py
create mode 100644 src/mainwindow.ui
create mode 100644 src/presetmanager.py
create mode 100644 src/presetmanager.ui
create mode 100644 src/preview_thread.py
create mode 100644 src/video_thread.py
delete mode 100644 video_thread.py
diff --git a/background.png b/background.png
deleted file mode 100644
index fb58593..0000000
Binary files a/background.png and /dev/null differ
diff --git a/command.py b/command.py
deleted file mode 100644
index 1a1e810..0000000
--- a/command.py
+++ /dev/null
@@ -1,126 +0,0 @@
-from PyQt4 import QtCore
-from PyQt4.QtCore import QSettings
-import argparse
-import os
-import sys
-
-import core
-import video_thread
-from main import LoadDefaultSettings
-
-
-class Command(QtCore.QObject):
-
- videoTask = QtCore.pyqtSignal(str, str, list)
-
- def __init__(self):
- QtCore.QObject.__init__(self)
- self.core = core.Core()
- self.dataDir = self.core.dataDir
- self.canceled = False
-
- self.parser = argparse.ArgumentParser(
- description='Create a visualization for an audio file',
- epilog='EXAMPLE COMMAND: main.py myvideotemplate.avp '
- '-i ~/Music/song.mp3 -o ~/video.mp4 '
- '-c 0 image path=~/Pictures/thisWeeksPicture.jpg '
- '-c 1 video "preset=My Logo" -c 2 vis layout=classic')
- self.parser.add_argument(
- '-i', '--input', metavar='SOUND',
- help='input audio file')
- self.parser.add_argument(
- '-o', '--output', metavar='OUTPUT',
- help='output video file')
-
- # optional arguments
- self.parser.add_argument(
- 'projpath', metavar='path-to-project',
- help='open a project file (.avp)', nargs='?')
- self.parser.add_argument(
- '-c', '--comp', metavar=('LAYER', 'ARG'),
- help='first arg must be component NAME to insert at LAYER.'
- '"help" for information about possible args for a component.',
- nargs='*', action='append')
-
- self.args = self.parser.parse_args()
- self.settings = QSettings(
- os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat)
- LoadDefaultSettings(self)
-
- if self.args.projpath:
- self.core.openProject(self, self.args.projpath)
- self.core.selectedComponents = list(
- reversed(self.core.selectedComponents))
- self.core.componentListChanged()
-
- if self.args.comp:
- for comp in self.args.comp:
- pos = comp[0]
- name = comp[1]
- args = comp[2:]
- try:
- pos = int(pos)
- except ValueError:
- print(pos, 'is not a layer number.')
- quit(1)
- realName = self.parseCompName(name)
- if not realName:
- print(name, 'is not a valid component name.')
- quit(1)
- modI = self.core.moduleIndexFor(realName)
- i = self.core.insertComponent(pos, modI, self)
- for arg in args:
- self.core.selectedComponents[i].command(arg)
-
- if self.args.input and self.args.output:
- self.createAudioVisualisation()
- elif 'help' not in sys.argv:
- self.parser.print_help()
- quit(1)
-
- def createAudioVisualisation(self):
- self.videoThread = QtCore.QThread(self)
- self.videoWorker = video_thread.Worker(self)
- self.videoWorker.moveToThread(self.videoThread)
- self.videoWorker.videoCreated.connect(self.videoCreated)
-
- self.videoThread.start()
- self.videoTask.emit(
- self.args.input,
- self.args.output,
- list(reversed(self.core.selectedComponents))
- )
-
- def videoCreated(self):
- self.videoThread.quit()
- self.videoThread.wait()
- quit(0)
-
- def showMessage(self, **kwargs):
- print(kwargs['msg'])
- if 'detail' in kwargs:
- print(kwargs['detail'])
-
- def drawPreview(self, *args):
- pass
-
- def parseCompName(self, name):
- '''Deduces a proper component name out of a commandline arg'''
-
- if name.title() in self.core.compNames:
- return name.title()
- for compName in self.core.compNames:
- if name.capitalize() in compName:
- return compName
-
- compFileNames = [ \
- os.path.splitext(os.path.basename(
- mod.__file__))[0] \
- for mod in self.core.modules \
- ]
- for i, compFileName in enumerate(compFileNames):
- if name.lower() in compFileName:
- return self.core.compNames[i]
- return
-
- return None
diff --git a/components/__base__.py b/components/__base__.py
deleted file mode 100644
index bef7f0e..0000000
--- a/components/__base__.py
+++ /dev/null
@@ -1,153 +0,0 @@
-from PyQt4 import QtGui, QtCore
-from PIL import Image
-import os
-
-
-class Component(QtCore.QObject):
- '''A base class for components to inherit from'''
-
- # modified = QtCore.pyqtSignal(int, bool)
-
- def __init__(self, moduleIndex, compPos, core):
- super().__init__()
- self.currentPreset = None
- self.moduleIndex = moduleIndex
- self.compPos = compPos
- self.core = core
-
- def __str__(self):
- return self.__doc__
-
- def version(self):
- # change this number to identify new versions of a component
- return 1
-
- def cancel(self):
- # please stop any lengthy process in response to this variable
- self.canceled = True
-
- def reset(self):
- self.canceled = False
-
- def update(self):
- self.modified.emit(self.compPos, self.savePreset())
- # read your widget values, then call super().update()
-
- def loadPreset(self, presetDict, presetName):
- '''Subclasses take (presetDict, presetName=None) as args.
- Must use super().loadPreset(presetDict, presetName) first,
- then update self.page widgets using the preset dict.
- '''
- self.currentPreset = presetName \
- if presetName != None else presetDict['preset']
-
- def preFrameRender(self, **kwargs):
- '''Triggered only before a video is exported (video_thread.py)
- self.worker = the video thread worker
- self.completeAudioArray = a list of audio samples
- self.sampleSize = number of audio samples per video frame
- self.progressBarUpdate = signal to set progress bar number
- self.progressBarSetText = signal to set progress bar text
- Use the latter two signals to update the MainProgram if needed
- for a long initialization procedure (i.e., for a visualizer)
- '''
- for var, value in kwargs.items():
- exec('self.%s = value' % var)
-
- def command(self, arg):
- '''Configure a component using argument from the commandline.
- Use super().command(arg) at the end of a subclass's method,
- if no arguments are found in that method first
- '''
- if arg.startswith('preset='):
- _, preset = arg.split('=', 1)
- path = os.path.join(self.core.getPresetDir(self), preset)
- if not os.path.exists(path):
- print('Couldn\'t locate preset "%s"' % preset)
- quit(1)
- else:
- print('Opening "%s" preset on layer %s' % \
- (preset, self.compPos))
- self.core.openPreset(path, self.compPos, preset)
- else:
- print(
- self.__doc__, 'Usage:\n'
- 'Open a preset for this component:\n'
- ' "preset=Preset Name"')
- self.commandHelp()
- quit(0)
-
- def commandHelp(self):
- '''Print help text for this Component's commandline arguments'''
-
- def blankFrame(self, width, height):
- return Image.new("RGBA", (width, height), (0, 0, 0, 0))
-
- def pickColor(self):
- '''Use color picker to get color input from the user,
- and return this as an RGB string and QPushButton stylesheet.
- In a subclass apply stylesheet to any color selection widgets
- '''
- dialog = QtGui.QColorDialog()
- dialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True)
- color = dialog.getColor()
- if color.isValid():
- RGBstring = '%s,%s,%s' % (
- str(color.red()), str(color.green()), str(color.blue()))
- btnStyle = "QPushButton{background-color: %s; outline: none;}" \
- % color.name()
- return RGBstring, btnStyle
- else:
- return None, None
-
- def RGBFromString(self, string):
- ''' Turns an RGB string like "255, 255, 255" into a tuple '''
- try:
- tup = tuple([int(i) for i in string.split(',')])
- if len(tup) != 3:
- raise ValueError
- for i in tup:
- if i > 255 or i < 0:
- raise ValueError
- return tup
- except:
- return (255, 255, 255)
-
- '''
- ### Reference methods for creating a new component
- ### (Inherit from this class and define these)
-
- def widget(self, parent):
- self.parent = parent
- page = uic.loadUi(os.path.join(
- os.path.dirname(os.path.realpath(__file__)), 'example.ui'))
- # --- connect widget signals here ---
- self.page = page
- return page
-
- def update(self):
- super().update()
- self.parent.drawPreview()
-
- def previewRender(self, previewWorker):
- width = int(previewWorker.core.settings.value('outputWidth'))
- height = int(previewWorker.core.settings.value('outputHeight'))
- image = Image.new("RGBA", (width, height), (0,0,0,0))
- return image
-
- def frameRender(self, moduleNo, frameNo):
- width = int(self.worker.core.settings.value('outputWidth'))
- height = int(self.worker.core.settings.value('outputHeight'))
- image = Image.new("RGBA", (width, height), (0,0,0,0))
- return image
- '''
-
-class BadComponentInit(Exception):
- def __init__(self, arg, name):
- string = \
-'''################################
-Mandatory argument "%s" not specified
- in %s instance initialization
-###################################'''
- print(string % (arg, name))
- quit()
diff --git a/components/__init__.py b/components/__init__.py
deleted file mode 100644
index 8b13789..0000000
--- a/components/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/components/color.py b/components/color.py
deleted file mode 100644
index 5ffcdea..0000000
--- a/components/color.py
+++ /dev/null
@@ -1,246 +0,0 @@
-from PIL import Image, ImageDraw
-from PyQt4 import uic, QtGui, QtCore
-from PyQt4.QtGui import QColor
-from PIL.ImageQt import ImageQt
-import os
-from . import __base__
-
-
-class Component(__base__.Component):
- '''Color'''
-
- modified = QtCore.pyqtSignal(int, dict)
-
- def widget(self, parent):
- self.parent = parent
- page = uic.loadUi(os.path.join(
- os.path.dirname(os.path.realpath(__file__)), 'color.ui'))
-
- self.color1 = (0, 0, 0)
- self.color2 = (133, 133, 133)
- self.x = 0
- self.y = 0
-
- page.lineEdit_color1.setText('%s,%s,%s' % self.color1)
- 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()
-
- page.pushButton_color1.setStyleSheet(btnStyle1)
- page.pushButton_color2.setStyleSheet(btnStyle2)
- page.pushButton_color1.clicked.connect(lambda: self.pickColor(1))
- page.pushButton_color2.clicked.connect(lambda: self.pickColor(2))
-
- # disable color #2 until non-default 'fill' option gets changed
- page.lineEdit_color2.setDisabled(True)
- page.pushButton_color2.setDisabled(True)
- page.spinBox_x.valueChanged.connect(self.update)
- page.spinBox_y.valueChanged.connect(self.update)
- page.spinBox_width.setValue(
- int(parent.settings.value("outputWidth")))
- page.spinBox_height.setValue(
- int(parent.settings.value("outputHeight")))
-
- page.lineEdit_color1.textChanged.connect(self.update)
- page.lineEdit_color2.textChanged.connect(self.update)
- page.spinBox_x.valueChanged.connect(self.update)
- page.spinBox_y.valueChanged.connect(self.update)
- page.spinBox_width.valueChanged.connect(self.update)
- page.spinBox_height.valueChanged.connect(self.update)
- page.checkBox_trans.stateChanged.connect(self.update)
-
- self.fillLabels = [ \
- 'Solid',
- 'Linear Gradient',
- 'Radial Gradient',
- ]
- for label in self.fillLabels:
- page.comboBox_fill.addItem(label)
- page.comboBox_fill.setCurrentIndex(0)
- page.comboBox_fill.currentIndexChanged.connect(self.update)
- page.comboBox_spread.currentIndexChanged.connect(self.update)
- page.spinBox_radialGradient_end.valueChanged.connect(self.update)
- page.spinBox_radialGradient_start.valueChanged.connect(self.update)
- page.spinBox_radialGradient_spread.valueChanged.connect(self.update)
- page.spinBox_linearGradient_end.valueChanged.connect(self.update)
- page.spinBox_linearGradient_start.valueChanged.connect(self.update)
- page.checkBox_stretch.stateChanged.connect(self.update)
-
- self.page = page
- return page
-
- def update(self):
- self.color1 = self.RGBFromString(self.page.lineEdit_color1.text())
- self.color2 = self.RGBFromString(self.page.lineEdit_color2.text())
- self.x = self.page.spinBox_x.value()
- self.y = self.page.spinBox_y.value()
- self.sizeWidth = self.page.spinBox_width.value()
- self.sizeHeight = self.page.spinBox_height.value()
- self.trans = self.page.checkBox_trans.isChecked()
- self.spread = self.page.comboBox_spread.currentIndex()
-
- self.RG_start = self.page.spinBox_radialGradient_start.value()
- self.RG_end = self.page.spinBox_radialGradient_end.value()
- self.RG_centre = self.page.spinBox_radialGradient_spread.value()
- self.stretch = self.page.checkBox_stretch.isChecked()
- self.LG_start = self.page.spinBox_linearGradient_start.value()
- self.LG_end = self.page.spinBox_linearGradient_end.value()
-
- self.fillType = self.page.comboBox_fill.currentIndex()
- if self.fillType == 0:
- self.page.lineEdit_color2.setEnabled(False)
- self.page.pushButton_color2.setEnabled(False)
- self.page.checkBox_trans.setEnabled(False)
- self.page.checkBox_stretch.setEnabled(False)
- self.page.comboBox_spread.setEnabled(False)
- else:
- self.page.lineEdit_color2.setEnabled(True)
- self.page.pushButton_color2.setEnabled(True)
- self.page.checkBox_trans.setEnabled(True)
- self.page.checkBox_stretch.setEnabled(True)
- self.page.comboBox_spread.setEnabled(True)
- self.page.fillWidget.setCurrentIndex(self.fillType)
-
- self.parent.drawPreview()
- super().update()
-
- def previewRender(self, previewWorker):
- width = int(previewWorker.core.settings.value('outputWidth'))
- height = int(previewWorker.core.settings.value('outputHeight'))
- return self.drawFrame(width, height)
-
- def preFrameRender(self, **kwargs):
- super().preFrameRender(**kwargs)
- return ['static']
-
- def frameRender(self, moduleNo, arrayNo, frameNo):
- width = int(self.worker.core.settings.value('outputWidth'))
- height = int(self.worker.core.settings.value('outputHeight'))
- return self.drawFrame(width, height)
-
- def drawFrame(self, width, height):
- r, g, b = self.color1
- shapeSize = (self.sizeWidth, self.sizeHeight)
- # in default state, skip all this logic and return a plain fill
- if self.fillType==0 and shapeSize == (width, height) \
- and self.x == 0 and self.y == 0:
- return Image.new("RGBA", (width, height), (r, g, b, 255))
-
- frame = self.blankFrame(width, height)
-
- # Return a solid image at x, y
- if self.fillType == 0:
- image = Image.new("RGBA", shapeSize, (r, g, b, 255))
- frame.paste(image, box=(self.x, self.y))
- return frame
-
- # Now fills that require using Qt...
- elif self.fillType > 0:
- image = ImageQt(frame)
- painter = QtGui.QPainter(image)
- if self.stretch:
- w = width; h = height
- else:
- w = self.sizeWidth; h = self.sizeWidth
-
- if self.fillType == 1: # Linear Gradient
- brush = QtGui.QLinearGradient(
- self.LG_start,
- self.LG_start,
- self.LG_start+width/3,
- 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)
- brush.setColorAt(0.0, QColor(*self.color1))
- if self.trans:
- brush.setColorAt(1.0, QColor(0, 0, 0, 0))
- elif self.fillType == 1 and self.stretch:
- brush.setColorAt(0.2, QColor(*self.color2))
- else:
- brush.setColorAt(1.0, QColor(*self.color2))
- painter.setBrush(brush)
- painter.drawRect(self.x, self.y,
- self.sizeWidth, self.sizeHeight)
- painter.end()
- imBytes = image.bits().asstring(image.numBytes())
- return Image.frombytes('RGBA', (width, height), imBytes)
-
- def loadPreset(self, pr, presetName=None):
- super().loadPreset(pr, presetName)
-
- self.page.comboBox_fill.setCurrentIndex(pr['fillType'])
- self.page.lineEdit_color1.setText('%s,%s,%s' % pr['color1'])
- self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2'])
- self.page.spinBox_x.setValue(pr['x'])
- self.page.spinBox_y.setValue(pr['y'])
- self.page.spinBox_width.setValue(pr['width'])
- self.page.spinBox_height.setValue(pr['height'])
- self.page.checkBox_trans.setChecked(pr['trans'])
-
- self.page.spinBox_radialGradient_start.setValue(pr['RG_start'])
- self.page.spinBox_radialGradient_end.setValue(pr['RG_end'])
- self.page.spinBox_radialGradient_spread.setValue(pr['RG_centre'])
- self.page.spinBox_linearGradient_start.setValue(pr['LG_start'])
- self.page.spinBox_linearGradient_end.setValue(pr['LG_end'])
- self.page.checkBox_stretch.setChecked(pr['stretch'])
- self.page.comboBox_spread.setCurrentIndex(pr['spread'])
-
- 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):
- return {
- 'preset': self.currentPreset,
- 'color1': self.color1,
- 'color2': self.color2,
- 'x': self.x,
- 'y': self.y,
- 'fillType': self.fillType,
- 'width': self.sizeWidth,
- 'height': self.sizeHeight,
- 'trans': self.trans,
- 'stretch': self.stretch,
- 'spread': self.spread,
- 'RG_start': self.RG_start,
- 'RG_end': self.RG_end,
- 'RG_centre': self.RG_centre,
- 'LG_start': self.LG_start,
- 'LG_end': self.LG_end,
- }
-
- def pickColor(self, num):
- RGBstring, btnStyle = super().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')
-
- def command(self, arg):
- if not arg.startswith('preset=') and '=' in arg:
- key, arg = arg.split('=', 1)
- if key == 'color':
- self.page.lineEdit_color1.setText(arg)
- return
- super().command(arg)
diff --git a/components/color.ui b/components/color.ui
deleted file mode 100644
index a9dacea..0000000
--- a/components/color.ui
+++ /dev/null
@@ -1,660 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 586
- 197
-
-
-
- Form
-
-
-
-
-
-
- 4
-
-
-
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 31
- 0
-
-
-
- Color #1
-
-
-
- -
-
-
-
- 32
- 32
-
-
-
-
-
-
-
- 32
- 32
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 1
- 0
-
-
-
- 12
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 31
- 0
-
-
-
- Color #2
-
-
-
- -
-
-
-
- 32
- 32
-
-
-
-
-
-
-
- 32
- 32
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 1
- 0
-
-
-
- 12
-
-
-
-
-
- -
-
-
- 0
-
-
-
-
-
-
- 0
- 0
-
-
-
- Width
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
-
- 0
- 0
-
-
-
- 0
-
-
- 999999999
-
-
- 0
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Height
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
- 999999999
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- X
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
-
- 0
- 0
-
-
-
- -10000
-
-
- 10000
-
-
- 0
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Y
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
- -10000
-
-
- 10000
-
-
-
-
-
- -
-
-
- 0
-
-
-
-
-
-
- 0
- 0
-
-
-
- Fill
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- -1
-
-
- QComboBox::AdjustToContentsOnFirstShow
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Transparent
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Stretch
-
-
-
- -
-
-
-
-
- Pad
-
-
- -
-
- Reflect
-
-
- -
-
- Repeat
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Minimum
-
-
-
- 40
- 20
-
-
-
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
- 0
-
-
- 2
-
-
-
-
-
-
- -1
- 0
- 561
- 31
-
-
-
-
-
-
-
-
- 0
- 0
-
-
-
- Start
-
-
-
- -
-
-
- -10000
-
-
- 10000
-
-
- 10
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- End
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- -10000
-
-
- 10000
-
-
- 10
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
-
-
-
-
-
- -1
- -1
- 561
- 31
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Start
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- -10000
-
-
- 10000
-
-
- 10
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- End
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- -10000
-
-
- 10000
-
-
- 10
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Centre
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- QAbstractSpinBox::PlusMinus
-
-
- -10000
-
-
- 10000
-
-
- 3
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/components/image.py b/components/image.py
deleted file mode 100644
index f8ae64e..0000000
--- a/components/image.py
+++ /dev/null
@@ -1,111 +0,0 @@
-from PIL import Image, ImageDraw
-from PyQt4 import uic, QtGui, QtCore
-import os
-from . import __base__
-
-
-class Component(__base__.Component):
- '''Image'''
-
- modified = QtCore.pyqtSignal(int, dict)
-
- def widget(self, parent):
- self.parent = parent
- self.settings = parent.settings
- page = uic.loadUi(os.path.join(
- os.path.dirname(os.path.realpath(__file__)), 'image.ui'))
- self.imagePath = ''
- self.x = 0
- self.y = 0
-
- page.lineEdit_image.textChanged.connect(self.update)
- page.pushButton_image.clicked.connect(self.pickImage)
- page.spinBox_scale.valueChanged.connect(self.update)
- page.checkBox_stretch.stateChanged.connect(self.update)
- page.spinBox_x.valueChanged.connect(self.update)
- page.spinBox_y.valueChanged.connect(self.update)
-
- self.page = page
- return page
-
- def update(self):
- self.imagePath = self.page.lineEdit_image.text()
- self.scale = self.page.spinBox_scale.value()
- self.xPosition = self.page.spinBox_x.value()
- self.yPosition = self.page.spinBox_y.value()
- self.stretched = self.page.checkBox_stretch.isChecked()
- self.parent.drawPreview()
- super().update()
-
- def previewRender(self, previewWorker):
- self.imageFormats = previewWorker.core.imageFormats
- width = int(previewWorker.core.settings.value('outputWidth'))
- height = int(previewWorker.core.settings.value('outputHeight'))
- return self.drawFrame(width, height)
-
- def preFrameRender(self, **kwargs):
- super().preFrameRender(**kwargs)
- return ['static']
-
- def frameRender(self, moduleNo, arrayNo, frameNo):
- width = int(self.worker.core.settings.value('outputWidth'))
- height = int(self.worker.core.settings.value('outputHeight'))
- return self.drawFrame(width, height)
-
- def drawFrame(self, width, height):
- frame = self.blankFrame(width, height)
- if self.imagePath and os.path.exists(self.imagePath):
- image = Image.open(self.imagePath)
- if self.stretched and image.size != (width, height):
- image = image.resize((width, height), Image.ANTIALIAS)
- if self.scale != 100:
- newHeight = int((image.height / 100) * self.scale)
- newWidth = int((image.width / 100) * self.scale)
- image = image.resize((newWidth, newHeight), Image.ANTIALIAS)
- frame.paste(image, box=(self.xPosition, self.yPosition))
- return frame
-
- def loadPreset(self, pr, presetName=None):
- super().loadPreset(pr, presetName)
- self.page.lineEdit_image.setText(pr['image'])
- self.page.spinBox_scale.setValue(pr['scale'])
- self.page.spinBox_x.setValue(pr['x'])
- self.page.spinBox_y.setValue(pr['y'])
- self.page.checkBox_stretch.setChecked(pr['stretched'])
-
- def savePreset(self):
- return {
- 'preset': self.currentPreset,
- 'image': self.imagePath,
- 'scale': self.scale,
- 'stretched': self.stretched,
- 'x': self.xPosition,
- 'y': self.yPosition,
- }
-
- def pickImage(self):
- imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
- filename = QtGui.QFileDialog.getOpenFileName(
- self.page, "Choose Image", imgDir,
- "Image Files (%s)" % " ".join(self.imageFormats))
- if filename:
- self.settings.setValue("backgroundDir", os.path.dirname(filename))
- self.page.lineEdit_image.setText(filename)
- self.update()
-
- def command(self, arg):
- if not arg.startswith('preset=') and '=' in arg:
- key, arg = arg.split('=', 1)
- if key == 'path' and os.path.exists(arg):
- try:
- Image.open(arg)
- self.page.lineEdit_image.setText(arg)
- self.page.checkBox_stretch.setChecked(True)
- return
- except OSError as e:
- print("Not a supported image format")
- quit(1)
- super().command(arg)
-
- def commandHelp(self):
- print('Load an image:\n path=/filepath/to/image.png')
diff --git a/components/image.ui b/components/image.ui
deleted file mode 100644
index 6df03a5..0000000
--- a/components/image.ui
+++ /dev/null
@@ -1,259 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 586
- 197
-
-
-
- Form
-
-
- -
-
-
- 4
-
-
-
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 31
- 0
-
-
-
- Image
-
-
-
- -
-
-
-
- 1
- 0
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 1
- 0
-
-
-
-
- 32
- 32
-
-
-
- ...
-
-
-
- 32
- 32
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- X
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
- -10000
-
-
- 10000
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Y
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
-
- 0
- 0
-
-
-
- -1000
-
-
- 1000
-
-
- 0
-
-
-
-
-
-
-
- -
-
-
-
-
-
- Stretch
-
-
- false
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
- Scale
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- QAbstractSpinBox::UpDownArrows
-
-
- %
-
-
- 10
-
-
- 400
-
-
- 100
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
-
-
diff --git a/components/original.py b/components/original.py
deleted file mode 100644
index 6222157..0000000
--- a/components/original.py
+++ /dev/null
@@ -1,204 +0,0 @@
-import numpy
-from PIL import Image, ImageDraw
-from PyQt4 import uic, QtGui, QtCore
-from PyQt4.QtGui import QColor
-import os
-from . import __base__
-import time
-from copy import copy
-
-
-class Component(__base__.Component):
- '''Original Audio Visualization'''
-
- modified = QtCore.pyqtSignal(int, dict)
-
- def widget(self, parent):
- self.parent = parent
- self.visColor = (255, 255, 255)
-
- page = uic.loadUi(os.path.join(
- os.path.dirname(os.path.realpath(__file__)), 'original.ui'))
- page.comboBox_visLayout.addItem("Classic")
- page.comboBox_visLayout.addItem("Split")
- page.comboBox_visLayout.addItem("Bottom")
- page.comboBox_visLayout.setCurrentIndex(0)
- page.comboBox_visLayout.currentIndexChanged.connect(self.update)
- page.lineEdit_visColor.setText('%s,%s,%s' % self.visColor)
- page.pushButton_visColor.clicked.connect(lambda: self.pickColor())
- btnStyle = "QPushButton { background-color : %s; outline: none; }" \
- % QColor(*self.visColor).name()
- page.pushButton_visColor.setStyleSheet(btnStyle)
- page.lineEdit_visColor.textChanged.connect(self.update)
- self.page = page
- self.canceled = False
- return page
-
- def update(self):
- self.layout = self.page.comboBox_visLayout.currentIndex()
- self.visColor = self.RGBFromString(self.page.lineEdit_visColor.text())
- self.parent.drawPreview()
- super().update()
-
- def loadPreset(self, pr, presetName=None):
- super().loadPreset(pr, presetName)
-
- 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)
- self.page.comboBox_visLayout.setCurrentIndex(pr['layout'])
-
- def savePreset(self):
- return {
- 'preset': self.currentPreset,
- 'layout': self.layout,
- 'visColor': self.visColor,
- }
-
- def previewRender(self, previewWorker):
- spectrum = numpy.fromfunction(
- lambda x: 0.008*(x-128)**2, (255,), dtype="int16")
- width = int(previewWorker.core.settings.value('outputWidth'))
- height = int(previewWorker.core.settings.value('outputHeight'))
- return self.drawBars(
- width, height, spectrum, self.visColor, self.layout)
-
- def preFrameRender(self, **kwargs):
- super().preFrameRender(**kwargs)
- self.smoothConstantDown = 0.08
- self.smoothConstantUp = 0.8
- self.lastSpectrum = None
- self.spectrumArray = {}
- self.width = int(self.worker.core.settings.value('outputWidth'))
- self.height = int(self.worker.core.settings.value('outputHeight'))
-
- for i in range(0, len(self.completeAudioArray), self.sampleSize):
- if self.canceled:
- break
- self.lastSpectrum = self.transformData(
- i, self.completeAudioArray, self.sampleSize,
- self.smoothConstantDown, self.smoothConstantUp,
- self.lastSpectrum)
- self.spectrumArray[i] = copy(self.lastSpectrum)
-
- progress = int(100*(i/len(self.completeAudioArray)))
- if progress >= 100:
- progress = 100
- pStr = "Analyzing audio: "+str(progress)+'%'
- self.progressBarSetText.emit(pStr)
- self.progressBarUpdate.emit(int(progress))
-
- def frameRender(self, moduleNo, arrayNo, frameNo):
- return self.drawBars(
- self.width, self.height,
- self.spectrumArray[arrayNo],
- self.visColor, self.layout)
-
- def pickColor(self):
- RGBstring, btnStyle = super().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):
- if len(completeAudioArray) < (i + sampleSize):
- sampleSize = len(completeAudioArray) - i
-
- window = numpy.hanning(sampleSize)
- data = completeAudioArray[i:i+sampleSize][::1] * window
- paddedSampleSize = 2048
- paddedData = numpy.pad(
- data, (0, paddedSampleSize - sampleSize), 'constant')
- spectrum = numpy.fft.fft(paddedData)
- sample_rate = 44100
- frequencies = numpy.fft.fftfreq(len(spectrum), 1./sample_rate)
-
- y = abs(spectrum[0:int(paddedSampleSize/2) - 1])
-
- # filter the noise away
- # y[y<80] = 0
-
- y = 20 * numpy.log10(y)
- y[numpy.isinf(y)] = 0
-
- if lastSpectrum is not None:
- lastSpectrum[y < lastSpectrum] = \
- y[y < lastSpectrum] * smoothConstantDown + \
- lastSpectrum[y < lastSpectrum] * (1 - smoothConstantDown)
-
- lastSpectrum[y >= lastSpectrum] = \
- y[y >= lastSpectrum] * smoothConstantUp + \
- lastSpectrum[y >= lastSpectrum] * (1 - smoothConstantUp)
- else:
- lastSpectrum = y
-
- x = frequencies[0:int(paddedSampleSize/2) - 1]
-
- return lastSpectrum
-
- def drawBars(self, width, height, spectrum, color, layout):
- vH = height-height/8
- bF = width / 64
- bH = bF / 2
- bQ = bF / 4
- imTop = self.blankFrame(width, height)
- draw = ImageDraw.Draw(imTop)
- r, g, b = color
- color2 = (r, g, b, 125)
-
- bP = height / 1200
-
- for j in range(0, 63):
- 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)
-
- im = self.blankFrame(width, height)
-
- if layout == 0:
- y = 0 - int(height/100*43)
- im.paste(imTop, (0, y), mask=imTop)
- y = 0 + int(height/100*43)
- im.paste(imBottom, (0, y), mask=imBottom)
-
- if layout == 1:
- y = 0 + int(height/100*10)
- im.paste(imTop, (0, y), mask=imTop)
- y = 0 - int(height/100*10)
- im.paste(imBottom, (0, y), mask=imBottom)
-
- if layout == 2:
- y = 0 + int(height/100*10)
- im.paste(imTop, (0, y), mask=imTop)
-
- return im
-
- def command(self, arg):
- if not arg.startswith('preset=') and '=' in arg:
- key, arg = arg.split('=', 1)
- if key == 'color':
- self.page.lineEdit_visColor.setText(arg)
- return
- elif key == 'layout':
- if arg == 'classic':
- self.page.comboBox_visLayout.setCurrentIndex(0)
- elif arg == 'split':
- self.page.comboBox_visLayout.setCurrentIndex(1)
- elif arg == 'bottom':
- self.page.comboBox_visLayout.setCurrentIndex(2)
- return
- super().command(arg)
-
- def commandHelp(self):
- print('Give a layout name:\n layout=[classic/split/bottom]')
- print('Specify a color:\n color=255,255,255')
diff --git a/components/original.ui b/components/original.ui
deleted file mode 100644
index 5808653..0000000
--- a/components/original.ui
+++ /dev/null
@@ -1,108 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 633
- 178
-
-
-
-
- 180
- 0
-
-
-
- Form
-
-
- -
-
-
- 4
-
-
-
-
-
-
- 0
- 0
-
-
-
- Visualizer Layout
-
-
-
- -
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
- Visualizer Color
-
-
-
- -
-
-
-
- 32
- 32
-
-
-
-
-
-
-
- 32
- 32
-
-
-
-
- -
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
-
-
diff --git a/components/text.py b/components/text.py
deleted file mode 100644
index 2375dcd..0000000
--- a/components/text.py
+++ /dev/null
@@ -1,176 +0,0 @@
-from PIL import Image, ImageDraw
-from PyQt4.QtGui import QPainter, QColor, QFont
-from PyQt4 import uic, QtGui, QtCore
-from PIL.ImageQt import ImageQt
-import os
-import io
-from . import __base__
-
-
-class Component(__base__.Component):
- '''Title Text'''
-
- modified = QtCore.pyqtSignal(int, dict)
-
- def __init__(self, *args):
- super().__init__(*args)
- self.titleFont = QFont()
-
- def widget(self, parent):
- height = int(parent.settings.value('outputHeight'))
- width = int(parent.settings.value('outputWidth'))
-
- self.parent = parent
- 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
-
- page = uic.loadUi(os.path.join(
- os.path.dirname(os.path.realpath(__file__)), 'text.ui'))
- page.comboBox_textAlign.addItem("Left")
- page.comboBox_textAlign.addItem("Middle")
- page.comboBox_textAlign.addItem("Right")
-
- page.lineEdit_textColor.setText('%s,%s,%s' % self.textColor)
- page.pushButton_textColor.clicked.connect(self.pickColor)
- btnStyle = "QPushButton { background-color : %s; outline: none; }" \
- % QColor(*self.textColor).name()
- page.pushButton_textColor.setStyleSheet(btnStyle)
-
- page.lineEdit_title.setText(self.title)
- page.comboBox_textAlign.setCurrentIndex(int(self.alignment))
- page.spinBox_fontSize.setValue(int(self.fontSize))
- page.spinBox_xTextAlign.setValue(int(self.xPosition))
- page.spinBox_yTextAlign.setValue(int(self.yPosition))
-
- page.fontComboBox_titleFont.currentFontChanged.connect(self.update)
- page.lineEdit_title.textChanged.connect(self.update)
- page.comboBox_textAlign.currentIndexChanged.connect(self.update)
- page.spinBox_xTextAlign.valueChanged.connect(self.update)
- page.spinBox_yTextAlign.valueChanged.connect(self.update)
- page.spinBox_fontSize.valueChanged.connect(self.update)
- page.lineEdit_textColor.textChanged.connect(self.update)
- self.page = page
- return page
-
- def update(self):
- self.title = self.page.lineEdit_title.text()
- self.alignment = self.page.comboBox_textAlign.currentIndex()
- self.titleFont = self.page.fontComboBox_titleFont.currentFont()
- self.fontSize = self.page.spinBox_fontSize.value()
- self.xPosition = self.page.spinBox_xTextAlign.value()
- self.yPosition = self.page.spinBox_yTextAlign.value()
- self.textColor = self.RGBFromString(
- self.page.lineEdit_textColor.text())
- self.parent.drawPreview()
- super().update()
-
- def getXY(self):
- '''Returns true x, y after considering alignment settings'''
- fm = QtGui.QFontMetrics(self.titleFont)
- if self.alignment == 0: # Left
- x = self.xPosition
-
- if self.alignment == 1: # Middle
- offset = fm.width(self.title)/2
- x = self.xPosition - offset
-
- if self.alignment == 2: # Right
- offset = fm.width(self.title)
- x = self.xPosition - offset
- return x, self.yPosition
-
- def loadPreset(self, pr, presetName=None):
- super().loadPreset(pr, presetName)
-
- self.page.lineEdit_title.setText(pr['title'])
- font = QFont()
- font.fromString(pr['titleFont'])
- self.page.fontComboBox_titleFont.setCurrentFont(font)
- self.page.spinBox_fontSize.setValue(pr['fontSize'])
- self.page.comboBox_textAlign.setCurrentIndex(pr['alignment'])
- self.page.spinBox_xTextAlign.setValue(pr['xPosition'])
- self.page.spinBox_yTextAlign.setValue(pr['yPosition'])
- 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):
- return {
- 'preset': self.currentPreset,
- 'title': self.title,
- 'titleFont': self.titleFont.toString(),
- 'alignment': self.alignment,
- 'fontSize': self.fontSize,
- 'xPosition': self.xPosition,
- 'yPosition': self.yPosition,
- 'textColor': self.textColor
- }
-
- def previewRender(self, previewWorker):
- width = int(previewWorker.core.settings.value('outputWidth'))
- height = int(previewWorker.core.settings.value('outputHeight'))
- return self.addText(width, height)
-
- def preFrameRender(self, **kwargs):
- super().preFrameRender(**kwargs)
- return ['static']
-
- def frameRender(self, moduleNo, arrayNo, frameNo):
- width = int(self.worker.core.settings.value('outputWidth'))
- height = int(self.worker.core.settings.value('outputHeight'))
- return self.addText(width, height)
-
- def addText(self, width, height):
- x, y = self.getXY()
- im = self.blankFrame(width, height)
- image = ImageQt(im)
-
- painter = QPainter(image)
- self.titleFont.setPixelSize(self.fontSize)
- painter.setFont(self.titleFont)
- painter.setPen(QColor(*self.textColor))
- painter.drawText(x, y, self.title)
- painter.end()
-
- imBytes = image.bits().asstring(image.numBytes())
-
- return Image.frombytes('RGBA', (width, height), imBytes)
-
- def pickColor(self):
- RGBstring, btnStyle = super().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"')
- 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 not arg.startswith('preset=') and '=' in arg:
- key, arg = arg.split('=', 1)
- if key == 'color':
- self.page.lineEdit_textColor.setText(arg)
- return
- elif key == 'size':
- self.page.spinBox_fontSize.setValue(int(arg))
- return
- elif key == 'x':
- self.page.spinBox_xTextAlign.setValue(int(arg))
- return
- elif key == 'y':
- self.page.spinBox_yTextAlign.setValue(int(arg))
- return
- elif key == 'title':
- self.page.lineEdit_title.setText(arg)
- return
- super().command(arg)
diff --git a/components/text.ui b/components/text.ui
deleted file mode 100644
index 05e7f8e..0000000
--- a/components/text.ui
+++ /dev/null
@@ -1,316 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 586
- 197
-
-
-
- Form
-
-
- -
-
-
- 4
-
-
-
-
-
-
-
-
-
- 0
- 0
-
-
-
- Font
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 0
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Font Size
-
-
-
- -
-
-
- 500
-
-
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
- Text Layout
-
-
-
- -
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
- Text Color
-
-
-
- -
-
-
-
- 32
- 32
-
-
-
-
-
-
-
- 32
- 32
-
-
-
-
- -
-
-
-
-
- -
-
-
- 0
-
-
-
-
-
- Title
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 0
-
-
-
- Testing New GUI
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- X
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
-
- 0
- 0
-
-
-
- 0
-
-
- 999999999
-
-
- 0
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Y
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
- 999999999
-
-
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
-
-
diff --git a/components/video.py b/components/video.py
deleted file mode 100644
index 1d250bd..0000000
--- a/components/video.py
+++ /dev/null
@@ -1,273 +0,0 @@
-from PIL import Image, ImageDraw
-from PyQt4 import uic, QtGui, QtCore
-import os
-import subprocess
-import threading
-from queue import PriorityQueue
-from . import __base__
-
-
-class Video:
- '''Video Component Frame-Fetcher'''
- def __init__(self, **kwargs):
- mandatoryArgs = [
- 'ffmpeg', # path to ffmpeg, usually 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:
- try:
- exec('self.%s = kwargs[arg]' % arg)
- except KeyError:
- raise __base__.BadComponentInit(arg, self.__doc__)
-
- 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: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=self.__doc__
- )
- 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):
- pipe = subprocess.Popen(
- self.command, 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.
- if len(self.currentFrame) == 0:
- self.frameBuffer.put((self.frameNo-1, self.lastFrame))
- continue
-
- self.currentFrame = pipe.stdout.read(self.chunkSize)
- if len(self.currentFrame) != 0:
- self.frameBuffer.put((self.frameNo, self.currentFrame))
- self.lastFrame = self.currentFrame
-
-
-class Component(__base__.Component):
- '''Video'''
-
- modified = QtCore.pyqtSignal(int, dict)
-
- def widget(self, parent):
- self.parent = parent
- self.settings = parent.settings
- page = uic.loadUi(os.path.join(
- os.path.dirname(os.path.realpath(__file__)),
- 'video.ui'
- ))
- self.videoPath = ''
- self.x = 0
- self.y = 0
- self.loopVideo = False
-
- page.lineEdit_video.textChanged.connect(self.update)
- page.pushButton_video.clicked.connect(self.pickVideo)
- page.checkBox_loop.stateChanged.connect(self.update)
- page.checkBox_distort.stateChanged.connect(self.update)
- page.spinBox_scale.valueChanged.connect(self.update)
- page.spinBox_x.valueChanged.connect(self.update)
- page.spinBox_y.valueChanged.connect(self.update)
-
- self.page = page
- return page
-
- def update(self):
- self.videoPath = self.page.lineEdit_video.text()
- self.loopVideo = self.page.checkBox_loop.isChecked()
- self.distort = self.page.checkBox_distort.isChecked()
- self.scale = self.page.spinBox_scale.value()
- self.xPosition = self.page.spinBox_x.value()
- self.yPosition = self.page.spinBox_y.value()
- self.parent.drawPreview()
- super().update()
-
- def previewRender(self, previewWorker):
- self.videoFormats = previewWorker.core.videoFormats
- width = int(previewWorker.core.settings.value('outputWidth'))
- height = int(previewWorker.core.settings.value('outputHeight'))
- self.updateChunksize(width, height)
- frame = self.getPreviewFrame(width, height)
- if not frame:
- return self.blankFrame(width, height)
- else:
- return frame
-
- def preFrameRender(self, **kwargs):
- super().preFrameRender(**kwargs)
- width = int(self.worker.core.settings.value('outputWidth'))
- height = int(self.worker.core.settings.value('outputHeight'))
- self.blankFrame_ = self.blankFrame(width, height)
- self.updateChunksize(width, height)
- self.video = Video(
- ffmpeg=self.parent.core.FFMPEG_BIN, videoPath=self.videoPath,
- width=width, height=height, chunkSize=self.chunkSize,
- frameRate=int(self.settings.value("outputFrameRate")),
- parent=self.parent, loopVideo=self.loopVideo,
- component=self, scale=self.scale
- ) if os.path.exists(self.videoPath) else None
-
- def frameRender(self, moduleNo, arrayNo, frameNo):
- if self.video:
- return self.video.frame(frameNo)
- else:
- return self.blankFrame_
-
- def loadPreset(self, pr, presetName=None):
- super().loadPreset(pr, presetName)
- self.page.lineEdit_video.setText(pr['video'])
- self.page.checkBox_loop.setChecked(pr['loop'])
- self.page.checkBox_distort.setChecked(pr['distort'])
- self.page.spinBox_scale.setValue(pr['scale'])
- self.page.spinBox_x.setValue(pr['x'])
- self.page.spinBox_y.setValue(pr['y'])
-
- def savePreset(self):
- return {
- 'preset': self.currentPreset,
- 'video': self.videoPath,
- 'loop': self.loopVideo,
- 'distort': self.distort,
- 'scale': self.scale,
- 'x': self.xPosition,
- 'y': self.yPosition,
- }
-
- def pickVideo(self):
- imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
- filename = QtGui.QFileDialog.getOpenFileName(
- self.page, "Choose Video",
- imgDir, "Video Files (%s)" % " ".join(self.videoFormats)
- )
- if filename:
- self.settings.setValue("backgroundDir", os.path.dirname(filename))
- self.page.lineEdit_video.setText(filename)
- self.update()
-
- def getPreviewFrame(self, width, height):
- if not self.videoPath or not os.path.exists(self.videoPath):
- return
-
- command = [
- self.parent.core.FFMPEG_BIN,
- '-thread_queue_size', '512',
- '-i', self.videoPath,
- '-f', 'image2pipe',
- '-pix_fmt', 'rgba',
- '-filter:v', 'scale=%s:%s' %
- scale(self.scale, width, height, str),
- '-vcodec', 'rawvideo', '-',
- '-ss', '90',
- '-vframes', '1',
- ]
- pipe = subprocess.Popen(
- command, stdout=subprocess.PIPE,
- stderr=subprocess.DEVNULL, bufsize=10**8
- )
- byteFrame = pipe.stdout.read(self.chunkSize)
- frame = finalizeFrame(self, byteFrame, width, height)
- pipe.stdout.close()
- pipe.kill()
-
- return frame
-
- def updateChunksize(self, width, height):
- if self.scale != 100 and not self.distort:
- width, height = scale(self.scale, width, height, int)
- self.chunkSize = 4*width*height
-
- def command(self, arg):
- if not arg.startswith('preset=') and '=' in arg:
- key, arg = arg.split('=', 1)
- if key == 'path' and os.path.exists(arg):
- if os.path.splitext(arg)[1] in self.core.videoFormats:
- self.page.lineEdit_video.setText(arg)
- self.page.spinBox_scale.setValue(100)
- self.page.checkBox_loop.setChecked(True)
- return
- else:
- print("Not a supported video format")
- quit(1)
- super().command(arg)
-
- def commandHelp(self):
- print('Load a video:\n path=/filepath/to/video.mp4')
-
-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(int(width)), str(int(height)))
- elif returntype == int:
- return (int(width), int(height))
- else:
- return (width, height)
-
-def finalizeFrame(self, imageData, width, height):
- if self.distort:
- try:
- image = Image.frombytes(
- 'RGBA',
- (width, height),
- imageData)
- except ValueError:
- print('#### ignored invalid data caused by distortion ####')
- image = self.blankFrame(width, height)
- else:
- image = Image.frombytes(
- 'RGBA',
- scale(self.scale, width, height, int),
- imageData)
-
- if self.scale != 100 \
- or self.xPosition != 0 or self.yPosition != 0:
- frame = self.blankFrame(width, height)
- frame.paste(image, box=(self.xPosition, self.yPosition))
- else:
- frame = image
- return frame
diff --git a/components/video.ui b/components/video.ui
deleted file mode 100644
index f05e8a5..0000000
--- a/components/video.ui
+++ /dev/null
@@ -1,266 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 586
- 197
-
-
-
- Form
-
-
- -
-
-
- 4
-
-
-
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 31
- 0
-
-
-
- Video
-
-
-
- -
-
-
-
- 1
- 0
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 1
- 0
-
-
-
-
- 32
- 32
-
-
-
- ...
-
-
-
- 32
- 32
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- X
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
- -10000
-
-
- 10000
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Y
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
-
- 0
- 0
-
-
-
- -10000
-
-
- 10000
-
-
- 0
-
-
-
-
-
-
-
- -
-
-
-
-
-
- Loop
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
- Distort by scale
-
-
-
- -
-
-
- Scale
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- QAbstractSpinBox::UpDownArrows
-
-
- %
-
-
- 10
-
-
- 400
-
-
- 100
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
- -
-
-
-
-
-
-
-
diff --git a/core.py b/core.py
deleted file mode 100644
index de6ed99..0000000
--- a/core.py
+++ /dev/null
@@ -1,476 +0,0 @@
-import sys
-import io
-import os
-from PyQt4 import QtCore, QtGui, uic
-from os.path import expanduser
-import subprocess as sp
-import numpy
-from PIL import Image
-from shutil import rmtree
-import time
-from collections import OrderedDict
-import json
-from importlib import import_module
-from PyQt4.QtGui import QDesktopServices
-import string
-
-
-class Core():
-
- def __init__(self):
- self.FFMPEG_BIN = self.findFfmpeg()
- self.dataDir = QDesktopServices.storageLocation(
- QDesktopServices.DataLocation)
- self.presetDir = os.path.join(self.dataDir, 'presets')
- if getattr(sys, 'frozen', False):
- # frozen
- self.wd = os.path.dirname(sys.executable)
- else:
- # unfrozen
- self.wd = os.path.dirname(os.path.realpath(__file__))
-
- self.loadEncoderOptions()
- self.videoFormats = Core.appendUppercase([
- '*.mp4',
- '*.mov',
- '*.mkv',
- '*.avi',
- '*.webm',
- '*.flv',
- ])
- self.audioFormats = Core.appendUppercase([
- '*.mp3',
- '*.wav',
- '*.ogg',
- '*.fla',
- '*.flac',
- '*.aac',
- ])
- self.imageFormats = Core.appendUppercase([
- '*.png',
- '*.jpg',
- '*.tif',
- '*.tiff',
- '*.gif',
- '*.bmp',
- '*.ico',
- '*.xbm',
- '*.xpm',
- ])
-
- self.findComponents()
- self.selectedComponents = []
- # copies of named presets to detect modification
- self.savedPresets = {}
-
- def findComponents(self):
- def findComponents():
- srcPath = os.path.join(self.wd, 'components')
- if os.path.exists(srcPath):
- for f in sorted(os.listdir(srcPath)):
- name, ext = os.path.splitext(f)
- if name.startswith("__"):
- continue
- elif ext == '.py':
- yield name
- self.modules = [
- import_module('components.%s' % name)
- for name in findComponents()
- ]
- self.moduleIndexes = [i for i in range(len(self.modules))]
- self.compNames = [mod.Component.__doc__ for mod in self.modules]
-
- def componentListChanged(self):
- for i, component in enumerate(self.selectedComponents):
- component.compPos = i
-
- def insertComponent(self, compPos, moduleIndex, loader):
- '''Creates a new component'''
- if compPos < 0 or compPos > len(self.selectedComponents):
- compPos = len(self.selectedComponents)
- if len(self.selectedComponents) > 50:
- return None
-
- component = self.modules[moduleIndex].Component(
- moduleIndex, compPos, self)
- self.selectedComponents.insert(
- compPos,
- component)
- self.componentListChanged()
-
- # init component's widget for loading/saving presets
- self.selectedComponents[compPos].widget(loader)
- self.updateComponent(compPos)
-
- if hasattr(loader, 'insertComponent'):
- loader.insertComponent(compPos)
- return compPos
-
- def moveComponent(self, startI, endI):
- comp = self.selectedComponents.pop(startI)
- self.selectedComponents.insert(endI, comp)
-
- self.componentListChanged()
- return endI
-
- def removeComponent(self, i):
- self.selectedComponents.pop(i)
- self.componentListChanged()
-
- def clearComponents(self):
- self.selectedComponents = list()
- self.componentListChanged()
-
- def updateComponent(self, i):
- # print('updating %s' % self.selectedComponents[i])
- self.selectedComponents[i].update()
-
- def moduleIndexFor(self, compName):
- index = self.compNames.index(compName)
- return self.moduleIndexes[index]
-
- def clearPreset(self, compIndex):
- self.selectedComponents[compIndex].currentPreset = None
-
- def openPreset(self, filepath, compIndex, presetName):
- '''Applies a preset to a specific component'''
- saveValueStore = self.getPreset(filepath)
- if not saveValueStore:
- return False
- try:
- self.selectedComponents[compIndex].loadPreset(
- saveValueStore,
- presetName
- )
- except KeyError as e:
- print('preset missing value: %s' % e)
-
- self.savedPresets[presetName] = dict(saveValueStore)
- return True
-
- def getPresetDir(self, comp):
- return os.path.join(
- self.presetDir, str(comp), str(comp.version()))
-
- def getPreset(self, filepath):
- '''Returns the preset dict stored at this filepath'''
- if not os.path.exists(filepath):
- return False
- with open(filepath, 'r') as f:
- for line in f:
- saveValueStore = Core.presetFromString(line.strip())
- break
- return saveValueStore
-
- def openProject(self, loader, filepath):
- ''' loader is the object calling this method which must have
- its own showMessage(**kwargs) method for displaying errors.
- '''
- if not os.path.exists(filepath):
- loader.showMessage(msg='Project file not found')
- return
-
- errcode, data = self.parseAvFile(filepath)
- if errcode == 0:
- try:
- for i, tup in enumerate(data['Components']):
- name, vers, preset = tup
- clearThis = False
-
- # add loaded named presets to savedPresets dict
- if 'preset' in preset and preset['preset'] != None:
- nam = preset['preset']
- filepath2 = os.path.join(
- self.presetDir, name, str(vers), nam)
- origSaveValueStore = self.getPreset(filepath2)
- if origSaveValueStore:
- self.savedPresets[nam] = dict(origSaveValueStore)
- else:
- # saved preset was renamed or deleted
- clearThis = True
-
- # create the actual component object & get its index
- i = self.insertComponent(
- -1,
- self.moduleIndexFor(name),
- loader)
- if i == None:
- loader.showMessage(msg="Too many components!")
- break
-
- try:
- if 'preset' in preset and preset['preset'] != None:
- self.selectedComponents[i].loadPreset(
- preset
- )
- else:
- self.selectedComponents[i].loadPreset(
- preset,
- preset['preset']
- )
- except KeyError as e:
- print('%s missing value %s' %
- (self.selectedComponents[i], e))
-
- if clearThis:
- self.clearPreset(i)
- if hasattr(loader, 'updateComponentTitle'):
- loader.updateComponentTitle(i)
- except:
- errcode = 1
- data = sys.exc_info()
-
-
- if errcode == 1:
- typ, value, _ = data
- if typ.__name__ == KeyError:
- # probably just an old version, still loadable
- print('file missing value: %s' % value)
- return
- if hasattr(loader, 'createNewProject'):
- loader.createNewProject()
- msg = '%s: %s' % (typ.__name__, value)
- loader.showMessage(
- msg="Project file '%s' is corrupted." % filepath,
- showCancel=False,
- icon=QtGui.QMessageBox.Warning,
- detail=msg)
-
- def parseAvFile(self, filepath):
- '''Parses an avp (project) or avl (preset package) file.
- Returns dictionary with section names as the keys, each one
- contains a list of tuples: (compName, version, compPresetDict)
- '''
- data = {}
- try:
- with open(filepath, 'r') as f:
- def parseLine(line):
- '''Decides if a file line is a section header'''
- validSections = ('Components')
- line = line.strip()
- newSection = ''
-
- if line.startswith('[') and line.endswith(']') \
- and line[1:-1] in validSections:
- newSection = line[1:-1]
-
- return line, newSection
-
- section = ''
- i = 0
- for line in f:
- line, newSection = parseLine(line)
- if newSection:
- section = str(newSection)
- data[section] = []
- continue
- if line and section == 'Components':
- if i == 0:
- lastCompName = str(line)
- i += 1
- elif i == 1:
- lastCompVers = str(line)
- i += 1
- elif i == 2:
- lastCompPreset = Core.presetFromString(line)
- data[section].append(
- (lastCompName,
- lastCompVers,
- lastCompPreset)
- )
- i = 0
- return 0, data
- except:
- return 1, sys.exc_info()
-
- def importPreset(self, filepath):
- errcode, data = self.parseAvFile(filepath)
- returnList = []
- if errcode == 0:
- name, vers, preset = data['Components'][0]
- presetName = preset['preset'] \
- if preset['preset'] else os.path.basename(filepath)[:-4]
- newPath = os.path.join(
- self.presetDir,
- name,
- vers,
- presetName
- )
- if os.path.exists(newPath):
- return False, newPath
- preset['preset'] = presetName
- self.createPresetFile(
- name, vers, presetName, preset
- )
- return True, presetName
- elif errcode == 1:
- # TODO: an error message
- return False, ''
-
- def exportPreset(self, exportPath, compName, vers, origName):
- internalPath = os.path.join(self.presetDir, compName, str(vers), origName)
- if not os.path.exists(internalPath):
- return
- if os.path.exists(exportPath):
- os.remove(exportPath)
- with open(internalPath, 'r') as f:
- internalData = [line for line in f]
- try:
- saveValueStore = Core.presetFromString(internalData[0].strip())
- self.createPresetFile(
- compName, vers,
- origName, saveValueStore,
- exportPath
- )
- return True
- except:
- return False
-
- def createPresetFile(
- self, compName, vers, presetName, saveValueStore, filepath=''):
- '''Create a preset file (.avl) at filepath using args.
- Or if filepath is empty, create an internal preset using args'''
- if not filepath:
- dirname = os.path.join(self.presetDir, compName, str(vers))
- if not os.path.exists(dirname):
- os.makedirs(dirname)
- filepath = os.path.join(dirname, presetName)
- internal = True
- else:
- if not filepath.endswith('.avl'):
- filepath += '.avl'
- internal = False
-
- with open(filepath, 'w') as f:
- if not internal:
- f.write('[Components]\n')
- f.write('%s\n' % compName)
- f.write('%s\n' % str(vers))
- f.write(Core.presetToString(saveValueStore))
-
- def createProjectFile(self, filepath):
- '''Create a project file (.avp) using the current program state'''
- try:
- if not filepath.endswith(".avp"):
- filepath += '.avp'
- if os.path.exists(filepath):
- os.remove(filepath)
- with open(filepath, 'w') as f:
- print('creating %s' % filepath)
- f.write('[Components]\n')
- for comp in self.selectedComponents:
- saveValueStore = comp.savePreset()
- f.write('%s\n' % str(comp))
- f.write('%s\n' % str(comp.version()))
- f.write('%s\n' % Core.presetToString(saveValueStore))
- return True
- except:
- return False
-
- def loadEncoderOptions(self):
- file_path = os.path.join(self.wd, 'encoder-options.json')
- with open(file_path) as json_file:
- self.encoder_options = json.load(json_file)
-
- def findFfmpeg(self):
- if sys.platform == "win32":
- return "ffmpeg.exe"
- else:
- try:
- with open(os.devnull, "w") as f:
- sp.check_call(['ffmpeg', '-version'], stdout=f, stderr=f)
- return "ffmpeg"
- except:
- return "avconv"
-
- def readAudioFile(self, filename, parent):
- command = [self.FFMPEG_BIN, '-i', filename]
-
- try:
- fileInfo = sp.check_output(command, stderr=sp.STDOUT, shell=False)
- except sp.CalledProcessError as ex:
- fileInfo = ex.output
- pass
-
- info = fileInfo.decode("utf-8").split('\n')
- for line in info:
- if 'Duration' in line:
- d = line.split(',')[0]
- d = d.split(' ')[3]
- d = d.split(':')
- duration = float(d[0])*3600 + float(d[1])*60 + float(d[2])
-
- command = [
- self.FFMPEG_BIN,
- '-i', filename,
- '-f', 's16le',
- '-acodec', 'pcm_s16le',
- '-ar', '44100', # ouput will have 44100 Hz
- '-ac', '1', # mono (set to '2' for stereo)
- '-']
- in_pipe = sp.Popen(
- command, stdout=sp.PIPE, stderr=sp.DEVNULL, bufsize=10**8)
-
- completeAudioArray = numpy.empty(0, dtype="int16")
-
- progress = 0
- lastPercent = None
- while True:
- if self.canceled:
- break
- # read 2 seconds of audio
- progress = progress + 4
- raw_audio = in_pipe.stdout.read(88200*4)
- if len(raw_audio) == 0:
- break
- audio_array = numpy.fromstring(raw_audio, dtype="int16")
- completeAudioArray = numpy.append(completeAudioArray, audio_array)
-
- percent = int(100*(progress/duration))
- if percent >= 100:
- percent = 100
-
- if lastPercent != percent:
- string = 'Loading audio file: '+str(percent)+'%'
- parent.progressBarSetText.emit(string)
- parent.progressBarUpdate.emit(percent)
-
- lastPercent = percent
-
- in_pipe.kill()
- in_pipe.wait()
-
- # add 0s the end
- completeAudioArrayCopy = numpy.zeros(
- len(completeAudioArray) + 44100, dtype="int16")
- completeAudioArrayCopy[:len(completeAudioArray)] = completeAudioArray
- completeAudioArray = completeAudioArrayCopy
-
- return completeAudioArray
-
- def cancel(self):
- self.canceled = True
-
- def reset(self):
- self.canceled = False
-
- @staticmethod
- def badName(name):
- '''Returns whether a name contains non-alphanumeric chars'''
- return any([letter in string.punctuation for letter in name])
-
- @staticmethod
- def presetToString(dictionary):
- '''Alphabetizes a dict into OrderedDict & returns string repr'''
- return repr(OrderedDict(sorted(dictionary.items(), key=lambda t: t[0])))
-
- @staticmethod
- def presetFromString(string):
- '''Turns a string repr of OrderedDict into a regular dict'''
- return dict(eval(string))
-
- @staticmethod
- def appendUppercase(lst):
- for form, i in zip(lst, range(len(lst))):
- lst.append(form.upper())
- return lst
diff --git a/encoder-options.json b/encoder-options.json
deleted file mode 100644
index 78bc940..0000000
--- a/encoder-options.json
+++ /dev/null
@@ -1,130 +0,0 @@
-{
- "containers":[
- {
- "name": "MP4",
- "container": "mp4",
- "default-vcodec": "H264",
- "default-acodec": "AAC",
- "video-codecs": [
- "H264",
- "H264 (nvenc)",
- "MPEG4"
- ],
- "audio-codecs": [
- "AAC",
- "AC3",
- "MP3"
- ]
- },
- {
- "name": "MOV",
- "container": "mov",
- "default-vcodec": "H264",
- "default-acodec": "AAC",
- "video-codecs": [
- "H264",
- "H264 (nvenc)",
- "MPEG4",
- "XVID"
- ],
- "audio-codecs": [
- "AAC",
- "AC3",
- "MP3",
- "PCM s16 LE"
- ]
- },
- {
- "name": "MKV",
- "container": "matroska",
- "default-vcodec": "H264",
- "default-acodec": "AAC",
- "video-codecs": [
- "H264",
- "H264 (nvenc)",
- "MPEG4",
- "MPEG2",
- "DV",
- "WMV"
- ],
- "audio-codecs": [
- "AAC",
- "AC3",
- "MP3",
- "PCM s16 LE",
- "WMA"
- ]
- },
- {
- "name": "AVI",
- "container": "avi",
- "default-vcodec": "H264",
- "default-acodec": "AAC",
- "video-codecs": [
- "H264",
- "H264 (nvenc)",
- "MPEG4",
- "MPEG2",
- "DV",
- "WMV"
- ],
- "audio-codecs": [
- "AAC",
- "AC3",
- "MP3",
- "PCM s16 LE",
- "WMA"
- ]
- },
- {
- "name": "WEBM",
- "container": "webm",
- "default-vcodec": "VP9",
- "default-acodec": "Vorbis",
- "video-codecs": [
- "VP9",
- "VP8"
- ],
- "audio-codecs": [
- "Vorbis"
- ]
- },
- {
- "name": "FLV",
- "container": "flv",
- "default-vcodec": "FLV",
- "default-acodec": "Vorbis",
- "video-codecs": [
- "Sorenson (flv)",
- "H264",
- "H264 (nvenc)",
- "MPEG4"
- ],
- "audio-codecs": [
- "MP3",
- "PCM s16 LE",
- "Vorbis"
- ]
- }
- ],
- "video-codecs":{
- "H264": ["libx264"],
- "H264 (nvenc)": ["h264_nvenc", "nvenc_h264"],
- "MPEG4": ["mpeg4"],
- "VP9": ["libvpx-vp9"],
- "VP8": ["libvpx"],
- "XVID": ["libxvid"],
- "Sorenson (flv)": ["flv"],
- "MPEG2": ["mp2video"],
- "DV": ["dvvideo"],
- "WMV": ["wmv2"]
- },
- "audio-codecs": {
- "AAC": ["libfdk_aac", "aac"],
- "AC3": ["ac3"],
- "MP3": ["libmp3lame"],
- "PCM s16 LE": ["pcm_s16le"],
- "WMA": ["wmav2"],
- "Vorbis": ["libvorbis"]
- }
-}
\ No newline at end of file
diff --git a/freeze.py b/freeze.py
new file mode 100644
index 0000000..48034dc
--- /dev/null
+++ b/freeze.py
@@ -0,0 +1,51 @@
+from cx_Freeze import setup, Executable
+import sys
+
+# Dependencies are automatically detected, but it might need
+# fine tuning.
+
+buildOptions = dict(
+ packages=[],
+ excludes=[
+ "apport",
+ "apt",
+ "curses",
+ "distutils",
+ "email",
+ "html",
+ "http",
+ "xmlrpc",
+ "nose"
+ ],
+ include_files=[
+ "mainwindow.ui",
+ "presetmanager.ui",
+ "background.png",
+ "encoder-options.json",
+ "components/"
+ ],
+ includes=[
+ 'numpy.core._methods',
+ 'numpy.lib.format'
+ ]
+)
+
+
+base = 'Win32GUI' if sys.platform == 'win32' else None
+
+executables = [
+ Executable(
+ 'main.py',
+ base=base,
+ targetName='audio-visualizer-python'
+ )
+]
+
+
+setup(
+ name='audio-visualizer-python',
+ version='1.0',
+ description='GUI tool to render visualization videos of audio files',
+ options=dict(build_exe=buildOptions),
+ executables=executables
+)
diff --git a/main.py b/main.py
deleted file mode 100644
index 106bd29..0000000
--- a/main.py
+++ /dev/null
@@ -1,88 +0,0 @@
-from PyQt4 import QtGui, uic
-import sys
-import os
-
-import core
-import preview_thread
-import video_thread
-
-
-def LoadDefaultSettings(self):
- self.resolutions = [
- '1920x1080',
- '1280x720',
- '854x480'
- ]
-
- default = {
- "outputWidth": 1280,
- "outputHeight": 720,
- "outputFrameRate": 30,
- "outputAudioCodec": "AAC",
- "outputAudioBitrate": "192",
- "outputVideoCodec": "H264",
- "outputVideoBitrate": "2500",
- "outputVideoFormat": "yuv420p",
- "outputPreset": "medium",
- "outputFormat": "mp4",
- "outputContainer": "MP4",
- "projectDir": os.path.join(self.dataDir, 'projects'),
- }
-
- for parm, value in default.items():
- #print(parm, self.settings.value(parm))
- if self.settings.value(parm) is None:
- self.settings.setValue(parm, value)
-
-if __name__ == "__main__":
- mode = 'gui'
- if len(sys.argv) > 2:
- mode = 'cmd'
-
- elif len(sys.argv) == 2:
- if sys.argv[1].startswith('-'):
- mode = 'cmd'
- else:
- # opening a project file with gui
- proj = sys.argv[1]
- else:
- # normal gui launch
- proj = None
-
- app = QtGui.QApplication(sys.argv)
- app.setApplicationName("audio-visualizer")
- app.setOrganizationName("audio-visualizer")
-
- if mode == 'cmd':
- from command import *
-
- main = Command()
-
- elif mode == 'gui':
- from mainwindow import *
- import atexit
- import signal
-
- if getattr(sys, 'frozen', False):
- # frozen
- wd = os.path.dirname(sys.executable)
- else:
- # unfrozen
- wd = os.path.dirname(os.path.realpath(__file__))
-
- window = uic.loadUi(os.path.join(wd, "mainwindow.ui"))
- # window.adjustSize()
- desc = QtGui.QDesktopWidget()
- dpi = desc.physicalDpiX()
-
- topMargin = 0 if (dpi == 96) else int(10 * (dpi / 96))
- window.resize(window.width() * (dpi / 96), window.height() * (dpi / 96))
- # window.verticalLayout_2.setContentsMargins(0, topMargin, 0, 0)
-
- main = MainWindow(window, proj)
-
- signal.signal(signal.SIGINT, main.cleanUp)
- atexit.register(main.cleanUp)
-
- # applicable to both modes
- sys.exit(app.exec_())
diff --git a/mainwindow.py b/mainwindow.py
deleted file mode 100644
index cdc2a51..0000000
--- a/mainwindow.py
+++ /dev/null
@@ -1,721 +0,0 @@
-from queue import Queue
-from PyQt4 import QtCore, QtGui, uic
-from PyQt4.QtCore import QSettings, Qt
-from PyQt4.QtGui import QMenu, QShortcut
-import sys
-import os
-import signal
-import filecmp
-import time
-
-import core
-import preview_thread
-import video_thread
-from presetmanager import PresetManager
-from main import LoadDefaultSettings
-
-
-class PreviewWindow(QtGui.QLabel):
- def __init__(self, parent, img):
- super(PreviewWindow, self).__init__()
- self.parent = parent
- self.setFrameStyle(QtGui.QFrame.StyledPanel)
- self.pixmap = QtGui.QPixmap(img)
-
- def paintEvent(self, event):
- size = self.size()
- painter = QtGui.QPainter(self)
- point = QtCore.QPoint(0, 0)
- scaledPix = self.pixmap.scaled(
- size, Qt.KeepAspectRatio, transformMode=Qt.SmoothTransformation)
-
- # start painting the label from left upper corner
- point.setX((size.width() - scaledPix.width())/2)
- point.setY((size.height() - scaledPix.height())/2)
- painter.drawPixmap(point, scaledPix)
-
- def changePixmap(self, img):
- self.pixmap = QtGui.QPixmap(img)
- self.repaint()
-
-
-class MainWindow(QtGui.QMainWindow):
-
- newTask = QtCore.pyqtSignal(list)
- processTask = QtCore.pyqtSignal()
- videoTask = QtCore.pyqtSignal(str, str, list)
-
- def __init__(self, window, project):
- QtGui.QMainWindow.__init__(self)
-
- # print('main thread id: {}'.format(QtCore.QThread.currentThreadId()))
- self.window = window
- self.core = core.Core()
-
- self.pages = [] # widgets of component settings
- self.lastAutosave = time.time()
-
- # Create data directory, load/create settings
- self.dataDir = self.core.dataDir
- self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
- self.settings = QSettings(
- os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat)
- LoadDefaultSettings(self)
- self.presetManager = PresetManager(
- uic.loadUi(
- os.path.join(self.core.wd, 'presetmanager.ui')), self)
-
- if not os.path.exists(self.dataDir):
- os.makedirs(self.dataDir)
- for neededDirectory in (
- self.core.presetDir, self.settings.value("projectDir")):
- if not os.path.exists(neededDirectory):
- os.mkdir(neededDirectory)
-
- # Make queues/timers for the preview thread
- self.previewQueue = Queue()
- self.previewThread = QtCore.QThread(self)
- self.previewWorker = preview_thread.Worker(self, self.previewQueue)
- self.previewWorker.moveToThread(self.previewThread)
- self.previewWorker.imageCreated.connect(self.showPreviewImage)
- self.previewThread.start()
-
- self.timer = QtCore.QTimer(self)
- self.timer.timeout.connect(self.processTask.emit)
- self.timer.start(500)
-
- # Begin decorating the window and connecting events
- componentList = self.window.listWidget_componentList
-
- window.toolButton_selectAudioFile.clicked.connect(
- self.openInputFileDialog)
-
- window.toolButton_selectOutputFile.clicked.connect(
- self.openOutputFileDialog)
-
- window.progressBar_createVideo.setValue(0)
-
- window.pushButton_createVideo.clicked.connect(
- self.createAudioVisualisation)
-
- window.pushButton_Cancel.clicked.connect(self.stopVideo)
-
- for i, container in enumerate(self.core.encoder_options['containers']):
- window.comboBox_videoContainer.addItem(container['name'])
- if container['name'] == self.settings.value('outputContainer'):
- selectedContainer = i
-
- window.comboBox_videoContainer.setCurrentIndex(selectedContainer)
- window.comboBox_videoContainer.currentIndexChanged.connect(
- self.updateCodecs
- )
-
- self.updateCodecs()
-
- for i in range(window.comboBox_videoCodec.count()):
- codec = window.comboBox_videoCodec.itemText(i)
- if codec == self.settings.value('outputVideoCodec'):
- window.comboBox_videoCodec.setCurrentIndex(i)
- #print(codec)
-
- for i in range(window.comboBox_audioCodec.count()):
- codec = window.comboBox_audioCodec.itemText(i)
- if codec == self.settings.value('outputAudioCodec'):
- window.comboBox_audioCodec.setCurrentIndex(i)
-
- window.comboBox_videoCodec.currentIndexChanged.connect(
- self.updateCodecSettings
- )
-
- window.comboBox_audioCodec.currentIndexChanged.connect(
- self.updateCodecSettings
- )
-
- vBitrate = int(self.settings.value('outputVideoBitrate'))
- aBitrate = int(self.settings.value('outputAudioBitrate'))
-
- window.spinBox_vBitrate.setValue(vBitrate)
- window.spinBox_aBitrate.setValue(aBitrate)
-
- window.spinBox_vBitrate.valueChanged.connect(self.updateCodecSettings)
- window.spinBox_aBitrate.valueChanged.connect(self.updateCodecSettings)
-
- self.previewWindow = PreviewWindow(self, os.path.join(
- self.core.wd, "background.png"))
- window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
-
- # Make component buttons
- self.compMenu = QMenu()
- for i, comp in enumerate(self.core.modules):
- action = self.compMenu.addAction(comp.Component.__doc__)
- action.triggered[()].connect(
- lambda item=i: self.core.insertComponent(0, item, self))
-
- self.window.pushButton_addComponent.setMenu(self.compMenu)
-
- componentList.dropEvent = self.dragComponent
- componentList.itemSelectionChanged.connect(
- self.changeComponentWidget)
-
- self.window.pushButton_removeComponent.clicked.connect(
- lambda _: self.removeComponent())
-
- componentList.setContextMenuPolicy(
- QtCore.Qt.CustomContextMenu)
- componentList.connect(
- componentList,
- QtCore.SIGNAL("customContextMenuRequested(QPoint)"),
- self.componentContextMenu)
-
- currentRes = str(self.settings.value('outputWidth'))+'x' + \
- str(self.settings.value('outputHeight'))
- for i, res in enumerate(self.resolutions):
- window.comboBox_resolution.addItem(res)
- if res == currentRes:
- currentRes = i
- window.comboBox_resolution.setCurrentIndex(currentRes)
- window.comboBox_resolution.currentIndexChanged.connect(
- self.updateResolution)
-
- self.window.pushButton_listMoveUp.clicked.connect(
- lambda: self.moveComponent(-1)
- )
- self.window.pushButton_listMoveDown.clicked.connect(
- lambda: self.moveComponent(1)
- )
-
- # Configure the Projects Menu
- self.projectMenu = QMenu()
- self.window.menuButton_newProject = self.projectMenu.addAction(
- "New Project")
- self.window.menuButton_newProject.triggered[()].connect(
- self.createNewProject)
-
- self.window.menuButton_openProject = self.projectMenu.addAction(
- "Open Project")
- self.window.menuButton_openProject.triggered[()].connect(
- self.openOpenProjectDialog)
-
- action = self.projectMenu.addAction("Save Project")
- action.triggered[()].connect(self.saveCurrentProject)
-
- action = self.projectMenu.addAction("Save Project As")
- action.triggered[()].connect(self.openSaveProjectDialog)
-
- self.window.pushButton_projects.setMenu(self.projectMenu)
-
- # Configure the Presets Button
- self.window.pushButton_presets.clicked.connect(
- self.openPresetManager
- )
-
- window.show()
-
- if project and project != self.autosavePath:
- if not project.endswith('.avp'):
- project += '.avp'
- # open a project from the commandline
- if not os.path.dirname(project):
- project = os.path.join(os.path.expanduser('~'), project)
- self.currentProject = project
- self.settings.setValue("currentProject", project)
- if os.path.exists(self.autosavePath):
- os.remove(self.autosavePath)
- else:
- # open the last currentProject from settings
- self.currentProject = self.settings.value("currentProject")
-
- # delete autosave if it's identical to this project
- if self.autosaveExists(identical=True):
- os.remove(self.autosavePath)
-
- if self.currentProject and os.path.exists(self.autosavePath):
- ch = self.showMessage(
- msg="Restore unsaved changes in project '%s'?"
- % os.path.basename(self.currentProject)[:-4],
- showCancel=True)
- if ch:
- self.saveProjectChanges()
- else:
- os.remove(self.autosavePath)
-
- self.openProject(self.currentProject, prompt=False)
- self.drawPreview(True)
-
- # Setup Hotkeys
- QtGui.QShortcut("Ctrl+S", self.window, self.saveCurrentProject)
- QtGui.QShortcut("Ctrl+A", self.window, self.openSaveProjectDialog)
- QtGui.QShortcut("Ctrl+O", self.window, self.openOpenProjectDialog)
- QtGui.QShortcut("Ctrl+N", self.window, self.createNewProject)
-
- QtGui.QShortcut("Ctrl+T", self.window, activated=lambda:
- self.window.pushButton_addComponent.click())
- QtGui.QShortcut("Ctrl+Space", self.window, activated=lambda:
- self.window.listWidget_componentList.setFocus())
- QtGui.QShortcut("Ctrl+Shift+S", self.window,
- self.presetManager.openSavePresetDialog)
- QtGui.QShortcut("Ctrl+Shift+C", self.window,
- self.presetManager.clearPreset)
-
- QtGui.QShortcut("Ctrl+Up", self.window,
- activated=lambda: self.moveComponent(-1))
- QtGui.QShortcut("Ctrl+Down", self.window,
- activated=lambda: self.moveComponent(1))
- QtGui.QShortcut("Ctrl+Home", self.window, self.moveComponentTop)
- QtGui.QShortcut("Ctrl+End", self.window, self.moveComponentBottom)
- QtGui.QShortcut("Ctrl+r", self.window, self.removeComponent)
-
- def cleanUp(self):
- self.timer.stop()
- self.previewThread.quit()
- self.previewThread.wait()
- self.autosave()
-
- def updateWindowTitle(self):
- appName = 'Audio Visualizer'
- if self.currentProject:
- appName += ' - %s' % \
- os.path.splitext(
- os.path.basename(self.currentProject))[0]
- self.window.setWindowTitle(appName)
-
- @QtCore.pyqtSlot(int, dict)
- def updateComponentTitle(self, pos, presetStore=False):
- if type(presetStore) == dict:
- name = presetStore['preset']
- if name == None or name not in self.core.savedPresets:
- modified = False
- else:
- modified = (presetStore != self.core.savedPresets[name])
- else:
- modified = bool(presetStore)
- if pos < 0:
- pos = len(self.core.selectedComponents)-1
- title = str(self.core.selectedComponents[pos])
- if self.core.selectedComponents[pos].currentPreset:
- title += ' - %s' % self.core.selectedComponents[pos].currentPreset
- if modified:
- title += '*'
- self.window.listWidget_componentList.item(pos).setText(title)
-
- def updateCodecs(self):
- containerWidget = self.window.comboBox_videoContainer
- vCodecWidget = self.window.comboBox_videoCodec
- aCodecWidget = self.window.comboBox_audioCodec
- index = containerWidget.currentIndex()
- name = containerWidget.itemText(index)
- self.settings.setValue('outputContainer', name)
-
- vCodecWidget.clear()
- aCodecWidget.clear()
-
- for container in self.core.encoder_options['containers']:
- if container['name'] == name:
- for vCodec in container['video-codecs']:
- vCodecWidget.addItem(vCodec)
- for aCodec in container['audio-codecs']:
- aCodecWidget.addItem(aCodec)
-
- def updateCodecSettings(self):
- vCodecWidget = self.window.comboBox_videoCodec
- vBitrateWidget = self.window.spinBox_vBitrate
- aBitrateWidget = self.window.spinBox_aBitrate
- aCodecWidget = self.window.comboBox_audioCodec
- currentVideoCodec = vCodecWidget.currentIndex()
- currentVideoCodec = vCodecWidget.itemText(currentVideoCodec)
- currentVideoBitrate = vBitrateWidget.value()
- currentAudioCodec = aCodecWidget.currentIndex()
- currentAudioCodec = aCodecWidget.itemText(currentAudioCodec)
- currentAudioBitrate = aBitrateWidget.value()
- self.settings.setValue('outputVideoCodec', currentVideoCodec)
- self.settings.setValue('outputAudioCodec', currentAudioCodec)
- self.settings.setValue('outputVideoBitrate', currentVideoBitrate)
- self.settings.setValue('outputAudioBitrate', currentAudioBitrate)
-
- def autosave(self, force=False):
- if not self.currentProject:
- if os.path.exists(self.autosavePath):
- os.remove(self.autosavePath)
- elif force or time.time() - self.lastAutosave >= 2.0:
- self.core.createProjectFile(self.autosavePath)
- self.lastAutosave = time.time()
-
- def autosaveExists(self, identical=True):
- try:
- if self.currentProject and os.path.exists(self.autosavePath) \
- and filecmp.cmp(
- self.autosavePath, self.currentProject) == identical:
- return True
- except FileNotFoundError:
- print('project file couldn\'t be located:', self.currentProject)
- return identical
- return False
-
- def saveProjectChanges(self):
- try:
- os.remove(self.currentProject)
- os.rename(self.autosavePath, self.currentProject)
- return True
- except (FileNotFoundError, IsADirectoryError) as e:
- self.showMessage(
- msg='Project file couldn\'t be saved.',
- detail=str(e))
- return False
-
- def openInputFileDialog(self):
- inputDir = self.settings.value("inputDir", os.path.expanduser("~"))
-
- fileName = QtGui.QFileDialog.getOpenFileName(
- self.window, "Open Audio File",
- inputDir, "Audio Files (%s)" % " ".join(self.core.audioFormats))
-
- if not fileName == "":
- self.settings.setValue("inputDir", os.path.dirname(fileName))
- self.window.lineEdit_audioFile.setText(fileName)
-
- def openOutputFileDialog(self):
- outputDir = self.settings.value("outputDir", os.path.expanduser("~"))
-
- fileName = QtGui.QFileDialog.getSaveFileName(
- self.window, "Set Output Video File",
- outputDir,
- "Video Files (%s);; All Files (*)" % " ".join(self.core.videoFormats))
-
- if not fileName == "":
- self.settings.setValue("outputDir", os.path.dirname(fileName))
- self.window.lineEdit_outputFile.setText(fileName)
-
- def stopVideo(self):
- print('stop')
- self.videoWorker.cancel()
- self.canceled = True
-
- def createAudioVisualisation(self):
- # create output video if mandatory settings are filled in
- if self.window.lineEdit_audioFile.text() and \
- self.window.lineEdit_outputFile.text():
- self.canceled = False
- self.progressBarUpdated(-1)
- self.videoThread = QtCore.QThread(self)
- self.videoWorker = video_thread.Worker(self)
- self.videoWorker.moveToThread(self.videoThread)
- self.videoWorker.videoCreated.connect(self.videoCreated)
- self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated)
- self.videoWorker.progressBarSetText.connect(
- self.progressBarSetText)
- self.videoWorker.imageCreated.connect(self.showPreviewImage)
- self.videoWorker.encoding.connect(self.changeEncodingStatus)
- self.videoThread.start()
- outputPath = self.window.lineEdit_outputFile.text()
- if not os.path.dirname(outputPath):
- outputPath = os.path.join(
- os.path.expanduser("~"), outputPath)
- self.videoTask.emit(
- self.window.lineEdit_audioFile.text(),
- outputPath,
- self.core.selectedComponents)
- else:
- self.showMessage(
- msg="You must select an audio file and output filename.")
-
- def changeEncodingStatus(self, status):
- if status:
- self.window.pushButton_createVideo.setEnabled(False)
- self.window.pushButton_Cancel.setEnabled(True)
- self.window.comboBox_resolution.setEnabled(False)
- self.window.stackedWidget.setEnabled(False)
- self.window.tab_encoderSettings.setEnabled(False)
- self.window.label_audioFile.setEnabled(False)
- self.window.toolButton_selectAudioFile.setEnabled(False)
- self.window.label_outputFile.setEnabled(False)
- self.window.toolButton_selectOutputFile.setEnabled(False)
- self.window.lineEdit_audioFile.setEnabled(False)
- self.window.lineEdit_outputFile.setEnabled(False)
- self.window.pushButton_addComponent.setEnabled(False)
- self.window.pushButton_removeComponent.setEnabled(False)
- self.window.pushButton_listMoveDown.setEnabled(False)
- self.window.pushButton_listMoveUp.setEnabled(False)
- self.window.listWidget_componentList.setEnabled(False)
- self.window.menuButton_newProject.setEnabled(False)
- self.window.menuButton_openProject.setEnabled(False)
- else:
- self.window.pushButton_createVideo.setEnabled(True)
- self.window.pushButton_Cancel.setEnabled(False)
- self.window.comboBox_resolution.setEnabled(True)
- self.window.stackedWidget.setEnabled(True)
- self.window.tab_encoderSettings.setEnabled(True)
- self.window.label_audioFile.setEnabled(True)
- self.window.toolButton_selectAudioFile.setEnabled(True)
- self.window.lineEdit_audioFile.setEnabled(True)
- self.window.label_outputFile.setEnabled(True)
- self.window.toolButton_selectOutputFile.setEnabled(True)
- self.window.lineEdit_outputFile.setEnabled(True)
- self.window.pushButton_addComponent.setEnabled(True)
- self.window.pushButton_removeComponent.setEnabled(True)
- self.window.pushButton_listMoveDown.setEnabled(True)
- self.window.pushButton_listMoveUp.setEnabled(True)
- self.window.listWidget_componentList.setEnabled(True)
- self.window.menuButton_newProject.setEnabled(True)
- self.window.menuButton_openProject.setEnabled(True)
- self.drawPreview(True)
-
- def progressBarUpdated(self, value):
- self.window.progressBar_createVideo.setValue(value)
-
- def progressBarSetText(self, value):
- self.window.progressBar_createVideo.setFormat(value)
-
- def videoCreated(self):
- self.videoThread.quit()
- self.videoThread.wait()
-
- def updateResolution(self):
- resIndex = int(self.window.comboBox_resolution.currentIndex())
- res = self.resolutions[resIndex].split('x')
- self.settings.setValue('outputWidth', res[0])
- self.settings.setValue('outputHeight', res[1])
- self.drawPreview()
-
- def drawPreview(self, force=False):
- self.newTask.emit(self.core.selectedComponents)
- # self.processTask.emit()
- self.autosave(force)
-
- def showPreviewImage(self, image):
- self.previewWindow.changePixmap(image)
-
- def insertComponent(self, index):
- componentList = self.window.listWidget_componentList
- stackedWidget = self.window.stackedWidget
-
- componentList.insertItem(
- index,
- self.core.selectedComponents[index].__doc__)
- componentList.setCurrentRow(index)
-
- # connect to signal that adds an asterisk when modified
- self.core.selectedComponents[index].modified.connect(
- self.updateComponentTitle)
-
- self.pages.insert(index, self.core.selectedComponents[index].page)
- stackedWidget.insertWidget(index, self.pages[index])
- stackedWidget.setCurrentIndex(index)
-
- return index
-
- def removeComponent(self):
- componentList = self.window.listWidget_componentList
-
- for selected in componentList.selectedItems():
- index = componentList.row(selected)
- self.window.stackedWidget.removeWidget(self.pages[index])
- componentList.takeItem(index)
- self.core.removeComponent(index)
- self.pages.pop(index)
- self.changeComponentWidget()
- self.drawPreview()
-
- def moveComponent(self, change):
- '''Moves a component relatively from its current position'''
- componentList = self.window.listWidget_componentList
- stackedWidget = self.window.stackedWidget
-
- row = componentList.currentRow()
- newRow = row + change
- if newRow > -1 and newRow < componentList.count():
- self.core.moveComponent(row, newRow)
-
- # update widgets
- page = self.pages.pop(row)
- self.pages.insert(newRow, page)
- item = componentList.takeItem(row)
- newItem = componentList.insertItem(newRow, item)
- widget = stackedWidget.removeWidget(page)
- stackedWidget.insertWidget(newRow, page)
- componentList.setCurrentRow(newRow)
- stackedWidget.setCurrentIndex(newRow)
- self.drawPreview()
-
- def moveComponentTop(self):
- componentList = self.window.listWidget_componentList
- row = -componentList.currentRow()
- self.moveComponent(row)
-
- def moveComponentBottom(self):
- componentList = self.window.listWidget_componentList
- row = len(componentList)-componentList.currentRow()-1
- self.moveComponent(row)
-
- def dragComponent(self, event):
- '''Drop event for the component listwidget'''
- componentList = self.window.listWidget_componentList
-
- modelIndexes = [ \
- componentList.model().index(i) \
- for i in range(componentList.count()) \
- ]
- rects = [ \
- componentList.visualRect(modelIndex) \
- for modelIndex in modelIndexes \
- ]
-
- rowPos = [rect.contains(event.pos()) for rect in rects]
- if not any(rowPos):
- return
-
- i = rowPos.index(True)
- change = (componentList.currentRow() - i) * -1
- self.moveComponent(change)
-
- def changeComponentWidget(self):
- selected = self.window.listWidget_componentList.selectedItems()
- if selected:
- index = self.window.listWidget_componentList.row(selected[0])
- self.window.stackedWidget.setCurrentIndex(index)
-
- def openPresetManager(self):
- '''Preset manager for importing, exporting, renaming, deleting'''
- self.presetManager.show()
-
- def clear(self):
- '''Get a blank slate'''
- self.core.clearComponents()
- self.window.listWidget_componentList.clear()
- for widget in self.pages:
- self.window.stackedWidget.removeWidget(widget)
- self.pages = []
-
- def createNewProject(self):
- self.openSaveChangesDialog('starting a new project')
-
- self.clear()
- self.currentProject = None
- self.settings.setValue("currentProject", None)
- self.drawPreview(True)
- self.updateWindowTitle()
-
- def saveCurrentProject(self):
- if self.currentProject:
- self.core.createProjectFile(self.currentProject)
- else:
- self.openSaveProjectDialog()
-
- def openSaveChangesDialog(self, phrase):
- success = True
- if self.autosaveExists(identical=False):
- ch = self.showMessage(
- msg="You have unsaved changes in project '%s'. "
- "Save before %s?" % \
- (os.path.basename(self.currentProject)[:-4],
- phrase),
- showCancel=True)
- if ch:
- success = self.saveProjectChanges()
-
- if success and os.path.exists(self.autosavePath):
- os.remove(self.autosavePath)
-
- def openSaveProjectDialog(self):
- filename = QtGui.QFileDialog.getSaveFileName(
- self.window, "Create Project File",
- self.settings.value("projectDir"),
- "Project Files (*.avp)")
- if not filename:
- return
- if not filename.endswith(".avp"):
- filename += '.avp'
- self.settings.setValue("projectDir", os.path.dirname(filename))
- self.settings.setValue("currentProject", filename)
- self.currentProject = filename
- self.updateWindowTitle()
- self.core.createProjectFile(filename)
-
- def openOpenProjectDialog(self):
- filename = QtGui.QFileDialog.getOpenFileName(
- self.window, "Open Project File",
- self.settings.value("projectDir"),
- "Project Files (*.avp)")
- self.openProject(filename)
-
- def openProject(self, filepath, prompt=True):
- if not filepath or not os.path.exists(filepath) \
- or not filepath.endswith('.avp'):
- self.updateWindowTitle()
- return
-
- self.clear()
- # ask to save any changes that are about to get deleted
- if prompt:
- self.openSaveChangesDialog('opening another project')
-
- self.currentProject = filepath
- self.updateWindowTitle()
- self.settings.setValue("currentProject", filepath)
- self.settings.setValue("projectDir", os.path.dirname(filepath))
- # actually load the project using core method
- self.core.openProject(self, filepath)
- if self.window.listWidget_componentList.count() == 0:
- self.drawPreview()
- self.autosave(True)
-
- def showMessage(self, **kwargs):
- parent = kwargs['parent'] if 'parent' in kwargs else self.window
- msg = QtGui.QMessageBox(parent)
- msg.setModal(True)
- msg.setText(kwargs['msg'])
- msg.setIcon(
- kwargs['icon'] if 'icon' in kwargs else QtGui.QMessageBox.Information)
- msg.setDetailedText(kwargs['detail'] if 'detail' in kwargs else None)
- if 'showCancel'in kwargs and kwargs['showCancel']:
- msg.setStandardButtons(
- QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel)
- else:
- msg.setStandardButtons(QtGui.QMessageBox.Ok)
- ch = msg.exec_()
- if ch == 1024:
- return True
- return False
-
- def componentContextMenu(self, QPos):
- '''Appears when right-clicking a component in the list'''
- componentList = self.window.listWidget_componentList
- if not componentList.selectedItems():
- return
-
- # don't show menu if clicking empty space
- parentPosition = componentList.mapToGlobal(QtCore.QPoint(0, 0))
- index = componentList.currentRow()
- modelIndex = componentList.model().index(index)
- if not componentList.visualRect(modelIndex).contains(QPos):
- return
-
- self.presetManager.findPresets()
- self.menu = QtGui.QMenu()
- menuItem = self.menu.addAction("Save Preset")
- menuItem.triggered.connect(
- self.presetManager.openSavePresetDialog
- )
-
- # submenu for opening presets
- try:
- presets = self.presetManager.presets[str(self.core.selectedComponents[index])]
- self.submenu = QtGui.QMenu("Open Preset")
- self.menu.addMenu(self.submenu)
-
- for version, presetName in presets:
- menuItem = self.submenu.addAction(presetName)
- menuItem.triggered.connect(
- lambda _, presetName=presetName:
- self.presetManager.openPreset(presetName)
- )
- except KeyError:
- pass
-
- if self.core.selectedComponents[index].currentPreset:
- menuItem = self.menu.addAction("Clear Preset")
- menuItem.triggered.connect(
- self.presetManager.clearPreset
- )
-
- self.menu.move(parentPosition + QPos)
- self.menu.show()
diff --git a/mainwindow.ui b/mainwindow.ui
deleted file mode 100644
index 4a12fd5..0000000
--- a/mainwindow.ui
+++ /dev/null
@@ -1,809 +0,0 @@
-
-
- MainWindow
-
-
-
- 0
- 0
- 1008
- 575
-
-
-
-
- 0
- 0
-
-
-
-
- 0
- 0
-
-
-
- MainWindow
-
-
-
-
- 0
- 0
-
-
-
- false
-
-
-
- 9
-
-
- 0
-
- -
-
-
-
-
-
- Qt::Vertical
-
-
- QSizePolicy::MinimumExpanding
-
-
-
- 0
- 360
-
-
-
-
- -
-
-
- QLayout::SetDefaultConstraint
-
-
- 0
-
-
-
-
-
- Qt::Horizontal
-
-
- QSizePolicy::MinimumExpanding
-
-
-
- 420
- 0
-
-
-
-
-
-
- -
-
-
- QLayout::SetMinimumSize
-
-
- 3
-
-
-
-
-
- QLayout::SetMinimumSize
-
-
- 3
-
-
-
-
-
- QLayout::SetMinimumSize
-
-
-
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 140
- 20
-
-
-
-
- -
-
-
- Projects
-
-
-
- -
-
-
- Presets
-
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Minimum
-
-
-
- 20
- 2
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 0
-
-
-
-
- 16777215
- 16777215
-
-
-
- true
-
-
- QFrame::StyledPanel
-
-
- QFrame::Sunken
-
-
- 1
-
-
- true
-
-
- true
-
-
- false
-
-
- QAbstractItemView::InternalMove
-
-
- Qt::MoveAction
-
-
-
- -
-
-
-
-
-
- Add
-
-
-
- -
-
-
- Remove
-
-
-
- -
-
-
- Up
-
-
-
- -
-
-
- Down
-
-
-
-
-
-
-
- -
-
-
- 4
-
-
- 2
-
-
-
-
-
-
-
- -
-
-
- QLayout::SetFixedSize
-
-
- 4
-
-
- 0
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 500
- 0
-
-
-
-
- 16777215
- 180
-
-
-
- QTabWidget::North
-
-
- QTabWidget::Rounded
-
-
- 0
-
-
-
- Export Video
-
-
-
- 10
-
-
-
-
-
- 0
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 85
- 0
-
-
-
-
- 80
- 16777215
-
-
-
-
- 80
- 0
-
-
-
- Audio File
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 28
-
-
-
-
- 16777215
- 28
-
-
-
-
- 0
- 0
-
-
-
-
- -
-
-
-
- 0
- 28
-
-
-
-
- 16777215
- 28
-
-
-
- ...
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 85
- 0
-
-
-
-
- 0
- 0
-
-
-
- Output File
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 28
-
-
-
-
- 16777215
- 28
-
-
-
-
- -
-
-
-
- 0
- 28
-
-
-
-
- 16777215
- 28
-
-
-
- ...
-
-
-
-
-
-
-
- -
-
-
- 0
-
-
-
-
-
-
- 0
- 0
-
-
-
- 24
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Minimum
-
-
-
- 10
- 20
-
-
-
-
- -
-
-
- Create Video
-
-
-
- -
-
-
- false
-
-
- Cancel
-
-
-
-
-
-
-
-
-
-
-
- Encoder Settings
-
-
-
- 10
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 85
- 0
-
-
-
- Container
-
-
-
- -
-
-
-
- 150
- 0
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Minimum
-
-
-
- 5
- 5
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Resolution
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 0
-
-
-
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 85
- 0
-
-
-
- Video Codec
-
-
-
- -
-
-
-
- 150
- 0
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 5
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Video Bitrate (Kbps)
-
-
-
- -
-
-
- 99999
-
-
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 85
- 0
-
-
-
- Audio Codec
-
-
-
- -
-
-
-
- 150
- 0
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 10
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Audio Bitrate (Kbps)
-
-
-
- -
-
-
- 9999
-
-
-
-
-
-
-
-
-
- -
-
-
- QLayout::SetDefaultConstraint
-
-
-
-
-
- Qt::Horizontal
-
-
- QSizePolicy::MinimumExpanding
-
-
-
- 500
- 0
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 180
-
-
-
-
- 16777215
- 180
-
-
-
- -1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/presetmanager.py b/presetmanager.py
deleted file mode 100644
index 3b02714..0000000
--- a/presetmanager.py
+++ /dev/null
@@ -1,290 +0,0 @@
-from PyQt4 import QtGui, QtCore
-import string
-import os
-
-import core
-
-
-class PresetManager(QtGui.QDialog):
- def __init__(self, window, parent):
- super().__init__(parent.window)
- self.parent = parent
- self.core = parent.core
- self.settings = parent.settings
- self.presetDir = self.core.presetDir
- if not self.settings.value('presetDir'):
- self.settings.setValue(
- "presetDir",
- os.path.join(self.core.dataDir, 'projects'))
-
- self.findPresets()
-
- # window
- self.lastFilter = '*'
- self.presetRows = [] # list of (comp, vers, name) tuples
- self.window = window
- self.window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
-
- # connect button signals
- self.window.pushButton_delete.clicked.connect(self.openDeletePresetDialog)
- self.window.pushButton_rename.clicked.connect(self.openRenamePresetDialog)
- self.window.pushButton_import.clicked.connect(self.openImportDialog)
- self.window.pushButton_export.clicked.connect(self.openExportDialog)
- self.window.pushButton_close.clicked.connect(self.window.close)
-
- # create filter box and preset list
- self.drawFilterList()
- self.window.comboBox_filter.currentIndexChanged.connect(
- lambda: self.drawPresetList(
- self.window.comboBox_filter.currentText(), self.window.lineEdit_search.text()
- )
- )
-
- # make auto-completion for search bar
- self.autocomplete = QtGui.QStringListModel()
- completer = QtGui.QCompleter()
- completer.setModel(self.autocomplete)
- self.window.lineEdit_search.setCompleter(completer)
- self.window.lineEdit_search.textChanged.connect(
- lambda: self.drawPresetList(
- self.window.comboBox_filter.currentText(), self.window.lineEdit_search.text()
- )
- )
- self.drawPresetList('*')
-
- def show(self):
- '''Open a new preset manager window from the mainwindow'''
- self.findPresets()
- self.drawFilterList()
- self.drawPresetList('*')
- self.window.show()
-
- def findPresets(self):
- parseList = []
- for dirpath, dirnames, filenames in os.walk(self.presetDir):
- # anything without a subdirectory must be a preset folder
- if dirnames:
- continue
- for preset in filenames:
- compName = os.path.basename(os.path.dirname(dirpath))
- compVers = os.path.basename(dirpath)
- try:
- parseList.append((compName, int(compVers), preset))
- except ValueError:
- continue
- self.presets =\
- {
- compName : \
- [
- (vers, preset) \
- for name, vers, preset in parseList \
- if name == compName \
- ] \
- for compName, _, __ in parseList \
- }
-
- def drawPresetList(self, compFilter=None, presetFilter=''):
- self.window.listWidget_presets.clear()
- if compFilter:
- self.lastFilter = str(compFilter)
- else:
- compFilter = str(self.lastFilter)
- self.presetRows = []
- presetNames = []
- for component, presets in self.presets.items():
- if compFilter != '*' and component != compFilter:
- continue
- for vers, preset in presets:
- if not presetFilter or presetFilter in preset:
- self.window.listWidget_presets.addItem('%s: %s' % (component, preset))
- self.presetRows.append((component, vers, preset))
- if preset not in presetNames:
- presetNames.append(preset)
- self.autocomplete.setStringList(presetNames)
-
- def drawFilterList(self):
- self.window.comboBox_filter.clear()
- self.window.comboBox_filter.addItem('*')
- for component in self.presets:
- self.window.comboBox_filter.addItem(component)
-
- def clearPreset(self, compI=None):
- '''Functions on mainwindow level from the context menu'''
- compI = self.parent.window.listWidget_componentList.currentRow()
- self.core.clearPreset(compI, self.parent)
-
- def openSavePresetDialog(self):
- '''Functions on mainwindow level from the context menu'''
- window = self.parent.window
- selectedComponents = self.core.selectedComponents
- componentList = self.parent.window.listWidget_componentList
-
- if componentList.currentRow() == -1:
- return
- while True:
- index = componentList.currentRow()
- currentPreset = selectedComponents[index].currentPreset
- newName, OK = QtGui.QInputDialog.getText(
- self.parent.window,
- 'Audio Visualizer',
- 'New Preset Name:',
- QtGui.QLineEdit.Normal,
- currentPreset
- )
- if OK:
- if core.Core.badName(newName):
- self.warnMessage(self.parent.window)
- continue
- if newName:
- if index != -1:
- selectedComponents[index].currentPreset = newName
- saveValueStore = \
- selectedComponents[index].savePreset()
- componentName = str(selectedComponents[index]).strip()
- vers = selectedComponents[index].version()
- self.createNewPreset(
- componentName, vers, newName,
- saveValueStore, window=self.parent.window)
- self.openPreset(newName)
- break
-
- def createNewPreset(
- self, compName, vers, filename, saveValueStore, **kwargs):
- path = os.path.join(self.presetDir, compName, str(vers), filename)
- if self.presetExists(path, **kwargs):
- return
- self.core.createPresetFile(compName, vers, filename, saveValueStore)
-
- def presetExists(self, path, **kwargs):
- if os.path.exists(path):
- window = self.window \
- if 'window' not in kwargs else kwargs['window']
- ch = self.parent.showMessage(
- msg="%s already exists! Overwrite it?" %
- os.path.basename(path),
- showCancel=True,
- icon=QtGui.QMessageBox.Warning,
- parent=window)
- if not ch:
- # user clicked cancel
- return True
-
- return False
-
- def openPreset(self, presetName):
- componentList = self.parent.window.listWidget_componentList
- selectedComponents = self.parent.core.selectedComponents
-
- index = componentList.currentRow()
- if index == -1:
- return
- componentName = str(selectedComponents[index]).strip()
- version = selectedComponents[index].version()
- dirname = os.path.join(self.presetDir, componentName, str(version))
- filepath = os.path.join(dirname, presetName)
- self.core.openPreset(filepath, index, presetName)
-
- self.parent.updateComponentTitle(index)
- self.parent.drawPreview()
-
- def openDeletePresetDialog(self):
- selected = self.window.listWidget_presets.selectedItems()
- if not selected:
- return
- row = self.window.listWidget_presets.row(selected[0])
- comp, vers, name = self.presetRows[row]
- ch = self.parent.showMessage(
- msg='Really delete %s?' % name,
- showCancel=True,
- icon=QtGui.QMessageBox.Warning,
- parent=self.window
- )
- if not ch:
- return
- self.deletePreset(comp, vers, name)
- self.findPresets()
- self.drawPresetList()
-
- def deletePreset(self, comp, vers, name):
- filepath = os.path.join(self.presetDir, comp, str(vers), name)
- os.remove(filepath)
-
- def warnMessage(self, window=None):
- print(window)
- self.parent.showMessage(
- msg='Preset names must contain only letters, '
- 'numbers, and spaces.',
- parent=window if window else self.window)
-
- def openRenamePresetDialog(self):
- presetList = self.window.listWidget_presets
- if presetList.currentRow() == -1:
- return
-
- while True:
- index = presetList.currentRow()
- newName, OK = QtGui.QInputDialog.getText(
- self.window,
- 'Preset Manager',
- 'Rename Preset:',
- QtGui.QLineEdit.Normal,
- self.presetRows[index][2]
- )
- if OK:
- if core.Core.badName(newName):
- self.warnMessage()
- continue
- if newName:
- comp, vers, oldName = self.presetRows[index]
- path = os.path.join(
- self.presetDir, comp, str(vers))
- newPath = os.path.join(path, newName)
- oldPath = os.path.join(path, oldName)
- if self.presetExists(newPath):
- return
- if os.path.exists(newPath):
- os.remove(newPath)
- os.rename(oldPath, newPath)
- self.findPresets()
- self.drawPresetList()
- break
-
- def openImportDialog(self):
- filename = QtGui.QFileDialog.getOpenFileName(
- self.window, "Import Preset File",
- self.settings.value("presetDir"),
- "Preset Files (*.avl)")
- if filename:
- # get installed path & ask user to overwrite if needed
- path = ''
- while True:
- if path:
- if self.presetExists(path):
- break
- else:
- if os.path.exists(path):
- os.remove(path)
- success, path = self.core.importPreset(filename)
- if success:
- break
-
- self.findPresets()
- self.drawPresetList()
- self.settings.setValue("presetDir", os.path.dirname(filename))
-
- def openExportDialog(self):
- if not self.window.listWidget_presets.selectedItems():
- return
- filename = QtGui.QFileDialog.getSaveFileName(
- self.window, "Export Preset",
- self.settings.value("presetDir"),
- "Preset Files (*.avl)")
- if filename:
- index = self.window.listWidget_presets.currentRow()
- comp, vers, name = self.presetRows[index]
- if not self.core.exportPreset(filename, comp, vers, name):
- self.parent.showMessage(
- msg='Couldn\'t export %s.' % filename,
- parent=self.window
- )
- self.settings.setValue("presetDir", os.path.dirname(filename))
diff --git a/presetmanager.ui b/presetmanager.ui
deleted file mode 100644
index 5257b1c..0000000
--- a/presetmanager.ui
+++ /dev/null
@@ -1,150 +0,0 @@
-
-
- presetmanager
-
-
- Qt::NonModal
-
-
- true
-
-
-
- 0
- 0
- 497
- 377
-
-
-
- Preset Manager
-
-
- -
-
-
-
-
-
-
-
-
- Filter by name
-
-
-
- -
-
-
-
- 200
- 0
-
-
-
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
- true
-
-
-
-
-
- -
-
-
- QLayout::SetMinimumSize
-
-
-
-
-
- Import
-
-
-
- -
-
-
- Export
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
- true
-
-
- Rename
-
-
-
- -
-
-
- Delete
-
-
-
-
-
- -
-
-
-
-
-
- <html><head/><body><p><span style=" font-size:10pt; font-style:italic;">Right-click components in the main window to create presets</span></p></body></html>
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
- Close
-
-
-
-
-
-
-
-
-
-
diff --git a/preview_thread.py b/preview_thread.py
deleted file mode 100644
index eabf715..0000000
--- a/preview_thread.py
+++ /dev/null
@@ -1,59 +0,0 @@
-from PyQt4 import QtCore, QtGui, uic
-from PyQt4.QtCore import pyqtSignal, pyqtSlot
-from PIL import Image
-from PIL.ImageQt import ImageQt
-import core
-from queue import Queue, Empty
-import os
-from copy import copy
-
-
-class Worker(QtCore.QObject):
-
- imageCreated = pyqtSignal(['QImage'])
-
- def __init__(self, parent=None, queue=None):
- QtCore.QObject.__init__(self)
- parent.newTask.connect(self.createPreviewImage)
- parent.processTask.connect(self.process)
- self.parent = parent
- self.core = core.Core()
- self.queue = queue
- self.core.settings = parent.settings
- self.stackedWidget = parent.window.stackedWidget
- self.background = Image.new("RGBA", (1920, 1080), (0, 0, 0, 0))
- self.background.paste(Image.open(os.path.join(
- self.core.wd, "background.png")))
-
- @pyqtSlot(str, list)
- def createPreviewImage(self, components):
- dic = {
- "components": components,
- }
- self.queue.put(dic)
-
- @pyqtSlot()
- def process(self):
- try:
- nextPreviewInformation = self.queue.get(block=False)
- while self.queue.qsize() >= 2:
- try:
- self.queue.get(block=False)
- except Empty:
- continue
-
- width = int(self.core.settings.value('outputWidth'))
- height = int(self.core.settings.value('outputHeight'))
- frame = copy(self.background)
- frame = frame.resize((width, height))
-
- components = nextPreviewInformation["components"]
- for component in reversed(components):
- frame = Image.alpha_composite(
- frame, component.previewRender(self))
-
- self._image = ImageQt(frame)
- self.imageCreated.emit(QtGui.QImage(self._image))
-
- except Empty:
- True
diff --git a/setup.py b/setup.py
index 48034dc..fde3461 100644
--- a/setup.py
+++ b/setup.py
@@ -1,51 +1,19 @@
-from cx_Freeze import setup, Executable
-import sys
-
-# Dependencies are automatically detected, but it might need
-# fine tuning.
-
-buildOptions = dict(
- packages=[],
- excludes=[
- "apport",
- "apt",
- "curses",
- "distutils",
- "email",
- "html",
- "http",
- "xmlrpc",
- "nose"
- ],
- include_files=[
- "mainwindow.ui",
- "presetmanager.ui",
- "background.png",
- "encoder-options.json",
- "components/"
- ],
- includes=[
- 'numpy.core._methods',
- 'numpy.lib.format'
- ]
-)
-
-
-base = 'Win32GUI' if sys.platform == 'win32' else None
-
-executables = [
- Executable(
- 'main.py',
- base=base,
- targetName='audio-visualizer-python'
- )
-]
-
-
-setup(
- name='audio-visualizer-python',
- version='1.0',
- description='GUI tool to render visualization videos of audio files',
- options=dict(build_exe=buildOptions),
- executables=executables
-)
++from setuptools import setup, find_packages
+
+ -# Dependencies are automatically detected, but it might need +setup(name='audio_visualizer_python',
+ -# fine tuning. + version='1.0',
+ -buildOptions = dict(packages = [], excludes = [ + description='a little GUI tool to render visualization \
+ - "apport", + videos of audio files',
+ - "apt", + license='MIT',
+ - "ctypes", + url='https://github.com/djfun/audio-visualizer-python',
+ - "curses", + packages=find_packages(),
+ - "distutils", + package_data={
+ - "email", + 'src': ['*'],
+ - "html", + },
+ - "http", + install_requires=['pillow-simd', 'numpy', ''],
+ - "json", + entry_points={
+ - "xmlrpc", + 'gui_scripts': [
+ - "nose" + 'audio-visualizer-python = avpython.main:main'
+ - ], include_files = ["main.ui"]) + ]
+ - + }
+ -import sys + )
\ No newline at end of file
diff --git a/src/background.png b/src/background.png
new file mode 100644
index 0000000..fb58593
Binary files /dev/null and b/src/background.png differ
diff --git a/src/command.py b/src/command.py
new file mode 100644
index 0000000..1a1e810
--- /dev/null
+++ b/src/command.py
@@ -0,0 +1,126 @@
+from PyQt4 import QtCore
+from PyQt4.QtCore import QSettings
+import argparse
+import os
+import sys
+
+import core
+import video_thread
+from main import LoadDefaultSettings
+
+
+class Command(QtCore.QObject):
+
+ videoTask = QtCore.pyqtSignal(str, str, list)
+
+ def __init__(self):
+ QtCore.QObject.__init__(self)
+ self.core = core.Core()
+ self.dataDir = self.core.dataDir
+ self.canceled = False
+
+ self.parser = argparse.ArgumentParser(
+ description='Create a visualization for an audio file',
+ epilog='EXAMPLE COMMAND: main.py myvideotemplate.avp '
+ '-i ~/Music/song.mp3 -o ~/video.mp4 '
+ '-c 0 image path=~/Pictures/thisWeeksPicture.jpg '
+ '-c 1 video "preset=My Logo" -c 2 vis layout=classic')
+ self.parser.add_argument(
+ '-i', '--input', metavar='SOUND',
+ help='input audio file')
+ self.parser.add_argument(
+ '-o', '--output', metavar='OUTPUT',
+ help='output video file')
+
+ # optional arguments
+ self.parser.add_argument(
+ 'projpath', metavar='path-to-project',
+ help='open a project file (.avp)', nargs='?')
+ self.parser.add_argument(
+ '-c', '--comp', metavar=('LAYER', 'ARG'),
+ help='first arg must be component NAME to insert at LAYER.'
+ '"help" for information about possible args for a component.',
+ nargs='*', action='append')
+
+ self.args = self.parser.parse_args()
+ self.settings = QSettings(
+ os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat)
+ LoadDefaultSettings(self)
+
+ if self.args.projpath:
+ self.core.openProject(self, self.args.projpath)
+ self.core.selectedComponents = list(
+ reversed(self.core.selectedComponents))
+ self.core.componentListChanged()
+
+ if self.args.comp:
+ for comp in self.args.comp:
+ pos = comp[0]
+ name = comp[1]
+ args = comp[2:]
+ try:
+ pos = int(pos)
+ except ValueError:
+ print(pos, 'is not a layer number.')
+ quit(1)
+ realName = self.parseCompName(name)
+ if not realName:
+ print(name, 'is not a valid component name.')
+ quit(1)
+ modI = self.core.moduleIndexFor(realName)
+ i = self.core.insertComponent(pos, modI, self)
+ for arg in args:
+ self.core.selectedComponents[i].command(arg)
+
+ if self.args.input and self.args.output:
+ self.createAudioVisualisation()
+ elif 'help' not in sys.argv:
+ self.parser.print_help()
+ quit(1)
+
+ def createAudioVisualisation(self):
+ self.videoThread = QtCore.QThread(self)
+ self.videoWorker = video_thread.Worker(self)
+ self.videoWorker.moveToThread(self.videoThread)
+ self.videoWorker.videoCreated.connect(self.videoCreated)
+
+ self.videoThread.start()
+ self.videoTask.emit(
+ self.args.input,
+ self.args.output,
+ list(reversed(self.core.selectedComponents))
+ )
+
+ def videoCreated(self):
+ self.videoThread.quit()
+ self.videoThread.wait()
+ quit(0)
+
+ def showMessage(self, **kwargs):
+ print(kwargs['msg'])
+ if 'detail' in kwargs:
+ print(kwargs['detail'])
+
+ def drawPreview(self, *args):
+ pass
+
+ def parseCompName(self, name):
+ '''Deduces a proper component name out of a commandline arg'''
+
+ if name.title() in self.core.compNames:
+ return name.title()
+ for compName in self.core.compNames:
+ if name.capitalize() in compName:
+ return compName
+
+ compFileNames = [ \
+ os.path.splitext(os.path.basename(
+ mod.__file__))[0] \
+ for mod in self.core.modules \
+ ]
+ for i, compFileName in enumerate(compFileNames):
+ if name.lower() in compFileName:
+ return self.core.compNames[i]
+ return
+
+ return None
diff --git a/src/components/__base__.py b/src/components/__base__.py
new file mode 100644
index 0000000..a4677b1
--- /dev/null
+++ b/src/components/__base__.py
@@ -0,0 +1,153 @@
+from PyQt5 import QtGui, QtCore, QtWidgets
+from PIL import Image
+import os
+
+
+class Component(QtCore.QObject):
+ '''A base class for components to inherit from'''
+
+ # modified = QtCore.pyqtSignal(int, bool)
+
+ def __init__(self, moduleIndex, compPos, core):
+ super().__init__()
+ self.currentPreset = None
+ self.moduleIndex = moduleIndex
+ self.compPos = compPos
+ self.core = core
+
+ def __str__(self):
+ return self.__doc__
+
+ def version(self):
+ # change this number to identify new versions of a component
+ return 1
+
+ def cancel(self):
+ # please stop any lengthy process in response to this variable
+ self.canceled = True
+
+ def reset(self):
+ self.canceled = False
+
+ def update(self):
+ self.modified.emit(self.compPos, self.savePreset())
+ # read your widget values, then call super().update()
+
+ def loadPreset(self, presetDict, presetName):
+ '''Subclasses take (presetDict, presetName=None) as args.
+ Must use super().loadPreset(presetDict, presetName) first,
+ then update self.page widgets using the preset dict.
+ '''
+ self.currentPreset = presetName \
+ if presetName != None else presetDict['preset']
+
+ def preFrameRender(self, **kwargs):
+ '''Triggered only before a video is exported (video_thread.py)
+ self.worker = the video thread worker
+ self.completeAudioArray = a list of audio samples
+ self.sampleSize = number of audio samples per video frame
+ self.progressBarUpdate = signal to set progress bar number
+ self.progressBarSetText = signal to set progress bar text
+ Use the latter two signals to update the MainProgram if needed
+ for a long initialization procedure (i.e., for a visualizer)
+ '''
+ for var, value in kwargs.items():
+ exec('self.%s = value' % var)
+
+ def command(self, arg):
+ '''Configure a component using argument from the commandline.
+ Use super().command(arg) at the end of a subclass's method,
+ if no arguments are found in that method first
+ '''
+ if arg.startswith('preset='):
+ _, preset = arg.split('=', 1)
+ path = os.path.join(self.core.getPresetDir(self), preset)
+ if not os.path.exists(path):
+ print('Couldn\'t locate preset "%s"' % preset)
+ quit(1)
+ else:
+ print('Opening "%s" preset on layer %s' % \
+ (preset, self.compPos))
+ self.core.openPreset(path, self.compPos, preset)
+ else:
+ print(
+ self.__doc__, 'Usage:\n'
+ 'Open a preset for this component:\n'
+ ' "preset=Preset Name"')
+ self.commandHelp()
+ quit(0)
+
+ def commandHelp(self):
+ '''Print help text for this Component's commandline arguments'''
+
+ def blankFrame(self, width, height):
+ return Image.new("RGBA", (width, height), (0, 0, 0, 0))
+
+ def pickColor(self):
+ '''Use color picker to get color input from the user,
+ and return this as an RGB string and QPushButton stylesheet.
+ In a subclass apply stylesheet to any color selection widgets
+ '''
+ dialog = QtGui.QColorDialog()
+ dialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True)
+ color = dialog.getColor()
+ if color.isValid():
+ RGBstring = '%s,%s,%s' % (
+ str(color.red()), str(color.green()), str(color.blue()))
+ btnStyle = "QPushButton{background-color: %s; outline: none;}" \
+ % color.name()
+ return RGBstring, btnStyle
+ else:
+ return None, None
+
+ def RGBFromString(self, string):
+ ''' Turns an RGB string like "255, 255, 255" into a tuple '''
+ try:
+ tup = tuple([int(i) for i in string.split(',')])
+ if len(tup) != 3:
+ raise ValueError
+ for i in tup:
+ if i > 255 or i < 0:
+ raise ValueError
+ return tup
+ except:
+ return (255, 255, 255)
+
+ '''
+ ### Reference methods for creating a new component
+ ### (Inherit from this class and define these)
+
+ def widget(self, parent):
+ self.parent = parent
+ page = uic.loadUi(os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), 'example.ui'))
+ # --- connect widget signals here ---
+ self.page = page
+ return page
+
+ def update(self):
+ super().update()
+ self.parent.drawPreview()
+
+ def previewRender(self, previewWorker):
+ width = int(previewWorker.core.settings.value('outputWidth'))
+ height = int(previewWorker.core.settings.value('outputHeight'))
+ image = Image.new("RGBA", (width, height), (0,0,0,0))
+ return image
+
+ def frameRender(self, moduleNo, frameNo):
+ width = int(self.worker.core.settings.value('outputWidth'))
+ height = int(self.worker.core.settings.value('outputHeight'))
+ image = Image.new("RGBA", (width, height), (0,0,0,0))
+ return image
+ '''
+
+class BadComponentInit(Exception):
+ def __init__(self, arg, name):
+ string = \
+'''################################
+Mandatory argument "%s" not specified
+ in %s instance initialization
+###################################'''
+ print(string % (arg, name))
+ quit()
diff --git a/src/components/__init__.py b/src/components/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/components/__init__.py
@@ -0,0 +1 @@
+
diff --git a/src/components/color.py b/src/components/color.py
new file mode 100644
index 0000000..8f9a1d1
--- /dev/null
+++ b/src/components/color.py
@@ -0,0 +1,246 @@
+from PIL import Image, ImageDraw
+from PyQt5 import uic, QtGui, QtCore
+from PyQt5.QtGui import QColor
+from PIL.ImageQt import ImageQt
+import os
+from . import __base__
+
+
+class Component(__base__.Component):
+ '''Color'''
+
+ modified = QtCore.pyqtSignal(int, dict)
+
+ def widget(self, parent):
+ self.parent = parent
+ page = uic.loadUi(os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), 'color.ui'))
+
+ self.color1 = (0, 0, 0)
+ self.color2 = (133, 133, 133)
+ self.x = 0
+ self.y = 0
+
+ page.lineEdit_color1.setText('%s,%s,%s' % self.color1)
+ 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()
+
+ page.pushButton_color1.setStyleSheet(btnStyle1)
+ page.pushButton_color2.setStyleSheet(btnStyle2)
+ page.pushButton_color1.clicked.connect(lambda: self.pickColor(1))
+ page.pushButton_color2.clicked.connect(lambda: self.pickColor(2))
+
+ # disable color #2 until non-default 'fill' option gets changed
+ page.lineEdit_color2.setDisabled(True)
+ page.pushButton_color2.setDisabled(True)
+ page.spinBox_x.valueChanged.connect(self.update)
+ page.spinBox_y.valueChanged.connect(self.update)
+ page.spinBox_width.setValue(
+ int(parent.settings.value("outputWidth")))
+ page.spinBox_height.setValue(
+ int(parent.settings.value("outputHeight")))
+
+ page.lineEdit_color1.textChanged.connect(self.update)
+ page.lineEdit_color2.textChanged.connect(self.update)
+ page.spinBox_x.valueChanged.connect(self.update)
+ page.spinBox_y.valueChanged.connect(self.update)
+ page.spinBox_width.valueChanged.connect(self.update)
+ page.spinBox_height.valueChanged.connect(self.update)
+ page.checkBox_trans.stateChanged.connect(self.update)
+
+ self.fillLabels = [ \
+ 'Solid',
+ 'Linear Gradient',
+ 'Radial Gradient',
+ ]
+ for label in self.fillLabels:
+ page.comboBox_fill.addItem(label)
+ page.comboBox_fill.setCurrentIndex(0)
+ page.comboBox_fill.currentIndexChanged.connect(self.update)
+ page.comboBox_spread.currentIndexChanged.connect(self.update)
+ page.spinBox_radialGradient_end.valueChanged.connect(self.update)
+ page.spinBox_radialGradient_start.valueChanged.connect(self.update)
+ page.spinBox_radialGradient_spread.valueChanged.connect(self.update)
+ page.spinBox_linearGradient_end.valueChanged.connect(self.update)
+ page.spinBox_linearGradient_start.valueChanged.connect(self.update)
+ page.checkBox_stretch.stateChanged.connect(self.update)
+
+ self.page = page
+ return page
+
+ def update(self):
+ self.color1 = self.RGBFromString(self.page.lineEdit_color1.text())
+ self.color2 = self.RGBFromString(self.page.lineEdit_color2.text())
+ self.x = self.page.spinBox_x.value()
+ self.y = self.page.spinBox_y.value()
+ self.sizeWidth = self.page.spinBox_width.value()
+ self.sizeHeight = self.page.spinBox_height.value()
+ self.trans = self.page.checkBox_trans.isChecked()
+ self.spread = self.page.comboBox_spread.currentIndex()
+
+ self.RG_start = self.page.spinBox_radialGradient_start.value()
+ self.RG_end = self.page.spinBox_radialGradient_end.value()
+ self.RG_centre = self.page.spinBox_radialGradient_spread.value()
+ self.stretch = self.page.checkBox_stretch.isChecked()
+ self.LG_start = self.page.spinBox_linearGradient_start.value()
+ self.LG_end = self.page.spinBox_linearGradient_end.value()
+
+ self.fillType = self.page.comboBox_fill.currentIndex()
+ if self.fillType == 0:
+ self.page.lineEdit_color2.setEnabled(False)
+ self.page.pushButton_color2.setEnabled(False)
+ self.page.checkBox_trans.setEnabled(False)
+ self.page.checkBox_stretch.setEnabled(False)
+ self.page.comboBox_spread.setEnabled(False)
+ else:
+ self.page.lineEdit_color2.setEnabled(True)
+ self.page.pushButton_color2.setEnabled(True)
+ self.page.checkBox_trans.setEnabled(True)
+ self.page.checkBox_stretch.setEnabled(True)
+ self.page.comboBox_spread.setEnabled(True)
+ self.page.fillWidget.setCurrentIndex(self.fillType)
+
+ self.parent.drawPreview()
+ super().update()
+
+ def previewRender(self, previewWorker):
+ width = int(previewWorker.core.settings.value('outputWidth'))
+ height = int(previewWorker.core.settings.value('outputHeight'))
+ return self.drawFrame(width, height)
+
+ def preFrameRender(self, **kwargs):
+ super().preFrameRender(**kwargs)
+ return ['static']
+
+ def frameRender(self, moduleNo, arrayNo, frameNo):
+ width = int(self.worker.core.settings.value('outputWidth'))
+ height = int(self.worker.core.settings.value('outputHeight'))
+ return self.drawFrame(width, height)
+
+ def drawFrame(self, width, height):
+ r, g, b = self.color1
+ shapeSize = (self.sizeWidth, self.sizeHeight)
+ # in default state, skip all this logic and return a plain fill
+ if self.fillType==0 and shapeSize == (width, height) \
+ and self.x == 0 and self.y == 0:
+ return Image.new("RGBA", (width, height), (r, g, b, 255))
+
+ frame = self.blankFrame(width, height)
+
+ # Return a solid image at x, y
+ if self.fillType == 0:
+ image = Image.new("RGBA", shapeSize, (r, g, b, 255))
+ frame.paste(image, box=(self.x, self.y))
+ return frame
+
+ # Now fills that require using Qt...
+ elif self.fillType > 0:
+ image = ImageQt(frame)
+ painter = QtGui.QPainter(image)
+ if self.stretch:
+ w = width; h = height
+ else:
+ w = self.sizeWidth; h = self.sizeWidth
+
+ if self.fillType == 1: # Linear Gradient
+ brush = QtGui.QLinearGradient(
+ self.LG_start,
+ self.LG_start,
+ self.LG_start+width/3,
+ 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)
+ brush.setColorAt(0.0, QColor(*self.color1))
+ if self.trans:
+ brush.setColorAt(1.0, QColor(0, 0, 0, 0))
+ elif self.fillType == 1 and self.stretch:
+ brush.setColorAt(0.2, QColor(*self.color2))
+ else:
+ brush.setColorAt(1.0, QColor(*self.color2))
+ painter.setBrush(brush)
+ painter.drawRect(self.x, self.y,
+ self.sizeWidth, self.sizeHeight)
+ painter.end()
+ imBytes = image.bits().asstring(image.numBytes())
+ return Image.frombytes('RGBA', (width, height), imBytes)
+
+ def loadPreset(self, pr, presetName=None):
+ super().loadPreset(pr, presetName)
+
+ self.page.comboBox_fill.setCurrentIndex(pr['fillType'])
+ self.page.lineEdit_color1.setText('%s,%s,%s' % pr['color1'])
+ self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2'])
+ self.page.spinBox_x.setValue(pr['x'])
+ self.page.spinBox_y.setValue(pr['y'])
+ self.page.spinBox_width.setValue(pr['width'])
+ self.page.spinBox_height.setValue(pr['height'])
+ self.page.checkBox_trans.setChecked(pr['trans'])
+
+ self.page.spinBox_radialGradient_start.setValue(pr['RG_start'])
+ self.page.spinBox_radialGradient_end.setValue(pr['RG_end'])
+ self.page.spinBox_radialGradient_spread.setValue(pr['RG_centre'])
+ self.page.spinBox_linearGradient_start.setValue(pr['LG_start'])
+ self.page.spinBox_linearGradient_end.setValue(pr['LG_end'])
+ self.page.checkBox_stretch.setChecked(pr['stretch'])
+ self.page.comboBox_spread.setCurrentIndex(pr['spread'])
+
+ 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):
+ return {
+ 'preset': self.currentPreset,
+ 'color1': self.color1,
+ 'color2': self.color2,
+ 'x': self.x,
+ 'y': self.y,
+ 'fillType': self.fillType,
+ 'width': self.sizeWidth,
+ 'height': self.sizeHeight,
+ 'trans': self.trans,
+ 'stretch': self.stretch,
+ 'spread': self.spread,
+ 'RG_start': self.RG_start,
+ 'RG_end': self.RG_end,
+ 'RG_centre': self.RG_centre,
+ 'LG_start': self.LG_start,
+ 'LG_end': self.LG_end,
+ }
+
+ def pickColor(self, num):
+ RGBstring, btnStyle = super().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')
+
+ def command(self, arg):
+ if not arg.startswith('preset=') and '=' in arg:
+ key, arg = arg.split('=', 1)
+ if key == 'color':
+ self.page.lineEdit_color1.setText(arg)
+ return
+ super().command(arg)
diff --git a/src/components/color.ui b/src/components/color.ui
new file mode 100644
index 0000000..a9dacea
--- /dev/null
+++ b/src/components/color.ui
@@ -0,0 +1,660 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 586
+ 197
+
+
+
+ Form
+
+
+ -
+
+
+ 4
+
+
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 31
+ 0
+
+
+
+ Color #1
+
+
+
+ -
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 1
+ 0
+
+
+
+ 12
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 31
+ 0
+
+
+
+ Color #2
+
+
+
+ -
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 1
+ 0
+
+
+
+ 12
+
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Width
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 0
+ 0
+
+
+
+ 0
+
+
+ 999999999
+
+
+ 0
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Height
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+ 999999999
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ X
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 0
+ 0
+
+
+
+ -10000
+
+
+ 10000
+
+
+ 0
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Y
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+ -10000
+
+
+ 10000
+
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Fill
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ -1
+
+
+ QComboBox::AdjustToContentsOnFirstShow
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Transparent
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Stretch
+
+
+
+ -
+
+
-
+
+ Pad
+
+
+ -
+
+ Reflect
+
+
+ -
+
+ Repeat
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Minimum
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ 0
+
+
+ 2
+
+
+
+
+
+
+ -1
+ 0
+ 561
+ 31
+
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Start
+
+
+
+ -
+
+
+ -10000
+
+
+ 10000
+
+
+ 10
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ End
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ -10000
+
+
+ 10000
+
+
+ 10
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
+
+
+ -1
+ -1
+ 561
+ 31
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Start
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ -10000
+
+
+ 10000
+
+
+ 10
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ End
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ -10000
+
+
+ 10000
+
+
+ 10
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Centre
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ QAbstractSpinBox::PlusMinus
+
+
+ -10000
+
+
+ 10000
+
+
+ 3
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/image.py b/src/components/image.py
new file mode 100644
index 0000000..8ca88d3
--- /dev/null
+++ b/src/components/image.py
@@ -0,0 +1,111 @@
+from PIL import Image, ImageDraw
+from PyQt5 import uic, QtGui, QtCore, QtWidgets
+import os
+from . import __base__
+
+
+class Component(__base__.Component):
+ '''Image'''
+
+ modified = QtCore.pyqtSignal(int, dict)
+
+ def widget(self, parent):
+ self.parent = parent
+ self.settings = parent.settings
+ page = uic.loadUi(os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), 'image.ui'))
+ self.imagePath = ''
+ self.x = 0
+ self.y = 0
+
+ page.lineEdit_image.textChanged.connect(self.update)
+ page.pushButton_image.clicked.connect(self.pickImage)
+ page.spinBox_scale.valueChanged.connect(self.update)
+ page.checkBox_stretch.stateChanged.connect(self.update)
+ page.spinBox_x.valueChanged.connect(self.update)
+ page.spinBox_y.valueChanged.connect(self.update)
+
+ self.page = page
+ return page
+
+ def update(self):
+ self.imagePath = self.page.lineEdit_image.text()
+ self.scale = self.page.spinBox_scale.value()
+ self.xPosition = self.page.spinBox_x.value()
+ self.yPosition = self.page.spinBox_y.value()
+ self.stretched = self.page.checkBox_stretch.isChecked()
+ self.parent.drawPreview()
+ super().update()
+
+ def previewRender(self, previewWorker):
+ self.imageFormats = previewWorker.core.imageFormats
+ width = int(previewWorker.core.settings.value('outputWidth'))
+ height = int(previewWorker.core.settings.value('outputHeight'))
+ return self.drawFrame(width, height)
+
+ def preFrameRender(self, **kwargs):
+ super().preFrameRender(**kwargs)
+ return ['static']
+
+ def frameRender(self, moduleNo, arrayNo, frameNo):
+ width = int(self.worker.core.settings.value('outputWidth'))
+ height = int(self.worker.core.settings.value('outputHeight'))
+ return self.drawFrame(width, height)
+
+ def drawFrame(self, width, height):
+ frame = self.blankFrame(width, height)
+ if self.imagePath and os.path.exists(self.imagePath):
+ image = Image.open(self.imagePath)
+ if self.stretched and image.size != (width, height):
+ image = image.resize((width, height), Image.ANTIALIAS)
+ if self.scale != 100:
+ newHeight = int((image.height / 100) * self.scale)
+ newWidth = int((image.width / 100) * self.scale)
+ image = image.resize((newWidth, newHeight), Image.ANTIALIAS)
+ frame.paste(image, box=(self.xPosition, self.yPosition))
+ return frame
+
+ def loadPreset(self, pr, presetName=None):
+ super().loadPreset(pr, presetName)
+ self.page.lineEdit_image.setText(pr['image'])
+ self.page.spinBox_scale.setValue(pr['scale'])
+ self.page.spinBox_x.setValue(pr['x'])
+ self.page.spinBox_y.setValue(pr['y'])
+ self.page.checkBox_stretch.setChecked(pr['stretched'])
+
+ def savePreset(self):
+ return {
+ 'preset': self.currentPreset,
+ 'image': self.imagePath,
+ 'scale': self.scale,
+ 'stretched': self.stretched,
+ 'x': self.xPosition,
+ 'y': self.yPosition,
+ }
+
+ def pickImage(self):
+ imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
+ filename = QtGui.QFileDialog.getOpenFileName(
+ self.page, "Choose Image", imgDir,
+ "Image Files (%s)" % " ".join(self.imageFormats))
+ if filename:
+ self.settings.setValue("backgroundDir", os.path.dirname(filename))
+ self.page.lineEdit_image.setText(filename)
+ self.update()
+
+ def command(self, arg):
+ if not arg.startswith('preset=') and '=' in arg:
+ key, arg = arg.split('=', 1)
+ if key == 'path' and os.path.exists(arg):
+ try:
+ Image.open(arg)
+ self.page.lineEdit_image.setText(arg)
+ self.page.checkBox_stretch.setChecked(True)
+ return
+ except OSError as e:
+ print("Not a supported image format")
+ quit(1)
+ super().command(arg)
+
+ def commandHelp(self):
+ print('Load an image:\n path=/filepath/to/image.png')
diff --git a/src/components/image.ui b/src/components/image.ui
new file mode 100644
index 0000000..6df03a5
--- /dev/null
+++ b/src/components/image.ui
@@ -0,0 +1,259 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 586
+ 197
+
+
+
+ Form
+
+
+ -
+
+
+ 4
+
+
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 31
+ 0
+
+
+
+ Image
+
+
+
+ -
+
+
+
+ 1
+ 0
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 1
+ 0
+
+
+
+
+ 32
+ 32
+
+
+
+ ...
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ X
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+ -10000
+
+
+ 10000
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Y
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 0
+ 0
+
+
+
+ -1000
+
+
+ 1000
+
+
+ 0
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Stretch
+
+
+ false
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+ Scale
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ QAbstractSpinBox::UpDownArrows
+
+
+ %
+
+
+ 10
+
+
+ 400
+
+
+ 100
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
diff --git a/src/components/original.py b/src/components/original.py
new file mode 100644
index 0000000..61f463d
--- /dev/null
+++ b/src/components/original.py
@@ -0,0 +1,204 @@
+import numpy
+from PIL import Image, ImageDraw
+from PyQt5 import uic, QtGui, QtCore
+from PyQt5.QtGui import QColor
+import os
+from . import __base__
+import time
+from copy import copy
+
+
+class Component(__base__.Component):
+ '''Original Audio Visualization'''
+
+ modified = QtCore.pyqtSignal(int, dict)
+
+ def widget(self, parent):
+ self.parent = parent
+ self.visColor = (255, 255, 255)
+
+ page = uic.loadUi(os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), 'original.ui'))
+ page.comboBox_visLayout.addItem("Classic")
+ page.comboBox_visLayout.addItem("Split")
+ page.comboBox_visLayout.addItem("Bottom")
+ page.comboBox_visLayout.setCurrentIndex(0)
+ page.comboBox_visLayout.currentIndexChanged.connect(self.update)
+ page.lineEdit_visColor.setText('%s,%s,%s' % self.visColor)
+ page.pushButton_visColor.clicked.connect(lambda: self.pickColor())
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" \
+ % QColor(*self.visColor).name()
+ page.pushButton_visColor.setStyleSheet(btnStyle)
+ page.lineEdit_visColor.textChanged.connect(self.update)
+ self.page = page
+ self.canceled = False
+ return page
+
+ def update(self):
+ self.layout = self.page.comboBox_visLayout.currentIndex()
+ self.visColor = self.RGBFromString(self.page.lineEdit_visColor.text())
+ self.parent.drawPreview()
+ super().update()
+
+ def loadPreset(self, pr, presetName=None):
+ super().loadPreset(pr, presetName)
+
+ 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)
+ self.page.comboBox_visLayout.setCurrentIndex(pr['layout'])
+
+ def savePreset(self):
+ return {
+ 'preset': self.currentPreset,
+ 'layout': self.layout,
+ 'visColor': self.visColor,
+ }
+
+ def previewRender(self, previewWorker):
+ spectrum = numpy.fromfunction(
+ lambda x: 0.008*(x-128)**2, (255,), dtype="int16")
+ width = int(previewWorker.core.settings.value('outputWidth'))
+ height = int(previewWorker.core.settings.value('outputHeight'))
+ return self.drawBars(
+ width, height, spectrum, self.visColor, self.layout)
+
+ def preFrameRender(self, **kwargs):
+ super().preFrameRender(**kwargs)
+ self.smoothConstantDown = 0.08
+ self.smoothConstantUp = 0.8
+ self.lastSpectrum = None
+ self.spectrumArray = {}
+ self.width = int(self.worker.core.settings.value('outputWidth'))
+ self.height = int(self.worker.core.settings.value('outputHeight'))
+
+ for i in range(0, len(self.completeAudioArray), self.sampleSize):
+ if self.canceled:
+ break
+ self.lastSpectrum = self.transformData(
+ i, self.completeAudioArray, self.sampleSize,
+ self.smoothConstantDown, self.smoothConstantUp,
+ self.lastSpectrum)
+ self.spectrumArray[i] = copy(self.lastSpectrum)
+
+ progress = int(100*(i/len(self.completeAudioArray)))
+ if progress >= 100:
+ progress = 100
+ pStr = "Analyzing audio: "+str(progress)+'%'
+ self.progressBarSetText.emit(pStr)
+ self.progressBarUpdate.emit(int(progress))
+
+ def frameRender(self, moduleNo, arrayNo, frameNo):
+ return self.drawBars(
+ self.width, self.height,
+ self.spectrumArray[arrayNo],
+ self.visColor, self.layout)
+
+ def pickColor(self):
+ RGBstring, btnStyle = super().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):
+ if len(completeAudioArray) < (i + sampleSize):
+ sampleSize = len(completeAudioArray) - i
+
+ window = numpy.hanning(sampleSize)
+ data = completeAudioArray[i:i+sampleSize][::1] * window
+ paddedSampleSize = 2048
+ paddedData = numpy.pad(
+ data, (0, paddedSampleSize - sampleSize), 'constant')
+ spectrum = numpy.fft.fft(paddedData)
+ sample_rate = 44100
+ frequencies = numpy.fft.fftfreq(len(spectrum), 1./sample_rate)
+
+ y = abs(spectrum[0:int(paddedSampleSize/2) - 1])
+
+ # filter the noise away
+ # y[y<80] = 0
+
+ y = 20 * numpy.log10(y)
+ y[numpy.isinf(y)] = 0
+
+ if lastSpectrum is not None:
+ lastSpectrum[y < lastSpectrum] = \
+ y[y < lastSpectrum] * smoothConstantDown + \
+ lastSpectrum[y < lastSpectrum] * (1 - smoothConstantDown)
+
+ lastSpectrum[y >= lastSpectrum] = \
+ y[y >= lastSpectrum] * smoothConstantUp + \
+ lastSpectrum[y >= lastSpectrum] * (1 - smoothConstantUp)
+ else:
+ lastSpectrum = y
+
+ x = frequencies[0:int(paddedSampleSize/2) - 1]
+
+ return lastSpectrum
+
+ def drawBars(self, width, height, spectrum, color, layout):
+ vH = height-height/8
+ bF = width / 64
+ bH = bF / 2
+ bQ = bF / 4
+ imTop = self.blankFrame(width, height)
+ draw = ImageDraw.Draw(imTop)
+ r, g, b = color
+ color2 = (r, g, b, 125)
+
+ bP = height / 1200
+
+ for j in range(0, 63):
+ 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)
+
+ im = self.blankFrame(width, height)
+
+ if layout == 0:
+ y = 0 - int(height/100*43)
+ im.paste(imTop, (0, y), mask=imTop)
+ y = 0 + int(height/100*43)
+ im.paste(imBottom, (0, y), mask=imBottom)
+
+ if layout == 1:
+ y = 0 + int(height/100*10)
+ im.paste(imTop, (0, y), mask=imTop)
+ y = 0 - int(height/100*10)
+ im.paste(imBottom, (0, y), mask=imBottom)
+
+ if layout == 2:
+ y = 0 + int(height/100*10)
+ im.paste(imTop, (0, y), mask=imTop)
+
+ return im
+
+ def command(self, arg):
+ if not arg.startswith('preset=') and '=' in arg:
+ key, arg = arg.split('=', 1)
+ if key == 'color':
+ self.page.lineEdit_visColor.setText(arg)
+ return
+ elif key == 'layout':
+ if arg == 'classic':
+ self.page.comboBox_visLayout.setCurrentIndex(0)
+ elif arg == 'split':
+ self.page.comboBox_visLayout.setCurrentIndex(1)
+ elif arg == 'bottom':
+ self.page.comboBox_visLayout.setCurrentIndex(2)
+ return
+ super().command(arg)
+
+ def commandHelp(self):
+ print('Give a layout name:\n layout=[classic/split/bottom]')
+ print('Specify a color:\n color=255,255,255')
diff --git a/src/components/original.ui b/src/components/original.ui
new file mode 100644
index 0000000..5808653
--- /dev/null
+++ b/src/components/original.ui
@@ -0,0 +1,108 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 633
+ 178
+
+
+
+
+ 180
+ 0
+
+
+
+ Form
+
+
+ -
+
+
+ 4
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Visualizer Layout
+
+
+
+ -
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+ Visualizer Color
+
+
+
+ -
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
diff --git a/src/components/text.py b/src/components/text.py
new file mode 100644
index 0000000..0f599ed
--- /dev/null
+++ b/src/components/text.py
@@ -0,0 +1,176 @@
+from PIL import Image, ImageDraw
+from PyQt5.QtGui import QPainter, QColor, QFont
+from PyQt5 import uic, QtGui, QtCore
+from PIL.ImageQt import ImageQt
+import os
+import io
+from . import __base__
+
+
+class Component(__base__.Component):
+ '''Title Text'''
+
+ modified = QtCore.pyqtSignal(int, dict)
+
+ def __init__(self, *args):
+ super().__init__(*args)
+ self.titleFont = QFont()
+
+ def widget(self, parent):
+ height = int(parent.settings.value('outputHeight'))
+ width = int(parent.settings.value('outputWidth'))
+
+ self.parent = parent
+ 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
+
+ page = uic.loadUi(os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), 'text.ui'))
+ page.comboBox_textAlign.addItem("Left")
+ page.comboBox_textAlign.addItem("Middle")
+ page.comboBox_textAlign.addItem("Right")
+
+ page.lineEdit_textColor.setText('%s,%s,%s' % self.textColor)
+ page.pushButton_textColor.clicked.connect(self.pickColor)
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" \
+ % QColor(*self.textColor).name()
+ page.pushButton_textColor.setStyleSheet(btnStyle)
+
+ page.lineEdit_title.setText(self.title)
+ page.comboBox_textAlign.setCurrentIndex(int(self.alignment))
+ page.spinBox_fontSize.setValue(int(self.fontSize))
+ page.spinBox_xTextAlign.setValue(int(self.xPosition))
+ page.spinBox_yTextAlign.setValue(int(self.yPosition))
+
+ page.fontComboBox_titleFont.currentFontChanged.connect(self.update)
+ page.lineEdit_title.textChanged.connect(self.update)
+ page.comboBox_textAlign.currentIndexChanged.connect(self.update)
+ page.spinBox_xTextAlign.valueChanged.connect(self.update)
+ page.spinBox_yTextAlign.valueChanged.connect(self.update)
+ page.spinBox_fontSize.valueChanged.connect(self.update)
+ page.lineEdit_textColor.textChanged.connect(self.update)
+ self.page = page
+ return page
+
+ def update(self):
+ self.title = self.page.lineEdit_title.text()
+ self.alignment = self.page.comboBox_textAlign.currentIndex()
+ self.titleFont = self.page.fontComboBox_titleFont.currentFont()
+ self.fontSize = self.page.spinBox_fontSize.value()
+ self.xPosition = self.page.spinBox_xTextAlign.value()
+ self.yPosition = self.page.spinBox_yTextAlign.value()
+ self.textColor = self.RGBFromString(
+ self.page.lineEdit_textColor.text())
+ self.parent.drawPreview()
+ super().update()
+
+ def getXY(self):
+ '''Returns true x, y after considering alignment settings'''
+ fm = QtGui.QFontMetrics(self.titleFont)
+ if self.alignment == 0: # Left
+ x = self.xPosition
+
+ if self.alignment == 1: # Middle
+ offset = fm.width(self.title)/2
+ x = self.xPosition - offset
+
+ if self.alignment == 2: # Right
+ offset = fm.width(self.title)
+ x = self.xPosition - offset
+ return x, self.yPosition
+
+ def loadPreset(self, pr, presetName=None):
+ super().loadPreset(pr, presetName)
+
+ self.page.lineEdit_title.setText(pr['title'])
+ font = QFont()
+ font.fromString(pr['titleFont'])
+ self.page.fontComboBox_titleFont.setCurrentFont(font)
+ self.page.spinBox_fontSize.setValue(pr['fontSize'])
+ self.page.comboBox_textAlign.setCurrentIndex(pr['alignment'])
+ self.page.spinBox_xTextAlign.setValue(pr['xPosition'])
+ self.page.spinBox_yTextAlign.setValue(pr['yPosition'])
+ 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):
+ return {
+ 'preset': self.currentPreset,
+ 'title': self.title,
+ 'titleFont': self.titleFont.toString(),
+ 'alignment': self.alignment,
+ 'fontSize': self.fontSize,
+ 'xPosition': self.xPosition,
+ 'yPosition': self.yPosition,
+ 'textColor': self.textColor
+ }
+
+ def previewRender(self, previewWorker):
+ width = int(previewWorker.core.settings.value('outputWidth'))
+ height = int(previewWorker.core.settings.value('outputHeight'))
+ return self.addText(width, height)
+
+ def preFrameRender(self, **kwargs):
+ super().preFrameRender(**kwargs)
+ return ['static']
+
+ def frameRender(self, moduleNo, arrayNo, frameNo):
+ width = int(self.worker.core.settings.value('outputWidth'))
+ height = int(self.worker.core.settings.value('outputHeight'))
+ return self.addText(width, height)
+
+ def addText(self, width, height):
+ x, y = self.getXY()
+ im = self.blankFrame(width, height)
+ image = ImageQt(im)
+
+ painter = QPainter(image)
+ self.titleFont.setPixelSize(self.fontSize)
+ painter.setFont(self.titleFont)
+ painter.setPen(QColor(*self.textColor))
+ painter.drawText(x, y, self.title)
+ painter.end()
+
+ imBytes = image.bits().asstring(image.numBytes())
+
+ return Image.frombytes('RGBA', (width, height), imBytes)
+
+ def pickColor(self):
+ RGBstring, btnStyle = super().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"')
+ 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 not arg.startswith('preset=') and '=' in arg:
+ key, arg = arg.split('=', 1)
+ if key == 'color':
+ self.page.lineEdit_textColor.setText(arg)
+ return
+ elif key == 'size':
+ self.page.spinBox_fontSize.setValue(int(arg))
+ return
+ elif key == 'x':
+ self.page.spinBox_xTextAlign.setValue(int(arg))
+ return
+ elif key == 'y':
+ self.page.spinBox_yTextAlign.setValue(int(arg))
+ return
+ elif key == 'title':
+ self.page.lineEdit_title.setText(arg)
+ return
+ super().command(arg)
diff --git a/src/components/text.ui b/src/components/text.ui
new file mode 100644
index 0000000..05e7f8e
--- /dev/null
+++ b/src/components/text.ui
@@ -0,0 +1,316 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 586
+ 197
+
+
+
+ Form
+
+
+ -
+
+
+ 4
+
+
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Font
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Font Size
+
+
+
+ -
+
+
+ 500
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Text Layout
+
+
+
+ -
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+ Text Color
+
+
+
+ -
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+ Title
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+ Testing New GUI
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ X
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 0
+ 0
+
+
+
+ 0
+
+
+ 999999999
+
+
+ 0
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Y
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+ 999999999
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
diff --git a/src/components/video.py b/src/components/video.py
new file mode 100644
index 0000000..58ce7a3
--- /dev/null
+++ b/src/components/video.py
@@ -0,0 +1,273 @@
+from PIL import Image, ImageDraw
+from PyQt5 import uic, QtGui, QtCore
+import os
+import subprocess
+import threading
+from queue import PriorityQueue
+from . import __base__
+
+
+class Video:
+ '''Video Component Frame-Fetcher'''
+ def __init__(self, **kwargs):
+ mandatoryArgs = [
+ 'ffmpeg', # path to ffmpeg, usually 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:
+ try:
+ exec('self.%s = kwargs[arg]' % arg)
+ except KeyError:
+ raise __base__.BadComponentInit(arg, self.__doc__)
+
+ 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: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=self.__doc__
+ )
+ 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):
+ pipe = subprocess.Popen(
+ self.command, 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.
+ if len(self.currentFrame) == 0:
+ self.frameBuffer.put((self.frameNo-1, self.lastFrame))
+ continue
+
+ self.currentFrame = pipe.stdout.read(self.chunkSize)
+ if len(self.currentFrame) != 0:
+ self.frameBuffer.put((self.frameNo, self.currentFrame))
+ self.lastFrame = self.currentFrame
+
+
+class Component(__base__.Component):
+ '''Video'''
+
+ modified = QtCore.pyqtSignal(int, dict)
+
+ def widget(self, parent):
+ self.parent = parent
+ self.settings = parent.settings
+ page = uic.loadUi(os.path.join(
+ os.path.dirname(os.path.realpath(__file__)),
+ 'video.ui'
+ ))
+ self.videoPath = ''
+ self.x = 0
+ self.y = 0
+ self.loopVideo = False
+
+ page.lineEdit_video.textChanged.connect(self.update)
+ page.pushButton_video.clicked.connect(self.pickVideo)
+ page.checkBox_loop.stateChanged.connect(self.update)
+ page.checkBox_distort.stateChanged.connect(self.update)
+ page.spinBox_scale.valueChanged.connect(self.update)
+ page.spinBox_x.valueChanged.connect(self.update)
+ page.spinBox_y.valueChanged.connect(self.update)
+
+ self.page = page
+ return page
+
+ def update(self):
+ self.videoPath = self.page.lineEdit_video.text()
+ self.loopVideo = self.page.checkBox_loop.isChecked()
+ self.distort = self.page.checkBox_distort.isChecked()
+ self.scale = self.page.spinBox_scale.value()
+ self.xPosition = self.page.spinBox_x.value()
+ self.yPosition = self.page.spinBox_y.value()
+ self.parent.drawPreview()
+ super().update()
+
+ def previewRender(self, previewWorker):
+ self.videoFormats = previewWorker.core.videoFormats
+ width = int(previewWorker.core.settings.value('outputWidth'))
+ height = int(previewWorker.core.settings.value('outputHeight'))
+ self.updateChunksize(width, height)
+ frame = self.getPreviewFrame(width, height)
+ if not frame:
+ return self.blankFrame(width, height)
+ else:
+ return frame
+
+ def preFrameRender(self, **kwargs):
+ super().preFrameRender(**kwargs)
+ width = int(self.worker.core.settings.value('outputWidth'))
+ height = int(self.worker.core.settings.value('outputHeight'))
+ self.blankFrame_ = self.blankFrame(width, height)
+ self.updateChunksize(width, height)
+ self.video = Video(
+ ffmpeg=self.parent.core.FFMPEG_BIN, videoPath=self.videoPath,
+ width=width, height=height, chunkSize=self.chunkSize,
+ frameRate=int(self.settings.value("outputFrameRate")),
+ parent=self.parent, loopVideo=self.loopVideo,
+ component=self, scale=self.scale
+ ) if os.path.exists(self.videoPath) else None
+
+ def frameRender(self, moduleNo, arrayNo, frameNo):
+ if self.video:
+ return self.video.frame(frameNo)
+ else:
+ return self.blankFrame_
+
+ def loadPreset(self, pr, presetName=None):
+ super().loadPreset(pr, presetName)
+ self.page.lineEdit_video.setText(pr['video'])
+ self.page.checkBox_loop.setChecked(pr['loop'])
+ self.page.checkBox_distort.setChecked(pr['distort'])
+ self.page.spinBox_scale.setValue(pr['scale'])
+ self.page.spinBox_x.setValue(pr['x'])
+ self.page.spinBox_y.setValue(pr['y'])
+
+ def savePreset(self):
+ return {
+ 'preset': self.currentPreset,
+ 'video': self.videoPath,
+ 'loop': self.loopVideo,
+ 'distort': self.distort,
+ 'scale': self.scale,
+ 'x': self.xPosition,
+ 'y': self.yPosition,
+ }
+
+ def pickVideo(self):
+ imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
+ filename = QtGui.QFileDialog.getOpenFileName(
+ self.page, "Choose Video",
+ imgDir, "Video Files (%s)" % " ".join(self.videoFormats)
+ )
+ if filename:
+ self.settings.setValue("backgroundDir", os.path.dirname(filename))
+ self.page.lineEdit_video.setText(filename)
+ self.update()
+
+ def getPreviewFrame(self, width, height):
+ if not self.videoPath or not os.path.exists(self.videoPath):
+ return
+
+ command = [
+ self.parent.core.FFMPEG_BIN,
+ '-thread_queue_size', '512',
+ '-i', self.videoPath,
+ '-f', 'image2pipe',
+ '-pix_fmt', 'rgba',
+ '-filter:v', 'scale=%s:%s' %
+ scale(self.scale, width, height, str),
+ '-vcodec', 'rawvideo', '-',
+ '-ss', '90',
+ '-vframes', '1',
+ ]
+ pipe = subprocess.Popen(
+ command, stdout=subprocess.PIPE,
+ stderr=subprocess.DEVNULL, bufsize=10**8
+ )
+ byteFrame = pipe.stdout.read(self.chunkSize)
+ frame = finalizeFrame(self, byteFrame, width, height)
+ pipe.stdout.close()
+ pipe.kill()
+
+ return frame
+
+ def updateChunksize(self, width, height):
+ if self.scale != 100 and not self.distort:
+ width, height = scale(self.scale, width, height, int)
+ self.chunkSize = 4*width*height
+
+ def command(self, arg):
+ if not arg.startswith('preset=') and '=' in arg:
+ key, arg = arg.split('=', 1)
+ if key == 'path' and os.path.exists(arg):
+ if os.path.splitext(arg)[1] in self.core.videoFormats:
+ self.page.lineEdit_video.setText(arg)
+ self.page.spinBox_scale.setValue(100)
+ self.page.checkBox_loop.setChecked(True)
+ return
+ else:
+ print("Not a supported video format")
+ quit(1)
+ super().command(arg)
+
+ def commandHelp(self):
+ print('Load a video:\n path=/filepath/to/video.mp4')
+
+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(int(width)), str(int(height)))
+ elif returntype == int:
+ return (int(width), int(height))
+ else:
+ return (width, height)
+
+def finalizeFrame(self, imageData, width, height):
+ if self.distort:
+ try:
+ image = Image.frombytes(
+ 'RGBA',
+ (width, height),
+ imageData)
+ except ValueError:
+ print('#### ignored invalid data caused by distortion ####')
+ image = self.blankFrame(width, height)
+ else:
+ image = Image.frombytes(
+ 'RGBA',
+ scale(self.scale, width, height, int),
+ imageData)
+
+ if self.scale != 100 \
+ or self.xPosition != 0 or self.yPosition != 0:
+ frame = self.blankFrame(width, height)
+ frame.paste(image, box=(self.xPosition, self.yPosition))
+ else:
+ frame = image
+ return frame
diff --git a/src/components/video.ui b/src/components/video.ui
new file mode 100644
index 0000000..f05e8a5
--- /dev/null
+++ b/src/components/video.ui
@@ -0,0 +1,266 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 586
+ 197
+
+
+
+ Form
+
+
+ -
+
+
+ 4
+
+
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 31
+ 0
+
+
+
+ Video
+
+
+
+ -
+
+
+
+ 1
+ 0
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 1
+ 0
+
+
+
+
+ 32
+ 32
+
+
+
+ ...
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ X
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+ -10000
+
+
+ 10000
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Y
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 0
+ 0
+
+
+
+ -10000
+
+
+ 10000
+
+
+ 0
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Loop
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+ Distort by scale
+
+
+
+ -
+
+
+ Scale
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ QAbstractSpinBox::UpDownArrows
+
+
+ %
+
+
+ 10
+
+
+ 400
+
+
+ 100
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+
+
+
+
+
diff --git a/src/core.py b/src/core.py
new file mode 100644
index 0000000..bb5d351
--- /dev/null
+++ b/src/core.py
@@ -0,0 +1,477 @@
+import sys
+import io
+import os
+from PyQt5 import QtCore, QtGui, uic
+from os.path import expanduser
+import subprocess as sp
+import numpy
+from PIL import Image
+from shutil import rmtree
+import time
+from collections import OrderedDict
+import json
+from importlib import import_module
+from PyQt5.QtCore import QStandardPaths
+import string
+
+
+class Core():
+
+ def __init__(self):
+ self.FFMPEG_BIN = self.findFfmpeg()
+ self.dataDir = QStandardPaths.writableLocation(
+ QStandardPaths.AppConfigLocation
+ )
+ self.presetDir = os.path.join(self.dataDir, 'presets')
+ if getattr(sys, 'frozen', False):
+ # frozen
+ self.wd = os.path.dirname(sys.executable)
+ else:
+ # unfrozen
+ self.wd = os.path.dirname(os.path.realpath(__file__))
+
+ self.loadEncoderOptions()
+ self.videoFormats = Core.appendUppercase([
+ '*.mp4',
+ '*.mov',
+ '*.mkv',
+ '*.avi',
+ '*.webm',
+ '*.flv',
+ ])
+ self.audioFormats = Core.appendUppercase([
+ '*.mp3',
+ '*.wav',
+ '*.ogg',
+ '*.fla',
+ '*.flac',
+ '*.aac',
+ ])
+ self.imageFormats = Core.appendUppercase([
+ '*.png',
+ '*.jpg',
+ '*.tif',
+ '*.tiff',
+ '*.gif',
+ '*.bmp',
+ '*.ico',
+ '*.xbm',
+ '*.xpm',
+ ])
+
+ self.findComponents()
+ self.selectedComponents = []
+ # copies of named presets to detect modification
+ self.savedPresets = {}
+
+ def findComponents(self):
+ def findComponents():
+ srcPath = os.path.join(self.wd, 'components')
+ if os.path.exists(srcPath):
+ for f in sorted(os.listdir(srcPath)):
+ name, ext = os.path.splitext(f)
+ if name.startswith("__"):
+ continue
+ elif ext == '.py':
+ yield name
+ self.modules = [
+ import_module('components.%s' % name)
+ for name in findComponents()
+ ]
+ self.moduleIndexes = [i for i in range(len(self.modules))]
+ self.compNames = [mod.Component.__doc__ for mod in self.modules]
+
+ def componentListChanged(self):
+ for i, component in enumerate(self.selectedComponents):
+ component.compPos = i
+
+ def insertComponent(self, compPos, moduleIndex, loader):
+ '''Creates a new component'''
+ if compPos < 0 or compPos > len(self.selectedComponents):
+ compPos = len(self.selectedComponents)
+ if len(self.selectedComponents) > 50:
+ return None
+
+ component = self.modules[moduleIndex].Component(
+ moduleIndex, compPos, self)
+ self.selectedComponents.insert(
+ compPos,
+ component)
+ self.componentListChanged()
+
+ # init component's widget for loading/saving presets
+ self.selectedComponents[compPos].widget(loader)
+ self.updateComponent(compPos)
+
+ if hasattr(loader, 'insertComponent'):
+ loader.insertComponent(compPos)
+ return compPos
+
+ def moveComponent(self, startI, endI):
+ comp = self.selectedComponents.pop(startI)
+ self.selectedComponents.insert(endI, comp)
+
+ self.componentListChanged()
+ return endI
+
+ def removeComponent(self, i):
+ self.selectedComponents.pop(i)
+ self.componentListChanged()
+
+ def clearComponents(self):
+ self.selectedComponents = list()
+ self.componentListChanged()
+
+ def updateComponent(self, i):
+ # print('updating %s' % self.selectedComponents[i])
+ self.selectedComponents[i].update()
+
+ def moduleIndexFor(self, compName):
+ index = self.compNames.index(compName)
+ return self.moduleIndexes[index]
+
+ def clearPreset(self, compIndex):
+ self.selectedComponents[compIndex].currentPreset = None
+
+ def openPreset(self, filepath, compIndex, presetName):
+ '''Applies a preset to a specific component'''
+ saveValueStore = self.getPreset(filepath)
+ if not saveValueStore:
+ return False
+ try:
+ self.selectedComponents[compIndex].loadPreset(
+ saveValueStore,
+ presetName
+ )
+ except KeyError as e:
+ print('preset missing value: %s' % e)
+
+ self.savedPresets[presetName] = dict(saveValueStore)
+ return True
+
+ def getPresetDir(self, comp):
+ return os.path.join(
+ self.presetDir, str(comp), str(comp.version()))
+
+ def getPreset(self, filepath):
+ '''Returns the preset dict stored at this filepath'''
+ if not os.path.exists(filepath):
+ return False
+ with open(filepath, 'r') as f:
+ for line in f:
+ saveValueStore = Core.presetFromString(line.strip())
+ break
+ return saveValueStore
+
+ def openProject(self, loader, filepath):
+ ''' loader is the object calling this method which must have
+ its own showMessage(**kwargs) method for displaying errors.
+ '''
+ if not os.path.exists(filepath):
+ loader.showMessage(msg='Project file not found')
+ return
+
+ errcode, data = self.parseAvFile(filepath)
+ if errcode == 0:
+ try:
+ for i, tup in enumerate(data['Components']):
+ name, vers, preset = tup
+ clearThis = False
+
+ # add loaded named presets to savedPresets dict
+ if 'preset' in preset and preset['preset'] != None:
+ nam = preset['preset']
+ filepath2 = os.path.join(
+ self.presetDir, name, str(vers), nam)
+ origSaveValueStore = self.getPreset(filepath2)
+ if origSaveValueStore:
+ self.savedPresets[nam] = dict(origSaveValueStore)
+ else:
+ # saved preset was renamed or deleted
+ clearThis = True
+
+ # create the actual component object & get its index
+ i = self.insertComponent(
+ -1,
+ self.moduleIndexFor(name),
+ loader)
+ if i == None:
+ loader.showMessage(msg="Too many components!")
+ break
+
+ try:
+ if 'preset' in preset and preset['preset'] != None:
+ self.selectedComponents[i].loadPreset(
+ preset
+ )
+ else:
+ self.selectedComponents[i].loadPreset(
+ preset,
+ preset['preset']
+ )
+ except KeyError as e:
+ print('%s missing value %s' %
+ (self.selectedComponents[i], e))
+
+ if clearThis:
+ self.clearPreset(i)
+ if hasattr(loader, 'updateComponentTitle'):
+ loader.updateComponentTitle(i)
+ except:
+ errcode = 1
+ data = sys.exc_info()
+
+
+ if errcode == 1:
+ typ, value, _ = data
+ if typ.__name__ == KeyError:
+ # probably just an old version, still loadable
+ print('file missing value: %s' % value)
+ return
+ if hasattr(loader, 'createNewProject'):
+ loader.createNewProject()
+ msg = '%s: %s' % (typ.__name__, value)
+ loader.showMessage(
+ msg="Project file '%s' is corrupted." % filepath,
+ showCancel=False,
+ icon=QtGui.QMessageBox.Warning,
+ detail=msg)
+
+ def parseAvFile(self, filepath):
+ '''Parses an avp (project) or avl (preset package) file.
+ Returns dictionary with section names as the keys, each one
+ contains a list of tuples: (compName, version, compPresetDict)
+ '''
+ data = {}
+ try:
+ with open(filepath, 'r') as f:
+ def parseLine(line):
+ '''Decides if a file line is a section header'''
+ validSections = ('Components')
+ line = line.strip()
+ newSection = ''
+
+ if line.startswith('[') and line.endswith(']') \
+ and line[1:-1] in validSections:
+ newSection = line[1:-1]
+
+ return line, newSection
+
+ section = ''
+ i = 0
+ for line in f:
+ line, newSection = parseLine(line)
+ if newSection:
+ section = str(newSection)
+ data[section] = []
+ continue
+ if line and section == 'Components':
+ if i == 0:
+ lastCompName = str(line)
+ i += 1
+ elif i == 1:
+ lastCompVers = str(line)
+ i += 1
+ elif i == 2:
+ lastCompPreset = Core.presetFromString(line)
+ data[section].append(
+ (lastCompName,
+ lastCompVers,
+ lastCompPreset)
+ )
+ i = 0
+ return 0, data
+ except:
+ return 1, sys.exc_info()
+
+ def importPreset(self, filepath):
+ errcode, data = self.parseAvFile(filepath)
+ returnList = []
+ if errcode == 0:
+ name, vers, preset = data['Components'][0]
+ presetName = preset['preset'] \
+ if preset['preset'] else os.path.basename(filepath)[:-4]
+ newPath = os.path.join(
+ self.presetDir,
+ name,
+ vers,
+ presetName
+ )
+ if os.path.exists(newPath):
+ return False, newPath
+ preset['preset'] = presetName
+ self.createPresetFile(
+ name, vers, presetName, preset
+ )
+ return True, presetName
+ elif errcode == 1:
+ # TODO: an error message
+ return False, ''
+
+ def exportPreset(self, exportPath, compName, vers, origName):
+ internalPath = os.path.join(self.presetDir, compName, str(vers), origName)
+ if not os.path.exists(internalPath):
+ return
+ if os.path.exists(exportPath):
+ os.remove(exportPath)
+ with open(internalPath, 'r') as f:
+ internalData = [line for line in f]
+ try:
+ saveValueStore = Core.presetFromString(internalData[0].strip())
+ self.createPresetFile(
+ compName, vers,
+ origName, saveValueStore,
+ exportPath
+ )
+ return True
+ except:
+ return False
+
+ def createPresetFile(
+ self, compName, vers, presetName, saveValueStore, filepath=''):
+ '''Create a preset file (.avl) at filepath using args.
+ Or if filepath is empty, create an internal preset using args'''
+ if not filepath:
+ dirname = os.path.join(self.presetDir, compName, str(vers))
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+ filepath = os.path.join(dirname, presetName)
+ internal = True
+ else:
+ if not filepath.endswith('.avl'):
+ filepath += '.avl'
+ internal = False
+
+ with open(filepath, 'w') as f:
+ if not internal:
+ f.write('[Components]\n')
+ f.write('%s\n' % compName)
+ f.write('%s\n' % str(vers))
+ f.write(Core.presetToString(saveValueStore))
+
+ def createProjectFile(self, filepath):
+ '''Create a project file (.avp) using the current program state'''
+ try:
+ if not filepath.endswith(".avp"):
+ filepath += '.avp'
+ if os.path.exists(filepath):
+ os.remove(filepath)
+ with open(filepath, 'w') as f:
+ print('creating %s' % filepath)
+ f.write('[Components]\n')
+ for comp in self.selectedComponents:
+ saveValueStore = comp.savePreset()
+ f.write('%s\n' % str(comp))
+ f.write('%s\n' % str(comp.version()))
+ f.write('%s\n' % Core.presetToString(saveValueStore))
+ return True
+ except:
+ return False
+
+ def loadEncoderOptions(self):
+ file_path = os.path.join(self.wd, 'encoder-options.json')
+ with open(file_path) as json_file:
+ self.encoder_options = json.load(json_file)
+
+ def findFfmpeg(self):
+ if sys.platform == "win32":
+ return "ffmpeg.exe"
+ else:
+ try:
+ with open(os.devnull, "w") as f:
+ sp.check_call(['ffmpeg', '-version'], stdout=f, stderr=f)
+ return "ffmpeg"
+ except:
+ return "avconv"
+
+ def readAudioFile(self, filename, parent):
+ command = [self.FFMPEG_BIN, '-i', filename]
+
+ try:
+ fileInfo = sp.check_output(command, stderr=sp.STDOUT, shell=False)
+ except sp.CalledProcessError as ex:
+ fileInfo = ex.output
+ pass
+
+ info = fileInfo.decode("utf-8").split('\n')
+ for line in info:
+ if 'Duration' in line:
+ d = line.split(',')[0]
+ d = d.split(' ')[3]
+ d = d.split(':')
+ duration = float(d[0])*3600 + float(d[1])*60 + float(d[2])
+
+ command = [
+ self.FFMPEG_BIN,
+ '-i', filename,
+ '-f', 's16le',
+ '-acodec', 'pcm_s16le',
+ '-ar', '44100', # ouput will have 44100 Hz
+ '-ac', '1', # mono (set to '2' for stereo)
+ '-']
+ in_pipe = sp.Popen(
+ command, stdout=sp.PIPE, stderr=sp.DEVNULL, bufsize=10**8)
+
+ completeAudioArray = numpy.empty(0, dtype="int16")
+
+ progress = 0
+ lastPercent = None
+ while True:
+ if self.canceled:
+ break
+ # read 2 seconds of audio
+ progress = progress + 4
+ raw_audio = in_pipe.stdout.read(88200*4)
+ if len(raw_audio) == 0:
+ break
+ audio_array = numpy.fromstring(raw_audio, dtype="int16")
+ completeAudioArray = numpy.append(completeAudioArray, audio_array)
+
+ percent = int(100*(progress/duration))
+ if percent >= 100:
+ percent = 100
+
+ if lastPercent != percent:
+ string = 'Loading audio file: '+str(percent)+'%'
+ parent.progressBarSetText.emit(string)
+ parent.progressBarUpdate.emit(percent)
+
+ lastPercent = percent
+
+ in_pipe.kill()
+ in_pipe.wait()
+
+ # add 0s the end
+ completeAudioArrayCopy = numpy.zeros(
+ len(completeAudioArray) + 44100, dtype="int16")
+ completeAudioArrayCopy[:len(completeAudioArray)] = completeAudioArray
+ completeAudioArray = completeAudioArrayCopy
+
+ return completeAudioArray
+
+ def cancel(self):
+ self.canceled = True
+
+ def reset(self):
+ self.canceled = False
+
+ @staticmethod
+ def badName(name):
+ '''Returns whether a name contains non-alphanumeric chars'''
+ return any([letter in string.punctuation for letter in name])
+
+ @staticmethod
+ def presetToString(dictionary):
+ '''Alphabetizes a dict into OrderedDict & returns string repr'''
+ return repr(OrderedDict(sorted(dictionary.items(), key=lambda t: t[0])))
+
+ @staticmethod
+ def presetFromString(string):
+ '''Turns a string repr of OrderedDict into a regular dict'''
+ return dict(eval(string))
+
+ @staticmethod
+ def appendUppercase(lst):
+ for form, i in zip(lst, range(len(lst))):
+ lst.append(form.upper())
+ return lst
diff --git a/src/encoder-options.json b/src/encoder-options.json
new file mode 100644
index 0000000..78bc940
--- /dev/null
+++ b/src/encoder-options.json
@@ -0,0 +1,130 @@
+{
+ "containers":[
+ {
+ "name": "MP4",
+ "container": "mp4",
+ "default-vcodec": "H264",
+ "default-acodec": "AAC",
+ "video-codecs": [
+ "H264",
+ "H264 (nvenc)",
+ "MPEG4"
+ ],
+ "audio-codecs": [
+ "AAC",
+ "AC3",
+ "MP3"
+ ]
+ },
+ {
+ "name": "MOV",
+ "container": "mov",
+ "default-vcodec": "H264",
+ "default-acodec": "AAC",
+ "video-codecs": [
+ "H264",
+ "H264 (nvenc)",
+ "MPEG4",
+ "XVID"
+ ],
+ "audio-codecs": [
+ "AAC",
+ "AC3",
+ "MP3",
+ "PCM s16 LE"
+ ]
+ },
+ {
+ "name": "MKV",
+ "container": "matroska",
+ "default-vcodec": "H264",
+ "default-acodec": "AAC",
+ "video-codecs": [
+ "H264",
+ "H264 (nvenc)",
+ "MPEG4",
+ "MPEG2",
+ "DV",
+ "WMV"
+ ],
+ "audio-codecs": [
+ "AAC",
+ "AC3",
+ "MP3",
+ "PCM s16 LE",
+ "WMA"
+ ]
+ },
+ {
+ "name": "AVI",
+ "container": "avi",
+ "default-vcodec": "H264",
+ "default-acodec": "AAC",
+ "video-codecs": [
+ "H264",
+ "H264 (nvenc)",
+ "MPEG4",
+ "MPEG2",
+ "DV",
+ "WMV"
+ ],
+ "audio-codecs": [
+ "AAC",
+ "AC3",
+ "MP3",
+ "PCM s16 LE",
+ "WMA"
+ ]
+ },
+ {
+ "name": "WEBM",
+ "container": "webm",
+ "default-vcodec": "VP9",
+ "default-acodec": "Vorbis",
+ "video-codecs": [
+ "VP9",
+ "VP8"
+ ],
+ "audio-codecs": [
+ "Vorbis"
+ ]
+ },
+ {
+ "name": "FLV",
+ "container": "flv",
+ "default-vcodec": "FLV",
+ "default-acodec": "Vorbis",
+ "video-codecs": [
+ "Sorenson (flv)",
+ "H264",
+ "H264 (nvenc)",
+ "MPEG4"
+ ],
+ "audio-codecs": [
+ "MP3",
+ "PCM s16 LE",
+ "Vorbis"
+ ]
+ }
+ ],
+ "video-codecs":{
+ "H264": ["libx264"],
+ "H264 (nvenc)": ["h264_nvenc", "nvenc_h264"],
+ "MPEG4": ["mpeg4"],
+ "VP9": ["libvpx-vp9"],
+ "VP8": ["libvpx"],
+ "XVID": ["libxvid"],
+ "Sorenson (flv)": ["flv"],
+ "MPEG2": ["mp2video"],
+ "DV": ["dvvideo"],
+ "WMV": ["wmv2"]
+ },
+ "audio-codecs": {
+ "AAC": ["libfdk_aac", "aac"],
+ "AC3": ["ac3"],
+ "MP3": ["libmp3lame"],
+ "PCM s16 LE": ["pcm_s16le"],
+ "WMA": ["wmav2"],
+ "Vorbis": ["libvorbis"]
+ }
+}
\ No newline at end of file
diff --git a/src/main.py b/src/main.py
new file mode 100644
index 0000000..4bf26db
--- /dev/null
+++ b/src/main.py
@@ -0,0 +1,88 @@
+from PyQt5 import QtGui, uic, QtWidgets
+import sys
+import os
+
+import core
+import preview_thread
+import video_thread
+
+
+def LoadDefaultSettings(self):
+ self.resolutions = [
+ '1920x1080',
+ '1280x720',
+ '854x480'
+ ]
+
+ default = {
+ "outputWidth": 1280,
+ "outputHeight": 720,
+ "outputFrameRate": 30,
+ "outputAudioCodec": "AAC",
+ "outputAudioBitrate": "192",
+ "outputVideoCodec": "H264",
+ "outputVideoBitrate": "2500",
+ "outputVideoFormat": "yuv420p",
+ "outputPreset": "medium",
+ "outputFormat": "mp4",
+ "outputContainer": "MP4",
+ "projectDir": os.path.join(self.dataDir, 'projects'),
+ }
+
+ for parm, value in default.items():
+ #print(parm, self.settings.value(parm))
+ if self.settings.value(parm) is None:
+ self.settings.setValue(parm, value)
+
+if __name__ == "__main__":
+ mode = 'gui'
+ if len(sys.argv) > 2:
+ mode = 'cmd'
+
+ elif len(sys.argv) == 2:
+ if sys.argv[1].startswith('-'):
+ mode = 'cmd'
+ else:
+ # opening a project file with gui
+ proj = sys.argv[1]
+ else:
+ # normal gui launch
+ proj = None
+
+ app = QtWidgets.QApplication(sys.argv)
+ app.setApplicationName("audio-visualizer")
+ app.setOrganizationName("audio-visualizer")
+
+ if mode == 'cmd':
+ from command import *
+
+ main = Command()
+
+ elif mode == 'gui':
+ from mainwindow import *
+ import atexit
+ import signal
+
+ if getattr(sys, 'frozen', False):
+ # frozen
+ wd = os.path.dirname(sys.executable)
+ else:
+ # unfrozen
+ wd = os.path.dirname(os.path.realpath(__file__))
+
+ window = uic.loadUi(os.path.join(wd, "mainwindow.ui"))
+ # window.adjustSize()
+ desc = QtWidgets.QDesktopWidget()
+ dpi = desc.physicalDpiX()
+
+ topMargin = 0 if (dpi == 96) else int(10 * (dpi / 96))
+ window.resize(window.width() * (dpi / 96), window.height() * (dpi / 96))
+ # window.verticalLayout_2.setContentsMargins(0, topMargin, 0, 0)
+
+ main = MainWindow(window, proj)
+
+ signal.signal(signal.SIGINT, main.cleanUp)
+ atexit.register(main.cleanUp)
+
+ # applicable to both modes
+ sys.exit(app.exec_())
diff --git a/src/mainwindow.py b/src/mainwindow.py
new file mode 100644
index 0000000..a52a0f4
--- /dev/null
+++ b/src/mainwindow.py
@@ -0,0 +1,718 @@
+from queue import Queue
+from PyQt5 import QtCore, QtGui, uic, QtWidgets
+from PyQt5.QtCore import QSettings, Qt
+from PyQt5.QtWidgets import QMenu, QShortcut
+import sys
+import os
+import signal
+import filecmp
+import time
+
+import core
+import preview_thread
+import video_thread
+from presetmanager import PresetManager
+from main import LoadDefaultSettings
+
+
+class PreviewWindow(QtWidgets.QLabel):
+ def __init__(self, parent, img):
+ super(PreviewWindow, self).__init__()
+ self.parent = parent
+ self.setFrameStyle(QtWidgets.QFrame.StyledPanel)
+ self.pixmap = QtGui.QPixmap(img)
+
+ def paintEvent(self, event):
+ size = self.size()
+ painter = QtGui.QPainter(self)
+ point = QtCore.QPoint(0, 0)
+ scaledPix = self.pixmap.scaled(
+ size, Qt.KeepAspectRatio, transformMode=Qt.SmoothTransformation)
+
+ # start painting the label from left upper corner
+ point.setX((size.width() - scaledPix.width())/2)
+ point.setY((size.height() - scaledPix.height())/2)
+ painter.drawPixmap(point, scaledPix)
+
+ def changePixmap(self, img):
+ self.pixmap = QtGui.QPixmap(img)
+ self.repaint()
+
+
+class MainWindow(QtWidgets.QMainWindow):
+
+ newTask = QtCore.pyqtSignal(list)
+ processTask = QtCore.pyqtSignal()
+ videoTask = QtCore.pyqtSignal(str, str, list)
+
+ def __init__(self, window, project):
+ QtWidgets.QMainWindow.__init__(self)
+
+ # print('main thread id: {}'.format(QtCore.QThread.currentThreadId()))
+ self.window = window
+ self.core = core.Core()
+
+ self.pages = [] # widgets of component settings
+ self.lastAutosave = time.time()
+
+ # Create data directory, load/create settings
+ self.dataDir = self.core.dataDir
+ self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
+ self.settings = QSettings(
+ os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat)
+ LoadDefaultSettings(self)
+ self.presetManager = PresetManager(
+ uic.loadUi(
+ os.path.join(self.core.wd, 'presetmanager.ui')), self)
+
+ if not os.path.exists(self.dataDir):
+ os.makedirs(self.dataDir)
+ for neededDirectory in (
+ self.core.presetDir, self.settings.value("projectDir")):
+ if not os.path.exists(neededDirectory):
+ os.mkdir(neededDirectory)
+
+ # Make queues/timers for the preview thread
+ self.previewQueue = Queue()
+ self.previewThread = QtCore.QThread(self)
+ self.previewWorker = preview_thread.Worker(self, self.previewQueue)
+ self.previewWorker.moveToThread(self.previewThread)
+ self.previewWorker.imageCreated.connect(self.showPreviewImage)
+ self.previewThread.start()
+
+ self.timer = QtCore.QTimer(self)
+ self.timer.timeout.connect(self.processTask.emit)
+ self.timer.start(500)
+
+ # Begin decorating the window and connecting events
+ componentList = self.window.listWidget_componentList
+
+ window.toolButton_selectAudioFile.clicked.connect(
+ self.openInputFileDialog)
+
+ window.toolButton_selectOutputFile.clicked.connect(
+ self.openOutputFileDialog)
+
+ window.progressBar_createVideo.setValue(0)
+
+ window.pushButton_createVideo.clicked.connect(
+ self.createAudioVisualisation)
+
+ window.pushButton_Cancel.clicked.connect(self.stopVideo)
+
+ for i, container in enumerate(self.core.encoder_options['containers']):
+ window.comboBox_videoContainer.addItem(container['name'])
+ if container['name'] == self.settings.value('outputContainer'):
+ selectedContainer = i
+
+ window.comboBox_videoContainer.setCurrentIndex(selectedContainer)
+ window.comboBox_videoContainer.currentIndexChanged.connect(
+ self.updateCodecs
+ )
+
+ self.updateCodecs()
+
+ for i in range(window.comboBox_videoCodec.count()):
+ codec = window.comboBox_videoCodec.itemText(i)
+ if codec == self.settings.value('outputVideoCodec'):
+ window.comboBox_videoCodec.setCurrentIndex(i)
+ #print(codec)
+
+ for i in range(window.comboBox_audioCodec.count()):
+ codec = window.comboBox_audioCodec.itemText(i)
+ if codec == self.settings.value('outputAudioCodec'):
+ window.comboBox_audioCodec.setCurrentIndex(i)
+
+ window.comboBox_videoCodec.currentIndexChanged.connect(
+ self.updateCodecSettings
+ )
+
+ window.comboBox_audioCodec.currentIndexChanged.connect(
+ self.updateCodecSettings
+ )
+
+ vBitrate = int(self.settings.value('outputVideoBitrate'))
+ aBitrate = int(self.settings.value('outputAudioBitrate'))
+
+ window.spinBox_vBitrate.setValue(vBitrate)
+ window.spinBox_aBitrate.setValue(aBitrate)
+
+ window.spinBox_vBitrate.valueChanged.connect(self.updateCodecSettings)
+ window.spinBox_aBitrate.valueChanged.connect(self.updateCodecSettings)
+
+ self.previewWindow = PreviewWindow(self, os.path.join(
+ self.core.wd, "background.png"))
+ window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
+
+ # Make component buttons
+ self.compMenu = QMenu()
+ for i, comp in enumerate(self.core.modules):
+ action = self.compMenu.addAction(comp.Component.__doc__)
+ action.triggered.connect(
+ lambda item=i: self.core.insertComponent(0, item, self))
+
+ self.window.pushButton_addComponent.setMenu(self.compMenu)
+
+ componentList.dropEvent = self.dragComponent
+ componentList.itemSelectionChanged.connect(
+ self.changeComponentWidget)
+
+ self.window.pushButton_removeComponent.clicked.connect(
+ lambda _: self.removeComponent())
+
+ componentList.setContextMenuPolicy(
+ QtCore.Qt.CustomContextMenu)
+ componentList.customContextMenuRequested.connect(self.componentContextMenu)
+
+ currentRes = str(self.settings.value('outputWidth'))+'x' + \
+ str(self.settings.value('outputHeight'))
+ for i, res in enumerate(self.resolutions):
+ window.comboBox_resolution.addItem(res)
+ if res == currentRes:
+ currentRes = i
+ window.comboBox_resolution.setCurrentIndex(currentRes)
+ window.comboBox_resolution.currentIndexChanged.connect(
+ self.updateResolution)
+
+ self.window.pushButton_listMoveUp.clicked.connect(
+ lambda: self.moveComponent(-1)
+ )
+ self.window.pushButton_listMoveDown.clicked.connect(
+ lambda: self.moveComponent(1)
+ )
+
+ # Configure the Projects Menu
+ self.projectMenu = QMenu()
+ self.window.menuButton_newProject = self.projectMenu.addAction(
+ "New Project")
+ self.window.menuButton_newProject.triggered.connect(
+ self.createNewProject)
+
+ self.window.menuButton_openProject = self.projectMenu.addAction(
+ "Open Project")
+ self.window.menuButton_openProject.triggered.connect(
+ self.openOpenProjectDialog)
+
+ action = self.projectMenu.addAction("Save Project")
+ action.triggered.connect(self.saveCurrentProject)
+
+ action = self.projectMenu.addAction("Save Project As")
+ action.triggered.connect(self.openSaveProjectDialog)
+
+ self.window.pushButton_projects.setMenu(self.projectMenu)
+
+ # Configure the Presets Button
+ self.window.pushButton_presets.clicked.connect(
+ self.openPresetManager
+ )
+
+ window.show()
+
+ if project and project != self.autosavePath:
+ if not project.endswith('.avp'):
+ project += '.avp'
+ # open a project from the commandline
+ if not os.path.dirname(project):
+ project = os.path.join(os.path.expanduser('~'), project)
+ self.currentProject = project
+ self.settings.setValue("currentProject", project)
+ if os.path.exists(self.autosavePath):
+ os.remove(self.autosavePath)
+ else:
+ # open the last currentProject from settings
+ self.currentProject = self.settings.value("currentProject")
+
+ # delete autosave if it's identical to this project
+ if self.autosaveExists(identical=True):
+ os.remove(self.autosavePath)
+
+ if self.currentProject and os.path.exists(self.autosavePath):
+ ch = self.showMessage(
+ msg="Restore unsaved changes in project '%s'?"
+ % os.path.basename(self.currentProject)[:-4],
+ showCancel=True)
+ if ch:
+ self.saveProjectChanges()
+ else:
+ os.remove(self.autosavePath)
+
+ self.openProject(self.currentProject, prompt=False)
+ self.drawPreview(True)
+
+ # Setup Hotkeys
+ QtWidgets.QShortcut("Ctrl+S", self.window, self.saveCurrentProject)
+ QtWidgets.QShortcut("Ctrl+A", self.window, self.openSaveProjectDialog)
+ QtWidgets.QShortcut("Ctrl+O", self.window, self.openOpenProjectDialog)
+ QtWidgets.QShortcut("Ctrl+N", self.window, self.createNewProject)
+
+ QtWidgets.QShortcut("Ctrl+T", self.window, activated=lambda:
+ self.window.pushButton_addComponent.click())
+ QtWidgets.QShortcut("Ctrl+Space", self.window, activated=lambda:
+ self.window.listWidget_componentList.setFocus())
+ QtWidgets.QShortcut("Ctrl+Shift+S", self.window,
+ self.presetManager.openSavePresetDialog)
+ QtWidgets.QShortcut("Ctrl+Shift+C", self.window,
+ self.presetManager.clearPreset)
+
+ QtWidgets.QShortcut("Ctrl+Up", self.window,
+ activated=lambda: self.moveComponent(-1))
+ QtWidgets.QShortcut("Ctrl+Down", self.window,
+ activated=lambda: self.moveComponent(1))
+ QtWidgets.QShortcut("Ctrl+Home", self.window, self.moveComponentTop)
+ QtWidgets.QShortcut("Ctrl+End", self.window, self.moveComponentBottom)
+ QtWidgets.QShortcut("Ctrl+r", self.window, self.removeComponent)
+
+ def cleanUp(self):
+ self.timer.stop()
+ self.previewThread.quit()
+ self.previewThread.wait()
+ self.autosave()
+
+ def updateWindowTitle(self):
+ appName = 'Audio Visualizer'
+ if self.currentProject:
+ appName += ' - %s' % \
+ os.path.splitext(
+ os.path.basename(self.currentProject))[0]
+ self.window.setWindowTitle(appName)
+
+ @QtCore.pyqtSlot(int, dict)
+ def updateComponentTitle(self, pos, presetStore=False):
+ if type(presetStore) == dict:
+ name = presetStore['preset']
+ if name == None or name not in self.core.savedPresets:
+ modified = False
+ else:
+ modified = (presetStore != self.core.savedPresets[name])
+ else:
+ modified = bool(presetStore)
+ if pos < 0:
+ pos = len(self.core.selectedComponents)-1
+ title = str(self.core.selectedComponents[pos])
+ if self.core.selectedComponents[pos].currentPreset:
+ title += ' - %s' % self.core.selectedComponents[pos].currentPreset
+ if modified:
+ title += '*'
+ self.window.listWidget_componentList.item(pos).setText(title)
+
+ def updateCodecs(self):
+ containerWidget = self.window.comboBox_videoContainer
+ vCodecWidget = self.window.comboBox_videoCodec
+ aCodecWidget = self.window.comboBox_audioCodec
+ index = containerWidget.currentIndex()
+ name = containerWidget.itemText(index)
+ self.settings.setValue('outputContainer', name)
+
+ vCodecWidget.clear()
+ aCodecWidget.clear()
+
+ for container in self.core.encoder_options['containers']:
+ if container['name'] == name:
+ for vCodec in container['video-codecs']:
+ vCodecWidget.addItem(vCodec)
+ for aCodec in container['audio-codecs']:
+ aCodecWidget.addItem(aCodec)
+
+ def updateCodecSettings(self):
+ vCodecWidget = self.window.comboBox_videoCodec
+ vBitrateWidget = self.window.spinBox_vBitrate
+ aBitrateWidget = self.window.spinBox_aBitrate
+ aCodecWidget = self.window.comboBox_audioCodec
+ currentVideoCodec = vCodecWidget.currentIndex()
+ currentVideoCodec = vCodecWidget.itemText(currentVideoCodec)
+ currentVideoBitrate = vBitrateWidget.value()
+ currentAudioCodec = aCodecWidget.currentIndex()
+ currentAudioCodec = aCodecWidget.itemText(currentAudioCodec)
+ currentAudioBitrate = aBitrateWidget.value()
+ self.settings.setValue('outputVideoCodec', currentVideoCodec)
+ self.settings.setValue('outputAudioCodec', currentAudioCodec)
+ self.settings.setValue('outputVideoBitrate', currentVideoBitrate)
+ self.settings.setValue('outputAudioBitrate', currentAudioBitrate)
+
+ def autosave(self, force=False):
+ if not self.currentProject:
+ if os.path.exists(self.autosavePath):
+ os.remove(self.autosavePath)
+ elif force or time.time() - self.lastAutosave >= 2.0:
+ self.core.createProjectFile(self.autosavePath)
+ self.lastAutosave = time.time()
+
+ def autosaveExists(self, identical=True):
+ try:
+ if self.currentProject and os.path.exists(self.autosavePath) \
+ and filecmp.cmp(
+ self.autosavePath, self.currentProject) == identical:
+ return True
+ except FileNotFoundError:
+ print('project file couldn\'t be located:', self.currentProject)
+ return identical
+ return False
+
+ def saveProjectChanges(self):
+ try:
+ os.remove(self.currentProject)
+ os.rename(self.autosavePath, self.currentProject)
+ return True
+ except (FileNotFoundError, IsADirectoryError) as e:
+ self.showMessage(
+ msg='Project file couldn\'t be saved.',
+ detail=str(e))
+ return False
+
+ def openInputFileDialog(self):
+ inputDir = self.settings.value("inputDir", os.path.expanduser("~"))
+
+ fileName = QtGui.QFileDialog.getOpenFileName(
+ self.window, "Open Audio File",
+ inputDir, "Audio Files (%s)" % " ".join(self.core.audioFormats))
+
+ if not fileName == "":
+ self.settings.setValue("inputDir", os.path.dirname(fileName))
+ self.window.lineEdit_audioFile.setText(fileName)
+
+ def openOutputFileDialog(self):
+ outputDir = self.settings.value("outputDir", os.path.expanduser("~"))
+
+ fileName = QtGui.QFileDialog.getSaveFileName(
+ self.window, "Set Output Video File",
+ outputDir,
+ "Video Files (%s);; All Files (*)" % " ".join(self.core.videoFormats))
+
+ if not fileName == "":
+ self.settings.setValue("outputDir", os.path.dirname(fileName))
+ self.window.lineEdit_outputFile.setText(fileName)
+
+ def stopVideo(self):
+ print('stop')
+ self.videoWorker.cancel()
+ self.canceled = True
+
+ def createAudioVisualisation(self):
+ # create output video if mandatory settings are filled in
+ if self.window.lineEdit_audioFile.text() and \
+ self.window.lineEdit_outputFile.text():
+ self.canceled = False
+ self.progressBarUpdated(-1)
+ self.videoThread = QtCore.QThread(self)
+ self.videoWorker = video_thread.Worker(self)
+ self.videoWorker.moveToThread(self.videoThread)
+ self.videoWorker.videoCreated.connect(self.videoCreated)
+ self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated)
+ self.videoWorker.progressBarSetText.connect(
+ self.progressBarSetText)
+ self.videoWorker.imageCreated.connect(self.showPreviewImage)
+ self.videoWorker.encoding.connect(self.changeEncodingStatus)
+ self.videoThread.start()
+ outputPath = self.window.lineEdit_outputFile.text()
+ if not os.path.dirname(outputPath):
+ outputPath = os.path.join(
+ os.path.expanduser("~"), outputPath)
+ self.videoTask.emit(
+ self.window.lineEdit_audioFile.text(),
+ outputPath,
+ self.core.selectedComponents)
+ else:
+ self.showMessage(
+ msg="You must select an audio file and output filename.")
+
+ def changeEncodingStatus(self, status):
+ if status:
+ self.window.pushButton_createVideo.setEnabled(False)
+ self.window.pushButton_Cancel.setEnabled(True)
+ self.window.comboBox_resolution.setEnabled(False)
+ self.window.stackedWidget.setEnabled(False)
+ self.window.tab_encoderSettings.setEnabled(False)
+ self.window.label_audioFile.setEnabled(False)
+ self.window.toolButton_selectAudioFile.setEnabled(False)
+ self.window.label_outputFile.setEnabled(False)
+ self.window.toolButton_selectOutputFile.setEnabled(False)
+ self.window.lineEdit_audioFile.setEnabled(False)
+ self.window.lineEdit_outputFile.setEnabled(False)
+ self.window.pushButton_addComponent.setEnabled(False)
+ self.window.pushButton_removeComponent.setEnabled(False)
+ self.window.pushButton_listMoveDown.setEnabled(False)
+ self.window.pushButton_listMoveUp.setEnabled(False)
+ self.window.listWidget_componentList.setEnabled(False)
+ self.window.menuButton_newProject.setEnabled(False)
+ self.window.menuButton_openProject.setEnabled(False)
+ else:
+ self.window.pushButton_createVideo.setEnabled(True)
+ self.window.pushButton_Cancel.setEnabled(False)
+ self.window.comboBox_resolution.setEnabled(True)
+ self.window.stackedWidget.setEnabled(True)
+ self.window.tab_encoderSettings.setEnabled(True)
+ self.window.label_audioFile.setEnabled(True)
+ self.window.toolButton_selectAudioFile.setEnabled(True)
+ self.window.lineEdit_audioFile.setEnabled(True)
+ self.window.label_outputFile.setEnabled(True)
+ self.window.toolButton_selectOutputFile.setEnabled(True)
+ self.window.lineEdit_outputFile.setEnabled(True)
+ self.window.pushButton_addComponent.setEnabled(True)
+ self.window.pushButton_removeComponent.setEnabled(True)
+ self.window.pushButton_listMoveDown.setEnabled(True)
+ self.window.pushButton_listMoveUp.setEnabled(True)
+ self.window.listWidget_componentList.setEnabled(True)
+ self.window.menuButton_newProject.setEnabled(True)
+ self.window.menuButton_openProject.setEnabled(True)
+ self.drawPreview(True)
+
+ def progressBarUpdated(self, value):
+ self.window.progressBar_createVideo.setValue(value)
+
+ def progressBarSetText(self, value):
+ self.window.progressBar_createVideo.setFormat(value)
+
+ def videoCreated(self):
+ self.videoThread.quit()
+ self.videoThread.wait()
+
+ def updateResolution(self):
+ resIndex = int(self.window.comboBox_resolution.currentIndex())
+ res = self.resolutions[resIndex].split('x')
+ self.settings.setValue('outputWidth', res[0])
+ self.settings.setValue('outputHeight', res[1])
+ self.drawPreview()
+
+ def drawPreview(self, force=False):
+ self.newTask.emit(self.core.selectedComponents)
+ # self.processTask.emit()
+ self.autosave(force)
+
+ def showPreviewImage(self, image):
+ self.previewWindow.changePixmap(image)
+
+ def insertComponent(self, index):
+ componentList = self.window.listWidget_componentList
+ stackedWidget = self.window.stackedWidget
+
+ componentList.insertItem(
+ index,
+ self.core.selectedComponents[index].__doc__)
+ componentList.setCurrentRow(index)
+
+ # connect to signal that adds an asterisk when modified
+ self.core.selectedComponents[index].modified.connect(
+ self.updateComponentTitle)
+
+ self.pages.insert(index, self.core.selectedComponents[index].page)
+ stackedWidget.insertWidget(index, self.pages[index])
+ stackedWidget.setCurrentIndex(index)
+
+ return index
+
+ def removeComponent(self):
+ componentList = self.window.listWidget_componentList
+
+ for selected in componentList.selectedItems():
+ index = componentList.row(selected)
+ self.window.stackedWidget.removeWidget(self.pages[index])
+ componentList.takeItem(index)
+ self.core.removeComponent(index)
+ self.pages.pop(index)
+ self.changeComponentWidget()
+ self.drawPreview()
+
+ def moveComponent(self, change):
+ '''Moves a component relatively from its current position'''
+ componentList = self.window.listWidget_componentList
+ stackedWidget = self.window.stackedWidget
+
+ row = componentList.currentRow()
+ newRow = row + change
+ if newRow > -1 and newRow < componentList.count():
+ self.core.moveComponent(row, newRow)
+
+ # update widgets
+ page = self.pages.pop(row)
+ self.pages.insert(newRow, page)
+ item = componentList.takeItem(row)
+ newItem = componentList.insertItem(newRow, item)
+ widget = stackedWidget.removeWidget(page)
+ stackedWidget.insertWidget(newRow, page)
+ componentList.setCurrentRow(newRow)
+ stackedWidget.setCurrentIndex(newRow)
+ self.drawPreview()
+
+ def moveComponentTop(self):
+ componentList = self.window.listWidget_componentList
+ row = -componentList.currentRow()
+ self.moveComponent(row)
+
+ def moveComponentBottom(self):
+ componentList = self.window.listWidget_componentList
+ row = len(componentList)-componentList.currentRow()-1
+ self.moveComponent(row)
+
+ def dragComponent(self, event):
+ '''Drop event for the component listwidget'''
+ componentList = self.window.listWidget_componentList
+
+ modelIndexes = [ \
+ componentList.model().index(i) \
+ for i in range(componentList.count()) \
+ ]
+ rects = [ \
+ componentList.visualRect(modelIndex) \
+ for modelIndex in modelIndexes \
+ ]
+
+ rowPos = [rect.contains(event.pos()) for rect in rects]
+ if not any(rowPos):
+ return
+
+ i = rowPos.index(True)
+ change = (componentList.currentRow() - i) * -1
+ self.moveComponent(change)
+
+ def changeComponentWidget(self):
+ selected = self.window.listWidget_componentList.selectedItems()
+ if selected:
+ index = self.window.listWidget_componentList.row(selected[0])
+ self.window.stackedWidget.setCurrentIndex(index)
+
+ def openPresetManager(self):
+ '''Preset manager for importing, exporting, renaming, deleting'''
+ self.presetManager.show()
+
+ def clear(self):
+ '''Get a blank slate'''
+ self.core.clearComponents()
+ self.window.listWidget_componentList.clear()
+ for widget in self.pages:
+ self.window.stackedWidget.removeWidget(widget)
+ self.pages = []
+
+ def createNewProject(self):
+ self.openSaveChangesDialog('starting a new project')
+
+ self.clear()
+ self.currentProject = None
+ self.settings.setValue("currentProject", None)
+ self.drawPreview(True)
+ self.updateWindowTitle()
+
+ def saveCurrentProject(self):
+ if self.currentProject:
+ self.core.createProjectFile(self.currentProject)
+ else:
+ self.openSaveProjectDialog()
+
+ def openSaveChangesDialog(self, phrase):
+ success = True
+ if self.autosaveExists(identical=False):
+ ch = self.showMessage(
+ msg="You have unsaved changes in project '%s'. "
+ "Save before %s?" % \
+ (os.path.basename(self.currentProject)[:-4],
+ phrase),
+ showCancel=True)
+ if ch:
+ success = self.saveProjectChanges()
+
+ if success and os.path.exists(self.autosavePath):
+ os.remove(self.autosavePath)
+
+ def openSaveProjectDialog(self):
+ filename = QtGui.QFileDialog.getSaveFileName(
+ self.window, "Create Project File",
+ self.settings.value("projectDir"),
+ "Project Files (*.avp)")
+ if not filename:
+ return
+ if not filename.endswith(".avp"):
+ filename += '.avp'
+ self.settings.setValue("projectDir", os.path.dirname(filename))
+ self.settings.setValue("currentProject", filename)
+ self.currentProject = filename
+ self.updateWindowTitle()
+ self.core.createProjectFile(filename)
+
+ def openOpenProjectDialog(self):
+ filename = QtGui.QFileDialog.getOpenFileName(
+ self.window, "Open Project File",
+ self.settings.value("projectDir"),
+ "Project Files (*.avp)")
+ self.openProject(filename)
+
+ def openProject(self, filepath, prompt=True):
+ if not filepath or not os.path.exists(filepath) \
+ or not filepath.endswith('.avp'):
+ self.updateWindowTitle()
+ return
+
+ self.clear()
+ # ask to save any changes that are about to get deleted
+ if prompt:
+ self.openSaveChangesDialog('opening another project')
+
+ self.currentProject = filepath
+ self.updateWindowTitle()
+ self.settings.setValue("currentProject", filepath)
+ self.settings.setValue("projectDir", os.path.dirname(filepath))
+ # actually load the project using core method
+ self.core.openProject(self, filepath)
+ if self.window.listWidget_componentList.count() == 0:
+ self.drawPreview()
+ self.autosave(True)
+
+ def showMessage(self, **kwargs):
+ parent = kwargs['parent'] if 'parent' in kwargs else self.window
+ msg = QtGui.QMessageBox(parent)
+ msg.setModal(True)
+ msg.setText(kwargs['msg'])
+ msg.setIcon(
+ kwargs['icon'] if 'icon' in kwargs else QtGui.QMessageBox.Information)
+ msg.setDetailedText(kwargs['detail'] if 'detail' in kwargs else None)
+ if 'showCancel'in kwargs and kwargs['showCancel']:
+ msg.setStandardButtons(
+ QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel)
+ else:
+ msg.setStandardButtons(QtGui.QMessageBox.Ok)
+ ch = msg.exec_()
+ if ch == 1024:
+ return True
+ return False
+
+ def componentContextMenu(self, QPos):
+ '''Appears when right-clicking a component in the list'''
+ componentList = self.window.listWidget_componentList
+ if not componentList.selectedItems():
+ return
+
+ # don't show menu if clicking empty space
+ parentPosition = componentList.mapToGlobal(QtCore.QPoint(0, 0))
+ index = componentList.currentRow()
+ modelIndex = componentList.model().index(index)
+ if not componentList.visualRect(modelIndex).contains(QPos):
+ return
+
+ self.presetManager.findPresets()
+ self.menu = QtGui.QMenu()
+ menuItem = self.menu.addAction("Save Preset")
+ menuItem.triggered.connect(
+ self.presetManager.openSavePresetDialog
+ )
+
+ # submenu for opening presets
+ try:
+ presets = self.presetManager.presets[str(self.core.selectedComponents[index])]
+ self.submenu = QtGui.QMenu("Open Preset")
+ self.menu.addMenu(self.submenu)
+
+ for version, presetName in presets:
+ menuItem = self.submenu.addAction(presetName)
+ menuItem.triggered.connect(
+ lambda _, presetName=presetName:
+ self.presetManager.openPreset(presetName)
+ )
+ except KeyError:
+ pass
+
+ if self.core.selectedComponents[index].currentPreset:
+ menuItem = self.menu.addAction("Clear Preset")
+ menuItem.triggered.connect(
+ self.presetManager.clearPreset
+ )
+
+ self.menu.move(parentPosition + QPos)
+ self.menu.show()
diff --git a/src/mainwindow.ui b/src/mainwindow.ui
new file mode 100644
index 0000000..4a12fd5
--- /dev/null
+++ b/src/mainwindow.ui
@@ -0,0 +1,809 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 1008
+ 575
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+ MainWindow
+
+
+
+
+ 0
+ 0
+
+
+
+ false
+
+
+
+ 9
+
+
+ 0
+
+ -
+
+
-
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::MinimumExpanding
+
+
+
+ 0
+ 360
+
+
+
+
+ -
+
+
+ QLayout::SetDefaultConstraint
+
+
+ 0
+
+
-
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::MinimumExpanding
+
+
+
+ 420
+ 0
+
+
+
+
+
+
+ -
+
+
+ QLayout::SetMinimumSize
+
+
+ 3
+
+
-
+
+
+ QLayout::SetMinimumSize
+
+
+ 3
+
+
-
+
+
+ QLayout::SetMinimumSize
+
+
-
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 140
+ 20
+
+
+
+
+ -
+
+
+ Projects
+
+
+
+ -
+
+
+ Presets
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Minimum
+
+
+
+ 20
+ 2
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+ true
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Sunken
+
+
+ 1
+
+
+ true
+
+
+ true
+
+
+ false
+
+
+ QAbstractItemView::InternalMove
+
+
+ Qt::MoveAction
+
+
+
+ -
+
+
-
+
+
+ Add
+
+
+
+ -
+
+
+ Remove
+
+
+
+ -
+
+
+ Up
+
+
+
+ -
+
+
+ Down
+
+
+
+
+
+
+
+ -
+
+
+ 4
+
+
+ 2
+
+
+
+
+
+
+
+ -
+
+
+ QLayout::SetFixedSize
+
+
+ 4
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 500
+ 0
+
+
+
+
+ 16777215
+ 180
+
+
+
+ QTabWidget::North
+
+
+ QTabWidget::Rounded
+
+
+ 0
+
+
+
+ Export Video
+
+
+
+ 10
+
+
-
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 85
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 80
+ 0
+
+
+
+ Audio File
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 28
+
+
+
+
+ 16777215
+ 28
+
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+
+ 0
+ 28
+
+
+
+
+ 16777215
+ 28
+
+
+
+ ...
+
+
+
+
+
+ -
+
+
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 85
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+ Output File
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 28
+
+
+
+
+ 16777215
+ 28
+
+
+
+
+ -
+
+
+
+ 0
+ 28
+
+
+
+
+ 16777215
+ 28
+
+
+
+ ...
+
+
+
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ 24
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Minimum
+
+
+
+ 10
+ 20
+
+
+
+
+ -
+
+
+ Create Video
+
+
+
+ -
+
+
+ false
+
+
+ Cancel
+
+
+
+
+
+
+
+
+
+
+
+ Encoder Settings
+
+
+
+ 10
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 85
+ 0
+
+
+
+ Container
+
+
+
+ -
+
+
+
+ 150
+ 0
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Minimum
+
+
+
+ 5
+ 5
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Resolution
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 85
+ 0
+
+
+
+ Video Codec
+
+
+
+ -
+
+
+
+ 150
+ 0
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 5
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Video Bitrate (Kbps)
+
+
+
+ -
+
+
+ 99999
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 85
+ 0
+
+
+
+ Audio Codec
+
+
+
+ -
+
+
+
+ 150
+ 0
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 10
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Audio Bitrate (Kbps)
+
+
+
+ -
+
+
+ 9999
+
+
+
+
+
+
+
+
+
+ -
+
+
+ QLayout::SetDefaultConstraint
+
+
-
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::MinimumExpanding
+
+
+
+ 500
+ 0
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 180
+
+
+
+
+ 16777215
+ 180
+
+
+
+ -1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/presetmanager.py b/src/presetmanager.py
new file mode 100644
index 0000000..ec3f5cd
--- /dev/null
+++ b/src/presetmanager.py
@@ -0,0 +1,290 @@
+from PyQt5 import QtGui, QtCore, QtWidgets
+import string
+import os
+
+import core
+
+
+class PresetManager(QtWidgets.QDialog):
+ def __init__(self, window, parent):
+ super().__init__(parent.window)
+ self.parent = parent
+ self.core = parent.core
+ self.settings = parent.settings
+ self.presetDir = self.core.presetDir
+ if not self.settings.value('presetDir'):
+ self.settings.setValue(
+ "presetDir",
+ os.path.join(self.core.dataDir, 'projects'))
+
+ self.findPresets()
+
+ # window
+ self.lastFilter = '*'
+ self.presetRows = [] # list of (comp, vers, name) tuples
+ self.window = window
+ self.window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
+
+ # connect button signals
+ self.window.pushButton_delete.clicked.connect(self.openDeletePresetDialog)
+ self.window.pushButton_rename.clicked.connect(self.openRenamePresetDialog)
+ self.window.pushButton_import.clicked.connect(self.openImportDialog)
+ self.window.pushButton_export.clicked.connect(self.openExportDialog)
+ self.window.pushButton_close.clicked.connect(self.window.close)
+
+ # create filter box and preset list
+ self.drawFilterList()
+ self.window.comboBox_filter.currentIndexChanged.connect(
+ lambda: self.drawPresetList(
+ self.window.comboBox_filter.currentText(), self.window.lineEdit_search.text()
+ )
+ )
+
+ # make auto-completion for search bar
+ self.autocomplete = QtCore.QStringListModel()
+ completer = QtWidgets.QCompleter()
+ completer.setModel(self.autocomplete)
+ self.window.lineEdit_search.setCompleter(completer)
+ self.window.lineEdit_search.textChanged.connect(
+ lambda: self.drawPresetList(
+ self.window.comboBox_filter.currentText(), self.window.lineEdit_search.text()
+ )
+ )
+ self.drawPresetList('*')
+
+ def show(self):
+ '''Open a new preset manager window from the mainwindow'''
+ self.findPresets()
+ self.drawFilterList()
+ self.drawPresetList('*')
+ self.window.show()
+
+ def findPresets(self):
+ parseList = []
+ for dirpath, dirnames, filenames in os.walk(self.presetDir):
+ # anything without a subdirectory must be a preset folder
+ if dirnames:
+ continue
+ for preset in filenames:
+ compName = os.path.basename(os.path.dirname(dirpath))
+ compVers = os.path.basename(dirpath)
+ try:
+ parseList.append((compName, int(compVers), preset))
+ except ValueError:
+ continue
+ self.presets =\
+ {
+ compName : \
+ [
+ (vers, preset) \
+ for name, vers, preset in parseList \
+ if name == compName \
+ ] \
+ for compName, _, __ in parseList \
+ }
+
+ def drawPresetList(self, compFilter=None, presetFilter=''):
+ self.window.listWidget_presets.clear()
+ if compFilter:
+ self.lastFilter = str(compFilter)
+ else:
+ compFilter = str(self.lastFilter)
+ self.presetRows = []
+ presetNames = []
+ for component, presets in self.presets.items():
+ if compFilter != '*' and component != compFilter:
+ continue
+ for vers, preset in presets:
+ if not presetFilter or presetFilter in preset:
+ self.window.listWidget_presets.addItem('%s: %s' % (component, preset))
+ self.presetRows.append((component, vers, preset))
+ if preset not in presetNames:
+ presetNames.append(preset)
+ self.autocomplete.setStringList(presetNames)
+
+ def drawFilterList(self):
+ self.window.comboBox_filter.clear()
+ self.window.comboBox_filter.addItem('*')
+ for component in self.presets:
+ self.window.comboBox_filter.addItem(component)
+
+ def clearPreset(self, compI=None):
+ '''Functions on mainwindow level from the context menu'''
+ compI = self.parent.window.listWidget_componentList.currentRow()
+ self.core.clearPreset(compI, self.parent)
+
+ def openSavePresetDialog(self):
+ '''Functions on mainwindow level from the context menu'''
+ window = self.parent.window
+ selectedComponents = self.core.selectedComponents
+ componentList = self.parent.window.listWidget_componentList
+
+ if componentList.currentRow() == -1:
+ return
+ while True:
+ index = componentList.currentRow()
+ currentPreset = selectedComponents[index].currentPreset
+ newName, OK = QtGui.QInputDialog.getText(
+ self.parent.window,
+ 'Audio Visualizer',
+ 'New Preset Name:',
+ QtGui.QLineEdit.Normal,
+ currentPreset
+ )
+ if OK:
+ if core.Core.badName(newName):
+ self.warnMessage(self.parent.window)
+ continue
+ if newName:
+ if index != -1:
+ selectedComponents[index].currentPreset = newName
+ saveValueStore = \
+ selectedComponents[index].savePreset()
+ componentName = str(selectedComponents[index]).strip()
+ vers = selectedComponents[index].version()
+ self.createNewPreset(
+ componentName, vers, newName,
+ saveValueStore, window=self.parent.window)
+ self.openPreset(newName)
+ break
+
+ def createNewPreset(
+ self, compName, vers, filename, saveValueStore, **kwargs):
+ path = os.path.join(self.presetDir, compName, str(vers), filename)
+ if self.presetExists(path, **kwargs):
+ return
+ self.core.createPresetFile(compName, vers, filename, saveValueStore)
+
+ def presetExists(self, path, **kwargs):
+ if os.path.exists(path):
+ window = self.window \
+ if 'window' not in kwargs else kwargs['window']
+ ch = self.parent.showMessage(
+ msg="%s already exists! Overwrite it?" %
+ os.path.basename(path),
+ showCancel=True,
+ icon=QtGui.QMessageBox.Warning,
+ parent=window)
+ if not ch:
+ # user clicked cancel
+ return True
+
+ return False
+
+ def openPreset(self, presetName):
+ componentList = self.parent.window.listWidget_componentList
+ selectedComponents = self.parent.core.selectedComponents
+
+ index = componentList.currentRow()
+ if index == -1:
+ return
+ componentName = str(selectedComponents[index]).strip()
+ version = selectedComponents[index].version()
+ dirname = os.path.join(self.presetDir, componentName, str(version))
+ filepath = os.path.join(dirname, presetName)
+ self.core.openPreset(filepath, index, presetName)
+
+ self.parent.updateComponentTitle(index)
+ self.parent.drawPreview()
+
+ def openDeletePresetDialog(self):
+ selected = self.window.listWidget_presets.selectedItems()
+ if not selected:
+ return
+ row = self.window.listWidget_presets.row(selected[0])
+ comp, vers, name = self.presetRows[row]
+ ch = self.parent.showMessage(
+ msg='Really delete %s?' % name,
+ showCancel=True,
+ icon=QtGui.QMessageBox.Warning,
+ parent=self.window
+ )
+ if not ch:
+ return
+ self.deletePreset(comp, vers, name)
+ self.findPresets()
+ self.drawPresetList()
+
+ def deletePreset(self, comp, vers, name):
+ filepath = os.path.join(self.presetDir, comp, str(vers), name)
+ os.remove(filepath)
+
+ def warnMessage(self, window=None):
+ print(window)
+ self.parent.showMessage(
+ msg='Preset names must contain only letters, '
+ 'numbers, and spaces.',
+ parent=window if window else self.window)
+
+ def openRenamePresetDialog(self):
+ presetList = self.window.listWidget_presets
+ if presetList.currentRow() == -1:
+ return
+
+ while True:
+ index = presetList.currentRow()
+ newName, OK = QtGui.QInputDialog.getText(
+ self.window,
+ 'Preset Manager',
+ 'Rename Preset:',
+ QtGui.QLineEdit.Normal,
+ self.presetRows[index][2]
+ )
+ if OK:
+ if core.Core.badName(newName):
+ self.warnMessage()
+ continue
+ if newName:
+ comp, vers, oldName = self.presetRows[index]
+ path = os.path.join(
+ self.presetDir, comp, str(vers))
+ newPath = os.path.join(path, newName)
+ oldPath = os.path.join(path, oldName)
+ if self.presetExists(newPath):
+ return
+ if os.path.exists(newPath):
+ os.remove(newPath)
+ os.rename(oldPath, newPath)
+ self.findPresets()
+ self.drawPresetList()
+ break
+
+ def openImportDialog(self):
+ filename = QtGui.QFileDialog.getOpenFileName(
+ self.window, "Import Preset File",
+ self.settings.value("presetDir"),
+ "Preset Files (*.avl)")
+ if filename:
+ # get installed path & ask user to overwrite if needed
+ path = ''
+ while True:
+ if path:
+ if self.presetExists(path):
+ break
+ else:
+ if os.path.exists(path):
+ os.remove(path)
+ success, path = self.core.importPreset(filename)
+ if success:
+ break
+
+ self.findPresets()
+ self.drawPresetList()
+ self.settings.setValue("presetDir", os.path.dirname(filename))
+
+ def openExportDialog(self):
+ if not self.window.listWidget_presets.selectedItems():
+ return
+ filename = QtGui.QFileDialog.getSaveFileName(
+ self.window, "Export Preset",
+ self.settings.value("presetDir"),
+ "Preset Files (*.avl)")
+ if filename:
+ index = self.window.listWidget_presets.currentRow()
+ comp, vers, name = self.presetRows[index]
+ if not self.core.exportPreset(filename, comp, vers, name):
+ self.parent.showMessage(
+ msg='Couldn\'t export %s.' % filename,
+ parent=self.window
+ )
+ self.settings.setValue("presetDir", os.path.dirname(filename))
diff --git a/src/presetmanager.ui b/src/presetmanager.ui
new file mode 100644
index 0000000..5257b1c
--- /dev/null
+++ b/src/presetmanager.ui
@@ -0,0 +1,150 @@
+
+
+ presetmanager
+
+
+ Qt::NonModal
+
+
+ true
+
+
+
+ 0
+ 0
+ 497
+ 377
+
+
+
+ Preset Manager
+
+
+ -
+
+
-
+
+
+
+
+
+ Filter by name
+
+
+
+ -
+
+
+
+ 200
+ 0
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ true
+
+
+
+
+
+ -
+
+
+ QLayout::SetMinimumSize
+
+
-
+
+
+ Import
+
+
+
+ -
+
+
+ Export
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ true
+
+
+ Rename
+
+
+
+ -
+
+
+ Delete
+
+
+
+
+
+ -
+
+
-
+
+
+ <html><head/><body><p><span style=" font-size:10pt; font-style:italic;">Right-click components in the main window to create presets</span></p></body></html>
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Close
+
+
+
+
+
+
+
+
+
+
diff --git a/src/preview_thread.py b/src/preview_thread.py
new file mode 100644
index 0000000..4a46d51
--- /dev/null
+++ b/src/preview_thread.py
@@ -0,0 +1,59 @@
+from PyQt5 import QtCore, QtGui, uic
+from PyQt5.QtCore import pyqtSignal, pyqtSlot
+from PIL import Image
+from PIL.ImageQt import ImageQt
+import core
+from queue import Queue, Empty
+import os
+from copy import copy
+
+
+class Worker(QtCore.QObject):
+
+ imageCreated = pyqtSignal(['QImage'])
+
+ def __init__(self, parent=None, queue=None):
+ QtCore.QObject.__init__(self)
+ parent.newTask.connect(self.createPreviewImage)
+ parent.processTask.connect(self.process)
+ self.parent = parent
+ self.core = core.Core()
+ self.queue = queue
+ self.core.settings = parent.settings
+ self.stackedWidget = parent.window.stackedWidget
+ self.background = Image.new("RGBA", (1920, 1080), (0, 0, 0, 0))
+ self.background.paste(Image.open(os.path.join(
+ self.core.wd, "background.png")))
+
+ @pyqtSlot(list)
+ def createPreviewImage(self, components):
+ dic = {
+ "components": components,
+ }
+ self.queue.put(dic)
+
+ @pyqtSlot()
+ def process(self):
+ try:
+ nextPreviewInformation = self.queue.get(block=False)
+ while self.queue.qsize() >= 2:
+ try:
+ self.queue.get(block=False)
+ except Empty:
+ continue
+
+ width = int(self.core.settings.value('outputWidth'))
+ height = int(self.core.settings.value('outputHeight'))
+ frame = copy(self.background)
+ frame = frame.resize((width, height))
+
+ components = nextPreviewInformation["components"]
+ for component in reversed(components):
+ frame = Image.alpha_composite(
+ frame, component.previewRender(self))
+
+ self._image = ImageQt(frame)
+ self.imageCreated.emit(QtGui.QImage(self._image))
+
+ except Empty:
+ True
diff --git a/src/video_thread.py b/src/video_thread.py
new file mode 100644
index 0000000..5ea6d21
--- /dev/null
+++ b/src/video_thread.py
@@ -0,0 +1,309 @@
+from PyQt5 import QtCore, QtGui, uic
+from PyQt5.QtCore import pyqtSignal, pyqtSlot
+from PIL import Image, ImageDraw, ImageFont
+from PIL.ImageQt import ImageQt
+import core
+import numpy
+import subprocess as sp
+import sys
+import os
+from queue import Queue, PriorityQueue
+from threading import Thread, Event
+import time
+from copy import copy
+import signal
+
+
+class Worker(QtCore.QObject):
+
+ imageCreated = pyqtSignal(['QImage'])
+ videoCreated = pyqtSignal()
+ progressBarUpdate = pyqtSignal(int)
+ progressBarSetText = pyqtSignal(str)
+ encoding = pyqtSignal(bool)
+
+ def __init__(self, parent=None):
+ QtCore.QObject.__init__(self)
+ self.core = core.Core()
+ self.core.settings = parent.settings
+ self.modules = parent.core.modules
+ self.parent = parent
+ parent.videoTask.connect(self.createVideo)
+ self.sampleSize = 1470 # 44100 / 30 = 1470
+ self.canceled = False
+ self.error = False
+ self.stopped = False
+
+ def renderNode(self):
+ while not self.stopped:
+ i = self.compositeQueue.get()
+ frame = None
+
+ for compNo, comp in reversed(list(enumerate(self.components))):
+ if compNo in self.staticComponents and \
+ self.staticComponents[compNo] is not None:
+ if frame is None:
+ frame = self.staticComponents[compNo]
+ else:
+ frame = Image.alpha_composite(
+ frame, self.staticComponents[compNo])
+ else:
+ if frame is None:
+ frame = comp.frameRender(compNo, i[0], i[1])
+ else:
+ frame = Image.alpha_composite(
+ frame, comp.frameRender(compNo, i[0], i[1]))
+
+ self.renderQueue.put([i[0], frame])
+ self.compositeQueue.task_done()
+
+ def renderDispatch(self):
+ print('Dispatching Frames for Compositing...')
+
+ for i in range(0, len(self.completeAudioArray), self.sampleSize):
+ self.compositeQueue.put([i, self.bgI])
+ # increment tracked video frame for next iteration
+ self.bgI += 1
+
+ def previewDispatch(self):
+ background = Image.new("RGBA", (1920, 1080), (0, 0, 0, 0))
+ background.paste(Image.open(os.path.join(
+ self.core.wd, "background.png")))
+ background = background.resize((self.width, self.height))
+
+ while not self.stopped:
+ i = self.previewQueue.get()
+ if time.time() - self.lastPreview >= 0.06 or i[0] == 0:
+ image = copy(background)
+ image = Image.alpha_composite(image, i[1])
+ self._image = ImageQt(image)
+ self.imageCreated.emit(QtGui.QImage(self._image))
+ self.lastPreview = time.time()
+
+ self.previewQueue.task_done()
+
+ @pyqtSlot(str, str, list)
+ def createVideo(self, inputFile, outputFile, components):
+ self.encoding.emit(True)
+ self.components = components
+ self.outputFile = outputFile
+ self.bgI = 0 # tracked video frame
+ self.reset()
+ self.width = int(self.core.settings.value('outputWidth'))
+ self.height = int(self.core.settings.value('outputHeight'))
+ progressBarValue = 0
+ self.progressBarUpdate.emit(progressBarValue)
+
+ self.progressBarSetText.emit('Loading audio file...')
+ self.completeAudioArray = self.core.readAudioFile(inputFile, self)
+
+ # test if user has libfdk_aac
+ encoders = sp.check_output(
+ self.core.FFMPEG_BIN + " -encoders -hide_banner",
+ shell=True)
+
+ encoders = encoders.decode("utf-8")
+
+ acodec = self.core.settings.value('outputAudioCodec')
+
+ options = self.core.encoder_options
+ containerName = self.core.settings.value('outputContainer')
+ vcodec = self.core.settings.value('outputVideoCodec')
+ vbitrate = str(self.core.settings.value('outputVideoBitrate'))+'k'
+ acodec = self.core.settings.value('outputAudioCodec')
+ abitrate = str(self.core.settings.value('outputAudioBitrate'))+'k'
+
+ for cont in options['containers']:
+ if cont['name'] == containerName:
+ container = cont['container']
+ break
+
+ vencoders = options['video-codecs'][vcodec]
+ aencoders = options['audio-codecs'][acodec]
+
+ #print(encoders)
+ for encoder in vencoders:
+ #print(encoder)
+ if encoder in encoders:
+ vencoder = encoder
+ break
+
+ for encoder in aencoders:
+ #print(encoder)
+ if encoder in encoders:
+ aencoder = encoder
+ break
+
+ ffmpegCommand = [
+ self.core.FFMPEG_BIN,
+ '-thread_queue_size', '512',
+ '-y', # overwrite the output file if it already exists.
+ '-f', 'rawvideo',
+ '-vcodec', 'rawvideo',
+ '-s', str(self.width)+'x'+str(self.height), # size of one frame
+ '-pix_fmt', 'rgba',
+
+ # frames per second
+ '-r', self.core.settings.value('outputFrameRate'),
+ '-i', '-', # The input comes from a pipe
+ '-an',
+ '-i', inputFile,
+ '-vcodec', vencoder,
+ '-acodec', aencoder, # output audio codec
+ '-b:v', vbitrate,
+ '-b:a', abitrate,
+ '-pix_fmt', self.core.settings.value('outputVideoFormat'),
+ '-preset', self.core.settings.value('outputPreset'),
+ '-f', container
+ ]
+
+ if acodec == 'aac':
+ ffmpegCommand.append('-strict')
+ ffmpegCommand.append('-2')
+
+ ffmpegCommand.append(outputFile)
+
+ # ### Now start creating video for output ###
+ numpy.seterr(divide='ignore')
+
+ # Call preFrameRender on all components
+ print('Loaded Components:', ", ".join(
+ ["%s) %s" % (num, str(component)) \
+ for num, component in enumerate(reversed(self.components))
+ ]))
+ self.staticComponents = {}
+ numComps = len(self.components)
+ for compNo, comp in enumerate(self.components):
+ pStr = "Analyzing audio..."
+ self.progressBarSetText.emit(pStr)
+ properties = None
+ properties = comp.preFrameRender(
+ worker=self,
+ completeAudioArray=self.completeAudioArray,
+ sampleSize=self.sampleSize,
+ progressBarUpdate=self.progressBarUpdate,
+ progressBarSetText=self.progressBarSetText
+ )
+
+ if properties and 'static' in properties:
+ self.staticComponents[compNo] = copy(
+ comp.frameRender(compNo, 0, 0))
+ self.progressBarUpdate.emit(100)
+
+ # Create ffmpeg pipe and queues for frames
+ self.out_pipe = sp.Popen(
+ ffmpegCommand, stdin=sp.PIPE, stdout=sys.stdout, stderr=sys.stdout)
+ self.compositeQueue = Queue()
+ self.compositeQueue.maxsize = 20
+ self.renderQueue = PriorityQueue()
+ self.renderQueue.maxsize = 20
+ self.previewQueue = PriorityQueue()
+
+ # Threads to render frames and send them back here for piping out
+ self.renderThreads = []
+ for i in range(3):
+ self.renderThreads.append(
+ Thread(target=self.renderNode, name="Render Thread"))
+ self.renderThreads[i].daemon = True
+ self.renderThreads[i].start()
+
+ self.dispatchThread = Thread(
+ target=self.renderDispatch, name="Render Dispatch Thread")
+ self.dispatchThread.daemon = True
+ self.dispatchThread.start()
+
+ self.previewDispatch = Thread(
+ target=self.previewDispatch, name="Render Dispatch Thread")
+ self.previewDispatch.daemon = True
+ self.previewDispatch.start()
+
+ frameBuffer = {}
+ self.lastPreview = 0.0
+ self.progressBarUpdate.emit(0)
+ pStr = "Exporting video..."
+ self.progressBarSetText.emit(pStr)
+ if not self.canceled:
+ for i in range(0, len(self.completeAudioArray), self.sampleSize):
+ while True:
+ if i in frameBuffer or self.canceled:
+ # if frame's in buffer, pipe it to ffmpeg
+ break
+ # else fetch the next frame & add to the buffer
+ data = self.renderQueue.get()
+ frameBuffer[data[0]] = data[1]
+ self.renderQueue.task_done()
+ if self.canceled:
+ break
+
+ try:
+ self.out_pipe.stdin.write(frameBuffer[i].tobytes())
+ self.previewQueue.put([i, frameBuffer[i]])
+ del frameBuffer[i]
+ except:
+ break
+
+ # increase progress bar value
+ if progressBarValue + 1 <= (i / len(self.completeAudioArray)) \
+ * 100:
+ progressBarValue = numpy.floor(
+ (i / len(self.completeAudioArray)) * 100)
+ self.progressBarUpdate.emit(progressBarValue)
+ pStr = "Exporting video: " + str(int(progressBarValue)) \
+ + "%"
+ self.progressBarSetText.emit(pStr)
+
+ numpy.seterr(all='print')
+
+ self.out_pipe.stdin.close()
+ if self.out_pipe.stderr is not None:
+ print(self.out_pipe.stderr.read())
+ self.out_pipe.stderr.close()
+ self.error = True
+ # out_pipe.terminate() # don't terminate ffmpeg too early
+ self.out_pipe.wait()
+ if self.canceled:
+ print("Export Canceled")
+ try:
+ os.remove(self.outputFile)
+ except:
+ pass
+ self.progressBarUpdate.emit(0)
+ self.progressBarSetText.emit('Export Canceled')
+
+ else:
+ if self.error:
+ print("Export Failed")
+ self.progressBarUpdate.emit(0)
+ self.progressBarSetText.emit('Export Failed')
+ else:
+ print("Export Complete")
+ self.progressBarUpdate.emit(100)
+ self.progressBarSetText.emit('Export Complete')
+
+ self.error = False
+ self.canceled = False
+ self.stopped = True
+ self.encoding.emit(False)
+ self.videoCreated.emit()
+
+ def updateProgress(self, pStr, pVal):
+ self.progressBarValue.emit(pVal)
+ self.progressBarSetText.emit(pStr)
+
+ def cancel(self):
+ self.canceled = True
+ self.core.cancel()
+
+ for comp in self.components:
+ comp.cancel()
+
+ try:
+ self.out_pipe.send_signal(signal.SIGINT)
+ except:
+ pass
+
+ def reset(self):
+ self.core.reset()
+ self.canceled = False
+ for comp in self.components:
+ comp.reset()
diff --git a/video_thread.py b/video_thread.py
deleted file mode 100644
index 265feee..0000000
--- a/video_thread.py
+++ /dev/null
@@ -1,309 +0,0 @@
-from PyQt4 import QtCore, QtGui, uic
-from PyQt4.QtCore import pyqtSignal, pyqtSlot
-from PIL import Image, ImageDraw, ImageFont
-from PIL.ImageQt import ImageQt
-import core
-import numpy
-import subprocess as sp
-import sys
-import os
-from queue import Queue, PriorityQueue
-from threading import Thread, Event
-import time
-from copy import copy
-import signal
-
-
-class Worker(QtCore.QObject):
-
- imageCreated = pyqtSignal(['QImage'])
- videoCreated = pyqtSignal()
- progressBarUpdate = pyqtSignal(int)
- progressBarSetText = pyqtSignal(str)
- encoding = pyqtSignal(bool)
-
- def __init__(self, parent=None):
- QtCore.QObject.__init__(self)
- self.core = core.Core()
- self.core.settings = parent.settings
- self.modules = parent.core.modules
- self.parent = parent
- parent.videoTask.connect(self.createVideo)
- self.sampleSize = 1470 # 44100 / 30 = 1470
- self.canceled = False
- self.error = False
- self.stopped = False
-
- def renderNode(self):
- while not self.stopped:
- i = self.compositeQueue.get()
- frame = None
-
- for compNo, comp in reversed(list(enumerate(self.components))):
- if compNo in self.staticComponents and \
- self.staticComponents[compNo] is not None:
- if frame is None:
- frame = self.staticComponents[compNo]
- else:
- frame = Image.alpha_composite(
- frame, self.staticComponents[compNo])
- else:
- if frame is None:
- frame = comp.frameRender(compNo, i[0], i[1])
- else:
- frame = Image.alpha_composite(
- frame, comp.frameRender(compNo, i[0], i[1]))
-
- self.renderQueue.put([i[0], frame])
- self.compositeQueue.task_done()
-
- def renderDispatch(self):
- print('Dispatching Frames for Compositing...')
-
- for i in range(0, len(self.completeAudioArray), self.sampleSize):
- self.compositeQueue.put([i, self.bgI])
- # increment tracked video frame for next iteration
- self.bgI += 1
-
- def previewDispatch(self):
- background = Image.new("RGBA", (1920, 1080), (0, 0, 0, 0))
- background.paste(Image.open(os.path.join(
- self.core.wd, "background.png")))
- background = background.resize((self.width, self.height))
-
- while not self.stopped:
- i = self.previewQueue.get()
- if time.time() - self.lastPreview >= 0.06 or i[0] == 0:
- image = copy(background)
- image = Image.alpha_composite(image, i[1])
- self._image = ImageQt(image)
- self.imageCreated.emit(QtGui.QImage(self._image))
- self.lastPreview = time.time()
-
- self.previewQueue.task_done()
-
- @pyqtSlot(str, str, list)
- def createVideo(self, inputFile, outputFile, components):
- self.encoding.emit(True)
- self.components = components
- self.outputFile = outputFile
- self.bgI = 0 # tracked video frame
- self.reset()
- self.width = int(self.core.settings.value('outputWidth'))
- self.height = int(self.core.settings.value('outputHeight'))
- progressBarValue = 0
- self.progressBarUpdate.emit(progressBarValue)
-
- self.progressBarSetText.emit('Loading audio file...')
- self.completeAudioArray = self.core.readAudioFile(inputFile, self)
-
- # test if user has libfdk_aac
- encoders = sp.check_output(
- self.core.FFMPEG_BIN + " -encoders -hide_banner",
- shell=True)
-
- encoders = encoders.decode("utf-8")
-
- acodec = self.core.settings.value('outputAudioCodec')
-
- options = self.core.encoder_options
- containerName = self.core.settings.value('outputContainer')
- vcodec = self.core.settings.value('outputVideoCodec')
- vbitrate = str(self.core.settings.value('outputVideoBitrate'))+'k'
- acodec = self.core.settings.value('outputAudioCodec')
- abitrate = str(self.core.settings.value('outputAudioBitrate'))+'k'
-
- for cont in options['containers']:
- if cont['name'] == containerName:
- container = cont['container']
- break
-
- vencoders = options['video-codecs'][vcodec]
- aencoders = options['audio-codecs'][acodec]
-
- #print(encoders)
- for encoder in vencoders:
- #print(encoder)
- if encoder in encoders:
- vencoder = encoder
- break
-
- for encoder in aencoders:
- #print(encoder)
- if encoder in encoders:
- aencoder = encoder
- break
-
- ffmpegCommand = [
- self.core.FFMPEG_BIN,
- '-thread_queue_size', '512',
- '-y', # overwrite the output file if it already exists.
- '-f', 'rawvideo',
- '-vcodec', 'rawvideo',
- '-s', str(self.width)+'x'+str(self.height), # size of one frame
- '-pix_fmt', 'rgba',
-
- # frames per second
- '-r', self.core.settings.value('outputFrameRate'),
- '-i', '-', # The input comes from a pipe
- '-an',
- '-i', inputFile,
- '-vcodec', vencoder,
- '-acodec', aencoder, # output audio codec
- '-b:v', vbitrate,
- '-b:a', abitrate,
- '-pix_fmt', self.core.settings.value('outputVideoFormat'),
- '-preset', self.core.settings.value('outputPreset'),
- '-f', container
- ]
-
- if acodec == 'aac':
- ffmpegCommand.append('-strict')
- ffmpegCommand.append('-2')
-
- ffmpegCommand.append(outputFile)
-
- # ### Now start creating video for output ###
- numpy.seterr(divide='ignore')
-
- # Call preFrameRender on all components
- print('Loaded Components:', ", ".join(
- ["%s) %s" % (num, str(component)) \
- for num, component in enumerate(reversed(self.components))
- ]))
- self.staticComponents = {}
- numComps = len(self.components)
- for compNo, comp in enumerate(self.components):
- pStr = "Analyzing audio..."
- self.progressBarSetText.emit(pStr)
- properties = None
- properties = comp.preFrameRender(
- worker=self,
- completeAudioArray=self.completeAudioArray,
- sampleSize=self.sampleSize,
- progressBarUpdate=self.progressBarUpdate,
- progressBarSetText=self.progressBarSetText
- )
-
- if properties and 'static' in properties:
- self.staticComponents[compNo] = copy(
- comp.frameRender(compNo, 0, 0))
- self.progressBarUpdate.emit(100)
-
- # Create ffmpeg pipe and queues for frames
- self.out_pipe = sp.Popen(
- ffmpegCommand, stdin=sp.PIPE, stdout=sys.stdout, stderr=sys.stdout)
- self.compositeQueue = Queue()
- self.compositeQueue.maxsize = 20
- self.renderQueue = PriorityQueue()
- self.renderQueue.maxsize = 20
- self.previewQueue = PriorityQueue()
-
- # Threads to render frames and send them back here for piping out
- self.renderThreads = []
- for i in range(3):
- self.renderThreads.append(
- Thread(target=self.renderNode, name="Render Thread"))
- self.renderThreads[i].daemon = True
- self.renderThreads[i].start()
-
- self.dispatchThread = Thread(
- target=self.renderDispatch, name="Render Dispatch Thread")
- self.dispatchThread.daemon = True
- self.dispatchThread.start()
-
- self.previewDispatch = Thread(
- target=self.previewDispatch, name="Render Dispatch Thread")
- self.previewDispatch.daemon = True
- self.previewDispatch.start()
-
- frameBuffer = {}
- self.lastPreview = 0.0
- self.progressBarUpdate.emit(0)
- pStr = "Exporting video..."
- self.progressBarSetText.emit(pStr)
- if not self.canceled:
- for i in range(0, len(self.completeAudioArray), self.sampleSize):
- while True:
- if i in frameBuffer or self.canceled:
- # if frame's in buffer, pipe it to ffmpeg
- break
- # else fetch the next frame & add to the buffer
- data = self.renderQueue.get()
- frameBuffer[data[0]] = data[1]
- self.renderQueue.task_done()
- if self.canceled:
- break
-
- try:
- self.out_pipe.stdin.write(frameBuffer[i].tobytes())
- self.previewQueue.put([i, frameBuffer[i]])
- del frameBuffer[i]
- except:
- break
-
- # increase progress bar value
- if progressBarValue + 1 <= (i / len(self.completeAudioArray)) \
- * 100:
- progressBarValue = numpy.floor(
- (i / len(self.completeAudioArray)) * 100)
- self.progressBarUpdate.emit(progressBarValue)
- pStr = "Exporting video: " + str(int(progressBarValue)) \
- + "%"
- self.progressBarSetText.emit(pStr)
-
- numpy.seterr(all='print')
-
- self.out_pipe.stdin.close()
- if self.out_pipe.stderr is not None:
- print(self.out_pipe.stderr.read())
- self.out_pipe.stderr.close()
- self.error = True
- # out_pipe.terminate() # don't terminate ffmpeg too early
- self.out_pipe.wait()
- if self.canceled:
- print("Export Canceled")
- try:
- os.remove(self.outputFile)
- except:
- pass
- self.progressBarUpdate.emit(0)
- self.progressBarSetText.emit('Export Canceled')
-
- else:
- if self.error:
- print("Export Failed")
- self.progressBarUpdate.emit(0)
- self.progressBarSetText.emit('Export Failed')
- else:
- print("Export Complete")
- self.progressBarUpdate.emit(100)
- self.progressBarSetText.emit('Export Complete')
-
- self.error = False
- self.canceled = False
- self.stopped = True
- self.encoding.emit(False)
- self.videoCreated.emit()
-
- def updateProgress(self, pStr, pVal):
- self.progressBarValue.emit(pVal)
- self.progressBarSetText.emit(pStr)
-
- def cancel(self):
- self.canceled = True
- self.core.cancel()
-
- for comp in self.components:
- comp.cancel()
-
- try:
- self.out_pipe.send_signal(signal.SIGINT)
- except:
- pass
-
- def reset(self):
- self.core.reset()
- self.canceled = False
- for comp in self.components:
- comp.reset()
--
cgit v1.2.3
From 680214f5180a12f2250d8e266df9375ce99b9f80 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Fri, 23 Jun 2017 23:00:24 -0400
Subject: qt5 fixes
also pep8 compliance
---
src/command.py | 15 +++----
src/components/__base__.py | 14 +++----
src/components/color.py | 15 ++++---
src/components/image.py | 2 +-
src/components/video.py | 14 ++++---
src/core.py | 28 +++++++------
src/main.py | 6 +--
src/mainwindow.py | 100 ++++++++++++++++++++++++++-------------------
src/presetmanager.py | 55 +++++++++++++------------
src/video_thread.py | 11 ++---
10 files changed, 143 insertions(+), 117 deletions(-)
diff --git a/src/command.py b/src/command.py
index 1a1e810..2f71f31 100644
--- a/src/command.py
+++ b/src/command.py
@@ -22,9 +22,9 @@ class Command(QtCore.QObject):
self.parser = argparse.ArgumentParser(
description='Create a visualization for an audio file',
epilog='EXAMPLE COMMAND: main.py myvideotemplate.avp '
- '-i ~/Music/song.mp3 -o ~/video.mp4 '
- '-c 0 image path=~/Pictures/thisWeeksPicture.jpg '
- '-c 1 video "preset=My Logo" -c 2 vis layout=classic')
+ '-i ~/Music/song.mp3 -o ~/video.mp4 '
+ '-c 0 image path=~/Pictures/thisWeeksPicture.jpg '
+ '-c 1 video "preset=My Logo" -c 2 vis layout=classic')
self.parser.add_argument(
'-i', '--input', metavar='SOUND',
help='input audio file')
@@ -113,10 +113,11 @@ class Command(QtCore.QObject):
if name.capitalize() in compName:
return compName
- compFileNames = [ \
- os.path.splitext(os.path.basename(
- mod.__file__))[0] \
- for mod in self.core.modules \
+ compFileNames = [
+ os.path.splitext(
+ os.path.basename(mod.__file__)
+ )[0]
+ for mod in self.core.modules
]
for i, compFileName in enumerate(compFileNames):
if name.lower() in compFileName:
diff --git a/src/components/__base__.py b/src/components/__base__.py
index a4677b1..a24af40 100644
--- a/src/components/__base__.py
+++ b/src/components/__base__.py
@@ -39,7 +39,7 @@ class Component(QtCore.QObject):
then update self.page widgets using the preset dict.
'''
self.currentPreset = presetName \
- if presetName != None else presetDict['preset']
+ if presetName is not None else presetDict['preset']
def preFrameRender(self, **kwargs):
'''Triggered only before a video is exported (video_thread.py)
@@ -66,8 +66,8 @@ class Component(QtCore.QObject):
print('Couldn\'t locate preset "%s"' % preset)
quit(1)
else:
- print('Opening "%s" preset on layer %s' % \
- (preset, self.compPos))
+ print('Opening "%s" preset on layer %s' % (
+ preset, self.compPos))
self.core.openPreset(path, self.compPos, preset)
else:
print(
@@ -88,8 +88,8 @@ class Component(QtCore.QObject):
and return this as an RGB string and QPushButton stylesheet.
In a subclass apply stylesheet to any color selection widgets
'''
- dialog = QtGui.QColorDialog()
- dialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True)
+ dialog = QtWidgets.QColorDialog()
+ dialog.setOption(QtWidgets.QColorDialog.ShowAlphaChannel, True)
color = dialog.getColor()
if color.isValid():
RGBstring = '%s,%s,%s' % (
@@ -142,10 +142,10 @@ class Component(QtCore.QObject):
return image
'''
+
class BadComponentInit(Exception):
def __init__(self, arg, name):
- string = \
-'''################################
+ string = '''################################
Mandatory argument "%s" not specified
in %s instance initialization
###################################'''
diff --git a/src/components/color.py b/src/components/color.py
index 8f9a1d1..2e3902a 100644
--- a/src/components/color.py
+++ b/src/components/color.py
@@ -53,7 +53,7 @@ class Component(__base__.Component):
page.spinBox_height.valueChanged.connect(self.update)
page.checkBox_trans.stateChanged.connect(self.update)
- self.fillLabels = [ \
+ self.fillLabels = [
'Solid',
'Linear Gradient',
'Radial Gradient',
@@ -126,8 +126,8 @@ class Component(__base__.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 Image.new("RGBA", (width, height), (r, g, b, 255))
frame = self.blankFrame(width, height)
@@ -143,9 +143,11 @@ class Component(__base__.Component):
image = ImageQt(frame)
painter = QtGui.QPainter(image)
if self.stretch:
- w = width; h = height
+ w = width
+ h = height
else:
- w = self.sizeWidth; h = self.sizeWidth
+ w = self.sizeWidth
+ h = self.sizeWidth
if self.fillType == 1: # Linear Gradient
brush = QtGui.QLinearGradient(
@@ -170,7 +172,8 @@ class Component(__base__.Component):
else:
brush.setColorAt(1.0, QColor(*self.color2))
painter.setBrush(brush)
- painter.drawRect(self.x, self.y,
+ painter.drawRect(
+ self.x, self.y,
self.sizeWidth, self.sizeHeight)
painter.end()
imBytes = image.bits().asstring(image.numBytes())
diff --git a/src/components/image.py b/src/components/image.py
index 8ca88d3..3517af6 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -85,7 +85,7 @@ class Component(__base__.Component):
def pickImage(self):
imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
- filename = QtGui.QFileDialog.getOpenFileName(
+ filename, _ = QtWidgets.QFileDialog.getOpenFileName(
self.page, "Choose Image", imgDir,
"Image Files (%s)" % " ".join(self.imageFormats))
if filename:
diff --git a/src/components/video.py b/src/components/video.py
index 58ce7a3..0090426 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -41,8 +41,8 @@ class Video:
'-i', self.videoPath,
'-f', 'image2pipe',
'-pix_fmt', 'rgba',
- '-filter:v', 'scale=%s:%s' %
- scale(self.scale, self.width, self.height, str),
+ '-filter:v', 'scale=%s:%s' % scale(
+ self.scale, self.width, self.height, str),
'-vcodec', 'rawvideo', '-',
]
@@ -180,7 +180,7 @@ class Component(__base__.Component):
def pickVideo(self):
imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
- filename = QtGui.QFileDialog.getOpenFileName(
+ filename, _ = QtWidgets.QFileDialog.getOpenFileName(
self.page, "Choose Video",
imgDir, "Video Files (%s)" % " ".join(self.videoFormats)
)
@@ -199,8 +199,8 @@ class Component(__base__.Component):
'-i', self.videoPath,
'-f', 'image2pipe',
'-pix_fmt', 'rgba',
- '-filter:v', 'scale=%s:%s' %
- scale(self.scale, width, height, str),
+ '-filter:v', 'scale=%s:%s' % scale(
+ self.scale, width, height, str),
'-vcodec', 'rawvideo', '-',
'-ss', '90',
'-vframes', '1',
@@ -238,6 +238,7 @@ class Component(__base__.Component):
def commandHelp(self):
print('Load a video:\n path=/filepath/to/video.mp4')
+
def scale(scale, width, height, returntype=None):
width = (float(width) / 100.0) * float(scale)
height = (float(height) / 100.0) * float(scale)
@@ -248,6 +249,7 @@ def scale(scale, width, height, returntype=None):
else:
return (width, height)
+
def finalizeFrame(self, imageData, width, height):
if self.distort:
try:
@@ -265,7 +267,7 @@ def finalizeFrame(self, imageData, width, height):
imageData)
if self.scale != 100 \
- or self.xPosition != 0 or self.yPosition != 0:
+ or self.xPosition != 0 or self.yPosition != 0:
frame = self.blankFrame(width, height)
frame.paste(image, box=(self.xPosition, self.yPosition))
else:
diff --git a/src/core.py b/src/core.py
index bb5d351..670a3c5 100644
--- a/src/core.py
+++ b/src/core.py
@@ -179,7 +179,7 @@ class Core():
clearThis = False
# add loaded named presets to savedPresets dict
- if 'preset' in preset and preset['preset'] != None:
+ if 'preset' in preset and preset['preset'] is not None:
nam = preset['preset']
filepath2 = os.path.join(
self.presetDir, name, str(vers), nam)
@@ -195,12 +195,12 @@ class Core():
-1,
self.moduleIndexFor(name),
loader)
- if i == None:
+ if i is None:
loader.showMessage(msg="Too many components!")
break
try:
- if 'preset' in preset and preset['preset'] != None:
+ if 'preset' in preset and preset['preset'] is not None:
self.selectedComponents[i].loadPreset(
preset
)
@@ -210,8 +210,8 @@ class Core():
preset['preset']
)
except KeyError as e:
- print('%s missing value %s' %
- (self.selectedComponents[i], e))
+ print('%s missing value %s' % (
+ self.selectedComponents[i], e))
if clearThis:
self.clearPreset(i)
@@ -221,7 +221,6 @@ class Core():
errcode = 1
data = sys.exc_info()
-
if errcode == 1:
typ, value, _ = data
if typ.__name__ == KeyError:
@@ -274,11 +273,11 @@ class Core():
i += 1
elif i == 2:
lastCompPreset = Core.presetFromString(line)
- data[section].append(
- (lastCompName,
+ data[section].append((
+ lastCompName,
lastCompVers,
- lastCompPreset)
- )
+ lastCompPreset
+ ))
i = 0
return 0, data
except:
@@ -309,7 +308,9 @@ class Core():
return False, ''
def exportPreset(self, exportPath, compName, vers, origName):
- internalPath = os.path.join(self.presetDir, compName, str(vers), origName)
+ internalPath = os.path.join(
+ self.presetDir, compName, str(vers), origName
+ )
if not os.path.exists(internalPath):
return
if os.path.exists(exportPath):
@@ -328,7 +329,7 @@ class Core():
return False
def createPresetFile(
- self, compName, vers, presetName, saveValueStore, filepath=''):
+ self, compName, vers, presetName, saveValueStore, filepath=''):
'''Create a preset file (.avl) at filepath using args.
Or if filepath is empty, create an internal preset using args'''
if not filepath:
@@ -463,7 +464,8 @@ class Core():
@staticmethod
def presetToString(dictionary):
'''Alphabetizes a dict into OrderedDict & returns string repr'''
- return repr(OrderedDict(sorted(dictionary.items(), key=lambda t: t[0])))
+ return repr(
+ OrderedDict(sorted(dictionary.items(), key=lambda t: t[0])))
@staticmethod
def presetFromString(string):
diff --git a/src/main.py b/src/main.py
index 4bf26db..58fdb46 100644
--- a/src/main.py
+++ b/src/main.py
@@ -30,7 +30,6 @@ def LoadDefaultSettings(self):
}
for parm, value in default.items():
- #print(parm, self.settings.value(parm))
if self.settings.value(parm) is None:
self.settings.setValue(parm, value)
@@ -51,7 +50,7 @@ if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
app.setApplicationName("audio-visualizer")
- app.setOrganizationName("audio-visualizer")
+ # app.setOrganizationName("audio-visualizer")
if mode == 'cmd':
from command import *
@@ -76,7 +75,8 @@ if __name__ == "__main__":
dpi = desc.physicalDpiX()
topMargin = 0 if (dpi == 96) else int(10 * (dpi / 96))
- window.resize(window.width() * (dpi / 96), window.height() * (dpi / 96))
+ window.resize(
+ window.width() * (dpi / 96), window.height() * (dpi / 96))
# window.verticalLayout_2.setContentsMargins(0, topMargin, 0, 0)
main = MainWindow(window, proj)
diff --git a/src/mainwindow.py b/src/mainwindow.py
index a52a0f4..7a9e397 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -116,7 +116,6 @@ class MainWindow(QtWidgets.QMainWindow):
codec = window.comboBox_videoCodec.itemText(i)
if codec == self.settings.value('outputVideoCodec'):
window.comboBox_videoCodec.setCurrentIndex(i)
- #print(codec)
for i in range(window.comboBox_audioCodec.count()):
codec = window.comboBox_audioCodec.itemText(i)
@@ -146,10 +145,11 @@ class MainWindow(QtWidgets.QMainWindow):
# Make component buttons
self.compMenu = QMenu()
+ self.compActions = []
for i, comp in enumerate(self.core.modules):
action = self.compMenu.addAction(comp.Component.__doc__)
action.triggered.connect(
- lambda item=i: self.core.insertComponent(0, item, self))
+ lambda _, item=i: self.core.insertComponent(0, item, self))
self.window.pushButton_addComponent.setMenu(self.compMenu)
@@ -160,9 +160,10 @@ class MainWindow(QtWidgets.QMainWindow):
self.window.pushButton_removeComponent.clicked.connect(
lambda _: self.removeComponent())
- componentList.setContextMenuPolicy(
- QtCore.Qt.CustomContextMenu)
- componentList.customContextMenuRequested.connect(self.componentContextMenu)
+ componentList.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+ componentList.customContextMenuRequested.connect(
+ self.componentContextMenu
+ )
currentRes = str(self.settings.value('outputWidth'))+'x' + \
str(self.settings.value('outputHeight'))
@@ -245,19 +246,30 @@ class MainWindow(QtWidgets.QMainWindow):
QtWidgets.QShortcut("Ctrl+O", self.window, self.openOpenProjectDialog)
QtWidgets.QShortcut("Ctrl+N", self.window, self.createNewProject)
- QtWidgets.QShortcut("Ctrl+T", self.window, activated=lambda:
- self.window.pushButton_addComponent.click())
- QtWidgets.QShortcut("Ctrl+Space", self.window, activated=lambda:
- self.window.listWidget_componentList.setFocus())
- QtWidgets.QShortcut("Ctrl+Shift+S", self.window,
- self.presetManager.openSavePresetDialog)
- QtWidgets.QShortcut("Ctrl+Shift+C", self.window,
- self.presetManager.clearPreset)
-
- QtWidgets.QShortcut("Ctrl+Up", self.window,
- activated=lambda: self.moveComponent(-1))
- QtWidgets.QShortcut("Ctrl+Down", self.window,
- activated=lambda: self.moveComponent(1))
+ QtWidgets.QShortcut(
+ "Ctrl+T", self.window,
+ activated=lambda: self.window.pushButton_addComponent.click()
+ )
+ QtWidgets.QShortcut(
+ "Ctrl+Space", self.window,
+ activated=lambda: self.window.listWidget_componentList.setFocus()
+ )
+ QtWidgets.QShortcut(
+ "Ctrl+Shift+S", self.window,
+ self.presetManager.openSavePresetDialog
+ )
+ QtWidgets.QShortcut(
+ "Ctrl+Shift+C", self.window, self.presetManager.clearPreset
+ )
+
+ QtWidgets.QShortcut(
+ "Ctrl+Up", self.window,
+ activated=lambda: self.moveComponent(-1)
+ )
+ QtWidgets.QShortcut(
+ "Ctrl+Down", self.window,
+ activated=lambda: self.moveComponent(1)
+ )
QtWidgets.QShortcut("Ctrl+Home", self.window, self.moveComponentTop)
QtWidgets.QShortcut("Ctrl+End", self.window, self.moveComponentBottom)
QtWidgets.QShortcut("Ctrl+r", self.window, self.removeComponent)
@@ -280,7 +292,7 @@ class MainWindow(QtWidgets.QMainWindow):
def updateComponentTitle(self, pos, presetStore=False):
if type(presetStore) == dict:
name = presetStore['preset']
- if name == None or name not in self.core.savedPresets:
+ if name is None or name not in self.core.savedPresets:
modified = False
else:
modified = (presetStore != self.core.savedPresets[name])
@@ -362,21 +374,22 @@ class MainWindow(QtWidgets.QMainWindow):
def openInputFileDialog(self):
inputDir = self.settings.value("inputDir", os.path.expanduser("~"))
- fileName = QtGui.QFileDialog.getOpenFileName(
+ fileName, _ = QtWidgets.QFileDialog.getOpenFileName(
self.window, "Open Audio File",
inputDir, "Audio Files (%s)" % " ".join(self.core.audioFormats))
- if not fileName == "":
+ if fileName:
self.settings.setValue("inputDir", os.path.dirname(fileName))
self.window.lineEdit_audioFile.setText(fileName)
def openOutputFileDialog(self):
outputDir = self.settings.value("outputDir", os.path.expanduser("~"))
- fileName = QtGui.QFileDialog.getSaveFileName(
+ fileName, _ = QtWidgets.QFileDialog.getSaveFileName(
self.window, "Set Output Video File",
outputDir,
- "Video Files (%s);; All Files (*)" % " ".join(self.core.videoFormats))
+ "Video Files (%s);; All Files (*)" % " ".join(
+ self.core.videoFormats))
if not fileName == "":
self.settings.setValue("outputDir", os.path.dirname(fileName))
@@ -547,13 +560,13 @@ class MainWindow(QtWidgets.QMainWindow):
'''Drop event for the component listwidget'''
componentList = self.window.listWidget_componentList
- modelIndexes = [ \
- componentList.model().index(i) \
- for i in range(componentList.count()) \
+ modelIndexes = [
+ componentList.model().index(i)
+ for i in range(componentList.count())
]
- rects = [ \
- componentList.visualRect(modelIndex) \
- for modelIndex in modelIndexes \
+ rects = [
+ componentList.visualRect(modelIndex)
+ for modelIndex in modelIndexes
]
rowPos = [rect.contains(event.pos()) for rect in rects]
@@ -602,9 +615,10 @@ class MainWindow(QtWidgets.QMainWindow):
if self.autosaveExists(identical=False):
ch = self.showMessage(
msg="You have unsaved changes in project '%s'. "
- "Save before %s?" % \
- (os.path.basename(self.currentProject)[:-4],
- phrase),
+ "Save before %s?" % (
+ os.path.basename(self.currentProject)[:-4],
+ phrase
+ ),
showCancel=True)
if ch:
success = self.saveProjectChanges()
@@ -613,7 +627,7 @@ class MainWindow(QtWidgets.QMainWindow):
os.remove(self.autosavePath)
def openSaveProjectDialog(self):
- filename = QtGui.QFileDialog.getSaveFileName(
+ filename, _ = QtWidgets.QFileDialog.getSaveFileName(
self.window, "Create Project File",
self.settings.value("projectDir"),
"Project Files (*.avp)")
@@ -628,7 +642,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.core.createProjectFile(filename)
def openOpenProjectDialog(self):
- filename = QtGui.QFileDialog.getOpenFileName(
+ filename, _ = QtWidgets.QFileDialog.getOpenFileName(
self.window, "Open Project File",
self.settings.value("projectDir"),
"Project Files (*.avp)")
@@ -657,17 +671,19 @@ class MainWindow(QtWidgets.QMainWindow):
def showMessage(self, **kwargs):
parent = kwargs['parent'] if 'parent' in kwargs else self.window
- msg = QtGui.QMessageBox(parent)
+ msg = QtWidgets.QMessageBox(parent)
msg.setModal(True)
msg.setText(kwargs['msg'])
msg.setIcon(
- kwargs['icon'] if 'icon' in kwargs else QtGui.QMessageBox.Information)
+ kwargs['icon']
+ if 'icon' in kwargs else QtWidgets.QMessageBox.Information
+ )
msg.setDetailedText(kwargs['detail'] if 'detail' in kwargs else None)
if 'showCancel'in kwargs and kwargs['showCancel']:
msg.setStandardButtons(
- QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel)
+ QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
else:
- msg.setStandardButtons(QtGui.QMessageBox.Ok)
+ msg.setStandardButtons(QtWidgets.QMessageBox.Ok)
ch = msg.exec_()
if ch == 1024:
return True
@@ -687,7 +703,7 @@ class MainWindow(QtWidgets.QMainWindow):
return
self.presetManager.findPresets()
- self.menu = QtGui.QMenu()
+ self.menu = QMenu()
menuItem = self.menu.addAction("Save Preset")
menuItem.triggered.connect(
self.presetManager.openSavePresetDialog
@@ -695,8 +711,10 @@ class MainWindow(QtWidgets.QMainWindow):
# submenu for opening presets
try:
- presets = self.presetManager.presets[str(self.core.selectedComponents[index])]
- self.submenu = QtGui.QMenu("Open Preset")
+ presets = self.presetManager.presets[
+ str(self.core.selectedComponents[index])
+ ]
+ self.submenu = QMenu("Open Preset")
self.menu.addMenu(self.submenu)
for version, presetName in presets:
diff --git a/src/presetmanager.py b/src/presetmanager.py
index ec3f5cd..97f6e0e 100644
--- a/src/presetmanager.py
+++ b/src/presetmanager.py
@@ -1,4 +1,4 @@
-from PyQt5 import QtGui, QtCore, QtWidgets
+from PyQt5 import QtCore, QtWidgets
import string
import os
@@ -21,13 +21,15 @@ class PresetManager(QtWidgets.QDialog):
# window
self.lastFilter = '*'
- self.presetRows = [] # list of (comp, vers, name) tuples
+ self.presetRows = [] # list of (comp, vers, name) tuples
self.window = window
self.window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
# connect button signals
- self.window.pushButton_delete.clicked.connect(self.openDeletePresetDialog)
- self.window.pushButton_rename.clicked.connect(self.openRenamePresetDialog)
+ self.window.pushButton_delete.clicked.connect(
+ self.openDeletePresetDialog)
+ self.window.pushButton_rename.clicked.connect(
+ self.openRenamePresetDialog)
self.window.pushButton_import.clicked.connect(self.openImportDialog)
self.window.pushButton_export.clicked.connect(self.openExportDialog)
self.window.pushButton_close.clicked.connect(self.window.close)
@@ -36,7 +38,8 @@ class PresetManager(QtWidgets.QDialog):
self.drawFilterList()
self.window.comboBox_filter.currentIndexChanged.connect(
lambda: self.drawPresetList(
- self.window.comboBox_filter.currentText(), self.window.lineEdit_search.text()
+ self.window.comboBox_filter.currentText(),
+ self.window.lineEdit_search.text()
)
)
@@ -47,7 +50,8 @@ class PresetManager(QtWidgets.QDialog):
self.window.lineEdit_search.setCompleter(completer)
self.window.lineEdit_search.textChanged.connect(
lambda: self.drawPresetList(
- self.window.comboBox_filter.currentText(), self.window.lineEdit_search.text()
+ self.window.comboBox_filter.currentText(),
+ self.window.lineEdit_search.text()
)
)
self.drawPresetList('*')
@@ -72,16 +76,14 @@ class PresetManager(QtWidgets.QDialog):
parseList.append((compName, int(compVers), preset))
except ValueError:
continue
- self.presets =\
- {
- compName : \
- [
- (vers, preset) \
- for name, vers, preset in parseList \
- if name == compName \
- ] \
- for compName, _, __ in parseList \
- }
+ self.presets = {
+ compName: [
+ (vers, preset)
+ for name, vers, preset in parseList
+ if name == compName
+ ]
+ for compName, _, __ in parseList
+ }
def drawPresetList(self, compFilter=None, presetFilter=''):
self.window.listWidget_presets.clear()
@@ -96,7 +98,8 @@ class PresetManager(QtWidgets.QDialog):
continue
for vers, preset in presets:
if not presetFilter or presetFilter in preset:
- self.window.listWidget_presets.addItem('%s: %s' % (component, preset))
+ self.window.listWidget_presets.addItem(
+ '%s: %s' % (component, preset))
self.presetRows.append((component, vers, preset))
if preset not in presetNames:
presetNames.append(preset)
@@ -124,11 +127,11 @@ class PresetManager(QtWidgets.QDialog):
while True:
index = componentList.currentRow()
currentPreset = selectedComponents[index].currentPreset
- newName, OK = QtGui.QInputDialog.getText(
+ newName, OK = QtWidgets.QInputDialog.getText(
self.parent.window,
'Audio Visualizer',
'New Preset Name:',
- QtGui.QLineEdit.Normal,
+ QtWidgets.QLineEdit.Normal,
currentPreset
)
if OK:
@@ -149,7 +152,7 @@ class PresetManager(QtWidgets.QDialog):
break
def createNewPreset(
- self, compName, vers, filename, saveValueStore, **kwargs):
+ self, compName, vers, filename, saveValueStore, **kwargs):
path = os.path.join(self.presetDir, compName, str(vers), filename)
if self.presetExists(path, **kwargs):
return
@@ -163,7 +166,7 @@ class PresetManager(QtWidgets.QDialog):
msg="%s already exists! Overwrite it?" %
os.path.basename(path),
showCancel=True,
- icon=QtGui.QMessageBox.Warning,
+ icon=QtWidgets.QMessageBox.Warning,
parent=window)
if not ch:
# user clicked cancel
@@ -196,7 +199,7 @@ class PresetManager(QtWidgets.QDialog):
ch = self.parent.showMessage(
msg='Really delete %s?' % name,
showCancel=True,
- icon=QtGui.QMessageBox.Warning,
+ icon=QtWidgets.QMessageBox.Warning,
parent=self.window
)
if not ch:
@@ -223,11 +226,11 @@ class PresetManager(QtWidgets.QDialog):
while True:
index = presetList.currentRow()
- newName, OK = QtGui.QInputDialog.getText(
+ newName, OK = QtWidgets.QInputDialog.getText(
self.window,
'Preset Manager',
'Rename Preset:',
- QtGui.QLineEdit.Normal,
+ QtWidgets.QLineEdit.Normal,
self.presetRows[index][2]
)
if OK:
@@ -250,7 +253,7 @@ class PresetManager(QtWidgets.QDialog):
break
def openImportDialog(self):
- filename = QtGui.QFileDialog.getOpenFileName(
+ filename, _ = QtWidgets.QFileDialog.getOpenFileName(
self.window, "Import Preset File",
self.settings.value("presetDir"),
"Preset Files (*.avl)")
@@ -275,7 +278,7 @@ class PresetManager(QtWidgets.QDialog):
def openExportDialog(self):
if not self.window.listWidget_presets.selectedItems():
return
- filename = QtGui.QFileDialog.getSaveFileName(
+ filename, _ = QtWidgets.QFileDialog.getSaveFileName(
self.window, "Export Preset",
self.settings.value("presetDir"),
"Preset Files (*.avl)")
diff --git a/src/video_thread.py b/src/video_thread.py
index 5ea6d21..b45381c 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -121,15 +121,12 @@ class Worker(QtCore.QObject):
vencoders = options['video-codecs'][vcodec]
aencoders = options['audio-codecs'][acodec]
- #print(encoders)
for encoder in vencoders:
- #print(encoder)
if encoder in encoders:
vencoder = encoder
break
for encoder in aencoders:
- #print(encoder)
if encoder in encoders:
aencoder = encoder
break
@@ -167,10 +164,10 @@ class Worker(QtCore.QObject):
numpy.seterr(divide='ignore')
# Call preFrameRender on all components
- print('Loaded Components:', ", ".join(
- ["%s) %s" % (num, str(component)) \
- for num, component in enumerate(reversed(self.components))
- ]))
+ print('Loaded Components:', ", ".join([
+ "%s) %s" % (num, str(component))
+ for num, component in enumerate(reversed(self.components))
+ ]))
self.staticComponents = {}
numComps = len(self.components)
for compNo, comp in enumerate(self.components):
--
cgit v1.2.3
From 83d55593d005cd540b042b27e6141a3d506d4215 Mon Sep 17 00:00:00 2001
From: DH4
Date: Fri, 23 Jun 2017 23:39:22 -0500
Subject: Fixed QtWidgets not imported on some components.
---
src/components/color.py | 2 +-
src/components/original.py | 2 +-
src/components/text.py | 4 ++--
src/components/video.py | 2 +-
4 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/components/color.py b/src/components/color.py
index 2e3902a..44ed82e 100644
--- a/src/components/color.py
+++ b/src/components/color.py
@@ -1,5 +1,5 @@
from PIL import Image, ImageDraw
-from PyQt5 import uic, QtGui, QtCore
+from PyQt5 import uic, QtGui, QtCore, QtWidgets
from PyQt5.QtGui import QColor
from PIL.ImageQt import ImageQt
import os
diff --git a/src/components/original.py b/src/components/original.py
index 61f463d..0d5001c 100644
--- a/src/components/original.py
+++ b/src/components/original.py
@@ -1,6 +1,6 @@
import numpy
from PIL import Image, ImageDraw
-from PyQt5 import uic, QtGui, QtCore
+from PyQt5 import uic, QtGui, QtCore, QtWidgets
from PyQt5.QtGui import QColor
import os
from . import __base__
diff --git a/src/components/text.py b/src/components/text.py
index 0f599ed..76961c9 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -1,6 +1,6 @@
from PIL import Image, ImageDraw
from PyQt5.QtGui import QPainter, QColor, QFont
-from PyQt5 import uic, QtGui, QtCore
+from PyQt5 import uic, QtGui, QtCore, QtWidgets
from PIL.ImageQt import ImageQt
import os
import io
@@ -138,7 +138,7 @@ class Component(__base__.Component):
painter.drawText(x, y, self.title)
painter.end()
- imBytes = image.bits().asstring(image.numBytes())
+ imBytes = image.bits().asstring(image.byteCount())
return Image.frombytes('RGBA', (width, height), imBytes)
diff --git a/src/components/video.py b/src/components/video.py
index 0090426..70247e1 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -1,5 +1,5 @@
from PIL import Image, ImageDraw
-from PyQt5 import uic, QtGui, QtCore
+from PyQt5 import uic, QtGui, QtCore, QtWidgets
import os
import subprocess
import threading
--
cgit v1.2.3
From e32ba958cb95146728d4985221b08c7e01b35470 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 24 Jun 2017 23:12:41 -0400
Subject: fixing bugs
---
src/components/__base__.py | 5 ++++-
src/components/color.py | 7 +++----
src/components/image.py | 5 ++---
src/components/original.py | 5 ++---
src/components/text.py | 5 ++---
src/components/video.py | 27 +++++++++++++++++----------
src/core.py | 21 +++++++++++----------
src/presetmanager.py | 3 ++-
src/preview_thread.py | 15 +++++++++++++--
9 files changed, 56 insertions(+), 37 deletions(-)
diff --git a/src/components/__base__.py b/src/components/__base__.py
index 9b7b958..84d41c8 100644
--- a/src/components/__base__.py
+++ b/src/components/__base__.py
@@ -1,4 +1,4 @@
-from PyQt5 import QtGui, QtCore, QtWidgets
+from PyQt5 import uic, QtGui, QtCore, QtWidgets
from PIL import Image
import os
@@ -114,6 +114,9 @@ class Component(QtCore.QObject):
except:
return (255, 255, 255)
+ def loadUi(self, filename):
+ return uic.loadUi(os.path.join(self.core.componentsPath, filename))
+
'''
### Reference methods for creating a new component
### (Inherit from this class and define these)
diff --git a/src/components/color.py b/src/components/color.py
index f1fb2b2..253ac83 100644
--- a/src/components/color.py
+++ b/src/components/color.py
@@ -1,5 +1,5 @@
from PIL import Image, ImageDraw
-from PyQt5 import uic, QtGui, QtCore, QtWidgets
+from PyQt5 import QtGui, QtCore, QtWidgets
from PyQt5.QtGui import QColor
from PIL.ImageQt import ImageQt
import os
@@ -13,8 +13,7 @@ class Component(__base__.Component):
def widget(self, parent):
self.parent = parent
- page = uic.loadUi(os.path.join(
- os.path.dirname(os.path.realpath(__file__)), 'color.ui'))
+ page = self.loadUi('color.ui')
self.color1 = (0, 0, 0)
self.color2 = (133, 133, 133)
@@ -177,7 +176,7 @@ class Component(__base__.Component):
self.sizeWidth, self.sizeHeight
)
painter.end()
- imBytes = image.bits().asstring(image.numBytes())
+ imBytes = image.bits().asstring(image.byteCount())
return Image.frombytes('RGBA', (width, height), imBytes)
def loadPreset(self, pr, presetName=None):
diff --git a/src/components/image.py b/src/components/image.py
index 3517af6..143ae59 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -1,5 +1,5 @@
from PIL import Image, ImageDraw
-from PyQt5 import uic, QtGui, QtCore, QtWidgets
+from PyQt5 import QtGui, QtCore, QtWidgets
import os
from . import __base__
@@ -12,8 +12,7 @@ class Component(__base__.Component):
def widget(self, parent):
self.parent = parent
self.settings = parent.settings
- page = uic.loadUi(os.path.join(
- os.path.dirname(os.path.realpath(__file__)), 'image.ui'))
+ page = self.loadUi('image.ui')
self.imagePath = ''
self.x = 0
self.y = 0
diff --git a/src/components/original.py b/src/components/original.py
index 0d5001c..0185e0d 100644
--- a/src/components/original.py
+++ b/src/components/original.py
@@ -1,6 +1,6 @@
import numpy
from PIL import Image, ImageDraw
-from PyQt5 import uic, QtGui, QtCore, QtWidgets
+from PyQt5 import QtGui, QtCore, QtWidgets
from PyQt5.QtGui import QColor
import os
from . import __base__
@@ -17,8 +17,7 @@ class Component(__base__.Component):
self.parent = parent
self.visColor = (255, 255, 255)
- page = uic.loadUi(os.path.join(
- os.path.dirname(os.path.realpath(__file__)), 'original.ui'))
+ page = self.loadUi('original.ui')
page.comboBox_visLayout.addItem("Classic")
page.comboBox_visLayout.addItem("Split")
page.comboBox_visLayout.addItem("Bottom")
diff --git a/src/components/text.py b/src/components/text.py
index 76961c9..7f4659f 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -1,6 +1,6 @@
from PIL import Image, ImageDraw
from PyQt5.QtGui import QPainter, QColor, QFont
-from PyQt5 import uic, QtGui, QtCore, QtWidgets
+from PyQt5 import QtGui, QtCore, QtWidgets
from PIL.ImageQt import ImageQt
import os
import io
@@ -29,8 +29,7 @@ class Component(__base__.Component):
self.xPosition = width / 2 - fm.width(self.title)/2
self.yPosition = height / 2 * 1.036
- page = uic.loadUi(os.path.join(
- os.path.dirname(os.path.realpath(__file__)), 'text.ui'))
+ page = self.loadUi('text.ui')
page.comboBox_textAlign.addItem("Left")
page.comboBox_textAlign.addItem("Middle")
page.comboBox_textAlign.addItem("Right")
diff --git a/src/components/video.py b/src/components/video.py
index 70247e1..3e87d2e 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -1,6 +1,7 @@
from PIL import Image, ImageDraw
-from PyQt5 import uic, QtGui, QtCore, QtWidgets
+from PyQt5 import QtGui, QtCore, QtWidgets
import os
+import math
import subprocess
import threading
from queue import PriorityQueue
@@ -79,9 +80,18 @@ class Video:
self.frameNo += 1
# If we run out of frames, use the last good frame and loop.
- if len(self.currentFrame) == 0:
- self.frameBuffer.put((self.frameNo-1, self.lastFrame))
- continue
+ try:
+ if len(self.currentFrame) == 0:
+ self.frameBuffer.put((self.frameNo-1, self.lastFrame))
+ continue
+ except AttributeError as e:
+ self.parent.showMessage(
+ msg='%s couldn\'t be loaded.' % os.path.basename(
+ self.videoPath
+ ),
+ detail=str(e)
+ )
+ self.parent.stopVideo()
self.currentFrame = pipe.stdout.read(self.chunkSize)
if len(self.currentFrame) != 0:
@@ -97,10 +107,7 @@ class Component(__base__.Component):
def widget(self, parent):
self.parent = parent
self.settings = parent.settings
- page = uic.loadUi(os.path.join(
- os.path.dirname(os.path.realpath(__file__)),
- 'video.ui'
- ))
+ page = self.loadUi('video.ui')
self.videoPath = ''
self.x = 0
self.y = 0
@@ -243,9 +250,9 @@ 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(int(width)), str(int(height)))
+ return (str(math.ceil(width)), str(math.ceil(height)))
elif returntype == int:
- return (int(width), int(height))
+ return (math.ceil(width), math.ceil(height))
else:
return (width, height)
diff --git a/src/core.py b/src/core.py
index c80d60e..89c1e86 100644
--- a/src/core.py
+++ b/src/core.py
@@ -29,6 +29,7 @@ class Core():
else:
# unfrozen
self.wd = os.path.dirname(os.path.realpath(__file__))
+ self.componentsPath = os.path.join(self.wd, 'components')
self.loadEncoderOptions()
self.videoFormats = Core.appendUppercase([
@@ -66,14 +67,12 @@ class Core():
def findComponents(self):
def findComponents():
- srcPath = os.path.join(self.wd, 'components')
- if os.path.exists(srcPath):
- for f in sorted(os.listdir(srcPath)):
- name, ext = os.path.splitext(f)
- if name.startswith("__"):
- continue
- elif ext == '.py':
- yield name
+ for f in sorted(os.listdir(self.componentsPath)):
+ name, ext = os.path.splitext(f)
+ if name.startswith("__"):
+ continue
+ elif ext == '.py':
+ yield name
self.modules = [
import_module('components.%s' % name)
for name in findComponents()
@@ -93,10 +92,12 @@ class Core():
return None
component = self.modules[moduleIndex].Component(
- moduleIndex, compPos, self)
+ moduleIndex, compPos, self
+ )
self.selectedComponents.insert(
compPos,
- component)
+ component
+ )
self.componentListChanged()
# init component's widget for loading/saving presets
diff --git a/src/presetmanager.py b/src/presetmanager.py
index 44203e5..069bf62 100644
--- a/src/presetmanager.py
+++ b/src/presetmanager.py
@@ -123,7 +123,8 @@ class PresetManager(QtWidgets.QDialog):
def clearPreset(self, compI=None):
'''Functions on mainwindow level from the context menu'''
compI = self.parent.window.listWidget_componentList.currentRow()
- self.core.clearPreset(compI, self.parent)
+ self.core.clearPreset(compI)
+ self.parent.updateComponentTitle(compI, False)
def openSavePresetDialog(self):
'''Functions on mainwindow level from the context menu'''
diff --git a/src/preview_thread.py b/src/preview_thread.py
index 4a46d51..ac5751d 100644
--- a/src/preview_thread.py
+++ b/src/preview_thread.py
@@ -49,8 +49,19 @@ class Worker(QtCore.QObject):
components = nextPreviewInformation["components"]
for component in reversed(components):
- frame = Image.alpha_composite(
- frame, component.previewRender(self))
+ try:
+ newFrame = component.previewRender(self)
+ frame = Image.alpha_composite(
+ frame, newFrame)
+ except ValueError:
+ self.parent.showMessage(
+ msg="Bad frame returned by %s's previewRender method. "
+ "This is a fatal error." %
+ str(component),
+ detail="bad frame: w%s, h%s" % (
+ newFrame.width, newFrame.height)
+ )
+ quit(1)
self._image = ImageQt(frame)
self.imageCreated.emit(QtGui.QImage(self._image))
--
cgit v1.2.3
From 45b55d8e2fbffceefc9a1cd50b9bdb3e7ec9da78 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 24 Jun 2017 23:40:32 -0400
Subject: fixed lack of asterisks after openProject, added asterisk to window
title
---
src/core.py | 4 +++-
src/mainwindow.py | 9 +++++----
2 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/src/core.py b/src/core.py
index 89c1e86..fdba1c4 100644
--- a/src/core.py
+++ b/src/core.py
@@ -178,6 +178,7 @@ class Core():
for i, tup in enumerate(data['Components']):
name, vers, preset = tup
clearThis = False
+ modified = False
# add loaded named presets to savedPresets dict
if 'preset' in preset and preset['preset'] is not None:
@@ -187,6 +188,7 @@ class Core():
origSaveValueStore = self.getPreset(filepath2)
if origSaveValueStore:
self.savedPresets[nam] = dict(origSaveValueStore)
+ modified = not origSaveValueStore == preset
else:
# saved preset was renamed or deleted
clearThis = True
@@ -218,7 +220,7 @@ class Core():
if clearThis:
self.clearPreset(i)
if hasattr(loader, 'updateComponentTitle'):
- loader.updateComponentTitle(i)
+ loader.updateComponentTitle(i, modified)
except:
errcode = 1
data = sys.exc_info()
diff --git a/src/mainwindow.py b/src/mainwindow.py
index 7a9e397..7fae4ea 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -286,6 +286,8 @@ class MainWindow(QtWidgets.QMainWindow):
appName += ' - %s' % \
os.path.splitext(
os.path.basename(self.currentProject))[0]
+ if self.autosaveExists(identical=False):
+ appName += '*'
self.window.setWindowTitle(appName)
@QtCore.pyqtSlot(int, dict)
@@ -490,6 +492,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.newTask.emit(self.core.selectedComponents)
# self.processTask.emit()
self.autosave(force)
+ self.updateWindowTitle()
def showPreviewImage(self, image):
self.previewWindow.changePixmap(image)
@@ -602,11 +605,11 @@ class MainWindow(QtWidgets.QMainWindow):
self.currentProject = None
self.settings.setValue("currentProject", None)
self.drawPreview(True)
- self.updateWindowTitle()
def saveCurrentProject(self):
if self.currentProject:
self.core.createProjectFile(self.currentProject)
+ self.updateWindowTitle()
else:
self.openSaveProjectDialog()
@@ -638,8 +641,8 @@ class MainWindow(QtWidgets.QMainWindow):
self.settings.setValue("projectDir", os.path.dirname(filename))
self.settings.setValue("currentProject", filename)
self.currentProject = filename
- self.updateWindowTitle()
self.core.createProjectFile(filename)
+ self.updateWindowTitle()
def openOpenProjectDialog(self):
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
@@ -651,7 +654,6 @@ class MainWindow(QtWidgets.QMainWindow):
def openProject(self, filepath, prompt=True):
if not filepath or not os.path.exists(filepath) \
or not filepath.endswith('.avp'):
- self.updateWindowTitle()
return
self.clear()
@@ -660,7 +662,6 @@ class MainWindow(QtWidgets.QMainWindow):
self.openSaveChangesDialog('opening another project')
self.currentProject = filepath
- self.updateWindowTitle()
self.settings.setValue("currentProject", filepath)
self.settings.setValue("projectDir", os.path.dirname(filepath))
# actually load the project using core method
--
cgit v1.2.3
From a2838a0c3898f999e71f76e6e8d5691155438aea Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 25 Jun 2017 10:36:32 -0400
Subject: disable some hotkeys while encoding, more friendly error messages
---
src/components/video.py | 28 +++++++-----
src/main.py | 9 ++++
src/mainwindow.py | 111 +++++++++++++++++++++++++++++++-----------------
src/preview_thread.py | 9 ++--
src/video_thread.py | 9 ++--
5 files changed, 107 insertions(+), 59 deletions(-)
diff --git a/src/components/video.py b/src/components/video.py
index 3e87d2e..44f88a5 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -86,12 +86,14 @@ class Video:
continue
except AttributeError as e:
self.parent.showMessage(
- msg='%s couldn\'t be loaded.' % os.path.basename(
+ msg='%s couldn\'t be loaded. '
+ 'This is a fatal error.' % os.path.basename(
self.videoPath
),
detail=str(e)
)
self.parent.stopVideo()
+ break
self.currentFrame = pipe.stdout.read(self.chunkSize)
if len(self.currentFrame) != 0:
@@ -258,20 +260,24 @@ def scale(scale, width, height, returntype=None):
def finalizeFrame(self, imageData, width, height):
- if self.distort:
- try:
+ try:
+ if self.distort:
image = Image.frombytes(
'RGBA',
(width, height),
imageData)
- except ValueError:
- print('#### ignored invalid data caused by distortion ####')
- image = self.blankFrame(width, height)
- else:
- image = Image.frombytes(
- 'RGBA',
- scale(self.scale, width, height, int),
- imageData)
+ 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'
+ )
+ return self.blankFrame(width, height)
if self.scale != 100 \
or self.xPosition != 0 or self.yPosition != 0:
diff --git a/src/main.py b/src/main.py
index a8dd562..5b54fc7 100644
--- a/src/main.py
+++ b/src/main.py
@@ -7,6 +7,15 @@ import preview_thread
import video_thread
+def disableWhenEncoding(func):
+ def decorator(*args):
+ if args[0].encoding:
+ return
+ else:
+ return func(*args)
+ return decorator
+
+
def LoadDefaultSettings(self):
self.resolutions = [
'1920x1080',
diff --git a/src/mainwindow.py b/src/mainwindow.py
index 7fae4ea..76c2b62 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -12,7 +12,7 @@ import core
import preview_thread
import video_thread
from presetmanager import PresetManager
-from main import LoadDefaultSettings
+from main import LoadDefaultSettings, disableWhenEncoding
class PreviewWindow(QtWidgets.QLabel):
@@ -54,6 +54,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.pages = [] # widgets of component settings
self.lastAutosave = time.time()
+ self.encoding = False
# Create data directory, load/create settings
self.dataDir = self.core.dataDir
@@ -149,16 +150,18 @@ class MainWindow(QtWidgets.QMainWindow):
for i, comp in enumerate(self.core.modules):
action = self.compMenu.addAction(comp.Component.__doc__)
action.triggered.connect(
- lambda _, item=i: self.core.insertComponent(0, item, self))
+ lambda _, item=i: self.core.insertComponent(0, item, self)
+ )
self.window.pushButton_addComponent.setMenu(self.compMenu)
componentList.dropEvent = self.dragComponent
componentList.itemSelectionChanged.connect(
- self.changeComponentWidget)
-
+ self.changeComponentWidget
+ )
self.window.pushButton_removeComponent.clicked.connect(
- lambda _: self.removeComponent())
+ lambda: self.removeComponent()
+ )
componentList.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
componentList.customContextMenuRequested.connect(
@@ -173,7 +176,8 @@ class MainWindow(QtWidgets.QMainWindow):
currentRes = i
window.comboBox_resolution.setCurrentIndex(currentRes)
window.comboBox_resolution.currentIndexChanged.connect(
- self.updateResolution)
+ self.updateResolution
+ )
self.window.pushButton_listMoveUp.clicked.connect(
lambda: self.moveComponent(-1)
@@ -185,14 +189,17 @@ class MainWindow(QtWidgets.QMainWindow):
# Configure the Projects Menu
self.projectMenu = QMenu()
self.window.menuButton_newProject = self.projectMenu.addAction(
- "New Project")
+ "New Project"
+ )
self.window.menuButton_newProject.triggered.connect(
- self.createNewProject)
-
+ lambda: self.createNewProject()
+ )
self.window.menuButton_openProject = self.projectMenu.addAction(
- "Open Project")
+ "Open Project"
+ )
self.window.menuButton_openProject.triggered.connect(
- self.openOpenProjectDialog)
+ lambda: self.openOpenProjectDialog()
+ )
action = self.projectMenu.addAction("Save Project")
action.triggered.connect(self.saveCurrentProject)
@@ -207,6 +214,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.openPresetManager
)
+ self.updateWindowTitle()
window.show()
if project and project != self.autosavePath:
@@ -282,12 +290,15 @@ class MainWindow(QtWidgets.QMainWindow):
def updateWindowTitle(self):
appName = 'Audio Visualizer'
- if self.currentProject:
- appName += ' - %s' % \
- os.path.splitext(
- os.path.basename(self.currentProject))[0]
- if self.autosaveExists(identical=False):
- appName += '*'
+ try:
+ if self.currentProject:
+ appName += ' - %s' % \
+ os.path.splitext(
+ os.path.basename(self.currentProject))[0]
+ if self.autosaveExists(identical=False):
+ appName += '*'
+ except AttributeError:
+ pass
self.window.setWindowTitle(appName)
@QtCore.pyqtSlot(int, dict)
@@ -347,7 +358,7 @@ class MainWindow(QtWidgets.QMainWindow):
if not self.currentProject:
if os.path.exists(self.autosavePath):
os.remove(self.autosavePath)
- elif force or time.time() - self.lastAutosave >= 2.0:
+ elif force or time.time() - self.lastAutosave >= 0.1:
self.core.createProjectFile(self.autosavePath)
self.lastAutosave = time.time()
@@ -393,7 +404,7 @@ class MainWindow(QtWidgets.QMainWindow):
"Video Files (%s);; All Files (*)" % " ".join(
self.core.videoFormats))
- if not fileName == "":
+ if fileName:
self.settings.setValue("outputDir", os.path.dirname(fileName))
self.window.lineEdit_outputFile.setText(fileName)
@@ -404,33 +415,50 @@ class MainWindow(QtWidgets.QMainWindow):
def createAudioVisualisation(self):
# create output video if mandatory settings are filled in
- if self.window.lineEdit_audioFile.text() and \
- self.window.lineEdit_outputFile.text():
- self.canceled = False
- self.progressBarUpdated(-1)
- self.videoThread = QtCore.QThread(self)
- self.videoWorker = video_thread.Worker(self)
- self.videoWorker.moveToThread(self.videoThread)
- self.videoWorker.videoCreated.connect(self.videoCreated)
- self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated)
- self.videoWorker.progressBarSetText.connect(
- self.progressBarSetText)
- self.videoWorker.imageCreated.connect(self.showPreviewImage)
- self.videoWorker.encoding.connect(self.changeEncodingStatus)
- self.videoThread.start()
- outputPath = self.window.lineEdit_outputFile.text()
+ audioFile = self.window.lineEdit_audioFile.text()
+ outputPath = self.window.lineEdit_outputFile.text()
+
+ if audioFile and outputPath and self.core.selectedComponents:
if not os.path.dirname(outputPath):
outputPath = os.path.join(
os.path.expanduser("~"), outputPath)
- self.videoTask.emit(
- self.window.lineEdit_audioFile.text(),
- outputPath,
- self.core.selectedComponents)
+ if outputPath and os.path.isdir(outputPath):
+ self.showMessage(
+ msg='Chosen filename matches a directory, which '
+ 'cannot be overwritten. Please choose a different '
+ 'filename or move the directory.'
+ )
+ return
else:
- self.showMessage(
- msg="You must select an audio file and output filename.")
+ if not audioFile or not outputPath:
+ self.showMessage(
+ msg="You must select an audio file and output filename."
+ )
+ elif not self.core.selectedComponents:
+ self.showMessage(
+ msg="Not enough components."
+ )
+ return
+
+ self.canceled = False
+ self.progressBarUpdated(-1)
+ self.videoThread = QtCore.QThread(self)
+ self.videoWorker = video_thread.Worker(self)
+ self.videoWorker.moveToThread(self.videoThread)
+ self.videoWorker.videoCreated.connect(self.videoCreated)
+ self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated)
+ self.videoWorker.progressBarSetText.connect(
+ self.progressBarSetText)
+ self.videoWorker.imageCreated.connect(self.showPreviewImage)
+ self.videoWorker.encoding.connect(self.changeEncodingStatus)
+ self.videoThread.start()
+ self.videoTask.emit(
+ audioFile,
+ outputPath,
+ self.core.selectedComponents)
def changeEncodingStatus(self, status):
+ self.encoding = status
if status:
self.window.pushButton_createVideo.setEnabled(False)
self.window.pushButton_Cancel.setEnabled(True)
@@ -598,6 +626,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.window.stackedWidget.removeWidget(widget)
self.pages = []
+ @disableWhenEncoding
def createNewProject(self):
self.openSaveChangesDialog('starting a new project')
@@ -644,6 +673,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.core.createProjectFile(filename)
self.updateWindowTitle()
+ @disableWhenEncoding
def openOpenProjectDialog(self):
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
self.window, "Open Project File",
@@ -669,6 +699,7 @@ class MainWindow(QtWidgets.QMainWindow):
if self.window.listWidget_componentList.count() == 0:
self.drawPreview()
self.autosave(True)
+ self.updateWindowTitle()
def showMessage(self, **kwargs):
parent = kwargs['parent'] if 'parent' in kwargs else self.window
diff --git a/src/preview_thread.py b/src/preview_thread.py
index ac5751d..769656b 100644
--- a/src/preview_thread.py
+++ b/src/preview_thread.py
@@ -50,16 +50,15 @@ class Worker(QtCore.QObject):
components = nextPreviewInformation["components"]
for component in reversed(components):
try:
- newFrame = component.previewRender(self)
frame = Image.alpha_composite(
- frame, newFrame)
- except ValueError:
+ frame, component.previewRender(self)
+ )
+ except ValueError as e:
self.parent.showMessage(
msg="Bad frame returned by %s's previewRender method. "
"This is a fatal error." %
str(component),
- detail="bad frame: w%s, h%s" % (
- newFrame.width, newFrame.height)
+ detail=str(e)
)
quit(1)
diff --git a/src/video_thread.py b/src/video_thread.py
index b45381c..9b0bf56 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -2,7 +2,6 @@ from PyQt5 import QtCore, QtGui, uic
from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PIL import Image, ImageDraw, ImageFont
from PIL.ImageQt import ImageQt
-import core
import numpy
import subprocess as sp
import sys
@@ -13,6 +12,8 @@ import time
from copy import copy
import signal
+import core
+
class Worker(QtCore.QObject):
@@ -87,8 +88,10 @@ class Worker(QtCore.QObject):
self.encoding.emit(True)
self.components = components
self.outputFile = outputFile
- self.bgI = 0 # tracked video frame
+
self.reset()
+
+ self.bgI = 0 # tracked video frame
self.width = int(self.core.settings.value('outputWidth'))
self.height = int(self.core.settings.value('outputHeight'))
progressBarValue = 0
@@ -171,7 +174,7 @@ class Worker(QtCore.QObject):
self.staticComponents = {}
numComps = len(self.components)
for compNo, comp in enumerate(self.components):
- pStr = "Analyzing audio..."
+ pStr = "Starting components..."
self.progressBarSetText.emit(pStr)
properties = None
properties = comp.preFrameRender(
--
cgit v1.2.3
From 675a06dd4c10babb3ef2553f6c7cdd92b5f5ef0a Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 25 Jun 2017 14:27:56 -0400
Subject: project files save settings & out/in fields
---
src/command.py | 6 ++---
src/components/image.py | 4 +--
src/components/video.py | 8 +++---
src/core.py | 71 ++++++++++++++++++++++++++++++++++++++++++-------
src/mainwindow.py | 30 +++++++++++++++------
src/presetmanager.py | 4 +--
6 files changed, 94 insertions(+), 29 deletions(-)
diff --git a/src/command.py b/src/command.py
index 2f71f31..b400773 100644
--- a/src/command.py
+++ b/src/command.py
@@ -1,5 +1,4 @@
-from PyQt4 import QtCore
-from PyQt4.QtCore import QSettings
+from PyQt5 import QtCore
import argparse
import os
import sys
@@ -43,8 +42,7 @@ class Command(QtCore.QObject):
nargs='*', action='append')
self.args = self.parser.parse_args()
- self.settings = QSettings(
- os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat)
+ self.settings = self.core.settings
LoadDefaultSettings(self)
if self.args.projpath:
diff --git a/src/components/image.py b/src/components/image.py
index 143ae59..4bb33b1 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -83,12 +83,12 @@ class Component(__base__.Component):
}
def pickImage(self):
- imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
+ imgDir = self.settings.value("componentDir", os.path.expanduser("~"))
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
self.page, "Choose Image", imgDir,
"Image Files (%s)" % " ".join(self.imageFormats))
if filename:
- self.settings.setValue("backgroundDir", os.path.dirname(filename))
+ self.settings.setValue("componentDir", os.path.dirname(filename))
self.page.lineEdit_image.setText(filename)
self.update()
diff --git a/src/components/video.py b/src/components/video.py
index 44f88a5..d37dd99 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -88,8 +88,8 @@ class Video:
self.parent.showMessage(
msg='%s couldn\'t be loaded. '
'This is a fatal error.' % os.path.basename(
- self.videoPath
- ),
+ self.videoPath
+ ),
detail=str(e)
)
self.parent.stopVideo()
@@ -188,13 +188,13 @@ class Component(__base__.Component):
}
def pickVideo(self):
- imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
+ imgDir = self.settings.value("componentDir", os.path.expanduser("~"))
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
self.page, "Choose Video",
imgDir, "Video Files (%s)" % " ".join(self.videoFormats)
)
if filename:
- self.settings.setValue("backgroundDir", os.path.dirname(filename))
+ self.settings.setValue("componentDir", os.path.dirname(filename))
self.page.lineEdit_video.setText(filename)
self.update()
diff --git a/src/core.py b/src/core.py
index fdba1c4..d7e8219 100644
--- a/src/core.py
+++ b/src/core.py
@@ -30,6 +30,10 @@ class Core():
# unfrozen
self.wd = os.path.dirname(os.path.realpath(__file__))
self.componentsPath = os.path.join(self.wd, 'components')
+ self.settings = QtCore.QSettings(
+ os.path.join(self.dataDir, 'settings.ini'),
+ QtCore.QSettings.IniFormat
+ )
self.loadEncoderOptions()
self.videoFormats = Core.appendUppercase([
@@ -169,13 +173,23 @@ class Core():
its own showMessage(**kwargs) method for displaying errors.
'''
if not os.path.exists(filepath):
- loader.showMessage(msg='Project file not found')
+ loader.showMessage(msg='Project file not found.')
return
errcode, data = self.parseAvFile(filepath)
if errcode == 0:
try:
- for i, tup in enumerate(data['Components']):
+ if hasattr(loader, 'window'):
+ for pair in data['WindowFields']:
+ widget, value = pair.split('=', 1)
+ widget = eval('loader.window.%s' % widget)
+ widget.setText(value.strip())
+
+ for pair in data['Settings']:
+ key, value = pair.split('=', 1)
+ self.settings.setValue(key, value.strip())
+
+ for tup in data['Components']:
name, vers, preset = tup
clearThis = False
modified = False
@@ -213,7 +227,7 @@ class Core():
preset['preset']
)
except KeyError as e:
- print('%s missing value %s' % (
+ print('%s missing value: %s' % (
self.selectedComponents[i], e)
)
@@ -221,23 +235,26 @@ class Core():
self.clearPreset(i)
if hasattr(loader, 'updateComponentTitle'):
loader.updateComponentTitle(i, modified)
+
except:
errcode = 1
data = sys.exc_info()
if errcode == 1:
- typ, value, _ = data
- if typ.__name__ == KeyError:
+ typ, value, tb = data
+ if typ.__name__ == 'KeyError':
# probably just an old version, still loadable
print('file missing value: %s' % value)
return
if hasattr(loader, 'createNewProject'):
loader.createNewProject()
- msg = '%s: %s' % (typ.__name__, value)
+ import traceback
+ msg = '%s: %s\n\nTraceback:\n' % (typ.__name__, value)
+ msg += "\n".join(traceback.format_tb(tb))
loader.showMessage(
msg="Project file '%s' is corrupted." % filepath,
showCancel=False,
- icon=QtGui.QMessageBox.Warning,
+ icon='Warning',
detail=msg)
def parseAvFile(self, filepath):
@@ -250,7 +267,11 @@ class Core():
with open(filepath, 'r') as f:
def parseLine(line):
'''Decides if a file line is a section header'''
- validSections = ('Components')
+ validSections = (
+ 'Components',
+ 'Settings',
+ 'WindowFields'
+ )
line = line.strip()
newSection = ''
@@ -283,6 +304,8 @@ class Core():
lastCompPreset
))
i = 0
+ elif line and section:
+ data[section].append(line)
return 0, data
except:
return 1, sys.exc_info()
@@ -354,8 +377,22 @@ class Core():
f.write('%s\n' % str(vers))
f.write(Core.presetToString(saveValueStore))
- def createProjectFile(self, filepath):
+ def createProjectFile(self, filepath, window=None):
'''Create a project file (.avp) using the current program state'''
+ forbiddenSettingsKeys = [
+ 'currentProject',
+ 'outputAudioBitrate',
+ 'outputAudioCodec',
+ 'outputContainer',
+ 'outputFormat',
+ 'outputFrameRate',
+ 'outputHeight',
+ 'outputPreset',
+ 'outputVideoBitrate',
+ 'outputVideoCodec',
+ 'outputVideoFormat',
+ 'outputWidth',
+ ]
try:
if not filepath.endswith(".avp"):
filepath += '.avp'
@@ -363,12 +400,28 @@ class Core():
os.remove(filepath)
with open(filepath, 'w') as f:
print('creating %s' % filepath)
+
f.write('[Components]\n')
for comp in self.selectedComponents:
saveValueStore = comp.savePreset()
f.write('%s\n' % str(comp))
f.write('%s\n' % str(comp.version()))
f.write('%s\n' % Core.presetToString(saveValueStore))
+
+ f.write('[Settings]\n')
+ for key in self.settings.allKeys():
+ if key not in forbiddenSettingsKeys:
+ f.write('%s=%s\n' % (key, self.settings.value(key)))
+
+ if window:
+ f.write('[WindowFields]\n')
+ f.write(
+ 'lineEdit_audioFile=%s\n'
+ 'lineEdit_outputFile=%s\n' % (
+ window.lineEdit_audioFile.text(),
+ window.lineEdit_outputFile.text()
+ )
+ )
return True
except:
return False
diff --git a/src/mainwindow.py b/src/mainwindow.py
index 76c2b62..e4e4f38 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -1,6 +1,5 @@
from queue import Queue
from PyQt5 import QtCore, QtGui, uic, QtWidgets
-from PyQt5.QtCore import QSettings, Qt
from PyQt5.QtWidgets import QMenu, QShortcut
import sys
import os
@@ -27,7 +26,9 @@ class PreviewWindow(QtWidgets.QLabel):
painter = QtGui.QPainter(self)
point = QtCore.QPoint(0, 0)
scaledPix = self.pixmap.scaled(
- size, Qt.KeepAspectRatio, transformMode=Qt.SmoothTransformation)
+ size,
+ QtCore.Qt.KeepAspectRatio,
+ transformMode=QtCore.Qt.SmoothTransformation)
# start painting the label from left upper corner
point.setX((size.width() - scaledPix.width())/2)
@@ -59,8 +60,7 @@ class MainWindow(QtWidgets.QMainWindow):
# Create data directory, load/create settings
self.dataDir = self.core.dataDir
self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
- self.settings = QSettings(
- os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat)
+ self.settings = self.core.settings
LoadDefaultSettings(self)
self.presetManager = PresetManager(
uic.loadUi(
@@ -94,6 +94,13 @@ class MainWindow(QtWidgets.QMainWindow):
window.toolButton_selectOutputFile.clicked.connect(
self.openOutputFileDialog)
+ def changedField():
+ self.autosave()
+ self.updateWindowTitle()
+
+ window.lineEdit_audioFile.textChanged.connect(changedField)
+ window.lineEdit_outputFile.textChanged.connect(changedField)
+
window.progressBar_createVideo.setValue(0)
window.pushButton_createVideo.clicked.connect(
@@ -359,7 +366,7 @@ class MainWindow(QtWidgets.QMainWindow):
if os.path.exists(self.autosavePath):
os.remove(self.autosavePath)
elif force or time.time() - self.lastAutosave >= 0.1:
- self.core.createProjectFile(self.autosavePath)
+ self.core.createProjectFile(self.autosavePath, self.window)
self.lastAutosave = time.time()
def autosaveExists(self, identical=True):
@@ -625,6 +632,13 @@ class MainWindow(QtWidgets.QMainWindow):
for widget in self.pages:
self.window.stackedWidget.removeWidget(widget)
self.pages = []
+ for field in (
+ self.window.lineEdit_audioFile,
+ self.window.lineEdit_outputFile
+ ):
+ field.blockSignals(True)
+ field.setText('')
+ field.blockSignals(False)
@disableWhenEncoding
def createNewProject(self):
@@ -637,7 +651,7 @@ class MainWindow(QtWidgets.QMainWindow):
def saveCurrentProject(self):
if self.currentProject:
- self.core.createProjectFile(self.currentProject)
+ self.core.createProjectFile(self.currentProject, self.window)
self.updateWindowTitle()
else:
self.openSaveProjectDialog()
@@ -670,7 +684,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.settings.setValue("projectDir", os.path.dirname(filename))
self.settings.setValue("currentProject", filename)
self.currentProject = filename
- self.core.createProjectFile(filename)
+ self.core.createProjectFile(filename, self.window)
self.updateWindowTitle()
@disableWhenEncoding
@@ -707,7 +721,7 @@ class MainWindow(QtWidgets.QMainWindow):
msg.setModal(True)
msg.setText(kwargs['msg'])
msg.setIcon(
- kwargs['icon']
+ eval('QtWidgets.QMessageBox.%s' % kwargs['icon'])
if 'icon' in kwargs else QtWidgets.QMessageBox.Information
)
msg.setDetailedText(kwargs['detail'] if 'detail' in kwargs else None)
diff --git a/src/presetmanager.py b/src/presetmanager.py
index 069bf62..3ab49ef 100644
--- a/src/presetmanager.py
+++ b/src/presetmanager.py
@@ -176,7 +176,7 @@ class PresetManager(QtWidgets.QDialog):
msg="%s already exists! Overwrite it?" %
os.path.basename(path),
showCancel=True,
- icon=QtWidgets.QMessageBox.Warning,
+ icon='Warning',
parent=window)
if not ch:
# user clicked cancel
@@ -209,7 +209,7 @@ class PresetManager(QtWidgets.QDialog):
ch = self.parent.showMessage(
msg='Really delete %s?' % name,
showCancel=True,
- icon=QtWidgets.QMessageBox.Warning,
+ icon='Warning',
parent=self.window
)
if not ch:
--
cgit v1.2.3
From 6a1a5cd6eb931f5f9316f89c680ca318f845a746 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 25 Jun 2017 15:31:42 -0400
Subject: --export commandline option
overrides -i and -o to use saved fields from a project file
---
src/command.py | 48 +++++++++++++++++++++++++++++++++++++++---------
src/components/video.py | 3 ++-
src/core.py | 2 ++
src/mainwindow.py | 7 +++++--
src/preview_thread.py | 3 ++-
5 files changed, 50 insertions(+), 13 deletions(-)
diff --git a/src/command.py b/src/command.py
index b400773..09b54ac 100644
--- a/src/command.py
+++ b/src/command.py
@@ -23,13 +23,20 @@ class Command(QtCore.QObject):
epilog='EXAMPLE COMMAND: main.py myvideotemplate.avp '
'-i ~/Music/song.mp3 -o ~/video.mp4 '
'-c 0 image path=~/Pictures/thisWeeksPicture.jpg '
- '-c 1 video "preset=My Logo" -c 2 vis layout=classic')
+ '-c 1 video "preset=My Logo" -c 2 vis layout=classic'
+ )
self.parser.add_argument(
'-i', '--input', metavar='SOUND',
- help='input audio file')
+ help='input audio file'
+ )
self.parser.add_argument(
'-o', '--output', metavar='OUTPUT',
- help='output video file')
+ help='output video file'
+ )
+ self.parser.add_argument(
+ '-e', '--export', action='store_true',
+ help='use input and output files from project file'
+ )
# optional arguments
self.parser.add_argument(
@@ -46,7 +53,15 @@ class Command(QtCore.QObject):
LoadDefaultSettings(self)
if self.args.projpath:
- self.core.openProject(self, self.args.projpath)
+ projPath = self.args.projpath
+ if not os.path.dirname(projPath):
+ projPath = os.path.join(
+ self.settings.value("projectDir"),
+ projPath
+ )
+ if not projPath.endswith('.avp'):
+ projPath += '.avp'
+ self.core.openProject(self, projPath)
self.core.selectedComponents = list(
reversed(self.core.selectedComponents))
self.core.componentListChanged()
@@ -70,13 +85,28 @@ class Command(QtCore.QObject):
for arg in args:
self.core.selectedComponents[i].command(arg)
- if self.args.input and self.args.output:
- self.createAudioVisualisation()
+ if self.args.export and self.args.projpath:
+ errcode, data = self.core.parseAvFile(projPath)
+ for line in data['WindowFields']:
+ if 'outputFile' in line:
+ output = line.split('=', 1)[1]
+ if not os.path.dirname(output):
+ output = os.path.join(
+ os.path.expanduser('~'),
+ output
+ )
+ if 'audioFile' in line:
+ input = line.split('=', 1)[1]
+ self.createAudioVisualisation(input, output)
+
+ elif self.args.input and self.args.output:
+ self.createAudioVisualisation(self.args.input, self.args.output)
+
elif 'help' not in sys.argv:
self.parser.print_help()
quit(1)
- def createAudioVisualisation(self):
+ def createAudioVisualisation(self, input, output):
self.videoThread = QtCore.QThread(self)
self.videoWorker = video_thread.Worker(self)
self.videoWorker.moveToThread(self.videoThread)
@@ -84,8 +114,8 @@ class Command(QtCore.QObject):
self.videoThread.start()
self.videoTask.emit(
- self.args.input,
- self.args.output,
+ input,
+ output,
list(reversed(self.core.selectedComponents))
)
diff --git a/src/components/video.py b/src/components/video.py
index d37dd99..02bb44b 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -90,7 +90,8 @@ class Video:
'This is a fatal error.' % os.path.basename(
self.videoPath
),
- detail=str(e)
+ detail=str(e),
+ icon='Warning'
)
self.parent.stopVideo()
break
diff --git a/src/core.py b/src/core.py
index d7e8219..a435c2c 100644
--- a/src/core.py
+++ b/src/core.py
@@ -183,7 +183,9 @@ class Core():
for pair in data['WindowFields']:
widget, value = pair.split('=', 1)
widget = eval('loader.window.%s' % widget)
+ widget.blockSignals(True)
widget.setText(value.strip())
+ widget.blockSignals(False)
for pair in data['Settings']:
key, value = pair.split('=', 1)
diff --git a/src/mainwindow.py b/src/mainwindow.py
index e4e4f38..203992b 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -229,7 +229,9 @@ class MainWindow(QtWidgets.QMainWindow):
project += '.avp'
# open a project from the commandline
if not os.path.dirname(project):
- project = os.path.join(os.path.expanduser('~'), project)
+ project = os.path.join(
+ self.settings.value("projectDir"), project
+ )
self.currentProject = project
self.settings.setValue("currentProject", project)
if os.path.exists(self.autosavePath):
@@ -433,7 +435,8 @@ class MainWindow(QtWidgets.QMainWindow):
self.showMessage(
msg='Chosen filename matches a directory, which '
'cannot be overwritten. Please choose a different '
- 'filename or move the directory.'
+ 'filename or move the directory.',
+ icon='Warning',
)
return
else:
diff --git a/src/preview_thread.py b/src/preview_thread.py
index 769656b..e58f04e 100644
--- a/src/preview_thread.py
+++ b/src/preview_thread.py
@@ -58,7 +58,8 @@ class Worker(QtCore.QObject):
msg="Bad frame returned by %s's previewRender method. "
"This is a fatal error." %
str(component),
- detail=str(e)
+ detail=str(e),
+ icon='Warning'
)
quit(1)
--
cgit v1.2.3
From fc2951379c418086bcc00af2b8901f92eafc224a Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 25 Jun 2017 15:34:33 -0400
Subject: newlines make project file easier to read
---
src/core.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/core.py b/src/core.py
index a435c2c..341aa01 100644
--- a/src/core.py
+++ b/src/core.py
@@ -410,13 +410,13 @@ class Core():
f.write('%s\n' % str(comp.version()))
f.write('%s\n' % Core.presetToString(saveValueStore))
- f.write('[Settings]\n')
+ f.write('\n[Settings]\n')
for key in self.settings.allKeys():
if key not in forbiddenSettingsKeys:
f.write('%s=%s\n' % (key, self.settings.value(key)))
if window:
- f.write('[WindowFields]\n')
+ f.write('\n[WindowFields]\n')
f.write(
'lineEdit_audioFile=%s\n'
'lineEdit_outputFile=%s\n' % (
--
cgit v1.2.3
From 2c82a65d1b79b898b2bc27fc5b1e0362fc160c46 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 25 Jun 2017 15:50:31 -0400
Subject: needs more tuples
---
src/command.py | 12 ++++++------
src/core.py | 27 +++++++++++++--------------
2 files changed, 19 insertions(+), 20 deletions(-)
diff --git a/src/command.py b/src/command.py
index 09b54ac..3eea1b6 100644
--- a/src/command.py
+++ b/src/command.py
@@ -87,16 +87,16 @@ class Command(QtCore.QObject):
if self.args.export and self.args.projpath:
errcode, data = self.core.parseAvFile(projPath)
- for line in data['WindowFields']:
- if 'outputFile' in line:
- output = line.split('=', 1)[1]
- if not os.path.dirname(output):
+ for key, value in data['WindowFields']:
+ if 'outputFile' in key:
+ output = value
+ if not os.path.dirname(value):
output = os.path.join(
os.path.expanduser('~'),
output
)
- if 'audioFile' in line:
- input = line.split('=', 1)[1]
+ if 'audioFile' in key:
+ input = value
self.createAudioVisualisation(input, output)
elif self.args.input and self.args.output:
diff --git a/src/core.py b/src/core.py
index 341aa01..2994a24 100644
--- a/src/core.py
+++ b/src/core.py
@@ -180,16 +180,14 @@ class Core():
if errcode == 0:
try:
if hasattr(loader, 'window'):
- for pair in data['WindowFields']:
- widget, value = pair.split('=', 1)
+ for widget, value in data['WindowFields']:
widget = eval('loader.window.%s' % widget)
widget.blockSignals(True)
- widget.setText(value.strip())
+ widget.setText(value)
widget.blockSignals(False)
- for pair in data['Settings']:
- key, value = pair.split('=', 1)
- self.settings.setValue(key, value.strip())
+ for key, value in data['Settings']:
+ self.settings.setValue(key, value)
for tup in data['Components']:
name, vers, preset = tup
@@ -264,16 +262,16 @@ class Core():
Returns dictionary with section names as the keys, each one
contains a list of tuples: (compName, version, compPresetDict)
'''
- data = {}
+ validSections = (
+ 'Components',
+ 'Settings',
+ 'WindowFields'
+ )
+ data = {sect: [] for sect in validSections}
try:
with open(filepath, 'r') as f:
def parseLine(line):
'''Decides if a file line is a section header'''
- validSections = (
- 'Components',
- 'Settings',
- 'WindowFields'
- )
line = line.strip()
newSection = ''
@@ -289,7 +287,6 @@ class Core():
line, newSection = parseLine(line)
if newSection:
section = str(newSection)
- data[section] = []
continue
if line and section == 'Components':
if i == 0:
@@ -307,7 +304,9 @@ class Core():
))
i = 0
elif line and section:
- data[section].append(line)
+ key, value = line.split('=', 1)
+ data[section].append((key, value.strip()))
+
return 0, data
except:
return 1, sys.exc_info()
--
cgit v1.2.3
From f284acbf19ca3549b4aa2c3cab226e5254cdf936 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 25 Jun 2017 16:13:54 -0400
Subject: whitelist is more sensible here than blacklist
---
src/core.py | 21 +++++++--------------
1 file changed, 7 insertions(+), 14 deletions(-)
diff --git a/src/core.py b/src/core.py
index 2994a24..47fa01a 100644
--- a/src/core.py
+++ b/src/core.py
@@ -380,19 +380,12 @@ class Core():
def createProjectFile(self, filepath, window=None):
'''Create a project file (.avp) using the current program state'''
- forbiddenSettingsKeys = [
- 'currentProject',
- 'outputAudioBitrate',
- 'outputAudioCodec',
- 'outputContainer',
- 'outputFormat',
- 'outputFrameRate',
- 'outputHeight',
- 'outputPreset',
- 'outputVideoBitrate',
- 'outputVideoCodec',
- 'outputVideoFormat',
- 'outputWidth',
+ settingsKeys = [
+ 'componentDir',
+ 'inputDir',
+ 'outputDir',
+ 'presetDir',
+ 'projectDir',
]
try:
if not filepath.endswith(".avp"):
@@ -411,7 +404,7 @@ class Core():
f.write('\n[Settings]\n')
for key in self.settings.allKeys():
- if key not in forbiddenSettingsKeys:
+ if key in settingsKeys:
f.write('%s=%s\n' % (key, self.settings.value(key)))
if window:
--
cgit v1.2.3
From 252639e9a2ab69e0aceb0caa6ae3ca0a3dfad686 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 25 Jun 2017 18:12:16 -0400
Subject: renamed Original Audio Visualization to Classic Visualizer
---
src/components/__base__.py | 5 +++++
src/components/original.py | 6 +++++-
src/core.py | 19 +++++++++++++++----
src/main.py | 4 ++--
src/mainwindow.py | 5 +++--
5 files changed, 30 insertions(+), 9 deletions(-)
diff --git a/src/components/__base__.py b/src/components/__base__.py
index 84d41c8..9b04157 100644
--- a/src/components/__base__.py
+++ b/src/components/__base__.py
@@ -144,6 +144,11 @@ class Component(QtCore.QObject):
height = int(self.worker.core.settings.value('outputHeight'))
image = Image.new("RGBA", (width, height), (0,0,0,0))
return image
+
+ @classmethod
+ def names(cls):
+ # Alternative names for renaming a component between project files
+ return []
'''
diff --git a/src/components/original.py b/src/components/original.py
index 0185e0d..8450aa1 100644
--- a/src/components/original.py
+++ b/src/components/original.py
@@ -9,10 +9,14 @@ from copy import copy
class Component(__base__.Component):
- '''Original Audio Visualization'''
+ '''Classic Visualizer'''
modified = QtCore.pyqtSignal(int, dict)
+ @classmethod
+ def names(cls):
+ return ['Original Audio Visualization']
+
def widget(self, parent):
self.parent = parent
self.visColor = (255, 255, 255)
diff --git a/src/core.py b/src/core.py
index 47fa01a..b3c5640 100644
--- a/src/core.py
+++ b/src/core.py
@@ -1,5 +1,4 @@
import sys
-import io
import os
from PyQt5 import QtCore, QtGui, uic
from os.path import expanduser
@@ -81,8 +80,15 @@ class Core():
import_module('components.%s' % name)
for name in findComponents()
]
+ # store canonical module names and indexes
self.moduleIndexes = [i for i in range(len(self.modules))]
self.compNames = [mod.Component.__doc__ for mod in self.modules]
+ self.altCompNames = []
+ # store alternative names for modules
+ for i, mod in enumerate(self.modules):
+ if hasattr(mod.Component, 'names'):
+ for name in mod.Component.names():
+ self.altCompNames.append((name, i))
def componentListChanged(self):
for i, component in enumerate(self.selectedComponents):
@@ -132,8 +138,13 @@ class Core():
self.selectedComponents[i].update()
def moduleIndexFor(self, compName):
- index = self.compNames.index(compName)
- return self.moduleIndexes[index]
+ try:
+ index = self.compNames.index(compName)
+ return self.moduleIndexes[index]
+ except ValueError:
+ for altName, modI in self.altCompNames:
+ if altName == compName:
+ return self.moduleIndexes[modI]
def clearPreset(self, compIndex):
self.selectedComponents[compIndex].currentPreset = None
@@ -247,7 +258,7 @@ class Core():
print('file missing value: %s' % value)
return
if hasattr(loader, 'createNewProject'):
- loader.createNewProject()
+ loader.createNewProject(prompt=False)
import traceback
msg = '%s: %s\n\nTraceback:\n' % (typ.__name__, value)
msg += "\n".join(traceback.format_tb(tb))
diff --git a/src/main.py b/src/main.py
index 5b54fc7..fd32b13 100644
--- a/src/main.py
+++ b/src/main.py
@@ -8,11 +8,11 @@ import video_thread
def disableWhenEncoding(func):
- def decorator(*args):
+ def decorator(*args, **kwargs):
if args[0].encoding:
return
else:
- return func(*args)
+ return func(*args, **kwargs)
return decorator
diff --git a/src/mainwindow.py b/src/mainwindow.py
index 203992b..a39f344 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -644,8 +644,9 @@ class MainWindow(QtWidgets.QMainWindow):
field.blockSignals(False)
@disableWhenEncoding
- def createNewProject(self):
- self.openSaveChangesDialog('starting a new project')
+ def createNewProject(self, prompt=True):
+ if prompt:
+ self.openSaveChangesDialog('starting a new project')
self.clear()
self.currentProject = None
--
cgit v1.2.3
From 9fe370d472b84a693187316716bf7201df930b0c Mon Sep 17 00:00:00 2001
From: Brianna
Date: Sun, 25 Jun 2017 18:28:40 -0400
Subject: Update readme to match newgui
---
README.md | 20 +++++++++-----------
1 file changed, 9 insertions(+), 11 deletions(-)
diff --git a/README.md b/README.md
index be5ae25..2d59979 100644
--- a/README.md
+++ b/README.md
@@ -1,28 +1,26 @@
audio-visualizer-python
=======================
-This is a little GUI tool which creates an audio visualization video from an input audio.
-You can also give it a background image and set a title text.
+This is a little GUI tool which creates an audio visualization video from an input audio file. Different components can be added and layered to change the resulting video and add images, videos, gradients, text, etc. The component setup can be saved as a Project and exporting can be automated using commandline options.
-I have tested the program on Linux (Ubuntu 14.10) and Windows (Windows 7), it should also work on Mac OS X. If you encounter problems
-running it or have other bug reports or features, that you wish to see implemented, please fork the project and send me a pull request and/or file an issue on this project.
+The program works on Linux (Ubuntu 16.04), Windows (Windows 7), and Mac OS X. If you encounter problems running it or have other bug reports or features that you wish to see implemented, please fork the project and send me a pull request and/or file an issue on this project.
I also need a good name that is not as generic as "audio-visualizer-python"!
Dependencies
------------
-You need Python 3, PyQt4, PIL (or Pillow), numpy and the program ffmpeg, which is used to read the audio and render the video.
+Python 3, PyQt5, pillow-simd, numpy, and ffmpeg 3.3
Installation
------------
-### Manual installation on Ubuntu
-* Get all the python stuff: `sudo apt-get install python3 python3-pyqt4 python3-pil python3-numpy`
-* Get ffmpeg/avconv:
-You can either use `avconv` from the standard repositories (package `libav-tools`) or get `ffmpeg` from the [website](http://ffmpeg.org/) or from a PPA (e.g. [https://launchpad.net/~jon-severinsson/+archive/ubuntu/ffmpeg](https://launchpad.net/~jon-severinsson/+archive/ubuntu/ffmpeg). The program does automatically detect if you don't have the ffmpeg binary and tries to use avconv instead.
+### Manual installation on Ubuntu 16.04
+* Install pip: `sudo apt-get install python3-pip`
+* Install dependencies: `sudo pip3 install pyqt5 numpy pillow-simd`
+* Install `ffmpeg` from the [website](http://ffmpeg.org/) or from a PPA (e.g. [https://launchpad.net/~jonathonf/+archive/ubuntu/ffmpeg-3](https://launchpad.net/~jonathonf/+archive/ubuntu/ffmpeg-3). NOTE: `ffmpeg` in the standard repos is too old (v2.8). Old versions and `avconv` may be used but full functionality is only guaranteed with `ffmpeg` 3.3 or higher.
Download audio-visualizer-python from this repository and run it with `python3 main.py`.
-### Manual installation on Windows
+### Manual installation on Windows [Outdated]
* Download and install Python 3.4 from [https://www.python.org/downloads/windows/](https://www.python.org/downloads/windows/)
* Download and install PyQt4 for Python 3.4 and Qt4 from [http://www.riverbankcomputing.co.uk/software/pyqt/download](http://www.riverbankcomputing.co.uk/software/pyqt/download)
* Download and install numpy from [http://www.scipy.org/scipylib/download.html](http://www.scipy.org/scipylib/download.html). There is an installer available, make sure to get the one for Python 3.4
@@ -32,7 +30,7 @@ Download audio-visualizer-python from this repository and run it with `python3 m
Download audio-visualizer-python from this repository and run it from the command line with `C:\Python34\python.exe main.py`.
-### Manual installation on macOS
+### Manual installation on macOS [Outdated]
* Install [Homebrew](http://brew.sh/)
* Use the following commands to install the needed dependencies:
--
cgit v1.2.3
From 4eb2bc9a41f0c066cf3d6493c0b3c829e66dc74a Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 25 Jun 2017 19:38:58 -0400
Subject: preset delete, rename, save updates both windows
---
src/components/color.py | 2 +-
src/presetmanager.py | 17 ++++++++++++++---
2 files changed, 15 insertions(+), 4 deletions(-)
diff --git a/src/components/color.py b/src/components/color.py
index 253ac83..8a994db 100644
--- a/src/components/color.py
+++ b/src/components/color.py
@@ -152,7 +152,7 @@ class Component(__base__.Component):
brush = QtGui.QLinearGradient(
self.LG_start,
self.LG_start,
- self.LG_start+width/3,
+ self.LG_end+width/3,
self.LG_end)
elif self.fillType == 2: # Radial Gradient
diff --git a/src/presetmanager.py b/src/presetmanager.py
index 3ab49ef..300b534 100644
--- a/src/presetmanager.py
+++ b/src/presetmanager.py
@@ -158,7 +158,9 @@ class PresetManager(QtWidgets.QDialog):
self.createNewPreset(
componentName, vers, newName,
saveValueStore, window=self.parent.window)
- self.openPreset(newName)
+ self.findPresets()
+ self.drawPresetList()
+ self.openPreset(newName, index)
break
def createNewPreset(
@@ -184,11 +186,11 @@ class PresetManager(QtWidgets.QDialog):
return False
- def openPreset(self, presetName):
+ def openPreset(self, presetName, compPos=None):
componentList = self.parent.window.listWidget_componentList
selectedComponents = self.parent.core.selectedComponents
- index = componentList.currentRow()
+ index = compPos if compPos is not None else componentList.currentRow()
if index == -1:
return
componentName = str(selectedComponents[index]).strip()
@@ -218,6 +220,10 @@ class PresetManager(QtWidgets.QDialog):
self.findPresets()
self.drawPresetList()
+ for i, comp in enumerate(self.core.selectedComponents):
+ if comp.currentPreset == name:
+ self.clearPreset(i)
+
def deletePreset(self, comp, vers, name):
filepath = os.path.join(self.presetDir, comp, str(vers), name)
os.remove(filepath)
@@ -260,6 +266,11 @@ class PresetManager(QtWidgets.QDialog):
os.rename(oldPath, newPath)
self.findPresets()
self.drawPresetList()
+
+ for i, comp in enumerate(self.core.selectedComponents):
+ if comp.currentPreset == oldName:
+ comp.currentPreset = newName
+ self.parent.updateComponentTitle(i, True)
break
def openImportDialog(self):
--
cgit v1.2.3
From 7b6ef6349b1922e71ab83fc45d4a1598ef203511 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 25 Jun 2017 20:43:06 -0400
Subject: component list not visually disabled
this looks better on Mac
---
src/mainwindow.py | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/mainwindow.py b/src/mainwindow.py
index a39f344..42d2a50 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -485,7 +485,6 @@ class MainWindow(QtWidgets.QMainWindow):
self.window.pushButton_removeComponent.setEnabled(False)
self.window.pushButton_listMoveDown.setEnabled(False)
self.window.pushButton_listMoveUp.setEnabled(False)
- self.window.listWidget_componentList.setEnabled(False)
self.window.menuButton_newProject.setEnabled(False)
self.window.menuButton_openProject.setEnabled(False)
else:
@@ -504,7 +503,6 @@ class MainWindow(QtWidgets.QMainWindow):
self.window.pushButton_removeComponent.setEnabled(True)
self.window.pushButton_listMoveDown.setEnabled(True)
self.window.pushButton_listMoveUp.setEnabled(True)
- self.window.listWidget_componentList.setEnabled(True)
self.window.menuButton_newProject.setEnabled(True)
self.window.menuButton_openProject.setEnabled(True)
self.drawPreview(True)
@@ -566,6 +564,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.changeComponentWidget()
self.drawPreview()
+ @disableWhenEncoding
def moveComponent(self, change):
'''Moves a component relatively from its current position'''
componentList = self.window.listWidget_componentList
@@ -587,16 +586,19 @@ class MainWindow(QtWidgets.QMainWindow):
stackedWidget.setCurrentIndex(newRow)
self.drawPreview()
+ @disableWhenEncoding
def moveComponentTop(self):
componentList = self.window.listWidget_componentList
row = -componentList.currentRow()
self.moveComponent(row)
+ @disableWhenEncoding
def moveComponentBottom(self):
componentList = self.window.listWidget_componentList
row = len(componentList)-componentList.currentRow()-1
self.moveComponent(row)
+ @disableWhenEncoding
def dragComponent(self, event):
'''Drop event for the component listwidget'''
componentList = self.window.listWidget_componentList
@@ -739,6 +741,7 @@ class MainWindow(QtWidgets.QMainWindow):
return True
return False
+ @disableWhenEncoding
def componentContextMenu(self, QPos):
'''Appears when right-clicking a component in the list'''
componentList = self.window.listWidget_componentList
--
cgit v1.2.3
From 0c394d77e388adb91beee210a9b66652db9d17cb Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 25 Jun 2017 23:05:44 -0400
Subject: an extra progress bar label for Mac
progressBar text is not visible in native Mac style
---
src/mainwindow.py | 17 +++++++++++++++--
src/mainwindow.ui | 18 +++++++++++++++++-
2 files changed, 32 insertions(+), 3 deletions(-)
diff --git a/src/mainwindow.py b/src/mainwindow.py
index 42d2a50..a406a7d 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -13,7 +13,6 @@ import video_thread
from presetmanager import PresetManager
from main import LoadDefaultSettings, disableWhenEncoding
-
class PreviewWindow(QtWidgets.QLabel):
def __init__(self, parent, img):
super(PreviewWindow, self).__init__()
@@ -88,6 +87,11 @@ class MainWindow(QtWidgets.QMainWindow):
# Begin decorating the window and connecting events
componentList = self.window.listWidget_componentList
+ if sys.platform == 'darwin':
+ window.progressBar_createVideo.setTextVisible(False)
+ else:
+ window.progressLabel.setHidden(True)
+
window.toolButton_selectAudioFile.clicked.connect(
self.openInputFileDialog)
@@ -487,6 +491,10 @@ class MainWindow(QtWidgets.QMainWindow):
self.window.pushButton_listMoveUp.setEnabled(False)
self.window.menuButton_newProject.setEnabled(False)
self.window.menuButton_openProject.setEnabled(False)
+ if sys.platform == 'darwin':
+ self.window.progressLabel.setHidden(False)
+ else:
+ self.window.listWidget_componentList.setEnabled(False)
else:
self.window.pushButton_createVideo.setEnabled(True)
self.window.pushButton_Cancel.setEnabled(False)
@@ -505,13 +513,18 @@ class MainWindow(QtWidgets.QMainWindow):
self.window.pushButton_listMoveUp.setEnabled(True)
self.window.menuButton_newProject.setEnabled(True)
self.window.menuButton_openProject.setEnabled(True)
+ self.window.listWidget_componentList.setEnabled(True)
+ self.window.progressLabel.setHidden(True)
self.drawPreview(True)
def progressBarUpdated(self, value):
self.window.progressBar_createVideo.setValue(value)
def progressBarSetText(self, value):
- self.window.progressBar_createVideo.setFormat(value)
+ if sys.platform == 'darwin':
+ self.window.progressLabel.setText(value)
+ else:
+ self.window.progressBar_createVideo.setFormat(value)
def videoCreated(self):
self.videoThread.quit()
diff --git a/src/mainwindow.ui b/src/mainwindow.ui
index 4a12fd5..b491323 100644
--- a/src/mainwindow.ui
+++ b/src/mainwindow.ui
@@ -518,9 +518,26 @@
+ -
+
+
+
+
+
+ true
+
+
+ Qt::AlignCenter
+
+
+ -1
+
+
+
+ progressLabel
@@ -802,7 +819,6 @@
-
--
cgit v1.2.3
From a95ecd7e42b3e6b199f7bcdbe363faa8e765f869 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Mon, 26 Jun 2017 19:07:49 -0400
Subject: added visualizer options + invalid presets get ignored
---
src/components/__base__.py | 1 +
src/components/original.py | 81 ++++++++++++++++++++++++++++-------------
src/components/original.ui | 89 ++++++++++++++++++++++++++++++++++++++++++++--
src/mainwindow.py | 1 +
src/presetmanager.py | 2 ++
5 files changed, 148 insertions(+), 26 deletions(-)
diff --git a/src/components/__base__.py b/src/components/__base__.py
index 9b04157..00601e7 100644
--- a/src/components/__base__.py
+++ b/src/components/__base__.py
@@ -11,6 +11,7 @@ class Component(QtCore.QObject):
def __init__(self, moduleIndex, compPos, core):
super().__init__()
self.currentPreset = None
+ self.canceled = False
self.moduleIndex = moduleIndex
self.compPos = compPos
self.core = core
diff --git a/src/components/original.py b/src/components/original.py
index 8450aa1..1aa72c9 100644
--- a/src/components/original.py
+++ b/src/components/original.py
@@ -20,11 +20,15 @@ class Component(__base__.Component):
def widget(self, parent):
self.parent = parent
self.visColor = (255, 255, 255)
+ self.scale = 20
+ self.y = 0
+ self.canceled = False
page = self.loadUi('original.ui')
page.comboBox_visLayout.addItem("Classic")
page.comboBox_visLayout.addItem("Split")
page.comboBox_visLayout.addItem("Bottom")
+ page.comboBox_visLayout.addItem("Top")
page.comboBox_visLayout.setCurrentIndex(0)
page.comboBox_visLayout.currentIndexChanged.connect(self.update)
page.lineEdit_visColor.setText('%s,%s,%s' % self.visColor)
@@ -33,13 +37,17 @@ class Component(__base__.Component):
% QColor(*self.visColor).name()
page.pushButton_visColor.setStyleSheet(btnStyle)
page.lineEdit_visColor.textChanged.connect(self.update)
+ page.spinBox_scale.valueChanged.connect(self.update)
+ page.spinBox_y.valueChanged.connect(self.update)
+
self.page = page
- self.canceled = False
return page
def update(self):
self.layout = self.page.comboBox_visLayout.currentIndex()
self.visColor = self.RGBFromString(self.page.lineEdit_visColor.text())
+ self.scale = self.page.spinBox_scale.value()
+ self.y = self.page.spinBox_y.value()
self.parent.drawPreview()
super().update()
@@ -51,21 +59,26 @@ class Component(__base__.Component):
% QColor(*pr['visColor']).name()
self.page.pushButton_visColor.setStyleSheet(btnStyle)
self.page.comboBox_visLayout.setCurrentIndex(pr['layout'])
+ self.page.spinBox_scale.setValue(pr['scale'])
+ self.page.spinBox_y.setValue(pr['y'])
def savePreset(self):
return {
'preset': self.currentPreset,
'layout': self.layout,
'visColor': self.visColor,
+ 'scale': self.scale,
+ 'y': self.y,
}
def previewRender(self, previewWorker):
spectrum = numpy.fromfunction(
- lambda x: 0.008*(x-128)**2, (255,), dtype="int16")
+ lambda x: float(self.scale)/2500*(x-128)**2, (255,), dtype="int16")
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
return self.drawBars(
- width, height, spectrum, self.visColor, self.layout)
+ width, height, spectrum, self.visColor, self.layout
+ )
def preFrameRender(self, **kwargs):
super().preFrameRender(**kwargs)
@@ -125,7 +138,7 @@ class Component(__base__.Component):
# filter the noise away
# y[y<80] = 0
- y = 20 * numpy.log10(y)
+ y = self.scale * numpy.log10(y)
y[numpy.isinf(y)] = 0
if lastSpectrum is not None:
@@ -168,40 +181,60 @@ class Component(__base__.Component):
im = self.blankFrame(width, height)
- if layout == 0:
- y = 0 - int(height/100*43)
+ if layout == 0: # Classic
+ y = self.y - int(height/100*43)
im.paste(imTop, (0, y), mask=imTop)
- y = 0 + int(height/100*43)
+ y = self.y + int(height/100*43)
im.paste(imBottom, (0, y), mask=imBottom)
- if layout == 1:
- y = 0 + int(height/100*10)
+ if layout == 1: # Split
+ y = self.y + int(height/100*10)
im.paste(imTop, (0, y), mask=imTop)
- y = 0 - int(height/100*10)
+ y = self.y - int(height/100*10)
im.paste(imBottom, (0, y), mask=imBottom)
- if layout == 2:
- y = 0 + int(height/100*10)
+ if layout == 2: # Bottom
+ y = self.y + int(height/100*10)
im.paste(imTop, (0, y), mask=imTop)
+ if layout == 3: # Top
+ y = self.y - int(height/100*10)
+ im.paste(imBottom, (0, y), mask=imBottom)
+
return im
def command(self, arg):
if not arg.startswith('preset=') and '=' in arg:
key, arg = arg.split('=', 1)
- if key == 'color':
- self.page.lineEdit_visColor.setText(arg)
- return
- elif key == 'layout':
- if arg == 'classic':
- self.page.comboBox_visLayout.setCurrentIndex(0)
- elif arg == 'split':
- self.page.comboBox_visLayout.setCurrentIndex(1)
- elif arg == 'bottom':
- self.page.comboBox_visLayout.setCurrentIndex(2)
- return
+ try:
+ if key == 'color':
+ self.page.lineEdit_visColor.setText(arg)
+ return
+ elif key == 'layout':
+ if arg == 'classic':
+ self.page.comboBox_visLayout.setCurrentIndex(0)
+ elif arg == 'split':
+ self.page.comboBox_visLayout.setCurrentIndex(1)
+ elif arg == 'bottom':
+ self.page.comboBox_visLayout.setCurrentIndex(2)
+ elif arg == 'top':
+ self.page.comboBox_visLayout.setCurrentIndex(3)
+ return
+ elif key == 'scale':
+ arg = int(arg)
+ self.page.spinBox_scale.setValue(arg)
+ return
+ elif key == 'y':
+ arg = int(arg)
+ self.page.spinBox_y.setValue(arg)
+ return
+ except ValueError:
+ print('You must enter a number.')
+ quit(1)
super().command(arg)
def commandHelp(self):
- print('Give a layout name:\n layout=[classic/split/bottom]')
+ print('Give a layout name:\n layout=[classic/split/bottom/top]')
print('Specify a color:\n color=255,255,255')
+ print('Visualizer scale (20 is default):\n scale=number')
+ print('Y position:\n y=number')
diff --git a/src/components/original.ui b/src/components/original.ui
index 5808653..8fa9b2b 100644
--- a/src/components/original.ui
+++ b/src/components/original.ui
@@ -34,7 +34,7 @@
- Visualizer Layout
+ Layout
@@ -57,10 +57,46 @@
+ -
+
+
+ Scale
+
+
+
+ -
+
+
+ QAbstractSpinBox::PlusMinus
+
+
+ 1
+
+
+ 20
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
-
- Visualizer Color
+ Color
@@ -88,6 +124,55 @@
+ -
+
+
+ 4
+
+
-
+
+
+ Y
+
+
+
+ -
+
+
+ QAbstractSpinBox::UpDownArrows
+
+
+ -5000
+
+
+ 5000
+
+
+ 10
+
+
+ 0
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Expanding
+
+
+
+ 5
+ 20
+
+
+
+
+
+
-
diff --git a/src/mainwindow.py b/src/mainwindow.py
index a406a7d..5068108 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -13,6 +13,7 @@ import video_thread
from presetmanager import PresetManager
from main import LoadDefaultSettings, disableWhenEncoding
+
class PreviewWindow(QtWidgets.QLabel):
def __init__(self, parent, img):
super(PreviewWindow, self).__init__()
diff --git a/src/presetmanager.py b/src/presetmanager.py
index 300b534..68679ec 100644
--- a/src/presetmanager.py
+++ b/src/presetmanager.py
@@ -79,6 +79,8 @@ class PresetManager(QtWidgets.QDialog):
continue
for preset in filenames:
compName = os.path.basename(os.path.dirname(dirpath))
+ if compName not in self.core.compNames:
+ continue
compVers = os.path.basename(dirpath)
try:
parseList.append((compName, int(compVers), preset))
--
cgit v1.2.3
From 0a9002b42cbfa51fb826868a215202c83c97c330 Mon Sep 17 00:00:00 2001
From: DH4
Date: Sat, 1 Jul 2017 00:01:29 -0500
Subject: Fixed Text Colors & update button color on manual value change.
---
src/components/text.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/components/text.py b/src/components/text.py
index 7f4659f..96421e6 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -65,6 +65,9 @@ class Component(__base__.Component):
self.yPosition = self.page.spinBox_yTextAlign.value()
self.textColor = self.RGBFromString(
self.page.lineEdit_textColor.text())
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" \
+ % QColor(*self.textColor).name()
+ self.page.pushButton_textColor.setStyleSheet(btnStyle)
self.parent.drawPreview()
super().update()
@@ -133,7 +136,7 @@ class Component(__base__.Component):
painter = QPainter(image)
self.titleFont.setPixelSize(self.fontSize)
painter.setFont(self.titleFont)
- painter.setPen(QColor(*self.textColor))
+ painter.setPen(QColor(*self.textColor[::-1]))
painter.drawText(x, y, self.title)
painter.end()
--
cgit v1.2.3
From 38557f29f91b8abc68ec3408ce466ee8a5da815e Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 2 Jul 2017 14:19:15 -0400
Subject: rm unneeded imports, work on freezing
---
.gitignore | 7 ++++++-
freeze.py | 35 +++++++++++++++++++++--------------
src/components/__base__.py | 2 +-
src/components/text.py | 7 +++++--
src/core.py | 26 +++++++++++++++++---------
src/main.py | 2 +-
6 files changed, 51 insertions(+), 28 deletions(-)
diff --git a/.gitignore b/.gitignore
index 0316a98..68dffc7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,11 @@
__pycache__
-settings.ini
build/*
+env/*
.vscode/*
*.mkv
*.mp4
+*.zip
+*.tar
+*.tar.*
+*.exe
+ffmpeg
diff --git a/freeze.py b/freeze.py
index 48034dc..a81f325 100644
--- a/freeze.py
+++ b/freeze.py
@@ -1,11 +1,14 @@
from cx_Freeze import setup, Executable
import sys
+import os
# Dependencies are automatically detected, but it might need
# fine tuning.
+deps = [os.path.join('src', p) for p in os.listdir('src') if p]
+deps.append('ffmpeg.exe' if sys.platform == 'win32' else 'ffmpeg')
+
buildOptions = dict(
- packages=[],
excludes=[
"apport",
"apt",
@@ -17,17 +20,21 @@ buildOptions = dict(
"xmlrpc",
"nose"
],
- include_files=[
- "mainwindow.ui",
- "presetmanager.ui",
- "background.png",
- "encoder-options.json",
- "components/"
- ],
includes=[
- 'numpy.core._methods',
- 'numpy.lib.format'
- ]
+ "encodings",
+ "json",
+ "filecmp",
+ "numpy.core._methods",
+ "numpy.lib.format",
+ "PyQt5.QtCore",
+ "PyQt5.QtGui",
+ "PyQt5.QtWidgets",
+ "PyQt5.uic",
+ "PIL.Image",
+ "PIL.ImageQt",
+ "PIL.ImageDraw",
+ ],
+ include_files=deps,
)
@@ -35,16 +42,16 @@ base = 'Win32GUI' if sys.platform == 'win32' else None
executables = [
Executable(
- 'main.py',
+ 'src/main.py',
base=base,
targetName='audio-visualizer-python'
- )
+ ),
]
setup(
name='audio-visualizer-python',
- version='1.0',
+ version='2.0',
description='GUI tool to render visualization videos of audio files',
options=dict(build_exe=buildOptions),
executables=executables
diff --git a/src/components/__base__.py b/src/components/__base__.py
index 00601e7..b5e7d93 100644
--- a/src/components/__base__.py
+++ b/src/components/__base__.py
@@ -1,4 +1,4 @@
-from PyQt5 import uic, QtGui, QtCore, QtWidgets
+from PyQt5 import uic, QtCore, QtWidgets
from PIL import Image
import os
diff --git a/src/components/text.py b/src/components/text.py
index 96421e6..6c5c4eb 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -3,7 +3,7 @@ from PyQt5.QtGui import QPainter, QColor, QFont
from PyQt5 import QtGui, QtCore, QtWidgets
from PIL.ImageQt import ImageQt
import os
-import io
+import sys
from . import __base__
@@ -136,7 +136,10 @@ class Component(__base__.Component):
painter = QPainter(image)
self.titleFont.setPixelSize(self.fontSize)
painter.setFont(self.titleFont)
- painter.setPen(QColor(*self.textColor[::-1]))
+ if sys.byteorder == 'big':
+ painter.setPen(QColor(*self.textColor))
+ else:
+ painter.setPen(QColor(*self.textColor[::-1]))
painter.drawText(x, y, self.title)
painter.end()
diff --git a/src/core.py b/src/core.py
index b3c5640..3fa67db 100644
--- a/src/core.py
+++ b/src/core.py
@@ -17,7 +17,6 @@ import string
class Core():
def __init__(self):
- self.FFMPEG_BIN = self.findFfmpeg()
self.dataDir = QStandardPaths.writableLocation(
QStandardPaths.AppConfigLocation
)
@@ -63,6 +62,7 @@ class Core():
'*.xpm',
])
+ self.FFMPEG_BIN = self.findFfmpeg()
self.findComponents()
self.selectedComponents = []
# copies of named presets to detect modification
@@ -437,15 +437,23 @@ class Core():
self.encoder_options = json.load(json_file)
def findFfmpeg(self):
- if sys.platform == "win32":
- return "ffmpeg.exe"
+ if getattr(sys, 'frozen', False):
+ # The application is frozen
+ if sys.platform == "win32":
+ return os.path.join(self.wd, 'ffmpeg.exe')
+ else:
+ return os.path.join(self.wd, 'ffmpeg')
+
else:
- try:
- with open(os.devnull, "w") as f:
- sp.check_call(['ffmpeg', '-version'], stdout=f, stderr=f)
- return "ffmpeg"
- except:
- return "avconv"
+ if sys.platform == "win32":
+ return "ffmpeg.exe"
+ else:
+ try:
+ with open(os.devnull, "w") as f:
+ sp.check_call(['ffmpeg', '-version'], stdout=f, stderr=f)
+ return "ffmpeg"
+ except:
+ return "avconv"
def readAudioFile(self, filename, parent):
command = [self.FFMPEG_BIN, '-i', filename]
diff --git a/src/main.py b/src/main.py
index fd32b13..bae9adf 100644
--- a/src/main.py
+++ b/src/main.py
@@ -1,4 +1,4 @@
-from PyQt5 import QtGui, uic, QtWidgets
+from PyQt5 import uic, QtWidgets
import sys
import os
--
cgit v1.2.3
From 0da275bf1b1dd2c956fed9d4a1051dcf3365c382 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 2 Jul 2017 20:46:48 -0400
Subject: renamed component base
---
src/component.py | 163 +++++++++++++++++++++++++++++++++++++++++++++
src/components/__base__.py | 163 ---------------------------------------------
src/components/color.py | 5 +-
src/components/image.py | 5 +-
src/components/original.py | 5 +-
src/components/text.py | 5 +-
src/components/video.py | 7 +-
7 files changed, 179 insertions(+), 174 deletions(-)
create mode 100644 src/component.py
delete mode 100644 src/components/__base__.py
diff --git a/src/component.py b/src/component.py
new file mode 100644
index 0000000..b5e7d93
--- /dev/null
+++ b/src/component.py
@@ -0,0 +1,163 @@
+from PyQt5 import uic, QtCore, QtWidgets
+from PIL import Image
+import os
+
+
+class Component(QtCore.QObject):
+ '''A base class for components to inherit from'''
+
+ # modified = QtCore.pyqtSignal(int, bool)
+
+ def __init__(self, moduleIndex, compPos, core):
+ super().__init__()
+ self.currentPreset = None
+ self.canceled = False
+ self.moduleIndex = moduleIndex
+ self.compPos = compPos
+ self.core = core
+
+ def __str__(self):
+ return self.__doc__
+
+ def version(self):
+ # change this number to identify new versions of a component
+ return 1
+
+ def cancel(self):
+ # please stop any lengthy process in response to this variable
+ self.canceled = True
+
+ def reset(self):
+ self.canceled = False
+
+ def update(self):
+ self.modified.emit(self.compPos, self.savePreset())
+ # read your widget values, then call super().update()
+
+ def loadPreset(self, presetDict, presetName):
+ '''Subclasses take (presetDict, presetName=None) as args.
+ Must use super().loadPreset(presetDict, presetName) first,
+ then update self.page widgets using the preset dict.
+ '''
+ self.currentPreset = presetName \
+ if presetName is not None else presetDict['preset']
+
+ def preFrameRender(self, **kwargs):
+ '''Triggered only before a video is exported (video_thread.py)
+ self.worker = the video thread worker
+ self.completeAudioArray = a list of audio samples
+ self.sampleSize = number of audio samples per video frame
+ self.progressBarUpdate = signal to set progress bar number
+ self.progressBarSetText = signal to set progress bar text
+ Use the latter two signals to update the MainProgram if needed
+ for a long initialization procedure (i.e., for a visualizer)
+ '''
+ for var, value in kwargs.items():
+ exec('self.%s = value' % var)
+
+ def command(self, arg):
+ '''Configure a component using argument from the commandline.
+ Use super().command(arg) at the end of a subclass's method,
+ if no arguments are found in that method first
+ '''
+ if arg.startswith('preset='):
+ _, preset = arg.split('=', 1)
+ path = os.path.join(self.core.getPresetDir(self), preset)
+ if not os.path.exists(path):
+ print('Couldn\'t locate preset "%s"' % preset)
+ quit(1)
+ else:
+ print('Opening "%s" preset on layer %s' % (
+ preset, self.compPos)
+ )
+ self.core.openPreset(path, self.compPos, preset)
+ else:
+ print(
+ self.__doc__, 'Usage:\n'
+ 'Open a preset for this component:\n'
+ ' "preset=Preset Name"')
+ self.commandHelp()
+ quit(0)
+
+ def commandHelp(self):
+ '''Print help text for this Component's commandline arguments'''
+
+ def blankFrame(self, width, height):
+ return Image.new("RGBA", (width, height), (0, 0, 0, 0))
+
+ def pickColor(self):
+ '''Use color picker to get color input from the user,
+ and return this as an RGB string and QPushButton stylesheet.
+ In a subclass apply stylesheet to any color selection widgets
+ '''
+ dialog = QtWidgets.QColorDialog()
+ dialog.setOption(QtWidgets.QColorDialog.ShowAlphaChannel, True)
+ color = dialog.getColor()
+ if color.isValid():
+ RGBstring = '%s,%s,%s' % (
+ str(color.red()), str(color.green()), str(color.blue()))
+ btnStyle = "QPushButton{background-color: %s; outline: none;}" \
+ % color.name()
+ return RGBstring, btnStyle
+ else:
+ return None, None
+
+ def RGBFromString(self, string):
+ ''' Turns an RGB string like "255, 255, 255" into a tuple '''
+ try:
+ tup = tuple([int(i) for i in string.split(',')])
+ if len(tup) != 3:
+ raise ValueError
+ for i in tup:
+ if i > 255 or i < 0:
+ raise ValueError
+ return tup
+ except:
+ return (255, 255, 255)
+
+ def loadUi(self, filename):
+ return uic.loadUi(os.path.join(self.core.componentsPath, filename))
+
+ '''
+ ### Reference methods for creating a new component
+ ### (Inherit from this class and define these)
+
+ def widget(self, parent):
+ self.parent = parent
+ page = uic.loadUi(os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), 'example.ui'))
+ # --- connect widget signals here ---
+ self.page = page
+ return page
+
+ def update(self):
+ super().update()
+ self.parent.drawPreview()
+
+ def previewRender(self, previewWorker):
+ width = int(previewWorker.core.settings.value('outputWidth'))
+ height = int(previewWorker.core.settings.value('outputHeight'))
+ image = Image.new("RGBA", (width, height), (0,0,0,0))
+ return image
+
+ def frameRender(self, moduleNo, frameNo):
+ width = int(self.worker.core.settings.value('outputWidth'))
+ height = int(self.worker.core.settings.value('outputHeight'))
+ image = Image.new("RGBA", (width, height), (0,0,0,0))
+ return image
+
+ @classmethod
+ def names(cls):
+ # Alternative names for renaming a component between project files
+ return []
+ '''
+
+
+class BadComponentInit(Exception):
+ def __init__(self, arg, name):
+ string = '''################################
+Mandatory argument "%s" not specified
+ in %s instance initialization
+###################################'''
+ print(string % (arg, name))
+ quit()
diff --git a/src/components/__base__.py b/src/components/__base__.py
deleted file mode 100644
index b5e7d93..0000000
--- a/src/components/__base__.py
+++ /dev/null
@@ -1,163 +0,0 @@
-from PyQt5 import uic, QtCore, QtWidgets
-from PIL import Image
-import os
-
-
-class Component(QtCore.QObject):
- '''A base class for components to inherit from'''
-
- # modified = QtCore.pyqtSignal(int, bool)
-
- def __init__(self, moduleIndex, compPos, core):
- super().__init__()
- self.currentPreset = None
- self.canceled = False
- self.moduleIndex = moduleIndex
- self.compPos = compPos
- self.core = core
-
- def __str__(self):
- return self.__doc__
-
- def version(self):
- # change this number to identify new versions of a component
- return 1
-
- def cancel(self):
- # please stop any lengthy process in response to this variable
- self.canceled = True
-
- def reset(self):
- self.canceled = False
-
- def update(self):
- self.modified.emit(self.compPos, self.savePreset())
- # read your widget values, then call super().update()
-
- def loadPreset(self, presetDict, presetName):
- '''Subclasses take (presetDict, presetName=None) as args.
- Must use super().loadPreset(presetDict, presetName) first,
- then update self.page widgets using the preset dict.
- '''
- self.currentPreset = presetName \
- if presetName is not None else presetDict['preset']
-
- def preFrameRender(self, **kwargs):
- '''Triggered only before a video is exported (video_thread.py)
- self.worker = the video thread worker
- self.completeAudioArray = a list of audio samples
- self.sampleSize = number of audio samples per video frame
- self.progressBarUpdate = signal to set progress bar number
- self.progressBarSetText = signal to set progress bar text
- Use the latter two signals to update the MainProgram if needed
- for a long initialization procedure (i.e., for a visualizer)
- '''
- for var, value in kwargs.items():
- exec('self.%s = value' % var)
-
- def command(self, arg):
- '''Configure a component using argument from the commandline.
- Use super().command(arg) at the end of a subclass's method,
- if no arguments are found in that method first
- '''
- if arg.startswith('preset='):
- _, preset = arg.split('=', 1)
- path = os.path.join(self.core.getPresetDir(self), preset)
- if not os.path.exists(path):
- print('Couldn\'t locate preset "%s"' % preset)
- quit(1)
- else:
- print('Opening "%s" preset on layer %s' % (
- preset, self.compPos)
- )
- self.core.openPreset(path, self.compPos, preset)
- else:
- print(
- self.__doc__, 'Usage:\n'
- 'Open a preset for this component:\n'
- ' "preset=Preset Name"')
- self.commandHelp()
- quit(0)
-
- def commandHelp(self):
- '''Print help text for this Component's commandline arguments'''
-
- def blankFrame(self, width, height):
- return Image.new("RGBA", (width, height), (0, 0, 0, 0))
-
- def pickColor(self):
- '''Use color picker to get color input from the user,
- and return this as an RGB string and QPushButton stylesheet.
- In a subclass apply stylesheet to any color selection widgets
- '''
- dialog = QtWidgets.QColorDialog()
- dialog.setOption(QtWidgets.QColorDialog.ShowAlphaChannel, True)
- color = dialog.getColor()
- if color.isValid():
- RGBstring = '%s,%s,%s' % (
- str(color.red()), str(color.green()), str(color.blue()))
- btnStyle = "QPushButton{background-color: %s; outline: none;}" \
- % color.name()
- return RGBstring, btnStyle
- else:
- return None, None
-
- def RGBFromString(self, string):
- ''' Turns an RGB string like "255, 255, 255" into a tuple '''
- try:
- tup = tuple([int(i) for i in string.split(',')])
- if len(tup) != 3:
- raise ValueError
- for i in tup:
- if i > 255 or i < 0:
- raise ValueError
- return tup
- except:
- return (255, 255, 255)
-
- def loadUi(self, filename):
- return uic.loadUi(os.path.join(self.core.componentsPath, filename))
-
- '''
- ### Reference methods for creating a new component
- ### (Inherit from this class and define these)
-
- def widget(self, parent):
- self.parent = parent
- page = uic.loadUi(os.path.join(
- os.path.dirname(os.path.realpath(__file__)), 'example.ui'))
- # --- connect widget signals here ---
- self.page = page
- return page
-
- def update(self):
- super().update()
- self.parent.drawPreview()
-
- def previewRender(self, previewWorker):
- width = int(previewWorker.core.settings.value('outputWidth'))
- height = int(previewWorker.core.settings.value('outputHeight'))
- image = Image.new("RGBA", (width, height), (0,0,0,0))
- return image
-
- def frameRender(self, moduleNo, frameNo):
- width = int(self.worker.core.settings.value('outputWidth'))
- height = int(self.worker.core.settings.value('outputHeight'))
- image = Image.new("RGBA", (width, height), (0,0,0,0))
- return image
-
- @classmethod
- def names(cls):
- # Alternative names for renaming a component between project files
- return []
- '''
-
-
-class BadComponentInit(Exception):
- def __init__(self, arg, name):
- string = '''################################
-Mandatory argument "%s" not specified
- in %s instance initialization
-###################################'''
- print(string % (arg, name))
- quit()
diff --git a/src/components/color.py b/src/components/color.py
index 8a994db..bd45951 100644
--- a/src/components/color.py
+++ b/src/components/color.py
@@ -3,10 +3,11 @@ from PyQt5 import QtGui, QtCore, QtWidgets
from PyQt5.QtGui import QColor
from PIL.ImageQt import ImageQt
import os
-from . import __base__
+from component import Component
-class Component(__base__.Component):
+
+class Component(Component):
'''Color'''
modified = QtCore.pyqtSignal(int, dict)
diff --git a/src/components/image.py b/src/components/image.py
index 4bb33b1..ba99113 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -1,10 +1,11 @@
from PIL import Image, ImageDraw
from PyQt5 import QtGui, QtCore, QtWidgets
import os
-from . import __base__
+from component import Component
-class Component(__base__.Component):
+
+class Component(Component):
'''Image'''
modified = QtCore.pyqtSignal(int, dict)
diff --git a/src/components/original.py b/src/components/original.py
index 1aa72c9..42049f3 100644
--- a/src/components/original.py
+++ b/src/components/original.py
@@ -3,12 +3,13 @@ from PIL import Image, ImageDraw
from PyQt5 import QtGui, QtCore, QtWidgets
from PyQt5.QtGui import QColor
import os
-from . import __base__
import time
from copy import copy
+from component import Component
-class Component(__base__.Component):
+
+class Component(Component):
'''Classic Visualizer'''
modified = QtCore.pyqtSignal(int, dict)
diff --git a/src/components/text.py b/src/components/text.py
index 6c5c4eb..6be3120 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -4,10 +4,11 @@ from PyQt5 import QtGui, QtCore, QtWidgets
from PIL.ImageQt import ImageQt
import os
import sys
-from . import __base__
+from component import Component
-class Component(__base__.Component):
+
+class Component(Component):
'''Title Text'''
modified = QtCore.pyqtSignal(int, dict)
diff --git a/src/components/video.py b/src/components/video.py
index 02bb44b..c5649c5 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -5,7 +5,8 @@ import math
import subprocess
import threading
from queue import PriorityQueue
-from . import __base__
+
+from component import Component, BadComponentInit
class Video:
@@ -26,7 +27,7 @@ class Video:
try:
exec('self.%s = kwargs[arg]' % arg)
except KeyError:
- raise __base__.BadComponentInit(arg, self.__doc__)
+ raise BadComponentInit(arg, self.__doc__)
self.frameNo = -1
self.currentFrame = 'None'
@@ -102,7 +103,7 @@ class Video:
self.lastFrame = self.currentFrame
-class Component(__base__.Component):
+class Component(Component):
'''Video'''
modified = QtCore.pyqtSignal(int, dict)
--
cgit v1.2.3
From 3a6d7ae421ad2b650cac7f17d43be313787f0e61 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 2 Jul 2017 21:38:19 -0400
Subject: frame-drawing tools for components to share
---
src/component.py | 10 ++++------
src/components/color.py | 27 +++++++++++++--------------
src/components/image.py | 3 ++-
src/components/original.py | 5 +++--
src/components/text.py | 23 +++++++----------------
src/components/video.py | 9 +++++----
src/frame.py | 42 ++++++++++++++++++++++++++++++++++++++++++
7 files changed, 76 insertions(+), 43 deletions(-)
create mode 100644 src/frame.py
diff --git a/src/component.py b/src/component.py
index b5e7d93..6637eac 100644
--- a/src/component.py
+++ b/src/component.py
@@ -1,11 +1,12 @@
+'''
+ Base classes for components to import.
+'''
from PyQt5 import uic, QtCore, QtWidgets
-from PIL import Image
import os
class Component(QtCore.QObject):
- '''A base class for components to inherit from'''
-
+ ''' A class for components to inherit.'''
# modified = QtCore.pyqtSignal(int, bool)
def __init__(self, moduleIndex, compPos, core):
@@ -82,9 +83,6 @@ class Component(QtCore.QObject):
def commandHelp(self):
'''Print help text for this Component's commandline arguments'''
- def blankFrame(self, width, height):
- return Image.new("RGBA", (width, height), (0, 0, 0, 0))
-
def pickColor(self):
'''Use color picker to get color input from the user,
and return this as an RGB string and QPushButton stylesheet.
diff --git a/src/components/color.py b/src/components/color.py
index bd45951..4a10263 100644
--- a/src/components/color.py
+++ b/src/components/color.py
@@ -5,6 +5,7 @@ from PIL.ImageQt import ImageQt
import os
from component import Component
+from frame import BlankFrame, FloodFrame, FramePainter, PaintColor
class Component(Component):
@@ -128,20 +129,19 @@ class Component(Component):
# in default state, skip all this logic and return a plain fill
if self.fillType == 0 and shapeSize == (width, height) \
and self.x == 0 and self.y == 0:
- return Image.new("RGBA", (width, height), (r, g, b, 255))
-
- frame = self.blankFrame(width, height)
+ return FloodFrame(width, height, (r, g, b, 255))
# Return a solid image at x, y
if self.fillType == 0:
+ frame = BlankFrame(width, height)
image = Image.new("RGBA", shapeSize, (r, g, b, 255))
frame.paste(image, box=(self.x, self.y))
return frame
# Now fills that require using Qt...
elif self.fillType > 0:
- image = ImageQt(frame)
- painter = QtGui.QPainter(image)
+ image = FramePainter(width, height)
+
if self.stretch:
w = width
h = height
@@ -164,21 +164,20 @@ class Component(Component):
self.RG_centre)
brush.setSpread(self.spread)
- brush.setColorAt(0.0, QColor(*self.color1))
+ brush.setColorAt(0.0, PaintColor(*self.color1))
if self.trans:
- brush.setColorAt(1.0, QColor(0, 0, 0, 0))
+ brush.setColorAt(1.0, PaintColor(0, 0, 0, 0))
elif self.fillType == 1 and self.stretch:
- brush.setColorAt(0.2, QColor(*self.color2))
+ brush.setColorAt(0.2, PaintColor(*self.color2))
else:
- brush.setColorAt(1.0, QColor(*self.color2))
- painter.setBrush(brush)
- painter.drawRect(
+ brush.setColorAt(1.0, PaintColor(*self.color2))
+ image.setBrush(brush)
+ image.drawRect(
self.x, self.y,
self.sizeWidth, self.sizeHeight
)
- painter.end()
- imBytes = image.bits().asstring(image.byteCount())
- return Image.frombytes('RGBA', (width, height), imBytes)
+
+ return image.finalize()
def loadPreset(self, pr, presetName=None):
super().loadPreset(pr, presetName)
diff --git a/src/components/image.py b/src/components/image.py
index ba99113..1aae51b 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -3,6 +3,7 @@ from PyQt5 import QtGui, QtCore, QtWidgets
import os
from component import Component
+from frame import BlankFrame
class Component(Component):
@@ -53,7 +54,7 @@ class Component(Component):
return self.drawFrame(width, height)
def drawFrame(self, width, height):
- frame = self.blankFrame(width, height)
+ frame = BlankFrame(width, height)
if self.imagePath and os.path.exists(self.imagePath):
image = Image.open(self.imagePath)
if self.stretched and image.size != (width, height):
diff --git a/src/components/original.py b/src/components/original.py
index 42049f3..82cdc1d 100644
--- a/src/components/original.py
+++ b/src/components/original.py
@@ -7,6 +7,7 @@ import time
from copy import copy
from component import Component
+from frame import BlankFrame
class Component(Component):
@@ -162,7 +163,7 @@ class Component(Component):
bF = width / 64
bH = bF / 2
bQ = bF / 4
- imTop = self.blankFrame(width, height)
+ imTop = BlankFrame(width, height)
draw = ImageDraw.Draw(imTop)
r, g, b = color
color2 = (r, g, b, 125)
@@ -180,7 +181,7 @@ class Component(Component):
imBottom = imTop.transpose(Image.FLIP_TOP_BOTTOM)
- im = self.blankFrame(width, height)
+ im = BlankFrame(width, height)
if layout == 0: # Classic
y = self.y - int(height/100*43)
diff --git a/src/components/text.py b/src/components/text.py
index 6be3120..97d7d07 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -1,11 +1,10 @@
from PIL import Image, ImageDraw
-from PyQt5.QtGui import QPainter, QColor, QFont
+from PyQt5.QtGui import QColor, QFont
from PyQt5 import QtGui, QtCore, QtWidgets
-from PIL.ImageQt import ImageQt
import os
-import sys
from component import Component
+from frame import FramePainter
class Component(Component):
@@ -131,22 +130,14 @@ class Component(Component):
def addText(self, width, height):
x, y = self.getXY()
- im = self.blankFrame(width, height)
- image = ImageQt(im)
+ image = FramePainter(width, height)
- painter = QPainter(image)
self.titleFont.setPixelSize(self.fontSize)
- painter.setFont(self.titleFont)
- if sys.byteorder == 'big':
- painter.setPen(QColor(*self.textColor))
- else:
- painter.setPen(QColor(*self.textColor[::-1]))
- painter.drawText(x, y, self.title)
- painter.end()
+ image.setFont(self.titleFont)
+ image.setPen(self.textColor)
+ image.drawText(x, y, self.title)
- imBytes = image.bits().asstring(image.byteCount())
-
- return Image.frombytes('RGBA', (width, height), imBytes)
+ return image.finalize()
def pickColor(self):
RGBstring, btnStyle = super().pickColor()
diff --git a/src/components/video.py b/src/components/video.py
index c5649c5..175cf29 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -7,6 +7,7 @@ import threading
from queue import PriorityQueue
from component import Component, BadComponentInit
+from frame import BlankFrame
class Video:
@@ -145,7 +146,7 @@ class Component(Component):
self.updateChunksize(width, height)
frame = self.getPreviewFrame(width, height)
if not frame:
- return self.blankFrame(width, height)
+ return BlankFrame(width, height)
else:
return frame
@@ -153,7 +154,7 @@ class Component(Component):
super().preFrameRender(**kwargs)
width = int(self.worker.core.settings.value('outputWidth'))
height = int(self.worker.core.settings.value('outputHeight'))
- self.blankFrame_ = self.blankFrame(width, height)
+ self.blankFrame_ = BlankFrame(width, height)
self.updateChunksize(width, height)
self.video = Video(
ffmpeg=self.parent.core.FFMPEG_BIN, videoPath=self.videoPath,
@@ -279,11 +280,11 @@ def finalizeFrame(self, imageData, width, height):
'### BAD VIDEO SELECTED ###\n'
'Video will not export with these settings'
)
- return self.blankFrame(width, height)
+ return BlankFrame(width, height)
if self.scale != 100 \
or self.xPosition != 0 or self.yPosition != 0:
- frame = self.blankFrame(width, height)
+ frame = BlankFrame(width, height)
frame.paste(image, box=(self.xPosition, self.yPosition))
else:
frame = image
diff --git a/src/frame.py b/src/frame.py
new file mode 100644
index 0000000..6d6d299
--- /dev/null
+++ b/src/frame.py
@@ -0,0 +1,42 @@
+'''
+ Common tools for drawing compatible frames in a Component's frameRender()
+'''
+from PyQt5 import QtGui
+from PIL import Image
+from PIL.ImageQt import ImageQt
+import sys
+
+
+class FramePainter(QtGui.QPainter):
+ def __init__(self, width, height):
+ image = BlankFrame(width, height)
+ self.image = ImageQt(image)
+ super().__init__(self.image)
+
+ def setPen(self, RgbTuple):
+ if sys.byteorder == 'big':
+ color = QtGui.QColor(*RgbTuple)
+ else:
+ color = QtGui.QColor(*RgbTuple[::-1])
+ super().setPen(QtGui.QColor(color))
+
+ def finalize(self):
+ self.end()
+ imBytes = self.image.bits().asstring(self.image.byteCount())
+
+ return Image.frombytes(
+ 'RGBA', (self.image.width(), self.image.height()), imBytes
+ )
+
+class PaintColor(QtGui.QColor):
+ def __init__(self, r, g, b, a=255):
+ if sys.byteorder == 'big':
+ super().__init__(r, g, b, a)
+ else:
+ super().__init__(b, g, r, a)
+
+def FloodFrame(width, height, RgbaTuple):
+ return Image.new("RGBA", (width, height), RgbaTuple)
+
+def BlankFrame(width, height):
+ return FloodFrame(width, height, (0, 0, 0, 0))
--
cgit v1.2.3
From ba0409829de62b745d6f87749572a416061a42b4 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 4 Jul 2017 19:52:52 -0400
Subject: moved functions into toolkit, fixed CMD appearing on Windows
---
src/command.py | 2 +-
src/components/video.py | 5 +--
src/core.py | 65 +++++++++++++-----------------------
src/core.pyc | Bin 0 -> 15050 bytes
src/main.py | 35 --------------------
src/mainwindow.py | 4 +--
src/presetmanager.py | 5 +--
src/toolkit.py | 85 ++++++++++++++++++++++++++++++++++++++++++++++++
src/video_thread.py | 3 +-
9 files changed, 119 insertions(+), 85 deletions(-)
create mode 100644 src/core.pyc
create mode 100644 src/toolkit.py
diff --git a/src/command.py b/src/command.py
index 3eea1b6..ee0e48d 100644
--- a/src/command.py
+++ b/src/command.py
@@ -5,7 +5,7 @@ import sys
import core
import video_thread
-from main import LoadDefaultSettings
+from toolkit import LoadDefaultSettings
class Command(QtCore.QObject):
diff --git a/src/components/video.py b/src/components/video.py
index 175cf29..19a9106 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -8,6 +8,7 @@ from queue import PriorityQueue
from component import Component, BadComponentInit
from frame import BlankFrame
+from toolkit import openPipe
class Video:
@@ -72,7 +73,7 @@ class Video:
self.frameBuffer.task_done()
def fillBuffer(self):
- pipe = subprocess.Popen(
+ pipe = openPipe(
self.command, stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL, bufsize=10**8
)
@@ -217,7 +218,7 @@ class Component(Component):
'-ss', '90',
'-vframes', '1',
]
- pipe = subprocess.Popen(
+ pipe = openPipe(
command, stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL, bufsize=10**8
)
diff --git a/src/core.py b/src/core.py
index 3fa67db..9ea9666 100644
--- a/src/core.py
+++ b/src/core.py
@@ -1,21 +1,24 @@
+'''
+ Home to the Core class which tracks the program state
+'''
import sys
import os
from PyQt5 import QtCore, QtGui, uic
-from os.path import expanduser
import subprocess as sp
import numpy
-from PIL import Image
-from shutil import rmtree
-import time
-from collections import OrderedDict
import json
from importlib import import_module
from PyQt5.QtCore import QStandardPaths
-import string
+import toolkit
-class Core():
+class Core:
+ '''
+ MainWindow and Command module both use an instance of this class
+ to store the program state. This object tracks the components,
+ opens projects and presets, and stores settings/paths to data.
+ '''
def __init__(self):
self.dataDir = QStandardPaths.writableLocation(
QStandardPaths.AppConfigLocation
@@ -34,7 +37,7 @@ class Core():
)
self.loadEncoderOptions()
- self.videoFormats = Core.appendUppercase([
+ self.videoFormats = toolkit.appendUppercase([
'*.mp4',
'*.mov',
'*.mkv',
@@ -42,7 +45,7 @@ class Core():
'*.webm',
'*.flv',
])
- self.audioFormats = Core.appendUppercase([
+ self.audioFormats = toolkit.appendUppercase([
'*.mp3',
'*.wav',
'*.ogg',
@@ -50,7 +53,7 @@ class Core():
'*.flac',
'*.aac',
])
- self.imageFormats = Core.appendUppercase([
+ self.imageFormats = toolkit.appendUppercase([
'*.png',
'*.jpg',
'*.tif',
@@ -175,7 +178,7 @@ class Core():
return False
with open(filepath, 'r') as f:
for line in f:
- saveValueStore = Core.presetFromString(line.strip())
+ saveValueStore = toolkit.presetFromString(line.strip())
break
return saveValueStore
@@ -307,7 +310,7 @@ class Core():
lastCompVers = str(line)
i += 1
elif i == 2:
- lastCompPreset = Core.presetFromString(line)
+ lastCompPreset = toolkit.presetFromString(line)
data[section].append((
lastCompName,
lastCompVers,
@@ -357,7 +360,7 @@ class Core():
with open(internalPath, 'r') as f:
internalData = [line for line in f]
try:
- saveValueStore = Core.presetFromString(internalData[0].strip())
+ saveValueStore = toolkit.presetFromString(internalData[0].strip())
self.createPresetFile(
compName, vers,
origName, saveValueStore,
@@ -387,7 +390,7 @@ class Core():
f.write('[Components]\n')
f.write('%s\n' % compName)
f.write('%s\n' % str(vers))
- f.write(Core.presetToString(saveValueStore))
+ f.write(toolkit.presetToString(saveValueStore))
def createProjectFile(self, filepath, window=None):
'''Create a project file (.avp) using the current program state'''
@@ -411,7 +414,7 @@ class Core():
saveValueStore = comp.savePreset()
f.write('%s\n' % str(comp))
f.write('%s\n' % str(comp.version()))
- f.write('%s\n' % Core.presetToString(saveValueStore))
+ f.write('%s\n' % toolkit.presetToString(saveValueStore))
f.write('\n[Settings]\n')
for key in self.settings.allKeys():
@@ -450,7 +453,9 @@ class Core():
else:
try:
with open(os.devnull, "w") as f:
- sp.check_call(['ffmpeg', '-version'], stdout=f, stderr=f)
+ sp.check_call(
+ ['ffmpeg', '-version'], stdout=f, stderr=f
+ )
return "ffmpeg"
except:
return "avconv"
@@ -459,10 +464,9 @@ class Core():
command = [self.FFMPEG_BIN, '-i', filename]
try:
- fileInfo = sp.check_output(command, stderr=sp.STDOUT, shell=False)
+ fileInfo = toolkit.checkOutput(command, stderr=sp.STDOUT)
except sp.CalledProcessError as ex:
fileInfo = ex.output
- pass
info = fileInfo.decode("utf-8").split('\n')
for line in info:
@@ -480,7 +484,7 @@ class Core():
'-ar', '44100', # ouput will have 44100 Hz
'-ac', '1', # mono (set to '2' for stereo)
'-']
- in_pipe = sp.Popen(
+ in_pipe = toolkit.openPipe(
command, stdout=sp.PIPE, stderr=sp.DEVNULL, bufsize=10**8)
completeAudioArray = numpy.empty(0, dtype="int16")
@@ -525,26 +529,3 @@ class Core():
def reset(self):
self.canceled = False
-
- @staticmethod
- def badName(name):
- '''Returns whether a name contains non-alphanumeric chars'''
- return any([letter in string.punctuation for letter in name])
-
- @staticmethod
- def presetToString(dictionary):
- '''Alphabetizes a dict into OrderedDict & returns string repr'''
- return repr(
- OrderedDict(sorted(dictionary.items(), key=lambda t: t[0]))
- )
-
- @staticmethod
- def presetFromString(string):
- '''Turns a string repr of OrderedDict into a regular dict'''
- return dict(eval(string))
-
- @staticmethod
- def appendUppercase(lst):
- for form, i in zip(lst, range(len(lst))):
- lst.append(form.upper())
- return lst
diff --git a/src/core.pyc b/src/core.pyc
new file mode 100644
index 0000000..ce68831
Binary files /dev/null and b/src/core.pyc differ
diff --git a/src/main.py b/src/main.py
index bae9adf..b0ece29 100644
--- a/src/main.py
+++ b/src/main.py
@@ -7,41 +7,6 @@ import preview_thread
import video_thread
-def disableWhenEncoding(func):
- def decorator(*args, **kwargs):
- if args[0].encoding:
- return
- else:
- return func(*args, **kwargs)
- return decorator
-
-
-def LoadDefaultSettings(self):
- self.resolutions = [
- '1920x1080',
- '1280x720',
- '854x480'
- ]
-
- default = {
- "outputWidth": 1280,
- "outputHeight": 720,
- "outputFrameRate": 30,
- "outputAudioCodec": "AAC",
- "outputAudioBitrate": "192",
- "outputVideoCodec": "H264",
- "outputVideoBitrate": "2500",
- "outputVideoFormat": "yuv420p",
- "outputPreset": "medium",
- "outputFormat": "mp4",
- "outputContainer": "MP4",
- "projectDir": os.path.join(self.dataDir, 'projects'),
- }
-
- for parm, value in default.items():
- if self.settings.value(parm) is None:
- self.settings.setValue(parm, value)
-
if __name__ == "__main__":
mode = 'gui'
if len(sys.argv) > 2:
diff --git a/src/mainwindow.py b/src/mainwindow.py
index 5068108..e8a3221 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -1,6 +1,6 @@
-from queue import Queue
from PyQt5 import QtCore, QtGui, uic, QtWidgets
from PyQt5.QtWidgets import QMenu, QShortcut
+from queue import Queue
import sys
import os
import signal
@@ -11,7 +11,7 @@ import core
import preview_thread
import video_thread
from presetmanager import PresetManager
-from main import LoadDefaultSettings, disableWhenEncoding
+from toolkit import LoadDefaultSettings, disableWhenEncoding
class PreviewWindow(QtWidgets.QLabel):
diff --git a/src/presetmanager.py b/src/presetmanager.py
index 68679ec..805b93e 100644
--- a/src/presetmanager.py
+++ b/src/presetmanager.py
@@ -3,6 +3,7 @@ import string
import os
import core
+import toolkit
class PresetManager(QtWidgets.QDialog):
@@ -147,7 +148,7 @@ class PresetManager(QtWidgets.QDialog):
currentPreset
)
if OK:
- if core.Core.badName(newName):
+ if toolkit.badName(newName):
self.warnMessage(self.parent.window)
continue
if newName:
@@ -252,7 +253,7 @@ class PresetManager(QtWidgets.QDialog):
self.presetRows[index][2]
)
if OK:
- if core.Core.badName(newName):
+ if toolkit.badName(newName):
self.warnMessage()
continue
if newName:
diff --git a/src/toolkit.py b/src/toolkit.py
new file mode 100644
index 0000000..8dce645
--- /dev/null
+++ b/src/toolkit.py
@@ -0,0 +1,85 @@
+'''
+ Common functions
+'''
+import string
+import os
+import sys
+import subprocess
+from collections import OrderedDict
+
+
+def badName(name):
+ '''Returns whether a name contains non-alphanumeric chars'''
+ return any([letter in string.punctuation for letter in name])
+
+
+def presetToString(dictionary):
+ '''Alphabetizes a dict into OrderedDict & returns string repr'''
+ return repr(
+ OrderedDict(sorted(dictionary.items(), key=lambda t: t[0]))
+ )
+
+
+def presetFromString(string):
+ '''Turns a string repr of OrderedDict into a regular dict'''
+ return dict(eval(string))
+
+
+def appendUppercase(lst):
+ for form, i in zip(lst, range(len(lst))):
+ lst.append(form.upper())
+ return lst
+
+
+def checkOutput(commandList, **kwargs):
+ _subprocess(subprocess.check_output)
+
+
+def openPipe(commandList, **kwargs):
+ _subprocess(subprocess.Popen)
+
+
+def _subprocess(func, commandList, **kwargs):
+ if not sys.platform == 'win32':
+ # Stop CMD window from appearing on Windows
+ # http://code.activestate.com/recipes/409002/
+ startupinfo = subprocess.STARTUPINFO()
+ startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
+ kwargs['startupinfo'] = startupinfo
+ return func(commandList, shell=False, **kwargs)
+
+
+def disableWhenEncoding(func):
+ def decorator(*args, **kwargs):
+ if args[0].encoding:
+ return
+ else:
+ return func(*args, **kwargs)
+ return decorator
+
+
+def LoadDefaultSettings(self):
+ self.resolutions = [
+ '1920x1080',
+ '1280x720',
+ '854x480'
+ ]
+
+ default = {
+ "outputWidth": 1280,
+ "outputHeight": 720,
+ "outputFrameRate": 30,
+ "outputAudioCodec": "AAC",
+ "outputAudioBitrate": "192",
+ "outputVideoCodec": "H264",
+ "outputVideoBitrate": "2500",
+ "outputVideoFormat": "yuv420p",
+ "outputPreset": "medium",
+ "outputFormat": "mp4",
+ "outputContainer": "MP4",
+ "projectDir": os.path.join(self.dataDir, 'projects'),
+ }
+
+ for parm, value in default.items():
+ if self.settings.value(parm) is None:
+ self.settings.setValue(parm, value)
diff --git a/src/video_thread.py b/src/video_thread.py
index 9b0bf56..aed4d60 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -13,6 +13,7 @@ from copy import copy
import signal
import core
+from toolkit import openPipe
class Worker(QtCore.QObject):
@@ -191,7 +192,7 @@ class Worker(QtCore.QObject):
self.progressBarUpdate.emit(100)
# Create ffmpeg pipe and queues for frames
- self.out_pipe = sp.Popen(
+ self.out_pipe = openPipe(
ffmpegCommand, stdin=sp.PIPE, stdout=sys.stdout, stderr=sys.stdout)
self.compositeQueue = Queue()
self.compositeQueue.maxsize = 20
--
cgit v1.2.3
From 63daa3138284f1e928b362c09e391eed53d4527d Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 4 Jul 2017 20:01:14 -0400
Subject: removed pyc
---
.gitignore | 1 +
src/core.pyc | Bin 15050 -> 0 bytes
2 files changed, 1 insertion(+)
delete mode 100644 src/core.pyc
diff --git a/.gitignore b/.gitignore
index 68dffc7..d44e3f2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
__pycache__
+.py[cod]
build/*
env/*
.vscode/*
diff --git a/src/core.pyc b/src/core.pyc
deleted file mode 100644
index ce68831..0000000
Binary files a/src/core.pyc and /dev/null differ
--
cgit v1.2.3
From 134779f6e65bae36accc9fd586e41b7e80b5dc93 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 4 Jul 2017 20:55:51 -0400
Subject: updated Windows installation instructions
---
README.md | 13 ++++++-------
src/toolkit.py | 2 +-
2 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/README.md b/README.md
index 2d59979..acf36d0 100644
--- a/README.md
+++ b/README.md
@@ -20,15 +20,14 @@ Installation
Download audio-visualizer-python from this repository and run it with `python3 main.py`.
-### Manual installation on Windows [Outdated]
-* Download and install Python 3.4 from [https://www.python.org/downloads/windows/](https://www.python.org/downloads/windows/)
-* Download and install PyQt4 for Python 3.4 and Qt4 from [http://www.riverbankcomputing.co.uk/software/pyqt/download](http://www.riverbankcomputing.co.uk/software/pyqt/download)
-* Download and install numpy from [http://www.scipy.org/scipylib/download.html](http://www.scipy.org/scipylib/download.html). There is an installer available, make sure to get the one for Python 3.4
-* Download and install Pillow from [https://pypi.python.org/pypi/Pillow/3.3.0](https://pypi.python.org/pypi/Pillow/3.3.0)
+### Manual installation on Windows
+* Download and install Python 3.6 from [https://www.python.org/downloads/windows/](https://www.python.org/downloads/windows/)
+* Add Python to your system PATH (it will ask during the installation process).
+* Open command prompt and run: `pip install pyqt5 numpy pillow-simd`
* Download and install ffmpeg from [https://www.ffmpeg.org/download.html](https://www.ffmpeg.org/download.html). You can use the static builds.
-* Add ffmpeg to your system PATH environment variable.
+* Add ffmpeg to your system PATH, too. [How to edit the PATH on Windows.](https://www.java.com/en/download/help/path.xml)
-Download audio-visualizer-python from this repository and run it from the command line with `C:\Python34\python.exe main.py`.
+Download audio-visualizer-python from this repository and run it from the command line with `python main.py`.
### Manual installation on macOS [Outdated]
diff --git a/src/toolkit.py b/src/toolkit.py
index 8dce645..589a6dc 100644
--- a/src/toolkit.py
+++ b/src/toolkit.py
@@ -40,7 +40,7 @@ def openPipe(commandList, **kwargs):
def _subprocess(func, commandList, **kwargs):
- if not sys.platform == 'win32':
+ if sys.platform == 'win32':
# Stop CMD window from appearing on Windows
# http://code.activestate.com/recipes/409002/
startupinfo = subprocess.STARTUPINFO()
--
cgit v1.2.3
From ad4dc052d82c32cadddcc0f3f647d1aafac9f05a Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 4 Jul 2017 22:18:57 -0400
Subject: made code work
---
src/toolkit.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/toolkit.py b/src/toolkit.py
index 589a6dc..fe752f4 100644
--- a/src/toolkit.py
+++ b/src/toolkit.py
@@ -32,11 +32,11 @@ def appendUppercase(lst):
def checkOutput(commandList, **kwargs):
- _subprocess(subprocess.check_output)
+ return _subprocess(subprocess.check_output, commandList, **kwargs)
def openPipe(commandList, **kwargs):
- _subprocess(subprocess.Popen)
+ return _subprocess(subprocess.Popen, commandList, **kwargs)
def _subprocess(func, commandList, **kwargs):
--
cgit v1.2.3
From 52f0f96f16e935e636431b18c855291879fcc0ff Mon Sep 17 00:00:00 2001
From: tassaron
Date: Wed, 5 Jul 2017 06:38:36 -0400
Subject: use decorator for readability
---
src/toolkit.py | 29 +++++++++++++++++------------
1 file changed, 17 insertions(+), 12 deletions(-)
diff --git a/src/toolkit.py b/src/toolkit.py
index fe752f4..5cfd5ee 100644
--- a/src/toolkit.py
+++ b/src/toolkit.py
@@ -31,22 +31,27 @@ def appendUppercase(lst):
return lst
+def hideCmdWin(func):
+ ''' Stops CMD window from appearing on Windows.
+ Adapted from here: http://code.activestate.com/recipes/409002/
+ '''
+ def decorator(func, commandList, **kwargs):
+ if sys.platform == 'win32':
+ startupinfo = subprocess.STARTUPINFO()
+ startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
+ kwargs['startupinfo'] = startupinfo
+ return func(commandList, **kwargs)
+ return func
+
+
+@hideCmdWin
def checkOutput(commandList, **kwargs):
- return _subprocess(subprocess.check_output, commandList, **kwargs)
+ return subprocess.check_output(commandList, **kwargs)
+@hideCmdWin
def openPipe(commandList, **kwargs):
- return _subprocess(subprocess.Popen, commandList, **kwargs)
-
-
-def _subprocess(func, commandList, **kwargs):
- if sys.platform == 'win32':
- # Stop CMD window from appearing on Windows
- # http://code.activestate.com/recipes/409002/
- startupinfo = subprocess.STARTUPINFO()
- startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
- kwargs['startupinfo'] = startupinfo
- return func(commandList, shell=False, **kwargs)
+ return subprocess.Popen(commandList, **kwargs)
def disableWhenEncoding(func):
--
cgit v1.2.3
From 3f7ead0d1f6be4f6b688cd05e75b69b61b15f861 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Wed, 5 Jul 2017 06:45:30 -0400
Subject: fixed syntax error
again...
---
src/toolkit.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/toolkit.py b/src/toolkit.py
index 5cfd5ee..798a5b5 100644
--- a/src/toolkit.py
+++ b/src/toolkit.py
@@ -35,13 +35,13 @@ def hideCmdWin(func):
''' Stops CMD window from appearing on Windows.
Adapted from here: http://code.activestate.com/recipes/409002/
'''
- def decorator(func, commandList, **kwargs):
+ def decorator(commandList, **kwargs):
if sys.platform == 'win32':
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
kwargs['startupinfo'] = startupinfo
return func(commandList, **kwargs)
- return func
+ return decorator
@hideCmdWin
--
cgit v1.2.3
From 3de45b3629aa994e986245b6af2ef8016818a8e2 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Wed, 5 Jul 2017 23:04:09 -0400
Subject: added basic rotate option to images
---
src/components/image.py | 9 ++++++---
src/components/image.ui | 51 +++++++++++++++++++++++++++++++++++++++++++++++++
src/toolkit.py | 6 ++++++
3 files changed, 63 insertions(+), 3 deletions(-)
diff --git a/src/components/image.py b/src/components/image.py
index 1aae51b..4ccfc80 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -15,13 +15,11 @@ class Component(Component):
self.parent = parent
self.settings = parent.settings
page = self.loadUi('image.ui')
- self.imagePath = ''
- self.x = 0
- self.y = 0
page.lineEdit_image.textChanged.connect(self.update)
page.pushButton_image.clicked.connect(self.pickImage)
page.spinBox_scale.valueChanged.connect(self.update)
+ page.spinBox_rotate.valueChanged.connect(self.update)
page.checkBox_stretch.stateChanged.connect(self.update)
page.spinBox_x.valueChanged.connect(self.update)
page.spinBox_y.valueChanged.connect(self.update)
@@ -32,6 +30,7 @@ class Component(Component):
def update(self):
self.imagePath = self.page.lineEdit_image.text()
self.scale = self.page.spinBox_scale.value()
+ self.rotate = self.page.spinBox_rotate.value()
self.xPosition = self.page.spinBox_x.value()
self.yPosition = self.page.spinBox_y.value()
self.stretched = self.page.checkBox_stretch.isChecked()
@@ -64,12 +63,15 @@ class Component(Component):
newWidth = int((image.width / 100) * self.scale)
image = image.resize((newWidth, newHeight), Image.ANTIALIAS)
frame.paste(image, box=(self.xPosition, self.yPosition))
+ if self.rotate != 0:
+ frame = frame.rotate(self.rotate)
return frame
def loadPreset(self, pr, presetName=None):
super().loadPreset(pr, presetName)
self.page.lineEdit_image.setText(pr['image'])
self.page.spinBox_scale.setValue(pr['scale'])
+ self.page.spinBox_rotate.setValue(pr['rotate'])
self.page.spinBox_x.setValue(pr['x'])
self.page.spinBox_y.setValue(pr['y'])
self.page.checkBox_stretch.setChecked(pr['stretched'])
@@ -79,6 +81,7 @@ class Component(Component):
'preset': self.currentPreset,
'image': self.imagePath,
'scale': self.scale,
+ 'rotate': self.rotate,
'stretched': self.stretched,
'x': self.xPosition,
'y': self.yPosition,
diff --git a/src/components/image.ui b/src/components/image.ui
index 6df03a5..33488f8 100644
--- a/src/components/image.ui
+++ b/src/components/image.ui
@@ -208,8 +208,59 @@
+ -
+
+
+ Rotation
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ QAbstractSpinBox::UpDownArrows
+
+
+ °
+
+
+ 0
+
+
+ 359
+
+
+ 0
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 10
+ 20
+
+
+
+
-
+
+
+ 0
+ 0
+
+
Scale
diff --git a/src/toolkit.py b/src/toolkit.py
index 798a5b5..589d8e6 100644
--- a/src/toolkit.py
+++ b/src/toolkit.py
@@ -55,6 +55,9 @@ def openPipe(commandList, **kwargs):
def disableWhenEncoding(func):
+ ''' Blocks calls to a function while the video is being exported
+ in MainWindow.
+ '''
def decorator(*args, **kwargs):
if args[0].encoding:
return
@@ -64,6 +67,9 @@ def disableWhenEncoding(func):
def LoadDefaultSettings(self):
+ ''' Runs once at each program start-up. Fills in default settings
+ for any settings not found in settings.ini
+ '''
self.resolutions = [
'1920x1080',
'1280x720',
--
cgit v1.2.3
From 9986b1c829caa12bcea120bb37ebb57ab5e0e874 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 6 Jul 2017 12:40:03 -0400
Subject: image options to mirror & saturate colours
and some friendly docstrings
---
freeze.py | 1 +
src/command.py | 5 ++++
src/components/image.py | 21 +++++++++++++++-
src/components/image.ui | 64 ++++++++++++++++++++++++++++++++++++++++++++++++-
src/core.py | 2 +-
src/mainwindow.py | 9 ++++++-
src/presetmanager.py | 4 ++++
src/preview_thread.py | 18 ++++++++++----
src/video_thread.py | 7 ++++++
9 files changed, 122 insertions(+), 9 deletions(-)
diff --git a/freeze.py b/freeze.py
index a81f325..3266f45 100644
--- a/freeze.py
+++ b/freeze.py
@@ -33,6 +33,7 @@ buildOptions = dict(
"PIL.Image",
"PIL.ImageQt",
"PIL.ImageDraw",
+ "PIL.ImageEnhance",
],
include_files=deps,
)
diff --git a/src/command.py b/src/command.py
index ee0e48d..be194d8 100644
--- a/src/command.py
+++ b/src/command.py
@@ -1,3 +1,8 @@
+'''
+ When using commandline mode, this module's object handles interpreting
+ the arguments and giving them to Core, which tracks the main program state.
+ Then it immediately exports a video.
+'''
from PyQt5 import QtCore
import argparse
import os
diff --git a/src/components/image.py b/src/components/image.py
index 4ccfc80..c9da137 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -1,4 +1,4 @@
-from PIL import Image, ImageDraw
+from PIL import Image, ImageDraw, ImageEnhance
from PyQt5 import QtGui, QtCore, QtWidgets
import os
@@ -20,7 +20,9 @@ class Component(Component):
page.pushButton_image.clicked.connect(self.pickImage)
page.spinBox_scale.valueChanged.connect(self.update)
page.spinBox_rotate.valueChanged.connect(self.update)
+ page.spinBox_color.valueChanged.connect(self.update)
page.checkBox_stretch.stateChanged.connect(self.update)
+ page.checkBox_mirror.stateChanged.connect(self.update)
page.spinBox_x.valueChanged.connect(self.update)
page.spinBox_y.valueChanged.connect(self.update)
@@ -31,9 +33,11 @@ class Component(Component):
self.imagePath = self.page.lineEdit_image.text()
self.scale = self.page.spinBox_scale.value()
self.rotate = self.page.spinBox_rotate.value()
+ self.color = self.page.spinBox_color.value()
self.xPosition = self.page.spinBox_x.value()
self.yPosition = self.page.spinBox_y.value()
self.stretched = self.page.checkBox_stretch.isChecked()
+ self.mirror = self.page.checkBox_mirror.isChecked()
self.parent.drawPreview()
super().update()
@@ -56,33 +60,48 @@ class Component(Component):
frame = BlankFrame(width, height)
if self.imagePath and os.path.exists(self.imagePath):
image = Image.open(self.imagePath)
+
+ # Modify image's appearance
+ if self.color != 100:
+ image = ImageEnhance.Color(image).enhance(
+ float(self.color / 100)
+ )
+ if self.mirror:
+ image = image.transpose(Image.FLIP_LEFT_RIGHT)
if self.stretched and image.size != (width, height):
image = image.resize((width, height), Image.ANTIALIAS)
if self.scale != 100:
newHeight = int((image.height / 100) * self.scale)
newWidth = int((image.width / 100) * self.scale)
image = image.resize((newWidth, newHeight), Image.ANTIALIAS)
+
+ # Paste image at correct position
frame.paste(image, box=(self.xPosition, self.yPosition))
if self.rotate != 0:
frame = frame.rotate(self.rotate)
+
return frame
def loadPreset(self, pr, presetName=None):
super().loadPreset(pr, presetName)
self.page.lineEdit_image.setText(pr['image'])
self.page.spinBox_scale.setValue(pr['scale'])
+ self.page.spinBox_color.setValue(pr['color'])
self.page.spinBox_rotate.setValue(pr['rotate'])
self.page.spinBox_x.setValue(pr['x'])
self.page.spinBox_y.setValue(pr['y'])
self.page.checkBox_stretch.setChecked(pr['stretched'])
+ self.page.checkBox_mirror.setChecked(pr['mirror'])
def savePreset(self):
return {
'preset': self.currentPreset,
'image': self.imagePath,
'scale': self.scale,
+ 'color': self.color,
'rotate': self.rotate,
'stretched': self.stretched,
+ 'mirror': self.mirror,
'x': self.xPosition,
'y': self.yPosition,
}
diff --git a/src/components/image.ui b/src/components/image.ui
index 33488f8..e549ed0 100644
--- a/src/components/image.ui
+++ b/src/components/image.ui
@@ -208,10 +208,17 @@
+ -
+
+
+ Mirror
+
+
+
-
- Rotation
+ Rotate
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
@@ -290,6 +297,61 @@
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Color
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ QAbstractSpinBox::UpDownArrows
+
+
+ %
+
+
+ 0
+
+
+ 999
+
+
+ 1
+
+
+ 100
+
+
+
+
+
-
diff --git a/src/core.py b/src/core.py
index 9ea9666..5623039 100644
--- a/src/core.py
+++ b/src/core.py
@@ -1,5 +1,5 @@
'''
- Home to the Core class which tracks the program state
+ Home to the Core class which tracks program state. Used by GUI & commandline
'''
import sys
import os
diff --git a/src/mainwindow.py b/src/mainwindow.py
index e8a3221..1c6bbc4 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -1,3 +1,9 @@
+'''
+ When using GUI mode, this module's object (the main window) takes
+ user input to construct a program state (stored in the Core object).
+ This shows a preview of the video being created and allows for saving
+ projects and exporting the video at a later time.
+'''
from PyQt5 import QtCore, QtGui, uic, QtWidgets
from PyQt5.QtWidgets import QMenu, QShortcut
from queue import Queue
@@ -79,6 +85,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.previewWorker = preview_thread.Worker(self, self.previewQueue)
self.previewWorker.moveToThread(self.previewThread)
self.previewWorker.imageCreated.connect(self.showPreviewImage)
+ self.previewWorker.error.connect(self.cleanUp)
self.previewThread.start()
self.timer = QtCore.QTimer(self)
@@ -296,11 +303,11 @@ class MainWindow(QtWidgets.QMainWindow):
QtWidgets.QShortcut("Ctrl+End", self.window, self.moveComponentBottom)
QtWidgets.QShortcut("Ctrl+r", self.window, self.removeComponent)
+ @QtCore.pyqtSlot()
def cleanUp(self):
self.timer.stop()
self.previewThread.quit()
self.previewThread.wait()
- self.autosave()
def updateWindowTitle(self):
appName = 'Audio Visualizer'
diff --git a/src/presetmanager.py b/src/presetmanager.py
index 805b93e..40aa73f 100644
--- a/src/presetmanager.py
+++ b/src/presetmanager.py
@@ -1,3 +1,7 @@
+'''
+ Preset manager object handles all interactions with presets, including
+ the context menu accessed from MainWindow.
+'''
from PyQt5 import QtCore, QtWidgets
import string
import os
diff --git a/src/preview_thread.py b/src/preview_thread.py
index e58f04e..afb5e50 100644
--- a/src/preview_thread.py
+++ b/src/preview_thread.py
@@ -1,3 +1,7 @@
+'''
+ Thread that runs to create QImages for MainWindow's preview label.
+ Processes a queue of component lists.
+'''
from PyQt5 import QtCore, QtGui, uic
from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PIL import Image
@@ -11,6 +15,7 @@ from copy import copy
class Worker(QtCore.QObject):
imageCreated = pyqtSignal(['QImage'])
+ error = pyqtSignal()
def __init__(self, parent=None, queue=None):
QtCore.QObject.__init__(self)
@@ -59,12 +64,15 @@ class Worker(QtCore.QObject):
"This is a fatal error." %
str(component),
detail=str(e),
- icon='Warning'
+ icon='Warning',
+ parent=None # mainwindow is in a different thread
)
- quit(1)
-
- self._image = ImageQt(frame)
- self.imageCreated.emit(QtGui.QImage(self._image))
+ from frame import BlankFrame
+ self.imageCreated.emit(ImageQt(BlankFrame))
+ self.error.emit()
+ break
+ else:
+ self.imageCreated.emit(ImageQt(frame))
except Empty:
True
diff --git a/src/video_thread.py b/src/video_thread.py
index aed4d60..d35a37a 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -1,3 +1,10 @@
+'''
+ Thread created to export a video. It has a slot to begin export using
+ an input file, output path, and component list. During export multiple
+ threads are created to render the video as quickly as possible. Signals
+ are emitted to update MainWindow's progress bar, detail text, and preview.
+ Export can be cancelled with cancel() + reset()
+'''
from PyQt5 import QtCore, QtGui, uic
from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PIL import Image, ImageDraw, ImageFont
--
cgit v1.2.3
From f027fd43537eb60f682b51a5018caee471bf33e2 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 6 Jul 2017 19:52:46 -0400
Subject: more thorough installation directions
---
.gitignore | 1 +
MANIFEST | 2 ++
README.md | 9 +++++++--
freeze.py | 3 ++-
4 files changed, 12 insertions(+), 3 deletions(-)
create mode 100644 MANIFEST
diff --git a/.gitignore b/.gitignore
index d44e3f2..1095610 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
__pycache__
.py[cod]
build/*
+dist/*
env/*
.vscode/*
*.mkv
diff --git a/MANIFEST b/MANIFEST
new file mode 100644
index 0000000..a0c51f7
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,2 @@
+# file GENERATED by distutils, do NOT edit
+freeze.py
diff --git a/README.md b/README.md
index acf36d0..b82f3b4 100644
--- a/README.md
+++ b/README.md
@@ -15,14 +15,19 @@ Installation
------------
### Manual installation on Ubuntu 16.04
* Install pip: `sudo apt-get install python3-pip`
-* Install dependencies: `sudo pip3 install pyqt5 numpy pillow-simd`
-* Install `ffmpeg` from the [website](http://ffmpeg.org/) or from a PPA (e.g. [https://launchpad.net/~jonathonf/+archive/ubuntu/ffmpeg-3](https://launchpad.net/~jonathonf/+archive/ubuntu/ffmpeg-3). NOTE: `ffmpeg` in the standard repos is too old (v2.8). Old versions and `avconv` may be used but full functionality is only guaranteed with `ffmpeg` 3.3 or higher.
+* Install [prerequisites to compile Pillow](http://pillow.readthedocs.io/en/3.1.x/installation.html#building-on-linux):`sudo apt-get install python3-dev python3-setuptools libtiff5-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python-tk`
+* Prerequisites on **Fedora**:`sudo dnf install python3-devel redhat-rpm-config libtiff-devel libjpeg-devel libzip-devel freetype-devel lcms2-devel libwebp-devel tcl-devel tk-devel`
+* Install dependencies from PyPI: `sudo pip3 install pyqt5 numpy pillow-simd`
+* Install `ffmpeg` from the [website](http://ffmpeg.org/) or from a PPA (e.g. [https://launchpad.net/~jonathonf/+archive/ubuntu/ffmpeg-3](https://launchpad.net/~jonathonf/+archive/ubuntu/ffmpeg-3)). NOTE: `ffmpeg` in the standard repos is too old (v2.8). Old versions and `avconv` may be used but full functionality is only guaranteed with `ffmpeg` 3.3 or higher.
Download audio-visualizer-python from this repository and run it with `python3 main.py`.
### Manual installation on Windows
+* **Not Recommended.** [Compiling Pillow is difficult on Windows](http://pillow.readthedocs.io/en/3.1.x/installation.html#building-on-windows) and required for a manual installation.
* Download and install Python 3.6 from [https://www.python.org/downloads/windows/](https://www.python.org/downloads/windows/)
* Add Python to your system PATH (it will ask during the installation process).
+* Brave treacherous valley of getting prerequisites to [compile Pillow on Windows](https://www.pypkg.com/pypi/pillow-simd/f/winbuild/README.md). This is necessary because binary builds for Pillow-SIMD are not available.
+* **Alternative:** install Pillow instead of Pillow-SIMD, for which binaries *are* available. However this will result in much slower video export times.
* Open command prompt and run: `pip install pyqt5 numpy pillow-simd`
* Download and install ffmpeg from [https://www.ffmpeg.org/download.html](https://www.ffmpeg.org/download.html). You can use the static builds.
* Add ffmpeg to your system PATH, too. [How to edit the PATH on Windows.](https://www.java.com/en/download/help/path.xml)
diff --git a/freeze.py b/freeze.py
index 3266f45..c9b7918 100644
--- a/freeze.py
+++ b/freeze.py
@@ -18,7 +18,8 @@ buildOptions = dict(
"html",
"http",
"xmlrpc",
- "nose"
+ "nose",
+ 'tkinter',
],
includes=[
"encodings",
--
cgit v1.2.3
From 94d4acc1f4f4abe4029e8f9c050932b67cae8cec Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 9 Jul 2017 01:10:06 -0400
Subject: more comments + warnings for outdated dependencies
---
MANIFEST | 2 --
README.md | 8 +++--
src/component.py | 55 +++++++++++++++++++--------------
src/components/color.py | 5 ++-
src/components/image.py | 2 +-
src/components/original.py | 3 +-
src/components/text.py | 2 +-
src/components/video.py | 2 +-
src/core.py | 8 ++---
src/frame.py | 15 ++++++---
src/mainwindow.py | 34 +++++++++++++++++++-
src/preview_thread.py | 12 +++++---
src/video_thread.py | 77 ++++++++++++++++++++++++++++------------------
13 files changed, 148 insertions(+), 77 deletions(-)
delete mode 100644 MANIFEST
diff --git a/MANIFEST b/MANIFEST
deleted file mode 100644
index a0c51f7..0000000
--- a/MANIFEST
+++ /dev/null
@@ -1,2 +0,0 @@
-# file GENERATED by distutils, do NOT edit
-freeze.py
diff --git a/README.md b/README.md
index b82f3b4..658a22d 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@ audio-visualizer-python
This is a little GUI tool which creates an audio visualization video from an input audio file. Different components can be added and layered to change the resulting video and add images, videos, gradients, text, etc. The component setup can be saved as a Project and exporting can be automated using commandline options.
-The program works on Linux (Ubuntu 16.04), Windows (Windows 7), and Mac OS X. If you encounter problems running it or have other bug reports or features that you wish to see implemented, please fork the project and send me a pull request and/or file an issue on this project.
+The program works on Linux, macOS, and Windows. If you encounter problems running it or have other bug reports or features that you wish to see implemented, please fork the project and send me a pull request and/or file an issue on this project.
I also need a good name that is not as generic as "audio-visualizer-python"!
@@ -11,6 +11,8 @@ Dependencies
------------
Python 3, PyQt5, pillow-simd, numpy, and ffmpeg 3.3
+**Note:** Pillow may be used as a drop-in replacement for Pillow-SIMD if problems are encountered installing. However this will result in much slower video export times.
+
Installation
------------
### Manual installation on Ubuntu 16.04
@@ -23,7 +25,7 @@ Installation
Download audio-visualizer-python from this repository and run it with `python3 main.py`.
### Manual installation on Windows
-* **Not Recommended.** [Compiling Pillow is difficult on Windows](http://pillow.readthedocs.io/en/3.1.x/installation.html#building-on-windows) and required for a manual installation.
+* **Warning:** [Compiling Pillow is difficult on Windows](http://pillow.readthedocs.io/en/3.1.x/installation.html#building-on-windows) and required for the best experience.
* Download and install Python 3.6 from [https://www.python.org/downloads/windows/](https://www.python.org/downloads/windows/)
* Add Python to your system PATH (it will ask during the installation process).
* Brave treacherous valley of getting prerequisites to [compile Pillow on Windows](https://www.pypkg.com/pypi/pillow-simd/f/winbuild/README.md). This is necessary because binary builds for Pillow-SIMD are not available.
@@ -34,7 +36,7 @@ Download audio-visualizer-python from this repository and run it with `python3 m
Download audio-visualizer-python from this repository and run it from the command line with `python main.py`.
-### Manual installation on macOS [Outdated]
+### Manual installation on macOS **[Outdated]**
* Install [Homebrew](http://brew.sh/)
* Use the following commands to install the needed dependencies:
diff --git a/src/component.py b/src/component.py
index 6637eac..648a6d6 100644
--- a/src/component.py
+++ b/src/component.py
@@ -6,8 +6,11 @@ import os
class Component(QtCore.QObject):
- ''' A class for components to inherit.'''
- # modified = QtCore.pyqtSignal(int, bool)
+ '''
+ A class for components to inherit. Read comments for documentation
+ on making a valid component. All subclasses must implement this signal:
+ modified = QtCore.pyqtSignal(int, bool)
+ '''
def __init__(self, moduleIndex, compPos, core):
super().__init__()
@@ -36,30 +39,32 @@ class Component(QtCore.QObject):
# read your widget values, then call super().update()
def loadPreset(self, presetDict, presetName):
- '''Subclasses take (presetDict, presetName=None) as args.
- Must use super().loadPreset(presetDict, presetName) first,
- then update self.page widgets using the preset dict.
+ '''
+ Subclasses take (presetDict, presetName=None) as args.
+ Must use super().loadPreset(presetDict, presetName) first,
+ then update self.page widgets using the preset dict.
'''
self.currentPreset = presetName \
if presetName is not None else presetDict['preset']
def preFrameRender(self, **kwargs):
- '''Triggered only before a video is exported (video_thread.py)
- self.worker = the video thread worker
- self.completeAudioArray = a list of audio samples
- self.sampleSize = number of audio samples per video frame
- self.progressBarUpdate = signal to set progress bar number
- self.progressBarSetText = signal to set progress bar text
- Use the latter two signals to update the MainProgram if needed
- for a long initialization procedure (i.e., for a visualizer)
+ ''' Triggered only before a video is exported (video_thread.py)
+ self.worker = the video thread worker
+ self.completeAudioArray = a list of audio samples
+ self.sampleSize = number of audio samples per video frame
+ self.progressBarUpdate = signal to set progress bar number
+ self.progressBarSetText = signal to set progress bar text
+ Use the latter two signals to update the MainWindow if needed
+ for a long initialization procedure (i.e., for a visualizer)
'''
for var, value in kwargs.items():
exec('self.%s = value' % var)
def command(self, arg):
- '''Configure a component using argument from the commandline.
- Use super().command(arg) at the end of a subclass's method,
- if no arguments are found in that method first
+ '''
+ Configure a component using argument from the commandline.
+ Use super().command(arg) at the end of a subclass's method,
+ if no arguments are found in that method first
'''
if arg.startswith('preset='):
_, preset = arg.split('=', 1)
@@ -84,9 +89,10 @@ class Component(QtCore.QObject):
'''Print help text for this Component's commandline arguments'''
def pickColor(self):
- '''Use color picker to get color input from the user,
- and return this as an RGB string and QPushButton stylesheet.
- In a subclass apply stylesheet to any color selection widgets
+ '''
+ Use color picker to get color input from the user,
+ and return this as an RGB string and QPushButton stylesheet.
+ In a subclass apply stylesheet to any color selection widgets
'''
dialog = QtWidgets.QColorDialog()
dialog.setOption(QtWidgets.QColorDialog.ShowAlphaChannel, True)
@@ -101,7 +107,7 @@ class Component(QtCore.QObject):
return None, None
def RGBFromString(self, string):
- ''' Turns an RGB string like "255, 255, 255" into a tuple '''
+ '''Turns an RGB string like "255, 255, 255" into a tuple'''
try:
tup = tuple([int(i) for i in string.split(',')])
if len(tup) != 3:
@@ -135,13 +141,16 @@ class Component(QtCore.QObject):
def previewRender(self, previewWorker):
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
- image = Image.new("RGBA", (width, height), (0,0,0,0))
+ from frame import BlankFrame
+ image = BlankFrame(width, height)
return image
- def frameRender(self, moduleNo, frameNo):
+ def frameRender(self, layerNo, frameNo):
+ audioArrayIndex = frameNo * self.sampleSize
width = int(self.worker.core.settings.value('outputWidth'))
height = int(self.worker.core.settings.value('outputHeight'))
- image = Image.new("RGBA", (width, height), (0,0,0,0))
+ from frame import BlankFrame
+ image = BlankFrame(width, height)
return image
@classmethod
diff --git a/src/components/color.py b/src/components/color.py
index 4a10263..b87f3e9 100644
--- a/src/components/color.py
+++ b/src/components/color.py
@@ -104,6 +104,9 @@ class Component(Component):
self.page.checkBox_trans.setEnabled(True)
self.page.checkBox_stretch.setEnabled(True)
self.page.comboBox_spread.setEnabled(True)
+ if self.trans:
+ self.page.lineEdit_color2.setEnabled(False)
+ self.page.pushButton_color2.setEnabled(False)
self.page.fillWidget.setCurrentIndex(self.fillType)
self.parent.drawPreview()
@@ -118,7 +121,7 @@ class Component(Component):
super().preFrameRender(**kwargs)
return ['static']
- def frameRender(self, moduleNo, arrayNo, frameNo):
+ def frameRender(self, layerNo, frameNo):
width = int(self.worker.core.settings.value('outputWidth'))
height = int(self.worker.core.settings.value('outputHeight'))
return self.drawFrame(width, height)
diff --git a/src/components/image.py b/src/components/image.py
index c9da137..6edd893 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -51,7 +51,7 @@ class Component(Component):
super().preFrameRender(**kwargs)
return ['static']
- def frameRender(self, moduleNo, arrayNo, frameNo):
+ def frameRender(self, layerNo, frameNo):
width = int(self.worker.core.settings.value('outputWidth'))
height = int(self.worker.core.settings.value('outputHeight'))
return self.drawFrame(width, height)
diff --git a/src/components/original.py b/src/components/original.py
index 82cdc1d..638095d 100644
--- a/src/components/original.py
+++ b/src/components/original.py
@@ -107,7 +107,8 @@ class Component(Component):
self.progressBarSetText.emit(pStr)
self.progressBarUpdate.emit(int(progress))
- def frameRender(self, moduleNo, arrayNo, frameNo):
+ def frameRender(self, layerNo, frameNo):
+ arrayNo = frameNo * self.sampleSize
return self.drawBars(
self.width, self.height,
self.spectrumArray[arrayNo],
diff --git a/src/components/text.py b/src/components/text.py
index 97d7d07..2b1884f 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -123,7 +123,7 @@ class Component(Component):
super().preFrameRender(**kwargs)
return ['static']
- def frameRender(self, moduleNo, arrayNo, frameNo):
+ def frameRender(self, layerNo, frameNo):
width = int(self.worker.core.settings.value('outputWidth'))
height = int(self.worker.core.settings.value('outputHeight'))
return self.addText(width, height)
diff --git a/src/components/video.py b/src/components/video.py
index 19a9106..e6890e0 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -165,7 +165,7 @@ class Component(Component):
component=self, scale=self.scale
) if os.path.exists(self.videoPath) else None
- def frameRender(self, moduleNo, arrayNo, frameNo):
+ def frameRender(self, layerNo, frameNo):
if self.video:
return self.video.frame(frameNo)
else:
diff --git a/src/core.py b/src/core.py
index 5623039..9792e88 100644
--- a/src/core.py
+++ b/src/core.py
@@ -449,15 +449,15 @@ class Core:
else:
if sys.platform == "win32":
- return "ffmpeg.exe"
+ return "ffmpeg"
else:
try:
with open(os.devnull, "w") as f:
- sp.check_call(
- ['ffmpeg', '-version'], stdout=f, stderr=f
+ toolkit.checkOutput(
+ ['ffmpeg', '-version'], stderr=f
)
return "ffmpeg"
- except:
+ except sp.CalledProcessError:
return "avconv"
def readAudioFile(self, filename, parent):
diff --git a/src/frame.py b/src/frame.py
index 6d6d299..57d33b0 100644
--- a/src/frame.py
+++ b/src/frame.py
@@ -8,17 +8,17 @@ import sys
class FramePainter(QtGui.QPainter):
+ '''
+ A QPainter for a blank frame, which can be converted into a
+ Pillow image with finalize()
+ '''
def __init__(self, width, height):
image = BlankFrame(width, height)
self.image = ImageQt(image)
super().__init__(self.image)
def setPen(self, RgbTuple):
- if sys.byteorder == 'big':
- color = QtGui.QColor(*RgbTuple)
- else:
- color = QtGui.QColor(*RgbTuple[::-1])
- super().setPen(QtGui.QColor(color))
+ super().setPen(PaintColor(*RgbTuple))
def finalize(self):
self.end()
@@ -28,15 +28,20 @@ class FramePainter(QtGui.QPainter):
'RGBA', (self.image.width(), self.image.height()), imBytes
)
+
class PaintColor(QtGui.QColor):
+ '''Reverse the painter colour if the hardware stores RGB values backward'''
def __init__(self, r, g, b, a=255):
if sys.byteorder == 'big':
super().__init__(r, g, b, a)
else:
super().__init__(b, g, r, a)
+
def FloodFrame(width, height, RgbaTuple):
return Image.new("RGBA", (width, height), RgbaTuple)
+
def BlankFrame(width, height):
+ '''The base frame used by each component to start drawing'''
return FloodFrame(width, height, (0, 0, 0, 0))
diff --git a/src/mainwindow.py b/src/mainwindow.py
index 1c6bbc4..165b5bd 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -6,6 +6,7 @@
'''
from PyQt5 import QtCore, QtGui, uic, QtWidgets
from PyQt5.QtWidgets import QMenu, QShortcut
+from PIL import Image
from queue import Queue
import sys
import os
@@ -17,7 +18,7 @@ import core
import preview_thread
import video_thread
from presetmanager import PresetManager
-from toolkit import LoadDefaultSettings, disableWhenEncoding
+from toolkit import LoadDefaultSettings, disableWhenEncoding, checkOutput
class PreviewWindow(QtWidgets.QLabel):
@@ -269,6 +270,37 @@ class MainWindow(QtWidgets.QMainWindow):
self.openProject(self.currentProject, prompt=False)
self.drawPreview(True)
+ # verify Pillow version
+ if not self.settings.value("pilMsgShown") \
+ and 'post' not in Image.PILLOW_VERSION:
+ self.showMessage(
+ msg="You are using the standard version of the "
+ "Python imaging library (Pillow %s). Upgrade "
+ "to the Pillow-SIMD fork to enable hardware accelerations "
+ "and export videos faster." % Image.PILLOW_VERSION
+ )
+ self.settings.setValue("pilMsgShown", True)
+
+ # verify Ffmpeg version
+ if not self.settings.value("ffmpegMsgShown"):
+ try:
+ with open(os.devnull, "w") as f:
+ ffmpegVers = checkOutput(
+ ['ffmpeg', '-version'], stderr=f
+ )
+ goodVersion = str(ffmpegVers).split()[2].startswith('3')
+ except:
+ goodVersion = False
+ else:
+ goodVersion = True
+
+ if not goodVersion:
+ self.showMessage(
+ msg="You're using an old version of Ffmpeg. "
+ "Some features may not work as expected."
+ )
+ self.settings.setValue("ffmpegMsgShown", True)
+
# Setup Hotkeys
QtWidgets.QShortcut("Ctrl+S", self.window, self.saveCurrentProject)
QtWidgets.QShortcut("Ctrl+A", self.window, self.openSaveProjectDialog)
diff --git a/src/preview_thread.py b/src/preview_thread.py
index afb5e50..95a26ec 100644
--- a/src/preview_thread.py
+++ b/src/preview_thread.py
@@ -9,7 +9,8 @@ from PIL.ImageQt import ImageQt
import core
from queue import Queue, Empty
import os
-from copy import copy
+
+from frame import FloodFrame
class Worker(QtCore.QObject):
@@ -22,11 +23,13 @@ class Worker(QtCore.QObject):
parent.newTask.connect(self.createPreviewImage)
parent.processTask.connect(self.process)
self.parent = parent
- self.core = core.Core()
+ self.core = self.parent.core
self.queue = queue
self.core.settings = parent.settings
self.stackedWidget = parent.window.stackedWidget
- self.background = Image.new("RGBA", (1920, 1080), (0, 0, 0, 0))
+
+ # create checkerboard background to represent transparency
+ self.background = FloodFrame(1920, 1080, (0, 0, 0, 0))
self.background.paste(Image.open(os.path.join(
self.core.wd, "background.png")))
@@ -49,7 +52,7 @@ class Worker(QtCore.QObject):
width = int(self.core.settings.value('outputWidth'))
height = int(self.core.settings.value('outputHeight'))
- frame = copy(self.background)
+ frame = self.background.copy()
frame = frame.resize((width, height))
components = nextPreviewInformation["components"]
@@ -58,6 +61,7 @@ class Worker(QtCore.QObject):
frame = Image.alpha_composite(
frame, component.previewRender(self)
)
+
except ValueError as e:
self.parent.showMessage(
msg="Bad frame returned by %s's previewRender method. "
diff --git a/src/video_thread.py b/src/video_thread.py
index d35a37a..e7f1ac7 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -3,7 +3,7 @@
an input file, output path, and component list. During export multiple
threads are created to render the video as quickly as possible. Signals
are emitted to update MainWindow's progress bar, detail text, and preview.
- Export can be cancelled with cancel() + reset()
+ Export can be cancelled with cancel()
'''
from PyQt5 import QtCore, QtGui, uic
from PyQt5.QtCore import pyqtSignal, pyqtSlot
@@ -16,11 +16,11 @@ import os
from queue import Queue, PriorityQueue
from threading import Thread, Event
import time
-from copy import copy
import signal
import core
from toolkit import openPipe
+from frame import FloodFrame
class Worker(QtCore.QObject):
@@ -44,49 +44,65 @@ class Worker(QtCore.QObject):
self.stopped = False
def renderNode(self):
+ '''
+ Grabs audio data indices at frames to export, from compositeQueue.
+ Sends it to the components' frameRender methods in layer order
+ to create subframes & composite them into the final frame.
+ The resulting frames are collected in the renderQueue
+ '''
while not self.stopped:
- i = self.compositeQueue.get()
+ audioI = self.compositeQueue.get()
+ bgI = int(audioI / self.sampleSize)
frame = None
for compNo, comp in reversed(list(enumerate(self.components))):
if compNo in self.staticComponents and \
self.staticComponents[compNo] is not None:
- if frame is None:
+ # static component
+ if frame is None: # bottom-most layer
frame = self.staticComponents[compNo]
else:
frame = Image.alpha_composite(
- frame, self.staticComponents[compNo])
+ frame, self.staticComponents[compNo]
+ )
else:
- if frame is None:
- frame = comp.frameRender(compNo, i[0], i[1])
+ # animated component
+ if frame is None: # bottom-most layer
+ frame = comp.frameRender(compNo, bgI)
else:
frame = Image.alpha_composite(
- frame, comp.frameRender(compNo, i[0], i[1]))
+ frame, comp.frameRender(compNo, bgI)
+ )
- self.renderQueue.put([i[0], frame])
+ self.renderQueue.put([audioI, frame])
self.compositeQueue.task_done()
def renderDispatch(self):
+ '''
+ Places audio data indices in the compositeQueue, to be used
+ by a renderNode later. All indices are multiples of self.sampleSize
+ sampleSize * frameNo = audioI, AKA audio data starting at frameNo
+ '''
print('Dispatching Frames for Compositing...')
- for i in range(0, len(self.completeAudioArray), self.sampleSize):
- self.compositeQueue.put([i, self.bgI])
- # increment tracked video frame for next iteration
- self.bgI += 1
+ for audioI in range(0, len(self.completeAudioArray), self.sampleSize):
+ self.compositeQueue.put(audioI)
def previewDispatch(self):
- background = Image.new("RGBA", (1920, 1080), (0, 0, 0, 0))
+ '''
+ Grabs frames from the previewQueue, adds them to the checkerboard
+ and emits a final QImage to the MainWindow for the live preview
+ '''
+ background = FloodFrame(1920, 1080, (0, 0, 0, 0))
background.paste(Image.open(os.path.join(
self.core.wd, "background.png")))
background = background.resize((self.width, self.height))
while not self.stopped:
- i = self.previewQueue.get()
- if time.time() - self.lastPreview >= 0.06 or i[0] == 0:
- image = copy(background)
- image = Image.alpha_composite(image, i[1])
- self._image = ImageQt(image)
- self.imageCreated.emit(QtGui.QImage(self._image))
+ audioI, frame = self.previewQueue.get()
+ if time.time() - self.lastPreview >= 0.06 or audioI == 0:
+ image = Image.alpha_composite(background.copy(), frame)
+ self.imageCreated.emit(ImageQt(image))
self.lastPreview = time.time()
self.previewQueue.task_done()
@@ -99,7 +115,6 @@ class Worker(QtCore.QObject):
self.reset()
- self.bgI = 0 # tracked video frame
self.width = int(self.core.settings.value('outputWidth'))
self.height = int(self.core.settings.value('outputHeight'))
progressBarValue = 0
@@ -194,8 +209,8 @@ class Worker(QtCore.QObject):
)
if properties and 'static' in properties:
- self.staticComponents[compNo] = copy(
- comp.frameRender(compNo, 0, 0))
+ self.staticComponents[compNo] = \
+ comp.frameRender(compNo, 0).copy()
self.progressBarUpdate.emit(100)
# Create ffmpeg pipe and queues for frames
@@ -231,9 +246,10 @@ class Worker(QtCore.QObject):
pStr = "Exporting video..."
self.progressBarSetText.emit(pStr)
if not self.canceled:
- for i in range(0, len(self.completeAudioArray), self.sampleSize):
+ for audioI in range(
+ 0, len(self.completeAudioArray), self.sampleSize):
while True:
- if i in frameBuffer or self.canceled:
+ if audioI in frameBuffer or self.canceled:
# if frame's in buffer, pipe it to ffmpeg
break
# else fetch the next frame & add to the buffer
@@ -244,15 +260,16 @@ class Worker(QtCore.QObject):
break
try:
- self.out_pipe.stdin.write(frameBuffer[i].tobytes())
- self.previewQueue.put([i, frameBuffer[i]])
- del frameBuffer[i]
+ self.out_pipe.stdin.write(frameBuffer[audioI].tobytes())
+ self.previewQueue.put([audioI, frameBuffer[audioI]])
+ del frameBuffer[audioI]
except:
break
# increase progress bar value
- if progressBarValue + 1 <= (i / len(self.completeAudioArray)) \
- * 100:
+ if progressBarValue + 1 <= (
+ audioI / len(self.completeAudioArray)
+ ) * 100:
progressBarValue = numpy.floor(
(i / len(self.completeAudioArray)) * 100)
self.progressBarUpdate.emit(progressBarValue)
--
cgit v1.2.3
From f6fbc8d2423ac5ae683a7613b53648db3e02e323 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 9 Jul 2017 14:31:19 -0400
Subject: a basic Sound component for mixing sounds
to be greatly expanded...
---
src/component.py | 20 ++++--
src/components/image.py | 3 +-
src/components/sound.py | 74 +++++++++++++++++++++
src/components/sound.ui | 122 ++++++++++++++++++++++++++++++++++
src/core.py | 5 +-
src/frame.py | 2 +-
src/mainwindow.py | 2 +
src/preview_thread.py | 9 ++-
src/video_thread.py | 169 ++++++++++++++++++++++++++++--------------------
9 files changed, 325 insertions(+), 81 deletions(-)
create mode 100644 src/components/sound.py
create mode 100644 src/components/sound.ui
diff --git a/src/component.py b/src/component.py
index 648a6d6..306072c 100644
--- a/src/component.py
+++ b/src/component.py
@@ -48,14 +48,18 @@ class Component(QtCore.QObject):
if presetName is not None else presetDict['preset']
def preFrameRender(self, **kwargs):
- ''' Triggered only before a video is exported (video_thread.py)
+ '''
+ Triggered only before a video is exported (video_thread.py)
self.worker = the video thread worker
self.completeAudioArray = a list of audio samples
self.sampleSize = number of audio samples per video frame
self.progressBarUpdate = signal to set progress bar number
self.progressBarSetText = signal to set progress bar text
- Use the latter two signals to update the MainWindow if needed
+ Use the latter two signals to update the MainWindow if needed
for a long initialization procedure (i.e., for a visualizer)
+
+ Return a list of properties to signify if your component is
+ non-animated ('static') or returns sound ('audio').
'''
for var, value in kwargs.items():
exec('self.%s = value' % var)
@@ -135,8 +139,8 @@ class Component(QtCore.QObject):
return page
def update(self):
- super().update()
self.parent.drawPreview()
+ super().update()
def previewRender(self, previewWorker):
width = int(previewWorker.core.settings.value('outputWidth'))
@@ -153,9 +157,17 @@ class Component(QtCore.QObject):
image = BlankFrame(width, height)
return image
+ def audio(self):
+ \'''
+ Return audio to mix into master as a string (path to audio file),
+ or an object that returns raw audio data [future feature].
+ \'''
+
@classmethod
def names(cls):
- # Alternative names for renaming a component between project files
+ \'''
+ Alternative names for renaming a component between project files.
+ \'''
return []
'''
diff --git a/src/components/image.py b/src/components/image.py
index 6edd893..55fa6dd 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -42,7 +42,6 @@ class Component(Component):
super().update()
def previewRender(self, previewWorker):
- self.imageFormats = previewWorker.core.imageFormats
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
return self.drawFrame(width, height)
@@ -110,7 +109,7 @@ class Component(Component):
imgDir = self.settings.value("componentDir", os.path.expanduser("~"))
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
self.page, "Choose Image", imgDir,
- "Image Files (%s)" % " ".join(self.imageFormats))
+ "Image Files (%s)" % " ".join(self.core.imageFormats))
if filename:
self.settings.setValue("componentDir", os.path.dirname(filename))
self.page.lineEdit_image.setText(filename)
diff --git a/src/components/sound.py b/src/components/sound.py
new file mode 100644
index 0000000..d3589b3
--- /dev/null
+++ b/src/components/sound.py
@@ -0,0 +1,74 @@
+from PyQt5 import QtGui, QtCore, QtWidgets
+import os
+
+from component import Component
+from frame import BlankFrame
+
+
+class Component(Component):
+ '''Sound'''
+
+ modified = QtCore.pyqtSignal(int, dict)
+
+ def widget(self, parent):
+ self.parent = parent
+ self.settings = parent.settings
+ page = self.loadUi('sound.ui')
+
+ page.lineEdit_sound.textChanged.connect(self.update)
+ page.pushButton_sound.clicked.connect(self.pickSound)
+
+ self.page = page
+ return page
+
+ def update(self):
+ self.sound = self.page.lineEdit_sound.text()
+ super().update()
+
+ def previewRender(self, previewWorker):
+ width = int(previewWorker.core.settings.value('outputWidth'))
+ height = int(previewWorker.core.settings.value('outputHeight'))
+ return self.frameRender(self.compPos, 0)
+
+ def preFrameRender(self, **kwargs):
+ # super().preFrameRender(**kwargs)
+ return ['static', 'audio']
+
+ def audio(self):
+ return self.sound
+
+ def pickSound(self):
+ sndDir = self.settings.value("componentDir", os.path.expanduser("~"))
+ filename, _ = QtWidgets.QFileDialog.getOpenFileName(
+ self.page, "Choose Sound", sndDir,
+ "Audio Files (%s)" % " ".join(self.core.audioFormats))
+ if filename:
+ self.settings.setValue("componentDir", os.path.dirname(filename))
+ self.page.lineEdit_sound.setText(filename)
+ self.update()
+
+ def frameRender(self, layerNo, frameNo):
+ width = int(self.core.settings.value('outputWidth'))
+ height = int(self.core.settings.value('outputHeight'))
+ return BlankFrame(width, height)
+
+ def loadPreset(self, pr, presetName=None):
+ super().loadPreset(pr, presetName)
+ self.page.lineEdit_sound.setText(pr['sound'])
+
+ def savePreset(self):
+ return {
+ 'preset': self.currentPreset,
+ 'sound': self.sound,
+ }
+
+ def commandHelp(self):
+ print('Path to audio file:\n path=/filepath/to/sound.ogg')
+
+ def command(self, arg):
+ if not arg.startswith('preset=') and '=' in arg:
+ key, arg = arg.split('=', 1)
+ if key == 'path':
+ self.page.lineEdit_sound.setText(arg)
+ return
+ super().command(arg)
diff --git a/src/components/sound.ui b/src/components/sound.ui
new file mode 100644
index 0000000..5fc00c1
--- /dev/null
+++ b/src/components/sound.ui
@@ -0,0 +1,122 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 586
+ 197
+
+
+
+ Form
+
+
+
-
+
+
+ 4
+
+
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 31
+ 0
+
+
+
+ Audio File
+
+
+
+ -
+
+
+
+ 1
+ 0
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 1
+ 0
+
+
+
+
+ 32
+ 32
+
+
+
+ ...
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
diff --git a/src/core.py b/src/core.py
index 9792e88..db430d1 100644
--- a/src/core.py
+++ b/src/core.py
@@ -485,7 +485,8 @@ class Core:
'-ac', '1', # mono (set to '2' for stereo)
'-']
in_pipe = toolkit.openPipe(
- command, stdout=sp.PIPE, stderr=sp.DEVNULL, bufsize=10**8)
+ command, stdout=sp.PIPE, stderr=sp.DEVNULL, bufsize=10**8
+ )
completeAudioArray = numpy.empty(0, dtype="int16")
@@ -495,7 +496,7 @@ class Core:
if self.canceled:
break
# read 2 seconds of audio
- progress = progress + 4
+ progress += 4
raw_audio = in_pipe.stdout.read(88200*4)
if len(raw_audio) == 0:
break
diff --git a/src/frame.py b/src/frame.py
index 57d33b0..c066cdb 100644
--- a/src/frame.py
+++ b/src/frame.py
@@ -14,7 +14,7 @@ class FramePainter(QtGui.QPainter):
'''
def __init__(self, width, height):
image = BlankFrame(width, height)
- self.image = ImageQt(image)
+ self.image = QtGui.QImage(ImageQt(image))
super().__init__(self.image)
def setPen(self, RgbTuple):
diff --git a/src/mainwindow.py b/src/mainwindow.py
index 165b5bd..3cd45d6 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -557,9 +557,11 @@ class MainWindow(QtWidgets.QMainWindow):
self.window.progressLabel.setHidden(True)
self.drawPreview(True)
+ @QtCore.pyqtSlot(int)
def progressBarUpdated(self, value):
self.window.progressBar_createVideo.setValue(value)
+ @QtCore.pyqtSlot(str)
def progressBarSetText(self, value):
if sys.platform == 'darwin':
self.window.progressLabel.setText(value)
diff --git a/src/preview_thread.py b/src/preview_thread.py
index 95a26ec..a72845b 100644
--- a/src/preview_thread.py
+++ b/src/preview_thread.py
@@ -69,10 +69,13 @@ class Worker(QtCore.QObject):
str(component),
detail=str(e),
icon='Warning',
- parent=None # mainwindow is in a different thread
+ parent=None # MainWindow is in a different thread
+ )
+ self.imageCreated.emit(
+ QtGui.QImage(ImageQt(
+ FloodFrame(width, height, (0, 0, 0, 0))
+ ))
)
- from frame import BlankFrame
- self.imageCreated.emit(ImageQt(BlankFrame))
self.error.emit()
break
else:
diff --git a/src/video_thread.py b/src/video_thread.py
index e7f1ac7..bd94be3 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -19,7 +19,7 @@ import time
import signal
import core
-from toolkit import openPipe
+from toolkit import openPipe, checkOutput
from frame import FloodFrame
@@ -102,32 +102,71 @@ class Worker(QtCore.QObject):
audioI, frame = self.previewQueue.get()
if time.time() - self.lastPreview >= 0.06 or audioI == 0:
image = Image.alpha_composite(background.copy(), frame)
- self.imageCreated.emit(ImageQt(image))
+ self.imageCreated.emit(QtGui.QImage(ImageQt(image)))
self.lastPreview = time.time()
self.previewQueue.task_done()
@pyqtSlot(str, str, list)
def createVideo(self, inputFile, outputFile, components):
+ numpy.seterr(divide='ignore')
self.encoding.emit(True)
self.components = components
self.outputFile = outputFile
-
- self.reset()
-
+ self.extraAudio = []
self.width = int(self.core.settings.value('outputWidth'))
self.height = int(self.core.settings.value('outputHeight'))
+
+ self.compositeQueue = Queue()
+ self.compositeQueue.maxsize = 20
+ self.renderQueue = PriorityQueue()
+ self.renderQueue.maxsize = 20
+ self.previewQueue = PriorityQueue()
+
+ self.reset()
progressBarValue = 0
self.progressBarUpdate.emit(progressBarValue)
- self.progressBarSetText.emit('Loading audio file...')
+ # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
+ # READ AUDIO AND INITIALIZE COMPONENTS
+ # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
+
+ self.progressBarSetText.emit("Loading audio file...")
self.completeAudioArray = self.core.readAudioFile(inputFile, self)
- # test if user has libfdk_aac
- encoders = sp.check_output(
- self.core.FFMPEG_BIN + " -encoders -hide_banner",
- shell=True)
+ self.progressBarUpdate.emit(0)
+ self.progressBarSetText.emit("Starting components...")
+ print('Loaded Components:', ", ".join([
+ "%s) %s" % (num, str(component))
+ for num, component in enumerate(reversed(self.components))
+ ]))
+ self.staticComponents = {}
+ numComps = len(self.components)
+ for compNo, comp in enumerate(self.components):
+ properties = None
+ properties = comp.preFrameRender(
+ worker=self,
+ completeAudioArray=self.completeAudioArray,
+ sampleSize=self.sampleSize,
+ progressBarUpdate=self.progressBarUpdate,
+ progressBarSetText=self.progressBarSetText
+ )
+
+ if properties:
+ if 'static' in properties:
+ self.staticComponents[compNo] = \
+ comp.frameRender(compNo, 0).copy()
+ if 'audio' in properties:
+ self.extraAudio.append(comp.audio())
+
+ # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
+ # DEDUCE ENCODERS
+ # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
+ # test if user has libfdk_aac
+ encoders = checkOutput(
+ "%s -encoders -hide_banner" % self.core.FFMPEG_BIN, shell=True
+ )
encoders = encoders.decode("utf-8")
acodec = self.core.settings.value('outputAudioCodec')
@@ -157,72 +196,66 @@ class Worker(QtCore.QObject):
aencoder = encoder
break
+ # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
+ # CREATE PIPE TO FFMPEG
+ # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
+
ffmpegCommand = [
self.core.FFMPEG_BIN,
'-thread_queue_size', '512',
'-y', # overwrite the output file if it already exists.
+
+ # INPUT VIDEO
'-f', 'rawvideo',
'-vcodec', 'rawvideo',
'-s', str(self.width)+'x'+str(self.height), # size of one frame
'-pix_fmt', 'rgba',
-
- # frames per second
'-r', self.core.settings.value('outputFrameRate'),
- '-i', '-', # The input comes from a pipe
- '-an',
- '-i', inputFile,
+ '-i', '-', # the video input comes from a pipe
+ '-an', # the video input has no sound
+
+ # INPUT SOUND
+ '-i', inputFile
+ ]
+
+ if self.extraAudio:
+ for extraInputFile in self.extraAudio:
+ ffmpegCommand.extend([
+ '-i', extraInputFile
+ ])
+ ffmpegCommand.extend([
+ '-filter_complex',
+ 'amix=inputs=%s:duration=longest:dropout_transition=3' % str(
+ len(self.extraAudio) + 1
+ )
+ ])
+
+ ffmpegCommand.extend([
+ # OUTPUT
'-vcodec', vencoder,
- '-acodec', aencoder, # output audio codec
+ '-acodec', aencoder,
'-b:v', vbitrate,
'-b:a', abitrate,
'-pix_fmt', self.core.settings.value('outputVideoFormat'),
'-preset', self.core.settings.value('outputPreset'),
'-f', container
- ]
+ ])
+ print(ffmpegCommand)
if acodec == 'aac':
ffmpegCommand.append('-strict')
ffmpegCommand.append('-2')
ffmpegCommand.append(outputFile)
-
- # ### Now start creating video for output ###
- numpy.seterr(divide='ignore')
-
- # Call preFrameRender on all components
- print('Loaded Components:', ", ".join([
- "%s) %s" % (num, str(component))
- for num, component in enumerate(reversed(self.components))
- ]))
- self.staticComponents = {}
- numComps = len(self.components)
- for compNo, comp in enumerate(self.components):
- pStr = "Starting components..."
- self.progressBarSetText.emit(pStr)
- properties = None
- properties = comp.preFrameRender(
- worker=self,
- completeAudioArray=self.completeAudioArray,
- sampleSize=self.sampleSize,
- progressBarUpdate=self.progressBarUpdate,
- progressBarSetText=self.progressBarSetText
- )
-
- if properties and 'static' in properties:
- self.staticComponents[compNo] = \
- comp.frameRender(compNo, 0).copy()
- self.progressBarUpdate.emit(100)
-
- # Create ffmpeg pipe and queues for frames
self.out_pipe = openPipe(
- ffmpegCommand, stdin=sp.PIPE, stdout=sys.stdout, stderr=sys.stdout)
- self.compositeQueue = Queue()
- self.compositeQueue.maxsize = 20
- self.renderQueue = PriorityQueue()
- self.renderQueue.maxsize = 20
- self.previewQueue = PriorityQueue()
+ ffmpegCommand, stdin=sp.PIPE, stdout=sys.stdout, stderr=sys.stdout
+ )
+
+ # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
+ # START CREATING THE VIDEO
+ # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
- # Threads to render frames and send them back here for piping out
+ # Make three renderNodes in new threads to create the frames
self.renderThreads = []
for i in range(3):
self.renderThreads.append(
@@ -235,16 +268,17 @@ class Worker(QtCore.QObject):
self.dispatchThread.daemon = True
self.dispatchThread.start()
+ self.lastPreview = 0.0
self.previewDispatch = Thread(
target=self.previewDispatch, name="Render Dispatch Thread")
self.previewDispatch.daemon = True
self.previewDispatch.start()
+ # Begin piping into ffmpeg!
frameBuffer = {}
- self.lastPreview = 0.0
- self.progressBarUpdate.emit(0)
- pStr = "Exporting video..."
- self.progressBarSetText.emit(pStr)
+ progressBarValue = 0
+ self.progressBarUpdate.emit(progressBarValue)
+ self.progressBarSetText.emit("Exporting video...")
if not self.canceled:
for audioI in range(
0, len(self.completeAudioArray), self.sampleSize):
@@ -253,29 +287,26 @@ class Worker(QtCore.QObject):
# if frame's in buffer, pipe it to ffmpeg
break
# else fetch the next frame & add to the buffer
- data = self.renderQueue.get()
- frameBuffer[data[0]] = data[1]
+ audioI_, frame = self.renderQueue.get()
+ frameBuffer[audioI_] = frame
self.renderQueue.task_done()
if self.canceled:
break
try:
self.out_pipe.stdin.write(frameBuffer[audioI].tobytes())
- self.previewQueue.put([audioI, frameBuffer[audioI]])
- del frameBuffer[audioI]
+ self.previewQueue.put([audioI, frameBuffer.pop(audioI)])
except:
break
# increase progress bar value
- if progressBarValue + 1 <= (
- audioI / len(self.completeAudioArray)
- ) * 100:
- progressBarValue = numpy.floor(
- (i / len(self.completeAudioArray)) * 100)
+ completion = (audioI / len(self.completeAudioArray)) * 100
+ if progressBarValue + 1 <= completion:
+ progressBarValue = numpy.floor(completion)
self.progressBarUpdate.emit(progressBarValue)
- pStr = "Exporting video: " + str(int(progressBarValue)) \
- + "%"
- self.progressBarSetText.emit(pStr)
+ self.progressBarSetText.emit(
+ "Exporting video: %s%%" % str(int(progressBarValue))
+ )
numpy.seterr(all='print')
--
cgit v1.2.3
From 4c3920e6309b4e67e3d8d809dd0b5b6cd245fd0c Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 9 Jul 2017 21:27:29 -0400
Subject: separated creation of ffmpeg command
for future use to sllow editing the command before starting the export
---
src/component.py | 10 +++--
src/components/color.py | 3 +-
src/components/image.py | 3 +-
src/components/sound.py | 4 +-
src/components/text.py | 3 +-
src/core.py | 93 ++++++++++++++++++++++++++++++++++++++++
src/video_thread.py | 110 +++++-------------------------------------------
7 files changed, 116 insertions(+), 110 deletions(-)
diff --git a/src/component.py b/src/component.py
index 306072c..7c2f753 100644
--- a/src/component.py
+++ b/src/component.py
@@ -27,6 +27,13 @@ class Component(QtCore.QObject):
# change this number to identify new versions of a component
return 1
+ def properties(self):
+ '''
+ Return a list of properties to signify if your component is
+ non-animated ('static') or returns sound ('audio').
+ '''
+ return []
+
def cancel(self):
# please stop any lengthy process in response to this variable
self.canceled = True
@@ -57,9 +64,6 @@ class Component(QtCore.QObject):
self.progressBarSetText = signal to set progress bar text
Use the latter two signals to update the MainWindow if needed
for a long initialization procedure (i.e., for a visualizer)
-
- Return a list of properties to signify if your component is
- non-animated ('static') or returns sound ('audio').
'''
for var, value in kwargs.items():
exec('self.%s = value' % var)
diff --git a/src/components/color.py b/src/components/color.py
index b87f3e9..82b45b3 100644
--- a/src/components/color.py
+++ b/src/components/color.py
@@ -117,8 +117,7 @@ class Component(Component):
height = int(previewWorker.core.settings.value('outputHeight'))
return self.drawFrame(width, height)
- def preFrameRender(self, **kwargs):
- super().preFrameRender(**kwargs)
+ def properties(self):
return ['static']
def frameRender(self, layerNo, frameNo):
diff --git a/src/components/image.py b/src/components/image.py
index 55fa6dd..94dcb83 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -46,8 +46,7 @@ class Component(Component):
height = int(previewWorker.core.settings.value('outputHeight'))
return self.drawFrame(width, height)
- def preFrameRender(self, **kwargs):
- super().preFrameRender(**kwargs)
+ def properties(self):
return ['static']
def frameRender(self, layerNo, frameNo):
diff --git a/src/components/sound.py b/src/components/sound.py
index d3589b3..1f43c83 100644
--- a/src/components/sound.py
+++ b/src/components/sound.py
@@ -31,7 +31,9 @@ class Component(Component):
return self.frameRender(self.compPos, 0)
def preFrameRender(self, **kwargs):
- # super().preFrameRender(**kwargs)
+ pass
+
+ def properties(self):
return ['static', 'audio']
def audio(self):
diff --git a/src/components/text.py b/src/components/text.py
index 2b1884f..fb6a90e 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -119,8 +119,7 @@ class Component(Component):
height = int(previewWorker.core.settings.value('outputHeight'))
return self.addText(width, height)
- def preFrameRender(self, **kwargs):
- super().preFrameRender(**kwargs)
+ def properties(self):
return ['static']
def frameRender(self, layerNo, frameNo):
diff --git a/src/core.py b/src/core.py
index db430d1..3d64c3b 100644
--- a/src/core.py
+++ b/src/core.py
@@ -460,6 +460,99 @@ class Core:
except sp.CalledProcessError:
return "avconv"
+ def createFfmpegCommand(self, inputFile, outputFile):
+ '''
+ Constructs the major ffmpeg command used to export the video
+ '''
+
+ # Test if user has libfdk_aac
+ encoders = toolkit.checkOutput(
+ "%s -encoders -hide_banner" % self.FFMPEG_BIN, shell=True
+ )
+ encoders = encoders.decode("utf-8")
+
+ acodec = self.settings.value('outputAudioCodec')
+
+ options = self.encoder_options
+ containerName = self.settings.value('outputContainer')
+ vcodec = self.settings.value('outputVideoCodec')
+ vbitrate = str(self.settings.value('outputVideoBitrate'))+'k'
+ acodec = self.settings.value('outputAudioCodec')
+ abitrate = str(self.settings.value('outputAudioBitrate'))+'k'
+
+ for cont in options['containers']:
+ if cont['name'] == containerName:
+ container = cont['container']
+ break
+
+ vencoders = options['video-codecs'][vcodec]
+ aencoders = options['audio-codecs'][acodec]
+
+ for encoder in vencoders:
+ if encoder in encoders:
+ vencoder = encoder
+ break
+
+ for encoder in aencoders:
+ if encoder in encoders:
+ aencoder = encoder
+ break
+
+ ffmpegCommand = [
+ self.FFMPEG_BIN,
+ '-thread_queue_size', '512',
+ '-y', # overwrite the output file if it already exists.
+
+ # INPUT VIDEO
+ '-f', 'rawvideo',
+ '-vcodec', 'rawvideo',
+ '-s', '%sx%s' % (
+ self.settings.value('outputWidth'),
+ self.settings.value('outputHeight'),
+ ),
+ '-pix_fmt', 'rgba',
+ '-r', self.settings.value('outputFrameRate'),
+ '-i', '-', # the video input comes from a pipe
+ '-an', # the video input has no sound
+
+ # INPUT SOUND
+ '-i', inputFile
+ ]
+
+ extraAudio = [
+ comp.audio() for comp in self.selectedComponents
+ if 'audio' in comp.properties()
+ ]
+ if extraAudio:
+ for extraInputFile in extraAudio:
+ ffmpegCommand.extend([
+ '-i', extraInputFile
+ ])
+ ffmpegCommand.extend([
+ '-filter_complex',
+ 'amix=inputs=%s:duration=longest:dropout_transition=3' % str(
+ len(extraAudio) + 1
+ )
+ ])
+
+ ffmpegCommand.extend([
+ # OUTPUT
+ '-vcodec', vencoder,
+ '-acodec', aencoder,
+ '-b:v', vbitrate,
+ '-b:a', abitrate,
+ '-pix_fmt', self.settings.value('outputVideoFormat'),
+ '-preset', self.settings.value('outputPreset'),
+ '-f', container
+ ])
+
+ if acodec == 'aac':
+ ffmpegCommand.append('-strict')
+ ffmpegCommand.append('-2')
+
+ ffmpegCommand.append(outputFile)
+ return ffmpegCommand
+
def readAudioFile(self, filename, parent):
command = [self.FFMPEG_BIN, '-i', filename]
diff --git a/src/video_thread.py b/src/video_thread.py
index bd94be3..dde71da 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -33,8 +33,8 @@ class Worker(QtCore.QObject):
def __init__(self, parent=None):
QtCore.QObject.__init__(self)
- self.core = core.Core()
- self.core.settings = parent.settings
+ self.core = parent.core
+ self.settings = parent.core.settings
self.modules = parent.core.modules
self.parent = parent
parent.videoTask.connect(self.createVideo)
@@ -114,8 +114,8 @@ class Worker(QtCore.QObject):
self.components = components
self.outputFile = outputFile
self.extraAudio = []
- self.width = int(self.core.settings.value('outputWidth'))
- self.height = int(self.core.settings.value('outputHeight'))
+ self.width = int(self.settings.value('outputWidth'))
+ self.height = int(self.settings.value('outputHeight'))
self.compositeQueue = Queue()
self.compositeQueue.maxsize = 20
@@ -128,7 +128,7 @@ class Worker(QtCore.QObject):
self.progressBarUpdate.emit(progressBarValue)
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
- # READ AUDIO AND INITIALIZE COMPONENTS
+ # READ AUDIO, INITIALIZE COMPONENTS, OPEN A PIPE TO FFMPEG
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
self.progressBarSetText.emit("Loading audio file...")
@@ -143,8 +143,7 @@ class Worker(QtCore.QObject):
self.staticComponents = {}
numComps = len(self.components)
for compNo, comp in enumerate(self.components):
- properties = None
- properties = comp.preFrameRender(
+ comp.preFrameRender(
worker=self,
completeAudioArray=self.completeAudioArray,
sampleSize=self.sampleSize,
@@ -152,101 +151,12 @@ class Worker(QtCore.QObject):
progressBarSetText=self.progressBarSetText
)
- if properties:
- if 'static' in properties:
- self.staticComponents[compNo] = \
- comp.frameRender(compNo, 0).copy()
- if 'audio' in properties:
- self.extraAudio.append(comp.audio())
+ if 'static' in comp.properties():
+ self.staticComponents[compNo] = \
+ comp.frameRender(compNo, 0).copy()
- # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
- # DEDUCE ENCODERS
- # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
-
- # test if user has libfdk_aac
- encoders = checkOutput(
- "%s -encoders -hide_banner" % self.core.FFMPEG_BIN, shell=True
- )
- encoders = encoders.decode("utf-8")
-
- acodec = self.core.settings.value('outputAudioCodec')
-
- options = self.core.encoder_options
- containerName = self.core.settings.value('outputContainer')
- vcodec = self.core.settings.value('outputVideoCodec')
- vbitrate = str(self.core.settings.value('outputVideoBitrate'))+'k'
- acodec = self.core.settings.value('outputAudioCodec')
- abitrate = str(self.core.settings.value('outputAudioBitrate'))+'k'
-
- for cont in options['containers']:
- if cont['name'] == containerName:
- container = cont['container']
- break
-
- vencoders = options['video-codecs'][vcodec]
- aencoders = options['audio-codecs'][acodec]
-
- for encoder in vencoders:
- if encoder in encoders:
- vencoder = encoder
- break
-
- for encoder in aencoders:
- if encoder in encoders:
- aencoder = encoder
- break
-
- # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
- # CREATE PIPE TO FFMPEG
- # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
-
- ffmpegCommand = [
- self.core.FFMPEG_BIN,
- '-thread_queue_size', '512',
- '-y', # overwrite the output file if it already exists.
-
- # INPUT VIDEO
- '-f', 'rawvideo',
- '-vcodec', 'rawvideo',
- '-s', str(self.width)+'x'+str(self.height), # size of one frame
- '-pix_fmt', 'rgba',
- '-r', self.core.settings.value('outputFrameRate'),
- '-i', '-', # the video input comes from a pipe
- '-an', # the video input has no sound
-
- # INPUT SOUND
- '-i', inputFile
- ]
-
- if self.extraAudio:
- for extraInputFile in self.extraAudio:
- ffmpegCommand.extend([
- '-i', extraInputFile
- ])
- ffmpegCommand.extend([
- '-filter_complex',
- 'amix=inputs=%s:duration=longest:dropout_transition=3' % str(
- len(self.extraAudio) + 1
- )
- ])
-
- ffmpegCommand.extend([
- # OUTPUT
- '-vcodec', vencoder,
- '-acodec', aencoder,
- '-b:v', vbitrate,
- '-b:a', abitrate,
- '-pix_fmt', self.core.settings.value('outputVideoFormat'),
- '-preset', self.core.settings.value('outputPreset'),
- '-f', container
- ])
+ ffmpegCommand = self.core.createFfmpegCommand(inputFile, outputFile)
print(ffmpegCommand)
-
- if acodec == 'aac':
- ffmpegCommand.append('-strict')
- ffmpegCommand.append('-2')
-
- ffmpegCommand.append(outputFile)
self.out_pipe = openPipe(
ffmpegCommand, stdin=sp.PIPE, stdout=sys.stdout, stderr=sys.stdout
)
--
cgit v1.2.3
From 2e37dafd7036973a315b525f131850a6fb6d0b35 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 11 Jul 2017 06:06:22 -0400
Subject: fixed various bugs
---
src/component.py | 9 ++++++++-
src/components/image.py | 10 +++++++++-
src/components/sound.py | 8 ++++----
src/components/text.py | 10 +++++-----
src/components/video.py | 21 +++++++++++++++++++++
src/components/video.ui | 17 +++++++++--------
src/core.py | 4 ++--
src/mainwindow.py | 4 ++++
src/preview_thread.py | 26 ++++++++++++--------------
src/video_thread.py | 9 +++++++++
10 files changed, 83 insertions(+), 35 deletions(-)
diff --git a/src/component.py b/src/component.py
index 7c2f753..eea82d7 100644
--- a/src/component.py
+++ b/src/component.py
@@ -30,10 +30,17 @@ class Component(QtCore.QObject):
def properties(self):
'''
Return a list of properties to signify if your component is
- non-animated ('static') or returns sound ('audio').
+ non-animated ('static'), returns sound ('audio'), or has
+ encountered an error in configuration ('error').
'''
return []
+ def error(self):
+ '''
+ Return a string containing an error message, or None for a default.
+ '''
+ return
+
def cancel(self):
# please stop any lengthy process in response to this variable
self.canceled = True
diff --git a/src/components/image.py b/src/components/image.py
index 94dcb83..07abc3f 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -47,7 +47,15 @@ class Component(Component):
return self.drawFrame(width, height)
def properties(self):
- return ['static']
+ props = ['static']
+ if not os.path.exists(self.imagePath):
+ props.append('error')
+ return props
+
+ def error(self):
+ if not os.path.exists(self.imagePath):
+ return "The image path selected on " \
+ "layer %s no longer exists!" % str(self.compPos)
def frameRender(self, layerNo, frameNo):
width = int(self.worker.core.settings.value('outputWidth'))
diff --git a/src/components/sound.py b/src/components/sound.py
index 1f43c83..9c114a8 100644
--- a/src/components/sound.py
+++ b/src/components/sound.py
@@ -28,7 +28,7 @@ class Component(Component):
def previewRender(self, previewWorker):
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
- return self.frameRender(self.compPos, 0)
+ return BlankFrame(width, height)
def preFrameRender(self, **kwargs):
pass
@@ -37,7 +37,7 @@ class Component(Component):
return ['static', 'audio']
def audio(self):
- return self.sound
+ return (self.sound, {})
def pickSound(self):
sndDir = self.settings.value("componentDir", os.path.expanduser("~"))
@@ -50,8 +50,8 @@ class Component(Component):
self.update()
def frameRender(self, layerNo, frameNo):
- width = int(self.core.settings.value('outputWidth'))
- height = int(self.core.settings.value('outputHeight'))
+ width = int(self.settings.value('outputWidth'))
+ height = int(self.settings.value('outputHeight'))
return BlankFrame(width, height)
def loadPreset(self, pr, presetName=None):
diff --git a/src/components/text.py b/src/components/text.py
index fb6a90e..ed50064 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -75,15 +75,15 @@ class Component(Component):
'''Returns true x, y after considering alignment settings'''
fm = QtGui.QFontMetrics(self.titleFont)
if self.alignment == 0: # Left
- x = self.xPosition
+ x = int(self.xPosition)
if self.alignment == 1: # Middle
offset = fm.width(self.title)/2
- x = self.xPosition - offset
+ x = int(self.xPosition - offset)
if self.alignment == 2: # Right
offset = fm.width(self.title)
- x = self.xPosition - offset
+ x = int(self.xPosition - offset)
return x, self.yPosition
def loadPreset(self, pr, presetName=None):
@@ -128,12 +128,12 @@ class Component(Component):
return self.addText(width, height)
def addText(self, width, height):
- x, y = self.getXY()
- image = FramePainter(width, height)
+ image = FramePainter(width, height)
self.titleFont.setPixelSize(self.fontSize)
image.setFont(self.titleFont)
image.setPen(self.textColor)
+ x, y = self.getXY()
image.drawText(x, y, self.title)
return image.finalize()
diff --git a/src/components/video.py b/src/components/video.py
index e6890e0..5303e3a 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -123,6 +123,7 @@ class Component(Component):
page.pushButton_video.clicked.connect(self.pickVideo)
page.checkBox_loop.stateChanged.connect(self.update)
page.checkBox_distort.stateChanged.connect(self.update)
+ page.checkBox_useAudio.stateChanged.connect(self.update)
page.spinBox_scale.valueChanged.connect(self.update)
page.spinBox_x.valueChanged.connect(self.update)
page.spinBox_y.valueChanged.connect(self.update)
@@ -133,6 +134,7 @@ class Component(Component):
def update(self):
self.videoPath = self.page.lineEdit_video.text()
self.loopVideo = self.page.checkBox_loop.isChecked()
+ self.useAudio = self.page.checkBox_useAudio.isChecked()
self.distort = self.page.checkBox_distort.isChecked()
self.scale = self.page.spinBox_scale.value()
self.xPosition = self.page.spinBox_x.value()
@@ -151,6 +153,23 @@ class Component(Component):
else:
return frame
+ def properties(self):
+ props = []
+ if self.useAudio:
+ # props.append('audio')
+ pass
+ if not os.path.exists(self.videoPath):
+ props.append('error')
+ return props
+
+ def error(self):
+ if not os.path.exists(self.videoPath):
+ return "The video path selected on " \
+ "layer %s no longer exists!" % str(self.compPos)
+
+ def audio(self):
+ return (self.videoPath, {})
+
def preFrameRender(self, **kwargs):
super().preFrameRender(**kwargs)
width = int(self.worker.core.settings.value('outputWidth'))
@@ -175,6 +194,7 @@ class Component(Component):
super().loadPreset(pr, presetName)
self.page.lineEdit_video.setText(pr['video'])
self.page.checkBox_loop.setChecked(pr['loop'])
+ self.page.checkBox_useAudio.setChecked(pr['useAudio'])
self.page.checkBox_distort.setChecked(pr['distort'])
self.page.spinBox_scale.setValue(pr['scale'])
self.page.spinBox_x.setValue(pr['x'])
@@ -185,6 +205,7 @@ class Component(Component):
'preset': self.currentPreset,
'video': self.videoPath,
'loop': self.loopVideo,
+ 'useAudio': self.useAudio,
'distort': self.distort,
'scale': self.scale,
'x': self.xPosition,
diff --git a/src/components/video.ui b/src/components/video.ui
index f05e8a5..97b7d6f 100644
--- a/src/components/video.ui
+++ b/src/components/video.ui
@@ -190,16 +190,20 @@
-
-
+
+
+ Use Audio
+
+
+
+ -
+
Qt::Horizontal
-
- QSizePolicy::Fixed
-
- 5
+ 40
20
@@ -256,9 +260,6 @@
- -
-
-
diff --git a/src/core.py b/src/core.py
index 3d64c3b..450e43b 100644
--- a/src/core.py
+++ b/src/core.py
@@ -524,7 +524,7 @@ class Core:
if 'audio' in comp.properties()
]
if extraAudio:
- for extraInputFile in extraAudio:
+ for extraInputFile, params in extraAudio:
ffmpegCommand.extend([
'-i', extraInputFile
])
@@ -532,7 +532,7 @@ class Core:
'-filter_complex',
'amix=inputs=%s:duration=longest:dropout_transition=3' % str(
len(extraAudio) + 1
- )
+ ),
])
ffmpegCommand.extend([
diff --git a/src/mainwindow.py b/src/mainwindow.py
index 3cd45d6..d21ba0a 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -713,6 +713,10 @@ class MainWindow(QtWidgets.QMainWindow):
def saveCurrentProject(self):
if self.currentProject:
self.core.createProjectFile(self.currentProject, self.window)
+ try:
+ os.remove(self.autosavePath)
+ except FileNotFoundError:
+ pass
self.updateWindowTitle()
else:
self.openSaveProjectDialog()
diff --git a/src/preview_thread.py b/src/preview_thread.py
index a72845b..fb3b792 100644
--- a/src/preview_thread.py
+++ b/src/preview_thread.py
@@ -25,8 +25,8 @@ class Worker(QtCore.QObject):
self.parent = parent
self.core = self.parent.core
self.queue = queue
- self.core.settings = parent.settings
- self.stackedWidget = parent.window.stackedWidget
+ self.width = int(self.core.settings.value('outputWidth'))
+ self.height = int(self.core.settings.value('outputHeight'))
# create checkerboard background to represent transparency
self.background = FloodFrame(1920, 1080, (0, 0, 0, 0))
@@ -50,10 +50,10 @@ class Worker(QtCore.QObject):
except Empty:
continue
- width = int(self.core.settings.value('outputWidth'))
- height = int(self.core.settings.value('outputHeight'))
+ if self.background.width != self.width:
+ self.background = self.background.resize(
+ (self.width, self.height))
frame = self.background.copy()
- frame = frame.resize((width, height))
components = nextPreviewInformation["components"]
for component in reversed(components):
@@ -63,23 +63,21 @@ class Worker(QtCore.QObject):
)
except ValueError as e:
+ errMsg = "Bad frame returned by %s's preview renderer. " \
+ "%s. This is a fatal error." % (
+ str(component), str(e).capitalize()
+ )
+ print(errMsg)
self.parent.showMessage(
- msg="Bad frame returned by %s's previewRender method. "
- "This is a fatal error." %
- str(component),
+ msg=errMsg,
detail=str(e),
icon='Warning',
parent=None # MainWindow is in a different thread
)
- self.imageCreated.emit(
- QtGui.QImage(ImageQt(
- FloodFrame(width, height, (0, 0, 0, 0))
- ))
- )
self.error.emit()
break
else:
- self.imageCreated.emit(ImageQt(frame))
+ self.imageCreated.emit(QtGui.QImage(ImageQt(frame)))
except Empty:
True
diff --git a/src/video_thread.py b/src/video_thread.py
index dde71da..b00d512 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -151,6 +151,15 @@ class Worker(QtCore.QObject):
progressBarSetText=self.progressBarSetText
)
+ if 'error' in comp.properties():
+ self.canceled = True
+ errMsg = "Component #%s encountered an error!" % compNo \
+ if comp.error() is None else comp.error()
+ self.parent.showMessage(
+ msg=errMsg,
+ icon='Warning',
+ parent=None # MainWindow is in a different thread
+ )
if 'static' in comp.properties():
self.staticComponents[compNo] = \
comp.frameRender(compNo, 0).copy()
--
cgit v1.2.3
From 8811b699a9c2d6b78af1e2a332d3031aef73aec4 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 13 Jul 2017 00:05:11 -0400
Subject: merge consecutive static components
---
src/components/color.py | 13 +++++++------
src/components/image.py | 16 ++++++++--------
src/components/original.py | 9 +++++----
src/components/text.py | 21 +++++++++++----------
src/components/video.py | 6 +++---
src/core.py | 2 ++
src/frame.py | 21 ++++++++++++++++++++-
src/mainwindow.py | 2 ++
src/preview_thread.py | 38 +++++++++++++++++++++++---------------
src/video_thread.py | 27 +++++++++++++++++++--------
10 files changed, 100 insertions(+), 55 deletions(-)
diff --git a/src/components/color.py b/src/components/color.py
index 82b45b3..da3bcf9 100644
--- a/src/components/color.py
+++ b/src/components/color.py
@@ -15,6 +15,7 @@ class Component(Component):
def widget(self, parent):
self.parent = parent
+ self.settings = self.parent.core.settings
page = self.loadUi('color.ui')
self.color1 = (0, 0, 0)
@@ -42,9 +43,9 @@ class Component(Component):
page.spinBox_x.valueChanged.connect(self.update)
page.spinBox_y.valueChanged.connect(self.update)
page.spinBox_width.setValue(
- int(parent.settings.value("outputWidth")))
+ int(self.settings.value("outputWidth")))
page.spinBox_height.setValue(
- int(parent.settings.value("outputHeight")))
+ int(self.settings.value("outputHeight")))
page.lineEdit_color1.textChanged.connect(self.update)
page.lineEdit_color2.textChanged.connect(self.update)
@@ -113,16 +114,16 @@ class Component(Component):
super().update()
def previewRender(self, previewWorker):
- width = int(previewWorker.core.settings.value('outputWidth'))
- height = int(previewWorker.core.settings.value('outputHeight'))
+ width = int(self.settings.value('outputWidth'))
+ height = int(self.settings.value('outputHeight'))
return self.drawFrame(width, height)
def properties(self):
return ['static']
def frameRender(self, layerNo, frameNo):
- width = int(self.worker.core.settings.value('outputWidth'))
- height = int(self.worker.core.settings.value('outputHeight'))
+ width = int(self.settings.value('outputWidth'))
+ height = int(self.settings.value('outputHeight'))
return self.drawFrame(width, height)
def drawFrame(self, width, height):
diff --git a/src/components/image.py b/src/components/image.py
index 07abc3f..6465bc9 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -13,7 +13,7 @@ class Component(Component):
def widget(self, parent):
self.parent = parent
- self.settings = parent.settings
+ self.settings = self.parent.core.settings
page = self.loadUi('image.ui')
page.lineEdit_image.textChanged.connect(self.update)
@@ -42,24 +42,24 @@ class Component(Component):
super().update()
def previewRender(self, previewWorker):
- width = int(previewWorker.core.settings.value('outputWidth'))
- height = int(previewWorker.core.settings.value('outputHeight'))
+ width = int(self.settings.value('outputWidth'))
+ height = int(self.settings.value('outputHeight'))
return self.drawFrame(width, height)
def properties(self):
props = ['static']
- if not os.path.exists(self.imagePath):
+ if self.imagePath and not os.path.exists(self.imagePath):
props.append('error')
return props
def error(self):
if not os.path.exists(self.imagePath):
- return "The image path selected on " \
- "layer %s no longer exists!" % str(self.compPos)
+ return "The image selected on " \
+ "layer %s does not exist!" % str(self.compPos)
def frameRender(self, layerNo, frameNo):
- width = int(self.worker.core.settings.value('outputWidth'))
- height = int(self.worker.core.settings.value('outputHeight'))
+ width = int(self.settings.value('outputWidth'))
+ height = int(self.settings.value('outputHeight'))
return self.drawFrame(width, height)
def drawFrame(self, width, height):
diff --git a/src/components/original.py b/src/components/original.py
index 638095d..3599c30 100644
--- a/src/components/original.py
+++ b/src/components/original.py
@@ -21,6 +21,7 @@ class Component(Component):
def widget(self, parent):
self.parent = parent
+ self.settings = self.parent.core.settings
self.visColor = (255, 255, 255)
self.scale = 20
self.y = 0
@@ -76,8 +77,8 @@ class Component(Component):
def previewRender(self, previewWorker):
spectrum = numpy.fromfunction(
lambda x: float(self.scale)/2500*(x-128)**2, (255,), dtype="int16")
- width = int(previewWorker.core.settings.value('outputWidth'))
- height = int(previewWorker.core.settings.value('outputHeight'))
+ width = int(self.settings.value('outputWidth'))
+ height = int(self.settings.value('outputHeight'))
return self.drawBars(
width, height, spectrum, self.visColor, self.layout
)
@@ -88,8 +89,8 @@ class Component(Component):
self.smoothConstantUp = 0.8
self.lastSpectrum = None
self.spectrumArray = {}
- self.width = int(self.worker.core.settings.value('outputWidth'))
- self.height = int(self.worker.core.settings.value('outputHeight'))
+ self.width = int(self.settings.value('outputWidth'))
+ self.height = int(self.settings.value('outputHeight'))
for i in range(0, len(self.completeAudioArray), self.sampleSize):
if self.canceled:
diff --git a/src/components/text.py b/src/components/text.py
index ed50064..4435b80 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -17,10 +17,11 @@ class Component(Component):
self.titleFont = QFont()
def widget(self, parent):
- height = int(parent.settings.value('outputHeight'))
- width = int(parent.settings.value('outputWidth'))
-
self.parent = parent
+ self.settings = self.parent.core.settings
+ height = int(self.settings.value('outputHeight'))
+ width = int(self.settings.value('outputWidth'))
+
self.textColor = (255, 255, 255)
self.title = 'Text'
self.alignment = 1
@@ -78,12 +79,12 @@ class Component(Component):
x = int(self.xPosition)
if self.alignment == 1: # Middle
- offset = fm.width(self.title)/2
- x = int(self.xPosition - offset)
+ offset = int(fm.width(self.title)/2)
+ x = self.xPosition - offset
if self.alignment == 2: # Right
offset = fm.width(self.title)
- x = int(self.xPosition - offset)
+ x = self.xPosition - offset
return x, self.yPosition
def loadPreset(self, pr, presetName=None):
@@ -115,16 +116,16 @@ class Component(Component):
}
def previewRender(self, previewWorker):
- width = int(previewWorker.core.settings.value('outputWidth'))
- height = int(previewWorker.core.settings.value('outputHeight'))
+ width = int(self.settings.value('outputWidth'))
+ height = int(self.settings.value('outputHeight'))
return self.addText(width, height)
def properties(self):
return ['static']
def frameRender(self, layerNo, frameNo):
- width = int(self.worker.core.settings.value('outputWidth'))
- height = int(self.worker.core.settings.value('outputHeight'))
+ width = int(self.settings.value('outputWidth'))
+ height = int(self.settings.value('outputHeight'))
return self.addText(width, height)
def addText(self, width, height):
diff --git a/src/components/video.py b/src/components/video.py
index 5303e3a..49bd145 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -158,14 +158,14 @@ class Component(Component):
if self.useAudio:
# props.append('audio')
pass
- if not os.path.exists(self.videoPath):
+ if self.videoPath and not os.path.exists(self.videoPath):
props.append('error')
return props
def error(self):
if not os.path.exists(self.videoPath):
- return "The video path selected on " \
- "layer %s no longer exists!" % str(self.compPos)
+ return "The video selected on " \
+ "layer %s does not exist!" % str(self.compPos)
def audio(self):
return (self.videoPath, {})
diff --git a/src/core.py b/src/core.py
index 450e43b..64f55eb 100644
--- a/src/core.py
+++ b/src/core.py
@@ -11,6 +11,7 @@ from importlib import import_module
from PyQt5.QtCore import QStandardPaths
import toolkit
+from frame import Frame
class Core:
@@ -20,6 +21,7 @@ class Core:
opens projects and presets, and stores settings/paths to data.
'''
def __init__(self):
+ Frame.core = self
self.dataDir = QStandardPaths.writableLocation(
QStandardPaths.AppConfigLocation
)
diff --git a/src/frame.py b/src/frame.py
index c066cdb..cddb611 100644
--- a/src/frame.py
+++ b/src/frame.py
@@ -5,6 +5,11 @@ from PyQt5 import QtGui
from PIL import Image
from PIL.ImageQt import ImageQt
import sys
+import os
+
+
+class Frame:
+ '''Controller class for all frames.'''
class FramePainter(QtGui.QPainter):
@@ -43,5 +48,19 @@ def FloodFrame(width, height, RgbaTuple):
def BlankFrame(width, height):
- '''The base frame used by each component to start drawing'''
+ '''The base frame used by each component to start drawing.'''
return FloodFrame(width, height, (0, 0, 0, 0))
+
+
+def Checkerboard(width, height):
+ '''
+ A checkerboard to represent transparency to the user.
+ TODO: Would be cool to generate this image with numpy instead.
+ '''
+ image = FloodFrame(1920, 1080, (0, 0, 0, 0))
+ image.paste(Image.open(
+ os.path.join(Frame.core.wd, "background.png")),
+ (0, 0)
+ )
+ image = image.resize((width, height))
+ return image
diff --git a/src/mainwindow.py b/src/mainwindow.py
index d21ba0a..771b6b8 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -306,6 +306,7 @@ class MainWindow(QtWidgets.QMainWindow):
QtWidgets.QShortcut("Ctrl+A", self.window, self.openSaveProjectDialog)
QtWidgets.QShortcut("Ctrl+O", self.window, self.openOpenProjectDialog)
QtWidgets.QShortcut("Ctrl+N", self.window, self.createNewProject)
+ QtWidgets.QShortcut("Ctrl+Alt+Shift+R", self.window, self.drawPreview)
QtWidgets.QShortcut(
"Ctrl+T", self.window,
@@ -585,6 +586,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.autosave(force)
self.updateWindowTitle()
+ @QtCore.pyqtSlot(QtGui.QImage)
def showPreviewImage(self, image):
self.previewWindow.changePixmap(image)
diff --git a/src/preview_thread.py b/src/preview_thread.py
index fb3b792..4ffb7f6 100644
--- a/src/preview_thread.py
+++ b/src/preview_thread.py
@@ -10,12 +10,12 @@ import core
from queue import Queue, Empty
import os
-from frame import FloodFrame
+from frame import Checkerboard
class Worker(QtCore.QObject):
- imageCreated = pyqtSignal(['QImage'])
+ imageCreated = pyqtSignal(QtGui.QImage)
error = pyqtSignal()
def __init__(self, parent=None, queue=None):
@@ -24,14 +24,12 @@ class Worker(QtCore.QObject):
parent.processTask.connect(self.process)
self.parent = parent
self.core = self.parent.core
+ self.settings = self.parent.core.settings
self.queue = queue
- self.width = int(self.core.settings.value('outputWidth'))
- self.height = int(self.core.settings.value('outputHeight'))
- # create checkerboard background to represent transparency
- self.background = FloodFrame(1920, 1080, (0, 0, 0, 0))
- self.background.paste(Image.open(os.path.join(
- self.core.wd, "background.png")))
+ width = int(self.settings.value('outputWidth'))
+ height = int(self.settings.value('outputHeight'))
+ self.background = Checkerboard(width, height)
@pyqtSlot(list)
def createPreviewImage(self, components):
@@ -42,6 +40,8 @@ class Worker(QtCore.QObject):
@pyqtSlot()
def process(self):
+ width = int(self.settings.value('outputWidth'))
+ height = int(self.settings.value('outputHeight'))
try:
nextPreviewInformation = self.queue.get(block=False)
while self.queue.qsize() >= 2:
@@ -50,22 +50,27 @@ class Worker(QtCore.QObject):
except Empty:
continue
- if self.background.width != self.width:
- self.background = self.background.resize(
- (self.width, self.height))
+ if self.background.width != width \
+ or self.background.height != height:
+ self.background = Checkerboard(width, height)
+
frame = self.background.copy()
components = nextPreviewInformation["components"]
for component in reversed(components):
try:
+ newFrame = component.previewRender(self)
frame = Image.alpha_composite(
- frame, component.previewRender(self)
+ frame, newFrame
)
except ValueError as e:
errMsg = "Bad frame returned by %s's preview renderer. " \
- "%s. This is a fatal error." % (
- str(component), str(e).capitalize()
+ "%s. New frame size was %s*%s; should be %s*%s. " \
+ "This is a fatal error." % (
+ str(component), str(e).capitalize(),
+ newFrame.width, newFrame.height,
+ width, height
)
print(errMsg)
self.parent.showMessage(
@@ -76,8 +81,11 @@ class Worker(QtCore.QObject):
)
self.error.emit()
break
+ except RuntimeError as e:
+ print(e)
else:
- self.imageCreated.emit(QtGui.QImage(ImageQt(frame)))
+ self.frame = ImageQt(frame)
+ self.imageCreated.emit(QtGui.QImage(self.frame))
except Empty:
True
diff --git a/src/video_thread.py b/src/video_thread.py
index b00d512..f736013 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -20,7 +20,7 @@ import signal
import core
from toolkit import openPipe, checkOutput
-from frame import FloodFrame
+from frame import Checkerboard
class Worker(QtCore.QObject):
@@ -56,8 +56,10 @@ class Worker(QtCore.QObject):
frame = None
for compNo, comp in reversed(list(enumerate(self.components))):
- if compNo in self.staticComponents and \
- self.staticComponents[compNo] is not None:
+ if compNo in self.staticComponents:
+ if self.staticComponents[compNo] is None:
+ # this layer was merged into a following layer
+ continue
# static component
if frame is None: # bottom-most layer
frame = self.staticComponents[compNo]
@@ -93,10 +95,7 @@ class Worker(QtCore.QObject):
Grabs frames from the previewQueue, adds them to the checkerboard
and emits a final QImage to the MainWindow for the live preview
'''
- background = FloodFrame(1920, 1080, (0, 0, 0, 0))
- background.paste(Image.open(os.path.join(
- self.core.wd, "background.png")))
- background = background.resize((self.width, self.height))
+ background = Checkerboard(self.width, self.height)
while not self.stopped:
audioI, frame = self.previewQueue.get()
@@ -164,8 +163,20 @@ class Worker(QtCore.QObject):
self.staticComponents[compNo] = \
comp.frameRender(compNo, 0).copy()
+ # Merge consecutive static component frames together
+ for compNo in range(len(self.components), 0, -1):
+ if compNo not in self.staticComponents \
+ or compNo - 1 not in self.staticComponents:
+ continue
+ self.staticComponents[compNo - 1] = Image.alpha_composite(
+ self.staticComponents.pop(compNo),
+ self.staticComponents[compNo - 1]
+ )
+ self.staticComponents[compNo] = None
+
ffmpegCommand = self.core.createFfmpegCommand(inputFile, outputFile)
- print(ffmpegCommand)
+ print('###### FFMPEG COMMAND ######\n %s' % " ".join(ffmpegCommand))
+ print('###### -------------- ######')
self.out_pipe = openPipe(
ffmpegCommand, stdin=sp.PIPE, stdout=sys.stdout, stderr=sys.stdout
)
--
cgit v1.2.3
From b7931572a73d408dceecc4b17b784a0338e0e35b Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 13 Jul 2017 14:46:22 -0400
Subject: added option to include audio from Video components
---
src/components/video.py | 8 +++-----
src/core.py | 16 ++++++++++++++--
src/video_thread.py | 4 ++--
3 files changed, 19 insertions(+), 9 deletions(-)
diff --git a/src/components/video.py b/src/components/video.py
index 49bd145..53487b1 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -143,7 +143,6 @@ class Component(Component):
super().update()
def previewRender(self, previewWorker):
- self.videoFormats = previewWorker.core.videoFormats
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
self.updateChunksize(width, height)
@@ -156,8 +155,7 @@ class Component(Component):
def properties(self):
props = []
if self.useAudio:
- # props.append('audio')
- pass
+ props.append('audio')
if self.videoPath and not os.path.exists(self.videoPath):
props.append('error')
return props
@@ -168,7 +166,7 @@ class Component(Component):
"layer %s does not exist!" % str(self.compPos)
def audio(self):
- return (self.videoPath, {})
+ return (self.videoPath, {'map': '-v'})
def preFrameRender(self, **kwargs):
super().preFrameRender(**kwargs)
@@ -216,7 +214,7 @@ class Component(Component):
imgDir = self.settings.value("componentDir", os.path.expanduser("~"))
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
self.page, "Choose Video",
- imgDir, "Video Files (%s)" % " ".join(self.videoFormats)
+ imgDir, "Video Files (%s)" % " ".join(self.core.videoFormats)
)
if filename:
self.settings.setValue("componentDir", os.path.dirname(filename))
diff --git a/src/core.py b/src/core.py
index 64f55eb..d72760d 100644
--- a/src/core.py
+++ b/src/core.py
@@ -526,13 +526,25 @@ class Core:
if 'audio' in comp.properties()
]
if extraAudio:
- for extraInputFile, params in extraAudio:
+ unwantedVideoStreams = []
+ for compNo, params in enumerate(extraAudio):
+ extraInputFile, params = params
ffmpegCommand.extend([
'-i', extraInputFile
])
+ if 'map' in params and params['map'] == '-v':
+ # a video stream to remove
+ unwantedVideoStreams.append(compNo + 1)
+
+ if unwantedVideoStreams:
+ ffmpegCommand.extend(['-map', '0'])
+ for compNo in unwantedVideoStreams:
+ ffmpegCommand.extend([
+ '-map', '-%s:v' % str(compNo)
+ ])
ffmpegCommand.extend([
'-filter_complex',
- 'amix=inputs=%s:duration=longest:dropout_transition=3' % str(
+ 'amix=inputs=%s:duration=first:dropout_transition=3' % str(
len(extraAudio) + 1
),
])
diff --git a/src/video_thread.py b/src/video_thread.py
index f736013..bfb0cc4 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -175,8 +175,8 @@ class Worker(QtCore.QObject):
self.staticComponents[compNo] = None
ffmpegCommand = self.core.createFfmpegCommand(inputFile, outputFile)
- print('###### FFMPEG COMMAND ######\n %s' % " ".join(ffmpegCommand))
- print('###### -------------- ######')
+ print('###### FFMPEG COMMAND ######\n%s' % " ".join(ffmpegCommand))
+ print('############################')
self.out_pipe = openPipe(
ffmpegCommand, stdin=sp.PIPE, stdout=sys.stdout, stderr=sys.stdout
)
--
cgit v1.2.3
From 06c27a48bc3f52e15c15445d822e8a6f523ab98f Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 13 Jul 2017 17:03:25 -0400
Subject: more error messages for blank components
---
src/components/image.py | 7 ++++---
src/components/sound.py | 11 ++++++++++-
src/components/text.py | 8 +++++++-
src/components/video.py | 13 ++++++++++---
src/video_thread.py | 28 ++++++++++++++++++++++------
5 files changed, 53 insertions(+), 14 deletions(-)
diff --git a/src/components/image.py b/src/components/image.py
index 6465bc9..6a70424 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -48,14 +48,15 @@ class Component(Component):
def properties(self):
props = ['static']
- if self.imagePath and not os.path.exists(self.imagePath):
+ if not os.path.exists(self.imagePath):
props.append('error')
return props
def error(self):
+ if not self.imagePath:
+ return "There is no image selected."
if not os.path.exists(self.imagePath):
- return "The image selected on " \
- "layer %s does not exist!" % str(self.compPos)
+ return "The image selected does not exist!"
def frameRender(self, layerNo, frameNo):
width = int(self.settings.value('outputWidth'))
diff --git a/src/components/sound.py b/src/components/sound.py
index 9c114a8..2ffb682 100644
--- a/src/components/sound.py
+++ b/src/components/sound.py
@@ -34,7 +34,16 @@ class Component(Component):
pass
def properties(self):
- return ['static', 'audio']
+ props = ['static', 'audio']
+ if not os.path.exists(self.sound):
+ props.append('error')
+ return props
+
+ def error(self):
+ if not self.sound:
+ return "No audio file selected."
+ if not os.path.exists(self.sound):
+ return "The audio file selected no longer exists!"
def audio(self):
return (self.sound, {})
diff --git a/src/components/text.py b/src/components/text.py
index 4435b80..c52bdc5 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -121,7 +121,13 @@ class Component(Component):
return self.addText(width, height)
def properties(self):
- return ['static']
+ props = ['static']
+ if not self.title:
+ props.append('error')
+ return props
+
+ def error(self):
+ return "No text provided."
def frameRender(self, layerNo, frameNo):
width = int(self.settings.value('outputWidth'))
diff --git a/src/components/video.py b/src/components/video.py
index 53487b1..8861d70 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -115,6 +115,7 @@ class Component(Component):
self.settings = parent.settings
page = self.loadUi('video.ui')
self.videoPath = ''
+ self.badVideo = False
self.x = 0
self.y = 0
self.loopVideo = False
@@ -156,14 +157,18 @@ class Component(Component):
props = []
if self.useAudio:
props.append('audio')
- if self.videoPath and not os.path.exists(self.videoPath):
+ if not self.videoPath or self.badVideo \
+ or not os.path.exists(self.videoPath):
props.append('error')
return props
def error(self):
+ if not self.videoPath:
+ return "There is no video selected."
if not os.path.exists(self.videoPath):
- return "The video selected on " \
- "layer %s does not exist!" % str(self.compPos)
+ return "The video selected does not exist!"
+ if self.badVideo:
+ return "The video selected is corrupt!"
def audio(self):
return (self.videoPath, {'map': '-v'})
@@ -300,6 +305,7 @@ def finalizeFrame(self, imageData, width, height):
'### BAD VIDEO SELECTED ###\n'
'Video will not export with these settings'
)
+ self.badVideo = True
return BlankFrame(width, height)
if self.scale != 100 \
@@ -308,4 +314,5 @@ def finalizeFrame(self, imageData, width, height):
frame.paste(image, box=(self.xPosition, self.yPosition))
else:
frame = image
+ self.badVideo = False
return frame
diff --git a/src/video_thread.py b/src/video_thread.py
index bfb0cc4..9ce9cc8 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -141,7 +141,7 @@ class Worker(QtCore.QObject):
]))
self.staticComponents = {}
numComps = len(self.components)
- for compNo, comp in enumerate(self.components):
+ for compNo, comp in enumerate(reversed(self.components)):
comp.preFrameRender(
worker=self,
completeAudioArray=self.completeAudioArray,
@@ -151,26 +151,41 @@ class Worker(QtCore.QObject):
)
if 'error' in comp.properties():
+ self.cancel()
self.canceled = True
errMsg = "Component #%s encountered an error!" % compNo \
- if comp.error() is None else comp.error()
+ if comp.error() is None else 'Component #%s (%s): %s' % (
+ str(compNo),
+ str(comp),
+ comp.error()
+ )
self.parent.showMessage(
msg=errMsg,
icon='Warning',
parent=None # MainWindow is in a different thread
)
+ break
if 'static' in comp.properties():
self.staticComponents[compNo] = \
comp.frameRender(compNo, 0).copy()
+ if self.canceled:
+ print('Export cancelled by component #%s (%s): %s' % (
+ compNo, str(comp), comp.error()
+ ))
+ self.progressBarSetText.emit('Export Canceled')
+ self.encoding.emit(False)
+ self.videoCreated.emit()
+ return
+
# Merge consecutive static component frames together
- for compNo in range(len(self.components), 0, -1):
+ for compNo in range(len(self.components)):
if compNo not in self.staticComponents \
- or compNo - 1 not in self.staticComponents:
+ or compNo + 1 not in self.staticComponents:
continue
- self.staticComponents[compNo - 1] = Image.alpha_composite(
+ self.staticComponents[compNo + 1] = Image.alpha_composite(
self.staticComponents.pop(compNo),
- self.staticComponents[compNo - 1]
+ self.staticComponents[compNo + 1]
)
self.staticComponents[compNo] = None
@@ -278,6 +293,7 @@ class Worker(QtCore.QObject):
def cancel(self):
self.canceled = True
+ self.stopped = True
self.core.cancel()
for comp in self.components:
--
cgit v1.2.3
From d7b678f66d1bb1d5c7ccbbf0c8871b66cc1f8750 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 13 Jul 2017 19:31:00 -0400
Subject: staticComponents list is reversed now
---
src/core.py | 8 ++++----
src/video_thread.py | 10 +++++-----
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/src/core.py b/src/core.py
index d72760d..3f0a6ad 100644
--- a/src/core.py
+++ b/src/core.py
@@ -527,20 +527,20 @@ class Core:
]
if extraAudio:
unwantedVideoStreams = []
- for compNo, params in enumerate(extraAudio):
+ for streamNo, params in enumerate(extraAudio):
extraInputFile, params = params
ffmpegCommand.extend([
'-i', extraInputFile
])
if 'map' in params and params['map'] == '-v':
# a video stream to remove
- unwantedVideoStreams.append(compNo + 1)
+ unwantedVideoStreams.append(streamNo + 1)
if unwantedVideoStreams:
ffmpegCommand.extend(['-map', '0'])
- for compNo in unwantedVideoStreams:
+ for streamNo in unwantedVideoStreams:
ffmpegCommand.extend([
- '-map', '-%s:v' % str(compNo)
+ '-map', '-%s:v' % str(streamNo)
])
ffmpegCommand.extend([
'-filter_complex',
diff --git a/src/video_thread.py b/src/video_thread.py
index 9ce9cc8..b0562db 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -54,18 +54,18 @@ class Worker(QtCore.QObject):
audioI = self.compositeQueue.get()
bgI = int(audioI / self.sampleSize)
frame = None
-
for compNo, comp in reversed(list(enumerate(self.components))):
- if compNo in self.staticComponents:
- if self.staticComponents[compNo] is None:
+ layerNo = len(self.components) - compNo
+ if layerNo in self.staticComponents:
+ if self.staticComponents[layerNo] is None:
# this layer was merged into a following layer
continue
# static component
if frame is None: # bottom-most layer
- frame = self.staticComponents[compNo]
+ frame = self.staticComponents[layerNo]
else:
frame = Image.alpha_composite(
- frame, self.staticComponents[compNo]
+ frame, self.staticComponents[layerNo]
)
else:
# animated component
--
cgit v1.2.3
From cbbb7876155cdb057b0d779cb8ab7bc1f31116b0 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 13 Jul 2017 21:59:23 -0400
Subject: components automatically drawPreview & save currentPreset
this makes a Component easier to program. also more comments
---
src/component.py | 36 ++++++++++++++++++++++--------------
src/components/color.py | 1 -
src/components/image.py | 2 +-
src/components/original.py | 2 +-
src/components/sound.py | 1 -
src/components/text.py | 2 +-
src/components/video.py | 2 +-
src/core.py | 1 +
src/presetmanager.py | 1 +
9 files changed, 28 insertions(+), 20 deletions(-)
diff --git a/src/component.py b/src/component.py
index eea82d7..2b297d1 100644
--- a/src/component.py
+++ b/src/component.py
@@ -24,7 +24,9 @@ class Component(QtCore.QObject):
return self.__doc__
def version(self):
- # change this number to identify new versions of a component
+ '''
+ Change this number to identify new versions of a component
+ '''
return 1
def properties(self):
@@ -42,15 +44,22 @@ class Component(QtCore.QObject):
return
def cancel(self):
- # please stop any lengthy process in response to this variable
+ '''
+ Stop any lengthy process in response to this variable
+ '''
self.canceled = True
def reset(self):
self.canceled = False
def update(self):
- self.modified.emit(self.compPos, self.savePreset())
- # read your widget values, then call super().update()
+ '''
+ Read your widget values from self.page, then call super().update()
+ '''
+ self.parent.drawPreview()
+ saveValueStore = self.savePreset()
+ saveValueStore['preset'] = self.currentPreset
+ self.modified.emit(self.compPos, saveValueStore)
def loadPreset(self, presetDict, presetName):
'''
@@ -72,8 +81,8 @@ class Component(QtCore.QObject):
Use the latter two signals to update the MainWindow if needed
for a long initialization procedure (i.e., for a visualizer)
'''
- for var, value in kwargs.items():
- exec('self.%s = value' % var)
+ for key, value in kwargs.items():
+ setattr(self, key, value)
def command(self, arg):
'''
@@ -143,16 +152,11 @@ class Component(QtCore.QObject):
def widget(self, parent):
self.parent = parent
- page = uic.loadUi(os.path.join(
- os.path.dirname(os.path.realpath(__file__)), 'example.ui'))
+ page = self.loadUi('example.ui')
# --- connect widget signals here ---
self.page = page
return page
- def update(self):
- self.parent.drawPreview()
- super().update()
-
def previewRender(self, previewWorker):
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
@@ -170,8 +174,12 @@ class Component(QtCore.QObject):
def audio(self):
\'''
- Return audio to mix into master as a string (path to audio file),
- or an object that returns raw audio data [future feature].
+ Return audio to mix into master as a tuple with two elements:
+ The first element can be:
+ - A string (path to audio file),
+ - Or an object that returns audio data through a pipe
+ The second element must be a dictionary of ffmpeg parameters
+ to apply to the input stream.
\'''
@classmethod
diff --git a/src/components/color.py b/src/components/color.py
index da3bcf9..ef4dd95 100644
--- a/src/components/color.py
+++ b/src/components/color.py
@@ -110,7 +110,6 @@ class Component(Component):
self.page.pushButton_color2.setEnabled(False)
self.page.fillWidget.setCurrentIndex(self.fillType)
- self.parent.drawPreview()
super().update()
def previewRender(self, previewWorker):
diff --git a/src/components/image.py b/src/components/image.py
index 6a70424..c0d1c0d 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -38,7 +38,7 @@ class Component(Component):
self.yPosition = self.page.spinBox_y.value()
self.stretched = self.page.checkBox_stretch.isChecked()
self.mirror = self.page.checkBox_mirror.isChecked()
- self.parent.drawPreview()
+
super().update()
def previewRender(self, previewWorker):
diff --git a/src/components/original.py b/src/components/original.py
index 3599c30..f5776a4 100644
--- a/src/components/original.py
+++ b/src/components/original.py
@@ -51,7 +51,7 @@ class Component(Component):
self.visColor = self.RGBFromString(self.page.lineEdit_visColor.text())
self.scale = self.page.spinBox_scale.value()
self.y = self.page.spinBox_y.value()
- self.parent.drawPreview()
+
super().update()
def loadPreset(self, pr, presetName=None):
diff --git a/src/components/sound.py b/src/components/sound.py
index 2ffb682..fedc32b 100644
--- a/src/components/sound.py
+++ b/src/components/sound.py
@@ -69,7 +69,6 @@ class Component(Component):
def savePreset(self):
return {
- 'preset': self.currentPreset,
'sound': self.sound,
}
diff --git a/src/components/text.py b/src/components/text.py
index c52bdc5..19460e5 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -69,7 +69,7 @@ class Component(Component):
btnStyle = "QPushButton { background-color : %s; outline: none; }" \
% QColor(*self.textColor).name()
self.page.pushButton_textColor.setStyleSheet(btnStyle)
- self.parent.drawPreview()
+
super().update()
def getXY(self):
diff --git a/src/components/video.py b/src/components/video.py
index 8861d70..8aa1420 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -140,7 +140,7 @@ class Component(Component):
self.scale = self.page.spinBox_scale.value()
self.xPosition = self.page.spinBox_x.value()
self.yPosition = self.page.spinBox_y.value()
- self.parent.drawPreview()
+
super().update()
def previewRender(self, previewWorker):
diff --git a/src/core.py b/src/core.py
index 3f0a6ad..2500fa6 100644
--- a/src/core.py
+++ b/src/core.py
@@ -414,6 +414,7 @@ class Core:
f.write('[Components]\n')
for comp in self.selectedComponents:
saveValueStore = comp.savePreset()
+ saveValueStore['preset'] = comp.currentPreset
f.write('%s\n' % str(comp))
f.write('%s\n' % str(comp.version()))
f.write('%s\n' % toolkit.presetToString(saveValueStore))
diff --git a/src/presetmanager.py b/src/presetmanager.py
index 40aa73f..0028203 100644
--- a/src/presetmanager.py
+++ b/src/presetmanager.py
@@ -160,6 +160,7 @@ class PresetManager(QtWidgets.QDialog):
selectedComponents[index].currentPreset = newName
saveValueStore = \
selectedComponents[index].savePreset()
+ saveValueStore['preset'] = newName
componentName = str(selectedComponents[index]).strip()
vers = selectedComponents[index].version()
self.createNewPreset(
--
cgit v1.2.3
From 62ab09e3f36dcaf6c1a4680dc6c4d048fb2e165c Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 15 Jul 2017 01:00:03 -0400
Subject: Video comp verifies audio streams, videoThread moved into Core
off-by-1 bug fixed in exporting, & use fewer threads for fewer CPUs
---
src/command.py | 22 ++++++++--------------
src/components/video.py | 25 ++++++++++++++++++++-----
src/core.py | 16 ++++++++++++++++
src/mainwindow.py | 22 ++++++----------------
src/video_thread.py | 35 +++++++++++++++++++++++------------
5 files changed, 73 insertions(+), 47 deletions(-)
diff --git a/src/command.py b/src/command.py
index be194d8..41618f8 100644
--- a/src/command.py
+++ b/src/command.py
@@ -9,13 +9,12 @@ import os
import sys
import core
-import video_thread
from toolkit import LoadDefaultSettings
class Command(QtCore.QObject):
- videoTask = QtCore.pyqtSignal(str, str, list)
+ createVideo = QtCore.pyqtSignal()
def __init__(self):
QtCore.QObject.__init__(self)
@@ -112,21 +111,16 @@ class Command(QtCore.QObject):
quit(1)
def createAudioVisualisation(self, input, output):
- self.videoThread = QtCore.QThread(self)
- self.videoWorker = video_thread.Worker(self)
- self.videoWorker.moveToThread(self.videoThread)
- self.videoWorker.videoCreated.connect(self.videoCreated)
-
- self.videoThread.start()
- self.videoTask.emit(
- input,
- output,
- list(reversed(self.core.selectedComponents))
+ self.core.selectedComponents = list(
+ reversed(self.core.selectedComponents))
+ self.core.componentListChanged()
+ self.worker = self.core.newVideoWorker(
+ self, input, output
)
+ self.worker.videoCreated.connect(self.videoCreated)
+ self.createVideo.emit()
def videoCreated(self):
- self.videoThread.quit()
- self.videoThread.wait()
quit(0)
def showMessage(self, **kwargs):
diff --git a/src/components/video.py b/src/components/video.py
index 8aa1420..b3b6a59 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -8,7 +8,7 @@ from queue import PriorityQueue
from component import Component, BadComponentInit
from frame import BlankFrame
-from toolkit import openPipe
+from toolkit import openPipe, checkOutput
class Video:
@@ -155,14 +155,29 @@ class Component(Component):
def properties(self):
props = []
- if self.useAudio:
- props.append('audio')
if not self.videoPath or self.badVideo \
or not os.path.exists(self.videoPath):
- props.append('error')
+ return ['error']
+
+ if self.useAudio:
+ props.append('audio')
+ # test if an audio stream really exists
+ audioTestCommand = [
+ self.core.FFMPEG_BIN,
+ '-i', self.videoPath,
+ '-vn', '-f', 'null', '-'
+ ]
+ try:
+ checkOutput(audioTestCommand, stderr=subprocess.DEVNULL)
+ except subprocess.CalledProcessError:
+ self.badAudio = True
+ return ['error']
+
return props
def error(self):
+ if hasattr(self, 'badAudio'):
+ return "Could not identify an audio stream in this video."
if not self.videoPath:
return "There is no video selected."
if not os.path.exists(self.videoPath):
@@ -180,7 +195,7 @@ class Component(Component):
self.blankFrame_ = BlankFrame(width, height)
self.updateChunksize(width, height)
self.video = Video(
- ffmpeg=self.parent.core.FFMPEG_BIN, videoPath=self.videoPath,
+ ffmpeg=self.core.FFMPEG_BIN, videoPath=self.videoPath,
width=width, height=height, chunkSize=self.chunkSize,
frameRate=int(self.settings.value("outputFrameRate")),
parent=self.parent, loopVideo=self.loopVideo,
diff --git a/src/core.py b/src/core.py
index 2500fa6..55bf261 100644
--- a/src/core.py
+++ b/src/core.py
@@ -12,6 +12,7 @@ from PyQt5.QtCore import QStandardPaths
import toolkit
from frame import Frame
+import video_thread
class Core:
@@ -633,6 +634,21 @@ class Core:
return completeAudioArray
+ def newVideoWorker(self, loader, audioFile, outputPath):
+ self.videoThread = QtCore.QThread(loader)
+ videoWorker = video_thread.Worker(
+ loader, audioFile, outputPath, self.selectedComponents
+ )
+ videoWorker.moveToThread(self.videoThread)
+ videoWorker.videoCreated.connect(self.videoCreated)
+
+ self.videoThread.start()
+ return videoWorker
+
+ def videoCreated(self):
+ self.videoThread.quit()
+ self.videoThread.wait()
+
def cancel(self):
self.canceled = True
diff --git a/src/mainwindow.py b/src/mainwindow.py
index 771b6b8..76ed179 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -16,7 +16,6 @@ import time
import core
import preview_thread
-import video_thread
from presetmanager import PresetManager
from toolkit import LoadDefaultSettings, disableWhenEncoding, checkOutput
@@ -49,9 +48,9 @@ class PreviewWindow(QtWidgets.QLabel):
class MainWindow(QtWidgets.QMainWindow):
- newTask = QtCore.pyqtSignal(list)
+ createVideo = QtCore.pyqtSignal()
+ newTask = QtCore.pyqtSignal(list) # for the preview window
processTask = QtCore.pyqtSignal()
- videoTask = QtCore.pyqtSignal(str, str, list)
def __init__(self, window, project):
QtWidgets.QMainWindow.__init__(self)
@@ -497,20 +496,15 @@ class MainWindow(QtWidgets.QMainWindow):
self.canceled = False
self.progressBarUpdated(-1)
- self.videoThread = QtCore.QThread(self)
- self.videoWorker = video_thread.Worker(self)
- self.videoWorker.moveToThread(self.videoThread)
- self.videoWorker.videoCreated.connect(self.videoCreated)
+ self.videoWorker = self.core.newVideoWorker(
+ self, audioFile, outputPath
+ )
self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated)
self.videoWorker.progressBarSetText.connect(
self.progressBarSetText)
self.videoWorker.imageCreated.connect(self.showPreviewImage)
self.videoWorker.encoding.connect(self.changeEncodingStatus)
- self.videoThread.start()
- self.videoTask.emit(
- audioFile,
- outputPath,
- self.core.selectedComponents)
+ self.createVideo.emit()
def changeEncodingStatus(self, status):
self.encoding = status
@@ -569,10 +563,6 @@ class MainWindow(QtWidgets.QMainWindow):
else:
self.window.progressBar_createVideo.setFormat(value)
- def videoCreated(self):
- self.videoThread.quit()
- self.videoThread.wait()
-
def updateResolution(self):
resIndex = int(self.window.comboBox_resolution.currentIndex())
res = self.resolutions[resIndex].split('x')
diff --git a/src/video_thread.py b/src/video_thread.py
index b0562db..5295a3b 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -19,7 +19,7 @@ import time
import signal
import core
-from toolkit import openPipe, checkOutput
+from toolkit import openPipe
from frame import Checkerboard
@@ -31,13 +31,19 @@ class Worker(QtCore.QObject):
progressBarSetText = pyqtSignal(str)
encoding = pyqtSignal(bool)
- def __init__(self, parent=None):
+
+ def __init__(self, parent, inputFile, outputFile, components):
QtCore.QObject.__init__(self)
self.core = parent.core
self.settings = parent.core.settings
self.modules = parent.core.modules
+ parent.createVideo.connect(self.createVideo)
+
self.parent = parent
- parent.videoTask.connect(self.createVideo)
+ self.components = components
+ self.outputFile = outputFile
+ self.inputFile = inputFile
+
self.sampleSize = 1470 # 44100 / 30 = 1470
self.canceled = False
self.error = False
@@ -55,7 +61,7 @@ class Worker(QtCore.QObject):
bgI = int(audioI / self.sampleSize)
frame = None
for compNo, comp in reversed(list(enumerate(self.components))):
- layerNo = len(self.components) - compNo
+ layerNo = len(self.components) - compNo - 1
if layerNo in self.staticComponents:
if self.staticComponents[layerNo] is None:
# this layer was merged into a following layer
@@ -106,12 +112,10 @@ class Worker(QtCore.QObject):
self.previewQueue.task_done()
- @pyqtSlot(str, str, list)
- def createVideo(self, inputFile, outputFile, components):
+ @pyqtSlot()
+ def createVideo(self):
numpy.seterr(divide='ignore')
self.encoding.emit(True)
- self.components = components
- self.outputFile = outputFile
self.extraAudio = []
self.width = int(self.settings.value('outputWidth'))
self.height = int(self.settings.value('outputHeight'))
@@ -131,7 +135,7 @@ class Worker(QtCore.QObject):
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
self.progressBarSetText.emit("Loading audio file...")
- self.completeAudioArray = self.core.readAudioFile(inputFile, self)
+ self.completeAudioArray = self.core.readAudioFile(self.inputFile, self)
self.progressBarUpdate.emit(0)
self.progressBarSetText.emit("Starting components...")
@@ -189,7 +193,9 @@ class Worker(QtCore.QObject):
)
self.staticComponents[compNo] = None
- ffmpegCommand = self.core.createFfmpegCommand(inputFile, outputFile)
+ ffmpegCommand = self.core.createFfmpegCommand(
+ self.inputFile, self.outputFile
+ )
print('###### FFMPEG COMMAND ######\n%s' % " ".join(ffmpegCommand))
print('############################')
self.out_pipe = openPipe(
@@ -200,9 +206,14 @@ class Worker(QtCore.QObject):
# START CREATING THE VIDEO
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
- # Make three renderNodes in new threads to create the frames
+ # Make 2 or 3 renderNodes in new threads to create the frames
self.renderThreads = []
- for i in range(3):
+ try:
+ numCpus = len(os.sched_getaffinity(0))
+ except:
+ numCpus = os.cpu_count()
+
+ for i in range(2 if numCpus <= 2 else 3):
self.renderThreads.append(
Thread(target=self.renderNode, name="Render Thread"))
self.renderThreads[i].daemon = True
--
cgit v1.2.3
From bcb8f27c2e4434d2296dcd66bf279b76ee0d0a4f Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 15 Jul 2017 13:13:53 -0400
Subject: use -t on inputs so ffmpeg knows when to stop filters
+ better feedback in cmd mode
---
src/command.py | 20 ++++++++++++++++++++
src/components/sound.py | 5 +++++
src/components/video.py | 38 ++++++++++++++++++++++++++------------
src/core.py | 8 ++++++--
src/main.py | 11 ++++++-----
src/video_thread.py | 8 ++++----
6 files changed, 67 insertions(+), 23 deletions(-)
diff --git a/src/command.py b/src/command.py
index 41618f8..84d798d 100644
--- a/src/command.py
+++ b/src/command.py
@@ -7,6 +7,7 @@ from PyQt5 import QtCore
import argparse
import os
import sys
+import time
import core
from toolkit import LoadDefaultSettings
@@ -118,8 +119,27 @@ class Command(QtCore.QObject):
self, input, output
)
self.worker.videoCreated.connect(self.videoCreated)
+ self.lastProgressUpdate = time.time()
+ self.worker.progressBarSetText.connect(self.progressBarSetText)
self.createVideo.emit()
+ @QtCore.pyqtSlot(str)
+ def progressBarSetText(self, value):
+ if 'Export ' in value:
+ # Don't duplicate completion/failure messages
+ return
+ if not value.startswith('Exporting') \
+ and time.time() - self.lastProgressUpdate >= 0.05:
+ # Show most messages very often
+ print(value)
+ elif time.time() - self.lastProgressUpdate >= 2.0:
+ # Give user time to read ffmpeg's output during the export
+ print('##### %s' % value)
+ else:
+ return
+ self.lastProgressUpdate = time.time()
+
+ @QtCore.pyqtSlot()
def videoCreated(self):
quit(0)
diff --git a/src/components/sound.py b/src/components/sound.py
index fedc32b..4a5714b 100644
--- a/src/components/sound.py
+++ b/src/components/sound.py
@@ -79,6 +79,11 @@ class Component(Component):
if not arg.startswith('preset=') and '=' in arg:
key, arg = arg.split('=', 1)
if key == 'path':
+ if '*%s' % os.path.splitext(arg)[1] \
+ not in self.core.audioFormats:
+ print("Not a supported audio format")
+ quit(1)
self.page.lineEdit_sound.setText(arg)
return
+
super().command(arg)
diff --git a/src/components/video.py b/src/components/video.py
index b3b6a59..0b93293 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -116,6 +116,7 @@ class Component(Component):
page = self.loadUi('video.ui')
self.videoPath = ''
self.badVideo = False
+ self.badAudio = False
self.x = 0
self.y = 0
self.loopVideo = False
@@ -161,22 +162,14 @@ class Component(Component):
if self.useAudio:
props.append('audio')
- # test if an audio stream really exists
- audioTestCommand = [
- self.core.FFMPEG_BIN,
- '-i', self.videoPath,
- '-vn', '-f', 'null', '-'
- ]
- try:
- checkOutput(audioTestCommand, stderr=subprocess.DEVNULL)
- except subprocess.CalledProcessError:
- self.badAudio = True
+ self.testAudioStream()
+ if self.badAudio:
return ['error']
return props
def error(self):
- if hasattr(self, 'badAudio'):
+ if self.badAudio:
return "Could not identify an audio stream in this video."
if not self.videoPath:
return "There is no video selected."
@@ -185,6 +178,20 @@ class Component(Component):
if self.badVideo:
return "The video selected is corrupt!"
+ def testAudioStream(self):
+ # test if an audio stream really exists
+ audioTestCommand = [
+ self.core.FFMPEG_BIN,
+ '-i', self.videoPath,
+ '-vn', '-f', 'null', '-'
+ ]
+ try:
+ checkOutput(audioTestCommand, stderr=subprocess.DEVNULL)
+ except subprocess.CalledProcessError:
+ self.badAudio = True
+ else:
+ self.badAudio = False
+
def audio(self):
return (self.videoPath, {'map': '-v'})
@@ -277,7 +284,7 @@ class Component(Component):
if not arg.startswith('preset=') and '=' in arg:
key, arg = arg.split('=', 1)
if key == 'path' and os.path.exists(arg):
- if os.path.splitext(arg)[1] in self.core.videoFormats:
+ 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)
@@ -285,10 +292,17 @@ class Component(Component):
else:
print("Not a supported video format")
quit(1)
+ elif arg == 'audio':
+ if not self.page.lineEdit_video.text():
+ print("'audio' option must follow a video selection")
+ quit(1)
+ self.page.checkBox_useAudio.setChecked(True)
+ return
super().command(arg)
def commandHelp(self):
print('Load a video:\n path=/filepath/to/video.mp4')
+ print('Using audio:\n path=/filepath/to/video.mp4 audio')
def scale(scale, width, height, returntype=None):
diff --git a/src/core.py b/src/core.py
index 55bf261..4c12209 100644
--- a/src/core.py
+++ b/src/core.py
@@ -464,10 +464,11 @@ class Core:
except sp.CalledProcessError:
return "avconv"
- def createFfmpegCommand(self, inputFile, outputFile):
+ def createFfmpegCommand(self, inputFile, outputFile, duration):
'''
Constructs the major ffmpeg command used to export the video
'''
+ duration = str(duration)
# Test if user has libfdk_aac
encoders = toolkit.checkOutput(
@@ -516,10 +517,12 @@ class Core:
),
'-pix_fmt', 'rgba',
'-r', self.settings.value('outputFrameRate'),
+ '-t', duration,
'-i', '-', # the video input comes from a pipe
'-an', # the video input has no sound
# INPUT SOUND
+ '-t', duration,
'-i', inputFile
]
@@ -532,6 +535,7 @@ class Core:
for streamNo, params in enumerate(extraAudio):
extraInputFile, params = params
ffmpegCommand.extend([
+ '-t', duration,
'-i', extraInputFile
])
if 'map' in params and params['map'] == '-v':
@@ -632,7 +636,7 @@ class Core:
completeAudioArrayCopy[:len(completeAudioArray)] = completeAudioArray
completeAudioArray = completeAudioArrayCopy
- return completeAudioArray
+ return (completeAudioArray, duration)
def newVideoWorker(self, loader, audioFile, outputPath):
self.videoThread = QtCore.QThread(loader)
diff --git a/src/main.py b/src/main.py
index b0ece29..2216d2a 100644
--- a/src/main.py
+++ b/src/main.py
@@ -8,13 +8,13 @@ import video_thread
if __name__ == "__main__":
- mode = 'gui'
+ mode = 'GUI'
if len(sys.argv) > 2:
- mode = 'cmd'
+ mode = 'commandline'
elif len(sys.argv) == 2:
if sys.argv[1].startswith('-'):
- mode = 'cmd'
+ mode = 'commandline'
else:
# opening a project file with gui
proj = sys.argv[1]
@@ -22,16 +22,17 @@ if __name__ == "__main__":
# normal gui launch
proj = None
+ print('Starting Audio Visualizer in %s mode' % mode)
app = QtWidgets.QApplication(sys.argv)
app.setApplicationName("audio-visualizer")
# app.setOrganizationName("audio-visualizer")
- if mode == 'cmd':
+ if mode == 'commandline':
from command import *
main = Command()
- elif mode == 'gui':
+ elif mode == 'GUI':
from mainwindow import *
import atexit
import signal
diff --git a/src/video_thread.py b/src/video_thread.py
index 5295a3b..674765a 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -31,7 +31,6 @@ class Worker(QtCore.QObject):
progressBarSetText = pyqtSignal(str)
encoding = pyqtSignal(bool)
-
def __init__(self, parent, inputFile, outputFile, components):
QtCore.QObject.__init__(self)
self.core = parent.core
@@ -135,7 +134,9 @@ class Worker(QtCore.QObject):
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
self.progressBarSetText.emit("Loading audio file...")
- self.completeAudioArray = self.core.readAudioFile(self.inputFile, self)
+ self.completeAudioArray, duration = self.core.readAudioFile(
+ self.inputFile, self
+ )
self.progressBarUpdate.emit(0)
self.progressBarSetText.emit("Starting components...")
@@ -144,7 +145,6 @@ class Worker(QtCore.QObject):
for num, component in enumerate(reversed(self.components))
]))
self.staticComponents = {}
- numComps = len(self.components)
for compNo, comp in enumerate(reversed(self.components)):
comp.preFrameRender(
worker=self,
@@ -194,7 +194,7 @@ class Worker(QtCore.QObject):
self.staticComponents[compNo] = None
ffmpegCommand = self.core.createFfmpegCommand(
- self.inputFile, self.outputFile
+ self.inputFile, self.outputFile, duration
)
print('###### FFMPEG COMMAND ######\n%s' % " ".join(ffmpegCommand))
print('############################')
--
cgit v1.2.3
From 17c8a6703a8093d31c6772ba3b8d9ee01adaa0da Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 15 Jul 2017 18:59:22 -0400
Subject: trying to make setup.py work
---
setup.py | 53 +++++++++++++++++++++++++++++++++------------------
src/__init__.py | 0
src/__main__.py | 3 +++
src/main.py | 29 +++++++++++++++-------------
src/presetmanager.py | 1 -
src/preview_thread.py | 1 -
src/video_thread.py | 1 -
7 files changed, 53 insertions(+), 35 deletions(-)
create mode 100644 src/__init__.py
create mode 100644 src/__main__.py
diff --git a/setup.py b/setup.py
index fde3461..4ef6077 100644
--- a/setup.py
+++ b/setup.py
@@ -1,19 +1,34 @@
-+from setuptools import setup, find_packages
-
- -# Dependencies are automatically detected, but it might need +setup(name='audio_visualizer_python',
- -# fine tuning. + version='1.0',
- -buildOptions = dict(packages = [], excludes = [ + description='a little GUI tool to render visualization \
- - "apport", + videos of audio files',
- - "apt", + license='MIT',
- - "ctypes", + url='https://github.com/djfun/audio-visualizer-python',
- - "curses", + packages=find_packages(),
- - "distutils", + package_data={
- - "email", + 'src': ['*'],
- - "html", + },
- - "http", + install_requires=['pillow-simd', 'numpy', ''],
- - "json", + entry_points={
- - "xmlrpc", + 'gui_scripts': [
- - "nose" + 'audio-visualizer-python = avpython.main:main'
- - ], include_files = ["main.ui"]) + ]
- - + }
- -import sys + )
\ No newline at end of file
+from setuptools import setup
+import os
+
+
+def package_files(directory):
+ paths = []
+ for (path, directories, filenames) in os.walk(directory):
+ for filename in filenames:
+ paths.append(os.path.join('..', path, filename))
+ return paths
+
+
+setup(
+ name='audio_visualizer_python',
+ version='2.0.0',
+ description='A little GUI tool to create audio visualization " \
+ "videos out of audio files',
+ license='MIT',
+ url='https://github.com/djfun/audio-visualizer-python',
+ packages=[
+ 'avpython',
+ 'avpython.components'
+ ],
+ package_dir={'avpython': 'src'},
+ package_data={
+ 'avpython': package_files('src'),
+ },
+ install_requires=['olefile', 'Pillow-SIMD', 'PyQt5', 'numpy'],
+ entry_points={
+ 'gui_scripts': [
+ 'avp = avpython.main:main'
+ ],
+ }
+)
diff --git a/src/__init__.py b/src/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/__main__.py b/src/__main__.py
new file mode 100644
index 0000000..a68739e
--- /dev/null
+++ b/src/__main__.py
@@ -0,0 +1,3 @@
+from avpython.main import main
+
+main()
\ No newline at end of file
diff --git a/src/main.py b/src/main.py
index 2216d2a..317237c 100644
--- a/src/main.py
+++ b/src/main.py
@@ -2,12 +2,18 @@ from PyQt5 import uic, QtWidgets
import sys
import os
-import core
-import preview_thread
-import video_thread
+def main():
+ if getattr(sys, 'frozen', False):
+ # frozen
+ wd = os.path.dirname(sys.executable)
+ else:
+ # unfrozen
+ wd = os.path.dirname(os.path.realpath(__file__))
+
+ # make local imports work everywhere
+ sys.path.append(wd)
-if __name__ == "__main__":
mode = 'GUI'
if len(sys.argv) > 2:
mode = 'commandline'
@@ -28,22 +34,15 @@ if __name__ == "__main__":
# app.setOrganizationName("audio-visualizer")
if mode == 'commandline':
- from command import *
+ from command import Command
main = Command()
elif mode == 'GUI':
- from mainwindow import *
+ from mainwindow import MainWindow
import atexit
import signal
- if getattr(sys, 'frozen', False):
- # frozen
- wd = os.path.dirname(sys.executable)
- else:
- # unfrozen
- wd = os.path.dirname(os.path.realpath(__file__))
-
window = uic.loadUi(os.path.join(wd, "mainwindow.ui"))
# window.adjustSize()
desc = QtWidgets.QDesktopWidget()
@@ -64,3 +63,7 @@ if __name__ == "__main__":
# applicable to both modes
sys.exit(app.exec_())
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/presetmanager.py b/src/presetmanager.py
index 0028203..6e003a1 100644
--- a/src/presetmanager.py
+++ b/src/presetmanager.py
@@ -6,7 +6,6 @@ from PyQt5 import QtCore, QtWidgets
import string
import os
-import core
import toolkit
diff --git a/src/preview_thread.py b/src/preview_thread.py
index 4ffb7f6..6c33aff 100644
--- a/src/preview_thread.py
+++ b/src/preview_thread.py
@@ -6,7 +6,6 @@ from PyQt5 import QtCore, QtGui, uic
from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PIL import Image
from PIL.ImageQt import ImageQt
-import core
from queue import Queue, Empty
import os
diff --git a/src/video_thread.py b/src/video_thread.py
index 674765a..60db99f 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -18,7 +18,6 @@ from threading import Thread, Event
import time
import signal
-import core
from toolkit import openPipe
from frame import Checkerboard
--
cgit v1.2.3
From ec0abd190273b7b636c7085d7caed8220ab09172 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 16 Jul 2017 14:06:11 -0400
Subject: apply complex filters to audio streams from components
tons of sound options could be given now, + installation using setup.py
---
README.md | 21 +++++-----
setup.py | 24 ++++++++---
src/component.py | 5 ++-
src/components/sound.py | 23 ++++++++++-
src/components/sound.ui | 50 +++++++++++++++++++++++
src/components/video.py | 16 +++++++-
src/components/video.ui | 75 +++++++++++++++++++++++++++++++----
src/core.py | 103 ++++++++++++++++++++++++++++++++++++++++--------
src/main.py | 2 +-
src/toolkit.py | 11 ++++--
10 files changed, 283 insertions(+), 47 deletions(-)
diff --git a/README.md b/README.md
index 658a22d..9149b4f 100644
--- a/README.md
+++ b/README.md
@@ -1,28 +1,31 @@
audio-visualizer-python
=======================
+**We need a good name that is not as generic as "audio-visualizer-python"!**
-This is a little GUI tool which creates an audio visualization video from an input audio file. Different components can be added and layered to change the resulting video and add images, videos, gradients, text, etc. The component setup can be saved as a Project and exporting can be automated using commandline options.
+This is a little GUI tool which creates an audio visualization video from an input audio file. Different components can be added and layered to change the resulting video and add images, videos, gradients, text, etc. Encoding options can be changed with a variety of different output containers.
-The program works on Linux, macOS, and Windows. If you encounter problems running it or have other bug reports or features that you wish to see implemented, please fork the project and send me a pull request and/or file an issue on this project.
+Projects can be created from the GUI and used in commandline mode for easy automation of video production. Create a template project named `template` with your typical visualizers and watermarks, and add text to the top layer from commandline:
+`avp template -c 99 text "title=Episode 371" -i /this/weeks/audio.ogg -o out`
-I also need a good name that is not as generic as "audio-visualizer-python"!
+For more information use `avp --help` or for help with a particular component use `avp -c 0 componentName help`.
+
+The program works on Linux, macOS, and Windows. If you encounter problems running it or have other bug reports or features that you wish to see implemented, please fork the project and submit a pull request and/or file an issue on this project.
Dependencies
------------
-Python 3, PyQt5, pillow-simd, numpy, and ffmpeg 3.3
+Python 3.4, FFmpeg 3.3, PyQt5, Pillow-SIMD, NumPy
-**Note:** Pillow may be used as a drop-in replacement for Pillow-SIMD if problems are encountered installing. However this will result in much slower video export times.
+**Note:** Pillow may be used as a drop-in replacement for Pillow-SIMD if problems are encountered installing. However this will result in much slower video export times. For help troubleshooting installation problems, the * For any problems with installing Pillow-SIMD, see the [Pillow installation guide](http://pillow.readthedocs.io/en/3.1.x/installation.html).
Installation
------------
### Manual installation on Ubuntu 16.04
* Install pip: `sudo apt-get install python3-pip`
-* Install [prerequisites to compile Pillow](http://pillow.readthedocs.io/en/3.1.x/installation.html#building-on-linux):`sudo apt-get install python3-dev python3-setuptools libtiff5-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python-tk`
-* Prerequisites on **Fedora**:`sudo dnf install python3-devel redhat-rpm-config libtiff-devel libjpeg-devel libzip-devel freetype-devel lcms2-devel libwebp-devel tcl-devel tk-devel`
-* Install dependencies from PyPI: `sudo pip3 install pyqt5 numpy pillow-simd`
+* If Pillow is installed, it must be removed. Nothing should break because Pillow-SIMD is simply a drop-in replacement with better performance.
+* Download audio-visualizer-python from this repository and run `sudo pip3 install .` in this directory
* Install `ffmpeg` from the [website](http://ffmpeg.org/) or from a PPA (e.g. [https://launchpad.net/~jonathonf/+archive/ubuntu/ffmpeg-3](https://launchpad.net/~jonathonf/+archive/ubuntu/ffmpeg-3)). NOTE: `ffmpeg` in the standard repos is too old (v2.8). Old versions and `avconv` may be used but full functionality is only guaranteed with `ffmpeg` 3.3 or higher.
-Download audio-visualizer-python from this repository and run it with `python3 main.py`.
+Run the program with `avp` or `python3 -m avpython`
### Manual installation on Windows
* **Warning:** [Compiling Pillow is difficult on Windows](http://pillow.readthedocs.io/en/3.1.x/installation.html#building-on-windows) and required for the best experience.
diff --git a/setup.py b/setup.py
index 4ef6077..71dc51f 100644
--- a/setup.py
+++ b/setup.py
@@ -12,11 +12,25 @@ def package_files(directory):
setup(
name='audio_visualizer_python',
- version='2.0.0',
- description='A little GUI tool to create audio visualization " \
- "videos out of audio files',
+ version='2.0.0rc1',
+ url='https://github.com/djfun/audio-visualizer-python/tree/feature-newgui',
license='MIT',
- url='https://github.com/djfun/audio-visualizer-python',
+ description='Create audio visualization videos from a GUI or commandline',
+ long_description="Create customized audio visualization videos and save "
+ "them as Projects to continue editing later. Different components can "
+ "be added and layered to add visualizers, images, videos, gradients, "
+ "text, etc. Use Projects created in the GUI with commandline mode to "
+ "automate your video production workflow without learning any complex "
+ "syntax.",
+ classifiers=[
+ 'Development Status :: 4 - Beta',
+ 'License :: OSI Approved :: MIT License',
+ 'Programming Language :: Python :: 3 :: Only',
+ 'Intended Audience :: End Users/Desktop',
+ 'Topic :: Multimedia :: Video :: Non-Linear Editor',
+ ],
+ keywords=['visualizer', 'visualization', 'commandline video',
+ 'video editor', 'ffmpeg', 'podcast']
packages=[
'avpython',
'avpython.components'
@@ -25,7 +39,7 @@ setup(
package_data={
'avpython': package_files('src'),
},
- install_requires=['olefile', 'Pillow-SIMD', 'PyQt5', 'numpy'],
+ install_requires=['Pillow-SIMD', 'PyQt5', 'numpy'],
entry_points={
'gui_scripts': [
'avp = avpython.main:main'
diff --git a/src/component.py b/src/component.py
index 2b297d1..adb170e 100644
--- a/src/component.py
+++ b/src/component.py
@@ -178,8 +178,9 @@ class Component(QtCore.QObject):
The first element can be:
- A string (path to audio file),
- Or an object that returns audio data through a pipe
- The second element must be a dictionary of ffmpeg parameters
- to apply to the input stream.
+ The second element must be a dictionary of ffmpeg filters/options
+ to apply to the input stream. See the filter docs for ideas:
+ https://ffmpeg.org/ffmpeg-filters.html
\'''
@classmethod
diff --git a/src/components/sound.py b/src/components/sound.py
index 4a5714b..bd7d002 100644
--- a/src/components/sound.py
+++ b/src/components/sound.py
@@ -17,12 +17,18 @@ class Component(Component):
page.lineEdit_sound.textChanged.connect(self.update)
page.pushButton_sound.clicked.connect(self.pickSound)
+ page.checkBox_chorus.stateChanged.connect(self.update)
+ page.spinBox_delay.valueChanged.connect(self.update)
+ page.spinBox_volume.valueChanged.connect(self.update)
self.page = page
return page
def update(self):
self.sound = self.page.lineEdit_sound.text()
+ self.delay = self.page.spinBox_delay.value()
+ self.volume = self.page.spinBox_volume.value()
+ self.chorus = self.page.checkBox_chorus.isChecked()
super().update()
def previewRender(self, previewWorker):
@@ -46,7 +52,16 @@ class Component(Component):
return "The audio file selected no longer exists!"
def audio(self):
- return (self.sound, {})
+ params = {}
+ if self.delay != 0.0:
+ params['adelay'] = '=%s' % str(int(self.delay * 1000.00))
+ if self.chorus:
+ params['chorus'] = \
+ '=0.5:0.9:50|60|40:0.4|0.32|0.3:0.25|0.4|0.3:2|2.3|1.3'
+ if self.volume != 1.0:
+ params['volume'] = '=%s:replaygain_noclip=0' % str(self.volume)
+
+ return (self.sound, params)
def pickSound(self):
sndDir = self.settings.value("componentDir", os.path.expanduser("~"))
@@ -66,10 +81,16 @@ class Component(Component):
def loadPreset(self, pr, presetName=None):
super().loadPreset(pr, presetName)
self.page.lineEdit_sound.setText(pr['sound'])
+ self.page.checkBox_chorus.setChecked(pr['chorus'])
+ self.page.spinBox_delay.setValue(pr['delay'])
+ self.page.spinBox_volume.setValue(pr['volume'])
def savePreset(self):
return {
'sound': self.sound,
+ 'chorus': self.chorus,
+ 'delay': self.delay,
+ 'volume': self.volume,
}
def commandHelp(self):
diff --git a/src/components/sound.ui b/src/components/sound.ui
index 5fc00c1..4c11332 100644
--- a/src/components/sound.ui
+++ b/src/components/sound.ui
@@ -87,6 +87,29 @@
-
+
-
+
+
+ Volume
+
+
+
+ -
+
+
+ x
+
+
+ 10.000000000000000
+
+
+ 0.100000000000000
+
+
+ 1.000000000000000
+
+
+
-
@@ -100,6 +123,33 @@
+ -
+
+
+ Delay
+
+
+
+ -
+
+
+ s
+
+
+ 9999999.990000000223517
+
+
+ 0.500000000000000
+
+
+
+ -
+
+
+ Chorus
+
+
+
-
diff --git a/src/components/video.py b/src/components/video.py
index 0b93293..e1f182c 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -127,6 +127,7 @@ class Component(Component):
page.checkBox_distort.stateChanged.connect(self.update)
page.checkBox_useAudio.stateChanged.connect(self.update)
page.spinBox_scale.valueChanged.connect(self.update)
+ page.spinBox_volume.valueChanged.connect(self.update)
page.spinBox_x.valueChanged.connect(self.update)
page.spinBox_y.valueChanged.connect(self.update)
@@ -139,9 +140,17 @@ class Component(Component):
self.useAudio = self.page.checkBox_useAudio.isChecked()
self.distort = self.page.checkBox_distort.isChecked()
self.scale = self.page.spinBox_scale.value()
+ self.volume = self.page.spinBox_volume.value()
self.xPosition = self.page.spinBox_x.value()
self.yPosition = self.page.spinBox_y.value()
+ if self.useAudio:
+ self.page.label_volume.setEnabled(True)
+ self.page.spinBox_volume.setEnabled(True)
+ else:
+ self.page.label_volume.setEnabled(False)
+ self.page.spinBox_volume.setEnabled(False)
+
super().update()
def previewRender(self, previewWorker):
@@ -193,7 +202,10 @@ class Component(Component):
self.badAudio = False
def audio(self):
- return (self.videoPath, {'map': '-v'})
+ params = {}
+ if self.volume != 1.0:
+ params['volume'] = '=%s:replaygain_noclip=0' % str(self.volume)
+ return (self.videoPath, params)
def preFrameRender(self, **kwargs):
super().preFrameRender(**kwargs)
@@ -222,6 +234,7 @@ class Component(Component):
self.page.checkBox_useAudio.setChecked(pr['useAudio'])
self.page.checkBox_distort.setChecked(pr['distort'])
self.page.spinBox_scale.setValue(pr['scale'])
+ self.page.spinBox_volume.setValue(pr['volume'])
self.page.spinBox_x.setValue(pr['x'])
self.page.spinBox_y.setValue(pr['y'])
@@ -233,6 +246,7 @@ class Component(Component):
'useAudio': self.useAudio,
'distort': self.distort,
'scale': self.scale,
+ 'volume': self.volume,
'x': self.xPosition,
'y': self.yPosition,
}
diff --git a/src/components/video.ui b/src/components/video.ui
index 97b7d6f..08d15d3 100644
--- a/src/components/video.ui
+++ b/src/components/video.ui
@@ -10,6 +10,18 @@
197
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 197
+
+
Form
@@ -189,13 +201,6 @@
- -
-
-
- Use Audio
-
-
-
-
@@ -247,6 +252,62 @@
+ -
+
+
-
+
+
+ Use Audio
+
+
+
+ -
+
+
+ Volume
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ x
+
+
+ 0.000000000000000
+
+
+ 10.000000000000000
+
+
+ 0.100000000000000
+
+
+ 1.000000000000000
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
-
diff --git a/src/core.py b/src/core.py
index 4c12209..324b04f 100644
--- a/src/core.py
+++ b/src/core.py
@@ -468,7 +468,8 @@ class Core:
'''
Constructs the major ffmpeg command used to export the video
'''
- duration = str(duration)
+ safeDuration = "{0:.3f}".format(duration - 0.05) # used by filters
+ duration = "{0:.3f}".format(duration + 0.1) # used by input sources
# Test if user has libfdk_aac
encoders = toolkit.checkOutput(
@@ -526,35 +527,99 @@ class Core:
'-i', inputFile
]
+ # Add extra audio inputs and any needed avfilters
+ # NOTE: Global filters are currently hard-coded here for debugging use
+ globalFilters = 0 # increase to add global filters
extraAudio = [
comp.audio() for comp in self.selectedComponents
if 'audio' in comp.properties()
]
- if extraAudio:
- unwantedVideoStreams = []
- for streamNo, params in enumerate(extraAudio):
+ if extraAudio or globalFilters > 0:
+ # Add -i options for extra input files
+ extraFilters = {}
+ for streamNo, params in enumerate(reversed(extraAudio)):
extraInputFile, params = params
ffmpegCommand.extend([
- '-t', duration,
+ '-t', safeDuration,
'-i', extraInputFile
])
- if 'map' in params and params['map'] == '-v':
- # a video stream to remove
- unwantedVideoStreams.append(streamNo + 1)
+ # Construct dataset of extra filters we'll need to add later
+ for ffmpegFilter in params:
+ if streamNo + 2 not in extraFilters:
+ extraFilters[streamNo + 2] = []
+ extraFilters[streamNo + 2].append((
+ ffmpegFilter, params[ffmpegFilter]
+ ))
+
+ # Start creating avfilters!
+ extraFilterCommand = []
+
+ if globalFilters <= 0:
+ # Dictionary of last-used tmp labels for a given stream number
+ tmpInputs = {streamNo: -1 for streamNo in extraFilters}
+ else:
+ # Insert blank entries for global filters into extraFilters
+ # so the per-stream filters know what input to source later
+ for streamNo in range(len(extraAudio), 0, -1):
+ if streamNo + 1 not in extraFilters:
+ extraFilters[streamNo + 1] = []
+ # Also filter the primary audio track
+ extraFilters[1] = []
+ tmpInputs = {
+ streamNo: globalFilters - 1
+ for streamNo in extraFilters
+ }
+
+ # Add the global filters!
+ # NOTE: list length must = globalFilters, currently hardcoded
+ if tmpInputs:
+ extraFilterCommand.extend([
+ '[%s:a] ashowinfo [%stmp0]' % (
+ str(streamNo),
+ str(streamNo)
+ )
+ for streamNo in tmpInputs
+ ])
+
+ # Now add the per-stream filters!
+ for streamNo, paramList in extraFilters.items():
+ for param in paramList:
+ source = '[%s:a]' % str(streamNo) \
+ if tmpInputs[streamNo] == -1 else \
+ '[%stmp%s]' % (
+ str(streamNo), str(tmpInputs[streamNo])
+ )
+ tmpInputs[streamNo] = tmpInputs[streamNo] + 1
+ extraFilterCommand.append(
+ '%s %s%s [%stmp%s]' % (
+ source, param[0], param[1], str(streamNo),
+ str(tmpInputs[streamNo])
+ )
+ )
- if unwantedVideoStreams:
- ffmpegCommand.extend(['-map', '0'])
- for streamNo in unwantedVideoStreams:
- ffmpegCommand.extend([
- '-map', '-%s:v' % str(streamNo)
- ])
+ # Join all the filters together and combine into 1 stream
+ extraFilterCommand = "; ".join(extraFilterCommand) + '; ' \
+ if tmpInputs else ''
ffmpegCommand.extend([
'-filter_complex',
- 'amix=inputs=%s:duration=first:dropout_transition=3' % str(
- len(extraAudio) + 1
+ extraFilterCommand +
+ '%s amix=inputs=%s:duration=first [a]'
+ % (
+ "".join([
+ '[%stmp%s]' % (str(i), tmpInputs[i])
+ if i in extraFilters else '[%s:a]' % str(i)
+ for i in range(1, len(extraAudio) + 2)
+ ]),
+ str(len(extraAudio) + 1)
),
])
+ # Only map audio from the filters, and video from the pipe
+ ffmpegCommand.extend([
+ '-map', '0:v',
+ '-map', '[a]',
+ ])
+
ffmpegCommand.extend([
# OUTPUT
'-vcodec', vencoder,
@@ -573,7 +638,7 @@ class Core:
ffmpegCommand.append(outputFile)
return ffmpegCommand
- def readAudioFile(self, filename, parent):
+ def getAudioDuration(self, filename):
command = [self.FFMPEG_BIN, '-i', filename]
try:
@@ -588,6 +653,10 @@ class Core:
d = d.split(' ')[3]
d = d.split(':')
duration = float(d[0])*3600 + float(d[1])*60 + float(d[2])
+ return duration
+
+ def readAudioFile(self, filename, parent):
+ duration = self.getAudioDuration(filename)
command = [
self.FFMPEG_BIN,
diff --git a/src/main.py b/src/main.py
index 317237c..6a9a25e 100644
--- a/src/main.py
+++ b/src/main.py
@@ -12,7 +12,7 @@ def main():
wd = os.path.dirname(os.path.realpath(__file__))
# make local imports work everywhere
- sys.path.append(wd)
+ sys.path.insert(0, wd)
mode = 'GUI'
if len(sys.argv) > 2:
diff --git a/src/toolkit.py b/src/toolkit.py
index 589d8e6..5493f37 100644
--- a/src/toolkit.py
+++ b/src/toolkit.py
@@ -13,11 +13,14 @@ def badName(name):
return any([letter in string.punctuation for letter in name])
+def alphabetizeDict(dictionary):
+ '''Alphabetizes a dict into OrderedDict '''
+ return OrderedDict(sorted(dictionary.items(), key=lambda t: t[0]))
+
+
def presetToString(dictionary):
- '''Alphabetizes a dict into OrderedDict & returns string repr'''
- return repr(
- OrderedDict(sorted(dictionary.items(), key=lambda t: t[0]))
- )
+ '''Returns string repr of a preset'''
+ return repr(alphabetizeDict(dictionary))
def presetFromString(string):
--
cgit v1.2.3
From aa464632c64725201dc7584ebf6bf2c3d06b47b6 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 16 Jul 2017 23:13:00 -0400
Subject: new hotkey to preview the ffmpeg command
---
setup.py | 2 +-
src/components/video.py | 4 ++--
src/core.py | 6 +++++-
src/mainwindow.py | 19 ++++++++++++++++++-
4 files changed, 26 insertions(+), 5 deletions(-)
diff --git a/setup.py b/setup.py
index 71dc51f..6ef688a 100644
--- a/setup.py
+++ b/setup.py
@@ -30,7 +30,7 @@ setup(
'Topic :: Multimedia :: Video :: Non-Linear Editor',
],
keywords=['visualizer', 'visualization', 'commandline video',
- 'video editor', 'ffmpeg', 'podcast']
+ 'video editor', 'ffmpeg', 'podcast'],
packages=[
'avpython',
'avpython.components'
diff --git a/src/components/video.py b/src/components/video.py
index e1f182c..9e3db30 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -45,7 +45,7 @@ class Video:
'-i', self.videoPath,
'-f', 'image2pipe',
'-pix_fmt', 'rgba',
- '-filter:v', 'scale=%s:%s' % scale(
+ '-filter_complex', '[0:v] scale=%s:%s' % scale(
self.scale, self.width, self.height, str),
'-vcodec', 'rawvideo', '-',
]
@@ -272,7 +272,7 @@ class Component(Component):
'-i', self.videoPath,
'-f', 'image2pipe',
'-pix_fmt', 'rgba',
- '-filter:v', 'scale=%s:%s' % scale(
+ '-filter_complex', '[0:v] scale=%s:%s' % scale(
self.scale, width, height, str),
'-vcodec', 'rawvideo', '-',
'-ss', '90',
diff --git a/src/core.py b/src/core.py
index 324b04f..a0a028b 100644
--- a/src/core.py
+++ b/src/core.py
@@ -541,6 +541,10 @@ class Core:
extraInputFile, params = params
ffmpegCommand.extend([
'-t', safeDuration,
+ # Tell ffmpeg about shorter clips (seemingly not needed)
+ # streamDuration = self.getAudioDuration(extraInputFile)
+ # if streamDuration > float(safeDuration)
+ # else "{0:.3f}".format(streamDuration),
'-i', extraInputFile
])
# Construct dataset of extra filters we'll need to add later
@@ -551,7 +555,7 @@ class Core:
ffmpegFilter, params[ffmpegFilter]
))
- # Start creating avfilters!
+ # Start creating avfilters! Popen-style, so don't use semicolons;
extraFilterCommand = []
if globalFilters <= 0:
diff --git a/src/mainwindow.py b/src/mainwindow.py
index 76ed179..ca8e697 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -305,7 +305,12 @@ class MainWindow(QtWidgets.QMainWindow):
QtWidgets.QShortcut("Ctrl+A", self.window, self.openSaveProjectDialog)
QtWidgets.QShortcut("Ctrl+O", self.window, self.openOpenProjectDialog)
QtWidgets.QShortcut("Ctrl+N", self.window, self.createNewProject)
- QtWidgets.QShortcut("Ctrl+Alt+Shift+R", self.window, self.drawPreview)
+ QtWidgets.QShortcut(
+ "Ctrl+Alt+Shift+R", self.window, self.drawPreview
+ )
+ QtWidgets.QShortcut(
+ "Ctrl+Alt+Shift+F", self.window, self.showFfmpegCommand
+ )
QtWidgets.QShortcut(
"Ctrl+T", self.window,
@@ -580,6 +585,18 @@ class MainWindow(QtWidgets.QMainWindow):
def showPreviewImage(self, image):
self.previewWindow.changePixmap(image)
+ def showFfmpegCommand(self):
+ from textwrap import wrap
+ command = self.core.createFfmpegCommand(
+ self.window.lineEdit_audioFile.text(),
+ self.window.lineEdit_outputFile.text(),
+ self.core.getAudioDuration(self.window.lineEdit_audioFile.text())
+ )
+ lines = wrap(" ".join(command), 49)
+ self.showMessage(
+ msg="Current FFmpeg command:\n\n %s" % " ".join(lines)
+ )
+
def insertComponent(self, index):
componentList = self.window.listWidget_componentList
stackedWidget = self.window.stackedWidget
--
cgit v1.2.3
From b1713d38fa91e39f142b0c234b6405229aa149e1 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Mon, 17 Jul 2017 22:07:33 -0400
Subject: combined toolkit.py & frame.py into toolkit package
---
README.md | 2 +-
src/__main__.py | 4 +-
src/component.py | 31 -----------
src/components/color.py | 9 +--
src/components/image.py | 2 +-
src/components/original.py | 7 ++-
src/components/sound.py | 2 +-
src/components/text.py | 7 ++-
src/components/video.py | 2 +-
src/core.py | 2 +-
src/frame.py | 66 ----------------------
src/preview_thread.py | 2 +-
src/toolkit.py | 99 ---------------------------------
src/toolkit/__init__.py | 1 +
src/toolkit/common.py | 133 +++++++++++++++++++++++++++++++++++++++++++++
src/toolkit/frame.py | 66 ++++++++++++++++++++++
src/video_thread.py | 2 +-
17 files changed, 223 insertions(+), 214 deletions(-)
delete mode 100644 src/frame.py
delete mode 100644 src/toolkit.py
create mode 100644 src/toolkit/__init__.py
create mode 100644 src/toolkit/common.py
create mode 100644 src/toolkit/frame.py
diff --git a/README.md b/README.md
index 9149b4f..5f4e1e7 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ Dependencies
------------
Python 3.4, FFmpeg 3.3, PyQt5, Pillow-SIMD, NumPy
-**Note:** Pillow may be used as a drop-in replacement for Pillow-SIMD if problems are encountered installing. However this will result in much slower video export times. For help troubleshooting installation problems, the * For any problems with installing Pillow-SIMD, see the [Pillow installation guide](http://pillow.readthedocs.io/en/3.1.x/installation.html).
+**Note:** Pillow may be used as a drop-in replacement for Pillow-SIMD if problems are encountered installing. However this will result in much slower video export times. For help installing Pillow-SIMD, see the [Pillow installation guide](http://pillow.readthedocs.io/en/3.1.x/installation.html).
Installation
------------
diff --git a/src/__main__.py b/src/__main__.py
index a68739e..3babeae 100644
--- a/src/__main__.py
+++ b/src/__main__.py
@@ -1,3 +1,5 @@
+# Allows for launching with python3 -m avpython
+
from avpython.main import main
-main()
\ No newline at end of file
+main()
diff --git a/src/component.py b/src/component.py
index adb170e..7842bd6 100644
--- a/src/component.py
+++ b/src/component.py
@@ -112,37 +112,6 @@ class Component(QtCore.QObject):
def commandHelp(self):
'''Print help text for this Component's commandline arguments'''
- def pickColor(self):
- '''
- Use color picker to get color input from the user,
- and return this as an RGB string and QPushButton stylesheet.
- In a subclass apply stylesheet to any color selection widgets
- '''
- dialog = QtWidgets.QColorDialog()
- dialog.setOption(QtWidgets.QColorDialog.ShowAlphaChannel, True)
- color = dialog.getColor()
- if color.isValid():
- RGBstring = '%s,%s,%s' % (
- str(color.red()), str(color.green()), str(color.blue()))
- btnStyle = "QPushButton{background-color: %s; outline: none;}" \
- % color.name()
- return RGBstring, btnStyle
- else:
- return None, None
-
- def RGBFromString(self, string):
- '''Turns an RGB string like "255, 255, 255" into a tuple'''
- try:
- tup = tuple([int(i) for i in string.split(',')])
- if len(tup) != 3:
- raise ValueError
- for i in tup:
- if i > 255 or i < 0:
- raise ValueError
- return tup
- except:
- return (255, 255, 255)
-
def loadUi(self, filename):
return uic.loadUi(os.path.join(self.core.componentsPath, filename))
diff --git a/src/components/color.py b/src/components/color.py
index ef4dd95..8d2526d 100644
--- a/src/components/color.py
+++ b/src/components/color.py
@@ -5,7 +5,8 @@ from PIL.ImageQt import ImageQt
import os
from component import Component
-from frame import BlankFrame, FloodFrame, FramePainter, PaintColor
+from toolkit.frame import BlankFrame, FloodFrame, FramePainter, PaintColor
+from toolkit import rgbFromString, pickColor
class Component(Component):
@@ -76,8 +77,8 @@ class Component(Component):
return page
def update(self):
- self.color1 = self.RGBFromString(self.page.lineEdit_color1.text())
- self.color2 = self.RGBFromString(self.page.lineEdit_color2.text())
+ self.color1 = rgbFromString(self.page.lineEdit_color1.text())
+ self.color2 = rgbFromString(self.page.lineEdit_color2.text())
self.x = self.page.spinBox_x.value()
self.y = self.page.spinBox_y.value()
self.sizeWidth = self.page.spinBox_width.value()
@@ -229,7 +230,7 @@ class Component(Component):
}
def pickColor(self, num):
- RGBstring, btnStyle = super().pickColor()
+ RGBstring, btnStyle = pickColor()
if not RGBstring:
return
if num == 1:
diff --git a/src/components/image.py b/src/components/image.py
index c0d1c0d..7f3f610 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -3,7 +3,7 @@ from PyQt5 import QtGui, QtCore, QtWidgets
import os
from component import Component
-from frame import BlankFrame
+from toolkit.frame import BlankFrame
class Component(Component):
diff --git a/src/components/original.py b/src/components/original.py
index f5776a4..586204a 100644
--- a/src/components/original.py
+++ b/src/components/original.py
@@ -7,7 +7,8 @@ import time
from copy import copy
from component import Component
-from frame import BlankFrame
+from toolkit.frame import BlankFrame
+from toolkit import rgbFromString, pickColor
class Component(Component):
@@ -48,7 +49,7 @@ class Component(Component):
def update(self):
self.layout = self.page.comboBox_visLayout.currentIndex()
- self.visColor = self.RGBFromString(self.page.lineEdit_visColor.text())
+ self.visColor = rgbFromString(self.page.lineEdit_visColor.text())
self.scale = self.page.spinBox_scale.value()
self.y = self.page.spinBox_y.value()
@@ -116,7 +117,7 @@ class Component(Component):
self.visColor, self.layout)
def pickColor(self):
- RGBstring, btnStyle = super().pickColor()
+ RGBstring, btnStyle = pickColor()
if not RGBstring:
return
self.page.lineEdit_visColor.setText(RGBstring)
diff --git a/src/components/sound.py b/src/components/sound.py
index bd7d002..5b06405 100644
--- a/src/components/sound.py
+++ b/src/components/sound.py
@@ -2,7 +2,7 @@ from PyQt5 import QtGui, QtCore, QtWidgets
import os
from component import Component
-from frame import BlankFrame
+from toolkit.frame import BlankFrame
class Component(Component):
diff --git a/src/components/text.py b/src/components/text.py
index 19460e5..fc3ef5f 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -4,7 +4,8 @@ from PyQt5 import QtGui, QtCore, QtWidgets
import os
from component import Component
-from frame import FramePainter
+from toolkit.frame import FramePainter
+from toolkit import rgbFromString, pickColor
class Component(Component):
@@ -64,7 +65,7 @@ class Component(Component):
self.fontSize = self.page.spinBox_fontSize.value()
self.xPosition = self.page.spinBox_xTextAlign.value()
self.yPosition = self.page.spinBox_yTextAlign.value()
- self.textColor = self.RGBFromString(
+ self.textColor = rgbFromString(
self.page.lineEdit_textColor.text())
btnStyle = "QPushButton { background-color : %s; outline: none; }" \
% QColor(*self.textColor).name()
@@ -146,7 +147,7 @@ class Component(Component):
return image.finalize()
def pickColor(self):
- RGBstring, btnStyle = super().pickColor()
+ RGBstring, btnStyle = pickColor()
if not RGBstring:
return
self.page.lineEdit_textColor.setText(RGBstring)
diff --git a/src/components/video.py b/src/components/video.py
index 9e3db30..a9f334e 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -7,7 +7,7 @@ import threading
from queue import PriorityQueue
from component import Component, BadComponentInit
-from frame import BlankFrame
+from toolkit.frame import BlankFrame
from toolkit import openPipe, checkOutput
diff --git a/src/core.py b/src/core.py
index a0a028b..07c1f71 100644
--- a/src/core.py
+++ b/src/core.py
@@ -11,7 +11,7 @@ from importlib import import_module
from PyQt5.QtCore import QStandardPaths
import toolkit
-from frame import Frame
+from toolkit.frame import Frame
import video_thread
diff --git a/src/frame.py b/src/frame.py
deleted file mode 100644
index cddb611..0000000
--- a/src/frame.py
+++ /dev/null
@@ -1,66 +0,0 @@
-'''
- Common tools for drawing compatible frames in a Component's frameRender()
-'''
-from PyQt5 import QtGui
-from PIL import Image
-from PIL.ImageQt import ImageQt
-import sys
-import os
-
-
-class Frame:
- '''Controller class for all frames.'''
-
-
-class FramePainter(QtGui.QPainter):
- '''
- A QPainter for a blank frame, which can be converted into a
- Pillow image with finalize()
- '''
- def __init__(self, width, height):
- image = BlankFrame(width, height)
- self.image = QtGui.QImage(ImageQt(image))
- super().__init__(self.image)
-
- def setPen(self, RgbTuple):
- super().setPen(PaintColor(*RgbTuple))
-
- def finalize(self):
- self.end()
- imBytes = self.image.bits().asstring(self.image.byteCount())
-
- return Image.frombytes(
- 'RGBA', (self.image.width(), self.image.height()), imBytes
- )
-
-
-class PaintColor(QtGui.QColor):
- '''Reverse the painter colour if the hardware stores RGB values backward'''
- def __init__(self, r, g, b, a=255):
- if sys.byteorder == 'big':
- super().__init__(r, g, b, a)
- else:
- super().__init__(b, g, r, a)
-
-
-def FloodFrame(width, height, RgbaTuple):
- return Image.new("RGBA", (width, height), RgbaTuple)
-
-
-def BlankFrame(width, height):
- '''The base frame used by each component to start drawing.'''
- return FloodFrame(width, height, (0, 0, 0, 0))
-
-
-def Checkerboard(width, height):
- '''
- A checkerboard to represent transparency to the user.
- TODO: Would be cool to generate this image with numpy instead.
- '''
- image = FloodFrame(1920, 1080, (0, 0, 0, 0))
- image.paste(Image.open(
- os.path.join(Frame.core.wd, "background.png")),
- (0, 0)
- )
- image = image.resize((width, height))
- return image
diff --git a/src/preview_thread.py b/src/preview_thread.py
index 6c33aff..c28e048 100644
--- a/src/preview_thread.py
+++ b/src/preview_thread.py
@@ -9,7 +9,7 @@ from PIL.ImageQt import ImageQt
from queue import Queue, Empty
import os
-from frame import Checkerboard
+from toolkit.frame import Checkerboard
class Worker(QtCore.QObject):
diff --git a/src/toolkit.py b/src/toolkit.py
deleted file mode 100644
index 5493f37..0000000
--- a/src/toolkit.py
+++ /dev/null
@@ -1,99 +0,0 @@
-'''
- Common functions
-'''
-import string
-import os
-import sys
-import subprocess
-from collections import OrderedDict
-
-
-def badName(name):
- '''Returns whether a name contains non-alphanumeric chars'''
- return any([letter in string.punctuation for letter in name])
-
-
-def alphabetizeDict(dictionary):
- '''Alphabetizes a dict into OrderedDict '''
- return OrderedDict(sorted(dictionary.items(), key=lambda t: t[0]))
-
-
-def presetToString(dictionary):
- '''Returns string repr of a preset'''
- return repr(alphabetizeDict(dictionary))
-
-
-def presetFromString(string):
- '''Turns a string repr of OrderedDict into a regular dict'''
- return dict(eval(string))
-
-
-def appendUppercase(lst):
- for form, i in zip(lst, range(len(lst))):
- lst.append(form.upper())
- return lst
-
-
-def hideCmdWin(func):
- ''' Stops CMD window from appearing on Windows.
- Adapted from here: http://code.activestate.com/recipes/409002/
- '''
- def decorator(commandList, **kwargs):
- if sys.platform == 'win32':
- startupinfo = subprocess.STARTUPINFO()
- startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
- kwargs['startupinfo'] = startupinfo
- return func(commandList, **kwargs)
- return decorator
-
-
-@hideCmdWin
-def checkOutput(commandList, **kwargs):
- return subprocess.check_output(commandList, **kwargs)
-
-
-@hideCmdWin
-def openPipe(commandList, **kwargs):
- return subprocess.Popen(commandList, **kwargs)
-
-
-def disableWhenEncoding(func):
- ''' Blocks calls to a function while the video is being exported
- in MainWindow.
- '''
- def decorator(*args, **kwargs):
- if args[0].encoding:
- return
- else:
- return func(*args, **kwargs)
- return decorator
-
-
-def LoadDefaultSettings(self):
- ''' Runs once at each program start-up. Fills in default settings
- for any settings not found in settings.ini
- '''
- self.resolutions = [
- '1920x1080',
- '1280x720',
- '854x480'
- ]
-
- default = {
- "outputWidth": 1280,
- "outputHeight": 720,
- "outputFrameRate": 30,
- "outputAudioCodec": "AAC",
- "outputAudioBitrate": "192",
- "outputVideoCodec": "H264",
- "outputVideoBitrate": "2500",
- "outputVideoFormat": "yuv420p",
- "outputPreset": "medium",
- "outputFormat": "mp4",
- "outputContainer": "MP4",
- "projectDir": os.path.join(self.dataDir, 'projects'),
- }
-
- for parm, value in default.items():
- if self.settings.value(parm) is None:
- self.settings.setValue(parm, value)
diff --git a/src/toolkit/__init__.py b/src/toolkit/__init__.py
new file mode 100644
index 0000000..3fca275
--- /dev/null
+++ b/src/toolkit/__init__.py
@@ -0,0 +1 @@
+from toolkit.common import *
diff --git a/src/toolkit/common.py b/src/toolkit/common.py
new file mode 100644
index 0000000..e3a1649
--- /dev/null
+++ b/src/toolkit/common.py
@@ -0,0 +1,133 @@
+'''
+ Common functions
+'''
+from PyQt5 import QtWidgets
+import string
+import os
+import sys
+import subprocess
+from collections import OrderedDict
+
+
+def badName(name):
+ '''Returns whether a name contains non-alphanumeric chars'''
+ return any([letter in string.punctuation for letter in name])
+
+
+def alphabetizeDict(dictionary):
+ '''Alphabetizes a dict into OrderedDict '''
+ return OrderedDict(sorted(dictionary.items(), key=lambda t: t[0]))
+
+
+def presetToString(dictionary):
+ '''Returns string repr of a preset'''
+ return repr(alphabetizeDict(dictionary))
+
+
+def presetFromString(string):
+ '''Turns a string repr of OrderedDict into a regular dict'''
+ return dict(eval(string))
+
+
+def appendUppercase(lst):
+ for form, i in zip(lst, range(len(lst))):
+ lst.append(form.upper())
+ return lst
+
+
+def hideCmdWin(func):
+ ''' Stops CMD window from appearing on Windows.
+ Adapted from here: http://code.activestate.com/recipes/409002/
+ '''
+ def decorator(commandList, **kwargs):
+ if sys.platform == 'win32':
+ startupinfo = subprocess.STARTUPINFO()
+ startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
+ kwargs['startupinfo'] = startupinfo
+ return func(commandList, **kwargs)
+ return decorator
+
+
+@hideCmdWin
+def checkOutput(commandList, **kwargs):
+ return subprocess.check_output(commandList, **kwargs)
+
+
+@hideCmdWin
+def openPipe(commandList, **kwargs):
+ return subprocess.Popen(commandList, **kwargs)
+
+
+def disableWhenEncoding(func):
+ ''' Blocks calls to a function while the video is being exported
+ in MainWindow.
+ '''
+ def decorator(*args, **kwargs):
+ if args[0].encoding:
+ return
+ else:
+ return func(*args, **kwargs)
+ return decorator
+
+
+def pickColor():
+ '''
+ Use color picker to get color input from the user,
+ and return this as an RGB string and QPushButton stylesheet.
+ In a subclass apply stylesheet to any color selection widgets
+ '''
+ dialog = QtWidgets.QColorDialog()
+ dialog.setOption(QtWidgets.QColorDialog.ShowAlphaChannel, True)
+ color = dialog.getColor()
+ if color.isValid():
+ RGBstring = '%s,%s,%s' % (
+ str(color.red()), str(color.green()), str(color.blue()))
+ btnStyle = "QPushButton{background-color: %s; outline: none;}" \
+ % color.name()
+ return RGBstring, btnStyle
+ else:
+ return None, None
+
+
+def rgbFromString(string):
+ '''Turns an RGB string like "255, 255, 255" into a tuple'''
+ try:
+ tup = tuple([int(i) for i in string.split(',')])
+ if len(tup) != 3:
+ raise ValueError
+ for i in tup:
+ if i > 255 or i < 0:
+ raise ValueError
+ return tup
+ except:
+ return (255, 255, 255)
+
+
+def LoadDefaultSettings(self):
+ ''' Runs once at each program start-up. Fills in default settings
+ for any settings not found in settings.ini
+ '''
+ self.resolutions = [
+ '1920x1080',
+ '1280x720',
+ '854x480'
+ ]
+
+ default = {
+ "outputWidth": 1280,
+ "outputHeight": 720,
+ "outputFrameRate": 30,
+ "outputAudioCodec": "AAC",
+ "outputAudioBitrate": "192",
+ "outputVideoCodec": "H264",
+ "outputVideoBitrate": "2500",
+ "outputVideoFormat": "yuv420p",
+ "outputPreset": "medium",
+ "outputFormat": "mp4",
+ "outputContainer": "MP4",
+ "projectDir": os.path.join(self.dataDir, 'projects'),
+ }
+
+ for parm, value in default.items():
+ if self.settings.value(parm) is None:
+ self.settings.setValue(parm, value)
diff --git a/src/toolkit/frame.py b/src/toolkit/frame.py
new file mode 100644
index 0000000..cddb611
--- /dev/null
+++ b/src/toolkit/frame.py
@@ -0,0 +1,66 @@
+'''
+ Common tools for drawing compatible frames in a Component's frameRender()
+'''
+from PyQt5 import QtGui
+from PIL import Image
+from PIL.ImageQt import ImageQt
+import sys
+import os
+
+
+class Frame:
+ '''Controller class for all frames.'''
+
+
+class FramePainter(QtGui.QPainter):
+ '''
+ A QPainter for a blank frame, which can be converted into a
+ Pillow image with finalize()
+ '''
+ def __init__(self, width, height):
+ image = BlankFrame(width, height)
+ self.image = QtGui.QImage(ImageQt(image))
+ super().__init__(self.image)
+
+ def setPen(self, RgbTuple):
+ super().setPen(PaintColor(*RgbTuple))
+
+ def finalize(self):
+ self.end()
+ imBytes = self.image.bits().asstring(self.image.byteCount())
+
+ return Image.frombytes(
+ 'RGBA', (self.image.width(), self.image.height()), imBytes
+ )
+
+
+class PaintColor(QtGui.QColor):
+ '''Reverse the painter colour if the hardware stores RGB values backward'''
+ def __init__(self, r, g, b, a=255):
+ if sys.byteorder == 'big':
+ super().__init__(r, g, b, a)
+ else:
+ super().__init__(b, g, r, a)
+
+
+def FloodFrame(width, height, RgbaTuple):
+ return Image.new("RGBA", (width, height), RgbaTuple)
+
+
+def BlankFrame(width, height):
+ '''The base frame used by each component to start drawing.'''
+ return FloodFrame(width, height, (0, 0, 0, 0))
+
+
+def Checkerboard(width, height):
+ '''
+ A checkerboard to represent transparency to the user.
+ TODO: Would be cool to generate this image with numpy instead.
+ '''
+ image = FloodFrame(1920, 1080, (0, 0, 0, 0))
+ image.paste(Image.open(
+ os.path.join(Frame.core.wd, "background.png")),
+ (0, 0)
+ )
+ image = image.resize((width, height))
+ return image
diff --git a/src/video_thread.py b/src/video_thread.py
index 60db99f..1f2eaf5 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -19,7 +19,7 @@ import time
import signal
from toolkit import openPipe
-from frame import Checkerboard
+from toolkit.frame import Checkerboard
class Worker(QtCore.QObject):
--
cgit v1.2.3
From f454814867443ceeeca2a3a2c2a676947184503c Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 20 Jul 2017 20:31:38 -0400
Subject: ffmpeg functions moved to toolkit, component format simplified
component methods are auto-decorated & settings are now class variables
---
freeze.py | 7 +-
setup.py | 15 +-
src/command.py | 10 +-
src/component.py | 167 +++++++++++++-------
src/components/color.py | 8 +-
src/components/image.py | 11 +-
src/components/original.py | 11 +-
src/components/sound.py | 14 +-
src/components/text.py | 8 +-
src/components/video.py | 23 ++-
src/core.py | 379 ++++++++-------------------------------------
src/mainwindow.py | 81 ++++++----
src/presetmanager.py | 20 +--
src/preview_thread.py | 4 +-
src/toolkit/common.py | 12 +-
src/toolkit/core.py | 18 +++
src/toolkit/ffmpeg.py | 284 +++++++++++++++++++++++++++++++++
src/toolkit/frame.py | 6 +-
src/video_thread.py | 45 ++++--
19 files changed, 628 insertions(+), 495 deletions(-)
create mode 100644 src/toolkit/core.py
create mode 100644 src/toolkit/ffmpeg.py
diff --git a/freeze.py b/freeze.py
index c9b7918..3281cad 100644
--- a/freeze.py
+++ b/freeze.py
@@ -2,8 +2,8 @@ from cx_Freeze import setup, Executable
import sys
import os
-# Dependencies are automatically detected, but it might need
-# fine tuning.
+from setup import VERSION
+
deps = [os.path.join('src', p) for p in os.listdir('src') if p]
deps.append('ffmpeg.exe' if sys.platform == 'win32' else 'ffmpeg')
@@ -39,7 +39,6 @@ buildOptions = dict(
include_files=deps,
)
-
base = 'Win32GUI' if sys.platform == 'win32' else None
executables = [
@@ -53,7 +52,7 @@ executables = [
setup(
name='audio-visualizer-python',
- version='2.0',
+ version=VERSION,
description='GUI tool to render visualization videos of audio files',
options=dict(build_exe=buildOptions),
executables=executables
diff --git a/setup.py b/setup.py
index 6ef688a..5abb976 100644
--- a/setup.py
+++ b/setup.py
@@ -2,6 +2,9 @@ from setuptools import setup
import os
+VERSION = '2.0.0.rc1'
+
+
def package_files(directory):
paths = []
for (path, directories, filenames) in os.walk(directory):
@@ -12,7 +15,7 @@ def package_files(directory):
setup(
name='audio_visualizer_python',
- version='2.0.0rc1',
+ version=VERSION,
url='https://github.com/djfun/audio-visualizer-python/tree/feature-newgui',
license='MIT',
description='Create audio visualization videos from a GUI or commandline',
@@ -20,8 +23,7 @@ setup(
"them as Projects to continue editing later. Different components can "
"be added and layered to add visualizers, images, videos, gradients, "
"text, etc. Use Projects created in the GUI with commandline mode to "
- "automate your video production workflow without learning any complex "
- "syntax.",
+ "automate your video production workflow without any complex syntax.",
classifiers=[
'Development Status :: 4 - Beta',
'License :: OSI Approved :: MIT License',
@@ -29,10 +31,13 @@ setup(
'Intended Audience :: End Users/Desktop',
'Topic :: Multimedia :: Video :: Non-Linear Editor',
],
- keywords=['visualizer', 'visualization', 'commandline video',
- 'video editor', 'ffmpeg', 'podcast'],
+ keywords=[
+ 'visualizer', 'visualization', 'commandline video',
+ 'video editor', 'ffmpeg', 'podcast'
+ ],
packages=[
'avpython',
+ 'avpython.toolkit',
'avpython.components'
],
package_dir={'avpython': 'src'},
diff --git a/src/command.py b/src/command.py
index 84d798d..046a1bf 100644
--- a/src/command.py
+++ b/src/command.py
@@ -9,8 +9,8 @@ import os
import sys
import time
-import core
-from toolkit import LoadDefaultSettings
+from core import Core
+from toolkit import loadDefaultSettings
class Command(QtCore.QObject):
@@ -19,7 +19,7 @@ class Command(QtCore.QObject):
def __init__(self):
QtCore.QObject.__init__(self)
- self.core = core.Core()
+ self.core = Core()
self.dataDir = self.core.dataDir
self.canceled = False
@@ -54,8 +54,8 @@ class Command(QtCore.QObject):
nargs='*', action='append')
self.args = self.parser.parse_args()
- self.settings = self.core.settings
- LoadDefaultSettings(self)
+ self.settings = Core.settings
+ loadDefaultSettings(self)
if self.args.projpath:
projPath = self.args.projpath
diff --git a/src/component.py b/src/component.py
index 7842bd6..92cc65c 100644
--- a/src/component.py
+++ b/src/component.py
@@ -1,33 +1,87 @@
'''
- Base classes for components to import.
+ Base classes for components to import. Read comments for some documentation
+ on making a valid component.
'''
from PyQt5 import uic, QtCore, QtWidgets
import os
+from core import Core
+from toolkit.common import getPresetDir
-class Component(QtCore.QObject):
+
+class ComponentMetaclass(type(QtCore.QObject)):
+ '''
+ Checks the validity of each Component class imported, and
+ mutates some attributes for easier use by the core program.
+ E.g., takes only major version from version string & decorates methods
+ '''
+ def __new__(cls, name, parents, attrs):
+ # print('Creating %s component' % attrs['name'])
+
+ # Turn certain class methods into properties and classmethods
+ for key in ('error', 'properties', 'audio', 'commandHelp'):
+ if key not in attrs:
+ continue
+ attrs[key] = property(attrs[key])
+
+ for key in ('names'):
+ if key not in attrs:
+ continue
+ attrs[key] = classmethod(key)
+
+ # Turn version string into a number
+ try:
+ if 'version' not in attrs:
+ print(
+ 'No version attribute in %s. Defaulting to 1' %
+ attrs['name'])
+ attrs['version'] = 1
+ else:
+ attrs['version'] = int(attrs['version'].split('.')[0])
+ except ValueError:
+ print('%s component has an invalid version string:\n%s' % (
+ attrs['name'], str(attrs['version'])))
+ except KeyError:
+ print('%s component has no version string.' % attrs['name'])
+ else:
+ return super().__new__(cls, name, parents, attrs)
+ quit(1)
+
+
+class Component(QtCore.QObject, metaclass=ComponentMetaclass):
'''
- A class for components to inherit. Read comments for documentation
- on making a valid component. All subclasses must implement this signal:
- modified = QtCore.pyqtSignal(int, bool)
+ The base class for components to inherit.
'''
- def __init__(self, moduleIndex, compPos, core):
+ name = 'Component'
+ version = '1.0.0'
+ # The 1st number (before dot, aka the major version) is used to determine
+ # preset compatibility; the rest is ignored so it can be non-numeric.
+
+ modified = QtCore.pyqtSignal(int, dict)
+ # ^ Signal used to tell core program that the component state changed,
+ # you shouldn't need to use this directly, it is used by self.update()
+
+ def __init__(self, moduleIndex, compPos):
super().__init__()
self.currentPreset = None
- self.canceled = False
self.moduleIndex = moduleIndex
self.compPos = compPos
- self.core = core
+
+ # Stop lengthy processes in response to this variable
+ self.canceled = False
def __str__(self):
- return self.__doc__
+ return self.__class__.name
- def version(self):
- '''
- Change this number to identify new versions of a component
- '''
- return 1
+ def __repr__(self):
+ return '%s\n%s\n%s' % (
+ self.__class__.name, str(self.__class__.version), self.savePreset()
+ )
+
+ # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
+ # Properties
+ # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
def properties(self):
'''
@@ -43,19 +97,32 @@ class Component(QtCore.QObject):
'''
return
- def cancel(self):
+ def audio(self):
'''
- Stop any lengthy process in response to this variable
+ Return audio to mix into master as a tuple with two elements:
+ The first element can be:
+ - A string (path to audio file),
+ - Or an object that returns audio data through a pipe
+ The second element must be a dictionary of ffmpeg filters/options
+ to apply to the input stream. See the filter docs for ideas:
+ https://ffmpeg.org/ffmpeg-filters.html
'''
- self.canceled = True
- def reset(self):
- self.canceled = False
-
- def update(self):
+ def names():
'''
- Read your widget values from self.page, then call super().update()
+ Alternative names for renaming a component between project files.
'''
+ return []
+
+ def commandHelp(self):
+ '''Help text as string for this component's commandline arguments'''
+
+ # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
+ # Methods
+ # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
+
+ def update(self):
+ '''Read widget values from self.page, then call super().update()'''
self.parent.drawPreview()
saveValueStore = self.savePreset()
saveValueStore['preset'] = self.currentPreset
@@ -92,7 +159,7 @@ class Component(QtCore.QObject):
'''
if arg.startswith('preset='):
_, preset = arg.split('=', 1)
- path = os.path.join(self.core.getPresetDir(self), preset)
+ path = os.path.join(getPresetDir(self), preset)
if not os.path.exists(path):
print('Couldn\'t locate preset "%s"' % preset)
quit(1)
@@ -106,14 +173,19 @@ class Component(QtCore.QObject):
self.__doc__, 'Usage:\n'
'Open a preset for this component:\n'
' "preset=Preset Name"')
- self.commandHelp()
+ print(self.commandHelp)
quit(0)
- def commandHelp(self):
- '''Print help text for this Component's commandline arguments'''
-
def loadUi(self, filename):
- return uic.loadUi(os.path.join(self.core.componentsPath, filename))
+ '''Load a Qt Designer ui file to use for this component's widget'''
+ return uic.loadUi(os.path.join(Core.componentsPath, filename))
+
+ def cancel(self):
+ '''Stop any lengthy process in response to this variable.'''
+ self.canceled = True
+
+ def reset(self):
+ self.canceled = False
'''
### Reference methods for creating a new component
@@ -121,47 +193,34 @@ class Component(QtCore.QObject):
def widget(self, parent):
self.parent = parent
- page = self.loadUi('example.ui')
+ self.settings = parent.settings
+ self.page = self.loadUi('example.ui')
# --- connect widget signals here ---
- self.page = page
- return page
+ return self.page
def previewRender(self, previewWorker):
- width = int(previewWorker.core.settings.value('outputWidth'))
+ width = int(self.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
- from frame import BlankFrame
+ from toolkit.frame import BlankFrame
image = BlankFrame(width, height)
return image
def frameRender(self, layerNo, frameNo):
audioArrayIndex = frameNo * self.sampleSize
- width = int(self.worker.core.settings.value('outputWidth'))
- height = int(self.worker.core.settings.value('outputHeight'))
- from frame import BlankFrame
+ width = int(self.settings.value('outputWidth'))
+ height = int(self.settings.value('outputHeight'))
+ from toolkit.frame import BlankFrame
image = BlankFrame(width, height)
return image
-
- def audio(self):
- \'''
- Return audio to mix into master as a tuple with two elements:
- The first element can be:
- - A string (path to audio file),
- - Or an object that returns audio data through a pipe
- The second element must be a dictionary of ffmpeg filters/options
- to apply to the input stream. See the filter docs for ideas:
- https://ffmpeg.org/ffmpeg-filters.html
- \'''
-
- @classmethod
- def names(cls):
- \'''
- Alternative names for renaming a component between project files.
- \'''
- return []
'''
class BadComponentInit(Exception):
+ '''
+ General purpose exception components can raise to indicate
+ a Python issue with e.g., dynamic creation of instances or something.
+ Decorative for now, may have future use for logging.
+ '''
def __init__(self, arg, name):
string = '''################################
Mandatory argument "%s" not specified
diff --git a/src/components/color.py b/src/components/color.py
index 8d2526d..03371e7 100644
--- a/src/components/color.py
+++ b/src/components/color.py
@@ -10,13 +10,12 @@ from toolkit import rgbFromString, pickColor
class Component(Component):
- '''Color'''
-
- modified = QtCore.pyqtSignal(int, dict)
+ name = 'Color'
+ version = '1.0.0'
def widget(self, parent):
self.parent = parent
- self.settings = self.parent.core.settings
+ self.settings = parent.settings
page = self.loadUi('color.ui')
self.color1 = (0, 0, 0)
@@ -211,7 +210,6 @@ class Component(Component):
def savePreset(self):
return {
- 'preset': self.currentPreset,
'color1': self.color1,
'color2': self.color2,
'x': self.x,
diff --git a/src/components/image.py b/src/components/image.py
index 7f3f610..591e03e 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -2,18 +2,18 @@ from PIL import Image, ImageDraw, ImageEnhance
from PyQt5 import QtGui, QtCore, QtWidgets
import os
+from core import Core
from component import Component
from toolkit.frame import BlankFrame
class Component(Component):
- '''Image'''
-
- modified = QtCore.pyqtSignal(int, dict)
+ name = 'Image'
+ version = '1.0.0'
def widget(self, parent):
self.parent = parent
- self.settings = self.parent.core.settings
+ self.settings = parent.settings
page = self.loadUi('image.ui')
page.lineEdit_image.textChanged.connect(self.update)
@@ -102,7 +102,6 @@ class Component(Component):
def savePreset(self):
return {
- 'preset': self.currentPreset,
'image': self.imagePath,
'scale': self.scale,
'color': self.color,
@@ -117,7 +116,7 @@ class Component(Component):
imgDir = self.settings.value("componentDir", os.path.expanduser("~"))
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
self.page, "Choose Image", imgDir,
- "Image Files (%s)" % " ".join(self.core.imageFormats))
+ "Image Files (%s)" % " ".join(Core.imageFormats))
if filename:
self.settings.setValue("componentDir", os.path.dirname(filename))
self.page.lineEdit_image.setText(filename)
diff --git a/src/components/original.py b/src/components/original.py
index 586204a..ae40df3 100644
--- a/src/components/original.py
+++ b/src/components/original.py
@@ -12,17 +12,15 @@ from toolkit import rgbFromString, pickColor
class Component(Component):
- '''Classic Visualizer'''
+ name = 'Classic Visualizer'
+ version = '1.0.0'
- modified = QtCore.pyqtSignal(int, dict)
-
- @classmethod
- def names(cls):
+ def names():
return ['Original Audio Visualization']
def widget(self, parent):
self.parent = parent
- self.settings = self.parent.core.settings
+ self.settings = parent.settings
self.visColor = (255, 255, 255)
self.scale = 20
self.y = 0
@@ -68,7 +66,6 @@ class Component(Component):
def savePreset(self):
return {
- 'preset': self.currentPreset,
'layout': self.layout,
'visColor': self.visColor,
'scale': self.scale,
diff --git a/src/components/sound.py b/src/components/sound.py
index 5b06405..677a22f 100644
--- a/src/components/sound.py
+++ b/src/components/sound.py
@@ -1,14 +1,14 @@
from PyQt5 import QtGui, QtCore, QtWidgets
import os
+from core import Core
from component import Component
from toolkit.frame import BlankFrame
class Component(Component):
- '''Sound'''
-
- modified = QtCore.pyqtSignal(int, dict)
+ name = 'Sound'
+ version = '1.0.0'
def widget(self, parent):
self.parent = parent
@@ -32,8 +32,8 @@ class Component(Component):
super().update()
def previewRender(self, previewWorker):
- width = int(previewWorker.core.settings.value('outputWidth'))
- height = int(previewWorker.core.settings.value('outputHeight'))
+ width = int(self.settings.value('outputWidth'))
+ height = int(self.settings.value('outputHeight'))
return BlankFrame(width, height)
def preFrameRender(self, **kwargs):
@@ -67,7 +67,7 @@ class Component(Component):
sndDir = self.settings.value("componentDir", os.path.expanduser("~"))
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
self.page, "Choose Sound", sndDir,
- "Audio Files (%s)" % " ".join(self.core.audioFormats))
+ "Audio Files (%s)" % " ".join(Core.audioFormats))
if filename:
self.settings.setValue("componentDir", os.path.dirname(filename))
self.page.lineEdit_sound.setText(filename)
@@ -101,7 +101,7 @@ class Component(Component):
key, arg = arg.split('=', 1)
if key == 'path':
if '*%s' % os.path.splitext(arg)[1] \
- not in self.core.audioFormats:
+ not in Core.audioFormats:
print("Not a supported audio format")
quit(1)
self.page.lineEdit_sound.setText(arg)
diff --git a/src/components/text.py b/src/components/text.py
index fc3ef5f..d511f22 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -9,9 +9,8 @@ from toolkit import rgbFromString, pickColor
class Component(Component):
- '''Title Text'''
-
- modified = QtCore.pyqtSignal(int, dict)
+ name = 'Title Text'
+ version = '1.0.0'
def __init__(self, *args):
super().__init__(*args)
@@ -19,7 +18,7 @@ class Component(Component):
def widget(self, parent):
self.parent = parent
- self.settings = self.parent.core.settings
+ self.settings = parent.settings
height = int(self.settings.value('outputHeight'))
width = int(self.settings.value('outputWidth'))
@@ -106,7 +105,6 @@ class Component(Component):
def savePreset(self):
return {
- 'preset': self.currentPreset,
'title': self.title,
'titleFont': self.titleFont.toString(),
'alignment': self.alignment,
diff --git a/src/components/video.py b/src/components/video.py
index a9f334e..b35c2e5 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -6,6 +6,7 @@ import subprocess
import threading
from queue import PriorityQueue
+from core import Core
from component import Component, BadComponentInit
from toolkit.frame import BlankFrame
from toolkit import openPipe, checkOutput
@@ -106,9 +107,8 @@ class Video:
class Component(Component):
- '''Video'''
-
- modified = QtCore.pyqtSignal(int, dict)
+ name = 'Video'
+ version = '1.0.0'
def widget(self, parent):
self.parent = parent
@@ -154,8 +154,8 @@ class Component(Component):
super().update()
def previewRender(self, previewWorker):
- width = int(previewWorker.core.settings.value('outputWidth'))
- height = int(previewWorker.core.settings.value('outputHeight'))
+ width = int(self.settings.value('outputWidth'))
+ height = int(self.settings.value('outputHeight'))
self.updateChunksize(width, height)
frame = self.getPreviewFrame(width, height)
if not frame:
@@ -190,7 +190,7 @@ class Component(Component):
def testAudioStream(self):
# test if an audio stream really exists
audioTestCommand = [
- self.core.FFMPEG_BIN,
+ Core.FFMPEG_BIN,
'-i', self.videoPath,
'-vn', '-f', 'null', '-'
]
@@ -209,12 +209,12 @@ class Component(Component):
def preFrameRender(self, **kwargs):
super().preFrameRender(**kwargs)
- width = int(self.worker.core.settings.value('outputWidth'))
- height = int(self.worker.core.settings.value('outputHeight'))
+ width = int(self.settings.value('outputWidth'))
+ height = int(self.settings.value('outputHeight'))
self.blankFrame_ = BlankFrame(width, height)
self.updateChunksize(width, height)
self.video = Video(
- ffmpeg=self.core.FFMPEG_BIN, videoPath=self.videoPath,
+ ffmpeg=Core.FFMPEG_BIN, videoPath=self.videoPath,
width=width, height=height, chunkSize=self.chunkSize,
frameRate=int(self.settings.value("outputFrameRate")),
parent=self.parent, loopVideo=self.loopVideo,
@@ -240,7 +240,6 @@ class Component(Component):
def savePreset(self):
return {
- 'preset': self.currentPreset,
'video': self.videoPath,
'loop': self.loopVideo,
'useAudio': self.useAudio,
@@ -255,7 +254,7 @@ class Component(Component):
imgDir = self.settings.value("componentDir", os.path.expanduser("~"))
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
self.page, "Choose Video",
- imgDir, "Video Files (%s)" % " ".join(self.core.videoFormats)
+ imgDir, "Video Files (%s)" % " ".join(Core.videoFormats)
)
if filename:
self.settings.setValue("componentDir", os.path.dirname(filename))
@@ -298,7 +297,7 @@ class Component(Component):
if not arg.startswith('preset=') and '=' 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 '*%s' % os.path.splitext(arg)[1] in Core.videoFormats:
self.page.lineEdit_video.setText(arg)
self.page.spinBox_scale.setValue(100)
self.page.checkBox_loop.setChecked(True)
diff --git a/src/core.py b/src/core.py
index 07c1f71..dd2ef18 100644
--- a/src/core.py
+++ b/src/core.py
@@ -1,46 +1,56 @@
'''
Home to the Core class which tracks program state. Used by GUI & commandline
'''
+from PyQt5 import QtCore, QtGui, uic
import sys
import os
-from PyQt5 import QtCore, QtGui, uic
-import subprocess as sp
-import numpy
import json
from importlib import import_module
-from PyQt5.QtCore import QStandardPaths
import toolkit
-from toolkit.frame import Frame
+from toolkit.ffmpeg import findFfmpeg
import video_thread
class Core:
'''
MainWindow and Command module both use an instance of this class
- to store the program state. This object tracks the components,
- opens projects and presets, and stores settings/paths to data.
+ to store the main program state. This object tracks the components
+ as an instance, has methods for managing the components and for
+ opening/creating project files and presets.
'''
- def __init__(self):
- Frame.core = self
- self.dataDir = QStandardPaths.writableLocation(
- QStandardPaths.AppConfigLocation
- )
- self.presetDir = os.path.join(self.dataDir, 'presets')
+
+ @classmethod
+ def storeSettings(cls):
+ '''
+ Stores settings/paths to directories as class variables
+ '''
if getattr(sys, 'frozen', False):
# frozen
- self.wd = os.path.dirname(sys.executable)
+ wd = os.path.dirname(sys.executable)
else:
- # unfrozen
- self.wd = os.path.dirname(os.path.realpath(__file__))
- self.componentsPath = os.path.join(self.wd, 'components')
- self.settings = QtCore.QSettings(
- os.path.join(self.dataDir, 'settings.ini'),
- QtCore.QSettings.IniFormat
- )
+ wd = os.path.dirname(os.path.realpath(__file__))
- self.loadEncoderOptions()
- self.videoFormats = toolkit.appendUppercase([
+ dataDir = QtCore.QStandardPaths.writableLocation(
+ QtCore.QStandardPaths.AppConfigLocation
+ )
+ with open(os.path.join(wd, 'encoder-options.json')) as json_file:
+ encoderOptions = json.load(json_file)
+
+ settings = {
+ 'wd': wd,
+ 'dataDir': dataDir,
+ 'settings': QtCore.QSettings(
+ os.path.join(dataDir, 'settings.ini'),
+ QtCore.QSettings.IniFormat),
+ 'presetDir': os.path.join(dataDir, 'presets'),
+ 'componentsPath': os.path.join(wd, 'components'),
+ 'encoderOptions': encoderOptions,
+ 'FFMPEG_BIN': findFfmpeg(),
+ 'canceled': False,
+ }
+
+ settings['videoFormats'] = toolkit.appendUppercase([
'*.mp4',
'*.mov',
'*.mkv',
@@ -48,7 +58,7 @@ class Core:
'*.webm',
'*.flv',
])
- self.audioFormats = toolkit.appendUppercase([
+ settings['audioFormats'] = toolkit.appendUppercase([
'*.mp3',
'*.wav',
'*.ogg',
@@ -56,7 +66,7 @@ class Core:
'*.flac',
'*.aac',
])
- self.imageFormats = toolkit.appendUppercase([
+ settings['imageFormats'] = toolkit.appendUppercase([
'*.png',
'*.jpg',
'*.tif',
@@ -68,15 +78,22 @@ class Core:
'*.xpm',
])
- self.FFMPEG_BIN = self.findFfmpeg()
+ # Register all settings as class variables
+ for classvar, val in settings.items():
+ setattr(cls, classvar, val)
+ # Make settings accessible to the toolkit package
+ toolkit.init(settings)
+
+ def __init__(self):
+ Core.storeSettings()
+
self.findComponents()
self.selectedComponents = []
- # copies of named presets to detect modification
- self.savedPresets = {}
+ self.savedPresets = {} # copies of presets to detect modification
def findComponents(self):
def findComponents():
- for f in sorted(os.listdir(self.componentsPath)):
+ for f in sorted(os.listdir(Core.componentsPath)):
name, ext = os.path.splitext(f)
if name.startswith("__"):
continue
@@ -88,7 +105,7 @@ class Core:
]
# store canonical module names and indexes
self.moduleIndexes = [i for i in range(len(self.modules))]
- self.compNames = [mod.Component.__doc__ for mod in self.modules]
+ self.compNames = [mod.Component.name for mod in self.modules]
self.altCompNames = []
# store alternative names for modules
for i, mod in enumerate(self.modules):
@@ -108,7 +125,7 @@ class Core:
return None
component = self.modules[moduleIndex].Component(
- moduleIndex, compPos, self
+ moduleIndex, compPos
)
self.selectedComponents.insert(
compPos,
@@ -171,10 +188,6 @@ class Core:
self.savedPresets[presetName] = dict(saveValueStore)
return True
- def getPresetDir(self, comp):
- return os.path.join(
- self.presetDir, str(comp), str(comp.version()))
-
def getPreset(self, filepath):
'''Returns the preset dict stored at this filepath'''
if not os.path.exists(filepath):
@@ -204,7 +217,7 @@ class Core:
widget.blockSignals(False)
for key, value in data['Settings']:
- self.settings.setValue(key, value)
+ Core.settings.setValue(key, value)
for tup in data['Components']:
name, vers, preset = tup
@@ -215,7 +228,7 @@ class Core:
if 'preset' in preset and preset['preset'] is not None:
nam = preset['preset']
filepath2 = os.path.join(
- self.presetDir, name, str(vers), nam)
+ Core.presetDir, name, str(vers), nam)
origSaveValueStore = self.getPreset(filepath2)
if origSaveValueStore:
self.savedPresets[nam] = dict(origSaveValueStore)
@@ -336,7 +349,7 @@ class Core:
presetName = preset['preset'] \
if preset['preset'] else os.path.basename(filepath)[:-4]
newPath = os.path.join(
- self.presetDir,
+ Core.presetDir,
name,
vers,
presetName
@@ -354,7 +367,7 @@ class Core:
def exportPreset(self, exportPath, compName, vers, origName):
internalPath = os.path.join(
- self.presetDir, compName, str(vers), origName
+ Core.presetDir, compName, str(vers), origName
)
if not os.path.exists(internalPath):
return
@@ -378,7 +391,7 @@ class Core:
'''Create a preset file (.avl) at filepath using args.
Or if filepath is empty, create an internal preset using args'''
if not filepath:
- dirname = os.path.join(self.presetDir, compName, str(vers))
+ dirname = os.path.join(Core.presetDir, compName, str(vers))
if not os.path.exists(dirname):
os.makedirs(dirname)
filepath = os.path.join(dirname, presetName)
@@ -417,13 +430,13 @@ class Core:
saveValueStore = comp.savePreset()
saveValueStore['preset'] = comp.currentPreset
f.write('%s\n' % str(comp))
- f.write('%s\n' % str(comp.version()))
+ f.write('%s\n' % str(comp.version))
f.write('%s\n' % toolkit.presetToString(saveValueStore))
f.write('\n[Settings]\n')
- for key in self.settings.allKeys():
+ for key in Core.settings.allKeys():
if key in settingsKeys:
- f.write('%s=%s\n' % (key, self.settings.value(key)))
+ f.write('%s=%s\n' % (key, Core.settings.value(key)))
if window:
f.write('\n[WindowFields]\n')
@@ -438,280 +451,8 @@ class Core:
except:
return False
- def loadEncoderOptions(self):
- file_path = os.path.join(self.wd, 'encoder-options.json')
- with open(file_path) as json_file:
- self.encoder_options = json.load(json_file)
-
- def findFfmpeg(self):
- if getattr(sys, 'frozen', False):
- # The application is frozen
- if sys.platform == "win32":
- return os.path.join(self.wd, 'ffmpeg.exe')
- else:
- return os.path.join(self.wd, 'ffmpeg')
-
- else:
- if sys.platform == "win32":
- return "ffmpeg"
- else:
- try:
- with open(os.devnull, "w") as f:
- toolkit.checkOutput(
- ['ffmpeg', '-version'], stderr=f
- )
- return "ffmpeg"
- except sp.CalledProcessError:
- return "avconv"
-
- def createFfmpegCommand(self, inputFile, outputFile, duration):
- '''
- Constructs the major ffmpeg command used to export the video
- '''
- safeDuration = "{0:.3f}".format(duration - 0.05) # used by filters
- duration = "{0:.3f}".format(duration + 0.1) # used by input sources
-
- # Test if user has libfdk_aac
- encoders = toolkit.checkOutput(
- "%s -encoders -hide_banner" % self.FFMPEG_BIN, shell=True
- )
- encoders = encoders.decode("utf-8")
-
- acodec = self.settings.value('outputAudioCodec')
-
- options = self.encoder_options
- containerName = self.settings.value('outputContainer')
- vcodec = self.settings.value('outputVideoCodec')
- vbitrate = str(self.settings.value('outputVideoBitrate'))+'k'
- acodec = self.settings.value('outputAudioCodec')
- abitrate = str(self.settings.value('outputAudioBitrate'))+'k'
-
- for cont in options['containers']:
- if cont['name'] == containerName:
- container = cont['container']
- break
-
- vencoders = options['video-codecs'][vcodec]
- aencoders = options['audio-codecs'][acodec]
-
- for encoder in vencoders:
- if encoder in encoders:
- vencoder = encoder
- break
-
- for encoder in aencoders:
- if encoder in encoders:
- aencoder = encoder
- break
-
- ffmpegCommand = [
- self.FFMPEG_BIN,
- '-thread_queue_size', '512',
- '-y', # overwrite the output file if it already exists.
-
- # INPUT VIDEO
- '-f', 'rawvideo',
- '-vcodec', 'rawvideo',
- '-s', '%sx%s' % (
- self.settings.value('outputWidth'),
- self.settings.value('outputHeight'),
- ),
- '-pix_fmt', 'rgba',
- '-r', self.settings.value('outputFrameRate'),
- '-t', duration,
- '-i', '-', # the video input comes from a pipe
- '-an', # the video input has no sound
-
- # INPUT SOUND
- '-t', duration,
- '-i', inputFile
- ]
-
- # Add extra audio inputs and any needed avfilters
- # NOTE: Global filters are currently hard-coded here for debugging use
- globalFilters = 0 # increase to add global filters
- extraAudio = [
- comp.audio() for comp in self.selectedComponents
- if 'audio' in comp.properties()
- ]
- if extraAudio or globalFilters > 0:
- # Add -i options for extra input files
- extraFilters = {}
- for streamNo, params in enumerate(reversed(extraAudio)):
- extraInputFile, params = params
- ffmpegCommand.extend([
- '-t', safeDuration,
- # Tell ffmpeg about shorter clips (seemingly not needed)
- # streamDuration = self.getAudioDuration(extraInputFile)
- # if streamDuration > float(safeDuration)
- # else "{0:.3f}".format(streamDuration),
- '-i', extraInputFile
- ])
- # Construct dataset of extra filters we'll need to add later
- for ffmpegFilter in params:
- if streamNo + 2 not in extraFilters:
- extraFilters[streamNo + 2] = []
- extraFilters[streamNo + 2].append((
- ffmpegFilter, params[ffmpegFilter]
- ))
-
- # Start creating avfilters! Popen-style, so don't use semicolons;
- extraFilterCommand = []
-
- if globalFilters <= 0:
- # Dictionary of last-used tmp labels for a given stream number
- tmpInputs = {streamNo: -1 for streamNo in extraFilters}
- else:
- # Insert blank entries for global filters into extraFilters
- # so the per-stream filters know what input to source later
- for streamNo in range(len(extraAudio), 0, -1):
- if streamNo + 1 not in extraFilters:
- extraFilters[streamNo + 1] = []
- # Also filter the primary audio track
- extraFilters[1] = []
- tmpInputs = {
- streamNo: globalFilters - 1
- for streamNo in extraFilters
- }
-
- # Add the global filters!
- # NOTE: list length must = globalFilters, currently hardcoded
- if tmpInputs:
- extraFilterCommand.extend([
- '[%s:a] ashowinfo [%stmp0]' % (
- str(streamNo),
- str(streamNo)
- )
- for streamNo in tmpInputs
- ])
-
- # Now add the per-stream filters!
- for streamNo, paramList in extraFilters.items():
- for param in paramList:
- source = '[%s:a]' % str(streamNo) \
- if tmpInputs[streamNo] == -1 else \
- '[%stmp%s]' % (
- str(streamNo), str(tmpInputs[streamNo])
- )
- tmpInputs[streamNo] = tmpInputs[streamNo] + 1
- extraFilterCommand.append(
- '%s %s%s [%stmp%s]' % (
- source, param[0], param[1], str(streamNo),
- str(tmpInputs[streamNo])
- )
- )
-
- # Join all the filters together and combine into 1 stream
- extraFilterCommand = "; ".join(extraFilterCommand) + '; ' \
- if tmpInputs else ''
- ffmpegCommand.extend([
- '-filter_complex',
- extraFilterCommand +
- '%s amix=inputs=%s:duration=first [a]'
- % (
- "".join([
- '[%stmp%s]' % (str(i), tmpInputs[i])
- if i in extraFilters else '[%s:a]' % str(i)
- for i in range(1, len(extraAudio) + 2)
- ]),
- str(len(extraAudio) + 1)
- ),
- ])
-
- # Only map audio from the filters, and video from the pipe
- ffmpegCommand.extend([
- '-map', '0:v',
- '-map', '[a]',
- ])
-
- ffmpegCommand.extend([
- # OUTPUT
- '-vcodec', vencoder,
- '-acodec', aencoder,
- '-b:v', vbitrate,
- '-b:a', abitrate,
- '-pix_fmt', self.settings.value('outputVideoFormat'),
- '-preset', self.settings.value('outputPreset'),
- '-f', container
- ])
-
- if acodec == 'aac':
- ffmpegCommand.append('-strict')
- ffmpegCommand.append('-2')
-
- ffmpegCommand.append(outputFile)
- return ffmpegCommand
-
- def getAudioDuration(self, filename):
- command = [self.FFMPEG_BIN, '-i', filename]
-
- try:
- fileInfo = toolkit.checkOutput(command, stderr=sp.STDOUT)
- except sp.CalledProcessError as ex:
- fileInfo = ex.output
-
- info = fileInfo.decode("utf-8").split('\n')
- for line in info:
- if 'Duration' in line:
- d = line.split(',')[0]
- d = d.split(' ')[3]
- d = d.split(':')
- duration = float(d[0])*3600 + float(d[1])*60 + float(d[2])
- return duration
-
- def readAudioFile(self, filename, parent):
- duration = self.getAudioDuration(filename)
-
- command = [
- self.FFMPEG_BIN,
- '-i', filename,
- '-f', 's16le',
- '-acodec', 'pcm_s16le',
- '-ar', '44100', # ouput will have 44100 Hz
- '-ac', '1', # mono (set to '2' for stereo)
- '-']
- in_pipe = toolkit.openPipe(
- command, stdout=sp.PIPE, stderr=sp.DEVNULL, bufsize=10**8
- )
-
- completeAudioArray = numpy.empty(0, dtype="int16")
-
- progress = 0
- lastPercent = None
- while True:
- if self.canceled:
- break
- # read 2 seconds of audio
- progress += 4
- raw_audio = in_pipe.stdout.read(88200*4)
- if len(raw_audio) == 0:
- break
- audio_array = numpy.fromstring(raw_audio, dtype="int16")
- completeAudioArray = numpy.append(completeAudioArray, audio_array)
-
- percent = int(100*(progress/duration))
- if percent >= 100:
- percent = 100
-
- if lastPercent != percent:
- string = 'Loading audio file: '+str(percent)+'%'
- parent.progressBarSetText.emit(string)
- parent.progressBarUpdate.emit(percent)
-
- lastPercent = percent
-
- in_pipe.kill()
- in_pipe.wait()
-
- # add 0s the end
- completeAudioArrayCopy = numpy.zeros(
- len(completeAudioArray) + 44100, dtype="int16")
- completeAudioArrayCopy[:len(completeAudioArray)] = completeAudioArray
- completeAudioArray = completeAudioArrayCopy
-
- return (completeAudioArray, duration)
-
def newVideoWorker(self, loader, audioFile, outputPath):
+ '''loader is MainWindow or Command object which must own the thread'''
self.videoThread = QtCore.QThread(loader)
videoWorker = video_thread.Worker(
loader, audioFile, outputPath, self.selectedComponents
@@ -727,7 +468,9 @@ class Core:
self.videoThread.wait()
def cancel(self):
- self.canceled = True
+ Core.canceled = True
+ toolkit.cancel()
def reset(self):
- self.canceled = False
+ Core.canceled = False
+ toolkit.reset()
diff --git a/src/mainwindow.py b/src/mainwindow.py
index ca8e697..9944d1a 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -14,13 +14,17 @@ import signal
import filecmp
import time
-import core
+from core import Core
import preview_thread
from presetmanager import PresetManager
-from toolkit import LoadDefaultSettings, disableWhenEncoding, checkOutput
+from toolkit import loadDefaultSettings, disableWhenEncoding, checkOutput
class PreviewWindow(QtWidgets.QLabel):
+ '''
+ Paints the preview QLabel and maintains the aspect ratio when the
+ window is resized.
+ '''
def __init__(self, parent, img):
super(PreviewWindow, self).__init__()
self.parent = parent
@@ -47,6 +51,14 @@ class PreviewWindow(QtWidgets.QLabel):
class MainWindow(QtWidgets.QMainWindow):
+ '''
+ The MainWindow wraps many Core methods in order to update the GUI
+ accordingly. E.g., instead of self.core.openProject(), it will use
+ self.openProject() and update the window titlebar within the wrapper.
+
+ MainWindow manages the autosave feature, although Core has the
+ primary functions for opening and creating project files.
+ '''
createVideo = QtCore.pyqtSignal()
newTask = QtCore.pyqtSignal(list) # for the preview window
@@ -57,25 +69,26 @@ class MainWindow(QtWidgets.QMainWindow):
# print('main thread id: {}'.format(QtCore.QThread.currentThreadId()))
self.window = window
- self.core = core.Core()
+ self.core = Core()
self.pages = [] # widgets of component settings
self.lastAutosave = time.time()
self.encoding = False
# Create data directory, load/create settings
- self.dataDir = self.core.dataDir
+ self.dataDir = Core.dataDir
+ self.presetDir = Core.presetDir
self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
- self.settings = self.core.settings
- LoadDefaultSettings(self)
+ self.settings = Core.settings
+ loadDefaultSettings(self)
self.presetManager = PresetManager(
uic.loadUi(
- os.path.join(self.core.wd, 'presetmanager.ui')), self)
+ os.path.join(Core.wd, 'presetmanager.ui')), self)
if not os.path.exists(self.dataDir):
os.makedirs(self.dataDir)
for neededDirectory in (
- self.core.presetDir, self.settings.value("projectDir")):
+ self.presetDir, self.settings.value("projectDir")):
if not os.path.exists(neededDirectory):
os.mkdir(neededDirectory)
@@ -120,7 +133,7 @@ class MainWindow(QtWidgets.QMainWindow):
window.pushButton_Cancel.clicked.connect(self.stopVideo)
- for i, container in enumerate(self.core.encoder_options['containers']):
+ for i, container in enumerate(Core.encoderOptions['containers']):
window.comboBox_videoContainer.addItem(container['name'])
if container['name'] == self.settings.value('outputContainer'):
selectedContainer = i
@@ -160,14 +173,14 @@ class MainWindow(QtWidgets.QMainWindow):
window.spinBox_aBitrate.valueChanged.connect(self.updateCodecSettings)
self.previewWindow = PreviewWindow(self, os.path.join(
- self.core.wd, "background.png"))
+ Core.wd, "background.png"))
window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
# Make component buttons
self.compMenu = QMenu()
self.compActions = []
for i, comp in enumerate(self.core.modules):
- action = self.compMenu.addAction(comp.Component.__doc__)
+ action = self.compMenu.addAction(comp.Component.name)
action.triggered.connect(
lambda _, item=i: self.core.insertComponent(0, item, self)
)
@@ -336,8 +349,14 @@ class MainWindow(QtWidgets.QMainWindow):
"Ctrl+Down", self.window,
activated=lambda: self.moveComponent(1)
)
- QtWidgets.QShortcut("Ctrl+Home", self.window, self.moveComponentTop)
- QtWidgets.QShortcut("Ctrl+End", self.window, self.moveComponentBottom)
+ QtWidgets.QShortcut(
+ "Ctrl+Home", self.window,
+ activated=lambda: self.moveComponent('top')
+ )
+ QtWidgets.QShortcut(
+ "Ctrl+End", self.window,
+ activated=lambda: self.moveComponent('bottom')
+ )
QtWidgets.QShortcut("Ctrl+r", self.window, self.removeComponent)
@QtCore.pyqtSlot()
@@ -389,7 +408,7 @@ class MainWindow(QtWidgets.QMainWindow):
vCodecWidget.clear()
aCodecWidget.clear()
- for container in self.core.encoder_options['containers']:
+ for container in Core.encoderOptions['containers']:
if container['name'] == name:
for vCodec in container['video-codecs']:
vCodecWidget.addItem(vCodec)
@@ -397,6 +416,7 @@ class MainWindow(QtWidgets.QMainWindow):
aCodecWidget.addItem(aCodec)
def updateCodecSettings(self):
+ '''Updates settings.ini to match encoder option widgets'''
vCodecWidget = self.window.comboBox_videoCodec
vBitrateWidget = self.window.spinBox_vBitrate
aBitrateWidget = self.window.spinBox_aBitrate
@@ -416,11 +436,12 @@ class MainWindow(QtWidgets.QMainWindow):
if not self.currentProject:
if os.path.exists(self.autosavePath):
os.remove(self.autosavePath)
- elif force or time.time() - self.lastAutosave >= 0.1:
+ elif force or time.time() - self.lastAutosave >= 0.2:
self.core.createProjectFile(self.autosavePath, self.window)
self.lastAutosave = time.time()
def autosaveExists(self, identical=True):
+ '''Determines if creating the autosave should be blocked.'''
try:
if self.currentProject and os.path.exists(self.autosavePath) \
and filecmp.cmp(
@@ -432,6 +453,7 @@ class MainWindow(QtWidgets.QMainWindow):
return False
def saveProjectChanges(self):
+ '''Overwrites project file with autosave file'''
try:
os.remove(self.currentProject)
os.rename(self.autosavePath, self.currentProject)
@@ -447,7 +469,7 @@ class MainWindow(QtWidgets.QMainWindow):
fileName, _ = QtWidgets.QFileDialog.getOpenFileName(
self.window, "Open Audio File",
- inputDir, "Audio Files (%s)" % " ".join(self.core.audioFormats))
+ inputDir, "Audio Files (%s)" % " ".join(Core.audioFormats))
if fileName:
self.settings.setValue("inputDir", os.path.dirname(fileName))
@@ -460,7 +482,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.window, "Set Output Video File",
outputDir,
"Video Files (%s);; All Files (*)" % " ".join(
- self.core.videoFormats))
+ Core.videoFormats))
if fileName:
self.settings.setValue("outputDir", os.path.dirname(fileName))
@@ -587,10 +609,11 @@ class MainWindow(QtWidgets.QMainWindow):
def showFfmpegCommand(self):
from textwrap import wrap
- command = self.core.createFfmpegCommand(
+ from toolkit.ffmpeg import createFfmpegCommand
+ command = createFfmpegCommand(
self.window.lineEdit_audioFile.text(),
self.window.lineEdit_outputFile.text(),
- self.core.getAudioDuration(self.window.lineEdit_audioFile.text())
+ self.core.selectedComponents
)
lines = wrap(" ".join(command), 49)
self.showMessage(
@@ -603,7 +626,7 @@ class MainWindow(QtWidgets.QMainWindow):
componentList.insertItem(
index,
- self.core.selectedComponents[index].__doc__)
+ self.core.selectedComponents[index].name)
componentList.setCurrentRow(index)
# connect to signal that adds an asterisk when modified
@@ -632,6 +655,10 @@ class MainWindow(QtWidgets.QMainWindow):
def moveComponent(self, change):
'''Moves a component relatively from its current position'''
componentList = self.window.listWidget_componentList
+ if change == 'top':
+ change = -componentList.currentRow()
+ elif change == 'bottom':
+ change = len(componentList)-componentList.currentRow()-1
stackedWidget = self.window.stackedWidget
row = componentList.currentRow()
@@ -650,21 +677,9 @@ class MainWindow(QtWidgets.QMainWindow):
stackedWidget.setCurrentIndex(newRow)
self.drawPreview()
- @disableWhenEncoding
- def moveComponentTop(self):
- componentList = self.window.listWidget_componentList
- row = -componentList.currentRow()
- self.moveComponent(row)
-
- @disableWhenEncoding
- def moveComponentBottom(self):
- componentList = self.window.listWidget_componentList
- row = len(componentList)-componentList.currentRow()-1
- self.moveComponent(row)
-
@disableWhenEncoding
def dragComponent(self, event):
- '''Drop event for the component listwidget'''
+ '''Used as Qt drop event for the component listwidget'''
componentList = self.window.listWidget_componentList
modelIndexes = [
diff --git a/src/presetmanager.py b/src/presetmanager.py
index 6e003a1..825fdee 100644
--- a/src/presetmanager.py
+++ b/src/presetmanager.py
@@ -15,11 +15,11 @@ class PresetManager(QtWidgets.QDialog):
self.parent = parent
self.core = parent.core
self.settings = parent.settings
- self.presetDir = self.core.presetDir
+ self.presetDir = parent.presetDir
if not self.settings.value('presetDir'):
self.settings.setValue(
"presetDir",
- os.path.join(self.core.dataDir, 'projects'))
+ os.path.join(parent.dataDir, 'projects'))
self.findPresets()
@@ -161,7 +161,7 @@ class PresetManager(QtWidgets.QDialog):
selectedComponents[index].savePreset()
saveValueStore['preset'] = newName
componentName = str(selectedComponents[index]).strip()
- vers = selectedComponents[index].version()
+ vers = selectedComponents[index].version
self.createNewPreset(
componentName, vers, newName,
saveValueStore, window=self.parent.window)
@@ -195,13 +195,13 @@ class PresetManager(QtWidgets.QDialog):
def openPreset(self, presetName, compPos=None):
componentList = self.parent.window.listWidget_componentList
- selectedComponents = self.parent.core.selectedComponents
+ selectedComponents = self.core.selectedComponents
index = compPos if compPos is not None else componentList.currentRow()
if index == -1:
return
componentName = str(selectedComponents[index]).strip()
- version = selectedComponents[index].version()
+ version = selectedComponents[index].version
dirname = os.path.join(self.presetDir, componentName, str(version))
filepath = os.path.join(dirname, presetName)
self.core.openPreset(filepath, index, presetName)
@@ -243,6 +243,7 @@ class PresetManager(QtWidgets.QDialog):
parent=window if window else self.window)
def openRenamePresetDialog(self):
+ # TODO: maintain consistency by changing this to call createNewPreset()
presetList = self.window.listWidget_presets
if presetList.currentRow() == -1:
return
@@ -273,11 +274,12 @@ class PresetManager(QtWidgets.QDialog):
os.rename(oldPath, newPath)
self.findPresets()
self.drawPresetList()
-
for i, comp in enumerate(self.core.selectedComponents):
- if comp.currentPreset == oldName:
- comp.currentPreset = newName
- self.parent.updateComponentTitle(i, True)
+ if toolkit.getPresetDir(comp) == path \
+ and comp.currentPreset == oldName:
+ self.core.openPreset(newPath, i, newName)
+ self.parent.updateComponentTitle(i, False)
+ self.parent.drawPreview()
break
def openImportDialog(self):
diff --git a/src/preview_thread.py b/src/preview_thread.py
index c28e048..3fc73b3 100644
--- a/src/preview_thread.py
+++ b/src/preview_thread.py
@@ -22,8 +22,8 @@ class Worker(QtCore.QObject):
parent.newTask.connect(self.createPreviewImage)
parent.processTask.connect(self.process)
self.parent = parent
- self.core = self.parent.core
- self.settings = self.parent.core.settings
+ self.core = parent.core
+ self.settings = parent.settings
self.queue = queue
width = int(self.settings.value('outputWidth'))
diff --git a/src/toolkit/common.py b/src/toolkit/common.py
index e3a1649..763d582 100644
--- a/src/toolkit/common.py
+++ b/src/toolkit/common.py
@@ -8,6 +8,13 @@ import sys
import subprocess
from collections import OrderedDict
+from toolkit.core import *
+
+
+def getPresetDir(comp):
+ '''Get the preset subdirectory for a particular version of a component'''
+ return os.path.join(Core.presetDir, str(comp), str(comp.version))
+
def badName(name):
'''Returns whether a name contains non-alphanumeric chars'''
@@ -103,8 +110,9 @@ def rgbFromString(string):
return (255, 255, 255)
-def LoadDefaultSettings(self):
- ''' Runs once at each program start-up. Fills in default settings
+def loadDefaultSettings(self):
+ '''
+ Runs once at each program start-up. Fills in default settings
for any settings not found in settings.ini
'''
self.resolutions = [
diff --git a/src/toolkit/core.py b/src/toolkit/core.py
new file mode 100644
index 0000000..a96a684
--- /dev/null
+++ b/src/toolkit/core.py
@@ -0,0 +1,18 @@
+class Core:
+ '''A very complicated class for tracking settings'''
+
+
+def init(settings):
+ global Core
+ for classvar, val in settings.items():
+ setattr(Core, classvar, val)
+
+
+def cancel():
+ global Core
+ Core.canceled = True
+
+
+def reset():
+ global Core
+ Core.canceled = False
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
new file mode 100644
index 0000000..89d4e9d
--- /dev/null
+++ b/src/toolkit/ffmpeg.py
@@ -0,0 +1,284 @@
+'''
+ Tools for using ffmpeg
+'''
+import numpy
+import sys
+import os
+import subprocess as sp
+
+from toolkit.common import Core, checkOutput, openPipe
+
+
+def findFfmpeg():
+ if getattr(sys, 'frozen', False):
+ # The application is frozen
+ if sys.platform == "win32":
+ return os.path.join(Core.wd, 'ffmpeg.exe')
+ else:
+ return os.path.join(Core.wd, 'ffmpeg')
+
+ else:
+ if sys.platform == "win32":
+ return "ffmpeg"
+ else:
+ try:
+ with open(os.devnull, "w") as f:
+ checkOutput(
+ ['ffmpeg', '-version'], stderr=f
+ )
+ return "ffmpeg"
+ except sp.CalledProcessError:
+ return "avconv"
+
+
+def createFfmpegCommand(inputFile, outputFile, components, duration=-1):
+ '''
+ Constructs the major ffmpeg command used to export the video
+ '''
+ if duration == -1:
+ duration = getAudioDuration(inputFile)
+
+ safeDuration = "{0:.3f}".format(duration - 0.05) # used by filters
+ duration = "{0:.3f}".format(duration + 0.1) # used by input sources
+
+ # Test if user has libfdk_aac
+ encoders = checkOutput(
+ "%s -encoders -hide_banner" % Core.FFMPEG_BIN, shell=True
+ )
+ encoders = encoders.decode("utf-8")
+
+ acodec = Core.settings.value('outputAudioCodec')
+
+ options = Core.encoderOptions
+ containerName = Core.settings.value('outputContainer')
+ vcodec = Core.settings.value('outputVideoCodec')
+ vbitrate = str(Core.settings.value('outputVideoBitrate'))+'k'
+ acodec = Core.settings.value('outputAudioCodec')
+ abitrate = str(Core.settings.value('outputAudioBitrate'))+'k'
+
+ for cont in options['containers']:
+ if cont['name'] == containerName:
+ container = cont['container']
+ break
+
+ vencoders = options['video-codecs'][vcodec]
+ aencoders = options['audio-codecs'][acodec]
+
+ for encoder in vencoders:
+ if encoder in encoders:
+ vencoder = encoder
+ break
+
+ for encoder in aencoders:
+ if encoder in encoders:
+ aencoder = encoder
+ break
+
+ ffmpegCommand = [
+ Core.FFMPEG_BIN,
+ '-thread_queue_size', '512',
+ '-y', # overwrite the output file if it already exists.
+
+ # INPUT VIDEO
+ '-f', 'rawvideo',
+ '-vcodec', 'rawvideo',
+ '-s', '%sx%s' % (
+ Core.settings.value('outputWidth'),
+ Core.settings.value('outputHeight'),
+ ),
+ '-pix_fmt', 'rgba',
+ '-r', Core.settings.value('outputFrameRate'),
+ '-t', duration,
+ '-i', '-', # the video input comes from a pipe
+ '-an', # the video input has no sound
+
+ # INPUT SOUND
+ '-t', duration,
+ '-i', inputFile
+ ]
+
+ # Add extra audio inputs and any needed avfilters
+ # NOTE: Global filters are currently hard-coded here for debugging use
+ globalFilters = 0 # increase to add global filters
+ extraAudio = [
+ comp.audio for comp in components
+ if 'audio' in comp.properties
+ ]
+ if extraAudio or globalFilters > 0:
+ # Add -i options for extra input files
+ extraFilters = {}
+ for streamNo, params in enumerate(reversed(extraAudio)):
+ extraInputFile, params = params
+ ffmpegCommand.extend([
+ '-t', safeDuration,
+ # Tell ffmpeg about shorter clips (seemingly not needed)
+ # streamDuration = getAudioDuration(extraInputFile)
+ # if streamDuration > float(safeDuration)
+ # else "{0:.3f}".format(streamDuration),
+ '-i', extraInputFile
+ ])
+ # Construct dataset of extra filters we'll need to add later
+ for ffmpegFilter in params:
+ if streamNo + 2 not in extraFilters:
+ extraFilters[streamNo + 2] = []
+ extraFilters[streamNo + 2].append((
+ ffmpegFilter, params[ffmpegFilter]
+ ))
+
+ # Start creating avfilters! Popen-style, so don't use semicolons;
+ extraFilterCommand = []
+
+ if globalFilters <= 0:
+ # Dictionary of last-used tmp labels for a given stream number
+ tmpInputs = {streamNo: -1 for streamNo in extraFilters}
+ else:
+ # Insert blank entries for global filters into extraFilters
+ # so the per-stream filters know what input to source later
+ for streamNo in range(len(extraAudio), 0, -1):
+ if streamNo + 1 not in extraFilters:
+ extraFilters[streamNo + 1] = []
+ # Also filter the primary audio track
+ extraFilters[1] = []
+ tmpInputs = {
+ streamNo: globalFilters - 1
+ for streamNo in extraFilters
+ }
+
+ # Add the global filters!
+ # NOTE: list length must = globalFilters, currently hardcoded
+ if tmpInputs:
+ extraFilterCommand.extend([
+ '[%s:a] ashowinfo [%stmp0]' % (
+ str(streamNo),
+ str(streamNo)
+ )
+ for streamNo in tmpInputs
+ ])
+
+ # Now add the per-stream filters!
+ for streamNo, paramList in extraFilters.items():
+ for param in paramList:
+ source = '[%s:a]' % str(streamNo) \
+ if tmpInputs[streamNo] == -1 else \
+ '[%stmp%s]' % (
+ str(streamNo), str(tmpInputs[streamNo])
+ )
+ tmpInputs[streamNo] = tmpInputs[streamNo] + 1
+ extraFilterCommand.append(
+ '%s %s%s [%stmp%s]' % (
+ source, param[0], param[1], str(streamNo),
+ str(tmpInputs[streamNo])
+ )
+ )
+
+ # Join all the filters together and combine into 1 stream
+ extraFilterCommand = "; ".join(extraFilterCommand) + '; ' \
+ if tmpInputs else ''
+ ffmpegCommand.extend([
+ '-filter_complex',
+ extraFilterCommand +
+ '%s amix=inputs=%s:duration=first [a]'
+ % (
+ "".join([
+ '[%stmp%s]' % (str(i), tmpInputs[i])
+ if i in extraFilters else '[%s:a]' % str(i)
+ for i in range(1, len(extraAudio) + 2)
+ ]),
+ str(len(extraAudio) + 1)
+ ),
+ ])
+
+ # Only map audio from the filters, and video from the pipe
+ ffmpegCommand.extend([
+ '-map', '0:v',
+ '-map', '[a]',
+ ])
+
+ ffmpegCommand.extend([
+ # OUTPUT
+ '-vcodec', vencoder,
+ '-acodec', aencoder,
+ '-b:v', vbitrate,
+ '-b:a', abitrate,
+ '-pix_fmt', Core.settings.value('outputVideoFormat'),
+ '-preset', Core.settings.value('outputPreset'),
+ '-f', container
+ ])
+
+ if acodec == 'aac':
+ ffmpegCommand.append('-strict')
+ ffmpegCommand.append('-2')
+
+ ffmpegCommand.append(outputFile)
+ return ffmpegCommand
+
+
+def getAudioDuration(filename):
+ command = [Core.FFMPEG_BIN, '-i', filename]
+
+ try:
+ fileInfo = checkOutput(command, stderr=sp.STDOUT)
+ except sp.CalledProcessError as ex:
+ fileInfo = ex.output
+
+ info = fileInfo.decode("utf-8").split('\n')
+ for line in info:
+ if 'Duration' in line:
+ d = line.split(',')[0]
+ d = d.split(' ')[3]
+ d = d.split(':')
+ duration = float(d[0])*3600 + float(d[1])*60 + float(d[2])
+ return duration
+
+
+def readAudioFile(filename, parent):
+ duration = getAudioDuration(filename)
+
+ command = [
+ Core.FFMPEG_BIN,
+ '-i', filename,
+ '-f', 's16le',
+ '-acodec', 'pcm_s16le',
+ '-ar', '44100', # ouput will have 44100 Hz
+ '-ac', '1', # mono (set to '2' for stereo)
+ '-']
+ in_pipe = openPipe(
+ command, stdout=sp.PIPE, stderr=sp.DEVNULL, bufsize=10**8
+ )
+
+ completeAudioArray = numpy.empty(0, dtype="int16")
+
+ progress = 0
+ lastPercent = None
+ while True:
+ if Core.canceled:
+ return
+ # read 2 seconds of audio
+ progress += 4
+ raw_audio = in_pipe.stdout.read(88200*4)
+ if len(raw_audio) == 0:
+ break
+ audio_array = numpy.fromstring(raw_audio, dtype="int16")
+ completeAudioArray = numpy.append(completeAudioArray, audio_array)
+
+ percent = int(100*(progress/duration))
+ if percent >= 100:
+ percent = 100
+
+ if lastPercent != percent:
+ string = 'Loading audio file: '+str(percent)+'%'
+ parent.progressBarSetText.emit(string)
+ parent.progressBarUpdate.emit(percent)
+
+ lastPercent = percent
+
+ in_pipe.kill()
+ in_pipe.wait()
+
+ # add 0s the end
+ completeAudioArrayCopy = numpy.zeros(
+ len(completeAudioArray) + 44100, dtype="int16")
+ completeAudioArrayCopy[:len(completeAudioArray)] = completeAudioArray
+ completeAudioArray = completeAudioArrayCopy
+
+ return (completeAudioArray, duration)
diff --git a/src/toolkit/frame.py b/src/toolkit/frame.py
index cddb611..83fd59e 100644
--- a/src/toolkit/frame.py
+++ b/src/toolkit/frame.py
@@ -7,9 +7,7 @@ from PIL.ImageQt import ImageQt
import sys
import os
-
-class Frame:
- '''Controller class for all frames.'''
+from toolkit.common import Core
class FramePainter(QtGui.QPainter):
@@ -59,7 +57,7 @@ def Checkerboard(width, height):
'''
image = FloodFrame(1920, 1080, (0, 0, 0, 0))
image.paste(Image.open(
- os.path.join(Frame.core.wd, "background.png")),
+ os.path.join(Core.wd, "background.png")),
(0, 0)
)
image = image.resize((width, height))
diff --git a/src/video_thread.py b/src/video_thread.py
index 1f2eaf5..8517b92 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -5,9 +5,9 @@
are emitted to update MainWindow's progress bar, detail text, and preview.
Export can be cancelled with cancel()
'''
-from PyQt5 import QtCore, QtGui, uic
+from PyQt5 import QtCore, QtGui
from PyQt5.QtCore import pyqtSignal, pyqtSlot
-from PIL import Image, ImageDraw, ImageFont
+from PIL import Image
from PIL.ImageQt import ImageQt
import numpy
import subprocess as sp
@@ -19,6 +19,7 @@ import time
import signal
from toolkit import openPipe
+from toolkit.ffmpeg import readAudioFile, createFfmpegCommand
from toolkit.frame import Checkerboard
@@ -33,7 +34,7 @@ class Worker(QtCore.QObject):
def __init__(self, parent, inputFile, outputFile, components):
QtCore.QObject.__init__(self)
self.core = parent.core
- self.settings = parent.core.settings
+ self.settings = parent.settings
self.modules = parent.core.modules
parent.createVideo.connect(self.createVideo)
@@ -133,12 +134,17 @@ class Worker(QtCore.QObject):
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
self.progressBarSetText.emit("Loading audio file...")
- self.completeAudioArray, duration = self.core.readAudioFile(
+ audioFileTraits = readAudioFile(
self.inputFile, self
)
+ if audioFileTraits is None:
+ self.cancelExport()
+ return
+ self.completeAudioArray, duration = audioFileTraits
self.progressBarUpdate.emit(0)
self.progressBarSetText.emit("Starting components...")
+ canceledByComponent = False
print('Loaded Components:', ", ".join([
"%s) %s" % (num, str(component))
for num, component in enumerate(reversed(self.components))
@@ -153,14 +159,15 @@ class Worker(QtCore.QObject):
progressBarSetText=self.progressBarSetText
)
- if 'error' in comp.properties():
+ if 'error' in comp.properties:
self.cancel()
self.canceled = True
+ canceledByComponent = True
errMsg = "Component #%s encountered an error!" % compNo \
- if comp.error() is None else 'Component #%s (%s): %s' % (
+ if comp.error is None else 'Component #%s (%s): %s' % (
str(compNo),
str(comp),
- comp.error()
+ comp.error
)
self.parent.showMessage(
msg=errMsg,
@@ -168,17 +175,16 @@ class Worker(QtCore.QObject):
parent=None # MainWindow is in a different thread
)
break
- if 'static' in comp.properties():
+ if 'static' in comp.properties:
self.staticComponents[compNo] = \
comp.frameRender(compNo, 0).copy()
if self.canceled:
- print('Export cancelled by component #%s (%s): %s' % (
- compNo, str(comp), comp.error()
- ))
- self.progressBarSetText.emit('Export Canceled')
- self.encoding.emit(False)
- self.videoCreated.emit()
+ if canceledByComponent:
+ print('Export cancelled by component #%s (%s): %s' % (
+ compNo, str(comp), comp.error
+ ))
+ self.cancelExport()
return
# Merge consecutive static component frames together
@@ -192,8 +198,8 @@ class Worker(QtCore.QObject):
)
self.staticComponents[compNo] = None
- ffmpegCommand = self.core.createFfmpegCommand(
- self.inputFile, self.outputFile, duration
+ ffmpegCommand = createFfmpegCommand(
+ self.inputFile, self.outputFile, self.components, duration
)
print('###### FFMPEG COMMAND ######\n%s' % " ".join(ffmpegCommand))
print('############################')
@@ -280,7 +286,6 @@ class Worker(QtCore.QObject):
pass
self.progressBarUpdate.emit(0)
self.progressBarSetText.emit('Export Canceled')
-
else:
if self.error:
print("Export Failed")
@@ -297,6 +302,12 @@ class Worker(QtCore.QObject):
self.encoding.emit(False)
self.videoCreated.emit()
+ def cancelExport(self):
+ self.progressBarUpdate.emit(0)
+ self.progressBarSetText.emit('Export Canceled')
+ self.encoding.emit(False)
+ self.videoCreated.emit()
+
def updateProgress(self, pStr, pVal):
self.progressBarValue.emit(pVal)
self.progressBarSetText.emit(pStr)
--
cgit v1.2.3
From 450b944b87487aa60a935bbeee3908e2a62cd45b Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 20 Jul 2017 22:37:15 -0400
Subject: add component in context menu, del/ins hotkeys
+ preset manager uses mainwindow component list
---
freeze.py | 4 +-
setup.py | 4 +-
src/__init__.py | 1 +
src/components/video.py | 4 +-
src/core.py | 10 ++--
src/mainwindow.py | 135 +++++++++++++++++++++++++++++-------------------
src/presetmanager.py | 23 +++++++--
src/toolkit/ffmpeg.py | 9 +++-
8 files changed, 122 insertions(+), 68 deletions(-)
diff --git a/freeze.py b/freeze.py
index 3281cad..520b445 100644
--- a/freeze.py
+++ b/freeze.py
@@ -2,7 +2,7 @@ from cx_Freeze import setup, Executable
import sys
import os
-from setup import VERSION
+from setup import __version__
deps = [os.path.join('src', p) for p in os.listdir('src') if p]
@@ -52,7 +52,7 @@ executables = [
setup(
name='audio-visualizer-python',
- version=VERSION,
+ version=__version__,
description='GUI tool to render visualization videos of audio files',
options=dict(build_exe=buildOptions),
executables=executables
diff --git a/setup.py b/setup.py
index 5abb976..a2d8495 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@ from setuptools import setup
import os
-VERSION = '2.0.0.rc1'
+__version__ = '2.0.0.rc1'
def package_files(directory):
@@ -15,7 +15,7 @@ def package_files(directory):
setup(
name='audio_visualizer_python',
- version=VERSION,
+ version=__version__,
url='https://github.com/djfun/audio-visualizer-python/tree/feature-newgui',
license='MIT',
description='Create audio visualization videos from a GUI or commandline',
diff --git a/src/__init__.py b/src/__init__.py
index e69de29..8b13789 100644
--- a/src/__init__.py
+++ b/src/__init__.py
@@ -0,0 +1 @@
+
diff --git a/src/components/video.py b/src/components/video.py
index b35c2e5..8758b12 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -16,7 +16,7 @@ class Video:
'''Video Component Frame-Fetcher'''
def __init__(self, **kwargs):
mandatoryArgs = [
- 'ffmpeg', # path to ffmpeg, usually core.FFMPEG_BIN
+ 'ffmpeg', # path to ffmpeg, usually Core.FFMPEG_BIN
'videoPath',
'width',
'height',
@@ -28,7 +28,7 @@ class Video:
]
for arg in mandatoryArgs:
try:
- exec('self.%s = kwargs[arg]' % arg)
+ setattr(self, arg, kwargs[arg])
except KeyError:
raise BadComponentInit(arg, self.__doc__)
diff --git a/src/core.py b/src/core.py
index dd2ef18..f6cf5eb 100644
--- a/src/core.py
+++ b/src/core.py
@@ -15,16 +15,14 @@ import video_thread
class Core:
'''
MainWindow and Command module both use an instance of this class
- to store the main program state. This object tracks the components
- as an instance, has methods for managing the components and for
- opening/creating project files and presets.
+ to store the core program state. This object tracks the components,
+ talks to the components and handles opening/creating project files
+ and presets. The class also stores constants as class variables.
'''
@classmethod
def storeSettings(cls):
- '''
- Stores settings/paths to directories as class variables
- '''
+ '''Store settings/paths to directories as class variables.'''
if getattr(sys, 'frozen', False):
# frozen
wd = os.path.dirname(sys.executable)
diff --git a/src/mainwindow.py b/src/mainwindow.py
index 9944d1a..2d598ae 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -178,7 +178,6 @@ class MainWindow(QtWidgets.QMainWindow):
# Make component buttons
self.compMenu = QMenu()
- self.compActions = []
for i, comp in enumerate(self.core.modules):
action = self.compMenu.addAction(comp.Component.name)
action.triggered.connect(
@@ -191,6 +190,9 @@ class MainWindow(QtWidgets.QMainWindow):
componentList.itemSelectionChanged.connect(
self.changeComponentWidget
)
+ componentList.itemSelectionChanged.connect(
+ self.presetManager.clearPresetListSelection
+ )
self.window.pushButton_removeComponent.clicked.connect(
lambda: self.removeComponent()
)
@@ -313,22 +315,23 @@ class MainWindow(QtWidgets.QMainWindow):
)
self.settings.setValue("ffmpegMsgShown", True)
- # Setup Hotkeys
+ # Hotkeys for projects
QtWidgets.QShortcut("Ctrl+S", self.window, self.saveCurrentProject)
QtWidgets.QShortcut("Ctrl+A", self.window, self.openSaveProjectDialog)
QtWidgets.QShortcut("Ctrl+O", self.window, self.openOpenProjectDialog)
QtWidgets.QShortcut("Ctrl+N", self.window, self.createNewProject)
- QtWidgets.QShortcut(
- "Ctrl+Alt+Shift+R", self.window, self.drawPreview
- )
- QtWidgets.QShortcut(
- "Ctrl+Alt+Shift+F", self.window, self.showFfmpegCommand
- )
- QtWidgets.QShortcut(
- "Ctrl+T", self.window,
- activated=lambda: self.window.pushButton_addComponent.click()
- )
+ # Hotkeys for component list
+ for inskey in ("Ctrl+T", QtCore.Qt.Key_Insert):
+ QtWidgets.QShortcut(
+ inskey, self.window,
+ activated=lambda: self.window.pushButton_addComponent.click()
+ )
+ for delkey in ("Ctrl+R", QtCore.Qt.Key_Delete):
+ QtWidgets.QShortcut(
+ delkey, self.window.listWidget_componentList,
+ self.removeComponent
+ )
QtWidgets.QShortcut(
"Ctrl+Space", self.window,
activated=lambda: self.window.listWidget_componentList.setFocus()
@@ -342,22 +345,29 @@ class MainWindow(QtWidgets.QMainWindow):
)
QtWidgets.QShortcut(
- "Ctrl+Up", self.window,
+ "Ctrl+Up", self.window.listWidget_componentList,
activated=lambda: self.moveComponent(-1)
)
QtWidgets.QShortcut(
- "Ctrl+Down", self.window,
+ "Ctrl+Down", self.window.listWidget_componentList,
activated=lambda: self.moveComponent(1)
)
QtWidgets.QShortcut(
- "Ctrl+Home", self.window,
+ "Ctrl+Home", self.window.listWidget_componentList,
activated=lambda: self.moveComponent('top')
)
QtWidgets.QShortcut(
- "Ctrl+End", self.window,
+ "Ctrl+End", self.window.listWidget_componentList,
activated=lambda: self.moveComponent('bottom')
)
- QtWidgets.QShortcut("Ctrl+r", self.window, self.removeComponent)
+
+ # Debug Hotkeys
+ QtWidgets.QShortcut(
+ "Ctrl+Alt+Shift+R", self.window, self.drawPreview
+ )
+ QtWidgets.QShortcut(
+ "Ctrl+Alt+Shift+F", self.window, self.showFfmpegCommand
+ )
@QtCore.pyqtSlot()
def cleanUp(self):
@@ -677,9 +687,7 @@ class MainWindow(QtWidgets.QMainWindow):
stackedWidget.setCurrentIndex(newRow)
self.drawPreview()
- @disableWhenEncoding
- def dragComponent(self, event):
- '''Used as Qt drop event for the component listwidget'''
+ def getComponentListRects(self):
componentList = self.window.listWidget_componentList
modelIndexes = [
@@ -690,6 +698,13 @@ class MainWindow(QtWidgets.QMainWindow):
componentList.visualRect(modelIndex)
for modelIndex in modelIndexes
]
+ return rects
+
+ @disableWhenEncoding
+ def dragComponent(self, event):
+ '''Used as Qt drop event for the component listwidget'''
+ componentList = self.window.listWidget_componentList
+ rects = self.getComponentListRects()
rowPos = [rect.contains(event.pos()) for rect in rects]
if not any(rowPos):
@@ -826,47 +841,63 @@ class MainWindow(QtWidgets.QMainWindow):
@disableWhenEncoding
def componentContextMenu(self, QPos):
- '''Appears when right-clicking a component in the list'''
+ '''Appears when right-clicking the component list'''
componentList = self.window.listWidget_componentList
- if not componentList.selectedItems():
- return
-
- # don't show menu if clicking empty space
- parentPosition = componentList.mapToGlobal(QtCore.QPoint(0, 0))
index = componentList.currentRow()
- modelIndex = componentList.model().index(index)
- if not componentList.visualRect(modelIndex).contains(QPos):
- return
- self.presetManager.findPresets()
self.menu = QMenu()
- menuItem = self.menu.addAction("Save Preset")
- menuItem.triggered.connect(
- self.presetManager.openSavePresetDialog
- )
+ parentPosition = componentList.mapToGlobal(QtCore.QPoint(0, 0))
- # submenu for opening presets
- try:
- presets = self.presetManager.presets[
- str(self.core.selectedComponents[index])
- ]
- self.submenu = QMenu("Open Preset")
- self.menu.addMenu(self.submenu)
-
- for version, presetName in presets:
- menuItem = self.submenu.addAction(presetName)
+ rects = self.getComponentListRects()
+ rowPos = [rect.contains(QPos) for rect in rects]
+ if not any(rowPos):
+ # Insert components at the top if clicking nothing
+ rowPos = 0
+ else:
+ rowPos = rowPos.index(True)
+
+ if index == rowPos:
+ # Show preset menu if clicking a component
+ self.presetManager.findPresets()
+ menuItem = self.menu.addAction("Save Preset")
+ menuItem.triggered.connect(
+ self.presetManager.openSavePresetDialog
+ )
+
+ # submenu for opening presets
+ try:
+ presets = self.presetManager.presets[
+ str(self.core.selectedComponents[index])
+ ]
+ self.presetSubmenu = QMenu("Open Preset")
+ self.menu.addMenu(self.presetSubmenu)
+
+ for version, presetName in presets:
+ menuItem = self.presetSubmenu.addAction(presetName)
+ menuItem.triggered.connect(
+ lambda _, presetName=presetName:
+ self.presetManager.openPreset(presetName)
+ )
+ except KeyError:
+ pass
+
+ if self.core.selectedComponents[index].currentPreset:
+ menuItem = self.menu.addAction("Clear Preset")
menuItem.triggered.connect(
- lambda _, presetName=presetName:
- self.presetManager.openPreset(presetName)
+ self.presetManager.clearPreset
)
- except KeyError:
- pass
+ self.menu.addSeparator()
- if self.core.selectedComponents[index].currentPreset:
- menuItem = self.menu.addAction("Clear Preset")
+ # "Add Component" submenu
+ self.submenu = QMenu("Add")
+ self.menu.addMenu(self.submenu)
+ for i, comp in enumerate(self.core.modules):
+ menuItem = self.submenu.addAction(comp.Component.name)
menuItem.triggered.connect(
- self.presetManager.clearPreset
- )
+ lambda _, item=i: self.core.insertComponent(
+ rowPos, item, self
+ )
+ )
self.menu.move(parentPosition + QPos)
self.menu.show()
diff --git a/src/presetmanager.py b/src/presetmanager.py
index 825fdee..64e2203 100644
--- a/src/presetmanager.py
+++ b/src/presetmanager.py
@@ -245,11 +245,25 @@ class PresetManager(QtWidgets.QDialog):
def openRenamePresetDialog(self):
# TODO: maintain consistency by changing this to call createNewPreset()
presetList = self.window.listWidget_presets
- if presetList.currentRow() == -1:
- return
+ index = presetList.currentRow()
+ if index == -1:
+ # check if component selected in MainWindow has preset loaded
+ componentList = self.parent.window.listWidget_componentList
+ compIndex = componentList.currentRow()
+ if compIndex == -1:
+ return
+ preset = self.core.selectedComponents[compIndex].currentPreset
+ if not preset:
+ return
+ else:
+ for i, tup in enumerate(self.presetRows):
+ if preset == tup[2]:
+ index = i
+ break
+ else:
+ return
while True:
- index = presetList.currentRow()
newName, OK = QtWidgets.QInputDialog.getText(
self.window,
'Preset Manager',
@@ -321,3 +335,6 @@ class PresetManager(QtWidgets.QDialog):
parent=self.window
)
self.settings.setValue("presetDir", os.path.dirname(filename))
+
+ def clearPresetListSelection(self):
+ self.window.listWidget_presets.setCurrentRow(-1)
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index 89d4e9d..cc59a6c 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -113,7 +113,7 @@ def createFfmpegCommand(inputFile, outputFile, components, duration=-1):
'-t', safeDuration,
# Tell ffmpeg about shorter clips (seemingly not needed)
# streamDuration = getAudioDuration(extraInputFile)
- # if streamDuration > float(safeDuration)
+ # if streamDuration and streamDuration > float(safeDuration)
# else "{0:.3f}".format(streamDuration),
'-i', extraInputFile
])
@@ -228,11 +228,18 @@ def getAudioDuration(filename):
d = d.split(' ')[3]
d = d.split(':')
duration = float(d[0])*3600 + float(d[1])*60 + float(d[2])
+ break
+ else:
+ # String not found in output
+ return False
return duration
def readAudioFile(filename, parent):
duration = getAudioDuration(filename)
+ if not duration:
+ print('Audio file doesn\'t exist or unreadable.')
+ return
command = [
Core.FFMPEG_BIN,
--
cgit v1.2.3
From bf0890e7c87c730b8970c1a20c5b6a9a1a55d203 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 23 Jul 2017 01:53:54 -0400
Subject: components auto-connect & track widgets, less autosave spam
importing toolkit from live interpreter now works
---
setup.py | 2 +-
src/__init__.py | 12 +++
src/command.py | 2 -
src/component.py | 196 +++++++++++++++++++++++++++++++++------------
src/components/color.py | 137 +++++++++++--------------------
src/components/image.py | 77 +++++-------------
src/components/original.py | 59 ++++++--------
src/components/sound.py | 50 +++---------
src/components/text.py | 81 ++++++++-----------
src/components/video.py | 98 +++++++----------------
src/core.py | 196 ++++++++++++++++++++++++++++-----------------
src/main.py | 23 ++----
src/mainwindow.py | 125 +++++++++++++++++++----------
src/mainwindow.ui | 3 +
src/presetmanager.py | 15 ++--
src/preview_thread.py | 17 ++--
src/toolkit/common.py | 56 +++----------
src/toolkit/core.py | 18 -----
src/toolkit/ffmpeg.py | 46 ++++++++---
src/toolkit/frame.py | 4 +-
src/video_thread.py | 7 +-
21 files changed, 604 insertions(+), 620 deletions(-)
delete mode 100644 src/toolkit/core.py
diff --git a/setup.py b/setup.py
index a2d8495..d4f226b 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@ from setuptools import setup
import os
-__version__ = '2.0.0.rc1'
+__version__ = '2.0.0.rc2'
def package_files(directory):
diff --git a/src/__init__.py b/src/__init__.py
index 8b13789..2f4cffa 100644
--- a/src/__init__.py
+++ b/src/__init__.py
@@ -1 +1,13 @@
+import sys
+import os
+
+if getattr(sys, 'frozen', False):
+ # frozen
+ wd = os.path.dirname(sys.executable)
+else:
+ # unfrozen
+ wd = os.path.dirname(os.path.realpath(__file__))
+
+# make relative imports work when using /src as a package
+sys.path.insert(0, wd)
diff --git a/src/command.py b/src/command.py
index 046a1bf..ca186e5 100644
--- a/src/command.py
+++ b/src/command.py
@@ -10,7 +10,6 @@ import sys
import time
from core import Core
-from toolkit import loadDefaultSettings
class Command(QtCore.QObject):
@@ -55,7 +54,6 @@ class Command(QtCore.QObject):
self.args = self.parser.parse_args()
self.settings = Core.settings
- loadDefaultSettings(self)
if self.args.projpath:
projPath = self.args.projpath
diff --git a/src/component.py b/src/component.py
index 92cc65c..bec2df5 100644
--- a/src/component.py
+++ b/src/component.py
@@ -5,8 +5,28 @@
from PyQt5 import uic, QtCore, QtWidgets
import os
-from core import Core
-from toolkit.common import getPresetDir
+from presetmanager import getPresetDir
+
+
+def commandWrapper(func):
+ '''Intercepts each component's command() method to check for global args'''
+ def decorator(self, arg):
+ if arg.startswith('preset='):
+ _, preset = arg.split('=', 1)
+ path = os.path.join(getPresetDir(self), preset)
+ if not os.path.exists(path):
+ print('Couldn\'t locate preset "%s"' % preset)
+ quit(1)
+ else:
+ print('Opening "%s" preset on layer %s' % (
+ preset, self.compPos)
+ )
+ self.core.openPreset(path, self.compPos, preset)
+ # Don't call the component's command() method
+ return
+ else:
+ return func(self, arg)
+ return decorator
class ComponentMetaclass(type(QtCore.QObject)):
@@ -16,10 +36,14 @@ class ComponentMetaclass(type(QtCore.QObject)):
E.g., takes only major version from version string & decorates methods
'''
def __new__(cls, name, parents, attrs):
- # print('Creating %s component' % attrs['name'])
+ if 'ui' not in attrs:
+ # use module name as ui filename by default
+ attrs['ui'] = '%s.ui' % os.path.splitext(
+ attrs['__module__'].split('.')[-1]
+ )[0]
# Turn certain class methods into properties and classmethods
- for key in ('error', 'properties', 'audio', 'commandHelp'):
+ for key in ('error', 'properties', 'audio'):
if key not in attrs:
continue
attrs[key] = property(attrs[key])
@@ -29,6 +53,10 @@ class ComponentMetaclass(type(QtCore.QObject)):
continue
attrs[key] = classmethod(key)
+ # Do not apply these mutations to the base class
+ if parents[0] != QtCore.QObject:
+ attrs['command'] = commandWrapper(attrs['command'])
+
# Turn version string into a number
try:
if 'version' not in attrs:
@@ -54,19 +82,24 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
'''
name = 'Component'
+ # ui = 'nameOfNonDefaultUiFile'
version = '1.0.0'
- # The 1st number (before dot, aka the major version) is used to determine
+ # The major version (before the first dot) is used to determine
# preset compatibility; the rest is ignored so it can be non-numeric.
modified = QtCore.pyqtSignal(int, dict)
# ^ Signal used to tell core program that the component state changed,
# you shouldn't need to use this directly, it is used by self.update()
- def __init__(self, moduleIndex, compPos):
+ def __init__(self, moduleIndex, compPos, core):
super().__init__()
- self.currentPreset = None
self.moduleIndex = moduleIndex
self.compPos = compPos
+ self.core = core
+ self.currentPreset = None
+
+ self._trackedWidgets = {}
+ self._presetNames = {}
# Stop lengthy processes in response to this variable
self.canceled = False
@@ -114,28 +147,103 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
'''
return []
- def commandHelp(self):
- '''Help text as string for this component's commandline arguments'''
-
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
# Methods
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
- def update(self):
- '''Read widget values from self.page, then call super().update()'''
- self.parent.drawPreview()
- saveValueStore = self.savePreset()
- saveValueStore['preset'] = self.currentPreset
- self.modified.emit(self.compPos, saveValueStore)
+ def widget(self, parent):
+ '''
+ Call super().widget(*args) to create the component widget
+ which also auto-connects any common widgets (e.g., checkBoxes)
+ to self.update(). Then in a subclass connect special actions
+ (e.g., pushButtons to select a file/colour) and initialize
+ '''
+ self.parent = parent
+ self.settings = parent.settings
+ self.page = self.loadUi(self.__class__.ui)
+
+ # Connect widget signals
+ widgets = {
+ 'lineEdit': self.page.findChildren(QtWidgets.QLineEdit),
+ 'checkBox': self.page.findChildren(QtWidgets.QCheckBox),
+ 'spinBox': self.page.findChildren(QtWidgets.QSpinBox),
+ 'comboBox': self.page.findChildren(QtWidgets.QComboBox),
+ }
+ widgets['spinBox'].extend(
+ self.page.findChildren(QtWidgets.QDoubleSpinBox)
+ )
+ for widget in widgets['lineEdit']:
+ widget.textChanged.connect(self.update)
+ for widget in widgets['checkBox']:
+ widget.stateChanged.connect(self.update)
+ for widget in widgets['spinBox']:
+ widget.valueChanged.connect(self.update)
+ for widget in widgets['comboBox']:
+ widget.currentIndexChanged.connect(self.update)
+
+ def trackWidgets(self, trackDict, presetNames=None):
+ '''
+ Name widgets to track in update(), savePreset(), and loadPreset()
+ Accepts a dict with attribute names as keys and widgets as values.
+ Optional: a dict of attribute names to map to preset variable names
+ '''
+ self._trackedWidgets = trackDict
+ if type(presetNames) is dict:
+ self._presetNames = presetNames
- def loadPreset(self, presetDict, presetName):
+ def update(self):
'''
- Subclasses take (presetDict, presetName=None) as args.
- Must use super().loadPreset(presetDict, presetName) first,
+ Reads all tracked widget values into instance attributes
+ and tells the MainWindow that the component was modified.
+ Call at the END of your method if you need to subclass this.
+ '''
+ for attr, widget in self._trackedWidgets.items():
+ if type(widget) == QtWidgets.QLineEdit:
+ setattr(self, attr, widget.text())
+ elif type(widget) == QtWidgets.QSpinBox \
+ or type(widget) == QtWidgets.QDoubleSpinBox:
+ setattr(self, attr, widget.value())
+ elif type(widget) == QtWidgets.QCheckBox:
+ setattr(self, attr, widget.isChecked())
+ elif type(widget) == QtWidgets.QComboBox:
+ setattr(self, attr, widget.currentIndex())
+ if not self.core.openingProject:
+ self.parent.drawPreview()
+ saveValueStore = self.savePreset()
+ saveValueStore['preset'] = self.currentPreset
+ self.modified.emit(self.compPos, saveValueStore)
+
+ def loadPreset(self, presetDict, presetName=None):
+ '''
+ Subclasses should take (presetDict, *args) as args.
+ Must use super().loadPreset(presetDict, *args) first,
then update self.page widgets using the preset dict.
'''
self.currentPreset = presetName \
if presetName is not None else presetDict['preset']
+ for attr, widget in self._trackedWidgets.items():
+ val = presetDict[
+ attr if attr not in self._presetNames
+ else self._presetNames[attr]
+ ]
+ if type(widget) == QtWidgets.QLineEdit:
+ widget.setText(val)
+ elif type(widget) == QtWidgets.QSpinBox \
+ or type(widget) == QtWidgets.QDoubleSpinBox:
+ widget.setValue(val)
+ elif type(widget) == QtWidgets.QCheckBox:
+ widget.setChecked(val)
+ elif type(widget) == QtWidgets.QComboBox:
+ widget.setCurrentIndex(val)
+
+ def savePreset(self):
+ saveValueStore = {}
+ for attr, widget in self._trackedWidgets.items():
+ saveValueStore[
+ attr if attr not in self._presetNames
+ else self._presetNames[attr]
+ ] = getattr(self, attr)
+ return saveValueStore
def preFrameRender(self, **kwargs):
'''
@@ -151,34 +259,27 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
for key, value in kwargs.items():
setattr(self, key, value)
- def command(self, arg):
+ def commandHelp(self):
+ '''Help text as string for this component's commandline arguments'''
+
+ def command(self, arg=''):
'''
- Configure a component using argument from the commandline.
- Use super().command(arg) at the end of a subclass's method,
- if no arguments are found in that method first
+ Configure a component using an arg from the commandline. This is
+ never called if global args like 'preset=' are found in the arg.
+ So simply check for any non-global args in your component and
+ call super().command() at the end to get a Help message.
'''
- if arg.startswith('preset='):
- _, preset = arg.split('=', 1)
- path = os.path.join(getPresetDir(self), preset)
- if not os.path.exists(path):
- print('Couldn\'t locate preset "%s"' % preset)
- quit(1)
- else:
- print('Opening "%s" preset on layer %s' % (
- preset, self.compPos)
- )
- self.core.openPreset(path, self.compPos, preset)
- else:
- print(
- self.__doc__, 'Usage:\n'
- 'Open a preset for this component:\n'
- ' "preset=Preset Name"')
- print(self.commandHelp)
- quit(0)
+ print(
+ self.__class__.name, 'Usage:\n'
+ 'Open a preset for this component:\n'
+ ' "preset=Preset Name"'
+ )
+ self.commandHelp()
+ quit(0)
def loadUi(self, filename):
'''Load a Qt Designer ui file to use for this component's widget'''
- return uic.loadUi(os.path.join(Core.componentsPath, filename))
+ return uic.loadUi(os.path.join(self.core.componentsPath, filename))
def cancel(self):
'''Stop any lengthy process in response to this variable.'''
@@ -191,16 +292,9 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
### Reference methods for creating a new component
### (Inherit from this class and define these)
- def widget(self, parent):
- self.parent = parent
- self.settings = parent.settings
- self.page = self.loadUi('example.ui')
- # --- connect widget signals here ---
- return self.page
-
def previewRender(self, previewWorker):
width = int(self.settings.value('outputWidth'))
- height = int(previewWorker.core.settings.value('outputHeight'))
+ height = int(self.settings.value('outputHeight'))
from toolkit.frame import BlankFrame
image = BlankFrame(width, height)
return image
@@ -217,7 +311,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
class BadComponentInit(Exception):
'''
- General purpose exception components can raise to indicate
+ General purpose exception that components can raise to indicate
a Python issue with e.g., dynamic creation of instances or something.
Decorative for now, may have future use for logging.
'''
diff --git a/src/components/color.py b/src/components/color.py
index 03371e7..8257ed9 100644
--- a/src/components/color.py
+++ b/src/components/color.py
@@ -13,18 +13,15 @@ class Component(Component):
name = 'Color'
version = '1.0.0'
- def widget(self, parent):
- self.parent = parent
- self.settings = parent.settings
- page = self.loadUi('color.ui')
-
+ def widget(self, *args):
self.color1 = (0, 0, 0)
self.color2 = (133, 133, 133)
self.x = 0
self.y = 0
+ super().widget(*args)
- page.lineEdit_color1.setText('%s,%s,%s' % self.color1)
- page.lineEdit_color2.setText('%s,%s,%s' % self.color2)
+ 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()
@@ -32,68 +29,55 @@ class Component(Component):
btnStyle2 = "QPushButton { background-color : %s; outline: none; }" \
% QColor(*self.color2).name()
- page.pushButton_color1.setStyleSheet(btnStyle1)
- page.pushButton_color2.setStyleSheet(btnStyle2)
- page.pushButton_color1.clicked.connect(lambda: self.pickColor(1))
- page.pushButton_color2.clicked.connect(lambda: self.pickColor(2))
+ 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))
# disable color #2 until non-default 'fill' option gets changed
- page.lineEdit_color2.setDisabled(True)
- page.pushButton_color2.setDisabled(True)
- page.spinBox_x.valueChanged.connect(self.update)
- page.spinBox_y.valueChanged.connect(self.update)
- page.spinBox_width.setValue(
+ self.page.lineEdit_color2.setDisabled(True)
+ self.page.pushButton_color2.setDisabled(True)
+ self.page.spinBox_width.setValue(
int(self.settings.value("outputWidth")))
- page.spinBox_height.setValue(
+ self.page.spinBox_height.setValue(
int(self.settings.value("outputHeight")))
- page.lineEdit_color1.textChanged.connect(self.update)
- page.lineEdit_color2.textChanged.connect(self.update)
- page.spinBox_x.valueChanged.connect(self.update)
- page.spinBox_y.valueChanged.connect(self.update)
- page.spinBox_width.valueChanged.connect(self.update)
- page.spinBox_height.valueChanged.connect(self.update)
- page.checkBox_trans.stateChanged.connect(self.update)
-
self.fillLabels = [
'Solid',
'Linear Gradient',
'Radial Gradient',
]
for label in self.fillLabels:
- page.comboBox_fill.addItem(label)
- page.comboBox_fill.setCurrentIndex(0)
- page.comboBox_fill.currentIndexChanged.connect(self.update)
- page.comboBox_spread.currentIndexChanged.connect(self.update)
- page.spinBox_radialGradient_end.valueChanged.connect(self.update)
- page.spinBox_radialGradient_start.valueChanged.connect(self.update)
- page.spinBox_radialGradient_spread.valueChanged.connect(self.update)
- page.spinBox_linearGradient_end.valueChanged.connect(self.update)
- page.spinBox_linearGradient_start.valueChanged.connect(self.update)
- page.checkBox_stretch.stateChanged.connect(self.update)
-
- self.page = page
- return page
+ 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',
+ }
+ )
def update(self):
self.color1 = rgbFromString(self.page.lineEdit_color1.text())
self.color2 = rgbFromString(self.page.lineEdit_color2.text())
- self.x = self.page.spinBox_x.value()
- self.y = self.page.spinBox_y.value()
- self.sizeWidth = self.page.spinBox_width.value()
- self.sizeHeight = self.page.spinBox_height.value()
- self.trans = self.page.checkBox_trans.isChecked()
- self.spread = self.page.comboBox_spread.currentIndex()
-
- self.RG_start = self.page.spinBox_radialGradient_start.value()
- self.RG_end = self.page.spinBox_radialGradient_end.value()
- self.RG_centre = self.page.spinBox_radialGradient_spread.value()
- self.stretch = self.page.checkBox_stretch.isChecked()
- self.LG_start = self.page.spinBox_linearGradient_start.value()
- self.LG_end = self.page.spinBox_linearGradient_end.value()
-
- self.fillType = self.page.comboBox_fill.currentIndex()
- if self.fillType == 0:
+
+ fillType = self.page.comboBox_fill.currentIndex()
+ if fillType == 0:
self.page.lineEdit_color2.setEnabled(False)
self.page.pushButton_color2.setEnabled(False)
self.page.checkBox_trans.setEnabled(False)
@@ -105,10 +89,10 @@ class Component(Component):
self.page.checkBox_trans.setEnabled(True)
self.page.checkBox_stretch.setEnabled(True)
self.page.comboBox_spread.setEnabled(True)
- if self.trans:
+ if self.page.checkBox_trans.isChecked():
self.page.lineEdit_color2.setEnabled(False)
self.page.pushButton_color2.setEnabled(False)
- self.page.fillWidget.setCurrentIndex(self.fillType)
+ self.page.fillWidget.setCurrentIndex(fillType)
super().update()
@@ -181,25 +165,11 @@ class Component(Component):
return image.finalize()
- def loadPreset(self, pr, presetName=None):
- super().loadPreset(pr, presetName)
+ def loadPreset(self, pr, *args):
+ super().loadPreset(pr, *args)
- self.page.comboBox_fill.setCurrentIndex(pr['fillType'])
self.page.lineEdit_color1.setText('%s,%s,%s' % pr['color1'])
self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2'])
- self.page.spinBox_x.setValue(pr['x'])
- self.page.spinBox_y.setValue(pr['y'])
- self.page.spinBox_width.setValue(pr['width'])
- self.page.spinBox_height.setValue(pr['height'])
- self.page.checkBox_trans.setChecked(pr['trans'])
-
- self.page.spinBox_radialGradient_start.setValue(pr['RG_start'])
- self.page.spinBox_radialGradient_end.setValue(pr['RG_end'])
- self.page.spinBox_radialGradient_spread.setValue(pr['RG_centre'])
- self.page.spinBox_linearGradient_start.setValue(pr['LG_start'])
- self.page.spinBox_linearGradient_end.setValue(pr['LG_end'])
- self.page.checkBox_stretch.setChecked(pr['stretch'])
- self.page.comboBox_spread.setCurrentIndex(pr['spread'])
btnStyle1 = "QPushButton { background-color : %s; outline: none; }" \
% QColor(*pr['color1']).name()
@@ -209,23 +179,10 @@ class Component(Component):
self.page.pushButton_color2.setStyleSheet(btnStyle2)
def savePreset(self):
- return {
- 'color1': self.color1,
- 'color2': self.color2,
- 'x': self.x,
- 'y': self.y,
- 'fillType': self.fillType,
- 'width': self.sizeWidth,
- 'height': self.sizeHeight,
- 'trans': self.trans,
- 'stretch': self.stretch,
- 'spread': self.spread,
- 'RG_start': self.RG_start,
- 'RG_end': self.RG_end,
- 'RG_centre': self.RG_centre,
- 'LG_start': self.LG_start,
- 'LG_end': self.LG_end,
- }
+ saveValueStore = super().savePreset()
+ saveValueStore['color1'] = self.color1
+ saveValueStore['color2'] = self.color2
+ return saveValueStore
def pickColor(self, num):
RGBstring, btnStyle = pickColor()
@@ -242,7 +199,7 @@ class Component(Component):
print('Specify a color:\n color=255,255,255')
def command(self, arg):
- if not arg.startswith('preset=') and '=' in arg:
+ if '=' in arg:
key, arg = arg.split('=', 1)
if key == 'color':
self.page.lineEdit_color1.setText(arg)
diff --git a/src/components/image.py b/src/components/image.py
index 591e03e..a705904 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -2,7 +2,6 @@ from PIL import Image, ImageDraw, ImageEnhance
from PyQt5 import QtGui, QtCore, QtWidgets
import os
-from core import Core
from component import Component
from toolkit.frame import BlankFrame
@@ -11,35 +10,26 @@ class Component(Component):
name = 'Image'
version = '1.0.0'
- def widget(self, parent):
- self.parent = parent
- self.settings = parent.settings
- page = self.loadUi('image.ui')
-
- page.lineEdit_image.textChanged.connect(self.update)
- page.pushButton_image.clicked.connect(self.pickImage)
- page.spinBox_scale.valueChanged.connect(self.update)
- page.spinBox_rotate.valueChanged.connect(self.update)
- page.spinBox_color.valueChanged.connect(self.update)
- page.checkBox_stretch.stateChanged.connect(self.update)
- page.checkBox_mirror.stateChanged.connect(self.update)
- page.spinBox_x.valueChanged.connect(self.update)
- page.spinBox_y.valueChanged.connect(self.update)
-
- self.page = page
- return page
-
- def update(self):
- self.imagePath = self.page.lineEdit_image.text()
- self.scale = self.page.spinBox_scale.value()
- self.rotate = self.page.spinBox_rotate.value()
- self.color = self.page.spinBox_color.value()
- self.xPosition = self.page.spinBox_x.value()
- self.yPosition = self.page.spinBox_y.value()
- self.stretched = self.page.checkBox_stretch.isChecked()
- self.mirror = self.page.checkBox_mirror.isChecked()
-
- super().update()
+ 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',
+ },
+ )
def previewRender(self, previewWorker):
width = int(self.settings.value('outputWidth'))
@@ -89,41 +79,18 @@ class Component(Component):
return frame
- def loadPreset(self, pr, presetName=None):
- super().loadPreset(pr, presetName)
- self.page.lineEdit_image.setText(pr['image'])
- self.page.spinBox_scale.setValue(pr['scale'])
- self.page.spinBox_color.setValue(pr['color'])
- self.page.spinBox_rotate.setValue(pr['rotate'])
- self.page.spinBox_x.setValue(pr['x'])
- self.page.spinBox_y.setValue(pr['y'])
- self.page.checkBox_stretch.setChecked(pr['stretched'])
- self.page.checkBox_mirror.setChecked(pr['mirror'])
-
- def savePreset(self):
- return {
- 'image': self.imagePath,
- 'scale': self.scale,
- 'color': self.color,
- 'rotate': self.rotate,
- 'stretched': self.stretched,
- 'mirror': self.mirror,
- 'x': self.xPosition,
- 'y': self.yPosition,
- }
-
def pickImage(self):
imgDir = self.settings.value("componentDir", os.path.expanduser("~"))
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
self.page, "Choose Image", imgDir,
- "Image Files (%s)" % " ".join(Core.imageFormats))
+ "Image Files (%s)" % " ".join(self.core.imageFormats))
if filename:
self.settings.setValue("componentDir", os.path.dirname(filename))
self.page.lineEdit_image.setText(filename)
self.update()
def command(self, arg):
- if not arg.startswith('preset=') and '=' in arg:
+ if '=' in arg:
key, arg = arg.split('=', 1)
if key == 'path' and os.path.exists(arg):
try:
diff --git a/src/components/original.py b/src/components/original.py
index ae40df3..2bda878 100644
--- a/src/components/original.py
+++ b/src/components/original.py
@@ -18,59 +18,46 @@ class Component(Component):
def names():
return ['Original Audio Visualization']
- def widget(self, parent):
- self.parent = parent
- self.settings = parent.settings
+ def widget(self, *args):
self.visColor = (255, 255, 255)
self.scale = 20
self.y = 0
- self.canceled = False
-
- page = self.loadUi('original.ui')
- page.comboBox_visLayout.addItem("Classic")
- page.comboBox_visLayout.addItem("Split")
- page.comboBox_visLayout.addItem("Bottom")
- page.comboBox_visLayout.addItem("Top")
- page.comboBox_visLayout.setCurrentIndex(0)
- page.comboBox_visLayout.currentIndexChanged.connect(self.update)
- page.lineEdit_visColor.setText('%s,%s,%s' % self.visColor)
- page.pushButton_visColor.clicked.connect(lambda: self.pickColor())
+ super().widget(*args)
+
+ self.page.comboBox_visLayout.addItem("Classic")
+ self.page.comboBox_visLayout.addItem("Split")
+ self.page.comboBox_visLayout.addItem("Bottom")
+ self.page.comboBox_visLayout.addItem("Top")
+ self.page.comboBox_visLayout.setCurrentIndex(0)
+
+ self.page.lineEdit_visColor.setText('%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()
- page.pushButton_visColor.setStyleSheet(btnStyle)
- page.lineEdit_visColor.textChanged.connect(self.update)
- page.spinBox_scale.valueChanged.connect(self.update)
- page.spinBox_y.valueChanged.connect(self.update)
+ self.page.pushButton_visColor.setStyleSheet(btnStyle)
- self.page = page
- return page
+ self.trackWidgets({
+ 'layout': self.page.comboBox_visLayout,
+ 'scale': self.page.spinBox_scale,
+ 'y': self.page.spinBox_y,
+ })
def update(self):
- self.layout = self.page.comboBox_visLayout.currentIndex()
self.visColor = rgbFromString(self.page.lineEdit_visColor.text())
- self.scale = self.page.spinBox_scale.value()
- self.y = self.page.spinBox_y.value()
-
super().update()
- def loadPreset(self, pr, presetName=None):
- super().loadPreset(pr, presetName)
+ 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)
- self.page.comboBox_visLayout.setCurrentIndex(pr['layout'])
- self.page.spinBox_scale.setValue(pr['scale'])
- self.page.spinBox_y.setValue(pr['y'])
def savePreset(self):
- return {
- 'layout': self.layout,
- 'visColor': self.visColor,
- 'scale': self.scale,
- 'y': self.y,
- }
+ saveValueStore = super().savePreset()
+ saveValueStore['visColor'] = self.visColor
+ return saveValueStore
def previewRender(self, previewWorker):
spectrum = numpy.fromfunction(
@@ -206,7 +193,7 @@ class Component(Component):
return im
def command(self, arg):
- if not arg.startswith('preset=') and '=' in arg:
+ if '=' in arg:
key, arg = arg.split('=', 1)
try:
if key == 'color':
diff --git a/src/components/sound.py b/src/components/sound.py
index 677a22f..dd3cbab 100644
--- a/src/components/sound.py
+++ b/src/components/sound.py
@@ -10,26 +10,15 @@ class Component(Component):
name = 'Sound'
version = '1.0.0'
- def widget(self, parent):
- self.parent = parent
- self.settings = parent.settings
- page = self.loadUi('sound.ui')
-
- page.lineEdit_sound.textChanged.connect(self.update)
- page.pushButton_sound.clicked.connect(self.pickSound)
- page.checkBox_chorus.stateChanged.connect(self.update)
- page.spinBox_delay.valueChanged.connect(self.update)
- page.spinBox_volume.valueChanged.connect(self.update)
-
- self.page = page
- return page
-
- def update(self):
- self.sound = self.page.lineEdit_sound.text()
- self.delay = self.page.spinBox_delay.value()
- self.volume = self.page.spinBox_volume.value()
- self.chorus = self.page.checkBox_chorus.isChecked()
- super().update()
+ 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,
+ })
def previewRender(self, previewWorker):
width = int(self.settings.value('outputWidth'))
@@ -67,7 +56,7 @@ class Component(Component):
sndDir = self.settings.value("componentDir", os.path.expanduser("~"))
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
self.page, "Choose Sound", sndDir,
- "Audio Files (%s)" % " ".join(Core.audioFormats))
+ "Audio Files (%s)" % " ".join(self.core.audioFormats))
if filename:
self.settings.setValue("componentDir", os.path.dirname(filename))
self.page.lineEdit_sound.setText(filename)
@@ -78,30 +67,15 @@ class Component(Component):
height = int(self.settings.value('outputHeight'))
return BlankFrame(width, height)
- def loadPreset(self, pr, presetName=None):
- super().loadPreset(pr, presetName)
- self.page.lineEdit_sound.setText(pr['sound'])
- self.page.checkBox_chorus.setChecked(pr['chorus'])
- self.page.spinBox_delay.setValue(pr['delay'])
- self.page.spinBox_volume.setValue(pr['volume'])
-
- def savePreset(self):
- return {
- 'sound': self.sound,
- 'chorus': self.chorus,
- 'delay': self.delay,
- 'volume': self.volume,
- }
-
def commandHelp(self):
print('Path to audio file:\n path=/filepath/to/sound.ogg')
def command(self, arg):
- if not arg.startswith('preset=') and '=' in arg:
+ if '=' in arg:
key, arg = arg.split('=', 1)
if key == 'path':
if '*%s' % os.path.splitext(arg)[1] \
- not in Core.audioFormats:
+ not in self.core.audioFormats:
print("Not a supported audio format")
quit(1)
self.page.lineEdit_sound.setText(arg)
diff --git a/src/components/text.py b/src/components/text.py
index d511f22..1d64617 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -16,12 +16,10 @@ class Component(Component):
super().__init__(*args)
self.titleFont = QFont()
- def widget(self, parent):
- self.parent = parent
- self.settings = parent.settings
+ 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
@@ -30,40 +28,35 @@ class Component(Component):
self.xPosition = width / 2 - fm.width(self.title)/2
self.yPosition = height / 2 * 1.036
- page = self.loadUi('text.ui')
- page.comboBox_textAlign.addItem("Left")
- page.comboBox_textAlign.addItem("Middle")
- page.comboBox_textAlign.addItem("Right")
+ self.page.comboBox_textAlign.addItem("Left")
+ self.page.comboBox_textAlign.addItem("Middle")
+ self.page.comboBox_textAlign.addItem("Right")
- page.lineEdit_textColor.setText('%s,%s,%s' % self.textColor)
- page.pushButton_textColor.clicked.connect(self.pickColor)
+ 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()
- page.pushButton_textColor.setStyleSheet(btnStyle)
-
- page.lineEdit_title.setText(self.title)
- page.comboBox_textAlign.setCurrentIndex(int(self.alignment))
- page.spinBox_fontSize.setValue(int(self.fontSize))
- page.spinBox_xTextAlign.setValue(int(self.xPosition))
- page.spinBox_yTextAlign.setValue(int(self.yPosition))
-
- page.fontComboBox_titleFont.currentFontChanged.connect(self.update)
- page.lineEdit_title.textChanged.connect(self.update)
- page.comboBox_textAlign.currentIndexChanged.connect(self.update)
- page.spinBox_xTextAlign.valueChanged.connect(self.update)
- page.spinBox_yTextAlign.valueChanged.connect(self.update)
- page.spinBox_fontSize.valueChanged.connect(self.update)
- page.lineEdit_textColor.textChanged.connect(self.update)
- self.page = page
- return page
+ 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.fontComboBox_titleFont.currentFontChanged.connect(
+ self.update
+ )
+ self.trackWidgets({
+ '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,
+ })
def update(self):
- self.title = self.page.lineEdit_title.text()
- self.alignment = self.page.comboBox_textAlign.currentIndex()
self.titleFont = self.page.fontComboBox_titleFont.currentFont()
- self.fontSize = self.page.spinBox_fontSize.value()
- self.xPosition = self.page.spinBox_xTextAlign.value()
- self.yPosition = self.page.spinBox_yTextAlign.value()
self.textColor = rgbFromString(
self.page.lineEdit_textColor.text())
btnStyle = "QPushButton { background-color : %s; outline: none; }" \
@@ -87,32 +80,22 @@ class Component(Component):
x = self.xPosition - offset
return x, self.yPosition
- def loadPreset(self, pr, presetName=None):
- super().loadPreset(pr, presetName)
+ def loadPreset(self, pr, *args):
+ super().loadPreset(pr, *args)
- self.page.lineEdit_title.setText(pr['title'])
font = QFont()
font.fromString(pr['titleFont'])
self.page.fontComboBox_titleFont.setCurrentFont(font)
- self.page.spinBox_fontSize.setValue(pr['fontSize'])
- self.page.comboBox_textAlign.setCurrentIndex(pr['alignment'])
- self.page.spinBox_xTextAlign.setValue(pr['xPosition'])
- self.page.spinBox_yTextAlign.setValue(pr['yPosition'])
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):
- return {
- 'title': self.title,
- 'titleFont': self.titleFont.toString(),
- 'alignment': self.alignment,
- 'fontSize': self.fontSize,
- 'xPosition': self.xPosition,
- 'yPosition': self.yPosition,
- 'textColor': self.textColor
- }
+ saveValueStore = super().savePreset()
+ saveValueStore['titleFont'] = self.titleFont.toString()
+ saveValueStore['textColor'] = self.textColor
+ return saveValueStore
def previewRender(self, previewWorker):
width = int(self.settings.value('outputWidth'))
@@ -158,7 +141,7 @@ class Component(Component):
print('Set custom x, y position:\n x=500 y=500')
def command(self, arg):
- if not arg.startswith('preset=') and '=' in arg:
+ if '=' in arg:
key, arg = arg.split('=', 1)
if key == 'color':
self.page.lineEdit_textColor.setText(arg)
diff --git a/src/components/video.py b/src/components/video.py
index 8758b12..677e3ee 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -9,6 +9,7 @@ from queue import PriorityQueue
from core import Core
from component import Component, BadComponentInit
from toolkit.frame import BlankFrame
+from toolkit.ffmpeg import testAudioStream
from toolkit import openPipe, checkOutput
@@ -16,7 +17,7 @@ class Video:
'''Video Component Frame-Fetcher'''
def __init__(self, **kwargs):
mandatoryArgs = [
- 'ffmpeg', # path to ffmpeg, usually Core.FFMPEG_BIN
+ 'ffmpeg', # path to ffmpeg, usually self.core.FFMPEG_BIN
'videoPath',
'width',
'height',
@@ -110,47 +111,40 @@ class Component(Component):
name = 'Video'
version = '1.0.0'
- def widget(self, parent):
- self.parent = parent
- self.settings = parent.settings
- page = self.loadUi('video.ui')
+ def widget(self, *args):
self.videoPath = ''
self.badVideo = False
self.badAudio = False
self.x = 0
self.y = 0
self.loopVideo = False
-
- page.lineEdit_video.textChanged.connect(self.update)
- page.pushButton_video.clicked.connect(self.pickVideo)
- page.checkBox_loop.stateChanged.connect(self.update)
- page.checkBox_distort.stateChanged.connect(self.update)
- page.checkBox_useAudio.stateChanged.connect(self.update)
- page.spinBox_scale.valueChanged.connect(self.update)
- page.spinBox_volume.valueChanged.connect(self.update)
- page.spinBox_x.valueChanged.connect(self.update)
- page.spinBox_y.valueChanged.connect(self.update)
-
- self.page = page
- return page
+ super().widget(*args)
+ 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',
+ }
+ )
def update(self):
- self.videoPath = self.page.lineEdit_video.text()
- self.loopVideo = self.page.checkBox_loop.isChecked()
- self.useAudio = self.page.checkBox_useAudio.isChecked()
- self.distort = self.page.checkBox_distort.isChecked()
- self.scale = self.page.spinBox_scale.value()
- self.volume = self.page.spinBox_volume.value()
- self.xPosition = self.page.spinBox_x.value()
- self.yPosition = self.page.spinBox_y.value()
-
- if self.useAudio:
+ if self.page.checkBox_useAudio.isChecked():
self.page.label_volume.setEnabled(True)
self.page.spinBox_volume.setEnabled(True)
else:
self.page.label_volume.setEnabled(False)
self.page.spinBox_volume.setEnabled(False)
-
super().update()
def previewRender(self, previewWorker):
@@ -188,18 +182,7 @@ class Component(Component):
return "The video selected is corrupt!"
def testAudioStream(self):
- # test if an audio stream really exists
- audioTestCommand = [
- Core.FFMPEG_BIN,
- '-i', self.videoPath,
- '-vn', '-f', 'null', '-'
- ]
- try:
- checkOutput(audioTestCommand, stderr=subprocess.DEVNULL)
- except subprocess.CalledProcessError:
- self.badAudio = True
- else:
- self.badAudio = False
+ self.badAudio = testAudioStream(self.videoPath)
def audio(self):
params = {}
@@ -214,7 +197,7 @@ class Component(Component):
self.blankFrame_ = BlankFrame(width, height)
self.updateChunksize(width, height)
self.video = Video(
- ffmpeg=Core.FFMPEG_BIN, videoPath=self.videoPath,
+ ffmpeg=self.core.FFMPEG_BIN, videoPath=self.videoPath,
width=width, height=height, chunkSize=self.chunkSize,
frameRate=int(self.settings.value("outputFrameRate")),
parent=self.parent, loopVideo=self.loopVideo,
@@ -227,34 +210,11 @@ class Component(Component):
else:
return self.blankFrame_
- def loadPreset(self, pr, presetName=None):
- super().loadPreset(pr, presetName)
- self.page.lineEdit_video.setText(pr['video'])
- self.page.checkBox_loop.setChecked(pr['loop'])
- self.page.checkBox_useAudio.setChecked(pr['useAudio'])
- self.page.checkBox_distort.setChecked(pr['distort'])
- self.page.spinBox_scale.setValue(pr['scale'])
- self.page.spinBox_volume.setValue(pr['volume'])
- self.page.spinBox_x.setValue(pr['x'])
- self.page.spinBox_y.setValue(pr['y'])
-
- def savePreset(self):
- return {
- 'video': self.videoPath,
- 'loop': self.loopVideo,
- 'useAudio': self.useAudio,
- 'distort': self.distort,
- 'scale': self.scale,
- 'volume': self.volume,
- 'x': self.xPosition,
- 'y': self.yPosition,
- }
-
def pickVideo(self):
imgDir = self.settings.value("componentDir", os.path.expanduser("~"))
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
self.page, "Choose Video",
- imgDir, "Video Files (%s)" % " ".join(Core.videoFormats)
+ imgDir, "Video Files (%s)" % " ".join(self.core.videoFormats)
)
if filename:
self.settings.setValue("componentDir", os.path.dirname(filename))
@@ -266,7 +226,7 @@ class Component(Component):
return
command = [
- self.parent.core.FFMPEG_BIN,
+ self.core.FFMPEG_BIN,
'-thread_queue_size', '512',
'-i', self.videoPath,
'-f', 'image2pipe',
@@ -294,10 +254,10 @@ class Component(Component):
self.chunkSize = 4*width*height
def command(self, arg):
- if not arg.startswith('preset=') and '=' in 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 Core.videoFormats:
+ 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)
diff --git a/src/core.py b/src/core.py
index f6cf5eb..eb6398b 100644
--- a/src/core.py
+++ b/src/core.py
@@ -1,5 +1,6 @@
'''
Home to the Core class which tracks program state. Used by GUI & commandline
+ to create a list of components and create a video thread to export.
'''
from PyQt5 import QtCore, QtGui, uic
import sys
@@ -8,7 +9,6 @@ import json
from importlib import import_module
import toolkit
-from toolkit.ffmpeg import findFfmpeg
import video_thread
@@ -16,82 +16,21 @@ class Core:
'''
MainWindow and Command module both use an instance of this class
to store the core program state. This object tracks the components,
- talks to the components and handles opening/creating project files
- and presets. The class also stores constants as class variables.
+ talks to the components, handles opening/creating project files
+ and presets, and creates the video thread to export.
+ This class also stores constants as class variables.
'''
- @classmethod
- def storeSettings(cls):
- '''Store settings/paths to directories as class variables.'''
- if getattr(sys, 'frozen', False):
- # frozen
- wd = os.path.dirname(sys.executable)
- else:
- wd = os.path.dirname(os.path.realpath(__file__))
-
- dataDir = QtCore.QStandardPaths.writableLocation(
- QtCore.QStandardPaths.AppConfigLocation
- )
- with open(os.path.join(wd, 'encoder-options.json')) as json_file:
- encoderOptions = json.load(json_file)
-
- settings = {
- 'wd': wd,
- 'dataDir': dataDir,
- 'settings': QtCore.QSettings(
- os.path.join(dataDir, 'settings.ini'),
- QtCore.QSettings.IniFormat),
- 'presetDir': os.path.join(dataDir, 'presets'),
- 'componentsPath': os.path.join(wd, 'components'),
- 'encoderOptions': encoderOptions,
- 'FFMPEG_BIN': findFfmpeg(),
- 'canceled': False,
- }
-
- settings['videoFormats'] = toolkit.appendUppercase([
- '*.mp4',
- '*.mov',
- '*.mkv',
- '*.avi',
- '*.webm',
- '*.flv',
- ])
- settings['audioFormats'] = toolkit.appendUppercase([
- '*.mp3',
- '*.wav',
- '*.ogg',
- '*.fla',
- '*.flac',
- '*.aac',
- ])
- settings['imageFormats'] = toolkit.appendUppercase([
- '*.png',
- '*.jpg',
- '*.tif',
- '*.tiff',
- '*.gif',
- '*.bmp',
- '*.ico',
- '*.xbm',
- '*.xpm',
- ])
-
- # Register all settings as class variables
- for classvar, val in settings.items():
- setattr(cls, classvar, val)
- # Make settings accessible to the toolkit package
- toolkit.init(settings)
-
def __init__(self):
- Core.storeSettings()
-
self.findComponents()
self.selectedComponents = []
self.savedPresets = {} # copies of presets to detect modification
+ self.openingProject = False
def findComponents(self):
+ '''Imports all the component modules'''
def findComponents():
- for f in sorted(os.listdir(Core.componentsPath)):
+ for f in os.listdir(Core.componentsPath):
name, ext = os.path.splitext(f)
if name.startswith("__"):
continue
@@ -104,8 +43,13 @@ class Core:
# store canonical module names and indexes
self.moduleIndexes = [i for i in range(len(self.modules))]
self.compNames = [mod.Component.name for mod in self.modules]
- self.altCompNames = []
+ # alphabetize modules by Component name
+ sortedModules = sorted(zip(self.compNames, self.modules))
+ self.compNames = [y[0] for y in sortedModules]
+ self.modules = [y[1] for y in sortedModules]
+
# store alternative names for modules
+ self.altCompNames = []
for i, mod in enumerate(self.modules):
if hasattr(mod.Component, 'names'):
for name in mod.Component.names():
@@ -116,14 +60,17 @@ class Core:
component.compPos = i
def insertComponent(self, compPos, moduleIndex, loader):
- '''Creates a new component'''
+ '''
+ Creates a new component using these args:
+ (compPos, moduleIndex in self.modules, MWindow/Command/Core obj)
+ '''
if compPos < 0 or compPos > len(self.selectedComponents):
compPos = len(self.selectedComponents)
if len(self.selectedComponents) > 50:
return None
component = self.modules[moduleIndex].Component(
- moduleIndex, compPos
+ moduleIndex, compPos, self
)
self.selectedComponents.insert(
compPos,
@@ -206,6 +153,7 @@ class Core:
errcode, data = self.parseAvFile(filepath)
if errcode == 0:
+ self.openingProject = True
try:
if hasattr(loader, 'window'):
for widget, value in data['WindowFields']:
@@ -239,7 +187,8 @@ class Core:
i = self.insertComponent(
-1,
self.moduleIndexFor(name),
- loader)
+ loader
+ )
if i is None:
loader.showMessage(msg="Too many components!")
break
@@ -284,6 +233,7 @@ class Core:
showCancel=False,
icon='Warning',
detail=msg)
+ self.openingProject = False
def parseAvFile(self, filepath):
'''Parses an avp (project) or avl (preset package) file.
@@ -467,8 +417,106 @@ class Core:
def cancel(self):
Core.canceled = True
- toolkit.cancel()
def reset(self):
Core.canceled = False
- toolkit.reset()
+
+ @classmethod
+ def storeSettings(cls):
+ '''Store settings/paths to directories as class variables'''
+ from __init__ import wd
+ from toolkit.ffmpeg import findFfmpeg
+
+ cls.wd = wd
+ dataDir = QtCore.QStandardPaths.writableLocation(
+ QtCore.QStandardPaths.AppConfigLocation
+ )
+ with open(os.path.join(wd, 'encoder-options.json')) as json_file:
+ encoderOptions = json.load(json_file)
+
+ settings = {
+ 'dataDir': dataDir,
+ 'settings': QtCore.QSettings(
+ os.path.join(dataDir, 'settings.ini'),
+ QtCore.QSettings.IniFormat),
+ 'presetDir': os.path.join(dataDir, 'presets'),
+ 'componentsPath': os.path.join(wd, 'components'),
+ 'encoderOptions': encoderOptions,
+ 'resolutions': [
+ '1920x1080',
+ '1280x720',
+ '854x480',
+ ],
+ 'windowHasFocus': False,
+ 'FFMPEG_BIN': findFfmpeg(),
+ 'canceled': False,
+ }
+
+ settings['videoFormats'] = toolkit.appendUppercase([
+ '*.mp4',
+ '*.mov',
+ '*.mkv',
+ '*.avi',
+ '*.webm',
+ '*.flv',
+ ])
+ settings['audioFormats'] = toolkit.appendUppercase([
+ '*.mp3',
+ '*.wav',
+ '*.ogg',
+ '*.fla',
+ '*.flac',
+ '*.aac',
+ ])
+ settings['imageFormats'] = toolkit.appendUppercase([
+ '*.png',
+ '*.jpg',
+ '*.tif',
+ '*.tiff',
+ '*.gif',
+ '*.bmp',
+ '*.ico',
+ '*.xbm',
+ '*.xpm',
+ ])
+
+ # Register all settings as class variables
+ for classvar, val in settings.items():
+ setattr(cls, classvar, val)
+
+ cls.loadDefaultSettings()
+
+ @classmethod
+ def loadDefaultSettings(cls):
+ defaultSettings = {
+ "outputWidth": 1280,
+ "outputHeight": 720,
+ "outputFrameRate": 30,
+ "outputAudioCodec": "AAC",
+ "outputAudioBitrate": "192",
+ "outputVideoCodec": "H264",
+ "outputVideoBitrate": "2500",
+ "outputVideoFormat": "yuv420p",
+ "outputPreset": "medium",
+ "outputFormat": "mp4",
+ "outputContainer": "MP4",
+ "projectDir": os.path.join(cls.dataDir, 'projects'),
+ "pref_insertCompAtTop": True,
+ }
+
+ for parm, value in defaultSettings.items():
+ if cls.settings.value(parm) is None:
+ cls.settings.setValue(parm, value)
+
+ # Allow manual editing of prefs. (Surprisingly necessary as Qt seems to
+ # store True as 'true' but interprets a manually-added 'true' as str.)
+ for key in cls.settings.allKeys():
+ if not key.startswith('pref_'):
+ continue
+ val = cls.settings.value(key)
+ if val in ('true', 'false'):
+ cls.settings.setValue(key, True if val == 'true' else False)
+
+
+# always store settings in class variables even if a Core object is not created
+Core.storeSettings()
diff --git a/src/main.py b/src/main.py
index 6a9a25e..977da3b 100644
--- a/src/main.py
+++ b/src/main.py
@@ -2,22 +2,17 @@ from PyQt5 import uic, QtWidgets
import sys
import os
+from __init__ import wd
-def main():
- if getattr(sys, 'frozen', False):
- # frozen
- wd = os.path.dirname(sys.executable)
- else:
- # unfrozen
- wd = os.path.dirname(os.path.realpath(__file__))
- # make local imports work everywhere
- sys.path.insert(0, wd)
+def main():
+ app = QtWidgets.QApplication(sys.argv)
+ app.setApplicationName("audio-visualizer")
+ # Determine mode
mode = 'GUI'
if len(sys.argv) > 2:
mode = 'commandline'
-
elif len(sys.argv) == 2:
if sys.argv[1].startswith('-'):
mode = 'commandline'
@@ -28,11 +23,7 @@ def main():
# normal gui launch
proj = None
- print('Starting Audio Visualizer in %s mode' % mode)
- app = QtWidgets.QApplication(sys.argv)
- app.setApplicationName("audio-visualizer")
- # app.setOrganizationName("audio-visualizer")
-
+ # Launch program
if mode == 'commandline':
from command import Command
@@ -61,9 +52,7 @@ def main():
signal.signal(signal.SIGINT, main.cleanUp)
atexit.register(main.cleanUp)
- # applicable to both modes
sys.exit(app.exec_())
-
if __name__ == "__main__":
main()
diff --git a/src/mainwindow.py b/src/mainwindow.py
index 2d598ae..f333513 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -17,7 +17,7 @@ import time
from core import Core
import preview_thread
from presetmanager import PresetManager
-from toolkit import loadDefaultSettings, disableWhenEncoding, checkOutput
+from toolkit import disableWhenEncoding, disableWhenOpeningProject, checkOutput
class PreviewWindow(QtWidgets.QLabel):
@@ -25,6 +25,7 @@ class PreviewWindow(QtWidgets.QLabel):
Paints the preview QLabel and maintains the aspect ratio when the
window is resized.
'''
+
def __init__(self, parent, img):
super(PreviewWindow, self).__init__()
self.parent = parent
@@ -49,6 +50,14 @@ class PreviewWindow(QtWidgets.QLabel):
self.pixmap = QtGui.QPixmap(img)
self.repaint()
+ @QtCore.pyqtSlot(str)
+ def threadError(self, msg):
+ self.parent.showMessage(
+ msg=msg,
+ icon='Warning',
+ parent=self
+ )
+
class MainWindow(QtWidgets.QMainWindow):
'''
@@ -66,13 +75,16 @@ class MainWindow(QtWidgets.QMainWindow):
def __init__(self, window, project):
QtWidgets.QMainWindow.__init__(self)
-
# print('main thread id: {}'.format(QtCore.QThread.currentThreadId()))
self.window = window
self.core = Core()
- self.pages = [] # widgets of component settings
+ # widgets of component settings
+ self.pages = []
self.lastAutosave = time.time()
+ # list of previous five autosave times, used to reduce update spam
+ self.autosaveTimes = []
+ self.autosaveCooldown = 0.2
self.encoding = False
# Create data directory, load/create settings
@@ -80,7 +92,6 @@ class MainWindow(QtWidgets.QMainWindow):
self.presetDir = Core.presetDir
self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
self.settings = Core.settings
- loadDefaultSettings(self)
self.presetManager = PresetManager(
uic.loadUi(
os.path.join(Core.wd, 'presetmanager.ui')), self)
@@ -92,13 +103,17 @@ class MainWindow(QtWidgets.QMainWindow):
if not os.path.exists(neededDirectory):
os.mkdir(neededDirectory)
- # Make queues/timers for the preview thread
+ # Create the preview window and its thread, queues, and timers
+ self.previewWindow = PreviewWindow(self, os.path.join(
+ Core.wd, "background.png"))
+ window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
+
self.previewQueue = Queue()
self.previewThread = QtCore.QThread(self)
self.previewWorker = preview_thread.Worker(self, self.previewQueue)
+ self.previewWorker.error.connect(self.previewWindow.threadError)
self.previewWorker.moveToThread(self.previewThread)
self.previewWorker.imageCreated.connect(self.showPreviewImage)
- self.previewWorker.error.connect(self.cleanUp)
self.previewThread.start()
self.timer = QtCore.QTimer(self)
@@ -106,6 +121,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.timer.start(500)
# Begin decorating the window and connecting events
+ self.window.installEventFilter(self)
componentList = self.window.listWidget_componentList
if sys.platform == 'darwin':
@@ -168,14 +184,9 @@ class MainWindow(QtWidgets.QMainWindow):
window.spinBox_vBitrate.setValue(vBitrate)
window.spinBox_aBitrate.setValue(aBitrate)
-
window.spinBox_vBitrate.valueChanged.connect(self.updateCodecSettings)
window.spinBox_aBitrate.valueChanged.connect(self.updateCodecSettings)
- self.previewWindow = PreviewWindow(self, os.path.join(
- Core.wd, "background.png"))
- window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
-
# Make component buttons
self.compMenu = QMenu()
for i, comp in enumerate(self.core.modules):
@@ -204,7 +215,7 @@ class MainWindow(QtWidgets.QMainWindow):
currentRes = str(self.settings.value('outputWidth'))+'x' + \
str(self.settings.value('outputHeight'))
- for i, res in enumerate(self.resolutions):
+ for i, res in enumerate(Core.resolutions):
window.comboBox_resolution.addItem(res)
if res == currentRes:
currentRes = i
@@ -375,6 +386,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.previewThread.quit()
self.previewThread.wait()
+ @disableWhenOpeningProject
def updateWindowTitle(self):
appName = 'Audio Visualizer'
try:
@@ -442,13 +454,29 @@ class MainWindow(QtWidgets.QMainWindow):
self.settings.setValue('outputVideoBitrate', currentVideoBitrate)
self.settings.setValue('outputAudioBitrate', currentAudioBitrate)
+ @disableWhenOpeningProject
def autosave(self, force=False):
if not self.currentProject:
if os.path.exists(self.autosavePath):
os.remove(self.autosavePath)
- elif force or time.time() - self.lastAutosave >= 0.2:
+ elif force or time.time() - self.lastAutosave >= self.autosaveCooldown:
self.core.createProjectFile(self.autosavePath, self.window)
self.lastAutosave = time.time()
+ if len(self.autosaveTimes) >= 5:
+ # Do some math to reduce autosave spam. This gives a smooth
+ # curve up to 5 seconds cooldown and maintains that for 30 secs
+ # if a component is continuously updated
+ timeDiff = self.lastAutosave - self.autosaveTimes.pop()
+ if not force and timeDiff >= 1.0 \
+ and timeDiff <= 10.0:
+ if self.autosaveCooldown / 4.0 < 0.5:
+ self.autosaveCooldown += 1.0
+ self.autosaveCooldown = (
+ 5.0 * (self.autosaveCooldown / 5.0)
+ ) + (self.autosaveCooldown / 5.0) * 2
+ elif force or timeDiff >= self.autosaveCooldown * 5:
+ self.autosaveCooldown = 0.2
+ self.autosaveTimes.insert(0, self.lastAutosave)
def autosaveExists(self, identical=True):
'''Determines if creating the autosave should be blocked.'''
@@ -602,15 +630,20 @@ class MainWindow(QtWidgets.QMainWindow):
def updateResolution(self):
resIndex = int(self.window.comboBox_resolution.currentIndex())
- res = self.resolutions[resIndex].split('x')
+ res = Core.resolutions[resIndex].split('x')
self.settings.setValue('outputWidth', res[0])
self.settings.setValue('outputHeight', res[1])
self.drawPreview()
- def drawPreview(self, force=False):
+ def drawPreview(self, force=False, **kwargs):
+ '''Use autosave keyword arg to force saving or not saving if needed'''
self.newTask.emit(self.core.selectedComponents)
# self.processTask.emit()
- self.autosave(force)
+ if force or 'autosave' in kwargs:
+ if force or kwargs['autosave']:
+ self.autosave(True)
+ else:
+ self.autosave()
self.updateWindowTitle()
@QtCore.pyqtSlot(QtGui.QImage)
@@ -685,9 +718,13 @@ class MainWindow(QtWidgets.QMainWindow):
stackedWidget.insertWidget(newRow, page)
componentList.setCurrentRow(newRow)
stackedWidget.setCurrentIndex(newRow)
- self.drawPreview()
+ self.drawPreview(True)
- def getComponentListRects(self):
+ def getComponentListMousePos(self, position):
+ '''
+ Given a QPos, returns the component index under the mouse cursor
+ or -1 if no component is there.
+ '''
componentList = self.window.listWidget_componentList
modelIndexes = [
@@ -698,20 +735,23 @@ class MainWindow(QtWidgets.QMainWindow):
componentList.visualRect(modelIndex)
for modelIndex in modelIndexes
]
- return rects
+ mousePos = [rect.contains(position) for rect in rects]
+ if not any(mousePos):
+ # Not clicking a component
+ mousePos = -1
+ else:
+ mousePos = mousePos.index(True)
+ return mousePos
@disableWhenEncoding
def dragComponent(self, event):
'''Used as Qt drop event for the component listwidget'''
componentList = self.window.listWidget_componentList
- rects = self.getComponentListRects()
-
- rowPos = [rect.contains(event.pos()) for rect in rects]
- if not any(rowPos):
- return
-
- i = rowPos.index(True)
- change = (componentList.currentRow() - i) * -1
+ mousePos = self.getComponentListMousePos(event.pos())
+ if mousePos > -1:
+ change = (componentList.currentRow() - mousePos) * -1
+ else:
+ change = (componentList.count() - componentList.currentRow() -1)
self.moveComponent(change)
def changeComponentWidget(self):
@@ -814,9 +854,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.settings.setValue("projectDir", os.path.dirname(filepath))
# actually load the project using core method
self.core.openProject(self, filepath)
- if self.window.listWidget_componentList.count() == 0:
- self.drawPreview()
- self.autosave(True)
+ self.drawPreview(autosave=False)
self.updateWindowTitle()
def showMessage(self, **kwargs):
@@ -843,20 +881,11 @@ class MainWindow(QtWidgets.QMainWindow):
def componentContextMenu(self, QPos):
'''Appears when right-clicking the component list'''
componentList = self.window.listWidget_componentList
- index = componentList.currentRow()
-
self.menu = QMenu()
parentPosition = componentList.mapToGlobal(QtCore.QPoint(0, 0))
- rects = self.getComponentListRects()
- rowPos = [rect.contains(QPos) for rect in rects]
- if not any(rowPos):
- # Insert components at the top if clicking nothing
- rowPos = 0
- else:
- rowPos = rowPos.index(True)
-
- if index == rowPos:
+ index = self.getComponentListMousePos(QPos)
+ if index > -1:
# Show preset menu if clicking a component
self.presetManager.findPresets()
menuItem = self.menu.addAction("Save Preset")
@@ -891,13 +920,23 @@ class MainWindow(QtWidgets.QMainWindow):
# "Add Component" submenu
self.submenu = QMenu("Add")
self.menu.addMenu(self.submenu)
+ insertCompAtTop = self.settings.value("pref_insertCompAtTop")
for i, comp in enumerate(self.core.modules):
menuItem = self.submenu.addAction(comp.Component.name)
menuItem.triggered.connect(
lambda _, item=i: self.core.insertComponent(
- rowPos, item, self
+ 0 if insertCompAtTop else index, item, self
)
- )
+ )
self.menu.move(parentPosition + QPos)
self.menu.show()
+
+ def eventFilter(self, object, event):
+ if event.type() == QtCore.QEvent.WindowActivate \
+ or event.type() == QtCore.QEvent.FocusIn:
+ Core.windowHasFocus = True
+ elif event.type()== QtCore.QEvent.WindowDeactivate \
+ or event.type() == QtCore.QEvent.FocusOut:
+ Core.windowHasFocus = False
+ return False
diff --git a/src/mainwindow.ui b/src/mainwindow.ui
index b491323..b43d375 100644
--- a/src/mainwindow.ui
+++ b/src/mainwindow.ui
@@ -22,6 +22,9 @@
0
+
+ Qt::StrongFocus
+
MainWindow
diff --git a/src/presetmanager.py b/src/presetmanager.py
index 64e2203..643e180 100644
--- a/src/presetmanager.py
+++ b/src/presetmanager.py
@@ -6,7 +6,8 @@ from PyQt5 import QtCore, QtWidgets
import string
import os
-import toolkit
+from toolkit import badName
+from core import Core
class PresetManager(QtWidgets.QDialog):
@@ -151,7 +152,7 @@ class PresetManager(QtWidgets.QDialog):
currentPreset
)
if OK:
- if toolkit.badName(newName):
+ if badName(newName):
self.warnMessage(self.parent.window)
continue
if newName:
@@ -236,7 +237,6 @@ class PresetManager(QtWidgets.QDialog):
os.remove(filepath)
def warnMessage(self, window=None):
- print(window)
self.parent.showMessage(
msg='Preset names must contain only letters, '
'numbers, and spaces.',
@@ -272,7 +272,7 @@ class PresetManager(QtWidgets.QDialog):
self.presetRows[index][2]
)
if OK:
- if toolkit.badName(newName):
+ if badName(newName):
self.warnMessage()
continue
if newName:
@@ -289,7 +289,7 @@ class PresetManager(QtWidgets.QDialog):
self.findPresets()
self.drawPresetList()
for i, comp in enumerate(self.core.selectedComponents):
- if toolkit.getPresetDir(comp) == path \
+ if getPresetDir(comp) == path \
and comp.currentPreset == oldName:
self.core.openPreset(newPath, i, newName)
self.parent.updateComponentTitle(i, False)
@@ -338,3 +338,8 @@ class PresetManager(QtWidgets.QDialog):
def clearPresetListSelection(self):
self.window.listWidget_presets.setCurrentRow(-1)
+
+
+def getPresetDir(comp):
+ '''Get the preset subdir for a particular version of a component'''
+ return os.path.join(Core.presetDir, str(comp), str(comp.version))
diff --git a/src/preview_thread.py b/src/preview_thread.py
index 3fc73b3..9917e4b 100644
--- a/src/preview_thread.py
+++ b/src/preview_thread.py
@@ -10,12 +10,13 @@ from queue import Queue, Empty
import os
from toolkit.frame import Checkerboard
+from toolkit import disableWhenOpeningProject
class Worker(QtCore.QObject):
imageCreated = pyqtSignal(QtGui.QImage)
- error = pyqtSignal()
+ error = pyqtSignal(str)
def __init__(self, parent=None, queue=None):
QtCore.QObject.__init__(self)
@@ -30,6 +31,7 @@ class Worker(QtCore.QObject):
height = int(self.settings.value('outputHeight'))
self.background = Checkerboard(width, height)
+ @disableWhenOpeningProject
@pyqtSlot(list)
def createPreviewImage(self, components):
dic = {
@@ -48,7 +50,6 @@ class Worker(QtCore.QObject):
self.queue.get(block=False)
except Empty:
continue
-
if self.background.width != width \
or self.background.height != height:
self.background = Checkerboard(width, height)
@@ -65,20 +66,12 @@ class Worker(QtCore.QObject):
except ValueError as e:
errMsg = "Bad frame returned by %s's preview renderer. " \
- "%s. New frame size was %s*%s; should be %s*%s. " \
- "This is a fatal error." % (
+ "%s. New frame size was %s*%s; should be %s*%s." % (
str(component), str(e).capitalize(),
newFrame.width, newFrame.height,
width, height
)
- print(errMsg)
- self.parent.showMessage(
- msg=errMsg,
- detail=str(e),
- icon='Warning',
- parent=None # MainWindow is in a different thread
- )
- self.error.emit()
+ self.error.emit(errMsg)
break
except RuntimeError as e:
print(e)
diff --git a/src/toolkit/common.py b/src/toolkit/common.py
index 763d582..5fe601f 100644
--- a/src/toolkit/common.py
+++ b/src/toolkit/common.py
@@ -8,13 +8,6 @@ import sys
import subprocess
from collections import OrderedDict
-from toolkit.core import *
-
-
-def getPresetDir(comp):
- '''Get the preset subdirectory for a particular version of a component'''
- return os.path.join(Core.presetDir, str(comp), str(comp.version))
-
def badName(name):
'''Returns whether a name contains non-alphanumeric chars'''
@@ -66,14 +59,20 @@ def openPipe(commandList, **kwargs):
def disableWhenEncoding(func):
- ''' Blocks calls to a function while the video is being exported
- in MainWindow.
- '''
- def decorator(*args, **kwargs):
- if args[0].encoding:
+ def decorator(self, *args, **kwargs):
+ if self.encoding:
return
else:
- return func(*args, **kwargs)
+ return func(self, *args, **kwargs)
+ return decorator
+
+
+def disableWhenOpeningProject(func):
+ def decorator(self, *args, **kwargs):
+ if self.core.openingProject:
+ return
+ else:
+ return func(self, *args, **kwargs)
return decorator
@@ -108,34 +107,3 @@ def rgbFromString(string):
return tup
except:
return (255, 255, 255)
-
-
-def loadDefaultSettings(self):
- '''
- Runs once at each program start-up. Fills in default settings
- for any settings not found in settings.ini
- '''
- self.resolutions = [
- '1920x1080',
- '1280x720',
- '854x480'
- ]
-
- default = {
- "outputWidth": 1280,
- "outputHeight": 720,
- "outputFrameRate": 30,
- "outputAudioCodec": "AAC",
- "outputAudioBitrate": "192",
- "outputVideoCodec": "H264",
- "outputVideoBitrate": "2500",
- "outputVideoFormat": "yuv420p",
- "outputPreset": "medium",
- "outputFormat": "mp4",
- "outputContainer": "MP4",
- "projectDir": os.path.join(self.dataDir, 'projects'),
- }
-
- for parm, value in default.items():
- if self.settings.value(parm) is None:
- self.settings.setValue(parm, value)
diff --git a/src/toolkit/core.py b/src/toolkit/core.py
deleted file mode 100644
index a96a684..0000000
--- a/src/toolkit/core.py
+++ /dev/null
@@ -1,18 +0,0 @@
-class Core:
- '''A very complicated class for tracking settings'''
-
-
-def init(settings):
- global Core
- for classvar, val in settings.items():
- setattr(Core, classvar, val)
-
-
-def cancel():
- global Core
- Core.canceled = True
-
-
-def reset():
- global Core
- Core.canceled = False
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index cc59a6c..30dc0b3 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -4,18 +4,19 @@
import numpy
import sys
import os
-import subprocess as sp
+import subprocess
-from toolkit.common import Core, checkOutput, openPipe
+import core
+from toolkit.common import checkOutput, openPipe
def findFfmpeg():
if getattr(sys, 'frozen', False):
# The application is frozen
if sys.platform == "win32":
- return os.path.join(Core.wd, 'ffmpeg.exe')
+ return os.path.join(core.Core.wd, 'ffmpeg.exe')
else:
- return os.path.join(Core.wd, 'ffmpeg')
+ return os.path.join(core.Core.wd, 'ffmpeg')
else:
if sys.platform == "win32":
@@ -27,7 +28,7 @@ def findFfmpeg():
['ffmpeg', '-version'], stderr=f
)
return "ffmpeg"
- except sp.CalledProcessError:
+ except subprocess.CalledProcessError:
return "avconv"
@@ -37,9 +38,9 @@ def createFfmpegCommand(inputFile, outputFile, components, duration=-1):
'''
if duration == -1:
duration = getAudioDuration(inputFile)
-
safeDuration = "{0:.3f}".format(duration - 0.05) # used by filters
duration = "{0:.3f}".format(duration + 0.1) # used by input sources
+ Core = core.Core
# Test if user has libfdk_aac
encoders = checkOutput(
@@ -213,12 +214,28 @@ def createFfmpegCommand(inputFile, outputFile, components, duration=-1):
return ffmpegCommand
+def testAudioStream(filename):
+ '''Test if an audio stream definitely exists'''
+ audioTestCommand = [
+ core.Core.FFMPEG_BIN,
+ '-i', filename,
+ '-vn', '-f', 'null', '-'
+ ]
+ try:
+ checkOutput(audioTestCommand, stderr=subprocess.DEVNULL)
+ except subprocess.CalledProcessError:
+ return True
+ else:
+ return False
+
+
def getAudioDuration(filename):
- command = [Core.FFMPEG_BIN, '-i', filename]
+ '''Try to get duration of audio file as float, or False if not possible'''
+ command = [core.Core.FFMPEG_BIN, '-i', filename]
try:
- fileInfo = checkOutput(command, stderr=sp.STDOUT)
- except sp.CalledProcessError as ex:
+ fileInfo = checkOutput(command, stderr=subprocess.STDOUT)
+ except subprocess.CalledProcessError as ex:
fileInfo = ex.output
info = fileInfo.decode("utf-8").split('\n')
@@ -236,13 +253,17 @@ def getAudioDuration(filename):
def readAudioFile(filename, parent):
+ '''
+ Creates the completeAudioArray given to components
+ and used to draw the classic visualizer.
+ '''
duration = getAudioDuration(filename)
if not duration:
print('Audio file doesn\'t exist or unreadable.')
return
command = [
- Core.FFMPEG_BIN,
+ core.Core.FFMPEG_BIN,
'-i', filename,
'-f', 's16le',
'-acodec', 'pcm_s16le',
@@ -250,7 +271,8 @@ def readAudioFile(filename, parent):
'-ac', '1', # mono (set to '2' for stereo)
'-']
in_pipe = openPipe(
- command, stdout=sp.PIPE, stderr=sp.DEVNULL, bufsize=10**8
+ command,
+ stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, bufsize=10**8
)
completeAudioArray = numpy.empty(0, dtype="int16")
@@ -258,7 +280,7 @@ def readAudioFile(filename, parent):
progress = 0
lastPercent = None
while True:
- if Core.canceled:
+ if core.Core.canceled:
return
# read 2 seconds of audio
progress += 4
diff --git a/src/toolkit/frame.py b/src/toolkit/frame.py
index 83fd59e..ca2a054 100644
--- a/src/toolkit/frame.py
+++ b/src/toolkit/frame.py
@@ -7,7 +7,7 @@ from PIL.ImageQt import ImageQt
import sys
import os
-from toolkit.common import Core
+import core
class FramePainter(QtGui.QPainter):
@@ -57,7 +57,7 @@ def Checkerboard(width, height):
'''
image = FloodFrame(1920, 1080, (0, 0, 0, 0))
image.paste(Image.open(
- os.path.join(Core.wd, "background.png")),
+ os.path.join(core.Core.wd, "background.png")),
(0, 0)
)
image = image.resize((width, height))
diff --git a/src/video_thread.py b/src/video_thread.py
index 8517b92..7fe3e02 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -18,6 +18,7 @@ from threading import Thread, Event
import time
import signal
+import core
from toolkit import openPipe
from toolkit.ffmpeg import readAudioFile, createFfmpegCommand
from toolkit.frame import Checkerboard
@@ -104,7 +105,8 @@ class Worker(QtCore.QObject):
while not self.stopped:
audioI, frame = self.previewQueue.get()
- if time.time() - self.lastPreview >= 0.06 or audioI == 0:
+ if core.Core.windowHasFocus \
+ and time.time() - self.lastPreview >= 0.06 or audioI == 0:
image = Image.alpha_composite(background.copy(), frame)
self.imageCreated.emit(QtGui.QImage(ImageQt(image)))
self.lastPreview = time.time()
@@ -231,7 +233,8 @@ class Worker(QtCore.QObject):
self.lastPreview = 0.0
self.previewDispatch = Thread(
- target=self.previewDispatch, name="Render Dispatch Thread")
+ target=self.previewDispatch, name="Render Dispatch Thread"
+ )
self.previewDispatch.daemon = True
self.previewDispatch.start()
--
cgit v1.2.3
From d38109453cea17a31c335837c0029ad51fa3dda1 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 23 Jul 2017 17:14:21 -0400
Subject: better component error messages
fatal errors cancel the export instead of crashing
---
src/component.py | 157 ++++++++++++++++++++++++++++++++++-----------
src/components/original.py | 2 +-
src/components/sound.py | 2 +
src/components/video.py | 24 +++----
src/core.py | 10 ++-
src/mainwindow.py | 15 ++++-
src/toolkit/common.py | 8 +++
src/toolkit/ffmpeg.py | 2 +-
src/video_thread.py | 52 ++++++++-------
9 files changed, 190 insertions(+), 82 deletions(-)
diff --git a/src/component.py b/src/component.py
index bec2df5..8b5f1b8 100644
--- a/src/component.py
+++ b/src/component.py
@@ -5,13 +5,12 @@
from PyQt5 import uic, QtCore, QtWidgets
import os
-from presetmanager import getPresetDir
-
def commandWrapper(func):
'''Intercepts each component's command() method to check for global args'''
def decorator(self, arg):
if arg.startswith('preset='):
+ from presetmanager import getPresetDir
_, preset = arg.split('=', 1)
path = os.path.join(getPresetDir(self), preset)
if not os.path.exists(path):
@@ -29,6 +28,26 @@ def commandWrapper(func):
return decorator
+def propertiesWrapper(func):
+ '''Intercepts the usual properties if the properties are locked.'''
+ def decorator(self):
+ if self._lockedProperties is not None:
+ return self._lockedProperties
+ else:
+ return func(self)
+ return decorator
+
+
+def errorWrapper(func):
+ '''Intercepts the usual error message if it is locked.'''
+ def decorator(self):
+ if self._lockedError is not None:
+ return self._lockedError
+ else:
+ return func(self)
+ return decorator
+
+
class ComponentMetaclass(type(QtCore.QObject)):
'''
Checks the validity of each Component class imported, and
@@ -37,25 +56,33 @@ class ComponentMetaclass(type(QtCore.QObject)):
'''
def __new__(cls, name, parents, attrs):
if 'ui' not in attrs:
- # use module name as ui filename by default
+ # Use module name as ui filename by default
attrs['ui'] = '%s.ui' % os.path.splitext(
attrs['__module__'].split('.')[-1]
)[0]
- # Turn certain class methods into properties and classmethods
- for key in ('error', 'properties', 'audio'):
- if key not in attrs:
- continue
- attrs[key] = property(attrs[key])
+ # if parents[0] == QtCore.QObject: else:
+ decorate = ('names', 'error', 'audio', 'command', 'properties')
- for key in ('names'):
+ # Auto-decorate methods
+ for key in decorate:
if key not in attrs:
continue
- attrs[key] = classmethod(key)
- # Do not apply these mutations to the base class
- if parents[0] != QtCore.QObject:
- attrs['command'] = commandWrapper(attrs['command'])
+ if key in ('names'):
+ attrs[key] = classmethod(attrs[key])
+
+ if key in ('audio'):
+ attrs[key] = property(attrs[key])
+
+ if key == 'command':
+ attrs[key] = commandWrapper(attrs[key])
+
+ if key == 'properties':
+ attrs[key] = propertiesWrapper(attrs[key])
+
+ if key == 'error':
+ attrs[key] = errorWrapper(attrs[key])
# Turn version string into a number
try:
@@ -83,13 +110,13 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
name = 'Component'
# ui = 'nameOfNonDefaultUiFile'
+
version = '1.0.0'
# The major version (before the first dot) is used to determine
# preset compatibility; the rest is ignored so it can be non-numeric.
modified = QtCore.pyqtSignal(int, dict)
- # ^ Signal used to tell core program that the component state changed,
- # you shouldn't need to use this directly, it is used by self.update()
+ _error = QtCore.pyqtSignal(str, str)
def __init__(self, moduleIndex, compPos, core):
super().__init__()
@@ -100,6 +127,9 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
self._trackedWidgets = {}
self._presetNames = {}
+ self._commandArgs = {}
+ self._lockedProperties = None
+ self._lockedError = None
# Stop lengthy processes in response to this variable
self.canceled = False
@@ -127,6 +157,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
def error(self):
'''
Return a string containing an error message, or None for a default.
+ Or tuple of two strings for a message with details.
'''
return
@@ -141,12 +172,6 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
https://ffmpeg.org/ffmpeg-filters.html
'''
- def names():
- '''
- Alternative names for renaming a component between project files.
- '''
- return []
-
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
# Methods
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
@@ -181,15 +206,29 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
for widget in widgets['comboBox']:
widget.currentIndexChanged.connect(self.update)
- def trackWidgets(self, trackDict, presetNames=None):
+ def trackWidgets(self, trackDict, **kwargs):
'''
- Name widgets to track in update(), savePreset(), and loadPreset()
- Accepts a dict with attribute names as keys and widgets as values.
- Optional: a dict of attribute names to map to preset variable names
+ Name widgets to track in update(), savePreset(), loadPreset(), and
+ command(). Requires a dict of attr names as keys, widgets as values
+
+ Optional args:
+ 'presetNames': preset variable names to replace attr names
+ 'commandArgs': arg keywords that differ from attr names
+
+ NOTE: Any kwarg key set to None will selectively disable tracking.
'''
self._trackedWidgets = trackDict
- if type(presetNames) is dict:
- self._presetNames = presetNames
+ for kwarg in kwargs:
+ try:
+ if kwarg in ('presetNames', 'commandArgs'):
+ setattr(self, '_%s' % kwarg, kwargs[kwarg])
+ else:
+ raise BadComponentInit(
+ self,
+ 'Nonsensical keywords to trackWidgets.',
+ immediate=True)
+ except BadComponentInit:
+ continue
def update(self):
'''
@@ -277,6 +316,22 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
self.commandHelp()
quit(0)
+ # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
+ # "Private" Methods
+ # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
+
+ def lockProperties(self, propList):
+ self._lockedProperties = propList
+
+ def lockError(self, msg):
+ self._lockedError = msg
+
+ def unlockProperties(self):
+ self._lockedProperties = None
+
+ def unlockError(self):
+ self._lockedError = None
+
def loadUi(self, filename):
'''Load a Qt Designer ui file to use for this component's widget'''
return uic.loadUi(os.path.join(self.core.componentsPath, filename))
@@ -287,6 +342,8 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
def reset(self):
self.canceled = False
+ self.unlockProperties()
+ self.unlockError()
'''
### Reference methods for creating a new component
@@ -309,16 +366,40 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
'''
-class BadComponentInit(Exception):
+class BadComponentInit(AttributeError):
'''
- General purpose exception that components can raise to indicate
- a Python issue with e.g., dynamic creation of instances or something.
- Decorative for now, may have future use for logging.
+ Indicates a Python error in constructing a component.
+ Raising this locks the component into an error state,
+ and gives the MainWindow a traceback to display.
'''
- def __init__(self, arg, name):
- string = '''################################
-Mandatory argument "%s" not specified
- in %s instance initialization
-###################################'''
- print(string % (arg, name))
- quit()
+ def __init__(self, caller, name, immediate=False):
+ from toolkit import formatTraceback
+ import sys
+ if sys.exc_info()[0] is not None:
+ string = (
+ "%s component's %s encountered %s %s." % (
+ caller.__class__.name,
+ name,
+ 'an' if any([
+ sys.exc_info()[0].__name__.startswith(vowel)
+ for vowel in ('A', 'I')
+ ]) else 'a',
+ sys.exc_info()[0].__name__,
+ )
+ )
+ detail = formatTraceback(sys.exc_info()[2])
+ else:
+ string = name
+ detail = "Methods:\n%s" % (
+ "\n".join(
+ [m for m in dir(caller) if not m.startswith('_')]
+ )
+ )
+
+ if immediate:
+ caller.parent.showMessage(
+ msg=string, detail=detail, icon='Warning'
+ )
+ else:
+ caller.lockProperties(['error'])
+ caller.lockError((string, detail))
diff --git a/src/components/original.py b/src/components/original.py
index 2bda878..570465d 100644
--- a/src/components/original.py
+++ b/src/components/original.py
@@ -15,7 +15,7 @@ class Component(Component):
name = 'Classic Visualizer'
version = '1.0.0'
- def names():
+ def names(*args):
return ['Original Audio Visualization']
def widget(self, *args):
diff --git a/src/components/sound.py b/src/components/sound.py
index dd3cbab..b3a627a 100644
--- a/src/components/sound.py
+++ b/src/components/sound.py
@@ -18,6 +18,8 @@ class Component(Component):
'chorus': self.page.checkBox_chorus,
'delay': self.page.spinBox_delay,
'volume': self.page.spinBox_volume,
+ }, commandArgs={
+ 'sound': None,
})
def previewRender(self, previewWorker):
diff --git a/src/components/video.py b/src/components/video.py
index 677e3ee..d3696d4 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -14,7 +14,7 @@ from toolkit import openPipe, checkOutput
class Video:
- '''Video Component Frame-Fetcher'''
+ '''Opens a pipe to ffmpeg and stores a buffer of raw video frames.'''
def __init__(self, **kwargs):
mandatoryArgs = [
'ffmpeg', # path to ffmpeg, usually self.core.FFMPEG_BIN
@@ -28,10 +28,7 @@ class Video:
'component', # component object
]
for arg in mandatoryArgs:
- try:
- setattr(self, arg, kwargs[arg])
- except KeyError:
- raise BadComponentInit(arg, self.__doc__)
+ setattr(self, arg, kwargs[arg])
self.frameNo = -1
self.currentFrame = 'None'
@@ -196,13 +193,16 @@ class Component(Component):
height = int(self.settings.value('outputHeight'))
self.blankFrame_ = BlankFrame(width, height)
self.updateChunksize(width, height)
- self.video = Video(
- ffmpeg=self.core.FFMPEG_BIN, videoPath=self.videoPath,
- width=width, height=height, chunkSize=self.chunkSize,
- frameRate=int(self.settings.value("outputFrameRate")),
- parent=self.parent, loopVideo=self.loopVideo,
- component=self, scale=self.scale
- ) if os.path.exists(self.videoPath) else None
+ try:
+ self.video = Video(
+ ffmpeg=self.core.FFMPEG_BIN, #videoPath=self.videoPath,
+ width=width, height=height, chunkSize=self.chunkSize,
+ frameRate=int(self.settings.value("outputFrameRate")),
+ parent=self.parent, loopVideo=self.loopVideo,
+ component=self, scale=self.scale
+ ) if os.path.exists(self.videoPath) else None
+ except KeyError:
+ raise BadComponentInit(self, 'Frame Fetcher initialization')
def frameRender(self, layerNo, frameNo):
if self.video:
diff --git a/src/core.py b/src/core.py
index eb6398b..2f9c36c 100644
--- a/src/core.py
+++ b/src/core.py
@@ -22,13 +22,12 @@ class Core:
'''
def __init__(self):
- self.findComponents()
+ self.importComponents()
self.selectedComponents = []
self.savedPresets = {} # copies of presets to detect modification
self.openingProject = False
- def findComponents(self):
- '''Imports all the component modules'''
+ def importComponents(self):
def findComponents():
for f in os.listdir(Core.componentsPath):
name, ext = os.path.splitext(f)
@@ -225,9 +224,8 @@ class Core:
return
if hasattr(loader, 'createNewProject'):
loader.createNewProject(prompt=False)
- import traceback
- msg = '%s: %s\n\nTraceback:\n' % (typ.__name__, value)
- msg += "\n".join(traceback.format_tb(tb))
+ msg = '%s: %s\n\n' % (typ.__name__, value)
+ msg += toolkit.formatTraceback(tb)
loader.showMessage(
msg="Project file '%s' is corrupted." % filepath,
showCancel=False,
diff --git a/src/mainwindow.py b/src/mainwindow.py
index f333513..a32c1b4 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -571,6 +571,15 @@ class MainWindow(QtWidgets.QMainWindow):
self.videoWorker.encoding.connect(self.changeEncodingStatus)
self.createVideo.emit()
+ @QtCore.pyqtSlot(str, str)
+ def videoThreadError(self, msg, detail):
+ self.showMessage(
+ msg=msg,
+ detail=detail,
+ icon='Warning',
+ )
+ self.stopVideo()
+
def changeEncodingStatus(self, status):
self.encoding = status
if status:
@@ -675,6 +684,8 @@ class MainWindow(QtWidgets.QMainWindow):
# connect to signal that adds an asterisk when modified
self.core.selectedComponents[index].modified.connect(
self.updateComponentTitle)
+ self.core.selectedComponents[index]._error.connect(
+ self.videoThreadError)
self.pages.insert(index, self.core.selectedComponents[index].page)
stackedWidget.insertWidget(index, self.pages[index])
@@ -751,7 +762,7 @@ class MainWindow(QtWidgets.QMainWindow):
if mousePos > -1:
change = (componentList.currentRow() - mousePos) * -1
else:
- change = (componentList.count() - componentList.currentRow() -1)
+ change = (componentList.count() - componentList.currentRow() - 1)
self.moveComponent(change)
def changeComponentWidget(self):
@@ -936,7 +947,7 @@ class MainWindow(QtWidgets.QMainWindow):
if event.type() == QtCore.QEvent.WindowActivate \
or event.type() == QtCore.QEvent.FocusIn:
Core.windowHasFocus = True
- elif event.type()== QtCore.QEvent.WindowDeactivate \
+ elif event.type() == QtCore.QEvent.WindowDeactivate \
or event.type() == QtCore.QEvent.FocusOut:
Core.windowHasFocus = False
return False
diff --git a/src/toolkit/common.py b/src/toolkit/common.py
index 5fe601f..251a2c1 100644
--- a/src/toolkit/common.py
+++ b/src/toolkit/common.py
@@ -107,3 +107,11 @@ def rgbFromString(string):
return tup
except:
return (255, 255, 255)
+
+
+def formatTraceback(tb=None):
+ import traceback
+ if tb is None:
+ import sys
+ tb = sys.exc_info()[2]
+ return 'Traceback:\n%s' % "\n".join(traceback.format_tb(tb))
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index 30dc0b3..8f5ae87 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -103,7 +103,7 @@ def createFfmpegCommand(inputFile, outputFile, components, duration=-1):
globalFilters = 0 # increase to add global filters
extraAudio = [
comp.audio for comp in components
- if 'audio' in comp.properties
+ if 'audio' in comp.properties()
]
if extraAudio or globalFilters > 0:
# Add -i options for extra input files
diff --git a/src/video_thread.py b/src/video_thread.py
index 7fe3e02..68eae4f 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -18,7 +18,7 @@ from threading import Thread, Event
import time
import signal
-import core
+from component import BadComponentInit
from toolkit import openPipe
from toolkit.ffmpeg import readAudioFile, createFfmpegCommand
from toolkit.frame import Checkerboard
@@ -105,8 +105,7 @@ class Worker(QtCore.QObject):
while not self.stopped:
audioI, frame = self.previewQueue.get()
- if core.Core.windowHasFocus \
- and time.time() - self.lastPreview >= 0.06 or audioI == 0:
+ if time.time() - self.lastPreview >= 0.06 or audioI == 0:
image = Image.alpha_composite(background.copy(), frame)
self.imageCreated.emit(QtGui.QImage(ImageQt(image)))
self.lastPreview = time.time()
@@ -153,39 +152,48 @@ class Worker(QtCore.QObject):
]))
self.staticComponents = {}
for compNo, comp in enumerate(reversed(self.components)):
- comp.preFrameRender(
- worker=self,
- completeAudioArray=self.completeAudioArray,
- sampleSize=self.sampleSize,
- progressBarUpdate=self.progressBarUpdate,
- progressBarSetText=self.progressBarSetText
- )
+ try:
+ comp.preFrameRender(
+ worker=self,
+ completeAudioArray=self.completeAudioArray,
+ sampleSize=self.sampleSize,
+ progressBarUpdate=self.progressBarUpdate,
+ progressBarSetText=self.progressBarSetText
+ )
+ except BadComponentInit:
+ pass
- if 'error' in comp.properties:
+ if 'error' in comp.properties():
self.cancel()
self.canceled = True
canceledByComponent = True
- errMsg = "Component #%s encountered an error!" % compNo \
- if comp.error is None else 'Component #%s (%s): %s' % (
+ compError = comp.error() \
+ if type(comp.error()) is tuple else (comp.error(), '')
+ errMsg = (
+ "Component #%s encountered an error!" % compNo
+ if comp.error() is None else
+ 'Export cancelled by component #%s (%s): %s' % (
str(compNo),
str(comp),
- comp.error
- )
- self.parent.showMessage(
- msg=errMsg,
- icon='Warning',
- parent=None # MainWindow is in a different thread
+ compError[0]
)
+ )
+ comp._error.emit(errMsg, compError[1])
break
- if 'static' in comp.properties:
+ if 'static' in comp.properties():
self.staticComponents[compNo] = \
comp.frameRender(compNo, 0).copy()
if self.canceled:
if canceledByComponent:
print('Export cancelled by component #%s (%s): %s' % (
- compNo, str(comp), comp.error
- ))
+ compNo,
+ comp.name,
+ 'No message.' if comp.error() is None else (
+ comp.error() if type(comp.error()) is str
+ else comp.error()[0])
+ )
+ )
self.cancelExport()
return
--
cgit v1.2.3
From d92fc6373fd070f0ea303e9795eb7648d5cd9e90 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 23 Jul 2017 22:55:41 -0400
Subject: ComponentError exception wraps previewRender
probably where errors are likeliest to be found
---
src/command.py | 6 +++
src/component.py | 119 +++++++++++++++++++++++++++---------------------
src/components/video.py | 6 +--
src/core.py | 3 ++
src/mainwindow.py | 8 ++--
src/toolkit/frame.py | 18 ++++++++
src/video_thread.py | 4 +-
7 files changed, 104 insertions(+), 60 deletions(-)
diff --git a/src/command.py b/src/command.py
index ca186e5..74ca821 100644
--- a/src/command.py
+++ b/src/command.py
@@ -146,6 +146,12 @@ class Command(QtCore.QObject):
if 'detail' in kwargs:
print(kwargs['detail'])
+ @QtCore.pyqtSlot(str, str)
+ def videoThreadError(self, msg, detail):
+ print(msg)
+ print(detail)
+ quit(1)
+
def drawPreview(self, *args):
pass
diff --git a/src/component.py b/src/component.py
index 8b5f1b8..41cb5eb 100644
--- a/src/component.py
+++ b/src/component.py
@@ -6,54 +6,64 @@ from PyQt5 import uic, QtCore, QtWidgets
import os
-def commandWrapper(func):
- '''Intercepts each component's command() method to check for global args'''
- def decorator(self, arg):
- if arg.startswith('preset='):
- from presetmanager import getPresetDir
- _, preset = arg.split('=', 1)
- path = os.path.join(getPresetDir(self), preset)
- if not os.path.exists(path):
- print('Couldn\'t locate preset "%s"' % preset)
- quit(1)
- else:
- print('Opening "%s" preset on layer %s' % (
- preset, self.compPos)
- )
- self.core.openPreset(path, self.compPos, preset)
- # Don't call the component's command() method
- return
- else:
- return func(self, arg)
- return decorator
-
-
-def propertiesWrapper(func):
- '''Intercepts the usual properties if the properties are locked.'''
- def decorator(self):
- if self._lockedProperties is not None:
- return self._lockedProperties
- else:
- return func(self)
- return decorator
-
-
-def errorWrapper(func):
- '''Intercepts the usual error message if it is locked.'''
- def decorator(self):
- if self._lockedError is not None:
- return self._lockedError
- else:
- return func(self)
- return decorator
-
-
class ComponentMetaclass(type(QtCore.QObject)):
'''
Checks the validity of each Component class imported, and
mutates some attributes for easier use by the core program.
E.g., takes only major version from version string & decorates methods
'''
+
+ def renderWrapper(func):
+ def decorator(self, *args, **kwargs):
+ try:
+ return func(self, *args, **kwargs)
+ except:
+ from toolkit.frame import BlankFrame
+ try:
+ raise ComponentError(self, 'renderer', immediate=True)
+ except ComponentError:
+ return BlankFrame()
+ return decorator
+
+ def commandWrapper(func):
+ '''Intercepts each component's command() method to check for global args'''
+ def decorator(self, arg):
+ if arg.startswith('preset='):
+ from presetmanager import getPresetDir
+ _, preset = arg.split('=', 1)
+ path = os.path.join(getPresetDir(self), preset)
+ if not os.path.exists(path):
+ print('Couldn\'t locate preset "%s"' % preset)
+ quit(1)
+ else:
+ print('Opening "%s" preset on layer %s' % (
+ preset, self.compPos)
+ )
+ self.core.openPreset(path, self.compPos, preset)
+ # Don't call the component's command() method
+ return
+ else:
+ return func(self, arg)
+ return decorator
+
+ def propertiesWrapper(func):
+ '''Intercepts the usual properties if the properties are locked.'''
+ def decorator(self):
+ if self._lockedProperties is not None:
+ return self._lockedProperties
+ else:
+ return func(self)
+ return decorator
+
+ def errorWrapper(func):
+ '''Intercepts the usual error message if it is locked.'''
+ def decorator(self):
+ if self._lockedError is not None:
+ return self._lockedError
+ else:
+ return func(self)
+ return decorator
+
def __new__(cls, name, parents, attrs):
if 'ui' not in attrs:
# Use module name as ui filename by default
@@ -62,7 +72,11 @@ class ComponentMetaclass(type(QtCore.QObject)):
)[0]
# if parents[0] == QtCore.QObject: else:
- decorate = ('names', 'error', 'audio', 'command', 'properties')
+ decorate = (
+ 'names', # Class methods
+ 'error', 'audio', 'properties', # Properties
+ 'previewRender', 'command',
+ )
# Auto-decorate methods
for key in decorate:
@@ -76,13 +90,16 @@ class ComponentMetaclass(type(QtCore.QObject)):
attrs[key] = property(attrs[key])
if key == 'command':
- attrs[key] = commandWrapper(attrs[key])
+ attrs[key] = cls.commandWrapper(attrs[key])
+
+ if key == 'previewRender':
+ attrs[key] = cls.renderWrapper(attrs[key])
if key == 'properties':
- attrs[key] = propertiesWrapper(attrs[key])
+ attrs[key] = cls.propertiesWrapper(attrs[key])
if key == 'error':
- attrs[key] = errorWrapper(attrs[key])
+ attrs[key] = cls.errorWrapper(attrs[key])
# Turn version string into a number
try:
@@ -223,11 +240,11 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
if kwarg in ('presetNames', 'commandArgs'):
setattr(self, '_%s' % kwarg, kwargs[kwarg])
else:
- raise BadComponentInit(
+ raise ComponentError(
self,
'Nonsensical keywords to trackWidgets.',
immediate=True)
- except BadComponentInit:
+ except ComponentError:
continue
def update(self):
@@ -366,7 +383,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
'''
-class BadComponentInit(AttributeError):
+class ComponentError(RuntimeError):
'''
Indicates a Python error in constructing a component.
Raising this locks the component into an error state,
@@ -397,9 +414,7 @@ class BadComponentInit(AttributeError):
)
if immediate:
- caller.parent.showMessage(
- msg=string, detail=detail, icon='Warning'
- )
+ caller._error.emit(string, detail)
else:
caller.lockProperties(['error'])
caller.lockError((string, detail))
diff --git a/src/components/video.py b/src/components/video.py
index d3696d4..383531e 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -7,7 +7,7 @@ import threading
from queue import PriorityQueue
from core import Core
-from component import Component, BadComponentInit
+from component import Component, ComponentError
from toolkit.frame import BlankFrame
from toolkit.ffmpeg import testAudioStream
from toolkit import openPipe, checkOutput
@@ -195,14 +195,14 @@ class Component(Component):
self.updateChunksize(width, height)
try:
self.video = Video(
- ffmpeg=self.core.FFMPEG_BIN, #videoPath=self.videoPath,
+ ffmpeg=self.core.FFMPEG_BIN, videoPath=self.videoPath,
width=width, height=height, chunkSize=self.chunkSize,
frameRate=int(self.settings.value("outputFrameRate")),
parent=self.parent, loopVideo=self.loopVideo,
component=self, scale=self.scale
) if os.path.exists(self.videoPath) else None
except KeyError:
- raise BadComponentInit(self, 'Frame Fetcher initialization')
+ raise ComponentError(self, 'Frame Fetcher initialization')
def frameRender(self, layerNo, frameNo):
if self.video:
diff --git a/src/core.py b/src/core.py
index 2f9c36c..4c08c04 100644
--- a/src/core.py
+++ b/src/core.py
@@ -76,6 +76,9 @@ class Core:
component
)
self.componentListChanged()
+ self.selectedComponents[compPos]._error.connect(
+ loader.videoThreadError
+ )
# init component's widget for loading/saving presets
self.selectedComponents[compPos].widget(loader)
diff --git a/src/mainwindow.py b/src/mainwindow.py
index a32c1b4..03b8dde 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -578,7 +578,11 @@ class MainWindow(QtWidgets.QMainWindow):
detail=detail,
icon='Warning',
)
- self.stopVideo()
+ try:
+ self.stopVideo()
+ except AttributeError as e:
+ if 'videoWorker' not in str(e):
+ raise
def changeEncodingStatus(self, status):
self.encoding = status
@@ -684,8 +688,6 @@ class MainWindow(QtWidgets.QMainWindow):
# connect to signal that adds an asterisk when modified
self.core.selectedComponents[index].modified.connect(
self.updateComponentTitle)
- self.core.selectedComponents[index]._error.connect(
- self.videoThreadError)
self.pages.insert(index, self.core.selectedComponents[index].page)
stackedWidget.insertWidget(index, self.pages[index])
diff --git a/src/toolkit/frame.py b/src/toolkit/frame.py
index ca2a054..b66e037 100644
--- a/src/toolkit/frame.py
+++ b/src/toolkit/frame.py
@@ -41,15 +41,33 @@ class PaintColor(QtGui.QColor):
super().__init__(b, g, r, a)
+def defaultSize(framefunc):
+ '''Makes width/height arguments optional'''
+ def decorator(*args):
+ if len(args) < 2:
+ newArgs = list(args)
+ if len(args) == 0 or len(args) == 1:
+ height = int(core.Core.settings.value("outputHeight"))
+ newArgs.append(height)
+ if len(args) == 0:
+ width = int(core.Core.settings.value("outputWidth"))
+ newArgs.insert(0, width)
+ args = tuple(newArgs)
+ return framefunc(*args)
+ return decorator
+
+
def FloodFrame(width, height, RgbaTuple):
return Image.new("RGBA", (width, height), RgbaTuple)
+@defaultSize
def BlankFrame(width, height):
'''The base frame used by each component to start drawing.'''
return FloodFrame(width, height, (0, 0, 0, 0))
+@defaultSize
def Checkerboard(width, height):
'''
A checkerboard to represent transparency to the user.
diff --git a/src/video_thread.py b/src/video_thread.py
index 68eae4f..dd957e5 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -18,7 +18,7 @@ from threading import Thread, Event
import time
import signal
-from component import BadComponentInit
+from component import ComponentError
from toolkit import openPipe
from toolkit.ffmpeg import readAudioFile, createFfmpegCommand
from toolkit.frame import Checkerboard
@@ -160,7 +160,7 @@ class Worker(QtCore.QObject):
progressBarUpdate=self.progressBarUpdate,
progressBarSetText=self.progressBarSetText
)
- except BadComponentInit:
+ except ComponentError:
pass
if 'error' in comp.properties():
--
cgit v1.2.3
From c517140a51256169cdcff0a4c2d5973d5f367259 Mon Sep 17 00:00:00 2001
From: rikai
Date: Sun, 23 Jul 2017 20:14:10 -0700
Subject: Fixes opening behind other windows on OS X
Just a quick fix, this will raise the window to the front after it's created.---
src/main.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/main.py b/src/main.py
index 6a9a25e..8d5a769 100644
--- a/src/main.py
+++ b/src/main.py
@@ -57,6 +57,7 @@ def main():
# window.verticalLayout_2.setContentsMargins(0, topMargin, 0, 0)
main = MainWindow(window, proj)
+ window.raise_()
signal.signal(signal.SIGINT, main.cleanUp)
atexit.register(main.cleanUp)
--
cgit v1.2.3
From d25dee6afc0cc72f477b577623079b4d644957a8 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Mon, 24 Jul 2017 21:22:04 -0400
Subject: preset manager uses mainwindow row for every button
and minor changes to componenterrors
---
src/component.py | 74 ++++++++++++++++++++++++++++++++++---------------
src/components/video.py | 17 +++++-------
src/presetmanager.py | 10 +++++--
3 files changed, 67 insertions(+), 34 deletions(-)
diff --git a/src/component.py b/src/component.py
index 41cb5eb..48e9c1a 100644
--- a/src/component.py
+++ b/src/component.py
@@ -13,21 +13,32 @@ class ComponentMetaclass(type(QtCore.QObject)):
E.g., takes only major version from version string & decorates methods
'''
+ def initializationWrapper(func):
+ def initializationWrapper(self, *args, **kwargs):
+ try:
+ return func(self, *args, **kwargs)
+ except:
+ try:
+ raise ComponentInitError(self, 'initialization process')
+ except ComponentError:
+ return
+ return initializationWrapper
+
def renderWrapper(func):
- def decorator(self, *args, **kwargs):
+ def renderWrapper(self, *args, **kwargs):
try:
return func(self, *args, **kwargs)
except:
from toolkit.frame import BlankFrame
try:
- raise ComponentError(self, 'renderer', immediate=True)
+ raise ComponentError(self, 'renderer')
except ComponentError:
return BlankFrame()
- return decorator
+ return renderWrapper
def commandWrapper(func):
- '''Intercepts each component's command() method to check for global args'''
- def decorator(self, arg):
+ '''Intercepts the command() method to check for global args'''
+ def commandWrapper(self, arg):
if arg.startswith('preset='):
from presetmanager import getPresetDir
_, preset = arg.split('=', 1)
@@ -44,25 +55,25 @@ class ComponentMetaclass(type(QtCore.QObject)):
return
else:
return func(self, arg)
- return decorator
+ return commandWrapper
def propertiesWrapper(func):
'''Intercepts the usual properties if the properties are locked.'''
- def decorator(self):
+ def propertiesWrapper(self):
if self._lockedProperties is not None:
return self._lockedProperties
else:
return func(self)
- return decorator
+ return propertiesWrapper
def errorWrapper(func):
'''Intercepts the usual error message if it is locked.'''
- def decorator(self):
+ def errorWrapper(self):
if self._lockedError is not None:
return self._lockedError
else:
return func(self)
- return decorator
+ return errorWrapper
def __new__(cls, name, parents, attrs):
if 'ui' not in attrs:
@@ -75,7 +86,8 @@ class ComponentMetaclass(type(QtCore.QObject)):
decorate = (
'names', # Class methods
'error', 'audio', 'properties', # Properties
- 'previewRender', 'command',
+ 'preFrameRender', 'previewRender',
+ 'command',
)
# Auto-decorate methods
@@ -95,6 +107,9 @@ class ComponentMetaclass(type(QtCore.QObject)):
if key == 'previewRender':
attrs[key] = cls.renderWrapper(attrs[key])
+ if key == 'preFrameRender':
+ attrs[key] = cls.initializationWrapper(attrs[key])
+
if key == 'properties':
attrs[key] = cls.propertiesWrapper(attrs[key])
@@ -126,7 +141,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
'''
name = 'Component'
- # ui = 'nameOfNonDefaultUiFile'
+ # ui = 'name_Of_Non_Default_Ui_File'
version = '1.0.0'
# The major version (before the first dot) is used to determine
@@ -241,9 +256,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
setattr(self, '_%s' % kwarg, kwargs[kwarg])
else:
raise ComponentError(
- self,
- 'Nonsensical keywords to trackWidgets.',
- immediate=True)
+ self, 'Nonsensical keywords to trackWidgets.')
except ComponentError:
continue
@@ -383,13 +396,10 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
'''
-class ComponentError(RuntimeError):
- '''
- Indicates a Python error in constructing a component.
- Raising this locks the component into an error state,
- and gives the MainWindow a traceback to display.
- '''
- def __init__(self, caller, name, immediate=False):
+class ComponentException(RuntimeError):
+ '''A base class for component errors'''
+ def __init__(self, caller, name, immediate):
+ super().__init__()
from toolkit import formatTraceback
import sys
if sys.exc_info()[0] is not None:
@@ -418,3 +428,23 @@ class ComponentError(RuntimeError):
else:
caller.lockProperties(['error'])
caller.lockError((string, detail))
+
+
+class ComponentError(ComponentException):
+ '''
+ Use for general Python errors caused by a component at any time.
+ Raising this gives the MainWindow a traceback to display and
+ cancels any export in progress.
+ '''
+ def __init__(self, caller, name):
+ ComponentException.__init__(self, caller, name, True)
+
+
+class ComponentInitError(ComponentError):
+ '''
+ Use for Python errors in preFrameRender, while the export is starting.
+ This will end the video thread in a clean way by locking the component
+ into an error state so the export definitely won't begin.
+ '''
+ def __init__(self, caller, name):
+ ComponentException.__init__(self, caller, name, False)
diff --git a/src/components/video.py b/src/components/video.py
index 383531e..153fc4d 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -193,16 +193,13 @@ class Component(Component):
height = int(self.settings.value('outputHeight'))
self.blankFrame_ = BlankFrame(width, height)
self.updateChunksize(width, height)
- try:
- self.video = Video(
- ffmpeg=self.core.FFMPEG_BIN, videoPath=self.videoPath,
- width=width, height=height, chunkSize=self.chunkSize,
- frameRate=int(self.settings.value("outputFrameRate")),
- parent=self.parent, loopVideo=self.loopVideo,
- component=self, scale=self.scale
- ) if os.path.exists(self.videoPath) else None
- except KeyError:
- raise ComponentError(self, 'Frame Fetcher initialization')
+ self.video = Video(
+ ffmpeg=self.core.FFMPEG_BIN, videoPath=self.videoPath,
+ width=width, height=height, chunkSize=self.chunkSize,
+ frameRate=int(self.settings.value("outputFrameRate")),
+ parent=self.parent, loopVideo=self.loopVideo,
+ component=self, scale=self.scale
+ ) if os.path.exists(self.videoPath) else None
def frameRender(self, layerNo, frameNo):
if self.video:
diff --git a/src/presetmanager.py b/src/presetmanager.py
index 643e180..e602c16 100644
--- a/src/presetmanager.py
+++ b/src/presetmanager.py
@@ -252,12 +252,18 @@ class PresetManager(QtWidgets.QDialog):
compIndex = componentList.currentRow()
if compIndex == -1:
return
+
preset = self.core.selectedComponents[compIndex].currentPreset
- if not preset:
+ if preset is None:
return
else:
+ rowTuple = (
+ self.core.selectedComponents[compIndex].name,
+ self.core.selectedComponents[compIndex].version,
+ preset
+ )
for i, tup in enumerate(self.presetRows):
- if preset == tup[2]:
+ if rowTuple == tup:
index = i
break
else:
--
cgit v1.2.3
From 661526b0739115594fda4c0e876398cdc940fbe1 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 25 Jul 2017 17:44:59 -0400
Subject: repeated errors don't cause repeated windows
---
src/component.py | 15 ++++++++++--
src/components/sound.py | 1 -
src/components/video.py | 4 ++--
src/core.py | 15 ++++++------
src/mainwindow.py | 4 ++--
src/presetmanager.py | 61 +++++++++++++++++++++++++++----------------------
src/video_thread.py | 8 +++----
7 files changed, 63 insertions(+), 45 deletions(-)
diff --git a/src/component.py b/src/component.py
index 48e9c1a..7a768ed 100644
--- a/src/component.py
+++ b/src/component.py
@@ -17,7 +17,7 @@ class ComponentMetaclass(type(QtCore.QObject)):
def initializationWrapper(self, *args, **kwargs):
try:
return func(self, *args, **kwargs)
- except:
+ except Exception:
try:
raise ComponentInitError(self, 'initialization process')
except ComponentError:
@@ -28,7 +28,7 @@ class ComponentMetaclass(type(QtCore.QObject)):
def renderWrapper(self, *args, **kwargs):
try:
return func(self, *args, **kwargs)
- except:
+ except Exception:
from toolkit.frame import BlankFrame
try:
raise ComponentError(self, 'renderer')
@@ -398,8 +398,19 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
class ComponentException(RuntimeError):
'''A base class for component errors'''
+
+ _prevErrors = []
+
def __init__(self, caller, name, immediate):
+ print('ComponentError by %s: %s' % (caller.name, name))
super().__init__()
+ if len(ComponentException._prevErrors) > 1:
+ ComponentException._prevErrors.pop()
+ ComponentException._prevErrors.insert(0, name)
+ if name in ComponentException._prevErrors[1:]:
+ # Don't create multiple windows for repeated messages
+ return
+
from toolkit import formatTraceback
import sys
if sys.exc_info()[0] is not None:
diff --git a/src/components/sound.py b/src/components/sound.py
index b3a627a..fcd9e4e 100644
--- a/src/components/sound.py
+++ b/src/components/sound.py
@@ -1,7 +1,6 @@
from PyQt5 import QtGui, QtCore, QtWidgets
import os
-from core import Core
from component import Component
from toolkit.frame import BlankFrame
diff --git a/src/components/video.py b/src/components/video.py
index 153fc4d..6b0a04a 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -6,8 +6,7 @@ import subprocess
import threading
from queue import PriorityQueue
-from core import Core
-from component import Component, ComponentError
+from component import Component
from toolkit.frame import BlankFrame
from toolkit.ffmpeg import testAudioStream
from toolkit import openPipe, checkOutput
@@ -155,6 +154,7 @@ class Component(Component):
return frame
def properties(self):
+ # TODO: Disallow selecting the same video you're exporting to
props = []
if not self.videoPath or self.badVideo \
or not os.path.exists(self.videoPath):
diff --git a/src/core.py b/src/core.py
index 4c08c04..b371d64 100644
--- a/src/core.py
+++ b/src/core.py
@@ -215,7 +215,7 @@ class Core:
if hasattr(loader, 'updateComponentTitle'):
loader.updateComponentTitle(i, modified)
- except:
+ except Exception:
errcode = 1
data = sys.exc_info()
@@ -237,9 +237,10 @@ class Core:
self.openingProject = False
def parseAvFile(self, filepath):
- '''Parses an avp (project) or avl (preset package) file.
- Returns dictionary with section names as the keys, each one
- contains a list of tuples: (compName, version, compPresetDict)
+ '''
+ Parses an avp (project) or avl (preset package) file.
+ Returns dictionary with section names as the keys, each one
+ contains a list of tuples: (compName, version, compPresetDict)
'''
validSections = (
'Components',
@@ -287,7 +288,7 @@ class Core:
data[section].append((key, value.strip()))
return 0, data
- except:
+ except Exception:
return 1, sys.exc_info()
def importPreset(self, filepath):
@@ -332,7 +333,7 @@ class Core:
exportPath
)
return True
- except:
+ except Exception:
return False
def createPresetFile(
@@ -397,7 +398,7 @@ class Core:
)
)
return True
- except:
+ except Exception:
return False
def newVideoWorker(self, loader, audioFile, outputPath):
diff --git a/src/mainwindow.py b/src/mainwindow.py
index 03b8dde..3cc5d26 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -314,7 +314,7 @@ class MainWindow(QtWidgets.QMainWindow):
['ffmpeg', '-version'], stderr=f
)
goodVersion = str(ffmpegVers).split()[2].startswith('3')
- except:
+ except Exception:
goodVersion = False
else:
goodVersion = True
@@ -381,7 +381,7 @@ class MainWindow(QtWidgets.QMainWindow):
)
@QtCore.pyqtSlot()
- def cleanUp(self):
+ def cleanUp(self, *args):
self.timer.stop()
self.previewThread.quit()
self.previewThread.wait()
diff --git a/src/presetmanager.py b/src/presetmanager.py
index e602c16..b1eeb34 100644
--- a/src/presetmanager.py
+++ b/src/presetmanager.py
@@ -211,10 +211,9 @@ class PresetManager(QtWidgets.QDialog):
self.parent.drawPreview()
def openDeletePresetDialog(self):
- selected = self.window.listWidget_presets.selectedItems()
- if not selected:
+ row = self.getPresetRow()
+ if row == -1:
return
- row = self.window.listWidget_presets.row(selected[0])
comp, vers, name = self.presetRows[row]
ch = self.parent.showMessage(
msg='Really delete %s?' % name,
@@ -242,32 +241,40 @@ class PresetManager(QtWidgets.QDialog):
'numbers, and spaces.',
parent=window if window else self.window)
+ def getPresetRow(self):
+ row = self.window.listWidget_presets.currentRow()
+ if row > -1:
+ return row
+
+ # check if component selected in MainWindow has preset loaded
+ componentList = self.parent.window.listWidget_componentList
+ compIndex = componentList.currentRow()
+ if compIndex == -1:
+ return compIndex
+
+ preset = self.core.selectedComponents[compIndex].currentPreset
+ if preset is None:
+ return -1
+ else:
+ rowTuple = (
+ self.core.selectedComponents[compIndex].name,
+ self.core.selectedComponents[compIndex].version,
+ preset
+ )
+ for i, tup in enumerate(self.presetRows):
+ if rowTuple == tup:
+ index = i
+ break
+ else:
+ return -1
+ return index
+
def openRenamePresetDialog(self):
# TODO: maintain consistency by changing this to call createNewPreset()
presetList = self.window.listWidget_presets
- index = presetList.currentRow()
+ index = self.getPresetRow()
if index == -1:
- # check if component selected in MainWindow has preset loaded
- componentList = self.parent.window.listWidget_componentList
- compIndex = componentList.currentRow()
- if compIndex == -1:
- return
-
- preset = self.core.selectedComponents[compIndex].currentPreset
- if preset is None:
- return
- else:
- rowTuple = (
- self.core.selectedComponents[compIndex].name,
- self.core.selectedComponents[compIndex].version,
- preset
- )
- for i, tup in enumerate(self.presetRows):
- if rowTuple == tup:
- index = i
- break
- else:
- return
+ return
while True:
newName, OK = QtWidgets.QInputDialog.getText(
@@ -326,14 +333,14 @@ class PresetManager(QtWidgets.QDialog):
self.settings.setValue("presetDir", os.path.dirname(filename))
def openExportDialog(self):
- if not self.window.listWidget_presets.selectedItems():
+ index = self.getPresetRow()
+ if index == -1:
return
filename, _ = QtWidgets.QFileDialog.getSaveFileName(
self.window, "Export Preset",
self.settings.value("presetDir"),
"Preset Files (*.avl)")
if filename:
- index = self.window.listWidget_presets.currentRow()
comp, vers, name = self.presetRows[index]
if not self.core.exportPreset(filename, comp, vers, name):
self.parent.showMessage(
diff --git a/src/video_thread.py b/src/video_thread.py
index dd957e5..8cbe8a8 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -225,7 +225,7 @@ class Worker(QtCore.QObject):
self.renderThreads = []
try:
numCpus = len(os.sched_getaffinity(0))
- except:
+ except Exception:
numCpus = os.cpu_count()
for i in range(2 if numCpus <= 2 else 3):
@@ -268,7 +268,7 @@ class Worker(QtCore.QObject):
try:
self.out_pipe.stdin.write(frameBuffer[audioI].tobytes())
self.previewQueue.put([audioI, frameBuffer.pop(audioI)])
- except:
+ except Exception:
break
# increase progress bar value
@@ -293,7 +293,7 @@ class Worker(QtCore.QObject):
print("Export Canceled")
try:
os.remove(self.outputFile)
- except:
+ except Exception:
pass
self.progressBarUpdate.emit(0)
self.progressBarSetText.emit('Export Canceled')
@@ -333,7 +333,7 @@ class Worker(QtCore.QObject):
try:
self.out_pipe.send_signal(signal.SIGINT)
- except:
+ except Exception:
pass
def reset(self):
--
cgit v1.2.3
From 15d70474d4df16cd03f4eb672d409166f793eabf Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 25 Jul 2017 22:02:47 -0400
Subject: error can be locked within properties()
and simplified the componenterrors again
---
src/component.py | 52 ++++++++++++++++--------------------------------
src/components/video.py | 35 ++++++++++++++------------------
src/mainwindow.py | 10 +++++-----
src/presetmanager.pyc | Bin 0 -> 10936 bytes
src/toolkit/ffmpeg.py | 4 ++--
src/video_thread.py | 11 ++++++----
6 files changed, 46 insertions(+), 66 deletions(-)
create mode 100644 src/presetmanager.pyc
diff --git a/src/component.py b/src/component.py
index 7a768ed..5de67d1 100644
--- a/src/component.py
+++ b/src/component.py
@@ -19,7 +19,7 @@ class ComponentMetaclass(type(QtCore.QObject)):
return func(self, *args, **kwargs)
except Exception:
try:
- raise ComponentInitError(self, 'initialization process')
+ raise ComponentError(self, 'initialization process')
except ComponentError:
return
return initializationWrapper
@@ -63,7 +63,13 @@ class ComponentMetaclass(type(QtCore.QObject)):
if self._lockedProperties is not None:
return self._lockedProperties
else:
- return func(self)
+ try:
+ return func(self)
+ except Exception:
+ try:
+ raise ComponentError(self, 'properties')
+ except ComponentError:
+ return []
return propertiesWrapper
def errorWrapper(func):
@@ -396,18 +402,18 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
'''
-class ComponentException(RuntimeError):
- '''A base class for component errors'''
+class ComponentError(RuntimeError):
+ '''Gives the MainWindow a traceback to display, and cancels the export.'''
- _prevErrors = []
+ prevErrors = []
- def __init__(self, caller, name, immediate):
+ def __init__(self, caller, name):
print('ComponentError by %s: %s' % (caller.name, name))
super().__init__()
- if len(ComponentException._prevErrors) > 1:
- ComponentException._prevErrors.pop()
- ComponentException._prevErrors.insert(0, name)
- if name in ComponentException._prevErrors[1:]:
+ if len(ComponentError.prevErrors) > 1:
+ ComponentError.prevErrors.pop()
+ ComponentError.prevErrors.insert(0, name)
+ if name in ComponentError.prevErrors[1:]:
# Don't create multiple windows for repeated messages
return
@@ -434,28 +440,4 @@ class ComponentException(RuntimeError):
)
)
- if immediate:
- caller._error.emit(string, detail)
- else:
- caller.lockProperties(['error'])
- caller.lockError((string, detail))
-
-
-class ComponentError(ComponentException):
- '''
- Use for general Python errors caused by a component at any time.
- Raising this gives the MainWindow a traceback to display and
- cancels any export in progress.
- '''
- def __init__(self, caller, name):
- ComponentException.__init__(self, caller, name, True)
-
-
-class ComponentInitError(ComponentError):
- '''
- Use for Python errors in preFrameRender, while the export is starting.
- This will end the video thread in a clean way by locking the component
- into an error state so the export definitely won't begin.
- '''
- def __init__(self, caller, name):
- ComponentException.__init__(self, caller, name, False)
+ caller._error.emit(string, detail)
diff --git a/src/components/video.py b/src/components/video.py
index 6b0a04a..8872fbf 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -154,33 +154,28 @@ class Component(Component):
return frame
def properties(self):
- # TODO: Disallow selecting the same video you're exporting to
props = []
- if not self.videoPath or self.badVideo \
- or not os.path.exists(self.videoPath):
- return ['error']
+
+ 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(
+ self.parent.window.lineEdit_outputFile.text())):
+ self.lockError("Input and output paths match.")
if self.useAudio:
props.append('audio')
- self.testAudioStream()
- if self.badAudio:
- return ['error']
+ if not testAudioStream(self.videoPath) \
+ and self.error() is None:
+ self.lockError(
+ "Could not identify an audio stream in this video.")
return props
- def error(self):
- if self.badAudio:
- return "Could not identify an audio stream in this video."
- if not self.videoPath:
- return "There is no video selected."
- if not os.path.exists(self.videoPath):
- return "The video selected does not exist!"
- if self.badVideo:
- return "The video selected is corrupt!"
-
- def testAudioStream(self):
- self.badAudio = testAudioStream(self.videoPath)
-
def audio(self):
params = {}
if self.volume != 1.0:
diff --git a/src/mainwindow.py b/src/mainwindow.py
index 3cc5d26..e478d19 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -573,16 +573,16 @@ class MainWindow(QtWidgets.QMainWindow):
@QtCore.pyqtSlot(str, str)
def videoThreadError(self, msg, detail):
- self.showMessage(
- msg=msg,
- detail=detail,
- icon='Warning',
- )
try:
self.stopVideo()
except AttributeError as e:
if 'videoWorker' not in str(e):
raise
+ self.showMessage(
+ msg=msg,
+ detail=detail,
+ icon='Warning',
+ )
def changeEncodingStatus(self, status):
self.encoding = status
diff --git a/src/presetmanager.pyc b/src/presetmanager.pyc
new file mode 100644
index 0000000..97069d2
Binary files /dev/null and b/src/presetmanager.pyc differ
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index 8f5ae87..8d63659 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -224,9 +224,9 @@ def testAudioStream(filename):
try:
checkOutput(audioTestCommand, stderr=subprocess.DEVNULL)
except subprocess.CalledProcessError:
- return True
- else:
return False
+ else:
+ return True
def getAudioDuration(filename):
diff --git a/src/video_thread.py b/src/video_thread.py
index 8cbe8a8..48f3729 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -163,24 +163,27 @@ class Worker(QtCore.QObject):
except ComponentError:
pass
- if 'error' in comp.properties():
+ compProps = comp.properties()
+ if 'error' in compProps or comp.error() is not None:
self.cancel()
self.canceled = True
canceledByComponent = True
compError = comp.error() \
if type(comp.error()) is tuple else (comp.error(), '')
errMsg = (
- "Component #%s encountered an error!" % compNo
+ "Component #%s (%s) encountered an error!" % (
+ str(compNo), comp.name
+ )
if comp.error() is None else
'Export cancelled by component #%s (%s): %s' % (
str(compNo),
- str(comp),
+ comp.name,
compError[0]
)
)
comp._error.emit(errMsg, compError[1])
break
- if 'static' in comp.properties():
+ if 'static' in compProps:
self.staticComponents[compNo] = \
comp.frameRender(compNo, 0).copy()
--
cgit v1.2.3
From 03a36d429761c169e23ae21d816a383fa73c0277 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 25 Jul 2017 22:04:50 -0400
Subject: removed pyc
---
.gitignore | 2 +-
src/presetmanager.pyc | Bin 10936 -> 0 bytes
2 files changed, 1 insertion(+), 1 deletion(-)
delete mode 100644 src/presetmanager.pyc
diff --git a/.gitignore b/.gitignore
index 1095610..bfdd0e7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,5 @@
__pycache__
-.py[cod]
+*.py[cod]
build/*
dist/*
env/*
diff --git a/src/presetmanager.pyc b/src/presetmanager.pyc
deleted file mode 100644
index 97069d2..0000000
Binary files a/src/presetmanager.pyc and /dev/null differ
--
cgit v1.2.3
From 4329b0e947471ced7ca0b3460a5f40e2703117e9 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 25 Jul 2017 22:14:34 -0400
Subject: don't]] always trigger error()
---
src/video_thread.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/video_thread.py b/src/video_thread.py
index 48f3729..c5a3c09 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -164,7 +164,7 @@ class Worker(QtCore.QObject):
pass
compProps = comp.properties()
- if 'error' in compProps or comp.error() is not None:
+ if 'error' in compProps or comp._lockedError is not None:
self.cancel()
self.canceled = True
canceledByComponent = True
--
cgit v1.2.3
From de1324a6a75eb2a9f97d8a6b416077cfc73b2bc9 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 27 Jul 2017 17:49:08 -0400
Subject: fixed video component eating stdout
+ made height/width into properties to simplify render methods
---
src/component.py | 150 +++++++++++++++++++++++++--------------------
src/components/color.py | 12 ++--
src/components/image.py | 12 ++--
src/components/original.py | 10 +--
src/components/sound.py | 10 ---
src/components/text.py | 13 ++--
src/components/video.py | 67 ++++++++++----------
src/preview_thread.py | 2 +-
src/toolkit/ffmpeg.py | 6 +-
src/video_thread.py | 14 +++--
10 files changed, 141 insertions(+), 155 deletions(-)
diff --git a/src/component.py b/src/component.py
index 5de67d1..1c5ccb3 100644
--- a/src/component.py
+++ b/src/component.py
@@ -4,6 +4,9 @@
'''
from PyQt5 import uic, QtCore, QtWidgets
import os
+import time
+
+from toolkit.frame import BlankFrame
class ComponentMetaclass(type(QtCore.QObject)):
@@ -28,10 +31,12 @@ class ComponentMetaclass(type(QtCore.QObject)):
def renderWrapper(self, *args, **kwargs):
try:
return func(self, *args, **kwargs)
- except Exception:
- from toolkit.frame import BlankFrame
+ except Exception as e:
try:
- raise ComponentError(self, 'renderer')
+ if e.__name__.startswith('Component'):
+ raise
+ else:
+ raise ComponentError(self, 'renderer')
except ComponentError:
return BlankFrame()
return renderWrapper
@@ -93,7 +98,7 @@ class ComponentMetaclass(type(QtCore.QObject)):
'names', # Class methods
'error', 'audio', 'properties', # Properties
'preFrameRender', 'previewRender',
- 'command',
+ 'frameRender', 'command',
)
# Auto-decorate methods
@@ -110,7 +115,7 @@ class ComponentMetaclass(type(QtCore.QObject)):
if key == 'command':
attrs[key] = cls.commandWrapper(attrs[key])
- if key == 'previewRender':
+ if key in ('previewRender', 'frameRender'):
attrs[key] = cls.renderWrapper(attrs[key])
if key == 'preFrameRender':
@@ -180,6 +185,37 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
self.__class__.name, str(self.__class__.version), self.savePreset()
)
+ # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
+ # Critical Methods
+ # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
+
+ def previewRender(self):
+ image = BlankFrame(self.width, self.height)
+ return image
+
+ def preFrameRender(self, **kwargs):
+ '''
+ Must call super() when subclassing
+ Triggered only before a video is exported (video_thread.py)
+ self.worker = the video thread worker
+ self.completeAudioArray = a list of audio samples
+ self.sampleSize = number of audio samples per video frame
+ self.progressBarUpdate = signal to set progress bar number
+ self.progressBarSetText = signal to set progress bar text
+ Use the latter two signals to update the MainWindow if needed
+ for a long initialization procedure (i.e., for a visualizer)
+ '''
+ for key, value in kwargs.items():
+ setattr(self, key, value)
+
+ def frameRender(self, frameNo):
+ audioArrayIndex = frameNo * self.sampleSize
+ image = BlankFrame(self.width, self.height)
+ return image
+
+ def renderFinished(self):
+ pass
+
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
# Properties
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
@@ -196,6 +232,8 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
'''
Return a string containing an error message, or None for a default.
Or tuple of two strings for a message with details.
+ Alternatively use lockError(msgString) within properties()
+ to skip this method entirely.
'''
return
@@ -211,7 +249,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
'''
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
- # Methods
+ # Idle Methods
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
def widget(self, parent):
@@ -244,33 +282,11 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
for widget in widgets['comboBox']:
widget.currentIndexChanged.connect(self.update)
- def trackWidgets(self, trackDict, **kwargs):
- '''
- Name widgets to track in update(), savePreset(), loadPreset(), and
- command(). Requires a dict of attr names as keys, widgets as values
-
- Optional args:
- 'presetNames': preset variable names to replace attr names
- 'commandArgs': arg keywords that differ from attr names
-
- NOTE: Any kwarg key set to None will selectively disable tracking.
- '''
- self._trackedWidgets = trackDict
- for kwarg in kwargs:
- try:
- if kwarg in ('presetNames', 'commandArgs'):
- setattr(self, '_%s' % kwarg, kwargs[kwarg])
- else:
- raise ComponentError(
- self, 'Nonsensical keywords to trackWidgets.')
- except ComponentError:
- continue
-
def update(self):
'''
Reads all tracked widget values into instance attributes
and tells the MainWindow that the component was modified.
- Call at the END of your method if you need to subclass this.
+ Call super() at the END if you need to subclass this.
'''
for attr, widget in self._trackedWidgets.items():
if type(widget) == QtWidgets.QLineEdit:
@@ -320,20 +336,6 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
] = getattr(self, attr)
return saveValueStore
- def preFrameRender(self, **kwargs):
- '''
- Triggered only before a video is exported (video_thread.py)
- self.worker = the video thread worker
- self.completeAudioArray = a list of audio samples
- self.sampleSize = number of audio samples per video frame
- self.progressBarUpdate = signal to set progress bar number
- self.progressBarSetText = signal to set progress bar text
- Use the latter two signals to update the MainWindow if needed
- for a long initialization procedure (i.e., for a visualizer)
- '''
- for key, value in kwargs.items():
- setattr(self, key, value)
-
def commandHelp(self):
'''Help text as string for this component's commandline arguments'''
@@ -356,6 +358,28 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
# "Private" Methods
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
+ def trackWidgets(self, trackDict, **kwargs):
+ '''
+ Name widgets to track in update(), savePreset(), loadPreset(), and
+ command(). Requires a dict of attr names as keys, widgets as values
+
+ Optional args:
+ 'presetNames': preset variable names to replace attr names
+ 'commandArgs': arg keywords that differ from attr names
+
+ NOTE: Any kwarg key set to None will selectively disable tracking.
+ '''
+ self._trackedWidgets = trackDict
+ for kwarg in kwargs:
+ try:
+ if kwarg in ('presetNames', 'commandArgs'):
+ setattr(self, '_%s' % kwarg, kwargs[kwarg])
+ else:
+ raise ComponentError(
+ self, 'Nonsensical keywords to trackWidgets.')
+ except ComponentError:
+ continue
+
def lockProperties(self, propList):
self._lockedProperties = propList
@@ -372,6 +396,14 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
'''Load a Qt Designer ui file to use for this component's widget'''
return uic.loadUi(os.path.join(self.core.componentsPath, filename))
+ @property
+ def width(self):
+ return int(self.settings.value('outputWidth'))
+
+ @property
+ def height(self):
+ return int(self.settings.value('outputHeight'))
+
def cancel(self):
'''Stop any lengthy process in response to this variable.'''
self.canceled = True
@@ -381,41 +413,24 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
self.unlockProperties()
self.unlockError()
- '''
- ### Reference methods for creating a new component
- ### (Inherit from this class and define these)
-
- def previewRender(self, previewWorker):
- width = int(self.settings.value('outputWidth'))
- height = int(self.settings.value('outputHeight'))
- from toolkit.frame import BlankFrame
- image = BlankFrame(width, height)
- return image
-
- def frameRender(self, layerNo, frameNo):
- audioArrayIndex = frameNo * self.sampleSize
- width = int(self.settings.value('outputWidth'))
- height = int(self.settings.value('outputHeight'))
- from toolkit.frame import BlankFrame
- image = BlankFrame(width, height)
- return image
- '''
-
class ComponentError(RuntimeError):
'''Gives the MainWindow a traceback to display, and cancels the export.'''
prevErrors = []
+ lastTime = time.time()
def __init__(self, caller, name):
- print('ComponentError by %s: %s' % (caller.name, name))
- super().__init__()
+ print('##### ComponentError by %s: %s' % (caller.name, name))
if len(ComponentError.prevErrors) > 1:
ComponentError.prevErrors.pop()
ComponentError.prevErrors.insert(0, name)
- if name in ComponentError.prevErrors[1:]:
- # Don't create multiple windows for repeated messages
+ curTime = time.time()
+ if name in ComponentError.prevErrors[1:] \
+ and curTime - ComponentError.lastTime < 0.2:
+ # Don't create multiple windows for quickly repeated messages
return
+ ComponentError.lastTime = time.time()
from toolkit import formatTraceback
import sys
@@ -440,4 +455,5 @@ class ComponentError(RuntimeError):
)
)
+ super().__init__(string)
caller._error.emit(string, detail)
diff --git a/src/components/color.py b/src/components/color.py
index 8257ed9..2abd79a 100644
--- a/src/components/color.py
+++ b/src/components/color.py
@@ -96,18 +96,14 @@ class Component(Component):
super().update()
- def previewRender(self, previewWorker):
- width = int(self.settings.value('outputWidth'))
- height = int(self.settings.value('outputHeight'))
- return self.drawFrame(width, height)
+ def previewRender(self):
+ return self.drawFrame(self.width, self.height)
def properties(self):
return ['static']
- def frameRender(self, layerNo, frameNo):
- width = int(self.settings.value('outputWidth'))
- height = int(self.settings.value('outputHeight'))
- return self.drawFrame(width, height)
+ def frameRender(self, frameNo):
+ return self.drawFrame(self.width, self.height)
def drawFrame(self, width, height):
r, g, b = self.color1
diff --git a/src/components/image.py b/src/components/image.py
index a705904..a96f127 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -31,10 +31,8 @@ class Component(Component):
},
)
- def previewRender(self, previewWorker):
- width = int(self.settings.value('outputWidth'))
- height = int(self.settings.value('outputHeight'))
- return self.drawFrame(width, height)
+ def previewRender(self):
+ return self.drawFrame(self.width, self.height)
def properties(self):
props = ['static']
@@ -48,10 +46,8 @@ class Component(Component):
if not os.path.exists(self.imagePath):
return "The image selected does not exist!"
- def frameRender(self, layerNo, frameNo):
- width = int(self.settings.value('outputWidth'))
- height = int(self.settings.value('outputHeight'))
- return self.drawFrame(width, height)
+ def frameRender(self, frameNo):
+ return self.drawFrame(self.width, self.height)
def drawFrame(self, width, height):
frame = BlankFrame(width, height)
diff --git a/src/components/original.py b/src/components/original.py
index 570465d..3d1a574 100644
--- a/src/components/original.py
+++ b/src/components/original.py
@@ -59,13 +59,11 @@ class Component(Component):
saveValueStore['visColor'] = self.visColor
return saveValueStore
- def previewRender(self, previewWorker):
+ def previewRender(self):
spectrum = numpy.fromfunction(
lambda x: float(self.scale)/2500*(x-128)**2, (255,), dtype="int16")
- width = int(self.settings.value('outputWidth'))
- height = int(self.settings.value('outputHeight'))
return self.drawBars(
- width, height, spectrum, self.visColor, self.layout
+ self.width, self.height, spectrum, self.visColor, self.layout
)
def preFrameRender(self, **kwargs):
@@ -74,8 +72,6 @@ class Component(Component):
self.smoothConstantUp = 0.8
self.lastSpectrum = None
self.spectrumArray = {}
- self.width = int(self.settings.value('outputWidth'))
- self.height = int(self.settings.value('outputHeight'))
for i in range(0, len(self.completeAudioArray), self.sampleSize):
if self.canceled:
@@ -93,7 +89,7 @@ class Component(Component):
self.progressBarSetText.emit(pStr)
self.progressBarUpdate.emit(int(progress))
- def frameRender(self, layerNo, frameNo):
+ def frameRender(self, frameNo):
arrayNo = frameNo * self.sampleSize
return self.drawBars(
self.width, self.height,
diff --git a/src/components/sound.py b/src/components/sound.py
index fcd9e4e..aff43d3 100644
--- a/src/components/sound.py
+++ b/src/components/sound.py
@@ -21,11 +21,6 @@ class Component(Component):
'sound': None,
})
- def previewRender(self, previewWorker):
- width = int(self.settings.value('outputWidth'))
- height = int(self.settings.value('outputHeight'))
- return BlankFrame(width, height)
-
def preFrameRender(self, **kwargs):
pass
@@ -63,11 +58,6 @@ class Component(Component):
self.page.lineEdit_sound.setText(filename)
self.update()
- def frameRender(self, layerNo, frameNo):
- width = int(self.settings.value('outputWidth'))
- height = int(self.settings.value('outputHeight'))
- return BlankFrame(width, height)
-
def commandHelp(self):
print('Path to audio file:\n path=/filepath/to/sound.ogg')
diff --git a/src/components/text.py b/src/components/text.py
index 1d64617..8a302ff 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -97,10 +97,8 @@ class Component(Component):
saveValueStore['textColor'] = self.textColor
return saveValueStore
- def previewRender(self, previewWorker):
- width = int(self.settings.value('outputWidth'))
- height = int(self.settings.value('outputHeight'))
- return self.addText(width, height)
+ def previewRender(self):
+ return self.addText(self.width, self.height)
def properties(self):
props = ['static']
@@ -111,13 +109,10 @@ class Component(Component):
def error(self):
return "No text provided."
- def frameRender(self, layerNo, frameNo):
- width = int(self.settings.value('outputWidth'))
- height = int(self.settings.value('outputHeight'))
- return self.addText(width, height)
+ def frameRender(self, frameNo):
+ return self.addText(self.width, self.height)
def addText(self, width, height):
-
image = FramePainter(width, height)
self.titleFont.setPixelSize(self.fontSize)
image.setFont(self.titleFont)
diff --git a/src/components/video.py b/src/components/video.py
index 8872fbf..48ac557 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -3,10 +3,11 @@ from PyQt5 import QtGui, QtCore, QtWidgets
import os
import math
import subprocess
+import signal
import threading
from queue import PriorityQueue
-from component import Component
+from component import Component, ComponentError
from toolkit.frame import BlankFrame
from toolkit.ffmpeg import testAudioStream
from toolkit import openPipe, checkOutput
@@ -14,6 +15,10 @@ 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
@@ -71,8 +76,8 @@ class Video:
self.frameBuffer.task_done()
def fillBuffer(self):
- pipe = openPipe(
- self.command, stdout=subprocess.PIPE,
+ self.pipe = openPipe(
+ self.command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL, bufsize=10**8
)
while True:
@@ -85,19 +90,11 @@ class Video:
if len(self.currentFrame) == 0:
self.frameBuffer.put((self.frameNo-1, self.lastFrame))
continue
- except AttributeError as e:
- self.parent.showMessage(
- msg='%s couldn\'t be loaded. '
- 'This is a fatal error.' % os.path.basename(
- self.videoPath
- ),
- detail=str(e),
- icon='Warning'
- )
- self.parent.stopVideo()
+ except AttributeError:
+ Video.threadError = ComponentError(self.component, 'video')
break
- self.currentFrame = pipe.stdout.read(self.chunkSize)
+ self.currentFrame = self.pipe.stdout.read(self.chunkSize)
if len(self.currentFrame) != 0:
self.frameBuffer.put((self.frameNo, self.currentFrame))
self.lastFrame = self.currentFrame
@@ -143,13 +140,11 @@ class Component(Component):
self.page.spinBox_volume.setEnabled(False)
super().update()
- def previewRender(self, previewWorker):
- width = int(self.settings.value('outputWidth'))
- height = int(self.settings.value('outputHeight'))
- self.updateChunksize(width, height)
- frame = self.getPreviewFrame(width, height)
+ def previewRender(self):
+ self.updateChunksize()
+ frame = self.getPreviewFrame(self.width, self.height)
if not frame:
- return BlankFrame(width, height)
+ return BlankFrame(self.width, self.height)
else:
return frame
@@ -184,23 +179,23 @@ class Component(Component):
def preFrameRender(self, **kwargs):
super().preFrameRender(**kwargs)
- width = int(self.settings.value('outputWidth'))
- height = int(self.settings.value('outputHeight'))
- self.blankFrame_ = BlankFrame(width, height)
- self.updateChunksize(width, height)
+ self.updateChunksize()
self.video = Video(
ffmpeg=self.core.FFMPEG_BIN, videoPath=self.videoPath,
- width=width, height=height, chunkSize=self.chunkSize,
+ 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
) if os.path.exists(self.videoPath) else None
- def frameRender(self, layerNo, frameNo):
- if self.video:
- return self.video.frame(frameNo)
- else:
- return self.blankFrame_
+ def frameRender(self, frameNo):
+ if Video.threadError is not None:
+ raise Video.threadError
+ return self.video.frame(frameNo)
+
+ def renderFinished(self):
+ self.video.pipe.stdout.close()
+ self.video.pipe.send_signal(signal.SIGINT)
def pickVideo(self):
imgDir = self.settings.value("componentDir", os.path.expanduser("~"))
@@ -230,20 +225,20 @@ class Component(Component):
'-vframes', '1',
]
pipe = openPipe(
- command, stdout=subprocess.PIPE,
+ command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL, bufsize=10**8
)
byteFrame = pipe.stdout.read(self.chunkSize)
- frame = finalizeFrame(self, byteFrame, width, height)
pipe.stdout.close()
- pipe.kill()
+ pipe.send_signal(signal.SIGINT)
+ frame = finalizeFrame(self, byteFrame, width, height)
return frame
- def updateChunksize(self, width, height):
+ def updateChunksize(self):
if self.scale != 100 and not self.distort:
- width, height = scale(self.scale, width, height, int)
- self.chunkSize = 4*width*height
+ width, height = scale(self.scale, self.width, self.height, int)
+ self.chunkSize = 4 * width * height
def command(self, arg):
if '=' in arg:
diff --git a/src/preview_thread.py b/src/preview_thread.py
index 9917e4b..0a6a856 100644
--- a/src/preview_thread.py
+++ b/src/preview_thread.py
@@ -59,7 +59,7 @@ class Worker(QtCore.QObject):
components = nextPreviewInformation["components"]
for component in reversed(components):
try:
- newFrame = component.previewRender(self)
+ newFrame = component.previewRender()
frame = Image.alpha_composite(
frame, newFrame
)
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index 8d63659..2fffc7b 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -252,7 +252,7 @@ def getAudioDuration(filename):
return duration
-def readAudioFile(filename, parent):
+def readAudioFile(filename, videoWorker):
'''
Creates the completeAudioArray given to components
and used to draw the classic visualizer.
@@ -296,8 +296,8 @@ def readAudioFile(filename, parent):
if lastPercent != percent:
string = 'Loading audio file: '+str(percent)+'%'
- parent.progressBarSetText.emit(string)
- parent.progressBarUpdate.emit(percent)
+ videoWorker.progressBarSetText.emit(string)
+ videoWorker.progressBarUpdate.emit(percent)
lastPercent = percent
diff --git a/src/video_thread.py b/src/video_thread.py
index c5a3c09..8c7d585 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -60,8 +60,7 @@ class Worker(QtCore.QObject):
audioI = self.compositeQueue.get()
bgI = int(audioI / self.sampleSize)
frame = None
- for compNo, comp in reversed(list(enumerate(self.components))):
- layerNo = len(self.components) - compNo - 1
+ for layerNo, comp in enumerate(reversed((self.components))):
if layerNo in self.staticComponents:
if self.staticComponents[layerNo] is None:
# this layer was merged into a following layer
@@ -76,10 +75,10 @@ class Worker(QtCore.QObject):
else:
# animated component
if frame is None: # bottom-most layer
- frame = comp.frameRender(compNo, bgI)
+ frame = comp.frameRender(bgI)
else:
frame = Image.alpha_composite(
- frame, comp.frameRender(compNo, bgI)
+ frame, comp.frameRender(bgI)
)
self.renderQueue.put([audioI, frame])
@@ -185,7 +184,7 @@ class Worker(QtCore.QObject):
break
if 'static' in compProps:
self.staticComponents[compNo] = \
- comp.frameRender(compNo, 0).copy()
+ comp.frameRender(0).copy()
if self.canceled:
if canceledByComponent:
@@ -290,8 +289,11 @@ class Worker(QtCore.QObject):
print(self.out_pipe.stderr.read())
self.out_pipe.stderr.close()
self.error = True
- # out_pipe.terminate() # don't terminate ffmpeg too early
self.out_pipe.wait()
+
+ for comp in reversed(self.components):
+ comp.renderFinished()
+
if self.canceled:
print("Export Canceled")
try:
--
cgit v1.2.3
From 6fc0398602c42a3d219ec92163c480c1833ab0c2 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 27 Jul 2017 18:43:02 -0400
Subject: quit if project doesn't exist when exporting from commandline
---
src/command.py | 4 +++-
src/core.py | 6 ++++--
2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/src/command.py b/src/command.py
index 74ca821..18f7408 100644
--- a/src/command.py
+++ b/src/command.py
@@ -64,7 +64,9 @@ class Command(QtCore.QObject):
)
if not projPath.endswith('.avp'):
projPath += '.avp'
- self.core.openProject(self, projPath)
+ success = self.core.openProject(self, projPath)
+ if not success:
+ quit(1)
self.core.selectedComponents = list(
reversed(self.core.selectedComponents))
self.core.componentListChanged()
diff --git a/src/core.py b/src/core.py
index b371d64..1c29774 100644
--- a/src/core.py
+++ b/src/core.py
@@ -214,7 +214,8 @@ class Core:
self.clearPreset(i)
if hasattr(loader, 'updateComponentTitle'):
loader.updateComponentTitle(i, modified)
-
+ self.openingProject = False
+ return True
except Exception:
errcode = 1
data = sys.exc_info()
@@ -234,7 +235,8 @@ class Core:
showCancel=False,
icon='Warning',
detail=msg)
- self.openingProject = False
+ self.openingProject = False
+ return False
def parseAvFile(self, filepath):
'''
--
cgit v1.2.3
From 6ecb6df23628de65c9efd8cac4810fdf74238c3d Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 27 Jul 2017 22:15:41 -0400
Subject: some minor bugfixes
---
src/component.py | 5 +++--
src/components/sound.py | 3 ---
src/components/video.py | 14 +++++++++-----
src/mainwindow.py | 2 +-
src/video_thread.py | 2 +-
5 files changed, 14 insertions(+), 12 deletions(-)
diff --git a/src/component.py b/src/component.py
index 1c5ccb3..03023e7 100644
--- a/src/component.py
+++ b/src/component.py
@@ -33,7 +33,7 @@ class ComponentMetaclass(type(QtCore.QObject)):
return func(self, *args, **kwargs)
except Exception as e:
try:
- if e.__name__.startswith('Component'):
+ if e.__class__.__name__.startswith('Component'):
raise
else:
raise ComponentError(self, 'renderer')
@@ -213,7 +213,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
image = BlankFrame(self.width, self.height)
return image
- def renderFinished(self):
+ def postFrameRender(self):
pass
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
@@ -456,4 +456,5 @@ class ComponentError(RuntimeError):
)
super().__init__(string)
+ caller.lockError(string)
caller._error.emit(string, detail)
diff --git a/src/components/sound.py b/src/components/sound.py
index aff43d3..26ecf93 100644
--- a/src/components/sound.py
+++ b/src/components/sound.py
@@ -21,9 +21,6 @@ class Component(Component):
'sound': None,
})
- def preFrameRender(self, **kwargs):
- pass
-
def properties(self):
props = ['static', 'audio']
if not os.path.exists(self.sound):
diff --git a/src/components/video.py b/src/components/video.py
index 48ac557..b2487c1 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -59,7 +59,7 @@ class Video:
self.thread = threading.Thread(
target=self.fillBuffer,
- name=self.__doc__
+ name='Video Frame-Fetcher'
)
self.thread.daemon = True
self.thread.start()
@@ -150,6 +150,10 @@ class Component(Component):
def properties(self):
props = []
+ if hasattr(self.parent, 'window'):
+ outputFile = self.parent.window.lineEdit_outputFile.text()
+ else:
+ outputFile = str(self.parent.args.output)
if not self.videoPath:
self.lockError("There is no video selected.")
@@ -157,9 +161,7 @@ class Component(Component):
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(
- self.parent.window.lineEdit_outputFile.text())):
+ elif os.path.realpath(self.videoPath) == os.path.realpath(outputFile):
self.lockError("Input and output paths match.")
if self.useAudio:
@@ -193,7 +195,7 @@ class Component(Component):
raise Video.threadError
return self.video.frame(frameNo)
- def renderFinished(self):
+ def postFrameRender(self):
self.video.pipe.stdout.close()
self.video.pipe.send_signal(signal.SIGINT)
@@ -238,6 +240,8 @@ class Component(Component):
def updateChunksize(self):
if self.scale != 100 and not self.distort:
width, height = scale(self.scale, self.width, self.height, int)
+ else:
+ width, height = self.width, self.height
self.chunkSize = 4 * width * height
def command(self, arg):
diff --git a/src/mainwindow.py b/src/mainwindow.py
index e478d19..070131c 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -54,7 +54,7 @@ class PreviewWindow(QtWidgets.QLabel):
def threadError(self, msg):
self.parent.showMessage(
msg=msg,
- icon='Warning',
+ icon='Critical',
parent=self
)
diff --git a/src/video_thread.py b/src/video_thread.py
index 8c7d585..32e8a38 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -292,7 +292,7 @@ class Worker(QtCore.QObject):
self.out_pipe.wait()
for comp in reversed(self.components):
- comp.renderFinished()
+ comp.postFrameRender()
if self.canceled:
print("Export Canceled")
--
cgit v1.2.3
From 6f8f178778c63f10b3bda42507c7d44f98884fcd Mon Sep 17 00:00:00 2001
From: tassaron
Date: Fri, 28 Jul 2017 22:15:25 -0400
Subject: move code into a new function for future expansion
---
src/toolkit/ffmpeg.py | 184 ++++++++++++++++++++++++++------------------------
1 file changed, 97 insertions(+), 87 deletions(-)
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index 2fffc7b..b8bc679 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -98,97 +98,13 @@ def createFfmpegCommand(inputFile, outputFile, components, duration=-1):
'-i', inputFile
]
- # Add extra audio inputs and any needed avfilters
- # NOTE: Global filters are currently hard-coded here for debugging use
- globalFilters = 0 # increase to add global filters
extraAudio = [
comp.audio for comp in components
if 'audio' in comp.properties()
]
- if extraAudio or globalFilters > 0:
- # Add -i options for extra input files
- extraFilters = {}
- for streamNo, params in enumerate(reversed(extraAudio)):
- extraInputFile, params = params
- ffmpegCommand.extend([
- '-t', safeDuration,
- # Tell ffmpeg about shorter clips (seemingly not needed)
- # streamDuration = getAudioDuration(extraInputFile)
- # if streamDuration and streamDuration > float(safeDuration)
- # else "{0:.3f}".format(streamDuration),
- '-i', extraInputFile
- ])
- # Construct dataset of extra filters we'll need to add later
- for ffmpegFilter in params:
- if streamNo + 2 not in extraFilters:
- extraFilters[streamNo + 2] = []
- extraFilters[streamNo + 2].append((
- ffmpegFilter, params[ffmpegFilter]
- ))
-
- # Start creating avfilters! Popen-style, so don't use semicolons;
- extraFilterCommand = []
-
- if globalFilters <= 0:
- # Dictionary of last-used tmp labels for a given stream number
- tmpInputs = {streamNo: -1 for streamNo in extraFilters}
- else:
- # Insert blank entries for global filters into extraFilters
- # so the per-stream filters know what input to source later
- for streamNo in range(len(extraAudio), 0, -1):
- if streamNo + 1 not in extraFilters:
- extraFilters[streamNo + 1] = []
- # Also filter the primary audio track
- extraFilters[1] = []
- tmpInputs = {
- streamNo: globalFilters - 1
- for streamNo in extraFilters
- }
-
- # Add the global filters!
- # NOTE: list length must = globalFilters, currently hardcoded
- if tmpInputs:
- extraFilterCommand.extend([
- '[%s:a] ashowinfo [%stmp0]' % (
- str(streamNo),
- str(streamNo)
- )
- for streamNo in tmpInputs
- ])
-
- # Now add the per-stream filters!
- for streamNo, paramList in extraFilters.items():
- for param in paramList:
- source = '[%s:a]' % str(streamNo) \
- if tmpInputs[streamNo] == -1 else \
- '[%stmp%s]' % (
- str(streamNo), str(tmpInputs[streamNo])
- )
- tmpInputs[streamNo] = tmpInputs[streamNo] + 1
- extraFilterCommand.append(
- '%s %s%s [%stmp%s]' % (
- source, param[0], param[1], str(streamNo),
- str(tmpInputs[streamNo])
- )
- )
-
- # Join all the filters together and combine into 1 stream
- extraFilterCommand = "; ".join(extraFilterCommand) + '; ' \
- if tmpInputs else ''
- ffmpegCommand.extend([
- '-filter_complex',
- extraFilterCommand +
- '%s amix=inputs=%s:duration=first [a]'
- % (
- "".join([
- '[%stmp%s]' % (str(i), tmpInputs[i])
- if i in extraFilters else '[%s:a]' % str(i)
- for i in range(1, len(extraAudio) + 2)
- ]),
- str(len(extraAudio) + 1)
- ),
- ])
-
+ segment = createAudioFilterCommand(extraAudio, safeDuration)
+ ffmpegCommand.extend(segment)
+ if segment:
# Only map audio from the filters, and video from the pipe
ffmpegCommand.extend([
'-map', '0:v',
@@ -214,6 +130,100 @@ def createFfmpegCommand(inputFile, outputFile, components, duration=-1):
return ffmpegCommand
+def createAudioFilterCommand(extraAudio, duration):
+ '''Add extra inputs and any needed filters to the main ffmpeg command.'''
+ # NOTE: Global filters are currently hard-coded here for debugging use
+ globalFilters = 0 # increase to add global filters
+
+ if not extraAudio and not globalFilters:
+ return []
+
+ ffmpegCommand = []
+ # Add -i options for extra input files
+ extraFilters = {}
+ for streamNo, params in enumerate(reversed(extraAudio)):
+ extraInputFile, params = params
+ ffmpegCommand.extend([
+ '-t', duration,
+ # Tell ffmpeg about shorter clips (seemingly not needed)
+ # streamDuration = getAudioDuration(extraInputFile)
+ # if streamDuration and streamDuration > float(safeDuration)
+ # else "{0:.3f}".format(streamDuration),
+ '-i', extraInputFile
+ ])
+ # Construct dataset of extra filters we'll need to add later
+ for ffmpegFilter in params:
+ if streamNo + 2 not in extraFilters:
+ extraFilters[streamNo + 2] = []
+ extraFilters[streamNo + 2].append((
+ ffmpegFilter, params[ffmpegFilter]
+ ))
+
+ # Start creating avfilters! Popen-style, so don't use semicolons;
+ extraFilterCommand = []
+
+ if globalFilters <= 0:
+ # Dictionary of last-used tmp labels for a given stream number
+ tmpInputs = {streamNo: -1 for streamNo in extraFilters}
+ else:
+ # Insert blank entries for global filters into extraFilters
+ # so the per-stream filters know what input to source later
+ for streamNo in range(len(extraAudio), 0, -1):
+ if streamNo + 1 not in extraFilters:
+ extraFilters[streamNo + 1] = []
+ # Also filter the primary audio track
+ extraFilters[1] = []
+ tmpInputs = {
+ streamNo: globalFilters - 1
+ for streamNo in extraFilters
+ }
+
+ # Add the global filters!
+ # NOTE: list length must = globalFilters, currently hardcoded
+ if tmpInputs:
+ extraFilterCommand.extend([
+ '[%s:a] ashowinfo [%stmp0]' % (
+ str(streamNo),
+ str(streamNo)
+ )
+ for streamNo in tmpInputs
+ ])
+
+ # Now add the per-stream filters!
+ for streamNo, paramList in extraFilters.items():
+ for param in paramList:
+ source = '[%s:a]' % str(streamNo) \
+ if tmpInputs[streamNo] == -1 else \
+ '[%stmp%s]' % (
+ str(streamNo), str(tmpInputs[streamNo])
+ )
+ tmpInputs[streamNo] = tmpInputs[streamNo] + 1
+ extraFilterCommand.append(
+ '%s %s%s [%stmp%s]' % (
+ source, param[0], param[1], str(streamNo),
+ str(tmpInputs[streamNo])
+ )
+ )
+
+ # Join all the filters together and combine into 1 stream
+ extraFilterCommand = "; ".join(extraFilterCommand) + '; ' \
+ if tmpInputs else ''
+ ffmpegCommand.extend([
+ '-filter_complex',
+ extraFilterCommand +
+ '%s amix=inputs=%s:duration=first [a]'
+ % (
+ "".join([
+ '[%stmp%s]' % (str(i), tmpInputs[i])
+ if i in extraFilters else '[%s:a]' % str(i)
+ for i in range(1, len(extraAudio) + 2)
+ ]),
+ str(len(extraAudio) + 1)
+ ),
+ ])
+ return ffmpegCommand
+
+
def testAudioStream(filename):
'''Test if an audio stream definitely exists'''
audioTestCommand = [
--
cgit v1.2.3
From c1457b6dad4640b17679dd802e372bd46a13d2a5 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 29 Jul 2017 13:08:28 -0400
Subject: starting work on Waveform component
split Video class out of Video component for reuse in Waveform
---
.gitignore | 2 +
src/component.py | 7 +-
src/components/video.py | 198 ++++++++-----------------------
src/components/waveform.py | 139 ++++++++++++++++++++++
src/components/waveform.ui | 283 +++++++++++++++++++++++++++++++++++++++++++++
src/toolkit/common.py | 37 ++++--
src/toolkit/ffmpeg.py | 99 ++++++++++++++++
src/video_thread.py | 2 +-
8 files changed, 607 insertions(+), 160 deletions(-)
create mode 100644 src/components/waveform.py
create mode 100644 src/components/waveform.ui
diff --git a/.gitignore b/.gitignore
index bfdd0e7..7cec615 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,5 @@ env/*
*.tar.*
*.exe
ffmpeg
+*.bak
+*~
diff --git a/src/component.py b/src/component.py
index 03023e7..fc8fbd3 100644
--- a/src/component.py
+++ b/src/component.py
@@ -197,7 +197,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
'''
Must call super() when subclassing
Triggered only before a video is exported (video_thread.py)
- self.worker = the video thread worker
+ self.audioFile = filepath to the main input audio file
self.completeAudioArray = a list of audio samples
self.sampleSize = number of audio samples per video frame
self.progressBarUpdate = signal to set progress bar number
@@ -436,7 +436,7 @@ class ComponentError(RuntimeError):
import sys
if sys.exc_info()[0] is not None:
string = (
- "%s component's %s encountered %s %s." % (
+ "%s component's %s encountered %s %s: %s" % (
caller.__class__.name,
name,
'an' if any([
@@ -444,12 +444,13 @@ class ComponentError(RuntimeError):
for vowel in ('A', 'I')
]) else 'a',
sys.exc_info()[0].__name__,
+ str(sys.exc_info()[1])
)
)
detail = formatTraceback(sys.exc_info()[2])
else:
string = name
- detail = "Methods:\n%s" % (
+ detail = "Attributes:\n%s" % (
"\n".join(
[m for m in dir(caller) if not m.startswith('_')]
)
diff --git a/src/components/video.py b/src/components/video.py
index b2487c1..d3460ff 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 toolkit.ffmpeg import testAudioStream, FfmpegVideo
+from toolkit import openPipe, closePipe, checkOutput, scale
class Component(Component):
@@ -182,22 +92,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 +129,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),
+ ]
+ command.extend(self.makeFfmpegFilter())
+ command.extend([
'-vcodec', 'rawvideo', '-',
'-ss', '90',
- '-vframes', '1',
- ]
+ '-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 +184,32 @@ class Component(Component):
print('Load a video:\n path=/filepath/to/video.mp4')
print('Using audio:\n path=/filepath/to/video.mp4 audio')
+ def finalizeFrame(self, imageData):
+ try:
+ if self.distort:
+ image = Image.frombytes(
+ 'RGBA',
+ (self.width, self.height),
+ imageData)
+ else:
+ image = Image.frombytes(
+ 'RGBA',
+ scale(self.scale, self.width, self.height, int),
+ imageData)
+
+ except ValueError:
+ print(
+ '### BAD VIDEO SELECTED ###\n'
+ 'Video will not export with these settings'
+ )
+ self.badVideo = True
+ return BlankFrame(self.width, self.height)
-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)
+ 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
+ self.badVideo = False
+ return frame
diff --git a/src/components/waveform.py b/src/components/waveform.py
new file mode 100644
index 0000000..487a3bb
--- /dev/null
+++ b/src/components/waveform.py
@@ -0,0 +1,139 @@
+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, ComponentError
+from toolkit.frame import BlankFrame
+from toolkit import openPipe, checkOutput, rgbFromString
+from toolkit.ffmpeg import FfmpegVideo
+
+
+class Component(Component):
+ name = 'Waveform'
+ version = '1.0.0'
+
+ def widget(self, *args):
+ self.color = (255, 255, 255)
+ super().widget(*args)
+
+ self.page.lineEdit_color.setText('%s,%s,%s' % self.color)
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" \
+ % QColor(*self.color1).name()
+ self.page.lineEdit_color.setStylesheet(btnStyle)
+ self.page.pushButton_color.clicked.connect(lambda: self.pickColor())
+
+ self.trackWidgets(
+ {
+ 'mode': self.page.comboBox_mode,
+ 'x': self.page.spinBox_x,
+ 'y': self.page.spinBox_y,
+ 'mirror': self.page.checkBox_mirror,
+ 'scale': self.page.spinBox_scale,
+ }
+ )
+
+ def update(self):
+ self.color = rgbFromString(self.page.lineEdit_color.text())
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" \
+ % QColor(*self.color).name()
+ self.page.pushButton_color.setStyleSheet(btnStyle)
+ super().update()
+
+ 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()
+ self.video = FfmpegVideo(
+ inputPath=self.audioFile,
+ filter_=makeFfmpegFilter(),
+ width=self.width, height=self.height,
+ 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 finalizeFrame(self.video.frame(frameNo))
+
+ def postFrameRender(self):
+ closePipe(self.video.pipe)
+
+ def getPreviewFrame(self, width, height):
+ inputFile = self.parent.window.lineEdit_audioFile.text()
+ if not inputFile or not os.path.exists(inputFile):
+ return
+
+ command = [
+ self.core.FFMPEG_BIN,
+ '-thread_queue_size', '512',
+ '-i', inputFile,
+ '-f', 'image2pipe',
+ '-pix_fmt', 'rgba',
+ ]
+ command.extend(self.makeFfmpegFilter())
+ command.extend([
+ '-vcodec', '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)
+ closePipe(pipe)
+
+ frame = finalizeFrame(self, byteFrame, width, height)
+ return frame
+
+ def makeFfmpegFilter(self):
+ w, h = scale(self.scale, self.width, self.height, str)
+ return [
+ '-filter_complex',
+ '[0:a] showwaves=s=%sx%s:mode=%s,format=rgba [v]' % (
+ w, h, self.mode,
+ ),
+ '-map', '[v]',
+ '-map', '0:a',
+ ]
+
+ def updateChunksize(self):
+ if self.scale != 100:
+ width, height = scale(self.scale, self.width, self.height, int)
+ else:
+ width, height = self.width, self.height
+ self.chunkSize = 4 * width * height
+
+
+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):
+ # frombytes goes here
+ if self.scale != 100 \
+ or self.x != 0 or self.y != 0:
+ frame = BlankFrame(width, 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..5d62150
--- /dev/null
+++ b/src/components/waveform.ui
@@ -0,0 +1,283 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 586
+ 197
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 197
+
+
+
+ Form
+
+
+
-
+
+
+ 4
+
+
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 31
+ 0
+
+
+
+ Mode
+
+
+
+ -
+
+
-
+
+ Cline
+
+
+ -
+
+ Line
+
+
+ -
+
+ P2p
+
+
+ -
+
+ Point
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ X
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+ -10000
+
+
+ 10000
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Y
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 0
+ 0
+
+
+
+ -10000
+
+
+ 10000
+
+
+ 0
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Wave Color
+
+
+
+ -
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+ false
+
+
+ false
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Mirror
+
+
+
+ -
+
+
+ Scale
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ QAbstractSpinBox::UpDownArrows
+
+
+ %
+
+
+ 10
+
+
+ 400
+
+
+ 100
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
diff --git a/src/toolkit/common.py b/src/toolkit/common.py
index 251a2c1..128ed08 100644
--- a/src/toolkit/common.py
+++ b/src/toolkit/common.py
@@ -6,9 +6,22 @@ import string
import os
import sys
import subprocess
+import signal
+import math
from collections import OrderedDict
+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 badName(name):
'''Returns whether a name contains non-alphanumeric chars'''
return any([letter in string.punctuation for letter in name])
@@ -34,29 +47,35 @@ def appendUppercase(lst):
lst.append(form.upper())
return lst
-
-def hideCmdWin(func):
- ''' Stops CMD window from appearing on Windows.
- Adapted from here: http://code.activestate.com/recipes/409002/
- '''
- def decorator(commandList, **kwargs):
+def pipeWrapper(func):
+ '''A decorator to insert proper kwargs into Popen objects.'''
+ def pipeWrapper(commandList, **kwargs):
if sys.platform == 'win32':
+ # Stop CMD window from appearing on Windows
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
kwargs['startupinfo'] = startupinfo
+
+ if 'bufsize' not in kwargs:
+ kwargs['bufsize'] = 10**8
+ if 'stdin' not in kwargs:
+ kwargs['stdin'] = subprocess.DEVNULL
return func(commandList, **kwargs)
- return decorator
+ return pipeWrapper
-@hideCmdWin
+@pipeWrapper
def checkOutput(commandList, **kwargs):
return subprocess.check_output(commandList, **kwargs)
-@hideCmdWin
+@pipeWrapper
def openPipe(commandList, **kwargs):
return subprocess.Popen(commandList, **kwargs)
+def closePipe(pipe):
+ pipe.stdout.close()
+ pipe.send_signal(signal.SIGINT)
def disableWhenEncoding(func):
def decorator(self, *args, **kwargs):
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index b8bc679..fea9d4e 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -5,11 +5,110 @@ import numpy
import sys
import os
import subprocess
+import threading
+from queue import PriorityQueue
import core
from toolkit.common import checkOutput, openPipe
+class FfmpegVideo:
+ '''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 = [
+ 'inputPath',
+ 'filter_',
+ 'width',
+ 'height',
+ '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'
+ self.map_ = None
+
+ if 'loopVideo' in kwargs and kwargs['loopVideo']:
+ self.loopValue = '-1'
+ else:
+ self.loopValue = '0'
+ if 'filter_' in kwargs:
+ if kwargs['filter_'][0] != '-filter_complex':
+ kwargs['filter_'].insert(0, '-filter_complex')
+ else:
+ kwargs['filter_'] = None
+
+ self.command = [
+ core.Core.FFMPEG_BIN,
+ '-thread_queue_size', '512',
+ '-r', str(self.frameRate),
+ '-stream_loop', self.loopValue,
+ '-i', self.inputPath,
+ '-f', 'image2pipe',
+ '-pix_fmt', 'rgba',
+ ]
+ if type(kwargs['filter_']) is list:
+ self.command.extend(
+ kwargs['filter_']
+ )
+ self.command.extend([
+ '-vcodec', 'rawvideo', '-',
+ ])
+
+ self.frameBuffer = PriorityQueue()
+ self.frameBuffer.maxsize = self.frameRate
+ self.finishedFrames = {}
+
+ self.thread = threading.Thread(
+ target=self.fillBuffer,
+ name='FFmpeg 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 image
+
+ 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
+
+
def findFfmpeg():
if getattr(sys, 'frozen', False):
# The application is frozen
diff --git a/src/video_thread.py b/src/video_thread.py
index 32e8a38..f27ec21 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -153,7 +153,7 @@ class Worker(QtCore.QObject):
for compNo, comp in enumerate(reversed(self.components)):
try:
comp.preFrameRender(
- worker=self,
+ audioFile=self.inputFile,
completeAudioArray=self.completeAudioArray,
sampleSize=self.sampleSize,
progressBarUpdate=self.progressBarUpdate,
--
cgit v1.2.3
From 1297af61c9ce00b6dd76f8ec690baedf5bf887c7 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 29 Jul 2017 20:27:46 -0400
Subject: waveform component is working, preview is glitchy
---
src/components/original.py | 3 +
src/components/video.py | 10 ++--
src/components/waveform.py | 134 +++++++++++++++++++++++++++++++--------------
src/components/waveform.ui | 95 +++++++++++++++++++++++++++++++-
src/toolkit/common.py | 21 -------
src/toolkit/ffmpeg.py | 30 ++++++++--
src/toolkit/frame.py | 12 ++++
src/video_thread.py | 38 +++++++++----
8 files changed, 256 insertions(+), 87 deletions(-)
diff --git a/src/components/original.py b/src/components/original.py
index 3d1a574..621af6f 100644
--- a/src/components/original.py
+++ b/src/components/original.py
@@ -18,6 +18,9 @@ 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
diff --git a/src/components/video.py b/src/components/video.py
index d3460ff..6cd16e5 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -4,10 +4,10 @@ import os
import math
import subprocess
-from component import Component, ComponentError
-from toolkit.frame import BlankFrame
-from toolkit.ffmpeg import testAudioStream, FfmpegVideo
-from toolkit import openPipe, closePipe, checkOutput, scale
+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):
@@ -132,7 +132,7 @@ class Component(Component):
]
command.extend(self.makeFfmpegFilter())
command.extend([
- '-vcodec', 'rawvideo', '-',
+ '-codec:v', 'rawvideo', '-',
'-ss', '90',
'-frames:v', '1',
])
diff --git a/src/components/waveform.py b/src/components/waveform.py
index 487a3bb..375b3fc 100644
--- a/src/components/waveform.py
+++ b/src/components/waveform.py
@@ -5,10 +5,10 @@ import os
import math
import subprocess
-from component import Component, ComponentError
-from toolkit.frame import BlankFrame
-from toolkit import openPipe, checkOutput, rgbFromString
-from toolkit.ffmpeg import FfmpegVideo
+from component import Component
+from toolkit.frame import BlankFrame, scale
+from toolkit import checkOutput, rgbFromString, pickColor
+from toolkit.ffmpeg import openPipe, closePipe, getAudioDuration, FfmpegVideo
class Component(Component):
@@ -21,17 +21,27 @@ class Component(Component):
self.page.lineEdit_color.setText('%s,%s,%s' % self.color)
btnStyle = "QPushButton { background-color : %s; outline: none; }" \
- % QColor(*self.color1).name()
- self.page.lineEdit_color.setStylesheet(btnStyle)
+ % QColor(*self.color).name()
+ self.page.pushButton_color.setStyleSheet(btnStyle)
self.page.pushButton_color.clicked.connect(lambda: self.pickColor())
+ self.page.spinBox_scale.valueChanged.connect(self.updateChunksize)
+
+ if hasattr(self.parent, 'window'):
+ self.parent.window.lineEdit_audioFile.textChanged.connect(
+ self.update
+ )
self.trackWidgets(
{
'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,
}
)
@@ -42,6 +52,26 @@ class Component(Component):
self.page.pushButton_color.setStyleSheet(btnStyle)
super().update()
+ def loadPreset(self, pr, *args):
+ super().loadPreset(pr, *args)
+
+ self.page.lineEdit_color.setText('%s,%s,%s' % pr['color'])
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" \
+ % QColor(*pr['color']).name()
+ self.page.pushButton_color.setStyleSheet(btnStyle)
+
+ def savePreset(self):
+ saveValueStore = super().savePreset()
+ saveValueStore['color'] = self.color
+ return saveValueStore
+
+ def pickColor(self):
+ RGBstring, btnStyle = pickColor()
+ if not RGBstring:
+ return
+ self.page.lineEdit_color.setText(RGBstring)
+ self.page.pushButton_color.setStyleSheet(btnStyle)
+
def previewRender(self):
self.updateChunksize()
frame = self.getPreviewFrame(self.width, self.height)
@@ -53,10 +83,11 @@ class Component(Component):
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_=makeFfmpegFilter(),
- width=self.width, height=self.height,
+ filter_=self.makeFfmpegFilter(),
+ width=w, height=h,
chunkSize=self.chunkSize,
frameRate=int(self.settings.value("outputFrameRate")),
parent=self.parent, component=self,
@@ -65,7 +96,7 @@ class Component(Component):
def frameRender(self, frameNo):
if FfmpegVideo.threadError is not None:
raise FfmpegVideo.threadError
- return finalizeFrame(self.video.frame(frameNo))
+ return self.finalizeFrame(self.video.frame(frameNo))
def postFrameRender(self):
closePipe(self.video.pipe)
@@ -74,18 +105,25 @@ class Component(Component):
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', inputFile,
'-f', 'image2pipe',
'-pix_fmt', 'rgba',
]
- command.extend(self.makeFfmpegFilter())
+ command.extend(self.makeFfmpegFilter(preview=True, startPt=startPt))
command.extend([
- '-vcodec', 'rawvideo', '-',
- '-ss', '90',
+ '-an',
+ '-s:v', '%sx%s' % scale(self.scale, self.width, self.height, str),
+ '-codec:v', 'rawvideo', '-',
'-frames:v', '1',
])
pipe = openPipe(
@@ -95,45 +133,57 @@ class Component(Component):
byteFrame = pipe.stdout.read(self.chunkSize)
closePipe(pipe)
- frame = finalizeFrame(self, byteFrame, width, height)
+ frame = self.finalizeFrame(byteFrame)
return frame
- def makeFfmpegFilter(self):
+ 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)
+
return [
'-filter_complex',
- '[0:a] showwaves=s=%sx%s:mode=%s,format=rgba [v]' % (
- w, h, self.mode,
+ '[0:a] %s%s'
+ 'showwaves=r=30:s=%sx%s:mode=%s:colors=%s@%s:scale=%s%s%s [v1]; '
+ '[v1] scale=%s:%s%s [v]' % (
+ 'compand=gain=2,' if self.compress else '',
+ 'aformat=channel_layouts=mono,' if self.mono else '',
+ self.settings.value("outputWidth"),
+ self.settings.value("outputHeight"),
+ str(self.page.comboBox_mode.currentText()).lower(),
+ hexcolor, opacity, amplitude,
+ ', 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 + 1) if preview else '',
),
'-map', '[v]',
- '-map', '0:a',
]
def updateChunksize(self):
- if self.scale != 100:
- width, height = scale(self.scale, self.width, self.height, int)
- else:
- width, height = self.width, self.height
+ width, height = scale(self.scale, self.width, self.height, int)
self.chunkSize = 4 * width * height
-
-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):
- # frombytes goes here
- if self.scale != 100 \
- or self.x != 0 or self.y != 0:
- frame = BlankFrame(width, height)
- frame.paste(image, box=(self.x, self.y))
- else:
- frame = image
- return frame
+ def finalizeFrame(self, imageData):
+ image = Image.frombytes(
+ 'RGBA',
+ scale(self.scale, self.width, self.height, int),
+ imageData
+ )
+ 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
index 5d62150..0e40380 100644
--- a/src/components/waveform.ui
+++ b/src/components/waveform.ui
@@ -226,9 +226,31 @@
-
-
+
- Mirror
+ Opacity
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ QAbstractSpinBox::UpDownArrows
+
+
+ %
+
+
+ 10
+
+
+ 400
+
+
+ 100
@@ -263,6 +285,75 @@
+ -
+
+
-
+
+
+ Compress
+
+
+
+ -
+
+
+ Mono
+
+
+
+ -
+
+
+ Mirror
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Amplitude
+
+
+
+ -
+
+
-
+
+ Linear
+
+
+ -
+
+ Logarithmic
+
+
+ -
+
+ Square root
+
+
+ -
+
+ Cubic root
+
+
+
+
+
+
-
diff --git a/src/toolkit/common.py b/src/toolkit/common.py
index 128ed08..5d424e0 100644
--- a/src/toolkit/common.py
+++ b/src/toolkit/common.py
@@ -6,22 +6,9 @@ import string
import os
import sys
import subprocess
-import signal
-import math
from collections import OrderedDict
-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 badName(name):
'''Returns whether a name contains non-alphanumeric chars'''
return any([letter in string.punctuation for letter in name])
@@ -69,14 +56,6 @@ def checkOutput(commandList, **kwargs):
return subprocess.check_output(commandList, **kwargs)
-@pipeWrapper
-def openPipe(commandList, **kwargs):
- return subprocess.Popen(commandList, **kwargs)
-
-def closePipe(pipe):
- pipe.stdout.close()
- pipe.send_signal(signal.SIGINT)
-
def disableWhenEncoding(func):
def decorator(self, *args, **kwargs):
if self.encoding:
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index fea9d4e..e37282f 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -6,10 +6,12 @@ import sys
import os
import subprocess
import threading
+import signal
from queue import PriorityQueue
import core
-from toolkit.common import checkOutput, openPipe
+from toolkit.common import checkOutput, pipeWrapper
+from component import ComponentError
class FfmpegVideo:
@@ -60,7 +62,8 @@ class FfmpegVideo:
kwargs['filter_']
)
self.command.extend([
- '-vcodec', 'rawvideo', '-',
+ '-s:v', '%sx%s' % (self.width, self.height),
+ '-codec:v', 'rawvideo', '-',
])
self.frameBuffer = PriorityQueue()
@@ -85,9 +88,11 @@ class FfmpegVideo:
self.frameBuffer.task_done()
def fillBuffer(self):
+ import sys
+ print(self.command)
self.pipe = openPipe(
self.command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
- stderr=subprocess.DEVNULL, bufsize=10**8
+ stderr=sys.__stdout__, bufsize=10**8
)
while True:
if self.parent.canceled:
@@ -100,7 +105,7 @@ class FfmpegVideo:
self.frameBuffer.put((self.frameNo-1, self.lastFrame))
continue
except AttributeError:
- Video.threadError = ComponentError(self.component, 'video')
+ FfmpegVideo.threadError = ComponentError(self.component, 'video')
break
self.currentFrame = self.pipe.stdout.read(self.chunkSize)
@@ -109,6 +114,16 @@ class FfmpegVideo:
self.lastFrame = self.currentFrame
+@pipeWrapper
+def openPipe(commandList, **kwargs):
+ return subprocess.Popen(commandList, **kwargs)
+
+
+def closePipe(pipe):
+ pipe.stdout.close()
+ pipe.send_signal(signal.SIGINT)
+
+
def findFfmpeg():
if getattr(sys, 'frozen', False):
# The application is frozen
@@ -347,7 +362,12 @@ def getAudioDuration(filename):
except subprocess.CalledProcessError as ex:
fileInfo = ex.output
- info = fileInfo.decode("utf-8").split('\n')
+ try:
+ info = fileInfo.decode("utf-8").split('\n')
+ except UnicodeDecodeError as e:
+ print('Unicode error:', str(e))
+ return False
+
for line in info:
if 'Duration' in line:
d = line.split(',')[0]
diff --git a/src/toolkit/frame.py b/src/toolkit/frame.py
index b66e037..f42d4c9 100644
--- a/src/toolkit/frame.py
+++ b/src/toolkit/frame.py
@@ -6,6 +6,7 @@ from PIL import Image
from PIL.ImageQt import ImageQt
import sys
import os
+import math
import core
@@ -41,6 +42,17 @@ class PaintColor(QtGui.QColor):
super().__init__(b, g, r, a)
+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 defaultSize(framefunc):
'''Makes width/height arguments optional'''
def decorator(*args):
diff --git a/src/video_thread.py b/src/video_thread.py
index f27ec21..5963def 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -19,9 +19,11 @@ import time
import signal
from component import ComponentError
-from toolkit import openPipe
-from toolkit.ffmpeg import readAudioFile, createFfmpegCommand
from toolkit.frame import Checkerboard
+from toolkit.ffmpeg import (
+ openPipe, readAudioFile,
+ getAudioDuration, createFfmpegCommand
+)
class Worker(QtCore.QObject):
@@ -132,15 +134,24 @@ class Worker(QtCore.QObject):
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
# READ AUDIO, INITIALIZE COMPONENTS, OPEN A PIPE TO FFMPEG
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
-
- self.progressBarSetText.emit("Loading audio file...")
- audioFileTraits = readAudioFile(
- self.inputFile, self
- )
- if audioFileTraits is None:
- self.cancelExport()
- return
- self.completeAudioArray, duration = audioFileTraits
+ if any([
+ True if 'pcm' in comp.properties() else False
+ for comp in self.components
+ ]):
+ self.progressBarSetText.emit("Loading audio file...")
+ audioFileTraits = readAudioFile(
+ self.inputFile, self
+ )
+ if audioFileTraits is None:
+ self.cancelExport()
+ return
+ self.completeAudioArray, duration = audioFileTraits
+ else:
+ duration = getAudioDuration(self.inputFile)
+ class FakeList:
+ def __len__(self):
+ return int((duration * 44100) + 44100) - 1470
+ self.completeAudioArray = FakeList()
self.progressBarUpdate.emit(0)
self.progressBarSetText.emit("Starting components...")
@@ -284,7 +295,10 @@ class Worker(QtCore.QObject):
numpy.seterr(all='print')
- self.out_pipe.stdin.close()
+ try:
+ self.out_pipe.stdin.close()
+ except BrokenPipeError:
+ print('Broken pipe to ffmpeg!')
if self.out_pipe.stderr is not None:
print(self.out_pipe.stderr.read())
self.out_pipe.stderr.close()
--
cgit v1.2.3
From db1ea1fc4edf19589e82171d48c417742c61c74b Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 29 Jul 2017 23:45:37 -0400
Subject: generic preview sound for waveform component
with secret preference to use the audio file again
---
src/component.py | 2 +-
src/components/waveform.py | 38 +++++++++++++++++++++++++-------------
src/core.py | 1 +
src/mainwindow.py | 2 ++
src/toolkit/ffmpeg.py | 14 ++++++++++----
5 files changed, 39 insertions(+), 18 deletions(-)
diff --git a/src/component.py b/src/component.py
index fc8fbd3..6d49406 100644
--- a/src/component.py
+++ b/src/component.py
@@ -427,7 +427,7 @@ class ComponentError(RuntimeError):
ComponentError.prevErrors.insert(0, name)
curTime = time.time()
if name in ComponentError.prevErrors[1:] \
- and curTime - ComponentError.lastTime < 0.2:
+ and curTime - ComponentError.lastTime < 1.0:
# Don't create multiple windows for quickly repeated messages
return
ComponentError.lastTime = time.time()
diff --git a/src/components/waveform.py b/src/components/waveform.py
index 375b3fc..b4b19e9 100644
--- a/src/components/waveform.py
+++ b/src/components/waveform.py
@@ -90,7 +90,7 @@ class Component(Component):
width=w, height=h,
chunkSize=self.chunkSize,
frameRate=int(self.settings.value("outputFrameRate")),
- parent=self.parent, component=self,
+ parent=self.parent, component=self, debug=True,
)
def frameRender(self, frameNo):
@@ -102,20 +102,25 @@ class Component(Component):
closePipe(self.video.pipe)
def getPreviewFrame(self, width, height):
- 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
+ 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', inputFile,
+ '-i',
+ os.path.join(self.core.wd, 'background.png')
+ if genericPreview else inputFile,
'-f', 'image2pipe',
'-pix_fmt', 'rgba',
]
@@ -148,13 +153,19 @@ class Component(Component):
amplitude = 'cbrt'
hexcolor = QColor(*self.color).name()
opacity = "{0:.1f}".format(self.opacity / 100)
+ genericPreview = self.settings.value("pref_genericPreview")
return [
'-filter_complex',
- '[0:a] %s%s'
+ '%s%s%s'
'showwaves=r=30:s=%sx%s:mode=%s:colors=%s@%s:scale=%s%s%s [v1]; '
- '[v1] scale=%s:%s%s [v]' % (
- 'compand=gain=2,' if self.compress else '',
+ '[v1] scale=%s:%s%s,setpts=2.0*PTS [v]' % (
+ 'aevalsrc=sin(1*2*PI*t)*sin(880*2*PI*t),'
+ if preview and genericPreview else '[0:a] ',
+ 'compand=.3|.3:1|1:-90/-60|-60/-40|-40/-30|-20/-20:6:0:-90:0.2'
+ ',' if self.compress and not preview else (
+ 'compand=gain=5,' if self.compress else ''
+ ),
'aformat=channel_layouts=mono,' if self.mono else '',
self.settings.value("outputWidth"),
self.settings.value("outputHeight"),
@@ -165,7 +176,8 @@ class Component(Component):
) if self.mode < 2 else '',
', hflip' if self.mirror else'',
w, h,
- ', trim=duration=%s' % "{0:.3f}".format(startPt + 1) if preview else '',
+ ', trim=duration=%s' % "{0:.3f}".format(startPt + 1)
+ if preview else '',
),
'-map', '[v]',
]
diff --git a/src/core.py b/src/core.py
index 1c29774..24bf097 100644
--- a/src/core.py
+++ b/src/core.py
@@ -506,6 +506,7 @@ class Core:
"outputContainer": "MP4",
"projectDir": os.path.join(cls.dataDir, 'projects'),
"pref_insertCompAtTop": True,
+ "pref_genericPreview": True,
}
for parm, value in defaultSettings.items():
diff --git a/src/mainwindow.py b/src/mainwindow.py
index 070131c..a97081e 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -791,6 +791,8 @@ class MainWindow(QtWidgets.QMainWindow):
field.blockSignals(True)
field.setText('')
field.blockSignals(False)
+ self.progressBarUpdated(0)
+ self.progressBarSetText('')
@disableWhenEncoding
def createNewProject(self, prompt=True):
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index e37282f..4ea2863 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -37,6 +37,7 @@ class FfmpegVideo:
self.frameNo = -1
self.currentFrame = 'None'
self.map_ = None
+ self.debug = False
if 'loopVideo' in kwargs and kwargs['loopVideo']:
self.loopValue = '-1'
@@ -47,6 +48,8 @@ class FfmpegVideo:
kwargs['filter_'].insert(0, '-filter_complex')
else:
kwargs['filter_'] = None
+ if 'debug' in kwargs:
+ self.debug = True
self.command = [
core.Core.FFMPEG_BIN,
@@ -62,7 +65,6 @@ class FfmpegVideo:
kwargs['filter_']
)
self.command.extend([
- '-s:v', '%sx%s' % (self.width, self.height),
'-codec:v', 'rawvideo', '-',
])
@@ -88,11 +90,15 @@ class FfmpegVideo:
self.frameBuffer.task_done()
def fillBuffer(self):
- import sys
- print(self.command)
+ if self.debug:
+ print(" ".join([word for word in self.command]))
+ err = sys.__stdout__
+ else:
+ err = subprocess.DEVNULL
+
self.pipe = openPipe(
self.command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
- stderr=sys.__stdout__, bufsize=10**8
+ stderr=err, bufsize=10**8
)
while True:
if self.parent.canceled:
--
cgit v1.2.3
From b6b45d12702f18f041acf65b0d5e34714835ecb4 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 30 Jul 2017 13:04:02 -0400
Subject: added Spectrum component with many options
tweaked Waveform, added some ffmpeg logging, made generic widget functions
---
src/component.py | 54 ++---
src/components/spectrum.py | 239 +++++++++++++++++++
src/components/spectrum.ui | 582 +++++++++++++++++++++++++++++++++++++++++++++
src/components/waveform.py | 48 ++--
src/components/waveform.ui | 21 +-
src/mainwindow.py | 2 +-
src/toolkit/common.py | 43 ++++
src/toolkit/ffmpeg.py | 41 ++--
8 files changed, 959 insertions(+), 71 deletions(-)
create mode 100644 src/components/spectrum.py
create mode 100644 src/components/spectrum.ui
diff --git a/src/component.py b/src/component.py
index 6d49406..1a5a5a4 100644
--- a/src/component.py
+++ b/src/component.py
@@ -4,9 +4,11 @@
'''
from PyQt5 import uic, QtCore, QtWidgets
import os
+import sys
import time
from toolkit.frame import BlankFrame
+from toolkit import getWidgetValue, setWidgetValue, connectWidget
class ComponentMetaclass(type(QtCore.QObject)):
@@ -273,14 +275,9 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
widgets['spinBox'].extend(
self.page.findChildren(QtWidgets.QDoubleSpinBox)
)
- for widget in widgets['lineEdit']:
- widget.textChanged.connect(self.update)
- for widget in widgets['checkBox']:
- widget.stateChanged.connect(self.update)
- for widget in widgets['spinBox']:
- widget.valueChanged.connect(self.update)
- for widget in widgets['comboBox']:
- widget.currentIndexChanged.connect(self.update)
+ for widgetList in widgets.values():
+ for widget in widgetList:
+ connectWidget(widget, self.update)
def update(self):
'''
@@ -289,15 +286,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
Call super() at the END if you need to subclass this.
'''
for attr, widget in self._trackedWidgets.items():
- if type(widget) == QtWidgets.QLineEdit:
- setattr(self, attr, widget.text())
- elif type(widget) == QtWidgets.QSpinBox \
- or type(widget) == QtWidgets.QDoubleSpinBox:
- setattr(self, attr, widget.value())
- elif type(widget) == QtWidgets.QCheckBox:
- setattr(self, attr, widget.isChecked())
- elif type(widget) == QtWidgets.QComboBox:
- setattr(self, attr, widget.currentIndex())
+ setattr(self, attr, getWidgetValue(widget))
if not self.core.openingProject:
self.parent.drawPreview()
saveValueStore = self.savePreset()
@@ -313,19 +302,10 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
self.currentPreset = presetName \
if presetName is not None else presetDict['preset']
for attr, widget in self._trackedWidgets.items():
- val = presetDict[
- attr if attr not in self._presetNames
+ key = attr if attr not in self._presetNames \
else self._presetNames[attr]
- ]
- if type(widget) == QtWidgets.QLineEdit:
- widget.setText(val)
- elif type(widget) == QtWidgets.QSpinBox \
- or type(widget) == QtWidgets.QDoubleSpinBox:
- widget.setValue(val)
- elif type(widget) == QtWidgets.QCheckBox:
- widget.setChecked(val)
- elif type(widget) == QtWidgets.QComboBox:
- widget.setCurrentIndex(val)
+ val = presetDict[key]
+ setWidgetValue(widget, val)
def savePreset(self):
saveValueStore = {}
@@ -420,24 +400,30 @@ class ComponentError(RuntimeError):
prevErrors = []
lastTime = time.time()
- def __init__(self, caller, name):
- print('##### ComponentError by %s: %s' % (caller.name, name))
+ def __init__(self, caller, name, msg=None):
+ if msg is None and sys.exc_info()[0] is not None:
+ msg = str(sys.exc_info()[1])
+ else:
+ msg = 'Unknown error.'
+ print("##### ComponentError by %s's %s: %s" % (
+ caller.name, name, msg))
+
+ # Don't create multiple windows for quickly repeated messages
if len(ComponentError.prevErrors) > 1:
ComponentError.prevErrors.pop()
ComponentError.prevErrors.insert(0, name)
curTime = time.time()
if name in ComponentError.prevErrors[1:] \
and curTime - ComponentError.lastTime < 1.0:
- # Don't create multiple windows for quickly repeated messages
return
ComponentError.lastTime = time.time()
from toolkit import formatTraceback
- import sys
if sys.exc_info()[0] is not None:
string = (
- "%s component's %s encountered %s %s: %s" % (
+ "%s component (#%s): %s encountered %s %s: %s" % (
caller.__class__.name,
+ str(caller.compPos),
name,
'an' if any([
sys.exc_info()[0].__name__.startswith(vowel)
diff --git a/src/components/spectrum.py b/src/components/spectrum.py
new file mode 100644
index 0000000..261d9cc
--- /dev/null
+++ b/src/components/spectrum.py
@@ -0,0 +1,239 @@
+from PIL import Image
+from PyQt5 import QtGui, QtCore, QtWidgets
+from PyQt5.QtGui import QColor
+import os
+import math
+import subprocess
+import time
+
+from component import Component
+from toolkit.frame import BlankFrame, scale
+from toolkit import checkOutput, rgbFromString, pickColor, connectWidget
+from toolkit.ffmpeg import (
+ openPipe, closePipe, getAudioDuration, FfmpegVideo, exampleSound
+)
+
+
+class Component(Component):
+ name = 'Spectrum'
+ version = '1.0.0'
+
+ def widget(self, *args):
+ self.color = (255, 255, 255)
+ self.previewFrame = None
+ super().widget(*args)
+ 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,
+ '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,
+ 'color': self.page.comboBox_color,
+ 'compress': self.page.checkBox_compress,
+ 'mono': self.page.checkBox_mono,
+ }
+ )
+ for widget in self._trackedWidgets.values():
+ connectWidget(widget, lambda: self.changed())
+
+ def changed(self):
+ self.changedOptions = True
+
+ def update(self):
+ count = self.page.stackedWidget.count()
+ i = self.page.comboBox_filterType.currentIndex()
+ self.page.stackedWidget.setCurrentIndex(i if i < count else count - 1)
+ 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)
+ 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'
+ color = self.page.comboBox_color.currentText().lower()
+ genericPreview = self.settings.value("pref_genericPreview")
+
+ if self.filterType == 0: # Spectrum
+ filter_ = (
+ 'showspectrum=s=%sx%s:slide=scroll:win_func=%s:'
+ 'color=%s:scale=%s' % (
+ self.settings.value("outputWidth"),
+ self.settings.value("outputHeight"),
+ self.page.comboBox_window.currentText(),
+ color, amplitude,
+ )
+ )
+ elif self.filterType == 1: # Histogram
+ filter_ = (
+ 'ahistogram=r=%s:s=%sx%s:dmode=separate' % (
+ self.settings.value("outputFrameRate"),
+ self.settings.value("outputWidth"),
+ self.settings.value("outputHeight"),
+ )
+ )
+ elif self.filterType == 2: # Vector Scope
+ filter_ = (
+ 'avectorscope=s=%sx%s:draw=line:m=polar:scale=log' % (
+ self.settings.value("outputWidth"),
+ self.settings.value("outputHeight"),
+ )
+ )
+ elif self.filterType == 3: # Musical Scale
+ filter_ = (
+ 'showcqt=r=%s:s=%sx%s:count=30:text=0' % (
+ self.settings.value("outputFrameRate"),
+ self.settings.value("outputWidth"),
+ self.settings.value("outputHeight"),
+ )
+ )
+ elif self.filterType == 4: # Phase
+ filter_ = (
+ 'aphasemeter=r=%s:s=%sx%s:mpc=white:video=1[atrash][vtmp]; '
+ '[atrash] anullsink; [vtmp] null' % (
+ self.settings.value("outputFrameRate"),
+ self.settings.value("outputWidth"),
+ self.settings.value("outputHeight"),
+ )
+ )
+
+ return [
+ '-filter_complex',
+ '%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 else '',
+ filter_,
+ ', hflip' if self.mirror else'',
+ w, h,
+ ', trim=start=%s:end=%s' % (
+ "{0:.3f}".format(startPt + 15),
+ "{0:.3f}".format(startPt + 15.5)
+ ) if preview 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):
+ image = Image.frombytes(
+ 'RGBA',
+ scale(self.scale, self.width, self.height, int),
+ imageData
+ )
+ 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..59ca0b8
--- /dev/null
+++ b/src/components/spectrum.ui
@@ -0,0 +1,582 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 586
+ 197
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 197
+
+
+
+ Form
+
+
+
-
+
+
+ 4
+
+
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Type
+
+
+
+ -
+
+
-
+
+ Spectrum
+
+
+ -
+
+ Histogram
+
+
+ -
+
+ Vector Scope
+
+
+ -
+
+ Musical Scale
+
+
+ -
+
+ Phase
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ X
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+ -10000
+
+
+ 10000
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Y
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 0
+ 0
+
+
+
+ -10000
+
+
+ 10000
+
+
+ 0
+
+
+
+
+
+ -
+
+
-
+
+
+ Compress
+
+
+
+ -
+
+
+ Mono
+
+
+
+ -
+
+
+ Mirror
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Scale
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ QAbstractSpinBox::UpDownArrows
+
+
+ %
+
+
+ 10
+
+
+ 400
+
+
+ 100
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ false
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Plain
+
+
+ 0
+
+
+
+
+
+ 0
+ 0
+ 561
+ 72
+
+
+
+
+ QLayout::SetMaximumSize
+
+
+ 0
+
+
-
+
+
+ QLayout::SetDefaultConstraint
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 31
+ 0
+
+
+
+ Window
+
+
+ 4
+
+
+
+ -
+
+
-
+
+ hann
+
+
+ -
+
+ gauss
+
+
+ -
+
+ tukey
+
+
+ -
+
+ dolph
+
+
+ -
+
+ cauchy
+
+
+ -
+
+ parzen
+
+
+ -
+
+ poisson
+
+
+ -
+
+ rect
+
+
+ -
+
+ bartlett
+
+
+ -
+
+ hanning
+
+
+ -
+
+ hamming
+
+
+ -
+
+ blackman
+
+
+ -
+
+ welch
+
+
+ -
+
+ flattop
+
+
+ -
+
+ bharris
+
+
+ -
+
+ bnuttall
+
+
+ -
+
+ lanczos
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Amplitude
+
+
+ 4
+
+
+
+ -
+
+
-
+
+ Square root
+
+
+ -
+
+ Cubic root
+
+
+ -
+
+ 4thrt
+
+
+ -
+
+ 5thrt
+
+
+ -
+
+ Linear
+
+
+ -
+
+ Logarithmic
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::MinimumExpanding
+
+
+
+ 10
+ 20
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Color
+
+
+ 4
+
+
+
+ -
+
+
-
+
+ Channel
+
+
+ -
+
+ Intensity
+
+
+ -
+
+ Rainbow
+
+
+ -
+
+ Moreland
+
+
+ -
+
+ Nebulae
+
+
+ -
+
+ Fire
+
+
+ -
+
+ Fiery
+
+
+ -
+
+ Fruit
+
+
+ -
+
+ Cool
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::MinimumExpanding
+
+
+
+ 10
+ 20
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::Fixed
+
+
+
+ 20
+ 10
+
+
+
+
+
+
+
+
+
diff --git a/src/components/waveform.py b/src/components/waveform.py
index b4b19e9..6c5133d 100644
--- a/src/components/waveform.py
+++ b/src/components/waveform.py
@@ -8,7 +8,9 @@ import subprocess
from component import Component
from toolkit.frame import BlankFrame, scale
from toolkit import checkOutput, rgbFromString, pickColor
-from toolkit.ffmpeg import openPipe, closePipe, getAudioDuration, FfmpegVideo
+from toolkit.ffmpeg import (
+ openPipe, closePipe, getAudioDuration, FfmpegVideo, exampleSound
+)
class Component(Component):
@@ -112,6 +114,8 @@ class Component(Component):
if not duration:
return
startPt = duration / 3
+ if startPt + 3 > duration:
+ startPt += startPt - 3
command = [
self.core.FFMPEG_BIN,
@@ -154,29 +158,43 @@ class Component(Component):
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'
- 'showwaves=r=30:s=%sx%s:mode=%s:colors=%s@%s:scale=%s%s%s [v1]; '
- '[v1] scale=%s:%s%s,setpts=2.0*PTS [v]' % (
- 'aevalsrc=sin(1*2*PI*t)*sin(880*2*PI*t),'
- if preview and genericPreview else '[0:a] ',
- 'compand=.3|.3:1|1:-90/-60|-60/-40|-40/-30|-20/-20:6:0:-90:0.2'
- ',' if self.compress and not preview else (
- 'compand=gain=5,' if self.compress else ''
- ),
- 'aformat=channel_layouts=mono,' if self.mono else '',
- self.settings.value("outputWidth"),
- self.settings.value("outputHeight"),
- str(self.page.comboBox_mode.currentText()).lower(),
- hexcolor, opacity, amplitude,
+ '%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 + 1)
+ ', trim=duration=%s' % "{0:.3f}".format(startPt + 3)
if preview else '',
),
'-map', '[v]',
diff --git a/src/components/waveform.ui b/src/components/waveform.ui
index 0e40380..5473f33 100644
--- a/src/components/waveform.ui
+++ b/src/components/waveform.ui
@@ -66,12 +66,17 @@
-
- P2p
+ Point
-
- Point
+ Frequency Bar
+
+
+ -
+
+ Frequency Line
@@ -180,12 +185,16 @@
-
- Wave Color
+ Color
-
-
+
+
+ Qt::ImhNone
+
+
-
@@ -244,10 +253,10 @@
%
- 10
+ 0
- 400
+ 100
100
diff --git a/src/mainwindow.py b/src/mainwindow.py
index a97081e..d9e95e2 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -581,7 +581,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.showMessage(
msg=msg,
detail=detail,
- icon='Warning',
+ icon='Critical',
)
def changeEncodingStatus(self, status):
diff --git a/src/toolkit/common.py b/src/toolkit/common.py
index 5d424e0..db278c0 100644
--- a/src/toolkit/common.py
+++ b/src/toolkit/common.py
@@ -113,3 +113,46 @@ def formatTraceback(tb=None):
import sys
tb = sys.exc_info()[2]
return 'Traceback:\n%s' % "\n".join(traceback.format_tb(tb))
+
+
+def connectWidget(widget, func):
+ if type(widget) == QtWidgets.QLineEdit:
+ widget.textChanged.connect(func)
+ elif type(widget) == QtWidgets.QSpinBox \
+ or type(widget) == QtWidgets.QDoubleSpinBox:
+ widget.valueChanged.connect(func)
+ elif type(widget) == QtWidgets.QCheckBox:
+ widget.stateChanged.connect(func)
+ elif type(widget) == QtWidgets.QComboBox:
+ widget.currentIndexChanged.connect(func)
+ else:
+ return False
+ return True
+
+
+def setWidgetValue(widget, val):
+ '''Generic setValue method for use with any typical QtWidget'''
+ if type(widget) == QtWidgets.QLineEdit:
+ widget.setText(val)
+ elif type(widget) == QtWidgets.QSpinBox \
+ or type(widget) == QtWidgets.QDoubleSpinBox:
+ widget.setValue(val)
+ elif type(widget) == QtWidgets.QCheckBox:
+ widget.setChecked(val)
+ elif type(widget) == QtWidgets.QComboBox:
+ widget.setCurrentIndex(val)
+ else:
+ return False
+ return True
+
+
+def getWidgetValue(widget):
+ if type(widget) == QtWidgets.QLineEdit:
+ return widget.text()
+ elif type(widget) == QtWidgets.QSpinBox \
+ or type(widget) == QtWidgets.QDoubleSpinBox:
+ return widget.value()
+ elif type(widget) == QtWidgets.QCheckBox:
+ return widget.isChecked()
+ elif type(widget) == QtWidgets.QComboBox:
+ return widget.currentIndex()
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index 4ea2863..3421049 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -37,7 +37,6 @@ class FfmpegVideo:
self.frameNo = -1
self.currentFrame = 'None'
self.map_ = None
- self.debug = False
if 'loopVideo' in kwargs and kwargs['loopVideo']:
self.loopValue = '-1'
@@ -48,8 +47,6 @@ class FfmpegVideo:
kwargs['filter_'].insert(0, '-filter_complex')
else:
kwargs['filter_'] = None
- if 'debug' in kwargs:
- self.debug = True
self.command = [
core.Core.FFMPEG_BIN,
@@ -90,16 +87,15 @@ class FfmpegVideo:
self.frameBuffer.task_done()
def fillBuffer(self):
- if self.debug:
- print(" ".join([word for word in self.command]))
- err = sys.__stdout__
- else:
- err = subprocess.DEVNULL
-
- self.pipe = openPipe(
- self.command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
- stderr=err, bufsize=10**8
- )
+ logFilename = os.path.join(
+ core.Core.dataDir, 'extra_%s.log' % str(self.component.compPos))
+ with open(logFilename, 'w') as log:
+ log.write(" ".join(self.command) + '\n\n')
+ with open(logFilename, 'a') as log:
+ self.pipe = openPipe(
+ self.command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
+ stderr=log, bufsize=10**8
+ )
while True:
if self.parent.canceled:
break
@@ -111,10 +107,18 @@ class FfmpegVideo:
self.frameBuffer.put((self.frameNo-1, self.lastFrame))
continue
except AttributeError:
- FfmpegVideo.threadError = ComponentError(self.component, 'video')
+ FfmpegVideo.threadError = ComponentError(
+ self.component, 'video',
+ "Video seemed playable but wasn't."
+ )
break
- self.currentFrame = self.pipe.stdout.read(self.chunkSize)
+ try:
+ self.currentFrame = self.pipe.stdout.read(self.chunkSize)
+ except ValueError:
+ FfmpegVideo.threadError = ComponentError(
+ self.component, 'video')
+
if len(self.currentFrame) != 0:
self.frameBuffer.put((self.frameNo, self.currentFrame))
self.lastFrame = self.currentFrame
@@ -446,3 +450,10 @@ def readAudioFile(filename, videoWorker):
completeAudioArray = completeAudioArrayCopy
return (completeAudioArray, duration)
+
+
+def exampleSound():
+ return (
+ 'aevalsrc=tan(random(1)*PI*t)*sin(random(0)*2*PI*t),'
+ 'apulsator=offset_l=0.5:offset_r=0.5,'
+ )
--
cgit v1.2.3
From 65420ce2855a24d54755a7a47804c2fb5f6d427e Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 30 Jul 2017 21:29:06 -0400
Subject: more options for the Spectrum component
---
src/component.py | 2 +-
src/components/spectrum.py | 99 ++++++++----
src/components/spectrum.ui | 370 ++++++++++++++++++++++++++++++++++++++++++++-
3 files changed, 437 insertions(+), 34 deletions(-)
diff --git a/src/component.py b/src/component.py
index 1a5a5a4..36ad9d3 100644
--- a/src/component.py
+++ b/src/component.py
@@ -427,7 +427,7 @@ class ComponentError(RuntimeError):
name,
'an' if any([
sys.exc_info()[0].__name__.startswith(vowel)
- for vowel in ('A', 'I')
+ for vowel in ('A', 'I', 'U', 'O', 'E')
]) else 'a',
sys.exc_info()[0].__name__,
str(sys.exc_info()[1])
diff --git a/src/components/spectrum.py b/src/components/spectrum.py
index 261d9cc..d1ad297 100644
--- a/src/components/spectrum.py
+++ b/src/components/spectrum.py
@@ -1,6 +1,5 @@
from PIL import Image
from PyQt5 import QtGui, QtCore, QtWidgets
-from PyQt5.QtGui import QColor
import os
import math
import subprocess
@@ -8,7 +7,7 @@ import time
from component import Component
from toolkit.frame import BlankFrame, scale
-from toolkit import checkOutput, rgbFromString, pickColor, connectWidget
+from toolkit import checkOutput, connectWidget
from toolkit.ffmpeg import (
openPipe, closePipe, getAudioDuration, FfmpegVideo, exampleSound
)
@@ -19,7 +18,6 @@ class Component(Component):
version = '1.0.0'
def widget(self, *args):
- self.color = (255, 255, 255)
self.previewFrame = None
super().widget(*args)
self.chunkSize = 4 * self.width * self.height
@@ -35,14 +33,22 @@ class Component(Component):
{
'filterType': self.page.comboBox_filterType,
'window': self.page.comboBox_window,
- 'amplitude': self.page.comboBox_amplitude,
+ '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,
}
)
for widget in self._trackedWidgets.values():
@@ -52,9 +58,8 @@ class Component(Component):
self.changedOptions = True
def update(self):
- count = self.page.stackedWidget.count()
- i = self.page.comboBox_filterType.currentIndex()
- self.page.stackedWidget.setCurrentIndex(i if i < count else count - 1)
+ self.page.stackedWidget.setCurrentIndex(
+ self.page.comboBox_filterType.currentIndex())
super().update()
def previewRender(self):
@@ -141,25 +146,26 @@ 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 = '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'
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' % (
+ '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(),
@@ -167,32 +173,61 @@ class Component(Component):
)
)
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' % (
+ '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=line:m=polar:scale=log' % (
+ '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' % (
+ '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:mpc=white:video=1[atrash][vtmp]; '
- '[atrash] anullsink; [vtmp] null' % (
+ 'aphasemeter=r=%s:s=%sx%s:video=1 [atrash][vtmp1]; '
+ '[atrash] anullsink; '
+ '[vtmp1] colorkey=color=black:similarity=0.1:blend=0.5 ' % (
self.settings.value("outputFrameRate"),
self.settings.value("outputWidth"),
self.settings.value("outputHeight"),
@@ -201,18 +236,22 @@ class Component(Component):
return [
'-filter_complex',
- '%s%s%s%s%s [v1]; '
- '[v1] scale=%s:%s%s [v]' % (
+ '%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'',
+ '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 + 15),
- "{0:.3f}".format(startPt + 15.5)
+ "{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]',
]
diff --git a/src/components/spectrum.ui b/src/components/spectrum.ui
index 59ca0b8..c6a8a15 100644
--- a/src/components/spectrum.ui
+++ b/src/components/spectrum.ui
@@ -31,6 +31,9 @@
4
+
-
+
+
-
-
@@ -208,6 +211,26 @@
+ -
+
+
+ Hue
+
+
+ 4
+
+
+
+ -
+
+
+ °
+
+
+ 359
+
+
+
-
@@ -272,7 +295,7 @@
0
0
561
- 72
+ 66
@@ -415,7 +438,7 @@
-
-
+
-
Square root
@@ -554,7 +577,348 @@
-
+
+
+
+
+ -1
+ -1
+ 561
+ 31
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Display Scale
+
+
+ 4
+
+
+
+ -
+
+
-
+
+ Logarithmic
+
+
+ -
+
+ Square root
+
+
+ -
+
+ Cubic root
+
+
+ -
+
+ Linear
+
+
+ -
+
+ Reverse Log
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Amplitude
+
+
+ 4
+
+
+
+ -
+
+
-
+
+ Logarithmic
+
+
+ -
+
+ Linear
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Minimum
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -1
+ -1
+ 585
+ 64
+
+
+
+ -
+
+
-
+
+
+ Mode
+
+
+
+ -
+
+
-
+
+ lissajous
+
+
+ -
+
+ lissajous_xy
+
+
+ -
+
+ polar
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Amplitude
+
+
+ 4
+
+
+
+ -
+
+
-
+
+ Linear
+
+
+ -
+
+ Square root
+
+
+ -
+
+ Cubic root
+
+
+ -
+
+ Logarithmic
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Zoom
+
+
+ 4
+
+
+
+ -
+
+
+ 1
+
+
+ 10
+
+
+
+ -
+
+
+ Line
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ 0
+ 561
+ 31
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Timeclamp
+
+
+ 4
+
+
+
+ -
+
+
+ s
+
+
+ 3
+
+
+ 0.002000000000000
+
+
+ 1.000000000000000
+
+
+ 0.010000000000000
+
+
+ 0.017000000000000
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ 0
+ 551
+ 31
+
+
+
+ -
+
+
+
+
+
--
cgit v1.2.3
From a472246dab69d0676c3c78ecd61659e432c960b4 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 30 Jul 2017 21:59:42 -0400
Subject: crop aphasermeter so it scales to look bigger
---
src/components/spectrum.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/components/spectrum.py b/src/components/spectrum.py
index d1ad297..8ab8404 100644
--- a/src/components/spectrum.py
+++ b/src/components/spectrum.py
@@ -227,7 +227,8 @@ class Component(Component):
filter_ = (
'aphasemeter=r=%s:s=%sx%s:video=1 [atrash][vtmp1]; '
'[atrash] anullsink; '
- '[vtmp1] colorkey=color=black:similarity=0.1:blend=0.5 ' % (
+ '[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"),
--
cgit v1.2.3
From 3c1b52205f183e9a2c943c5f666ed2c01db3aaf5 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 1 Aug 2017 17:57:39 -0400
Subject: component class now tracks colorwidgets
so adding new color-selection widgets is now simple
---
setup.py | 2 +-
src/component.py | 73 +++++++++++++++++++++++++++++++++++++++++-----
src/components/color.py | 58 +++++-------------------------------
src/components/original.py | 35 +++-------------------
src/components/text.py | 27 ++---------------
src/components/waveform.py | 40 ++++---------------------
src/toolkit/common.py | 19 ------------
src/toolkit/frame.py | 6 ++--
8 files changed, 90 insertions(+), 170 deletions(-)
diff --git a/setup.py b/setup.py
index d4f226b..4a4511f 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@ from setuptools import setup
import os
-__version__ = '2.0.0.rc2'
+__version__ = '2.0.0.rc3'
def package_files(directory):
diff --git a/src/component.py b/src/component.py
index 36ad9d3..d47aeae 100644
--- a/src/component.py
+++ b/src/component.py
@@ -3,18 +3,20 @@
on making a valid component.
'''
from PyQt5 import uic, QtCore, QtWidgets
+from PyQt5.QtGui import QColor
import os
import sys
import time
from toolkit.frame import BlankFrame
-from toolkit import getWidgetValue, setWidgetValue, connectWidget
+from toolkit import (
+ getWidgetValue, setWidgetValue, connectWidget, rgbFromString
+)
class ComponentMetaclass(type(QtCore.QObject)):
'''
- Checks the validity of each Component class imported, and
- mutates some attributes for easier use by the core program.
+ Checks the validity of each Component class and mutates some attrs.
E.g., takes only major version from version string & decorates methods
'''
@@ -173,6 +175,8 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
self._trackedWidgets = {}
self._presetNames = {}
self._commandArgs = {}
+ self._colorWidgets = {}
+ self._relativeWidgets = {}
self._lockedProperties = None
self._lockedError = None
@@ -188,7 +192,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
)
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
- # Critical Methods
+ # Render Methods
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
def previewRender(self):
@@ -286,7 +290,17 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
Call super() at the END if you need to subclass this.
'''
for attr, widget in self._trackedWidgets.items():
- setattr(self, attr, getWidgetValue(widget))
+ if attr in self._colorWidgets:
+ rgbTuple = rgbFromString(widget.text())
+ setattr(self, attr, rgbTuple)
+ btnStyle = (
+ "QPushButton { background-color : %s; outline: none; }"
+ % QColor(*rgbTuple).name()
+ )
+ self._colorWidgets[attr].setStyleSheet(btnStyle)
+ else:
+ setattr(self, attr, getWidgetValue(widget))
+
if not self.core.openingProject:
self.parent.drawPreview()
saveValueStore = self.savePreset()
@@ -305,7 +319,16 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
key = attr if attr not in self._presetNames \
else self._presetNames[attr]
val = presetDict[key]
- setWidgetValue(widget, val)
+
+ if attr in self._colorWidgets:
+ widget.setText('%s,%s,%s' % val)
+ btnStyle = (
+ "QPushButton { background-color : %s; outline: none; }"
+ % QColor(*val).name()
+ )
+ self._colorWidgets[attr].setStyleSheet(btnStyle)
+ else:
+ setWidgetValue(widget, val)
def savePreset(self):
saveValueStore = {}
@@ -352,7 +375,12 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
self._trackedWidgets = trackDict
for kwarg in kwargs:
try:
- if kwarg in ('presetNames', 'commandArgs'):
+ if kwarg in (
+ 'presetNames',
+ 'commandArgs',
+ 'colorWidgets',
+ 'relativeWidgets',
+ ):
setattr(self, '_%s' % kwarg, kwargs[kwarg])
else:
raise ComponentError(
@@ -360,6 +388,37 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
except ComponentError:
continue
+ if kwarg == 'colorWidgets':
+ def makeColorFunc(attr):
+ def pickColor_():
+ self.pickColor(
+ self._trackedWidgets[attr],
+ self._colorWidgets[attr]
+ )
+ return pickColor_
+ self._colorFuncs = {
+ attr: makeColorFunc(attr) for attr in kwargs[kwarg]
+ }
+ for attr, func in self._colorFuncs.items():
+ self._colorWidgets[attr].clicked.connect(func)
+ self._colorWidgets[attr].setStyleSheet(
+ "QPushButton {"
+ "background-color : #FFFFFF; outline: none; }"
+ )
+
+ def pickColor(self, textWidget, button):
+ '''Use color picker to get color input from the user.'''
+ dialog = QtWidgets.QColorDialog()
+ dialog.setOption(QtWidgets.QColorDialog.ShowAlphaChannel, True)
+ color = dialog.getColor()
+ if color.isValid():
+ RGBstring = '%s,%s,%s' % (
+ str(color.red()), str(color.green()), str(color.blue()))
+ btnStyle = "QPushButton{background-color: %s; outline: none;}" \
+ % color.name()
+ textWidget.setText(RGBstring)
+ button.setStyleSheet(btnStyle)
+
def lockProperties(self, propList):
self._lockedProperties = propList
diff --git a/src/components/color.py b/src/components/color.py
index 2abd79a..d6fffc6 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)
@@ -66,16 +52,18 @@ class Component(Component):
'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,
+ },
)
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 +149,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/original.py b/src/components/original.py
index 621af6f..950ac7b 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):
@@ -22,7 +21,6 @@ class Component(Component):
return ['pcm']
def widget(self, *args):
- self.visColor = (255, 255, 255)
self.scale = 20
self.y = 0
super().widget(*args)
@@ -33,35 +31,17 @@ 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,
+ }, colorWidgets={
+ 'visColor': self.page.pushButton_visColor,
})
- 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
-
def previewRender(self):
spectrum = numpy.fromfunction(
lambda x: float(self.scale)/2500*(x-128)**2, (255,), dtype="int16")
@@ -99,13 +79,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/text.py b/src/components/text.py
index 8a302ff..1fe3467 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -5,7 +5,6 @@ import os
from component import Component
from toolkit.frame import FramePainter
-from toolkit import rgbFromString, pickColor
class Component(Component):
@@ -33,11 +32,6 @@ class Component(Component):
self.page.comboBox_textAlign.addItem("Right")
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))
@@ -48,21 +42,18 @@ class Component(Component):
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,
})
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 getXY(self):
@@ -86,15 +77,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 +108,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/waveform.py b/src/components/waveform.py
index 6c5133d..9c3cf86 100644
--- a/src/components/waveform.py
+++ b/src/components/waveform.py
@@ -7,7 +7,7 @@ import subprocess
from component import Component
from toolkit.frame import BlankFrame, scale
-from toolkit import checkOutput, rgbFromString, pickColor
+from toolkit import checkOutput
from toolkit.ffmpeg import (
openPipe, closePipe, getAudioDuration, FfmpegVideo, exampleSound
)
@@ -18,15 +18,9 @@ class Component(Component):
version = '1.0.0'
def widget(self, *args):
- self.color = (255, 255, 255)
super().widget(*args)
- self.page.lineEdit_color.setText('%s,%s,%s' % self.color)
- btnStyle = "QPushButton { background-color : %s; outline: none; }" \
- % QColor(*self.color).name()
- self.page.pushButton_color.setStyleSheet(btnStyle)
- self.page.pushButton_color.clicked.connect(lambda: self.pickColor())
- self.page.spinBox_scale.valueChanged.connect(self.updateChunksize)
+ self.page.lineEdit_color.setText('255,255,255')
if hasattr(self.parent, 'window'):
self.parent.window.lineEdit_audioFile.textChanged.connect(
@@ -35,6 +29,7 @@ class Component(Component):
self.trackWidgets(
{
+ 'color': self.page.lineEdit_color,
'mode': self.page.comboBox_mode,
'amplitude': self.page.comboBox_amplitude,
'x': self.page.spinBox_x,
@@ -44,36 +39,11 @@ class Component(Component):
'opacity': self.page.spinBox_opacity,
'compress': self.page.checkBox_compress,
'mono': self.page.checkBox_mono,
+ }, colorWidgets={
+ 'color': self.page.pushButton_color,
}
)
- def update(self):
- self.color = rgbFromString(self.page.lineEdit_color.text())
- btnStyle = "QPushButton { background-color : %s; outline: none; }" \
- % QColor(*self.color).name()
- self.page.pushButton_color.setStyleSheet(btnStyle)
- super().update()
-
- def loadPreset(self, pr, *args):
- super().loadPreset(pr, *args)
-
- self.page.lineEdit_color.setText('%s,%s,%s' % pr['color'])
- btnStyle = "QPushButton { background-color : %s; outline: none; }" \
- % QColor(*pr['color']).name()
- self.page.pushButton_color.setStyleSheet(btnStyle)
-
- def savePreset(self):
- saveValueStore = super().savePreset()
- saveValueStore['color'] = self.color
- return saveValueStore
-
- def pickColor(self):
- RGBstring, btnStyle = pickColor()
- if not RGBstring:
- return
- self.page.lineEdit_color.setText(RGBstring)
- self.page.pushButton_color.setStyleSheet(btnStyle)
-
def previewRender(self):
self.updateChunksize()
frame = self.getPreviewFrame(self.width, self.height)
diff --git a/src/toolkit/common.py b/src/toolkit/common.py
index db278c0..eba57d9 100644
--- a/src/toolkit/common.py
+++ b/src/toolkit/common.py
@@ -74,25 +74,6 @@ def disableWhenOpeningProject(func):
return decorator
-def pickColor():
- '''
- Use color picker to get color input from the user,
- and return this as an RGB string and QPushButton stylesheet.
- In a subclass apply stylesheet to any color selection widgets
- '''
- dialog = QtWidgets.QColorDialog()
- dialog.setOption(QtWidgets.QColorDialog.ShowAlphaChannel, True)
- color = dialog.getColor()
- if color.isValid():
- RGBstring = '%s,%s,%s' % (
- str(color.red()), str(color.green()), str(color.blue()))
- btnStyle = "QPushButton{background-color: %s; outline: none;}" \
- % color.name()
- return RGBstring, btnStyle
- else:
- return None, None
-
-
def rgbFromString(string):
'''Turns an RGB string like "255, 255, 255" into a tuple'''
try:
diff --git a/src/toolkit/frame.py b/src/toolkit/frame.py
index f42d4c9..c007188 100644
--- a/src/toolkit/frame.py
+++ b/src/toolkit/frame.py
@@ -42,9 +42,9 @@ class PaintColor(QtGui.QColor):
super().__init__(b, g, r, a)
-def scale(scale, width, height, returntype=None):
- width = (float(width) / 100.0) * float(scale)
- height = (float(height) / 100.0) * float(scale)
+def scale(scalePercent, width, height, returntype=None):
+ width = (float(width) / 100.0) * float(scalePercent)
+ height = (float(height) / 100.0) * float(scalePercent)
if returntype == str:
return (str(math.ceil(width)), str(math.ceil(height)))
elif returntype == int:
--
cgit v1.2.3
From 5784cdbcf87556b61519782cd1fc27065ffbc631 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 1 Aug 2017 21:57:36 -0400
Subject: x/y pixel values update to match output resolution
---
src/component.py | 39 ++++++++++++++++++++++++++++++++++++---
src/components/color.py | 3 +++
src/components/image.py | 3 +++
src/components/original.py | 2 ++
src/components/spectrum.py | 3 +++
src/components/text.py | 19 +++++++++++--------
src/components/video.py | 3 +++
src/components/waveform.py | 3 +++
src/mainwindow.py | 5 ++++-
9 files changed, 68 insertions(+), 12 deletions(-)
diff --git a/src/component.py b/src/component.py
index d47aeae..5dfe2ab 100644
--- a/src/component.py
+++ b/src/component.py
@@ -6,6 +6,7 @@ from PyQt5 import uic, QtCore, QtWidgets
from PyQt5.QtGui import QColor
import os
import sys
+import math
import time
from toolkit.frame import BlankFrame
@@ -176,7 +177,9 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
self._presetNames = {}
self._commandArgs = {}
self._colorWidgets = {}
+ self._colorFuncs = {}
self._relativeWidgets = {}
+ self._relativeValues = {}
self._lockedProperties = None
self._lockedError = None
@@ -291,14 +294,44 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
'''
for attr, widget in self._trackedWidgets.items():
if attr in self._colorWidgets:
+ # Color Widgets: text stored as tuple & update the button color
rgbTuple = rgbFromString(widget.text())
- setattr(self, attr, rgbTuple)
btnStyle = (
"QPushButton { background-color : %s; outline: none; }"
- % QColor(*rgbTuple).name()
- )
+ % QColor(*rgbTuple).name())
self._colorWidgets[attr].setStyleSheet(btnStyle)
+ setattr(self, attr, rgbTuple)
+
+ elif attr in self._relativeWidgets:
+ # Relative widgets: number scales to fit export resolution
+ if self._relativeWidgets[attr] == 'x':
+ dimension = self.width
+ else:
+ dimension = self.height
+ try:
+ oldUserValue = getattr(self, attr)
+ except AttributeError:
+ oldUserValue = self._trackedWidgets[attr].value()
+ newUserValue = self._trackedWidgets[attr].value()
+ newRelativeVal = newUserValue / dimension
+
+ if attr in self._relativeValues:
+ if oldUserValue == newUserValue:
+ oldRelativeVal = self._relativeValues[attr]
+ if oldRelativeVal != newRelativeVal:
+ # Float changed without pixel value changing, which
+ # means the pixel value needs to be updated
+ self._trackedWidgets[attr].blockSignals(True)
+ self._trackedWidgets[attr].setValue(
+ math.ceil(dimension * oldRelativeVal))
+ self._trackedWidgets[attr].blockSignals(False)
+ if oldUserValue != newUserValue \
+ or attr not in self._relativeValues:
+ self._relativeValues[attr] = newRelativeVal
+ setattr(self, attr, self._trackedWidgets[attr].value())
+
else:
+ # Normal tracked widget
setattr(self, attr, getWidgetValue(widget))
if not self.core.openingProject:
diff --git a/src/components/color.py b/src/components/color.py
index d6fffc6..703caca 100644
--- a/src/components/color.py
+++ b/src/components/color.py
@@ -60,6 +60,9 @@ class Component(Component):
}, colorWidgets={
'color1': self.page.pushButton_color1,
'color2': self.page.pushButton_color2,
+ }, relativeWidgets={
+ 'x': 'x',
+ 'y': 'y',
},
)
diff --git a/src/components/image.py b/src/components/image.py
index a96f127..2ffa5a1 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -28,6 +28,9 @@ class Component(Component):
'imagePath': 'image',
'xPosition': 'x',
'yPosition': 'y',
+ }, relativeWidgets={
+ 'xPosition': 'x',
+ 'yPosition': 'y',
},
)
diff --git a/src/components/original.py b/src/components/original.py
index 950ac7b..67e3239 100644
--- a/src/components/original.py
+++ b/src/components/original.py
@@ -40,6 +40,8 @@ class Component(Component):
'y': self.page.spinBox_y,
}, colorWidgets={
'visColor': self.page.pushButton_visColor,
+ }, relativeWidgets={
+ 'y': 'y',
})
def previewRender(self):
diff --git a/src/components/spectrum.py b/src/components/spectrum.py
index 8ab8404..2cc641d 100644
--- a/src/components/spectrum.py
+++ b/src/components/spectrum.py
@@ -49,6 +49,9 @@ class Component(Component):
'compress': self.page.checkBox_compress,
'mono': self.page.checkBox_mono,
'hue': self.page.spinBox_hue,
+ }, relativeWidgets={
+ 'x': 'x',
+ 'y': 'y',
}
)
for widget in self._trackedWidgets.values():
diff --git a/src/components/text.py b/src/components/text.py
index 1fe3467..0f87038 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -17,15 +17,12 @@ class Component(Component):
def widget(self, *args):
super().widget(*args)
- height = int(self.settings.value('outputHeight'))
- width = int(self.settings.value('outputWidth'))
+ # 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")
@@ -35,8 +32,11 @@ class Component(Component):
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))
+
+ fm = QtGui.QFontMetrics(self.titleFont)
+ self.page.spinBox_xTextAlign.setValue(
+ self.width / 2 - fm.width(self.title)/2)
+ self.page.spinBox_yTextAlign.setValue(self.height / 2 * 1.036)
self.page.fontComboBox_titleFont.currentFontChanged.connect(
self.update
@@ -50,6 +50,9 @@ class Component(Component):
'yPosition': self.page.spinBox_yTextAlign,
}, colorWidgets={
'textColor': self.page.pushButton_textColor,
+ }, relativeWidgets={
+ 'xPosition': 'x',
+ 'yPosition': 'y',
})
def update(self):
diff --git a/src/components/video.py b/src/components/video.py
index 6cd16e5..3569d17 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -38,6 +38,9 @@ class Component(Component):
'loopVideo': 'loop',
'xPosition': 'x',
'yPosition': 'y',
+ }, relativeWidgets={
+ 'xPosition': 'x',
+ 'yPosition': 'y',
}
)
diff --git a/src/components/waveform.py b/src/components/waveform.py
index 9c3cf86..a25116b 100644
--- a/src/components/waveform.py
+++ b/src/components/waveform.py
@@ -41,6 +41,9 @@ class Component(Component):
'mono': self.page.checkBox_mono,
}, colorWidgets={
'color': self.page.pushButton_color,
+ }, relativeWidgets={
+ 'x': 'x',
+ 'y': 'y',
}
)
diff --git a/src/mainwindow.py b/src/mainwindow.py
index d9e95e2..1c8806d 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -644,9 +644,12 @@ class MainWindow(QtWidgets.QMainWindow):
def updateResolution(self):
resIndex = int(self.window.comboBox_resolution.currentIndex())
res = Core.resolutions[resIndex].split('x')
+ changed = res[0] != self.settings.value("outputWidth")
self.settings.setValue('outputWidth', res[0])
self.settings.setValue('outputHeight', res[1])
- self.drawPreview()
+ if changed:
+ for i in range(len(self.core.selectedComponents)):
+ self.core.updateComponent(i)
def drawPreview(self, force=False, **kwargs):
'''Use autosave keyword arg to force saving or not saving if needed'''
--
cgit v1.2.3
From 8812c37213987a5e842af8b8dfcd090ca4ec8610 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 1 Aug 2017 22:04:51 -0400
Subject: width/height fields should be relative too
---
src/components/color.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/components/color.py b/src/components/color.py
index 703caca..2b100d9 100644
--- a/src/components/color.py
+++ b/src/components/color.py
@@ -63,6 +63,8 @@ class Component(Component):
}, relativeWidgets={
'x': 'x',
'y': 'y',
+ 'sizeWidth': 'x',
+ 'sizeHeight': 'y',
},
)
--
cgit v1.2.3
From 62431a3cfebdc8490b7010d71b8e646dd6bd0d35 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 1 Aug 2017 22:07:49 -0400
Subject: fontsize is also relative
---
src/components/text.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/components/text.py b/src/components/text.py
index 0f87038..be4de4a 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -53,6 +53,7 @@ class Component(Component):
}, relativeWidgets={
'xPosition': 'x',
'yPosition': 'y',
+ 'fontSize': 'y',
})
def update(self):
--
cgit v1.2.3
From 6611492b30a7daf7bdbe77f6e12f9d322bdd90c1 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 3 Aug 2017 00:44:46 -0400
Subject: relative gradients & last good frame used for preview errors
---
src/components/color.py | 5 +++++
src/components/spectrum.py | 15 ++++++++++-----
src/components/text.py | 2 --
src/components/video.py | 15 ++++-----------
src/components/waveform.py | 15 ++++++++++-----
5 files changed, 29 insertions(+), 23 deletions(-)
diff --git a/src/components/color.py b/src/components/color.py
index 2b100d9..f5d618e 100644
--- a/src/components/color.py
+++ b/src/components/color.py
@@ -65,6 +65,11 @@ class Component(Component):
'y': 'y',
'sizeWidth': 'x',
'sizeHeight': 'y',
+ 'RG_start': 'x',
+ 'LG_start': 'x',
+ 'RG_end': 'x',
+ 'LG_end': 'x',
+ 'RG_centre': 'x',
},
)
diff --git a/src/components/spectrum.py b/src/components/spectrum.py
index 2cc641d..9a0c59a 100644
--- a/src/components/spectrum.py
+++ b/src/components/spectrum.py
@@ -20,6 +20,7 @@ class Component(Component):
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
@@ -268,11 +269,15 @@ class Component(Component):
return changed
def finalizeFrame(self, imageData):
- image = Image.frombytes(
- 'RGBA',
- scale(self.scale, self.width, self.height, int),
- 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)
diff --git a/src/components/text.py b/src/components/text.py
index be4de4a..2a5d433 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -17,8 +17,6 @@ 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
diff --git a/src/components/video.py b/src/components/video.py
index 3569d17..2cd67c6 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -16,12 +16,12 @@ 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(
{
@@ -70,8 +70,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):
@@ -199,14 +197,10 @@ class Component(Component):
'RGBA',
scale(self.scale, self.width, self.height, int),
imageData)
-
+ self._image = image
except ValueError:
- print(
- '### BAD VIDEO SELECTED ###\n'
- 'Video will not export with these settings'
- )
- self.badVideo = True
- return BlankFrame(self.width, self.height)
+ # use last good frame
+ image = self._image
if self.scale != 100 \
or self.xPosition != 0 or self.yPosition != 0:
@@ -214,5 +208,4 @@ class Component(Component):
frame.paste(image, box=(self.xPosition, self.yPosition))
else:
frame = image
- self.badVideo = False
return frame
diff --git a/src/components/waveform.py b/src/components/waveform.py
index a25116b..526e6fb 100644
--- a/src/components/waveform.py
+++ b/src/components/waveform.py
@@ -19,6 +19,7 @@ class Component(Component):
def widget(self, *args):
super().widget(*args)
+ self._image = BlankFrame(self.width, self.height)
self.page.lineEdit_color.setText('255,255,255')
@@ -178,11 +179,15 @@ class Component(Component):
self.chunkSize = 4 * width * height
def finalizeFrame(self, imageData):
- image = Image.frombytes(
- 'RGBA',
- scale(self.scale, self.width, self.height, int),
- 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)
--
cgit v1.2.3
From 219e846984bb10e9674432fa7aeac4157635c743 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 3 Aug 2017 12:16:57 -0400
Subject: relativeWidgets might as well be a list
---
src/component.py | 5 +---
src/components/color.py | 63 +++++++++++++++++++++-------------------------
src/components/image.py | 36 ++++++++++++--------------
src/components/original.py | 6 ++---
src/components/spectrum.py | 47 ++++++++++++++++------------------
src/components/text.py | 8 +++---
src/components/video.py | 37 +++++++++++++--------------
src/components/waveform.py | 35 ++++++++++++--------------
8 files changed, 106 insertions(+), 131 deletions(-)
diff --git a/src/component.py b/src/component.py
index 5dfe2ab..c5bc44b 100644
--- a/src/component.py
+++ b/src/component.py
@@ -304,10 +304,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
elif attr in self._relativeWidgets:
# Relative widgets: number scales to fit export resolution
- if self._relativeWidgets[attr] == 'x':
- dimension = self.width
- else:
- dimension = self.height
+ dimension = self.width
try:
oldUserValue = getattr(self, attr)
except AttributeError:
diff --git a/src/components/color.py b/src/components/color.py
index f5d618e..5d1233e 100644
--- a/src/components/color.py
+++ b/src/components/color.py
@@ -37,41 +37,34 @@ 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,
- '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': 'x',
- 'y': 'y',
- 'sizeWidth': 'x',
- 'sizeHeight': 'y',
- 'RG_start': 'x',
- 'LG_start': 'x',
- 'RG_end': 'x',
- 'LG_end': 'x',
- 'RG_centre': 'x',
- },
- )
+ 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()
diff --git a/src/components/image.py b/src/components/image.py
index 2ffa5a1..19c4796 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -13,26 +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',
- }, relativeWidgets={
- '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,
+ }, presetNames={
+ 'mirror': self.page.checkBox_mirror,
+ 'imagePath': 'image',
+ 'xPosition': 'x',
+ 'yPosition': 'y',
+ }, relativeWidgets=[
+ 'xPosition', 'yPosition',
+ ])
def previewRender(self):
return self.drawFrame(self.width, self.height)
diff --git a/src/components/original.py b/src/components/original.py
index 67e3239..f886374 100644
--- a/src/components/original.py
+++ b/src/components/original.py
@@ -40,9 +40,9 @@ class Component(Component):
'y': self.page.spinBox_y,
}, colorWidgets={
'visColor': self.page.pushButton_visColor,
- }, relativeWidgets={
- 'y': 'y',
- })
+ }, relativeWidgets=[
+ 'y',
+ ])
def previewRender(self):
spectrum = numpy.fromfunction(
diff --git a/src/components/spectrum.py b/src/components/spectrum.py
index 9a0c59a..666e20a 100644
--- a/src/components/spectrum.py
+++ b/src/components/spectrum.py
@@ -30,31 +30,28 @@ class Component(Component):
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': 'x',
- 'y': '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())
diff --git a/src/components/text.py b/src/components/text.py
index 2a5d433..b7c244e 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -48,11 +48,9 @@ class Component(Component):
'yPosition': self.page.spinBox_yTextAlign,
}, colorWidgets={
'textColor': self.page.pushButton_textColor,
- }, relativeWidgets={
- 'xPosition': 'x',
- 'yPosition': 'y',
- 'fontSize': 'y',
- })
+ }, relativeWidgets=[
+ 'xPosition', 'yPosition', 'fontSize',
+ ])
def update(self):
self.titleFont = self.page.fontComboBox_titleFont.currentFont()
diff --git a/src/components/video.py b/src/components/video.py
index 2cd67c6..b6bdd52 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -23,26 +23,23 @@ 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': '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():
diff --git a/src/components/waveform.py b/src/components/waveform.py
index 526e6fb..71cbcac 100644
--- a/src/components/waveform.py
+++ b/src/components/waveform.py
@@ -28,25 +28,22 @@ class Component(Component):
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': 'x',
- 'y': 'y',
- }
- )
+ 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()
--
cgit v1.2.3
From ae8a547b77a618c793929701f9c1fa72d3300110 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 3 Aug 2017 18:08:49 -0400
Subject: max spinbox vals scale relatively & less errors when spamming res
change
w/h attrs are locked during render so preview thread always get correctly-sized frame
---
src/component.py | 92 ++++++++++++++++++++++++++++++++++++-------------
src/components/image.py | 2 +-
src/components/text.ui | 3 ++
src/core.py | 6 ++--
src/preview_thread.py | 2 ++
5 files changed, 77 insertions(+), 28 deletions(-)
diff --git a/src/component.py b/src/component.py
index c5bc44b..ea4b5ec 100644
--- a/src/component.py
+++ b/src/component.py
@@ -179,9 +179,14 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
self._colorWidgets = {}
self._colorFuncs = {}
self._relativeWidgets = {}
+ # pixel values stored as floats
self._relativeValues = {}
+ # maximum values of spinBoxes at 1080p (Core.resolutions[0])
+ self._relativeMaximums = {}
+
self._lockedProperties = None
self._lockedError = None
+ self._lockedSize = None
# Stop lengthy processes in response to this variable
self.canceled = False
@@ -190,8 +195,12 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
return self.__class__.name
def __repr__(self):
+ try:
+ preset = self.savePreset()
+ except Exception as e:
+ preset = '%s occured while saving preset' % str(e)
return '%s\n%s\n%s' % (
- self.__class__.name, str(self.__class__.version), self.savePreset()
+ self.__class__.name, str(self.__class__.version), preset
)
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
@@ -304,27 +313,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
elif attr in self._relativeWidgets:
# Relative widgets: number scales to fit export resolution
- dimension = self.width
- try:
- oldUserValue = getattr(self, attr)
- except AttributeError:
- oldUserValue = self._trackedWidgets[attr].value()
- newUserValue = self._trackedWidgets[attr].value()
- newRelativeVal = newUserValue / dimension
-
- if attr in self._relativeValues:
- if oldUserValue == newUserValue:
- oldRelativeVal = self._relativeValues[attr]
- if oldRelativeVal != newRelativeVal:
- # Float changed without pixel value changing, which
- # means the pixel value needs to be updated
- self._trackedWidgets[attr].blockSignals(True)
- self._trackedWidgets[attr].setValue(
- math.ceil(dimension * oldRelativeVal))
- self._trackedWidgets[attr].blockSignals(False)
- if oldUserValue != newUserValue \
- or attr not in self._relativeValues:
- self._relativeValues[attr] = newRelativeVal
+ self.updateRelativeWidget(attr)
setattr(self, attr, self._trackedWidgets[attr].value())
else:
@@ -436,6 +425,13 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
"background-color : #FFFFFF; outline: none; }"
)
+ if kwarg == 'relativeWidgets':
+ # store maximum values of spinBoxes to be scaled appropriately
+ for attr in kwargs[kwarg]:
+ self._relativeMaximums[attr] = \
+ self._trackedWidgets[attr].maximum()
+ self.updateRelativeWidgetMaximum(attr)
+
def pickColor(self, textWidget, button):
'''Use color picker to get color input from the user.'''
dialog = QtWidgets.QColorDialog()
@@ -455,23 +451,35 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
def lockError(self, msg):
self._lockedError = msg
+ def lockSize(self, w, h):
+ self._lockedSize = (w, h)
+
def unlockProperties(self):
self._lockedProperties = None
def unlockError(self):
self._lockedError = None
+ def unlockSize(self):
+ self._lockedSize = None
+
def loadUi(self, filename):
'''Load a Qt Designer ui file to use for this component's widget'''
return uic.loadUi(os.path.join(self.core.componentsPath, filename))
@property
def width(self):
- return int(self.settings.value('outputWidth'))
+ if self._lockedSize is None:
+ return int(self.settings.value('outputWidth'))
+ else:
+ return self._lockedSize[0]
@property
def height(self):
- return int(self.settings.value('outputHeight'))
+ if self._lockedSize is None:
+ return int(self.settings.value('outputHeight'))
+ else:
+ return self._lockedSize[1]
def cancel(self):
'''Stop any lengthy process in response to this variable.'''
@@ -482,6 +490,42 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
self.unlockProperties()
self.unlockError()
+ def updateRelativeWidget(self, attr):
+ dimension = self.width
+ if 'height' in attr.lower() \
+ or 'ypos' in attr.lower() or attr == 'y':
+ dimension = self.height
+ try:
+ oldUserValue = getattr(self, attr)
+ except AttributeError:
+ oldUserValue = self._trackedWidgets[attr].value()
+ newUserValue = self._trackedWidgets[attr].value()
+ newRelativeVal = newUserValue / dimension
+
+ if attr in self._relativeValues:
+ oldRelativeVal = self._relativeValues[attr]
+ if oldUserValue == newUserValue \
+ and oldRelativeVal != newRelativeVal:
+ # Float changed without pixel value changing, which
+ # means the pixel value needs to be updated
+ self._trackedWidgets[attr].blockSignals(True)
+ self.updateRelativeWidgetMaximum(attr)
+ self._trackedWidgets[attr].setValue(
+ math.ceil(dimension * oldRelativeVal))
+ self._trackedWidgets[attr].blockSignals(False)
+
+ if attr not in self._relativeValues \
+ or oldUserValue != newUserValue:
+ self._relativeValues[attr] = newRelativeVal
+
+ def updateRelativeWidgetMaximum(self, attr):
+ maxRes = int(self.core.resolutions[0].split('x')[0])
+ newMaximumValue = self.width * (
+ self._relativeMaximums[attr] /
+ maxRes
+ )
+ self._trackedWidgets[attr].setMaximum(int(newMaximumValue))
+
class ComponentError(RuntimeError):
'''Gives the MainWindow a traceback to display, and cancels the export.'''
diff --git a/src/components/image.py b/src/components/image.py
index 19c4796..555dfb1 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -21,8 +21,8 @@ class Component(Component):
'xPosition': self.page.spinBox_x,
'yPosition': self.page.spinBox_y,
'stretched': self.page.checkBox_stretch,
- }, presetNames={
'mirror': self.page.checkBox_mirror,
+ }, presetNames={
'imagePath': 'image',
'xPosition': 'x',
'yPosition': 'y',
diff --git a/src/components/text.ui b/src/components/text.ui
index 05e7f8e..bb5e5af 100644
--- a/src/components/text.ui
+++ b/src/components/text.ui
@@ -81,6 +81,9 @@
-
+
+ 1
+
500
diff --git a/src/core.py b/src/core.py
index 24bf097..afb1e45 100644
--- a/src/core.py
+++ b/src/core.py
@@ -451,8 +451,8 @@ class Core:
'1280x720',
'854x480',
],
- 'windowHasFocus': False,
'FFMPEG_BIN': findFfmpeg(),
+ 'windowHasFocus': False,
'canceled': False,
}
@@ -492,7 +492,7 @@ class Core:
@classmethod
def loadDefaultSettings(cls):
- defaultSettings = {
+ cls.defaultSettings = {
"outputWidth": 1280,
"outputHeight": 720,
"outputFrameRate": 30,
@@ -509,7 +509,7 @@ class Core:
"pref_genericPreview": True,
}
- for parm, value in defaultSettings.items():
+ for parm, value in cls.defaultSettings.items():
if cls.settings.value(parm) is None:
cls.settings.setValue(parm, value)
diff --git a/src/preview_thread.py b/src/preview_thread.py
index 0a6a856..bb22f0c 100644
--- a/src/preview_thread.py
+++ b/src/preview_thread.py
@@ -59,7 +59,9 @@ class Worker(QtCore.QObject):
components = nextPreviewInformation["components"]
for component in reversed(components):
try:
+ component.lockSize(width, height)
newFrame = component.previewRender()
+ component.unlockSize()
frame = Image.alpha_composite(
frame, newFrame
)
--
cgit v1.2.3
From 98a47a21d986ccede574baececd179be7550c9d6 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 3 Aug 2017 20:43:23 -0400
Subject: save presets as floats so project resolution is not relevant
unfortunately this breaks old projects and presets
---
src/component.py | 56 ++++++++++++++++++++----
src/components/text.py | 18 ++++----
src/components/text.ui | 114 ++++++++++++++++++++++++++++++-------------------
src/core.py | 2 +-
4 files changed, 127 insertions(+), 63 deletions(-)
diff --git a/src/component.py b/src/component.py
index ea4b5ec..5b38473 100644
--- a/src/component.py
+++ b/src/component.py
@@ -346,16 +346,29 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
% QColor(*val).name()
)
self._colorWidgets[attr].setStyleSheet(btnStyle)
+ elif attr in self._relativeWidgets:
+ self._relativeValues[attr] = val
+ pixelVal = self.pixelValForAttr(attr, val)
+ setWidgetValue(widget, pixelVal)
else:
setWidgetValue(widget, val)
def savePreset(self):
saveValueStore = {}
for attr, widget in self._trackedWidgets.items():
- saveValueStore[
+ presetAttrName = (
attr if attr not in self._presetNames
else self._presetNames[attr]
- ] = getattr(self, attr)
+ )
+ if attr in self._relativeWidgets:
+ try:
+ val = self._relativeValues[attr]
+ except AttributeError:
+ val = self.floatValForAttr(attr)
+ else:
+ val = getattr(self, attr)
+
+ saveValueStore[presetAttrName] = val
return saveValueStore
def commandHelp(self):
@@ -490,17 +503,42 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
self.unlockProperties()
self.unlockError()
+ def relativeWidgetAxis(func):
+ def relativeWidgetAxis(self, attr, *args, **kwargs):
+ if 'axis' not in kwargs:
+ axis = self.width
+ if 'height' in attr.lower() \
+ or 'ypos' in attr.lower() or attr == 'y':
+ axis = self.height
+ kwargs['axis'] = axis
+ return func(self, attr, *args, **kwargs)
+ return relativeWidgetAxis
+
+ @relativeWidgetAxis
+ def pixelValForAttr(self, attr, val=None, **kwargs):
+ if val is None:
+ val = self._relativeValues[attr]
+ return math.ceil(kwargs['axis'] * val)
+
+ @relativeWidgetAxis
+ def floatValForAttr(self, attr, val=None, **kwargs):
+ if val is None:
+ val = self._trackedWidgets[attr].value()
+ return val / kwargs['axis']
+
+ def setRelativeWidget(self, attr, floatVal):
+ '''Set a relative widget using a float'''
+ pixelVal = self.pixelValForAttr(attr, floatVal)
+ self._trackedWidgets[attr].setValue(pixelVal)
+
+
def updateRelativeWidget(self, attr):
- dimension = self.width
- if 'height' in attr.lower() \
- or 'ypos' in attr.lower() or attr == 'y':
- dimension = self.height
try:
oldUserValue = getattr(self, attr)
except AttributeError:
oldUserValue = self._trackedWidgets[attr].value()
newUserValue = self._trackedWidgets[attr].value()
- newRelativeVal = newUserValue / dimension
+ newRelativeVal = self.floatValForAttr(attr, newUserValue)
if attr in self._relativeValues:
oldRelativeVal = self._relativeValues[attr]
@@ -510,8 +548,8 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
# means the pixel value needs to be updated
self._trackedWidgets[attr].blockSignals(True)
self.updateRelativeWidgetMaximum(attr)
- self._trackedWidgets[attr].setValue(
- math.ceil(dimension * oldRelativeVal))
+ pixelVal = self.pixelValForAttr(attr, oldRelativeVal)
+ self._trackedWidgets[attr].setValue(pixelVal)
self._trackedWidgets[attr].blockSignals(False)
if attr not in self._relativeValues \
diff --git a/src/components/text.py b/src/components/text.py
index b7c244e..c3f3bdc 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -9,7 +9,7 @@ from toolkit.frame import FramePainter
class Component(Component):
name = 'Title Text'
- version = '1.0.0'
+ version = '1.0.1'
def __init__(self, *args):
super().__init__(*args)
@@ -25,20 +25,17 @@ class Component(Component):
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.lineEdit_title.setText(self.title)
- self.page.comboBox_textAlign.setCurrentIndex(int(self.alignment))
self.page.spinBox_fontSize.setValue(int(self.fontSize))
+ self.page.lineEdit_title.setText(self.title)
- fm = QtGui.QFontMetrics(self.titleFont)
- self.page.spinBox_xTextAlign.setValue(
- self.width / 2 - fm.width(self.title)/2)
- self.page.spinBox_yTextAlign.setValue(self.height / 2 * 1.036)
-
+ 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,
@@ -51,11 +48,16 @@ class Component(Component):
}, relativeWidgets=[
'xPosition', 'yPosition', 'fontSize',
])
+ self.centerXY()
def update(self):
self.titleFont = self.page.fontComboBox_titleFont.currentFont()
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)
diff --git a/src/components/text.ui b/src/components/text.ui
index bb5e5af..f76979c 100644
--- a/src/components/text.ui
+++ b/src/components/text.ui
@@ -19,6 +19,36 @@
4
+
-
+
+
-
+
+
+ Title
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+ Testing New GUI
+
+
+
+
+
-
-
@@ -93,38 +123,6 @@
-
-
-
-
-
-
- 0
- 0
-
-
-
- Text Layout
-
-
-
- -
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
-
@@ -132,6 +130,9 @@
+ -
+
+
-
@@ -152,7 +153,17 @@
-
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
@@ -162,28 +173,41 @@
0
-
-
+
+
+
+ 0
+ 0
+
+
- Title
+ Text Layout
-
-
-
-
- 0
- 0
-
+
+
+ -
+
+
+ Qt::Horizontal
-
+
+ QSizePolicy::Fixed
+
+
- 0
- 0
+ 5
+ 20
+
+
+ -
+
- Testing New GUI
+ Center
diff --git a/src/core.py b/src/core.py
index afb1e45..61905eb 100644
--- a/src/core.py
+++ b/src/core.py
@@ -161,7 +161,7 @@ class Core:
for widget, value in data['WindowFields']:
widget = eval('loader.window.%s' % widget)
widget.blockSignals(True)
- widget.setText(value)
+ toolkit.setWidgetValue(widget, value)
widget.blockSignals(False)
for key, value in data['Settings']:
--
cgit v1.2.3
From d04ddba484f1c8993971f79d5ee14b0cc7a512fb Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 3 Aug 2017 20:50:22 -0400
Subject: image scale needs to be relative
---
src/components/image.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/image.py b/src/components/image.py
index 555dfb1..1555541 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -27,7 +27,7 @@ class Component(Component):
'xPosition': 'x',
'yPosition': 'y',
}, relativeWidgets=[
- 'xPosition', 'yPosition',
+ 'xPosition', 'yPosition', 'scale'
])
def previewRender(self):
--
cgit v1.2.3
From 998f74149553ac7a9e27d7c85cebceda2ef32c64 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 6 Aug 2017 21:52:44 -0400
Subject: added stroke and font style options to Text component
---
src/component.py | 4 +-
src/components/image.ui | 336 +++++++++++++++++-----------------
src/components/original.ui | 2 +-
src/components/text.py | 65 +++++--
src/components/text.ui | 439 +++++++++++++++++++++++++++++++++++++--------
5 files changed, 593 insertions(+), 253 deletions(-)
diff --git a/src/component.py b/src/component.py
index 5b38473..5b6f9a7 100644
--- a/src/component.py
+++ b/src/component.py
@@ -198,7 +198,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
try:
preset = self.savePreset()
except Exception as e:
- preset = '%s occured while saving preset' % str(e)
+ preset = '%s occurred while saving preset' % str(e)
return '%s\n%s\n%s' % (
self.__class__.name, str(self.__class__.version), preset
)
@@ -275,7 +275,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
Call super().widget(*args) to create the component widget
which also auto-connects any common widgets (e.g., checkBoxes)
to self.update(). Then in a subclass connect special actions
- (e.g., pushButtons to select a file/colour) and initialize
+ (e.g., pushButtons to select a file) and initialize
'''
self.parent = parent
self.settings = parent.settings
diff --git a/src/components/image.ui b/src/components/image.ui
index e549ed0..1837b64 100644
--- a/src/components/image.ui
+++ b/src/components/image.ui
@@ -178,177 +178,177 @@
-
-
- -
-
-
-
-
-
- Stretch
-
-
- false
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
- Mirror
-
-
-
- -
-
-
- Rotate
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- QAbstractSpinBox::UpDownArrows
-
-
- °
-
-
- 0
-
-
- 359
-
-
- 0
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 10
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Scale
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- QAbstractSpinBox::UpDownArrows
-
-
- %
-
-
- 10
-
-
- 400
-
-
- 100
-
-
-
-
-
- -
-
-
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Color
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
+
+
-
+
+
+ Stretch
+
+
+ false
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+ Mirror
+
+
+
+ -
+
+
+ Rotate
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ QAbstractSpinBox::UpDownArrows
+
+
+ °
+
+
+ 0
+
+
+ 359
+
+
+ 0
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 10
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Scale
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ QAbstractSpinBox::UpDownArrows
+
+
+ %
+
+
+ 10
+
+
+ 400
+
+
+ 100
+
+
+
+
-
-
-
- QAbstractSpinBox::UpDownArrows
-
-
- %
-
-
- 0
-
-
- 999
-
-
- 1
-
-
- 100
-
-
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Color
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ QAbstractSpinBox::UpDownArrows
+
+
+ %
+
+
+ 0
+
+
+ 999
+
+
+ 1
+
+
+ 100
+
+
+
+
diff --git a/src/components/original.ui b/src/components/original.ui
index 8fa9b2b..a4d5119 100644
--- a/src/components/original.ui
+++ b/src/components/original.ui
@@ -6,7 +6,7 @@
0
0
- 633
+ 586
178
diff --git a/src/components/text.py b/src/components/text.py
index c3f3bdc..f88f373 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -4,22 +4,20 @@ from PyQt5 import QtGui, QtCore, QtWidgets
import os
from component import Component
-from toolkit.frame import FramePainter
+from toolkit.frame import FramePainter, PaintColor
class Component(Component):
name = 'Title Text'
version = '1.0.1'
- def __init__(self, *args):
- super().__init__(*args)
- self.titleFont = QFont()
-
def widget(self, *args):
super().widget(*args)
self.textColor = (255, 255, 255)
+ self.strokeColor = (0, 0, 0)
self.title = 'Text'
self.alignment = 1
+ self.titleFont = QFont()
self.fontSize = self.height / 13.5
self.page.comboBox_textAlign.addItem("Left")
@@ -28,6 +26,7 @@ class Component(Component):
self.page.comboBox_textAlign.setCurrentIndex(int(self.alignment))
self.page.lineEdit_textColor.setText('%s,%s,%s' % self.textColor)
+ self.page.lineEdit_strokeColor.setText('%s,%s,%s' % self.strokeColor)
self.page.spinBox_fontSize.setValue(int(self.fontSize))
self.page.lineEdit_title.setText(self.title)
@@ -43,8 +42,16 @@ class Component(Component):
'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',
])
@@ -52,11 +59,23 @@ class Component(Component):
def update(self):
self.titleFont = self.page.fontComboBox_titleFont.currentFont()
+ if self.page.checkBox_shadow.isChecked():
+ self.page.label_shadX.setHidden(False)
+ self.page.spinBox_shadX.setHidden(False)
+ self.page.spinBox_shadY.setHidden(False)
+ self.page.label_shadBlur.setHidden(False)
+ self.page.spinBox_shadBlur.setHidden(False)
+ else:
+ self.page.label_shadX.setHidden(True)
+ self.page.spinBox_shadX.setHidden(True)
+ self.page.spinBox_shadY.setHidden(True)
+ self.page.label_shadBlur.setHidden(True)
+ self.page.spinBox_shadBlur.setHidden(True)
super().update()
def centerXY(self):
self.setRelativeWidget('xPosition', 0.5)
- self.setRelativeWidget('yPosition', 0.5)
+ self.setRelativeWidget('yPosition', 0.521)
def getXY(self):
'''Returns true x, y after considering alignment settings'''
@@ -101,13 +120,39 @@ class Component(Component):
return self.addText(self.width, self.height)
def addText(self, width, height):
+ font = self.titleFont
+ font.setPixelSize(self.fontSize)
+ font.setStyle(QFont.StyleNormal)
+ font.setWeight(QFont.Normal)
+ font.setCapitalization(QFont.MixedCase)
+ if self.fontStyle == 1:
+ font.setWeight(QFont.DemiBold)
+ if self.fontStyle == 2:
+ font.setWeight(QFont.Bold)
+ elif self.fontStyle == 3:
+ font.setStyle(QFont.StyleItalic)
+ elif self.fontStyle == 4:
+ font.setWeight(QFont.Bold)
+ font.setStyle(QFont.StyleItalic)
+ elif self.fontStyle == 5:
+ font.setStyle(QFont.StyleOblique)
+ elif self.fontStyle == 6:
+ font.setCapitalization(QFont.SmallCaps)
+
image = FramePainter(width, height)
- self.titleFont.setPixelSize(self.fontSize)
- image.setFont(self.titleFont)
- image.setPen(self.textColor)
x, y = self.getXY()
+ if self.stroke > 0:
+ outliner = QtGui.QPainterPathStroker()
+ outliner.setWidth(self.stroke)
+ path = QtGui.QPainterPath()
+ path.addText(x, y, font, self.title)
+ path = outliner.createStroke(path)
+ image.setBrush(PaintColor(*self.strokeColor))
+ image.drawPath(path)
+
+ image.setFont(font)
+ image.setPen(self.textColor)
image.drawText(x, y, self.title)
-
return image.finalize()
def commandHelp(self):
diff --git a/src/components/text.ui b/src/components/text.ui
index f76979c..5a7e831 100644
--- a/src/components/text.ui
+++ b/src/components/text.ui
@@ -16,6 +16,12 @@
-
+
+ 6
+
+
+ QLayout::SetDefaultConstraint
+
4
@@ -31,7 +37,7 @@
-
-
+
0
0
@@ -47,14 +53,10 @@
-
-
- -
-
-
-
+
0
0
@@ -67,7 +69,7 @@
-
-
+
0
0
@@ -80,8 +82,44 @@
+
+
+ -
+
+
+ 0
+
-
-
+
+
+
+ 0
+ 0
+
+
+
+ Text Layout
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 100
+ 16777215
+
+
+
+
+ -
+
Qt::Horizontal
@@ -97,7 +135,36 @@
-
-
+
+
+
+ 0
+ 0
+
+
+
+ Center Text
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
0
@@ -105,36 +172,104 @@
- Font Size
+ X
-
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 50
+ 16777215
+
+
+
+
+ 0
+ 0
+
+
- 1
+ 0
- 500
+ 999999999
+
+
+ 0
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Y
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 50
+ 16777215
+
+
+
+ 999999999
-
-
+
-
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 16777215
+
+
Text Color
- -
-
-
-
+
+
+ 0
+ 0
+
+
32
@@ -153,27 +288,23 @@
-
-
+
Qt::Horizontal
+
+ QSizePolicy::Fixed
+
- 40
+ 5
20
-
-
- -
-
-
- 0
-
-
-
+
0
@@ -181,15 +312,34 @@
- Text Layout
+ Font Size
-
-
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+
+
+
+ 1
+
+
+ 500
+
+
-
-
+
Qt::Horizontal
@@ -205,30 +355,82 @@
-
-
+
+
+
+ 0
+ 0
+
+
- Center
+ Font Style
-
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
+
+
-
+
+ Normal
+
+
+ -
+
+ Semi-Bold
+
+
+ -
+
+ Bold
+
+
+ -
+
+ Italic
+
+
+ -
+
+ Bold Italic
+
+
+ -
+
+ Faux Italic
+
+
+ -
+
+ Small Caps
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
-
+
- 5
- 20
+ 0
+ 16777215
-
+
+ Qt::NoFocus
+
+
-
-
+
0
@@ -236,59 +438,112 @@
- X
+ Stroke
-
-
+
-
+
+ 0
+ 0
+
+
+
+ px
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Stroke Color
+
+
+
+ -
+
+
+
0
0
- 80
+ 0
16777215
-
+
+ Qt::NoFocus
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
- 0
- 0
+ 32
+ 32
-
- 0
-
-
- 999999999
+
+
-
- 0
+
+
+ 32
+ 32
+
-
-
+
Qt::Horizontal
-
- QSizePolicy::Fixed
-
- 5
+ 40
20
+
+
+ -
+
-
-
+
+
+
+ 0
+ 0
+
+
+
+ Shadow
+
+
+
+ -
+
0
@@ -296,29 +551,69 @@
- Y
+ Shadow Offset
-
-
+
0
0
-
-
- 80
- 16777215
-
+
+
+ -
+
+
+
+ 0
+ 0
+
-
- 999999999
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Shadow Blur
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Minimum
+
+
+
+ 40
+ 20
+
+
+
+
--
cgit v1.2.3
From 060a7dc2d263c0fd0e36e162943b8946df937bbd Mon Sep 17 00:00:00 2001
From: tassaron
Date: Mon, 7 Aug 2017 21:03:01 -0400
Subject: dropshadow option for Text component
---
src/components/text.py | 16 ++++++++++++++--
src/components/text.ui | 27 +++++++++++++++++++++++++++
2 files changed, 41 insertions(+), 2 deletions(-)
diff --git a/src/components/text.py b/src/components/text.py
index f88f373..c50c812 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -1,4 +1,4 @@
-from PIL import Image, ImageDraw
+from PIL import ImageEnhance, ImageFilter, ImageChops
from PyQt5.QtGui import QColor, QFont
from PyQt5 import QtGui, QtCore, QtWidgets
import os
@@ -153,7 +153,19 @@ class Component(Component):
image.setFont(font)
image.setPen(self.textColor)
image.drawText(x, y, self.title)
- return image.finalize()
+
+ # turn QImage into Pillow frame
+ frame = image.finalize()
+ if self.shadow:
+ shadImg = ImageEnhance.Contrast(frame).enhance(0.0)
+ shadImg = shadImg.filter(ImageFilter.GaussianBlur(self.shadBlur))
+ shadImg = ImageChops.offset(shadImg, self.shadX, self.shadY)
+ shadImg.paste(frame, box=(0, 0), mask=frame)
+ frame = shadImg
+
+ return frame
+
+
def commandHelp(self):
print('Enter a string to use as centred white text:')
diff --git a/src/components/text.ui b/src/components/text.ui
index 5a7e831..13d3467 100644
--- a/src/components/text.ui
+++ b/src/components/text.ui
@@ -563,6 +563,15 @@
0
+
+ -1000
+
+
+ 1000
+
+
+ -4
+
-
@@ -573,6 +582,15 @@
0
+
+ -1000
+
+
+ 1000
+
+
+ 8
+
-
@@ -596,6 +614,15 @@
0
+
+ 99.000000000000000
+
+
+ 0.100000000000000
+
+
+ 5.000000000000000
+
-
--
cgit v1.2.3
From 354637d34c201b9389b9085889275d6850873d08 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 8 Aug 2017 05:57:19 -0400
Subject: relative stroke px size & no Qt pen on stroke
---
src/components/text.py | 3 ++-
src/toolkit/frame.py | 7 +++++--
2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/src/components/text.py b/src/components/text.py
index c50c812..46fb001 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -53,7 +53,7 @@ class Component(Component):
'textColor': self.page.pushButton_textColor,
'strokeColor': self.page.pushButton_strokeColor,
}, relativeWidgets=[
- 'xPosition', 'yPosition', 'fontSize',
+ 'xPosition', 'yPosition', 'fontSize', 'stroke'
])
self.centerXY()
@@ -147,6 +147,7 @@ class Component(Component):
path = QtGui.QPainterPath()
path.addText(x, y, font, self.title)
path = outliner.createStroke(path)
+ image.setPen(QtCore.Qt.NoPen)
image.setBrush(PaintColor(*self.strokeColor))
image.drawPath(path)
diff --git a/src/toolkit/frame.py b/src/toolkit/frame.py
index c007188..7e83d58 100644
--- a/src/toolkit/frame.py
+++ b/src/toolkit/frame.py
@@ -21,8 +21,11 @@ class FramePainter(QtGui.QPainter):
self.image = QtGui.QImage(ImageQt(image))
super().__init__(self.image)
- def setPen(self, RgbTuple):
- super().setPen(PaintColor(*RgbTuple))
+ def setPen(self, penStyle):
+ if type(penStyle) is tuple:
+ super().setPen(PaintColor(*penStyle))
+ else:
+ super().setPen(penStyle)
def finalize(self):
self.end()
--
cgit v1.2.3
From 4d0daa4336432948ba6543d4becaaa42425ecafd Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 8 Aug 2017 06:03:14 -0400
Subject: relative dropshadow
---
src/components/text.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/components/text.py b/src/components/text.py
index 46fb001..f6bd17d 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -53,7 +53,8 @@ class Component(Component):
'textColor': self.page.pushButton_textColor,
'strokeColor': self.page.pushButton_strokeColor,
}, relativeWidgets=[
- 'xPosition', 'yPosition', 'fontSize', 'stroke'
+ 'xPosition', 'yPosition', 'fontSize',
+ 'stroke', 'shadX', 'shadY', 'shadBlur'
])
self.centerXY()
--
cgit v1.2.3
From 3ed84e1c3edba46fe8990544ef7e58fe8e3dd901 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 8 Aug 2017 20:53:51 -0400
Subject: fixed incorrect outline for small-caps
---
src/components/text.py | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/src/components/text.py b/src/components/text.py
index f6bd17d..4d4f5d3 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -146,7 +146,18 @@ class Component(Component):
outliner = QtGui.QPainterPathStroker()
outliner.setWidth(self.stroke)
path = QtGui.QPainterPath()
- path.addText(x, y, font, self.title)
+ if self.fontStyle == 6:
+ # PathStroker ignores smallcaps so we need this weird hack
+ path.addText(x, y, font, self.title[0])
+ fm = QtGui.QFontMetrics(font)
+ newX = x + fm.width(self.title[0])
+ strokeFont = self.page.fontComboBox_titleFont.currentFont()
+ strokeFont.setCapitalization(QFont.SmallCaps)
+ strokeFont.setPixelSize(int((self.fontSize / 7) * 5))
+ strokeFont.setLetterSpacing(QFont.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.setBrush(PaintColor(*self.strokeColor))
@@ -167,8 +178,6 @@ class Component(Component):
return frame
-
-
def commandHelp(self):
print('Enter a string to use as centred white text:')
print(' "title=User Error"')
--
cgit v1.2.3
From 8b253717f7c0dd3fe73b1f3474fea2176e8f19ba Mon Sep 17 00:00:00 2001
From: tassaron
Date: Wed, 9 Aug 2017 16:46:59 -0400
Subject: Conway's Game of Life component
---
src/components/__template__.ui | 119 ++++++++++++++++
src/components/life.py | 155 +++++++++++++++++++++
src/components/life.ui | 302 +++++++++++++++++++++++++++++++++++++++++
src/mainwindow.py | 16 +++
4 files changed, 592 insertions(+)
create mode 100644 src/components/__template__.ui
create mode 100644 src/components/life.py
create mode 100644 src/components/life.ui
diff --git a/src/components/__template__.ui b/src/components/__template__.ui
new file mode 100644
index 0000000..301a2b7
--- /dev/null
+++ b/src/components/__template__.ui
@@ -0,0 +1,119 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 586
+ 197
+
+
+
+ Form
+
+
+
-
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
diff --git a/src/components/life.py b/src/components/life.py
new file mode 100644
index 0000000..1e72620
--- /dev/null
+++ b/src/components/life.py
@@ -0,0 +1,155 @@
+from PyQt5 import QtGui, QtCore, QtWidgets
+from PIL import ImageDraw, ImageEnhance, ImageChops, ImageFilter
+import os
+import math
+
+from component import Component
+from toolkit.frame import BlankFrame, FramePainter
+
+
+class Component(Component):
+ name = 'Conway\'s Game of Life'
+ version = '1.0.0a'
+
+ def widget(self, *args):
+ super().widget(*args)
+ self.scale = 32
+ self.updateGridSize()
+ self.startingGrid = {}
+ 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,
+ }, colorWidgets={
+ 'color': self.page.pushButton_color,
+ })
+ self.page.spinBox_scale.setValue(self.scale)
+
+ def update(self):
+ self.updateGridSize()
+ super().update()
+
+ 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
+ )
+ if button == 1:
+ self.startingGrid[pos] = True
+ elif button == 2 and pos in self.startingGrid:
+ self.startingGrid.pop(pos)
+
+ def updateGridSize(self):
+ w, h = self.core.resolutions[-1].split('x')
+ self.gridWidth = int(int(w) / self.scale)
+ self.gridHeight = int(int(h) / self.scale)
+ self.pxWidth = math.ceil(self.width / self.gridWidth)
+ self.pxHeight = math.ceil(self.height / self.gridHeight)
+
+ def previewRender(self):
+ return self.drawGrid(self.startingGrid)
+
+ def preFrameRender(self, *args, **kwargs):
+ super().preFrameRender(*args, **kwargs)
+ self.progressBarSetText.emit("Computing evolution...")
+ self.tickGrids = {0: self.startingGrid}
+ tick = 0
+ for frameNo in range(
+ self.tickRate, len(self.completeAudioArray), self.sampleSize
+ ):
+ if frameNo % self.tickRate == 0:
+ tick += 1
+ self.tickGrids[tick] = self.gridForTick(tick)
+
+ # update progress bar
+ progress = int(100*(frameNo/len(self.completeAudioArray)))
+ if progress >= 100:
+ progress = 100
+ pStr = "Computing evolution: "+str(progress)+'%'
+ self.progressBarSetText.emit(pStr)
+ self.progressBarUpdate.emit(int(progress))
+
+ def frameRender(self, frameNo):
+ tick = math.floor(frameNo / self.tickRate)
+ grid = self.tickGrids[tick]
+ return self.drawGrid(grid)
+
+ def drawGrid(self, grid):
+ frame = BlankFrame(self.width, self.height)
+ drawer = ImageDraw.Draw(frame)
+
+ for x, y in grid:
+ drawPtX = x * self.pxWidth
+ drawPtY = y * self.pxHeight
+ rect = (
+ (drawPtX, drawPtY),
+ (drawPtX + self.pxWidth, drawPtY + self.pxHeight)
+ )
+ if self.shapeType == 0:
+ drawer.rectangle(rect, fill=self.color)
+ elif self.shapeType == 1:
+ drawer.ellipse(rect, fill=self.color)
+ elif self.shapeType == 2:
+ drawer.pieslice(rect, 290, 250, fill=self.color)
+ elif self.shapeType == 3:
+ drawer.pieslice(rect, 20, 340, fill=self.color)
+
+ if self.shadow:
+ shadImg = ImageEnhance.Contrast(frame).enhance(0.0)
+ shadImg = shadImg.filter(ImageFilter.GaussianBlur(5.00))
+ shadImg = ImageChops.offset(shadImg, -2, 2)
+ shadImg.paste(frame, box=(0, 0), mask=frame)
+ frame = shadImg
+ return frame
+
+ def gridForTick(self, tick):
+ '''Given a tick number over 0, returns a new grid dict of tuples'''
+ lastGrid = self.tickGrids[tick - 1]
+
+ def nearbyCoords(x, y):
+ yield x + 1, y + 1
+ yield x + 1, y - 1
+ yield x - 1, y + 1
+ yield x - 1, y - 1
+ yield x, y + 1
+ yield x, y - 1
+ yield x + 1, y
+ yield x - 1, y
+
+ def neighbours(x, y):
+ nearbyCells = [
+ lastGrid.get(cell) for cell in nearbyCoords(x, y)
+ ]
+ return [
+ nearbyCell for nearbyCell in nearbyCells
+ if nearbyCell is not None
+ ]
+
+ newGrid = {}
+ for x, y in lastGrid:
+ surrounding = len(neighbours(x, y))
+ if surrounding == 2 or surrounding == 3:
+ newGrid[(x, y)] = True
+ potentialNewCells = set([
+ coordTup for origin in lastGrid
+ for coordTup in list(nearbyCoords(*origin))
+ ])
+ for x, y in potentialNewCells:
+ if (x, y) in newGrid:
+ continue
+ surrounding = len(neighbours(x, y))
+ if surrounding == 3:
+ newGrid[(x, y)] = True
+
+ return newGrid
+
+ def savePreset(self):
+ pr = super().savePreset()
+ pr['GRID'] = self.startingGrid
+ return pr
+
+ def loadPreset(self, pr, *args):
+ super().loadPreset(pr, *args)
+ self.startingGrid = pr['GRID']
diff --git a/src/components/life.ui b/src/components/life.ui
new file mode 100644
index 0000000..88f8eca
--- /dev/null
+++ b/src/components/life.ui
@@ -0,0 +1,302 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 586
+ 197
+
+
+
+ Form
+
+
+ -
+
+
-
+
+
-
+
+
-
+
+
+ Simulation Speed
+
+
+
+ -
+
+
+ frames per tick
+
+
+ 1
+
+
+ 30
+
+
+ 15
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 16777215
+
+
+
+ 0,0,0
+
+
+
+
+
+ -
+
+
-
+
+
+ Grid Scale
+
+
+
+ -
+
+
+ 24
+
+
+ 128
+
+
+ 32
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Color
+
+
+
+ -
+
+
+
+ 0
+ 16777215
+
+
+
+ 0,0,0
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+ false
+
+
+ false
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Shape
+
+
+
+ -
+
+
-
+
+ Rectangle
+
+
+ -
+
+ Circle
+
+
+ -
+
+ Lilypad
+
+
+ -
+
+ Pac-Man
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Shadow
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+ -
+
+
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
+<html><head><meta name="qrichtext" content="1" /><style type="text/css">
+p, li { white-space: pre-wrap; }
+</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;">
+<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Click the preview window to place a cell. Right-click to remove.</span></p>
+<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">- A cell with less than 2 neighbours will die from underpopulation</p>
+<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">- A cell with more than 3 neighbours will die from overpopulation.</p>
+<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">- An empty space surrounded by 3 live cells will cause reproduction.</p></body></html>
+
+
+ 80
+
+
+ Qt::NoTextInteraction
+
+
+ false
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
diff --git a/src/mainwindow.py b/src/mainwindow.py
index 1c8806d..789a6e7 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -50,6 +50,22 @@ class PreviewWindow(QtWidgets.QLabel):
self.pixmap = QtGui.QPixmap(img)
self.repaint()
+ def mousePressEvent(self, event):
+ if self.parent.encoding:
+ return
+
+ i = self.parent.window.listWidget_componentList.currentRow()
+ if i >= 0:
+ component = self.parent.core.selectedComponents[i]
+ if not hasattr(component, 'previewClickEvent'):
+ return
+ pos = (event.x(), event.y())
+ size = (self.width(), self.height())
+ component.previewClickEvent(
+ pos, size, event.button()
+ )
+ self.parent.core.updateComponent(i)
+
@QtCore.pyqtSlot(str)
def threadError(self, msg):
self.parent.showMessage(
--
cgit v1.2.3
From cacab464c7655a1c0cedcfe95b63609f55d78322 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 10 Aug 2017 00:46:31 -0400
Subject: more shapes and custom image option for Life
---
src/.goutputstream-67IS4Y | 976 ++++++++++++++++++++++++++++++++++++++++++++++
src/components/life.py | 167 +++++++-
src/components/life.ui | 57 ++-
3 files changed, 1186 insertions(+), 14 deletions(-)
create mode 100644 src/.goutputstream-67IS4Y
diff --git a/src/.goutputstream-67IS4Y b/src/.goutputstream-67IS4Y
new file mode 100644
index 0000000..789a6e7
--- /dev/null
+++ b/src/.goutputstream-67IS4Y
@@ -0,0 +1,976 @@
+'''
+ When using GUI mode, this module's object (the main window) takes
+ user input to construct a program state (stored in the Core object).
+ This shows a preview of the video being created and allows for saving
+ projects and exporting the video at a later time.
+'''
+from PyQt5 import QtCore, QtGui, uic, QtWidgets
+from PyQt5.QtWidgets import QMenu, QShortcut
+from PIL import Image
+from queue import Queue
+import sys
+import os
+import signal
+import filecmp
+import time
+
+from core import Core
+import preview_thread
+from presetmanager import PresetManager
+from toolkit import disableWhenEncoding, disableWhenOpeningProject, checkOutput
+
+
+class PreviewWindow(QtWidgets.QLabel):
+ '''
+ Paints the preview QLabel and maintains the aspect ratio when the
+ window is resized.
+ '''
+
+ def __init__(self, parent, img):
+ super(PreviewWindow, self).__init__()
+ self.parent = parent
+ self.setFrameStyle(QtWidgets.QFrame.StyledPanel)
+ self.pixmap = QtGui.QPixmap(img)
+
+ def paintEvent(self, event):
+ size = self.size()
+ painter = QtGui.QPainter(self)
+ point = QtCore.QPoint(0, 0)
+ scaledPix = self.pixmap.scaled(
+ size,
+ QtCore.Qt.KeepAspectRatio,
+ transformMode=QtCore.Qt.SmoothTransformation)
+
+ # start painting the label from left upper corner
+ point.setX((size.width() - scaledPix.width())/2)
+ point.setY((size.height() - scaledPix.height())/2)
+ painter.drawPixmap(point, scaledPix)
+
+ def changePixmap(self, img):
+ self.pixmap = QtGui.QPixmap(img)
+ self.repaint()
+
+ def mousePressEvent(self, event):
+ if self.parent.encoding:
+ return
+
+ i = self.parent.window.listWidget_componentList.currentRow()
+ if i >= 0:
+ component = self.parent.core.selectedComponents[i]
+ if not hasattr(component, 'previewClickEvent'):
+ return
+ pos = (event.x(), event.y())
+ size = (self.width(), self.height())
+ component.previewClickEvent(
+ pos, size, event.button()
+ )
+ self.parent.core.updateComponent(i)
+
+ @QtCore.pyqtSlot(str)
+ def threadError(self, msg):
+ self.parent.showMessage(
+ msg=msg,
+ icon='Critical',
+ parent=self
+ )
+
+
+class MainWindow(QtWidgets.QMainWindow):
+ '''
+ The MainWindow wraps many Core methods in order to update the GUI
+ accordingly. E.g., instead of self.core.openProject(), it will use
+ self.openProject() and update the window titlebar within the wrapper.
+
+ MainWindow manages the autosave feature, although Core has the
+ primary functions for opening and creating project files.
+ '''
+
+ createVideo = QtCore.pyqtSignal()
+ newTask = QtCore.pyqtSignal(list) # for the preview window
+ processTask = QtCore.pyqtSignal()
+
+ def __init__(self, window, project):
+ QtWidgets.QMainWindow.__init__(self)
+ # print('main thread id: {}'.format(QtCore.QThread.currentThreadId()))
+ self.window = window
+ self.core = Core()
+
+ # widgets of component settings
+ self.pages = []
+ self.lastAutosave = time.time()
+ # list of previous five autosave times, used to reduce update spam
+ self.autosaveTimes = []
+ self.autosaveCooldown = 0.2
+ self.encoding = False
+
+ # Create data directory, load/create settings
+ self.dataDir = Core.dataDir
+ self.presetDir = Core.presetDir
+ self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
+ self.settings = Core.settings
+ self.presetManager = PresetManager(
+ uic.loadUi(
+ os.path.join(Core.wd, 'presetmanager.ui')), self)
+
+ if not os.path.exists(self.dataDir):
+ os.makedirs(self.dataDir)
+ for neededDirectory in (
+ self.presetDir, self.settings.value("projectDir")):
+ if not os.path.exists(neededDirectory):
+ os.mkdir(neededDirectory)
+
+ # Create the preview window and its thread, queues, and timers
+ self.previewWindow = PreviewWindow(self, os.path.join(
+ Core.wd, "background.png"))
+ window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
+
+ self.previewQueue = Queue()
+ self.previewThread = QtCore.QThread(self)
+ self.previewWorker = preview_thread.Worker(self, self.previewQueue)
+ self.previewWorker.error.connect(self.previewWindow.threadError)
+ self.previewWorker.moveToThread(self.previewThread)
+ self.previewWorker.imageCreated.connect(self.showPreviewImage)
+ self.previewThread.start()
+
+ self.timer = QtCore.QTimer(self)
+ self.timer.timeout.connect(self.processTask.emit)
+ self.timer.start(500)
+
+ # Begin decorating the window and connecting events
+ self.window.installEventFilter(self)
+ componentList = self.window.listWidget_componentList
+
+ if sys.platform == 'darwin':
+ window.progressBar_createVideo.setTextVisible(False)
+ else:
+ window.progressLabel.setHidden(True)
+
+ window.toolButton_selectAudioFile.clicked.connect(
+ self.openInputFileDialog)
+
+ window.toolButton_selectOutputFile.clicked.connect(
+ self.openOutputFileDialog)
+
+ def changedField():
+ self.autosave()
+ self.updateWindowTitle()
+
+ window.lineEdit_audioFile.textChanged.connect(changedField)
+ window.lineEdit_outputFile.textChanged.connect(changedField)
+
+ window.progressBar_createVideo.setValue(0)
+
+ window.pushButton_createVideo.clicked.connect(
+ self.createAudioVisualisation)
+
+ window.pushButton_Cancel.clicked.connect(self.stopVideo)
+
+ for i, container in enumerate(Core.encoderOptions['containers']):
+ window.comboBox_videoContainer.addItem(container['name'])
+ if container['name'] == self.settings.value('outputContainer'):
+ selectedContainer = i
+
+ window.comboBox_videoContainer.setCurrentIndex(selectedContainer)
+ window.comboBox_videoContainer.currentIndexChanged.connect(
+ self.updateCodecs
+ )
+
+ self.updateCodecs()
+
+ for i in range(window.comboBox_videoCodec.count()):
+ codec = window.comboBox_videoCodec.itemText(i)
+ if codec == self.settings.value('outputVideoCodec'):
+ window.comboBox_videoCodec.setCurrentIndex(i)
+
+ for i in range(window.comboBox_audioCodec.count()):
+ codec = window.comboBox_audioCodec.itemText(i)
+ if codec == self.settings.value('outputAudioCodec'):
+ window.comboBox_audioCodec.setCurrentIndex(i)
+
+ window.comboBox_videoCodec.currentIndexChanged.connect(
+ self.updateCodecSettings
+ )
+
+ window.comboBox_audioCodec.currentIndexChanged.connect(
+ self.updateCodecSettings
+ )
+
+ vBitrate = int(self.settings.value('outputVideoBitrate'))
+ aBitrate = int(self.settings.value('outputAudioBitrate'))
+
+ window.spinBox_vBitrate.setValue(vBitrate)
+ window.spinBox_aBitrate.setValue(aBitrate)
+ window.spinBox_vBitrate.valueChanged.connect(self.updateCodecSettings)
+ window.spinBox_aBitrate.valueChanged.connect(self.updateCodecSettings)
+
+ # Make component buttons
+ self.compMenu = QMenu()
+ for i, comp in enumerate(self.core.modules):
+ action = self.compMenu.addAction(comp.Component.name)
+ action.triggered.connect(
+ lambda _, item=i: self.core.insertComponent(0, item, self)
+ )
+
+ self.window.pushButton_addComponent.setMenu(self.compMenu)
+
+ componentList.dropEvent = self.dragComponent
+ componentList.itemSelectionChanged.connect(
+ self.changeComponentWidget
+ )
+ componentList.itemSelectionChanged.connect(
+ self.presetManager.clearPresetListSelection
+ )
+ self.window.pushButton_removeComponent.clicked.connect(
+ lambda: self.removeComponent()
+ )
+
+ componentList.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+ componentList.customContextMenuRequested.connect(
+ self.componentContextMenu
+ )
+
+ currentRes = str(self.settings.value('outputWidth'))+'x' + \
+ str(self.settings.value('outputHeight'))
+ for i, res in enumerate(Core.resolutions):
+ window.comboBox_resolution.addItem(res)
+ if res == currentRes:
+ currentRes = i
+ window.comboBox_resolution.setCurrentIndex(currentRes)
+ window.comboBox_resolution.currentIndexChanged.connect(
+ self.updateResolution
+ )
+
+ self.window.pushButton_listMoveUp.clicked.connect(
+ lambda: self.moveComponent(-1)
+ )
+ self.window.pushButton_listMoveDown.clicked.connect(
+ lambda: self.moveComponent(1)
+ )
+
+ # Configure the Projects Menu
+ self.projectMenu = QMenu()
+ self.window.menuButton_newProject = self.projectMenu.addAction(
+ "New Project"
+ )
+ self.window.menuButton_newProject.triggered.connect(
+ lambda: self.createNewProject()
+ )
+ self.window.menuButton_openProject = self.projectMenu.addAction(
+ "Open Project"
+ )
+ self.window.menuButton_openProject.triggered.connect(
+ lambda: self.openOpenProjectDialog()
+ )
+
+ action = self.projectMenu.addAction("Save Project")
+ action.triggered.connect(self.saveCurrentProject)
+
+ action = self.projectMenu.addAction("Save Project As")
+ action.triggered.connect(self.openSaveProjectDialog)
+
+ self.window.pushButton_projects.setMenu(self.projectMenu)
+
+ # Configure the Presets Button
+ self.window.pushButton_presets.clicked.connect(
+ self.openPresetManager
+ )
+
+ self.updateWindowTitle()
+ window.show()
+
+ if project and project != self.autosavePath:
+ if not project.endswith('.avp'):
+ project += '.avp'
+ # open a project from the commandline
+ if not os.path.dirname(project):
+ project = os.path.join(
+ self.settings.value("projectDir"), project
+ )
+ self.currentProject = project
+ self.settings.setValue("currentProject", project)
+ if os.path.exists(self.autosavePath):
+ os.remove(self.autosavePath)
+ else:
+ # open the last currentProject from settings
+ self.currentProject = self.settings.value("currentProject")
+
+ # delete autosave if it's identical to this project
+ if self.autosaveExists(identical=True):
+ os.remove(self.autosavePath)
+
+ if self.currentProject and os.path.exists(self.autosavePath):
+ ch = self.showMessage(
+ msg="Restore unsaved changes in project '%s'?"
+ % os.path.basename(self.currentProject)[:-4],
+ showCancel=True)
+ if ch:
+ self.saveProjectChanges()
+ else:
+ os.remove(self.autosavePath)
+
+ self.openProject(self.currentProject, prompt=False)
+ self.drawPreview(True)
+
+ # verify Pillow version
+ if not self.settings.value("pilMsgShown") \
+ and 'post' not in Image.PILLOW_VERSION:
+ self.showMessage(
+ msg="You are using the standard version of the "
+ "Python imaging library (Pillow %s). Upgrade "
+ "to the Pillow-SIMD fork to enable hardware accelerations "
+ "and export videos faster." % Image.PILLOW_VERSION
+ )
+ self.settings.setValue("pilMsgShown", True)
+
+ # verify Ffmpeg version
+ if not self.settings.value("ffmpegMsgShown"):
+ try:
+ with open(os.devnull, "w") as f:
+ ffmpegVers = checkOutput(
+ ['ffmpeg', '-version'], stderr=f
+ )
+ goodVersion = str(ffmpegVers).split()[2].startswith('3')
+ except Exception:
+ goodVersion = False
+ else:
+ goodVersion = True
+
+ if not goodVersion:
+ self.showMessage(
+ msg="You're using an old version of Ffmpeg. "
+ "Some features may not work as expected."
+ )
+ self.settings.setValue("ffmpegMsgShown", True)
+
+ # Hotkeys for projects
+ QtWidgets.QShortcut("Ctrl+S", self.window, self.saveCurrentProject)
+ QtWidgets.QShortcut("Ctrl+A", self.window, self.openSaveProjectDialog)
+ QtWidgets.QShortcut("Ctrl+O", self.window, self.openOpenProjectDialog)
+ QtWidgets.QShortcut("Ctrl+N", self.window, self.createNewProject)
+
+ # Hotkeys for component list
+ for inskey in ("Ctrl+T", QtCore.Qt.Key_Insert):
+ QtWidgets.QShortcut(
+ inskey, self.window,
+ activated=lambda: self.window.pushButton_addComponent.click()
+ )
+ for delkey in ("Ctrl+R", QtCore.Qt.Key_Delete):
+ QtWidgets.QShortcut(
+ delkey, self.window.listWidget_componentList,
+ self.removeComponent
+ )
+ QtWidgets.QShortcut(
+ "Ctrl+Space", self.window,
+ activated=lambda: self.window.listWidget_componentList.setFocus()
+ )
+ QtWidgets.QShortcut(
+ "Ctrl+Shift+S", self.window,
+ self.presetManager.openSavePresetDialog
+ )
+ QtWidgets.QShortcut(
+ "Ctrl+Shift+C", self.window, self.presetManager.clearPreset
+ )
+
+ QtWidgets.QShortcut(
+ "Ctrl+Up", self.window.listWidget_componentList,
+ activated=lambda: self.moveComponent(-1)
+ )
+ QtWidgets.QShortcut(
+ "Ctrl+Down", self.window.listWidget_componentList,
+ activated=lambda: self.moveComponent(1)
+ )
+ QtWidgets.QShortcut(
+ "Ctrl+Home", self.window.listWidget_componentList,
+ activated=lambda: self.moveComponent('top')
+ )
+ QtWidgets.QShortcut(
+ "Ctrl+End", self.window.listWidget_componentList,
+ activated=lambda: self.moveComponent('bottom')
+ )
+
+ # Debug Hotkeys
+ QtWidgets.QShortcut(
+ "Ctrl+Alt+Shift+R", self.window, self.drawPreview
+ )
+ QtWidgets.QShortcut(
+ "Ctrl+Alt+Shift+F", self.window, self.showFfmpegCommand
+ )
+
+ @QtCore.pyqtSlot()
+ def cleanUp(self, *args):
+ self.timer.stop()
+ self.previewThread.quit()
+ self.previewThread.wait()
+
+ @disableWhenOpeningProject
+ def updateWindowTitle(self):
+ appName = 'Audio Visualizer'
+ try:
+ if self.currentProject:
+ appName += ' - %s' % \
+ os.path.splitext(
+ os.path.basename(self.currentProject))[0]
+ if self.autosaveExists(identical=False):
+ appName += '*'
+ except AttributeError:
+ pass
+ self.window.setWindowTitle(appName)
+
+ @QtCore.pyqtSlot(int, dict)
+ def updateComponentTitle(self, pos, presetStore=False):
+ if type(presetStore) == dict:
+ name = presetStore['preset']
+ if name is None or name not in self.core.savedPresets:
+ modified = False
+ else:
+ modified = (presetStore != self.core.savedPresets[name])
+ else:
+ modified = bool(presetStore)
+ if pos < 0:
+ pos = len(self.core.selectedComponents)-1
+ title = str(self.core.selectedComponents[pos])
+ if self.core.selectedComponents[pos].currentPreset:
+ title += ' - %s' % self.core.selectedComponents[pos].currentPreset
+ if modified:
+ title += '*'
+ self.window.listWidget_componentList.item(pos).setText(title)
+
+ def updateCodecs(self):
+ containerWidget = self.window.comboBox_videoContainer
+ vCodecWidget = self.window.comboBox_videoCodec
+ aCodecWidget = self.window.comboBox_audioCodec
+ index = containerWidget.currentIndex()
+ name = containerWidget.itemText(index)
+ self.settings.setValue('outputContainer', name)
+
+ vCodecWidget.clear()
+ aCodecWidget.clear()
+
+ for container in Core.encoderOptions['containers']:
+ if container['name'] == name:
+ for vCodec in container['video-codecs']:
+ vCodecWidget.addItem(vCodec)
+ for aCodec in container['audio-codecs']:
+ aCodecWidget.addItem(aCodec)
+
+ def updateCodecSettings(self):
+ '''Updates settings.ini to match encoder option widgets'''
+ vCodecWidget = self.window.comboBox_videoCodec
+ vBitrateWidget = self.window.spinBox_vBitrate
+ aBitrateWidget = self.window.spinBox_aBitrate
+ aCodecWidget = self.window.comboBox_audioCodec
+ currentVideoCodec = vCodecWidget.currentIndex()
+ currentVideoCodec = vCodecWidget.itemText(currentVideoCodec)
+ currentVideoBitrate = vBitrateWidget.value()
+ currentAudioCodec = aCodecWidget.currentIndex()
+ currentAudioCodec = aCodecWidget.itemText(currentAudioCodec)
+ currentAudioBitrate = aBitrateWidget.value()
+ self.settings.setValue('outputVideoCodec', currentVideoCodec)
+ self.settings.setValue('outputAudioCodec', currentAudioCodec)
+ self.settings.setValue('outputVideoBitrate', currentVideoBitrate)
+ self.settings.setValue('outputAudioBitrate', currentAudioBitrate)
+
+ @disableWhenOpeningProject
+ def autosave(self, force=False):
+ if not self.currentProject:
+ if os.path.exists(self.autosavePath):
+ os.remove(self.autosavePath)
+ elif force or time.time() - self.lastAutosave >= self.autosaveCooldown:
+ self.core.createProjectFile(self.autosavePath, self.window)
+ self.lastAutosave = time.time()
+ if len(self.autosaveTimes) >= 5:
+ # Do some math to reduce autosave spam. This gives a smooth
+ # curve up to 5 seconds cooldown and maintains that for 30 secs
+ # if a component is continuously updated
+ timeDiff = self.lastAutosave - self.autosaveTimes.pop()
+ if not force and timeDiff >= 1.0 \
+ and timeDiff <= 10.0:
+ if self.autosaveCooldown / 4.0 < 0.5:
+ self.autosaveCooldown += 1.0
+ self.autosaveCooldown = (
+ 5.0 * (self.autosaveCooldown / 5.0)
+ ) + (self.autosaveCooldown / 5.0) * 2
+ elif force or timeDiff >= self.autosaveCooldown * 5:
+ self.autosaveCooldown = 0.2
+ self.autosaveTimes.insert(0, self.lastAutosave)
+
+ def autosaveExists(self, identical=True):
+ '''Determines if creating the autosave should be blocked.'''
+ try:
+ if self.currentProject and os.path.exists(self.autosavePath) \
+ and filecmp.cmp(
+ self.autosavePath, self.currentProject) == identical:
+ return True
+ except FileNotFoundError:
+ print('project file couldn\'t be located:', self.currentProject)
+ return identical
+ return False
+
+ def saveProjectChanges(self):
+ '''Overwrites project file with autosave file'''
+ try:
+ os.remove(self.currentProject)
+ os.rename(self.autosavePath, self.currentProject)
+ return True
+ except (FileNotFoundError, IsADirectoryError) as e:
+ self.showMessage(
+ msg='Project file couldn\'t be saved.',
+ detail=str(e))
+ return False
+
+ def openInputFileDialog(self):
+ inputDir = self.settings.value("inputDir", os.path.expanduser("~"))
+
+ fileName, _ = QtWidgets.QFileDialog.getOpenFileName(
+ self.window, "Open Audio File",
+ inputDir, "Audio Files (%s)" % " ".join(Core.audioFormats))
+
+ if fileName:
+ self.settings.setValue("inputDir", os.path.dirname(fileName))
+ self.window.lineEdit_audioFile.setText(fileName)
+
+ def openOutputFileDialog(self):
+ outputDir = self.settings.value("outputDir", os.path.expanduser("~"))
+
+ fileName, _ = QtWidgets.QFileDialog.getSaveFileName(
+ self.window, "Set Output Video File",
+ outputDir,
+ "Video Files (%s);; All Files (*)" % " ".join(
+ Core.videoFormats))
+
+ if fileName:
+ self.settings.setValue("outputDir", os.path.dirname(fileName))
+ self.window.lineEdit_outputFile.setText(fileName)
+
+ def stopVideo(self):
+ print('stop')
+ self.videoWorker.cancel()
+ self.canceled = True
+
+ def createAudioVisualisation(self):
+ # create output video if mandatory settings are filled in
+ audioFile = self.window.lineEdit_audioFile.text()
+ outputPath = self.window.lineEdit_outputFile.text()
+
+ if audioFile and outputPath and self.core.selectedComponents:
+ if not os.path.dirname(outputPath):
+ outputPath = os.path.join(
+ os.path.expanduser("~"), outputPath)
+ if outputPath and os.path.isdir(outputPath):
+ self.showMessage(
+ msg='Chosen filename matches a directory, which '
+ 'cannot be overwritten. Please choose a different '
+ 'filename or move the directory.',
+ icon='Warning',
+ )
+ return
+ else:
+ if not audioFile or not outputPath:
+ self.showMessage(
+ msg="You must select an audio file and output filename."
+ )
+ elif not self.core.selectedComponents:
+ self.showMessage(
+ msg="Not enough components."
+ )
+ return
+
+ self.canceled = False
+ self.progressBarUpdated(-1)
+ self.videoWorker = self.core.newVideoWorker(
+ self, audioFile, outputPath
+ )
+ self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated)
+ self.videoWorker.progressBarSetText.connect(
+ self.progressBarSetText)
+ self.videoWorker.imageCreated.connect(self.showPreviewImage)
+ self.videoWorker.encoding.connect(self.changeEncodingStatus)
+ self.createVideo.emit()
+
+ @QtCore.pyqtSlot(str, str)
+ def videoThreadError(self, msg, detail):
+ try:
+ self.stopVideo()
+ except AttributeError as e:
+ if 'videoWorker' not in str(e):
+ raise
+ self.showMessage(
+ msg=msg,
+ detail=detail,
+ icon='Critical',
+ )
+
+ def changeEncodingStatus(self, status):
+ self.encoding = status
+ if status:
+ self.window.pushButton_createVideo.setEnabled(False)
+ self.window.pushButton_Cancel.setEnabled(True)
+ self.window.comboBox_resolution.setEnabled(False)
+ self.window.stackedWidget.setEnabled(False)
+ self.window.tab_encoderSettings.setEnabled(False)
+ self.window.label_audioFile.setEnabled(False)
+ self.window.toolButton_selectAudioFile.setEnabled(False)
+ self.window.label_outputFile.setEnabled(False)
+ self.window.toolButton_selectOutputFile.setEnabled(False)
+ self.window.lineEdit_audioFile.setEnabled(False)
+ self.window.lineEdit_outputFile.setEnabled(False)
+ self.window.pushButton_addComponent.setEnabled(False)
+ self.window.pushButton_removeComponent.setEnabled(False)
+ self.window.pushButton_listMoveDown.setEnabled(False)
+ self.window.pushButton_listMoveUp.setEnabled(False)
+ self.window.menuButton_newProject.setEnabled(False)
+ self.window.menuButton_openProject.setEnabled(False)
+ if sys.platform == 'darwin':
+ self.window.progressLabel.setHidden(False)
+ else:
+ self.window.listWidget_componentList.setEnabled(False)
+ else:
+ self.window.pushButton_createVideo.setEnabled(True)
+ self.window.pushButton_Cancel.setEnabled(False)
+ self.window.comboBox_resolution.setEnabled(True)
+ self.window.stackedWidget.setEnabled(True)
+ self.window.tab_encoderSettings.setEnabled(True)
+ self.window.label_audioFile.setEnabled(True)
+ self.window.toolButton_selectAudioFile.setEnabled(True)
+ self.window.lineEdit_audioFile.setEnabled(True)
+ self.window.label_outputFile.setEnabled(True)
+ self.window.toolButton_selectOutputFile.setEnabled(True)
+ self.window.lineEdit_outputFile.setEnabled(True)
+ self.window.pushButton_addComponent.setEnabled(True)
+ self.window.pushButton_removeComponent.setEnabled(True)
+ self.window.pushButton_listMoveDown.setEnabled(True)
+ self.window.pushButton_listMoveUp.setEnabled(True)
+ self.window.menuButton_newProject.setEnabled(True)
+ self.window.menuButton_openProject.setEnabled(True)
+ self.window.listWidget_componentList.setEnabled(True)
+ self.window.progressLabel.setHidden(True)
+ self.drawPreview(True)
+
+ @QtCore.pyqtSlot(int)
+ def progressBarUpdated(self, value):
+ self.window.progressBar_createVideo.setValue(value)
+
+ @QtCore.pyqtSlot(str)
+ def progressBarSetText(self, value):
+ if sys.platform == 'darwin':
+ self.window.progressLabel.setText(value)
+ else:
+ self.window.progressBar_createVideo.setFormat(value)
+
+ def updateResolution(self):
+ resIndex = int(self.window.comboBox_resolution.currentIndex())
+ res = Core.resolutions[resIndex].split('x')
+ changed = res[0] != self.settings.value("outputWidth")
+ self.settings.setValue('outputWidth', res[0])
+ self.settings.setValue('outputHeight', res[1])
+ if changed:
+ for i in range(len(self.core.selectedComponents)):
+ self.core.updateComponent(i)
+
+ def drawPreview(self, force=False, **kwargs):
+ '''Use autosave keyword arg to force saving or not saving if needed'''
+ self.newTask.emit(self.core.selectedComponents)
+ # self.processTask.emit()
+ if force or 'autosave' in kwargs:
+ if force or kwargs['autosave']:
+ self.autosave(True)
+ else:
+ self.autosave()
+ self.updateWindowTitle()
+
+ @QtCore.pyqtSlot(QtGui.QImage)
+ def showPreviewImage(self, image):
+ self.previewWindow.changePixmap(image)
+
+ def showFfmpegCommand(self):
+ from textwrap import wrap
+ from toolkit.ffmpeg import createFfmpegCommand
+ command = createFfmpegCommand(
+ self.window.lineEdit_audioFile.text(),
+ self.window.lineEdit_outputFile.text(),
+ self.core.selectedComponents
+ )
+ lines = wrap(" ".join(command), 49)
+ self.showMessage(
+ msg="Current FFmpeg command:\n\n %s" % " ".join(lines)
+ )
+
+ def insertComponent(self, index):
+ componentList = self.window.listWidget_componentList
+ stackedWidget = self.window.stackedWidget
+
+ componentList.insertItem(
+ index,
+ self.core.selectedComponents[index].name)
+ componentList.setCurrentRow(index)
+
+ # connect to signal that adds an asterisk when modified
+ self.core.selectedComponents[index].modified.connect(
+ self.updateComponentTitle)
+
+ self.pages.insert(index, self.core.selectedComponents[index].page)
+ stackedWidget.insertWidget(index, self.pages[index])
+ stackedWidget.setCurrentIndex(index)
+
+ return index
+
+ def removeComponent(self):
+ componentList = self.window.listWidget_componentList
+
+ for selected in componentList.selectedItems():
+ index = componentList.row(selected)
+ self.window.stackedWidget.removeWidget(self.pages[index])
+ componentList.takeItem(index)
+ self.core.removeComponent(index)
+ self.pages.pop(index)
+ self.changeComponentWidget()
+ self.drawPreview()
+
+ @disableWhenEncoding
+ def moveComponent(self, change):
+ '''Moves a component relatively from its current position'''
+ componentList = self.window.listWidget_componentList
+ if change == 'top':
+ change = -componentList.currentRow()
+ elif change == 'bottom':
+ change = len(componentList)-componentList.currentRow()-1
+ stackedWidget = self.window.stackedWidget
+
+ row = componentList.currentRow()
+ newRow = row + change
+ if newRow > -1 and newRow < componentList.count():
+ self.core.moveComponent(row, newRow)
+
+ # update widgets
+ page = self.pages.pop(row)
+ self.pages.insert(newRow, page)
+ item = componentList.takeItem(row)
+ newItem = componentList.insertItem(newRow, item)
+ widget = stackedWidget.removeWidget(page)
+ stackedWidget.insertWidget(newRow, page)
+ componentList.setCurrentRow(newRow)
+ stackedWidget.setCurrentIndex(newRow)
+ self.drawPreview(True)
+
+ def getComponentListMousePos(self, position):
+ '''
+ Given a QPos, returns the component index under the mouse cursor
+ or -1 if no component is there.
+ '''
+ componentList = self.window.listWidget_componentList
+
+ modelIndexes = [
+ componentList.model().index(i)
+ for i in range(componentList.count())
+ ]
+ rects = [
+ componentList.visualRect(modelIndex)
+ for modelIndex in modelIndexes
+ ]
+ mousePos = [rect.contains(position) for rect in rects]
+ if not any(mousePos):
+ # Not clicking a component
+ mousePos = -1
+ else:
+ mousePos = mousePos.index(True)
+ return mousePos
+
+ @disableWhenEncoding
+ def dragComponent(self, event):
+ '''Used as Qt drop event for the component listwidget'''
+ componentList = self.window.listWidget_componentList
+ mousePos = self.getComponentListMousePos(event.pos())
+ if mousePos > -1:
+ change = (componentList.currentRow() - mousePos) * -1
+ else:
+ change = (componentList.count() - componentList.currentRow() - 1)
+ self.moveComponent(change)
+
+ def changeComponentWidget(self):
+ selected = self.window.listWidget_componentList.selectedItems()
+ if selected:
+ index = self.window.listWidget_componentList.row(selected[0])
+ self.window.stackedWidget.setCurrentIndex(index)
+
+ def openPresetManager(self):
+ '''Preset manager for importing, exporting, renaming, deleting'''
+ self.presetManager.show()
+
+ def clear(self):
+ '''Get a blank slate'''
+ self.core.clearComponents()
+ self.window.listWidget_componentList.clear()
+ for widget in self.pages:
+ self.window.stackedWidget.removeWidget(widget)
+ self.pages = []
+ for field in (
+ self.window.lineEdit_audioFile,
+ self.window.lineEdit_outputFile
+ ):
+ field.blockSignals(True)
+ field.setText('')
+ field.blockSignals(False)
+ self.progressBarUpdated(0)
+ self.progressBarSetText('')
+
+ @disableWhenEncoding
+ def createNewProject(self, prompt=True):
+ if prompt:
+ self.openSaveChangesDialog('starting a new project')
+
+ self.clear()
+ self.currentProject = None
+ self.settings.setValue("currentProject", None)
+ self.drawPreview(True)
+
+ def saveCurrentProject(self):
+ if self.currentProject:
+ self.core.createProjectFile(self.currentProject, self.window)
+ try:
+ os.remove(self.autosavePath)
+ except FileNotFoundError:
+ pass
+ self.updateWindowTitle()
+ else:
+ self.openSaveProjectDialog()
+
+ def openSaveChangesDialog(self, phrase):
+ success = True
+ if self.autosaveExists(identical=False):
+ ch = self.showMessage(
+ msg="You have unsaved changes in project '%s'. "
+ "Save before %s?" % (
+ os.path.basename(self.currentProject)[:-4],
+ phrase
+ ),
+ showCancel=True)
+ if ch:
+ success = self.saveProjectChanges()
+
+ if success and os.path.exists(self.autosavePath):
+ os.remove(self.autosavePath)
+
+ def openSaveProjectDialog(self):
+ filename, _ = QtWidgets.QFileDialog.getSaveFileName(
+ self.window, "Create Project File",
+ self.settings.value("projectDir"),
+ "Project Files (*.avp)")
+ if not filename:
+ return
+ if not filename.endswith(".avp"):
+ filename += '.avp'
+ self.settings.setValue("projectDir", os.path.dirname(filename))
+ self.settings.setValue("currentProject", filename)
+ self.currentProject = filename
+ self.core.createProjectFile(filename, self.window)
+ self.updateWindowTitle()
+
+ @disableWhenEncoding
+ def openOpenProjectDialog(self):
+ filename, _ = QtWidgets.QFileDialog.getOpenFileName(
+ self.window, "Open Project File",
+ self.settings.value("projectDir"),
+ "Project Files (*.avp)")
+ self.openProject(filename)
+
+ def openProject(self, filepath, prompt=True):
+ if not filepath or not os.path.exists(filepath) \
+ or not filepath.endswith('.avp'):
+ return
+
+ self.clear()
+ # ask to save any changes that are about to get deleted
+ if prompt:
+ self.openSaveChangesDialog('opening another project')
+
+ self.currentProject = filepath
+ self.settings.setValue("currentProject", filepath)
+ self.settings.setValue("projectDir", os.path.dirname(filepath))
+ # actually load the project using core method
+ self.core.openProject(self, filepath)
+ self.drawPreview(autosave=False)
+ self.updateWindowTitle()
+
+ def showMessage(self, **kwargs):
+ parent = kwargs['parent'] if 'parent' in kwargs else self.window
+ msg = QtWidgets.QMessageBox(parent)
+ msg.setModal(True)
+ msg.setText(kwargs['msg'])
+ msg.setIcon(
+ eval('QtWidgets.QMessageBox.%s' % kwargs['icon'])
+ if 'icon' in kwargs else QtWidgets.QMessageBox.Information
+ )
+ msg.setDetailedText(kwargs['detail'] if 'detail' in kwargs else None)
+ if 'showCancel'in kwargs and kwargs['showCancel']:
+ msg.setStandardButtons(
+ QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
+ else:
+ msg.setStandardButtons(QtWidgets.QMessageBox.Ok)
+ ch = msg.exec_()
+ if ch == 1024:
+ return True
+ return False
+
+ @disableWhenEncoding
+ def componentContextMenu(self, QPos):
+ '''Appears when right-clicking the component list'''
+ componentList = self.window.listWidget_componentList
+ self.menu = QMenu()
+ parentPosition = componentList.mapToGlobal(QtCore.QPoint(0, 0))
+
+ index = self.getComponentListMousePos(QPos)
+ if index > -1:
+ # Show preset menu if clicking a component
+ self.presetManager.findPresets()
+ menuItem = self.menu.addAction("Save Preset")
+ menuItem.triggered.connect(
+ self.presetManager.openSavePresetDialog
+ )
+
+ # submenu for opening presets
+ try:
+ presets = self.presetManager.presets[
+ str(self.core.selectedComponents[index])
+ ]
+ self.presetSubmenu = QMenu("Open Preset")
+ self.menu.addMenu(self.presetSubmenu)
+
+ for version, presetName in presets:
+ menuItem = self.presetSubmenu.addAction(presetName)
+ menuItem.triggered.connect(
+ lambda _, presetName=presetName:
+ self.presetManager.openPreset(presetName)
+ )
+ except KeyError:
+ pass
+
+ if self.core.selectedComponents[index].currentPreset:
+ menuItem = self.menu.addAction("Clear Preset")
+ menuItem.triggered.connect(
+ self.presetManager.clearPreset
+ )
+ self.menu.addSeparator()
+
+ # "Add Component" submenu
+ self.submenu = QMenu("Add")
+ self.menu.addMenu(self.submenu)
+ insertCompAtTop = self.settings.value("pref_insertCompAtTop")
+ for i, comp in enumerate(self.core.modules):
+ menuItem = self.submenu.addAction(comp.Component.name)
+ menuItem.triggered.connect(
+ lambda _, item=i: self.core.insertComponent(
+ 0 if insertCompAtTop else index, item, self
+ )
+ )
+
+ self.menu.move(parentPosition + QPos)
+ self.menu.show()
+
+ def eventFilter(self, object, event):
+ if event.type() == QtCore.QEvent.WindowActivate \
+ or event.type() == QtCore.QEvent.FocusIn:
+ Core.windowHasFocus = True
+ elif event.type() == QtCore.QEvent.WindowDeactivate \
+ or event.type() == QtCore.QEvent.FocusOut:
+ Core.windowHasFocus = False
+ return False
diff --git a/src/components/life.py b/src/components/life.py
index 1e72620..89a4c5c 100644
--- a/src/components/life.py
+++ b/src/components/life.py
@@ -1,10 +1,10 @@
from PyQt5 import QtGui, QtCore, QtWidgets
-from PIL import ImageDraw, ImageEnhance, ImageChops, ImageFilter
+from PIL import Image, ImageDraw, ImageEnhance, ImageChops, ImageFilter
import os
import math
from component import Component
-from toolkit.frame import BlankFrame, FramePainter
+from toolkit.frame import BlankFrame, scale
class Component(Component):
@@ -16,19 +16,51 @@ class Component(Component):
self.scale = 32
self.updateGridSize()
self.startingGrid = {}
+ 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,
+ 'image': self.page.lineEdit_image,
}, colorWidgets={
'color': self.page.pushButton_color,
})
self.page.spinBox_scale.setValue(self.scale)
+ self.page.spinBox_scale.valueChanged.connect(self.updateGridSize)
+
+ def pickImage(self):
+ imgDir = self.settings.value("componentDir", os.path.expanduser("~"))
+ filename, _ = QtWidgets.QFileDialog.getOpenFileName(
+ self.page, "Choose Image", imgDir,
+ "Image Files (%s)" % " ".join(self.core.imageFormats))
+ if filename:
+ self.settings.setValue("componentDir", os.path.dirname(filename))
+ self.page.lineEdit_image.setText(filename)
+ self.update()
def update(self):
self.updateGridSize()
+ if self.page.checkBox_customImg.isChecked():
+ self.page.label_color.setVisible(False)
+ self.page.lineEdit_color.setVisible(False)
+ self.page.pushButton_color.setVisible(False)
+ self.page.label_shape.setVisible(False)
+ self.page.comboBox_shapeType.setVisible(False)
+ self.page.label_image.setVisible(True)
+ self.page.lineEdit_image.setVisible(True)
+ self.page.pushButton_pickImage.setVisible(True)
+ else:
+ self.page.label_color.setVisible(True)
+ self.page.lineEdit_color.setVisible(True)
+ self.page.pushButton_color.setVisible(True)
+ self.page.label_shape.setVisible(True)
+ self.page.comboBox_shapeType.setVisible(True)
+ self.page.label_image.setVisible(False)
+ self.page.lineEdit_image.setVisible(False)
+ self.page.pushButton_pickImage.setVisible(False)
super().update()
def previewClickEvent(self, pos, size, button):
@@ -59,6 +91,8 @@ class Component(Component):
for frameNo in range(
self.tickRate, len(self.completeAudioArray), self.sampleSize
):
+ if self.parent.canceled:
+ break
if frameNo % self.tickRate == 0:
tick += 1
self.tickGrids[tick] = self.gridForTick(tick)
@@ -71,6 +105,16 @@ class Component(Component):
self.progressBarSetText.emit(pStr)
self.progressBarUpdate.emit(int(progress))
+ def properties(self):
+ if self.customImg and (
+ not self.image or not os.path.exists(self.image)
+ ):
+ return ['error']
+ return []
+
+ def error(self):
+ return "No image selected to represent life."
+
def frameRender(self, frameNo):
tick = math.floor(frameNo / self.tickRate)
grid = self.tickGrids[tick]
@@ -78,23 +122,124 @@ class Component(Component):
def drawGrid(self, grid):
frame = BlankFrame(self.width, self.height)
- drawer = ImageDraw.Draw(frame)
+
+ def drawCustomImg():
+ try:
+ img = Image.open(self.image)
+ except Exception:
+ return
+ img = img.resize((self.pxWidth, self.pxHeight), Image.ANTIALIAS)
+ frame.paste(img, box=(drawPtX, drawPtY))
+
+ def drawShape():
+ drawer = ImageDraw.Draw(frame)
+
+ # Rectangle
+ if self.shapeType == 0:
+ drawer.rectangle(rect, fill=self.color)
+
+ # Ellipse
+ elif self.shapeType == 1:
+ drawer.ellipse(rect, fill=self.color)
+
+ tenthX, tenthY = scale(10, self.pxWidth, self.pxHeight, int)
+ smallerShape = (
+ (drawPtX + tenthX + int(tenthX / 4),
+ drawPtY + tenthY + int(tenthY / 2)),
+ (drawPtX + self.pxWidth - tenthX - int(tenthX / 4),
+ drawPtY + self.pxHeight - (tenthY + int(tenthY / 2)))
+ )
+ outlineShape = (
+ (drawPtX + int(tenthX / 4),
+ drawPtY + int(tenthY / 2)),
+ (drawPtX + self.pxWidth - int(tenthX / 4),
+ drawPtY + self.pxHeight - int(tenthY / 2))
+ )
+
+ # Circle
+ if self.shapeType == 2:
+ drawer.ellipse(outlineShape, fill=self.color)
+ drawer.ellipse(smallerShape, fill=(0,0,0,0))
+
+ # Lilypad
+ elif self.shapeType == 3:
+ drawer.pieslice(smallerShape, 290, 250, fill=self.color)
+
+ # Pac-Man
+ elif self.shapeType == 4:
+ drawer.pieslice(outlineShape, 35, 320, fill=self.color)
+
+ hX, hY = scale(50, self.pxWidth, self.pxHeight, int) # halfline
+ tX, tY = scale(33, self.pxWidth, self.pxHeight, int) # thirdline
+ qX, qY = scale(20, self.pxWidth, self.pxHeight, int) # quarterline
+
+ # Duck
+ if self.shapeType == 5:
+ duckHead = (
+ (drawPtX + qX, drawPtY + qY),
+ (drawPtX + int(qX * 3), drawPtY + int(tY * 2))
+ )
+ duckBeak = (
+ (drawPtX + hX, drawPtY + qY),
+ (drawPtX + self.pxWidth + qX,
+ drawPtY + int(qY * 3))
+ )
+ duckWing = (
+ (drawPtX, drawPtY + hY),
+ rect[1]
+ )
+ duckBody = (
+ (drawPtX + int(qX / 4), drawPtY + int(qY * 3)),
+ (drawPtX + int(tX * 2), drawPtY + self.pxHeight)
+ )
+ drawer.ellipse(duckBody, fill=self.color)
+ drawer.ellipse(duckHead, fill=self.color)
+ drawer.pieslice(duckWing, 130, 200, fill=self.color)
+ drawer.pieslice(duckBeak, 145, 200, fill=self.color)
+
+ # Peace
+ elif self.shapeType == 6:
+ line = (
+ (drawPtX + hX - int(tenthX / 2), drawPtY + int(tenthY / 2)),
+ (drawPtX + hX + int(tenthX / 2),
+ drawPtY + self.pxHeight - int(tenthY / 2))
+ )
+ drawer.ellipse(outlineShape, fill=self.color)
+ drawer.ellipse(smallerShape, fill=(0,0,0,0))
+ drawer.rectangle(line, fill=self.color)
+ slantLine = lambda difference: (
+ ((drawPtX + difference),
+ (drawPtY + self.pxHeight - qY)),
+ ((drawPtX + hX),
+ (drawPtY + hY)),
+ )
+ drawer.line(
+ slantLine(qX),
+ fill=self.color,
+ width=tenthX
+ )
+ drawer.line(
+ slantLine(self.pxWidth - qX),
+ fill=self.color,
+ width=tenthX
+ )
for x, y in grid:
drawPtX = x * self.pxWidth
+ if drawPtX > self.width:
+ continue
drawPtY = y * self.pxHeight
+ if drawPtY > self.height:
+ continue
rect = (
(drawPtX, drawPtY),
(drawPtX + self.pxWidth, drawPtY + self.pxHeight)
)
- if self.shapeType == 0:
- drawer.rectangle(rect, fill=self.color)
- elif self.shapeType == 1:
- drawer.ellipse(rect, fill=self.color)
- elif self.shapeType == 2:
- drawer.pieslice(rect, 290, 250, fill=self.color)
- elif self.shapeType == 3:
- drawer.pieslice(rect, 20, 340, fill=self.color)
+
+ if self.customImg:
+ drawCustomImg()
+ else:
+ drawShape()
if self.shadow:
shadImg = ImageEnhance.Contrast(frame).enhance(0.0)
diff --git a/src/components/life.ui b/src/components/life.ui
index 88f8eca..2341c19 100644
--- a/src/components/life.ui
+++ b/src/components/life.ui
@@ -39,7 +39,7 @@
30
- 15
+ 5
@@ -93,6 +93,13 @@
+ -
+
+
+ Custom Image
+
+
+
-
@@ -111,7 +118,36 @@
-
-
-
+
+
+ Image
+
+
+
+ -
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 32
+ 32
+
+
+
+ ...
+
+
+
+ -
+
Color
@@ -169,7 +205,7 @@
-
-
+
Shape
@@ -182,6 +218,11 @@
Rectangle
+ -
+
+ Ellipse
+
+
-
Circle
@@ -197,6 +238,16 @@
Pac-Man
+ -
+
+ Duck
+
+
+ -
+
+ Peace
+
+
-
--
cgit v1.2.3
From 9732f3bdebc7fbeb944d71f313d3c0797b3dbcd4 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 10 Aug 2017 00:48:07 -0400
Subject: rm garbage file
---
.gitignore | 1 +
src/.goutputstream-67IS4Y | 976 ----------------------------------------------
2 files changed, 1 insertion(+), 976 deletions(-)
delete mode 100644 src/.goutputstream-67IS4Y
diff --git a/.gitignore b/.gitignore
index 7cec615..916c6c1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,4 @@ env/*
ffmpeg
*.bak
*~
+*.goutput*
\ No newline at end of file
diff --git a/src/.goutputstream-67IS4Y b/src/.goutputstream-67IS4Y
deleted file mode 100644
index 789a6e7..0000000
--- a/src/.goutputstream-67IS4Y
+++ /dev/null
@@ -1,976 +0,0 @@
-'''
- When using GUI mode, this module's object (the main window) takes
- user input to construct a program state (stored in the Core object).
- This shows a preview of the video being created and allows for saving
- projects and exporting the video at a later time.
-'''
-from PyQt5 import QtCore, QtGui, uic, QtWidgets
-from PyQt5.QtWidgets import QMenu, QShortcut
-from PIL import Image
-from queue import Queue
-import sys
-import os
-import signal
-import filecmp
-import time
-
-from core import Core
-import preview_thread
-from presetmanager import PresetManager
-from toolkit import disableWhenEncoding, disableWhenOpeningProject, checkOutput
-
-
-class PreviewWindow(QtWidgets.QLabel):
- '''
- Paints the preview QLabel and maintains the aspect ratio when the
- window is resized.
- '''
-
- def __init__(self, parent, img):
- super(PreviewWindow, self).__init__()
- self.parent = parent
- self.setFrameStyle(QtWidgets.QFrame.StyledPanel)
- self.pixmap = QtGui.QPixmap(img)
-
- def paintEvent(self, event):
- size = self.size()
- painter = QtGui.QPainter(self)
- point = QtCore.QPoint(0, 0)
- scaledPix = self.pixmap.scaled(
- size,
- QtCore.Qt.KeepAspectRatio,
- transformMode=QtCore.Qt.SmoothTransformation)
-
- # start painting the label from left upper corner
- point.setX((size.width() - scaledPix.width())/2)
- point.setY((size.height() - scaledPix.height())/2)
- painter.drawPixmap(point, scaledPix)
-
- def changePixmap(self, img):
- self.pixmap = QtGui.QPixmap(img)
- self.repaint()
-
- def mousePressEvent(self, event):
- if self.parent.encoding:
- return
-
- i = self.parent.window.listWidget_componentList.currentRow()
- if i >= 0:
- component = self.parent.core.selectedComponents[i]
- if not hasattr(component, 'previewClickEvent'):
- return
- pos = (event.x(), event.y())
- size = (self.width(), self.height())
- component.previewClickEvent(
- pos, size, event.button()
- )
- self.parent.core.updateComponent(i)
-
- @QtCore.pyqtSlot(str)
- def threadError(self, msg):
- self.parent.showMessage(
- msg=msg,
- icon='Critical',
- parent=self
- )
-
-
-class MainWindow(QtWidgets.QMainWindow):
- '''
- The MainWindow wraps many Core methods in order to update the GUI
- accordingly. E.g., instead of self.core.openProject(), it will use
- self.openProject() and update the window titlebar within the wrapper.
-
- MainWindow manages the autosave feature, although Core has the
- primary functions for opening and creating project files.
- '''
-
- createVideo = QtCore.pyqtSignal()
- newTask = QtCore.pyqtSignal(list) # for the preview window
- processTask = QtCore.pyqtSignal()
-
- def __init__(self, window, project):
- QtWidgets.QMainWindow.__init__(self)
- # print('main thread id: {}'.format(QtCore.QThread.currentThreadId()))
- self.window = window
- self.core = Core()
-
- # widgets of component settings
- self.pages = []
- self.lastAutosave = time.time()
- # list of previous five autosave times, used to reduce update spam
- self.autosaveTimes = []
- self.autosaveCooldown = 0.2
- self.encoding = False
-
- # Create data directory, load/create settings
- self.dataDir = Core.dataDir
- self.presetDir = Core.presetDir
- self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
- self.settings = Core.settings
- self.presetManager = PresetManager(
- uic.loadUi(
- os.path.join(Core.wd, 'presetmanager.ui')), self)
-
- if not os.path.exists(self.dataDir):
- os.makedirs(self.dataDir)
- for neededDirectory in (
- self.presetDir, self.settings.value("projectDir")):
- if not os.path.exists(neededDirectory):
- os.mkdir(neededDirectory)
-
- # Create the preview window and its thread, queues, and timers
- self.previewWindow = PreviewWindow(self, os.path.join(
- Core.wd, "background.png"))
- window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
-
- self.previewQueue = Queue()
- self.previewThread = QtCore.QThread(self)
- self.previewWorker = preview_thread.Worker(self, self.previewQueue)
- self.previewWorker.error.connect(self.previewWindow.threadError)
- self.previewWorker.moveToThread(self.previewThread)
- self.previewWorker.imageCreated.connect(self.showPreviewImage)
- self.previewThread.start()
-
- self.timer = QtCore.QTimer(self)
- self.timer.timeout.connect(self.processTask.emit)
- self.timer.start(500)
-
- # Begin decorating the window and connecting events
- self.window.installEventFilter(self)
- componentList = self.window.listWidget_componentList
-
- if sys.platform == 'darwin':
- window.progressBar_createVideo.setTextVisible(False)
- else:
- window.progressLabel.setHidden(True)
-
- window.toolButton_selectAudioFile.clicked.connect(
- self.openInputFileDialog)
-
- window.toolButton_selectOutputFile.clicked.connect(
- self.openOutputFileDialog)
-
- def changedField():
- self.autosave()
- self.updateWindowTitle()
-
- window.lineEdit_audioFile.textChanged.connect(changedField)
- window.lineEdit_outputFile.textChanged.connect(changedField)
-
- window.progressBar_createVideo.setValue(0)
-
- window.pushButton_createVideo.clicked.connect(
- self.createAudioVisualisation)
-
- window.pushButton_Cancel.clicked.connect(self.stopVideo)
-
- for i, container in enumerate(Core.encoderOptions['containers']):
- window.comboBox_videoContainer.addItem(container['name'])
- if container['name'] == self.settings.value('outputContainer'):
- selectedContainer = i
-
- window.comboBox_videoContainer.setCurrentIndex(selectedContainer)
- window.comboBox_videoContainer.currentIndexChanged.connect(
- self.updateCodecs
- )
-
- self.updateCodecs()
-
- for i in range(window.comboBox_videoCodec.count()):
- codec = window.comboBox_videoCodec.itemText(i)
- if codec == self.settings.value('outputVideoCodec'):
- window.comboBox_videoCodec.setCurrentIndex(i)
-
- for i in range(window.comboBox_audioCodec.count()):
- codec = window.comboBox_audioCodec.itemText(i)
- if codec == self.settings.value('outputAudioCodec'):
- window.comboBox_audioCodec.setCurrentIndex(i)
-
- window.comboBox_videoCodec.currentIndexChanged.connect(
- self.updateCodecSettings
- )
-
- window.comboBox_audioCodec.currentIndexChanged.connect(
- self.updateCodecSettings
- )
-
- vBitrate = int(self.settings.value('outputVideoBitrate'))
- aBitrate = int(self.settings.value('outputAudioBitrate'))
-
- window.spinBox_vBitrate.setValue(vBitrate)
- window.spinBox_aBitrate.setValue(aBitrate)
- window.spinBox_vBitrate.valueChanged.connect(self.updateCodecSettings)
- window.spinBox_aBitrate.valueChanged.connect(self.updateCodecSettings)
-
- # Make component buttons
- self.compMenu = QMenu()
- for i, comp in enumerate(self.core.modules):
- action = self.compMenu.addAction(comp.Component.name)
- action.triggered.connect(
- lambda _, item=i: self.core.insertComponent(0, item, self)
- )
-
- self.window.pushButton_addComponent.setMenu(self.compMenu)
-
- componentList.dropEvent = self.dragComponent
- componentList.itemSelectionChanged.connect(
- self.changeComponentWidget
- )
- componentList.itemSelectionChanged.connect(
- self.presetManager.clearPresetListSelection
- )
- self.window.pushButton_removeComponent.clicked.connect(
- lambda: self.removeComponent()
- )
-
- componentList.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
- componentList.customContextMenuRequested.connect(
- self.componentContextMenu
- )
-
- currentRes = str(self.settings.value('outputWidth'))+'x' + \
- str(self.settings.value('outputHeight'))
- for i, res in enumerate(Core.resolutions):
- window.comboBox_resolution.addItem(res)
- if res == currentRes:
- currentRes = i
- window.comboBox_resolution.setCurrentIndex(currentRes)
- window.comboBox_resolution.currentIndexChanged.connect(
- self.updateResolution
- )
-
- self.window.pushButton_listMoveUp.clicked.connect(
- lambda: self.moveComponent(-1)
- )
- self.window.pushButton_listMoveDown.clicked.connect(
- lambda: self.moveComponent(1)
- )
-
- # Configure the Projects Menu
- self.projectMenu = QMenu()
- self.window.menuButton_newProject = self.projectMenu.addAction(
- "New Project"
- )
- self.window.menuButton_newProject.triggered.connect(
- lambda: self.createNewProject()
- )
- self.window.menuButton_openProject = self.projectMenu.addAction(
- "Open Project"
- )
- self.window.menuButton_openProject.triggered.connect(
- lambda: self.openOpenProjectDialog()
- )
-
- action = self.projectMenu.addAction("Save Project")
- action.triggered.connect(self.saveCurrentProject)
-
- action = self.projectMenu.addAction("Save Project As")
- action.triggered.connect(self.openSaveProjectDialog)
-
- self.window.pushButton_projects.setMenu(self.projectMenu)
-
- # Configure the Presets Button
- self.window.pushButton_presets.clicked.connect(
- self.openPresetManager
- )
-
- self.updateWindowTitle()
- window.show()
-
- if project and project != self.autosavePath:
- if not project.endswith('.avp'):
- project += '.avp'
- # open a project from the commandline
- if not os.path.dirname(project):
- project = os.path.join(
- self.settings.value("projectDir"), project
- )
- self.currentProject = project
- self.settings.setValue("currentProject", project)
- if os.path.exists(self.autosavePath):
- os.remove(self.autosavePath)
- else:
- # open the last currentProject from settings
- self.currentProject = self.settings.value("currentProject")
-
- # delete autosave if it's identical to this project
- if self.autosaveExists(identical=True):
- os.remove(self.autosavePath)
-
- if self.currentProject and os.path.exists(self.autosavePath):
- ch = self.showMessage(
- msg="Restore unsaved changes in project '%s'?"
- % os.path.basename(self.currentProject)[:-4],
- showCancel=True)
- if ch:
- self.saveProjectChanges()
- else:
- os.remove(self.autosavePath)
-
- self.openProject(self.currentProject, prompt=False)
- self.drawPreview(True)
-
- # verify Pillow version
- if not self.settings.value("pilMsgShown") \
- and 'post' not in Image.PILLOW_VERSION:
- self.showMessage(
- msg="You are using the standard version of the "
- "Python imaging library (Pillow %s). Upgrade "
- "to the Pillow-SIMD fork to enable hardware accelerations "
- "and export videos faster." % Image.PILLOW_VERSION
- )
- self.settings.setValue("pilMsgShown", True)
-
- # verify Ffmpeg version
- if not self.settings.value("ffmpegMsgShown"):
- try:
- with open(os.devnull, "w") as f:
- ffmpegVers = checkOutput(
- ['ffmpeg', '-version'], stderr=f
- )
- goodVersion = str(ffmpegVers).split()[2].startswith('3')
- except Exception:
- goodVersion = False
- else:
- goodVersion = True
-
- if not goodVersion:
- self.showMessage(
- msg="You're using an old version of Ffmpeg. "
- "Some features may not work as expected."
- )
- self.settings.setValue("ffmpegMsgShown", True)
-
- # Hotkeys for projects
- QtWidgets.QShortcut("Ctrl+S", self.window, self.saveCurrentProject)
- QtWidgets.QShortcut("Ctrl+A", self.window, self.openSaveProjectDialog)
- QtWidgets.QShortcut("Ctrl+O", self.window, self.openOpenProjectDialog)
- QtWidgets.QShortcut("Ctrl+N", self.window, self.createNewProject)
-
- # Hotkeys for component list
- for inskey in ("Ctrl+T", QtCore.Qt.Key_Insert):
- QtWidgets.QShortcut(
- inskey, self.window,
- activated=lambda: self.window.pushButton_addComponent.click()
- )
- for delkey in ("Ctrl+R", QtCore.Qt.Key_Delete):
- QtWidgets.QShortcut(
- delkey, self.window.listWidget_componentList,
- self.removeComponent
- )
- QtWidgets.QShortcut(
- "Ctrl+Space", self.window,
- activated=lambda: self.window.listWidget_componentList.setFocus()
- )
- QtWidgets.QShortcut(
- "Ctrl+Shift+S", self.window,
- self.presetManager.openSavePresetDialog
- )
- QtWidgets.QShortcut(
- "Ctrl+Shift+C", self.window, self.presetManager.clearPreset
- )
-
- QtWidgets.QShortcut(
- "Ctrl+Up", self.window.listWidget_componentList,
- activated=lambda: self.moveComponent(-1)
- )
- QtWidgets.QShortcut(
- "Ctrl+Down", self.window.listWidget_componentList,
- activated=lambda: self.moveComponent(1)
- )
- QtWidgets.QShortcut(
- "Ctrl+Home", self.window.listWidget_componentList,
- activated=lambda: self.moveComponent('top')
- )
- QtWidgets.QShortcut(
- "Ctrl+End", self.window.listWidget_componentList,
- activated=lambda: self.moveComponent('bottom')
- )
-
- # Debug Hotkeys
- QtWidgets.QShortcut(
- "Ctrl+Alt+Shift+R", self.window, self.drawPreview
- )
- QtWidgets.QShortcut(
- "Ctrl+Alt+Shift+F", self.window, self.showFfmpegCommand
- )
-
- @QtCore.pyqtSlot()
- def cleanUp(self, *args):
- self.timer.stop()
- self.previewThread.quit()
- self.previewThread.wait()
-
- @disableWhenOpeningProject
- def updateWindowTitle(self):
- appName = 'Audio Visualizer'
- try:
- if self.currentProject:
- appName += ' - %s' % \
- os.path.splitext(
- os.path.basename(self.currentProject))[0]
- if self.autosaveExists(identical=False):
- appName += '*'
- except AttributeError:
- pass
- self.window.setWindowTitle(appName)
-
- @QtCore.pyqtSlot(int, dict)
- def updateComponentTitle(self, pos, presetStore=False):
- if type(presetStore) == dict:
- name = presetStore['preset']
- if name is None or name not in self.core.savedPresets:
- modified = False
- else:
- modified = (presetStore != self.core.savedPresets[name])
- else:
- modified = bool(presetStore)
- if pos < 0:
- pos = len(self.core.selectedComponents)-1
- title = str(self.core.selectedComponents[pos])
- if self.core.selectedComponents[pos].currentPreset:
- title += ' - %s' % self.core.selectedComponents[pos].currentPreset
- if modified:
- title += '*'
- self.window.listWidget_componentList.item(pos).setText(title)
-
- def updateCodecs(self):
- containerWidget = self.window.comboBox_videoContainer
- vCodecWidget = self.window.comboBox_videoCodec
- aCodecWidget = self.window.comboBox_audioCodec
- index = containerWidget.currentIndex()
- name = containerWidget.itemText(index)
- self.settings.setValue('outputContainer', name)
-
- vCodecWidget.clear()
- aCodecWidget.clear()
-
- for container in Core.encoderOptions['containers']:
- if container['name'] == name:
- for vCodec in container['video-codecs']:
- vCodecWidget.addItem(vCodec)
- for aCodec in container['audio-codecs']:
- aCodecWidget.addItem(aCodec)
-
- def updateCodecSettings(self):
- '''Updates settings.ini to match encoder option widgets'''
- vCodecWidget = self.window.comboBox_videoCodec
- vBitrateWidget = self.window.spinBox_vBitrate
- aBitrateWidget = self.window.spinBox_aBitrate
- aCodecWidget = self.window.comboBox_audioCodec
- currentVideoCodec = vCodecWidget.currentIndex()
- currentVideoCodec = vCodecWidget.itemText(currentVideoCodec)
- currentVideoBitrate = vBitrateWidget.value()
- currentAudioCodec = aCodecWidget.currentIndex()
- currentAudioCodec = aCodecWidget.itemText(currentAudioCodec)
- currentAudioBitrate = aBitrateWidget.value()
- self.settings.setValue('outputVideoCodec', currentVideoCodec)
- self.settings.setValue('outputAudioCodec', currentAudioCodec)
- self.settings.setValue('outputVideoBitrate', currentVideoBitrate)
- self.settings.setValue('outputAudioBitrate', currentAudioBitrate)
-
- @disableWhenOpeningProject
- def autosave(self, force=False):
- if not self.currentProject:
- if os.path.exists(self.autosavePath):
- os.remove(self.autosavePath)
- elif force or time.time() - self.lastAutosave >= self.autosaveCooldown:
- self.core.createProjectFile(self.autosavePath, self.window)
- self.lastAutosave = time.time()
- if len(self.autosaveTimes) >= 5:
- # Do some math to reduce autosave spam. This gives a smooth
- # curve up to 5 seconds cooldown and maintains that for 30 secs
- # if a component is continuously updated
- timeDiff = self.lastAutosave - self.autosaveTimes.pop()
- if not force and timeDiff >= 1.0 \
- and timeDiff <= 10.0:
- if self.autosaveCooldown / 4.0 < 0.5:
- self.autosaveCooldown += 1.0
- self.autosaveCooldown = (
- 5.0 * (self.autosaveCooldown / 5.0)
- ) + (self.autosaveCooldown / 5.0) * 2
- elif force or timeDiff >= self.autosaveCooldown * 5:
- self.autosaveCooldown = 0.2
- self.autosaveTimes.insert(0, self.lastAutosave)
-
- def autosaveExists(self, identical=True):
- '''Determines if creating the autosave should be blocked.'''
- try:
- if self.currentProject and os.path.exists(self.autosavePath) \
- and filecmp.cmp(
- self.autosavePath, self.currentProject) == identical:
- return True
- except FileNotFoundError:
- print('project file couldn\'t be located:', self.currentProject)
- return identical
- return False
-
- def saveProjectChanges(self):
- '''Overwrites project file with autosave file'''
- try:
- os.remove(self.currentProject)
- os.rename(self.autosavePath, self.currentProject)
- return True
- except (FileNotFoundError, IsADirectoryError) as e:
- self.showMessage(
- msg='Project file couldn\'t be saved.',
- detail=str(e))
- return False
-
- def openInputFileDialog(self):
- inputDir = self.settings.value("inputDir", os.path.expanduser("~"))
-
- fileName, _ = QtWidgets.QFileDialog.getOpenFileName(
- self.window, "Open Audio File",
- inputDir, "Audio Files (%s)" % " ".join(Core.audioFormats))
-
- if fileName:
- self.settings.setValue("inputDir", os.path.dirname(fileName))
- self.window.lineEdit_audioFile.setText(fileName)
-
- def openOutputFileDialog(self):
- outputDir = self.settings.value("outputDir", os.path.expanduser("~"))
-
- fileName, _ = QtWidgets.QFileDialog.getSaveFileName(
- self.window, "Set Output Video File",
- outputDir,
- "Video Files (%s);; All Files (*)" % " ".join(
- Core.videoFormats))
-
- if fileName:
- self.settings.setValue("outputDir", os.path.dirname(fileName))
- self.window.lineEdit_outputFile.setText(fileName)
-
- def stopVideo(self):
- print('stop')
- self.videoWorker.cancel()
- self.canceled = True
-
- def createAudioVisualisation(self):
- # create output video if mandatory settings are filled in
- audioFile = self.window.lineEdit_audioFile.text()
- outputPath = self.window.lineEdit_outputFile.text()
-
- if audioFile and outputPath and self.core.selectedComponents:
- if not os.path.dirname(outputPath):
- outputPath = os.path.join(
- os.path.expanduser("~"), outputPath)
- if outputPath and os.path.isdir(outputPath):
- self.showMessage(
- msg='Chosen filename matches a directory, which '
- 'cannot be overwritten. Please choose a different '
- 'filename or move the directory.',
- icon='Warning',
- )
- return
- else:
- if not audioFile or not outputPath:
- self.showMessage(
- msg="You must select an audio file and output filename."
- )
- elif not self.core.selectedComponents:
- self.showMessage(
- msg="Not enough components."
- )
- return
-
- self.canceled = False
- self.progressBarUpdated(-1)
- self.videoWorker = self.core.newVideoWorker(
- self, audioFile, outputPath
- )
- self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated)
- self.videoWorker.progressBarSetText.connect(
- self.progressBarSetText)
- self.videoWorker.imageCreated.connect(self.showPreviewImage)
- self.videoWorker.encoding.connect(self.changeEncodingStatus)
- self.createVideo.emit()
-
- @QtCore.pyqtSlot(str, str)
- def videoThreadError(self, msg, detail):
- try:
- self.stopVideo()
- except AttributeError as e:
- if 'videoWorker' not in str(e):
- raise
- self.showMessage(
- msg=msg,
- detail=detail,
- icon='Critical',
- )
-
- def changeEncodingStatus(self, status):
- self.encoding = status
- if status:
- self.window.pushButton_createVideo.setEnabled(False)
- self.window.pushButton_Cancel.setEnabled(True)
- self.window.comboBox_resolution.setEnabled(False)
- self.window.stackedWidget.setEnabled(False)
- self.window.tab_encoderSettings.setEnabled(False)
- self.window.label_audioFile.setEnabled(False)
- self.window.toolButton_selectAudioFile.setEnabled(False)
- self.window.label_outputFile.setEnabled(False)
- self.window.toolButton_selectOutputFile.setEnabled(False)
- self.window.lineEdit_audioFile.setEnabled(False)
- self.window.lineEdit_outputFile.setEnabled(False)
- self.window.pushButton_addComponent.setEnabled(False)
- self.window.pushButton_removeComponent.setEnabled(False)
- self.window.pushButton_listMoveDown.setEnabled(False)
- self.window.pushButton_listMoveUp.setEnabled(False)
- self.window.menuButton_newProject.setEnabled(False)
- self.window.menuButton_openProject.setEnabled(False)
- if sys.platform == 'darwin':
- self.window.progressLabel.setHidden(False)
- else:
- self.window.listWidget_componentList.setEnabled(False)
- else:
- self.window.pushButton_createVideo.setEnabled(True)
- self.window.pushButton_Cancel.setEnabled(False)
- self.window.comboBox_resolution.setEnabled(True)
- self.window.stackedWidget.setEnabled(True)
- self.window.tab_encoderSettings.setEnabled(True)
- self.window.label_audioFile.setEnabled(True)
- self.window.toolButton_selectAudioFile.setEnabled(True)
- self.window.lineEdit_audioFile.setEnabled(True)
- self.window.label_outputFile.setEnabled(True)
- self.window.toolButton_selectOutputFile.setEnabled(True)
- self.window.lineEdit_outputFile.setEnabled(True)
- self.window.pushButton_addComponent.setEnabled(True)
- self.window.pushButton_removeComponent.setEnabled(True)
- self.window.pushButton_listMoveDown.setEnabled(True)
- self.window.pushButton_listMoveUp.setEnabled(True)
- self.window.menuButton_newProject.setEnabled(True)
- self.window.menuButton_openProject.setEnabled(True)
- self.window.listWidget_componentList.setEnabled(True)
- self.window.progressLabel.setHidden(True)
- self.drawPreview(True)
-
- @QtCore.pyqtSlot(int)
- def progressBarUpdated(self, value):
- self.window.progressBar_createVideo.setValue(value)
-
- @QtCore.pyqtSlot(str)
- def progressBarSetText(self, value):
- if sys.platform == 'darwin':
- self.window.progressLabel.setText(value)
- else:
- self.window.progressBar_createVideo.setFormat(value)
-
- def updateResolution(self):
- resIndex = int(self.window.comboBox_resolution.currentIndex())
- res = Core.resolutions[resIndex].split('x')
- changed = res[0] != self.settings.value("outputWidth")
- self.settings.setValue('outputWidth', res[0])
- self.settings.setValue('outputHeight', res[1])
- if changed:
- for i in range(len(self.core.selectedComponents)):
- self.core.updateComponent(i)
-
- def drawPreview(self, force=False, **kwargs):
- '''Use autosave keyword arg to force saving or not saving if needed'''
- self.newTask.emit(self.core.selectedComponents)
- # self.processTask.emit()
- if force or 'autosave' in kwargs:
- if force or kwargs['autosave']:
- self.autosave(True)
- else:
- self.autosave()
- self.updateWindowTitle()
-
- @QtCore.pyqtSlot(QtGui.QImage)
- def showPreviewImage(self, image):
- self.previewWindow.changePixmap(image)
-
- def showFfmpegCommand(self):
- from textwrap import wrap
- from toolkit.ffmpeg import createFfmpegCommand
- command = createFfmpegCommand(
- self.window.lineEdit_audioFile.text(),
- self.window.lineEdit_outputFile.text(),
- self.core.selectedComponents
- )
- lines = wrap(" ".join(command), 49)
- self.showMessage(
- msg="Current FFmpeg command:\n\n %s" % " ".join(lines)
- )
-
- def insertComponent(self, index):
- componentList = self.window.listWidget_componentList
- stackedWidget = self.window.stackedWidget
-
- componentList.insertItem(
- index,
- self.core.selectedComponents[index].name)
- componentList.setCurrentRow(index)
-
- # connect to signal that adds an asterisk when modified
- self.core.selectedComponents[index].modified.connect(
- self.updateComponentTitle)
-
- self.pages.insert(index, self.core.selectedComponents[index].page)
- stackedWidget.insertWidget(index, self.pages[index])
- stackedWidget.setCurrentIndex(index)
-
- return index
-
- def removeComponent(self):
- componentList = self.window.listWidget_componentList
-
- for selected in componentList.selectedItems():
- index = componentList.row(selected)
- self.window.stackedWidget.removeWidget(self.pages[index])
- componentList.takeItem(index)
- self.core.removeComponent(index)
- self.pages.pop(index)
- self.changeComponentWidget()
- self.drawPreview()
-
- @disableWhenEncoding
- def moveComponent(self, change):
- '''Moves a component relatively from its current position'''
- componentList = self.window.listWidget_componentList
- if change == 'top':
- change = -componentList.currentRow()
- elif change == 'bottom':
- change = len(componentList)-componentList.currentRow()-1
- stackedWidget = self.window.stackedWidget
-
- row = componentList.currentRow()
- newRow = row + change
- if newRow > -1 and newRow < componentList.count():
- self.core.moveComponent(row, newRow)
-
- # update widgets
- page = self.pages.pop(row)
- self.pages.insert(newRow, page)
- item = componentList.takeItem(row)
- newItem = componentList.insertItem(newRow, item)
- widget = stackedWidget.removeWidget(page)
- stackedWidget.insertWidget(newRow, page)
- componentList.setCurrentRow(newRow)
- stackedWidget.setCurrentIndex(newRow)
- self.drawPreview(True)
-
- def getComponentListMousePos(self, position):
- '''
- Given a QPos, returns the component index under the mouse cursor
- or -1 if no component is there.
- '''
- componentList = self.window.listWidget_componentList
-
- modelIndexes = [
- componentList.model().index(i)
- for i in range(componentList.count())
- ]
- rects = [
- componentList.visualRect(modelIndex)
- for modelIndex in modelIndexes
- ]
- mousePos = [rect.contains(position) for rect in rects]
- if not any(mousePos):
- # Not clicking a component
- mousePos = -1
- else:
- mousePos = mousePos.index(True)
- return mousePos
-
- @disableWhenEncoding
- def dragComponent(self, event):
- '''Used as Qt drop event for the component listwidget'''
- componentList = self.window.listWidget_componentList
- mousePos = self.getComponentListMousePos(event.pos())
- if mousePos > -1:
- change = (componentList.currentRow() - mousePos) * -1
- else:
- change = (componentList.count() - componentList.currentRow() - 1)
- self.moveComponent(change)
-
- def changeComponentWidget(self):
- selected = self.window.listWidget_componentList.selectedItems()
- if selected:
- index = self.window.listWidget_componentList.row(selected[0])
- self.window.stackedWidget.setCurrentIndex(index)
-
- def openPresetManager(self):
- '''Preset manager for importing, exporting, renaming, deleting'''
- self.presetManager.show()
-
- def clear(self):
- '''Get a blank slate'''
- self.core.clearComponents()
- self.window.listWidget_componentList.clear()
- for widget in self.pages:
- self.window.stackedWidget.removeWidget(widget)
- self.pages = []
- for field in (
- self.window.lineEdit_audioFile,
- self.window.lineEdit_outputFile
- ):
- field.blockSignals(True)
- field.setText('')
- field.blockSignals(False)
- self.progressBarUpdated(0)
- self.progressBarSetText('')
-
- @disableWhenEncoding
- def createNewProject(self, prompt=True):
- if prompt:
- self.openSaveChangesDialog('starting a new project')
-
- self.clear()
- self.currentProject = None
- self.settings.setValue("currentProject", None)
- self.drawPreview(True)
-
- def saveCurrentProject(self):
- if self.currentProject:
- self.core.createProjectFile(self.currentProject, self.window)
- try:
- os.remove(self.autosavePath)
- except FileNotFoundError:
- pass
- self.updateWindowTitle()
- else:
- self.openSaveProjectDialog()
-
- def openSaveChangesDialog(self, phrase):
- success = True
- if self.autosaveExists(identical=False):
- ch = self.showMessage(
- msg="You have unsaved changes in project '%s'. "
- "Save before %s?" % (
- os.path.basename(self.currentProject)[:-4],
- phrase
- ),
- showCancel=True)
- if ch:
- success = self.saveProjectChanges()
-
- if success and os.path.exists(self.autosavePath):
- os.remove(self.autosavePath)
-
- def openSaveProjectDialog(self):
- filename, _ = QtWidgets.QFileDialog.getSaveFileName(
- self.window, "Create Project File",
- self.settings.value("projectDir"),
- "Project Files (*.avp)")
- if not filename:
- return
- if not filename.endswith(".avp"):
- filename += '.avp'
- self.settings.setValue("projectDir", os.path.dirname(filename))
- self.settings.setValue("currentProject", filename)
- self.currentProject = filename
- self.core.createProjectFile(filename, self.window)
- self.updateWindowTitle()
-
- @disableWhenEncoding
- def openOpenProjectDialog(self):
- filename, _ = QtWidgets.QFileDialog.getOpenFileName(
- self.window, "Open Project File",
- self.settings.value("projectDir"),
- "Project Files (*.avp)")
- self.openProject(filename)
-
- def openProject(self, filepath, prompt=True):
- if not filepath or not os.path.exists(filepath) \
- or not filepath.endswith('.avp'):
- return
-
- self.clear()
- # ask to save any changes that are about to get deleted
- if prompt:
- self.openSaveChangesDialog('opening another project')
-
- self.currentProject = filepath
- self.settings.setValue("currentProject", filepath)
- self.settings.setValue("projectDir", os.path.dirname(filepath))
- # actually load the project using core method
- self.core.openProject(self, filepath)
- self.drawPreview(autosave=False)
- self.updateWindowTitle()
-
- def showMessage(self, **kwargs):
- parent = kwargs['parent'] if 'parent' in kwargs else self.window
- msg = QtWidgets.QMessageBox(parent)
- msg.setModal(True)
- msg.setText(kwargs['msg'])
- msg.setIcon(
- eval('QtWidgets.QMessageBox.%s' % kwargs['icon'])
- if 'icon' in kwargs else QtWidgets.QMessageBox.Information
- )
- msg.setDetailedText(kwargs['detail'] if 'detail' in kwargs else None)
- if 'showCancel'in kwargs and kwargs['showCancel']:
- msg.setStandardButtons(
- QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
- else:
- msg.setStandardButtons(QtWidgets.QMessageBox.Ok)
- ch = msg.exec_()
- if ch == 1024:
- return True
- return False
-
- @disableWhenEncoding
- def componentContextMenu(self, QPos):
- '''Appears when right-clicking the component list'''
- componentList = self.window.listWidget_componentList
- self.menu = QMenu()
- parentPosition = componentList.mapToGlobal(QtCore.QPoint(0, 0))
-
- index = self.getComponentListMousePos(QPos)
- if index > -1:
- # Show preset menu if clicking a component
- self.presetManager.findPresets()
- menuItem = self.menu.addAction("Save Preset")
- menuItem.triggered.connect(
- self.presetManager.openSavePresetDialog
- )
-
- # submenu for opening presets
- try:
- presets = self.presetManager.presets[
- str(self.core.selectedComponents[index])
- ]
- self.presetSubmenu = QMenu("Open Preset")
- self.menu.addMenu(self.presetSubmenu)
-
- for version, presetName in presets:
- menuItem = self.presetSubmenu.addAction(presetName)
- menuItem.triggered.connect(
- lambda _, presetName=presetName:
- self.presetManager.openPreset(presetName)
- )
- except KeyError:
- pass
-
- if self.core.selectedComponents[index].currentPreset:
- menuItem = self.menu.addAction("Clear Preset")
- menuItem.triggered.connect(
- self.presetManager.clearPreset
- )
- self.menu.addSeparator()
-
- # "Add Component" submenu
- self.submenu = QMenu("Add")
- self.menu.addMenu(self.submenu)
- insertCompAtTop = self.settings.value("pref_insertCompAtTop")
- for i, comp in enumerate(self.core.modules):
- menuItem = self.submenu.addAction(comp.Component.name)
- menuItem.triggered.connect(
- lambda _, item=i: self.core.insertComponent(
- 0 if insertCompAtTop else index, item, self
- )
- )
-
- self.menu.move(parentPosition + QPos)
- self.menu.show()
-
- def eventFilter(self, object, event):
- if event.type() == QtCore.QEvent.WindowActivate \
- or event.type() == QtCore.QEvent.FocusIn:
- Core.windowHasFocus = True
- elif event.type() == QtCore.QEvent.WindowDeactivate \
- or event.type() == QtCore.QEvent.FocusOut:
- Core.windowHasFocus = False
- return False
--
cgit v1.2.3
From 8baa24e87847a0c7c530cbb55196103ce9cc511c Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 10 Aug 2017 09:12:48 -0400
Subject: added connected path shape to Life
---
src/components/life.py | 104 ++++++++++++++++++++++++++++++++++++-------------
src/components/life.ui | 7 +++-
2 files changed, 82 insertions(+), 29 deletions(-)
diff --git a/src/components/life.py b/src/components/life.py
index 89a4c5c..08360a2 100644
--- a/src/components/life.py
+++ b/src/components/life.py
@@ -133,13 +133,18 @@ class Component(Component):
def drawShape():
drawer = ImageDraw.Draw(frame)
+ rect = (
+ (drawPtX, drawPtY),
+ (drawPtX + self.pxWidth, drawPtY + self.pxHeight)
+ )
+ shape = self.page.comboBox_shapeType.currentText().lower()
# Rectangle
- if self.shapeType == 0:
+ if shape == 'rectangle':
drawer.rectangle(rect, fill=self.color)
- # Ellipse
- elif self.shapeType == 1:
+ # Elliptical
+ elif shape == 'elliptical':
drawer.ellipse(rect, fill=self.color)
tenthX, tenthY = scale(10, self.pxWidth, self.pxHeight, int)
@@ -155,26 +160,75 @@ class Component(Component):
(drawPtX + self.pxWidth - int(tenthX / 4),
drawPtY + self.pxHeight - int(tenthY / 2))
)
-
# Circle
- if self.shapeType == 2:
+ if shape == 'circle':
drawer.ellipse(outlineShape, fill=self.color)
drawer.ellipse(smallerShape, fill=(0,0,0,0))
# Lilypad
- elif self.shapeType == 3:
+ elif shape == 'lilypad':
drawer.pieslice(smallerShape, 290, 250, fill=self.color)
# Pac-Man
- elif self.shapeType == 4:
+ elif shape == 'pac-man':
drawer.pieslice(outlineShape, 35, 320, fill=self.color)
hX, hY = scale(50, self.pxWidth, self.pxHeight, int) # halfline
tX, tY = scale(33, self.pxWidth, self.pxHeight, int) # thirdline
qX, qY = scale(20, self.pxWidth, self.pxHeight, int) # quarterline
+ # Path
+ if shape == 'path':
+ drawer.ellipse(rect, fill=self.color)
+ rects = {
+ direction: False
+ for direction in (
+ 'up', 'down', 'left', 'right',
+ )
+ }
+ for cell in nearbyCoords(x, y):
+ if grid.get(cell) is None:
+ continue
+ if cell[0] == x:
+ if cell[1] < y:
+ rects['up'] = True
+ if cell[1] > y:
+ rects['down'] = True
+ if cell[1] == y:
+ if cell[0] < x:
+ rects['left'] = True
+ if cell[0] > x:
+ rects['right'] = True
+
+ for direction, rect in rects.items():
+ if rect:
+ if direction == 'up':
+ sect = (
+ (drawPtX, drawPtY),
+ (drawPtX + self.pxWidth, drawPtY + hY)
+ )
+ elif direction == 'down':
+ sect = (
+ (drawPtX, drawPtY + hY),
+ (drawPtX + self.pxWidth,
+ drawPtY + self.pxHeight)
+ )
+ elif direction == 'left':
+ sect = (
+ (drawPtX, drawPtY),
+ (drawPtX + hX,
+ drawPtY + self.pxHeight)
+ )
+ elif direction == 'right':
+ sect = (
+ (drawPtX + hX, drawPtY),
+ (drawPtX + self.pxWidth,
+ drawPtY + self.pxHeight)
+ )
+ drawer.rectangle(sect, fill=self.color)
+
# Duck
- if self.shapeType == 5:
+ elif shape == 'duck':
duckHead = (
(drawPtX + qX, drawPtY + qY),
(drawPtX + int(qX * 3), drawPtY + int(tY * 2))
@@ -198,7 +252,7 @@ class Component(Component):
drawer.pieslice(duckBeak, 145, 200, fill=self.color)
# Peace
- elif self.shapeType == 6:
+ elif shape == 'peace':
line = (
(drawPtX + hX - int(tenthX / 2), drawPtY + int(tenthY / 2)),
(drawPtX + hX + int(tenthX / 2),
@@ -231,10 +285,6 @@ class Component(Component):
drawPtY = y * self.pxHeight
if drawPtY > self.height:
continue
- rect = (
- (drawPtX, drawPtY),
- (drawPtX + self.pxWidth, drawPtY + self.pxHeight)
- )
if self.customImg:
drawCustomImg()
@@ -253,23 +303,10 @@ class Component(Component):
'''Given a tick number over 0, returns a new grid dict of tuples'''
lastGrid = self.tickGrids[tick - 1]
- def nearbyCoords(x, y):
- yield x + 1, y + 1
- yield x + 1, y - 1
- yield x - 1, y + 1
- yield x - 1, y - 1
- yield x, y + 1
- yield x, y - 1
- yield x + 1, y
- yield x - 1, y
-
def neighbours(x, y):
- nearbyCells = [
- lastGrid.get(cell) for cell in nearbyCoords(x, y)
- ]
return [
- nearbyCell for nearbyCell in nearbyCells
- if nearbyCell is not None
+ cell for cell in nearbyCoords(x, y)
+ if lastGrid.get(cell) is not None
]
newGrid = {}
@@ -298,3 +335,14 @@ class Component(Component):
def loadPreset(self, pr, *args):
super().loadPreset(pr, *args)
self.startingGrid = pr['GRID']
+
+
+def nearbyCoords(x, y):
+ yield x + 1, y + 1
+ yield x + 1, y - 1
+ yield x - 1, y + 1
+ yield x - 1, y - 1
+ yield x, y + 1
+ yield x, y - 1
+ yield x + 1, y
+ yield x - 1, y
diff --git a/src/components/life.ui b/src/components/life.ui
index 2341c19..3b393dd 100644
--- a/src/components/life.ui
+++ b/src/components/life.ui
@@ -213,6 +213,11 @@
-
+
-
+
+ Path
+
+
-
Rectangle
@@ -220,7 +225,7 @@
-
- Ellipse
+ Elliptical
-
--
cgit v1.2.3
From 1c4afc96d69789f16284c067ffd7098dc7b2ca70 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 10 Aug 2017 16:04:41 -0400
Subject: using the builtin logging module
---
src/component.py | 14 ++++++---
src/components/spectrum.py | 16 ++++++----
src/components/video.py | 19 +++++++++---
src/components/waveform.py | 18 +++++++++---
src/core.py | 73 +++++++++++++++++++++++++++++++++++++++++-----
src/main.py | 6 ++++
src/mainwindow.py | 57 +++++++++++++++++++++++++++---------
src/preview_thread.py | 9 ++++--
src/toolkit/ffmpeg.py | 19 +++++++-----
src/toolkit/frame.py | 6 ++++
src/video_thread.py | 24 ++++++++++-----
11 files changed, 206 insertions(+), 55 deletions(-)
diff --git a/src/component.py b/src/component.py
index 5b6f9a7..a1e24db 100644
--- a/src/component.py
+++ b/src/component.py
@@ -8,6 +8,7 @@ import os
import sys
import math
import time
+import logging
from toolkit.frame import BlankFrame
from toolkit import (
@@ -15,6 +16,9 @@ from toolkit import (
)
+log = logging.getLogger('AVP.ComponentHandler')
+
+
class ComponentMetaclass(type(QtCore.QObject)):
'''
Checks the validity of each Component class and mutates some attrs.
@@ -135,17 +139,17 @@ class ComponentMetaclass(type(QtCore.QObject)):
# Turn version string into a number
try:
if 'version' not in attrs:
- print(
+ log.error(
'No version attribute in %s. Defaulting to 1' %
attrs['name'])
attrs['version'] = 1
else:
attrs['version'] = int(attrs['version'].split('.')[0])
except ValueError:
- print('%s component has an invalid version string:\n%s' % (
+ log.critical('%s component has an invalid version string:\n%s' % (
attrs['name'], str(attrs['version'])))
except KeyError:
- print('%s component has no version string.' % attrs['name'])
+ log.critical('%s component has no version string.' % attrs['name'])
else:
return super().__new__(cls, name, parents, attrs)
quit(1)
@@ -546,6 +550,8 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
and oldRelativeVal != newRelativeVal:
# Float changed without pixel value changing, which
# means the pixel value needs to be updated
+ log.debug('Updating %s #%s\'s relative widget: %s' % (
+ self.name, self.compPos, attr))
self._trackedWidgets[attr].blockSignals(True)
self.updateRelativeWidgetMaximum(attr)
pixelVal = self.pixelValForAttr(attr, oldRelativeVal)
@@ -576,7 +582,7 @@ class ComponentError(RuntimeError):
msg = str(sys.exc_info()[1])
else:
msg = 'Unknown error.'
- print("##### ComponentError by %s's %s: %s" % (
+ log.error("ComponentError by %s's %s: %s" % (
caller.name, name, msg))
# Don't create multiple windows for quickly repeated messages
diff --git a/src/components/spectrum.py b/src/components/spectrum.py
index 666e20a..32763c0 100644
--- a/src/components/spectrum.py
+++ b/src/components/spectrum.py
@@ -4,6 +4,7 @@ import os
import math
import subprocess
import time
+import logging
from component import Component
from toolkit.frame import BlankFrame, scale
@@ -13,6 +14,9 @@ from toolkit.ffmpeg import (
)
+log = logging.getLogger('AVP.Components.Spectrum')
+
+
class Component(Component):
name = 'Spectrum'
version = '1.0.0'
@@ -68,6 +72,7 @@ class Component(Component):
if not changedSize \
and not self.changedOptions \
and self.previewFrame is not None:
+ log.debug('Comp #%s is reusing old preview frame' % self.compPos)
return self.previewFrame
frame = self.getPreviewFrame()
@@ -131,13 +136,14 @@ class Component(Component):
'-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:
+ 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=log, bufsize=10**8
+ stderr=logf, bufsize=10**8
)
byteFrame = pipe.stdout.read(self.chunkSize)
closePipe(pipe)
diff --git a/src/components/video.py b/src/components/video.py
index b6bdd52..a189f60 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -3,6 +3,7 @@ from PyQt5 import QtGui, QtCore, QtWidgets
import os
import math
import subprocess
+import logging
from component import Component
from toolkit.frame import BlankFrame, scale
@@ -10,6 +11,9 @@ from toolkit.ffmpeg import openPipe, closePipe, testAudioStream, FfmpegVideo
from toolkit import checkOutput
+log = logging.getLogger('AVP.Components.Video')
+
+
class Component(Component):
name = 'Video'
version = '1.0.0'
@@ -134,10 +138,17 @@ class Component(Component):
'-ss', '90',
'-frames:v', '1',
])
- pipe = openPipe(
- command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
- stderr=subprocess.DEVNULL, bufsize=10**8
- )
+
+ logFilename = os.path.join(
+ self.core.logDir, 'preview_%s.log' % str(self.compPos))
+ log.debug('Creating ffmpeg process (log at %s)' % logFilename)
+ with open(logFilename, 'w') as logf:
+ logf.write(" ".join(command) + '\n\n')
+ with open(logFilename, 'a') as logf:
+ pipe = openPipe(
+ command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
+ stderr=logf, bufsize=10**8
+ )
byteFrame = pipe.stdout.read(self.chunkSize)
closePipe(pipe)
diff --git a/src/components/waveform.py b/src/components/waveform.py
index 71cbcac..1517be2 100644
--- a/src/components/waveform.py
+++ b/src/components/waveform.py
@@ -4,6 +4,7 @@ from PyQt5.QtGui import QColor
import os
import math
import subprocess
+import logging
from component import Component
from toolkit.frame import BlankFrame, scale
@@ -13,6 +14,9 @@ from toolkit.ffmpeg import (
)
+log = logging.getLogger('AVP.Components.Waveform')
+
+
class Component(Component):
name = 'Waveform'
version = '1.0.0'
@@ -106,10 +110,16 @@ class Component(Component):
'-codec:v', 'rawvideo', '-',
'-frames:v', '1',
])
- pipe = openPipe(
- command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
- stderr=subprocess.DEVNULL, bufsize=10**8
- )
+ logFilename = os.path.join(
+ self.core.logDir, 'preview_%s.log' % str(self.compPos))
+ log.debug('Creating ffmpeg process (log at %s)' % logFilename)
+ with open(logFilename, 'w') as logf:
+ logf.write(" ".join(command) + '\n\n')
+ with open(logFilename, 'a') as logf:
+ pipe = openPipe(
+ command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
+ stderr=logf, bufsize=10**8
+ )
byteFrame = pipe.stdout.read(self.chunkSize)
closePipe(pipe)
diff --git a/src/core.py b/src/core.py
index 61905eb..4023542 100644
--- a/src/core.py
+++ b/src/core.py
@@ -7,11 +7,17 @@ import sys
import os
import json
from importlib import import_module
+import logging
import toolkit
import video_thread
+log = logging.getLogger('AVP.Core')
+STDOUT_LOGLVL = logging.WARNING
+FILE_LOGLVL = logging.DEBUG
+
+
class Core:
'''
MainWindow and Command module both use an instance of this class
@@ -35,6 +41,7 @@ class Core:
continue
elif ext == '.py':
yield name
+ log.debug('Importing component modules')
self.modules = [
import_module('components.%s' % name)
for name in findComponents()
@@ -67,7 +74,7 @@ class Core:
compPos = len(self.selectedComponents)
if len(self.selectedComponents) > 50:
return None
-
+ log.debug('Inserting Component from module #%s' % moduleIndex)
component = self.modules[moduleIndex].Component(
moduleIndex, compPos, self
)
@@ -104,7 +111,7 @@ class Core:
self.componentListChanged()
def updateComponent(self, i):
- # print('updating %s' % self.selectedComponents[i])
+ log.debug('Updating %s #%s' % (self.selectedComponents[i], str(i)))
self.selectedComponents[i].update()
def moduleIndexFor(self, compName):
@@ -125,12 +132,17 @@ class Core:
if not saveValueStore:
return False
try:
- self.selectedComponents[compIndex].loadPreset(
+ comp = self.selectedComponents[compIndex]
+ comp.loadPreset(
saveValueStore,
presetName
)
except KeyError as e:
- print('preset missing value: %s' % e)
+ log.warning(
+ '%s #%s\'s preset is missing value: %s' % (
+ comp.name, str(compIndex), str(e)
+ )
+ )
self.savedPresets[presetName] = dict(saveValueStore)
return True
@@ -206,7 +218,7 @@ class Core:
preset['preset']
)
except KeyError as e:
- print('%s missing value: %s' % (
+ log.warning('%s missing value: %s' % (
self.selectedComponents[i], e)
)
@@ -224,7 +236,7 @@ class Core:
typ, value, tb = data
if typ.__name__ == 'KeyError':
# probably just an old version, still loadable
- print('file missing value: %s' % value)
+ log.warning('Project file missing value: %s' % value)
return
if hasattr(loader, 'createNewProject'):
loader.createNewProject(prompt=False)
@@ -244,6 +256,7 @@ class Core:
Returns dictionary with section names as the keys, each one
contains a list of tuples: (compName, version, compPresetDict)
'''
+ log.debug('Parsing av file: %s' % filepath)
validSections = (
'Components',
'Settings',
@@ -362,6 +375,7 @@ class Core:
def createProjectFile(self, filepath, window=None):
'''Create a project file (.avp) using the current program state'''
+ log.info('Creating %s' % filepath)
settingsKeys = [
'componentDir',
'inputDir',
@@ -374,9 +388,8 @@ class Core:
filepath += '.avp'
if os.path.exists(filepath):
os.remove(filepath)
- with open(filepath, 'w') as f:
- print('creating %s' % filepath)
+ with open(filepath, 'w') as f:
f.write('[Components]\n')
for comp in self.selectedComponents:
saveValueStore = comp.savePreset()
@@ -443,6 +456,7 @@ class Core:
'settings': QtCore.QSettings(
os.path.join(dataDir, 'settings.ini'),
QtCore.QSettings.IniFormat),
+ 'logDir': os.path.join(dataDir, 'log'),
'presetDir': os.path.join(dataDir, 'presets'),
'componentsPath': os.path.join(wd, 'components'),
'encoderOptions': encoderOptions,
@@ -489,6 +503,13 @@ class Core:
setattr(cls, classvar, val)
cls.loadDefaultSettings()
+ if not os.path.exists(cls.dataDir):
+ os.makedirs(cls.dataDir)
+ for neededDirectory in (
+ cls.presetDir, cls.logDir, cls.settings.value("projectDir")):
+ if not os.path.exists(neededDirectory):
+ os.mkdir(neededDirectory)
+ cls.makeLogger()
@classmethod
def loadDefaultSettings(cls):
@@ -522,6 +543,42 @@ class Core:
if val in ('true', 'false'):
cls.settings.setValue(key, True if val == 'true' else False)
+ @staticmethod
+ def makeLogger():
+ logFilename = os.path.join(Core.logDir, 'avp_debug.log')
+ libLogFilename = os.path.join(Core.logDir, 'global_debug.log')
+ # delete old logs
+ for log in (logFilename, libLogFilename):
+ if os.path.exists(log):
+ os.remove(log)
+
+ # create file handlers to capture every log message somewhere
+ logFile = logging.FileHandler(logFilename)
+ logFile.setLevel(FILE_LOGLVL)
+ libLogFile = logging.FileHandler(libLogFilename)
+ libLogFile.setLevel(FILE_LOGLVL)
+
+ # send some critical log messages to stdout as well
+ logStream = logging.StreamHandler()
+ logStream.setLevel(STDOUT_LOGLVL)
+
+ # create formatters and put everything together
+ fileFormatter = logging.Formatter(
+ '[%(asctime)s] <%(name)s> %(levelname)s: %(message)s'
+ )
+ streamFormatter = logging.Formatter(
+ '<%(name)s> %(message)s'
+ )
+ logFile.setFormatter(fileFormatter)
+ libLogFile.setFormatter(fileFormatter)
+ logStream.setFormatter(streamFormatter)
+ log = logging.getLogger('AVP')
+ log.setLevel(FILE_LOGLVL)
+ log.addHandler(logFile)
+ log.addHandler(logStream)
+ libLog = logging.getLogger()
+ libLog.setLevel(FILE_LOGLVL)
+ libLog.addHandler(libLogFile)
# always store settings in class variables even if a Core object is not created
Core.storeSettings()
diff --git a/src/main.py b/src/main.py
index 421a09f..3a6fbe7 100644
--- a/src/main.py
+++ b/src/main.py
@@ -1,10 +1,14 @@
from PyQt5 import uic, QtWidgets
import sys
import os
+import logging
from __init__ import wd
+log = logging.getLogger('AVP.Entrypoint')
+
+
def main():
app = QtWidgets.QApplication(sys.argv)
app.setApplicationName("audio-visualizer")
@@ -28,6 +32,7 @@ def main():
from command import Command
main = Command()
+ log.debug("Finished creating command object")
elif mode == 'GUI':
from mainwindow import MainWindow
@@ -48,6 +53,7 @@ def main():
# window.verticalLayout_2.setContentsMargins(0, topMargin, 0, 0)
main = MainWindow(window, proj)
+ log.debug("Finished creating main window")
window.raise_()
signal.signal(signal.SIGINT, main.cleanUp)
diff --git a/src/mainwindow.py b/src/mainwindow.py
index 789a6e7..114015c 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -13,6 +13,7 @@ import os
import signal
import filecmp
import time
+import logging
from core import Core
import preview_thread
@@ -20,11 +21,15 @@ from presetmanager import PresetManager
from toolkit import disableWhenEncoding, disableWhenOpeningProject, checkOutput
+log = logging.getLogger('AVP.MainWindow')
+
+
class PreviewWindow(QtWidgets.QLabel):
'''
Paints the preview QLabel and maintains the aspect ratio when the
window is resized.
'''
+ log = logging.getLogger('AVP.MainWindow.Preview')
def __init__(self, parent, img):
super(PreviewWindow, self).__init__()
@@ -58,11 +63,15 @@ class PreviewWindow(QtWidgets.QLabel):
if i >= 0:
component = self.parent.core.selectedComponents[i]
if not hasattr(component, 'previewClickEvent'):
+ self.log.info('Ignored click event')
return
pos = (event.x(), event.y())
size = (self.width(), self.height())
+ butt = event.button()
+ self.log.info('Click event for #%s: %s button %s' % (
+ i, pos, butt))
component.previewClickEvent(
- pos, size, event.button()
+ pos, size, butt
)
self.parent.core.updateComponent(i)
@@ -91,9 +100,10 @@ class MainWindow(QtWidgets.QMainWindow):
def __init__(self, window, project):
QtWidgets.QMainWindow.__init__(self)
- # print('main thread id: {}'.format(QtCore.QThread.currentThreadId()))
self.window = window
self.core = Core()
+ log.debug(
+ 'Main thread id: {}'.format(QtCore.QThread.currentThreadId()))
# widgets of component settings
self.pages = []
@@ -103,27 +113,23 @@ class MainWindow(QtWidgets.QMainWindow):
self.autosaveCooldown = 0.2
self.encoding = False
- # Create data directory, load/create settings
+ # Find settings created by Core object
self.dataDir = Core.dataDir
self.presetDir = Core.presetDir
self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
self.settings = Core.settings
+
self.presetManager = PresetManager(
uic.loadUi(
os.path.join(Core.wd, 'presetmanager.ui')), self)
- if not os.path.exists(self.dataDir):
- os.makedirs(self.dataDir)
- for neededDirectory in (
- self.presetDir, self.settings.value("projectDir")):
- if not os.path.exists(neededDirectory):
- os.mkdir(neededDirectory)
-
# Create the preview window and its thread, queues, and timers
+ log.debug('Creating preview window')
self.previewWindow = PreviewWindow(self, os.path.join(
Core.wd, "background.png"))
window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
+ log.debug('Starting preview thread')
self.previewQueue = Queue()
self.previewThread = QtCore.QThread(self)
self.previewWorker = preview_thread.Worker(self, self.previewQueue)
@@ -132,6 +138,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.previewWorker.imageCreated.connect(self.showPreviewImage)
self.previewThread.start()
+ log.debug('Starting preview timer')
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.processTask.emit)
self.timer.start(500)
@@ -141,6 +148,8 @@ class MainWindow(QtWidgets.QMainWindow):
componentList = self.window.listWidget_componentList
if sys.platform == 'darwin':
+ log.debug(
+ 'Darwin detected: showing progress label below progress bar')
window.progressBar_createVideo.setTextVisible(False)
else:
window.progressLabel.setHidden(True)
@@ -276,6 +285,7 @@ class MainWindow(QtWidgets.QMainWindow):
)
self.updateWindowTitle()
+ log.debug('Showing main window')
window.show()
if project and project != self.autosavePath:
@@ -398,6 +408,7 @@ class MainWindow(QtWidgets.QMainWindow):
@QtCore.pyqtSlot()
def cleanUp(self, *args):
+ log.info('Ending the preview thread')
self.timer.stop()
self.previewThread.quit()
self.previewThread.wait()
@@ -414,11 +425,12 @@ class MainWindow(QtWidgets.QMainWindow):
appName += '*'
except AttributeError:
pass
+ log.debug('Setting window title to %s' % appName)
self.window.setWindowTitle(appName)
@QtCore.pyqtSlot(int, dict)
def updateComponentTitle(self, pos, presetStore=False):
- if type(presetStore) == dict:
+ if type(presetStore) is dict:
name = presetStore['preset']
if name is None or name not in self.core.savedPresets:
modified = False
@@ -428,11 +440,20 @@ class MainWindow(QtWidgets.QMainWindow):
modified = bool(presetStore)
if pos < 0:
pos = len(self.core.selectedComponents)-1
- title = str(self.core.selectedComponents[pos])
+ name = str(self.core.selectedComponents[pos])
+ title = str(name)
if self.core.selectedComponents[pos].currentPreset:
title += ' - %s' % self.core.selectedComponents[pos].currentPreset
if modified:
title += '*'
+ if type(presetStore) is bool:
+ log.debug('Forcing %s #%s\'s modified status to %s: %s' % (
+ name, pos, modified, title
+ ))
+ else:
+ log.debug('Setting %s #%s\'s title: %s' % (
+ name, pos, title
+ ))
self.window.listWidget_componentList.item(pos).setText(title)
def updateCodecs(self):
@@ -493,6 +514,8 @@ class MainWindow(QtWidgets.QMainWindow):
elif force or timeDiff >= self.autosaveCooldown * 5:
self.autosaveCooldown = 0.2
self.autosaveTimes.insert(0, self.lastAutosave)
+ else:
+ log.debug('Autosave rejected by cooldown')
def autosaveExists(self, identical=True):
'''Determines if creating the autosave should be blocked.'''
@@ -500,9 +523,14 @@ class MainWindow(QtWidgets.QMainWindow):
if self.currentProject and os.path.exists(self.autosavePath) \
and filecmp.cmp(
self.autosavePath, self.currentProject) == identical:
+ log.debug(
+ 'Autosave found %s to be identical' % \
+ 'not' if not identical else ''
+ )
return True
except FileNotFoundError:
- print('project file couldn\'t be located:', self.currentProject)
+ log.error(
+ 'Project file couldn\'t be located:', self.currentProject)
return identical
return False
@@ -543,7 +571,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.window.lineEdit_outputFile.setText(fileName)
def stopVideo(self):
- print('stop')
+ log.info('Export cancelled')
self.videoWorker.cancel()
self.canceled = True
@@ -773,6 +801,7 @@ class MainWindow(QtWidgets.QMainWindow):
mousePos = -1
else:
mousePos = mousePos.index(True)
+ log.debug('Click component list row %s' % mousePos)
return mousePos
@disableWhenEncoding
diff --git a/src/preview_thread.py b/src/preview_thread.py
index bb22f0c..9615884 100644
--- a/src/preview_thread.py
+++ b/src/preview_thread.py
@@ -8,11 +8,15 @@ from PIL import Image
from PIL.ImageQt import ImageQt
from queue import Queue, Empty
import os
+import logging
from toolkit.frame import Checkerboard
from toolkit import disableWhenOpeningProject
+log = logging.getLogger("AVP.PreviewThread")
+
+
class Worker(QtCore.QObject):
imageCreated = pyqtSignal(QtGui.QImage)
@@ -55,7 +59,7 @@ class Worker(QtCore.QObject):
self.background = Checkerboard(width, height)
frame = self.background.copy()
-
+ log.debug('Creating new preview frame')
components = nextPreviewInformation["components"]
for component in reversed(components):
try:
@@ -73,10 +77,11 @@ class Worker(QtCore.QObject):
newFrame.width, newFrame.height,
width, height
)
+ log.critical(errMsg)
self.error.emit(errMsg)
break
except RuntimeError as e:
- print(e)
+ log.error(str(e))
else:
self.frame = ImageQt(frame)
self.imageCreated.emit(QtGui.QImage(self.frame))
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index 3421049..6ab445c 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -8,12 +8,16 @@ import subprocess
import threading
import signal
from queue import PriorityQueue
+import logging
import core
from toolkit.common import checkOutput, pipeWrapper
from component import ComponentError
+log = logging.getLogger('AVP.Toolkit.Ffmpeg')
+
+
class FfmpegVideo:
'''Opens a pipe to ffmpeg and stores a buffer of raw video frames.'''
@@ -88,13 +92,14 @@ class FfmpegVideo:
def fillBuffer(self):
logFilename = os.path.join(
- core.Core.dataDir, 'extra_%s.log' % str(self.component.compPos))
- with open(logFilename, 'w') as log:
- log.write(" ".join(self.command) + '\n\n')
- with open(logFilename, 'a') as log:
+ core.Core.logDir, 'render_%s.log' % str(self.component.compPos))
+ log.debug('Creating ffmpeg process (log at %s)' % logFilename)
+ with open(logFilename, 'w') as logf:
+ logf.write(" ".join(self.command) + '\n\n')
+ with open(logFilename, 'a') as logf:
self.pipe = openPipe(
self.command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
- stderr=log, bufsize=10**8
+ stderr=logf, bufsize=10**8
)
while True:
if self.parent.canceled:
@@ -375,7 +380,7 @@ def getAudioDuration(filename):
try:
info = fileInfo.decode("utf-8").split('\n')
except UnicodeDecodeError as e:
- print('Unicode error:', str(e))
+ log.error('Unicode error:', str(e))
return False
for line in info:
@@ -398,7 +403,7 @@ def readAudioFile(filename, videoWorker):
'''
duration = getAudioDuration(filename)
if not duration:
- print('Audio file doesn\'t exist or unreadable.')
+ log.error('Audio file doesn\'t exist or unreadable.')
return
command = [
diff --git a/src/toolkit/frame.py b/src/toolkit/frame.py
index 7e83d58..02f9229 100644
--- a/src/toolkit/frame.py
+++ b/src/toolkit/frame.py
@@ -7,10 +7,14 @@ from PIL.ImageQt import ImageQt
import sys
import os
import math
+import logging
import core
+log = logging.getLogger('AVP.Toolkit.Frame')
+
+
class FramePainter(QtGui.QPainter):
'''
A QPainter for a blank frame, which can be converted into a
@@ -79,6 +83,7 @@ def FloodFrame(width, height, RgbaTuple):
@defaultSize
def BlankFrame(width, height):
'''The base frame used by each component to start drawing.'''
+ log.debug('Creating new %s*%s blank frame' % (width, height))
return FloodFrame(width, height, (0, 0, 0, 0))
@@ -88,6 +93,7 @@ def Checkerboard(width, height):
A checkerboard to represent transparency to the user.
TODO: Would be cool to generate this image with numpy instead.
'''
+ log.debug('Creating new %s*%s checkerboard' % (width, height))
image = FloodFrame(1920, 1080, (0, 0, 0, 0))
image.paste(Image.open(
os.path.join(core.Core.wd, "background.png")),
diff --git a/src/video_thread.py b/src/video_thread.py
index 5963def..e7e4136 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -17,6 +17,7 @@ from queue import Queue, PriorityQueue
from threading import Thread, Event
import time
import signal
+import logging
from component import ComponentError
from toolkit.frame import Checkerboard
@@ -26,6 +27,9 @@ from toolkit.ffmpeg import (
)
+log = logging.getLogger("AVP.VideoThread")
+
+
class Worker(QtCore.QObject):
imageCreated = pyqtSignal(['QImage'])
@@ -92,7 +96,7 @@ class Worker(QtCore.QObject):
by a renderNode later. All indices are multiples of self.sampleSize
sampleSize * frameNo = audioI, AKA audio data starting at frameNo
'''
- print('Dispatching Frames for Compositing...')
+ log.debug('Dispatching Frames for Compositing...')
for audioI in range(0, len(self.completeAudioArray), self.sampleSize):
self.compositeQueue.put(audioI)
@@ -156,10 +160,12 @@ class Worker(QtCore.QObject):
self.progressBarUpdate.emit(0)
self.progressBarSetText.emit("Starting components...")
canceledByComponent = False
- print('Loaded Components:', ", ".join([
+ initText = ", ".join([
"%s) %s" % (num, str(component))
for num, component in enumerate(reversed(self.components))
- ]))
+ ])
+ print('Loaded Components:', initText)
+ log.info('Calling preFrameRender for %s' % initText)
self.staticComponents = {}
for compNo, comp in enumerate(reversed(self.components)):
try:
@@ -191,6 +197,7 @@ class Worker(QtCore.QObject):
compError[0]
)
)
+ log.critical(errMsg)
comp._error.emit(errMsg, compError[1])
break
if 'static' in compProps:
@@ -199,7 +206,7 @@ class Worker(QtCore.QObject):
if self.canceled:
if canceledByComponent:
- print('Export cancelled by component #%s (%s): %s' % (
+ log.critical('Export cancelled by component #%s (%s): %s' % (
compNo,
comp.name,
'No message.' if comp.error() is None else (
@@ -224,8 +231,11 @@ class Worker(QtCore.QObject):
ffmpegCommand = createFfmpegCommand(
self.inputFile, self.outputFile, self.components, duration
)
- print('###### FFMPEG COMMAND ######\n%s' % " ".join(ffmpegCommand))
+ cmd = " ".join(ffmpegCommand)
+ print('###### FFMPEG COMMAND ######\n%s' % cmd)
print('############################')
+ log.info('Opening pipe to ffmpeg')
+ log.info(cmd)
self.out_pipe = openPipe(
ffmpegCommand, stdin=sp.PIPE, stdout=sys.stdout, stderr=sys.stdout
)
@@ -298,9 +308,9 @@ class Worker(QtCore.QObject):
try:
self.out_pipe.stdin.close()
except BrokenPipeError:
- print('Broken pipe to ffmpeg!')
+ log.error('Broken pipe to ffmpeg!')
if self.out_pipe.stderr is not None:
- print(self.out_pipe.stderr.read())
+ log.error(self.out_pipe.stderr.read())
self.out_pipe.stderr.close()
self.error = True
self.out_pipe.wait()
--
cgit v1.2.3
From bdb006f25d2237ad69ee88d7f054cefaa0c5a3d8 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 10 Aug 2017 17:27:59 -0400
Subject: fixed relative image scale bug & Life preset bug
dicts must be alphabetized in AV files
---
setup.py | 2 +-
src/components/image.py | 32 ++++++++++++++++++++++++++++----
src/components/image.ui | 16 ++++++++++++++++
src/components/life.py | 7 ++++---
4 files changed, 49 insertions(+), 8 deletions(-)
diff --git a/setup.py b/setup.py
index 4a4511f..dd546e2 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@ from setuptools import setup
import os
-__version__ = '2.0.0.rc3'
+__version__ = '2.0.0.rc4'
def package_files(directory):
diff --git a/src/components/image.py b/src/components/image.py
index 1555541..63bee1a 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -8,7 +8,7 @@ from toolkit.frame import BlankFrame
class Component(Component):
name = 'Image'
- version = '1.0.0'
+ version = '1.0.1'
def widget(self, *args):
super().widget(*args)
@@ -16,6 +16,7 @@ class Component(Component):
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,
@@ -51,6 +52,7 @@ class Component(Component):
def drawFrame(self, width, height):
frame = BlankFrame(width, height)
if self.imagePath and os.path.exists(self.imagePath):
+ scale = self.scale if not self.stretched else self.stretchScale
image = Image.open(self.imagePath)
# Modify image's appearance
@@ -62,9 +64,9 @@ class Component(Component):
image = image.transpose(Image.FLIP_LEFT_RIGHT)
if self.stretched and image.size != (width, height):
image = image.resize((width, height), Image.ANTIALIAS)
- if self.scale != 100:
- newHeight = int((image.height / 100) * self.scale)
- newWidth = int((image.width / 100) * self.scale)
+ if scale != 100:
+ newHeight = int((image.height / 100) * scale)
+ newWidth = int((image.width / 100) * scale)
image = image.resize((newWidth, newHeight), Image.ANTIALIAS)
# Paste image at correct position
@@ -100,3 +102,25 @@ class Component(Component):
def commandHelp(self):
print('Load an image:\n path=/filepath/to/image.png')
+
+ def savePreset(self):
+ # Maintain the illusion that the scale spinbox is one widget
+ scaleBox = self.page.spinBox_scale
+ stretchScaleBox = self.page.spinBox_scale_stretch
+ if self.page.checkBox_stretch.isChecked():
+ scaleBox.setValue(stretchScaleBox.value())
+ else:
+ stretchScaleBox.setValue(scaleBox.value())
+ return super().savePreset()
+
+ def update(self):
+ # Maintain the illusion that the scale spinbox is one widget
+ scaleBox = self.page.spinBox_scale
+ stretchScaleBox = self.page.spinBox_scale_stretch
+ if self.page.checkBox_stretch.isChecked():
+ scaleBox.setVisible(False)
+ stretchScaleBox.setVisible(True)
+ else:
+ scaleBox.setVisible(True)
+ stretchScaleBox.setVisible(False)
+ super().update()
diff --git a/src/components/image.ui b/src/components/image.ui
index 1837b64..2dad127 100644
--- a/src/components/image.ui
+++ b/src/components/image.ui
@@ -293,6 +293,22 @@
+ -
+
+
+ %
+
+
+ 10
+
+
+ 400
+
+
+ 100
+
+
+
-
diff --git a/src/components/life.py b/src/components/life.py
index 08360a2..147d4d5 100644
--- a/src/components/life.py
+++ b/src/components/life.py
@@ -4,12 +4,13 @@ import os
import math
from component import Component
+from toolkit import alphabetizeDict
from toolkit.frame import BlankFrame, scale
class Component(Component):
name = 'Conway\'s Game of Life'
- version = '1.0.0a'
+ version = '1.0.0'
def widget(self, *args):
super().widget(*args)
@@ -329,12 +330,12 @@ class Component(Component):
def savePreset(self):
pr = super().savePreset()
- pr['GRID'] = self.startingGrid
+ pr['GRID'] = alphabetizeDict(self.startingGrid)
return pr
def loadPreset(self, pr, *args):
super().loadPreset(pr, *args)
- self.startingGrid = pr['GRID']
+ self.startingGrid = dict(pr['GRID'])
def nearbyCoords(x, y):
--
cgit v1.2.3
From c3f128806b45c427058448e6f2ff799de16da418 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 10 Aug 2017 21:57:06 -0400
Subject: Life comp shift buttons and Show Grid option
---
src/component.py | 2 ++
src/components/life.py | 56 +++++++++++++++++++++++++++++++++++++++++++++++++-
src/components/life.ui | 49 ++++++++++++++++++++++++++++++++++++++++++-
3 files changed, 105 insertions(+), 2 deletions(-)
diff --git a/src/component.py b/src/component.py
index a1e24db..d011f1e 100644
--- a/src/component.py
+++ b/src/component.py
@@ -323,7 +323,9 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
else:
# Normal tracked widget
setattr(self, attr, getWidgetValue(widget))
+ self.sendUpdateSignal()
+ def sendUpdateSignal(self):
if not self.core.openingProject:
self.parent.drawPreview()
saveValueStore = self.savePreset()
diff --git a/src/components/life.py b/src/components/life.py
index 147d4d5..9254126 100644
--- a/src/components/life.py
+++ b/src/components/life.py
@@ -25,10 +25,24 @@ class Component(Component):
'shapeType': self.page.comboBox_shapeType,
'shadow': self.page.checkBox_shadow,
'customImg': self.page.checkBox_customImg,
+ 'showGrid': self.page.checkBox_showGrid,
'image': self.page.lineEdit_image,
}, colorWidgets={
'color': self.page.pushButton_color,
})
+ self.shiftButtons = (
+ self.page.toolButton_up,
+ self.page.toolButton_down,
+ self.page.toolButton_left,
+ self.page.toolButton_right,
+ )
+ def shiftFunc(i):
+ def shift():
+ self.shiftGrid(i)
+ return shift
+ shiftFuncs = [shiftFunc(i) for i in range(len(self.shiftButtons))]
+ for i, widget in enumerate(self.shiftButtons):
+ widget.clicked.connect(shiftFuncs[i])
self.page.spinBox_scale.setValue(self.scale)
self.page.spinBox_scale.valueChanged.connect(self.updateGridSize)
@@ -42,6 +56,24 @@ class Component(Component):
self.page.lineEdit_image.setText(filename)
self.update()
+ def shiftGrid(self, d):
+ def newGrid(Xchange, Ychange):
+ return {
+ (x + Xchange, y + Ychange): True
+ for x, y in self.startingGrid
+ }
+
+ if d == 0:
+ newGrid = newGrid(0, -1)
+ elif d == 1:
+ newGrid = newGrid(0, 1)
+ elif d == 2:
+ newGrid = newGrid(-1, 0)
+ elif d == 3:
+ newGrid = newGrid(1, 0)
+ self.startingGrid = newGrid
+ self.sendUpdateSignal()
+
def update(self):
self.updateGridSize()
if self.page.checkBox_customImg.isChecked():
@@ -62,6 +94,9 @@ 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)
+ for widget in self.shiftButtons:
+ widget.setEnabled(enabled)
super().update()
def previewClickEvent(self, pos, size, button):
@@ -298,6 +333,22 @@ class Component(Component):
shadImg = ImageChops.offset(shadImg, -2, 2)
shadImg.paste(frame, box=(0, 0), mask=frame)
frame = shadImg
+ if self.showGrid:
+ drawer = ImageDraw.Draw(frame)
+ w, h = scale(0.05, self.width, self.height, int)
+ for x in range(self.pxWidth, self.width, self.pxWidth):
+ drawer.rectangle(
+ ((x, 0),
+ (x + w, self.height)),
+ fill=self.color,
+ )
+ for y in range(self.pxHeight, self.height, self.pxHeight):
+ drawer.rectangle(
+ ((0, y),
+ (self.width, y + h)),
+ fill=self.color,
+ )
+
return frame
def gridForTick(self, tick):
@@ -334,8 +385,11 @@ class Component(Component):
return pr
def loadPreset(self, pr, *args):
- super().loadPreset(pr, *args)
self.startingGrid = dict(pr['GRID'])
+ if self.startingGrid:
+ for widget in self.shiftButtons:
+ widget.setEnabled(True)
+ super().loadPreset(pr, *args)
def nearbyCoords(x, y):
diff --git a/src/components/life.ui b/src/components/life.ui
index 3b393dd..85b2926 100644
--- a/src/components/life.ui
+++ b/src/components/life.ui
@@ -83,7 +83,7 @@
-
- 24
+ 22
128
@@ -279,6 +279,13 @@
+ -
+
+
+ Show Grid
+
+
+
-
@@ -296,6 +303,46 @@
-
+
-
+
+
+ Up
+
+
+ Qt::UpArrow
+
+
+
+ -
+
+
+ Down
+
+
+ Qt::DownArrow
+
+
+
+ -
+
+
+ Left
+
+
+ Qt::LeftArrow
+
+
+
+ -
+
+
+ Right
+
+
+ Qt::RightArrow
+
+
+
-
--
cgit v1.2.3
From 64da6f14cea6eb0bf8fdffcc8277027fb0e96e54 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Fri, 11 Aug 2017 19:03:10 -0400
Subject: why did I use a dict here?
---
src/components/life.py | 35 +++++++++++++++++------------------
1 file changed, 17 insertions(+), 18 deletions(-)
diff --git a/src/components/life.py b/src/components/life.py
index 9254126..02dd76b 100644
--- a/src/components/life.py
+++ b/src/components/life.py
@@ -4,7 +4,6 @@ import os
import math
from component import Component
-from toolkit import alphabetizeDict
from toolkit.frame import BlankFrame, scale
@@ -16,7 +15,7 @@ class Component(Component):
super().widget(*args)
self.scale = 32
self.updateGridSize()
- self.startingGrid = {}
+ self.startingGrid = set()
self.page.pushButton_pickImage.clicked.connect(self.pickImage)
self.trackWidgets({
'tickRate': self.page.spinBox_tickRate,
@@ -59,7 +58,7 @@ class Component(Component):
def shiftGrid(self, d):
def newGrid(Xchange, Ychange):
return {
- (x + Xchange, y + Ychange): True
+ (x + Xchange, y + Ychange)
for x, y in self.startingGrid
}
@@ -105,9 +104,9 @@ class Component(Component):
math.ceil((pos[1] / size[1]) * self.gridHeight) - 1
)
if button == 1:
- self.startingGrid[pos] = True
- elif button == 2 and pos in self.startingGrid:
- self.startingGrid.pop(pos)
+ self.startingGrid.add(pos)
+ elif button == 2:
+ self.startingGrid.discard(pos)
def updateGridSize(self):
w, h = self.core.resolutions[-1].split('x')
@@ -223,7 +222,7 @@ class Component(Component):
)
}
for cell in nearbyCoords(x, y):
- if grid.get(cell) is None:
+ if cell not in grid:
continue
if cell[0] == x:
if cell[1] < y:
@@ -352,40 +351,40 @@ class Component(Component):
return frame
def gridForTick(self, tick):
- '''Given a tick number over 0, returns a new grid dict of tuples'''
+ '''Given a tick number over 0, returns a new grid set of tuples'''
lastGrid = self.tickGrids[tick - 1]
def neighbours(x, y):
- return [
+ return {
cell for cell in nearbyCoords(x, y)
- if lastGrid.get(cell) is not None
- ]
+ if cell in lastGrid
+ }
- newGrid = {}
+ newGrid = set()
for x, y in lastGrid:
surrounding = len(neighbours(x, y))
if surrounding == 2 or surrounding == 3:
- newGrid[(x, y)] = True
- potentialNewCells = set([
+ newGrid.add((x, y))
+ potentialNewCells = {
coordTup for origin in lastGrid
for coordTup in list(nearbyCoords(*origin))
- ])
+ }
for x, y in potentialNewCells:
if (x, y) in newGrid:
continue
surrounding = len(neighbours(x, y))
if surrounding == 3:
- newGrid[(x, y)] = True
+ newGrid.add((x, y))
return newGrid
def savePreset(self):
pr = super().savePreset()
- pr['GRID'] = alphabetizeDict(self.startingGrid)
+ pr['GRID'] = sorted(self.startingGrid)
return pr
def loadPreset(self, pr, *args):
- self.startingGrid = dict(pr['GRID'])
+ self.startingGrid = set(pr['GRID'])
if self.startingGrid:
for widget in self.shiftButtons:
widget.setEnabled(True)
--
cgit v1.2.3
From 282f1c4b12b485a567f0d055832a5bf4409404a3 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 12 Aug 2017 09:44:11 -0400
Subject: move previewWindow class into new file
and cache frequently-created blank frames
---
src/mainwindow.py | 61 +--------------------------------------------------
src/preview_win.py | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++
src/toolkit/frame.py | 37 +++++++++++++++++++++----------
3 files changed, 88 insertions(+), 72 deletions(-)
create mode 100644 src/preview_win.py
diff --git a/src/mainwindow.py b/src/mainwindow.py
index 114015c..1abb108 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -17,6 +17,7 @@ import logging
from core import Core
import preview_thread
+from preview_win import PreviewWindow
from presetmanager import PresetManager
from toolkit import disableWhenEncoding, disableWhenOpeningProject, checkOutput
@@ -24,66 +25,6 @@ from toolkit import disableWhenEncoding, disableWhenOpeningProject, checkOutput
log = logging.getLogger('AVP.MainWindow')
-class PreviewWindow(QtWidgets.QLabel):
- '''
- Paints the preview QLabel and maintains the aspect ratio when the
- window is resized.
- '''
- log = logging.getLogger('AVP.MainWindow.Preview')
-
- def __init__(self, parent, img):
- super(PreviewWindow, self).__init__()
- self.parent = parent
- self.setFrameStyle(QtWidgets.QFrame.StyledPanel)
- self.pixmap = QtGui.QPixmap(img)
-
- def paintEvent(self, event):
- size = self.size()
- painter = QtGui.QPainter(self)
- point = QtCore.QPoint(0, 0)
- scaledPix = self.pixmap.scaled(
- size,
- QtCore.Qt.KeepAspectRatio,
- transformMode=QtCore.Qt.SmoothTransformation)
-
- # start painting the label from left upper corner
- point.setX((size.width() - scaledPix.width())/2)
- point.setY((size.height() - scaledPix.height())/2)
- painter.drawPixmap(point, scaledPix)
-
- def changePixmap(self, img):
- self.pixmap = QtGui.QPixmap(img)
- self.repaint()
-
- def mousePressEvent(self, event):
- if self.parent.encoding:
- return
-
- i = self.parent.window.listWidget_componentList.currentRow()
- if i >= 0:
- component = self.parent.core.selectedComponents[i]
- if not hasattr(component, 'previewClickEvent'):
- self.log.info('Ignored click event')
- return
- pos = (event.x(), event.y())
- size = (self.width(), self.height())
- butt = event.button()
- self.log.info('Click event for #%s: %s button %s' % (
- i, pos, butt))
- component.previewClickEvent(
- pos, size, butt
- )
- self.parent.core.updateComponent(i)
-
- @QtCore.pyqtSlot(str)
- def threadError(self, msg):
- self.parent.showMessage(
- msg=msg,
- icon='Critical',
- parent=self
- )
-
-
class MainWindow(QtWidgets.QMainWindow):
'''
The MainWindow wraps many Core methods in order to update the GUI
diff --git a/src/preview_win.py b/src/preview_win.py
new file mode 100644
index 0000000..40c19c6
--- /dev/null
+++ b/src/preview_win.py
@@ -0,0 +1,62 @@
+from PyQt5 import QtCore, QtGui, QtWidgets
+import logging
+
+
+class PreviewWindow(QtWidgets.QLabel):
+ '''
+ Paints the preview QLabel in MainWindow and maintains the aspect ratio
+ when the window is resized.
+ '''
+ log = logging.getLogger('AVP.PreviewWindow')
+
+ def __init__(self, parent, img):
+ super(PreviewWindow, self).__init__()
+ self.parent = parent
+ self.setFrameStyle(QtWidgets.QFrame.StyledPanel)
+ self.pixmap = QtGui.QPixmap(img)
+
+ def paintEvent(self, event):
+ size = self.size()
+ painter = QtGui.QPainter(self)
+ point = QtCore.QPoint(0, 0)
+ scaledPix = self.pixmap.scaled(
+ size,
+ QtCore.Qt.KeepAspectRatio,
+ transformMode=QtCore.Qt.SmoothTransformation)
+
+ # start painting the label from left upper corner
+ point.setX((size.width() - scaledPix.width())/2)
+ point.setY((size.height() - scaledPix.height())/2)
+ painter.drawPixmap(point, scaledPix)
+
+ def changePixmap(self, img):
+ self.pixmap = QtGui.QPixmap(img)
+ self.repaint()
+
+ def mousePressEvent(self, event):
+ if self.parent.encoding:
+ return
+
+ i = self.parent.window.listWidget_componentList.currentRow()
+ if i >= 0:
+ component = self.parent.core.selectedComponents[i]
+ if not hasattr(component, 'previewClickEvent'):
+ self.log.info('Ignored click event')
+ return
+ pos = (event.x(), event.y())
+ size = (self.width(), self.height())
+ butt = event.button()
+ self.log.info('Click event for #%s: %s button %s' % (
+ i, pos, butt))
+ component.previewClickEvent(
+ pos, size, butt
+ )
+ self.parent.core.updateComponent(i)
+
+ @QtCore.pyqtSlot(str)
+ def threadError(self, msg):
+ self.parent.showMessage(
+ msg=msg,
+ icon='Critical',
+ parent=self
+ )
diff --git a/src/toolkit/frame.py b/src/toolkit/frame.py
index 02f9229..e4332eb 100644
--- a/src/toolkit/frame.py
+++ b/src/toolkit/frame.py
@@ -77,27 +77,40 @@ def defaultSize(framefunc):
def FloodFrame(width, height, RgbaTuple):
+ log.debug('Creating new %s*%s %s flood frame' % (
+ width, height,
+ 'blank' if RgbaTuple[3] == 0 else RgbaTuple
+ )
+ )
return Image.new("RGBA", (width, height), RgbaTuple)
@defaultSize
-def BlankFrame(width, height):
+def BlankFrame(width, height, blankFrames={}):
'''The base frame used by each component to start drawing.'''
- log.debug('Creating new %s*%s blank frame' % (width, height))
- return FloodFrame(width, height, (0, 0, 0, 0))
+ try:
+ return blankFrames[(width, height)]
+ except KeyError:
+ newFrame = FloodFrame(width, height, (0, 0, 0, 0))
+ blankFrames[(width, height)] = newFrame
+ return newFrame
@defaultSize
-def Checkerboard(width, height):
+def Checkerboard(width, height, checkerboards={}):
'''
A checkerboard to represent transparency to the user.
TODO: Would be cool to generate this image with numpy instead.
'''
- log.debug('Creating new %s*%s checkerboard' % (width, height))
- image = FloodFrame(1920, 1080, (0, 0, 0, 0))
- image.paste(Image.open(
- os.path.join(core.Core.wd, "background.png")),
- (0, 0)
- )
- image = image.resize((width, height))
- return image
+ try:
+ return checkerboards[(width, height)]
+ except KeyError:
+ log.debug('Creating new %s*%s checkerboard' % (width, height))
+ image = FloodFrame(1920, 1080, (0, 0, 0, 0))
+ image.paste(Image.open(
+ os.path.join(core.Core.wd, "background.png")),
+ (0, 0)
+ )
+ image = image.resize((width, height))
+ checkerboards[(width, height)] = image
+ return image
--
cgit v1.2.3
From d6b6083f80ae609c801ef63285718325cd71d0c9 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 12 Aug 2017 22:51:46 -0400
Subject: rv pointless optimization & remove circular imports (again...)
the last commit does showcase an enlightening bug however
---
src/core.py | 2 +-
src/toolkit/ffmpeg.py | 2 +-
src/toolkit/frame.py | 34 ++++++++++++++--------------------
3 files changed, 16 insertions(+), 22 deletions(-)
diff --git a/src/core.py b/src/core.py
index 4023542..2b85f7e 100644
--- a/src/core.py
+++ b/src/core.py
@@ -10,7 +10,6 @@ from importlib import import_module
import logging
import toolkit
-import video_thread
log = logging.getLogger('AVP.Core')
@@ -418,6 +417,7 @@ class Core:
def newVideoWorker(self, loader, audioFile, outputPath):
'''loader is MainWindow or Command object which must own the thread'''
+ import video_thread
self.videoThread = QtCore.QThread(loader)
videoWorker = video_thread.Worker(
loader, audioFile, outputPath, self.selectedComponents
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index 6ab445c..afcb37c 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -12,7 +12,6 @@ import logging
import core
from toolkit.common import checkOutput, pipeWrapper
-from component import ComponentError
log = logging.getLogger('AVP.Toolkit.Ffmpeg')
@@ -91,6 +90,7 @@ class FfmpegVideo:
self.frameBuffer.task_done()
def fillBuffer(self):
+ from component import ComponentError
logFilename = os.path.join(
core.Core.logDir, 'render_%s.log' % str(self.component.compPos))
log.debug('Creating ffmpeg process (log at %s)' % logFilename)
diff --git a/src/toolkit/frame.py b/src/toolkit/frame.py
index e4332eb..6174072 100644
--- a/src/toolkit/frame.py
+++ b/src/toolkit/frame.py
@@ -86,31 +86,25 @@ def FloodFrame(width, height, RgbaTuple):
@defaultSize
-def BlankFrame(width, height, blankFrames={}):
+def BlankFrame(width, height):
'''The base frame used by each component to start drawing.'''
- try:
- return blankFrames[(width, height)]
- except KeyError:
- newFrame = FloodFrame(width, height, (0, 0, 0, 0))
- blankFrames[(width, height)] = newFrame
- return newFrame
+ newFrame = FloodFrame(width, height, (0, 0, 0, 0))
+ blankFrames[(width, height)] = newFrame
+ return newFrame
@defaultSize
-def Checkerboard(width, height, checkerboards={}):
+def Checkerboard(width, height):
'''
A checkerboard to represent transparency to the user.
TODO: Would be cool to generate this image with numpy instead.
'''
- try:
- return checkerboards[(width, height)]
- except KeyError:
- log.debug('Creating new %s*%s checkerboard' % (width, height))
- image = FloodFrame(1920, 1080, (0, 0, 0, 0))
- image.paste(Image.open(
- os.path.join(core.Core.wd, "background.png")),
- (0, 0)
- )
- image = image.resize((width, height))
- checkerboards[(width, height)] = image
- return image
+ log.debug('Creating new %s*%s checkerboard' % (width, height))
+ image = FloodFrame(1920, 1080, (0, 0, 0, 0))
+ image.paste(Image.open(
+ os.path.join(core.Core.wd, "background.png")),
+ (0, 0)
+ )
+ image = image.resize((width, height))
+ checkerboards[(width, height)] = image
+ return image
--
cgit v1.2.3
From 3f2834529fc31e0f00440237dd4a0a374f378718 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 12 Aug 2017 23:03:56 -0400
Subject: fix
---
src/toolkit/frame.py | 16 +++++-----------
1 file changed, 5 insertions(+), 11 deletions(-)
diff --git a/src/toolkit/frame.py b/src/toolkit/frame.py
index 6174072..63774a6 100644
--- a/src/toolkit/frame.py
+++ b/src/toolkit/frame.py
@@ -32,12 +32,12 @@ class FramePainter(QtGui.QPainter):
super().setPen(penStyle)
def finalize(self):
- self.end()
imBytes = self.image.bits().asstring(self.image.byteCount())
-
- return Image.frombytes(
+ frame = Image.frombytes(
'RGBA', (self.image.width(), self.image.height()), imBytes
)
+ self.end()
+ return frame
class PaintColor(QtGui.QColor):
@@ -78,19 +78,14 @@ def defaultSize(framefunc):
def FloodFrame(width, height, RgbaTuple):
log.debug('Creating new %s*%s %s flood frame' % (
- width, height,
- 'blank' if RgbaTuple[3] == 0 else RgbaTuple
- )
- )
+ width, height, RgbaTuple))
return Image.new("RGBA", (width, height), RgbaTuple)
@defaultSize
def BlankFrame(width, height):
'''The base frame used by each component to start drawing.'''
- newFrame = FloodFrame(width, height, (0, 0, 0, 0))
- blankFrames[(width, height)] = newFrame
- return newFrame
+ return FloodFrame(width, height, (0, 0, 0, 0))
@defaultSize
@@ -106,5 +101,4 @@ def Checkerboard(width, height):
(0, 0)
)
image = image.resize((width, height))
- checkerboards[(width, height)] = image
return image
--
cgit v1.2.3
From a233d36ce29be459cc9cc041e77f96b9f40d0ed0 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 13 Aug 2017 15:43:42 -0400
Subject: graceful renderNode crash, code clean-up
---
src/components/life.py | 4 +--
src/video_thread.py | 88 +++++++++++++++++++++++++++++---------------------
2 files changed, 54 insertions(+), 38 deletions(-)
diff --git a/src/components/life.py b/src/components/life.py
index 02dd76b..2383d30 100644
--- a/src/components/life.py
+++ b/src/components/life.py
@@ -124,7 +124,7 @@ class Component(Component):
self.tickGrids = {0: self.startingGrid}
tick = 0
for frameNo in range(
- self.tickRate, len(self.completeAudioArray), self.sampleSize
+ self.tickRate, self.audioArrayLen, self.sampleSize
):
if self.parent.canceled:
break
@@ -133,7 +133,7 @@ class Component(Component):
self.tickGrids[tick] = self.gridForTick(tick)
# update progress bar
- progress = int(100*(frameNo/len(self.completeAudioArray)))
+ progress = int(100*(frameNo/self.audioArrayLen))
if progress >= 100:
progress = 100
pStr = "Computing evolution: "+str(progress)+'%'
diff --git a/src/video_thread.py b/src/video_thread.py
index e7e4136..63d06ee 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -32,7 +32,7 @@ log = logging.getLogger("AVP.VideoThread")
class Worker(QtCore.QObject):
- imageCreated = pyqtSignal(['QImage'])
+ imageCreated = pyqtSignal('QImage')
videoCreated = pyqtSignal()
progressBarUpdate = pyqtSignal(int)
progressBarSetText = pyqtSignal(str)
@@ -50,6 +50,7 @@ class Worker(QtCore.QObject):
self.outputFile = outputFile
self.inputFile = inputFile
+ self.hertz = 44100
self.sampleSize = 1470 # 44100 / 30 = 1470
self.canceled = False
self.error = False
@@ -62,30 +63,40 @@ class Worker(QtCore.QObject):
to create subframes & composite them into the final frame.
The resulting frames are collected in the renderQueue
'''
+ def err():
+ self.closePipe()
+ self.cancelExport()
+ self.error = True
+ comp._error.emit('A render node failed critically.', str(e))
+
while not self.stopped:
audioI = self.compositeQueue.get()
bgI = int(audioI / self.sampleSize)
frame = None
for layerNo, comp in enumerate(reversed((self.components))):
- if layerNo in self.staticComponents:
- if self.staticComponents[layerNo] is None:
- # this layer was merged into a following layer
- continue
- # static component
- if frame is None: # bottom-most layer
- frame = self.staticComponents[layerNo]
- else:
- frame = Image.alpha_composite(
- frame, self.staticComponents[layerNo]
- )
- else:
- # animated component
- if frame is None: # bottom-most layer
- frame = comp.frameRender(bgI)
+ try:
+ if layerNo in self.staticComponents:
+ if self.staticComponents[layerNo] is None:
+ # this layer was merged into a following layer
+ continue
+ # static component
+ if frame is None: # bottom-most layer
+ frame = self.staticComponents[layerNo]
+ else:
+ frame = Image.alpha_composite(
+ frame, self.staticComponents[layerNo]
+ )
+
else:
- frame = Image.alpha_composite(
- frame, comp.frameRender(bgI)
- )
+ # animated component
+ if frame is None: # bottom-most layer
+ frame = comp.frameRender(bgI)
+ else:
+ frame = Image.alpha_composite(
+ frame, comp.frameRender(bgI)
+ )
+ except Exception as e:
+ err()
self.renderQueue.put([audioI, frame])
self.compositeQueue.task_done()
@@ -98,7 +109,7 @@ class Worker(QtCore.QObject):
'''
log.debug('Dispatching Frames for Compositing...')
- for audioI in range(0, len(self.completeAudioArray), self.sampleSize):
+ for audioI in range(0, self.audioArrayLen, self.sampleSize):
self.compositeQueue.put(audioI)
def previewDispatch(self):
@@ -150,17 +161,18 @@ class Worker(QtCore.QObject):
self.cancelExport()
return
self.completeAudioArray, duration = audioFileTraits
+ self.audioArrayLen = len(self.completeAudioArray)
else:
duration = getAudioDuration(self.inputFile)
- class FakeList:
- def __len__(self):
- return int((duration * 44100) + 44100) - 1470
- self.completeAudioArray = FakeList()
+ self.completeAudioArray = []
+ self.audioArrayLen = int(
+ ((duration * self.hertz) +
+ self.hertz) - self.sampleSize)
self.progressBarUpdate.emit(0)
self.progressBarSetText.emit("Starting components...")
canceledByComponent = False
- initText = ", ".join([
+ initText = ", ".join([
"%s) %s" % (num, str(component))
for num, component in enumerate(reversed(self.components))
])
@@ -172,6 +184,7 @@ class Worker(QtCore.QObject):
comp.preFrameRender(
audioFile=self.inputFile,
completeAudioArray=self.completeAudioArray,
+ audioArrayLen=self.audioArrayLen,
sampleSize=self.sampleSize,
progressBarUpdate=self.progressBarUpdate,
progressBarSetText=self.progressBarSetText
@@ -276,7 +289,7 @@ class Worker(QtCore.QObject):
self.progressBarSetText.emit("Exporting video...")
if not self.canceled:
for audioI in range(
- 0, len(self.completeAudioArray), self.sampleSize):
+ 0, self.audioArrayLen, self.sampleSize):
while True:
if audioI in frameBuffer or self.canceled:
# if frame's in buffer, pipe it to ffmpeg
@@ -295,7 +308,7 @@ class Worker(QtCore.QObject):
break
# increase progress bar value
- completion = (audioI / len(self.completeAudioArray)) * 100
+ completion = (audioI / self.audioArrayLen) * 100
if progressBarValue + 1 <= completion:
progressBarValue = numpy.floor(completion)
self.progressBarUpdate.emit(progressBarValue)
@@ -305,15 +318,7 @@ class Worker(QtCore.QObject):
numpy.seterr(all='print')
- try:
- self.out_pipe.stdin.close()
- except BrokenPipeError:
- log.error('Broken pipe to ffmpeg!')
- if self.out_pipe.stderr is not None:
- log.error(self.out_pipe.stderr.read())
- self.out_pipe.stderr.close()
- self.error = True
- self.out_pipe.wait()
+ self.closePipe()
for comp in reversed(self.components):
comp.postFrameRender()
@@ -342,6 +347,17 @@ class Worker(QtCore.QObject):
self.encoding.emit(False)
self.videoCreated.emit()
+ def closePipe(self):
+ try:
+ self.out_pipe.stdin.close()
+ except BrokenPipeError:
+ log.error('Broken pipe to ffmpeg!')
+ if self.out_pipe.stderr is not None:
+ log.error(self.out_pipe.stderr.read())
+ self.out_pipe.stderr.close()
+ self.error = True
+ self.out_pipe.wait()
+
def cancelExport(self):
self.progressBarUpdate.emit(0)
self.progressBarSetText.emit('Export Canceled')
--
cgit v1.2.3
From 9c8792df9bad068fed8a9a1777b2774c103c9ce4 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 13 Aug 2017 15:54:36 -0400
Subject: made an authors file
---
AUTHORS | 11 +++++++++++
src/video_thread.py | 4 +++-
2 files changed, 14 insertions(+), 1 deletion(-)
create mode 100644 AUTHORS
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..417d97e
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,11 @@
+Original version created by Martin Kaistra
+Version 2 created by tassaron and DH4
+
+Contributors:
+* Martin Kaistra
+* Brianna Rainey
+* DH4
+
+Pull Requests By:
+* HunterwolfAT
+* rikai
diff --git a/src/video_thread.py b/src/video_thread.py
index 63d06ee..5acbda4 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -67,7 +67,9 @@ class Worker(QtCore.QObject):
self.closePipe()
self.cancelExport()
self.error = True
- comp._error.emit('A render node failed critically.', str(e))
+ msg = 'A render node failed critically.'
+ log.critical(msg)
+ comp._error.emit(msg, str(e))
while not self.stopped:
audioI = self.compositeQueue.get()
--
cgit v1.2.3
From bed07479f1b4bf24a0b9c84217d41ebbe880a8fb Mon Sep 17 00:00:00 2001
From: tassaron
Date: Mon, 14 Aug 2017 10:10:32 -0400
Subject: faster Spectrum preview & custom VERBOSE loglvl
---
src/__init__.py | 23 ++++++++++++++++++++
src/component.py | 5 +++++
src/components/spectrum.py | 52 +++++++++++++++++++++++-----------------------
src/core.py | 10 +++++----
src/mainwindow.py | 6 +++---
src/toolkit/frame.py | 3 ++-
src/video_thread.py | 4 ++--
7 files changed, 67 insertions(+), 36 deletions(-)
diff --git a/src/__init__.py b/src/__init__.py
index 2f4cffa..73f174a 100644
--- a/src/__init__.py
+++ b/src/__init__.py
@@ -1,5 +1,28 @@
import sys
import os
+import logging
+
+
+class Logger(logging.getLoggerClass()):
+ '''
+ Custom Logger class to handle custom VERBOSE log level.
+ Levels used in this program are as follows:
+ VERBOSE Annoyingly frequent debug messages (e.g, in loops)
+ DEBUG Ordinary debug information
+ INFO Expected events that are expensive or irreversible
+ WARNING A non-fatal error or suspicious behaviour
+ ERROR Any error that would interrupt the user
+ CRITICAL Things that really shouldn't happen at all
+ '''
+ def __init__(self, name, level=logging.NOTSET):
+ super().__init__(name, level)
+ logging.addLevelName(5, "VERBOSE")
+
+ def verbose(self, msg, *args, **kwargs):
+ if self.isEnabledFor(5):
+ self._log(5, msg, args, **kwargs)
+logging.setLoggerClass(Logger)
+logging.VERBOSE = 5
if getattr(sys, 'frozen', False):
diff --git a/src/component.py b/src/component.py
index d011f1e..cf3085c 100644
--- a/src/component.py
+++ b/src/component.py
@@ -39,6 +39,11 @@ class ComponentMetaclass(type(QtCore.QObject)):
def renderWrapper(func):
def renderWrapper(self, *args, **kwargs):
try:
+ log.verbose('### %s #%s renders%s frame %s###' % (
+ self.__class__.name, str(self.compPos),
+ '' if args else ' a preview',
+ '' if not args else '%s ' % args[0],
+ ))
return func(self, *args, **kwargs)
except Exception as e:
try:
diff --git a/src/components/spectrum.py b/src/components/spectrum.py
index 32763c0..246b839 100644
--- a/src/components/spectrum.py
+++ b/src/components/spectrum.py
@@ -27,6 +27,8 @@ class Component(Component):
self._image = BlankFrame(self.width, self.height)
self.chunkSize = 4 * self.width * self.height
self.changedOptions = True
+ self.previewSize = (214, 120)
+ self.previewPipe = None
if hasattr(self.parent, 'window'):
# update preview when audio file changes (if genericPreview is off)
@@ -72,7 +74,8 @@ class Component(Component):
if not changedSize \
and not self.changedOptions \
and self.previewFrame is not None:
- log.debug('Comp #%s is reusing old preview frame' % self.compPos)
+ log.debug(
+ 'Spectrum #%s is reusing old preview frame' % self.compPos)
return self.previewFrame
frame = self.getPreviewFrame()
@@ -86,6 +89,7 @@ class Component(Component):
def preFrameRender(self, **kwargs):
super().preFrameRender(**kwargs)
+ self.previewPipe.wait()
self.updateChunksize()
w, h = scale(self.scale, self.width, self.height, str)
self.video = FfmpegVideo(
@@ -141,18 +145,21 @@ class Component(Component):
with open(logFilename, 'w') as logf:
logf.write(" ".join(command) + '\n\n')
with open(logFilename, 'a') as logf:
- pipe = openPipe(
+ self.previewPipe = openPipe(
command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
stderr=logf, bufsize=10**8
)
- byteFrame = pipe.stdout.read(self.chunkSize)
- closePipe(pipe)
+ byteFrame = self.previewPipe.stdout.read(self.chunkSize)
+ closePipe(self.previewPipe)
frame = self.finalizeFrame(byteFrame)
return frame
def makeFfmpegFilter(self, preview=False, startPt=0):
- w, h = scale(self.scale, self.width, self.height, str)
+ if preview:
+ w, h = self.previewSize
+ else:
+ w, h = (self.width, self.height)
color = self.page.comboBox_color.currentText().lower()
genericPreview = self.settings.value("pref_genericPreview")
@@ -173,8 +180,7 @@ class Component(Component):
'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"),
+ w, h,
self.page.comboBox_window.currentText(),
color, amplitude,
)
@@ -197,8 +203,7 @@ class Component(Component):
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"),
+ w, h,
amplitude, display
)
)
@@ -214,8 +219,7 @@ class Component(Component):
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"),
+ w, h,
'line'if self.draw else 'dot',
m, amplitude, str(self.zoom),
)
@@ -225,8 +229,7 @@ class Component(Component):
'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"),
+ w, h,
str(self.tc),
)
)
@@ -235,28 +238,28 @@ class Component(Component):
'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 '% (
+ 'crop=in_w/8:in_h:(in_w/8)*7:0 ' % (
self.settings.value("outputFrameRate"),
- self.settings.value("outputWidth"),
- self.settings.value("outputHeight"),
+ w, h,
)
)
return [
'-filter_complex',
'%s%s%s%s [v1]; '
- '[v1] %sscale=%s:%s%s%s%s [v]' % (
+ '[v1] %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' % (
+ 'trim=start=%s:end=%s, ' % (
"{0:.3f}".format(startPt + 12),
"{0:.3f}".format(startPt + 12.5)
) if preview else '',
+ 'scale=%sx%s' % scale(
+ self.scale, self.width, self.height, str),
+ ', hue=h=%s:s=10' % str(self.hue) if self.hue > 0 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 ''
@@ -281,10 +284,7 @@ class Component(Component):
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
+
+ frame = BlankFrame(self.width, self.height)
+ frame.paste(image, box=(self.x, self.y))
return frame
diff --git a/src/core.py b/src/core.py
index 2b85f7e..4dfb210 100644
--- a/src/core.py
+++ b/src/core.py
@@ -562,9 +562,10 @@ class Core:
logStream = logging.StreamHandler()
logStream.setLevel(STDOUT_LOGLVL)
- # create formatters and put everything together
+ # create formatters for each stream
fileFormatter = logging.Formatter(
- '[%(asctime)s] <%(name)s> %(levelname)s: %(message)s'
+ '[%(asctime)s] %(threadName)-10.10s %(name)-23.23s %(levelname)s: '
+ '%(message)s'
)
streamFormatter = logging.Formatter(
'<%(name)s> %(message)s'
@@ -572,13 +573,14 @@ class Core:
logFile.setFormatter(fileFormatter)
libLogFile.setFormatter(fileFormatter)
logStream.setFormatter(streamFormatter)
+
log = logging.getLogger('AVP')
- log.setLevel(FILE_LOGLVL)
log.addHandler(logFile)
log.addHandler(logStream)
libLog = logging.getLogger()
- libLog.setLevel(FILE_LOGLVL)
libLog.addHandler(libLogFile)
+ # lowest level must be explicitly set on the root Logger
+ libLog.setLevel(0)
# always store settings in class variables even if a Core object is not created
Core.storeSettings()
diff --git a/src/mainwindow.py b/src/mainwindow.py
index 1abb108..af6e190 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -44,7 +44,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.window = window
self.core = Core()
log.debug(
- 'Main thread id: {}'.format(QtCore.QThread.currentThreadId()))
+ 'Main thread id: {}'.format(int(QtCore.QThread.currentThreadId())))
# widgets of component settings
self.pages = []
@@ -465,8 +465,8 @@ class MainWindow(QtWidgets.QMainWindow):
and filecmp.cmp(
self.autosavePath, self.currentProject) == identical:
log.debug(
- 'Autosave found %s to be identical' % \
- 'not' if not identical else ''
+ 'Autosave found %s to be identical'
+ % 'not' if not identical else ''
)
return True
except FileNotFoundError:
diff --git a/src/toolkit/frame.py b/src/toolkit/frame.py
index 63774a6..ad8537c 100644
--- a/src/toolkit/frame.py
+++ b/src/toolkit/frame.py
@@ -21,6 +21,7 @@ class FramePainter(QtGui.QPainter):
Pillow image with finalize()
'''
def __init__(self, width, height):
+ log.verbose('Creating new FramePainter')
image = BlankFrame(width, height)
self.image = QtGui.QImage(ImageQt(image))
super().__init__(self.image)
@@ -77,7 +78,7 @@ def defaultSize(framefunc):
def FloodFrame(width, height, RgbaTuple):
- log.debug('Creating new %s*%s %s flood frame' % (
+ log.verbose('Creating new %s*%s %s flood frame' % (
width, height, RgbaTuple))
return Image.new("RGBA", (width, height), RgbaTuple)
diff --git a/src/video_thread.py b/src/video_thread.py
index 5acbda4..87fb9bd 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -212,7 +212,7 @@ class Worker(QtCore.QObject):
compError[0]
)
)
- log.critical(errMsg)
+ log.error(errMsg)
comp._error.emit(errMsg, compError[1])
break
if 'static' in compProps:
@@ -221,7 +221,7 @@ class Worker(QtCore.QObject):
if self.canceled:
if canceledByComponent:
- log.critical('Export cancelled by component #%s (%s): %s' % (
+ log.error('Export cancelled by component #%s (%s): %s' % (
compNo,
comp.name,
'No message.' if comp.error() is None else (
--
cgit v1.2.3
From ea1a422cc52bc972574070fbe784a35876ff8baa Mon Sep 17 00:00:00 2001
From: tassaron
Date: Mon, 14 Aug 2017 14:28:30 -0400
Subject: better aevalsrc inputs for spectrum previews
---
src/components/spectrum.py | 32 ++++++++++++++++++++++++++------
src/components/waveform.py | 25 +++++++++++++++----------
src/toolkit/ffmpeg.py | 19 ++++++++++++++-----
test.wav | Bin 0 -> 14348366 bytes
4 files changed, 55 insertions(+), 21 deletions(-)
create mode 100644 test.wav
diff --git a/src/components/spectrum.py b/src/components/spectrum.py
index 246b839..89130a2 100644
--- a/src/components/spectrum.py
+++ b/src/components/spectrum.py
@@ -19,7 +19,7 @@ log = logging.getLogger('AVP.Components.Spectrum')
class Component(Component):
name = 'Spectrum'
- version = '1.0.0'
+ version = '1.0.1'
def widget(self, *args):
self.previewFrame = None
@@ -65,8 +65,17 @@ class Component(Component):
self.changedOptions = True
def update(self):
- self.page.stackedWidget.setCurrentIndex(
- self.page.comboBox_filterType.currentIndex())
+ filterType = self.page.comboBox_filterType.currentIndex()
+ self.page.stackedWidget.setCurrentIndex(filterType)
+ if filterType == 3:
+ self.page.spinBox_hue.setEnabled(False)
+ else:
+ self.page.spinBox_hue.setEnabled(True)
+ if filterType == 2 or filterType == 4:
+ self.page.checkBox_mono.setEnabled(False)
+ else:
+ self.page.checkBox_mono.setEnabled(True)
+
super().update()
def previewRender(self):
@@ -81,6 +90,8 @@ class Component(Component):
frame = self.getPreviewFrame()
self.changedOptions = False
if not frame:
+ log.warning(
+ 'Spectrum #%s failed to create a preview frame' % self.compPos)
self.previewFrame = None
return BlankFrame(self.width, self.height)
else:
@@ -244,13 +255,21 @@ class Component(Component):
)
)
+ if self.filterType < 2:
+ exampleSnd = exampleSound('freq')
+ elif self.filterType == 2 or self.filterType == 4:
+ exampleSnd = exampleSound('stereo')
+ elif self.filterType == 3:
+ exampleSnd = exampleSound('white')
+
return [
'-filter_complex',
'%s%s%s%s [v1]; '
'[v1] %s%s%s%s%s [v]' % (
- exampleSound() if preview and genericPreview else '[0:a] ',
+ exampleSnd if preview and genericPreview else '[0:a] ',
'compand=gain=4,' if self.compress else '',
- 'aformat=channel_layouts=mono,' if self.mono else '',
+ 'aformat=channel_layouts=mono,'
+ if self.mono and self.filterType not in (2, 4) else '',
filter_,
'hflip, ' if self.mirror else '',
'trim=start=%s:end=%s, ' % (
@@ -259,7 +278,8 @@ class Component(Component):
) if preview else '',
'scale=%sx%s' % scale(
self.scale, self.width, self.height, str),
- ', hue=h=%s:s=10' % str(self.hue) if self.hue > 0 else '',
+ ', hue=h=%s:s=10' % str(self.hue)
+ if self.hue > 0 and self.filterType != 3 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 ''
diff --git a/src/components/waveform.py b/src/components/waveform.py
index 1517be2..0743e55 100644
--- a/src/components/waveform.py
+++ b/src/components/waveform.py
@@ -140,13 +140,16 @@ class Component(Component):
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,
+ 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_ = (
@@ -160,18 +163,20 @@ class Component(Component):
)
)
+ baselineHeight = int(self.height * (4 / 1080))
return [
'-filter_complex',
'%s%s%s'
'%s%s%s [v1]; '
'[v1] scale=%s:%s%s [v]' % (
- exampleSound() if preview and genericPreview else '[0:a] ',
+ exampleSound('wave', extra='')
+ 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
+ ', drawbox=x=(iw-w)/2:y=(ih-h)/2:w=iw:h=%s:color=%s@%s' % (
+ baselineHeight, hexcolor, opacity,
) if self.mode < 2 else '',
', hflip' if self.mirror else'',
w, h,
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index afcb37c..8fe9148 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -457,8 +457,17 @@ def readAudioFile(filename, videoWorker):
return (completeAudioArray, duration)
-def exampleSound():
- return (
- 'aevalsrc=tan(random(1)*PI*t)*sin(random(0)*2*PI*t),'
- 'apulsator=offset_l=0.5:offset_r=0.5,'
- )
+def exampleSound(
+ style='white', extra='apulsator=offset_l=0.35:offset_r=0.67'):
+ '''Help generate an example sound for use in creating a preview'''
+
+ if style == 'white':
+ src = '-2+random(0)'
+ elif style == 'freq':
+ src = 'sin(1000*t*PI*t)'
+ elif style == 'wave':
+ src = 'sin(random(0)*2*PI*t)*tan(random(0)*2*PI*t)'
+ elif style == 'stereo':
+ src = '0.1*sin(2*PI*(360-2.5/2)*t) : 0.1*sin(2*PI*(360+2.5/2)*t)'
+
+ return "aevalsrc='%s', %s%s" % (src, extra, ', ' if extra else '')
diff --git a/test.wav b/test.wav
new file mode 100644
index 0000000..98afe5f
Binary files /dev/null and b/test.wav differ
--
cgit v1.2.3
From 39d6a4e5af94a8aa612a009bbe235715b84e7abc Mon Sep 17 00:00:00 2001
From: tassaron
Date: Mon, 14 Aug 2017 14:30:57 -0400
Subject: rm test.wav
---
.gitignore | 6 +++++-
test.wav | Bin 14348366 -> 0 bytes
2 files changed, 5 insertions(+), 1 deletion(-)
delete mode 100644 test.wav
diff --git a/.gitignore b/.gitignore
index 916c6c1..380168f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,10 @@ env/*
.vscode/*
*.mkv
*.mp4
+*.wav
+*.mp3
+*.aif
+*.ac3
*.zip
*.tar
*.tar.*
@@ -13,4 +17,4 @@ env/*
ffmpeg
*.bak
*~
-*.goutput*
\ No newline at end of file
+*.goutput*
diff --git a/test.wav b/test.wav
deleted file mode 100644
index 98afe5f..0000000
Binary files a/test.wav and /dev/null differ
--
cgit v1.2.3
From a327bec4e42cc572fb84e559025e888a4a20edd3 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Mon, 14 Aug 2017 16:39:53 -0400
Subject: organizing GUImode-specific code
---
src/gui/__init__.py | 0
src/gui/mainwindow.py | 946 ++++++++++++++++++++++++++++++++++++++++++++++
src/gui/mainwindow.ui | 828 ++++++++++++++++++++++++++++++++++++++++
src/gui/presetmanager.py | 358 ++++++++++++++++++
src/gui/presetmanager.ui | 150 ++++++++
src/gui/preview_thread.py | 90 +++++
src/gui/preview_win.py | 62 +++
src/mainwindow.py | 946 ----------------------------------------------
src/mainwindow.ui | 828 ----------------------------------------
src/presetmanager.py | 358 ------------------
src/presetmanager.ui | 150 --------
src/preview_thread.py | 90 -----
src/preview_win.py | 62 ---
13 files changed, 2434 insertions(+), 2434 deletions(-)
create mode 100644 src/gui/__init__.py
create mode 100644 src/gui/mainwindow.py
create mode 100644 src/gui/mainwindow.ui
create mode 100644 src/gui/presetmanager.py
create mode 100644 src/gui/presetmanager.ui
create mode 100644 src/gui/preview_thread.py
create mode 100644 src/gui/preview_win.py
delete mode 100644 src/mainwindow.py
delete mode 100644 src/mainwindow.ui
delete mode 100644 src/presetmanager.py
delete mode 100644 src/presetmanager.ui
delete mode 100644 src/preview_thread.py
delete mode 100644 src/preview_win.py
diff --git a/src/gui/__init__.py b/src/gui/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/gui/mainwindow.py b/src/gui/mainwindow.py
new file mode 100644
index 0000000..af6e190
--- /dev/null
+++ b/src/gui/mainwindow.py
@@ -0,0 +1,946 @@
+'''
+ When using GUI mode, this module's object (the main window) takes
+ user input to construct a program state (stored in the Core object).
+ This shows a preview of the video being created and allows for saving
+ projects and exporting the video at a later time.
+'''
+from PyQt5 import QtCore, QtGui, uic, QtWidgets
+from PyQt5.QtWidgets import QMenu, QShortcut
+from PIL import Image
+from queue import Queue
+import sys
+import os
+import signal
+import filecmp
+import time
+import logging
+
+from core import Core
+import preview_thread
+from preview_win import PreviewWindow
+from presetmanager import PresetManager
+from toolkit import disableWhenEncoding, disableWhenOpeningProject, checkOutput
+
+
+log = logging.getLogger('AVP.MainWindow')
+
+
+class MainWindow(QtWidgets.QMainWindow):
+ '''
+ The MainWindow wraps many Core methods in order to update the GUI
+ accordingly. E.g., instead of self.core.openProject(), it will use
+ self.openProject() and update the window titlebar within the wrapper.
+
+ MainWindow manages the autosave feature, although Core has the
+ primary functions for opening and creating project files.
+ '''
+
+ createVideo = QtCore.pyqtSignal()
+ newTask = QtCore.pyqtSignal(list) # for the preview window
+ processTask = QtCore.pyqtSignal()
+
+ def __init__(self, window, project):
+ QtWidgets.QMainWindow.__init__(self)
+ self.window = window
+ self.core = Core()
+ log.debug(
+ 'Main thread id: {}'.format(int(QtCore.QThread.currentThreadId())))
+
+ # widgets of component settings
+ self.pages = []
+ self.lastAutosave = time.time()
+ # list of previous five autosave times, used to reduce update spam
+ self.autosaveTimes = []
+ self.autosaveCooldown = 0.2
+ self.encoding = False
+
+ # Find settings created by Core object
+ self.dataDir = Core.dataDir
+ self.presetDir = Core.presetDir
+ self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
+ self.settings = Core.settings
+
+ self.presetManager = PresetManager(
+ uic.loadUi(
+ os.path.join(Core.wd, 'presetmanager.ui')), self)
+
+ # Create the preview window and its thread, queues, and timers
+ log.debug('Creating preview window')
+ self.previewWindow = PreviewWindow(self, os.path.join(
+ Core.wd, "background.png"))
+ window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
+
+ log.debug('Starting preview thread')
+ self.previewQueue = Queue()
+ self.previewThread = QtCore.QThread(self)
+ self.previewWorker = preview_thread.Worker(self, self.previewQueue)
+ self.previewWorker.error.connect(self.previewWindow.threadError)
+ self.previewWorker.moveToThread(self.previewThread)
+ self.previewWorker.imageCreated.connect(self.showPreviewImage)
+ self.previewThread.start()
+
+ log.debug('Starting preview timer')
+ self.timer = QtCore.QTimer(self)
+ self.timer.timeout.connect(self.processTask.emit)
+ self.timer.start(500)
+
+ # Begin decorating the window and connecting events
+ self.window.installEventFilter(self)
+ componentList = self.window.listWidget_componentList
+
+ if sys.platform == 'darwin':
+ log.debug(
+ 'Darwin detected: showing progress label below progress bar')
+ window.progressBar_createVideo.setTextVisible(False)
+ else:
+ window.progressLabel.setHidden(True)
+
+ window.toolButton_selectAudioFile.clicked.connect(
+ self.openInputFileDialog)
+
+ window.toolButton_selectOutputFile.clicked.connect(
+ self.openOutputFileDialog)
+
+ def changedField():
+ self.autosave()
+ self.updateWindowTitle()
+
+ window.lineEdit_audioFile.textChanged.connect(changedField)
+ window.lineEdit_outputFile.textChanged.connect(changedField)
+
+ window.progressBar_createVideo.setValue(0)
+
+ window.pushButton_createVideo.clicked.connect(
+ self.createAudioVisualisation)
+
+ window.pushButton_Cancel.clicked.connect(self.stopVideo)
+
+ for i, container in enumerate(Core.encoderOptions['containers']):
+ window.comboBox_videoContainer.addItem(container['name'])
+ if container['name'] == self.settings.value('outputContainer'):
+ selectedContainer = i
+
+ window.comboBox_videoContainer.setCurrentIndex(selectedContainer)
+ window.comboBox_videoContainer.currentIndexChanged.connect(
+ self.updateCodecs
+ )
+
+ self.updateCodecs()
+
+ for i in range(window.comboBox_videoCodec.count()):
+ codec = window.comboBox_videoCodec.itemText(i)
+ if codec == self.settings.value('outputVideoCodec'):
+ window.comboBox_videoCodec.setCurrentIndex(i)
+
+ for i in range(window.comboBox_audioCodec.count()):
+ codec = window.comboBox_audioCodec.itemText(i)
+ if codec == self.settings.value('outputAudioCodec'):
+ window.comboBox_audioCodec.setCurrentIndex(i)
+
+ window.comboBox_videoCodec.currentIndexChanged.connect(
+ self.updateCodecSettings
+ )
+
+ window.comboBox_audioCodec.currentIndexChanged.connect(
+ self.updateCodecSettings
+ )
+
+ vBitrate = int(self.settings.value('outputVideoBitrate'))
+ aBitrate = int(self.settings.value('outputAudioBitrate'))
+
+ window.spinBox_vBitrate.setValue(vBitrate)
+ window.spinBox_aBitrate.setValue(aBitrate)
+ window.spinBox_vBitrate.valueChanged.connect(self.updateCodecSettings)
+ window.spinBox_aBitrate.valueChanged.connect(self.updateCodecSettings)
+
+ # Make component buttons
+ self.compMenu = QMenu()
+ for i, comp in enumerate(self.core.modules):
+ action = self.compMenu.addAction(comp.Component.name)
+ action.triggered.connect(
+ lambda _, item=i: self.core.insertComponent(0, item, self)
+ )
+
+ self.window.pushButton_addComponent.setMenu(self.compMenu)
+
+ componentList.dropEvent = self.dragComponent
+ componentList.itemSelectionChanged.connect(
+ self.changeComponentWidget
+ )
+ componentList.itemSelectionChanged.connect(
+ self.presetManager.clearPresetListSelection
+ )
+ self.window.pushButton_removeComponent.clicked.connect(
+ lambda: self.removeComponent()
+ )
+
+ componentList.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+ componentList.customContextMenuRequested.connect(
+ self.componentContextMenu
+ )
+
+ currentRes = str(self.settings.value('outputWidth'))+'x' + \
+ str(self.settings.value('outputHeight'))
+ for i, res in enumerate(Core.resolutions):
+ window.comboBox_resolution.addItem(res)
+ if res == currentRes:
+ currentRes = i
+ window.comboBox_resolution.setCurrentIndex(currentRes)
+ window.comboBox_resolution.currentIndexChanged.connect(
+ self.updateResolution
+ )
+
+ self.window.pushButton_listMoveUp.clicked.connect(
+ lambda: self.moveComponent(-1)
+ )
+ self.window.pushButton_listMoveDown.clicked.connect(
+ lambda: self.moveComponent(1)
+ )
+
+ # Configure the Projects Menu
+ self.projectMenu = QMenu()
+ self.window.menuButton_newProject = self.projectMenu.addAction(
+ "New Project"
+ )
+ self.window.menuButton_newProject.triggered.connect(
+ lambda: self.createNewProject()
+ )
+ self.window.menuButton_openProject = self.projectMenu.addAction(
+ "Open Project"
+ )
+ self.window.menuButton_openProject.triggered.connect(
+ lambda: self.openOpenProjectDialog()
+ )
+
+ action = self.projectMenu.addAction("Save Project")
+ action.triggered.connect(self.saveCurrentProject)
+
+ action = self.projectMenu.addAction("Save Project As")
+ action.triggered.connect(self.openSaveProjectDialog)
+
+ self.window.pushButton_projects.setMenu(self.projectMenu)
+
+ # Configure the Presets Button
+ self.window.pushButton_presets.clicked.connect(
+ self.openPresetManager
+ )
+
+ self.updateWindowTitle()
+ log.debug('Showing main window')
+ window.show()
+
+ if project and project != self.autosavePath:
+ if not project.endswith('.avp'):
+ project += '.avp'
+ # open a project from the commandline
+ if not os.path.dirname(project):
+ project = os.path.join(
+ self.settings.value("projectDir"), project
+ )
+ self.currentProject = project
+ self.settings.setValue("currentProject", project)
+ if os.path.exists(self.autosavePath):
+ os.remove(self.autosavePath)
+ else:
+ # open the last currentProject from settings
+ self.currentProject = self.settings.value("currentProject")
+
+ # delete autosave if it's identical to this project
+ if self.autosaveExists(identical=True):
+ os.remove(self.autosavePath)
+
+ if self.currentProject and os.path.exists(self.autosavePath):
+ ch = self.showMessage(
+ msg="Restore unsaved changes in project '%s'?"
+ % os.path.basename(self.currentProject)[:-4],
+ showCancel=True)
+ if ch:
+ self.saveProjectChanges()
+ else:
+ os.remove(self.autosavePath)
+
+ self.openProject(self.currentProject, prompt=False)
+ self.drawPreview(True)
+
+ # verify Pillow version
+ if not self.settings.value("pilMsgShown") \
+ and 'post' not in Image.PILLOW_VERSION:
+ self.showMessage(
+ msg="You are using the standard version of the "
+ "Python imaging library (Pillow %s). Upgrade "
+ "to the Pillow-SIMD fork to enable hardware accelerations "
+ "and export videos faster." % Image.PILLOW_VERSION
+ )
+ self.settings.setValue("pilMsgShown", True)
+
+ # verify Ffmpeg version
+ if not self.settings.value("ffmpegMsgShown"):
+ try:
+ with open(os.devnull, "w") as f:
+ ffmpegVers = checkOutput(
+ ['ffmpeg', '-version'], stderr=f
+ )
+ goodVersion = str(ffmpegVers).split()[2].startswith('3')
+ except Exception:
+ goodVersion = False
+ else:
+ goodVersion = True
+
+ if not goodVersion:
+ self.showMessage(
+ msg="You're using an old version of Ffmpeg. "
+ "Some features may not work as expected."
+ )
+ self.settings.setValue("ffmpegMsgShown", True)
+
+ # Hotkeys for projects
+ QtWidgets.QShortcut("Ctrl+S", self.window, self.saveCurrentProject)
+ QtWidgets.QShortcut("Ctrl+A", self.window, self.openSaveProjectDialog)
+ QtWidgets.QShortcut("Ctrl+O", self.window, self.openOpenProjectDialog)
+ QtWidgets.QShortcut("Ctrl+N", self.window, self.createNewProject)
+
+ # Hotkeys for component list
+ for inskey in ("Ctrl+T", QtCore.Qt.Key_Insert):
+ QtWidgets.QShortcut(
+ inskey, self.window,
+ activated=lambda: self.window.pushButton_addComponent.click()
+ )
+ for delkey in ("Ctrl+R", QtCore.Qt.Key_Delete):
+ QtWidgets.QShortcut(
+ delkey, self.window.listWidget_componentList,
+ self.removeComponent
+ )
+ QtWidgets.QShortcut(
+ "Ctrl+Space", self.window,
+ activated=lambda: self.window.listWidget_componentList.setFocus()
+ )
+ QtWidgets.QShortcut(
+ "Ctrl+Shift+S", self.window,
+ self.presetManager.openSavePresetDialog
+ )
+ QtWidgets.QShortcut(
+ "Ctrl+Shift+C", self.window, self.presetManager.clearPreset
+ )
+
+ QtWidgets.QShortcut(
+ "Ctrl+Up", self.window.listWidget_componentList,
+ activated=lambda: self.moveComponent(-1)
+ )
+ QtWidgets.QShortcut(
+ "Ctrl+Down", self.window.listWidget_componentList,
+ activated=lambda: self.moveComponent(1)
+ )
+ QtWidgets.QShortcut(
+ "Ctrl+Home", self.window.listWidget_componentList,
+ activated=lambda: self.moveComponent('top')
+ )
+ QtWidgets.QShortcut(
+ "Ctrl+End", self.window.listWidget_componentList,
+ activated=lambda: self.moveComponent('bottom')
+ )
+
+ # Debug Hotkeys
+ QtWidgets.QShortcut(
+ "Ctrl+Alt+Shift+R", self.window, self.drawPreview
+ )
+ QtWidgets.QShortcut(
+ "Ctrl+Alt+Shift+F", self.window, self.showFfmpegCommand
+ )
+
+ @QtCore.pyqtSlot()
+ def cleanUp(self, *args):
+ log.info('Ending the preview thread')
+ self.timer.stop()
+ self.previewThread.quit()
+ self.previewThread.wait()
+
+ @disableWhenOpeningProject
+ def updateWindowTitle(self):
+ appName = 'Audio Visualizer'
+ try:
+ if self.currentProject:
+ appName += ' - %s' % \
+ os.path.splitext(
+ os.path.basename(self.currentProject))[0]
+ if self.autosaveExists(identical=False):
+ appName += '*'
+ except AttributeError:
+ pass
+ log.debug('Setting window title to %s' % appName)
+ self.window.setWindowTitle(appName)
+
+ @QtCore.pyqtSlot(int, dict)
+ def updateComponentTitle(self, pos, presetStore=False):
+ if type(presetStore) is dict:
+ name = presetStore['preset']
+ if name is None or name not in self.core.savedPresets:
+ modified = False
+ else:
+ modified = (presetStore != self.core.savedPresets[name])
+ else:
+ modified = bool(presetStore)
+ if pos < 0:
+ pos = len(self.core.selectedComponents)-1
+ name = str(self.core.selectedComponents[pos])
+ title = str(name)
+ if self.core.selectedComponents[pos].currentPreset:
+ title += ' - %s' % self.core.selectedComponents[pos].currentPreset
+ if modified:
+ title += '*'
+ if type(presetStore) is bool:
+ log.debug('Forcing %s #%s\'s modified status to %s: %s' % (
+ name, pos, modified, title
+ ))
+ else:
+ log.debug('Setting %s #%s\'s title: %s' % (
+ name, pos, title
+ ))
+ self.window.listWidget_componentList.item(pos).setText(title)
+
+ def updateCodecs(self):
+ containerWidget = self.window.comboBox_videoContainer
+ vCodecWidget = self.window.comboBox_videoCodec
+ aCodecWidget = self.window.comboBox_audioCodec
+ index = containerWidget.currentIndex()
+ name = containerWidget.itemText(index)
+ self.settings.setValue('outputContainer', name)
+
+ vCodecWidget.clear()
+ aCodecWidget.clear()
+
+ for container in Core.encoderOptions['containers']:
+ if container['name'] == name:
+ for vCodec in container['video-codecs']:
+ vCodecWidget.addItem(vCodec)
+ for aCodec in container['audio-codecs']:
+ aCodecWidget.addItem(aCodec)
+
+ def updateCodecSettings(self):
+ '''Updates settings.ini to match encoder option widgets'''
+ vCodecWidget = self.window.comboBox_videoCodec
+ vBitrateWidget = self.window.spinBox_vBitrate
+ aBitrateWidget = self.window.spinBox_aBitrate
+ aCodecWidget = self.window.comboBox_audioCodec
+ currentVideoCodec = vCodecWidget.currentIndex()
+ currentVideoCodec = vCodecWidget.itemText(currentVideoCodec)
+ currentVideoBitrate = vBitrateWidget.value()
+ currentAudioCodec = aCodecWidget.currentIndex()
+ currentAudioCodec = aCodecWidget.itemText(currentAudioCodec)
+ currentAudioBitrate = aBitrateWidget.value()
+ self.settings.setValue('outputVideoCodec', currentVideoCodec)
+ self.settings.setValue('outputAudioCodec', currentAudioCodec)
+ self.settings.setValue('outputVideoBitrate', currentVideoBitrate)
+ self.settings.setValue('outputAudioBitrate', currentAudioBitrate)
+
+ @disableWhenOpeningProject
+ def autosave(self, force=False):
+ if not self.currentProject:
+ if os.path.exists(self.autosavePath):
+ os.remove(self.autosavePath)
+ elif force or time.time() - self.lastAutosave >= self.autosaveCooldown:
+ self.core.createProjectFile(self.autosavePath, self.window)
+ self.lastAutosave = time.time()
+ if len(self.autosaveTimes) >= 5:
+ # Do some math to reduce autosave spam. This gives a smooth
+ # curve up to 5 seconds cooldown and maintains that for 30 secs
+ # if a component is continuously updated
+ timeDiff = self.lastAutosave - self.autosaveTimes.pop()
+ if not force and timeDiff >= 1.0 \
+ and timeDiff <= 10.0:
+ if self.autosaveCooldown / 4.0 < 0.5:
+ self.autosaveCooldown += 1.0
+ self.autosaveCooldown = (
+ 5.0 * (self.autosaveCooldown / 5.0)
+ ) + (self.autosaveCooldown / 5.0) * 2
+ elif force or timeDiff >= self.autosaveCooldown * 5:
+ self.autosaveCooldown = 0.2
+ self.autosaveTimes.insert(0, self.lastAutosave)
+ else:
+ log.debug('Autosave rejected by cooldown')
+
+ def autosaveExists(self, identical=True):
+ '''Determines if creating the autosave should be blocked.'''
+ try:
+ if self.currentProject and os.path.exists(self.autosavePath) \
+ and filecmp.cmp(
+ self.autosavePath, self.currentProject) == identical:
+ log.debug(
+ 'Autosave found %s to be identical'
+ % 'not' if not identical else ''
+ )
+ return True
+ except FileNotFoundError:
+ log.error(
+ 'Project file couldn\'t be located:', self.currentProject)
+ return identical
+ return False
+
+ def saveProjectChanges(self):
+ '''Overwrites project file with autosave file'''
+ try:
+ os.remove(self.currentProject)
+ os.rename(self.autosavePath, self.currentProject)
+ return True
+ except (FileNotFoundError, IsADirectoryError) as e:
+ self.showMessage(
+ msg='Project file couldn\'t be saved.',
+ detail=str(e))
+ return False
+
+ def openInputFileDialog(self):
+ inputDir = self.settings.value("inputDir", os.path.expanduser("~"))
+
+ fileName, _ = QtWidgets.QFileDialog.getOpenFileName(
+ self.window, "Open Audio File",
+ inputDir, "Audio Files (%s)" % " ".join(Core.audioFormats))
+
+ if fileName:
+ self.settings.setValue("inputDir", os.path.dirname(fileName))
+ self.window.lineEdit_audioFile.setText(fileName)
+
+ def openOutputFileDialog(self):
+ outputDir = self.settings.value("outputDir", os.path.expanduser("~"))
+
+ fileName, _ = QtWidgets.QFileDialog.getSaveFileName(
+ self.window, "Set Output Video File",
+ outputDir,
+ "Video Files (%s);; All Files (*)" % " ".join(
+ Core.videoFormats))
+
+ if fileName:
+ self.settings.setValue("outputDir", os.path.dirname(fileName))
+ self.window.lineEdit_outputFile.setText(fileName)
+
+ def stopVideo(self):
+ log.info('Export cancelled')
+ self.videoWorker.cancel()
+ self.canceled = True
+
+ def createAudioVisualisation(self):
+ # create output video if mandatory settings are filled in
+ audioFile = self.window.lineEdit_audioFile.text()
+ outputPath = self.window.lineEdit_outputFile.text()
+
+ if audioFile and outputPath and self.core.selectedComponents:
+ if not os.path.dirname(outputPath):
+ outputPath = os.path.join(
+ os.path.expanduser("~"), outputPath)
+ if outputPath and os.path.isdir(outputPath):
+ self.showMessage(
+ msg='Chosen filename matches a directory, which '
+ 'cannot be overwritten. Please choose a different '
+ 'filename or move the directory.',
+ icon='Warning',
+ )
+ return
+ else:
+ if not audioFile or not outputPath:
+ self.showMessage(
+ msg="You must select an audio file and output filename."
+ )
+ elif not self.core.selectedComponents:
+ self.showMessage(
+ msg="Not enough components."
+ )
+ return
+
+ self.canceled = False
+ self.progressBarUpdated(-1)
+ self.videoWorker = self.core.newVideoWorker(
+ self, audioFile, outputPath
+ )
+ self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated)
+ self.videoWorker.progressBarSetText.connect(
+ self.progressBarSetText)
+ self.videoWorker.imageCreated.connect(self.showPreviewImage)
+ self.videoWorker.encoding.connect(self.changeEncodingStatus)
+ self.createVideo.emit()
+
+ @QtCore.pyqtSlot(str, str)
+ def videoThreadError(self, msg, detail):
+ try:
+ self.stopVideo()
+ except AttributeError as e:
+ if 'videoWorker' not in str(e):
+ raise
+ self.showMessage(
+ msg=msg,
+ detail=detail,
+ icon='Critical',
+ )
+
+ def changeEncodingStatus(self, status):
+ self.encoding = status
+ if status:
+ self.window.pushButton_createVideo.setEnabled(False)
+ self.window.pushButton_Cancel.setEnabled(True)
+ self.window.comboBox_resolution.setEnabled(False)
+ self.window.stackedWidget.setEnabled(False)
+ self.window.tab_encoderSettings.setEnabled(False)
+ self.window.label_audioFile.setEnabled(False)
+ self.window.toolButton_selectAudioFile.setEnabled(False)
+ self.window.label_outputFile.setEnabled(False)
+ self.window.toolButton_selectOutputFile.setEnabled(False)
+ self.window.lineEdit_audioFile.setEnabled(False)
+ self.window.lineEdit_outputFile.setEnabled(False)
+ self.window.pushButton_addComponent.setEnabled(False)
+ self.window.pushButton_removeComponent.setEnabled(False)
+ self.window.pushButton_listMoveDown.setEnabled(False)
+ self.window.pushButton_listMoveUp.setEnabled(False)
+ self.window.menuButton_newProject.setEnabled(False)
+ self.window.menuButton_openProject.setEnabled(False)
+ if sys.platform == 'darwin':
+ self.window.progressLabel.setHidden(False)
+ else:
+ self.window.listWidget_componentList.setEnabled(False)
+ else:
+ self.window.pushButton_createVideo.setEnabled(True)
+ self.window.pushButton_Cancel.setEnabled(False)
+ self.window.comboBox_resolution.setEnabled(True)
+ self.window.stackedWidget.setEnabled(True)
+ self.window.tab_encoderSettings.setEnabled(True)
+ self.window.label_audioFile.setEnabled(True)
+ self.window.toolButton_selectAudioFile.setEnabled(True)
+ self.window.lineEdit_audioFile.setEnabled(True)
+ self.window.label_outputFile.setEnabled(True)
+ self.window.toolButton_selectOutputFile.setEnabled(True)
+ self.window.lineEdit_outputFile.setEnabled(True)
+ self.window.pushButton_addComponent.setEnabled(True)
+ self.window.pushButton_removeComponent.setEnabled(True)
+ self.window.pushButton_listMoveDown.setEnabled(True)
+ self.window.pushButton_listMoveUp.setEnabled(True)
+ self.window.menuButton_newProject.setEnabled(True)
+ self.window.menuButton_openProject.setEnabled(True)
+ self.window.listWidget_componentList.setEnabled(True)
+ self.window.progressLabel.setHidden(True)
+ self.drawPreview(True)
+
+ @QtCore.pyqtSlot(int)
+ def progressBarUpdated(self, value):
+ self.window.progressBar_createVideo.setValue(value)
+
+ @QtCore.pyqtSlot(str)
+ def progressBarSetText(self, value):
+ if sys.platform == 'darwin':
+ self.window.progressLabel.setText(value)
+ else:
+ self.window.progressBar_createVideo.setFormat(value)
+
+ def updateResolution(self):
+ resIndex = int(self.window.comboBox_resolution.currentIndex())
+ res = Core.resolutions[resIndex].split('x')
+ changed = res[0] != self.settings.value("outputWidth")
+ self.settings.setValue('outputWidth', res[0])
+ self.settings.setValue('outputHeight', res[1])
+ if changed:
+ for i in range(len(self.core.selectedComponents)):
+ self.core.updateComponent(i)
+
+ def drawPreview(self, force=False, **kwargs):
+ '''Use autosave keyword arg to force saving or not saving if needed'''
+ self.newTask.emit(self.core.selectedComponents)
+ # self.processTask.emit()
+ if force or 'autosave' in kwargs:
+ if force or kwargs['autosave']:
+ self.autosave(True)
+ else:
+ self.autosave()
+ self.updateWindowTitle()
+
+ @QtCore.pyqtSlot(QtGui.QImage)
+ def showPreviewImage(self, image):
+ self.previewWindow.changePixmap(image)
+
+ def showFfmpegCommand(self):
+ from textwrap import wrap
+ from toolkit.ffmpeg import createFfmpegCommand
+ command = createFfmpegCommand(
+ self.window.lineEdit_audioFile.text(),
+ self.window.lineEdit_outputFile.text(),
+ self.core.selectedComponents
+ )
+ lines = wrap(" ".join(command), 49)
+ self.showMessage(
+ msg="Current FFmpeg command:\n\n %s" % " ".join(lines)
+ )
+
+ def insertComponent(self, index):
+ componentList = self.window.listWidget_componentList
+ stackedWidget = self.window.stackedWidget
+
+ componentList.insertItem(
+ index,
+ self.core.selectedComponents[index].name)
+ componentList.setCurrentRow(index)
+
+ # connect to signal that adds an asterisk when modified
+ self.core.selectedComponents[index].modified.connect(
+ self.updateComponentTitle)
+
+ self.pages.insert(index, self.core.selectedComponents[index].page)
+ stackedWidget.insertWidget(index, self.pages[index])
+ stackedWidget.setCurrentIndex(index)
+
+ return index
+
+ def removeComponent(self):
+ componentList = self.window.listWidget_componentList
+
+ for selected in componentList.selectedItems():
+ index = componentList.row(selected)
+ self.window.stackedWidget.removeWidget(self.pages[index])
+ componentList.takeItem(index)
+ self.core.removeComponent(index)
+ self.pages.pop(index)
+ self.changeComponentWidget()
+ self.drawPreview()
+
+ @disableWhenEncoding
+ def moveComponent(self, change):
+ '''Moves a component relatively from its current position'''
+ componentList = self.window.listWidget_componentList
+ if change == 'top':
+ change = -componentList.currentRow()
+ elif change == 'bottom':
+ change = len(componentList)-componentList.currentRow()-1
+ stackedWidget = self.window.stackedWidget
+
+ row = componentList.currentRow()
+ newRow = row + change
+ if newRow > -1 and newRow < componentList.count():
+ self.core.moveComponent(row, newRow)
+
+ # update widgets
+ page = self.pages.pop(row)
+ self.pages.insert(newRow, page)
+ item = componentList.takeItem(row)
+ newItem = componentList.insertItem(newRow, item)
+ widget = stackedWidget.removeWidget(page)
+ stackedWidget.insertWidget(newRow, page)
+ componentList.setCurrentRow(newRow)
+ stackedWidget.setCurrentIndex(newRow)
+ self.drawPreview(True)
+
+ def getComponentListMousePos(self, position):
+ '''
+ Given a QPos, returns the component index under the mouse cursor
+ or -1 if no component is there.
+ '''
+ componentList = self.window.listWidget_componentList
+
+ modelIndexes = [
+ componentList.model().index(i)
+ for i in range(componentList.count())
+ ]
+ rects = [
+ componentList.visualRect(modelIndex)
+ for modelIndex in modelIndexes
+ ]
+ mousePos = [rect.contains(position) for rect in rects]
+ if not any(mousePos):
+ # Not clicking a component
+ mousePos = -1
+ else:
+ mousePos = mousePos.index(True)
+ log.debug('Click component list row %s' % mousePos)
+ return mousePos
+
+ @disableWhenEncoding
+ def dragComponent(self, event):
+ '''Used as Qt drop event for the component listwidget'''
+ componentList = self.window.listWidget_componentList
+ mousePos = self.getComponentListMousePos(event.pos())
+ if mousePos > -1:
+ change = (componentList.currentRow() - mousePos) * -1
+ else:
+ change = (componentList.count() - componentList.currentRow() - 1)
+ self.moveComponent(change)
+
+ def changeComponentWidget(self):
+ selected = self.window.listWidget_componentList.selectedItems()
+ if selected:
+ index = self.window.listWidget_componentList.row(selected[0])
+ self.window.stackedWidget.setCurrentIndex(index)
+
+ def openPresetManager(self):
+ '''Preset manager for importing, exporting, renaming, deleting'''
+ self.presetManager.show()
+
+ def clear(self):
+ '''Get a blank slate'''
+ self.core.clearComponents()
+ self.window.listWidget_componentList.clear()
+ for widget in self.pages:
+ self.window.stackedWidget.removeWidget(widget)
+ self.pages = []
+ for field in (
+ self.window.lineEdit_audioFile,
+ self.window.lineEdit_outputFile
+ ):
+ field.blockSignals(True)
+ field.setText('')
+ field.blockSignals(False)
+ self.progressBarUpdated(0)
+ self.progressBarSetText('')
+
+ @disableWhenEncoding
+ def createNewProject(self, prompt=True):
+ if prompt:
+ self.openSaveChangesDialog('starting a new project')
+
+ self.clear()
+ self.currentProject = None
+ self.settings.setValue("currentProject", None)
+ self.drawPreview(True)
+
+ def saveCurrentProject(self):
+ if self.currentProject:
+ self.core.createProjectFile(self.currentProject, self.window)
+ try:
+ os.remove(self.autosavePath)
+ except FileNotFoundError:
+ pass
+ self.updateWindowTitle()
+ else:
+ self.openSaveProjectDialog()
+
+ def openSaveChangesDialog(self, phrase):
+ success = True
+ if self.autosaveExists(identical=False):
+ ch = self.showMessage(
+ msg="You have unsaved changes in project '%s'. "
+ "Save before %s?" % (
+ os.path.basename(self.currentProject)[:-4],
+ phrase
+ ),
+ showCancel=True)
+ if ch:
+ success = self.saveProjectChanges()
+
+ if success and os.path.exists(self.autosavePath):
+ os.remove(self.autosavePath)
+
+ def openSaveProjectDialog(self):
+ filename, _ = QtWidgets.QFileDialog.getSaveFileName(
+ self.window, "Create Project File",
+ self.settings.value("projectDir"),
+ "Project Files (*.avp)")
+ if not filename:
+ return
+ if not filename.endswith(".avp"):
+ filename += '.avp'
+ self.settings.setValue("projectDir", os.path.dirname(filename))
+ self.settings.setValue("currentProject", filename)
+ self.currentProject = filename
+ self.core.createProjectFile(filename, self.window)
+ self.updateWindowTitle()
+
+ @disableWhenEncoding
+ def openOpenProjectDialog(self):
+ filename, _ = QtWidgets.QFileDialog.getOpenFileName(
+ self.window, "Open Project File",
+ self.settings.value("projectDir"),
+ "Project Files (*.avp)")
+ self.openProject(filename)
+
+ def openProject(self, filepath, prompt=True):
+ if not filepath or not os.path.exists(filepath) \
+ or not filepath.endswith('.avp'):
+ return
+
+ self.clear()
+ # ask to save any changes that are about to get deleted
+ if prompt:
+ self.openSaveChangesDialog('opening another project')
+
+ self.currentProject = filepath
+ self.settings.setValue("currentProject", filepath)
+ self.settings.setValue("projectDir", os.path.dirname(filepath))
+ # actually load the project using core method
+ self.core.openProject(self, filepath)
+ self.drawPreview(autosave=False)
+ self.updateWindowTitle()
+
+ def showMessage(self, **kwargs):
+ parent = kwargs['parent'] if 'parent' in kwargs else self.window
+ msg = QtWidgets.QMessageBox(parent)
+ msg.setModal(True)
+ msg.setText(kwargs['msg'])
+ msg.setIcon(
+ eval('QtWidgets.QMessageBox.%s' % kwargs['icon'])
+ if 'icon' in kwargs else QtWidgets.QMessageBox.Information
+ )
+ msg.setDetailedText(kwargs['detail'] if 'detail' in kwargs else None)
+ if 'showCancel'in kwargs and kwargs['showCancel']:
+ msg.setStandardButtons(
+ QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
+ else:
+ msg.setStandardButtons(QtWidgets.QMessageBox.Ok)
+ ch = msg.exec_()
+ if ch == 1024:
+ return True
+ return False
+
+ @disableWhenEncoding
+ def componentContextMenu(self, QPos):
+ '''Appears when right-clicking the component list'''
+ componentList = self.window.listWidget_componentList
+ self.menu = QMenu()
+ parentPosition = componentList.mapToGlobal(QtCore.QPoint(0, 0))
+
+ index = self.getComponentListMousePos(QPos)
+ if index > -1:
+ # Show preset menu if clicking a component
+ self.presetManager.findPresets()
+ menuItem = self.menu.addAction("Save Preset")
+ menuItem.triggered.connect(
+ self.presetManager.openSavePresetDialog
+ )
+
+ # submenu for opening presets
+ try:
+ presets = self.presetManager.presets[
+ str(self.core.selectedComponents[index])
+ ]
+ self.presetSubmenu = QMenu("Open Preset")
+ self.menu.addMenu(self.presetSubmenu)
+
+ for version, presetName in presets:
+ menuItem = self.presetSubmenu.addAction(presetName)
+ menuItem.triggered.connect(
+ lambda _, presetName=presetName:
+ self.presetManager.openPreset(presetName)
+ )
+ except KeyError:
+ pass
+
+ if self.core.selectedComponents[index].currentPreset:
+ menuItem = self.menu.addAction("Clear Preset")
+ menuItem.triggered.connect(
+ self.presetManager.clearPreset
+ )
+ self.menu.addSeparator()
+
+ # "Add Component" submenu
+ self.submenu = QMenu("Add")
+ self.menu.addMenu(self.submenu)
+ insertCompAtTop = self.settings.value("pref_insertCompAtTop")
+ for i, comp in enumerate(self.core.modules):
+ menuItem = self.submenu.addAction(comp.Component.name)
+ menuItem.triggered.connect(
+ lambda _, item=i: self.core.insertComponent(
+ 0 if insertCompAtTop else index, item, self
+ )
+ )
+
+ self.menu.move(parentPosition + QPos)
+ self.menu.show()
+
+ def eventFilter(self, object, event):
+ if event.type() == QtCore.QEvent.WindowActivate \
+ or event.type() == QtCore.QEvent.FocusIn:
+ Core.windowHasFocus = True
+ elif event.type() == QtCore.QEvent.WindowDeactivate \
+ or event.type() == QtCore.QEvent.FocusOut:
+ Core.windowHasFocus = False
+ return False
diff --git a/src/gui/mainwindow.ui b/src/gui/mainwindow.ui
new file mode 100644
index 0000000..b43d375
--- /dev/null
+++ b/src/gui/mainwindow.ui
@@ -0,0 +1,828 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 1008
+ 575
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+ Qt::StrongFocus
+
+
+ MainWindow
+
+
+
+
+ 0
+ 0
+
+
+
+ false
+
+
+
+ 9
+
+
+ 0
+
+
-
+
+
-
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::MinimumExpanding
+
+
+
+ 0
+ 360
+
+
+
+
+ -
+
+
+ QLayout::SetDefaultConstraint
+
+
+ 0
+
+
-
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::MinimumExpanding
+
+
+
+ 420
+ 0
+
+
+
+
+
+
+ -
+
+
+ QLayout::SetMinimumSize
+
+
+ 3
+
+
-
+
+
+ QLayout::SetMinimumSize
+
+
+ 3
+
+
-
+
+
+ QLayout::SetMinimumSize
+
+
-
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 140
+ 20
+
+
+
+
+ -
+
+
+ Projects
+
+
+
+ -
+
+
+ Presets
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Minimum
+
+
+
+ 20
+ 2
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+ true
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Sunken
+
+
+ 1
+
+
+ true
+
+
+ true
+
+
+ false
+
+
+ QAbstractItemView::InternalMove
+
+
+ Qt::MoveAction
+
+
+
+ -
+
+
-
+
+
+ Add
+
+
+
+ -
+
+
+ Remove
+
+
+
+ -
+
+
+ Up
+
+
+
+ -
+
+
+ Down
+
+
+
+
+
+
+
+ -
+
+
+ 4
+
+
+ 2
+
+
+
+
+
+
+
+ -
+
+
+ QLayout::SetFixedSize
+
+
+ 4
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 500
+ 0
+
+
+
+
+ 16777215
+ 180
+
+
+
+ QTabWidget::North
+
+
+ QTabWidget::Rounded
+
+
+ 0
+
+
+
+ Export Video
+
+
+
+ 10
+
+
-
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 85
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 80
+ 0
+
+
+
+ Audio File
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 28
+
+
+
+
+ 16777215
+ 28
+
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+
+ 0
+ 28
+
+
+
+
+ 16777215
+ 28
+
+
+
+ ...
+
+
+
+
+
+ -
+
+
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 85
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+ Output File
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 28
+
+
+
+
+ 16777215
+ 28
+
+
+
+
+ -
+
+
+
+ 0
+ 28
+
+
+
+
+ 16777215
+ 28
+
+
+
+ ...
+
+
+
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ 24
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Minimum
+
+
+
+ 10
+ 20
+
+
+
+
+ -
+
+
+ Create Video
+
+
+
+ -
+
+
+ false
+
+
+ Cancel
+
+
+
+
+
+ -
+
+
+
+
+
+ true
+
+
+ Qt::AlignCenter
+
+
+ -1
+
+
+
+
+
+
+ progressLabel
+
+
+
+ Encoder Settings
+
+
+
+ 10
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 85
+ 0
+
+
+
+ Container
+
+
+
+ -
+
+
+
+ 150
+ 0
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Minimum
+
+
+
+ 5
+ 5
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Resolution
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 85
+ 0
+
+
+
+ Video Codec
+
+
+
+ -
+
+
+
+ 150
+ 0
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 5
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Video Bitrate (Kbps)
+
+
+
+ -
+
+
+ 99999
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 85
+ 0
+
+
+
+ Audio Codec
+
+
+
+ -
+
+
+
+ 150
+ 0
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 10
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Audio Bitrate (Kbps)
+
+
+
+ -
+
+
+ 9999
+
+
+
+
+
+
+
+
+
+ -
+
+
+ QLayout::SetDefaultConstraint
+
+
-
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::MinimumExpanding
+
+
+
+ 500
+ 0
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 180
+
+
+
+
+ 16777215
+ 180
+
+
+
+ -1
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/gui/presetmanager.py b/src/gui/presetmanager.py
new file mode 100644
index 0000000..b1eeb34
--- /dev/null
+++ b/src/gui/presetmanager.py
@@ -0,0 +1,358 @@
+'''
+ Preset manager object handles all interactions with presets, including
+ the context menu accessed from MainWindow.
+'''
+from PyQt5 import QtCore, QtWidgets
+import string
+import os
+
+from toolkit import badName
+from core import Core
+
+
+class PresetManager(QtWidgets.QDialog):
+ def __init__(self, window, parent):
+ super().__init__(parent.window)
+ self.parent = parent
+ self.core = parent.core
+ self.settings = parent.settings
+ self.presetDir = parent.presetDir
+ if not self.settings.value('presetDir'):
+ self.settings.setValue(
+ "presetDir",
+ os.path.join(parent.dataDir, 'projects'))
+
+ self.findPresets()
+
+ # window
+ self.lastFilter = '*'
+ self.presetRows = [] # list of (comp, vers, name) tuples
+ self.window = window
+ self.window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
+
+ # connect button signals
+ self.window.pushButton_delete.clicked.connect(
+ self.openDeletePresetDialog
+ )
+ self.window.pushButton_rename.clicked.connect(
+ self.openRenamePresetDialog
+ )
+ self.window.pushButton_import.clicked.connect(
+ self.openImportDialog
+ )
+ self.window.pushButton_export.clicked.connect(
+ self.openExportDialog
+ )
+ self.window.pushButton_close.clicked.connect(
+ self.window.close
+ )
+
+ # create filter box and preset list
+ self.drawFilterList()
+ self.window.comboBox_filter.currentIndexChanged.connect(
+ lambda: self.drawPresetList(
+ self.window.comboBox_filter.currentText(),
+ self.window.lineEdit_search.text()
+ )
+ )
+
+ # make auto-completion for search bar
+ self.autocomplete = QtCore.QStringListModel()
+ completer = QtWidgets.QCompleter()
+ completer.setModel(self.autocomplete)
+ self.window.lineEdit_search.setCompleter(completer)
+ self.window.lineEdit_search.textChanged.connect(
+ lambda: self.drawPresetList(
+ self.window.comboBox_filter.currentText(),
+ self.window.lineEdit_search.text()
+ )
+ )
+ self.drawPresetList('*')
+
+ def show(self):
+ '''Open a new preset manager window from the mainwindow'''
+ self.findPresets()
+ self.drawFilterList()
+ self.drawPresetList('*')
+ self.window.show()
+
+ def findPresets(self):
+ parseList = []
+ for dirpath, dirnames, filenames in os.walk(self.presetDir):
+ # anything without a subdirectory must be a preset folder
+ if dirnames:
+ continue
+ for preset in filenames:
+ compName = os.path.basename(os.path.dirname(dirpath))
+ if compName not in self.core.compNames:
+ continue
+ compVers = os.path.basename(dirpath)
+ try:
+ parseList.append((compName, int(compVers), preset))
+ except ValueError:
+ continue
+ self.presets = {
+ compName: [
+ (vers, preset)
+ for name, vers, preset in parseList
+ if name == compName
+ ]
+ for compName, _, __ in parseList
+ }
+
+ def drawPresetList(self, compFilter=None, presetFilter=''):
+ self.window.listWidget_presets.clear()
+ if compFilter:
+ self.lastFilter = str(compFilter)
+ else:
+ compFilter = str(self.lastFilter)
+ self.presetRows = []
+ presetNames = []
+ for component, presets in self.presets.items():
+ if compFilter != '*' and component != compFilter:
+ continue
+ for vers, preset in presets:
+ if not presetFilter or presetFilter in preset:
+ self.window.listWidget_presets.addItem(
+ '%s: %s' % (component, preset)
+ )
+ self.presetRows.append((component, vers, preset))
+ if preset not in presetNames:
+ presetNames.append(preset)
+ self.autocomplete.setStringList(presetNames)
+
+ def drawFilterList(self):
+ self.window.comboBox_filter.clear()
+ self.window.comboBox_filter.addItem('*')
+ for component in self.presets:
+ self.window.comboBox_filter.addItem(component)
+
+ def clearPreset(self, compI=None):
+ '''Functions on mainwindow level from the context menu'''
+ compI = self.parent.window.listWidget_componentList.currentRow()
+ self.core.clearPreset(compI)
+ self.parent.updateComponentTitle(compI, False)
+
+ def openSavePresetDialog(self):
+ '''Functions on mainwindow level from the context menu'''
+ window = self.parent.window
+ selectedComponents = self.core.selectedComponents
+ componentList = self.parent.window.listWidget_componentList
+
+ if componentList.currentRow() == -1:
+ return
+ while True:
+ index = componentList.currentRow()
+ currentPreset = selectedComponents[index].currentPreset
+ newName, OK = QtWidgets.QInputDialog.getText(
+ self.parent.window,
+ 'Audio Visualizer',
+ 'New Preset Name:',
+ QtWidgets.QLineEdit.Normal,
+ currentPreset
+ )
+ if OK:
+ if badName(newName):
+ self.warnMessage(self.parent.window)
+ continue
+ if newName:
+ if index != -1:
+ selectedComponents[index].currentPreset = newName
+ saveValueStore = \
+ selectedComponents[index].savePreset()
+ saveValueStore['preset'] = newName
+ componentName = str(selectedComponents[index]).strip()
+ vers = selectedComponents[index].version
+ self.createNewPreset(
+ componentName, vers, newName,
+ saveValueStore, window=self.parent.window)
+ self.findPresets()
+ self.drawPresetList()
+ self.openPreset(newName, index)
+ break
+
+ def createNewPreset(
+ self, compName, vers, filename, saveValueStore, **kwargs):
+ path = os.path.join(self.presetDir, compName, str(vers), filename)
+ if self.presetExists(path, **kwargs):
+ return
+ self.core.createPresetFile(compName, vers, filename, saveValueStore)
+
+ def presetExists(self, path, **kwargs):
+ if os.path.exists(path):
+ window = self.window \
+ if 'window' not in kwargs else kwargs['window']
+ ch = self.parent.showMessage(
+ msg="%s already exists! Overwrite it?" %
+ os.path.basename(path),
+ showCancel=True,
+ icon='Warning',
+ parent=window)
+ if not ch:
+ # user clicked cancel
+ return True
+
+ return False
+
+ def openPreset(self, presetName, compPos=None):
+ componentList = self.parent.window.listWidget_componentList
+ selectedComponents = self.core.selectedComponents
+
+ index = compPos if compPos is not None else componentList.currentRow()
+ if index == -1:
+ return
+ componentName = str(selectedComponents[index]).strip()
+ version = selectedComponents[index].version
+ dirname = os.path.join(self.presetDir, componentName, str(version))
+ filepath = os.path.join(dirname, presetName)
+ self.core.openPreset(filepath, index, presetName)
+
+ self.parent.updateComponentTitle(index)
+ self.parent.drawPreview()
+
+ def openDeletePresetDialog(self):
+ row = self.getPresetRow()
+ if row == -1:
+ return
+ comp, vers, name = self.presetRows[row]
+ ch = self.parent.showMessage(
+ msg='Really delete %s?' % name,
+ showCancel=True,
+ icon='Warning',
+ parent=self.window
+ )
+ if not ch:
+ return
+ self.deletePreset(comp, vers, name)
+ self.findPresets()
+ self.drawPresetList()
+
+ for i, comp in enumerate(self.core.selectedComponents):
+ if comp.currentPreset == name:
+ self.clearPreset(i)
+
+ def deletePreset(self, comp, vers, name):
+ filepath = os.path.join(self.presetDir, comp, str(vers), name)
+ os.remove(filepath)
+
+ def warnMessage(self, window=None):
+ self.parent.showMessage(
+ msg='Preset names must contain only letters, '
+ 'numbers, and spaces.',
+ parent=window if window else self.window)
+
+ def getPresetRow(self):
+ row = self.window.listWidget_presets.currentRow()
+ if row > -1:
+ return row
+
+ # check if component selected in MainWindow has preset loaded
+ componentList = self.parent.window.listWidget_componentList
+ compIndex = componentList.currentRow()
+ if compIndex == -1:
+ return compIndex
+
+ preset = self.core.selectedComponents[compIndex].currentPreset
+ if preset is None:
+ return -1
+ else:
+ rowTuple = (
+ self.core.selectedComponents[compIndex].name,
+ self.core.selectedComponents[compIndex].version,
+ preset
+ )
+ for i, tup in enumerate(self.presetRows):
+ if rowTuple == tup:
+ index = i
+ break
+ else:
+ return -1
+ return index
+
+ def openRenamePresetDialog(self):
+ # TODO: maintain consistency by changing this to call createNewPreset()
+ presetList = self.window.listWidget_presets
+ index = self.getPresetRow()
+ if index == -1:
+ return
+
+ while True:
+ newName, OK = QtWidgets.QInputDialog.getText(
+ self.window,
+ 'Preset Manager',
+ 'Rename Preset:',
+ QtWidgets.QLineEdit.Normal,
+ self.presetRows[index][2]
+ )
+ if OK:
+ if badName(newName):
+ self.warnMessage()
+ continue
+ if newName:
+ comp, vers, oldName = self.presetRows[index]
+ path = os.path.join(
+ self.presetDir, comp, str(vers))
+ newPath = os.path.join(path, newName)
+ oldPath = os.path.join(path, oldName)
+ if self.presetExists(newPath):
+ return
+ if os.path.exists(newPath):
+ os.remove(newPath)
+ os.rename(oldPath, newPath)
+ self.findPresets()
+ self.drawPresetList()
+ for i, comp in enumerate(self.core.selectedComponents):
+ if getPresetDir(comp) == path \
+ and comp.currentPreset == oldName:
+ self.core.openPreset(newPath, i, newName)
+ self.parent.updateComponentTitle(i, False)
+ self.parent.drawPreview()
+ break
+
+ def openImportDialog(self):
+ filename, _ = QtWidgets.QFileDialog.getOpenFileName(
+ self.window, "Import Preset File",
+ self.settings.value("presetDir"),
+ "Preset Files (*.avl)")
+ if filename:
+ # get installed path & ask user to overwrite if needed
+ path = ''
+ while True:
+ if path:
+ if self.presetExists(path):
+ break
+ else:
+ if os.path.exists(path):
+ os.remove(path)
+ success, path = self.core.importPreset(filename)
+ if success:
+ break
+
+ self.findPresets()
+ self.drawPresetList()
+ self.settings.setValue("presetDir", os.path.dirname(filename))
+
+ def openExportDialog(self):
+ index = self.getPresetRow()
+ if index == -1:
+ return
+ filename, _ = QtWidgets.QFileDialog.getSaveFileName(
+ self.window, "Export Preset",
+ self.settings.value("presetDir"),
+ "Preset Files (*.avl)")
+ if filename:
+ comp, vers, name = self.presetRows[index]
+ if not self.core.exportPreset(filename, comp, vers, name):
+ self.parent.showMessage(
+ msg='Couldn\'t export %s.' % filename,
+ parent=self.window
+ )
+ self.settings.setValue("presetDir", os.path.dirname(filename))
+
+ def clearPresetListSelection(self):
+ self.window.listWidget_presets.setCurrentRow(-1)
+
+
+def getPresetDir(comp):
+ '''Get the preset subdir for a particular version of a component'''
+ return os.path.join(Core.presetDir, str(comp), str(comp.version))
diff --git a/src/gui/presetmanager.ui b/src/gui/presetmanager.ui
new file mode 100644
index 0000000..5257b1c
--- /dev/null
+++ b/src/gui/presetmanager.ui
@@ -0,0 +1,150 @@
+
+
+ presetmanager
+
+
+ Qt::NonModal
+
+
+ true
+
+
+
+ 0
+ 0
+ 497
+ 377
+
+
+
+ Preset Manager
+
+
+ -
+
+
-
+
+
+
+
+
+ Filter by name
+
+
+
+ -
+
+
+
+ 200
+ 0
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ true
+
+
+
+
+
+ -
+
+
+ QLayout::SetMinimumSize
+
+
-
+
+
+ Import
+
+
+
+ -
+
+
+ Export
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ true
+
+
+ Rename
+
+
+
+ -
+
+
+ Delete
+
+
+
+
+
+ -
+
+
-
+
+
+ <html><head/><body><p><span style=" font-size:10pt; font-style:italic;">Right-click components in the main window to create presets</span></p></body></html>
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Close
+
+
+
+
+
+
+
+
+
+
diff --git a/src/gui/preview_thread.py b/src/gui/preview_thread.py
new file mode 100644
index 0000000..9615884
--- /dev/null
+++ b/src/gui/preview_thread.py
@@ -0,0 +1,90 @@
+'''
+ Thread that runs to create QImages for MainWindow's preview label.
+ Processes a queue of component lists.
+'''
+from PyQt5 import QtCore, QtGui, uic
+from PyQt5.QtCore import pyqtSignal, pyqtSlot
+from PIL import Image
+from PIL.ImageQt import ImageQt
+from queue import Queue, Empty
+import os
+import logging
+
+from toolkit.frame import Checkerboard
+from toolkit import disableWhenOpeningProject
+
+
+log = logging.getLogger("AVP.PreviewThread")
+
+
+class Worker(QtCore.QObject):
+
+ imageCreated = pyqtSignal(QtGui.QImage)
+ error = pyqtSignal(str)
+
+ def __init__(self, parent=None, queue=None):
+ QtCore.QObject.__init__(self)
+ parent.newTask.connect(self.createPreviewImage)
+ parent.processTask.connect(self.process)
+ self.parent = parent
+ self.core = parent.core
+ self.settings = parent.settings
+ self.queue = queue
+
+ width = int(self.settings.value('outputWidth'))
+ height = int(self.settings.value('outputHeight'))
+ self.background = Checkerboard(width, height)
+
+ @disableWhenOpeningProject
+ @pyqtSlot(list)
+ def createPreviewImage(self, components):
+ dic = {
+ "components": components,
+ }
+ self.queue.put(dic)
+
+ @pyqtSlot()
+ def process(self):
+ width = int(self.settings.value('outputWidth'))
+ height = int(self.settings.value('outputHeight'))
+ try:
+ nextPreviewInformation = self.queue.get(block=False)
+ while self.queue.qsize() >= 2:
+ try:
+ self.queue.get(block=False)
+ except Empty:
+ continue
+ if self.background.width != width \
+ or self.background.height != height:
+ self.background = Checkerboard(width, height)
+
+ frame = self.background.copy()
+ log.debug('Creating new preview frame')
+ components = nextPreviewInformation["components"]
+ for component in reversed(components):
+ try:
+ component.lockSize(width, height)
+ newFrame = component.previewRender()
+ component.unlockSize()
+ frame = Image.alpha_composite(
+ frame, newFrame
+ )
+
+ except ValueError as e:
+ errMsg = "Bad frame returned by %s's preview renderer. " \
+ "%s. New frame size was %s*%s; should be %s*%s." % (
+ str(component), str(e).capitalize(),
+ newFrame.width, newFrame.height,
+ width, height
+ )
+ log.critical(errMsg)
+ self.error.emit(errMsg)
+ break
+ except RuntimeError as e:
+ log.error(str(e))
+ else:
+ self.frame = ImageQt(frame)
+ self.imageCreated.emit(QtGui.QImage(self.frame))
+
+ except Empty:
+ True
diff --git a/src/gui/preview_win.py b/src/gui/preview_win.py
new file mode 100644
index 0000000..40c19c6
--- /dev/null
+++ b/src/gui/preview_win.py
@@ -0,0 +1,62 @@
+from PyQt5 import QtCore, QtGui, QtWidgets
+import logging
+
+
+class PreviewWindow(QtWidgets.QLabel):
+ '''
+ Paints the preview QLabel in MainWindow and maintains the aspect ratio
+ when the window is resized.
+ '''
+ log = logging.getLogger('AVP.PreviewWindow')
+
+ def __init__(self, parent, img):
+ super(PreviewWindow, self).__init__()
+ self.parent = parent
+ self.setFrameStyle(QtWidgets.QFrame.StyledPanel)
+ self.pixmap = QtGui.QPixmap(img)
+
+ def paintEvent(self, event):
+ size = self.size()
+ painter = QtGui.QPainter(self)
+ point = QtCore.QPoint(0, 0)
+ scaledPix = self.pixmap.scaled(
+ size,
+ QtCore.Qt.KeepAspectRatio,
+ transformMode=QtCore.Qt.SmoothTransformation)
+
+ # start painting the label from left upper corner
+ point.setX((size.width() - scaledPix.width())/2)
+ point.setY((size.height() - scaledPix.height())/2)
+ painter.drawPixmap(point, scaledPix)
+
+ def changePixmap(self, img):
+ self.pixmap = QtGui.QPixmap(img)
+ self.repaint()
+
+ def mousePressEvent(self, event):
+ if self.parent.encoding:
+ return
+
+ i = self.parent.window.listWidget_componentList.currentRow()
+ if i >= 0:
+ component = self.parent.core.selectedComponents[i]
+ if not hasattr(component, 'previewClickEvent'):
+ self.log.info('Ignored click event')
+ return
+ pos = (event.x(), event.y())
+ size = (self.width(), self.height())
+ butt = event.button()
+ self.log.info('Click event for #%s: %s button %s' % (
+ i, pos, butt))
+ component.previewClickEvent(
+ pos, size, butt
+ )
+ self.parent.core.updateComponent(i)
+
+ @QtCore.pyqtSlot(str)
+ def threadError(self, msg):
+ self.parent.showMessage(
+ msg=msg,
+ icon='Critical',
+ parent=self
+ )
diff --git a/src/mainwindow.py b/src/mainwindow.py
deleted file mode 100644
index af6e190..0000000
--- a/src/mainwindow.py
+++ /dev/null
@@ -1,946 +0,0 @@
-'''
- When using GUI mode, this module's object (the main window) takes
- user input to construct a program state (stored in the Core object).
- This shows a preview of the video being created and allows for saving
- projects and exporting the video at a later time.
-'''
-from PyQt5 import QtCore, QtGui, uic, QtWidgets
-from PyQt5.QtWidgets import QMenu, QShortcut
-from PIL import Image
-from queue import Queue
-import sys
-import os
-import signal
-import filecmp
-import time
-import logging
-
-from core import Core
-import preview_thread
-from preview_win import PreviewWindow
-from presetmanager import PresetManager
-from toolkit import disableWhenEncoding, disableWhenOpeningProject, checkOutput
-
-
-log = logging.getLogger('AVP.MainWindow')
-
-
-class MainWindow(QtWidgets.QMainWindow):
- '''
- The MainWindow wraps many Core methods in order to update the GUI
- accordingly. E.g., instead of self.core.openProject(), it will use
- self.openProject() and update the window titlebar within the wrapper.
-
- MainWindow manages the autosave feature, although Core has the
- primary functions for opening and creating project files.
- '''
-
- createVideo = QtCore.pyqtSignal()
- newTask = QtCore.pyqtSignal(list) # for the preview window
- processTask = QtCore.pyqtSignal()
-
- def __init__(self, window, project):
- QtWidgets.QMainWindow.__init__(self)
- self.window = window
- self.core = Core()
- log.debug(
- 'Main thread id: {}'.format(int(QtCore.QThread.currentThreadId())))
-
- # widgets of component settings
- self.pages = []
- self.lastAutosave = time.time()
- # list of previous five autosave times, used to reduce update spam
- self.autosaveTimes = []
- self.autosaveCooldown = 0.2
- self.encoding = False
-
- # Find settings created by Core object
- self.dataDir = Core.dataDir
- self.presetDir = Core.presetDir
- self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
- self.settings = Core.settings
-
- self.presetManager = PresetManager(
- uic.loadUi(
- os.path.join(Core.wd, 'presetmanager.ui')), self)
-
- # Create the preview window and its thread, queues, and timers
- log.debug('Creating preview window')
- self.previewWindow = PreviewWindow(self, os.path.join(
- Core.wd, "background.png"))
- window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
-
- log.debug('Starting preview thread')
- self.previewQueue = Queue()
- self.previewThread = QtCore.QThread(self)
- self.previewWorker = preview_thread.Worker(self, self.previewQueue)
- self.previewWorker.error.connect(self.previewWindow.threadError)
- self.previewWorker.moveToThread(self.previewThread)
- self.previewWorker.imageCreated.connect(self.showPreviewImage)
- self.previewThread.start()
-
- log.debug('Starting preview timer')
- self.timer = QtCore.QTimer(self)
- self.timer.timeout.connect(self.processTask.emit)
- self.timer.start(500)
-
- # Begin decorating the window and connecting events
- self.window.installEventFilter(self)
- componentList = self.window.listWidget_componentList
-
- if sys.platform == 'darwin':
- log.debug(
- 'Darwin detected: showing progress label below progress bar')
- window.progressBar_createVideo.setTextVisible(False)
- else:
- window.progressLabel.setHidden(True)
-
- window.toolButton_selectAudioFile.clicked.connect(
- self.openInputFileDialog)
-
- window.toolButton_selectOutputFile.clicked.connect(
- self.openOutputFileDialog)
-
- def changedField():
- self.autosave()
- self.updateWindowTitle()
-
- window.lineEdit_audioFile.textChanged.connect(changedField)
- window.lineEdit_outputFile.textChanged.connect(changedField)
-
- window.progressBar_createVideo.setValue(0)
-
- window.pushButton_createVideo.clicked.connect(
- self.createAudioVisualisation)
-
- window.pushButton_Cancel.clicked.connect(self.stopVideo)
-
- for i, container in enumerate(Core.encoderOptions['containers']):
- window.comboBox_videoContainer.addItem(container['name'])
- if container['name'] == self.settings.value('outputContainer'):
- selectedContainer = i
-
- window.comboBox_videoContainer.setCurrentIndex(selectedContainer)
- window.comboBox_videoContainer.currentIndexChanged.connect(
- self.updateCodecs
- )
-
- self.updateCodecs()
-
- for i in range(window.comboBox_videoCodec.count()):
- codec = window.comboBox_videoCodec.itemText(i)
- if codec == self.settings.value('outputVideoCodec'):
- window.comboBox_videoCodec.setCurrentIndex(i)
-
- for i in range(window.comboBox_audioCodec.count()):
- codec = window.comboBox_audioCodec.itemText(i)
- if codec == self.settings.value('outputAudioCodec'):
- window.comboBox_audioCodec.setCurrentIndex(i)
-
- window.comboBox_videoCodec.currentIndexChanged.connect(
- self.updateCodecSettings
- )
-
- window.comboBox_audioCodec.currentIndexChanged.connect(
- self.updateCodecSettings
- )
-
- vBitrate = int(self.settings.value('outputVideoBitrate'))
- aBitrate = int(self.settings.value('outputAudioBitrate'))
-
- window.spinBox_vBitrate.setValue(vBitrate)
- window.spinBox_aBitrate.setValue(aBitrate)
- window.spinBox_vBitrate.valueChanged.connect(self.updateCodecSettings)
- window.spinBox_aBitrate.valueChanged.connect(self.updateCodecSettings)
-
- # Make component buttons
- self.compMenu = QMenu()
- for i, comp in enumerate(self.core.modules):
- action = self.compMenu.addAction(comp.Component.name)
- action.triggered.connect(
- lambda _, item=i: self.core.insertComponent(0, item, self)
- )
-
- self.window.pushButton_addComponent.setMenu(self.compMenu)
-
- componentList.dropEvent = self.dragComponent
- componentList.itemSelectionChanged.connect(
- self.changeComponentWidget
- )
- componentList.itemSelectionChanged.connect(
- self.presetManager.clearPresetListSelection
- )
- self.window.pushButton_removeComponent.clicked.connect(
- lambda: self.removeComponent()
- )
-
- componentList.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
- componentList.customContextMenuRequested.connect(
- self.componentContextMenu
- )
-
- currentRes = str(self.settings.value('outputWidth'))+'x' + \
- str(self.settings.value('outputHeight'))
- for i, res in enumerate(Core.resolutions):
- window.comboBox_resolution.addItem(res)
- if res == currentRes:
- currentRes = i
- window.comboBox_resolution.setCurrentIndex(currentRes)
- window.comboBox_resolution.currentIndexChanged.connect(
- self.updateResolution
- )
-
- self.window.pushButton_listMoveUp.clicked.connect(
- lambda: self.moveComponent(-1)
- )
- self.window.pushButton_listMoveDown.clicked.connect(
- lambda: self.moveComponent(1)
- )
-
- # Configure the Projects Menu
- self.projectMenu = QMenu()
- self.window.menuButton_newProject = self.projectMenu.addAction(
- "New Project"
- )
- self.window.menuButton_newProject.triggered.connect(
- lambda: self.createNewProject()
- )
- self.window.menuButton_openProject = self.projectMenu.addAction(
- "Open Project"
- )
- self.window.menuButton_openProject.triggered.connect(
- lambda: self.openOpenProjectDialog()
- )
-
- action = self.projectMenu.addAction("Save Project")
- action.triggered.connect(self.saveCurrentProject)
-
- action = self.projectMenu.addAction("Save Project As")
- action.triggered.connect(self.openSaveProjectDialog)
-
- self.window.pushButton_projects.setMenu(self.projectMenu)
-
- # Configure the Presets Button
- self.window.pushButton_presets.clicked.connect(
- self.openPresetManager
- )
-
- self.updateWindowTitle()
- log.debug('Showing main window')
- window.show()
-
- if project and project != self.autosavePath:
- if not project.endswith('.avp'):
- project += '.avp'
- # open a project from the commandline
- if not os.path.dirname(project):
- project = os.path.join(
- self.settings.value("projectDir"), project
- )
- self.currentProject = project
- self.settings.setValue("currentProject", project)
- if os.path.exists(self.autosavePath):
- os.remove(self.autosavePath)
- else:
- # open the last currentProject from settings
- self.currentProject = self.settings.value("currentProject")
-
- # delete autosave if it's identical to this project
- if self.autosaveExists(identical=True):
- os.remove(self.autosavePath)
-
- if self.currentProject and os.path.exists(self.autosavePath):
- ch = self.showMessage(
- msg="Restore unsaved changes in project '%s'?"
- % os.path.basename(self.currentProject)[:-4],
- showCancel=True)
- if ch:
- self.saveProjectChanges()
- else:
- os.remove(self.autosavePath)
-
- self.openProject(self.currentProject, prompt=False)
- self.drawPreview(True)
-
- # verify Pillow version
- if not self.settings.value("pilMsgShown") \
- and 'post' not in Image.PILLOW_VERSION:
- self.showMessage(
- msg="You are using the standard version of the "
- "Python imaging library (Pillow %s). Upgrade "
- "to the Pillow-SIMD fork to enable hardware accelerations "
- "and export videos faster." % Image.PILLOW_VERSION
- )
- self.settings.setValue("pilMsgShown", True)
-
- # verify Ffmpeg version
- if not self.settings.value("ffmpegMsgShown"):
- try:
- with open(os.devnull, "w") as f:
- ffmpegVers = checkOutput(
- ['ffmpeg', '-version'], stderr=f
- )
- goodVersion = str(ffmpegVers).split()[2].startswith('3')
- except Exception:
- goodVersion = False
- else:
- goodVersion = True
-
- if not goodVersion:
- self.showMessage(
- msg="You're using an old version of Ffmpeg. "
- "Some features may not work as expected."
- )
- self.settings.setValue("ffmpegMsgShown", True)
-
- # Hotkeys for projects
- QtWidgets.QShortcut("Ctrl+S", self.window, self.saveCurrentProject)
- QtWidgets.QShortcut("Ctrl+A", self.window, self.openSaveProjectDialog)
- QtWidgets.QShortcut("Ctrl+O", self.window, self.openOpenProjectDialog)
- QtWidgets.QShortcut("Ctrl+N", self.window, self.createNewProject)
-
- # Hotkeys for component list
- for inskey in ("Ctrl+T", QtCore.Qt.Key_Insert):
- QtWidgets.QShortcut(
- inskey, self.window,
- activated=lambda: self.window.pushButton_addComponent.click()
- )
- for delkey in ("Ctrl+R", QtCore.Qt.Key_Delete):
- QtWidgets.QShortcut(
- delkey, self.window.listWidget_componentList,
- self.removeComponent
- )
- QtWidgets.QShortcut(
- "Ctrl+Space", self.window,
- activated=lambda: self.window.listWidget_componentList.setFocus()
- )
- QtWidgets.QShortcut(
- "Ctrl+Shift+S", self.window,
- self.presetManager.openSavePresetDialog
- )
- QtWidgets.QShortcut(
- "Ctrl+Shift+C", self.window, self.presetManager.clearPreset
- )
-
- QtWidgets.QShortcut(
- "Ctrl+Up", self.window.listWidget_componentList,
- activated=lambda: self.moveComponent(-1)
- )
- QtWidgets.QShortcut(
- "Ctrl+Down", self.window.listWidget_componentList,
- activated=lambda: self.moveComponent(1)
- )
- QtWidgets.QShortcut(
- "Ctrl+Home", self.window.listWidget_componentList,
- activated=lambda: self.moveComponent('top')
- )
- QtWidgets.QShortcut(
- "Ctrl+End", self.window.listWidget_componentList,
- activated=lambda: self.moveComponent('bottom')
- )
-
- # Debug Hotkeys
- QtWidgets.QShortcut(
- "Ctrl+Alt+Shift+R", self.window, self.drawPreview
- )
- QtWidgets.QShortcut(
- "Ctrl+Alt+Shift+F", self.window, self.showFfmpegCommand
- )
-
- @QtCore.pyqtSlot()
- def cleanUp(self, *args):
- log.info('Ending the preview thread')
- self.timer.stop()
- self.previewThread.quit()
- self.previewThread.wait()
-
- @disableWhenOpeningProject
- def updateWindowTitle(self):
- appName = 'Audio Visualizer'
- try:
- if self.currentProject:
- appName += ' - %s' % \
- os.path.splitext(
- os.path.basename(self.currentProject))[0]
- if self.autosaveExists(identical=False):
- appName += '*'
- except AttributeError:
- pass
- log.debug('Setting window title to %s' % appName)
- self.window.setWindowTitle(appName)
-
- @QtCore.pyqtSlot(int, dict)
- def updateComponentTitle(self, pos, presetStore=False):
- if type(presetStore) is dict:
- name = presetStore['preset']
- if name is None or name not in self.core.savedPresets:
- modified = False
- else:
- modified = (presetStore != self.core.savedPresets[name])
- else:
- modified = bool(presetStore)
- if pos < 0:
- pos = len(self.core.selectedComponents)-1
- name = str(self.core.selectedComponents[pos])
- title = str(name)
- if self.core.selectedComponents[pos].currentPreset:
- title += ' - %s' % self.core.selectedComponents[pos].currentPreset
- if modified:
- title += '*'
- if type(presetStore) is bool:
- log.debug('Forcing %s #%s\'s modified status to %s: %s' % (
- name, pos, modified, title
- ))
- else:
- log.debug('Setting %s #%s\'s title: %s' % (
- name, pos, title
- ))
- self.window.listWidget_componentList.item(pos).setText(title)
-
- def updateCodecs(self):
- containerWidget = self.window.comboBox_videoContainer
- vCodecWidget = self.window.comboBox_videoCodec
- aCodecWidget = self.window.comboBox_audioCodec
- index = containerWidget.currentIndex()
- name = containerWidget.itemText(index)
- self.settings.setValue('outputContainer', name)
-
- vCodecWidget.clear()
- aCodecWidget.clear()
-
- for container in Core.encoderOptions['containers']:
- if container['name'] == name:
- for vCodec in container['video-codecs']:
- vCodecWidget.addItem(vCodec)
- for aCodec in container['audio-codecs']:
- aCodecWidget.addItem(aCodec)
-
- def updateCodecSettings(self):
- '''Updates settings.ini to match encoder option widgets'''
- vCodecWidget = self.window.comboBox_videoCodec
- vBitrateWidget = self.window.spinBox_vBitrate
- aBitrateWidget = self.window.spinBox_aBitrate
- aCodecWidget = self.window.comboBox_audioCodec
- currentVideoCodec = vCodecWidget.currentIndex()
- currentVideoCodec = vCodecWidget.itemText(currentVideoCodec)
- currentVideoBitrate = vBitrateWidget.value()
- currentAudioCodec = aCodecWidget.currentIndex()
- currentAudioCodec = aCodecWidget.itemText(currentAudioCodec)
- currentAudioBitrate = aBitrateWidget.value()
- self.settings.setValue('outputVideoCodec', currentVideoCodec)
- self.settings.setValue('outputAudioCodec', currentAudioCodec)
- self.settings.setValue('outputVideoBitrate', currentVideoBitrate)
- self.settings.setValue('outputAudioBitrate', currentAudioBitrate)
-
- @disableWhenOpeningProject
- def autosave(self, force=False):
- if not self.currentProject:
- if os.path.exists(self.autosavePath):
- os.remove(self.autosavePath)
- elif force or time.time() - self.lastAutosave >= self.autosaveCooldown:
- self.core.createProjectFile(self.autosavePath, self.window)
- self.lastAutosave = time.time()
- if len(self.autosaveTimes) >= 5:
- # Do some math to reduce autosave spam. This gives a smooth
- # curve up to 5 seconds cooldown and maintains that for 30 secs
- # if a component is continuously updated
- timeDiff = self.lastAutosave - self.autosaveTimes.pop()
- if not force and timeDiff >= 1.0 \
- and timeDiff <= 10.0:
- if self.autosaveCooldown / 4.0 < 0.5:
- self.autosaveCooldown += 1.0
- self.autosaveCooldown = (
- 5.0 * (self.autosaveCooldown / 5.0)
- ) + (self.autosaveCooldown / 5.0) * 2
- elif force or timeDiff >= self.autosaveCooldown * 5:
- self.autosaveCooldown = 0.2
- self.autosaveTimes.insert(0, self.lastAutosave)
- else:
- log.debug('Autosave rejected by cooldown')
-
- def autosaveExists(self, identical=True):
- '''Determines if creating the autosave should be blocked.'''
- try:
- if self.currentProject and os.path.exists(self.autosavePath) \
- and filecmp.cmp(
- self.autosavePath, self.currentProject) == identical:
- log.debug(
- 'Autosave found %s to be identical'
- % 'not' if not identical else ''
- )
- return True
- except FileNotFoundError:
- log.error(
- 'Project file couldn\'t be located:', self.currentProject)
- return identical
- return False
-
- def saveProjectChanges(self):
- '''Overwrites project file with autosave file'''
- try:
- os.remove(self.currentProject)
- os.rename(self.autosavePath, self.currentProject)
- return True
- except (FileNotFoundError, IsADirectoryError) as e:
- self.showMessage(
- msg='Project file couldn\'t be saved.',
- detail=str(e))
- return False
-
- def openInputFileDialog(self):
- inputDir = self.settings.value("inputDir", os.path.expanduser("~"))
-
- fileName, _ = QtWidgets.QFileDialog.getOpenFileName(
- self.window, "Open Audio File",
- inputDir, "Audio Files (%s)" % " ".join(Core.audioFormats))
-
- if fileName:
- self.settings.setValue("inputDir", os.path.dirname(fileName))
- self.window.lineEdit_audioFile.setText(fileName)
-
- def openOutputFileDialog(self):
- outputDir = self.settings.value("outputDir", os.path.expanduser("~"))
-
- fileName, _ = QtWidgets.QFileDialog.getSaveFileName(
- self.window, "Set Output Video File",
- outputDir,
- "Video Files (%s);; All Files (*)" % " ".join(
- Core.videoFormats))
-
- if fileName:
- self.settings.setValue("outputDir", os.path.dirname(fileName))
- self.window.lineEdit_outputFile.setText(fileName)
-
- def stopVideo(self):
- log.info('Export cancelled')
- self.videoWorker.cancel()
- self.canceled = True
-
- def createAudioVisualisation(self):
- # create output video if mandatory settings are filled in
- audioFile = self.window.lineEdit_audioFile.text()
- outputPath = self.window.lineEdit_outputFile.text()
-
- if audioFile and outputPath and self.core.selectedComponents:
- if not os.path.dirname(outputPath):
- outputPath = os.path.join(
- os.path.expanduser("~"), outputPath)
- if outputPath and os.path.isdir(outputPath):
- self.showMessage(
- msg='Chosen filename matches a directory, which '
- 'cannot be overwritten. Please choose a different '
- 'filename or move the directory.',
- icon='Warning',
- )
- return
- else:
- if not audioFile or not outputPath:
- self.showMessage(
- msg="You must select an audio file and output filename."
- )
- elif not self.core.selectedComponents:
- self.showMessage(
- msg="Not enough components."
- )
- return
-
- self.canceled = False
- self.progressBarUpdated(-1)
- self.videoWorker = self.core.newVideoWorker(
- self, audioFile, outputPath
- )
- self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated)
- self.videoWorker.progressBarSetText.connect(
- self.progressBarSetText)
- self.videoWorker.imageCreated.connect(self.showPreviewImage)
- self.videoWorker.encoding.connect(self.changeEncodingStatus)
- self.createVideo.emit()
-
- @QtCore.pyqtSlot(str, str)
- def videoThreadError(self, msg, detail):
- try:
- self.stopVideo()
- except AttributeError as e:
- if 'videoWorker' not in str(e):
- raise
- self.showMessage(
- msg=msg,
- detail=detail,
- icon='Critical',
- )
-
- def changeEncodingStatus(self, status):
- self.encoding = status
- if status:
- self.window.pushButton_createVideo.setEnabled(False)
- self.window.pushButton_Cancel.setEnabled(True)
- self.window.comboBox_resolution.setEnabled(False)
- self.window.stackedWidget.setEnabled(False)
- self.window.tab_encoderSettings.setEnabled(False)
- self.window.label_audioFile.setEnabled(False)
- self.window.toolButton_selectAudioFile.setEnabled(False)
- self.window.label_outputFile.setEnabled(False)
- self.window.toolButton_selectOutputFile.setEnabled(False)
- self.window.lineEdit_audioFile.setEnabled(False)
- self.window.lineEdit_outputFile.setEnabled(False)
- self.window.pushButton_addComponent.setEnabled(False)
- self.window.pushButton_removeComponent.setEnabled(False)
- self.window.pushButton_listMoveDown.setEnabled(False)
- self.window.pushButton_listMoveUp.setEnabled(False)
- self.window.menuButton_newProject.setEnabled(False)
- self.window.menuButton_openProject.setEnabled(False)
- if sys.platform == 'darwin':
- self.window.progressLabel.setHidden(False)
- else:
- self.window.listWidget_componentList.setEnabled(False)
- else:
- self.window.pushButton_createVideo.setEnabled(True)
- self.window.pushButton_Cancel.setEnabled(False)
- self.window.comboBox_resolution.setEnabled(True)
- self.window.stackedWidget.setEnabled(True)
- self.window.tab_encoderSettings.setEnabled(True)
- self.window.label_audioFile.setEnabled(True)
- self.window.toolButton_selectAudioFile.setEnabled(True)
- self.window.lineEdit_audioFile.setEnabled(True)
- self.window.label_outputFile.setEnabled(True)
- self.window.toolButton_selectOutputFile.setEnabled(True)
- self.window.lineEdit_outputFile.setEnabled(True)
- self.window.pushButton_addComponent.setEnabled(True)
- self.window.pushButton_removeComponent.setEnabled(True)
- self.window.pushButton_listMoveDown.setEnabled(True)
- self.window.pushButton_listMoveUp.setEnabled(True)
- self.window.menuButton_newProject.setEnabled(True)
- self.window.menuButton_openProject.setEnabled(True)
- self.window.listWidget_componentList.setEnabled(True)
- self.window.progressLabel.setHidden(True)
- self.drawPreview(True)
-
- @QtCore.pyqtSlot(int)
- def progressBarUpdated(self, value):
- self.window.progressBar_createVideo.setValue(value)
-
- @QtCore.pyqtSlot(str)
- def progressBarSetText(self, value):
- if sys.platform == 'darwin':
- self.window.progressLabel.setText(value)
- else:
- self.window.progressBar_createVideo.setFormat(value)
-
- def updateResolution(self):
- resIndex = int(self.window.comboBox_resolution.currentIndex())
- res = Core.resolutions[resIndex].split('x')
- changed = res[0] != self.settings.value("outputWidth")
- self.settings.setValue('outputWidth', res[0])
- self.settings.setValue('outputHeight', res[1])
- if changed:
- for i in range(len(self.core.selectedComponents)):
- self.core.updateComponent(i)
-
- def drawPreview(self, force=False, **kwargs):
- '''Use autosave keyword arg to force saving or not saving if needed'''
- self.newTask.emit(self.core.selectedComponents)
- # self.processTask.emit()
- if force or 'autosave' in kwargs:
- if force or kwargs['autosave']:
- self.autosave(True)
- else:
- self.autosave()
- self.updateWindowTitle()
-
- @QtCore.pyqtSlot(QtGui.QImage)
- def showPreviewImage(self, image):
- self.previewWindow.changePixmap(image)
-
- def showFfmpegCommand(self):
- from textwrap import wrap
- from toolkit.ffmpeg import createFfmpegCommand
- command = createFfmpegCommand(
- self.window.lineEdit_audioFile.text(),
- self.window.lineEdit_outputFile.text(),
- self.core.selectedComponents
- )
- lines = wrap(" ".join(command), 49)
- self.showMessage(
- msg="Current FFmpeg command:\n\n %s" % " ".join(lines)
- )
-
- def insertComponent(self, index):
- componentList = self.window.listWidget_componentList
- stackedWidget = self.window.stackedWidget
-
- componentList.insertItem(
- index,
- self.core.selectedComponents[index].name)
- componentList.setCurrentRow(index)
-
- # connect to signal that adds an asterisk when modified
- self.core.selectedComponents[index].modified.connect(
- self.updateComponentTitle)
-
- self.pages.insert(index, self.core.selectedComponents[index].page)
- stackedWidget.insertWidget(index, self.pages[index])
- stackedWidget.setCurrentIndex(index)
-
- return index
-
- def removeComponent(self):
- componentList = self.window.listWidget_componentList
-
- for selected in componentList.selectedItems():
- index = componentList.row(selected)
- self.window.stackedWidget.removeWidget(self.pages[index])
- componentList.takeItem(index)
- self.core.removeComponent(index)
- self.pages.pop(index)
- self.changeComponentWidget()
- self.drawPreview()
-
- @disableWhenEncoding
- def moveComponent(self, change):
- '''Moves a component relatively from its current position'''
- componentList = self.window.listWidget_componentList
- if change == 'top':
- change = -componentList.currentRow()
- elif change == 'bottom':
- change = len(componentList)-componentList.currentRow()-1
- stackedWidget = self.window.stackedWidget
-
- row = componentList.currentRow()
- newRow = row + change
- if newRow > -1 and newRow < componentList.count():
- self.core.moveComponent(row, newRow)
-
- # update widgets
- page = self.pages.pop(row)
- self.pages.insert(newRow, page)
- item = componentList.takeItem(row)
- newItem = componentList.insertItem(newRow, item)
- widget = stackedWidget.removeWidget(page)
- stackedWidget.insertWidget(newRow, page)
- componentList.setCurrentRow(newRow)
- stackedWidget.setCurrentIndex(newRow)
- self.drawPreview(True)
-
- def getComponentListMousePos(self, position):
- '''
- Given a QPos, returns the component index under the mouse cursor
- or -1 if no component is there.
- '''
- componentList = self.window.listWidget_componentList
-
- modelIndexes = [
- componentList.model().index(i)
- for i in range(componentList.count())
- ]
- rects = [
- componentList.visualRect(modelIndex)
- for modelIndex in modelIndexes
- ]
- mousePos = [rect.contains(position) for rect in rects]
- if not any(mousePos):
- # Not clicking a component
- mousePos = -1
- else:
- mousePos = mousePos.index(True)
- log.debug('Click component list row %s' % mousePos)
- return mousePos
-
- @disableWhenEncoding
- def dragComponent(self, event):
- '''Used as Qt drop event for the component listwidget'''
- componentList = self.window.listWidget_componentList
- mousePos = self.getComponentListMousePos(event.pos())
- if mousePos > -1:
- change = (componentList.currentRow() - mousePos) * -1
- else:
- change = (componentList.count() - componentList.currentRow() - 1)
- self.moveComponent(change)
-
- def changeComponentWidget(self):
- selected = self.window.listWidget_componentList.selectedItems()
- if selected:
- index = self.window.listWidget_componentList.row(selected[0])
- self.window.stackedWidget.setCurrentIndex(index)
-
- def openPresetManager(self):
- '''Preset manager for importing, exporting, renaming, deleting'''
- self.presetManager.show()
-
- def clear(self):
- '''Get a blank slate'''
- self.core.clearComponents()
- self.window.listWidget_componentList.clear()
- for widget in self.pages:
- self.window.stackedWidget.removeWidget(widget)
- self.pages = []
- for field in (
- self.window.lineEdit_audioFile,
- self.window.lineEdit_outputFile
- ):
- field.blockSignals(True)
- field.setText('')
- field.blockSignals(False)
- self.progressBarUpdated(0)
- self.progressBarSetText('')
-
- @disableWhenEncoding
- def createNewProject(self, prompt=True):
- if prompt:
- self.openSaveChangesDialog('starting a new project')
-
- self.clear()
- self.currentProject = None
- self.settings.setValue("currentProject", None)
- self.drawPreview(True)
-
- def saveCurrentProject(self):
- if self.currentProject:
- self.core.createProjectFile(self.currentProject, self.window)
- try:
- os.remove(self.autosavePath)
- except FileNotFoundError:
- pass
- self.updateWindowTitle()
- else:
- self.openSaveProjectDialog()
-
- def openSaveChangesDialog(self, phrase):
- success = True
- if self.autosaveExists(identical=False):
- ch = self.showMessage(
- msg="You have unsaved changes in project '%s'. "
- "Save before %s?" % (
- os.path.basename(self.currentProject)[:-4],
- phrase
- ),
- showCancel=True)
- if ch:
- success = self.saveProjectChanges()
-
- if success and os.path.exists(self.autosavePath):
- os.remove(self.autosavePath)
-
- def openSaveProjectDialog(self):
- filename, _ = QtWidgets.QFileDialog.getSaveFileName(
- self.window, "Create Project File",
- self.settings.value("projectDir"),
- "Project Files (*.avp)")
- if not filename:
- return
- if not filename.endswith(".avp"):
- filename += '.avp'
- self.settings.setValue("projectDir", os.path.dirname(filename))
- self.settings.setValue("currentProject", filename)
- self.currentProject = filename
- self.core.createProjectFile(filename, self.window)
- self.updateWindowTitle()
-
- @disableWhenEncoding
- def openOpenProjectDialog(self):
- filename, _ = QtWidgets.QFileDialog.getOpenFileName(
- self.window, "Open Project File",
- self.settings.value("projectDir"),
- "Project Files (*.avp)")
- self.openProject(filename)
-
- def openProject(self, filepath, prompt=True):
- if not filepath or not os.path.exists(filepath) \
- or not filepath.endswith('.avp'):
- return
-
- self.clear()
- # ask to save any changes that are about to get deleted
- if prompt:
- self.openSaveChangesDialog('opening another project')
-
- self.currentProject = filepath
- self.settings.setValue("currentProject", filepath)
- self.settings.setValue("projectDir", os.path.dirname(filepath))
- # actually load the project using core method
- self.core.openProject(self, filepath)
- self.drawPreview(autosave=False)
- self.updateWindowTitle()
-
- def showMessage(self, **kwargs):
- parent = kwargs['parent'] if 'parent' in kwargs else self.window
- msg = QtWidgets.QMessageBox(parent)
- msg.setModal(True)
- msg.setText(kwargs['msg'])
- msg.setIcon(
- eval('QtWidgets.QMessageBox.%s' % kwargs['icon'])
- if 'icon' in kwargs else QtWidgets.QMessageBox.Information
- )
- msg.setDetailedText(kwargs['detail'] if 'detail' in kwargs else None)
- if 'showCancel'in kwargs and kwargs['showCancel']:
- msg.setStandardButtons(
- QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
- else:
- msg.setStandardButtons(QtWidgets.QMessageBox.Ok)
- ch = msg.exec_()
- if ch == 1024:
- return True
- return False
-
- @disableWhenEncoding
- def componentContextMenu(self, QPos):
- '''Appears when right-clicking the component list'''
- componentList = self.window.listWidget_componentList
- self.menu = QMenu()
- parentPosition = componentList.mapToGlobal(QtCore.QPoint(0, 0))
-
- index = self.getComponentListMousePos(QPos)
- if index > -1:
- # Show preset menu if clicking a component
- self.presetManager.findPresets()
- menuItem = self.menu.addAction("Save Preset")
- menuItem.triggered.connect(
- self.presetManager.openSavePresetDialog
- )
-
- # submenu for opening presets
- try:
- presets = self.presetManager.presets[
- str(self.core.selectedComponents[index])
- ]
- self.presetSubmenu = QMenu("Open Preset")
- self.menu.addMenu(self.presetSubmenu)
-
- for version, presetName in presets:
- menuItem = self.presetSubmenu.addAction(presetName)
- menuItem.triggered.connect(
- lambda _, presetName=presetName:
- self.presetManager.openPreset(presetName)
- )
- except KeyError:
- pass
-
- if self.core.selectedComponents[index].currentPreset:
- menuItem = self.menu.addAction("Clear Preset")
- menuItem.triggered.connect(
- self.presetManager.clearPreset
- )
- self.menu.addSeparator()
-
- # "Add Component" submenu
- self.submenu = QMenu("Add")
- self.menu.addMenu(self.submenu)
- insertCompAtTop = self.settings.value("pref_insertCompAtTop")
- for i, comp in enumerate(self.core.modules):
- menuItem = self.submenu.addAction(comp.Component.name)
- menuItem.triggered.connect(
- lambda _, item=i: self.core.insertComponent(
- 0 if insertCompAtTop else index, item, self
- )
- )
-
- self.menu.move(parentPosition + QPos)
- self.menu.show()
-
- def eventFilter(self, object, event):
- if event.type() == QtCore.QEvent.WindowActivate \
- or event.type() == QtCore.QEvent.FocusIn:
- Core.windowHasFocus = True
- elif event.type() == QtCore.QEvent.WindowDeactivate \
- or event.type() == QtCore.QEvent.FocusOut:
- Core.windowHasFocus = False
- return False
diff --git a/src/mainwindow.ui b/src/mainwindow.ui
deleted file mode 100644
index b43d375..0000000
--- a/src/mainwindow.ui
+++ /dev/null
@@ -1,828 +0,0 @@
-
-
- MainWindow
-
-
-
- 0
- 0
- 1008
- 575
-
-
-
-
- 0
- 0
-
-
-
-
- 0
- 0
-
-
-
- Qt::StrongFocus
-
-
- MainWindow
-
-
-
-
- 0
- 0
-
-
-
- false
-
-
-
- 9
-
-
- 0
-
- -
-
-
-
-
-
- Qt::Vertical
-
-
- QSizePolicy::MinimumExpanding
-
-
-
- 0
- 360
-
-
-
-
- -
-
-
- QLayout::SetDefaultConstraint
-
-
- 0
-
-
-
-
-
- Qt::Horizontal
-
-
- QSizePolicy::MinimumExpanding
-
-
-
- 420
- 0
-
-
-
-
-
-
- -
-
-
- QLayout::SetMinimumSize
-
-
- 3
-
-
-
-
-
- QLayout::SetMinimumSize
-
-
- 3
-
-
-
-
-
- QLayout::SetMinimumSize
-
-
-
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 140
- 20
-
-
-
-
- -
-
-
- Projects
-
-
-
- -
-
-
- Presets
-
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Minimum
-
-
-
- 20
- 2
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 0
-
-
-
-
- 16777215
- 16777215
-
-
-
- true
-
-
- QFrame::StyledPanel
-
-
- QFrame::Sunken
-
-
- 1
-
-
- true
-
-
- true
-
-
- false
-
-
- QAbstractItemView::InternalMove
-
-
- Qt::MoveAction
-
-
-
- -
-
-
-
-
-
- Add
-
-
-
- -
-
-
- Remove
-
-
-
- -
-
-
- Up
-
-
-
- -
-
-
- Down
-
-
-
-
-
-
-
- -
-
-
- 4
-
-
- 2
-
-
-
-
-
-
-
- -
-
-
- QLayout::SetFixedSize
-
-
- 4
-
-
- 0
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 500
- 0
-
-
-
-
- 16777215
- 180
-
-
-
- QTabWidget::North
-
-
- QTabWidget::Rounded
-
-
- 0
-
-
-
- Export Video
-
-
-
- 10
-
-
-
-
-
- 0
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 85
- 0
-
-
-
-
- 80
- 16777215
-
-
-
-
- 80
- 0
-
-
-
- Audio File
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 28
-
-
-
-
- 16777215
- 28
-
-
-
-
- 0
- 0
-
-
-
-
- -
-
-
-
- 0
- 28
-
-
-
-
- 16777215
- 28
-
-
-
- ...
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 85
- 0
-
-
-
-
- 0
- 0
-
-
-
- Output File
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 28
-
-
-
-
- 16777215
- 28
-
-
-
-
- -
-
-
-
- 0
- 28
-
-
-
-
- 16777215
- 28
-
-
-
- ...
-
-
-
-
-
-
-
- -
-
-
- 0
-
-
-
-
-
-
- 0
- 0
-
-
-
- 24
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Minimum
-
-
-
- 10
- 20
-
-
-
-
- -
-
-
- Create Video
-
-
-
- -
-
-
- false
-
-
- Cancel
-
-
-
-
-
- -
-
-
-
-
-
- true
-
-
- Qt::AlignCenter
-
-
- -1
-
-
-
-
-
-
- progressLabel
-
-
-
- Encoder Settings
-
-
-
- 10
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 85
- 0
-
-
-
- Container
-
-
-
- -
-
-
-
- 150
- 0
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Minimum
-
-
-
- 5
- 5
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Resolution
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 0
-
-
-
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 85
- 0
-
-
-
- Video Codec
-
-
-
- -
-
-
-
- 150
- 0
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 5
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Video Bitrate (Kbps)
-
-
-
- -
-
-
- 99999
-
-
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 85
- 0
-
-
-
- Audio Codec
-
-
-
- -
-
-
-
- 150
- 0
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 10
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Audio Bitrate (Kbps)
-
-
-
- -
-
-
- 9999
-
-
-
-
-
-
-
-
-
- -
-
-
- QLayout::SetDefaultConstraint
-
-
-
-
-
- Qt::Horizontal
-
-
- QSizePolicy::MinimumExpanding
-
-
-
- 500
- 0
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 180
-
-
-
-
- 16777215
- 180
-
-
-
- -1
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/presetmanager.py b/src/presetmanager.py
deleted file mode 100644
index b1eeb34..0000000
--- a/src/presetmanager.py
+++ /dev/null
@@ -1,358 +0,0 @@
-'''
- Preset manager object handles all interactions with presets, including
- the context menu accessed from MainWindow.
-'''
-from PyQt5 import QtCore, QtWidgets
-import string
-import os
-
-from toolkit import badName
-from core import Core
-
-
-class PresetManager(QtWidgets.QDialog):
- def __init__(self, window, parent):
- super().__init__(parent.window)
- self.parent = parent
- self.core = parent.core
- self.settings = parent.settings
- self.presetDir = parent.presetDir
- if not self.settings.value('presetDir'):
- self.settings.setValue(
- "presetDir",
- os.path.join(parent.dataDir, 'projects'))
-
- self.findPresets()
-
- # window
- self.lastFilter = '*'
- self.presetRows = [] # list of (comp, vers, name) tuples
- self.window = window
- self.window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
-
- # connect button signals
- self.window.pushButton_delete.clicked.connect(
- self.openDeletePresetDialog
- )
- self.window.pushButton_rename.clicked.connect(
- self.openRenamePresetDialog
- )
- self.window.pushButton_import.clicked.connect(
- self.openImportDialog
- )
- self.window.pushButton_export.clicked.connect(
- self.openExportDialog
- )
- self.window.pushButton_close.clicked.connect(
- self.window.close
- )
-
- # create filter box and preset list
- self.drawFilterList()
- self.window.comboBox_filter.currentIndexChanged.connect(
- lambda: self.drawPresetList(
- self.window.comboBox_filter.currentText(),
- self.window.lineEdit_search.text()
- )
- )
-
- # make auto-completion for search bar
- self.autocomplete = QtCore.QStringListModel()
- completer = QtWidgets.QCompleter()
- completer.setModel(self.autocomplete)
- self.window.lineEdit_search.setCompleter(completer)
- self.window.lineEdit_search.textChanged.connect(
- lambda: self.drawPresetList(
- self.window.comboBox_filter.currentText(),
- self.window.lineEdit_search.text()
- )
- )
- self.drawPresetList('*')
-
- def show(self):
- '''Open a new preset manager window from the mainwindow'''
- self.findPresets()
- self.drawFilterList()
- self.drawPresetList('*')
- self.window.show()
-
- def findPresets(self):
- parseList = []
- for dirpath, dirnames, filenames in os.walk(self.presetDir):
- # anything without a subdirectory must be a preset folder
- if dirnames:
- continue
- for preset in filenames:
- compName = os.path.basename(os.path.dirname(dirpath))
- if compName not in self.core.compNames:
- continue
- compVers = os.path.basename(dirpath)
- try:
- parseList.append((compName, int(compVers), preset))
- except ValueError:
- continue
- self.presets = {
- compName: [
- (vers, preset)
- for name, vers, preset in parseList
- if name == compName
- ]
- for compName, _, __ in parseList
- }
-
- def drawPresetList(self, compFilter=None, presetFilter=''):
- self.window.listWidget_presets.clear()
- if compFilter:
- self.lastFilter = str(compFilter)
- else:
- compFilter = str(self.lastFilter)
- self.presetRows = []
- presetNames = []
- for component, presets in self.presets.items():
- if compFilter != '*' and component != compFilter:
- continue
- for vers, preset in presets:
- if not presetFilter or presetFilter in preset:
- self.window.listWidget_presets.addItem(
- '%s: %s' % (component, preset)
- )
- self.presetRows.append((component, vers, preset))
- if preset not in presetNames:
- presetNames.append(preset)
- self.autocomplete.setStringList(presetNames)
-
- def drawFilterList(self):
- self.window.comboBox_filter.clear()
- self.window.comboBox_filter.addItem('*')
- for component in self.presets:
- self.window.comboBox_filter.addItem(component)
-
- def clearPreset(self, compI=None):
- '''Functions on mainwindow level from the context menu'''
- compI = self.parent.window.listWidget_componentList.currentRow()
- self.core.clearPreset(compI)
- self.parent.updateComponentTitle(compI, False)
-
- def openSavePresetDialog(self):
- '''Functions on mainwindow level from the context menu'''
- window = self.parent.window
- selectedComponents = self.core.selectedComponents
- componentList = self.parent.window.listWidget_componentList
-
- if componentList.currentRow() == -1:
- return
- while True:
- index = componentList.currentRow()
- currentPreset = selectedComponents[index].currentPreset
- newName, OK = QtWidgets.QInputDialog.getText(
- self.parent.window,
- 'Audio Visualizer',
- 'New Preset Name:',
- QtWidgets.QLineEdit.Normal,
- currentPreset
- )
- if OK:
- if badName(newName):
- self.warnMessage(self.parent.window)
- continue
- if newName:
- if index != -1:
- selectedComponents[index].currentPreset = newName
- saveValueStore = \
- selectedComponents[index].savePreset()
- saveValueStore['preset'] = newName
- componentName = str(selectedComponents[index]).strip()
- vers = selectedComponents[index].version
- self.createNewPreset(
- componentName, vers, newName,
- saveValueStore, window=self.parent.window)
- self.findPresets()
- self.drawPresetList()
- self.openPreset(newName, index)
- break
-
- def createNewPreset(
- self, compName, vers, filename, saveValueStore, **kwargs):
- path = os.path.join(self.presetDir, compName, str(vers), filename)
- if self.presetExists(path, **kwargs):
- return
- self.core.createPresetFile(compName, vers, filename, saveValueStore)
-
- def presetExists(self, path, **kwargs):
- if os.path.exists(path):
- window = self.window \
- if 'window' not in kwargs else kwargs['window']
- ch = self.parent.showMessage(
- msg="%s already exists! Overwrite it?" %
- os.path.basename(path),
- showCancel=True,
- icon='Warning',
- parent=window)
- if not ch:
- # user clicked cancel
- return True
-
- return False
-
- def openPreset(self, presetName, compPos=None):
- componentList = self.parent.window.listWidget_componentList
- selectedComponents = self.core.selectedComponents
-
- index = compPos if compPos is not None else componentList.currentRow()
- if index == -1:
- return
- componentName = str(selectedComponents[index]).strip()
- version = selectedComponents[index].version
- dirname = os.path.join(self.presetDir, componentName, str(version))
- filepath = os.path.join(dirname, presetName)
- self.core.openPreset(filepath, index, presetName)
-
- self.parent.updateComponentTitle(index)
- self.parent.drawPreview()
-
- def openDeletePresetDialog(self):
- row = self.getPresetRow()
- if row == -1:
- return
- comp, vers, name = self.presetRows[row]
- ch = self.parent.showMessage(
- msg='Really delete %s?' % name,
- showCancel=True,
- icon='Warning',
- parent=self.window
- )
- if not ch:
- return
- self.deletePreset(comp, vers, name)
- self.findPresets()
- self.drawPresetList()
-
- for i, comp in enumerate(self.core.selectedComponents):
- if comp.currentPreset == name:
- self.clearPreset(i)
-
- def deletePreset(self, comp, vers, name):
- filepath = os.path.join(self.presetDir, comp, str(vers), name)
- os.remove(filepath)
-
- def warnMessage(self, window=None):
- self.parent.showMessage(
- msg='Preset names must contain only letters, '
- 'numbers, and spaces.',
- parent=window if window else self.window)
-
- def getPresetRow(self):
- row = self.window.listWidget_presets.currentRow()
- if row > -1:
- return row
-
- # check if component selected in MainWindow has preset loaded
- componentList = self.parent.window.listWidget_componentList
- compIndex = componentList.currentRow()
- if compIndex == -1:
- return compIndex
-
- preset = self.core.selectedComponents[compIndex].currentPreset
- if preset is None:
- return -1
- else:
- rowTuple = (
- self.core.selectedComponents[compIndex].name,
- self.core.selectedComponents[compIndex].version,
- preset
- )
- for i, tup in enumerate(self.presetRows):
- if rowTuple == tup:
- index = i
- break
- else:
- return -1
- return index
-
- def openRenamePresetDialog(self):
- # TODO: maintain consistency by changing this to call createNewPreset()
- presetList = self.window.listWidget_presets
- index = self.getPresetRow()
- if index == -1:
- return
-
- while True:
- newName, OK = QtWidgets.QInputDialog.getText(
- self.window,
- 'Preset Manager',
- 'Rename Preset:',
- QtWidgets.QLineEdit.Normal,
- self.presetRows[index][2]
- )
- if OK:
- if badName(newName):
- self.warnMessage()
- continue
- if newName:
- comp, vers, oldName = self.presetRows[index]
- path = os.path.join(
- self.presetDir, comp, str(vers))
- newPath = os.path.join(path, newName)
- oldPath = os.path.join(path, oldName)
- if self.presetExists(newPath):
- return
- if os.path.exists(newPath):
- os.remove(newPath)
- os.rename(oldPath, newPath)
- self.findPresets()
- self.drawPresetList()
- for i, comp in enumerate(self.core.selectedComponents):
- if getPresetDir(comp) == path \
- and comp.currentPreset == oldName:
- self.core.openPreset(newPath, i, newName)
- self.parent.updateComponentTitle(i, False)
- self.parent.drawPreview()
- break
-
- def openImportDialog(self):
- filename, _ = QtWidgets.QFileDialog.getOpenFileName(
- self.window, "Import Preset File",
- self.settings.value("presetDir"),
- "Preset Files (*.avl)")
- if filename:
- # get installed path & ask user to overwrite if needed
- path = ''
- while True:
- if path:
- if self.presetExists(path):
- break
- else:
- if os.path.exists(path):
- os.remove(path)
- success, path = self.core.importPreset(filename)
- if success:
- break
-
- self.findPresets()
- self.drawPresetList()
- self.settings.setValue("presetDir", os.path.dirname(filename))
-
- def openExportDialog(self):
- index = self.getPresetRow()
- if index == -1:
- return
- filename, _ = QtWidgets.QFileDialog.getSaveFileName(
- self.window, "Export Preset",
- self.settings.value("presetDir"),
- "Preset Files (*.avl)")
- if filename:
- comp, vers, name = self.presetRows[index]
- if not self.core.exportPreset(filename, comp, vers, name):
- self.parent.showMessage(
- msg='Couldn\'t export %s.' % filename,
- parent=self.window
- )
- self.settings.setValue("presetDir", os.path.dirname(filename))
-
- def clearPresetListSelection(self):
- self.window.listWidget_presets.setCurrentRow(-1)
-
-
-def getPresetDir(comp):
- '''Get the preset subdir for a particular version of a component'''
- return os.path.join(Core.presetDir, str(comp), str(comp.version))
diff --git a/src/presetmanager.ui b/src/presetmanager.ui
deleted file mode 100644
index 5257b1c..0000000
--- a/src/presetmanager.ui
+++ /dev/null
@@ -1,150 +0,0 @@
-
-
- presetmanager
-
-
- Qt::NonModal
-
-
- true
-
-
-
- 0
- 0
- 497
- 377
-
-
-
- Preset Manager
-
-
- -
-
-
-
-
-
-
-
-
- Filter by name
-
-
-
- -
-
-
-
- 200
- 0
-
-
-
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
- true
-
-
-
-
-
- -
-
-
- QLayout::SetMinimumSize
-
-
-
-
-
- Import
-
-
-
- -
-
-
- Export
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
- true
-
-
- Rename
-
-
-
- -
-
-
- Delete
-
-
-
-
-
- -
-
-
-
-
-
- <html><head/><body><p><span style=" font-size:10pt; font-style:italic;">Right-click components in the main window to create presets</span></p></body></html>
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
- Close
-
-
-
-
-
-
-
-
-
-
diff --git a/src/preview_thread.py b/src/preview_thread.py
deleted file mode 100644
index 9615884..0000000
--- a/src/preview_thread.py
+++ /dev/null
@@ -1,90 +0,0 @@
-'''
- Thread that runs to create QImages for MainWindow's preview label.
- Processes a queue of component lists.
-'''
-from PyQt5 import QtCore, QtGui, uic
-from PyQt5.QtCore import pyqtSignal, pyqtSlot
-from PIL import Image
-from PIL.ImageQt import ImageQt
-from queue import Queue, Empty
-import os
-import logging
-
-from toolkit.frame import Checkerboard
-from toolkit import disableWhenOpeningProject
-
-
-log = logging.getLogger("AVP.PreviewThread")
-
-
-class Worker(QtCore.QObject):
-
- imageCreated = pyqtSignal(QtGui.QImage)
- error = pyqtSignal(str)
-
- def __init__(self, parent=None, queue=None):
- QtCore.QObject.__init__(self)
- parent.newTask.connect(self.createPreviewImage)
- parent.processTask.connect(self.process)
- self.parent = parent
- self.core = parent.core
- self.settings = parent.settings
- self.queue = queue
-
- width = int(self.settings.value('outputWidth'))
- height = int(self.settings.value('outputHeight'))
- self.background = Checkerboard(width, height)
-
- @disableWhenOpeningProject
- @pyqtSlot(list)
- def createPreviewImage(self, components):
- dic = {
- "components": components,
- }
- self.queue.put(dic)
-
- @pyqtSlot()
- def process(self):
- width = int(self.settings.value('outputWidth'))
- height = int(self.settings.value('outputHeight'))
- try:
- nextPreviewInformation = self.queue.get(block=False)
- while self.queue.qsize() >= 2:
- try:
- self.queue.get(block=False)
- except Empty:
- continue
- if self.background.width != width \
- or self.background.height != height:
- self.background = Checkerboard(width, height)
-
- frame = self.background.copy()
- log.debug('Creating new preview frame')
- components = nextPreviewInformation["components"]
- for component in reversed(components):
- try:
- component.lockSize(width, height)
- newFrame = component.previewRender()
- component.unlockSize()
- frame = Image.alpha_composite(
- frame, newFrame
- )
-
- except ValueError as e:
- errMsg = "Bad frame returned by %s's preview renderer. " \
- "%s. New frame size was %s*%s; should be %s*%s." % (
- str(component), str(e).capitalize(),
- newFrame.width, newFrame.height,
- width, height
- )
- log.critical(errMsg)
- self.error.emit(errMsg)
- break
- except RuntimeError as e:
- log.error(str(e))
- else:
- self.frame = ImageQt(frame)
- self.imageCreated.emit(QtGui.QImage(self.frame))
-
- except Empty:
- True
diff --git a/src/preview_win.py b/src/preview_win.py
deleted file mode 100644
index 40c19c6..0000000
--- a/src/preview_win.py
+++ /dev/null
@@ -1,62 +0,0 @@
-from PyQt5 import QtCore, QtGui, QtWidgets
-import logging
-
-
-class PreviewWindow(QtWidgets.QLabel):
- '''
- Paints the preview QLabel in MainWindow and maintains the aspect ratio
- when the window is resized.
- '''
- log = logging.getLogger('AVP.PreviewWindow')
-
- def __init__(self, parent, img):
- super(PreviewWindow, self).__init__()
- self.parent = parent
- self.setFrameStyle(QtWidgets.QFrame.StyledPanel)
- self.pixmap = QtGui.QPixmap(img)
-
- def paintEvent(self, event):
- size = self.size()
- painter = QtGui.QPainter(self)
- point = QtCore.QPoint(0, 0)
- scaledPix = self.pixmap.scaled(
- size,
- QtCore.Qt.KeepAspectRatio,
- transformMode=QtCore.Qt.SmoothTransformation)
-
- # start painting the label from left upper corner
- point.setX((size.width() - scaledPix.width())/2)
- point.setY((size.height() - scaledPix.height())/2)
- painter.drawPixmap(point, scaledPix)
-
- def changePixmap(self, img):
- self.pixmap = QtGui.QPixmap(img)
- self.repaint()
-
- def mousePressEvent(self, event):
- if self.parent.encoding:
- return
-
- i = self.parent.window.listWidget_componentList.currentRow()
- if i >= 0:
- component = self.parent.core.selectedComponents[i]
- if not hasattr(component, 'previewClickEvent'):
- self.log.info('Ignored click event')
- return
- pos = (event.x(), event.y())
- size = (self.width(), self.height())
- butt = event.button()
- self.log.info('Click event for #%s: %s button %s' % (
- i, pos, butt))
- component.previewClickEvent(
- pos, size, butt
- )
- self.parent.core.updateComponent(i)
-
- @QtCore.pyqtSlot(str)
- def threadError(self, msg):
- self.parent.showMessage(
- msg=msg,
- icon='Critical',
- parent=self
- )
--
cgit v1.2.3
From 733c005eeaf5d3ff15e0f60d320f5c03472bad60 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Mon, 14 Aug 2017 18:41:45 -0400
Subject: undoable removeComponent action
---
src/command.py | 1 +
src/component.py | 3 +--
src/core.py | 36 ++++++++++++++++++++++++------------
src/gui/actions.py | 37 +++++++++++++++++++++++++++++++++++++
src/gui/mainwindow.py | 28 +++++++++++++++-------------
src/gui/presetmanager.py | 7 +------
src/main.py | 4 ++--
7 files changed, 81 insertions(+), 35 deletions(-)
create mode 100644 src/gui/actions.py
diff --git a/src/command.py b/src/command.py
index 18f7408..4116c5a 100644
--- a/src/command.py
+++ b/src/command.py
@@ -19,6 +19,7 @@ class Command(QtCore.QObject):
def __init__(self):
QtCore.QObject.__init__(self)
self.core = Core()
+ Core.mode = 'commandline'
self.dataDir = self.core.dataDir
self.canceled = False
diff --git a/src/component.py b/src/component.py
index cf3085c..0e5144c 100644
--- a/src/component.py
+++ b/src/component.py
@@ -59,9 +59,8 @@ class ComponentMetaclass(type(QtCore.QObject)):
'''Intercepts the command() method to check for global args'''
def commandWrapper(self, arg):
if arg.startswith('preset='):
- from presetmanager import getPresetDir
_, preset = arg.split('=', 1)
- path = os.path.join(getPresetDir(self), preset)
+ path = os.path.join(self.core.getPresetDir(self), preset)
if not os.path.exists(path):
print('Couldn\'t locate preset "%s"' % preset)
quit(1)
diff --git a/src/core.py b/src/core.py
index 4dfb210..20b9c1d 100644
--- a/src/core.py
+++ b/src/core.py
@@ -64,31 +64,39 @@ class Core:
for i, component in enumerate(self.selectedComponents):
component.compPos = i
- def insertComponent(self, compPos, moduleIndex, loader):
+ def insertComponent(self, compPos, component, loader):
'''
Creates a new component using these args:
- (compPos, moduleIndex in self.modules, MWindow/Command/Core obj)
+ (compPos, component obj or moduleIndex, MWindow/Command/Core obj)
'''
if compPos < 0 or compPos > len(self.selectedComponents):
compPos = len(self.selectedComponents)
if len(self.selectedComponents) > 50:
return None
- log.debug('Inserting Component from module #%s' % moduleIndex)
- component = self.modules[moduleIndex].Component(
- moduleIndex, compPos, self
+ if type(component) is int:
+ # create component using module index in self.modules
+ moduleIndex = int(component)
+ log.debug('Creating new component from module #%s' % moduleIndex)
+ component = self.modules[moduleIndex].Component(
+ moduleIndex, compPos, self
+ )
+ # init component's widget for loading/saving presets
+ component.widget(loader)
+ else:
+ moduleIndex = -1
+ log.debug(
+ 'Inserting previously-created %s component' % component.name)
+
+ component._error.connect(
+ loader.videoThreadError
)
self.selectedComponents.insert(
compPos,
component
)
self.componentListChanged()
- self.selectedComponents[compPos]._error.connect(
- loader.videoThreadError
- )
-
- # init component's widget for loading/saving presets
- self.selectedComponents[compPos].widget(loader)
- self.updateComponent(compPos)
+ if moduleIndex > -1:
+ self.updateComponent(compPos)
if hasattr(loader, 'insertComponent'):
loader.insertComponent(compPos)
@@ -156,6 +164,10 @@ class Core:
break
return saveValueStore
+ def getPresetDir(self, comp):
+ '''Get the preset subdir for a particular version of a component'''
+ return os.path.join(Core.presetDir, str(comp), str(comp.version))
+
def openProject(self, loader, filepath):
''' loader is the object calling this method which must have
its own showMessage(**kwargs) method for displaying errors.
diff --git a/src/gui/actions.py b/src/gui/actions.py
new file mode 100644
index 0000000..5cf64e1
--- /dev/null
+++ b/src/gui/actions.py
@@ -0,0 +1,37 @@
+'''
+ QCommand classes for every undoable user action performed in the MainWindow
+'''
+from PyQt5.QtWidgets import QUndoCommand
+
+
+class RemoveComponent(QUndoCommand):
+ def __init__(self, parent, selectedRows):
+ super().__init__('Remove component')
+ self.parent = parent
+ componentList = self.parent.window.listWidget_componentList
+ self.selectedRows = [
+ componentList.row(selected) for selected in selectedRows
+ ]
+ self.components = [
+ parent.core.selectedComponents[i] for i in self.selectedRows
+ ]
+
+ def redo(self):
+ stackedWidget = self.parent.window.stackedWidget
+ componentList = self.parent.window.listWidget_componentList
+ for index in self.selectedRows:
+ stackedWidget.removeWidget(self.parent.pages[index])
+ componentList.takeItem(index)
+ self.parent.core.removeComponent(index)
+ self.parent.pages.pop(index)
+ self.parent.changeComponentWidget()
+ self.parent.drawPreview()
+
+ def undo(self):
+ componentList = self.parent.window.listWidget_componentList
+ for index, comp in zip(self.selectedRows, self.components):
+ self.parent.core.insertComponent(
+ index, comp, self.parent
+ )
+ self.parent.drawPreview()
+
diff --git a/src/gui/mainwindow.py b/src/gui/mainwindow.py
index af6e190..2edb750 100644
--- a/src/gui/mainwindow.py
+++ b/src/gui/mainwindow.py
@@ -16,9 +16,10 @@ import time
import logging
from core import Core
-import preview_thread
-from preview_win import PreviewWindow
-from presetmanager import PresetManager
+import gui.preview_thread as preview_thread
+from gui.preview_win import PreviewWindow
+from gui.presetmanager import PresetManager
+from gui.actions import *
from toolkit import disableWhenEncoding, disableWhenOpeningProject, checkOutput
@@ -43,9 +44,12 @@ class MainWindow(QtWidgets.QMainWindow):
QtWidgets.QMainWindow.__init__(self)
self.window = window
self.core = Core()
+ Core.mode = 'GUI'
log.debug(
'Main thread id: {}'.format(int(QtCore.QThread.currentThreadId())))
+ self.undoStack = QtWidgets.QUndoStack(self)
+
# widgets of component settings
self.pages = []
self.lastAutosave = time.time()
@@ -62,7 +66,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.presetManager = PresetManager(
uic.loadUi(
- os.path.join(Core.wd, 'presetmanager.ui')), self)
+ os.path.join(Core.wd, 'gui', 'presetmanager.ui')), self)
# Create the preview window and its thread, queues, and timers
log.debug('Creating preview window')
@@ -298,6 +302,9 @@ class MainWindow(QtWidgets.QMainWindow):
QtWidgets.QShortcut("Ctrl+A", self.window, self.openSaveProjectDialog)
QtWidgets.QShortcut("Ctrl+O", self.window, self.openOpenProjectDialog)
QtWidgets.QShortcut("Ctrl+N", self.window, self.createNewProject)
+ QtWidgets.QShortcut("Ctrl+Z", self.window, self.undoStack.undo)
+ QtWidgets.QShortcut("Ctrl+Y", self.window, self.undoStack.redo)
+ QtWidgets.QShortcut("Ctrl+Shift+Z", self.window, self.undoStack.redo)
# Hotkeys for component list
for inskey in ("Ctrl+T", QtCore.Qt.Key_Insert):
@@ -685,15 +692,10 @@ class MainWindow(QtWidgets.QMainWindow):
def removeComponent(self):
componentList = self.window.listWidget_componentList
-
- for selected in componentList.selectedItems():
- index = componentList.row(selected)
- self.window.stackedWidget.removeWidget(self.pages[index])
- componentList.takeItem(index)
- self.core.removeComponent(index)
- self.pages.pop(index)
- self.changeComponentWidget()
- self.drawPreview()
+ selected = componentList.selectedItems()
+ if selected:
+ action = RemoveComponent(self, selected)
+ self.undoStack.push(action)
@disableWhenEncoding
def moveComponent(self, change):
diff --git a/src/gui/presetmanager.py b/src/gui/presetmanager.py
index b1eeb34..1cc0887 100644
--- a/src/gui/presetmanager.py
+++ b/src/gui/presetmanager.py
@@ -302,7 +302,7 @@ class PresetManager(QtWidgets.QDialog):
self.findPresets()
self.drawPresetList()
for i, comp in enumerate(self.core.selectedComponents):
- if getPresetDir(comp) == path \
+ if self.core.getPresetDir(comp) == path \
and comp.currentPreset == oldName:
self.core.openPreset(newPath, i, newName)
self.parent.updateComponentTitle(i, False)
@@ -351,8 +351,3 @@ class PresetManager(QtWidgets.QDialog):
def clearPresetListSelection(self):
self.window.listWidget_presets.setCurrentRow(-1)
-
-
-def getPresetDir(comp):
- '''Get the preset subdir for a particular version of a component'''
- return os.path.join(Core.presetDir, str(comp), str(comp.version))
diff --git a/src/main.py b/src/main.py
index 3a6fbe7..c1278da 100644
--- a/src/main.py
+++ b/src/main.py
@@ -35,11 +35,11 @@ def main():
log.debug("Finished creating command object")
elif mode == 'GUI':
- from mainwindow import MainWindow
+ from gui.mainwindow import MainWindow
import atexit
import signal
- window = uic.loadUi(os.path.join(wd, "mainwindow.ui"))
+ window = uic.loadUi(os.path.join(wd, "gui", "mainwindow.ui"))
# window.adjustSize()
desc = QtWidgets.QDesktopWidget()
dpi = desc.physicalDpiX()
--
cgit v1.2.3
From a1d7cbb984f2a6c2ea976daa8914a2c9845ee21c Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 15 Aug 2017 22:20:25 -0400
Subject: undoable edits for normal component settings; TODO: merge small edits
---
src/background.png | Bin 45367 -> 0 bytes
src/component.py | 77 +++++++++++++++++++++++++++++++++++++++++-------
src/components/color.py | 3 --
src/components/color.ui | 6 ++++
src/components/text.py | 4 ---
src/components/text.ui | 6 ++++
src/core.py | 20 ++++++++-----
src/gui/background.png | Bin 0 -> 45367 bytes
src/gui/mainwindow.py | 34 +++++++++++++++------
src/toolkit/common.py | 12 ++++++++
src/toolkit/frame.py | 2 +-
11 files changed, 130 insertions(+), 34 deletions(-)
delete mode 100644 src/background.png
create mode 100644 src/gui/background.png
diff --git a/src/background.png b/src/background.png
deleted file mode 100644
index fb58593..0000000
Binary files a/src/background.png and /dev/null differ
diff --git a/src/component.py b/src/component.py
index 0e5144c..dcba082 100644
--- a/src/component.py
+++ b/src/component.py
@@ -12,7 +12,7 @@ import logging
from toolkit.frame import BlankFrame
from toolkit import (
- getWidgetValue, setWidgetValue, connectWidget, rgbFromString
+ getWidgetValue, setWidgetValue, connectWidget, rgbFromString, blockSignals
)
@@ -305,14 +305,46 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
def update(self):
'''
- Reads all tracked widget values into instance attributes
- and tells the MainWindow that the component was modified.
- Call super() at the END if you need to subclass this.
+ A component update triggered by the user changing a widget value
+ Call super() at the END when subclassing this.
'''
- for attr, widget in self._trackedWidgets.items():
+ oldWidgetVals = {
+ attr: getattr(self, attr)
+ for attr in self._trackedWidgets
+ }
+ newWidgetVals = {
+ attr: getWidgetValue(widget)
+ if attr not in self._colorWidgets else rgbFromString(widget.text())
+ for attr, widget in self._trackedWidgets.items()
+ }
+ if any([val != oldWidgetVals[attr]
+ for attr, val in newWidgetVals.items()
+ ]):
+ action = ComponentUpdate(self, oldWidgetVals, newWidgetVals)
+ self.parent.undoStack.push(action)
+
+ def _update(self):
+ '''An internal component update that is not undoable'''
+
+ newWidgetVals = {
+ attr: getWidgetValue(widget)
+ for attr, widget in self._trackedWidgets.items()
+ }
+ self.setAttrs(newWidgetVals)
+ self.sendUpdateSignal()
+
+ def setAttrs(self, attrDict):
+ '''
+ Sets attrs (linked to trackedWidgets) in this preset to
+ the values in the attrDict. Mutates certain widget values if needed
+ '''
+ for attr, val in attrDict.items():
if attr in self._colorWidgets:
# Color Widgets: text stored as tuple & update the button color
- rgbTuple = rgbFromString(widget.text())
+ if type(val) is tuple:
+ rgbTuple = val
+ else:
+ rgbTuple = rgbFromString(val)
btnStyle = (
"QPushButton { background-color : %s; outline: none; }"
% QColor(*rgbTuple).name())
@@ -322,12 +354,11 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
elif attr in self._relativeWidgets:
# Relative widgets: number scales to fit export resolution
self.updateRelativeWidget(attr)
- setattr(self, attr, self._trackedWidgets[attr].value())
+ setattr(self, attr, val)
else:
# Normal tracked widget
- setattr(self, attr, getWidgetValue(widget))
- self.sendUpdateSignal()
+ setattr(self, attr, val)
def sendUpdateSignal(self):
if not self.core.openingProject:
@@ -541,7 +572,6 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
pixelVal = self.pixelValForAttr(attr, floatVal)
self._trackedWidgets[attr].setValue(pixelVal)
-
def updateRelativeWidget(self, attr):
try:
oldUserValue = getattr(self, attr)
@@ -628,3 +658,30 @@ class ComponentError(RuntimeError):
super().__init__(string)
caller.lockError(string)
caller._error.emit(string, detail)
+
+
+class ComponentUpdate(QtWidgets.QUndoCommand):
+ '''Command object for making a component action undoable'''
+ def __init__(self, parent, oldWidgetVals, newWidgetVals):
+ super().__init__(
+ 'Changed %s component #%s' % (
+ parent.name, parent.compPos
+ )
+ )
+ self.parent = parent
+ self.oldWidgetVals = oldWidgetVals
+ self.newWidgetVals = newWidgetVals
+
+ def redo(self):
+ self.parent.setAttrs(self.newWidgetVals)
+ self.parent.sendUpdateSignal()
+
+ def undo(self):
+ self.parent.setAttrs(self.oldWidgetVals)
+ with blockSignals(self.parent):
+ for attr, widget in self.parent._trackedWidgets.items():
+ val = self.oldWidgetVals[attr]
+ if attr in self.parent._colorWidgets:
+ val = '%s,%s,%s' % val
+ setWidgetValue(widget, val)
+ self.parent.sendUpdateSignal()
diff --git a/src/components/color.py b/src/components/color.py
index 5d1233e..d09cee8 100644
--- a/src/components/color.py
+++ b/src/components/color.py
@@ -17,9 +17,6 @@ class Component(Component):
self.y = 0
super().widget(*args)
- 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)
self.page.pushButton_color2.setDisabled(True)
diff --git a/src/components/color.ui b/src/components/color.ui
index a9dacea..1865e60 100644
--- a/src/components/color.ui
+++ b/src/components/color.ui
@@ -73,6 +73,9 @@
0
+
+ 0,0,0
+
12
@@ -146,6 +149,9 @@
0
+
+ 133,133,133
+
12
diff --git a/src/components/text.py b/src/components/text.py
index 4d4f5d3..d3afd5c 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -13,8 +13,6 @@ class Component(Component):
def widget(self, *args):
super().widget(*args)
- self.textColor = (255, 255, 255)
- self.strokeColor = (0, 0, 0)
self.title = 'Text'
self.alignment = 1
self.titleFont = QFont()
@@ -25,8 +23,6 @@ class Component(Component):
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.lineEdit_strokeColor.setText('%s,%s,%s' % self.strokeColor)
self.page.spinBox_fontSize.setValue(int(self.fontSize))
self.page.lineEdit_title.setText(self.title)
diff --git a/src/components/text.ui b/src/components/text.ui
index 13d3467..b62e0ed 100644
--- a/src/components/text.ui
+++ b/src/components/text.ui
@@ -427,6 +427,9 @@
Qt::NoFocus
+
+ 255,255,255
+
-
@@ -485,6 +488,9 @@
Qt::NoFocus
+
+ 0,0,0
+
-
diff --git a/src/core.py b/src/core.py
index 20b9c1d..cee0f56 100644
--- a/src/core.py
+++ b/src/core.py
@@ -94,12 +94,11 @@ class Core:
compPos,
component
)
- self.componentListChanged()
- if moduleIndex > -1:
- self.updateComponent(compPos)
-
if hasattr(loader, 'insertComponent'):
loader.insertComponent(compPos)
+
+ self.componentListChanged()
+ self.updateComponent(compPos)
return compPos
def moveComponent(self, startI, endI):
@@ -119,7 +118,7 @@ class Core:
def updateComponent(self, i):
log.debug('Updating %s #%s' % (self.selectedComponents[i], str(i)))
- self.selectedComponents[i].update()
+ self.selectedComponents[i]._update()
def moduleIndexFor(self, compName):
try:
@@ -540,6 +539,7 @@ class Core:
"projectDir": os.path.join(cls.dataDir, 'projects'),
"pref_insertCompAtTop": True,
"pref_genericPreview": True,
+ "pref_undoLimit": 10,
}
for parm, value in cls.defaultSettings.items():
@@ -552,8 +552,14 @@ class Core:
if not key.startswith('pref_'):
continue
val = cls.settings.value(key)
- if val in ('true', 'false'):
- cls.settings.setValue(key, True if val == 'true' else False)
+ try:
+ val = int(val)
+ except ValueError:
+ if val == 'true':
+ val = True
+ elif val == 'false':
+ val = False
+ cls.settings.setValue(key, val)
@staticmethod
def makeLogger():
diff --git a/src/gui/background.png b/src/gui/background.png
new file mode 100644
index 0000000..fb58593
Binary files /dev/null and b/src/gui/background.png differ
diff --git a/src/gui/mainwindow.py b/src/gui/mainwindow.py
index 2edb750..47111a0 100644
--- a/src/gui/mainwindow.py
+++ b/src/gui/mainwindow.py
@@ -42,13 +42,22 @@ class MainWindow(QtWidgets.QMainWindow):
def __init__(self, window, project):
QtWidgets.QMainWindow.__init__(self)
+ log.debug(
+ 'Main thread id: {}'.format(int(QtCore.QThread.currentThreadId())))
self.window = window
self.core = Core()
Core.mode = 'GUI'
- log.debug(
- 'Main thread id: {}'.format(int(QtCore.QThread.currentThreadId())))
+ # Find settings created by Core object
+ self.dataDir = Core.dataDir
+ self.presetDir = Core.presetDir
+ self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
+ self.settings = Core.settings
+
+ # Create stack of undoable user actions
self.undoStack = QtWidgets.QUndoStack(self)
+ undoLimit = self.settings.value("pref_undoLimit")
+ self.undoStack.setUndoLimit(undoLimit)
# widgets of component settings
self.pages = []
@@ -58,12 +67,6 @@ class MainWindow(QtWidgets.QMainWindow):
self.autosaveCooldown = 0.2
self.encoding = False
- # Find settings created by Core object
- self.dataDir = Core.dataDir
- self.presetDir = Core.presetDir
- self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
- self.settings = Core.settings
-
self.presetManager = PresetManager(
uic.loadUi(
os.path.join(Core.wd, 'gui', 'presetmanager.ui')), self)
@@ -302,6 +305,7 @@ class MainWindow(QtWidgets.QMainWindow):
QtWidgets.QShortcut("Ctrl+A", self.window, self.openSaveProjectDialog)
QtWidgets.QShortcut("Ctrl+O", self.window, self.openOpenProjectDialog)
QtWidgets.QShortcut("Ctrl+N", self.window, self.createNewProject)
+
QtWidgets.QShortcut("Ctrl+Z", self.window, self.undoStack.undo)
QtWidgets.QShortcut("Ctrl+Y", self.window, self.undoStack.redo)
QtWidgets.QShortcut("Ctrl+Shift+Z", self.window, self.undoStack.redo)
@@ -353,6 +357,9 @@ class MainWindow(QtWidgets.QMainWindow):
QtWidgets.QShortcut(
"Ctrl+Alt+Shift+F", self.window, self.showFfmpegCommand
)
+ QtWidgets.QShortcut(
+ "Ctrl+Alt+Shift+U", self.window, self.showUndoStack
+ )
@QtCore.pyqtSlot()
def cleanUp(self, *args):
@@ -658,6 +665,14 @@ class MainWindow(QtWidgets.QMainWindow):
def showPreviewImage(self, image):
self.previewWindow.changePixmap(image)
+ def showUndoStack(self):
+ dialog = QtWidgets.QDialog(self.window)
+ undoView = QtWidgets.QUndoView(self.undoStack)
+ layout = QtWidgets.QVBoxLayout()
+ layout.addWidget(undoView)
+ dialog.setLayout(layout)
+ dialog.show()
+
def showFfmpegCommand(self):
from textwrap import wrap
from toolkit.ffmpeg import createFfmpegCommand
@@ -784,6 +799,7 @@ class MainWindow(QtWidgets.QMainWindow):
field.blockSignals(False)
self.progressBarUpdated(0)
self.progressBarSetText('')
+ self.undoStack.clear()
@disableWhenEncoding
def createNewProject(self, prompt=True):
@@ -847,7 +863,7 @@ class MainWindow(QtWidgets.QMainWindow):
def openProject(self, filepath, prompt=True):
if not filepath or not os.path.exists(filepath) \
- or not filepath.endswith('.avp'):
+ or not filepath.endswith('.avp'):
return
self.clear()
diff --git a/src/toolkit/common.py b/src/toolkit/common.py
index eba57d9..51ad023 100644
--- a/src/toolkit/common.py
+++ b/src/toolkit/common.py
@@ -9,6 +9,18 @@ import subprocess
from collections import OrderedDict
+class blockSignals:
+ '''A context manager to temporarily block a Qt widget from updating'''
+ def __init__(self, widget):
+ self.widget = widget
+
+ def __enter__(self):
+ self.widget.blockSignals(True)
+
+ def __exit__(self, *args):
+ self.widget.blockSignals(False)
+
+
def badName(name):
'''Returns whether a name contains non-alphanumeric chars'''
return any([letter in string.punctuation for letter in name])
diff --git a/src/toolkit/frame.py b/src/toolkit/frame.py
index ad8537c..2104978 100644
--- a/src/toolkit/frame.py
+++ b/src/toolkit/frame.py
@@ -98,7 +98,7 @@ def Checkerboard(width, height):
log.debug('Creating new %s*%s checkerboard' % (width, height))
image = FloodFrame(1920, 1080, (0, 0, 0, 0))
image.paste(Image.open(
- os.path.join(core.Core.wd, "background.png")),
+ os.path.join(core.Core.wd, 'gui', "background.png")),
(0, 0)
)
image = image.resize((width, height))
--
cgit v1.2.3
From f65ced2853a07b312516bcb729cc28509f524077 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Wed, 16 Aug 2017 20:44:37 -0400
Subject: merge consecutive actions on the same widget type
---
src/component.py | 54 ++++++++++++++++++++++++++++++++++++++++++++----------
1 file changed, 44 insertions(+), 10 deletions(-)
diff --git a/src/component.py b/src/component.py
index dcba082..488b92a 100644
--- a/src/component.py
+++ b/src/component.py
@@ -317,10 +317,14 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
if attr not in self._colorWidgets else rgbFromString(widget.text())
for attr, widget in self._trackedWidgets.items()
}
- if any([val != oldWidgetVals[attr]
- for attr, val in newWidgetVals.items()
- ]):
- action = ComponentUpdate(self, oldWidgetVals, newWidgetVals)
+ modifiedWidgets = {
+ attr: val
+ for attr, val in newWidgetVals.items()
+ if val != oldWidgetVals[attr]
+ }
+
+ if modifiedWidgets:
+ action = ComponentUpdate(self, oldWidgetVals, modifiedWidgets)
self.parent.undoStack.push(action)
def _update(self):
@@ -662,25 +666,55 @@ class ComponentError(RuntimeError):
class ComponentUpdate(QtWidgets.QUndoCommand):
'''Command object for making a component action undoable'''
- def __init__(self, parent, oldWidgetVals, newWidgetVals):
+ def __init__(self, parent, oldWidgetVals, modifiedVals):
super().__init__(
'Changed %s component #%s' % (
parent.name, parent.compPos
)
)
self.parent = parent
- self.oldWidgetVals = oldWidgetVals
- self.newWidgetVals = newWidgetVals
+ self.oldWidgetVals = {
+ attr: val
+ for attr, val in oldWidgetVals.items()
+ if attr in modifiedVals
+ }
+ self.modifiedVals = modifiedVals
+
+ # Determine if this update is mergeable
+ self.id_ = -1
+ if len(self.modifiedVals) == 1:
+ attr, val = self.modifiedVals.popitem()
+ widget = self.parent._trackedWidgets[attr]
+ if type(widget) is QtWidgets.QLineEdit:
+ self.id_ = 10
+ elif type(widget) is QtWidgets.QSpinBox \
+ or type(widget) is QtWidgets.QDoubleSpinBox:
+ self.id_ = 20
+ self.modifiedVals[attr] = val
+ else:
+ log.warning(
+ '%s component settings changed at once. (%s)' % (
+ len(self.modifiedVals), repr(self.modifiedVals)
+ )
+ )
+
+ def id(self):
+ '''If 2 consecutive updates have same id, Qt will call mergeWith()'''
+ return self.id_
+
+ def mergeWith(self, other):
+ self.modifiedVals.update(other.modifiedVals)
+ return True
def redo(self):
- self.parent.setAttrs(self.newWidgetVals)
+ self.parent.setAttrs(self.modifiedVals)
self.parent.sendUpdateSignal()
def undo(self):
self.parent.setAttrs(self.oldWidgetVals)
with blockSignals(self.parent):
- for attr, widget in self.parent._trackedWidgets.items():
- val = self.oldWidgetVals[attr]
+ for attr, val in self.oldWidgetVals.items():
+ widget = self.parent._trackedWidgets[attr]
if attr in self.parent._colorWidgets:
val = '%s,%s,%s' % val
setWidgetValue(widget, val)
--
cgit v1.2.3
From ddb04f3a2fe6454a9c98bba39d07a12bd6a91b45 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Wed, 16 Aug 2017 21:02:53 -0400
Subject: undo merge IDs given per attr instead of widget type
---
src/component.py | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/src/component.py b/src/component.py
index 488b92a..b883627 100644
--- a/src/component.py
+++ b/src/component.py
@@ -684,12 +684,7 @@ class ComponentUpdate(QtWidgets.QUndoCommand):
self.id_ = -1
if len(self.modifiedVals) == 1:
attr, val = self.modifiedVals.popitem()
- widget = self.parent._trackedWidgets[attr]
- if type(widget) is QtWidgets.QLineEdit:
- self.id_ = 10
- elif type(widget) is QtWidgets.QSpinBox \
- or type(widget) is QtWidgets.QDoubleSpinBox:
- self.id_ = 20
+ self.id_ = sum([ord(letter) for letter in attr[:14]])
self.modifiedVals[attr] = val
else:
log.warning(
--
cgit v1.2.3
From f66ec40ba6e9c4062d1e41894e0a88f713add96d Mon Sep 17 00:00:00 2001
From: tassaron
Date: Wed, 16 Aug 2017 22:17:12 -0400
Subject: undoable component movement
---
src/core.py | 1 +
src/gui/actions.py | 39 +++++++++++++++++++++++++++++++++++++++
src/gui/mainwindow.py | 18 +++++-------------
3 files changed, 45 insertions(+), 13 deletions(-)
diff --git a/src/core.py b/src/core.py
index cee0f56..14517b0 100644
--- a/src/core.py
+++ b/src/core.py
@@ -73,6 +73,7 @@ class Core:
compPos = len(self.selectedComponents)
if len(self.selectedComponents) > 50:
return None
+
if type(component) is int:
# create component using module index in self.modules
moduleIndex = int(component)
diff --git a/src/gui/actions.py b/src/gui/actions.py
index 5cf64e1..5a0869d 100644
--- a/src/gui/actions.py
+++ b/src/gui/actions.py
@@ -35,3 +35,42 @@ class RemoveComponent(QUndoCommand):
)
self.parent.drawPreview()
+
+class MoveComponent(QUndoCommand):
+ def __init__(self, parent, row, newRow, tag):
+ super().__init__("Move component %s" % tag)
+ self.parent = parent
+ self.row = row
+ self.newRow = newRow
+ self.id_ = ord(tag[0])
+
+ def id(self):
+ '''If 2 consecutive updates have same id, Qt will call mergeWith()'''
+ return self.id_
+
+ def mergeWith(self, other):
+ self.newRow = other.newRow
+ return True
+
+ def do(self, rowa, rowb):
+ componentList = self.parent.window.listWidget_componentList
+
+ page = self.parent.pages.pop(rowa)
+ self.parent.pages.insert(rowb, page)
+
+ item = componentList.takeItem(rowa)
+ componentList.insertItem(rowb, item)
+
+ stackedWidget = self.parent.window.stackedWidget
+ widget = stackedWidget.removeWidget(page)
+ stackedWidget.insertWidget(rowb, page)
+ componentList.setCurrentRow(rowb)
+ stackedWidget.setCurrentIndex(rowb)
+ self.parent.core.moveComponent(rowa, rowb)
+ self.parent.drawPreview(True)
+
+ def redo(self):
+ self.do(self.row, self.newRow)
+
+ def undo(self):
+ self.do(self.newRow, self.row)
diff --git a/src/gui/mainwindow.py b/src/gui/mainwindow.py
index 47111a0..26464a9 100644
--- a/src/gui/mainwindow.py
+++ b/src/gui/mainwindow.py
@@ -716,27 +716,19 @@ class MainWindow(QtWidgets.QMainWindow):
def moveComponent(self, change):
'''Moves a component relatively from its current position'''
componentList = self.window.listWidget_componentList
+ tag = change
if change == 'top':
change = -componentList.currentRow()
elif change == 'bottom':
change = len(componentList)-componentList.currentRow()-1
- stackedWidget = self.window.stackedWidget
+ else:
+ tag = 'down' if change == 1 else 'up'
row = componentList.currentRow()
newRow = row + change
if newRow > -1 and newRow < componentList.count():
- self.core.moveComponent(row, newRow)
-
- # update widgets
- page = self.pages.pop(row)
- self.pages.insert(newRow, page)
- item = componentList.takeItem(row)
- newItem = componentList.insertItem(newRow, item)
- widget = stackedWidget.removeWidget(page)
- stackedWidget.insertWidget(newRow, page)
- componentList.setCurrentRow(newRow)
- stackedWidget.setCurrentIndex(newRow)
- self.drawPreview(True)
+ action = MoveComponent(self, row, newRow, tag)
+ self.undoStack.push(action)
def getComponentListMousePos(self, position):
'''
--
cgit v1.2.3
From c06ca5cdcb603f1855cb0122fc2ab6d2473f3c24 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 17 Aug 2017 10:42:15 -0400
Subject: undoable add-comp & clear-preset actions
---
src/component.py | 35 +++++++++++++++++++++++++++++++----
src/gui/actions.py | 45 ++++++++++++++++++++++++++++++++++++---------
src/gui/mainwindow.py | 31 ++++++++++++++++++++++++-------
src/gui/presetmanager.py | 5 +++--
4 files changed, 94 insertions(+), 22 deletions(-)
diff --git a/src/component.py b/src/component.py
index b883627..f0a8c6b 100644
--- a/src/component.py
+++ b/src/component.py
@@ -99,6 +99,23 @@ class ComponentMetaclass(type(QtCore.QObject)):
return func(self)
return errorWrapper
+ def presetWrapper(func):
+ '''Wraps loadPreset to handle the self.openingPreset boolean'''
+ class openingPreset:
+ def __init__(self, comp):
+ self.comp = comp
+
+ def __enter__(self):
+ self.comp.openingPreset = True
+
+ def __exit__(self, *args):
+ self.comp.openingPreset = False
+
+ def presetWrapper(self, *args):
+ with openingPreset(self):
+ return func(self, *args)
+ return presetWrapper
+
def __new__(cls, name, parents, attrs):
if 'ui' not in attrs:
# Use module name as ui filename by default
@@ -111,7 +128,7 @@ class ComponentMetaclass(type(QtCore.QObject)):
'names', # Class methods
'error', 'audio', 'properties', # Properties
'preFrameRender', 'previewRender',
- 'frameRender', 'command',
+ 'frameRender', 'command', 'loadPreset'
)
# Auto-decorate methods
@@ -140,6 +157,9 @@ class ComponentMetaclass(type(QtCore.QObject)):
if key == 'error':
attrs[key] = cls.errorWrapper(attrs[key])
+ if key == 'loadPreset':
+ attrs[key] = cls.presetWrapper(attrs[key])
+
# Turn version string into a number
try:
if 'version' not in attrs:
@@ -180,6 +200,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
self.compPos = compPos
self.core = core
self.currentPreset = None
+ self.openingPreset = False
self._trackedWidgets = {}
self._presetNames = {}
@@ -207,7 +228,10 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
preset = self.savePreset()
except Exception as e:
preset = '%s occurred while saving preset' % str(e)
- return '%s\n%s\n%s' % (
+
+ return 'Component(%s, %s, Core)\n' \
+ 'Name: %s v%s\n Preset: %s' % (
+ self.moduleIndex, self.compPos,
self.__class__.name, str(self.__class__.version), preset
)
@@ -308,6 +332,9 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
A component update triggered by the user changing a widget value
Call super() at the END when subclassing this.
'''
+ if self.openingPreset or not hasattr(self.parent, 'undoStack'):
+ return self._update()
+
oldWidgetVals = {
attr: getattr(self, attr)
for attr in self._trackedWidgets
@@ -328,7 +355,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
self.parent.undoStack.push(action)
def _update(self):
- '''An internal component update that is not undoable'''
+ '''A component update that is not undoable'''
newWidgetVals = {
attr: getWidgetValue(widget)
@@ -684,7 +711,7 @@ class ComponentUpdate(QtWidgets.QUndoCommand):
self.id_ = -1
if len(self.modifiedVals) == 1:
attr, val = self.modifiedVals.popitem()
- self.id_ = sum([ord(letter) for letter in attr[:14]])
+ self.id_ = sum([ord(letter) for letter in attr[-14:]])
self.modifiedVals[attr] = val
else:
log.warning(
diff --git a/src/gui/actions.py b/src/gui/actions.py
index 5a0869d..cdd3dfa 100644
--- a/src/gui/actions.py
+++ b/src/gui/actions.py
@@ -4,6 +4,23 @@
from PyQt5.QtWidgets import QUndoCommand
+class AddComponent(QUndoCommand):
+ def __init__(self, parent, compI, moduleI):
+ super().__init__(
+ "New %s component" %
+ parent.core.modules[moduleI].Component.name
+ )
+ self.parent = parent
+ self.moduleI = moduleI
+ self.compI = compI
+
+ def redo(self):
+ self.parent.core.insertComponent(self.compI, self.moduleI, self.parent)
+
+ def undo(self):
+ self.parent._removeComponent(self.compI)
+
+
class RemoveComponent(QUndoCommand):
def __init__(self, parent, selectedRows):
super().__init__('Remove component')
@@ -17,15 +34,7 @@ class RemoveComponent(QUndoCommand):
]
def redo(self):
- stackedWidget = self.parent.window.stackedWidget
- componentList = self.parent.window.listWidget_componentList
- for index in self.selectedRows:
- stackedWidget.removeWidget(self.parent.pages[index])
- componentList.takeItem(index)
- self.parent.core.removeComponent(index)
- self.parent.pages.pop(index)
- self.parent.changeComponentWidget()
- self.parent.drawPreview()
+ self.parent._removeComponent(self.selectedRows[0])
def undo(self):
componentList = self.parent.window.listWidget_componentList
@@ -74,3 +83,21 @@ class MoveComponent(QUndoCommand):
def undo(self):
self.do(self.newRow, self.row)
+
+
+class ClearPreset(QUndoCommand):
+ def __init__(self, parent, compI):
+ super().__init__("Clear preset")
+ self.parent = parent
+ self.compI = compI
+ self.component = self.parent.core.selectedComponents[compI]
+ self.store = self.component.savePreset()
+ self.store['preset'] = self.component.currentPreset
+
+ def redo(self):
+ self.parent.core.clearPreset(self.compI)
+ self.parent.updateComponentTitle(self.compI, False)
+
+ def undo(self):
+ self.parent.core.selectedComponents[self.compI].loadPreset(self.store)
+ self.parent.updateComponentTitle(self.compI, self.store)
diff --git a/src/gui/mainwindow.py b/src/gui/mainwindow.py
index 26464a9..8000b3b 100644
--- a/src/gui/mainwindow.py
+++ b/src/gui/mainwindow.py
@@ -20,7 +20,9 @@ import gui.preview_thread as preview_thread
from gui.preview_win import PreviewWindow
from gui.presetmanager import PresetManager
from gui.actions import *
-from toolkit import disableWhenEncoding, disableWhenOpeningProject, checkOutput
+from toolkit import (
+ disableWhenEncoding, disableWhenOpeningProject, checkOutput, blockSignals
+)
log = logging.getLogger('AVP.MainWindow')
@@ -165,7 +167,7 @@ class MainWindow(QtWidgets.QMainWindow):
for i, comp in enumerate(self.core.modules):
action = self.compMenu.addAction(comp.Component.name)
action.triggered.connect(
- lambda _, item=i: self.core.insertComponent(0, item, self)
+ lambda _, item=i: self.addComponent(0, item)
)
self.window.pushButton_addComponent.setMenu(self.compMenu)
@@ -686,7 +688,13 @@ class MainWindow(QtWidgets.QMainWindow):
msg="Current FFmpeg command:\n\n %s" % " ".join(lines)
)
+ def addComponent(self, compPos, moduleIndex):
+ '''Creates an undoable action that adds a new component.'''
+ action = AddComponent(self, compPos, moduleIndex)
+ self.undoStack.push(action)
+
def insertComponent(self, index):
+ '''Triggered by Core to finish initializing a new component.'''
componentList = self.window.listWidget_componentList
stackedWidget = self.window.stackedWidget
@@ -712,6 +720,16 @@ class MainWindow(QtWidgets.QMainWindow):
action = RemoveComponent(self, selected)
self.undoStack.push(action)
+ def _removeComponent(self, index):
+ stackedWidget = self.window.stackedWidget
+ componentList = self.window.listWidget_componentList
+ stackedWidget.removeWidget(self.pages[index])
+ componentList.takeItem(index)
+ self.core.removeComponent(index)
+ self.pages.pop(index)
+ self.changeComponentWidget()
+ self.drawPreview()
+
@disableWhenEncoding
def moveComponent(self, change):
'''Moves a component relatively from its current position'''
@@ -786,9 +804,8 @@ class MainWindow(QtWidgets.QMainWindow):
self.window.lineEdit_audioFile,
self.window.lineEdit_outputFile
):
- field.blockSignals(True)
- field.setText('')
- field.blockSignals(False)
+ with blockSignals(field):
+ field.setText('')
self.progressBarUpdated(0)
self.progressBarSetText('')
self.undoStack.clear()
@@ -938,8 +955,8 @@ class MainWindow(QtWidgets.QMainWindow):
for i, comp in enumerate(self.core.modules):
menuItem = self.submenu.addAction(comp.Component.name)
menuItem.triggered.connect(
- lambda _, item=i: self.core.insertComponent(
- 0 if insertCompAtTop else index, item, self
+ lambda _, item=i: self.addComponent(
+ 0 if insertCompAtTop else index, item
)
)
diff --git a/src/gui/presetmanager.py b/src/gui/presetmanager.py
index 1cc0887..79ec539 100644
--- a/src/gui/presetmanager.py
+++ b/src/gui/presetmanager.py
@@ -8,6 +8,7 @@ import os
from toolkit import badName
from core import Core
+from gui.actions import *
class PresetManager(QtWidgets.QDialog):
@@ -130,8 +131,8 @@ class PresetManager(QtWidgets.QDialog):
def clearPreset(self, compI=None):
'''Functions on mainwindow level from the context menu'''
compI = self.parent.window.listWidget_componentList.currentRow()
- self.core.clearPreset(compI)
- self.parent.updateComponentTitle(compI, False)
+ action = ClearPreset(self.parent, compI)
+ self.parent.undoStack.push(action)
def openSavePresetDialog(self):
'''Functions on mainwindow level from the context menu'''
--
cgit v1.2.3
From 43ea3bfd733f63e5b22d2f1eb7ef7c8ad2cc97c9 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 17 Aug 2017 15:12:22 -0400
Subject: component updateWrapper and more obvious method names
---
src/component.py | 208 +++++++++++++++++++++++++++++++------------------------
src/core.py | 7 +-
2 files changed, 122 insertions(+), 93 deletions(-)
diff --git a/src/component.py b/src/component.py
index f0a8c6b..1fe9237 100644
--- a/src/component.py
+++ b/src/component.py
@@ -99,7 +99,7 @@ class ComponentMetaclass(type(QtCore.QObject)):
return func(self)
return errorWrapper
- def presetWrapper(func):
+ def loadPresetWrapper(func):
'''Wraps loadPreset to handle the self.openingPreset boolean'''
class openingPreset:
def __init__(self, comp):
@@ -116,6 +116,36 @@ class ComponentMetaclass(type(QtCore.QObject)):
return func(self, *args)
return presetWrapper
+ def updateWrapper(func):
+ '''
+ For undoable updates triggered by the user,
+ call _userUpdate() after the subclass's update() method.
+ For non-user updates, call _autoUpdate()
+ '''
+ class wrap:
+ def __init__(self, comp, auto):
+ self.comp = comp
+ self.auto = auto
+
+ def __enter__(self):
+ pass
+
+ def __exit__(self, *args):
+ if self.auto or self.comp.openingPreset \
+ or not hasattr(self.comp.parent, 'undoStack'):
+ self.comp._autoUpdate()
+ else:
+ self.comp._userUpdate()
+
+ def updateWrapper(self, **kwargs):
+ auto = False
+ if 'auto' in kwargs:
+ auto = kwargs['auto']
+
+ with wrap(self, auto):
+ return func(self)
+ return updateWrapper
+
def __new__(cls, name, parents, attrs):
if 'ui' not in attrs:
# Use module name as ui filename by default
@@ -128,37 +158,32 @@ class ComponentMetaclass(type(QtCore.QObject)):
'names', # Class methods
'error', 'audio', 'properties', # Properties
'preFrameRender', 'previewRender',
- 'frameRender', 'command', 'loadPreset'
+ 'frameRender', 'command',
+ 'loadPreset', 'update'
)
# Auto-decorate methods
for key in decorate:
if key not in attrs:
continue
-
if key in ('names'):
attrs[key] = classmethod(attrs[key])
-
- if key in ('audio'):
+ elif key in ('audio'):
attrs[key] = property(attrs[key])
-
- if key == 'command':
+ elif key == 'command':
attrs[key] = cls.commandWrapper(attrs[key])
-
- if key in ('previewRender', 'frameRender'):
+ elif key in ('previewRender', 'frameRender'):
attrs[key] = cls.renderWrapper(attrs[key])
-
- if key == 'preFrameRender':
+ elif key == 'preFrameRender':
attrs[key] = cls.initializationWrapper(attrs[key])
-
- if key == 'properties':
+ elif key == 'properties':
attrs[key] = cls.propertiesWrapper(attrs[key])
-
- if key == 'error':
+ elif key == 'error':
attrs[key] = cls.errorWrapper(attrs[key])
-
- if key == 'loadPreset':
- attrs[key] = cls.presetWrapper(attrs[key])
+ elif key == 'loadPreset':
+ attrs[key] = cls.loadPresetWrapper(attrs[key])
+ elif key == 'update':
+ attrs[key] = cls.updateWrapper(attrs[key])
# Turn version string into a number
try:
@@ -229,10 +254,12 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
except Exception as e:
preset = '%s occurred while saving preset' % str(e)
- return 'Component(%s, %s, Core)\n' \
- 'Name: %s v%s\n Preset: %s' % (
- self.moduleIndex, self.compPos,
- self.__class__.name, str(self.__class__.version), preset
+ return (
+ 'Component(%s, %s, Core)\n'
+ 'Name: %s v%s\n Preset: %s' % (
+ self.moduleIndex, self.compPos,
+ self.__class__.name, str(self.__class__.version), preset
+ )
)
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
@@ -329,74 +356,10 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
def update(self):
'''
- A component update triggered by the user changing a widget value
- Call super() at the END when subclassing this.
+ Starting point for a component update. A subclass should override
+ this method, and the base class will then magically insert a call
+ to either _autoUpdate() or _userUpdate() at the end.
'''
- if self.openingPreset or not hasattr(self.parent, 'undoStack'):
- return self._update()
-
- oldWidgetVals = {
- attr: getattr(self, attr)
- for attr in self._trackedWidgets
- }
- newWidgetVals = {
- attr: getWidgetValue(widget)
- if attr not in self._colorWidgets else rgbFromString(widget.text())
- for attr, widget in self._trackedWidgets.items()
- }
- modifiedWidgets = {
- attr: val
- for attr, val in newWidgetVals.items()
- if val != oldWidgetVals[attr]
- }
-
- if modifiedWidgets:
- action = ComponentUpdate(self, oldWidgetVals, modifiedWidgets)
- self.parent.undoStack.push(action)
-
- def _update(self):
- '''A component update that is not undoable'''
-
- newWidgetVals = {
- attr: getWidgetValue(widget)
- for attr, widget in self._trackedWidgets.items()
- }
- self.setAttrs(newWidgetVals)
- self.sendUpdateSignal()
-
- def setAttrs(self, attrDict):
- '''
- Sets attrs (linked to trackedWidgets) in this preset to
- the values in the attrDict. Mutates certain widget values if needed
- '''
- for attr, val in attrDict.items():
- if attr in self._colorWidgets:
- # Color Widgets: text stored as tuple & update the button color
- if type(val) is tuple:
- rgbTuple = val
- else:
- rgbTuple = rgbFromString(val)
- btnStyle = (
- "QPushButton { background-color : %s; outline: none; }"
- % QColor(*rgbTuple).name())
- self._colorWidgets[attr].setStyleSheet(btnStyle)
- setattr(self, attr, rgbTuple)
-
- elif attr in self._relativeWidgets:
- # Relative widgets: number scales to fit export resolution
- self.updateRelativeWidget(attr)
- setattr(self, attr, val)
-
- else:
- # Normal tracked widget
- setattr(self, attr, val)
-
- def sendUpdateSignal(self):
- if not self.core.openingProject:
- self.parent.drawPreview()
- saveValueStore = self.savePreset()
- saveValueStore['preset'] = self.currentPreset
- self.modified.emit(self.compPos, saveValueStore)
def loadPreset(self, presetDict, presetName=None):
'''
@@ -464,6 +427,69 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
# "Private" Methods
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
+ def _userUpdate(self):
+ '''An undoable component update triggered by the user'''
+ oldWidgetVals = {
+ attr: getattr(self, attr)
+ for attr in self._trackedWidgets
+ }
+ newWidgetVals = {
+ attr: getWidgetValue(widget)
+ if attr not in self._colorWidgets else rgbFromString(widget.text())
+ for attr, widget in self._trackedWidgets.items()
+ }
+ modifiedWidgets = {
+ attr: val
+ for attr, val in newWidgetVals.items()
+ if val != oldWidgetVals[attr]
+ }
+
+ if modifiedWidgets:
+ action = ComponentUpdate(self, oldWidgetVals, modifiedWidgets)
+ self.parent.undoStack.push(action)
+
+ def _autoUpdate(self):
+ '''An internal component update that is not undoable'''
+ newWidgetVals = {
+ attr: getWidgetValue(widget)
+ for attr, widget in self._trackedWidgets.items()
+ }
+ self.setAttrs(newWidgetVals)
+ self._sendUpdateSignal()
+
+ def setAttrs(self, attrDict):
+ '''
+ Sets attrs (linked to trackedWidgets) in this preset to
+ the values in the attrDict. Mutates certain widget values if needed
+ '''
+ for attr, val in attrDict.items():
+ if attr in self._colorWidgets:
+ # Color Widgets: text stored as tuple & update the button color
+ if type(val) is tuple:
+ rgbTuple = val
+ else:
+ rgbTuple = rgbFromString(val)
+ btnStyle = (
+ "QPushButton { background-color : %s; outline: none; }"
+ % QColor(*rgbTuple).name())
+ self._colorWidgets[attr].setStyleSheet(btnStyle)
+ setattr(self, attr, rgbTuple)
+
+ elif attr in self._relativeWidgets:
+ # Relative widgets: number scales to fit export resolution
+ self.updateRelativeWidget(attr)
+ setattr(self, attr, val)
+
+ else:
+ # Normal tracked widget
+ setattr(self, attr, val)
+
+ def _sendUpdateSignal(self):
+ if not self.core.openingProject:
+ self.parent.drawPreview()
+ saveValueStore = self.savePreset()
+ saveValueStore['preset'] = self.currentPreset
+ self.modified.emit(self.compPos, saveValueStore)
def trackWidgets(self, trackDict, **kwargs):
'''
@@ -730,7 +756,7 @@ class ComponentUpdate(QtWidgets.QUndoCommand):
def redo(self):
self.parent.setAttrs(self.modifiedVals)
- self.parent.sendUpdateSignal()
+ self.parent._sendUpdateSignal()
def undo(self):
self.parent.setAttrs(self.oldWidgetVals)
@@ -740,4 +766,4 @@ class ComponentUpdate(QtWidgets.QUndoCommand):
if attr in self.parent._colorWidgets:
val = '%s,%s,%s' % val
setWidgetValue(widget, val)
- self.parent.sendUpdateSignal()
+ self.parent._sendUpdateSignal()
diff --git a/src/core.py b/src/core.py
index 14517b0..7609698 100644
--- a/src/core.py
+++ b/src/core.py
@@ -83,6 +83,8 @@ class Core:
)
# init component's widget for loading/saving presets
component.widget(loader)
+ # use autoUpdate() method before update() this 1 time to set attrs
+ component._autoUpdate()
else:
moduleIndex = -1
log.debug(
@@ -118,8 +120,9 @@ class Core:
self.componentListChanged()
def updateComponent(self, i):
- log.debug('Updating %s #%s' % (self.selectedComponents[i], str(i)))
- self.selectedComponents[i]._update()
+ log.debug('Auto-updating %s #%s' % (
+ self.selectedComponents[i], str(i)))
+ self.selectedComponents[i].update(auto=True)
def moduleIndexFor(self, compName):
try:
--
cgit v1.2.3
From 87e762a8aa3fa97a3d43a18c59098b287bb95506 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 17 Aug 2017 20:12:46 -0400
Subject: undoable preset open, rename, and delete'
---
src/core.py | 4 +--
src/gui/actions.py | 79 ++++++++++++++++++++++++++++++++++++++++++++++++
src/gui/presetmanager.py | 49 ++++++++++++++++--------------
3 files changed, 107 insertions(+), 25 deletions(-)
diff --git a/src/core.py b/src/core.py
index 7609698..d9499f7 100644
--- a/src/core.py
+++ b/src/core.py
@@ -83,7 +83,7 @@ class Core:
)
# init component's widget for loading/saving presets
component.widget(loader)
- # use autoUpdate() method before update() this 1 time to set attrs
+ # use autoUpdate() method before update() this 1 time to set attrs
component._autoUpdate()
else:
moduleIndex = -1
@@ -169,7 +169,7 @@ class Core:
def getPresetDir(self, comp):
'''Get the preset subdir for a particular version of a component'''
- return os.path.join(Core.presetDir, str(comp), str(comp.version))
+ return os.path.join(Core.presetDir, comp.name, str(comp.version))
def openProject(self, loader, filepath):
''' loader is the object calling this method which must have
diff --git a/src/gui/actions.py b/src/gui/actions.py
index cdd3dfa..0fe97f2 100644
--- a/src/gui/actions.py
+++ b/src/gui/actions.py
@@ -2,7 +2,14 @@
QCommand classes for every undoable user action performed in the MainWindow
'''
from PyQt5.QtWidgets import QUndoCommand
+import os
+from core import Core
+
+
+# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
+# COMPONENT ACTIONS
+# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
class AddComponent(QUndoCommand):
def __init__(self, parent, compI, moduleI):
@@ -85,6 +92,10 @@ class MoveComponent(QUndoCommand):
self.do(self.newRow, self.row)
+# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
+# PRESET ACTIONS
+# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
+
class ClearPreset(QUndoCommand):
def __init__(self, parent, compI):
super().__init__("Clear preset")
@@ -101,3 +112,71 @@ class ClearPreset(QUndoCommand):
def undo(self):
self.parent.core.selectedComponents[self.compI].loadPreset(self.store)
self.parent.updateComponentTitle(self.compI, self.store)
+
+
+class OpenPreset(QUndoCommand):
+ def __init__(self, parent, presetName, compI):
+ super().__init__("Open %s preset" % presetName)
+ self.parent = parent
+ self.presetName = presetName
+ self.compI = compI
+
+ comp = self.parent.core.selectedComponents[compI]
+ self.store = comp.savePreset()
+ self.store['preset'] = str(comp.currentPreset)
+
+ def redo(self):
+ self.parent._openPreset(self.presetName, self.compI)
+
+ def undo(self):
+ self.parent.core.selectedComponents[self.compI].loadPreset(
+ self.store)
+ self.parent.parent.updateComponentTitle(self.compI, self.store)
+
+
+class RenamePreset(QUndoCommand):
+ def __init__(self, parent, path, oldName, newName):
+ super().__init__('Rename preset')
+ self.parent = parent
+ self.path = path
+ self.oldName = oldName
+ self.newName = newName
+
+ def redo(self):
+ self.parent.renamePreset(self.path, self.oldName, self.newName)
+
+ def undo(self):
+ self.parent.renamePreset(self.path, self.newName, self.oldName)
+
+
+class DeletePreset(QUndoCommand):
+ def __init__(self, parent, compName, vers, presetFile):
+ self.parent = parent
+ self.preset = (compName, vers, presetFile)
+ self.path = os.path.join(
+ Core.presetDir, compName, str(vers), presetFile
+ )
+ self.store = self.parent.core.getPreset(self.path)
+ self.presetName = self.store['preset']
+ super().__init__('Delete %s preset (%s)' % (self.presetName, compName))
+ self.loadedPresets = [
+ i for i, comp in enumerate(self.parent.core.selectedComponents)
+ if self.presetName == str(comp.currentPreset)
+ ]
+
+ def redo(self):
+ os.remove(self.path)
+ for i in self.loadedPresets:
+ self.parent.core.clearPreset(i)
+ self.parent.parent.updateComponentTitle(i, False)
+ self.parent.findPresets()
+ self.parent.drawPresetList()
+
+ def undo(self):
+ self.parent.createNewPreset(*self.preset, self.store)
+ selectedComponents = self.parent.core.selectedComponents
+ for i in self.loadedPresets:
+ selectedComponents[i].currentPreset = self.presetName
+ self.parent.parent.updateComponentTitle(i)
+ self.parent.findPresets()
+ self.parent.drawPresetList()
diff --git a/src/gui/presetmanager.py b/src/gui/presetmanager.py
index 79ec539..dce5333 100644
--- a/src/gui/presetmanager.py
+++ b/src/gui/presetmanager.py
@@ -197,11 +197,15 @@ class PresetManager(QtWidgets.QDialog):
def openPreset(self, presetName, compPos=None):
componentList = self.parent.window.listWidget_componentList
- selectedComponents = self.core.selectedComponents
-
index = compPos if compPos is not None else componentList.currentRow()
if index == -1:
return
+ action = OpenPreset(self, presetName, index)
+ self.parent.undoStack.push(action)
+
+ def _openPreset(self, presetName, index):
+ selectedComponents = self.core.selectedComponents
+
componentName = str(selectedComponents[index]).strip()
version = selectedComponents[index].version
dirname = os.path.join(self.presetDir, componentName, str(version))
@@ -225,16 +229,10 @@ class PresetManager(QtWidgets.QDialog):
if not ch:
return
self.deletePreset(comp, vers, name)
- self.findPresets()
- self.drawPresetList()
-
- for i, comp in enumerate(self.core.selectedComponents):
- if comp.currentPreset == name:
- self.clearPreset(i)
def deletePreset(self, comp, vers, name):
- filepath = os.path.join(self.presetDir, comp, str(vers), name)
- os.remove(filepath)
+ action = DeletePreset(self, comp, vers, name)
+ self.parent.undoStack.push(action)
def warnMessage(self, window=None):
self.parent.showMessage(
@@ -271,7 +269,6 @@ class PresetManager(QtWidgets.QDialog):
return index
def openRenamePresetDialog(self):
- # TODO: maintain consistency by changing this to call createNewPreset()
presetList = self.window.listWidget_presets
index = self.getPresetRow()
if index == -1:
@@ -294,22 +291,28 @@ class PresetManager(QtWidgets.QDialog):
path = os.path.join(
self.presetDir, comp, str(vers))
newPath = os.path.join(path, newName)
- oldPath = os.path.join(path, oldName)
if self.presetExists(newPath):
return
- if os.path.exists(newPath):
- os.remove(newPath)
- os.rename(oldPath, newPath)
- self.findPresets()
- self.drawPresetList()
- for i, comp in enumerate(self.core.selectedComponents):
- if self.core.getPresetDir(comp) == path \
- and comp.currentPreset == oldName:
- self.core.openPreset(newPath, i, newName)
- self.parent.updateComponentTitle(i, False)
- self.parent.drawPreview()
+ action = RenamePreset(self, path, oldName, newName)
+ self.parent.undoStack.push(action)
break
+ def renamePreset(self, path, oldName, newName):
+ oldPath = os.path.join(path, oldName)
+ newPath = os.path.join(path, newName)
+ if os.path.exists(newPath):
+ os.remove(newPath)
+ os.rename(oldPath, newPath)
+ self.findPresets()
+ self.drawPresetList()
+ path = os.path.dirname(newPath)
+ for i, comp in enumerate(self.core.selectedComponents):
+ if self.core.getPresetDir(comp) == path \
+ and comp.currentPreset == oldName:
+ self.core.openPreset(newPath, i, newName)
+ self.parent.updateComponentTitle(i, False)
+ self.parent.drawPreview()
+
def openImportDialog(self):
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
self.window, "Import Preset File",
--
cgit v1.2.3
From c07f2426ceeada205fdacbfba66329179a74a1dc Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 19 Aug 2017 18:32:12 -0400
Subject: fixed issues with undoing relative widgets
---
src/component.py | 198 +++++++++++++++++++++++++++++++++------------
src/components/color.py | 2 -
src/components/image.py | 2 -
src/components/life.py | 1 -
src/components/sound.py | 1 -
src/components/spectrum.py | 4 +-
src/components/text.py | 1 -
src/components/video.py | 2 -
src/components/waveform.py | 2 +-
src/core.py | 11 +--
src/gui/actions.py | 11 ++-
src/gui/mainwindow.py | 4 +-
src/gui/presetmanager.py | 4 +
src/gui/preview_thread.py | 2 +-
src/gui/preview_win.py | 2 +-
src/main.py | 2 +-
src/toolkit/common.py | 47 +++++++++--
17 files changed, 215 insertions(+), 81 deletions(-)
diff --git a/src/component.py b/src/component.py
index 1fe9237..ba86422 100644
--- a/src/component.py
+++ b/src/component.py
@@ -9,6 +9,7 @@ import sys
import math
import time
import logging
+from copy import copy
from toolkit.frame import BlankFrame
from toolkit import (
@@ -113,14 +114,20 @@ class ComponentMetaclass(type(QtCore.QObject)):
def presetWrapper(self, *args):
with openingPreset(self):
- return func(self, *args)
+ try:
+ return func(self, *args)
+ except Exception:
+ try:
+ raise ComponentError(self, 'preset loader')
+ except ComponentError:
+ return
return presetWrapper
def updateWrapper(func):
'''
- For undoable updates triggered by the user,
- call _userUpdate() after the subclass's update() method.
- For non-user updates, call _autoUpdate()
+ Calls _preUpdate before every subclass update().
+ Afterwards, for non-user updates, calls _autoUpdate().
+ For undoable updates triggered by the user, calls _userUpdate()
'''
class wrap:
def __init__(self, comp, auto):
@@ -128,24 +135,57 @@ class ComponentMetaclass(type(QtCore.QObject)):
self.auto = auto
def __enter__(self):
- pass
+ self.comp._preUpdate()
def __exit__(self, *args):
if self.auto or self.comp.openingPreset \
or not hasattr(self.comp.parent, 'undoStack'):
+ log.verbose('Automatic update')
self.comp._autoUpdate()
else:
+ log.verbose('User update')
self.comp._userUpdate()
def updateWrapper(self, **kwargs):
- auto = False
- if 'auto' in kwargs:
- auto = kwargs['auto']
-
+ auto = kwargs['auto'] if 'auto' in kwargs else False
with wrap(self, auto):
- return func(self)
+ try:
+ return func(self)
+ except Exception:
+ try:
+ raise ComponentError(self, 'update method')
+ except ComponentError:
+ return
return updateWrapper
+ def widgetWrapper(func):
+ '''Connects all widgets to update method after the subclass's method'''
+ class wrap:
+ def __init__(self, comp):
+ self.comp = comp
+
+ def __enter__(self):
+ pass
+
+ def __exit__(self, *args):
+ for widgetList in self.comp._allWidgets.values():
+ for widget in widgetList:
+ log.verbose('Connecting %s' % str(
+ widget.__class__.__name__))
+ connectWidget(widget, self.comp.update)
+
+ def widgetWrapper(self, *args, **kwargs):
+ auto = kwargs['auto'] if 'auto' in kwargs else False
+ with wrap(self):
+ try:
+ return func(self, *args, **kwargs)
+ except Exception:
+ try:
+ raise ComponentError(self, 'widget creation')
+ except ComponentError:
+ return
+ return widgetWrapper
+
def __new__(cls, name, parents, attrs):
if 'ui' not in attrs:
# Use module name as ui filename by default
@@ -153,13 +193,12 @@ class ComponentMetaclass(type(QtCore.QObject)):
attrs['__module__'].split('.')[-1]
)[0]
- # if parents[0] == QtCore.QObject: else:
decorate = (
'names', # Class methods
'error', 'audio', 'properties', # Properties
'preFrameRender', 'previewRender',
'frameRender', 'command',
- 'loadPreset', 'update'
+ 'loadPreset', 'update', 'widget',
)
# Auto-decorate methods
@@ -184,6 +223,8 @@ class ComponentMetaclass(type(QtCore.QObject)):
attrs[key] = cls.loadPresetWrapper(attrs[key])
elif key == 'update':
attrs[key] = cls.updateWrapper(attrs[key])
+ elif key == 'widget' and parents[0] != QtCore.QObject:
+ attrs[key] = cls.widgetWrapper(attrs[key])
# Turn version string into a number
try:
@@ -224,23 +265,28 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
self.moduleIndex = moduleIndex
self.compPos = compPos
self.core = core
- self.currentPreset = None
- self.openingPreset = False
+ # STATUS VARIABLES
+ self.currentPreset = None
+ self._allWidgets = {}
self._trackedWidgets = {}
self._presetNames = {}
self._commandArgs = {}
self._colorWidgets = {}
self._colorFuncs = {}
self._relativeWidgets = {}
- # pixel values stored as floats
+ # Pixel values stored as floats
self._relativeValues = {}
- # maximum values of spinBoxes at 1080p (Core.resolutions[0])
+ # Maximum values of spinBoxes at 1080p (Core.resolutions[0])
self._relativeMaximums = {}
+ # LOCKING VARIABLES
+ self.openingPreset = False
self._lockedProperties = None
self._lockedError = None
self._lockedSize = None
+ # If set to a dict, values are used as basis to update relative widgets
+ self.oldAttrs = None
# Stop lengthy processes in response to this variable
self.canceled = False
@@ -338,21 +384,21 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
'''
self.parent = parent
self.settings = parent.settings
+ log.verbose('Creating UI for %s #%s\'s widget' % (
+ self.name, self.compPos
+ ))
self.page = self.loadUi(self.__class__.ui)
- # Connect widget signals
- widgets = {
+ # Find all normal widgets which will be connected after subclass method
+ self._allWidgets = {
'lineEdit': self.page.findChildren(QtWidgets.QLineEdit),
'checkBox': self.page.findChildren(QtWidgets.QCheckBox),
'spinBox': self.page.findChildren(QtWidgets.QSpinBox),
'comboBox': self.page.findChildren(QtWidgets.QComboBox),
}
- widgets['spinBox'].extend(
+ self._allWidgets['spinBox'].extend(
self.page.findChildren(QtWidgets.QDoubleSpinBox)
)
- for widgetList in widgets.values():
- for widget in widgetList:
- connectWidget(widget, self.update)
def update(self):
'''
@@ -427,10 +473,15 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
# "Private" Methods
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
+ def _preUpdate(self):
+ '''Happens before subclass update()'''
+ for attr in self._relativeWidgets:
+ self.updateRelativeWidget(attr)
+
def _userUpdate(self):
- '''An undoable component update triggered by the user'''
+ '''Happens after subclass update() for an undoable update by user.'''
oldWidgetVals = {
- attr: getattr(self, attr)
+ attr: copy(getattr(self, attr))
for attr in self._trackedWidgets
}
newWidgetVals = {
@@ -443,13 +494,12 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
for attr, val in newWidgetVals.items()
if val != oldWidgetVals[attr]
}
-
if modifiedWidgets:
action = ComponentUpdate(self, oldWidgetVals, modifiedWidgets)
self.parent.undoStack.push(action)
def _autoUpdate(self):
- '''An internal component update that is not undoable'''
+ '''Happens after subclass update() for an internal component update.'''
newWidgetVals = {
attr: getWidgetValue(widget)
for attr, widget in self._trackedWidgets.items()
@@ -459,12 +509,12 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
def setAttrs(self, attrDict):
'''
- Sets attrs (linked to trackedWidgets) in this preset to
+ Sets attrs (linked to trackedWidgets) in this component to
the values in the attrDict. Mutates certain widget values if needed
'''
for attr, val in attrDict.items():
if attr in self._colorWidgets:
- # Color Widgets: text stored as tuple & update the button color
+ # Color Widgets must have a tuple & have a button to update
if type(val) is tuple:
rgbTuple = val
else:
@@ -475,15 +525,25 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
self._colorWidgets[attr].setStyleSheet(btnStyle)
setattr(self, attr, rgbTuple)
- elif attr in self._relativeWidgets:
- # Relative widgets: number scales to fit export resolution
- self.updateRelativeWidget(attr)
- setattr(self, attr, val)
-
else:
# Normal tracked widget
setattr(self, attr, val)
+ def setWidgetValues(self, attrDict):
+ '''
+ Sets widgets defined by keys in trackedWidgets in this preset to
+ the values in the attrDict.
+ '''
+ affectedWidgets = [
+ self._trackedWidgets[attr] for attr in attrDict
+ ]
+ with blockSignals(affectedWidgets):
+ for attr, val in attrDict.items():
+ widget = self._trackedWidgets[attr]
+ if attr in self._colorWidgets:
+ val = '%s,%s,%s' % val
+ setWidgetValue(widget, val)
+
def _sendUpdateSignal(self):
if not self.core.openingProject:
self.parent.drawPreview()
@@ -499,6 +559,8 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
Optional args:
'presetNames': preset variable names to replace attr names
'commandArgs': arg keywords that differ from attr names
+ 'colorWidgets': identify attr as RGB tuple & update button CSS
+ 'relativeWidgets': change value proportionally to resolution
NOTE: Any kwarg key set to None will selectively disable tracking.
'''
@@ -542,6 +604,8 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
self._relativeMaximums[attr] = \
self._trackedWidgets[attr].maximum()
self.updateRelativeWidgetMaximum(attr)
+ self._preUpdate()
+ self._autoUpdate()
def pickColor(self, textWidget, button):
'''Use color picker to get color input from the user.'''
@@ -627,12 +691,28 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
def setRelativeWidget(self, attr, floatVal):
'''Set a relative widget using a float'''
pixelVal = self.pixelValForAttr(attr, floatVal)
- self._trackedWidgets[attr].setValue(pixelVal)
+ with blockSignals(self._allWidgets):
+ self._trackedWidgets[attr].setValue(pixelVal)
+ self.update(auto=True)
+
+ def getOldAttr(self, attr):
+ '''
+ Returns previous state of this attr. Used to determine whether
+ a relative widget must be updated. Required because undoing/redoing
+ can make determining the 'previous' value tricky.
+ '''
+ if self.oldAttrs is not None:
+ log.verbose('Using nonstandard oldAttr for %s' % attr)
+ return self.oldAttrs[attr]
+ else:
+ return getattr(self, attr)
def updateRelativeWidget(self, attr):
+ '''Called by _preUpdate() for each relativeWidget before each update'''
try:
- oldUserValue = getattr(self, attr)
- except AttributeError:
+ oldUserValue = self.getOldAttr(attr)
+ except (AttributeError, KeyError):
+ log.info('Using visible values as basis for relative widgets')
oldUserValue = self._trackedWidgets[attr].value()
newUserValue = self._trackedWidgets[attr].value()
newRelativeVal = self.floatValForAttr(attr, newUserValue)
@@ -645,11 +725,10 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
# means the pixel value needs to be updated
log.debug('Updating %s #%s\'s relative widget: %s' % (
self.name, self.compPos, attr))
- self._trackedWidgets[attr].blockSignals(True)
- self.updateRelativeWidgetMaximum(attr)
- pixelVal = self.pixelValForAttr(attr, oldRelativeVal)
- self._trackedWidgets[attr].setValue(pixelVal)
- self._trackedWidgets[attr].blockSignals(False)
+ with blockSignals(self._trackedWidgets[attr]):
+ self.updateRelativeWidgetMaximum(attr)
+ pixelVal = self.pixelValForAttr(attr, oldRelativeVal)
+ self._trackedWidgets[attr].setValue(pixelVal)
if attr not in self._relativeValues \
or oldUserValue != newUserValue:
@@ -725,14 +804,22 @@ class ComponentUpdate(QtWidgets.QUndoCommand):
parent.name, parent.compPos
)
)
+ self.undone = False
self.parent = parent
self.oldWidgetVals = {
- attr: val
+ attr: copy(val)
for attr, val in oldWidgetVals.items()
if attr in modifiedVals
}
self.modifiedVals = modifiedVals
+ # Because relative widgets change themselves every update based on
+ # their previous value, we must store ALL their values in case of undo
+ self.redoRelativeWidgetVals = {
+ attr: copy(getattr(self.parent, attr))
+ for attr in self.parent._relativeWidgets
+ }
+
# Determine if this update is mergeable
self.id_ = -1
if len(self.modifiedVals) == 1:
@@ -755,15 +842,26 @@ class ComponentUpdate(QtWidgets.QUndoCommand):
return True
def redo(self):
+ if self.undone:
+ log.debug('Redoing component update')
+ self.parent.setWidgetValues(self.modifiedVals)
self.parent.setAttrs(self.modifiedVals)
- self.parent._sendUpdateSignal()
+ if self.undone:
+ self.parent.oldAttrs = self.redoRelativeWidgetVals
+ self.parent.update(auto=True)
+ self.parent.oldAttrs = None
+ else:
+ self.undoRelativeWidgetVals = {
+ attr: copy(getattr(self.parent, attr))
+ for attr in self.parent._relativeWidgets
+ }
+ self.parent._sendUpdateSignal()
def undo(self):
+ log.debug('Undoing component update')
+ self.undone = True
+ self.parent.oldAttrs = self.undoRelativeWidgetVals
+ self.parent.setWidgetValues(self.oldWidgetVals)
self.parent.setAttrs(self.oldWidgetVals)
- with blockSignals(self.parent):
- for attr, val in self.oldWidgetVals.items():
- widget = self.parent._trackedWidgets[attr]
- if attr in self.parent._colorWidgets:
- val = '%s,%s,%s' % val
- setWidgetValue(widget, val)
- self.parent._sendUpdateSignal()
+ self.parent.update(auto=True)
+ self.parent.oldAttrs = None
diff --git a/src/components/color.py b/src/components/color.py
index d09cee8..a55aa10 100644
--- a/src/components/color.py
+++ b/src/components/color.py
@@ -82,8 +82,6 @@ class Component(Component):
self.page.pushButton_color2.setEnabled(False)
self.page.fillWidget.setCurrentIndex(fillType)
- super().update()
-
def previewRender(self):
return self.drawFrame(self.width, self.height)
diff --git a/src/components/image.py b/src/components/image.py
index 63bee1a..c57b69c 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -84,7 +84,6 @@ class Component(Component):
if filename:
self.settings.setValue("componentDir", os.path.dirname(filename))
self.page.lineEdit_image.setText(filename)
- self.update()
def command(self, arg):
if '=' in arg:
@@ -123,4 +122,3 @@ class Component(Component):
else:
scaleBox.setVisible(True)
stretchScaleBox.setVisible(False)
- super().update()
diff --git a/src/components/life.py b/src/components/life.py
index 2383d30..76d2c5f 100644
--- a/src/components/life.py
+++ b/src/components/life.py
@@ -53,7 +53,6 @@ class Component(Component):
if filename:
self.settings.setValue("componentDir", os.path.dirname(filename))
self.page.lineEdit_image.setText(filename)
- self.update()
def shiftGrid(self, d):
def newGrid(Xchange, Ychange):
diff --git a/src/components/sound.py b/src/components/sound.py
index 26ecf93..b86f40c 100644
--- a/src/components/sound.py
+++ b/src/components/sound.py
@@ -53,7 +53,6 @@ class Component(Component):
if filename:
self.settings.setValue("componentDir", os.path.dirname(filename))
self.page.lineEdit_sound.setText(filename)
- self.update()
def commandHelp(self):
print('Path to audio file:\n path=/filepath/to/sound.ogg')
diff --git a/src/components/spectrum.py b/src/components/spectrum.py
index 89130a2..2b98dc2 100644
--- a/src/components/spectrum.py
+++ b/src/components/spectrum.py
@@ -76,8 +76,6 @@ class Component(Component):
else:
self.page.checkBox_mono.setEnabled(True)
- super().update()
-
def previewRender(self):
changedSize = self.updateChunksize()
if not changedSize \
@@ -138,7 +136,7 @@ class Component(Component):
'-r', self.settings.value("outputFrameRate"),
'-ss', "{0:.3f}".format(startPt),
'-i',
- os.path.join(self.core.wd, 'background.png')
+ self.core.junkStream
if genericPreview else inputFile,
'-f', 'image2pipe',
'-pix_fmt', 'rgba',
diff --git a/src/components/text.py b/src/components/text.py
index d3afd5c..92f0599 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -68,7 +68,6 @@ class Component(Component):
self.page.spinBox_shadY.setHidden(True)
self.page.label_shadBlur.setHidden(True)
self.page.spinBox_shadBlur.setHidden(True)
- super().update()
def centerXY(self):
self.setRelativeWidget('xPosition', 0.5)
diff --git a/src/components/video.py b/src/components/video.py
index a189f60..9c0d608 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -52,7 +52,6 @@ class Component(Component):
else:
self.page.label_volume.setEnabled(False)
self.page.spinBox_volume.setEnabled(False)
- super().update()
def previewRender(self):
self.updateChunksize()
@@ -119,7 +118,6 @@ class Component(Component):
if filename:
self.settings.setValue("componentDir", os.path.dirname(filename))
self.page.lineEdit_video.setText(filename)
- self.update()
def getPreviewFrame(self, width, height):
if not self.videoPath or not os.path.exists(self.videoPath):
diff --git a/src/components/waveform.py b/src/components/waveform.py
index 0743e55..5c02bbf 100644
--- a/src/components/waveform.py
+++ b/src/components/waveform.py
@@ -98,7 +98,7 @@ class Component(Component):
'-r', self.settings.value("outputFrameRate"),
'-ss', "{0:.3f}".format(startPt),
'-i',
- os.path.join(self.core.wd, 'background.png')
+ self.core.junkStream
if genericPreview else inputFile,
'-f', 'image2pipe',
'-pix_fmt', 'rgba',
diff --git a/src/core.py b/src/core.py
index d9499f7..169716c 100644
--- a/src/core.py
+++ b/src/core.py
@@ -13,7 +13,7 @@ import toolkit
log = logging.getLogger('AVP.Core')
-STDOUT_LOGLVL = logging.WARNING
+STDOUT_LOGLVL = logging.VERBOSE
FILE_LOGLVL = logging.DEBUG
@@ -81,10 +81,7 @@ class Core:
component = self.modules[moduleIndex].Component(
moduleIndex, compPos, self
)
- # init component's widget for loading/saving presets
component.widget(loader)
- # use autoUpdate() method before update() this 1 time to set attrs
- component._autoUpdate()
else:
moduleIndex = -1
log.debug(
@@ -186,9 +183,8 @@ class Core:
if hasattr(loader, 'window'):
for widget, value in data['WindowFields']:
widget = eval('loader.window.%s' % widget)
- widget.blockSignals(True)
- toolkit.setWidgetValue(widget, value)
- widget.blockSignals(False)
+ with toolkit.blockSignals(widget):
+ toolkit.setWidgetValue(widget, value)
for key, value in data['Settings']:
Core.settings.setValue(key, value)
@@ -474,6 +470,7 @@ class Core:
'logDir': os.path.join(dataDir, 'log'),
'presetDir': os.path.join(dataDir, 'presets'),
'componentsPath': os.path.join(wd, 'components'),
+ 'junkStream': os.path.join(wd, 'gui', 'background.png'),
'encoderOptions': encoderOptions,
'resolutions': [
'1920x1080',
diff --git a/src/gui/actions.py b/src/gui/actions.py
index 0fe97f2..1444569 100644
--- a/src/gui/actions.py
+++ b/src/gui/actions.py
@@ -20,11 +20,20 @@ class AddComponent(QUndoCommand):
self.parent = parent
self.moduleI = moduleI
self.compI = compI
+ self.comp = None
def redo(self):
- self.parent.core.insertComponent(self.compI, self.moduleI, self.parent)
+ if self.comp is None:
+ self.parent.core.insertComponent(
+ self.compI, self.moduleI, self.parent)
+ else:
+ # inserting previously-created component
+ self.parent.core.insertComponent(
+ self.compI, self.comp, self.parent)
+
def undo(self):
+ self.comp = self.parent.core.selectedComponents[self.compI]
self.parent._removeComponent(self.compI)
diff --git a/src/gui/mainwindow.py b/src/gui/mainwindow.py
index 8000b3b..76c53af 100644
--- a/src/gui/mainwindow.py
+++ b/src/gui/mainwindow.py
@@ -25,7 +25,7 @@ from toolkit import (
)
-log = logging.getLogger('AVP.MainWindow')
+log = logging.getLogger('AVP.Gui.MainWindow')
class MainWindow(QtWidgets.QMainWindow):
@@ -76,7 +76,7 @@ class MainWindow(QtWidgets.QMainWindow):
# Create the preview window and its thread, queues, and timers
log.debug('Creating preview window')
self.previewWindow = PreviewWindow(self, os.path.join(
- Core.wd, "background.png"))
+ Core.wd, 'gui', "background.png"))
window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
log.debug('Starting preview thread')
diff --git a/src/gui/presetmanager.py b/src/gui/presetmanager.py
index dce5333..befa7cd 100644
--- a/src/gui/presetmanager.py
+++ b/src/gui/presetmanager.py
@@ -5,12 +5,16 @@
from PyQt5 import QtCore, QtWidgets
import string
import os
+import logging
from toolkit import badName
from core import Core
from gui.actions import *
+log = logging.getLogger('AVP.Gui.PresetManager')
+
+
class PresetManager(QtWidgets.QDialog):
def __init__(self, window, parent):
super().__init__(parent.window)
diff --git a/src/gui/preview_thread.py b/src/gui/preview_thread.py
index 9615884..33a9e7a 100644
--- a/src/gui/preview_thread.py
+++ b/src/gui/preview_thread.py
@@ -14,7 +14,7 @@ from toolkit.frame import Checkerboard
from toolkit import disableWhenOpeningProject
-log = logging.getLogger("AVP.PreviewThread")
+log = logging.getLogger("AVP.Gui.PreviewThread")
class Worker(QtCore.QObject):
diff --git a/src/gui/preview_win.py b/src/gui/preview_win.py
index 40c19c6..c6b9a32 100644
--- a/src/gui/preview_win.py
+++ b/src/gui/preview_win.py
@@ -7,7 +7,7 @@ class PreviewWindow(QtWidgets.QLabel):
Paints the preview QLabel in MainWindow and maintains the aspect ratio
when the window is resized.
'''
- log = logging.getLogger('AVP.PreviewWindow')
+ log = logging.getLogger('AVP.Gui.PreviewWindow')
def __init__(self, parent, img):
super(PreviewWindow, self).__init__()
diff --git a/src/main.py b/src/main.py
index c1278da..6d18af3 100644
--- a/src/main.py
+++ b/src/main.py
@@ -6,7 +6,7 @@ import logging
from __init__ import wd
-log = logging.getLogger('AVP.Entrypoint')
+log = logging.getLogger('AVP.Main')
def main():
diff --git a/src/toolkit/common.py b/src/toolkit/common.py
index 51ad023..74143e8 100644
--- a/src/toolkit/common.py
+++ b/src/toolkit/common.py
@@ -6,19 +6,53 @@ import string
import os
import sys
import subprocess
+import logging
+from copy import copy
from collections import OrderedDict
+log = logging.getLogger('AVP.Toolkit.Common')
+
+
class blockSignals:
- '''A context manager to temporarily block a Qt widget from updating'''
- def __init__(self, widget):
- self.widget = widget
+ '''
+ Context manager to temporarily block list of QtWidgets from updating,
+ and guarantee restoring the previous state afterwards.
+ '''
+ def __init__(self, widgets):
+ if type(widgets) is dict:
+ self.widgets = concatDictVals(widgets)
+ else:
+ self.widgets = (
+ widgets if hasattr(widgets, '__iter__')
+ else [widgets]
+ )
def __enter__(self):
- self.widget.blockSignals(True)
+ log.verbose('Blocking signals for %s' % ", ".join([
+ str(w.__class__.__name__) for w in self.widgets
+ ]))
+ self.oldStates = [w.signalsBlocked() for w in self.widgets]
+ for w in self.widgets:
+ w.blockSignals(True)
def __exit__(self, *args):
- self.widget.blockSignals(False)
+ log.verbose('Resetting blockSignals to %s' % sum(self.oldStates))
+ for w, state in zip(self.widgets, self.oldStates):
+ w.blockSignals(state)
+
+
+def concatDictVals(d):
+ '''Concatenates all values in given dict into one list.'''
+ key, value = d.popitem()
+ d[key] = value
+ final = copy(value)
+ if type(final) is not list:
+ final = [final]
+ final.extend([val for val in d.values()])
+ else:
+ value.extend([item for val in d.values() for item in val])
+ return final
def badName(name):
@@ -119,12 +153,14 @@ def connectWidget(widget, func):
elif type(widget) == QtWidgets.QComboBox:
widget.currentIndexChanged.connect(func)
else:
+ log.warning('Failed to connect %s ' % str(widget.__class__.__name__))
return False
return True
def setWidgetValue(widget, val):
'''Generic setValue method for use with any typical QtWidget'''
+ log.verbose('Setting %s to %s' % (str(widget.__class__.__name__), val))
if type(widget) == QtWidgets.QLineEdit:
widget.setText(val)
elif type(widget) == QtWidgets.QSpinBox \
@@ -135,6 +171,7 @@ def setWidgetValue(widget, val):
elif type(widget) == QtWidgets.QComboBox:
widget.setCurrentIndex(val)
else:
+ log.warning('Failed to set %s ' % str(widget.__class__.__name__))
return False
return True
--
cgit v1.2.3
From d4b63e4d4612db262424fe10c83f8eaa4f741f24 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 19 Aug 2017 20:45:44 -0400
Subject: remove % from log calls
---
src/component.py | 32 +++++++++++++++++---------------
src/core.py | 19 ++++++++++---------
src/gui/actions.py | 3 ++-
src/gui/mainwindow.py | 26 +++++++++++++++++++++-----
src/gui/presetmanager.py | 2 +-
src/toolkit/common.py | 16 ++++++++++------
src/toolkit/ffmpeg.py | 2 +-
src/video_thread.py | 7 ++++---
8 files changed, 66 insertions(+), 41 deletions(-)
diff --git a/src/component.py b/src/component.py
index ba86422..992a82e 100644
--- a/src/component.py
+++ b/src/component.py
@@ -40,11 +40,11 @@ class ComponentMetaclass(type(QtCore.QObject)):
def renderWrapper(func):
def renderWrapper(self, *args, **kwargs):
try:
- log.verbose('### %s #%s renders%s frame %s###' % (
+ log.verbose('### %s #%s renders%s frame %s###',
self.__class__.name, str(self.compPos),
'' if args else ' a preview',
'' if not args else '%s ' % args[0],
- ))
+ )
return func(self, *args, **kwargs)
except Exception as e:
try:
@@ -170,7 +170,7 @@ class ComponentMetaclass(type(QtCore.QObject)):
def __exit__(self, *args):
for widgetList in self.comp._allWidgets.values():
for widget in widgetList:
- log.verbose('Connecting %s' % str(
+ log.verbose('Connecting %s', str(
widget.__class__.__name__))
connectWidget(widget, self.comp.update)
@@ -230,16 +230,18 @@ class ComponentMetaclass(type(QtCore.QObject)):
try:
if 'version' not in attrs:
log.error(
- 'No version attribute in %s. Defaulting to 1' %
+ 'No version attribute in %s. Defaulting to 1',
attrs['name'])
attrs['version'] = 1
else:
attrs['version'] = int(attrs['version'].split('.')[0])
except ValueError:
- log.critical('%s component has an invalid version string:\n%s' % (
- attrs['name'], str(attrs['version'])))
+ log.critical(
+ '%s component has an invalid version string:\n%s',
+ attrs['name'], str(attrs['version'])
+ )
except KeyError:
- log.critical('%s component has no version string.' % attrs['name'])
+ log.critical('%s component has no version string.', attrs['name'])
else:
return super().__new__(cls, name, parents, attrs)
quit(1)
@@ -384,9 +386,9 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
'''
self.parent = parent
self.settings = parent.settings
- log.verbose('Creating UI for %s #%s\'s widget' % (
+ log.verbose('Creating UI for %s #%s\'s widget',
self.name, self.compPos
- ))
+ )
self.page = self.loadUi(self.__class__.ui)
# Find all normal widgets which will be connected after subclass method
@@ -702,7 +704,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
can make determining the 'previous' value tricky.
'''
if self.oldAttrs is not None:
- log.verbose('Using nonstandard oldAttr for %s' % attr)
+ log.verbose('Using nonstandard oldAttr for %s', attr)
return self.oldAttrs[attr]
else:
return getattr(self, attr)
@@ -723,8 +725,9 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
and oldRelativeVal != newRelativeVal:
# Float changed without pixel value changing, which
# means the pixel value needs to be updated
- log.debug('Updating %s #%s\'s relative widget: %s' % (
- self.name, self.compPos, attr))
+ log.debug(
+ 'Updating %s #%s\'s relative widget: %s',
+ self.name, self.compPos, attr)
with blockSignals(self._trackedWidgets[attr]):
self.updateRelativeWidgetMaximum(attr)
pixelVal = self.pixelValForAttr(attr, oldRelativeVal)
@@ -828,9 +831,8 @@ class ComponentUpdate(QtWidgets.QUndoCommand):
self.modifiedVals[attr] = val
else:
log.warning(
- '%s component settings changed at once. (%s)' % (
- len(self.modifiedVals), repr(self.modifiedVals)
- )
+ '%s component settings changed at once. (%s)',
+ len(self.modifiedVals), repr(self.modifiedVals)
)
def id(self):
diff --git a/src/core.py b/src/core.py
index 169716c..bfb8272 100644
--- a/src/core.py
+++ b/src/core.py
@@ -77,7 +77,8 @@ class Core:
if type(component) is int:
# create component using module index in self.modules
moduleIndex = int(component)
- log.debug('Creating new component from module #%s' % moduleIndex)
+ log.debug(
+ 'Creating new component from module #%s', str(moduleIndex))
component = self.modules[moduleIndex].Component(
moduleIndex, compPos, self
)
@@ -85,7 +86,7 @@ class Core:
else:
moduleIndex = -1
log.debug(
- 'Inserting previously-created %s component' % component.name)
+ 'Inserting previously-created %s component', component.name)
component._error.connect(
loader.videoThreadError
@@ -117,8 +118,9 @@ class Core:
self.componentListChanged()
def updateComponent(self, i):
- log.debug('Auto-updating %s #%s' % (
- self.selectedComponents[i], str(i)))
+ log.debug(
+ 'Auto-updating %s #%s',
+ self.selectedComponents[i], str(i))
self.selectedComponents[i].update(auto=True)
def moduleIndexFor(self, compName):
@@ -146,9 +148,8 @@ class Core:
)
except KeyError as e:
log.warning(
- '%s #%s\'s preset is missing value: %s' % (
- comp.name, str(compIndex), str(e)
- )
+ '%s #%s\'s preset is missing value: %s',
+ comp.name, str(compIndex), str(e)
)
self.savedPresets[presetName] = dict(saveValueStore)
@@ -266,7 +267,7 @@ class Core:
Returns dictionary with section names as the keys, each one
contains a list of tuples: (compName, version, compPresetDict)
'''
- log.debug('Parsing av file: %s' % filepath)
+ log.debug('Parsing av file: %s', filepath)
validSections = (
'Components',
'Settings',
@@ -385,7 +386,7 @@ class Core:
def createProjectFile(self, filepath, window=None):
'''Create a project file (.avp) using the current program state'''
- log.info('Creating %s' % filepath)
+ log.info('Creating %s', filepath)
settingsKeys = [
'componentDir',
'inputDir',
diff --git a/src/gui/actions.py b/src/gui/actions.py
index 1444569..f101bd7 100644
--- a/src/gui/actions.py
+++ b/src/gui/actions.py
@@ -3,6 +3,7 @@
'''
from PyQt5.QtWidgets import QUndoCommand
import os
+from copy import copy
from core import Core
@@ -132,7 +133,7 @@ class OpenPreset(QUndoCommand):
comp = self.parent.core.selectedComponents[compI]
self.store = comp.savePreset()
- self.store['preset'] = str(comp.currentPreset)
+ self.store['preset'] = copy(comp.currentPreset)
def redo(self):
self.parent._openPreset(self.presetName, self.compI)
diff --git a/src/gui/mainwindow.py b/src/gui/mainwindow.py
index 76c53af..833d2d1 100644
--- a/src/gui/mainwindow.py
+++ b/src/gui/mainwindow.py
@@ -387,30 +387,46 @@ class MainWindow(QtWidgets.QMainWindow):
@QtCore.pyqtSlot(int, dict)
def updateComponentTitle(self, pos, presetStore=False):
+ '''
+ Sets component title to modified or unmodified when given boolean.
+ If given a preset dict, compares it against the component to
+ determine if it is modified.
+ A component with no preset is always unmodified.
+ '''
if type(presetStore) is dict:
name = presetStore['preset']
if name is None or name not in self.core.savedPresets:
modified = False
else:
modified = (presetStore != self.core.savedPresets[name])
+ if modified:
+ log.verbose(
+ 'Differing values between presets: %s',
+ ", ".join([
+ '%s: %s' % item for item in presetStore.items()
+ if val != self.core.savedPresets[name][key]
+ ])
+ )
else:
modified = bool(presetStore)
if pos < 0:
pos = len(self.core.selectedComponents)-1
- name = str(self.core.selectedComponents[pos])
+ name = self.core.selectedComponents[pos].name
title = str(name)
if self.core.selectedComponents[pos].currentPreset:
title += ' - %s' % self.core.selectedComponents[pos].currentPreset
if modified:
title += '*'
if type(presetStore) is bool:
- log.debug('Forcing %s #%s\'s modified status to %s: %s' % (
+ log.debug(
+ 'Forcing %s #%s\'s modified status to %s: %s',
name, pos, modified, title
- ))
+ )
else:
- log.debug('Setting %s #%s\'s title: %s' % (
+ log.debug(
+ 'Setting %s #%s\'s title: %s',
name, pos, title
- ))
+ )
self.window.listWidget_componentList.item(pos).setText(title)
def updateCodecs(self):
diff --git a/src/gui/presetmanager.py b/src/gui/presetmanager.py
index befa7cd..2445760 100644
--- a/src/gui/presetmanager.py
+++ b/src/gui/presetmanager.py
@@ -210,7 +210,7 @@ class PresetManager(QtWidgets.QDialog):
def _openPreset(self, presetName, index):
selectedComponents = self.core.selectedComponents
- componentName = str(selectedComponents[index]).strip()
+ componentName = selectedComponents[index].name.strip()
version = selectedComponents[index].version
dirname = os.path.join(self.presetDir, componentName, str(version))
filepath = os.path.join(dirname, presetName)
diff --git a/src/toolkit/common.py b/src/toolkit/common.py
index 74143e8..95aeab3 100644
--- a/src/toolkit/common.py
+++ b/src/toolkit/common.py
@@ -29,15 +29,19 @@ class blockSignals:
)
def __enter__(self):
- log.verbose('Blocking signals for %s' % ", ".join([
- str(w.__class__.__name__) for w in self.widgets
- ]))
+ log.verbose(
+ 'Blocking signals for %s',
+ ", ".join([
+ str(w.__class__.__name__) for w in self.widgets
+ ])
+ )
self.oldStates = [w.signalsBlocked() for w in self.widgets]
for w in self.widgets:
w.blockSignals(True)
def __exit__(self, *args):
- log.verbose('Resetting blockSignals to %s' % sum(self.oldStates))
+ log.verbose(
+ 'Resetting blockSignals to %s', str(bool(sum(self.oldStates))))
for w, state in zip(self.widgets, self.oldStates):
w.blockSignals(state)
@@ -153,7 +157,7 @@ def connectWidget(widget, func):
elif type(widget) == QtWidgets.QComboBox:
widget.currentIndexChanged.connect(func)
else:
- log.warning('Failed to connect %s ' % str(widget.__class__.__name__))
+ log.warning('Failed to connect %s ', str(widget.__class__.__name__))
return False
return True
@@ -171,7 +175,7 @@ def setWidgetValue(widget, val):
elif type(widget) == QtWidgets.QComboBox:
widget.setCurrentIndex(val)
else:
- log.warning('Failed to set %s ' % str(widget.__class__.__name__))
+ log.warning('Failed to set %s ', str(widget.__class__.__name__))
return False
return True
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index 8fe9148..f007f90 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -93,7 +93,7 @@ class FfmpegVideo:
from component import ComponentError
logFilename = os.path.join(
core.Core.logDir, 'render_%s.log' % str(self.component.compPos))
- log.debug('Creating ffmpeg process (log at %s)' % logFilename)
+ log.debug('Creating ffmpeg process (log at %s)', logFilename)
with open(logFilename, 'w') as logf:
logf.write(" ".join(self.command) + '\n\n')
with open(logFilename, 'a') as logf:
diff --git a/src/video_thread.py b/src/video_thread.py
index 87fb9bd..823ac73 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -179,7 +179,7 @@ class Worker(QtCore.QObject):
for num, component in enumerate(reversed(self.components))
])
print('Loaded Components:', initText)
- log.info('Calling preFrameRender for %s' % initText)
+ log.info('Calling preFrameRender for %s', initText)
self.staticComponents = {}
for compNo, comp in enumerate(reversed(self.components)):
try:
@@ -221,12 +221,13 @@ class Worker(QtCore.QObject):
if self.canceled:
if canceledByComponent:
- log.error('Export cancelled by component #%s (%s): %s' % (
+ log.error(
+ 'Export cancelled by component #%s (%s): %s',
compNo,
comp.name,
'No message.' if comp.error() is None else (
comp.error() if type(comp.error()) is str
- else comp.error()[0])
+ else comp.error()[0]
)
)
self.cancelExport()
--
cgit v1.2.3
From be9eb9077b2234e6d91c78d70bb8e1d8347b03aa Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 20 Aug 2017 17:47:00 -0400
Subject: relative widgets scale properly when undoing at different resolutions
---
src/component.py | 81 +++++++++++++++++++++++++++++++++--------------
src/components/life.py | 2 +-
src/gui/mainwindow.py | 7 ++--
src/gui/preview_thread.py | 6 ++--
src/toolkit/common.py | 1 +
5 files changed, 68 insertions(+), 29 deletions(-)
diff --git a/src/component.py b/src/component.py
index 992a82e..0ff2fbd 100644
--- a/src/component.py
+++ b/src/component.py
@@ -40,7 +40,8 @@ class ComponentMetaclass(type(QtCore.QObject)):
def renderWrapper(func):
def renderWrapper(self, *args, **kwargs):
try:
- log.verbose('### %s #%s renders%s frame %s###',
+ log.verbose(
+ '### %s #%s renders%s frame %s###',
self.__class__.name, str(self.compPos),
'' if args else ' a preview',
'' if not args else '%s ' % args[0],
@@ -289,7 +290,6 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
self._lockedSize = None
# If set to a dict, values are used as basis to update relative widgets
self.oldAttrs = None
-
# Stop lengthy processes in response to this variable
self.canceled = False
@@ -386,7 +386,8 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
'''
self.parent = parent
self.settings = parent.settings
- log.verbose('Creating UI for %s #%s\'s widget',
+ log.verbose(
+ 'Creating UI for %s #%s\'s widget',
self.name, self.compPos
)
self.page = self.loadUi(self.__class__.ui)
@@ -530,6 +531,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
else:
# Normal tracked widget
setattr(self, attr, val)
+ log.verbose('Setting %s self.%s to %s' % (self.name, attr, val))
def setWidgetValues(self, attrDict):
'''
@@ -669,12 +671,22 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
def relativeWidgetAxis(func):
def relativeWidgetAxis(self, attr, *args, **kwargs):
+ hasVerticalWords = (
+ lambda attr:
+ 'height' in attr.lower() or
+ 'ypos' in attr.lower() or
+ attr == 'y'
+ )
if 'axis' not in kwargs:
axis = self.width
- if 'height' in attr.lower() \
- or 'ypos' in attr.lower() or attr == 'y':
+ if hasVerticalWords(attr):
axis = self.height
kwargs['axis'] = axis
+ if 'axis' in kwargs and type(kwargs['axis']) is tuple:
+ axis = kwargs['axis'][0]
+ if hasVerticalWords(attr):
+ axis = kwargs['axis'][1]
+ kwargs['axis'] = axis
return func(self, attr, *args, **kwargs)
return relativeWidgetAxis
@@ -682,7 +694,12 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
def pixelValForAttr(self, attr, val=None, **kwargs):
if val is None:
val = self._relativeValues[attr]
- return math.ceil(kwargs['axis'] * val)
+ result = math.ceil(kwargs['axis'] * val)
+ log.verbose(
+ 'Converting %s: f%s to px%s using axis %s',
+ attr, val, result, kwargs['axis']
+ )
+ return result
@relativeWidgetAxis
def floatValForAttr(self, attr, val=None, **kwargs):
@@ -693,7 +710,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
def setRelativeWidget(self, attr, floatVal):
'''Set a relative widget using a float'''
pixelVal = self.pixelValForAttr(attr, floatVal)
- with blockSignals(self._allWidgets):
+ with blockSignals(self._trackedWidgets[attr]):
self._trackedWidgets[attr].setValue(pixelVal)
self.update(auto=True)
@@ -707,15 +724,15 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
log.verbose('Using nonstandard oldAttr for %s', attr)
return self.oldAttrs[attr]
else:
- return getattr(self, attr)
+ try:
+ return getattr(self, attr)
+ except AttributeError:
+ log.info('Using visible values instead of attrs')
+ return self._trackedWidgets[attr].value()
def updateRelativeWidget(self, attr):
'''Called by _preUpdate() for each relativeWidget before each update'''
- try:
- oldUserValue = self.getOldAttr(attr)
- except (AttributeError, KeyError):
- log.info('Using visible values as basis for relative widgets')
- oldUserValue = self._trackedWidgets[attr].value()
+ oldUserValue = self.getOldAttr(attr)
newUserValue = self._trackedWidgets[attr].value()
newRelativeVal = self.floatValForAttr(attr, newUserValue)
@@ -808,17 +825,25 @@ class ComponentUpdate(QtWidgets.QUndoCommand):
)
)
self.undone = False
+ self.res = (int(parent.width), int(parent.height))
self.parent = parent
self.oldWidgetVals = {
attr: copy(val)
+ if attr not in self.parent._relativeWidgets
+ else self.parent.floatValForAttr(attr, val, axis=self.res)
for attr, val in oldWidgetVals.items()
if attr in modifiedVals
}
- self.modifiedVals = modifiedVals
+ self.modifiedVals = {
+ attr: val
+ if attr not in self.parent._relativeWidgets
+ else self.parent.floatValForAttr(attr, val, axis=self.res)
+ for attr, val in modifiedVals.items()
+ }
# Because relative widgets change themselves every update based on
# their previous value, we must store ALL their values in case of undo
- self.redoRelativeWidgetVals = {
+ self.relativeWidgetValsAfterUndo = {
attr: copy(getattr(self.parent, attr))
for attr in self.parent._relativeWidgets
}
@@ -843,17 +868,28 @@ class ComponentUpdate(QtWidgets.QUndoCommand):
self.modifiedVals.update(other.modifiedVals)
return True
+ def setWidgetValues(self, attrDict):
+ '''
+ Mask the component's usual method to handle our
+ relative widgets in case the resolution has changed.
+ '''
+ newAttrDict = {
+ attr: val if attr not in self.parent._relativeWidgets
+ else self.parent.pixelValForAttr(attr, val)
+ for attr, val in attrDict.items()
+ }
+ self.parent.setWidgetValues(newAttrDict)
+
def redo(self):
if self.undone:
log.debug('Redoing component update')
- self.parent.setWidgetValues(self.modifiedVals)
- self.parent.setAttrs(self.modifiedVals)
- if self.undone:
- self.parent.oldAttrs = self.redoRelativeWidgetVals
+ self.parent.oldAttrs = self.relativeWidgetValsAfterUndo
+ self.setWidgetValues(self.modifiedVals)
self.parent.update(auto=True)
self.parent.oldAttrs = None
else:
- self.undoRelativeWidgetVals = {
+ self.parent.setAttrs(self.modifiedVals)
+ self.relativeWidgetValsAfterRedo = {
attr: copy(getattr(self.parent, attr))
for attr in self.parent._relativeWidgets
}
@@ -862,8 +898,7 @@ class ComponentUpdate(QtWidgets.QUndoCommand):
def undo(self):
log.debug('Undoing component update')
self.undone = True
- self.parent.oldAttrs = self.undoRelativeWidgetVals
- self.parent.setWidgetValues(self.oldWidgetVals)
- self.parent.setAttrs(self.oldWidgetVals)
+ self.parent.oldAttrs = self.relativeWidgetValsAfterRedo
+ self.setWidgetValues(self.oldWidgetVals)
self.parent.update(auto=True)
self.parent.oldAttrs = None
diff --git a/src/components/life.py b/src/components/life.py
index 76d2c5f..5d00987 100644
--- a/src/components/life.py
+++ b/src/components/life.py
@@ -70,7 +70,7 @@ class Component(Component):
elif d == 3:
newGrid = newGrid(1, 0)
self.startingGrid = newGrid
- self.sendUpdateSignal()
+ self._sendUpdateSignal()
def update(self):
self.updateGridSize()
diff --git a/src/gui/mainwindow.py b/src/gui/mainwindow.py
index 833d2d1..2841896 100644
--- a/src/gui/mainwindow.py
+++ b/src/gui/mainwindow.py
@@ -88,10 +88,13 @@ class MainWindow(QtWidgets.QMainWindow):
self.previewWorker.imageCreated.connect(self.showPreviewImage)
self.previewThread.start()
- log.debug('Starting preview timer')
+ timeout = 500
+ log.debug(
+ 'Preview timer set to trigger when idle for %sms' % str(timeout)
+ )
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.processTask.emit)
- self.timer.start(500)
+ self.timer.start(timeout)
# Begin decorating the window and connecting events
self.window.installEventFilter(self)
diff --git a/src/gui/preview_thread.py b/src/gui/preview_thread.py
index 33a9e7a..d3e0581 100644
--- a/src/gui/preview_thread.py
+++ b/src/gui/preview_thread.py
@@ -45,8 +45,6 @@ class Worker(QtCore.QObject):
@pyqtSlot()
def process(self):
- width = int(self.settings.value('outputWidth'))
- height = int(self.settings.value('outputHeight'))
try:
nextPreviewInformation = self.queue.get(block=False)
while self.queue.qsize() >= 2:
@@ -54,12 +52,14 @@ class Worker(QtCore.QObject):
self.queue.get(block=False)
except Empty:
continue
+ width = int(self.settings.value('outputWidth'))
+ height = int(self.settings.value('outputHeight'))
if self.background.width != width \
or self.background.height != height:
self.background = Checkerboard(width, height)
frame = self.background.copy()
- log.debug('Creating new preview frame')
+ log.info('Creating new preview frame')
components = nextPreviewInformation["components"]
for component in reversed(components):
try:
diff --git a/src/toolkit/common.py b/src/toolkit/common.py
index 95aeab3..2e800eb 100644
--- a/src/toolkit/common.py
+++ b/src/toolkit/common.py
@@ -84,6 +84,7 @@ def appendUppercase(lst):
lst.append(form.upper())
return lst
+
def pipeWrapper(func):
'''A decorator to insert proper kwargs into Popen objects.'''
def pipeWrapper(commandList, **kwargs):
--
cgit v1.2.3
From 6bf8a553d6170e0ca6e7d2002e46ae327a6e5e81 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 20 Aug 2017 18:36:43 -0400
Subject: don't merge undos when setting text with a button
plus changes to life.py for pep8 compliance
---
src/component.py | 5 ++++-
src/components/image.py | 2 ++
src/components/life.py | 46 +++++++++++++++++++++++++++-------------------
src/components/sound.py | 2 ++
src/components/video.py | 2 ++
src/gui/actions.py | 1 -
6 files changed, 37 insertions(+), 21 deletions(-)
diff --git a/src/component.py b/src/component.py
index 0ff2fbd..1f55a19 100644
--- a/src/component.py
+++ b/src/component.py
@@ -285,6 +285,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
# LOCKING VARIABLES
self.openingPreset = False
+ self.mergeUndo = True
self._lockedProperties = None
self._lockedError = None
self._lockedSize = None
@@ -587,10 +588,12 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
if kwarg == 'colorWidgets':
def makeColorFunc(attr):
def pickColor_():
+ self.mergeUndo = False
self.pickColor(
self._trackedWidgets[attr],
self._colorWidgets[attr]
)
+ self.mergeUndo = True
return pickColor_
self._colorFuncs = {
attr: makeColorFunc(attr) for attr in kwargs[kwarg]
@@ -850,7 +853,7 @@ class ComponentUpdate(QtWidgets.QUndoCommand):
# Determine if this update is mergeable
self.id_ = -1
- if len(self.modifiedVals) == 1:
+ if len(self.modifiedVals) == 1 and self.parent.mergeUndo:
attr, val = self.modifiedVals.popitem()
self.id_ = sum([ord(letter) for letter in attr[-14:]])
self.modifiedVals[attr] = val
diff --git a/src/components/image.py b/src/components/image.py
index c57b69c..dd363bf 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -83,7 +83,9 @@ class Component(Component):
"Image Files (%s)" % " ".join(self.core.imageFormats))
if filename:
self.settings.setValue("componentDir", os.path.dirname(filename))
+ self.mergeUndo = False
self.page.lineEdit_image.setText(filename)
+ self.mergeUndo = True
def command(self, arg):
if '=' in arg:
diff --git a/src/components/life.py b/src/components/life.py
index 5d00987..d4a455d 100644
--- a/src/components/life.py
+++ b/src/components/life.py
@@ -35,6 +35,7 @@ class Component(Component):
self.page.toolButton_left,
self.page.toolButton_right,
)
+
def shiftFunc(i):
def shift():
self.shiftGrid(i)
@@ -52,7 +53,9 @@ class Component(Component):
"Image Files (%s)" % " ".join(self.core.imageFormats))
if filename:
self.settings.setValue("componentDir", os.path.dirname(filename))
+ self.mergeUndo = False
self.page.lineEdit_image.setText(filename)
+ self.mergeUndo = True
def shiftGrid(self, d):
def newGrid(Xchange, Ychange):
@@ -197,7 +200,7 @@ class Component(Component):
# Circle
if shape == 'circle':
drawer.ellipse(outlineShape, fill=self.color)
- drawer.ellipse(smallerShape, fill=(0,0,0,0))
+ drawer.ellipse(smallerShape, fill=(0, 0, 0, 0))
# Lilypad
elif shape == 'lilypad':
@@ -207,9 +210,9 @@ class Component(Component):
elif shape == 'pac-man':
drawer.pieslice(outlineShape, 35, 320, fill=self.color)
- hX, hY = scale(50, self.pxWidth, self.pxHeight, int) # halfline
- tX, tY = scale(33, self.pxWidth, self.pxHeight, int) # thirdline
- qX, qY = scale(20, self.pxWidth, self.pxHeight, int) # quarterline
+ hX, hY = scale(50, self.pxWidth, self.pxHeight, int) # halfline
+ tX, tY = scale(33, self.pxWidth, self.pxHeight, int) # thirdline
+ qX, qY = scale(20, self.pxWidth, self.pxHeight, int) # quarterline
# Path
if shape == 'path':
@@ -245,19 +248,19 @@ class Component(Component):
sect = (
(drawPtX, drawPtY + hY),
(drawPtX + self.pxWidth,
- drawPtY + self.pxHeight)
+ drawPtY + self.pxHeight)
)
elif direction == 'left':
sect = (
(drawPtX, drawPtY),
(drawPtX + hX,
- drawPtY + self.pxHeight)
+ drawPtY + self.pxHeight)
)
elif direction == 'right':
sect = (
(drawPtX + hX, drawPtY),
(drawPtX + self.pxWidth,
- drawPtY + self.pxHeight)
+ drawPtY + self.pxHeight)
)
drawer.rectangle(sect, fill=self.color)
@@ -287,20 +290,25 @@ class Component(Component):
# Peace
elif shape == 'peace':
- line = (
- (drawPtX + hX - int(tenthX / 2), drawPtY + int(tenthY / 2)),
+ line = ((
+ drawPtX + hX - int(tenthX / 2), drawPtY + int(tenthY / 2)),
(drawPtX + hX + int(tenthX / 2),
- drawPtY + self.pxHeight - int(tenthY / 2))
+ drawPtY + self.pxHeight - int(tenthY / 2))
)
drawer.ellipse(outlineShape, fill=self.color)
- drawer.ellipse(smallerShape, fill=(0,0,0,0))
+ drawer.ellipse(smallerShape, fill=(0, 0, 0, 0))
drawer.rectangle(line, fill=self.color)
- slantLine = lambda difference: (
- ((drawPtX + difference),
- (drawPtY + self.pxHeight - qY)),
- ((drawPtX + hX),
- (drawPtY + hY)),
- )
+
+ def slantLine(difference):
+ return (
+ (drawPtX + difference),
+ (drawPtY + self.pxHeight - qY)
+ ),
+ (
+ (drawPtX + hX),
+ (drawPtY + hY)
+ )
+
drawer.line(
slantLine(qX),
fill=self.color,
@@ -337,13 +345,13 @@ class Component(Component):
for x in range(self.pxWidth, self.width, self.pxWidth):
drawer.rectangle(
((x, 0),
- (x + w, self.height)),
+ (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)),
+ (self.width, y + h)),
fill=self.color,
)
diff --git a/src/components/sound.py b/src/components/sound.py
index b86f40c..18d2a65 100644
--- a/src/components/sound.py
+++ b/src/components/sound.py
@@ -52,7 +52,9 @@ class Component(Component):
"Audio Files (%s)" % " ".join(self.core.audioFormats))
if filename:
self.settings.setValue("componentDir", os.path.dirname(filename))
+ self.mergeUndo = False
self.page.lineEdit_sound.setText(filename)
+ self.mergeUndo = True
def commandHelp(self):
print('Path to audio file:\n path=/filepath/to/sound.ogg')
diff --git a/src/components/video.py b/src/components/video.py
index 9c0d608..e6486ea 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -117,7 +117,9 @@ class Component(Component):
)
if filename:
self.settings.setValue("componentDir", os.path.dirname(filename))
+ self.mergeUndo = False
self.page.lineEdit_video.setText(filename)
+ self.mergeUndo = True
def getPreviewFrame(self, width, height):
if not self.videoPath or not os.path.exists(self.videoPath):
diff --git a/src/gui/actions.py b/src/gui/actions.py
index f101bd7..ebd9702 100644
--- a/src/gui/actions.py
+++ b/src/gui/actions.py
@@ -32,7 +32,6 @@ class AddComponent(QUndoCommand):
self.parent.core.insertComponent(
self.compI, self.comp, self.parent)
-
def undo(self):
self.comp = self.parent.core.selectedComponents[self.compI]
self.parent._removeComponent(self.compI)
--
cgit v1.2.3
From 9d9c4076ac1dfccdd1a753d137d87bcf5f179e3b Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 20 Aug 2017 22:04:57 -0400
Subject: added undo button to GUI
with icons that theoretically should look ok cross-platform
---
src/component.py | 2 +-
src/gui/actions.py | 14 +++++++-------
src/gui/mainwindow.py | 36 ++++++++++++++++++++++++++++++++++++
src/gui/mainwindow.ui | 7 +++++++
4 files changed, 51 insertions(+), 8 deletions(-)
diff --git a/src/component.py b/src/component.py
index 1f55a19..35fc717 100644
--- a/src/component.py
+++ b/src/component.py
@@ -823,7 +823,7 @@ class ComponentUpdate(QtWidgets.QUndoCommand):
'''Command object for making a component action undoable'''
def __init__(self, parent, oldWidgetVals, modifiedVals):
super().__init__(
- 'Changed %s component #%s' % (
+ 'change %s component #%s' % (
parent.name, parent.compPos
)
)
diff --git a/src/gui/actions.py b/src/gui/actions.py
index ebd9702..8e867b9 100644
--- a/src/gui/actions.py
+++ b/src/gui/actions.py
@@ -15,7 +15,7 @@ from core import Core
class AddComponent(QUndoCommand):
def __init__(self, parent, compI, moduleI):
super().__init__(
- "New %s component" %
+ "create new %s component" %
parent.core.modules[moduleI].Component.name
)
self.parent = parent
@@ -39,7 +39,7 @@ class AddComponent(QUndoCommand):
class RemoveComponent(QUndoCommand):
def __init__(self, parent, selectedRows):
- super().__init__('Remove component')
+ super().__init__('remove component')
self.parent = parent
componentList = self.parent.window.listWidget_componentList
self.selectedRows = [
@@ -63,7 +63,7 @@ class RemoveComponent(QUndoCommand):
class MoveComponent(QUndoCommand):
def __init__(self, parent, row, newRow, tag):
- super().__init__("Move component %s" % tag)
+ super().__init__("move component %s" % tag)
self.parent = parent
self.row = row
self.newRow = newRow
@@ -107,7 +107,7 @@ class MoveComponent(QUndoCommand):
class ClearPreset(QUndoCommand):
def __init__(self, parent, compI):
- super().__init__("Clear preset")
+ super().__init__("clear preset")
self.parent = parent
self.compI = compI
self.component = self.parent.core.selectedComponents[compI]
@@ -125,7 +125,7 @@ class ClearPreset(QUndoCommand):
class OpenPreset(QUndoCommand):
def __init__(self, parent, presetName, compI):
- super().__init__("Open %s preset" % presetName)
+ super().__init__("open %s preset" % presetName)
self.parent = parent
self.presetName = presetName
self.compI = compI
@@ -145,7 +145,7 @@ class OpenPreset(QUndoCommand):
class RenamePreset(QUndoCommand):
def __init__(self, parent, path, oldName, newName):
- super().__init__('Rename preset')
+ super().__init__('rename preset')
self.parent = parent
self.path = path
self.oldName = oldName
@@ -167,7 +167,7 @@ class DeletePreset(QUndoCommand):
)
self.store = self.parent.core.getPreset(self.path)
self.presetName = self.store['preset']
- super().__init__('Delete %s preset (%s)' % (self.presetName, compName))
+ super().__init__('delete %s preset (%s)' % (self.presetName, compName))
self.loadedPresets = [
i for i, comp in enumerate(self.parent.core.selectedComponents)
if self.presetName == str(comp.currentPreset)
diff --git a/src/gui/mainwindow.py b/src/gui/mainwindow.py
index 2841896..3b204b7 100644
--- a/src/gui/mainwindow.py
+++ b/src/gui/mainwindow.py
@@ -100,6 +100,42 @@ class MainWindow(QtWidgets.QMainWindow):
self.window.installEventFilter(self)
componentList = self.window.listWidget_componentList
+ style = window.pushButton_undo.style()
+ undoButton = window.pushButton_undo
+ undoButton.setIcon(
+ style.standardIcon(QtWidgets.QStyle.SP_FileDialogBack)
+ )
+ undoButton.clicked.connect(self.undoStack.undo)
+ undoButton.setEnabled(False)
+ self.undoStack.cleanChanged.connect(
+ lambda change: undoButton.setEnabled(self.undoStack.count())
+ )
+ self.undoMenu = QMenu()
+ self.undoMenu.addAction(
+ self.undoStack.createUndoAction(self)
+ )
+ self.undoMenu.addAction(
+ self.undoStack.createRedoAction(self)
+ )
+ action = self.undoMenu.addAction('Show History...')
+ action.triggered.connect(
+ lambda _: self.showUndoStack()
+ )
+ undoButton.setMenu(self.undoMenu)
+
+ style = window.pushButton_listMoveUp.style()
+ window.pushButton_listMoveUp.setIcon(
+ style.standardIcon(QtWidgets.QStyle.SP_ArrowUp)
+ )
+ style = window.pushButton_listMoveDown.style()
+ window.pushButton_listMoveDown.setIcon(
+ style.standardIcon(QtWidgets.QStyle.SP_ArrowDown)
+ )
+ style = window.pushButton_removeComponent.style()
+ window.pushButton_removeComponent.setIcon(
+ style.standardIcon(QtWidgets.QStyle.SP_DialogDiscardButton)
+ )
+
if sys.platform == 'darwin':
log.debug(
'Darwin detected: showing progress label below progress bar')
diff --git a/src/gui/mainwindow.ui b/src/gui/mainwindow.ui
index b43d375..cd8454d 100644
--- a/src/gui/mainwindow.ui
+++ b/src/gui/mainwindow.ui
@@ -110,6 +110,13 @@
QLayout::SetMinimumSize
+
-
+
+
+ Undo
+
+
+
-
--
cgit v1.2.3
From 62e2ef18a3a31c15f88a96f07b2bc587808f5ad5 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Mon, 21 Aug 2017 07:06:12 -0400
Subject: potential dataDir paths in comments for future reference
---
src/core.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/core.py b/src/core.py
index bfb8272..784f3b8 100644
--- a/src/core.py
+++ b/src/core.py
@@ -14,7 +14,7 @@ import toolkit
log = logging.getLogger('AVP.Core')
STDOUT_LOGLVL = logging.VERBOSE
-FILE_LOGLVL = logging.DEBUG
+FILE_LOGLVL = logging.VERBOSE
class Core:
@@ -460,6 +460,9 @@ class Core:
dataDir = QtCore.QStandardPaths.writableLocation(
QtCore.QStandardPaths.AppConfigLocation
)
+ # Windows: C:/Users//AppData/Local/audio-visualizer
+ # macOS: ~/Library/Preferences/audio-visualizer
+ # Linux: ~/.config/audio-visualizer
with open(os.path.join(wd, 'encoder-options.json')) as json_file:
encoderOptions = json.load(json_file)
--
cgit v1.2.3
From 85d3b779d07ad92b0f540ea52185777c3c3f5e48 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 26 Aug 2017 21:23:44 -0400
Subject: fixed too-large Color sizes, fixed a redoing bug, rm pointless things
and now Ctrl+Alt+Shift+A gives a bunch of debug info
---
src/component.py | 30 +++++++++++------------
src/components/color.py | 2 +-
src/components/color.ui | 4 ++--
src/components/text.py | 13 ++++++----
src/core.py | 8 +++++--
src/gui/mainwindow.py | 63 +++++++++++++++++++++++++++++--------------------
src/gui/preview_win.py | 1 +
src/main.py | 5 ----
src/toolkit/ffmpeg.py | 2 +-
src/toolkit/frame.py | 3 ---
10 files changed, 72 insertions(+), 59 deletions(-)
diff --git a/src/component.py b/src/component.py
index 35fc717..de4b6a7 100644
--- a/src/component.py
+++ b/src/component.py
@@ -41,10 +41,8 @@ class ComponentMetaclass(type(QtCore.QObject)):
def renderWrapper(self, *args, **kwargs):
try:
log.verbose(
- '### %s #%s renders%s frame %s###',
+ '### %s #%s renders a preview frame ###',
self.__class__.name, str(self.compPos),
- '' if args else ' a preview',
- '' if not args else '%s ' % args[0],
)
return func(self, *args, **kwargs)
except Exception as e:
@@ -198,8 +196,8 @@ class ComponentMetaclass(type(QtCore.QObject)):
'names', # Class methods
'error', 'audio', 'properties', # Properties
'preFrameRender', 'previewRender',
- 'frameRender', 'command',
- 'loadPreset', 'update', 'widget',
+ 'loadPreset', 'command',
+ 'update', 'widget',
)
# Auto-decorate methods
@@ -212,7 +210,7 @@ class ComponentMetaclass(type(QtCore.QObject)):
attrs[key] = property(attrs[key])
elif key == 'command':
attrs[key] = cls.commandWrapper(attrs[key])
- elif key in ('previewRender', 'frameRender'):
+ elif key == 'previewRender':
attrs[key] = cls.renderWrapper(attrs[key])
elif key == 'preFrameRender':
attrs[key] = cls.initializationWrapper(attrs[key])
@@ -298,16 +296,19 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
return self.__class__.name
def __repr__(self):
+ import pprint
try:
preset = self.savePreset()
except Exception as e:
preset = '%s occurred while saving preset' % str(e)
return (
- 'Component(%s, %s, Core)\n'
- 'Name: %s v%s\n Preset: %s' % (
+ 'Component(module %s, pos %s) (%s)\n'
+ 'Name: %s v%s\nPreset: %s' % (
self.moduleIndex, self.compPos,
- self.__class__.name, str(self.__class__.version), preset
+ object.__repr__(self),
+ self.__class__.name, str(self.__class__.version),
+ pprint.pformat(preset)
)
)
@@ -886,12 +887,11 @@ class ComponentUpdate(QtWidgets.QUndoCommand):
def redo(self):
if self.undone:
log.debug('Redoing component update')
- self.parent.oldAttrs = self.relativeWidgetValsAfterUndo
- self.setWidgetValues(self.modifiedVals)
- self.parent.update(auto=True)
- self.parent.oldAttrs = None
- else:
- self.parent.setAttrs(self.modifiedVals)
+ self.parent.oldAttrs = self.relativeWidgetValsAfterUndo
+ self.setWidgetValues(self.modifiedVals)
+ self.parent.update(auto=True)
+ self.parent.oldAttrs = None
+ if not self.undone:
self.relativeWidgetValsAfterRedo = {
attr: copy(getattr(self.parent, attr))
for attr in self.parent._relativeWidgets
diff --git a/src/components/color.py b/src/components/color.py
index a55aa10..7d4f86d 100644
--- a/src/components/color.py
+++ b/src/components/color.py
@@ -102,7 +102,7 @@ class Component(Component):
# Return a solid image at x, y
if self.fillType == 0:
frame = BlankFrame(width, height)
- image = Image.new("RGBA", shapeSize, (r, g, b, 255))
+ image = FloodFrame(self.sizeWidth, self.sizeHeight, (r, g, b, 255))
frame.paste(image, box=(self.x, self.y))
return frame
diff --git a/src/components/color.ui b/src/components/color.ui
index 1865e60..c1713fb 100644
--- a/src/components/color.ui
+++ b/src/components/color.ui
@@ -204,7 +204,7 @@
0
- 999999999
+ 19200
0
@@ -239,7 +239,7 @@
- 999999999
+ 10800
diff --git a/src/components/text.py b/src/components/text.py
index 92f0599..32a108e 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -2,10 +2,13 @@ from PIL import ImageEnhance, ImageFilter, ImageChops
from PyQt5.QtGui import QColor, QFont
from PyQt5 import QtGui, QtCore, QtWidgets
import os
+import logging
from component import Component
from toolkit.frame import FramePainter, PaintColor
+log = logging.getLogger('AVP.Components.Text')
+
class Component(Component):
name = 'Title Text'
@@ -76,16 +79,15 @@ class Component(Component):
def getXY(self):
'''Returns true x, y after considering alignment settings'''
fm = QtGui.QFontMetrics(self.titleFont)
- if self.alignment == 0: # Left
- x = int(self.xPosition)
+ x = self.pixelValForAttr('xPosition')
if self.alignment == 1: # Middle
offset = int(fm.width(self.title)/2)
- x = self.xPosition - offset
-
+ x -= offset
if self.alignment == 2: # Right
offset = fm.width(self.title)
- x = self.xPosition - offset
+ x -= offset
+
return x, self.yPosition
def loadPreset(self, pr, *args):
@@ -137,6 +139,7 @@ class Component(Component):
image = FramePainter(width, height)
x, y = self.getXY()
+ log.debug('Text position translates to %s, %s', x, y)
if self.stroke > 0:
outliner = QtGui.QPainterPathStroker()
outliner.setWidth(self.stroke)
diff --git a/src/core.py b/src/core.py
index 784f3b8..b9e2335 100644
--- a/src/core.py
+++ b/src/core.py
@@ -14,7 +14,7 @@ import toolkit
log = logging.getLogger('AVP.Core')
STDOUT_LOGLVL = logging.VERBOSE
-FILE_LOGLVL = logging.VERBOSE
+FILE_LOGLVL = logging.DEBUG
class Core:
@@ -32,6 +32,11 @@ class Core:
self.savedPresets = {} # copies of presets to detect modification
self.openingProject = False
+ def __repr__(self):
+ return "\n=~=~=~=\n".join(
+ [repr(comp) for comp in self.selectedComponents]
+ )
+
def importComponents(self):
def findComponents():
for f in os.listdir(Core.componentsPath):
@@ -482,7 +487,6 @@ class Core:
'854x480',
],
'FFMPEG_BIN': findFfmpeg(),
- 'windowHasFocus': False,
'canceled': False,
}
diff --git a/src/gui/mainwindow.py b/src/gui/mainwindow.py
index 3b204b7..d7fde5c 100644
--- a/src/gui/mainwindow.py
+++ b/src/gui/mainwindow.py
@@ -11,6 +11,7 @@ from queue import Queue
import sys
import os
import signal
+import atexit
import filecmp
import time
import logging
@@ -49,6 +50,13 @@ class MainWindow(QtWidgets.QMainWindow):
self.window = window
self.core = Core()
Core.mode = 'GUI'
+ # widgets of component settings
+ self.pages = []
+ self.lastAutosave = time.time()
+ # list of previous five autosave times, used to reduce update spam
+ self.autosaveTimes = []
+ self.autosaveCooldown = 0.2
+ self.encoding = False
# Find settings created by Core object
self.dataDir = Core.dataDir
@@ -56,19 +64,16 @@ class MainWindow(QtWidgets.QMainWindow):
self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
self.settings = Core.settings
+ # Register clean-up functions
+ signal.signal(signal.SIGINT, self.terminate)
+ atexit.register(self.cleanUp)
+
# Create stack of undoable user actions
self.undoStack = QtWidgets.QUndoStack(self)
undoLimit = self.settings.value("pref_undoLimit")
self.undoStack.setUndoLimit(undoLimit)
- # widgets of component settings
- self.pages = []
- self.lastAutosave = time.time()
- # list of previous five autosave times, used to reduce update spam
- self.autosaveTimes = []
- self.autosaveCooldown = 0.2
- self.encoding = False
-
+ # Create Preset Manager
self.presetManager = PresetManager(
uic.loadUi(
os.path.join(Core.wd, 'gui', 'presetmanager.ui')), self)
@@ -97,7 +102,6 @@ class MainWindow(QtWidgets.QMainWindow):
self.timer.start(timeout)
# Begin decorating the window and connecting events
- self.window.installEventFilter(self)
componentList = self.window.listWidget_componentList
style = window.pushButton_undo.style()
@@ -391,24 +395,41 @@ class MainWindow(QtWidgets.QMainWindow):
activated=lambda: self.moveComponent('bottom')
)
- # Debug Hotkeys
QtWidgets.QShortcut(
- "Ctrl+Alt+Shift+R", self.window, self.drawPreview
+ "Ctrl+Shift+F", self.window, self.showFfmpegCommand
)
QtWidgets.QShortcut(
- "Ctrl+Alt+Shift+F", self.window, self.showFfmpegCommand
+ "Ctrl+Shift+U", self.window, self.showUndoStack
)
- QtWidgets.QShortcut(
- "Ctrl+Alt+Shift+U", self.window, self.showUndoStack
+
+ if log.isEnabledFor(logging.DEBUG):
+ QtWidgets.QShortcut(
+ "Ctrl+Alt+Shift+R", self.window, self.drawPreview
+ )
+ QtWidgets.QShortcut(
+ "Ctrl+Alt+Shift+A", self.window, lambda: log.debug(repr(self))
+ )
+
+ def __repr__(self):
+ return (
+ '\n%s\n'
+ '#####\n'
+ 'Preview thread is %s\n' % (
+ repr(self.core),
+ 'live' if self.previewThread.isRunning() else 'dead',
+ )
)
- @QtCore.pyqtSlot()
def cleanUp(self, *args):
log.info('Ending the preview thread')
self.timer.stop()
self.previewThread.quit()
self.previewThread.wait()
+ def terminate(self, *args):
+ self.cleanUp()
+ sys.exit(0)
+
@disableWhenOpeningProject
def updateWindowTitle(self):
appName = 'Audio Visualizer'
@@ -542,7 +563,7 @@ class MainWindow(QtWidgets.QMainWindow):
return True
except FileNotFoundError:
log.error(
- 'Project file couldn\'t be located:', self.currentProject)
+ 'Project file couldn\'t be located: %s', self.currentProject)
return identical
return False
@@ -639,6 +660,7 @@ class MainWindow(QtWidgets.QMainWindow):
detail=detail,
icon='Critical',
)
+ log.info('%s', repr(self))
def changeEncodingStatus(self, status):
self.encoding = status
@@ -1017,12 +1039,3 @@ class MainWindow(QtWidgets.QMainWindow):
self.menu.move(parentPosition + QPos)
self.menu.show()
-
- def eventFilter(self, object, event):
- if event.type() == QtCore.QEvent.WindowActivate \
- or event.type() == QtCore.QEvent.FocusIn:
- Core.windowHasFocus = True
- elif event.type() == QtCore.QEvent.WindowDeactivate \
- or event.type() == QtCore.QEvent.FocusOut:
- Core.windowHasFocus = False
- return False
diff --git a/src/gui/preview_win.py b/src/gui/preview_win.py
index c6b9a32..49a22eb 100644
--- a/src/gui/preview_win.py
+++ b/src/gui/preview_win.py
@@ -60,3 +60,4 @@ class PreviewWindow(QtWidgets.QLabel):
icon='Critical',
parent=self
)
+ log.info('%', repr(self.parent))
diff --git a/src/main.py b/src/main.py
index 6d18af3..f767de1 100644
--- a/src/main.py
+++ b/src/main.py
@@ -36,8 +36,6 @@ def main():
elif mode == 'GUI':
from gui.mainwindow import MainWindow
- import atexit
- import signal
window = uic.loadUi(os.path.join(wd, "gui", "mainwindow.ui"))
# window.adjustSize()
@@ -56,9 +54,6 @@ def main():
log.debug("Finished creating main window")
window.raise_()
- signal.signal(signal.SIGINT, main.cleanUp)
- atexit.register(main.cleanUp)
-
sys.exit(app.exec_())
if __name__ == "__main__":
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index f007f90..a77831e 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -157,7 +157,7 @@ def findFfmpeg():
['ffmpeg', '-version'], stderr=f
)
return "ffmpeg"
- except subprocess.CalledProcessError:
+ except (subprocess.CalledProcessError, FileNotFoundError):
return "avconv"
diff --git a/src/toolkit/frame.py b/src/toolkit/frame.py
index 2104978..aefb55f 100644
--- a/src/toolkit/frame.py
+++ b/src/toolkit/frame.py
@@ -21,7 +21,6 @@ class FramePainter(QtGui.QPainter):
Pillow image with finalize()
'''
def __init__(self, width, height):
- log.verbose('Creating new FramePainter')
image = BlankFrame(width, height)
self.image = QtGui.QImage(ImageQt(image))
super().__init__(self.image)
@@ -78,8 +77,6 @@ def defaultSize(framefunc):
def FloodFrame(width, height, RgbaTuple):
- log.verbose('Creating new %s*%s %s flood frame' % (
- width, height, RgbaTuple))
return Image.new("RGBA", (width, height), RgbaTuple)
--
cgit v1.2.3
From e8a7b18293768497df272bb4cb64b678d57f58da Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 27 Aug 2017 09:53:18 -0400
Subject: disallow suspiciously enormous floats
this stops a bad project file from crashing my computer...
---
src/component.py | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/src/component.py b/src/component.py
index de4b6a7..01c1d06 100644
--- a/src/component.py
+++ b/src/component.py
@@ -390,7 +390,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
self.settings = parent.settings
log.verbose(
'Creating UI for %s #%s\'s widget',
- self.name, self.compPos
+ self.__class__.name, self.compPos
)
self.page = self.loadUi(self.__class__.ui)
@@ -533,7 +533,8 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
else:
# Normal tracked widget
setattr(self, attr, val)
- log.verbose('Setting %s self.%s to %s' % (self.name, attr, val))
+ log.verbose('Setting %s self.%s to %s' % (
+ self.__class__.name, attr, val))
def setWidgetValues(self, attrDict):
'''
@@ -698,6 +699,12 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
def pixelValForAttr(self, attr, val=None, **kwargs):
if val is None:
val = self._relativeValues[attr]
+ if val > 50.0:
+ log.warning(
+ '%s #%s attempted to set %s to dangerously high number %s',
+ self.__class__.name, self.compPos, attr, val
+ )
+ val = 50.0
result = math.ceil(kwargs['axis'] * val)
log.verbose(
'Converting %s: f%s to px%s using axis %s',
@@ -748,7 +755,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
# means the pixel value needs to be updated
log.debug(
'Updating %s #%s\'s relative widget: %s',
- self.name, self.compPos, attr)
+ self.__class__.name, self.compPos, attr)
with blockSignals(self._trackedWidgets[attr]):
self.updateRelativeWidgetMaximum(attr)
pixelVal = self.pixelValForAttr(attr, oldRelativeVal)
--
cgit v1.2.3
From 4a310ffb2870babf6774da843cad271f8a477bcc Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 27 Aug 2017 12:10:21 -0400
Subject: file logging can be turned completely off
and various changes to log levels and messages everywhere
---
src/component.py | 22 ++++++++----
src/components/spectrum.py | 21 ++++++++----
src/components/video.py | 21 ++++++++----
src/components/waveform.py | 20 +++++++----
src/core.py | 85 ++++++++++++++++++++++------------------------
src/gui/mainwindow.py | 18 ++++------
src/toolkit/ffmpeg.py | 22 ++++++++----
7 files changed, 119 insertions(+), 90 deletions(-)
diff --git a/src/component.py b/src/component.py
index 01c1d06..f3ee188 100644
--- a/src/component.py
+++ b/src/component.py
@@ -423,7 +423,14 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
for attr, widget in self._trackedWidgets.items():
key = attr if attr not in self._presetNames \
else self._presetNames[attr]
- val = presetDict[key]
+ try:
+ val = presetDict[key]
+ except KeyError as e:
+ log.info(
+ '%s missing value %s. Outdated preset?',
+ self.currentPreset, str(e)
+ )
+ val = getattr(self, key)
if attr in self._colorWidgets:
widget.setText('%s,%s,%s' % val)
@@ -580,7 +587,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
'colorWidgets',
'relativeWidgets',
):
- setattr(self, '_%s' % kwarg, kwargs[kwarg])
+ setattr(self, '_{}'.format(kwarg), kwargs[kwarg])
else:
raise ComponentError(
self, 'Nonsensical keywords to trackWidgets.')
@@ -613,6 +620,10 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
self._relativeMaximums[attr] = \
self._trackedWidgets[attr].maximum()
self.updateRelativeWidgetMaximum(attr)
+ setattr(
+ self, attr, getWidgetValue(self._trackedWidgets[attr])
+ )
+
self._preUpdate()
self._autoUpdate()
@@ -732,13 +743,12 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
can make determining the 'previous' value tricky.
'''
if self.oldAttrs is not None:
- log.verbose('Using nonstandard oldAttr for %s', attr)
return self.oldAttrs[attr]
else:
try:
return getattr(self, attr)
except AttributeError:
- log.info('Using visible values instead of attrs')
+ log.error('Using visible values instead of oldAttrs')
return self._trackedWidgets[attr].value()
def updateRelativeWidget(self, attr):
@@ -893,7 +903,7 @@ class ComponentUpdate(QtWidgets.QUndoCommand):
def redo(self):
if self.undone:
- log.debug('Redoing component update')
+ log.info('Redoing component update')
self.parent.oldAttrs = self.relativeWidgetValsAfterUndo
self.setWidgetValues(self.modifiedVals)
self.parent.update(auto=True)
@@ -906,7 +916,7 @@ class ComponentUpdate(QtWidgets.QUndoCommand):
self.parent._sendUpdateSignal()
def undo(self):
- log.debug('Undoing component update')
+ log.info('Undoing component update')
self.undone = True
self.parent.oldAttrs = self.relativeWidgetValsAfterRedo
self.setWidgetValues(self.oldWidgetVals)
diff --git a/src/components/spectrum.py b/src/components/spectrum.py
index 2b98dc2..77cb086 100644
--- a/src/components/spectrum.py
+++ b/src/components/spectrum.py
@@ -148,15 +148,22 @@ class Component(Component):
'-codec:v', 'rawvideo', '-',
'-frames:v', '1',
])
- 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:
+
+ if self.core.logEnabled:
+ logFilename = os.path.join(
+ self.core.logDir, 'preview_%s.log' % str(self.compPos))
+ log.debug('Creating ffmpeg process (log at %s)' % logFilename)
+ with open(logFilename, 'w') as logf:
+ logf.write(" ".join(command) + '\n\n')
+ with open(logFilename, 'a') as logf:
+ self.previewPipe = openPipe(
+ command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
+ stderr=logf, bufsize=10**8
+ )
+ else:
self.previewPipe = openPipe(
command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
- stderr=logf, bufsize=10**8
+ stderr=subprocess.DEVNULL, bufsize=10**8
)
byteFrame = self.previewPipe.stdout.read(self.chunkSize)
closePipe(self.previewPipe)
diff --git a/src/components/video.py b/src/components/video.py
index e6486ea..8ad21b5 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -139,16 +139,23 @@ class Component(Component):
'-frames:v', '1',
])
- 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:
+ if self.core.logEnabled:
+ logFilename = os.path.join(
+ self.core.logDir, 'preview_%s.log' % str(self.compPos))
+ log.debug('Creating ffmpeg process (log at %s)' % logFilename)
+ with open(logFilename, 'w') as logf:
+ logf.write(" ".join(command) + '\n\n')
+ with open(logFilename, 'a') as logf:
+ pipe = openPipe(
+ command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
+ stderr=logf, bufsize=10**8
+ )
+ else:
pipe = openPipe(
command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
- stderr=logf, bufsize=10**8
+ stderr=subprocess.DEVNULL, bufsize=10**8
)
+
byteFrame = pipe.stdout.read(self.chunkSize)
closePipe(pipe)
diff --git a/src/components/waveform.py b/src/components/waveform.py
index 5c02bbf..cbfc47f 100644
--- a/src/components/waveform.py
+++ b/src/components/waveform.py
@@ -110,15 +110,21 @@ class Component(Component):
'-codec:v', 'rawvideo', '-',
'-frames:v', '1',
])
- 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:
+ if self.core.logEnabled:
+ logFilename = os.path.join(
+ self.core.logDir, 'preview_%s.log' % str(self.compPos))
+ log.debug('Creating ffmpeg log at %s', logFilename)
+ with open(logFilename, 'w') as logf:
+ logf.write(" ".join(command) + '\n\n')
+ with open(logFilename, 'a') as logf:
+ pipe = openPipe(
+ command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
+ stderr=logf, bufsize=10**8
+ )
+ else:
pipe = openPipe(
command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
- stderr=logf, bufsize=10**8
+ stderr=subprocess.DEVNULL, bufsize=10**8
)
byteFrame = pipe.stdout.read(self.chunkSize)
closePipe(pipe)
diff --git a/src/core.py b/src/core.py
index b9e2335..1a90296 100644
--- a/src/core.py
+++ b/src/core.py
@@ -13,8 +13,8 @@ import toolkit
log = logging.getLogger('AVP.Core')
-STDOUT_LOGLVL = logging.VERBOSE
-FILE_LOGLVL = logging.DEBUG
+STDOUT_LOGLVL = logging.INFO
+FILE_LOGLVL = logging.VERBOSE
class Core:
@@ -145,17 +145,11 @@ class Core:
saveValueStore = self.getPreset(filepath)
if not saveValueStore:
return False
- try:
- comp = self.selectedComponents[compIndex]
- comp.loadPreset(
- saveValueStore,
- presetName
- )
- except KeyError as e:
- log.warning(
- '%s #%s\'s preset is missing value: %s',
- comp.name, str(compIndex), str(e)
- )
+ comp = self.selectedComponents[compIndex]
+ comp.loadPreset(
+ saveValueStore,
+ presetName
+ )
self.savedPresets[presetName] = dict(saveValueStore)
return True
@@ -472,11 +466,12 @@ class Core:
encoderOptions = json.load(json_file)
settings = {
+ 'canceled': False,
+ 'FFMPEG_BIN': findFfmpeg(),
'dataDir': dataDir,
'settings': QtCore.QSettings(
os.path.join(dataDir, 'settings.ini'),
QtCore.QSettings.IniFormat),
- 'logDir': os.path.join(dataDir, 'log'),
'presetDir': os.path.join(dataDir, 'presets'),
'componentsPath': os.path.join(wd, 'components'),
'junkStream': os.path.join(wd, 'gui', 'background.png'),
@@ -486,8 +481,8 @@ class Core:
'1280x720',
'854x480',
],
- 'FFMPEG_BIN': findFfmpeg(),
- 'canceled': False,
+ 'logDir': os.path.join(dataDir, 'log'),
+ 'logEnabled': False,
}
settings['videoFormats'] = toolkit.appendUppercase([
@@ -572,42 +567,42 @@ class Core:
@staticmethod
def makeLogger():
- logFilename = os.path.join(Core.logDir, 'avp_debug.log')
- libLogFilename = os.path.join(Core.logDir, 'global_debug.log')
- # delete old logs
- for log in (logFilename, libLogFilename):
- if os.path.exists(log):
- os.remove(log)
-
- # create file handlers to capture every log message somewhere
- logFile = logging.FileHandler(logFilename)
- logFile.setLevel(FILE_LOGLVL)
- libLogFile = logging.FileHandler(libLogFilename)
- libLogFile.setLevel(FILE_LOGLVL)
-
- # send some critical log messages to stdout as well
+ # send critical log messages to stdout
logStream = logging.StreamHandler()
logStream.setLevel(STDOUT_LOGLVL)
-
- # create formatters for each stream
- fileFormatter = logging.Formatter(
- '[%(asctime)s] %(threadName)-10.10s %(name)-23.23s %(levelname)s: '
- '%(message)s'
- )
streamFormatter = logging.Formatter(
- '<%(name)s> %(message)s'
+ '<%(name)s> %(levelname)s: %(message)s'
)
- logFile.setFormatter(fileFormatter)
- libLogFile.setFormatter(fileFormatter)
logStream.setFormatter(streamFormatter)
-
log = logging.getLogger('AVP')
- log.addHandler(logFile)
log.addHandler(logStream)
- libLog = logging.getLogger()
- libLog.addHandler(libLogFile)
- # lowest level must be explicitly set on the root Logger
- libLog.setLevel(0)
+
+ if FILE_LOGLVL is not None:
+ # write log files as well!
+ Core.logEnabled = True
+ logFilename = os.path.join(Core.logDir, 'avp_debug.log')
+ libLogFilename = os.path.join(Core.logDir, 'global_debug.log')
+ # delete old logs
+ for log_ in (logFilename, libLogFilename):
+ if os.path.exists(log_):
+ os.remove(log_)
+
+ logFile = logging.FileHandler(logFilename)
+ logFile.setLevel(FILE_LOGLVL)
+ libLogFile = logging.FileHandler(libLogFilename)
+ libLogFile.setLevel(FILE_LOGLVL)
+ fileFormatter = logging.Formatter(
+ '[%(asctime)s] %(threadName)-10.10s %(name)-23.23s %(levelname)s: '
+ '%(message)s'
+ )
+ logFile.setFormatter(fileFormatter)
+ libLogFile.setFormatter(fileFormatter)
+
+ libLog = logging.getLogger()
+ log.addHandler(logFile)
+ libLog.addHandler(libLogFile)
+ # lowest level must be explicitly set on the root Logger
+ libLog.setLevel(0)
# always store settings in class variables even if a Core object is not created
Core.storeSettings()
diff --git a/src/gui/mainwindow.py b/src/gui/mainwindow.py
index d7fde5c..81c5d7c 100644
--- a/src/gui/mainwindow.py
+++ b/src/gui/mainwindow.py
@@ -92,6 +92,10 @@ class MainWindow(QtWidgets.QMainWindow):
self.previewWorker.moveToThread(self.previewThread)
self.previewWorker.imageCreated.connect(self.showPreviewImage)
self.previewThread.start()
+ self.previewThread.finished.connect(
+ lambda:
+ log.critical('PREVIEW THREAD DIED! This should never happen.')
+ )
timeout = 500
log.debug(
@@ -442,7 +446,7 @@ class MainWindow(QtWidgets.QMainWindow):
appName += '*'
except AttributeError:
pass
- log.debug('Setting window title to %s' % appName)
+ log.verbose('Setting window title to %s' % appName)
self.window.setWindowTitle(appName)
@QtCore.pyqtSlot(int, dict)
@@ -459,16 +463,8 @@ class MainWindow(QtWidgets.QMainWindow):
modified = False
else:
modified = (presetStore != self.core.savedPresets[name])
- if modified:
- log.verbose(
- 'Differing values between presets: %s',
- ", ".join([
- '%s: %s' % item for item in presetStore.items()
- if val != self.core.savedPresets[name][key]
- ])
- )
- else:
- modified = bool(presetStore)
+
+ modified = bool(presetStore)
if pos < 0:
pos = len(self.core.selectedComponents)-1
name = self.core.selectedComponents[pos].name
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index a77831e..d78d803 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -91,16 +91,24 @@ class FfmpegVideo:
def fillBuffer(self):
from component import ComponentError
- logFilename = os.path.join(
- core.Core.logDir, 'render_%s.log' % str(self.component.compPos))
- log.debug('Creating ffmpeg process (log at %s)', logFilename)
- with open(logFilename, 'w') as logf:
- logf.write(" ".join(self.command) + '\n\n')
- with open(logFilename, 'a') as logf:
+ if core.Core.logEnabled:
+ logFilename = os.path.join(
+ core.Core.logDir, 'render_%s.log' % str(self.component.compPos)
+ )
+ log.debug('Creating ffmpeg process (log at %s)', logFilename)
+ with open(logFilename, 'w') as logf:
+ logf.write(" ".join(self.command) + '\n\n')
+ with open(logFilename, 'a') as logf:
+ self.pipe = openPipe(
+ self.command, stdin=subprocess.DEVNULL,
+ stdout=subprocess.PIPE, stderr=logf, bufsize=10**8
+ )
+ else:
self.pipe = openPipe(
self.command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
- stderr=logf, bufsize=10**8
+ stderr=subprocess.DEVNULL, bufsize=10**8
)
+
while True:
if self.parent.canceled:
break
--
cgit v1.2.3
From ad6dd9f5329f3e23e75c181c21ca8701028b538f Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sun, 27 Aug 2017 19:59:51 -0400
Subject: undoable Life component grid actions
---
src/components/life.py | 132 ++++++++++++++++++++++++++++++++++++-------------
src/gui/preview_win.py | 8 ++-
2 files changed, 102 insertions(+), 38 deletions(-)
diff --git a/src/components/life.py b/src/components/life.py
index d4a455d..7a610eb 100644
--- a/src/components/life.py
+++ b/src/components/life.py
@@ -1,4 +1,5 @@
from PyQt5 import QtGui, QtCore, QtWidgets
+from PyQt5.QtWidgets import QUndoCommand
from PIL import Image, ImageDraw, ImageEnhance, ImageChops, ImageFilter
import os
import math
@@ -58,22 +59,8 @@ class Component(Component):
self.mergeUndo = True
def shiftGrid(self, d):
- def newGrid(Xchange, Ychange):
- return {
- (x + Xchange, y + Ychange)
- for x, y in self.startingGrid
- }
-
- if d == 0:
- newGrid = newGrid(0, -1)
- elif d == 1:
- newGrid = newGrid(0, 1)
- elif d == 2:
- newGrid = newGrid(-1, 0)
- elif d == 3:
- newGrid = newGrid(1, 0)
- self.startingGrid = newGrid
- self._sendUpdateSignal()
+ action = ShiftGrid(self, d)
+ self.parent.undoStack.push(action)
def update(self):
self.updateGridSize()
@@ -98,17 +85,14 @@ class Component(Component):
enabled = (len(self.startingGrid) > 0)
for widget in self.shiftButtons:
widget.setEnabled(enabled)
- super().update()
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
)
- if button == 1:
- self.startingGrid.add(pos)
- elif button == 2:
- self.startingGrid.discard(pos)
+ action = ClickGrid(self, pos, button)
+ self.parent.undoStack.push(action)
def updateGridSize(self):
w, h = self.core.resolutions[-1].split('x')
@@ -223,7 +207,7 @@ class Component(Component):
'up', 'down', 'left', 'right',
)
}
- for cell in nearbyCoords(x, y):
+ for cell in self.nearbyCoords(x, y):
if cell not in grid:
continue
if cell[0] == x:
@@ -363,7 +347,7 @@ class Component(Component):
def neighbours(x, y):
return {
- cell for cell in nearbyCoords(x, y)
+ cell for cell in self.nearbyCoords(x, y)
if cell in lastGrid
}
@@ -374,7 +358,7 @@ class Component(Component):
newGrid.add((x, y))
potentialNewCells = {
coordTup for origin in lastGrid
- for coordTup in list(nearbyCoords(*origin))
+ for coordTup in list(self.nearbyCoords(*origin))
}
for x, y in potentialNewCells:
if (x, y) in newGrid:
@@ -397,13 +381,95 @@ class Component(Component):
widget.setEnabled(True)
super().loadPreset(pr, *args)
+ def nearbyCoords(self, x, y):
+ yield x + 1, y + 1
+ yield x + 1, y - 1
+ yield x - 1, y + 1
+ yield x - 1, y - 1
+ yield x, y + 1
+ yield x, y - 1
+ yield x + 1, y
+ yield x - 1, y
+
+
+class ClickGrid(QUndoCommand):
+ def __init__(self, comp, pos, id_):
+ super().__init__(
+ "click %s component #%s" % (comp.name, comp.compPos))
+ self.comp = comp
+ self.pos = [pos]
+ self.id_ = id_
+
+ def id(self):
+ return self.id_
+
+ def mergeWith(self, other):
+ self.pos.extend(other.pos)
+ return True
+
+ def add(self):
+ for pos in self.pos[:]:
+ self.comp.startingGrid.add(pos)
+ self.comp.update(auto=True)
+
+ def remove(self):
+ for pos in self.pos[:]:
+ self.comp.startingGrid.discard(pos)
+ self.comp.update(auto=True)
+
+ def redo(self):
+ if self.id_ == 1: # Left-click
+ self.add()
+ elif self.id_ == 2: # Right-click
+ self.remove()
+
+ def undo(self):
+ if self.id_ == 1: # Left-click
+ self.remove()
+ elif self.id_ == 2: # Right-click
+ self.add()
+
+class ShiftGrid(QUndoCommand):
+ def __init__(self, comp, direction):
+ super().__init__(
+ "change %s component #%s" % (comp.name, comp.compPos))
+ self.comp = comp
+ self.direction = direction
+ self.distance = 1
+
+ def id(self):
+ return self.direction
+
+ def mergeWith(self, other):
+ self.distance += other.distance
+ return True
+
+ def newGrid(self, Xchange, Ychange):
+ return {
+ (x + Xchange, y + Ychange)
+ for x, y in self.comp.startingGrid
+ }
-def nearbyCoords(x, y):
- yield x + 1, y + 1
- yield x + 1, y - 1
- yield x - 1, y + 1
- yield x - 1, y - 1
- yield x, y + 1
- yield x, y - 1
- yield x + 1, y
- yield x - 1, y
+ def redo(self):
+ if self.direction == 0:
+ newGrid = self.newGrid(0, -self.distance)
+ elif self.direction == 1:
+ newGrid = self.newGrid(0, self.distance)
+ elif self.direction == 2:
+ newGrid = self.newGrid(-self.distance, 0)
+ elif self.direction == 3:
+ newGrid = self.newGrid(self.distance, 0)
+ self.comp.startingGrid = newGrid
+ self.comp._sendUpdateSignal()
+
+ def undo(self):
+ if self.direction == 0:
+ newGrid = self.newGrid(0, self.distance)
+ elif self.direction == 1:
+ newGrid = self.newGrid(0, -self.distance)
+ elif self.direction == 2:
+ newGrid = self.newGrid(self.distance, 0)
+ elif self.direction == 3:
+ newGrid = self.newGrid(-self.distance, 0)
+ self.comp.startingGrid = newGrid
+ self.comp._sendUpdateSignal()
diff --git a/src/gui/preview_win.py b/src/gui/preview_win.py
index 49a22eb..3db420c 100644
--- a/src/gui/preview_win.py
+++ b/src/gui/preview_win.py
@@ -1,14 +1,14 @@
from PyQt5 import QtCore, QtGui, QtWidgets
import logging
+log = logging.getLogger('AVP.Gui.PreviewWindow')
+
class PreviewWindow(QtWidgets.QLabel):
'''
Paints the preview QLabel in MainWindow and maintains the aspect ratio
when the window is resized.
'''
- log = logging.getLogger('AVP.Gui.PreviewWindow')
-
def __init__(self, parent, img):
super(PreviewWindow, self).__init__()
self.parent = parent
@@ -41,17 +41,15 @@ class PreviewWindow(QtWidgets.QLabel):
if i >= 0:
component = self.parent.core.selectedComponents[i]
if not hasattr(component, 'previewClickEvent'):
- self.log.info('Ignored click event')
return
pos = (event.x(), event.y())
size = (self.width(), self.height())
butt = event.button()
- self.log.info('Click event for #%s: %s button %s' % (
+ log.info('Click event for #%s: %s button %s' % (
i, pos, butt))
component.previewClickEvent(
pos, size, butt
)
- self.parent.core.updateComponent(i)
@QtCore.pyqtSlot(str)
def threadError(self, msg):
--
cgit v1.2.3
From 8411857030d92e448d5c64682f396e677161afbe Mon Sep 17 00:00:00 2001
From: tassaron
Date: Mon, 28 Aug 2017 18:54:54 -0400
Subject: ctrl-c ends commandline mode properly
---
setup.py | 2 +-
src/command.py | 9 +++++++++
src/components/spectrum.py | 3 ++-
src/core.py | 10 ++++------
src/toolkit/frame.py | 1 +
src/video_thread.py | 11 ++++++++---
6 files changed, 25 insertions(+), 11 deletions(-)
diff --git a/setup.py b/setup.py
index dd546e2..cdf4c4a 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@ from setuptools import setup
import os
-__version__ = '2.0.0.rc4'
+__version__ = '2.0.0rc5'
def package_files(directory):
diff --git a/src/command.py b/src/command.py
index 4116c5a..cd3c6c3 100644
--- a/src/command.py
+++ b/src/command.py
@@ -8,6 +8,7 @@ import argparse
import os
import sys
import time
+import signal
from core import Core
@@ -91,6 +92,9 @@ class Command(QtCore.QObject):
for arg in args:
self.core.selectedComponents[i].command(arg)
+ # ctrl-c stops the export thread
+ signal.signal(signal.SIGINT, self.stopVideo)
+
if self.args.export and self.args.projpath:
errcode, data = self.core.parseAvFile(projPath)
for key, value in data['WindowFields']:
@@ -124,6 +128,11 @@ class Command(QtCore.QObject):
self.worker.progressBarSetText.connect(self.progressBarSetText)
self.createVideo.emit()
+ def stopVideo(self, *args):
+ self.worker.error = True
+ self.worker.cancelExport()
+ self.worker.cancel()
+
@QtCore.pyqtSlot(str)
def progressBarSetText(self, value):
if 'Export ' in value:
diff --git a/src/components/spectrum.py b/src/components/spectrum.py
index 77cb086..6675f5b 100644
--- a/src/components/spectrum.py
+++ b/src/components/spectrum.py
@@ -98,7 +98,8 @@ class Component(Component):
def preFrameRender(self, **kwargs):
super().preFrameRender(**kwargs)
- self.previewPipe.wait()
+ if self.previewPipe is not None:
+ self.previewPipe.wait()
self.updateChunksize()
w, h = scale(self.scale, self.width, self.height, str)
self.video = FfmpegVideo(
diff --git a/src/core.py b/src/core.py
index 1a90296..d7445c9 100644
--- a/src/core.py
+++ b/src/core.py
@@ -13,8 +13,8 @@ import toolkit
log = logging.getLogger('AVP.Core')
-STDOUT_LOGLVL = logging.INFO
-FILE_LOGLVL = logging.VERBOSE
+STDOUT_LOGLVL = logging.WARNING
+FILE_LOGLVL = None
class Core:
@@ -77,8 +77,7 @@ class Core:
if compPos < 0 or compPos > len(self.selectedComponents):
compPos = len(self.selectedComponents)
if len(self.selectedComponents) > 50:
- return None
-
+ return -1
if type(component) is int:
# create component using module index in self.modules
moduleIndex = int(component)
@@ -188,7 +187,6 @@ class Core:
for key, value in data['Settings']:
Core.settings.setValue(key, value)
-
for tup in data['Components']:
name, vers, preset = tup
clearThis = False
@@ -213,7 +211,7 @@ class Core:
self.moduleIndexFor(name),
loader
)
- if i is None:
+ if i == -1:
loader.showMessage(msg="Too many components!")
break
diff --git a/src/toolkit/frame.py b/src/toolkit/frame.py
index aefb55f..0e200b5 100644
--- a/src/toolkit/frame.py
+++ b/src/toolkit/frame.py
@@ -32,6 +32,7 @@ class FramePainter(QtGui.QPainter):
super().setPen(penStyle)
def finalize(self):
+ log.verbose("Finalizing FramePainter")
imBytes = self.image.bits().asstring(self.image.byteCount())
frame = Image.frombytes(
'RGBA', (self.image.width(), self.image.height()), imBytes
diff --git a/src/video_thread.py b/src/video_thread.py
index 823ac73..91ebe93 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -252,9 +252,14 @@ class Worker(QtCore.QObject):
print('############################')
log.info('Opening pipe to ffmpeg')
log.info(cmd)
- self.out_pipe = openPipe(
- ffmpegCommand, stdin=sp.PIPE, stdout=sys.stdout, stderr=sys.stdout
- )
+ try:
+ self.out_pipe = openPipe(
+ ffmpegCommand,
+ stdin=sp.PIPE, stdout=sys.stdout, stderr=sys.stdout
+ )
+ except sp.CalledProcessError:
+ log.critical('Ffmpeg pipe couldn\'t be created!')
+ raise
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
# START CREATING THE VIDEO
--
cgit v1.2.3
From eadf0e59fda3b442bf660b562c5fc4a70ba18c33 Mon Sep 17 00:00:00 2001
From: tassaron2
Date: Sun, 15 Mar 2020 22:22:50 -0400
Subject: quick update to be somewhat compatible with newer versions of Pillow,
ffmpeg, and Ubuntu
---
README.md | 14 ++++++++------
src/gui/mainwindow.py | 7 ++++---
2 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/README.md b/README.md
index 5f4e1e7..c28ac35 100644
--- a/README.md
+++ b/README.md
@@ -19,13 +19,15 @@ Python 3.4, FFmpeg 3.3, PyQt5, Pillow-SIMD, NumPy
Installation
------------
-### Manual installation on Ubuntu 16.04
-* Install pip: `sudo apt-get install python3-pip`
-* If Pillow is installed, it must be removed. Nothing should break because Pillow-SIMD is simply a drop-in replacement with better performance.
-* Download audio-visualizer-python from this repository and run `sudo pip3 install .` in this directory
-* Install `ffmpeg` from the [website](http://ffmpeg.org/) or from a PPA (e.g. [https://launchpad.net/~jonathonf/+archive/ubuntu/ffmpeg-3](https://launchpad.net/~jonathonf/+archive/ubuntu/ffmpeg-3)). NOTE: `ffmpeg` in the standard repos is too old (v2.8). Old versions and `avconv` may be used but full functionality is only guaranteed with `ffmpeg` 3.3 or higher.
+### Manual installation on Ubuntu 20.04
+* Install ffmpeg: `sudo apt install ffmpeg`
+* Install pip: `sudo apt install python3-pip`
+* Install PyQt5: `sudo apt install python3-pyqt5`
+* Install dependencies to compile Pillow-SIMD: `sudo apt install python3-dev libtiff5-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python-tk`
+* Download audio-visualizer-python from this repository and run `pip3 install .` in this directory
+* Run the program with `avp` or `python3 -m avpython`
+* (Optional Note) If using a virtual environmennt, PyQt5 doesn't seem to work when installed from the setup.py. You can use `--system-site-packages --copies` to copy the system site-packages into your venv
-Run the program with `avp` or `python3 -m avpython`
### Manual installation on Windows
* **Warning:** [Compiling Pillow is difficult on Windows](http://pillow.readthedocs.io/en/3.1.x/installation.html#building-on-windows) and required for the best experience.
diff --git a/src/gui/mainwindow.py b/src/gui/mainwindow.py
index 81c5d7c..53a6bd1 100644
--- a/src/gui/mainwindow.py
+++ b/src/gui/mainwindow.py
@@ -320,12 +320,12 @@ class MainWindow(QtWidgets.QMainWindow):
# verify Pillow version
if not self.settings.value("pilMsgShown") \
- and 'post' not in Image.PILLOW_VERSION:
+ and 'post' not in Image.__version__:
self.showMessage(
msg="You are using the standard version of the "
"Python imaging library (Pillow %s). Upgrade "
"to the Pillow-SIMD fork to enable hardware accelerations "
- "and export videos faster." % Image.PILLOW_VERSION
+ "and export videos faster." % Image.__version__
)
self.settings.setValue("pilMsgShown", True)
@@ -336,7 +336,8 @@ class MainWindow(QtWidgets.QMainWindow):
ffmpegVers = checkOutput(
['ffmpeg', '-version'], stderr=f
)
- goodVersion = str(ffmpegVers).split()[2].startswith('3')
+ goodVersion = (str(ffmpegVers).split()[2].startswith('3') or
+ str(ffmpegVers).split()[2].startswith('4'))
except Exception:
goodVersion = False
else:
--
cgit v1.2.3
From 765a35119f258f352718a556fbea4af708236900 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Wed, 13 Apr 2022 16:04:32 -0400
Subject: cast floats to ints when calling resize(), setX(), and setY()
(argument types changed in newer version)
---
src/gui/preview_win.py | 4 ++--
src/main.py | 7 ++++---
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/src/gui/preview_win.py b/src/gui/preview_win.py
index 3db420c..27e0a59 100644
--- a/src/gui/preview_win.py
+++ b/src/gui/preview_win.py
@@ -25,8 +25,8 @@ class PreviewWindow(QtWidgets.QLabel):
transformMode=QtCore.Qt.SmoothTransformation)
# start painting the label from left upper corner
- point.setX((size.width() - scaledPix.width())/2)
- point.setY((size.height() - scaledPix.height())/2)
+ point.setX(int((size.width() - scaledPix.width())/2))
+ point.setY(int((size.height() - scaledPix.height())/2))
painter.drawPixmap(point, scaledPix)
def changePixmap(self, img):
diff --git a/src/main.py b/src/main.py
index f767de1..126e4a8 100644
--- a/src/main.py
+++ b/src/main.py
@@ -44,9 +44,10 @@ def main():
topMargin = 0 if (dpi == 96) else int(10 * (dpi / 96))
window.resize(
- window.width() *
- (dpi / 96), window.height() *
- (dpi / 96)
+ int(window.width() *
+ (dpi / 96)),
+ int(window.height() *
+ (dpi / 96))
)
# window.verticalLayout_2.setContentsMargins(0, topMargin, 0, 0)
--
cgit v1.2.3
From eb656192c49ba97eaebe932195d589cfc9535d84 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Wed, 13 Apr 2022 16:05:55 -0400
Subject: fix missing audio in output video due to change in ffmpeg command
syntax (tested with v4.4.1)
---
src/toolkit/ffmpeg.py | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index d78d803..0a536bc 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -227,8 +227,8 @@ def createFfmpegCommand(inputFile, outputFile, components, duration=-1):
'-pix_fmt', 'rgba',
'-r', Core.settings.value('outputFrameRate'),
'-t', duration,
- '-i', '-', # the video input comes from a pipe
'-an', # the video input has no sound
+ '-i', '-', # the video input comes from a pipe
# INPUT SOUND
'-t', duration,
@@ -241,12 +241,11 @@ def createFfmpegCommand(inputFile, outputFile, components, duration=-1):
]
segment = createAudioFilterCommand(extraAudio, safeDuration)
ffmpegCommand.extend(segment)
- if segment:
- # Only map audio from the filters, and video from the pipe
- ffmpegCommand.extend([
- '-map', '0:v',
- '-map', '[a]',
- ])
+ # Map audio from the filters or the single audio input, and map video from the pipe
+ ffmpegCommand.extend([
+ '-map', '0:v',
+ '-map', '[a]' if segment else '1:a',
+ ])
ffmpegCommand.extend([
# OUTPUT
--
cgit v1.2.3
From a0291e3fcde739355669e0ca40487f0eb8ee82e4 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Wed, 13 Apr 2022 16:20:04 -0400
Subject: update readme with known working versions
---
README.md | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index c28ac35..1dee874 100644
--- a/README.md
+++ b/README.md
@@ -13,13 +13,13 @@ The program works on Linux, macOS, and Windows. If you encounter problems runnin
Dependencies
------------
-Python 3.4, FFmpeg 3.3, PyQt5, Pillow-SIMD, NumPy
+Python 3.10, FFmpeg 4.4.1, PyQt5 (Qt v5.15.3), Pillow-SIMD, NumPy
**Note:** Pillow may be used as a drop-in replacement for Pillow-SIMD if problems are encountered installing. However this will result in much slower video export times. For help installing Pillow-SIMD, see the [Pillow installation guide](http://pillow.readthedocs.io/en/3.1.x/installation.html).
Installation
------------
-### Manual installation on Ubuntu 20.04
+### Manual installation on Ubuntu 22.04
* Install ffmpeg: `sudo apt install ffmpeg`
* Install pip: `sudo apt install python3-pip`
* Install PyQt5: `sudo apt install python3-pyqt5`
@@ -41,8 +41,9 @@ Installation
Download audio-visualizer-python from this repository and run it from the command line with `python main.py`.
-### Manual installation on macOS **[Outdated]**
+### Manual installation on macOS
+* **[Outdated]**: No one has updated these instructions for a while.
* Install [Homebrew](http://brew.sh/)
* Use the following commands to install the needed dependencies:
--
cgit v1.2.3
From 1c9f5e4ae685a957a29100e7ade1ac365149c218 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 21 Apr 2022 01:05:55 -0400
Subject: fix crash if ffmpeg is not installed (tested on windows 11)
---
src/toolkit/ffmpeg.py | 3 +++
src/video_thread.py | 12 +++++++++---
2 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index 0a536bc..3f083ba 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -383,6 +383,9 @@ def getAudioDuration(filename):
fileInfo = checkOutput(command, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as ex:
fileInfo = ex.output
+ except FileNotFoundError:
+ # ffmpeg is possibly not installed
+ return False
try:
info = fileInfo.decode("utf-8").split('\n')
diff --git a/src/video_thread.py b/src/video_thread.py
index 91ebe93..4a07fb2 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -244,9 +244,15 @@ class Worker(QtCore.QObject):
)
self.staticComponents[compNo] = None
- ffmpegCommand = createFfmpegCommand(
- self.inputFile, self.outputFile, self.components, duration
- )
+ try:
+ ffmpegCommand = createFfmpegCommand(
+ self.inputFile, self.outputFile, self.components, duration
+ )
+ except sp.CalledProcessError as e:
+ self.components[0]._error.emit("Ffmpeg could not be found. Is it installed?", str(e))
+ self.cancelExport()
+ return
+
cmd = " ".join(ffmpegCommand)
print('###### FFMPEG COMMAND ######\n%s' % cmd)
print('############################')
--
cgit v1.2.3
From c29be67845ccb17093565bec961202b3a44e37db Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 21 Apr 2022 15:49:18 -0400
Subject: fix RuntimeError caused by QUndoStack signal handler
---
src/gui/mainwindow.py | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/src/gui/mainwindow.py b/src/gui/mainwindow.py
index 53a6bd1..75534c2 100644
--- a/src/gui/mainwindow.py
+++ b/src/gui/mainwindow.py
@@ -108,6 +108,15 @@ class MainWindow(QtWidgets.QMainWindow):
# Begin decorating the window and connecting events
componentList = self.window.listWidget_componentList
+ # Undo Feature
+ def toggleUndoButtonEnabled(*_):
+ """ Enable/disable undo button depending on whether UndoStack contains Actions """
+ try:
+ undoButton.setEnabled(self.undoStack.count())
+ except RuntimeError:
+ # program is probably in midst of exiting
+ pass
+
style = window.pushButton_undo.style()
undoButton = window.pushButton_undo
undoButton.setIcon(
@@ -115,9 +124,7 @@ class MainWindow(QtWidgets.QMainWindow):
)
undoButton.clicked.connect(self.undoStack.undo)
undoButton.setEnabled(False)
- self.undoStack.cleanChanged.connect(
- lambda change: undoButton.setEnabled(self.undoStack.count())
- )
+ self.undoStack.cleanChanged.connect(toggleUndoButtonEnabled)
self.undoMenu = QMenu()
self.undoMenu.addAction(
self.undoStack.createUndoAction(self)
@@ -130,6 +137,7 @@ class MainWindow(QtWidgets.QMainWindow):
lambda _: self.showUndoStack()
)
undoButton.setMenu(self.undoMenu)
+ # end of Undo Feature
style = window.pushButton_listMoveUp.style()
window.pushButton_listMoveUp.setIcon(
--
cgit v1.2.3
From c91d10033a74d8df62696803ccaf4a3750c7ed8c Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 21 Apr 2022 15:55:56 -0400
Subject: createFfmpegCommand returns an empty list if it fails previously it
raised an exception
---
src/toolkit/ffmpeg.py | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index 3f083ba..419d491 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -202,15 +202,24 @@ def createFfmpegCommand(inputFile, outputFile, components, duration=-1):
vencoders = options['video-codecs'][vcodec]
aencoders = options['audio-codecs'][acodec]
+ def error():
+ nonlocal encoders, encoder
+ log.critical("Selected encoder (%s) is not supported by Ffmpeg. The supported encoders are: %s", encoder, encoders)
+ return []
+
for encoder in vencoders:
if encoder in encoders:
vencoder = encoder
break
+ else:
+ return error()
for encoder in aencoders:
if encoder in encoders:
aencoder = encoder
break
+ else:
+ return error()
ffmpegCommand = [
Core.FFMPEG_BIN,
--
cgit v1.2.3
From 2a66c3b77bb8c438a3f278da2d4d90a7ee476feb Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 21 Apr 2022 16:01:34 -0400
Subject: fail gracefully if createFfmpegCommand returns empty
---
src/video_thread.py | 23 ++++++++++++++++-------
1 file changed, 16 insertions(+), 7 deletions(-)
diff --git a/src/video_thread.py b/src/video_thread.py
index 4a07fb2..0a39f28 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -249,13 +249,21 @@ class Worker(QtCore.QObject):
self.inputFile, self.outputFile, self.components, duration
)
except sp.CalledProcessError as e:
+ #FIXME video_thread should own this error signal, not components
self.components[0]._error.emit("Ffmpeg could not be found. Is it installed?", str(e))
- self.cancelExport()
+ self.error = True
return
cmd = " ".join(ffmpegCommand)
print('###### FFMPEG COMMAND ######\n%s' % cmd)
print('############################')
+ if not cmd:
+ #FIXME video_thread should own this error signal, not components
+ self.components[0]._error.emit("The ffmpeg command could not be generated.", "")
+ log.critical("Cancelling render process due to failure while generating the ffmpeg command.")
+ self.failExport()
+ return
+
log.info('Opening pipe to ffmpeg')
log.info(cmd)
try:
@@ -264,7 +272,7 @@ class Worker(QtCore.QObject):
stdin=sp.PIPE, stdout=sys.stdout, stderr=sys.stdout
)
except sp.CalledProcessError:
- log.critical('Ffmpeg pipe couldn\'t be created!')
+ log.critical('Ffmpeg pipe couldn\'t be created!', exc_info=True)
raise
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
@@ -347,9 +355,7 @@ class Worker(QtCore.QObject):
self.progressBarSetText.emit('Export Canceled')
else:
if self.error:
- print("Export Failed")
- self.progressBarUpdate.emit(0)
- self.progressBarSetText.emit('Export Failed')
+ self.failExport()
else:
print("Export Complete")
self.progressBarUpdate.emit(100)
@@ -372,12 +378,15 @@ class Worker(QtCore.QObject):
self.error = True
self.out_pipe.wait()
- def cancelExport(self):
+ def cancelExport(self, message='Export Canceled'):
self.progressBarUpdate.emit(0)
- self.progressBarSetText.emit('Export Canceled')
+ self.progressBarSetText.emit(message)
self.encoding.emit(False)
self.videoCreated.emit()
+ def failExport(self):
+ self.cancelExport('Export Failed')
+
def updateProgress(self, pStr, pVal):
self.progressBarValue.emit(pVal)
self.progressBarSetText.emit(pStr)
--
cgit v1.2.3
From 05d2ebc3c69f5a876d602004f69202c5ba8b09f7 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Fri, 22 Apr 2022 17:09:50 -0400
Subject: make pip-installable as a package
---
MANIFEST.in | 7 ++++++
setup.py | 61 ++++++++++++++++++++++++++--------------------
src/__init__.py | 6 ++---
src/__main__.py | 4 +--
src/component.py | 4 +--
src/components/color.py | 4 +--
src/components/image.py | 4 +--
src/components/life.py | 4 +--
src/components/original.py | 4 +--
src/components/sound.py | 4 +--
src/components/spectrum.py | 8 +++---
src/components/text.py | 4 +--
src/components/video.py | 8 +++---
src/components/waveform.py | 8 +++---
src/core.py | 12 ++++-----
src/gui/actions.py | 2 +-
src/gui/mainwindow.py | 13 +++++-----
src/gui/presetmanager.py | 6 ++---
src/gui/preview_thread.py | 4 +--
src/main.py | 11 ++++-----
src/toolkit/__init__.py | 2 +-
src/toolkit/ffmpeg.py | 8 +++---
src/toolkit/frame.py | 2 +-
src/video_thread.py | 8 +++---
24 files changed, 106 insertions(+), 92 deletions(-)
create mode 100644 MANIFEST.in
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..2b2d794
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,7 @@
+recursive-include src/tests
+include src/components/*.ui
+include src/gui/*.ui
+include src/gui/background.png
+include src/encoder-options.json
+global-exclude src/components/__template__.ui
+global-exclude *.py[cod]
diff --git a/setup.py b/setup.py
index cdf4c4a..5e01229 100644
--- a/setup.py
+++ b/setup.py
@@ -1,29 +1,39 @@
-from setuptools import setup
-import os
+from setuptools import setup, find_packages
+from importlib import import_module
+from os import path
+import re
-__version__ = '2.0.0rc5'
+def getTextFromFile(filename, fallback):
+ try:
+ with open(
+ path.join(path.abspath(path.dirname(__file__)), filename), encoding="utf-8"
+ ) as f:
+ output = f.read()
+ except Exception:
+ output = fallback
+ return output
-def package_files(directory):
- paths = []
- for (path, directories, filenames) in os.walk(directory):
- for filename in filenames:
- paths.append(os.path.join('..', path, filename))
- return paths
+PACKAGE_NAME = 'avp'
+SOURCE_DIRECTORY = 'src'
+SOURCE_PACKAGE_REGEX = re.compile(rf'^{SOURCE_DIRECTORY}')
+PACKAGE_DESCRIPTION = 'Create audio visualization videos from a GUI or commandline'
+
+
+avp = import_module(SOURCE_DIRECTORY)
+source_packages = find_packages(include=[SOURCE_DIRECTORY, f'{SOURCE_DIRECTORY}.*'])
+proj_packages = [SOURCE_PACKAGE_REGEX.sub(PACKAGE_NAME, name) for name in source_packages]
setup(
name='audio_visualizer_python',
- version=__version__,
+ version=avp.__version__,
url='https://github.com/djfun/audio-visualizer-python/tree/feature-newgui',
license='MIT',
- description='Create audio visualization videos from a GUI or commandline',
- long_description="Create customized audio visualization videos and save "
- "them as Projects to continue editing later. Different components can "
- "be added and layered to add visualizers, images, videos, gradients, "
- "text, etc. Use Projects created in the GUI with commandline mode to "
- "automate your video production workflow without any complex syntax.",
+ description=PACKAGE_DESCRIPTION,
+ author=getTextFromFile('AUTHORS', 'djfun, tassaron'),
+ long_description=getTextFromFile('README.md', PACKAGE_DESCRIPTION),
classifiers=[
'Development Status :: 4 - Beta',
'License :: OSI Approved :: MIT License',
@@ -35,19 +45,18 @@ setup(
'visualizer', 'visualization', 'commandline video',
'video editor', 'ffmpeg', 'podcast'
],
- packages=[
- 'avpython',
- 'avpython.toolkit',
- 'avpython.components'
+ packages=proj_packages,
+ package_dir={PACKAGE_NAME: SOURCE_DIRECTORY},
+ include_package_data=True,
+ install_requires=[
+ 'Pillow-SIMD',
+ 'PyQt5',
+ 'numpy',
+ 'pytest'
],
- package_dir={'avpython': 'src'},
- package_data={
- 'avpython': package_files('src'),
- },
- install_requires=['Pillow-SIMD', 'PyQt5', 'numpy'],
entry_points={
'gui_scripts': [
- 'avp = avpython.main:main'
+ f'avp = {PACKAGE_NAME}.main:main'
],
}
)
diff --git a/src/__init__.py b/src/__init__.py
index 73f174a..08131ce 100644
--- a/src/__init__.py
+++ b/src/__init__.py
@@ -3,6 +3,9 @@ import os
import logging
+__version__ = '2.0.0rc6'
+
+
class Logger(logging.getLoggerClass()):
'''
Custom Logger class to handle custom VERBOSE log level.
@@ -31,6 +34,3 @@ if getattr(sys, 'frozen', False):
else:
# unfrozen
wd = os.path.dirname(os.path.realpath(__file__))
-
-# make relative imports work when using /src as a package
-sys.path.insert(0, wd)
diff --git a/src/__main__.py b/src/__main__.py
index 3babeae..3206bc8 100644
--- a/src/__main__.py
+++ b/src/__main__.py
@@ -1,5 +1,5 @@
-# Allows for launching with python3 -m avpython
+# Allows for launching with python3 -m avp
-from avpython.main import main
+from .main import main
main()
diff --git a/src/component.py b/src/component.py
index f3ee188..33c7657 100644
--- a/src/component.py
+++ b/src/component.py
@@ -11,8 +11,8 @@ import time
import logging
from copy import copy
-from toolkit.frame import BlankFrame
-from toolkit import (
+from .toolkit.frame import BlankFrame
+from .toolkit import (
getWidgetValue, setWidgetValue, connectWidget, rgbFromString, blockSignals
)
diff --git a/src/components/color.py b/src/components/color.py
index 7d4f86d..6336194 100644
--- a/src/components/color.py
+++ b/src/components/color.py
@@ -4,8 +4,8 @@ from PyQt5.QtGui import QColor
from PIL.ImageQt import ImageQt
import os
-from component import Component
-from toolkit.frame import BlankFrame, FloodFrame, FramePainter, PaintColor
+from ..component import Component
+from ..toolkit.frame import BlankFrame, FloodFrame, FramePainter, PaintColor
class Component(Component):
diff --git a/src/components/image.py b/src/components/image.py
index dd363bf..42f9564 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -2,8 +2,8 @@ from PIL import Image, ImageDraw, ImageEnhance
from PyQt5 import QtGui, QtCore, QtWidgets
import os
-from component import Component
-from toolkit.frame import BlankFrame
+from ..component import Component
+from ..toolkit.frame import BlankFrame
class Component(Component):
diff --git a/src/components/life.py b/src/components/life.py
index 7a610eb..94704bc 100644
--- a/src/components/life.py
+++ b/src/components/life.py
@@ -4,8 +4,8 @@ from PIL import Image, ImageDraw, ImageEnhance, ImageChops, ImageFilter
import os
import math
-from component import Component
-from toolkit.frame import BlankFrame, scale
+from ..component import Component
+from ..toolkit.frame import BlankFrame, scale
class Component(Component):
diff --git a/src/components/original.py b/src/components/original.py
index f886374..80228fe 100644
--- a/src/components/original.py
+++ b/src/components/original.py
@@ -6,8 +6,8 @@ import os
import time
from copy import copy
-from component import Component
-from toolkit.frame import BlankFrame
+from ..component import Component
+from ..toolkit.frame import BlankFrame
class Component(Component):
diff --git a/src/components/sound.py b/src/components/sound.py
index 18d2a65..118ea23 100644
--- a/src/components/sound.py
+++ b/src/components/sound.py
@@ -1,8 +1,8 @@
from PyQt5 import QtGui, QtCore, QtWidgets
import os
-from component import Component
-from toolkit.frame import BlankFrame
+from ..component import Component
+from ..toolkit.frame import BlankFrame
class Component(Component):
diff --git a/src/components/spectrum.py b/src/components/spectrum.py
index 6675f5b..d1f8fb6 100644
--- a/src/components/spectrum.py
+++ b/src/components/spectrum.py
@@ -6,10 +6,10 @@ import subprocess
import time
import logging
-from component import Component
-from toolkit.frame import BlankFrame, scale
-from toolkit import checkOutput, connectWidget
-from toolkit.ffmpeg import (
+from ..component import Component
+from ..toolkit.frame import BlankFrame, scale
+from ..toolkit import checkOutput, connectWidget
+from ..toolkit.ffmpeg import (
openPipe, closePipe, getAudioDuration, FfmpegVideo, exampleSound
)
diff --git a/src/components/text.py b/src/components/text.py
index 32a108e..e8c5a9c 100644
--- a/src/components/text.py
+++ b/src/components/text.py
@@ -4,8 +4,8 @@ from PyQt5 import QtGui, QtCore, QtWidgets
import os
import logging
-from component import Component
-from toolkit.frame import FramePainter, PaintColor
+from ..component import Component
+from ..toolkit.frame import FramePainter, PaintColor
log = logging.getLogger('AVP.Components.Text')
diff --git a/src/components/video.py b/src/components/video.py
index 8ad21b5..070940d 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -5,10 +5,10 @@ import math
import subprocess
import logging
-from component import Component
-from toolkit.frame import BlankFrame, scale
-from toolkit.ffmpeg import openPipe, closePipe, testAudioStream, FfmpegVideo
-from toolkit import checkOutput
+from ..component import Component
+from ..toolkit.frame import BlankFrame, scale
+from ..toolkit.ffmpeg import openPipe, closePipe, testAudioStream, FfmpegVideo
+from ..toolkit import checkOutput
log = logging.getLogger('AVP.Components.Video')
diff --git a/src/components/waveform.py b/src/components/waveform.py
index cbfc47f..1a6035f 100644
--- a/src/components/waveform.py
+++ b/src/components/waveform.py
@@ -6,10 +6,10 @@ import math
import subprocess
import logging
-from component import Component
-from toolkit.frame import BlankFrame, scale
-from toolkit import checkOutput
-from toolkit.ffmpeg import (
+from ..component import Component
+from ..toolkit.frame import BlankFrame, scale
+from ..toolkit import checkOutput
+from ..toolkit.ffmpeg import (
openPipe, closePipe, getAudioDuration, FfmpegVideo, exampleSound
)
diff --git a/src/core.py b/src/core.py
index d7445c9..bc6f9b4 100644
--- a/src/core.py
+++ b/src/core.py
@@ -9,12 +9,12 @@ import json
from importlib import import_module
import logging
-import toolkit
+from . import toolkit
log = logging.getLogger('AVP.Core')
STDOUT_LOGLVL = logging.WARNING
-FILE_LOGLVL = None
+FILE_LOGLVL = logging.ERROR
class Core:
@@ -47,7 +47,7 @@ class Core:
yield name
log.debug('Importing component modules')
self.modules = [
- import_module('components.%s' % name)
+ import_module('.components.%s' % name, __package__)
for name in findComponents()
]
# store canonical module names and indexes
@@ -426,7 +426,7 @@ class Core:
def newVideoWorker(self, loader, audioFile, outputPath):
'''loader is MainWindow or Command object which must own the thread'''
- import video_thread
+ from . import video_thread
self.videoThread = QtCore.QThread(loader)
videoWorker = video_thread.Worker(
loader, audioFile, outputPath, self.selectedComponents
@@ -450,8 +450,8 @@ class Core:
@classmethod
def storeSettings(cls):
'''Store settings/paths to directories as class variables'''
- from __init__ import wd
- from toolkit.ffmpeg import findFfmpeg
+ from .__init__ import wd
+ from .toolkit.ffmpeg import findFfmpeg
cls.wd = wd
dataDir = QtCore.QStandardPaths.writableLocation(
diff --git a/src/gui/actions.py b/src/gui/actions.py
index 8e867b9..eb7b953 100644
--- a/src/gui/actions.py
+++ b/src/gui/actions.py
@@ -5,7 +5,7 @@ from PyQt5.QtWidgets import QUndoCommand
import os
from copy import copy
-from core import Core
+from ..core import Core
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
diff --git a/src/gui/mainwindow.py b/src/gui/mainwindow.py
index 75534c2..da8370d 100644
--- a/src/gui/mainwindow.py
+++ b/src/gui/mainwindow.py
@@ -16,12 +16,12 @@ import filecmp
import time
import logging
-from core import Core
-import gui.preview_thread as preview_thread
-from gui.preview_win import PreviewWindow
-from gui.presetmanager import PresetManager
-from gui.actions import *
-from toolkit import (
+from ..core import Core
+from . import preview_thread
+from .preview_win import PreviewWindow
+from .presetmanager import PresetManager
+from .actions import *
+from ..toolkit import (
disableWhenEncoding, disableWhenOpeningProject, checkOutput, blockSignals
)
@@ -65,7 +65,6 @@ class MainWindow(QtWidgets.QMainWindow):
self.settings = Core.settings
# Register clean-up functions
- signal.signal(signal.SIGINT, self.terminate)
atexit.register(self.cleanUp)
# Create stack of undoable user actions
diff --git a/src/gui/presetmanager.py b/src/gui/presetmanager.py
index 2445760..1e47a7f 100644
--- a/src/gui/presetmanager.py
+++ b/src/gui/presetmanager.py
@@ -7,9 +7,9 @@ import string
import os
import logging
-from toolkit import badName
-from core import Core
-from gui.actions import *
+from ..toolkit import badName
+from ..core import Core
+from .actions import *
log = logging.getLogger('AVP.Gui.PresetManager')
diff --git a/src/gui/preview_thread.py b/src/gui/preview_thread.py
index d3e0581..7829476 100644
--- a/src/gui/preview_thread.py
+++ b/src/gui/preview_thread.py
@@ -10,8 +10,8 @@ from queue import Queue, Empty
import os
import logging
-from toolkit.frame import Checkerboard
-from toolkit import disableWhenOpeningProject
+from ..toolkit.frame import Checkerboard
+from ..toolkit import disableWhenOpeningProject
log = logging.getLogger("AVP.Gui.PreviewThread")
diff --git a/src/main.py b/src/main.py
index 126e4a8..5fabda3 100644
--- a/src/main.py
+++ b/src/main.py
@@ -3,7 +3,7 @@ import sys
import os
import logging
-from __init__ import wd
+from .__init__ import wd
log = logging.getLogger('AVP.Main')
@@ -12,6 +12,7 @@ log = logging.getLogger('AVP.Main')
def main():
app = QtWidgets.QApplication(sys.argv)
app.setApplicationName("audio-visualizer")
+ proj = None
# Determine mode
mode = 'GUI'
@@ -23,19 +24,17 @@ def main():
else:
# opening a project file with gui
proj = sys.argv[1]
- else:
- # normal gui launch
- proj = None
# Launch program
if mode == 'commandline':
- from command import Command
+ from .command import Command
main = Command()
+ main.parseArgs()
log.debug("Finished creating command object")
elif mode == 'GUI':
- from gui.mainwindow import MainWindow
+ from .gui.mainwindow import MainWindow
window = uic.loadUi(os.path.join(wd, "gui", "mainwindow.ui"))
# window.adjustSize()
diff --git a/src/toolkit/__init__.py b/src/toolkit/__init__.py
index 3fca275..55e5f84 100644
--- a/src/toolkit/__init__.py
+++ b/src/toolkit/__init__.py
@@ -1 +1 @@
-from toolkit.common import *
+from .common import *
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index 419d491..3298c04 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -10,8 +10,8 @@ import signal
from queue import PriorityQueue
import logging
-import core
-from toolkit.common import checkOutput, pipeWrapper
+from .. import core
+from .common import checkOutput, pipeWrapper
log = logging.getLogger('AVP.Toolkit.Ffmpeg')
@@ -90,7 +90,7 @@ class FfmpegVideo:
self.frameBuffer.task_done()
def fillBuffer(self):
- from component import ComponentError
+ from ..component import ComponentError
if core.Core.logEnabled:
logFilename = os.path.join(
core.Core.logDir, 'render_%s.log' % str(self.component.compPos)
@@ -144,7 +144,7 @@ def openPipe(commandList, **kwargs):
def closePipe(pipe):
pipe.stdout.close()
- pipe.send_signal(signal.SIGINT)
+ pipe.send_signal(signal.SIGTERM)
def findFfmpeg():
diff --git a/src/toolkit/frame.py b/src/toolkit/frame.py
index 0e200b5..f2511fe 100644
--- a/src/toolkit/frame.py
+++ b/src/toolkit/frame.py
@@ -9,7 +9,7 @@ import os
import math
import logging
-import core
+from .. import core
log = logging.getLogger('AVP.Toolkit.Frame')
diff --git a/src/video_thread.py b/src/video_thread.py
index 0a39f28..31331a3 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -19,9 +19,9 @@ import time
import signal
import logging
-from component import ComponentError
-from toolkit.frame import Checkerboard
-from toolkit.ffmpeg import (
+from .component import ComponentError
+from .toolkit.frame import Checkerboard
+from .toolkit.ffmpeg import (
openPipe, readAudioFile,
getAudioDuration, createFfmpegCommand
)
@@ -400,7 +400,7 @@ class Worker(QtCore.QObject):
comp.cancel()
try:
- self.out_pipe.send_signal(signal.SIGINT)
+ self.out_pipe.send_signal(signal.SIGTERM)
except Exception:
pass
--
cgit v1.2.3
From a42ea1cd69fcf3f6c1b2ff79871cd00f24b95118 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Fri, 22 Apr 2022 17:10:35 -0400
Subject: add commandline option for tests. add first tests
---
.gitignore | 10 ++++++----
src/command.py | 39 ++++++++++++++++++++++++++++++++-------
src/tests/__init__.py | 32 ++++++++++++++++++++++++++++++++
src/tests/data/test.jpg | Bin 0 -> 48766 bytes
src/tests/data/test.ogg | Bin 0 -> 30043 bytes
src/tests/data/test.png | Bin 0 -> 220 bytes
src/tests/test_core_init.py | 19 +++++++++++++++++++
src/tests/test_export_classic.py | 5 +++++
8 files changed, 94 insertions(+), 11 deletions(-)
create mode 100644 src/tests/__init__.py
create mode 100644 src/tests/data/test.jpg
create mode 100644 src/tests/data/test.ogg
create mode 100644 src/tests/data/test.png
create mode 100644 src/tests/test_core_init.py
create mode 100644 src/tests/test_export_classic.py
diff --git a/.gitignore b/.gitignore
index 380168f..1595776 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,11 @@
__pycache__
*.py[cod]
-build/*
-dist/*
-env/*
-.vscode/*
+*.egg-info
+.pytest_cache
+build/
+dist/
+env/
+.vscode/
*.mkv
*.mp4
*.wav
diff --git a/src/command.py b/src/command.py
index cd3c6c3..49026c6 100644
--- a/src/command.py
+++ b/src/command.py
@@ -9,21 +9,33 @@ import os
import sys
import time
import signal
+import logging
-from core import Core
+from . import core
+
+
+log = logging.getLogger('AVP.Commandline')
class Command(QtCore.QObject):
+ """
+ This replaces the GUI MainWindow when in commandline mode.
+ """
createVideo = QtCore.pyqtSignal()
def __init__(self):
QtCore.QObject.__init__(self)
- self.core = Core()
- Core.mode = 'commandline'
+ self.core = core.Core()
+ core.Core.mode = 'commandline'
self.dataDir = self.core.dataDir
self.canceled = False
+ self.settings = core.Core.settings
+
+ # ctrl-c stops the export thread
+ signal.signal(signal.SIGINT, self.stopVideo)
+ def parseArgs(self):
self.parser = argparse.ArgumentParser(
description='Create a visualization for an audio file',
epilog='EXAMPLE COMMAND: main.py myvideotemplate.avp '
@@ -31,6 +43,10 @@ class Command(QtCore.QObject):
'-c 0 image path=~/Pictures/thisWeeksPicture.jpg '
'-c 1 video "preset=My Logo" -c 2 vis layout=classic'
)
+ self.parser.add_argument(
+ '-t', '--test', action='store_true',
+ help='run tests and generate a logfile to report a bug'
+ )
self.parser.add_argument(
'-i', '--input', metavar='SOUND',
help='input audio file'
@@ -55,7 +71,10 @@ class Command(QtCore.QObject):
nargs='*', action='append')
self.args = self.parser.parse_args()
- self.settings = Core.settings
+
+ if self.args.test:
+ self.runTests()
+ quit(0)
if self.args.projpath:
projPath = self.args.projpath
@@ -92,9 +111,6 @@ class Command(QtCore.QObject):
for arg in args:
self.core.selectedComponents[i].command(arg)
- # ctrl-c stops the export thread
- signal.signal(signal.SIGINT, self.stopVideo)
-
if self.args.export and self.args.projpath:
errcode, data = self.core.parseAvFile(projPath)
for key, value in data['WindowFields']:
@@ -188,3 +204,12 @@ class Command(QtCore.QObject):
return
return None
+
+ def runTests(self):
+ core.FILE_LOGLVL = logging.DEBUG
+ from . import tests
+ test_report = os.path.join(core.Core.logDir, "test_report.log")
+ tests.run(test_report)
+ with open(test_report, "r") as f:
+ output = f.readlines()
+ print("".join(output))
diff --git a/src/tests/__init__.py b/src/tests/__init__.py
new file mode 100644
index 0000000..f2b2ff1
--- /dev/null
+++ b/src/tests/__init__.py
@@ -0,0 +1,32 @@
+import pytest
+import os
+import sys
+from ..core import Core
+from ..command import Command
+
+
+@pytest.fixture
+def core():
+ return Core()
+
+
+@pytest.fixture
+def command():
+ """Like a MainWindow for commandline mode, this owns the Core"""
+ return Command()
+
+
+def run(logFile):
+ """Run Pytest, which then imports and runs all tests in this module."""
+ with open(logFile, "w") as f:
+ # temporarily redirect stdout to a text file so we capture pytest's output
+ sys.stdout = f
+ try:
+ val = pytest.main([
+ os.path.dirname(__file__),
+ "-s", # disable pytest's internal capturing of stdout etc.
+ ])
+ finally:
+ sys.stdout = sys.__stdout__
+
+ return val
diff --git a/src/tests/data/test.jpg b/src/tests/data/test.jpg
new file mode 100644
index 0000000..86266d9
Binary files /dev/null and b/src/tests/data/test.jpg differ
diff --git a/src/tests/data/test.ogg b/src/tests/data/test.ogg
new file mode 100644
index 0000000..46af76c
Binary files /dev/null and b/src/tests/data/test.ogg differ
diff --git a/src/tests/data/test.png b/src/tests/data/test.png
new file mode 100644
index 0000000..f1ffd4a
Binary files /dev/null and b/src/tests/data/test.png differ
diff --git a/src/tests/test_core_init.py b/src/tests/test_core_init.py
new file mode 100644
index 0000000..696533a
--- /dev/null
+++ b/src/tests/test_core_init.py
@@ -0,0 +1,19 @@
+from .__init__ import core
+
+
+def test_component_names(core):
+ assert core.compNames == [
+ 'Classic Visualizer',
+ 'Color',
+ "Conway's Game of Life",
+ 'Image',
+ 'Sound',
+ 'Spectrum',
+ 'Title Text',
+ 'Video',
+ 'Waveform',
+ ]
+
+
+def test_moduleindex(core):
+ assert core.moduleIndexFor("Classic Visualizer") == 0
diff --git a/src/tests/test_export_classic.py b/src/tests/test_export_classic.py
new file mode 100644
index 0000000..a6d3e8c
--- /dev/null
+++ b/src/tests/test_export_classic.py
@@ -0,0 +1,5 @@
+from .__init__ import command
+
+
+def test_export_classic_visualizer_default(command):
+ assert command
--
cgit v1.2.3
From c40c124409a6c1af8ab3444fd47bec6997be5cb3 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Fri, 22 Apr 2022 17:24:29 -0400
Subject: authors file is just a list of names
---
AUTHORS | 16 +++++-----------
1 file changed, 5 insertions(+), 11 deletions(-)
diff --git a/AUTHORS b/AUTHORS
index 417d97e..1b717a2 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,11 +1,5 @@
-Original version created by Martin Kaistra
-Version 2 created by tassaron and DH4
-
-Contributors:
-* Martin Kaistra
-* Brianna Rainey
-* DH4
-
-Pull Requests By:
-* HunterwolfAT
-* rikai
+Martin Kaistra ,
+Brianna Rainey ,
+DH4,
+HunterwolfAT,
+rikai
--
cgit v1.2.3
From b382d0620199e5b456ced30bb8ea713def98b87e Mon Sep 17 00:00:00 2001
From: tassaron
Date: Fri, 22 Apr 2022 20:39:49 -0400
Subject: some work on the readme (install instructions not done yet)
---
README.md | 83 +++++++++++++++++++++++++++++++++------------------------------
1 file changed, 44 insertions(+), 39 deletions(-)
diff --git a/README.md b/README.md
index 1dee874..abb9a73 100644
--- a/README.md
+++ b/README.md
@@ -1,52 +1,58 @@
-audio-visualizer-python
-=======================
+# audio-visualizer-python
**We need a good name that is not as generic as "audio-visualizer-python"!**
This is a little GUI tool which creates an audio visualization video from an input audio file. Different components can be added and layered to change the resulting video and add images, videos, gradients, text, etc. Encoding options can be changed with a variety of different output containers.
-Projects can be created from the GUI and used in commandline mode for easy automation of video production. Create a template project named `template` with your typical visualizers and watermarks, and add text to the top layer from commandline:
-`avp template -c 99 text "title=Episode 371" -i /this/weeks/audio.ogg -o out`
+Projects can be created from the GUI and used in commandline mode for easy automation of video production. For more information use `avp --help` or for help with a particular component use `avp -c 0 componentName help`.
-For more information use `avp --help` or for help with a particular component use `avp -c 0 componentName help`.
+The program works on Linux, macOS, and Windows. If you encounter problems running it or have other bug reports or features that you wish to see implemented, please fork the project and submit a pull request and/or file an issue on this project. To gather extra information to help us debug the problem, run `avp -t` and include the text file it creates.
-The program works on Linux, macOS, and Windows. If you encounter problems running it or have other bug reports or features that you wish to see implemented, please fork the project and submit a pull request and/or file an issue on this project.
-Dependencies
-------------
-Python 3.10, FFmpeg 4.4.1, PyQt5 (Qt v5.15.3), Pillow-SIMD, NumPy
+# Examples
+## What the app creates
+* **[YouTube: A day in spring](https://www.youtube.com/watch?v=-M3jR1NuJHM)**
-**Note:** Pillow may be used as a drop-in replacement for Pillow-SIMD if problems are encountered installing. However this will result in much slower video export times. For help installing Pillow-SIMD, see the [Pillow installation guide](http://pillow.readthedocs.io/en/3.1.x/installation.html).
+## Graphical version demo
+* [YouTube: Audio Visualizer demonstration](https://www.youtube.com/watch?v=EVt2ckQs1Yg)
-Installation
-------------
-### Manual installation on Ubuntu 22.04
+## Commandline snippets
+* Create a simple visualization: `avp -c 0 classic -i something.mp3 -o output.mp4`
+* Create the same visualization but with split layout and more extreme fluctuations: `avp -c 0 classic layout=split scale=40 -i something.mp3 -o output.mp4`
+* Create a template project named `template` with your typical visualizers and watermarks using the GUI, then add text to the top layer from commandline: `avp template -c 99 text "title=Episode 371" -i /this/weeks/audio.ogg -o out`
+
+
+# Dependencies
+* Python 3.10
+* FFmpeg 4.4.1
+* PyQt5 (Qt v5.15.3)
+* Pillow
+* NumPy
+* Pytest
+
+
+# Installation
+## Manual installation on Linux (tested on Ubuntu 22.04)
* Install ffmpeg: `sudo apt install ffmpeg`
* Install pip: `sudo apt install python3-pip`
-* Install PyQt5: `sudo apt install python3-pyqt5`
-* Install dependencies to compile Pillow-SIMD: `sudo apt install python3-dev libtiff5-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python-tk`
-* Download audio-visualizer-python from this repository and run `pip3 install .` in this directory
-* Run the program with `avp` or `python3 -m avpython`
-* (Optional Note) If using a virtual environmennt, PyQt5 doesn't seem to work when installed from the setup.py. You can use `--system-site-packages --copies` to copy the system site-packages into your venv
-
-
-### Manual installation on Windows
-* **Warning:** [Compiling Pillow is difficult on Windows](http://pillow.readthedocs.io/en/3.1.x/installation.html#building-on-windows) and required for the best experience.
-* Download and install Python 3.6 from [https://www.python.org/downloads/windows/](https://www.python.org/downloads/windows/)
-* Add Python to your system PATH (it will ask during the installation process).
-* Brave treacherous valley of getting prerequisites to [compile Pillow on Windows](https://www.pypkg.com/pypi/pillow-simd/f/winbuild/README.md). This is necessary because binary builds for Pillow-SIMD are not available.
-* **Alternative:** install Pillow instead of Pillow-SIMD, for which binaries *are* available. However this will result in much slower video export times.
-* Open command prompt and run: `pip install pyqt5 numpy pillow-simd`
-* Download and install ffmpeg from [https://www.ffmpeg.org/download.html](https://www.ffmpeg.org/download.html). You can use the static builds.
-* Add ffmpeg to your system PATH, too. [How to edit the PATH on Windows.](https://www.java.com/en/download/help/path.xml)
+* Download this repo and run `pip3 install --user .` in this directory
+* Run the program with `avp` or `python3 -m avp`
+* **Sidenote:** If using a Python virtual environment, `pip3 install --system-site-packages --copies` may be useful to copy system packages when installing. This can be useful if you have difficulty installing PyQt5 using pip or wish to use the distro's package manager instead.
-Download audio-visualizer-python from this repository and run it from the command line with `python main.py`.
-### Manual installation on macOS
+## Manual installation on Windows
+* Install Python from the Windows Store
+* Add Python to your system PATH (it should ask during the installation process)
+* Download this repo
+* Open command prompt, `cd` into the repo directory, and run: `pip install --user .`
+* Download and install ffmpeg from [https://www.ffmpeg.org/download.html](https://www.ffmpeg.org/download.html). You can use the static builds.
+* Add ffmpeg to the system PATH as well, or copy ffmpeg.exe into the directory with the rest of the app
+* Now run `avp` from a command prompt window to start the app
+
+## Manual installation on macOS
* **[Outdated]**: No one has updated these instructions for a while.
* Install [Homebrew](http://brew.sh/)
* Use the following commands to install the needed dependencies:
-
```
brew install python3
brew install ffmpeg --with-fdk-aac --with-ffplay --with-freetype --with-libass --with-libquvi --with-libvorbis --with-libvpx --with-opus --with-x265
@@ -57,14 +63,13 @@ pip3 install --upgrade pip
pip3 install pillow
pip3 install numpy
```
-
Download audio-visualizer-python from this repository and run it with `python3 main.py`.
-Example
--------
-You can find an example video here:
-[Youtube: A day in spring](https://www.youtube.com/watch?v=-M3jR1NuJHM)
-License
--------
+# Faster Export Times
+* [Pillow-SIMD](https://github.com/uploadcare/pillow-simd) may be used as a drop-in replacement for Pillow if you desire faster video export times, but it must be compiled from source. For help installing dependencies to compile Pillow-SIMD, see the [Pillow installation guide](http://pillow.readthedocs.io/en/3.1.x/installation.html).
+* **Warning:** [Compiling from source is difficult on Windows](http://pillow.readthedocs.io/en/3.1.x/installation.html#building-on-windows).
+
+
+# License
audio-visualizer-python is licensed under the MIT license.
--
cgit v1.2.3
From 42ad29a5be09f44a92b6aede29072ef0b19c6dac Mon Sep 17 00:00:00 2001
From: tassaron
Date: Mon, 25 Apr 2022 13:50:37 -0400
Subject: fix ImportError
---
src/component.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/component.py b/src/component.py
index 33c7657..1e10f66 100644
--- a/src/component.py
+++ b/src/component.py
@@ -808,7 +808,7 @@ class ComponentError(RuntimeError):
return
ComponentError.lastTime = time.time()
- from toolkit import formatTraceback
+ from .toolkit import formatTraceback
if sys.exc_info()[0] is not None:
string = (
"%s component (#%s): %s encountered %s %s: %s" % (
--
cgit v1.2.3
From 0d59d29945d1538d4de073d92aa6a287d7907829 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Mon, 25 Apr 2022 13:51:08 -0400
Subject: add --debug option and rename -e so it's more explicit
---
src/command.py | 25 ++++++++++++++++++-------
1 file changed, 18 insertions(+), 7 deletions(-)
diff --git a/src/command.py b/src/command.py
index 49026c6..0aab0f7 100644
--- a/src/command.py
+++ b/src/command.py
@@ -43,10 +43,6 @@ class Command(QtCore.QObject):
'-c 0 image path=~/Pictures/thisWeeksPicture.jpg '
'-c 1 video "preset=My Logo" -c 2 vis layout=classic'
)
- self.parser.add_argument(
- '-t', '--test', action='store_true',
- help='run tests and generate a logfile to report a bug'
- )
self.parser.add_argument(
'-i', '--input', metavar='SOUND',
help='input audio file'
@@ -56,8 +52,16 @@ class Command(QtCore.QObject):
help='output video file'
)
self.parser.add_argument(
- '-e', '--export', action='store_true',
- help='use input and output files from project file'
+ '--export-project', action='store_true',
+ help='ignore -i and -o, use input and output from project file'
+ )
+ self.parser.add_argument(
+ '--test', action='store_true',
+ help='run tests, generate logfiles, then exit'
+ )
+ self.parser.add_argument(
+ '--debug', action='store_true',
+ help='create bigger logfiles while program is running'
)
# optional arguments
@@ -72,6 +76,11 @@ class Command(QtCore.QObject):
self.args = self.parser.parse_args()
+ if self.args.debug:
+ core.FILE_LOGLVL = logging.DEBUG
+ core.STDOUT_LOGLVL = logging.DEBUG
+ core.Core.makeLogger()
+
if self.args.test:
self.runTests()
quit(0)
@@ -111,7 +120,7 @@ class Command(QtCore.QObject):
for arg in args:
self.core.selectedComponents[i].command(arg)
- if self.args.export and self.args.projpath:
+ if self.args.export_project and self.args.projpath:
errcode, data = self.core.parseAvFile(projPath)
for key, value in data['WindowFields']:
if 'outputFile' in key:
@@ -139,6 +148,7 @@ class Command(QtCore.QObject):
self.worker = self.core.newVideoWorker(
self, input, output
)
+ # quit(0) after video is created
self.worker.videoCreated.connect(self.videoCreated)
self.lastProgressUpdate = time.time()
self.worker.progressBarSetText.connect(self.progressBarSetText)
@@ -207,6 +217,7 @@ class Command(QtCore.QObject):
def runTests(self):
core.FILE_LOGLVL = logging.DEBUG
+ core.Core.makeLogger()
from . import tests
test_report = os.path.join(core.Core.logDir, "test_report.log")
tests.run(test_report)
--
cgit v1.2.3
From fe3251c528df7eff51be6ecbb18261990b524944 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Mon, 25 Apr 2022 13:52:03 -0400
Subject: add more logging to the video export
---
src/video_thread.py | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/src/video_thread.py b/src/video_thread.py
index 31331a3..2fe264a 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -132,6 +132,7 @@ class Worker(QtCore.QObject):
@pyqtSlot()
def createVideo(self):
+ log.debug("Video worker received signal to createVideo")
numpy.seterr(divide='ignore')
self.encoding.emit(True)
self.extraAudio = []
@@ -151,6 +152,7 @@ class Worker(QtCore.QObject):
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
# READ AUDIO, INITIALIZE COMPONENTS, OPEN A PIPE TO FFMPEG
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
+ log.debug("Determining length of audio...")
if any([
True if 'pcm' in comp.properties() else False
for comp in self.components
@@ -192,7 +194,11 @@ class Worker(QtCore.QObject):
progressBarSetText=self.progressBarSetText
)
except ComponentError:
- pass
+ log.warning(
+ '#%s %s encountered an error in its preFrameRender method',
+ compNo,
+ comp
+ )
compProps = comp.properties()
if 'error' in compProps or comp._lockedError is not None:
@@ -216,9 +222,11 @@ class Worker(QtCore.QObject):
comp._error.emit(errMsg, compError[1])
break
if 'static' in compProps:
+ log.info('Saving static frame from #%s %s', compNo, comp)
self.staticComponents[compNo] = \
comp.frameRender(0).copy()
+ log.debug("Checking if a component wishes to cancel the export...")
if self.canceled:
if canceledByComponent:
log.error(
@@ -233,7 +241,7 @@ class Worker(QtCore.QObject):
self.cancelExport()
return
- # Merge consecutive static component frames together
+ log.info("Merging consecutive static component frames")
for compNo in range(len(self.components)):
if compNo not in self.staticComponents \
or compNo + 1 not in self.staticComponents:
--
cgit v1.2.3
From 17b4cba6d1a5f24b4de3b53f79b93dd409e28ccd Mon Sep 17 00:00:00 2001
From: tassaron
Date: Tue, 26 Apr 2022 13:10:29 -0400
Subject: tests for commandline argument parsing
---
src/command.py | 6 +++++-
src/main.py | 12 +++++------
src/tests/__init__.py | 4 ++++
src/tests/test_commandline_parser.py | 39 ++++++++++++++++++++++++++++++++++++
src/tests/test_export_classic.py | 5 -----
5 files changed, 54 insertions(+), 12 deletions(-)
create mode 100644 src/tests/test_commandline_parser.py
delete mode 100644 src/tests/test_export_classic.py
diff --git a/src/command.py b/src/command.py
index 0aab0f7..db72de7 100644
--- a/src/command.py
+++ b/src/command.py
@@ -133,14 +133,18 @@ class Command(QtCore.QObject):
if 'audioFile' in key:
input = value
self.createAudioVisualisation(input, output)
+ return "commandline"
elif self.args.input and self.args.output:
self.createAudioVisualisation(self.args.input, self.args.output)
+ return "commandline"
- elif 'help' not in sys.argv:
+ elif 'help' not in sys.argv and self.args.projpath is None and '--debug' not in sys.argv:
self.parser.print_help()
quit(1)
+ return "GUI"
+
def createAudioVisualisation(self, input, output):
self.core.selectedComponents = list(
reversed(self.core.selectedComponents))
diff --git a/src/main.py b/src/main.py
index 5fabda3..39fa997 100644
--- a/src/main.py
+++ b/src/main.py
@@ -30,25 +30,25 @@ def main():
from .command import Command
main = Command()
- main.parseArgs()
+ mode = main.parseArgs()
log.debug("Finished creating command object")
- elif mode == 'GUI':
+ # Both branches here may occur in one execution:
+ # Commandline parsing could change mode back to GUI
+ if mode == 'GUI':
from .gui.mainwindow import MainWindow
window = uic.loadUi(os.path.join(wd, "gui", "mainwindow.ui"))
- # window.adjustSize()
desc = QtWidgets.QDesktopWidget()
dpi = desc.physicalDpiX()
-
- topMargin = 0 if (dpi == 96) else int(10 * (dpi / 96))
+ log.info("Detected screen DPI: %s", dpi)
+
window.resize(
int(window.width() *
(dpi / 96)),
int(window.height() *
(dpi / 96))
)
- # window.verticalLayout_2.setContentsMargins(0, topMargin, 0, 0)
main = MainWindow(window, proj)
log.debug("Finished creating main window")
diff --git a/src/tests/__init__.py b/src/tests/__init__.py
index f2b2ff1..062dca7 100644
--- a/src/tests/__init__.py
+++ b/src/tests/__init__.py
@@ -16,6 +16,10 @@ def command():
return Command()
+def getTestData(filename):
+ return os.path.join(Core.wd, 'tests', 'data', filename)
+
+
def run(logFile):
"""Run Pytest, which then imports and runs all tests in this module."""
with open(logFile, "w") as f:
diff --git a/src/tests/test_commandline_parser.py b/src/tests/test_commandline_parser.py
new file mode 100644
index 0000000..d672441
--- /dev/null
+++ b/src/tests/test_commandline_parser.py
@@ -0,0 +1,39 @@
+import sys
+import pytest
+from .__init__ import command
+
+
+def test_commandline_help(command):
+ sys.argv = ['', '--help']
+ with pytest.raises(SystemExit):
+ command.parseArgs()
+
+
+def test_commandline_help_if_bad_args(command):
+ sys.argv = ['', '--junk']
+ with pytest.raises(SystemExit):
+ command.parseArgs()
+
+
+def test_commandline_launches_gui_if_debug(command):
+ sys.argv = ['', '--debug']
+ mode = command.parseArgs()
+ assert mode == "GUI"
+
+
+def test_commandline_launches_gui_if_debug_with_project(command):
+ sys.argv = ['', 'test', '--debug']
+ mode = command.parseArgs()
+ assert mode == "GUI"
+
+
+def test_commandline_export_creates_audio_visualization(command):
+ didCallFunction = False
+ def captureFunction(*args):
+ nonlocal didCallFunction
+ didCallFunction = True
+
+ sys.argv = ['', '-c', '0', 'classic', '-i', '_', '-o', '_']
+ command.createAudioVisualisation = captureFunction
+ command.parseArgs()
+ assert didCallFunction
diff --git a/src/tests/test_export_classic.py b/src/tests/test_export_classic.py
deleted file mode 100644
index a6d3e8c..0000000
--- a/src/tests/test_export_classic.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from .__init__ import command
-
-
-def test_export_classic_visualizer_default(command):
- assert command
--
cgit v1.2.3
From 973c28a2947798dfd7efe59ac9b55f2585504679 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Wed, 27 Apr 2022 12:42:45 -0400
Subject: fix segmentation fault when rendering Color component Pillow's
ImageQt is a subclass of QImage
---
src/toolkit/frame.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/toolkit/frame.py b/src/toolkit/frame.py
index f2511fe..520bd43 100644
--- a/src/toolkit/frame.py
+++ b/src/toolkit/frame.py
@@ -22,7 +22,8 @@ class FramePainter(QtGui.QPainter):
'''
def __init__(self, width, height):
image = BlankFrame(width, height)
- self.image = QtGui.QImage(ImageQt(image))
+ log.debug("Creating QImage from PIL image object")
+ self.image = ImageQt(image)
super().__init__(self.image)
def setPen(self, penStyle):
--
cgit v1.2.3
From b9a16165106709151b579e82b0aa071d8bb43ad4 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Wed, 27 Apr 2022 13:01:38 -0400
Subject: add logging to Color component. remove unused imports
---
src/components/color.py | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/src/components/color.py b/src/components/color.py
index 6336194..8d0edd2 100644
--- a/src/components/color.py
+++ b/src/components/color.py
@@ -1,13 +1,13 @@
-from PIL import Image, ImageDraw
-from PyQt5 import QtGui, QtCore, QtWidgets
-from PyQt5.QtGui import QColor
-from PIL.ImageQt import ImageQt
-import os
+from PyQt5 import QtGui
+import logging
from ..component import Component
from ..toolkit.frame import BlankFrame, FloodFrame, FramePainter, PaintColor
+log = logging.getLogger('AVP.Components.Color')
+
+
class Component(Component):
name = 'Color'
version = '1.0.0'
@@ -89,6 +89,7 @@ class Component(Component):
return ['static']
def frameRender(self, frameNo):
+ log.debug("Color component is drawing frame #%s", frameNo)
return self.drawFrame(self.width, self.height)
def drawFrame(self, width, height):
--
cgit v1.2.3
From 4e6159725227de952f0c578595423aa51d9a0b34 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 28 Apr 2022 19:26:09 -0400
Subject: change call to superclass __init__ to super()
---
src/command.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/command.py b/src/command.py
index db72de7..267117e 100644
--- a/src/command.py
+++ b/src/command.py
@@ -25,7 +25,7 @@ class Command(QtCore.QObject):
createVideo = QtCore.pyqtSignal()
def __init__(self):
- QtCore.QObject.__init__(self)
+ super()
self.core = core.Core()
core.Core.mode = 'commandline'
self.dataDir = self.core.dataDir
--
cgit v1.2.3
From 1fda5fbe8121f15f7a6e13ddedefb0cf07ae5d48 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 28 Apr 2022 19:26:31 -0400
Subject: cast int to str in ffmpeg command
---
src/toolkit/ffmpeg.py | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index 3298c04..37c1511 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -55,7 +55,7 @@ class FfmpegVideo:
core.Core.FFMPEG_BIN,
'-thread_queue_size', '512',
'-r', str(self.frameRate),
- '-stream_loop', self.loopValue,
+ '-stream_loop', str(self.loopValue),
'-i', self.inputPath,
'-f', 'image2pipe',
'-pix_fmt', 'rgba',
@@ -229,12 +229,9 @@ def createFfmpegCommand(inputFile, outputFile, components, duration=-1):
# INPUT VIDEO
'-f', 'rawvideo',
'-vcodec', 'rawvideo',
- '-s', '%sx%s' % (
- Core.settings.value('outputWidth'),
- Core.settings.value('outputHeight'),
- ),
+ '-s', f'{Core.settings.value("outputWidth")}x{Core.settings.value("outputHeight")}',
'-pix_fmt', 'rgba',
- '-r', Core.settings.value('outputFrameRate'),
+ '-r', str(Core.settings.value('outputFrameRate')),
'-t', duration,
'-an', # the video input has no sound
'-i', '-', # the video input comes from a pipe
--
cgit v1.2.3
From a4dff0b3e0aa817822c1a490a423192a8cbf97eb Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 28 Apr 2022 19:48:01 -0400
Subject: remove punctuation from project names at commandline stop someone
shooting themself in the foot by doing `avp /?` on Windows
---
src/main.py | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/src/main.py b/src/main.py
index 39fa997..ec4b8bc 100644
--- a/src/main.py
+++ b/src/main.py
@@ -2,6 +2,8 @@ from PyQt5 import uic, QtWidgets
import sys
import os
import logging
+import re
+import string
from .__init__ import wd
@@ -10,11 +12,8 @@ log = logging.getLogger('AVP.Main')
def main():
- app = QtWidgets.QApplication(sys.argv)
- app.setApplicationName("audio-visualizer")
+ # Determine primary mode
proj = None
-
- # Determine mode
mode = 'GUI'
if len(sys.argv) > 2:
mode = 'commandline'
@@ -22,9 +21,14 @@ def main():
if sys.argv[1].startswith('-'):
mode = 'commandline'
else:
+ # remove unsafe punctuation characters such as \/?*&^%$#
+ sys.argv[1] = re.sub(f'[{re.escape(string.punctuation)}]', '', sys.argv[1])
# opening a project file with gui
proj = sys.argv[1]
+ # Create Qt Application
+ app = QtWidgets.QApplication(sys.argv)
+ app.setApplicationName("audio-visualizer")
# Launch program
if mode == 'commandline':
from .command import Command
@@ -56,5 +60,6 @@ def main():
sys.exit(app.exec_())
+
if __name__ == "__main__":
main()
--
cgit v1.2.3
From b4fc89cfbeacb9dc99eba01b0dbfb0023709fe40 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Fri, 29 Apr 2022 00:22:26 -0400
Subject: document keyboard shortcuts. fix "show ffmpeg command"
---
README.md | 24 ++++++++++++++++++++++++
src/gui/mainwindow.py | 8 +++++---
2 files changed, 29 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index abb9a73..3ca5305 100644
--- a/README.md
+++ b/README.md
@@ -71,5 +71,29 @@ Download audio-visualizer-python from this repository and run it with `python3 m
* **Warning:** [Compiling from source is difficult on Windows](http://pillow.readthedocs.io/en/3.1.x/installation.html#building-on-windows).
+# Keyboard Shortcuts
+| Key Combo | Effect |
+| ------------------------- | -------------------------------------------------- |
+| Ctrl+S | Save Current Project |
+| Ctrl+A | Save Project As... |
+| Ctrl+O | Open Project |
+| Ctrl+N | New Project (prompts to save current project) |
+| Ctrl+Z | Undo |
+| Ctrl+Shift+Z _or_ Ctrl+Y | Redo |
+| Ctrl+T _or_ Insert | Add Component |
+| Ctrl+R _or_ Delete | Remove Component |
+| Ctrl+Space | Focus Component List |
+| Ctrl+Shift+S | Save Component Preset |
+| Ctrl+Shift+C | Remove Preset from Component |
+| Ctrl+Up | Move Selected Component Up |
+| Ctrl+Down | Move Selected Component Down |
+| Ctrl+Home | Move Selected Component to Top |
+| Ctrl+End | Move Selected Component to Bottom |
+| Ctrl+Shift+U | Open Undo History |
+| Ctrl+Shift+F | Show FFmpeg Command |
+| Ctrl+Alt+Shift+R | Force redraw preview (must use `--debug`) |
+| Ctrl+Alt+Shift+A | Dump MainWindow data into log (must use `--debug`) |
+
+
# License
audio-visualizer-python is licensed under the MIT license.
diff --git a/src/gui/mainwindow.py b/src/gui/mainwindow.py
index da8370d..f60befd 100644
--- a/src/gui/mainwindow.py
+++ b/src/gui/mainwindow.py
@@ -758,15 +758,17 @@ class MainWindow(QtWidgets.QMainWindow):
def showFfmpegCommand(self):
from textwrap import wrap
- from toolkit.ffmpeg import createFfmpegCommand
+ from ..toolkit.ffmpeg import createFfmpegCommand
command = createFfmpegCommand(
self.window.lineEdit_audioFile.text(),
self.window.lineEdit_outputFile.text(),
self.core.selectedComponents
)
- lines = wrap(" ".join(command), 49)
+ command = " ".join(command)
+ log.info(f"FFmpeg command: {command}")
+ lines = wrap(command, 49)
self.showMessage(
- msg="Current FFmpeg command:\n\n %s" % " ".join(lines)
+ msg=f"Current FFmpeg command:\n\n{' '.join(lines)}"
)
def addComponent(self, compPos, moduleIndex):
--
cgit v1.2.3
From dee29d0e700d4812bcf4f1a56d4cb2fb2b8cc0d1 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Fri, 29 Apr 2022 12:15:10 -0400
Subject: delay opening logfile until first call to logger fix deleting an open
file if logger changes after parsing commandline args on Windows deleting an
open file raises an exception
---
src/core.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/core.py b/src/core.py
index bc6f9b4..a3757e6 100644
--- a/src/core.py
+++ b/src/core.py
@@ -585,9 +585,9 @@ class Core:
if os.path.exists(log_):
os.remove(log_)
- logFile = logging.FileHandler(logFilename)
+ logFile = logging.FileHandler(logFilename, delay=True)
logFile.setLevel(FILE_LOGLVL)
- libLogFile = logging.FileHandler(libLogFilename)
+ libLogFile = logging.FileHandler(libLogFilename, delay=True)
libLogFile.setLevel(FILE_LOGLVL)
fileFormatter = logging.Formatter(
'[%(asctime)s] %(threadName)-10.10s %(name)-23.23s %(levelname)s: '
--
cgit v1.2.3
From 5a95302bb50766b169cc897a3f5c03c3fb83daa3 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Fri, 29 Apr 2022 12:16:38 -0400
Subject: better log messages when setting window title log before and after
method call instead of just after
---
src/gui/mainwindow.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/gui/mainwindow.py b/src/gui/mainwindow.py
index f60befd..463d028 100644
--- a/src/gui/mainwindow.py
+++ b/src/gui/mainwindow.py
@@ -444,6 +444,7 @@ class MainWindow(QtWidgets.QMainWindow):
@disableWhenOpeningProject
def updateWindowTitle(self):
+ log.debug("Setting main window's title")
appName = 'Audio Visualizer'
try:
if self.currentProject:
@@ -454,7 +455,7 @@ class MainWindow(QtWidgets.QMainWindow):
appName += '*'
except AttributeError:
pass
- log.verbose('Setting window title to %s' % appName)
+ log.verbose(f'Window title is "{appName}"')
self.window.setWindowTitle(appName)
@QtCore.pyqtSlot(int, dict)
--
cgit v1.2.3
From 069edd9086ad7a99c78c5637af23d50a633396cf Mon Sep 17 00:00:00 2001
From: tassaron
Date: Fri, 29 Apr 2022 12:58:26 -0400
Subject: use super().__init__ in the modern python3 style
---
src/command.py | 2 +-
src/gui/preview_thread.py | 4 ++--
src/gui/preview_win.py | 2 +-
src/video_thread.py | 4 ++--
4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/command.py b/src/command.py
index 267117e..cc13684 100644
--- a/src/command.py
+++ b/src/command.py
@@ -25,7 +25,7 @@ class Command(QtCore.QObject):
createVideo = QtCore.pyqtSignal()
def __init__(self):
- super()
+ super().__init__()
self.core = core.Core()
core.Core.mode = 'commandline'
self.dataDir = self.core.dataDir
diff --git a/src/gui/preview_thread.py b/src/gui/preview_thread.py
index 7829476..614b584 100644
--- a/src/gui/preview_thread.py
+++ b/src/gui/preview_thread.py
@@ -23,10 +23,10 @@ class Worker(QtCore.QObject):
error = pyqtSignal(str)
def __init__(self, parent=None, queue=None):
- QtCore.QObject.__init__(self)
+ super().__init__()
parent.newTask.connect(self.createPreviewImage)
parent.processTask.connect(self.process)
- self.parent = parent
+ #self.parent = parent
self.core = parent.core
self.settings = parent.settings
self.queue = queue
diff --git a/src/gui/preview_win.py b/src/gui/preview_win.py
index 27e0a59..426ff66 100644
--- a/src/gui/preview_win.py
+++ b/src/gui/preview_win.py
@@ -10,7 +10,7 @@ class PreviewWindow(QtWidgets.QLabel):
when the window is resized.
'''
def __init__(self, parent, img):
- super(PreviewWindow, self).__init__()
+ super().__init__()
self.parent = parent
self.setFrameStyle(QtWidgets.QFrame.StyledPanel)
self.pixmap = QtGui.QPixmap(img)
diff --git a/src/video_thread.py b/src/video_thread.py
index 2fe264a..4a28261 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -39,13 +39,13 @@ class Worker(QtCore.QObject):
encoding = pyqtSignal(bool)
def __init__(self, parent, inputFile, outputFile, components):
- QtCore.QObject.__init__(self)
+ super().__init__()
self.core = parent.core
self.settings = parent.settings
self.modules = parent.core.modules
parent.createVideo.connect(self.createVideo)
- self.parent = parent
+ #self.parent = parent
self.components = components
self.outputFile = outputFile
self.inputFile = inputFile
--
cgit v1.2.3
From 6f7b3b5f7cb72d09b2b86bd58b2e526515739590 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Fri, 29 Apr 2022 12:59:18 -0400
Subject: rename videoCreated method to stopVideoThread
---
src/core.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/core.py b/src/core.py
index a3757e6..42fd1c3 100644
--- a/src/core.py
+++ b/src/core.py
@@ -432,12 +432,12 @@ class Core:
loader, audioFile, outputPath, self.selectedComponents
)
videoWorker.moveToThread(self.videoThread)
- videoWorker.videoCreated.connect(self.videoCreated)
+ videoWorker.videoCreated.connect(self.stopVideoThread)
self.videoThread.start()
return videoWorker
- def videoCreated(self):
+ def stopVideoThread(self):
self.videoThread.quit()
self.videoThread.wait()
--
cgit v1.2.3
From c2c3f0aa5adf3127b84b3d50da9e1aa655c8a824 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Fri, 29 Apr 2022 21:15:17 -0400
Subject: remove extra window properties from window objects instead of windows
with properties which are windows, windows now have the UI added directly to
them using an argument of `uic.loadUi` Also, DPI scaling moved to MainWindow
__init__
---
src/components/spectrum.py | 6 +-
src/components/video.py | 4 +-
src/components/waveform.py | 6 +-
src/core.py | 2 +-
src/gui/actions.py | 8 +-
src/gui/mainwindow.py | 351 +++++++++++++++++++++++----------------------
src/gui/presetmanager.py | 88 ++++++------
src/gui/preview_win.py | 2 +-
src/main.py | 18 +--
9 files changed, 242 insertions(+), 243 deletions(-)
diff --git a/src/components/spectrum.py b/src/components/spectrum.py
index d1f8fb6..91f2afb 100644
--- a/src/components/spectrum.py
+++ b/src/components/spectrum.py
@@ -30,9 +30,9 @@ class Component(Component):
self.previewSize = (214, 120)
self.previewPipe = None
- if hasattr(self.parent, 'window'):
+ if hasattr(self.parent, 'lineEdit_audioFile'):
# update preview when audio file changes (if genericPreview is off)
- self.parent.window.lineEdit_audioFile.textChanged.connect(
+ self.parent.lineEdit_audioFile.textChanged.connect(
self.update
)
@@ -123,7 +123,7 @@ class Component(Component):
genericPreview = self.settings.value("pref_genericPreview")
startPt = 0
if not genericPreview:
- inputFile = self.parent.window.lineEdit_audioFile.text()
+ inputFile = self.parent.lineEdit_audioFile.text()
if not inputFile or not os.path.exists(inputFile):
return
duration = getAudioDuration(inputFile)
diff --git a/src/components/video.py b/src/components/video.py
index 070940d..9fffc26 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -63,8 +63,8 @@ class Component(Component):
def properties(self):
props = []
- if hasattr(self.parent, 'window'):
- outputFile = self.parent.window.lineEdit_outputFile.text()
+ if hasattr(self.parent, 'lineEdit_outputFile'):
+ outputFile = self.parent.lineEdit_outputFile.text()
else:
outputFile = str(self.parent.args.output)
diff --git a/src/components/waveform.py b/src/components/waveform.py
index 1a6035f..227f711 100644
--- a/src/components/waveform.py
+++ b/src/components/waveform.py
@@ -27,8 +27,8 @@ class Component(Component):
self.page.lineEdit_color.setText('255,255,255')
- if hasattr(self.parent, 'window'):
- self.parent.window.lineEdit_audioFile.textChanged.connect(
+ if hasattr(self.parent, 'lineEdit_audioFile'):
+ self.parent.lineEdit_audioFile.textChanged.connect(
self.update
)
@@ -82,7 +82,7 @@ class Component(Component):
genericPreview = self.settings.value("pref_genericPreview")
startPt = 0
if not genericPreview:
- inputFile = self.parent.window.lineEdit_audioFile.text()
+ inputFile = self.parent.lineEdit_audioFile.text()
if not inputFile or not os.path.exists(inputFile):
return
duration = getAudioDuration(inputFile)
diff --git a/src/core.py b/src/core.py
index 42fd1c3..225d8e0 100644
--- a/src/core.py
+++ b/src/core.py
@@ -181,7 +181,7 @@ class Core:
try:
if hasattr(loader, 'window'):
for widget, value in data['WindowFields']:
- widget = eval('loader.window.%s' % widget)
+ widget = eval('loader.%s' % widget)
with toolkit.blockSignals(widget):
toolkit.setWidgetValue(widget, value)
diff --git a/src/gui/actions.py b/src/gui/actions.py
index eb7b953..afb980a 100644
--- a/src/gui/actions.py
+++ b/src/gui/actions.py
@@ -41,7 +41,7 @@ class RemoveComponent(QUndoCommand):
def __init__(self, parent, selectedRows):
super().__init__('remove component')
self.parent = parent
- componentList = self.parent.window.listWidget_componentList
+ componentList = self.parent.listWidget_componentList
self.selectedRows = [
componentList.row(selected) for selected in selectedRows
]
@@ -53,7 +53,7 @@ class RemoveComponent(QUndoCommand):
self.parent._removeComponent(self.selectedRows[0])
def undo(self):
- componentList = self.parent.window.listWidget_componentList
+ componentList = self.parent.listWidget_componentList
for index, comp in zip(self.selectedRows, self.components):
self.parent.core.insertComponent(
index, comp, self.parent
@@ -78,7 +78,7 @@ class MoveComponent(QUndoCommand):
return True
def do(self, rowa, rowb):
- componentList = self.parent.window.listWidget_componentList
+ componentList = self.parent.listWidget_componentList
page = self.parent.pages.pop(rowa)
self.parent.pages.insert(rowb, page)
@@ -86,7 +86,7 @@ class MoveComponent(QUndoCommand):
item = componentList.takeItem(rowa)
componentList.insertItem(rowb, item)
- stackedWidget = self.parent.window.stackedWidget
+ stackedWidget = self.parent.stackedWidget
widget = stackedWidget.removeWidget(page)
stackedWidget.insertWidget(rowb, page)
componentList.setCurrentRow(rowb)
diff --git a/src/gui/mainwindow.py b/src/gui/mainwindow.py
index 463d028..c31eec9 100644
--- a/src/gui/mainwindow.py
+++ b/src/gui/mainwindow.py
@@ -4,13 +4,12 @@
This shows a preview of the video being created and allows for saving
projects and exporting the video at a later time.
'''
-from PyQt5 import QtCore, QtGui, uic, QtWidgets
-from PyQt5.QtWidgets import QMenu, QShortcut
+from PyQt5 import QtCore, QtGui, QtWidgets, uic
+import PyQt5.QtWidgets as QtWidgets
from PIL import Image
from queue import Queue
import sys
import os
-import signal
import atexit
import filecmp
import time
@@ -43,11 +42,22 @@ class MainWindow(QtWidgets.QMainWindow):
newTask = QtCore.pyqtSignal(list) # for the preview window
processTask = QtCore.pyqtSignal()
- def __init__(self, window, project):
- QtWidgets.QMainWindow.__init__(self)
+ def __init__(self, project):
+ super().__init__()
log.debug(
'Main thread id: {}'.format(int(QtCore.QThread.currentThreadId())))
- self.window = window
+ uic.loadUi(os.path.join(Core.wd, "gui", "mainwindow.ui"), self)
+ desk = QtWidgets.QDesktopWidget()
+ dpi = desk.physicalDpiX()
+ log.info("Detected screen DPI: %s", dpi)
+
+ self.resize(
+ int(self.width() *
+ (dpi / 96)),
+ int(self.height() *
+ (dpi / 96))
+ )
+
self.core = Core()
Core.mode = 'GUI'
# widgets of component settings
@@ -73,15 +83,13 @@ class MainWindow(QtWidgets.QMainWindow):
self.undoStack.setUndoLimit(undoLimit)
# Create Preset Manager
- self.presetManager = PresetManager(
- uic.loadUi(
- os.path.join(Core.wd, 'gui', 'presetmanager.ui')), self)
+ self.presetManager = PresetManager(self)
# Create the preview window and its thread, queues, and timers
log.debug('Creating preview window')
self.previewWindow = PreviewWindow(self, os.path.join(
Core.wd, 'gui', "background.png"))
- window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
+ self.verticalLayout_previewWrapper.addWidget(self.previewWindow)
log.debug('Starting preview thread')
self.previewQueue = Queue()
@@ -105,7 +113,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.timer.start(timeout)
# Begin decorating the window and connecting events
- componentList = self.window.listWidget_componentList
+ componentList = self.listWidget_componentList
# Undo Feature
def toggleUndoButtonEnabled(*_):
@@ -116,15 +124,15 @@ class MainWindow(QtWidgets.QMainWindow):
# program is probably in midst of exiting
pass
- style = window.pushButton_undo.style()
- undoButton = window.pushButton_undo
+ style = self.pushButton_undo.style()
+ undoButton = self.pushButton_undo
undoButton.setIcon(
style.standardIcon(QtWidgets.QStyle.SP_FileDialogBack)
)
undoButton.clicked.connect(self.undoStack.undo)
undoButton.setEnabled(False)
self.undoStack.cleanChanged.connect(toggleUndoButtonEnabled)
- self.undoMenu = QMenu()
+ self.undoMenu = QtWidgets.QMenu()
self.undoMenu.addAction(
self.undoStack.createUndoAction(self)
)
@@ -138,93 +146,93 @@ class MainWindow(QtWidgets.QMainWindow):
undoButton.setMenu(self.undoMenu)
# end of Undo Feature
- style = window.pushButton_listMoveUp.style()
- window.pushButton_listMoveUp.setIcon(
+ style = self.pushButton_listMoveUp.style()
+ self.pushButton_listMoveUp.setIcon(
style.standardIcon(QtWidgets.QStyle.SP_ArrowUp)
)
- style = window.pushButton_listMoveDown.style()
- window.pushButton_listMoveDown.setIcon(
+ style = self.pushButton_listMoveDown.style()
+ self.pushButton_listMoveDown.setIcon(
style.standardIcon(QtWidgets.QStyle.SP_ArrowDown)
)
- style = window.pushButton_removeComponent.style()
- window.pushButton_removeComponent.setIcon(
+ style = self.pushButton_removeComponent.style()
+ self.pushButton_removeComponent.setIcon(
style.standardIcon(QtWidgets.QStyle.SP_DialogDiscardButton)
)
if sys.platform == 'darwin':
log.debug(
'Darwin detected: showing progress label below progress bar')
- window.progressBar_createVideo.setTextVisible(False)
+ self.progressBar_createVideo.setTextVisible(False)
else:
- window.progressLabel.setHidden(True)
+ self.progressLabel.setHidden(True)
- window.toolButton_selectAudioFile.clicked.connect(
+ self.toolButton_selectAudioFile.clicked.connect(
self.openInputFileDialog)
- window.toolButton_selectOutputFile.clicked.connect(
+ self.toolButton_selectOutputFile.clicked.connect(
self.openOutputFileDialog)
def changedField():
self.autosave()
self.updateWindowTitle()
- window.lineEdit_audioFile.textChanged.connect(changedField)
- window.lineEdit_outputFile.textChanged.connect(changedField)
+ self.lineEdit_audioFile.textChanged.connect(changedField)
+ self.lineEdit_outputFile.textChanged.connect(changedField)
- window.progressBar_createVideo.setValue(0)
+ self.progressBar_createVideo.setValue(0)
- window.pushButton_createVideo.clicked.connect(
+ self.pushButton_createVideo.clicked.connect(
self.createAudioVisualisation)
- window.pushButton_Cancel.clicked.connect(self.stopVideo)
+ self.pushButton_Cancel.clicked.connect(self.stopVideo)
for i, container in enumerate(Core.encoderOptions['containers']):
- window.comboBox_videoContainer.addItem(container['name'])
+ self.comboBox_videoContainer.addItem(container['name'])
if container['name'] == self.settings.value('outputContainer'):
selectedContainer = i
- window.comboBox_videoContainer.setCurrentIndex(selectedContainer)
- window.comboBox_videoContainer.currentIndexChanged.connect(
+ self.comboBox_videoContainer.setCurrentIndex(selectedContainer)
+ self.comboBox_videoContainer.currentIndexChanged.connect(
self.updateCodecs
)
self.updateCodecs()
- for i in range(window.comboBox_videoCodec.count()):
- codec = window.comboBox_videoCodec.itemText(i)
+ for i in range(self.comboBox_videoCodec.count()):
+ codec = self.comboBox_videoCodec.itemText(i)
if codec == self.settings.value('outputVideoCodec'):
- window.comboBox_videoCodec.setCurrentIndex(i)
+ self.comboBox_videoCodec.setCurrentIndex(i)
- for i in range(window.comboBox_audioCodec.count()):
- codec = window.comboBox_audioCodec.itemText(i)
+ for i in range(self.comboBox_audioCodec.count()):
+ codec = self.comboBox_audioCodec.itemText(i)
if codec == self.settings.value('outputAudioCodec'):
- window.comboBox_audioCodec.setCurrentIndex(i)
+ self.comboBox_audioCodec.setCurrentIndex(i)
- window.comboBox_videoCodec.currentIndexChanged.connect(
+ self.comboBox_videoCodec.currentIndexChanged.connect(
self.updateCodecSettings
)
- window.comboBox_audioCodec.currentIndexChanged.connect(
+ self.comboBox_audioCodec.currentIndexChanged.connect(
self.updateCodecSettings
)
vBitrate = int(self.settings.value('outputVideoBitrate'))
aBitrate = int(self.settings.value('outputAudioBitrate'))
- window.spinBox_vBitrate.setValue(vBitrate)
- window.spinBox_aBitrate.setValue(aBitrate)
- window.spinBox_vBitrate.valueChanged.connect(self.updateCodecSettings)
- window.spinBox_aBitrate.valueChanged.connect(self.updateCodecSettings)
+ self.spinBox_vBitrate.setValue(vBitrate)
+ self.spinBox_aBitrate.setValue(aBitrate)
+ self.spinBox_vBitrate.valueChanged.connect(self.updateCodecSettings)
+ self.spinBox_aBitrate.valueChanged.connect(self.updateCodecSettings)
# Make component buttons
- self.compMenu = QMenu()
+ self.compMenu = QtWidgets.QMenu()
for i, comp in enumerate(self.core.modules):
action = self.compMenu.addAction(comp.Component.name)
action.triggered.connect(
lambda _, item=i: self.addComponent(0, item)
)
- self.window.pushButton_addComponent.setMenu(self.compMenu)
+ self.pushButton_addComponent.setMenu(self.compMenu)
componentList.dropEvent = self.dragComponent
componentList.itemSelectionChanged.connect(
@@ -233,7 +241,7 @@ class MainWindow(QtWidgets.QMainWindow):
componentList.itemSelectionChanged.connect(
self.presetManager.clearPresetListSelection
)
- self.window.pushButton_removeComponent.clicked.connect(
+ self.pushButton_removeComponent.clicked.connect(
lambda: self.removeComponent()
)
@@ -245,33 +253,33 @@ class MainWindow(QtWidgets.QMainWindow):
currentRes = str(self.settings.value('outputWidth'))+'x' + \
str(self.settings.value('outputHeight'))
for i, res in enumerate(Core.resolutions):
- window.comboBox_resolution.addItem(res)
+ self.comboBox_resolution.addItem(res)
if res == currentRes:
currentRes = i
- window.comboBox_resolution.setCurrentIndex(currentRes)
- window.comboBox_resolution.currentIndexChanged.connect(
+ self.comboBox_resolution.setCurrentIndex(currentRes)
+ self.comboBox_resolution.currentIndexChanged.connect(
self.updateResolution
)
- self.window.pushButton_listMoveUp.clicked.connect(
+ self.pushButton_listMoveUp.clicked.connect(
lambda: self.moveComponent(-1)
)
- self.window.pushButton_listMoveDown.clicked.connect(
+ self.pushButton_listMoveDown.clicked.connect(
lambda: self.moveComponent(1)
)
# Configure the Projects Menu
- self.projectMenu = QMenu()
- self.window.menuButton_newProject = self.projectMenu.addAction(
+ self.projectMenu = QtWidgets.QMenu()
+ self.menuButton_newProject = self.projectMenu.addAction(
"New Project"
)
- self.window.menuButton_newProject.triggered.connect(
+ self.menuButton_newProject.triggered.connect(
lambda: self.createNewProject()
)
- self.window.menuButton_openProject = self.projectMenu.addAction(
+ self.menuButton_openProject = self.projectMenu.addAction(
"Open Project"
)
- self.window.menuButton_openProject.triggered.connect(
+ self.menuButton_openProject.triggered.connect(
lambda: self.openOpenProjectDialog()
)
@@ -281,16 +289,16 @@ class MainWindow(QtWidgets.QMainWindow):
action = self.projectMenu.addAction("Save Project As")
action.triggered.connect(self.openSaveProjectDialog)
- self.window.pushButton_projects.setMenu(self.projectMenu)
+ self.pushButton_projects.setMenu(self.projectMenu)
# Configure the Presets Button
- self.window.pushButton_presets.clicked.connect(
+ self.pushButton_presets.clicked.connect(
self.openPresetManager
)
self.updateWindowTitle()
log.debug('Showing main window')
- window.show()
+ self.show()
if project and project != self.autosavePath:
if not project.endswith('.avp'):
@@ -358,77 +366,80 @@ class MainWindow(QtWidgets.QMainWindow):
self.settings.setValue("ffmpegMsgShown", True)
# Hotkeys for projects
- QtWidgets.QShortcut("Ctrl+S", self.window, self.saveCurrentProject)
- QtWidgets.QShortcut("Ctrl+A", self.window, self.openSaveProjectDialog)
- QtWidgets.QShortcut("Ctrl+O", self.window, self.openOpenProjectDialog)
- QtWidgets.QShortcut("Ctrl+N", self.window, self.createNewProject)
+ QtWidgets.QShortcut("Ctrl+S", self, self.saveCurrentProject)
+ QtWidgets.QShortcut("Ctrl+A", self, self.openSaveProjectDialog)
+ QtWidgets.QShortcut("Ctrl+O", self, self.openOpenProjectDialog)
+ QtWidgets.QShortcut("Ctrl+N", self, self.createNewProject)
- QtWidgets.QShortcut("Ctrl+Z", self.window, self.undoStack.undo)
- QtWidgets.QShortcut("Ctrl+Y", self.window, self.undoStack.redo)
- QtWidgets.QShortcut("Ctrl+Shift+Z", self.window, self.undoStack.redo)
+ # Hotkeys for undo/redo
+ QtWidgets.QShortcut("Ctrl+Z", self, self.undoStack.undo)
+ QtWidgets.QShortcut("Ctrl+Y", self, self.undoStack.redo)
+ QtWidgets.QShortcut("Ctrl+Shift+Z", self, self.undoStack.redo)
# Hotkeys for component list
for inskey in ("Ctrl+T", QtCore.Qt.Key_Insert):
QtWidgets.QShortcut(
- inskey, self.window,
- activated=lambda: self.window.pushButton_addComponent.click()
+ inskey, self,
+ activated=lambda: self.pushButton_addComponent.click()
)
for delkey in ("Ctrl+R", QtCore.Qt.Key_Delete):
QtWidgets.QShortcut(
- delkey, self.window.listWidget_componentList,
+ delkey, self.listWidget_componentList,
self.removeComponent
)
QtWidgets.QShortcut(
- "Ctrl+Space", self.window,
- activated=lambda: self.window.listWidget_componentList.setFocus()
+ "Ctrl+Space", self,
+ activated=lambda: self.listWidget_componentList.setFocus()
)
QtWidgets.QShortcut(
- "Ctrl+Shift+S", self.window,
+ "Ctrl+Shift+S", self,
self.presetManager.openSavePresetDialog
)
QtWidgets.QShortcut(
- "Ctrl+Shift+C", self.window, self.presetManager.clearPreset
+ "Ctrl+Shift+C", self, self.presetManager.clearPreset
)
QtWidgets.QShortcut(
- "Ctrl+Up", self.window.listWidget_componentList,
+ "Ctrl+Up", self.listWidget_componentList,
activated=lambda: self.moveComponent(-1)
)
QtWidgets.QShortcut(
- "Ctrl+Down", self.window.listWidget_componentList,
+ "Ctrl+Down", self.listWidget_componentList,
activated=lambda: self.moveComponent(1)
)
QtWidgets.QShortcut(
- "Ctrl+Home", self.window.listWidget_componentList,
+ "Ctrl+Home", self.listWidget_componentList,
activated=lambda: self.moveComponent('top')
)
QtWidgets.QShortcut(
- "Ctrl+End", self.window.listWidget_componentList,
+ "Ctrl+End", self.listWidget_componentList,
activated=lambda: self.moveComponent('bottom')
)
QtWidgets.QShortcut(
- "Ctrl+Shift+F", self.window, self.showFfmpegCommand
+ "Ctrl+Shift+F", self, self.showFfmpegCommand
)
QtWidgets.QShortcut(
- "Ctrl+Shift+U", self.window, self.showUndoStack
+ "Ctrl+Shift+U", self, self.showUndoStack
)
if log.isEnabledFor(logging.DEBUG):
QtWidgets.QShortcut(
- "Ctrl+Alt+Shift+R", self.window, self.drawPreview
+ "Ctrl+Alt+Shift+R", self, self.drawPreview
)
QtWidgets.QShortcut(
- "Ctrl+Alt+Shift+A", self.window, lambda: log.debug(repr(self))
+ "Ctrl+Alt+Shift+A", self, lambda: log.debug(repr(self))
)
def __repr__(self):
return (
+ '%s\n'
'\n%s\n'
'#####\n'
'Preview thread is %s\n' % (
- repr(self.core),
- 'live' if self.previewThread.isRunning() else 'dead',
+ super().__repr__(),
+ "core not initialized" if not hasattr(self, "core") else repr(self.core),
+ 'live' if hasattr(self, "previewThread") and self.previewThread.isRunning() else 'dead',
)
)
@@ -456,7 +467,7 @@ class MainWindow(QtWidgets.QMainWindow):
except AttributeError:
pass
log.verbose(f'Window title is "{appName}"')
- self.window.setWindowTitle(appName)
+ self.setWindowTitle(appName)
@QtCore.pyqtSlot(int, dict)
def updateComponentTitle(self, pos, presetStore=False):
@@ -492,12 +503,12 @@ class MainWindow(QtWidgets.QMainWindow):
'Setting %s #%s\'s title: %s',
name, pos, title
)
- self.window.listWidget_componentList.item(pos).setText(title)
+ self.listWidget_componentList.item(pos).setText(title)
def updateCodecs(self):
- containerWidget = self.window.comboBox_videoContainer
- vCodecWidget = self.window.comboBox_videoCodec
- aCodecWidget = self.window.comboBox_audioCodec
+ containerWidget = self.comboBox_videoContainer
+ vCodecWidget = self.comboBox_videoCodec
+ aCodecWidget = self.comboBox_audioCodec
index = containerWidget.currentIndex()
name = containerWidget.itemText(index)
self.settings.setValue('outputContainer', name)
@@ -514,10 +525,10 @@ class MainWindow(QtWidgets.QMainWindow):
def updateCodecSettings(self):
'''Updates settings.ini to match encoder option widgets'''
- vCodecWidget = self.window.comboBox_videoCodec
- vBitrateWidget = self.window.spinBox_vBitrate
- aBitrateWidget = self.window.spinBox_aBitrate
- aCodecWidget = self.window.comboBox_audioCodec
+ vCodecWidget = self.comboBox_videoCodec
+ vBitrateWidget = self.spinBox_vBitrate
+ aBitrateWidget = self.spinBox_aBitrate
+ aCodecWidget = self.comboBox_audioCodec
currentVideoCodec = vCodecWidget.currentIndex()
currentVideoCodec = vCodecWidget.itemText(currentVideoCodec)
currentVideoBitrate = vBitrateWidget.value()
@@ -535,7 +546,7 @@ class MainWindow(QtWidgets.QMainWindow):
if os.path.exists(self.autosavePath):
os.remove(self.autosavePath)
elif force or time.time() - self.lastAutosave >= self.autosaveCooldown:
- self.core.createProjectFile(self.autosavePath, self.window)
+ self.core.createProjectFile(self.autosavePath, self)
self.lastAutosave = time.time()
if len(self.autosaveTimes) >= 5:
# Do some math to reduce autosave spam. This gives a smooth
@@ -588,25 +599,25 @@ class MainWindow(QtWidgets.QMainWindow):
inputDir = self.settings.value("inputDir", os.path.expanduser("~"))
fileName, _ = QtWidgets.QFileDialog.getOpenFileName(
- self.window, "Open Audio File",
+ self, "Open Audio File",
inputDir, "Audio Files (%s)" % " ".join(Core.audioFormats))
if fileName:
self.settings.setValue("inputDir", os.path.dirname(fileName))
- self.window.lineEdit_audioFile.setText(fileName)
+ self.lineEdit_audioFile.setText(fileName)
def openOutputFileDialog(self):
outputDir = self.settings.value("outputDir", os.path.expanduser("~"))
fileName, _ = QtWidgets.QFileDialog.getSaveFileName(
- self.window, "Set Output Video File",
+ self, "Set Output Video File",
outputDir,
"Video Files (%s);; All Files (*)" % " ".join(
Core.videoFormats))
if fileName:
self.settings.setValue("outputDir", os.path.dirname(fileName))
- self.window.lineEdit_outputFile.setText(fileName)
+ self.lineEdit_outputFile.setText(fileName)
def stopVideo(self):
log.info('Export cancelled')
@@ -615,8 +626,8 @@ class MainWindow(QtWidgets.QMainWindow):
def createAudioVisualisation(self):
# create output video if mandatory settings are filled in
- audioFile = self.window.lineEdit_audioFile.text()
- outputPath = self.window.lineEdit_outputFile.text()
+ audioFile = self.lineEdit_audioFile.text()
+ outputPath = self.lineEdit_outputFile.text()
if audioFile and outputPath and self.core.selectedComponents:
if not os.path.dirname(outputPath):
@@ -670,62 +681,62 @@ class MainWindow(QtWidgets.QMainWindow):
def changeEncodingStatus(self, status):
self.encoding = status
if status:
- self.window.pushButton_createVideo.setEnabled(False)
- self.window.pushButton_Cancel.setEnabled(True)
- self.window.comboBox_resolution.setEnabled(False)
- self.window.stackedWidget.setEnabled(False)
- self.window.tab_encoderSettings.setEnabled(False)
- self.window.label_audioFile.setEnabled(False)
- self.window.toolButton_selectAudioFile.setEnabled(False)
- self.window.label_outputFile.setEnabled(False)
- self.window.toolButton_selectOutputFile.setEnabled(False)
- self.window.lineEdit_audioFile.setEnabled(False)
- self.window.lineEdit_outputFile.setEnabled(False)
- self.window.pushButton_addComponent.setEnabled(False)
- self.window.pushButton_removeComponent.setEnabled(False)
- self.window.pushButton_listMoveDown.setEnabled(False)
- self.window.pushButton_listMoveUp.setEnabled(False)
- self.window.menuButton_newProject.setEnabled(False)
- self.window.menuButton_openProject.setEnabled(False)
+ self.pushButton_createVideo.setEnabled(False)
+ self.pushButton_Cancel.setEnabled(True)
+ self.comboBox_resolution.setEnabled(False)
+ self.stackedWidget.setEnabled(False)
+ self.tab_encoderSettings.setEnabled(False)
+ self.label_audioFile.setEnabled(False)
+ self.toolButton_selectAudioFile.setEnabled(False)
+ self.label_outputFile.setEnabled(False)
+ self.toolButton_selectOutputFile.setEnabled(False)
+ self.lineEdit_audioFile.setEnabled(False)
+ self.lineEdit_outputFile.setEnabled(False)
+ self.pushButton_addComponent.setEnabled(False)
+ self.pushButton_removeComponent.setEnabled(False)
+ self.pushButton_listMoveDown.setEnabled(False)
+ self.pushButton_listMoveUp.setEnabled(False)
+ self.menuButton_newProject.setEnabled(False)
+ self.menuButton_openProject.setEnabled(False)
if sys.platform == 'darwin':
- self.window.progressLabel.setHidden(False)
+ self.progressLabel.setHidden(False)
else:
- self.window.listWidget_componentList.setEnabled(False)
+ self.listWidget_componentList.setEnabled(False)
else:
- self.window.pushButton_createVideo.setEnabled(True)
- self.window.pushButton_Cancel.setEnabled(False)
- self.window.comboBox_resolution.setEnabled(True)
- self.window.stackedWidget.setEnabled(True)
- self.window.tab_encoderSettings.setEnabled(True)
- self.window.label_audioFile.setEnabled(True)
- self.window.toolButton_selectAudioFile.setEnabled(True)
- self.window.lineEdit_audioFile.setEnabled(True)
- self.window.label_outputFile.setEnabled(True)
- self.window.toolButton_selectOutputFile.setEnabled(True)
- self.window.lineEdit_outputFile.setEnabled(True)
- self.window.pushButton_addComponent.setEnabled(True)
- self.window.pushButton_removeComponent.setEnabled(True)
- self.window.pushButton_listMoveDown.setEnabled(True)
- self.window.pushButton_listMoveUp.setEnabled(True)
- self.window.menuButton_newProject.setEnabled(True)
- self.window.menuButton_openProject.setEnabled(True)
- self.window.listWidget_componentList.setEnabled(True)
- self.window.progressLabel.setHidden(True)
+ self.pushButton_createVideo.setEnabled(True)
+ self.pushButton_Cancel.setEnabled(False)
+ self.comboBox_resolution.setEnabled(True)
+ self.stackedWidget.setEnabled(True)
+ self.tab_encoderSettings.setEnabled(True)
+ self.label_audioFile.setEnabled(True)
+ self.toolButton_selectAudioFile.setEnabled(True)
+ self.lineEdit_audioFile.setEnabled(True)
+ self.label_outputFile.setEnabled(True)
+ self.toolButton_selectOutputFile.setEnabled(True)
+ self.lineEdit_outputFile.setEnabled(True)
+ self.pushButton_addComponent.setEnabled(True)
+ self.pushButton_removeComponent.setEnabled(True)
+ self.pushButton_listMoveDown.setEnabled(True)
+ self.pushButton_listMoveUp.setEnabled(True)
+ self.menuButton_newProject.setEnabled(True)
+ self.menuButton_openProject.setEnabled(True)
+ self.listWidget_componentList.setEnabled(True)
+ self.progressLabel.setHidden(True)
self.drawPreview(True)
@QtCore.pyqtSlot(int)
def progressBarUpdated(self, value):
- self.window.progressBar_createVideo.setValue(value)
+ self.progressBar_createVideo.setValue(value)
@QtCore.pyqtSlot(str)
def progressBarSetText(self, value):
if sys.platform == 'darwin':
- self.window.progressLabel.setText(value)
+ self.progressLabel.setText(value)
else:
- self.window.progressBar_createVideo.setFormat(value)
+ self.progressBar_createVideo.setFormat(value)
def updateResolution(self):
- resIndex = int(self.window.comboBox_resolution.currentIndex())
+ resIndex = int(self.comboBox_resolution.currentIndex())
res = Core.resolutions[resIndex].split('x')
changed = res[0] != self.settings.value("outputWidth")
self.settings.setValue('outputWidth', res[0])
@@ -750,7 +761,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.previewWindow.changePixmap(image)
def showUndoStack(self):
- dialog = QtWidgets.QDialog(self.window)
+ dialog = QtWidgets.QDialog(self)
undoView = QtWidgets.QUndoView(self.undoStack)
layout = QtWidgets.QVBoxLayout()
layout.addWidget(undoView)
@@ -761,8 +772,8 @@ class MainWindow(QtWidgets.QMainWindow):
from textwrap import wrap
from ..toolkit.ffmpeg import createFfmpegCommand
command = createFfmpegCommand(
- self.window.lineEdit_audioFile.text(),
- self.window.lineEdit_outputFile.text(),
+ self.lineEdit_audioFile.text(),
+ self.lineEdit_outputFile.text(),
self.core.selectedComponents
)
command = " ".join(command)
@@ -779,8 +790,8 @@ class MainWindow(QtWidgets.QMainWindow):
def insertComponent(self, index):
'''Triggered by Core to finish initializing a new component.'''
- componentList = self.window.listWidget_componentList
- stackedWidget = self.window.stackedWidget
+ componentList = self.listWidget_componentList
+ stackedWidget = self.stackedWidget
componentList.insertItem(
index,
@@ -798,15 +809,15 @@ class MainWindow(QtWidgets.QMainWindow):
return index
def removeComponent(self):
- componentList = self.window.listWidget_componentList
+ componentList = self.listWidget_componentList
selected = componentList.selectedItems()
if selected:
action = RemoveComponent(self, selected)
self.undoStack.push(action)
def _removeComponent(self, index):
- stackedWidget = self.window.stackedWidget
- componentList = self.window.listWidget_componentList
+ stackedWidget = self.stackedWidget
+ componentList = self.listWidget_componentList
stackedWidget.removeWidget(self.pages[index])
componentList.takeItem(index)
self.core.removeComponent(index)
@@ -817,7 +828,7 @@ class MainWindow(QtWidgets.QMainWindow):
@disableWhenEncoding
def moveComponent(self, change):
'''Moves a component relatively from its current position'''
- componentList = self.window.listWidget_componentList
+ componentList = self.listWidget_componentList
tag = change
if change == 'top':
change = -componentList.currentRow()
@@ -837,7 +848,7 @@ class MainWindow(QtWidgets.QMainWindow):
Given a QPos, returns the component index under the mouse cursor
or -1 if no component is there.
'''
- componentList = self.window.listWidget_componentList
+ componentList = self.listWidget_componentList
modelIndexes = [
componentList.model().index(i)
@@ -859,7 +870,7 @@ class MainWindow(QtWidgets.QMainWindow):
@disableWhenEncoding
def dragComponent(self, event):
'''Used as Qt drop event for the component listwidget'''
- componentList = self.window.listWidget_componentList
+ componentList = self.listWidget_componentList
mousePos = self.getComponentListMousePos(event.pos())
if mousePos > -1:
change = (componentList.currentRow() - mousePos) * -1
@@ -868,25 +879,25 @@ class MainWindow(QtWidgets.QMainWindow):
self.moveComponent(change)
def changeComponentWidget(self):
- selected = self.window.listWidget_componentList.selectedItems()
+ selected = self.listWidget_componentList.selectedItems()
if selected:
- index = self.window.listWidget_componentList.row(selected[0])
- self.window.stackedWidget.setCurrentIndex(index)
+ index = self.listWidget_componentList.row(selected[0])
+ self.stackedWidget.setCurrentIndex(index)
def openPresetManager(self):
'''Preset manager for importing, exporting, renaming, deleting'''
- self.presetManager.show()
+ self.presetManager.show_()
def clear(self):
'''Get a blank slate'''
self.core.clearComponents()
- self.window.listWidget_componentList.clear()
+ self.listWidget_componentList.clear()
for widget in self.pages:
- self.window.stackedWidget.removeWidget(widget)
+ self.stackedWidget.removeWidget(widget)
self.pages = []
for field in (
- self.window.lineEdit_audioFile,
- self.window.lineEdit_outputFile
+ self.lineEdit_audioFile,
+ self.lineEdit_outputFile
):
with blockSignals(field):
field.setText('')
@@ -906,7 +917,7 @@ class MainWindow(QtWidgets.QMainWindow):
def saveCurrentProject(self):
if self.currentProject:
- self.core.createProjectFile(self.currentProject, self.window)
+ self.core.createProjectFile(self.currentProject, self)
try:
os.remove(self.autosavePath)
except FileNotFoundError:
@@ -933,7 +944,7 @@ class MainWindow(QtWidgets.QMainWindow):
def openSaveProjectDialog(self):
filename, _ = QtWidgets.QFileDialog.getSaveFileName(
- self.window, "Create Project File",
+ self, "Create Project File",
self.settings.value("projectDir"),
"Project Files (*.avp)")
if not filename:
@@ -943,13 +954,13 @@ class MainWindow(QtWidgets.QMainWindow):
self.settings.setValue("projectDir", os.path.dirname(filename))
self.settings.setValue("currentProject", filename)
self.currentProject = filename
- self.core.createProjectFile(filename, self.window)
+ self.core.createProjectFile(filename, self)
self.updateWindowTitle()
@disableWhenEncoding
def openOpenProjectDialog(self):
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
- self.window, "Open Project File",
+ self, "Open Project File",
self.settings.value("projectDir"),
"Project Files (*.avp)")
self.openProject(filename)
@@ -973,7 +984,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.updateWindowTitle()
def showMessage(self, **kwargs):
- parent = kwargs['parent'] if 'parent' in kwargs else self.window
+ parent = kwargs['parent'] if 'parent' in kwargs else self
msg = QtWidgets.QMessageBox(parent)
msg.setModal(True)
msg.setText(kwargs['msg'])
@@ -995,8 +1006,8 @@ class MainWindow(QtWidgets.QMainWindow):
@disableWhenEncoding
def componentContextMenu(self, QPos):
'''Appears when right-clicking the component list'''
- componentList = self.window.listWidget_componentList
- self.menu = QMenu()
+ componentList = self.listWidget_componentList
+ self.menu = QtWidgets.QMenu()
parentPosition = componentList.mapToGlobal(QtCore.QPoint(0, 0))
index = self.getComponentListMousePos(QPos)
@@ -1013,7 +1024,7 @@ class MainWindow(QtWidgets.QMainWindow):
presets = self.presetManager.presets[
str(self.core.selectedComponents[index])
]
- self.presetSubmenu = QMenu("Open Preset")
+ self.presetSubmenu = QtWidgets.QMenu("Open Preset")
self.menu.addMenu(self.presetSubmenu)
for version, presetName in presets:
@@ -1033,7 +1044,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.menu.addSeparator()
# "Add Component" submenu
- self.submenu = QMenu("Add")
+ self.submenu = QtWidgets.QMenu("Add")
self.menu.addMenu(self.submenu)
insertCompAtTop = self.settings.value("pref_insertCompAtTop")
for i, comp in enumerate(self.core.modules):
diff --git a/src/gui/presetmanager.py b/src/gui/presetmanager.py
index 1e47a7f..9cf95b4 100644
--- a/src/gui/presetmanager.py
+++ b/src/gui/presetmanager.py
@@ -2,7 +2,7 @@
Preset manager object handles all interactions with presets, including
the context menu accessed from MainWindow.
'''
-from PyQt5 import QtCore, QtWidgets
+from PyQt5 import QtCore, QtWidgets, uic
import string
import os
import logging
@@ -16,8 +16,10 @@ log = logging.getLogger('AVP.Gui.PresetManager')
class PresetManager(QtWidgets.QDialog):
- def __init__(self, window, parent):
- super().__init__(parent.window)
+ def __init__(self, parent):
+ super().__init__()
+ uic.loadUi(
+ os.path.join(Core.wd, 'gui', 'presetmanager.ui'), self)
self.parent = parent
self.core = parent.core
self.settings = parent.settings
@@ -32,32 +34,31 @@ class PresetManager(QtWidgets.QDialog):
# window
self.lastFilter = '*'
self.presetRows = [] # list of (comp, vers, name) tuples
- self.window = window
- self.window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
+ self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
# connect button signals
- self.window.pushButton_delete.clicked.connect(
+ self.pushButton_delete.clicked.connect(
self.openDeletePresetDialog
)
- self.window.pushButton_rename.clicked.connect(
+ self.pushButton_rename.clicked.connect(
self.openRenamePresetDialog
)
- self.window.pushButton_import.clicked.connect(
+ self.pushButton_import.clicked.connect(
self.openImportDialog
)
- self.window.pushButton_export.clicked.connect(
+ self.pushButton_export.clicked.connect(
self.openExportDialog
)
- self.window.pushButton_close.clicked.connect(
- self.window.close
+ self.pushButton_close.clicked.connect(
+ self.close
)
# create filter box and preset list
self.drawFilterList()
- self.window.comboBox_filter.currentIndexChanged.connect(
+ self.comboBox_filter.currentIndexChanged.connect(
lambda: self.drawPresetList(
- self.window.comboBox_filter.currentText(),
- self.window.lineEdit_search.text()
+ self.comboBox_filter.currentText(),
+ self.lineEdit_search.text()
)
)
@@ -65,23 +66,24 @@ class PresetManager(QtWidgets.QDialog):
self.autocomplete = QtCore.QStringListModel()
completer = QtWidgets.QCompleter()
completer.setModel(self.autocomplete)
- self.window.lineEdit_search.setCompleter(completer)
- self.window.lineEdit_search.textChanged.connect(
+ self.lineEdit_search.setCompleter(completer)
+ self.lineEdit_search.textChanged.connect(
lambda: self.drawPresetList(
- self.window.comboBox_filter.currentText(),
- self.window.lineEdit_search.text()
+ self.comboBox_filter.currentText(),
+ self.lineEdit_search.text()
)
)
self.drawPresetList('*')
- def show(self):
+ def show_(self):
'''Open a new preset manager window from the mainwindow'''
self.findPresets()
self.drawFilterList()
self.drawPresetList('*')
- self.window.show()
+ self.show()
def findPresets(self):
+ log.debug("Searching %s for presets", self.presetDir)
parseList = []
for dirpath, dirnames, filenames in os.walk(self.presetDir):
# anything without a subdirectory must be a preset folder
@@ -106,7 +108,7 @@ class PresetManager(QtWidgets.QDialog):
}
def drawPresetList(self, compFilter=None, presetFilter=''):
- self.window.listWidget_presets.clear()
+ self.listWidget_presets.clear()
if compFilter:
self.lastFilter = str(compFilter)
else:
@@ -118,7 +120,7 @@ class PresetManager(QtWidgets.QDialog):
continue
for vers, preset in presets:
if not presetFilter or presetFilter in preset:
- self.window.listWidget_presets.addItem(
+ self.listWidget_presets.addItem(
'%s: %s' % (component, preset)
)
self.presetRows.append((component, vers, preset))
@@ -127,22 +129,21 @@ class PresetManager(QtWidgets.QDialog):
self.autocomplete.setStringList(presetNames)
def drawFilterList(self):
- self.window.comboBox_filter.clear()
- self.window.comboBox_filter.addItem('*')
+ self.comboBox_filter.clear()
+ self.comboBox_filter.addItem('*')
for component in self.presets:
- self.window.comboBox_filter.addItem(component)
+ self.comboBox_filter.addItem(component)
def clearPreset(self, compI=None):
'''Functions on mainwindow level from the context menu'''
- compI = self.parent.window.listWidget_componentList.currentRow()
+ compI = self.parent.listWidget_componentList.currentRow()
action = ClearPreset(self.parent, compI)
self.parent.undoStack.push(action)
def openSavePresetDialog(self):
'''Functions on mainwindow level from the context menu'''
- window = self.parent.window
selectedComponents = self.core.selectedComponents
- componentList = self.parent.window.listWidget_componentList
+ componentList = self.parent.listWidget_componentList
if componentList.currentRow() == -1:
return
@@ -150,7 +151,7 @@ class PresetManager(QtWidgets.QDialog):
index = componentList.currentRow()
currentPreset = selectedComponents[index].currentPreset
newName, OK = QtWidgets.QInputDialog.getText(
- self.parent.window,
+ self.parent,
'Audio Visualizer',
'New Preset Name:',
QtWidgets.QLineEdit.Normal,
@@ -158,7 +159,7 @@ class PresetManager(QtWidgets.QDialog):
)
if OK:
if badName(newName):
- self.warnMessage(self.parent.window)
+ self.warnMessage(self.parent)
continue
if newName:
if index != -1:
@@ -170,7 +171,7 @@ class PresetManager(QtWidgets.QDialog):
vers = selectedComponents[index].version
self.createNewPreset(
componentName, vers, newName,
- saveValueStore, window=self.parent.window)
+ saveValueStore, window=self.parent)
self.findPresets()
self.drawPresetList()
self.openPreset(newName, index)
@@ -185,8 +186,7 @@ class PresetManager(QtWidgets.QDialog):
def presetExists(self, path, **kwargs):
if os.path.exists(path):
- window = self.window \
- if 'window' not in kwargs else kwargs['window']
+ window = kwargs.get("window", self)
ch = self.parent.showMessage(
msg="%s already exists! Overwrite it?" %
os.path.basename(path),
@@ -200,7 +200,7 @@ class PresetManager(QtWidgets.QDialog):
return False
def openPreset(self, presetName, compPos=None):
- componentList = self.parent.window.listWidget_componentList
+ componentList = self.parent.listWidget_componentList
index = compPos if compPos is not None else componentList.currentRow()
if index == -1:
return
@@ -228,7 +228,7 @@ class PresetManager(QtWidgets.QDialog):
msg='Really delete %s?' % name,
showCancel=True,
icon='Warning',
- parent=self.window
+ parent=self
)
if not ch:
return
@@ -242,15 +242,15 @@ class PresetManager(QtWidgets.QDialog):
self.parent.showMessage(
msg='Preset names must contain only letters, '
'numbers, and spaces.',
- parent=window if window else self.window)
+ parent=window if window else self)
def getPresetRow(self):
- row = self.window.listWidget_presets.currentRow()
+ row = self.listWidget_presets.currentRow()
if row > -1:
return row
# check if component selected in MainWindow has preset loaded
- componentList = self.parent.window.listWidget_componentList
+ componentList = self.parent.listWidget_componentList
compIndex = componentList.currentRow()
if compIndex == -1:
return compIndex
@@ -273,14 +273,14 @@ class PresetManager(QtWidgets.QDialog):
return index
def openRenamePresetDialog(self):
- presetList = self.window.listWidget_presets
+ presetList = self.listWidget_presets
index = self.getPresetRow()
if index == -1:
return
while True:
newName, OK = QtWidgets.QInputDialog.getText(
- self.window,
+ self,
'Preset Manager',
'Rename Preset:',
QtWidgets.QLineEdit.Normal,
@@ -319,7 +319,7 @@ class PresetManager(QtWidgets.QDialog):
def openImportDialog(self):
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
- self.window, "Import Preset File",
+ self, "Import Preset File",
self.settings.value("presetDir"),
"Preset Files (*.avl)")
if filename:
@@ -345,7 +345,7 @@ class PresetManager(QtWidgets.QDialog):
if index == -1:
return
filename, _ = QtWidgets.QFileDialog.getSaveFileName(
- self.window, "Export Preset",
+ self, "Export Preset",
self.settings.value("presetDir"),
"Preset Files (*.avl)")
if filename:
@@ -353,9 +353,9 @@ class PresetManager(QtWidgets.QDialog):
if not self.core.exportPreset(filename, comp, vers, name):
self.parent.showMessage(
msg='Couldn\'t export %s.' % filename,
- parent=self.window
+ parent=self
)
self.settings.setValue("presetDir", os.path.dirname(filename))
def clearPresetListSelection(self):
- self.window.listWidget_presets.setCurrentRow(-1)
+ self.listWidget_presets.setCurrentRow(-1)
diff --git a/src/gui/preview_win.py b/src/gui/preview_win.py
index 426ff66..d910456 100644
--- a/src/gui/preview_win.py
+++ b/src/gui/preview_win.py
@@ -37,7 +37,7 @@ class PreviewWindow(QtWidgets.QLabel):
if self.parent.encoding:
return
- i = self.parent.window.listWidget_componentList.currentRow()
+ i = self.parent.listWidget_componentList.currentRow()
if i >= 0:
component = self.parent.core.selectedComponents[i]
if not hasattr(component, 'previewClickEvent'):
diff --git a/src/main.py b/src/main.py
index ec4b8bc..709e5e7 100644
--- a/src/main.py
+++ b/src/main.py
@@ -42,21 +42,9 @@ def main():
if mode == 'GUI':
from .gui.mainwindow import MainWindow
- window = uic.loadUi(os.path.join(wd, "gui", "mainwindow.ui"))
- desc = QtWidgets.QDesktopWidget()
- dpi = desc.physicalDpiX()
- log.info("Detected screen DPI: %s", dpi)
-
- window.resize(
- int(window.width() *
- (dpi / 96)),
- int(window.height() *
- (dpi / 96))
- )
-
- main = MainWindow(window, proj)
- log.debug("Finished creating main window")
- window.raise_()
+ mainWindow = MainWindow(proj)
+ log.debug("Finished creating MainWindow")
+ mainWindow.raise_()
sys.exit(app.exec_())
--
cgit v1.2.3
From d51d49701e5880e35bbbade72c52bbec18f6e398 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Fri, 29 Apr 2022 21:17:02 -0400
Subject: ignore benign error from reading a closed pipe happens when the video
is done exporting sometimes. Not worth fixing
---
src/toolkit/ffmpeg.py | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index 37c1511..256646e 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -128,9 +128,13 @@ class FfmpegVideo:
try:
self.currentFrame = self.pipe.stdout.read(self.chunkSize)
- except ValueError:
- FfmpegVideo.threadError = ComponentError(
- self.component, 'video')
+ except ValueError as e:
+ if str(e) == "PyMemoryView_FromBuffer(): info->buf must not be NULL":
+ log.debug("Ignored 'info->buf must not be NULL' error from FFmpeg pipe")
+ return
+ else:
+ FfmpegVideo.threadError = ComponentError(
+ self.component, 'video')
if len(self.currentFrame) != 0:
self.frameBuffer.put((self.frameNo, self.currentFrame))
--
cgit v1.2.3
From 67c6fa43ac5ed85719179485b0fff4a8ad071a9f Mon Sep 17 00:00:00 2001
From: tassaron
Date: Fri, 29 Apr 2022 23:19:47 -0400
Subject: switch Pillow-SIMD for Pillow It is easier for people to install with
pip. We can always go back to SIMD in the future when we have a better
install script. Packaged versions can still use Pillow-SIMD
---
setup.py | 4 ++--
src/gui/mainwindow.py | 14 ++------------
2 files changed, 4 insertions(+), 14 deletions(-)
diff --git a/setup.py b/setup.py
index 5e01229..3709e7b 100644
--- a/setup.py
+++ b/setup.py
@@ -29,7 +29,7 @@ proj_packages = [SOURCE_PACKAGE_REGEX.sub(PACKAGE_NAME, name) for name in source
setup(
name='audio_visualizer_python',
version=avp.__version__,
- url='https://github.com/djfun/audio-visualizer-python/tree/feature-newgui',
+ url='https://github.com/djfun/audio-visualizer-python',
license='MIT',
description=PACKAGE_DESCRIPTION,
author=getTextFromFile('AUTHORS', 'djfun, tassaron'),
@@ -49,7 +49,7 @@ setup(
package_dir={PACKAGE_NAME: SOURCE_DIRECTORY},
include_package_data=True,
install_requires=[
- 'Pillow-SIMD',
+ 'Pillow',
'PyQt5',
'numpy',
'pytest'
diff --git a/src/gui/mainwindow.py b/src/gui/mainwindow.py
index c31eec9..1b28b7e 100644
--- a/src/gui/mainwindow.py
+++ b/src/gui/mainwindow.py
@@ -333,16 +333,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.openProject(self.currentProject, prompt=False)
self.drawPreview(True)
- # verify Pillow version
- if not self.settings.value("pilMsgShown") \
- and 'post' not in Image.__version__:
- self.showMessage(
- msg="You are using the standard version of the "
- "Python imaging library (Pillow %s). Upgrade "
- "to the Pillow-SIMD fork to enable hardware accelerations "
- "and export videos faster." % Image.__version__
- )
- self.settings.setValue("pilMsgShown", True)
+ log.info("Pillow version %s", Image.__version__)
# verify Ffmpeg version
if not self.settings.value("ffmpegMsgShown"):
@@ -351,8 +342,7 @@ class MainWindow(QtWidgets.QMainWindow):
ffmpegVers = checkOutput(
['ffmpeg', '-version'], stderr=f
)
- goodVersion = (str(ffmpegVers).split()[2].startswith('3') or
- str(ffmpegVers).split()[2].startswith('4'))
+ goodVersion = str(ffmpegVers).split()[2].startswith('4')
except Exception:
goodVersion = False
else:
--
cgit v1.2.3
From 271db4bff3f4dca16671b6e95396acbd6757f44a Mon Sep 17 00:00:00 2001
From: tassaron
Date: Fri, 29 Apr 2022 23:19:58 -0400
Subject: log ffmpeg bin
---
src/core.py | 19 ++++++++++++-------
1 file changed, 12 insertions(+), 7 deletions(-)
diff --git a/src/core.py b/src/core.py
index 225d8e0..0f7fe8e 100644
--- a/src/core.py
+++ b/src/core.py
@@ -463,9 +463,13 @@ class Core:
with open(os.path.join(wd, 'encoder-options.json')) as json_file:
encoderOptions = json.load(json_file)
+ # Locate FFmpeg
+ ffmpegBin = findFfmpeg()
+ log.info("Detected FFmpeg bin: %s", ffmpegBin)
+
settings = {
'canceled': False,
- 'FFMPEG_BIN': findFfmpeg(),
+ 'FFMPEG_BIN': ffmpegBin,
'dataDir': dataDir,
'settings': QtCore.QSettings(
os.path.join(dataDir, 'settings.ini'),
@@ -522,7 +526,7 @@ class Core:
cls.presetDir, cls.logDir, cls.settings.value("projectDir")):
if not os.path.exists(neededDirectory):
os.mkdir(neededDirectory)
- cls.makeLogger()
+ cls.makeLogger(deleteOldLogs=True)
@classmethod
def loadDefaultSettings(cls):
@@ -564,7 +568,7 @@ class Core:
cls.settings.setValue(key, val)
@staticmethod
- def makeLogger():
+ def makeLogger(deleteOldLogs=False):
# send critical log messages to stdout
logStream = logging.StreamHandler()
logStream.setLevel(STDOUT_LOGLVL)
@@ -580,10 +584,11 @@ class Core:
Core.logEnabled = True
logFilename = os.path.join(Core.logDir, 'avp_debug.log')
libLogFilename = os.path.join(Core.logDir, 'global_debug.log')
- # delete old logs
- for log_ in (logFilename, libLogFilename):
- if os.path.exists(log_):
- os.remove(log_)
+
+ if deleteOldLogs:
+ for log_ in (logFilename, libLogFilename):
+ if os.path.exists(log_):
+ os.remove(log_)
logFile = logging.FileHandler(logFilename, delay=True)
logFile.setLevel(FILE_LOGLVL)
--
cgit v1.2.3
From 9610631f0299151b5e8cb0b77e2f00ca2084d1c4 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Fri, 29 Apr 2022 23:29:47 -0400
Subject: license may be affected by packaging
https://github.com/djfun/audio-visualizer-python/issues/28#issuecomment-314791421
---
README.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 3ca5305..2c33b56 100644
--- a/README.md
+++ b/README.md
@@ -96,4 +96,6 @@ Download audio-visualizer-python from this repository and run it with `python3 m
# License
-audio-visualizer-python is licensed under the MIT license.
+Source code of audio-visualizer-python is licensed under the MIT license.
+
+Some dependencies of this application are under the GPL license. When packaged with these dependencies, audio-visualizer-python may also be under the terms of this GPL license.
\ No newline at end of file
--
cgit v1.2.3
From 340062712cd88bd1467b40fd49892566bfbccc04 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 30 Apr 2022 00:16:10 -0400
Subject: raise log level of library logfile
---
src/core.py | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/src/core.py b/src/core.py
index 0f7fe8e..77b0894 100644
--- a/src/core.py
+++ b/src/core.py
@@ -14,7 +14,8 @@ from . import toolkit
log = logging.getLogger('AVP.Core')
STDOUT_LOGLVL = logging.WARNING
-FILE_LOGLVL = logging.ERROR
+FILE_LIBLOGLVL = logging.WARNING
+FILE_LOGLVL = logging.INFO
class Core:
@@ -465,7 +466,8 @@ class Core:
# Locate FFmpeg
ffmpegBin = findFfmpeg()
- log.info("Detected FFmpeg bin: %s", ffmpegBin)
+ if not ffmpegBin:
+ print("Could not find FFmpeg")
settings = {
'canceled': False,
@@ -530,6 +532,7 @@ class Core:
@classmethod
def loadDefaultSettings(cls):
+ # settings that get saved into the ini file
cls.defaultSettings = {
"outputWidth": 1280,
"outputHeight": 720,
@@ -593,7 +596,7 @@ class Core:
logFile = logging.FileHandler(logFilename, delay=True)
logFile.setLevel(FILE_LOGLVL)
libLogFile = logging.FileHandler(libLogFilename, delay=True)
- libLogFile.setLevel(FILE_LOGLVL)
+ libLogFile.setLevel(FILE_LIBLOGLVL)
fileFormatter = logging.Formatter(
'[%(asctime)s] %(threadName)-10.10s %(name)-23.23s %(levelname)s: '
'%(message)s'
--
cgit v1.2.3
From 893c10c6ca8b7a9c04b9aaa086a46503166c880b Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 30 Apr 2022 00:16:38 -0400
Subject: test if ffmpeg is really found at startup
---
src/gui/mainwindow.py | 40 ++++++++++++++++++++++++----------------
src/toolkit/ffmpeg.py | 31 +++++++++++++++----------------
2 files changed, 39 insertions(+), 32 deletions(-)
diff --git a/src/gui/mainwindow.py b/src/gui/mainwindow.py
index 1b28b7e..fcf4b4c 100644
--- a/src/gui/mainwindow.py
+++ b/src/gui/mainwindow.py
@@ -336,24 +336,32 @@ class MainWindow(QtWidgets.QMainWindow):
log.info("Pillow version %s", Image.__version__)
# verify Ffmpeg version
- if not self.settings.value("ffmpegMsgShown"):
- try:
- with open(os.devnull, "w") as f:
- ffmpegVers = checkOutput(
- ['ffmpeg', '-version'], stderr=f
- )
- goodVersion = str(ffmpegVers).split()[2].startswith('4')
- except Exception:
- goodVersion = False
- else:
- goodVersion = True
-
- if not goodVersion:
+ if not self.core.FFMPEG_BIN:
self.showMessage(
- msg="You're using an old version of Ffmpeg. "
- "Some features may not work as expected."
+ msg="FFmpeg could not be found. This is a critical error. "
+ "Install FFmpeg, or download it and place the program executable "
+ "in the same folder as this program.",
+ icon='Critical'
)
- self.settings.setValue("ffmpegMsgShown", True)
+ else:
+ if not self.settings.value("ffmpegMsgShown"):
+ try:
+ with open(os.devnull, "w") as f:
+ ffmpegVers = checkOutput(
+ [self.core.FFMPEG_BIN, '-version'], stderr=f
+ )
+ goodVersion = str(ffmpegVers).split()[2].startswith('4')
+ except Exception:
+ goodVersion = False
+ else:
+ goodVersion = True
+
+ if not goodVersion:
+ self.showMessage(
+ msg="You're using an old version of Ffmpeg. "
+ "Some features may not work as expected."
+ )
+ self.settings.setValue("ffmpegMsgShown", True)
# Hotkeys for projects
QtWidgets.QShortcut("Ctrl+S", self, self.saveCurrentProject)
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index 256646e..5f9dec1 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -152,25 +152,24 @@ def closePipe(pipe):
def findFfmpeg():
+ if sys.platform == "win32":
+ bin = 'ffmpeg.exe'
+ else:
+ bin = 'ffmfpeg'
+
if getattr(sys, 'frozen', False):
# The application is frozen
- if sys.platform == "win32":
- return os.path.join(core.Core.wd, 'ffmpeg.exe')
- else:
- return os.path.join(core.Core.wd, 'ffmpeg')
+ bin = os.path.join(core.Core.wd, bin)
- else:
- if sys.platform == "win32":
- return "ffmpeg"
- else:
- try:
- with open(os.devnull, "w") as f:
- checkOutput(
- ['ffmpeg', '-version'], stderr=f
- )
- return "ffmpeg"
- except (subprocess.CalledProcessError, FileNotFoundError):
- return "avconv"
+ with open(os.devnull, "w") as f:
+ try:
+ checkOutput(
+ [bin, '-version'], stderr=f
+ )
+ except (subprocess.CalledProcessError, FileNotFoundError):
+ bin = ""
+
+ return bin
def createFfmpegCommand(inputFile, outputFile, components, duration=-1):
--
cgit v1.2.3
From 43580a961e43c15d5685039058286d8dde9d58a4 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 30 Apr 2022 21:42:47 -0400
Subject: fix misspelled ffmpeg
---
src/toolkit/ffmpeg.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py
index 5f9dec1..1649670 100644
--- a/src/toolkit/ffmpeg.py
+++ b/src/toolkit/ffmpeg.py
@@ -155,7 +155,7 @@ def findFfmpeg():
if sys.platform == "win32":
bin = 'ffmpeg.exe'
else:
- bin = 'ffmfpeg'
+ bin = 'ffmpeg'
if getattr(sys, 'frozen', False):
# The application is frozen
--
cgit v1.2.3
From e79d9db9f16b325d7433fc19dc8ea24dfc8a132c Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 30 Apr 2022 21:45:16 -0400
Subject: fix 'QThread killed while running' at program exit
---
src/gui/mainwindow.py | 30 +++++++++++++++---------------
src/gui/preview_thread.py | 12 ++++--------
2 files changed, 19 insertions(+), 23 deletions(-)
diff --git a/src/gui/mainwindow.py b/src/gui/mainwindow.py
index fcf4b4c..f6de763 100644
--- a/src/gui/mainwindow.py
+++ b/src/gui/mainwindow.py
@@ -10,7 +10,7 @@ from PIL import Image
from queue import Queue
import sys
import os
-import atexit
+import signal
import filecmp
import time
import logging
@@ -74,9 +74,6 @@ class MainWindow(QtWidgets.QMainWindow):
self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
self.settings = Core.settings
- # Register clean-up functions
- atexit.register(self.cleanUp)
-
# Create stack of undoable user actions
self.undoStack = QtWidgets.QUndoStack(self)
undoLimit = self.settings.value("pref_undoLimit")
@@ -94,15 +91,18 @@ class MainWindow(QtWidgets.QMainWindow):
log.debug('Starting preview thread')
self.previewQueue = Queue()
self.previewThread = QtCore.QThread(self)
- self.previewWorker = preview_thread.Worker(self, self.previewQueue)
- self.previewWorker.error.connect(self.previewWindow.threadError)
+ self.previewWorker = preview_thread.Worker(
+ self.core,
+ self.settings,
+ self.previewQueue
+ )
self.previewWorker.moveToThread(self.previewThread)
+ self.newTask.connect(self.previewWorker.createPreviewImage)
+ self.processTask.connect(self.previewWorker.process)
+ self.previewWorker.error.connect(self.previewWindow.threadError)
self.previewWorker.imageCreated.connect(self.showPreviewImage)
self.previewThread.start()
- self.previewThread.finished.connect(
- lambda:
- log.critical('PREVIEW THREAD DIED! This should never happen.')
- )
+ self.previewThread.finished.connect(lambda: log.info('Preview thread finished.'))
timeout = 500
log.debug(
@@ -429,6 +429,9 @@ class MainWindow(QtWidgets.QMainWindow):
"Ctrl+Alt+Shift+A", self, lambda: log.debug(repr(self))
)
+ # Close MainWindow when receiving Ctrl+C from terminal
+ signal.signal(signal.SIGINT, lambda *args: self.close())
+
def __repr__(self):
return (
'%s\n'
@@ -441,15 +444,12 @@ class MainWindow(QtWidgets.QMainWindow):
)
)
- def cleanUp(self, *args):
+ def closeEvent(self, event):
log.info('Ending the preview thread')
self.timer.stop()
self.previewThread.quit()
self.previewThread.wait()
-
- def terminate(self, *args):
- self.cleanUp()
- sys.exit(0)
+ return super().closeEvent(event)
@disableWhenOpeningProject
def updateWindowTitle(self):
diff --git a/src/gui/preview_thread.py b/src/gui/preview_thread.py
index 614b584..137864b 100644
--- a/src/gui/preview_thread.py
+++ b/src/gui/preview_thread.py
@@ -22,17 +22,13 @@ class Worker(QtCore.QObject):
imageCreated = pyqtSignal(QtGui.QImage)
error = pyqtSignal(str)
- def __init__(self, parent=None, queue=None):
+ def __init__(self, core, settings, queue):
super().__init__()
- parent.newTask.connect(self.createPreviewImage)
- parent.processTask.connect(self.process)
- #self.parent = parent
- self.core = parent.core
- self.settings = parent.settings
- self.queue = queue
-
+ self.core = core
+ self.settings = settings
width = int(self.settings.value('outputWidth'))
height = int(self.settings.value('outputHeight'))
+ self.queue = queue
self.background = Checkerboard(width, height)
@disableWhenOpeningProject
--
cgit v1.2.3
From a0d8e7bc0543aa9eb82d6d378ce6a8f5d0f85c11 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 30 Apr 2022 22:07:06 -0400
Subject: fix progress bar percentage not increasing numpy.floor now returns a
float if given a float
---
src/video_thread.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/video_thread.py b/src/video_thread.py
index 4a28261..5a28beb 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -340,7 +340,7 @@ class Worker(QtCore.QObject):
# increase progress bar value
completion = (audioI / self.audioArrayLen) * 100
if progressBarValue + 1 <= completion:
- progressBarValue = numpy.floor(completion)
+ progressBarValue = numpy.floor(completion).astype(int)
self.progressBarUpdate.emit(progressBarValue)
self.progressBarSetText.emit(
"Exporting video: %s%%" % str(int(progressBarValue))
--
cgit v1.2.3
From 597da503cadbb1008e0cf15c37f570ad4d27c105 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 30 Apr 2022 22:53:42 -0400
Subject: create test report in home folder after `--test`
---
src/command.py | 35 ++++++++++++++++++++++++++++++++++-
1 file changed, 34 insertions(+), 1 deletion(-)
diff --git a/src/command.py b/src/command.py
index cc13684..bf7941a 100644
--- a/src/command.py
+++ b/src/command.py
@@ -9,6 +9,7 @@ import os
import sys
import time
import signal
+import shutil
import logging
from . import core
@@ -225,6 +226,38 @@ class Command(QtCore.QObject):
from . import tests
test_report = os.path.join(core.Core.logDir, "test_report.log")
tests.run(test_report)
+
+ # Print test report into terminal
with open(test_report, "r") as f:
output = f.readlines()
- print("".join(output))
+ test_output = "".join(output)
+ print(test_output)
+
+ # Choose a numbered location to put the output file
+ logNumber = 0
+ def getFilename():
+ """Get a numbered filename for the final test report"""
+ nonlocal logNumber
+ name = os.path.join(os.path.expanduser('~'), "avp_test_report")
+ while True:
+ possibleName = f"{name}{logNumber:0>2}.txt"
+ if os.path.exists(possibleName) and logNumber < 100:
+ logNumber += 1
+ continue
+ break
+ return possibleName
+
+ # Copy latest debug log to chosen test report location
+ filename = getFilename()
+ if logNumber == 100:
+ print("Test Report could not be created.")
+ return
+ try:
+ shutil.copy(os.path.join(core.Core.logDir, "avp_debug.log"), filename)
+ except FileNotFoundError:
+ print("No debug log found.")
+ # Append actual test report to debug log
+ with open(filename, "a") as f:
+ f.write(f"{'='*59} debug log ends {'='*59}\n")
+ f.write(test_output)
+ print(f"Test Report created at {filename}")
--
cgit v1.2.3
From d2c88294171c7f16d7caa3e6d368cbc6da779107 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Sat, 30 Apr 2022 23:56:08 -0400
Subject: tested working instructions for Ubuntu 22.04 using the default
environment, Gnome on Wayland, in a clean virtual machine
---
README.md | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/README.md b/README.md
index 2c33b56..7d3e17c 100644
--- a/README.md
+++ b/README.md
@@ -31,22 +31,22 @@ The program works on Linux, macOS, and Windows. If you encounter problems runnin
# Installation
-## Manual installation on Linux (tested on Ubuntu 22.04)
+## Manual installation on Ubuntu 22.04
* Install ffmpeg: `sudo apt install ffmpeg`
* Install pip: `sudo apt install python3-pip`
-* Download this repo and run `pip3 install --user .` in this directory
-* Run the program with `avp` or `python3 -m avp`
-* **Sidenote:** If using a Python virtual environment, `pip3 install --system-site-packages --copies` may be useful to copy system packages when installing. This can be useful if you have difficulty installing PyQt5 using pip or wish to use the distro's package manager instead.
+* Install PyQt5: `sudo apt install python3-pyqt5`
+* Download this repo and run `pip install .` in this directory
+* Run the program with `python3 -m avp`
## Manual installation on Windows
* Install Python from the Windows Store
* Add Python to your system PATH (it should ask during the installation process)
* Download this repo
-* Open command prompt, `cd` into the repo directory, and run: `pip install --user .`
+* Open command prompt, `cd` into the repo directory, and run: `pip install .`
* Download and install ffmpeg from [https://www.ffmpeg.org/download.html](https://www.ffmpeg.org/download.html). You can use the static builds.
* Add ffmpeg to the system PATH as well, or copy ffmpeg.exe into the directory with the rest of the app
-* Now run `avp` from a command prompt window to start the app
+* Now run `python3 -m avp` from a command prompt window to start the app
## Manual installation on macOS
--
cgit v1.2.3