Version 217

This commit is contained in:
Hydrus Network Developer 2016-08-03 17:15:54 -05:00
parent 1698dfdee1
commit 2aec39b034
19 changed files with 845 additions and 179 deletions

View File

@ -8,6 +8,35 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 217</h3></li>
<ul>
<li>fixed some high-res video streaming thread scheduling problems with the new video renderer</li>
<li>fixed a cause of huge memory bloat with greatly upscaled videos</li>
<li>to improve seek response time, streaming buffer for the video renderer has a much smaller cap</li>
<li>renderer throttling calculations are more sensible and reliable</li>
<li>the video renderer discards frames to save time if they happen to still be in its buffer</li>
<li>the video scanbar now displays the current frame buffer around the caret!</li>
<li>video canvas now recycles the same frame blit bitmap to save a little time</li>
<li>wrote a prototype related-tags suggestion 'service' for the suggested tags control</li>
<li>you can turn it on and set some options for it at options->tags, feedback would be appreciated</li>
<li>munged increasingly complicated components of the suggested tags control into a clean and proper self-hiding panel</li>
<li>fixed a very important bug that was failing to filter visible thumbnail fetch on mass select and thus massively slowing down the client on large ctrl+a-like operations</li>
<li>open externally button now shows the media's thumbnail, if it has one</li>
<li>open externally and embed buttons now use hand cursor</li>
<li>the simple path tagging dialog panel now cuts off .jpg extensions from filenames on filename parse</li>
<li>if the string component of a generated file export path already ends in the correct .jpg extension, a second will not be added</li>
<li>ipfs unpin will no longer break if the file was already unpinned</li>
<li>the hydrus server now gives filename (for a file save as dialog) correctly on a content-disposition header (this affects the client's local booru as well)</li>
<li>the secondary sort can now be a namespace or rating sort</li>
<li>fixed some potential init problems with some dropdown controls</li>
<li>an edge case object-missing cache retrieval bug is fixed</li>
<li>updated openssl on os x, which might have fixed some problems</li>
<li>updated python on windows, which updated openssl and a bunch of other stuff</li>
<li>updated sqlite on windows</li>
<li>updated linux dev machine to ubuntu 16.04, so a variety of packaged libraries are updated</li>
<li>fixed auto server setup if the client is launched from a windows cmd window</li>
<li>misc cleanup</li>
</ul>
<li><h3>version 216</h3></li>
<ul>
<li>video rendering pipeline rewritten to be much smoother</li>

View File

@ -1106,6 +1106,21 @@ class DataCache( object ):
self._total_estimated_memory_footprint = sum( ( data.GetEstimatedMemoryFootprint() for data in self._keys_to_data.values() ) )
def _TouchKey( self, key ):
for ( i, ( fifo_key, last_access_time ) ) in enumerate( self._keys_fifo ):
if fifo_key == key:
del self._keys_fifo[ i ]
break
self._keys_fifo.append( ( key, HydrusData.GetNow() ) )
def Clear( self ):
with self._lock:
@ -1148,22 +1163,29 @@ class DataCache( object ):
raise Exception( 'Cache error! Looking for ' + HydrusData.ToUnicode( key ) + ', but it was missing.' )
for ( i, ( fifo_key, last_access_time ) ) in enumerate( self._keys_fifo ):
if fifo_key == key:
del self._keys_fifo[ i ]
break
self._keys_fifo.append( ( key, HydrusData.GetNow() ) )
self._TouchKey( key )
return self._keys_to_data[ key ]
def GetIfHasData( self, key ):
with self._lock:
if key in self._keys_to_data:
self._TouchKey( key )
return self._keys_to_data[ key ]
else:
return None
def HasData( self, key ):
with self._lock:
@ -1537,18 +1559,20 @@ class RenderedImageCache( object ):
key = ( hash, target_resolution )
if self._data_cache.HasData( key ):
return self._data_cache.GetData( key )
else:
result = self._data_cache.GetIfHasData( key )
if result is None:
image_container = ClientRendering.RasterContainerImage( media, target_resolution )
self._data_cache.AddData( key, image_container )
return image_container
else:
image_container = result
return image_container
def HasImage( self, hash, target_resolution ):
@ -1758,14 +1782,20 @@ class ThumbnailCache( object ):
hash = display_media.GetHash()
if not self._data_cache.HasData( hash ):
result = self._data_cache.GetIfHasData( hash )
if result is None:
hydrus_bitmap = self._GetResizedHydrusBitmapFromHardDrive( display_media )
self._data_cache.AddData( hash, hydrus_bitmap )
else:
hydrus_bitmap = result
return self._data_cache.GetData( hash )
return hydrus_bitmap
elif mime in HC.AUDIO: return self._special_thumbs[ 'audio' ]
elif mime in HC.VIDEO: return self._special_thumbs[ 'video' ]

View File

@ -14,7 +14,6 @@ import HydrusConstants as HC
import HydrusDB
import ClientDownloading
import ClientImageHandling
import HydrusEncryption
import HydrusExceptions
import HydrusFileHandling
import HydrusImageHandling
@ -26,6 +25,7 @@ import HydrusTags
import HydrusThreading
import ClientConstants as CC
import lz4
import numpy
import os
import psutil
import Queue
@ -4297,6 +4297,136 @@ class DB( HydrusDB.HydrusDB ):
def _GetRelatedTags( self, service_key, skip_hash, search_tags, max_results, max_time_to_take ):
start = HydrusData.GetNowPrecise()
service_id = self._GetServiceId( service_key )
skip_hash_id = self._GetHashId( skip_hash )
( current_mappings_table_name, deleted_mappings_table_name, pending_mappings_table_name, petitioned_mappings_table_name ) = GenerateMappingsTableNames( service_id )
namespace_ids_to_tag_ids = HydrusData.BuildKeyToListDict( [ self._GetNamespaceIdTagId( tag ) for tag in search_tags ] )
namespace_ids = namespace_ids_to_tag_ids.keys()
if len( namespace_ids ) == 0:
return []
random.shuffle( namespace_ids )
time_on_this_section = max_time_to_take / 2
# this biases namespaced tags when we are in a rush, as they are less common than unnamespaced but will get the same search time
time_per_namespace = time_on_this_section / len( namespace_ids )
hash_ids_counter = collections.Counter()
for namespace_id in namespace_ids:
namespace_start = HydrusData.GetNowPrecise()
tag_ids = namespace_ids_to_tag_ids[ namespace_id ]
random.shuffle( tag_ids )
query = self._c.execute( 'SELECT hash_id FROM ' + current_mappings_table_name + ' WHERE namespace_id = ? AND tag_id IN ' + HydrusData.SplayListForDB( tag_ids ) + ';', ( namespace_id, ) )
results = query.fetchmany( 100 )
while len( results ) > 0:
for ( hash_id, ) in results:
hash_ids_counter[ hash_id ] += 1
if HydrusData.TimeHasPassedPrecise( namespace_start + time_per_namespace ):
break
results = query.fetchmany( 100 )
if HydrusData.TimeHasPassedPrecise( namespace_start + time_per_namespace ):
break
if skip_hash_id in hash_ids_counter:
del hash_ids_counter[ skip_hash_id ]
#
if len( hash_ids_counter ) == 0:
return []
# this stuff is often 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.....
# the 1 stuff often produces large quantities of the same very popular tag, so your search for [ 'eva', 'female' ] will produce 'touhou' because so many 2hu images have 'female'
# so we want to do a 'soft' intersect, only picking the files that have the greatest number of shared search_tags
# this filters to only the '2' results, which gives us eva females and their hair colour and a few choice other popular tags for that particular domain
[ ( gumpf, largest_count ) ] = hash_ids_counter.most_common( 1 )
hash_ids = [ hash_id for ( hash_id, count ) in hash_ids_counter.items() if count > largest_count * 0.8 ]
counter = collections.Counter()
random.shuffle( hash_ids )
for hash_id in hash_ids:
for ( namespace_id, tag_id ) in self._c.execute( 'SELECT namespace_id, tag_id FROM ' + current_mappings_table_name + ' WHERE hash_id = ?;', ( hash_id, ) ):
counter[ ( namespace_id, tag_id ) ] += 1
if HydrusData.TimeHasPassedPrecise( start + max_time_to_take ):
break
#
for ( namespace_id, tag_ids ) in namespace_ids_to_tag_ids.items():
for tag_id in tag_ids:
if ( namespace_id, tag_id ) in counter:
del counter[ ( namespace_id, tag_id ) ]
results = counter.most_common( max_results )
tags_and_counts = [ ( self._GetNamespaceTag( namespace_id, tag_id ), count ) for ( ( namespace_id, tag_id ), count ) in results ]
tags_to_do = [ tag for ( tag, count ) in tags_and_counts ]
tag_censorship_manager = self._controller.GetManager( 'tag_censorship' )
filtered_tags = tag_censorship_manager.FilterTags( service_key, tags_to_do )
inclusive = True
pending_count = 0
predicates = [ ClientSearch.Predicate( HC.PREDICATE_TYPE_TAG, tag, inclusive, current_count, pending_count ) for ( tag, current_count ) in tags_and_counts if tag in filtered_tags ]
return predicates
def _GetRemoteThumbnailHashesIShouldHave( self, service_key ):
service_id = self._GetServiceId( service_key )
@ -6005,6 +6135,7 @@ class DB( HydrusDB.HydrusDB ):
elif action == 'service_filenames': result = self._GetServiceFilenames( *args, **kwargs )
elif action == 'service_info': result = self._GetServiceInfo( *args, **kwargs )
elif action == 'services': result = self._GetServices( *args, **kwargs )
elif action == 'related_tags': result = self._GetRelatedTags( *args, **kwargs )
elif action == 'tag_censorship': result = self._GetTagCensorship( *args, **kwargs )
elif action == 'tag_parents': result = self._GetTagParents( *args, **kwargs )
elif action == 'tag_siblings': result = self._GetTagSiblings( *args, **kwargs )

View File

@ -7,7 +7,6 @@ import httplib
import itertools
import HydrusConstants as HC
import HydrusData
import HydrusEncryption
import HydrusExceptions
import HydrusImageHandling
import HydrusNATPunch

View File

@ -249,6 +249,25 @@ def GetMediasTagCount( pool, tag_service_key = CC.COMBINED_TAG_SERVICE_KEY, coll
return ( current_tags_to_count, deleted_tags_to_count, pending_tags_to_count, petitioned_tags_to_count )
def GetSortChoices( add_namespaces_and_ratings = True ):
sort_choices = list( CC.SORT_CHOICES )
if add_namespaces_and_ratings:
sort_choices.extend( HC.options[ 'sort_by' ] )
ratings_services = HydrusGlobals.client_controller.GetServicesManager().GetServices( ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ) )
for ratings_service in ratings_services:
sort_choices.append( ( 'rating_descend', ratings_service ) )
sort_choices.append( ( 'rating_ascend', ratings_service ) )
return sort_choices
def ShowExceptionClient( e ):
( etype, value, tb ) = sys.exc_info()
@ -467,6 +486,8 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
self._dictionary[ 'booleans' ][ 'replace_siblings_on_manage_tags' ] = True
self._dictionary[ 'booleans' ][ 'show_related_tags' ] = False
#
self._dictionary[ 'noneable_integers' ] = {}
@ -484,6 +505,11 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
self._dictionary[ 'integers' ][ 'video_buffer_size_mb' ] = 96
self._dictionary[ 'integers' ][ 'related_tags_width' ] = 150
self._dictionary[ 'integers' ][ 'related_tags_search_1_duration_ms' ] = 250
self._dictionary[ 'integers' ][ 'related_tags_search_2_duration_ms' ] = 2000
self._dictionary[ 'integers' ][ 'related_tags_search_3_duration_ms' ] = 6000
#
client_files_default = os.path.join( HC.DB_DIR, 'client_files' )
@ -2259,7 +2285,17 @@ class ServiceIPFS( ServiceRemote ):
url = api_base_url + 'pin/rm/' + multihash
ClientNetworking.RequestsGet( url )
try:
ClientNetworking.RequestsGet( url )
except HydrusExceptions.NetworkException as e:
if 'not pinned' not in str( e ):
raise
content_updates = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, { hash } ) ]

