Subversion Repositories pub

Compare Revisions

No changes between revisions

Ignore whitespace Rev 318 → Rev 319

/video-contact-sheet/tags/1.0.8a/vcs
0,0 → 1,2126
#!/bin/bash
#
# $Rev$ $Date$
#
# vcs
# Video Contact Sheet *NIX: Generates contact sheets (previews) of videos
#
# Copyright (C) 2007 Toni Corvera
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# Author: Toni Corvera <outlyer@outlyer.net>
#
# References:
# Pages from I've taken snippets or wrote code based on them.
# I refer to them in comments as e.g. [[R1]]. [[R1#3]] Means section 3 in R1.
# I also use internal references in the form [x1] (anchor -where to point-)
# and [[x1]] (x-reference -point to what-).
# [R0] getopt-parse.bash example, on Debian systems:
# /usr/share/doc/util-linux/examples/getopt-parse.bash.gz
# [R1] Bash (and other shells) tips
# <http://wooledge.org/mywiki/BashFaq>
# [R2] List of officially registered FOURCCs and WAVE Formats (aka wFormatTag)
# <http://msdn2.microsoft.com/en-us/library/ms867195.aspx>
# [R3] Unofficial list of FOURCCs
# <http://www.fourcc.org/>
# [R4] A php module with a list of FOURCCs and wFormatTag's mappings
# <http://webcvs.freedesktop.org/clipart/experimental/rejon/getid3/getid3/module.audio-video.riff.php>
#
 
declare -r VERSION="1.0.8a"
# {{{ # CHANGELOG
# History (The full changelog was moved to a separate file and can be found
# at <http://p.outlyer.net/vcs/files/CHANGELOG>).
#
# 1.0.8a: (2007-06-02)
# * BUGFIX: User set number of columns wasn't being used if -n wasn't used
# (Thanks to 'Homer S')
# * BUGFIX: Right side of heading wasn't using the user's font colour
# (Thanks to 'Dougn Redhammer').
# }}} # CHANGELOG
 
set -e
 
# {{{ # TODO
 
# TODO / FIXME:
# * [[R1#22]] states that not all bc versions understand '<', more info required
# * [[x1]] Find out why the order of ffmpeg arguments breaks some files.
# * [[x2]] Find out if egrep is safe to use or grep -E is more commonplace.
#
 
# }}} # TODO
 
# {{{ # Constants
 
# Configuration file, please, use this file to modify the behaviour of the
# script. Using this allows overriding some variables (see below)
# to your liking. Only lines with a variable assignment are evaluated,
# it should follow bash syntax, note though that ';' can't be used
# currently in the variable values; e.g.:
#
# # Sample configuration for vcs
# user=myname # Sign all compositions as myname
# bg_heading=gray # Make the heading gray
#
# There is a total of three configuration files than are loaded if the exist:
# * /etc/vcs.conf: System wide conf, least precedence
# * $CFGFILE (by default ~/.vcs.conf): Per-user conf, second least precedence
# * ./vcs.conf: Per-dir confif, most precedence
#
# The variables that can be overriden are below the block of constants ahead.
declare -r CFGFILE=~/.vcs.conf
 
# see $decoder
declare -ri DEC_MPLAYER=1 DEC_FFMPEG=3
# See $timecode_from
declare -ri TC_INTERVAL=4 TC_NUMCAPS=8
# These can't be overriden, modify this line if you feel the need
declare -r PROGRAM_SIGNATURE="Video Contact Sheet *NIX ${VERSION} <http://p.outlyer.net/vcs/>"
# see $safe_rename_pattern
declare -r DEFAULT_SAFE_REN_PATT="%b-%N.%e"
# see $extended_factor
declare -ri DEFAULT_EXT_FACTOR=4
# see $verbosity
declare -ri V_ALL=5 V_NONE=-1 V_ERROR=1 V_WARN=2 V_INFO=3
# see $font_filename and $FONT_MINCHO
declare -ri FF_DEFAULT=5 FF_MINCHO=7
# Indexes in $VID
declare -ri W=0 H=1 FPS=2 LEN=3 VCODEC=4 ACODEC=5 VDEC=6 CHANS=7
# Exit codes, same numbers as /usr/include/sysexits.h
declare -r EX_OK=0 EX_USAGE=64 EX_UNAVAILABLE=69 \
EX_NOINPUT=66 EX_SOFTWARE=70 EX_CANTCREAT=73 \
EX_INTERRUPTED=79 # This one is not on sysexits.h
# The context allows the creator to identify which contact sheet it is creating
# (CTX_*) HL: Highlight (-l), STD: Normal, EXT: Extended (-e)
declare -ri CTX_HL=1 CTX_STD=2 CTX_EXT=3
 
# }}} # End of constants
 
# {{{ # Override-able variables
 
declare -i DEFAULT_INTERVAL=300
declare -i DEFAULT_NUMCAPS=16
declare -i DEFAULT_COLS=2
# Text before the user name in the signature
declare user_signature="Preview created by"
# By default sign as the system's username (see -u, -U)
declare user=$(id -un)
# Which of the two methods should be used to guess the number of thumbnails
declare -i timecode_from=$TC_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
# also, ffmpeg allows better seeking: ffmpeg allows exact second.fraction
# seeking while mplayer apparently only seeks to nearest keyframe
declare -i decoder=$DEC_FFMPEG
# Options used in imagemagick, these options set the final aspect
# of the contact sheet
declare output_format=png # ImageMagick decides the type from the extension
declare -i output_quality=92 # Output image quality (only affects the final
# image and obviously only in lossy formats)
# Colours, see convert -list color to get the list
declare bg_heading=YellowGreen # Background for meta info (size, codec...)
declare bg_sign=SlateGray # Background for signature
declare bg_title=White # Background for the title (see -T)
declare bg_contact=White # Background of the thumbnails
declare fg_heading=black # Font colour for meta info box
declare fg_sign=black # Font colour for signature
declare fg_tstamps=white # Font colour for timestamps
declare fg_title=Black # Font colour fot the title
# Fonts, see convert -list type to get the list
declare font_tstamps=courier # Used for timestamps over the thumbnails
declare font_heading=helvetica # Used for the heading (meta info box)
declare font_sign=$font_heading # Used for the signature box
# Unlike other font_ variables this doesn't take a font name directly
# but is restricted to the $FF_ values. This is to allow overrides
# from the command line to be placed anywhere, i.e. in
# $ vcs -I file.avi -O 'FONT_MINCHO=whatever'
# as the font is overridden is after requesting its use, it wouldn't be
# affected
# The other font_ variables are only affected by overrides and not command
# line options that's why this one is special.
declare font_filename=$FF_DEFAULT # Used to print only the filename in the heading
declare font_title=$font_heading # Used fot the title (see -T)
# Font sizes, in points
declare -i pts_tstamps=18 # Used for the timestamps
declare -i pts_meta=16 # Used for the meta info box
declare -i pts_sign=11 # Used for the signature
declare -i pts_title=36 # Used for the title (see -T)
# See --shoehorn
declare shoehorned=
# See -E / $end_offset
declare -i DEFAULT_END_OFFSET=60
# This can only be changed in the configuration file
# Change it to change the safe renanimg:
# When writing the output file, the input name + output extension is
# used (e.g.: "some video.avi.png"), if it already exists, though,
# a number if appended to the name. This variable dictates where the number is
# placed.
# By default "%b-%N.%e" where:
# %b is the basename (file name without extension)
# %N is the appended number
# %e is the extension
# The default creates outputs like "output.avi-1.png"
#
# If overridden with an incorrect value it will be silently set to the default
declare safe_rename_pattern="$DEFAULT_SAFE_REN_PATT"
# Controls how many extra captures will be created in the extended mode
# (see -e), 0 is the same as disabling the extended mode
# This number is multiplied by the total number of captures to get
# the number of extra captures. So, e.g. -n2 -e2 leads to 4 extra captures.
declare extended_factor=0
# Options added always to the ones in the command line
# (command line options override them).
# Note using this is a bit tricky :P mostly because I've no clue of how this
# should be done.
# As an example: you want to set always the title to "My Title" and output
# to jpeg: default_options="-T'My Title' -j"
#declare default_options=
# Verbosity level so far from the command line can only be muted (see -q)
# it can be overridden, though
declare -i verbosity=$V_ALL
# When set to 0 the status messages printed by vcs while running
# are coloured if the terminal supports it. Set to 1 if this annoys you.
declare -i plain_messages=0
# Experimental in 1.0.7b:
# Experiment to get international font support
# I'll need to get some help here, so if you use anything beyond a latin
# alphabet, please help me choosing the correct fonts
# To my understanding Ming/Minchō fonts should cover most of Japanse,
# Chinese and Korean
# Apparently Kochi Mincho should include Hangul *and* Cyrillic... which would be
# great :) Although it couldn't write my hangul test, and also the default font
# (helvetica) in my system seems to include cyrillic too, or at least a subset of
# it.
declare FONT_MINCHO=/usr/share/fonts/truetype/kochi/kochi-mincho.ttf
# Output of capturing programs is redirected here
declare stdout=/dev/null stderr=/dev/null
 
# }}} # End of override-able variables
 
# {{{ # Variables
 
# Options and other internal usage variables, no need to mess with this!
declare interval=$DEFAULT_INTERVAL # Interval of captures (=numsecs/numcaps)
declare -i numcaps=$DEFAULT_NUMCAPS # Number of captures (=numsecs/interval)
declare title=""
declare fromtime=0 # Starting second (see -f)
declare totime=-1 # Ending second (see -t)
declare -a initial_stamps=( ) # Manually added stamps (see -S)
declare -i th_height= # Height of the thumbnails, by default use same as input
declare -i cols=$DEFAULT_COLS # Number of output columns
declare -i manual_mode=0 # if 1, only command line timestamps will be used
declare aspect_ratio=0 # If 0 no transformations done (see -a)
# If -1 try to guess (see -A)
 
declare -a TEMPSTUFF=( ) # Temporal files
declare -a TIMECODES=( ) # Timestamps of the video captures
declare -a HLTIMECODES=( ) # Timestamps of the highlights (see -l)
 
declare VCSTEMPDIR= # Temporal directory, all temporal files
# go there
# This holds the output of mplayer -identify on the current video
declare MPLAYER_CACHE=
# This holds the parsed values of MPLAYER_CACHE, see also the Indexes in VID
# (defined in the constants block)
declare -a VID=
 
