From c1457b6dad4640b17679dd802e372bd46a13d2a5 Mon Sep 17 00:00:00 2001 From: tassaron Date: Sat, 29 Jul 2017 13:08:28 -0400 Subject: starting work on Waveform component split Video class out of Video component for reuse in Waveform --- src/components/waveform.py | 139 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 src/components/waveform.py (limited to 'src/components/waveform.py') diff --git a/src/components/waveform.py b/src/components/waveform.py new file mode 100644 index 0000000..487a3bb --- /dev/null +++ b/src/components/waveform.py @@ -0,0 +1,139 @@ +from PIL import Image +from PyQt5 import QtGui, QtCore, QtWidgets +from PyQt5.QtGui import QColor +import os +import math +import subprocess + +from component import Component, ComponentError +from toolkit.frame import BlankFrame +from toolkit import openPipe, checkOutput, rgbFromString +from toolkit.ffmpeg import FfmpegVideo + + +class Component(Component): + name = 'Waveform' + version = '1.0.0' + + def widget(self, *args): + self.color = (255, 255, 255) + super().widget(*args) + + self.page.lineEdit_color.setText('%s,%s,%s' % self.color) + btnStyle = "QPushButton { background-color : %s; outline: none; }" \ + % QColor(*self.color1).name() + self.page.lineEdit_color.setStylesheet(btnStyle) + self.page.pushButton_color.clicked.connect(lambda: self.pickColor()) + + self.trackWidgets( + { + 'mode': self.page.comboBox_mode, + 'x': self.page.spinBox_x, + 'y': self.page.spinBox_y, + 'mirror': self.page.checkBox_mirror, + 'scale': self.page.spinBox_scale, + } + ) + + def update(self): + self.color = rgbFromString(self.page.lineEdit_color.text()) + btnStyle = "QPushButton { background-color : %s; outline: none; }" \ + % QColor(*self.color).name() + self.page.pushButton_color.setStyleSheet(btnStyle) + super().update() + + def previewRender(self): + self.updateChunksize() + frame = self.getPreviewFrame(self.width, self.height) + if not frame: + return BlankFrame(self.width, self.height) + else: + return frame + + def preFrameRender(self, **kwargs): + super().preFrameRender(**kwargs) + self.updateChunksize() + self.video = FfmpegVideo( + inputPath=self.audioFile, + filter_=makeFfmpegFilter(), + width=self.width, height=self.height, + chunkSize=self.chunkSize, + frameRate=int(self.settings.value("outputFrameRate")), + parent=self.parent, component=self, + ) + + def frameRender(self, frameNo): + if FfmpegVideo.threadError is not None: + raise FfmpegVideo.threadError + return finalizeFrame(self.video.frame(frameNo)) + + def postFrameRender(self): + closePipe(self.video.pipe) + + def getPreviewFrame(self, width, height): + inputFile = self.parent.window.lineEdit_audioFile.text() + if not inputFile or not os.path.exists(inputFile): + return + + command = [ + self.core.FFMPEG_BIN, + '-thread_queue_size', '512', + '-i', inputFile, + '-f', 'image2pipe', + '-pix_fmt', 'rgba', + ] + command.extend(self.makeFfmpegFilter()) + command.extend([ + '-vcodec', 'rawvideo', '-', + '-ss', '90', + '-frames:v', '1', + ]) + pipe = openPipe( + command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, bufsize=10**8 + ) + byteFrame = pipe.stdout.read(self.chunkSize) + closePipe(pipe) + + frame = finalizeFrame(self, byteFrame, width, height) + return frame + + def makeFfmpegFilter(self): + w, h = scale(self.scale, self.width, self.height, str) + return [ + '-filter_complex', + '[0:a] showwaves=s=%sx%s:mode=%s,format=rgba [v]' % ( + w, h, self.mode, + ), + '-map', '[v]', + '-map', '0:a', + ] + + def updateChunksize(self): + if self.scale != 100: + width, height = scale(self.scale, self.width, self.height, int) + else: + width, height = self.width, self.height + self.chunkSize = 4 * width * height + + +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(math.ceil(width)), str(math.ceil(height))) + elif returntype == int: + return (math.ceil(width), math.ceil(height)) + else: + return (width, height) + + +def finalizeFrame(self, imageData, width, height): + # frombytes goes here + if self.scale != 100 \ + or self.x != 0 or self.y != 0: + frame = BlankFrame(width, height) + frame.paste(image, box=(self.x, self.y)) + else: + frame = image + return frame -- cgit v1.2.3 From 1297af61c9ce00b6dd76f8ec690baedf5bf887c7 Mon Sep 17 00:00:00 2001 From: tassaron Date: Sat, 29 Jul 2017 20:27:46 -0400 Subject: waveform component is working, preview is glitchy --- src/components/original.py | 3 + src/components/video.py | 10 ++-- src/components/waveform.py | 134 +++++++++++++++++++++++++++++++-------------- src/components/waveform.ui | 95 +++++++++++++++++++++++++++++++- src/toolkit/common.py | 21 ------- src/toolkit/ffmpeg.py | 30 ++++++++-- src/toolkit/frame.py | 12 ++++ src/video_thread.py | 38 +++++++++---- 8 files changed, 256 insertions(+), 87 deletions(-) (limited to 'src/components/waveform.py') diff --git a/src/components/original.py b/src/components/original.py index 3d1a574..621af6f 100644 --- a/src/components/original.py +++ b/src/components/original.py @@ -18,6 +18,9 @@ class Component(Component): def names(*args): return ['Original Audio Visualization'] + def properties(self): + return ['pcm'] + def widget(self, *args): self.visColor = (255, 255, 255) self.scale = 20 diff --git a/src/components/video.py b/src/components/video.py index d3460ff..6cd16e5 100644 --- a/src/components/video.py +++ b/src/components/video.py @@ -4,10 +4,10 @@ import os import math import subprocess -from component import Component, ComponentError -from toolkit.frame import BlankFrame -from toolkit.ffmpeg import testAudioStream, FfmpegVideo -from toolkit import openPipe, closePipe, checkOutput, scale +from component import Component +from toolkit.frame import BlankFrame, scale +from toolkit.ffmpeg import openPipe, closePipe, testAudioStream, FfmpegVideo +from toolkit import checkOutput class Component(Component): @@ -132,7 +132,7 @@ class Component(Component): ] command.extend(self.makeFfmpegFilter()) command.extend([ - '-vcodec', 'rawvideo', '-', + '-codec:v', 'rawvideo', '-', '-ss', '90', '-frames:v', '1', ]) diff --git a/src/components/waveform.py b/src/components/waveform.py index 487a3bb..375b3fc 100644 --- a/src/components/waveform.py +++ b/src/components/waveform.py @@ -5,10 +5,10 @@ import os import math import subprocess -from component import Component, ComponentError -from toolkit.frame import BlankFrame -from toolkit import openPipe, checkOutput, rgbFromString -from toolkit.ffmpeg import FfmpegVideo +from component import Component +from toolkit.frame import BlankFrame, scale +from toolkit import checkOutput, rgbFromString, pickColor +from toolkit.ffmpeg import openPipe, closePipe, getAudioDuration, FfmpegVideo class Component(Component): @@ -21,17 +21,27 @@ class Component(Component): self.page.lineEdit_color.setText('%s,%s,%s' % self.color) btnStyle = "QPushButton { background-color : %s; outline: none; }" \ - % QColor(*self.color1).name() - self.page.lineEdit_color.setStylesheet(btnStyle) + % QColor(*self.color).name() + self.page.pushButton_color.setStyleSheet(btnStyle) self.page.pushButton_color.clicked.connect(lambda: self.pickColor()) + self.page.spinBox_scale.valueChanged.connect(self.updateChunksize) + + if hasattr(self.parent, 'window'): + self.parent.window.lineEdit_audioFile.textChanged.connect( + self.update + ) self.trackWidgets( { 'mode': self.page.comboBox_mode, + 'amplitude': self.page.comboBox_amplitude, 'x': self.page.spinBox_x, 'y': self.page.spinBox_y, 'mirror': self.page.checkBox_mirror, 'scale': self.page.spinBox_scale, + 'opacity': self.page.spinBox_opacity, + 'compress': self.page.checkBox_compress, + 'mono': self.page.checkBox_mono, } ) @@ -42,6 +52,26 @@ class Component(Component): self.page.pushButton_color.setStyleSheet(btnStyle) super().update() + def loadPreset(self, pr, *args): + super().loadPreset(pr, *args) + + self.page.lineEdit_color.setText('%s,%s,%s' % pr['color']) + btnStyle = "QPushButton { background-color : %s; outline: none; }" \ + % QColor(*pr['color']).name() + self.page.pushButton_color.setStyleSheet(btnStyle) + + def savePreset(self): + saveValueStore = super().savePreset() + saveValueStore['color'] = self.color + return saveValueStore + + def pickColor(self): + RGBstring, btnStyle = pickColor() + if not RGBstring: + return + self.page.lineEdit_color.setText(RGBstring) + self.page.pushButton_color.setStyleSheet(btnStyle) + def previewRender(self): self.updateChunksize() frame = self.getPreviewFrame(self.width, self.height) @@ -53,10 +83,11 @@ class Component(Component): def preFrameRender(self, **kwargs): super().preFrameRender(**kwargs) self.updateChunksize() + w, h = scale(self.scale, self.width, self.height, str) self.video = FfmpegVideo( inputPath=self.audioFile, - filter_=makeFfmpegFilter(), - width=self.width, height=self.height, + filter_=self.makeFfmpegFilter(), + width=w, height=h, chunkSize=self.chunkSize, frameRate=int(self.settings.value("outputFrameRate")), parent=self.parent, component=self, @@ -65,7 +96,7 @@ class Component(Component): def frameRender(self, frameNo): if FfmpegVideo.threadError is not None: raise FfmpegVideo.threadError - return finalizeFrame(self.video.frame(frameNo)) + return self.finalizeFrame(self.video.frame(frameNo)) def postFrameRender(self): closePipe(self.video.pipe) @@ -74,18 +105,25 @@ class Component(Component): inputFile = self.parent.window.lineEdit_audioFile.text() if not inputFile or not os.path.exists(inputFile): return + duration = getAudioDuration(inputFile) + if not duration: + return + startPt = duration / 3 command = [ self.core.FFMPEG_BIN, '-thread_queue_size', '512', + '-r', self.settings.value("outputFrameRate"), + '-ss', "{0:.3f}".format(startPt), '-i', inputFile, '-f', 'image2pipe', '-pix_fmt', 'rgba', ] - command.extend(self.makeFfmpegFilter()) + command.extend(self.makeFfmpegFilter(preview=True, startPt=startPt)) command.extend([ - '-vcodec', 'rawvideo', '-', - '-ss', '90', + '-an', + '-s:v', '%sx%s' % scale(self.scale, self.width, self.height, str), + '-codec:v', 'rawvideo', '-', '-frames:v', '1', ]) pipe = openPipe( @@ -95,45 +133,57 @@ class Component(Component): byteFrame = pipe.stdout.read(self.chunkSize) closePipe(pipe) - frame = finalizeFrame(self, byteFrame, width, height) + frame = self.finalizeFrame(byteFrame) return frame - def makeFfmpegFilter(self): + def makeFfmpegFilter(self, preview=False, startPt=0): w, h = scale(self.scale, self.width, self.height, str) + if self.amplitude == 0: + amplitude = 'lin' + elif self.amplitude == 1: + amplitude = 'log' + elif self.amplitude == 2: + amplitude = 'sqrt' + elif self.amplitude == 3: + amplitude = 'cbrt' + hexcolor = QColor(*self.color).name() + opacity = "{0:.1f}".format(self.opacity / 100) + return [ '-filter_complex', - '[0:a] showwaves=s=%sx%s:mode=%s,format=rgba [v]' % ( - w, h, self.mode, + '[0:a] %s%s' + 'showwaves=r=30:s=%sx%s:mode=%s:colors=%s@%s:scale=%s%s%s [v1]; ' + '[v1] scale=%s:%s%s [v]' % ( + 'compand=gain=2,' if self.compress else '', + 'aformat=channel_layouts=mono,' if self.mono else '', + self.settings.value("outputWidth"), + self.settings.value("outputHeight"), + str(self.page.comboBox_mode.currentText()).lower(), + hexcolor, opacity, amplitude, + ', drawbox=x=(iw-w)/2:y=(ih-h)/2:w=iw:h=4:color=%s@%s' % ( + hexcolor, opacity + ) if self.mode < 2 else '', + ', hflip' if self.mirror else'', + w, h, + ', trim=duration=%s' % "{0:.3f}".format(startPt + 1) if preview else '', ), '-map', '[v]', - '-map', '0:a', ] def updateChunksize(self): - if self.scale != 100: - width, height = scale(self.scale, self.width, self.height, int) - else: - width, height = self.width, self.height + width, height = scale(self.scale, self.width, self.height, int) self.chunkSize = 4 * width * height - -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(math.ceil(width)), str(math.ceil(height))) - elif returntype == int: - return (math.ceil(width), math.ceil(height)) - else: - return (width, height) - - -def finalizeFrame(self, imageData, width, height): - # frombytes goes here - if self.scale != 100 \ - or self.x != 0 or self.y != 0: - frame = BlankFrame(width, height) - frame.paste(image, box=(self.x, self.y)) - else: - frame = image - return frame + def finalizeFrame(self, imageData): + image = Image.frombytes( + 'RGBA', + scale(self.scale, self.width, self.height, int), + imageData + ) + if self.scale != 100 \ + or self.x != 0 or self.y != 0: + frame = BlankFrame(self.width, self.height) + frame.paste(image, box=(self.x, self.y)) + else: + frame = image + return frame diff --git a/src/components/waveform.ui b/src/components/waveform.ui index 5d62150..0e40380 100644 --- a/src/components/waveform.ui +++ b/src/components/waveform.ui @@ -226,9 +226,31 @@ - + - Mirror + Opacity + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + QAbstractSpinBox::UpDownArrows + + + % + + + 10 + + + 400 + + + 100 @@ -263,6 +285,75 @@ + + + + + + Compress + + + + + + + Mono + + + + + + + Mirror + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Amplitude + + + + + + + + Linear + + + + + Logarithmic + + + + + Square root + + + + + Cubic root + + + + + + diff --git a/src/toolkit/common.py b/src/toolkit/common.py index 128ed08..5d424e0 100644 --- a/src/toolkit/common.py +++ b/src/toolkit/common.py @@ -6,22 +6,9 @@ import string import os import sys import subprocess -import signal -import math from collections import OrderedDict -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(math.ceil(width)), str(math.ceil(height))) - elif returntype == int: - return (math.ceil(width), math.ceil(height)) - else: - return (width, height) - - def badName(name): '''Returns whether a name contains non-alphanumeric chars''' return any([letter in string.punctuation for letter in name]) @@ -69,14 +56,6 @@ def checkOutput(commandList, **kwargs): return subprocess.check_output(commandList, **kwargs) -@pipeWrapper -def openPipe(commandList, **kwargs): - return subprocess.Popen(commandList, **kwargs) - -def closePipe(pipe): - pipe.stdout.close() - pipe.send_signal(signal.SIGINT) - def disableWhenEncoding(func): def decorator(self, *args, **kwargs): if self.encoding: diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py index fea9d4e..e37282f 100644 --- a/src/toolkit/ffmpeg.py +++ b/src/toolkit/ffmpeg.py @@ -6,10 +6,12 @@ import sys import os import subprocess import threading +import signal from queue import PriorityQueue import core -from toolkit.common import checkOutput, openPipe +from toolkit.common import checkOutput, pipeWrapper +from component import ComponentError class FfmpegVideo: @@ -60,7 +62,8 @@ class FfmpegVideo: kwargs['filter_'] ) self.command.extend([ - '-vcodec', 'rawvideo', '-', + '-s:v', '%sx%s' % (self.width, self.height), + '-codec:v', 'rawvideo', '-', ]) self.frameBuffer = PriorityQueue() @@ -85,9 +88,11 @@ class FfmpegVideo: self.frameBuffer.task_done() def fillBuffer(self): + import sys + print(self.command) self.pipe = openPipe( self.command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL, bufsize=10**8 + stderr=sys.__stdout__, bufsize=10**8 ) while True: if self.parent.canceled: @@ -100,7 +105,7 @@ class FfmpegVideo: self.frameBuffer.put((self.frameNo-1, self.lastFrame)) continue except AttributeError: - Video.threadError = ComponentError(self.component, 'video') + FfmpegVideo.threadError = ComponentError(self.component, 'video') break self.currentFrame = self.pipe.stdout.read(self.chunkSize) @@ -109,6 +114,16 @@ class FfmpegVideo: self.lastFrame = self.currentFrame +@pipeWrapper +def openPipe(commandList, **kwargs): + return subprocess.Popen(commandList, **kwargs) + + +def closePipe(pipe): + pipe.stdout.close() + pipe.send_signal(signal.SIGINT) + + def findFfmpeg(): if getattr(sys, 'frozen', False): # The application is frozen @@ -347,7 +362,12 @@ def getAudioDuration(filename): except subprocess.CalledProcessError as ex: fileInfo = ex.output - info = fileInfo.decode("utf-8").split('\n') + try: + info = fileInfo.decode("utf-8").split('\n') + except UnicodeDecodeError as e: + print('Unicode error:', str(e)) + return False + for line in info: if 'Duration' in line: d = line.split(',')[0] diff --git a/src/toolkit/frame.py b/src/toolkit/frame.py index b66e037..f42d4c9 100644 --- a/src/toolkit/frame.py +++ b/src/toolkit/frame.py @@ -6,6 +6,7 @@ from PIL import Image from PIL.ImageQt import ImageQt import sys import os +import math import core @@ -41,6 +42,17 @@ class PaintColor(QtGui.QColor): super().__init__(b, g, r, a) +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(math.ceil(width)), str(math.ceil(height))) + elif returntype == int: + return (math.ceil(width), math.ceil(height)) + else: + return (width, height) + + def defaultSize(framefunc): '''Makes width/height arguments optional''' def decorator(*args): diff --git a/src/video_thread.py b/src/video_thread.py index f27ec21..5963def 100644 --- a/src/video_thread.py +++ b/src/video_thread.py @@ -19,9 +19,11 @@ import time import signal from component import ComponentError -from toolkit import openPipe -from toolkit.ffmpeg import readAudioFile, createFfmpegCommand from toolkit.frame import Checkerboard +from toolkit.ffmpeg import ( + openPipe, readAudioFile, + getAudioDuration, createFfmpegCommand +) class Worker(QtCore.QObject): @@ -132,15 +134,24 @@ class Worker(QtCore.QObject): # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~ # READ AUDIO, INITIALIZE COMPONENTS, OPEN A PIPE TO FFMPEG # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~ - - self.progressBarSetText.emit("Loading audio file...") - audioFileTraits = readAudioFile( - self.inputFile, self - ) - if audioFileTraits is None: - self.cancelExport() - return - self.completeAudioArray, duration = audioFileTraits + if any([ + True if 'pcm' in comp.properties() else False + for comp in self.components + ]): + self.progressBarSetText.emit("Loading audio file...") + audioFileTraits = readAudioFile( + self.inputFile, self + ) + if audioFileTraits is None: + self.cancelExport() + return + self.completeAudioArray, duration = audioFileTraits + else: + duration = getAudioDuration(self.inputFile) + class FakeList: + def __len__(self): + return int((duration * 44100) + 44100) - 1470 + self.completeAudioArray = FakeList() self.progressBarUpdate.emit(0) self.progressBarSetText.emit("Starting components...") @@ -284,7 +295,10 @@ class Worker(QtCore.QObject): numpy.seterr(all='print') - self.out_pipe.stdin.close() + try: + self.out_pipe.stdin.close() + except BrokenPipeError: + print('Broken pipe to ffmpeg!') if self.out_pipe.stderr is not None: print(self.out_pipe.stderr.read()) self.out_pipe.stderr.close() -- cgit v1.2.3 From db1ea1fc4edf19589e82171d48c417742c61c74b Mon Sep 17 00:00:00 2001 From: tassaron Date: Sat, 29 Jul 2017 23:45:37 -0400 Subject: generic preview sound for waveform component with secret preference to use the audio file again --- src/component.py | 2 +- src/components/waveform.py | 38 +++++++++++++++++++++++++------------- src/core.py | 1 + src/mainwindow.py | 2 ++ src/toolkit/ffmpeg.py | 14 ++++++++++---- 5 files changed, 39 insertions(+), 18 deletions(-) (limited to 'src/components/waveform.py') diff --git a/src/component.py b/src/component.py index fc8fbd3..6d49406 100644 --- a/src/component.py +++ b/src/component.py @@ -427,7 +427,7 @@ class ComponentError(RuntimeError): ComponentError.prevErrors.insert(0, name) curTime = time.time() if name in ComponentError.prevErrors[1:] \ - and curTime - ComponentError.lastTime < 0.2: + and curTime - ComponentError.lastTime < 1.0: # Don't create multiple windows for quickly repeated messages return ComponentError.lastTime = time.time() diff --git a/src/components/waveform.py b/src/components/waveform.py index 375b3fc..b4b19e9 100644 --- a/src/components/waveform.py +++ b/src/components/waveform.py @@ -90,7 +90,7 @@ class Component(Component): width=w, height=h, chunkSize=self.chunkSize, frameRate=int(self.settings.value("outputFrameRate")), - parent=self.parent, component=self, + parent=self.parent, component=self, debug=True, ) def frameRender(self, frameNo): @@ -102,20 +102,25 @@ class Component(Component): closePipe(self.video.pipe) def getPreviewFrame(self, width, height): - inputFile = self.parent.window.lineEdit_audioFile.text() - if not inputFile or not os.path.exists(inputFile): - return - duration = getAudioDuration(inputFile) - if not duration: - return - startPt = duration / 3 + genericPreview = self.settings.value("pref_genericPreview") + startPt = 0 + if not genericPreview: + inputFile = self.parent.window.lineEdit_audioFile.text() + if not inputFile or not os.path.exists(inputFile): + return + duration = getAudioDuration(inputFile) + if not duration: + return + startPt = duration / 3 command = [ self.core.FFMPEG_BIN, '-thread_queue_size', '512', '-r', self.settings.value("outputFrameRate"), '-ss', "{0:.3f}".format(startPt), - '-i', inputFile, + '-i', + os.path.join(self.core.wd, 'background.png') + if genericPreview else inputFile, '-f', 'image2pipe', '-pix_fmt', 'rgba', ] @@ -148,13 +153,19 @@ class Component(Component): amplitude = 'cbrt' hexcolor = QColor(*self.color).name() opacity = "{0:.1f}".format(self.opacity / 100) + genericPreview = self.settings.value("pref_genericPreview") return [ '-filter_complex', - '[0:a] %s%s' + '%s%s%s' 'showwaves=r=30:s=%sx%s:mode=%s:colors=%s@%s:scale=%s%s%s [v1]; ' - '[v1] scale=%s:%s%s [v]' % ( - 'compand=gain=2,' if self.compress else '', + '[v1] scale=%s:%s%s,setpts=2.0*PTS [v]' % ( + 'aevalsrc=sin(1*2*PI*t)*sin(880*2*PI*t),' + if preview and genericPreview else '[0:a] ', + 'compand=.3|.3:1|1:-90/-60|-60/-40|-40/-30|-20/-20:6:0:-90:0.2' + ',' if self.compress and not preview else ( + 'compand=gain=5,' if self.compress else '' + ), 'aformat=channel_layouts=mono,' if self.mono else '', self.settings.value("outputWidth"), self.settings.value("outputHeight"), @@ -165,7 +176,8 @@ class Component(Component): ) if self.mode < 2 else '', ', hflip' if self.mirror else'', w, h, - ', trim=duration=%s' % "{0:.3f}".format(startPt + 1) if preview else '', + ', trim=duration=%s' % "{0:.3f}".format(startPt + 1) + if preview else '', ), '-map', '[v]', ] diff --git a/src/core.py b/src/core.py index 1c29774..24bf097 100644 --- a/src/core.py +++ b/src/core.py @@ -506,6 +506,7 @@ class Core: "outputContainer": "MP4", "projectDir": os.path.join(cls.dataDir, 'projects'), "pref_insertCompAtTop": True, + "pref_genericPreview": True, } for parm, value in defaultSettings.items(): diff --git a/src/mainwindow.py b/src/mainwindow.py index 070131c..a97081e 100644 --- a/src/mainwindow.py +++ b/src/mainwindow.py @@ -791,6 +791,8 @@ class MainWindow(QtWidgets.QMainWindow): field.blockSignals(True) field.setText('') field.blockSignals(False) + self.progressBarUpdated(0) + self.progressBarSetText('') @disableWhenEncoding def createNewProject(self, prompt=True): diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py index e37282f..4ea2863 100644 --- a/src/toolkit/ffmpeg.py +++ b/src/toolkit/ffmpeg.py @@ -37,6 +37,7 @@ class FfmpegVideo: self.frameNo = -1 self.currentFrame = 'None' self.map_ = None + self.debug = False if 'loopVideo' in kwargs and kwargs['loopVideo']: self.loopValue = '-1' @@ -47,6 +48,8 @@ class FfmpegVideo: kwargs['filter_'].insert(0, '-filter_complex') else: kwargs['filter_'] = None + if 'debug' in kwargs: + self.debug = True self.command = [ core.Core.FFMPEG_BIN, @@ -62,7 +65,6 @@ class FfmpegVideo: kwargs['filter_'] ) self.command.extend([ - '-s:v', '%sx%s' % (self.width, self.height), '-codec:v', 'rawvideo', '-', ]) @@ -88,11 +90,15 @@ class FfmpegVideo: self.frameBuffer.task_done() def fillBuffer(self): - import sys - print(self.command) + if self.debug: + print(" ".join([word for word in self.command])) + err = sys.__stdout__ + else: + err = subprocess.DEVNULL + self.pipe = openPipe( self.command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, - stderr=sys.__stdout__, bufsize=10**8 + stderr=err, bufsize=10**8 ) while True: if self.parent.canceled: -- cgit v1.2.3 From b6b45d12702f18f041acf65b0d5e34714835ecb4 Mon Sep 17 00:00:00 2001 From: tassaron Date: Sun, 30 Jul 2017 13:04:02 -0400 Subject: added Spectrum component with many options tweaked Waveform, added some ffmpeg logging, made generic widget functions --- src/component.py | 54 ++--- src/components/spectrum.py | 239 +++++++++++++++++++ src/components/spectrum.ui | 582 +++++++++++++++++++++++++++++++++++++++++++++ src/components/waveform.py | 48 ++-- src/components/waveform.ui | 21 +- src/mainwindow.py | 2 +- src/toolkit/common.py | 43 ++++ src/toolkit/ffmpeg.py | 41 ++-- 8 files changed, 959 insertions(+), 71 deletions(-) create mode 100644 src/components/spectrum.py create mode 100644 src/components/spectrum.ui (limited to 'src/components/waveform.py') diff --git a/src/component.py b/src/component.py index 6d49406..1a5a5a4 100644 --- a/src/component.py +++ b/src/component.py @@ -4,9 +4,11 @@ ''' from PyQt5 import uic, QtCore, QtWidgets import os +import sys import time from toolkit.frame import BlankFrame +from toolkit import getWidgetValue, setWidgetValue, connectWidget class ComponentMetaclass(type(QtCore.QObject)): @@ -273,14 +275,9 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): widgets['spinBox'].extend( self.page.findChildren(QtWidgets.QDoubleSpinBox) ) - for widget in widgets['lineEdit']: - widget.textChanged.connect(self.update) - for widget in widgets['checkBox']: - widget.stateChanged.connect(self.update) - for widget in widgets['spinBox']: - widget.valueChanged.connect(self.update) - for widget in widgets['comboBox']: - widget.currentIndexChanged.connect(self.update) + for widgetList in widgets.values(): + for widget in widgetList: + connectWidget(widget, self.update) def update(self): ''' @@ -289,15 +286,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): Call super() at the END if you need to subclass this. ''' for attr, widget in self._trackedWidgets.items(): - if type(widget) == QtWidgets.QLineEdit: - setattr(self, attr, widget.text()) - elif type(widget) == QtWidgets.QSpinBox \ - or type(widget) == QtWidgets.QDoubleSpinBox: - setattr(self, attr, widget.value()) - elif type(widget) == QtWidgets.QCheckBox: - setattr(self, attr, widget.isChecked()) - elif type(widget) == QtWidgets.QComboBox: - setattr(self, attr, widget.currentIndex()) + setattr(self, attr, getWidgetValue(widget)) if not self.core.openingProject: self.parent.drawPreview() saveValueStore = self.savePreset() @@ -313,19 +302,10 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): self.currentPreset = presetName \ if presetName is not None else presetDict['preset'] for attr, widget in self._trackedWidgets.items(): - val = presetDict[ - attr if attr not in self._presetNames + key = attr if attr not in self._presetNames \ else self._presetNames[attr] - ] - if type(widget) == QtWidgets.QLineEdit: - widget.setText(val) - elif type(widget) == QtWidgets.QSpinBox \ - or type(widget) == QtWidgets.QDoubleSpinBox: - widget.setValue(val) - elif type(widget) == QtWidgets.QCheckBox: - widget.setChecked(val) - elif type(widget) == QtWidgets.QComboBox: - widget.setCurrentIndex(val) + val = presetDict[key] + setWidgetValue(widget, val) def savePreset(self): saveValueStore = {} @@ -420,24 +400,30 @@ class ComponentError(RuntimeError): prevErrors = [] lastTime = time.time() - def __init__(self, caller, name): - print('##### ComponentError by %s: %s' % (caller.name, name)) + def __init__(self, caller, name, msg=None): + if msg is None and sys.exc_info()[0] is not None: + msg = str(sys.exc_info()[1]) + else: + msg = 'Unknown error.' + print("##### ComponentError by %s's %s: %s" % ( + caller.name, name, msg)) + + # Don't create multiple windows for quickly repeated messages if len(ComponentError.prevErrors) > 1: ComponentError.prevErrors.pop() ComponentError.prevErrors.insert(0, name) curTime = time.time() if name in ComponentError.prevErrors[1:] \ and curTime - ComponentError.lastTime < 1.0: - # Don't create multiple windows for quickly repeated messages return ComponentError.lastTime = time.time() from toolkit import formatTraceback - import sys if sys.exc_info()[0] is not None: string = ( - "%s component's %s encountered %s %s: %s" % ( + "%s component (#%s): %s encountered %s %s: %s" % ( caller.__class__.name, + str(caller.compPos), name, 'an' if any([ sys.exc_info()[0].__name__.startswith(vowel) diff --git a/src/components/spectrum.py b/src/components/spectrum.py new file mode 100644 index 0000000..261d9cc --- /dev/null +++ b/src/components/spectrum.py @@ -0,0 +1,239 @@ +from PIL import Image +from PyQt5 import QtGui, QtCore, QtWidgets +from PyQt5.QtGui import QColor +import os +import math +import subprocess +import time + +from component import Component +from toolkit.frame import BlankFrame, scale +from toolkit import checkOutput, rgbFromString, pickColor, connectWidget +from toolkit.ffmpeg import ( + openPipe, closePipe, getAudioDuration, FfmpegVideo, exampleSound +) + + +class Component(Component): + name = 'Spectrum' + version = '1.0.0' + + def widget(self, *args): + self.color = (255, 255, 255) + self.previewFrame = None + super().widget(*args) + self.chunkSize = 4 * self.width * self.height + self.changedOptions = True + + if hasattr(self.parent, 'window'): + # update preview when audio file changes (if genericPreview is off) + self.parent.window.lineEdit_audioFile.textChanged.connect( + self.update + ) + + self.trackWidgets( + { + 'filterType': self.page.comboBox_filterType, + 'window': self.page.comboBox_window, + 'amplitude': self.page.comboBox_amplitude, + 'x': self.page.spinBox_x, + 'y': self.page.spinBox_y, + 'mirror': self.page.checkBox_mirror, + 'scale': self.page.spinBox_scale, + 'color': self.page.comboBox_color, + 'compress': self.page.checkBox_compress, + 'mono': self.page.checkBox_mono, + } + ) + for widget in self._trackedWidgets.values(): + connectWidget(widget, lambda: self.changed()) + + def changed(self): + self.changedOptions = True + + def update(self): + count = self.page.stackedWidget.count() + i = self.page.comboBox_filterType.currentIndex() + self.page.stackedWidget.setCurrentIndex(i if i < count else count - 1) + super().update() + + def previewRender(self): + changedSize = self.updateChunksize() + if not changedSize \ + and not self.changedOptions \ + and self.previewFrame is not None: + return self.previewFrame + + frame = self.getPreviewFrame() + self.changedOptions = False + if not frame: + self.previewFrame = None + return BlankFrame(self.width, self.height) + else: + self.previewFrame = frame + return frame + + def preFrameRender(self, **kwargs): + super().preFrameRender(**kwargs) + self.updateChunksize() + w, h = scale(self.scale, self.width, self.height, str) + self.video = FfmpegVideo( + inputPath=self.audioFile, + filter_=self.makeFfmpegFilter(), + width=w, height=h, + chunkSize=self.chunkSize, + frameRate=int(self.settings.value("outputFrameRate")), + parent=self.parent, component=self, + ) + + def frameRender(self, frameNo): + if FfmpegVideo.threadError is not None: + raise FfmpegVideo.threadError + return self.finalizeFrame(self.video.frame(frameNo)) + + def postFrameRender(self): + closePipe(self.video.pipe) + + def getPreviewFrame(self): + genericPreview = self.settings.value("pref_genericPreview") + startPt = 0 + if not genericPreview: + inputFile = self.parent.window.lineEdit_audioFile.text() + if not inputFile or not os.path.exists(inputFile): + return + duration = getAudioDuration(inputFile) + if not duration: + return + startPt = duration / 3 + + command = [ + self.core.FFMPEG_BIN, + '-thread_queue_size', '512', + '-r', self.settings.value("outputFrameRate"), + '-ss', "{0:.3f}".format(startPt), + '-i', + os.path.join(self.core.wd, 'background.png') + if genericPreview else inputFile, + '-f', 'image2pipe', + '-pix_fmt', 'rgba', + ] + command.extend(self.makeFfmpegFilter(preview=True, startPt=startPt)) + command.extend([ + '-an', + '-s:v', '%sx%s' % scale(self.scale, self.width, self.height, str), + '-codec:v', 'rawvideo', '-', + '-frames:v', '1', + ]) + logFilename = os.path.join( + self.core.dataDir, 'preview_%s.log' % str(self.compPos)) + with open(logFilename, 'w') as log: + log.write(" ".join(command) + '\n\n') + with open(logFilename, 'a') as log: + pipe = openPipe( + command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, + stderr=log, bufsize=10**8 + ) + byteFrame = pipe.stdout.read(self.chunkSize) + closePipe(pipe) + + frame = self.finalizeFrame(byteFrame) + return frame + + def makeFfmpegFilter(self, preview=False, startPt=0): + w, h = scale(self.scale, self.width, self.height, str) + if self.amplitude == 0: + amplitude = 'sqrt' + elif self.amplitude == 1: + amplitude = 'cbrt' + elif self.amplitude == 2: + amplitude = '4thrt' + elif self.amplitude == 3: + amplitude = '5thrt' + elif self.amplitude == 4: + amplitude = 'lin' + elif self.amplitude == 5: + amplitude = 'log' + color = self.page.comboBox_color.currentText().lower() + genericPreview = self.settings.value("pref_genericPreview") + + if self.filterType == 0: # Spectrum + filter_ = ( + 'showspectrum=s=%sx%s:slide=scroll:win_func=%s:' + 'color=%s:scale=%s' % ( + self.settings.value("outputWidth"), + self.settings.value("outputHeight"), + self.page.comboBox_window.currentText(), + color, amplitude, + ) + ) + elif self.filterType == 1: # Histogram + filter_ = ( + 'ahistogram=r=%s:s=%sx%s:dmode=separate' % ( + self.settings.value("outputFrameRate"), + self.settings.value("outputWidth"), + self.settings.value("outputHeight"), + ) + ) + elif self.filterType == 2: # Vector Scope + filter_ = ( + 'avectorscope=s=%sx%s:draw=line:m=polar:scale=log' % ( + self.settings.value("outputWidth"), + self.settings.value("outputHeight"), + ) + ) + elif self.filterType == 3: # Musical Scale + filter_ = ( + 'showcqt=r=%s:s=%sx%s:count=30:text=0' % ( + self.settings.value("outputFrameRate"), + self.settings.value("outputWidth"), + self.settings.value("outputHeight"), + ) + ) + elif self.filterType == 4: # Phase + filter_ = ( + 'aphasemeter=r=%s:s=%sx%s:mpc=white:video=1[atrash][vtmp]; ' + '[atrash] anullsink; [vtmp] null' % ( + self.settings.value("outputFrameRate"), + self.settings.value("outputWidth"), + self.settings.value("outputHeight"), + ) + ) + + return [ + '-filter_complex', + '%s%s%s%s%s [v1]; ' + '[v1] scale=%s:%s%s [v]' % ( + exampleSound() if preview and genericPreview else '[0:a] ', + 'compand=gain=4,' if self.compress else '', + 'aformat=channel_layouts=mono,' if self.mono else '', + filter_, + ', hflip' if self.mirror else'', + w, h, + ', trim=start=%s:end=%s' % ( + "{0:.3f}".format(startPt + 15), + "{0:.3f}".format(startPt + 15.5) + ) if preview else '', + ), + '-map', '[v]', + ] + + def updateChunksize(self): + width, height = scale(self.scale, self.width, self.height, int) + oldChunkSize = int(self.chunkSize) + self.chunkSize = 4 * width * height + changed = self.chunkSize != oldChunkSize + return changed + + def finalizeFrame(self, imageData): + image = Image.frombytes( + 'RGBA', + scale(self.scale, self.width, self.height, int), + imageData + ) + if self.scale != 100 \ + or self.x != 0 or self.y != 0: + frame = BlankFrame(self.width, self.height) + frame.paste(image, box=(self.x, self.y)) + else: + frame = image + return frame diff --git a/src/components/spectrum.ui b/src/components/spectrum.ui new file mode 100644 index 0000000..59ca0b8 --- /dev/null +++ b/src/components/spectrum.ui @@ -0,0 +1,582 @@ + + + Form + + + + 0 + 0 + 586 + 197 + + + + + 0 + 0 + + + + + 0 + 197 + + + + Form + + + + + + 4 + + + + + + + + 0 + 0 + + + + Type + + + + + + + + Spectrum + + + + + Histogram + + + + + Vector Scope + + + + + Musical Scale + + + + + Phase + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 5 + 20 + + + + + + + + + 0 + 0 + + + + X + + + + + + + + 0 + 0 + + + + + 80 + 16777215 + + + + -10000 + + + 10000 + + + + + + + + 0 + 0 + + + + Y + + + + + + + + 0 + 0 + + + + + 80 + 16777215 + + + + + 0 + 0 + + + + -10000 + + + 10000 + + + 0 + + + + + + + + + + + Compress + + + + + + + Mono + + + + + + + Mirror + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Scale + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + QAbstractSpinBox::UpDownArrows + + + % + + + 10 + + + 400 + + + 100 + + + + + + + + + + 0 + 0 + + + + false + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + + + + 0 + 0 + 561 + 72 + + + + + QLayout::SetMaximumSize + + + 0 + + + + + QLayout::SetDefaultConstraint + + + + + + 0 + 0 + + + + + 31 + 0 + + + + Window + + + 4 + + + + + + + + hann + + + + + gauss + + + + + tukey + + + + + dolph + + + + + cauchy + + + + + parzen + + + + + poisson + + + + + rect + + + + + bartlett + + + + + hanning + + + + + hamming + + + + + blackman + + + + + welch + + + + + flattop + + + + + bharris + + + + + bnuttall + + + + + lanczos + + + + + + + + + 0 + 0 + + + + Amplitude + + + 4 + + + + + + + + Square root + + + + + Cubic root + + + + + 4thrt + + + + + 5thrt + + + + + Linear + + + + + Logarithmic + + + + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 10 + 20 + + + + + + + + + + + + + 0 + 0 + + + + Color + + + 4 + + + + + + + + Channel + + + + + Intensity + + + + + Rainbow + + + + + Moreland + + + + + Nebulae + + + + + Fire + + + + + Fiery + + + + + Fruit + + + + + Cool + + + + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 10 + 20 + + + + + + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + diff --git a/src/components/waveform.py b/src/components/waveform.py index b4b19e9..6c5133d 100644 --- a/src/components/waveform.py +++ b/src/components/waveform.py @@ -8,7 +8,9 @@ import subprocess from component import Component from toolkit.frame import BlankFrame, scale from toolkit import checkOutput, rgbFromString, pickColor -from toolkit.ffmpeg import openPipe, closePipe, getAudioDuration, FfmpegVideo +from toolkit.ffmpeg import ( + openPipe, closePipe, getAudioDuration, FfmpegVideo, exampleSound +) class Component(Component): @@ -112,6 +114,8 @@ class Component(Component): if not duration: return startPt = duration / 3 + if startPt + 3 > duration: + startPt += startPt - 3 command = [ self.core.FFMPEG_BIN, @@ -154,29 +158,43 @@ class Component(Component): hexcolor = QColor(*self.color).name() opacity = "{0:.1f}".format(self.opacity / 100) genericPreview = self.settings.value("pref_genericPreview") + if self.mode < 3: + filter_ = 'showwaves=r=%s:s=%sx%s:mode=%s:colors=%s@%s:scale=%s' % ( + self.settings.value("outputFrameRate"), + self.settings.value("outputWidth"), + self.settings.value("outputHeight"), + self.page.comboBox_mode.currentText().lower() + if self.mode != 3 else 'p2p', + hexcolor, opacity, amplitude, + ) + elif self.mode > 2: + filter_ = ( + 'showfreqs=s=%sx%s:mode=%s:colors=%s@%s' + ':ascale=%s:fscale=%s' % ( + self.settings.value("outputWidth"), + self.settings.value("outputHeight"), + 'line' if self.mode == 4 else 'bar', + hexcolor, opacity, amplitude, + 'log' if self.mono else 'lin' + ) + ) return [ '-filter_complex', '%s%s%s' - 'showwaves=r=30:s=%sx%s:mode=%s:colors=%s@%s:scale=%s%s%s [v1]; ' - '[v1] scale=%s:%s%s,setpts=2.0*PTS [v]' % ( - 'aevalsrc=sin(1*2*PI*t)*sin(880*2*PI*t),' - if preview and genericPreview else '[0:a] ', - 'compand=.3|.3:1|1:-90/-60|-60/-40|-40/-30|-20/-20:6:0:-90:0.2' - ',' if self.compress and not preview else ( - 'compand=gain=5,' if self.compress else '' - ), - 'aformat=channel_layouts=mono,' if self.mono else '', - self.settings.value("outputWidth"), - self.settings.value("outputHeight"), - str(self.page.comboBox_mode.currentText()).lower(), - hexcolor, opacity, amplitude, + '%s%s%s [v1]; ' + '[v1] scale=%s:%s%s [v]' % ( + exampleSound() if preview and genericPreview else '[0:a] ', + 'compand=gain=4,' if self.compress else '', + 'aformat=channel_layouts=mono,' + if self.mono and self.mode < 3 else '', + filter_, ', drawbox=x=(iw-w)/2:y=(ih-h)/2:w=iw:h=4:color=%s@%s' % ( hexcolor, opacity ) if self.mode < 2 else '', ', hflip' if self.mirror else'', w, h, - ', trim=duration=%s' % "{0:.3f}".format(startPt + 1) + ', trim=duration=%s' % "{0:.3f}".format(startPt + 3) if preview else '', ), '-map', '[v]', diff --git a/src/components/waveform.ui b/src/components/waveform.ui index 0e40380..5473f33 100644 --- a/src/components/waveform.ui +++ b/src/components/waveform.ui @@ -66,12 +66,17 @@ - P2p + Point - Point + Frequency Bar + + + + + Frequency Line @@ -180,12 +185,16 @@ - Wave Color + Color - + + + Qt::ImhNone + + @@ -244,10 +253,10 @@ % - 10 + 0 - 400 + 100 100 diff --git a/src/mainwindow.py b/src/mainwindow.py index a97081e..d9e95e2 100644 --- a/src/mainwindow.py +++ b/src/mainwindow.py @@ -581,7 +581,7 @@ class MainWindow(QtWidgets.QMainWindow): self.showMessage( msg=msg, detail=detail, - icon='Warning', + icon='Critical', ) def changeEncodingStatus(self, status): diff --git a/src/toolkit/common.py b/src/toolkit/common.py index 5d424e0..db278c0 100644 --- a/src/toolkit/common.py +++ b/src/toolkit/common.py @@ -113,3 +113,46 @@ def formatTraceback(tb=None): import sys tb = sys.exc_info()[2] return 'Traceback:\n%s' % "\n".join(traceback.format_tb(tb)) + + +def connectWidget(widget, func): + if type(widget) == QtWidgets.QLineEdit: + widget.textChanged.connect(func) + elif type(widget) == QtWidgets.QSpinBox \ + or type(widget) == QtWidgets.QDoubleSpinBox: + widget.valueChanged.connect(func) + elif type(widget) == QtWidgets.QCheckBox: + widget.stateChanged.connect(func) + elif type(widget) == QtWidgets.QComboBox: + widget.currentIndexChanged.connect(func) + else: + return False + return True + + +def setWidgetValue(widget, val): + '''Generic setValue method for use with any typical QtWidget''' + if type(widget) == QtWidgets.QLineEdit: + widget.setText(val) + elif type(widget) == QtWidgets.QSpinBox \ + or type(widget) == QtWidgets.QDoubleSpinBox: + widget.setValue(val) + elif type(widget) == QtWidgets.QCheckBox: + widget.setChecked(val) + elif type(widget) == QtWidgets.QComboBox: + widget.setCurrentIndex(val) + else: + return False + return True + + +def getWidgetValue(widget): + if type(widget) == QtWidgets.QLineEdit: + return widget.text() + elif type(widget) == QtWidgets.QSpinBox \ + or type(widget) == QtWidgets.QDoubleSpinBox: + return widget.value() + elif type(widget) == QtWidgets.QCheckBox: + return widget.isChecked() + elif type(widget) == QtWidgets.QComboBox: + return widget.currentIndex() diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py index 4ea2863..3421049 100644 --- a/src/toolkit/ffmpeg.py +++ b/src/toolkit/ffmpeg.py @@ -37,7 +37,6 @@ class FfmpegVideo: self.frameNo = -1 self.currentFrame = 'None' self.map_ = None - self.debug = False if 'loopVideo' in kwargs and kwargs['loopVideo']: self.loopValue = '-1' @@ -48,8 +47,6 @@ class FfmpegVideo: kwargs['filter_'].insert(0, '-filter_complex') else: kwargs['filter_'] = None - if 'debug' in kwargs: - self.debug = True self.command = [ core.Core.FFMPEG_BIN, @@ -90,16 +87,15 @@ class FfmpegVideo: self.frameBuffer.task_done() def fillBuffer(self): - if self.debug: - print(" ".join([word for word in self.command])) - err = sys.__stdout__ - else: - err = subprocess.DEVNULL - - self.pipe = openPipe( - self.command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, - stderr=err, bufsize=10**8 - ) + logFilename = os.path.join( + core.Core.dataDir, 'extra_%s.log' % str(self.component.compPos)) + with open(logFilename, 'w') as log: + log.write(" ".join(self.command) + '\n\n') + with open(logFilename, 'a') as log: + self.pipe = openPipe( + self.command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, + stderr=log, bufsize=10**8 + ) while True: if self.parent.canceled: break @@ -111,10 +107,18 @@ class FfmpegVideo: self.frameBuffer.put((self.frameNo-1, self.lastFrame)) continue except AttributeError: - FfmpegVideo.threadError = ComponentError(self.component, 'video') + FfmpegVideo.threadError = ComponentError( + self.component, 'video', + "Video seemed playable but wasn't." + ) break - self.currentFrame = self.pipe.stdout.read(self.chunkSize) + try: + self.currentFrame = self.pipe.stdout.read(self.chunkSize) + except ValueError: + FfmpegVideo.threadError = ComponentError( + self.component, 'video') + if len(self.currentFrame) != 0: self.frameBuffer.put((self.frameNo, self.currentFrame)) self.lastFrame = self.currentFrame @@ -446,3 +450,10 @@ def readAudioFile(filename, videoWorker): completeAudioArray = completeAudioArrayCopy return (completeAudioArray, duration) + + +def exampleSound(): + return ( + 'aevalsrc=tan(random(1)*PI*t)*sin(random(0)*2*PI*t),' + 'apulsator=offset_l=0.5:offset_r=0.5,' + ) -- cgit v1.2.3 From 3c1b52205f183e9a2c943c5f666ed2c01db3aaf5 Mon Sep 17 00:00:00 2001 From: tassaron Date: Tue, 1 Aug 2017 17:57:39 -0400 Subject: component class now tracks colorwidgets so adding new color-selection widgets is now simple --- setup.py | 2 +- src/component.py | 73 +++++++++++++++++++++++++++++++++++++++++----- src/components/color.py | 58 +++++------------------------------- src/components/original.py | 35 +++------------------- src/components/text.py | 27 ++--------------- src/components/waveform.py | 40 ++++--------------------- src/toolkit/common.py | 19 ------------ src/toolkit/frame.py | 6 ++-- 8 files changed, 90 insertions(+), 170 deletions(-) (limited to 'src/components/waveform.py') diff --git a/setup.py b/setup.py index d4f226b..4a4511f 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup import os -__version__ = '2.0.0.rc2' +__version__ = '2.0.0.rc3' def package_files(directory): diff --git a/src/component.py b/src/component.py index 36ad9d3..d47aeae 100644 --- a/src/component.py +++ b/src/component.py @@ -3,18 +3,20 @@ on making a valid component. ''' from PyQt5 import uic, QtCore, QtWidgets +from PyQt5.QtGui import QColor import os import sys import time from toolkit.frame import BlankFrame -from toolkit import getWidgetValue, setWidgetValue, connectWidget +from toolkit import ( + getWidgetValue, setWidgetValue, connectWidget, rgbFromString +) class ComponentMetaclass(type(QtCore.QObject)): ''' - Checks the validity of each Component class imported, and - mutates some attributes for easier use by the core program. + Checks the validity of each Component class and mutates some attrs. E.g., takes only major version from version string & decorates methods ''' @@ -173,6 +175,8 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): self._trackedWidgets = {} self._presetNames = {} self._commandArgs = {} + self._colorWidgets = {} + self._relativeWidgets = {} self._lockedProperties = None self._lockedError = None @@ -188,7 +192,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): ) # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~ - # Critical Methods + # Render Methods # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~ def previewRender(self): @@ -286,7 +290,17 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): Call super() at the END if you need to subclass this. ''' for attr, widget in self._trackedWidgets.items(): - setattr(self, attr, getWidgetValue(widget)) + if attr in self._colorWidgets: + rgbTuple = rgbFromString(widget.text()) + setattr(self, attr, rgbTuple) + btnStyle = ( + "QPushButton { background-color : %s; outline: none; }" + % QColor(*rgbTuple).name() + ) + self._colorWidgets[attr].setStyleSheet(btnStyle) + else: + setattr(self, attr, getWidgetValue(widget)) + if not self.core.openingProject: self.parent.drawPreview() saveValueStore = self.savePreset() @@ -305,7 +319,16 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): key = attr if attr not in self._presetNames \ else self._presetNames[attr] val = presetDict[key] - setWidgetValue(widget, val) + + if attr in self._colorWidgets: + widget.setText('%s,%s,%s' % val) + btnStyle = ( + "QPushButton { background-color : %s; outline: none; }" + % QColor(*val).name() + ) + self._colorWidgets[attr].setStyleSheet(btnStyle) + else: + setWidgetValue(widget, val) def savePreset(self): saveValueStore = {} @@ -352,7 +375,12 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): self._trackedWidgets = trackDict for kwarg in kwargs: try: - if kwarg in ('presetNames', 'commandArgs'): + if kwarg in ( + 'presetNames', + 'commandArgs', + 'colorWidgets', + 'relativeWidgets', + ): setattr(self, '_%s' % kwarg, kwargs[kwarg]) else: raise ComponentError( @@ -360,6 +388,37 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): except ComponentError: continue + if kwarg == 'colorWidgets': + def makeColorFunc(attr): + def pickColor_(): + self.pickColor( + self._trackedWidgets[attr], + self._colorWidgets[attr] + ) + return pickColor_ + self._colorFuncs = { + attr: makeColorFunc(attr) for attr in kwargs[kwarg] + } + for attr, func in self._colorFuncs.items(): + self._colorWidgets[attr].clicked.connect(func) + self._colorWidgets[attr].setStyleSheet( + "QPushButton {" + "background-color : #FFFFFF; outline: none; }" + ) + + def pickColor(self, textWidget, button): + '''Use color picker to get color input from the user.''' + dialog = QtWidgets.QColorDialog() + dialog.setOption(QtWidgets.QColorDialog.ShowAlphaChannel, True) + color = dialog.getColor() + if color.isValid(): + RGBstring = '%s,%s,%s' % ( + str(color.red()), str(color.green()), str(color.blue())) + btnStyle = "QPushButton{background-color: %s; outline: none;}" \ + % color.name() + textWidget.setText(RGBstring) + button.setStyleSheet(btnStyle) + def lockProperties(self, propList): self._lockedProperties = propList diff --git a/src/components/color.py b/src/components/color.py index 2abd79a..d6fffc6 100644 --- a/src/components/color.py +++ b/src/components/color.py @@ -6,7 +6,6 @@ import os from component import Component from toolkit.frame import BlankFrame, FloodFrame, FramePainter, PaintColor -from toolkit import rgbFromString, pickColor class Component(Component): @@ -14,25 +13,12 @@ class Component(Component): version = '1.0.0' def widget(self, *args): - self.color1 = (0, 0, 0) - self.color2 = (133, 133, 133) self.x = 0 self.y = 0 super().widget(*args) - self.page.lineEdit_color1.setText('%s,%s,%s' % self.color1) - self.page.lineEdit_color2.setText('%s,%s,%s' % self.color2) - - btnStyle1 = "QPushButton { background-color : %s; outline: none; }" \ - % QColor(*self.color1).name() - - btnStyle2 = "QPushButton { background-color : %s; outline: none; }" \ - % QColor(*self.color2).name() - - self.page.pushButton_color1.setStyleSheet(btnStyle1) - self.page.pushButton_color2.setStyleSheet(btnStyle2) - self.page.pushButton_color1.clicked.connect(lambda: self.pickColor(1)) - self.page.pushButton_color2.clicked.connect(lambda: self.pickColor(2)) + self.page.lineEdit_color1.setText('0,0,0') + self.page.lineEdit_color2.setText('133,133,133') # disable color #2 until non-default 'fill' option gets changed self.page.lineEdit_color2.setDisabled(True) @@ -66,16 +52,18 @@ class Component(Component): 'LG_end': self.page.spinBox_linearGradient_end, 'RG_centre': self.page.spinBox_radialGradient_spread, 'fillType': self.page.comboBox_fill, + 'color1': self.page.lineEdit_color1, + 'color2': self.page.lineEdit_color2, }, presetNames={ 'sizeWidth': 'width', 'sizeHeight': 'height', - } + }, colorWidgets={ + 'color1': self.page.pushButton_color1, + 'color2': self.page.pushButton_color2, + }, ) def update(self): - self.color1 = rgbFromString(self.page.lineEdit_color1.text()) - self.color2 = rgbFromString(self.page.lineEdit_color2.text()) - fillType = self.page.comboBox_fill.currentIndex() if fillType == 0: self.page.lineEdit_color2.setEnabled(False) @@ -161,36 +149,6 @@ class Component(Component): return image.finalize() - def loadPreset(self, pr, *args): - super().loadPreset(pr, *args) - - self.page.lineEdit_color1.setText('%s,%s,%s' % pr['color1']) - self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2']) - - btnStyle1 = "QPushButton { background-color : %s; outline: none; }" \ - % QColor(*pr['color1']).name() - btnStyle2 = "QPushButton { background-color : %s; outline: none; }" \ - % QColor(*pr['color2']).name() - self.page.pushButton_color1.setStyleSheet(btnStyle1) - self.page.pushButton_color2.setStyleSheet(btnStyle2) - - def savePreset(self): - saveValueStore = super().savePreset() - saveValueStore['color1'] = self.color1 - saveValueStore['color2'] = self.color2 - return saveValueStore - - def pickColor(self, num): - RGBstring, btnStyle = 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) - def commandHelp(self): print('Specify a color:\n color=255,255,255') diff --git a/src/components/original.py b/src/components/original.py index 621af6f..950ac7b 100644 --- a/src/components/original.py +++ b/src/components/original.py @@ -8,7 +8,6 @@ from copy import copy from component import Component from toolkit.frame import BlankFrame -from toolkit import rgbFromString, pickColor class Component(Component): @@ -22,7 +21,6 @@ class Component(Component): return ['pcm'] def widget(self, *args): - self.visColor = (255, 255, 255) self.scale = 20 self.y = 0 super().widget(*args) @@ -33,35 +31,17 @@ class Component(Component): self.page.comboBox_visLayout.addItem("Top") self.page.comboBox_visLayout.setCurrentIndex(0) - self.page.lineEdit_visColor.setText('%s,%s,%s' % self.visColor) - self.page.pushButton_visColor.clicked.connect(lambda: self.pickColor()) - btnStyle = "QPushButton { background-color : %s; outline: none; }" \ - % QColor(*self.visColor).name() - self.page.pushButton_visColor.setStyleSheet(btnStyle) + self.page.lineEdit_visColor.setText('255,255,255') self.trackWidgets({ + 'visColor': self.page.lineEdit_visColor, 'layout': self.page.comboBox_visLayout, 'scale': self.page.spinBox_scale, 'y': self.page.spinBox_y, + }, colorWidgets={ + 'visColor': self.page.pushButton_visColor, }) - def update(self): - self.visColor = rgbFromString(self.page.lineEdit_visColor.text()) - super().update() - - def loadPreset(self, pr, *args): - super().loadPreset(pr, *args) - - self.page.lineEdit_visColor.setText('%s,%s,%s' % pr['visColor']) - btnStyle = "QPushButton { background-color : %s; outline: none; }" \ - % QColor(*pr['visColor']).name() - self.page.pushButton_visColor.setStyleSheet(btnStyle) - - def savePreset(self): - saveValueStore = super().savePreset() - saveValueStore['visColor'] = self.visColor - return saveValueStore - def previewRender(self): spectrum = numpy.fromfunction( lambda x: float(self.scale)/2500*(x-128)**2, (255,), dtype="int16") @@ -99,13 +79,6 @@ class Component(Component): self.spectrumArray[arrayNo], self.visColor, self.layout) - def pickColor(self): - RGBstring, btnStyle = pickColor() - if not RGBstring: - return - self.page.lineEdit_visColor.setText(RGBstring) - self.page.pushButton_visColor.setStyleSheet(btnStyle) - def transformData( self, i, completeAudioArray, sampleSize, smoothConstantDown, smoothConstantUp, lastSpectrum): diff --git a/src/components/text.py b/src/components/text.py index 8a302ff..1fe3467 100644 --- a/src/components/text.py +++ b/src/components/text.py @@ -5,7 +5,6 @@ import os from component import Component from toolkit.frame import FramePainter -from toolkit import rgbFromString, pickColor class Component(Component): @@ -33,11 +32,6 @@ class Component(Component): self.page.comboBox_textAlign.addItem("Right") self.page.lineEdit_textColor.setText('%s,%s,%s' % self.textColor) - self.page.pushButton_textColor.clicked.connect(self.pickColor) - btnStyle = "QPushButton { background-color : %s; outline: none; }" \ - % QColor(*self.textColor).name() - self.page.pushButton_textColor.setStyleSheet(btnStyle) - self.page.lineEdit_title.setText(self.title) self.page.comboBox_textAlign.setCurrentIndex(int(self.alignment)) self.page.spinBox_fontSize.setValue(int(self.fontSize)) @@ -48,21 +42,18 @@ class Component(Component): self.update ) self.trackWidgets({ + 'textColor': self.page.lineEdit_textColor, 'title': self.page.lineEdit_title, 'alignment': self.page.comboBox_textAlign, 'fontSize': self.page.spinBox_fontSize, 'xPosition': self.page.spinBox_xTextAlign, 'yPosition': self.page.spinBox_yTextAlign, + }, colorWidgets={ + 'textColor': self.page.pushButton_textColor, }) def update(self): self.titleFont = self.page.fontComboBox_titleFont.currentFont() - self.textColor = rgbFromString( - self.page.lineEdit_textColor.text()) - btnStyle = "QPushButton { background-color : %s; outline: none; }" \ - % QColor(*self.textColor).name() - self.page.pushButton_textColor.setStyleSheet(btnStyle) - super().update() def getXY(self): @@ -86,15 +77,10 @@ class Component(Component): font = QFont() font.fromString(pr['titleFont']) self.page.fontComboBox_titleFont.setCurrentFont(font) - self.page.lineEdit_textColor.setText('%s,%s,%s' % pr['textColor']) - btnStyle = "QPushButton { background-color : %s; outline: none; }" \ - % QColor(*pr['textColor']).name() - self.page.pushButton_textColor.setStyleSheet(btnStyle) def savePreset(self): saveValueStore = super().savePreset() saveValueStore['titleFont'] = self.titleFont.toString() - saveValueStore['textColor'] = self.textColor return saveValueStore def previewRender(self): @@ -122,13 +108,6 @@ class Component(Component): return image.finalize() - def pickColor(self): - RGBstring, btnStyle = pickColor() - if not RGBstring: - return - self.page.lineEdit_textColor.setText(RGBstring) - self.page.pushButton_textColor.setStyleSheet(btnStyle) - def commandHelp(self): print('Enter a string to use as centred white text:') print(' "title=User Error"') diff --git a/src/components/waveform.py b/src/components/waveform.py index 6c5133d..9c3cf86 100644 --- a/src/components/waveform.py +++ b/src/components/waveform.py @@ -7,7 +7,7 @@ import subprocess from component import Component from toolkit.frame import BlankFrame, scale -from toolkit import checkOutput, rgbFromString, pickColor +from toolkit import checkOutput from toolkit.ffmpeg import ( openPipe, closePipe, getAudioDuration, FfmpegVideo, exampleSound ) @@ -18,15 +18,9 @@ class Component(Component): version = '1.0.0' def widget(self, *args): - self.color = (255, 255, 255) super().widget(*args) - self.page.lineEdit_color.setText('%s,%s,%s' % self.color) - btnStyle = "QPushButton { background-color : %s; outline: none; }" \ - % QColor(*self.color).name() - self.page.pushButton_color.setStyleSheet(btnStyle) - self.page.pushButton_color.clicked.connect(lambda: self.pickColor()) - self.page.spinBox_scale.valueChanged.connect(self.updateChunksize) + self.page.lineEdit_color.setText('255,255,255') if hasattr(self.parent, 'window'): self.parent.window.lineEdit_audioFile.textChanged.connect( @@ -35,6 +29,7 @@ class Component(Component): self.trackWidgets( { + 'color': self.page.lineEdit_color, 'mode': self.page.comboBox_mode, 'amplitude': self.page.comboBox_amplitude, 'x': self.page.spinBox_x, @@ -44,36 +39,11 @@ class Component(Component): 'opacity': self.page.spinBox_opacity, 'compress': self.page.checkBox_compress, 'mono': self.page.checkBox_mono, + }, colorWidgets={ + 'color': self.page.pushButton_color, } ) - def update(self): - self.color = rgbFromString(self.page.lineEdit_color.text()) - btnStyle = "QPushButton { background-color : %s; outline: none; }" \ - % QColor(*self.color).name() - self.page.pushButton_color.setStyleSheet(btnStyle) - super().update() - - def loadPreset(self, pr, *args): - super().loadPreset(pr, *args) - - self.page.lineEdit_color.setText('%s,%s,%s' % pr['color']) - btnStyle = "QPushButton { background-color : %s; outline: none; }" \ - % QColor(*pr['color']).name() - self.page.pushButton_color.setStyleSheet(btnStyle) - - def savePreset(self): - saveValueStore = super().savePreset() - saveValueStore['color'] = self.color - return saveValueStore - - def pickColor(self): - RGBstring, btnStyle = pickColor() - if not RGBstring: - return - self.page.lineEdit_color.setText(RGBstring) - self.page.pushButton_color.setStyleSheet(btnStyle) - def previewRender(self): self.updateChunksize() frame = self.getPreviewFrame(self.width, self.height) diff --git a/src/toolkit/common.py b/src/toolkit/common.py index db278c0..eba57d9 100644 --- a/src/toolkit/common.py +++ b/src/toolkit/common.py @@ -74,25 +74,6 @@ def disableWhenOpeningProject(func): return decorator -def pickColor(): - ''' - Use color picker to get color input from the user, - and return this as an RGB string and QPushButton stylesheet. - In a subclass apply stylesheet to any color selection widgets - ''' - dialog = QtWidgets.QColorDialog() - dialog.setOption(QtWidgets.QColorDialog.ShowAlphaChannel, True) - color = dialog.getColor() - if color.isValid(): - RGBstring = '%s,%s,%s' % ( - str(color.red()), str(color.green()), str(color.blue())) - btnStyle = "QPushButton{background-color: %s; outline: none;}" \ - % color.name() - return RGBstring, btnStyle - else: - return None, None - - def rgbFromString(string): '''Turns an RGB string like "255, 255, 255" into a tuple''' try: diff --git a/src/toolkit/frame.py b/src/toolkit/frame.py index f42d4c9..c007188 100644 --- a/src/toolkit/frame.py +++ b/src/toolkit/frame.py @@ -42,9 +42,9 @@ class PaintColor(QtGui.QColor): super().__init__(b, g, r, a) -def scale(scale, width, height, returntype=None): - width = (float(width) / 100.0) * float(scale) - height = (float(height) / 100.0) * float(scale) +def scale(scalePercent, width, height, returntype=None): + width = (float(width) / 100.0) * float(scalePercent) + height = (float(height) / 100.0) * float(scalePercent) if returntype == str: return (str(math.ceil(width)), str(math.ceil(height))) elif returntype == int: -- cgit v1.2.3 From 5784cdbcf87556b61519782cd1fc27065ffbc631 Mon Sep 17 00:00:00 2001 From: tassaron Date: Tue, 1 Aug 2017 21:57:36 -0400 Subject: x/y pixel values update to match output resolution --- src/component.py | 39 ++++++++++++++++++++++++++++++++++++--- src/components/color.py | 3 +++ src/components/image.py | 3 +++ src/components/original.py | 2 ++ src/components/spectrum.py | 3 +++ src/components/text.py | 19 +++++++++++-------- src/components/video.py | 3 +++ src/components/waveform.py | 3 +++ src/mainwindow.py | 5 ++++- 9 files changed, 68 insertions(+), 12 deletions(-) (limited to 'src/components/waveform.py') diff --git a/src/component.py b/src/component.py index d47aeae..5dfe2ab 100644 --- a/src/component.py +++ b/src/component.py @@ -6,6 +6,7 @@ from PyQt5 import uic, QtCore, QtWidgets from PyQt5.QtGui import QColor import os import sys +import math import time from toolkit.frame import BlankFrame @@ -176,7 +177,9 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): self._presetNames = {} self._commandArgs = {} self._colorWidgets = {} + self._colorFuncs = {} self._relativeWidgets = {} + self._relativeValues = {} self._lockedProperties = None self._lockedError = None @@ -291,14 +294,44 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): ''' for attr, widget in self._trackedWidgets.items(): if attr in self._colorWidgets: + # Color Widgets: text stored as tuple & update the button color rgbTuple = rgbFromString(widget.text()) - setattr(self, attr, rgbTuple) btnStyle = ( "QPushButton { background-color : %s; outline: none; }" - % QColor(*rgbTuple).name() - ) + % QColor(*rgbTuple).name()) self._colorWidgets[attr].setStyleSheet(btnStyle) + setattr(self, attr, rgbTuple) + + elif attr in self._relativeWidgets: + # Relative widgets: number scales to fit export resolution + if self._relativeWidgets[attr] == 'x': + dimension = self.width + else: + dimension = self.height + try: + oldUserValue = getattr(self, attr) + except AttributeError: + oldUserValue = self._trackedWidgets[attr].value() + newUserValue = self._trackedWidgets[attr].value() + newRelativeVal = newUserValue / dimension + + if attr in self._relativeValues: + if oldUserValue == newUserValue: + oldRelativeVal = self._relativeValues[attr] + if oldRelativeVal != newRelativeVal: + # Float changed without pixel value changing, which + # means the pixel value needs to be updated + self._trackedWidgets[attr].blockSignals(True) + self._trackedWidgets[attr].setValue( + math.ceil(dimension * oldRelativeVal)) + self._trackedWidgets[attr].blockSignals(False) + if oldUserValue != newUserValue \ + or attr not in self._relativeValues: + self._relativeValues[attr] = newRelativeVal + setattr(self, attr, self._trackedWidgets[attr].value()) + else: + # Normal tracked widget setattr(self, attr, getWidgetValue(widget)) if not self.core.openingProject: diff --git a/src/components/color.py b/src/components/color.py index d6fffc6..703caca 100644 --- a/src/components/color.py +++ b/src/components/color.py @@ -60,6 +60,9 @@ class Component(Component): }, colorWidgets={ 'color1': self.page.pushButton_color1, 'color2': self.page.pushButton_color2, + }, relativeWidgets={ + 'x': 'x', + 'y': 'y', }, ) diff --git a/src/components/image.py b/src/components/image.py index a96f127..2ffa5a1 100644 --- a/src/components/image.py +++ b/src/components/image.py @@ -28,6 +28,9 @@ class Component(Component): 'imagePath': 'image', 'xPosition': 'x', 'yPosition': 'y', + }, relativeWidgets={ + 'xPosition': 'x', + 'yPosition': 'y', }, ) diff --git a/src/components/original.py b/src/components/original.py index 950ac7b..67e3239 100644 --- a/src/components/original.py +++ b/src/components/original.py @@ -40,6 +40,8 @@ class Component(Component): 'y': self.page.spinBox_y, }, colorWidgets={ 'visColor': self.page.pushButton_visColor, + }, relativeWidgets={ + 'y': 'y', }) def previewRender(self): diff --git a/src/components/spectrum.py b/src/components/spectrum.py index 8ab8404..2cc641d 100644 --- a/src/components/spectrum.py +++ b/src/components/spectrum.py @@ -49,6 +49,9 @@ class Component(Component): 'compress': self.page.checkBox_compress, 'mono': self.page.checkBox_mono, 'hue': self.page.spinBox_hue, + }, relativeWidgets={ + 'x': 'x', + 'y': 'y', } ) for widget in self._trackedWidgets.values(): diff --git a/src/components/text.py b/src/components/text.py index 1fe3467..0f87038 100644 --- a/src/components/text.py +++ b/src/components/text.py @@ -17,15 +17,12 @@ class Component(Component): def widget(self, *args): super().widget(*args) - height = int(self.settings.value('outputHeight')) - width = int(self.settings.value('outputWidth')) + # height = int(self.settings.value('outputHeight')) + # width = int(self.settings.value('outputWidth')) self.textColor = (255, 255, 255) self.title = 'Text' self.alignment = 1 - self.fontSize = height / 13.5 - fm = QtGui.QFontMetrics(self.titleFont) - self.xPosition = width / 2 - fm.width(self.title)/2 - self.yPosition = height / 2 * 1.036 + self.fontSize = self.height / 13.5 self.page.comboBox_textAlign.addItem("Left") self.page.comboBox_textAlign.addItem("Middle") @@ -35,8 +32,11 @@ class Component(Component): self.page.lineEdit_title.setText(self.title) self.page.comboBox_textAlign.setCurrentIndex(int(self.alignment)) self.page.spinBox_fontSize.setValue(int(self.fontSize)) - self.page.spinBox_xTextAlign.setValue(int(self.xPosition)) - self.page.spinBox_yTextAlign.setValue(int(self.yPosition)) + + fm = QtGui.QFontMetrics(self.titleFont) + self.page.spinBox_xTextAlign.setValue( + self.width / 2 - fm.width(self.title)/2) + self.page.spinBox_yTextAlign.setValue(self.height / 2 * 1.036) self.page.fontComboBox_titleFont.currentFontChanged.connect( self.update @@ -50,6 +50,9 @@ class Component(Component): 'yPosition': self.page.spinBox_yTextAlign, }, colorWidgets={ 'textColor': self.page.pushButton_textColor, + }, relativeWidgets={ + 'xPosition': 'x', + 'yPosition': 'y', }) def update(self): diff --git a/src/components/video.py b/src/components/video.py index 6cd16e5..3569d17 100644 --- a/src/components/video.py +++ b/src/components/video.py @@ -38,6 +38,9 @@ class Component(Component): 'loopVideo': 'loop', 'xPosition': 'x', 'yPosition': 'y', + }, relativeWidgets={ + 'xPosition': 'x', + 'yPosition': 'y', } ) diff --git a/src/components/waveform.py b/src/components/waveform.py index 9c3cf86..a25116b 100644 --- a/src/components/waveform.py +++ b/src/components/waveform.py @@ -41,6 +41,9 @@ class Component(Component): 'mono': self.page.checkBox_mono, }, colorWidgets={ 'color': self.page.pushButton_color, + }, relativeWidgets={ + 'x': 'x', + 'y': 'y', } ) diff --git a/src/mainwindow.py b/src/mainwindow.py index d9e95e2..1c8806d 100644 --- a/src/mainwindow.py +++ b/src/mainwindow.py @@ -644,9 +644,12 @@ class MainWindow(QtWidgets.QMainWindow): 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]) - self.drawPreview() + 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''' -- cgit v1.2.3 From 6611492b30a7daf7bdbe77f6e12f9d322bdd90c1 Mon Sep 17 00:00:00 2001 From: tassaron Date: Thu, 3 Aug 2017 00:44:46 -0400 Subject: relative gradients & last good frame used for preview errors --- src/components/color.py | 5 +++++ src/components/spectrum.py | 15 ++++++++++----- src/components/text.py | 2 -- src/components/video.py | 15 ++++----------- src/components/waveform.py | 15 ++++++++++----- 5 files changed, 29 insertions(+), 23 deletions(-) (limited to 'src/components/waveform.py') diff --git a/src/components/color.py b/src/components/color.py index 2b100d9..f5d618e 100644 --- a/src/components/color.py +++ b/src/components/color.py @@ -65,6 +65,11 @@ class Component(Component): 'y': 'y', 'sizeWidth': 'x', 'sizeHeight': 'y', + 'RG_start': 'x', + 'LG_start': 'x', + 'RG_end': 'x', + 'LG_end': 'x', + 'RG_centre': 'x', }, ) diff --git a/src/components/spectrum.py b/src/components/spectrum.py index 2cc641d..9a0c59a 100644 --- a/src/components/spectrum.py +++ b/src/components/spectrum.py @@ -20,6 +20,7 @@ class Component(Component): def widget(self, *args): self.previewFrame = None super().widget(*args) + self._image = BlankFrame(self.width, self.height) self.chunkSize = 4 * self.width * self.height self.changedOptions = True @@ -268,11 +269,15 @@ class Component(Component): return changed def finalizeFrame(self, imageData): - image = Image.frombytes( - 'RGBA', - scale(self.scale, self.width, self.height, int), - imageData - ) + try: + image = Image.frombytes( + 'RGBA', + scale(self.scale, self.width, self.height, int), + imageData + ) + self._image = image + except ValueError: + image = self._image if self.scale != 100 \ or self.x != 0 or self.y != 0: frame = BlankFrame(self.width, self.height) diff --git a/src/components/text.py b/src/components/text.py index be4de4a..2a5d433 100644 --- a/src/components/text.py +++ b/src/components/text.py @@ -17,8 +17,6 @@ class Component(Component): def widget(self, *args): super().widget(*args) - # height = int(self.settings.value('outputHeight')) - # width = int(self.settings.value('outputWidth')) self.textColor = (255, 255, 255) self.title = 'Text' self.alignment = 1 diff --git a/src/components/video.py b/src/components/video.py index 3569d17..2cd67c6 100644 --- a/src/components/video.py +++ b/src/components/video.py @@ -16,12 +16,12 @@ class Component(Component): def widget(self, *args): self.videoPath = '' - self.badVideo = False self.badAudio = False self.x = 0 self.y = 0 self.loopVideo = False super().widget(*args) + self._image = BlankFrame(self.width, self.height) self.page.pushButton_video.clicked.connect(self.pickVideo) self.trackWidgets( { @@ -70,8 +70,6 @@ class Component(Component): if not self.videoPath: self.lockError("There is no video selected.") - elif self.badVideo: - self.lockError("Could not identify an audio stream in this video.") elif not os.path.exists(self.videoPath): self.lockError("The video selected does not exist!") elif os.path.realpath(self.videoPath) == os.path.realpath(outputFile): @@ -199,14 +197,10 @@ class Component(Component): 'RGBA', scale(self.scale, self.width, self.height, int), imageData) - + self._image = image except ValueError: - print( - '### BAD VIDEO SELECTED ###\n' - 'Video will not export with these settings' - ) - self.badVideo = True - return BlankFrame(self.width, self.height) + # use last good frame + image = self._image if self.scale != 100 \ or self.xPosition != 0 or self.yPosition != 0: @@ -214,5 +208,4 @@ class Component(Component): frame.paste(image, box=(self.xPosition, self.yPosition)) else: frame = image - self.badVideo = False return frame diff --git a/src/components/waveform.py b/src/components/waveform.py index a25116b..526e6fb 100644 --- a/src/components/waveform.py +++ b/src/components/waveform.py @@ -19,6 +19,7 @@ class Component(Component): def widget(self, *args): super().widget(*args) + self._image = BlankFrame(self.width, self.height) self.page.lineEdit_color.setText('255,255,255') @@ -178,11 +179,15 @@ class Component(Component): self.chunkSize = 4 * width * height def finalizeFrame(self, imageData): - image = Image.frombytes( - 'RGBA', - scale(self.scale, self.width, self.height, int), - imageData - ) + try: + image = Image.frombytes( + 'RGBA', + scale(self.scale, self.width, self.height, int), + imageData + ) + self._image = image + except ValueError: + image = self._image if self.scale != 100 \ or self.x != 0 or self.y != 0: frame = BlankFrame(self.width, self.height) -- cgit v1.2.3 From 219e846984bb10e9674432fa7aeac4157635c743 Mon Sep 17 00:00:00 2001 From: tassaron Date: Thu, 3 Aug 2017 12:16:57 -0400 Subject: relativeWidgets might as well be a list --- src/component.py | 5 +--- src/components/color.py | 63 +++++++++++++++++++++------------------------- src/components/image.py | 36 ++++++++++++-------------- src/components/original.py | 6 ++--- src/components/spectrum.py | 47 ++++++++++++++++------------------ src/components/text.py | 8 +++--- src/components/video.py | 37 +++++++++++++-------------- src/components/waveform.py | 35 ++++++++++++-------------- 8 files changed, 106 insertions(+), 131 deletions(-) (limited to 'src/components/waveform.py') diff --git a/src/component.py b/src/component.py index 5dfe2ab..c5bc44b 100644 --- a/src/component.py +++ b/src/component.py @@ -304,10 +304,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): elif attr in self._relativeWidgets: # Relative widgets: number scales to fit export resolution - if self._relativeWidgets[attr] == 'x': - dimension = self.width - else: - dimension = self.height + dimension = self.width try: oldUserValue = getattr(self, attr) except AttributeError: diff --git a/src/components/color.py b/src/components/color.py index f5d618e..5d1233e 100644 --- a/src/components/color.py +++ b/src/components/color.py @@ -37,41 +37,34 @@ class Component(Component): self.page.comboBox_fill.addItem(label) self.page.comboBox_fill.setCurrentIndex(0) - self.trackWidgets( - { - 'x': self.page.spinBox_x, - 'y': self.page.spinBox_y, - 'sizeWidth': self.page.spinBox_width, - 'sizeHeight': self.page.spinBox_height, - 'trans': self.page.checkBox_trans, - 'spread': self.page.comboBox_spread, - 'stretch': self.page.checkBox_stretch, - 'RG_start': self.page.spinBox_radialGradient_start, - 'LG_start': self.page.spinBox_linearGradient_start, - 'RG_end': self.page.spinBox_radialGradient_end, - 'LG_end': self.page.spinBox_linearGradient_end, - 'RG_centre': self.page.spinBox_radialGradient_spread, - 'fillType': self.page.comboBox_fill, - 'color1': self.page.lineEdit_color1, - 'color2': self.page.lineEdit_color2, - }, presetNames={ - 'sizeWidth': 'width', - 'sizeHeight': 'height', - }, colorWidgets={ - 'color1': self.page.pushButton_color1, - 'color2': self.page.pushButton_color2, - }, relativeWidgets={ - 'x': 'x', - 'y': 'y', - 'sizeWidth': 'x', - 'sizeHeight': 'y', - 'RG_start': 'x', - 'LG_start': 'x', - 'RG_end': 'x', - 'LG_end': 'x', - 'RG_centre': 'x', - }, - ) + self.trackWidgets({ + 'x': self.page.spinBox_x, + 'y': self.page.spinBox_y, + 'sizeWidth': self.page.spinBox_width, + 'sizeHeight': self.page.spinBox_height, + 'trans': self.page.checkBox_trans, + 'spread': self.page.comboBox_spread, + 'stretch': self.page.checkBox_stretch, + 'RG_start': self.page.spinBox_radialGradient_start, + 'LG_start': self.page.spinBox_linearGradient_start, + 'RG_end': self.page.spinBox_radialGradient_end, + 'LG_end': self.page.spinBox_linearGradient_end, + 'RG_centre': self.page.spinBox_radialGradient_spread, + 'fillType': self.page.comboBox_fill, + 'color1': self.page.lineEdit_color1, + 'color2': self.page.lineEdit_color2, + }, presetNames={ + 'sizeWidth': 'width', + 'sizeHeight': 'height', + }, colorWidgets={ + 'color1': self.page.pushButton_color1, + 'color2': self.page.pushButton_color2, + }, relativeWidgets=[ + 'x', 'y', + 'sizeWidth', 'sizeHeight', + 'LG_start', 'LG_end', + 'RG_start', 'RG_end', 'RG_centre', + ]) def update(self): fillType = self.page.comboBox_fill.currentIndex() diff --git a/src/components/image.py b/src/components/image.py index 2ffa5a1..19c4796 100644 --- a/src/components/image.py +++ b/src/components/image.py @@ -13,26 +13,22 @@ class Component(Component): def widget(self, *args): super().widget(*args) self.page.pushButton_image.clicked.connect(self.pickImage) - self.trackWidgets( - { - 'imagePath': self.page.lineEdit_image, - 'scale': self.page.spinBox_scale, - 'rotate': self.page.spinBox_rotate, - 'color': self.page.spinBox_color, - 'xPosition': self.page.spinBox_x, - 'yPosition': self.page.spinBox_y, - 'stretched': self.page.checkBox_stretch, - 'mirror': self.page.checkBox_mirror, - }, - presetNames={ - 'imagePath': 'image', - 'xPosition': 'x', - 'yPosition': 'y', - }, relativeWidgets={ - 'xPosition': 'x', - 'yPosition': 'y', - }, - ) + self.trackWidgets({ + 'imagePath': self.page.lineEdit_image, + 'scale': self.page.spinBox_scale, + 'rotate': self.page.spinBox_rotate, + 'color': self.page.spinBox_color, + 'xPosition': self.page.spinBox_x, + 'yPosition': self.page.spinBox_y, + 'stretched': self.page.checkBox_stretch, + }, presetNames={ + 'mirror': self.page.checkBox_mirror, + 'imagePath': 'image', + 'xPosition': 'x', + 'yPosition': 'y', + }, relativeWidgets=[ + 'xPosition', 'yPosition', + ]) def previewRender(self): return self.drawFrame(self.width, self.height) diff --git a/src/components/original.py b/src/components/original.py index 67e3239..f886374 100644 --- a/src/components/original.py +++ b/src/components/original.py @@ -40,9 +40,9 @@ class Component(Component): 'y': self.page.spinBox_y, }, colorWidgets={ 'visColor': self.page.pushButton_visColor, - }, relativeWidgets={ - 'y': 'y', - }) + }, relativeWidgets=[ + 'y', + ]) def previewRender(self): spectrum = numpy.fromfunction( diff --git a/src/components/spectrum.py b/src/components/spectrum.py index 9a0c59a..666e20a 100644 --- a/src/components/spectrum.py +++ b/src/components/spectrum.py @@ -30,31 +30,28 @@ class Component(Component): self.update ) - self.trackWidgets( - { - 'filterType': self.page.comboBox_filterType, - 'window': self.page.comboBox_window, - 'mode': self.page.comboBox_mode, - 'amplitude': self.page.comboBox_amplitude0, - 'amplitude1': self.page.comboBox_amplitude1, - 'amplitude2': self.page.comboBox_amplitude2, - 'display': self.page.comboBox_display, - 'zoom': self.page.spinBox_zoom, - 'tc': self.page.spinBox_tc, - 'x': self.page.spinBox_x, - 'y': self.page.spinBox_y, - 'mirror': self.page.checkBox_mirror, - 'draw': self.page.checkBox_draw, - 'scale': self.page.spinBox_scale, - 'color': self.page.comboBox_color, - 'compress': self.page.checkBox_compress, - 'mono': self.page.checkBox_mono, - 'hue': self.page.spinBox_hue, - }, relativeWidgets={ - 'x': 'x', - 'y': 'y', - } - ) + self.trackWidgets({ + 'filterType': self.page.comboBox_filterType, + 'window': self.page.comboBox_window, + 'mode': self.page.comboBox_mode, + 'amplitude': self.page.comboBox_amplitude0, + 'amplitude1': self.page.comboBox_amplitude1, + 'amplitude2': self.page.comboBox_amplitude2, + 'display': self.page.comboBox_display, + 'zoom': self.page.spinBox_zoom, + 'tc': self.page.spinBox_tc, + 'x': self.page.spinBox_x, + 'y': self.page.spinBox_y, + 'mirror': self.page.checkBox_mirror, + 'draw': self.page.checkBox_draw, + 'scale': self.page.spinBox_scale, + 'color': self.page.comboBox_color, + 'compress': self.page.checkBox_compress, + 'mono': self.page.checkBox_mono, + 'hue': self.page.spinBox_hue, + }, relativeWidgets=[ + 'x', 'y', + ]) for widget in self._trackedWidgets.values(): connectWidget(widget, lambda: self.changed()) diff --git a/src/components/text.py b/src/components/text.py index 2a5d433..b7c244e 100644 --- a/src/components/text.py +++ b/src/components/text.py @@ -48,11 +48,9 @@ class Component(Component): 'yPosition': self.page.spinBox_yTextAlign, }, colorWidgets={ 'textColor': self.page.pushButton_textColor, - }, relativeWidgets={ - 'xPosition': 'x', - 'yPosition': 'y', - 'fontSize': 'y', - }) + }, relativeWidgets=[ + 'xPosition', 'yPosition', 'fontSize', + ]) def update(self): self.titleFont = self.page.fontComboBox_titleFont.currentFont() diff --git a/src/components/video.py b/src/components/video.py index 2cd67c6..b6bdd52 100644 --- a/src/components/video.py +++ b/src/components/video.py @@ -23,26 +23,23 @@ class Component(Component): super().widget(*args) self._image = BlankFrame(self.width, self.height) self.page.pushButton_video.clicked.connect(self.pickVideo) - self.trackWidgets( - { - 'videoPath': self.page.lineEdit_video, - 'loopVideo': self.page.checkBox_loop, - 'useAudio': self.page.checkBox_useAudio, - 'distort': self.page.checkBox_distort, - 'scale': self.page.spinBox_scale, - 'volume': self.page.spinBox_volume, - 'xPosition': self.page.spinBox_x, - 'yPosition': self.page.spinBox_y, - }, presetNames={ - 'videoPath': 'video', - 'loopVideo': 'loop', - 'xPosition': 'x', - 'yPosition': 'y', - }, relativeWidgets={ - 'xPosition': 'x', - 'yPosition': 'y', - } - ) + self.trackWidgets({ + 'videoPath': self.page.lineEdit_video, + 'loopVideo': self.page.checkBox_loop, + 'useAudio': self.page.checkBox_useAudio, + 'distort': self.page.checkBox_distort, + 'scale': self.page.spinBox_scale, + 'volume': self.page.spinBox_volume, + 'xPosition': self.page.spinBox_x, + 'yPosition': self.page.spinBox_y, + }, presetNames={ + 'videoPath': 'video', + 'loopVideo': 'loop', + 'xPosition': 'x', + 'yPosition': 'y', + }, relativeWidgets=[ + 'xPosition', 'yPosition', + ]) def update(self): if self.page.checkBox_useAudio.isChecked(): diff --git a/src/components/waveform.py b/src/components/waveform.py index 526e6fb..71cbcac 100644 --- a/src/components/waveform.py +++ b/src/components/waveform.py @@ -28,25 +28,22 @@ class Component(Component): self.update ) - self.trackWidgets( - { - 'color': self.page.lineEdit_color, - 'mode': self.page.comboBox_mode, - 'amplitude': self.page.comboBox_amplitude, - 'x': self.page.spinBox_x, - 'y': self.page.spinBox_y, - 'mirror': self.page.checkBox_mirror, - 'scale': self.page.spinBox_scale, - 'opacity': self.page.spinBox_opacity, - 'compress': self.page.checkBox_compress, - 'mono': self.page.checkBox_mono, - }, colorWidgets={ - 'color': self.page.pushButton_color, - }, relativeWidgets={ - 'x': 'x', - 'y': 'y', - } - ) + self.trackWidgets({ + 'color': self.page.lineEdit_color, + 'mode': self.page.comboBox_mode, + 'amplitude': self.page.comboBox_amplitude, + 'x': self.page.spinBox_x, + 'y': self.page.spinBox_y, + 'mirror': self.page.checkBox_mirror, + 'scale': self.page.spinBox_scale, + 'opacity': self.page.spinBox_opacity, + 'compress': self.page.checkBox_compress, + 'mono': self.page.checkBox_mono, + }, colorWidgets={ + 'color': self.page.pushButton_color, + }, relativeWidgets=[ + 'x', 'y', + ]) def previewRender(self): self.updateChunksize() -- cgit v1.2.3 From 1c4afc96d69789f16284c067ffd7098dc7b2ca70 Mon Sep 17 00:00:00 2001 From: tassaron Date: Thu, 10 Aug 2017 16:04:41 -0400 Subject: using the builtin logging module --- src/component.py | 14 ++++++--- src/components/spectrum.py | 16 ++++++---- src/components/video.py | 19 +++++++++--- src/components/waveform.py | 18 +++++++++--- src/core.py | 73 +++++++++++++++++++++++++++++++++++++++++----- src/main.py | 6 ++++ src/mainwindow.py | 57 +++++++++++++++++++++++++++--------- src/preview_thread.py | 9 ++++-- src/toolkit/ffmpeg.py | 19 +++++++----- src/toolkit/frame.py | 6 ++++ src/video_thread.py | 24 ++++++++++----- 11 files changed, 206 insertions(+), 55 deletions(-) (limited to 'src/components/waveform.py') diff --git a/src/component.py b/src/component.py index 5b6f9a7..a1e24db 100644 --- a/src/component.py +++ b/src/component.py @@ -8,6 +8,7 @@ import os import sys import math import time +import logging from toolkit.frame import BlankFrame from toolkit import ( @@ -15,6 +16,9 @@ from toolkit import ( ) +log = logging.getLogger('AVP.ComponentHandler') + + class ComponentMetaclass(type(QtCore.QObject)): ''' Checks the validity of each Component class and mutates some attrs. @@ -135,17 +139,17 @@ class ComponentMetaclass(type(QtCore.QObject)): # Turn version string into a number try: if 'version' not in attrs: - print( + log.error( 'No version attribute in %s. Defaulting to 1' % attrs['name']) attrs['version'] = 1 else: attrs['version'] = int(attrs['version'].split('.')[0]) except ValueError: - print('%s component has an invalid version string:\n%s' % ( + log.critical('%s component has an invalid version string:\n%s' % ( attrs['name'], str(attrs['version']))) except KeyError: - print('%s component has no version string.' % attrs['name']) + log.critical('%s component has no version string.' % attrs['name']) else: return super().__new__(cls, name, parents, attrs) quit(1) @@ -546,6 +550,8 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): and oldRelativeVal != newRelativeVal: # Float changed without pixel value changing, which # means the pixel value needs to be updated + log.debug('Updating %s #%s\'s relative widget: %s' % ( + self.name, self.compPos, attr)) self._trackedWidgets[attr].blockSignals(True) self.updateRelativeWidgetMaximum(attr) pixelVal = self.pixelValForAttr(attr, oldRelativeVal) @@ -576,7 +582,7 @@ class ComponentError(RuntimeError): msg = str(sys.exc_info()[1]) else: msg = 'Unknown error.' - print("##### ComponentError by %s's %s: %s" % ( + log.error("ComponentError by %s's %s: %s" % ( caller.name, name, msg)) # Don't create multiple windows for quickly repeated messages diff --git a/src/components/spectrum.py b/src/components/spectrum.py index 666e20a..32763c0 100644 --- a/src/components/spectrum.py +++ b/src/components/spectrum.py @@ -4,6 +4,7 @@ import os import math import subprocess import time +import logging from component import Component from toolkit.frame import BlankFrame, scale @@ -13,6 +14,9 @@ from toolkit.ffmpeg import ( ) +log = logging.getLogger('AVP.Components.Spectrum') + + class Component(Component): name = 'Spectrum' version = '1.0.0' @@ -68,6 +72,7 @@ class Component(Component): if not changedSize \ and not self.changedOptions \ and self.previewFrame is not None: + log.debug('Comp #%s is reusing old preview frame' % self.compPos) return self.previewFrame frame = self.getPreviewFrame() @@ -131,13 +136,14 @@ class Component(Component): '-frames:v', '1', ]) logFilename = os.path.join( - self.core.dataDir, 'preview_%s.log' % str(self.compPos)) - with open(logFilename, 'w') as log: - log.write(" ".join(command) + '\n\n') - with open(logFilename, 'a') as log: + self.core.logDir, 'preview_%s.log' % str(self.compPos)) + log.debug('Creating ffmpeg process (log at %s)' % logFilename) + with open(logFilename, 'w') as logf: + logf.write(" ".join(command) + '\n\n') + with open(logFilename, 'a') as logf: pipe = openPipe( command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, - stderr=log, bufsize=10**8 + stderr=logf, bufsize=10**8 ) byteFrame = pipe.stdout.read(self.chunkSize) closePipe(pipe) diff --git a/src/components/video.py b/src/components/video.py index b6bdd52..a189f60 100644 --- a/src/components/video.py +++ b/src/components/video.py @@ -3,6 +3,7 @@ from PyQt5 import QtGui, QtCore, QtWidgets import os import math import subprocess +import logging from component import Component from toolkit.frame import BlankFrame, scale @@ -10,6 +11,9 @@ from toolkit.ffmpeg import openPipe, closePipe, testAudioStream, FfmpegVideo from toolkit import checkOutput +log = logging.getLogger('AVP.Components.Video') + + class Component(Component): name = 'Video' version = '1.0.0' @@ -134,10 +138,17 @@ class Component(Component): '-ss', '90', '-frames:v', '1', ]) - pipe = openPipe( - command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL, bufsize=10**8 - ) + + logFilename = os.path.join( + self.core.logDir, 'preview_%s.log' % str(self.compPos)) + log.debug('Creating ffmpeg process (log at %s)' % logFilename) + with open(logFilename, 'w') as logf: + logf.write(" ".join(command) + '\n\n') + with open(logFilename, 'a') as logf: + pipe = openPipe( + command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, + stderr=logf, bufsize=10**8 + ) byteFrame = pipe.stdout.read(self.chunkSize) closePipe(pipe) diff --git a/src/components/waveform.py b/src/components/waveform.py index 71cbcac..1517be2 100644 --- a/src/components/waveform.py +++ b/src/components/waveform.py @@ -4,6 +4,7 @@ from PyQt5.QtGui import QColor import os import math import subprocess +import logging from component import Component from toolkit.frame import BlankFrame, scale @@ -13,6 +14,9 @@ from toolkit.ffmpeg import ( ) +log = logging.getLogger('AVP.Components.Waveform') + + class Component(Component): name = 'Waveform' version = '1.0.0' @@ -106,10 +110,16 @@ class Component(Component): '-codec:v', 'rawvideo', '-', '-frames:v', '1', ]) - pipe = openPipe( - command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL, bufsize=10**8 - ) + logFilename = os.path.join( + self.core.logDir, 'preview_%s.log' % str(self.compPos)) + log.debug('Creating ffmpeg process (log at %s)' % logFilename) + with open(logFilename, 'w') as logf: + logf.write(" ".join(command) + '\n\n') + with open(logFilename, 'a') as logf: + pipe = openPipe( + command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, + stderr=logf, bufsize=10**8 + ) byteFrame = pipe.stdout.read(self.chunkSize) closePipe(pipe) diff --git a/src/core.py b/src/core.py index 61905eb..4023542 100644 --- a/src/core.py +++ b/src/core.py @@ -7,11 +7,17 @@ import sys import os import json from importlib import import_module +import logging import toolkit import video_thread +log = logging.getLogger('AVP.Core') +STDOUT_LOGLVL = logging.WARNING +FILE_LOGLVL = logging.DEBUG + + class Core: ''' MainWindow and Command module both use an instance of this class @@ -35,6 +41,7 @@ class Core: continue elif ext == '.py': yield name + log.debug('Importing component modules') self.modules = [ import_module('components.%s' % name) for name in findComponents() @@ -67,7 +74,7 @@ class Core: compPos = len(self.selectedComponents) if len(self.selectedComponents) > 50: return None - + log.debug('Inserting Component from module #%s' % moduleIndex) component = self.modules[moduleIndex].Component( moduleIndex, compPos, self ) @@ -104,7 +111,7 @@ class Core: self.componentListChanged() def updateComponent(self, i): - # print('updating %s' % self.selectedComponents[i]) + log.debug('Updating %s #%s' % (self.selectedComponents[i], str(i))) self.selectedComponents[i].update() def moduleIndexFor(self, compName): @@ -125,12 +132,17 @@ class Core: if not saveValueStore: return False try: - self.selectedComponents[compIndex].loadPreset( + comp = self.selectedComponents[compIndex] + comp.loadPreset( saveValueStore, presetName ) except KeyError as e: - print('preset missing value: %s' % e) + log.warning( + '%s #%s\'s preset is missing value: %s' % ( + comp.name, str(compIndex), str(e) + ) + ) self.savedPresets[presetName] = dict(saveValueStore) return True @@ -206,7 +218,7 @@ class Core: preset['preset'] ) except KeyError as e: - print('%s missing value: %s' % ( + log.warning('%s missing value: %s' % ( self.selectedComponents[i], e) ) @@ -224,7 +236,7 @@ class Core: typ, value, tb = data if typ.__name__ == 'KeyError': # probably just an old version, still loadable - print('file missing value: %s' % value) + log.warning('Project file missing value: %s' % value) return if hasattr(loader, 'createNewProject'): loader.createNewProject(prompt=False) @@ -244,6 +256,7 @@ class Core: Returns dictionary with section names as the keys, each one contains a list of tuples: (compName, version, compPresetDict) ''' + log.debug('Parsing av file: %s' % filepath) validSections = ( 'Components', 'Settings', @@ -362,6 +375,7 @@ class Core: def createProjectFile(self, filepath, window=None): '''Create a project file (.avp) using the current program state''' + log.info('Creating %s' % filepath) settingsKeys = [ 'componentDir', 'inputDir', @@ -374,9 +388,8 @@ class Core: filepath += '.avp' if os.path.exists(filepath): os.remove(filepath) - with open(filepath, 'w') as f: - print('creating %s' % filepath) + with open(filepath, 'w') as f: f.write('[Components]\n') for comp in self.selectedComponents: saveValueStore = comp.savePreset() @@ -443,6 +456,7 @@ class Core: 'settings': QtCore.QSettings( os.path.join(dataDir, 'settings.ini'), QtCore.QSettings.IniFormat), + 'logDir': os.path.join(dataDir, 'log'), 'presetDir': os.path.join(dataDir, 'presets'), 'componentsPath': os.path.join(wd, 'components'), 'encoderOptions': encoderOptions, @@ -489,6 +503,13 @@ class Core: setattr(cls, classvar, val) cls.loadDefaultSettings() + if not os.path.exists(cls.dataDir): + os.makedirs(cls.dataDir) + for neededDirectory in ( + cls.presetDir, cls.logDir, cls.settings.value("projectDir")): + if not os.path.exists(neededDirectory): + os.mkdir(neededDirectory) + cls.makeLogger() @classmethod def loadDefaultSettings(cls): @@ -522,6 +543,42 @@ class Core: if val in ('true', 'false'): cls.settings.setValue(key, True if val == 'true' else False) + @staticmethod + def makeLogger(): + logFilename = os.path.join(Core.logDir, 'avp_debug.log') + libLogFilename = os.path.join(Core.logDir, 'global_debug.log') + # delete old logs + for log in (logFilename, libLogFilename): + if os.path.exists(log): + os.remove(log) + + # create file handlers to capture every log message somewhere + logFile = logging.FileHandler(logFilename) + logFile.setLevel(FILE_LOGLVL) + libLogFile = logging.FileHandler(libLogFilename) + libLogFile.setLevel(FILE_LOGLVL) + + # send some critical log messages to stdout as well + logStream = logging.StreamHandler() + logStream.setLevel(STDOUT_LOGLVL) + + # create formatters and put everything together + fileFormatter = logging.Formatter( + '[%(asctime)s] <%(name)s> %(levelname)s: %(message)s' + ) + streamFormatter = logging.Formatter( + '<%(name)s> %(message)s' + ) + logFile.setFormatter(fileFormatter) + libLogFile.setFormatter(fileFormatter) + logStream.setFormatter(streamFormatter) + log = logging.getLogger('AVP') + log.setLevel(FILE_LOGLVL) + log.addHandler(logFile) + log.addHandler(logStream) + libLog = logging.getLogger() + libLog.setLevel(FILE_LOGLVL) + libLog.addHandler(libLogFile) # always store settings in class variables even if a Core object is not created Core.storeSettings() diff --git a/src/main.py b/src/main.py index 421a09f..3a6fbe7 100644 --- a/src/main.py +++ b/src/main.py @@ -1,10 +1,14 @@ from PyQt5 import uic, QtWidgets import sys import os +import logging from __init__ import wd +log = logging.getLogger('AVP.Entrypoint') + + def main(): app = QtWidgets.QApplication(sys.argv) app.setApplicationName("audio-visualizer") @@ -28,6 +32,7 @@ def main(): from command import Command main = Command() + log.debug("Finished creating command object") elif mode == 'GUI': from mainwindow import MainWindow @@ -48,6 +53,7 @@ def main(): # window.verticalLayout_2.setContentsMargins(0, topMargin, 0, 0) main = MainWindow(window, proj) + log.debug("Finished creating main window") window.raise_() signal.signal(signal.SIGINT, main.cleanUp) diff --git a/src/mainwindow.py b/src/mainwindow.py index 789a6e7..114015c 100644 --- a/src/mainwindow.py +++ b/src/mainwindow.py @@ -13,6 +13,7 @@ import os import signal import filecmp import time +import logging from core import Core import preview_thread @@ -20,11 +21,15 @@ from presetmanager import PresetManager from toolkit import disableWhenEncoding, disableWhenOpeningProject, checkOutput +log = logging.getLogger('AVP.MainWindow') + + class PreviewWindow(QtWidgets.QLabel): ''' Paints the preview QLabel and maintains the aspect ratio when the window is resized. ''' + log = logging.getLogger('AVP.MainWindow.Preview') def __init__(self, parent, img): super(PreviewWindow, self).__init__() @@ -58,11 +63,15 @@ class PreviewWindow(QtWidgets.QLabel): if i >= 0: component = self.parent.core.selectedComponents[i] if not hasattr(component, 'previewClickEvent'): + self.log.info('Ignored click event') return pos = (event.x(), event.y()) size = (self.width(), self.height()) + butt = event.button() + self.log.info('Click event for #%s: %s button %s' % ( + i, pos, butt)) component.previewClickEvent( - pos, size, event.button() + pos, size, butt ) self.parent.core.updateComponent(i) @@ -91,9 +100,10 @@ class MainWindow(QtWidgets.QMainWindow): def __init__(self, window, project): QtWidgets.QMainWindow.__init__(self) - # print('main thread id: {}'.format(QtCore.QThread.currentThreadId())) self.window = window self.core = Core() + log.debug( + 'Main thread id: {}'.format(QtCore.QThread.currentThreadId())) # widgets of component settings self.pages = [] @@ -103,27 +113,23 @@ class MainWindow(QtWidgets.QMainWindow): self.autosaveCooldown = 0.2 self.encoding = False - # Create data directory, load/create settings + # Find settings created by Core object 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 + log.debug('Creating preview window') self.previewWindow = PreviewWindow(self, os.path.join( Core.wd, "background.png")) window.verticalLayout_previewWrapper.addWidget(self.previewWindow) + log.debug('Starting preview thread') self.previewQueue = Queue() self.previewThread = QtCore.QThread(self) self.previewWorker = preview_thread.Worker(self, self.previewQueue) @@ -132,6 +138,7 @@ class MainWindow(QtWidgets.QMainWindow): self.previewWorker.imageCreated.connect(self.showPreviewImage) self.previewThread.start() + log.debug('Starting preview timer') self.timer = QtCore.QTimer(self) self.timer.timeout.connect(self.processTask.emit) self.timer.start(500) @@ -141,6 +148,8 @@ class MainWindow(QtWidgets.QMainWindow): componentList = self.window.listWidget_componentList if sys.platform == 'darwin': + log.debug( + 'Darwin detected: showing progress label below progress bar') window.progressBar_createVideo.setTextVisible(False) else: window.progressLabel.setHidden(True) @@ -276,6 +285,7 @@ class MainWindow(QtWidgets.QMainWindow): ) self.updateWindowTitle() + log.debug('Showing main window') window.show() if project and project != self.autosavePath: @@ -398,6 +408,7 @@ class MainWindow(QtWidgets.QMainWindow): @QtCore.pyqtSlot() def cleanUp(self, *args): + log.info('Ending the preview thread') self.timer.stop() self.previewThread.quit() self.previewThread.wait() @@ -414,11 +425,12 @@ class MainWindow(QtWidgets.QMainWindow): appName += '*' except AttributeError: pass + log.debug('Setting window title to %s' % appName) self.window.setWindowTitle(appName) @QtCore.pyqtSlot(int, dict) def updateComponentTitle(self, pos, presetStore=False): - if type(presetStore) == dict: + if type(presetStore) is dict: name = presetStore['preset'] if name is None or name not in self.core.savedPresets: modified = False @@ -428,11 +440,20 @@ class MainWindow(QtWidgets.QMainWindow): modified = bool(presetStore) if pos < 0: pos = len(self.core.selectedComponents)-1 - title = str(self.core.selectedComponents[pos]) + name = str(self.core.selectedComponents[pos]) + title = str(name) if self.core.selectedComponents[pos].currentPreset: title += ' - %s' % self.core.selectedComponents[pos].currentPreset if modified: title += '*' + if type(presetStore) is bool: + log.debug('Forcing %s #%s\'s modified status to %s: %s' % ( + name, pos, modified, title + )) + else: + log.debug('Setting %s #%s\'s title: %s' % ( + name, pos, title + )) self.window.listWidget_componentList.item(pos).setText(title) def updateCodecs(self): @@ -493,6 +514,8 @@ class MainWindow(QtWidgets.QMainWindow): elif force or timeDiff >= self.autosaveCooldown * 5: self.autosaveCooldown = 0.2 self.autosaveTimes.insert(0, self.lastAutosave) + else: + log.debug('Autosave rejected by cooldown') def autosaveExists(self, identical=True): '''Determines if creating the autosave should be blocked.''' @@ -500,9 +523,14 @@ class MainWindow(QtWidgets.QMainWindow): if self.currentProject and os.path.exists(self.autosavePath) \ and filecmp.cmp( self.autosavePath, self.currentProject) == identical: + log.debug( + 'Autosave found %s to be identical' % \ + 'not' if not identical else '' + ) return True except FileNotFoundError: - print('project file couldn\'t be located:', self.currentProject) + log.error( + 'Project file couldn\'t be located:', self.currentProject) return identical return False @@ -543,7 +571,7 @@ class MainWindow(QtWidgets.QMainWindow): self.window.lineEdit_outputFile.setText(fileName) def stopVideo(self): - print('stop') + log.info('Export cancelled') self.videoWorker.cancel() self.canceled = True @@ -773,6 +801,7 @@ class MainWindow(QtWidgets.QMainWindow): mousePos = -1 else: mousePos = mousePos.index(True) + log.debug('Click component list row %s' % mousePos) return mousePos @disableWhenEncoding diff --git a/src/preview_thread.py b/src/preview_thread.py index bb22f0c..9615884 100644 --- a/src/preview_thread.py +++ b/src/preview_thread.py @@ -8,11 +8,15 @@ from PIL import Image from PIL.ImageQt import ImageQt from queue import Queue, Empty import os +import logging from toolkit.frame import Checkerboard from toolkit import disableWhenOpeningProject +log = logging.getLogger("AVP.PreviewThread") + + class Worker(QtCore.QObject): imageCreated = pyqtSignal(QtGui.QImage) @@ -55,7 +59,7 @@ class Worker(QtCore.QObject): self.background = Checkerboard(width, height) frame = self.background.copy() - + log.debug('Creating new preview frame') components = nextPreviewInformation["components"] for component in reversed(components): try: @@ -73,10 +77,11 @@ class Worker(QtCore.QObject): newFrame.width, newFrame.height, width, height ) + log.critical(errMsg) self.error.emit(errMsg) break except RuntimeError as e: - print(e) + log.error(str(e)) else: self.frame = ImageQt(frame) self.imageCreated.emit(QtGui.QImage(self.frame)) diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py index 3421049..6ab445c 100644 --- a/src/toolkit/ffmpeg.py +++ b/src/toolkit/ffmpeg.py @@ -8,12 +8,16 @@ import subprocess import threading import signal from queue import PriorityQueue +import logging import core from toolkit.common import checkOutput, pipeWrapper from component import ComponentError +log = logging.getLogger('AVP.Toolkit.Ffmpeg') + + class FfmpegVideo: '''Opens a pipe to ffmpeg and stores a buffer of raw video frames.''' @@ -88,13 +92,14 @@ class FfmpegVideo: def fillBuffer(self): logFilename = os.path.join( - core.Core.dataDir, 'extra_%s.log' % str(self.component.compPos)) - with open(logFilename, 'w') as log: - log.write(" ".join(self.command) + '\n\n') - with open(logFilename, 'a') as log: + core.Core.logDir, 'render_%s.log' % str(self.component.compPos)) + log.debug('Creating ffmpeg process (log at %s)' % logFilename) + with open(logFilename, 'w') as logf: + logf.write(" ".join(self.command) + '\n\n') + with open(logFilename, 'a') as logf: self.pipe = openPipe( self.command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, - stderr=log, bufsize=10**8 + stderr=logf, bufsize=10**8 ) while True: if self.parent.canceled: @@ -375,7 +380,7 @@ def getAudioDuration(filename): try: info = fileInfo.decode("utf-8").split('\n') except UnicodeDecodeError as e: - print('Unicode error:', str(e)) + log.error('Unicode error:', str(e)) return False for line in info: @@ -398,7 +403,7 @@ def readAudioFile(filename, videoWorker): ''' duration = getAudioDuration(filename) if not duration: - print('Audio file doesn\'t exist or unreadable.') + log.error('Audio file doesn\'t exist or unreadable.') return command = [ diff --git a/src/toolkit/frame.py b/src/toolkit/frame.py index 7e83d58..02f9229 100644 --- a/src/toolkit/frame.py +++ b/src/toolkit/frame.py @@ -7,10 +7,14 @@ from PIL.ImageQt import ImageQt import sys import os import math +import logging import core +log = logging.getLogger('AVP.Toolkit.Frame') + + class FramePainter(QtGui.QPainter): ''' A QPainter for a blank frame, which can be converted into a @@ -79,6 +83,7 @@ def FloodFrame(width, height, RgbaTuple): @defaultSize def BlankFrame(width, height): '''The base frame used by each component to start drawing.''' + log.debug('Creating new %s*%s blank frame' % (width, height)) return FloodFrame(width, height, (0, 0, 0, 0)) @@ -88,6 +93,7 @@ def Checkerboard(width, height): A checkerboard to represent transparency to the user. TODO: Would be cool to generate this image with numpy instead. ''' + log.debug('Creating new %s*%s checkerboard' % (width, height)) image = FloodFrame(1920, 1080, (0, 0, 0, 0)) image.paste(Image.open( os.path.join(core.Core.wd, "background.png")), diff --git a/src/video_thread.py b/src/video_thread.py index 5963def..e7e4136 100644 --- a/src/video_thread.py +++ b/src/video_thread.py @@ -17,6 +17,7 @@ from queue import Queue, PriorityQueue from threading import Thread, Event import time import signal +import logging from component import ComponentError from toolkit.frame import Checkerboard @@ -26,6 +27,9 @@ from toolkit.ffmpeg import ( ) +log = logging.getLogger("AVP.VideoThread") + + class Worker(QtCore.QObject): imageCreated = pyqtSignal(['QImage']) @@ -92,7 +96,7 @@ class Worker(QtCore.QObject): by a renderNode later. All indices are multiples of self.sampleSize sampleSize * frameNo = audioI, AKA audio data starting at frameNo ''' - print('Dispatching Frames for Compositing...') + log.debug('Dispatching Frames for Compositing...') for audioI in range(0, len(self.completeAudioArray), self.sampleSize): self.compositeQueue.put(audioI) @@ -156,10 +160,12 @@ class Worker(QtCore.QObject): self.progressBarUpdate.emit(0) self.progressBarSetText.emit("Starting components...") canceledByComponent = False - print('Loaded Components:', ", ".join([ + initText = ", ".join([ "%s) %s" % (num, str(component)) for num, component in enumerate(reversed(self.components)) - ])) + ]) + print('Loaded Components:', initText) + log.info('Calling preFrameRender for %s' % initText) self.staticComponents = {} for compNo, comp in enumerate(reversed(self.components)): try: @@ -191,6 +197,7 @@ class Worker(QtCore.QObject): compError[0] ) ) + log.critical(errMsg) comp._error.emit(errMsg, compError[1]) break if 'static' in compProps: @@ -199,7 +206,7 @@ class Worker(QtCore.QObject): if self.canceled: if canceledByComponent: - print('Export cancelled by component #%s (%s): %s' % ( + log.critical('Export cancelled by component #%s (%s): %s' % ( compNo, comp.name, 'No message.' if comp.error() is None else ( @@ -224,8 +231,11 @@ class Worker(QtCore.QObject): ffmpegCommand = createFfmpegCommand( self.inputFile, self.outputFile, self.components, duration ) - print('###### FFMPEG COMMAND ######\n%s' % " ".join(ffmpegCommand)) + cmd = " ".join(ffmpegCommand) + print('###### FFMPEG COMMAND ######\n%s' % cmd) print('############################') + log.info('Opening pipe to ffmpeg') + log.info(cmd) self.out_pipe = openPipe( ffmpegCommand, stdin=sp.PIPE, stdout=sys.stdout, stderr=sys.stdout ) @@ -298,9 +308,9 @@ class Worker(QtCore.QObject): try: self.out_pipe.stdin.close() except BrokenPipeError: - print('Broken pipe to ffmpeg!') + log.error('Broken pipe to ffmpeg!') if self.out_pipe.stderr is not None: - print(self.out_pipe.stderr.read()) + log.error(self.out_pipe.stderr.read()) self.out_pipe.stderr.close() self.error = True self.out_pipe.wait() -- cgit v1.2.3 From ea1a422cc52bc972574070fbe784a35876ff8baa Mon Sep 17 00:00:00 2001 From: tassaron Date: Mon, 14 Aug 2017 14:28:30 -0400 Subject: better aevalsrc inputs for spectrum previews --- src/components/spectrum.py | 32 ++++++++++++++++++++++++++------ src/components/waveform.py | 25 +++++++++++++++---------- src/toolkit/ffmpeg.py | 19 ++++++++++++++----- test.wav | Bin 0 -> 14348366 bytes 4 files changed, 55 insertions(+), 21 deletions(-) create mode 100644 test.wav (limited to 'src/components/waveform.py') diff --git a/src/components/spectrum.py b/src/components/spectrum.py index 246b839..89130a2 100644 --- a/src/components/spectrum.py +++ b/src/components/spectrum.py @@ -19,7 +19,7 @@ log = logging.getLogger('AVP.Components.Spectrum') class Component(Component): name = 'Spectrum' - version = '1.0.0' + version = '1.0.1' def widget(self, *args): self.previewFrame = None @@ -65,8 +65,17 @@ class Component(Component): self.changedOptions = True def update(self): - self.page.stackedWidget.setCurrentIndex( - self.page.comboBox_filterType.currentIndex()) + filterType = self.page.comboBox_filterType.currentIndex() + self.page.stackedWidget.setCurrentIndex(filterType) + if filterType == 3: + self.page.spinBox_hue.setEnabled(False) + else: + self.page.spinBox_hue.setEnabled(True) + if filterType == 2 or filterType == 4: + self.page.checkBox_mono.setEnabled(False) + else: + self.page.checkBox_mono.setEnabled(True) + super().update() def previewRender(self): @@ -81,6 +90,8 @@ class Component(Component): frame = self.getPreviewFrame() self.changedOptions = False if not frame: + log.warning( + 'Spectrum #%s failed to create a preview frame' % self.compPos) self.previewFrame = None return BlankFrame(self.width, self.height) else: @@ -244,13 +255,21 @@ class Component(Component): ) ) + if self.filterType < 2: + exampleSnd = exampleSound('freq') + elif self.filterType == 2 or self.filterType == 4: + exampleSnd = exampleSound('stereo') + elif self.filterType == 3: + exampleSnd = exampleSound('white') + return [ '-filter_complex', '%s%s%s%s [v1]; ' '[v1] %s%s%s%s%s [v]' % ( - exampleSound() if preview and genericPreview else '[0:a] ', + exampleSnd if preview and genericPreview else '[0:a] ', 'compand=gain=4,' if self.compress else '', - 'aformat=channel_layouts=mono,' if self.mono else '', + 'aformat=channel_layouts=mono,' + if self.mono and self.filterType not in (2, 4) else '', filter_, 'hflip, ' if self.mirror else '', 'trim=start=%s:end=%s, ' % ( @@ -259,7 +278,8 @@ class Component(Component): ) if preview else '', 'scale=%sx%s' % scale( self.scale, self.width, self.height, str), - ', hue=h=%s:s=10' % str(self.hue) if self.hue > 0 else '', + ', hue=h=%s:s=10' % str(self.hue) + if self.hue > 0 and self.filterType != 3 else '', ', convolution=-2 -1 0 -1 1 1 0 1 2:-2 -1 0 -1 1 1 0 1 2:-2 ' '-1 0 -1 1 1 0 1 2:-2 -1 0 -1 1 1 0 1 2' if self.filterType == 3 else '' diff --git a/src/components/waveform.py b/src/components/waveform.py index 1517be2..0743e55 100644 --- a/src/components/waveform.py +++ b/src/components/waveform.py @@ -140,13 +140,16 @@ class Component(Component): opacity = "{0:.1f}".format(self.opacity / 100) genericPreview = self.settings.value("pref_genericPreview") if self.mode < 3: - filter_ = 'showwaves=r=%s:s=%sx%s:mode=%s:colors=%s@%s:scale=%s' % ( - self.settings.value("outputFrameRate"), - self.settings.value("outputWidth"), - self.settings.value("outputHeight"), - self.page.comboBox_mode.currentText().lower() - if self.mode != 3 else 'p2p', - hexcolor, opacity, amplitude, + filter_ = ( + 'showwaves=' + 'r=%s:s=%sx%s:mode=%s:colors=%s@%s:scale=%s' % ( + self.settings.value("outputFrameRate"), + self.settings.value("outputWidth"), + self.settings.value("outputHeight"), + self.page.comboBox_mode.currentText().lower() + if self.mode != 3 else 'p2p', + hexcolor, opacity, amplitude, + ) ) elif self.mode > 2: filter_ = ( @@ -160,18 +163,20 @@ class Component(Component): ) ) + baselineHeight = int(self.height * (4 / 1080)) return [ '-filter_complex', '%s%s%s' '%s%s%s [v1]; ' '[v1] scale=%s:%s%s [v]' % ( - exampleSound() if preview and genericPreview else '[0:a] ', + exampleSound('wave', extra='') + if preview and genericPreview else '[0:a] ', 'compand=gain=4,' if self.compress else '', 'aformat=channel_layouts=mono,' if self.mono and self.mode < 3 else '', filter_, - ', drawbox=x=(iw-w)/2:y=(ih-h)/2:w=iw:h=4:color=%s@%s' % ( - hexcolor, opacity + ', drawbox=x=(iw-w)/2:y=(ih-h)/2:w=iw:h=%s:color=%s@%s' % ( + baselineHeight, hexcolor, opacity, ) if self.mode < 2 else '', ', hflip' if self.mirror else'', w, h, diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py index afcb37c..8fe9148 100644 --- a/src/toolkit/ffmpeg.py +++ b/src/toolkit/ffmpeg.py @@ -457,8 +457,17 @@ def readAudioFile(filename, videoWorker): return (completeAudioArray, duration) -def exampleSound(): - return ( - 'aevalsrc=tan(random(1)*PI*t)*sin(random(0)*2*PI*t),' - 'apulsator=offset_l=0.5:offset_r=0.5,' - ) +def exampleSound( + style='white', extra='apulsator=offset_l=0.35:offset_r=0.67'): + '''Help generate an example sound for use in creating a preview''' + + if style == 'white': + src = '-2+random(0)' + elif style == 'freq': + src = 'sin(1000*t*PI*t)' + elif style == 'wave': + src = 'sin(random(0)*2*PI*t)*tan(random(0)*2*PI*t)' + elif style == 'stereo': + src = '0.1*sin(2*PI*(360-2.5/2)*t) : 0.1*sin(2*PI*(360+2.5/2)*t)' + + return "aevalsrc='%s', %s%s" % (src, extra, ', ' if extra else '') diff --git a/test.wav b/test.wav new file mode 100644 index 0000000..98afe5f Binary files /dev/null and b/test.wav differ -- cgit v1.2.3 From c07f2426ceeada205fdacbfba66329179a74a1dc Mon Sep 17 00:00:00 2001 From: tassaron Date: Sat, 19 Aug 2017 18:32:12 -0400 Subject: fixed issues with undoing relative widgets --- src/component.py | 198 +++++++++++++++++++++++++++++++++------------ src/components/color.py | 2 - src/components/image.py | 2 - src/components/life.py | 1 - src/components/sound.py | 1 - src/components/spectrum.py | 4 +- src/components/text.py | 1 - src/components/video.py | 2 - src/components/waveform.py | 2 +- src/core.py | 11 +-- src/gui/actions.py | 11 ++- src/gui/mainwindow.py | 4 +- src/gui/presetmanager.py | 4 + src/gui/preview_thread.py | 2 +- src/gui/preview_win.py | 2 +- src/main.py | 2 +- src/toolkit/common.py | 47 +++++++++-- 17 files changed, 215 insertions(+), 81 deletions(-) (limited to 'src/components/waveform.py') diff --git a/src/component.py b/src/component.py index 1fe9237..ba86422 100644 --- a/src/component.py +++ b/src/component.py @@ -9,6 +9,7 @@ import sys import math import time import logging +from copy import copy from toolkit.frame import BlankFrame from toolkit import ( @@ -113,14 +114,20 @@ class ComponentMetaclass(type(QtCore.QObject)): def presetWrapper(self, *args): with openingPreset(self): - return func(self, *args) + try: + return func(self, *args) + except Exception: + try: + raise ComponentError(self, 'preset loader') + except ComponentError: + return return presetWrapper def updateWrapper(func): ''' - For undoable updates triggered by the user, - call _userUpdate() after the subclass's update() method. - For non-user updates, call _autoUpdate() + Calls _preUpdate before every subclass update(). + Afterwards, for non-user updates, calls _autoUpdate(). + For undoable updates triggered by the user, calls _userUpdate() ''' class wrap: def __init__(self, comp, auto): @@ -128,24 +135,57 @@ class ComponentMetaclass(type(QtCore.QObject)): self.auto = auto def __enter__(self): - pass + self.comp._preUpdate() def __exit__(self, *args): if self.auto or self.comp.openingPreset \ or not hasattr(self.comp.parent, 'undoStack'): + log.verbose('Automatic update') self.comp._autoUpdate() else: + log.verbose('User update') self.comp._userUpdate() def updateWrapper(self, **kwargs): - auto = False - if 'auto' in kwargs: - auto = kwargs['auto'] - + auto = kwargs['auto'] if 'auto' in kwargs else False with wrap(self, auto): - return func(self) + try: + return func(self) + except Exception: + try: + raise ComponentError(self, 'update method') + except ComponentError: + return return updateWrapper + def widgetWrapper(func): + '''Connects all widgets to update method after the subclass's method''' + class wrap: + def __init__(self, comp): + self.comp = comp + + def __enter__(self): + pass + + def __exit__(self, *args): + for widgetList in self.comp._allWidgets.values(): + for widget in widgetList: + log.verbose('Connecting %s' % str( + widget.__class__.__name__)) + connectWidget(widget, self.comp.update) + + def widgetWrapper(self, *args, **kwargs): + auto = kwargs['auto'] if 'auto' in kwargs else False + with wrap(self): + try: + return func(self, *args, **kwargs) + except Exception: + try: + raise ComponentError(self, 'widget creation') + except ComponentError: + return + return widgetWrapper + def __new__(cls, name, parents, attrs): if 'ui' not in attrs: # Use module name as ui filename by default @@ -153,13 +193,12 @@ class ComponentMetaclass(type(QtCore.QObject)): attrs['__module__'].split('.')[-1] )[0] - # if parents[0] == QtCore.QObject: else: decorate = ( 'names', # Class methods 'error', 'audio', 'properties', # Properties 'preFrameRender', 'previewRender', 'frameRender', 'command', - 'loadPreset', 'update' + 'loadPreset', 'update', 'widget', ) # Auto-decorate methods @@ -184,6 +223,8 @@ class ComponentMetaclass(type(QtCore.QObject)): attrs[key] = cls.loadPresetWrapper(attrs[key]) elif key == 'update': attrs[key] = cls.updateWrapper(attrs[key]) + elif key == 'widget' and parents[0] != QtCore.QObject: + attrs[key] = cls.widgetWrapper(attrs[key]) # Turn version string into a number try: @@ -224,23 +265,28 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): self.moduleIndex = moduleIndex self.compPos = compPos self.core = core - self.currentPreset = None - self.openingPreset = False + # STATUS VARIABLES + self.currentPreset = None + self._allWidgets = {} self._trackedWidgets = {} self._presetNames = {} self._commandArgs = {} self._colorWidgets = {} self._colorFuncs = {} self._relativeWidgets = {} - # pixel values stored as floats + # Pixel values stored as floats self._relativeValues = {} - # maximum values of spinBoxes at 1080p (Core.resolutions[0]) + # Maximum values of spinBoxes at 1080p (Core.resolutions[0]) self._relativeMaximums = {} + # LOCKING VARIABLES + self.openingPreset = False self._lockedProperties = None self._lockedError = None self._lockedSize = None + # If set to a dict, values are used as basis to update relative widgets + self.oldAttrs = None # Stop lengthy processes in response to this variable self.canceled = False @@ -338,21 +384,21 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): ''' self.parent = parent self.settings = parent.settings + log.verbose('Creating UI for %s #%s\'s widget' % ( + self.name, self.compPos + )) self.page = self.loadUi(self.__class__.ui) - # Connect widget signals - widgets = { + # Find all normal widgets which will be connected after subclass method + self._allWidgets = { 'lineEdit': self.page.findChildren(QtWidgets.QLineEdit), 'checkBox': self.page.findChildren(QtWidgets.QCheckBox), 'spinBox': self.page.findChildren(QtWidgets.QSpinBox), 'comboBox': self.page.findChildren(QtWidgets.QComboBox), } - widgets['spinBox'].extend( + self._allWidgets['spinBox'].extend( self.page.findChildren(QtWidgets.QDoubleSpinBox) ) - for widgetList in widgets.values(): - for widget in widgetList: - connectWidget(widget, self.update) def update(self): ''' @@ -427,10 +473,15 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~ # "Private" Methods # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~ + def _preUpdate(self): + '''Happens before subclass update()''' + for attr in self._relativeWidgets: + self.updateRelativeWidget(attr) + def _userUpdate(self): - '''An undoable component update triggered by the user''' + '''Happens after subclass update() for an undoable update by user.''' oldWidgetVals = { - attr: getattr(self, attr) + attr: copy(getattr(self, attr)) for attr in self._trackedWidgets } newWidgetVals = { @@ -443,13 +494,12 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): for attr, val in newWidgetVals.items() if val != oldWidgetVals[attr] } - if modifiedWidgets: action = ComponentUpdate(self, oldWidgetVals, modifiedWidgets) self.parent.undoStack.push(action) def _autoUpdate(self): - '''An internal component update that is not undoable''' + '''Happens after subclass update() for an internal component update.''' newWidgetVals = { attr: getWidgetValue(widget) for attr, widget in self._trackedWidgets.items() @@ -459,12 +509,12 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): def setAttrs(self, attrDict): ''' - Sets attrs (linked to trackedWidgets) in this preset to + Sets attrs (linked to trackedWidgets) in this component to the values in the attrDict. Mutates certain widget values if needed ''' for attr, val in attrDict.items(): if attr in self._colorWidgets: - # Color Widgets: text stored as tuple & update the button color + # Color Widgets must have a tuple & have a button to update if type(val) is tuple: rgbTuple = val else: @@ -475,15 +525,25 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): self._colorWidgets[attr].setStyleSheet(btnStyle) setattr(self, attr, rgbTuple) - elif attr in self._relativeWidgets: - # Relative widgets: number scales to fit export resolution - self.updateRelativeWidget(attr) - setattr(self, attr, val) - else: # Normal tracked widget setattr(self, attr, val) + def setWidgetValues(self, attrDict): + ''' + Sets widgets defined by keys in trackedWidgets in this preset to + the values in the attrDict. + ''' + affectedWidgets = [ + self._trackedWidgets[attr] for attr in attrDict + ] + with blockSignals(affectedWidgets): + for attr, val in attrDict.items(): + widget = self._trackedWidgets[attr] + if attr in self._colorWidgets: + val = '%s,%s,%s' % val + setWidgetValue(widget, val) + def _sendUpdateSignal(self): if not self.core.openingProject: self.parent.drawPreview() @@ -499,6 +559,8 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): Optional args: 'presetNames': preset variable names to replace attr names 'commandArgs': arg keywords that differ from attr names + 'colorWidgets': identify attr as RGB tuple & update button CSS + 'relativeWidgets': change value proportionally to resolution NOTE: Any kwarg key set to None will selectively disable tracking. ''' @@ -542,6 +604,8 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): self._relativeMaximums[attr] = \ self._trackedWidgets[attr].maximum() self.updateRelativeWidgetMaximum(attr) + self._preUpdate() + self._autoUpdate() def pickColor(self, textWidget, button): '''Use color picker to get color input from the user.''' @@ -627,12 +691,28 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): def setRelativeWidget(self, attr, floatVal): '''Set a relative widget using a float''' pixelVal = self.pixelValForAttr(attr, floatVal) - self._trackedWidgets[attr].setValue(pixelVal) + with blockSignals(self._allWidgets): + self._trackedWidgets[attr].setValue(pixelVal) + self.update(auto=True) + + def getOldAttr(self, attr): + ''' + Returns previous state of this attr. Used to determine whether + a relative widget must be updated. Required because undoing/redoing + can make determining the 'previous' value tricky. + ''' + if self.oldAttrs is not None: + log.verbose('Using nonstandard oldAttr for %s' % attr) + return self.oldAttrs[attr] + else: + return getattr(self, attr) def updateRelativeWidget(self, attr): + '''Called by _preUpdate() for each relativeWidget before each update''' try: - oldUserValue = getattr(self, attr) - except AttributeError: + oldUserValue = self.getOldAttr(attr) + except (AttributeError, KeyError): + log.info('Using visible values as basis for relative widgets') oldUserValue = self._trackedWidgets[attr].value() newUserValue = self._trackedWidgets[attr].value() newRelativeVal = self.floatValForAttr(attr, newUserValue) @@ -645,11 +725,10 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): # means the pixel value needs to be updated log.debug('Updating %s #%s\'s relative widget: %s' % ( self.name, self.compPos, attr)) - self._trackedWidgets[attr].blockSignals(True) - self.updateRelativeWidgetMaximum(attr) - pixelVal = self.pixelValForAttr(attr, oldRelativeVal) - self._trackedWidgets[attr].setValue(pixelVal) - self._trackedWidgets[attr].blockSignals(False) + with blockSignals(self._trackedWidgets[attr]): + self.updateRelativeWidgetMaximum(attr) + pixelVal = self.pixelValForAttr(attr, oldRelativeVal) + self._trackedWidgets[attr].setValue(pixelVal) if attr not in self._relativeValues \ or oldUserValue != newUserValue: @@ -725,14 +804,22 @@ class ComponentUpdate(QtWidgets.QUndoCommand): parent.name, parent.compPos ) ) + self.undone = False self.parent = parent self.oldWidgetVals = { - attr: val + attr: copy(val) for attr, val in oldWidgetVals.items() if attr in modifiedVals } self.modifiedVals = modifiedVals + # Because relative widgets change themselves every update based on + # their previous value, we must store ALL their values in case of undo + self.redoRelativeWidgetVals = { + attr: copy(getattr(self.parent, attr)) + for attr in self.parent._relativeWidgets + } + # Determine if this update is mergeable self.id_ = -1 if len(self.modifiedVals) == 1: @@ -755,15 +842,26 @@ class ComponentUpdate(QtWidgets.QUndoCommand): return True def redo(self): + if self.undone: + log.debug('Redoing component update') + self.parent.setWidgetValues(self.modifiedVals) self.parent.setAttrs(self.modifiedVals) - self.parent._sendUpdateSignal() + if self.undone: + self.parent.oldAttrs = self.redoRelativeWidgetVals + self.parent.update(auto=True) + self.parent.oldAttrs = None + else: + self.undoRelativeWidgetVals = { + attr: copy(getattr(self.parent, attr)) + for attr in self.parent._relativeWidgets + } + self.parent._sendUpdateSignal() def undo(self): + log.debug('Undoing component update') + self.undone = True + self.parent.oldAttrs = self.undoRelativeWidgetVals + self.parent.setWidgetValues(self.oldWidgetVals) self.parent.setAttrs(self.oldWidgetVals) - with blockSignals(self.parent): - for attr, val in self.oldWidgetVals.items(): - widget = self.parent._trackedWidgets[attr] - if attr in self.parent._colorWidgets: - val = '%s,%s,%s' % val - setWidgetValue(widget, val) - self.parent._sendUpdateSignal() + self.parent.update(auto=True) + self.parent.oldAttrs = None diff --git a/src/components/color.py b/src/components/color.py index d09cee8..a55aa10 100644 --- a/src/components/color.py +++ b/src/components/color.py @@ -82,8 +82,6 @@ class Component(Component): self.page.pushButton_color2.setEnabled(False) self.page.fillWidget.setCurrentIndex(fillType) - super().update() - def previewRender(self): return self.drawFrame(self.width, self.height) diff --git a/src/components/image.py b/src/components/image.py index 63bee1a..c57b69c 100644 --- a/src/components/image.py +++ b/src/components/image.py @@ -84,7 +84,6 @@ class Component(Component): if filename: self.settings.setValue("componentDir", os.path.dirname(filename)) self.page.lineEdit_image.setText(filename) - self.update() def command(self, arg): if '=' in arg: @@ -123,4 +122,3 @@ class Component(Component): else: scaleBox.setVisible(True) stretchScaleBox.setVisible(False) - super().update() diff --git a/src/components/life.py b/src/components/life.py index 2383d30..76d2c5f 100644 --- a/src/components/life.py +++ b/src/components/life.py @@ -53,7 +53,6 @@ class Component(Component): if filename: self.settings.setValue("componentDir", os.path.dirname(filename)) self.page.lineEdit_image.setText(filename) - self.update() def shiftGrid(self, d): def newGrid(Xchange, Ychange): diff --git a/src/components/sound.py b/src/components/sound.py index 26ecf93..b86f40c 100644 --- a/src/components/sound.py +++ b/src/components/sound.py @@ -53,7 +53,6 @@ class Component(Component): if filename: self.settings.setValue("componentDir", os.path.dirname(filename)) self.page.lineEdit_sound.setText(filename) - self.update() def commandHelp(self): print('Path to audio file:\n path=/filepath/to/sound.ogg') diff --git a/src/components/spectrum.py b/src/components/spectrum.py index 89130a2..2b98dc2 100644 --- a/src/components/spectrum.py +++ b/src/components/spectrum.py @@ -76,8 +76,6 @@ class Component(Component): else: self.page.checkBox_mono.setEnabled(True) - super().update() - def previewRender(self): changedSize = self.updateChunksize() if not changedSize \ @@ -138,7 +136,7 @@ class Component(Component): '-r', self.settings.value("outputFrameRate"), '-ss', "{0:.3f}".format(startPt), '-i', - os.path.join(self.core.wd, 'background.png') + self.core.junkStream if genericPreview else inputFile, '-f', 'image2pipe', '-pix_fmt', 'rgba', diff --git a/src/components/text.py b/src/components/text.py index d3afd5c..92f0599 100644 --- a/src/components/text.py +++ b/src/components/text.py @@ -68,7 +68,6 @@ class Component(Component): self.page.spinBox_shadY.setHidden(True) self.page.label_shadBlur.setHidden(True) self.page.spinBox_shadBlur.setHidden(True) - super().update() def centerXY(self): self.setRelativeWidget('xPosition', 0.5) diff --git a/src/components/video.py b/src/components/video.py index a189f60..9c0d608 100644 --- a/src/components/video.py +++ b/src/components/video.py @@ -52,7 +52,6 @@ class Component(Component): else: self.page.label_volume.setEnabled(False) self.page.spinBox_volume.setEnabled(False) - super().update() def previewRender(self): self.updateChunksize() @@ -119,7 +118,6 @@ class Component(Component): if filename: self.settings.setValue("componentDir", os.path.dirname(filename)) self.page.lineEdit_video.setText(filename) - self.update() def getPreviewFrame(self, width, height): if not self.videoPath or not os.path.exists(self.videoPath): diff --git a/src/components/waveform.py b/src/components/waveform.py index 0743e55..5c02bbf 100644 --- a/src/components/waveform.py +++ b/src/components/waveform.py @@ -98,7 +98,7 @@ class Component(Component): '-r', self.settings.value("outputFrameRate"), '-ss', "{0:.3f}".format(startPt), '-i', - os.path.join(self.core.wd, 'background.png') + self.core.junkStream if genericPreview else inputFile, '-f', 'image2pipe', '-pix_fmt', 'rgba', diff --git a/src/core.py b/src/core.py index d9499f7..169716c 100644 --- a/src/core.py +++ b/src/core.py @@ -13,7 +13,7 @@ import toolkit log = logging.getLogger('AVP.Core') -STDOUT_LOGLVL = logging.WARNING +STDOUT_LOGLVL = logging.VERBOSE FILE_LOGLVL = logging.DEBUG @@ -81,10 +81,7 @@ class Core: component = self.modules[moduleIndex].Component( moduleIndex, compPos, self ) - # init component's widget for loading/saving presets component.widget(loader) - # use autoUpdate() method before update() this 1 time to set attrs - component._autoUpdate() else: moduleIndex = -1 log.debug( @@ -186,9 +183,8 @@ class Core: if hasattr(loader, 'window'): for widget, value in data['WindowFields']: widget = eval('loader.window.%s' % widget) - widget.blockSignals(True) - toolkit.setWidgetValue(widget, value) - widget.blockSignals(False) + with toolkit.blockSignals(widget): + toolkit.setWidgetValue(widget, value) for key, value in data['Settings']: Core.settings.setValue(key, value) @@ -474,6 +470,7 @@ class Core: 'logDir': os.path.join(dataDir, 'log'), 'presetDir': os.path.join(dataDir, 'presets'), 'componentsPath': os.path.join(wd, 'components'), + 'junkStream': os.path.join(wd, 'gui', 'background.png'), 'encoderOptions': encoderOptions, 'resolutions': [ '1920x1080', diff --git a/src/gui/actions.py b/src/gui/actions.py index 0fe97f2..1444569 100644 --- a/src/gui/actions.py +++ b/src/gui/actions.py @@ -20,11 +20,20 @@ class AddComponent(QUndoCommand): self.parent = parent self.moduleI = moduleI self.compI = compI + self.comp = None def redo(self): - self.parent.core.insertComponent(self.compI, self.moduleI, self.parent) + if self.comp is None: + self.parent.core.insertComponent( + self.compI, self.moduleI, self.parent) + else: + # inserting previously-created component + self.parent.core.insertComponent( + self.compI, self.comp, self.parent) + def undo(self): + self.comp = self.parent.core.selectedComponents[self.compI] self.parent._removeComponent(self.compI) diff --git a/src/gui/mainwindow.py b/src/gui/mainwindow.py index 8000b3b..76c53af 100644 --- a/src/gui/mainwindow.py +++ b/src/gui/mainwindow.py @@ -25,7 +25,7 @@ from toolkit import ( ) -log = logging.getLogger('AVP.MainWindow') +log = logging.getLogger('AVP.Gui.MainWindow') class MainWindow(QtWidgets.QMainWindow): @@ -76,7 +76,7 @@ class MainWindow(QtWidgets.QMainWindow): # Create the preview window and its thread, queues, and timers log.debug('Creating preview window') self.previewWindow = PreviewWindow(self, os.path.join( - Core.wd, "background.png")) + Core.wd, 'gui', "background.png")) window.verticalLayout_previewWrapper.addWidget(self.previewWindow) log.debug('Starting preview thread') diff --git a/src/gui/presetmanager.py b/src/gui/presetmanager.py index dce5333..befa7cd 100644 --- a/src/gui/presetmanager.py +++ b/src/gui/presetmanager.py @@ -5,12 +5,16 @@ from PyQt5 import QtCore, QtWidgets import string import os +import logging from toolkit import badName from core import Core from gui.actions import * +log = logging.getLogger('AVP.Gui.PresetManager') + + class PresetManager(QtWidgets.QDialog): def __init__(self, window, parent): super().__init__(parent.window) diff --git a/src/gui/preview_thread.py b/src/gui/preview_thread.py index 9615884..33a9e7a 100644 --- a/src/gui/preview_thread.py +++ b/src/gui/preview_thread.py @@ -14,7 +14,7 @@ from toolkit.frame import Checkerboard from toolkit import disableWhenOpeningProject -log = logging.getLogger("AVP.PreviewThread") +log = logging.getLogger("AVP.Gui.PreviewThread") class Worker(QtCore.QObject): diff --git a/src/gui/preview_win.py b/src/gui/preview_win.py index 40c19c6..c6b9a32 100644 --- a/src/gui/preview_win.py +++ b/src/gui/preview_win.py @@ -7,7 +7,7 @@ class PreviewWindow(QtWidgets.QLabel): Paints the preview QLabel in MainWindow and maintains the aspect ratio when the window is resized. ''' - log = logging.getLogger('AVP.PreviewWindow') + log = logging.getLogger('AVP.Gui.PreviewWindow') def __init__(self, parent, img): super(PreviewWindow, self).__init__() diff --git a/src/main.py b/src/main.py index c1278da..6d18af3 100644 --- a/src/main.py +++ b/src/main.py @@ -6,7 +6,7 @@ import logging from __init__ import wd -log = logging.getLogger('AVP.Entrypoint') +log = logging.getLogger('AVP.Main') def main(): diff --git a/src/toolkit/common.py b/src/toolkit/common.py index 51ad023..74143e8 100644 --- a/src/toolkit/common.py +++ b/src/toolkit/common.py @@ -6,19 +6,53 @@ import string import os import sys import subprocess +import logging +from copy import copy from collections import OrderedDict +log = logging.getLogger('AVP.Toolkit.Common') + + class blockSignals: - '''A context manager to temporarily block a Qt widget from updating''' - def __init__(self, widget): - self.widget = widget + ''' + Context manager to temporarily block list of QtWidgets from updating, + and guarantee restoring the previous state afterwards. + ''' + def __init__(self, widgets): + if type(widgets) is dict: + self.widgets = concatDictVals(widgets) + else: + self.widgets = ( + widgets if hasattr(widgets, '__iter__') + else [widgets] + ) def __enter__(self): - self.widget.blockSignals(True) + log.verbose('Blocking signals for %s' % ", ".join([ + str(w.__class__.__name__) for w in self.widgets + ])) + self.oldStates = [w.signalsBlocked() for w in self.widgets] + for w in self.widgets: + w.blockSignals(True) def __exit__(self, *args): - self.widget.blockSignals(False) + log.verbose('Resetting blockSignals to %s' % sum(self.oldStates)) + for w, state in zip(self.widgets, self.oldStates): + w.blockSignals(state) + + +def concatDictVals(d): + '''Concatenates all values in given dict into one list.''' + key, value = d.popitem() + d[key] = value + final = copy(value) + if type(final) is not list: + final = [final] + final.extend([val for val in d.values()]) + else: + value.extend([item for val in d.values() for item in val]) + return final def badName(name): @@ -119,12 +153,14 @@ def connectWidget(widget, func): elif type(widget) == QtWidgets.QComboBox: widget.currentIndexChanged.connect(func) else: + log.warning('Failed to connect %s ' % str(widget.__class__.__name__)) return False return True def setWidgetValue(widget, val): '''Generic setValue method for use with any typical QtWidget''' + log.verbose('Setting %s to %s' % (str(widget.__class__.__name__), val)) if type(widget) == QtWidgets.QLineEdit: widget.setText(val) elif type(widget) == QtWidgets.QSpinBox \ @@ -135,6 +171,7 @@ def setWidgetValue(widget, val): elif type(widget) == QtWidgets.QComboBox: widget.setCurrentIndex(val) else: + log.warning('Failed to set %s ' % str(widget.__class__.__name__)) return False return True -- cgit v1.2.3 From 4a310ffb2870babf6774da843cad271f8a477bcc Mon Sep 17 00:00:00 2001 From: tassaron Date: Sun, 27 Aug 2017 12:10:21 -0400 Subject: file logging can be turned completely off and various changes to log levels and messages everywhere --- src/component.py | 22 ++++++++---- src/components/spectrum.py | 21 ++++++++---- src/components/video.py | 21 ++++++++---- src/components/waveform.py | 20 +++++++---- src/core.py | 85 ++++++++++++++++++++++------------------------ src/gui/mainwindow.py | 18 ++++------ src/toolkit/ffmpeg.py | 22 ++++++++---- 7 files changed, 119 insertions(+), 90 deletions(-) (limited to 'src/components/waveform.py') diff --git a/src/component.py b/src/component.py index 01c1d06..f3ee188 100644 --- a/src/component.py +++ b/src/component.py @@ -423,7 +423,14 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): for attr, widget in self._trackedWidgets.items(): key = attr if attr not in self._presetNames \ else self._presetNames[attr] - val = presetDict[key] + try: + val = presetDict[key] + except KeyError as e: + log.info( + '%s missing value %s. Outdated preset?', + self.currentPreset, str(e) + ) + val = getattr(self, key) if attr in self._colorWidgets: widget.setText('%s,%s,%s' % val) @@ -580,7 +587,7 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): 'colorWidgets', 'relativeWidgets', ): - setattr(self, '_%s' % kwarg, kwargs[kwarg]) + setattr(self, '_{}'.format(kwarg), kwargs[kwarg]) else: raise ComponentError( self, 'Nonsensical keywords to trackWidgets.') @@ -613,6 +620,10 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): self._relativeMaximums[attr] = \ self._trackedWidgets[attr].maximum() self.updateRelativeWidgetMaximum(attr) + setattr( + self, attr, getWidgetValue(self._trackedWidgets[attr]) + ) + self._preUpdate() self._autoUpdate() @@ -732,13 +743,12 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass): can make determining the 'previous' value tricky. ''' if self.oldAttrs is not None: - log.verbose('Using nonstandard oldAttr for %s', attr) return self.oldAttrs[attr] else: try: return getattr(self, attr) except AttributeError: - log.info('Using visible values instead of attrs') + log.error('Using visible values instead of oldAttrs') return self._trackedWidgets[attr].value() def updateRelativeWidget(self, attr): @@ -893,7 +903,7 @@ class ComponentUpdate(QtWidgets.QUndoCommand): def redo(self): if self.undone: - log.debug('Redoing component update') + log.info('Redoing component update') self.parent.oldAttrs = self.relativeWidgetValsAfterUndo self.setWidgetValues(self.modifiedVals) self.parent.update(auto=True) @@ -906,7 +916,7 @@ class ComponentUpdate(QtWidgets.QUndoCommand): self.parent._sendUpdateSignal() def undo(self): - log.debug('Undoing component update') + log.info('Undoing component update') self.undone = True self.parent.oldAttrs = self.relativeWidgetValsAfterRedo self.setWidgetValues(self.oldWidgetVals) diff --git a/src/components/spectrum.py b/src/components/spectrum.py index 2b98dc2..77cb086 100644 --- a/src/components/spectrum.py +++ b/src/components/spectrum.py @@ -148,15 +148,22 @@ class Component(Component): '-codec:v', 'rawvideo', '-', '-frames:v', '1', ]) - logFilename = os.path.join( - self.core.logDir, 'preview_%s.log' % str(self.compPos)) - log.debug('Creating ffmpeg process (log at %s)' % logFilename) - with open(logFilename, 'w') as logf: - logf.write(" ".join(command) + '\n\n') - with open(logFilename, 'a') as logf: + + if self.core.logEnabled: + logFilename = os.path.join( + self.core.logDir, 'preview_%s.log' % str(self.compPos)) + log.debug('Creating ffmpeg process (log at %s)' % logFilename) + with open(logFilename, 'w') as logf: + logf.write(" ".join(command) + '\n\n') + with open(logFilename, 'a') as logf: + self.previewPipe = openPipe( + command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, + stderr=logf, bufsize=10**8 + ) + else: self.previewPipe = openPipe( command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, - stderr=logf, bufsize=10**8 + stderr=subprocess.DEVNULL, bufsize=10**8 ) byteFrame = self.previewPipe.stdout.read(self.chunkSize) closePipe(self.previewPipe) diff --git a/src/components/video.py b/src/components/video.py index e6486ea..8ad21b5 100644 --- a/src/components/video.py +++ b/src/components/video.py @@ -139,16 +139,23 @@ class Component(Component): '-frames:v', '1', ]) - logFilename = os.path.join( - self.core.logDir, 'preview_%s.log' % str(self.compPos)) - log.debug('Creating ffmpeg process (log at %s)' % logFilename) - with open(logFilename, 'w') as logf: - logf.write(" ".join(command) + '\n\n') - with open(logFilename, 'a') as logf: + if self.core.logEnabled: + logFilename = os.path.join( + self.core.logDir, 'preview_%s.log' % str(self.compPos)) + log.debug('Creating ffmpeg process (log at %s)' % logFilename) + with open(logFilename, 'w') as logf: + logf.write(" ".join(command) + '\n\n') + with open(logFilename, 'a') as logf: + pipe = openPipe( + command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, + stderr=logf, bufsize=10**8 + ) + else: pipe = openPipe( command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, - stderr=logf, bufsize=10**8 + stderr=subprocess.DEVNULL, bufsize=10**8 ) + byteFrame = pipe.stdout.read(self.chunkSize) closePipe(pipe) diff --git a/src/components/waveform.py b/src/components/waveform.py index 5c02bbf..cbfc47f 100644 --- a/src/components/waveform.py +++ b/src/components/waveform.py @@ -110,15 +110,21 @@ class Component(Component): '-codec:v', 'rawvideo', '-', '-frames:v', '1', ]) - logFilename = os.path.join( - self.core.logDir, 'preview_%s.log' % str(self.compPos)) - log.debug('Creating ffmpeg process (log at %s)' % logFilename) - with open(logFilename, 'w') as logf: - logf.write(" ".join(command) + '\n\n') - with open(logFilename, 'a') as logf: + if self.core.logEnabled: + logFilename = os.path.join( + self.core.logDir, 'preview_%s.log' % str(self.compPos)) + log.debug('Creating ffmpeg log at %s', logFilename) + with open(logFilename, 'w') as logf: + logf.write(" ".join(command) + '\n\n') + with open(logFilename, 'a') as logf: + pipe = openPipe( + command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, + stderr=logf, bufsize=10**8 + ) + else: pipe = openPipe( command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, - stderr=logf, bufsize=10**8 + stderr=subprocess.DEVNULL, bufsize=10**8 ) byteFrame = pipe.stdout.read(self.chunkSize) closePipe(pipe) diff --git a/src/core.py b/src/core.py index b9e2335..1a90296 100644 --- a/src/core.py +++ b/src/core.py @@ -13,8 +13,8 @@ import toolkit log = logging.getLogger('AVP.Core') -STDOUT_LOGLVL = logging.VERBOSE -FILE_LOGLVL = logging.DEBUG +STDOUT_LOGLVL = logging.INFO +FILE_LOGLVL = logging.VERBOSE class Core: @@ -145,17 +145,11 @@ class Core: saveValueStore = self.getPreset(filepath) if not saveValueStore: return False - try: - comp = self.selectedComponents[compIndex] - comp.loadPreset( - saveValueStore, - presetName - ) - except KeyError as e: - log.warning( - '%s #%s\'s preset is missing value: %s', - comp.name, str(compIndex), str(e) - ) + comp = self.selectedComponents[compIndex] + comp.loadPreset( + saveValueStore, + presetName + ) self.savedPresets[presetName] = dict(saveValueStore) return True @@ -472,11 +466,12 @@ class Core: encoderOptions = json.load(json_file) settings = { + 'canceled': False, + 'FFMPEG_BIN': findFfmpeg(), 'dataDir': dataDir, 'settings': QtCore.QSettings( os.path.join(dataDir, 'settings.ini'), QtCore.QSettings.IniFormat), - 'logDir': os.path.join(dataDir, 'log'), 'presetDir': os.path.join(dataDir, 'presets'), 'componentsPath': os.path.join(wd, 'components'), 'junkStream': os.path.join(wd, 'gui', 'background.png'), @@ -486,8 +481,8 @@ class Core: '1280x720', '854x480', ], - 'FFMPEG_BIN': findFfmpeg(), - 'canceled': False, + 'logDir': os.path.join(dataDir, 'log'), + 'logEnabled': False, } settings['videoFormats'] = toolkit.appendUppercase([ @@ -572,42 +567,42 @@ class Core: @staticmethod def makeLogger(): - logFilename = os.path.join(Core.logDir, 'avp_debug.log') - libLogFilename = os.path.join(Core.logDir, 'global_debug.log') - # delete old logs - for log in (logFilename, libLogFilename): - if os.path.exists(log): - os.remove(log) - - # create file handlers to capture every log message somewhere - logFile = logging.FileHandler(logFilename) - logFile.setLevel(FILE_LOGLVL) - libLogFile = logging.FileHandler(libLogFilename) - libLogFile.setLevel(FILE_LOGLVL) - - # send some critical log messages to stdout as well + # send critical log messages to stdout logStream = logging.StreamHandler() logStream.setLevel(STDOUT_LOGLVL) - - # create formatters for each stream - fileFormatter = logging.Formatter( - '[%(asctime)s] %(threadName)-10.10s %(name)-23.23s %(levelname)s: ' - '%(message)s' - ) streamFormatter = logging.Formatter( - '<%(name)s> %(message)s' + '<%(name)s> %(levelname)s: %(message)s' ) - logFile.setFormatter(fileFormatter) - libLogFile.setFormatter(fileFormatter) logStream.setFormatter(streamFormatter) - log = logging.getLogger('AVP') - log.addHandler(logFile) log.addHandler(logStream) - libLog = logging.getLogger() - libLog.addHandler(libLogFile) - # lowest level must be explicitly set on the root Logger - libLog.setLevel(0) + + if FILE_LOGLVL is not None: + # write log files as well! + Core.logEnabled = True + logFilename = os.path.join(Core.logDir, 'avp_debug.log') + libLogFilename = os.path.join(Core.logDir, 'global_debug.log') + # delete old logs + for log_ in (logFilename, libLogFilename): + if os.path.exists(log_): + os.remove(log_) + + logFile = logging.FileHandler(logFilename) + logFile.setLevel(FILE_LOGLVL) + libLogFile = logging.FileHandler(libLogFilename) + libLogFile.setLevel(FILE_LOGLVL) + fileFormatter = logging.Formatter( + '[%(asctime)s] %(threadName)-10.10s %(name)-23.23s %(levelname)s: ' + '%(message)s' + ) + logFile.setFormatter(fileFormatter) + libLogFile.setFormatter(fileFormatter) + + libLog = logging.getLogger() + log.addHandler(logFile) + libLog.addHandler(libLogFile) + # lowest level must be explicitly set on the root Logger + libLog.setLevel(0) # always store settings in class variables even if a Core object is not created Core.storeSettings() diff --git a/src/gui/mainwindow.py b/src/gui/mainwindow.py index d7fde5c..81c5d7c 100644 --- a/src/gui/mainwindow.py +++ b/src/gui/mainwindow.py @@ -92,6 +92,10 @@ class MainWindow(QtWidgets.QMainWindow): self.previewWorker.moveToThread(self.previewThread) self.previewWorker.imageCreated.connect(self.showPreviewImage) self.previewThread.start() + self.previewThread.finished.connect( + lambda: + log.critical('PREVIEW THREAD DIED! This should never happen.') + ) timeout = 500 log.debug( @@ -442,7 +446,7 @@ class MainWindow(QtWidgets.QMainWindow): appName += '*' except AttributeError: pass - log.debug('Setting window title to %s' % appName) + log.verbose('Setting window title to %s' % appName) self.window.setWindowTitle(appName) @QtCore.pyqtSlot(int, dict) @@ -459,16 +463,8 @@ class MainWindow(QtWidgets.QMainWindow): modified = False else: modified = (presetStore != self.core.savedPresets[name]) - if modified: - log.verbose( - 'Differing values between presets: %s', - ", ".join([ - '%s: %s' % item for item in presetStore.items() - if val != self.core.savedPresets[name][key] - ]) - ) - else: - modified = bool(presetStore) + + modified = bool(presetStore) if pos < 0: pos = len(self.core.selectedComponents)-1 name = self.core.selectedComponents[pos].name diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py index a77831e..d78d803 100644 --- a/src/toolkit/ffmpeg.py +++ b/src/toolkit/ffmpeg.py @@ -91,16 +91,24 @@ class FfmpegVideo: def fillBuffer(self): from component import ComponentError - logFilename = os.path.join( - core.Core.logDir, 'render_%s.log' % str(self.component.compPos)) - log.debug('Creating ffmpeg process (log at %s)', logFilename) - with open(logFilename, 'w') as logf: - logf.write(" ".join(self.command) + '\n\n') - with open(logFilename, 'a') as logf: + if core.Core.logEnabled: + logFilename = os.path.join( + core.Core.logDir, 'render_%s.log' % str(self.component.compPos) + ) + log.debug('Creating ffmpeg process (log at %s)', logFilename) + with open(logFilename, 'w') as logf: + logf.write(" ".join(self.command) + '\n\n') + with open(logFilename, 'a') as logf: + self.pipe = openPipe( + self.command, stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, stderr=logf, bufsize=10**8 + ) + else: self.pipe = openPipe( self.command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, - stderr=logf, bufsize=10**8 + stderr=subprocess.DEVNULL, bufsize=10**8 ) + while True: if self.parent.canceled: break -- cgit v1.2.3 From 05d2ebc3c69f5a876d602004f69202c5ba8b09f7 Mon Sep 17 00:00:00 2001 From: tassaron Date: Fri, 22 Apr 2022 17:09:50 -0400 Subject: make pip-installable as a package --- MANIFEST.in | 7 ++++++ setup.py | 61 ++++++++++++++++++++++++++-------------------- src/__init__.py | 6 ++--- src/__main__.py | 4 +-- src/component.py | 4 +-- src/components/color.py | 4 +-- src/components/image.py | 4 +-- src/components/life.py | 4 +-- src/components/original.py | 4 +-- src/components/sound.py | 4 +-- src/components/spectrum.py | 8 +++--- src/components/text.py | 4 +-- src/components/video.py | 8 +++--- src/components/waveform.py | 8 +++--- src/core.py | 12 ++++----- src/gui/actions.py | 2 +- src/gui/mainwindow.py | 13 +++++----- src/gui/presetmanager.py | 6 ++--- src/gui/preview_thread.py | 4 +-- src/main.py | 11 ++++----- src/toolkit/__init__.py | 2 +- src/toolkit/ffmpeg.py | 8 +++--- src/toolkit/frame.py | 2 +- src/video_thread.py | 8 +++--- 24 files changed, 106 insertions(+), 92 deletions(-) create mode 100644 MANIFEST.in (limited to 'src/components/waveform.py') diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..2b2d794 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,7 @@ +recursive-include src/tests +include src/components/*.ui +include src/gui/*.ui +include src/gui/background.png +include src/encoder-options.json +global-exclude src/components/__template__.ui +global-exclude *.py[cod] diff --git a/setup.py b/setup.py index cdf4c4a..5e01229 100644 --- a/setup.py +++ b/setup.py @@ -1,29 +1,39 @@ -from setuptools import setup -import os +from setuptools import setup, find_packages +from importlib import import_module +from os import path +import re -__version__ = '2.0.0rc5' +def getTextFromFile(filename, fallback): + try: + with open( + path.join(path.abspath(path.dirname(__file__)), filename), encoding="utf-8" + ) as f: + output = f.read() + except Exception: + output = fallback + return output -def package_files(directory): - paths = [] - for (path, directories, filenames) in os.walk(directory): - for filename in filenames: - paths.append(os.path.join('..', path, filename)) - return paths +PACKAGE_NAME = 'avp' +SOURCE_DIRECTORY = 'src' +SOURCE_PACKAGE_REGEX = re.compile(rf'^{SOURCE_DIRECTORY}') +PACKAGE_DESCRIPTION = 'Create audio visualization videos from a GUI or commandline' + + +avp = import_module(SOURCE_DIRECTORY) +source_packages = find_packages(include=[SOURCE_DIRECTORY, f'{SOURCE_DIRECTORY}.*']) +proj_packages = [SOURCE_PACKAGE_REGEX.sub(PACKAGE_NAME, name) for name in source_packages] setup( name='audio_visualizer_python', - version=__version__, + version=avp.__version__, url='https://github.com/djfun/audio-visualizer-python/tree/feature-newgui', license='MIT', - description='Create audio visualization videos from a GUI or commandline', - long_description="Create customized audio visualization videos and save " - "them as Projects to continue editing later. Different components can " - "be added and layered to add visualizers, images, videos, gradients, " - "text, etc. Use Projects created in the GUI with commandline mode to " - "automate your video production workflow without any complex syntax.", + description=PACKAGE_DESCRIPTION, + author=getTextFromFile('AUTHORS', 'djfun, tassaron'), + long_description=getTextFromFile('README.md', PACKAGE_DESCRIPTION), classifiers=[ 'Development Status :: 4 - Beta', 'License :: OSI Approved :: MIT License', @@ -35,19 +45,18 @@ setup( 'visualizer', 'visualization', 'commandline video', 'video editor', 'ffmpeg', 'podcast' ], - packages=[ - 'avpython', - 'avpython.toolkit', - 'avpython.components' + packages=proj_packages, + package_dir={PACKAGE_NAME: SOURCE_DIRECTORY}, + include_package_data=True, + install_requires=[ + 'Pillow-SIMD', + 'PyQt5', + 'numpy', + 'pytest' ], - package_dir={'avpython': 'src'}, - package_data={ - 'avpython': package_files('src'), - }, - install_requires=['Pillow-SIMD', 'PyQt5', 'numpy'], entry_points={ 'gui_scripts': [ - 'avp = avpython.main:main' + f'avp = {PACKAGE_NAME}.main:main' ], } ) diff --git a/src/__init__.py b/src/__init__.py index 73f174a..08131ce 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -3,6 +3,9 @@ import os import logging +__version__ = '2.0.0rc6' + + class Logger(logging.getLoggerClass()): ''' Custom Logger class to handle custom VERBOSE log level. @@ -31,6 +34,3 @@ if getattr(sys, 'frozen', False): else: # unfrozen wd = os.path.dirname(os.path.realpath(__file__)) - -# make relative imports work when using /src as a package -sys.path.insert(0, wd) diff --git a/src/__main__.py b/src/__main__.py index 3babeae..3206bc8 100644 --- a/src/__main__.py +++ b/src/__main__.py @@ -1,5 +1,5 @@ -# Allows for launching with python3 -m avpython +# Allows for launching with python3 -m avp -from avpython.main import main +from .main import main main() diff --git a/src/component.py b/src/component.py index f3ee188..33c7657 100644 --- a/src/component.py +++ b/src/component.py @@ -11,8 +11,8 @@ import time import logging from copy import copy -from toolkit.frame import BlankFrame -from toolkit import ( +from .toolkit.frame import BlankFrame +from .toolkit import ( getWidgetValue, setWidgetValue, connectWidget, rgbFromString, blockSignals ) diff --git a/src/components/color.py b/src/components/color.py index 7d4f86d..6336194 100644 --- a/src/components/color.py +++ b/src/components/color.py @@ -4,8 +4,8 @@ from PyQt5.QtGui import QColor from PIL.ImageQt import ImageQt import os -from component import Component -from toolkit.frame import BlankFrame, FloodFrame, FramePainter, PaintColor +from ..component import Component +from ..toolkit.frame import BlankFrame, FloodFrame, FramePainter, PaintColor class Component(Component): diff --git a/src/components/image.py b/src/components/image.py index dd363bf..42f9564 100644 --- a/src/components/image.py +++ b/src/components/image.py @@ -2,8 +2,8 @@ from PIL import Image, ImageDraw, ImageEnhance from PyQt5 import QtGui, QtCore, QtWidgets import os -from component import Component -from toolkit.frame import BlankFrame +from ..component import Component +from ..toolkit.frame import BlankFrame class Component(Component): diff --git a/src/components/life.py b/src/components/life.py index 7a610eb..94704bc 100644 --- a/src/components/life.py +++ b/src/components/life.py @@ -4,8 +4,8 @@ from PIL import Image, ImageDraw, ImageEnhance, ImageChops, ImageFilter import os import math -from component import Component -from toolkit.frame import BlankFrame, scale +from ..component import Component +from ..toolkit.frame import BlankFrame, scale class Component(Component): diff --git a/src/components/original.py b/src/components/original.py index f886374..80228fe 100644 --- a/src/components/original.py +++ b/src/components/original.py @@ -6,8 +6,8 @@ import os import time from copy import copy -from component import Component -from toolkit.frame import BlankFrame +from ..component import Component +from ..toolkit.frame import BlankFrame class Component(Component): diff --git a/src/components/sound.py b/src/components/sound.py index 18d2a65..118ea23 100644 --- a/src/components/sound.py +++ b/src/components/sound.py @@ -1,8 +1,8 @@ from PyQt5 import QtGui, QtCore, QtWidgets import os -from component import Component -from toolkit.frame import BlankFrame +from ..component import Component +from ..toolkit.frame import BlankFrame class Component(Component): diff --git a/src/components/spectrum.py b/src/components/spectrum.py index 6675f5b..d1f8fb6 100644 --- a/src/components/spectrum.py +++ b/src/components/spectrum.py @@ -6,10 +6,10 @@ import subprocess import time import logging -from component import Component -from toolkit.frame import BlankFrame, scale -from toolkit import checkOutput, connectWidget -from toolkit.ffmpeg import ( +from ..component import Component +from ..toolkit.frame import BlankFrame, scale +from ..toolkit import checkOutput, connectWidget +from ..toolkit.ffmpeg import ( openPipe, closePipe, getAudioDuration, FfmpegVideo, exampleSound ) diff --git a/src/components/text.py b/src/components/text.py index 32a108e..e8c5a9c 100644 --- a/src/components/text.py +++ b/src/components/text.py @@ -4,8 +4,8 @@ from PyQt5 import QtGui, QtCore, QtWidgets import os import logging -from component import Component -from toolkit.frame import FramePainter, PaintColor +from ..component import Component +from ..toolkit.frame import FramePainter, PaintColor log = logging.getLogger('AVP.Components.Text') diff --git a/src/components/video.py b/src/components/video.py index 8ad21b5..070940d 100644 --- a/src/components/video.py +++ b/src/components/video.py @@ -5,10 +5,10 @@ import math import subprocess import logging -from component import Component -from toolkit.frame import BlankFrame, scale -from toolkit.ffmpeg import openPipe, closePipe, testAudioStream, FfmpegVideo -from toolkit import checkOutput +from ..component import Component +from ..toolkit.frame import BlankFrame, scale +from ..toolkit.ffmpeg import openPipe, closePipe, testAudioStream, FfmpegVideo +from ..toolkit import checkOutput log = logging.getLogger('AVP.Components.Video') diff --git a/src/components/waveform.py b/src/components/waveform.py index cbfc47f..1a6035f 100644 --- a/src/components/waveform.py +++ b/src/components/waveform.py @@ -6,10 +6,10 @@ import math import subprocess import logging -from component import Component -from toolkit.frame import BlankFrame, scale -from toolkit import checkOutput -from toolkit.ffmpeg import ( +from ..component import Component +from ..toolkit.frame import BlankFrame, scale +from ..toolkit import checkOutput +from ..toolkit.ffmpeg import ( openPipe, closePipe, getAudioDuration, FfmpegVideo, exampleSound ) diff --git a/src/core.py b/src/core.py index d7445c9..bc6f9b4 100644 --- a/src/core.py +++ b/src/core.py @@ -9,12 +9,12 @@ import json from importlib import import_module import logging -import toolkit +from . import toolkit log = logging.getLogger('AVP.Core') STDOUT_LOGLVL = logging.WARNING -FILE_LOGLVL = None +FILE_LOGLVL = logging.ERROR class Core: @@ -47,7 +47,7 @@ class Core: yield name log.debug('Importing component modules') self.modules = [ - import_module('components.%s' % name) + import_module('.components.%s' % name, __package__) for name in findComponents() ] # store canonical module names and indexes @@ -426,7 +426,7 @@ class Core: def newVideoWorker(self, loader, audioFile, outputPath): '''loader is MainWindow or Command object which must own the thread''' - import video_thread + from . import video_thread self.videoThread = QtCore.QThread(loader) videoWorker = video_thread.Worker( loader, audioFile, outputPath, self.selectedComponents @@ -450,8 +450,8 @@ class Core: @classmethod def storeSettings(cls): '''Store settings/paths to directories as class variables''' - from __init__ import wd - from toolkit.ffmpeg import findFfmpeg + from .__init__ import wd + from .toolkit.ffmpeg import findFfmpeg cls.wd = wd dataDir = QtCore.QStandardPaths.writableLocation( diff --git a/src/gui/actions.py b/src/gui/actions.py index 8e867b9..eb7b953 100644 --- a/src/gui/actions.py +++ b/src/gui/actions.py @@ -5,7 +5,7 @@ from PyQt5.QtWidgets import QUndoCommand import os from copy import copy -from core import Core +from ..core import Core # =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~ diff --git a/src/gui/mainwindow.py b/src/gui/mainwindow.py index 75534c2..da8370d 100644 --- a/src/gui/mainwindow.py +++ b/src/gui/mainwindow.py @@ -16,12 +16,12 @@ import filecmp import time import logging -from core import Core -import gui.preview_thread as preview_thread -from gui.preview_win import PreviewWindow -from gui.presetmanager import PresetManager -from gui.actions import * -from toolkit import ( +from ..core import Core +from . import preview_thread +from .preview_win import PreviewWindow +from .presetmanager import PresetManager +from .actions import * +from ..toolkit import ( disableWhenEncoding, disableWhenOpeningProject, checkOutput, blockSignals ) @@ -65,7 +65,6 @@ class MainWindow(QtWidgets.QMainWindow): self.settings = Core.settings # Register clean-up functions - signal.signal(signal.SIGINT, self.terminate) atexit.register(self.cleanUp) # Create stack of undoable user actions diff --git a/src/gui/presetmanager.py b/src/gui/presetmanager.py index 2445760..1e47a7f 100644 --- a/src/gui/presetmanager.py +++ b/src/gui/presetmanager.py @@ -7,9 +7,9 @@ import string import os import logging -from toolkit import badName -from core import Core -from gui.actions import * +from ..toolkit import badName +from ..core import Core +from .actions import * log = logging.getLogger('AVP.Gui.PresetManager') diff --git a/src/gui/preview_thread.py b/src/gui/preview_thread.py index d3e0581..7829476 100644 --- a/src/gui/preview_thread.py +++ b/src/gui/preview_thread.py @@ -10,8 +10,8 @@ from queue import Queue, Empty import os import logging -from toolkit.frame import Checkerboard -from toolkit import disableWhenOpeningProject +from ..toolkit.frame import Checkerboard +from ..toolkit import disableWhenOpeningProject log = logging.getLogger("AVP.Gui.PreviewThread") diff --git a/src/main.py b/src/main.py index 126e4a8..5fabda3 100644 --- a/src/main.py +++ b/src/main.py @@ -3,7 +3,7 @@ import sys import os import logging -from __init__ import wd +from .__init__ import wd log = logging.getLogger('AVP.Main') @@ -12,6 +12,7 @@ log = logging.getLogger('AVP.Main') def main(): app = QtWidgets.QApplication(sys.argv) app.setApplicationName("audio-visualizer") + proj = None # Determine mode mode = 'GUI' @@ -23,19 +24,17 @@ def main(): else: # opening a project file with gui proj = sys.argv[1] - else: - # normal gui launch - proj = None # Launch program if mode == 'commandline': - from command import Command + from .command import Command main = Command() + main.parseArgs() log.debug("Finished creating command object") elif mode == 'GUI': - from gui.mainwindow import MainWindow + from .gui.mainwindow import MainWindow window = uic.loadUi(os.path.join(wd, "gui", "mainwindow.ui")) # window.adjustSize() diff --git a/src/toolkit/__init__.py b/src/toolkit/__init__.py index 3fca275..55e5f84 100644 --- a/src/toolkit/__init__.py +++ b/src/toolkit/__init__.py @@ -1 +1 @@ -from toolkit.common import * +from .common import * diff --git a/src/toolkit/ffmpeg.py b/src/toolkit/ffmpeg.py index 419d491..3298c04 100644 --- a/src/toolkit/ffmpeg.py +++ b/src/toolkit/ffmpeg.py @@ -10,8 +10,8 @@ import signal from queue import PriorityQueue import logging -import core -from toolkit.common import checkOutput, pipeWrapper +from .. import core +from .common import checkOutput, pipeWrapper log = logging.getLogger('AVP.Toolkit.Ffmpeg') @@ -90,7 +90,7 @@ class FfmpegVideo: self.frameBuffer.task_done() def fillBuffer(self): - from component import ComponentError + from ..component import ComponentError if core.Core.logEnabled: logFilename = os.path.join( core.Core.logDir, 'render_%s.log' % str(self.component.compPos) @@ -144,7 +144,7 @@ def openPipe(commandList, **kwargs): def closePipe(pipe): pipe.stdout.close() - pipe.send_signal(signal.SIGINT) + pipe.send_signal(signal.SIGTERM) def findFfmpeg(): diff --git a/src/toolkit/frame.py b/src/toolkit/frame.py index 0e200b5..f2511fe 100644 --- a/src/toolkit/frame.py +++ b/src/toolkit/frame.py @@ -9,7 +9,7 @@ import os import math import logging -import core +from .. import core log = logging.getLogger('AVP.Toolkit.Frame') diff --git a/src/video_thread.py b/src/video_thread.py index 0a39f28..31331a3 100644 --- a/src/video_thread.py +++ b/src/video_thread.py @@ -19,9 +19,9 @@ import time import signal import logging -from component import ComponentError -from toolkit.frame import Checkerboard -from toolkit.ffmpeg import ( +from .component import ComponentError +from .toolkit.frame import Checkerboard +from .toolkit.ffmpeg import ( openPipe, readAudioFile, getAudioDuration, createFfmpegCommand ) @@ -400,7 +400,7 @@ class Worker(QtCore.QObject): comp.cancel() try: - self.out_pipe.send_signal(signal.SIGINT) + self.out_pipe.send_signal(signal.SIGTERM) except Exception: pass -- cgit v1.2.3 From c2c3f0aa5adf3127b84b3d50da9e1aa655c8a824 Mon Sep 17 00:00:00 2001 From: tassaron Date: Fri, 29 Apr 2022 21:15:17 -0400 Subject: remove extra window properties from window objects instead of windows with properties which are windows, windows now have the UI added directly to them using an argument of `uic.loadUi` Also, DPI scaling moved to MainWindow __init__ --- src/components/spectrum.py | 6 +- src/components/video.py | 4 +- src/components/waveform.py | 6 +- src/core.py | 2 +- src/gui/actions.py | 8 +- src/gui/mainwindow.py | 351 +++++++++++++++++++++++---------------------- src/gui/presetmanager.py | 88 ++++++------ src/gui/preview_win.py | 2 +- src/main.py | 18 +-- 9 files changed, 242 insertions(+), 243 deletions(-) (limited to 'src/components/waveform.py') diff --git a/src/components/spectrum.py b/src/components/spectrum.py index d1f8fb6..91f2afb 100644 --- a/src/components/spectrum.py +++ b/src/components/spectrum.py @@ -30,9 +30,9 @@ class Component(Component): self.previewSize = (214, 120) self.previewPipe = None - if hasattr(self.parent, 'window'): + if hasattr(self.parent, 'lineEdit_audioFile'): # update preview when audio file changes (if genericPreview is off) - self.parent.window.lineEdit_audioFile.textChanged.connect( + self.parent.lineEdit_audioFile.textChanged.connect( self.update ) @@ -123,7 +123,7 @@ class Component(Component): genericPreview = self.settings.value("pref_genericPreview") startPt = 0 if not genericPreview: - inputFile = self.parent.window.lineEdit_audioFile.text() + inputFile = self.parent.lineEdit_audioFile.text() if not inputFile or not os.path.exists(inputFile): return duration = getAudioDuration(inputFile) diff --git a/src/components/video.py b/src/components/video.py index 070940d..9fffc26 100644 --- a/src/components/video.py +++ b/src/components/video.py @@ -63,8 +63,8 @@ class Component(Component): def properties(self): props = [] - if hasattr(self.parent, 'window'): - outputFile = self.parent.window.lineEdit_outputFile.text() + if hasattr(self.parent, 'lineEdit_outputFile'): + outputFile = self.parent.lineEdit_outputFile.text() else: outputFile = str(self.parent.args.output) diff --git a/src/components/waveform.py b/src/components/waveform.py index 1a6035f..227f711 100644 --- a/src/components/waveform.py +++ b/src/components/waveform.py @@ -27,8 +27,8 @@ class Component(Component): self.page.lineEdit_color.setText('255,255,255') - if hasattr(self.parent, 'window'): - self.parent.window.lineEdit_audioFile.textChanged.connect( + if hasattr(self.parent, 'lineEdit_audioFile'): + self.parent.lineEdit_audioFile.textChanged.connect( self.update ) @@ -82,7 +82,7 @@ class Component(Component): genericPreview = self.settings.value("pref_genericPreview") startPt = 0 if not genericPreview: - inputFile = self.parent.window.lineEdit_audioFile.text() + inputFile = self.parent.lineEdit_audioFile.text() if not inputFile or not os.path.exists(inputFile): return duration = getAudioDuration(inputFile) diff --git a/src/core.py b/src/core.py index 42fd1c3..225d8e0 100644 --- a/src/core.py +++ b/src/core.py @@ -181,7 +181,7 @@ class Core: try: if hasattr(loader, 'window'): for widget, value in data['WindowFields']: - widget = eval('loader.window.%s' % widget) + widget = eval('loader.%s' % widget) with toolkit.blockSignals(widget): toolkit.setWidgetValue(widget, value) diff --git a/src/gui/actions.py b/src/gui/actions.py index eb7b953..afb980a 100644 --- a/src/gui/actions.py +++ b/src/gui/actions.py @@ -41,7 +41,7 @@ class RemoveComponent(QUndoCommand): def __init__(self, parent, selectedRows): super().__init__('remove component') self.parent = parent - componentList = self.parent.window.listWidget_componentList + componentList = self.parent.listWidget_componentList self.selectedRows = [ componentList.row(selected) for selected in selectedRows ] @@ -53,7 +53,7 @@ class RemoveComponent(QUndoCommand): self.parent._removeComponent(self.selectedRows[0]) def undo(self): - componentList = self.parent.window.listWidget_componentList + componentList = self.parent.listWidget_componentList for index, comp in zip(self.selectedRows, self.components): self.parent.core.insertComponent( index, comp, self.parent @@ -78,7 +78,7 @@ class MoveComponent(QUndoCommand): return True def do(self, rowa, rowb): - componentList = self.parent.window.listWidget_componentList + componentList = self.parent.listWidget_componentList page = self.parent.pages.pop(rowa) self.parent.pages.insert(rowb, page) @@ -86,7 +86,7 @@ class MoveComponent(QUndoCommand): item = componentList.takeItem(rowa) componentList.insertItem(rowb, item) - stackedWidget = self.parent.window.stackedWidget + stackedWidget = self.parent.stackedWidget widget = stackedWidget.removeWidget(page) stackedWidget.insertWidget(rowb, page) componentList.setCurrentRow(rowb) diff --git a/src/gui/mainwindow.py b/src/gui/mainwindow.py index 463d028..c31eec9 100644 --- a/src/gui/mainwindow.py +++ b/src/gui/mainwindow.py @@ -4,13 +4,12 @@ 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 PyQt5 import QtCore, QtGui, QtWidgets, uic +import PyQt5.QtWidgets as QtWidgets from PIL import Image from queue import Queue import sys import os -import signal import atexit import filecmp import time @@ -43,11 +42,22 @@ class MainWindow(QtWidgets.QMainWindow): newTask = QtCore.pyqtSignal(list) # for the preview window processTask = QtCore.pyqtSignal() - def __init__(self, window, project): - QtWidgets.QMainWindow.__init__(self) + def __init__(self, project): + super().__init__() log.debug( 'Main thread id: {}'.format(int(QtCore.QThread.currentThreadId()))) - self.window = window + uic.loadUi(os.path.join(Core.wd, "gui", "mainwindow.ui"), self) + desk = QtWidgets.QDesktopWidget() + dpi = desk.physicalDpiX() + log.info("Detected screen DPI: %s", dpi) + + self.resize( + int(self.width() * + (dpi / 96)), + int(self.height() * + (dpi / 96)) + ) + self.core = Core() Core.mode = 'GUI' # widgets of component settings @@ -73,15 +83,13 @@ class MainWindow(QtWidgets.QMainWindow): self.undoStack.setUndoLimit(undoLimit) # Create Preset Manager - self.presetManager = PresetManager( - uic.loadUi( - os.path.join(Core.wd, 'gui', 'presetmanager.ui')), self) + self.presetManager = PresetManager(self) # Create the preview window and its thread, queues, and timers log.debug('Creating preview window') self.previewWindow = PreviewWindow(self, os.path.join( Core.wd, 'gui', "background.png")) - window.verticalLayout_previewWrapper.addWidget(self.previewWindow) + self.verticalLayout_previewWrapper.addWidget(self.previewWindow) log.debug('Starting preview thread') self.previewQueue = Queue() @@ -105,7 +113,7 @@ class MainWindow(QtWidgets.QMainWindow): self.timer.start(timeout) # Begin decorating the window and connecting events - componentList = self.window.listWidget_componentList + componentList = self.listWidget_componentList # Undo Feature def toggleUndoButtonEnabled(*_): @@ -116,15 +124,15 @@ class MainWindow(QtWidgets.QMainWindow): # program is probably in midst of exiting pass - style = window.pushButton_undo.style() - undoButton = window.pushButton_undo + style = self.pushButton_undo.style() + undoButton = self.pushButton_undo undoButton.setIcon( style.standardIcon(QtWidgets.QStyle.SP_FileDialogBack) ) undoButton.clicked.connect(self.undoStack.undo) undoButton.setEnabled(False) self.undoStack.cleanChanged.connect(toggleUndoButtonEnabled) - self.undoMenu = QMenu() + self.undoMenu = QtWidgets.QMenu() self.undoMenu.addAction( self.undoStack.createUndoAction(self) ) @@ -138,93 +146,93 @@ class MainWindow(QtWidgets.QMainWindow): undoButton.setMenu(self.undoMenu) # end of Undo Feature - style = window.pushButton_listMoveUp.style() - window.pushButton_listMoveUp.setIcon( + style = self.pushButton_listMoveUp.style() + self.pushButton_listMoveUp.setIcon( style.standardIcon(QtWidgets.QStyle.SP_ArrowUp) ) - style = window.pushButton_listMoveDown.style() - window.pushButton_listMoveDown.setIcon( + style = self.pushButton_listMoveDown.style() + self.pushButton_listMoveDown.setIcon( style.standardIcon(QtWidgets.QStyle.SP_ArrowDown) ) - style = window.pushButton_removeComponent.style() - window.pushButton_removeComponent.setIcon( + style = self.pushButton_removeComponent.style() + self.pushButton_removeComponent.setIcon( style.standardIcon(QtWidgets.QStyle.SP_DialogDiscardButton) ) if sys.platform == 'darwin': log.debug( 'Darwin detected: showing progress label below progress bar') - window.progressBar_createVideo.setTextVisible(False) + self.progressBar_createVideo.setTextVisible(False) else: - window.progressLabel.setHidden(True) + self.progressLabel.setHidden(True) - window.toolButton_selectAudioFile.clicked.connect( + self.toolButton_selectAudioFile.clicked.connect( self.openInputFileDialog) - window.toolButton_selectOutputFile.clicked.connect( + self.toolButton_selectOutputFile.clicked.connect( self.openOutputFileDialog) def changedField(): self.autosave() self.updateWindowTitle() - window.lineEdit_audioFile.textChanged.connect(changedField) - window.lineEdit_outputFile.textChanged.connect(changedField) + self.lineEdit_audioFile.textChanged.connect(changedField) + self.lineEdit_outputFile.textChanged.connect(changedField) - window.progressBar_createVideo.setValue(0) + self.progressBar_createVideo.setValue(0) - window.pushButton_createVideo.clicked.connect( + self.pushButton_createVideo.clicked.connect( self.createAudioVisualisation) - window.pushButton_Cancel.clicked.connect(self.stopVideo) + self.pushButton_Cancel.clicked.connect(self.stopVideo) for i, container in enumerate(Core.encoderOptions['containers']): - window.comboBox_videoContainer.addItem(container['name']) + self.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.comboBox_videoContainer.setCurrentIndex(selectedContainer) + self.comboBox_videoContainer.currentIndexChanged.connect( self.updateCodecs ) self.updateCodecs() - for i in range(window.comboBox_videoCodec.count()): - codec = window.comboBox_videoCodec.itemText(i) + for i in range(self.comboBox_videoCodec.count()): + codec = self.comboBox_videoCodec.itemText(i) if codec == self.settings.value('outputVideoCodec'): - window.comboBox_videoCodec.setCurrentIndex(i) + self.comboBox_videoCodec.setCurrentIndex(i) - for i in range(window.comboBox_audioCodec.count()): - codec = window.comboBox_audioCodec.itemText(i) + for i in range(self.comboBox_audioCodec.count()): + codec = self.comboBox_audioCodec.itemText(i) if codec == self.settings.value('outputAudioCodec'): - window.comboBox_audioCodec.setCurrentIndex(i) + self.comboBox_audioCodec.setCurrentIndex(i) - window.comboBox_videoCodec.currentIndexChanged.connect( + self.comboBox_videoCodec.currentIndexChanged.connect( self.updateCodecSettings ) - window.comboBox_audioCodec.currentIndexChanged.connect( + self.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) + self.spinBox_vBitrate.setValue(vBitrate) + self.spinBox_aBitrate.setValue(aBitrate) + self.spinBox_vBitrate.valueChanged.connect(self.updateCodecSettings) + self.spinBox_aBitrate.valueChanged.connect(self.updateCodecSettings) # Make component buttons - self.compMenu = QMenu() + self.compMenu = QtWidgets.QMenu() for i, comp in enumerate(self.core.modules): action = self.compMenu.addAction(comp.Component.name) action.triggered.connect( lambda _, item=i: self.addComponent(0, item) ) - self.window.pushButton_addComponent.setMenu(self.compMenu) + self.pushButton_addComponent.setMenu(self.compMenu) componentList.dropEvent = self.dragComponent componentList.itemSelectionChanged.connect( @@ -233,7 +241,7 @@ class MainWindow(QtWidgets.QMainWindow): componentList.itemSelectionChanged.connect( self.presetManager.clearPresetListSelection ) - self.window.pushButton_removeComponent.clicked.connect( + self.pushButton_removeComponent.clicked.connect( lambda: self.removeComponent() ) @@ -245,33 +253,33 @@ class MainWindow(QtWidgets.QMainWindow): currentRes = str(self.settings.value('outputWidth'))+'x' + \ str(self.settings.value('outputHeight')) for i, res in enumerate(Core.resolutions): - window.comboBox_resolution.addItem(res) + self.comboBox_resolution.addItem(res) if res == currentRes: currentRes = i - window.comboBox_resolution.setCurrentIndex(currentRes) - window.comboBox_resolution.currentIndexChanged.connect( + self.comboBox_resolution.setCurrentIndex(currentRes) + self.comboBox_resolution.currentIndexChanged.connect( self.updateResolution ) - self.window.pushButton_listMoveUp.clicked.connect( + self.pushButton_listMoveUp.clicked.connect( lambda: self.moveComponent(-1) ) - self.window.pushButton_listMoveDown.clicked.connect( + self.pushButton_listMoveDown.clicked.connect( lambda: self.moveComponent(1) ) # Configure the Projects Menu - self.projectMenu = QMenu() - self.window.menuButton_newProject = self.projectMenu.addAction( + self.projectMenu = QtWidgets.QMenu() + self.menuButton_newProject = self.projectMenu.addAction( "New Project" ) - self.window.menuButton_newProject.triggered.connect( + self.menuButton_newProject.triggered.connect( lambda: self.createNewProject() ) - self.window.menuButton_openProject = self.projectMenu.addAction( + self.menuButton_openProject = self.projectMenu.addAction( "Open Project" ) - self.window.menuButton_openProject.triggered.connect( + self.menuButton_openProject.triggered.connect( lambda: self.openOpenProjectDialog() ) @@ -281,16 +289,16 @@ class MainWindow(QtWidgets.QMainWindow): action = self.projectMenu.addAction("Save Project As") action.triggered.connect(self.openSaveProjectDialog) - self.window.pushButton_projects.setMenu(self.projectMenu) + self.pushButton_projects.setMenu(self.projectMenu) # Configure the Presets Button - self.window.pushButton_presets.clicked.connect( + self.pushButton_presets.clicked.connect( self.openPresetManager ) self.updateWindowTitle() log.debug('Showing main window') - window.show() + self.show() if project and project != self.autosavePath: if not project.endswith('.avp'): @@ -358,77 +366,80 @@ class MainWindow(QtWidgets.QMainWindow): 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) + QtWidgets.QShortcut("Ctrl+S", self, self.saveCurrentProject) + QtWidgets.QShortcut("Ctrl+A", self, self.openSaveProjectDialog) + QtWidgets.QShortcut("Ctrl+O", self, self.openOpenProjectDialog) + QtWidgets.QShortcut("Ctrl+N", self, self.createNewProject) - QtWidgets.QShortcut("Ctrl+Z", self.window, self.undoStack.undo) - QtWidgets.QShortcut("Ctrl+Y", self.window, self.undoStack.redo) - QtWidgets.QShortcut("Ctrl+Shift+Z", self.window, self.undoStack.redo) + # Hotkeys for undo/redo + QtWidgets.QShortcut("Ctrl+Z", self, self.undoStack.undo) + QtWidgets.QShortcut("Ctrl+Y", self, self.undoStack.redo) + QtWidgets.QShortcut("Ctrl+Shift+Z", self, self.undoStack.redo) # 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() + inskey, self, + activated=lambda: self.pushButton_addComponent.click() ) for delkey in ("Ctrl+R", QtCore.Qt.Key_Delete): QtWidgets.QShortcut( - delkey, self.window.listWidget_componentList, + delkey, self.listWidget_componentList, self.removeComponent ) QtWidgets.QShortcut( - "Ctrl+Space", self.window, - activated=lambda: self.window.listWidget_componentList.setFocus() + "Ctrl+Space", self, + activated=lambda: self.listWidget_componentList.setFocus() ) QtWidgets.QShortcut( - "Ctrl+Shift+S", self.window, + "Ctrl+Shift+S", self, self.presetManager.openSavePresetDialog ) QtWidgets.QShortcut( - "Ctrl+Shift+C", self.window, self.presetManager.clearPreset + "Ctrl+Shift+C", self, self.presetManager.clearPreset ) QtWidgets.QShortcut( - "Ctrl+Up", self.window.listWidget_componentList, + "Ctrl+Up", self.listWidget_componentList, activated=lambda: self.moveComponent(-1) ) QtWidgets.QShortcut( - "Ctrl+Down", self.window.listWidget_componentList, + "Ctrl+Down", self.listWidget_componentList, activated=lambda: self.moveComponent(1) ) QtWidgets.QShortcut( - "Ctrl+Home", self.window.listWidget_componentList, + "Ctrl+Home", self.listWidget_componentList, activated=lambda: self.moveComponent('top') ) QtWidgets.QShortcut( - "Ctrl+End", self.window.listWidget_componentList, + "Ctrl+End", self.listWidget_componentList, activated=lambda: self.moveComponent('bottom') ) QtWidgets.QShortcut( - "Ctrl+Shift+F", self.window, self.showFfmpegCommand + "Ctrl+Shift+F", self, self.showFfmpegCommand ) QtWidgets.QShortcut( - "Ctrl+Shift+U", self.window, self.showUndoStack + "Ctrl+Shift+U", self, self.showUndoStack ) if log.isEnabledFor(logging.DEBUG): QtWidgets.QShortcut( - "Ctrl+Alt+Shift+R", self.window, self.drawPreview + "Ctrl+Alt+Shift+R", self, self.drawPreview ) QtWidgets.QShortcut( - "Ctrl+Alt+Shift+A", self.window, lambda: log.debug(repr(self)) + "Ctrl+Alt+Shift+A", self, lambda: log.debug(repr(self)) ) def __repr__(self): return ( + '%s\n' '\n%s\n' '#####\n' 'Preview thread is %s\n' % ( - repr(self.core), - 'live' if self.previewThread.isRunning() else 'dead', + super().__repr__(), + "core not initialized" if not hasattr(self, "core") else repr(self.core), + 'live' if hasattr(self, "previewThread") and self.previewThread.isRunning() else 'dead', ) ) @@ -456,7 +467,7 @@ class MainWindow(QtWidgets.QMainWindow): except AttributeError: pass log.verbose(f'Window title is "{appName}"') - self.window.setWindowTitle(appName) + self.setWindowTitle(appName) @QtCore.pyqtSlot(int, dict) def updateComponentTitle(self, pos, presetStore=False): @@ -492,12 +503,12 @@ class MainWindow(QtWidgets.QMainWindow): 'Setting %s #%s\'s title: %s', name, pos, title ) - self.window.listWidget_componentList.item(pos).setText(title) + self.listWidget_componentList.item(pos).setText(title) def updateCodecs(self): - containerWidget = self.window.comboBox_videoContainer - vCodecWidget = self.window.comboBox_videoCodec - aCodecWidget = self.window.comboBox_audioCodec + containerWidget = self.comboBox_videoContainer + vCodecWidget = self.comboBox_videoCodec + aCodecWidget = self.comboBox_audioCodec index = containerWidget.currentIndex() name = containerWidget.itemText(index) self.settings.setValue('outputContainer', name) @@ -514,10 +525,10 @@ class MainWindow(QtWidgets.QMainWindow): 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 + vCodecWidget = self.comboBox_videoCodec + vBitrateWidget = self.spinBox_vBitrate + aBitrateWidget = self.spinBox_aBitrate + aCodecWidget = self.comboBox_audioCodec currentVideoCodec = vCodecWidget.currentIndex() currentVideoCodec = vCodecWidget.itemText(currentVideoCodec) currentVideoBitrate = vBitrateWidget.value() @@ -535,7 +546,7 @@ class MainWindow(QtWidgets.QMainWindow): 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.core.createProjectFile(self.autosavePath, self) self.lastAutosave = time.time() if len(self.autosaveTimes) >= 5: # Do some math to reduce autosave spam. This gives a smooth @@ -588,25 +599,25 @@ class MainWindow(QtWidgets.QMainWindow): inputDir = self.settings.value("inputDir", os.path.expanduser("~")) fileName, _ = QtWidgets.QFileDialog.getOpenFileName( - self.window, "Open Audio File", + self, "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) + self.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", + self, "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) + self.lineEdit_outputFile.setText(fileName) def stopVideo(self): log.info('Export cancelled') @@ -615,8 +626,8 @@ class MainWindow(QtWidgets.QMainWindow): def createAudioVisualisation(self): # create output video if mandatory settings are filled in - audioFile = self.window.lineEdit_audioFile.text() - outputPath = self.window.lineEdit_outputFile.text() + audioFile = self.lineEdit_audioFile.text() + outputPath = self.lineEdit_outputFile.text() if audioFile and outputPath and self.core.selectedComponents: if not os.path.dirname(outputPath): @@ -670,62 +681,62 @@ class MainWindow(QtWidgets.QMainWindow): 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) + self.pushButton_createVideo.setEnabled(False) + self.pushButton_Cancel.setEnabled(True) + self.comboBox_resolution.setEnabled(False) + self.stackedWidget.setEnabled(False) + self.tab_encoderSettings.setEnabled(False) + self.label_audioFile.setEnabled(False) + self.toolButton_selectAudioFile.setEnabled(False) + self.label_outputFile.setEnabled(False) + self.toolButton_selectOutputFile.setEnabled(False) + self.lineEdit_audioFile.setEnabled(False) + self.lineEdit_outputFile.setEnabled(False) + self.pushButton_addComponent.setEnabled(False) + self.pushButton_removeComponent.setEnabled(False) + self.pushButton_listMoveDown.setEnabled(False) + self.pushButton_listMoveUp.setEnabled(False) + self.menuButton_newProject.setEnabled(False) + self.menuButton_openProject.setEnabled(False) if sys.platform == 'darwin': - self.window.progressLabel.setHidden(False) + self.progressLabel.setHidden(False) else: - self.window.listWidget_componentList.setEnabled(False) + self.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.pushButton_createVideo.setEnabled(True) + self.pushButton_Cancel.setEnabled(False) + self.comboBox_resolution.setEnabled(True) + self.stackedWidget.setEnabled(True) + self.tab_encoderSettings.setEnabled(True) + self.label_audioFile.setEnabled(True) + self.toolButton_selectAudioFile.setEnabled(True) + self.lineEdit_audioFile.setEnabled(True) + self.label_outputFile.setEnabled(True) + self.toolButton_selectOutputFile.setEnabled(True) + self.lineEdit_outputFile.setEnabled(True) + self.pushButton_addComponent.setEnabled(True) + self.pushButton_removeComponent.setEnabled(True) + self.pushButton_listMoveDown.setEnabled(True) + self.pushButton_listMoveUp.setEnabled(True) + self.menuButton_newProject.setEnabled(True) + self.menuButton_openProject.setEnabled(True) + self.listWidget_componentList.setEnabled(True) + self.progressLabel.setHidden(True) self.drawPreview(True) @QtCore.pyqtSlot(int) def progressBarUpdated(self, value): - self.window.progressBar_createVideo.setValue(value) + self.progressBar_createVideo.setValue(value) @QtCore.pyqtSlot(str) def progressBarSetText(self, value): if sys.platform == 'darwin': - self.window.progressLabel.setText(value) + self.progressLabel.setText(value) else: - self.window.progressBar_createVideo.setFormat(value) + self.progressBar_createVideo.setFormat(value) def updateResolution(self): - resIndex = int(self.window.comboBox_resolution.currentIndex()) + resIndex = int(self.comboBox_resolution.currentIndex()) res = Core.resolutions[resIndex].split('x') changed = res[0] != self.settings.value("outputWidth") self.settings.setValue('outputWidth', res[0]) @@ -750,7 +761,7 @@ class MainWindow(QtWidgets.QMainWindow): self.previewWindow.changePixmap(image) def showUndoStack(self): - dialog = QtWidgets.QDialog(self.window) + dialog = QtWidgets.QDialog(self) undoView = QtWidgets.QUndoView(self.undoStack) layout = QtWidgets.QVBoxLayout() layout.addWidget(undoView) @@ -761,8 +772,8 @@ class MainWindow(QtWidgets.QMainWindow): from textwrap import wrap from ..toolkit.ffmpeg import createFfmpegCommand command = createFfmpegCommand( - self.window.lineEdit_audioFile.text(), - self.window.lineEdit_outputFile.text(), + self.lineEdit_audioFile.text(), + self.lineEdit_outputFile.text(), self.core.selectedComponents ) command = " ".join(command) @@ -779,8 +790,8 @@ class MainWindow(QtWidgets.QMainWindow): def insertComponent(self, index): '''Triggered by Core to finish initializing a new component.''' - componentList = self.window.listWidget_componentList - stackedWidget = self.window.stackedWidget + componentList = self.listWidget_componentList + stackedWidget = self.stackedWidget componentList.insertItem( index, @@ -798,15 +809,15 @@ class MainWindow(QtWidgets.QMainWindow): return index def removeComponent(self): - componentList = self.window.listWidget_componentList + componentList = self.listWidget_componentList selected = componentList.selectedItems() if selected: action = RemoveComponent(self, selected) self.undoStack.push(action) def _removeComponent(self, index): - stackedWidget = self.window.stackedWidget - componentList = self.window.listWidget_componentList + stackedWidget = self.stackedWidget + componentList = self.listWidget_componentList stackedWidget.removeWidget(self.pages[index]) componentList.takeItem(index) self.core.removeComponent(index) @@ -817,7 +828,7 @@ class MainWindow(QtWidgets.QMainWindow): @disableWhenEncoding def moveComponent(self, change): '''Moves a component relatively from its current position''' - componentList = self.window.listWidget_componentList + componentList = self.listWidget_componentList tag = change if change == 'top': change = -componentList.currentRow() @@ -837,7 +848,7 @@ class MainWindow(QtWidgets.QMainWindow): Given a QPos, returns the component index under the mouse cursor or -1 if no component is there. ''' - componentList = self.window.listWidget_componentList + componentList = self.listWidget_componentList modelIndexes = [ componentList.model().index(i) @@ -859,7 +870,7 @@ class MainWindow(QtWidgets.QMainWindow): @disableWhenEncoding def dragComponent(self, event): '''Used as Qt drop event for the component listwidget''' - componentList = self.window.listWidget_componentList + componentList = self.listWidget_componentList mousePos = self.getComponentListMousePos(event.pos()) if mousePos > -1: change = (componentList.currentRow() - mousePos) * -1 @@ -868,25 +879,25 @@ class MainWindow(QtWidgets.QMainWindow): self.moveComponent(change) def changeComponentWidget(self): - selected = self.window.listWidget_componentList.selectedItems() + selected = self.listWidget_componentList.selectedItems() if selected: - index = self.window.listWidget_componentList.row(selected[0]) - self.window.stackedWidget.setCurrentIndex(index) + index = self.listWidget_componentList.row(selected[0]) + self.stackedWidget.setCurrentIndex(index) def openPresetManager(self): '''Preset manager for importing, exporting, renaming, deleting''' - self.presetManager.show() + self.presetManager.show_() def clear(self): '''Get a blank slate''' self.core.clearComponents() - self.window.listWidget_componentList.clear() + self.listWidget_componentList.clear() for widget in self.pages: - self.window.stackedWidget.removeWidget(widget) + self.stackedWidget.removeWidget(widget) self.pages = [] for field in ( - self.window.lineEdit_audioFile, - self.window.lineEdit_outputFile + self.lineEdit_audioFile, + self.lineEdit_outputFile ): with blockSignals(field): field.setText('') @@ -906,7 +917,7 @@ class MainWindow(QtWidgets.QMainWindow): def saveCurrentProject(self): if self.currentProject: - self.core.createProjectFile(self.currentProject, self.window) + self.core.createProjectFile(self.currentProject, self) try: os.remove(self.autosavePath) except FileNotFoundError: @@ -933,7 +944,7 @@ class MainWindow(QtWidgets.QMainWindow): def openSaveProjectDialog(self): filename, _ = QtWidgets.QFileDialog.getSaveFileName( - self.window, "Create Project File", + self, "Create Project File", self.settings.value("projectDir"), "Project Files (*.avp)") if not filename: @@ -943,13 +954,13 @@ class MainWindow(QtWidgets.QMainWindow): self.settings.setValue("projectDir", os.path.dirname(filename)) self.settings.setValue("currentProject", filename) self.currentProject = filename - self.core.createProjectFile(filename, self.window) + self.core.createProjectFile(filename, self) self.updateWindowTitle() @disableWhenEncoding def openOpenProjectDialog(self): filename, _ = QtWidgets.QFileDialog.getOpenFileName( - self.window, "Open Project File", + self, "Open Project File", self.settings.value("projectDir"), "Project Files (*.avp)") self.openProject(filename) @@ -973,7 +984,7 @@ class MainWindow(QtWidgets.QMainWindow): self.updateWindowTitle() def showMessage(self, **kwargs): - parent = kwargs['parent'] if 'parent' in kwargs else self.window + parent = kwargs['parent'] if 'parent' in kwargs else self msg = QtWidgets.QMessageBox(parent) msg.setModal(True) msg.setText(kwargs['msg']) @@ -995,8 +1006,8 @@ class MainWindow(QtWidgets.QMainWindow): @disableWhenEncoding def componentContextMenu(self, QPos): '''Appears when right-clicking the component list''' - componentList = self.window.listWidget_componentList - self.menu = QMenu() + componentList = self.listWidget_componentList + self.menu = QtWidgets.QMenu() parentPosition = componentList.mapToGlobal(QtCore.QPoint(0, 0)) index = self.getComponentListMousePos(QPos) @@ -1013,7 +1024,7 @@ class MainWindow(QtWidgets.QMainWindow): presets = self.presetManager.presets[ str(self.core.selectedComponents[index]) ] - self.presetSubmenu = QMenu("Open Preset") + self.presetSubmenu = QtWidgets.QMenu("Open Preset") self.menu.addMenu(self.presetSubmenu) for version, presetName in presets: @@ -1033,7 +1044,7 @@ class MainWindow(QtWidgets.QMainWindow): self.menu.addSeparator() # "Add Component" submenu - self.submenu = QMenu("Add") + self.submenu = QtWidgets.QMenu("Add") self.menu.addMenu(self.submenu) insertCompAtTop = self.settings.value("pref_insertCompAtTop") for i, comp in enumerate(self.core.modules): diff --git a/src/gui/presetmanager.py b/src/gui/presetmanager.py index 1e47a7f..9cf95b4 100644 --- a/src/gui/presetmanager.py +++ b/src/gui/presetmanager.py @@ -2,7 +2,7 @@ Preset manager object handles all interactions with presets, including the context menu accessed from MainWindow. ''' -from PyQt5 import QtCore, QtWidgets +from PyQt5 import QtCore, QtWidgets, uic import string import os import logging @@ -16,8 +16,10 @@ log = logging.getLogger('AVP.Gui.PresetManager') class PresetManager(QtWidgets.QDialog): - def __init__(self, window, parent): - super().__init__(parent.window) + def __init__(self, parent): + super().__init__() + uic.loadUi( + os.path.join(Core.wd, 'gui', 'presetmanager.ui'), self) self.parent = parent self.core = parent.core self.settings = parent.settings @@ -32,32 +34,31 @@ class PresetManager(QtWidgets.QDialog): # window self.lastFilter = '*' self.presetRows = [] # list of (comp, vers, name) tuples - self.window = window - self.window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) + self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) # connect button signals - self.window.pushButton_delete.clicked.connect( + self.pushButton_delete.clicked.connect( self.openDeletePresetDialog ) - self.window.pushButton_rename.clicked.connect( + self.pushButton_rename.clicked.connect( self.openRenamePresetDialog ) - self.window.pushButton_import.clicked.connect( + self.pushButton_import.clicked.connect( self.openImportDialog ) - self.window.pushButton_export.clicked.connect( + self.pushButton_export.clicked.connect( self.openExportDialog ) - self.window.pushButton_close.clicked.connect( - self.window.close + self.pushButton_close.clicked.connect( + self.close ) # create filter box and preset list self.drawFilterList() - self.window.comboBox_filter.currentIndexChanged.connect( + self.comboBox_filter.currentIndexChanged.connect( lambda: self.drawPresetList( - self.window.comboBox_filter.currentText(), - self.window.lineEdit_search.text() + self.comboBox_filter.currentText(), + self.lineEdit_search.text() ) ) @@ -65,23 +66,24 @@ class PresetManager(QtWidgets.QDialog): self.autocomplete = QtCore.QStringListModel() completer = QtWidgets.QCompleter() completer.setModel(self.autocomplete) - self.window.lineEdit_search.setCompleter(completer) - self.window.lineEdit_search.textChanged.connect( + self.lineEdit_search.setCompleter(completer) + self.lineEdit_search.textChanged.connect( lambda: self.drawPresetList( - self.window.comboBox_filter.currentText(), - self.window.lineEdit_search.text() + self.comboBox_filter.currentText(), + self.lineEdit_search.text() ) ) self.drawPresetList('*') - def show(self): + def show_(self): '''Open a new preset manager window from the mainwindow''' self.findPresets() self.drawFilterList() self.drawPresetList('*') - self.window.show() + self.show() def findPresets(self): + log.debug("Searching %s for presets", self.presetDir) parseList = [] for dirpath, dirnames, filenames in os.walk(self.presetDir): # anything without a subdirectory must be a preset folder @@ -106,7 +108,7 @@ class PresetManager(QtWidgets.QDialog): } def drawPresetList(self, compFilter=None, presetFilter=''): - self.window.listWidget_presets.clear() + self.listWidget_presets.clear() if compFilter: self.lastFilter = str(compFilter) else: @@ -118,7 +120,7 @@ class PresetManager(QtWidgets.QDialog): continue for vers, preset in presets: if not presetFilter or presetFilter in preset: - self.window.listWidget_presets.addItem( + self.listWidget_presets.addItem( '%s: %s' % (component, preset) ) self.presetRows.append((component, vers, preset)) @@ -127,22 +129,21 @@ class PresetManager(QtWidgets.QDialog): self.autocomplete.setStringList(presetNames) def drawFilterList(self): - self.window.comboBox_filter.clear() - self.window.comboBox_filter.addItem('*') + self.comboBox_filter.clear() + self.comboBox_filter.addItem('*') for component in self.presets: - self.window.comboBox_filter.addItem(component) + self.comboBox_filter.addItem(component) def clearPreset(self, compI=None): '''Functions on mainwindow level from the context menu''' - compI = self.parent.window.listWidget_componentList.currentRow() + compI = self.parent.listWidget_componentList.currentRow() action = ClearPreset(self.parent, compI) self.parent.undoStack.push(action) def openSavePresetDialog(self): '''Functions on mainwindow level from the context menu''' - window = self.parent.window selectedComponents = self.core.selectedComponents - componentList = self.parent.window.listWidget_componentList + componentList = self.parent.listWidget_componentList if componentList.currentRow() == -1: return @@ -150,7 +151,7 @@ class PresetManager(QtWidgets.QDialog): index = componentList.currentRow() currentPreset = selectedComponents[index].currentPreset newName, OK = QtWidgets.QInputDialog.getText( - self.parent.window, + self.parent, 'Audio Visualizer', 'New Preset Name:', QtWidgets.QLineEdit.Normal, @@ -158,7 +159,7 @@ class PresetManager(QtWidgets.QDialog): ) if OK: if badName(newName): - self.warnMessage(self.parent.window) + self.warnMessage(self.parent) continue if newName: if index != -1: @@ -170,7 +171,7 @@ class PresetManager(QtWidgets.QDialog): vers = selectedComponents[index].version self.createNewPreset( componentName, vers, newName, - saveValueStore, window=self.parent.window) + saveValueStore, window=self.parent) self.findPresets() self.drawPresetList() self.openPreset(newName, index) @@ -185,8 +186,7 @@ class PresetManager(QtWidgets.QDialog): def presetExists(self, path, **kwargs): if os.path.exists(path): - window = self.window \ - if 'window' not in kwargs else kwargs['window'] + window = kwargs.get("window", self) ch = self.parent.showMessage( msg="%s already exists! Overwrite it?" % os.path.basename(path), @@ -200,7 +200,7 @@ class PresetManager(QtWidgets.QDialog): return False def openPreset(self, presetName, compPos=None): - componentList = self.parent.window.listWidget_componentList + componentList = self.parent.listWidget_componentList index = compPos if compPos is not None else componentList.currentRow() if index == -1: return @@ -228,7 +228,7 @@ class PresetManager(QtWidgets.QDialog): msg='Really delete %s?' % name, showCancel=True, icon='Warning', - parent=self.window + parent=self ) if not ch: return @@ -242,15 +242,15 @@ class PresetManager(QtWidgets.QDialog): self.parent.showMessage( msg='Preset names must contain only letters, ' 'numbers, and spaces.', - parent=window if window else self.window) + parent=window if window else self) def getPresetRow(self): - row = self.window.listWidget_presets.currentRow() + row = self.listWidget_presets.currentRow() if row > -1: return row # check if component selected in MainWindow has preset loaded - componentList = self.parent.window.listWidget_componentList + componentList = self.parent.listWidget_componentList compIndex = componentList.currentRow() if compIndex == -1: return compIndex @@ -273,14 +273,14 @@ class PresetManager(QtWidgets.QDialog): return index def openRenamePresetDialog(self): - presetList = self.window.listWidget_presets + presetList = self.listWidget_presets index = self.getPresetRow() if index == -1: return while True: newName, OK = QtWidgets.QInputDialog.getText( - self.window, + self, 'Preset Manager', 'Rename Preset:', QtWidgets.QLineEdit.Normal, @@ -319,7 +319,7 @@ class PresetManager(QtWidgets.QDialog): def openImportDialog(self): filename, _ = QtWidgets.QFileDialog.getOpenFileName( - self.window, "Import Preset File", + self, "Import Preset File", self.settings.value("presetDir"), "Preset Files (*.avl)") if filename: @@ -345,7 +345,7 @@ class PresetManager(QtWidgets.QDialog): if index == -1: return filename, _ = QtWidgets.QFileDialog.getSaveFileName( - self.window, "Export Preset", + self, "Export Preset", self.settings.value("presetDir"), "Preset Files (*.avl)") if filename: @@ -353,9 +353,9 @@ class PresetManager(QtWidgets.QDialog): if not self.core.exportPreset(filename, comp, vers, name): self.parent.showMessage( msg='Couldn\'t export %s.' % filename, - parent=self.window + parent=self ) self.settings.setValue("presetDir", os.path.dirname(filename)) def clearPresetListSelection(self): - self.window.listWidget_presets.setCurrentRow(-1) + self.listWidget_presets.setCurrentRow(-1) diff --git a/src/gui/preview_win.py b/src/gui/preview_win.py index 426ff66..d910456 100644 --- a/src/gui/preview_win.py +++ b/src/gui/preview_win.py @@ -37,7 +37,7 @@ class PreviewWindow(QtWidgets.QLabel): if self.parent.encoding: return - i = self.parent.window.listWidget_componentList.currentRow() + i = self.parent.listWidget_componentList.currentRow() if i >= 0: component = self.parent.core.selectedComponents[i] if not hasattr(component, 'previewClickEvent'): diff --git a/src/main.py b/src/main.py index ec4b8bc..709e5e7 100644 --- a/src/main.py +++ b/src/main.py @@ -42,21 +42,9 @@ def main(): if mode == 'GUI': from .gui.mainwindow import MainWindow - window = uic.loadUi(os.path.join(wd, "gui", "mainwindow.ui")) - desc = QtWidgets.QDesktopWidget() - dpi = desc.physicalDpiX() - log.info("Detected screen DPI: %s", dpi) - - window.resize( - int(window.width() * - (dpi / 96)), - int(window.height() * - (dpi / 96)) - ) - - main = MainWindow(window, proj) - log.debug("Finished creating main window") - window.raise_() + mainWindow = MainWindow(proj) + log.debug("Finished creating MainWindow") + mainWindow.raise_() sys.exit(app.exec_()) -- cgit v1.2.3