This repository has been archived on 2020-08-22. You can view files and clone it, but cannot push or open issues or pull requests.
pyaudviz/mainwindow.py

587 lines
24 KiB
Python
Raw Normal View History

2017-06-06 11:14:39 -04:00
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 = []