Skip to content

Instantly share code, notes, and snippets.

@dargmuesli
Last active June 9, 2022 10:44
Show Gist options
  • Save dargmuesli/daf2ed891fdcefcb5b774da5d23ed8e7 to your computer and use it in GitHub Desktop.
Save dargmuesli/daf2ed891fdcefcb5b774da5d23ed8e7 to your computer and use it in GitHub Desktop.
mp4/mp3 helper script. Convert, extract covers, ...
#!/bin/bash
# Exit on errors, use last pipe error code, do not overwrite files, ensure
# variables exist
set -o errexit -o pipefail -o noclobber -o nounset
# allow matching of files by glob directly without find
shopt -s globstar nullglob
# Set color codes for use with echo
LIGHT_BLUE='\e[94m'
LIGHT_GREEN='\e[92m'
LIGHT_RED='\e[91m'
LIGHT_YELLOW='\e[93m'
NC='\e[0m'
if ! hash ffmpeg 2>/dev/null; then
echo -e "${LIGHT_BLUE}ffmpeg${LIGHT_YELLOW} is not available.${NC}"
if ! hash lsb_release 2>/dev/null; then
echo -e "${LIGHT_RED}Could not determine OS distribution!${NC}"
echo -e "${LIGHT_BLUE}lsb_release${NC} is not available."
exit 1
fi
if [[ ("$(lsb_release -is)" == "Debian") || (\
"$(lsb_release -is)" == "Ubuntu") ]]; then
sudo apt-get install -y ffmpeg
else
echo -e "${LIGHT_RED}Could not install ${LIGHT_BLUE}ffmpeg${LIGHT_RED}" \
" automatically!${NC}"
echo -e "Please install it manually."
exit 1
fi
fi
DRY_RUN=false
INPUT_PATH=""
MIRROR=false
OUTPUT_PATH=""
SEEK_DEFAULT="00:00:10"
SEEK="$SEEK_DEFAULT"
function usage() {
echo -e "usage: ${0##*/} ${LIGHT_YELLOW}<module>${NC}" \
"${LIGHT_YELLOW}<options>${NC}"
echo -e ""
echo -e "${LIGHT_YELLOW}modules${NC}"
echo -e " cover-extract Extracts screenshots at the time" \
"given by ${LIGHT_BLUE}--seek${NC}."
echo -e " cover-set Sets images as ID3 cover tag."
echo -e " mp Extracts mp3s from mp4s."
echo -e ""
echo -e "${LIGHT_YELLOW}options${NC}"
echo -e " -d, --dry-run Do not perform any changes."
echo -e " -i, --input-path * The files' source path."
echo -e " -m, --mirror Remove orphaned files in output" \
"directory."
echo -e " -o, --output-path * The files' output path."
echo -e " -h, --help Display this help."
echo -e ""
echo -e " ${LIGHT_BLUE}cover-extract${NC}"
echo -e " -s, --seek A position within the media," \
"defaulting to '$SEEK_DEFAULT'."
echo -e ""
echo -e "*=required"
exit 1
}
function coverExtract() {
DRY_RUN="$1"
INPUT_PATH="$2"
OUTPUT_PATH="$3"
MIRROR="$4"
SEEK="$5"
echo -e "Extracting covers..."
while read -r -d $'\0' mp4Name
do
fileName="$(basename "$mp4Name")"
fileNameJpg="${fileName/.mp4/.jpg}"
filePathJpg="$OUTPUT_PATH/$fileNameJpg"
if [ -f "$filePathJpg" ]; then
echo -e "${LIGHT_YELLOW}Skipped${NC} ${LIGHT_BLUE}$fileNameJpg${NC} as it already exists."
else
if [ "$DRY_RUN" = "false" ]; then
echo -e "Processing ${LIGHT_BLUE}$fileName${NC}..."
ffmpeg -nostdin -ss "$SEEK" -i "$mp4Name" -frames:v 1 "$OUTPUT_PATH/$fileNameJpg"
else
echo -e "${LIGHT_YELLOW}Skipped${NC} processing ${LIGHT_BLUE}$fileName${NC} due to dry run."
fi
fi
done < <(find "$INPUT_PATH" -type f -name '*.mp4' -print0)
echo -e "Checking for orphaned files in output directory..."
while read -r -d $'\0' jpgName
do
fileName="$(basename "$jpgName")"
fileNameMp4="${fileName/.jpg/.mp4}"
filePathMp4="$INPUT_PATH/$fileNameMp4"
if [ ! -f "$filePathMp4" ]; then
echo -e "${LIGHT_YELLOW}Orphan${NC} ${LIGHT_BLUE}$fileName${NC} found."
if [ "$MIRROR" = true ]; then
if [ "$DRY_RUN" = "false" ]; then
echo -e "${LIGHT_YELLOW}Deleting${NC} ${LIGHT_BLUE}$fileName${NC}..."
rm "$jpgName"
else
echo -e "${LIGHT_YELLOW}Skipped${NC} deleting ${LIGHT_BLUE}$fileName${NC} due to dry run."
fi
fi
fi
done < <(find "$OUTPUT_PATH" -type f -name '*.jpg' -print0)
}
function coverSet() {
DRY_RUN="$1"
INPUT_PATH="$2"
OUTPUT_PATH="$3"
MIRROR="$4"
echo -e "Setting covers..."
outputPath="$OUTPUT_PATH/4to3/"
mkdir -p "$outputPath"
while read -r -d $'\0' jpgName
do
fileName="$(basename "$jpgName")"
fileNameMp3="${fileName/.jpg/.mp3}"
filePathMp3="$OUTPUT_PATH/$fileNameMp3"
filePathMp3Output="$OUTPUT_PATH/4to3/$fileNameMp3"
if [ -f "$filePathMp3" ]; then
if [ -f "$filePathMp3Output" ]; then
echo -e "${LIGHT_YELLOW}Skipped${NC} ${LIGHT_BLUE}$filePathMp3Output${NC} as it already exists."
else
if [ "$DRY_RUN" = "false" ]; then
echo -e "Processing ${LIGHT_BLUE}$fileName${NC}..."
ffmpeg -nostdin -i "$filePathMp3" -i "$jpgName" -map 0:0 -map 1:0 -c copy -id3v2_version 3 -metadata:s:v title="Album cover" -metadata:s:v comment="Cover (front)" "$filePathMp3Output"
else
echo -e "${LIGHT_YELLOW}Skipped${NC} processing ${LIGHT_BLUE}$fileName${NC} due to dry run."
fi
fi
else
echo -e "${LIGHT_BLUE}$fileNameMp3${LIGHT_RED} does not exist!${NC}"
fi
done < <(find "$INPUT_PATH" -type f -name '*.jpg' -print0)
echo -e "Checking for orphaned files in output directory..."
while read -r -d $'\0' mp3Name
do
fileName="$(basename "$mp3Name")"
fileNameJpg="${fileName/.mp3/.jpg}"
filePathJpg="$INPUT_PATH/$fileNameJpg"
if [ ! -f "$filePathJpg" ]; then
echo -e "${LIGHT_YELLOW}Orphan${NC} ${LIGHT_BLUE}$fileName${NC} found."
if [ "$MIRROR" = true ]; then
if [ "$DRY_RUN" = "false" ]; then
echo -e "${LIGHT_YELLOW}Deleting${NC} ${LIGHT_BLUE}$fileName${NC}..."
rm "$mp3Name"
else
echo -e "${LIGHT_YELLOW}Skipped${NC} deleting ${LIGHT_BLUE}$fileName${NC} due to dry run."
fi
fi
fi
done < <(find "$OUTPUT_PATH" -type f -name '*.mp3' -print0)
}
function mp() {
DRY_RUN="$1"
INPUT_PATH="$2"
OUTPUT_PATH="$3"
MIRROR="$4"
echo -e "Converting mp4 to mp3..."
while read -r -d $'\0' mp4Name
do
fileName="$(basename "$mp4Name")"
fileNameMp3="${fileName/.mp4/.mp3}"
filePathMp3="$OUTPUT_PATH/$fileNameMp3"
if [ -f "$filePathMp3" ]; then
echo -e "${LIGHT_YELLOW}Skipped${NC} ${LIGHT_BLUE}$fileNameMp3${NC} as it already exists."
else
if [ "$DRY_RUN" = "false" ]; then
echo -e "Processing ${LIGHT_BLUE}$fileName${NC}..."
ffmpeg -nostdin -i "$mp4Name" -b:a 320k "$OUTPUT_PATH/$fileNameMp3"
else
echo -e "${LIGHT_YELLOW}Skipped${NC} processing ${LIGHT_BLUE}$fileName${NC} due to dry run."
fi
fi
done < <(find "$INPUT_PATH" -type f -name '*.mp4' -print0)
echo -e "Checking for orphaned files in output directory..."
while read -r -d $'\0' mp3Name
do
fileName="$(basename "$mp3Name")"
fileNameMp4="${fileName/.mp3/.mp4}"
filePathMp4="$INPUT_PATH/$fileNameMp4"
if [ ! -f "$filePathMp4" ]; then
echo -e "${LIGHT_YELLOW}Orphan${NC} ${LIGHT_BLUE}$fileName${NC} found."
if [ "$MIRROR" = true ]; then
if [ "$DRY_RUN" = "false" ]; then
echo -e "${LIGHT_YELLOW}Deleting${NC} ${LIGHT_BLUE}$fileName${NC}..."
rm "$mp3Name"
else
echo -e "${LIGHT_YELLOW}Skipped${NC} deleting ${LIGHT_BLUE}$fileName${NC} due to dry run."
fi
fi
fi
done < <(find "$OUTPUT_PATH" -type f -name '*.mp3' -print0)
}
# Check if getopt is available
# shellcheck disable=SC2251
! getopt --test >/dev/null
if [[ ${PIPESTATUS[0]} -ne 4 ]]; then
echo -e "${LIGHT_RED}Cannot parse parameters!${NC}"
exit 1
fi
# Parse command line parameters
OPTIONS=dhi:mo:s:
LONGOPTS=dry-run,help,input-path:,mirror,output-path:,seek:
# shellcheck disable=SC2251
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS \
--name "$0" -- "$@")
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
exit 2
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-d | --dry-run)
DRY_RUN=true
shift 1
;;
-h | --help)
echo -e "mp4/mp3 helper script."
echo -e ""
usage
;;
-i | --input-path)
INPUT_PATH="$2"
shift 2
;;
-m | --mirror)
MIRROR=true
shift 1
;;
-o | --output-path)
OUTPUT_PATH="$2"
shift 2
;;
-s | --seek)
SEEK="$2"
shift 2
;;
--)
shift
break
;;
*)
echo -e "${LIGHT_RED}Programming error!${NC}"
exit 2
;;
esac
done
if [ $# -lt 1 ]; then
echo -e "${LIGHT_RED}No module parameter provided!${NC}"
usage
exit 1
elif [[ "$1" != "cover-extract" && "$1" != "cover-set" && "$1" != "mp" ]]; then
echo -e "${LIGHT_RED}Parameter '$1' equals neither" \
"${LIGHT_BLUE}cover-extract${LIGHT_RED} nor ${LIGHT_BLUE}cover-set" \
"${LIGHT_RED} nor ${LIGHT_BLUE}mp${LIGHT_RED}!${NC}"
exit 1
fi
if [ -z "$INPUT_PATH" ]; then
echo -e "${LIGHT_RED}Input path not provided!${NC}"
usage
exit 1
fi
if [ ! -d "$INPUT_PATH" ]; then
echo -e "${LIGHT_RED}Input path is not a directory!${NC}"
exit 1
fi
if [ -z "$OUTPUT_PATH" ]; then
echo -e "${LIGHT_RED}Output path not provided!${NC}"
usage
exit 1
fi
if [ ! -d "$OUTPUT_PATH" ]; then
echo -e "${LIGHT_RED}Output path is not a directory!${NC}"
exit 1
fi
case "$1" in
"cover-extract")
coverExtract "$DRY_RUN" "$INPUT_PATH" "$OUTPUT_PATH" "$MIRROR" "$SEEK"
;;
"cover-set")
coverSet "$DRY_RUN" "$INPUT_PATH" "$OUTPUT_PATH" "$MIRROR"
;;
"mp")
mp "$DRY_RUN" "$INPUT_PATH" "$OUTPUT_PATH" "$MIRROR"
;;
esac
echo -e "\n${LIGHT_GREEN}Done${NC}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment