Version 217
This commit is contained in:
parent
1698dfdee1
commit
2aec39b034
|
@ -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>
|
||||
|
|
|
@ -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' ]
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -7,7 +7,6 @@ import httplib
|
|||
import itertools
|
||||
import HydrusConstants as HC
|
||||
import HydrusData
|
||||
import HydrusEncryption
|
||||
import HydrusExceptions
|
||||
import HydrusImageHandling
|
||||
import HydrusNATPunch
|
||||
|
|
|
@ -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 } ) ]
|
||||
|
||||
|
|
|
@ -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 ):
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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' ) )
|
||||
|
||||
|
|
|
@ -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 )
|
||||
|
||||
#
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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 )
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -48,7 +48,7 @@ options = {}
|
|||
# Misc
|
||||
|
||||
NETWORK_VERSION = 17
|
||||
SOFTWARE_VERSION = 216
|
||||
SOFTWARE_VERSION = 217
|
||||
|
||||
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
|
||||
|
||||
|
|
|
@ -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 ) )
|
||||
|
|
2
test.py
2
test.py
|
@ -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 ) )
|
||||
|
|
Loading…
Reference in New Issue