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" |