View File

@ -76,7 +76,14 @@ def GenerateExportFilename( media, terms ):
filename = re.sub( '/', '_', filename, flags = re.UNICODE )
return filename + HC.mime_ext_lookup[ mime ]
ext = HC.mime_ext_lookup[ mime ]
if not filename.endswith( ext ):
filename += ext
return filename
def GetAllPaths( raw_paths ):

View File

@ -295,30 +295,33 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
if HC.PLATFORM_WINDOWS:
my_frozen_path = os.path.join( HC.BASE_DIR, 'client.exe' )
server_frozen_path = os.path.join( HC.BASE_DIR, 'server.exe' )
else:
my_frozen_path = os.path.join( HC.BASE_DIR, 'client' )
server_frozen_path = os.path.join( HC.BASE_DIR, 'server' )
my_executable = sys.executable
if os.path.exists( server_frozen_path ):
if my_executable == my_frozen_path:
if HC.PLATFORM_WINDOWS: subprocess.Popen( [ os.path.join( HC.BASE_DIR, 'server.exe' ) ] )
else: subprocess.Popen( [ os.path.join( '.', HC.BASE_DIR, 'server' ) ] )
if HC.PLATFORM_WINDOWS: subprocess.Popen( [ server_frozen_path ] )
else: subprocess.Popen( [ server_frozen_path ] )
else:
server_executable = my_executable
python_executable = sys.executable
if 'pythonw' in server_executable:
if python_executable.endswith( 'client.exe' ) or python_executable.endswith( 'client' ):
server_executable = server_executable.replace( 'pythonw', 'python' )
raise Exception( 'Could not automatically set up the server--could not find server executable or python executable.' )
subprocess.Popen( [ server_executable, os.path.join( HC.BASE_DIR, 'server.py' ) ] )
if 'pythonw' in python_executable:
python_executable = python_executable.replace( 'pythonw', 'python' )
subprocess.Popen( [ python_executable, os.path.join( HC.BASE_DIR, 'server.py' ) ] )
time_waited = 0

