Trying to change extension of filename on an ffmpeg script - bash

(first time posting a question here)
So I'm looking to write a ffmmpeg script to automate encoding my files to VP9.
The problem I'm having is when I try to strip the extension and add a new one.
For example
Demo.mp4
Should change to
Demo.webm
I'm running this on a Ubuntu-16.04 (Server Non-GI Version)
I've tried a few different ways to accomplish this (using google and other posts on StackOverflow) but I can't seem to make it work
This is the error I keep getting..
line 31: Demo.mp4+.vp9: syntax error: invalid arithmetic operator (error token is ".mp4+.vp9")
I've also commented (in the code below) where the syntax error is pointing to..
#!/bin/bash
# Welcome Message
clear
printf "====================================\n"
printf "FFMPEG Encoder\n"
printf "(Using HDR-4k Profile)\n"
printf "====================================\n\n"
printf " Loading Files in Current Directory...\n\n"
sleep 3s
# Variables
i=1
ext=".webm"
vadd=4000000
vsub=2000000
# Iterate through files in current directory
for j in *.{mp4,mkv};
do
echo "$i.$j"
file[i]=$j
i=$(( i + 1 ))
done
# Select File & Bitrate
printf "Enter file number\n"
read fselect
printf "${file[$fselect]}: Selected for encoding\n\n"
printf "Enter Average Bitrate (Eg: 8000000)\n\n"
read bselect
# ***THIS IS WHERE THE PROBLEM IS***
# Prepare output file, strip trailing extension (eg .mkv) and add .webm
ftemp1="${file[$fselect]}"
ftemp2="${ftemp1::-4}"
fout="$(($ftemp2+$ext))"
printf "Output file will be: $fout"
printf "Preparing to encode..."
sleep 5s
# Encode with User-Defined Parameters
ffmpeg -y -report -i ${file[$fselect]} -b:v $bselect -speed 4 -pass 1 \
-pix_fmt yuv420p10le \
-color_primaries 9 -color_trc 16 -colorspace 9 -color_range 1 \
-maxrate "$(($bselect+$vadd))" -minrate "$(($bselect-$vsub))" \
-profile:v 2 -vcodec libvpx-vp9 -f webm /dev/null && \
ffmpeg -y -report -i ${file[$fselect]} -b:v $bselect -pass 2 \
-pix_fmt yuv420p10le \
-color_primaries 9 -color_trc 16 -colorspace 9 -color_range 1 \
-maxrate "$(($bselect+$vadd))" -minrate "$(($bselect-$vsub))" \
-profile:v 2 -vcodec libvpx-vp9 \
$fout
I'm certain there is a much cleaner way to do this - but I'm not expecting help with that :P
My suspicion is that I'm trying to add two different types of variables? But I thought I defined them as strings..I could be wrong
Please Help... lol

You are trying to do arithmetic calculus ($((...))). But you just need to concatenate two strings:
fout="$ftemp2$ext"
BTW, you can simplify this transformation in three lines with a single line:
fout="${file[$fselect]/%.mp4/$ext}"
This works as a regular expression, where an .mp4 string found at the end (the % symbol) is repalced by the contents of $ext.

Related

FFmpeg - divide single .webm video into multiple videos (each representing part of the screen) specifying rows and columns count

Can FFmpeg divide one big rectangular video into x smaller rectangular ones?
What would be a command for it?
Can we parametrize the command with number of rows and columns we desire?
Can we somehow prevent loosing pixel precision when command is provided with improper rows/column count for source video resolution?
This script does the job:
inputFile=$1
rows=$2
columns=$3
counter=0
for((r=0; r<$rows; r++))
do
for((c=0; c<$columns; c++))
doinputFile=$1
ffmpeg -i $inputFile -filter:v "crop=iw/$columns:ih/$rows:$c*
(iw/$columns):$r*(ih/$rows)" -vcodec libvpx -crf 5 -b:v 10M -an
"$(dirname "$inputFile")/$(basename "$inputFile" ".webm")$counter.webm"
((counter=counter+1))
done
done
There is a single-call solution to do single-in multiple-out like this:
ffmpeg -i inputFile \
-vsync vfr \
-filter_complex [0:v]untile=$ncolsx$nrows:$nout,select=mod(n\,$nout)+1:$nout[v1][v2][v3]...[v$nout] \
-map [v1] -r $fps output1.webm \
-map [v2] -r $fps output2.webm \
...
-map [v$nout] -r $fps output$nout.webm
Here, $nout = $ncols * $nrows and you need to set the output framerate $fps explicitly (or it defaults to $input_fps * $nout).
Accordingly, you can run your nested loops to form the FFmpeg command argument string, and call it once after the loop. Note that my use of pseudo-variables $xxx is not adhering to any language so please make necessary adjustments.

Problem with escaped quotes in variable (I think) [duplicate]

