vid2ani/vid2ani.sh

323 lines
9.2 KiB
Bash
Executable File

#!/bin/bash
# Description: Video to GIF/APNG/WEBP converter
# By: MDHEXT, Nabi KaramAliZadeh, Pathduck
# Version: 6.0
# Url: https://github.com/Pathduck/vid2ani/
# License: GNU General Public License v3.0 (GPLv3)
# Enable error handling
# set -euo pipefail
### Start Main ###
main() {
# Define ANSI Colors
OFF=$(tput sgr0)
RED=$(tput setaf 1)
GREEN=$(tput setaf 10)
YELLOW=$(tput setaf 11)
BLUE=$(tput setaf 12)
CYAN=$(tput setaf 14)
# Checking for blank input or help commands
if [ $# -eq 0 ]; then print_help; exit; fi
case "$1" in
-h) print_help; exit;;
-?) print_help; exit;;
--help) print_help; exit;;
esac
# Assign input and output
input="$1"
output="${input%.*}"
# Validate input file
if [[ ! -f "$input" ]]; then
echo ${RED}"Input file not found: $input"${OFF}; exit 1
fi
# Fix paths for Cygwin and create working directory
if [[ "$(uname)" == *"CYGWIN"* ]]; then
# Use Windows-compatible directories for Cygwin
input=$(cygpath -w "$input")
output=$(cygpath -w "$output")
WD=$(cygpath -w "$(mktemp -d -t vid2ani-XXXXXX)")
else
# Use POSIX-compatible directories
WD=$(mktemp -d -t vid2ani-XXXXXX)
fi
# Cleanup on exit, interrupt, termination
trap 'rm -rf "$WD"' EXIT INT TERM
# Clearing input vars and setting defaults
fps=15
mode=1
dither=0
scale="-1"
filetype="gif"
webp_lossy=""
webp_lossy_def=75
loglevel="error"
bayerscale=""
colormax=""
start_time=""
end_time=""
errorswitch=""
picswitch=""
# Parse Arguments
shift
while [[ $# -gt 0 ]]; do
case "$1" in
-o) output="${2%.*}"; shift;;
-t) filetype="$2"; shift;;
-r) scale="$2"; shift;;
-l) if [[ "$2" =~ ^[0-9]+$ ]]; then webp_lossy="$2"; shift
else webp_lossy=$webp_lossy_def; fi ;;
-f) fps="$2"; shift;;
-s) start_time="$2"; shift;;
-e) end_time="$2"; shift;;
-d) dither="$2"; shift;;
-b) bayerscale="$2"; shift;;
-m) mode="$2"; shift;;
-c) colormax="$2"; shift;;
-v) loglevel="$2"; shift;;
-k) errorswitch=1;;
-p) picswitch=1;;
*) echo ${RED}"Unknown option $1"${OFF}; exit 1;;
esac
shift
done
# Validate output file extension
case "$filetype" in
gif) output="$output.gif";;
png) output="$output.png"; filetype="apng";;
apng) output="$output.png";;
webp) output="$output.webp";;
*) echo ${RED}"Invalid file type: $filetype"${OFF}; exit 1;;
esac
# Validate Palettegen
if [[ "$mode" -lt 1 || "$mode" -gt 3 ]]; then
echo ${RED}"Not a valid palettegen (-m) mode"${OFF}; exit 1
fi
# Validate Dithering
if [[ "$dither" -gt 8 || "$dither" -lt 0 ]]; then
echo ${RED}"Not a valid dither (-d) algorithm"${OFF}; exit 1
fi
# Validate Bayerscale
if [[ -n "$bayerscale" ]]; then
if [[ "$bayerscale" -gt 5 || "$bayerscale" -lt 0 ]]; then
echo ${RED}"Not a valid bayerscale (-b) value"${OFF}; exit 1
fi
if [[ "$dither" -ne 1 ]]; then
echo ${RED}"Bayerscale (-b) only works with Bayer dithering"${OFF}; exit 1
fi
fi
# Validate Lossy WEBP
if [[ -n "$webp_lossy" ]]; then
if [[ "$filetype" != "webp" ]]; then
echo ${RED}"Lossy (-l) is only valid for filetype webp"${OFF}; exit 1
fi
if [[ "$webp_lossy" -gt 100 || "$webp_lossy" -lt 0 ]]; then
echo ${RED}"Not a valid lossy (-l) quality value"${OFF}; exit 1
fi
fi
# Validate Clipping
if [[ -n "$start_time" && -z "$end_time" ]]; then
echo ${RED}"End time (-e) is required when Start time (-e) is specified."${OFF}; exit 1
elif [[ -n "$end_time" && -z "$start_time" ]]; then
echo ${RED}"Start time (-s) is required when End time (-e) is specified."${OFF}; exit 1
elif [[ -n "$end_time" && -n "$start_time" ]]; then
trim="-ss $start_time -to $end_time"
fi
# Validate Framerate
if [[ "$fps" -le 0 ]]; then
echo ${RED}"Framerate (-f) must be greater than 0."${OFF}; exit 1
fi
# Validate Max Colors
if [[ -n "$colormax" && "$colormax" -lt 3 || "$colormax" -gt 256 ]]; then
echo ${RED}"Max colors (-c) must be between 3 and 256."${OFF}; exit 1
fi
# Displaying FFmpeg version string
ffmpeg_version=$(ffmpeg -version | head -n2)
echo ${YELLOW}"$ffmpeg_version"${OFF}
echo ${GREEN}Output file:${OFF} $output
## Putting together command to generate palette ##
palette="$WD/palette_%05d.png"
filters="fps=$fps,scale=$scale:-1:flags=lanczos"
# APNG muxer does not support multiple palettes so fallback to using palettegen diff mode
if [[ "$filetype" == "apng" && "$mode" -eq 2 ]]; then
echo ${YELLOW}"APNG does not support multiple palettes - falling back to Palettegen mode 1 (diff)"${OFF}
mode=1
fi
# Palettegen encode mode
if [[ -n "$mode" ]]; then
case "$mode" in
1) encode="palettegen=stats_mode=diff";;
2) encode="palettegen=stats_mode=single";;
3) encode="palettegen";;
*) echo ${RED}"Invalid palettegen (-m) mode"${OFF}; exit 1;;
esac
fi
# Max colors
if [[ -n "$colormax" ]]; then
if [[ "$mode" -le 2 ]]; then mcol=":max_colors=${colormax}"; fi
if [[ "$mode" -eq 3 ]]; then mcol="=max_colors=${colormax}"; fi
fi
# Executing command to generate palette
echo ${GREEN}"Generating palette..."${OFF}
ffmpeg -v "${loglevel}" ${trim:-} -i "${input}" -vf "${filters},${encode}${mcol}" -y "${palette}"
# Checking if the palette file is in the Working Directory, if not cleaning up
if [[ ! -f "$WD/palette_00001.png" ]]; then
echo ${RED}"Palette generation failed: $palette not found."${OFF}; exit 1
fi
## Setting variables to put the encode command together ##
# Palettegen decode mode
if [[ -n "$mode" ]]; then
case "$mode" in
1) decode="paletteuse";;
2) decode="paletteuse=new=1";;
3) decode="paletteuse";;
*) echo ${RED}"Invalid palettegen (-m) mode"${OFF}; exit 1;;
esac
fi
# Error diffusion
if [[ -n "$errorswitch" ]]; then
case "$mode" in
1) errordiff="=diff_mode=rectangle";;
2) errordiff=":diff_mode=rectangle";;
3) errordiff="=diff_mode=rectangle";;
*) echo ${RED}"Invalid palettegen (-m) mode"${OFF}; exit 1;;
esac
fi
# Prepare dithering and encoding options
case "$dither" in
0) ditheralg="none";;
1) ditheralg="bayer";;
2) ditheralg="heckbert";;
3) ditheralg="floyd_steinberg";;
4) ditheralg="sierra2";;
5) ditheralg="sierra2_4a";;
6) ditheralg="sierra3";;
7) ditheralg="burkes";;
8) ditheralg="atkinson";;
*) echo ${RED}"Invalid dither (-d ) mode"${OFF}; exit 1;;
esac
# Paletteuse error diffusion
if [[ "$mode" -ne 2 ]]; then
if [[ -n "$errorswitch" ]]; then ditherenc=":dither=$ditheralg"; fi
if [[ -z "$errorswitch" ]]; then ditherenc="=dither=$ditheralg"; fi
else
ditherenc=":dither=$ditheralg"
fi
# Checking for Bayer Scale and adjusting command
if [[ -n "$bayerscale" ]]; then bayer=":bayer_scale=$bayerscale"; fi
if [[ -z "$bayerscale" ]]; then bayer=""; fi
# WEBP pixel format and lossy quality
if [[ "$filetype" == "webp" && -n "$webp_lossy" ]]; then
webp_lossy="-lossless 0 -quality $webp_lossy -pix_fmt yuva420p"
elif [[ "$filetype" == "webp" && -z "$webp_lossy" ]]; then
webp_lossy="-lossless 1"
fi
# Executing the encoding command
echo ${GREEN}"Encoding animation..."${OFF}
ffmpeg -v "${loglevel}" ${trim:-} -i "${input}" -thread_queue_size 512 -i "${palette}" -lavfi "${filters} [x]; [x][1:v] ${decode}${errordiff}${ditherenc}${bayer}" -f "${filetype}" ${webp_lossy:-} -loop 0 -plays 0 -y "${output}"
# Checking if output file was created
if [[ ! -f "$output" ]]; then
echo ${RED}"Failed to generate animation: $output not found"${OFF}; exit 1
fi
# Open output file if picswitch is enabled
if [[ -n "$picswitch" ]]; then
xdg-open "$output"
fi
echo ${GREEN}"Done."${OFF}
}
### End Main ###
### Function to print the help message ###
print_help() {
cat << EOF
${GREEN}Video to GIF/APNG/WEBP converter v6.0${OFF}
${BLUE}By MDHEXT, Nabi KaramAliZadeh, Pathduck${OFF}
${GREEN}Usage:${OFF}
$(basename $0) [input_file] [arguments]
${GREEN}Arguments:${OFF}
-o Output file. Default is the same as input file, sans extension.
-t Output file type. Valid: 'gif' (default), 'apng', 'png', 'webp'.
-r Scale or size. Width of the animation in pixels.
-l Enable lossy WebP compression and quality. Range 0-100, default 75.
-f Framerate in frames per seconds, default 15.
-s Start time of the animation (HH:MM:SS.MS).
-e End time of the animation (HH:MM:SS.MS).
-d Dithering algorithm to be used, default 0.
-b Bayer Scale setting. Range 0-5, default 2.
-m Palettegen mode: 1 (diff), 2 (single), 3 (full), default 1.
-c Maximum colors usable per palette. Range 3-256 (default).
-k Enables paletteuse error diffusion.
-p Opens the resulting animation in the default image viewer.
-v Set FFmpeg log level (default: error).
${GREEN}Dithering Algorithms${OFF}
0: None
1: Bayer
2: Heckbert
3: Floyd Steinberg
4: Sierra2
5: Sierra2_4a
6: Sierra3
7: Burkes
8: Atkinson
${GREEN}Palettegen Modes${OFF}
1: diff - only what moves affects the palette
2: single - one palette per frame
3: full - one palette for the whole animation
${GREEN}About Bayerscale${OFF}
When bayer dithering is selected, the Bayer Scale option defines the
scale of the pattern (how much the crosshatch pattern is visible).
A low value means more visible pattern for less banding, a higher value
means less visible pattern at the cost of more banding.
${GREEN}People who made this project come to fruition${OFF}
ubitux, Nabi KaramAliZadeh, and the very kind and patient people in the
Batch Discord Server. Without these people's contributions, this script
would not be possible. Thank you all for your contributions and
assistance^^!
EOF
}
### End print_help ###
# Call Main function
main "$@"; exit;