View File

@ -11,6 +11,7 @@ import ClientGUIPanels
import ClientGUITopLevelWindows
import ClientMedia
import ClientRatings
import ClientRendering
import collections
import gc
import HydrusImageHandling
@ -88,7 +89,16 @@ def CalculateMediaContainerSize( media, zoom ):
elif action == CC.MEDIA_VIEWER_SHOW_OPEN_EXTERNALLY_BUTTON:
return OPEN_EXTERNALLY_BUTTON_SIZE
( width, height ) = OPEN_EXTERNALLY_BUTTON_SIZE
if media.GetMime() in HC.MIMES_WITH_THUMBNAILS:
( thumb_width, thumb_height ) = HydrusImageHandling.GetThumbnailResolution( media.GetResolution(), HC.UNSCALED_THUMBNAIL_DIMENSIONS )
height = height + thumb_height
return ( width, height )
else:
@ -150,6 +160,7 @@ class Animation( wx.Window ):
self._video_container = ClientRendering.RasterContainerVideo( self._media, initial_size, init_position = self._current_frame_index )
self._canvas_bmp = wx.EmptyBitmap( initial_width, initial_height, 24 )
self._frame_bmp = None
self._timer_video = wx.Timer( self, id = ID_TIMER_VIDEO )
@ -169,6 +180,13 @@ class Animation( wx.Window ):
self._video_container.Stop()
if self._frame_bmp is not None:
self._frame_bmp.Destroy()
self._canvas_bmp.Destroy()
wx.CallLater( 500, gc.collect )
@ -180,11 +198,22 @@ class Animation( wx.Window ):
( frame_width, frame_height ) = current_frame.GetSize()
wx_bmp = current_frame.GetWxBitmap()
if self._frame_bmp is None or self._frame_bmp.GetSize() != current_frame.GetSize():
self._frame_bmp = wx.EmptyBitmap( frame_width, frame_height, current_frame.GetDepth() * 8 )
if HC.PLATFORM_OSX:
current_frame.CopyToWxBitmap( self._frame_bmp )
# since stretchblit is unreliable, and since stretched drawing is so slow anyway, let's do it at the numpy_level
# so this calls for 'copy this clipped region to this bmp'
# the frame container clips the numpy_image, resizes up in cv, fills the bmp
# then we blit in 0.001ms no prob
if HC.PLATFORM_OSX or HC.PLATFORM_LINUX:
# for some reason, stretchblit just draws white for os x
# and for ubuntu 16.04, it only handles the first frame!
# maybe a wx.copy problem?
# or a mask?
# os x double buffering something?
@ -194,7 +223,7 @@ class Animation( wx.Window ):
dc.SetUserScale( scale, scale )
dc.DrawBitmap( wx_bmp, 0, 0 )
dc.DrawBitmap( self._frame_bmp, 0, 0 )
dc.SetUserScale( 1.0, 1.0 )
@ -209,13 +238,11 @@ class Animation( wx.Window ):
# will need to setdirty on drag that reveals offscreen region
# hence prob a good idea to give the bmp 100px or so spare offscreen buffer, to reduce redraw spam, if that can be neatly done
mdc = wx.MemoryDC( wx_bmp )
mdc = wx.MemoryDC( self._frame_bmp )
dc.StretchBlit( 0, 0, my_width, my_height, mdc, 0, 0, frame_width, frame_height )
wx_bmp.Destroy()
if self._animation_bar is not None:
self._animation_bar.GotoFrame( self._current_frame_index )
@ -446,6 +473,13 @@ class Animation( wx.Window ):
if self._animation_bar is not None:
buffer_indices = self._video_container.GetBufferIndices()
self._animation_bar.SetBufferIndices( buffer_indices )
except wx.PyDeadObjectError:
@ -478,6 +512,7 @@ class AnimationBar( wx.Window ):
self._media_window = media_window
self._num_frames = self._media.GetNumFrames()
self._current_frame_index = 0
self._buffer_indices = None
self._currently_in_a_drag = False
self._it_was_playing = False
@ -492,6 +527,13 @@ class AnimationBar( wx.Window ):
self._timer_update.Start( 100, wx.TIMER_CONTINUOUS )
def _GetXFromFrameIndex( self, index, width_offset = 0 ):
( my_width, my_height ) = self._canvas_bmp.GetSize()
return int( float( my_width - width_offset ) * float( index ) / float( self._num_frames - 1 ) )
def _Redraw( self, dc ):
( my_width, my_height ) = self._canvas_bmp.GetSize()
@ -517,9 +559,66 @@ class AnimationBar( wx.Window ):
#
if self._buffer_indices is not None:
( start_index, rendered_to_index, end_index ) = self._buffer_indices
start_x = self._GetXFromFrameIndex( start_index )
rendered_to_x = self._GetXFromFrameIndex( rendered_to_index )
end_x = self._GetXFromFrameIndex( end_index )
if start_x != rendered_to_x:
( r, g, b ) = background_colour.Get()
r = int( r * 0.85 )
g = int( g * 0.85 )
rendered_colour = wx.Colour( r, g, b )
dc.SetBrush( wx.Brush( rendered_colour ) )
if rendered_to_x > start_x:
dc.DrawRectangle( start_x, 0, rendered_to_x - start_x, ANIMATED_SCANBAR_HEIGHT )
else:
dc.DrawRectangle( start_x, 0, my_width - start_x, ANIMATED_SCANBAR_HEIGHT )
dc.DrawRectangle( 0, 0, rendered_to_x, ANIMATED_SCANBAR_HEIGHT )
if rendered_to_x != end_x:
( r, g, b ) = background_colour.Get()
r = int( r * 0.93 )
g = int( g * 0.93 )
to_be_rendered_colour = wx.Colour( r, g, b )
dc.SetBrush( wx.Brush( to_be_rendered_colour ) )
if end_x > rendered_to_x:
dc.DrawRectangle( rendered_to_x, 0, end_x - rendered_to_x, ANIMATED_SCANBAR_HEIGHT )
else:
dc.DrawRectangle( rendered_to_x, 0, my_width - rendered_to_x, ANIMATED_SCANBAR_HEIGHT )
dc.DrawRectangle( 0, 0, end_x, ANIMATED_SCANBAR_HEIGHT )
dc.SetBrush( wx.Brush( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNSHADOW ) ) )
dc.DrawRectangle( int( float( my_width - ANIMATED_SCANBAR_CARET_WIDTH ) * float( self._current_frame_index ) / float( self._num_frames - 1 ) ), 0, ANIMATED_SCANBAR_CARET_WIDTH, ANIMATED_SCANBAR_HEIGHT )
caret_x = self._GetXFromFrameIndex( self._current_frame_index, width_offset = ANIMATED_SCANBAR_CARET_WIDTH )
dc.DrawRectangle( caret_x, 0, ANIMATED_SCANBAR_CARET_WIDTH, ANIMATED_SCANBAR_HEIGHT )
#
@ -621,6 +720,18 @@ class AnimationBar( wx.Window ):
self.Refresh()
def SetBufferIndices( self, buffer_indices ):
if buffer_indices != self._buffer_indices:
self._buffer_indices = buffer_indices
self._dirty = True
self.Refresh()
def SetPaused( self, paused ):
self._paused = paused
@ -3370,7 +3481,7 @@ class MediaContainer( wx.Window ):
elif action == CC.MEDIA_VIEWER_SHOW_OPEN_EXTERNALLY_BUTTON:
self._media_window = OpenExternallyButton( self, self._media )
self._media_window = OpenExternallyPanel( self, self._media )
else:
@ -3559,6 +3670,8 @@ class EmbedButton( wx.Window ):
self._canvas_bmp = wx.EmptyBitmap( x, y, 24 )
self.SetCursor( wx.StockCursor( wx.CURSOR_HAND ) )
self.Bind( wx.EVT_PAINT, self.EventPaint )
self.Bind( wx.EVT_SIZE, self.EventResize )
self.Bind( wx.EVT_ERASE_BACKGROUND, self.EventEraseBackground )
@ -3572,9 +3685,7 @@ class EmbedButton( wx.Window ):
dc.SetBackground( background_brush )
dc.Clear() # gcdc doesn't support clear
dc = wx.GCDC( dc )
dc.Clear()
center_x = x / 2
center_y = y / 2
@ -3644,17 +3755,45 @@ class EmbedButton( wx.Window ):
class OpenExternallyButton( wx.Button ):
class OpenExternallyPanel( wx.Panel ):
def __init__( self, parent, media ):
wx.Button.__init__( self, parent, label = 'open externally', size = OPEN_EXTERNALLY_BUTTON_SIZE )
wx.Panel.__init__( self, parent )
self.SetCursor( wx.StockCursor( wx.CURSOR_ARROW ) )
self.SetBackgroundColour( wx.Colour( *HC.options[ 'gui_colours' ][ 'media_background' ] ) )
self._media = media
self.Bind( wx.EVT_BUTTON, self.EventButton )
vbox = wx.BoxSizer( wx.VERTICAL )
if self._media.GetLocationsManager().HasLocal() and self._media.GetMime() in HC.MIMES_WITH_THUMBNAILS:
hash = self._media.GetHash()
thumbnail_path = HydrusGlobals.client_controller.GetClientFilesManager().GetFullSizeThumbnailPath( hash )
bmp = ClientRendering.GenerateHydrusBitmap( thumbnail_path ).GetWxBitmap()
thumbnail = ClientGUICommon.BufferedWindowIcon( self, bmp )
thumbnail.Bind( wx.EVT_LEFT_DOWN, self.EventButton )
vbox.AddF( thumbnail, CC.FLAGS_CENTER )
m_text = HC.mime_string_lookup[ media.GetMime() ]
button = wx.Button( self, label = 'open ' + m_text + ' externally', size = OPEN_EXTERNALLY_BUTTON_SIZE )
vbox.AddF( button, CC.FLAGS_CENTER )
self.SetSizer( vbox )
self.SetCursor( wx.StockCursor( wx.CURSOR_HAND ) )
self.Bind( wx.EVT_LEFT_DOWN, self.EventButton )
button.Bind( wx.EVT_BUTTON, self.EventButton )
def EventButton( self, event ):

