Bash loop over directory tree with ffmpeg, wrong spaces? [duplicate] - bash

This question already has answers here:
Iterate over a list of files with spaces
(12 answers)
How to loop through file names returned by find?
(17 answers)
Closed 5 years ago.
I am trying to interate over multiple video files that are grouped in directories, and ffmpeg returns errors about paths, it seems like paths are broken, end at first space. Can you point me too what is the problem here? Files and directories have spaces.
$ for f in $(find -type f -name *.mkv); do ffmpeg -n -i "$f" -c:v copy "~/Pobrane/$f" ; done
Loop splits paths by space and takes words as entries. How to fix this?
$ for f in $(find -type f -name *.mkv); do echo "$f"; done
./homeworks/2017-04-03
00-54-57
homework3b.mkv
./homeworks/2017-04-03
00-21-36
homework1.mkv
./homeworks/2017-04-03

Replacing the Loop
Use
find -type f -name '*.mkv' -exec ffmpeg -n -i {} -c:v copy ~/Pobrane/{} \;
-exec executes the following ffmpeg command for each of the found paths, replacing {} with the current path. The ; informs find that the ffmpeg command ends there.
Quote '*.mkv' in order to pass the literal string to find, which then searches for files ending with *.mkv. If you do not quote the string and have some mkv files laying around in your working directory, the shell will expand the unquoted *.mkv resulting in find -type f -name firstFile.mkv secondFile.mkv ... before starting find.
Do not quote ~. The unquoted ~ expands to your home directory (probably /home/yourname) but the quoted '~' is a directory/file with the literal name ~ .
Creating Parent Directories
How would I add mkdir -p before the ffmpeg call?
You could wrap the mkdir and ffmpeg in one function and execute the function:
myFunction() {
mkdir -p "$(dirname "$1")"
ffmpeg -n -i "$1" -c:v copy ~/Pobrane/"$1"
}
export -f myFunction
find -type f -name '*.mkv' -exec bash -c 'myFunction "$0"' {} \;
or use a loop:
find . -type f -iname "*.txt" -print0 | while IFS= read -r -d $'\0' file; do
mkdir -p "$(dirname "$file")"
ffmpeg -n -i "$file" -c:v copy ~/Pobrane/"$file"
done

Related

mac terminal ffmpeg batch recursive conversion preserving directory structure

i'm using ffmpeg on mac to batch convert .flv to .mp4 files. i'm trying to find all files in subdirectories of the current directory and save new files in the same directory.
for instance starting with:
subdirectory1/video1.flv
subdirectory1/video2.flv
subdirectory2/video1.flv
and ending with
subdirectory1/video1.mp4
subdirectory1/video2.mp4
subdirectory2/video1.mp4
i've gotten this far but can't figure out how to save with preserved recursive directories
for i in `find -name . "*.flv"`; do ffmpeg -i "$i" "${i%.*}.mp4"; done
maybe there was a better way but this ultimately worked for my purposes. i had to rename the files and directories to remove spaces and rework the find command. the xargs inclusion tried to account for spaces but it didnt work
so i removed spaces from directories with this:
for f in *; do mv "$f" `echo $f | tr ' ' '_'`; done
and removed spaces from filenames with this
find . -type f -name "* *.flv" -exec bash -c 'mv "$0" "${0// /_}"' {} \;
then this command recursively reencoded my flv files and saved in their directory
for i in `find . -name "*.flv" -print0| xargs -0`; do ffmpeg -i "$i" -c:v libx264 -f mp4 "${i%.*}.mp4"; done

How do handle filename space and Character with FIND command

Good day all,
I am working on a bash script to merge multiple mp3 files to one. The code is working fine but cannot handle file name with space or Characters. Below is the code. Can you please tell me what I'm doing wrong. Thanks
for file in $(find . -type f -name "*.mp3" -print0 | xargs -0 ); do
ffmpeg -i "concat:intro.mp3|"$file"|outro.mp3" -acodec copy "${file%.mp3}-".mp3;
done
find has an -exec flag that allows you to call scripts with the search results.
e.g. creating a simple helper-script addxtros.sh:
#!/bin/sh
infile=$1
outfile=${infile%.mp3}-.mp3
ffmpeg -i "concat:intro.mp3|${infile}|outro.mp3" -acodec copy "${outfile}"
you can use it like:
find . -type f -name "*.mp3" -exec ./addxtros.sh {} ";"
read builtin with -d followed by empty argument to use NUL caracter as input record delimiter
while IFS= read -r -d '' file; do
ffmpeg -i "concat:intro.mp3|$file|outro.mp3" -acodec copy "${file%.mp3}-".mp3;
done < <(find . -type f -name "*.mp3" -print0)
You can use find together with bash -c command (that allows passing 2 arguments to ffmpeg):
find . -type f -name "*.mp3" -exec bash -c 'ffmpeg -i "concat:intro.mp3|$1|outro.mp3" -acodec copy "${1%.mp3}-.mp3"' _ {} \;

