added framebuffer to keep frames in order

NOT WORKING: end of video detection
This commit is contained in:
tassaron 2017-06-06 01:40:26 -04:00
parent be18deece5
commit 47509ae2b1
2 changed files with 76 additions and 81 deletions

View File

@ -1,15 +1,56 @@
from PIL import Image, ImageDraw from PIL import Image, ImageDraw
from PyQt4 import uic, QtGui, QtCore from PyQt4 import uic, QtGui, QtCore
import os, subprocess import os, subprocess, threading
import numpy from queue import PriorityQueue
from . import __base__ from . import __base__
class Video:
'''Video Component Frame-Fetcher'''
def __init__(self, ffmpeg, videoPath, width, height, frameRate, chunkSize, parent):
self.parent = parent
self.chunkSize = chunkSize
self.size = (width, height)
self.frameNo = -1
self.command = [
ffmpeg,
'-thread_queue_size', '512',
'-r', frameRate,
'-i', videoPath,
'-f', 'image2pipe',
'-pix_fmt', 'rgba',
'-filter:v', 'scale='+str(width)+':'+str(height),
'-vcodec', 'rawvideo', '-',
]
self.frameBuffer = PriorityQueue()
self.frameBuffer.maxsize = int(frameRate)
self.finishedFrames = {}
self.thread = threading.Thread(target=self.fillBuffer, name=self.__doc__)
self.thread.daemon = True
self.thread.start()
def frame(self, num):
while True:
if num in self.finishedFrames:
image = self.finishedFrames.pop(num)
return Image.frombytes('RGBA', self.size, image)
i, image = self.frameBuffer.get()
self.finishedFrames[i] = image
self.frameBuffer.task_done()
def fillBuffer(self):
self.pipe = subprocess.Popen(self.command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, bufsize=10**8)
while True:
if self.parent.canceled:
break
self.frameNo += 1
image = self.pipe.stdout.read(self.chunkSize)
print('creating frame #%s' % str(self.frameNo))
self.frameBuffer.put((self.frameNo, image))
class Component(__base__.Component): class Component(__base__.Component):
'''Video''' '''Video'''
def __init__(self):
super().__init__()
self.working = False
def widget(self, parent): def widget(self, parent):
self.parent = parent self.parent = parent
self.settings = parent.settings self.settings = parent.settings
@ -29,41 +70,22 @@ class Component(__base__.Component):
self.parent.drawPreview() self.parent.drawPreview()
def previewRender(self, previewWorker): def previewRender(self, previewWorker):
self.width = int(previewWorker.core.settings.value('outputWidth')) width = int(previewWorker.core.settings.value('outputWidth'))
self.height = int(previewWorker.core.settings.value('outputHeight')) height = int(previewWorker.core.settings.value('outputHeight'))
frame1 = self.getPreviewFrame() self.chunkSize = 4*width*height
if not hasattr(self, 'staticFrame') or not self.working and frame1: return self.getPreviewFrame(width, height)
frame = Image.new("RGBA", (self.width, self.height), (0, 0, 0, 0))
if frame1:
im = Image.open(frame1)
frame.paste(im)
if not self.working:
self.staticFrame = frame
return self.staticFrame
def preFrameRender(self, **kwargs): def preFrameRender(self, **kwargs):
super().preFrameRender(**kwargs) super().preFrameRender(**kwargs)
self.width = int(self.worker.core.settings.value('outputWidth')) width = int(self.worker.core.settings.value('outputWidth'))
self.height = int(self.worker.core.settings.value('outputHeight')) height = int(self.worker.core.settings.value('outputHeight'))
self.working = True self.chunkSize = 4*width*height
self.frames = self.getVideoFrames() self.video = Video(self.parent.core.FFMPEG_BIN, self.videoPath,
width, height, self.settings.value("outputFrameRate"),
self.chunkSize, self.parent)
def frameRender(self, moduleNo, arrayNo, frameNo): def frameRender(self, moduleNo, arrayNo, frameNo):
# don't make a new frame return self.video.frame(frameNo)
if not self.working:
return self.staticFrame
byteFrame = self.frames.stdout.read(self.chunkSize)
if len(byteFrame) == 0:
self.working = False
self.frames.kill()
return self.staticFrame
# make a new frame
width = self.width
height = self.height
image = Image.frombytes('RGBA', (width, height), byteFrame)
self.staticFrame = image
return self.staticFrame
def loadPreset(self, pr): def loadPreset(self, pr):
self.page.lineEdit_video.setText(pr['video']) self.page.lineEdit_video.setText(pr['video'])
@ -82,51 +104,21 @@ class Component(__base__.Component):
self.page.lineEdit_video.setText(filename) self.page.lineEdit_video.setText(filename)
self.update() self.update()
def getPreviewFrame(self): def getPreviewFrame(self, width, height):
if not self.videoPath:
return
name = os.path.basename(self.videoPath).split('.', 1)[0]
filename = 'preview%s.jpg' % name
if os.path.exists(os.path.join(self.parent.core.tempDir, filename)):
# no, we don't need a new preview frame
return False
# get a preview frame
subprocess.call( \
'%s -i "%s" -y %s %s "%s"' % ( \
self.parent.core.FFMPEG_BIN,
self.videoPath,
'-ss 10 -vframes 1',
'-filter:v scale='+str(self.width)+':'+str(self.height),
os.path.join(self.parent.core.tempDir, filename)
),
shell=True
)
print('### Got Preview Frame From %s ###' % name)
return os.path.join(self.parent.core.tempDir, filename)
def getVideoFrames(self):
if not self.videoPath:
return
command = [ command = [
self.parent.core.FFMPEG_BIN, self.parent.core.FFMPEG_BIN,
'-thread_queue_size', '512', '-thread_queue_size', '512',
'-i', self.videoPath, '-i', self.videoPath,
'-f', 'image2pipe', '-f', 'image2pipe',
'-pix_fmt', 'rgba', '-pix_fmt', 'rgba',
'-filter:v', 'scale='+str(self.width)+':'+str(self.height), '-filter:v', 'scale='+str(width)+':'+str(height),
'-vcodec', 'rawvideo', '-', '-vcodec', 'rawvideo', '-',
'-ss', '90',
'-vframes', '1',
] ]
pipe = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, bufsize=10**8)
# pipe in video frames from ffmpeg byteFrame = pipe.stdout.read(self.chunkSize)
in_pipe = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, bufsize=10**8) image = Image.frombytes('RGBA', (width, height), byteFrame)
#width, height = self.realSize pipe.stdout.close()
self.chunkSize = 4*self.width*self.height pipe.kill()
return image
return in_pipe
def resize(self, im):
if im.size != (self.width, self.height):
im = im.resize((self.width, self.height), Image.ANTIALIAS)
return im

