1

I'm trying to get familiar with python subprocess and here is my little code:

import subprocess
import os
import re
import subprocess as sp
import logging

the_file = "/home/vagrant/test/out.pkg"
out_file = "/home/vagrant/test/result.mp4"

ffmpeg = sp.Popen(['/usr/bin/ffmpeg', '-i', the_file, out_file], stdout = sp.PIPE, stderr = sp.STDOUT)
process_output =  ffmpeg.communicate()
print "communicate:", process_output

The result :

communicate: ("ffmpeg version N-75410-g58fe57d Copyright (c) 2000-2015 the FFmpeg developers\n  built with gcc 4.4.7 (GCC) 20120313 (Red Hat 4.4.7-16)\n  configuration: --prefix=/home/vagrant/ffmpeg_build --extra-cflags=-I/home/vagrant/ffmpeg_build/include --extra-ldflags=-L/home/vagrant/ffmpeg_build/lib --bindir=/usr/local/bin --enable-gpl --enable-nonfree --enable-libfdk_aac --enable-libmp3lame --enable-libvorbis --enable-libopus --enable-libvpx --enable-libx264 --enable-libfreetype\n  libavutil      55.  2.100 / 55.  2.100\n  libavcodec     57.  3.100 / 57.  3.100\n  libavformat    57.  2.100 / 57.  2.100\n  libavdevice    57.  0.100 / 57.  0.100\n  libavfilter     6.  5.100 /  6.  5.100\n  libswscale      4.  0.100 /  4.  0.100\n  libswresample   2.  0.100 /  2.  0.100\n  libpostproc    54.  0.100 / 54.  0.100\nInput #0, tty, from '/home/vagrant/test/out.txt':\n  Duration: 00:00:00.04, bitrate: 1 kb/s\n    Stream #0:0: Video: ansi, pal8, 640x400, 25 fps, 25 tbr, 25 tbn, 25 tbc\nNo pixel format specified, yuv444p for H.264 encoding chosen.\nUse -pix_fmt yuv420p for compatibility with outdated media players.\n[libx264 @ 0x379f300] using cpu capabilities: MMX2 SSE2Fast SSSE3 Cache64\n[libx264 @ 0x379f300] profile High 4:4:4 Predictive, level 3.0, 4:4:4 8-bit\n[libx264 @ 0x379f300] 264 - core 148 r2597 e86f3a1 - H.264/MPEG-4 AVC codec - Copyleft 2003-2015 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=4 threads=1 lookahead_threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=250 keyint_min=25 scenecut=40 intra_refresh=0 rc_lookahead=40 rc=crf mbtree=1 crf=23.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00\nOutput #0, mp4, to '/home/vagrant/test/result.mp4':\n  Metadata:\n    encoder         : Lavf57.2.100\n    Stream #0:0: Video: h264 (libx264) ([33][0][0][0] / 0x0021), yuv444p, 640x400, q=-1--1, 25 fps, 12800 tbn, 25 tbc\n    Metadata:\n      encoder         : Lavc57.3.100 libx264\nStream mapping:\n  Stream #0:0 -> #0:0 (ansi (native) -> h264 (libx264))\nPress [q] to stop, [?] for help\nframe=    1 fps=0.0 q=28.0 Lsize=       2kB time=00:00:00.04 bitrate= 362.4kbits/s    \nvideo:1kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 78.698227%\n[libx264 @ 0x379f300] frame I:1     Avg QP:13.08  size:   326\n[libx264 @ 0x379f300] mb I  I16..4:  0.4% 99.1%  0.5%\n[libx264 @ 0x379f300] 8x8 transform intra:99.1%\n[libx264 @ 0x379f300] coded y,u,v intra: 0.4% 0.0% 0.0%\n[libx264 @ 0x379f300] i16 v,h,dc,p:  0% 75% 25%  0%\n[libx264 @ 0x379f300] i8 v,h,dc,ddl,ddr,vr,hd,vl,hu:  0% 92%  8%  0%  0%  0%  0%  0%  0%\n[libx264 @ 0x379f300] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 24% 35% 35%  2%  1%  0%  2%  0%  0%\n[libx264 @ 0x379f300] kb/s:65.20\n", None)

As it expected, sends me back a tuple (stdout,stderr). But the problem is, ffmpeg converts any file(as you can see above, even pkg file) without giving me back an error or exit status with 1. stderr = None

Can anyone explain what's wrong on this one? Thanks so much.

KevinOelen
  • 749
  • 2
  • 10
  • 24
  • 1
    did you try run ffmpeg in terminal? – Andrew Sep 22 '15 at 10:42
  • It's not ffmpeg , i think it's your code which seems to have error, as you have mentioned result.mpr as your output file , so it converts the same. Read more about ffmpeg – coder3521 Sep 22 '15 at 10:44

2 Answers2

1

Check ffmpeg.returncode, to see the exit status.

Set stderr=sp.PIPE to get the corresponding non-None value from communicate().

jfs
  • 399,953
  • 195
  • 994
  • 1,670
1

In this case, you might be better off using subprocess.check_output().

from subprocess import check_output, STDOUT, CalledProcessError

