Subversion Repositories pub

Compare Revisions

No changes between revisions

Ignore whitespace Rev 264 → Rev 265

/video-contact-sheet/tags/1.0a/vcs
0,0 → 1,595
#!/bin/bash
 
# $Rev: 246 $ $Date: 2007-04-11 00:06:25 +0200 (dc, 11 abr 2007) $
 
#
# History:
#
# 1.0a:
# * First release keeping track of history
# * Put vcs' url in the signature
# * Use system username in signature
# * Added --shoehorn (you get the idea, right?) to feed extra commands to
# the cappers. Lowelevel and not intended to be used anyway :P
# * When just a vidcap is requested, take it from the middle of the video
# * Added -H|--height
#
# 0.99.1a: Interim version, renamed to 1.0a
#
# 0.99a:
# * Added shadows
# * More colourful headers
# * Easier change of colours/fonts
#
# 0.5a: * First usable version
# 0.1: * First proof of concept
 
declare -r VERSION="0.99.1a"
 
# Options
declare -i interval=300 # Interval of captures (= numsecs / numcaps)
declare -i numcaps=16 # Number of captures (= numsecs / interval)
 
declare user=$(id -un)
declare -r USER_SIGNATURE="Preview created by"
declare -r PROGRAM_SIGNATURE="with Video Contact Sheet *NIX ${VERSION} <http://p.outlyer.net/vcs/>"
 
# Options used in imagemagick, these options set the final aspect
# of the contact sheet so tweak them to your liking
#
declare -i OUTPUT_QUALITY=92 # Output image quality (only affects the final image,
# and only in lossy formats)
# Colours, see convert -list color
declare -r BG_META=YellowGreen # Background for meta info (size, codec...)
declare -r BG_SIGN=SandyBrown # Background for signature
declare -r FG_META=black # Font colour for meta info box
declare -r FG_SIGN=black # Font colour for signature
declare -r FG_STAMPS=white # Font colour for timestamps
# Fonts, see convert -list type
declare -r FONT_STAMPS=courier # Used for timestamps behind the thumbnails
declare -r FONT_META=helvetica # Used for meta info box
declare -r FONT_SIGN=$FONT_META # Used for the signature box
# Font sizes, in points
declare -i PS_STAMPS=18 # Used for the timestamps
declare -i PS_META=16 # Used for the meta info box
declare -i PS_SIGN=11 # Used for the signature
 
# Constants and other internal usage variables, no need to mess with this!
# Which of the two methods should be used to guess the number of thumbnails
declare -ri INTERVAL=1 NUMCAPS=3
declare -i derive_from=$INTERVAL
declare -ri DEFAULT_INTERVAL=$interval
# Which of the two vidcappers should be used (see -F, -M)
# mplayer seems to fail for mpeg or WMV9 files, at least on my system
declare -ri METHOD_MPLAYER=1
declare -ri METHOD_FFMPEG=2
declare -i METHOD=$METHOD_FFMPEG
 
# Internal variables:
declare title=""
declare -i fromtime=0 # Starting second (see -f)
declare -i totime=-1 # Ending second (see -t)
declare initial_stamps=( ) # Stamps added to the calculated ones (see -S)
declare OUTFMT=png
declare shoehorned= # See --shoehorn
declare -i th_height= # Height of the thumbnails, by default use same as input
declare -i cols=2 # Number of output columns
 
# {{{ # Convenience functions
 
is_number() {
egrep -q '^[0-9]+$' <<<"$1"
return $?
}
 
get_interval() {
if is_number "$1" ; then echo $1 ; return 0 ; fi
 
local s=$(tr '[A-Z]' '[a-z]' <<<"$1")
local len_n=$(( ${#s} - 1 )) # Length of the theoretical numeric part
local u=${s:$len_n}
local n=${s:0:$len_n}
local i=0
 
if ! is_number "$n" ; then return 1 ; fi
 
case "$u" in
s) i=$n ;;
m) i=$((n * 60)) ;;
h) i=$((n * 3600)) ;;
*) return 2 ; break ;;
esac
 
