diff --git a/background.jpg b/background.jpg deleted file mode 100644 index f746432..0000000 Binary files a/background.jpg and /dev/null differ diff --git a/background.png b/background.png new file mode 100644 index 0000000..7a33158 Binary files /dev/null and b/background.png differ diff --git a/components/__base__.py b/components/__base__.py index 45512ce..f564aad 100644 --- a/components/__base__.py +++ b/components/__base__.py @@ -7,6 +7,13 @@ class Component: def version(self): # change this number to identify new versions of a component return 1 + + def cancel(self): + # make sure your component responds to these variables in frameRender() + self.canceled = True + + def reset(self): + self.canceled = False def preFrameRender(self, **kwargs): for var, value in kwargs.items(): @@ -66,4 +73,10 @@ class Component: def savePreset(self): return {} + + def cancel(self): + self.canceled = True + + def reset(self): + self.canceled = False ''' diff --git a/components/color.py b/components/color.py new file mode 100644 index 0000000..c2a49e2 --- /dev/null +++ b/components/color.py @@ -0,0 +1,87 @@ +from PIL import Image, ImageDraw +from PyQt4 import uic, QtGui, QtCore +from PyQt4.QtGui import QColor +import os +from . import __base__ + +class Component(__base__.Component): + '''Color''' + def widget(self, parent): + self.parent = parent + page = uic.loadUi(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'color.ui')) + + self.color1 = (0,0,0) + self.color2 = (133,133,133) + self.x = 0 + self.y = 0 + + page.lineEdit_color1.setText('%s,%s,%s' % self.color1) + page.lineEdit_color2.setText('%s,%s,%s' % self.color2) + page.pushButton_color1.clicked.connect(lambda: self.pickColor(1)) + btnStyle = "QPushButton { background-color : %s; outline: none; }" % QColor(*self.color1).name() + page.pushButton_color1.setStyleSheet(btnStyle) + page.pushButton_color2.clicked.connect(lambda: self.pickColor(2)) + btnStyle = "QPushButton { background-color : %s; outline: none; }" % QColor(*self.color2).name() + page.pushButton_color2.setStyleSheet(btnStyle) + # disable color #2 until non-default 'fill' option gets changed + page.lineEdit_color2.setDisabled(True) + page.pushButton_color2.setDisabled(True) + page.spinBox_x.setValue(self.x) + page.spinBox_x.setValue(self.y) + + page.lineEdit_color1.textChanged.connect(self.update) + page.lineEdit_color2.textChanged.connect(self.update) + page.spinBox_x.valueChanged.connect(self.update) + page.spinBox_y.valueChanged.connect(self.update) + self.page = page + return page + + def update(self): + self.color1 = self.RGBFromString(self.page.lineEdit_color1.text()) + self.color2 = self.RGBFromString(self.page.lineEdit_color2.text()) + self.x = self.page.spinBox_x.value() + self.y = self.page.spinBox_y.value() + self.parent.drawPreview() + + def previewRender(self, previewWorker): + width = int(previewWorker.core.settings.value('outputWidth')) + height = int(previewWorker.core.settings.value('outputHeight')) + return self.drawFrame(width, height) + + def preFrameRender(self, **kwargs): + super().preFrameRender(**kwargs) + return ['static'] + + def frameRender(self, moduleNo, arrayNo, frameNo): + width = int(self.worker.core.settings.value('outputWidth')) + height = int(self.worker.core.settings.value('outputHeight')) + return self.drawFrame(width, height) + + def drawFrame(self, width, height): + r,g,b = self.color1 + return Image.new("RGBA", (width, height), (r, g, b, 255)) + + def loadPreset(self, pr): + self.page.lineEdit_color1.setText('%s,%s,%s' % pr['color1']) + self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2']) + btnStyle = "QPushButton { background-color : %s; outline: none; }" % QColor(*pr['color1']).name() + self.page.pushButton_color1.setStyleSheet(btnStyle) + btnStyle = "QPushButton { background-color : %s; outline: none; }" % QColor(*pr['color2']).name() + self.page.pushButton_color2.setStyleSheet(btnStyle) + + def savePreset(self): + return { + 'color1' : self.color1, + 'color2' : self.color2, + } + + def pickColor(self, num): + RGBstring, btnStyle = super().pickColor() + if not RGBstring: + return + if num == 1: + self.page.lineEdit_color1.setText(RGBstring) + self.page.pushButton_color1.setStyleSheet(btnStyle) + else: + self.page.lineEdit_color2.setText(RGBstring) + self.page.pushButton_color2.setStyleSheet(btnStyle) diff --git a/components/color.ui b/components/color.ui new file mode 100644 index 0000000..fd427e6 --- /dev/null +++ b/components/color.ui @@ -0,0 +1,306 @@ + + + Form + + + + 0 + 0 + 586 + 197 + + + + Form + + + + + + 4 + + + + + + + + 0 + 0 + + + + + 31 + 0 + + + + Color #1 + + + + + + + + 32 + 32 + + + + + + + + 32 + 32 + + + + + + + + + 0 + 0 + + + + + 1 + 0 + + + + 12 + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 5 + 20 + + + + + + + + + 0 + 0 + + + + + 31 + 0 + + + + Color #2 + + + + + + + + 32 + 32 + + + + + + + + 32 + 32 + + + + + + + + + 0 + 0 + + + + + 1 + 0 + + + + 12 + + + + + + + + + 0 + + + + + + 0 + 0 + + + + Fill + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 5 + 20 + + + + + + + + + 0 + 0 + + + + X + + + + + + + + 0 + 0 + + + + + 80 + 16777215 + + + + + 0 + 0 + + + + 0 + + + 999999999 + + + 0 + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 5 + 20 + + + + + + + + + 0 + 0 + + + + Y + + + + + + + + 0 + 0 + + + + + 80 + 16777215 + + + + 999999999 + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/components/image.py b/components/image.py new file mode 100644 index 0000000..ffbb117 --- /dev/null +++ b/components/image.py @@ -0,0 +1,64 @@ +from PIL import Image, ImageDraw +from PyQt4 import uic, QtGui, QtCore +import os +from . import __base__ + +class Component(__base__.Component): + '''Image''' + 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')) + self.imagePath = '' + self.x = 0 + self.y = 0 + + page.lineEdit_image.textChanged.connect(self.update) + page.pushButton_image.clicked.connect(self.pickImage) + + self.page = page + return page + + def update(self): + self.imagePath = self.page.lineEdit_image.text() + self.parent.drawPreview() + + def previewRender(self, previewWorker): + width = int(previewWorker.core.settings.value('outputWidth')) + height = int(previewWorker.core.settings.value('outputHeight')) + return self.drawFrame(width, height) + + def preFrameRender(self, **kwargs): + super().preFrameRender(**kwargs) + return ['static'] + + def frameRender(self, moduleNo, arrayNo, frameNo): + width = int(self.worker.core.settings.value('outputWidth')) + height = int(self.worker.core.settings.value('outputHeight')) + return self.drawFrame(width, height) + + def drawFrame(self, width, height): + frame = Image.new("RGBA", (width, height), (0,0,0,0)) + if self.imagePath and os.path.exists(self.imagePath): + image = Image.open(self.imagePath) + if image.size != (width, height): + image = image.resize((width, height), Image.ANTIALIAS) + frame.paste(image) + return frame + + def loadPreset(self, pr): + self.page.lineEdit_image.setText(pr['image']) + + def savePreset(self): + return { + 'image' : self.imagePath, + } + + def pickImage(self): + imgDir = self.settings.value("backgroundDir", os.path.expanduser("~")) + filename = QtGui.QFileDialog.getOpenFileName(self.page, + "Choose Image", imgDir, "Image Files (*.jpg *.png)") + if filename: + self.settings.setValue("backgroundDir", os.path.dirname(filename)) + self.page.lineEdit_image.setText(filename) + self.update() diff --git a/components/image.ui b/components/image.ui new file mode 100644 index 0000000..3cd5b1b --- /dev/null +++ b/components/image.ui @@ -0,0 +1,197 @@ + + + Form + + + + 0 + 0 + 586 + 197 + + + + Form + + + + + + 4 + + + + + + + + 0 + 0 + + + + + 31 + 0 + + + + Image + + + + + + + + 1 + 0 + + + + + + + + + 0 + 0 + + + + + 1 + 0 + + + + + 32 + 32 + + + + ... + + + + 32 + 32 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 5 + 20 + + + + + + + + + 0 + 0 + + + + X + + + + + + + + 0 + 0 + + + + + 80 + 16777215 + + + + 999999999 + + + + + + + + 0 + 0 + + + + Y + + + + + + + + 0 + 0 + + + + + 80 + 16777215 + + + + + 0 + 0 + + + + 0 + + + 999999999 + + + 0 + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/components/original.py b/components/original.py index 6903a5f..b0a7579 100644 --- a/components/original.py +++ b/components/original.py @@ -58,6 +58,8 @@ class Component(__base__.Component): self.smoothConstantUp = 0.8 self.lastSpectrum = None self.spectrumArray = {} + self.width = int(self.worker.core.settings.value('outputWidth')) + self.height = int(self.worker.core.settings.value('outputHeight')) for i in range(0, len(self.completeAudioArray), self.sampleSize): if self.canceled: @@ -72,12 +74,9 @@ class Component(__base__.Component): pStr = "Analyzing audio: "+ str(progress) +'%' self.progressBarSetText.emit(pStr) self.progressBarUpdate.emit(int(progress)) - - - def frameRender(self, moduleNo, frameNo): - width = int(self.worker.core.settings.value('outputWidth')) - height = int(self.worker.core.settings.value('outputHeight')) - return self.drawBars(width, height, self.spectrumArray[frameNo], self.visColor, self.layout) + + def frameRender(self, moduleNo, arrayNo, frameNo): + return self.drawBars(self.width, self.height, self.spectrumArray[arrayNo], self.visColor, self.layout) def pickColor(self): RGBstring, btnStyle = super().pickColor() @@ -154,12 +153,3 @@ class Component(__base__.Component): return im - def cancel(self): - self.canceled = True - - def reset(self): - self.canceled = False - - - - diff --git a/components/text.py b/components/text.py index 7b2289d..d2bd684 100644 --- a/components/text.py +++ b/components/text.py @@ -18,7 +18,6 @@ class Component(__base__.Component): self.parent = parent self.textColor = (255,255,255) self.title = 'Text' - self.titleFont = None self.alignment = 1 self.fontSize = height / 13.5 self.xPosition = width / 2 @@ -35,8 +34,6 @@ class Component(__base__.Component): page.pushButton_textColor.setStyleSheet(btnStyle) page.lineEdit_title.setText(self.title) - #if self.titleFont: - # page.fontComboBox_titleFont.setCurrentFont(QFont(self.titleFont)) page.comboBox_textAlign.setCurrentIndex(int(self.alignment)) page.spinBox_fontSize.setValue(int(self.fontSize)) page.spinBox_xTextAlign.setValue(int(self.xPosition)) @@ -109,7 +106,7 @@ class Component(__base__.Component): super().preFrameRender(**kwargs) return ['static'] - def frameRender(self, moduleNo, frameNo): + def frameRender(self, moduleNo, arrayNo, frameNo): width = int(self.worker.core.settings.value('outputWidth')) height = int(self.worker.core.settings.value('outputHeight')) return self.addText(width, height) @@ -142,9 +139,3 @@ class Component(__base__.Component): return self.page.lineEdit_textColor.setText(RGBstring) self.page.pushButton_textColor.setStyleSheet(btnStyle) - - def cancel(self): - self.canceled = True - - def reset(self): - self.canceled = False diff --git a/components/video.py b/components/video.py new file mode 100644 index 0000000..3162279 --- /dev/null +++ b/components/video.py @@ -0,0 +1,141 @@ +from PIL import Image, ImageDraw +from PyQt4 import uic, QtGui, QtCore +import os, subprocess, threading +from queue import PriorityQueue +from . import __base__ + +class Video: + '''Video Component Frame-Fetcher''' + def __init__(self, ffmpeg, videoPath, width, height, frameRate, chunkSize, parent, loopVideo): + self.parent = parent + self.chunkSize = chunkSize + self.size = (width, height) + self.frameNo = -1 + self.currentFrame = 'None' + if loopVideo: + self.loopValue = '-1' + else: + self.loopValue = '0' + self.command = [ + ffmpeg, + '-thread_queue_size', '512', + '-r', frameRate, + '-stream_loop', self.loopValue, + '-i', videoPath, + '-f', 'image2pipe', + '-pix_fmt', 'rgba', + '-filter:v', 'scale='+str(width)+':'+str(height), + '-vcodec', 'rawvideo', '-', + ] + + self.frameBuffer = PriorityQueue() + self.frameBuffer.maxsize = int(frameRate) + self.finishedFrames = {} + + self.thread = threading.Thread(target=self.fillBuffer, name=self.__doc__) + self.thread.daemon = True + self.thread.start() + + def frame(self, num): + while True: + if num in self.finishedFrames: + image = self.finishedFrames.pop(num) + return Image.frombytes('RGBA', self.size, image) + i, image = self.frameBuffer.get() + self.finishedFrames[i] = image + self.frameBuffer.task_done() + + def fillBuffer(self): + self.pipe = subprocess.Popen(self.command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, bufsize=10**8) + while True: + if self.parent.canceled: + break + 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 + + self.currentFrame = self.pipe.stdout.read(self.chunkSize) + #print('creating frame #%s' % str(self.frameNo)) + if len(self.currentFrame) != 0: + self.frameBuffer.put((self.frameNo, self.currentFrame)) + self.lastFrame = self.currentFrame + +class Component(__base__.Component): + '''Video''' + 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')) + self.videoPath = '' + self.x = 0 + self.y = 0 + self.loopVideo = False + + page.lineEdit_video.textChanged.connect(self.update) + page.pushButton_video.clicked.connect(self.pickVideo) + page.checkBox_loop.stateChanged.connect(self.update) + + self.page = page + return page + + def update(self): + self.videoPath = self.page.lineEdit_video.text() + self.loopVideo = self.page.checkBox_loop.isChecked() + self.parent.drawPreview() + + def previewRender(self, previewWorker): + width = int(previewWorker.core.settings.value('outputWidth')) + height = int(previewWorker.core.settings.value('outputHeight')) + self.chunkSize = 4*width*height + return self.getPreviewFrame(width, height) + + def preFrameRender(self, **kwargs): + super().preFrameRender(**kwargs) + width = int(self.worker.core.settings.value('outputWidth')) + height = int(self.worker.core.settings.value('outputHeight')) + self.chunkSize = 4*width*height + self.video = Video(self.parent.core.FFMPEG_BIN, self.videoPath, + width, height, self.settings.value("outputFrameRate"), + self.chunkSize, self.parent, self.loopVideo) + + def frameRender(self, moduleNo, arrayNo, frameNo): + return self.video.frame(frameNo) + + def loadPreset(self, pr): + self.page.lineEdit_video.setText(pr['video']) + + def savePreset(self): + return { + 'video' : self.videoPath, + } + + def pickVideo(self): + imgDir = self.settings.value("backgroundDir", os.path.expanduser("~")) + filename = QtGui.QFileDialog.getOpenFileName(self.page, + "Choose Video", imgDir, "Video Files (*.mp4 *.mov)") + if filename: + self.settings.setValue("backgroundDir", os.path.dirname(filename)) + self.page.lineEdit_video.setText(filename) + self.update() + + def getPreviewFrame(self, width, height): + command = [ + self.parent.core.FFMPEG_BIN, + '-thread_queue_size', '512', + '-i', self.videoPath, + '-f', 'image2pipe', + '-pix_fmt', 'rgba', + '-filter:v', 'scale='+str(width)+':'+str(height), + '-vcodec', 'rawvideo', '-', + '-ss', '90', + '-vframes', '1', + ] + pipe = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, bufsize=10**8) + byteFrame = pipe.stdout.read(self.chunkSize) + image = Image.frombytes('RGBA', (width, height), byteFrame) + pipe.stdout.close() + pipe.kill() + return image diff --git a/components/video.ui b/components/video.ui new file mode 100644 index 0000000..6a01368 --- /dev/null +++ b/components/video.ui @@ -0,0 +1,224 @@ + + + Form + + + + 0 + 0 + 586 + 197 + + + + Form + + + + + + 4 + + + + + + + + 0 + 0 + + + + + 31 + 0 + + + + Video + + + + + + + + 1 + 0 + + + + + + + + + 0 + 0 + + + + + 1 + 0 + + + + + 32 + 32 + + + + ... + + + + 32 + 32 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 5 + 20 + + + + + + + + + 0 + 0 + + + + X + + + + + + + + 0 + 0 + + + + + 80 + 16777215 + + + + 999999999 + + + + + + + + 0 + 0 + + + + Y + + + + + + + + 0 + 0 + + + + + 80 + 16777215 + + + + + 0 + 0 + + + + 0 + + + 999999999 + + + 0 + + + + + + + + + + + + + Loop + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 5 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/core.py b/core.py index 16ecb35..99403f1 100644 --- a/core.py +++ b/core.py @@ -13,11 +13,10 @@ from collections import OrderedDict class Core(): def __init__(self): - self.lastBackgroundImage = "" - self._image = None - self.FFMPEG_BIN = self.findFfmpeg() - self.tempDir = None + self.tempDir = os.path.join(tempfile.gettempdir(), 'audio-visualizer-python-data') + if not os.path.exists(self.tempDir): + os.makedirs(self.tempDir) atexit.register(self.deleteTempDir) def findFfmpeg(self): @@ -31,31 +30,6 @@ class Core(): except: return "avconv" - def parseBaseImage(self, backgroundImage, preview=False): - ''' determines if the base image is a single frame or list of frames ''' - if backgroundImage == "": - return [''] - else: - _, bgExt = os.path.splitext(backgroundImage) - if not bgExt == '.mp4': - return [backgroundImage] - else: - return self.getVideoFrames(backgroundImage, preview) - - def drawBaseImage(self, backgroundFile): - if backgroundFile == '': - im = Image.new("RGB", (int(self.settings.value('outputWidth')), int(self.settings.value('outputHeight'))), "black") - else: - im = Image.open(backgroundFile) - - if self._image == None or not self.lastBackgroundImage == backgroundFile: - self.lastBackgroundImage = backgroundFile - # resize if necessary - if not im.size == (int(self.settings.value('outputWidth')), int(self.settings.value('outputHeight'))): - im = im.resize((int(self.settings.value('outputWidth')), int(self.settings.value('outputHeight'))), Image.ANTIALIAS) - - return im - def readAudioFile(self, filename, parent): command = [ self.FFMPEG_BIN, '-i', filename] @@ -121,30 +95,10 @@ class Core(): return completeAudioArray def deleteTempDir(self): - if self.tempDir and os.path.exists(self.tempDir): - rmtree(self.tempDir) - - def getVideoFrames(self, videoPath, firstOnly=False): - self.tempDir = os.path.join(tempfile.gettempdir(), 'audio-visualizer-python-data') - # recreate the temporary directory so it is empty - self.deleteTempDir() - os.mkdir(self.tempDir) - if firstOnly: - filename = 'preview%s.jpg' % os.path.basename(videoPath).split('.', 1)[0] - options = '-ss 10 -vframes 1' - else: - filename = '$frame%05d.jpg' - options = '' - sp.call( \ - '%s -i "%s" -y %s "%s"' % ( \ - self.FFMPEG_BIN, - videoPath, - options, - os.path.join(self.tempDir, filename) - ), - shell=True - ) - return sorted([os.path.join(self.tempDir, f) for f in os.listdir(self.tempDir)]) + try: + rmtree(self.tempDir) + except FileNotFoundError: + pass def cancel(self): self.canceled = True @@ -153,6 +107,6 @@ class Core(): self.canceled = False @staticmethod - def sortedStringDict(dictionary): + def stringOrderedDict(dictionary): sorted_ = OrderedDict(sorted(dictionary.items(), key=lambda t: t[0])) return repr(sorted_) diff --git a/main.py b/main.py index 2aa7fa9..c75a7f7 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,4 @@ -import sys, io, os, shutil, atexit, string, signal, filecmp +import sys, io, os, shutil, atexit, string, signal, filecmp, time from os.path import expanduser from queue import Queue from importlib import import_module @@ -133,9 +133,9 @@ class PreviewWindow(QtGui.QLabel): class Main(QtCore.QObject): - newTask = QtCore.pyqtSignal(str, list) + newTask = QtCore.pyqtSignal(list) processTask = QtCore.pyqtSignal() - videoTask = QtCore.pyqtSignal(str, str, str, list) + videoTask = QtCore.pyqtSignal(str, str, list) def __init__(self, window): QtCore.QObject.__init__(self) @@ -145,6 +145,7 @@ class Main(QtCore.QObject): self.core = core.Core() self.pages = [] self.selectedComponents = [] + self.lastAutosave = time.time() # create data directory, load/create settings self.dataDir = QDesktopServices.storageLocation(QDesktopServices.DataLocation) @@ -172,14 +173,13 @@ class Main(QtCore.QObject): # begin decorating the window and connecting events window.toolButton_selectAudioFile.clicked.connect(self.openInputFileDialog) - window.toolButton_selectBackground.clicked.connect(self.openBackgroundFileDialog) window.toolButton_selectOutputFile.clicked.connect(self.openOutputFileDialog) window.progressBar_createVideo.setValue(0) window.pushButton_createVideo.clicked.connect(self.createAudioVisualisation) window.pushButton_Cancel.clicked.connect(self.stopVideo) window.setWindowTitle("Audio Visualizer") - self.previewWindow = PreviewWindow(self, os.path.join(os.path.dirname(os.path.realpath(__file__)), "background.jpg")) + self.previewWindow = PreviewWindow(self, os.path.join(os.path.dirname(os.path.realpath(__file__)), "background.png")) window.verticalLayout_previewWrapper.addWidget(self.previewWindow) self.modules = self.findComponents() @@ -236,15 +236,17 @@ class Main(QtCore.QObject): self.autosave() def autosave(self): - if os.path.exists(self.autosavePath): - os.remove(self.autosavePath) - self.createProjectFile(self.autosavePath) + if time.time() - self.lastAutosave >= 1.0: + if os.path.exists(self.autosavePath): + os.remove(self.autosavePath) + self.createProjectFile(self.autosavePath) + self.lastAutosave = time.time() def openInputFileDialog(self): inputDir = self.settings.value("inputDir", expanduser("~")) fileName = QtGui.QFileDialog.getOpenFileName(self.window, - "Open Music File", inputDir, "Music Files (*.mp3 *.wav *.ogg *.flac)"); + "Open Music File", inputDir, "Music Files (*.mp3 *.wav *.ogg *.fla *.aac)"); if not fileName == "": self.settings.setValue("inputDir", os.path.dirname(fileName)) @@ -254,23 +256,12 @@ class Main(QtCore.QObject): outputDir = self.settings.value("outputDir", expanduser("~")) fileName = QtGui.QFileDialog.getSaveFileName(self.window, - "Set Output Video File", outputDir, "Video Files (*.mkv)"); + "Set Output Video File", outputDir, "Video Files (*.mp4 *.mov *.mkv *.avi *.webm *.flv)"); if not fileName == "": self.settings.setValue("outputDir", os.path.dirname(fileName)) self.window.lineEdit_outputFile.setText(fileName) - def openBackgroundFileDialog(self): - backgroundDir = self.settings.value("backgroundDir", expanduser("~")) - - fileName = QtGui.QFileDialog.getOpenFileName(self.window, - "Open Background Image", backgroundDir, "Image Files (*.jpg *.png);; Video Files (*.mp4)"); - - if not fileName == "": - self.settings.setValue("backgroundDir", os.path.dirname(fileName)) - self.window.lineEdit_background.setText(fileName) - self.drawPreview() - def stopVideo(self): print('stop') self.videoWorker.cancel() @@ -291,8 +282,7 @@ class Main(QtCore.QObject): self.videoWorker.imageCreated.connect(self.showPreviewImage) self.videoWorker.encoding.connect(self.changeEncodingStatus) self.videoThread.start() - self.videoTask.emit(self.window.lineEdit_background.text(), - self.window.lineEdit_audioFile.text(), + self.videoTask.emit(self.window.lineEdit_audioFile.text(), self.window.lineEdit_outputFile.text(), self.selectedComponents) else: @@ -323,10 +313,6 @@ class Main(QtCore.QObject): self.window.pushButton_savePreset.setEnabled(False) self.window.pushButton_openProject.setEnabled(False) self.window.listWidget_componentList.setEnabled(False) - - self.window.label_background.setEnabled(False) - self.window.lineEdit_background.setEnabled(False) - self.window.toolButton_selectBackground.setEnabled(False) else: self.window.pushButton_createVideo.setEnabled(True) self.window.pushButton_Cancel.setEnabled(False) @@ -349,12 +335,6 @@ class Main(QtCore.QObject): self.window.pushButton_openProject.setEnabled(True) self.window.listWidget_componentList.setEnabled(True) - self.window.label_background.setEnabled(True) - self.window.lineEdit_background.setEnabled(True) - self.window.toolButton_selectBackground.setEnabled(True) - - - def progressBarSetText(self, value): self.window.progressBar_createVideo.setFormat(value) @@ -370,7 +350,7 @@ class Main(QtCore.QObject): self.drawPreview() def drawPreview(self): - self.newTask.emit(self.window.lineEdit_background.text(), self.selectedComponents) + self.newTask.emit(self.selectedComponents) # self.processTask.emit() self.autosave() @@ -381,7 +361,7 @@ class Main(QtCore.QObject): def findComponents(): srcPath = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'components') if os.path.exists(srcPath): - for f in os.listdir(srcPath): + for f in sorted(os.listdir(srcPath)): name, ext = os.path.splitext(f) if name.startswith("__"): continue @@ -446,7 +426,7 @@ class Main(QtCore.QObject): def moveComponentDown(self): row = self.window.listWidget_componentList.currentRow() - if row < len(self.pages) + 1: + if row != -1 and row < len(self.pages)+1: module = self.selectedComponents[row] self.selectedComponents.pop(row) self.selectedComponents.insert(row + 1,module) @@ -507,7 +487,7 @@ class Main(QtCore.QObject): if self.window.comboBox_openPreset.itemText(i) == filename: self.window.comboBox_openPreset.removeItem(i) with open(filepath, 'w') as f: - f.write(core.Core.sortedStringDict(saveValueStore)) + f.write(core.Core.stringOrderedDict(saveValueStore)) self.window.comboBox_openPreset.addItem(filename) self.window.comboBox_openPreset.setCurrentIndex(self.window.comboBox_openPreset.count()-1) @@ -550,12 +530,13 @@ class Main(QtCore.QObject): if not filepath.endswith(".avp"): filepath += '.avp' with open(filepath, 'w') as f: + print('creating %s' % filepath) f.write('[Components]\n') for comp in self.selectedComponents: saveValueStore = comp.savePreset() f.write('%s\n' % str(comp)) f.write('%s\n' % str(comp.version())) - f.write('%s\n' % core.Core.sortedStringDict(saveValueStore)) + f.write('%s\n' % core.Core.stringOrderedDict(saveValueStore)) if filepath != self.autosavePath: self.settings.setValue("projectDir", os.path.dirname(filepath)) self.settings.setValue("currentProject", filepath) @@ -604,14 +585,18 @@ class Main(QtCore.QObject): saveValueStore = dict(eval(line)) self.selectedComponents[-1].loadPreset(saveValueStore) i = 0 - except: + except (IndexError, ValueError, KeyError, NameError, SyntaxError, AttributeError, TypeError) as e: self.clear() - self.showMessage("Project file '%s' is corrupted." % filepath) + typ, value, _ = sys.exc_info() + msg = '%s: %s' % (typ.__name__, value) + self.showMessage("Project file '%s' is corrupted." % filepath, False, + QtGui.QMessageBox.Warning, msg) - def showMessage(self, string, showCancel=False, icon=QtGui.QMessageBox.Information): + def showMessage(self, string, showCancel=False, icon=QtGui.QMessageBox.Information, detail=None): msg = QtGui.QMessageBox() msg.setIcon(icon) msg.setText(string) + msg.setDetailedText(detail) if showCancel: msg.setStandardButtons(QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel) else: diff --git a/mainwindow.ui b/mainwindow.ui index f9e8f5e..b703997 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -6,8 +6,8 @@ 0 0 - 1008 - 575 + 1028 + 592 @@ -421,73 +421,6 @@ - - - - - - - 0 - 0 - - - - - 85 - 0 - - - - Background - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - 0 - 28 - - - - - 16777215 - 28 - - - - - - - - - 0 - 28 - - - - - 16777215 - 28 - - - - ... - - - - - @@ -621,7 +554,6 @@ - diff --git a/preview_thread.py b/preview_thread.py index 63d1ac5..5116707 100644 --- a/preview_thread.py +++ b/preview_thread.py @@ -22,10 +22,9 @@ class Worker(QtCore.QObject): @pyqtSlot(str, list) - def createPreviewImage(self, backgroundImage, components): + def createPreviewImage(self, components): # print('worker thread id: {}'.format(QtCore.QThread.currentThreadId())) dic = { - "backgroundImage": backgroundImage, "components": components, } self.queue.put(dic) @@ -40,25 +39,14 @@ class Worker(QtCore.QObject): except Empty: continue - bgImage = self.core.parseBaseImage(\ - nextPreviewInformation["backgroundImage"], - preview=True - ) - if bgImage == []: - bgImage = '' - else: - bgImage = bgImage[0] - - im = self.core.drawBaseImage(bgImage) width = int(self.core.settings.value('outputWidth')) height = int(self.core.settings.value('outputHeight')) - frame = Image.new("RGBA", (width, height),(0,0,0,255)) - frame.paste(im) + frame = Image.new("RGBA", (width, height),(0,0,0,0)) components = nextPreviewInformation["components"] for component in reversed(components): - newFrame = Image.alpha_composite(frame,component.previewRender(self)) - frame = Image.alpha_composite(frame,newFrame) + #newFrame = Image.alpha_composite(frame,) + frame = Image.alpha_composite(frame,component.previewRender(self)) self._image = ImageQt(frame) self.imageCreated.emit(QtGui.QImage(self._image)) diff --git a/video_thread.py b/video_thread.py index c97cc24..ac4162c 100644 --- a/video_thread.py +++ b/video_thread.py @@ -37,19 +37,19 @@ class Worker(QtCore.QObject): def renderNode(self): while not self.stopped: i = self.compositeQueue.get() - - if self.imBackground is not None: - frame = self.imBackground - else: - frame = self.getBackgroundAtIndex(i[1]) + frame = None for compNo, comp in reversed(list(enumerate(self.components))): if compNo in self.staticComponents and self.staticComponents[compNo] != None: - frame = Image.alpha_composite(frame, self.staticComponents[compNo]) + if frame is None: + frame = self.staticComponents[compNo] + else: + frame = Image.alpha_composite(frame, self.staticComponents[compNo]) else: - frame = Image.alpha_composite(frame, comp.frameRender(compNo, i[0])) - - # frame.paste(compFrame, mask=compFrame) + if frame is None: + frame = comp.frameRender(compNo, i[0], i[1]) + else: + frame = Image.alpha_composite(frame, comp.frameRender(compNo, i[0], i[1])) self.renderQueue.put([i[0], frame]) self.compositeQueue.task_done() @@ -59,10 +59,8 @@ class Worker(QtCore.QObject): for i in range(0, len(self.completeAudioArray), self.sampleSize): self.compositeQueue.put([i, self.bgI]) - if not self.imBackground: - # increment background video frame for next iteration - if self.bgI < len(self.backgroundFrames)-1: - self.bgI += 1 + # increment tracked video frame for next iteration + self.bgI += 1 def previewDispatch(self): while not self.stopped: @@ -74,39 +72,18 @@ class Worker(QtCore.QObject): self.previewQueue.task_done() - def getBackgroundAtIndex(self, i): - background = Image.new( - "RGBA", - (self.width, self.height), - (0, 0, 0, 255) - ) - layer = self.core.drawBaseImage(self.backgroundFrames[i]) - background.paste(layer) - return background - - @pyqtSlot(str, str, str, list) - def createVideo(self, backgroundImage, inputFile, outputFile, components): + @pyqtSlot(str, str, list) + def createVideo(self, inputFile, outputFile, components): self.encoding.emit(True) self.components = components self.outputFile = outputFile + self.bgI = 0 # tracked video frame self.reset() self.width = int(self.core.settings.value('outputWidth')) self.height = int(self.core.settings.value('outputHeight')) # print('worker thread id: {}'.format(QtCore.QThread.currentThreadId())) progressBarValue = 0 self.progressBarUpdate.emit(progressBarValue) - self.progressBarSetText.emit('Loading background imageā€¦') - - self.backgroundImage = backgroundImage - - self.backgroundFrames = self.core.parseBaseImage(backgroundImage) - if len(self.backgroundFrames) < 2: - # the base image is not a video so we can draw it now - self.imBackground = self.getBackgroundAtIndex(0) - else: - # base images will be drawn while drawing the audio bars - self.imBackground = None - self.bgI = 0 self.progressBarSetText.emit('Loading audio file...') self.completeAudioArray = self.core.readAudioFile(inputFile, self) @@ -120,6 +97,7 @@ class Worker(QtCore.QObject): ffmpegCommand = [ self.core.FFMPEG_BIN, + '-thread_queue_size', '512', '-y', # (optional) means overwrite the output file if it already exists. '-f', 'rawvideo', '-vcodec', 'rawvideo', @@ -165,7 +143,7 @@ class Worker(QtCore.QObject): ) if properties and 'static' in properties: - self.staticComponents[compNo] = copy(comp.frameRender(compNo, 0)) + self.staticComponents[compNo] = copy(comp.frameRender(compNo, 0, 0)) self.progressBarUpdate.emit(100) self.compositeQueue = Queue() @@ -250,7 +228,6 @@ class Worker(QtCore.QObject): self.error = False self.canceled = False self.parent.drawPreview() - self.core.deleteTempDir() self.stopped = True self.encoding.emit(False) self.videoCreated.emit()