waveform component is working, preview is glitchy

This commit is contained in:
tassaron 2017-07-29 20:27:46 -04:00
parent c1457b6dad
commit 1297af61c9
8 changed files with 256 additions and 87 deletions

View File

@ -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

View File

@ -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',
])

View File

@ -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

View File

@ -226,9 +226,31 @@
</spacer>
</item>
<item>
<widget class="QCheckBox" name="checkBox_mirror">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Mirror</string>
<string>Opacity</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinBox_opacity">
<property name="buttonSymbols">
<enum>QAbstractSpinBox::UpDownArrows</enum>
</property>
<property name="suffix">
<string>%</string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>400</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
@ -263,6 +285,75 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QCheckBox" name="checkBox_compress">
<property name="text">
<string>Compress</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_mono">
<property name="text">
<string>Mono</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_mirror">
<property name="text">
<string>Mirror</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Amplitude</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBox_amplitude">
<item>
<property name="text">
<string>Linear</string>
</property>
</item>
<item>
<property name="text">
<string>Logarithmic</string>
</property>
</item>
<item>
<property name="text">
<string>Square root</string>
</property>
</item>
<item>
<property name="text">
<string>Cubic root</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">

View File

@ -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:

View File

@ -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]

View File

@ -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):

View File

@ -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()