# Workarounds:
# Argument order in FFmpeg is important -ss before or after -i will make
# the capture work or not depending on the file. See -Wo.
# TODO: [x1].
# Admittedly the workaraound is abit obscure: those variables will be added to
# the ffmpeg arguments, before and after -i, replacing spaces by the timestamp.
# e.g.: for second 50 '-ss ' will become '-ss 50' while '' will stay empty
# By default -ss goes before -i.
declare wa_ss_af="" wa_ss_be="-ss "
# This number of seconds is *not* captured from the end of the video
declare -i end_offset=$DEFAULT_END_OFFSET
 
# Experimental in 1.0.7b: transformations/filters
# Operations are decomposed into independent optional steps, this will allow
# to add some intermediate steps (e.g. polaroid mode)
# Filters in this context are functions.
# There're two kinds of filters and a delegate:
# * individual filters are run over each vidcap
# * global filters are run over all vidcaps at once
# * The contact sheet creator delegates on some function to create the actual
# contact sheet
#
# Individual filters take the form:
# filt_name( vidcapfile, timestamp in seconds.milliseconds, width, height )
# They're executed in order by filter_vidcap()
declare -a FILTERS_IND=( 'filt_resize' 'filt_apply_stamp' )
# Global filters take the form
# filtall_name( vidcapfile1, vidcapfile2, ... )
# They're executed in order by filter_all_vidcaps
declare -a FILTERS_CS=( )
# The contact sheet creators take the form
# csheet_name( number of columns, context, width, height, vidcapfile1,
# vidcapfile2, ... ) : outputfile
# Context is one of the CTX_* constants (see below)
# The width and height are those of an individual capture
# It is executed by create_contact_sheet()
declare CSHEET_DELEGATE=csheet_montage
 
# Gravity of the timestamp (will be override-able in the future)
declare grav_timestamp=SouthEast
 
# When set to 1 the signature won't contain the "Preview created by..." line
declare -i anonymous_mode=0
 
# }}} # Variables
 
# {{{ # Configuration handling
 
# These are the variables allowed to be overriden in the config file,
# please.
# They're REGEXes, they'll be concatenated to form a regex like
# (override1|override2|...).
# Don't mess with this unless you're pretty sure of what you're doing.
# All this extra complexity is done to avoid including the config
# file directly for security reasons.
declare -ra ALLOWED_OVERRIDES=(
'user'
'user_signature'
'bg_.*'
'font_.*'
'pts_.*'
'fg_.*'
'output_quality'
'DEFAULT_INTERVAL'
'DEFAULT_NUMCAPS'
'DEFAULT_COLS'
'decoder'
'output_format'
'shoehorned'
'timecode_from'
'safe_rename_pattern'
# 'default_options'
'extended_factor'
'verbosity'
'plain_messages'
'FONT_MINCHO'
'stdout'
'stderr'
'DEFAULT_END_OFFSET'
)
 
# This is only used to exit when -DD is used
declare -i DEBUGGED=0 # It will be 1 after using -D
 
# Loads the configuration files if present
# load_config()
load_config() {
local CONFIGS=( /etc/vcs.conf $CFGFILE ./vcs.conf)
 
for cfgfile in ${CONFIGS[*]} ;do
if [ ! -f "$cfgfile" ]; then continue; fi
 
while read line ; do # auto variable $line
override "$line" "file $cfgfile" # Feeding it comments should be harmless
done <$cfgfile
done
 
# Override-able hack, this won't work with command line overrides, though
end_offset=$DEFAULT_END_OFFSET
interval=$DEFAULT_INTERVAL
numcaps=$DEAFULT_NUMCAPS
}
 
# Do an override
# It takes basically an assignment (in the same format as bash)
# to one of the override-able variables (see $ALLOWED_OVERRIDES).
# There are some restrictions though. Currently ';' is not allowed to
# be in the assignment.
# override($1 = bash variable assignment, $2 = source)
override() {
local o="$1"
local src="$2"
 
local compregex=$( sed 's/ /|/g' <<<${ALLOWED_OVERRIDES[*]} )
 
# Don't allow ';', FIXME: dunno how secure that really is...
# FIXME: ...it doesn't really works anyway
if ! egrep -q '^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]*=[^;]*' <<<"$o" ; then
return
fi
if ! egrep -q "^($compregex)=" <<<"$o" ; then
return
fi
 
local varname=$(sed -r 's/^[[:space:]]*([a-zA-Z0-9_]*)=.*/\1/'<<<"$o")
local varval=$(sed -r 's/[^=]*=(.*)/\1/'<<<"$o")
# FIXME: Security!
local curvarval=
eval curvarval='$'"$varname"
if [ "$curvarval" == "$varval" ]; then
warn "Ignored override '$varname' (already had same value)"
else
eval "$varname=\"$varval\""
# FIXME: Only for really overridden ones
warn "Overridden variable '$varname' from $src"
fi
}
 
# }}} # Configuration handling
 
# {{{ # Convenience functions
 
# Returns true if input is composed only of numbers
# is_number($1 = input)
is_number() {
egrep -q '^[0-9]+$' <<<"$1"
}
 
# Returns true if input can be parsed as a floating point number
# Accepted: XX.YY XX. .YY (.24=0.24
# is_float($1 = input)
is_float() {
egrep -q '^([0-9]+\.?([0-9])?+|(\.[0-9]+))$'<<<"$1"
}
 
# Returns true if input is a fraction (*strictly*, i.e. "1" is not a fraction)
# Only accepts XX/YY
# is_fraction($1 = input)
is_fraction() {
egrep -q '^[0-9]+/[0-9]+$'<<<"$1"
}
 
# Makes a string lowercase
# tolower($1 = string)
tolower() {
tr '[A-Z]' '[a-z]' <<<"$1"
}
 
 
# Rounded product
# multiplies parameters and prints the result, rounded to the closest int
# parameters can be separated by commas or spaces
# e.g.: rmultiply 4/3,576 OR 4/3 576 = 4/3 * 576 = 768
# rmultiply($1 = operator1, [$2 = operator2, ...])
# rmultiply($1 = "operator1,operator2,...")
rmultiply() {
local exp=$(sed 's/[ ,]/*/g'<<<"$@") # bc expression
#local f=$(bc -lq<<<"$exp") # exact float value
# division is integer by default (without -l) so it's the smae
# as rounding to the lower int
#bc -q <<<"( $f + 0.5 ) / 1"
bc -q <<<"scale=5; v=( ($exp) + 0.5 ) ; scale=0 ; v/1"
}
 
# Like rmultiply() but always rounded upwards
ceilmultiply() {
local exp=$(sed 's/[ ,]/*/g'<<<"$@") # bc expression
local f=$(bc -lq<<<"$exp") # exact float value
bc -q <<<"( $f + 0.999999999 ) / 1"
}
 
# Round to a multiple
# Rounds a number ($1) to a multiple of ($2)
# rtomult($1 = number, $2 = divisor)
rtomult() {
local n=$1 d=$2
local r=$(( $n % $d ))
if [ $r -ne 0 ]; then
let 'n += ( d - r )'
fi
echo $n
}
 
# numeric test eqivalent for floating point
# fptest($1 = op1, $2 = operator, $3 = op2)
fptest() {
local op=
case $2 in
-gt) op='>' ;;
-lt) op='<' ;;
-ge) op='>=' ;;
-le) op='<=' ;;
-eq) op='==' ;;
-ne) op='!=' ;;
*) error "Internal error" && return $EX_SOFTWARE
esac
[ '1' == $(bc -q <<<"$1 $op $3") ]
}
 
# Applies the Pythagorean Theorem
# pyth_th($1 = cathetus1, $2 = cathetus2)
pyth_th() {
bc -ql <<<"sqrt( $1^2 + $2^2)"
}
 
# Prints the width correspoding to the input height and the variable
# aspect ratio
# compute_width($1 = height) (=AR*height) (rounded)
compute_width() {
rmultiply $aspect_ratio,$1
}
 
# Parse an interval and print the corresponding value in seconds
# returns something not 0 if the interval is not recognized.
#
# The current code is a tad permissive, it allows e.g. things like
# 10m1h (equivalent to 1h10m)
# 1m1m (equivalent to 2m)
# I don't see reason to make it more anal, though.
# get_interval($1 = interval)
get_interval() {
if is_number "$1" ; then echo $1 ; return 0 ; fi
 
local s=$(tolower "$1") t r
 
# Only allowed characters
if ! egrep -qi '^[0-9smh.]+$' <<<"$s"; then
return $EX_USAGE;
fi
 
# New parsing code: replaces units by a product
# and feeds the resulting string to bc
t=$s
t=$(sed -r 's/([0-9]+)h/ ( \1 * 3600 ) + /g' <<<$t)
t=$(sed -r 's/([0-9]+)m/ ( \1 * 60 ) + /g' <<<$t)
t=$(sed 's/s/ + /g' <<<$t)
t=$(sed -r 's/\.\.+/./g'<<<$t)
t=$(sed -r 's/(\.[0-9]+)/0\1 + /g' <<<$t)
t=$(sed -r 's/\+ ?$//g' <<<$t)
 
r=$(bc -lq <<<$t 2>/dev/null) # bc parsing fails with correct return code
if [ -z "$r" ]; then
return $EX_USAGE
fi
# Negative interval
if [ "-" == ${r:0:1} ]; then
return $EX_USAGE
fi
 
echo $r
}
 
# Pads a string with zeroes on the left until it is at least
# the indicated length
# pad($1 = minimum length, $2 = string)
pad() {
# printf "%0${1}d\n" "$2" # [[R1#18]] # Can't be used with non-numbers
local str=$2
while [ "${#str}" -lt $1 ]; do
str="0$str"
done
echo $str
}
 
# Get Image Width
# imw($1 = file)
imw() {
identify "$1" | cut -d' ' -f3 | cut -d'x' -f1
}
 
# Get Image Height
# imh($1 = file)
imh() {
identify "$1" | cut -d' ' -f3 | cut -d'x' -f2
}
 
