testsAndMisc/scripts/utils/to_mp4.sh

195 lines
4.6 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# to_mp4.sh
#
# Convert video files (non-mp4) to mp4 format using ffmpeg.
# Accepts either a single video file or a directory (will recurse into subdirectories).
# Video extensions to search for (excluding mp4 since that's our target)
VIDEO_EXTENSIONS=("webm" "mkv" "avi" "mov" "wmv" "flv" "m4v" "mpg" "mpeg" "3gp" "ogv" "ts" "mts" "m2ts" "vob" "asf" "rm" "rmvb" "divx" "f4v")
# Conversion settings
CRF=23 # Quality (0-51, lower = better quality, 18-28 is reasonable for H.264)
PRESET="medium" # Encoding speed: ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow
DELETE_ORIGINAL=false
log() {
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
}
usage() {
cat <<EOF
Usage:
$(basename "$0") [OPTIONS] PATH
Convert video files to mp4 format using ffmpeg.
PATH can be a single video file or a directory (will recurse into subdirectories).
Options:
-c CRF Quality level 0-51 (default: 23, lower = better quality)
-p PRESET Encoding preset (default: medium)
Options: ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow
-d Delete original file after successful conversion
-h Show this help
Examples:
$(basename "$0") video.webm
$(basename "$0") /path/to/videos/
$(basename "$0") -c 20 -d /path/to/videos/
$(basename "$0") -p slow -c 18 movie.mkv
EOF
}
ensure_ffmpeg() {
if ! command -v ffmpeg >/dev/null 2>&1; then
echo "Error: 'ffmpeg' is not installed or not in PATH." >&2
exit 1
fi
}
is_video_file() {
local file="$1"
local ext="${file##*.}"
ext="${ext,,}" # lowercase
for video_ext in "${VIDEO_EXTENSIONS[@]}"; do
if [[ "$ext" == "${video_ext,,}" ]]; then
return 0
fi
done
return 1
}
convert_to_mp4() {
local input_file="$1"
local output_file="${input_file%.*}.mp4"
# Skip if output already exists
if [[ -f "$output_file" ]]; then
log "Skipping '$input_file': output '$output_file' already exists"
return 0
fi
log "Converting '$input_file' -> '$output_file'"
# Use H.264 codec for video and AAC for audio (maximum compatibility)
if ffmpeg -hide_banner -loglevel warning -i "$input_file" \
-c:v libx264 -crf "$CRF" -preset "$PRESET" \
-c:a aac -b:a 192k \
-movflags +faststart \
"$output_file"; then
log "Successfully converted '$input_file'"
if [[ "$DELETE_ORIGINAL" == true ]]; then
log "Deleting original: '$input_file'"
rm "$input_file"
fi
else
log "Error converting '$input_file'"
# Remove partial output file if it exists
[[ -f "$output_file" ]] && rm "$output_file"
return 1
fi
}
process_directory() {
local dir="$1"
local count=0
local failed=0
log "Searching for video files in '$dir'..."
# Find all video files (case-insensitive)
while IFS= read -r -d '' file; do
# Skip mp4 files
if [[ "${file,,}" == *.mp4 ]]; then
continue
fi
((count++)) || true
if ! convert_to_mp4 "$file"; then
((failed++)) || true
fi
done < <(find "$dir" -type f \( -iname "*.webm" -o -iname "*.mkv" -o -iname "*.avi" -o -iname "*.mov" \
-o -iname "*.wmv" -o -iname "*.flv" -o -iname "*.m4v" -o -iname "*.mpg" -o -iname "*.mpeg" \
-o -iname "*.3gp" -o -iname "*.ogv" -o -iname "*.ts" -o -iname "*.mts" -o -iname "*.m2ts" \
-o -iname "*.vob" -o -iname "*.asf" -o -iname "*.rm" -o -iname "*.rmvb" -o -iname "*.divx" \
-o -iname "*.f4v" \) -print0 2>/dev/null)
log "Processed $count video file(s), $failed failed"
if [[ $count -eq 0 ]]; then
log "No video files found in '$dir'"
fi
}
parse_args() {
while getopts ":c:p:dh" opt; do
case "$opt" in
c) CRF="$OPTARG" ;;
p) PRESET="$OPTARG" ;;
d) DELETE_ORIGINAL=true ;;
h)
usage
exit 0
;;
:)
echo "Error: Option -$OPTARG requires an argument." >&2
usage
exit 1
;;
\?)
echo "Error: Invalid option -$OPTARG" >&2
usage
exit 1
;;
esac
done
shift $((OPTIND - 1))
if [[ $# -lt 1 ]]; then
echo "Error: No path specified." >&2
usage
exit 1
fi
TARGET_PATH="$1"
}
main() {
ensure_ffmpeg
parse_args "$@"
if [[ ! -e "$TARGET_PATH" ]]; then
echo "Error: Path '$TARGET_PATH' does not exist." >&2
exit 1
fi
if [[ -f "$TARGET_PATH" ]]; then
# Single file
if [[ "${TARGET_PATH,,}" == *.mp4 ]]; then
log "File '$TARGET_PATH' is already in mp4 format, skipping."
exit 0
fi
if is_video_file "$TARGET_PATH"; then
convert_to_mp4 "$TARGET_PATH"
else
echo "Error: '$TARGET_PATH' is not a recognized video file." >&2
exit 1
fi
elif [[ -d "$TARGET_PATH" ]]; then
# Directory
process_directory "$TARGET_PATH"
else
echo "Error: '$TARGET_PATH' is neither a file nor a directory." >&2
exit 1
fi
log "Done!"
}
main "$@"