echo $i
}
 
pad() {
local len=$1
local str=$2
 
while [ ${#str} -lt $len ]; do
str=0$str
done
 
echo $str
}
 
pretty_stamp() {
if ! is_number "$1" ; then return 1 ; fi
 
local t=$1
local h=$(( $t / 3600 ))
t=$(( $t % 3600 ))
local m=$(( $t / 60 ))
t=$(( $t % 60 ))
local s=$t
 
local R=""
 
if [ $h -gt 0 ]; then
R+="$h:"
fi
R+=$(pad 2 "$m"):$(pad 2 $s)
 
echo $R
}
 
get_pretty_size() {
local f="$1"
 
local bytes=$(du -DL --bytes "$f" | cut -f1)
local size=""
 
if [ "$bytes" -gt $(( 1024**3 )) ]; then
local gibs=$(( $bytes / 1024**3 ))
local mibs=$(( ( $bytes % 1024**3 ) / 1024**2 ))
size="${gibs}.${mibs:0:2} GiB"
elif [ "$bytes" -gt $(( 1024**2)) ]; then
local mibs=$(( $bytes / 1024**2 ))
local kibs=$(( ( $bytes % 1024**2 ) / 1024 ))
size="${mibs}.${kibs:0:2} MiB"
elif [ "$bytes" -gt 1024 ]; then
local kibs=$(( $bytes / 1024 ))
bytes=$(( $bytes % 1024 ))
size="${kibs}.${bytes:0:2} KiB"
else
size="${bytes} B"
fi
 
echo $size
}
 
# Rename a file, if the target exists, try with -1, -2, ...
safe_rename() {
local from="$1"
local to="$2"
 
local ext=$(sed -r 's/.*\.(.*)/\1/g' <<<"$to")
 
local n=1
while [ -f "$to" ]; do
to="$(basename "$2" .$ext)-$n.$ext"
n=$(( $n + 1))
done
 
mv "$from" "$to"
}
 
test_programs() {
for prog in mplayer convert montage ffmpeg ; do
type -pf "$prog" >/dev/null
local retval=$?
if [ $retval -ne 0 ] ; then
error "Required program $prog not found!"
return $retval
fi
done
}
 
# Print some text to stderr
error() {
echo "$1" >&2
}
 
# }}} # Convenience functions
 
# {{{ # Core functionality
 
process() {
local f=$1
 
local numcols=$cols
 
if [ ! -f "$f" ]; then
error "File \"$f\" doesn't exist"
return 100
fi
echo "Processing $f..." >&2
 
# Meta data extraction
# Don't change the -vc as it would affect $vdec
local mplayer_cache=$(mplayer -benchmark -ao null -vo null -identify -frames 0 -quiet "$f" 2>/dev/null | grep ^ID)
local vcodec=$(grep ID_VIDEO_FORMAT <<<"$mplayer_cache" | cut -d'=' -f2) # FourCC
local acodec=$(grep ID_AUDIO_FORMAT <<<"$mplayer_cache" | cut -d'=' -f2)
local vdec=$(grep ID_VIDEO_CODEC <<<"$mplayer_cache" | cut -d'=' -f2) # Decoder
local width=$(grep ID_VIDEO_WIDTH <<<"$mplayer_cache" | cut -d'=' -f2)
local height=$(grep ID_VIDEO_HEIGHT <<<"$mplayer_cache" | cut -d'=' -f2)
local fps=$(grep ID_VIDEO_FPS <<<"$mplayer_cache" | cut -d'=' -f2)
# Vidcap/Thumbnail height
local vidcap_height=$th_height
if ! is_number "$vidcap_height" || [ "$vidcap_height" -eq 0 ]; then
vidcap_height=$height
fi
 
local numsecs=$(grep ID_LENGTH <<<"$mplayer_cache"| cut -d'=' -f2 | cut -d. -f1)
if ! is_number $numsecs ; then
error "Internal error!"
return 15
fi
 
local nc=$numcaps
local in=$interval
 
# Start bound:
local startsec=0
if [ $startsec -lt $fromtime ]; then
startsec=$fromtime
fi
 
# End bound:
local endsec=$numsecs
if [ $totime -ne -1 ] && [ $endsec -gt $totime ]; then
endsec=$totime
fi
if [ $startsec -ne 0 ] || [ $endsec -ne $numsecs ]; then
echo "Restricting to range [$startsec..$endsec]" >&2
fi
 
local delta=$(( $endsec - $startsec ))
# FIXME: the total # of caps is currently broken when using -f and -t
 
# Note that when numcaps mandates the interval is obtained from
# the actually allowed secods (hence it is not movie_length / numcaps )
# Adjust interval/numcaps:
if [ "$derive_from" -eq "$INTERVAL" ]; then
# Interval rules => it doesn't change
nc=$(( $delta / $interval ))
# If a multiple, an extra vidcap is generated (at the last second)
if [ $(( $delta % $interval )) -eq 0 ]; then
nc=$(( $nc + 1 ))
fi
elif [ "$derive_from" -eq "$NUMCAPS" ]; then
# Numcaps rules => it doesn't change
if [ $numcaps -eq 1 ]; then # If just one cap, center it
in=$(( $numsecs / 2 ))
else
in=$(( $numsecs / $numcaps ))
fi
else
error "Internal error!"
return 145
fi
 
# Let's try to make some sense...
# Minimum interval allowance:
if [ $in -gt $numsecs ]; then
error "The interval is longer than the video length."
error "Use a lower interval or numcaps instead."
error "Skipping \"$f\"."
return 16
fi
# Contact sheet minimum cols:
if [ $nc -lt $numcols ]; then
numcols=$nc
fi
 
# Tempdir
 
local dir=$(mktemp -d -p . vcs.XXXXXX)
if [ "$?" -ne 0 ]; then
error "Error creating temporary directory"
return 17
fi
 
# Get the stamps...
 
local n=$(( $startsec + $in ))
local stamps=( ${initial_stamps[*]} $n )
 
while [ $n -le $endsec ]; do
n=$(( $n + $in ))
if [ $n -gt $endsec ]; then break; fi
 
stamps=( ${stamps[*]} $n )
done
 
n=1
local p=""
local cap=""
local montage_command="montage -font $FONT_STAMPS -pointsize $PS_STAMPS \
-gravity SouthEast -fill white "
local output=$(tempfile --prefix "vcs-" --suffix '-preview.png' -d .)
 
# Let's reorder the stamps, this away user-added stamps get their correct position
local temp=""
for stamp in ${stamps[*]} ; do
temp+="$stamp\n"
done
stamps=$(echo -e $temp | sort -n)
 
local VIDCAPFILE=00000001.png
 
# If the temporal vidcap already exists, abort
if [ -f $VIDCAPFILE ]; then
error "Temporal vidcap file ($VIDCAPFILE) exists, remove it before running!."
exit 134
fi
 
# TODO: Aspect ratio
for stamp in $stamps; do
# Note that it must be checked against numsecs and not endsec, to allow
# the user manually setting stamps beyond the boundaries
if [ $stamp -gt $numsecs ]; then continue; fi
 
echo "Generating capture #${n}/${#stamps[*]}..." >&2
 
if [ $METHOD -eq $METHOD_MPLAYER ]; then
mplayer -sws 9 -ao null -benchmark -vo "png:z=0" -quiet \
-frames 1 -ss $stamp $shoehorned "$f" >/dev/null 2>&1
elif [ $METHOD -eq $METHOD_FFMPEG ]; then
ffmpeg -y -ss $stamp -i "$f" -an -dframes 1 -vframes 1 -vcodec png \
-f rawvideo $shoehorned $VIDCAPFILE >/dev/null 2>&1
else
error "Internal error!"
return 142
fi
 
p=$(pad 6 $stamp).png
# mv 00000001.png $dir/$p
n=$(( $n + 1 ))
 
cap=$dir/$p
# Add the timestamp to each vidcap, doing it hear is much powerful/simple
# than with the next montage command
convert -box '#000000aa' \
-fill $FG_STAMPS -pointsize $PS_STAMPS -gravity SouthEast \
-stroke none -strokewidth 3 -annotate +5+5 " $(pretty_stamp $stamp) " \
$VIDCAPFILE "$cap"
 
montage_command+=" $cap"
done
rm -f $VIDCAPFILE
 
# geometry affects the source images, not the target one!
# Note the file name could also be added by using the "-title" option, but I reserved
# it for used set titles
montage_command+=" -geometry x${vidcap_height}+10+5 -tile ${numcols}x -shadow"
if [ "$title" ]; then
montage_command+=" -font $FONT_META -fill $FG_META -title '$title'"
fi
montage_command+=" $output"
 
echo "Composing contact sheet..." >&2
eval $montage_command # eval is required to evaluate correctly the text in quotes!
mv "$output" . 2>/dev/null || true
 
# Codec "prettyfication"
case $( tr '[A-Z]' '[a-z]' <<<$vcodec) in
xvid) vcodec=Xvid ;;
dx50) vcodec="DivX 5" ;;
0x10000001) vcodec="MPEG-1" ;;
0x10000002) vcodec="MPEG-2" ;;
avc1) vcodec="MPEG-4 AVC" ;;
esac
if [ "$vdec" == "ffodivx" ]; then
vcodec+=" (MPEG-4)"
elif [ "$vdec" == "ffh264" ]; then
vcodec+=" (h.264)"
fi
 