# Prints a number of seconds in a more human readable form
# e.g.: 3600 becomes 1:00:00
# pretty_stamp($1 = seconds)
pretty_stamp() {
if ! is_float "$1" ; then return $EX_USAGE ; fi
 
local t=$1
#local h=$(( $t / 3600 ))
# bc's modulus seems to *require* not using the math lib (-l)
local h=$( bc -q <<<"$t / 3600")
t=$(bc -q <<<"$t % 3600")
local m=$( bc -q <<<"$t / 60")
t=$(bc -q <<<"$t % 60")
local s=$(cut -d'.' -f1 <<<$t)
local ms=$(cut -d'.' -f2 <<<$t)
 
local R=""
 
if [ $h -gt 0 ]; then
R+="$h:"
fi
# Right pad of decimal seconds
if [ ${#ms} -lt 2 ]; then
ms="${ms}0"
fi
R+=$(pad 2 "$m"):$(pad 2 $s).$ms
 
# Trim (most) decimals
sed -r 's/\.([0-9][0-9]).*/.\1/'<<<$R
}
 
# Prints the size of a file in a human friendly form
# The units are in the IEC/IEEE/binary format (e.g. MiB -for mebibytes-
# instead of MB -for megabytes-)
# get_pretty_size($1 = file)
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 appending numbers to the name
# And print the output name to stdout
# See $safe_rename_pattern
# safe_rename($1 = original file, $2 = target file)
# XXX: Note it fails if target has no extension
safe_rename() {
local from="$1"
local to="$2"
 
# Output extension
local ext=$(sed -r 's/.*\.(.*)/\1/' <<<$to)
# Input extension
local iext=$(sed -r 's/.*\.(.*)/\1/' <<<$to)
# Input filename without extension
local b=${to%.$iext}
 
# safe_rename_pattern is override-able, ensure it has a valid value:
if ! grep -q '%e' <<<"$safe_rename_pattern" ||
! grep -q '%N' <<<"$safe_rename_pattern" ||
! grep -q '%b' <<<"$safe_rename_pattern" ; then
safe_rename_pattern=$DEFAULT_SAFE_REN_PATT
fi
 
local n=1
while [ -f "$to" ]; do # Only executes if $2 exists
to=$(sed "s#%b#$b#g" <<<"$safe_rename_pattern")
to=$(sed "s#%N#$n#g" <<<"$to")
to=$(sed "s#%e#$ext#g" <<<"$to")
 
let 'n++';
done
 
mv -- "$from" "$to"
echo "$to"
}
 
# Tests the presence of all required programs
# test_programs()
test_programs() {
local retval=0 last=0
for prog in getopt mplayer convert montage identify bc \
ffmpeg mktemp sed grep egrep cut; do
type -pf "$prog" >/dev/null
if [ $? -ne 0 ] ; then
error "Required program $prog not found!"
let 'retval++'
fi
done
# TODO: [x2]
 
return $retval
}
 
# Remove any temporal files
# Does nothing if none has been created so far
# cleanup()
cleanup() {
if [ -z $TEMPSTUFF ]; then return 0 ; fi
inf "Cleaning up..."
rm -rf ${TEMPSTUFF[*]}
TEMPSTUFF=( )
}
 
# Exit callback. This function is executed on exit (correct, failed or
# interrupted)
# exithdlr()
exithdlr() {
cleanup
}
 
# Feedback handling, these functions are use to print messages respecting
# the verbosity level
# Optional color usage added from explanation found in
# <http://wooledge.org/mywiki/BashFaq>
#
# error($1 = text)
error() {
if [ $verbosity -ge $V_ERROR ]; then
if [ $plain_messages -eq 0 ]; then
tput bold ; tput setaf 1;
fi
# sgr0 is always used, this way if
# a) something prints inbetween messages it isn't affected
# b) if plain_messages is overridden colour stops after the override
echo "$1" ; tput sgr0
fi >&2
# It is important to redirect both tput and echo to stderr. Otherwise
# n=$(something) wouldn't be coloured
}
#
# Print a non-fatal error or warning
# warning($1 = text)
warn() {
if [ $verbosity -ge $V_WARN ]; then
if [ $plain_messages -eq 0 ]; then
tput bold ; tput setaf 3;
fi
echo "$1" ; tput sgr0
fi >&2
}
#
# Print an informational message
# inf($1 = text)
inf() {
if [ $verbosity -ge $V_INFO ]; then
if [ $plain_messages -eq 0 ]; then
tput bold ; tput setaf 2;
fi
echo "$1" ; tput sgr0
fi >&2
}
#
# Same as inf but with no colour ever.
# infplain($1 = text)
infplain() {
if [ $verbosity -ge $V_INFO ]; then
echo "$1" >&2
fi
}
 
# }}} # Convenience functions
 
# {{{ # Core functionality
 
# Creates a new temporary directory
# create_temp_dir()
create_temp_dir() {
# Try to use /dev/shm if available, this provided a very small
# benefit on my system but me of help for huge files. Or maybe won't.
if [ -d /dev/shm ] && [ -w /dev/shm ]; then
VCSTEMPDIR=$(mktemp -d -p /dev/shm vcs.XXXXXX)
else
VCSTEMPDIR=$(mktemp -d -t vcs.XXXXXX)
fi
if [ ! -d "$VCSTEMPDIR" ]; then
error "Error creating temporary directory"
return $EX_CANTCREAT
fi
TEMPSTUFF+=( "$VCSTEMPDIR" )
}
 
# Create a new temporal file and print its filename
# new_temp_file($1 = suffix)
new_temp_file() {
local r=$(mktemp -p "$VCSTEMPDIR" "vcs-XXXXXX")
if [ ! -f "$r" ]; then
error "Failed to create temporary file"
return $EX_CANTCREAT
fi
r=$(safe_rename "$r" "$r$1") || {
error "Failed to create temporary file"
return $EX_CANTCREAT
}
TEMPSTUFF+=( "$r" )
echo "$r"
}
 
# Randomizes the colours and fonts. The result won't be of much use
# in most cases but it might be a good way to discover some colour/font
# or colour combination you like.
# randomize_look()
randomize_look() {
 
local mode=f lineno
 
if [ "f" == $mode ]; then # Random mode
# There're 5 rows of extra info printed
local ncolours=$(( $(convert -list color | wc -l) - 5 ))
randcolour() {
lineno=$(( 5 + ( $RANDOM % $ncolours ) ))
convert -list color | sed -n "${lineno}p" | cut -d' ' -f1 # [[R1#19]]
}
else # Pseudo-random mode, WIP!
randccomp() {
# colours are in the 0..65535 range, while RANDOM in 0..32767
echo $(( $RANDOM + $RANDOM + ($RANDOM % 1) ))
}
randcolour() {
echo "rgb($(randccomp),$(randccomp),$(randccomp))"
}
fi
 
local nfonts=$(( $(convert -list type | wc -l) - 5 ))
randfont() {
lineno=$(( 5 + ( $RANDOM % $nfonts )))
convert -list type | sed -n "${lineno}p" | cut -d' ' -f1 # [[R1#19]]
}
 
bg_heading=$(randcolour)
bg_sign=$(randcolour)
bg_title=$(randcolour)
bg_contact=$(randcolour)
fg_heading=$(randcolour)
fg_sign=$(randcolour)
fg_tstamps=$(randcolour)
fg_title=$(randcolour)
font_tstamps=$(randfont)
font_heading=$(randfont)
font_sign=$(randfont)
font_title=$(randfont)
inf "Randomization result:
Chosen backgrounds:
'$bg_heading' for the heading
'$bg_sign' for the signature
'$bg_title' for the title
'$bg_contact' for the contact sheet
Chosen font colours:
'$fg_heading' for the heading
'$fg_sign' for the signature
'$fg_title' for the title
'$fg_tstamps' for the timestamps,
Chosen fonts:
'$font_heading' for the heading
'$font_sign' for the signature
'$font_title' for the title
'$font_tstamps' for the timestamps"
 
unset -f randcolour randfound randccomp
}
 
# Add to $TIMECODES the timecodes at which a capture should be taken
# from the current video
# compute_timecodes($1 = timecode_from, $2 = interval, $3 = numcaps)
compute_timecodes() {
local st=0 end=${VID[$LEN]} tcfrom=$1 tcint=$2 tcnumcaps=$3 eo=0
# globals: fromtime, totime, timecode_from, TIMECODES, end_offset
if fptest $st -lt $fromtime ; then
st=$fromtime
fi
if fptest $totime -gt 0 && fptest $end -gt $totime ; then
end=$totime
fi
if fptest $totime -le 0 ; then # If no totime is set, use end_offset
eo=$end_offset
 
local runlen=$( bc <<<"$end - $st" )
 
if fptest "($end-$eo-$st)" -le 0 ; then
if fptest "$eo" -gt 0 && fptest "$eo" -eq "$DEFAULT_END_OFFSET" ; then
warn "Default end offset was too high, ignoring it."
eo=0
else
error "End offset too high, use e.g. '-E0'."
return $EX_UNAVAILABLE
fi
fi
fi
 
local inc=
if [ "$tcfrom" -eq $TC_INTERVAL ]; then
inc=$tcint
elif [ "$tcfrom" -eq $TC_NUMCAPS ]; then
# Numcaps mandates: timecodes are obtained dividing the length
# by the number of captures
if [ $tcnumcaps -eq 1 ]; then # Special case, just one capture, center it
inc=$( bc -lq <<< "scale=3; ($end-$st)/2 + 1" )
else
#inc=$(( ($end-$st) / $tcnumcaps ))
# FIXME: The last second is avoided (-1) to get the correct caps number
inc=$( bc -lq <<< "scale=3; ($end-$eo-$st)/$tcnumcaps" )
fi
else
error "Internal error"
return $EX_SOFTWARE
fi
 
if fptest $inc -gt ${VID[$LEN]}; then
error "Interval is longer than video length, skipping $f"
return $EX_USAGE
fi
 
local LTC=( ) stamp=$st
while fptest $stamp -le $(bc -q <<<"$end-$eo"); do
if fptest $stamp -lt 0 ; then
error "Internal error, negative timestamp calculated!"
return $EX_SOFTWARE
fi
LTC+=( $stamp )
stamp=$(bc -q <<<"$stamp+$inc")
done
unset LTC[0] # Discard initial cap (=$st)
TIMECODES=( ${TIMECODES[@]} ${LTC[@]} )
}
 
# Tries to guess an aspect ratio comparing width and height to some
# known values (e.g. VCD resolution turns into 4/3)
# guess_aspect($1 = width, $2 = height)
guess_aspect() {
# mplayer's ID_ASPECT seems to be always 0 ¿?
local w=$1 h=$2 ar
if [ $w -eq 352 ]; then # VCD / DVD @ VCD Res. / Half-D1 / CVD
if [ $h -eq 288 ] || [ $h -eq 240 ]; then
ar=4/3
elif [ $h -eq 576 ] || [ $h -eq 480 ]; then # Half-D1 / CVD
ar=4/3
fi
elif [ $w -eq 704 ] || [ $w -eq 720 ]; then # DVD / DVB
# Actually for 720x576/720x480 16/9 is as good a guess
if [ $h -eq 576 ] || [ $h -eq 480 ]; then
ar=4/3
fi
elif [ $w -eq 480 ]; then # SVCD
if [ $h -eq 576 ] || [ $h -eq 480 ]; then
ar=4/3
fi
else
warn "Couldn't guess aspect ratio."
ar="$w/$h" # Don't calculate it yet
fi
echo $ar
}
 
# Capture a frame
# capture($1 = filename, $2 = second)
capture() {
local f=$1 stamp=$2
local VIDCAPFILE=00000001.png
# globals: $shoehorned $decoder
 
if [ $decoder -eq $DEC_MPLAYER ]; then
{
mplayer -sws 9 -ao null -benchmark -vo "png:z=0" -quiet \
-frames 1 -ss $stamp $shoehorned "$f"
} >"$stdout" 2>"$stderr"
elif [ $decoder -eq $DEC_FFMPEG ]; then
# XXX: It would be nice to show a message if it takes too long
{
# See wa_ss_* declarations at the start of the file for details
ffmpeg -y ${wa_ss_be/ / $stamp} -i "$f" ${wa_ss_af/ / $stamp} -an \
-dframes 1 -vframes 1 -vcodec png \
-f rawvideo $shoehorned $VIDCAPFILE
# Used to test bogus files (e.g. to test codec ids)
#convert -size 1x xc:black $VIDCAPFILE
} >"$stdout" 2>"$stderr"
else
error "Internal error!"
return $EX_SOFTWARE
fi || {
local retval=$?
error "The capturing program failed!"
return $retval
}
if [ ! -f "$VIDCAPFILE" ] || [ "0" == "$(du "$VIDCAPFILE" | cut -f1)" ]; then
error "Failed to capture frame (at second $stamp)"
return $EX_SOFTWARE
fi
 
return 0
}
 
# Applies all individual vidcap filters
# filter_vidcap($1 = filename, $2 = timestamp, $3 = width, $4 = height)
filter_vidcap() {
# For performance purposes each filter simply prints a set of options
# to 'convert'. That's less flexible but enough right now for the current
# filters.
local cmdopts=
for filter in ${FILTERS_IND[@]}; do
cmdopts+=" $( $filter "$1" "$2" "$3" "$4" ) "
done
local t=$(new_temp_file .png)
eval "convert '$1' $cmdopts '$t'"
# If $t doesn't exist returns non-zero
[ -f "$t" ] && mv "$t" "$1"
}
 
# Applies all global vidcap filters
#filter_all_vidcaps() {
# # TODO: Do something with "$@"
# true
#}
 
filt_resize() {
local f="$1" t=$2 w=$3 h=$4
 
# Note the '!', required to change the aspect ratio
echo " \( -geometry ${w}x${h}! \) "
}
 
# Draw a timestamp in the file
# filt_apply_stamp($1 = filename, $2 = timestamp, $3 = width, $4 = height)
filt_apply_stamp() {
local filename=$1 timestamp=$2 width=$3 height=$4
 
echo -n " \( -box '#000000aa' -fill '$fg_tstamps' -pointsize '$pts_tstamps' "
echo -n " -gravity '$grav_timestamp' -stroke none -strokewidth 3 -annotate +5+5 "
echo " ' $(pretty_stamp $stamp) ' \) -flatten "
}
 
# Apply a Polaroid-like effect
# Taken from <http://www.imagemagick.org/Usage/thumbnails/#polaroid>
# filt_photoframe($1 = filename, $2 = timestamp, $3 = width, $4 = height)
filt_photoframe() {
# local file="$1" ts=$2 w=$3 h=$4
# Tweaking the size gives a nice effect too
# w=$(( $w - ( $RANDOM % ( $w / 3 ) ) ))
# TODO: Split softshadow in a filter
echo -n "-bordercolor white -border 6 -bordercolor grey60 -border 1 "
echo -n "-background black \( +clone -shadow 60x4+4+4 \) +swap "
echo "-background none -flatten -trim +repage"
}
 
# Applies a random rotation
# Taken from <http://www.imagemagick.org/Usage/thumbnails/#polaroid>
# filt_randrot($1 = filename, $2 = timestamp, $3 = width, $4 = height)
filt_randrot() {
# Rotation angle [-18..18]
local angle=$(( ($RANDOM % 37) - 18 ))
echo "-background none -rotate $angle "
}
 
# This one requires much more work, the results are pretty rough, but ok as
# a starting point / proof of concept
filt_film() {
local file="$1" ts=$2 w=$3 h=$4
# Base reel dimensions
local rw=$(rmultiply $w,0.08) # 8% width
local rh=$(( $rw / 2 ))
# Ellipse center
local ecx=$(( $rw / 2 )) ecy=0
# Ellipse x, y radius
local erx=$(( (rw/2)*60/100 )) # 60% halt rect width
local ery=$(( $erx / 2))
 
local base_reel=$(new_temp_file .png) reel_strip=$(new_temp_file .png)
 
# Create the reel pattern...
convert -size ${rw}x${rh} 'xc:black' \
-fill white -draw "ellipse $ecx,$ecy $erx,$ery 0,360" -flatten \
\( +clone -flip \) -append \
-fuzz '40%' -transparent white \
"$base_reel"
# FIXME: Error handling
 
# Repeat it until the height is reached and crop to the exact height
local sh=$(imh "$base_reel") in=
local repeat=$( ceilmultiply $h/$sh)
while [ $repeat -gt 1 ]; do
in+=" '$base_reel' "
let 'repeat--'
done
eval convert "$base_reel" $in -append -crop $(imw "$base_reel")x${h}+0+0 \
"$reel_strip"
 
# As this options will be appended to the commandline we cannot
# order the arguments optimally (eg: reel.png image.png reel.png +append)
# A bit of trickery must be done flipping the image. Note also that the
# second strip will be appended flipped, which is intended.
echo -n "'$reel_strip' +append -flop '$reel_strip' +append -flop "
}
 
# Creates a contact sheet by calling the delegate
# create_contact_sheet($1 = columns, $2 = context, $3 = width, $4 = height,
# $5...$# = vidcaps) : output
create_contact_sheet() {
$CSHEET_DELEGATE "$@"
}
 
# This is the standard contact sheet creator
# csheet_montage($1 = columns, $2 = context, $3 = width, $4 = height,
# $5... = vidcaps) : output
csheet_montage() {
local cols=$1 ctx=$2 width=$3 height=$4 output=$(new_temp_file .png)
shift 4
case $ctx in
$CTX_STD|$CTX_HL) hpad=10 vpad=5 ;;
$CTX_EXT) hpad=5 vpad=2 ;;
*) error "Internal error" && return $EX_SOFTWARE ;;
esac
# Using transparent seems to make -shadow futile
montage -background Transparent "$@" -geometry +$hpad+$vpad -tile "$cols"x "$output"
# This produces soft-shadows, which look much better than the montage ones
#
convert \( -shadow 50x2+10+10 "$output" \) "$output" -composite "$output"
 
