Subversion Repositories pub

Compare Revisions

No changes between revisions

Ignore whitespace Rev 343 → Rev 344

/video-contact-sheet/tags/1.0.11/vcs
0,0 → 1,2274
#!/bin/bash
#
# $Rev$ $Date$
#
# vcs
# Video Contact Sheet *NIX: Generates contact sheets (previews) of videos
#
# Copyright (C) 2007, 2008 Toni Corvera
# with patches from Phil Grundig and suggestions/corrections from
# many others (see homepage)
#
# 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@gmail.com>
#
# References:
# Pages I've taken snippets from 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>
# [M1] "[MEncoder-users] Thumbnail creation"
# <http://lists.mplayerhq.hu/pipermail/mencoder-users/2006-August/003843.html>
# [VC1] VC-1 and derived codecs information
# <http://wiki.multimedia.cx/index.php?title=VC-1>
#
 
declare -r VERSION="1.0.11"
# {{{ # CHANGELOG
# History (The full changelog can be found at <http://p.outlyer.net/vcs/files/CHANGELOG>).
#
# 1.0.11: (2008-04-08)
# * BUGFIX: (brown bag bug) Corrected typo in variable name that made vcs
# fail when setting the default timecode derivation to number of
# captures instead of interval (i.e. when including timecode_from=8 in
# the config file) (thanks to Chris Hills for the bug report)
# * WORKAROUND: Fix for all-equal captures (seems to be a known problem
# with mplayer [M1]) (contributed by Phil Grundig)
# * RETROCOMPATIBILITY: Older bash syntax for appending and initialising
# arrays (contributed by Phil Grundig)
# * COMPATIBILITY: Support alternative du syntax for compatibility with
# busybox (based on Phil Grundig's contribution)
# * COSMETIC: Don't print milliseconds when using mplayer as capturer
# (they're not really meaningful then) (suggested by Phil Grundig)
# * COSMETIC: Align the extended set captures (-e) and the standard set
# (bug pointed by Chris Hills). Seems to fail at some (smaller?)
# sizes.
# "Funky" modes aren't correctly aligned yet.
# * DEBUGGING: Added optional function call trace (by setting variable DEBUG
# to 1)
# * Added FOURCC for VC-1
# * COSMETIC: Fixed captures recount with multiple files (prompted by a
# bugreport from 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
 
# This is the horizontal padding added to each capture. Changing it might break
# extended set's alignement so keep this in mind if you tinker with it
declare -ri HPAD=8
# }}} # End of constants
 
# {{{ # Override-able variables
 
# Set to 1 to print function calls
declare -i DEBUG=0
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
 
# See csheet_montage for more details
declare -i DISABLE_SHADOWS=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'
'DEBUG'
)
 
# 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=$DEFAULT_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:"
# Unreproducible bug reported by wdef: Minutes printed as hours
# fixed with "else R="00:""
fi
R="$R$(pad 2 "$m"):$(pad 2 $s)"
# Milliseconds, only supported by ffmpeg, not printed otherwise
if [ $decoder -eq $DEC_FFMPEG ]; then
# Right pad of decimal seconds
if [ ${#ms} -lt 2 ]; then
ms="${ms}0"
fi
R="$R.$ms"
fi
 
# 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=$(get_file_size "$f")
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"
}
 
# Gets the file size in bytes
# get_file_size($1 = filename)
# du can provide bytes or kilobytes depending on the version used. The difference
# can be notorius...
# At least the busybox implementation is a real world du in usage that doesn't allow
# using --bytes. Note that using "ls -H" is not an option either for the same reason.
get_file_size() {
# First, try the extended du arguments:
local bytes
bytes=$(du -L --bytes "$1" 2>/dev/null) || {
echo $(( 1024 * $(du -Lk "$1" | cut -f1) ))
return
}
# Getting to here means the first du worked correctly
cut -f1 <<<"$bytes"
}
 
# 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[*]}
unset TEMPSTUFF ; declare -a 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
}
 
#
# trace($1 = function name = $FUNCNAME, function arguments...)
trace() {
if [ "$DEBUG" -ne "1" ]; then return; fi
echo "[TRACE]: $@" >&2
}
 
# }}} # Convenience functions
 
# {{{ # Core functionality
 
