Use subprocess to use complex ffmpeg commands with pyQt

1

Using pyQt I want to run ffmpeg. With simple orders, there is no problem. For example:

def b3_clicked():
    subprocess.call(['ffmpeg', '-i', fileinput, fileoutput + '.mov'])

This works well. The problem is when I want to use something complex. After a long time I managed to get a series of arguments in ffmpeg to paste black frames in front of and behind a video:

ffmpeg -f lavfi -i color=c=black:s=1920x1080:r=25:d=1 -f lavfi -i "aevalsrc=0:c=stereo:d=1" -i input.mov -filter_complex "[0:v] trim=start_frame=1:end_frame=3 [blackstart]; [0:v] trim=start_frame=1:end_frame=5 [blackend]; [1:a] atrim=duration=0.12 [audiostart]; [1:a] atrim=duration=0.2 [audioend]; [blackstart] [audiostart] [2:v] [2:a] [blackend] [audioend] concat=n=3:v=1:a=1[v][a]" -map "[v]" -map "[a]" -c:v qtrle -c:a pcm_s24le -ar 48000 -timecode 00:02:00:00 output.mov

This works from the terminal perfectly. The issue is that I do not know how to pass it to pyQt. When I try doing this:

subprocess.call(['ffmpeg', '-f', 'lavfi', '-i', 'color=c=black:s=1920x1080:r=25:d=1', '-f', 'lavfi', '-i', "aevalsrc=0:c=stereo:d=1", '-i', fileinput, '-filter_complex', "[0:v] trim=start_frame=1:end_frame=3 [blackstart]; [0:v] trim=start_frame=1:end_frame=5 [blackend]; [1:a] atrim=duration=0.12 [audiostart]; [1:a] atrim=duration=0.2 [audioend]; [blackstart] [audiostart] [2:v] [2:a] [blackend] [audioend] concat=n=3:v=1:a=1[v][a]" '-map', "[v]", '-map', "[a]", '-c:v', 'qtrle', '-c:a', 'pcm_s24le', '-ar', '48000', '-timecode', '00:02:00:00', filesaved])

I get the following error:

  

[AVFilterGraph @ 0x56281d60f6c0] Unable to parse graph description   substring: "-map" Error initializing complex filters. Invalid argument

    
asked by salvaestudio 11.05.2017 в 20:57
source

1 answer

2

You could save your command in a string and use str.format to add the variables, it is more reusable and readable than passing 100 arguments to subprocess.call :

import subprocess

fileinput = 'input.mov'
filesaved = 'output.mov'

comando = '\
ffmpeg \
-f lavfi \
-i color=c=black:s=1920x1080:r=25:d=1 \
-f lavfi \
-i "aevalsrc=0:c=stereo:d=1" \
-i {} -filter_complex \
"[0:v] trim=start_frame=1:end_frame=3 [blackstart]; \
[0:v] trim=start_frame=1:end_frame=5 [blackend]; \
[1:a] atrim=duration=0.12 [audiostart]; \
[1:a] atrim=duration=0.2 [audioend]; \
[blackstart] [audiostart] [2:v] [2:a] [blackend] [audioend] concat=n=3:v=1:a=1[v][a]" \
-map "[v]" \
-map "[a]" \
-c:v qtrle \
-c:a pcm_s24le \
-ar 48000 \
-timecode 00:02:00:00 {}'

subprocess.call(comando.format(fileinput, filesaved)) 

The string comando is divided into several lines using the escape sequence \ to make the script more readable. It's exactly the same as:

comando = 'ffmpeg -f lavfi -i color=c=black:s=1920x1080:r=25:d=1 -f lavfi -i "aevalsrc=0:c=stereo:d=1" -i {} -filter_complex "[0:v] trim=start_frame=1:end_frame=3 [blackstart]; [0:v] trim=start_frame=1:end_frame=5 [blackend]; [1:a] atrim=duration=0.12 [audiostart]; [1:a] atrim=duration=0.2 [audioend]; [blackstart] [audiostart] [2:v] [2:a] [blackend] [audioend] concat=n=3:v=1:a=1[v][a]" -map "[v]" -map "[a]" -c:v qtrle -c:a pcm_s24le -ar 48000 -timecode 00:02:00:00 {}'

str.format() works through "replacement fields" denoted by with keys {}. Anything that is not contained in braces is considered literal text, which is copied without changes in the output. You can use the number you want of replacements in the same chain. You can pass strings or variables (including integers, floats ...), operations, function calls, etc:

c = 'mi cumpleaños'
cadena = 'La fecha de {} es el {} de {}.'.format(c, 20+1, 'Enero')
print(cadena)

Exit:

  

The date of my birthday is January 21.

EDITING:

The above method works in Windows but not in POSIX since if the argument args of subprocess.call is a string, as in this case, the handling differs. You can see the documentation for more information.

You can use shlex to ensure the correct tokenization of args :

import subprocess
import shlex



fileinput = 'input.mov'
filesaved = 'output.mov'

comando = '\
ffmpeg \
-f lavfi \
-i color=c=black:s=1920x1080:r=25:d=1 \
-f lavfi \
-i "aevalsrc=0:c=stereo:d=1" \
-i {} -filter_complex \
"[0:v] trim=start_frame=1:end_frame=3 [blackstart]; \
[0:v] trim=start_frame=1:end_frame=5 [blackend]; \
[1:a] atrim=duration=0.12 [audiostart]; \
[1:a] atrim=duration=0.2 [audioend]; \
[blackstart] [audiostart] [2:v] [2:a] [blackend] [audioend] concat=n=3:v=1:a=1[v][a]" \
-map "[v]" \
-map "[a]" \
-c:v qtrle \
-c:a pcm_s24le \
-ar 48000 \
-timecode 00:02:00:00 {}'

subprocess.call(shlex.split(comando.format(fileinput, filesaved)))

The previous code has been tested on both Kubuntu 16.04 and Windows with its own video and works on both without problems.

P.D: The original error was actually very simple, although it is difficult to find it. It is an error when tokenize the arguments, you just need a comma in:

... [audioend] concat=n=3:v=1:a=1[v][a]" '-map',
                                        ^
                                        ^
                                        ^

The call should be:

subprocess.call(['ffmpeg', '-f', 'lavfi', '-i', 'color=c=black:s=1280x720:r=25:d=1', '-f', 'lavfi', '-i', 'aevalsrc=0:c=stereo:d=1', '-i', 'video.mkv', '-filter_complex', '[0:v] trim=start_frame=1:end_frame=3 [blackstart]; [0:v] trim=start_frame=1:end_frame=5 [blackend]; [1:a] atrim=duration=0.12 [audiostart]; [1:a] atrim=duration=0.2 [audioend]; [blackstart] [audiostart] [2:v] [2:a] [blackend] [audioend] concat=n=3:v=1:a=1[v][a]', '-map', '[v]', '-map', '[a]', '-c:v', 'qtrle', '-c:a', 'pcm_s24le', '-ar', '48000', '-timecode', '00:02:00:00', 'output.mov'])

To avoid these problems with calls that have many arguments use shlex as we saw before or create script sh and call them with subprocess as recommended by Toledo in your comment.

    
answered by 11.05.2017 / 22:19
source