case $( tr '[A-Z]' '[a-z]' <<<$acodec ) in
85) acodec='MPEG-1 Layer III (MP3)' ;;
80) acodec='MPEG-1 Layer II (MP2)' ;;
mp4a) acodec='MPEG-4 AAC' ;;
"") acodec="no audio" ;;
esac
 
 
local meta="Filename: $f
File size: $(get_pretty_size "$f")
Length: $(pretty_stamp "$numsecs")"
local meta2="Dimensions: ${width}x${height}
Format: $vcodec / $acodec
FPS: $fps"
local signature="$USER_SIGNATURE $user
$PROGRAM_SIGNATURE"
 
# Now let's add meta info
# This one enlarges the image to add the text, and puts
# meta info in two columns
convert -font $FONT_META -pointsize $PS_META \
-background $BG_META -fill $FG_META -splice 0x$(( $PS_META * 4 )) \
-gravity NorthWest -draw "text 10,10 '$meta'" \
-gravity NorthEast -draw "text 10,10 '$meta2'" \
"$output" "$output"
# Finishing touch, signature
 
convert -gravity South -font $FONT_SIGN -pointsize $PS_SIGN \
-background $BG_SIGN -splice 0x34+0-0 \
-fill $FG_SIGN -draw "text 10,3 '$signature'" "$output" "$output"
rm -rf $dir/
 
if [ $OUTFMT != "png" ]; then
local newout="$(basename "$output" .png).$OUTFMT"
convert -quality $OUTPUT_QUALITY "$output" "$newout"
rm "$output"
output="$newout"
fi
safe_rename "$output" "$(basename "$f").$OUTFMT"
}
 
show_help() {
local P=$(basename $0)
cat <<EOF
Video Contact Sheet *NIX v${VERSION}, (c) 2007 Toni Corvera
 
Usage: $P [options] <file>
 
Options:
-i|--interval <arg> Set the interval to arg. An optional unit can be
used (case-insensitive) , e.g.:
Seconds: 90
Minutes: 3m
Hours: 1h
Use either -i or -n.
-n|--numcaps <arg> Set the number of captured images to arg. Use either
-i or -n.
-f|--from <arg> Set starting time. No caps before this. Same format
as -i.
-t|--to <arg> Set ending time. No caps beyond this. Same format
as -i.
-T|--title <arg> Add a title above the vidcaps.
-u|--user <arg> Set the username found in the signature to this.
-S|--stamp <arg> Add the image found at the timestamp "arg", same format
as -i.
-H|--height <arg> Set the output (individual thumbnail) height. Width is
derived accordingly. Note width cannot be manually set.
-j|--jpeg Output in jpeg (by default output is in png).
-h|--help Show this text.
 
Options used for debugging purposes or to tweak the internal workings:
-M|--mplayer Force the usage of mplayer.
-F|--ffmpeg Force the usage of ffmpeg.
--shoehorn <arg> Pass "arg" to mplayer/ffmpeg. You probably don't need it.
 
Examples:
Create a contact sheet with default values (vidcaps at intervals of
$DEFAULT_INTERVAL seconds), the resulting file will be called
input.avi.png:
\$ $P input.avi
 
Create a sheet with vidcaps at intervals of 3 minutes:
\$ $P -i 3m input.avi
 
Create a sheet with vidcaps starting at 3 mins and ending at 18 mins,
add an extra vidcap at 2m and another one at 19m:
\$ $P -f 3m -t 18m -S2m -S 19m input.avi
 
Quirks:
Currently MPEG handling seems to be broken.
 
EOF
}
 
