Run a function from while read line - bash

I'm trying to run a function from while read line, the function contains ffmpeg commands to marge two files. but for some reason it's running the first $line and than breaks from loop.
"$filesList" contains three lines. I'm not sure what's wrong, but i can confirm with echo "$OFILE" that opener function runs three times if I comment out the ffmpeg commands, and only once with ffmpeg commands.
opener(){
OFILE="$1"
echo "$OFILE"
ffmpeg -i $opener_path -c copy -bsf:v h264_mp4toannexb -f mpegts intermediate1.ts
ffmpeg -i $OFILE -c copy -bsf:v h264_mp4toannexb -f mpegts intermediate2.ts
ffmpeg -i "concat:intermediate1.ts|intermediate2.ts" -c copy -bsf:a aac_adtstoasc merge_$OFILE
mv merge_$OFILE $OFILE
rm intermediate1.ts intermediate2.ts
}
while read line; do
if [ -e "$line" ]; then
opener "$line"
fi
done <<< "$filesList"

It appears one of the ffmpeg commands is reading from standard input, which consumes the rest of the contents of $filesList before the next call to read. I'm not familiar with ffmpeg, but two possibilities:
Does -i require an argument? Your posted code doesn't set the value of opener_path, so its unquoted expansion would produce an empty string that is discarded by the shell.
How is concat:intermediate1.ts|intermediate2.ts interpreted by ffmpeg? Given the call to rm, it seems to produce a pair of files from an unknown source.

Related

How to use ffmpeg stream copy within bash "for loop"?

ok i have this and it works but it's slow
for i in *.mov;
do name=`echo "$i" | cut -d'.' -f1`
echo "$name"
ffmpeg -i "$i" "${name}.mp4"
done
I want it to convert with these ffmpeg options
ffmpeg -i movie.mov -vcodec copy -acodec copy out.mp4
Parameter expansion is sufficient; you don't need pipelines involving cut.
for i in *.mov; do
name=${i%%.mov} # i=foo.mov => name=foo
ffmpeg -i "$i" "$name.mp4"
done
Untested, but you should be able to do them in parallel with GNU Parallel if it's too slow and you have a multicore CPU:
parallel ffmpeg -i {} -vcodec copy -acodec copy {.}.mp4 ::: *.mov
If you want to see what it would do first, without actually doing anything:
parallel --dry-run ffmpeg -i {} -vcodec copy -acodec copy {.}.mp4 ::: *.mov
Note: On macOS, install these two packages simply with homebrew using:
brew install ffmpeg
brew install parallel
Use:
for i in *.mov; do ffmpeg -i "$i" -c copy "${i%.*}.mp4"; done
-c copy is the same as -vcodec copy -acodec copy.
Adapted from How do you convert an entire directory with ffmpeg?

/usr/local/bin/ffmpeg: cannot execute binary file

