more shapes and custom image option for Life
This commit is contained in:
parent
8b253717f7
commit
cacab464c7
|
@ -0,0 +1,976 @@
|
|||
'''
|
||||
When using GUI mode, this module's object (the main window) takes
|
||||
user input to construct a program state (stored in the Core object).
|
||||
This shows a preview of the video being created and allows for saving
|
||||
projects and exporting the video at a later time.
|
||||
'''
|
||||
from PyQt5 import QtCore, QtGui, uic, QtWidgets
|
||||
from PyQt5.QtWidgets import QMenu, QShortcut
|
||||
from PIL import Image
|
||||
from queue import Queue
|
||||
import sys
|
||||
import os
|
||||
import signal
|
||||
import filecmp
|
||||
import time
|
||||
|
||||
from core import Core
|
||||
import preview_thread
|
||||
from presetmanager import PresetManager
|
||||
from toolkit import disableWhenEncoding, disableWhenOpeningProject, checkOutput
|
||||
|
||||
|
||||
class PreviewWindow(QtWidgets.QLabel):
|
||||
'''
|
||||
Paints the preview QLabel and maintains the aspect ratio when the
|
||||
window is resized.
|
||||
'''
|
||||
|
||||
def __init__(self, parent, img):
|
||||
super(PreviewWindow, self).__init__()
|
||||
self.parent = parent
|
||||
self.setFrameStyle(QtWidgets.QFrame.StyledPanel)
|
||||
self.pixmap = QtGui.QPixmap(img)
|
||||
|
||||
def paintEvent(self, event):
|
||||
size = self.size()
|
||||
painter = QtGui.QPainter(self)
|
||||
point = QtCore.QPoint(0, 0)
|
||||
scaledPix = self.pixmap.scaled(
|
||||
size,
|
||||
QtCore.Qt.KeepAspectRatio,
|
||||
transformMode=QtCore.Qt.SmoothTransformation)
|
||||
|
||||
# start painting the label from left upper corner
|
||||
point.setX((size.width() - scaledPix.width())/2)
|
||||
point.setY((size.height() - scaledPix.height())/2)
|
||||
painter.drawPixmap(point, scaledPix)
|
||||
|
||||
def changePixmap(self, img):
|
||||
self.pixmap = QtGui.QPixmap(img)
|
||||
self.repaint()
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if self.parent.encoding:
|
||||
return
|
||||
|
||||
i = self.parent.window.listWidget_componentList.currentRow()
|
||||
if i >= 0:
|
||||
component = self.parent.core.selectedComponents[i]
|
||||
if not hasattr(component, 'previewClickEvent'):
|
||||
return
|
||||
pos = (event.x(), event.y())
|
||||
size = (self.width(), self.height())
|
||||
component.previewClickEvent(
|
||||
pos, size, event.button()
|
||||
)
|
||||
self.parent.core.updateComponent(i)
|
||||
|
||||
@QtCore.pyqtSlot(str)
|
||||
def threadError(self, msg):
|
||||
self.parent.showMessage(
|
||||
msg=msg,
|
||||
icon='Critical',
|
||||
parent=self
|
||||
)
|
||||
|
||||
|
||||
class MainWindow(QtWidgets.QMainWindow):
|
||||
'''
|
||||
The MainWindow wraps many Core methods in order to update the GUI
|
||||
accordingly. E.g., instead of self.core.openProject(), it will use
|
||||
self.openProject() and update the window titlebar within the wrapper.
|
||||
|
||||
MainWindow manages the autosave feature, although Core has the
|
||||
primary functions for opening and creating project files.
|
||||
'''
|
||||
|
||||
createVideo = QtCore.pyqtSignal()
|
||||
newTask = QtCore.pyqtSignal(list) # for the preview window
|
||||
processTask = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, window, project):
|
||||
QtWidgets.QMainWindow.__init__(self)
|
||||
# print('main thread id: {}'.format(QtCore.QThread.currentThreadId()))
|
||||
self.window = window
|
||||
self.core = Core()
|
||||
|
||||
# widgets of component settings
|
||||
self.pages = []
|
||||
self.lastAutosave = time.time()
|
||||
# list of previous five autosave times, used to reduce update spam
|
||||
self.autosaveTimes = []
|
||||
self.autosaveCooldown = 0.2
|
||||
self.encoding = False
|
||||
|
||||
# Create data directory, load/create settings
|
||||
self.dataDir = Core.dataDir
|
||||
self.presetDir = Core.presetDir
|
||||
self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
|
||||
self.settings = Core.settings
|
||||
self.presetManager = PresetManager(
|
||||
uic.loadUi(
|
||||
os.path.join(Core.wd, 'presetmanager.ui')), self)
|
||||
|
||||
if not os.path.exists(self.dataDir):
|
||||
os.makedirs(self.dataDir)
|
||||
for neededDirectory in (
|
||||
self.presetDir, self.settings.value("projectDir")):
|
||||
if not os.path.exists(neededDirectory):
|
||||
os.mkdir(neededDirectory)
|
||||
|
||||
# Create the preview window and its thread, queues, and timers
|
||||
self.previewWindow = PreviewWindow(self, os.path.join(
|
||||
Core.wd, "background.png"))
|
||||
window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
|
||||
|
||||
self.previewQueue = Queue()
|
||||
self.previewThread = QtCore.QThread(self)
|
||||
self.previewWorker = preview_thread.Worker(self, self.previewQueue)
|
||||
self.previewWorker.error.connect(self.previewWindow.threadError)
|
||||
self.previewWorker.moveToThread(self.previewThread)
|
||||
self.previewWorker.imageCreated.connect(self.showPreviewImage)
|
||||
self.previewThread.start()
|
||||
|
||||
self.timer = QtCore.QTimer(self)
|
||||
self.timer.timeout.connect(self.processTask.emit)
|
||||
self.timer.start(500)
|
||||
|
||||
# Begin decorating the window and connecting events
|
||||
self.window.installEventFilter(self)
|
||||
componentList = self.window.listWidget_componentList
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
window.progressBar_createVideo.setTextVisible(False)
|
||||
else:
|
||||
window.progressLabel.setHidden(True)
|
||||
|
||||
window.toolButton_selectAudioFile.clicked.connect(
|
||||
self.openInputFileDialog)
|
||||
|
||||
window.toolButton_selectOutputFile.clicked.connect(
|
||||
self.openOutputFileDialog)
|
||||
|
||||
def changedField():
|
||||
self.autosave()
|
||||
self.updateWindowTitle()
|
||||
|
||||
window.lineEdit_audioFile.textChanged.connect(changedField)
|
||||
window.lineEdit_outputFile.textChanged.connect(changedField)
|
||||
|
||||
window.progressBar_createVideo.setValue(0)
|
||||
|
||||
window.pushButton_createVideo.clicked.connect(
|
||||
self.createAudioVisualisation)
|
||||
|
||||
window.pushButton_Cancel.clicked.connect(self.stopVideo)
|
||||
|
||||
for i, container in enumerate(Core.encoderOptions['containers']):
|
||||
window.comboBox_videoContainer.addItem(container['name'])
|
||||
if container['name'] == self.settings.value('outputContainer'):
|
||||
selectedContainer = i
|
||||
|
||||
window.comboBox_videoContainer.setCurrentIndex(selectedContainer)
|
||||
window.comboBox_videoContainer.currentIndexChanged.connect(
|
||||
self.updateCodecs
|
||||
)
|
||||
|
||||
self.updateCodecs()
|
||||
|
||||
for i in range(window.comboBox_videoCodec.count()):
|
||||
codec = window.comboBox_videoCodec.itemText(i)
|
||||
if codec == self.settings.value('outputVideoCodec'):
|
||||
window.comboBox_videoCodec.setCurrentIndex(i)
|
||||
|
||||
for i in range(window.comboBox_audioCodec.count()):
|
||||
codec = window.comboBox_audioCodec.itemText(i)
|
||||
if codec == self.settings.value('outputAudioCodec'):
|
||||
window.comboBox_audioCodec.setCurrentIndex(i)
|
||||
|
||||
window.comboBox_videoCodec.currentIndexChanged.connect(
|
||||
self.updateCodecSettings
|
||||
)
|
||||
|
||||
window.comboBox_audioCodec.currentIndexChanged.connect(
|
||||
self.updateCodecSettings
|
||||
)
|
||||
|
||||
vBitrate = int(self.settings.value('outputVideoBitrate'))
|
||||
aBitrate = int(self.settings.value('outputAudioBitrate'))
|
||||
|
||||
window.spinBox_vBitrate.setValue(vBitrate)
|
||||
window.spinBox_aBitrate.setValue(aBitrate)
|
||||
window.spinBox_vBitrate.valueChanged.connect(self.updateCodecSettings)
|
||||
window.spinBox_aBitrate.valueChanged.connect(self.updateCodecSettings)
|
||||
|
||||
# Make component buttons
|
||||
self.compMenu = QMenu()
|
||||
for i, comp in enumerate(self.core.modules):
|
||||
action = self.compMenu.addAction(comp.Component.name)
|
||||
action.triggered.connect(
|
||||
lambda _, item=i: self.core.insertComponent(0, item, self)
|
||||
)
|
||||
|
||||
self.window.pushButton_addComponent.setMenu(self.compMenu)
|
||||
|
||||
componentList.dropEvent = self.dragComponent
|
||||
componentList.itemSelectionChanged.connect(
|
||||
self.changeComponentWidget
|
||||
)
|
||||
componentList.itemSelectionChanged.connect(
|
||||
self.presetManager.clearPresetListSelection
|
||||
)
|
||||
self.window.pushButton_removeComponent.clicked.connect(
|
||||
lambda: self.removeComponent()
|
||||
)
|
||||
|
||||
componentList.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
componentList.customContextMenuRequested.connect(
|
||||
self.componentContextMenu
|
||||
)
|
||||
|
||||
currentRes = str(self.settings.value('outputWidth'))+'x' + \
|
||||
str(self.settings.value('outputHeight'))
|
||||
for i, res in enumerate(Core.resolutions):
|
||||
window.comboBox_resolution.addItem(res)
|
||||
if res == currentRes:
|
||||
currentRes = i
|
||||
window.comboBox_resolution.setCurrentIndex(currentRes)
|
||||
window.comboBox_resolution.currentIndexChanged.connect(
|
||||
self.updateResolution
|
||||
)
|
||||
|
||||
self.window.pushButton_listMoveUp.clicked.connect(
|
||||
lambda: self.moveComponent(-1)
|
||||
)
|
||||
self.window.pushButton_listMoveDown.clicked.connect(
|
||||
lambda: self.moveComponent(1)
|
||||
)
|
||||
|
||||
# Configure the Projects Menu
|
||||
self.projectMenu = QMenu()
|
||||
self.window.menuButton_newProject = self.projectMenu.addAction(
|
||||
"New Project"
|
||||
)
|
||||
self.window.menuButton_newProject.triggered.connect(
|
||||
lambda: self.createNewProject()
|
||||
)
|
||||
self.window.menuButton_openProject = self.projectMenu.addAction(
|
||||
"Open Project"
|
||||
)
|
||||
self.window.menuButton_openProject.triggered.connect(
|
||||
lambda: 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
|
||||
)
|
||||
|
||||
self.updateWindowTitle()
|
||||
window.show()
|
||||
|
||||
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(
|
||||
self.settings.value("projectDir"), 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(True)
|
||||
|
||||
# verify Pillow version
|
||||
if not self.settings.value("pilMsgShown") \
|
||||
and 'post' not in Image.PILLOW_VERSION:
|
||||
self.showMessage(
|
||||
msg="You are using the standard version of the "
|
||||
"Python imaging library (Pillow %s). Upgrade "
|
||||
"to the Pillow-SIMD fork to enable hardware accelerations "
|
||||
"and export videos faster." % Image.PILLOW_VERSION
|
||||
)
|
||||
self.settings.setValue("pilMsgShown", True)
|
||||
|
||||
# verify Ffmpeg version
|
||||
if not self.settings.value("ffmpegMsgShown"):
|
||||
try:
|
||||
with open(os.devnull, "w") as f:
|
||||
ffmpegVers = checkOutput(
|
||||
['ffmpeg', '-version'], stderr=f
|
||||
)
|
||||
goodVersion = str(ffmpegVers).split()[2].startswith('3')
|
||||
except Exception:
|
||||
goodVersion = False
|
||||
else:
|
||||
goodVersion = True
|
||||
|
||||
if not goodVersion:
|
||||
self.showMessage(
|
||||
msg="You're using an old version of Ffmpeg. "
|
||||
"Some features may not work as expected."
|
||||
)
|
||||
self.settings.setValue("ffmpegMsgShown", True)
|
||||
|
||||
# Hotkeys for projects
|
||||
QtWidgets.QShortcut("Ctrl+S", self.window, self.saveCurrentProject)
|
||||
QtWidgets.QShortcut("Ctrl+A", self.window, self.openSaveProjectDialog)
|
||||
QtWidgets.QShortcut("Ctrl+O", self.window, self.openOpenProjectDialog)
|
||||
QtWidgets.QShortcut("Ctrl+N", self.window, self.createNewProject)
|
||||
|
||||
# Hotkeys for component list
|
||||
for inskey in ("Ctrl+T", QtCore.Qt.Key_Insert):
|
||||
QtWidgets.QShortcut(
|
||||
inskey, self.window,
|
||||
activated=lambda: self.window.pushButton_addComponent.click()
|
||||
)
|
||||
for delkey in ("Ctrl+R", QtCore.Qt.Key_Delete):
|
||||
QtWidgets.QShortcut(
|
||||
delkey, self.window.listWidget_componentList,
|
||||
self.removeComponent
|
||||
)
|
||||
QtWidgets.QShortcut(
|
||||
"Ctrl+Space", self.window,
|
||||
activated=lambda: self.window.listWidget_componentList.setFocus()
|
||||
)
|
||||
QtWidgets.QShortcut(
|
||||
"Ctrl+Shift+S", self.window,
|
||||
self.presetManager.openSavePresetDialog
|
||||
)
|
||||
QtWidgets.QShortcut(
|
||||
"Ctrl+Shift+C", self.window, self.presetManager.clearPreset
|
||||
)
|
||||
|
||||
QtWidgets.QShortcut(
|
||||
"Ctrl+Up", self.window.listWidget_componentList,
|
||||
activated=lambda: self.moveComponent(-1)
|
||||
)
|
||||
QtWidgets.QShortcut(
|
||||
"Ctrl+Down", self.window.listWidget_componentList,
|
||||
activated=lambda: self.moveComponent(1)
|
||||
)
|
||||
QtWidgets.QShortcut(
|
||||
"Ctrl+Home", self.window.listWidget_componentList,
|
||||
activated=lambda: self.moveComponent('top')
|
||||
)
|
||||
QtWidgets.QShortcut(
|
||||
"Ctrl+End", self.window.listWidget_componentList,
|
||||
activated=lambda: self.moveComponent('bottom')
|
||||
)
|
||||
|
||||
# Debug Hotkeys
|
||||
QtWidgets.QShortcut(
|
||||
"Ctrl+Alt+Shift+R", self.window, self.drawPreview
|
||||
)
|
||||
QtWidgets.QShortcut(
|
||||
"Ctrl+Alt+Shift+F", self.window, self.showFfmpegCommand
|
||||
)
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def cleanUp(self, *args):
|
||||
self.timer.stop()
|
||||
self.previewThread.quit()
|
||||
self.previewThread.wait()
|
||||
|
||||
@disableWhenOpeningProject
|
||||
def updateWindowTitle(self):
|
||||
appName = 'Audio Visualizer'
|
||||
try:
|
||||
if self.currentProject:
|
||||
appName += ' - %s' % \
|
||||
os.path.splitext(
|
||||
os.path.basename(self.currentProject))[0]
|
||||
if self.autosaveExists(identical=False):
|
||||
appName += '*'
|
||||
except AttributeError:
|
||||
pass
|
||||
self.window.setWindowTitle(appName)
|
||||
|
||||
@QtCore.pyqtSlot(int, dict)
|
||||
def updateComponentTitle(self, pos, presetStore=False):
|
||||
if type(presetStore) == dict:
|
||||
name = presetStore['preset']
|
||||
if name is None or name not in self.core.savedPresets:
|
||||
modified = False
|
||||
else:
|
||||
modified = (presetStore != self.core.savedPresets[name])
|
||||
else:
|
||||
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):
|
||||
containerWidget = self.window.comboBox_videoContainer
|
||||
vCodecWidget = self.window.comboBox_videoCodec
|
||||
aCodecWidget = self.window.comboBox_audioCodec
|
||||
index = containerWidget.currentIndex()
|
||||
name = containerWidget.itemText(index)
|
||||
self.settings.setValue('outputContainer', name)
|
||||
|
||||
vCodecWidget.clear()
|
||||
aCodecWidget.clear()
|
||||
|
||||
for container in Core.encoderOptions['containers']:
|
||||
if container['name'] == name:
|
||||
for vCodec in container['video-codecs']:
|
||||
vCodecWidget.addItem(vCodec)
|
||||
for aCodec in container['audio-codecs']:
|
||||
aCodecWidget.addItem(aCodec)
|
||||
|
||||
def updateCodecSettings(self):
|
||||
'''Updates settings.ini to match encoder option widgets'''
|
||||
vCodecWidget = self.window.comboBox_videoCodec
|
||||
vBitrateWidget = self.window.spinBox_vBitrate
|
||||
aBitrateWidget = self.window.spinBox_aBitrate
|
||||
aCodecWidget = self.window.comboBox_audioCodec
|
||||
currentVideoCodec = vCodecWidget.currentIndex()
|
||||
currentVideoCodec = vCodecWidget.itemText(currentVideoCodec)
|
||||
currentVideoBitrate = vBitrateWidget.value()
|
||||
currentAudioCodec = aCodecWidget.currentIndex()
|
||||
currentAudioCodec = aCodecWidget.itemText(currentAudioCodec)
|
||||
currentAudioBitrate = aBitrateWidget.value()
|
||||
self.settings.setValue('outputVideoCodec', currentVideoCodec)
|
||||
self.settings.setValue('outputAudioCodec', currentAudioCodec)
|
||||
self.settings.setValue('outputVideoBitrate', currentVideoBitrate)
|
||||
self.settings.setValue('outputAudioBitrate', currentAudioBitrate)
|
||||
|
||||
@disableWhenOpeningProject
|
||||
def autosave(self, force=False):
|
||||
if not self.currentProject:
|
||||
if os.path.exists(self.autosavePath):
|
||||
os.remove(self.autosavePath)
|
||||
elif force or time.time() - self.lastAutosave >= self.autosaveCooldown:
|
||||
self.core.createProjectFile(self.autosavePath, self.window)
|
||||
self.lastAutosave = time.time()
|
||||
if len(self.autosaveTimes) >= 5:
|
||||
# Do some math to reduce autosave spam. This gives a smooth
|
||||
# curve up to 5 seconds cooldown and maintains that for 30 secs
|
||||
# if a component is continuously updated
|
||||
timeDiff = self.lastAutosave - self.autosaveTimes.pop()
|
||||
if not force and timeDiff >= 1.0 \
|
||||
and timeDiff <= 10.0:
|
||||
if self.autosaveCooldown / 4.0 < 0.5:
|
||||
self.autosaveCooldown += 1.0
|
||||
self.autosaveCooldown = (
|
||||
5.0 * (self.autosaveCooldown / 5.0)
|
||||
) + (self.autosaveCooldown / 5.0) * 2
|
||||
elif force or timeDiff >= self.autosaveCooldown * 5:
|
||||
self.autosaveCooldown = 0.2
|
||||
self.autosaveTimes.insert(0, self.lastAutosave)
|
||||
|
||||
def autosaveExists(self, identical=True):
|
||||
'''Determines if creating the autosave should be blocked.'''
|
||||
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):
|
||||
'''Overwrites project file with autosave file'''
|
||||
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("~"))
|
||||
|
||||
fileName, _ = QtWidgets.QFileDialog.getOpenFileName(
|
||||
self.window, "Open Audio File",
|
||||
inputDir, "Audio Files (%s)" % " ".join(Core.audioFormats))
|
||||
|
||||
if fileName:
|
||||
self.settings.setValue("inputDir", os.path.dirname(fileName))
|
||||
self.window.lineEdit_audioFile.setText(fileName)
|
||||
|
||||
def openOutputFileDialog(self):
|
||||
outputDir = self.settings.value("outputDir", os.path.expanduser("~"))
|
||||
|
||||
fileName, _ = QtWidgets.QFileDialog.getSaveFileName(
|
||||
self.window, "Set Output Video File",
|
||||
outputDir,
|
||||
"Video Files (%s);; All Files (*)" % " ".join(
|
||||
Core.videoFormats))
|
||||
|
||||
if fileName:
|
||||
self.settings.setValue("outputDir", os.path.dirname(fileName))
|
||||
self.window.lineEdit_outputFile.setText(fileName)
|
||||
|
||||
def stopVideo(self):
|
||||
print('stop')
|
||||
self.videoWorker.cancel()
|
||||
self.canceled = True
|
||||
|
||||
def createAudioVisualisation(self):
|
||||
# create output video if mandatory settings are filled in
|
||||
audioFile = self.window.lineEdit_audioFile.text()
|
||||
outputPath = self.window.lineEdit_outputFile.text()
|
||||
|
||||
if audioFile and outputPath and self.core.selectedComponents:
|
||||
if not os.path.dirname(outputPath):
|
||||
outputPath = os.path.join(
|
||||
os.path.expanduser("~"), outputPath)
|
||||
if outputPath and os.path.isdir(outputPath):
|
||||
self.showMessage(
|
||||
msg='Chosen filename matches a directory, which '
|
||||
'cannot be overwritten. Please choose a different '
|
||||
'filename or move the directory.',
|
||||
icon='Warning',
|
||||
)
|
||||
return
|
||||
else:
|
||||
if not audioFile or not outputPath:
|
||||
self.showMessage(
|
||||
msg="You must select an audio file and output filename."
|
||||
)
|
||||
elif not self.core.selectedComponents:
|
||||
self.showMessage(
|
||||
msg="Not enough components."
|
||||
)
|
||||
return
|
||||
|
||||
self.canceled = False
|
||||
self.progressBarUpdated(-1)
|
||||
self.videoWorker = self.core.newVideoWorker(
|
||||
self, audioFile, outputPath
|
||||
)
|
||||
self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated)
|
||||
self.videoWorker.progressBarSetText.connect(
|
||||
self.progressBarSetText)
|
||||
self.videoWorker.imageCreated.connect(self.showPreviewImage)
|
||||
self.videoWorker.encoding.connect(self.changeEncodingStatus)
|
||||
self.createVideo.emit()
|
||||
|
||||
@QtCore.pyqtSlot(str, str)
|
||||
def videoThreadError(self, msg, detail):
|
||||
try:
|
||||
self.stopVideo()
|
||||
except AttributeError as e:
|
||||
if 'videoWorker' not in str(e):
|
||||
raise
|
||||
self.showMessage(
|
||||
msg=msg,
|
||||
detail=detail,
|
||||
icon='Critical',
|
||||
)
|
||||
|
||||
def changeEncodingStatus(self, status):
|
||||
self.encoding = status
|
||||
if status:
|
||||
self.window.pushButton_createVideo.setEnabled(False)
|
||||
self.window.pushButton_Cancel.setEnabled(True)
|
||||
self.window.comboBox_resolution.setEnabled(False)
|
||||
self.window.stackedWidget.setEnabled(False)
|
||||
self.window.tab_encoderSettings.setEnabled(False)
|
||||
self.window.label_audioFile.setEnabled(False)
|
||||
self.window.toolButton_selectAudioFile.setEnabled(False)
|
||||
self.window.label_outputFile.setEnabled(False)
|
||||
self.window.toolButton_selectOutputFile.setEnabled(False)
|
||||
self.window.lineEdit_audioFile.setEnabled(False)
|
||||
self.window.lineEdit_outputFile.setEnabled(False)
|
||||
self.window.pushButton_addComponent.setEnabled(False)
|
||||
self.window.pushButton_removeComponent.setEnabled(False)
|
||||
self.window.pushButton_listMoveDown.setEnabled(False)
|
||||
self.window.pushButton_listMoveUp.setEnabled(False)
|
||||
self.window.menuButton_newProject.setEnabled(False)
|
||||
self.window.menuButton_openProject.setEnabled(False)
|
||||
if sys.platform == 'darwin':
|
||||
self.window.progressLabel.setHidden(False)
|
||||
else:
|
||||
self.window.listWidget_componentList.setEnabled(False)
|
||||
else:
|
||||
self.window.pushButton_createVideo.setEnabled(True)
|
||||
self.window.pushButton_Cancel.setEnabled(False)
|
||||
self.window.comboBox_resolution.setEnabled(True)
|
||||
self.window.stackedWidget.setEnabled(True)
|
||||
self.window.tab_encoderSettings.setEnabled(True)
|
||||
self.window.label_audioFile.setEnabled(True)
|
||||
self.window.toolButton_selectAudioFile.setEnabled(True)
|
||||
self.window.lineEdit_audioFile.setEnabled(True)
|
||||
self.window.label_outputFile.setEnabled(True)
|
||||
self.window.toolButton_selectOutputFile.setEnabled(True)
|
||||
self.window.lineEdit_outputFile.setEnabled(True)
|
||||
self.window.pushButton_addComponent.setEnabled(True)
|
||||
self.window.pushButton_removeComponent.setEnabled(True)
|
||||
self.window.pushButton_listMoveDown.setEnabled(True)
|
||||
self.window.pushButton_listMoveUp.setEnabled(True)
|
||||
self.window.menuButton_newProject.setEnabled(True)
|
||||
self.window.menuButton_openProject.setEnabled(True)
|
||||
self.window.listWidget_componentList.setEnabled(True)
|
||||
self.window.progressLabel.setHidden(True)
|
||||
self.drawPreview(True)
|
||||
|
||||
@QtCore.pyqtSlot(int)
|
||||
def progressBarUpdated(self, value):
|
||||
self.window.progressBar_createVideo.setValue(value)
|
||||
|
||||
@QtCore.pyqtSlot(str)
|
||||
def progressBarSetText(self, value):
|
||||
if sys.platform == 'darwin':
|
||||
self.window.progressLabel.setText(value)
|
||||
else:
|
||||
self.window.progressBar_createVideo.setFormat(value)
|
||||
|
||||
def updateResolution(self):
|
||||
resIndex = int(self.window.comboBox_resolution.currentIndex())
|
||||
res = Core.resolutions[resIndex].split('x')
|
||||
changed = res[0] != self.settings.value("outputWidth")
|
||||
self.settings.setValue('outputWidth', res[0])
|
||||
self.settings.setValue('outputHeight', res[1])
|
||||
if changed:
|
||||
for i in range(len(self.core.selectedComponents)):
|
||||
self.core.updateComponent(i)
|
||||
|
||||
def drawPreview(self, force=False, **kwargs):
|
||||
'''Use autosave keyword arg to force saving or not saving if needed'''
|
||||
self.newTask.emit(self.core.selectedComponents)
|
||||
# self.processTask.emit()
|
||||
if force or 'autosave' in kwargs:
|
||||
if force or kwargs['autosave']:
|
||||
self.autosave(True)
|
||||
else:
|
||||
self.autosave()
|
||||
self.updateWindowTitle()
|
||||
|
||||
@QtCore.pyqtSlot(QtGui.QImage)
|
||||
def showPreviewImage(self, image):
|
||||
self.previewWindow.changePixmap(image)
|
||||
|
||||
def showFfmpegCommand(self):
|
||||
from textwrap import wrap
|
||||
from toolkit.ffmpeg import createFfmpegCommand
|
||||
command = createFfmpegCommand(
|
||||
self.window.lineEdit_audioFile.text(),
|
||||
self.window.lineEdit_outputFile.text(),
|
||||
self.core.selectedComponents
|
||||
)
|
||||
lines = wrap(" ".join(command), 49)
|
||||
self.showMessage(
|
||||
msg="Current FFmpeg command:\n\n %s" % " ".join(lines)
|
||||
)
|
||||
|
||||
def insertComponent(self, index):
|
||||
componentList = self.window.listWidget_componentList
|
||||
stackedWidget = self.window.stackedWidget
|
||||
|
||||
componentList.insertItem(
|
||||
index,
|
||||
self.core.selectedComponents[index].name)
|
||||
componentList.setCurrentRow(index)
|
||||
|
||||
# connect to signal that adds an asterisk when modified
|
||||
self.core.selectedComponents[index].modified.connect(
|
||||
self.updateComponentTitle)
|
||||
|
||||
self.pages.insert(index, self.core.selectedComponents[index].page)
|
||||
stackedWidget.insertWidget(index, self.pages[index])
|
||||
stackedWidget.setCurrentIndex(index)
|
||||
|
||||
return index
|
||||
|
||||
def removeComponent(self):
|
||||
componentList = self.window.listWidget_componentList
|
||||
|
||||
for selected in componentList.selectedItems():
|
||||
index = componentList.row(selected)
|
||||
self.window.stackedWidget.removeWidget(self.pages[index])
|
||||
componentList.takeItem(index)
|
||||
self.core.removeComponent(index)
|
||||
self.pages.pop(index)
|
||||
self.changeComponentWidget()
|
||||
self.drawPreview()
|
||||
|
||||
@disableWhenEncoding
|
||||
def moveComponent(self, change):
|
||||
'''Moves a component relatively from its current position'''
|
||||
componentList = self.window.listWidget_componentList
|
||||
if change == 'top':
|
||||
change = -componentList.currentRow()
|
||||
elif change == 'bottom':
|
||||
change = len(componentList)-componentList.currentRow()-1
|
||||
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(True)
|
||||
|
||||
def getComponentListMousePos(self, position):
|
||||
'''
|
||||
Given a QPos, returns the component index under the mouse cursor
|
||||
or -1 if no component is there.
|
||||
'''
|
||||
componentList = self.window.listWidget_componentList
|
||||
|
||||
modelIndexes = [
|
||||
componentList.model().index(i)
|
||||
for i in range(componentList.count())
|
||||
]
|
||||
rects = [
|
||||
componentList.visualRect(modelIndex)
|
||||
for modelIndex in modelIndexes
|
||||
]
|
||||
mousePos = [rect.contains(position) for rect in rects]
|
||||
if not any(mousePos):
|
||||
# Not clicking a component
|
||||
mousePos = -1
|
||||
else:
|
||||
mousePos = mousePos.index(True)
|
||||
return mousePos
|
||||
|
||||
@disableWhenEncoding
|
||||
def dragComponent(self, event):
|
||||
'''Used as Qt drop event for the component listwidget'''
|
||||
componentList = self.window.listWidget_componentList
|
||||
mousePos = self.getComponentListMousePos(event.pos())
|
||||
if mousePos > -1:
|
||||
change = (componentList.currentRow() - mousePos) * -1
|
||||
else:
|
||||
change = (componentList.count() - componentList.currentRow() - 1)
|
||||
self.moveComponent(change)
|
||||
|
||||
def changeComponentWidget(self):
|
||||
selected = self.window.listWidget_componentList.selectedItems()
|
||||
if selected:
|
||||
index = self.window.listWidget_componentList.row(selected[0])
|
||||
self.window.stackedWidget.setCurrentIndex(index)
|
||||
|
||||
def openPresetManager(self):
|
||||
'''Preset manager for importing, exporting, renaming, deleting'''
|
||||
self.presetManager.show()
|
||||
|
||||
def clear(self):
|
||||
'''Get a blank slate'''
|
||||
self.core.clearComponents()
|
||||
self.window.listWidget_componentList.clear()
|
||||
for widget in self.pages:
|
||||
self.window.stackedWidget.removeWidget(widget)
|
||||
self.pages = []
|
||||
for field in (
|
||||
self.window.lineEdit_audioFile,
|
||||
self.window.lineEdit_outputFile
|
||||
):
|
||||
field.blockSignals(True)
|
||||
field.setText('')
|
||||
field.blockSignals(False)
|
||||
self.progressBarUpdated(0)
|
||||
self.progressBarSetText('')
|
||||
|
||||
@disableWhenEncoding
|
||||
def createNewProject(self, prompt=True):
|
||||
if prompt:
|
||||
self.openSaveChangesDialog('starting a new project')
|
||||
|
||||
self.clear()
|
||||
self.currentProject = None
|
||||
self.settings.setValue("currentProject", None)
|
||||
self.drawPreview(True)
|
||||
|
||||
def saveCurrentProject(self):
|
||||
if self.currentProject:
|
||||
self.core.createProjectFile(self.currentProject, self.window)
|
||||
try:
|
||||
os.remove(self.autosavePath)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
self.updateWindowTitle()
|
||||
else:
|
||||
self.openSaveProjectDialog()
|
||||
|
||||
def openSaveChangesDialog(self, phrase):
|
||||
success = True
|
||||
if self.autosaveExists(identical=False):
|
||||
ch = self.showMessage(
|
||||
msg="You have unsaved changes in project '%s'. "
|
||||
"Save before %s?" % (
|
||||
os.path.basename(self.currentProject)[:-4],
|
||||
phrase
|
||||
),
|
||||
showCancel=True)
|
||||
if ch:
|
||||
success = self.saveProjectChanges()
|
||||
|
||||
if success and os.path.exists(self.autosavePath):
|
||||
os.remove(self.autosavePath)
|
||||
|
||||
def openSaveProjectDialog(self):
|
||||
filename, _ = QtWidgets.QFileDialog.getSaveFileName(
|
||||
self.window, "Create Project File",
|
||||
self.settings.value("projectDir"),
|
||||
"Project Files (*.avp)")
|
||||
if not filename:
|
||||
return
|
||||
if not filename.endswith(".avp"):
|
||||
filename += '.avp'
|
||||
self.settings.setValue("projectDir", os.path.dirname(filename))
|
||||
self.settings.setValue("currentProject", filename)
|
||||
self.currentProject = filename
|
||||
self.core.createProjectFile(filename, self.window)
|
||||
self.updateWindowTitle()
|
||||
|
||||
@disableWhenEncoding
|
||||
def openOpenProjectDialog(self):
|
||||
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
|
||||
self.window, "Open Project File",
|
||||
self.settings.value("projectDir"),
|
||||
"Project Files (*.avp)")
|
||||
self.openProject(filename)
|
||||
|
||||
def openProject(self, filepath, prompt=True):
|
||||
if not filepath or not os.path.exists(filepath) \
|
||||
or not filepath.endswith('.avp'):
|
||||
return
|
||||
|
||||
self.clear()
|
||||
# ask to save any changes that are about to get deleted
|
||||
if prompt:
|
||||
self.openSaveChangesDialog('opening another project')
|
||||
|
||||
self.currentProject = filepath
|
||||
self.settings.setValue("currentProject", filepath)
|
||||
self.settings.setValue("projectDir", os.path.dirname(filepath))
|
||||
# actually load the project using core method
|
||||
self.core.openProject(self, filepath)
|
||||
self.drawPreview(autosave=False)
|
||||
self.updateWindowTitle()
|
||||
|
||||
def showMessage(self, **kwargs):
|
||||
parent = kwargs['parent'] if 'parent' in kwargs else self.window
|
||||
msg = QtWidgets.QMessageBox(parent)
|
||||
msg.setModal(True)
|
||||
msg.setText(kwargs['msg'])
|
||||
msg.setIcon(
|
||||
eval('QtWidgets.QMessageBox.%s' % kwargs['icon'])
|
||||
if 'icon' in kwargs else QtWidgets.QMessageBox.Information
|
||||
)
|
||||
msg.setDetailedText(kwargs['detail'] if 'detail' in kwargs else None)
|
||||
if 'showCancel'in kwargs and kwargs['showCancel']:
|
||||
msg.setStandardButtons(
|
||||
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
|
||||
else:
|
||||
msg.setStandardButtons(QtWidgets.QMessageBox.Ok)
|
||||
ch = msg.exec_()
|
||||
if ch == 1024:
|
||||
return True
|
||||
return False
|
||||
|
||||
@disableWhenEncoding
|
||||
def componentContextMenu(self, QPos):
|
||||
'''Appears when right-clicking the component list'''
|
||||
componentList = self.window.listWidget_componentList
|
||||
self.menu = QMenu()
|
||||
parentPosition = componentList.mapToGlobal(QtCore.QPoint(0, 0))
|
||||
|
||||
index = self.getComponentListMousePos(QPos)
|
||||
if index > -1:
|
||||
# Show preset menu if clicking a component
|
||||
self.presetManager.findPresets()
|
||||
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.presetSubmenu = QMenu("Open Preset")
|
||||
self.menu.addMenu(self.presetSubmenu)
|
||||
|
||||
for version, presetName in presets:
|
||||
menuItem = self.presetSubmenu.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.addSeparator()
|
||||
|
||||
# "Add Component" submenu
|
||||
self.submenu = QMenu("Add")
|
||||
self.menu.addMenu(self.submenu)
|
||||
insertCompAtTop = self.settings.value("pref_insertCompAtTop")
|
||||
for i, comp in enumerate(self.core.modules):
|
||||
menuItem = self.submenu.addAction(comp.Component.name)
|
||||
menuItem.triggered.connect(
|
||||
lambda _, item=i: self.core.insertComponent(
|
||||
0 if insertCompAtTop else index, item, self
|
||||
)
|
||||
)
|
||||
|
||||
self.menu.move(parentPosition + QPos)
|
||||
self.menu.show()
|
||||
|
||||
def eventFilter(self, object, event):
|
||||
if event.type() == QtCore.QEvent.WindowActivate \
|
||||
or event.type() == QtCore.QEvent.FocusIn:
|
||||
Core.windowHasFocus = True
|
||||
elif event.type() == QtCore.QEvent.WindowDeactivate \
|
||||
or event.type() == QtCore.QEvent.FocusOut:
|
||||
Core.windowHasFocus = False
|
||||
return False
|
|
@ -1,10 +1,10 @@
|
|||
from PyQt5 import QtGui, QtCore, QtWidgets
|
||||
from PIL import ImageDraw, ImageEnhance, ImageChops, ImageFilter
|
||||
from PIL import Image, ImageDraw, ImageEnhance, ImageChops, ImageFilter
|
||||
import os
|
||||
import math
|
||||
|
||||
from component import Component
|
||||
from toolkit.frame import BlankFrame, FramePainter
|
||||
from toolkit.frame import BlankFrame, scale
|
||||
|
||||
|
||||
class Component(Component):
|
||||
|
@ -16,19 +16,51 @@ class Component(Component):
|
|||
self.scale = 32
|
||||
self.updateGridSize()
|
||||
self.startingGrid = {}
|
||||
self.page.pushButton_pickImage.clicked.connect(self.pickImage)
|
||||
self.trackWidgets({
|
||||
'tickRate': self.page.spinBox_tickRate,
|
||||
'scale': self.page.spinBox_scale,
|
||||
'color': self.page.lineEdit_color,
|
||||
'shapeType': self.page.comboBox_shapeType,
|
||||
'shadow': self.page.checkBox_shadow,
|
||||
'customImg': self.page.checkBox_customImg,
|
||||
'image': self.page.lineEdit_image,
|
||||
}, colorWidgets={
|
||||
'color': self.page.pushButton_color,
|
||||
})
|
||||
self.page.spinBox_scale.setValue(self.scale)
|
||||
self.page.spinBox_scale.valueChanged.connect(self.updateGridSize)
|
||||
|
||||
def pickImage(self):
|
||||
imgDir = self.settings.value("componentDir", os.path.expanduser("~"))
|
||||
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
|
||||
self.page, "Choose Image", imgDir,
|
||||
"Image Files (%s)" % " ".join(self.core.imageFormats))
|
||||
if filename:
|
||||
self.settings.setValue("componentDir", os.path.dirname(filename))
|
||||
self.page.lineEdit_image.setText(filename)
|
||||
self.update()
|
||||
|
||||
def update(self):
|
||||
self.updateGridSize()
|
||||
if self.page.checkBox_customImg.isChecked():
|
||||
self.page.label_color.setVisible(False)
|
||||
self.page.lineEdit_color.setVisible(False)
|
||||
self.page.pushButton_color.setVisible(False)
|
||||
self.page.label_shape.setVisible(False)
|
||||
self.page.comboBox_shapeType.setVisible(False)
|
||||
self.page.label_image.setVisible(True)
|
||||
self.page.lineEdit_image.setVisible(True)
|
||||
self.page.pushButton_pickImage.setVisible(True)
|
||||
else:
|
||||
self.page.label_color.setVisible(True)
|
||||
self.page.lineEdit_color.setVisible(True)
|
||||
self.page.pushButton_color.setVisible(True)
|
||||
self.page.label_shape.setVisible(True)
|
||||
self.page.comboBox_shapeType.setVisible(True)
|
||||
self.page.label_image.setVisible(False)
|
||||
self.page.lineEdit_image.setVisible(False)
|
||||
self.page.pushButton_pickImage.setVisible(False)
|
||||
super().update()
|
||||
|
||||
def previewClickEvent(self, pos, size, button):
|
||||
|
@ -59,6 +91,8 @@ class Component(Component):
|
|||
for frameNo in range(
|
||||
self.tickRate, len(self.completeAudioArray), self.sampleSize
|
||||
):
|
||||
if self.parent.canceled:
|
||||
break
|
||||
if frameNo % self.tickRate == 0:
|
||||
tick += 1
|
||||
self.tickGrids[tick] = self.gridForTick(tick)
|
||||
|
@ -71,6 +105,16 @@ class Component(Component):
|
|||
self.progressBarSetText.emit(pStr)
|
||||
self.progressBarUpdate.emit(int(progress))
|
||||
|
||||
def properties(self):
|
||||
if self.customImg and (
|
||||
not self.image or not os.path.exists(self.image)
|
||||
):
|
||||
return ['error']
|
||||
return []
|
||||
|
||||
def error(self):
|
||||
return "No image selected to represent life."
|
||||
|
||||
def frameRender(self, frameNo):
|
||||
tick = math.floor(frameNo / self.tickRate)
|
||||
grid = self.tickGrids[tick]
|
||||
|
@ -78,23 +122,124 @@ class Component(Component):
|
|||
|
||||
def drawGrid(self, grid):
|
||||
frame = BlankFrame(self.width, self.height)
|
||||
drawer = ImageDraw.Draw(frame)
|
||||
|
||||
def drawCustomImg():
|
||||
try:
|
||||
img = Image.open(self.image)
|
||||
except Exception:
|
||||
return
|
||||
img = img.resize((self.pxWidth, self.pxHeight), Image.ANTIALIAS)
|
||||
frame.paste(img, box=(drawPtX, drawPtY))
|
||||
|
||||
def drawShape():
|
||||
drawer = ImageDraw.Draw(frame)
|
||||
|
||||
# Rectangle
|
||||
if self.shapeType == 0:
|
||||
drawer.rectangle(rect, fill=self.color)
|
||||
|
||||
# Ellipse
|
||||
elif self.shapeType == 1:
|
||||
drawer.ellipse(rect, fill=self.color)
|
||||
|
||||
tenthX, tenthY = scale(10, self.pxWidth, self.pxHeight, int)
|
||||
smallerShape = (
|
||||
(drawPtX + tenthX + int(tenthX / 4),
|
||||
drawPtY + tenthY + int(tenthY / 2)),
|
||||
(drawPtX + self.pxWidth - tenthX - int(tenthX / 4),
|
||||
drawPtY + self.pxHeight - (tenthY + int(tenthY / 2)))
|
||||
)
|
||||
outlineShape = (
|
||||
(drawPtX + int(tenthX / 4),
|
||||
drawPtY + int(tenthY / 2)),
|
||||
(drawPtX + self.pxWidth - int(tenthX / 4),
|
||||
drawPtY + self.pxHeight - int(tenthY / 2))
|
||||
)
|
||||
|
||||
# Circle
|
||||
if self.shapeType == 2:
|
||||
drawer.ellipse(outlineShape, fill=self.color)
|
||||
drawer.ellipse(smallerShape, fill=(0,0,0,0))
|
||||
|
||||
# Lilypad
|
||||
elif self.shapeType == 3:
|
||||
drawer.pieslice(smallerShape, 290, 250, fill=self.color)
|
||||
|
||||
# Pac-Man
|
||||
elif self.shapeType == 4:
|
||||
drawer.pieslice(outlineShape, 35, 320, fill=self.color)
|
||||
|
||||
hX, hY = scale(50, self.pxWidth, self.pxHeight, int) # halfline
|
||||
tX, tY = scale(33, self.pxWidth, self.pxHeight, int) # thirdline
|
||||
qX, qY = scale(20, self.pxWidth, self.pxHeight, int) # quarterline
|
||||
|
||||
# Duck
|
||||
if self.shapeType == 5:
|
||||
duckHead = (
|
||||
(drawPtX + qX, drawPtY + qY),
|
||||
(drawPtX + int(qX * 3), drawPtY + int(tY * 2))
|
||||
)
|
||||
duckBeak = (
|
||||
(drawPtX + hX, drawPtY + qY),
|
||||
(drawPtX + self.pxWidth + qX,
|
||||
drawPtY + int(qY * 3))
|
||||
)
|
||||
duckWing = (
|
||||
(drawPtX, drawPtY + hY),
|
||||
rect[1]
|
||||
)
|
||||
duckBody = (
|
||||
(drawPtX + int(qX / 4), drawPtY + int(qY * 3)),
|
||||
(drawPtX + int(tX * 2), drawPtY + self.pxHeight)
|
||||
)
|
||||
drawer.ellipse(duckBody, fill=self.color)
|
||||
drawer.ellipse(duckHead, fill=self.color)
|
||||
drawer.pieslice(duckWing, 130, 200, fill=self.color)
|
||||
drawer.pieslice(duckBeak, 145, 200, fill=self.color)
|
||||
|
||||
# Peace
|
||||
elif self.shapeType == 6:
|
||||
line = (
|
||||
(drawPtX + hX - int(tenthX / 2), drawPtY + int(tenthY / 2)),
|
||||
(drawPtX + hX + int(tenthX / 2),
|
||||
drawPtY + self.pxHeight - int(tenthY / 2))
|
||||
)
|
||||
drawer.ellipse(outlineShape, fill=self.color)
|
||||
drawer.ellipse(smallerShape, fill=(0,0,0,0))
|
||||
drawer.rectangle(line, fill=self.color)
|
||||
slantLine = lambda difference: (
|
||||
((drawPtX + difference),
|
||||
(drawPtY + self.pxHeight - qY)),
|
||||
((drawPtX + hX),
|
||||
(drawPtY + hY)),
|
||||
)
|
||||
drawer.line(
|
||||
slantLine(qX),
|
||||
fill=self.color,
|
||||
width=tenthX
|
||||
)
|
||||
drawer.line(
|
||||
slantLine(self.pxWidth - qX),
|
||||
fill=self.color,
|
||||
width=tenthX
|
||||
)
|
||||
|
||||
for x, y in grid:
|
||||
drawPtX = x * self.pxWidth
|
||||
if drawPtX > self.width:
|
||||
continue
|
||||
drawPtY = y * self.pxHeight
|
||||
if drawPtY > self.height:
|
||||
continue
|
||||
rect = (
|
||||
(drawPtX, drawPtY),
|
||||
(drawPtX + self.pxWidth, drawPtY + self.pxHeight)
|
||||
)
|
||||
if self.shapeType == 0:
|
||||
drawer.rectangle(rect, fill=self.color)
|
||||
elif self.shapeType == 1:
|
||||
drawer.ellipse(rect, fill=self.color)
|
||||
elif self.shapeType == 2:
|
||||
drawer.pieslice(rect, 290, 250, fill=self.color)
|
||||
elif self.shapeType == 3:
|
||||
drawer.pieslice(rect, 20, 340, fill=self.color)
|
||||
|
||||
if self.customImg:
|
||||
drawCustomImg()
|
||||
else:
|
||||
drawShape()
|
||||
|
||||
if self.shadow:
|
||||
shadImg = ImageEnhance.Contrast(frame).enhance(0.0)
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
<number>30</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>15</number>
|
||||
<number>5</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -93,6 +93,13 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_customImg">
|
||||
<property name="text">
|
||||
<string>Custom Image</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_5">
|
||||
<property name="orientation">
|
||||
|
@ -111,7 +118,36 @@
|
|||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_8">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_15">
|
||||
<widget class="QLabel" name="label_image">
|
||||
<property name="text">
|
||||
<string>Image</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="lineEdit_image"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_pickImage">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_color">
|
||||
<property name="text">
|
||||
<string>Color</string>
|
||||
</property>
|
||||
|
@ -169,7 +205,7 @@
|
|||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_16">
|
||||
<widget class="QLabel" name="label_shape">
|
||||
<property name="text">
|
||||
<string>Shape</string>
|
||||
</property>
|
||||
|
@ -182,6 +218,11 @@
|
|||
<string>Rectangle</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Ellipse</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Circle</string>
|
||||
|
@ -197,6 +238,16 @@
|
|||
<string>Pac-Man</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Duck</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Peace</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
|
Reference in New Issue