# Creates a new temporary directory
# create_temp_dir()
create_temp_dir() {
trace $FUNCNAME $@
# 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() {
trace $FUNCNAME $@
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() {
trace $FUNCNAME $@
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() {
trace $FUNCNAME $@
 
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 stamp=$st
local -a LTC
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() {
trace $FUNCNAME $@
# mplayer's ID_ASPECT seems to be always 0 ¿?
local w=$1 h=$2 ar
 
case "$w" in
352)
if [ $h -eq 288 ] || [ $h -eq 240 ]; then
# Ambiguous, could perfectly be 16/9
# VCD / DVD @ VCD Res. / Half-D1 / CVD
ar=4/3
elif [ $h -eq 576 ] || [ $h -eq 480 ]; then
# Ambiguous, could perfectly be 16/9
# Half-D1 / CVD
ar=4/3
fi
;;
704|720)
if [ $h -eq 576 ] || [ $h -eq 480 ]; then # DVD / DVB
# Ambiguous, could perfectly be 16/9
ar=4/3
fi
;;
480)
if [ $h -eq 576 ] || [ $h -eq 480 ]; then # SVCD
ar=4/3
fi
;;
esac
 
if [ -z "$ar" ]; then
if [ $h -eq 720 ] || [ $h -eq 1080 ]; then # HD
# TODO: Is there a standard for PAL yet?
ar=16/9
fi
fi
 
if [ -z "$ar" ]; then
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() {
trace $FUNCNAME $@
local f=$1 stamp=$2
local VIDCAPFILE=00000005.png
# globals: $shoehorned $decoder
 
if [ $decoder -eq $DEC_MPLAYER ]; then
{
# Capture 5 frames and drop the first 4, fixes a weird bug/feature of mplayer ([M1])
mplayer -sws 9 -ao null -benchmark -vo "png:z=0" -quiet \
-frames 5 -ss $stamp $shoehorned "$f"
} >"$stdout" 2>"$stderr"
rm -f 0000000{1,2,3,4}.png # Remove the first four
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() {
trace $FUNCNAME $@
# 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="$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() {
trace $FUNCNAME $@
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() {
trace $FUNCNAME $@
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() {
trace $FUNCNAME $@
# 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() {
trace $FUNCNAME $@
# 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() {
trace $FUNCNAME $@
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() {
trace $FUNCNAME $@
$CSHEET_DELEGATE "$@"
}
 
# This is the standard contact sheet creator
# csheet_montage($1 = columns, $2 = context, $3 = width, $4 = height,
# $5... = vidcaps) : output
csheet_montage() {
trace $FUNCNAME $@
local cols=$1 ctx=$2 width=$3 height=$4 output=$(new_temp_file .png)
shift 4
# Padding is no longer dependant upong context since alignment of the
# captures was far trickier then
local hpad=$HPAD vpad=4
 
# Using transparent seems to make -shadow futile
montage -background Transparent "$@" -geometry +$hpad+$vpad -tile "$cols"x "$output"
 
# This should actually be moved to a filter but with the current
# architecture I'm unable to come up with the correct convert options
if [ $DISABLE_SHADOWS -eq 0 ]; then
# This produces soft-shadows, which look much better than the montage ones
convert \( -shadow 50x2+10+10 "$output" \) "$output" -composite "$output"
fi
#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() {
trace $FUNCNAME $@
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="$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="$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() {
trace $FUNCNAME $@
# 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() {
trace $FUNCNAME $@
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() {
trace $FUNCNAME $@
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
 
unset TIMECODES
unset HLTIMECODES
# 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=00000005.png
 
# If the temporal vidcap already exists, abort
if [ -f $VIDCAPFILE ]; then
error "File 0000000$f.png exists and would be overwritten, move it out before running."
return $EX_CANTCREAT
fi
# mplayer will re-write also 00000001.png-00000004.png
if [ $decoder -eq $DEC_MPLAYER ]; then
for f_ in 1 2 3 4; do
if [ -f "0000000${f_}.png" ]; then
error "File 0000000${f_}.png exists and would be overwritten, move it out before running."
return $EX_CANTCREAT
fi
done
fi
 
TEMPSTUFF+=( $VIDCAPFILE )
 
# Highlights
local hlfile n=1 # hlfile Must be outside the if!
if [ "$HLTIMECODES" ]; then
local hlcapfile= pretty=
local -a 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
unset capfiles ; local -a 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 # must carry on to the extended caps: 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 are not included anymore
compute_timecodes $TC_NUMCAPS "" $hlnc
unset hlnc
 
local n=1 w= h= capfile= pretty=
unset capfiles ; local -a capfiles
# The image size of the extra captures is 1/4, adjusted to compensante the padding
let 'w=vidcap_width/2-HPAD, h=vidcap_height*w/vidcap_width'
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 $numcols $CTX_EXT $w $h "${capfiles[@]}" )
 
unset w h capfile pretty n numcols
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. Deprecated by Microsoft.
XVID) vcodec="Xvid" ;;
 
# These are known FourCCs that I haven't tested against so far
WVC1) vcodec="VC-1" ;;
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 ratio. Accepts a floating point number or a
fraction.
-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.
-d|--disable <arg> Disable some default functionality.
Features that can be disabled are:
* timestamps: use -dt or --disable timestamps
* shadows: use -ds or --disable shadows
-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:d: \
--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:,disable:" \
-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
;;
-d|--disable) # Disable default features
case $(tolower "$2") in
# timestamp (no final s) is undocumented but will stay
t|timestamps|timestamp)
inf "Timestamps disabled."
# TODO: Can array splicing be done in a saner way?
declare -a tmp=${FILTERS_IND[@]}
unset FILTERS_IND
FILTERS_IND=${tmp[@]/filt_apply_stamp/}
unset tmp
;;
s|shadows|shadow)
if [ $DISABLE_SHADOWS -eq 0 ]; then
inf "Shadows disabled."
DISABLE_SHADOWS=1
fi
;;
*)
error "Requested disabling unknown feature. 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.11/debian-package/debian/changelog
0,0 → 1,5
vcs (1.0.11) experimental; urgency=low
 
* First package released.
 
-- Toni Corvera <outlyer@gmail.com> Tue, 08 Apr 2008 21:15:14 +0200
/video-contact-sheet/tags/1.0.11/debian-package/debian/control
0,0 → 1,17
Source: vcs
Section: contrib/graphics
Priority: extra
Maintainer: Toni Corvera <outlyer@gmail.com>
Build-Depends: debhelper (>= 5)
Standards-Version: 3.7.2
 
Package: vcs
Architecture: all
Depends: bc, bash, grep, imagemagick (>= 6.0), mktemp, mplayer, ffmpeg
Description: vcs is a script that creates a contact sheet (preview) from videos
Video Contact Sheet *NIX (vcs for short) is a script that creates a contact
sheet (preview) from videos by taking still captures distributed over the
length of the video. The output image contains useful information on the video
such as codecs, file size, screen size, frame rate, and length.
.
Upstream homepage <http://p.outlyer.net/vcs/>.
/video-contact-sheet/tags/1.0.11/debian-package/debian/dirs
0,0 → 1,0
usr/bin
/video-contact-sheet/tags/1.0.11/debian-package/debian/compat
0,0 → 1,0
5
/video-contact-sheet/tags/1.0.11/debian-package/debian/copyright
0,0 → 1,37
This package was debianized by Toni Corvera <outlyer@gmail.com> on
Mon, 04 Feb 2008 03:32:28 +0100.
 
It was downloaded from <http://p.outlyer.net/vcs/>
 
Upstream Author:
 
Toni Corvera <outlyer@gmail.com>
 
Copyright:
 
<Copyright (C) 2007 Toni Corvera>
 
License:
 
This package 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 of the License, or (at your option) any later version.
 
This package 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 package; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 
On Debian systems, the complete text of the GNU Lesser General
Public License can be found in `/usr/share/common-licenses/LGPL'.
 
The Debian packaging is (C) 2008, Toni Corvera <outlyer@gmail.com> and
is licensed under the GPL, see `/usr/share/common-licenses/GPL'.
 
# Please also look if there are files or directories which have a
# different copyright/license attached and list them here.
/video-contact-sheet/tags/1.0.11/debian-package/debian/rules
0,0 → 1,98
#!/usr/bin/make -f
# -*- makefile -*-
# Sample debian/rules that uses debhelper.
# This file was originally written by Joey Hess and Craig Small.
# As a special exception, when this file is copied by dh-make into a
# dh-make output file, you may use that output file without restriction.
# This special exception was added by Craig Small in version 0.37 of dh-make.
 
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
 
 
 
 
CFLAGS = -Wall -g
 
ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS)))
CFLAGS += -O0
else
CFLAGS += -O2
endif
 