Replace files name in a directory using bash

I'm writing a bash script for copying an existing directory into another one and there modify all the filenames containing the name of the older directory replacing it by the name of the new one.
I'm using this command, which it works fine
find . -depth -name '*foo*' -execdir bash -c 'for f; do mv -i "$f" "${f//foo/bar}"; done' bash {} +
However, as I must do this for several destination directories, I want the 'bar' string in the above command to be contained into a variable, so I could change the content of the variable in a loop (iterating an array) and copying and replacing these files in a batch.
What changes should I do to the command? I've tried the following, but it does not work
app="Workflow2";
strChange="f//Workflow1/"${app};
find . -depth -name '*Workflow1*' -execdir bash -c 'for f; do mv -i "$f" "${strChange}"; done' bash {} +
The command must be modified as the following
$(find . -depth -name '*Workflow1*' -execdir bash -c 'for f; do mv -i "$f" "${'${strChange}'}"; done' bash {} +)

while loop stops after first iteration in BASH [duplicate]

This question already has answers here:
While loop stops reading after the first line in Bash
(5 answers)
Closed 1 year ago.
I wrote the script that has to convert *.avi files to mp4 format.
However "while" loop stops after first iteration.
#!/bin/bash
shopt -s lastpipe
cd <some_directory>
find . -name *.avi -type f |
while read -r avi
do
/usr/bin/HandBrakeCLI -i "${avi}" -o "${avi%.avi}.mp4" -f mp4 -m -O -e x264 -q 20 --vfr \
# &> /dev/null
if [ "$?" -eq 0 ]
then
echo "${avi} was converted successfully"
rm "${avi}"
else
echo "${avi} was not converted"
break
fi
done
This part is wrong: find . -name *.avi -type f
The shell is expanding the wildcard before find starts, so the find command looks like:
find . -name a.avi b.avi.c.avi d.avi ... -type f
I'm surprised you didn't notice an error message, like "find: paths must precede expression: b.avi"
You need to protect the asterisk from the shell so find can to its own expansion. Pick one of
find . -name \*.avi -type f
find . -name '*.avi' -type f
You don't mention if you're on a GNU system or not. You're while loop is at risk of being tripped up by filenames with leading or trailing whitespace. Try this:
find . -name \*.avi -type f -print0 | while read -rd '' avi; do ...
HandBrakeCLI could also be reading input that it makes your loop end after the first instance is called. Since you're using bash, you can use process substitution with redirected input to another file descriptor. In this example with use 4:
while read -ru 4 avi; do
...
done 4< <(exec find . -name *.avi -type f)
My preferred version too is to use readarray. It's quite enough if you don't target irregular filenames where they have newlines:
readarray -t files < <(exec find . -name *.avi -type f)
for avi in "${files[#]}"; do
...
done
Another way perhaps is to redirect input of HandBrakeCLI to /dev/null:
</dev/null /usr/bin/HandBrakeCLI ...
Other suggestions:
Quote your -name pattern: '*.avi'
Use IFS= to prevent stripping of leading and trailing spaces: IFS= while read ...

Bash script to apply operation to each file found by find

I'm trying to execute an operation to each file found by find - with a specific file extension (wma). For example, in python, I would simply write the following script:
for file in os.listdir('.'):
if file.endswith('wma'):
name = file[:-4]
command = "ffmpeg -i '{0}.wma' '{0}.mp3'".format(name)
os.system(command)
I know I need to execute something similar to
find -type f -name "*.wma" \
exec ffmpeg -i {}.wma {}.mp3;
But obviously this isn't working or else I wouldn't be asking this question =]
Most of the time it's better to use read when parsing input than doing word splitting with for and depending on IFS as there's risk with unexpected pathname expansion.
while IFS= read -u 4 -r LINE; do
ffmpeg -i "$LINE" "${LINE%.*}.mp3"
done 4< <(exec find -type f -name '*.wma')
Or use readarray (Bash 4.0+)
readarray -t FILES < <(exec find -type f -name '*.wma')
for FILE in "${FILES[#]}"; do
ffmpeg -i "$FILE" "${FILE%.*}.mp3"
done
Sticking to the basics always gets the job done (does not handle spaces in filenames):
for f in $(find "." -type f -name "*.wma"); do ffmpeg -i "$f" "${f//wma/mp3}"; done
Starting from konsolebox's suggestions below, I've come up with this complete version:
find "." -type f -name "*.wma" | while read -d $'\n' f; do ffmpeg -i "$f" "${f//wma/mp3}"; done

Resources