hydrus/include/HydrusVideoHandling.py

602 lines
18 KiB
Python
Raw Normal View History

2014-05-07 22:42:30 +00:00
#import numpy.core.multiarray # important this comes before cv!
import cv2
2013-02-19 00:11:43 +00:00
from flvlib import tags as flv_tags
2013-07-10 20:25:57 +00:00
import HydrusConstants as HC
2015-06-17 20:01:41 +00:00
import HydrusData
2014-05-28 21:03:24 +00:00
import HydrusExceptions
import HydrusImageHandling
import HydrusThreading
2014-04-30 21:31:40 +00:00
import matroska
2014-06-18 21:53:48 +00:00
import numpy
2013-07-10 20:25:57 +00:00
import os
2014-06-18 21:53:48 +00:00
import re
import subprocess
2015-09-23 21:21:02 +00:00
import sys
import tempfile
2013-02-19 00:11:43 +00:00
import traceback
2014-05-28 21:03:24 +00:00
import threading
import time
2013-02-19 00:11:43 +00:00
2015-11-04 22:30:28 +00:00
if HC.PLATFORM_LINUX: FFMPEG_PATH = os.path.join( HC.BIN_DIR, 'ffmpeg' )
elif HC.PLATFORM_OSX: FFMPEG_PATH = os.path.join( HC.BIN_DIR, 'ffmpeg' )
elif HC.PLATFORM_WINDOWS: FFMPEG_PATH = os.path.join( HC.BIN_DIR, 'ffmpeg.exe' )
2014-06-18 21:53:48 +00:00
2015-10-28 21:29:05 +00:00
if cv2.__version__.startswith( '2' ):
CAP_PROP_FRAME_COUNT = cv2.cv.CV_CAP_PROP_FRAME_COUNT
CAP_PROP_FPS = cv2.cv.CV_CAP_PROP_FPS
CAP_PROP_FRAME_WIDTH = cv2.cv.CV_CAP_PROP_FRAME_WIDTH
CAP_PROP_FRAME_HEIGHT = cv2.cv.CV_CAP_PROP_FRAME_HEIGHT
CAP_PROP_CONVERT_RGB = cv2.cv.CV_CAP_PROP_CONVERT_RGB
CAP_PROP_POS_FRAMES = cv2.cv.CV_CAP_PROP_POS_FRAMES
else:
CAP_PROP_FRAME_COUNT = cv2.CAP_PROP_FRAME_COUNT
CAP_PROP_FPS = cv2.CAP_PROP_FPS
CAP_PROP_FRAME_WIDTH = cv2.CAP_PROP_FRAME_WIDTH
CAP_PROP_FRAME_HEIGHT = cv2.CAP_PROP_FRAME_HEIGHT
CAP_PROP_CONVERT_RGB = cv2.CAP_PROP_CONVERT_RGB
CAP_PROP_POS_FRAMES = cv2.CAP_PROP_POS_FRAMES
2014-05-28 21:03:24 +00:00
def GetCVVideoProperties( path ):
capture = cv2.VideoCapture( path )
2015-10-28 21:29:05 +00:00
num_frames = int( capture.get( CAP_PROP_FRAME_COUNT ) )
2014-05-28 21:03:24 +00:00
2015-10-28 21:29:05 +00:00
fps = capture.get( CAP_PROP_FPS )
2014-05-28 21:03:24 +00:00
length_in_seconds = num_frames / fps
length_in_ms = int( length_in_seconds * 1000 )
duration = length_in_ms
2015-10-28 21:29:05 +00:00
width = int( capture.get( CAP_PROP_FRAME_WIDTH ) )
2014-05-28 21:03:24 +00:00
2015-10-28 21:29:05 +00:00
height = int( capture.get( CAP_PROP_FRAME_HEIGHT ) )
2014-05-28 21:03:24 +00:00
return ( ( width, height ), duration, num_frames )
2014-06-25 20:37:06 +00:00
def GetFFMPEGVideoProperties( path ):
info = Hydrusffmpeg_parse_infos( path )
( w, h ) = info[ 'video_size' ]
duration_in_s = info[ 'duration' ]
duration = int( duration_in_s * 1000 )
num_frames = info[ 'video_nframes' ]
return ( ( w, h ), duration, num_frames )
2013-08-07 22:25:18 +00:00
def GetFLVProperties( path ):
2013-02-19 00:11:43 +00:00
2013-08-14 20:21:49 +00:00
with open( path, 'rb' ) as f:
2013-02-19 00:11:43 +00:00
2013-08-07 22:25:18 +00:00
flv = flv_tags.FLV( f )
script_tag = None
for tag in flv.iter_tags():
2013-02-19 00:11:43 +00:00
2013-08-07 22:25:18 +00:00
if isinstance( tag, flv_tags.ScriptTag ):
script_tag = tag
break
2013-02-19 00:11:43 +00:00
2013-08-07 22:25:18 +00:00
width = 853
height = 480
duration = 0
num_frames = 0
2013-02-19 00:11:43 +00:00
2013-08-07 22:25:18 +00:00
if script_tag is not None:
tag_dict = script_tag.variable
# tag_dict can sometimes be a float?
# it is on the broken one I tried!
if 'width' in tag_dict: width = tag_dict[ 'width' ]
if 'height' in tag_dict: height = tag_dict[ 'height' ]
if 'duration' in tag_dict: duration = int( tag_dict[ 'duration' ] * 1000 )
if 'framerate' in tag_dict: num_frames = int( ( duration / 1000.0 ) * tag_dict[ 'framerate' ] )
2013-02-19 00:11:43 +00:00
2013-08-07 22:25:18 +00:00
return ( ( width, height ), duration, num_frames )
2013-02-19 00:11:43 +00:00
2013-07-10 20:25:57 +00:00
2014-05-28 21:03:24 +00:00
def GetVideoFrameDuration( path ):
cv_video = cv2.VideoCapture( path )
2013-08-14 20:21:49 +00:00
2015-10-28 21:29:05 +00:00
fps = cv_video.get( CAP_PROP_FPS )
2013-08-07 22:25:18 +00:00
2015-04-29 19:20:35 +00:00
if fps == 0: raise HydrusExceptions.CantRenderWithCVException()
2015-04-25 22:31:50 +00:00
2014-05-28 21:03:24 +00:00
return 1000.0 / fps
2014-04-30 21:31:40 +00:00
def GetMatroskaOrWebm( path ):
tags = matroska.parse( path )
ebml = tags[ 'EBML' ][0]
if ebml[ 'DocType' ][0] == 'matroska': return HC.VIDEO_MKV
elif ebml[ 'DocType' ][0] == 'webm': return HC.VIDEO_WEBM
raise Exception()
def GetMatroskaOrWebMProperties( path ):
tags = matroska.parse( path )
segment = tags['Segment'][0]
info = segment['Info'][0]
duration = int( ( info['Duration'][0] * info['TimecodeScale'][0] / 1e9 ) * 1000 )
tracks = segment['Tracks'][0]
trackentries = tracks['TrackEntry']
for trackentry in trackentries:
if 'Video' in trackentry:
video = trackentry['Video'][0]
width = video[ 'PixelWidth' ][0]
height = video[ 'PixelHeight' ][0]
break
num_frames = 0
return ( ( width, height ), duration, num_frames )
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
# this is cribbed from moviepy
2014-06-18 21:53:48 +00:00
def Hydrusffmpeg_parse_infos(filename, print_infos=False):
"""Get file infos using ffmpeg.
Returns a dictionnary with the fields:
"video_found", "video_fps", "duration", "video_nframes",
"video_duration"
"audio_found", "audio_fps"
"video_duration" is slightly smaller than "duration" to avoid
fetching the uncomplete frames at the end, which raises an error.
"""
# open the file in a pipe, provoke an error, read output
2015-09-23 21:21:02 +00:00
cmd = [ FFMPEG_PATH, "-i", filename ]
2014-06-18 21:53:48 +00:00
is_GIF = filename.endswith('.gif')
if is_GIF:
if HC.PLATFORM_WINDOWS: cmd += ["-f", "null", "NUL"]
else: cmd += ["-f", "null", "/dev/null"]
2015-06-17 20:01:41 +00:00
proc = subprocess.Popen( cmd, bufsize=10**5, stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo = HydrusData.GetSubprocessStartupInfo() )
2014-06-18 21:53:48 +00:00
infos = proc.stderr.read().decode('utf8')
proc.terminate()
del proc
if print_infos:
# print the whole info text returned by FFMPEG
print( infos )
lines = infos.splitlines()
if "No such file or directory" in lines[-1]:
raise IOError("%s not found ! Wrong path ?"%filename)
result = dict()
# get duration (in seconds)
2014-06-25 20:37:06 +00:00
# Duration: 00:00:02.46, start: 0.033000, bitrate: 1069 kb/s
2014-06-18 21:53:48 +00:00
try:
keyword = ('frame=' if is_GIF else 'Duration: ')
line = [l for l in lines if keyword in l][0]
2014-06-25 20:37:06 +00:00
if 'start:' in line:
m = re.search( '(start\\: )' + '[0-9]\\.[0-9]*', line )
start_offset = float( line[ m.start() + 7 : m.end() ] )
else: start_offset = 0
2014-06-18 21:53:48 +00:00
match = re.search("[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9]", line)
hms = map(float, line[match.start()+1:match.end()].split(':'))
if len(hms) == 1:
result['duration'] = hms[0]
elif len(hms) == 2:
result['duration'] = 60*hms[0]+hms[1]
elif len(hms) ==3:
result['duration'] = 3600*hms[0]+60*hms[1]+hms[2]
2014-06-25 20:37:06 +00:00
result[ 'duration' ] -= start_offset
2014-06-18 21:53:48 +00:00
except:
raise IOError("Error reading duration in file %s,"%(filename)+
"Text parsed: %s"%infos)
# get the output line that speaks about video
lines_video = [l for l in lines if ' Video: ' in l]
result['video_found'] = ( lines_video != [] )
if result['video_found']:
line = lines_video[0]
# get the size, of the form 460x320 (w x h)
match = re.search(" [0-9]*x[0-9]*(,| )", line)
s = list(map(int, line[match.start():match.end()-1].split('x')))
result['video_size'] = s
# get the frame rate
try:
match = re.search("( [0-9]*.| )[0-9]* tbr", line)
result['video_fps'] = float(line[match.start():match.end()].split(' ')[1])
except:
match = re.search("( [0-9]*.| )[0-9]* fps", line)
result['video_fps'] = float(line[match.start():match.end()].split(' ')[1])
2014-06-25 20:37:06 +00:00
num_frames = result['duration'] * result['video_fps']
if num_frames != int( num_frames ): num_frames += 1 # rounding up
result['video_nframes'] = int( num_frames )
2014-06-18 21:53:48 +00:00
result['video_duration'] = result['duration']
# We could have also recomputed the duration from the number
# of frames, as follows:
# >>> result['video_duration'] = result['video_nframes'] / result['video_fps']
lines_audio = [l for l in lines if ' Audio: ' in l]
result['audio_found'] = lines_audio != []
if result['audio_found']:
line = lines_audio[0]
try:
match = re.search(" [0-9]* Hz", line)
result['audio_fps'] = int(line[match.start()+1:match.end()])
except:
result['audio_fps'] = 'unknown'
return result
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
# This was built from moviepy's FFMPEG_VideoReader
class VideoRendererFFMPEG( object ):
def __init__( self, path, mime, duration, num_frames, target_resolution, pix_fmt = "rgb24" ):
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
self._path = path
self._mime = mime
self._duration = float( duration ) / 1000.0
self._num_frames = num_frames
2014-05-28 21:03:24 +00:00
self._target_resolution = target_resolution
2015-03-04 22:44:32 +00:00
self.lastread = None
2014-06-25 20:37:06 +00:00
self.fps = float( self._num_frames ) / self._duration
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
if self.fps == 0: self.fps = 24
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
self.pix_fmt = pix_fmt
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
if pix_fmt == 'rgba': self.depth = 4
else: self.depth = 3
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
( x, y ) = self._target_resolution
bufsize = self.depth * x * y
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
self.process = None
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
self.bufsize = bufsize
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
self.initialize()
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
def __del__( self ):
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
self.close()
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
def close(self):
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
if self.process is not None:
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
self.process.terminate()
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
self.process.stdout.close()
self.process.stderr.close()
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
self.process = None
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
def initialize( self, start_index = 0 ):
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
self.close()
if self._mime == HC.IMAGE_GIF:
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
ss = 0
self.pos = 0
skip_frames = start_index
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
else:
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
ss = float( start_index ) / self.fps
self.pos = start_index
skip_frames = 0
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
( w, h ) = self._target_resolution
2014-05-28 21:03:24 +00:00
2015-06-17 20:01:41 +00:00
cmd = [ FFMPEG_PATH,
2014-06-25 20:37:06 +00:00
'-ss', "%.03f" % ss,
2015-06-17 20:01:41 +00:00
'-i', self._path,
2014-06-25 20:37:06 +00:00
'-loglevel', 'quiet',
'-f', 'image2pipe',
"-pix_fmt", self.pix_fmt,
"-s", str( w ) + 'x' + str( h ),
2015-06-17 20:01:41 +00:00
'-vcodec', 'rawvideo', '-' ]
2014-05-28 21:03:24 +00:00
2015-06-17 20:01:41 +00:00
self.process = subprocess.Popen( cmd, bufsize = self.bufsize, stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo = HydrusData.GetSubprocessStartupInfo() )
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
self.skip_frames( skip_frames )
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
def skip_frames( self, n ):
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
( w, h ) = self._target_resolution
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
for i in range( n ):
2014-05-28 21:03:24 +00:00
2015-09-09 22:04:39 +00:00
if self.process is not None:
self.process.stdout.read( self.depth * w * h )
self.process.stdout.flush()
2014-06-25 20:37:06 +00:00
self.pos += 1
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
def read_frame( self ):
if self.pos == self._num_frames: self.initialize()
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
if self.process is None: result = self.lastread
else:
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
( w, h ) = self._target_resolution
nbytes = self.depth * w * h
s = self.process.stdout.read(nbytes)
if len(s) != nbytes:
result = self.lastread
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
self.close()
2014-05-28 21:03:24 +00:00
else:
2014-06-25 20:37:06 +00:00
result = numpy.fromstring( s, dtype = 'uint8' ).reshape( ( h, w, len( s ) // ( w * h ) ) )
self.lastread = result
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
self.pos += 1
return result
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
def set_position( self, pos ):
2014-05-28 21:03:24 +00:00
2014-06-25 20:37:06 +00:00
rewind = pos < self.pos
jump_a_long_way_ahead = pos > self.pos + 60
if rewind or jump_a_long_way_ahead: self.initialize( pos )
else: self.skip_frames( pos - self.pos )
2014-05-28 21:03:24 +00:00
2014-06-18 21:53:48 +00:00
2015-08-05 18:42:35 +00:00
# the cv code was initially written by @fluffy_cub
2014-07-30 21:18:17 +00:00
class GIFRenderer( object ):
2014-06-18 21:53:48 +00:00
2014-07-23 21:21:37 +00:00
def __init__( self, path, num_frames, target_resolution ):
2014-06-18 21:53:48 +00:00
2014-07-23 21:21:37 +00:00
self._path = path
self._num_frames = num_frames
2014-06-18 21:53:48 +00:00
self._target_resolution = target_resolution
2015-11-04 22:30:28 +00:00
if cv2.__version__.startswith( '2' ):
2015-06-17 20:01:41 +00:00
self._InitialisePIL()
else:
self._InitialiseCV()
2014-06-18 21:53:48 +00:00
def _GetCurrentFrame( self ):
2014-07-30 21:18:17 +00:00
if self._cv_mode:
2014-08-06 20:29:17 +00:00
( retval, numpy_image ) = self._cv_video.read()
2014-07-30 21:18:17 +00:00
if not retval:
self._next_render_index = ( self._next_render_index + 1 ) % self._num_frames
2015-11-04 22:30:28 +00:00
raise HydrusExceptions.CantRenderWithCVException( 'CV could not render frame ' + str( self._next_render_index - 1 ) + '.' )
2014-07-30 21:18:17 +00:00
else:
if self._pil_image.mode == 'P' and 'transparency' in self._pil_image.info:
2015-07-29 19:11:35 +00:00
# The gif problems seem to be here.
# I think that while some transparent animated gifs expect their frames to be pasted over each other, the others expect them to be fresh every time.
# Determining which is which doesn't seem to be available in PIL, and PIL's internal calculations seem to not be 100% correct.
# Just letting PIL try to do it on its own with P rather than converting to RGBA sometimes produces artifacts
2014-07-30 21:18:17 +00:00
current_frame = self._pil_image.convert( 'RGBA' )
if self._pil_canvas is None: self._pil_canvas = current_frame
else: self._pil_canvas.paste( current_frame, None, current_frame ) # use the rgba image as its own mask
else: self._pil_canvas = self._pil_image
2014-08-06 20:29:17 +00:00
numpy_image = HydrusImageHandling.GenerateNumPyImageFromPILImage( self._pil_canvas )
2014-07-30 21:18:17 +00:00
2014-06-18 21:53:48 +00:00
2014-07-23 21:21:37 +00:00
self._next_render_index = ( self._next_render_index + 1 ) % self._num_frames
2014-06-18 21:53:48 +00:00
2014-07-23 21:21:37 +00:00
if self._next_render_index == 0:
2014-06-25 20:37:06 +00:00
2014-07-23 21:21:37 +00:00
self._RewindGIF()
2014-07-30 21:18:17 +00:00
else:
2014-06-25 20:37:06 +00:00
2014-07-30 21:18:17 +00:00
if not self._cv_mode:
self._pil_image.seek( self._next_render_index )
2015-10-21 21:53:10 +00:00
if self._pil_global_palette is not None and self._pil_image.palette == self._pil_global_palette: # for some reason, when pil falls back from local palette to global palette, a bunch of important variables reset!
2014-07-30 21:18:17 +00:00
2015-02-25 19:34:30 +00:00
self._pil_image.palette.dirty = self._pil_dirty
self._pil_image.palette.mode = self._pil_mode
self._pil_image.palette.rawmode = self._pil_rawmode
2014-07-30 21:18:17 +00:00
2014-06-25 20:37:06 +00:00
2014-08-06 20:29:17 +00:00
return numpy_image
2014-06-18 21:53:48 +00:00
2014-07-30 21:18:17 +00:00
def _InitialiseCV( self ):
2015-06-17 20:01:41 +00:00
self._cv_mode = True
2014-07-30 21:18:17 +00:00
self._cv_video = cv2.VideoCapture( self._path )
2015-10-28 21:29:05 +00:00
self._cv_video.set( CAP_PROP_CONVERT_RGB, True )
2014-07-30 21:18:17 +00:00
self._next_render_index = 0
self._last_frame = None
def _InitialisePIL( self ):
2015-06-17 20:01:41 +00:00
self._cv_mode = False
2014-07-30 21:18:17 +00:00
self._pil_image = HydrusImageHandling.GeneratePILImage( self._path )
self._pil_canvas = None
self._pil_global_palette = self._pil_image.palette
2015-10-21 21:53:10 +00:00
if self._pil_global_palette is not None:
self._pil_dirty = self._pil_image.palette.dirty
self._pil_mode = self._pil_image.palette.mode
self._pil_rawmode = self._pil_image.palette.rawmode
2014-07-30 21:18:17 +00:00
self._next_render_index = 0
self._last_frame = None
# believe it or not, doing this actually fixed a couple of gifs!
self._pil_image.seek( 1 )
self._pil_image.seek( 0 )
2014-06-18 21:53:48 +00:00
def _RenderCurrentFrame( self ):
2014-07-30 21:18:17 +00:00
if self._cv_mode:
try:
2014-08-06 20:29:17 +00:00
numpy_image = self._GetCurrentFrame()
2014-07-30 21:18:17 +00:00
2014-08-06 20:29:17 +00:00
numpy_image = HydrusImageHandling.EfficientlyResizeNumpyImage( numpy_image, self._target_resolution )
2014-07-30 21:18:17 +00:00
2014-08-06 20:29:17 +00:00
numpy_image = cv2.cvtColor( numpy_image, cv2.COLOR_BGR2RGB )
2014-07-30 21:18:17 +00:00
except HydrusExceptions.CantRenderWithCVException:
if self._last_frame is None:
self._InitialisePIL()
2015-04-29 19:20:35 +00:00
numpy_image = self._RenderCurrentFrame()
2014-07-30 21:18:17 +00:00
2014-08-06 20:29:17 +00:00
else: numpy_image = self._last_frame
2014-07-30 21:18:17 +00:00
else:
2014-06-18 21:53:48 +00:00
2014-08-06 20:29:17 +00:00
numpy_image = self._GetCurrentFrame()
2014-06-18 21:53:48 +00:00
2014-08-06 20:29:17 +00:00
numpy_image = HydrusImageHandling.EfficientlyResizeNumpyImage( numpy_image, self._target_resolution )
2014-06-18 21:53:48 +00:00
2015-04-29 19:20:35 +00:00
self._last_frame = numpy_image
2014-06-18 21:53:48 +00:00
2014-08-06 20:29:17 +00:00
return numpy_image
2014-06-18 21:53:48 +00:00
2014-06-25 20:37:06 +00:00
def _RewindGIF( self ):
2014-06-18 21:53:48 +00:00
2014-07-30 21:18:17 +00:00
if self._cv_mode:
self._cv_video.release()
self._cv_video.open( self._path )
2015-10-28 21:29:05 +00:00
#self._cv_video.set( CAP_PROP_POS_FRAMES, 0.0 )
2014-07-30 21:18:17 +00:00
else:
self._pil_image.seek( 0 )
2014-06-18 21:53:48 +00:00
2015-04-29 19:20:35 +00:00
self._next_render_index = 0
2014-06-18 21:53:48 +00:00
2014-07-23 21:21:37 +00:00
def read_frame( self ): return self._RenderCurrentFrame()
2014-06-18 21:53:48 +00:00
2014-07-23 21:21:37 +00:00
def set_position( self, index ):
2014-06-18 21:53:48 +00:00
2014-07-23 21:21:37 +00:00
if index == self._next_render_index: return
elif index < self._next_render_index: self._RewindGIF()
2014-06-18 21:53:48 +00:00
2014-07-23 21:21:37 +00:00
while self._next_render_index < index: self._GetCurrentFrame()
2014-06-18 21:53:48 +00:00
2015-10-28 21:29:05 +00:00
#self._cv_video.set( CV_CAP_PROP_POS_FRAMES, index )
2014-06-18 21:53:48 +00:00
2013-07-10 20:25:57 +00:00