Batch converting videos in folders and sub folders - bash

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

Related

How to use bash string formatting to reverse date format?

I have a lot of files that are named as: MM-DD-YYYY.pdf. I want to rename them as YYYY-MM-DD.pdf I’m sure there is some bash magic to do this. What is it?
For files in the current directory:
for name in ./??-??-????.pdf; do
if [[ "$name" =~ (.*)/([0-9]{2})-([0-9]{2})-([0-9]{4})\.pdf ]]; then
echo mv "$name" "${BASH_REMATCH[1]}/${BASH_REMATCH[4]}-${BASH_REMATCH[3]}-${BASH_REMATCH[2]}.pdf"
fi
done
Recursively, in or under the current directory:
find . -type f -name '??-??-????.pdf' -exec bash -c '
for name do
if [[ "$name" =~ (.*)/([0-9]{2})-([0-9]{2})-([0-9]{4})\.pdf ]]; then
echo mv "$name" "${BASH_REMATCH[1]}/${BASH_REMATCH[4]}-${BASH_REMATCH[3]}-${BASH_REMATCH[2]}.pdf"
fi
done' bash {} +
Enabling the globstar shell option in bash lets us do the following (will also, like the above solution, handle all files in or below the current directory):
shopt -s globstar
for name in **/??-??-????.pdf; do
if [[ "$name" =~ (.*)/([0-9]{2})-([0-9]{2})-([0-9]{4})\.pdf ]]; then
echo mv "$name" "${BASH_REMATCH[1]}/${BASH_REMATCH[4]}-${BASH_REMATCH[3]}-${BASH_REMATCH[2]}.pdf"
fi
done
All three of these solutions uses a regular expression to pick out the relevant parts of the filenames, and then rearranges these parts into the new name. The only difference between them is how the list of pathnames is generated.
The code prefixes mv with echo for safety. To actually rename files, remove the echo (but run at least once with echo to see that it does what you want).
A direct approach example from the command line:
$ ls
10-01-2018.pdf 11-01-2018.pdf 12-01-2018.pdf
$ ls [0-9]*-[0-9]*-[0-9]*.pdf|sed -r 'p;s/([0-9]{2})-([0-9]{2})-([0-9]{4})/\3-\1-\2/'|xargs -n2 mv
$ ls
2018-10-01.pdf 2018-11-01.pdf 2018-12-01.pdf
The ls output is piped to sed , then we use the p flag to print the argument without modifications, in other words, the original name of the file, and s to perform and output the conversion.
The ls + sed result is a combined output that consist of a sequence of old_file_name and new_file_name.
Finally we pipe the resulting feed through xargs to get the effective rename of the files.
From xargs man:
-n number Execute command using as many standard input arguments as possible, up to number arguments maximum.
You can use the following command very close to the one of klashxx:
for f in *.pdf; do echo "$f"; mv "$f" "$(echo "$f" | sed 's#\(..\)-\(..\)-\(....\)#\3-\2-\1#')"; done
before:
ls *.pdf
12-01-1998.pdf 12-03-2018.pdf
after:
ls *.pdf
1998-01-12.pdf 2018-03-12.pdf
Also if you have other pdf files that does not respect this format in your folder, what you can do is to select only the files that respect the format: MM-DD-YYYY.pdf to do so use the following command:
for f in `find . -maxdepth 1 -type f -regextype sed -regex './[0-9]\{2\}-[0-9]\{2\}-[0-9]\{4\}.pdf' | xargs -n1 basename`; do echo "$f"; mv "$f" "$(echo "$f" | sed 's#\(..\)-\(..\)-\(....\)#\3-\2-\1#')"; done
Explanations:
find . -maxdepth 1 -type f -regextype sed -regex './[0-9]\{2\}-[0-9]\{2\}-[0-9]\{4\}.pdf this find command will look only for files in the current working directory that respect your syntax and extract their basename (remove the ./ at the beginning, folders and other type of files that would have the same name are not taken into account, other *.pdf files are also ignored.
for each file you do a move and the resulting file name is computed using sed and back reference to the 3 groups for MM,DD and YYYY
For these simple filenames, using a more verbose pattern, you can simplify the body of the loop a bit:
twodigit=[[:digit:]][[:digit:]]
fourdigit="$twodigit$twodigit"
for f in $twodigit-$twodigit-$fourdigit.pdf; do
IFS=- read month day year <<< "${f%.pdf}"
mv "$f" "$year-$month-$day.pdf"
done
This is basically #Kusalananda's answer, but without the verbosity of regular-expression matching.

HandBrakeCLI bash script convert all videos in a folder

