while read with spaces in filenames - bash

On this .ogg files
$ tree
.
├── Disc 1 - 01 - Procrastination.ogg
├── Disc 1 - 02 - À carreaux !.ogg
├── Disc 1 - 03 - Météo marine.ogg
└── mp3
I try with a while loop to ffmpeg convert them to mp3 keeping spaces in filenames::
$ ls *.ogg | while read line; do ffmpeg -i "$line" mp3/"$line".mp3 ; done
But I get this error::
$ ls *.ogg | while read line; do ffmpeg -i "$line" mp3/"$line".mp3 ; done
...
Parse error, at least 3 arguments were expected, only 0 given
in string ' 1 - 02 - À carreaux !.ogg' ...
...
This report bash ffmpeg find and spaces in filenames even if it look similar is for a more complicate script and has no answer.
This ffmpeg not working with filenames that have whitespace only fix it when output is a http:// URL

Use find -print0 to get the NUL-separated list of files, instead of parsing ls output which is never a good idea:
#!/bin/bash
while read -d '' -r file; do
ffmpeg -i "$file" mp3/"$file".mp3 </dev/null
done < <(find . -type f -name '*.ogg' -print0)
You can use a simple glob to do this as well:
shopt -s nullglob # make glob expand to nothing in case there are no matching files
for file in *.ogg; do
ffmpeg -i "$file" mp3/"$file".mp3
done
See:
Why you shouldn't parse the output of ls(1)

You don't need a loop here; let find execute the command for you.
find . -type f -name '*.ogg' -exec ffmpeg -i {} mp3/{}.mp3 \;
Or, if you want to strip the .ogg extension from the result:
find . -type f -name '*.ogg' -exec sh -c 'ffmpeg -i "$1" mp3/"${1%.ogg}.mp3"' _ {} \;
Conversely, you can skip find altogether:
shopt -s extglob
for f in **/*.ogg; do
[[ -f $f ]] || continue
ffmpeg -i "$f" mp3/"${f%.ogg}.mp3"
done

Related

Running a bash script recursively and performing operations on all files within the subdirectories

I'm trying to convert flac files into wav files using ffmpeg. The flac files are located in various subdirectories.
/speech_files
/speech_files/201/speech1.flac
/speech_files/201/speech2.flac
/speech_files/44/speech45.flac
/speech_files/44/speech109.flac
/speech_files/66/speech200.flac
/speech_files/66/speech33.flac
What I want after the script runs is the following
/speech_files
/speech_files/201/speech1.wav
/speech_files/201/speech2.wav
/speech_files/44/speech45.wav
/speech_files/44/speech109.wav
/speech_files/66/speech200.wav
/speech_files/66/speech33.wav
I can get my script to work within one directory but I'm having a hard time getting it to run from the top level directory (speech_files) and work it's way through all the subdirectories. Below is the script I'm using.
#!/bin/bash
for f in "./"/*
do
filename=$(basename $f)
if [[ ($filename == *.flac) ]]; then
new_file=${filename%?????}
file_ext="_mono_16000.wav"
wav_file_ext=".wav"
ffmpeg -i $filename $new_shits$wav_file_ext
ffmpeg -i $new_file$wav_file_ext -ac 1 -ar 16000 $new_file$file_ext
rm -f $filename
rm -f $new_file$wav_file_ext
fi
done
Use find from the top level directory and filter by using *.flac.
for f in $(find . -name "*.flac"); do
echo "$f" # f points to each file
# do your logic here
done
Using bash only :
#!/bin/bash
DIR="/.../speech_files"
process() {
filename=$(basename "$1")
# ...
}
for f in n "${DIR}"/*/*.flac; do
process "$f"
done
Using find which is recursive and more efficient to do that kind of task to me :
find "${DIR}" -type f -a -iname "*.flac" -exec ... {} \;

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

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

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

How to pipe multiple files to ffmpeg?

