: ########################################################################## # Title : mkvideothumb - create animated GIF as a preview for a video # Author : Heiner Steven # Date : 2015-04-13 # Category : Video # Requires : convert, ffmpeg, showvideoduration # SCCS-Id. : @(#) mkvideothumb 1.2 18/05/03 ########################################################################## # Description # # TODO ########################################################################## PN=`basename "$0"` # Program name VER='1.2' set -u : ${PREVIEW_IMAGE_COUNT:=20} : ${DEFAULT_FRAME_DELAY=1/1} WorkDir=${TMPDIR:=/tmp}/$PN.$$ Logfile=$WorkDir/$PN-$$.log PublicLog=$TMPDIR/$PN.log usage () { echo >&2 "$PN - create animated GIF as a preview for a video, $VER usage: $PN [-hf] [{-d delay | -r rate}] [-n imgcnt] [-s size] \ [-o outfile] video [...] -d delay (in seconds) between each frame (default: $DEFAULT_FRAME_DELAY). Use a quotient for specifying parts of a second, e.g. 1/2 for 0.5s. -r (rate) images per second to show -f overwrite existing files; continue after errors (default: false) -n number of preview images to extract (default: $PREVIEW_IMAGE_COUNT) -o output file name (must end with '.gif'). Default: input file name with '.gif' extension) -s video frame size (width x height, default: same as video) -h: print this usage summary" exit 1 } msg () { echo >&2 "$PN:" "$@"; } fatal () { msg "$@"; exit 1; } tmpimgext=.jpg tmpimgfmt="frame_%05d${tmpimgext}" ########################################################################## # extract_thumb_images - extract sample images from the video stream ########################################################################## extract_thumb_images () { [ $# -eq 1 ] || fatal "usage: extract_thumb_images inputfile" : ${WorkDir?} ${Logfile?} : ${rate?} ${tmpimgfmt?} : ${filenum?} : ${framewidth?} ${frameheight?} #_prefix=`basename "$1"` _prefix="file_`echo "00000$filenum" | sed 's/^.*\(.....\)$/\1/'`" if [ -n "${framewidth}${frameheight}" ] then scalearg="-s ${framewidth}x${frameheight}" else scalearg= fi set -x ffmpeg -y -i "$1" -r $rate $scalearg "$WorkDir/${_prefix}_$tmpimgfmt" \ >> "$Logfile" 2>&1 _error=$? set +x unset _prefix return $_error } ########################################################################## # create_thumb - combine sample images to an animated GIF image ########################################################################## create_thumb () { [ $# -eq 1 ] || fatal "usage: create_thumb outputfile" : ${WorkDir?} ${Logfile?} : ${tmpimgext?} # Write to temporary file _tmpdir=`dirname "$1"` _tmpbase=`basename "$1"` _tmpfile=$_tmpdir/.$$.${_tmpbase} _delayarg= [ -n "$frame_delay" ] && _delayarg="-delay ${frame_delay}" [ -n "$frame_rate" ] && _delayarg="-delay 1x${frame_rate}" set -x convert -fuzz 1% +dither $_delayarg "$WorkDir/*$tmpimgext" -coalesce \ -layers OptimizeTransparency "$_tmpfile" >> "$Logfile" 2>&1 set +x if [ $_error -eq 0 ] && [ -s "$_tmpfile" ] && mv -f -- "$_tmpfile" "$1" then _error=0 else _error=1 rm -f -- "$_tmpfile" fi unset _tmpdir _tmpbase _tmpfile _delayarg delete_tempfiles return $_error } ########################################################################## delete_tempfiles () { : ${WorkDir?} ${tmpimgext?} #set -x rm -f "$WorkDir/"*"$tmpimgext" #set +x } ########################################################################## is_valid_integer () { [ $# -eq 1 ] || fatal "usage: $0 string" case "$1" in *[!0-9]*) return 1;; *) return 0;; esac } parse_duration () { [ $# -eq 1 ] || fatal "usage: $0 string" if is_valid_integer "$1" then echo "$1" return 0 else # Format: seconds[/divisor] set -- `echo "$1" | sed 's/[ ]//g' | awk ' # Examples: "640x480", "x640", "640x" /^[0-9][0-9]*\/[0-9][0-9]*$/ || /^\/[0-9][0-9]*$/ || /^[0-9][0-9]*\/$/ { split($0, f, /\//) print f[1] + 0, f[2] + 0 } ' ` if [ $# -eq 2 ] then echo "${1}x${2}" return 0 fi fi return 1 } ########################################################################## parse_frame_size () { [ $# -eq 1 ] || fatal "usage: parse_frame_size argument" # Format: width[x height] set -- `echo "$1" | sed 's/[ ]//g' | awk ' # Examples: "640x480", "x640", "640x" /^[0-9][0-9]*x[0-9][0-9]*$/ || /^x[0-9][0-9]*$/ || /^[0-9][0-9]*x$/ { split($0, f, /x/) print f[1] + 0, f[2] + 0 } ' ` if [ $# -eq 2 ] then FrameWidth=$1 FrameHeight=$2 return 0 fi return 1 } ########################################################################## FrameDelay= FrameRate= PreviewImageCount= ForceRun=false Output= while getopts :d:fhn:o:r:s: opt do case "$opt" in d) FrameDelay=`parse_duration "$OPTARG"` [ -n "$FrameDelay" ] || fatal "ERROR: invalid frame delay: $OPTARG" ;; f) ForceRun=true;; h) usage;; n) if is_valid_integer "$OPTARG" then PreviewImageCount=$OPTARG else fatal "ERROR: invalid number of images: $OPTARG" fi ;; o) Output=$OPTARG;; r) if is_valid_integer "$OPTARG" then FrameRate=$OPTARG else fatal "ERROR: invalid frame rate: $OPTARG" fi ;; s) # Set FrameWidth and FrameHeight if parse_frame_size "$OPTARG" then : ${FrameWidth?} ${FrameHeight?} else fatal "ERROR: invalid frame size: $OPTARG" fi ;; ?) usage;; esac done shift `expr ${OPTIND-1} - 1` [ $# -lt 1 ] && usage if [ -n "$FrameDelay" ] && [ -n "$FrameRate" ] then msg "ERROR: options -d and -r are mutually exclusive" fatal "($PN -h prints a usage message)" fi preview_image_count=${PreviewImageCount:-$PREVIEW_IMAGE_COUNT} frame_delay=${FrameDelay:=`parse_duration "$DEFAULT_FRAME_DELAY}"`} frame_rate=${FrameRate:-} framewidth=${FrameWidth:-} frameheight=${FrameHeight:-} if [ -n "$Output" ] && [ $# -gt 1 ] then combine_images=true else combine_images=false fi #msg "DEBUG: ${frame_delay:+delay ${frame_delay}s after each image} ${frame_rate:+animation speed $frame_rate images/s} ${framewidth:+size ${framewidth}x$frameheight}" ########################################################################## if mkdir "$WorkDir" then trap 'rm -rf "$WorkDir" >&2' 0 trap "exit 1" 1 2 3 13 15 else fatal "ERROR: cannot create temporary directory '$WorkDir'" fi error=0 filenum=0 # File number, used as part of temporary file name for videofile do filenum=`expr $filenum + 1` errormsg= [ -f "$videofile" ] || : ${errormsg:="cannot read file '$videofile'"} [ -s "$videofile" ] || : ${errormsg:="file '$videofile' is empty"} if [ -z "$errormsg" ] then # Result file name thumbfile=${Output:-`echo "$videofile" | sed 's/\.[^.]*$/.gif/'`} if [ -f "$thumbfile" ] && [ -s "$thumbfile" ] && [ "$ForceRun" != "true" ] then msg "WARNING: will not overwrite existing file '$thumbfile'. Use option -f for overwriting existing result files." continue fi fi if [ -z "$errormsg" ] then durationtxt=`showvideoduration -s "$videofile"` duration=`echo "$durationtxt" | awk '{print $1 + 0}'` if [ -z "$durationtxt" ] || [ "$duration" = "0" ] && [ "$ForceRun" != "true" ] then errormsg="\ cannot determine length of video '$videofile' This usually means that the input file is not valid video file. If you want to process it nevertheless, use option -f." fi fi if [ -z "$errormsg" ] then rate=`awk ' BEGIN { imagecount = '"$preview_image_count"' + 0 duration = '"$duration"' + 0 if (duration < imagecount) duration = 1 print imagecount / duration } '` msg "DEBUG: $videofile; duration=$duration rate=$rate" : ${rate:=$preview_image_count} if extract_thumb_images "$videofile" then if [ "$combine_images" != "true" ] then if create_thumb "$thumbfile" then msg "INFO: wrote file '$thumbfile'" else errormsg="cannot create thumb file '$thumbfile'" fi fi else errormsg="cannot extract thumb images from stream '$videofile'" fi fi if [ -n "$errormsg" ] then if [ "$ForceRun" != "true" ] then msg "ERROR: $errormsg" error=1 break else msg "WARNING: $errormsg - ignored" fi fi done if [ "$error" = "0" ] && $combine_images then if create_thumb "$thumbfile" then msg "INFO: wrote thumb file '$thumbfile'" else msg "ERROR: cannot create thumb file '$thumbfile'" error=1 fi fi if [ "$error" != "0" ] && [ -s "$Logfile" ] then cp -f "$Logfile" "$PublicLog" msg "NOTE: the following file contains log messages: $PublicLog" fi exit $error ########################################################################## #ffmpeg -i "$videofile" -r $rate -vf scale=480:-1 "$WorkDir/frame_%04d.png" #ffmpeg -i "$videofile" -r $rate "$WorkDir/frame_%05d.jpg" ##ffmpeg -i "$videofile" -t 60 -r 10000 -vf scale=480:-1 "$WorkDir/thumb.gif" #ffmpeg -y -loop 1 -r 1 -i "$WorkDir/frame_%04d.png" -c:v libx264 -r 30 -pix_fmt yuv420p "$WorkDir/slideshow.mp4" ##ffmpeg -y -r 1/1 -i "$WorkDir/frame_%05d.jpg" -vcodec libx264 "$WorkDir/slideshow.mp4" #convert -fuzz 1% +dither -delay 1x1 "$WorkDir/*.jpg" -coalesce -layers OptimizeTransparency "$WorkDir/animation.gif"