View File

@ -1481,7 +1481,7 @@ class BetterChoice( wx.Choice ):
selection = self.GetSelection()
if selection != wx.NOT_FOUND: return self.GetClientData( selection )
else: raise Exception( 'Choice not chosen!' )
else: return self.GetClientData( 0 )
def SelectClientData( self, client_data ):
@ -1496,6 +1496,8 @@ class BetterChoice( wx.Choice ):
self.Select( 0 )
class CheckboxCollect( wx.combo.ComboCtrl ):
@ -1647,20 +1649,7 @@ class ChoiceSort( BetterChoice ):
self._page_key = page_key
sort_choices = list( CC.SORT_CHOICES )
if add_namespaces_and_ratings:
sort_choices.extend( HC.options[ 'sort_by' ] )
ratings_services = HydrusGlobals.client_controller.GetServicesManager().GetServices( ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ) )
for ratings_service in ratings_services:
sort_choices.append( ( 'rating_descend', ratings_service ) )
sort_choices.append( ( 'rating_ascend', ratings_service ) )
sort_choices = ClientData.GetSortChoices( add_namespaces_and_ratings = add_namespaces_and_ratings )
for ( sort_by_type, sort_by_data ) in sort_choices:
@ -3594,50 +3583,6 @@ class ListBoxTagsStringsAddRemove( ListBoxTagsStrings ):
self._RemoveTags( tags )
class ListBoxTagsSuggestions( ListBoxTagsStrings ):
def __init__( self, parent, activate_callable ):
ListBoxTagsStrings.__init__( self, parent )
self._activate_callable = activate_callable
def _Activate( self ):
if len( self._selected_terms ) > 0:
tags = set( self._selected_terms )
self._activate_callable( tags )
def SetTags( self, tags ):
ListBoxTagsStrings.SetTags( self, tags )
width = HydrusGlobals.client_controller.GetNewOptions().GetNoneableInteger( 'suggested_tags_width' )
if width is None:
if len( tags ) > 0:
dc = wx.MemoryDC( self._client_bmp )
dc.SetFont( wx.SystemSettings.GetFont( wx.SYS_DEFAULT_GUI_FONT ) )
width = max( ( dc.GetTextExtent( s )[0] for s in self._ordered_strings ) )
self.SetMinClientSize( ( width + 2 * self.TEXT_X_PADDING, -1 ) )
else:
self.SetMinSize( ( width, -1 ) )
class ListBoxTagsPredicates( ListBoxTags ):
delete_key_activates = True