I am trying to make a bash script that searches all subfolders on given path for .mov files and converts them with ffmpeg and outputs them in an destination folder, keeping the clip name.
I'm very new to scripting and I'm having a hard time finding out how to solve this.
So far I've tried using ls and find to output the filepaths, but have no idea how to pipe this to ffmpeg in the right way.
Any clues?
Edit:
got some sucess with this:
#!/bin/bash
echo "drop source folder: "
read source
echo "drop destination folder: "
read des
find "$source" -name '*.mov' -exec sh -c 'ffmpeg -i "$0" -vcodec prores -profile:v 0 -an "$des/${0%%.mov}.mov"' {} \;
exit;
but, the it seems to output to the source folder asking for a overwrite. How can i setup the parameters correctly so it outputs to the "destination folder" and keeps the filenames?
You could start with this:
#!/bin/bash
shopt -s extglob || {
echo "Unable to enable exglob."
exit 1
}
TARGETEXT='.avi'
TARGETPREFIX='/path/to/somewhere/' ## Make sure it ends with /.
while IFS= read -r FILE; do
BASE=${FILE##*/}
NOEXT=${BASE%.*}
TARGETFILEPATH=${TARGETPREFIX}${NOEXT}${TARGETEXT}
echo ffmpeg -i "$FILE" "$TARGETFILEPATH" ## Remove echo if it's already correct.
done < <(exec find -type f -name '*.mov') ## You could also use -iname '*.sh' instead.
Of course you could use a custom directory to search for the files:
find /path/to/directory -type f -name '*.mov'
something like this should do the job:
for f in *.mov; do ffmpeg -i "$f" -vcodec copy -acodec copy "/desination/${f%.mov}.mp4"; done

Batch converting videos in folders and sub folders

I have a huge collection of videos that all need to be converted into mp4. The folder structure is like this
Events
Chicago
Boston
San Fran
London Expo
Inside each event holds all of videos in either avi format or .mkv. I want them to be converted to the same file name. but with the mp4 extension.
My question is how do I loop through a folders sub folders, and also keep the file names because at the moment they have spaces in them.. Here is what I have at the moment.
sourcedir="$1"
destdir="$2"
cd "$sourcedir"
for i in `ls`; do
HandBrakeCLI -i "$i" -o "$destdir/${i%.*}.mp4" --preset="AppleTV"
echo $i
done
Phillips Code:
cd "$sourcedir"
echo "Directory: $sourcedir"
destdir = sourcedir
for subdir in *
do
if [[ -d "$subdir" ]]
then
for file in "$subdir"/*
do
HandBrakeCLI -i "$file" -o "${file%.*}.mp4" --preset=AppleTV
echo "$file"
done
fi
done
Use a nested loop, and don't use ls:
for subdir in "$sourcedir"/*
do
if [[ -d "$subdir" ]]
then
for file in "$subdir"/*
do
HandBrakeCLI -i "$file" -o "$destdir/${file%.*}.mp4" --preset=AppleTV
echo "$file"
done
fi
done
Another option is to use find:
find "$sourcedir" -maxdepth 2 -mindepth 2 -type f -exec bash -c 'HandBrakeCLI -i "$0" -o "'"$destdir"'/${0%.*}.mp4" --preset=AppleTV' '{}' ';' -print
Both solutions will work with filenames containing spaces or newlines.
Either use for i in "$sourcedir/*" (or since you've already done a cd there you could do for i in *).
or do find "$sourcedir" -type f | while read -r i (with this, the variable i will include the source directory name, so you'll have to strip that off using a brace expansion or basename).
change program to iVI link to iVi.
this program does the trcik, even better than handbrake...
bash 4
shopt -s globstar
destdir="/somewhere"
for file in **/*.mkv **/*.avi
do
HandBrakeCLI -i "$file" -o "$destdir/${file%.*}.mp4" --preset=AppleTV
done

Resources