Supplying Powershell arguments to 'start' breaks the command? - powershell

I have a PowerShell script that calls ffmpeg to do a two-pass encode of all .mp4 video files in the current folder.
foreach ($i in Get-ChildItem . | Where { $_.extension -like ".mp4" }) {
ffmpeg -y -i "$i" -c:v libx265 -b:v 2.2M -preset medium -x265-params pass=1 -an -f mkv NUL
ffmpeg -i "$i" -c:v libx265 -b:v 2.2M -x265-params pass=2 -c:a libopus -ac 1 -b:a 64k -preset medium "small\$i.mkv"
}
It works perfectly but brings my system to a halt when in use, so I want to give the ffmpeg processes a low priority and an affinity that only lets them use two CPU cores. So I try
foreach ($i in Get-ChildItem . | Where { $_.extension -like ".mp4" }) {
start "x265ify" /wait /low /b /affinity C ffmpeg -y -i "$i" -c:v libx265 -b:v 2.1M -preset medium -x265-params pass=1 -an -f mkv NUL
start "x265ify" /wait /low /b /affinity C ffmpeg -i "$i" -c:v libx265 -b:v 2.1M -x265-params pass=2 -c:a libopus -ac 1 -b:a 64k -preset medium "small\$i.mkv"
}
This doesn't work, because the -i is considered to be ambiguous. So I try putting the entire command ("ffmpeg -y -i..." onward) in a string, with the quotation marks escaped. That doesn't work because a positional parameter can't be found that accepts /low. I try omitting that, passing the command as a separate variable, nothing I try seems to work.
Yet I have an Ruby script invoking ffmpeg the exact same way that does work. It reads
start "x265-ifier" /wait /low /b /affinity #{AFFINITY} ffmpeg \
-hide_banner \
-i "#{v}" \
-vf scale=-2:#{HEIGHT} \
-c:a libopus \
-b:a #{AUDIO_BITRATE}k \
-ac 1 \
-c:v libx265 \
-x265-params vbv-maxrate=#{MAX_VIDEO_BITRATE}:vbv-bufsize=7000 \
-preset #{PRESET} \
-crf #{CRF} \
"#{dest}" \
-y
So what am I doing wrong? Why does PowerShell not want to run this command, which runs okay if Ruby is the middleman? Why is /low not a parameter when it's listed in the documentation as one, and how can I make -i any less ambiguous when the entire string passed to start is supposed to be the command it's running, with -i as a valid argument of that command?

In PowerShell, start is the built-in alias for the Start-Process cmdlet[1], whose syntax and behavior differ from the internal start command of cmd.exe.
In order to call the latter[1], you must invoke cmd.exe explicitly, as follows:
cmd /c start `"x265ify`" /wait /low /b /affinity C ffmpeg -y -i $i -c:v libx265 -b:v 2.1M -preset medium -x265-params pass=1 -an -f mkv NUL
Note:
The " chars. around x265ify are escaped as `" to ensure that they're passed through to start, which (awkwardly) uses them to distinguish between the first argument representing a window title vs. the command to invoke.
$i doesn't have to be double-quoted, because PowerShell will do that on demand.
Using /wait means that your second start command won't be invoked until the first one has completed.
As an aside: PowerShell's handling of empty arguments ("") and arguments with embedded " chars. passed to external programs is fundamentally broken as of PowerShell [Core] 7.0, requiring awkward workarounds - see this answer for more information.
[1] Use Get-Command start to discover this fact.
[2] which is necessary in your case, because Start-Process doesn't offer control over the launched process' priority or CPU affinity.

Related

ffmpeg trailing options with colon on windows

