hydrus/include/HydrusVideoHandling.py

799 lines
20 KiB
Python
Raw Normal View History

2019-01-09 22:59:03 +00:00
from . import HydrusConstants as HC
from . import HydrusData
from . import HydrusExceptions
from . import HydrusImageHandling
from . import HydrusPaths
2019-02-13 22:26:43 +00:00
from . import HydrusText
2019-01-09 22:59:03 +00:00
from . import HydrusThreading
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
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
2017-06-28 20:23:21 +00:00
def CheckFFMPEGError( lines ):
2018-06-20 20:20:22 +00:00
if len( lines ) == 0:
raise HydrusExceptions.MimeException( 'Could not parse that file--no FFMPEG output given.' )
2017-06-28 20:23:21 +00:00
if "No such file or directory" in lines[-1]:
raise IOError( "File not found!" )
if 'Invalid data' in lines[-1]:
raise HydrusExceptions.MimeException( 'FFMPEG could not parse.' )
2016-12-14 21:19:07 +00:00
def GetFFMPEGVersion():
2017-11-15 22:35:49 +00:00
2016-12-14 21:19:07 +00:00
cmd = [ FFMPEG_PATH, '-version' ]
try:
2019-01-16 22:40:53 +00:00
sbp_kwargs = HydrusData.GetSubprocessKWArgs( text = True )
2019-01-09 22:59:03 +00:00
2019-01-16 22:40:53 +00:00
proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **sbp_kwargs )
2019-01-09 22:59:03 +00:00
except FileNotFoundError:
return 'no ffmpeg found'
2016-12-14 21:19:07 +00:00
except Exception as e:
2019-01-09 22:59:03 +00:00
HydrusData.ShowException( e )
return 'unable to execute ffmpeg'
2016-12-14 21:19:07 +00:00
2019-01-09 22:59:03 +00:00
infos = proc.stdout.read()
2016-12-14 21:19:07 +00:00
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'
2017-06-28 20:23:21 +00:00
# bits of this were originally cribbed from moviepy
def GetFFMPEGInfoLines( path, count_frames_manually = False ):
# open the file in a pipe, provoke an error, read output
cmd = [ FFMPEG_PATH, "-i", path ]
if count_frames_manually:
if HC.PLATFORM_WINDOWS:
cmd += [ "-f", "null", "NUL" ]
else:
cmd += [ "-f", "null", "/dev/null" ]
2019-02-06 22:41:35 +00:00
sbp_kwargs = HydrusData.GetSubprocessKWArgs()
2017-06-28 20:23:21 +00:00
2019-01-16 22:40:53 +00:00
proc = subprocess.Popen( cmd, bufsize = 10**5, stdout = subprocess.PIPE, stderr = subprocess.PIPE, **sbp_kwargs )
2017-06-28 20:23:21 +00:00
2019-02-06 22:41:35 +00:00
data_bytes = proc.stderr.read()
2017-06-28 20:23:21 +00:00
proc.wait()
proc.communicate()
del proc
2019-02-13 22:26:43 +00:00
( text, encoding ) = HydrusText.NonFailingUnicodeDecode( data_bytes, 'utf-8' )
2019-02-06 22:41:35 +00:00
lines = text.splitlines()
2017-06-28 20:23:21 +00:00
2017-11-15 22:35:49 +00:00
try:
CheckFFMPEGError( lines )
except:
HydrusData.Print( 'FFMPEG had problem with file: ' + path )
raise
2017-06-28 20:23:21 +00:00
return lines
2017-06-07 22:05:15 +00:00
def GetFFMPEGVideoProperties( path, count_frames_manually = False ):
2014-06-25 20:37:06 +00:00
2017-07-05 21:09:28 +00:00
lines = GetFFMPEGInfoLines( path, count_frames_manually )
2014-06-25 20:37:06 +00:00
2017-06-28 20:23:21 +00:00
if not ParseFFMPEGHasVideo( lines ):
raise HydrusExceptions.MimeException( 'File did not appear to have a video stream!' )
2014-06-25 20:37:06 +00:00
2017-06-28 20:23:21 +00:00
resolution = ParseFFMPEGVideoResolution( lines )
2014-06-25 20:37:06 +00:00
2017-06-28 20:23:21 +00:00
duration = ParseFFMPEGDuration( lines )
2014-06-25 20:37:06 +00:00
2017-06-28 20:23:21 +00:00
if duration is None:
fps = ParseFFMPEGFPS( lines )
if fps is None:
2017-07-12 20:03:45 +00:00
fps = 24 # screw it, let's just put one in there
2017-06-28 20:23:21 +00:00
if not count_frames_manually:
count_frames_manually = True
lines = GetFFMPEGInfoLines( path, count_frames_manually )
num_frames = ParseFFMPEGNumFramesManually( lines )
2019-01-09 22:59:03 +00:00
duration = num_frames / fps
2017-06-28 20:23:21 +00:00
else:
2017-08-02 21:32:54 +00:00
num_frames = None
2017-06-28 20:23:21 +00:00
if not count_frames_manually:
fps = ParseFFMPEGFPS( lines )
it_was_accurate = fps is not None
if it_was_accurate:
num_frames = duration * fps
if num_frames != int( num_frames ): # we want whole numbers--anything else suggests start_offset is off or whatever
2017-07-27 00:47:13 +00:00
if os.path.getsize( path ) < 30 * 1048576: # but only defer to a super precise +/- 1-frame manual count in this case when the file is small
it_was_accurate = False
2017-06-28 20:23:21 +00:00
if not it_was_accurate:
count_frames_manually = True
lines = GetFFMPEGInfoLines( path, count_frames_manually )
if count_frames_manually:
2017-08-02 21:32:54 +00:00
try:
num_frames = ParseFFMPEGNumFramesManually( lines )
except HydrusExceptions.MimeException:
if num_frames is None:
raise
2017-06-28 20:23:21 +00:00
duration_in_ms = int( duration * 1000 )
2018-11-14 23:10:55 +00:00
num_frames = int( num_frames )
2014-06-25 20:37:06 +00:00
2017-06-28 20:23:21 +00:00
return ( resolution, duration_in_ms, num_frames )
2014-06-25 20:37:06 +00:00
2017-06-28 20:23:21 +00:00
def GetMime( path ):
2016-08-17 20:07:22 +00:00
2017-06-28 20:23:21 +00:00
lines = GetFFMPEGInfoLines( path )
2016-08-17 20:07:22 +00:00
2017-06-28 20:23:21 +00:00
try:
2016-08-17 20:07:22 +00:00
2017-06-28 20:23:21 +00:00
mime_text = ParseFFMPEGMimeText( lines )
2016-08-17 20:07:22 +00:00
2017-06-28 20:23:21 +00:00
except HydrusExceptions.MimeException:
2016-02-24 21:42:54 +00:00
2017-06-28 20:23:21 +00:00
return HC.APPLICATION_UNKNOWN
2016-02-24 21:42:54 +00:00
2017-06-28 20:23:21 +00:00
if 'matroska' in mime_text or 'webm' in mime_text:
2016-06-01 20:04:15 +00:00
2019-01-09 22:59:03 +00:00
# a webm has at least vp8/vp9 video and optionally vorbis audio
2016-06-01 20:04:15 +00:00
2019-01-09 22:59:03 +00:00
has_webm_video = False
2019-03-06 23:06:22 +00:00
has_webm_audio = False
2019-01-09 22:59:03 +00:00
if ParseFFMPEGHasVideo( lines ):
video_format = ParseFFMPEGVideoFormat( lines )
webm_video_formats = ( 'vp8', 'vp9' )
has_webm_video = True in ( webm_video_format in video_format for webm_video_format in webm_video_formats )
( has_audio, audio_format ) = ParseFFMPEGAudio( lines )
2019-03-06 23:06:22 +00:00
if has_audio:
2019-01-09 22:59:03 +00:00
2019-03-06 23:06:22 +00:00
webm_audio_formats = ( 'vorbis', 'opus' )
has_webm_audio = True in ( webm_audio_format in audio_format for webm_audio_format in webm_audio_formats )
2019-01-09 22:59:03 +00:00
2019-03-13 21:04:21 +00:00
else:
# no audio at all is not a vote against webm
has_webm_audio = True
2019-01-09 22:59:03 +00:00
if has_webm_video and has_webm_audio:
return HC.VIDEO_WEBM
else:
return HC.VIDEO_MKV
2016-02-24 21:42:54 +00:00
2017-06-28 20:23:21 +00:00
elif mime_text in ( 'mpeg', 'mpegvideo', 'mpegts' ):
2017-05-24 20:28:24 +00:00
2017-06-28 20:23:21 +00:00
return HC.VIDEO_MPEG
2017-05-24 20:28:24 +00:00
2017-06-28 20:23:21 +00:00
elif mime_text == 'flac':
2017-05-24 20:28:24 +00:00
2017-06-28 20:23:21 +00:00
return HC.AUDIO_FLAC
2017-05-24 20:28:24 +00:00
2017-06-28 20:23:21 +00:00
elif mime_text == 'mp3':
2017-05-24 20:28:24 +00:00
2017-06-28 20:23:21 +00:00
return HC.AUDIO_MP3
2017-05-24 20:28:24 +00:00
2017-07-05 21:09:28 +00:00
elif 'mp4' in mime_text:
return HC.VIDEO_MP4
2017-06-28 20:23:21 +00:00
elif mime_text == 'ogg':
2016-12-07 22:12:52 +00:00
2017-06-28 20:23:21 +00:00
return HC.AUDIO_OGG
2016-12-07 22:12:52 +00:00
2017-06-28 20:23:21 +00:00
elif mime_text == 'asf':
2016-12-07 22:12:52 +00:00
2017-06-28 20:23:21 +00:00
if ParseFFMPEGHasVideo( lines ):
2016-12-07 22:12:52 +00:00
2017-06-28 20:23:21 +00:00
return HC.VIDEO_WMV
2016-12-07 22:12:52 +00:00
else:
2017-06-28 20:23:21 +00:00
return HC.AUDIO_WMA
2016-12-07 22:12:52 +00:00
2014-06-18 21:53:48 +00:00
2017-06-28 20:23:21 +00:00
return HC.APPLICATION_UNKNOWN
2017-06-14 21:19:11 +00:00
2017-06-28 20:23:21 +00:00
def HasVideoStream( path ):
2016-08-17 20:07:22 +00:00
2017-06-28 20:23:21 +00:00
lines = GetFFMPEGInfoLines( path )
2017-06-07 22:05:15 +00:00
2017-06-28 20:23:21 +00:00
return ParseFFMPEGHasVideo( lines )
2014-06-18 21:53:48 +00:00
2017-06-28 20:23:21 +00:00
def ParseFFMPEGAudio( lines ):
2014-06-18 21:53:48 +00:00
2017-06-28 20:23:21 +00:00
# this is from the old stuff--might be helpful later when we add audio
2014-06-18 21:53:48 +00:00
2017-12-13 22:33:07 +00:00
lines_audio = [l for l in lines if 'Audio: ' in l]
2017-06-28 20:23:21 +00:00
audio_found = lines_audio != []
2019-01-09 22:59:03 +00:00
audio_format = None
2014-06-18 21:53:48 +00:00
2017-06-28 20:23:21 +00:00
if audio_found:
2019-01-09 22:59:03 +00:00
2017-06-28 20:23:21 +00:00
line = lines_audio[0]
2019-01-09 22:59:03 +00:00
2017-06-28 20:23:21 +00:00
try:
2019-01-09 22:59:03 +00:00
2017-06-28 20:23:21 +00:00
match = re.search(" [0-9]* Hz", line)
2019-01-09 22:59:03 +00:00
2017-06-28 20:23:21 +00:00
audio_fps = int(line[match.start()+1:match.end()])
2019-01-09 22:59:03 +00:00
2017-06-28 20:23:21 +00:00
except:
2019-01-09 22:59:03 +00:00
2017-06-28 20:23:21 +00:00
audio_fps = 'unknown'
2019-01-09 22:59:03 +00:00
try:
match = re.search( '(?<=Audio\:\s).+?(?=,)', line )
audio_format = match.group()
except:
audio_format = 'unknown'
return ( audio_found, audio_format )
2017-06-28 20:23:21 +00:00
def ParseFFMPEGDuration( lines ):
2014-06-18 21:53:48 +00:00
# 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:
2017-06-28 20:23:21 +00:00
line = [ l for l in lines if 'Duration:' in l ][0]
if 'Duration: N/A' in line:
return None
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)
2019-01-09 22:59:03 +00:00
hms = list(map(float, line[match.start()+1:match.end()].split(':')))
2014-06-18 21:53:48 +00:00
2017-06-28 20:23:21 +00:00
if len( hms ) == 1:
duration = hms[0]
elif len( hms ) == 2:
duration = 60 * hms[0] + hms[1]
elif len( hms ) ==3:
duration = 3600 * hms[0] + 60 * hms[1] + hms[2]
2019-01-23 22:19:16 +00:00
if duration == 0:
return None
2017-06-28 20:23:21 +00:00
duration -= start_offset
return duration
except:
raise HydrusExceptions.MimeException( 'Error reading duration!' )
def ParseFFMPEGFPS( lines ):
try:
line = ParseFFMPEGVideoLine( lines )
# get the frame rate
2018-10-17 21:00:09 +00:00
possible_results = []
2017-06-28 20:23:21 +00:00
match = re.search("( [0-9]*.| )[0-9]* tbr", line)
if match is not None:
2018-10-17 21:00:09 +00:00
tbr = line[match.start():match.end()].split(' ')[1]
2017-06-28 20:23:21 +00:00
2018-10-17 21:00:09 +00:00
tbr_fps_is_likely_garbage = match is None or tbr.endswith( 'k' ) or float( tbr ) > 60
2017-06-28 20:23:21 +00:00
2018-10-17 21:00:09 +00:00
if not tbr_fps_is_likely_garbage:
2017-06-28 20:23:21 +00:00
2018-10-17 21:00:09 +00:00
possible_results.append( float( tbr ) )
2017-06-28 20:23:21 +00:00
2018-10-17 21:00:09 +00:00
#
match = re.search("( [0-9]*.| )[0-9]* fps", line)
if match is not None:
fps = line[match.start():match.end()].split(' ')[1]
2017-06-28 20:23:21 +00:00
fps_is_likely_garbage = match is None or fps.endswith( 'k' ) or float( fps ) > 60
2018-10-17 21:00:09 +00:00
if not fps_is_likely_garbage:
2017-06-28 20:23:21 +00:00
2018-10-17 21:00:09 +00:00
possible_results.append( float( fps ) )
2017-06-28 20:23:21 +00:00
2018-10-17 21:00:09 +00:00
if len( possible_results ) == 0:
return None
else:
# in some cases, fps is 0.77 and tbr is incorrectly 20. extreme values cause bad results. let's try erroring on the side of slow
# tbh in these cases, the frame are prob going to get counted manually anyway due to no neat ints at the end, so nbd
return min( possible_results )
2014-06-25 20:37:06 +00:00
2014-06-18 21:53:48 +00:00
except:
2017-06-28 20:23:21 +00:00
raise HydrusExceptions.MimeException( 'Error estimating framerate!' )
def ParseFFMPEGHasVideo( lines ):
try:
video_line = ParseFFMPEGVideoLine( lines )
except HydrusExceptions.MimeException:
return False
return True
def ParseFFMPEGMimeText( lines ):
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]
2017-06-28 20:23:21 +00:00
return mime_text
2016-08-17 20:07:22 +00:00
except:
2017-06-28 20:23:21 +00:00
raise HydrusExceptions.MimeException( 'Error reading mime!' )
2016-08-17 20:07:22 +00:00
2017-06-28 20:23:21 +00:00
def ParseFFMPEGNumFramesManually( lines ):
try:
2017-05-31 21:50:53 +00:00
frame_lines = [ l for l in lines if l.startswith( 'frame= ' ) ]
2017-06-28 20:23:21 +00:00
l = frame_lines[-1] # there will be several of these, counting up as the file renders. we hence want the final one
while ' ' in l:
2017-05-31 21:50:53 +00:00
2017-06-28 20:23:21 +00:00
l = l.replace( ' ', ' ' )
2017-05-31 21:50:53 +00:00
2017-06-28 20:23:21 +00:00
num_frames = int( l.split( ' ' )[1] )
return num_frames
except:
raise HydrusExceptions.MimeException( 'Error counting number of frames!' )
2019-01-09 22:59:03 +00:00
def ParseFFMPEGVideoFormat( lines ):
line = ParseFFMPEGVideoLine( lines )
try:
match = re.search( '(?<=Video\:\s).+?(?=,)', line )
video_format = match.group()
except:
video_format = 'unknown'
return video_format
2017-06-28 20:23:21 +00:00
def ParseFFMPEGVideoLine( lines ):
2017-05-31 21:50:53 +00:00
2014-06-18 21:53:48 +00:00
# get the output line that speaks about video
2019-01-16 22:40:53 +00:00
# the ^\sStream is to exclude the 'title' line, when it exists, includes the string 'Video: ', ha ha
lines_video = [ l for l in lines if re.search( '^\s*Stream', l ) is not None and '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
2017-06-28 20:23:21 +00:00
if len( lines_video ) == 0:
raise HydrusExceptions.MimeException( 'Could not find video information!' )
line = lines_video[0]
return line
def ParseFFMPEGVideoResolution( lines ):
2014-06-18 21:53:48 +00:00
2017-06-28 20:23:21 +00:00
try:
line = ParseFFMPEGVideoLine( lines )
2014-06-18 21:53:48 +00:00
# get the size, of the form 460x320 (w x h)
match = re.search(" [0-9]*x[0-9]*(,| )", line)
2014-06-25 20:37:06 +00:00
2017-06-28 20:23:21 +00:00
resolution = list(map(int, line[match.start():match.end()-1].split('x')))
2014-06-25 20:37:06 +00:00
2017-11-01 20:37:39 +00:00
sar_match = re.search( " SAR [0-9]*:[0-9]* ", line )
if sar_match is not None:
# ' SAR 2:3 '
sar_string = line[ sar_match.start() : sar_match.end() ]
# '2:3'
sar_string = sar_string[5:-1]
( sar_w, sar_h ) = sar_string.split( ':' )
( sar_w, sar_h ) = ( int( sar_w ), int( sar_h ) )
( x, y ) = resolution
x *= sar_w
x //= sar_h
resolution = ( x, y )
2017-06-28 20:23:21 +00:00
return resolution
except:
2017-11-01 20:37:39 +00:00
raise HydrusExceptions.MimeException( 'Error parsing resolution!' )
2014-06-25 20:37:06 +00:00
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
2019-01-09 22:59:03 +00:00
self._duration = duration / 1000.0
2014-06-25 20:37:06 +00:00
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
2019-01-09 22:59:03 +00:00
self.fps = 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
2019-03-06 23:06:22 +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()
2017-06-28 20:23:21 +00:00
if self._mime in ( HC.IMAGE_APNG, HC.IMAGE_GIF ):
2014-05-28 21:03:24 +00:00
2018-02-07 23:40:33 +00:00
do_ss = False
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
2018-02-07 23:40:33 +00:00
if start_index == 0:
2018-02-14 21:47:18 +00:00
do_ss = False
2018-02-07 23:40:33 +00:00
else:
2018-02-14 21:47:18 +00:00
do_ss = True
2018-02-07 23:40:33 +00:00
2019-01-09 22:59:03 +00:00
ss = start_index / self.fps
2014-06-25 20:37:06 +00:00
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
2018-02-07 23:40:33 +00:00
cmd = [ FFMPEG_PATH ]
if do_ss:
cmd.extend( [ '-ss', "%.03f" % ss ] )
cmd.extend( [ '-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',
2018-02-07 23:40:33 +00:00
'-vcodec', 'rawvideo', '-' ] )
2015-06-17 20:01:41 +00:00
2014-05-28 21:03:24 +00:00
2019-01-09 22:59:03 +00:00
sbp_kwargs = HydrusData.GetSubprocessKWArgs()
self.process = subprocess.Popen( cmd, bufsize = self.bufsize, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **sbp_kwargs )
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
2018-11-14 23:10:55 +00:00
n = int( n )
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
2019-03-06 23:06:22 +00:00
s = self.process.stdout.read( nbytes )
2014-06-25 20:37:06 +00:00
if len(s) != nbytes:
2018-01-10 22:41:51 +00:00
if self.lastread is None:
2019-03-06 23:06:22 +00:00
if self.pos != 0:
# this renderer was asked to render starting from mid-vid and this was not possible due to broken key frame index whatever
# lets try and render from the vid start before we say the whole vid is broke
# I tried doing 'start from 0 and skip n frames', but this is super laggy so would need updates further up the pipe to display this to the user
# atm this error states does not communicate to the videocontainer that the current frame num has changed, so the frames are henceforth out of phase
frames_to_jump = self.pos
self.set_position( 0 )
return self.read_frame()
2018-01-10 22:41:51 +00:00
raise Exception( 'Unable to render that video! Please send it to hydrus dev so he can look at it!' )
2014-06-25 20:37:06 +00:00
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
2018-09-05 20:52:32 +00:00
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
2019-03-06 23:06:22 +00:00
def Stop( self ):
self.close()