140 lines
4.3 KiB
Python
140 lines
4.3 KiB
Python
![]() |
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
|