hydrus/hydrus/client/ClientRendering.py

815 lines
24 KiB
Python
Raw Normal View History

2017-03-15 20:13:04 +00:00
import os
2015-08-05 18:42:35 +00:00
import threading
import time
2020-05-20 21:36:02 +00:00
2019-11-14 03:56:30 +00:00
from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
from qtpy import QtGui as QG
2015-08-05 18:42:35 +00:00
2018-01-17 22:52:10 +00:00
LZ4_OK = False
try:
2018-08-22 21:10:59 +00:00
import lz4
2018-01-17 22:52:10 +00:00
import lz4.block
LZ4_OK = True
2018-08-22 21:10:59 +00:00
except Exception as e: # 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
2020-05-20 21:36:02 +00:00
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusImageHandling
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusThreading
from hydrus.core import HydrusVideoHandling
2020-07-29 20:52:44 +00:00
from hydrus.client import ClientFiles
from hydrus.client import ClientImageHandling
from hydrus.client import ClientVideoHandling
2018-12-05 22:35:30 +00:00
def FrameIndexOutOfRange( index, range_start, range_end ):
before_start = index < range_start
after_end = range_end < index
if range_start < range_end:
if before_start or after_end:
return True
else:
if after_end and before_start:
return True
return False
2019-04-03 22:45:57 +00:00
def GenerateHydrusBitmap( path, mime, compressed = True ):
2015-08-05 18:42:35 +00:00
2019-05-08 21:06:42 +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._numpy_image = None
2019-11-14 03:56:30 +00:00
self._hash = media.GetHash()
self._mime = media.GetMime()
self._num_frames = media.GetNumFrames()
self._resolution = media.GetResolution()
2016-09-21 19:54:04 +00:00
2021-02-11 01:59:52 +00:00
self._path = None
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
2021-05-05 20:12:11 +00:00
def _GetNumPyImage( self, clip_rect: QC.QRect, target_resolution: QC.QSize ):
2019-11-14 03:56:30 +00:00
2021-05-05 20:12:11 +00:00
clip_topleft = clip_rect.topLeft()
clip_size = clip_rect.size()
( my_width, my_height ) = self.GetResolution()
my_full_rect = QC.QRect( 0, 0, my_width, my_height )
if clip_rect == my_full_rect:
source = self._numpy_image
else:
( x, y ) = ( clip_topleft.x(), clip_topleft.y() )
( clip_width, clip_height ) = ( clip_size.width(), clip_size.height() )
2019-11-14 03:56:30 +00:00
2021-05-05 20:12:11 +00:00
source = self._numpy_image[ y : y + clip_height, x : x + clip_width ]
if target_resolution == clip_size:
return source.copy()
2019-11-14 03:56:30 +00:00
else:
2021-05-05 20:12:11 +00:00
numpy_image = ClientImageHandling.ResizeNumPyImageForMediaViewer( self._mime, source, ( target_resolution.width(), target_resolution.height() ) )
2019-11-14 03:56:30 +00:00
return numpy_image
2016-09-21 19:54:04 +00:00
def _Initialise( self ):
2021-02-11 01:59:52 +00:00
# do this here so we are off the main thread and can wait
client_files_manager = HG.client_controller.client_files_manager
self._path = client_files_manager.GetFilePath( self._hash, self._mime )
2019-05-08 21:06:42 +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:
2020-04-01 21:51:42 +00:00
return self._numpy_image.nbytes
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
def GetHash( self ): return self._hash
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
def GetNumFrames( self ): return self._num_frames
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
def GetResolution( self ): return self._resolution
2016-09-21 19:54:04 +00:00
2021-05-05 20:12:11 +00:00
def GetQtImage( self, clip_rect = None, target_resolution = None ):
if clip_rect is None:
clip_rect = QC.QRect( QC.QPoint( 0, 0 ), QC.QSize( self._resolution ) )
2016-09-21 19:54:04 +00:00
2021-05-05 20:12:11 +00:00
if target_resolution is None:
target_resolution = clip_rect.size()
2016-09-21 19:54:04 +00:00
2021-05-05 20:12:11 +00:00
numpy_image = self._GetNumPyImage( clip_rect, target_resolution )
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
( height, width, depth ) = numpy_image.shape
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
data = numpy_image.data
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
return HG.client_controller.bitmap_manager.GetQtImageFromBuffer( width, height, depth * 8, data )
2021-05-05 20:12:11 +00:00
def GetQtPixmap( self, clip_rect = None, target_resolution = None ):
if clip_rect is None:
clip_rect = QC.QRect( QC.QPoint( 0, 0 ), QC.QSize( self._resolution ) )
2019-11-14 03:56:30 +00:00
2021-05-05 20:12:11 +00:00
if target_resolution is None:
target_resolution = clip_rect.size()
2019-11-14 03:56:30 +00:00
2021-05-05 20:12:11 +00:00
numpy_image = self._GetNumPyImage( clip_rect, target_resolution )
2019-11-14 03:56:30 +00:00
( height, width, depth ) = numpy_image.shape
data = numpy_image.data
return HG.client_controller.bitmap_manager.GetQtPixmapFromBuffer( width, height, depth * 8, data )
2016-09-21 19:54:04 +00:00
def IsReady( self ):
return self._numpy_image is not None
2021-05-05 20:12:11 +00:00
class ImageTile( object ):
def __init__( self, hash: bytes, clip_rect: QC.QRect, qt_pixmap: QG.QPixmap ):
self.hash = hash
self.clip_rect = clip_rect
self.qt_pixmap = qt_pixmap
self._num_bytes = self.qt_pixmap.width() * self.qt_pixmap.height() * 3
def GetEstimatedMemoryFootprint( self ):
return self._num_bytes
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
2019-01-09 22:59:03 +00:00
width_zoom = target_width / media_width
height_zoom = target_height / media_height
2015-08-05 18:42:35 +00:00
self._zoom = min( ( width_zoom, height_zoom ) )
2019-01-09 22:59:03 +00:00
if self._zoom > 1.0:
self._zoom = 1.0
2015-08-05 18:42:35 +00:00
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
2019-03-06 23:06:22 +00:00
self._renderer = None
2015-08-05 18:42:35 +00:00
self._frames = {}
2018-09-05 20:52:32 +00:00
2015-08-05 18:42:35 +00:00
self._buffer_start_index = -1
self._buffer_end_index = -1
2016-07-27 21:53:34 +00:00
2020-02-26 22:28:52 +00:00
self._times_to_play_gif = 0
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()
2018-09-05 20:52:32 +00:00
num_frames_in_video = self._media.GetNumFrames()
2016-07-27 21:53:34 +00:00
2018-10-24 21:34:02 +00:00
if duration is None or duration == 0:
2019-01-09 22:59:03 +00:00
message = 'The file with hash ' + media.GetHash().hex() + ', had an invalid duration.'
2018-10-24 21:34:02 +00:00
message += os.linesep * 2
message += 'You may wish to try regenerating its metadata through the advanced mode right-click menu.'
HydrusData.ShowText( message )
duration = 1.0
2018-09-05 20:52:32 +00:00
if num_frames_in_video is None or num_frames_in_video == 0:
2017-03-15 20:13:04 +00:00
2019-01-09 22:59:03 +00:00
message = 'The file with hash ' + media.GetHash().hex() + ', had an invalid number of frames.'
2017-03-15 20:13:04 +00:00
message += os.linesep * 2
2018-10-24 21:34:02 +00:00
message += 'You may wish to try regenerating its metadata through the advanced mode right-click menu.'
2017-03-15 20:13:04 +00:00
HydrusData.ShowText( message )
2018-09-05 20:52:32 +00:00
num_frames_in_video = 1
2017-03-15 20:13:04 +00:00
2019-01-09 22:59:03 +00:00
self._average_frame_duration = duration / num_frames_in_video
2016-07-27 21:53:34 +00:00
2019-01-09 22:59:03 +00:00
frame_buffer_length = ( video_buffer_size_mb * 1024 * 1024 ) // ( x * y * 3 )
2016-07-27 21:53:34 +00:00
# if we can't buffer the whole vid, then don't have a clunky massive buffer
2018-09-05 20:52:32 +00:00
max_streaming_buffer_size = max( 48, int( num_frames_in_video / ( duration / 3.0 ) ) ) # 48 or 3 seconds
2016-08-03 22:15:54 +00:00
2018-09-05 20:52:32 +00:00
if max_streaming_buffer_size < frame_buffer_length and frame_buffer_length < num_frames_in_video:
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
2019-01-09 22:59:03 +00:00
self._num_frames_backwards = frame_buffer_length * 2 // 3
self._num_frames_forwards = frame_buffer_length // 3
2015-08-05 18:42:35 +00:00
2018-09-05 20:52:32 +00:00
self._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._rendered_first_frame = False
2018-09-05 20:52:32 +00:00
self._ideal_next_frame = 0
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
2018-09-05 20:52:32 +00:00
def _HasFrame( self, index ):
return index in self._frames
def _IndexInRange( self, index, range_start, range_end ):
2018-12-05 22:35:30 +00:00
return not FrameIndexOutOfRange( index, range_start, range_end )
2015-08-05 18:42:35 +00:00
2016-07-27 21:53:34 +00:00
def _MaintainBuffer( self ):
2019-01-09 22:59:03 +00:00
deletees = [ index for index in list(self._frames.keys()) if FrameIndexOutOfRange( index, self._buffer_start_index, self._buffer_end_index ) ]
2016-08-03 22:15:54 +00:00
2018-09-05 20:52:32 +00:00
for i in deletees:
2016-08-03 22:15:54 +00:00
2018-09-05 20:52:32 +00:00
del self._frames[ i ]
2016-10-19 20:02:56 +00:00
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()
2018-09-05 20:52:32 +00:00
num_frames_in_video = self._media.GetNumFrames()
2015-08-05 18:42:35 +00:00
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
2019-04-10 22:50:53 +00:00
time.sleep( 0.00001 )
2016-10-19 20:02:56 +00:00
if self._media.GetMime() == HC.IMAGE_GIF:
2020-02-26 22:28:52 +00:00
( self._durations, self._times_to_play_gif ) = HydrusImageHandling.GetGIFFrameDurations( self._path )
2016-10-19 20:02:56 +00:00
2018-09-05 20:52:32 +00:00
self._renderer = ClientVideoHandling.GIFRenderer( self._path, num_frames_in_video, self._target_resolution )
2016-10-19 20:02:56 +00:00
else:
2018-09-05 20:52:32 +00:00
self._renderer = HydrusVideoHandling.VideoRendererFFMPEG( self._path, mime, duration, num_frames_in_video, self._target_resolution )
2016-10-19 20:02:56 +00:00
2019-04-10 22:50:53 +00:00
# give ui a chance to draw a blank frame rather than hard-charge right into CPUland
time.sleep( 0.00001 )
2016-10-19 20:02:56 +00:00
self.GetReadyForFrame( self._init_position )
2018-09-05 20:52:32 +00:00
with self._lock:
self._initialised = True
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
2019-03-06 23:06:22 +00:00
self._renderer.Stop()
self._renderer = None
with self._lock:
self._frames = {}
2016-07-27 21:53:34 +00:00
return
2015-08-05 18:42:35 +00:00
2018-09-05 20:52:32 +00:00
#
with self._lock:
# lets see if we should move the renderer to a new position
2018-12-05 22:35:30 +00:00
next_render_is_out_of_buffer = FrameIndexOutOfRange( self._next_render_index, self._buffer_start_index, self._buffer_end_index )
2018-09-05 20:52:32 +00:00
buffer_not_fully_rendered = self._last_index_rendered != self._buffer_end_index
currently_rendering_out_of_buffer = next_render_is_out_of_buffer and buffer_not_fully_rendered
will_render_ideal_frame_soon = self._IndexInRange( self._next_render_index, self._buffer_start_index, self._ideal_next_frame )
need_ideal_next_frame = not self._HasFrame( self._ideal_next_frame )
will_not_get_to_ideal_frame = need_ideal_next_frame and not will_render_ideal_frame_soon
if currently_rendering_out_of_buffer or will_not_get_to_ideal_frame:
# we cannot get to the ideal next frame, so we need to rewind/reposition
self._renderer.set_position( self._buffer_start_index )
self._last_index_rendered = -1
self._next_render_index = self._buffer_start_index
#
need_to_render = self._last_index_rendered != self._buffer_end_index
2016-10-19 20:02:56 +00:00
2018-09-05 20:52:32 +00:00
if need_to_render:
2015-08-05 18:42:35 +00:00
2018-09-05 20:52:32 +00:00
with self._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
2019-11-14 03:56:30 +00:00
renderer = self._renderer
try:
numpy_image = renderer.read_frame()
except Exception as e:
HydrusData.ShowException( e )
return
finally:
with self._lock:
2016-07-06 21:13:15 +00:00
2016-08-03 22:15:54 +00:00
self._last_index_rendered = frame_index
2018-09-05 20:52:32 +00:00
self._next_render_index = ( self._next_render_index + 1 ) % num_frames_in_video
2016-07-06 21:13:15 +00:00
2015-08-05 18:42:35 +00:00
2016-08-03 22:15:54 +00:00
2018-09-05 20:52:32 +00:00
with self._lock:
2016-08-03 22:15:54 +00:00
2018-09-05 20:52:32 +00:00
if self._next_render_index == 0 and self._buffer_end_index != num_frames_in_video - 1:
2016-08-03 22:15:54 +00:00
2018-09-05 20:52:32 +00:00
# we need to rewind renderer
2016-08-03 22:15:54 +00:00
2018-09-05 20:52:32 +00:00
self._renderer.set_position( 0 )
2016-08-03 22:15:54 +00:00
2018-09-05 20:52:32 +00:00
self._last_index_rendered = -1
should_save_frame = not self._HasFrame( frame_index )
2016-08-03 22:15:54 +00:00
2018-09-05 20:52:32 +00:00
if should_save_frame:
2016-08-03 22:15:54 +00:00
2015-08-05 18:42:35 +00:00
frame = GenerateHydrusBitmapFromNumPyImage( numpy_image, compressed = False )
2018-09-05 20:52:32 +00:00
with self._lock:
2016-07-27 21:53:34 +00:00
self._frames[ frame_index ] = frame
2018-09-05 20:52:32 +00:00
self._MaintainBuffer()
2016-07-06 21:13:15 +00:00
2016-08-03 22:15:54 +00:00
2018-09-05 20:52:32 +00:00
with self._lock:
2019-03-06 23:06:22 +00:00
work_still_to_do = self._last_index_rendered != self._buffer_end_index
2018-09-05 20:52:32 +00:00
2019-03-06 23:06:22 +00:00
if work_still_to_do:
2016-08-03 22:15:54 +00:00
2019-03-06 23:06:22 +00:00
time.sleep( 0.0001 )
2016-08-03 22:15:54 +00:00
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
2018-09-05 20:52:32 +00:00
sleep_duration = min( 0.1, half_a_frame ) # for 10s-long 3-frame gifs, wew
time.sleep( sleep_duration ) # 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 ):
2018-09-05 20:52:32 +00:00
with self._lock:
2016-07-27 21:53:34 +00:00
frame = self._frames[ index ]
2015-08-05 18:42:35 +00:00
2018-09-05 20:52:32 +00:00
num_frames_in_video = self.GetNumFrames()
2017-06-21 21:15:59 +00:00
2018-09-05 20:52:32 +00:00
if index == num_frames_in_video - 1:
2017-06-21 21:15:59 +00:00
next_index = 0
else:
next_index = index + 1
self.GetReadyForFrame( next_index )
2015-08-05 18:42:35 +00:00
return frame
2018-09-05 20:52:32 +00:00
def GetHash( self ):
return self._media.GetHash()
2015-08-05 18:42:35 +00:00
2018-09-05 20:52:32 +00:00
def GetKey( self ):
return ( self._media.GetHash(), self._target_resolution )
2015-08-05 18:42:35 +00:00
2018-09-05 20:52:32 +00:00
def GetNumFrames( self ):
return self._media.GetNumFrames()
2015-08-05 18:42:35 +00:00
2016-07-27 21:53:34 +00:00
def GetReadyForFrame( self, next_index_to_expect ):
2015-08-05 18:42:35 +00:00
2018-09-05 20:52:32 +00:00
num_frames_in_video = self.GetNumFrames()
2015-08-05 18:42:35 +00:00
2018-12-05 22:35:30 +00:00
frame_request_is_impossible = FrameIndexOutOfRange( next_index_to_expect, 0, num_frames_in_video - 1 )
2017-06-21 21:15:59 +00:00
2018-09-05 20:52:32 +00:00
if frame_request_is_impossible:
2017-06-21 21:15:59 +00:00
return
2018-09-05 20:52:32 +00:00
with self._lock:
2016-07-27 21:53:34 +00:00
2018-09-05 20:52:32 +00:00
self._ideal_next_frame = next_index_to_expect
2015-08-05 18:42:35 +00:00
2018-09-05 20:52:32 +00:00
video_is_bigger_than_buffer = num_frames_in_video > self._num_frames_backwards + 1 + self._num_frames_forwards
2015-08-05 18:42:35 +00:00
2018-09-05 20:52:32 +00:00
if video_is_bigger_than_buffer:
2015-08-05 18:42:35 +00:00
2018-12-05 22:35:30 +00:00
current_ideal_is_out_of_buffer = self._buffer_start_index == -1 or FrameIndexOutOfRange( self._ideal_next_frame, self._buffer_start_index, self._buffer_end_index )
2015-08-05 18:42:35 +00:00
2018-09-05 20:52:32 +00:00
ideal_buffer_start_index = max( 0, self._ideal_next_frame - self._num_frames_backwards )
2015-08-05 18:42:35 +00:00
2018-09-05 20:52:32 +00:00
ideal_buffer_end_index = ( self._ideal_next_frame + self._num_frames_forwards ) % num_frames_in_video
2015-08-05 18:42:35 +00:00
2018-09-05 20:52:32 +00:00
if current_ideal_is_out_of_buffer:
# the current buffer won't get to where we want, so remake it
2015-08-05 18:42:35 +00:00
2016-07-27 21:53:34 +00:00
self._buffer_start_index = ideal_buffer_start_index
2018-09-05 20:52:32 +00:00
self._buffer_end_index = ideal_buffer_end_index
2015-08-05 18:42:35 +00:00
2018-09-05 20:52:32 +00:00
else:
2016-07-27 21:53:34 +00:00
2018-09-05 20:52:32 +00:00
# we can get to our desired position, but should we move the start and beginning on a bit?
2016-07-27 21:53:34 +00:00
2018-09-05 20:52:32 +00:00
# we do not ever want to shunt left (rewind)
# we do not want to shunt right if we don't have the earliest frames yet--be patient
# i.e. it is between the current start and the ideal
next_ideal_start_would_shunt_right = self._IndexInRange( ideal_buffer_start_index, self._buffer_start_index, self._ideal_next_frame )
have_next_ideal_start = self._HasFrame( ideal_buffer_start_index )
if next_ideal_start_would_shunt_right and have_next_ideal_start:
self._buffer_start_index = ideal_buffer_start_index
next_ideal_end_would_shunt_right = self._IndexInRange( ideal_buffer_end_index, self._buffer_end_index, self._buffer_start_index )
if next_ideal_end_would_shunt_right:
self._buffer_end_index = ideal_buffer_end_index
2015-08-05 18:42:35 +00:00
2018-09-05 20:52:32 +00:00
else:
2015-08-05 18:42:35 +00:00
self._buffer_start_index = 0
2018-09-05 20:52:32 +00:00
self._buffer_end_index = num_frames_in_video - 1
2015-08-05 18:42:35 +00:00
2018-09-05 20:52:32 +00:00
self._render_event.set()
2015-08-05 18:42:35 +00:00
2018-09-05 20:52:32 +00:00
def GetResolution( self ):
return self._media.GetResolution()
2016-07-27 21:53:34 +00:00
2018-09-05 20:52:32 +00:00
def GetSize( self ):
return self._target_resolution
2016-07-27 21:53:34 +00:00
2020-02-26 22:28:52 +00:00
def GetTimesToPlayGIF( self ):
return self._times_to_play_gif
2019-03-20 21:22:10 +00:00
def GetTimestampMS( self, frame_index ):
if self._media.GetMime() == HC.IMAGE_GIF:
return sum( self._durations[ : frame_index ] )
else:
return self._average_frame_duration * frame_index
2016-07-27 21:53:34 +00:00
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 ):
2018-09-05 20:52:32 +00:00
with self._lock:
2016-07-27 21:53:34 +00:00
2018-09-05 20:52:32 +00:00
return self._HasFrame( index )
2016-07-27 21:53:34 +00:00
2019-03-20 21:22:10 +00:00
def CanHaveVariableFramerate( self ):
with self._lock:
return self._media.GetMime() == HC.IMAGE_GIF
2016-10-19 20:02:56 +00:00
def IsInitialised( self ):
2018-09-05 20:52:32 +00:00
with self._lock:
return self._initialised
2016-10-19 20:02:56 +00:00
2018-09-05 20:52:32 +00:00
def IsScaled( self ):
return self._zoom != 1.0
2016-07-27 21:53:34 +00:00
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
2019-11-14 03:56:30 +00:00
def _GetQtImageFormat( self ):
2018-05-30 20:13:21 +00:00
if self._depth == 3:
2019-11-14 03:56:30 +00:00
return QG.QImage.Format_RGB888
2018-05-30 20:13:21 +00:00
elif self._depth == 4:
2019-11-14 03:56:30 +00:00
return QG.QImage.Format_RGBA8888
2018-05-30 20:13:21 +00:00
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
2019-11-14 03:56:30 +00:00
def GetQtImage( self ):
2015-08-05 18:42:35 +00:00
( width, height ) = self._size
2019-11-14 03:56:30 +00:00
return HG.client_controller.bitmap_manager.GetQtImageFromBuffer( width, height, self._depth * 8, self._GetData() )
2015-08-05 18:42:35 +00:00
2019-11-14 03:56:30 +00:00
def GetQtPixmap( self ):
2015-08-05 18:42:35 +00:00
( width, height ) = self._size
2019-11-14 03:56:30 +00:00
return HG.client_controller.bitmap_manager.GetQtPixmapFromBuffer( width, height, self._depth * 8, self._GetData() )
2015-08-05 18:42:35 +00:00
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