Want to convert my m4a files into mp3 files using a script. It would save some time... I have over 100 GB of music files.
OS: OSX10.14 / Terminal vs Bash script
I can run ffmpeg -v 5 -y -i musicFile.m4a -acodec libmp3lame -ac 2 -b:a 320k musicFile.mp3 from the terminal. It converts the file and I can see and play the file from itunes.
When I run the same from a bash script it fails to convert.
ffmpeg -v 5 -y -i $ENTRY_FILE -acodec libmp3lame -ac 2 -b:a 320k $MP3NAME
My ipod nano just died and I got a new mp3 player. Now I need to convert my itunes files from AAC format to MP3.
ffmpeg is an established video and music file converter.
When I run it from the bash script I tried a few things.
I added ./ in front of the file, that failed because it was installed under /usr/local/bin and not under the same directory.
I also tried sh ffmpeg... and that gave me the cannot execute a binary file.
#!/usr/bin/env bash
# convert m4a file to mp3
set -e
file_convert() {
ENTRY_FILE=$(printf %q "${entry}")
FILE_NAME=$(printf %q "$(basename "${entry}")")
DIR=$(printf %q "$(dirname "${entry}")")
NAME="${FILE_NAME%.*}"
EXT="${FILE_NAME##*.}"
MP3NAME="${DIR}/${NAME}.mp3"
printf "%*s%s\n" $((indent+2)) '' "$ENTRY_FILE"
printf "%*s\tNew File :\t%s\n" $((indent+2)) '' "$MP3NAME"
if [ $EXT == "m4a" ]
then
printf "%*s\tConverting: \t%s\n" $((index+2)) '' "$ENTRY_FILE"
ffmpeg -v 5 -y -i $ENTRY_FILE -acodec libmp3lame -ac 2 -b:a 320k $MP3NAME
fi
}
walk() {
local indent="${2:-0}"
printf "\n%*s%s\n\n" "$indent" '' "$1"
# If the entry is a file convert it
for entry in "$1"/*; do [[ -f "$entry" ]] && file_convert; done
# If the entry is a directory recurse
for entry in "$1"/*; do [[ -d "$entry" ]] && walk "$entry" $((indent+2)); done
}
# If the path is empty use the current, otherwise convert relative to absolute; Exec walk()
[[ -z "${1}" ]] && ABS_PATH="${PWD}" || pushd "${1}" && ABS_PATH="${PWD}"
walk "${ABS_PATH}"
popd
echo
I expect >./aacToMp3.sh ./music to traverses the music directory and convert each m4a file to .mp3.
It is walking the file system and printing out correct files, with the spaces escaped. When it hits the ffmpeg line it halts. I put the set -e at the top of the file to force it to fail if the command fails. Without the set -e it happily walks all the music files and prints them to the stdout.
If you have lots of files to process and a decent multi-core CPU and fast disk, I would recommend GNU Parallel which you can install with homebrew:
brew install parallel
Then make a copy of a few files in a test directory and try:
parallel --dry-run ffmpeg -v 5 -y -i {} -acodec libmp3lame -ac 2 -b:a 320k {.}.mp3 ::: *.m4a
If that looks good, replace --dry-run with --progress.
If that looks good, you can (make a backup first) and do the whole lot:
find path/to/music -name "*.m4a" -print0 | parallel -0 --progress ffmpeg -v 5 -y -i {} -acodec libmp3lame -ac 2 -b:a 320k {.}.mp3
Thanks for all the input.
After a while I ended up writing all the ffmpeg lines to a script file. Glad I did. I was able to quickly scan the file and see some errors and fix them.
This is what I came up with.
Basically I am writing straight text to stdout and directing it to a file. Which I converted to a shell script to convert each file. One at a time. Ran the generated script overnight.
file_convert() {
ENTRY_FILE=$(printf %q "${entry}")
FILE_NAME=$(printf %q "$(basename "${entry}")")
DIR=$(printf %q "$(dirname "${entry}")")
NAME="${FILE_NAME%.*}"
EXT="${FILE_NAME##*.}"
MP3NAME="${DIR}/${NAME}.mp3"
if [ $EXT == "m4a" ]
then
printf 'echo "Converting %s ..."\n' "$FILE_NAME"
printf 'ffmpeg -v 5 -y -i %s -acodec libmp3lame -ac 2 -b:a 320k %s\n\n' "$ENTRY_FILE" "$MP3NAME"
fi
}
The output looked like.
#!/usr/bin/env bash
echo "Converting 03\ It\'s\ Not\ My\ Time.m4a ..."
ffmpeg -v 5 -y -i /Users/arthuranderson/Documents/work/projects/mp3Convert/music/3\ Doors\ Down/3\ Doors\ Down\ \(Bonus\ Track\ Version\)/03\ It\'s\ Not\ My\ Time.m4a -acodec libmp3lame -ac 2 -b:a 320k /Users/arthuranderson/Documents/work/projects/mp3Convert/music/3\ Doors\ Down/3\ Doors\ Down\ \(Bonus\ Track\ Version\)/03\ It\'s\ Not\ My\ Time.mp3
echo "Converting 01\ Down.m4a ..."
ffmpeg -v 5 -y -i /Users/arthuranderson/Documents/work/projects/mp3Convert/music/311/Greatest\ Hits\ \'93-\'03/01\ Down.m4a -acodec libmp3lame -ac 2 -b:a 320k /Users/arthuranderson/Documents/work/projects/mp3Convert/music/311/Greatest\ Hits\ \'93-\'03/01\ Down.mp3
Mark I will try parallel when I purchase some more music next time.
Thanks everyone!

convert metadata value to string while doing ffmpeg conversion

I have a script which splits mp3 files into smaller files and incrementally numbers them. I'm now also trying to incrementally increase the -metadata title number value and -metadata track number value but ffmpeg sees everything as a string. The line I'm having issue with is.
ffmpeg -i "$f" -f segment -segment_time 1200 -ar 22050 -ac 1 -metadata title="$%03d-$fn.mp3" -metadata track="$%02d" "$splitdirname/%03d-$fn.mp3" #split every 20mins
If you look at the metadata title created it says $%03d-title_of_file.mp3 I'm trying to get the metatdata title to increment like 001-title_of_file.mp3, 002-title_of_file.mp3, 003-title_of_file.mp3,...
The full script i'm using is below:
#!/bin/bash
#run using bash mp3spl.sh
currentdir="$(pwd)" #get current directory
for f in *.mp3 # only look for mp3 files
do
#fn=`echo "$1" | cut -d'.' -f1` #get just the filename no extension
fn=$(basename "$f" | cut -d'.' -f1) #get just the filename no extension
echo "($fn)"
splitdirname="$currentdir/split-$fn" #sub directory with correct names
#echo "splitdirname $splitdirname"
mkdir -p "$splitdirname" #make split directory
#echo "Processing $f"
ffmpeg -i "$1" 2> tmp.txt
ffmpeg -i "$f" -f segment -segment_time 1200 -ar 22050 -ac 1 -metadata title="$%03d-$fn.mp3" -metadata track="$%02d" "$splitdirname/%03d-$fn.mp3" #split every 20mins
#rm tmp.txt
done
Using -metadata title="$%03d-$fn.mp3" -metadata track="$%02d" does not create incremental numbers with leading zeros when used with ffmpeg.
The %03d syntax is only valid for input or output file patterns, for those (de)muxers that implement this kind of parsing (e.g., image2, segment). You cannot use it for setting metadata attributes. In other words, you can't have ffmpeg populate the field with the current segment filename.
Also, the $ in $%03d does not make sense—you're not referring to a shell variable here.
If you want to set the metadata according to the filename of the file that is generated, you have to do this in a second pass. Loop over each generated file, parse its filename, and use that to set the metadata value. Copy the existing audio/video streams with -c copy and -map 0 (the latter is necessary if you have more than one audio/video stream).

Bash while loop stops for no reason? [duplicate]

This question already has answers here:
While loop stops reading after the first line in Bash
(5 answers)
Closed 3 years ago.
I run the following bash script to rotate my mobile phone vids
while read filename ; do
nf=$(echo $filename |rev | cut -f1 -d '/'|cut -f2- -d '.' |rev)
echo $nf
rm -f ffmpeg2pass-0.log
rm -f rotate/tmp.avi
ffmpeg -i $filename -c:v libxvid -pass 1 -c:a libmp3lame -qscale:a 4 -vf "transpose=2,transpose=2" "rotate/tmp.avi"
ffmpeg -i $filename -c:v libxvid -pass 2 -c:a libmp3lame -qscale:a 4 -vf "transpose=2,transpose=2" "rotate/$nf.avi"
done <rotatelist_2
I know there are better ways to do this; I budged this together but I'm figuring out how to do the videos right so the rest don't need to look nice ;-))
However after the first run, the loop unexpectedly ends with no error message. I run similar loops for other things which work pretty well.
The echo is not called again so I guess there's something wrong with the loop itself. The linebreak in the list is a 0x0A, so it should be ok.
Use
</dev/null ffmpeg ...
</dev/null ffmpeg ...
or
ffmpeg ... </dev/null
ffmpeg ... </dev/null
to prevent ffmpeg reading from rotatelist_2 via stdin.

Running FFMPEG from Shell Script /bin/sh

I am trying to setup a Shell Script to work within an automator watch folder...
Everything works with the exception of the Run Shell Scrip portion...
Essentially when a file shows up in the watch folder, it runs the shell scrip which calls FFMPEG and then will move the file to an archive folder for safe keeping. However right now automator is telling me everything worked but now file is being created.
I have the Shell set to /bin/sh and Pass input set to as arguments
Here is my script:
for f in "$#"
do
name=$(basename "$f")
dir=$(dirname "$f")
ffmpeg -i "$f" -b 250k -strict experimental -deinterlace -vcodec h264 -acodec aac "$dir/mp4/${name%.*}.mp4"
echo "$dir/mp4/${name%.*}.mp4"
done
it does echo the correct filename, but does not actually run ffmpeg
I have tried adding -exec before it like I have seen in some scripts but still nothing...
FFmpeg searches STDIN while it is running. This is to allow the user to hit q to stop encoding, among other tasks. This could cause a problem with a script. Perhaps to workaround try this:
# notice --+
# |
# v
ffmpeg -nostdin -i "$f" -b 250k -strict experimental -deinterlace \
-vcodec h264 -acodec aac "$dir/mp4/${name%.*}.mp4"
mysterious error with ffmpeg on OSX
Not sure if it is because I am running on a Mac OS X Server but I imagine it is, I had to include the absolute path to ffmpeg...which fixed it
for f in "$#"
do
name=$(basename "$f")
dir=$(dirname "$f")
/opt/local/bin/ffmpeg -i "$f" -b 250k -strict experimental -deinterlace -vcodec h264 -acodec aac "$dir/mp4/${name%.*}.mp4"
echo "$dir/mp4/${name%.*}.mp4"
done

Resources