#!/bin/bash #set -e get_clip_params () { CLIP_PATH=$( dirname "$1" ) CLIP_NAME=$( basename "$1" | sed -r "s|\.[^. ]*$||g" ) CLIP_EXTENSION=$( sed -r "s|^.*\.||g" <<< "$1" ) CLIP_WIDTH=$( ffprobe -v error -select_streams v:0 -show_entries stream=width -of default=noprint_wrappers=1:nokey=1 "$1" ) CLIP_HEIGHT=$( ffprobe -v error -select_streams v:0 -show_entries stream=height -of default=noprint_wrappers=1:nokey=1 "$1" ) CLIP_SAR=$( ffprobe -v error -select_streams v:0 -show_entries stream=sample_aspect_ratio -of default=noprint_wrappers=1:nokey=1 "$1" ) CLIP_FRAMERATE=$( ffprobe -i "$1" 2>&1 | grep -oP "\d{1,5}\.?\d{1,5} fps" | tail -1 | sed -r "s|\ fps||g" ) MOD=1 [[ "$CLIP_SAR" != "1:1" ]] && MOD=$( echo "$CLIP_SAR" | sed -r "s|\:|\ \/\ |g; s|^|scale\=5\;\ |g" | bc ) && CLIP_HEIGHT=$( printf "%.0f" $( bc <<< "$CLIP_HEIGHT / $MOD" ) ) } dump_clip_params () { echo -e "\nCLIP NAME: $CLIP_NAME.$CLIP_EXTENSION\nCLIP LOCATION: $CLIP_PATH/\nCLIP DIMENSIONS: ${CLIP_WIDTH}x${CLIP_HEIGHT}\nCLIP FRAMERATE: $CLIP_FRAMERATE\nCLIP SAR: $CLIP_SAR" } crop () { echo -e "-----out_w is the width of the output rectangle-----\n-----out_h is the height of the output rectangle-----\n-----x and y specify the top left corner of the output rectangle-----" echo -e "-----CURRENT VIDEO DIMENSIONS OF $CLIP_NAME.$CLIP_EXTENSION: ${CLIP_WIDTH}x${CLIP_HEIGHT}" re='^[0-9]+(\ )+[0-9]+(\ )+[0-9]+(\ )+[0-9]+$' declare OUT_W OUT_H X Y CROP_PARAMS while read -p "Enter new clip dimensions [FORMAT: out_w out_h x y]: " CROP_PARAMS; do CROP_PARAMS=${CROP_PARAMS:-"$CLIP_WIDTH $CLIP_HEIGHT 0 0"} if ! [[ $CROP_PARAMS =~ $re ]]; then echo "error: Invalid crop parameters" >&2; continue fi OUT_W=$( echo "$CROP_PARAMS" | tr -s ' ' | cut -d ' ' -f 1 ) OUT_H=$( echo "$CROP_PARAMS" | tr -s ' ' | cut -d ' ' -f 2 ) X=$( echo "$CROP_PARAMS" | tr -s ' ' | cut -d ' ' -f 3 ) Y=$( echo "$CROP_PARAMS" | tr -s ' ' | cut -d ' ' -f 4 ) if [[ $( bc <<< "$OUT_W + $X" ) -gt $CLIP_WIDTH || $( bc <<< "$OUT_H + $Y" ) -gt $CLIP_HEIGHT ]]; then echo "error: Crop parameters go outside clip bounds" >&2; continue fi break done OUT_H_MOD=$( bc <<< "$OUT_H * $MOD" ) Y_MOD=$( bc <<< "$Y * $MOD" ) ffmpeg -i "$1" -filter:v "crop=$OUT_W:$OUT_H_MOD:$X:$Y_MOD" "$CLIP_NAME [cropped $OUT_W:$OUT_H:$X:$Y].$CLIP_EXTENSION" } join_no_reencode () { [[ ! -f "join.$CLIP_EXTENSION" ]] && cp "$1" "join.$CLIP_EXTENSION" && return echo "file 'join.${CLIP_EXTENSION}'" > cliparmyknife_concat.txt echo "file '$1'" >> cliparmyknife_concat.txt ffmpeg -y -f concat -safe 0 -i cliparmyknife_concat.txt -c copy join.$CLIP_EXTENSION rm cliparmyknife_concat.txt } join_yes_reencode () { [[ ! -f "join.$CLIP_EXTENSION" ]] && cp "$1" "join.$CLIP_EXTENSION" && return JOIN_WIDTH=$( ffprobe -i "join.$CLIP_EXTENSION" 2>&1 | grep -oP "[1-9]\d{1,10}x[1-9]\d{1,10}" | sed -r "s|\ ||g; s|x[0-9]*$||g" ) JOIN_HEIGHT=$( ffprobe -i "join.$CLIP_EXTENSION" 2>&1 | grep -oP "[1-9]\d{1,10}x[1-9]\d{1,10}" | sed -r "s|\ ||g; s|^[0-9]*x||g" ) #ffmpeg -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 -i boximouto.webm -c:v copy -c:a libopus -shortest boximouto2.webm #ffprobe -i ikadiabetes2.gif -show_streams -select_streams a -loglevel error [[ -z $(ffprobe -i "$1" -show_streams -select_streams a -loglevel error) ]] && ffmpeg -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 -i "$1" -c:v copy -c:a libopus -shortest "/tmp/clip_treated.$CLIP_EXTENSION" || cp "$1" "/tmp/clip_treated.$CLIP_EXTENSION" [[ -z $(ffprobe -i "join.$CLIP_EXTENSION" -show_streams -select_streams a -loglevel error) ]] && ffmpeg -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 -i "join.$CLIP_EXTENSION" -c:v copy -c:a libopus -shortest "/tmp/join_treated.$CLIP_EXTENSION" || cp "join.$CLIP_EXTENSION" "/tmp/join_treated.$CLIP_EXTENSION" echo "JOIN DIMENSIONS: $JOIN_WIDTH x $JOIN_HEIGHT" [[ $JOIN_WIDTH -gt $CLIP_WIDTH ]] && PAD_WIDTH=$JOIN_WIDTH || PAD_WIDTH=$CLIP_WIDTH [[ $JOIN_HEIGHT -gt $CLIP_HEIGHT ]] && PAD_HEIGHT=$JOIN_HEIGHT || PAD_HEIGHT=$CLIP_HEIGHT PAD_X=$( bc <<< "($JOIN_WIDTH - $CLIP_WIDTH) / 2" ) PAD_Y=$( bc <<< "($JOIN_HEIGHT - $CLIP_HEIGHT) / 2" ) echo "PAD DIMENSIONS: $PAD_X, $PAD_Y" if [[ $PAD_X -ge 0 && $PAD_Y -ge 0 ]]; then cp "/tmp/join_treated.$CLIP_EXTENSION" "/tmp/join_temp.$CLIP_EXTENSION" ffmpeg -y -i "/tmp/clip_treated.$CLIP_EXTENSION" -vf "pad=width=$PAD_WIDTH:height=$PAD_HEIGHT:x=$PAD_X:y=$PAD_Y:color=black" "/tmp/clip_temp.$CLIP_EXTENSION" elif [[ $PAD_X -lt 0 && $PAD_Y -ge 0 ]]; then ffmpeg -y -i "/tmp/join_treated.$CLIP_EXTENSION" -vf "pad=width=$PAD_WIDTH:height=$PAD_HEIGHT:x=${PAD_X#-}:y=0:color=black" "/tmp/join_temp.$CLIP_EXTENSION" ffmpeg -y -i "/tmp/clip_treated.$CLIP_EXTENSION" -vf "pad=width=$PAD_WIDTH:height=$PAD_HEIGHT:x=0:y=$PAD_Y:color=black" "/tmp/clip_temp.$CLIP_EXTENSION" elif [[ $PAD_X -ge 0 && $PAD_Y -lt 0 ]]; then ffmpeg -y -i "/tmp/join_treated.$CLIP_EXTENSION" -vf "pad=width=$PAD_WIDTH:height=$PAD_HEIGHT:x=0:y=${PAD_Y#-}:color=black" "/tmp/join_temp.$CLIP_EXTENSION" ffmpeg -y -i "/tmp/clip_treated.$CLIP_EXTENSION" -vf "pad=width=$PAD_WIDTH:height=$PAD_HEIGHT:x=$PAD_X:y=0:color=black" "/tmp/clip_temp.$CLIP_EXTENSION" elif [[ $PAD_X -lt 0 && $PAD_Y -lt 0 ]]; then ffmpeg -y -i "/tmp/join_treated.$CLIP_EXTENSION" -vf "pad=width=$PAD_WIDTH:height=$PAD_HEIGHT:x=${PAD_X#-}:y=${PAD_Y#-}:color=black" "/tmp/join_temp.$CLIP_EXTENSION" cp "/tmp/clip_treated.$CLIP_EXTENSION" "/tmp/clip_temp.$CLIP_EXTENSION" else echo "something fucked up" && exit 1 fi if [[ -n $(ffprobe -i "/tmp/clip_temp.$CLIP_EXTENSION" -show_streams -select_streams a -loglevel error) && -n $(ffprobe -i "/tmp/clip_temp.$CLIP_EXTENSION" -show_streams -select_streams a -loglevel error) ]]; then ffmpeg -y -i "/tmp/join_temp.$CLIP_EXTENSION" -i "/tmp/clip_temp.$CLIP_EXTENSION" -filter_complex "[0:v:0][0:a:0][1:v:0][1:a:0]concat=n=2:v=1:a=1[outv][outa]" -map "[outv]" -map "[outa]" "/tmp/join2.$CLIP_EXTENSION" else ffmpeg -y -i "/tmp/join_temp.$CLIP_EXTENSION" -i "/tmp/clip_temp.$CLIP_EXTENSION" -filter_complex "[0:v:0] [1:v:0]concat=n=2:v=1[outv]" -map "[outv]" "/tmp/join2.$CLIP_EXTENSION" fi mv "/tmp/join2.$CLIP_EXTENSION" "join.$CLIP_EXTENSION" rm "/tmp/join_treated.$CLIP_EXTENSION" "/tmp/clip_treated.$CLIP_EXTENSION" "/tmp/join_temp.$CLIP_EXTENSION" "/tmp/clip_temp.$CLIP_EXTENSION" } join () { if [[ "$REENCODE_FLAG" = "true" ]]; then join_yes_reencode "$1" elif [[ "$REENCODE_FLAG" = "false" ]]; then join_no_reencode "$1" else while true; do read -p "Are the video parameters identical (y/n)? " yn case $( tr '[A-Z]' '[a-z]' <<< "$yn" ) in y|yes) REENCODE_FLAG="false"; join_no_reencode "$1" ; break;; n|no) REENCODE_FLAG="true"; join_yes_reencode "$1" ; break;; *) echo "Invalid response." ;; esac done fi } mute () { ffmpeg -i "$1" -c copy -an "$CLIP_NAME [no audio].$CLIP_EXTENSION" } extract_audio () { ffmpeg -i "$1" -codec:a libmp3lame "$CLIP_NAME [audio only].mp3" } scale () { echo -e "-----CURRENT VIDEO DIMENSIONS OF $CLIP_NAME.$CLIP_EXTENSION: $CLIP_WIDTH x $CLIP_HEIGHT" while read -p "Enter scale coefficient [Default: 2; Recommended: $( bc <<< "scale=2; $CLIP_HEIGHT / 360" )]: " SCALE; do SCALE=${SCALE:-2} re='^[0-9]*([.][0-9]+)?$' if ! [[ $SCALE =~ $re ]] ; then echo "error: Not a non-zero number" >&2; continue fi break done ffmpeg -i "$1" -vf scale="-1:$( printf "%.0f" $( bc <<< "scale=5; $CLIP_HEIGHT / $SCALE" ) )" -c:a copy "$CLIP_NAME [$SCALE].$CLIP_EXTENSION" } reverse () { ffmpeg -i "$1" -vf reverse -af areverse "$CLIP_NAME [reversed].$CLIP_EXTENSION" } clip_to_gif () { echo -e "-----CURRENT VIDEO DIMENSIONS OF $CLIP_NAME.$CLIP_EXTENSION: $CLIP_WIDTH x $CLIP_HEIGHT" while read -p "Enter scale coefficient [Default: 2; Recommended: $( bc <<< "scale=2; $CLIP_HEIGHT / 360" )]: " SCALE; do SCALE=${SCALE:-2} re='^[0-9]*([.][0-9]+)?$' if ! [[ $SCALE =~ $re ]] ; then echo "error: Not a non-zero number" >&2; continue fi break done echo -e "-----CURRENT FRAMERATE OF $CLIP_NAME.$CLIP_EXTENSION: $CLIP_FRAMERATE fps" while read -p "Enter new framerate [Default: 23.98; Recommended: $( bc <<< "scale=2; $CLIP_FRAMERATE / 2" )]: " RATE; do RATE=${RATE:-23.98} re='^[1-9][0-9]*([.][0-9]+)?$' if ! [[ $RATE =~ $re ]] ; then echo "error: Not a non-zero number" >&2; continue fi break done filters="fps=$RATE,scale=$( bc <<< "$CLIP_WIDTH / $SCALE" ):-1:flags=lanczos" temp_palette="/tmp/_tmp_palette_$CLIP_NAME.png" temp_gif="/tmp/tmp_out_$CLIP_NAME.gif" ffmpeg -v warning -i "$1" -vf "$filters,palettegen=stats_mode=diff" -y $temp_palette ffmpeg -i "$1" -i "$temp_palette" -lavfi "$filters,paletteuse=dither=bayer:bayer_scale=3:diff_mode=rectangle" "$temp_gif" gifsicle --optimize=3 --no-background --output "$CLIP_NAME [$SCALE - $RATE].gif" "$temp_gif" rm "$temp_palette" "$temp_gif" } clip_to_webm () { echo -e "-----CURRENT VIDEO DIMENSIONS OF $CLIP_NAME.$CLIP_EXTENSION: $CLIP_WIDTH x $CLIP_HEIGHT" while read -p "Enter scale coefficient [Default: 2; Recommended: $( bc <<< "scale=2; $CLIP_HEIGHT / 360" )]: " SCALE; do SCALE=${SCALE:-2} re='^[1-9][0-9]*([.][0-9]+)?$' if ! [[ $SCALE =~ $re ]] ; then echo "error: Not a non-zero number" >&2; continue fi break done ffmpeg -i "$1" -c:v libvpx-vp9 -crf 30 -b:v 0 -b:a 128k -vf scale="-1:$( bc <<< "$CLIP_HEIGHT / $SCALE" )" -c:a libopus "$CLIP_NAME [$SCALE].webm" } webp_to_gif () { python3 -c "from PIL import Image;Image.open('$1').save('${1%.webp}.gif','gif',save_all=True,optimize=True,background=0)" } FUNCTION="" while getopts "hicsrjmxgw" opt; do case $opt in h) echo -e "usage: $0 [-h help] [-i get clip info] [-c crop clip] [-s scale/resize clip] [-r reverse clip] [-j join clips] [-m mute clip] [-x extract audio] [-g convert clip to gif] [-w convert clip to webm]"; exit ;; i) FUNCTION="dump_clip_params"; break;; c) FUNCTION="crop"; break ;; s) FUNCTION="scale"; break ;; r) FUNCTION="reverse"; break ;; j) FUNCTION="join"; break ;; m) FUNCTION="mute"; break ;; x) FUNCTION="extract_audio"; break ;; g) FUNCTION="convert"; break ;; w) FUNCTION="clip_to_webm"; break ;; ?) echo "error: option -$OPTARG is not implemented"; exit ;; esac done if [ "$#" -lt 2 ]; then echo "Please insert only one flag and at least one argument" exit else echo -e "\c" fi i=0 for file in "${@:2}" do re='s|\.[^. ]*$||g' ! [[ $file =~ $re ]] && echo "$file NOT A PROPER FILE, IGNORING..." && continue get_clip_params "$file" echo "$CLIP_NAME - $CLIP_EXTENSION - $FUNCTION" if [[ "$FUNCTION" == "convert" ]]; then if [[ "$CLIP_EXTENSION" == "gif" ]]; then echo -e "$CLIP_NAME.$CLIP_EXTENSION IS ALREADY A GIF. SKIPPING...\n\n-------------------------$file DONE-------------------------\n\n" && continue elif [[ "$CLIP_EXTENSION" == "webp" ]]; then FUNCTION="webp_to_gif" else FUNCTION="clip_to_gif" fi fi $FUNCTION "$file" [[ "$FUNCTION" =~ ^(clip|webp)_to_gif$ ]] && FUNCTION="convert" echo -e "\n\n------------------------- $file - DONE -------------------------\n\n" done # TO ADD: MORE FLEXIBLE FLAG/ARGUMENT PROCESSING, FLAG WHICH MAKES EVERY FUNCTION RESPECT FILEPATHS (I.E. DOESN'T DRAG EVERY OUTPUT TO CURRENT DIRECTORY)