Bash while loop wait until task has completed - bash

I have a bash script that I created to process videos from within a folder and it's subfolders:
find . -type f -name '*.mkv' | while read file;
do
ffmpeg -i $file ...
done
The problem: Instead of the while loop waiting ffmpeg to complete, it continues iterate through the loop. The end result is, files not getting processed. I need a way to have the current while loop iteration to wait until ffmpeg is complete before continuing to the next. Or alternatively a way to queue these items.
Edit: So The solution when iterating over a set of files is to pass the -nostdin param to ffmpeg. Hope this helps anyone else who might have a similar issue.
Also file --> $file was a copy/paste typo.

I realize I posted this a while ago but I found the solution. Thanks for all of the responses. Providing the -nostdin param to ffmpeg will do the trick. It will process only the current file before moving onto the next file for processing.
ffmpeg's -nostdin option avoids attempting to read user input from stdin otherwise the video file itself is interpreted.
ffmpeg -i <filename> ... -nostdin
The best part about using the above is that you can continue to use verbosity in case an error is thrown in the output:
ffmpeg -i <filename> ... -nostdin -loglevel panic
OR if you would rather report the output to a file do so this way:
# Custom log name (optional). Helpful when multiple files are involved.
# FFREPORT=./${filename}-$(date +%h.%m.%s).log
ffmpeg -i <filename> ... -nostdin -report
You can also use a combination of the two as well. Also thanks #Barmar for the solution!

I think that this is as simple as you missing the $ before file.
find . -type f -name '*.mkv' | while read file;
do
ffmpeg -i $file ...
done

This is good for you?
find . -type f -name '*.mkv' -exec ffmpeg -i {} \;

I'm a vicious answer snatcher. I found one:
ffmpeg -i $file &
wait $!
Thanks to puchu, here: apply ffmpeg to many files

Related

Bash script that lists files in a directory doesn't work

I made a bash script because I need to convert a lot of files in a directory from .MOV to .mp4 format.
I created this script for the purpose:
#!/bin/bash
touch .lista
ls -1 "$1" | grep -i .MOV > .lista
list= `pwd`/.lista
cd "$1"
while read -r line;
do filename=${line%????}
ffmpeg -i "$line" -vcodec copy -acodec copy "$filename.mp4"; done < $list
rm .lista
This script is supposed to convert me each .MOV file into the directory indicated by $1, but it doesn't work, it converts me only one file, then it terminates. I can't understand why. What's wrong with that?
It's better to simply loop using globs:
for file in "$1"/*.MOV; do
ffmpeg -i "$file" ... "${file%.*}.mp4"
done
Why you shouldn't parse the output of ls.
Do them all fast and succinctly in parallel with GNU Parallel like this:
parallel --dry-run ffmpeg -i {} -vcodec copy -acodec copy {.}.mp4 ::: movies/*MOV
Sample Output
ffmpeg -i movies/a.MOV -vcodec copy -acodec copy movies/a.mp4
ffmpeg -i movies/b.MOV -vcodec copy -acodec copy movies/b.mp4
If that looks good, do it again but without --dry-run.
Note how easily GNU Parallel takes care of all the loops, all the quoting and changing the extension for you.
Your code is working for me. I cannot see any error. But I can suggest you a better approach. Don't use ls to get the filenames, it is not a good idea. Also, you can avoid changing dir.
#!/bin/bash
for line in $(find "$1" -maxdepth 1 -type f -iname "*.mov")
do
ffmpeg -i "$line" -vcodec copy -acodec copy "${line%????}.mp4"
done
You don't need to start by touching the file. In any case, you don't need a file at all, you can use a for loop to iterate over the files returned by find directly. With find, I'm already selecting all the files in the specified folder that have the expected extension.
Here I add a one-liner that should avoid problems with spaces:
find "$1" -maxdepth 1 -type f -iname "*.mov" -print0 | xargs -0 -n 1 -I{} bash -c "F={}; ffmpeg -i \"\$F\" -vcodec copy -acodec copy \"\${F%.*}\".mp4"

FFmpeg script does not loop

I have multiple video and sub files inside a directory.
My aim is to embed subtitles into each video.
My instance in directory:
A.mp4
A.ass
B.mp4
B.ass
C.mp4
C.ass
.
.
Z.mp4
Z.ass
My script:
for f in *.mp4; do base=$(basename ${$f%.mp4}) ffmpeg -i $base.mp4 \
-vf "ass=$base.ass" 222$base.mp4; done;
It works only for the first file, when the process finishes, it asks me to overwrite output file. Could you please explain why it happens?
base is being interpreted as $(basename ${$f%.mp4}) ffmpeg -i $base.mp4 (at least), and not just $(basename ${$f%.mp4}).
Lazy solution
Add a semicolon:
for f in *.mp4; do base=$(basename "$f" .mp4); ffmpeg -i "$base.mp4" -vf "ass=$base.ass" "222$base.mp4"; done
Alternatively, break the command into multiple lines to separate base from the command. I also made some additional minor changes not related to the main issue (proper quoting, corrected baseline usage, stream copy audio instead of re-encode).
Check your original command/script with shellcheck for a more in-depth explanation and solutions.
Better solution
Eliminate the extraneous basename process and use parameter expansion instead:
for f in *.mp4; do ffmpeg -i "$f" -vf "ass=${f%.*}.ass" -c:a copy "222${f%.*}.mp4"; done
This is more efficient and shorter.

Convert files on directories that are not always there

I've got a script that runs when a torrent download is finished to see if there are FLAC audio files and if yes convert them to MP3. Until today I've used:
for file in "$torrentpath"/"$torrentname"/*.flac
do
ffmpeg -i "$file" -qscale:a 0 "${file[#]/%flac/mp3}"
done
But I realised that when a torrent comes containing sub-directories the script is useless. I've tried messing around for the past few days with "find" and "if" and other ways but I can't really see the answer. I know it's there.
The script should just test if there are sub-dirs and execute ffmpeg on those, otherwise directly go with the conversion.
Any little hint will be appreciated.
to handle arbitrary subdirectories in bash:
shopt -s globstar nullglob
for file in "$torrentpath/$torrentname"/**/*.flac
do ...
find "$torrentpath"/"$torrentname" -name '*.flac' -print | while read file; do
ffmpeg -i "$file" -qscale:a 0 "${file[#]/%flac/mp3}"
done

