Is there a way to track a progress after running an ffmpeg?
For example, below is what i would like to do:
import ffmpeg
ffmpeg.input('test.mp4').output('frame_%06d.jpg').run()
This command writes each frame of a video as an image to disk.
At the very least it would be great if we could see the output generated by running ffmpeg on commandline ffmpeg -i test.mp4 %06d.png -hide_banner
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'workflow_video_01.mp4':
Metadata:
major_brand : mp42
minor_version : 512
compatible_brands: isomiso2avc1mp41
creation_time : 2036-02-06 06:28:16
encoder : HandBrake 0.10.2 2015060900
Duration: 00:50:57.00, start: 0.000000, bitrate: 7040 kb/s
Stream #0:0(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p(tv, bt709), 1920x1080 [SAR 1:1 DAR 16:9], 7038 kb/s, 25 fps, 25 tbr, 90k tbn, 50 tbc (default)
Metadata:
creation_time : 2036-02-06 06:28:16
handler_name : VideoHandler
Output #0, image2, to 'dump/%06d.png':
Metadata:
major_brand : mp42
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf56.40.101
Stream #0:0(und): Video: png, rgb24, 1920x1080 [SAR 1:1 DAR 16:9], q=2-31, 200 kb/s, 25 fps, 25 tbn, 25 tbc (default)
Metadata:
creation_time : 2036-02-06 06:28:16
handler_name : VideoHandler
encoder : Lavc56.60.100 png
Stream mapping:
Stream #0:0 -> #0:0 (h264 (native) -> png (native))
Press [q] to stop, [?] for help
frame= 677 fps= 59 q=-0.0 Lsize=N/A time=00:00:27.08 bitrate=N/A
Any ideas on how to do this?
I think these are the only options:
ffmpeg: https://stackoverflow.com/a/31353647From man ffmpeg:
-progress url (global)
Send program-friendly progress information to url.
Progress information is written approximately every second and at the end of the
encoding process. It is made of "key=value" lines. key consists of only alphanumeric
characters. The last key of a sequence of progress information is always "progress".
To implement either of these, the programs that use this library need to be structured for some degree of concurrency.
The progress checking code either needs to be run asynchronously or in another thread. In the first case for obvious reasons: you constantly need to poll the socket for incoming connections; in the second case to avoid filling up the stderr buffer in case you wait for too long between one poll and another, unless you're okay with ffmpeg randomly getting stuck.
This can be done with threading.Thread, twisted, asyncio or anything else, probably except multiprocessing (having a separate process do the polling thing would totally be an overkill and would only make things more complicated).
Just a note for those looking for a quick solution: If you run ffmpeg from a jupyter notebook, you can see the stdout and stderr from the ffmpeg command in the terminal from which jupyter was started. I didn't notice this at first, since I tend to keep that terminal minimized.
Here's a semi-hacked-together example using gevent and the -progress param @Depau mentioned:
import gevent.monkey; gevent.monkey.patch_all()
import contextlib
import ffmpeg
import gevent
import os
import shutil
import socket
import subprocess
import sys
import tempfile
@contextlib.contextmanager
def _tmpdir_scope():
tmpdir = tempfile.mkdtemp()
try:
yield tmpdir
finally:
shutil.rmtree(tmpdir)
def _watch_progress(filename, sock, handler):
connection, client_address = sock.accept()
data = ''
with contextlib.closing(connection):
while True:
more_data = connection.recv(16)
if not more_data:
break
data += more_data
lines = data.split('\n')
for line in lines[:-1]:
parts = line.split('=')
key = parts[0] if len(parts) > 0 else None
value = parts[1] if len(parts) > 1 else None
handler(key, value)
data = lines[-1]
@contextlib.contextmanager
def watch_progress(handler):
with _tmpdir_scope() as tmpdir:
filename = os.path.join(tmpdir, 'sock')
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
with contextlib.closing(sock):
sock.bind(filename)
sock.listen(1)
child = gevent.spawn(_watch_progress, filename, sock, handler)
try:
yield filename
except:
gevent.kill(child)
raise
duration = float(ffmpeg.probe('in.mp4')['format']['duration'])
prev_text = None
def handler(key, value):
global prev_text
if key == 'out_time_ms':
text = '{:.02f}%'.format(float(value) / 10000. / duration)
if text != prev_text:
print(text)
prev_text = text
with watch_progress(handler) as filename:
p = subprocess.Popen(
(ffmpeg
.input('in.mp4')
.output('out.mp4')
.global_args('-progress', 'unix://{}'.format(filename))
.overwrite_output()
.compile()
),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
out = p.communicate()
if p.returncode != 0:
sys.stderr.write(out[1])
sys.exit(1)
subprocess.Popen to silence stdout/stderr. There's probably a better way to silence ffmpeg, in which case .run() can be used instead of calling subprocess manually.I'm not sure if/when this will be incorporated into ffmpeg-python, but something to play with in the mean time.
Perhaps the above example could be condensed into something like the following at some point:
def progress_handler(progress_info):
print('{:.2f}'.format(progress_info['percentage']))
(ffmpeg
.input('in.mp4')
.output('out.mp4')
.progress(progress_handler)
.overwrite_output()
.run()
)
i think this implementation is nice: https://github.com/althonos/ffpb
The output is designed for a CLI but the idea of providing a callable collection looks good.
Maybe getting in contact with the author and see if he can integrate here or allows us to use his design ?
what's the release plan about this?
I found another ffmpeg python wrapper asynchronously parsing stderr:
using regular expression r'(frame|fps|size|time|bitrate|speed)\s*\=\s*(\S+)':
https://github.com/jonghwanhyeon/python-ffmpeg/blob/ccfbba93c46dc0d2cafc1e40ecb71ebf3b5587d2/ffmpeg/utils.py#L8
Updates on this?
@kkroening You there? We still waiting on this update.
Most helpful comment
Perhaps the above example could be condensed into something like the following at some point: