hydrus/include/ClientRendering.py

531 lines
16 KiB
Python
Raw Normal View History

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
import HydrusGlobals
import HydrusThreading
import HydrusVideoHandling
import lz4
import threading
import time
import wx
def GenerateHydrusBitmap( path, compressed = True ):
2016-04-27 19:20:37 +00:00
new_options = HydrusGlobals.client_controller.GetNewOptions()
2016-02-03 22:12:53 +00:00
2016-05-11 18:16:39 +00:00
numpy_image = ClientImageHandling.GenerateNumpyImage( path )
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
2016-05-11 18:16:39 +00:00
if depth == 4:
buffer_format = wx.BitmapBufferFormat_RGBA
else:
buffer_format = wx.BitmapBufferFormat_RGB
2015-08-05 18:42:35 +00:00
return HydrusBitmap( numpy_image.data, buffer_format, ( x, y ), compressed = compressed )
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
2016-05-11 18:16:39 +00:00
buffer_format = wx.BitmapBufferFormat_RGBA
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
2016-05-11 18:16:39 +00:00
buffer_format = wx.BitmapBufferFormat_RGB
2015-08-05 18:42:35 +00:00
2016-05-11 18:16:39 +00:00
return HydrusBitmap( pil_image.tobytes(), buffer_format, pil_image.size, compressed = compressed )
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()
2015-12-02 22:32:18 +00:00
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
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 RasterContainerImage( RasterContainer ):
def __init__( self, media, target_resolution = None ):
RasterContainer.__init__( self, media, target_resolution )
self._hydrus_bitmap = None
2015-09-16 18:11:00 +00:00
HydrusGlobals.client_controller.CallToThread( self._InitialiseHydrusBitmap )
2015-08-05 18:42:35 +00:00
def _InitialiseHydrusBitmap( self ):
time.sleep( 0.00001 )
2016-05-11 18:16:39 +00:00
numpy_image = ClientImageHandling.GenerateNumpyImage( self._path )
2016-04-27 19:20:37 +00:00
2016-05-11 18:16:39 +00:00
resized_numpy_image = ClientImageHandling.EfficientlyResizeNumpyImage( numpy_image, self._target_resolution )
hydrus_bitmap = GenerateHydrusBitmapFromNumPyImage( resized_numpy_image )
2015-08-05 18:42:35 +00:00
self._hydrus_bitmap = hydrus_bitmap
def GetEstimatedMemoryFootprint( self ):
if self._hydrus_bitmap is None:
( width, height ) = self._target_resolution
return width * height * 3
2016-04-14 01:54:29 +00:00
else:
return self._hydrus_bitmap.GetEstimatedMemoryFootprint()
2015-08-05 18:42:35 +00:00
def GetHash( self ): return self._media.GetHash()
def GetHydrusBitmap( self ): return self._hydrus_bitmap
def GetNumFrames( self ): return self._media.GetNumFrames()
def GetResolution( self ): return self._media.GetResolution()
def GetSize( self ): return self._target_resolution
def GetZoom( self ): return self._zoom
def IsRendered( self ): return self._hydrus_bitmap is not None
def IsScaled( self ): return self._zoom != 1.0
class RasterContainerVideo( RasterContainer ):
def __init__( self, media, target_resolution = None, init_position = 0 ):
RasterContainer.__init__( self, media, target_resolution )
self._frames = {}
self._last_index_asked_for = -1
self._buffer_start_index = -1
self._buffer_end_index = -1
2016-07-27 21:53:34 +00:00
self._renderer_awake = False
self._stop = False
2015-08-05 18:42:35 +00:00
( x, y ) = self._target_resolution
2016-07-27 21:53:34 +00:00
new_options = HydrusGlobals.client_controller.GetNewOptions()
video_buffer_size_mb = new_options.GetInteger( 'video_buffer_size_mb' )
duration = self._media.GetDuration()
num_frames = self._media.GetNumFrames()
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
if num_frames * 0.1 < frame_buffer_length and frame_buffer_length < num_frames:
frame_buffer_length = int( num_frames * 0.1 )
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
hash = self._media.GetHash()
mime = self._media.GetMime()
2015-12-02 22:32:18 +00:00
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
path = client_files_manager.GetFilePath( hash, mime )
2015-08-05 18:42:35 +00:00
if self._media.GetMime() == HC.IMAGE_GIF:
self._durations = HydrusImageHandling.GetGIFFrameDurations( self._path )
2015-11-18 22:44:07 +00:00
self._renderer = ClientVideoHandling.GIFRenderer( path, num_frames, target_resolution )
2015-08-05 18:42:35 +00:00
else:
self._renderer = HydrusVideoHandling.VideoRendererFFMPEG( path, mime, duration, num_frames, target_resolution )
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-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-07-27 21:53:34 +00:00
self.GetReadyForFrame( init_position )
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 ]
def THREADMoveBuffer( 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-07-27 21:53:34 +00:00
if not self._renderer_awake:
HydrusGlobals.client_controller.CallToThread( self.THREADRender )
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 )
self._next_render_index = start_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-07-27 21:53:34 +00:00
HydrusGlobals.client_controller.CallToThread( self.THREADRender, rush_to_index )
2015-08-05 18:42:35 +00:00
2016-07-27 21:53:34 +00:00
def THREADRender( self, rush_to_index = None ):
2015-08-05 18:42:35 +00:00
num_frames = self._media.GetNumFrames()
while True:
2016-07-27 21:53:34 +00:00
if self._stop:
self._renderer_awake = False
return
2015-08-05 18:42:35 +00:00
with self._render_lock:
2016-07-27 21:53:34 +00:00
self._renderer_awake = True
2015-08-05 18:42:35 +00:00
if not self._rendered_first_frame or self._next_render_index != ( self._render_to_index + 1 ) % num_frames:
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
self._renderer_awake = False
return
2015-08-05 18:42:35 +00:00
2016-07-06 21:13:15 +00:00
finally:
self._next_render_index = ( self._next_render_index + 1 ) % num_frames
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
else:
2016-07-27 21:53:34 +00:00
self._renderer_awake = False
return
2015-08-05 18:42:35 +00:00
2016-07-27 21:53:34 +00:00
if rush_to_index is not None and not self._IndexOutOfRange( rush_to_index, self._next_render_index, self._render_to_index ):
time.sleep( 0.00001 )
else:
half_a_frame = ( self._average_frame_duration / 1000.0 ) * 0.5
time.sleep( half_a_frame ) # just so we don't spam cpu
2016-07-06 21:13:15 +00:00
2015-08-05 18:42:35 +00:00
def GetDuration( self, index ):
if self._media.GetMime() == HC.IMAGE_GIF: return self._durations[ index ]
2016-07-27 21:53:34 +00:00
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
self._last_index_asked_for = index
2016-07-27 21:53:34 +00:00
self.GetReadyForFrame( self._last_index_asked_for + 1 )
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()
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
2016-07-27 21:53:34 +00:00
HydrusGlobals.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
2016-07-27 21:53:34 +00:00
HydrusGlobals.client_controller.CallToThread( self.THREADMoveBuffer, 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
2016-07-27 21:53:34 +00:00
HydrusGlobals.client_controller.CallToThread( self.THREADMoveRenderer, self._buffer_start_index, next_index_to_expect, self._buffer_end_index )
else:
if not self.HasFrame( next_index_to_expect ):
# this rushes rendering to this point
HydrusGlobals.client_controller.CallToThread( self.THREADRender, next_index_to_expect )
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 ):
if self._media.GetMime() == HC.IMAGE_GIF: return sum( self._durations )
else: return self._average_frame_duration * self.GetNumFrames()
def GetZoom( self ): return self._zoom
def HasFrame( self, index ):
with self._buffer_lock:
return index in self._frames
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 ):
def __init__( self, data, format, size, compressed = True ):
self._compressed = compressed
if self._compressed:
self._data = lz4.dumps( data )
else:
self._data = data
self._format = format
self._size = size
def _GetData( self ):
if self._compressed:
return lz4.loads( self._data )
else:
return self._data
def GetWxBitmap( self ):
( width, height ) = self._size
if self._format == wx.BitmapBufferFormat_RGB: return wx.BitmapFromBuffer( width, height, self._GetData() )
else: return wx.BitmapFromBufferRGBA( width, height, self._GetData() )
def GetWxImage( self ):
( width, height ) = self._size
2016-04-14 01:54:29 +00:00
if self._format == wx.BitmapBufferFormat_RGB:
return wx.ImageFromBuffer( width, height, self._GetData() )
2015-08-05 18:42:35 +00:00
else:
bitmap = wx.BitmapFromBufferRGBA( width, height, self._GetData() )
image = wx.ImageFromBitmap( bitmap )
wx.CallAfter( bitmap.Destroy )
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
def GetSize( self ): return self._size