Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

complexFilters can't handle round brackets and double quotes #781

Closed
ocramot opened this issue Dec 12, 2017 · 6 comments
Closed

complexFilters can't handle round brackets and double quotes #781

ocramot opened this issue Dec 12, 2017 · 6 comments

Comments

@ocramot
Copy link

ocramot commented Dec 12, 2017

ffmpeg tag filter_complex allows to use complex formulas with brackets ( and ); but it has to be surrounded by double quotes ". If there are no quotes, the bash command will exit with an error:
-bash: syntax error near unexpected token (.
Unfortunately, fluent-ffmpeg uses child_process's spawn to create the ffmpeg process (see https://nodejs.org/api/child_process.html#child_process_child_process; if you try to force the double quotes in the complexFilters() params (see below), spawn will add a backslash, and ffmpeg will fail with the usual error:
ffmpeg exited with code 1: Error configuring filters.

spawn has the shell option to avoid this problem (see nodejs/node#10461), but it is not currently supported by fluent-ffmpeg's options.

Version information

  • fluent-ffmpeg version: 2.1.2
  • ffmpeg version: n2.2.16-18-gc7fe604
  • OS: rhel fedora

Code to reproduce

//First attempt
var command = new ffmpeg()
  .input('path/to/video.mp4')
  .input('path/to/overlay/picture.png')
  .outputOptions(['-s 1024x576','-c:a libvo_aacenc','-b:a 192k','-c:v libx264','-preset:v medium','-profile:v main','-b:v 3500k','-pix_fmt yuv420p'])
  .complexFilter(['overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2, fps=fps=25'])
  .on('start', function(commandLine) {
    console.log('Ffmpeg starting conversion with command: ' + commandLine);
  });

//Second attempt
var command = new ffmpeg()
  .input('path/to/video.mp4')
  .input('path/to/overlay/picture.png')
  .outputOptions(['-s 1024x576','-c:a libvo_aacenc','-b:a 192k','-c:v libx264','-preset:v medium','-profile:v main','-b:v 3500k','-pix_fmt yuv420p'])
  .complexFilter(['"overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2, fps=fps=25"'])
  .on('start', function(commandLine) {
    console.log('Ffmpeg starting conversion with command: ' + commandLine);
  });

Expected results

complexFilter should have a way to map it as
-filter_complex "overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2,fps=fps=25"

Observed results

First attempt:
complexFilter is mapping it as
-filter_complex overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2,fps=fps=25
this results in the bash error
-bash: syntax error near unexpected token (.

Second attempt
complexFilter is mapping it correctly as
-filter_complex "overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2,fps=fps=25"
but spawn is adding a backslash to the double quotes, invalidating it, and resulting in the ffmpeg error
ffmpeg exited with code 1: Error configuring filters.

@njoyard
Copy link
Member

njoyard commented Jan 18, 2018

Not sure I understand the problem here. We don't pass shell: true to the spawn call, so why would you get a bash error? You didn't show how you actually run the commands, though.

You're not expected to quote parts of the command line as you would quote them for a shell here. Just apply whatever quoting ffmpeg expects.

@ocramot
Copy link
Author

ocramot commented Jan 18, 2018

We don't pass shell: true to the spawn call

That's the point, I tried to call the complexFilter (first attempt, in the code above) and I got that bash error.
If I try to force the shell:true option in the code, in the processor.js, at line 151, like this:

  // Spawn process
  options.shell = true;
  var ffmpegProc = spawn(command, args, options);

then the code of my first attempt works.

You didn't show how you actually run the commands, though

I'm not sure what you mean here, the code in the "code to reproduce" part is not enough? What can I add? The "first attempt" part is the one that works with the shell:true option.

You're not expected to quote parts of the command line as you would quote them for a shell here. Just apply whatever quoting ffmpeg expects.

ffmpeg is expecting double quotes, and I need to pass double quotes to the spawn function without the function escaping them.

I know how to fix this and I would have done a pull request myself, but I don't want to force that param like that, and I'm not really expert here and I'm not sure if and how you guys want to add this option to node-fluent-ffmpeg.

@njoyard
Copy link
Member

njoyard commented Jan 18, 2018

I think you are mistaken.

Let's do some tests.

test.js

const spawn = require('child_process').spawn;

function run(file) {
	const sp1 = spawn('cat', [file]);
	sp1.on('error', e => console.log(file + ' error: ' + e));
	sp1.stderr.on('data', d => console.log(file + ' stderr: ' + d));
	sp1.stdout.on('data', d => console.log(file + ' stdout: ' + d));
	sp1.on('close', c => console.log(file + ' exit code: ' + c));
}

run('nospaces');
run('with spaces');
run('weird (really) filename');
run('"with spaces"');
run('"weird (really) filename"');

In bash:

$ echo -n foo > nospaces
$ echo -n bar > "with spaces"
$ echo -n baz > "weird (really) filename"
$ node test.js
nospaces stdout: no spaces in filename
with spaces stdout: some spaces in filename
weird (really) filename stdout: some parentheses in filename
"with spaces" stderr: cat: '"with spaces"': No such file or directory
"weird (really) filename" stderr: cat: '"weird (really) filename"': No such file or directory
"with spaces" exit code: 1
weird (really) filename exit code: 0
with spaces exit code: 0
nospaces exit code: 0
"weird (really) filename" exit code: 1

(order of messages may vary)

So we're not running a shell here. We're directly invoking the program (here cat) and passing it the exact string specified as an argument. If we specify parentheses or quotes, the program receives it. If ffmpeg expects quotes and you pass quotes in the options, then it should be good.

What is happening to you I guess is that somehow bash is run when you spawn ffmpeg. Do you have some kind of shell wrapper in place of ffmpeg on your system ?

You didn't show how you actually run the commands, though
I'm not sure what you mean here, the code in the "code to reproduce" part is not enough? What can I add? The "first attempt" part is the one that works with the shell:true option.

The code you show does not include any call that actually runs the command (.run, .save, .exec, .screenshots, etc)

ffmpeg is expecting double quotes, and I need to pass double quotes to the spawn function without the function escaping them.

The spawn function does not escape them, it passes them raw. Take care that the on('start') event does not really produce a usable command, just a space-joined concatenation of all arguments. It can be misleading as "joining" spaces look the same as spaces inside the arguments. You cannot use the string it produces in a shell.

@njoyard
Copy link
Member

njoyard commented Mar 31, 2018

Closing, no further feedback given. Feel free to reopen or keep discussing.

@njoyard njoyard closed this as completed Mar 31, 2018
@miadabdi
Copy link

miadabdi commented Apr 23, 2021

@njoyard @ocramot
Hey, I am experiencing the same issue. sometimes the value of filter_complex require to be in double quotes. when trying to put the quotes manually, it throws back error: Error: ffmpeg exited with code 1: Error initializing complex filters..
I copy the created spawn command and run it directly in terminal and it works as expected but in fluent-ffmpeg it throws error.
this is the code:

const ffmpeg = require('fluent-ffmpeg');
const command = ffmpeg('./segment_1080p.ts');

command
.output('manifest_%v.m3u8')
.videoCodec('libx264')
.audioCodec('libfdk_aac')
.complexFilter([
    `"[0:v]fps=fps=30,split=3[v1][v2][v3];[v1]scale=width=-2:height=1080[1080p];[v2]scale=width=-2:height=720[720p];[v3]scale=width=-2:height=360[360p]"` 
])
.audioChannels(2)
.outputOptions([
    `-crf:v 23`,
    `-profile:v high `,
    `-pix_fmt:v`, `yuv420p`,
    `-rc-lookahead:v 60`,
    `-force_key_frames:v expr:'gte(t,n_forced*2.000)'`,
    `-b-pyramid:v "strict"`,
    `-preset:v "medium"`,
    `-map [1080p]`, 
    `-maxrate:v:0 2000000`, 
    `-bufsize:v:0 2*2000000`, 
    `-level:v:0 4.0`,
    `-map [720p]`,
    `-maxrate:v:1 1200000`,
    `-bufsize:v:1 2*1000000`,
    `-level:v:1 3.1`,
    `-map [360p]`,
    `-maxrate:v:2 700000`,
    `-bufsize:v:2 2*500000`,
    `-level:v:2 3.1`,
    `-map 0:a:0`,
    `-b:a:0 192000`,
    `-map 0:a:0`,
    `-b:a:1 128000`,
    `-map 0:a:0`,
    `-b:a:2 96000`,
    `-f hls`,
    `-hls_flags +independent_segments+program_date_time+single_file`,
    `-hls_time 6`,
    `-hls_playlist_type vod`,
    `-hls_segment_type mpegts`,
    `-master_pl_name 'master.m3u8'`,
    `-var_stream_map`, `"v:0,a:0,name:1080p v:1,a:1,name:720p v:2,a:2,name:360p"`,
    `-hls_segment_filename 'segment_%v_%05d.ts'`
]);

command
    .on('start', (commandLine) => {
        console.log('Spawned Ffmpeg with command: ' + commandLine);
    })
    .on('progress', function(progress) {
        console.log(+progress.percent);
    })
    .on('end', function(stdout, stderr) {
        console.log("Ended");
    })
    .on('error', (err) => {
        console.error(err);
    })
    .run();

and the output:

Spawned Ffmpeg with command: ffmpeg -i ./segment_1080p.ts -y -filter_complex "[0:v]fps=fps=30,split=3[v1][v2][v3];[v1]scale=width=-2:height=1080[1080p];[v2]scale=width=-2:height=720[720p];[v3]scale=width=-2:height=360[360p]" -acodec libfdk_aac -ac 2 -vcodec libx264 -crf:v 23 -profile:v high  -pix_fmt:v yuv420p -rc-lookahead:v 60 -force_key_frames:v expr:'gte(t,n_forced*2.000)' -b-pyramid:v "strict" -preset:v "medium" -map [1080p] -maxrate:v:0 2000000 -bufsize:v:0 2*2000000 -level:v:0 4.0 -map [720p] -maxrate:v:1 1200000 -bufsize:v:1 2*1000000 -level:v:1 3.1 -map [360p] -maxrate:v:2 700000 -bufsize:v:2 2*500000 -level:v:2 3.1 -map 0:a:0 -b:a:0 192000 -map 0:a:0 -b:a:1 128000 -map 0:a:0 -b:a:2 96000 -f hls -hls_flags +independent_segments+program_date_time+single_file -hls_time 6 -hls_playlist_type vod -hls_segment_type mpegts -master_pl_name 'master.m3u8' -var_stream_map "v:0,a:0,name:1080p v:1,a:1,name:720p v:2,a:2,name:360p" -hls_segment_filename 'segment_%v_%05d.ts' manifest_%v.m3u8
Error: ffmpeg exited with code 1: Error initializing complex filters.
Invalid argument

    at ChildProcess.<anonymous> (/media/Drive/projects/Playground/node_modules/fluent-ffmpeg/lib/processor.js:182:22)
    at ChildProcess.emit (events.js:315:20)
    at Process.ChildProcess._handle.onexit (internal/child_process.js:277:12)

And I can confirm OP's word, setting shell to true solves this issue.
So, any workarounds for it? manually editing the code is not reliable.

@ocramot
Copy link
Author

ocramot commented Apr 23, 2021

Sorry for not giving any feedback earlier, unfortunately I don't work on that project anymore and I have no way to check it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants