#!/bin/sh # # video-thumbnails: A simple shell script to generate video thumbnails/screencaps # # Author: rumia # License: WTFPL # function quit { rm -rf "$1" exit } function get_seconds { if [[ "$1" == *:* ]]; then local tm_rev="$(echo "$1" | rev)" local ss="$(echo "$tm_rev" | cut -d: -f1 | rev)" local mm="$(echo "$tm_rev" | cut -d: -f2 | rev)" local hh="$(echo "$tm_rev" | cut -d: -f3 | rev)" local hhmmss="$(printf "%02d:%02d:%02d" "${hh:-0}" "${mm:-0}" "${ss:-0}")" date +%s -d "1970-01-01 $hhmmss UTC" else echo $1 fi } usage="Generate screencaps for a video Usage: $(basename "$0") [options] video-filename [interval-in-seconds] Options: -t w[xh] Numbers of tile (default: 4) -q quality Image quality (default: 98) -d n Divisor for frame dimension (default: 2) -b color Background color (default: white) -f font Default font (default: Droid-Sans-Regular) -s size Default text size (default: 15) -p position Timestamp position (default: NorthWest) -c color Timestamp color (default: rgba(255, 255, 255, 0.7)) -o color Timestamp outline color (default: rgba(0, 0, 0, 0.2)) -w px Timestamp outline width (default: 2) -F font Header font (will use default font if not specified) -S size Header text size (will use default text size if not specified) -P position Header text position (default: NorthWest) -C color Header text color (default: black) -B color Header background color (the default is the color specified by -b) -k n Skip n seconds before capturing -h Display this help " text_font="Droid-Sans-Regular" text_size=15 timestamp_color="rgba(255, 255, 255, 0.7)" timestamp_offset="+10+5" timestamp_position="NorthWest" outline_color="rgba(0, 0, 0, 0.2)" outline_width=2 quality=98 tile=4 dim_divisor=2 background="white" skip_sec=0 while getopts ":b:f:s:c:p:o:w:q:t:d:F:S:P:B:C:k:h" opt; do case "$opt" in b) background="$OPTARG" ;; f) text_font="$OPTARG" ;; s) text_size="$OPTARG" ;; c) timestamp_color="$OPTARG" ;; p) timestamp_position="$OPTARG" ;; o) outline_color="$OPTARG" ;; w) outline_width="$OPTARG" ;; q) quality="$OPTARG" ;; t) tile="$OPTARG" ;; d) dim_divisor="$OPTARG" ;; F) header_font="$OPTARG" ;; S) header_text_size="$OPTARG" ;; P) header_position="$OPTARG" ;; C) header_color="$OPTARG" ;; B) header_background="$OPTARG" ;; k) skip_sec=$(get_seconds "$OPTARG") ;; h) echo "$usage" exit 0 ;; :) echo "Option -$OPTARG requires a value" >&2 exit 1 ;; esac done shift $((OPTIND-1)) input="$1" if [ "$input" = "" ]; then echo "$usage" >&2 exit 1 fi if [ ! -e "$input" ]; then echo "No such file: $input" >&2 exit 1 fi filesize="$(stat -c %s "$input")" if [ ${filesize:-0} -lt 1 ]; then echo "Empty video" >&2 exit 2 fi if [ $filesize -ge 1024 ]; then if [ $filesize -gt 1073741824 ]; then suffix="G" fs_div=1073741824 elif [ $filesize -ge 1048576 ]; then suffix="M" fs_div=1048576 else suffix="K" fs_div=1024 fi filesize_info="$filesize B ($(echo "scale=2; $filesize / $fs_div" | bc -l) ${suffix}iB)" else filesize_info="$filesize B" fi input_basename="$(basename "$input")" input_filename="${input_basename%.*}" interval=${2:-50} interval=$(get_seconds $interval) if [ $interval -lt 1 ]; then interval=50 fi info="$(ffmpeg -i "$input" 2>&1)" duration="$(echo "$info" | grep Duration | sed -r 's/^\s*Duration: ([:0-9]+).*$/\1/')" if [ "$duration" = "" -o "$duration" = "00:00:00" ]; then echo "Empty video" >&2 exit 2 fi total_seconds="$(date +%s -d "1970-01-01 $duration UTC")" if [ $total_seconds -lt 1 ]; then echo "Empty video" >&2 exit 2 elif [ $interval -ge $total_seconds ]; then echo "The interval must be less than total length of the video: total=$total_seconds interval=$interval" >&1 exit 3 fi ndigit=${#total_seconds} dimension="$(echo "$info" | grep '^\s*Stream.*Video' | cut -d , -f 3 | sed -r 's/^\s*([0-9x]+).*$/\1/')" width="$(echo "$dimension" | cut -d x -f 1)" height="$(echo "$dimension" | cut -d x -f 2)" thumb_width=$(( $width / $dim_divisor )) thumb_height=$(( $height / $dim_divisor )) thumb_geom="${thumb_width}x${thumb_height}" temp_dir=$(mktemp -td vidthumbs.XXXXXX) if [ $? -ne 0 ]; then echo "Unable to create temporary directory" >&2 exit 4 fi trap 'quit "$temp_dir"' EXIT SIGINT SIGTERM printf "Generating thumbnails: " >&2 thumb_index=0 start_sec=$interval if [ $skip_sec -gt 0 ]; then start_sec=$(( $start_sec + $skip_sec )) fi for (( i = $start_sec; i < $total_seconds ; i += $interval )); do hh=$(( ($i / 3600) % 24 )) mm=$(( ($i / 60) % 60 )) ss=$(( $i % 60 )) timestamp="$(printf "%02d:%02d:%02d" $hh $mm $ss)" frame_file="$temp_dir/.frame.jpg" ((thumb_index++)) ffmpeg \ -ss $i \ -i "$input" \ -s $thumb_geom \ "$frame_file" \ -r 1 \ -vframes 1 \ -qscale 1 \ -an \ -vcodec mjpeg > /dev/null 2>&1 thumb_name="$(printf "%s/%0${ndigit}d.jpg" "$temp_dir" "$thumb_index")" convert \ "$frame_file" \ -gravity "$timestamp_position" \ -font "$text_font" \ -pointsize "$text_size" \ -stroke "$outline_color" \ -strokewidth $outline_width \ -fill none \ -annotate "$timestamp_offset" "$timestamp" \ -stroke none \ -fill "$timestamp_color" \ -annotate "$timestamp_offset" "$timestamp" \ -quality "$quality" \ "$thumb_name" printf "\rGenerating thumbnails: %d image(s)" "$thumb_index" >&2 done echo if [ $thumb_index -lt 2 ]; then montage_file="$thumb_name" montage_width="$thumb_width" else echo -n "Creating montage: " >&2 montage_file="$temp_dir/.montage.jpg" montage "$temp_dir"/*.jpg -geometry $thumb_geom -tile "$tile" -background "$background" -quality "$quality" "$montage_file" montage_dimension="$(identify -format "%wx%h" "$montage_file")" montage_width="$(echo "$montage_dimension" | cut -d x -f 1)" printf " %s px @ %d tile(s) created.\n" "$montage_dimension" "$tile" >&2 fi header="File Name: $input_basename File Size: $filesize_info $(echo "$info" | sed -rn 's/^\s+//;/^(Duration|Stream)/p')" echo "Creating preview header..." >&2 header_file="$temp_dir/.header.gif" convert \ -size "$(( $montage_width - 10 ))x" \ -background "${header_background:-$background}" \ -font "${header_font:-$text_font}" \ -pointsize "${header_text_size:-$text_size}" \ -fill "${header_color:-black}" \ -stroke none \ -gravity "${header_position:-NorthWest}" \ -bordercolor "${header_background:-$background}" \ -border 5x5 \ caption:"$header" \ "$header_file" output_filename="${3:-${input_filename}-preview.jpg}" montage -mode concatenate -tile 1 "$header_file" "$montage_file" -background "$background" -quality "$quality" "$output_filename" echo "Generated preview: $output_filename" >&2