Firstly, I searched around for my problem. But none can solve it.
I want to convert all videos file in a directory and the output will be saved in another directory. I got a bash script from somewhere I dont remember.
#!/bin/bash
SRC="/home/abc/public_html/filex/store/vids/toriko/VIDEOS HERE"
DEST="/home/abc/public_html/filex/store/vids/toriko/51-100"
DEST_EXT=mp4
HANDBRAKE_CLI=HandBrakeCLI
PRESET="iPhone & iPod Touch"
for FILE in "`ls $SRC`"
do
filename=$(basename $FILE)
extension=${filename##*.}
filename=${filename%.*}
$HANDBRAKE_CLI -i "$SRC"/$FILE -o "$DEST"/"$filename".$DEST_EXT "$PRESET"
done
the problem is, the output of the file will be without filename.. only ".mp4".
and, there is only 1 file generated.. means, from 50 videos in the folder, only 1 files generated with name ".mp4" and after that, HandBrakeCLI exit.
can anyone fix my code?
I got no experince in bash coding.. so, the right script giiven will be appreciate :)
Your line
for FILE in "`ls $SRC`"
effectively creates only one iteration where FILE contains the list of the files (and it is not able to handle the space in $SRC). Better replace it with
for FILE in "$SRC"/*
Example:
$ ls test
1.txt 2.txt
$ SRC=test; for f in "`ls $SRC`" ; do echo $f; done
1.txt 2.txt
$ SRC=test; for f in "$SRC"/* ; do echo $f; done
test/1.txt
test/2.txt
Side note: you can have a space in there with no problem
$ ls "the test"
1.txt 2.txt
$ SRC="the test"; for f in "$SRC"/* ; do echo $f; done
the test/1.txt
the test/2.txt
I tried this script, and others like it, but I wanted to convert recursive directory tree's and have files placed in the same directory with .mp4 extension and delete .avi files, after much trial and error I gave up on this code and searched for a new code, id like to credit
http://www.surlyjake.com/blog/2010/08/10/script-to-run-handbrake-recursively-through-a-folder-tree/
For the original code!
Here is my modified script, barely modified BTW this script is short, sweet and easy to understand.
#!/bin/bash
# This Script Goes in Root Folder of TV show -- Example Folder Structure
# /Stargate/Season\ 1/Epiosde.avi
# /Stargate/Season\ 2/Epiosde.avi
# /Stargate/handbrake_folder.script
# Outputs all Files back inside same dir's and does all folders inside Startgate DIR
# /Stargate/Season\ 1/Epiosde.mp4
# /Stargate/Season\ 2/Epiosde.mp4
# PRESET = -o flags for CLI can be got from GUI under Activity Log or from https://trac.handbrake.fr/wiki/CLIGuide OR you can use actual Presets!
# PRESET="iPhone & iPod Touch"
PRESET="--modulus 2 -e x264 -q 20 --vfr -a 1 -E ac3 -6 5point1 -R Auto -B 384 -D 0 --gain 0 --audio-fallback ac3 --encoder-preset=veryfast --encoder-level="5.2" --encoder-profile=high --verbose=1"
if [ -z "$1" ] ; then
TRANSCODEDIR="."
else
TRANSCODEDIR="$1"
fi
find "$TRANSCODEDIR"/* -type f -name "*.avi" -exec bash -c 'HandBrakeCLI -i "$1" -o "${1%\.*}".mp4 --preset="$PRESET"' __ {} \; && find . -name '*.avi' -exec rm -r {} \;
BE WARNED: THIS WILL CONVERT THEN DELETE ALL .AVI FILES ABOVE THE SCRIPT IN FILE TREE!
Feel free to remove the
[-name "*.avi"] & [&& find . -name '*.avi' -exec rm -r {} \;]
to disable only converting .avi and removal of .avi or modify to suite another extension.
I have found the solution:
#!/bin/bash
SRC="/home/abc/public_html/filex/store/vids/toriko/VIDEOS HERE"
DEST="/home/abc/public_html/filex/store/vids/toriko/51-100"
DEST_EXT=mp4
HANDBRAKE_CLI=HandBrakeCLI
for FILE in "$SRC"/*
do
filename=$(basename "$FILE")
extension=${filename##*.}
filename=${filename%.*}
$HANDBRAKE_CLI -i "$FILE" -o "$DEST"/"$filename".$DEST_EXT
done
I just tried using this script with the modification suggested above. I found I need to to put double quotes around the two uses of $FILE in order to handle file names with spaces.
So...
filename=$(basename "$FILE")
and
$HANDBRAKE_CLI -i "$SRC"/"$FILE" -o "$DEST"/"$filename".$DEST_EXT "$PRESET"
I'd rather prefer this solution:
#!/bin/bash
SRC="$1"
DEST="$2"
EXT='mp4'
PRESET='iPhone & iPod Touch'
#for FILE in "`ls $SRC`"; do
for FILE in `find . -type f`; do
FILE=$(basename "$FILE")
filename=$(basename "$FILE")
extension=${filename##*.}
filename=${filename%.*}
HandBrakeCLI -i "$SRC"/$FILE -o "$DEST"/"$filename"."$EXT" "$PRESET"
done

Rename file names in current directory and all subdirectories

I have 4 files with the following names in different directories and subdirectories
tag0.txt, tag1.txt, tag2.txt and tag3.txt
and wish to rename them as tag0a.txt, tag1a.txt ,tag2a.txt and tag3a.txt in all directories and subdirectories.
Could anyone help me out using a shell script?
Cheers
$ shopt -s globstar
$ rename -n 's/\.txt$/a\.txt/' **/*.txt
foo/bar/tag2.txt renamed as foo/bar/tag2a.txt
foo/tag1.txt renamed as foo/tag1a.txt
tag0.txt renamed as tag0a.txt
Remove -n to rename after checking the result - It is the "dry run" option.
This can of course be done with find:
find . -name 'tag?.txt' -type f -exec bash -c 'mv "$1" ${1%.*}a.${1##*.}' -- {} \;
Here is a posix shell script (checked with dash):
visitDir() {
local file
for file in "$1"/*; do
if [ -d "$file" ]; then
visitDir "$file";
else
if [ -f "$file" ] && echo "$file"|grep -q '^.*/tag[0-3]\.txt$'; then
newfile=$(echo $file | sed 's/\.txt/a.txt/')
echo mv "$file" "$newfile"
fi
fi
done
}
visitDir .
If you can use bashisms, just replace the inner IF with:
if [[ -f "$file" && "$file" =~ ^.*/tag[0-3]\.txt$ ]]; then
echo mv "$file" "${file/.txt/a.txt}"
fi
First check that the result is what you expected, then possibly remove the "echo" in front of the mv command.
Using the Perl script version of rename that may be on your system:
find . -name 'tag?.txt' -exec rename 's/\.txt$/a$&/' {} \;
Using the binary executable version of rename:
find . -name 'tag?.txt' -exec rename .txt a.txt {} \;
which changes the first occurrence of ".txt". Since the file names are constrained by the -name argument, that won't be a problem.
Is this good enough?
jcomeau#intrepid:/tmp$ find . -name tag?.txt
./a/tag0.txt
./b/tagb.txt
./c/tag1.txt
./c/d/tag3.txt
jcomeau#intrepid:/tmp$ for txtfile in $(find . -name 'tag?.txt'); do \
mv $txtfile ${txtfile%%.txt}a.txt; done
jcomeau#intrepid:/tmp$ find . -name tag*.txt
./a/tag0a.txt
./b/tagba.txt
./c/d/tag3a.txt
./c/tag1a.txt
Don't actually put the backslash into the command, and if you do, expect a '>' prompt on the next line. I didn't put that into the output to avoid confusion, but I didn't want anybody to have to scroll either.