# FIXME: Error handling
echo $output
}
 
# Polaroid contact sheet creator: it overlaps vidcaps with some randomness
# csheet_overlap($1 = columns, $2 = context, $3 = width, $4 = height,
# $5... = $vidcaps) : output
csheet_overlap() {
local cols=$1 ctx=$2 width=$3 height=$4
# globals: $VID
shift 4
 
# TBD: Handle context
 
# Explanation of how this works:
# On the first loop we do what the "montage" command would do (arrange the
# images in a grid) but overlapping each image to the one on their left,
# creating the output row by row, each row in a file.
# On the second loop we append the rows, again overlapping each one to the
# one before (above) it.
# XXX: Compositing over huge images is quite slow, there's probably a
# better way to do it
 
# Offset bounds, this controls how much of each snap will be over the
# previous one. Note it is important to work over $width and not $VID[$W]
# to cover all possibilities (extended mode and -H change the vidcap size)
local maxoffset=$(( $width / 3 ))
local minoffset=$(( $width / 6 ))
 
# Holds the files that will form the full contact sheet
# each file is a row on the final composition
local -a rowfiles=( )
 
# Dimensions of the canvas for each row, it should be big enough
# to hold all snaps.
# My trigonometry is pretty rusty but considering we restrict the angle a lot
# I believe no image should ever be wider/taller than the diagonal (note the
# ceilmultiply is there to simply round the result)
local diagonal=$(ceilmultiply $(pyth_th $width $height) 1)
# XXX: The width, though isn't guaranteed (e.g. using filt_film it ends wider)
# adding 3% to the diagonal *should* be enough to compensate
local canvasw=$(( ( $diagonal + $(rmultiply $diagonal,0.3) ) * $cols ))
local canvash=$(( $diagonal ))
 
# The number of rows required to hold all the snaps
local numrows=$(ceilmultiply ${#@},1/$cols) # rounded division
 
# Variables inside the loop
local col # Current column
local rowfile # Holds the row we're working on
local offset # Random offset of the current snap [$minoffset..$maxoffset]
local accoffset # The absolute (horizontal) offset used on the next iteration
local cmdopts # Holds the arguments passed to convert to compose the sheet
local w # Width of the current snap
for row in $(seq 1 $numrows) ; do
col=0
rowfile=$(new_temp_file .png)
rowfiles+=( "$rowfile" )
accoffset=0
cmdopts= # This command is pretty time-consuming, let's make it in a row
 
# Base canvas
inf "Creating polaroid base canvas $row/$numrows..."
convert -size ${canvasw}x${canvash} xc:transparent "$rowfile"
 
# Step through vidcaps (col=[0..cols-1])
for col in $(seq 0 $(( $cols - 1 ))); do
# More cols than files in the last iteration (e.g. -n10 -c4)
if [ -z "$1" ]; then break; fi
w=$(imw "$1")
 
# Stick the vicap in the canvas
#convert -geometry +${accoffset}+0 "$rowfile" "$1" -composite "$rowfile"
cmdopts+=" -geometry +${accoffset}+0 '$1' -composite "
 
offset=$(( $minoffset + ( $RANDOM % $maxoffset ) ))
let 'accoffset=accoffset + w - offset'
shift
done
inf "Composing polaroid row $row/$numrows..."
eval convert "'$rowfile'" "$cmdopts" -trim +repage "'$rowfile'" >&2
done
 
inf "Merging polaroid rows..."
output=$(new_temp_file .png)
# Standard composition
#convert -background Transparent "${rowfiles[@]}" -append polaroid.png
# Overlapped composition
convert -size ${canvasw}x$(( $canvash * $cols )) xc:transparent "$output"
cmdopts=
accoffset=0
local h
for row in "${rowfiles[@]}" ; do
w=$(imw "$row")
h=$(imh "$row")
minoffset=$(( $h / 8 ))
maxoffset=$(( $h / 4 ))
offset=$(( $minoffset + ( $RANDOM % $maxoffset ) ))
# The row is also offset horizontally
cmdopts+=" -geometry +$(( $RANDOM % $maxoffset ))+$accoffset '$row' -composite "
let 'accoffset=accoffset + h - offset'
done
# After the trim the top corners are too near the heading, we add some space
# with -splce
eval convert -background transparent "$output" $cmdopts -trim +repage \
-bordercolor Transparent -splice 0x10 "$output" >&2
 
# FIXME: Error handling
echo $output
}
 
# Sorts timestamps and removes duplicates
# clean_timestamps($1 = space separated timestamps)
clean_timestamps() {
# Note AFAIK sort only sorts lines, that's why y replace spaces by newlines
local s=$1
sed 's/ /\n/g'<<<"$s" | sort -n | uniq
}
 
# Fills the $MPLAYER_CACHE and $VID variables with the video data
# identify_video($1 = file)
identify_video() {
local f=$1
# Meta data extraction
# Note to self: Don't change the -vc as it would affect $vdec
MPLAYER_CACHE=$(mplayer -benchmark -ao null -vo null -identify -frames 0 -quiet "$f" 2>/dev/null | grep ^ID)
VID[$VCODEC]=$(grep ID_VIDEO_FORMAT <<<"$MPLAYER_CACHE" | cut -d'=' -f2) # FourCC
VID[$ACODEC]=$(grep ID_AUDIO_FORMAT <<<"$MPLAYER_CACHE" | cut -d'=' -f2)
VID[$VDEC]=$(grep ID_VIDEO_CODEC <<<"$MPLAYER_CACHE" | cut -d'=' -f2) # Decoder (!= Codec)
VID[$W]=$(grep ID_VIDEO_WIDTH <<<"$MPLAYER_CACHE" | cut -d'=' -f2)
VID[$H]=$(grep ID_VIDEO_HEIGHT <<<"$MPLAYER_CACHE" | cut -d'=' -f2)
VID[$FPS]=$(grep ID_VIDEO_FPS <<<"$MPLAYER_CACHE" | cut -d'=' -f2)
VID[$LEN]=$(grep ID_LENGTH <<<"$MPLAYER_CACHE"| cut -d'=' -f2)
# For some reason my (one track) samples have two ..._NCH, first one 0
VID[$CHANS]=$(grep ID_AUDIO_NCH <<<"$MPLAYER_CACHE"|cut -d'=' -f2|head -2|tail -1)
 
# Upon consideration:
#if grep -q '\.[0-9]*0$' <<<${VID[$FPS]} ; then
# # Remove trailing zeroes...
# VID[$FPS]=$(sed -r 's/(\.[1-9]*)0*$/\1/' <<<${VID[$FPS]})
# # ...And trailing decimal point
# VID[$FPS]=$(sed 's/\.$//'<<<${VID[$FPS]})
#fi
 
# Voodoo :P Remove (one) trailing zero
if [ "${VID[$FPS]:$(( ${#VID[$FPS]} - 1 ))}" == "0" ]; then
VID[$FPS]="${VID[$FPS]:0:$(( ${#VID[$FPS]} - 1 ))}"
fi
 
# Check sanity of the most important values
is_number "${VID[$W]}" && is_number "${VID[$H]}" && is_float "${VID[$LEN]}"
}
 
# Main function.
# Creates the contact sheet.
# process($1 = file)
process() {
local f=$1
 
local numcols=
 
if [ ! -f "$f" ]; then
error "File \"$f\" doesn't exist"
return $EX_NOINPUT
fi
inf "Processing $f..."
 
identify_video "$f" || {
error "Found unsupported value while identifying video. Can't continue."
return $EX_SOFTWARE
}
 
# Vidcap/Thumbnail height
local vidcap_height=$th_height
if ! is_number "$vidcap_height" || [ "$vidcap_height" -eq 0 ]; then
vidcap_height=${VID[$H]}
fi
if [ "0" == "$aspect_ratio" ]; then
aspect_ratio=$(bc -lq <<< "${VID[$W]} / ${VID[$H]}")
elif [ "-1" == "$aspect_ratio" ]; then
aspect_ratio=$(guess_aspect ${VID[$W]} ${VID[$H]})
inf "Aspect ratio set to $(sed -r 's/(\.[0-9]{2}).*/\1/g'<<<$aspect_ratio)"
fi
local vidcap_width=$(compute_width $vidcap_height)
 
local numsecs=$(grep ID_LENGTH <<<"$MPLAYER_CACHE"| cut -d'=' -f2 | cut -d. -f1)
local nc=$numcaps
 
create_temp_dir
 
# Compute the stamps (if in auto mode)...
TIMECODES=${initial_stamps[*]}
if [ $manual_mode -ne 1 ]; then
compute_timecodes $timecode_from $interval $numcaps || {
return $?
}
fi
 
local base_montage_command="montage -font $font_tstamps -pointsize $pts_tstamps \
-gravity SouthEast -fill white "
local output=$(new_temp_file '-preview.png')
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!."
return $EX_CANTCREAT
fi
 
TEMPSTUFF+=( $VIDCAPFILE )
 
# Highlights
local hlfile n=1 # hlfile Must be outside the if!
if [ "$HLTIMECODES" ]; then
local hlcapfile= pretty= capfiles=( )
for stamp in $(clean_timestamps "${HLTIMECODES[*]}"); do
if fptest $stamp -gt $numsecs ; then let 'n++' && continue ; fi
pretty=$(pretty_stamp $stamp)
inf "Generating highlight #${n}/${#HLTIMECODES[*]} ($pretty)..."
 
capture "$f" $stamp || return $?
filter_vidcap "$VIDCAPFILE" $pretty $vidcap_width $vidcap_height || {
local r=$?
error "Failed to apply transformations to the capture."
return $r
}
hlcapfile=$(new_temp_file "-hl-$(pad 6 $n).png")
mv "$VIDCAPFILE" "$hlcapfile"
capfiles+=( "$hlcapfile" )
let 'n++'
done
 
let 'n--' # There's an extra inc
if [ "$n" -lt "$cols" ]; then
numcols=$n
else
numcols=$cols
fi
 
inf "Composing highlights contact sheet..."
hlfile=$( create_contact_sheet $numcols $CTX_HL $vidcap_width $vidcap_height "${capfiles[@]}" )
unset hlcapfile pretty n capfiles numcols
fi
unset n
 
# Normal captures
# TODO: Don't reference $VIDCAPFILE
local capfile pretty n=1 capfiles=( )
for stamp in $(clean_timestamps "${TIMECODES[*]}"); do
pretty=$(pretty_stamp $stamp)
inf "Generating capture #${n}/${#TIMECODES[*]} ($pretty)..."
 
capture "$f" $stamp || return $?
filter_vidcap "$VIDCAPFILE" $pretty $vidcap_width $vidcap_height || return $?
 
# identified by capture number, padded to 6 characters
capfile=$(new_temp_file "-cap-$(pad 6 $n).png")
mv "$VIDCAPFILE" "$capfile"
capfiles+=( "$capfile" )
let 'n++' # $n++
done
#filter_all_vidcaps "${capfiles[@]}"
 
let 'n--' # there's an extra inc
if [ "$n" -lt "$cols" ]; then
numcols=$n
else
numcols=$cols
fi
 
inf "Composing standard contact sheet..."
output=$(create_contact_sheet $numcols $CTX_STD $vidcap_width $vidcap_height "${capfiles[@]}")
unset capfile capfiles pretty n numcols
 
# Extended mode
local extoutput=
if [ "$extended_factor" != 0 ]; then
# Number of captures. Always rounded to a multiplier of *double* the
# number of columns (the extended caps are half width, this way they
# match approx with the standard caps width)
local hlnc=$(rtomult "$(( ${#TIMECODES[@]} * $extended_factor ))" $((2*$numcols)))
 
unset TIMECODES # required step to get the right count
declare -a TIMECODES # Note the manual stamps aren't included anymore
compute_timecodes $TC_NUMCAPS "" $hlnc
unset hlnc
 
local n=1 w= h= capfile= pretty= capfiles=( )
# The image size of the extra captures is 1/4
let 'w=vidcap_width/2, h=vidcap_height/2'
for stamp in $(clean_timestamps "${TIMECODES[*]}"); do
pretty=$(pretty_stamp $stamp)
inf "Generating capture from extended set: ${n}/${#TIMECODES[*]} ($pretty)..."
capture "$f" $stamp || return $?
filter_vidcap "$VIDCAPFILE" $pretty $w $h || return $?
 
capfile=$(new_temp_file "-excap-$(pad 6 $n).png")
mv "$VIDCAPFILE" "$capfile"
capfiles+=( "$capfile" )
let 'n++'
done
 
let 'n--' # There's an extra inc
if [ $n -lt $(( $cols * 2 )) ]; then
numcols=$n
else
numcols=$(( $cols * 2 ))
fi
 
inf "Composing extended contact sheet..."
extoutput=$( create_contact_sheet $cols $CTX_EXT $w $h "${capfiles[@]}" )
 
unset w h capfile pretty n
fi # Extended mode
 
# Video codec "prettyfication", see [[R2]], [[R3]], [[R4]]
local vcodec= acodec=
case "${VID[$VCODEC]}" in
0x10000001) vcodec="MPEG-1" ;;
0x10000002) vcodec="MPEG-2" ;;
0x00000000) vcodec="Raw RGB" ;; # How correct is this?
avc1) vcodec="MPEG-4 AVC" ;;
DIV3) vcodec="DivX ;-) Low-Motion" ;; # Technically same as mp43
DX50) vcodec="DivX 5" ;;
FMP4) vcodec="FFmpeg" ;; # XXX: Would LAVC be a better name?
I420) vcodec="Raw I420 Video" ;; # XXX: Officially I420 is Indeo 4 but it is mapped to raw ¿?
MJPG) vcodec="M-JPEG" ;; # XXX: Actually mJPG != MJPG
MPG4) vcodec="MS MPEG-4 V1" ;;
MP42) vcodec="MS MPEG-4 V2" ;;
MP43) vcodec="MS MPEG-4 V3" ;;
RV10) vcodec="RealVideo 1.0/5.0" ;;
RV20) vcodec="RealVideo G2" ;;
RV30) vcodec="RealVideo 8" ;;
RV40) vcodec="RealVideo 9/10" ;;
SVQ1) vcodec="Sorenson Video 1" ;;
SVQ3) vcodec="Sorenson Video 3" ;;
theo) vcodec="Ogg Theora" ;;
tscc) vcodec="TechSmith Screen Capture Codec" ;;
VP6[012]) vcodec="On2 Truemotion VP6" ;;
WMV1) vcodec="WMV7" ;;
WMV2) vcodec="WMV8" ;;
WMV3) vcodec="WMV9" ;;
WMVA) vcodec="WMV9 Advanced Profile" ;; # Not VC1 compliant. Unsupported.
XVID) vcodec="Xvid" ;;
 
