2017-07-29 13:08:28 -04:00
|
|
|
from PIL import Image
|
|
|
|
from PyQt5 import QtGui, QtCore, QtWidgets
|
|
|
|
from PyQt5.QtGui import QColor
|
|
|
|
import os
|
|
|
|
import math
|
|
|
|
import subprocess
|
2017-08-10 16:04:41 -04:00
|
|
|
import logging
|
2017-07-29 13:08:28 -04:00
|
|
|
|
2017-07-29 20:27:46 -04:00
|
|
|
from component import Component
|
|
|
|
from toolkit.frame import BlankFrame, scale
|
2017-08-01 17:57:39 -04:00
|
|
|
from toolkit import checkOutput
|
2017-07-30 13:04:02 -04:00
|
|
|
from toolkit.ffmpeg import (
|
|
|
|
openPipe, closePipe, getAudioDuration, FfmpegVideo, exampleSound
|
|
|
|
)
|
2017-07-29 13:08:28 -04:00
|
|
|
|
|
|
|
|
2017-08-10 16:04:41 -04:00
|
|
|
log = logging.getLogger('AVP.Components.Waveform')
|
|
|
|
|
|
|
|
|
2017-07-29 13:08:28 -04:00
|
|
|
class Component(Component):
|
|
|
|
name = 'Waveform'
|
|
|
|
version = '1.0.0'
|
|
|
|
|
|
|
|
def widget(self, *args):
|
|
|
|
super().widget(*args)
|
2017-08-03 00:44:46 -04:00
|
|
|
self._image = BlankFrame(self.width, self.height)
|
2017-07-29 13:08:28 -04:00
|
|
|
|
2017-08-01 17:57:39 -04:00
|
|
|
self.page.lineEdit_color.setText('255,255,255')
|
2017-07-29 20:27:46 -04:00
|
|
|
|
|
|
|
if hasattr(self.parent, 'window'):
|
|
|
|
self.parent.window.lineEdit_audioFile.textChanged.connect(
|
|
|
|
self.update
|
|
|
|
)
|
2017-07-29 13:08:28 -04:00
|
|
|
|
2017-08-03 12:16:57 -04:00
|
|
|
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',
|
|
|
|
])
|
2017-07-29 13:08:28 -04:00
|
|
|
|
|
|
|
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()
|
2017-07-29 20:27:46 -04:00
|
|
|
w, h = scale(self.scale, self.width, self.height, str)
|
2017-07-29 13:08:28 -04:00
|
|
|
self.video = FfmpegVideo(
|
|
|
|
inputPath=self.audioFile,
|
2017-07-29 20:27:46 -04:00
|
|
|
filter_=self.makeFfmpegFilter(),
|
|
|
|
width=w, height=h,
|
2017-07-29 13:08:28 -04:00
|
|
|
chunkSize=self.chunkSize,
|
|
|
|
frameRate=int(self.settings.value("outputFrameRate")),
|
2017-07-29 23:45:37 -04:00
|
|
|
parent=self.parent, component=self, debug=True,
|
2017-07-29 13:08:28 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
def frameRender(self, frameNo):
|
|
|
|
if FfmpegVideo.threadError is not None:
|
|
|
|
raise FfmpegVideo.threadError
|
2017-07-29 20:27:46 -04:00
|
|
|
return self.finalizeFrame(self.video.frame(frameNo))
|
2017-07-29 13:08:28 -04:00
|
|
|
|
|
|
|
def postFrameRender(self):
|
|
|
|
closePipe(self.video.pipe)
|
|
|
|
|
|
|
|
def getPreviewFrame(self, width, height):
|
2017-07-29 23:45:37 -04:00
|
|
|
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
|
2017-07-30 13:04:02 -04:00
|
|
|
if startPt + 3 > duration:
|
|
|
|
startPt += startPt - 3
|
2017-07-29 13:08:28 -04:00
|
|
|
|
|
|
|
command = [
|
|
|
|
self.core.FFMPEG_BIN,
|
|
|
|
'-thread_queue_size', '512',
|
2017-07-29 20:27:46 -04:00
|
|
|
'-r', self.settings.value("outputFrameRate"),
|
|
|
|
'-ss', "{0:.3f}".format(startPt),
|
2017-07-29 23:45:37 -04:00
|
|
|
'-i',
|
|
|
|
os.path.join(self.core.wd, 'background.png')
|
|
|
|
if genericPreview else inputFile,
|
2017-07-29 13:08:28 -04:00
|
|
|
'-f', 'image2pipe',
|
|
|
|
'-pix_fmt', 'rgba',
|
|
|
|
]
|
2017-07-29 20:27:46 -04:00
|
|
|
command.extend(self.makeFfmpegFilter(preview=True, startPt=startPt))
|
2017-07-29 13:08:28 -04:00
|
|
|
command.extend([
|
2017-07-29 20:27:46 -04:00
|
|
|
'-an',
|
|
|
|
'-s:v', '%sx%s' % scale(self.scale, self.width, self.height, str),
|
|
|
|
'-codec:v', 'rawvideo', '-',
|
2017-07-29 13:08:28 -04:00
|
|
|
'-frames:v', '1',
|
|
|
|
])
|
2017-08-10 16:04:41 -04:00
|
|
|
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
|
|
|
|
)
|
2017-07-29 13:08:28 -04:00
|
|
|
byteFrame = pipe.stdout.read(self.chunkSize)
|
|
|
|
closePipe(pipe)
|
|
|
|
|
2017-07-29 20:27:46 -04:00
|
|
|
frame = self.finalizeFrame(byteFrame)
|
2017-07-29 13:08:28 -04:00
|
|
|
return frame
|
|
|
|
|
2017-07-29 20:27:46 -04:00
|
|
|
def makeFfmpegFilter(self, preview=False, startPt=0):
|
2017-07-29 13:08:28 -04:00
|
|
|
w, h = scale(self.scale, self.width, self.height, str)
|
2017-07-29 20:27:46 -04:00
|
|
|
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)
|
2017-07-29 23:45:37 -04:00
|
|
|
genericPreview = self.settings.value("pref_genericPreview")
|
2017-07-30 13:04:02 -04:00
|
|
|
if self.mode < 3:
|
2017-08-14 14:28:30 -04:00
|
|
|
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,
|
|
|
|
)
|
2017-07-30 13:04:02 -04:00
|
|
|
)
|
|
|
|
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'
|
|
|
|
)
|
|
|
|
)
|
2017-07-29 20:27:46 -04:00
|
|
|
|
2017-08-14 14:28:30 -04:00
|
|
|
baselineHeight = int(self.height * (4 / 1080))
|
2017-07-29 13:08:28 -04:00
|
|
|
return [
|
|
|
|
'-filter_complex',
|
2017-07-29 23:45:37 -04:00
|
|
|
'%s%s%s'
|
2017-07-30 13:04:02 -04:00
|
|
|
'%s%s%s [v1]; '
|
|
|
|
'[v1] scale=%s:%s%s [v]' % (
|
2017-08-14 14:28:30 -04:00
|
|
|
exampleSound('wave', extra='')
|
|
|
|
if preview and genericPreview else '[0:a] ',
|
2017-07-30 13:04:02 -04:00
|
|
|
'compand=gain=4,' if self.compress else '',
|
|
|
|
'aformat=channel_layouts=mono,'
|
|
|
|
if self.mono and self.mode < 3 else '',
|
|
|
|
filter_,
|
2017-08-14 14:28:30 -04:00
|
|
|
', drawbox=x=(iw-w)/2:y=(ih-h)/2:w=iw:h=%s:color=%s@%s' % (
|
|
|
|
baselineHeight, hexcolor, opacity,
|
2017-07-29 20:27:46 -04:00
|
|
|
) if self.mode < 2 else '',
|
|
|
|
', hflip' if self.mirror else'',
|
|
|
|
w, h,
|
2017-07-30 13:04:02 -04:00
|
|
|
', trim=duration=%s' % "{0:.3f}".format(startPt + 3)
|
2017-07-29 23:45:37 -04:00
|
|
|
if preview else '',
|
2017-07-29 13:08:28 -04:00
|
|
|
),
|
|
|
|
'-map', '[v]',
|
|
|
|
]
|
|
|
|
|
|
|
|
def updateChunksize(self):
|
2017-07-29 20:27:46 -04:00
|
|
|
width, height = scale(self.scale, self.width, self.height, int)
|
2017-07-29 13:08:28 -04:00
|
|
|
self.chunkSize = 4 * width * height
|
|
|
|
|
2017-07-29 20:27:46 -04:00
|
|
|
def finalizeFrame(self, imageData):
|
2017-08-03 00:44:46 -04:00
|
|
|
try:
|
|
|
|
image = Image.frombytes(
|
|
|
|
'RGBA',
|
|
|
|
scale(self.scale, self.width, self.height, int),
|
|
|
|
imageData
|
|
|
|
)
|
|
|
|
self._image = image
|
|
|
|
except ValueError:
|
|
|
|
image = self._image
|
2017-07-29 20:27:46 -04:00
|
|
|
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
|