Version 114
This commit is contained in:
parent
9f5a441c08
commit
21bd1e15fe
|
@ -17,6 +17,7 @@ from include import HydrusConstants as HC
|
|||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from include import ClientController
|
||||
import threading
|
||||
from twisted.internet import reactor
|
||||
|
|
|
@ -8,6 +8,16 @@
|
|||
<div class="content">
|
||||
<h3>changelog</h3>
|
||||
<ul>
|
||||
<li><h3>version 114</h3></li>
|
||||
<ul>
|
||||
<li>gif rendering seems to be fixed in all cases! hooray!</li>
|
||||
<li>fixed 'pop from empty list' popup error spam in the new cache clearing system</li>
|
||||
<li>fixed weird behaviour on right-clicking 'dismiss all' popups button</li>
|
||||
<li>added a unit test for perceptual hash generation, with an eye to moving the delicate code from cv to cv2</li>
|
||||
<li>updated opencv to 2.4.9</li>
|
||||
<li>updated pyinstaller, so frozen releases may be a bit more stable!</li>
|
||||
<li>moved webm and mkv info parsing over to cv, which allows num_frames</li>
|
||||
</ul>
|
||||
<li><h3>version 113</h3></li>
|
||||
<ul>
|
||||
<li>added mkv+webm support!</li>
|
||||
|
|
|
@ -1348,6 +1348,10 @@ class DataCache():
|
|||
|
||||
del self._keys_fifo[ 0 ]
|
||||
|
||||
data = self._keys_to_data[ key ]
|
||||
|
||||
self._total_estimated_memory_footprint -= data.GetEstimatedMemoryFootprint()
|
||||
|
||||
del self._keys_to_data[ key ]
|
||||
|
||||
else: break
|
||||
|
|
|
@ -1319,6 +1319,13 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
service_type = service_identifier.GetType()
|
||||
name = service_identifier.GetName()
|
||||
|
||||
if service_type in HC.CLIENT_SERVICES:
|
||||
|
||||
if 'max_monthly_data' not in info: info[ 'max_monthly_data' ] = None
|
||||
if 'port' not in info: info[ 'port' ] = 45871
|
||||
if 'upnp' not in info: info[ 'upnp' ] = None
|
||||
|
||||
|
||||
if service_type in HC.REMOTE_SERVICES:
|
||||
|
||||
if 'last_error' not in info: info[ 'last_error' ] = 0
|
||||
|
|
|
@ -2195,6 +2195,7 @@ class FrameReviewServices( ClientGUICommon.Frame ):
|
|||
elif service_type == HC.SERVER_ADMIN: name = 'servers admin'
|
||||
elif service_type == HC.LOCAL_RATING_LIKE: name = 'local ratings like'
|
||||
elif service_type == HC.LOCAL_RATING_NUMERICAL: name = 'local ratings numerical'
|
||||
elif service_type == HC.BOORU: name = 'booru'
|
||||
|
||||
self._listbook.AddPage( listbook, name )
|
||||
|
||||
|
@ -2237,6 +2238,15 @@ class FrameReviewServices( ClientGUICommon.Frame ):
|
|||
|
||||
def InitialiseControls():
|
||||
|
||||
if service_type in HC.CLIENT_SERVICES:
|
||||
|
||||
# show bandwidth used and so on
|
||||
|
||||
# if a booru, show how many shares currently active
|
||||
|
||||
pass
|
||||
|
||||
|
||||
if service_type in HC.RESTRICTED_SERVICES:
|
||||
|
||||
self._permissions_panel = ClientGUICommon.StaticBox( self, 'service permissions' )
|
||||
|
@ -2330,6 +2340,15 @@ class FrameReviewServices( ClientGUICommon.Frame ):
|
|||
|
||||
vbox = wx.BoxSizer( wx.VERTICAL )
|
||||
|
||||
if service_type in HC.CLIENT_SERVICES:
|
||||
|
||||
# show bandwidth used and so on
|
||||
|
||||
# if a booru, show how many shares currently active
|
||||
|
||||
pass
|
||||
|
||||
|
||||
if service_type in HC.RESTRICTED_SERVICES:
|
||||
|
||||
self._permissions_panel.AddF( self._account_type, FLAGS_EXPAND_PERPENDICULAR )
|
||||
|
@ -2441,6 +2460,15 @@ class FrameReviewServices( ClientGUICommon.Frame ):
|
|||
|
||||
now = HC.GetNow()
|
||||
|
||||
if service_type in HC.CLIENT_SERVICES:
|
||||
|
||||
# show bandwidth used and so on
|
||||
|
||||
# if a booru, show how many shares currently active
|
||||
|
||||
pass
|
||||
|
||||
|
||||
if service_type in HC.RESTRICTED_SERVICES:
|
||||
|
||||
account = self._service.GetAccount()
|
||||
|
@ -2536,6 +2564,15 @@ class FrameReviewServices( ClientGUICommon.Frame ):
|
|||
|
||||
self._DisplayAccountInfo()
|
||||
|
||||
if service_type in HC.CLIENT_SERVICES:
|
||||
|
||||
# show bandwidth used and so on
|
||||
|
||||
# if a booru, show how many shares currently active
|
||||
|
||||
pass
|
||||
|
||||
|
||||
if service_type in [ HC.FILE_REPOSITORY, HC.TAG_REPOSITORY, HC.LOCAL_FILE, HC.LOCAL_TAG, HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ]:
|
||||
|
||||
service_info = HC.app.Read( 'service_info', self._service_identifier )
|
||||
|
|
|
@ -2392,6 +2392,8 @@ class PopupDismissAll( PopupWindow ):
|
|||
self.SetSizer( hbox )
|
||||
|
||||
|
||||
def Dismiss( self ): pass
|
||||
|
||||
def EventButton( self, event ): self.GetParent().DismissAll()
|
||||
|
||||
def SetNumMessages( self, num_messages_pending ): self._text.SetLabel( HC.ConvertIntToPrettyString( num_messages_pending ) + ' more messages' )
|
||||
|
|
|
@ -4517,6 +4517,8 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
|
|||
self._listbook.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventServiceChanging )
|
||||
self._listbook.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventPageChanging, source = self._listbook )
|
||||
|
||||
# boorus
|
||||
|
||||
self._local_ratings_like = ClientGUICommon.ListBook( self._listbook )
|
||||
self._local_ratings_like.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventServiceChanging )
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ options = {}
|
|||
# Misc
|
||||
|
||||
NETWORK_VERSION = 13
|
||||
SOFTWARE_VERSION = 113
|
||||
SOFTWARE_VERSION = 114
|
||||
|
||||
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
|
||||
|
||||
|
@ -166,6 +166,7 @@ RATING_NUMERICAL_REPOSITORY = 8
|
|||
RATING_LIKE_REPOSITORY = 9
|
||||
COMBINED_TAG = 10
|
||||
COMBINED_FILE = 11
|
||||
BOORU = 12
|
||||
SERVER_ADMIN = 99
|
||||
NULL_SERVICE = 100
|
||||
|
||||
|
@ -182,6 +183,7 @@ service_string_lookup[ RATING_NUMERICAL_REPOSITORY ] = 'hydrus numerical rating
|
|||
service_string_lookup[ RATING_LIKE_REPOSITORY ] = 'hydrus like/dislike rating repository'
|
||||
service_string_lookup[ COMBINED_TAG ] = 'virtual combined tag service'
|
||||
service_string_lookup[ COMBINED_FILE ] = 'virtual combined file service'
|
||||
service_string_lookup[ BOORU ] = 'hydrus booru'
|
||||
service_string_lookup[ SERVER_ADMIN ] = 'hydrus server administration'
|
||||
service_string_lookup[ NULL_SERVICE ] = 'null service'
|
||||
|
||||
|
@ -189,6 +191,7 @@ RATINGS_SERVICES = [ LOCAL_RATING_LIKE, LOCAL_RATING_NUMERICAL, RATING_LIKE_REPO
|
|||
REPOSITORIES = [ TAG_REPOSITORY, FILE_REPOSITORY, RATING_LIKE_REPOSITORY, RATING_NUMERICAL_REPOSITORY ]
|
||||
RESTRICTED_SERVICES = ( REPOSITORIES ) + [ SERVER_ADMIN, MESSAGE_DEPOT ]
|
||||
REMOTE_SERVICES = list( RESTRICTED_SERVICES )
|
||||
CLIENT_SERVICES = [ BOORU ]
|
||||
ALL_SERVICES = list( REMOTE_SERVICES ) + [ LOCAL_FILE, LOCAL_TAG, LOCAL_RATING_LIKE, LOCAL_RATING_NUMERICAL ]
|
||||
|
||||
SERVICES_WITH_THUMBNAILS = [ FILE_REPOSITORY, LOCAL_FILE ]
|
||||
|
|
|
@ -65,7 +65,7 @@ def GetFileInfo( path, hash ):
|
|||
|
||||
elif mime in ( HC.VIDEO_MKV, HC.VIDEO_WEBM ):
|
||||
|
||||
( ( width, height ), duration, num_frames ) = HydrusVideoHandling.GetMatroskaOrWebMProperties( path )
|
||||
( ( width, height ), duration, num_frames ) = HydrusVideoHandling.GetCVVideoProperties( path )
|
||||
|
||||
elif mime == HC.APPLICATION_PDF: num_words = HydrusDocumentHandling.GetPDFNumWords( path )
|
||||
elif mime == HC.AUDIO_MP3: duration = HydrusAudioHandling.GetMP3Duration( path )
|
||||
|
|
|
@ -248,6 +248,34 @@ def GenerateThumbnail( path, dimensions = HC.UNSCALED_THUMBNAIL_DIMENSIONS ):
|
|||
|
||||
return thumbnail
|
||||
|
||||
def GetFrameDurations( path ):
|
||||
|
||||
pil_image_for_duration = GeneratePILImage( path )
|
||||
|
||||
frame_durations = []
|
||||
|
||||
i = 0
|
||||
|
||||
while True:
|
||||
|
||||
try: pil_image_for_duration.seek( i )
|
||||
except: break
|
||||
|
||||
if 'duration' not in pil_image_for_duration.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_for_duration.info[ 'duration' ]
|
||||
|
||||
if duration == 0: duration = 40
|
||||
|
||||
|
||||
frame_durations.append( duration )
|
||||
|
||||
i += 1
|
||||
|
||||
|
||||
return frame_durations
|
||||
|
||||
def GetHammingDistance( phash1, phash2 ):
|
||||
|
||||
distance = 0
|
||||
|
@ -306,10 +334,12 @@ class AnimatedFrameRenderer( FrameRenderer ):
|
|||
|
||||
# this code initially written by @fluffy_cub
|
||||
|
||||
frame_durations = GetFrameDurations( self._path )
|
||||
|
||||
cv_image = cv2.VideoCapture( self._path )
|
||||
cv_image.set( cv2.cv.CV_CAP_PROP_CONVERT_RGB, True )
|
||||
|
||||
no_frames_yet = False
|
||||
no_frames_yet = True
|
||||
|
||||
while True:
|
||||
|
||||
|
@ -322,13 +352,16 @@ class AnimatedFrameRenderer( FrameRenderer ):
|
|||
|
||||
else:
|
||||
|
||||
no_frames_yet = False
|
||||
|
||||
rgb_data = cv2.cvtColor( frame, cv2.COLOR_BGR2RGBA )
|
||||
|
||||
pil_frame = PILImage.fromarray( rgb_data, 'RGBA' )
|
||||
|
||||
pil_frame = EfficientlyResizeImage( pil_frame, self._target_resolution )
|
||||
|
||||
duration = 40 # will have to use pil to get accurate duration, as cv assumes 25fps
|
||||
try: duration = frame_durations.pop( 0 )
|
||||
except: duration = 40
|
||||
|
||||
yield ( GenerateHydrusBitmapFromPILImage( pil_frame ), duration )
|
||||
|
||||
|
@ -374,9 +407,8 @@ class AnimatedFrameRenderer( FrameRenderer ):
|
|||
|
||||
def GetFrames( self ):
|
||||
|
||||
for ( frame, duration ) in self._GetFramesPIL(): yield ( frame, duration )
|
||||
#for ( frame, duration ) in self._GetFramesPIL(): yield ( frame, duration )
|
||||
|
||||
'''
|
||||
try:
|
||||
|
||||
for ( frame, duration ) in self._GetFramesCV(): yield ( frame, duration )
|
||||
|
@ -385,7 +417,7 @@ class AnimatedFrameRenderer( FrameRenderer ):
|
|||
|
||||
for ( frame, duration ) in self._GetFramesPIL(): yield ( frame, duration )
|
||||
|
||||
'''
|
||||
|
||||
|
||||
def Render( self ):
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import numpy.core.multiarray # important this comes before cv!
|
||||
import cv
|
||||
#import numpy.core.multiarray # important this comes before cv!
|
||||
import cv2
|
||||
from flvlib import tags as flv_tags
|
||||
import HydrusConstants as HC
|
||||
import matroska
|
||||
|
@ -47,11 +47,11 @@ def GetFLVProperties( path ):
|
|||
|
||||
def GetCVVideoProperties( path ):
|
||||
|
||||
cvcapture = cv.CaptureFromFile( path )
|
||||
capture = cv2.VideoCapture( path )
|
||||
|
||||
num_frames = int( cv.GetCaptureProperty( cvcapture, cv.CV_CAP_PROP_FRAME_COUNT ) )
|
||||
num_frames = int( capture.get( cv2.cv.CV_CAP_PROP_FRAME_COUNT ) )
|
||||
|
||||
fps = cv.GetCaptureProperty( cvcapture, cv.CV_CAP_PROP_FPS )
|
||||
fps = capture.get( cv2.cv.CV_CAP_PROP_FPS )
|
||||
|
||||
length_in_seconds = num_frames / fps
|
||||
|
||||
|
@ -59,9 +59,9 @@ def GetCVVideoProperties( path ):
|
|||
|
||||
duration = length_in_ms
|
||||
|
||||
width = int( cv.GetCaptureProperty( cvcapture, cv.CV_CAP_PROP_FRAME_WIDTH ) )
|
||||
width = int( capture.get( cv2.cv.CV_CAP_PROP_FRAME_WIDTH ) )
|
||||
|
||||
height = int( cv.GetCaptureProperty( cvcapture, cv.CV_CAP_PROP_FRAME_HEIGHT ) )
|
||||
height = int( capture.get( cv2.cv.CV_CAP_PROP_FRAME_HEIGHT ) )
|
||||
|
||||
return ( ( width, height ), duration, num_frames )
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import collections
|
||||
import HydrusConstants as HC
|
||||
import HydrusImageHandling
|
||||
import os
|
||||
import TestConstants
|
||||
import unittest
|
||||
import wx
|
||||
|
||||
class TestImageHandling( unittest.TestCase ):
|
||||
|
||||
def test_phash( self ):
|
||||
|
||||
phash = HydrusImageHandling.GeneratePerceptualHash( HC.STATIC_DIR + os.path.sep + 'hydrus.png' )
|
||||
|
||||
self.assertEqual( phash, 'a2088220080a2808'.decode( 'hex' ) )
|
||||
|
4
test.py
4
test.py
|
@ -12,6 +12,7 @@ from include import TestDB
|
|||
from include import TestFunctions
|
||||
from include import TestHydrusDownloading
|
||||
from include import TestHydrusEncryption
|
||||
from include import TestHydrusImageHandling
|
||||
from include import TestHydrusNATPunch
|
||||
from include import TestHydrusServer
|
||||
from include import TestHydrusSessions
|
||||
|
@ -79,9 +80,10 @@ class App( wx.App ):
|
|||
if run_all or only_run == 'daemons': suites.append( unittest.TestLoader().loadTestsFromModule( TestClientDaemons ) )
|
||||
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( TestHydrusDownloading ) )
|
||||
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 == 'downloading': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusDownloading ) )
|
||||
if run_all or only_run == 'image': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusImageHandling ) )
|
||||
if run_all or only_run == 'nat': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusNATPunch ) )
|
||||
if run_all or only_run == 'server': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusServer ) )
|
||||
if run_all or only_run == 'sessions': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusSessions ) )
|
||||
|
|
Loading…
Reference in New Issue