View File

@ -1,4 +1,3 @@
import Crypto.PublicKey.RSA
import HydrusConstants as HC
import ClientDefaults
import ClientDownloading
@ -1505,28 +1504,28 @@ class DialogInputLocalFiles( Dialog ):
self._gauge_cancel.Bind( wx.EVT_BUTTON, self.EventGaugeCancel )
self._gauge_cancel.Disable()
self._add_files_button = wx.Button( self, label = 'Add Files' )
self._add_files_button = wx.Button( self, label = 'add files' )
self._add_files_button.Bind( wx.EVT_BUTTON, self.EventAddPaths )
self._add_folder_button = wx.Button( self, label = 'Add Folder' )
self._add_folder_button = wx.Button( self, label = 'add folder' )
self._add_folder_button.Bind( wx.EVT_BUTTON, self.EventAddFolder )
self._remove_files_button = wx.Button( self, label = 'Remove Files' )
self._remove_files_button = wx.Button( self, label = 'remove files' )
self._remove_files_button.Bind( wx.EVT_BUTTON, self.EventRemovePaths )
self._import_file_options = ClientGUICollapsible.CollapsibleOptionsImportFiles( self )
self._delete_after_success = wx.CheckBox( self, label = 'delete original files after successful import' )
self._add_button = wx.Button( self, label = 'Import now' )
self._add_button = wx.Button( self, label = 'import now' )
self._add_button.Bind( wx.EVT_BUTTON, self.EventOK )
self._add_button.SetForegroundColour( ( 0, 128, 0 ) )
self._tag_button = wx.Button( self, label = 'Add tags based on filename' )
self._tag_button = wx.Button( self, label = 'add tags based on filename' )
self._tag_button.Bind( wx.EVT_BUTTON, self.EventTags )
self._tag_button.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'Cancel' )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.Bind( wx.EVT_BUTTON, self.EventCancel )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
@ -3627,6 +3626,8 @@ class DialogPathsToTags( Dialog ):
( base, filename ) = os.path.split( path )
( filename, any_ext_gumpf ) = os.path.splitext( filename )
if self._filename_checkbox.IsChecked():
namespace = self._filename_namespace.GetValue()

View File

@ -1731,7 +1731,7 @@ class MediaPanelThumbnails( MediaPanel ):
thumbnails_to_render_later = []
for thumbnail in thumbnails:
for thumbnail in visible_thumbnails:
if thumbnail_cache.HasThumbnailCached( thumbnail ):
@ -3117,7 +3117,7 @@ class Thumbnail( Selectable ):
dc.DrawBitmap( wx_bmp, x_offset, y_offset )
wx.CallAfter( wx_bmp.Destroy )
wx_bmp.Destroy()
namespaces = self.GetTagsManager().GetCombinedNamespaces( ( 'creator', 'series', 'title', 'volume', 'chapter', 'page' ) )

View File

