Redesigned preset UI + video & image component scaling/positioning

Added preset manager
This commit is contained in:
Brianna 2017-06-15 23:21:34 -04:00 committed by GitHub
commit fc7ee6d8e5
16 changed files with 1424 additions and 470 deletions

View File

@ -1,7 +1,18 @@
from PyQt4 import QtGui from PyQt4 import QtGui, QtCore
from PIL import Image
class Component: class Component(QtCore.QObject):
'''A base class for components to inherit from'''
# modified = QtCore.pyqtSignal(int, bool)
def __init__(self, moduleIndex, compPos):
super().__init__()
self.currentPreset = None
self.moduleIndex = moduleIndex
self.compPos = compPos
def __str__(self): def __str__(self):
return self.__doc__ return self.__doc__
@ -10,18 +21,38 @@ class Component:
return 1 return 1
def cancel(self): def cancel(self):
# make sure your component responds to these variables in frameRender() # please stop any lengthy process in response to this variable
self.canceled = True self.canceled = True
def reset(self): def reset(self):
self.canceled = False 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):
'''Children should take (presetDict, presetName=None) as args'''
# Use super().loadPreset(presetDict, presetName)
# Then update your widgets using the preset dict
self.currentPreset = presetName \
if presetName != None else presetDict['preset']
'''
def savePreset(self):
return {}
'''
def preFrameRender(self, **kwargs): def preFrameRender(self, **kwargs):
for var, value in kwargs.items(): for var, value in kwargs.items():
exec('self.%s = value' % var) exec('self.%s = value' % var)
def blankFrame(self, width, height):
return Image.new("RGBA", (width, height), (0, 0, 0, 0))
def pickColor(self): def pickColor(self):
color = QtGui.QColorDialog.getColor() dialog = QtGui.QColorDialog()
dialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True)
color = dialog.getColor()
if color.isValid(): if color.isValid():
RGBstring = '%s,%s,%s' % ( RGBstring = '%s,%s,%s' % (
str(color.red()), str(color.green()), str(color.blue())) str(color.red()), str(color.green()), str(color.blue()))
@ -57,7 +88,7 @@ class Component:
return page return page
def update(self): def update(self):
# read widget values super().update()
self.parent.drawPreview() self.parent.drawPreview()
def previewRender(self, previewWorker): def previewRender(self, previewWorker):
@ -72,12 +103,6 @@ class Component:
image = Image.new("RGBA", (width, height), (0,0,0,0)) image = Image.new("RGBA", (width, height), (0,0,0,0))
return image return image
def loadPreset(self, presetDict):
# update widgets using a preset dict
def savePreset(self):
return {}
def cancel(self): def cancel(self):
self.canceled = True self.canceled = True

View File

