My question is based on https://github.com/kkroening/ffmpeg-python/blob/master/examples/show_progress.py Ideally, all I want is to keep track e.g 0 - 100 for the transcoding process, emitting a streaming yield response to my gRPC client. Technically, I do not need a progress bar. How can I provide my own socket to the ffmpeg subprocess and handle write events to it?
Asked
Active
Viewed 1,748 times
1
-
`ffmpeg` cli tool has the `-progress` option that outputs progress info to a file. Maybe that's what you're looking for. – llogan May 04 '21 at 15:55
-
I'll have to yield the progress status response though – Gwen J May 04 '21 at 16:28
-
1@GwenJ You can redirect "progress" to `stdout` using `-progress pipe:1`. Take a look [here](https://stackoverflow.com/questions/54385690/how-to-redirect-progress-option-output-of-ffmpeg-to-stderr). I think using a "reader" thread for reading `stdout` is an adequate solution. – Rotem May 05 '21 at 17:18
1 Answers
5
You can redirect "progress" to stdout
using -progress pipe:1
.
See How to redirect -progress option output of ffmpeg to stderr?
The hard part is to actually get the progress in percentage.
- Start by counting the total number of frames using FFprobe as described here.
- Execute FFmpeg as sub-process, redirect
-progress
tostdout
. - Start a thread that reads text lines from
stdout
.
The thread looks forframe=xx
, get the frame, and put it in a list (list of 1 element). - Execute a "main loop" for demonstrating the progress readings.
The loop sleeps 1 second, reads the last element from the queue, and prints the progress.
The code starts by building a synthetic video file input.mp4
- used as input.
Here is a "self contained" code sample:
import subprocess as sp
import shlex
import json
from threading import Thread
import time
def progress_reader(procs, q):
while True:
if procs.poll() is not None:
break # Break if FFmpeg sun-process is closed
progress_text = procs.stdout.readline() # Read line from the pipe
# Break the loop if progress_text is None (when pipe is closed).
if progress_text is None:
break
progress_text = progress_text.decode("utf-8") # Convert bytes array to strings
# Look for "frame=xx"
if progress_text.startswith("frame="):
frame = int(progress_text.partition('=')[-1]) # Get the frame number
q[0] = frame # Store the last sample
# Build synthetic video for testing:
################################################################################
sp.run(shlex.split('ffmpeg -y -f lavfi -i testsrc=size=320x240:rate=30 -f lavfi -i sine=frequency=400 -f lavfi -i sine=frequency=1000 -filter_complex amerge -vcodec libx265 -crf 17 -pix_fmt yuv420p -acodec aac -ar 22050 -t 30 input.mp4'))
################################################################################
# Use FFprobe for counting the total number of frames
################################################################################
# Execute ffprobe (to show streams), and get the output in JSON format
# Actually counts packets instead of frames but it is much faster
# https://stackoverflow.com/questions/2017843/fetch-frame-count-with-ffmpeg/28376817#28376817
data = sp.run(shlex.split('ffprobe -v error -select_streams v:0 -count_packets -show_entries stream=nb_read_packets -of csv=p=0 -of json input.mp4'), stdout=sp.PIPE).stdout
dict = json.loads(data) # Convert data from JSON string to dictionary
tot_n_frames = float(dict['streams'][0]['nb_read_packets']) # Get the total number of frames.
################################################################################
# Execute FFmpeg as sub-process with stdout as a pipe
# Redirect progress to stdout using -progress pipe:1 arguments
process = sp.Popen(shlex.split('ffmpeg -y -loglevel error -i input.mp4 -acodec libvorbis -vcodec libvpx-vp9 -crf 20 -pix_fmt yuv420p -progress pipe:1 output.webm'), stdout=sp.PIPE)
q = [0] # We don't really need to use a Queue - use a list of of size 1
progress_reader_thread = Thread(target=progress_reader, args=(process, q)) # Initialize progress reader thread
progress_reader_thread.start() # Start the thread
while True:
if process.poll() is not None:
break # Break if FFmpeg sun-process is closed
time.sleep(1) # Sleep 1 second (do some work...)
n_frame = q[0] # Read last element from progress_reader - current encoded frame
progress_percent = (n_frame/tot_n_frames)*100 # Convert to percentage.
print(f'Progress [%]: {progress_percent:.2f}') # Print the progress
process.stdout.close() # Close stdin pipe.
progress_reader_thread.join() # Join thread
process.wait() # Wait for FFmpeg sub-process to finish
Note:
- The code sample assumes that
ffmpeg
andffprobe
are in the executable path.
Sample output:
Progress [%]: 7.33
Progress [%]: 16.00
Progress [%]: 24.67
Progress [%]: 33.33
Progress [%]: 42.13
Progress [%]: 50.40
Progress [%]: 58.80
Progress [%]: 67.20
Progress [%]: 75.60
Progress [%]: 84.00
Progress [%]: 92.40
Progress [%]: 100.00

Rotem
- 30,366
- 4
- 32
- 65
-
Some very minor comments: You can directly [get the number of frames from `ffprobe`](https://stackoverflow.com/a/28376817/). No need for `-r 25` since you used `rate=30`. "`frmae`" looks like a typo. – llogan May 05 '21 at 21:56
-
@llogan Thanks, I saw your post, but you wrote *"Not all formats (such as Matroska) will report the number of frames resulting in the output of N/A"*, and I didn't want to use slow counting, so I used a the computation. But now I see that it's not working with Matroska... There is a `DURATION` tag, but it makes the parsing complicated. – Rotem May 05 '21 at 22:29
-
1
-
Thank you all @Rotem and @llogan! If I were to implement tracking transcoding to mp3 process, will it be able to support? - `process = sp.Popen(shlex.split(f'ffmpeg -y -loglevel error -i {media_in_path} -vn -ar 44100 -ac 2 -b:a 192k -progress pipe:1 {media_out_path}'), stdout=sp.PIPE)` – Gwen J May 10 '21 at 08:20
-
@GwenJ The posted code implements video transcoding process, it's **not** going to work for audio files (without a video channel). I suppose it can work for audio transcoding, with few modifications. – Rotem May 10 '21 at 09:03
-
@Rotem Do you mind explaining what are the modifications, would appreciate it very much! – Gwen J May 10 '21 at 09:13
-
1I don't know the rules of audio packets, so I could be wrong... **1.** You can't use `ffprobe -select_streams v:0`, because it counts video packets. You can count audio packets: `ffprobe -select_streams a:0` (I am not sure it's the best way for getting audio duration). The audio duration is `tot_audio_frames*1152 / 44100` seconds (Google it...). **2.** base your progress on duration (instead of frames) - the audio transcoding progress report includes `out_time_us` that you can use for tracking percentage of total duration. – Rotem May 10 '21 at 10:33
-
@GwenJ You can also [get the duration using `ffprobe`](https://superuser.com/a/945604/). – llogan May 10 '21 at 16:38