Version 120

This commit is contained in:
Hydrus 2014-06-25 15:37:06 -05:00
parent babac9e405
commit 11f375509f
12 changed files with 2269 additions and 382 deletions

View File

@ -8,6 +8,21 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 120</h3></li>
<ul>
<li>improved quality of downsized animation rendering</li>
<li>sped up downsized animation rendering in this case</li>
<li>neatened animation code</li>
<li>fixed a mid-animation resize parameter bug</li>
<li>I think I fixed an animation scan bug that was sometimes giving the wrong frames</li>
<li>added thumbnails for all video formats</li>
<li>fixed an off-by-one framecount bug for certain videos, and retroactively fixed counts for existing videos</li>
<li>fixed a couple harmless width/height numpy.shape switcharounds</li>
<li>cleaned up some file parsing code</li>
<li>added a pixiv unit test</li>
<li>fixed a bug when servers were returning unusual relative redirect urls (gelbooru)</li>
<li>semi-fixed a bug when servers were returning unescaped redirect urls (gelbooru)</li>
</ul>
<li><h3>version 119</h3></li>
<ul>
<li>fixed an overzealous error in v118 update code</li>

View File

@ -3079,7 +3079,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
if can_add:
( size, mime, width, height, duration, num_frames, num_words ) = HydrusFileHandling.GetFileInfo( path, hash )
( size, mime, width, height, duration, num_frames, num_words ) = HydrusFileHandling.GetFileInfo( path )
if width is not None and height is not None:
@ -4771,7 +4771,7 @@ class DB( ServiceDB ):
if resize_thumbs:
thumbnail_paths = ( path for path in CC.IterateAllThumbnailPaths() if path.endswith( '_resized' ) )
thumbnail_paths = [ path for path in CC.IterateAllThumbnailPaths() if path.endswith( '_resized' ) ]
for path in thumbnail_paths: os.remove( path )
@ -4907,10 +4907,52 @@ class DB( ServiceDB ):
i += 1
if i % 100 == 0: HC.app.SetSplashText( 'reprocessing thumbs: ' + HC.ConvertIntToPrettyString( i ) )
except: print( 'When updating to v118, ' + path + '\'s phash could not be recalculated.' )
if i % 100 == 0: HC.app.SetSplashText( 'reprocessing thumbs: ' + HC.ConvertIntToPrettyString( i ) )
if version == 119:
i = 0
for path in CC.IterateAllFilePaths():
try:
filename = os.path.basename( path )
( hash_encoded, ext ) = filename.split( '.' )
hash = hash_encoded.decode( 'hex' )
hash_id = self._GetHashId( c, hash )
if ext not in ( 'flv', 'mp4', 'wmv', 'mkv', 'webm' ): continue
( size, mime, width, height, duration, num_frames, num_words ) = HydrusFileHandling.GetFileInfo( path )
c.execute( 'UPDATE files_info SET duration = ?, num_frames = ? WHERE hash_id = ?;', ( duration, num_frames, hash_id ) )
thumbnail = HydrusFileHandling.GenerateThumbnail( path )
thumbnail_path = CC.GetExpectedThumbnailPath( hash )
with open( thumbnail_path, 'wb' ) as f: f.write( thumbnail )
phash = HydrusImageHandling.GeneratePerceptualHash( thumbnail_path )
c.execute( 'INSERT OR REPLACE INTO perceptual_hashes ( hash_id, phash ) VALUES ( ?, ? );', ( hash_id, sqlite3.Binary( phash ) ) )
i += 1
if i % 100 == 0: HC.app.SetSplashText( 'creating video thumbs: ' + HC.ConvertIntToPrettyString( i ) )
except:
print( traceback.format_exc())
print( 'When updating to v119, ' + path + '\'s thumbnail or phash could not be calculated.' )

View File

@ -64,7 +64,7 @@ options = {}
# Misc
NETWORK_VERSION = 13
SOFTWARE_VERSION = 119
SOFTWARE_VERSION = 120
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, VIDEO_WEBM )
MIMES_WITH_THUMBNAILS = ( IMAGE_JPEG, IMAGE_PNG, IMAGE_GIF, IMAGE_BMP, VIDEO_WEBM, VIDEO_FLV, VIDEO_MP4, VIDEO_WMV, VIDEO_MKV, VIDEO_WEBM )
# mp3 header is complicated

View File

