aboutsummaryrefslogtreecommitdiff
path: root/mainwindow.py
diff options
context:
space:
mode:
Diffstat (limited to 'mainwindow.py')
-rw-r--r--mainwindow.py586
1 files changed, 586 insertions, 0 deletions
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 = []