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

603 lines
23 KiB
Python
Raw Normal View History

2017-06-06 11:14:39 -04:00
from os.path import expanduser
from queue import Queue
from PyQt4 import QtCore, QtGui, uic
2017-06-06 11:14:39 -04:00
from PyQt4.QtCore import QSettings, Qt
from PyQt4.QtGui import QMenu
2017-06-06 11:14:39 -04:00
import sys
import os
import signal
import filecmp
import time
import core
import preview_thread
import video_thread
from presetmanager import PresetManager
2017-06-06 11:14:39 -04:00
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 = [] # widgets of component settings
2017-06-06 11:14:39 -04:00
self.lastAutosave = time.time()
# Create data directory, load/create settings
self.dataDir = self.core.dataDir
2017-06-08 22:56:33 -04:00
self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
self.settings = QSettings(
os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat)
LoadDefaultSettings(self)
self.presetManager = PresetManager(
uic.loadUi(
os.path.join(os.path.dirname(os.path.realpath(__file__)),
'presetmanager.ui')),
self)
2017-06-06 11:14:39 -04:00
if not os.path.exists(self.dataDir):
os.makedirs(self.dataDir)
for neededDirectory in (
self.core.presetDir, self.settings.value("projectDir")):
2017-06-06 11:14:39 -04:00
if not os.path.exists(neededDirectory):
os.mkdir(neededDirectory)
# Make queues/timers for the preview thread
2017-06-06 11:14:39 -04:00
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
2017-06-06 11:14:39 -04:00
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")
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
)
2017-06-07 13:33:22 -04:00
vBitrate = int(self.settings.value('outputVideoBitrate'))
aBitrate = int(self.settings.value('outputAudioBitrate'))
2017-06-07 13:33:22 -04:00
window.spinBox_vBitrate.setValue(vBitrate)
window.spinBox_aBitrate.setValue(aBitrate)
window.spinBox_vBitrate.valueChanged.connect(self.updateCodecSettings)
window.spinBox_aBitrate.valueChanged.connect(self.updateCodecSettings)
2017-06-06 11:14:39 -04:00
self.previewWindow = PreviewWindow(self, os.path.join(
os.path.dirname(os.path.realpath(__file__)), "background.png"))
window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
# Make component buttons
2017-06-06 11:14:39 -04:00
self.compMenu = QMenu()
for i, comp in enumerate(self.core.modules):
2017-06-06 11:14:39 -04:00
action = self.compMenu.addAction(comp.Component.__doc__)
action.triggered[()].connect(
lambda item=i: self.insertComponent(item))
self.window.pushButton_addComponent.setMenu(self.compMenu)
componentList.dropEvent = self.componentListChanged
componentList.itemSelectionChanged.connect(
self.changeComponentWidget)
2017-06-06 11:14:39 -04:00
self.window.pushButton_removeComponent.clicked.connect(
lambda _: self.removeComponent())
componentList.setContextMenuPolicy(
QtCore.Qt.CustomContextMenu)
componentList.connect(
componentList,
QtCore.SIGNAL("customContextMenuRequested(QPoint)"),
self.componentContextMenu)
2017-06-06 11:14:39 -04:00
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)
2017-06-06 11:14:39 -04:00
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()
action = self.projectMenu.addAction("New Project")
action.triggered[()].connect(self.createNewProject)
action = self.projectMenu.addAction("Open Project")
action.triggered[()].connect(self.openOpenProjectDialog)
action = self.projectMenu.addAction("Save Project")
action.triggered[()].connect(self.saveCurrentProject)
action = self.projectMenu.addAction("Save Project As")
action.triggered[()].connect(self.openSaveProjectDialog)
self.window.pushButton_projects.setMenu(self.projectMenu)
# Configure the Presets Button
self.window.pushButton_presets.clicked.connect(
self.openPresetManager
)
# Show the window and load current project
2017-06-06 11:14:39 -04:00
window.show()
self.currentProject = self.settings.value("currentProject")
if self.autosaveExists():
2017-06-06 11:14:39 -04:00
# delete autosave if it's identical to the project
os.remove(self.autosavePath)
if self.currentProject and os.path.exists(self.autosavePath):
ch = self.showMessage(
msg="Restore unsaved changes in project '%s'?"
% os.path.basename(self.currentProject)[:-4],
showCancel=True)
2017-06-06 11:14:39 -04:00
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()
@QtCore.pyqtSlot(int, bool)
def updateComponentTitle(self, pos, modified=False):
#print(pos, modified)
if pos < 0:
pos = len(self.core.selectedComponents)-1
title = str(self.core.selectedComponents[pos])
if self.core.selectedComponents[pos].currentPreset:
title += ' - %s' % self.core.selectedComponents[pos].currentPreset
if modified:
title += '*'
self.window.listWidget_componentList.item(pos).setText(title)
if modified:
self.core.componentModified(pos)
else:
self.core.componentUnmodified(pos)
def updateCodecs(self):
containerWidget = self.window.comboBox_videoContainer
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
2017-06-07 13:33:22 -04:00
vBitrateWidget = self.window.spinBox_vBitrate
aBitrateWidget = self.window.spinBox_aBitrate
aCodecWidget = self.window.comboBox_audioCodec
currentVideoCodec = vCodecWidget.currentIndex()
currentVideoCodec = vCodecWidget.itemText(currentVideoCodec)
2017-06-07 13:33:22 -04:00
currentVideoBitrate = vBitrateWidget.value()
currentAudioCodec = aCodecWidget.currentIndex()
currentAudioCodec = aCodecWidget.itemText(currentAudioCodec)
2017-06-07 13:33:22 -04:00
currentAudioBitrate = aBitrateWidget.value()
self.settings.setValue('outputVideoCodec', currentVideoCodec)
self.settings.setValue('outputAudioCodec', currentAudioCodec)
2017-06-07 13:33:22 -04:00
self.settings.setValue('outputVideoBitrate', currentVideoBitrate)
self.settings.setValue('outputAudioBitrate', currentAudioBitrate)
2017-06-06 11:14:39 -04:00
def autosave(self):
if not self.currentProject:
2017-06-06 11:14:39 -04:00
if os.path.exists(self.autosavePath):
os.remove(self.autosavePath)
elif time.time() - self.lastAutosave >= 2.0:
self.core.createProjectFile(self.autosavePath)
2017-06-06 11:14:39 -04:00
self.lastAutosave = time.time()
def autosaveExists(self):
if self.currentProject and os.path.exists(self.autosavePath) \
and filecmp.cmp(self.autosavePath, self.currentProject):
return True
else:
return False
2017-06-06 11:14:39 -04:00
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.core.selectedComponents)
2017-06-06 11:14:39 -04:00
else:
self.showMessage(
msg="You must select an audio file and output filename.")
2017-06-06 11:14:39 -04:00
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.pushButton_presets.setEnabled(False)
2017-06-06 11:14:39 -04:00
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.pushButton_presets.setEnabled(True)
2017-06-06 11:14:39 -04:00
self.window.listWidget_componentList.setEnabled(True)
def progressBarUpdated(self, value):
self.window.progressBar_createVideo.setValue(value)
2017-06-06 11:14:39 -04:00
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())
2017-06-06 11:14:39 -04:00
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.core.selectedComponents)
2017-06-06 11:14:39 -04:00
# self.processTask.emit()
self.autosave()
def showPreviewImage(self, image):
self.previewWindow.changePixmap(image)
def insertComponent(self, moduleIndex, compPos=0):
componentList = self.window.listWidget_componentList
stackedWidget = self.window.stackedWidget
if compPos < 0:
compPos = componentList.count()
index = self.core.insertComponent(
compPos, moduleIndex)
row = componentList.insertItem(
index,
self.core.selectedComponents[index].__doc__)
componentList.setCurrentRow(index)
# connect to signal that adds an asterisk when modified
self.core.selectedComponents[index].modified.connect(
self.updateComponentTitle)
self.pages.insert(index, self.core.selectedComponents[index].widget(self))
stackedWidget.insertWidget(index, self.pages[index])
stackedWidget.setCurrentIndex(index)
self.core.updateComponent(index)
2017-06-06 11:14:39 -04:00
def removeComponent(self):
componentList = self.window.listWidget_componentList
for selected in componentList.selectedItems():
index = componentList.row(selected)
2017-06-06 11:14:39 -04:00
self.window.stackedWidget.removeWidget(self.pages[index])
componentList.takeItem(index)
self.core.removeComponent(index)
2017-06-06 11:14:39 -04:00
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
2017-06-06 11:14:39 -04:00
row = componentList.currentRow()
newRow = row + change
if newRow > -1 and newRow < componentList.count():
self.core.moveComponent(row, newRow)
2017-06-06 11:14:39 -04:00
# 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)
2017-06-06 11:14:39 -04:00
self.drawPreview()
def componentListChanged(self, *args):
'''Update all our tracking variables to match the widget'''
pass
def changeComponentWidget(self):
selected = self.window.listWidget_componentList.selectedItems()
if selected:
index = self.window.listWidget_componentList.row(selected[0])
self.window.stackedWidget.setCurrentIndex(index)
2017-06-06 11:14:39 -04:00
def openPresetManager(self):
'''Preset manager for importing, exporting, renaming, deleting'''
self.presetManager.show()
def clear(self):
'''Get a blank slate'''
self.core.selectedComponents = []
self.window.listWidget_componentList.clear()
for widget in self.pages:
self.window.stackedWidget.removeWidget(widget)
self.pages = []
def createNewProject(self):
if self.autosaveExists():
ch = self.showMessage(
msg="You have unsaved changes in project '%s'. "
"Save before starting a new project?"
% os.path.basename(self.currentProject)[:-4],
showCancel=True)
if ch:
self.saveCurrentProject()
self.clear()
self.currentProject = None
self.settings.setValue("currentProject", None)
self.drawPreview()
2017-06-06 11:14:39 -04:00
def saveCurrentProject(self):
if self.currentProject:
self.core.createProjectFile(self.currentProject)
2017-06-06 11:14:39 -04:00
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.settings.setValue("projectDir", os.path.dirname(filename))
self.settings.setValue("currentProject", filename)
self.currentProject = filename
self.core.createProjectFile(filename)
2017-06-06 11:14:39 -04:00
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()
2017-06-06 11:14:39 -04:00
self.currentProject = filepath
self.settings.setValue("currentProject", filepath)
self.settings.setValue("projectDir", os.path.dirname(filepath))
# actually load the project using core method
self.core.openProject(self, filepath)
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']:
2017-06-06 11:14:39 -04:00
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
self.menu.move(parentPosition + QPos)
self.menu.show()