args = ['/usr/bin/ffmpeg', '-i', the_file, out_file]
try:
    txt = check_output(args, stderr=STDOUT)
except CalledProcessError as e:
    print "conversion failed", e
else:
    print the_file, 'converted to', out_file

The above code captures both standard output and standard error stream of ffmpeg in txt. And it handles a non-zero return value by catching the exception.

Using Popen is good if you want to run multiple programs. The program given below runs multiple instances of ffmpeg in parallel to convert lots of videos (e.g. from a camera or phone) to MP4 format.

#!/usr/bin/env python3
# vim:fileencoding=utf-8:ft=python
#
# Author: R.F. Smith <rsmith@xs4all.nl>
# Last modified: 2015-09-22 21:41:17 +0200
#
# To the extent possible under law, Roland Smith has waived all copyright and
# related or neighboring rights to vid2mp4.py. This work is published from the
# Netherlands. See http://creativecommons.org/publicdomain/zero/1.0/

"""Convert all video files given on the command line to H.264/AAC streams in
an MP4 container."""

__version__ = '1.1.1'

from multiprocessing import cpu_count
from time import sleep
import logging
import os
import subprocess
import sys


def main(argv):
    """
    Entry point for vid2mp4.

    Arguments:
        argv: All command line arguments.
    """
    if len(argv) == 1:
        binary = os.path.basename(argv[0])
        print("{} version {}".format(binary, __version__), file=sys.stderr)
        print("Usage: {} [file ...]".format(binary), file=sys.stderr)
        sys.exit(0)
    logging.basicConfig(level="INFO", format='%(levelname)s: %(message)s')
    checkfor(['ffmpeg', '-version'])
    vids = argv[1:]
    procs = []
    maxprocs = cpu_count()
    for ifile in vids:
        while len(procs) == maxprocs:
            manageprocs(procs)
        procs.append(startencoder(ifile))
    while len(procs) > 0:
        manageprocs(procs)


def checkfor(args, rv=0):
    """
    Make sure that a program necessary for using this script is available.

    Arguments:
        args: String or list of strings of commands. A single string may
            not contain spaces.
        rv: Expected return value from evoking the command.
    """
    if isinstance(args, str):
        if ' ' in args:
            raise ValueError('no spaces in single command allowed')
        args = [args]
    try:
        rc = subprocess.call(args, stdout=subprocess.DEVNULL,
                             stderr=subprocess.DEVNULL)
        if rc != rv:
            raise OSError
    except OSError as oops:
        outs = "required program '{}' not found: {}."
        logging.error(outs.format(args[0], oops.strerror))
        sys.exit(1)


def startencoder(fname):
    """
    Use ffmpeg to convert a video file to H.264/AAC streams in an MP4
    container.

    Arguments:
        fname: Name of the file to convert.

    Returns:
        A 3-tuple of a Process, input path and output path.
    """
    basename, ext = os.path.splitext(fname)
    known = ['.mp4', '.avi', '.wmv', '.flv', '.mpg', '.mpeg', '.mov', '.ogv']
    if ext.lower() not in known:
        ls = "File {} has unknown extension, ignoring it.".format(fname)
        logging.warning(ls)
        return (None, fname, None)
    ofn = basename + '.mp4'
    args = ['ffmpeg', '-i', fname, '-c:v', 'libx264', '-crf', '29', '-flags',
            '+aic+mv4', '-c:a', 'libfaac', '-sn', ofn]
    with open(os.devnull, 'w') as bitbucket:
        try:
            p = subprocess.Popen(args, stdout=bitbucket, stderr=bitbucket)
            logging.info("Conversion of {} to {} started.".format(fname, ofn))
        except:
            logging.error("Starting conversion of {} failed.".format(fname))
    return (p, fname, ofn)


def manageprocs(proclist):
    """
    Check a list of subprocesses tuples for processes that have ended and
    remove them from the list.

    Arguments:
        proclist: a list of (process, input filename, output filename)
    tuples.
    """
    nr = '# of conversions running: {}\r'.format(len(proclist))
    logging.info(nr, end='')
    sys.stdout.flush()
    for p in proclist:
        pr, ifn, ofn = p
        if pr is None:
            proclist.remove(p)
        elif pr.poll() is not None:
            logging.info('Conversion of {} to {} finished.'.format(ifn, ofn))
            proclist.remove(p)
    sleep(0.5)


if __name__ == '__main__':
    main(sys.argv)
Roland Smith
  • 42,427
  • 3
  • 64
  • 94
  • you could use `e.output`, to get ffmpeg's error message in the exception handler. The parallel tasks handling looks complicated. You could [simplify it using a thread pool: `for result in pool.imap_unordered(convert_video, vids):...`](http://stackoverflow.com/a/14533902/4279). Use `subprocess.DEVNULL` instead of `bitbucket`. – jfs Sep 22 '15 at 22:52
  • Using `subprocess.DEVNULL` is an improvement that I'll definitely use! At first I used a (process) `Pool` for launching subprocesses, but that seemed kind of wasteful. I'll give thread pools a try. – Roland Smith Sep 22 '15 at 23:33
  • you might also want to set `stdin=DEVNULL` (it may change the output format (if stdin is not a tty)). – jfs Sep 22 '15 at 23:42