@ -7,6 +7,9 @@ from . import __base__
class Component(__base__.Component): class Component(__base__.Component):
'''Color''' '''Color'''
modified = QtCore.pyqtSignal(int, dict)
def widget(self, parent): def widget(self, parent):
self.parent = parent self.parent = parent
page = uic.loadUi(os.path.join( page = uic.loadUi(os.path.join(
@ -20,14 +23,14 @@ class Component(__base__.Component):
page.lineEdit_color1.setText('%s,%s,%s' % self.color1) page.lineEdit_color1.setText('%s,%s,%s' % self.color1)
page.lineEdit_color2.setText('%s,%s,%s' % self.color2) page.lineEdit_color2.setText('%s,%s,%s' % self.color2)
btnStyle = "QPushButton { background-color : %s; outline: none; }" \ btnStyle1 = "QPushButton { background-color : %s; outline: none; }" \
% QColor(*self.color1).name() % QColor(*self.color1).name()
btnStyle = "QPushButton { background-color : %s; outline: none; }" \ btnStyle2 = "QPushButton { background-color : %s; outline: none; }" \
% QColor(*self.color2).name() % QColor(*self.color2).name()
page.pushButton_color1.setStyleSheet(btnStyle) page.pushButton_color1.setStyleSheet(btnStyle1)
page.pushButton_color2.setStyleSheet(btnStyle) page.pushButton_color2.setStyleSheet(btnStyle2)
page.pushButton_color1.clicked.connect(lambda: self.pickColor(1)) page.pushButton_color1.clicked.connect(lambda: self.pickColor(1))
page.pushButton_color2.clicked.connect(lambda: self.pickColor(2)) page.pushButton_color2.clicked.connect(lambda: self.pickColor(2))
@ -50,6 +53,7 @@ class Component(__base__.Component):
self.x = self.page.spinBox_x.value() self.x = self.page.spinBox_x.value()
self.y = self.page.spinBox_y.value() self.y = self.page.spinBox_y.value()
self.parent.drawPreview() self.parent.drawPreview()
super().update()
def previewRender(self, previewWorker): def previewRender(self, previewWorker):
width = int(previewWorker.core.settings.value('outputWidth')) width = int(previewWorker.core.settings.value('outputWidth'))
@ -67,23 +71,26 @@ class Component(__base__.Component):
def drawFrame(self, 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)) return self.blankFrame(width, height)
def loadPreset(self, pr, presetName=None):
super().loadPreset(pr, presetName)
def loadPreset(self, pr):
self.page.lineEdit_color1.setText('%s,%s,%s' % pr['color1']) self.page.lineEdit_color1.setText('%s,%s,%s' % pr['color1'])
self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2']) self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2'])
btnStyle = "QPushButton { background-color : %s; outline: none; }" \ btnStyle1 = "QPushButton { background-color : %s; outline: none; }" \
% QColor(*pr['color1']).name() % QColor(*pr['color1']).name()
btnStyle = "QPushButton { background-color : %s; outline: none; }" \ btnStyle2 = "QPushButton { background-color : %s; outline: none; }" \
% QColor(*pr['color2']).name() % QColor(*pr['color2']).name()
self.page.pushButton_color1.setStyleSheet(btnStyle) self.page.pushButton_color1.setStyleSheet(btnStyle1)
self.page.pushButton_color2.setStyleSheet(btnStyle) self.page.pushButton_color2.setStyleSheet(btnStyle2)
def savePreset(self): def savePreset(self):
return { return {
'preset': self.currentPreset,
'color1': self.color1, 'color1': self.color1,
'color2': self.color2, 'color2': self.color2,
} }

View File

@ -6,6 +6,9 @@ from . import __base__
class Component(__base__.Component): class Component(__base__.Component):
'''Image''' '''Image'''
modified = QtCore.pyqtSignal(int, dict)
def widget(self, parent): def widget(self, parent):
self.parent = parent self.parent = parent
self.settings = parent.settings self.settings = parent.settings
@ -17,15 +20,25 @@ class Component(__base__.Component):
page.lineEdit_image.textChanged.connect(self.update) page.lineEdit_image.textChanged.connect(self.update)
page.pushButton_image.clicked.connect(self.pickImage) 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 self.page = page
return page return page
def update(self): def update(self):
self.imagePath = self.page.lineEdit_image.text() 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() self.parent.drawPreview()
super().update()
def previewRender(self, previewWorker): def previewRender(self, previewWorker):
self.imageFormats = previewWorker.core.imageFormats
width = int(previewWorker.core.settings.value('outputWidth')) width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight')) height = int(previewWorker.core.settings.value('outputHeight'))
return self.drawFrame(width, height) return self.drawFrame(width, height)
@ -40,26 +53,41 @@ class Component(__base__.Component):
return self.drawFrame(width, height) return self.drawFrame(width, height)
def drawFrame(self, 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): if self.imagePath and os.path.exists(self.imagePath):
image = Image.open(self.imagePath) image = Image.open(self.imagePath)
if image.size != (width, height): if self.stretched and image.size != (width, height):
image = image.resize((width, height), Image.ANTIALIAS) image = image.resize((width, height), Image.ANTIALIAS)
frame.paste(image) 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 return frame
def loadPreset(self, pr): def loadPreset(self, pr, presetName=None):
super().loadPreset(pr, presetName)
self.page.lineEdit_image.setText(pr['image']) 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): def savePreset(self):
return { return {
'preset': self.currentPreset,
'image': self.imagePath, 'image': self.imagePath,
'scale': self.scale,
'stretched': self.stretched,
'x': self.xPosition,
'y': self.yPosition,
} }
def pickImage(self): def pickImage(self):
imgDir = self.settings.value("backgroundDir", os.path.expanduser("~")) imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
filename = QtGui.QFileDialog.getOpenFileName( 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: if filename:
self.settings.setValue("backgroundDir", os.path.dirname(filename)) self.settings.setValue("backgroundDir", os.path.dirname(filename))
self.page.lineEdit_image.setText(filename) self.page.lineEdit_image.setText(filename)

View File

@ -124,8 +124,11 @@
<height>16777215</height> <height>16777215</height>
</size> </size>
</property> </property>
<property name="minimum">
<number>-10000</number>
</property>
<property name="maximum"> <property name="maximum">
<number>999999999</number> <number>10000</number>
</property> </property>
</widget> </widget>
</item> </item>
@ -163,10 +166,10 @@
</size> </size>
</property> </property>
<property name="minimum"> <property name="minimum">
<number>0</number> <number>-1000</number>
</property> </property>
<property name="maximum"> <property name="maximum">
<number>999999999</number> <number>1000</number>
</property> </property>
<property name="value"> <property name="value">
<number>0</number> <number>0</number>
@ -177,6 +180,65 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QCheckBox" name="checkBox_stretch">
<property name="text">
<string>Stretch</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_10">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>5</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Scale</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinBox_scale">
<property name="buttonSymbols">
<enum>QAbstractSpinBox::UpDownArrows</enum>
</property>
<property name="suffix">
<string>%</string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>400</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
</layout>
</item>
<item> <item>
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">

View File

@ -1,9 +1,8 @@
import numpy import numpy
from PIL import Image, ImageDraw from PIL import Image, ImageDraw
from PyQt4 import uic, QtGui from PyQt4 import uic, QtGui, QtCore
from PyQt4.QtGui import QColor from PyQt4.QtGui import QColor
import os import os
import random
from . import __base__ from . import __base__
import time import time
from copy import copy from copy import copy
@ -11,6 +10,9 @@ from copy import copy
class Component(__base__.Component): class Component(__base__.Component):
'''Original Audio Visualization''' '''Original Audio Visualization'''
modified = QtCore.pyqtSignal(int, dict)
def widget(self, parent): def widget(self, parent):
self.parent = parent self.parent = parent
self.visColor = (255, 255, 255) self.visColor = (255, 255, 255)
@ -36,8 +38,11 @@ class Component(__base__.Component):
self.layout = self.page.comboBox_visLayout.currentIndex() self.layout = self.page.comboBox_visLayout.currentIndex()
self.visColor = self.RGBFromString(self.page.lineEdit_visColor.text()) self.visColor = self.RGBFromString(self.page.lineEdit_visColor.text())
self.parent.drawPreview() self.parent.drawPreview()
super().update()
def loadPreset(self, pr, presetName=None):
super().loadPreset(pr, presetName)
def loadPreset(self, pr):
self.page.lineEdit_visColor.setText('%s,%s,%s' % pr['visColor']) self.page.lineEdit_visColor.setText('%s,%s,%s' % pr['visColor'])
btnStyle = "QPushButton { background-color : %s; outline: none; }" \ btnStyle = "QPushButton { background-color : %s; outline: none; }" \
% QColor(*pr['visColor']).name() % QColor(*pr['visColor']).name()
@ -46,6 +51,7 @@ class Component(__base__.Component):
def savePreset(self): def savePreset(self):
return { return {
'preset': self.currentPreset,
'layout': self.layout, 'layout': self.layout,
'visColor': self.visColor, 'visColor': self.visColor,
} }
@ -139,7 +145,7 @@ class Component(__base__.Component):
bF = width / 64 bF = width / 64
bH = bF / 2 bH = bF / 2
bQ = bF / 4 bQ = bF / 4
imTop = Image.new("RGBA", (width, height), (0, 0, 0, 0)) imTop = self.blankFrame(width, height)
draw = ImageDraw.Draw(imTop) draw = ImageDraw.Draw(imTop)
r, g, b = color r, g, b = color
color2 = (r, g, b, 125) color2 = (r, g, b, 125)
@ -157,7 +163,7 @@ class Component(__base__.Component):
imBottom = imTop.transpose(Image.FLIP_TOP_BOTTOM) 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: if layout == 0:
y = 0 - int(height/100*43) y = 0 - int(height/100*43)

View File

@ -9,8 +9,11 @@ from . import __base__
class Component(__base__.Component): class Component(__base__.Component):
'''Title Text''' '''Title Text'''
def __init__(self):
super().__init__() modified = QtCore.pyqtSignal(int, dict)
def __init__(self, *args):
super().__init__(*args)
self.titleFont = QFont() self.titleFont = QFont()
def widget(self, parent): def widget(self, parent):
@ -31,7 +34,7 @@ class Component(__base__.Component):
page.comboBox_textAlign.addItem("Right") page.comboBox_textAlign.addItem("Right")
page.lineEdit_textColor.setText('%s,%s,%s' % self.textColor) page.lineEdit_textColor.setText('%s,%s,%s' % self.textColor)
page.pushButton_textColor.clicked.connect(lambda: self.pickColor()) page.pushButton_textColor.clicked.connect(self.pickColor)
btnStyle = "QPushButton { background-color : %s; outline: none; }" \ btnStyle = "QPushButton { background-color : %s; outline: none; }" \
% QColor(*self.textColor).name() % QColor(*self.textColor).name()
page.pushButton_textColor.setStyleSheet(btnStyle) page.pushButton_textColor.setStyleSheet(btnStyle)
@ -62,6 +65,7 @@ class Component(__base__.Component):
self.textColor = self.RGBFromString( self.textColor = self.RGBFromString(
self.page.lineEdit_textColor.text()) self.page.lineEdit_textColor.text())
self.parent.drawPreview() self.parent.drawPreview()
super().update()
def getXY(self): def getXY(self):
'''Returns true x, y after considering alignment settings''' '''Returns true x, y after considering alignment settings'''
@ -78,7 +82,9 @@ class Component(__base__.Component):
x = self.xPosition - offset x = self.xPosition - offset
return x, self.yPosition return x, self.yPosition
def loadPreset(self, pr): def loadPreset(self, pr, presetName=None):
super().loadPreset(pr, presetName)
self.page.lineEdit_title.setText(pr['title']) self.page.lineEdit_title.setText(pr['title'])
font = QFont() font = QFont()
font.fromString(pr['titleFont']) font.fromString(pr['titleFont'])
@ -94,6 +100,7 @@ class Component(__base__.Component):
def savePreset(self): def savePreset(self):
return { return {
'preset': self.currentPreset,
'title': self.title, 'title': self.title,
'titleFont': self.titleFont.toString(), 'titleFont': self.titleFont.toString(),
'alignment': self.alignment, 'alignment': self.alignment,
@ -119,7 +126,7 @@ class Component(__base__.Component):
def addText(self, width, height): def addText(self, width, height):
x, y = self.getXY() x, y = self.getXY()
im = Image.new("RGBA", (width, height), (0, 0, 0, 0)) im = self.blankFrame(width, height)
image = ImageQt(im) image = ImageQt(im)
painter = QPainter(image) painter = QPainter(image)

View File

@ -6,11 +6,21 @@ import threading
from queue import PriorityQueue from queue import PriorityQueue
from . import __base__ from . import __base__
class Video: class Video:
'''Video Component Frame-Fetcher''' '''Video Component Frame-Fetcher'''
def __init__(self, **kwargs): def __init__(self, **kwargs):
mandatoryArgs = ['ffmpeg', 'videoPath', 'width', 'height', mandatoryArgs = [
'frameRate', 'chunkSize', 'parent'] '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: for arg in mandatoryArgs:
try: try:
exec('self.%s = kwargs[arg]' % arg) exec('self.%s = kwargs[arg]' % arg)
@ -31,7 +41,8 @@ class Video:
'-i', self.videoPath, '-i', self.videoPath,
'-f', 'image2pipe', '-f', 'image2pipe',
'-pix_fmt', 'rgba', '-pix_fmt', 'rgba',
'-filter:v', 'scale='+str(self.width)+':'+str(self.height), '-filter:v', 'scale=%s:%s' %
scale(self.scale, self.width, self.height, str),
'-vcodec', 'rawvideo', '-', '-vcodec', 'rawvideo', '-',
] ]
@ -50,7 +61,9 @@ class Video:
while True: while True:
if num in self.finishedFrames: if num in self.finishedFrames:
image = self.finishedFrames.pop(num) image = self.finishedFrames.pop(num)
return Image.frombytes('RGBA', (self.width, self.height), image) return finalizeFrame(
self.component, image, self.width, self.height)
i, image = self.frameBuffer.get() i, image = self.frameBuffer.get()
self.finishedFrames[i] = image self.finishedFrames[i] = image
self.frameBuffer.task_done() self.frameBuffer.task_done()
@ -78,6 +91,9 @@ class Video:
class Component(__base__.Component): class Component(__base__.Component):
'''Video''' '''Video'''
modified = QtCore.pyqtSignal(int, dict)
def widget(self, parent): def widget(self, parent):
self.parent = parent self.parent = parent
self.settings = parent.settings self.settings = parent.settings
@ -93,6 +109,10 @@ class Component(__base__.Component):
page.lineEdit_video.textChanged.connect(self.update) page.lineEdit_video.textChanged.connect(self.update)
page.pushButton_video.clicked.connect(self.pickVideo) page.pushButton_video.clicked.connect(self.pickVideo)
page.checkBox_loop.stateChanged.connect(self.update) 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 self.page = page
return page return page
@ -100,15 +120,21 @@ class Component(__base__.Component):
def update(self): def update(self):
self.videoPath = self.page.lineEdit_video.text() self.videoPath = self.page.lineEdit_video.text()
self.loopVideo = self.page.checkBox_loop.isChecked() 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() self.parent.drawPreview()
super().update()
def previewRender(self, previewWorker): def previewRender(self, previewWorker):
self.videoFormats = previewWorker.core.videoFormats
width = int(previewWorker.core.settings.value('outputWidth')) width = int(previewWorker.core.settings.value('outputWidth'))
height = int(previewWorker.core.settings.value('outputHeight')) height = int(previewWorker.core.settings.value('outputHeight'))
self.chunkSize = 4*width*height self.updateChunksize(width, height)
frame = self.getPreviewFrame(width, height) frame = self.getPreviewFrame(width, height)
if not frame: if not frame:
return Image.new("RGBA", (width, height), (0, 0, 0, 0)) return self.blankFrame(width, height)
else: else:
return frame return frame
@ -116,30 +142,47 @@ class Component(__base__.Component):
super().preFrameRender(**kwargs) super().preFrameRender(**kwargs)
width = int(self.worker.core.settings.value('outputWidth')) width = int(self.worker.core.settings.value('outputWidth'))
height = int(self.worker.core.settings.value('outputHeight')) height = int(self.worker.core.settings.value('outputHeight'))
self.chunkSize = 4*width*height self.blankFrame_ = self.blankFrame(width, height)
self.updateChunksize(width, height)
self.video = Video( self.video = Video(
ffmpeg=self.parent.core.FFMPEG_BIN, videoPath=self.videoPath, ffmpeg=self.parent.core.FFMPEG_BIN, videoPath=self.videoPath,
width=width, height=height, chunkSize=self.chunkSize, width=width, height=height, chunkSize=self.chunkSize,
frameRate=int(self.settings.value("outputFrameRate")), frameRate=int(self.settings.value("outputFrameRate")),
parent=self.parent, loopVideo=self.loopVideo 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): 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): def loadPreset(self, pr, presetName=None):
super().loadPreset(pr, presetName)
self.page.lineEdit_video.setText(pr['video']) 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): def savePreset(self):
return { return {
'preset': self.currentPreset,
'video': self.videoPath, 'video': self.videoPath,
'loop': self.loopVideo,
'distort': self.distort,
'scale': self.scale,
'x': self.xPosition,
'y': self.yPosition,
} }
def pickVideo(self): def pickVideo(self):
imgDir = self.settings.value("backgroundDir", os.path.expanduser("~")) imgDir = self.settings.value("backgroundDir", os.path.expanduser("~"))
filename = QtGui.QFileDialog.getOpenFileName( filename = QtGui.QFileDialog.getOpenFileName(
self.page, "Choose Video", self.page, "Choose Video",
imgDir, "Video Files (*.mp4 *.mov)" imgDir, "Video Files (%s)" % " ".join(self.videoFormats)
) )
if filename: if filename:
self.settings.setValue("backgroundDir", os.path.dirname(filename)) self.settings.setValue("backgroundDir", os.path.dirname(filename))
@ -149,13 +192,15 @@ class Component(__base__.Component):
def getPreviewFrame(self, width, height): def getPreviewFrame(self, width, height):
if not self.videoPath or not os.path.exists(self.videoPath): if not self.videoPath or not os.path.exists(self.videoPath):
return return
command = [ command = [
self.parent.core.FFMPEG_BIN, self.parent.core.FFMPEG_BIN,
'-thread_queue_size', '512', '-thread_queue_size', '512',
'-i', self.videoPath, '-i', self.videoPath,
'-f', 'image2pipe', '-f', 'image2pipe',
'-pix_fmt', 'rgba', '-pix_fmt', 'rgba',
'-filter:v', 'scale='+str(width)+':'+str(height), '-filter:v', 'scale=%s:%s' %
scale(self.scale, width, height, str),
'-vcodec', 'rawvideo', '-', '-vcodec', 'rawvideo', '-',
'-ss', '90', '-ss', '90',
'-vframes', '1', '-vframes', '1',
@ -165,7 +210,47 @@ class Component(__base__.Component):
stderr=subprocess.DEVNULL, bufsize=10**8 stderr=subprocess.DEVNULL, bufsize=10**8
) )
byteFrame = pipe.stdout.read(self.chunkSize) byteFrame = pipe.stdout.read(self.chunkSize)
image = Image.frombytes('RGBA', (width, height), byteFrame) frame = finalizeFrame(self, byteFrame, width, height)
pipe.stdout.close() pipe.stdout.close()
pipe.kill() pipe.kill()
return image
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 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

View File

@ -111,7 +111,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QSpinBox" name="spinBox_x_2"> <widget class="QSpinBox" name="spinBox_x">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"> <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>
@ -124,8 +124,11 @@
<height>16777215</height> <height>16777215</height>
</size> </size>
</property> </property>
<property name="minimum">
<number>-10000</number>
</property>
<property name="maximum"> <property name="maximum">
<number>999999999</number> <number>10000</number>
</property> </property>
</widget> </widget>
</item> </item>
@ -163,10 +166,10 @@
</size> </size>
</property> </property>
<property name="minimum"> <property name="minimum">
<number>0</number> <number>-10000</number>
</property> </property>
<property name="maximum"> <property name="maximum">
<number>999999999</number> <number>10000</number>
</property> </property>
<property name="value"> <property name="value">
<number>0</number> <number>0</number>
@ -202,6 +205,42 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item>
<widget class="QCheckBox" name="checkBox_distort">
<property name="text">
<string>Distort by scale</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Scale</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinBox_scale">
<property name="buttonSymbols">
<enum>QAbstractSpinBox::UpDownArrows</enum>
</property>
<property name="suffix">
<string>%</string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>400</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
<item> <item>
@ -217,6 +256,9 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item>
<widget class="QWidget" name="widget" native="true"/>
</item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>

347
core.py
View File

@ -6,25 +6,330 @@ from os.path import expanduser
import subprocess as sp import subprocess as sp
import numpy import numpy
from PIL import Image from PIL import Image
import tempfile
from shutil import rmtree from shutil import rmtree
import atexit
import time import time
from collections import OrderedDict from collections import OrderedDict
import json import json
from importlib import import_module
from PyQt4.QtGui import QDesktopServices
import string
class Core(): class Core():
def __init__(self): def __init__(self):
self.FFMPEG_BIN = self.findFfmpeg() self.FFMPEG_BIN = self.findFfmpeg()
self.tempDir = os.path.join( self.dataDir = QDesktopServices.storageLocation(
tempfile.gettempdir(), 'audio-visualizer-python-data') QDesktopServices.DataLocation)
if not os.path.exists(self.tempDir): self.presetDir = os.path.join(self.dataDir, 'presets')
os.makedirs(self.tempDir)
atexit.register(self.deleteTempDir)
self.wd = os.path.dirname(os.path.realpath(__file__)) self.wd = os.path.dirname(os.path.realpath(__file__))
self.loadEncoderOptions() 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 = []
# 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))]
def componentListChanged(self):
for i, component in enumerate(self.selectedComponents):
component.compPos = i
def insertComponent(self, compPos, moduleIndex):
if compPos < 0:
compPos = len(self.selectedComponents) -1
component = self.modules[moduleIndex].Component(
moduleIndex, compPos)
self.selectedComponents.insert(
compPos,
component)
self.componentListChanged()
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 updateComponent(self, i):
# print('updating %s' % self.selectedComponents[i])
self.selectedComponents[i].update()
def moduleIndexFor(self, compName):
compNames = [mod.Component.__doc__ for mod in self.modules]
index = compNames.index(compName)
return self.moduleIndexes[index]
def clearPreset(self, compIndex, loader=None):
'''Clears a preset from a component'''
self.selectedComponents[compIndex].currentPreset = None
if loader:
loader.updateComponentTitle(compIndex)
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 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 (mainwindow/command)
which implements an insertComponent method'''
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
# 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()
if errcode == 1:
typ, value, _ = data
if typ.__name__ == KeyError:
# probably just an old version, still loadable
print('file missing value: %s' % value)
return
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 data usable by another method.'''
data = {}
try:
with open(filepath, 'r') as f:
def parseLine(line):
'''Decides if a given avp or avl 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
the args for the filepath.'''
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): def loadEncoderOptions(self):
file_path = os.path.join(self.wd, 'encoder-options.json') file_path = os.path.join(self.wd, 'encoder-options.json')
@ -107,12 +412,6 @@ class Core():
return completeAudioArray return completeAudioArray
def deleteTempDir(self):
try:
rmtree(self.tempDir)
except FileNotFoundError:
pass
def cancel(self): def cancel(self):
self.canceled = True self.canceled = True
@ -120,6 +419,22 @@ class Core():
self.canceled = False self.canceled = False
@staticmethod @staticmethod
def stringOrderedDict(dictionary): def badName(name):
sorted_ = OrderedDict(sorted(dictionary.items(), key=lambda t: t[0])) '''Returns whether a name contains non-alphanumeric chars'''
return repr(sorted_) 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

