Merge branch 'feature-newgui' of github.com:djfun/audio-visualizer-python into feature-newgui
This commit is contained in:
commit
f3da72ea54
216
command.py
216
command.py
|
@ -1,122 +1,126 @@
|
|||
# FIXME: commandline functionality broken until we decide how to implement it
|
||||
'''
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4.QtCore import QSettings
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
import core
|
||||
import video_thread
|
||||
from main import LoadDefaultSettings
|
||||
|
||||
|
||||
class Command(QtCore.QObject):
|
||||
|
||||
videoTask = QtCore.pyqtSignal(str, str, str, list)
|
||||
videoTask = QtCore.pyqtSignal(str, str, list)
|
||||
|
||||
def __init__(self):
|
||||
QtCore.QObject.__init__(self)
|
||||
self.modules = []
|
||||
self.selectedComponents = []
|
||||
def __init__(self):
|
||||
QtCore.QObject.__init__(self)
|
||||
self.core = core.Core()
|
||||
self.dataDir = self.core.dataDir
|
||||
self.canceled = False
|
||||
|
||||
import argparse
|
||||
self.parser = argparse.ArgumentParser(
|
||||
description='Create a visualization for an audio file')
|
||||
self.parser.add_argument(
|
||||
'-i', '--input', dest='input', help='input audio file', required=True)
|
||||
self.parser.add_argument(
|
||||
'-o', '--output', dest='output',
|
||||
help='output video file', required=True)
|
||||
self.parser.add_argument(
|
||||
'-b', '--background', dest='bgimage',
|
||||
help='background image file', required=True)
|
||||
self.parser.add_argument(
|
||||
'-t', '--text', dest='text', help='title text', required=True)
|
||||
self.parser.add_argument(
|
||||
'-f', '--font', dest='font', help='title font', required=False)
|
||||
self.parser.add_argument(
|
||||
'-s', '--fontsize', dest='fontsize',
|
||||
help='title font size', required=False)
|
||||
self.parser.add_argument(
|
||||
'-c', '--textcolor', dest='textcolor',
|
||||
help='title text color in r,g,b format', required=False)
|
||||
self.parser.add_argument(
|
||||
'-C', '--viscolor', dest='viscolor',
|
||||
help='visualization color in r,g,b format', required=False)
|
||||
self.parser.add_argument(
|
||||
'-x', '--xposition', dest='xposition',
|
||||
help='x position', required=False)
|
||||
self.parser.add_argument(
|
||||
'-y', '--yposition', dest='yposition',
|
||||
help='y position', required=False)
|
||||
self.parser.add_argument(
|
||||
'-a', '--alignment', dest='alignment',
|
||||
help='title alignment', required=False,
|
||||
type=int, choices=[0, 1, 2])
|
||||
self.args = self.parser.parse_args()
|
||||
self.parser = argparse.ArgumentParser(
|
||||
description='Create a visualization for an audio file',
|
||||
epilog='EXAMPLE COMMAND: main.py myvideotemplate.avp '
|
||||
'-i ~/Music/song.mp3 -o ~/video.mp4 '
|
||||
'-c 0 image path=~/Pictures/thisWeeksPicture.jpg '
|
||||
'-c 1 video "preset=My Logo" -c 2 vis layout=classic')
|
||||
self.parser.add_argument(
|
||||
'-i', '--input', metavar='SOUND',
|
||||
help='input audio file')
|
||||
self.parser.add_argument(
|
||||
'-o', '--output', metavar='OUTPUT',
|
||||
help='output video file')
|
||||
|
||||
self.settings = QSettings('settings.ini', QSettings.IniFormat)
|
||||
LoadDefaultSettings(self)
|
||||
# optional arguments
|
||||
self.parser.add_argument(
|
||||
'projpath', metavar='path-to-project',
|
||||
help='open a project file (.avp)', nargs='?')
|
||||
self.parser.add_argument(
|
||||
'-c', '--comp', metavar=('LAYER', 'ARG'),
|
||||
help='first arg must be component NAME to insert at LAYER.'
|
||||
'"help" for information about possible args for a component.',
|
||||
nargs='*', action='append')
|
||||
|
||||
# load colours as tuples from comma-separated strings
|
||||
self.textColor = core.Core.RGBFromString(
|
||||
self.settings.value("textColor", '255, 255, 255'))
|
||||
self.visColor = core.Core.RGBFromString(
|
||||
self.settings.value("visColor", '255, 255, 255'))
|
||||
if self.args.textcolor:
|
||||
self.textColor = core.Core.RGBFromString(self.args.textcolor)
|
||||
if self.args.viscolor:
|
||||
self.visColor = core.Core.RGBFromString(self.args.viscolor)
|
||||
self.args = self.parser.parse_args()
|
||||
self.settings = QSettings(
|
||||
os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat)
|
||||
LoadDefaultSettings(self)
|
||||
|
||||
# font settings
|
||||
if self.args.font:
|
||||
self.font = QFont(self.args.font)
|
||||
else:
|
||||
self.font = QFont(self.settings.value("titleFont", QFont()))
|
||||
if self.args.projpath:
|
||||
self.core.openProject(self, self.args.projpath)
|
||||
self.core.selectedComponents = list(
|
||||
reversed(self.core.selectedComponents))
|
||||
self.core.componentListChanged()
|
||||
|
||||
if self.args.fontsize:
|
||||
self.fontsize = int(self.args.fontsize)
|
||||
else:
|
||||
self.fontsize = int(self.settings.value("fontSize", 35))
|
||||
if self.args.alignment:
|
||||
self.alignment = int(self.args.alignment)
|
||||
else:
|
||||
self.alignment = int(self.settings.value("alignment", 0))
|
||||
if self.args.comp:
|
||||
for comp in self.args.comp:
|
||||
pos = comp[0]
|
||||
name = comp[1]
|
||||
args = comp[2:]
|
||||
try:
|
||||
pos = int(pos)
|
||||
except ValueError:
|
||||
print(pos, 'is not a layer number.')
|
||||
quit(1)
|
||||
realName = self.parseCompName(name)
|
||||
if not realName:
|
||||
print(name, 'is not a valid component name.')
|
||||
quit(1)
|
||||
modI = self.core.moduleIndexFor(realName)
|
||||
i = self.core.insertComponent(pos, modI, self)
|
||||
for arg in args:
|
||||
self.core.selectedComponents[i].command(arg)
|
||||
|
||||
if self.args.xposition:
|
||||
self.textX = int(self.args.xposition)
|
||||
else:
|
||||
self.textX = int(self.settings.value("xPosition", 70))
|
||||
if self.args.input and self.args.output:
|
||||
self.createAudioVisualisation()
|
||||
elif 'help' not in sys.argv:
|
||||
self.parser.print_help()
|
||||
quit(1)
|
||||
|
||||
if self.args.yposition:
|
||||
self.textY = int(self.args.yposition)
|
||||
else:
|
||||
self.textY = int(self.settings.value("yPosition", 375))
|
||||
def createAudioVisualisation(self):
|
||||
self.videoThread = QtCore.QThread(self)
|
||||
self.videoWorker = video_thread.Worker(self)
|
||||
self.videoWorker.moveToThread(self.videoThread)
|
||||
self.videoWorker.videoCreated.connect(self.videoCreated)
|
||||
|
||||
ffmpeg_cmd = self.settings.value("ffmpeg_cmd", expanduser("~"))
|
||||
self.videoThread.start()
|
||||
self.videoTask.emit(
|
||||
self.args.input,
|
||||
self.args.output,
|
||||
list(reversed(self.core.selectedComponents))
|
||||
)
|
||||
|
||||
self.videoThread = QtCore.QThread(self)
|
||||
self.videoWorker = video_thread.Worker(self)
|
||||
def videoCreated(self):
|
||||
self.videoThread.quit()
|
||||
self.videoThread.wait()
|
||||
quit(0)
|
||||
|
||||
self.videoWorker.moveToThread(self.videoThread)
|
||||
self.videoWorker.videoCreated.connect(self.videoCreated)
|
||||
def showMessage(self, **kwargs):
|
||||
print(kwargs['msg'])
|
||||
if 'detail' in kwargs:
|
||||
print(kwargs['detail'])
|
||||
|
||||
self.videoThread.start()
|
||||
self.videoTask.emit(self.args.bgimage,
|
||||
self.args.text,
|
||||
self.font,
|
||||
self.fontsize,
|
||||
self.alignment,
|
||||
self.textX,
|
||||
self.textY,
|
||||
self.textColor,
|
||||
self.visColor,
|
||||
self.args.input,
|
||||
self.args.output,
|
||||
self.selectedComponents)
|
||||
def drawPreview(self, *args):
|
||||
pass
|
||||
|
||||
def videoCreated(self):
|
||||
self.videoThread.quit()
|
||||
self.videoThread.wait()
|
||||
self.cleanUp()
|
||||
def parseCompName(self, name):
|
||||
'''Deduces a proper component name out of a commandline arg'''
|
||||
|
||||
def cleanUp(self):
|
||||
self.settings.setValue("titleFont", self.font.toString())
|
||||
self.settings.setValue("alignment", str(self.alignment))
|
||||
self.settings.setValue("fontSize", str(self.fontsize))
|
||||
self.settings.setValue("xPosition", str(self.textX))
|
||||
self.settings.setValue("yPosition", str(self.textY))
|
||||
self.settings.setValue("visColor", '%s,%s,%s' % self.visColor)
|
||||
self.settings.setValue("textColor", '%s,%s,%s' % self.textColor)
|
||||
sys.exit(0)
|
||||
'''
|
||||
if name.title() in self.core.compNames:
|
||||
return name.title()
|
||||
for compName in self.core.compNames:
|
||||
if name.capitalize() in compName:
|
||||
return compName
|
||||
|
||||
compFileNames = [ \
|
||||
os.path.splitext(os.path.basename(
|
||||
mod.__file__))[0] \
|
||||
for mod in self.core.modules \
|
||||
]
|
||||
for i, compFileName in enumerate(compFileNames):
|
||||
if name.lower() in compFileName:
|
||||
return self.core.compNames[i]
|
||||
return
|
||||
|
||||
return None
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from PyQt4 import QtGui, QtCore
|
||||
from PIL import Image
|
||||
import os
|
||||
|
||||
|
||||
class Component(QtCore.QObject):
|
||||
|
@ -7,11 +8,12 @@ class Component(QtCore.QObject):
|
|||
|
||||
# modified = QtCore.pyqtSignal(int, bool)
|
||||
|
||||
def __init__(self, moduleIndex, compPos):
|
||||
def __init__(self, moduleIndex, compPos, core):
|
||||
super().__init__()
|
||||
self.currentPreset = None
|
||||
self.moduleIndex = moduleIndex
|
||||
self.compPos = compPos
|
||||
self.core = core
|
||||
|
||||
def __str__(self):
|
||||
return self.__doc__
|
||||
|
@ -32,24 +34,60 @@ class Component(QtCore.QObject):
|
|||
# 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
|
||||
'''Subclasses take (presetDict, presetName=None) as args.
|
||||
Must use super().loadPreset(presetDict, presetName) first,
|
||||
then update self.page widgets using the preset dict.
|
||||
'''
|
||||
self.currentPreset = presetName \
|
||||
if presetName != None else presetDict['preset']
|
||||
'''
|
||||
def savePreset(self):
|
||||
return {}
|
||||
'''
|
||||
|
||||
def preFrameRender(self, **kwargs):
|
||||
'''Triggered only before a video is exported (video_thread.py)
|
||||
self.worker = the video thread worker
|
||||
self.completeAudioArray = a list of audio samples
|
||||
self.sampleSize = number of audio samples per video frame
|
||||
self.progressBarUpdate = signal to set progress bar number
|
||||
self.progressBarSetText = signal to set progress bar text
|
||||
Use the latter two signals to update the MainProgram if needed
|
||||
for a long initialization procedure (i.e., for a visualizer)
|
||||
'''
|
||||
for var, value in kwargs.items():
|
||||
exec('self.%s = value' % var)
|
||||
|
||||
def command(self, arg):
|
||||
'''Configure a component using argument from the commandline.
|
||||
Use super().command(arg) at the end of a subclass's method,
|
||||
if no arguments are found in that method first
|
||||
'''
|
||||
if arg.startswith('preset='):
|
||||
_, preset = arg.split('=', 1)
|
||||
path = os.path.join(self.core.getPresetDir(self), preset)
|
||||
if not os.path.exists(path):
|
||||
print('Couldn\'t locate preset "%s"' % preset)
|
||||
quit(1)
|
||||
else:
|
||||
print('Opening "%s" preset on layer %s' % \
|
||||
(preset, self.compPos))
|
||||
self.core.openPreset(path, self.compPos, preset)
|
||||
else:
|
||||
print(
|
||||
self.__doc__, 'Usage:\n'
|
||||
'Open a preset for this component:\n'
|
||||
' "preset=Preset Name"')
|
||||
self.commandHelp()
|
||||
quit(0)
|
||||
|
||||
def commandHelp(self):
|
||||
'''Print help text for this Component's commandline arguments'''
|
||||
|
||||
def blankFrame(self, width, height):
|
||||
return Image.new("RGBA", (width, height), (0, 0, 0, 0))
|
||||
|
||||
def pickColor(self):
|
||||
'''Use color picker to get color input from the user,
|
||||
and return this as an RGB string and QPushButton stylesheet.
|
||||
In a subclass apply stylesheet to any color selection widgets
|
||||
'''
|
||||
dialog = QtGui.QColorDialog()
|
||||
dialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True)
|
||||
color = dialog.getColor()
|
||||
|
@ -63,7 +101,7 @@ class Component(QtCore.QObject):
|
|||
return None, None
|
||||
|
||||
def RGBFromString(self, string):
|
||||
''' turns an RGB string like "255, 255, 255" into a tuple '''
|
||||
''' Turns an RGB string like "255, 255, 255" into a tuple '''
|
||||
try:
|
||||
tup = tuple([int(i) for i in string.split(',')])
|
||||
if len(tup) != 3:
|
||||
|
@ -83,7 +121,7 @@ class Component(QtCore.QObject):
|
|||
self.parent = parent
|
||||
page = uic.loadUi(os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)), 'example.ui'))
|
||||
# connect widgets signals
|
||||
# --- connect widget signals here ---
|
||||
self.page = page
|
||||
return page
|
||||
|
||||
|
@ -102,12 +140,6 @@ class Component(QtCore.QObject):
|
|||
height = int(self.worker.core.settings.value('outputHeight'))
|
||||
image = Image.new("RGBA", (width, height), (0,0,0,0))
|
||||
return image
|
||||
|
||||
def cancel(self):
|
||||
self.canceled = True
|
||||
|
||||
def reset(self):
|
||||
self.canceled = False
|
||||
'''
|
||||
|
||||
class BadComponentInit(Exception):
|
||||
|
|
|
@ -233,3 +233,14 @@ class Component(__base__.Component):
|
|||
else:
|
||||
self.page.lineEdit_color2.setText(RGBstring)
|
||||
self.page.pushButton_color2.setStyleSheet(btnStyle)
|
||||
|
||||
def commandHelp(self):
|
||||
print('Specify a color:\n color=255,255,255')
|
||||
|
||||
def command(self, arg):
|
||||
if not arg.startswith('preset=') and '=' in arg:
|
||||
key, arg = arg.split('=', 1)
|
||||
if key == 'color':
|
||||
self.page.lineEdit_color1.setText(arg)
|
||||
return
|
||||
super().command(arg)
|
||||
|
|
|
@ -92,3 +92,20 @@ class Component(__base__.Component):
|
|||
self.settings.setValue("backgroundDir", os.path.dirname(filename))
|
||||
self.page.lineEdit_image.setText(filename)
|
||||
self.update()
|
||||
|
||||
def command(self, arg):
|
||||
if not arg.startswith('preset=') and '=' in arg:
|
||||
key, arg = arg.split('=', 1)
|
||||
if key == 'path' and os.path.exists(arg):
|
||||
try:
|
||||
Image.open(arg)
|
||||
self.page.lineEdit_image.setText(arg)
|
||||
self.page.checkBox_stretch.setChecked(True)
|
||||
return
|
||||
except OSError as e:
|
||||
print("Not a supported image format")
|
||||
quit(1)
|
||||
super().command(arg)
|
||||
|
||||
def commandHelp(self):
|
||||
print('Load an image:\n path=/filepath/to/image.png')
|
||||
|
|
|
@ -183,3 +183,22 @@ class Component(__base__.Component):
|
|||
|
||||
return im
|
||||
|
||||
def command(self, arg):
|
||||
if not arg.startswith('preset=') and '=' in arg:
|
||||
key, arg = arg.split('=', 1)
|
||||
if key == 'color':
|
||||
self.page.lineEdit_visColor.setText(arg)
|
||||
return
|
||||
elif key == 'layout':
|
||||
if arg == 'classic':
|
||||
self.page.comboBox_visLayout.setCurrentIndex(0)
|
||||
elif arg == 'split':
|
||||
self.page.comboBox_visLayout.setCurrentIndex(1)
|
||||
elif arg == 'bottom':
|
||||
self.page.comboBox_visLayout.setCurrentIndex(2)
|
||||
return
|
||||
super().command(arg)
|
||||
|
||||
def commandHelp(self):
|
||||
print('Give a layout name:\n layout=[classic/split/bottom]')
|
||||
print('Specify a color:\n color=255,255,255')
|
||||
|
|
|
@ -19,12 +19,14 @@ class Component(__base__.Component):
|
|||
def widget(self, parent):
|
||||
height = int(parent.settings.value('outputHeight'))
|
||||
width = int(parent.settings.value('outputWidth'))
|
||||
|
||||
self.parent = parent
|
||||
self.textColor = (255, 255, 255)
|
||||
self.title = 'Text'
|
||||
self.alignment = 1
|
||||
self.fontSize = height / 13.5
|
||||
self.xPosition = width / 2
|
||||
fm = QtGui.QFontMetrics(self.titleFont)
|
||||
self.xPosition = width / 2 - fm.width(self.title)/2
|
||||
self.yPosition = height / 2 * 1.036
|
||||
|
||||
page = uic.loadUi(os.path.join(
|
||||
|
@ -146,3 +148,29 @@ class Component(__base__.Component):
|
|||
return
|
||||
self.page.lineEdit_textColor.setText(RGBstring)
|
||||
self.page.pushButton_textColor.setStyleSheet(btnStyle)
|
||||
|
||||
def commandHelp(self):
|
||||
print('Enter a string to use as centred white text:')
|
||||
print(' "title=User Error"')
|
||||
print('Specify a text color:\n color=255,255,255')
|
||||
print('Set custom x, y position:\n x=500 y=500')
|
||||
|
||||
def command(self, arg):
|
||||
if not arg.startswith('preset=') and '=' in arg:
|
||||
key, arg = arg.split('=', 1)
|
||||
if key == 'color':
|
||||
self.page.lineEdit_textColor.setText(arg)
|
||||
return
|
||||
elif key == 'size':
|
||||
self.page.spinBox_fontSize.setValue(int(arg))
|
||||
return
|
||||
elif key == 'x':
|
||||
self.page.spinBox_xTextAlign.setValue(int(arg))
|
||||
return
|
||||
elif key == 'y':
|
||||
self.page.spinBox_yTextAlign.setValue(int(arg))
|
||||
return
|
||||
elif key == 'title':
|
||||
self.page.lineEdit_title.setText(arg)
|
||||
return
|
||||
super().command(arg)
|
||||
|
|
|
@ -221,6 +221,23 @@ class Component(__base__.Component):
|
|||
width, height = scale(self.scale, width, height, int)
|
||||
self.chunkSize = 4*width*height
|
||||
|
||||
def command(self, arg):
|
||||
if not arg.startswith('preset=') and '=' in arg:
|
||||
key, arg = arg.split('=', 1)
|
||||
if key == 'path' and os.path.exists(arg):
|
||||
if os.path.splitext(arg)[1] in self.core.videoFormats:
|
||||
self.page.lineEdit_video.setText(arg)
|
||||
self.page.spinBox_scale.setValue(100)
|
||||
self.page.checkBox_loop.setChecked(True)
|
||||
return
|
||||
else:
|
||||
print("Not a supported video format")
|
||||
quit(1)
|
||||
super().command(arg)
|
||||
|
||||
def commandHelp(self):
|
||||
print('Load a video:\n path=/filepath/to/video.mp4')
|
||||
|
||||
def scale(scale, width, height, returntype=None):
|
||||
width = (float(width) / 100.0) * float(scale)
|
||||
height = (float(height) / 100.0) * float(scale)
|
||||
|
|
65
core.py
65
core.py
|
@ -43,6 +43,7 @@ class Core():
|
|||
'*.wav',
|
||||
'*.ogg',
|
||||
'*.fla',
|
||||
'*.flac',
|
||||
'*.aac',
|
||||
])
|
||||
self.imageFormats = Core.appendUppercase([
|
||||
|
@ -77,24 +78,32 @@ class Core():
|
|||
for name in findComponents()
|
||||
]
|
||||
self.moduleIndexes = [i for i in range(len(self.modules))]
|
||||
self.compNames = [mod.Component.__doc__ for mod in self.modules]
|
||||
|
||||
def componentListChanged(self):
|
||||
for i, component in enumerate(self.selectedComponents):
|
||||
component.compPos = i
|
||||
|
||||
def insertComponent(self, compPos, moduleIndex):
|
||||
if compPos < 0:
|
||||
compPos = len(self.selectedComponents) -1
|
||||
def insertComponent(self, compPos, moduleIndex, loader):
|
||||
'''Creates a new component'''
|
||||
if compPos < 0 or compPos > len(self.selectedComponents):
|
||||
compPos = len(self.selectedComponents)
|
||||
if len(self.selectedComponents) > 50:
|
||||
return None
|
||||
|
||||
component = self.modules[moduleIndex].Component(
|
||||
moduleIndex, compPos)
|
||||
moduleIndex, compPos, self)
|
||||
self.selectedComponents.insert(
|
||||
compPos,
|
||||
component)
|
||||
|
||||
self.componentListChanged()
|
||||
|
||||
# init component's widget for loading/saving presets
|
||||
self.selectedComponents[compPos].widget(loader)
|
||||
self.updateComponent(compPos)
|
||||
|
||||
if hasattr(loader, 'insertComponent'):
|
||||
loader.insertComponent(compPos)
|
||||
return compPos
|
||||
|
||||
def moveComponent(self, startI, endI):
|
||||
|
@ -117,15 +126,11 @@ class Core():
|
|||
self.selectedComponents[i].update()
|
||||
|
||||
def moduleIndexFor(self, compName):
|
||||
compNames = [mod.Component.__doc__ for mod in self.modules]
|
||||
index = compNames.index(compName)
|
||||
index = self.compNames.index(compName)
|
||||
return self.moduleIndexes[index]
|
||||
|
||||
def clearPreset(self, compIndex, loader=None):
|
||||
'''Clears a preset from a component'''
|
||||
def clearPreset(self, compIndex):
|
||||
self.selectedComponents[compIndex].currentPreset = None
|
||||
if loader:
|
||||
loader.updateComponentTitle(compIndex)
|
||||
|
||||
def openPreset(self, filepath, compIndex, presetName):
|
||||
'''Applies a preset to a specific component'''
|
||||
|
@ -143,6 +148,10 @@ class Core():
|
|||
self.savedPresets[presetName] = dict(saveValueStore)
|
||||
return True
|
||||
|
||||
def getPresetDir(self, comp):
|
||||
return os.path.join(
|
||||
self.presetDir, str(comp), str(comp.version()))
|
||||
|
||||
def getPreset(self, filepath):
|
||||
'''Returns the preset dict stored at this filepath'''
|
||||
if not os.path.exists(filepath):
|
||||
|
@ -154,8 +163,13 @@ class Core():
|
|||
return saveValueStore
|
||||
|
||||
def openProject(self, loader, filepath):
|
||||
'''loader is the object calling this method (mainwindow/command)
|
||||
which implements an insertComponent method'''
|
||||
''' loader is the object calling this method which must have
|
||||
its own showMessage(**kwargs) method for displaying errors.
|
||||
'''
|
||||
if not os.path.exists(filepath):
|
||||
loader.showMessage(msg='Project file not found')
|
||||
return
|
||||
|
||||
errcode, data = self.parseAvFile(filepath)
|
||||
if errcode == 0:
|
||||
try:
|
||||
|
@ -175,10 +189,13 @@ class Core():
|
|||
# saved preset was renamed or deleted
|
||||
clearThis = True
|
||||
|
||||
# insert component into the loader
|
||||
i = loader.insertComponent(
|
||||
self.moduleIndexFor(name), -1)
|
||||
# create the actual component object & get its index
|
||||
i = self.insertComponent(
|
||||
-1,
|
||||
self.moduleIndexFor(name),
|
||||
loader)
|
||||
if i == None:
|
||||
loader.showMessage(msg="Too many components!")
|
||||
break
|
||||
|
||||
try:
|
||||
|
@ -196,7 +213,9 @@ class Core():
|
|||
(self.selectedComponents[i], e))
|
||||
|
||||
if clearThis:
|
||||
self.clearPreset(i, loader)
|
||||
self.clearPreset(i)
|
||||
if hasattr(loader, 'updateComponentTitle'):
|
||||
loader.updateComponentTitle(i)
|
||||
except:
|
||||
errcode = 1
|
||||
data = sys.exc_info()
|
||||
|
@ -208,7 +227,8 @@ class Core():
|
|||
# probably just an old version, still loadable
|
||||
print('file missing value: %s' % value)
|
||||
return
|
||||
loader.createNewProject()
|
||||
if hasattr(loader, 'createNewProject'):
|
||||
loader.createNewProject()
|
||||
msg = '%s: %s' % (typ.__name__, value)
|
||||
loader.showMessage(
|
||||
msg="Project file '%s' is corrupted." % filepath,
|
||||
|
@ -218,12 +238,14 @@ class Core():
|
|||
|
||||
def parseAvFile(self, filepath):
|
||||
'''Parses an avp (project) or avl (preset package) file.
|
||||
Returns data usable by another method.'''
|
||||
Returns dictionary with section names as the keys, each one
|
||||
contains a list of tuples: (compName, version, compPresetDict)
|
||||
'''
|
||||
data = {}
|
||||
try:
|
||||
with open(filepath, 'r') as f:
|
||||
def parseLine(line):
|
||||
'''Decides if a given avp or avl line is a section header'''
|
||||
'''Decides if a file line is a section header'''
|
||||
validSections = ('Components')
|
||||
line = line.strip()
|
||||
newSection = ''
|
||||
|
@ -307,8 +329,7 @@ class Core():
|
|||
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.'''
|
||||
Or if filepath is empty, create an internal preset using args'''
|
||||
if not filepath:
|
||||
dirname = os.path.join(self.presetDir, compName, str(vers))
|
||||
if not os.path.exists(dirname):
|
||||
|
|
71
main.py
71
main.py
|
@ -1,16 +1,10 @@
|
|||
from importlib import import_module
|
||||
from PyQt4 import QtGui, uic
|
||||
from PyQt4.QtCore import Qt
|
||||
import sys
|
||||
import io
|
||||
import os
|
||||
import atexit
|
||||
import signal
|
||||
|
||||
import core
|
||||
import preview_thread
|
||||
import video_thread
|
||||
from mainwindow import *
|
||||
|
||||
|
||||
def LoadDefaultSettings(self):
|
||||
|
@ -36,42 +30,59 @@ def LoadDefaultSettings(self):
|
|||
}
|
||||
|
||||
for parm, value in default.items():
|
||||
#print(parm, self.settings.value(parm))
|
||||
if self.settings.value(parm) is None:
|
||||
self.settings.setValue(parm, value)
|
||||
|
||||
if __name__ == "__main__":
|
||||
''' FIXME commandline functionality broken until we decide how to implement
|
||||
if len(sys.argv) > 1:
|
||||
# command line mode
|
||||
app = QtGui.QApplication(sys.argv, False)
|
||||
command = Command()
|
||||
signal.signal(signal.SIGINT, command.cleanUp)
|
||||
sys.exit(app.exec_())
|
||||
mode = 'gui'
|
||||
if len(sys.argv) > 2:
|
||||
mode = 'cmd'
|
||||
|
||||
elif len(sys.argv) == 2:
|
||||
if sys.argv[1].startswith('-'):
|
||||
mode = 'cmd'
|
||||
else:
|
||||
# opening a project file with gui
|
||||
proj = sys.argv[1]
|
||||
else:
|
||||
'''
|
||||
# normal gui launch
|
||||
proj = None
|
||||
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
app.setApplicationName("audio-visualizer")
|
||||
app.setOrganizationName("audio-visualizer")
|
||||
|
||||
if getattr(sys, 'frozen', False):
|
||||
# frozen
|
||||
wd = os.path.dirname(sys.executable)
|
||||
else:
|
||||
# unfrozen
|
||||
wd = os.path.dirname(os.path.realpath(__file__))
|
||||
if mode == 'cmd':
|
||||
from command import *
|
||||
|
||||
window = uic.loadUi(os.path.join(wd, "mainwindow.ui"))
|
||||
# window.adjustSize()
|
||||
desc = QtGui.QDesktopWidget()
|
||||
dpi = desc.physicalDpiX()
|
||||
main = Command()
|
||||
|
||||
topMargin = 0 if (dpi == 96) else int(10 * (dpi / 96))
|
||||
window.resize(window.width() * (dpi / 96), window.height() * (dpi / 96))
|
||||
# window.verticalLayout_2.setContentsMargins(0, topMargin, 0, 0)
|
||||
elif mode == 'gui':
|
||||
from mainwindow import *
|
||||
import atexit
|
||||
import signal
|
||||
|
||||
main = MainWindow(window)
|
||||
if getattr(sys, 'frozen', False):
|
||||
# frozen
|
||||
wd = os.path.dirname(sys.executable)
|
||||
else:
|
||||
# unfrozen
|
||||
wd = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
signal.signal(signal.SIGINT, main.cleanUp)
|
||||
atexit.register(main.cleanUp)
|
||||
window = uic.loadUi(os.path.join(wd, "mainwindow.ui"))
|
||||
# window.adjustSize()
|
||||
desc = QtGui.QDesktopWidget()
|
||||
dpi = desc.physicalDpiX()
|
||||
|
||||
topMargin = 0 if (dpi == 96) else int(10 * (dpi / 96))
|
||||
window.resize(window.width() * (dpi / 96), window.height() * (dpi / 96))
|
||||
# window.verticalLayout_2.setContentsMargins(0, topMargin, 0, 0)
|
||||
|
||||
main = MainWindow(window, proj)
|
||||
|
||||
signal.signal(signal.SIGINT, main.cleanUp)
|
||||
atexit.register(main.cleanUp)
|
||||
|
||||
# applicable to both modes
|
||||
sys.exit(app.exec_())
|
||||
|
|
|
@ -45,7 +45,7 @@ class MainWindow(QtGui.QMainWindow):
|
|||
processTask = QtCore.pyqtSignal()
|
||||
videoTask = QtCore.pyqtSignal(str, str, list)
|
||||
|
||||
def __init__(self, window):
|
||||
def __init__(self, window, project):
|
||||
QtGui.QMainWindow.__init__(self)
|
||||
|
||||
# print('main thread id: {}'.format(QtCore.QThread.currentThreadId()))
|
||||
|
@ -149,7 +149,7 @@ class MainWindow(QtGui.QMainWindow):
|
|||
for i, comp in enumerate(self.core.modules):
|
||||
action = self.compMenu.addAction(comp.Component.__doc__)
|
||||
action.triggered[()].connect(
|
||||
lambda item=i: self.insertComponent(item))
|
||||
lambda item=i: self.core.insertComponent(0, item, self))
|
||||
|
||||
self.window.pushButton_addComponent.setMenu(self.compMenu)
|
||||
|
||||
|
@ -209,24 +209,38 @@ class MainWindow(QtGui.QMainWindow):
|
|||
self.openPresetManager
|
||||
)
|
||||
|
||||
# Show the window and load current project
|
||||
window.show()
|
||||
self.currentProject = self.settings.value("currentProject")
|
||||
if self.autosaveExists(identical=True):
|
||||
# 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)
|
||||
if ch:
|
||||
self.saveProjectChanges()
|
||||
else:
|
||||
if project and project != self.autosavePath:
|
||||
if not project.endswith('.avp'):
|
||||
project += '.avp'
|
||||
# open a project from the commandline
|
||||
if not os.path.dirname(project):
|
||||
project = os.path.join(os.path.expanduser('~'), project)
|
||||
self.currentProject = project
|
||||
self.settings.setValue("currentProject", project)
|
||||
if os.path.exists(self.autosavePath):
|
||||
os.remove(self.autosavePath)
|
||||
else:
|
||||
# open the last currentProject from settings
|
||||
self.currentProject = self.settings.value("currentProject")
|
||||
|
||||
# delete autosave if it's identical to this project
|
||||
if self.autosaveExists(identical=True):
|
||||
os.remove(self.autosavePath)
|
||||
|
||||
if self.currentProject and os.path.exists(self.autosavePath):
|
||||
ch = self.showMessage(
|
||||
msg="Restore unsaved changes in project '%s'?"
|
||||
% os.path.basename(self.currentProject)[:-4],
|
||||
showCancel=True)
|
||||
if ch:
|
||||
self.saveProjectChanges()
|
||||
else:
|
||||
os.remove(self.autosavePath)
|
||||
|
||||
self.openProject(self.currentProject, prompt=False)
|
||||
self.drawPreview()
|
||||
self.drawPreview(True)
|
||||
|
||||
# Setup Hotkeys
|
||||
QtGui.QShortcut("Ctrl+S", self.window, self.saveCurrentProject)
|
||||
|
@ -261,7 +275,8 @@ class MainWindow(QtGui.QMainWindow):
|
|||
appName = 'Audio Visualizer'
|
||||
if self.currentProject:
|
||||
appName += ' - %s' % \
|
||||
os.path.basename(self.currentProject)[:-4]
|
||||
os.path.splitext(
|
||||
os.path.basename(self.currentProject))[0]
|
||||
self.window.setWindowTitle(appName)
|
||||
|
||||
@QtCore.pyqtSlot(int, dict)
|
||||
|
@ -273,7 +288,6 @@ class MainWindow(QtGui.QMainWindow):
|
|||
else:
|
||||
modified = (presetStore != self.core.savedPresets[name])
|
||||
else:
|
||||
print(pos, presetStore)
|
||||
modified = bool(presetStore)
|
||||
if pos < 0:
|
||||
pos = len(self.core.selectedComponents)-1
|
||||
|
@ -327,15 +341,26 @@ class MainWindow(QtGui.QMainWindow):
|
|||
self.lastAutosave = time.time()
|
||||
|
||||
def autosaveExists(self, identical=True):
|
||||
if self.currentProject and os.path.exists(self.autosavePath) \
|
||||
and filecmp.cmp(
|
||||
self.autosavePath, self.currentProject) == identical:
|
||||
return True
|
||||
try:
|
||||
if self.currentProject and os.path.exists(self.autosavePath) \
|
||||
and filecmp.cmp(
|
||||
self.autosavePath, self.currentProject) == identical:
|
||||
return True
|
||||
except FileNotFoundError:
|
||||
print('project file couldn\'t be located:', self.currentProject)
|
||||
return identical
|
||||
return False
|
||||
|
||||
def saveProjectChanges(self):
|
||||
os.remove(self.currentProject)
|
||||
os.rename(self.autosavePath, self.currentProject)
|
||||
try:
|
||||
os.remove(self.currentProject)
|
||||
os.rename(self.autosavePath, self.currentProject)
|
||||
return True
|
||||
except (FileNotFoundError, IsADirectoryError) as e:
|
||||
self.showMessage(
|
||||
msg='Project file couldn\'t be saved.',
|
||||
detail=str(e))
|
||||
return False
|
||||
|
||||
def openInputFileDialog(self):
|
||||
inputDir = self.settings.value("inputDir", os.path.expanduser("~"))
|
||||
|
@ -432,6 +457,7 @@ class MainWindow(QtGui.QMainWindow):
|
|||
self.window.listWidget_componentList.setEnabled(True)
|
||||
self.window.menuButton_newProject.setEnabled(True)
|
||||
self.window.menuButton_openProject.setEnabled(True)
|
||||
self.drawPreview(True)
|
||||
|
||||
def progressBarUpdated(self, value):
|
||||
self.window.progressBar_createVideo.setValue(value)
|
||||
|
@ -458,19 +484,11 @@ class MainWindow(QtGui.QMainWindow):
|
|||
def showPreviewImage(self, image):
|
||||
self.previewWindow.changePixmap(image)
|
||||
|
||||
def insertComponent(self, moduleIndex, compPos=0):
|
||||
def insertComponent(self, index):
|
||||
componentList = self.window.listWidget_componentList
|
||||
stackedWidget = self.window.stackedWidget
|
||||
if compPos < 0:
|
||||
compPos = componentList.count()
|
||||
|
||||
index = self.core.insertComponent(
|
||||
compPos, moduleIndex)
|
||||
if index == None:
|
||||
self.showMessage(msg="Too many components!")
|
||||
return None
|
||||
|
||||
row = componentList.insertItem(
|
||||
componentList.insertItem(
|
||||
index,
|
||||
self.core.selectedComponents[index].__doc__)
|
||||
componentList.setCurrentRow(index)
|
||||
|
@ -479,11 +497,10 @@ class MainWindow(QtGui.QMainWindow):
|
|||
self.core.selectedComponents[index].modified.connect(
|
||||
self.updateComponentTitle)
|
||||
|
||||
self.pages.insert(index, self.core.selectedComponents[index].widget(self))
|
||||
self.pages.insert(index, self.core.selectedComponents[index].page)
|
||||
stackedWidget.insertWidget(index, self.pages[index])
|
||||
stackedWidget.setCurrentIndex(index)
|
||||
|
||||
self.core.updateComponent(index)
|
||||
return index
|
||||
|
||||
def removeComponent(self):
|
||||
|
@ -584,6 +601,7 @@ class MainWindow(QtGui.QMainWindow):
|
|||
self.openSaveProjectDialog()
|
||||
|
||||
def openSaveChangesDialog(self, phrase):
|
||||
success = True
|
||||
if self.autosaveExists(identical=False):
|
||||
ch = self.showMessage(
|
||||
msg="You have unsaved changes in project '%s'. "
|
||||
|
@ -592,9 +610,9 @@ class MainWindow(QtGui.QMainWindow):
|
|||
phrase),
|
||||
showCancel=True)
|
||||
if ch:
|
||||
self.saveProjectChanges()
|
||||
success = self.saveProjectChanges()
|
||||
|
||||
if os.path.exists(self.autosavePath):
|
||||
if success and os.path.exists(self.autosavePath):
|
||||
os.remove(self.autosavePath)
|
||||
|
||||
def openSaveProjectDialog(self):
|
||||
|
@ -620,7 +638,6 @@ class MainWindow(QtGui.QMainWindow):
|
|||
self.openProject(filename)
|
||||
|
||||
def openProject(self, filepath, prompt=True):
|
||||
print('opening', filepath)
|
||||
if not filepath or not os.path.exists(filepath) \
|
||||
or not filepath.endswith('.avp'):
|
||||
self.updateWindowTitle()
|
||||
|
|
|
@ -27,10 +27,9 @@ class Worker(QtCore.QObject):
|
|||
self.core = core.Core()
|
||||
self.core.settings = parent.settings
|
||||
self.modules = parent.core.modules
|
||||
self.stackedWidget = parent.window.stackedWidget
|
||||
self.parent = parent
|
||||
parent.videoTask.connect(self.createVideo)
|
||||
self.sampleSize = 1470
|
||||
self.sampleSize = 1470 # 44100 / 30 = 1470
|
||||
self.canceled = False
|
||||
self.error = False
|
||||
self.stopped = False
|
||||
|
@ -100,7 +99,8 @@ class Worker(QtCore.QObject):
|
|||
|
||||
# test if user has libfdk_aac
|
||||
encoders = sp.check_output(
|
||||
self.core.FFMPEG_BIN + " -encoders -hide_banner", shell=True)
|
||||
self.core.FFMPEG_BIN + " -encoders -hide_banner",
|
||||
shell=True)
|
||||
|
||||
encoders = encoders.decode("utf-8")
|
||||
|
||||
|
@ -121,15 +121,15 @@ class Worker(QtCore.QObject):
|
|||
vencoders = options['video-codecs'][vcodec]
|
||||
aencoders = options['audio-codecs'][acodec]
|
||||
|
||||
print(encoders)
|
||||
#print(encoders)
|
||||
for encoder in vencoders:
|
||||
print(encoder)
|
||||
#print(encoder)
|
||||
if encoder in encoders:
|
||||
vencoder = encoder
|
||||
break
|
||||
|
||||
for encoder in aencoders:
|
||||
print(encoder)
|
||||
#print(encoder)
|
||||
if encoder in encoders:
|
||||
aencoder = encoder
|
||||
break
|
||||
|
@ -162,16 +162,15 @@ class Worker(QtCore.QObject):
|
|||
ffmpegCommand.append('-2')
|
||||
|
||||
ffmpegCommand.append(outputFile)
|
||||
self.out_pipe = sp.Popen(
|
||||
ffmpegCommand, stdin=sp.PIPE, stdout=sys.stdout, stderr=sys.stdout)
|
||||
|
||||
# create video for output
|
||||
# ### Now start creating video for output ###
|
||||
numpy.seterr(divide='ignore')
|
||||
|
||||
# initialize components
|
||||
print('loaded components:',
|
||||
["%s%s" % (num, str(component)) for num,
|
||||
component in enumerate(self.components)])
|
||||
# Call preFrameRender on all components
|
||||
print('Loaded Components:', ", ".join(
|
||||
["%s) %s" % (num, str(component)) \
|
||||
for num, component in enumerate(reversed(self.components))
|
||||
]))
|
||||
self.staticComponents = {}
|
||||
numComps = len(self.components)
|
||||
for compNo, comp in enumerate(self.components):
|
||||
|
@ -191,14 +190,17 @@ class Worker(QtCore.QObject):
|
|||
comp.frameRender(compNo, 0, 0))
|
||||
self.progressBarUpdate.emit(100)
|
||||
|
||||
# Create ffmpeg pipe and queues for frames
|
||||
self.out_pipe = sp.Popen(
|
||||
ffmpegCommand, stdin=sp.PIPE, stdout=sys.stdout, stderr=sys.stdout)
|
||||
self.compositeQueue = Queue()
|
||||
self.compositeQueue.maxsize = 20
|
||||
self.renderQueue = PriorityQueue()
|
||||
self.renderQueue.maxsize = 20
|
||||
self.previewQueue = PriorityQueue()
|
||||
|
||||
self.renderThreads = []
|
||||
# Threads to render frames and send them back here for piping out
|
||||
self.renderThreads = []
|
||||
for i in range(3):
|
||||
self.renderThreads.append(
|
||||
Thread(target=self.renderNode, name="Render Thread"))
|
||||
|
@ -280,7 +282,6 @@ class Worker(QtCore.QObject):
|
|||
|
||||
self.error = False
|
||||
self.canceled = False
|
||||
self.parent.drawPreview()
|
||||
self.stopped = True
|
||||
self.encoding.emit(False)
|
||||
self.videoCreated.emit()
|
||||
|
|
Reference in New Issue