Using xargs and sed to reprocess avi files

I have a bunch of AVI's that I'd like to reprocess to h264 MKVs. The command I'm trying to use is:
find . -name "*.avi" | xargs -I '{}' ffmpeg -i {} -vcodec libx264 `echo {} | sed 's/avi/mkv/'`
However, it does not work as expected. Adding -t to xargs shows that the command being run is (given a directory with file1.avi)
ffmpeg ./file1.avi -vcodec libx264 ./file1.avi
I'm not sure why the sed command isn't getting processed correctly. ffmpeg fails because it does not overwrite by default.
The AVIs are all in sub-directories so I don't really want to do a for loop for each subfolder and find / xargs would be a much better solution. Also, I don't really want to rename everything after this is done.
The reason why this is not working is because everything between ` ` is executed before the xargs execution. But if you quote it in ' ', then it is going to be passed correctly, but xargs is not going to parse or expand it.
But you can pipe to the bash function!
convertFile() {
while read -r filename; do
newFilename="${filename:0:-3}mkv"
ffmpeg "$filename" -vcodec libx264 "$newFilename"
done
}
find . -name "*.avi" | convertFile
Please note that instead of sed I am using bash string manipulations. The reason for that is that your file can sometimes contain avi word inside, like gravity. That is why more fail-proof way was used.
find *.avi | while read f; do
ffmpeg "$f" -vcodec libx264 "${f%.avi}.mkv"
done
and i split the lines to not scare people, but in real life it'd be
find *.avi|while read f;do ffmpeg "$f" -vcodec libx264 "${f%.avi}.mkv"; done
because this is one-liner territory.
You can use the following command instead :
find *.avi | awk '{print "\""$0"\"", "-vodec libx264","\""$0"\""}' | sed 's/\(.*\)avi\"$/\1mkv\"/g' | xargs ffmpeg -i
Of course this has a lot of escape characters. It isn't a script, it's a command-line...

Bash Script to convert all flv file in a directory to mp3

This is my code so far.
#!/bin/bash
#James Kenaley
#Flv to Mp3 directory converter
find /home/downloads -iname "*.flv" | \
while read I;
do
`ffmpeg -i ${I} -acodec copy ${I/%.flv/.mp3}`
echo "$I has been converted"
done
but its picking up white spaces in the names of the flv files and throws a error saying its not in the directory. how do make it use the whole file name and not the just the first word before the space?
ffmpeg runs in forked threads, so simple batching can give weird behaviours. If you are running ffmpeg in the suggested batch loop, you should control your command and command-error output, so that it doesn't interfere.
If you run this and are getting every other item converted properly, but errors on the rest, try using this ffmpeg call in the loop:
ffmpeg -y -i "${I}" -acodec mp3 -ar 22050 -f wav "${I/%.3gp/.mp3}" > /dev/null & 2> /dev/null
Notice the > dev/null & 2> /dev/null on the end. This pipes the command output, and command error output into oblivion. Then the script works.
One should note too that the program output will look strangely disorganized, with multiple files compressing at the same time. The results will be correct.
[EDIT: NOTE THE -y THAT I HAVE, THIS MAKES FFMPEG OVERWRITE EXISTING MP3 FILES]
Try this:
`ffmpeg -i "${I}" -acodec copy "${I/%.flv/.mp3}"`
Use quotes. And don't use backquotes.
ffmpeg -i "${I}" -acodec copy "${I%.flv}".mp3
Either call a short script, to do conversion and renaming in one pass:
adhoc.sh:
$file="$1"
ffmpeg -i "$file" -acodec copy "${file/%.flv/.mp3}"
call it:
find /home/downloads -iname "*.flv" -exec ./adhoc.sh {} ";" -ls
or convert:
find /home/downloads -iname "*.flv" -exec ffmpeg -i {} -acodec copy {}.mp3 ";" -ls
and rename later:
rename 's/.flv.mp3/.mp3/' /home/downloads/*.flv.mp3
Rename is part of a perl package which might need installation.

Resources