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.
---
main.py | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
(limited to 'main.py')
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)
--
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(-)
(limited to 'main.py')
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(-)
(limited to 'main.py')
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 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
(limited to 'main.py')
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
(limited to 'main.py')
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(-)
(limited to 'main.py')
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
(limited to 'main.py')
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 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(-)
(limited to 'main.py')
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(-)
(limited to 'main.py')
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 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(-)
(limited to 'main.py')
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(-)
(limited to 'main.py')
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(-)
(limited to 'main.py')
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(-)
(limited to 'main.py')
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(-)
(limited to 'main.py')
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 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
(limited to 'main.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(-)
(limited to 'main.py')
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(-)
(limited to 'main.py')
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(-)
(limited to 'main.py')
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(-)
(limited to 'main.py')
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 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(-)
(limited to 'main.py')
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(-)
(limited to 'main.py')
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(-)
(limited to 'main.py')
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(-)
(limited to 'main.py')
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(-)
(limited to 'main.py')
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(-)
(limited to 'main.py')
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(-)
(limited to 'main.py')
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
(limited to 'main.py')
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(-)
(limited to 'main.py')
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(-)
(limited to 'main.py')
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
(limited to 'main.py')
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
(limited to 'main.py')
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(-)
(limited to 'main.py')
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(-)
(limited to 'main.py')
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(-)
(limited to 'main.py')
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(-)
(limited to 'main.py')
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 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(-)
(limited to 'main.py')
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 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(-)
(limited to 'main.py')
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
(limited to 'main.py')
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 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(-)
(limited to 'main.py')
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 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
(limited to 'main.py')
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(-)
(limited to 'main.py')
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 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(-)
(limited to 'main.py')
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(-)
(limited to 'main.py')
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 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
(limited to 'main.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 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(-)
(limited to 'main.py')
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(-)
(limited to 'main.py')
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 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(-)
(limited to 'main.py')
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 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(-)
(limited to 'main.py')
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 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(-)
(limited to 'main.py')
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(-)
(limited to 'main.py')
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 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(-)
(limited to 'main.py')
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 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
(limited to 'main.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