@ -857,9 +857,7 @@ class DownloaderPixiv( Downloader ):
tag = self._query
tag = urllib.quote( tag.encode( 'utf-8' ) )
gallery_url = 'http://www.pixiv.net/search.php?word=' + urllib.quote( tag ) + '&s_mode=s_tag_full&order=date_d'
gallery_url = 'http://www.pixiv.net/search.php?word=' + urllib.quote( tag.encode( 'utf-8' ) ) + '&s_mode=s_tag_full&order=date_d'
return gallery_url + '&p=' + HC.u( self._num_pages_done + 1 )

View File

@ -1,4 +1,4 @@
import cv2
#import cv2
import hashlib
import hsaudiotag
import hsaudiotag.auto
@ -55,24 +55,36 @@ def GenerateThumbnail( path, dimensions = HC.UNSCALED_THUMBNAIL_DIMENSIONS ):
else:
cv_video = cv2.VideoCapture( path )
( size, mime, width, height, duration, num_frames, num_words ) = GetFileInfo( path )
cv_video.set( cv2.cv.CV_CAP_PROP_CONVERT_RGB, True )
cropped_dimensions = HydrusImageHandling.GetThumbnailResolution( ( width, height ), dimensions )
( retval, cv_image ) = cv_video.read()
renderer = HydrusVideoHandling.VideoRendererFFMPEG( path, mime, duration, num_frames, cropped_dimensions )
if not retval: raise Exception( 'Could not read first frame of ' + HC.u( path ) + ' to create thumbnail!' )
numpy_image = renderer.read_frame()
cv_image = HydrusImageHandling.EfficientlyThumbnailCVImage( cv_image, dimensions )
pil_image = HydrusImageHandling.GeneratePILImageFromNumpyImage( numpy_image )
( retval, thumbnail ) = cv2.imencode( '.jpg', cv_image, ( cv2.cv.CV_IMWRITE_JPEG_QUALITY, 92 ) )
f = cStringIO.StringIO()
if not retval: raise Exception( 'Could not export thumbnail for ' + HC.u( path ) + '!' )
pil_image.save( f, 'JPEG', quality=92 )
f.seek( 0 )
thumbnail = f.read()
f.close()
#numpy_image = cv2.cvtColor( numpy_image, cv2.COLOR_RGB2BGR )
#( retval, thumbnail ) = cv2.imencode( '.jpg', numpy_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 ):
def GetFileInfo( path ):
info = os.lstat( path )
@ -102,13 +114,9 @@ def GetFileInfo( path, hash ):
( ( width, height ), duration, num_frames ) = HydrusVideoHandling.GetFLVProperties( path )
elif mime in ( HC.VIDEO_WMV, HC.VIDEO_MP4 ):
elif mime in ( HC.VIDEO_WMV, HC.VIDEO_MP4, HC.VIDEO_MKV, HC.VIDEO_WEBM ):
( ( width, height ), duration, num_frames ) = HydrusVideoHandling.GetCVVideoProperties( path )
elif mime in ( HC.VIDEO_MKV, HC.VIDEO_WEBM ):
( ( width, height ), duration, num_frames ) = HydrusVideoHandling.GetCVVideoProperties( path )
( ( width, height ), duration, num_frames ) = HydrusVideoHandling.GetFFMPEGVideoProperties( path )
elif mime == HC.APPLICATION_PDF: num_words = HydrusDocumentHandling.GetPDFNumWords( path )
elif mime == HC.AUDIO_MP3: duration = HydrusAudioHandling.GetMP3Duration( path )

View File