I've just switched to Windows 10 Pro.
When I open a shell to use ffmpeg, any time I use an option with a colon I get an error.
for example:
ffmpeg -thread_queue_size 4096 -r 24 -i uncompressed.mov -c:v libx264 -b:v 40m -profile:v main -crf 31 -pix_fmt yuv420p -c:a aac -b:a 192K -strict -2 -vf eq=saturation=1.3 compressed_31.mp4
gives:
[NULL # 0000023184ac8c40] Unable to find a suitable output format for 'libx264'
libx264: Invalid argument
if I get rid of -c:v libx264, then I get the same error with '40m', or with 'main' etc... so it's always trying to read the argument after an option with a colon as the output file.
Is this a windows thing? All my commands work fine in a mac terminal.
I assume the answer is simple. Apologies for my ignorance. Feels like I'm misunderstanding the windows shell or ffmpeg on windows.

Better way to use ffmpeg with vidstab and encoding 2 pass

I scan old 8mm films
so I have folder with set of jpeg
I transform them to films using ffmpeg ( I choose x264 2 pass encoding)
//On all folder that start by 1 I launch the pass1 for x264
for f in 1*/ ; do cd "$f"; ffmpeg -y -r 18 -i img%05d.jpg -c:v libx264 -s 1200x898 -b:v 3000k -pass 1 -an -f mp4 /dev/null; cd ..; done
//On all folder that start by 1 I launch the pass2 x264
for f in 1*/ ; do cd "$f"; ffmpeg -y -r 18 -i img%05d.jpg -c:v libx264 -s 1200x898 -b:v 3000k -pass 2 ../"`echo ${PWD##*/}`.mp4"; cd ..; done
--> Before I have set of folder with jpeg
1965-FamilyStuff01\img1111.jpg,..,img9999.jpg
1965-FamilyStuff02\img1111.jpg,..,img9999.jpg
and I get
1965-FamilyStuff01.mp4
1965-FamilyStuff02.mp4
then I discover vidstab that also need 2 pass
// Stabilize every Video of a folder
mkdir stab;for f in ./*.mp4 ; do echo "Stabilize $f" ;
ffmpeg -i "$f" -vf vidstabdetect=shakiness=5:accuracy=15:stepsize=6:mincontrast=0.3:show=2 -y -f mp4 /dev/null;
ffmpeg -i "$f" -vf vidstabtransform=smoothing=30:input="transforms.trf":interpol=linear:crop=black:zoom=0:optzoom=1,unsharp=5:5:0.8:3:3:0.4 -y "stab/$f"
; done; rm transforms.trf;
But I ask myself, that perhaps the order is not correct or perhaps there is a way to do the encoding with vidstab in less than 4 pass (2 pass for x264 encoding then 2 pass for vidstab)
or perhaps the order should be change to optimize quality of film output)
You will need to run two commands to use vidstab. But x264 does not need two-passes for best quality. Two-pass encoding is used to target a specific output file size. Just use a single pass with the -crf option.
So you only need to use two commands:
ffmpeg -i input.mp4 -vf "scale=1200:-2,vidstabdetect=shakiness=5:accuracy=15:stepsize=6:mincontrast=0.3:show=2" -f null -
ffmpeg -i input.mp4 -vf "scale=1200:-2,vidstabtransform=smoothing=30:interpol=linear:crop=black:zoom=0:optzoom=1,unsharp=5:5:0.8:3:3:0.4,format=yuv420p" -crf 23 -preset medium output.mp4
See FFmpeg Wiki: H.264 for more info on -crf and -preset.

Fish shell run a command with parenthesis in arguments

I am attempting to run the command
ffmpeg -i in.mp4 -vf yadif,format=yuv420p -force_key_frames expr:gte(t\,n_forced/2) -c:v libx264 -crf 18 -bf 2 -c:a aac -q:a 1 -ac 2 -ar 48000 -use_editlist 0 -movflags +faststart out.mp4
as mentioned here. However, the issue is the argument expr:gte(t\,n_forced/2) contains parenthesis and fish shell will interpret t\,n_forced/2 as a commmand. Is there any way to run this in fish shell instead of needing to make a seperate bash script?
Note, I cannot wrap with single quotes as I get the following
Surrounding the argument expr:gte(t\,n_forced/2) in single quotes and removing the backslash in \, fixed the problem.
The resulting argument is 'expr:gte(t\n_forced/2)'.

How to get -vf to ffmpeg from powershell commandline without parsing

I made a powershell function to recode video with some extra parameters. It basically makes a get-childitem in the directory and feeds every occurrence it finds to a foreach loop. This worked well as long as I have default values inside my function which gets fed to the ffmpeg string in the loop in case I do not provide anything on the commandline (like number of passes, audio quality etc.). Now I wanted to integrate the option to use the -vf ffmpeg filter option. My problem there is, that I usualy dont need that, so there is no sane default option I could use, so I can not have something like -vf $filteroption in my command line. So I am trying to figure out how to get that "-vf" inside the variable without powershell or ffmpeg screwing me over, because atm I get either the error of a missing - in what ffmpeg sees (I guess powershell parses this away) and when I \ escape the - I see it now in the ffmpeg line, but ffmpeg does not recognize it as single parameter.
examples which work:
&$encoder -hide_banner -i $i -c:v libvpx-vp9 -b:v 0 -crf $quality -tile-columns 6 -tile-rows 2 -threads 8 -speed 2 -frame-parallel 0 -row-mt 1 -c:a libopus -b:a $bitrate -af aformat=channel_layouts=$audio -c:s copy -auto-alt-ref 1 -lag-in-frames 25 -y $outfile;
here I provide $quality, $audio etc. with powershell parameters to the function like -quality 31 -audio stereo and it all works.
But now I need to get something like "-vf scale=1920:-1" or "" inside that line and that does not work with something like just this:
&$encoder -hide_banner -i $i -c:v libvpx-vp9 -b:v 0 -crf $quality -tile-columns 6 -tile-rows 2 -threads 8 -speed 2 -frame-parallel 0 -row-mt 1 -c:a libopus -b:a $bitrate -af aformat=channel_layouts=$audio -c:s copy -auto-alt-ref 1 -lag-in-frames 25 -y $extra $outfile;
when I call the function with: "RecodeVP9 -extra -vf scale=1920:-1" powershell takes away the -, if I try it with escaping the - with - ffmpeg whines about it saying that "Unable to find a suitable output format for '-vf'". I also tried "" and "-" with similiar results. So it seems that either powershell screws me over or ffmpeg.
So to sum it up:
I need a way to get extra ffmpeg arguments WITH the parameter name itself from the powershell command line into my powershell function (like -vf scale=1920:-1).

Dividing, processing and merging files with ffmpeg

I am trying to build an application that will divide an input video file (usually mp4) into chunks so that I can apply some processing to them concurrently and then merge them back into a single file.
To do this, I have outlined 4 steps:
Forcing keyframes at specific intervals so to make sure that each
chunk can be played on its own. For this I am using the following
command:
ffmpeg -i input.mp4 -force_key_frames
"expr:gte(t,n_forced*chunk_length)" keyframed.mp4
where chunk_length is the duration of each chunk.
Dividing keyframed.mp4 into multiple chunks.
Here is where I have my problem. I am using the following command:
`ffmpeg -i keyframed.mp4 -ss 00:00:00 -t chunk_length -vcodec copy -acodec copy test1.mp4`
to get the first chunk from my keyframed file but it isn't capturing
the output correctly, since it appears to miss the first keyframe.
On other chunks, the duration of the output is also sometimes
slightly less than chunk_length, even though I am always using the
same -t chunk_length option
Processing each chunk For this task, I am using the following
commands:
ffmpeg -y -i INPUT_FILE -threads 1 -pass 1 -s 1280x720 -preset
medium -vprofile baseline -c:v libx264 -level 3.0 -vf
"format=yuv420p" -b:v 2000k -maxrate:v 2688k -bufsize:v 2688k -r 25
-g 25 -keyint_min 50 -x264opts "keyint=50:min-keyint=50:no-scenecut" -an -f mp4 -movflags faststart /dev/null
ffmpeg -y -i INPUT_FILE -threads 1 -pass 2 -s 1280x720 -preset
medium -vprofile baseline -c:v libx264 -level 3.0 -vf
"format=yuv420p" -b:v 2000k -maxrate:v 2688k -bufsize:v 2688k -r 25
-g 25 -keyint_min 50 -x264opts "keyint=50:min-keyint=50:no-scenecut" -acodec libfaac -ac 2 -ar 48000 -ab 128k -f mp4 -movflags faststart OUTPUT_FILE.mp4
This commands are not allowed to be modified, since my goal here is
to parallelize this process.
Finally, to merge the files I am using concat and a list of the
outputs of the 2nd step, as follows:
ffmpeg -f concat -i mylist.txt -c copy final.mp4
In conclusion, I am trying to find out a way to solve the problem with step 2 and also get some opinions if there is a better way to do this.
I found a solution with the following code, which segments the file without the need to force keyframes (it cuts on the nearest keyframe) and multiple commands.
ffmpeg -i test.mp4 -f segment -segment_time chunk_length -reset_timestamps 1 -c copy test%02d.mp4