Version 116
This commit is contained in:
parent
239b2ce31b
commit
2d4353a232
|
@ -8,6 +8,14 @@
|
|||
<div class="content">
|
||||
<h3>changelog</h3>
|
||||
<ul>
|
||||
<li><h3>version 116</h3></li>
|
||||
<ul>
|
||||
<li>gifs now render their frames as needed, rather than building a giant memory-hogging cache of all of them</li>
|
||||
<li>webms now have thumbnails!</li>
|
||||
<li>refactored important hydrus threading code to a separate module</li>
|
||||
<li>added new 'call-to' worker threads for snappier job-thread behaviour</li>
|
||||
<li>fixed a typo that was stopping new repositories being entered in the manage repositories dialog</li>
|
||||
</ul>
|
||||
<li><h3>version 115</h3></li>
|
||||
<ul>
|
||||
<li>moved review and manage services windows to a local/remote dichotomy</li>
|
||||
|
|
|
@ -20,7 +20,6 @@ import sqlite3
|
|||
import sys
|
||||
import threading
|
||||
import time
|
||||
import threading
|
||||
import traceback
|
||||
import urllib
|
||||
import yaml
|
||||
|
@ -650,7 +649,7 @@ def GetThumbnailPath( hash, full_size = True ):
|
|||
|
||||
thumbnail_dimensions = HC.options[ 'thumbnail_dimensions' ]
|
||||
|
||||
thumbnail_resized = HydrusImageHandling.GenerateThumbnail( full_size_path, thumbnail_dimensions )
|
||||
thumbnail_resized = HydrusFileHandling.GenerateThumbnail( full_size_path, thumbnail_dimensions )
|
||||
|
||||
with open( path, 'wb' ) as f: f.write( thumbnail_resized )
|
||||
|
||||
|
@ -2009,8 +2008,6 @@ class MediaResult():
|
|||
|
||||
def GetTimestamp( self ): return self._tuple[4]
|
||||
|
||||
def IsAnimated( self ): return self.GetNumFrames() > 0
|
||||
|
||||
def ProcessContentUpdate( self, service_identifier, content_update ):
|
||||
|
||||
( data_type, action, row ) = content_update.ToTuple()
|
||||
|
@ -2113,7 +2110,7 @@ class RenderedImageCache():
|
|||
elif key in self._keys_being_rendered: return self._keys_being_rendered[ key ]
|
||||
else:
|
||||
|
||||
image_container = HydrusImageHandling.RenderImage( media, target_resolution )
|
||||
image_container = HydrusImageHandling.ImageContainerStatic( media, target_resolution )
|
||||
|
||||
self._keys_being_rendered[ key ] = image_container
|
||||
|
||||
|
@ -2430,7 +2427,7 @@ class ThumbnailCache():
|
|||
|
||||
path = HC.STATIC_DIR + os.path.sep + name + '.png'
|
||||
|
||||
thumbnail = HydrusImageHandling.GenerateThumbnail( path, HC.options[ 'thumbnail_dimensions' ] )
|
||||
thumbnail = HydrusFileHandling.GenerateThumbnail( path, HC.options[ 'thumbnail_dimensions' ] )
|
||||
|
||||
temp_path = HC.GetTempPath()
|
||||
|
||||
|
@ -2446,7 +2443,7 @@ class ThumbnailCache():
|
|||
|
||||
mime = media.GetDisplayMedia().GetMime()
|
||||
|
||||
if mime in HC.IMAGES:
|
||||
if mime in HC.MIMES_WITH_THUMBNAILS:
|
||||
|
||||
hash = media.GetDisplayMedia().GetHash()
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@ import random
|
|||
import sqlite3
|
||||
import threading
|
||||
import time
|
||||
import threading
|
||||
import traceback
|
||||
import yaml
|
||||
import wx
|
||||
|
|
|
@ -9,6 +9,7 @@ import HydrusImageHandling
|
|||
import HydrusSessions
|
||||
import HydrusServer
|
||||
import HydrusTags
|
||||
import HydrusThreading
|
||||
import ClientConstants as CC
|
||||
import ClientDB
|
||||
import ClientGUI
|
||||
|
@ -454,10 +455,7 @@ Once it is done, the client will restart.'''
|
|||
self.Yield() # this processes the event queue immediately, so the paint event can occur
|
||||
|
||||
|
||||
def StartFileQuery( self, query_key, search_context ):
|
||||
|
||||
threading.Thread( target = self.THREADDoFileQuery, name = 'file query', args = ( query_key, search_context ) ).start()
|
||||
|
||||
def StartFileQuery( self, query_key, search_context ): HydrusThreading.CallToThread( self.THREADDoFileQuery, query_key, search_context )
|
||||
|
||||
def THREADDoFileQuery( self, query_key, search_context ):
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import HydrusImageHandling
|
|||
import HydrusMessageHandling
|
||||
import HydrusServer
|
||||
import HydrusTags
|
||||
import HydrusThreading
|
||||
import ClientConstants as CC
|
||||
import ClientConstantsMessages
|
||||
import os
|
||||
|
@ -46,7 +47,7 @@ class FileDB():
|
|||
|
||||
with open( thumbnail_path, 'wb' ) as f: f.write( thumbnail )
|
||||
|
||||
thumbnail_resized = HydrusImageHandling.GenerateThumbnail( thumbnail_path, HC.options[ 'thumbnail_dimensions' ] )
|
||||
thumbnail_resized = HydrusFileHandling.GenerateThumbnail( thumbnail_path, HC.options[ 'thumbnail_dimensions' ] )
|
||||
|
||||
thumbnail_resized_path = CC.GetExpectedThumbnailPath( hash, False )
|
||||
|
||||
|
@ -3110,7 +3111,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
|
||||
if mime in HC.MIMES_WITH_THUMBNAILS:
|
||||
|
||||
thumbnail = HydrusImageHandling.GenerateThumbnail( path )
|
||||
thumbnail = HydrusFileHandling.GenerateThumbnail( path )
|
||||
|
||||
self._AddThumbnails( c, [ ( hash, thumbnail ) ] )
|
||||
|
||||
|
@ -4856,6 +4857,29 @@ class DB( ServiceDB ):
|
|||
c.execute( 'CREATE TABLE booru_shares ( service_id INTEGER REFERENCES services ( service_id ) ON DELETE CASCADE, share_key BLOB_BYTES, share TEXT_YAML, expiry INTEGER, used_monthly_data INTEGER, max_monthly_data INTEGER, ip_restriction TEXT, notes TEXT, PRIMARY KEY( service_id, share_key ) );' )
|
||||
|
||||
|
||||
if version == 115:
|
||||
|
||||
for path in CC.IterateAllFilePaths():
|
||||
|
||||
try:
|
||||
|
||||
filename = os.path.basename( path )
|
||||
|
||||
( hash_encoded, ext ) = filename.split( '.', 1 )
|
||||
|
||||
hash = hash_encoded.decode( 'hex' )
|
||||
|
||||
if ext == 'webm':
|
||||
|
||||
thumbnail = HydrusFileHandling.GenerateThumbnail( path )
|
||||
|
||||
with open( CC.GetExpectedThumbnailPath( hash ), 'wb' ) as f: f.write( thumbnail )
|
||||
|
||||
|
||||
except: print( traceback.format_exc())
|
||||
|
||||
|
||||
|
||||
c.execute( 'UPDATE version SET version = ?;', ( version + 1, ) )
|
||||
|
||||
HC.is_db_updated = True
|
||||
|
@ -7088,15 +7112,15 @@ class DB( ServiceDB ):
|
|||
|
||||
def StartDaemons( self ):
|
||||
|
||||
HC.DAEMONWorker( 'CheckImportFolders', DAEMONCheckImportFolders, ( 'notify_restart_import_folders_daemon', 'notify_new_import_folders' ), period = 180 )
|
||||
HC.DAEMONWorker( 'CheckExportFolders', DAEMONCheckExportFolders, ( 'notify_restart_export_folders_daemon', 'notify_new_export_folders' ), period = 180 )
|
||||
HC.DAEMONWorker( 'DownloadFiles', DAEMONDownloadFiles, ( 'notify_new_downloads', 'notify_new_permissions' ) )
|
||||
HC.DAEMONWorker( 'DownloadThumbnails', DAEMONDownloadThumbnails, ( 'notify_new_permissions', 'notify_new_thumbnails' ) )
|
||||
HC.DAEMONWorker( 'ResizeThumbnails', DAEMONResizeThumbnails, init_wait = 600 )
|
||||
HC.DAEMONWorker( 'SynchroniseAccounts', DAEMONSynchroniseAccounts, ( 'notify_new_services', 'permissions_are_stale' ) )
|
||||
HC.DAEMONWorker( 'SynchroniseRepositories', DAEMONSynchroniseRepositories, ( 'notify_restart_repo_sync_daemon', 'notify_new_permissions' ) )
|
||||
HC.DAEMONWorker( 'SynchroniseSubscriptions', DAEMONSynchroniseSubscriptions, ( 'notify_restart_subs_sync_daemon', 'notify_new_subscriptions' ) )
|
||||
HC.DAEMONQueue( 'FlushRepositoryUpdates', DAEMONFlushServiceUpdates, 'service_updates_delayed', period = 5 )
|
||||
HydrusThreading.DAEMONWorker( 'CheckImportFolders', DAEMONCheckImportFolders, ( 'notify_restart_import_folders_daemon', 'notify_new_import_folders' ), period = 180 )
|
||||
HydrusThreading.DAEMONWorker( 'CheckExportFolders', DAEMONCheckExportFolders, ( 'notify_restart_export_folders_daemon', 'notify_new_export_folders' ), period = 180 )
|
||||
HydrusThreading.DAEMONWorker( 'DownloadFiles', DAEMONDownloadFiles, ( 'notify_new_downloads', 'notify_new_permissions' ) )
|
||||
HydrusThreading.DAEMONWorker( 'DownloadThumbnails', DAEMONDownloadThumbnails, ( 'notify_new_permissions', 'notify_new_thumbnails' ) )
|
||||
HydrusThreading.DAEMONWorker( 'ResizeThumbnails', DAEMONResizeThumbnails, init_wait = 600 )
|
||||
HydrusThreading.DAEMONWorker( 'SynchroniseAccounts', DAEMONSynchroniseAccounts, ( 'notify_new_services', 'permissions_are_stale' ) )
|
||||
HydrusThreading.DAEMONWorker( 'SynchroniseRepositories', DAEMONSynchroniseRepositories, ( 'notify_restart_repo_sync_daemon', 'notify_new_permissions' ) )
|
||||
HydrusThreading.DAEMONWorker( 'SynchroniseSubscriptions', DAEMONSynchroniseSubscriptions, ( 'notify_restart_subs_sync_daemon', 'notify_new_subscriptions' ) )
|
||||
HydrusThreading.DAEMONQueue( 'FlushRepositoryUpdates', DAEMONFlushServiceUpdates, 'service_updates_delayed', period = 5 )
|
||||
|
||||
|
||||
def WaitUntilGoodTimeToUseDBThread( self ):
|
||||
|
@ -7457,7 +7481,7 @@ def DAEMONResizeThumbnails():
|
|||
|
||||
try:
|
||||
|
||||
thumbnail_resized = HydrusImageHandling.GenerateThumbnail( thumbnail_path, HC.options[ 'thumbnail_dimensions' ] )
|
||||
thumbnail_resized = HydrusFileHandling.GenerateThumbnail( thumbnail_path, HC.options[ 'thumbnail_dimensions' ] )
|
||||
|
||||
thumbnail_resized_path = thumbnail_path + '_resized'
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import ClientGUIPages
|
|||
import HydrusDownloading
|
||||
import HydrusFileHandling
|
||||
import HydrusImageHandling
|
||||
import HydrusThreading
|
||||
import itertools
|
||||
import os
|
||||
import random
|
||||
|
@ -1418,7 +1419,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
|
|||
|
||||
if dlg.ShowModal() == wx.ID_YES:
|
||||
|
||||
def do_it():
|
||||
def THREADRegenerateThumbnails():
|
||||
|
||||
message = HC.Message( HC.MESSAGE_TYPE_TEXT, { 'text' : 'regenerating thumbnails - creating directories' } )
|
||||
|
||||
|
@ -1455,13 +1456,13 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
|
|||
|
||||
hash = hash_encoded.decode( 'hex' )
|
||||
|
||||
thumbnail = HydrusImageHandling.GenerateThumbnail( path )
|
||||
thumbnail = HydrusFileHandling.GenerateThumbnail( path )
|
||||
|
||||
thumbnail_path = CC.GetExpectedThumbnailPath( hash, True )
|
||||
|
||||
with open( thumbnail_path, 'wb' ) as f: f.write( thumbnail )
|
||||
|
||||
thumbnail_resized = HydrusImageHandling.GenerateThumbnail( thumbnail_path, HC.options[ 'thumbnail_dimensions' ] )
|
||||
thumbnail_resized = HydrusFileHandling.GenerateThumbnail( thumbnail_path, HC.options[ 'thumbnail_dimensions' ] )
|
||||
|
||||
thumbnail_resized_path = CC.GetExpectedThumbnailPath( hash, False )
|
||||
|
||||
|
@ -1476,7 +1477,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
|
|||
message.SetInfo( 'text', 'done regenerating thumbnails' )
|
||||
|
||||
|
||||
threading.Thread( target = do_it ).start()
|
||||
HydrusThreading.CallToThread( THREADRegenerateThumbnails )
|
||||
|
||||
|
||||
|
||||
|
@ -1589,7 +1590,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
|
||||
HC.pubsub.pub( 'message', message )
|
||||
|
||||
threading.Thread( target = HydrusDownloading.THREADDownloadURL, args = ( message, url, url_string ) ).start()
|
||||
HydrusThreading.CallToThread( HydrusDownloading.THREADDownloadURL, message, url, url_string )
|
||||
|
||||
|
||||
|
||||
|
@ -1639,7 +1640,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
|
||||
def _UploadPending( self, service_identifier ):
|
||||
|
||||
threading.Thread( target = self._THREADUploadPending, args = ( service_identifier, ) ).start()
|
||||
HydrusThreading.CallToThread( self._THREADUploadPending, service_identifier )
|
||||
|
||||
|
||||
def _VacuumDatabase( self ):
|
||||
|
|
|
@ -11,7 +11,6 @@ import Queue
|
|||
import random
|
||||
import shutil
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
import urllib
|
||||
|
@ -21,6 +20,7 @@ import wx.media
|
|||
if HC.PLATFORM_WINDOWS: import wx.lib.flashwin
|
||||
|
||||
ID_TIMER_ANIMATED = wx.NewId()
|
||||
ID_TIMER_RENDER_WAIT = wx.NewId()
|
||||
ID_TIMER_ANIMATION_BAR_UPDATE = wx.NewId()
|
||||
ID_TIMER_SLIDESHOW = wx.NewId()
|
||||
ID_TIMER_CURSOR_HIDE = wx.NewId()
|
||||
|
@ -73,6 +73,174 @@ def GetExtraDimensions( media ):
|
|||
|
||||
return ( extra_width, extra_height )
|
||||
|
||||
class Animation( wx.Window ):
|
||||
|
||||
def __init__( self, parent, media, initial_size, initial_position ):
|
||||
|
||||
wx.Window.__init__( self, parent, size = initial_size, pos = initial_position )
|
||||
|
||||
self.SetDoubleBuffered( True )
|
||||
|
||||
self._media = media
|
||||
self._animation_container = None
|
||||
|
||||
self._animation_bar = None
|
||||
|
||||
self._current_frame_index = 0
|
||||
self._current_frame_drawn = False
|
||||
self._current_frame_drawn_at = 0
|
||||
|
||||
self._paused = False
|
||||
|
||||
self._canvas_bmp = wx.EmptyBitmap( 0, 0, 24 )
|
||||
|
||||
self._timer_animated = wx.Timer( self, id = ID_TIMER_ANIMATED )
|
||||
|
||||
self._paused = False
|
||||
|
||||
self.Bind( wx.EVT_PAINT, self.EventPaint )
|
||||
self.Bind( wx.EVT_SIZE, self.EventResize )
|
||||
self.Bind( wx.EVT_TIMER, self.TIMEREventAnimated, id = ID_TIMER_ANIMATED )
|
||||
self.Bind( wx.EVT_MOUSE_EVENTS, self.EventPropagateMouse )
|
||||
|
||||
self.EventResize( None )
|
||||
|
||||
self._timer_animated.Start( 5, wx.TIMER_CONTINUOUS )
|
||||
|
||||
|
||||
def _DrawFrame( self ):
|
||||
|
||||
dc = wx.BufferedDC( wx.ClientDC( self ), self._canvas_bmp )
|
||||
|
||||
current_frame = self._animation_container.GetFrame( self._current_frame_index )
|
||||
|
||||
( my_width, my_height ) = self._canvas_bmp.GetSize()
|
||||
|
||||
( frame_width, frame_height ) = current_frame.GetSize()
|
||||
|
||||
x_scale = my_width / float( frame_width )
|
||||
y_scale = my_height / float( frame_height )
|
||||
|
||||
dc.SetUserScale( x_scale, y_scale )
|
||||
|
||||
hydrus_bmp = current_frame.CreateWxBmp()
|
||||
|
||||
dc.DrawBitmap( hydrus_bmp, 0, 0 )
|
||||
|
||||
wx.CallAfter( hydrus_bmp.Destroy )
|
||||
|
||||
dc.SetUserScale( 1.0, 1.0 )
|
||||
|
||||
if self._animation_bar is not None: self._animation_bar.GotoFrame( self._current_frame_index )
|
||||
|
||||
self._current_frame_drawn = True
|
||||
|
||||
self._current_frame_drawn_at = time.clock()
|
||||
|
||||
|
||||
def _DrawWhite( self ):
|
||||
|
||||
dc = wx.BufferedDC( wx.ClientDC( self ), self._canvas_bmp )
|
||||
|
||||
dc.SetBackground( wx.Brush( wx.WHITE ) )
|
||||
|
||||
dc.Clear()
|
||||
|
||||
|
||||
def CurrentFrame( self ): return self._current_frame_index
|
||||
|
||||
def EventPaint( self, event ): wx.BufferedPaintDC( self, self._canvas_bmp )
|
||||
|
||||
def EventPropagateMouse( self, event ):
|
||||
|
||||
screen_position = self.ClientToScreen( event.GetPosition() )
|
||||
( x, y ) = self.GetParent().ScreenToClient( screen_position )
|
||||
|
||||
event.SetX( x )
|
||||
event.SetY( y )
|
||||
|
||||
event.ResumePropagation( 1 )
|
||||
event.Skip()
|
||||
|
||||
|
||||
def EventResize( self, event ):
|
||||
|
||||
( my_width, my_height ) = self.GetClientSize()
|
||||
|
||||
( current_bmp_width, current_bmp_height ) = self._canvas_bmp.GetSize()
|
||||
|
||||
if my_width != current_bmp_width or my_height != current_bmp_height:
|
||||
|
||||
if my_width > 0 and my_height > 0:
|
||||
|
||||
if self._animation_container is None: self._animation_container = HydrusImageHandling.ImageContainerAnimated( self._media, ( my_width, my_height ) )
|
||||
else:
|
||||
|
||||
( image_width, image_height ) = self._animation_container.GetSize()
|
||||
|
||||
we_just_zoomed_in = my_width > image_width
|
||||
|
||||
if we_just_zoomed_in and self._animation_container.IsScaled():
|
||||
|
||||
full_resolution = self._animation_container.GetResolution()
|
||||
|
||||
self._animation_container = HydrusImageHandling.ImageContainerAnimated( self._media, full_resolution )
|
||||
|
||||
self._animation_container.SetPosition( self._current_frame_index )
|
||||
|
||||
self._current_frame_drawn = False
|
||||
|
||||
|
||||
|
||||
wx.CallAfter( self._canvas_bmp.Destroy )
|
||||
|
||||
self._canvas_bmp = wx.EmptyBitmap( my_width, my_height, 24 )
|
||||
|
||||
if self._animation_container.HasFrame( self._current_frame_index ): self._DrawFrame()
|
||||
else: self._DrawWhite()
|
||||
|
||||
|
||||
|
||||
|
||||
def GotoFrame( self, frame_index ):
|
||||
|
||||
if frame_index != self._current_frame_index:
|
||||
|
||||
self._current_frame_index = frame_index
|
||||
|
||||
self._current_frame_drawn = False
|
||||
|
||||
self._animation_container.SetPosition( frame_index )
|
||||
|
||||
|
||||
self._paused = True
|
||||
|
||||
|
||||
def Play( self ): self._paused = False
|
||||
|
||||
def SetAnimationBar( self, animation_bar ): self._animation_bar = animation_bar
|
||||
|
||||
def TIMEREventAnimated( self, event ):
|
||||
|
||||
if self.IsShown():
|
||||
|
||||
if self._current_frame_drawn:
|
||||
|
||||
ms_since_current_frame_drawn = int( 1000.0 * ( time.clock() - self._current_frame_drawn_at ) )
|
||||
|
||||
if not self._paused and ms_since_current_frame_drawn > self._animation_container.GetDuration( self._current_frame_index ):
|
||||
|
||||
num_frames = self._media.GetNumFrames()
|
||||
|
||||
self._current_frame_index = ( self._current_frame_index + 1 ) % num_frames
|
||||
|
||||
self._current_frame_drawn = False
|
||||
|
||||
|
||||
elif self._animation_container.HasFrame( self._current_frame_index ): self._DrawFrame()
|
||||
|
||||
|
||||
|
||||
class AnimationBar( wx.Window ):
|
||||
|
||||
def __init__( self, parent, media, media_window ):
|
||||
|
@ -88,7 +256,6 @@ class AnimationBar( wx.Window ):
|
|||
self._media = media
|
||||
self._media_window = media_window
|
||||
self._num_frames = self._media.GetNumFrames()
|
||||
self._num_frames_rendered = 0
|
||||
self._current_frame_index = 0
|
||||
|
||||
self._currently_in_a_drag = False
|
||||
|
@ -112,30 +279,9 @@ class AnimationBar( wx.Window ):
|
|||
|
||||
dc.SetPen( wx.TRANSPARENT_PEN )
|
||||
|
||||
if self._media.GetMime() in HC.IMAGES:
|
||||
|
||||
image_container = self._media_window.GetImageContainer()
|
||||
|
||||
self._num_frames_rendered = image_container.GetNumFramesRendered()
|
||||
|
||||
num_frames = image_container.GetNumFrames()
|
||||
|
||||
my_rendered_width = int( my_width * ( float( self._num_frames_rendered ) / num_frames ) )
|
||||
|
||||
dc.SetBrush( wx.Brush( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) ) )
|
||||
|
||||
dc.DrawRectangle( 0, 0, my_rendered_width, ANIMATED_SCANBAR_HEIGHT )
|
||||
|
||||
dc.SetBrush( wx.Brush( wx.SystemSettings.GetColour( wx.SYS_COLOUR_SCROLLBAR ) ) )
|
||||
|
||||
dc.DrawRectangle( my_rendered_width, 0, my_width - my_rendered_width, ANIMATED_SCANBAR_HEIGHT )
|
||||
|
||||
else:
|
||||
|
||||
dc.SetBrush( wx.Brush( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) ) )
|
||||
|
||||
dc.DrawRectangle( 0, 0, my_width, ANIMATED_SCANBAR_HEIGHT )
|
||||
|
||||
dc.SetBrush( wx.Brush( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) ) )
|
||||
|
||||
dc.DrawRectangle( 0, 0, my_width, ANIMATED_SCANBAR_HEIGHT )
|
||||
|
||||
dc.SetBrush( wx.Brush( wx.SystemSettings.GetColour( wx.SYS_COLOUR_SCROLLBAR ) ) )
|
||||
|
||||
|
@ -207,16 +353,7 @@ class AnimationBar( wx.Window ):
|
|||
|
||||
if self.IsShown():
|
||||
|
||||
if self._media.GetMime() in HC.IMAGES:
|
||||
|
||||
image_container = self._media_window.GetImageContainer()
|
||||
|
||||
if self._num_frames_rendered != image_container.GetNumFramesRendered():
|
||||
|
||||
self._Draw()
|
||||
|
||||
|
||||
elif self._media.GetMime() == HC.APPLICATION_FLASH:
|
||||
if self._media.GetMime() == HC.APPLICATION_FLASH:
|
||||
|
||||
frame_index = self._media_window.CurrentFrame()
|
||||
|
||||
|
@ -2760,7 +2897,7 @@ class RatingsFilterFrameNumerical( ClientGUICommon.FrameThatResizes ):
|
|||
|
||||
|
||||
|
||||
if best_media_first.qsize() > 0: ( value, media_to_rate_against ) = best_media_first.get()
|
||||
if not best_media_first.empty(): ( value, media_to_rate_against ) = best_media_first.get()
|
||||
|
||||
|
||||
if media_to_rate_against is None:
|
||||
|
@ -3477,7 +3614,11 @@ class MediaContainer( wx.Window ):
|
|||
media_initial_size = ( x, y - ANIMATED_SCANBAR_HEIGHT )
|
||||
|
||||
|
||||
if self._media.GetMime() in HC.IMAGES: self._media_window = Image( self, self._media, self._image_cache, media_initial_size, media_initial_position )
|
||||
if self._media.GetMime() in HC.IMAGES:
|
||||
|
||||
if ShouldHaveAnimationBar( self._media ): self._media_window = Animation( self, self._media, media_initial_size, media_initial_position )
|
||||
else: self._media_window = self._media_window = StaticImage( self, self._media, self._image_cache, media_initial_size, media_initial_position )
|
||||
|
||||
elif self._media.GetMime() == HC.APPLICATION_FLASH:
|
||||
|
||||
self._media_window = wx.lib.flashwin.FlashWindow( self, size = media_initial_size, pos = media_initial_position )
|
||||
|
@ -3748,7 +3889,28 @@ class EmbedWindowVideo( wx.Window ):
|
|||
subprocess.call( 'start "" "' + path + '"', shell = True )
|
||||
|
||||
|
||||
class Image( wx.Window ):
|
||||
class PDFButton( wx.Button ):
|
||||
|
||||
def __init__( self, parent, hash, size ):
|
||||
|
||||
wx.Button.__init__( self, parent, label = 'launch pdf', size = size )
|
||||
|
||||
self.SetCursor( wx.StockCursor( wx.CURSOR_ARROW ) )
|
||||
|
||||
self._hash = hash
|
||||
|
||||
self.Bind( wx.EVT_BUTTON, self.EventButton )
|
||||
|
||||
|
||||
def EventButton( self, event ):
|
||||
|
||||
path = CC.GetFilePath( self._hash, HC.APPLICATION_PDF )
|
||||
|
||||
# os.system( 'start ' + path )
|
||||
subprocess.call( 'start "" "' + path + '"', shell = True )
|
||||
|
||||
|
||||
class StaticImage( wx.Window ):
|
||||
|
||||
def __init__( self, parent, media, image_cache, initial_size, initial_position ):
|
||||
|
||||
|
@ -3760,43 +3922,33 @@ class Image( wx.Window ):
|
|||
self._image_container = None
|
||||
self._image_cache = image_cache
|
||||
|
||||
self._animation_bar = None
|
||||
|
||||
self._last_clock = time.clock()
|
||||
self._current_frame_index = 0
|
||||
|
||||
self._canvas_bmp = wx.EmptyBitmap( 0, 0, 24 )
|
||||
|
||||
self._timer_animated = wx.Timer( self, id = ID_TIMER_ANIMATED )
|
||||
|
||||
self._yet_to_draw_initial_frame = True
|
||||
self._timer_render_wait = wx.Timer( self, id = ID_TIMER_RENDER_WAIT )
|
||||
|
||||
self._paused = False
|
||||
|
||||
self.Bind( wx.EVT_PAINT, self.EventPaint )
|
||||
self.Bind( wx.EVT_SIZE, self.EventResize )
|
||||
self.Bind( wx.EVT_TIMER, self.TIMEREventAnimated, id = ID_TIMER_ANIMATED )
|
||||
self.Bind( wx.EVT_TIMER, self.TIMEREventRenderWait, id = ID_TIMER_RENDER_WAIT )
|
||||
self.Bind( wx.EVT_MOUSE_EVENTS, self.EventPropagateMouse )
|
||||
|
||||
self.EventResize( None )
|
||||
|
||||
self._timer_animated.Start( 16, wx.TIMER_CONTINUOUS )
|
||||
self._timer_render_wait.Start( 16, wx.TIMER_CONTINUOUS )
|
||||
|
||||
|
||||
def _Draw( self ):
|
||||
|
||||
dc = wx.BufferedDC( wx.ClientDC( self ), self._canvas_bmp )
|
||||
|
||||
dc.SetBackground( wx.Brush( wx.WHITE ) )
|
||||
|
||||
if self._image_container.HasFrame( self._current_frame_index ):
|
||||
dc.Clear()
|
||||
|
||||
if self._image_container.IsRendered():
|
||||
|
||||
if not self._image_container.IsAnimated():
|
||||
|
||||
dc.SetBackground( wx.Brush( wx.WHITE ) )
|
||||
|
||||
dc.Clear()
|
||||
|
||||
|
||||
current_frame = self._image_container.GetFrame( self._current_frame_index )
|
||||
current_frame = self._image_container.GetFrame()
|
||||
|
||||
( my_width, my_height ) = self._canvas_bmp.GetSize()
|
||||
|
||||
|
@ -3815,22 +3967,10 @@ class Image( wx.Window ):
|
|||
|
||||
dc.SetUserScale( 1.0, 1.0 )
|
||||
|
||||
if self._image_container.IsAnimated():
|
||||
|
||||
if self._animation_bar is not None: self._animation_bar.GotoFrame( self._current_frame_index )
|
||||
|
||||
else: self._timer_animated.Stop()
|
||||
|
||||
else:
|
||||
|
||||
dc.SetBackground( wx.Brush( wx.WHITE ) )
|
||||
|
||||
dc.Clear()
|
||||
self._timer_render_wait.Stop()
|
||||
|
||||
|
||||
|
||||
def CurrentFrame( self ): return self._current_frame_index
|
||||
|
||||
def EventPaint( self, event ): wx.BufferedPaintDC( self, self._canvas_bmp )
|
||||
|
||||
def EventPropagateMouse( self, event ):
|
||||
|
@ -3855,27 +3995,18 @@ class Image( wx.Window ):
|
|||
|
||||
if my_width > 0 and my_height > 0:
|
||||
|
||||
if self._image_container is None:
|
||||
|
||||
if self._media.IsAnimated(): self._image_container = HydrusImageHandling.RenderImage( self._media, ( my_width, my_height ) )
|
||||
else: self._image_container = self._image_cache.GetImage( self._media, ( my_width, my_height ) )
|
||||
|
||||
if self._image_container is None: self._image_container = self._image_cache.GetImage( self._media, ( my_width, my_height ) )
|
||||
else:
|
||||
|
||||
( image_width, image_height ) = self._image_container.GetSize()
|
||||
|
||||
we_just_zoomed_in = my_width > image_width
|
||||
we_just_zoomed_in = my_width > image_width or my_height > image_height
|
||||
|
||||
if we_just_zoomed_in and self._image_container.IsScaled():
|
||||
|
||||
full_resolution = self._image_container.GetResolution()
|
||||
full_resolution = self._media.GetResolution()
|
||||
|
||||
if self._media.IsAnimated(): self._image_container = HydrusImageHandling.RenderImage( self._media, full_resolution )
|
||||
else: self._image_container = self._image_cache.GetImage( self._media, full_resolution )
|
||||
|
||||
self._yet_to_draw_initial_frame = True
|
||||
|
||||
self._timer_animated.Start()
|
||||
self._image_container = self._image_cache.GetImage( self._media, full_resolution )
|
||||
|
||||
|
||||
|
||||
|
@ -3884,76 +4015,14 @@ class Image( wx.Window ):
|
|||
self._canvas_bmp = wx.EmptyBitmap( my_width, my_height, 24 )
|
||||
|
||||
self._Draw()
|
||||
|
||||
if not self._image_container.IsRendered(): self._timer_render_wait.Start()
|
||||
|
||||
|
||||
|
||||
|
||||
def GetImageContainer( self ): return self._image_container
|
||||
|
||||
def GotoFrame( self, frame_index ):
|
||||
def TIMEREventRenderWait( self, event ):
|
||||
|
||||
self._current_frame_index = frame_index
|
||||
|
||||
self._Draw()
|
||||
|
||||
self._timer_animated.Stop()
|
||||
|
||||
|
||||
def Play( self ): self._timer_animated.Start()
|
||||
|
||||
def SetAnimationBar( self, animation_bar ): self._animation_bar = animation_bar
|
||||
|
||||
def TIMEREventAnimated( self, event ):
|
||||
|
||||
if self.IsShown():
|
||||
|
||||
now = time.clock()
|
||||
|
||||
ms_since_last_clock = int( 1000.0 * ( now - self._last_clock ) )
|
||||
|
||||
if self._yet_to_draw_initial_frame or ms_since_last_clock > self._image_container.GetDuration( self._current_frame_index ):
|
||||
|
||||
if self._yet_to_draw_initial_frame: next_frame = 0
|
||||
else:
|
||||
|
||||
num_frames = self._media.GetNumFrames()
|
||||
|
||||
if num_frames is None: next_frame = 0
|
||||
else: next_frame = ( self._current_frame_index + 1 ) % num_frames
|
||||
|
||||
|
||||
if self._image_container.HasFrame( next_frame ):
|
||||
|
||||
self._current_frame_index = next_frame
|
||||
|
||||
self._last_clock = now
|
||||
|
||||
self._Draw()
|
||||
|
||||
self._yet_to_draw_initial_frame = False
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class PDFButton( wx.Button ):
|
||||
|
||||
def __init__( self, parent, hash, size ):
|
||||
|
||||
wx.Button.__init__( self, parent, label = 'launch pdf', size = size )
|
||||
|
||||
self.SetCursor( wx.StockCursor( wx.CURSOR_ARROW ) )
|
||||
|
||||
self._hash = hash
|
||||
|
||||
self.Bind( wx.EVT_BUTTON, self.EventButton )
|
||||
|
||||
|
||||
def EventButton( self, event ):
|
||||
|
||||
path = CC.GetFilePath( self._hash, HC.APPLICATION_PDF )
|
||||
|
||||
# os.system( 'start ' + path )
|
||||
subprocess.call( 'start "" "' + path + '"', shell = True )
|
||||
if self.IsShown() and self._image_container.IsRendered(): self._Draw()
|
||||
|
||||
|
|
@ -5,6 +5,7 @@ import HydrusEncryption
|
|||
import HydrusExceptions
|
||||
import HydrusFileHandling
|
||||
import HydrusTags
|
||||
import HydrusThreading
|
||||
import ClientConstants as CC
|
||||
import ClientGUICommon
|
||||
import collections
|
||||
|
@ -2125,7 +2126,7 @@ class DialogInputLocalFiles( Dialog ):
|
|||
|
||||
self._job_key = HC.JobKey()
|
||||
|
||||
threading.Thread( target = self.THREADParseImportablePaths, args = ( paths, self._job_key ) ).start()
|
||||
HydrusThreading.CallToThread( self.THREADParseImportablePaths, paths, self._job_key )
|
||||
|
||||
self.SetGaugeInfo( None, None, '' )
|
||||
|
||||
|
@ -4726,7 +4727,7 @@ class DialogSelectYoutubeURL( Dialog ):
|
|||
|
||||
message = HC.MessageGauge( HC.MESSAGE_TYPE_GAUGE, url_string )
|
||||
|
||||
threading.Thread( target = HydrusDownloading.THREADDownloadURL, args = ( message, url, url_string ) ).start()
|
||||
HydrusThreading.CallToThread( HydrusDownloading.THREADDownloadURL, message, url, url_string )
|
||||
|
||||
HC.pubsub.pub( 'message', message )
|
||||
|
||||
|
|
|
@ -4731,6 +4731,8 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
|
|||
info[ 'port' ] = port
|
||||
|
||||
|
||||
if service_type in HC.REPOSITORIES: info[ 'paused' ] = False
|
||||
|
||||
if service_type == HC.LOCAL_RATING_LIKE:
|
||||
|
||||
info[ 'like' ] = 'like'
|
||||
|
|
|
@ -4,6 +4,7 @@ import HydrusDownloading
|
|||
import HydrusExceptions
|
||||
import HydrusFileHandling
|
||||
import HydrusImageHandling
|
||||
import HydrusThreading
|
||||
import ClientConstants as CC
|
||||
import ClientConstantsMessages
|
||||
import ClientGUICommon
|
||||
|
@ -1119,7 +1120,7 @@ class ManagementPanelDumper( ManagementPanel ):
|
|||
|
||||
self._actually_dumping = True
|
||||
|
||||
threading.Thread( target = self._THREADDoDump, args = ( hash, post_field_info, headers, body ) ).start()
|
||||
HydrusThreading.CallToThread( self._THREADDoDump, hash, post_field_info, headers, body )
|
||||
|
||||
|
||||
except Exception as e:
|
||||
|
|
|
@ -736,8 +736,6 @@ class MediaSingleton( Media ):
|
|||
|
||||
def HasInbox( self ): return self._media_result.GetInbox()
|
||||
|
||||
def IsAnimated( self ): return self._media_result.IsAnimated()
|
||||
|
||||
def IsCollection( self ): return False
|
||||
|
||||
def IsImage( self ): return HC.IsImage( self._media_result.GetMime() )
|
||||
|
|
|
@ -355,7 +355,7 @@ class PageImport( PageWithMedia ):
|
|||
|
||||
self._import_controller = HydrusDownloading.ImportController( import_args_generator_factory, import_queue_generator_factory, page_key = self._page_key )
|
||||
|
||||
self._import_controller.StartThread()
|
||||
self._import_controller.StartDaemon()
|
||||
|
||||
|
||||
def _PauseControllers( self ):
|
||||
|
|
|
@ -64,7 +64,7 @@ options = {}
|
|||
# Misc
|
||||
|
||||
NETWORK_VERSION = 13
|
||||
SOFTWARE_VERSION = 115
|
||||
SOFTWARE_VERSION = 116
|
||||
|
||||
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
|
||||
|
||||
|
@ -303,7 +303,7 @@ NOISY_MIMES = tuple( [ APPLICATION_FLASH ] + list( AUDIO ) + list( VIDEO ) )
|
|||
|
||||
ARCHIVES = ( APPLICATION_ZIP, APPLICATION_HYDRUS_ENCRYPTED_ZIP )
|
||||
|
||||
MIMES_WITH_THUMBNAILS = ( IMAGE_JPEG, IMAGE_PNG, IMAGE_GIF, IMAGE_BMP )
|
||||
MIMES_WITH_THUMBNAILS = ( IMAGE_JPEG, IMAGE_PNG, IMAGE_GIF, IMAGE_BMP, VIDEO_WEBM )
|
||||
|
||||
# mp3 header is complicated
|
||||
|
||||
|
@ -1676,98 +1676,6 @@ class ContentUpdate():
|
|||
|
||||
def ToTuple( self ): return ( self._data_type, self._action, self._row )
|
||||
|
||||
class DAEMON( threading.Thread ):
|
||||
|
||||
def __init__( self, name, callable, period = 1200 ):
|
||||
|
||||
threading.Thread.__init__( self, name = name )
|
||||
|
||||
self._name = name
|
||||
self._callable = callable
|
||||
self._period = period
|
||||
|
||||
self._event = threading.Event()
|
||||
|
||||
pubsub.sub( self, 'shutdown', 'shutdown' )
|
||||
|
||||
|
||||
def shutdown( self ): self._event.set()
|
||||
|
||||
class DAEMONQueue( DAEMON ):
|
||||
|
||||
def __init__( self, name, callable, queue_topic, period = 10 ):
|
||||
|
||||
DAEMON.__init__( self, name, callable, period )
|
||||
|
||||
self._queue = Queue.Queue()
|
||||
self._queue_topic = queue_topic
|
||||
|
||||
self.start()
|
||||
|
||||
pubsub.sub( self, 'put', queue_topic )
|
||||
|
||||
|
||||
def put( self, data ): self._queue.put( data )
|
||||
|
||||
def run( self ):
|
||||
|
||||
time.sleep( 3 )
|
||||
|
||||
while True:
|
||||
|
||||
while self._queue.qsize() == 0:
|
||||
|
||||
if shutdown: return
|
||||
|
||||
self._event.wait( self._period )
|
||||
|
||||
self._event.clear()
|
||||
|
||||
|
||||
items = []
|
||||
|
||||
while self._queue.qsize() > 0: items.append( self._queue.get() )
|
||||
|
||||
try: self._callable( items )
|
||||
except Exception as e: ShowException( e )
|
||||
|
||||
|
||||
|
||||
class DAEMONWorker( DAEMON ):
|
||||
|
||||
def __init__( self, name, callable, topics = [], period = 1200, init_wait = 3 ):
|
||||
|
||||
DAEMON.__init__( self, name, callable, period )
|
||||
|
||||
self._topics = topics
|
||||
self._init_wait = init_wait
|
||||
|
||||
self.start()
|
||||
|
||||
for topic in topics: pubsub.sub( self, 'set', topic )
|
||||
|
||||
|
||||
def run( self ):
|
||||
|
||||
self._event.wait( self._init_wait )
|
||||
|
||||
while True:
|
||||
|
||||
if shutdown: return
|
||||
|
||||
try: self._callable()
|
||||
except Exception as e: ShowException( e )
|
||||
|
||||
if shutdown: return
|
||||
|
||||
self._event.wait( self._period )
|
||||
|
||||
self._event.clear()
|
||||
|
||||
|
||||
|
||||
def set( self, *args, **kwargs ): self._event.set()
|
||||
|
||||
class JobDatabase():
|
||||
|
||||
yaml_tag = u'!JobDatabase'
|
||||
|
|
|
@ -4,6 +4,7 @@ import httplib
|
|||
import HydrusConstants as HC
|
||||
import HydrusExceptions
|
||||
import HydrusNetworking
|
||||
import HydrusThreading
|
||||
import json
|
||||
import lxml
|
||||
import os
|
||||
|
@ -1484,7 +1485,7 @@ class ImportController():
|
|||
|
||||
args_generator = self._import_args_generator_factory( self._import_job_key, item )
|
||||
|
||||
threading.Thread( target = args_generator, name = 'Generate Import Args' ).start()
|
||||
HydrusThreading.CallToThread( args_generator )
|
||||
|
||||
else:
|
||||
|
||||
|
@ -1518,7 +1519,8 @@ class ImportController():
|
|||
|
||||
queue_generator = self._import_queue_generator_factory( self._import_queue_job_key, item )
|
||||
|
||||
threading.Thread( target = queue_generator, name = 'Generate Import Items' ).start()
|
||||
# make it a daemon, not a thread job, as it has a loop!
|
||||
threading.Thread( target = queue_generator ).start()
|
||||
|
||||
|
||||
|
||||
|
@ -1535,10 +1537,7 @@ class ImportController():
|
|||
|
||||
|
||||
|
||||
def StartThread( self ):
|
||||
|
||||
threading.Thread( target = self.MainLoop ).start()
|
||||
|
||||
def StartDaemon( self ): threading.Thread( target = self.MainLoop ).start()
|
||||
|
||||
class ImportQueueGenerator():
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import cv2
|
||||
import hashlib
|
||||
import hsaudiotag
|
||||
import hsaudiotag.auto
|
||||
|
@ -16,11 +17,61 @@ import threading
|
|||
import time
|
||||
import traceback
|
||||
import wx
|
||||
import cStringIO
|
||||
|
||||
# Mime
|
||||
|
||||
#magic_mime = magic.Magic( HC.STATIC_DIR + os.path.sep + 'magic.mime', HC.STATIC_DIR + os.path.sep + 'magic.mime.cache' )
|
||||
|
||||
def GenerateThumbnail( path, dimensions = HC.UNSCALED_THUMBNAIL_DIMENSIONS ):
|
||||
|
||||
mime = GetMime( path )
|
||||
|
||||
if mime in HC.IMAGES:
|
||||
|
||||
pil_image = HydrusImageHandling.GeneratePILImage( path )
|
||||
|
||||
HydrusImageHandling.EfficientlyThumbnailPILImage( pil_image, dimensions )
|
||||
|
||||
f = cStringIO.StringIO()
|
||||
|
||||
if pil_image.mode == 'P' and pil_image.info.has_key( 'transparency' ):
|
||||
|
||||
pil_image.save( f, 'PNG', transparency = pil_image.info[ 'transparency' ] )
|
||||
|
||||
elif pil_image.mode == 'RGBA': pil_image.save( f, 'PNG' )
|
||||
else:
|
||||
|
||||
pil_image = pil_image.convert( 'RGB' )
|
||||
|
||||
pil_image.save( f, 'JPEG', quality=92 )
|
||||
|
||||
|
||||
f.seek( 0 )
|
||||
|
||||
thumbnail = f.read()
|
||||
|
||||
f.close()
|
||||
|
||||
else:
|
||||
|
||||
cv_video = cv2.VideoCapture( path )
|
||||
|
||||
cv_video.set( cv2.cv.CV_CAP_PROP_CONVERT_RGB, True )
|
||||
|
||||
( retval, cv_image ) = cv_video.read()
|
||||
|
||||
if not retval: raise Exception( 'Could not read first frame of ' + HC.u( path ) + ' to create thumbnail!' )
|
||||
|
||||
cv_image = HydrusImageHandling.EfficientlyThumbnailCVImage( cv_image, dimensions )
|
||||
|
||||
( retval, thumbnail ) = cv2.imencode( '.jpg', cv_image, ( cv2.cv.CV_IMWRITE_JPEG_QUALITY, 92 ) )
|
||||
|
||||
if not retval: raise Exception( 'Could not export thumbnail for ' + HC.u( path ) + '!' )
|
||||
|
||||
|
||||
return thumbnail
|
||||
|
||||
def GetFileInfo( path, hash ):
|
||||
|
||||
info = os.lstat( path )
|
||||
|
|
|
@ -5,6 +5,7 @@ import cv
|
|||
import cv2
|
||||
import HydrusConstants as HC
|
||||
import HydrusExceptions
|
||||
import HydrusThreading
|
||||
import lz4
|
||||
import os
|
||||
from PIL import Image as PILImage
|
||||
|
@ -36,67 +37,52 @@ def ConvertToPngIfBmp( path ):
|
|||
os.remove( temp_path )
|
||||
|
||||
|
||||
def EfficientlyResizeCVImage( cv_image, ( x, y ) ):
|
||||
def EfficientlyResizeCVImage( cv_image, ( target_x, target_y ) ):
|
||||
|
||||
( im_y, im_x, depth ) = cv_image.shape
|
||||
|
||||
if x >= im_x and y >= im_y: return cv_image
|
||||
if target_x >= im_x and target_y >= im_y: return cv_image
|
||||
|
||||
result = cv_image
|
||||
|
||||
# this seems to slow things down a lot, at least for cv!
|
||||
#if im_x > 2 * x and im_y > 2 * y: result = cv2.resize( cv_image, ( 2 * x, 2 * y ), interpolation = cv2.INTER_NEAREST )
|
||||
#if im_x > 2 * target_x and im_y > 2 * target_y: result = cv2.resize( cv_image, ( 2 * target_x, 2 * target_y ), interpolation = cv2.INTER_NEAREST )
|
||||
|
||||
return cv2.resize( result, ( x, y ), interpolation = cv2.INTER_AREA )
|
||||
return cv2.resize( result, ( target_x, target_y ), interpolation = cv2.INTER_AREA )
|
||||
|
||||
def EfficientlyResizePILImage( pil_image, ( x, y ) ):
|
||||
def EfficientlyResizePILImage( pil_image, ( target_x, target_y ) ):
|
||||
|
||||
( im_x, im_y ) = pil_image.size
|
||||
|
||||
if x >= im_x and y >= im_y: return pil_image
|
||||
if target_x >= im_x and target_y >= im_y: return pil_image
|
||||
|
||||
if pil_image.mode == 'RGB': # low quality resize screws up alpha channel!
|
||||
|
||||
if im_x > 2 * x and im_y > 2 * y: pil_image.thumbnail( ( 2 * x, 2 * y ), PILImage.NEAREST )
|
||||
|
||||
#if pil_image.mode == 'RGB': # low quality resize screws up alpha channel!
|
||||
#
|
||||
# if im_x > 2 * target_x and im_y > 2 * target_y: pil_image.thumbnail( ( 2 * target_x, 2 * target_y ), PILImage.NEAREST )
|
||||
#
|
||||
|
||||
return pil_image.resize( ( x, y ), PILImage.ANTIALIAS )
|
||||
return pil_image.resize( ( target_x, target_y ), PILImage.ANTIALIAS )
|
||||
|
||||
def EfficientlyThumbnailPILImage( pil_image, ( x, y ) ):
|
||||
def EfficientlyThumbnailCVImage( cv_image, ( target_x, target_y ) ):
|
||||
|
||||
( im_y, im_x, depth ) = cv_image.shape
|
||||
|
||||
if target_x >= im_x and target_y >= im_y: return cv_image
|
||||
|
||||
( target_x, target_y ) = GetThumbnailResolution( ( im_x, im_y ), ( target_x, target_y ) )
|
||||
|
||||
return cv2.resize( cv_image, ( target_x, target_y ), interpolation = cv2.INTER_AREA )
|
||||
|
||||
def EfficientlyThumbnailPILImage( pil_image, ( target_x, target_y ) ):
|
||||
|
||||
( im_x, im_y ) = pil_image.size
|
||||
|
||||
if pil_image.mode == 'RGB': # low quality resize screws up alpha channel!
|
||||
|
||||
if im_x > 2 * x or im_y > 2 * y: pil_image.thumbnail( ( 2 * x, 2 * y ), PILImage.NEAREST )
|
||||
if im_x > 2 * target_x or im_y > 2 * target_y: pil_image.thumbnail( ( 2 * target_x, 2 * target_y ), PILImage.NEAREST )
|
||||
|
||||
|
||||
pil_image.thumbnail( ( x, y ), PILImage.ANTIALIAS )
|
||||
|
||||
def GenerateAnimatedFrame( pil_image, target_resolution, canvas ):
|
||||
|
||||
if 'duration' not in pil_image.info: duration = 40 # 25 fps default when duration is missing or too funky to extract. most stuff looks ok at this.
|
||||
else:
|
||||
|
||||
duration = pil_image.info[ 'duration' ]
|
||||
|
||||
if duration == 0: duration = 40
|
||||
|
||||
|
||||
current_frame = EfficientlyResizePILImage( pil_image, target_resolution )
|
||||
|
||||
if pil_image.mode == 'P' and 'transparency' in pil_image.info:
|
||||
|
||||
# I think gif problems are around here somewhere; the transparency info is not converted to RGBA properly, so it starts drawing colours when it should draw nothing
|
||||
|
||||
current_frame = current_frame.convert( 'RGBA' )
|
||||
|
||||
if canvas is None: canvas = current_frame
|
||||
else: canvas.paste( current_frame, None, current_frame ) # yeah, use the rgba image as its own mask, wut.
|
||||
|
||||
else: canvas = current_frame
|
||||
|
||||
return ( canvas, duration )
|
||||
pil_image.thumbnail( ( target_x, target_y ), PILImage.ANTIALIAS )
|
||||
|
||||
def GenerateCVImage( path ):
|
||||
|
||||
|
@ -238,34 +224,6 @@ def GeneratePerceptualHash( path ):
|
|||
|
||||
def GeneratePILImage( path ): return PILImage.open( path )
|
||||
|
||||
def GenerateThumbnail( path, dimensions = HC.UNSCALED_THUMBNAIL_DIMENSIONS ):
|
||||
|
||||
pil_image = GeneratePILImage( path )
|
||||
|
||||
EfficientlyThumbnailPILImage( pil_image, dimensions )
|
||||
|
||||
f = cStringIO.StringIO()
|
||||
|
||||
if pil_image.mode == 'P' and pil_image.info.has_key( 'transparency' ):
|
||||
|
||||
pil_image.save( f, 'PNG', transparency = pil_image.info[ 'transparency' ] )
|
||||
|
||||
elif pil_image.mode == 'RGBA': pil_image.save( f, 'PNG' )
|
||||
else:
|
||||
|
||||
pil_image = pil_image.convert( 'RGB' )
|
||||
|
||||
pil_image.save( f, 'JPEG', quality=92 )
|
||||
|
||||
|
||||
f.seek( 0 )
|
||||
|
||||
thumbnail = f.read()
|
||||
|
||||
f.close()
|
||||
|
||||
return thumbnail
|
||||
|
||||
def GetFrameDurations( path ):
|
||||
|
||||
pil_image_for_duration = GeneratePILImage( path )
|
||||
|
@ -359,26 +317,23 @@ def GetResolutionAndNumFrames( path ):
|
|||
|
||||
return ( ( x, y ), num_frames )
|
||||
|
||||
def RenderImage( media, target_resolution = None ):
|
||||
def GetThumbnailResolution( ( im_x, im_y ), ( target_x, target_y ) ):
|
||||
|
||||
if target_resolution is None: target_resolution = media.GetResolution()
|
||||
im_x = float( im_x )
|
||||
im_y = float( im_y )
|
||||
|
||||
if media.IsAnimated():
|
||||
|
||||
image_container = ImageContainerAnimated( media, target_resolution )
|
||||
|
||||
renderer = AnimatedFrameRenderer( image_container, media, target_resolution )
|
||||
|
||||
else:
|
||||
|
||||
image_container = ImageContainerStatic( media, target_resolution )
|
||||
|
||||
renderer = StaticFrameRenderer( image_container, media, target_resolution )
|
||||
|
||||
target_x = float( target_x )
|
||||
target_y = float( target_y )
|
||||
|
||||
threading.Thread( target = renderer.THREADRender ).start()
|
||||
x_ratio = im_x / target_x
|
||||
y_ratio = im_y / target_y
|
||||
|
||||
return image_container
|
||||
ratio_to_use = max( x_ratio, y_ratio )
|
||||
|
||||
target_x = int( im_x / ratio_to_use )
|
||||
target_y = int( im_y / ratio_to_use )
|
||||
|
||||
return ( target_x, target_y )
|
||||
|
||||
class FrameRenderer():
|
||||
|
||||
|
@ -394,138 +349,239 @@ class FrameRenderer():
|
|||
self._path = CC.GetFilePath( hash, mime )
|
||||
|
||||
|
||||
''' # old pil code
|
||||
|
||||
def _GetCurrentFramePIL( pil_image, target_resolution, canvas ):
|
||||
|
||||
current_frame = EfficientlyResizePILImage( pil_image, target_resolution )
|
||||
|
||||
if pil_image.mode == 'P' and 'transparency' in pil_image.info:
|
||||
|
||||
# I think gif problems are around here somewhere; the transparency info is not converted to RGBA properly, so it starts drawing colours when it should draw nothing
|
||||
|
||||
current_frame = current_frame.convert( 'RGBA' )
|
||||
|
||||
if canvas is None: canvas = current_frame
|
||||
else: canvas.paste( current_frame, None, current_frame ) # yeah, use the rgba image as its own mask, wut.
|
||||
|
||||
else: canvas = current_frame
|
||||
|
||||
return canvas
|
||||
|
||||
|
||||
def _GetFramePIL( self, index ):
|
||||
|
||||
pil_image = self._image_object
|
||||
|
||||
pil_image.seek( index )
|
||||
|
||||
canvas = self._GetCurrentFramePIL( pil_image, self._target_resolution, canvas )
|
||||
|
||||
return GenerateHydrusBitmapFromPILImage( canvas )
|
||||
|
||||
|
||||
def _GetFramesPIL( self ):
|
||||
|
||||
pil_image = self._image_object
|
||||
|
||||
canvas = None
|
||||
|
||||
global_palette = pil_image.palette
|
||||
|
||||
dirty = pil_image.palette.dirty
|
||||
mode = pil_image.palette.mode
|
||||
rawmode = pil_image.palette.rawmode
|
||||
|
||||
# believe it or not, doing this actually fixed a couple of gifs!
|
||||
pil_image.seek( 1 )
|
||||
pil_image.seek( 0 )
|
||||
|
||||
while True:
|
||||
|
||||
canvas = self._GetCurrentFramePIL( pil_image, self._target_resolution, canvas )
|
||||
|
||||
yield GenerateHydrusBitmapFromPILImage( canvas )
|
||||
|
||||
try:
|
||||
|
||||
pil_image.seek( pil_image.tell() + 1 )
|
||||
|
||||
if pil_image.palette == global_palette: # for some reason, when we fall back to global palette (no local-frame palette), we reset bunch of important variables!
|
||||
|
||||
pil_image.palette.dirty = dirty
|
||||
pil_image.palette.mode = mode
|
||||
pil_image.palette.rawmode = rawmode
|
||||
|
||||
|
||||
except: break
|
||||
|
||||
|
||||
'''
|
||||
|
||||
# the cv code was initially written by @fluffy_cub
|
||||
class AnimatedFrameRenderer( FrameRenderer ):
|
||||
|
||||
def _GetFramesCV( self ):
|
||||
def __init__( self, image_container, media, target_resolution ):
|
||||
|
||||
# this code initially written by @fluffy_cub
|
||||
FrameRenderer.__init__( self, image_container, media, target_resolution )
|
||||
|
||||
frame_durations = GetFrameDurations( self._path )
|
||||
self._lock = threading.Lock()
|
||||
|
||||
cv_video = cv2.VideoCapture( self._path )
|
||||
cv_video.set( cv2.cv.CV_CAP_PROP_CONVERT_RGB, True )
|
||||
self._cv_video = cv2.VideoCapture( self._path )
|
||||
|
||||
self._cv_video.set( cv2.cv.CV_CAP_PROP_CONVERT_RGB, True )
|
||||
|
||||
self._current_index = 0
|
||||
self._last_index_rendered = -1
|
||||
self._render_to_index = -1
|
||||
|
||||
|
||||
def _GetCurrentFrame( self ):
|
||||
|
||||
( retval, cv_image ) = self._cv_video.read()
|
||||
|
||||
self._last_index_rendered = self._current_index
|
||||
|
||||
num_frames = self._media.GetNumFrames()
|
||||
|
||||
if not retval:
|
||||
|
||||
raise HydrusExceptions.CantRenderWithCVException( 'CV could not render frame ' + HC.u( self._current_index ) + '.' )
|
||||
|
||||
|
||||
self._current_index = ( self._current_index + 1 ) % num_frames
|
||||
|
||||
if self._current_index == 0 and self._last_index_rendered != 0:
|
||||
|
||||
if self._media.GetMime() == HC.IMAGE_GIF: self._RewindGIF()
|
||||
|
||||
else: self._cv_video.set( cv2.cv.CV_CAP_PROP_POS_FRAMES, 0.0 )
|
||||
|
||||
|
||||
return cv_image
|
||||
|
||||
|
||||
def _RenderCurrentFrame( self ):
|
||||
|
||||
cv_image = self._GetCurrentFrame()
|
||||
|
||||
cv_image = EfficientlyResizeCVImage( cv_image, self._target_resolution )
|
||||
|
||||
cv_image = cv2.cvtColor( cv_image, cv2.COLOR_BGR2RGB )
|
||||
|
||||
return GenerateHydrusBitmapFromCVImage( cv_image )
|
||||
|
||||
|
||||
def _RenderFrames( self ):
|
||||
|
||||
no_frames_yet = True
|
||||
|
||||
while True:
|
||||
|
||||
( retval, cv_image ) = cv_video.read()
|
||||
|
||||
if not retval:
|
||||
try:
|
||||
|
||||
if no_frames_yet: raise HydrusExceptions.CantRenderWithCVException()
|
||||
else: break
|
||||
|
||||
else:
|
||||
yield self._RenderCurrentFrame()
|
||||
|
||||
no_frames_yet = False
|
||||
|
||||
cv_image = EfficientlyResizeCVImage( cv_image, self._target_resolution )
|
||||
except HydrusExceptions.CantRenderWithCVException:
|
||||
|
||||
cv_image = cv2.cvtColor( cv_image, cv2.COLOR_BGR2RGB )
|
||||
|
||||
try: duration = frame_durations.pop( 0 )
|
||||
except: duration = 40
|
||||
|
||||
yield ( GenerateHydrusBitmapFromCVImage( cv_image ), duration )
|
||||
if no_frames_yet: raise
|
||||
else: break
|
||||
|
||||
|
||||
|
||||
|
||||
def _GetFramesPIL( self ):
|
||||
def _RewindGIF( self ):
|
||||
|
||||
pil_image = GeneratePILImage( self._path )
|
||||
self._cv_video.release()
|
||||
self._cv_video.open( self._path )
|
||||
|
||||
canvas = None
|
||||
self._current_index = 0
|
||||
|
||||
global_palette = pil_image.palette
|
||||
|
||||
def SetRenderToPosition( self, index ):
|
||||
|
||||
dirty = pil_image.palette.dirty
|
||||
mode = pil_image.palette.mode
|
||||
rawmode = pil_image.palette.rawmode
|
||||
with self._lock:
|
||||
|
||||
if self._render_to_index != index:
|
||||
|
||||
self._render_to_index = index
|
||||
|
||||
HydrusThreading.CallToThread( self.THREADDoWork )
|
||||
|
||||
|
||||
|
||||
# believe it or not, doing this actually fixed a couple of gifs!
|
||||
pil_image.seek( 1 )
|
||||
pil_image.seek( 0 )
|
||||
|
||||
def SetPosition( self, index ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
if self._media.GetMime() == HC.IMAGE_GIF:
|
||||
|
||||
if index == self._current_index: return
|
||||
elif index < self._current_index: self._RewindGIF()
|
||||
|
||||
while self._current_index < index: self._GetCurrentFrame()
|
||||
|
||||
else:
|
||||
|
||||
self._cv_video.set( cv2.cv.CV_CAP_PROP_POS_FRAMES, index )
|
||||
|
||||
|
||||
self._render_to_index = index
|
||||
|
||||
|
||||
|
||||
def THREADDoWork( self ):
|
||||
|
||||
while True:
|
||||
|
||||
( canvas, duration ) = GenerateAnimatedFrame( pil_image, self._target_resolution, canvas )
|
||||
time.sleep( 0.00001 ) # thread yield
|
||||
|
||||
yield ( GenerateHydrusBitmapFromPILImage( canvas ), duration )
|
||||
|
||||
try:
|
||||
with self._lock:
|
||||
|
||||
pil_image.seek( pil_image.tell() + 1 )
|
||||
|
||||
if pil_image.palette == global_palette: # for some reason, when we fall back to global palette (no local-frame palette), we reset bunch of important variables!
|
||||
if self._last_index_rendered != self._render_to_index:
|
||||
|
||||
pil_image.palette.dirty = dirty
|
||||
pil_image.palette.mode = mode
|
||||
pil_image.palette.rawmode = rawmode
|
||||
index = self._current_index
|
||||
|
||||
|
||||
except: break
|
||||
frame = self._RenderCurrentFrame()
|
||||
|
||||
wx.CallAfter( self._image_container.AddFrame, index, frame )
|
||||
|
||||
else: break
|
||||
|
||||
|
||||
|
||||
def GetFrames( self ):
|
||||
|
||||
try:
|
||||
|
||||
for ( frame, duration ) in self._GetFramesCV(): yield ( frame, duration )
|
||||
|
||||
except HydrusExceptions.CantRenderWithCVException:
|
||||
|
||||
for ( frame, duration ) in self._GetFramesPIL(): yield ( frame, duration )
|
||||
|
||||
|
||||
|
||||
def Render( self ):
|
||||
|
||||
for ( frame, duration ) in self.GetFrames(): self._image_container.AddFrame( frame, duration )
|
||||
|
||||
|
||||
def THREADRender( self ):
|
||||
|
||||
time.sleep( 0.00001 ) # thread yield
|
||||
|
||||
for ( frame, duration ) in self.GetFrames(): wx.CallAfter( self._image_container.AddFrame, frame, duration )
|
||||
|
||||
HC.pubsub.pub( 'finished_rendering', self._image_container.GetKey() )
|
||||
|
||||
|
||||
class StaticFrameRenderer( FrameRenderer ):
|
||||
|
||||
def _GetFrame( self ):
|
||||
|
||||
try: frame = self._GetFrameCV()
|
||||
except: frame = self._GetFramePIL()
|
||||
|
||||
return frame
|
||||
try:
|
||||
|
||||
cv_image = GenerateCVImage( self._path )
|
||||
|
||||
resized_cv_image = EfficientlyResizeCVImage( cv_image, self._target_resolution )
|
||||
|
||||
return GenerateHydrusBitmapFromCVImage( resized_cv_image )
|
||||
|
||||
except:
|
||||
|
||||
pil_image = GeneratePILImage( self._path )
|
||||
|
||||
resized_pil_image = EfficientlyResizePILImage( pil_image, self._target_resolution )
|
||||
|
||||
return GenerateHydrusBitmapFromPILImage( resized_pil_image )
|
||||
|
||||
|
||||
|
||||
def _GetFrameCV( self ):
|
||||
|
||||
cv_image = GenerateCVImage( self._path )
|
||||
|
||||
return GenerateHydrusBitmapFromCVImage( EfficientlyResizeCVImage( cv_image, self._target_resolution ) )
|
||||
|
||||
|
||||
def _GetFramePIL( self ):
|
||||
|
||||
pil_image = GeneratePILImage( self._path )
|
||||
|
||||
return GenerateHydrusBitmapFromPILImage( EfficientlyResizePILImage( pil_image, self._target_resolution ) )
|
||||
|
||||
|
||||
def Render( self ): self._image_container.AddFrame( self._GetFrame() )
|
||||
def Render( self ): self._image_container.SetFrame( self._GetFrame() )
|
||||
|
||||
def THREADRender( self ):
|
||||
|
||||
time.sleep( 0.00001 ) # thread yield
|
||||
|
||||
wx.CallAfter( self._image_container.AddFrame, self._GetFrame() )
|
||||
wx.CallAfter( self._image_container.SetFrame, self._GetFrame() )
|
||||
|
||||
HC.pubsub.pub( 'finished_rendering', self._image_container.GetKey() )
|
||||
|
||||
|
@ -553,11 +609,18 @@ class HydrusBitmap():
|
|||
|
||||
class ImageContainer():
|
||||
|
||||
def __init__( self, media, target_resolution ):
|
||||
def __init__( self, media, target_resolution = None ):
|
||||
|
||||
if target_resolution is None: target_resolution = media.GetResolution()
|
||||
|
||||
self._media = media
|
||||
self._target_resolution = target_resolution
|
||||
|
||||
hash = self._media.GetHash()
|
||||
mime = self._media.GetMime()
|
||||
|
||||
self._path = CC.GetFilePath( hash, mime )
|
||||
|
||||
( original_width, original_height ) = self._media.GetResolution()
|
||||
|
||||
( my_width, my_height ) = target_resolution
|
||||
|
@ -574,29 +637,53 @@ class ImageContainer():
|
|||
|
||||
class ImageContainerAnimated( ImageContainer ):
|
||||
|
||||
def __init__( self, media, target_resolution ):
|
||||
NUM_FRAMES_BACKWARDS = 30
|
||||
NUM_FRAMES_FORWARDS = 15
|
||||
|
||||
def __init__( self, media, target_resolution = None, init_position = 0 ):
|
||||
|
||||
ImageContainer.__init__( self, media, target_resolution )
|
||||
|
||||
self._frames = []
|
||||
self._durations = []
|
||||
self._frames = {}
|
||||
self._last_index_asked_for = 0
|
||||
self._durations = GetFrameDurations( self._path )
|
||||
|
||||
self._renderer = AnimatedFrameRenderer( self, self._media, self._target_resolution )
|
||||
|
||||
self.SetPosition( init_position )
|
||||
|
||||
|
||||
def AddFrame( self, frame, duration = None ):
|
||||
def _MaintainBuffer( self ):
|
||||
|
||||
self._frames.append( frame )
|
||||
num_frames = self.GetNumFrames()
|
||||
|
||||
if duration is not None: self._durations.append( duration )
|
||||
if num_frames < self.NUM_FRAMES_BACKWARDS + 1 + self.NUM_FRAMES_FORWARDS: render_to_index = num_frames - 1
|
||||
else: render_to_index = self._last_index_asked_for + self.NUM_FRAMES_FORWARDS % num_frames
|
||||
|
||||
self._renderer.SetRenderToPosition( render_to_index )
|
||||
|
||||
indices_i_want = { ( self._last_index_asked_for + i ) % num_frames for i in range( -self.NUM_FRAMES_BACKWARDS, self.NUM_FRAMES_FORWARDS + 1 ) }
|
||||
|
||||
current_indices = set( self._frames.keys() )
|
||||
|
||||
deletees = current_indices.difference( indices_i_want )
|
||||
|
||||
for i in deletees: del self._frames[ i ]
|
||||
|
||||
|
||||
def AddFrame( self, index, frame ): self._frames[ index ] = frame
|
||||
|
||||
def GetDuration( self, index ): return self._durations[ index ]
|
||||
|
||||
def GetEstimatedMemoryFootprint( self ): return sum( [ frame.GetEstimatedMemoryFootprint() for frame in self._frames ] )
|
||||
|
||||
def GetFrame( self, index = None ):
|
||||
def GetFrame( self, index ):
|
||||
|
||||
if index is None: return self._frames[ 0 ]
|
||||
else: return self._frames[ index ]
|
||||
frame = self._frames[ index ]
|
||||
|
||||
self._last_index_asked_for = index
|
||||
|
||||
self._MaintainBuffer()
|
||||
|
||||
return frame
|
||||
|
||||
|
||||
def GetHash( self ): return self._media.GetHash()
|
||||
|
@ -605,8 +692,6 @@ class ImageContainerAnimated( ImageContainer ):
|
|||
|
||||
def GetNumFrames( self ): return self._media.GetNumFrames()
|
||||
|
||||
def GetNumFramesRendered( self ): return len( self._frames )
|
||||
|
||||
def GetResolution( self ): return self._media.GetResolution()
|
||||
|
||||
def GetSize( self ): return self._target_resolution
|
||||
|
@ -615,33 +700,39 @@ class ImageContainerAnimated( ImageContainer ):
|
|||
|
||||
def GetZoom( self ): return self._zoom
|
||||
|
||||
def HasFrame( self, index = None ):
|
||||
|
||||
if index is None: index = 0
|
||||
|
||||
return len( self._frames ) > index
|
||||
|
||||
|
||||
def IsAnimated( self ): return True
|
||||
|
||||
def IsFinishedRendering( self ): return len( self._frames ) == self.GetNumFrames()
|
||||
def HasFrame( self, index ): return index in self._frames
|
||||
|
||||
def IsScaled( self ): return self._zoom != 1.0
|
||||
|
||||
def SetPosition( self, index ):
|
||||
|
||||
num_frames = self.GetNumFrames()
|
||||
|
||||
self._last_index_asked_for = index
|
||||
|
||||
pre_index = max( 0, index - self.NUM_FRAMES_BACKWARDS ) % num_frames
|
||||
|
||||
self._renderer.SetPosition( pre_index )
|
||||
|
||||
self._MaintainBuffer()
|
||||
|
||||
|
||||
class ImageContainerStatic( ImageContainer ):
|
||||
|
||||
def __init__( self, media, target_resolution ):
|
||||
def __init__( self, media, target_resolution = None ):
|
||||
|
||||
ImageContainer.__init__( self, media, target_resolution )
|
||||
|
||||
self._frame = None
|
||||
|
||||
|
||||
def AddFrame( self, frame, duration = None ): self._frame = frame
|
||||
renderer = StaticFrameRenderer( self, self._media, self._target_resolution )
|
||||
|
||||
HydrusThreading.CallToThread( renderer.THREADRender )
|
||||
|
||||
|
||||
def GetEstimatedMemoryFootprint( self ): return self._frame.GetEstimatedMemoryFootprint()
|
||||
|
||||
def GetFrame( self, index = None ): return self._frame
|
||||
def GetFrame( self ): return self._frame
|
||||
|
||||
def GetHash( self ): return self._media.GetHash()
|
||||
|
||||
|
@ -655,11 +746,9 @@ class ImageContainerStatic( ImageContainer ):
|
|||
|
||||
def GetZoom( self ): return self._zoom
|
||||
|
||||
def HasFrame( self, index = None ): return self._frame is not None
|
||||
|
||||
def IsAnimated( self ): return False
|
||||
|
||||
def IsFinishedRendering( self ): return len( self._frames ) == 1
|
||||
def IsRendered( self ): return self._frame is not None
|
||||
|
||||
def IsScaled( self ): return self._zoom != 1.0
|
||||
|
||||
def SetFrame( self, frame ): self._frame = frame
|
||||
|
|
@ -129,7 +129,7 @@ class HTTPConnectionManager():
|
|||
|
||||
self._lock = threading.Lock()
|
||||
|
||||
threading.Thread( target = self.MaintainConnections, name = 'Maintain Connections' ).start()
|
||||
threading.Thread( target = self.DAEMONMaintainConnections, name = 'Maintain Connections' ).start()
|
||||
|
||||
|
||||
def _DoRequest( self, location, method, path_and_query, request_headers, body, follow_redirects = True, report_hooks = [], response_to_path = False, num_redirects_permitted = 4, long_timeout = False ):
|
||||
|
@ -203,7 +203,7 @@ class HTTPConnectionManager():
|
|||
else: return response
|
||||
|
||||
|
||||
def MaintainConnections( self ):
|
||||
def DAEMONMaintainConnections( self ):
|
||||
|
||||
while True:
|
||||
|
||||
|
|
|
@ -259,7 +259,7 @@ def ParseFileArguments( path ):
|
|||
|
||||
if mime in HC.IMAGES:
|
||||
|
||||
try: thumbnail = HydrusImageHandling.GenerateThumbnail( path )
|
||||
try: thumbnail = HydrusFileHandling.GenerateThumbnail( path )
|
||||
except: raise HydrusExceptions.ForbiddenException( 'Could not generate thumbnail from that file.' )
|
||||
|
||||
args[ 'thumbnail' ] = thumbnail
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
import collections
|
||||
import HydrusConstants as HC
|
||||
import itertools
|
||||
import os
|
||||
import Queue
|
||||
import random
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
import wx
|
||||
|
||||
class DAEMON( threading.Thread ):
|
||||
|
||||
def __init__( self, name, period = 1200 ):
|
||||
|
||||
threading.Thread.__init__( self, name = name )
|
||||
|
||||
self._name = name
|
||||
|
||||
self._event = threading.Event()
|
||||
|
||||
HC.pubsub.sub( self, 'shutdown', 'shutdown' )
|
||||
|
||||
|
||||
def shutdown( self ): self._event.set()
|
||||
|
||||
class DAEMONQueue( DAEMON ):
|
||||
|
||||
def __init__( self, name, callable, queue_topic, period = 10 ):
|
||||
|
||||
DAEMON.__init__( self, name )
|
||||
|
||||
self._callable = callable
|
||||
self._queue = Queue.Queue()
|
||||
self._queue_topic = queue_topic
|
||||
self._period = period
|
||||
|
||||
HC.pubsub.sub( self, 'put', queue_topic )
|
||||
|
||||
self.start()
|
||||
|
||||
|
||||
def put( self, data ): self._queue.put( data )
|
||||
|
||||
def run( self ):
|
||||
|
||||
time.sleep( 3 )
|
||||
|
||||
while True:
|
||||
|
||||
while self._queue.empty():
|
||||
|
||||
if HC.shutdown: return
|
||||
|
||||
self._event.wait( self._period )
|
||||
|
||||
self._event.clear()
|
||||
|
||||
|
||||
items = []
|
||||
|
||||
while not self._queue.empty(): items.append( self._queue.get() )
|
||||
|
||||
try: self._callable( items )
|
||||
except Exception as e: HC.ShowException( e )
|
||||
|
||||
|
||||
|
||||
class DAEMONWorker( DAEMON ):
|
||||
|
||||
def __init__( self, name, callable, topics = [], period = 1200, init_wait = 3 ):
|
||||
|
||||
DAEMON.__init__( self, name )
|
||||
|
||||
self._callable = callable
|
||||
self._topics = topics
|
||||
self._period = period
|
||||
self._init_wait = init_wait
|
||||
|
||||
for topic in topics: HC.pubsub.sub( self, 'set', topic )
|
||||
|
||||
self.start()
|
||||
|
||||
|
||||
def run( self ):
|
||||
|
||||
self._event.wait( self._init_wait )
|
||||
|
||||
while True:
|
||||
|
||||
if HC.shutdown: return
|
||||
|
||||
try: self._callable()
|
||||
except Exception as e: HC.ShowException( e )
|
||||
|
||||
if HC.shutdown: return
|
||||
|
||||
self._event.wait( self._period )
|
||||
|
||||
self._event.clear()
|
||||
|
||||
|
||||
|
||||
def set( self, *args, **kwargs ): self._event.set()
|
||||
|
||||
class DAEMONCallToThread( DAEMON ):
|
||||
|
||||
def __init__( self ):
|
||||
|
||||
DAEMON.__init__( self, 'CallToThread' )
|
||||
|
||||
self._queue = Queue.Queue()
|
||||
|
||||
self.start()
|
||||
|
||||
|
||||
def put( self, callable, *args, **kwargs ):
|
||||
|
||||
self._queue.put( ( callable, args, kwargs ) )
|
||||
|
||||
self._event.set()
|
||||
|
||||
|
||||
def run( self ):
|
||||
|
||||
while True:
|
||||
|
||||
while self._queue.empty():
|
||||
|
||||
if HC.shutdown: return
|
||||
|
||||
self._event.wait( 1200 )
|
||||
|
||||
self._event.clear()
|
||||
|
||||
|
||||
( callable, args, kwargs ) = self._queue.get()
|
||||
|
||||
try: callable( *args, **kwargs )
|
||||
except Exception as e: HC.ShowException( e )
|
||||
|
||||
|
||||
|
||||
call_to_threads = [ DAEMONCallToThread() for i in range( 10 ) ]
|
||||
|
||||
def CallToThread( callable, *args, **kwargs ):
|
||||
|
||||
call_to_thread = random.choice( call_to_threads )
|
||||
|
||||
while call_to_thread == threading.current_thread: call_to_thread = random.choice( call_to_threads )
|
||||
|
||||
call_to_thread.put( callable, *args, **kwargs )
|
||||
|
|
@ -2,6 +2,7 @@ import httplib
|
|||
import HydrusConstants as HC
|
||||
import HydrusServer
|
||||
import HydrusSessions
|
||||
import HydrusThreading
|
||||
import ServerConstants as SC
|
||||
import ServerDB
|
||||
import os
|
||||
|
@ -180,14 +181,14 @@ class Controller( wx.App ):
|
|||
|
||||
def StartDaemons( self ):
|
||||
|
||||
HC.DAEMONQueue( 'FlushRequestsMade', ServerDB.DAEMONFlushRequestsMade, 'request_made', period = 60 )
|
||||
HydrusThreading.DAEMONQueue( 'FlushRequestsMade', ServerDB.DAEMONFlushRequestsMade, 'request_made', period = 60 )
|
||||
|
||||
HC.DAEMONWorker( 'CheckMonthlyData', ServerDB.DAEMONCheckMonthlyData, period = 3600 )
|
||||
HC.DAEMONWorker( 'ClearBans', ServerDB.DAEMONClearBans, period = 3600 )
|
||||
HC.DAEMONWorker( 'DeleteOrphans', ServerDB.DAEMONDeleteOrphans, period = 86400 )
|
||||
HC.DAEMONWorker( 'GenerateUpdates', ServerDB.DAEMONGenerateUpdates, period = 1200 )
|
||||
HC.DAEMONWorker( 'CheckDataUsage', ServerDB.DAEMONCheckDataUsage, period = 86400 )
|
||||
HC.DAEMONWorker( 'UPnP', ServerDB.DAEMONUPnP, ( 'notify_new_options', ), period = 43200 )
|
||||
HydrusThreading.DAEMONWorker( 'CheckMonthlyData', ServerDB.DAEMONCheckMonthlyData, period = 3600 )
|
||||
HydrusThreading.DAEMONWorker( 'ClearBans', ServerDB.DAEMONClearBans, period = 3600 )
|
||||
HydrusThreading.DAEMONWorker( 'DeleteOrphans', ServerDB.DAEMONDeleteOrphans, period = 86400 )
|
||||
HydrusThreading.DAEMONWorker( 'GenerateUpdates', ServerDB.DAEMONGenerateUpdates, period = 1200 )
|
||||
HydrusThreading.DAEMONWorker( 'CheckDataUsage', ServerDB.DAEMONCheckDataUsage, period = 86400 )
|
||||
HydrusThreading.DAEMONWorker( 'UPnP', ServerDB.DAEMONUPnP, ( 'notify_new_options', ), period = 43200 )
|
||||
|
||||
|
||||
def Write( self, action, *args, **kwargs ):
|
||||
|
|
Loading…
Reference in New Issue