diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/__base__.py | 5 | ||||
| -rw-r--r-- | src/components/color.py | 7 | ||||
| -rw-r--r-- | src/components/image.py | 5 | ||||
| -rw-r--r-- | src/components/original.py | 5 | ||||
| -rw-r--r-- | src/components/text.py | 5 | ||||
| -rw-r--r-- | src/components/video.py | 53 | ||||
| -rw-r--r-- | src/core.py | 25 | ||||
| -rw-r--r-- | src/main.py | 9 | ||||
| -rw-r--r-- | src/mainwindow.py | 116 | ||||
| -rw-r--r-- | src/presetmanager.py | 3 | ||||
| -rw-r--r-- | src/preview_thread.py | 14 | ||||
| -rw-r--r-- | src/video_thread.py | 9 |
12 files changed, 163 insertions, 93 deletions
diff --git a/src/components/__base__.py b/src/components/__base__.py index 9b7b958..84d41c8 100644 --- a/src/components/__base__.py +++ b/src/components/__base__.py @@ -1,4 +1,4 @@ -from PyQt5 import QtGui, QtCore, QtWidgets +from PyQt5 import uic, QtGui, QtCore, QtWidgets from PIL import Image import os @@ -114,6 +114,9 @@ class Component(QtCore.QObject): except: return (255, 255, 255) + def loadUi(self, filename): + return uic.loadUi(os.path.join(self.core.componentsPath, filename)) + ''' ### Reference methods for creating a new component ### (Inherit from this class and define these) diff --git a/src/components/color.py b/src/components/color.py index f1fb2b2..253ac83 100644 --- a/src/components/color.py +++ b/src/components/color.py @@ -1,5 +1,5 @@ from PIL import Image, ImageDraw -from PyQt5 import uic, QtGui, QtCore, QtWidgets +from PyQt5 import QtGui, QtCore, QtWidgets from PyQt5.QtGui import QColor from PIL.ImageQt import ImageQt import os @@ -13,8 +13,7 @@ class Component(__base__.Component): def widget(self, parent): self.parent = parent - page = uic.loadUi(os.path.join( - os.path.dirname(os.path.realpath(__file__)), 'color.ui')) + page = self.loadUi('color.ui') self.color1 = (0, 0, 0) self.color2 = (133, 133, 133) @@ -177,7 +176,7 @@ class Component(__base__.Component): self.sizeWidth, self.sizeHeight ) painter.end() - imBytes = image.bits().asstring(image.numBytes()) + imBytes = image.bits().asstring(image.byteCount()) return Image.frombytes('RGBA', (width, height), imBytes) def loadPreset(self, pr, presetName=None): diff --git a/src/components/image.py b/src/components/image.py index 3517af6..143ae59 100644 --- a/src/components/image.py +++ b/src/components/image.py @@ -1,5 +1,5 @@ from PIL import Image, ImageDraw -from PyQt5 import uic, QtGui, QtCore, QtWidgets +from PyQt5 import QtGui, QtCore, QtWidgets import os from . import __base__ @@ -12,8 +12,7 @@ class Component(__base__.Component): def widget(self, parent): self.parent = parent self.settings = parent.settings - page = uic.loadUi(os.path.join( - os.path.dirname(os.path.realpath(__file__)), 'image.ui')) + page = self.loadUi('image.ui') self.imagePath = '' self.x = 0 self.y = 0 diff --git a/src/components/original.py b/src/components/original.py index 0d5001c..0185e0d 100644 --- a/src/components/original.py +++ b/src/components/original.py @@ -1,6 +1,6 @@ import numpy from PIL import Image, ImageDraw -from PyQt5 import uic, QtGui, QtCore, QtWidgets +from PyQt5 import QtGui, QtCore, QtWidgets from PyQt5.QtGui import QColor import os from . import __base__ @@ -17,8 +17,7 @@ class Component(__base__.Component): self.parent = parent self.visColor = (255, 255, 255) - page = uic.loadUi(os.path.join( - os.path.dirname(os.path.realpath(__file__)), 'original.ui')) + page = self.loadUi('original.ui') page.comboBox_visLayout.addItem("Classic") page.comboBox_visLayout.addItem("Split") page.comboBox_visLayout.addItem("Bottom") diff --git a/src/components/text.py b/src/components/text.py index 76961c9..7f4659f 100644 --- a/src/components/text.py +++ b/src/components/text.py @@ -1,6 +1,6 @@ from PIL import Image, ImageDraw from PyQt5.QtGui import QPainter, QColor, QFont -from PyQt5 import uic, QtGui, QtCore, QtWidgets +from PyQt5 import QtGui, QtCore, QtWidgets from PIL.ImageQt import ImageQt import os import io @@ -29,8 +29,7 @@ class Component(__base__.Component): self.xPosition = width / 2 - fm.width(self.title)/2 self.yPosition = height / 2 * 1.036 - page = uic.loadUi(os.path.join( - os.path.dirname(os.path.realpath(__file__)), 'text.ui')) + page = self.loadUi('text.ui') page.comboBox_textAlign.addItem("Left") page.comboBox_textAlign.addItem("Middle") page.comboBox_textAlign.addItem("Right") diff --git a/src/components/video.py b/src/components/video.py index 70247e1..44f88a5 100644 --- a/src/components/video.py +++ b/src/components/video.py @@ -1,6 +1,7 @@ from PIL import Image, ImageDraw -from PyQt5 import uic, QtGui, QtCore, QtWidgets +from PyQt5 import QtGui, QtCore, QtWidgets import os +import math import subprocess import threading from queue import PriorityQueue @@ -79,9 +80,20 @@ class Video: self.frameNo += 1 # If we run out of frames, use the last good frame and loop. - if len(self.currentFrame) == 0: - self.frameBuffer.put((self.frameNo-1, self.lastFrame)) - continue + try: + if len(self.currentFrame) == 0: + self.frameBuffer.put((self.frameNo-1, self.lastFrame)) + continue + except AttributeError as e: + self.parent.showMessage( + msg='%s couldn\'t be loaded. ' + 'This is a fatal error.' % os.path.basename( + self.videoPath + ), + detail=str(e) + ) + self.parent.stopVideo() + break self.currentFrame = pipe.stdout.read(self.chunkSize) if len(self.currentFrame) != 0: @@ -97,10 +109,7 @@ class Component(__base__.Component): def widget(self, parent): self.parent = parent self.settings = parent.settings - page = uic.loadUi(os.path.join( - os.path.dirname(os.path.realpath(__file__)), - 'video.ui' - )) + page = self.loadUi('video.ui') self.videoPath = '' self.x = 0 self.y = 0 @@ -243,28 +252,32 @@ def scale(scale, width, height, returntype=None): width = (float(width) / 100.0) * float(scale) height = (float(height) / 100.0) * float(scale) if returntype == str: - return (str(int(width)), str(int(height))) + return (str(math.ceil(width)), str(math.ceil(height))) elif returntype == int: - return (int(width), int(height)) + return (math.ceil(width), math.ceil(height)) else: return (width, height) def finalizeFrame(self, imageData, width, height): - if self.distort: - try: + try: + if self.distort: image = Image.frombytes( 'RGBA', (width, height), imageData) - except ValueError: - print('#### ignored invalid data caused by distortion ####') - image = self.blankFrame(width, height) - else: - image = Image.frombytes( - 'RGBA', - scale(self.scale, width, height, int), - imageData) + else: + image = Image.frombytes( + 'RGBA', + scale(self.scale, width, height, int), + imageData) + + except ValueError: + print( + '### BAD VIDEO SELECTED ###\n' + 'Video will not export with these settings' + ) + return self.blankFrame(width, height) if self.scale != 100 \ or self.xPosition != 0 or self.yPosition != 0: diff --git a/src/core.py b/src/core.py index c80d60e..fdba1c4 100644 --- a/src/core.py +++ b/src/core.py @@ -29,6 +29,7 @@ class Core(): else: # unfrozen self.wd = os.path.dirname(os.path.realpath(__file__)) + self.componentsPath = os.path.join(self.wd, 'components') self.loadEncoderOptions() self.videoFormats = Core.appendUppercase([ @@ -66,14 +67,12 @@ class Core(): def findComponents(self): def findComponents(): - srcPath = os.path.join(self.wd, 'components') - if os.path.exists(srcPath): - for f in sorted(os.listdir(srcPath)): - name, ext = os.path.splitext(f) - if name.startswith("__"): - continue - elif ext == '.py': - yield name + for f in sorted(os.listdir(self.componentsPath)): + name, ext = os.path.splitext(f) + if name.startswith("__"): + continue + elif ext == '.py': + yield name self.modules = [ import_module('components.%s' % name) for name in findComponents() @@ -93,10 +92,12 @@ class Core(): return None component = self.modules[moduleIndex].Component( - moduleIndex, compPos, self) + moduleIndex, compPos, self + ) self.selectedComponents.insert( compPos, - component) + component + ) self.componentListChanged() # init component's widget for loading/saving presets @@ -177,6 +178,7 @@ class Core(): for i, tup in enumerate(data['Components']): name, vers, preset = tup clearThis = False + modified = False # add loaded named presets to savedPresets dict if 'preset' in preset and preset['preset'] is not None: @@ -186,6 +188,7 @@ class Core(): origSaveValueStore = self.getPreset(filepath2) if origSaveValueStore: self.savedPresets[nam] = dict(origSaveValueStore) + modified = not origSaveValueStore == preset else: # saved preset was renamed or deleted clearThis = True @@ -217,7 +220,7 @@ class Core(): if clearThis: self.clearPreset(i) if hasattr(loader, 'updateComponentTitle'): - loader.updateComponentTitle(i) + loader.updateComponentTitle(i, modified) except: errcode = 1 data = sys.exc_info() diff --git a/src/main.py b/src/main.py index a8dd562..5b54fc7 100644 --- a/src/main.py +++ b/src/main.py @@ -7,6 +7,15 @@ import preview_thread import video_thread +def disableWhenEncoding(func): + def decorator(*args): + if args[0].encoding: + return + else: + return func(*args) + return decorator + + def LoadDefaultSettings(self): self.resolutions = [ '1920x1080', diff --git a/src/mainwindow.py b/src/mainwindow.py index 7a9e397..76c2b62 100644 --- a/src/mainwindow.py +++ b/src/mainwindow.py @@ -12,7 +12,7 @@ import core import preview_thread import video_thread from presetmanager import PresetManager -from main import LoadDefaultSettings +from main import LoadDefaultSettings, disableWhenEncoding class PreviewWindow(QtWidgets.QLabel): @@ -54,6 +54,7 @@ class MainWindow(QtWidgets.QMainWindow): self.pages = [] # widgets of component settings self.lastAutosave = time.time() + self.encoding = False # Create data directory, load/create settings self.dataDir = self.core.dataDir @@ -149,16 +150,18 @@ class MainWindow(QtWidgets.QMainWindow): for i, comp in enumerate(self.core.modules): action = self.compMenu.addAction(comp.Component.__doc__) action.triggered.connect( - lambda _, item=i: self.core.insertComponent(0, item, self)) + 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) - + self.changeComponentWidget + ) self.window.pushButton_removeComponent.clicked.connect( - lambda _: self.removeComponent()) + lambda: self.removeComponent() + ) componentList.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) componentList.customContextMenuRequested.connect( @@ -173,7 +176,8 @@ class MainWindow(QtWidgets.QMainWindow): currentRes = i window.comboBox_resolution.setCurrentIndex(currentRes) window.comboBox_resolution.currentIndexChanged.connect( - self.updateResolution) + self.updateResolution + ) self.window.pushButton_listMoveUp.clicked.connect( lambda: self.moveComponent(-1) @@ -185,14 +189,17 @@ class MainWindow(QtWidgets.QMainWindow): # Configure the Projects Menu self.projectMenu = QMenu() self.window.menuButton_newProject = self.projectMenu.addAction( - "New Project") + "New Project" + ) self.window.menuButton_newProject.triggered.connect( - self.createNewProject) - + lambda: self.createNewProject() + ) self.window.menuButton_openProject = self.projectMenu.addAction( - "Open Project") + "Open Project" + ) self.window.menuButton_openProject.triggered.connect( - self.openOpenProjectDialog) + lambda: self.openOpenProjectDialog() + ) action = self.projectMenu.addAction("Save Project") action.triggered.connect(self.saveCurrentProject) @@ -207,6 +214,7 @@ class MainWindow(QtWidgets.QMainWindow): self.openPresetManager ) + self.updateWindowTitle() window.show() if project and project != self.autosavePath: @@ -282,10 +290,15 @@ class MainWindow(QtWidgets.QMainWindow): def updateWindowTitle(self): appName = 'Audio Visualizer' - if self.currentProject: - appName += ' - %s' % \ - os.path.splitext( - os.path.basename(self.currentProject))[0] + 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) @@ -345,7 +358,7 @@ class MainWindow(QtWidgets.QMainWindow): if not self.currentProject: if os.path.exists(self.autosavePath): os.remove(self.autosavePath) - elif force or time.time() - self.lastAutosave >= 2.0: + elif force or time.time() - self.lastAutosave >= 0.1: self.core.createProjectFile(self.autosavePath) self.lastAutosave = time.time() @@ -391,7 +404,7 @@ class MainWindow(QtWidgets.QMainWindow): "Video Files (%s);; All Files (*)" % " ".join( self.core.videoFormats)) - if not fileName == "": + if fileName: self.settings.setValue("outputDir", os.path.dirname(fileName)) self.window.lineEdit_outputFile.setText(fileName) @@ -402,33 +415,50 @@ class MainWindow(QtWidgets.QMainWindow): def createAudioVisualisation(self): # create output video if mandatory settings are filled in - if self.window.lineEdit_audioFile.text() and \ - self.window.lineEdit_outputFile.text(): - self.canceled = False - self.progressBarUpdated(-1) - self.videoThread = QtCore.QThread(self) - self.videoWorker = video_thread.Worker(self) - self.videoWorker.moveToThread(self.videoThread) - self.videoWorker.videoCreated.connect(self.videoCreated) - self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated) - self.videoWorker.progressBarSetText.connect( - self.progressBarSetText) - self.videoWorker.imageCreated.connect(self.showPreviewImage) - self.videoWorker.encoding.connect(self.changeEncodingStatus) - self.videoThread.start() - outputPath = self.window.lineEdit_outputFile.text() + 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) - self.videoTask.emit( - self.window.lineEdit_audioFile.text(), - outputPath, - self.core.selectedComponents) + 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.' + ) + return else: - self.showMessage( - msg="You must select an audio file and output filename.") + 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.videoThread = QtCore.QThread(self) + self.videoWorker = video_thread.Worker(self) + self.videoWorker.moveToThread(self.videoThread) + self.videoWorker.videoCreated.connect(self.videoCreated) + self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated) + self.videoWorker.progressBarSetText.connect( + self.progressBarSetText) + self.videoWorker.imageCreated.connect(self.showPreviewImage) + self.videoWorker.encoding.connect(self.changeEncodingStatus) + self.videoThread.start() + self.videoTask.emit( + audioFile, + outputPath, + self.core.selectedComponents) def changeEncodingStatus(self, status): + self.encoding = status if status: self.window.pushButton_createVideo.setEnabled(False) self.window.pushButton_Cancel.setEnabled(True) @@ -490,6 +520,7 @@ class MainWindow(QtWidgets.QMainWindow): self.newTask.emit(self.core.selectedComponents) # self.processTask.emit() self.autosave(force) + self.updateWindowTitle() def showPreviewImage(self, image): self.previewWindow.changePixmap(image) @@ -595,6 +626,7 @@ class MainWindow(QtWidgets.QMainWindow): self.window.stackedWidget.removeWidget(widget) self.pages = [] + @disableWhenEncoding def createNewProject(self): self.openSaveChangesDialog('starting a new project') @@ -602,11 +634,11 @@ class MainWindow(QtWidgets.QMainWindow): self.currentProject = None self.settings.setValue("currentProject", None) self.drawPreview(True) - self.updateWindowTitle() def saveCurrentProject(self): if self.currentProject: self.core.createProjectFile(self.currentProject) + self.updateWindowTitle() else: self.openSaveProjectDialog() @@ -638,9 +670,10 @@ class MainWindow(QtWidgets.QMainWindow): self.settings.setValue("projectDir", os.path.dirname(filename)) self.settings.setValue("currentProject", filename) self.currentProject = filename - self.updateWindowTitle() self.core.createProjectFile(filename) + self.updateWindowTitle() + @disableWhenEncoding def openOpenProjectDialog(self): filename, _ = QtWidgets.QFileDialog.getOpenFileName( self.window, "Open Project File", @@ -651,7 +684,6 @@ class MainWindow(QtWidgets.QMainWindow): def openProject(self, filepath, prompt=True): if not filepath or not os.path.exists(filepath) \ or not filepath.endswith('.avp'): - self.updateWindowTitle() return self.clear() @@ -660,7 +692,6 @@ class MainWindow(QtWidgets.QMainWindow): self.openSaveChangesDialog('opening another project') self.currentProject = filepath - self.updateWindowTitle() self.settings.setValue("currentProject", filepath) self.settings.setValue("projectDir", os.path.dirname(filepath)) # actually load the project using core method @@ -668,6 +699,7 @@ class MainWindow(QtWidgets.QMainWindow): if self.window.listWidget_componentList.count() == 0: self.drawPreview() self.autosave(True) + self.updateWindowTitle() def showMessage(self, **kwargs): parent = kwargs['parent'] if 'parent' in kwargs else self.window diff --git a/src/presetmanager.py b/src/presetmanager.py index 44203e5..069bf62 100644 --- a/src/presetmanager.py +++ b/src/presetmanager.py @@ -123,7 +123,8 @@ class PresetManager(QtWidgets.QDialog): def clearPreset(self, compI=None): '''Functions on mainwindow level from the context menu''' compI = self.parent.window.listWidget_componentList.currentRow() - self.core.clearPreset(compI, self.parent) + self.core.clearPreset(compI) + self.parent.updateComponentTitle(compI, False) def openSavePresetDialog(self): '''Functions on mainwindow level from the context menu''' diff --git a/src/preview_thread.py b/src/preview_thread.py index 4a46d51..769656b 100644 --- a/src/preview_thread.py +++ b/src/preview_thread.py @@ -49,8 +49,18 @@ class Worker(QtCore.QObject): components = nextPreviewInformation["components"] for component in reversed(components): - frame = Image.alpha_composite( - frame, component.previewRender(self)) + try: + frame = Image.alpha_composite( + frame, component.previewRender(self) + ) + except ValueError as e: + self.parent.showMessage( + msg="Bad frame returned by %s's previewRender method. " + "This is a fatal error." % + str(component), + detail=str(e) + ) + quit(1) self._image = ImageQt(frame) self.imageCreated.emit(QtGui.QImage(self._image)) diff --git a/src/video_thread.py b/src/video_thread.py index b45381c..9b0bf56 100644 --- a/src/video_thread.py +++ b/src/video_thread.py @@ -2,7 +2,6 @@ from PyQt5 import QtCore, QtGui, uic from PyQt5.QtCore import pyqtSignal, pyqtSlot from PIL import Image, ImageDraw, ImageFont from PIL.ImageQt import ImageQt -import core import numpy import subprocess as sp import sys @@ -13,6 +12,8 @@ import time from copy import copy import signal +import core + class Worker(QtCore.QObject): @@ -87,8 +88,10 @@ class Worker(QtCore.QObject): self.encoding.emit(True) self.components = components self.outputFile = outputFile - self.bgI = 0 # tracked video frame + self.reset() + + self.bgI = 0 # tracked video frame self.width = int(self.core.settings.value('outputWidth')) self.height = int(self.core.settings.value('outputHeight')) progressBarValue = 0 @@ -171,7 +174,7 @@ class Worker(QtCore.QObject): self.staticComponents = {} numComps = len(self.components) for compNo, comp in enumerate(self.components): - pStr = "Analyzing audio..." + pStr = "Starting components..." self.progressBarSetText.emit(pStr) properties = None properties = comp.preFrameRender( |