configure: configure-stamp
configure-stamp:
dh_testdir
# Add here commands to configure the package.
 
touch configure-stamp
 
 
build: build-stamp
 
build-stamp: configure-stamp
dh_testdir
 
# Add here commands to compile the package.
$(MAKE)
#docbook-to-man debian/vcs.sgml > vcs.1
 
touch $@
 
clean:
dh_testdir
dh_testroot
rm -f build-stamp configure-stamp
 
# Add here commands to clean up after the build process.
-$(MAKE) clean
 
dh_clean
 
install: build
dh_testdir
dh_testroot
dh_clean -k
dh_installdirs
 
# Add here commands to install the package into debian/vcs.
$(MAKE) DESTDIR=$(CURDIR)/debian/vcs install
 
 
# Build architecture-independent files here.
binary-indep: build install
# We have nothing to do by default.
 
# Build architecture-dependent files here.
binary-arch: build install
dh_testdir
dh_testroot
dh_installchangelogs CHANGELOG
dh_installdocs
dh_installexamples
# dh_install
# dh_installmenu
# dh_installdebconf
# dh_installlogrotate
# dh_installemacsen
# dh_installpam
# dh_installmime
# dh_python
# dh_installinit
# dh_installcron
# dh_installinfo
dh_installman
dh_link
dh_strip
dh_compress
dh_fixperms
# dh_perl
# dh_makeshlibs
dh_installdeb
dh_shlibdeps
dh_gencontrol
dh_md5sums
dh_builddeb
 
