How to fetch both live video frame and timestamp from ffmpeg to python on Windows
Redirecting stderr works in python.
So instead of this pipe = sp.Popen(command, stdout = sp.PIPE, stderr = sp.PIPE)
do this pipe = sp.Popen(command, stdout = sp.PIPE, stderr = sp.STDOUT)
We could avoid redirection by adding an asynchronous call to read both the standard streams (stdout and stderr) of ffmpeg. This would avoid any mixing of the video frame and timestamp and thus the error prone seperation.So modifying the original code to use threading
module would look like this:
# Python script to read video frames and timestamps using ffmpegimport subprocess as spimport threadingimport matplotlib.pyplot as pltimport numpyimport cv2ffmpeg_command = [ 'ffmpeg', '-nostats', # do not print extra statistics #'-debug_ts', # -debug_ts could provide timestamps avoiding showinfo filter (-vcodec copy). Need to check by providing expected fps TODO '-r', '30', # output 30 frames per second '-i', 'e:\sample.wmv', '-an','-sn', #-an, -sn disables audio and sub-title processing respectively '-pix_fmt', 'rgb24', '-vcodec', 'rawvideo', #'-vcodec', 'copy', # very fast!, direct copy - Note: No Filters, No Decode/Encode, no quality loss #'-vframes', '20', # process n video frames only. For Debugging '-vf', 'showinfo', # showinfo videofilter provides frame timestamps as pts_time '-f', 'image2pipe', 'pipe:1' ] # outputs to stdout pipe. can also use '-' which is redirected to pipe# seperate method to read images on stdout asynchronouslydef AppendProcStdout(proc, nbytes, AppendList): while proc.poll() is None: # continue while the process is alive AppendList.append(proc.stdout.read(nbytes)) # read image bytes at a time# seperate method to read image info. on stderr asynchronouslydef AppendProcStderr(proc, AppendList): while proc.poll() is None: # continue while the process is alive try: AppendList.append(proc.stderr.next()) # read stderr until empty except StopIteration: continue # ignore stderr empty exception and continueif __name__ == '__main__': # run ffmpeg command pipe = sp.Popen(ffmpeg_command, stdout=sp.PIPE, stderr=sp.PIPE) # 2 threads to talk with ffmpeg stdout and stderr pipes framesList = []; frameDetailsList = [] appendFramesThread = threading.Thread(group=None, target=AppendProcStdout, name='FramesThread', args=(pipe, 1280*720*3, framesList), kwargs=None, verbose=None) # assuming rgb video frame with size 1280*720 appendInfoThread = threading.Thread(group=None, target=AppendProcStderr, name='InfoThread', args=(pipe, frameDetailsList), kwargs=None, verbose=None) # start threads to capture ffmpeg frames and info. appendFramesThread.start() appendInfoThread.start() # wait for few seconds and close - simulating cancel import time; time.sleep(2) pipe.terminate() # check if threads finished and close appendFramesThread.join() appendInfoThread.join() # save an image per 30 frames to disk savedList = [] for cnt,raw_image in enumerate(framesList): if (cnt%30 != 0): continue image1 = numpy.fromstring(raw_image, dtype='uint8') image2 = image1.reshape((720,1280,3)) # assuming rgb image with size 1280 X 720 # write video frame to file just to verify videoFrameName = 'video_frame{0}.png'.format(cnt) cv2.imwrite(videoFrameName,image2) savedList.append('{} {}'.format(videoFrameName, image2.shape)) print '### Results ###' print 'Images captured: ({}) \nImages saved to disk:{}\n'.format(len(framesList), savedList) # framesList contains all the video frames got from the ffmpeg print 'Images info captured: \n', ''.join(frameDetailsList) # this contains all the timestamp details got from the ffmpeg showinfo videofilter and some initial noise text which can be easily removed while parsing
You can try to specify the buffer size so you're sure the whole frame fits in it :
bufsize = w*h*3 + 100 pipe = sp.Popen(command, bufsize=bufsize, stdout = sp.PIPE, stderr = sp.PIPE)
with this set up, you can normally read on pipe.stdout for your frames and pipe.stderr for its info