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.
---
preview_thread.py | 1 +
1 file changed, 1 insertion(+)
(limited to 'preview_thread.py')
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)
--
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 'preview_thread.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 'preview_thread.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 'preview_thread.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 'preview_thread.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 e6d119769f825a523bc96c3bfe1c87015be54c9f Mon Sep 17 00:00:00 2001
From: DH4
Date: Sat, 3 Jun 2017 19:29:25 -0500
Subject: Render order reversed to match component list.
---
preview_thread.py | 2 +-
video_thread.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
(limited to 'preview_thread.py')
diff --git a/preview_thread.py b/preview_thread.py
index b20e9a1..63d1ac5 100644
--- a/preview_thread.py
+++ b/preview_thread.py
@@ -56,7 +56,7 @@ class Worker(QtCore.QObject):
frame.paste(im)
components = nextPreviewInformation["components"]
- for component in components:
+ for component in reversed(components):
newFrame = Image.alpha_composite(frame,component.previewRender(self))
frame = Image.alpha_composite(frame,newFrame)
diff --git a/video_thread.py b/video_thread.py
index 0d42406..c97cc24 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -43,7 +43,7 @@ class Worker(QtCore.QObject):
else:
frame = self.getBackgroundAtIndex(i[1])
- for compNo, comp in enumerate(self.components):
+ for compNo, comp in reversed(list(enumerate(self.components))):
if compNo in self.staticComponents and self.staticComponents[compNo] != None:
frame = Image.alpha_composite(frame, self.staticComponents[compNo])
else:
--
cgit v1.2.3
From 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 'preview_thread.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 6a1deb9b781890f9a62a6f6ce608856c1fc9e720 Mon Sep 17 00:00:00 2001
From: DH4
Date: Tue, 6 Jun 2017 04:04:42 -0500
Subject: Add checkerboard for transpart frames.
---
background.png | Bin 3711 -> 45367 bytes
preview_thread.py | 8 +++++++-
video_thread.py | 8 +++++++-
3 files changed, 14 insertions(+), 2 deletions(-)
(limited to 'preview_thread.py')
diff --git a/background.png b/background.png
index 7a33158..fb58593 100644
Binary files a/background.png and b/background.png differ
diff --git a/preview_thread.py b/preview_thread.py
index 5116707..04683ae 100644
--- a/preview_thread.py
+++ b/preview_thread.py
@@ -6,6 +6,8 @@ import core
import time
from queue import Queue, Empty
import numpy
+import os
+from copy import copy
class Worker(QtCore.QObject):
@@ -19,6 +21,9 @@ class Worker(QtCore.QObject):
self.queue = queue
self.core.settings = parent.settings
self.stackedWidget = parent.window.stackedWidget
+ self.background = Image.new("RGBA", (1920, 1080),(0,0,0,0))
+ self.background.paste(Image.open(os.path.join(os.path.dirname(os.path.realpath(__file__)),"background.png")))
+
@pyqtSlot(str, list)
@@ -41,7 +46,8 @@ class Worker(QtCore.QObject):
width = int(self.core.settings.value('outputWidth'))
height = int(self.core.settings.value('outputHeight'))
- frame = Image.new("RGBA", (width, height),(0,0,0,0))
+ frame = copy(self.background)
+ frame = frame.resize((width,height))
components = nextPreviewInformation["components"]
for component in reversed(components):
diff --git a/video_thread.py b/video_thread.py
index ac4162c..e880263 100644
--- a/video_thread.py
+++ b/video_thread.py
@@ -63,10 +63,16 @@ class Worker(QtCore.QObject):
self.bgI += 1
def previewDispatch(self):
+ background = Image.new("RGBA", (1920, 1080),(0,0,0,0))
+ background.paste(Image.open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "background.png")))
+ background = background.resize((self.width, self.height))
+
while not self.stopped:
i = self.previewQueue.get()
if time.time() - self.lastPreview >= 0.06 or i[0] == 0:
- self._image = ImageQt(i[1])
+ image = copy(background)
+ image = Image.alpha_composite(image, i[1])
+ self._image = ImageQt(image)
self.imageCreated.emit(QtGui.QImage(self._image))
self.lastPreview = time.time()
--
cgit v1.2.3
From 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 'preview_thread.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 c05efc73ee069fe2eb8776a27b503ada2adb4af6 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 15 Jun 2017 22:15:03 -0400
Subject: various bugfixes, blankFrame method for components
don't crash from broken project files or nonexistent videopaths, and shareable common paths in core.py
---
components/__base__.py | 4 ++
components/color.py | 2 +-
components/image.py | 6 ++-
components/image.ui | 2 +-
components/original.py | 4 +-
components/text.py | 2 +-
components/video.py | 27 ++++++++-----
components/video.ui | 2 +-
core.py | 106 +++++++++++++++++++++++++++++++++----------------
mainwindow.py | 4 +-
preview_thread.py | 5 +--
11 files changed, 107 insertions(+), 57 deletions(-)
(limited to 'preview_thread.py')
diff --git a/components/__base__.py b/components/__base__.py
index bc6644b..88f22d4 100644
--- a/components/__base__.py
+++ b/components/__base__.py
@@ -1,4 +1,5 @@
from PyQt4 import QtGui, QtCore
+from PIL import Image
class Component(QtCore.QObject):
@@ -45,6 +46,9 @@ class Component(QtCore.QObject):
for var, value in kwargs.items():
exec('self.%s = value' % var)
+ def blankFrame(self, width, height):
+ return Image.new("RGBA", (width, height), (0, 0, 0, 0))
+
def pickColor(self):
dialog = QtGui.QColorDialog()
dialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True)
diff --git a/components/color.py b/components/color.py
index 5912bfa..36f3906 100644
--- a/components/color.py
+++ b/components/color.py
@@ -71,7 +71,7 @@ class Component(__base__.Component):
def drawFrame(self, width, height):
r, g, b = self.color1
- return Image.new("RGBA", (width, height), (r, g, b, 255))
+ return self.blankFrame(width, height)
def loadPreset(self, pr, presetName=None):
super().loadPreset(pr, presetName)
diff --git a/components/image.py b/components/image.py
index a2f0521..b6aa29b 100644
--- a/components/image.py
+++ b/components/image.py
@@ -38,6 +38,7 @@ class Component(__base__.Component):
super().update()
def previewRender(self, previewWorker):
+ self.imageFormats = previewWorker.core.imageFormats
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
return self.drawFrame(width, height)
@@ -52,7 +53,7 @@ class Component(__base__.Component):
return self.drawFrame(width, height)
def drawFrame(self, width, height):
- frame = Image.new("RGBA", (width, height), (0, 0, 0, 0))
+ frame = self.blankFrame(width, height)
if self.imagePath and os.path.exists(self.imagePath):
image = Image.open(self.imagePath)
if self.stretched and image.size != (width, height):
@@ -85,7 +86,8 @@ class Component(__base__.Component):
def pickImage(self):
imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
filename = QtGui.QFileDialog.getOpenFileName(
- self.page, "Choose Image", imgDir, "Image Files (*.jpg *.png)")
+ self.page, "Choose Image", imgDir,
+ "Image Files (%s)" % " ".join(self.imageFormats))
if filename:
self.settings.setValue("backgroundDir", os.path.dirname(filename))
self.page.lineEdit_image.setText(filename)
diff --git a/components/image.ui b/components/image.ui
index 685e997..6df03a5 100644
--- a/components/image.ui
+++ b/components/image.ui
@@ -230,7 +230,7 @@
10
- 200
+ 400
100
diff --git a/components/original.py b/components/original.py
index 9df2815..5e2f9d4 100644
--- a/components/original.py
+++ b/components/original.py
@@ -145,7 +145,7 @@ class Component(__base__.Component):
bF = width / 64
bH = bF / 2
bQ = bF / 4
- imTop = Image.new("RGBA", (width, height), (0, 0, 0, 0))
+ imTop = self.blankFrame(width, height)
draw = ImageDraw.Draw(imTop)
r, g, b = color
color2 = (r, g, b, 125)
@@ -163,7 +163,7 @@ class Component(__base__.Component):
imBottom = imTop.transpose(Image.FLIP_TOP_BOTTOM)
- im = Image.new("RGBA", (width, height), (0, 0, 0, 0))
+ im = self.blankFrame(width, height)
if layout == 0:
y = 0 - int(height/100*43)
diff --git a/components/text.py b/components/text.py
index 165a093..f8ef7b3 100644
--- a/components/text.py
+++ b/components/text.py
@@ -126,7 +126,7 @@ class Component(__base__.Component):
def addText(self, width, height):
x, y = self.getXY()
- im = Image.new("RGBA", (width, height), (0, 0, 0, 0))
+ im = self.blankFrame(width, height)
image = ImageQt(im)
painter = QPainter(image)
diff --git a/components/video.py b/components/video.py
index 5f55211..3d43a18 100644
--- a/components/video.py
+++ b/components/video.py
@@ -128,12 +128,13 @@ class Component(__base__.Component):
super().update()
def previewRender(self, previewWorker):
+ self.videoFormats = previewWorker.core.videoFormats
width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight'))
self.updateChunksize(width, height)
frame = self.getPreviewFrame(width, height)
if not frame:
- return Image.new("RGBA", (width, height), (0, 0, 0, 0))
+ return self.blankFrame(width, height)
else:
return frame
@@ -141,6 +142,7 @@ class Component(__base__.Component):
super().preFrameRender(**kwargs)
width = int(self.worker.core.settings.value('outputWidth'))
height = int(self.worker.core.settings.value('outputHeight'))
+ self.blankFrame_ = self.blankFrame(width, height)
self.updateChunksize(width, height)
self.video = Video(
ffmpeg=self.parent.core.FFMPEG_BIN, videoPath=self.videoPath,
@@ -148,10 +150,13 @@ class Component(__base__.Component):
frameRate=int(self.settings.value("outputFrameRate")),
parent=self.parent, loopVideo=self.loopVideo,
component=self, scale=self.scale
- )
+ ) if os.path.exists(self.videoPath) else None
def frameRender(self, moduleNo, arrayNo, frameNo):
- return self.video.frame(frameNo)
+ if self.video:
+ return self.video.frame(frameNo)
+ else:
+ return self.blankFrame_
def loadPreset(self, pr, presetName=None):
super().loadPreset(pr, presetName)
@@ -177,7 +182,7 @@ class Component(__base__.Component):
imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
filename = QtGui.QFileDialog.getOpenFileName(
self.page, "Choose Video",
- imgDir, "Video Files (*.mp4 *.mov)"
+ imgDir, "Video Files (%s)" % " ".join(self.videoFormats)
)
if filename:
self.settings.setValue("backgroundDir", os.path.dirname(filename))
@@ -228,10 +233,14 @@ def scale(scale, width, height, returntype=None):
def finalizeFrame(self, imageData, width, height):
if self.distort:
- image = Image.frombytes(
- 'RGBA',
- (width, height),
- imageData)
+ try:
+ image = Image.frombytes(
+ 'RGBA',
+ (width, height),
+ imageData)
+ except ValueError:
+ print('#### ignored invalid data caused by distortion ####')
+ image = self.blankFrame(width, height)
else:
image = Image.frombytes(
'RGBA',
@@ -240,7 +249,7 @@ def finalizeFrame(self, imageData, width, height):
if self.scale != 100 \
or self.xPosition != 0 or self.yPosition != 0:
- frame = Image.new("RGBA", (width, height), (0, 0, 0, 0))
+ frame = self.blankFrame(width, height)
frame.paste(image, box=(self.xPosition, self.yPosition))
else:
frame = image
diff --git a/components/video.ui b/components/video.ui
index fa088fa..f05e8a5 100644
--- a/components/video.ui
+++ b/components/video.ui
@@ -234,7 +234,7 @@
10
- 200
+ 400
100
diff --git a/core.py b/core.py
index 8eb7d16..3fca7bf 100644
--- a/core.py
+++ b/core.py
@@ -24,6 +24,32 @@ class Core():
self.presetDir = os.path.join(self.dataDir, 'presets')
self.wd = os.path.dirname(os.path.realpath(__file__))
self.loadEncoderOptions()
+ self.videoFormats = Core.appendUppercase([
+ '*.mp4',
+ '*.mov',
+ '*.mkv',
+ '*.avi',
+ '*.webm',
+ '*.flv',
+ ])
+ self.audioFormats = Core.appendUppercase([
+ '*.mp3',
+ '*.wav',
+ '*.ogg',
+ '*.fla',
+ '*.aac',
+ ])
+ self.imageFormats = Core.appendUppercase([
+ '*.png',
+ '*.jpg',
+ '*.tif',
+ '*.tiff',
+ '*.gif',
+ '*.bmp',
+ '*.ico',
+ '*.xbm',
+ '*.xpm',
+ ])
self.findComponents()
self.selectedComponents = []
@@ -116,44 +142,48 @@ class Core():
which implements an insertComponent method'''
errcode, data = self.parseAvFile(filepath)
if errcode == 0:
- for i, tup in enumerate(data['Components']):
- name, vers, preset = tup
- clearThis = False
-
- # add loaded named presets to savedPresets dict
- if 'preset' in preset and preset['preset'] != None:
- nam = preset['preset']
- filepath2 = os.path.join(
- self.presetDir, name, str(vers), nam)
- origSaveValueStore = self.getPreset(filepath2)
- if origSaveValueStore:
- self.savedPresets[nam] = dict(origSaveValueStore)
- else:
- # saved preset was renamed or deleted
- clearThis = True
-
- # insert component into the loader
- loader.insertComponent(
- self.moduleIndexFor(name), -1)
- try:
+ try:
+ for i, tup in enumerate(data['Components']):
+ name, vers, preset = tup
+ clearThis = False
+
+ # add loaded named presets to savedPresets dict
if 'preset' in preset and preset['preset'] != None:
- self.selectedComponents[-1].loadPreset(
- preset
- )
- else:
- self.selectedComponents[-1].loadPreset(
- preset,
- preset['preset']
- )
- except KeyError as e:
- print('%s missing value %s' %
- (self.selectedComponents[-1], e))
-
- if clearThis:
- self.clearPreset(-1, loader)
+ nam = preset['preset']
+ filepath2 = os.path.join(
+ self.presetDir, name, str(vers), nam)
+ origSaveValueStore = self.getPreset(filepath2)
+ if origSaveValueStore:
+ self.savedPresets[nam] = dict(origSaveValueStore)
+ else:
+ # saved preset was renamed or deleted
+ clearThis = True
+
+ # insert component into the loader
+ loader.insertComponent(
+ self.moduleIndexFor(name), -1)
+ try:
+ if 'preset' in preset and preset['preset'] != None:
+ self.selectedComponents[-1].loadPreset(
+ preset
+ )
+ else:
+ self.selectedComponents[-1].loadPreset(
+ preset,
+ preset['preset']
+ )
+ except KeyError as e:
+ print('%s missing value %s' %
+ (self.selectedComponents[-1], e))
+ if clearThis:
+ self.clearPreset(-1, loader)
+ except:
+ errcode = 1
+ data = sys.exc_info()
- elif errcode == 1:
+
+ if errcode == 1:
typ, value, _ = data
if typ.__name__ == KeyError:
# probably just an old version, still loadable
@@ -398,3 +428,9 @@ class Core():
def presetFromString(string):
'''Turns a string repr of OrderedDict into a regular dict'''
return dict(eval(string))
+
+ @staticmethod
+ def appendUppercase(lst):
+ for form, i in zip(lst, range(len(lst))):
+ lst.append(form.upper())
+ return lst
diff --git a/mainwindow.py b/mainwindow.py
index ad1df10..f1959cb 100644
--- a/mainwindow.py
+++ b/mainwindow.py
@@ -310,7 +310,7 @@ class MainWindow(QtCore.QObject):
fileName = QtGui.QFileDialog.getOpenFileName(
self.window, "Open Music File",
- inputDir, "Music Files (*.mp3 *.wav *.ogg *.fla *.aac)")
+ inputDir, "Music Files (%s)" % " ".join(self.core.audioFormats))
if not fileName == "":
self.settings.setValue("inputDir", os.path.dirname(fileName))
@@ -322,7 +322,7 @@ class MainWindow(QtCore.QObject):
fileName = QtGui.QFileDialog.getSaveFileName(
self.window, "Set Output Video File",
outputDir,
- "Video Files (*.mp4 *.mov *.mkv *.avi *.webm *.flv);; All Files (*)")
+ "Video Files (%s);; All Files (*)" % " ".join(self.core.videoFormats))
if not fileName == "":
self.settings.setValue("outputDir", os.path.dirname(fileName))
diff --git a/preview_thread.py b/preview_thread.py
index d54dba5..e3e8279 100644
--- a/preview_thread.py
+++ b/preview_thread.py
@@ -1,11 +1,9 @@
from PyQt4 import QtCore, QtGui, uic
from PyQt4.QtCore import pyqtSignal, pyqtSlot
-from PIL import Image, ImageDraw, ImageFont
+from PIL import Image
from PIL.ImageQt import ImageQt
import core
-import time
from queue import Queue, Empty
-import numpy
import os
from copy import copy
@@ -18,6 +16,7 @@ class Worker(QtCore.QObject):
QtCore.QObject.__init__(self)
parent.newTask.connect(self.createPreviewImage)
parent.processTask.connect(self.process)
+ self.parent = parent
self.core = core.Core()
self.queue = queue
self.core.settings = parent.settings
--
cgit v1.2.3
From 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 'preview_thread.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 'preview_thread.py')
diff --git a/background.png b/background.png
deleted file mode 100644
index fb58593..0000000
Binary files a/background.png and /dev/null differ
diff --git a/command.py b/command.py
deleted file mode 100644
index 1a1e810..0000000
--- a/command.py
+++ /dev/null
@@ -1,126 +0,0 @@
-from PyQt4 import QtCore
-from PyQt4.QtCore import QSettings
-import argparse
-import os
-import sys
-
-import core
-import video_thread
-from main import LoadDefaultSettings
-
-
-class Command(QtCore.QObject):
-
- videoTask = QtCore.pyqtSignal(str, str, list)
-
- def __init__(self):
- QtCore.QObject.__init__(self)
- self.core = core.Core()
- self.dataDir = self.core.dataDir
- self.canceled = False
-
- self.parser = argparse.ArgumentParser(
- description='Create a visualization for an audio file',
- epilog='EXAMPLE COMMAND: main.py myvideotemplate.avp '
- '-i ~/Music/song.mp3 -o ~/video.mp4 '
- '-c 0 image path=~/Pictures/thisWeeksPicture.jpg '
- '-c 1 video "preset=My Logo" -c 2 vis layout=classic')
- self.parser.add_argument(
- '-i', '--input', metavar='SOUND',
- help='input audio file')
- self.parser.add_argument(
- '-o', '--output', metavar='OUTPUT',
- help='output video file')
-
- # optional arguments
- self.parser.add_argument(
- 'projpath', metavar='path-to-project',
- help='open a project file (.avp)', nargs='?')
- self.parser.add_argument(
- '-c', '--comp', metavar=('LAYER', 'ARG'),
- help='first arg must be component NAME to insert at LAYER.'
- '"help" for information about possible args for a component.',
- nargs='*', action='append')
-
- self.args = self.parser.parse_args()
- self.settings = QSettings(
- os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat)
- LoadDefaultSettings(self)
-
- if self.args.projpath:
- self.core.openProject(self, self.args.projpath)
- self.core.selectedComponents = list(
- reversed(self.core.selectedComponents))
- self.core.componentListChanged()
-
- if self.args.comp:
- for comp in self.args.comp:
- pos = comp[0]
- name = comp[1]
- args = comp[2:]
- try:
- pos = int(pos)
- except ValueError:
- print(pos, 'is not a layer number.')
- quit(1)
- realName = self.parseCompName(name)
- if not realName:
- print(name, 'is not a valid component name.')
- quit(1)
- modI = self.core.moduleIndexFor(realName)
- i = self.core.insertComponent(pos, modI, self)
- for arg in args:
- self.core.selectedComponents[i].command(arg)
-
- if self.args.input and self.args.output:
- self.createAudioVisualisation()
- elif 'help' not in sys.argv:
- self.parser.print_help()
- quit(1)
-
- def createAudioVisualisation(self):
- self.videoThread = QtCore.QThread(self)
- self.videoWorker = video_thread.Worker(self)
- self.videoWorker.moveToThread(self.videoThread)
- self.videoWorker.videoCreated.connect(self.videoCreated)
-
- self.videoThread.start()
- self.videoTask.emit(
- self.args.input,
- self.args.output,
- list(reversed(self.core.selectedComponents))
- )
-
- def videoCreated(self):
- self.videoThread.quit()
- self.videoThread.wait()
- quit(0)
-
- def showMessage(self, **kwargs):
- print(kwargs['msg'])
- if 'detail' in kwargs:
- print(kwargs['detail'])
-
- def drawPreview(self, *args):
- pass
-
- def parseCompName(self, name):
- '''Deduces a proper component name out of a commandline arg'''
-
- if name.title() in self.core.compNames:
- return name.title()
- for compName in self.core.compNames:
- if name.capitalize() in compName:
- return compName
-
- compFileNames = [ \
- os.path.splitext(os.path.basename(
- mod.__file__))[0] \
- for mod in self.core.modules \
- ]
- for i, compFileName in enumerate(compFileNames):
- if name.lower() in compFileName:
- return self.core.compNames[i]
- return
-
- return None
diff --git a/components/__base__.py b/components/__base__.py
deleted file mode 100644
index bef7f0e..0000000
--- a/components/__base__.py
+++ /dev/null
@@ -1,153 +0,0 @@
-from PyQt4 import QtGui, QtCore
-from PIL import Image
-import os
-
-
-class Component(QtCore.QObject):
- '''A base class for components to inherit from'''
-
- # modified = QtCore.pyqtSignal(int, bool)
-
- def __init__(self, moduleIndex, compPos, core):
- super().__init__()
- self.currentPreset = None
- self.moduleIndex = moduleIndex
- self.compPos = compPos
- self.core = core
-
- def __str__(self):
- return self.__doc__
-
- def version(self):
- # change this number to identify new versions of a component
- return 1
-
- def cancel(self):
- # please stop any lengthy process in response to this variable
- self.canceled = True
-
- def reset(self):
- self.canceled = False
-
- def update(self):
- self.modified.emit(self.compPos, self.savePreset())
- # read your widget values, then call super().update()
-
- def loadPreset(self, presetDict, presetName):
- '''Subclasses take (presetDict, presetName=None) as args.
- Must use super().loadPreset(presetDict, presetName) first,
- then update self.page widgets using the preset dict.
- '''
- self.currentPreset = presetName \
- if presetName != None else presetDict['preset']
-
- def preFrameRender(self, **kwargs):
- '''Triggered only before a video is exported (video_thread.py)
- self.worker = the video thread worker
- self.completeAudioArray = a list of audio samples
- self.sampleSize = number of audio samples per video frame
- self.progressBarUpdate = signal to set progress bar number
- self.progressBarSetText = signal to set progress bar text
- Use the latter two signals to update the MainProgram if needed
- for a long initialization procedure (i.e., for a visualizer)
- '''
- for var, value in kwargs.items():
- exec('self.%s = value' % var)
-
- def command(self, arg):
- '''Configure a component using argument from the commandline.
- Use super().command(arg) at the end of a subclass's method,
- if no arguments are found in that method first
- '''
- if arg.startswith('preset='):
- _, preset = arg.split('=', 1)
- path = os.path.join(self.core.getPresetDir(self), preset)
- if not os.path.exists(path):
- print('Couldn\'t locate preset "%s"' % preset)
- quit(1)
- else:
- print('Opening "%s" preset on layer %s' % \
- (preset, self.compPos))
- self.core.openPreset(path, self.compPos, preset)
- else:
- print(
- self.__doc__, 'Usage:\n'
- 'Open a preset for this component:\n'
- ' "preset=Preset Name"')
- self.commandHelp()
- quit(0)
-
- def commandHelp(self):
- '''Print help text for this Component's commandline arguments'''
-
- def blankFrame(self, width, height):
- return Image.new("RGBA", (width, height), (0, 0, 0, 0))
-
- def pickColor(self):
- '''Use color picker to get color input from the user,
- and return this as an RGB string and QPushButton stylesheet.
- In a subclass apply stylesheet to any color selection widgets
- '''
- dialog = QtGui.QColorDialog()
- dialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True)
- color = dialog.getColor()
- if color.isValid():
- RGBstring = '%s,%s,%s' % (
- str(color.red()), str(color.green()), str(color.blue()))
- btnStyle = "QPushButton{background-color: %s; outline: none;}" \
- % color.name()
- return RGBstring, btnStyle
- else:
- return None, None
-
- def RGBFromString(self, string):
- ''' Turns an RGB string like "255, 255, 255" into a tuple '''
- try:
- tup = tuple([int(i) for i in string.split(',')])
- if len(tup) != 3:
- raise ValueError
- for i in tup:
- if i > 255 or i < 0:
- raise ValueError
- return tup
- except:
- return (255, 255, 255)
-
- '''
- ### Reference methods for creating a new component
- ### (Inherit from this class and define these)
-
- def widget(self, parent):
- self.parent = parent
- page = uic.loadUi(os.path.join(
- os.path.dirname(os.path.realpath(__file__)), 'example.ui'))
- # --- connect widget signals here ---
- self.page = page
- return page
-
- def update(self):
- super().update()
- self.parent.drawPreview()
-
- def previewRender(self, previewWorker):
- width = int(previewWorker.core.settings.value('outputWidth'))
- height = int(previewWorker.core.settings.value('outputHeight'))
- image = Image.new("RGBA", (width, height), (0,0,0,0))
- return image
-
- def frameRender(self, moduleNo, frameNo):
- width = int(self.worker.core.settings.value('outputWidth'))
- height = int(self.worker.core.settings.value('outputHeight'))
- image = Image.new("RGBA", (width, height), (0,0,0,0))
- return image
- '''
-
-class BadComponentInit(Exception):
- def __init__(self, arg, name):
- string = \
-'''################################
-Mandatory argument "%s" not specified
- in %s instance initialization
-###################################'''
- print(string % (arg, name))
- quit()
diff --git a/components/__init__.py b/components/__init__.py
deleted file mode 100644
index 8b13789..0000000
--- a/components/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/components/color.py b/components/color.py
deleted file mode 100644
index 5ffcdea..0000000
--- a/components/color.py
+++ /dev/null
@@ -1,246 +0,0 @@
-from PIL import Image, ImageDraw
-from PyQt4 import uic, QtGui, QtCore
-from PyQt4.QtGui import QColor
-from PIL.ImageQt import ImageQt
-import os
-from . import __base__
-
-
-class Component(__base__.Component):
- '''Color'''
-
- modified = QtCore.pyqtSignal(int, dict)
-
- def widget(self, parent):
- self.parent = parent
- page = uic.loadUi(os.path.join(
- os.path.dirname(os.path.realpath(__file__)), 'color.ui'))
-
- self.color1 = (0, 0, 0)
- self.color2 = (133, 133, 133)
- self.x = 0
- self.y = 0
-
- page.lineEdit_color1.setText('%s,%s,%s' % self.color1)
- page.lineEdit_color2.setText('%s,%s,%s' % self.color2)
-
- btnStyle1 = "QPushButton { background-color : %s; outline: none; }" \
- % QColor(*self.color1).name()
-
- btnStyle2 = "QPushButton { background-color : %s; outline: none; }" \
- % QColor(*self.color2).name()
-
- page.pushButton_color1.setStyleSheet(btnStyle1)
- page.pushButton_color2.setStyleSheet(btnStyle2)
- page.pushButton_color1.clicked.connect(lambda: self.pickColor(1))
- page.pushButton_color2.clicked.connect(lambda: self.pickColor(2))
-
- # disable color #2 until non-default 'fill' option gets changed
- page.lineEdit_color2.setDisabled(True)
- page.pushButton_color2.setDisabled(True)
- page.spinBox_x.valueChanged.connect(self.update)
- page.spinBox_y.valueChanged.connect(self.update)
- page.spinBox_width.setValue(
- int(parent.settings.value("outputWidth")))
- page.spinBox_height.setValue(
- int(parent.settings.value("outputHeight")))
-
- page.lineEdit_color1.textChanged.connect(self.update)
- page.lineEdit_color2.textChanged.connect(self.update)
- page.spinBox_x.valueChanged.connect(self.update)
- page.spinBox_y.valueChanged.connect(self.update)
- page.spinBox_width.valueChanged.connect(self.update)
- page.spinBox_height.valueChanged.connect(self.update)
- page.checkBox_trans.stateChanged.connect(self.update)
-
- self.fillLabels = [ \
- 'Solid',
- 'Linear Gradient',
- 'Radial Gradient',
- ]
- for label in self.fillLabels:
- page.comboBox_fill.addItem(label)
- page.comboBox_fill.setCurrentIndex(0)
- page.comboBox_fill.currentIndexChanged.connect(self.update)
- page.comboBox_spread.currentIndexChanged.connect(self.update)
- page.spinBox_radialGradient_end.valueChanged.connect(self.update)
- page.spinBox_radialGradient_start.valueChanged.connect(self.update)
- page.spinBox_radialGradient_spread.valueChanged.connect(self.update)
- page.spinBox_linearGradient_end.valueChanged.connect(self.update)
- page.spinBox_linearGradient_start.valueChanged.connect(self.update)
- page.checkBox_stretch.stateChanged.connect(self.update)
-
- self.page = page
- return page
-
- def update(self):
- self.color1 = self.RGBFromString(self.page.lineEdit_color1.text())
- self.color2 = self.RGBFromString(self.page.lineEdit_color2.text())
- self.x = self.page.spinBox_x.value()
- self.y = self.page.spinBox_y.value()
- self.sizeWidth = self.page.spinBox_width.value()
- self.sizeHeight = self.page.spinBox_height.value()
- self.trans = self.page.checkBox_trans.isChecked()
- self.spread = self.page.comboBox_spread.currentIndex()
-
- self.RG_start = self.page.spinBox_radialGradient_start.value()
- self.RG_end = self.page.spinBox_radialGradient_end.value()
- self.RG_centre = self.page.spinBox_radialGradient_spread.value()
- self.stretch = self.page.checkBox_stretch.isChecked()
- self.LG_start = self.page.spinBox_linearGradient_start.value()
- self.LG_end = self.page.spinBox_linearGradient_end.value()
-
- self.fillType = self.page.comboBox_fill.currentIndex()
- if self.fillType == 0:
- self.page.lineEdit_color2.setEnabled(False)
- self.page.pushButton_color2.setEnabled(False)
- self.page.checkBox_trans.setEnabled(False)
- self.page.checkBox_stretch.setEnabled(False)
- self.page.comboBox_spread.setEnabled(False)
- else:
- self.page.lineEdit_color2.setEnabled(True)
- self.page.pushButton_color2.setEnabled(True)
- self.page.checkBox_trans.setEnabled(True)
- self.page.checkBox_stretch.setEnabled(True)
- self.page.comboBox_spread.setEnabled(True)
- self.page.fillWidget.setCurrentIndex(self.fillType)
-
- self.parent.drawPreview()
- super().update()
-
- def previewRender(self, previewWorker):
- width = int(previewWorker.core.settings.value('outputWidth'))
- height = int(previewWorker.core.settings.value('outputHeight'))
- return self.drawFrame(width, height)
-
- def preFrameRender(self, **kwargs):
- super().preFrameRender(**kwargs)
- return ['static']
-
- def frameRender(self, moduleNo, arrayNo, frameNo):
- width = int(self.worker.core.settings.value('outputWidth'))
- height = int(self.worker.core.settings.value('outputHeight'))
- return self.drawFrame(width, height)
-
- def drawFrame(self, width, height):
- r, g, b = self.color1
- shapeSize = (self.sizeWidth, self.sizeHeight)
- # in default state, skip all this logic and return a plain fill
- if self.fillType==0 and shapeSize == (width, height) \
- and self.x == 0 and self.y == 0:
- return Image.new("RGBA", (width, height), (r, g, b, 255))
-
- frame = self.blankFrame(width, height)
-
- # Return a solid image at x, y
- if self.fillType == 0:
- image = Image.new("RGBA", shapeSize, (r, g, b, 255))
- frame.paste(image, box=(self.x, self.y))
- return frame
-
- # Now fills that require using Qt...
- elif self.fillType > 0:
- image = ImageQt(frame)
- painter = QtGui.QPainter(image)
- if self.stretch:
- w = width; h = height
- else:
- w = self.sizeWidth; h = self.sizeWidth
-
- if self.fillType == 1: # Linear Gradient
- brush = QtGui.QLinearGradient(
- self.LG_start,
- self.LG_start,
- self.LG_start+width/3,
- self.LG_end)
-
- elif self.fillType == 2: # Radial Gradient
- brush = QtGui.QRadialGradient(
- self.RG_start,
- self.RG_end,
- w, h,
- self.RG_centre)
-
- brush.setSpread(self.spread)
- brush.setColorAt(0.0, QColor(*self.color1))
- if self.trans:
- brush.setColorAt(1.0, QColor(0, 0, 0, 0))
- elif self.fillType == 1 and self.stretch:
- brush.setColorAt(0.2, QColor(*self.color2))
- else:
- brush.setColorAt(1.0, QColor(*self.color2))
- painter.setBrush(brush)
- painter.drawRect(self.x, self.y,
- self.sizeWidth, self.sizeHeight)
- painter.end()
- imBytes = image.bits().asstring(image.numBytes())
- return Image.frombytes('RGBA', (width, height), imBytes)
-
- def loadPreset(self, pr, presetName=None):
- super().loadPreset(pr, presetName)
-
- self.page.comboBox_fill.setCurrentIndex(pr['fillType'])
- self.page.lineEdit_color1.setText('%s,%s,%s' % pr['color1'])
- self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2'])
- self.page.spinBox_x.setValue(pr['x'])
- self.page.spinBox_y.setValue(pr['y'])
- self.page.spinBox_width.setValue(pr['width'])
- self.page.spinBox_height.setValue(pr['height'])
- self.page.checkBox_trans.setChecked(pr['trans'])
-
- self.page.spinBox_radialGradient_start.setValue(pr['RG_start'])
- self.page.spinBox_radialGradient_end.setValue(pr['RG_end'])
- self.page.spinBox_radialGradient_spread.setValue(pr['RG_centre'])
- self.page.spinBox_linearGradient_start.setValue(pr['LG_start'])
- self.page.spinBox_linearGradient_end.setValue(pr['LG_end'])
- self.page.checkBox_stretch.setChecked(pr['stretch'])
- self.page.comboBox_spread.setCurrentIndex(pr['spread'])
-
- btnStyle1 = "QPushButton { background-color : %s; outline: none; }" \
- % QColor(*pr['color1']).name()
- btnStyle2 = "QPushButton { background-color : %s; outline: none; }" \
- % QColor(*pr['color2']).name()
- self.page.pushButton_color1.setStyleSheet(btnStyle1)
- self.page.pushButton_color2.setStyleSheet(btnStyle2)
-
- def savePreset(self):
- return {
- 'preset': self.currentPreset,
- 'color1': self.color1,
- 'color2': self.color2,
- 'x': self.x,
- 'y': self.y,
- 'fillType': self.fillType,
- 'width': self.sizeWidth,
- 'height': self.sizeHeight,
- 'trans': self.trans,
- 'stretch': self.stretch,
- 'spread': self.spread,
- 'RG_start': self.RG_start,
- 'RG_end': self.RG_end,
- 'RG_centre': self.RG_centre,
- 'LG_start': self.LG_start,
- 'LG_end': self.LG_end,
- }
-
- def pickColor(self, num):
- RGBstring, btnStyle = super().pickColor()
- if not RGBstring:
- return
- if num == 1:
- self.page.lineEdit_color1.setText(RGBstring)
- self.page.pushButton_color1.setStyleSheet(btnStyle)
- else:
- self.page.lineEdit_color2.setText(RGBstring)
- self.page.pushButton_color2.setStyleSheet(btnStyle)
-
- def commandHelp(self):
- print('Specify a color:\n color=255,255,255')
-
- def command(self, arg):
- if not arg.startswith('preset=') and '=' in arg:
- key, arg = arg.split('=', 1)
- if key == 'color':
- self.page.lineEdit_color1.setText(arg)
- return
- super().command(arg)
diff --git a/components/color.ui b/components/color.ui
deleted file mode 100644
index a9dacea..0000000
--- a/components/color.ui
+++ /dev/null
@@ -1,660 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 586
- 197
-
-
-
- Form
-
-
- -
-
-
- 4
-
-
-
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 31
- 0
-
-
-
- Color #1
-
-
-
- -
-
-
-
- 32
- 32
-
-
-
-
-
-
-
- 32
- 32
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 1
- 0
-
-
-
- 12
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 31
- 0
-
-
-
- Color #2
-
-
-
- -
-
-
-
- 32
- 32
-
-
-
-
-
-
-
- 32
- 32
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 1
- 0
-
-
-
- 12
-
-
-
-
-
- -
-
-
- 0
-
-
-
-
-
-
- 0
- 0
-
-
-
- Width
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
-
- 0
- 0
-
-
-
- 0
-
-
- 999999999
-
-
- 0
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Height
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
- 999999999
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- X
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
-
- 0
- 0
-
-
-
- -10000
-
-
- 10000
-
-
- 0
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Y
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
- -10000
-
-
- 10000
-
-
-
-
-
- -
-
-
- 0
-
-
-
-
-
-
- 0
- 0
-
-
-
- Fill
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- -1
-
-
- QComboBox::AdjustToContentsOnFirstShow
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Transparent
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Stretch
-
-
-
- -
-
-
-
-
- Pad
-
-
- -
-
- Reflect
-
-
- -
-
- Repeat
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Minimum
-
-
-
- 40
- 20
-
-
-
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
- 0
-
-
- 2
-
-
-
-
-
-
- -1
- 0
- 561
- 31
-
-
-
-
-
-
-
-
- 0
- 0
-
-
-
- Start
-
-
-
- -
-
-
- -10000
-
-
- 10000
-
-
- 10
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- End
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- -10000
-
-
- 10000
-
-
- 10
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
-
-
-
-
-
- -1
- -1
- 561
- 31
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Start
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- -10000
-
-
- 10000
-
-
- 10
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- End
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- -10000
-
-
- 10000
-
-
- 10
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Centre
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- QAbstractSpinBox::PlusMinus
-
-
- -10000
-
-
- 10000
-
-
- 3
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/components/image.py b/components/image.py
deleted file mode 100644
index f8ae64e..0000000
--- a/components/image.py
+++ /dev/null
@@ -1,111 +0,0 @@
-from PIL import Image, ImageDraw
-from PyQt4 import uic, QtGui, QtCore
-import os
-from . import __base__
-
-
-class Component(__base__.Component):
- '''Image'''
-
- modified = QtCore.pyqtSignal(int, dict)
-
- def widget(self, parent):
- self.parent = parent
- self.settings = parent.settings
- page = uic.loadUi(os.path.join(
- os.path.dirname(os.path.realpath(__file__)), 'image.ui'))
- self.imagePath = ''
- self.x = 0
- self.y = 0
-
- page.lineEdit_image.textChanged.connect(self.update)
- page.pushButton_image.clicked.connect(self.pickImage)
- page.spinBox_scale.valueChanged.connect(self.update)
- page.checkBox_stretch.stateChanged.connect(self.update)
- page.spinBox_x.valueChanged.connect(self.update)
- page.spinBox_y.valueChanged.connect(self.update)
-
- self.page = page
- return page
-
- def update(self):
- self.imagePath = self.page.lineEdit_image.text()
- self.scale = self.page.spinBox_scale.value()
- self.xPosition = self.page.spinBox_x.value()
- self.yPosition = self.page.spinBox_y.value()
- self.stretched = self.page.checkBox_stretch.isChecked()
- self.parent.drawPreview()
- super().update()
-
- def previewRender(self, previewWorker):
- self.imageFormats = previewWorker.core.imageFormats
- width = int(previewWorker.core.settings.value('outputWidth'))
- height = int(previewWorker.core.settings.value('outputHeight'))
- return self.drawFrame(width, height)
-
- def preFrameRender(self, **kwargs):
- super().preFrameRender(**kwargs)
- return ['static']
-
- def frameRender(self, moduleNo, arrayNo, frameNo):
- width = int(self.worker.core.settings.value('outputWidth'))
- height = int(self.worker.core.settings.value('outputHeight'))
- return self.drawFrame(width, height)
-
- def drawFrame(self, width, height):
- frame = self.blankFrame(width, height)
- if self.imagePath and os.path.exists(self.imagePath):
- image = Image.open(self.imagePath)
- if self.stretched and image.size != (width, height):
- image = image.resize((width, height), Image.ANTIALIAS)
- if self.scale != 100:
- newHeight = int((image.height / 100) * self.scale)
- newWidth = int((image.width / 100) * self.scale)
- image = image.resize((newWidth, newHeight), Image.ANTIALIAS)
- frame.paste(image, box=(self.xPosition, self.yPosition))
- return frame
-
- def loadPreset(self, pr, presetName=None):
- super().loadPreset(pr, presetName)
- self.page.lineEdit_image.setText(pr['image'])
- self.page.spinBox_scale.setValue(pr['scale'])
- self.page.spinBox_x.setValue(pr['x'])
- self.page.spinBox_y.setValue(pr['y'])
- self.page.checkBox_stretch.setChecked(pr['stretched'])
-
- def savePreset(self):
- return {
- 'preset': self.currentPreset,
- 'image': self.imagePath,
- 'scale': self.scale,
- 'stretched': self.stretched,
- 'x': self.xPosition,
- 'y': self.yPosition,
- }
-
- def pickImage(self):
- imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
- filename = QtGui.QFileDialog.getOpenFileName(
- self.page, "Choose Image", imgDir,
- "Image Files (%s)" % " ".join(self.imageFormats))
- if filename:
- self.settings.setValue("backgroundDir", os.path.dirname(filename))
- self.page.lineEdit_image.setText(filename)
- self.update()
-
- def command(self, arg):
- if not arg.startswith('preset=') and '=' in arg:
- key, arg = arg.split('=', 1)
- if key == 'path' and os.path.exists(arg):
- try:
- Image.open(arg)
- self.page.lineEdit_image.setText(arg)
- self.page.checkBox_stretch.setChecked(True)
- return
- except OSError as e:
- print("Not a supported image format")
- quit(1)
- super().command(arg)
-
- def commandHelp(self):
- print('Load an image:\n path=/filepath/to/image.png')
diff --git a/components/image.ui b/components/image.ui
deleted file mode 100644
index 6df03a5..0000000
--- a/components/image.ui
+++ /dev/null
@@ -1,259 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 586
- 197
-
-
-
- Form
-
-
- -
-
-
- 4
-
-
-
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 31
- 0
-
-
-
- Image
-
-
-
- -
-
-
-
- 1
- 0
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 1
- 0
-
-
-
-
- 32
- 32
-
-
-
- ...
-
-
-
- 32
- 32
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- X
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
- -10000
-
-
- 10000
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Y
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
-
- 0
- 0
-
-
-
- -1000
-
-
- 1000
-
-
- 0
-
-
-
-
-
-
-
- -
-
-
-
-
-
- Stretch
-
-
- false
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
- Scale
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- QAbstractSpinBox::UpDownArrows
-
-
- %
-
-
- 10
-
-
- 400
-
-
- 100
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
-
-
diff --git a/components/original.py b/components/original.py
deleted file mode 100644
index 6222157..0000000
--- a/components/original.py
+++ /dev/null
@@ -1,204 +0,0 @@
-import numpy
-from PIL import Image, ImageDraw
-from PyQt4 import uic, QtGui, QtCore
-from PyQt4.QtGui import QColor
-import os
-from . import __base__
-import time
-from copy import copy
-
-
-class Component(__base__.Component):
- '''Original Audio Visualization'''
-
- modified = QtCore.pyqtSignal(int, dict)
-
- def widget(self, parent):
- self.parent = parent
- self.visColor = (255, 255, 255)
-
- page = uic.loadUi(os.path.join(
- os.path.dirname(os.path.realpath(__file__)), 'original.ui'))
- page.comboBox_visLayout.addItem("Classic")
- page.comboBox_visLayout.addItem("Split")
- page.comboBox_visLayout.addItem("Bottom")
- page.comboBox_visLayout.setCurrentIndex(0)
- page.comboBox_visLayout.currentIndexChanged.connect(self.update)
- page.lineEdit_visColor.setText('%s,%s,%s' % self.visColor)
- page.pushButton_visColor.clicked.connect(lambda: self.pickColor())
- btnStyle = "QPushButton { background-color : %s; outline: none; }" \
- % QColor(*self.visColor).name()
- page.pushButton_visColor.setStyleSheet(btnStyle)
- page.lineEdit_visColor.textChanged.connect(self.update)
- self.page = page
- self.canceled = False
- return page
-
- def update(self):
- self.layout = self.page.comboBox_visLayout.currentIndex()
- self.visColor = self.RGBFromString(self.page.lineEdit_visColor.text())
- self.parent.drawPreview()
- super().update()
-
- def loadPreset(self, pr, presetName=None):
- super().loadPreset(pr, presetName)
-
- self.page.lineEdit_visColor.setText('%s,%s,%s' % pr['visColor'])
- btnStyle = "QPushButton { background-color : %s; outline: none; }" \
- % QColor(*pr['visColor']).name()
- self.page.pushButton_visColor.setStyleSheet(btnStyle)
- self.page.comboBox_visLayout.setCurrentIndex(pr['layout'])
-
- def savePreset(self):
- return {
- 'preset': self.currentPreset,
- 'layout': self.layout,
- 'visColor': self.visColor,
- }
-
- def previewRender(self, previewWorker):
- spectrum = numpy.fromfunction(
- lambda x: 0.008*(x-128)**2, (255,), dtype="int16")
- width = int(previewWorker.core.settings.value('outputWidth'))
- height = int(previewWorker.core.settings.value('outputHeight'))
- return self.drawBars(
- width, height, spectrum, self.visColor, self.layout)
-
- def preFrameRender(self, **kwargs):
- super().preFrameRender(**kwargs)
- self.smoothConstantDown = 0.08
- self.smoothConstantUp = 0.8
- self.lastSpectrum = None
- self.spectrumArray = {}
- self.width = int(self.worker.core.settings.value('outputWidth'))
- self.height = int(self.worker.core.settings.value('outputHeight'))
-
- for i in range(0, len(self.completeAudioArray), self.sampleSize):
- if self.canceled:
- break
- self.lastSpectrum = self.transformData(
- i, self.completeAudioArray, self.sampleSize,
- self.smoothConstantDown, self.smoothConstantUp,
- self.lastSpectrum)
- self.spectrumArray[i] = copy(self.lastSpectrum)
-
- progress = int(100*(i/len(self.completeAudioArray)))
- if progress >= 100:
- progress = 100
- pStr = "Analyzing audio: "+str(progress)+'%'
- self.progressBarSetText.emit(pStr)
- self.progressBarUpdate.emit(int(progress))
-
- def frameRender(self, moduleNo, arrayNo, frameNo):
- return self.drawBars(
- self.width, self.height,
- self.spectrumArray[arrayNo],
- self.visColor, self.layout)
-
- def pickColor(self):
- RGBstring, btnStyle = super().pickColor()
- if not RGBstring:
- return
- self.page.lineEdit_visColor.setText(RGBstring)
- self.page.pushButton_visColor.setStyleSheet(btnStyle)
-
- def transformData(
- self, i, completeAudioArray, sampleSize,
- smoothConstantDown, smoothConstantUp, lastSpectrum):
- if len(completeAudioArray) < (i + sampleSize):
- sampleSize = len(completeAudioArray) - i
-
- window = numpy.hanning(sampleSize)
- data = completeAudioArray[i:i+sampleSize][::1] * window
- paddedSampleSize = 2048
- paddedData = numpy.pad(
- data, (0, paddedSampleSize - sampleSize), 'constant')
- spectrum = numpy.fft.fft(paddedData)
- sample_rate = 44100
- frequencies = numpy.fft.fftfreq(len(spectrum), 1./sample_rate)
-
- y = abs(spectrum[0:int(paddedSampleSize/2) - 1])
-
- # filter the noise away
- # y[y<80] = 0
-
- y = 20 * numpy.log10(y)
- y[numpy.isinf(y)] = 0
-
- if lastSpectrum is not None:
- lastSpectrum[y < lastSpectrum] = \
- y[y < lastSpectrum] * smoothConstantDown + \
- lastSpectrum[y < lastSpectrum] * (1 - smoothConstantDown)
-
- lastSpectrum[y >= lastSpectrum] = \
- y[y >= lastSpectrum] * smoothConstantUp + \
- lastSpectrum[y >= lastSpectrum] * (1 - smoothConstantUp)
- else:
- lastSpectrum = y
-
- x = frequencies[0:int(paddedSampleSize/2) - 1]
-
- return lastSpectrum
-
- def drawBars(self, width, height, spectrum, color, layout):
- vH = height-height/8
- bF = width / 64
- bH = bF / 2
- bQ = bF / 4
- imTop = self.blankFrame(width, height)
- draw = ImageDraw.Draw(imTop)
- r, g, b = color
- color2 = (r, g, b, 125)
-
- bP = height / 1200
-
- for j in range(0, 63):
- draw.rectangle((
- bH + j * bF, vH+bQ, bH + j * bF + bF, vH + bQ -
- spectrum[j * 4] * bP - bH), fill=color2)
-
- draw.rectangle((
- bH + bQ + j * bF, vH, bH + bQ + j * bF + bH, vH -
- spectrum[j * 4] * bP), fill=color)
-
- imBottom = imTop.transpose(Image.FLIP_TOP_BOTTOM)
-
- im = self.blankFrame(width, height)
-
- if layout == 0:
- y = 0 - int(height/100*43)
- im.paste(imTop, (0, y), mask=imTop)
- y = 0 + int(height/100*43)
- im.paste(imBottom, (0, y), mask=imBottom)
-
- if layout == 1:
- y = 0 + int(height/100*10)
- im.paste(imTop, (0, y), mask=imTop)
- y = 0 - int(height/100*10)
- im.paste(imBottom, (0, y), mask=imBottom)
-
- if layout == 2:
- y = 0 + int(height/100*10)
- im.paste(imTop, (0, y), mask=imTop)
-
- return im
-
- def command(self, arg):
- if not arg.startswith('preset=') and '=' in arg:
- key, arg = arg.split('=', 1)
- if key == 'color':
- self.page.lineEdit_visColor.setText(arg)
- return
- elif key == 'layout':
- if arg == 'classic':
- self.page.comboBox_visLayout.setCurrentIndex(0)
- elif arg == 'split':
- self.page.comboBox_visLayout.setCurrentIndex(1)
- elif arg == 'bottom':
- self.page.comboBox_visLayout.setCurrentIndex(2)
- return
- super().command(arg)
-
- def commandHelp(self):
- print('Give a layout name:\n layout=[classic/split/bottom]')
- print('Specify a color:\n color=255,255,255')
diff --git a/components/original.ui b/components/original.ui
deleted file mode 100644
index 5808653..0000000
--- a/components/original.ui
+++ /dev/null
@@ -1,108 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 633
- 178
-
-
-
-
- 180
- 0
-
-
-
- Form
-
-
- -
-
-
- 4
-
-
-
-
-
-
- 0
- 0
-
-
-
- Visualizer Layout
-
-
-
- -
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
- Visualizer Color
-
-
-
- -
-
-
-
- 32
- 32
-
-
-
-
-
-
-
- 32
- 32
-
-
-
-
- -
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
-
-
diff --git a/components/text.py b/components/text.py
deleted file mode 100644
index 2375dcd..0000000
--- a/components/text.py
+++ /dev/null
@@ -1,176 +0,0 @@
-from PIL import Image, ImageDraw
-from PyQt4.QtGui import QPainter, QColor, QFont
-from PyQt4 import uic, QtGui, QtCore
-from PIL.ImageQt import ImageQt
-import os
-import io
-from . import __base__
-
-
-class Component(__base__.Component):
- '''Title Text'''
-
- modified = QtCore.pyqtSignal(int, dict)
-
- def __init__(self, *args):
- super().__init__(*args)
- self.titleFont = QFont()
-
- def widget(self, parent):
- height = int(parent.settings.value('outputHeight'))
- width = int(parent.settings.value('outputWidth'))
-
- self.parent = parent
- self.textColor = (255, 255, 255)
- self.title = 'Text'
- self.alignment = 1
- self.fontSize = height / 13.5
- fm = QtGui.QFontMetrics(self.titleFont)
- self.xPosition = width / 2 - fm.width(self.title)/2
- self.yPosition = height / 2 * 1.036
-
- page = uic.loadUi(os.path.join(
- os.path.dirname(os.path.realpath(__file__)), 'text.ui'))
- page.comboBox_textAlign.addItem("Left")
- page.comboBox_textAlign.addItem("Middle")
- page.comboBox_textAlign.addItem("Right")
-
- page.lineEdit_textColor.setText('%s,%s,%s' % self.textColor)
- page.pushButton_textColor.clicked.connect(self.pickColor)
- btnStyle = "QPushButton { background-color : %s; outline: none; }" \
- % QColor(*self.textColor).name()
- page.pushButton_textColor.setStyleSheet(btnStyle)
-
- page.lineEdit_title.setText(self.title)
- page.comboBox_textAlign.setCurrentIndex(int(self.alignment))
- page.spinBox_fontSize.setValue(int(self.fontSize))
- page.spinBox_xTextAlign.setValue(int(self.xPosition))
- page.spinBox_yTextAlign.setValue(int(self.yPosition))
-
- page.fontComboBox_titleFont.currentFontChanged.connect(self.update)
- page.lineEdit_title.textChanged.connect(self.update)
- page.comboBox_textAlign.currentIndexChanged.connect(self.update)
- page.spinBox_xTextAlign.valueChanged.connect(self.update)
- page.spinBox_yTextAlign.valueChanged.connect(self.update)
- page.spinBox_fontSize.valueChanged.connect(self.update)
- page.lineEdit_textColor.textChanged.connect(self.update)
- self.page = page
- return page
-
- def update(self):
- self.title = self.page.lineEdit_title.text()
- self.alignment = self.page.comboBox_textAlign.currentIndex()
- self.titleFont = self.page.fontComboBox_titleFont.currentFont()
- self.fontSize = self.page.spinBox_fontSize.value()
- self.xPosition = self.page.spinBox_xTextAlign.value()
- self.yPosition = self.page.spinBox_yTextAlign.value()
- self.textColor = self.RGBFromString(
- self.page.lineEdit_textColor.text())
- self.parent.drawPreview()
- super().update()
-
- def getXY(self):
- '''Returns true x, y after considering alignment settings'''
- fm = QtGui.QFontMetrics(self.titleFont)
- if self.alignment == 0: # Left
- x = self.xPosition
-
- if self.alignment == 1: # Middle
- offset = fm.width(self.title)/2
- x = self.xPosition - offset
-
- if self.alignment == 2: # Right
- offset = fm.width(self.title)
- x = self.xPosition - offset
- return x, self.yPosition
-
- def loadPreset(self, pr, presetName=None):
- super().loadPreset(pr, presetName)
-
- self.page.lineEdit_title.setText(pr['title'])
- font = QFont()
- font.fromString(pr['titleFont'])
- self.page.fontComboBox_titleFont.setCurrentFont(font)
- self.page.spinBox_fontSize.setValue(pr['fontSize'])
- self.page.comboBox_textAlign.setCurrentIndex(pr['alignment'])
- self.page.spinBox_xTextAlign.setValue(pr['xPosition'])
- self.page.spinBox_yTextAlign.setValue(pr['yPosition'])
- self.page.lineEdit_textColor.setText('%s,%s,%s' % pr['textColor'])
- btnStyle = "QPushButton { background-color : %s; outline: none; }" \
- % QColor(*pr['textColor']).name()
- self.page.pushButton_textColor.setStyleSheet(btnStyle)
-
- def savePreset(self):
- return {
- 'preset': self.currentPreset,
- 'title': self.title,
- 'titleFont': self.titleFont.toString(),
- 'alignment': self.alignment,
- 'fontSize': self.fontSize,
- 'xPosition': self.xPosition,
- 'yPosition': self.yPosition,
- 'textColor': self.textColor
- }
-
- def previewRender(self, previewWorker):
- width = int(previewWorker.core.settings.value('outputWidth'))
- height = int(previewWorker.core.settings.value('outputHeight'))
- return self.addText(width, height)
-
- def preFrameRender(self, **kwargs):
- super().preFrameRender(**kwargs)
- return ['static']
-
- def frameRender(self, moduleNo, arrayNo, frameNo):
- width = int(self.worker.core.settings.value('outputWidth'))
- height = int(self.worker.core.settings.value('outputHeight'))
- return self.addText(width, height)
-
- def addText(self, width, height):
- x, y = self.getXY()
- im = self.blankFrame(width, height)
- image = ImageQt(im)
-
- painter = QPainter(image)
- self.titleFont.setPixelSize(self.fontSize)
- painter.setFont(self.titleFont)
- painter.setPen(QColor(*self.textColor))
- painter.drawText(x, y, self.title)
- painter.end()
-
- imBytes = image.bits().asstring(image.numBytes())
-
- return Image.frombytes('RGBA', (width, height), imBytes)
-
- def pickColor(self):
- RGBstring, btnStyle = super().pickColor()
- if not RGBstring:
- return
- self.page.lineEdit_textColor.setText(RGBstring)
- self.page.pushButton_textColor.setStyleSheet(btnStyle)
-
- def commandHelp(self):
- print('Enter a string to use as centred white text:')
- print(' "title=User Error"')
- print('Specify a text color:\n color=255,255,255')
- print('Set custom x, y position:\n x=500 y=500')
-
- def command(self, arg):
- if not arg.startswith('preset=') and '=' in arg:
- key, arg = arg.split('=', 1)
- if key == 'color':
- self.page.lineEdit_textColor.setText(arg)
- return
- elif key == 'size':
- self.page.spinBox_fontSize.setValue(int(arg))
- return
- elif key == 'x':
- self.page.spinBox_xTextAlign.setValue(int(arg))
- return
- elif key == 'y':
- self.page.spinBox_yTextAlign.setValue(int(arg))
- return
- elif key == 'title':
- self.page.lineEdit_title.setText(arg)
- return
- super().command(arg)
diff --git a/components/text.ui b/components/text.ui
deleted file mode 100644
index 05e7f8e..0000000
--- a/components/text.ui
+++ /dev/null
@@ -1,316 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 586
- 197
-
-
-
- Form
-
-
- -
-
-
- 4
-
-
-
-
-
-
-
-
-
- 0
- 0
-
-
-
- Font
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 0
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Font Size
-
-
-
- -
-
-
- 500
-
-
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
- Text Layout
-
-
-
- -
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
- Text Color
-
-
-
- -
-
-
-
- 32
- 32
-
-
-
-
-
-
-
- 32
- 32
-
-
-
-
- -
-
-
-
-
- -
-
-
- 0
-
-
-
-
-
- Title
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 0
-
-
-
- Testing New GUI
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- X
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
-
- 0
- 0
-
-
-
- 0
-
-
- 999999999
-
-
- 0
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Y
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
- 999999999
-
-
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
-
-
diff --git a/components/video.py b/components/video.py
deleted file mode 100644
index 1d250bd..0000000
--- a/components/video.py
+++ /dev/null
@@ -1,273 +0,0 @@
-from PIL import Image, ImageDraw
-from PyQt4 import uic, QtGui, QtCore
-import os
-import subprocess
-import threading
-from queue import PriorityQueue
-from . import __base__
-
-
-class Video:
- '''Video Component Frame-Fetcher'''
- def __init__(self, **kwargs):
- mandatoryArgs = [
- 'ffmpeg', # path to ffmpeg, usually core.FFMPEG_BIN
- 'videoPath',
- 'width',
- 'height',
- 'scale', # percentage scale
- 'frameRate', # frames per second
- 'chunkSize', # number of bytes in one frame
- 'parent', # mainwindow object
- 'component', # component object
- ]
- for arg in mandatoryArgs:
- try:
- exec('self.%s = kwargs[arg]' % arg)
- except KeyError:
- raise __base__.BadComponentInit(arg, self.__doc__)
-
- self.frameNo = -1
- self.currentFrame = 'None'
- if 'loopVideo' in kwargs and kwargs['loopVideo']:
- self.loopValue = '-1'
- else:
- self.loopValue = '0'
- self.command = [
- self.ffmpeg,
- '-thread_queue_size', '512',
- '-r', str(self.frameRate),
- '-stream_loop', self.loopValue,
- '-i', self.videoPath,
- '-f', 'image2pipe',
- '-pix_fmt', 'rgba',
- '-filter:v', 'scale=%s:%s' %
- scale(self.scale, self.width, self.height, str),
- '-vcodec', 'rawvideo', '-',
- ]
-
- self.frameBuffer = PriorityQueue()
- self.frameBuffer.maxsize = self.frameRate
- self.finishedFrames = {}
-
- self.thread = threading.Thread(
- target=self.fillBuffer,
- name=self.__doc__
- )
- self.thread.daemon = True
- self.thread.start()
-
- def frame(self, num):
- while True:
- if num in self.finishedFrames:
- image = self.finishedFrames.pop(num)
- return finalizeFrame(
- self.component, image, self.width, self.height)
-
- i, image = self.frameBuffer.get()
- self.finishedFrames[i] = image
- self.frameBuffer.task_done()
-
- def fillBuffer(self):
- pipe = subprocess.Popen(
- self.command, stdout=subprocess.PIPE,
- stderr=subprocess.DEVNULL, bufsize=10**8
- )
- while True:
- if self.parent.canceled:
- break
- self.frameNo += 1
-
- # If we run out of frames, use the last good frame and loop.
- if len(self.currentFrame) == 0:
- self.frameBuffer.put((self.frameNo-1, self.lastFrame))
- continue
-
- self.currentFrame = pipe.stdout.read(self.chunkSize)
- if len(self.currentFrame) != 0:
- self.frameBuffer.put((self.frameNo, self.currentFrame))
- self.lastFrame = self.currentFrame
-
-
-class Component(__base__.Component):
- '''Video'''
-
- modified = QtCore.pyqtSignal(int, dict)
-
- def widget(self, parent):
- self.parent = parent
- self.settings = parent.settings
- page = uic.loadUi(os.path.join(
- os.path.dirname(os.path.realpath(__file__)),
- 'video.ui'
- ))
- self.videoPath = ''
- self.x = 0
- self.y = 0
- self.loopVideo = False
-
- page.lineEdit_video.textChanged.connect(self.update)
- page.pushButton_video.clicked.connect(self.pickVideo)
- page.checkBox_loop.stateChanged.connect(self.update)
- page.checkBox_distort.stateChanged.connect(self.update)
- page.spinBox_scale.valueChanged.connect(self.update)
- page.spinBox_x.valueChanged.connect(self.update)
- page.spinBox_y.valueChanged.connect(self.update)
-
- self.page = page
- return page
-
- def update(self):
- self.videoPath = self.page.lineEdit_video.text()
- self.loopVideo = self.page.checkBox_loop.isChecked()
- self.distort = self.page.checkBox_distort.isChecked()
- self.scale = self.page.spinBox_scale.value()
- self.xPosition = self.page.spinBox_x.value()
- self.yPosition = self.page.spinBox_y.value()
- self.parent.drawPreview()
- super().update()
-
- def previewRender(self, previewWorker):
- self.videoFormats = previewWorker.core.videoFormats
- width = int(previewWorker.core.settings.value('outputWidth'))
- height = int(previewWorker.core.settings.value('outputHeight'))
- self.updateChunksize(width, height)
- frame = self.getPreviewFrame(width, height)
- if not frame:
- return self.blankFrame(width, height)
- else:
- return frame
-
- def preFrameRender(self, **kwargs):
- super().preFrameRender(**kwargs)
- width = int(self.worker.core.settings.value('outputWidth'))
- height = int(self.worker.core.settings.value('outputHeight'))
- self.blankFrame_ = self.blankFrame(width, height)
- self.updateChunksize(width, height)
- self.video = Video(
- ffmpeg=self.parent.core.FFMPEG_BIN, videoPath=self.videoPath,
- width=width, height=height, chunkSize=self.chunkSize,
- frameRate=int(self.settings.value("outputFrameRate")),
- parent=self.parent, loopVideo=self.loopVideo,
- component=self, scale=self.scale
- ) if os.path.exists(self.videoPath) else None
-
- def frameRender(self, moduleNo, arrayNo, frameNo):
- if self.video:
- return self.video.frame(frameNo)
- else:
- return self.blankFrame_
-
- def loadPreset(self, pr, presetName=None):
- super().loadPreset(pr, presetName)
- self.page.lineEdit_video.setText(pr['video'])
- self.page.checkBox_loop.setChecked(pr['loop'])
- self.page.checkBox_distort.setChecked(pr['distort'])
- self.page.spinBox_scale.setValue(pr['scale'])
- self.page.spinBox_x.setValue(pr['x'])
- self.page.spinBox_y.setValue(pr['y'])
-
- def savePreset(self):
- return {
- 'preset': self.currentPreset,
- 'video': self.videoPath,
- 'loop': self.loopVideo,
- 'distort': self.distort,
- 'scale': self.scale,
- 'x': self.xPosition,
- 'y': self.yPosition,
- }
-
- def pickVideo(self):
- imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
- filename = QtGui.QFileDialog.getOpenFileName(
- self.page, "Choose Video",
- imgDir, "Video Files (%s)" % " ".join(self.videoFormats)
- )
- if filename:
- self.settings.setValue("backgroundDir", os.path.dirname(filename))
- self.page.lineEdit_video.setText(filename)
- self.update()
-
- def getPreviewFrame(self, width, height):
- if not self.videoPath or not os.path.exists(self.videoPath):
- return
-
- command = [
- self.parent.core.FFMPEG_BIN,
- '-thread_queue_size', '512',
- '-i', self.videoPath,
- '-f', 'image2pipe',
- '-pix_fmt', 'rgba',
- '-filter:v', 'scale=%s:%s' %
- scale(self.scale, width, height, str),
- '-vcodec', 'rawvideo', '-',
- '-ss', '90',
- '-vframes', '1',
- ]
- pipe = subprocess.Popen(
- command, stdout=subprocess.PIPE,
- stderr=subprocess.DEVNULL, bufsize=10**8
- )
- byteFrame = pipe.stdout.read(self.chunkSize)
- frame = finalizeFrame(self, byteFrame, width, height)
- pipe.stdout.close()
- pipe.kill()
-
- return frame
-
- def updateChunksize(self, width, height):
- if self.scale != 100 and not self.distort:
- width, height = scale(self.scale, width, height, int)
- self.chunkSize = 4*width*height
-
- def command(self, arg):
- if not arg.startswith('preset=') and '=' in arg:
- key, arg = arg.split('=', 1)
- if key == 'path' and os.path.exists(arg):
- if os.path.splitext(arg)[1] in self.core.videoFormats:
- self.page.lineEdit_video.setText(arg)
- self.page.spinBox_scale.setValue(100)
- self.page.checkBox_loop.setChecked(True)
- return
- else:
- print("Not a supported video format")
- quit(1)
- super().command(arg)
-
- def commandHelp(self):
- print('Load a video:\n path=/filepath/to/video.mp4')
-
-def scale(scale, width, height, returntype=None):
- width = (float(width) / 100.0) * float(scale)
- height = (float(height) / 100.0) * float(scale)
- if returntype == str:
- return (str(int(width)), str(int(height)))
- elif returntype == int:
- return (int(width), int(height))
- else:
- return (width, height)
-
-def finalizeFrame(self, imageData, width, height):
- if self.distort:
- try:
- image = Image.frombytes(
- 'RGBA',
- (width, height),
- imageData)
- except ValueError:
- print('#### ignored invalid data caused by distortion ####')
- image = self.blankFrame(width, height)
- else:
- image = Image.frombytes(
- 'RGBA',
- scale(self.scale, width, height, int),
- imageData)
-
- if self.scale != 100 \
- or self.xPosition != 0 or self.yPosition != 0:
- frame = self.blankFrame(width, height)
- frame.paste(image, box=(self.xPosition, self.yPosition))
- else:
- frame = image
- return frame
diff --git a/components/video.ui b/components/video.ui
deleted file mode 100644
index f05e8a5..0000000
--- a/components/video.ui
+++ /dev/null
@@ -1,266 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 586
- 197
-
-
-
- Form
-
-
- -
-
-
- 4
-
-
-
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 31
- 0
-
-
-
- Video
-
-
-
- -
-
-
-
- 1
- 0
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 1
- 0
-
-
-
-
- 32
- 32
-
-
-
- ...
-
-
-
- 32
- 32
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- X
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
- -10000
-
-
- 10000
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Y
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 80
- 16777215
-
-
-
-
- 0
- 0
-
-
-
- -10000
-
-
- 10000
-
-
- 0
-
-
-
-
-
-
-
- -
-
-
-
-
-
- Loop
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 20
-
-
-
-
- -
-
-
- Distort by scale
-
-
-
- -
-
-
- Scale
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- QAbstractSpinBox::UpDownArrows
-
-
- %
-
-
- 10
-
-
- 400
-
-
- 100
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
- -
-
-
-
-
-
-
-
diff --git a/core.py b/core.py
deleted file mode 100644
index de6ed99..0000000
--- a/core.py
+++ /dev/null
@@ -1,476 +0,0 @@
-import sys
-import io
-import os
-from PyQt4 import QtCore, QtGui, uic
-from os.path import expanduser
-import subprocess as sp
-import numpy
-from PIL import Image
-from shutil import rmtree
-import time
-from collections import OrderedDict
-import json
-from importlib import import_module
-from PyQt4.QtGui import QDesktopServices
-import string
-
-
-class Core():
-
- def __init__(self):
- self.FFMPEG_BIN = self.findFfmpeg()
- self.dataDir = QDesktopServices.storageLocation(
- QDesktopServices.DataLocation)
- self.presetDir = os.path.join(self.dataDir, 'presets')
- if getattr(sys, 'frozen', False):
- # frozen
- self.wd = os.path.dirname(sys.executable)
- else:
- # unfrozen
- self.wd = os.path.dirname(os.path.realpath(__file__))
-
- self.loadEncoderOptions()
- self.videoFormats = Core.appendUppercase([
- '*.mp4',
- '*.mov',
- '*.mkv',
- '*.avi',
- '*.webm',
- '*.flv',
- ])
- self.audioFormats = Core.appendUppercase([
- '*.mp3',
- '*.wav',
- '*.ogg',
- '*.fla',
- '*.flac',
- '*.aac',
- ])
- self.imageFormats = Core.appendUppercase([
- '*.png',
- '*.jpg',
- '*.tif',
- '*.tiff',
- '*.gif',
- '*.bmp',
- '*.ico',
- '*.xbm',
- '*.xpm',
- ])
-
- self.findComponents()
- self.selectedComponents = []
- # copies of named presets to detect modification
- self.savedPresets = {}
-
- def findComponents(self):
- def findComponents():
- srcPath = os.path.join(self.wd, 'components')
- if os.path.exists(srcPath):
- for f in sorted(os.listdir(srcPath)):
- name, ext = os.path.splitext(f)
- if name.startswith("__"):
- continue
- elif ext == '.py':
- yield name
- self.modules = [
- import_module('components.%s' % name)
- for name in findComponents()
- ]
- self.moduleIndexes = [i for i in range(len(self.modules))]
- self.compNames = [mod.Component.__doc__ for mod in self.modules]
-
- def componentListChanged(self):
- for i, component in enumerate(self.selectedComponents):
- component.compPos = i
-
- def insertComponent(self, compPos, moduleIndex, loader):
- '''Creates a new component'''
- if compPos < 0 or compPos > len(self.selectedComponents):
- compPos = len(self.selectedComponents)
- if len(self.selectedComponents) > 50:
- return None
-
- component = self.modules[moduleIndex].Component(
- moduleIndex, compPos, self)
- self.selectedComponents.insert(
- compPos,
- component)
- self.componentListChanged()
-
- # init component's widget for loading/saving presets
- self.selectedComponents[compPos].widget(loader)
- self.updateComponent(compPos)
-
- if hasattr(loader, 'insertComponent'):
- loader.insertComponent(compPos)
- return compPos
-
- def moveComponent(self, startI, endI):
- comp = self.selectedComponents.pop(startI)
- self.selectedComponents.insert(endI, comp)
-
- self.componentListChanged()
- return endI
-
- def removeComponent(self, i):
- self.selectedComponents.pop(i)
- self.componentListChanged()
-
- def clearComponents(self):
- self.selectedComponents = list()
- self.componentListChanged()
-
- def updateComponent(self, i):
- # print('updating %s' % self.selectedComponents[i])
- self.selectedComponents[i].update()
-
- def moduleIndexFor(self, compName):
- index = self.compNames.index(compName)
- return self.moduleIndexes[index]
-
- def clearPreset(self, compIndex):
- self.selectedComponents[compIndex].currentPreset = None
-
- def openPreset(self, filepath, compIndex, presetName):
- '''Applies a preset to a specific component'''
- saveValueStore = self.getPreset(filepath)
- if not saveValueStore:
- return False
- try:
- self.selectedComponents[compIndex].loadPreset(
- saveValueStore,
- presetName
- )
- except KeyError as e:
- print('preset missing value: %s' % e)
-
- self.savedPresets[presetName] = dict(saveValueStore)
- return True
-
- def getPresetDir(self, comp):
- return os.path.join(
- self.presetDir, str(comp), str(comp.version()))
-
- def getPreset(self, filepath):
- '''Returns the preset dict stored at this filepath'''
- if not os.path.exists(filepath):
- return False
- with open(filepath, 'r') as f:
- for line in f:
- saveValueStore = Core.presetFromString(line.strip())
- break
- return saveValueStore
-
- def openProject(self, loader, filepath):
- ''' loader is the object calling this method which must have
- its own showMessage(**kwargs) method for displaying errors.
- '''
- if not os.path.exists(filepath):
- loader.showMessage(msg='Project file not found')
- return
-
- errcode, data = self.parseAvFile(filepath)
- if errcode == 0:
- try:
- for i, tup in enumerate(data['Components']):
- name, vers, preset = tup
- clearThis = False
-
- # add loaded named presets to savedPresets dict
- if 'preset' in preset and preset['preset'] != None:
- nam = preset['preset']
- filepath2 = os.path.join(
- self.presetDir, name, str(vers), nam)
- origSaveValueStore = self.getPreset(filepath2)
- if origSaveValueStore:
- self.savedPresets[nam] = dict(origSaveValueStore)
- else:
- # saved preset was renamed or deleted
- clearThis = True
-
- # create the actual component object & get its index
- i = self.insertComponent(
- -1,
- self.moduleIndexFor(name),
- loader)
- if i == None:
- loader.showMessage(msg="Too many components!")
- break
-
- try:
- if 'preset' in preset and preset['preset'] != None:
- self.selectedComponents[i].loadPreset(
- preset
- )
- else:
- self.selectedComponents[i].loadPreset(
- preset,
- preset['preset']
- )
- except KeyError as e:
- print('%s missing value %s' %
- (self.selectedComponents[i], e))
-
- if clearThis:
- self.clearPreset(i)
- if hasattr(loader, 'updateComponentTitle'):
- loader.updateComponentTitle(i)
- except:
- errcode = 1
- data = sys.exc_info()
-
-
- if errcode == 1:
- typ, value, _ = data
- if typ.__name__ == KeyError:
- # probably just an old version, still loadable
- print('file missing value: %s' % value)
- return
- if hasattr(loader, 'createNewProject'):
- loader.createNewProject()
- msg = '%s: %s' % (typ.__name__, value)
- loader.showMessage(
- msg="Project file '%s' is corrupted." % filepath,
- showCancel=False,
- icon=QtGui.QMessageBox.Warning,
- detail=msg)
-
- def parseAvFile(self, filepath):
- '''Parses an avp (project) or avl (preset package) file.
- Returns dictionary with section names as the keys, each one
- contains a list of tuples: (compName, version, compPresetDict)
- '''
- data = {}
- try:
- with open(filepath, 'r') as f:
- def parseLine(line):
- '''Decides if a file line is a section header'''
- validSections = ('Components')
- line = line.strip()
- newSection = ''
-
- if line.startswith('[') and line.endswith(']') \
- and line[1:-1] in validSections:
- newSection = line[1:-1]
-
- return line, newSection
-
- section = ''
- i = 0
- for line in f:
- line, newSection = parseLine(line)
- if newSection:
- section = str(newSection)
- data[section] = []
- continue
- if line and section == 'Components':
- if i == 0:
- lastCompName = str(line)
- i += 1
- elif i == 1:
- lastCompVers = str(line)
- i += 1
- elif i == 2:
- lastCompPreset = Core.presetFromString(line)
- data[section].append(
- (lastCompName,
- lastCompVers,
- lastCompPreset)
- )
- i = 0
- return 0, data
- except:
- return 1, sys.exc_info()
-
- def importPreset(self, filepath):
- errcode, data = self.parseAvFile(filepath)
- returnList = []
- if errcode == 0:
- name, vers, preset = data['Components'][0]
- presetName = preset['preset'] \
- if preset['preset'] else os.path.basename(filepath)[:-4]
- newPath = os.path.join(
- self.presetDir,
- name,
- vers,
- presetName
- )
- if os.path.exists(newPath):
- return False, newPath
- preset['preset'] = presetName
- self.createPresetFile(
- name, vers, presetName, preset
- )
- return True, presetName
- elif errcode == 1:
- # TODO: an error message
- return False, ''
-
- def exportPreset(self, exportPath, compName, vers, origName):
- internalPath = os.path.join(self.presetDir, compName, str(vers), origName)
- if not os.path.exists(internalPath):
- return
- if os.path.exists(exportPath):
- os.remove(exportPath)
- with open(internalPath, 'r') as f:
- internalData = [line for line in f]
- try:
- saveValueStore = Core.presetFromString(internalData[0].strip())
- self.createPresetFile(
- compName, vers,
- origName, saveValueStore,
- exportPath
- )
- return True
- except:
- return False
-
- def createPresetFile(
- self, compName, vers, presetName, saveValueStore, filepath=''):
- '''Create a preset file (.avl) at filepath using args.
- Or if filepath is empty, create an internal preset using args'''
- if not filepath:
- dirname = os.path.join(self.presetDir, compName, str(vers))
- if not os.path.exists(dirname):
- os.makedirs(dirname)
- filepath = os.path.join(dirname, presetName)
- internal = True
- else:
- if not filepath.endswith('.avl'):
- filepath += '.avl'
- internal = False
-
- with open(filepath, 'w') as f:
- if not internal:
- f.write('[Components]\n')
- f.write('%s\n' % compName)
- f.write('%s\n' % str(vers))
- f.write(Core.presetToString(saveValueStore))
-
- def createProjectFile(self, filepath):
- '''Create a project file (.avp) using the current program state'''
- try:
- if not filepath.endswith(".avp"):
- filepath += '.avp'
- if os.path.exists(filepath):
- os.remove(filepath)
- with open(filepath, 'w') as f:
- print('creating %s' % filepath)
- f.write('[Components]\n')
- for comp in self.selectedComponents:
- saveValueStore = comp.savePreset()
- f.write('%s\n' % str(comp))
- f.write('%s\n' % str(comp.version()))
- f.write('%s\n' % Core.presetToString(saveValueStore))
- return True
- except:
- return False
-
- def loadEncoderOptions(self):
- file_path = os.path.join(self.wd, 'encoder-options.json')
- with open(file_path) as json_file:
- self.encoder_options = json.load(json_file)
-
- def findFfmpeg(self):
- if sys.platform == "win32":
- return "ffmpeg.exe"
- else:
- try:
- with open(os.devnull, "w") as f:
- sp.check_call(['ffmpeg', '-version'], stdout=f, stderr=f)
- return "ffmpeg"
- except:
- return "avconv"
-
- def readAudioFile(self, filename, parent):
- command = [self.FFMPEG_BIN, '-i', filename]
-
- try:
- fileInfo = sp.check_output(command, stderr=sp.STDOUT, shell=False)
- except sp.CalledProcessError as ex:
- fileInfo = ex.output
- pass
-
- info = fileInfo.decode("utf-8").split('\n')
- for line in info:
- if 'Duration' in line:
- d = line.split(',')[0]
- d = d.split(' ')[3]
- d = d.split(':')
- duration = float(d[0])*3600 + float(d[1])*60 + float(d[2])
-
- command = [
- self.FFMPEG_BIN,
- '-i', filename,
- '-f', 's16le',
- '-acodec', 'pcm_s16le',
- '-ar', '44100', # ouput will have 44100 Hz
- '-ac', '1', # mono (set to '2' for stereo)
- '-']
- in_pipe = sp.Popen(
- command, stdout=sp.PIPE, stderr=sp.DEVNULL, bufsize=10**8)
-
- completeAudioArray = numpy.empty(0, dtype="int16")
-
- progress = 0
- lastPercent = None
- while True:
- if self.canceled:
- break
- # read 2 seconds of audio
- progress = progress + 4
- raw_audio = in_pipe.stdout.read(88200*4)
- if len(raw_audio) == 0:
- break
- audio_array = numpy.fromstring(raw_audio, dtype="int16")
- completeAudioArray = numpy.append(completeAudioArray, audio_array)
-
- percent = int(100*(progress/duration))
- if percent >= 100:
- percent = 100
-
- if lastPercent != percent:
- string = 'Loading audio file: '+str(percent)+'%'
- parent.progressBarSetText.emit(string)
- parent.progressBarUpdate.emit(percent)
-
- lastPercent = percent
-
- in_pipe.kill()
- in_pipe.wait()
-
- # add 0s the end
- completeAudioArrayCopy = numpy.zeros(
- len(completeAudioArray) + 44100, dtype="int16")
- completeAudioArrayCopy[:len(completeAudioArray)] = completeAudioArray
- completeAudioArray = completeAudioArrayCopy
-
- return completeAudioArray
-
- def cancel(self):
- self.canceled = True
-
- def reset(self):
- self.canceled = False
-
- @staticmethod
- def badName(name):
- '''Returns whether a name contains non-alphanumeric chars'''
- return any([letter in string.punctuation for letter in name])
-
- @staticmethod
- def presetToString(dictionary):
- '''Alphabetizes a dict into OrderedDict & returns string repr'''
- return repr(OrderedDict(sorted(dictionary.items(), key=lambda t: t[0])))
-
- @staticmethod
- def presetFromString(string):
- '''Turns a string repr of OrderedDict into a regular dict'''
- return dict(eval(string))
-
- @staticmethod
- def appendUppercase(lst):
- for form, i in zip(lst, range(len(lst))):
- lst.append(form.upper())
- return lst
diff --git a/encoder-options.json b/encoder-options.json
deleted file mode 100644
index 78bc940..0000000
--- a/encoder-options.json
+++ /dev/null
@@ -1,130 +0,0 @@
-{
- "containers":[
- {
- "name": "MP4",
- "container": "mp4",
- "default-vcodec": "H264",
- "default-acodec": "AAC",
- "video-codecs": [
- "H264",
- "H264 (nvenc)",
- "MPEG4"
- ],
- "audio-codecs": [
- "AAC",
- "AC3",
- "MP3"
- ]
- },
- {
- "name": "MOV",
- "container": "mov",
- "default-vcodec": "H264",
- "default-acodec": "AAC",
- "video-codecs": [
- "H264",
- "H264 (nvenc)",
- "MPEG4",
- "XVID"
- ],
- "audio-codecs": [
- "AAC",
- "AC3",
- "MP3",
- "PCM s16 LE"
- ]
- },
- {
- "name": "MKV",
- "container": "matroska",
- "default-vcodec": "H264",
- "default-acodec": "AAC",
- "video-codecs": [
- "H264",
- "H264 (nvenc)",
- "MPEG4",
- "MPEG2",
- "DV",
- "WMV"
- ],
- "audio-codecs": [
- "AAC",
- "AC3",
- "MP3",
- "PCM s16 LE",
- "WMA"
- ]
- },
- {
- "name": "AVI",
- "container": "avi",
- "default-vcodec": "H264",
- "default-acodec": "AAC",
- "video-codecs": [
- "H264",
- "H264 (nvenc)",
- "MPEG4",
- "MPEG2",
- "DV",
- "WMV"
- ],
- "audio-codecs": [
- "AAC",
- "AC3",
- "MP3",
- "PCM s16 LE",
- "WMA"
- ]
- },
- {
- "name": "WEBM",
- "container": "webm",
- "default-vcodec": "VP9",
- "default-acodec": "Vorbis",
- "video-codecs": [
- "VP9",
- "VP8"
- ],
- "audio-codecs": [
- "Vorbis"
- ]
- },
- {
- "name": "FLV",
- "container": "flv",
- "default-vcodec": "FLV",
- "default-acodec": "Vorbis",
- "video-codecs": [
- "Sorenson (flv)",
- "H264",
- "H264 (nvenc)",
- "MPEG4"
- ],
- "audio-codecs": [
- "MP3",
- "PCM s16 LE",
- "Vorbis"
- ]
- }
- ],
- "video-codecs":{
- "H264": ["libx264"],
- "H264 (nvenc)": ["h264_nvenc", "nvenc_h264"],
- "MPEG4": ["mpeg4"],
- "VP9": ["libvpx-vp9"],
- "VP8": ["libvpx"],
- "XVID": ["libxvid"],
- "Sorenson (flv)": ["flv"],
- "MPEG2": ["mp2video"],
- "DV": ["dvvideo"],
- "WMV": ["wmv2"]
- },
- "audio-codecs": {
- "AAC": ["libfdk_aac", "aac"],
- "AC3": ["ac3"],
- "MP3": ["libmp3lame"],
- "PCM s16 LE": ["pcm_s16le"],
- "WMA": ["wmav2"],
- "Vorbis": ["libvorbis"]
- }
-}
\ No newline at end of file
diff --git a/freeze.py b/freeze.py
new file mode 100644
index 0000000..48034dc
--- /dev/null
+++ b/freeze.py
@@ -0,0 +1,51 @@
+from cx_Freeze import setup, Executable
+import sys
+
+# Dependencies are automatically detected, but it might need
+# fine tuning.
+
+buildOptions = dict(
+ packages=[],
+ excludes=[
+ "apport",
+ "apt",
+ "curses",
+ "distutils",
+ "email",
+ "html",
+ "http",
+ "xmlrpc",
+ "nose"
+ ],
+ include_files=[
+ "mainwindow.ui",
+ "presetmanager.ui",
+ "background.png",
+ "encoder-options.json",
+ "components/"
+ ],
+ includes=[
+ 'numpy.core._methods',
+ 'numpy.lib.format'
+ ]
+)
+
+
+base = 'Win32GUI' if sys.platform == 'win32' else None
+
+executables = [
+ Executable(
+ 'main.py',
+ base=base,
+ targetName='audio-visualizer-python'
+ )
+]
+
+
+setup(
+ name='audio-visualizer-python',
+ version='1.0',
+ description='GUI tool to render visualization videos of audio files',
+ options=dict(build_exe=buildOptions),
+ executables=executables
+)
diff --git a/main.py b/main.py
deleted file mode 100644
index 106bd29..0000000
--- a/main.py
+++ /dev/null
@@ -1,88 +0,0 @@
-from PyQt4 import QtGui, uic
-import sys
-import os
-
-import core
-import preview_thread
-import video_thread
-
-
-def LoadDefaultSettings(self):
- self.resolutions = [
- '1920x1080',
- '1280x720',
- '854x480'
- ]
-
- default = {
- "outputWidth": 1280,
- "outputHeight": 720,
- "outputFrameRate": 30,
- "outputAudioCodec": "AAC",
- "outputAudioBitrate": "192",
- "outputVideoCodec": "H264",
- "outputVideoBitrate": "2500",
- "outputVideoFormat": "yuv420p",
- "outputPreset": "medium",
- "outputFormat": "mp4",
- "outputContainer": "MP4",
- "projectDir": os.path.join(self.dataDir, 'projects'),
- }
-
- for parm, value in default.items():
- #print(parm, self.settings.value(parm))
- if self.settings.value(parm) is None:
- self.settings.setValue(parm, value)
-
-if __name__ == "__main__":
- mode = 'gui'
- if len(sys.argv) > 2:
- mode = 'cmd'
-
- elif len(sys.argv) == 2:
- if sys.argv[1].startswith('-'):
- mode = 'cmd'
- else:
- # opening a project file with gui
- proj = sys.argv[1]
- else:
- # normal gui launch
- proj = None
-
- app = QtGui.QApplication(sys.argv)
- app.setApplicationName("audio-visualizer")
- app.setOrganizationName("audio-visualizer")
-
- if mode == 'cmd':
- from command import *
-
- main = Command()
-
- elif mode == 'gui':
- from mainwindow import *
- import atexit
- import signal
-
- if getattr(sys, 'frozen', False):
- # frozen
- wd = os.path.dirname(sys.executable)
- else:
- # unfrozen
- wd = os.path.dirname(os.path.realpath(__file__))
-
- window = uic.loadUi(os.path.join(wd, "mainwindow.ui"))
- # window.adjustSize()
- desc = QtGui.QDesktopWidget()
- dpi = desc.physicalDpiX()
-
- topMargin = 0 if (dpi == 96) else int(10 * (dpi / 96))
- window.resize(window.width() * (dpi / 96), window.height() * (dpi / 96))
- # window.verticalLayout_2.setContentsMargins(0, topMargin, 0, 0)
-
- main = MainWindow(window, proj)
-
- signal.signal(signal.SIGINT, main.cleanUp)
- atexit.register(main.cleanUp)
-
- # applicable to both modes
- sys.exit(app.exec_())
diff --git a/mainwindow.py b/mainwindow.py
deleted file mode 100644
index cdc2a51..0000000
--- a/mainwindow.py
+++ /dev/null
@@ -1,721 +0,0 @@
-from queue import Queue
-from PyQt4 import QtCore, QtGui, uic
-from PyQt4.QtCore import QSettings, Qt
-from PyQt4.QtGui import QMenu, QShortcut
-import sys
-import os
-import signal
-import filecmp
-import time
-
-import core
-import preview_thread
-import video_thread
-from presetmanager import PresetManager
-from main import LoadDefaultSettings
-
-
-class PreviewWindow(QtGui.QLabel):
- def __init__(self, parent, img):
- super(PreviewWindow, self).__init__()
- self.parent = parent
- self.setFrameStyle(QtGui.QFrame.StyledPanel)
- self.pixmap = QtGui.QPixmap(img)
-
- def paintEvent(self, event):
- size = self.size()
- painter = QtGui.QPainter(self)
- point = QtCore.QPoint(0, 0)
- scaledPix = self.pixmap.scaled(
- size, Qt.KeepAspectRatio, transformMode=Qt.SmoothTransformation)
-
- # start painting the label from left upper corner
- point.setX((size.width() - scaledPix.width())/2)
- point.setY((size.height() - scaledPix.height())/2)
- painter.drawPixmap(point, scaledPix)
-
- def changePixmap(self, img):
- self.pixmap = QtGui.QPixmap(img)
- self.repaint()
-
-
-class MainWindow(QtGui.QMainWindow):
-
- newTask = QtCore.pyqtSignal(list)
- processTask = QtCore.pyqtSignal()
- videoTask = QtCore.pyqtSignal(str, str, list)
-
- def __init__(self, window, project):
- QtGui.QMainWindow.__init__(self)
-
- # print('main thread id: {}'.format(QtCore.QThread.currentThreadId()))
- self.window = window
- self.core = core.Core()
-
- self.pages = [] # widgets of component settings
- self.lastAutosave = time.time()
-
- # Create data directory, load/create settings
- self.dataDir = self.core.dataDir
- self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
- self.settings = QSettings(
- os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat)
- LoadDefaultSettings(self)
- self.presetManager = PresetManager(
- uic.loadUi(
- os.path.join(self.core.wd, 'presetmanager.ui')), self)
-
- if not os.path.exists(self.dataDir):
- os.makedirs(self.dataDir)
- for neededDirectory in (
- self.core.presetDir, self.settings.value("projectDir")):
- if not os.path.exists(neededDirectory):
- os.mkdir(neededDirectory)
-
- # Make queues/timers for the preview thread
- self.previewQueue = Queue()
- self.previewThread = QtCore.QThread(self)
- self.previewWorker = preview_thread.Worker(self, self.previewQueue)
- self.previewWorker.moveToThread(self.previewThread)
- self.previewWorker.imageCreated.connect(self.showPreviewImage)
- self.previewThread.start()
-
- self.timer = QtCore.QTimer(self)
- self.timer.timeout.connect(self.processTask.emit)
- self.timer.start(500)
-
- # Begin decorating the window and connecting events
- componentList = self.window.listWidget_componentList
-
- window.toolButton_selectAudioFile.clicked.connect(
- self.openInputFileDialog)
-
- window.toolButton_selectOutputFile.clicked.connect(
- self.openOutputFileDialog)
-
- window.progressBar_createVideo.setValue(0)
-
- window.pushButton_createVideo.clicked.connect(
- self.createAudioVisualisation)
-
- window.pushButton_Cancel.clicked.connect(self.stopVideo)
-
- for i, container in enumerate(self.core.encoder_options['containers']):
- window.comboBox_videoContainer.addItem(container['name'])
- if container['name'] == self.settings.value('outputContainer'):
- selectedContainer = i
-
- window.comboBox_videoContainer.setCurrentIndex(selectedContainer)
- window.comboBox_videoContainer.currentIndexChanged.connect(
- self.updateCodecs
- )
-
- self.updateCodecs()
-
- for i in range(window.comboBox_videoCodec.count()):
- codec = window.comboBox_videoCodec.itemText(i)
- if codec == self.settings.value('outputVideoCodec'):
- window.comboBox_videoCodec.setCurrentIndex(i)
- #print(codec)
-
- for i in range(window.comboBox_audioCodec.count()):
- codec = window.comboBox_audioCodec.itemText(i)
- if codec == self.settings.value('outputAudioCodec'):
- window.comboBox_audioCodec.setCurrentIndex(i)
-
- window.comboBox_videoCodec.currentIndexChanged.connect(
- self.updateCodecSettings
- )
-
- window.comboBox_audioCodec.currentIndexChanged.connect(
- self.updateCodecSettings
- )
-
- vBitrate = int(self.settings.value('outputVideoBitrate'))
- aBitrate = int(self.settings.value('outputAudioBitrate'))
-
- window.spinBox_vBitrate.setValue(vBitrate)
- window.spinBox_aBitrate.setValue(aBitrate)
-
- window.spinBox_vBitrate.valueChanged.connect(self.updateCodecSettings)
- window.spinBox_aBitrate.valueChanged.connect(self.updateCodecSettings)
-
- self.previewWindow = PreviewWindow(self, os.path.join(
- self.core.wd, "background.png"))
- window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
-
- # Make component buttons
- self.compMenu = QMenu()
- for i, comp in enumerate(self.core.modules):
- action = self.compMenu.addAction(comp.Component.__doc__)
- action.triggered[()].connect(
- lambda item=i: self.core.insertComponent(0, item, self))
-
- self.window.pushButton_addComponent.setMenu(self.compMenu)
-
- componentList.dropEvent = self.dragComponent
- componentList.itemSelectionChanged.connect(
- self.changeComponentWidget)
-
- self.window.pushButton_removeComponent.clicked.connect(
- lambda _: self.removeComponent())
-
- componentList.setContextMenuPolicy(
- QtCore.Qt.CustomContextMenu)
- componentList.connect(
- componentList,
- QtCore.SIGNAL("customContextMenuRequested(QPoint)"),
- self.componentContextMenu)
-
- currentRes = str(self.settings.value('outputWidth'))+'x' + \
- str(self.settings.value('outputHeight'))
- for i, res in enumerate(self.resolutions):
- window.comboBox_resolution.addItem(res)
- if res == currentRes:
- currentRes = i
- window.comboBox_resolution.setCurrentIndex(currentRes)
- window.comboBox_resolution.currentIndexChanged.connect(
- self.updateResolution)
-
- self.window.pushButton_listMoveUp.clicked.connect(
- lambda: self.moveComponent(-1)
- )
- self.window.pushButton_listMoveDown.clicked.connect(
- lambda: self.moveComponent(1)
- )
-
- # Configure the Projects Menu
- self.projectMenu = QMenu()
- self.window.menuButton_newProject = self.projectMenu.addAction(
- "New Project")
- self.window.menuButton_newProject.triggered[()].connect(
- self.createNewProject)
-
- self.window.menuButton_openProject = self.projectMenu.addAction(
- "Open Project")
- self.window.menuButton_openProject.triggered[()].connect(
- self.openOpenProjectDialog)
-
- action = self.projectMenu.addAction("Save Project")
- action.triggered[()].connect(self.saveCurrentProject)
-
- action = self.projectMenu.addAction("Save Project As")
- action.triggered[()].connect(self.openSaveProjectDialog)
-
- self.window.pushButton_projects.setMenu(self.projectMenu)
-
- # Configure the Presets Button
- self.window.pushButton_presets.clicked.connect(
- self.openPresetManager
- )
-
- window.show()
-
- if project and project != self.autosavePath:
- if not project.endswith('.avp'):
- project += '.avp'
- # open a project from the commandline
- if not os.path.dirname(project):
- project = os.path.join(os.path.expanduser('~'), project)
- self.currentProject = project
- self.settings.setValue("currentProject", project)
- if os.path.exists(self.autosavePath):
- os.remove(self.autosavePath)
- else:
- # open the last currentProject from settings
- self.currentProject = self.settings.value("currentProject")
-
- # delete autosave if it's identical to this project
- if self.autosaveExists(identical=True):
- os.remove(self.autosavePath)
-
- if self.currentProject and os.path.exists(self.autosavePath):
- ch = self.showMessage(
- msg="Restore unsaved changes in project '%s'?"
- % os.path.basename(self.currentProject)[:-4],
- showCancel=True)
- if ch:
- self.saveProjectChanges()
- else:
- os.remove(self.autosavePath)
-
- self.openProject(self.currentProject, prompt=False)
- self.drawPreview(True)
-
- # Setup Hotkeys
- QtGui.QShortcut("Ctrl+S", self.window, self.saveCurrentProject)
- QtGui.QShortcut("Ctrl+A", self.window, self.openSaveProjectDialog)
- QtGui.QShortcut("Ctrl+O", self.window, self.openOpenProjectDialog)
- QtGui.QShortcut("Ctrl+N", self.window, self.createNewProject)
-
- QtGui.QShortcut("Ctrl+T", self.window, activated=lambda:
- self.window.pushButton_addComponent.click())
- QtGui.QShortcut("Ctrl+Space", self.window, activated=lambda:
- self.window.listWidget_componentList.setFocus())
- QtGui.QShortcut("Ctrl+Shift+S", self.window,
- self.presetManager.openSavePresetDialog)
- QtGui.QShortcut("Ctrl+Shift+C", self.window,
- self.presetManager.clearPreset)
-
- QtGui.QShortcut("Ctrl+Up", self.window,
- activated=lambda: self.moveComponent(-1))
- QtGui.QShortcut("Ctrl+Down", self.window,
- activated=lambda: self.moveComponent(1))
- QtGui.QShortcut("Ctrl+Home", self.window, self.moveComponentTop)
- QtGui.QShortcut("Ctrl+End", self.window, self.moveComponentBottom)
- QtGui.QShortcut("Ctrl+r", self.window, self.removeComponent)
-
- def cleanUp(self):
- self.timer.stop()
- self.previewThread.quit()
- self.previewThread.wait()
- self.autosave()
-
- def updateWindowTitle(self):
- appName = 'Audio Visualizer'
- if self.currentProject:
- appName += ' - %s' % \
- os.path.splitext(
- os.path.basename(self.currentProject))[0]
- self.window.setWindowTitle(appName)
-
- @QtCore.pyqtSlot(int, dict)
- def updateComponentTitle(self, pos, presetStore=False):
- if type(presetStore) == dict:
- name = presetStore['preset']
- if name == None or name not in self.core.savedPresets:
- modified = False
- else:
- modified = (presetStore != self.core.savedPresets[name])
- else:
- modified = bool(presetStore)
- if pos < 0:
- pos = len(self.core.selectedComponents)-1
- title = str(self.core.selectedComponents[pos])
- if self.core.selectedComponents[pos].currentPreset:
- title += ' - %s' % self.core.selectedComponents[pos].currentPreset
- if modified:
- title += '*'
- self.window.listWidget_componentList.item(pos).setText(title)
-
- def updateCodecs(self):
- containerWidget = self.window.comboBox_videoContainer
- vCodecWidget = self.window.comboBox_videoCodec
- aCodecWidget = self.window.comboBox_audioCodec
- index = containerWidget.currentIndex()
- name = containerWidget.itemText(index)
- self.settings.setValue('outputContainer', name)
-
- vCodecWidget.clear()
- aCodecWidget.clear()
-
- for container in self.core.encoder_options['containers']:
- if container['name'] == name:
- for vCodec in container['video-codecs']:
- vCodecWidget.addItem(vCodec)
- for aCodec in container['audio-codecs']:
- aCodecWidget.addItem(aCodec)
-
- def updateCodecSettings(self):
- vCodecWidget = self.window.comboBox_videoCodec
- vBitrateWidget = self.window.spinBox_vBitrate
- aBitrateWidget = self.window.spinBox_aBitrate
- aCodecWidget = self.window.comboBox_audioCodec
- currentVideoCodec = vCodecWidget.currentIndex()
- currentVideoCodec = vCodecWidget.itemText(currentVideoCodec)
- currentVideoBitrate = vBitrateWidget.value()
- currentAudioCodec = aCodecWidget.currentIndex()
- currentAudioCodec = aCodecWidget.itemText(currentAudioCodec)
- currentAudioBitrate = aBitrateWidget.value()
- self.settings.setValue('outputVideoCodec', currentVideoCodec)
- self.settings.setValue('outputAudioCodec', currentAudioCodec)
- self.settings.setValue('outputVideoBitrate', currentVideoBitrate)
- self.settings.setValue('outputAudioBitrate', currentAudioBitrate)
-
- def autosave(self, force=False):
- if not self.currentProject:
- if os.path.exists(self.autosavePath):
- os.remove(self.autosavePath)
- elif force or time.time() - self.lastAutosave >= 2.0:
- self.core.createProjectFile(self.autosavePath)
- self.lastAutosave = time.time()
-
- def autosaveExists(self, identical=True):
- try:
- if self.currentProject and os.path.exists(self.autosavePath) \
- and filecmp.cmp(
- self.autosavePath, self.currentProject) == identical:
- return True
- except FileNotFoundError:
- print('project file couldn\'t be located:', self.currentProject)
- return identical
- return False
-
- def saveProjectChanges(self):
- try:
- os.remove(self.currentProject)
- os.rename(self.autosavePath, self.currentProject)
- return True
- except (FileNotFoundError, IsADirectoryError) as e:
- self.showMessage(
- msg='Project file couldn\'t be saved.',
- detail=str(e))
- return False
-
- def openInputFileDialog(self):
- inputDir = self.settings.value("inputDir", os.path.expanduser("~"))
-
- fileName = QtGui.QFileDialog.getOpenFileName(
- self.window, "Open Audio File",
- inputDir, "Audio Files (%s)" % " ".join(self.core.audioFormats))
-
- if not fileName == "":
- self.settings.setValue("inputDir", os.path.dirname(fileName))
- self.window.lineEdit_audioFile.setText(fileName)
-
- def openOutputFileDialog(self):
- outputDir = self.settings.value("outputDir", os.path.expanduser("~"))
-
- fileName = QtGui.QFileDialog.getSaveFileName(
- self.window, "Set Output Video File",
- outputDir,
- "Video Files (%s);; All Files (*)" % " ".join(self.core.videoFormats))
-
- if not fileName == "":
- self.settings.setValue("outputDir", os.path.dirname(fileName))
- self.window.lineEdit_outputFile.setText(fileName)
-
- def stopVideo(self):
- print('stop')
- self.videoWorker.cancel()
- self.canceled = True
-
- def createAudioVisualisation(self):
- # create output video if mandatory settings are filled in
- if self.window.lineEdit_audioFile.text() and \
- self.window.lineEdit_outputFile.text():
- self.canceled = False
- self.progressBarUpdated(-1)
- self.videoThread = QtCore.QThread(self)
- self.videoWorker = video_thread.Worker(self)
- self.videoWorker.moveToThread(self.videoThread)
- self.videoWorker.videoCreated.connect(self.videoCreated)
- self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated)
- self.videoWorker.progressBarSetText.connect(
- self.progressBarSetText)
- self.videoWorker.imageCreated.connect(self.showPreviewImage)
- self.videoWorker.encoding.connect(self.changeEncodingStatus)
- self.videoThread.start()
- outputPath = self.window.lineEdit_outputFile.text()
- if not os.path.dirname(outputPath):
- outputPath = os.path.join(
- os.path.expanduser("~"), outputPath)
- self.videoTask.emit(
- self.window.lineEdit_audioFile.text(),
- outputPath,
- self.core.selectedComponents)
- else:
- self.showMessage(
- msg="You must select an audio file and output filename.")
-
- def changeEncodingStatus(self, status):
- if status:
- self.window.pushButton_createVideo.setEnabled(False)
- self.window.pushButton_Cancel.setEnabled(True)
- self.window.comboBox_resolution.setEnabled(False)
- self.window.stackedWidget.setEnabled(False)
- self.window.tab_encoderSettings.setEnabled(False)
- self.window.label_audioFile.setEnabled(False)
- self.window.toolButton_selectAudioFile.setEnabled(False)
- self.window.label_outputFile.setEnabled(False)
- self.window.toolButton_selectOutputFile.setEnabled(False)
- self.window.lineEdit_audioFile.setEnabled(False)
- self.window.lineEdit_outputFile.setEnabled(False)
- self.window.pushButton_addComponent.setEnabled(False)
- self.window.pushButton_removeComponent.setEnabled(False)
- self.window.pushButton_listMoveDown.setEnabled(False)
- self.window.pushButton_listMoveUp.setEnabled(False)
- self.window.listWidget_componentList.setEnabled(False)
- self.window.menuButton_newProject.setEnabled(False)
- self.window.menuButton_openProject.setEnabled(False)
- else:
- self.window.pushButton_createVideo.setEnabled(True)
- self.window.pushButton_Cancel.setEnabled(False)
- self.window.comboBox_resolution.setEnabled(True)
- self.window.stackedWidget.setEnabled(True)
- self.window.tab_encoderSettings.setEnabled(True)
- self.window.label_audioFile.setEnabled(True)
- self.window.toolButton_selectAudioFile.setEnabled(True)
- self.window.lineEdit_audioFile.setEnabled(True)
- self.window.label_outputFile.setEnabled(True)
- self.window.toolButton_selectOutputFile.setEnabled(True)
- self.window.lineEdit_outputFile.setEnabled(True)
- self.window.pushButton_addComponent.setEnabled(True)
- self.window.pushButton_removeComponent.setEnabled(True)
- self.window.pushButton_listMoveDown.setEnabled(True)
- self.window.pushButton_listMoveUp.setEnabled(True)
- self.window.listWidget_componentList.setEnabled(True)
- self.window.menuButton_newProject.setEnabled(True)
- self.window.menuButton_openProject.setEnabled(True)
- self.drawPreview(True)
-
- def progressBarUpdated(self, value):
- self.window.progressBar_createVideo.setValue(value)
-
- def progressBarSetText(self, value):
- self.window.progressBar_createVideo.setFormat(value)
-
- def videoCreated(self):
- self.videoThread.quit()
- self.videoThread.wait()
-
- def updateResolution(self):
- resIndex = int(self.window.comboBox_resolution.currentIndex())
- res = self.resolutions[resIndex].split('x')
- self.settings.setValue('outputWidth', res[0])
- self.settings.setValue('outputHeight', res[1])
- self.drawPreview()
-
- def drawPreview(self, force=False):
- self.newTask.emit(self.core.selectedComponents)
- # self.processTask.emit()
- self.autosave(force)
-
- def showPreviewImage(self, image):
- self.previewWindow.changePixmap(image)
-
- def insertComponent(self, index):
- componentList = self.window.listWidget_componentList
- stackedWidget = self.window.stackedWidget
-
- componentList.insertItem(
- index,
- self.core.selectedComponents[index].__doc__)
- componentList.setCurrentRow(index)
-
- # connect to signal that adds an asterisk when modified
- self.core.selectedComponents[index].modified.connect(
- self.updateComponentTitle)
-
- self.pages.insert(index, self.core.selectedComponents[index].page)
- stackedWidget.insertWidget(index, self.pages[index])
- stackedWidget.setCurrentIndex(index)
-
- return index
-
- def removeComponent(self):
- componentList = self.window.listWidget_componentList
-
- for selected in componentList.selectedItems():
- index = componentList.row(selected)
- self.window.stackedWidget.removeWidget(self.pages[index])
- componentList.takeItem(index)
- self.core.removeComponent(index)
- self.pages.pop(index)
- self.changeComponentWidget()
- self.drawPreview()
-
- def moveComponent(self, change):
- '''Moves a component relatively from its current position'''
- componentList = self.window.listWidget_componentList
- stackedWidget = self.window.stackedWidget
-
- row = componentList.currentRow()
- newRow = row + change
- if newRow > -1 and newRow < componentList.count():
- self.core.moveComponent(row, newRow)
-
- # update widgets
- page = self.pages.pop(row)
- self.pages.insert(newRow, page)
- item = componentList.takeItem(row)
- newItem = componentList.insertItem(newRow, item)
- widget = stackedWidget.removeWidget(page)
- stackedWidget.insertWidget(newRow, page)
- componentList.setCurrentRow(newRow)
- stackedWidget.setCurrentIndex(newRow)
- self.drawPreview()
-
- def moveComponentTop(self):
- componentList = self.window.listWidget_componentList
- row = -componentList.currentRow()
- self.moveComponent(row)
-
- def moveComponentBottom(self):
- componentList = self.window.listWidget_componentList
- row = len(componentList)-componentList.currentRow()-1
- self.moveComponent(row)
-
- def dragComponent(self, event):
- '''Drop event for the component listwidget'''
- componentList = self.window.listWidget_componentList
-
- modelIndexes = [ \
- componentList.model().index(i) \
- for i in range(componentList.count()) \
- ]
- rects = [ \
- componentList.visualRect(modelIndex) \
- for modelIndex in modelIndexes \
- ]
-
- rowPos = [rect.contains(event.pos()) for rect in rects]
- if not any(rowPos):
- return
-
- i = rowPos.index(True)
- change = (componentList.currentRow() - i) * -1
- self.moveComponent(change)
-
- def changeComponentWidget(self):
- selected = self.window.listWidget_componentList.selectedItems()
- if selected:
- index = self.window.listWidget_componentList.row(selected[0])
- self.window.stackedWidget.setCurrentIndex(index)
-
- def openPresetManager(self):
- '''Preset manager for importing, exporting, renaming, deleting'''
- self.presetManager.show()
-
- def clear(self):
- '''Get a blank slate'''
- self.core.clearComponents()
- self.window.listWidget_componentList.clear()
- for widget in self.pages:
- self.window.stackedWidget.removeWidget(widget)
- self.pages = []
-
- def createNewProject(self):
- self.openSaveChangesDialog('starting a new project')
-
- self.clear()
- self.currentProject = None
- self.settings.setValue("currentProject", None)
- self.drawPreview(True)
- self.updateWindowTitle()
-
- def saveCurrentProject(self):
- if self.currentProject:
- self.core.createProjectFile(self.currentProject)
- else:
- self.openSaveProjectDialog()
-
- def openSaveChangesDialog(self, phrase):
- success = True
- if self.autosaveExists(identical=False):
- ch = self.showMessage(
- msg="You have unsaved changes in project '%s'. "
- "Save before %s?" % \
- (os.path.basename(self.currentProject)[:-4],
- phrase),
- showCancel=True)
- if ch:
- success = self.saveProjectChanges()
-
- if success and os.path.exists(self.autosavePath):
- os.remove(self.autosavePath)
-
- def openSaveProjectDialog(self):
- filename = QtGui.QFileDialog.getSaveFileName(
- self.window, "Create Project File",
- self.settings.value("projectDir"),
- "Project Files (*.avp)")
- if not filename:
- return
- if not filename.endswith(".avp"):
- filename += '.avp'
- self.settings.setValue("projectDir", os.path.dirname(filename))
- self.settings.setValue("currentProject", filename)
- self.currentProject = filename
- self.updateWindowTitle()
- self.core.createProjectFile(filename)
-
- def openOpenProjectDialog(self):
- filename = QtGui.QFileDialog.getOpenFileName(
- self.window, "Open Project File",
- self.settings.value("projectDir"),
- "Project Files (*.avp)")
- self.openProject(filename)
-
- def openProject(self, filepath, prompt=True):
- if not filepath or not os.path.exists(filepath) \
- or not filepath.endswith('.avp'):
- self.updateWindowTitle()
- return
-
- self.clear()
- # ask to save any changes that are about to get deleted
- if prompt:
- self.openSaveChangesDialog('opening another project')
-
- self.currentProject = filepath
- self.updateWindowTitle()
- self.settings.setValue("currentProject", filepath)
- self.settings.setValue("projectDir", os.path.dirname(filepath))
- # actually load the project using core method
- self.core.openProject(self, filepath)
- if self.window.listWidget_componentList.count() == 0:
- self.drawPreview()
- self.autosave(True)
-
- def showMessage(self, **kwargs):
- parent = kwargs['parent'] if 'parent' in kwargs else self.window
- msg = QtGui.QMessageBox(parent)
- msg.setModal(True)
- msg.setText(kwargs['msg'])
- msg.setIcon(
- kwargs['icon'] if 'icon' in kwargs else QtGui.QMessageBox.Information)
- msg.setDetailedText(kwargs['detail'] if 'detail' in kwargs else None)
- if 'showCancel'in kwargs and kwargs['showCancel']:
- msg.setStandardButtons(
- QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel)
- else:
- msg.setStandardButtons(QtGui.QMessageBox.Ok)
- ch = msg.exec_()
- if ch == 1024:
- return True
- return False
-
- def componentContextMenu(self, QPos):
- '''Appears when right-clicking a component in the list'''
- componentList = self.window.listWidget_componentList
- if not componentList.selectedItems():
- return
-
- # don't show menu if clicking empty space
- parentPosition = componentList.mapToGlobal(QtCore.QPoint(0, 0))
- index = componentList.currentRow()
- modelIndex = componentList.model().index(index)
- if not componentList.visualRect(modelIndex).contains(QPos):
- return
-
- self.presetManager.findPresets()
- self.menu = QtGui.QMenu()
- menuItem = self.menu.addAction("Save Preset")
- menuItem.triggered.connect(
- self.presetManager.openSavePresetDialog
- )
-
- # submenu for opening presets
- try:
- presets = self.presetManager.presets[str(self.core.selectedComponents[index])]
- self.submenu = QtGui.QMenu("Open Preset")
- self.menu.addMenu(self.submenu)
-
- for version, presetName in presets:
- menuItem = self.submenu.addAction(presetName)
- menuItem.triggered.connect(
- lambda _, presetName=presetName:
- self.presetManager.openPreset(presetName)
- )
- except KeyError:
- pass
-
- if self.core.selectedComponents[index].currentPreset:
- menuItem = self.menu.addAction("Clear Preset")
- menuItem.triggered.connect(
- self.presetManager.clearPreset
- )
-
- self.menu.move(parentPosition + QPos)
- self.menu.show()
diff --git a/mainwindow.ui b/mainwindow.ui
deleted file mode 100644
index 4a12fd5..0000000
--- a/mainwindow.ui
+++ /dev/null
@@ -1,809 +0,0 @@
-
-
- MainWindow
-
-
-
- 0
- 0
- 1008
- 575
-
-
-
-
- 0
- 0
-
-
-
-
- 0
- 0
-
-
-
- MainWindow
-
-
-
-
- 0
- 0
-
-
-
- false
-
-
-
- 9
-
-
- 0
-
- -
-
-
-
-
-
- Qt::Vertical
-
-
- QSizePolicy::MinimumExpanding
-
-
-
- 0
- 360
-
-
-
-
- -
-
-
- QLayout::SetDefaultConstraint
-
-
- 0
-
-
-
-
-
- Qt::Horizontal
-
-
- QSizePolicy::MinimumExpanding
-
-
-
- 420
- 0
-
-
-
-
-
-
- -
-
-
- QLayout::SetMinimumSize
-
-
- 3
-
-
-
-
-
- QLayout::SetMinimumSize
-
-
- 3
-
-
-
-
-
- QLayout::SetMinimumSize
-
-
-
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 140
- 20
-
-
-
-
- -
-
-
- Projects
-
-
-
- -
-
-
- Presets
-
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Minimum
-
-
-
- 20
- 2
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 0
-
-
-
-
- 16777215
- 16777215
-
-
-
- true
-
-
- QFrame::StyledPanel
-
-
- QFrame::Sunken
-
-
- 1
-
-
- true
-
-
- true
-
-
- false
-
-
- QAbstractItemView::InternalMove
-
-
- Qt::MoveAction
-
-
-
- -
-
-
-
-
-
- Add
-
-
-
- -
-
-
- Remove
-
-
-
- -
-
-
- Up
-
-
-
- -
-
-
- Down
-
-
-
-
-
-
-
- -
-
-
- 4
-
-
- 2
-
-
-
-
-
-
-
- -
-
-
- QLayout::SetFixedSize
-
-
- 4
-
-
- 0
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 500
- 0
-
-
-
-
- 16777215
- 180
-
-
-
- QTabWidget::North
-
-
- QTabWidget::Rounded
-
-
- 0
-
-
-
- Export Video
-
-
-
- 10
-
-
-
-
-
- 0
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 85
- 0
-
-
-
-
- 80
- 16777215
-
-
-
-
- 80
- 0
-
-
-
- Audio File
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 28
-
-
-
-
- 16777215
- 28
-
-
-
-
- 0
- 0
-
-
-
-
- -
-
-
-
- 0
- 28
-
-
-
-
- 16777215
- 28
-
-
-
- ...
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 85
- 0
-
-
-
-
- 0
- 0
-
-
-
- Output File
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 28
-
-
-
-
- 16777215
- 28
-
-
-
-
- -
-
-
-
- 0
- 28
-
-
-
-
- 16777215
- 28
-
-
-
- ...
-
-
-
-
-
-
-
- -
-
-
- 0
-
-
-
-
-
-
- 0
- 0
-
-
-
- 24
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Minimum
-
-
-
- 10
- 20
-
-
-
-
- -
-
-
- Create Video
-
-
-
- -
-
-
- false
-
-
- Cancel
-
-
-
-
-
-
-
-
-
-
-
- Encoder Settings
-
-
-
- 10
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 85
- 0
-
-
-
- Container
-
-
-
- -
-
-
-
- 150
- 0
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Minimum
-
-
-
- 5
- 5
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Resolution
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 0
-
-
-
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 85
- 0
-
-
-
- Video Codec
-
-
-
- -
-
-
-
- 150
- 0
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 5
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Video Bitrate (Kbps)
-
-
-
- -
-
-
- 99999
-
-
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 85
- 0
-
-
-
- Audio Codec
-
-
-
- -
-
-
-
- 150
- 0
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 5
- 10
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Audio Bitrate (Kbps)
-
-
-
- -
-
-
- 9999
-
-
-
-
-
-
-
-
-
- -
-
-
- QLayout::SetDefaultConstraint
-
-
-
-
-
- Qt::Horizontal
-
-
- QSizePolicy::MinimumExpanding
-
-
-
- 500
- 0
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 180
-
-
-
-
- 16777215
- 180
-
-
-
- -1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/presetmanager.py b/presetmanager.py
deleted file mode 100644
index 3b02714..0000000
--- a/presetmanager.py
+++ /dev/null
@@ -1,290 +0,0 @@
-from PyQt4 import QtGui, QtCore
-import string
-import os
-
-import core
-
-
-class PresetManager(QtGui.QDialog):
- def __init__(self, window, parent):
- super().__init__(parent.window)
- self.parent = parent
- self.core = parent.core
- self.settings = parent.settings
- self.presetDir = self.core.presetDir
- if not self.settings.value('presetDir'):
- self.settings.setValue(
- "presetDir",
- os.path.join(self.core.dataDir, 'projects'))
-
- self.findPresets()
-
- # window
- self.lastFilter = '*'
- self.presetRows = [] # list of (comp, vers, name) tuples
- self.window = window
- self.window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
-
- # connect button signals
- self.window.pushButton_delete.clicked.connect(self.openDeletePresetDialog)
- self.window.pushButton_rename.clicked.connect(self.openRenamePresetDialog)
- self.window.pushButton_import.clicked.connect(self.openImportDialog)
- self.window.pushButton_export.clicked.connect(self.openExportDialog)
- self.window.pushButton_close.clicked.connect(self.window.close)
-
- # create filter box and preset list
- self.drawFilterList()
- self.window.comboBox_filter.currentIndexChanged.connect(
- lambda: self.drawPresetList(
- self.window.comboBox_filter.currentText(), self.window.lineEdit_search.text()
- )
- )
-
- # make auto-completion for search bar
- self.autocomplete = QtGui.QStringListModel()
- completer = QtGui.QCompleter()
- completer.setModel(self.autocomplete)
- self.window.lineEdit_search.setCompleter(completer)
- self.window.lineEdit_search.textChanged.connect(
- lambda: self.drawPresetList(
- self.window.comboBox_filter.currentText(), self.window.lineEdit_search.text()
- )
- )
- self.drawPresetList('*')
-
- def show(self):
- '''Open a new preset manager window from the mainwindow'''
- self.findPresets()
- self.drawFilterList()
- self.drawPresetList('*')
- self.window.show()
-
- def findPresets(self):
- parseList = []
- for dirpath, dirnames, filenames in os.walk(self.presetDir):
- # anything without a subdirectory must be a preset folder
- if dirnames:
- continue
- for preset in filenames:
- compName = os.path.basename(os.path.dirname(dirpath))
- compVers = os.path.basename(dirpath)
- try:
- parseList.append((compName, int(compVers), preset))
- except ValueError:
- continue
- self.presets =\
- {
- compName : \
- [
- (vers, preset) \
- for name, vers, preset in parseList \
- if name == compName \
- ] \
- for compName, _, __ in parseList \
- }
-
- def drawPresetList(self, compFilter=None, presetFilter=''):
- self.window.listWidget_presets.clear()
- if compFilter:
- self.lastFilter = str(compFilter)
- else:
- compFilter = str(self.lastFilter)
- self.presetRows = []
- presetNames = []
- for component, presets in self.presets.items():
- if compFilter != '*' and component != compFilter:
- continue
- for vers, preset in presets:
- if not presetFilter or presetFilter in preset:
- self.window.listWidget_presets.addItem('%s: %s' % (component, preset))
- self.presetRows.append((component, vers, preset))
- if preset not in presetNames:
- presetNames.append(preset)
- self.autocomplete.setStringList(presetNames)
-
- def drawFilterList(self):
- self.window.comboBox_filter.clear()
- self.window.comboBox_filter.addItem('*')
- for component in self.presets:
- self.window.comboBox_filter.addItem(component)
-
- def clearPreset(self, compI=None):
- '''Functions on mainwindow level from the context menu'''
- compI = self.parent.window.listWidget_componentList.currentRow()
- self.core.clearPreset(compI, self.parent)
-
- def openSavePresetDialog(self):
- '''Functions on mainwindow level from the context menu'''
- window = self.parent.window
- selectedComponents = self.core.selectedComponents
- componentList = self.parent.window.listWidget_componentList
-
- if componentList.currentRow() == -1:
- return
- while True:
- index = componentList.currentRow()
- currentPreset = selectedComponents[index].currentPreset
- newName, OK = QtGui.QInputDialog.getText(
- self.parent.window,
- 'Audio Visualizer',
- 'New Preset Name:',
- QtGui.QLineEdit.Normal,
- currentPreset
- )
- if OK:
- if core.Core.badName(newName):
- self.warnMessage(self.parent.window)
- continue
- if newName:
- if index != -1:
- selectedComponents[index].currentPreset = newName
- saveValueStore = \
- selectedComponents[index].savePreset()
- componentName = str(selectedComponents[index]).strip()
- vers = selectedComponents[index].version()
- self.createNewPreset(
- componentName, vers, newName,
- saveValueStore, window=self.parent.window)
- self.openPreset(newName)
- break
-
- def createNewPreset(
- self, compName, vers, filename, saveValueStore, **kwargs):
- path = os.path.join(self.presetDir, compName, str(vers), filename)
- if self.presetExists(path, **kwargs):
- return
- self.core.createPresetFile(compName, vers, filename, saveValueStore)
-
- def presetExists(self, path, **kwargs):
- if os.path.exists(path):
- window = self.window \
- if 'window' not in kwargs else kwargs['window']
- ch = self.parent.showMessage(
- msg="%s already exists! Overwrite it?" %
- os.path.basename(path),
- showCancel=True,
- icon=QtGui.QMessageBox.Warning,
- parent=window)
- if not ch:
- # user clicked cancel
- return True
-
- return False
-
- def openPreset(self, presetName):
- componentList = self.parent.window.listWidget_componentList
- selectedComponents = self.parent.core.selectedComponents
-
- index = componentList.currentRow()
- if index == -1:
- return
- componentName = str(selectedComponents[index]).strip()
- version = selectedComponents[index].version()
- dirname = os.path.join(self.presetDir, componentName, str(version))
- filepath = os.path.join(dirname, presetName)
- self.core.openPreset(filepath, index, presetName)
-
- self.parent.updateComponentTitle(index)
- self.parent.drawPreview()
-
- def openDeletePresetDialog(self):
- selected = self.window.listWidget_presets.selectedItems()
- if not selected:
- return
- row = self.window.listWidget_presets.row(selected[0])
- comp, vers, name = self.presetRows[row]
- ch = self.parent.showMessage(
- msg='Really delete %s?' % name,
- showCancel=True,
- icon=QtGui.QMessageBox.Warning,
- parent=self.window
- )
- if not ch:
- return
- self.deletePreset(comp, vers, name)
- self.findPresets()
- self.drawPresetList()
-
- def deletePreset(self, comp, vers, name):
- filepath = os.path.join(self.presetDir, comp, str(vers), name)
- os.remove(filepath)
-
- def warnMessage(self, window=None):
- print(window)
- self.parent.showMessage(
- msg='Preset names must contain only letters, '
- 'numbers, and spaces.',
- parent=window if window else self.window)
-
- def openRenamePresetDialog(self):
- presetList = self.window.listWidget_presets
- if presetList.currentRow() == -1:
- return
-
- while True:
- index = presetList.currentRow()
- newName, OK = QtGui.QInputDialog.getText(
- self.window,
- 'Preset Manager',
- 'Rename Preset:',
- QtGui.QLineEdit.Normal,
- self.presetRows[index][2]
- )
- if OK:
- if core.Core.badName(newName):
- self.warnMessage()
- continue
- if newName:
- comp, vers, oldName = self.presetRows[index]
- path = os.path.join(
- self.presetDir, comp, str(vers))
- newPath = os.path.join(path, newName)
- oldPath = os.path.join(path, oldName)
- if self.presetExists(newPath):
- return
- if os.path.exists(newPath):
- os.remove(newPath)
- os.rename(oldPath, newPath)
- self.findPresets()
- self.drawPresetList()
- break
-
- def openImportDialog(self):
- filename = QtGui.QFileDialog.getOpenFileName(
- self.window, "Import Preset File",
- self.settings.value("presetDir"),
- "Preset Files (*.avl)")
- if filename:
- # get installed path & ask user to overwrite if needed
- path = ''
- while True:
- if path:
- if self.presetExists(path):
- break
- else:
- if os.path.exists(path):
- os.remove(path)
- success, path = self.core.importPreset(filename)
- if success:
- break
-
- self.findPresets()
- self.drawPresetList()
- self.settings.setValue("presetDir", os.path.dirname(filename))
-
- def openExportDialog(self):
- if not self.window.listWidget_presets.selectedItems():
- return
- filename = QtGui.QFileDialog.getSaveFileName(
- self.window, "Export Preset",
- self.settings.value("presetDir"),
- "Preset Files (*.avl)")
- if filename:
- index = self.window.listWidget_presets.currentRow()
- comp, vers, name = self.presetRows[index]
- if not self.core.exportPreset(filename, comp, vers, name):
- self.parent.showMessage(
- msg='Couldn\'t export %s.' % filename,
- parent=self.window
- )
- self.settings.setValue("presetDir", os.path.dirname(filename))
diff --git a/presetmanager.ui b/presetmanager.ui
deleted file mode 100644
index 5257b1c..0000000
--- a/presetmanager.ui
+++ /dev/null
@@ -1,150 +0,0 @@
-
-
- presetmanager
-
-
- Qt::NonModal
-
-
- true
-
-
-
- 0
- 0
- 497
- 377
-
-
-
- Preset Manager
-
-
- -
-
-
-
-
-
-
-
-
- Filter by name
-
-
-
- -
-
-
-
- 200
- 0
-
-
-
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
- true
-
-
-
-
-
- -
-
-
- QLayout::SetMinimumSize
-
-
-
-
-
- Import
-
-
-
- -
-
-
- Export
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
- true
-
-
- Rename
-
-
-
- -
-
-
- Delete
-
-
-
-
-
- -
-
-
-
-
-
- <html><head/><body><p><span style=" font-size:10pt; font-style:italic;">Right-click components in the main window to create presets</span></p></body></html>
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
- Close
-
-
-
-
-
-
-
-
-
-
diff --git a/preview_thread.py b/preview_thread.py
deleted file mode 100644
index eabf715..0000000
--- a/preview_thread.py
+++ /dev/null
@@ -1,59 +0,0 @@
-from PyQt4 import QtCore, QtGui, uic
-from PyQt4.QtCore import pyqtSignal, pyqtSlot
-from PIL import Image
-from PIL.ImageQt import ImageQt
-import core
-from queue import Queue, Empty
-import os
-from copy import copy
-
-
-class Worker(QtCore.QObject):
-
- imageCreated = pyqtSignal(['QImage'])
-
- def __init__(self, parent=None, queue=None):
- QtCore.QObject.__init__(self)
- parent.newTask.connect(self.createPreviewImage)
- parent.processTask.connect(self.process)
- self.parent = parent
- self.core = core.Core()
- self.queue = queue
- self.core.settings = parent.settings
- self.stackedWidget = parent.window.stackedWidget
- self.background = Image.new("RGBA", (1920, 1080), (0, 0, 0, 0))
- self.background.paste(Image.open(os.path.join(
- self.core.wd, "background.png")))
-
- @pyqtSlot(str, list)
- def createPreviewImage(self, components):
- dic = {
- "components": components,
- }
- self.queue.put(dic)
-
- @pyqtSlot()
- def process(self):
- try:
- nextPreviewInformation = self.queue.get(block=False)
- while self.queue.qsize() >= 2:
- try:
- self.queue.get(block=False)
- except Empty:
- continue
-
- width = int(self.core.settings.value('outputWidth'))
- height = int(self.core.settings.value('outputHeight'))
- frame = copy(self.background)
- frame = frame.resize((width, height))
-
- components = nextPreviewInformation["components"]
- for component in reversed(components):
- frame = Image.alpha_composite(
- frame, component.previewRender(self))
-
- self._image = ImageQt(frame)
- self.imageCreated.emit(QtGui.QImage(self._image))
-
- except Empty:
- True
diff --git a/setup.py b/setup.py
index 48034dc..fde3461 100644
--- a/setup.py
+++ b/setup.py
@@ -1,51 +1,19 @@
-from cx_Freeze import setup, Executable
-import sys
-
-# Dependencies are automatically detected, but it might need
-# fine tuning.
-
-buildOptions = dict(
- packages=[],
- excludes=[
- "apport",
- "apt",
- "curses",
- "distutils",
- "email",
- "html",
- "http",
- "xmlrpc",
- "nose"
- ],
- include_files=[
- "mainwindow.ui",
- "presetmanager.ui",
- "background.png",
- "encoder-options.json",
- "components/"
- ],
- includes=[
- 'numpy.core._methods',
- 'numpy.lib.format'
- ]
-)
-
-
-base = 'Win32GUI' if sys.platform == 'win32' else None
-
-executables = [
- Executable(
- 'main.py',
- base=base,
- targetName='audio-visualizer-python'
- )
-]
-
-
-setup(
- name='audio-visualizer-python',
- version='1.0',
- description='GUI tool to render visualization videos of audio files',
- options=dict(build_exe=buildOptions),
- executables=executables
-)
++from setuptools import setup, find_packages
+
+ -# Dependencies are automatically detected, but it might need +setup(name='audio_visualizer_python',
+ -# fine tuning. + version='1.0',
+ -buildOptions = dict(packages = [], excludes = [ + description='a little GUI tool to render visualization \
+ - "apport", + videos of audio files',
+ - "apt", + license='MIT',
+ - "ctypes", + url='https://github.com/djfun/audio-visualizer-python',
+ - "curses", + packages=find_packages(),
+ - "distutils", + package_data={
+ - "email", + 'src': ['*'],
+ - "html", + },
+ - "http", + install_requires=['pillow-simd', 'numpy', ''],
+ - "json", + entry_points={
+ - "xmlrpc", + 'gui_scripts': [
+ - "nose" + 'audio-visualizer-python = avpython.main:main'
+ - ], include_files = ["main.ui"]) + ]
+ - + }
+ -import sys + )
\ No newline at end of file
diff --git a/src/background.png b/src/background.png
new file mode 100644
index 0000000..fb58593
Binary files /dev/null and b/src/background.png differ
diff --git a/src/command.py b/src/command.py
new file mode 100644
index 0000000..1a1e810
--- /dev/null
+++ b/src/command.py
@@ -0,0 +1,126 @@
+from PyQt4 import QtCore
+from PyQt4.QtCore import QSettings
+import argparse
+import os
+import sys
+
+import core
+import video_thread
+from main import LoadDefaultSettings
+
+
+class Command(QtCore.QObject):
+
+ videoTask = QtCore.pyqtSignal(str, str, list)
+
+ def __init__(self):
+ QtCore.QObject.__init__(self)
+ self.core = core.Core()
+ self.dataDir = self.core.dataDir
+ self.canceled = False
+
+ self.parser = argparse.ArgumentParser(
+ description='Create a visualization for an audio file',
+ epilog='EXAMPLE COMMAND: main.py myvideotemplate.avp '
+ '-i ~/Music/song.mp3 -o ~/video.mp4 '
+ '-c 0 image path=~/Pictures/thisWeeksPicture.jpg '
+ '-c 1 video "preset=My Logo" -c 2 vis layout=classic')
+ self.parser.add_argument(
+ '-i', '--input', metavar='SOUND',
+ help='input audio file')
+ self.parser.add_argument(
+ '-o', '--output', metavar='OUTPUT',
+ help='output video file')
+
+ # optional arguments
+ self.parser.add_argument(
+ 'projpath', metavar='path-to-project',
+ help='open a project file (.avp)', nargs='?')
+ self.parser.add_argument(
+ '-c', '--comp', metavar=('LAYER', 'ARG'),
+ help='first arg must be component NAME to insert at LAYER.'
+ '"help" for information about possible args for a component.',
+ nargs='*', action='append')
+
+ self.args = self.parser.parse_args()
+ self.settings = QSettings(
+ os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat)
+ LoadDefaultSettings(self)
+
+ if self.args.projpath:
+ self.core.openProject(self, self.args.projpath)
+ self.core.selectedComponents = list(
+ reversed(self.core.selectedComponents))
+ self.core.componentListChanged()
+
+ if self.args.comp:
+ for comp in self.args.comp:
+ pos = comp[0]
+ name = comp[1]
+ args = comp[2:]
+ try:
+ pos = int(pos)
+ except ValueError:
+ print(pos, 'is not a layer number.')
+ quit(1)
+ realName = self.parseCompName(name)
+ if not realName:
+ print(name, 'is not a valid component name.')
+ quit(1)
+ modI = self.core.moduleIndexFor(realName)
+ i = self.core.insertComponent(pos, modI, self)
+ for arg in args:
+ self.core.selectedComponents[i].command(arg)
+
+ if self.args.input and self.args.output:
+ self.createAudioVisualisation()
+ elif 'help' not in sys.argv:
+ self.parser.print_help()
+ quit(1)
+
+ def createAudioVisualisation(self):
+ self.videoThread = QtCore.QThread(self)
+ self.videoWorker = video_thread.Worker(self)
+ self.videoWorker.moveToThread(self.videoThread)
+ self.videoWorker.videoCreated.connect(self.videoCreated)
+
+ self.videoThread.start()
+ self.videoTask.emit(
+ self.args.input,
+ self.args.output,
+ list(reversed(self.core.selectedComponents))
+ )
+
+ def videoCreated(self):
+ self.videoThread.quit()
+ self.videoThread.wait()
+ quit(0)
+
+ def showMessage(self, **kwargs):
+ print(kwargs['msg'])
+ if 'detail' in kwargs:
+ print(kwargs['detail'])
+
+ def drawPreview(self, *args):
+ pass
+
+ def parseCompName(self, name):
+ '''Deduces a proper component name out of a commandline arg'''
+
+ if name.title() in self.core.compNames:
+ return name.title()
+ for compName in self.core.compNames:
+ if name.capitalize() in compName:
+ return compName
+
+ compFileNames = [ \
+ os.path.splitext(os.path.basename(
+ mod.__file__))[0] \
+ for mod in self.core.modules \
+ ]
+ for i, compFileName in enumerate(compFileNames):
+ if name.lower() in compFileName:
+ return self.core.compNames[i]
+ return
+
+ return None
diff --git a/src/components/__base__.py b/src/components/__base__.py
new file mode 100644
index 0000000..a4677b1
--- /dev/null
+++ b/src/components/__base__.py
@@ -0,0 +1,153 @@
+from PyQt5 import QtGui, QtCore, QtWidgets
+from PIL import Image
+import os
+
+
+class Component(QtCore.QObject):
+ '''A base class for components to inherit from'''
+
+ # modified = QtCore.pyqtSignal(int, bool)
+
+ def __init__(self, moduleIndex, compPos, core):
+ super().__init__()
+ self.currentPreset = None
+ self.moduleIndex = moduleIndex
+ self.compPos = compPos
+ self.core = core
+
+ def __str__(self):
+ return self.__doc__
+
+ def version(self):
+ # change this number to identify new versions of a component
+ return 1
+
+ def cancel(self):
+ # please stop any lengthy process in response to this variable
+ self.canceled = True
+
+ def reset(self):
+ self.canceled = False
+
+ def update(self):
+ self.modified.emit(self.compPos, self.savePreset())
+ # read your widget values, then call super().update()
+
+ def loadPreset(self, presetDict, presetName):
+ '''Subclasses take (presetDict, presetName=None) as args.
+ Must use super().loadPreset(presetDict, presetName) first,
+ then update self.page widgets using the preset dict.
+ '''
+ self.currentPreset = presetName \
+ if presetName != None else presetDict['preset']
+
+ def preFrameRender(self, **kwargs):
+ '''Triggered only before a video is exported (video_thread.py)
+ self.worker = the video thread worker
+ self.completeAudioArray = a list of audio samples
+ self.sampleSize = number of audio samples per video frame
+ self.progressBarUpdate = signal to set progress bar number
+ self.progressBarSetText = signal to set progress bar text
+ Use the latter two signals to update the MainProgram if needed
+ for a long initialization procedure (i.e., for a visualizer)
+ '''
+ for var, value in kwargs.items():
+ exec('self.%s = value' % var)
+
+ def command(self, arg):
+ '''Configure a component using argument from the commandline.
+ Use super().command(arg) at the end of a subclass's method,
+ if no arguments are found in that method first
+ '''
+ if arg.startswith('preset='):
+ _, preset = arg.split('=', 1)
+ path = os.path.join(self.core.getPresetDir(self), preset)
+ if not os.path.exists(path):
+ print('Couldn\'t locate preset "%s"' % preset)
+ quit(1)
+ else:
+ print('Opening "%s" preset on layer %s' % \
+ (preset, self.compPos))
+ self.core.openPreset(path, self.compPos, preset)
+ else:
+ print(
+ self.__doc__, 'Usage:\n'
+ 'Open a preset for this component:\n'
+ ' "preset=Preset Name"')
+ self.commandHelp()
+ quit(0)
+
+ def commandHelp(self):
+ '''Print help text for this Component's commandline arguments'''
+
+ def blankFrame(self, width, height):
+ return Image.new("RGBA", (width, height), (0, 0, 0, 0))
+
+ def pickColor(self):
+ '''Use color picker to get color input from the user,
+ and return this as an RGB string and QPushButton stylesheet.
+ In a subclass apply stylesheet to any color selection widgets
+ '''
+ dialog = QtGui.QColorDialog()
+ dialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True)
+ color = dialog.getColor()
+ if color.isValid():
+ RGBstring = '%s,%s,%s' % (
+ str(color.red()), str(color.green()), str(color.blue()))
+ btnStyle = "QPushButton{background-color: %s; outline: none;}" \
+ % color.name()
+ return RGBstring, btnStyle
+ else:
+ return None, None
+
+ def RGBFromString(self, string):
+ ''' Turns an RGB string like "255, 255, 255" into a tuple '''
+ try:
+ tup = tuple([int(i) for i in string.split(',')])
+ if len(tup) != 3:
+ raise ValueError
+ for i in tup:
+ if i > 255 or i < 0:
+ raise ValueError
+ return tup
+ except:
+ return (255, 255, 255)
+
+ '''
+ ### Reference methods for creating a new component
+ ### (Inherit from this class and define these)
+
+ def widget(self, parent):
+ self.parent = parent
+ page = uic.loadUi(os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), 'example.ui'))
+ # --- connect widget signals here ---
+ self.page = page
+ return page
+
+ def update(self):
+ super().update()
+ self.parent.drawPreview()
+
+ def previewRender(self, previewWorker):
+ width = int(previewWorker.core.settings.value('outputWidth'))
+ height = int(previewWorker.core.settings.value('outputHeight'))
+ image = Image.new("RGBA", (width, height), (0,0,0,0))
+ return image
+
+ def frameRender(self, moduleNo, frameNo):
+ width = int(self.worker.core.settings.value('outputWidth'))
+ height = int(self.worker.core.settings.value('outputHeight'))
+ image = Image.new("RGBA", (width, height), (0,0,0,0))
+ return image
+ '''
+
+class BadComponentInit(Exception):
+ def __init__(self, arg, name):
+ string = \
+'''################################
+Mandatory argument "%s" not specified
+ in %s instance initialization
+###################################'''
+ print(string % (arg, name))
+ quit()
diff --git a/src/components/__init__.py b/src/components/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/components/__init__.py
@@ -0,0 +1 @@
+
diff --git a/src/components/color.py b/src/components/color.py
new file mode 100644
index 0000000..8f9a1d1
--- /dev/null
+++ b/src/components/color.py
@@ -0,0 +1,246 @@
+from PIL import Image, ImageDraw
+from PyQt5 import uic, QtGui, QtCore
+from PyQt5.QtGui import QColor
+from PIL.ImageQt import ImageQt
+import os
+from . import __base__
+
+
+class Component(__base__.Component):
+ '''Color'''
+
+ modified = QtCore.pyqtSignal(int, dict)
+
+ def widget(self, parent):
+ self.parent = parent
+ page = uic.loadUi(os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), 'color.ui'))
+
+ self.color1 = (0, 0, 0)
+ self.color2 = (133, 133, 133)
+ self.x = 0
+ self.y = 0
+
+ page.lineEdit_color1.setText('%s,%s,%s' % self.color1)
+ page.lineEdit_color2.setText('%s,%s,%s' % self.color2)
+
+ btnStyle1 = "QPushButton { background-color : %s; outline: none; }" \
+ % QColor(*self.color1).name()
+
+ btnStyle2 = "QPushButton { background-color : %s; outline: none; }" \
+ % QColor(*self.color2).name()
+
+ page.pushButton_color1.setStyleSheet(btnStyle1)
+ page.pushButton_color2.setStyleSheet(btnStyle2)
+ page.pushButton_color1.clicked.connect(lambda: self.pickColor(1))
+ page.pushButton_color2.clicked.connect(lambda: self.pickColor(2))
+
+ # disable color #2 until non-default 'fill' option gets changed
+ page.lineEdit_color2.setDisabled(True)
+ page.pushButton_color2.setDisabled(True)
+ page.spinBox_x.valueChanged.connect(self.update)
+ page.spinBox_y.valueChanged.connect(self.update)
+ page.spinBox_width.setValue(
+ int(parent.settings.value("outputWidth")))
+ page.spinBox_height.setValue(
+ int(parent.settings.value("outputHeight")))
+
+ page.lineEdit_color1.textChanged.connect(self.update)
+ page.lineEdit_color2.textChanged.connect(self.update)
+ page.spinBox_x.valueChanged.connect(self.update)
+ page.spinBox_y.valueChanged.connect(self.update)
+ page.spinBox_width.valueChanged.connect(self.update)
+ page.spinBox_height.valueChanged.connect(self.update)
+ page.checkBox_trans.stateChanged.connect(self.update)
+
+ self.fillLabels = [ \
+ 'Solid',
+ 'Linear Gradient',
+ 'Radial Gradient',
+ ]
+ for label in self.fillLabels:
+ page.comboBox_fill.addItem(label)
+ page.comboBox_fill.setCurrentIndex(0)
+ page.comboBox_fill.currentIndexChanged.connect(self.update)
+ page.comboBox_spread.currentIndexChanged.connect(self.update)
+ page.spinBox_radialGradient_end.valueChanged.connect(self.update)
+ page.spinBox_radialGradient_start.valueChanged.connect(self.update)
+ page.spinBox_radialGradient_spread.valueChanged.connect(self.update)
+ page.spinBox_linearGradient_end.valueChanged.connect(self.update)
+ page.spinBox_linearGradient_start.valueChanged.connect(self.update)
+ page.checkBox_stretch.stateChanged.connect(self.update)
+
+ self.page = page
+ return page
+
+ def update(self):
+ self.color1 = self.RGBFromString(self.page.lineEdit_color1.text())
+ self.color2 = self.RGBFromString(self.page.lineEdit_color2.text())
+ self.x = self.page.spinBox_x.value()
+ self.y = self.page.spinBox_y.value()
+ self.sizeWidth = self.page.spinBox_width.value()
+ self.sizeHeight = self.page.spinBox_height.value()
+ self.trans = self.page.checkBox_trans.isChecked()
+ self.spread = self.page.comboBox_spread.currentIndex()
+
+ self.RG_start = self.page.spinBox_radialGradient_start.value()
+ self.RG_end = self.page.spinBox_radialGradient_end.value()
+ self.RG_centre = self.page.spinBox_radialGradient_spread.value()
+ self.stretch = self.page.checkBox_stretch.isChecked()
+ self.LG_start = self.page.spinBox_linearGradient_start.value()
+ self.LG_end = self.page.spinBox_linearGradient_end.value()
+
+ self.fillType = self.page.comboBox_fill.currentIndex()
+ if self.fillType == 0:
+ self.page.lineEdit_color2.setEnabled(False)
+ self.page.pushButton_color2.setEnabled(False)
+ self.page.checkBox_trans.setEnabled(False)
+ self.page.checkBox_stretch.setEnabled(False)
+ self.page.comboBox_spread.setEnabled(False)
+ else:
+ self.page.lineEdit_color2.setEnabled(True)
+ self.page.pushButton_color2.setEnabled(True)
+ self.page.checkBox_trans.setEnabled(True)
+ self.page.checkBox_stretch.setEnabled(True)
+ self.page.comboBox_spread.setEnabled(True)
+ self.page.fillWidget.setCurrentIndex(self.fillType)
+
+ self.parent.drawPreview()
+ super().update()
+
+ def previewRender(self, previewWorker):
+ width = int(previewWorker.core.settings.value('outputWidth'))
+ height = int(previewWorker.core.settings.value('outputHeight'))
+ return self.drawFrame(width, height)
+
+ def preFrameRender(self, **kwargs):
+ super().preFrameRender(**kwargs)
+ return ['static']
+
+ def frameRender(self, moduleNo, arrayNo, frameNo):
+ width = int(self.worker.core.settings.value('outputWidth'))
+ height = int(self.worker.core.settings.value('outputHeight'))
+ return self.drawFrame(width, height)
+
+ def drawFrame(self, width, height):
+ r, g, b = self.color1
+ shapeSize = (self.sizeWidth, self.sizeHeight)
+ # in default state, skip all this logic and return a plain fill
+ if self.fillType==0 and shapeSize == (width, height) \
+ and self.x == 0 and self.y == 0:
+ return Image.new("RGBA", (width, height), (r, g, b, 255))
+
+ frame = self.blankFrame(width, height)
+
+ # Return a solid image at x, y
+ if self.fillType == 0:
+ image = Image.new("RGBA", shapeSize, (r, g, b, 255))
+ frame.paste(image, box=(self.x, self.y))
+ return frame
+
+ # Now fills that require using Qt...
+ elif self.fillType > 0:
+ image = ImageQt(frame)
+ painter = QtGui.QPainter(image)
+ if self.stretch:
+ w = width; h = height
+ else:
+ w = self.sizeWidth; h = self.sizeWidth
+
+ if self.fillType == 1: # Linear Gradient
+ brush = QtGui.QLinearGradient(
+ self.LG_start,
+ self.LG_start,
+ self.LG_start+width/3,
+ self.LG_end)
+
+ elif self.fillType == 2: # Radial Gradient
+ brush = QtGui.QRadialGradient(
+ self.RG_start,
+ self.RG_end,
+ w, h,
+ self.RG_centre)
+
+ brush.setSpread(self.spread)
+ brush.setColorAt(0.0, QColor(*self.color1))
+ if self.trans:
+ brush.setColorAt(1.0, QColor(0, 0, 0, 0))
+ elif self.fillType == 1 and self.stretch:
+ brush.setColorAt(0.2, QColor(*self.color2))
+ else:
+ brush.setColorAt(1.0, QColor(*self.color2))
+ painter.setBrush(brush)
+ painter.drawRect(self.x, self.y,
+ self.sizeWidth, self.sizeHeight)
+ painter.end()
+ imBytes = image.bits().asstring(image.numBytes())
+ return Image.frombytes('RGBA', (width, height), imBytes)
+
+ def loadPreset(self, pr, presetName=None):
+ super().loadPreset(pr, presetName)
+
+ self.page.comboBox_fill.setCurrentIndex(pr['fillType'])
+ self.page.lineEdit_color1.setText('%s,%s,%s' % pr['color1'])
+ self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2'])
+ self.page.spinBox_x.setValue(pr['x'])
+ self.page.spinBox_y.setValue(pr['y'])
+ self.page.spinBox_width.setValue(pr['width'])
+ self.page.spinBox_height.setValue(pr['height'])
+ self.page.checkBox_trans.setChecked(pr['trans'])
+
+ self.page.spinBox_radialGradient_start.setValue(pr['RG_start'])
+ self.page.spinBox_radialGradient_end.setValue(pr['RG_end'])
+ self.page.spinBox_radialGradient_spread.setValue(pr['RG_centre'])
+ self.page.spinBox_linearGradient_start.setValue(pr['LG_start'])
+ self.page.spinBox_linearGradient_end.setValue(pr['LG_end'])
+ self.page.checkBox_stretch.setChecked(pr['stretch'])
+ self.page.comboBox_spread.setCurrentIndex(pr['spread'])
+
+ btnStyle1 = "QPushButton { background-color : %s; outline: none; }" \
+ % QColor(*pr['color1']).name()
+ btnStyle2 = "QPushButton { background-color : %s; outline: none; }" \
+ % QColor(*pr['color2']).name()
+ self.page.pushButton_color1.setStyleSheet(btnStyle1)
+ self.page.pushButton_color2.setStyleSheet(btnStyle2)
+
+ def savePreset(self):
+ return {
+ 'preset': self.currentPreset,
+ 'color1': self.color1,
+ 'color2': self.color2,
+ 'x': self.x,
+ 'y': self.y,
+ 'fillType': self.fillType,
+ 'width': self.sizeWidth,
+ 'height': self.sizeHeight,
+ 'trans': self.trans,
+ 'stretch': self.stretch,
+ 'spread': self.spread,
+ 'RG_start': self.RG_start,
+ 'RG_end': self.RG_end,
+ 'RG_centre': self.RG_centre,
+ 'LG_start': self.LG_start,
+ 'LG_end': self.LG_end,
+ }
+
+ def pickColor(self, num):
+ RGBstring, btnStyle = super().pickColor()
+ if not RGBstring:
+ return
+ if num == 1:
+ self.page.lineEdit_color1.setText(RGBstring)
+ self.page.pushButton_color1.setStyleSheet(btnStyle)
+ else:
+ self.page.lineEdit_color2.setText(RGBstring)
+ self.page.pushButton_color2.setStyleSheet(btnStyle)
+
+ def commandHelp(self):
+ print('Specify a color:\n color=255,255,255')
+
+ def command(self, arg):
+ if not arg.startswith('preset=') and '=' in arg:
+ key, arg = arg.split('=', 1)
+ if key == 'color':
+ self.page.lineEdit_color1.setText(arg)
+ return
+ super().command(arg)
diff --git a/src/components/color.ui b/src/components/color.ui
new file mode 100644
index 0000000..a9dacea
--- /dev/null
+++ b/src/components/color.ui
@@ -0,0 +1,660 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 586
+ 197
+
+
+
+ Form
+
+
+ -
+
+
+ 4
+
+
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 31
+ 0
+
+
+
+ Color #1
+
+
+
+ -
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 1
+ 0
+
+
+
+ 12
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 31
+ 0
+
+
+
+ Color #2
+
+
+
+ -
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 1
+ 0
+
+
+
+ 12
+
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Width
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 0
+ 0
+
+
+
+ 0
+
+
+ 999999999
+
+
+ 0
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Height
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+ 999999999
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ X
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 0
+ 0
+
+
+
+ -10000
+
+
+ 10000
+
+
+ 0
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Y
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+ -10000
+
+
+ 10000
+
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Fill
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ -1
+
+
+ QComboBox::AdjustToContentsOnFirstShow
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Transparent
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Stretch
+
+
+
+ -
+
+
-
+
+ Pad
+
+
+ -
+
+ Reflect
+
+
+ -
+
+ Repeat
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Minimum
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ 0
+
+
+ 2
+
+
+
+
+
+
+ -1
+ 0
+ 561
+ 31
+
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Start
+
+
+
+ -
+
+
+ -10000
+
+
+ 10000
+
+
+ 10
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ End
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ -10000
+
+
+ 10000
+
+
+ 10
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
+
+
+ -1
+ -1
+ 561
+ 31
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Start
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ -10000
+
+
+ 10000
+
+
+ 10
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ End
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ -10000
+
+
+ 10000
+
+
+ 10
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Centre
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ QAbstractSpinBox::PlusMinus
+
+
+ -10000
+
+
+ 10000
+
+
+ 3
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/image.py b/src/components/image.py
new file mode 100644
index 0000000..8ca88d3
--- /dev/null
+++ b/src/components/image.py
@@ -0,0 +1,111 @@
+from PIL import Image, ImageDraw
+from PyQt5 import uic, QtGui, QtCore, QtWidgets
+import os
+from . import __base__
+
+
+class Component(__base__.Component):
+ '''Image'''
+
+ modified = QtCore.pyqtSignal(int, dict)
+
+ def widget(self, parent):
+ self.parent = parent
+ self.settings = parent.settings
+ page = uic.loadUi(os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), 'image.ui'))
+ self.imagePath = ''
+ self.x = 0
+ self.y = 0
+
+ page.lineEdit_image.textChanged.connect(self.update)
+ page.pushButton_image.clicked.connect(self.pickImage)
+ page.spinBox_scale.valueChanged.connect(self.update)
+ page.checkBox_stretch.stateChanged.connect(self.update)
+ page.spinBox_x.valueChanged.connect(self.update)
+ page.spinBox_y.valueChanged.connect(self.update)
+
+ self.page = page
+ return page
+
+ def update(self):
+ self.imagePath = self.page.lineEdit_image.text()
+ self.scale = self.page.spinBox_scale.value()
+ self.xPosition = self.page.spinBox_x.value()
+ self.yPosition = self.page.spinBox_y.value()
+ self.stretched = self.page.checkBox_stretch.isChecked()
+ self.parent.drawPreview()
+ super().update()
+
+ def previewRender(self, previewWorker):
+ self.imageFormats = previewWorker.core.imageFormats
+ width = int(previewWorker.core.settings.value('outputWidth'))
+ height = int(previewWorker.core.settings.value('outputHeight'))
+ return self.drawFrame(width, height)
+
+ def preFrameRender(self, **kwargs):
+ super().preFrameRender(**kwargs)
+ return ['static']
+
+ def frameRender(self, moduleNo, arrayNo, frameNo):
+ width = int(self.worker.core.settings.value('outputWidth'))
+ height = int(self.worker.core.settings.value('outputHeight'))
+ return self.drawFrame(width, height)
+
+ def drawFrame(self, width, height):
+ frame = self.blankFrame(width, height)
+ if self.imagePath and os.path.exists(self.imagePath):
+ image = Image.open(self.imagePath)
+ if self.stretched and image.size != (width, height):
+ image = image.resize((width, height), Image.ANTIALIAS)
+ if self.scale != 100:
+ newHeight = int((image.height / 100) * self.scale)
+ newWidth = int((image.width / 100) * self.scale)
+ image = image.resize((newWidth, newHeight), Image.ANTIALIAS)
+ frame.paste(image, box=(self.xPosition, self.yPosition))
+ return frame
+
+ def loadPreset(self, pr, presetName=None):
+ super().loadPreset(pr, presetName)
+ self.page.lineEdit_image.setText(pr['image'])
+ self.page.spinBox_scale.setValue(pr['scale'])
+ self.page.spinBox_x.setValue(pr['x'])
+ self.page.spinBox_y.setValue(pr['y'])
+ self.page.checkBox_stretch.setChecked(pr['stretched'])
+
+ def savePreset(self):
+ return {
+ 'preset': self.currentPreset,
+ 'image': self.imagePath,
+ 'scale': self.scale,
+ 'stretched': self.stretched,
+ 'x': self.xPosition,
+ 'y': self.yPosition,
+ }
+
+ def pickImage(self):
+ imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
+ filename = QtGui.QFileDialog.getOpenFileName(
+ self.page, "Choose Image", imgDir,
+ "Image Files (%s)" % " ".join(self.imageFormats))
+ if filename:
+ self.settings.setValue("backgroundDir", os.path.dirname(filename))
+ self.page.lineEdit_image.setText(filename)
+ self.update()
+
+ def command(self, arg):
+ if not arg.startswith('preset=') and '=' in arg:
+ key, arg = arg.split('=', 1)
+ if key == 'path' and os.path.exists(arg):
+ try:
+ Image.open(arg)
+ self.page.lineEdit_image.setText(arg)
+ self.page.checkBox_stretch.setChecked(True)
+ return
+ except OSError as e:
+ print("Not a supported image format")
+ quit(1)
+ super().command(arg)
+
+ def commandHelp(self):
+ print('Load an image:\n path=/filepath/to/image.png')
diff --git a/src/components/image.ui b/src/components/image.ui
new file mode 100644
index 0000000..6df03a5
--- /dev/null
+++ b/src/components/image.ui
@@ -0,0 +1,259 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 586
+ 197
+
+
+
+ Form
+
+
+ -
+
+
+ 4
+
+
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 31
+ 0
+
+
+
+ Image
+
+
+
+ -
+
+
+
+ 1
+ 0
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 1
+ 0
+
+
+
+
+ 32
+ 32
+
+
+
+ ...
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ X
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+ -10000
+
+
+ 10000
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Y
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 0
+ 0
+
+
+
+ -1000
+
+
+ 1000
+
+
+ 0
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Stretch
+
+
+ false
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+ Scale
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ QAbstractSpinBox::UpDownArrows
+
+
+ %
+
+
+ 10
+
+
+ 400
+
+
+ 100
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
diff --git a/src/components/original.py b/src/components/original.py
new file mode 100644
index 0000000..61f463d
--- /dev/null
+++ b/src/components/original.py
@@ -0,0 +1,204 @@
+import numpy
+from PIL import Image, ImageDraw
+from PyQt5 import uic, QtGui, QtCore
+from PyQt5.QtGui import QColor
+import os
+from . import __base__
+import time
+from copy import copy
+
+
+class Component(__base__.Component):
+ '''Original Audio Visualization'''
+
+ modified = QtCore.pyqtSignal(int, dict)
+
+ def widget(self, parent):
+ self.parent = parent
+ self.visColor = (255, 255, 255)
+
+ page = uic.loadUi(os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), 'original.ui'))
+ page.comboBox_visLayout.addItem("Classic")
+ page.comboBox_visLayout.addItem("Split")
+ page.comboBox_visLayout.addItem("Bottom")
+ page.comboBox_visLayout.setCurrentIndex(0)
+ page.comboBox_visLayout.currentIndexChanged.connect(self.update)
+ page.lineEdit_visColor.setText('%s,%s,%s' % self.visColor)
+ page.pushButton_visColor.clicked.connect(lambda: self.pickColor())
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" \
+ % QColor(*self.visColor).name()
+ page.pushButton_visColor.setStyleSheet(btnStyle)
+ page.lineEdit_visColor.textChanged.connect(self.update)
+ self.page = page
+ self.canceled = False
+ return page
+
+ def update(self):
+ self.layout = self.page.comboBox_visLayout.currentIndex()
+ self.visColor = self.RGBFromString(self.page.lineEdit_visColor.text())
+ self.parent.drawPreview()
+ super().update()
+
+ def loadPreset(self, pr, presetName=None):
+ super().loadPreset(pr, presetName)
+
+ self.page.lineEdit_visColor.setText('%s,%s,%s' % pr['visColor'])
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" \
+ % QColor(*pr['visColor']).name()
+ self.page.pushButton_visColor.setStyleSheet(btnStyle)
+ self.page.comboBox_visLayout.setCurrentIndex(pr['layout'])
+
+ def savePreset(self):
+ return {
+ 'preset': self.currentPreset,
+ 'layout': self.layout,
+ 'visColor': self.visColor,
+ }
+
+ def previewRender(self, previewWorker):
+ spectrum = numpy.fromfunction(
+ lambda x: 0.008*(x-128)**2, (255,), dtype="int16")
+ width = int(previewWorker.core.settings.value('outputWidth'))
+ height = int(previewWorker.core.settings.value('outputHeight'))
+ return self.drawBars(
+ width, height, spectrum, self.visColor, self.layout)
+
+ def preFrameRender(self, **kwargs):
+ super().preFrameRender(**kwargs)
+ self.smoothConstantDown = 0.08
+ self.smoothConstantUp = 0.8
+ self.lastSpectrum = None
+ self.spectrumArray = {}
+ self.width = int(self.worker.core.settings.value('outputWidth'))
+ self.height = int(self.worker.core.settings.value('outputHeight'))
+
+ for i in range(0, len(self.completeAudioArray), self.sampleSize):
+ if self.canceled:
+ break
+ self.lastSpectrum = self.transformData(
+ i, self.completeAudioArray, self.sampleSize,
+ self.smoothConstantDown, self.smoothConstantUp,
+ self.lastSpectrum)
+ self.spectrumArray[i] = copy(self.lastSpectrum)
+
+ progress = int(100*(i/len(self.completeAudioArray)))
+ if progress >= 100:
+ progress = 100
+ pStr = "Analyzing audio: "+str(progress)+'%'
+ self.progressBarSetText.emit(pStr)
+ self.progressBarUpdate.emit(int(progress))
+
+ def frameRender(self, moduleNo, arrayNo, frameNo):
+ return self.drawBars(
+ self.width, self.height,
+ self.spectrumArray[arrayNo],
+ self.visColor, self.layout)
+
+ def pickColor(self):
+ RGBstring, btnStyle = super().pickColor()
+ if not RGBstring:
+ return
+ self.page.lineEdit_visColor.setText(RGBstring)
+ self.page.pushButton_visColor.setStyleSheet(btnStyle)
+
+ def transformData(
+ self, i, completeAudioArray, sampleSize,
+ smoothConstantDown, smoothConstantUp, lastSpectrum):
+ if len(completeAudioArray) < (i + sampleSize):
+ sampleSize = len(completeAudioArray) - i
+
+ window = numpy.hanning(sampleSize)
+ data = completeAudioArray[i:i+sampleSize][::1] * window
+ paddedSampleSize = 2048
+ paddedData = numpy.pad(
+ data, (0, paddedSampleSize - sampleSize), 'constant')
+ spectrum = numpy.fft.fft(paddedData)
+ sample_rate = 44100
+ frequencies = numpy.fft.fftfreq(len(spectrum), 1./sample_rate)
+
+ y = abs(spectrum[0:int(paddedSampleSize/2) - 1])
+
+ # filter the noise away
+ # y[y<80] = 0
+
+ y = 20 * numpy.log10(y)
+ y[numpy.isinf(y)] = 0
+
+ if lastSpectrum is not None:
+ lastSpectrum[y < lastSpectrum] = \
+ y[y < lastSpectrum] * smoothConstantDown + \
+ lastSpectrum[y < lastSpectrum] * (1 - smoothConstantDown)
+
+ lastSpectrum[y >= lastSpectrum] = \
+ y[y >= lastSpectrum] * smoothConstantUp + \
+ lastSpectrum[y >= lastSpectrum] * (1 - smoothConstantUp)
+ else:
+ lastSpectrum = y
+
+ x = frequencies[0:int(paddedSampleSize/2) - 1]
+
+ return lastSpectrum
+
+ def drawBars(self, width, height, spectrum, color, layout):
+ vH = height-height/8
+ bF = width / 64
+ bH = bF / 2
+ bQ = bF / 4
+ imTop = self.blankFrame(width, height)
+ draw = ImageDraw.Draw(imTop)
+ r, g, b = color
+ color2 = (r, g, b, 125)
+
+ bP = height / 1200
+
+ for j in range(0, 63):
+ draw.rectangle((
+ bH + j * bF, vH+bQ, bH + j * bF + bF, vH + bQ -
+ spectrum[j * 4] * bP - bH), fill=color2)
+
+ draw.rectangle((
+ bH + bQ + j * bF, vH, bH + bQ + j * bF + bH, vH -
+ spectrum[j * 4] * bP), fill=color)
+
+ imBottom = imTop.transpose(Image.FLIP_TOP_BOTTOM)
+
+ im = self.blankFrame(width, height)
+
+ if layout == 0:
+ y = 0 - int(height/100*43)
+ im.paste(imTop, (0, y), mask=imTop)
+ y = 0 + int(height/100*43)
+ im.paste(imBottom, (0, y), mask=imBottom)
+
+ if layout == 1:
+ y = 0 + int(height/100*10)
+ im.paste(imTop, (0, y), mask=imTop)
+ y = 0 - int(height/100*10)
+ im.paste(imBottom, (0, y), mask=imBottom)
+
+ if layout == 2:
+ y = 0 + int(height/100*10)
+ im.paste(imTop, (0, y), mask=imTop)
+
+ return im
+
+ def command(self, arg):
+ if not arg.startswith('preset=') and '=' in arg:
+ key, arg = arg.split('=', 1)
+ if key == 'color':
+ self.page.lineEdit_visColor.setText(arg)
+ return
+ elif key == 'layout':
+ if arg == 'classic':
+ self.page.comboBox_visLayout.setCurrentIndex(0)
+ elif arg == 'split':
+ self.page.comboBox_visLayout.setCurrentIndex(1)
+ elif arg == 'bottom':
+ self.page.comboBox_visLayout.setCurrentIndex(2)
+ return
+ super().command(arg)
+
+ def commandHelp(self):
+ print('Give a layout name:\n layout=[classic/split/bottom]')
+ print('Specify a color:\n color=255,255,255')
diff --git a/src/components/original.ui b/src/components/original.ui
new file mode 100644
index 0000000..5808653
--- /dev/null
+++ b/src/components/original.ui
@@ -0,0 +1,108 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 633
+ 178
+
+
+
+
+ 180
+ 0
+
+
+
+ Form
+
+
+ -
+
+
+ 4
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Visualizer Layout
+
+
+
+ -
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+ Visualizer Color
+
+
+
+ -
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
diff --git a/src/components/text.py b/src/components/text.py
new file mode 100644
index 0000000..0f599ed
--- /dev/null
+++ b/src/components/text.py
@@ -0,0 +1,176 @@
+from PIL import Image, ImageDraw
+from PyQt5.QtGui import QPainter, QColor, QFont
+from PyQt5 import uic, QtGui, QtCore
+from PIL.ImageQt import ImageQt
+import os
+import io
+from . import __base__
+
+
+class Component(__base__.Component):
+ '''Title Text'''
+
+ modified = QtCore.pyqtSignal(int, dict)
+
+ def __init__(self, *args):
+ super().__init__(*args)
+ self.titleFont = QFont()
+
+ def widget(self, parent):
+ height = int(parent.settings.value('outputHeight'))
+ width = int(parent.settings.value('outputWidth'))
+
+ self.parent = parent
+ self.textColor = (255, 255, 255)
+ self.title = 'Text'
+ self.alignment = 1
+ self.fontSize = height / 13.5
+ fm = QtGui.QFontMetrics(self.titleFont)
+ self.xPosition = width / 2 - fm.width(self.title)/2
+ self.yPosition = height / 2 * 1.036
+
+ page = uic.loadUi(os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), 'text.ui'))
+ page.comboBox_textAlign.addItem("Left")
+ page.comboBox_textAlign.addItem("Middle")
+ page.comboBox_textAlign.addItem("Right")
+
+ page.lineEdit_textColor.setText('%s,%s,%s' % self.textColor)
+ page.pushButton_textColor.clicked.connect(self.pickColor)
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" \
+ % QColor(*self.textColor).name()
+ page.pushButton_textColor.setStyleSheet(btnStyle)
+
+ page.lineEdit_title.setText(self.title)
+ page.comboBox_textAlign.setCurrentIndex(int(self.alignment))
+ page.spinBox_fontSize.setValue(int(self.fontSize))
+ page.spinBox_xTextAlign.setValue(int(self.xPosition))
+ page.spinBox_yTextAlign.setValue(int(self.yPosition))
+
+ page.fontComboBox_titleFont.currentFontChanged.connect(self.update)
+ page.lineEdit_title.textChanged.connect(self.update)
+ page.comboBox_textAlign.currentIndexChanged.connect(self.update)
+ page.spinBox_xTextAlign.valueChanged.connect(self.update)
+ page.spinBox_yTextAlign.valueChanged.connect(self.update)
+ page.spinBox_fontSize.valueChanged.connect(self.update)
+ page.lineEdit_textColor.textChanged.connect(self.update)
+ self.page = page
+ return page
+
+ def update(self):
+ self.title = self.page.lineEdit_title.text()
+ self.alignment = self.page.comboBox_textAlign.currentIndex()
+ self.titleFont = self.page.fontComboBox_titleFont.currentFont()
+ self.fontSize = self.page.spinBox_fontSize.value()
+ self.xPosition = self.page.spinBox_xTextAlign.value()
+ self.yPosition = self.page.spinBox_yTextAlign.value()
+ self.textColor = self.RGBFromString(
+ self.page.lineEdit_textColor.text())
+ self.parent.drawPreview()
+ super().update()
+
+ def getXY(self):
+ '''Returns true x, y after considering alignment settings'''
+ fm = QtGui.QFontMetrics(self.titleFont)
+ if self.alignment == 0: # Left
+ x = self.xPosition
+
+ if self.alignment == 1: # Middle
+ offset = fm.width(self.title)/2
+ x = self.xPosition - offset
+
+ if self.alignment == 2: # Right
+ offset = fm.width(self.title)
+ x = self.xPosition - offset
+ return x, self.yPosition
+
+ def loadPreset(self, pr, presetName=None):
+ super().loadPreset(pr, presetName)
+
+ self.page.lineEdit_title.setText(pr['title'])
+ font = QFont()
+ font.fromString(pr['titleFont'])
+ self.page.fontComboBox_titleFont.setCurrentFont(font)
+ self.page.spinBox_fontSize.setValue(pr['fontSize'])
+ self.page.comboBox_textAlign.setCurrentIndex(pr['alignment'])
+ self.page.spinBox_xTextAlign.setValue(pr['xPosition'])
+ self.page.spinBox_yTextAlign.setValue(pr['yPosition'])
+ self.page.lineEdit_textColor.setText('%s,%s,%s' % pr['textColor'])
+ btnStyle = "QPushButton { background-color : %s; outline: none; }" \
+ % QColor(*pr['textColor']).name()
+ self.page.pushButton_textColor.setStyleSheet(btnStyle)
+
+ def savePreset(self):
+ return {
+ 'preset': self.currentPreset,
+ 'title': self.title,
+ 'titleFont': self.titleFont.toString(),
+ 'alignment': self.alignment,
+ 'fontSize': self.fontSize,
+ 'xPosition': self.xPosition,
+ 'yPosition': self.yPosition,
+ 'textColor': self.textColor
+ }
+
+ def previewRender(self, previewWorker):
+ width = int(previewWorker.core.settings.value('outputWidth'))
+ height = int(previewWorker.core.settings.value('outputHeight'))
+ return self.addText(width, height)
+
+ def preFrameRender(self, **kwargs):
+ super().preFrameRender(**kwargs)
+ return ['static']
+
+ def frameRender(self, moduleNo, arrayNo, frameNo):
+ width = int(self.worker.core.settings.value('outputWidth'))
+ height = int(self.worker.core.settings.value('outputHeight'))
+ return self.addText(width, height)
+
+ def addText(self, width, height):
+ x, y = self.getXY()
+ im = self.blankFrame(width, height)
+ image = ImageQt(im)
+
+ painter = QPainter(image)
+ self.titleFont.setPixelSize(self.fontSize)
+ painter.setFont(self.titleFont)
+ painter.setPen(QColor(*self.textColor))
+ painter.drawText(x, y, self.title)
+ painter.end()
+
+ imBytes = image.bits().asstring(image.numBytes())
+
+ return Image.frombytes('RGBA', (width, height), imBytes)
+
+ def pickColor(self):
+ RGBstring, btnStyle = super().pickColor()
+ if not RGBstring:
+ return
+ self.page.lineEdit_textColor.setText(RGBstring)
+ self.page.pushButton_textColor.setStyleSheet(btnStyle)
+
+ def commandHelp(self):
+ print('Enter a string to use as centred white text:')
+ print(' "title=User Error"')
+ print('Specify a text color:\n color=255,255,255')
+ print('Set custom x, y position:\n x=500 y=500')
+
+ def command(self, arg):
+ if not arg.startswith('preset=') and '=' in arg:
+ key, arg = arg.split('=', 1)
+ if key == 'color':
+ self.page.lineEdit_textColor.setText(arg)
+ return
+ elif key == 'size':
+ self.page.spinBox_fontSize.setValue(int(arg))
+ return
+ elif key == 'x':
+ self.page.spinBox_xTextAlign.setValue(int(arg))
+ return
+ elif key == 'y':
+ self.page.spinBox_yTextAlign.setValue(int(arg))
+ return
+ elif key == 'title':
+ self.page.lineEdit_title.setText(arg)
+ return
+ super().command(arg)
diff --git a/src/components/text.ui b/src/components/text.ui
new file mode 100644
index 0000000..05e7f8e
--- /dev/null
+++ b/src/components/text.ui
@@ -0,0 +1,316 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 586
+ 197
+
+
+
+ Form
+
+
+ -
+
+
+ 4
+
+
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Font
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Font Size
+
+
+
+ -
+
+
+ 500
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Text Layout
+
+
+
+ -
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+ Text Color
+
+
+
+ -
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+ Title
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+ Testing New GUI
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ X
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 0
+ 0
+
+
+
+ 0
+
+
+ 999999999
+
+
+ 0
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Y
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+ 999999999
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
diff --git a/src/components/video.py b/src/components/video.py
new file mode 100644
index 0000000..58ce7a3
--- /dev/null
+++ b/src/components/video.py
@@ -0,0 +1,273 @@
+from PIL import Image, ImageDraw
+from PyQt5 import uic, QtGui, QtCore
+import os
+import subprocess
+import threading
+from queue import PriorityQueue
+from . import __base__
+
+
+class Video:
+ '''Video Component Frame-Fetcher'''
+ def __init__(self, **kwargs):
+ mandatoryArgs = [
+ 'ffmpeg', # path to ffmpeg, usually core.FFMPEG_BIN
+ 'videoPath',
+ 'width',
+ 'height',
+ 'scale', # percentage scale
+ 'frameRate', # frames per second
+ 'chunkSize', # number of bytes in one frame
+ 'parent', # mainwindow object
+ 'component', # component object
+ ]
+ for arg in mandatoryArgs:
+ try:
+ exec('self.%s = kwargs[arg]' % arg)
+ except KeyError:
+ raise __base__.BadComponentInit(arg, self.__doc__)
+
+ self.frameNo = -1
+ self.currentFrame = 'None'
+ if 'loopVideo' in kwargs and kwargs['loopVideo']:
+ self.loopValue = '-1'
+ else:
+ self.loopValue = '0'
+ self.command = [
+ self.ffmpeg,
+ '-thread_queue_size', '512',
+ '-r', str(self.frameRate),
+ '-stream_loop', self.loopValue,
+ '-i', self.videoPath,
+ '-f', 'image2pipe',
+ '-pix_fmt', 'rgba',
+ '-filter:v', 'scale=%s:%s' %
+ scale(self.scale, self.width, self.height, str),
+ '-vcodec', 'rawvideo', '-',
+ ]
+
+ self.frameBuffer = PriorityQueue()
+ self.frameBuffer.maxsize = self.frameRate
+ self.finishedFrames = {}
+
+ self.thread = threading.Thread(
+ target=self.fillBuffer,
+ name=self.__doc__
+ )
+ self.thread.daemon = True
+ self.thread.start()
+
+ def frame(self, num):
+ while True:
+ if num in self.finishedFrames:
+ image = self.finishedFrames.pop(num)
+ return finalizeFrame(
+ self.component, image, self.width, self.height)
+
+ i, image = self.frameBuffer.get()
+ self.finishedFrames[i] = image
+ self.frameBuffer.task_done()
+
+ def fillBuffer(self):
+ pipe = subprocess.Popen(
+ self.command, stdout=subprocess.PIPE,
+ stderr=subprocess.DEVNULL, bufsize=10**8
+ )
+ while True:
+ if self.parent.canceled:
+ break
+ self.frameNo += 1
+
+ # If we run out of frames, use the last good frame and loop.
+ if len(self.currentFrame) == 0:
+ self.frameBuffer.put((self.frameNo-1, self.lastFrame))
+ continue
+
+ self.currentFrame = pipe.stdout.read(self.chunkSize)
+ if len(self.currentFrame) != 0:
+ self.frameBuffer.put((self.frameNo, self.currentFrame))
+ self.lastFrame = self.currentFrame
+
+
+class Component(__base__.Component):
+ '''Video'''
+
+ modified = QtCore.pyqtSignal(int, dict)
+
+ def widget(self, parent):
+ self.parent = parent
+ self.settings = parent.settings
+ page = uic.loadUi(os.path.join(
+ os.path.dirname(os.path.realpath(__file__)),
+ 'video.ui'
+ ))
+ self.videoPath = ''
+ self.x = 0
+ self.y = 0
+ self.loopVideo = False
+
+ page.lineEdit_video.textChanged.connect(self.update)
+ page.pushButton_video.clicked.connect(self.pickVideo)
+ page.checkBox_loop.stateChanged.connect(self.update)
+ page.checkBox_distort.stateChanged.connect(self.update)
+ page.spinBox_scale.valueChanged.connect(self.update)
+ page.spinBox_x.valueChanged.connect(self.update)
+ page.spinBox_y.valueChanged.connect(self.update)
+
+ self.page = page
+ return page
+
+ def update(self):
+ self.videoPath = self.page.lineEdit_video.text()
+ self.loopVideo = self.page.checkBox_loop.isChecked()
+ self.distort = self.page.checkBox_distort.isChecked()
+ self.scale = self.page.spinBox_scale.value()
+ self.xPosition = self.page.spinBox_x.value()
+ self.yPosition = self.page.spinBox_y.value()
+ self.parent.drawPreview()
+ super().update()
+
+ def previewRender(self, previewWorker):
+ self.videoFormats = previewWorker.core.videoFormats
+ width = int(previewWorker.core.settings.value('outputWidth'))
+ height = int(previewWorker.core.settings.value('outputHeight'))
+ self.updateChunksize(width, height)
+ frame = self.getPreviewFrame(width, height)
+ if not frame:
+ return self.blankFrame(width, height)
+ else:
+ return frame
+
+ def preFrameRender(self, **kwargs):
+ super().preFrameRender(**kwargs)
+ width = int(self.worker.core.settings.value('outputWidth'))
+ height = int(self.worker.core.settings.value('outputHeight'))
+ self.blankFrame_ = self.blankFrame(width, height)
+ self.updateChunksize(width, height)
+ self.video = Video(
+ ffmpeg=self.parent.core.FFMPEG_BIN, videoPath=self.videoPath,
+ width=width, height=height, chunkSize=self.chunkSize,
+ frameRate=int(self.settings.value("outputFrameRate")),
+ parent=self.parent, loopVideo=self.loopVideo,
+ component=self, scale=self.scale
+ ) if os.path.exists(self.videoPath) else None
+
+ def frameRender(self, moduleNo, arrayNo, frameNo):
+ if self.video:
+ return self.video.frame(frameNo)
+ else:
+ return self.blankFrame_
+
+ def loadPreset(self, pr, presetName=None):
+ super().loadPreset(pr, presetName)
+ self.page.lineEdit_video.setText(pr['video'])
+ self.page.checkBox_loop.setChecked(pr['loop'])
+ self.page.checkBox_distort.setChecked(pr['distort'])
+ self.page.spinBox_scale.setValue(pr['scale'])
+ self.page.spinBox_x.setValue(pr['x'])
+ self.page.spinBox_y.setValue(pr['y'])
+
+ def savePreset(self):
+ return {
+ 'preset': self.currentPreset,
+ 'video': self.videoPath,
+ 'loop': self.loopVideo,
+ 'distort': self.distort,
+ 'scale': self.scale,
+ 'x': self.xPosition,
+ 'y': self.yPosition,
+ }
+
+ def pickVideo(self):
+ imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
+ filename = QtGui.QFileDialog.getOpenFileName(
+ self.page, "Choose Video",
+ imgDir, "Video Files (%s)" % " ".join(self.videoFormats)
+ )
+ if filename:
+ self.settings.setValue("backgroundDir", os.path.dirname(filename))
+ self.page.lineEdit_video.setText(filename)
+ self.update()
+
+ def getPreviewFrame(self, width, height):
+ if not self.videoPath or not os.path.exists(self.videoPath):
+ return
+
+ command = [
+ self.parent.core.FFMPEG_BIN,
+ '-thread_queue_size', '512',
+ '-i', self.videoPath,
+ '-f', 'image2pipe',
+ '-pix_fmt', 'rgba',
+ '-filter:v', 'scale=%s:%s' %
+ scale(self.scale, width, height, str),
+ '-vcodec', 'rawvideo', '-',
+ '-ss', '90',
+ '-vframes', '1',
+ ]
+ pipe = subprocess.Popen(
+ command, stdout=subprocess.PIPE,
+ stderr=subprocess.DEVNULL, bufsize=10**8
+ )
+ byteFrame = pipe.stdout.read(self.chunkSize)
+ frame = finalizeFrame(self, byteFrame, width, height)
+ pipe.stdout.close()
+ pipe.kill()
+
+ return frame
+
+ def updateChunksize(self, width, height):
+ if self.scale != 100 and not self.distort:
+ width, height = scale(self.scale, width, height, int)
+ self.chunkSize = 4*width*height
+
+ def command(self, arg):
+ if not arg.startswith('preset=') and '=' in arg:
+ key, arg = arg.split('=', 1)
+ if key == 'path' and os.path.exists(arg):
+ if os.path.splitext(arg)[1] in self.core.videoFormats:
+ self.page.lineEdit_video.setText(arg)
+ self.page.spinBox_scale.setValue(100)
+ self.page.checkBox_loop.setChecked(True)
+ return
+ else:
+ print("Not a supported video format")
+ quit(1)
+ super().command(arg)
+
+ def commandHelp(self):
+ print('Load a video:\n path=/filepath/to/video.mp4')
+
+def scale(scale, width, height, returntype=None):
+ width = (float(width) / 100.0) * float(scale)
+ height = (float(height) / 100.0) * float(scale)
+ if returntype == str:
+ return (str(int(width)), str(int(height)))
+ elif returntype == int:
+ return (int(width), int(height))
+ else:
+ return (width, height)
+
+def finalizeFrame(self, imageData, width, height):
+ if self.distort:
+ try:
+ image = Image.frombytes(
+ 'RGBA',
+ (width, height),
+ imageData)
+ except ValueError:
+ print('#### ignored invalid data caused by distortion ####')
+ image = self.blankFrame(width, height)
+ else:
+ image = Image.frombytes(
+ 'RGBA',
+ scale(self.scale, width, height, int),
+ imageData)
+
+ if self.scale != 100 \
+ or self.xPosition != 0 or self.yPosition != 0:
+ frame = self.blankFrame(width, height)
+ frame.paste(image, box=(self.xPosition, self.yPosition))
+ else:
+ frame = image
+ return frame
diff --git a/src/components/video.ui b/src/components/video.ui
new file mode 100644
index 0000000..f05e8a5
--- /dev/null
+++ b/src/components/video.ui
@@ -0,0 +1,266 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 586
+ 197
+
+
+
+ Form
+
+
+ -
+
+
+ 4
+
+
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 31
+ 0
+
+
+
+ Video
+
+
+
+ -
+
+
+
+ 1
+ 0
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 1
+ 0
+
+
+
+
+ 32
+ 32
+
+
+
+ ...
+
+
+
+ 32
+ 32
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ X
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+ -10000
+
+
+ 10000
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Y
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 0
+ 0
+
+
+
+ -10000
+
+
+ 10000
+
+
+ 0
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Loop
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 20
+
+
+
+
+ -
+
+
+ Distort by scale
+
+
+
+ -
+
+
+ Scale
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ QAbstractSpinBox::UpDownArrows
+
+
+ %
+
+
+ 10
+
+
+ 400
+
+
+ 100
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+
+
+
+
+
diff --git a/src/core.py b/src/core.py
new file mode 100644
index 0000000..bb5d351
--- /dev/null
+++ b/src/core.py
@@ -0,0 +1,477 @@
+import sys
+import io
+import os
+from PyQt5 import QtCore, QtGui, uic
+from os.path import expanduser
+import subprocess as sp
+import numpy
+from PIL import Image
+from shutil import rmtree
+import time
+from collections import OrderedDict
+import json
+from importlib import import_module
+from PyQt5.QtCore import QStandardPaths
+import string
+
+
+class Core():
+
+ def __init__(self):
+ self.FFMPEG_BIN = self.findFfmpeg()
+ self.dataDir = QStandardPaths.writableLocation(
+ QStandardPaths.AppConfigLocation
+ )
+ self.presetDir = os.path.join(self.dataDir, 'presets')
+ if getattr(sys, 'frozen', False):
+ # frozen
+ self.wd = os.path.dirname(sys.executable)
+ else:
+ # unfrozen
+ self.wd = os.path.dirname(os.path.realpath(__file__))
+
+ self.loadEncoderOptions()
+ self.videoFormats = Core.appendUppercase([
+ '*.mp4',
+ '*.mov',
+ '*.mkv',
+ '*.avi',
+ '*.webm',
+ '*.flv',
+ ])
+ self.audioFormats = Core.appendUppercase([
+ '*.mp3',
+ '*.wav',
+ '*.ogg',
+ '*.fla',
+ '*.flac',
+ '*.aac',
+ ])
+ self.imageFormats = Core.appendUppercase([
+ '*.png',
+ '*.jpg',
+ '*.tif',
+ '*.tiff',
+ '*.gif',
+ '*.bmp',
+ '*.ico',
+ '*.xbm',
+ '*.xpm',
+ ])
+
+ self.findComponents()
+ self.selectedComponents = []
+ # copies of named presets to detect modification
+ self.savedPresets = {}
+
+ def findComponents(self):
+ def findComponents():
+ srcPath = os.path.join(self.wd, 'components')
+ if os.path.exists(srcPath):
+ for f in sorted(os.listdir(srcPath)):
+ name, ext = os.path.splitext(f)
+ if name.startswith("__"):
+ continue
+ elif ext == '.py':
+ yield name
+ self.modules = [
+ import_module('components.%s' % name)
+ for name in findComponents()
+ ]
+ self.moduleIndexes = [i for i in range(len(self.modules))]
+ self.compNames = [mod.Component.__doc__ for mod in self.modules]
+
+ def componentListChanged(self):
+ for i, component in enumerate(self.selectedComponents):
+ component.compPos = i
+
+ def insertComponent(self, compPos, moduleIndex, loader):
+ '''Creates a new component'''
+ if compPos < 0 or compPos > len(self.selectedComponents):
+ compPos = len(self.selectedComponents)
+ if len(self.selectedComponents) > 50:
+ return None
+
+ component = self.modules[moduleIndex].Component(
+ moduleIndex, compPos, self)
+ self.selectedComponents.insert(
+ compPos,
+ component)
+ self.componentListChanged()
+
+ # init component's widget for loading/saving presets
+ self.selectedComponents[compPos].widget(loader)
+ self.updateComponent(compPos)
+
+ if hasattr(loader, 'insertComponent'):
+ loader.insertComponent(compPos)
+ return compPos
+
+ def moveComponent(self, startI, endI):
+ comp = self.selectedComponents.pop(startI)
+ self.selectedComponents.insert(endI, comp)
+
+ self.componentListChanged()
+ return endI
+
+ def removeComponent(self, i):
+ self.selectedComponents.pop(i)
+ self.componentListChanged()
+
+ def clearComponents(self):
+ self.selectedComponents = list()
+ self.componentListChanged()
+
+ def updateComponent(self, i):
+ # print('updating %s' % self.selectedComponents[i])
+ self.selectedComponents[i].update()
+
+ def moduleIndexFor(self, compName):
+ index = self.compNames.index(compName)
+ return self.moduleIndexes[index]
+
+ def clearPreset(self, compIndex):
+ self.selectedComponents[compIndex].currentPreset = None
+
+ def openPreset(self, filepath, compIndex, presetName):
+ '''Applies a preset to a specific component'''
+ saveValueStore = self.getPreset(filepath)
+ if not saveValueStore:
+ return False
+ try:
+ self.selectedComponents[compIndex].loadPreset(
+ saveValueStore,
+ presetName
+ )
+ except KeyError as e:
+ print('preset missing value: %s' % e)
+
+ self.savedPresets[presetName] = dict(saveValueStore)
+ return True
+
+ def getPresetDir(self, comp):
+ return os.path.join(
+ self.presetDir, str(comp), str(comp.version()))
+
+ def getPreset(self, filepath):
+ '''Returns the preset dict stored at this filepath'''
+ if not os.path.exists(filepath):
+ return False
+ with open(filepath, 'r') as f:
+ for line in f:
+ saveValueStore = Core.presetFromString(line.strip())
+ break
+ return saveValueStore
+
+ def openProject(self, loader, filepath):
+ ''' loader is the object calling this method which must have
+ its own showMessage(**kwargs) method for displaying errors.
+ '''
+ if not os.path.exists(filepath):
+ loader.showMessage(msg='Project file not found')
+ return
+
+ errcode, data = self.parseAvFile(filepath)
+ if errcode == 0:
+ try:
+ for i, tup in enumerate(data['Components']):
+ name, vers, preset = tup
+ clearThis = False
+
+ # add loaded named presets to savedPresets dict
+ if 'preset' in preset and preset['preset'] != None:
+ nam = preset['preset']
+ filepath2 = os.path.join(
+ self.presetDir, name, str(vers), nam)
+ origSaveValueStore = self.getPreset(filepath2)
+ if origSaveValueStore:
+ self.savedPresets[nam] = dict(origSaveValueStore)
+ else:
+ # saved preset was renamed or deleted
+ clearThis = True
+
+ # create the actual component object & get its index
+ i = self.insertComponent(
+ -1,
+ self.moduleIndexFor(name),
+ loader)
+ if i == None:
+ loader.showMessage(msg="Too many components!")
+ break
+
+ try:
+ if 'preset' in preset and preset['preset'] != None:
+ self.selectedComponents[i].loadPreset(
+ preset
+ )
+ else:
+ self.selectedComponents[i].loadPreset(
+ preset,
+ preset['preset']
+ )
+ except KeyError as e:
+ print('%s missing value %s' %
+ (self.selectedComponents[i], e))
+
+ if clearThis:
+ self.clearPreset(i)
+ if hasattr(loader, 'updateComponentTitle'):
+ loader.updateComponentTitle(i)
+ except:
+ errcode = 1
+ data = sys.exc_info()
+
+
+ if errcode == 1:
+ typ, value, _ = data
+ if typ.__name__ == KeyError:
+ # probably just an old version, still loadable
+ print('file missing value: %s' % value)
+ return
+ if hasattr(loader, 'createNewProject'):
+ loader.createNewProject()
+ msg = '%s: %s' % (typ.__name__, value)
+ loader.showMessage(
+ msg="Project file '%s' is corrupted." % filepath,
+ showCancel=False,
+ icon=QtGui.QMessageBox.Warning,
+ detail=msg)
+
+ def parseAvFile(self, filepath):
+ '''Parses an avp (project) or avl (preset package) file.
+ Returns dictionary with section names as the keys, each one
+ contains a list of tuples: (compName, version, compPresetDict)
+ '''
+ data = {}
+ try:
+ with open(filepath, 'r') as f:
+ def parseLine(line):
+ '''Decides if a file line is a section header'''
+ validSections = ('Components')
+ line = line.strip()
+ newSection = ''
+
+ if line.startswith('[') and line.endswith(']') \
+ and line[1:-1] in validSections:
+ newSection = line[1:-1]
+
+ return line, newSection
+
+ section = ''
+ i = 0
+ for line in f:
+ line, newSection = parseLine(line)
+ if newSection:
+ section = str(newSection)
+ data[section] = []
+ continue
+ if line and section == 'Components':
+ if i == 0:
+ lastCompName = str(line)
+ i += 1
+ elif i == 1:
+ lastCompVers = str(line)
+ i += 1
+ elif i == 2:
+ lastCompPreset = Core.presetFromString(line)
+ data[section].append(
+ (lastCompName,
+ lastCompVers,
+ lastCompPreset)
+ )
+ i = 0
+ return 0, data
+ except:
+ return 1, sys.exc_info()
+
+ def importPreset(self, filepath):
+ errcode, data = self.parseAvFile(filepath)
+ returnList = []
+ if errcode == 0:
+ name, vers, preset = data['Components'][0]
+ presetName = preset['preset'] \
+ if preset['preset'] else os.path.basename(filepath)[:-4]
+ newPath = os.path.join(
+ self.presetDir,
+ name,
+ vers,
+ presetName
+ )
+ if os.path.exists(newPath):
+ return False, newPath
+ preset['preset'] = presetName
+ self.createPresetFile(
+ name, vers, presetName, preset
+ )
+ return True, presetName
+ elif errcode == 1:
+ # TODO: an error message
+ return False, ''
+
+ def exportPreset(self, exportPath, compName, vers, origName):
+ internalPath = os.path.join(self.presetDir, compName, str(vers), origName)
+ if not os.path.exists(internalPath):
+ return
+ if os.path.exists(exportPath):
+ os.remove(exportPath)
+ with open(internalPath, 'r') as f:
+ internalData = [line for line in f]
+ try:
+ saveValueStore = Core.presetFromString(internalData[0].strip())
+ self.createPresetFile(
+ compName, vers,
+ origName, saveValueStore,
+ exportPath
+ )
+ return True
+ except:
+ return False
+
+ def createPresetFile(
+ self, compName, vers, presetName, saveValueStore, filepath=''):
+ '''Create a preset file (.avl) at filepath using args.
+ Or if filepath is empty, create an internal preset using args'''
+ if not filepath:
+ dirname = os.path.join(self.presetDir, compName, str(vers))
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+ filepath = os.path.join(dirname, presetName)
+ internal = True
+ else:
+ if not filepath.endswith('.avl'):
+ filepath += '.avl'
+ internal = False
+
+ with open(filepath, 'w') as f:
+ if not internal:
+ f.write('[Components]\n')
+ f.write('%s\n' % compName)
+ f.write('%s\n' % str(vers))
+ f.write(Core.presetToString(saveValueStore))
+
+ def createProjectFile(self, filepath):
+ '''Create a project file (.avp) using the current program state'''
+ try:
+ if not filepath.endswith(".avp"):
+ filepath += '.avp'
+ if os.path.exists(filepath):
+ os.remove(filepath)
+ with open(filepath, 'w') as f:
+ print('creating %s' % filepath)
+ f.write('[Components]\n')
+ for comp in self.selectedComponents:
+ saveValueStore = comp.savePreset()
+ f.write('%s\n' % str(comp))
+ f.write('%s\n' % str(comp.version()))
+ f.write('%s\n' % Core.presetToString(saveValueStore))
+ return True
+ except:
+ return False
+
+ def loadEncoderOptions(self):
+ file_path = os.path.join(self.wd, 'encoder-options.json')
+ with open(file_path) as json_file:
+ self.encoder_options = json.load(json_file)
+
+ def findFfmpeg(self):
+ if sys.platform == "win32":
+ return "ffmpeg.exe"
+ else:
+ try:
+ with open(os.devnull, "w") as f:
+ sp.check_call(['ffmpeg', '-version'], stdout=f, stderr=f)
+ return "ffmpeg"
+ except:
+ return "avconv"
+
+ def readAudioFile(self, filename, parent):
+ command = [self.FFMPEG_BIN, '-i', filename]
+
+ try:
+ fileInfo = sp.check_output(command, stderr=sp.STDOUT, shell=False)
+ except sp.CalledProcessError as ex:
+ fileInfo = ex.output
+ pass
+
+ info = fileInfo.decode("utf-8").split('\n')
+ for line in info:
+ if 'Duration' in line:
+ d = line.split(',')[0]
+ d = d.split(' ')[3]
+ d = d.split(':')
+ duration = float(d[0])*3600 + float(d[1])*60 + float(d[2])
+
+ command = [
+ self.FFMPEG_BIN,
+ '-i', filename,
+ '-f', 's16le',
+ '-acodec', 'pcm_s16le',
+ '-ar', '44100', # ouput will have 44100 Hz
+ '-ac', '1', # mono (set to '2' for stereo)
+ '-']
+ in_pipe = sp.Popen(
+ command, stdout=sp.PIPE, stderr=sp.DEVNULL, bufsize=10**8)
+
+ completeAudioArray = numpy.empty(0, dtype="int16")
+
+ progress = 0
+ lastPercent = None
+ while True:
+ if self.canceled:
+ break
+ # read 2 seconds of audio
+ progress = progress + 4
+ raw_audio = in_pipe.stdout.read(88200*4)
+ if len(raw_audio) == 0:
+ break
+ audio_array = numpy.fromstring(raw_audio, dtype="int16")
+ completeAudioArray = numpy.append(completeAudioArray, audio_array)
+
+ percent = int(100*(progress/duration))
+ if percent >= 100:
+ percent = 100
+
+ if lastPercent != percent:
+ string = 'Loading audio file: '+str(percent)+'%'
+ parent.progressBarSetText.emit(string)
+ parent.progressBarUpdate.emit(percent)
+
+ lastPercent = percent
+
+ in_pipe.kill()
+ in_pipe.wait()
+
+ # add 0s the end
+ completeAudioArrayCopy = numpy.zeros(
+ len(completeAudioArray) + 44100, dtype="int16")
+ completeAudioArrayCopy[:len(completeAudioArray)] = completeAudioArray
+ completeAudioArray = completeAudioArrayCopy
+
+ return completeAudioArray
+
+ def cancel(self):
+ self.canceled = True
+
+ def reset(self):
+ self.canceled = False
+
+ @staticmethod
+ def badName(name):
+ '''Returns whether a name contains non-alphanumeric chars'''
+ return any([letter in string.punctuation for letter in name])
+
+ @staticmethod
+ def presetToString(dictionary):
+ '''Alphabetizes a dict into OrderedDict & returns string repr'''
+ return repr(OrderedDict(sorted(dictionary.items(), key=lambda t: t[0])))
+
+ @staticmethod
+ def presetFromString(string):
+ '''Turns a string repr of OrderedDict into a regular dict'''
+ return dict(eval(string))
+
+ @staticmethod
+ def appendUppercase(lst):
+ for form, i in zip(lst, range(len(lst))):
+ lst.append(form.upper())
+ return lst
diff --git a/src/encoder-options.json b/src/encoder-options.json
new file mode 100644
index 0000000..78bc940
--- /dev/null
+++ b/src/encoder-options.json
@@ -0,0 +1,130 @@
+{
+ "containers":[
+ {
+ "name": "MP4",
+ "container": "mp4",
+ "default-vcodec": "H264",
+ "default-acodec": "AAC",
+ "video-codecs": [
+ "H264",
+ "H264 (nvenc)",
+ "MPEG4"
+ ],
+ "audio-codecs": [
+ "AAC",
+ "AC3",
+ "MP3"
+ ]
+ },
+ {
+ "name": "MOV",
+ "container": "mov",
+ "default-vcodec": "H264",
+ "default-acodec": "AAC",
+ "video-codecs": [
+ "H264",
+ "H264 (nvenc)",
+ "MPEG4",
+ "XVID"
+ ],
+ "audio-codecs": [
+ "AAC",
+ "AC3",
+ "MP3",
+ "PCM s16 LE"
+ ]
+ },
+ {
+ "name": "MKV",
+ "container": "matroska",
+ "default-vcodec": "H264",
+ "default-acodec": "AAC",
+ "video-codecs": [
+ "H264",
+ "H264 (nvenc)",
+ "MPEG4",
+ "MPEG2",
+ "DV",
+ "WMV"
+ ],
+ "audio-codecs": [
+ "AAC",
+ "AC3",
+ "MP3",
+ "PCM s16 LE",
+ "WMA"
+ ]
+ },
+ {
+ "name": "AVI",
+ "container": "avi",
+ "default-vcodec": "H264",
+ "default-acodec": "AAC",
+ "video-codecs": [
+ "H264",
+ "H264 (nvenc)",
+ "MPEG4",
+ "MPEG2",
+ "DV",
+ "WMV"
+ ],
+ "audio-codecs": [
+ "AAC",
+ "AC3",
+ "MP3",
+ "PCM s16 LE",
+ "WMA"
+ ]
+ },
+ {
+ "name": "WEBM",
+ "container": "webm",
+ "default-vcodec": "VP9",
+ "default-acodec": "Vorbis",
+ "video-codecs": [
+ "VP9",
+ "VP8"
+ ],
+ "audio-codecs": [
+ "Vorbis"
+ ]
+ },
+ {
+ "name": "FLV",
+ "container": "flv",
+ "default-vcodec": "FLV",
+ "default-acodec": "Vorbis",
+ "video-codecs": [
+ "Sorenson (flv)",
+ "H264",
+ "H264 (nvenc)",
+ "MPEG4"
+ ],
+ "audio-codecs": [
+ "MP3",
+ "PCM s16 LE",
+ "Vorbis"
+ ]
+ }
+ ],
+ "video-codecs":{
+ "H264": ["libx264"],
+ "H264 (nvenc)": ["h264_nvenc", "nvenc_h264"],
+ "MPEG4": ["mpeg4"],
+ "VP9": ["libvpx-vp9"],
+ "VP8": ["libvpx"],
+ "XVID": ["libxvid"],
+ "Sorenson (flv)": ["flv"],
+ "MPEG2": ["mp2video"],
+ "DV": ["dvvideo"],
+ "WMV": ["wmv2"]
+ },
+ "audio-codecs": {
+ "AAC": ["libfdk_aac", "aac"],
+ "AC3": ["ac3"],
+ "MP3": ["libmp3lame"],
+ "PCM s16 LE": ["pcm_s16le"],
+ "WMA": ["wmav2"],
+ "Vorbis": ["libvorbis"]
+ }
+}
\ No newline at end of file
diff --git a/src/main.py b/src/main.py
new file mode 100644
index 0000000..4bf26db
--- /dev/null
+++ b/src/main.py
@@ -0,0 +1,88 @@
+from PyQt5 import QtGui, uic, QtWidgets
+import sys
+import os
+
+import core
+import preview_thread
+import video_thread
+
+
+def LoadDefaultSettings(self):
+ self.resolutions = [
+ '1920x1080',
+ '1280x720',
+ '854x480'
+ ]
+
+ default = {
+ "outputWidth": 1280,
+ "outputHeight": 720,
+ "outputFrameRate": 30,
+ "outputAudioCodec": "AAC",
+ "outputAudioBitrate": "192",
+ "outputVideoCodec": "H264",
+ "outputVideoBitrate": "2500",
+ "outputVideoFormat": "yuv420p",
+ "outputPreset": "medium",
+ "outputFormat": "mp4",
+ "outputContainer": "MP4",
+ "projectDir": os.path.join(self.dataDir, 'projects'),
+ }
+
+ for parm, value in default.items():
+ #print(parm, self.settings.value(parm))
+ if self.settings.value(parm) is None:
+ self.settings.setValue(parm, value)
+
+if __name__ == "__main__":
+ mode = 'gui'
+ if len(sys.argv) > 2:
+ mode = 'cmd'
+
+ elif len(sys.argv) == 2:
+ if sys.argv[1].startswith('-'):
+ mode = 'cmd'
+ else:
+ # opening a project file with gui
+ proj = sys.argv[1]
+ else:
+ # normal gui launch
+ proj = None
+
+ app = QtWidgets.QApplication(sys.argv)
+ app.setApplicationName("audio-visualizer")
+ app.setOrganizationName("audio-visualizer")
+
+ if mode == 'cmd':
+ from command import *
+
+ main = Command()
+
+ elif mode == 'gui':
+ from mainwindow import *
+ import atexit
+ import signal
+
+ if getattr(sys, 'frozen', False):
+ # frozen
+ wd = os.path.dirname(sys.executable)
+ else:
+ # unfrozen
+ wd = os.path.dirname(os.path.realpath(__file__))
+
+ window = uic.loadUi(os.path.join(wd, "mainwindow.ui"))
+ # window.adjustSize()
+ desc = QtWidgets.QDesktopWidget()
+ dpi = desc.physicalDpiX()
+
+ topMargin = 0 if (dpi == 96) else int(10 * (dpi / 96))
+ window.resize(window.width() * (dpi / 96), window.height() * (dpi / 96))
+ # window.verticalLayout_2.setContentsMargins(0, topMargin, 0, 0)
+
+ main = MainWindow(window, proj)
+
+ signal.signal(signal.SIGINT, main.cleanUp)
+ atexit.register(main.cleanUp)
+
+ # applicable to both modes
+ sys.exit(app.exec_())
diff --git a/src/mainwindow.py b/src/mainwindow.py
new file mode 100644
index 0000000..a52a0f4
--- /dev/null
+++ b/src/mainwindow.py
@@ -0,0 +1,718 @@
+from queue import Queue
+from PyQt5 import QtCore, QtGui, uic, QtWidgets
+from PyQt5.QtCore import QSettings, Qt
+from PyQt5.QtWidgets import QMenu, QShortcut
+import sys
+import os
+import signal
+import filecmp
+import time
+
+import core
+import preview_thread
+import video_thread
+from presetmanager import PresetManager
+from main import LoadDefaultSettings
+
+
+class PreviewWindow(QtWidgets.QLabel):
+ def __init__(self, parent, img):
+ super(PreviewWindow, self).__init__()
+ self.parent = parent
+ self.setFrameStyle(QtWidgets.QFrame.StyledPanel)
+ self.pixmap = QtGui.QPixmap(img)
+
+ def paintEvent(self, event):
+ size = self.size()
+ painter = QtGui.QPainter(self)
+ point = QtCore.QPoint(0, 0)
+ scaledPix = self.pixmap.scaled(
+ size, Qt.KeepAspectRatio, transformMode=Qt.SmoothTransformation)
+
+ # start painting the label from left upper corner
+ point.setX((size.width() - scaledPix.width())/2)
+ point.setY((size.height() - scaledPix.height())/2)
+ painter.drawPixmap(point, scaledPix)
+
+ def changePixmap(self, img):
+ self.pixmap = QtGui.QPixmap(img)
+ self.repaint()
+
+
+class MainWindow(QtWidgets.QMainWindow):
+
+ newTask = QtCore.pyqtSignal(list)
+ processTask = QtCore.pyqtSignal()
+ videoTask = QtCore.pyqtSignal(str, str, list)
+
+ def __init__(self, window, project):
+ QtWidgets.QMainWindow.__init__(self)
+
+ # print('main thread id: {}'.format(QtCore.QThread.currentThreadId()))
+ self.window = window
+ self.core = core.Core()
+
+ self.pages = [] # widgets of component settings
+ self.lastAutosave = time.time()
+
+ # Create data directory, load/create settings
+ self.dataDir = self.core.dataDir
+ self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
+ self.settings = QSettings(
+ os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat)
+ LoadDefaultSettings(self)
+ self.presetManager = PresetManager(
+ uic.loadUi(
+ os.path.join(self.core.wd, 'presetmanager.ui')), self)
+
+ if not os.path.exists(self.dataDir):
+ os.makedirs(self.dataDir)
+ for neededDirectory in (
+ self.core.presetDir, self.settings.value("projectDir")):
+ if not os.path.exists(neededDirectory):
+ os.mkdir(neededDirectory)
+
+ # Make queues/timers for the preview thread
+ self.previewQueue = Queue()
+ self.previewThread = QtCore.QThread(self)
+ self.previewWorker = preview_thread.Worker(self, self.previewQueue)
+ self.previewWorker.moveToThread(self.previewThread)
+ self.previewWorker.imageCreated.connect(self.showPreviewImage)
+ self.previewThread.start()
+
+ self.timer = QtCore.QTimer(self)
+ self.timer.timeout.connect(self.processTask.emit)
+ self.timer.start(500)
+
+ # Begin decorating the window and connecting events
+ componentList = self.window.listWidget_componentList
+
+ window.toolButton_selectAudioFile.clicked.connect(
+ self.openInputFileDialog)
+
+ window.toolButton_selectOutputFile.clicked.connect(
+ self.openOutputFileDialog)
+
+ window.progressBar_createVideo.setValue(0)
+
+ window.pushButton_createVideo.clicked.connect(
+ self.createAudioVisualisation)
+
+ window.pushButton_Cancel.clicked.connect(self.stopVideo)
+
+ for i, container in enumerate(self.core.encoder_options['containers']):
+ window.comboBox_videoContainer.addItem(container['name'])
+ if container['name'] == self.settings.value('outputContainer'):
+ selectedContainer = i
+
+ window.comboBox_videoContainer.setCurrentIndex(selectedContainer)
+ window.comboBox_videoContainer.currentIndexChanged.connect(
+ self.updateCodecs
+ )
+
+ self.updateCodecs()
+
+ for i in range(window.comboBox_videoCodec.count()):
+ codec = window.comboBox_videoCodec.itemText(i)
+ if codec == self.settings.value('outputVideoCodec'):
+ window.comboBox_videoCodec.setCurrentIndex(i)
+ #print(codec)
+
+ for i in range(window.comboBox_audioCodec.count()):
+ codec = window.comboBox_audioCodec.itemText(i)
+ if codec == self.settings.value('outputAudioCodec'):
+ window.comboBox_audioCodec.setCurrentIndex(i)
+
+ window.comboBox_videoCodec.currentIndexChanged.connect(
+ self.updateCodecSettings
+ )
+
+ window.comboBox_audioCodec.currentIndexChanged.connect(
+ self.updateCodecSettings
+ )
+
+ vBitrate = int(self.settings.value('outputVideoBitrate'))
+ aBitrate = int(self.settings.value('outputAudioBitrate'))
+
+ window.spinBox_vBitrate.setValue(vBitrate)
+ window.spinBox_aBitrate.setValue(aBitrate)
+
+ window.spinBox_vBitrate.valueChanged.connect(self.updateCodecSettings)
+ window.spinBox_aBitrate.valueChanged.connect(self.updateCodecSettings)
+
+ self.previewWindow = PreviewWindow(self, os.path.join(
+ self.core.wd, "background.png"))
+ window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
+
+ # Make component buttons
+ self.compMenu = QMenu()
+ for i, comp in enumerate(self.core.modules):
+ action = self.compMenu.addAction(comp.Component.__doc__)
+ action.triggered.connect(
+ lambda item=i: self.core.insertComponent(0, item, self))
+
+ self.window.pushButton_addComponent.setMenu(self.compMenu)
+
+ componentList.dropEvent = self.dragComponent
+ componentList.itemSelectionChanged.connect(
+ self.changeComponentWidget)
+
+ self.window.pushButton_removeComponent.clicked.connect(
+ lambda _: self.removeComponent())
+
+ componentList.setContextMenuPolicy(
+ QtCore.Qt.CustomContextMenu)
+ componentList.customContextMenuRequested.connect(self.componentContextMenu)
+
+ currentRes = str(self.settings.value('outputWidth'))+'x' + \
+ str(self.settings.value('outputHeight'))
+ for i, res in enumerate(self.resolutions):
+ window.comboBox_resolution.addItem(res)
+ if res == currentRes:
+ currentRes = i
+ window.comboBox_resolution.setCurrentIndex(currentRes)
+ window.comboBox_resolution.currentIndexChanged.connect(
+ self.updateResolution)
+
+ self.window.pushButton_listMoveUp.clicked.connect(
+ lambda: self.moveComponent(-1)
+ )
+ self.window.pushButton_listMoveDown.clicked.connect(
+ lambda: self.moveComponent(1)
+ )
+
+ # Configure the Projects Menu
+ self.projectMenu = QMenu()
+ self.window.menuButton_newProject = self.projectMenu.addAction(
+ "New Project")
+ self.window.menuButton_newProject.triggered.connect(
+ self.createNewProject)
+
+ self.window.menuButton_openProject = self.projectMenu.addAction(
+ "Open Project")
+ self.window.menuButton_openProject.triggered.connect(
+ self.openOpenProjectDialog)
+
+ action = self.projectMenu.addAction("Save Project")
+ action.triggered.connect(self.saveCurrentProject)
+
+ action = self.projectMenu.addAction("Save Project As")
+ action.triggered.connect(self.openSaveProjectDialog)
+
+ self.window.pushButton_projects.setMenu(self.projectMenu)
+
+ # Configure the Presets Button
+ self.window.pushButton_presets.clicked.connect(
+ self.openPresetManager
+ )
+
+ window.show()
+
+ if project and project != self.autosavePath:
+ if not project.endswith('.avp'):
+ project += '.avp'
+ # open a project from the commandline
+ if not os.path.dirname(project):
+ project = os.path.join(os.path.expanduser('~'), project)
+ self.currentProject = project
+ self.settings.setValue("currentProject", project)
+ if os.path.exists(self.autosavePath):
+ os.remove(self.autosavePath)
+ else:
+ # open the last currentProject from settings
+ self.currentProject = self.settings.value("currentProject")
+
+ # delete autosave if it's identical to this project
+ if self.autosaveExists(identical=True):
+ os.remove(self.autosavePath)
+
+ if self.currentProject and os.path.exists(self.autosavePath):
+ ch = self.showMessage(
+ msg="Restore unsaved changes in project '%s'?"
+ % os.path.basename(self.currentProject)[:-4],
+ showCancel=True)
+ if ch:
+ self.saveProjectChanges()
+ else:
+ os.remove(self.autosavePath)
+
+ self.openProject(self.currentProject, prompt=False)
+ self.drawPreview(True)
+
+ # Setup Hotkeys
+ QtWidgets.QShortcut("Ctrl+S", self.window, self.saveCurrentProject)
+ QtWidgets.QShortcut("Ctrl+A", self.window, self.openSaveProjectDialog)
+ QtWidgets.QShortcut("Ctrl+O", self.window, self.openOpenProjectDialog)
+ QtWidgets.QShortcut("Ctrl+N", self.window, self.createNewProject)
+
+ QtWidgets.QShortcut("Ctrl+T", self.window, activated=lambda:
+ self.window.pushButton_addComponent.click())
+ QtWidgets.QShortcut("Ctrl+Space", self.window, activated=lambda:
+ self.window.listWidget_componentList.setFocus())
+ QtWidgets.QShortcut("Ctrl+Shift+S", self.window,
+ self.presetManager.openSavePresetDialog)
+ QtWidgets.QShortcut("Ctrl+Shift+C", self.window,
+ self.presetManager.clearPreset)
+
+ QtWidgets.QShortcut("Ctrl+Up", self.window,
+ activated=lambda: self.moveComponent(-1))
+ QtWidgets.QShortcut("Ctrl+Down", self.window,
+ activated=lambda: self.moveComponent(1))
+ QtWidgets.QShortcut("Ctrl+Home", self.window, self.moveComponentTop)
+ QtWidgets.QShortcut("Ctrl+End", self.window, self.moveComponentBottom)
+ QtWidgets.QShortcut("Ctrl+r", self.window, self.removeComponent)
+
+ def cleanUp(self):
+ self.timer.stop()
+ self.previewThread.quit()
+ self.previewThread.wait()
+ self.autosave()
+
+ def updateWindowTitle(self):
+ appName = 'Audio Visualizer'
+ if self.currentProject:
+ appName += ' - %s' % \
+ os.path.splitext(
+ os.path.basename(self.currentProject))[0]
+ self.window.setWindowTitle(appName)
+
+ @QtCore.pyqtSlot(int, dict)
+ def updateComponentTitle(self, pos, presetStore=False):
+ if type(presetStore) == dict:
+ name = presetStore['preset']
+ if name == None or name not in self.core.savedPresets:
+ modified = False
+ else:
+ modified = (presetStore != self.core.savedPresets[name])
+ else:
+ modified = bool(presetStore)
+ if pos < 0:
+ pos = len(self.core.selectedComponents)-1
+ title = str(self.core.selectedComponents[pos])
+ if self.core.selectedComponents[pos].currentPreset:
+ title += ' - %s' % self.core.selectedComponents[pos].currentPreset
+ if modified:
+ title += '*'
+ self.window.listWidget_componentList.item(pos).setText(title)
+
+ def updateCodecs(self):
+ containerWidget = self.window.comboBox_videoContainer
+ vCodecWidget = self.window.comboBox_videoCodec
+ aCodecWidget = self.window.comboBox_audioCodec
+ index = containerWidget.currentIndex()
+ name = containerWidget.itemText(index)
+ self.settings.setValue('outputContainer', name)
+
+ vCodecWidget.clear()
+ aCodecWidget.clear()
+
+ for container in self.core.encoder_options['containers']:
+ if container['name'] == name:
+ for vCodec in container['video-codecs']:
+ vCodecWidget.addItem(vCodec)
+ for aCodec in container['audio-codecs']:
+ aCodecWidget.addItem(aCodec)
+
+ def updateCodecSettings(self):
+ vCodecWidget = self.window.comboBox_videoCodec
+ vBitrateWidget = self.window.spinBox_vBitrate
+ aBitrateWidget = self.window.spinBox_aBitrate
+ aCodecWidget = self.window.comboBox_audioCodec
+ currentVideoCodec = vCodecWidget.currentIndex()
+ currentVideoCodec = vCodecWidget.itemText(currentVideoCodec)
+ currentVideoBitrate = vBitrateWidget.value()
+ currentAudioCodec = aCodecWidget.currentIndex()
+ currentAudioCodec = aCodecWidget.itemText(currentAudioCodec)
+ currentAudioBitrate = aBitrateWidget.value()
+ self.settings.setValue('outputVideoCodec', currentVideoCodec)
+ self.settings.setValue('outputAudioCodec', currentAudioCodec)
+ self.settings.setValue('outputVideoBitrate', currentVideoBitrate)
+ self.settings.setValue('outputAudioBitrate', currentAudioBitrate)
+
+ def autosave(self, force=False):
+ if not self.currentProject:
+ if os.path.exists(self.autosavePath):
+ os.remove(self.autosavePath)
+ elif force or time.time() - self.lastAutosave >= 2.0:
+ self.core.createProjectFile(self.autosavePath)
+ self.lastAutosave = time.time()
+
+ def autosaveExists(self, identical=True):
+ try:
+ if self.currentProject and os.path.exists(self.autosavePath) \
+ and filecmp.cmp(
+ self.autosavePath, self.currentProject) == identical:
+ return True
+ except FileNotFoundError:
+ print('project file couldn\'t be located:', self.currentProject)
+ return identical
+ return False
+
+ def saveProjectChanges(self):
+ try:
+ os.remove(self.currentProject)
+ os.rename(self.autosavePath, self.currentProject)
+ return True
+ except (FileNotFoundError, IsADirectoryError) as e:
+ self.showMessage(
+ msg='Project file couldn\'t be saved.',
+ detail=str(e))
+ return False
+
+ def openInputFileDialog(self):
+ inputDir = self.settings.value("inputDir", os.path.expanduser("~"))
+
+ fileName = QtGui.QFileDialog.getOpenFileName(
+ self.window, "Open Audio File",
+ inputDir, "Audio Files (%s)" % " ".join(self.core.audioFormats))
+
+ if not fileName == "":
+ self.settings.setValue("inputDir", os.path.dirname(fileName))
+ self.window.lineEdit_audioFile.setText(fileName)
+
+ def openOutputFileDialog(self):
+ outputDir = self.settings.value("outputDir", os.path.expanduser("~"))
+
+ fileName = QtGui.QFileDialog.getSaveFileName(
+ self.window, "Set Output Video File",
+ outputDir,
+ "Video Files (%s);; All Files (*)" % " ".join(self.core.videoFormats))
+
+ if not fileName == "":
+ self.settings.setValue("outputDir", os.path.dirname(fileName))
+ self.window.lineEdit_outputFile.setText(fileName)
+
+ def stopVideo(self):
+ print('stop')
+ self.videoWorker.cancel()
+ self.canceled = True
+
+ def createAudioVisualisation(self):
+ # create output video if mandatory settings are filled in
+ if self.window.lineEdit_audioFile.text() and \
+ self.window.lineEdit_outputFile.text():
+ self.canceled = False
+ self.progressBarUpdated(-1)
+ self.videoThread = QtCore.QThread(self)
+ self.videoWorker = video_thread.Worker(self)
+ self.videoWorker.moveToThread(self.videoThread)
+ self.videoWorker.videoCreated.connect(self.videoCreated)
+ self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated)
+ self.videoWorker.progressBarSetText.connect(
+ self.progressBarSetText)
+ self.videoWorker.imageCreated.connect(self.showPreviewImage)
+ self.videoWorker.encoding.connect(self.changeEncodingStatus)
+ self.videoThread.start()
+ outputPath = self.window.lineEdit_outputFile.text()
+ if not os.path.dirname(outputPath):
+ outputPath = os.path.join(
+ os.path.expanduser("~"), outputPath)
+ self.videoTask.emit(
+ self.window.lineEdit_audioFile.text(),
+ outputPath,
+ self.core.selectedComponents)
+ else:
+ self.showMessage(
+ msg="You must select an audio file and output filename.")
+
+ def changeEncodingStatus(self, status):
+ if status:
+ self.window.pushButton_createVideo.setEnabled(False)
+ self.window.pushButton_Cancel.setEnabled(True)
+ self.window.comboBox_resolution.setEnabled(False)
+ self.window.stackedWidget.setEnabled(False)
+ self.window.tab_encoderSettings.setEnabled(False)
+ self.window.label_audioFile.setEnabled(False)
+ self.window.toolButton_selectAudioFile.setEnabled(False)
+ self.window.label_outputFile.setEnabled(False)
+ self.window.toolButton_selectOutputFile.setEnabled(False)
+ self.window.lineEdit_audioFile.setEnabled(False)
+ self.window.lineEdit_outputFile.setEnabled(False)
+ self.window.pushButton_addComponent.setEnabled(False)
+ self.window.pushButton_removeComponent.setEnabled(False)
+ self.window.pushButton_listMoveDown.setEnabled(False)
+ self.window.pushButton_listMoveUp.setEnabled(False)
+ self.window.listWidget_componentList.setEnabled(False)
+ self.window.menuButton_newProject.setEnabled(False)
+ self.window.menuButton_openProject.setEnabled(False)
+ else:
+ self.window.pushButton_createVideo.setEnabled(True)
+ self.window.pushButton_Cancel.setEnabled(False)
+ self.window.comboBox_resolution.setEnabled(True)
+ self.window.stackedWidget.setEnabled(True)
+ self.window.tab_encoderSettings.setEnabled(True)
+ self.window.label_audioFile.setEnabled(True)
+ self.window.toolButton_selectAudioFile.setEnabled(True)
+ self.window.lineEdit_audioFile.setEnabled(True)
+ self.window.label_outputFile.setEnabled(True)
+ self.window.toolButton_selectOutputFile.setEnabled(True)
+ self.window.lineEdit_outputFile.setEnabled(True)
+ self.window.pushButton_addComponent.setEnabled(True)
+ self.window.pushButton_removeComponent.setEnabled(True)
+ self.window.pushButton_listMoveDown.setEnabled(True)
+ self.window.pushButton_listMoveUp.setEnabled(True)
+ self.window.listWidget_componentList.setEnabled(True)
+ self.window.menuButton_newProject.setEnabled(True)
+ self.window.menuButton_openProject.setEnabled(True)
+ self.drawPreview(True)
+
+ def progressBarUpdated(self, value):
+ self.window.progressBar_createVideo.setValue(value)
+
+ def progressBarSetText(self, value):
+ self.window.progressBar_createVideo.setFormat(value)
+
+ def videoCreated(self):
+ self.videoThread.quit()
+ self.videoThread.wait()
+
+ def updateResolution(self):
+ resIndex = int(self.window.comboBox_resolution.currentIndex())
+ res = self.resolutions[resIndex].split('x')
+ self.settings.setValue('outputWidth', res[0])
+ self.settings.setValue('outputHeight', res[1])
+ self.drawPreview()
+
+ def drawPreview(self, force=False):
+ self.newTask.emit(self.core.selectedComponents)
+ # self.processTask.emit()
+ self.autosave(force)
+
+ def showPreviewImage(self, image):
+ self.previewWindow.changePixmap(image)
+
+ def insertComponent(self, index):
+ componentList = self.window.listWidget_componentList
+ stackedWidget = self.window.stackedWidget
+
+ componentList.insertItem(
+ index,
+ self.core.selectedComponents[index].__doc__)
+ componentList.setCurrentRow(index)
+
+ # connect to signal that adds an asterisk when modified
+ self.core.selectedComponents[index].modified.connect(
+ self.updateComponentTitle)
+
+ self.pages.insert(index, self.core.selectedComponents[index].page)
+ stackedWidget.insertWidget(index, self.pages[index])
+ stackedWidget.setCurrentIndex(index)
+
+ return index
+
+ def removeComponent(self):
+ componentList = self.window.listWidget_componentList
+
+ for selected in componentList.selectedItems():
+ index = componentList.row(selected)
+ self.window.stackedWidget.removeWidget(self.pages[index])
+ componentList.takeItem(index)
+ self.core.removeComponent(index)
+ self.pages.pop(index)
+ self.changeComponentWidget()
+ self.drawPreview()
+
+ def moveComponent(self, change):
+ '''Moves a component relatively from its current position'''
+ componentList = self.window.listWidget_componentList
+ stackedWidget = self.window.stackedWidget
+
+ row = componentList.currentRow()
+ newRow = row + change
+ if newRow > -1 and newRow < componentList.count():
+ self.core.moveComponent(row, newRow)
+
+ # update widgets
+ page = self.pages.pop(row)
+ self.pages.insert(newRow, page)
+ item = componentList.takeItem(row)
+ newItem = componentList.insertItem(newRow, item)
+ widget = stackedWidget.removeWidget(page)
+ stackedWidget.insertWidget(newRow, page)
+ componentList.setCurrentRow(newRow)
+ stackedWidget.setCurrentIndex(newRow)
+ self.drawPreview()
+
+ def moveComponentTop(self):
+ componentList = self.window.listWidget_componentList
+ row = -componentList.currentRow()
+ self.moveComponent(row)
+
+ def moveComponentBottom(self):
+ componentList = self.window.listWidget_componentList
+ row = len(componentList)-componentList.currentRow()-1
+ self.moveComponent(row)
+
+ def dragComponent(self, event):
+ '''Drop event for the component listwidget'''
+ componentList = self.window.listWidget_componentList
+
+ modelIndexes = [ \
+ componentList.model().index(i) \
+ for i in range(componentList.count()) \
+ ]
+ rects = [ \
+ componentList.visualRect(modelIndex) \
+ for modelIndex in modelIndexes \
+ ]
+
+ rowPos = [rect.contains(event.pos()) for rect in rects]
+ if not any(rowPos):
+ return
+
+ i = rowPos.index(True)
+ change = (componentList.currentRow() - i) * -1
+ self.moveComponent(change)
+
+ def changeComponentWidget(self):
+ selected = self.window.listWidget_componentList.selectedItems()
+ if selected:
+ index = self.window.listWidget_componentList.row(selected[0])
+ self.window.stackedWidget.setCurrentIndex(index)
+
+ def openPresetManager(self):
+ '''Preset manager for importing, exporting, renaming, deleting'''
+ self.presetManager.show()
+
+ def clear(self):
+ '''Get a blank slate'''
+ self.core.clearComponents()
+ self.window.listWidget_componentList.clear()
+ for widget in self.pages:
+ self.window.stackedWidget.removeWidget(widget)
+ self.pages = []
+
+ def createNewProject(self):
+ self.openSaveChangesDialog('starting a new project')
+
+ self.clear()
+ self.currentProject = None
+ self.settings.setValue("currentProject", None)
+ self.drawPreview(True)
+ self.updateWindowTitle()
+
+ def saveCurrentProject(self):
+ if self.currentProject:
+ self.core.createProjectFile(self.currentProject)
+ else:
+ self.openSaveProjectDialog()
+
+ def openSaveChangesDialog(self, phrase):
+ success = True
+ if self.autosaveExists(identical=False):
+ ch = self.showMessage(
+ msg="You have unsaved changes in project '%s'. "
+ "Save before %s?" % \
+ (os.path.basename(self.currentProject)[:-4],
+ phrase),
+ showCancel=True)
+ if ch:
+ success = self.saveProjectChanges()
+
+ if success and os.path.exists(self.autosavePath):
+ os.remove(self.autosavePath)
+
+ def openSaveProjectDialog(self):
+ filename = QtGui.QFileDialog.getSaveFileName(
+ self.window, "Create Project File",
+ self.settings.value("projectDir"),
+ "Project Files (*.avp)")
+ if not filename:
+ return
+ if not filename.endswith(".avp"):
+ filename += '.avp'
+ self.settings.setValue("projectDir", os.path.dirname(filename))
+ self.settings.setValue("currentProject", filename)
+ self.currentProject = filename
+ self.updateWindowTitle()
+ self.core.createProjectFile(filename)
+
+ def openOpenProjectDialog(self):
+ filename = QtGui.QFileDialog.getOpenFileName(
+ self.window, "Open Project File",
+ self.settings.value("projectDir"),
+ "Project Files (*.avp)")
+ self.openProject(filename)
+
+ def openProject(self, filepath, prompt=True):
+ if not filepath or not os.path.exists(filepath) \
+ or not filepath.endswith('.avp'):
+ self.updateWindowTitle()
+ return
+
+ self.clear()
+ # ask to save any changes that are about to get deleted
+ if prompt:
+ self.openSaveChangesDialog('opening another project')
+
+ self.currentProject = filepath
+ self.updateWindowTitle()
+ self.settings.setValue("currentProject", filepath)
+ self.settings.setValue("projectDir", os.path.dirname(filepath))
+ # actually load the project using core method
+ self.core.openProject(self, filepath)
+ if self.window.listWidget_componentList.count() == 0:
+ self.drawPreview()
+ self.autosave(True)
+
+ def showMessage(self, **kwargs):
+ parent = kwargs['parent'] if 'parent' in kwargs else self.window
+ msg = QtGui.QMessageBox(parent)
+ msg.setModal(True)
+ msg.setText(kwargs['msg'])
+ msg.setIcon(
+ kwargs['icon'] if 'icon' in kwargs else QtGui.QMessageBox.Information)
+ msg.setDetailedText(kwargs['detail'] if 'detail' in kwargs else None)
+ if 'showCancel'in kwargs and kwargs['showCancel']:
+ msg.setStandardButtons(
+ QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel)
+ else:
+ msg.setStandardButtons(QtGui.QMessageBox.Ok)
+ ch = msg.exec_()
+ if ch == 1024:
+ return True
+ return False
+
+ def componentContextMenu(self, QPos):
+ '''Appears when right-clicking a component in the list'''
+ componentList = self.window.listWidget_componentList
+ if not componentList.selectedItems():
+ return
+
+ # don't show menu if clicking empty space
+ parentPosition = componentList.mapToGlobal(QtCore.QPoint(0, 0))
+ index = componentList.currentRow()
+ modelIndex = componentList.model().index(index)
+ if not componentList.visualRect(modelIndex).contains(QPos):
+ return
+
+ self.presetManager.findPresets()
+ self.menu = QtGui.QMenu()
+ menuItem = self.menu.addAction("Save Preset")
+ menuItem.triggered.connect(
+ self.presetManager.openSavePresetDialog
+ )
+
+ # submenu for opening presets
+ try:
+ presets = self.presetManager.presets[str(self.core.selectedComponents[index])]
+ self.submenu = QtGui.QMenu("Open Preset")
+ self.menu.addMenu(self.submenu)
+
+ for version, presetName in presets:
+ menuItem = self.submenu.addAction(presetName)
+ menuItem.triggered.connect(
+ lambda _, presetName=presetName:
+ self.presetManager.openPreset(presetName)
+ )
+ except KeyError:
+ pass
+
+ if self.core.selectedComponents[index].currentPreset:
+ menuItem = self.menu.addAction("Clear Preset")
+ menuItem.triggered.connect(
+ self.presetManager.clearPreset
+ )
+
+ self.menu.move(parentPosition + QPos)
+ self.menu.show()
diff --git a/src/mainwindow.ui b/src/mainwindow.ui
new file mode 100644
index 0000000..4a12fd5
--- /dev/null
+++ b/src/mainwindow.ui
@@ -0,0 +1,809 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 1008
+ 575
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+ MainWindow
+
+
+
+
+ 0
+ 0
+
+
+
+ false
+
+
+
+ 9
+
+
+ 0
+
+ -
+
+
-
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::MinimumExpanding
+
+
+
+ 0
+ 360
+
+
+
+
+ -
+
+
+ QLayout::SetDefaultConstraint
+
+
+ 0
+
+
-
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::MinimumExpanding
+
+
+
+ 420
+ 0
+
+
+
+
+
+
+ -
+
+
+ QLayout::SetMinimumSize
+
+
+ 3
+
+
-
+
+
+ QLayout::SetMinimumSize
+
+
+ 3
+
+
-
+
+
+ QLayout::SetMinimumSize
+
+
-
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 140
+ 20
+
+
+
+
+ -
+
+
+ Projects
+
+
+
+ -
+
+
+ Presets
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Minimum
+
+
+
+ 20
+ 2
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+ true
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Sunken
+
+
+ 1
+
+
+ true
+
+
+ true
+
+
+ false
+
+
+ QAbstractItemView::InternalMove
+
+
+ Qt::MoveAction
+
+
+
+ -
+
+
-
+
+
+ Add
+
+
+
+ -
+
+
+ Remove
+
+
+
+ -
+
+
+ Up
+
+
+
+ -
+
+
+ Down
+
+
+
+
+
+
+
+ -
+
+
+ 4
+
+
+ 2
+
+
+
+
+
+
+
+ -
+
+
+ QLayout::SetFixedSize
+
+
+ 4
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 500
+ 0
+
+
+
+
+ 16777215
+ 180
+
+
+
+ QTabWidget::North
+
+
+ QTabWidget::Rounded
+
+
+ 0
+
+
+
+ Export Video
+
+
+
+ 10
+
+
-
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 85
+ 0
+
+
+
+
+ 80
+ 16777215
+
+
+
+
+ 80
+ 0
+
+
+
+ Audio File
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 28
+
+
+
+
+ 16777215
+ 28
+
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+
+ 0
+ 28
+
+
+
+
+ 16777215
+ 28
+
+
+
+ ...
+
+
+
+
+
+ -
+
+
-
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 85
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+ Output File
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 28
+
+
+
+
+ 16777215
+ 28
+
+
+
+
+ -
+
+
+
+ 0
+ 28
+
+
+
+
+ 16777215
+ 28
+
+
+
+ ...
+
+
+
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ 24
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Minimum
+
+
+
+ 10
+ 20
+
+
+
+
+ -
+
+
+ Create Video
+
+
+
+ -
+
+
+ false
+
+
+ Cancel
+
+
+
+
+
+
+
+
+
+
+
+ Encoder Settings
+
+
+
+ 10
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 85
+ 0
+
+
+
+ Container
+
+
+
+ -
+
+
+
+ 150
+ 0
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Minimum
+
+
+
+ 5
+ 5
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Resolution
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 85
+ 0
+
+
+
+ Video Codec
+
+
+
+ -
+
+
+
+ 150
+ 0
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 5
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Video Bitrate (Kbps)
+
+
+
+ -
+
+
+ 99999
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 85
+ 0
+
+
+
+ Audio Codec
+
+
+
+ -
+
+
+
+ 150
+ 0
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 5
+ 10
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Audio Bitrate (Kbps)
+
+
+
+ -
+
+
+ 9999
+
+
+
+
+
+
+
+
+
+ -
+
+
+ QLayout::SetDefaultConstraint
+
+
-
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::MinimumExpanding
+
+
+
+ 500
+ 0
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 180
+
+
+
+
+ 16777215
+ 180
+
+
+
+ -1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/presetmanager.py b/src/presetmanager.py
new file mode 100644
index 0000000..ec3f5cd
--- /dev/null
+++ b/src/presetmanager.py
@@ -0,0 +1,290 @@
+from PyQt5 import QtGui, QtCore, QtWidgets
+import string
+import os
+
+import core
+
+
+class PresetManager(QtWidgets.QDialog):
+ def __init__(self, window, parent):
+ super().__init__(parent.window)
+ self.parent = parent
+ self.core = parent.core
+ self.settings = parent.settings
+ self.presetDir = self.core.presetDir
+ if not self.settings.value('presetDir'):
+ self.settings.setValue(
+ "presetDir",
+ os.path.join(self.core.dataDir, 'projects'))
+
+ self.findPresets()
+
+ # window
+ self.lastFilter = '*'
+ self.presetRows = [] # list of (comp, vers, name) tuples
+ self.window = window
+ self.window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
+
+ # connect button signals
+ self.window.pushButton_delete.clicked.connect(self.openDeletePresetDialog)
+ self.window.pushButton_rename.clicked.connect(self.openRenamePresetDialog)
+ self.window.pushButton_import.clicked.connect(self.openImportDialog)
+ self.window.pushButton_export.clicked.connect(self.openExportDialog)
+ self.window.pushButton_close.clicked.connect(self.window.close)
+
+ # create filter box and preset list
+ self.drawFilterList()
+ self.window.comboBox_filter.currentIndexChanged.connect(
+ lambda: self.drawPresetList(
+ self.window.comboBox_filter.currentText(), self.window.lineEdit_search.text()
+ )
+ )
+
+ # make auto-completion for search bar
+ self.autocomplete = QtCore.QStringListModel()
+ completer = QtWidgets.QCompleter()
+ completer.setModel(self.autocomplete)
+ self.window.lineEdit_search.setCompleter(completer)
+ self.window.lineEdit_search.textChanged.connect(
+ lambda: self.drawPresetList(
+ self.window.comboBox_filter.currentText(), self.window.lineEdit_search.text()
+ )
+ )
+ self.drawPresetList('*')
+
+ def show(self):
+ '''Open a new preset manager window from the mainwindow'''
+ self.findPresets()
+ self.drawFilterList()
+ self.drawPresetList('*')
+ self.window.show()
+
+ def findPresets(self):
+ parseList = []
+ for dirpath, dirnames, filenames in os.walk(self.presetDir):
+ # anything without a subdirectory must be a preset folder
+ if dirnames:
+ continue
+ for preset in filenames:
+ compName = os.path.basename(os.path.dirname(dirpath))
+ compVers = os.path.basename(dirpath)
+ try:
+ parseList.append((compName, int(compVers), preset))
+ except ValueError:
+ continue
+ self.presets =\
+ {
+ compName : \
+ [
+ (vers, preset) \
+ for name, vers, preset in parseList \
+ if name == compName \
+ ] \
+ for compName, _, __ in parseList \
+ }
+
+ def drawPresetList(self, compFilter=None, presetFilter=''):
+ self.window.listWidget_presets.clear()
+ if compFilter:
+ self.lastFilter = str(compFilter)
+ else:
+ compFilter = str(self.lastFilter)
+ self.presetRows = []
+ presetNames = []
+ for component, presets in self.presets.items():
+ if compFilter != '*' and component != compFilter:
+ continue
+ for vers, preset in presets:
+ if not presetFilter or presetFilter in preset:
+ self.window.listWidget_presets.addItem('%s: %s' % (component, preset))
+ self.presetRows.append((component, vers, preset))
+ if preset not in presetNames:
+ presetNames.append(preset)
+ self.autocomplete.setStringList(presetNames)
+
+ def drawFilterList(self):
+ self.window.comboBox_filter.clear()
+ self.window.comboBox_filter.addItem('*')
+ for component in self.presets:
+ self.window.comboBox_filter.addItem(component)
+
+ def clearPreset(self, compI=None):
+ '''Functions on mainwindow level from the context menu'''
+ compI = self.parent.window.listWidget_componentList.currentRow()
+ self.core.clearPreset(compI, self.parent)
+
+ def openSavePresetDialog(self):
+ '''Functions on mainwindow level from the context menu'''
+ window = self.parent.window
+ selectedComponents = self.core.selectedComponents
+ componentList = self.parent.window.listWidget_componentList
+
+ if componentList.currentRow() == -1:
+ return
+ while True:
+ index = componentList.currentRow()
+ currentPreset = selectedComponents[index].currentPreset
+ newName, OK = QtGui.QInputDialog.getText(
+ self.parent.window,
+ 'Audio Visualizer',
+ 'New Preset Name:',
+ QtGui.QLineEdit.Normal,
+ currentPreset
+ )
+ if OK:
+ if core.Core.badName(newName):
+ self.warnMessage(self.parent.window)
+ continue
+ if newName:
+ if index != -1:
+ selectedComponents[index].currentPreset = newName
+ saveValueStore = \
+ selectedComponents[index].savePreset()
+ componentName = str(selectedComponents[index]).strip()
+ vers = selectedComponents[index].version()
+ self.createNewPreset(
+ componentName, vers, newName,
+ saveValueStore, window=self.parent.window)
+ self.openPreset(newName)
+ break
+
+ def createNewPreset(
+ self, compName, vers, filename, saveValueStore, **kwargs):
+ path = os.path.join(self.presetDir, compName, str(vers), filename)
+ if self.presetExists(path, **kwargs):
+ return
+ self.core.createPresetFile(compName, vers, filename, saveValueStore)
+
+ def presetExists(self, path, **kwargs):
+ if os.path.exists(path):
+ window = self.window \
+ if 'window' not in kwargs else kwargs['window']
+ ch = self.parent.showMessage(
+ msg="%s already exists! Overwrite it?" %
+ os.path.basename(path),
+ showCancel=True,
+ icon=QtGui.QMessageBox.Warning,
+ parent=window)
+ if not ch:
+ # user clicked cancel
+ return True
+
+ return False
+
+ def openPreset(self, presetName):
+ componentList = self.parent.window.listWidget_componentList
+ selectedComponents = self.parent.core.selectedComponents
+
+ index = componentList.currentRow()
+ if index == -1:
+ return
+ componentName = str(selectedComponents[index]).strip()
+ version = selectedComponents[index].version()
+ dirname = os.path.join(self.presetDir, componentName, str(version))
+ filepath = os.path.join(dirname, presetName)
+ self.core.openPreset(filepath, index, presetName)
+
+ self.parent.updateComponentTitle(index)
+ self.parent.drawPreview()
+
+ def openDeletePresetDialog(self):
+ selected = self.window.listWidget_presets.selectedItems()
+ if not selected:
+ return
+ row = self.window.listWidget_presets.row(selected[0])
+ comp, vers, name = self.presetRows[row]
+ ch = self.parent.showMessage(
+ msg='Really delete %s?' % name,
+ showCancel=True,
+ icon=QtGui.QMessageBox.Warning,
+ parent=self.window
+ )
+ if not ch:
+ return
+ self.deletePreset(comp, vers, name)
+ self.findPresets()
+ self.drawPresetList()
+
+ def deletePreset(self, comp, vers, name):
+ filepath = os.path.join(self.presetDir, comp, str(vers), name)
+ os.remove(filepath)
+
+ def warnMessage(self, window=None):
+ print(window)
+ self.parent.showMessage(
+ msg='Preset names must contain only letters, '
+ 'numbers, and spaces.',
+ parent=window if window else self.window)
+
+ def openRenamePresetDialog(self):
+ presetList = self.window.listWidget_presets
+ if presetList.currentRow() == -1:
+ return
+
+ while True:
+ index = presetList.currentRow()
+ newName, OK = QtGui.QInputDialog.getText(
+ self.window,
+ 'Preset Manager',
+ 'Rename Preset:',
+ QtGui.QLineEdit.Normal,
+ self.presetRows[index][2]
+ )
+ if OK:
+ if core.Core.badName(newName):
+ self.warnMessage()
+ continue
+ if newName:
+ comp, vers, oldName = self.presetRows[index]
+ path = os.path.join(
+ self.presetDir, comp, str(vers))
+ newPath = os.path.join(path, newName)
+ oldPath = os.path.join(path, oldName)
+ if self.presetExists(newPath):
+ return
+ if os.path.exists(newPath):
+ os.remove(newPath)
+ os.rename(oldPath, newPath)
+ self.findPresets()
+ self.drawPresetList()
+ break
+
+ def openImportDialog(self):
+ filename = QtGui.QFileDialog.getOpenFileName(
+ self.window, "Import Preset File",
+ self.settings.value("presetDir"),
+ "Preset Files (*.avl)")
+ if filename:
+ # get installed path & ask user to overwrite if needed
+ path = ''
+ while True:
+ if path:
+ if self.presetExists(path):
+ break
+ else:
+ if os.path.exists(path):
+ os.remove(path)
+ success, path = self.core.importPreset(filename)
+ if success:
+ break
+
+ self.findPresets()
+ self.drawPresetList()
+ self.settings.setValue("presetDir", os.path.dirname(filename))
+
+ def openExportDialog(self):
+ if not self.window.listWidget_presets.selectedItems():
+ return
+ filename = QtGui.QFileDialog.getSaveFileName(
+ self.window, "Export Preset",
+ self.settings.value("presetDir"),
+ "Preset Files (*.avl)")
+ if filename:
+ index = self.window.listWidget_presets.currentRow()
+ comp, vers, name = self.presetRows[index]
+ if not self.core.exportPreset(filename, comp, vers, name):
+ self.parent.showMessage(
+ msg='Couldn\'t export %s.' % filename,
+ parent=self.window
+ )
+ self.settings.setValue("presetDir", os.path.dirname(filename))
diff --git a/src/presetmanager.ui b/src/presetmanager.ui
new file mode 100644
index 0000000..5257b1c
--- /dev/null
+++ b/src/presetmanager.ui
@@ -0,0 +1,150 @@
+
+
+ presetmanager
+
+
+ Qt::NonModal
+
+
+ true
+
+
+
+ 0
+ 0
+ 497
+ 377
+
+
+
+ Preset Manager
+
+
+ -
+
+
-
+
+
+
+
+
+ Filter by name
+
+
+
+ -
+
+
+
+ 200
+ 0
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ true
+
+
+
+
+
+ -
+
+
+ QLayout::SetMinimumSize
+
+
-
+
+
+ Import
+
+
+
+ -
+
+
+ Export
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ true
+
+
+ Rename
+
+
+
+ -
+
+
+ Delete
+
+
+
+
+
+ -
+
+
-
+
+
+ <html><head/><body><p><span style=" font-size:10pt; font-style:italic;">Right-click components in the main window to create presets</span></p></body></html>
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Close
+
+
+
+
+
+
+
+
+
+
diff --git a/src/preview_thread.py b/src/preview_thread.py
new file mode 100644
index 0000000..4a46d51
--- /dev/null
+++ b/src/preview_thread.py
@@ -0,0 +1,59 @@
+from PyQt5 import QtCore, QtGui, uic
+from PyQt5.QtCore import pyqtSignal, pyqtSlot
+from PIL import Image
+from PIL.ImageQt import ImageQt
+import core
+from queue import Queue, Empty
+import os
+from copy import copy
+
+
+class Worker(QtCore.QObject):
+
+ imageCreated = pyqtSignal(['QImage'])
+
+ def __init__(self, parent=None, queue=None):
+ QtCore.QObject.__init__(self)
+ parent.newTask.connect(self.createPreviewImage)
+ parent.processTask.connect(self.process)
+ self.parent = parent
+ self.core = core.Core()
+ self.queue = queue
+ self.core.settings = parent.settings
+ self.stackedWidget = parent.window.stackedWidget
+ self.background = Image.new("RGBA", (1920, 1080), (0, 0, 0, 0))
+ self.background.paste(Image.open(os.path.join(
+ self.core.wd, "background.png")))
+
+ @pyqtSlot(list)
+ def createPreviewImage(self, components):
+ dic = {
+ "components": components,
+ }
+ self.queue.put(dic)
+
+ @pyqtSlot()
+ def process(self):
+ try:
+ nextPreviewInformation = self.queue.get(block=False)
+ while self.queue.qsize() >= 2:
+ try:
+ self.queue.get(block=False)
+ except Empty:
+ continue
+
+ width = int(self.core.settings.value('outputWidth'))
+ height = int(self.core.settings.value('outputHeight'))
+ frame = copy(self.background)
+ frame = frame.resize((width, height))
+
+ components = nextPreviewInformation["components"]
+ for component in reversed(components):
+ frame = Image.alpha_composite(
+ frame, component.previewRender(self))
+
+ self._image = ImageQt(frame)
+ self.imageCreated.emit(QtGui.QImage(self._image))
+
+ except Empty:
+ True
diff --git a/src/video_thread.py b/src/video_thread.py
new file mode 100644
index 0000000..5ea6d21
--- /dev/null
+++ b/src/video_thread.py
@@ -0,0 +1,309 @@
+from PyQt5 import QtCore, QtGui, uic
+from PyQt5.QtCore import pyqtSignal, pyqtSlot
+from PIL import Image, ImageDraw, ImageFont
+from PIL.ImageQt import ImageQt
+import core
+import numpy
+import subprocess as sp
+import sys
+import os
+from queue import Queue, PriorityQueue
+from threading import Thread, Event
+import time
+from copy import copy
+import signal
+
+
+class Worker(QtCore.QObject):
+
+ imageCreated = pyqtSignal(['QImage'])
+ videoCreated = pyqtSignal()
+ progressBarUpdate = pyqtSignal(int)
+ progressBarSetText = pyqtSignal(str)
+ encoding = pyqtSignal(bool)
+
+ def __init__(self, parent=None):
+ QtCore.QObject.__init__(self)
+ self.core = core.Core()
+ self.core.settings = parent.settings
+ self.modules = parent.core.modules
+ self.parent = parent
+ parent.videoTask.connect(self.createVideo)
+ self.sampleSize = 1470 # 44100 / 30 = 1470
+ self.canceled = False
+ self.error = False
+ self.stopped = False
+
+ def renderNode(self):
+ while not self.stopped:
+ i = self.compositeQueue.get()
+ frame = None
+
+ for compNo, comp in reversed(list(enumerate(self.components))):
+ if compNo in self.staticComponents and \
+ self.staticComponents[compNo] is not None:
+ if frame is None:
+ frame = self.staticComponents[compNo]
+ else:
+ frame = Image.alpha_composite(
+ frame, self.staticComponents[compNo])
+ else:
+ if frame is None:
+ frame = comp.frameRender(compNo, i[0], i[1])
+ else:
+ frame = Image.alpha_composite(
+ frame, comp.frameRender(compNo, i[0], i[1]))
+
+ self.renderQueue.put([i[0], frame])
+ self.compositeQueue.task_done()
+
+ def renderDispatch(self):
+ print('Dispatching Frames for Compositing...')
+
+ for i in range(0, len(self.completeAudioArray), self.sampleSize):
+ self.compositeQueue.put([i, self.bgI])
+ # increment tracked video frame for next iteration
+ self.bgI += 1
+
+ def previewDispatch(self):
+ background = Image.new("RGBA", (1920, 1080), (0, 0, 0, 0))
+ background.paste(Image.open(os.path.join(
+ self.core.wd, "background.png")))
+ background = background.resize((self.width, self.height))
+
+ while not self.stopped:
+ i = self.previewQueue.get()
+ if time.time() - self.lastPreview >= 0.06 or i[0] == 0:
+ image = copy(background)
+ image = Image.alpha_composite(image, i[1])
+ self._image = ImageQt(image)
+ self.imageCreated.emit(QtGui.QImage(self._image))
+ self.lastPreview = time.time()
+
+ self.previewQueue.task_done()
+
+ @pyqtSlot(str, str, list)
+ def createVideo(self, inputFile, outputFile, components):
+ self.encoding.emit(True)
+ self.components = components
+ self.outputFile = outputFile
+ self.bgI = 0 # tracked video frame
+ self.reset()
+ self.width = int(self.core.settings.value('outputWidth'))
+ self.height = int(self.core.settings.value('outputHeight'))
+ progressBarValue = 0
+ self.progressBarUpdate.emit(progressBarValue)
+
+ self.progressBarSetText.emit('Loading audio file...')
+ self.completeAudioArray = self.core.readAudioFile(inputFile, self)
+
+ # test if user has libfdk_aac
+ encoders = sp.check_output(
+ self.core.FFMPEG_BIN + " -encoders -hide_banner",
+ shell=True)
+
+ encoders = encoders.decode("utf-8")
+
+ acodec = self.core.settings.value('outputAudioCodec')
+
+ options = self.core.encoder_options
+ containerName = self.core.settings.value('outputContainer')
+ vcodec = self.core.settings.value('outputVideoCodec')
+ vbitrate = str(self.core.settings.value('outputVideoBitrate'))+'k'
+ acodec = self.core.settings.value('outputAudioCodec')
+ abitrate = str(self.core.settings.value('outputAudioBitrate'))+'k'
+
+ for cont in options['containers']:
+ if cont['name'] == containerName:
+ container = cont['container']
+ break
+
+ vencoders = options['video-codecs'][vcodec]
+ aencoders = options['audio-codecs'][acodec]
+
+ #print(encoders)
+ for encoder in vencoders:
+ #print(encoder)
+ if encoder in encoders:
+ vencoder = encoder
+ break
+
+ for encoder in aencoders:
+ #print(encoder)
+ if encoder in encoders:
+ aencoder = encoder
+ break
+
+ ffmpegCommand = [
+ self.core.FFMPEG_BIN,
+ '-thread_queue_size', '512',
+ '-y', # overwrite the output file if it already exists.
+ '-f', 'rawvideo',
+ '-vcodec', 'rawvideo',
+ '-s', str(self.width)+'x'+str(self.height), # size of one frame
+ '-pix_fmt', 'rgba',
+
+ # frames per second
+ '-r', self.core.settings.value('outputFrameRate'),
+ '-i', '-', # The input comes from a pipe
+ '-an',
+ '-i', inputFile,
+ '-vcodec', vencoder,
+ '-acodec', aencoder, # output audio codec
+ '-b:v', vbitrate,
+ '-b:a', abitrate,
+ '-pix_fmt', self.core.settings.value('outputVideoFormat'),
+ '-preset', self.core.settings.value('outputPreset'),
+ '-f', container
+ ]
+
+ if acodec == 'aac':
+ ffmpegCommand.append('-strict')
+ ffmpegCommand.append('-2')
+
+ ffmpegCommand.append(outputFile)
+
+ # ### Now start creating video for output ###
+ numpy.seterr(divide='ignore')
+
+ # Call preFrameRender on all components
+ print('Loaded Components:', ", ".join(
+ ["%s) %s" % (num, str(component)) \
+ for num, component in enumerate(reversed(self.components))
+ ]))
+ self.staticComponents = {}
+ numComps = len(self.components)
+ for compNo, comp in enumerate(self.components):
+ pStr = "Analyzing audio..."
+ self.progressBarSetText.emit(pStr)
+ properties = None
+ properties = comp.preFrameRender(
+ worker=self,
+ completeAudioArray=self.completeAudioArray,
+ sampleSize=self.sampleSize,
+ progressBarUpdate=self.progressBarUpdate,
+ progressBarSetText=self.progressBarSetText
+ )
+
+ if properties and 'static' in properties:
+ self.staticComponents[compNo] = copy(
+ comp.frameRender(compNo, 0, 0))
+ self.progressBarUpdate.emit(100)
+
+ # Create ffmpeg pipe and queues for frames
+ self.out_pipe = sp.Popen(
+ ffmpegCommand, stdin=sp.PIPE, stdout=sys.stdout, stderr=sys.stdout)
+ self.compositeQueue = Queue()
+ self.compositeQueue.maxsize = 20
+ self.renderQueue = PriorityQueue()
+ self.renderQueue.maxsize = 20
+ self.previewQueue = PriorityQueue()
+
+ # Threads to render frames and send them back here for piping out
+ self.renderThreads = []
+ for i in range(3):
+ self.renderThreads.append(
+ Thread(target=self.renderNode, name="Render Thread"))
+ self.renderThreads[i].daemon = True
+ self.renderThreads[i].start()
+
+ self.dispatchThread = Thread(
+ target=self.renderDispatch, name="Render Dispatch Thread")
+ self.dispatchThread.daemon = True
+ self.dispatchThread.start()
+
+ self.previewDispatch = Thread(
+ target=self.previewDispatch, name="Render Dispatch Thread")
+ self.previewDispatch.daemon = True
+ self.previewDispatch.start()
+
+ frameBuffer = {}
+ self.lastPreview = 0.0
+ self.progressBarUpdate.emit(0)
+ pStr = "Exporting video..."
+ self.progressBarSetText.emit(pStr)
+ if not self.canceled:
+ for i in range(0, len(self.completeAudioArray), self.sampleSize):
+ while True:
+ if i in frameBuffer or self.canceled:
+ # if frame's in buffer, pipe it to ffmpeg
+ break
+ # else fetch the next frame & add to the buffer
+ data = self.renderQueue.get()
+ frameBuffer[data[0]] = data[1]
+ self.renderQueue.task_done()
+ if self.canceled:
+ break
+
+ try:
+ self.out_pipe.stdin.write(frameBuffer[i].tobytes())
+ self.previewQueue.put([i, frameBuffer[i]])
+ del frameBuffer[i]
+ except:
+ break
+
+ # increase progress bar value
+ if progressBarValue + 1 <= (i / len(self.completeAudioArray)) \
+ * 100:
+ progressBarValue = numpy.floor(
+ (i / len(self.completeAudioArray)) * 100)
+ self.progressBarUpdate.emit(progressBarValue)
+ pStr = "Exporting video: " + str(int(progressBarValue)) \
+ + "%"
+ self.progressBarSetText.emit(pStr)
+
+ numpy.seterr(all='print')
+
+ self.out_pipe.stdin.close()
+ if self.out_pipe.stderr is not None:
+ print(self.out_pipe.stderr.read())
+ self.out_pipe.stderr.close()
+ self.error = True
+ # out_pipe.terminate() # don't terminate ffmpeg too early
+ self.out_pipe.wait()
+ if self.canceled:
+ print("Export Canceled")
+ try:
+ os.remove(self.outputFile)
+ except:
+ pass
+ self.progressBarUpdate.emit(0)
+ self.progressBarSetText.emit('Export Canceled')
+
+ else:
+ if self.error:
+ print("Export Failed")
+ self.progressBarUpdate.emit(0)
+ self.progressBarSetText.emit('Export Failed')
+ else:
+ print("Export Complete")
+ self.progressBarUpdate.emit(100)
+ self.progressBarSetText.emit('Export Complete')
+
+ self.error = False
+ self.canceled = False
+ self.stopped = True
+ self.encoding.emit(False)
+ self.videoCreated.emit()
+
+ def updateProgress(self, pStr, pVal):
+ self.progressBarValue.emit(pVal)
+ self.progressBarSetText.emit(pStr)
+
+ def cancel(self):
+ self.canceled = True
+ self.core.cancel()
+
+ for comp in self.components:
+ comp.cancel()
+
+ try:
+ self.out_pipe.send_signal(signal.SIGINT)
+ except:
+ pass
+
+ def reset(self):
+ self.core.reset()
+ self.canceled = False
+ for comp in self.components:
+ comp.reset()
diff --git a/video_thread.py b/video_thread.py
deleted file mode 100644
index 265feee..0000000
--- a/video_thread.py
+++ /dev/null
@@ -1,309 +0,0 @@
-from PyQt4 import QtCore, QtGui, uic
-from PyQt4.QtCore import pyqtSignal, pyqtSlot
-from PIL import Image, ImageDraw, ImageFont
-from PIL.ImageQt import ImageQt
-import core
-import numpy
-import subprocess as sp
-import sys
-import os
-from queue import Queue, PriorityQueue
-from threading import Thread, Event
-import time
-from copy import copy
-import signal
-
-
-class Worker(QtCore.QObject):
-
- imageCreated = pyqtSignal(['QImage'])
- videoCreated = pyqtSignal()
- progressBarUpdate = pyqtSignal(int)
- progressBarSetText = pyqtSignal(str)
- encoding = pyqtSignal(bool)
-
- def __init__(self, parent=None):
- QtCore.QObject.__init__(self)
- self.core = core.Core()
- self.core.settings = parent.settings
- self.modules = parent.core.modules
- self.parent = parent
- parent.videoTask.connect(self.createVideo)
- self.sampleSize = 1470 # 44100 / 30 = 1470
- self.canceled = False
- self.error = False
- self.stopped = False
-
- def renderNode(self):
- while not self.stopped:
- i = self.compositeQueue.get()
- frame = None
-
- for compNo, comp in reversed(list(enumerate(self.components))):
- if compNo in self.staticComponents and \
- self.staticComponents[compNo] is not None:
- if frame is None:
- frame = self.staticComponents[compNo]
- else:
- frame = Image.alpha_composite(
- frame, self.staticComponents[compNo])
- else:
- if frame is None:
- frame = comp.frameRender(compNo, i[0], i[1])
- else:
- frame = Image.alpha_composite(
- frame, comp.frameRender(compNo, i[0], i[1]))
-
- self.renderQueue.put([i[0], frame])
- self.compositeQueue.task_done()
-
- def renderDispatch(self):
- print('Dispatching Frames for Compositing...')
-
- for i in range(0, len(self.completeAudioArray), self.sampleSize):
- self.compositeQueue.put([i, self.bgI])
- # increment tracked video frame for next iteration
- self.bgI += 1
-
- def previewDispatch(self):
- background = Image.new("RGBA", (1920, 1080), (0, 0, 0, 0))
- background.paste(Image.open(os.path.join(
- self.core.wd, "background.png")))
- background = background.resize((self.width, self.height))
-
- while not self.stopped:
- i = self.previewQueue.get()
- if time.time() - self.lastPreview >= 0.06 or i[0] == 0:
- image = copy(background)
- image = Image.alpha_composite(image, i[1])
- self._image = ImageQt(image)
- self.imageCreated.emit(QtGui.QImage(self._image))
- self.lastPreview = time.time()
-
- self.previewQueue.task_done()
-
- @pyqtSlot(str, str, list)
- def createVideo(self, inputFile, outputFile, components):
- self.encoding.emit(True)
- self.components = components
- self.outputFile = outputFile
- self.bgI = 0 # tracked video frame
- self.reset()
- self.width = int(self.core.settings.value('outputWidth'))
- self.height = int(self.core.settings.value('outputHeight'))
- progressBarValue = 0
- self.progressBarUpdate.emit(progressBarValue)
-
- self.progressBarSetText.emit('Loading audio file...')
- self.completeAudioArray = self.core.readAudioFile(inputFile, self)
-
- # test if user has libfdk_aac
- encoders = sp.check_output(
- self.core.FFMPEG_BIN + " -encoders -hide_banner",
- shell=True)
-
- encoders = encoders.decode("utf-8")
-
- acodec = self.core.settings.value('outputAudioCodec')
-
- options = self.core.encoder_options
- containerName = self.core.settings.value('outputContainer')
- vcodec = self.core.settings.value('outputVideoCodec')
- vbitrate = str(self.core.settings.value('outputVideoBitrate'))+'k'
- acodec = self.core.settings.value('outputAudioCodec')
- abitrate = str(self.core.settings.value('outputAudioBitrate'))+'k'
-
- for cont in options['containers']:
- if cont['name'] == containerName:
- container = cont['container']
- break
-
- vencoders = options['video-codecs'][vcodec]
- aencoders = options['audio-codecs'][acodec]
-
- #print(encoders)
- for encoder in vencoders:
- #print(encoder)
- if encoder in encoders:
- vencoder = encoder
- break
-
- for encoder in aencoders:
- #print(encoder)
- if encoder in encoders:
- aencoder = encoder
- break
-
- ffmpegCommand = [
- self.core.FFMPEG_BIN,
- '-thread_queue_size', '512',
- '-y', # overwrite the output file if it already exists.
- '-f', 'rawvideo',
- '-vcodec', 'rawvideo',
- '-s', str(self.width)+'x'+str(self.height), # size of one frame
- '-pix_fmt', 'rgba',
-
- # frames per second
- '-r', self.core.settings.value('outputFrameRate'),
- '-i', '-', # The input comes from a pipe
- '-an',
- '-i', inputFile,
- '-vcodec', vencoder,
- '-acodec', aencoder, # output audio codec
- '-b:v', vbitrate,
- '-b:a', abitrate,
- '-pix_fmt', self.core.settings.value('outputVideoFormat'),
- '-preset', self.core.settings.value('outputPreset'),
- '-f', container
- ]
-
- if acodec == 'aac':
- ffmpegCommand.append('-strict')
- ffmpegCommand.append('-2')
-
- ffmpegCommand.append(outputFile)
-
- # ### Now start creating video for output ###
- numpy.seterr(divide='ignore')
-
- # Call preFrameRender on all components
- print('Loaded Components:', ", ".join(
- ["%s) %s" % (num, str(component)) \
- for num, component in enumerate(reversed(self.components))
- ]))
- self.staticComponents = {}
- numComps = len(self.components)
- for compNo, comp in enumerate(self.components):
- pStr = "Analyzing audio..."
- self.progressBarSetText.emit(pStr)
- properties = None
- properties = comp.preFrameRender(
- worker=self,
- completeAudioArray=self.completeAudioArray,
- sampleSize=self.sampleSize,
- progressBarUpdate=self.progressBarUpdate,
- progressBarSetText=self.progressBarSetText
- )
-
- if properties and 'static' in properties:
- self.staticComponents[compNo] = copy(
- comp.frameRender(compNo, 0, 0))
- self.progressBarUpdate.emit(100)
-
- # Create ffmpeg pipe and queues for frames
- self.out_pipe = sp.Popen(
- ffmpegCommand, stdin=sp.PIPE, stdout=sys.stdout, stderr=sys.stdout)
- self.compositeQueue = Queue()
- self.compositeQueue.maxsize = 20
- self.renderQueue = PriorityQueue()
- self.renderQueue.maxsize = 20
- self.previewQueue = PriorityQueue()
-
- # Threads to render frames and send them back here for piping out
- self.renderThreads = []
- for i in range(3):
- self.renderThreads.append(
- Thread(target=self.renderNode, name="Render Thread"))
- self.renderThreads[i].daemon = True
- self.renderThreads[i].start()
-
- self.dispatchThread = Thread(
- target=self.renderDispatch, name="Render Dispatch Thread")
- self.dispatchThread.daemon = True
- self.dispatchThread.start()
-
- self.previewDispatch = Thread(
- target=self.previewDispatch, name="Render Dispatch Thread")
- self.previewDispatch.daemon = True
- self.previewDispatch.start()
-
- frameBuffer = {}
- self.lastPreview = 0.0
- self.progressBarUpdate.emit(0)
- pStr = "Exporting video..."
- self.progressBarSetText.emit(pStr)
- if not self.canceled:
- for i in range(0, len(self.completeAudioArray), self.sampleSize):
- while True:
- if i in frameBuffer or self.canceled:
- # if frame's in buffer, pipe it to ffmpeg
- break
- # else fetch the next frame & add to the buffer
- data = self.renderQueue.get()
- frameBuffer[data[0]] = data[1]
- self.renderQueue.task_done()
- if self.canceled:
- break
-
- try:
- self.out_pipe.stdin.write(frameBuffer[i].tobytes())
- self.previewQueue.put([i, frameBuffer[i]])
- del frameBuffer[i]
- except:
- break
-
- # increase progress bar value
- if progressBarValue + 1 <= (i / len(self.completeAudioArray)) \
- * 100:
- progressBarValue = numpy.floor(
- (i / len(self.completeAudioArray)) * 100)
- self.progressBarUpdate.emit(progressBarValue)
- pStr = "Exporting video: " + str(int(progressBarValue)) \
- + "%"
- self.progressBarSetText.emit(pStr)
-
- numpy.seterr(all='print')
-
- self.out_pipe.stdin.close()
- if self.out_pipe.stderr is not None:
- print(self.out_pipe.stderr.read())
- self.out_pipe.stderr.close()
- self.error = True
- # out_pipe.terminate() # don't terminate ffmpeg too early
- self.out_pipe.wait()
- if self.canceled:
- print("Export Canceled")
- try:
- os.remove(self.outputFile)
- except:
- pass
- self.progressBarUpdate.emit(0)
- self.progressBarSetText.emit('Export Canceled')
-
- else:
- if self.error:
- print("Export Failed")
- self.progressBarUpdate.emit(0)
- self.progressBarSetText.emit('Export Failed')
- else:
- print("Export Complete")
- self.progressBarUpdate.emit(100)
- self.progressBarSetText.emit('Export Complete')
-
- self.error = False
- self.canceled = False
- self.stopped = True
- self.encoding.emit(False)
- self.videoCreated.emit()
-
- def updateProgress(self, pStr, pVal):
- self.progressBarValue.emit(pVal)
- self.progressBarSetText.emit(pStr)
-
- def cancel(self):
- self.canceled = True
- self.core.cancel()
-
- for comp in self.components:
- comp.cancel()
-
- try:
- self.out_pipe.send_signal(signal.SIGINT)
- except:
- pass
-
- def reset(self):
- self.core.reset()
- self.canceled = False
- for comp in self.components:
- comp.reset()
--
cgit v1.2.3