2013-07-10 20:25:57 +00:00
|
|
|
import HydrusConstants as HC
|
2015-06-17 20:01:41 +00:00
|
|
|
import HydrusData
|
2014-05-28 21:03:24 +00:00
|
|
|
import HydrusExceptions
|
|
|
|
import HydrusImageHandling
|
2017-05-24 20:28:24 +00:00
|
|
|
import HydrusPaths
|
2014-05-28 21:03:24 +00:00
|
|
|
import HydrusThreading
|
2014-04-30 21:31:40 +00:00
|
|
|
import matroska
|
2014-06-18 21:53:48 +00:00
|
|
|
import numpy
|
2013-07-10 20:25:57 +00:00
|
|
|
import os
|
2014-06-18 21:53:48 +00:00
|
|
|
import re
|
|
|
|
import subprocess
|
2015-09-23 21:21:02 +00:00
|
|
|
import sys
|
|
|
|
import tempfile
|
2013-02-19 00:11:43 +00:00
|
|
|
import traceback
|
2014-05-28 21:03:24 +00:00
|
|
|
import threading
|
|
|
|
import time
|
2013-02-19 00:11:43 +00:00
|
|
|
|
2015-11-18 22:44:07 +00:00
|
|
|
if HC.PLATFORM_LINUX or HC.PLATFORM_OSX:
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2015-11-18 22:44:07 +00:00
|
|
|
FFMPEG_PATH = os.path.join( HC.BIN_DIR, 'ffmpeg' )
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2015-11-18 22:44:07 +00:00
|
|
|
elif HC.PLATFORM_WINDOWS:
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2015-11-18 22:44:07 +00:00
|
|
|
FFMPEG_PATH = os.path.join( HC.BIN_DIR, 'ffmpeg.exe' )
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2016-12-07 22:12:52 +00:00
|
|
|
if not os.path.exists( FFMPEG_PATH ):
|
|
|
|
|
|
|
|
FFMPEG_PATH = os.path.basename( FFMPEG_PATH )
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2016-12-14 21:19:07 +00:00
|
|
|
def GetFFMPEGVersion():
|
|
|
|
# open the file in a pipe, provoke an error, read output
|
|
|
|
|
|
|
|
cmd = [ FFMPEG_PATH, '-version' ]
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
proc = subprocess.Popen( cmd, bufsize=10**5, stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo = HydrusData.GetSubprocessStartupInfo() )
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
|
|
if not os.path.exists( FFMPEG_PATH ):
|
|
|
|
|
|
|
|
return 'no ffmpeg found'
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
HydrusData.ShowException( e )
|
|
|
|
|
|
|
|
return 'unable to execute ffmpeg'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
infos = proc.stdout.read().decode( 'utf8' )
|
|
|
|
|
|
|
|
proc.terminate()
|
|
|
|
|
|
|
|
del proc
|
|
|
|
|
|
|
|
lines = infos.splitlines()
|
|
|
|
|
|
|
|
if len( lines ) > 0:
|
|
|
|
|
|
|
|
# typically 'ffmpeg version [VERSION] Copyright ...
|
|
|
|
top_line = lines[0]
|
|
|
|
|
|
|
|
if top_line.startswith( 'ffmpeg version ' ):
|
|
|
|
|
|
|
|
top_line = top_line.replace( 'ffmpeg version ', '' )
|
|
|
|
|
|
|
|
if ' ' in top_line:
|
|
|
|
|
|
|
|
version_string = top_line.split( ' ' )[0]
|
|
|
|
|
|
|
|
return version_string
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return 'unknown'
|
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
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 )
|
|
|
|
|
2014-04-30 21:31:40 +00:00
|
|
|
def GetMatroskaOrWebm( path ):
|
|
|
|
|
2016-10-12 21:52:50 +00:00
|
|
|
try:
|
|
|
|
|
|
|
|
# a whole bunch of otherwise good webms aren't parseable by this, so default to 'I guess it is a webm, then.'
|
|
|
|
|
|
|
|
tags = matroska.parse( path )
|
|
|
|
|
|
|
|
ebml = tags[ 'EBML' ][0]
|
|
|
|
|
|
|
|
if ebml[ 'DocType' ][0] == 'matroska':
|
|
|
|
|
|
|
|
return HC.VIDEO_MKV
|
|
|
|
|
|
|
|
|
|
|
|
except:
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
2014-04-30 21:31:40 +00:00
|
|
|
|
2016-10-12 21:52:50 +00:00
|
|
|
return HC.VIDEO_WEBM
|
2014-04-30 21:31:40 +00:00
|
|
|
|
|
|
|
def GetMatroskaOrWebMProperties( path ):
|
|
|
|
|
|
|
|
tags = matroska.parse( path )
|
|
|
|
|
|
|
|
segment = tags['Segment'][0]
|
|
|
|
|
|
|
|
info = segment['Info'][0]
|
|
|
|
duration = int( ( info['Duration'][0] * info['TimecodeScale'][0] / 1e9 ) * 1000 )
|
|
|
|
|
|
|
|
tracks = segment['Tracks'][0]
|
|
|
|
trackentries = tracks['TrackEntry']
|
|
|
|
|
|
|
|
for trackentry in trackentries:
|
|
|
|
|
|
|
|
if 'Video' in trackentry:
|
|
|
|
|
|
|
|
video = trackentry['Video'][0]
|
|
|
|
|
|
|
|
width = video[ 'PixelWidth' ][0]
|
|
|
|
height = video[ 'PixelHeight' ][0]
|
|
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
num_frames = 0
|
|
|
|
|
|
|
|
return ( ( width, height ), duration, num_frames )
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2016-08-17 20:07:22 +00:00
|
|
|
def GetMimeFromFFMPEG( path ):
|
|
|
|
|
|
|
|
info = Hydrusffmpeg_parse_infos( path )
|
|
|
|
|
|
|
|
if 'mime_text' in info:
|
|
|
|
|
|
|
|
mime_text = info[ 'mime_text' ]
|
|
|
|
|
|
|
|
if 'matroska' in mime_text or 'webm' in mime_text:
|
|
|
|
|
|
|
|
# typically it is 'matroska,webm'
|
|
|
|
|
|
|
|
return GetMatroskaOrWebm( path )
|
|
|
|
|
2016-10-26 20:45:34 +00:00
|
|
|
elif mime_text in ( 'mpeg', 'mpegvideo', 'mpegts' ):
|
2016-08-17 20:07:22 +00:00
|
|
|
|
|
|
|
return HC.VIDEO_MPEG
|
|
|
|
|
2017-02-01 21:11:17 +00:00
|
|
|
elif mime_text == 'flac':
|
|
|
|
|
|
|
|
return HC.AUDIO_FLAC
|
|
|
|
|
|
|
|
elif mime_text == 'mp3':
|
|
|
|
|
|
|
|
return HC.AUDIO_MP3
|
|
|
|
|
|
|
|
elif mime_text == 'ogg':
|
|
|
|
|
|
|
|
return HC.AUDIO_OGG
|
|
|
|
|
|
|
|
elif mime_text == 'asf':
|
|
|
|
|
|
|
|
if info[ 'video_found' ]:
|
|
|
|
|
|
|
|
return HC.VIDEO_WMV
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
return HC.AUDIO_WMA
|
|
|
|
|
|
|
|
|
2016-08-17 20:07:22 +00:00
|
|
|
|
|
|
|
|
2017-01-04 22:48:23 +00:00
|
|
|
return HC.APPLICATION_UNKNOWN
|
2016-08-17 20:07:22 +00:00
|
|
|
|
2015-11-18 22:44:07 +00:00
|
|
|
def HasVideoStream( path ):
|
|
|
|
|
2016-02-24 21:42:54 +00:00
|
|
|
try:
|
|
|
|
|
|
|
|
info = Hydrusffmpeg_parse_infos( path )
|
|
|
|
|
|
|
|
except IOError as e:
|
2016-06-01 20:04:15 +00:00
|
|
|
|
|
|
|
HydrusData.ShowException( 'Determining the mime for the file at ' + path + ' caused the following problem:' )
|
2016-02-24 21:42:54 +00:00
|
|
|
HydrusData.ShowException( e )
|
2016-06-01 20:04:15 +00:00
|
|
|
|
2016-02-24 21:42:54 +00:00
|
|
|
return False
|
|
|
|
|
2015-11-18 22:44:07 +00:00
|
|
|
|
|
|
|
return info[ 'video_found' ]
|
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
# this is cribbed from moviepy
|
2017-05-31 21:50:53 +00:00
|
|
|
def Hydrusffmpeg_parse_infos(filename, print_infos=False, count_frames_manually = False ):
|
2014-06-18 21:53:48 +00:00
|
|
|
"""Get file infos using ffmpeg.
|
|
|
|
|
|
|
|
Returns a dictionnary with the fields:
|
|
|
|
"video_found", "video_fps", "duration", "video_nframes",
|
|
|
|
"video_duration"
|
|
|
|
"audio_found", "audio_fps"
|
|
|
|
|
|
|
|
"video_duration" is slightly smaller than "duration" to avoid
|
|
|
|
fetching the uncomplete frames at the end, which raises an error.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
# open the file in a pipe, provoke an error, read output
|
|
|
|
|
2017-05-24 20:28:24 +00:00
|
|
|
try:
|
|
|
|
|
|
|
|
filename.encode( 'ascii' ) # throwing unicode at the console is a mess best left for Python 3
|
|
|
|
|
|
|
|
except UnicodeEncodeError:
|
|
|
|
|
|
|
|
( os_file_handle, temp_path ) = HydrusPaths.GetTempPath()
|
|
|
|
|
|
|
|
with open( filename, 'rb' ) as source:
|
|
|
|
|
|
|
|
with open( temp_path, 'wb' ) as dest:
|
|
|
|
|
|
|
|
HydrusPaths.CopyFileLikeToFileLike( source, dest )
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
return Hydrusffmpeg_parse_infos( temp_path )
|
|
|
|
|
|
|
|
finally:
|
|
|
|
|
|
|
|
HydrusPaths.CleanUpTempPath( os_file_handle, temp_path )
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-09-23 21:21:02 +00:00
|
|
|
cmd = [ FFMPEG_PATH, "-i", filename ]
|
2014-06-18 21:53:48 +00:00
|
|
|
|
|
|
|
is_GIF = filename.endswith('.gif')
|
|
|
|
|
2017-05-31 21:50:53 +00:00
|
|
|
doing_manual_frame_count = is_GIF or count_frames_manually
|
|
|
|
|
|
|
|
if doing_manual_frame_count:
|
2014-06-18 21:53:48 +00:00
|
|
|
if HC.PLATFORM_WINDOWS: cmd += ["-f", "null", "NUL"]
|
|
|
|
else: cmd += ["-f", "null", "/dev/null"]
|
|
|
|
|
2016-12-07 22:12:52 +00:00
|
|
|
try:
|
|
|
|
|
|
|
|
proc = subprocess.Popen( cmd, bufsize=10**5, stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo = HydrusData.GetSubprocessStartupInfo() )
|
|
|
|
|
|
|
|
except:
|
|
|
|
|
|
|
|
if not os.path.exists( FFMPEG_PATH ):
|
|
|
|
|
|
|
|
raise Exception( 'FFMPEG was not found!' )
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
2014-06-18 21:53:48 +00:00
|
|
|
|
|
|
|
infos = proc.stderr.read().decode('utf8')
|
2016-08-17 20:07:22 +00:00
|
|
|
|
2014-06-18 21:53:48 +00:00
|
|
|
proc.terminate()
|
|
|
|
|
|
|
|
del proc
|
|
|
|
|
|
|
|
if print_infos:
|
|
|
|
# print the whole info text returned by FFMPEG
|
2015-11-18 22:44:07 +00:00
|
|
|
HydrusData.Print( infos )
|
2014-06-18 21:53:48 +00:00
|
|
|
|
|
|
|
lines = infos.splitlines()
|
|
|
|
if "No such file or directory" in lines[-1]:
|
|
|
|
raise IOError("%s not found ! Wrong path ?"%filename)
|
2016-12-07 22:12:52 +00:00
|
|
|
if 'Invalid data' in lines[-1]:
|
|
|
|
raise HydrusExceptions.MimeException( 'FFMPEG could not parse.' )
|
2014-06-18 21:53:48 +00:00
|
|
|
|
|
|
|
result = dict()
|
|
|
|
|
|
|
|
# get duration (in seconds)
|
2014-06-25 20:37:06 +00:00
|
|
|
# Duration: 00:00:02.46, start: 0.033000, bitrate: 1069 kb/s
|
2014-06-18 21:53:48 +00:00
|
|
|
try:
|
|
|
|
keyword = ('frame=' if is_GIF else 'Duration: ')
|
|
|
|
line = [l for l in lines if keyword in l][0]
|
2014-06-25 20:37:06 +00:00
|
|
|
|
|
|
|
if 'start:' in line:
|
|
|
|
|
2016-08-24 18:36:56 +00:00
|
|
|
m = re.search( '(start\\: )' + '-?[0-9]+\\.[0-9]*', line )
|
2014-06-25 20:37:06 +00:00
|
|
|
|
|
|
|
start_offset = float( line[ m.start() + 7 : m.end() ] )
|
|
|
|
|
2016-08-24 18:36:56 +00:00
|
|
|
if abs( start_offset ) > 1.0: # once had a file with start offset of 957499 seconds jej
|
|
|
|
|
|
|
|
start_offset = 0
|
|
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
start_offset = 0
|
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
|
2014-06-18 21:53:48 +00:00
|
|
|
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(':'))
|
|
|
|
|
|
|
|
if len(hms) == 1:
|
|
|
|
result['duration'] = hms[0]
|
|
|
|
elif len(hms) == 2:
|
|
|
|
result['duration'] = 60*hms[0]+hms[1]
|
|
|
|
elif len(hms) ==3:
|
|
|
|
result['duration'] = 3600*hms[0]+60*hms[1]+hms[2]
|
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
result[ 'duration' ] -= start_offset
|
|
|
|
|
2014-06-18 21:53:48 +00:00
|
|
|
except:
|
|
|
|
raise IOError("Error reading duration in file %s,"%(filename)+
|
|
|
|
"Text parsed: %s"%infos)
|
2016-08-17 20:07:22 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
( input_line, ) = [ l for l in lines if l.startswith( 'Input #0' ) ]
|
|
|
|
|
|
|
|
# Input #0, matroska, webm, from 'm.mkv':
|
|
|
|
|
|
|
|
text = input_line[10:]
|
|
|
|
|
|
|
|
mime_text = text.split( ', from' )[0]
|
|
|
|
|
|
|
|
result[ 'mime_text' ] = mime_text
|
|
|
|
|
|
|
|
except:
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2017-05-31 21:50:53 +00:00
|
|
|
if count_frames_manually:
|
|
|
|
|
|
|
|
frame_lines = [ l for l in lines if l.startswith( 'frame= ' ) ]
|
|
|
|
|
|
|
|
if len( frame_lines ) > 0:
|
|
|
|
|
|
|
|
l = frame_lines[0]
|
|
|
|
|
|
|
|
while ' ' in l:
|
|
|
|
|
|
|
|
l = l.replace( ' ', ' ' )
|
|
|
|
|
|
|
|
|
|
|
|
num_frames = int( l.split( ' ' )[1] )
|
|
|
|
|
|
|
|
result[ 'video_nframes' ] = num_frames
|
|
|
|
|
|
|
|
|
|
|
|
|
2014-06-18 21:53:48 +00:00
|
|
|
# get the output line that speaks about video
|
2017-02-01 21:11:17 +00:00
|
|
|
lines_video = [ l for l in lines if ' Video: ' in l and not ( ' Video: png' in l or ' Video: jpg' in l ) ] # mp3 says it has a 'png' video stream
|
2014-06-18 21:53:48 +00:00
|
|
|
|
|
|
|
result['video_found'] = ( lines_video != [] )
|
|
|
|
|
|
|
|
if result['video_found']:
|
|
|
|
|
|
|
|
line = lines_video[0]
|
|
|
|
|
|
|
|
# get the size, of the form 460x320 (w x h)
|
|
|
|
match = re.search(" [0-9]*x[0-9]*(,| )", line)
|
|
|
|
s = list(map(int, line[match.start():match.end()-1].split('x')))
|
|
|
|
result['video_size'] = s
|
2014-06-25 20:37:06 +00:00
|
|
|
|
2017-05-31 21:50:53 +00:00
|
|
|
have_to_fetch_manually = False
|
2014-06-25 20:37:06 +00:00
|
|
|
|
2017-05-31 21:50:53 +00:00
|
|
|
if 'video_nframes' in result:
|
|
|
|
|
|
|
|
result[ 'video_fps' ] = result[ 'video_nframes' ] / result[ 'duration' ]
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
# get the frame rate
|
|
|
|
try:
|
|
|
|
|
|
|
|
match = re.search("( [0-9]*.| )[0-9]* tbr", line)
|
|
|
|
|
|
|
|
fps = line[match.start():match.end()].split(' ')[1]
|
|
|
|
|
|
|
|
if fps.endswith( 'k' ):
|
|
|
|
|
|
|
|
raise Exception()
|
|
|
|
|
|
|
|
|
|
|
|
result['video_fps'] = float( fps )
|
|
|
|
|
|
|
|
except:
|
|
|
|
|
|
|
|
match = re.search("( [0-9]*.| )[0-9]* fps", line)
|
|
|
|
|
|
|
|
fps = line[match.start():match.end()].split(' ')[1]
|
|
|
|
|
|
|
|
if fps.endswith( 'k' ):
|
|
|
|
|
|
|
|
if not doing_manual_frame_count:
|
|
|
|
|
|
|
|
return Hydrusffmpeg_parse_infos( filename, count_frames_manually = True )
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
raise Exception( 'Could not determine framerate!' )
|
|
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
result['video_fps'] = float( fps )
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
num_frames = result['duration'] * result['video_fps']
|
|
|
|
|
|
|
|
if num_frames != int( num_frames ): num_frames += 1 # rounding up
|
|
|
|
|
|
|
|
result['video_nframes'] = int( num_frames )
|
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
|
2014-06-18 21:53:48 +00:00
|
|
|
result['video_duration'] = result['duration']
|
|
|
|
# We could have also recomputed the duration from the number
|
|
|
|
# of frames, as follows:
|
|
|
|
# >>> result['video_duration'] = result['video_nframes'] / result['video_fps']
|
|
|
|
|
|
|
|
|
|
|
|
lines_audio = [l for l in lines if ' Audio: ' in l]
|
|
|
|
|
|
|
|
result['audio_found'] = lines_audio != []
|
|
|
|
|
|
|
|
if result['audio_found']:
|
|
|
|
line = lines_audio[0]
|
|
|
|
try:
|
|
|
|
match = re.search(" [0-9]* Hz", line)
|
|
|
|
result['audio_fps'] = int(line[match.start()+1:match.end()])
|
|
|
|
except:
|
|
|
|
result['audio_fps'] = 'unknown'
|
|
|
|
|
|
|
|
return result
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
# This was built from moviepy's FFMPEG_VideoReader
|
|
|
|
class VideoRendererFFMPEG( object ):
|
2015-11-18 22:44:07 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
def __init__( self, path, mime, duration, num_frames, target_resolution, pix_fmt = "rgb24" ):
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
self._path = path
|
|
|
|
self._mime = mime
|
|
|
|
self._duration = float( duration ) / 1000.0
|
|
|
|
self._num_frames = num_frames
|
2014-05-28 21:03:24 +00:00
|
|
|
self._target_resolution = target_resolution
|
|
|
|
|
2015-03-04 22:44:32 +00:00
|
|
|
self.lastread = None
|
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
self.fps = float( self._num_frames ) / self._duration
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2016-07-06 21:13:15 +00:00
|
|
|
if self.fps == 0:
|
|
|
|
|
|
|
|
self.fps = 24
|
|
|
|
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
self.pix_fmt = pix_fmt
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
if pix_fmt == 'rgba': self.depth = 4
|
|
|
|
else: self.depth = 3
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
( x, y ) = self._target_resolution
|
2015-11-25 22:00:57 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
bufsize = self.depth * x * y
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
self.process = None
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
self.bufsize = bufsize
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
self.initialize()
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
|
|
|
|
def __del__( self ):
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
self.close()
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
|
|
|
|
def close(self):
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
if self.process is not None:
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
self.process.terminate()
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
self.process.stdout.close()
|
|
|
|
self.process.stderr.close()
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
self.process = None
|
2014-05-28 21:03:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
def initialize( self, start_index = 0 ):
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
self.close()
|
|
|
|
|
|
|
|
if self._mime == HC.IMAGE_GIF:
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
ss = 0
|
|
|
|
self.pos = 0
|
|
|
|
skip_frames = start_index
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
else:
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
ss = float( start_index ) / self.fps
|
|
|
|
self.pos = start_index
|
|
|
|
skip_frames = 0
|
2014-05-28 21:03:24 +00:00
|
|
|
|
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
( w, h ) = self._target_resolution
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2015-06-17 20:01:41 +00:00
|
|
|
cmd = [ FFMPEG_PATH,
|
2014-06-25 20:37:06 +00:00
|
|
|
'-ss', "%.03f" % ss,
|
2015-06-17 20:01:41 +00:00
|
|
|
'-i', self._path,
|
2014-06-25 20:37:06 +00:00
|
|
|
'-loglevel', 'quiet',
|
|
|
|
'-f', 'image2pipe',
|
|
|
|
"-pix_fmt", self.pix_fmt,
|
|
|
|
"-s", str( w ) + 'x' + str( h ),
|
2017-05-31 21:50:53 +00:00
|
|
|
'-vsync', '0',
|
2015-06-17 20:01:41 +00:00
|
|
|
'-vcodec', 'rawvideo', '-' ]
|
|
|
|
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2016-12-07 22:12:52 +00:00
|
|
|
try:
|
|
|
|
|
|
|
|
self.process = subprocess.Popen( cmd, bufsize = self.bufsize, stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo = HydrusData.GetSubprocessStartupInfo() )
|
|
|
|
|
|
|
|
except:
|
|
|
|
|
|
|
|
if not os.path.exists( FFMPEG_PATH ):
|
|
|
|
|
|
|
|
raise Exception( 'FFMPEG was not found!' )
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2016-07-27 21:53:34 +00:00
|
|
|
if skip_frames > 0:
|
|
|
|
|
|
|
|
self.skip_frames( skip_frames )
|
|
|
|
|
2014-05-28 21:03:24 +00:00
|
|
|
|
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
def skip_frames( self, n ):
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
( w, h ) = self._target_resolution
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
for i in range( n ):
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2015-09-09 22:04:39 +00:00
|
|
|
if self.process is not None:
|
|
|
|
|
|
|
|
self.process.stdout.read( self.depth * w * h )
|
|
|
|
|
|
|
|
self.process.stdout.flush()
|
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
|
|
|
|
self.pos += 1
|
2014-05-28 21:03:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
def read_frame( self ):
|
|
|
|
|
2016-07-20 19:57:10 +00:00
|
|
|
if self.pos == self._num_frames:
|
|
|
|
|
|
|
|
self.initialize()
|
|
|
|
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2016-07-06 21:13:15 +00:00
|
|
|
if self.process is None:
|
|
|
|
|
|
|
|
result = self.lastread
|
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
else:
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
( w, h ) = self._target_resolution
|
|
|
|
|
|
|
|
nbytes = self.depth * w * h
|
|
|
|
|
|
|
|
s = self.process.stdout.read(nbytes)
|
|
|
|
|
|
|
|
if len(s) != nbytes:
|
|
|
|
|
|
|
|
result = self.lastread
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
self.close()
|
2014-05-28 21:03:24 +00:00
|
|
|
|
|
|
|
else:
|
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
result = numpy.fromstring( s, dtype = 'uint8' ).reshape( ( h, w, len( s ) // ( w * h ) ) )
|
|
|
|
|
|
|
|
self.lastread = result
|
2014-05-28 21:03:24 +00:00
|
|
|
|
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
|
|
|
|
self.pos += 1
|
|
|
|
|
|
|
|
return result
|
2014-05-28 21:03:24 +00:00
|
|
|
|
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
def set_position( self, pos ):
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
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 )
|
2014-05-28 21:03:24 +00:00
|
|
|
|
2016-12-07 22:12:52 +00:00
|
|
|
|