View File

@ -1,5 +1,4 @@
from importlib import import_module from importlib import import_module
from collections import OrderedDict
from PyQt4 import QtGui, uic from PyQt4 import QtGui, uic
from PyQt4.QtCore import Qt from PyQt4.QtCore import Qt
import sys import sys

View File

@ -1,14 +1,10 @@
from os.path import expanduser from os.path import expanduser
from queue import Queue from queue import Queue
from importlib import import_module from PyQt4 import QtCore, QtGui, uic
from collections import OrderedDict
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import QSettings, Qt from PyQt4.QtCore import QSettings, Qt
from PyQt4.QtGui import QDesktopServices, QMenu from PyQt4.QtGui import QMenu
import sys import sys
import io
import os import os
import string
import signal import signal
import filecmp import filecmp
import time import time
@ -16,6 +12,7 @@ import time
import core import core
import preview_thread import preview_thread
import video_thread import video_thread
from presetmanager import PresetManager
from main import LoadDefaultSettings from main import LoadDefaultSettings
@ -55,26 +52,30 @@ class MainWindow(QtCore.QObject):
# print('main thread id: {}'.format(QtCore.QThread.currentThreadId())) # print('main thread id: {}'.format(QtCore.QThread.currentThreadId()))
self.window = window self.window = window
self.core = core.Core() self.core = core.Core()
self.pages = []
self.selectedComponents = [] self.pages = [] # widgets of component settings
self.lastAutosave = time.time() self.lastAutosave = time.time()
# create data directory, load/create settings # Create data directory, load/create settings
self.dataDir = QDesktopServices.storageLocation( self.dataDir = self.core.dataDir
QDesktopServices.DataLocation)
self.autosavePath = os.path.join(self.dataDir, 'autosave.avp') self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
self.presetDir = os.path.join(self.dataDir, 'presets')
self.settings = QSettings( self.settings = QSettings(
os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat) os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat)
LoadDefaultSettings(self) LoadDefaultSettings(self)
self.presetManager = PresetManager(
uic.loadUi(
os.path.join(os.path.dirname(os.path.realpath(__file__)),
'presetmanager.ui')),
self)
if not os.path.exists(self.dataDir): if not os.path.exists(self.dataDir):
os.makedirs(self.dataDir) os.makedirs(self.dataDir)
for neededDirectory in ( for neededDirectory in (
self.presetDir, self.settings.value("projectDir")): self.core.presetDir, self.settings.value("projectDir")):
if not os.path.exists(neededDirectory): if not os.path.exists(neededDirectory):
os.mkdir(neededDirectory) os.mkdir(neededDirectory)
# # Make queues/timers for the preview thread
self.previewQueue = Queue() self.previewQueue = Queue()
self.previewThread = QtCore.QThread(self) self.previewThread = QtCore.QThread(self)
self.previewWorker = preview_thread.Worker(self, self.previewQueue) self.previewWorker = preview_thread.Worker(self, self.previewQueue)
@ -86,7 +87,9 @@ class MainWindow(QtCore.QObject):
self.timer.timeout.connect(self.processTask.emit) self.timer.timeout.connect(self.processTask.emit)
self.timer.start(500) self.timer.start(500)
# begin decorating the window and connecting events # Begin decorating the window and connecting events
componentList = self.window.listWidget_componentList
window.toolButton_selectAudioFile.clicked.connect( window.toolButton_selectAudioFile.clicked.connect(
self.openInputFileDialog) self.openInputFileDialog)
@ -117,7 +120,7 @@ class MainWindow(QtCore.QObject):
codec = window.comboBox_videoCodec.itemText(i) codec = window.comboBox_videoCodec.itemText(i)
if codec == self.settings.value('outputVideoCodec'): if codec == self.settings.value('outputVideoCodec'):
window.comboBox_videoCodec.setCurrentIndex(i) window.comboBox_videoCodec.setCurrentIndex(i)
print(codec) #print(codec)
for i in range(window.comboBox_audioCodec.count()): for i in range(window.comboBox_audioCodec.count()):
codec = window.comboBox_audioCodec.itemText(i) codec = window.comboBox_audioCodec.itemText(i)
@ -141,25 +144,33 @@ class MainWindow(QtCore.QObject):
window.spinBox_vBitrate.valueChanged.connect(self.updateCodecSettings) window.spinBox_vBitrate.valueChanged.connect(self.updateCodecSettings)
window.spinBox_aBitrate.valueChanged.connect(self.updateCodecSettings) window.spinBox_aBitrate.valueChanged.connect(self.updateCodecSettings)
self.previewWindow = PreviewWindow(self, os.path.join( self.previewWindow = PreviewWindow(self, os.path.join(
os.path.dirname(os.path.realpath(__file__)), "background.png")) os.path.dirname(os.path.realpath(__file__)), "background.png"))
window.verticalLayout_previewWrapper.addWidget(self.previewWindow) window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
self.modules = self.findComponents() # Make component buttons
self.compMenu = QMenu() self.compMenu = QMenu()
for i, comp in enumerate(self.modules): for i, comp in enumerate(self.core.modules):
action = self.compMenu.addAction(comp.Component.__doc__) action = self.compMenu.addAction(comp.Component.__doc__)
action.triggered[()].connect( action.triggered[()].connect(
lambda item=i: self.insertComponent(item)) lambda item=i: self.insertComponent(item))
self.window.pushButton_addComponent.setMenu(self.compMenu) self.window.pushButton_addComponent.setMenu(self.compMenu)
window.listWidget_componentList.clicked.connect(
lambda _: self.changeComponentWidget()) componentList.dropEvent = self.dragComponent
componentList.itemSelectionChanged.connect(
self.changeComponentWidget)
self.window.pushButton_removeComponent.clicked.connect( self.window.pushButton_removeComponent.clicked.connect(
lambda _: self.removeComponent()) lambda _: self.removeComponent())
componentList.setContextMenuPolicy(
QtCore.Qt.CustomContextMenu)
componentList.connect(
componentList,
QtCore.SIGNAL("customContextMenuRequested(QPoint)"),
self.componentContextMenu)
currentRes = str(self.settings.value('outputWidth'))+'x' + \ currentRes = str(self.settings.value('outputWidth'))+'x' + \
str(self.settings.value('outputHeight')) str(self.settings.value('outputHeight'))
for i, res in enumerate(self.resolutions): for i, res in enumerate(self.resolutions):
@ -170,35 +181,46 @@ class MainWindow(QtCore.QObject):
window.comboBox_resolution.currentIndexChanged.connect( window.comboBox_resolution.currentIndexChanged.connect(
self.updateResolution) self.updateResolution)
self.window.pushButton_listMoveUp.clicked.connect( self.window.pushButton_listMoveUp.clicked.connect(
self.moveComponentUp) lambda: self.moveComponent(-1)
)
self.window.pushButton_listMoveDown.clicked.connect( self.window.pushButton_listMoveDown.clicked.connect(
self.moveComponentDown) lambda: self.moveComponent(1)
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 # Configure the Projects Menu
self.projectMenu = QMenu()
self.ui_newProject = self.projectMenu.addAction("New Project")
self.ui_newProject.triggered[()].connect(self.createNewProject)
self.ui_openProject = self.projectMenu.addAction("Open Project")
self.ui_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
)
# Show the window and load current project
window.show() window.show()
self.currentProject = self.settings.value("currentProject") self.currentProject = self.settings.value("currentProject")
if self.currentProject and os.path.exists(self.autosavePath) \ if self.autosaveExists():
and filecmp.cmp(self.autosavePath, self.currentProject):
# delete autosave if it's identical to the project # delete autosave if it's identical to the project
os.remove(self.autosavePath) os.remove(self.autosavePath)
if self.currentProject and os.path.exists(self.autosavePath): if self.currentProject and os.path.exists(self.autosavePath):
ch = self.showMessage( ch = self.showMessage(
"Restore unsaved changes in project '%s'?" msg="Restore unsaved changes in project '%s'?"
% os.path.basename(self.currentProject)[:-4], True) % os.path.basename(self.currentProject)[:-4],
showCancel=True)
if ch: if ch:
os.remove(self.currentProject) os.remove(self.currentProject)
os.rename(self.autosavePath, self.currentProject) os.rename(self.autosavePath, self.currentProject)
@ -214,6 +236,26 @@ class MainWindow(QtCore.QObject):
self.previewThread.wait() self.previewThread.wait()
self.autosave() self.autosave()
@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:
print(pos, presetStore)
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): def updateCodecs(self):
containerWidget = self.window.comboBox_videoContainer containerWidget = self.window.comboBox_videoContainer
vCodecWidget = self.window.comboBox_videoCodec vCodecWidget = self.window.comboBox_videoCodec
@ -249,18 +291,26 @@ class MainWindow(QtCore.QObject):
self.settings.setValue('outputAudioBitrate', currentAudioBitrate) self.settings.setValue('outputAudioBitrate', currentAudioBitrate)
def autosave(self): def autosave(self):
if time.time() - self.lastAutosave >= 1.0: if not self.currentProject:
if os.path.exists(self.autosavePath): if os.path.exists(self.autosavePath):
os.remove(self.autosavePath) os.remove(self.autosavePath)
self.createProjectFile(self.autosavePath) elif time.time() - self.lastAutosave >= 2.0:
self.core.createProjectFile(self.autosavePath)
self.lastAutosave = time.time() 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
def openInputFileDialog(self): def openInputFileDialog(self):
inputDir = self.settings.value("inputDir", expanduser("~")) inputDir = self.settings.value("inputDir", expanduser("~"))
fileName = QtGui.QFileDialog.getOpenFileName( fileName = QtGui.QFileDialog.getOpenFileName(
self.window, "Open Music File", self.window, "Open Music File",
inputDir, "Music Files (*.mp3 *.wav *.ogg *.fla *.aac)") inputDir, "Music Files (%s)" % " ".join(self.core.audioFormats))
if not fileName == "": if not fileName == "":
self.settings.setValue("inputDir", os.path.dirname(fileName)) self.settings.setValue("inputDir", os.path.dirname(fileName))
@ -271,7 +321,8 @@ class MainWindow(QtCore.QObject):
fileName = QtGui.QFileDialog.getSaveFileName( fileName = QtGui.QFileDialog.getSaveFileName(
self.window, "Set Output Video File", self.window, "Set Output Video File",
outputDir, "Video Files (*.mp4 *.mov *.mkv *.avi *.webm *.flv)") outputDir,
"Video Files (%s);; All Files (*)" % " ".join(self.core.videoFormats))
if not fileName == "": if not fileName == "":
self.settings.setValue("outputDir", os.path.dirname(fileName)) self.settings.setValue("outputDir", os.path.dirname(fileName))
@ -302,13 +353,10 @@ class MainWindow(QtCore.QObject):
self.videoTask.emit( self.videoTask.emit(
self.window.lineEdit_audioFile.text(), self.window.lineEdit_audioFile.text(),
self.window.lineEdit_outputFile.text(), self.window.lineEdit_outputFile.text(),
self.selectedComponents) self.core.selectedComponents)
else: else:
self.showMessage( self.showMessage(
"You must select an audio file and output filename.") msg="You must select an audio file and output filename.")
def progressBarUpdated(self, value):
self.window.progressBar_createVideo.setValue(value)
def changeEncodingStatus(self, status): def changeEncodingStatus(self, status):
if status: if status:
@ -327,11 +375,9 @@ class MainWindow(QtCore.QObject):
self.window.pushButton_removeComponent.setEnabled(False) self.window.pushButton_removeComponent.setEnabled(False)
self.window.pushButton_listMoveDown.setEnabled(False) self.window.pushButton_listMoveDown.setEnabled(False)
self.window.pushButton_listMoveUp.setEnabled(False) self.window.pushButton_listMoveUp.setEnabled(False)
self.window.comboBox_openPreset.setEnabled(False)
self.window.pushButton_removePreset.setEnabled(False)
self.window.pushButton_savePreset.setEnabled(False)
self.window.pushButton_openProject.setEnabled(False)
self.window.listWidget_componentList.setEnabled(False) self.window.listWidget_componentList.setEnabled(False)
self.ui_newProject.setEnabled(False)
self.ui_openProject.setEnabled(False)
else: else:
self.window.pushButton_createVideo.setEnabled(True) self.window.pushButton_createVideo.setEnabled(True)
self.window.pushButton_Cancel.setEnabled(False) self.window.pushButton_Cancel.setEnabled(False)
@ -348,11 +394,12 @@ class MainWindow(QtCore.QObject):
self.window.pushButton_removeComponent.setEnabled(True) self.window.pushButton_removeComponent.setEnabled(True)
self.window.pushButton_listMoveDown.setEnabled(True) self.window.pushButton_listMoveDown.setEnabled(True)
self.window.pushButton_listMoveUp.setEnabled(True) self.window.pushButton_listMoveUp.setEnabled(True)
self.window.comboBox_openPreset.setEnabled(True)
self.window.pushButton_removePreset.setEnabled(True)
self.window.pushButton_savePreset.setEnabled(True)
self.window.pushButton_openProject.setEnabled(True)
self.window.listWidget_componentList.setEnabled(True) self.window.listWidget_componentList.setEnabled(True)
self.ui_newProject.setEnabled(True)
self.ui_openProject.setEnabled(True)
def progressBarUpdated(self, value):
self.window.progressBar_createVideo.setValue(value)
def progressBarSetText(self, value): def progressBarSetText(self, value):
self.window.progressBar_createVideo.setFormat(value) self.window.progressBar_createVideo.setFormat(value)
@ -369,187 +416,126 @@ class MainWindow(QtCore.QObject):
self.drawPreview() self.drawPreview()
def drawPreview(self): def drawPreview(self):
self.newTask.emit(self.selectedComponents) self.newTask.emit(self.core.selectedComponents)
# self.processTask.emit() # self.processTask.emit()
self.autosave() self.autosave()
def showPreviewImage(self, image): def showPreviewImage(self, image):
self.previewWindow.changePixmap(image) self.previewWindow.changePixmap(image)
def findComponents(self): def insertComponent(self, moduleIndex, compPos=0):
def findComponents(): componentList = self.window.listWidget_componentList
srcPath = os.path.join( stackedWidget = self.window.stackedWidget
os.path.dirname(os.path.realpath(__file__)), 'components') if compPos < 0:
if os.path.exists(srcPath): compPos = componentList.count()
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 = self.core.insertComponent(
index = len(self.pages) compPos, moduleIndex)
self.selectedComponents.append(self.modules[moduleIndex].Component()) row = componentList.insertItem(
self.window.listWidget_componentList.addItem( index,
self.selectedComponents[-1].__doc__) self.core.selectedComponents[index].__doc__)
self.pages.append(self.selectedComponents[-1].widget(self)) componentList.setCurrentRow(index)
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): # connect to signal that adds an asterisk when modified
self.selectedComponents.insert( self.core.selectedComponents[index].modified.connect(
0, self.modules[moduleIndex].Component()) self.updateComponentTitle)
self.window.listWidget_componentList.insertItem(
0, self.selectedComponents[0].__doc__) self.pages.insert(index, self.core.selectedComponents[index].widget(self))
self.pages.insert(0, self.selectedComponents[0].widget(self)) stackedWidget.insertWidget(index, self.pages[index])
self.window.listWidget_componentList.setCurrentRow(0) stackedWidget.setCurrentIndex(index)
self.window.stackedWidget.insertWidget(0, self.pages[0])
self.window.stackedWidget.setCurrentIndex(0) self.core.updateComponent(index)
self.selectedComponents[0].update()
self.updateOpenPresetComboBox(self.selectedComponents[0])
def removeComponent(self): def removeComponent(self):
for selected in self.window.listWidget_componentList.selectedItems(): componentList = self.window.listWidget_componentList
index = self.window.listWidget_componentList.row(selected)
for selected in componentList.selectedItems():
index = componentList.row(selected)
self.window.stackedWidget.removeWidget(self.pages[index]) self.window.stackedWidget.removeWidget(self.pages[index])
self.window.listWidget_componentList.takeItem(index) componentList.takeItem(index)
self.selectedComponents.pop(index) self.core.removeComponent(index)
self.pages.pop(index) self.pages.pop(index)
self.changeComponentWidget() self.changeComponentWidget()
self.drawPreview() 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 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): def changeComponentWidget(self):
selected = self.window.listWidget_componentList.selectedItems() selected = self.window.listWidget_componentList.selectedItems()
if selected: if selected:
index = self.window.listWidget_componentList.row(selected[0]) index = self.window.listWidget_componentList.row(selected[0])
self.window.stackedWidget.setCurrentIndex(index) self.window.stackedWidget.setCurrentIndex(index)
self.updateOpenPresetComboBox(self.selectedComponents[index])
def moveComponentUp(self): def openPresetManager(self):
row = self.window.listWidget_componentList.currentRow() '''Preset manager for importing, exporting, renaming, deleting'''
if row > 0: self.presetManager.show()
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): def clear(self):
row = self.window.listWidget_componentList.currentRow() '''Get a blank slate'''
if row != -1 and row < len(self.pages)+1: self.core.selectedComponents = []
module = self.selectedComponents[row] self.window.listWidget_componentList.clear()
self.selectedComponents.pop(row) for widget in self.pages:
self.selectedComponents.insert(row + 1, module) self.window.stackedWidget.removeWidget(widget)
page = self.pages[row] self.pages = []
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): def createNewProject(self):
self.window.comboBox_openPreset.clear() if self.autosaveExists():
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( ch = self.showMessage(
"%s already exists! Overwrite it?" % filename, msg="You have unsaved changes in project '%s'. "
True, QtGui.QMessageBox.Warning) "Save before starting a new project?"
if not ch: % os.path.basename(self.currentProject)[:-4],
return showCancel=True)
# remove old copies of the preset if ch:
for i in range(0, self.window.comboBox_openPreset.count()): self.saveCurrentProject()
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): self.clear()
if self.window.comboBox_openPreset.currentIndex() < 1: self.currentProject = None
return self.settings.setValue("currentProject", None)
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() self.drawPreview()
def saveCurrentProject(self): def saveCurrentProject(self):
if self.currentProject: if self.currentProject:
self.createProjectFile(self.currentProject) self.core.createProjectFile(self.currentProject)
else: else:
self.openSaveProjectDialog() self.openSaveProjectDialog()
@ -560,23 +546,13 @@ class MainWindow(QtCore.QObject):
"Project Files (*.avp)") "Project Files (*.avp)")
if not filename: if not filename:
return return
self.createProjectFile(filename) if not filename.endswith(".avp"):
filename += '.avp'
self.settings.setValue("projectDir", os.path.dirname(filename))
self.settings.setValue("currentProject", filename)
self.currentProject = filename
def createProjectFile(self, filepath): self.core.createProjectFile(filename)
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): def openOpenProjectDialog(self):
filename = QtGui.QFileDialog.getOpenFileName( filename = QtGui.QFileDialog.getOpenFileName(
@ -593,58 +569,18 @@ class MainWindow(QtCore.QObject):
self.currentProject = filepath self.currentProject = filepath
self.settings.setValue("currentProject", filepath) self.settings.setValue("currentProject", filepath)
self.settings.setValue("projectDir", os.path.dirname(filepath)) self.settings.setValue("projectDir", os.path.dirname(filepath))
compNames = [mod.Component.__doc__ for mod in self.modules] # actually load the project using core method
try: self.core.openProject(self, filepath)
with open(filepath, 'r') as f:
validSections = ('Components')
section = ''
def parseLine(line): def showMessage(self, **kwargs):
line = line.strip() parent = kwargs['parent'] if 'parent' in kwargs else self.window
newSection = '' msg = QtGui.QMessageBox(parent)
msg.setModal(True)
if line.startswith('[') and line.endswith(']') \ msg.setText(kwargs['msg'])
and line[1:-1] in validSections: msg.setIcon(
newSection = line[1:-1] kwargs['icon'] if 'icon' in kwargs else QtGui.QMessageBox.Information)
msg.setDetailedText(kwargs['detail'] if 'detail' in kwargs else None)
return line, newSection if 'showCancel'in kwargs and kwargs['showCancel']:
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( msg.setStandardButtons(
QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel) QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel)
else: else:
@ -654,10 +590,46 @@ class MainWindow(QtCore.QObject):
return True return True
return False return False
def clear(self): def componentContextMenu(self, QPos):
''' empty out all components and fields, get a blank slate ''' '''Appears when right-clicking a component in the list'''
self.selectedComponents = [] componentList = self.window.listWidget_componentList
self.window.listWidget_componentList.clear() if not componentList.selectedItems():
for widget in self.pages: return
self.window.stackedWidget.removeWidget(widget)
self.pages = [] # 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()

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1008</width> <width>1028</width>
<height>575</height> <height>592</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@ -108,23 +108,32 @@
<enum>QLayout::SetMinimumSize</enum> <enum>QLayout::SetMinimumSize</enum>
</property> </property>
<item> <item>
<widget class="QPushButton" name="pushButton_openProject"> <spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>140</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton_projects">
<property name="text"> <property name="text">
<string>Open Project</string> <string>Projects</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="pushButton_saveProject"> <widget class="QPushButton" name="pushButton_presets">
<property name="text"> <property name="text">
<string>Save Project</string> <string>Presets</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_saveAs">
<property name="text">
<string>Save As</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -141,11 +150,60 @@
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
<width>20</width> <width>20</width>
<height>15</height> <height>2</height>
</size> </size>
</property> </property>
</spacer> </spacer>
</item> </item>
<item>
<widget class="QListWidget" name="listWidget_componentList">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="acceptDrops">
<bool>true</bool>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="lineWidth">
<number>1</number>
</property>
<property name="tabKeyNavigation">
<bool>true</bool>
</property>
<property name="dragEnabled">
<bool>true</bool>
</property>
<property name="dragDropOverwriteMode">
<bool>false</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::InternalMove</enum>
</property>
<property name="defaultDropAction">
<enum>Qt::MoveAction</enum>
</property>
</widget>
</item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_14"> <layout class="QHBoxLayout" name="horizontalLayout_14">
<item> <item>
@ -188,97 +246,6 @@
<property name="rightMargin"> <property name="rightMargin">
<number>2</number> <number>2</number>
</property> </property>
<item>
<widget class="QListWidget" name="listWidget_componentList">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="lineWidth">
<number>1</number>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="dragEnabled">
<bool>false</bool>
</property>
<property name="dragDropOverwriteMode">
<bool>false</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::NoDragDrop</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_15">
<property name="leftMargin">
<number>2</number>
</property>
<item>
<widget class="QComboBox" name="comboBox_openPreset">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>180</width>
<height>0</height>
</size>
</property>
<item>
<property name="text">
<string>Component Presets</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_savePreset">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Save</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_removePreset">
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
</layout> </layout>

290
presetmanager.py Normal file
View File

@ -0,0 +1,290 @@
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))