@ -89,7 +89,7 @@ def GenerateCVImage( path ):
cv_image = cv2.imread( self._path, flags = -1 ) # flags = -1 loads alpha channel, if present
( x, y, depth ) = cv_image.shape
( y, x, depth ) = cv_image.shape
if depth == 4: raise Exception( 'CV is bad at alpha!' )
else: cv_image = cv2.cvtColor( cv_image, cv2.COLOR_BGR2RGB )
@ -102,7 +102,7 @@ def GenerateHydrusBitmap( path ):
cv_image = GenerateCVImage( path )
return GenerateHydrusBitmapFromCVImage( cv_image )
return GenerateHydrusBitmapFromNumPyImage( cv_image )
except:
@ -111,7 +111,7 @@ def GenerateHydrusBitmap( path ):
return GenerateHydrusBitmapFromPILImage( pil_image )
def GenerateHydrusBitmapFromCVImage( cv_image ):
def GenerateHydrusBitmapFromNumPyImage( cv_image ):
( y, x, depth ) = cv_image.shape
@ -137,7 +137,7 @@ def GeneratePerceptualHash( path ):
cv_image = cv2.imread( path, cv2.CV_LOAD_IMAGE_UNCHANGED )
( x, y, depth ) = cv_image.shape
( y, x, depth ) = cv_image.shape
if depth == 4:
@ -153,15 +153,15 @@ def GeneratePerceptualHash( path ):
cv_image_gray = cv2.cvtColor( cv_image_bgr, cv2.COLOR_BGR2GRAY )
cv_image_result = numpy.empty( ( x, y ), numpy.float32 )
cv_image_result = numpy.empty( ( y, x ), numpy.float32 )
# paste greyscale onto the white
# can't think of a better way to do this!
# cv2.addWeighted only takes a scalar for weight!
for i in range( x ):
for i in range( y ):
for j in range( y ):
for j in range( x ):
opacity = float( cv_alpha[ i, j ] ) / 255.0
@ -321,6 +321,17 @@ def old_GeneratePerceptualHash( path ):
def GeneratePILImage( path ): return PILImage.open( path )
def GeneratePILImageFromNumpyImage( numpy_image ):
( h, w, depth ) = numpy_image.shape
if depth == 3: format = 'RGB'
elif depth == 4: format = 'RGBA'
pil_image = PILImage.fromstring( format, ( w, h ), numpy_image.data )
return pil_image
def GetGIFFrameDurations( path ):
pil_image_for_duration = GeneratePILImage( path )
@ -568,7 +579,7 @@ class ImageContainer( RasterContainer ):
resized_cv_image = EfficientlyResizeCVImage( cv_image, self._target_resolution )
return GenerateHydrusBitmapFromCVImage( resized_cv_image )
return GenerateHydrusBitmapFromNumPyImage( resized_cv_image )
except:

View File

@ -3,6 +3,7 @@ import HydrusExceptions
import httplib
import threading
import time
import urllib
import urlparse
import yaml
@ -132,12 +133,15 @@ class HTTPConnectionManager():
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 ):
def _DoRequest( self, method, location, path, query, request_headers, body, follow_redirects = True, report_hooks = [], response_to_path = False, num_redirects_permitted = 4, long_timeout = False ):
connection = self._GetConnection( location, long_timeout )
try:
if query == '': path_and_query = path
else: path_and_query = path + '?' + query
with connection.lock:
( parsed_response, redirect_info, size_of_response, response_headers, cookies ) = connection.Request( method, path_and_query, request_headers, body, report_hooks = report_hooks, response_to_path = response_to_path )
@ -154,10 +158,7 @@ class HTTPConnectionManager():
if new_location is None: new_location = location
if new_query != '': new_path_and_query = new_path + '?' + new_query
else: new_path_and_query = new_path
return self._DoRequest( new_location, new_method, new_path_and_query, request_headers, body, report_hooks = report_hooks, response_to_path = response_to_path, num_redirects_permitted = num_redirects_permitted - 1 )
return self._DoRequest( new_method, new_location, new_path, new_query, request_headers, body, follow_redirects = follow_redirects, report_hooks = report_hooks, response_to_path = response_to_path, num_redirects_permitted = num_redirects_permitted - 1, long_timeout = long_timeout )
except:
@ -191,12 +192,9 @@ class HTTPConnectionManager():
( location, path, query ) = ParseURL( url )
if query != '': path_and_query = path + '?' + query
else: path_and_query = path
follow_redirects = not return_cookies
( response, size_of_response, response_headers, cookies ) = self._DoRequest( location, method, path_and_query, request_headers, body, follow_redirects = follow_redirects, report_hooks = report_hooks, response_to_path = response_to_path, long_timeout = long_timeout )
( response, size_of_response, response_headers, cookies ) = self._DoRequest( method, location, path, query, request_headers, body, follow_redirects = follow_redirects, report_hooks = report_hooks, response_to_path = response_to_path, long_timeout = long_timeout )
if return_everything: return ( response, size_of_response, response_headers, cookies )
elif return_cookies: return ( response, cookies )
@ -453,6 +451,23 @@ class HTTPConnection():
url = location
if ' ' in url:
# some booru is giving daft redirect responses
print( url )
url = urllib.quote( url, safe = '/?=&' )
print( url )
if not url.startswith( self._scheme ):
# assume it is like 'index.php' or '/index.php', rather than 'http://blah.com/index.php'
if url.startswith( '/' ): slash_sep = ''
else: slash_sep = '/'
url = self._scheme + '://' + self._host + slash_sep + url
if response.status in ( 301, 307 ):
# 301: moved permanently, repeat request

View File