@ -5,6 +5,7 @@ import ClientDownloading
import ClientGUICommon
import ClientGUIDialogs
import ClientGUIPredicates
import ClientGUITagSuggestions
import ClientGUITopLevelWindows
import ClientMedia
import ClientThreading
@ -1703,7 +1704,7 @@ class ManageOptionsPanel( ManagePanel ):
self._default_sort = ClientGUICommon.ChoiceSort( self )
self._sort_fallback = ClientGUICommon.ChoiceSort( self, add_namespaces_and_ratings = False )
self._sort_fallback = ClientGUICommon.ChoiceSort( self )
self._default_collect = ClientGUICommon.CheckboxCollect( self )
@ -1724,7 +1725,14 @@ class ManageOptionsPanel( ManagePanel ):
self._default_sort.SetSelection( 0 )
self._sort_fallback.SetSelection( HC.options[ 'sort_fallback' ] )
try:
self._sort_fallback.SetSelection( HC.options[ 'sort_fallback' ] )
except:
self._sort_fallback.SetSelection( 0 )
for ( sort_by_type, sort_by ) in HC.options[ 'sort_by' ]:
@ -2175,6 +2183,16 @@ class ManageOptionsPanel( ManagePanel ):
self._suggested_favourites_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( suggested_tags_favourites_panel, self._suggested_favourites.AddTags, expand_parents, CC.LOCAL_FILE_SERVICE_KEY, CC.LOCAL_TAG_SERVICE_KEY )
suggested_tags_related_panel = ClientGUICommon.StaticBox( suggested_tags_panel, 'related' )
self._show_related_tags = wx.CheckBox( suggested_tags_related_panel )
self._related_tags_width = wx.SpinCtrl( suggested_tags_related_panel, min = 60, max = 400 )
self._related_tags_search_1_duration_ms = wx.SpinCtrl( suggested_tags_related_panel, min = 50, max = 60000 )
self._related_tags_search_2_duration_ms = wx.SpinCtrl( suggested_tags_related_panel, min = 50, max = 60000 )
self._related_tags_search_3_duration_ms = wx.SpinCtrl( suggested_tags_related_panel, min = 50, max = 60000 )
#
if HC.options[ 'default_tag_sort' ] == CC.SORT_BY_LEXICOGRAPHIC_ASC: self._default_tag_sort.Select( 0 )
@ -2200,6 +2218,14 @@ class ManageOptionsPanel( ManagePanel ):
self._suggested_favourites_services.SelectClientData( CC.LOCAL_TAG_SERVICE_KEY )
self._show_related_tags.SetValue( self._new_options.GetBoolean( 'show_related_tags' ) )
self._related_tags_width.SetValue( self._new_options.GetInteger( 'related_tags_width' ) )
self._related_tags_search_1_duration_ms.SetValue( self._new_options.GetInteger( 'related_tags_search_1_duration_ms' ) )
self._related_tags_search_2_duration_ms.SetValue( self._new_options.GetInteger( 'related_tags_search_2_duration_ms' ) )
self._related_tags_search_3_duration_ms.SetValue( self._new_options.GetInteger( 'related_tags_search_3_duration_ms' ) )
#
gridbox = wx.FlexGridSizer( 0, 2 )
@ -2222,8 +2248,30 @@ class ManageOptionsPanel( ManagePanel ):
suggested_tags_favourites_panel.AddF( self._suggested_favourites, CC.FLAGS_EXPAND_BOTH_WAYS )
suggested_tags_favourites_panel.AddF( self._suggested_favourites_input, CC.FLAGS_EXPAND_PERPENDICULAR )
related_gridbox = wx.FlexGridSizer( 0, 2 )
related_gridbox.AddGrowableCol( 1, 1 )
related_gridbox.AddF( wx.StaticText( suggested_tags_related_panel, label = 'show related tags on single-file manage tags windows' ), CC.FLAGS_MIXED )
related_gridbox.AddF( self._show_related_tags, CC.FLAGS_MIXED )
related_gridbox.AddF( wx.StaticText( suggested_tags_related_panel, label = 'width of related tags list' ), CC.FLAGS_MIXED )
related_gridbox.AddF( self._related_tags_width, CC.FLAGS_MIXED )
related_gridbox.AddF( wx.StaticText( suggested_tags_related_panel, label = 'search 1 duration (ms)' ), CC.FLAGS_MIXED )
related_gridbox.AddF( self._related_tags_search_1_duration_ms, CC.FLAGS_MIXED )
related_gridbox.AddF( wx.StaticText( suggested_tags_related_panel, label = 'search 2 duration (ms)' ), CC.FLAGS_MIXED )
related_gridbox.AddF( self._related_tags_search_2_duration_ms, CC.FLAGS_MIXED )
related_gridbox.AddF( wx.StaticText( suggested_tags_related_panel, label = 'search 3 duration (ms)' ), CC.FLAGS_MIXED )
related_gridbox.AddF( self._related_tags_search_3_duration_ms, CC.FLAGS_MIXED )
suggested_tags_related_panel.AddF( related_gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
suggested_tags_panel.AddF( self._suggested_tags_width, CC.FLAGS_EXPAND_PERPENDICULAR )
suggested_tags_panel.AddF( suggested_tags_favourites_panel, CC.FLAGS_EXPAND_SIZER_DEPTH_ONLY )
suggested_tags_panel.AddF( suggested_tags_related_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox = wx.BoxSizer( wx.VERTICAL )
@ -2284,6 +2332,15 @@ class ManageOptionsPanel( ManagePanel ):
self._new_options.SetSuggestedTagsFavourites( service_key, favourites )
self._new_options.SetBoolean( 'show_related_tags', self._show_related_tags.GetValue() )
self._new_options.SetInteger( 'related_tags_width', self._related_tags_width.GetValue() )
self._new_options.SetInteger( 'related_tags_search_1_duration_ms', self._related_tags_search_1_duration_ms.GetValue() )
self._new_options.SetInteger( 'related_tags_search_2_duration_ms', self._related_tags_search_2_duration_ms.GetValue() )
self._new_options.SetInteger( 'related_tags_search_3_duration_ms', self._related_tags_search_3_duration_ms.GetValue() )
def CommitChanges( self ):
@ -2556,6 +2613,8 @@ class ManageTagsPanel( ManagePanel ):
self.SetMedia( media )
self._suggested_tags = ClientGUITagSuggestions.SuggestedTagsPanel( self, self._tag_service_key, self._media, self.AddTags )
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
if self._i_am_local_tag_service:
@ -2592,17 +2651,7 @@ class ManageTagsPanel( ManagePanel ):
hbox = wx.BoxSizer( wx.HORIZONTAL )
favourites = self._new_options.GetSuggestedTagsFavourites( tag_service_key )
if len( favourites ) > 0:
suggested_tags = ClientGUICommon.ListBoxTagsSuggestions( self, self.AddTags )
suggested_tags.SetTags( favourites )
hbox.AddF( suggested_tags, CC.FLAGS_EXPAND_PERPENDICULAR )
hbox.AddF( self._suggested_tags, CC.FLAGS_EXPAND_PERPENDICULAR )
hbox.AddF( vbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
#

View File

@ -0,0 +1,227 @@
import ClientConstants as CC
import ClientData
import ClientGUICommon
import ClientSearch
import collections
import HydrusConstants as HC
import HydrusGlobals
import wx
class ListBoxTagsSuggestionsFavourites( ClientGUICommon.ListBoxTagsStrings ):
def __init__( self, parent, activate_callable ):
ClientGUICommon.ListBoxTagsStrings.__init__( self, parent )
self._activate_callable = activate_callable
def _Activate( self ):
if len( self._selected_terms ) > 0:
tags = set( self._selected_terms )
self._activate_callable( tags )
def SetTags( self, tags ):
ClientGUICommon.ListBoxTagsStrings.SetTags( self, tags )
width = HydrusGlobals.client_controller.GetNewOptions().GetNoneableInteger( 'suggested_tags_width' )
if width is None:
if len( self._ordered_strings ) > 0:
dc = wx.MemoryDC( self._client_bmp )
dc.SetFont( wx.SystemSettings.GetFont( wx.SYS_DEFAULT_GUI_FONT ) )
width = max( ( dc.GetTextExtent( s )[0] for s in self._ordered_strings ) )
self.SetMinClientSize( ( width + 2 * self.TEXT_X_PADDING, -1 ) )
else:
self.SetMinSize( ( width, -1 ) )
wx.PostEvent( self.GetParent(), CC.SizeChangedEvent( -1 ) )
class ListBoxTagsSuggestionsRelated( ClientGUICommon.ListBoxTags ):
def __init__( self, parent, activate_callable ):
ClientGUICommon.ListBoxTags.__init__( self, parent )
self._activate_callable = activate_callable
width = HydrusGlobals.client_controller.GetNewOptions().GetInteger( 'related_tags_width' )
self.SetMinSize( ( 200, -1 ) )
def _Activate( self ):
if len( self._selected_terms ) > 0:
tags = { predicate.GetValue() for predicate in self._selected_terms }
self._activate_callable( tags )
def SetPredicates( self, predicates ):
self._ordered_strings = []
self._strings_to_terms = {}
for predicate in predicates:
tag_string = predicate.GetValue()
self._ordered_strings.append( tag_string )
self._strings_to_terms[ tag_string ] = predicate
self._TextsHaveChanged()
class RelatedTagsPanel( wx.Panel ):
def __init__( self, parent, service_key, media, activate_callable ):
wx.Panel.__init__( self, parent )
self._service_key = service_key
self._media = media
self._new_options = HydrusGlobals.client_controller.GetNewOptions()
vbox = wx.BoxSizer( wx.VERTICAL )
button_1 = wx.Button( self, label = '1' )
button_1.Bind( wx.EVT_BUTTON, self.EventSuggestedRelatedTags1 )
button_1.SetMinSize( ( 30, -1 ) )
button_2 = wx.Button( self, label = '2' )
button_2.Bind( wx.EVT_BUTTON, self.EventSuggestedRelatedTags2 )
button_2.SetMinSize( ( 30, -1 ) )
button_3 = wx.Button( self, label = '3' )
button_3.Bind( wx.EVT_BUTTON, self.EventSuggestedRelatedTags3 )
button_3.SetMinSize( ( 30, -1 ) )
self._related_tags = ListBoxTagsSuggestionsRelated( self, activate_callable )
button_hbox = wx.BoxSizer( wx.HORIZONTAL )
button_hbox.AddF( button_1, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
button_hbox.AddF( button_2, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
button_hbox.AddF( button_3, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
vbox.AddF( button_hbox, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._related_tags, CC.FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( vbox )
def _FetchRelatedTags( self, max_time_to_take ):
( m, ) = self._media
hash = m.GetHash()
( current_tags_to_count, deleted_tags_to_count, pending_tags_to_count, petitioned_tags_to_count ) = ClientData.GetMediasTagCount( self._media, tag_service_key = self._service_key, collapse_siblings = False )
tags_to_count = collections.Counter()
tags_to_count.update( current_tags_to_count )
tags_to_count.update( pending_tags_to_count )
search_tags = set( tags_to_count.keys() )
max_results = 100
predicates = HydrusGlobals.client_controller.Read( 'related_tags', self._service_key, hash, search_tags, max_results, max_time_to_take )
siblings_manager = HydrusGlobals.client_controller.GetManager( 'tag_siblings' )
predicates = siblings_manager.CollapsePredicates( predicates )
predicates = ClientSearch.SortPredicates( predicates )
self._related_tags.SetPredicates( predicates )
def EventSuggestedRelatedTags1( self, event ):
max_time_to_take = self._new_options.GetInteger( 'related_tags_search_1_duration_ms' ) / 1000.0
self._FetchRelatedTags( max_time_to_take )
def EventSuggestedRelatedTags2( self, event ):
max_time_to_take = self._new_options.GetInteger( 'related_tags_search_2_duration_ms' ) / 1000.0
self._FetchRelatedTags( max_time_to_take )
def EventSuggestedRelatedTags3( self, event ):
max_time_to_take = self._new_options.GetInteger( 'related_tags_search_3_duration_ms' ) / 1000.0
self._FetchRelatedTags( max_time_to_take )
class SuggestedTagsPanel( wx.Panel ):
def __init__( self, parent, service_key, media, activate_callable ):
wx.Panel.__init__( self, parent )
self._service_key = service_key
self._media = media
self._new_options = HydrusGlobals.client_controller.GetNewOptions()
something_to_show = False
hbox = wx.BoxSizer( wx.HORIZONTAL )
favourites = self._new_options.GetSuggestedTagsFavourites( service_key )
if len( favourites ) > 0:
favourite_tags = ListBoxTagsSuggestionsFavourites( self, activate_callable )
favourite_tags.SetTags( favourites )
hbox.AddF( favourite_tags, CC.FLAGS_EXPAND_PERPENDICULAR )
something_to_show = True
if self._new_options.GetBoolean( 'show_related_tags' ) and len( media ) == 1:
related_tags = RelatedTagsPanel( self, service_key, media, activate_callable )
hbox.AddF( related_tags, CC.FLAGS_EXPAND_BOTH_WAYS )
something_to_show = True
self.SetSizer( hbox )
if not something_to_show:
self.Hide()

View File

@ -1435,7 +1435,7 @@ class PageOfImagesImport( HydrusSerialisable.SerialisableBase ):
parser_status = 'page checked OK - ' + HydrusData.ConvertIntToPrettyString( num_new ) + ' new files'
parser_status = 'page checked OK - ' + HydrusData.ConvertIntToPrettyString( num_new ) + ' new urls'
except HydrusExceptions.NotFoundException:
@ -2339,7 +2339,7 @@ class Subscription( HydrusSerialisable.SerialisableBaseNamed ):
keep_checking = False
job_key.SetVariable( 'popup_text_1', prefix + ': found ' + HydrusData.ConvertIntToPrettyString( total_new_urls ) + ' new files' )
job_key.SetVariable( 'popup_text_1', prefix + ': found ' + HydrusData.ConvertIntToPrettyString( total_new_urls ) + ' new urls' )
ClientData.WaitPolitely()
@ -2780,7 +2780,7 @@ class ThreadWatcherImport( HydrusSerialisable.SerialisableBase ):
watcher_status = 'thread checked OK - ' + HydrusData.ConvertIntToPrettyString( num_new ) + ' new files'
watcher_status = 'thread checked OK - ' + HydrusData.ConvertIntToPrettyString( num_new ) + ' new urls'
except HydrusExceptions.NotFoundException:

View File

@ -791,7 +791,18 @@ class MediaList( object ):
self._sort_by = sort_by
sort_function = self._GetSortFunction( ( 'system', HC.options[ 'sort_fallback' ] ) )
sort_choices = ClientData.GetSortChoices( add_namespaces_and_ratings = True )
try:
sort_by_fallback = sort_choices[ HC.options[ 'sort_fallback' ] ]
except IndexError:
sort_by_fallback = sort_choices[ 0 ]
sort_function = self._GetSortFunction( sort_by_fallback )
self._sorted_media.sort( sort_function )

View File

@ -15,8 +15,6 @@ import wx
def GenerateHydrusBitmap( path, compressed = True ):
new_options = HydrusGlobals.client_controller.GetNewOptions()
numpy_image = ClientImageHandling.GenerateNumpyImage( path )
return GenerateHydrusBitmapFromNumPyImage( numpy_image, compressed = compressed )
@ -154,13 +152,13 @@ class RasterContainerVideo( RasterContainer ):
RasterContainer.__init__( self, media, target_resolution )
self._frames = {}
self._last_index_asked_for = -1
self._buffer_start_index = -1
self._buffer_end_index = -1
self._renderer_awake = False
self._stop = False
self._render_event = threading.Event()
( x, y ) = self._target_resolution
new_options = HydrusGlobals.client_controller.GetNewOptions()
@ -176,9 +174,11 @@ class RasterContainerVideo( RasterContainer ):
# 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:
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:
frame_buffer_length = int( num_frames * 0.1 )
frame_buffer_length = max_streaming_buffer_size
self._num_frames_backwards = frame_buffer_length * 2 / 3
@ -195,22 +195,26 @@ class RasterContainerVideo( RasterContainer ):
self._durations = HydrusImageHandling.GetGIFFrameDurations( self._path )
self._renderer = ClientVideoHandling.GIFRenderer( path, num_frames, target_resolution )
self._renderer = ClientVideoHandling.GIFRenderer( path, num_frames, self._target_resolution )
else:
self._renderer = HydrusVideoHandling.VideoRendererFFMPEG( path, mime, duration, num_frames, target_resolution )
self._renderer = HydrusVideoHandling.VideoRendererFFMPEG( path, mime, duration, num_frames, self._target_resolution )
self._render_lock = threading.Lock()
self._buffer_lock = threading.Lock()
self._last_index_rendered = -1
self._next_render_index = -1
self._render_to_index = -1
self._rendered_first_frame = False
self._rush_to_index = None
self.GetReadyForFrame( init_position )
HydrusGlobals.client_controller.CallToThread( self.THREADRender )
def _IndexOutOfRange( self, index, range_start, range_end ):
@ -248,7 +252,7 @@ class RasterContainerVideo( RasterContainer ):
def THREADMoveBuffer( self, render_to_index ):
def THREADMoveRenderTo( self, render_to_index ):
with self._render_lock:
@ -256,10 +260,7 @@ class RasterContainerVideo( RasterContainer ):
self._render_to_index = render_to_index
if not self._renderer_awake:
HydrusGlobals.client_controller.CallToThread( self.THREADRender )
self._render_event.set()
@ -272,33 +273,43 @@ class RasterContainerVideo( RasterContainer ):
self._renderer.set_position( start_index )
self._last_index_rendered = -1
self._next_render_index = start_index
self._rush_to_index = rush_to_index
self._render_to_index = render_to_index
HydrusGlobals.client_controller.CallToThread( self.THREADRender, rush_to_index )
self._render_event.set()
def THREADRender( self, rush_to_index = None ):
def THREADRushTo( self, rush_to_index ):
with self._render_lock:
self._rush_to_index = rush_to_index
self._render_event.set()
def THREADRender( self ):
num_frames = self._media.GetNumFrames()
while True:
if self._stop:
self._renderer_awake = False
if self._stop or HydrusGlobals.view_shutdown:
return
with self._render_lock:
if not self._rendered_first_frame or self._next_render_index != ( self._render_to_index + 1 ) % num_frames:
self._renderer_awake = True
if not self._rendered_first_frame or self._next_render_index != ( self._render_to_index + 1 ) % num_frames:
with self._render_lock:
self._rendered_first_frame = True
@ -312,15 +323,35 @@ class RasterContainerVideo( RasterContainer ):
HydrusData.ShowException( e )
self._renderer_awake = False
return
finally:
self._last_index_rendered = frame_index
self._next_render_index = ( self._next_render_index + 1 ) % num_frames
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:
frame = GenerateHydrusBitmapFromNumPyImage( numpy_image, compressed = False )
with self._buffer_lock:
@ -328,27 +359,39 @@ class RasterContainerVideo( RasterContainer ):
self._frames[ frame_index ] = frame
if self._rush_to_index is not None:
time.sleep( 0.00001 )
else:
self._renderer_awake = False
half_a_frame = ( self._average_frame_duration / 1000.0 ) * 0.5
return
time.sleep( half_a_frame ) # just so we don't spam cpu
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
self._render_event.wait( 1 )
time.sleep( half_a_frame ) # just so we don't spam cpu
self._render_event.clear()
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 )
def GetDuration( self, index ):
if self._media.GetMime() == HC.IMAGE_GIF: return self._durations[ index ]
@ -362,9 +405,7 @@ class RasterContainerVideo( RasterContainer ):
frame = self._frames[ index ]
self._last_index_asked_for = index
self.GetReadyForFrame( self._last_index_asked_for + 1 )
self.GetReadyForFrame( index + 1 )
return frame
@ -409,7 +450,7 @@ class RasterContainerVideo( RasterContainer ):
self._buffer_end_index = ideal_buffer_end_index
HydrusGlobals.client_controller.CallToThread( self.THREADMoveBuffer, self._buffer_end_index )
HydrusGlobals.client_controller.CallToThread( self.THREADMoveRenderTo, self._buffer_end_index )
else:
@ -426,9 +467,7 @@ class RasterContainerVideo( RasterContainer ):
if not self.HasFrame( next_index_to_expect ):
# this rushes rendering to this point
HydrusGlobals.client_controller.CallToThread( self.THREADRender, next_index_to_expect )
HydrusGlobals.client_controller.CallToThread( self.THREADRushTo, next_index_to_expect )
@ -494,6 +533,23 @@ class HydrusBitmap( object ):
def CopyToWxBitmap( self, wx_bmp ):
wx_bmp.CopyFromBuffer( self._GetData(), self._format )
def GetDepth( self ):
if self._format == wx.BitmapBufferFormat_RGB:
return 3
elif self._format == wx.BitmapBufferFormat_RGBA:
return 4
def GetWxBitmap( self ):
( width, height ) = self._size

View File

@ -48,7 +48,7 @@ options = {}
# Misc
NETWORK_VERSION = 17
SOFTWARE_VERSION = 216
SOFTWARE_VERSION = 217
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )

View File

@ -308,13 +308,17 @@ class HydrusResourceCommand( Resource ):
content_type = HC.mime_string_lookup[ mime ]
content_disposition = 'inline'
else:
mime = HydrusFileHandling.GetMime( path )
( base, filename ) = os.path.split( path )
content_type = HC.mime_string_lookup[ mime ] + '; ' + filename
content_type = HC.mime_string_lookup[ mime ]
content_disposition = 'inline; filename="' + filename + '"'
content_length = size
@ -322,6 +326,7 @@ class HydrusResourceCommand( Resource ):
# can't be unicode!
request.setHeader( 'Content-Type', str( content_type ) )
request.setHeader( 'Content-Length', str( content_length ) )
request.setHeader( 'Content-Disposition', str( content_disposition ) )
request.setHeader( 'Expires', time.strftime( '%a, %d %b %Y %H:%M:%S GMT', time.gmtime( time.time() + 86400 * 365 ) ) )
request.setHeader( 'Cache-Control', str( 86400 * 365 ) )

View File

@ -21,7 +21,6 @@ from include import TestConstants
from include import TestDialogs
from include import TestDB
from include import TestFunctions
from include import TestHydrusEncryption
from include import TestClientImageHandling
from include import TestHydrusNATPunch
from include import TestHydrusServer
@ -242,7 +241,6 @@ class Controller( object ):
if run_all or only_run == 'dialogs': suites.append( unittest.TestLoader().loadTestsFromModule( TestDialogs ) )
if run_all or only_run == 'db': suites.append( unittest.TestLoader().loadTestsFromModule( TestDB ) )
if run_all or only_run == 'downloading': suites.append( unittest.TestLoader().loadTestsFromModule( TestClientDownloading ) )
if run_all or only_run == 'encryption': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusEncryption ) )
if run_all or only_run == 'functions': suites.append( unittest.TestLoader().loadTestsFromModule( TestFunctions ) )
if run_all or only_run == 'image': suites.append( unittest.TestLoader().loadTestsFromModule( TestClientImageHandling ) )
if run_all or only_run == 'nat': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusNATPunch ) )