This question already has an answer here:
Pass dynamically generated parameters to command inside script
(1 answer)
Closed 11 months ago.
I am trying to write a parameter driven routine to extract parts of audio files using ffmpeg.
Because the routine is parameter driven I end up with a number of options in variables (a technique I have used successfully before in simpler examples) and for some reason this time it isn't working. having stared at it and tried various experiments for hours I give up and hope the helpful experts can sort me out
This is a simplified version with the variables set directly
...
#!/bin/bash
a="a b c.mp3"
b="out-$a"
trackstring="-metadata track=\"07/93\""
echo "trackstring=$trackstring"
titlestring="-metadata title=\"$a\""
echo "titlestring=$titlestring"
startpoint="-ss 0"
echo "startpoint=$startpoint"
endpoint="-to 300"
echo "endpoint=$endpoint"
coverstring="-c:v copy"
echo "coverstring=$coverstring"
audiostring="-c:a libmp3lame -ab 32k -ac 1"
echo "audiostring=$audiostring"
echo "ffmpeg $startpoint $endpoint -i \"$a\" -hide_banner -loglevel warning $coverstring $audiostring $titlestring $trackstring \"$b\""
ffmpeg $startpoint $endpoint -i "$a" -hide_banner -loglevel warning $coverstring $audiostring $titlestring $trackstring "$b"
...
The resulting output from my script looks like this:
trackstring=-metadata track="07/93"
titlestring=-metadata title="a b c.mp3"
startpoint=-ss 0
endpoint=-to 300
coverstring=-c:v copy
audiostring=-c:a libmp3lame -ab 32k -ac 1
ffmpeg -ss 0 -to 300 -i "a b c.mp3" -hide_banner -loglevel warning -c:v copy -c:a libmp3lame -ab 32k -ac 1 -metadata title="a b c.mp3" -metadata track="07/93" "out-a b c.mp3"
Which gives me exactly what I am expecting and I think all valid BUT....
Then ffmpeg gives me:
[mp3 # 0x55ae679e4640] Estimating duration from bitrate, this may be inaccurate
[NULL # 0x55ae679ea0c0] Unable to find a suitable output format for 'b'
b: Invalid argument
A bad one!
But interestingly putting the whole command into a string and the using an explicit sub-shell works exactly as expected: So starting from the last of the assignments in the original post
...
audiostring="-c:a libmp3lame -ab 32k -ac 1"
echo "audiostring=$audiostring"
cmd="ffmpeg $startpoint $endpoint -i \"$1\" -hide_banner -loglevel warning $coverstring $audiostring $titlestring $trackstring \"$2\""
echo "$cmd"
bash -c "$cmd"
Frankly, although I now have "working code" I think I am more confused than before.
The output is unchanged (except no error from ffmpeg) and the file(s) are generated exactly as expected

Can I recycle ffmpeg2pass-0.log

Can I re-use 2 pass logs?
That is to say: I am doing this, but I wonder if I should be doing this. ie: does a -pass 1 that had different crf/b:v parameters from -pass 2 output produce the same results as always uniquely encoding both passes for every input?
I have a feeling I shouldn't be reusing pass1.
Say I am doing tests, and for the same input file produce multiple 2 pass ouputs with varying constrained bitrate/crf variants...
eg:
constrainedQ-br9M-crf12.webm
constrainedQ-br12M-crf18.webm
constrainedQ-br14M-crf18.webm
constrainedQ-br16M-crf18.webm
Is it ok to detect the prior log file, check that it was produced for the same input file, and re-use it by skipping -pass 1 for subsequent renders? (in which case ffmpeg finds the existing log and appears to use it for pass 2)
Or
Should I be re-generating the pass 1 log whenever the bitrate or crf changes?
[edit] everyone loves a bit of context code
f_rm2passFilesVP9() {
rm \
"${input%/*}/ffmpeg2pass-0.log" \
"${input%/*}/ffmpeg2pass-0.log.temp" \
"${input%/*}/ffmpeg2pass-0.log.mbtree.temp" &> /dev/null
}
...
f_2passLogForThisInputExists() {
if [[ "$input" == $(cat "${input%/*}/.priorInput" 2> /dev/null) ]];then
echo 1
else
echo 0
fi
}
...
if [[ 0 == $(f_2passLogForThisInputExists) ]];then
echo " ENCODING CONSTRAINED QUALITY br:$br crf:$CRF - PASS 1/2"
trap "f_rm2passFilesVP9" 1 2 3 6
ffmpeg -hide_banner -y -i "${input}" \
-c:v libvpx-vp9 -pass 1 -b:v "$br" -crf "$CRF" -threads 4 \
-tile-columns 6 -frame-parallel 1 \
-an -f webm /dev/null
echo "$input" >"${input%/*}/.priorInput"
trap "" 1 2 3 6
else
echo "REUSING - PASS 1 FOR THIS INPUT - PASS 1/2"
fi
echo "ENCODING CONSTRAINED QUALITY br:$br crf:$CRF - PASS 2/2"
ffmpeg -hide_banner -y -i "${input}" \
-c:v libvpx-vp9 -pass 2 -b:v "$br" -crf "$CRF" -threads 4 -speed 2 \
-tile-columns 6 -frame-parallel 1 -auto-alt-ref 1 -lag-in-frames 25 \
-c:a libopus -b:a 64k -f webm \
"${exportName}"
This should be OK if you are simply giving it more or less bitrate. I do this sometimes, but I also use a lot of zones, so I usually just rerun first pass because I know zones will definitely make a difference in the first pass file. If I add a zone after the first pass file is created the it will apply the difference in bitrate across the file and if I do it before then the zone bitrate modifier gets applied only to the frames specified. Since I think you are simply giving the file more or less bitrate then the distribution should be pretty much the same. I would say only rerun first pass if your second pass is going to much higher bitrate, like 20% or more. Better just to run first pass at the highest bitrate you intend to encode, if possible.

ffmpeg: Incompatible pixel format 'yuv420p' for codec 'png', auto-selecting format 'rgb24'

I have a bunch of .png files
file_000.png
file_005.png
file_010.png
I am using an inherited ffmpeg script to stitch together .png files into an .mp4 file. I don't understand all the flags, but I've used this script successfully in the past
## images_to_movie.sh
set -o noglob
in_files=$1
out_file=$2
ffmpeg \
-framerate 10 \
-loglevel warning \
-pattern_type glob -i $in_files \
-pix_fmt yuv420p \
-vf 'crop=trunc(iw/2)*2:trunc(ih/2)*2:0:0' \
-y \
$out_file
with the command
./images_to_movie file_*.png files.mp4
I am now receiving the error
Incompatible pixel format 'yuv420p' for codec 'png', auto-selecting
format 'rgb24'
Some searching reveals that
-pix_fmt yuv420p
is deprecated and I should instead use
-pix_fmt yuv420p -color_range 2
but the error remains. I tried using
-pix_fmt rgb24
and the error disappears but I'm no .mp4 file is created.
How do I need to modify this script to create an .mp4 ?
What's Going Wrong
file_*.png isn't being passed to your program at all -- neither is files.mp4.
Instead, when you run ./images_to_movie file_*.png files.mp4, what your shell actually invokes is ./images_to_movie file_000.png file_005.png file_010.png files.mp4.
Thus, file_000.png is treated as the only input file, and file_005.png is treated as the output file to use. The extra arguments (file_010.png and files.mp4) are in positions $3 and $4; are never read; and are thus ignored entirely.
How To Fix It
The original code assumes that ./images_to_movie file_*.png files.mp4 will put file_*.png in $1 and files.mp4 in $2. Unless file_*.png has no matches, that assumption is false.
set -o noglob prevents glob expansion from taking place after your script is started, but nothing in your script's contents can prevent the calling script from expanding globs beforehand. Thus, you want an invocation with quotes, akin to:
./images_to_movie 'file_*.png' files.mp4
...and script contents akin to:
#!/usr/bin/env bash
in_files=$1
out_file=$2
ffmpeg \
-framerate 10 \
-loglevel warning \
-pattern_type glob -i "$in_files" \
-pix_fmt rgb24 \
-vf 'crop=trunc(iw/2)*2:trunc(ih/2)*2:0:0' \
-y \
"$out_file"
Note that we took out the noglob, and instead quoted all expansions; this not only prevents globbing, but also prevents string-splitting -- so a pattern like File *.png wouldn't be split into one word File and a second word *.png even in the absence of globbing.

Batch avconv re-encode videos halfway through the list

As I have a low-end computer running Linux I often need to re-encode HD videos, lowering the quality to be able to watch them in my machine. A typical case is when I download several episodes of a series, I can't convert them all at once, and I need to start re-encoding halfway through the series.
I typically use this line to convert a single episode to a lower quality:
avconv -i anime_episode_01.mkv -map 0 -c copy -c:v libx264 -crf 31 anime_01.mkv
If I were to batch-convert them at once I would use something like:
for i in *.mkv;do avconv -i "$i" -map 0 -c copy -c:v libx264 -crf 31 "encoded/$i";done
Where encoded is a subdirectory.
But what if I need to start re-encoding at, say, episode 5?
I have no idea.
There are probably lots of ways to do this, here are a couple of options.
Option 1: Use seq
Use seq to generate a sequence, loop over this and encode.
A sequence from 5 to 15:
seq 5 15
If you need to format the numbers, e.g. to get a 0 prefix for one digit numbers, you can
use the -f switch, which takes a printf style formatting argument of a float/double.
seq -f %02.0f 5 15
This can be used in a loop, e.g. something like this:
for i in $(seq -f %02.0f 5 15); do
filename="anime_episode${i}.mkv"
echo "Encoding episode $i: $filename"
avconv -i "$filename" -map 0 -c copy -c:v libx264 -crf 31 "encoded/$filename"
done
Option 2: Check whether encoded file exists
Do pretty much the same as you do in your current loop, but only perform encoding if
the encoded file does not already exist.
for i in *.mkv; do
if [ ! -f encoded/$i ]; then
echo "Encoding file: $i"
avconv -i "$i" -map 0 -c copy -c:v libx264 -crf 31 "encoded/$i"
else
echo "Skipped file: $i"
fi
done

Resources