better component error messages
fatal errors cancel the export instead of crashing
This commit is contained in:
parent
bf0890e7c8
commit
d38109453c
157
src/component.py
157
src/component.py
|
@ -5,13 +5,12 @@
|
||||||
from PyQt5 import uic, QtCore, QtWidgets
|
from PyQt5 import uic, QtCore, QtWidgets
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from presetmanager import getPresetDir
|
|
||||||
|
|
||||||
|
|
||||||
def commandWrapper(func):
|
def commandWrapper(func):
|
||||||
'''Intercepts each component's command() method to check for global args'''
|
'''Intercepts each component's command() method to check for global args'''
|
||||||
def decorator(self, arg):
|
def decorator(self, arg):
|
||||||
if arg.startswith('preset='):
|
if arg.startswith('preset='):
|
||||||
|
from presetmanager import getPresetDir
|
||||||
_, preset = arg.split('=', 1)
|
_, preset = arg.split('=', 1)
|
||||||
path = os.path.join(getPresetDir(self), preset)
|
path = os.path.join(getPresetDir(self), preset)
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
|
@ -29,6 +28,26 @@ def commandWrapper(func):
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def propertiesWrapper(func):
|
||||||
|
'''Intercepts the usual properties if the properties are locked.'''
|
||||||
|
def decorator(self):
|
||||||
|
if self._lockedProperties is not None:
|
||||||
|
return self._lockedProperties
|
||||||
|
else:
|
||||||
|
return func(self)
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def errorWrapper(func):
|
||||||
|
'''Intercepts the usual error message if it is locked.'''
|
||||||
|
def decorator(self):
|
||||||
|
if self._lockedError is not None:
|
||||||
|
return self._lockedError
|
||||||
|
else:
|
||||||
|
return func(self)
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
class ComponentMetaclass(type(QtCore.QObject)):
|
class ComponentMetaclass(type(QtCore.QObject)):
|
||||||
'''
|
'''
|
||||||
Checks the validity of each Component class imported, and
|
Checks the validity of each Component class imported, and
|
||||||
|
@ -37,25 +56,33 @@ class ComponentMetaclass(type(QtCore.QObject)):
|
||||||
'''
|
'''
|
||||||
def __new__(cls, name, parents, attrs):
|
def __new__(cls, name, parents, attrs):
|
||||||
if 'ui' not in attrs:
|
if 'ui' not in attrs:
|
||||||
# use module name as ui filename by default
|
# Use module name as ui filename by default
|
||||||
attrs['ui'] = '%s.ui' % os.path.splitext(
|
attrs['ui'] = '%s.ui' % os.path.splitext(
|
||||||
attrs['__module__'].split('.')[-1]
|
attrs['__module__'].split('.')[-1]
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
# Turn certain class methods into properties and classmethods
|
# if parents[0] == QtCore.QObject: else:
|
||||||
for key in ('error', 'properties', 'audio'):
|
decorate = ('names', 'error', 'audio', 'command', 'properties')
|
||||||
|
|
||||||
|
# Auto-decorate methods
|
||||||
|
for key in decorate:
|
||||||
if key not in attrs:
|
if key not in attrs:
|
||||||
continue
|
continue
|
||||||
attrs[key] = property(attrs[key])
|
|
||||||
|
|
||||||
for key in ('names'):
|
if key in ('names'):
|
||||||
if key not in attrs:
|
attrs[key] = classmethod(attrs[key])
|
||||||
continue
|
|
||||||
attrs[key] = classmethod(key)
|
|
||||||
|
|
||||||
# Do not apply these mutations to the base class
|
if key in ('audio'):
|
||||||
if parents[0] != QtCore.QObject:
|
attrs[key] = property(attrs[key])
|
||||||
attrs['command'] = commandWrapper(attrs['command'])
|
|
||||||
|
if key == 'command':
|
||||||
|
attrs[key] = commandWrapper(attrs[key])
|
||||||
|
|
||||||
|
if key == 'properties':
|
||||||
|
attrs[key] = propertiesWrapper(attrs[key])
|
||||||
|
|
||||||
|
if key == 'error':
|
||||||
|
attrs[key] = errorWrapper(attrs[key])
|
||||||
|
|
||||||
# Turn version string into a number
|
# Turn version string into a number
|
||||||
try:
|
try:
|
||||||
|
@ -83,13 +110,13 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
|
||||||
|
|
||||||
name = 'Component'
|
name = 'Component'
|
||||||
# ui = 'nameOfNonDefaultUiFile'
|
# ui = 'nameOfNonDefaultUiFile'
|
||||||
|
|
||||||
version = '1.0.0'
|
version = '1.0.0'
|
||||||
# The major version (before the first dot) is used to determine
|
# The major version (before the first dot) is used to determine
|
||||||
# preset compatibility; the rest is ignored so it can be non-numeric.
|
# preset compatibility; the rest is ignored so it can be non-numeric.
|
||||||
|
|
||||||
modified = QtCore.pyqtSignal(int, dict)
|
modified = QtCore.pyqtSignal(int, dict)
|
||||||
# ^ Signal used to tell core program that the component state changed,
|
_error = QtCore.pyqtSignal(str, str)
|
||||||
# you shouldn't need to use this directly, it is used by self.update()
|
|
||||||
|
|
||||||
def __init__(self, moduleIndex, compPos, core):
|
def __init__(self, moduleIndex, compPos, core):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
@ -100,6 +127,9 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
|
||||||
|
|
||||||
self._trackedWidgets = {}
|
self._trackedWidgets = {}
|
||||||
self._presetNames = {}
|
self._presetNames = {}
|
||||||
|
self._commandArgs = {}
|
||||||
|
self._lockedProperties = None
|
||||||
|
self._lockedError = None
|
||||||
|
|
||||||
# Stop lengthy processes in response to this variable
|
# Stop lengthy processes in response to this variable
|
||||||
self.canceled = False
|
self.canceled = False
|
||||||
|
@ -127,6 +157,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
|
||||||
def error(self):
|
def error(self):
|
||||||
'''
|
'''
|
||||||
Return a string containing an error message, or None for a default.
|
Return a string containing an error message, or None for a default.
|
||||||
|
Or tuple of two strings for a message with details.
|
||||||
'''
|
'''
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -141,12 +172,6 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
|
||||||
https://ffmpeg.org/ffmpeg-filters.html
|
https://ffmpeg.org/ffmpeg-filters.html
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def names():
|
|
||||||
'''
|
|
||||||
Alternative names for renaming a component between project files.
|
|
||||||
'''
|
|
||||||
return []
|
|
||||||
|
|
||||||
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
|
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
|
||||||
# Methods
|
# Methods
|
||||||
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
|
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
|
||||||
|
@ -181,15 +206,29 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
|
||||||
for widget in widgets['comboBox']:
|
for widget in widgets['comboBox']:
|
||||||
widget.currentIndexChanged.connect(self.update)
|
widget.currentIndexChanged.connect(self.update)
|
||||||
|
|
||||||
def trackWidgets(self, trackDict, presetNames=None):
|
def trackWidgets(self, trackDict, **kwargs):
|
||||||
'''
|
'''
|
||||||
Name widgets to track in update(), savePreset(), and loadPreset()
|
Name widgets to track in update(), savePreset(), loadPreset(), and
|
||||||
Accepts a dict with attribute names as keys and widgets as values.
|
command(). Requires a dict of attr names as keys, widgets as values
|
||||||
Optional: a dict of attribute names to map to preset variable names
|
|
||||||
|
Optional args:
|
||||||
|
'presetNames': preset variable names to replace attr names
|
||||||
|
'commandArgs': arg keywords that differ from attr names
|
||||||
|
|
||||||
|
NOTE: Any kwarg key set to None will selectively disable tracking.
|
||||||
'''
|
'''
|
||||||
self._trackedWidgets = trackDict
|
self._trackedWidgets = trackDict
|
||||||
if type(presetNames) is dict:
|
for kwarg in kwargs:
|
||||||
self._presetNames = presetNames
|
try:
|
||||||
|
if kwarg in ('presetNames', 'commandArgs'):
|
||||||
|
setattr(self, '_%s' % kwarg, kwargs[kwarg])
|
||||||
|
else:
|
||||||
|
raise BadComponentInit(
|
||||||
|
self,
|
||||||
|
'Nonsensical keywords to trackWidgets.',
|
||||||
|
immediate=True)
|
||||||
|
except BadComponentInit:
|
||||||
|
continue
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
'''
|
'''
|
||||||
|
@ -277,6 +316,22 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
|
||||||
self.commandHelp()
|
self.commandHelp()
|
||||||
quit(0)
|
quit(0)
|
||||||
|
|
||||||
|
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
|
||||||
|
# "Private" Methods
|
||||||
|
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
|
||||||
|
|
||||||
|
def lockProperties(self, propList):
|
||||||
|
self._lockedProperties = propList
|
||||||
|
|
||||||
|
def lockError(self, msg):
|
||||||
|
self._lockedError = msg
|
||||||
|
|
||||||
|
def unlockProperties(self):
|
||||||
|
self._lockedProperties = None
|
||||||
|
|
||||||
|
def unlockError(self):
|
||||||
|
self._lockedError = None
|
||||||
|
|
||||||
def loadUi(self, filename):
|
def loadUi(self, filename):
|
||||||
'''Load a Qt Designer ui file to use for this component's widget'''
|
'''Load a Qt Designer ui file to use for this component's widget'''
|
||||||
return uic.loadUi(os.path.join(self.core.componentsPath, filename))
|
return uic.loadUi(os.path.join(self.core.componentsPath, filename))
|
||||||
|
@ -287,6 +342,8 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.canceled = False
|
self.canceled = False
|
||||||
|
self.unlockProperties()
|
||||||
|
self.unlockError()
|
||||||
|
|
||||||
'''
|
'''
|
||||||
### Reference methods for creating a new component
|
### Reference methods for creating a new component
|
||||||
|
@ -309,16 +366,40 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
class BadComponentInit(Exception):
|
class BadComponentInit(AttributeError):
|
||||||
'''
|
'''
|
||||||
General purpose exception that components can raise to indicate
|
Indicates a Python error in constructing a component.
|
||||||
a Python issue with e.g., dynamic creation of instances or something.
|
Raising this locks the component into an error state,
|
||||||
Decorative for now, may have future use for logging.
|
and gives the MainWindow a traceback to display.
|
||||||
'''
|
'''
|
||||||
def __init__(self, arg, name):
|
def __init__(self, caller, name, immediate=False):
|
||||||
string = '''################################
|
from toolkit import formatTraceback
|
||||||
Mandatory argument "%s" not specified
|
import sys
|
||||||
in %s instance initialization
|
if sys.exc_info()[0] is not None:
|
||||||
###################################'''
|
string = (
|
||||||
print(string % (arg, name))
|
"%s component's %s encountered %s %s." % (
|
||||||
quit()
|
caller.__class__.name,
|
||||||
|
name,
|
||||||
|
'an' if any([
|
||||||
|
sys.exc_info()[0].__name__.startswith(vowel)
|
||||||
|
for vowel in ('A', 'I')
|
||||||
|
]) else 'a',
|
||||||
|
sys.exc_info()[0].__name__,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
detail = formatTraceback(sys.exc_info()[2])
|
||||||
|
else:
|
||||||
|
string = name
|
||||||
|
detail = "Methods:\n%s" % (
|
||||||
|
"\n".join(
|
||||||
|
[m for m in dir(caller) if not m.startswith('_')]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if immediate:
|
||||||
|
caller.parent.showMessage(
|
||||||
|
msg=string, detail=detail, icon='Warning'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
caller.lockProperties(['error'])
|
||||||
|
caller.lockError((string, detail))
|
||||||
|
|
|
@ -15,7 +15,7 @@ class Component(Component):
|
||||||
name = 'Classic Visualizer'
|
name = 'Classic Visualizer'
|
||||||
version = '1.0.0'
|
version = '1.0.0'
|
||||||
|
|
||||||
def names():
|
def names(*args):
|
||||||
return ['Original Audio Visualization']
|
return ['Original Audio Visualization']
|
||||||
|
|
||||||
def widget(self, *args):
|
def widget(self, *args):
|
||||||
|
|
|
@ -18,6 +18,8 @@ class Component(Component):
|
||||||
'chorus': self.page.checkBox_chorus,
|
'chorus': self.page.checkBox_chorus,
|
||||||
'delay': self.page.spinBox_delay,
|
'delay': self.page.spinBox_delay,
|
||||||
'volume': self.page.spinBox_volume,
|
'volume': self.page.spinBox_volume,
|
||||||
|
}, commandArgs={
|
||||||
|
'sound': None,
|
||||||
})
|
})
|
||||||
|
|
||||||
def previewRender(self, previewWorker):
|
def previewRender(self, previewWorker):
|
||||||
|
|
|
@ -14,7 +14,7 @@ from toolkit import openPipe, checkOutput
|
||||||
|
|
||||||
|
|
||||||
class Video:
|
class Video:
|
||||||
'''Video Component Frame-Fetcher'''
|
'''Opens a pipe to ffmpeg and stores a buffer of raw video frames.'''
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
mandatoryArgs = [
|
mandatoryArgs = [
|
||||||
'ffmpeg', # path to ffmpeg, usually self.core.FFMPEG_BIN
|
'ffmpeg', # path to ffmpeg, usually self.core.FFMPEG_BIN
|
||||||
|
@ -28,10 +28,7 @@ class Video:
|
||||||
'component', # component object
|
'component', # component object
|
||||||
]
|
]
|
||||||
for arg in mandatoryArgs:
|
for arg in mandatoryArgs:
|
||||||
try:
|
setattr(self, arg, kwargs[arg])
|
||||||
setattr(self, arg, kwargs[arg])
|
|
||||||
except KeyError:
|
|
||||||
raise BadComponentInit(arg, self.__doc__)
|
|
||||||
|
|
||||||
self.frameNo = -1
|
self.frameNo = -1
|
||||||
self.currentFrame = 'None'
|
self.currentFrame = 'None'
|
||||||
|
@ -196,13 +193,16 @@ class Component(Component):
|
||||||
height = int(self.settings.value('outputHeight'))
|
height = int(self.settings.value('outputHeight'))
|
||||||
self.blankFrame_ = BlankFrame(width, height)
|
self.blankFrame_ = BlankFrame(width, height)
|
||||||
self.updateChunksize(width, height)
|
self.updateChunksize(width, height)
|
||||||
self.video = Video(
|
try:
|
||||||
ffmpeg=self.core.FFMPEG_BIN, videoPath=self.videoPath,
|
self.video = Video(
|
||||||
width=width, height=height, chunkSize=self.chunkSize,
|
ffmpeg=self.core.FFMPEG_BIN, #videoPath=self.videoPath,
|
||||||
frameRate=int(self.settings.value("outputFrameRate")),
|
width=width, height=height, chunkSize=self.chunkSize,
|
||||||
parent=self.parent, loopVideo=self.loopVideo,
|
frameRate=int(self.settings.value("outputFrameRate")),
|
||||||
component=self, scale=self.scale
|
parent=self.parent, loopVideo=self.loopVideo,
|
||||||
) if os.path.exists(self.videoPath) else None
|
component=self, scale=self.scale
|
||||||
|
) if os.path.exists(self.videoPath) else None
|
||||||
|
except KeyError:
|
||||||
|
raise BadComponentInit(self, 'Frame Fetcher initialization')
|
||||||
|
|
||||||
def frameRender(self, layerNo, frameNo):
|
def frameRender(self, layerNo, frameNo):
|
||||||
if self.video:
|
if self.video:
|
||||||
|
|
10
src/core.py
10
src/core.py
|
@ -22,13 +22,12 @@ class Core:
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.findComponents()
|
self.importComponents()
|
||||||
self.selectedComponents = []
|
self.selectedComponents = []
|
||||||
self.savedPresets = {} # copies of presets to detect modification
|
self.savedPresets = {} # copies of presets to detect modification
|
||||||
self.openingProject = False
|
self.openingProject = False
|
||||||
|
|
||||||
def findComponents(self):
|
def importComponents(self):
|
||||||
'''Imports all the component modules'''
|
|
||||||
def findComponents():
|
def findComponents():
|
||||||
for f in os.listdir(Core.componentsPath):
|
for f in os.listdir(Core.componentsPath):
|
||||||
name, ext = os.path.splitext(f)
|
name, ext = os.path.splitext(f)
|
||||||
|
@ -225,9 +224,8 @@ class Core:
|
||||||
return
|
return
|
||||||
if hasattr(loader, 'createNewProject'):
|
if hasattr(loader, 'createNewProject'):
|
||||||
loader.createNewProject(prompt=False)
|
loader.createNewProject(prompt=False)
|
||||||
import traceback
|
msg = '%s: %s\n\n' % (typ.__name__, value)
|
||||||
msg = '%s: %s\n\nTraceback:\n' % (typ.__name__, value)
|
msg += toolkit.formatTraceback(tb)
|
||||||
msg += "\n".join(traceback.format_tb(tb))
|
|
||||||
loader.showMessage(
|
loader.showMessage(
|
||||||
msg="Project file '%s' is corrupted." % filepath,
|
msg="Project file '%s' is corrupted." % filepath,
|
||||||
showCancel=False,
|
showCancel=False,
|
||||||
|
|
|
@ -571,6 +571,15 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
self.videoWorker.encoding.connect(self.changeEncodingStatus)
|
self.videoWorker.encoding.connect(self.changeEncodingStatus)
|
||||||
self.createVideo.emit()
|
self.createVideo.emit()
|
||||||
|
|
||||||
|
@QtCore.pyqtSlot(str, str)
|
||||||
|
def videoThreadError(self, msg, detail):
|
||||||
|
self.showMessage(
|
||||||
|
msg=msg,
|
||||||
|
detail=detail,
|
||||||
|
icon='Warning',
|
||||||
|
)
|
||||||
|
self.stopVideo()
|
||||||
|
|
||||||
def changeEncodingStatus(self, status):
|
def changeEncodingStatus(self, status):
|
||||||
self.encoding = status
|
self.encoding = status
|
||||||
if status:
|
if status:
|
||||||
|
@ -675,6 +684,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
# connect to signal that adds an asterisk when modified
|
# connect to signal that adds an asterisk when modified
|
||||||
self.core.selectedComponents[index].modified.connect(
|
self.core.selectedComponents[index].modified.connect(
|
||||||
self.updateComponentTitle)
|
self.updateComponentTitle)
|
||||||
|
self.core.selectedComponents[index]._error.connect(
|
||||||
|
self.videoThreadError)
|
||||||
|
|
||||||
self.pages.insert(index, self.core.selectedComponents[index].page)
|
self.pages.insert(index, self.core.selectedComponents[index].page)
|
||||||
stackedWidget.insertWidget(index, self.pages[index])
|
stackedWidget.insertWidget(index, self.pages[index])
|
||||||
|
@ -751,7 +762,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
if mousePos > -1:
|
if mousePos > -1:
|
||||||
change = (componentList.currentRow() - mousePos) * -1
|
change = (componentList.currentRow() - mousePos) * -1
|
||||||
else:
|
else:
|
||||||
change = (componentList.count() - componentList.currentRow() -1)
|
change = (componentList.count() - componentList.currentRow() - 1)
|
||||||
self.moveComponent(change)
|
self.moveComponent(change)
|
||||||
|
|
||||||
def changeComponentWidget(self):
|
def changeComponentWidget(self):
|
||||||
|
@ -936,7 +947,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
if event.type() == QtCore.QEvent.WindowActivate \
|
if event.type() == QtCore.QEvent.WindowActivate \
|
||||||
or event.type() == QtCore.QEvent.FocusIn:
|
or event.type() == QtCore.QEvent.FocusIn:
|
||||||
Core.windowHasFocus = True
|
Core.windowHasFocus = True
|
||||||
elif event.type()== QtCore.QEvent.WindowDeactivate \
|
elif event.type() == QtCore.QEvent.WindowDeactivate \
|
||||||
or event.type() == QtCore.QEvent.FocusOut:
|
or event.type() == QtCore.QEvent.FocusOut:
|
||||||
Core.windowHasFocus = False
|
Core.windowHasFocus = False
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -107,3 +107,11 @@ def rgbFromString(string):
|
||||||
return tup
|
return tup
|
||||||
except:
|
except:
|
||||||
return (255, 255, 255)
|
return (255, 255, 255)
|
||||||
|
|
||||||
|
|
||||||
|
def formatTraceback(tb=None):
|
||||||
|
import traceback
|
||||||
|
if tb is None:
|
||||||
|
import sys
|
||||||
|
tb = sys.exc_info()[2]
|
||||||
|
return 'Traceback:\n%s' % "\n".join(traceback.format_tb(tb))
|
||||||
|
|
|
@ -103,7 +103,7 @@ def createFfmpegCommand(inputFile, outputFile, components, duration=-1):
|
||||||
globalFilters = 0 # increase to add global filters
|
globalFilters = 0 # increase to add global filters
|
||||||
extraAudio = [
|
extraAudio = [
|
||||||
comp.audio for comp in components
|
comp.audio for comp in components
|
||||||
if 'audio' in comp.properties
|
if 'audio' in comp.properties()
|
||||||
]
|
]
|
||||||
if extraAudio or globalFilters > 0:
|
if extraAudio or globalFilters > 0:
|
||||||
# Add -i options for extra input files
|
# Add -i options for extra input files
|
||||||
|
|
|
@ -18,7 +18,7 @@ from threading import Thread, Event
|
||||||
import time
|
import time
|
||||||
import signal
|
import signal
|
||||||
|
|
||||||
import core
|
from component import BadComponentInit
|
||||||
from toolkit import openPipe
|
from toolkit import openPipe
|
||||||
from toolkit.ffmpeg import readAudioFile, createFfmpegCommand
|
from toolkit.ffmpeg import readAudioFile, createFfmpegCommand
|
||||||
from toolkit.frame import Checkerboard
|
from toolkit.frame import Checkerboard
|
||||||
|
@ -105,8 +105,7 @@ class Worker(QtCore.QObject):
|
||||||
|
|
||||||
while not self.stopped:
|
while not self.stopped:
|
||||||
audioI, frame = self.previewQueue.get()
|
audioI, frame = self.previewQueue.get()
|
||||||
if core.Core.windowHasFocus \
|
if time.time() - self.lastPreview >= 0.06 or audioI == 0:
|
||||||
and time.time() - self.lastPreview >= 0.06 or audioI == 0:
|
|
||||||
image = Image.alpha_composite(background.copy(), frame)
|
image = Image.alpha_composite(background.copy(), frame)
|
||||||
self.imageCreated.emit(QtGui.QImage(ImageQt(image)))
|
self.imageCreated.emit(QtGui.QImage(ImageQt(image)))
|
||||||
self.lastPreview = time.time()
|
self.lastPreview = time.time()
|
||||||
|
@ -153,39 +152,48 @@ class Worker(QtCore.QObject):
|
||||||
]))
|
]))
|
||||||
self.staticComponents = {}
|
self.staticComponents = {}
|
||||||
for compNo, comp in enumerate(reversed(self.components)):
|
for compNo, comp in enumerate(reversed(self.components)):
|
||||||
comp.preFrameRender(
|
try:
|
||||||
worker=self,
|
comp.preFrameRender(
|
||||||
completeAudioArray=self.completeAudioArray,
|
worker=self,
|
||||||
sampleSize=self.sampleSize,
|
completeAudioArray=self.completeAudioArray,
|
||||||
progressBarUpdate=self.progressBarUpdate,
|
sampleSize=self.sampleSize,
|
||||||
progressBarSetText=self.progressBarSetText
|
progressBarUpdate=self.progressBarUpdate,
|
||||||
)
|
progressBarSetText=self.progressBarSetText
|
||||||
|
)
|
||||||
|
except BadComponentInit:
|
||||||
|
pass
|
||||||
|
|
||||||
if 'error' in comp.properties:
|
if 'error' in comp.properties():
|
||||||
self.cancel()
|
self.cancel()
|
||||||
self.canceled = True
|
self.canceled = True
|
||||||
canceledByComponent = True
|
canceledByComponent = True
|
||||||
errMsg = "Component #%s encountered an error!" % compNo \
|
compError = comp.error() \
|
||||||
if comp.error is None else 'Component #%s (%s): %s' % (
|
if type(comp.error()) is tuple else (comp.error(), '')
|
||||||
|
errMsg = (
|
||||||
|
"Component #%s encountered an error!" % compNo
|
||||||
|
if comp.error() is None else
|
||||||
|
'Export cancelled by component #%s (%s): %s' % (
|
||||||
str(compNo),
|
str(compNo),
|
||||||
str(comp),
|
str(comp),
|
||||||
comp.error
|
compError[0]
|
||||||
)
|
|
||||||
self.parent.showMessage(
|
|
||||||
msg=errMsg,
|
|
||||||
icon='Warning',
|
|
||||||
parent=None # MainWindow is in a different thread
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
comp._error.emit(errMsg, compError[1])
|
||||||
break
|
break
|
||||||
if 'static' in comp.properties:
|
if 'static' in comp.properties():
|
||||||
self.staticComponents[compNo] = \
|
self.staticComponents[compNo] = \
|
||||||
comp.frameRender(compNo, 0).copy()
|
comp.frameRender(compNo, 0).copy()
|
||||||
|
|
||||||
if self.canceled:
|
if self.canceled:
|
||||||
if canceledByComponent:
|
if canceledByComponent:
|
||||||
print('Export cancelled by component #%s (%s): %s' % (
|
print('Export cancelled by component #%s (%s): %s' % (
|
||||||
compNo, str(comp), comp.error
|
compNo,
|
||||||
))
|
comp.name,
|
||||||
|
'No message.' if comp.error() is None else (
|
||||||
|
comp.error() if type(comp.error()) is str
|
||||||
|
else comp.error()[0])
|
||||||
|
)
|
||||||
|
)
|
||||||
self.cancelExport()
|
self.cancelExport()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
Reference in New Issue