2015-08-05 18:42:35 +00:00
|
|
|
import ClientFiles
|
2015-11-18 22:44:07 +00:00
|
|
|
import ClientImageHandling
|
|
|
|
import ClientVideoHandling
|
2015-08-05 18:42:35 +00:00
|
|
|
import HydrusConstants as HC
|
|
|
|
import HydrusData
|
|
|
|
import HydrusExceptions
|
|
|
|
import HydrusImageHandling
|
2017-05-10 21:33:58 +00:00
|
|
|
import HydrusGlobals as HG
|
2015-08-05 18:42:35 +00:00
|
|
|
import HydrusThreading
|
|
|
|
import HydrusVideoHandling
|
2017-03-15 20:13:04 +00:00
|
|
|
import os
|
2015-08-05 18:42:35 +00:00
|
|
|
import threading
|
|
|
|
import time
|
|
|
|
import wx
|
|
|
|
|
2018-01-17 22:52:10 +00:00
|
|
|
LZ4_OK = False
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
import lz4.block
|
|
|
|
|
|
|
|
LZ4_OK = True
|
|
|
|
|
2018-07-18 21:07:15 +00:00
|
|
|
except: # ImportError wasn't enough here as Linux went up the shoot with a __version__ doesn't exist bs
|
2018-01-17 22:52:10 +00:00
|
|
|
|
|
|
|
pass
|
|
|
|
|
2017-08-23 21:34:25 +00:00
|
|
|
def GenerateHydrusBitmap( path, mime, compressed = True ):
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2017-08-23 21:34:25 +00:00
|
|
|
numpy_image = ClientImageHandling.GenerateNumpyImage( path, mime )
|
2016-05-11 18:16:39 +00:00
|
|
|
|
|
|
|
return GenerateHydrusBitmapFromNumPyImage( numpy_image, compressed = compressed )
|
2015-08-05 18:42:35 +00:00
|
|
|
|
|
|
|
def GenerateHydrusBitmapFromNumPyImage( numpy_image, compressed = True ):
|
|
|
|
|
|
|
|
( y, x, depth ) = numpy_image.shape
|
|
|
|
|
2018-05-30 20:13:21 +00:00
|
|
|
return HydrusBitmap( numpy_image.data, ( x, y ), depth, compressed = compressed )
|
2015-08-05 18:42:35 +00:00
|
|
|
|
|
|
|
def GenerateHydrusBitmapFromPILImage( pil_image, compressed = True ):
|
|
|
|
|
2016-06-29 19:55:46 +00:00
|
|
|
pil_image = HydrusImageHandling.Dequantize( pil_image )
|
|
|
|
|
|
|
|
if pil_image.mode == 'RGBA':
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2018-05-30 20:13:21 +00:00
|
|
|
depth = 4
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-06-29 19:55:46 +00:00
|
|
|
elif pil_image.mode == 'RGB':
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2018-05-30 20:13:21 +00:00
|
|
|
depth = 3
|
2015-08-05 18:42:35 +00:00
|
|
|
|
|
|
|
|
2018-05-30 20:13:21 +00:00
|
|
|
return HydrusBitmap( pil_image.tobytes(), pil_image.size, depth, compressed = compressed )
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-09-21 19:54:04 +00:00
|
|
|
class ImageRenderer( object ):
|
|
|
|
|
|
|
|
def __init__( self, media ):
|
|
|
|
|
|
|
|
self._media = media
|
|
|
|
self._numpy_image = None
|
|
|
|
|
2017-08-23 21:34:25 +00:00
|
|
|
self._hash = self._media.GetHash()
|
|
|
|
self._mime = self._media.GetMime()
|
2016-09-21 19:54:04 +00:00
|
|
|
|
2017-06-28 20:23:21 +00:00
|
|
|
client_files_manager = HG.client_controller.client_files_manager
|
2016-09-21 19:54:04 +00:00
|
|
|
|
2017-08-23 21:34:25 +00:00
|
|
|
self._path = client_files_manager.GetFilePath( self._hash, self._mime )
|
2016-09-21 19:54:04 +00:00
|
|
|
|
2017-05-10 21:33:58 +00:00
|
|
|
HG.client_controller.CallToThread( self._Initialise )
|
2016-09-21 19:54:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
def _Initialise( self ):
|
|
|
|
|
|
|
|
time.sleep( 0.00001 )
|
|
|
|
|
2017-08-23 21:34:25 +00:00
|
|
|
self._numpy_image = ClientImageHandling.GenerateNumpyImage( self._path, self._mime )
|
2016-09-21 19:54:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
def GetEstimatedMemoryFootprint( self ):
|
|
|
|
|
|
|
|
if self._numpy_image is None:
|
|
|
|
|
|
|
|
( width, height ) = self.GetResolution()
|
|
|
|
|
|
|
|
return width * height * 3
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
( height, width, depth ) = self._numpy_image.shape
|
|
|
|
|
|
|
|
return height * width * depth
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def GetHash( self ): return self._media.GetHash()
|
|
|
|
|
|
|
|
def GetNumFrames( self ): return self._media.GetNumFrames()
|
|
|
|
|
|
|
|
def GetResolution( self ): return self._media.GetResolution()
|
|
|
|
|
|
|
|
def GetWXBitmap( self, target_resolution = None ):
|
|
|
|
|
|
|
|
# add region param to this to allow clipping before resize
|
|
|
|
|
|
|
|
if target_resolution is None:
|
|
|
|
|
|
|
|
wx_numpy_image = self._numpy_image
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
wx_numpy_image = ClientImageHandling.ResizeNumpyImage( self._media.GetMime(), self._numpy_image, target_resolution )
|
|
|
|
|
|
|
|
|
|
|
|
( wx_height, wx_width, wx_depth ) = wx_numpy_image.shape
|
|
|
|
|
|
|
|
wx_data = wx_numpy_image.data
|
|
|
|
|
|
|
|
if wx_depth == 3:
|
|
|
|
|
2018-01-03 22:37:30 +00:00
|
|
|
return wx.Bitmap.FromBuffer( wx_width, wx_height, wx_data )
|
2016-09-21 19:54:04 +00:00
|
|
|
|
|
|
|
else:
|
|
|
|
|
2018-01-03 22:37:30 +00:00
|
|
|
return wx.Bitmap.FromBufferRGBA( wx_width, wx_height, wx_data )
|
2016-09-21 19:54:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def IsReady( self ):
|
|
|
|
|
|
|
|
return self._numpy_image is not None
|
|
|
|
|
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
class RasterContainer( object ):
|
|
|
|
|
|
|
|
def __init__( self, media, target_resolution = None ):
|
|
|
|
|
|
|
|
if target_resolution is None: target_resolution = media.GetResolution()
|
|
|
|
|
|
|
|
( width, height ) = target_resolution
|
|
|
|
|
2016-04-06 19:52:45 +00:00
|
|
|
if width == 0 or height == 0:
|
|
|
|
|
|
|
|
target_resolution = ( 100, 100 )
|
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
|
|
|
|
self._media = media
|
2016-04-06 19:52:45 +00:00
|
|
|
|
|
|
|
( media_width, media_height ) = self._media.GetResolution()
|
|
|
|
( target_width, target_height ) = target_resolution
|
|
|
|
|
|
|
|
if target_width > media_width or target_height > media_height:
|
|
|
|
|
|
|
|
target_resolution = self._media.GetResolution()
|
|
|
|
|
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
self._target_resolution = target_resolution
|
|
|
|
|
2016-04-06 19:52:45 +00:00
|
|
|
( target_width, target_height ) = target_resolution
|
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
hash = self._media.GetHash()
|
|
|
|
mime = self._media.GetMime()
|
|
|
|
|
2017-06-28 20:23:21 +00:00
|
|
|
client_files_manager = HG.client_controller.client_files_manager
|
2015-12-02 22:32:18 +00:00
|
|
|
|
|
|
|
self._path = client_files_manager.GetFilePath( hash, mime )
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-04-06 19:52:45 +00:00
|
|
|
width_zoom = target_width / float( media_width )
|
|
|
|
height_zoom = target_height / float( media_height )
|
2015-08-05 18:42:35 +00:00
|
|
|
|
|
|
|
self._zoom = min( ( width_zoom, height_zoom ) )
|
|
|
|
|
|
|
|
if self._zoom > 1.0: self._zoom = 1.0
|
|
|
|
|
|
|
|
|
|
|
|
class RasterContainerVideo( RasterContainer ):
|
|
|
|
|
|
|
|
def __init__( self, media, target_resolution = None, init_position = 0 ):
|
|
|
|
|
|
|
|
RasterContainer.__init__( self, media, target_resolution )
|
|
|
|
|
2016-10-19 20:02:56 +00:00
|
|
|
self._init_position = init_position
|
|
|
|
|
|
|
|
self._initialised = False
|
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
self._frames = {}
|
|
|
|
self._buffer_start_index = -1
|
|
|
|
self._buffer_end_index = -1
|
2016-07-27 21:53:34 +00:00
|
|
|
|
|
|
|
self._stop = False
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-08-03 22:15:54 +00:00
|
|
|
self._render_event = threading.Event()
|
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
( x, y ) = self._target_resolution
|
|
|
|
|
2017-12-06 22:06:56 +00:00
|
|
|
new_options = HG.client_controller.new_options
|
2016-07-27 21:53:34 +00:00
|
|
|
|
|
|
|
video_buffer_size_mb = new_options.GetInteger( 'video_buffer_size_mb' )
|
|
|
|
|
|
|
|
duration = self._media.GetDuration()
|
|
|
|
num_frames = self._media.GetNumFrames()
|
|
|
|
|
2017-03-15 20:13:04 +00:00
|
|
|
if num_frames is None or num_frames == 0:
|
|
|
|
|
|
|
|
message = 'The file with hash ' + media.GetHash().encode( 'hex' ) + ', had an invalid number of frames.'
|
|
|
|
message += os.linesep * 2
|
|
|
|
message += 'You may wish to try exporting, deleting, and the reimporting it.'
|
|
|
|
|
|
|
|
HydrusData.ShowText( message )
|
|
|
|
|
|
|
|
num_frames = 1
|
|
|
|
|
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
self._average_frame_duration = float( duration ) / num_frames
|
|
|
|
|
|
|
|
frame_buffer_length = ( video_buffer_size_mb * 1024 * 1024 ) / ( x * y * 3 )
|
|
|
|
|
|
|
|
# if we can't buffer the whole vid, then don't have a clunky massive buffer
|
|
|
|
|
2016-08-03 22:15:54 +00:00
|
|
|
max_streaming_buffer_size = max( 48, int( num_frames / ( duration / 3.0 ) ) ) # 48 or 3 seconds
|
|
|
|
|
|
|
|
if max_streaming_buffer_size < frame_buffer_length and frame_buffer_length < num_frames:
|
2016-07-27 21:53:34 +00:00
|
|
|
|
2016-08-03 22:15:54 +00:00
|
|
|
frame_buffer_length = max_streaming_buffer_size
|
2016-07-27 21:53:34 +00:00
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
|
|
|
|
self._num_frames_backwards = frame_buffer_length * 2 / 3
|
|
|
|
self._num_frames_forwards = frame_buffer_length / 3
|
|
|
|
|
|
|
|
self._render_lock = threading.Lock()
|
2016-07-27 21:53:34 +00:00
|
|
|
self._buffer_lock = threading.Lock()
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-08-03 22:15:54 +00:00
|
|
|
self._last_index_rendered = -1
|
2016-07-27 21:53:34 +00:00
|
|
|
self._next_render_index = -1
|
2015-08-05 18:42:35 +00:00
|
|
|
self._render_to_index = -1
|
|
|
|
self._rendered_first_frame = False
|
2016-08-03 22:15:54 +00:00
|
|
|
self._rush_to_index = None
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2017-05-10 21:33:58 +00:00
|
|
|
HG.client_controller.CallToThread( self.THREADRender )
|
2016-08-03 22:15:54 +00:00
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
def _IndexOutOfRange( self, index, range_start, range_end ):
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
before_start = index < range_start
|
|
|
|
after_end = range_end < index
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
if range_start < range_end:
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
if before_start or after_end:
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
return True
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
if after_end and before_start:
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
return True
|
2015-08-05 18:42:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
return False
|
2015-08-05 18:42:35 +00:00
|
|
|
|
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
def _MaintainBuffer( self ):
|
|
|
|
|
|
|
|
with self._buffer_lock:
|
|
|
|
|
|
|
|
deletees = [ index for index in self._frames.keys() if self._IndexOutOfRange( index, self._buffer_start_index, self._buffer_end_index ) ]
|
|
|
|
|
|
|
|
for i in deletees:
|
|
|
|
|
|
|
|
del self._frames[ i ]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-08-03 22:15:54 +00:00
|
|
|
def THREADMoveRenderTo( self, render_to_index ):
|
2015-08-05 18:42:35 +00:00
|
|
|
|
|
|
|
with self._render_lock:
|
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
if self._render_to_index != render_to_index:
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
self._render_to_index = render_to_index
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-08-03 22:15:54 +00:00
|
|
|
self._render_event.set()
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-10-19 20:02:56 +00:00
|
|
|
self._initialised = True
|
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
def THREADMoveRenderer( self, start_index, rush_to_index, render_to_index ):
|
2015-08-05 18:42:35 +00:00
|
|
|
|
|
|
|
with self._render_lock:
|
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
if self._next_render_index != start_index:
|
|
|
|
|
|
|
|
self._renderer.set_position( start_index )
|
|
|
|
|
2016-08-03 22:15:54 +00:00
|
|
|
self._last_index_rendered = -1
|
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
self._next_render_index = start_index
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-08-03 22:15:54 +00:00
|
|
|
self._rush_to_index = rush_to_index
|
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
self._render_to_index = render_to_index
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-08-03 22:15:54 +00:00
|
|
|
self._render_event.set()
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-10-19 20:02:56 +00:00
|
|
|
self._initialised = True
|
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
2016-08-03 22:15:54 +00:00
|
|
|
def THREADRushTo( self, rush_to_index ):
|
|
|
|
|
|
|
|
with self._render_lock:
|
|
|
|
|
|
|
|
self._rush_to_index = rush_to_index
|
|
|
|
|
|
|
|
self._render_event.set()
|
|
|
|
|
2016-10-19 20:02:56 +00:00
|
|
|
self._initialised = True
|
|
|
|
|
2016-08-03 22:15:54 +00:00
|
|
|
|
|
|
|
|
|
|
|
def THREADRender( self ):
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-10-19 20:02:56 +00:00
|
|
|
hash = self._media.GetHash()
|
|
|
|
mime = self._media.GetMime()
|
|
|
|
duration = self._media.GetDuration()
|
2015-08-05 18:42:35 +00:00
|
|
|
num_frames = self._media.GetNumFrames()
|
|
|
|
|
2017-06-28 20:23:21 +00:00
|
|
|
client_files_manager = HG.client_controller.client_files_manager
|
2016-10-19 20:02:56 +00:00
|
|
|
|
|
|
|
if self._media.GetMime() == HC.IMAGE_GIF:
|
|
|
|
|
|
|
|
self._durations = HydrusImageHandling.GetGIFFrameDurations( self._path )
|
|
|
|
|
|
|
|
self._renderer = ClientVideoHandling.GIFRenderer( self._path, num_frames, self._target_resolution )
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
self._renderer = HydrusVideoHandling.VideoRendererFFMPEG( self._path, mime, duration, num_frames, self._target_resolution )
|
|
|
|
|
|
|
|
|
|
|
|
self.GetReadyForFrame( self._init_position )
|
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
while True:
|
|
|
|
|
2017-05-10 21:33:58 +00:00
|
|
|
if self._stop or HG.view_shutdown:
|
2016-07-27 21:53:34 +00:00
|
|
|
|
|
|
|
return
|
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-10-19 20:02:56 +00:00
|
|
|
ready_to_render = self._initialised
|
|
|
|
frames_needed = not self._rendered_first_frame or self._next_render_index != ( self._render_to_index + 1 ) % num_frames
|
|
|
|
|
|
|
|
if ready_to_render and frames_needed:
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-08-03 22:15:54 +00:00
|
|
|
with self._render_lock:
|
2015-08-05 18:42:35 +00:00
|
|
|
|
|
|
|
self._rendered_first_frame = True
|
|
|
|
|
|
|
|
frame_index = self._next_render_index # keep this before the get call, as it increments in a clock arithmetic way afterwards
|
|
|
|
|
2016-07-06 21:13:15 +00:00
|
|
|
try:
|
|
|
|
|
|
|
|
numpy_image = self._renderer.read_frame()
|
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
except Exception as e:
|
|
|
|
|
|
|
|
HydrusData.ShowException( e )
|
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
return
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-07-06 21:13:15 +00:00
|
|
|
finally:
|
|
|
|
|
2016-08-03 22:15:54 +00:00
|
|
|
self._last_index_rendered = frame_index
|
|
|
|
|
2016-07-06 21:13:15 +00:00
|
|
|
self._next_render_index = ( self._next_render_index + 1 ) % num_frames
|
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-08-03 22:15:54 +00:00
|
|
|
|
|
|
|
with self._buffer_lock:
|
|
|
|
|
|
|
|
frame_needed = frame_index not in self._frames
|
|
|
|
|
|
|
|
if self._rush_to_index is not None:
|
|
|
|
|
|
|
|
reached_it = self._rush_to_index == frame_index
|
|
|
|
already_got_it = self._rush_to_index in self._frames
|
|
|
|
can_no_longer_reach_it = self._IndexOutOfRange( self._rush_to_index, self._next_render_index, self._render_to_index )
|
|
|
|
|
|
|
|
if reached_it or already_got_it or can_no_longer_reach_it:
|
|
|
|
|
|
|
|
self._rush_to_index = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if frame_needed:
|
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
frame = GenerateHydrusBitmapFromNumPyImage( numpy_image, compressed = False )
|
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
with self._buffer_lock:
|
|
|
|
|
|
|
|
self._frames[ frame_index ] = frame
|
|
|
|
|
2016-07-06 21:13:15 +00:00
|
|
|
|
2016-08-03 22:15:54 +00:00
|
|
|
|
|
|
|
if self._rush_to_index is not None:
|
|
|
|
|
|
|
|
time.sleep( 0.00001 )
|
|
|
|
|
2016-07-06 21:13:15 +00:00
|
|
|
else:
|
|
|
|
|
2016-08-03 22:15:54 +00:00
|
|
|
half_a_frame = ( self._average_frame_duration / 1000.0 ) * 0.5
|
2016-07-27 21:53:34 +00:00
|
|
|
|
2016-08-03 22:15:54 +00:00
|
|
|
time.sleep( half_a_frame ) # just so we don't spam cpu
|
2015-08-05 18:42:35 +00:00
|
|
|
|
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
else:
|
|
|
|
|
2016-08-03 22:15:54 +00:00
|
|
|
self._render_event.wait( 1 )
|
2016-07-27 21:53:34 +00:00
|
|
|
|
2016-08-03 22:15:54 +00:00
|
|
|
self._render_event.clear()
|
2016-07-27 21:53:34 +00:00
|
|
|
|
|
|
|
|
2016-07-06 21:13:15 +00:00
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-08-03 22:15:54 +00:00
|
|
|
def GetBufferIndices( self ):
|
|
|
|
|
|
|
|
if self._last_index_rendered == -1:
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
return ( self._buffer_start_index, self._last_index_rendered, self._buffer_end_index )
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
def GetDuration( self, index ):
|
|
|
|
|
2017-06-28 20:23:21 +00:00
|
|
|
if self._media.GetMime() == HC.IMAGE_GIF:
|
|
|
|
|
|
|
|
return self._durations[ index ]
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
return self._average_frame_duration
|
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
def GetFrame( self, index ):
|
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
with self._buffer_lock:
|
|
|
|
|
|
|
|
frame = self._frames[ index ]
|
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2017-06-21 21:15:59 +00:00
|
|
|
num_frames = self.GetNumFrames()
|
|
|
|
|
|
|
|
if index == num_frames - 1:
|
|
|
|
|
|
|
|
next_index = 0
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
next_index = index + 1
|
|
|
|
|
|
|
|
|
|
|
|
self.GetReadyForFrame( next_index )
|
2015-08-05 18:42:35 +00:00
|
|
|
|
|
|
|
return frame
|
|
|
|
|
|
|
|
|
|
|
|
def GetHash( self ): return self._media.GetHash()
|
|
|
|
|
|
|
|
def GetKey( self ): return ( self._media.GetHash(), self._target_resolution )
|
|
|
|
|
|
|
|
def GetNumFrames( self ): return self._media.GetNumFrames()
|
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
def GetReadyForFrame( self, next_index_to_expect ):
|
2015-08-05 18:42:35 +00:00
|
|
|
|
|
|
|
num_frames = self.GetNumFrames()
|
|
|
|
|
2017-06-21 21:15:59 +00:00
|
|
|
frame_exists = 0 <= next_index_to_expect and next_index_to_expect <= ( num_frames - 1 )
|
|
|
|
|
|
|
|
if not frame_exists:
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
if num_frames > self._num_frames_backwards + 1 + self._num_frames_forwards:
|
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
index_out_of_buffer = self._IndexOutOfRange( next_index_to_expect, self._buffer_start_index, self._buffer_end_index )
|
|
|
|
|
|
|
|
ideal_buffer_start_index = max( 0, next_index_to_expect - self._num_frames_backwards )
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
ideal_buffer_end_index = ( next_index_to_expect + self._num_frames_forwards ) % num_frames
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
if not self._rendered_first_frame or index_out_of_buffer:
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
self._buffer_start_index = ideal_buffer_start_index
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
self._buffer_end_index = ideal_buffer_end_index
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2017-05-10 21:33:58 +00:00
|
|
|
HG.client_controller.CallToThread( self.THREADMoveRenderer, self._buffer_start_index, next_index_to_expect, self._buffer_end_index )
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
else:
|
|
|
|
|
|
|
|
# rendering can't go backwards, so dragging caret back shouldn't rewind either of these!
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
if self.HasFrame( ideal_buffer_start_index ):
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
self._buffer_start_index = ideal_buffer_start_index
|
2015-08-05 18:42:35 +00:00
|
|
|
|
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
if not self._IndexOutOfRange( self._next_render_index + 1, self._buffer_start_index, ideal_buffer_end_index ):
|
|
|
|
|
|
|
|
self._buffer_end_index = ideal_buffer_end_index
|
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2017-05-10 21:33:58 +00:00
|
|
|
HG.client_controller.CallToThread( self.THREADMoveRenderTo, self._buffer_end_index )
|
2015-08-05 18:42:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
if self._buffer_end_index == -1:
|
|
|
|
|
|
|
|
self._buffer_start_index = 0
|
|
|
|
|
|
|
|
self._buffer_end_index = num_frames - 1
|
|
|
|
|
2017-05-10 21:33:58 +00:00
|
|
|
HG.client_controller.CallToThread( self.THREADMoveRenderer, self._buffer_start_index, next_index_to_expect, self._buffer_end_index )
|
2016-07-27 21:53:34 +00:00
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
if not self.HasFrame( next_index_to_expect ):
|
|
|
|
|
2017-05-10 21:33:58 +00:00
|
|
|
HG.client_controller.CallToThread( self.THREADRushTo, next_index_to_expect )
|
2016-07-27 21:53:34 +00:00
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self._MaintainBuffer()
|
|
|
|
|
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
def GetResolution( self ): return self._media.GetResolution()
|
|
|
|
|
|
|
|
def GetSize( self ): return self._target_resolution
|
|
|
|
|
|
|
|
def GetTotalDuration( self ):
|
|
|
|
|
2017-06-28 20:23:21 +00:00
|
|
|
if self._media.GetMime() == HC.IMAGE_GIF:
|
|
|
|
|
|
|
|
return sum( self._durations )
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
return self._average_frame_duration * self.GetNumFrames()
|
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
def HasFrame( self, index ):
|
|
|
|
|
|
|
|
with self._buffer_lock:
|
|
|
|
|
|
|
|
return index in self._frames
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-10-19 20:02:56 +00:00
|
|
|
def IsInitialised( self ):
|
|
|
|
|
|
|
|
return self._initialised
|
|
|
|
|
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
def IsScaled( self ): return self._zoom != 1.0
|
|
|
|
|
|
|
|
def Stop( self ):
|
|
|
|
|
|
|
|
self._stop = True
|
|
|
|
|
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
class HydrusBitmap( object ):
|
|
|
|
|
2018-05-30 20:13:21 +00:00
|
|
|
def __init__( self, data, size, depth, compressed = True ):
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2018-01-17 22:52:10 +00:00
|
|
|
if not LZ4_OK:
|
|
|
|
|
|
|
|
compressed = False
|
|
|
|
|
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
self._compressed = compressed
|
|
|
|
|
|
|
|
if self._compressed:
|
|
|
|
|
2017-06-14 21:19:11 +00:00
|
|
|
self._data = lz4.block.compress( data )
|
2015-08-05 18:42:35 +00:00
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
self._data = data
|
|
|
|
|
|
|
|
|
|
|
|
self._size = size
|
2018-05-30 20:13:21 +00:00
|
|
|
self._depth = depth
|
2015-08-05 18:42:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
def _GetData( self ):
|
|
|
|
|
|
|
|
if self._compressed:
|
|
|
|
|
2017-06-14 21:19:11 +00:00
|
|
|
return lz4.block.decompress( self._data )
|
2015-08-05 18:42:35 +00:00
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
return self._data
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-05-30 20:13:21 +00:00
|
|
|
def _GetWXBitmapFormat( self ):
|
|
|
|
|
|
|
|
if self._depth == 3:
|
|
|
|
|
|
|
|
return wx.BitmapBufferFormat_RGB
|
|
|
|
|
|
|
|
elif self._depth == 4:
|
|
|
|
|
|
|
|
return wx.BitmapBufferFormat_RGBA
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-08-03 22:15:54 +00:00
|
|
|
def CopyToWxBitmap( self, wx_bmp ):
|
|
|
|
|
2018-05-30 20:13:21 +00:00
|
|
|
fmt = self._GetWXBitmapFormat()
|
|
|
|
|
|
|
|
wx_bmp.CopyFromBuffer( self._GetData(), fmt )
|
2016-08-03 22:15:54 +00:00
|
|
|
|
|
|
|
|
|
|
|
def GetDepth( self ):
|
|
|
|
|
2018-05-30 20:13:21 +00:00
|
|
|
return self._depth
|
2016-08-03 22:15:54 +00:00
|
|
|
|
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
def GetWxBitmap( self ):
|
|
|
|
|
|
|
|
( width, height ) = self._size
|
|
|
|
|
2018-05-30 20:13:21 +00:00
|
|
|
if self._depth == 3:
|
2017-11-29 21:48:23 +00:00
|
|
|
|
2018-01-03 22:37:30 +00:00
|
|
|
return wx.Bitmap.FromBuffer( width, height, self._GetData() )
|
2017-11-29 21:48:23 +00:00
|
|
|
|
2018-05-30 20:13:21 +00:00
|
|
|
elif self._depth == 4:
|
2017-11-29 21:48:23 +00:00
|
|
|
|
2018-01-03 22:37:30 +00:00
|
|
|
return wx.Bitmap.FromBufferRGBA( width, height, self._GetData() )
|
2017-11-29 21:48:23 +00:00
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
def GetWxImage( self ):
|
|
|
|
|
|
|
|
( width, height ) = self._size
|
|
|
|
|
2018-05-30 20:13:21 +00:00
|
|
|
if self._depth == 3:
|
2016-04-14 01:54:29 +00:00
|
|
|
|
|
|
|
return wx.ImageFromBuffer( width, height, self._GetData() )
|
|
|
|
|
2018-05-30 20:13:21 +00:00
|
|
|
elif self._depth == 4:
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2018-01-03 22:37:30 +00:00
|
|
|
bitmap = wx.Bitmap.FromBufferRGBA( width, height, self._GetData() )
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2018-04-11 22:30:40 +00:00
|
|
|
image = bitmap.ConvertToImage()
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2016-08-24 18:36:56 +00:00
|
|
|
bitmap.Destroy()
|
2015-08-05 18:42:35 +00:00
|
|
|
|
|
|
|
return image
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-14 01:54:29 +00:00
|
|
|
def GetEstimatedMemoryFootprint( self ):
|
|
|
|
|
|
|
|
return len( self._data )
|
|
|
|
|
2015-08-05 18:42:35 +00:00
|
|
|
|
2017-11-29 21:48:23 +00:00
|
|
|
def GetSize( self ):
|
|
|
|
|
|
|
|
return self._size
|
|
|
|
|
2017-03-15 20:13:04 +00:00
|
|
|
|