@ -238,7 +238,7 @@ def ParseFileArguments( path ):
hash = HydrusFileHandling.GetHashFromPath( path )
try: ( size, mime, width, height, duration, num_frames, num_words ) = HydrusFileHandling.GetFileInfo( path, hash )
try: ( size, mime, width, height, duration, num_frames, num_words ) = HydrusFileHandling.GetFileInfo( path )
except HydrusExceptions.SizeException: raise HydrusExceptions.ForbiddenException( 'File is of zero length!' )
except HydrusExceptions.MimeException: raise HydrusExceptions.ForbiddenException( 'Filetype is not permitted!' )
except Exception as e: raise HydrusExceptions.ForbiddenException( HC.u( e ) )

View File

@ -40,6 +40,20 @@ def GetCVVideoProperties( path ):
return ( ( width, height ), duration, num_frames )
def GetFFMPEGVideoProperties( path ):
info = Hydrusffmpeg_parse_infos( path )
( w, h ) = info[ 'video_size' ]
duration_in_s = info[ 'duration' ]
duration = int( duration_in_s * 1000 )
num_frames = info[ 'video_nframes' ]
return ( ( w, h ), duration, num_frames )
def GetFLVProperties( path ):
with open( path, 'rb' ) as f:
@ -127,144 +141,7 @@ def GetMatroskaOrWebMProperties( path ):
return ( ( width, height ), duration, num_frames )
# This is cribbed from moviepy's FFMPEG_VideoReader
class HydrusFFMPEG_VideoReader( object ):
def __init__(self, media, print_infos=False, bufsize = None, pix_fmt="rgb24"):
self._media = media
hash = self._media.GetHash()
mime = self._media.GetMime()
self._path = CC.GetFilePath( hash, mime )
self.size = self._media.GetResolution()
self.duration = float( self._media.GetDuration() ) / 1000.0
self.nframes = self._media.GetNumFrames()
self.fps = float( self.nframes ) / self.duration
self.pix_fmt = pix_fmt
if pix_fmt == 'rgba': self.depth = 4
else: self.depth = 3
if bufsize is None:
( x, y ) = self.size
bufsize = self.depth * x * y * 5
self.process = None
self.bufsize = bufsize
self.initialize()
def __del__( self ):
self.close()
def close(self):
if self.process is not None:
self.process.terminate()
self.process.stdout.close()
self.process.stderr.close()
self.process = None
def initialize( self, starttime=0 ):
"""Opens the file, creates the pipe. """
self.close()
if starttime != 0:
offset = min( 1, starttime )
i_arg = [ '-ss', "%.03f" % ( starttime - offset ), '-i', '"' + self._path + '"' ]
else: i_arg = [ '-i', '"' + self._path + '"' ]
cmd = ([FFMPEG_PATH]+ i_arg +
['-loglevel', 'error',
'-f', 'image2pipe',
"-pix_fmt", self.pix_fmt,
'-vcodec', 'rawvideo', '-'])
self.process = subprocess.Popen( ' '.join( cmd ), shell = True, bufsize= self.bufsize, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
self.pos = int( round( self.fps * starttime ) )
def skip_frames(self, n=1):
"""Reads and throws away n frames """
( w, h ) = self.size
for i in range( n ):
self.process.stdout.read( self.depth * w * h )
self.process.stdout.flush()
self.pos += n
def read_frame( self ):
( w, h ) = self.size
nbytes = self.depth * w * h
s = self.process.stdout.read(nbytes)
self.pos += 1
if len(s) != nbytes:
print( "Warning: in file %s, "%(self._path)+
"%d bytes wanted but %d bytes read,"%(nbytes, len(s))+
"at frame %d/%d, at time %.02f/%.02f sec. "%(
self.pos,self.nframes,
1.0*self.pos/self.fps,
self.duration)+
"Using the last valid frame instead.")
result = self.lastread
self.close()
else:
result = numpy.fromstring( s, dtype = 'uint8' ).reshape( ( h, w, len( s ) // ( w * h ) ) )
self.lastread = result
return result
def set_position( self, pos ):
if ( pos < self.pos ) or ( pos > self.pos + 60 ):
starttime = float( pos ) / self.fps
self.initialize( starttime )
else: self.skip_frames( pos - self.pos )
# same thing; this is cribbed from moviepy
# this is cribbed from moviepy
def Hydrusffmpeg_parse_infos(filename, print_infos=False):
"""Get file infos using ffmpeg.
@ -306,9 +183,19 @@ def Hydrusffmpeg_parse_infos(filename, print_infos=False):
result = dict()
# get duration (in seconds)
# Duration: 00:00:02.46, start: 0.033000, bitrate: 1069 kb/s
try:
keyword = ('frame=' if is_GIF else 'Duration: ')
line = [l for l in lines if keyword in l][0]
if 'start:' in line:
m = re.search( '(start\\: )' + '[0-9]\\.[0-9]*', line )
start_offset = float( line[ m.start() + 7 : m.end() ] )
else: start_offset = 0
match = re.search("[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9]", line)
hms = map(float, line[match.start()+1:match.end()].split(':'))
@ -319,6 +206,8 @@ def Hydrusffmpeg_parse_infos(filename, print_infos=False):
elif len(hms) ==3:
result['duration'] = 3600*hms[0]+60*hms[1]+hms[2]
result[ 'duration' ] -= start_offset
except:
raise IOError("Error reading duration in file %s,"%(filename)+
"Text parsed: %s"%infos)
@ -345,9 +234,13 @@ def Hydrusffmpeg_parse_infos(filename, print_infos=False):
except:
match = re.search("( [0-9]*.| )[0-9]* fps", line)
result['video_fps'] = float(line[match.start():match.end()].split(' ')[1])
result['video_nframes'] = int(result['duration']*result['video_fps'])+1
num_frames = result['duration'] * result['video_fps']
if num_frames != int( num_frames ): num_frames += 1 # rounding up
result['video_nframes'] = int( num_frames )
result['video_duration'] = result['duration']
# We could have also recomputed the duration from the number
# of frames, as follows:
@ -378,8 +271,8 @@ class VideoContainer( HydrusImageHandling.RasterContainer ):
self._frames = {}
self._last_index_asked_for = -1
self._minimum_frame_asked_for = 0
self._maximum_frame_asked_for = 0
self._buffer_start_index = -1
self._buffer_end_index = -1
( x, y ) = self._target_resolution
@ -391,9 +284,21 @@ class VideoContainer( HydrusImageHandling.RasterContainer ):
if self._media.GetMime() == HC.IMAGE_GIF: self._durations = HydrusImageHandling.GetGIFFrameDurations( self._path )
else: self._frame_duration = GetVideoFrameDuration( self._path )
self._renderer = VideoRendererMoviePy( self, self._media, self._target_resolution )
self._render_lock = threading.Lock()
num_frames = self.GetNumFrames()
hash = self._media.GetHash()
mime = self._media.GetMime()
path = CC.GetFilePath( hash, mime )
duration = self._media.GetDuration()
num_frames = self._media.GetNumFrames()
self._ffmpeg_reader = VideoRendererFFMPEG( path, mime, duration, num_frames, target_resolution )
self._next_render_index = 0
self._render_to_index = -1
self._rendered_first_frame = False
self.SetPosition( init_position )
@ -404,19 +309,81 @@ class VideoContainer( HydrusImageHandling.RasterContainer ):
for index in self._frames.keys():
if self._minimum_frame_asked_for < self._maximum_frame_asked_for:
if self._buffer_start_index < self._buffer_end_index:
if index < self._minimum_frame_asked_for or self._maximum_frame_asked_for < index: deletees.append( index )
if index < self._buffer_start_index or self._buffer_end_index < index: deletees.append( index )
else:
if self._maximum_frame_asked_for < index and index < self._minimum_frame_asked_for: deletees.append( index )
if self._buffer_end_index < index and index < self._buffer_start_index: deletees.append( index )
for i in deletees: del self._frames[ i ]
def _RENDERERSetRenderToPosition( self, index ):
with self._render_lock:
if self._render_to_index != index:
self._render_to_index = index
HydrusThreading.CallToThread( self.THREADRender )
def _RENDERERSetPosition( self, index ):
with self._render_lock:
if index == self._next_render_index: return
else:
self._ffmpeg_reader.set_position( index )
self._next_render_index = index
self._render_to_index = index
def THREADRender( self ):
num_frames = self._media.GetNumFrames()
while True:
time.sleep( 0.00001 ) # thread yield
with self._render_lock:
if not self._rendered_first_frame or self._next_render_index != ( self._render_to_index + 1 ) % num_frames:
self._rendered_first_frame = True
frame_index = self._next_render_index # keep this before the get call, as it increments in a clock arithmetic way afterwards
try: numpy_image = self._ffmpeg_reader.read_frame()
except Exception as e:
HC.ShowException( e )
break
finally: self._next_render_index = ( self._next_render_index + 1 ) % num_frames
frame = HydrusImageHandling.GenerateHydrusBitmapFromNumPyImage( numpy_image )
wx.CallAfter( self.AddFrame, frame_index, frame )
else: break
def AddFrame( self, index, frame ): self._frames[ index ] = frame
def GetDuration( self, index ):
@ -464,61 +431,205 @@ class VideoContainer( HydrusImageHandling.RasterContainer ):
if num_frames > self._num_frames_backwards + 1 + self._num_frames_forwards:
new_minimum_frame_to_ask_for = max( 0, index - self._num_frames_backwards ) % num_frames
new_buffer_start_index = max( 0, index - self._num_frames_backwards ) % num_frames
new_maximum_frame_to_ask_for = ( index + self._num_frames_forwards ) % num_frames
new_buffer_end_index = ( index + self._num_frames_forwards ) % num_frames
if index == self._last_index_asked_for: return
elif index < self._last_index_asked_for:
if index < self._minimum_frame_asked_for:
if index < self._buffer_start_index:
self._minimum_frame_asked_for = new_minimum_frame_to_ask_for
self._buffer_start_index = new_buffer_start_index
self._renderer.SetPosition( self._minimum_frame_asked_for )
self._RENDERERSetPosition( self._buffer_start_index )
self._maximum_frame_asked_for = new_maximum_frame_to_ask_for
self._buffer_end_index = new_buffer_end_index
self._renderer.SetRenderToPosition( self._maximum_frame_asked_for )
self._RENDERERSetRenderToPosition( self._buffer_end_index )
else: # index > self._last_index_asked_for
currently_no_wraparound = self._minimum_frame_asked_for < self._maximum_frame_asked_for
currently_no_wraparound = self._buffer_start_index < self._buffer_end_index
self._minimum_frame_asked_for = new_minimum_frame_to_ask_for
self._buffer_start_index = new_buffer_start_index
if currently_no_wraparound:
if index > self._maximum_frame_asked_for:
if index > self._buffer_end_index:
self._renderer.SetPosition( self._minimum_frame_asked_for )
self._RENDERERSetPosition( self._buffer_start_index )
self._maximum_frame_asked_for = new_maximum_frame_to_ask_for
self._buffer_end_index = new_buffer_end_index
self._renderer.SetRenderToPosition( self._maximum_frame_asked_for )
self._RENDERERSetRenderToPosition( self._buffer_end_index )
else:
if self._maximum_frame_asked_for == 0:
if self._buffer_end_index == -1:
self._minimum_frame_asked_for = 0
self._buffer_start_index = 0
self._renderer.SetPosition( 0 )
self._RENDERERSetPosition( 0 )
self._maximum_frame_asked_for = num_frames - 1
self._buffer_end_index = num_frames - 1
self._renderer.SetRenderToPosition( self._maximum_frame_asked_for )
self._RENDERERSetRenderToPosition( self._buffer_end_index )
self._MaintainBuffer()
class VideoRendererCV():
# This was built from moviepy's FFMPEG_VideoReader
class VideoRendererFFMPEG( object ):
def __init__( self, path, mime, duration, num_frames, target_resolution, pix_fmt = "rgb24" ):
self._path = path
self._mime = mime
self._duration = float( duration ) / 1000.0
self._num_frames = num_frames
self._target_resolution = target_resolution
self.fps = float( self._num_frames ) / self._duration
if self.fps == 0: self.fps = 24
self.pix_fmt = pix_fmt
if pix_fmt == 'rgba': self.depth = 4
else: self.depth = 3
( x, y ) = self._target_resolution
bufsize = self.depth * x * y
self.process = None
self.bufsize = bufsize
self.initialize()
def __del__( self ):
self.close()
def close(self):
if self.process is not None:
self.process.terminate()
self.process.stdout.close()
self.process.stderr.close()
self.process = None
def initialize( self, start_index = 0 ):
self.close()
if self._mime == HC.IMAGE_GIF:
ss = 0
self.pos = 0
skip_frames = start_index
else:
ss = float( start_index ) / self.fps
self.pos = start_index
skip_frames = 0
( w, h ) = self._target_resolution
cmd = ( [ FFMPEG_PATH,
'-ss', "%.03f" % ss,
'-i', '"' + self._path + '"',
'-loglevel', 'quiet',
'-f', 'image2pipe',
"-pix_fmt", self.pix_fmt,
"-s", str( w ) + 'x' + str( h ),
'-vcodec', 'rawvideo', '-' ] )
self.process = subprocess.Popen( ' '.join( cmd ), shell = True, bufsize= self.bufsize, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
self.skip_frames( skip_frames )
def skip_frames( self, n ):
( w, h ) = self._target_resolution
for i in range( n ):
self.process.stdout.read( self.depth * w * h )
self.process.stdout.flush()
self.pos += 1
def read_frame( self ):
if self.pos == self._num_frames: self.initialize()
if self.process is None: result = self.lastread
else:
( w, h ) = self._target_resolution
nbytes = self.depth * w * h
s = self.process.stdout.read(nbytes)
if len(s) != nbytes:
print( "Warning: in file %s, "%(self._path)+
"%d bytes wanted but %d bytes read,"%(nbytes, len(s))+
"at frame %d/%d, at time %.02f/%.02f sec. "%(
self.pos,self._num_frames,
1.0*self.pos/self.fps,
self._duration)+
"Using the last valid frame instead.")
result = self.lastread
self.close()
else:
result = numpy.fromstring( s, dtype = 'uint8' ).reshape( ( h, w, len( s ) // ( w * h ) ) )
self.lastread = result
self.pos += 1
return result
def set_position( self, pos ):
rewind = pos < self.pos
jump_a_long_way_ahead = pos > self.pos + 60
if rewind or jump_a_long_way_ahead: self.initialize( pos )
else: self.skip_frames( pos - self.pos )
class OLDCODEVideoRendererCV():
def __init__( self, image_container, media, target_resolution ):
@ -531,13 +642,13 @@ class VideoRendererCV():
self._path = CC.GetFilePath( hash, mime )
self._lock = threading.Lock()
self._render_lock = threading.Lock()
self._cv_video = cv2.VideoCapture( self._path )
self._cv_video.set( cv2.cv.CV_CAP_PROP_CONVERT_RGB, True )
self._current_index = 0
self._next_render_index = 0
self._last_index_rendered = -1
self._last_frame = None
self._render_to_index = -1
@ -547,13 +658,13 @@ class VideoRendererCV():
( retval, cv_image ) = self._cv_video.read()
self._last_index_rendered = self._current_index
self._last_index_rendered = self._next_render_index
num_frames = self._media.GetNumFrames()
self._current_index = ( self._current_index + 1 ) % num_frames
self._next_render_index = ( self._next_render_index + 1 ) % num_frames
if self._current_index == 0 and self._last_index_rendered != 0:
if self._next_render_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 )
@ -561,7 +672,7 @@ class VideoRendererCV():
if not retval:
raise HydrusExceptions.CantRenderWithCVException( 'CV could not render frame ' + HC.u( self._current_index ) + '.' )
raise HydrusExceptions.CantRenderWithCVException( 'CV could not render frame ' + HC.u( self._next_render_index ) + '.' )
return cv_image
@ -584,27 +695,7 @@ class VideoRendererCV():
cv_image = self._last_frame
return HydrusImageHandling.GenerateHydrusBitmapFromCVImage( cv_image )
def _RenderFrames( self ):
no_frames_yet = True
while True:
try:
yield self._RenderCurrentFrame()
no_frames_yet = False
except HydrusExceptions.CantRenderWithCVException:
if no_frames_yet: raise
else: break
return HydrusImageHandling.GenerateHydrusBitmapFromNumPyImage( cv_image )
def _RewindGIF( self ):
@ -612,12 +703,12 @@ class VideoRendererCV():
self._cv_video.release()
self._cv_video.open( self._path )
self._current_index = 0
self._next_render_index = 0
def SetRenderToPosition( self, index ):
with self._lock:
with self._render_lock:
if self._render_to_index != index:
@ -630,14 +721,14 @@ class VideoRendererCV():
def SetPosition( self, index ):
with self._lock:
with self._render_lock:
if self._media.GetMime() == HC.IMAGE_GIF:
if index == self._current_index: return
elif index < self._current_index: self._RewindGIF()
if index == self._next_render_index: return
elif index < self._next_render_index: self._RewindGIF()
while self._current_index < index: self._GetCurrentFrame()
while self._next_render_index < index: self._GetCurrentFrame()
else:
@ -654,149 +745,11 @@ class VideoRendererCV():
time.sleep( 0.00001 ) # thread yield
with self._lock:
with self._render_lock:
if self._last_index_rendered != self._render_to_index:
index = self._current_index
frame = self._RenderCurrentFrame()
wx.CallAfter( self._image_container.AddFrame, index, frame )
else: break
class VideoRendererMoviePy():
def __init__( self, image_container, media, target_resolution ):
self._image_container = image_container
self._media = media
self._target_resolution = target_resolution
self._lock = threading.Lock()
( x, y ) = media.GetResolution()
self._ffmpeg_reader = HydrusFFMPEG_VideoReader( media, bufsize = x * y * 3 * 5 )
self._current_index = 0
self._last_index_rendered = -1
self._last_frame = None
self._render_to_index = -1
def _GetCurrentFrame( self ):
try: image = self._ffmpeg_reader.read_frame()
except Exception as e: raise HydrusExceptions.CantRenderWithCVException( 'FFMPEG could not render frame ' + HC.u( self._current_index ) + '.' + os.linesep * 2 + HC.u( e ) )
self._last_index_rendered = self._current_index
num_frames = self._media.GetNumFrames()
self._current_index = ( self._current_index + 1 ) % num_frames
if self._current_index == 0 and self._last_index_rendered != 0: self._ffmpeg_reader.initialize()
return image
def _RenderCurrentFrame( self ):
try:
image = self._GetCurrentFrame()
image = HydrusImageHandling.EfficientlyResizeCVImage( image, self._target_resolution )
self._last_frame = image
except HydrusExceptions.CantRenderWithCVException:
if self._last_frame is None: raise
image = self._last_frame
return HydrusImageHandling.GenerateHydrusBitmapFromCVImage( image )
def _RenderFrames( self ):
no_frames_yet = True
while True:
try:
yield self._RenderCurrentFrame()
no_frames_yet = False
except HydrusExceptions.CantRenderWithCVException:
if no_frames_yet: raise
else: break
def SetRenderToPosition( self, index ):
with self._lock:
if self._render_to_index != index:
self._render_to_index = index
HydrusThreading.CallToThread( self.THREADDoWork )
def SetPosition( self, index ):
with self._lock:
if index == self._current_index: return
else:
if self._media.GetMime() == HC.IMAGE_GIF:
if index < self._current_index:
self._ffmpeg_reader.initialize()
self._ffmpeg_reader.skip_frames( index )
else:
timecode = float( index ) / self._ffmpeg_reader.fps
self._ffmpeg_reader.set_position( timecode )
self._render_to_index = index
def THREADDoWork( self ):
while True:
time.sleep( 0.00001 ) # thread yield
with self._lock:
if self._last_index_rendered != self._render_to_index:
index = self._current_index
index = self._next_render_index
frame = self._RenderCurrentFrame()

View File

@ -98,7 +98,46 @@ class TestDownloaders( unittest.TestCase ):
info = ( data, tags )
expected_info = ('swf file', set([u'chores', u'laser', u'silent', u'title:Cat Dust', u'creator:warlord-of-noodles', u'pointer']))
expected_info = ( 'swf file', set([u'chores', u'laser', u'silent', u'title:Cat Dust', u'creator:warlord-of-noodles', u'pointer']) )
self.assertEqual( info, expected_info )
def test_pixiv( self ):
with open( HC.STATIC_DIR + os.path.sep + 'testing' + os.path.sep + 'pixiv_gallery.html' ) as f: pixiv_gallery = f.read()
with open( HC.STATIC_DIR + os.path.sep + 'testing' + os.path.sep + 'pixiv_page.html' ) as f: pixiv_page = f.read()
HC.http.SetResponse( HC.GET, 'http://www.pixiv.net/search.php?word=naruto&s_mode=s_tag_full&order=date_d&p=1', pixiv_gallery )
HC.http.SetResponse( HC.GET, 'http://www.pixiv.net/member_illust.php?mode=medium&illust_id=43718605', pixiv_page )
HC.http.SetResponse( HC.GET, 'http://i1.pixiv.net/img59/img/dbhope/43718605.jpg', 'image file' )
#
downloader = HydrusDownloading.DownloaderPixiv( 'tags', 'naruto' )
#
gallery_urls = downloader.GetAnotherPage()
expected_gallery_urls = [ ( u'http://www.pixiv.net/member_illust.php?mode=medium&illust_id=43718605', ), 'a bunch of others' ]
self.assertEqual( gallery_urls[0], expected_gallery_urls[0] )
#
info = downloader.GetFileAndTags( 'http://www.pixiv.net/member_illust.php?mode=medium&illust_id=43718605' )
( temp_path, tags ) = info
with open( temp_path, 'rb' ) as f: data = f.read()
info = ( data, tags )
expected_tags = [ u'1P\u6f2b\u753b', u'\u7720\u305f\u3044', u'NARUTO', u'\u30ca\u30eb\u30c8', u'\u30b5\u30b9\u30b1', u'\u30b5\u30af\u30e9', u'\u30d2\u30ca\u30bf', u'creator:\u30df\u30c4\u30ad\u30e8\u3063\u3057\uff5e', u'title:\u7720\u305f\u3044', 'creator:dbhope' ]
expected_info = ( 'image file', expected_tags )
self.assertEqual( info, expected_info )

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long