aboutsummaryrefslogtreecommitdiff
path: root/src/components/video.py
diff options
context:
space:
mode:
authorBrianna2017-08-03 21:16:51 -0400
committerGitHub2017-08-03 21:16:51 -0400
commit20905230fe6df814fdaa6173b266dd7de51bd269 (patch)
tree3a95b7e752fcaacaf820229fe99a858e69db022d /src/components/video.py
parent6f8f178778c63f10b3bda42507c7d44f98884fcd (diff)
parentd04ddba484f1c8993971f79d5ee14b0cc7a512fb (diff)
new Waveform & Spectrum component + relative coords for everything
NOTE: projects and presets saved from old versions will not load correctly anymore
Diffstat (limited to 'src/components/video.py')
-rw-r--r--src/components/video.py239
1 files changed, 68 insertions, 171 deletions
diff --git a/src/components/video.py b/src/components/video.py
index b2487c1..b6bdd52 100644
--- a/src/components/video.py
+++ b/src/components/video.py
@@ -1,103 +1,13 @@
-from PIL import Image, ImageDraw
+from PIL import Image
from PyQt5 import QtGui, QtCore, QtWidgets
import os
import math
import subprocess
-import signal
-import threading
-from queue import PriorityQueue
-from component import Component, ComponentError
-from toolkit.frame import BlankFrame
-from toolkit.ffmpeg import testAudioStream
-from toolkit import openPipe, checkOutput
-
-
-class Video:
- '''Opens a pipe to ffmpeg and stores a buffer of raw video frames.'''
-
- # error from the thread used to fill the buffer
- threadError = None
-
- def __init__(self, **kwargs):
- mandatoryArgs = [
- 'ffmpeg', # path to ffmpeg, usually self.core.FFMPEG_BIN
- 'videoPath',
- 'width',
- 'height',
- 'scale', # percentage scale
- 'frameRate', # frames per second
- 'chunkSize', # number of bytes in one frame
- 'parent', # mainwindow object
- 'component', # component object
- ]
- for arg in mandatoryArgs:
- setattr(self, arg, kwargs[arg])
-
- self.frameNo = -1
- self.currentFrame = 'None'
- if 'loopVideo' in kwargs and kwargs['loopVideo']:
- self.loopValue = '-1'
- else:
- self.loopValue = '0'
- self.command = [
- self.ffmpeg,
- '-thread_queue_size', '512',
- '-r', str(self.frameRate),
- '-stream_loop', self.loopValue,
- '-i', self.videoPath,
- '-f', 'image2pipe',
- '-pix_fmt', 'rgba',
- '-filter_complex', '[0:v] scale=%s:%s' % scale(
- self.scale, self.width, self.height, str),
- '-vcodec', 'rawvideo', '-',
- ]
-
- self.frameBuffer = PriorityQueue()
- self.frameBuffer.maxsize = self.frameRate
- self.finishedFrames = {}
-
- self.thread = threading.Thread(
- target=self.fillBuffer,
- name='Video Frame-Fetcher'
- )
- self.thread.daemon = True
- self.thread.start()
-
- def frame(self, num):
- while True:
- if num in self.finishedFrames:
- image = self.finishedFrames.pop(num)
- return finalizeFrame(
- self.component, image, self.width, self.height)
-
- i, image = self.frameBuffer.get()
- self.finishedFrames[i] = image
- self.frameBuffer.task_done()
-
- def fillBuffer(self):
- self.pipe = openPipe(
- self.command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
- stderr=subprocess.DEVNULL, bufsize=10**8
- )
- while True:
- if self.parent.canceled:
- break
- self.frameNo += 1
-
- # If we run out of frames, use the last good frame and loop.
- try:
- if len(self.currentFrame) == 0:
- self.frameBuffer.put((self.frameNo-1, self.lastFrame))
- continue
- except AttributeError:
- Video.threadError = ComponentError(self.component, 'video')
- break
-
- self.currentFrame = self.pipe.stdout.read(self.chunkSize)
- if len(self.currentFrame) != 0:
- self.frameBuffer.put((self.frameNo, self.currentFrame))
- self.lastFrame = self.currentFrame
+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):
@@ -106,30 +16,30 @@ 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(
- {
- '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',
- }
- )
+ 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():
@@ -157,8 +67,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):
@@ -182,22 +90,21 @@ class Component(Component):
def preFrameRender(self, **kwargs):
super().preFrameRender(**kwargs)
self.updateChunksize()
- self.video = Video(
- ffmpeg=self.core.FFMPEG_BIN, videoPath=self.videoPath,
+ self.video = FfmpegVideo(
+ inputPath=self.videoPath, filter_=self.makeFfmpegFilter(),
width=self.width, height=self.height, chunkSize=self.chunkSize,
frameRate=int(self.settings.value("outputFrameRate")),
parent=self.parent, loopVideo=self.loopVideo,
- component=self, scale=self.scale
+ component=self
) if os.path.exists(self.videoPath) else None
def frameRender(self, frameNo):
- if Video.threadError is not None:
- raise Video.threadError
- return self.video.frame(frameNo)
+ if FfmpegVideo.threadError is not None:
+ raise FfmpegVideo.threadError
+ return self.finalizeFrame(self.video.frame(frameNo))
def postFrameRender(self):
- self.video.pipe.stdout.close()
- self.video.pipe.send_signal(signal.SIGINT)
+ closePipe(self.video.pipe)
def pickVideo(self):
imgDir = self.settings.value("componentDir", os.path.expanduser("~"))
@@ -220,23 +127,30 @@ class Component(Component):
'-i', self.videoPath,
'-f', 'image2pipe',
'-pix_fmt', 'rgba',
- '-filter_complex', '[0:v] scale=%s:%s' % scale(
- self.scale, width, height, str),
- '-vcodec', 'rawvideo', '-',
- '-ss', '90',
- '-vframes', '1',
]
+ command.extend(self.makeFfmpegFilter())
+ command.extend([
+ '-codec:v', '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)
- pipe.stdout.close()
- pipe.send_signal(signal.SIGINT)
+ closePipe(pipe)
- frame = finalizeFrame(self, byteFrame, width, height)
+ frame = self.finalizeFrame(byteFrame)
return frame
+ def makeFfmpegFilter(self):
+ return [
+ '-filter_complex',
+ '[0:v] scale=%s:%s' % scale(
+ self.scale, self.width, self.height, str),
+ ]
+
def updateChunksize(self):
if self.scale != 100 and not self.distort:
width, height = scale(self.scale, self.width, self.height, int)
@@ -268,44 +182,27 @@ class Component(Component):
print('Load a video:\n path=/filepath/to/video.mp4')
print('Using audio:\n path=/filepath/to/video.mp4 audio')
-
-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):
- try:
- if self.distort:
- image = Image.frombytes(
- 'RGBA',
- (width, height),
- imageData)
+ def finalizeFrame(self, imageData):
+ try:
+ if self.distort:
+ image = Image.frombytes(
+ 'RGBA',
+ (self.width, self.height),
+ imageData)
+ else:
+ image = Image.frombytes(
+ 'RGBA',
+ scale(self.scale, self.width, self.height, int),
+ imageData)
+ self._image = image
+ except ValueError:
+ # use last good frame
+ image = self._image
+
+ if self.scale != 100 \
+ or self.xPosition != 0 or self.yPosition != 0:
+ frame = BlankFrame(self.width, self.height)
+ frame.paste(image, box=(self.xPosition, self.yPosition))
else:
- image = Image.frombytes(
- 'RGBA',
- scale(self.scale, width, height, int),
- imageData)
-
- except ValueError:
- print(
- '### BAD VIDEO SELECTED ###\n'
- 'Video will not export with these settings'
- )
- self.badVideo = True
- return BlankFrame(width, height)
-
- if self.scale != 100 \
- or self.xPosition != 0 or self.yPosition != 0:
- frame = BlankFrame(width, height)
- frame.paste(image, box=(self.xPosition, self.yPosition))
- else:
- frame = image
- self.badVideo = False
- return frame
+ frame = image
+ return frame