# }}} # Core functionality
 
# Test requirements
test_programs || exit 54
 
# {{{ # Command line parsing
 
# Based on getopt-parse.bash example.
# On debian systems see </usr/share/doc/util-linux/examples/getopt-parse.bash.gz>
 
# FIXME: use username / no name at all with -u noarg, and not -u
TEMP=$(getopt -o i:n:u:T:f:t:S:jhFMH:c: \
--long "interval:,numcaps:,username:,title:,from:,to:,stamp:,jpeg,help,shoehorn:,mplayer,ffmpeg,height:,columns:" \
-n $0 -- "$@")
 
eval set -- "$TEMP"
 
while true ; do
case "$1" in
-i|--interval)
if ! interval=$(get_interval "$2") ; then
error "Interval must be a number (given $2)"
exit 68
fi
if [ "$interval" -le 0 ]; then
error "Interval must be higher than 0, set to the default $DEFAULT_INTERVAL"
interval=$DEFAULT_INTERVAL
fi
derive_from=$INTERVAL
shift # Option arg
;;
-n|--numcaps)
if ! is_number "$2" ; then
error "Number of caps must be a number! (given $2)"
exit 68
fi
numcaps="$2"
derive_from=$NUMCAPS
shift # Option arg
;;
-u|--username) user="$2" ; shift ;;
-T|--title) title="$2" ; shift ;;
-f|--from)
if ! fromtime=$(get_interval "$2") ; then
error "Starting timestamp must be a valid interval"
exit 68
fi
shift
;;
-t|--to)
if ! totime=$(get_interval "$2") ; then
error "Ending timestamp must be a valid interval"
exit 68
fi
if [ "$totime" -eq 0 ]; then
error "Ending timestamp was set to 0, set to movie length"
totime=-1
fi
shift
;;
-S|--stamp)
if ! temp=$(get_interval "$2") ; then
error "Timestamps must be a valid interval"
exit 68
fi
initial_stamps=( ${initial_stamps[*]} $temp )
shift
;;
-j|--jpeg) OUTFMT=jpg ;;
-h|--help) show_help ; exit 0 ;;
--shoehorn)
shoehorned="$2"
shift
;;
-F) METHOD=$METHOD_FFMPEG ;;
-M) METHOD=$METHOD_MPLAYER ;;
-H|--height)
if ! is_number "$2" ; then
error "Height must be a number (given $2)"
exit 68
fi
th_height="$2"
shift
;;
-c|--columns)
if ! is_number "$2" ; then
error "Columns must be a number (given $2)"
exit 68
fi
cols="$2"
shift
;;
--) shift ; break ;;
*) error "Internal error! (remaining opts: $@)" ; exit 76 ;
esac
shift
done
 
# Remaining arguments
if [ ! "$1" ]; then
show_help
exit 67
fi
for arg do process "$arg" ; done
 
# }}} # Command line parsing
 
 
# vim:set ts=4 ai: #
Property changes:
Added: svn:executable
/video-contact-sheet/tags/1.0a/.
Property changes:
Added: svn:mergeinfo
Merged /video-contact-sheet/branches/1.0a:r262-263
Merged /video-contact-sheet/tags/0.99a:r261