Run command inside all folders and sub folders with specific extension - bash

Currently, I'm trying run a command on my linux server (Ubuntu 16.04) to reencode all .mp4 files. But I have a lot of files inside folders and subfolders, and I'm going to waste a lot of time running these commands manually
Here is my script:
for D in ./*; do
if [ -d "$D" ]; then
cd "$D"
for i in *.mp4; do
ffmpeg -i "$i" -codec copy -acodec copy -shortest -map 0:v -map 0:m:language:eng "${i%.*}.mp4"
done
cd ..
fi
done
But, I'm getting this error:
Error: ENOENT: no such file or directory, stat '*.mp4'
How can I solve this?
Thank you.

Use find for this. It handles sub-directory recursion for you. Like so:
find . -type f -name "*.mp4" -exec ffmpeg -i {} -codec copy -acodec copy -shortest -map 0:v -map 0:m:language:eng {} \; -print
find will automatically process the current directory and all sub-directories
-type f: to search only files
`-name "*.mp4": only files with mp4 extension
-exec COMMAND {} \;: execute that command on all found files. {} will be replaced by the file name of each file. \; to end the command.
-print: will print which file was processed
As far as your code goes, there is a possibility that a directory will not have any "*.mp4" files, hence the no such file or directory, stat '*.mp4' error when you hit such a directory.

Example (no effect, only do echo ...):
$ find \
. \
-type f \
-name '*.mp4' \
-exec sh -c '
for src in "$#"; do
dst="${src%.*}.mkv"
echo ffmpeg ... "$src" ... "$dst"
done
' sh {} + \
-print \
;
You must replace echo ffmpeg ... "$src" ... "$dst" with your command-line for production use. You can remove -print from the example.

Related

How do you convert to the same format an entire directory with subfolders using ffmpeg?

How to keep the file name and the extension the same and append _backup to the old file?
I have had tried this
find . -name "*.mp4" -exec bash -c 'for f; do ffmpeg -i "$f" -codec copy "${f%.*}.mp4"; done' -- {} +
but here the files would be overwritten.
I hope what I have requested is possible.
I suggest you append "_backup" to your input files first, then process the just renamed files with ffmpeg:
Simple for-loop to process files in current directory:
for f in *.mp4; do
mv "$f" "${f%.*}_backup.mp4"
ffmpeg -i "${f%.*}_backup.mp4" -c copy "$f"
done
#or single-line:
for f in *.mp4; do mv "$f" "${f%.*}_backup.mp4"; ffmpeg -i "${f%.*}_backup.mp4" -c copy "$f"; done
find to process files in current directory and sub directories:
find -name "*.mp4" -exec bash -c '
f="{}"
mv "$f" "${f%.*}_backup.mp4"
ffmpeg -i "${f%.*}_backup.mp4" -c copy "$f"
' \;
#or single-line:
find -name "*.mp4" -exec bash -c 'f="{}"; mv "$f" "${f%.*}_backup.mp4"; ffmpeg -i "${f%.*}_backup.mp4" -c copy "$f"' \;

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

Use parameter expansions in a command run from "find | xargs" to prevent output overwriting

I have this bash script that is looking for mp4 files in subfolders with certain names and saves frames of those videos as jpeg.
#!/bin/bash
find ../folder -type f -iname '*C00*.mp4' | xargs -I %% ffmpeg -i %% -vf fps=1 -q:v 3 "../frames/_${i%.*}_frame%d.jpg"
The problem is that everytime the script finishes one video the .jepg output files of the next videos are overwriting the existing ones.
How can I prevent that?
Here's a quick stab which creates a directory with the same name as the input file with any .mp4 extension trimmed off.
#!/bin/bash
find ../folder -type f -iname '*C00*.mp4' -print0 |
xargs -r0 sh -c 'for f; do
d="../frames/${f%.[Mm][Pp]4}"
mkdir "$d" || { echo "$d already exists" >&2; exit 123; }
ffmpeg -i "$f" -vf fps=1 -q:v 3 "$d/frame%d.jpg"
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

xargs - multiple commands and rm at the end

I'm trying to remove the file that I was working on previously but it's not letting me, please help. Here is the command I run:
find . -type f -name '*.flac' -print0 |
xargs -0i ffmpeg -i {} -acodec libmp3lame -ab 320k "{}.mp3" |
rm -rf {}
If you have GNU Parallel installed you can do:
find . -type f -name '*.flac' | parallel ffmpeg -i {} -acodec libmp3lame -ab 320k {.}.mp3 '&&' rm {}
It will run one ffmpeg process per CPU core.
To learn more watch the intro videos: http://pi.dk/1
You are piping the output of the ffmpeg call(s) into the rm command. Since ffmpeg produces no interesting output and rm does not read any input, this doesn't do anything.
I'm not sure what you're trying to do. I think you want to remove the flac file after processing. You have several choices: you can first convert all the ffmpeg files, then remove them all; or you can remove each file after it's been processed. I advise the latter, otherwise it will be difficult to only remove the flac file if the conversion succeeded.
Rather than use xargs, it's simpler to use find … -exec here. For each flac file, call ffmpeg, and then delete the file if ffmpeg succeeded. If your find doesn't have the -delete action, use -exec rm {} \; instead. Use an intermediate shell to construct the output file name.
find . -type f -name '*.flac' \
-exec sh -c 'ffmpeg -i "$0" -acodec libmp3lame -ab 320k "${0%.*}.mp3"' {} \; \
-delete
You can use the rm command inside the shell snippet instead.
find . -type f -name '*.flac' \
-exec sh -c 'ffmpeg -i "$0" -acodec libmp3lame -ab 320k "${0%.*}.mp3" && rm "$0"' {} \;
With some versions of find, if you want the output file to be called foo.flac.mp3, you can skip the intermediate shell.
find . -type f -name '*.flac' \
-exec ffmpeg -i {} -acodec libmp3lame -ab 320k {}.mp3 \; \
-delete
The command rm doesn't take input from standard input, so you need to send it input using xargs just like you did for ffmpeg
try this:
find . -type f -name '*.flac' -print0 |
xargs -0i bash -c 'ffmpeg -i {} -acodec libmp3lame -ab 320k \"{}.mp3" ; rm -rf {}'
This adds rm into the command xargs executes, essentially creating an inline shell script using bash -c.
First of all, "it's not letting me" is not the best way to describe the problem. You should post the error message or describe what happens.
Second, I don't quite get what you're trying to achieve, but you're not using rm with xargs, you just pipe something to rm. That's not how rm works.
find . -type f -name '*.flac' -print0 | xargs -0i rm "{}"
would probably work, for example. I'm not using rm -r here, because find only looks for files anyway.

Resources