# These are known FourCCs that I haven't tested against so far
DIV4) vcodec="DivX ;-) Fast-Motion" ;;
DIVX|divx) vcodec="DivX" ;; # OpenDivX / DivX 5(?) / Project Mayo
IV4[0-9]) vcodec="Indeo Video 4" ;;
IV50) vcodec="Indeo 5.0" ;;
VP3[01]) vcodec="On2 VP3" ;;
VP40) vcodec="On2 VP4" ;;
VP50) vcodec="On2 VP5" ;;
# Legacy(-er) codecs (haven't seen files in these formats in awhile)
IV3[0-9]) vcodec="Indeo Video 3" ;;
MSVC) vcodec="Microsoft Video 1" ;;
MRLE) vcodec="Microsoft RLE" ;;
*) # If not recognized show FOURCC
vcodec=${VID[$VCODEC]}
;;
esac
if [ "${VID[$VDEC]}" == "ffodivx" ]; then
vcodec+=" (MPEG-4)"
elif [ "${VID[$VDEC]}" == "ffh264" ]; then
vcodec+=" (h.264)"
fi
 
# Audio codec "prettyfication", see [[R4]]
case $(tolower ${VID[$ACODEC]} ) in
85) acodec='MPEG Layer III (MP3)' ;;
80) acodec='MPEG Layer I/II (MP1/MP2)' ;; # Apparently they use the same tag
mp4a) acodec='MPEG-4 AAC' ;; # LC and HE, apparently
352) acodec='WMA7' ;; # =WMA1
353) acodec='WMA8' ;; # =WMA2 No idea if lossless can be detected
354) acodec='WMA9' ;; # =WMA3
8192) acodec='AC3' ;;
1|65534)
# 1 is standard PCM (apparently all sample sizes)
# 65534 seems to be multichannel PCM
acodec='Linear PCM' ;;
vrbs|22127)
# 22127 = Vorbis in AVI (with ffmpeg) DON'T!
# vrbs = Vorbis in Matroska, probably other sane containers
acodec='Vorbis'
;;
qdm2) acodec="QDesign" ;;
"") acodec="no audio" ;;
# Following not seen by me so far, don't even know if mplayer would
# identify them
#<http://lists.mplayerhq.hu/pipermail/ffmpeg-devel/2005-November/005054.html>
355) acodec="WMA9 Lossless" ;;
10) acodec="WMA9 Voice" ;;
*) # If not recognized show audio id tag
acodec=${VID[$ACODEC]}
;;
esac
 
if [ "${VID[$CHANS]}" ] && is_number "${VID[$CHANS]}" &&[ ${VID[$CHANS]} -ne 2 ]; then
if [ ${VID[$CHANS]} -eq 0 ]; then
# This happens e.g. in non-i386 when playing WMA9 at the time of
# this writing
warn "Detected 0 audio channels."
warn " Does this version of mplayer support the audio codec ($acodec)?"
elif [ ${VID[$CHANS]} -eq 1 ]; then
acodec+=" (mono)"
else
acodec+=" (${VID[$CHANS]}ch)"
fi
fi
 
 
if [ "$HLTIMECODES" ] || [ "$extended_factor" != "0" ]; then
inf "Merging contact sheets..."
fi
# If there were highlights then mix them in
if [ "$HLTIMECODES" ]; then
#\( -geometry x2 xc:black -background black \) # This breaks it!
convert \( -background LightGoldenRod "$hlfile" -flatten \) \
\( "$output" \) -append "$output"
fi
# Extended captures
if [ "$extended_factor" != 0 ]; then
convert "$output" "$extoutput" -append "$output"
fi
# Add the background
convert -background "$bg_contact" "$output" -flatten "$output"
 
# Let's add meta inf and signature
inf "Adding header and footer..."
local meta2="Dimensions: ${VID[$W]}x${VID[$H]}
Format: $vcodec / $acodec
FPS: ${VID[$FPS]}"
local signature
if [ $anonymous_mode -eq 0 ]; then
signature="$user_signature $user
with $PROGRAM_SIGNATURE"
else
signature="Created with $PROGRAM_SIGNATURE"
fi
local headwidth=$(identify "$output" | cut -d' ' -f3 | cut -d'x' -f1)
# TODO: Use a better height calculation
local headheight=$(($pts_meta * 4 ))
local heading=$(new_temp_file .png)
# Add the title if any
if [ "$title" ]; then
# TODO: Use a better height calculation
convert \
\( \
-size ${headwidth}x$(( $pts_title + ($pts_title/2) )) "xc:$bg_title" \
-font "$font_title" -pointsize "$pts_title" \
-background "$bg_title" -fill "$fg_title" \
-gravity Center -annotate 0 "$title" \
\) \
-flatten \
"$output" -append "$output"
fi
local fn_font= # see $font_filename
case $font_filename in
$FF_DEFAULT) fn_font="$font_heading" ;;
$FF_MINCHO) fn_font="$FONT_MINCHO" ;;
*)
warn "\$font_filename was overridden with an incorrect value, using default."
fn_font="$font_heading"
;;
esac
# Talk about voodoo... feel the power of IM... let's try to explain what's this:
# It might technically be wrong but it seems to work as I think it should
# (hence the voodoo I was talking)
# Parentheses restrict options inside them to only affect what's inside too
# * Create a base canvas of the desired width and height 1. The width is tweaked
# because using "label:" later makes the text too close to the border, that
# will be compensated in the last step.
# * Create independent intermediate images with each row of information, the
# filename row is split in two images to allow changing the font, and then
# they're horizontally appended (and the font reset)
# * All rows are vertically appended and cropped to regain the width in case
# the filename is too long
# * The appended rows are appended to the original canvas, the resulting image
# contains the left row of information with the full heading width and
# height, and this is the *new base canvas*
# * Draw over the new canvas the right row with annotate in one
# operation, the offset compensates for the extra pixel from the original
# base canvas. XXX: Using -annotate allows setting alignment but it breaks
# vertical alignment with the other rows' labels.
# * Finally add the border that was missing from the initial width, we have
# now the *complete header*
# * Add the contact sheet and append it to what we had.
# * Start a new image and annotate it with the signature, then append it too.
convert \
\( \
-size $(( ${headwidth} -18 ))x1 "xc:$bg_heading" +size \
-font "$font_heading" -pointsize "$pts_meta" \
-background "$bg_heading" -fill "$fg_heading" \
\( \
-gravity West \
\( label:"Filename:" \
-font "$fn_font" label:"$(basename "$f")" +append \
\) \
-font "$font_heading" \
label:"File size: $(get_pretty_size "$f")" \
label:"Length: $(cut -d'.' -f1 <<<$(pretty_stamp ${VID[$LEN]}))" \
-append -crop ${headwidth}x${headheight}+0+0 \
\) \
-append \
\( \
-size ${headwidth}x${headheight} \
-gravity East -fill "$fg_heading" -annotate +0-1 "$meta2" \
\) \
-bordercolor "$bg_heading" -border 9 \
\) \
"$output" -append \
\( \
-size ${headwidth}x34 -gravity Center "xc:$bg_sign" \
-font "$font_sign" -pointsize "$pts_sign" \
-fill "$fg_sign" -annotate 0 "$signature" \
\) \
-append \
"$output"
unset signature meta2 headwidth headheight heading fn_font
 
if [ $output_format != "png" ]; then
local newout="$(dirname "$output")/$(basename "$output" .png).$output_format"
convert -quality $output_quality "$output" "$newout"
output="$newout"
fi
output_name=$( safe_rename "$output" "$(basename "$f").$output_format" ) || {
error "Failed to write the output file!"
return $EX_CANTCREAT
}
inf "Done. Output wrote to $output_name"
 
cleanup
}
 
# }}} # Core functionality
 
# {{{ # Debugging helpers
 
# Tests integrity of some operations.
# Used to test internal changes for consistency.
# It helps me to identify incorrect optimizations.
# unit_test()
unit_test() {
local t op val ret comm retval=0
 
# Textual tests, compare output to expected output
# Tests are in the form "operation arguments correct_result #Description"
local TESTS=(
"rmultiply 1,1 1 #Identity"
"rmultiply 16/9,1 2 #Rounding" # 1.77 rounded 2
"rmultiply 4/3 1 #Rounding" # 1.33 rounded 1
"rmultiply 1,16/9 2 #Commutative property"
"rmultiply 1.7 2 #Alternate syntax"
 
"ceilmultiply 1,1 1 #"
"ceilmultiply 4/3 2 #" # 1.33 rounded 2
"ceilmultiply 7,1/2 4 #" # 3.5 rounded 4
"ceilmultiply 7/2 4 #Alternative syntax"
"ceilmultiply 1/2,7 4 #Commutative property"
 
"pad 10 0 0000000000 #Padding"
"pad 1 20 20 #Unneeded padding"
"pad 5 23.3 023.3 #Floating point padding"
 
"guess_aspect 720 576 4/3 #DVD AR Guess"
"guess_aspect 1024 576 1024/576 #Unsupported Wide AR Guess"
 
"tolower ABC abc #lowercase conversion"
 
"pyth_th 4 3 5 #Integer pythagorean theorem"
"pyth_th 16 9 18.35755975068581929849 #FP pythagorean theorem"
)
for t in "${TESTS[@]}" ; do
# Note the use of ! as separator, this is because # and / are used in
# many of the inputs
comm=$(sed 's!.* #!!g' <<<$t)
# Expected value
val=$(sed -r "s!.* (.*) #$comm\$!\1!g"<<<$t)
op=$(sed "s! $val #$comm\$!!g" <<<$t)
if [ -z "$comm" ]; then
comm=unnamed
fi
ret=$($op) || true
 
if [ "$ret" != "$val" ] && fptest "$ret" -ne "$val" ; then
error "Failed test ($comm): '$op $val'. Got result '$ret'."
let 'retval++,1' # The ,1 ensures let doesn't fail
else
inf "Passed test ($comm): '$op $val'."
fi
done
 
# Returned value tests, compare return to expected return
local TESTS=(
# Don't use anything with a RE meaning
 
# Floating point numeric "test"
"fptest 3 -eq 3 0 #FP test"
"fptest 3.2 -gt 1 0 #FP test"
"fptest 1/2 -le 2/3 0 #FP test"
"fptest 6.34 -gt 6.34 1 #FP test"
"fptest 1>0 -eq 1 0 #FP -logical- test"
 
"is_number 3 0 #Numeric recognition"
"is_number '3' 1 #Quoted numeric recognition"
"is_number 3.3 1 #Non-numeric recognition"
 
"is_float 3.33 0 #Float recognition"
"is_float 3 0 #Float recognition"
"is_float 1/3 1 #Non-float recognition"
 
"is_fraction 1/1 0 #Fraction recognition"
"is_fraction 1 1 #non-fraction recognition"
"is_fraction 1.1 1 #Non-fraction recognition"
)
for t in "${TESTS[@]}"; do
comm=$(sed 's!.* #!!g' <<<$t)
# Expected value
val=$(sed -r "s!.* (.*) #$comm\$!\1!g"<<<$t)
op=$(sed "s! $val #$comm\$!!g" <<<$t)
if [ -z "$comm" ]; then
comm=unnamed
fi
ret=0
$op || {
ret=$?
}
 
if [ $val -eq $ret ]; then
inf "Passed test ($comm): '$op; returns $val'."
else
error "Failed test ($comm): '$op; returns $val'. Returned '$ret'"
let 'retval++,1'
fi
done
 
return $retval
}
 
 
# }}} # Debugging helpers
 
# {{{ # Help / Info
 
# Prints the program identification to stderr
show_vcs_info() { # Won't be printed in quiet modes
inf "Video Contact Sheet *NIX v${VERSION}, (c) 2007 Toni Corvera" "sgr0"
}
 
# Prints the list of options to stdout
show_help() {
local P=$(basename $0)
cat <<EOF
Usage: $P [options] <file>
 
Options:
-i|--interval <arg> Set the interval to arg. Units can be used
(case-insensitive), i.e.:
Seconds: 90 or 90s
Minutes: 3m
Hours: 1h
Combined: 1h3m90
Use either -i or -n.
-n|--numcaps <arg> Set the number of captured images to arg. Use either
-i or -n.
-c|--columns <arg> Arrange the output in 'arg' columns.
-H|--height <arg> Set the output (individual thumbnail) height. Width is
derived accordingly. Note width cannot be manually set.
-a|--aspect <aspect> Aspect ration. Accepts floating point number or
fractions.
-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.
-E|--end_offset <arg> This time is ignored, from the end of the video. Same
format as -i. This value is not used when a explicit
ending time is set. By default it is $DEFAULT_END_OFFSET.
-T|--title <arg> Add a title above the vidcaps.
-j|--jpeg Output in jpeg (by default output is in png).
-q|--quiet Don't print progess messages just errors. Repeat to
mute completely even on error.
-h|--help Show this text.
-Wo Workaround: Change ffmpeg's arguments order, might
work with some files that fail otherwise.
-A|--autoaspect Try to guess aspect ratio from resolution.
-e[num] | --extended=[num]
Enables extended mode and optionally sets the extended
factor. -e is the same as -e$DEFAULT_EXT_FACTOR.
-l|--highlight <arg> Add the image found at the timestamp "arg" as a
highlight. Same format as -i.
-m|--manual Manual mode: Only timestamps indicated by the user are
used (use in conjunction with -S), when using this
-i and -n are ignored.
-O|--override <arg> Use it to override a variable (see the homepage for
more details). Format accepted is 'variable=value' (can
also be quoted -variable="some value"- and can take an
internal variable too -variable="\$SOME_VAR"-).
-S|--stamp <arg> Add the image found at the timestamp "arg". Same format
as -i.
 
-u|--user <arg> Set the username found in the signature to this.
-U|--fullname Use user's full/real name (e.g. John Smith) as found in
/etc/passwd.
-Ij|-Ik
--mincho Use the kana/kanji/hiragana font (EXPERIMENTAL) might
also work partially with Hangul and Cyrillic.
-k <arg>
--funky <arg> Funky modes:
These are toy output modes in which the contact sheet
gets a more informal look.
Order *IS IMPORTANT*. A bad order gets a bad result :P
They're random in nature so using the same funky mode
twice will usually lead to quite different results.
Currently available "funky modes":
"overlap": Use '-ko' or '--funky overlap'
Randomly overlap captures.
"rotate": Use '-kr' or '--funky rotate'
Randomly rotate each image.
"photoframe": Use '-kf' or '--funky photoframe'
Adds a photo-like white frame to each image.
"polaroid": Use '-kp' or '--funky polaroid'
Combination of rotate, photoframe and overlap.
Same as -kr -ko -kf.
"film": Use '-ki' or '--funky film'
Imitates filmstrip look.
"random": Use '-kx' or '--funky random'
Randomizes colours and fonts.
 
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 shouldn't need it.
-D Debug mode. Used to test features/integrity. It:
* Prints the input command line
* Sets the title to reflect the command line
* Does a basic test of consistency.
 
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 and a half minutes:
\$ $P -i 3m30 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
 
See more examples at vcs' homepage <http://p.outlyer.net/vcs/>.
 
EOF
}
 
# }}} # Help / Info
 
#### Execution starts here ####
 
# If tput isn't found simply ignore tput commands
# (no colour support)
# Important to do it before any message can be thrown
if ! type -pf tput >/dev/null ; then
tput() { cat >/dev/null <<<"$1"; }
warn "tput wasn't found. Coloured feedback disabled."
fi
 
# Execute exithdlr on exit
trap exithdlr EXIT
 
show_vcs_info
 
load_config
 
# {{{ # Command line parsing
 
# TODO: Find how to do this correctly (this way the quoting of $@ gets messed):
#eval set -- "${default_options} ${@}"
ARGS="$@"
 
# [[R0]]
TEMP=$(getopt -s bash -o i:n:u:T:f:t:S:jhFMH:c:ma:l:De::U::qAO:I::k:W:E: \
--long "interval:,numcaps:,username:,title:,from:,to:,stamp:,jpeg,help,"\
"shoehorn:,mplayer,ffmpeg,height:,columns:,manual,aspect:,highlight:,"\
"extended::,fullname,anonymous,quiet,autoaspect,override:,mincho,funky:,end_offset:" \
-n $0 -- "$@")
 
eval set -- "$TEMP"
 
while true ; do
case "$1" in
-i|--interval)
if ! interval=$(get_interval "$2") ; then
error "Incorrect interval format. Got '$2'."
exit $EX_USAGE
fi
if [ "$interval" == "0" ]; then
error "Interval must be higher than 0, set to the default $DEFAULT_INTERVAL"
interval=$DEFAULT_INTERVAL
fi
timecode_from=$TC_INTERVAL
shift # Option arg
;;
-n|--numcaps)
if ! is_number "$2" ; then
error "Number of captures must be (positive) a number! Got '$2'."
exit $EX_USAGE
fi
if [ $2 -eq 0 ]; then
error "Number of captures must be greater than 0! Got '$2'."
exit $EX_USAGE
fi
numcaps="$2"
timecode_from=$TC_NUMCAPS
shift # Option arg
;;
-u|--username) user="$2" ; shift ;;
-U|--fullname)
# -U accepts an optiona argument, 0, to make an anonymous signature
# --fullname accepts no argument
if [ "$2" ]; then # With argument, special handling
if [ "$2" != "0" ]; then
error "Use '-U0' to make an anonymous contact sheet or '-u \"My Name\"'"
error " to sign as My Name. Got -U$2"
exit $EX_USAGE
fi
anonymous_mode=1
shift
else # No argument, default handling (try to guess real name)
user=$(grep ^$(id -un): /etc/passwd | cut -d':' -f5 |sed 's/,.*//g')
if [ -z "$user" ]; then
user=$(id -un)
error "No fullname found, falling back to default ($user)"
fi
fi
;;
--anonymous) anonymous_mode=1 ;; # Same as -U0
-T|--title) title="$2" ; shift ;;
-f|--from)
if ! fromtime=$(get_interval "$2") ; then
error "Starting timestamp must be a valid timecode. Got '$2'."
exit $EX_USAGE
fi
shift
;;
-E|--end_offset)
if ! end_offset=$(get_interval "$2") ; then
error "End offset must be a valid timecode. Got '$2'."
exit $EX_USAGE
fi
shift
;;
-t|--to)
if ! totime=$(get_interval "$2") ; then
error "Ending timestamp must be a valid timecode. Got '$2'."
exit $EX_USAGE
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 timecode. Got '$2'."
exit $EX_USAGE
fi
initial_stamps=( ${initial_stamps[*]} $temp )
shift
;;
-l|--highlight)
if ! temp=$(get_interval "$2"); then
error "Timestamps must be a valid timecode. Got '$2'."
exit $EX_USAGE
fi
HLTIMECODES+=( $temp )
shift
;;
-j|--jpeg) output_format=jpg ;;
-h|--help) show_help ; exit $EX_OK ;;
--shoehorn)
shoehorned="$2"
shift
;;
-F) decoder=$DEC_FFMPEG ;;
-M) decoder=$DEC_MPLAYER ;;
-H|--height)
if ! is_number "$2" ; then
error "Height must be a (positive) number. Got '$2'."
exit $EX_USAGE
fi
th_height="$2"
shift
;;
-a|--aspect)
if ! is_float "$2" && ! is_fraction "$2" ; then
error "Aspect ratio must be expressed as a (positive) floating "
error " point number or a fraction (ie: 1, 1.33, 4/3, 2.5). Got '$2'."
exit $EX_USAGE
fi
aspect_ratio="$2"
shift
;;
-A|--autoaspect) aspect_ratio=-1 ;;
-c|--columns)
if ! is_number "$2" ; then
error "Columns must be a (positive) number. Got '$2'."
exit $EX_USAGE
fi
cols="$2"
shift
;;
-m|--manual) manual_mode=1 ;;
-e|--extended)
# Optional argument quirks: $2 is always present, set to '' if unused
# from the commandline it MUST be directly after the -e (-e2 not -e 2)
# the long format is --extended=VAL
# XXX: For some reason parsing of floats gives an error, so for now
# ints and only fractions are allowed
if [ "$2" ] && ! is_float "$2" && ! is_fraction "$2" ; then
error "Extended multiplier must be a (positive) number (integer, float "\
"or fraction)."
error " Got '$2'."
exit $EX_USAGE
fi
if [ "$2" ]; then
extended_factor="$2"
else
extended_factor=$DEFAULT_EXT_FACTOR
fi
shift
;;
--mincho) font_filename=$FF_MINCHO ;;
-I) # -I technically takes an optional argument (for future alternative
# fonts) although it is documented as a two-letter option
# Don't relay on using -I though, if I ever add a new alternative font
# I might not allow it anymore
if [ "$2" ] ; then
# If an argument is passed, test it is one of the known ones
case "$2" in
k|j) ;;
*) error "-I must be followed by j or k!" && exit $EX_USAGE ;;
esac
fi
# It isn't tested for existence because it could also be a font
# which convert would understand without giving the full path
font_filename=$FF_MINCHO;
shift
;;
-O|--override)
# Rough test
if ! egrep -q '[a-zA-Z_]+=[^;]*' <<<"$2"; then
error "Wrong override format, it should be variable=value. Got '$2'."
exit $EX_USAGE
fi
override "$2" "command line"
shift
;;
-W) # Workaround mode, see wa_ss_* declarations at the start for details
if [ "$2" != "o" ]; then
error "Wrong argument. Use -Wo instead of -W$2."
exit $EX_USAGE
fi
wa_ss_af='-ss ' wa_ss_be=''
shift
;;
-k|--funky) # Funky modes
case $(tolower "$2") in
p|polaroid) # Same as overlap + rotate + photoframe
inf "Changed to polaroid funky mode."
FILTERS_IND+=( 'filt_photoframe' 'filt_randrot' )
CSHEET_DELEGATE='csheet_overlap'
# The timestamp must change location to be visible
grav_timestamp=NorthWest
;;
o|overlap) # Random overlap mode
CSHEET_DELEGATE='csheet_overlap'
grav_timestamp=NorthWest
;;
r|rotate) # Random rotation
FILTERS_IND+=( 'filt_randrot' )
;;
f|photoframe) # White photo frame
FILTERS_IND+=( 'filt_photoframe' )
;;
i|film)
inf "Enabled film mode."
FILTERS_IND+=( 'filt_film' )
;;
x|random) # Random colours/fonts
inf "Enabled random colours and fonts."
randomize_look
;;
*)
error "Unknown funky mode. Got '$2'."
exit $EX_USAGE
;;
esac
shift
;;
-q|--quiet)
# -q to only show errors
# -qq to be completely quiet
if [ $verbosity -gt $V_ERROR ]; then
verbosity=$V_ERROR
else
verbosity=$V_NONE
fi
;;
-D) # Repeat to just test consistency
if [ $DEBUGGED -gt 0 ]; then exit ; fi
inf "Testing internal consistency..."
unit_test
if [ $? -eq 0 ]; then
warn "All tests passed"
else
error "Some tests failed!"
fi
DEBUGGED=1
warn "Command line: $0 $ARGS"
title="$(basename "$0") $ARGS"
;;
--) shift ; break ;;
*) error "Internal error! (remaining opts: $@)" ; exit $EX_SOFTWARE ;
esac
shift
done
 
# Remaining arguments
if [ ! "$1" ]; then
show_help
exit $EX_USAGE
fi
 
# }}} # Command line parsing
 
# Test requirements
test_programs || exit $EX_UNAVAILABLE
 
# If -m is used then -S must be used
if [ $manual_mode -eq 1 ] && [ -z $initial_stamps ]; then
error "You must provide timestamps (-S) when using manual mode (-m)"
exit $EX_USAGE
fi
 
set +e # Don't fail automatically
for arg do process "$arg" ; done
 
 
# vim:set ts=4 ai foldmethod=marker: #
Property changes:
Added: svn:executable
Added: svn:keywords
+Rev Id Date
\ No newline at end of property
/video-contact-sheet/tags/1.0.8a/CHANGELOG
0,0 → 1,136
1.0.8a: (2007-06-02) (Bugfix release)
* BUGFIX: User set number of columns wasn't being used if -n wasn't used
(Thanks to 'Homer S').
 
1.0.7a: (2007-05-12)
* Print title *before* the highlights.
* Added the forgotten -O and -c to the help text (oops!)
* Experimental: Allow using non-latin alphabets by switching font. See -I.
It only affects the filename! Also allow overriding the font to be used
to print the filename ($font_filename). Right now only using a Mincho font,
it can be overriding by overriding $FONT_MINCHO.
* Make title font size independent of the timestamps size. And allow
overriding the title font ($font_title), font size ($pts_title)
and colours ($fg_title and $bg_title).
* Allow overriding the previews' background ($bg_contact)
* Added getopt, identify, sed, grep and egrep to the checked programs
* BUGFIX: Corrected test of accepted characters for intervals
* INTERNAL: New parsing code
* FEATURE: Replaced hard by soft shadows
* BUGFIX: Corrected console colour usage: Print the colours to the correct
channel
* Made tput (coloured console output) optional (AFAIK should be present in
any sane system though).
* FEATURE: Funky modes (more to come...): Polaroid, Film (rough, initial,
version), Photoframe and Random colours/fonts. (see --help)
* INTERNAL: Use /dev/shm as base tempdir if possible
* BUGFIX: Fixed safe_rename(): Don't assume current dir, added '--' to mv
* Added workaround for ffmpeg arguments order
* Allow getting the output of ffmpeg/mplayer (with $stdout and $stderr)
* INTERNAL: Renamed info() to inf() to eliminate ambiguities
* INTERNAL: guess_aspect() doesn't operate globally
* Reorganized help by alphabetical/rarity order
* FEATURE: Full milliseconds support (actually, full decimal point seconds),
timecode format extended to support e.g. 3m.24 (which means 00:03:00.240)
* BUGFIX/FEATURE: The number of extended captures is rounded to match the
standard columns (extended width matches standard)
* Made FOURCCs list case sensitive (the list has grown enough that I no
longer see a benefit in being ambigous)
* Added codec ids for On2's VP3, VP4, VP5 and VP6, TechSmith Screen Capture
Codec (Camtasia's) and Theora, expanded list of FOURCCs of Indeo's
codecs.
* Added -E / --end_offset / $DEFAULT_END_OFFSET, used to eliminate some
seconds from the end
 
1.0.6b: (2007-04-21) (Bugfix release)
* BUGFIX: Use mktemp instead of tempfile (Thanks to 'o kapi')
* Make sure mktemp is installed, just in case ;)
 
1.0.5b: (2007-04-20)
* INTERNAL: Split functionality in more separate pieces (functions)
* BUGFIX: Corrected --aspect declaration
* CLEANUP: Put all temporary files in the same temporary directory
* FEATURE: Highlight support
* FEATURE: Extended mode (-e)
* FEATURE: Added -U (--fullname)
* Requirements detection now prints all failed requirements
* BUGFIX: (Regression introduced in 1.0.4b) Fail if interval is longer
than video
* Don't print the sucess line unless it was really successful
* Allow quiet operation (-q and -qq), and different verbosity levels
(only through config overrides)
* Print vcs' identification on operation
* FEATURE: Auto aspect ratio (-A, --autoaspect)
* INTERNAL: Added better documentation of functions
* Print coloured messages if possible (can be disabled by overriding
$plain_messages)
* FEATURE: Command line overrides (-O, --override)
* BUGFIX: Don't allow setting -n0
* Renamed codec ids of WMA2 (to WMA8) and WMA3 (to WMA9)
* Changed audio codec ids from MPEG-1 to MPEG, as there's no difference,
from mplayer's identification at least, between MPEG-1 and MPEG-2
* Audio identified as MP2 can also actually be MP1, added it to the codec id
* Added codec ids for: Vorbis, WMA7/WMA1, WMV1/WMV7, PCM, DivX ;),
OpenDivX, LAVC MPEG-4, MSMPEG-4 family, M-JPEG, RealVideo family, I420,
Sorenson 1 & 3, QDM2, and some legacy codecs (Indeo 3.2, Indeo 5.0,
MS Video 1 and MS RLE)
* Print the number of channels if != 2
 
1.0.4b: (2007-04-17)
* Added error checks for failures to create vidcap or to process it
convert
* BUGFIX: Corrected error check on tempdir creation
* BUGFIX: Use temporary locations for temporary files (thanks to
Alon Levy).
* Aspect ratio support (might be buggy). Requires bc.
* Added $safe_rename_pattern to allow overriding the default alternate
naming when the output file exists
* Moved previous previous versions' changes to a separate file.
* Support for per-dir and system-wide configuration files. Precedence
in ascending order:
/etc/vcs.conf ~/.vcs.conf ./vcs.conf
* Added default_options (broken, currently ignored)
* BUGFIX: (Apparently) Corrected the one-vidcap-less/more bug
* Added codec ids of WMV9 and WMA3
 
1.0.3b: (2007-04-14)
* BUGFIX: Don't put the full video path in the heading
 
1.0.2b: (2007-04-14)
* Licensed under LGPL (was unlicensed before)
* Renamed variables and constants to me more congruent
* Added DEFAULT_COLS
* BUGFIX: Fixed program signature (broken in 1.0.1a)
* Streamlined error codes
* Added cleanup on failure and on delayed cleanup on success
* Changed default signature background to SlateGray (blue-ish gray)
 
1.0.1a: (2007-04-13)
* Print output filename
* Added manual mode (all timestamps provided by user)
* More flexible timestamp format (now e.g. 1h5 is allowed (means 1h 5secs)
* BUGFIX: Discard repeated timestamps
* Added "set -e". TODO: Add more verbose error messages when called
programs fail.
* Added basic support for a user configuration file.
 
1.0a: (2007-04-10)
* 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
* Added codec ids of WMV8 and WMA2
 
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
 
Property changes:
Added: svn:keywords
+Rev Id Date
\ No newline at end of property
/video-contact-sheet/tags/1.0.8a
Property changes:
Added: svn:mergeinfo
Merged /video-contact-sheet/branches/1.0a:r262-263
Merged /video-contact-sheet/branches/1.0.1a:r266-267
Merged /video-contact-sheet/tags/0.99a:r261
Merged /video-contact-sheet/branches/1.0.2b:r270-271
Merged /video-contact-sheet/branches/1.0.3b:r276-277
Merged /video-contact-sheet/branches/1.0.4b:r280-281
Merged /video-contact-sheet/branches/1.0.5b:r284-285
Merged /video-contact-sheet/branches/1.0.6b:r289-290
Merged /video-contact-sheet/branches/1.0.7a:r294-311
Merged /video-contact-sheet/branches/1.0.8a:r315-317
Merged /video-contact-sheet/tags/1.0.2b:r274