Last active
September 14, 2024 16:33
-
-
Save Tynach/b296cc6502808a57955e410c36f5ce83 to your computer and use it in GitHub Desktop.
Uses FFMPEG to convert any file FFMPEG understands into a .mp4 file that Telegram will consider as a 'gif', at higher quality than Telegram converts gifs to itself.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Settings | |
outfolder="converted" | |
codec="libx264" | |
maxres="448" | |
#filter="crop=floor(in_w/2)*2:floor(in_h/2)*2:0:0:exact=1,colorspace=bt601-6-625:range=pc:irange=tv:iall=bt601-6-625:format=yuv444p12,zscale=rin=full:tin=601:t=linear,scale=if(gt(iw\,ih)\,min($maxres\,floor((iw+1)/2)*2)\,-2):if(gt(iw\,ih)\,-2\,min($maxres\,floor((ih+1)/2)*2)),zscale=rin=full:r=limited:tin=linear:t=601" | |
filter="scale=if(gt(iw\,ih)\,min($maxres\,floor((iw+1)/2)*2)\,-2):if(gt(iw\,ih)\,-2\,min($maxres\,floor((ih+1)/2)*2)):out_color_matrix=bt601:out_range=tv:flags=accurate_rnd+full_chroma_inp+full_chroma_int+bicublin" | |
preset="veryslow" | |
profile="high" | |
tune="film" | |
quality="18" | |
# Input Parameters | |
inparams=( | |
"-loglevel" "quiet" | |
"-stats" | |
) | |
# Output Parameters | |
outparams=( | |
"-flags" "+global_header" | |
"-movflags" "faststart" | |
"-c:v" "$codec" | |
"-profile:v" "$profile" | |
#"-tune:v" "$tune" | |
"-bf" "0" | |
"-copyts" | |
"-avoid_negative_ts" "disabled" | |
"-correct_ts_overflow" "0" | |
"-pix_fmt" "yuv420p" | |
"-color_primaries" "bt709" | |
"-color_trc" "iec61966_2_1" | |
"-colorspace" "bt470bg" | |
"-color_range" "tv" | |
) | |
# Make a folder if it doesn't already exist. | |
function make_folder | |
{ | |
local folder="$1" | |
if [ ! -d "$folder" ]; then | |
mkdir "$folder" | |
fi | |
} | |
# Move files to where they belong. | |
function detect_2pass | |
{ | |
local file="$1" | |
local converted="$outfolder/$2" | |
if [ ! -f "$converted" ]; then | |
echo "$2 was not created!" | |
return | |
fi | |
local size="$(stat -c%s "$converted")" | |
if [ "$size" -gt 10485760 ]; then | |
needs2pass+=("$file") | |
fi | |
} | |
function framerate | |
{ | |
# If the file is a gif | |
if [ "${file##*.}" == "gif" ]; then | |
# Determine if the gif has a constant framerate | |
local cfr="$(identify -format "%T\n" "$1" | awk 'BEGIN{getline;d=$0;if($0==0){d=10}c=1} {t=$0;if(t==0){t=10}if(t==d){next}else{c=0;exit}} END{printf("%d",c)}')" | |
# If the framerate is variable | |
if [[ "$cfr" -eq 0 ]]; then | |
# Gets double the FPS based on the most commonly used frame duration. | |
#fps=$(identify -format "%T\n" "$1" | awk 'max<++c[$0]{if($0==0){max=c[10];line=10}else{max=c[$0];line=$0}} END{printf("%.2f",200/line)}') | |
# Gets double the minimum framerate. | |
#fps=$(identify -format "%T\n" "$1" | awk 'BEGIN{getline;m=$0} {d=$0;if($0==0){d=10}if(d<m){m=d}} END{printf("200/%d",m)}') | |
# Get size and duration of original gif | |
local size="$(identify -format "%wx%h" "$1[0]")" | |
local duration="$(identify -format "%T\n" "$1" | awk '{t+=$0;f++;if($0==0){t+=10}} END{printf("%.2f",t/100)}')" | |
# Fix inaccurate gif duration | |
# Commented out version had fixed some bug in the past related to off-by-one-frame duration issues. | |
# I'm not seeing this anymore though, so I've made a simpler version that *seems* to work just fine. | |
filter="color=s=$size:d=$duration:r=$fps:c=white[b];[0:v]fps=$fps:round=down[f];[b][f]overlay,$filter" | |
#local filter="color=c=white:s=$size:d=$duration:r=$fps,select=between(t\,0\,$duration-(1/$fps))[b];[0:v]fps=$fps,select=between(t\,0\,$duration-(1/$fps))[f];[b][f]overlay,$filter" | |
# Constant framerate | |
else | |
fps="$(identify -format "%T\n" "$1[0]" | awk '{d=$0;if(d==0){d=10}printf("100/%d",d)}')" | |
fi | |
if [[ "$fps" != *"/"* ]]; then | |
fps="${fps}/1" | |
fi | |
local tb="${fps#*/}/${fps%/*}" | |
outparams+=( | |
"-r" "$fps" | |
"-vsync" "cfr" | |
"-time_base" "$tb" | |
"-enc_time_base" "$tb" | |
"-video_track_timescale" "$fps" | |
) | |
else | |
duration="$(ffprobe -i "$1" -select_streams v:0 -show_entries stream=duration_ts -v quiet -of default=noprint_wrappers=1:nokey=1)" | |
frames="$(ffprobe -i "$1" -select_streams v:0 -show_entries stream=nb_frames -v quiet -of default=noprint_wrappers=1:nokey=1)" | |
tb="$(ffprobe -i "$1" -select_streams v:0 -show_entries stream=time_base -v quiet -of default=noprint_wrappers=1:nokey=1)" | |
#ctb="$duration/$(($frames * ${tb#*/}))" | |
#fps="$(($frames * ${tb#*/}))/$duration" | |
local test="$(ffprobe -i "$1" -select_streams v:0 -show_entries stream=duration -v quiet -of default=noprint_wrappers=1:nokey=1)" | |
if [ "$test" == "N/A" ]; then | |
fps="$(ffprobe -i "$1" -select_streams v:0 -show_entries stream=r_frame_rate -v quiet -of default=noprint_wrappers=1:nokey=1)" | |
else | |
fps="$(ffprobe -i "$1" -select_streams v:0 -show_entries stream=time_base -v quiet -of default=noprint_wrappers=1:nokey=1 | sed -r 's/([0-9]+)\/([0-9]+)/\2\/\1/')" | |
fi | |
outparams+=( | |
#"-time_base" "$tb" | |
#"-enc_time_base" "-1" | |
"-video_track_timescale" "$fps" | |
"-vsync" "passthrough" | |
) | |
fi | |
} | |
# Convert all files with standard settings. | |
function conv | |
{ | |
local outparams=("${outparams[@]}") | |
# Set fallback framerate of 25. | |
local fps="25" | |
local filter="$filter" | |
local tags="" | |
framerate "$1" | |
# Qualaity and preset are different for 2-pass encoding, so can't keep them in the base array. | |
# Filter gets changed by other parameters, so can't be either. | |
outparams+=( | |
"-qp" "$quality" | |
"-preset" "$preset" | |
"-lavfi" "$filter" | |
) | |
if [ -f "backup/$2" ]; then | |
if [ "$(attr "backup/$2" -ql)" == "xdg.tags" ]; then | |
tags="$(attr "backup/$2" -qg xdg.tags)" | |
fi | |
fi | |
echo "$1 -> $2" | |
ffmpeg "${inparams[@]}" -i "$1" "${outparams[@]}" -an "$outfolder/$2" | |
if [ "$tags" != "" ]; then | |
attr "$outfolder/$2" -qs xdg.tags -V "$tags" | |
fi | |
detect_2pass "$1" "$2" | |
} | |
# Attempt 2-pass conversion on files that are too large. | |
function run_2pass | |
{ | |
if [ "${1##*.}" == "gif" ]; then | |
local duration="$(identify -format "%T\n" "$1" | awk '{t+=$0;f++;if($0==0){t+=10}} END{printf("%.2f",t/100)}')" | |
else | |
local duration="$(ffprobe -i "$1" -select_streams v:0 -show_entries format=duration -v quiet -of csv=p=0)" | |
fi | |
local bitrate="$((49807360/(625*${duration%.*})))k" | |
local outparams=("${outparams[@]}") | |
local fps="25" | |
local tags="" | |
framerate "$1" | |
outparams+=( | |
"-passlogfile" "pass/ffmpeg2pass" | |
"-b:v" "$bitrate" | |
"-preset" "placebo" | |
"-lavfi" "$filter" | |
) | |
if [ -f "backup/$2" ]; then | |
if [ "$(attr "backup/$2" -ql)" == "xdg.tags" ]; then | |
tags="$(attr "backup/$2" -qg xdg.tags)" | |
fi | |
fi | |
make_folder "pass" | |
echo "$1 -> $2; pass 1" | |
ffmpeg "${inparams[@]}" -i "$1" "${outparams[@]}" -pass 1 -an -y -f mp4 /dev/null | |
echo "$1 -> $2; pass 2" | |
ffmpeg "${inparams[@]}" -i "$1" "${outparams[@]}" -pass 2 -an -y "$outfolder/$2" | |
if [ "$tags" != "" ]; then | |
attr "$outfolder/$2" -qs xdg.tags -V "$tags" | |
fi | |
detect_2pass "$1" "$2" | |
} | |
needs2pass=() | |
make_folder "$outfolder" | |
shopt -s nullglob | |
for file in *.*; do | |
shopt -u nullglob | |
converted="${file%.*}.mp4" | |
if [ -f "$outfolder/$converted" ]; then | |
detect_2pass "$file" "$converted" | |
else | |
conv "$file" "$converted" | |
fi | |
done | |
for file in "${needs2pass[@]}"; do | |
converted="${file%.*}.mp4" | |
needs2pass=("${needs2pass[@]:1}") | |
run_2pass "$file" "$converted" | |
done | |
if [ ${#needs2pass[@]} -gt 0 ]; then | |
make_folder "$outfolder/toobig" | |
for failed in "${needs2pass[@]}"; do | |
converted="${failed%.*}.mp4" | |
mv "$outfolder/$converted" "$outfolder/toobig/$converted" | |
done | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment