aboutsummaryrefslogtreecommitdiff
path: root/src/components/waveform.py
diff options
context:
space:
mode:
authortassaron2017-07-29 13:08:28 -0400
committertassaron2017-07-29 13:08:28 -0400
commitc1457b6dad4640b17679dd802e372bd46a13d2a5 (patch)
tree368ea39f9383cd1e0e779a3860b3691fc3e6b68c /src/components/waveform.py
parent6f8f178778c63f10b3bda42507c7d44f98884fcd (diff)
starting work on Waveform component
split Video class out of Video component for reuse in Waveform
Diffstat (limited to 'src/components/waveform.py')
-rw-r--r--src/components/waveform.py139
1 files changed, 139 insertions, 0 deletions
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