Concatenate multiple variables isn't working - bash

I have these variables:
$reel = XF_1
$base = AA000201
$output_dir = directory
This command isn't working. It produces a file called directory/AA000201.mp4
ffmpeg -i $1 $output_dir"/$reel_$base.mp4
This command works. It produces a file called directory/XF_1_AA000201.mp4.
ffmpeg -i $1 $output_dir"/"$reel"_"$base".mp4"
But it gives this error: /Volumes/RAID/LIGHTS/RawFootage/Day01/B_Camera/XF_1/CONTENTS/CLIPS001/AA0002/AA000201.MXF: line 5: continue: only meaningful in a for',while', or `until' loop
Note: I have shortened directory names here for simplicity.

ffmpeg -i "${1}" "${output_dir}/${reel}_${base}.mp4"

Polbrelkey gave you the answer, but i'm still gonna add this.
Lets see why ffmpeg -i $1 $output_dir"/$reel_$base.mp4 is un-interpretable unambiguously :
Look at those three variables :
reel="A"
reel_="B" #Nothing prevents you from ending a variable with an underscore
base="C"
Now, let's interpret ffmpeg -i $1 $output_dir"/$reel_$base.mp4 :
Depending on how your decompose the string, you get either :
#ffmpeg -i $1 $output_dir"/${reel_}${base}.mp4
ffmpeg -i $1 $output_dir"/BC.mp4
#ffmpeg -i $1 $output_dir"/${reel}_${base}.mp4
ffmpeg -i $1 $output_dir"/A_C.mp4
In your case, Bash is actually interpreting the first one (actually, he looks for the longest possible variable name) and since reel_="" is not assigned, outputs directory/AA000201.mp4.
You don't have these kind of problems with base.mp4 since a variable name can't have a dot.
Source : Parameter Expansion on Bash-Hackers

Related

rename files from id3v1 tags in terminal

I have a directory of mp3 files which are all named 1.mp3, 2.mp3 etc..
#dir with numbers for names
10.mp3 15.mp3 2.mp3 24.mp3 29.mp3 33.mp3 38.mp3 42.mp3 47.mp3 51.mp3 56.mp3 60.mp3 65.mp3 7.mp3 74.mp3 79.mp3 83.mp3 88.mp3 92.mp3
11.mp3 16.mp3 20.mp3 25.mp3 3.mp3 34.mp3 39.mp3 43.mp3 48.mp3 52.mp3 57.mp3 61.mp3 66.mp3 70.mp3 75.mp3 8.mp3 84.mp3 89.mp3 93.mp3
12.mp3 17.mp3 21.mp3 26.mp3 30.mp3 35.mp3 4.mp3 44.mp3 49.mp3 53.mp3 58.mp3 62.mp3 67.mp3 71.mp3 76.mp3 80.mp3 85.mp3 9.mp3 94.mp3
13.mp3 18.mp3 22.mp3 27.mp3 31.mp3 36.mp3 40.mp3 45.mp3 5.mp3 54.mp3 59.mp3 63.mp3 68.mp3 72.mp3 77.mp3 81.mp3 86.mp3 90.mp3 95.mp3
14.mp3 19.mp3 23.mp3 28.mp3 32.mp3 37.mp3 41.mp3 46.mp3 50.mp3 55.mp3 6.mp3 64.mp3 69.mp3 73.mp3 78.mp3 82.mp3 87.mp3 91.mp3 96.mp3
I wrote a for loop to extract the title from the metadata using ffmpeg:
for x in *.mp3; do
ffmpeg -i $x ./$("ffmpeg -i $x 2>&1 |grep -E '^\s*title\s*\:\s.*$' |awk -F ' :' '{print $2}'".mp3
done
Instead of extracting the title and renaming the file it says that the file '.mp3' already exists, would I like to rewrite it. when I type y to rewrite this new '.mp3' the same things just happens again.
I fixed the problem by putting the full path of the output file in double quotes instead of just the title extraction command
for x in *.mp3; do
ffmpeg -I $x "./$(ffmpeg -i $x 2>&1 |grep -E '^\s*title\s*\:\s.*$' |awk -F ' :' '{print $2}'
)".mp3
done
My question is why does it create a new file called .mp3 when I only wrap the title extraction command in quotes and not the whole path?
I'm sorry if this is a little lengthly, Im new to stack overflow
In the command substitution $(command), you should not wrap the command
with double quotes as $("command") especially when the command includes
options and/or pipeline sequences, because the double-quoted string is
treated as a single command.
Please see the difference of
echo $("ls")
and
echo $("ls -la")
The 1st one will work but the 2nd one does not, because bash interpretes
ls -la as a single command, not the ls command followed by -la option.
BTW if you just want to rename the files, without re-encoding (which may degrade the quality), you can
say:
for f in *.mp3; do
title=$(ffmpeg -i "$f" 2>&1 | awk '/title/ {sub(/.*title\s*:\s*/, ""); print; exit}')
mv -i -- "$f" "$title".mp3
done
The last exit is for the edge case the mp3 file includes multiple title metadata.
[Update]
As #llogan comments, the ffprobe is more robust to extract the media information
of the file. Please try instead:
for f in *.mp3; do
title=$(ffprobe -v error -of csv=p=0 -show_entries format_tags=title "$f")
mv -- "$f" "$title".mp3
done

Merging video in a specific order using ffmpeg

I use the following to merge video in numeric order.
for f in *; do mv "$f" "${f: -17}"; done &&. find *.ts|. sed 's:\:\ :g'| sed 's/^/file /' > fraglist.txt && ffmpeg -f concat -safe 0 -i fraglist.txt -c copy output.ts; rm fraglist.txt
This works great for files named like the following...
000001
000002
000003
000004
000005
000006
000007
000008
000009
000010
But If I need something like the following merged the order is based on how many digits there are in the file name...
1708.ts 9803.ts 13798.ts 17815.ts 21804.ts 25819.ts
29832.ts
What command could I use to get the second group of files merged in that order? Thank you for your help!
Simplify your whole process by using printf alone to make the txt file contents, and use sort to provide natural/version sorting:
printf "file '%s'\n" *.ts | sort -V > fraglist.txt
ffmpeg -f concat -i fraglist.txt -c copy output.ts
Result:
file '1708.ts'
file '9803.ts'
file '13798.ts'
file '17815.ts'
file '21804.ts'
file '25819.ts'
file '29832.ts'
No need to rm fraglist.txt as the sort redirect (>) will overwrite fraglist.txt.
Note that I removed -safe 0 (an option specific to the concat demuxer) from the ffmpeg command because you don't need it in this exact example. But if you get the Unsafe file name error (such as due to special characters including spaces) then you will need to add it.

Parse csv into variables and execute ffmpeg command in loop

I am trying perform a batch operation to extract specific frames from multiple video files and save as a PNG, using a bash script. I hope to do this by using ffmpeg within a bash script, supplemented by a csv file that contains the name of the input video, the specific frame number to be extracted from the input video, and the output name of the PNG file.
The video files and the csv file would all be placed within the same folder. The script can also be placed in there if necessary.
My csv - called "select.csv" - currently takes the following format (input,output,frame):
mad0.m4v,mad0_out1,9950
mad0.m4v,mad0_out2,4500
mad1.m4v,mad1_out1,3200
My current script - called "frame.sh" - takes the following form:
#!/bin/bash
OLDIFS=$IFS
IFS=“,”
SDIR=/Users/myuser/Desktop/f-input/
cd $SDIR;
while read input output frame
do
echo "$input"
echo "$output"
echo "$frame"
input1=$input
output1=$output
frame1=$frame
ffmpeg -i "$input1" -vf select='eq(n\,'"$frame1"')' -vsync 0
"$output1".png
done < $1
IFS=$OLDIFS
This should allow me to run ./frame.sh select.csv to then process all relevant files in the "f-input" folder on my desktop and extract the specified frames.
I ended up echoing the variables read from the csv so that they could actually be used as variables and looped in the ffmpeg command because carrying out the ffmpeg command using $input, $frame and $output directly after the read operation only ever completed the process on the first line of the csv, without progressing further.
Essentially I would like the following to actually loop through each csv entry, instead of only the first line:
#!/bin/bash
OLDIFS=$IFS
IFS=“,”
SDIR=/Users/myuser/Desktop/f-input/
cd $SDIR;
while read input output frame
do
ffmpeg -i "$input" -vf select='eq(n\,'"$frame"')' -vsync 0 "$output".png
done < $1
IFS=$OLDIFS
Any and all advice appreciated!
Many thanks
Replace IFS=“,” with IFS=",".
I wrote a similar script that reads csv and process movies by ffmpeg.
It works well on the first line of the csv but fails after the second lines.
I found ffmpeg in a loop seems to affect the "read" command and trim the first character of the lines after the second line.
So I ended up with adding extra "garbage" column on the left-most side of the csv and let ffmpeg trim it.
my csv is like:
101,movie1.mp4
102,movie2.mp4
103,movie3.mp4
...
and the (simplified) script is like:
while IFS="," read id movie; do
ffmpeg -v quiet -s 1280x720 -i "$movie" "$id-$movie" </dev/null
done
it generates "101-movie1.mp4" for the first line of the csv just like I expect
but after the second line it generates "02-movie1.mp4" "03-movie3.mp4" and so force because ffmpeg (seems to have) trimmed the first character of the lines.
I added a garbage column on the 1st column like this
x,101,movie1.mp4
x,102,movie2.mp4
x,103,movie3.mp4
and fix the script:
while IFS="," read garbage id movie; do
ffmpeg -v quiet -s 1280x720 -i "$movie" "$id-$movie" </dev/null
done
this worked for me.

Why isn't my bash script working?

I'm trying to get this bash script to work but am at a loss. I have a text file that contains a list of frame numbers line by line. ffmpeg reports the error:
Undefined constant or missing '(' in '$name)'
The script
#!/bin/bash
source text.txt
while read name
do
ffmpeg -i result.mp4 -vf "setpts=N+1,select='eq(n,\$name)'" -vframes 1 frame-$i.jpg
done <text.txt
You are escaping the $ before variable name i.e. $name, so the $name will be treated literally without any variable expansion being done.
Do:
ffmpeg -i result.mp4 -vf "setpts=N+1,select='eq(n,$name)'" -vframes 1 frame-$i.jpg

ZSH: Get basename for for loop

I'm trying to get the basename (without file extension) so that I can process a directory of audio files (.flac -> .m4a) quickly and and up with proper filenames.
So this works:
for i in *.flac;do ffmpeg -i $i -c:a libfdk_aac -vbr 3 $i.m4a; done
But it leaves me with a file like like this:
audiofile.flac.m4a
What I would like to do is just grab the "audiofile" part of the filename so that this works:
for i in *.flac;do ffmpeg -i $i.flac -c:a libfdk_aac -vbr 3 $i.m4a; done
But I don't understand modifiers (apparently a ":t" modifier might get me the results?) or how to define variables on the for loop (for each file). Also, I see that there are many examples of how to do this with BASH, but I'm looking for something that works with zsh.
Any ideas?
Thanks!
In zsh you can use the Modifiers from History Expansion with Parameter Expansions. Each modifier is preceded by a colon: ${name:modifier}. Thermodifier removes the extension, i.e. everything from - including - the last.` to the end of the file name:
for i in *.flac; do
ffmpeg -i $i -c:a libfdk_aac -vbr 3 ${i:r}.m4a
done
Note: the modifier r will remove any extension, not only ".flac".
A more portable (read: should work in any POSIX-compliant shell) and specific solution would be to use the Parameter Expansion ${name%pattern}, which will remove the smallest string matching pattern from the end of the value of name. If pattern does not match the end of the value of name, the value will be returned unchanged:
for i in *.flac; do
ffmpeg -i $i -c:a libfdk_aac -vbr 3 ${i%.flac}.m4a
done
This will remove only ".flac" specifically from the end of the file names.

Resources