150
presetmanager.ui Normal file
View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>presetmanager</class>
<widget class="QWidget" name="presetmanager">
<property name="windowModality">
<enum>Qt::NonModal</enum>
</property>
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>497</width>
<height>377</height>
</rect>
</property>
<property name="windowTitle">
<string>Preset Manager</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLineEdit" name="lineEdit_search">
<property name="text">
<string/>
</property>
<property name="placeholderText">
<string>Filter by name</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBox_filter">
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QListWidget" name="listWidget_presets">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="tabKeyNavigation">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
<item>
<widget class="QPushButton" name="pushButton_import">
<property name="text">
<string>Import</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_export">
<property name="text">
<string>Export</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton_rename">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Rename</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_delete">
<property name="text">
<string>Delete</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item alignment="Qt::AlignRight">
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt; font-style:italic;&quot;&gt;Right-click components in the main window to create presets&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton_close">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -1,11 +1,9 @@
from PyQt4 import QtCore, QtGui, uic from PyQt4 import QtCore, QtGui, uic
from PyQt4.QtCore import pyqtSignal, pyqtSlot from PyQt4.QtCore import pyqtSignal, pyqtSlot
from PIL import Image, ImageDraw, ImageFont from PIL import Image
from PIL.ImageQt import ImageQt from PIL.ImageQt import ImageQt
import core import core
import time
from queue import Queue, Empty from queue import Queue, Empty
import numpy
import os import os
from copy import copy from copy import copy
@ -18,6 +16,7 @@ class Worker(QtCore.QObject):
QtCore.QObject.__init__(self) QtCore.QObject.__init__(self)
parent.newTask.connect(self.createPreviewImage) parent.newTask.connect(self.createPreviewImage)
parent.processTask.connect(self.process) parent.processTask.connect(self.process)
self.parent = parent
self.core = core.Core() self.core = core.Core()
self.queue = queue self.queue = queue
self.core.settings = parent.settings self.core.settings = parent.settings

View File

@ -26,7 +26,7 @@ class Worker(QtCore.QObject):
QtCore.QObject.__init__(self) QtCore.QObject.__init__(self)
self.core = core.Core() self.core = core.Core()
self.core.settings = parent.settings self.core.settings = parent.settings
self.modules = parent.modules self.modules = parent.core.modules
self.stackedWidget = parent.window.stackedWidget self.stackedWidget = parent.window.stackedWidget
self.parent = parent self.parent = parent
parent.videoTask.connect(self.createVideo) parent.videoTask.connect(self.createVideo)