Create a thumbnail grid (8 cols, 5 rows) with additional meta data in the style of ezthumb but with ffmpeg.
- ffmpeg
- imagemagick (montage & convert)
- bc
- fonts-dejavu-core
#!/usr/bin/env bash | |
# Check if ffmpeg and imagemagick (montage) are installed | |
if ! command -v ffmpeg &> /dev/null; then | |
echo "FFmpeg is not installed." | |
exit 1 | |
fi | |
if ! command -v montage &> /dev/null; then | |
echo "ImageMagick is not installed." | |
exit 1 | |
fi | |
rows=5 | |
columns=8 | |
thumb_count=$((rows * columns)) | |
function create_thumbnail { | |
filename=$(basename -- "$1") | |
filename_no_ext="${filename%.*}" | |
output_dir=$(dirname "$1") | |
output_thumbnail="${output_dir}/${filename_no_ext}_thumb.jpg" | |
#rm -f "$output_thumbnail" | |
if [[ -e "$output_thumbnail" ]]; then | |
echo "Thumbnail for $filename already exists. Skipped creation." | |
return 0 | |
fi | |
# Extract metadata | |
metadata=$(ffmpeg -i "$1" 2>&1 | grep -E "Duration|Video:|Audio:") | |
duration=$(echo "$metadata" | grep "Duration" | awk '{print $2}' | tr -d ,) | |
# Calculate thumbnail interval | |
hours=$(echo "$duration" | cut -d: -f1 | sed 's/^0*//') | |
minutes=$(echo "$duration" | cut -d: -f2 | sed 's/^0*//') | |
seconds=$(echo "$duration" | cut -d: -f3 | cut -d. -f1 | sed 's/^0*//') | |
total_seconds=$((hours * 3600 + minutes * 60 + seconds)) | |
interval=$((total_seconds / thumb_count)) | |
# Determine filesize (for header) | |
file_size=$(stat -c%s "$1") | |
if (( file_size >= 1073741824 )); then | |
file_size_gb=$(echo "scale=2; $file_size / 1073741824" | bc) | |
file_size_display="${file_size_gb} GB" | |
else | |
file_size_mb=$(echo "scale=2; $file_size / 1048576" | bc) | |
file_size_display="${file_size_mb} MB" | |
fi | |
temp_dir=$(mktemp -d -t thumbnailer-XXXXXX) | |
echo -n "Creating $thumb_count thumbnails now" | |
for i in $(seq 0 $((thumb_count - 1))); do | |
#echo "Creating thumbnail $((i + 1)) of $thumb_count..." | |
timestamp=$((i * interval)) | |
timestamp_formatted=$(printf '%02d:%02d:%02d' $((timestamp/3600)) $(((timestamp%3600)/60)) $((timestamp%60))) | |
( | |
ffmpeg -hide_banner -loglevel error -ss "$timestamp_formatted" -i "$1" -vframes 1 "$temp_dir/thumb_$i.png" | |
echo -n "." | |
) & | |
if (( (i + 1) % columns == 0 )); then | |
wait | |
fi | |
done | |
wait | |
echo | |
montage "$temp_dir/thumb_*.png" -tile "${columns}x${rows}" -geometry 320x+2+2 "$temp_dir/grid.png" | |
header_text=$(echo -e "Name: $filename\nSize: ${file_size_display}\n$metadata" | sed 's/^[[:space:]]*//gm') | |
convert "$temp_dir/grid.png" \ | |
-gravity NorthWest -background White -splice 0x150 \ | |
-fill black -font "DejaVu-Sans-Mono" -pointsize 18 -annotate +10+10 "$header_text" \ | |
"$output_thumbnail" | |
rm -rf "$temp_dir" | |
echo "$output_thumbnail created!" | |
} | |
for input_video in "$@"; do | |
# Check if input_video exists | |
if [ ! -f "$input_video" ]; then | |
echo "Input file not found: $input_video" | |
else | |
create_thumbnail "$input_video" | |
fi | |
done | |
echo "done" |