13
main.py
View File

@ -1,4 +1,4 @@
import sys, io, os, shutil, atexit, string, signal, filecmp import sys, io, os, shutil, atexit, string, signal, filecmp, time
from os.path import expanduser from os.path import expanduser
from queue import Queue from queue import Queue
from importlib import import_module from importlib import import_module
@ -145,6 +145,7 @@ class Main(QtCore.QObject):
self.core = core.Core() self.core = core.Core()
self.pages = [] self.pages = []
self.selectedComponents = [] self.selectedComponents = []
self.lastAutosave = time.time()
# create data directory, load/create settings # create data directory, load/create settings
self.dataDir = QDesktopServices.storageLocation(QDesktopServices.DataLocation) self.dataDir = QDesktopServices.storageLocation(QDesktopServices.DataLocation)
@ -235,9 +236,11 @@ class Main(QtCore.QObject):
self.autosave() self.autosave()
def autosave(self): def autosave(self):
if os.path.exists(self.autosavePath): if time.time() - self.lastAutosave >= 1.0:
os.remove(self.autosavePath) if os.path.exists(self.autosavePath):
self.createProjectFile(self.autosavePath) os.remove(self.autosavePath)
self.createProjectFile(self.autosavePath)
self.lastAutosave = time.time()
def openInputFileDialog(self): def openInputFileDialog(self):
inputDir = self.settings.value("inputDir", expanduser("~")) inputDir = self.settings.value("inputDir", expanduser("~"))
@ -423,7 +426,7 @@ class Main(QtCore.QObject):
def moveComponentDown(self): def moveComponentDown(self):
row = self.window.listWidget_componentList.currentRow() row = self.window.listWidget_componentList.currentRow()
if row < len(self.pages) + 1: if row != -1 and row < len(self.pages)+1:
module = self.selectedComponents[row] module = self.selectedComponents[row]
self.selectedComponents.pop(row) self.selectedComponents.pop(row)
self.selectedComponents.insert(row + 1,module) self.selectedComponents.insert(row + 1,module)