Version 124
This commit is contained in:
parent
b7eaed8695
commit
033829f432
|
@ -4,9 +4,10 @@
|
|||
# To Public License, Version 2, as published by Sam Hocevar. See
|
||||
# http://sam.zoy.org/wtfpl/COPYING for more details.
|
||||
|
||||
import wx
|
||||
import locale
|
||||
|
||||
wx.Locale()
|
||||
try: locale.setlocale( locale.LC_ALL, '' )
|
||||
except: pass
|
||||
|
||||
from include import HydrusConstants as HC
|
||||
|
||||
|
|
|
@ -8,6 +8,13 @@
|
|||
<div class="content">
|
||||
<h3>changelog</h3>
|
||||
<ul>
|
||||
<li><h3>version 124</h3></li>
|
||||
<ul>
|
||||
<li>fixed some more broken gifs (those with funky transparency, I think)</li>
|
||||
<li>fixed a critical bug in the subscriptions dialog that was causing subscriptions to be deleted</li>
|
||||
<li>reintroduced old locale code, with minor improvement, to fix number grouping (123,456,789) issue</li>
|
||||
<li>added a short pre-activation wait to all daemons to reduce more wake-from-sleep problems like the UPnP issue last week</li>
|
||||
</ul>
|
||||
<li><h3>version 123</h3></li>
|
||||
<ul>
|
||||
<li>rewrote the old gif rendering code to the new system, so gifs with variable framerates now work again!</li>
|
||||
|
@ -24,7 +31,7 @@
|
|||
<li>neatened all object declarations</li>
|
||||
<li>fixed a menu bug in OS X</li>
|
||||
<li>added put a little delay in the upnp daemon, hopefully to stop the error popups after waking from sleep</li>
|
||||
<li>updateid a old hacky bit of locale code that was breaking certain platforms of wx 2.9.5.0</li>
|
||||
<li>updated an old hacky bit of locale code that was breaking certain platforms of wx 2.9.5.0</li>
|
||||
<li>improved animation and other rendering timings on non-windows platforms</li>
|
||||
<li>improved media focus on non-windows platforms</li>
|
||||
</ul>
|
||||
|
|
|
@ -5386,6 +5386,8 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
|
|||
|
||||
self._original_subscription_names = HC.app.Read( 'subscription_names' )
|
||||
|
||||
self._names_to_delete = set()
|
||||
|
||||
InitialiseControls()
|
||||
|
||||
PopulateControls()
|
||||
|
@ -5498,15 +5500,20 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
|
|||
|
||||
all_pages = self._listbook.GetNameToPageDict().values()
|
||||
|
||||
subscriptions = [ page.GetSubscription() for page in all_pages ]
|
||||
|
||||
try:
|
||||
|
||||
for ( name, info ) in subscriptions: HC.app.Write( 'subscription', name, info )
|
||||
for name in self._names_to_delete: HC.app.Write( 'delete_subscription', name )
|
||||
|
||||
deletees = set( self._original_subscription_names ) - { name for ( name, info ) in subscriptions }
|
||||
|
||||
for name in deletees: HC.app.Write( 'delete_subscription', name )
|
||||
for page in all_pages:
|
||||
|
||||
( name, info ) = page.GetSubscription()
|
||||
|
||||
original_name = page.GetOriginalName()
|
||||
|
||||
if original_name != name: HC.app.Write( 'delete_subscription', original_name )
|
||||
|
||||
HC.app.Write( 'subscription', name, info )
|
||||
|
||||
|
||||
HC.subs_changed = True
|
||||
|
||||
|
@ -5530,6 +5537,10 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
|
|||
|
||||
panel = self._listbook.GetCurrentPage()
|
||||
|
||||
name = panel.GetOriginalName()
|
||||
|
||||
self._names_to_delete.add( name )
|
||||
|
||||
if panel is not None: self._listbook.DeleteCurrentPage()
|
||||
|
||||
|
||||
|
@ -5694,6 +5705,7 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
|
|||
|
||||
else: info = HC.app.Read( 'subscription', name )
|
||||
|
||||
self._original_name = name
|
||||
self._original_info = info
|
||||
|
||||
InitialiseControls()
|
||||
|
@ -5870,6 +5882,8 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
|
|||
return ( name, info )
|
||||
|
||||
|
||||
def GetOriginalName( self ): return self._original_name
|
||||
|
||||
def GetName( self ): return self._name.GetValue()
|
||||
|
||||
def Update( self, name, info ):
|
||||
|
|
|
@ -64,7 +64,7 @@ options = {}
|
|||
# Misc
|
||||
|
||||
NETWORK_VERSION = 13
|
||||
SOFTWARE_VERSION = 123
|
||||
SOFTWARE_VERSION = 124
|
||||
|
||||
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
|
||||
|
||||
|
|
|
@ -115,24 +115,43 @@ def GenerateHydrusBitmapFromNumPyImage( cv_image ):
|
|||
|
||||
( y, x, depth ) = cv_image.shape
|
||||
|
||||
if depth == 4: raise Exception( 'CV is bad at alpha!' )
|
||||
if depth == 4: return HydrusBitmap( cv_image.data, wx.BitmapBufferFormat_RGBA, ( x, y ) )
|
||||
else: return HydrusBitmap( cv_image.data, wx.BitmapBufferFormat_RGB, ( x, y ) )
|
||||
|
||||
def GenerateNumPyImageFromPILImage( pil_image ):
|
||||
|
||||
if pil_image.mode == 'RGBA' or ( pil_image.mode == 'P' and pil_image.info.has_key( 'transparency' ) ):
|
||||
|
||||
if pil_image.mode == 'P': pil_image = pil_image.convert( 'RGBA' )
|
||||
|
||||
else:
|
||||
|
||||
if pil_image.mode != 'RGB': pil_image = pil_image.convert( 'RGB' )
|
||||
|
||||
|
||||
( w, h ) = pil_image.size
|
||||
|
||||
s = pil_image.tostring()
|
||||
|
||||
return numpy.fromstring( s, dtype = 'uint8' ).reshape( ( h, w, len( s ) // ( w * h ) ) )
|
||||
|
||||
def GenerateHydrusBitmapFromPILImage( pil_image ):
|
||||
|
||||
if pil_image.mode == 'RGBA' or ( pil_image.mode == 'P' and pil_image.info.has_key( 'transparency' ) ):
|
||||
|
||||
if pil_image.mode == 'P': pil_image = pil_image.convert( 'RGBA' )
|
||||
|
||||
return HydrusBitmap( pil_image.tostring(), wx.BitmapBufferFormat_RGBA, pil_image.size )
|
||||
format = wx.BitmapBufferFormat_RGBA
|
||||
|
||||
else:
|
||||
|
||||
if pil_image.mode != 'RGB': pil_image = pil_image.convert( 'RGB' )
|
||||
|
||||
return HydrusBitmap( pil_image.tostring(), wx.BitmapBufferFormat_RGB, pil_image.size )
|
||||
format = wx.BitmapBufferFormat_RGB
|
||||
|
||||
|
||||
return HydrusBitmap( pil_image.tostring(), format, pil_image.size )
|
||||
|
||||
def GeneratePerceptualHash( path ):
|
||||
|
||||
cv_image = cv2.imread( path, cv2.CV_LOAD_IMAGE_UNCHANGED )
|
||||
|
|
|
@ -68,7 +68,7 @@ class DAEMONQueue( DAEMON ):
|
|||
|
||||
class DAEMONWorker( DAEMON ):
|
||||
|
||||
def __init__( self, name, callable, topics = [], period = 1200, init_wait = 3, pre_callable_wait = 0 ):
|
||||
def __init__( self, name, callable, topics = [], period = 1200, init_wait = 3, pre_callable_wait = 3 ):
|
||||
|
||||
DAEMON.__init__( self, name )
|
||||
|
||||
|
|
|
@ -293,7 +293,7 @@ class VideoContainer( HydrusImageHandling.RasterContainer ):
|
|||
|
||||
self._durations = HydrusImageHandling.GetGIFFrameDurations( self._path )
|
||||
|
||||
self._renderer = GIFRendererCV( path, num_frames, target_resolution )
|
||||
self._renderer = GIFRenderer( path, num_frames, target_resolution )
|
||||
|
||||
else:
|
||||
|
||||
|
@ -637,7 +637,7 @@ class VideoRendererFFMPEG( object ):
|
|||
else: self.skip_frames( pos - self.pos )
|
||||
|
||||
|
||||
class GIFRendererCV( object ):
|
||||
class GIFRenderer( object ):
|
||||
|
||||
def __init__( self, path, num_frames, target_resolution ):
|
||||
|
||||
|
@ -645,6 +645,66 @@ class GIFRendererCV( object ):
|
|||
self._num_frames = num_frames
|
||||
self._target_resolution = target_resolution
|
||||
|
||||
self._cv_mode = True
|
||||
|
||||
self._InitialiseCV()
|
||||
|
||||
|
||||
def _GetCurrentFrame( self ):
|
||||
|
||||
if self._cv_mode:
|
||||
|
||||
( retval, cv_image ) = self._cv_video.read()
|
||||
|
||||
if not retval:
|
||||
|
||||
self._next_render_index = ( self._next_render_index + 1 ) % self._num_frames
|
||||
|
||||
raise HydrusExceptions.CantRenderWithCVException( 'CV could not render frame ' + HC.u( self._next_render_index - 1 ) + '.' )
|
||||
|
||||
|
||||
else:
|
||||
|
||||
if self._pil_image.mode == 'P' and 'transparency' in self._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 = self._pil_image.convert( 'RGBA' )
|
||||
|
||||
if self._pil_canvas is None: self._pil_canvas = current_frame
|
||||
else: self._pil_canvas.paste( current_frame, None, current_frame ) # use the rgba image as its own mask
|
||||
|
||||
else: self._pil_canvas = self._pil_image
|
||||
|
||||
cv_image = HydrusImageHandling.GenerateNumPyImageFromPILImage( self._pil_canvas )
|
||||
|
||||
|
||||
self._next_render_index = ( self._next_render_index + 1 ) % self._num_frames
|
||||
|
||||
if self._next_render_index == 0:
|
||||
|
||||
self._RewindGIF()
|
||||
|
||||
else:
|
||||
|
||||
if not self._cv_mode:
|
||||
|
||||
self._pil_image.seek( self._next_render_index )
|
||||
|
||||
if self._pil_image.palette == self._pil_global_palette: # for some reason, when pil falls back from local palette to global palette, a bunch of important variables reset!
|
||||
|
||||
pil_image.palette.dirty = self._pil_dirty
|
||||
pil_image.palette.mode = self._pil_mode
|
||||
pil_image.palette.rawmode = self._pil_rawmode
|
||||
|
||||
|
||||
|
||||
|
||||
return cv_image
|
||||
|
||||
|
||||
def _InitialiseCV( self ):
|
||||
|
||||
self._cv_video = cv2.VideoCapture( self._path )
|
||||
|
||||
self._cv_video.set( cv2.cv.CV_CAP_PROP_CONVERT_RGB, True )
|
||||
|
@ -653,53 +713,80 @@ class GIFRendererCV( object ):
|
|||
self._last_frame = None
|
||||
|
||||
|
||||
def _GetCurrentFrame( self ):
|
||||
def _InitialisePIL( self ):
|
||||
|
||||
( retval, cv_image ) = self._cv_video.read()
|
||||
self._pil_image = HydrusImageHandling.GeneratePILImage( self._path )
|
||||
|
||||
self._next_render_index = ( self._next_render_index + 1 ) % self._num_frames
|
||||
self._pil_canvas = None
|
||||
|
||||
if self._next_render_index == 0:
|
||||
|
||||
self._RewindGIF()
|
||||
|
||||
#self._cv_video.set( cv2.cv.CV_CAP_PROP_POS_FRAMES, 0.0 )
|
||||
|
||||
self._pil_global_palette = self._pil_image.palette
|
||||
|
||||
if not retval:
|
||||
|
||||
raise HydrusExceptions.CantRenderWithCVException( 'CV could not render frame ' + HC.u( self._next_render_index ) + '.' )
|
||||
|
||||
self._pil_dirty = self._pil_image.palette.dirty
|
||||
self._pil_mode = self._pil_image.palette.mode
|
||||
self._pil_rawmode = self._pil_image.palette.rawmode
|
||||
|
||||
return cv_image
|
||||
self._next_render_index = 0
|
||||
self._last_frame = None
|
||||
|
||||
# believe it or not, doing this actually fixed a couple of gifs!
|
||||
self._pil_image.seek( 1 )
|
||||
self._pil_image.seek( 0 )
|
||||
|
||||
|
||||
def _RenderCurrentFrame( self ):
|
||||
|
||||
try:
|
||||
if self._cv_mode:
|
||||
|
||||
try:
|
||||
|
||||
cv_image = self._GetCurrentFrame()
|
||||
|
||||
cv_image = HydrusImageHandling.EfficientlyResizeCVImage( cv_image, self._target_resolution )
|
||||
|
||||
cv_image = cv2.cvtColor( cv_image, cv2.COLOR_BGR2RGB )
|
||||
|
||||
self._last_frame = cv_image
|
||||
|
||||
except HydrusExceptions.CantRenderWithCVException:
|
||||
|
||||
if self._last_frame is None:
|
||||
|
||||
self._cv_mode = False
|
||||
|
||||
self._InitialisePIL()
|
||||
|
||||
return self._RenderCurrentFrame()
|
||||
|
||||
else: cv_image = self._last_frame
|
||||
|
||||
|
||||
else:
|
||||
|
||||
cv_image = self._GetCurrentFrame()
|
||||
|
||||
cv_image = HydrusImageHandling.EfficientlyResizeCVImage( cv_image, self._target_resolution )
|
||||
|
||||
cv_image = cv2.cvtColor( cv_image, cv2.COLOR_BGR2RGB )
|
||||
|
||||
self._last_frame = cv_image
|
||||
|
||||
except HydrusExceptions.CantRenderWithCVException:
|
||||
|
||||
cv_image = self._last_frame
|
||||
|
||||
|
||||
return cv_image
|
||||
|
||||
|
||||
def _RewindGIF( self ):
|
||||
|
||||
self._cv_video.release()
|
||||
self._cv_video.open( self._path )
|
||||
|
||||
self._next_render_index = 0
|
||||
if self._cv_mode:
|
||||
|
||||
self._cv_video.release()
|
||||
self._cv_video.open( self._path )
|
||||
|
||||
self._next_render_index = 0
|
||||
|
||||
#self._cv_video.set( cv2.cv.CV_CAP_PROP_POS_FRAMES, 0.0 )
|
||||
|
||||
else:
|
||||
|
||||
self._pil_image.seek( 0 )
|
||||
|
||||
|
||||
|
||||
def read_frame( self ): return self._RenderCurrentFrame()
|
||||
|
|
|
@ -40,6 +40,17 @@ class TestHydrusDownloadingFunctions( unittest.TestCase ):
|
|||
self.assertEqual( HydrusDownloading.ConvertServiceIdentifiersToTagsToServiceIdentifiersToContentUpdates( hash, service_identifiers_to_tags ), content_updates )
|
||||
|
||||
|
||||
def test_number_conversion( self ):
|
||||
|
||||
i = 123456789
|
||||
|
||||
i_pretty = HC.ConvertIntToPrettyString( i )
|
||||
|
||||
# this test only works on anglo computers; it is mostly so I can check it is working on mine
|
||||
|
||||
self.assertEqual( i_pretty, '123,456,789' )
|
||||
|
||||
|
||||
def test_tags_to_dict( self ):
|
||||
|
||||
local = TestConstants.GenerateClientServiceIdentifier( HC.LOCAL_TAG )
|
||||
|
|
|
@ -4,9 +4,10 @@
|
|||
# To Public License, Version 2, as published by Sam Hocevar. See
|
||||
# http://sam.zoy.org/wtfpl/COPYING for more details.
|
||||
|
||||
import wx
|
||||
import locale
|
||||
|
||||
wx.Locale()
|
||||
try: locale.setlocale( locale.LC_ALL, '' )
|
||||
except: pass
|
||||
|
||||
from include import HydrusConstants as HC
|
||||
|
||||
|
|
Loading…
Reference in New Issue