binary: binary-indep binary-arch
.PHONY: build clean binary-indep binary-arch binary install configure
Property changes:
Added: svn:executable
+*
\ No newline at end of property
/video-contact-sheet/tags/1.0.11/debian-package/Makefile
0,0 → 1,14
# $Id$
 
prefix:=/usr
DESTDIR:=/
 
all:
clean:
 
install:
mkdir -p $(DESTDIR)$(prefix)/bin
install -m755 -o0 -g0 vcs $(DESTDIR)$(prefix)/bin
 
 
.PHONY: all install clean
Property changes:
Added: svn:keywords
+Rev Id Date
\ No newline at end of property
/video-contact-sheet/tags/1.0.11/Makefile
0,0 → 1,41
#!/usr/bin/make -f
# $Id$
 
VER=$(shell grep VERSION vcs|head -n1|sed -r 's/.*"(.*)".*/\1/g')
 
all:
@echo "Use $(MAKE) dist"
 
check-no-svn:
if [ -d .svn ]; then echo "Don't release from SVN working copy" ; false ; fi
 
prep:
cp vcs CHANGELOG debian-package/
chmod -x vcs
 
dist: check-no-svn prep gz bz2 plaintext changelog deb cleanup
 
gz:
cp vcs vcs-$(VER)
gzip -9 vcs-$(VER)
 
bz2:
cp vcs vcs-$(VER)
bzip2 -9 vcs-$(VER)
 
plaintext:
mv vcs vcs-$(VER)
 
changelog:
gzip -9 CHANGELOG
gzip -dc CHANGELOG.gz > CHANGELOG
 
cleanup:
$(RM) -i Makefile *.changes
$(RM) -r debian-package
 
deb:
cd debian-package/ && dpkg-buildpackage -rfakeroot -us -uc -b
 
 
.PHONY: dist
Property changes:
Added: svn:executable
+*
\ No newline at end of property
Added: svn:keywords
+Rev Id Date
\ No newline at end of property
/video-contact-sheet/tags/1.0.11/CHANGELOG
0,0 → 1,177
1.0.11: (2008-04-08)
* BUGFIX: (brown bag bug) Corrected typo in variable name that made vcs
fail when setting the default timecode derivation to number of
captures instead of interval (i.e. when including timecode_from=8 in
the config file) (thanks to Chris Hills for the bug report)
* WORKAROUND: Fix for all-equal captures (seems to be a known problem
with mplayer [M1]) (contributed by Phil Grundig)
* RETROCOMPATIBILITY: Older bash syntax for appending and initialising
arrays (contributed by Phil Grundig)
* COMPATIBILITY: Support alternative du syntax for compatibility with
busybox (based on Phil Grundig's contribution)
* COSMETIC: Don't print milliseconds when using mplayer as capturer
(they're not really meaningful then) (suggested by Phil Grundig)
* COSMETIC: Align the extended set captures (-e) and the standard set
(bug pointed by Chris Hills). Seems to fail at some (smaller?)
sizes.
"Funky" modes aren't correctly aligned yet.
* DEBUGGING: Added optional function call trace (by setting variable DEBUG
to 1)
* Added FOURCC for VC-1
* COSMETIC: Fixed captures recount with multiple files (prompted by a
bugreport from Dougn Redhammer)
 
1.0.10: (2007-11-08)
* BUGFIX: Corrected aspect guessing bug: would fail if width was standard
but height not
* FEATURE: Allow explicitly disabling timestamps (-dt or --disable
timestamps)
* FEATURE: Allow explicitly disabling shadows (-ds or --disable shadows)
* Added HD resolution guessed aspect ratio (defaults to 16/9)
* OTHER: Changed e-mail address in the comments to gmail's, would probably
 
 
1.0.9a: (2007-06-10) (-Brown bag- Bugfix release)
* BUGFIX: Fixed regression introduced in 1.0.8a: unsetting numcols
broke extended mode captures (Thanks to 'Aleksandar Urošević').
* BUGFIX: Use the computed number of columns for extended mode
(instead of the global one)
 
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').
* BUGFIX: Right side of heading wasn't using the user's font colour
(Thanks to 'Dougn Redhammer').
 
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.11
Property changes:
Added: svn:mergeinfo
Merged /video-contact-sheet/branches/1.0a:r262-263
Merged /video-contact-sheet/tags/1.0.8a:r319-320
Merged /video-contact-sheet/branches/1.0.10:r328-331
Merged /video-contact-sheet/branches/1.0.11:r334-342
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/branches/1.0.9a:r322-325
Merged /video-contact-sheet/tags/1.0.2b:r274