Bash rename extension recursive

I know there are a lot of things like this around, but either they don't work recursively or they are huge.
This is what I got:
find . -name "*.so" -exec mv {} `echo {} | sed s/.so/.dylib/` \;
When I just run the find part it gives me a list of files. When I run the sed part it replaces any .so with .dylib. When I run them together they don't work.
I replaced mv with echo to see what happened:
./AI/Interfaces/C/0.1/libAIInterface.so ./AI/Interfaces/C/0.1/libAIInterface.so
Nothing is replaced at all!
What is wrong?
This will do everything correctly:
find -L . -type f -name "*.so" -print0 | while IFS= read -r -d '' FNAME; do
mv -- "$FNAME" "${FNAME%.so}.dylib"
done
By correctly, we mean:
1) It will rename just the file extension (due to use of ${FNAME%.so}.dylib). All the other solutions using ${X/.so/.dylib} are incorrect as they wrongly rename the first occurrence of .so in the filename (e.g. x.so.so is renamed to x.dylib.so, or worse, ./libraries/libTemp.so-1.9.3/libTemp.so is renamed to ./libraries/libTemp.dylib-1.9.3/libTemp.so - an error).
2) It will handle spaces and any other special characters in filenames (except double quotes).
3) It will not change directories or other special files.
4) It will follow symbolic links into subdirectories and links to target files and rename the target file, not the link itself (the default behaviour of find is to process the symbolic link itself, not the file pointed to by the link).
for X in `find . -name "*.so"`
do
mv $X ${X/.so/.dylib}
done
A bash script to rename file extensions generally
#/bin/bash
find -L . -type f -name '*.'$1 -print0 | while IFS= read -r -d '' file; do
echo "renaming $file to $(basename ${file%.$1}.$2)";
mv -- "$file" "${file%.$1}.$2";
done
Credits to aps2012.
Usage
Create a file e.g. called ext-rename (no extension, so you can run it like a command) in e.g. /usr/bin (make sure /usr/bin is added to your $PATH)
run ext-rename [ext1] [ext2] anywhere in terminal, where [ext1] is renaming from and [ext2] is renaming to. An example use would be: ext-rename so dylib, which will rename any file with extension .so to same name but with extension .dylib.
What is wrong is that
echo {} | sed s/.so/.dylib/
is only executed once, before the find is launched, sed is given {} on its input, which doesn't match /.so/ and is left unchanged, so your resulting command line is
find . -name "*.so" -exec mv {} {}
if you have Bash 4
#!/bin/bash
shopt -s globstar
shopt -s nullglob
for file in /path/**/*.so
do
echo mv "$file" "${file/%.so}.dylib"
done
He needs recursion:
#!/bin/bash
function walk_tree {
local directory="$1"
local i
for i in "$directory"/*;
do
if [ "$i" = . -o "$i" = .. ]; then
continue
elif [ -d "$i" ]; then
walk_tree "$i"
elif [ "${i##*.}" = "so" ]; then
echo mv $i ${i%.*}.dylib
else
continue
fi
done
}
walk_tree "."

How do I copy directory structure containing placeholders

I have the situation, where a template directory - containing files and links (!) - needs to be copied recursively to a destination directory, preserving all attributes. The template directory contains any number of placeholders (__NOTATION__), that need to be renamed to certain values.
For example template looks like this:
./template/__PLACEHOLDER__/name/__PLACEHOLDER__/prog/prefix___FILENAME___blah.txt
Destination becomes like this:
./destination/project1/name/project1/prog/prefix_customer_blah.txt
What I tried so far is this:
# first create dest directory structure
while read line; do
dest="$(echo "$line" | sed -e 's#__PLACEHOLDER__#project1#g' -e 's#__FILENAME__#customer#g' -e 's#template#destination#')"
if ! [ -d "$dest" ]; then
mkdir -p "$dest"
fi
done < <(find ./template -type d)
# now copy files
while read line; do
dest="$(echo "$line" | sed -e 's#__PLACEHOLDER__#project1#g' -e 's#__FILENAME__#customer#g' -e 's#template#destination#')"
cp -a "$line" "$dest"
done < <(find ./template -type f)
However, I realized that if I want to take care about permissions and links, this is going to be endless and very complicated. Is there a better way to replace __PLACEHOLDER__ with "value", maybe using cp, find or rsync?
I suspect that your script will already do what you want, if only you replace
find ./template -type f
with
find ./template ! -type d
Otherwise, the obvious solution is to use cp -a to make an "archive" copy of the template, complete with all links, permissions, etc, and then rename the placeholders in the copy.
cp -a ./template ./destination
while read path; do
dir=`dirname "$path"`
file=`basename "$path"`
mv -v "$path" "$dir/${file//__PLACEHOLDER__/project1}"
done < <(`find ./destination -depth -name '*__PLACEHOLDER__*'`)
Note that you'll want to use -depth or else renaming files inside renamed directories will break.
If it's very important to you that the directory tree is created with the names already changed (i.e. you must never see placeholders in the destination), then I'd recommend simply using an intermediate location.
First copy with rsync, preserving all the properties and links etc.
Then change the placeholder strings in the destination filenames:
#!/bin/bash
TEMPL="$PWD/template" # somewhere else
DEST="$PWD/dest" # wherever it is
mkdir "$DEST"
(cd "$TEMPL"; rsync -Hra . "$DEST") #
MyRen=$(mktemp)
trap "rm -f $MyRen" 0 1 2 3 13 15
cat >$MyRen <<'EOF'
#!/bin/bash
fn="$1"
newfn="$(echo "$fn" | sed -e 's#__PLACEHOLDER__#project1#g' -e s#__FILENAME__#customer#g' -e 's#template#destination#')"
test "$fn" != "$newfn" && mv "$fn" "$newfn"
EOF
chmod +x $MyRen
find "$DEST" -depth -execdir $MyRen {} \;

Resources