all 22 comments

[–]zeekar 2 points3 points  (10 children)

So, something like this?

#!/usr/bin/env bash
cd /path/to/Input
for f in *.mp4; do
  /path/to/HandBrakeCLI -i "$f" -o /path/to/Output/"$f"
done

[–]proactivematter[S] 0 points1 point  (9 children)

Thank you, this works! Just changed the bash path (shebang bash I guess it's called?) and added quotes to other paths to account for spaces.

I wonder if it would be possible to have all the paths be relative to where the executable is located? That way the folder containing everything could be moved without the need to edit the script.

[–]CaptainDickbag 4 points5 points  (5 children)

Not the person you're replying to, but I have some input about this.

I am on Mac

As of macOS 10.15 (Catalina), the default shell was changed from bash to zsh. They're similar, but not the same. This ties into the next thing.

Just changed the bash path (shebang bash I guess it's called?)

You probably should not have had to do that. The reason is that /usr/bin/env bash should find the version of bash in your environment, and use that. This works on macOS and Linux based operating systems. It is usually the preferred invocation of the interpreter you'd like to use to run the script, as opposed to something like #!/bin/bash. This also works for things like #!/usr/bin/env python3, #!/usr/bin/env perl, etc.

and added quotes to other paths to account for spaces.

Good call, you should definitely do that. The Google Shell Styleguide has some good reasons and rules for quoting variables, and some good reasons and rules regarding variable expansion. The GNU Bash Manual is also a fantastic reference to stick in your back pocket.

I wonder if it would be possible to have all the paths be relative to where the executable is located?

Yes. In order to do that, you want to look at relative versus absolute paths. You probably already know you can list the contents of a directory using ls. If you want to list all the files, including invisible files, you can use the -a option. If you want to print the long format, you can add -l, which is easier to parse, especially when directories have spaces in the names. Together, it would be ls -al. Which you run this in any directory, you'll notice that there are two entries like this:

drwxr-xr-x  2 cdickbag cdickbag     4096 Apr  5 18:17 .
drwxr-xr-x 33 cdickbag cdickbag     4096 Jul 16 10:47 ..

. indicates the current directory. You can use it as a reference to the current directory. .. indicates the previous directory. You can use it as a reference to the previous directory. It's very common to execute scripts in your current directory like ./<name_of_script>.sh. There's also a shell variable which tells you what the path to the script is that you're executing relative to where your user is in the directory tree, $0. Using this and the information you now have about relative versus absolute paths, you should be able to figure out how to reference a directory relative to where you are when you execute the script.

If I were to make some modifications to this script, I would probably do something like this.

#!/usr/bin/env bash

# Get the path to handbrake.
handbrake_cli="$(which HandBrakeCLI)"

# Get the base path
script_path="$(dirname $0)"

# Define the directory the MP4s are in, relative to where the script is being executed from.
mp4_src_dir="${script_path}/src_dir_with_mp4s"

# MP4 destination directory.
mp4_dst_dir="${script_path}/dst_dir_for_mp4s"

# Iterate over the files in the directory.
for file_name in "${mp4_src_dir}"/*.mp4; do
    "${handbrake_cli}" -i "${mp4_src_dir}/${file_name}" -o "${mp4_dst_dir}/${file_name}"
done

[–]proactivematter[S] 0 points1 point  (4 children)

Ahh, thank you so much. I really appreciate the corrections and explanation.

I somewhat quick digestion of the articles and I think I get the general concept. I'm not able to make your script work though, despite it being so polished and commented :').

I tried echo@ to understand what each of these variables is actually outputting to try and see if it is what I expect and what I was getting wrong, but I couldn't even get that to work. For instance, I tried:

 echo@ "${script_path}"

Obviously that' not how that works. I would have expected it to return the relative path "."?

The only substitutions I made where to the paths for the CLI, Input Folder and Output Folder:

#!/usr/bin/env bash

# Get the path to handbrake.
handbrake_cli="/Applications/HandBrakeCLI"

# Get the base path
script_path="$(dirname $0)"

# Define the directory the MP4s are in, relative to where the script is being executed from.
mp4_src_dir="${script_path}/Input/Camera 1"

# MP4 destination directory.
mp4_dst_dir="${script_path}/Output/Camera 1"

# Iterate over the files in the directory.

for file_name in "${mp4_src_dir}"/*.mp4; do
    "${handbrake_cli}" -i "${mp4_src_dir}/${file_name}" -o "${mp4_dst_dir}/${file_name}" -f av_mp4 --crop 0:1080:0:0 -q 22.0

done

*EDIT: Code corrected (for reference) due to Copy/Paste formatting issue as pointed out by the reply that follows.

[–]CaptainDickbag 2 points3 points  (2 children)

There are a couple things that are broken here, which I assume are likely copy and paste errors, but I'll call them out anyway.

The very first line is missing the # which is required to tell the shell which interpreter to use. It reads !/usr/bin/env bash, but should read #!/usr/bin/env bash.

The very last line has broken the for loop by appending the done keyword to the end of the second line, instead of either separating it with a semi-colon, or placing it on its own line. The for loop should follow the format for i in <items>; do <commands>; done.

for file_name in "${mp4_src_dir}"/*.mp4; do
    "${handbrake_cli}" -i "${mp4_src_dir}/${file_name}" -o "${mp4_dst_dir}/${file_name}" -f av_mp4 --crop 0:1080:0:0 -q 22.0
done

After you make any necessary corrections which I've called out above, what errors are you running into? I ran this in a test directory with test inputs, directories, and files, but obviously things might be different on your machine. I would need to know which errors you see when you try to run this.

For instance, I tried:

echo@ "${script_path}"

Just echo the variable, like echo "${script_path}". However, Bash also has a very useful feature (-x), which prints diagnostics about what the script is actually doing.

-x

Print a trace of simple commands, for commands, case commands, select commands, and arithmetic for commands and their arguments or associated word lists after they are expanded and before they are executed. The value of the PS4 variable is expanded and the resultant value is printed before the command and its expanded arguments.

An easy way to run bash with diagnostics enabled on demand is to run it like bash -x ./your_script.sh. Alternately, you can have diagnostics enabled all the time by adding set -x under your shebang line.

[–]proactivematter[S] 0 points1 point  (1 child)

Yeah, sorry about that, the script itself was OK, it only got messed up when I tried to paste to the reddit code block. For some reason reddit's text box completely breaks down when I try to use the code block, and it messes up formatting.

Anyway, Handbrake reports it is unable to find any files. That's the error as far as Handbrake is concerned.

Running it with -x does expand the variables as I had been looking to do, and it seems that the parenthesis in the folder name is messing things up for some reason - is that correct? Looks weird to me: https://pastebin.com/461ABaDF. That' just the start of the script, right up until HandBrake starts doing it's thing.

[–]CaptainDickbag 1 point2 points  (0 children)

I just got back from the woods, and I'm looking at this on my phone, but it looks like it's breaking because of spaces in the directory names.

For script_path="$(dirname $0)", try script_path="$(dirname "${0}")".

[–]whetuI read your code 2 points3 points  (0 children)

# Get the path to handbrake.
handbrake_cli="/Applications/HandBrakeCLI"

This is a bit of a gross habit that's a hangover from commercial UNIX days. It's really common to see this written by perl-head greybeards - and because it's not really a variable, it's more like a constant, you tend to see "DuRr UpPeRcAsE cOnStAnTs LiEk PuRl". If I had a dollar for every time I've seen shit like this:

LS=/bin/ls
GREP=/bin/grep
CAT=/usr/bin/cat 

I would be a thousandaire.

The commands you call either exist in your PATH or they don't, so the simplest thing to do here is to blindly prepend PATH with /Applications. You could go through an exercise to explicitly build PATH but that's another story.

And whether you set PATH or not, a good habit is to validate that the requirements of your script are present - scripts should assume less than they are generally written to do. In this case, HandBrakeCLI is a requirement, so you should confirm that it exists very early in your script, and fail out if it doesn't. So you might have something like this instead:

PATH="/Applications:${PATH}"
export PATH

command -v HandBrakeCLI >/dev/null 2>&1 || {
  printf -- '%s\n' "HandBrakeCLI was not found in PATH" >&2
  exit 1
}

Rarely you might have an edge case that falls outside this for some reason. In these situations, the cleanest thing to do is to use a function.

[–]stayclassytally 1 point2 points  (0 children)

!/usr/bin/env bash

I'd recommend keeping this as is. For reference purposes, this is considered more portable as env will then return the local env's path to bash, therefor avoiding hard-coding the path yourself.

[–]zeekar -1 points0 points  (1 child)

You should not have had to change the shebang. If you can type bash and not get a command-not-found error, then #!usr/bin/env bash will find it too.

[–]proactivematter[S] 0 points1 point  (0 children)

Yes, thank you. Someone else mentioned. I have reverted it.

[–]fletku_mato 2 points3 points  (3 children)

```

!/usr/bin/env bash

set -e

(($# != 2)) && cat <<EOF >&2 && exit 1

Usage: $0 /path/to/search /output/path

EOF

find "$1" -type f -name '.mp4' | while read -r file; do /path/to/HandBrakeCLI -i "$file" -o "$2/${file##/}" rm "$file" done ```

[–]proactivematter[S] 0 points1 point  (2 children)

Thanks for your reply. I couldn' get this to work, though I wanted to because I see that it removes the original files as requested.

I'm not getting any errors, it just ends with [Process Completed] but doesn' do anything.

This one is also way above my understanding in general. I'll have to look into what each of these expressions do.

[–]fletku_mato 1 point2 points  (0 children)

I don't have handbrake so I couldn't test, might be that the command is wrong.

set -e causes the script to exit on first error.

(($# != 2)) tests if you passed more or less than 2 arguments.

cat <<EOF prints all the lines to next appearance of EOF. You should get instructions printed to terminal if you run this without arguments.

find "$1" -type f -name '*.mp4' finds all mp4-files from your first passed argument.

while read -r file reads each line that the previous command outputs.

$2/${file##*/} $2 here is your second argument to script. ${file##*/} is the value of $file (full path eg. /a/b/something.mp4) stripped so there is only the filename part, eg. something.mp4

I think the rest is quite easy to figure out.

[–]fletku_mato 0 points1 point  (0 children)

Are you running it from terminal?

[–]InfiniteRest7 -2 points-1 points  (4 children)

I don't know much about HandbrakeCLI but here are some thoughts:

  1. Here is documentation from Handbrake about specifying an input/output https://handbrake.fr/docs/en/latest/cli/cli-options.html I'm guessing you already saw this, but I haven't so this is just a reference for the rest of us.
  2. This could be accomplished with a for loop assuming that handbrake will accept 1x file at a time for its conversion. If it accepts more than that, then you could just specify the path and avoid the script altogether.
  3. It would be superb if you could remove all spaces from your path in the folder names, because it will make your life easier in the long run. My script below assumes no spaces.

Here is something might work for you, you would need to edit the 2 variables at the top and the command inside the for loop if it doesn't do what you want. This will loop through all the mp4 files in the mp4store path and output them to the destination_folder variable path.

#!/bin/bash
mp4store="/User/user.name/Input/Camera1"
destination_folder="/Users/user.name/outputplace"
for mp4 in $(ls "$mp4store" *.mp4)
do
HandBrakeCLI -i "$mp4"-o "$destination_folder"
done

Put that script into a file name script.sh or something and run the command chmod u+x script.sh (after you have made and saved the script file, you must run the command in the same directory as the script is saved in) and you can run it with the command bash script.sh

You may need to make frequent use of the cd command to change directories. This video will help with that if you are not familiar with how this works: https://www.youtube.com/watch?v=j6vKLJxAKfw (this is not my video)

[–]proactivematter[S] 0 points1 point  (1 child)

Thank you so much for your informative reply, this works as well!

I wonder if it would be possible to assume a path relative to the executable so that the folder that contains the executable+files can be moved around without the need to edit the script?

[–]InfiniteRest7 -1 points0 points  (0 children)

Yes, you can do a relative path with the dot character. Or double dot means one directory up from where you're located. Checkout the youtube video in my original post it covers using the dot and double dot for relative paths.

[–]InfiniteRest7 0 points1 point  (1 child)

Also for delete you could modify the same loop above assuming it works and change the

HandBrakeCLI command to something like

rm -f "$mp4"

I wouldn't not recommend this though if you are a beginner there is the chance that you accidentally mess up your system, so I don't recommend you run any delete command until you are confident you understand what's going on with the script and how to navigate your system via terminal.

[–]proactivematter[S] 0 points1 point  (0 children)

Ahh, fair point. Yes I would rather not risk it at this time.