Subversion Repositories pub

Compare Revisions

Problem with comparison.

Ignore whitespace Rev HEAD → Rev 400

/video-contact-sheet/branches/1.11.2/pkg/vcs
0,0 → 1,4087
#!/usr/bin/env bash
#
# $Rev$ $Date$
#
# vcs
# Video Contact Sheet *NIX: Generates contact sheets (previews) of videos
#
# Copyright (C) 2007, 2008, 2009, 2010 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>
#
# (Note: The references that used to be here have been moved to
#+ <http://p.outlyer.net/dox/vcs:devel:references>)
 
declare -r VERSION="1.11.2"
declare -r RELEASE=1
 
# {{{ # CHANGELOG
# Last release changes:
# (The full changelog can be found at <http://p.outlyer.net/vcs/files/CHANGELOG>).
#
# 1.11.2:
# * BUGFIXES:
# - Remove extra, empty, temporary dir
# - Use standard awk syntax for exponentiation (pyth_th)
# - Woraround for systems that don't register fonts with ImageMagick
# 1.11.1:
# * Added FLV1 codec
# * BUGFIXES:
# - Deprecate DEFAULT_INTERVAL, DEFAULT_NUMCAPS and DEFAULT_COLS as
# overrides, warn about their new names (interval, numcaps and cols)
# - Fix ImageMagick version detection
# 1.11:
# * FEATURES
# - Allow setting output filename. With extension will set output format,
# without will inherit it.
# - Allow percentages in height.
# - Require mplayer OR ffmpeg instead of both. Having both is still
# recommended for better results.
# - Safe mode, for files whose length doesn't get reported correctly.
# Completely automated.
# Number of tries can be increased with -Ws. Repeat to increase further.
# Use -WS to do try as many times as possible.
# Accuracy (stepping) can be increased with -Wp. Repeat to increase
# accuracy. Decrease with -WP.
# Can be deliberately disabled with -Wb to force processing of broken
# files. *VCS WITH -Wb WILL FAIL ON BROKEN FILES*
# - Added -dp (--disable padding) equivalent to overriding HPAD to 0
# * BUGFIXES:
# - Don't pass ms to mplayer. It ignores them anyway and in some rare
# cases breaks the last capture (possibly due to the 5-frames hack)
# - Honor detected aspect ratio if found
# - Try to detect files that might fail on the last capture and trigger
# safe mode
# - Timestamps font was being ignored. As a side effect this produced
# italiced timestamps in some systems
# - Fixed obscure bug with safe_rename_pattern overrides
# * COMPAT: Support for bash 2.05b. This will (probably) be the last version
# capable of running under bash 2.
# * DVD mode revamp
# - Print title file size instead of disc size when possible
# - Aspect ratio detection, if available
# - Use of FFmpeg if available to get better information
# - Mostly x-platform, only ISOs identification is a bit better in Linux
# * Added FourCCs: 3IV1, 3IV2 (3ivx); s263 (H.263); mp4v, MP4V, H264
# (MPEG-4 and AVC in mov/mp4), VP6F (VP6 Flash Version), AMR
# Video codec renamings:
# - TechSmith codec name shortened to TechSmith SCC
# - Raw RGB renamed to Raw video
# * Help cleanup. The default help output is much shorter, the full text
# can be displayed with --fullhelp. Also print the decoder choice near
# the appropriate option (-M/-F)
# * Added --anonymous to help (never was in it)
# * Drop requirement on seq/jot and bc, replaced by inline awk
# ... New requirement: Perl (only for DVDs).
# * Adopt new/fixed numbering scheme
# <http://p.outlyer.net/dox/vcs:devel:renumbering>
# * Check ImageMagick version (must decide which is the real minimum
# required)
# * Non-latin fonts revamp:
# - -I no longer works alone (use -Ij or -Ik instead)
# - -Ik, -Ij and --nonlatin try to pick an appropriate font automatically
# - -I accepts a font name or font filename like
# -Ij=Kochi-Mincho-Regular or
# -Ij=/usr/share/fonts/truetype/kochi/kochi-mincho.ttf
# * Deprecated options:
# --shoehorn: Will be removed unless someone really needs it.
# --mincho: Replaced by --nonlatin
# * COSMETIC:
# - Default font switched to DejaVu Sans.
# Font sizes reduced to accomodate the new default.
# Should fall back to a sane default if it's not available
# - Much tighter padding
# - Smaller timestamps font by default
# - Print friendlier timestamp when a capture fails
# - Print program signature to console without colour
# - Use main font by default in timestamps
# - Heading background colour toned down
# - Added colourised output when tput is not capable (i.e. FreeBSD)
# - Added prefixes when colour is not available for console output
# - Don't print lsdvd error channel is DVD mode
# - Suppress mv errors (e.g. over VFS being unable to preserve)
# * Minimum ImageMagick version set to 6.3.5-7
# * Better detection of requirements (e.g. disallow decoders without png
# support)
# * Allow overriding height, number of captures, interval, columns, and
# padding
# * UNDOCUMENTED/DEBUG:
# - Allow stopping the main loop before cleaning up (--undocumented hang)
# - Identification-only mode. Might be promoted to an actual feature
# (--undocumented idonly)
# - Allow setting ffmpeg and mplayer path (--undocumented set_ffmpeg and
# set_mplayer)
# - Allow disabling either mplayer of ffmpeg (as if they weren't
# installed (--undocumented disable_ffmpeg and disable_mplayer)
# - Added -Wc to disable console colour, repeat to disable prefixes
# * INTERNAL:
# - assert()
# - Cleanup: correctness checks converted to asserts, removal of old dead
# code
# - Typos
#
# }}} # CHANGELOG
 
set -e
 
# Fail soon if this version of bash is too old for the syntax, don't expose bash to the newer
# syntax
# See the "Bash syntax notes" section for details
[ "$BASH_VERSINFO" ] && {
# Absolute minimum right now is 2.05b
if [ "${BASH_VERSINFO[0]}" -le 2 ]; then # Only bash <=2 needs extra testing
# I guess we can't expect any new bash2 release
if [ "${BASH_VERSINFO[0]}" -lt 2 ] ||
( [ "${BASH_VERSINFO[0]}" -eq 2 ] && [ "${BASH_VERSINFO[1]}" != '05b' ] ); then
echo "Bash 2.05b or higher is required" >&2
exit 1
fi
fi
}
 
# {{{ # TO-DO
# TODO / FIXME:
# * (1.12) Start replacing 'grep' with bash's '[[ =~ ]]'. Will break bash 2 compatibility for good
# * [[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. =>
#+ SUS v2: egrep is deprecated, grep -E replaces it
# * Change default DVD_TITLE to 0
# * Deprecations:
# OPTION/VAR -> ALTERNATIVE DEPRECATED FROM VERSION REMOVAL ETA
# --shoehorn -> --undocumented shoehorn 1.11 1.12
# --undocumented shoehorn -> NONE 1.12 1.13 or 1.14
# --funky -> --profile ? ?+1
# MIN_LENGTH_FOR_END_OFFSET -> none 1.11 1.12 or 1.13
# * No warning shown in 1.11. 1.12 will switch back default end offset either to 0 or a
# percentage *and* use it always (disable with -E0)
# end offset -> none ? ?+1
# --mincho -> --nonlatin 1.11 1.12
# * Better check for coherence of overrides
# * Variables cleanup:
# Variables will use a more uniform scheme, with prefixes where appropriate:
# - INTERNAL_*: Used internally to adapt messages and the like to the input
# - UNDFLAG_*: Undocumented flags. Used internally to keep track of undocumented modes (-Z)
# - USR_*: Holds values of variables as set by the user, either from overrides or from the
# command-line
# }}} # TO-DO
 
# {{{ # 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 config, most precedence
#
# The variables that can be overriden are below the block of constants ahead.
declare -r CFGFILE=~/.vcs.conf
 
# Default values, use interval, numcaps and cols to override
declare -ri DEFAULT_INTERVAL=300
declare -ri DEFAULT_NUMCAPS=16
declare -ri DEFAULT_COLS=2
 
# 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 ASPECT=8 VCNAME=9 ACNAME=10
# 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
# GETOPT must be correctly set or the script will fail.
# It can be set in the configuration files if it isn't in the path or
# the first getopt in the path isn't the right version.
# A check will be made and a warning with details shown if required.
declare GETOPT=getopt
# Set to 1 to print function calls
declare -i DEBUG=0
# 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='#afcd7a' # Background for meta info (size, codec...)
declare bg_sign=SlateGray #'#a2a9af' # Background for signature
declare bg_title=White # Background for the title (see -T)
declare bg_contact=White # Background for the captures
declare bg_tstamps='#000000aa' # Background for the timestamps box
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 for the title
# Fonts, use identify -list font to get the list, up to IM 6.3.5-7 was '-list type' [[IM1]]
# If a font is not available IM will pick a sane default. In theory it will be silent
# although in practica it prints an error
declare font_tstamps=DejaVu-Sans-Book # Used for timestamps over the thumbnails
declare font_heading=DejaVu-Sans-Book # Used for the meta info heading
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 for the title (see -T)
# Font sizes, in points
declare -i pts_tstamps=14 # Used for the timestamps
declare -i pts_meta=14 # Used for the meta info heading
declare -i pts_sign=10 # Used for the signature
declare -i pts_title=33 # Used for the title (see -T)
# See --shoehorn
declare shoehorned=
# See -E / $end_offset
declare -i DEFAULT_END_OFFSET=60
# If the video is less than this length, end offset won't be used at all
declare MIN_LENGTH_FOR_END_OFFSET=19m30s
# 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
# Verbosity level so far from the command line can only be muted (see -q)
# it can be overridden, though
declare -i verbosity=$V_ALL
# Set to 1 to disable colours in console output
declare -i plain_messages=0
# See coherence_check for more details
declare -i DISABLE_SHADOWS=0
declare -i DISABLE_TIMESTAMPS=0
 
# Introduced in 1.0.7b, revamped in 1.11:
# This font is used to display international names (i.e. CJK names) correctly
# Help from users actually needing this would be appreciated :)
# This variable is filled either automatically through the set_extended_font()
#+function (and option -Ij) or manually (with option -Ij=MyFontName)
# The automatic picks a semi-random one from the fonts believed to support CJK/Cyrillic
#+characters.
declare FONT_MINCHO= # Filename or font name as known to ImageMagick (identify -list font)
# Output of capturing programs is redirected here
declare stdout=/dev/null stderr=/dev/null
declare -i DVD_MODE=0 DVD_TITLE=1
declare DVD_FILE=
 
# Override-able since 1.11:
# Height of the thumbnails, by default use same as input
declare th_height= # *WILL CHANGE NAME*
declare interval=$DEFAULT_INTERVAL # Interval of captures (=numsecs/numcaps)
declare -i numcaps=$DEFAULT_NUMCAPS # Number of captures (=numsecs/interval)
# This is the horizontal padding added to each capture.
# Beware when changing this since extended set's alignment might break.
# When shadows are enabled this is ignored since they already add padding.
declare -i HPAD=2 # *WILL CHANGE NAME*
declare -i cols=$DEFAULT_COLS # Number of output columns
 
# }}} # End of override-able variables
 
# {{{ # Variables
 
# Options and other internal usage variables, no need to mess with this!
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 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
# Identification output from ffmpeg and mplayer for the current video
declare FFMPEG_CACHE=
declare MPLAYER_CACHE=
# This holds the parsed identification values, see also the Indexes in VID
# (defined in the constants block)
declare -a VID=( )
 
# These variables will hold the output of tput, used
# to colourise feedback
declare prefix_err= prefix_inf= prefix_warn= suffix_fback=
 
# 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
 
# Transformations/filters
# Operations are decomposed into independent optional steps, this allows
# to add some intermediate steps (e.g. polaroid/photo mode's frames)
# 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 (currently deprecated)
# * 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, [context, [index]] )
# They're executed in order by filter_vidcap()
declare -a FILTERS_IND=( 'filt_resize' 'filt_apply_stamp' 'filt_softshadow' )
# Deprecated: 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
 
# Sets which function is used to obtain random numbers valid values are
# bashrand and filerand.
# Setting it manually will break it, calling with -R changes this to filerand.
# See rand() for an explanation
declare RANDFUNCTION=bashrand
 
# Which file are we working on (i.e. how many times has process() been called)
declare -i FILEIDX=0
 
# Names for output files, each index is a file name, an empty index will use
# the input file and append an extension to it
declare -a OUTPUT_FILES=( )
 
# Mplayer and FFmpeg binaries. Will be detected.
# Don't set manually, if you need to override set the path temporarily, e.g.:
# $ env PATH=/whatever:$PATH vcs ...
# or use the undocumented (and unchecked!) appropriate option:
# $ vcs --undocumented set_ffmpeg=/mypath/ffmpeg
declare MPLAYER=
declare FFMPEG=
 
# When set to 1 the reported length by mplayer and ffmpeg won't be trusted
# and will trigger some custom tests.
# Enabled automatically on problematic files
declare -i QUIRKS=0
# If the reported lengths differ by at least this much QUIRKS will be enabled
declare QUIRKS_LEN_THRESHOLD=0.2
# When trying to determine the correct length, file will be probed each...:
declare QUIRKS_LEN_STEP=0.5 # ~ 10 frames @ 20fps
# Maximum number of seconds to "rewind" from reported length (after this
# vcs surrenders but processing continues with a rewinded length)
declare QUIRKS_MAX_REWIND=20
 
# Set when the console output will be in color. It doesn't control color!
declare HAS_COLORS=
 
declare -i multiple_input_files=0
 
# Internal counts, used only to adjust messages
declare -i INTERNAL_WS_C=0 # -Ws count
declare -i INTERNAL_WP_C=0 # -Wp count
declare -i INTERNAL_MAXREWIND_REACHED=0 # More -Ws in the command-line won't help
 
# }}} # 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'
'MIN_LENGTH_FOR_END_OFFSET'
'DEBUG'
'DISABLE_.*'
'th_height'
'interval'
'numcaps'
'HPAD'
'cols'
# Note GETOPT doesn't make sense to be overridden from the command-line
'GETOPT'
)
 
# 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 -a 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
 
end_offset=$DEFAULT_END_OFFSET
}
 
# 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=${ALLOWED_OVERRIDES[*]}
compregex=${compregex// /|} # = s ' ' => '|'
 
# Don't allow ';', FIXME: dunno how secure that really is...
# FIXME: ...it doesn't really work 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=$(egrep -o '^[[:space:]]*[a-zA-Z0-9_]*=.' <<<"$o" | cut -d'=' -f1 | egrep -o '[^ ]*')
local varval=$(egrep -o '^[^=]*=.*' <<<"$o" | cut -d'=' -f2-)
if [ "$varname" = "DEFAULT_INTERVAL" ]; then
warn '$DEFAULT_INTERVAL is deprecated, please use $interval instead'
varname=interval
elif [ "$varname" = "DEFAULT_NUMCAPS" ]; then
warn '$DEFAULT_NUMCAPS is deprecated, please use $numcaps instead'
varname=numcaps
elif [ "$varname" = "DEFAULT_COLS" ]; then
warn '$DEFAULT_COLS is deprecated, please use $cols instead'
varname=cols
fi
# FIXME: Security!
local curvarval=
eval curvarval='$'"$varname"
if [ "$curvarval" == "$varval" ]; then
warn "Ignored override '$varname' (already had same value)"
else
eval "$varname=\"$varval\""
eval "USR_$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 is a valid percentage (xx% or xx.yy%)
# is_percentage($1 = input)
is_percentage() {
egrep -q '^([0-9]+\.?([0-9])?+|(\.[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() {
awkex "int(${*//[ ,]/ * }+0.5)" # ' ' = ',' => '*'
}
 
# Like rmultiply() but always rounded upwards
ceilmultiply() {
# TODO: breaks with $@. Why?
awkex "int(${*//[ ,]/ * }+0.99999)" # ' ' = ',' => '*'
}
 
# Basic mathematic stuff
# min($1 = operand1, $2 = operand2)
min() { awk "BEGIN { if (($1) < ($2)) print ($1) ; else print ($2) }" ; }
max() { awk "BEGIN { if (($1) > ($2)) print ($1) ; else print ($2) }" ; }
abs() { awk "BEGIN { if (($1) < (0)) print (($1) * -1) ; else print ($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='!=' ;;
*) assert $LINENO "[ \"'$1' '$2' '$3'\" ] && false" && return $EX_SOFTWARE
esac
# Empty operands
if [ -z "$1" ] || [ -z "$3" ]; then
assert $LINENO "[ \"'$1' '$2' '$3'\" ] && false"
else
awk "BEGIN { if ($1 $op $3) exit 0 ; else exit 1 }"
fi
}
 
# Keep a number of decimals *rounded*
keepdecimals() {
local N="$1" D="$2"
awk "BEGIN { printf \"%.${D}f\", (($N)+0) }"
}
 
# Keep a number of decimals, last decimal rounded to lower
keepdecimals_lower() {
grep -q '\.' <<<"$1" || { echo "$1" ; return ; }
local D=${1/#*.} # Decimals only
echo ${1/%.*}.${D:0:$2} # Integer part + . + Number of decimals
}
 
# Evaluate in AWK. Intended for arithmetic operations.
#+Keep decimals. I.e. 5 = 5.000000...
# awkex($1 = expression)
awkexf() {
# By default awk prints in compact form (scientific notation and/or up to 6 digits/decimals),
# printf is used to avoid this, TODO: Is there any direct way?
# .%20f is clearly overkill but matches the all code (default bc -l)
awk "BEGIN { printf \"%.20f\", ($1)+0 }"
}
 
# Evaluate in AWK. Intended for arithmetic operations.
#+Use default output. I.e. 5 = 5
# awkex($1 = expression)
awkex() {
awk "BEGIN { print ($1)+0 }"
}
 
# converts spaces to newlines in a x-platform way [[FNL]]
# stonl($1 = string)
stonl() {
awk '{gsub(" ", "\n");print}' <<<"$1" | egrep -v '^$'
}
 
# Converts newlines to spaces portably
# nltos($1 = string)
nltos() {
awk '{printf "%s ",$0}' <<<"$1" | sed 's/ *//'
}
 
# bash version of ord() [[ORD]]
# prints the ASCII value of a character
ord() {
printf '%d' "'$1"
}
 
# Get file extension
filext() {
grep -o '\.[^.]*$' <<<"$1" | cut -d. -f2
}
 
# Wrapper around $RANDOM, not called directly, wrapped again in rand().
# See rand() for an explanation.
bashrand() {
echo $RANDOM
}
 
# Prepares for "filerand()" calls
# File descriptor 7 is used to keep a file open, from which data is read
# and then transformed into a number.
# init_filerand($1 = filename)
init_filerand() { # [[FD1]], [[FD2]]
test -r "$1"
exec 7<"$1"
# closed in exithdlr
}
 
# Produce a (not-really-)random number from a file, not called directly wrapped
# in rand()
# Note that once the file end is reached, the random values will always
# be the same (hash_string result for an empty string)
filerand() {
local b=
# "read 5 bytes from file descriptor 7 and put them in $b"
read -n5 -u7 b
hash_string "$b"
}
 
# Produce a random number
# $RANDFUNCTION defines wich one to use (bashrand or filerand).
# Since functions using random values are most often run in subshells
# setting $RANDOM to a given seed has not the desired effect.
# filerand() is used to that effect; it keeps a file open from which bytes
# are read and not-so-random values generated; since file descriptors are
# inherited, subshells will "advance" the random sequence.
# Argument -R enables the filerand() function
rand() {
$RANDFUNCTION
}
 
# produces a numeric value from a string
hash_string() {
local HASH_LIMIT=65536
local v="$1"
local -i hv=15031
local c=
if [ "$v" ]; then
for i in $(seqr 0 ${#v} ); do
c=$( ord ${v:$i:1} )
hv=$(( ( ( $hv << 1 ) + $c ) % $HASH_LIMIT ))
done
fi
echo $hv
}
 
# Applies the Pythagorean Theorem
# pyth_th($1 = cathetus1, $2 = cathetus2)
pyth_th() {
awkex "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() {
trace $FUNCNAME $@
# eval it even if it's numeric to strip leading zeroes. Note the quoting
if is_number "$1" ; then awkex "\"$1\"" ; return 0 ; fi
 
local s=$(tolower "$1") t r n
 
# Only allowed characters
if ! egrep -qi '^[0-9smh.]+$' <<<"$s"; then
return $EX_USAGE;
fi
 
# Two consecutive dots are no longer accepted
if egrep -q '\.\.' <<<"$s" ; then
return $EX_USAGE
fi
 
# Newer parsing code: replaces units by a product
# and feeds the resulting string to awk for evaluation
# Note leading zeroes will lead awk to believe they are octal numbers
# as a quick and dirty fix I'm just wrapping them in quotes, forcing awk
# to re-evaluate them, which appears to be enough to make them decimal.
# This is the only place where leading zeroes have no meaning.
 
# Split into lines of time + unit:
t=
for item in $(echo "$s" | grep -o '[0-9]*[hms]') ;do
n="\"$(echo $item | grep -o '[0-9]*')\"" # Number, quoted
t=$t$n$(echo $item | grep -o '[hms]') # + Number + Unit
done
# Split all ms or s.ms
for item in $(echo "$s" | grep -o '[0-9]*\.[0-9]*') ;do
t="${t}\"$item\" + "
done
# Seconds without unit. They must be preceded by h, m or s at this point
local secs=$(echo $s | egrep -o '.?[0-9]*$')
# When preceded by '.', they're ms
[ "$secs" ] && grep -q '\.'<<<"$secs" && secs=
# Quote and addition. Note BSD grep/egrep wants the anchor ($) or won't match
[ "$secs" ] && secs=" \"$(egrep -o '[0-9]*$'<<<"$secs")\" + "
t=${t//h/ * 3600 + }
t=${t//m/ * 60 + }
t=${t//s/ + }
t="$t$secs"
t=${t/% + /} # Strip empty addition
r=$(awkex "$t" 2>/dev/null)
 
# Negative and empty intervals
assert $LINENO "[ '$r' ] && [ '$t' ]"
assert $LINENO "fptest $r -gt 0"
 
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 -format '%w' "$1"
}
 
# Get Image Height
# imh($1 = file)
imh() {
identify -format '%h' "$1"
}
 
# Prints a number of seconds in a more human readable form
# e.g.: 3600 becomes 1:00:00
# pretty_stamp($1 = seconds)
pretty_stamp() {
assert $LINENO "is_float '$1'"
# Fully implemented in AWK to discard bc.
# As a bonus now it's much faster and compact
awk "BEGIN {
t=$1 ; NOTMS=($DEC_MPLAYER==$decoder);
MS=(t - int(t));
h=int(t / 3600);
t=(t % 3600);
m=int(t / 60);
t=(t % 60);
s=t
if (h != 0) h=h\":\" ; else h=\"\"
if (NOTMS!=1) ms=sprintf(\".%02d\", int(MS*100+0.5));
printf \"%s%02d:%02d%s\", h, m, s, ms
}"
# Note the rounding applied to $MS, it is required to match the precission passed on
# to ffmpeg
}
 
# Prints a given size in human friendly form
get_pretty_size() {
local bytes=$1
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
}
 
# 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_file_size($1 = file)
get_pretty_file_size() {
local f="$1"
local bytes=$(get_file_size "$f")
 
get_pretty_size "$bytes"
}
 
# Clean $safe_rename_pattern, which is override-able
# Since safe_rename() is called from $() it won't be able to affect global variables directly
# Hopefully sson this won't be needed
sanitise_rename_pattern() {
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
# Hashmarks will break the regex used in safe_rename()
if grep -q '#' <<<"$safe_rename_pattern" ; then
warn "Illegal character \"#\" found in safe renaming pattern, resetting it"
safe_rename_pattern=$DEFAULT_SAFE_REN_PATT
fi
}
 
# mv quiet
# Move a file, be quiet about errors.
# Ownership preservation is a common error on vfs, for example
mvq() {
mv -- "$@" 2>/dev/null
}
 
# 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() {
trace $FUNCNAME $@
local from="$1"
local to="$2"
 
# Output extension
local ext=$(filext "$to")
# Output filename without extension
local b=${to%.$ext}
 
local n=1
while [ -f "$to" ]; do # Only executes if $2 exists
# Bash 2 and Bash 3 behave differently with substring replacement (${//}) and '%'
# Sed is a safer bet
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
 
mvq "$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...
# Neither busybox's nor BSD's du allow --bytes.
# Note that using "ls -H" is not an option for portability reasons either.
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"
}
 
# Du replacement. This differs from get_file_size in that it takes multiple arguments
dur() {
for file in $@ ; do
get_file_size "$file"
done
}
 
# Gets the size of the dvd device, in DVD mode
get_dvd_size() {
# FIXME: Case sensivity might break with iso9660
if [ -f "$DVD_MOUNTP/VIDEO_TS/VTS_${DVD_VTS}_1.VOB" ]; then
# Some VOBs available
local vfiles="$DVD_MOUNTP/VIDEO_TS/VTS_${DVD_VTS}_*.VOB"
# Print all sizes, each on a line, add '+' to the end of each line, add 0 to the end.
local feed="$(dur "$DVD_MOUNTP/VIDEO_TS/VTS_${DVD_VTS}_"*".VOB" | cut -f1 | sed 's/$/ + /') 0"
get_pretty_size $(awkex "$(nltos "$feed")")
else
echo "?"
fi
}
 
is_linux() {
uname -s | grep -iq '^Linux$'
}
 
# Get the mountpoint of a mounted image.
# This only works on Linux. *BSD normal users aren't able to use mdconfig -l
# Is there any better way?
get_dvd_image_mountpoint() {
if is_linux ; then
local lodev=$(/sbin/losetup -j "$DVD_FILE" | cut -d':' -f1 | head -1)
mount | grep "^$lodev " | cut -d' ' -f3
fi
}
 
# Tests the presence of all required programs
# test_programs()
test_programs() {
local retval=0 last=0
local nopng=0
 
MPLAYER=$(type -pf mplayer) || true
FFMPEG=$(type -pf ffmpeg) || true
 
# Test we can actually use FFmpeg
[ "$FFMPEG" ] && {
# Newer FF has -codecs, -formats, -protocols, older has only -formats
#+png is a codec so it's on different lists on newer and older
if ! "$FFMPEG" -formats 2>/dev/null | grep -q 'EV.* png' && \
! "$FFMPEG" -codecs 2>/dev/null | grep -q 'EV.* png' ; then
warn "FFmpeg can't output to png, won't be able to use it."
FFMPEG=''
nopng=1
fi
}
# Same for Mplayer
[ "$MPLAYER" ] && {
if ! "$MPLAYER" -vo help 2>&1 | grep -q 'png' ; then
warn "MPlayer can't output to png, won't be able to use it."
MPLAYER=''
nopng=1
fi
}
 
[ "$MPLAYER" ] || [ "$FFMPEG" ] || {
local pngwarn=
[ $nopng -eq 1 ] && pngwarn=', with PNG output support,'
error "mplayer and/or ffmpeg$pngwarn are required!"
let 'retval++,1'
}
 
 
if [ $decoder -eq $DEC_FFMPEG ] && [ -z "$FFMPEG" ]; then
decoder=$DEC_MPLAYER
elif [ $decoder -eq $DEC_MPLAYER ] && [ -z "$MPLAYER" ]; then
decoder=$DEC_FFMPEG
fi
 
# awk is required by SUS/POSIX but just to be sure...
for prog in convert montage identify mktemp grep egrep cut sed awk ; do
if ! type -pf "$prog" ; then
error "Required program $prog not found!"
let 'retval++,1'
fi >/dev/null
done
# TODO: [[x2]]
 
# Early exit
[ $retval -eq 0 ] || return $EX_UNAVAILABLE
 
# ImageMagick version. 6 is a must, I'm probably using some
# features that require a higher minor version
# Versions tested:
# * Fedora 9: IM 6.4.0
local ver
ver=$(identify -version | head -n1 | grep -o 'ImageMagick[[:space:]]*[^ ]*' |\
cut -f 2 -d' ')
if [ "$ver" ]; then
local verx=${ver//-/.}.0 # Extra .0 in case rev doesn't exist
local major=$(cut -d'.' -f1 <<<"$verx")
local minor=$(cut -d'.' -f2 <<<"$verx")
local micro=$(cut -d'.' -f3 <<<"$verx")
local rev=$(cut -d'.' -f4 <<<"$verx")
local serial=$(( $major * 100000 + $minor * 10000 + $micro * 100 + $rev))
if [ "$serial" -lt 630507 ]; then
error "ImageMagick 6.3.5-7 or higher is required. Found $ver." ;
let 'retval++,1'
fi
else
error "Failed to check ImageMagick version."
let 'retval++,1'
fi
 
[ $retval -eq 0 ] || return $EX_UNAVAILABLE
}
 
# Test wether $GETOP is a compatible version; try to choose an alternate if
# possible
choose_getopt() {
if ! type -pf $GETOPT ; then
# getopt not in path
error "Required program getopt not found!"
return $EX_UNAVAILABLE
fi >/dev/null
local goe= gor=0
# Try getopt. If there's more than one in the path, try all of them
for goe in $(type -paf $GETOPT) ; do
"$goe" -T || gor=$?
if [ $gor -eq 4 ]; then
# Correct getopt found
GETOPT="$goe"
break;
fi
done >/dev/null
if [ $gor -ne 4 ]; then
error "No compatible version of getopt in path, can't continue."
error " For details on how to correct this problems, see <http://p.outlyer.net/vcs#getopt>"
return $EX_UNAVAILABLE
fi
return 0
}
 
# 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 VCSTEMPDIR
unset TEMPSTUFF ; declare -a TEMPSTUFF
}
 
# Exit callback. This function is executed on exit (correct, failed or
# interrupted)
# exithdlr()
exithdlr() {
# I don't think that's really required anyway
if [ "$RANDFUNCTION" == "filerand" ]; then
7<&- # Close FD 7
fi
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
[ $plain_messages -eq 0 ] && echo -n "$prefix_err"
# 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$suffix_fback"
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
[ $plain_messages -eq 0 ] && echo -n "$prefix_warn"
echo "$1$suffix_fback"
fi >&2
}
#
# Print an informational message
# inf($1 = text)
inf() {
if [ $verbosity -ge $V_INFO ]; then
[ $plain_messages -eq 0 ] && echo -n "$prefix_inf"
echo "$1$suffix_fback"
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
}
 
#
# Tests if the filter chain contains the provided filter
# has_filter($1 = filtername)
has_filter() {
local filter= ref=$1
for filter in ${FILTERS_IND[@]} ; do
[ "$filter" == "$ref" ] || continue
return 0
done
return 1
}
 
#
# Enables prefixes in console output (instead of colour)
set_feedback_prefixes() {
prefix_err='[E] '
prefix_inf='[i] '
prefix_warn='[w] '
suffix_fback=
}
 
#
# Initialises the variables affecting colourised feedback
init_feedback() {
HAS_COLORS=
 
# tput might be preferable (Linux: man console_codes), but it doesn't
# work on FreeBSD to set colors
 
# Is tput available?
if type -pf tput >/dev/null ; then
# Is it able to set colours?
if tput bold && tput setaf 0 >/dev/null && tput sgr0 ; then
prefix_err=$(tput bold; tput setaf 1)
prefix_warn=$(tput bold; tput setaf 3)
prefix_inf=$(tput bold; tput setaf 2)
suffix_fback=$(tput sgr0)
HAS_COLORS="yes"
fi
fi
 
if [ -z "$HAS_COLORS" ]; then
# tput was not an option, let's try ANSI escape codes instead [[AEC]]
# TODO: Detect support
# Alternatively: $ perl -e 'print "\e[31m\e[1m"'
# echo -e is not portable but echo $'' is bash-specific so it should be fine...
# except when ANSI escape codes aren't supported of course
prefix_err=$(echo $'\033[1m\033[31m')
prefix_warn=$(echo $'\033[1m\033[33m')
prefix_inf=$(echo $'\033[1m\033[32m')
suffix_fback=$(echo $'\033[0m')
HAS_COLORS="yes"
fi
 
# Finally, if there's no colour support, use prefixes instead
if [ -z "$HAS_COLORS" ]; then
set_feedback_prefixes
fi
}
 
#
# seq replacement
# seq is not always present, jot is an alternative on FreeBSD. Instead, this is
# a direct replacement
# Note pure bash is *slower* than the awk (or perl) version
# seqr($1 = from, $2 = to, $3 = increment)
seqr() {
local from=$1 to=$2 inc=$3
[ "$inc" ] || inc=1
awk "BEGIN { for (i=$from;i<=$to;i+=$inc) print i }"
}
 
#
# assertion operator
# assert($1 = line, $* = code)
# TODO: Limit usage to values that will expand correctly always (i.e. not with quotes)
assert() {
[ $RELEASE -eq 1 ] && return
LINE=$1
shift
eval "$@" || {
error "Internal error at line $LINE: $@"
exit $EX_SOFTWARE
}
}
 
# }}} # Convenience functions
 
# {{{ # Core functionality
 
# Creates a new temporary directory
# create_temp_dir()
create_temp_dir() {
trace $FUNCNAME $@
 
[ "$VCSTEMPDIR" ] && return 0
 
# 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.
# Passing a full path template is more x-platform than using
# -t / -p
if [ -d /dev/shm ] && [ -w /dev/shm ]; then
VCSTEMPDIR=$(mktemp -d /dev/shm/vcs.XXXXXX)
else
[ "$TMPDIR" ] || TMPDIR="/tmp"
VCSTEMPDIR=$(env TMPDIR="$TMPDIR" mktemp -d "$TMPDIR/vcs.XXXXXX")
fi
if [ ! -d "$VCSTEMPDIR" ]; then
error "Error creating temporary directory"
return $EX_CANTCREAT
fi
TEMPSTUFF=( "${TEMPSTUFF[@]}" "$VCSTEMPDIR" )
}
 
# Resolve path. Realpath is not always available and readlink [[LC]] behaves differently in
# GNU and BSD. FIXME: Has AWK or bash something similar? This is the only place requiring perl!
realpathr() {
perl -e "use Cwd qw(realpath);print realpath('$1')"
}
 
# Create a new temporal file and print its filename
# new_temp_file($1 = suffix)
new_temp_file() {
trace $FUNCNAME $@
local r=$(env TMPDIR="$VCSTEMPDIR" mktemp "$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=( "${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 + ( $(rand) % $ncolours ) ))
convert -list color | sed -n "${lineno}{p;q;}" | cut -d' ' -f1 # [[R1#11]]
}
else # Pseudo-random mode, WIP!
randccomp() {
# colours are in the 0..65535 range, while RANDOM in 0..32767
echo $(( $(rand) + $(rand) + ($(rand) % 1) ))
}
randcolour() {
echo "rgb($(randccomp),$(randccomp),$(randccomp))"
}
fi
 
# Older IM output was pretty different. Since this is a mode used for testing
# I don't believe it's worth the effort to get it always right
# This used to be -list type. Was this an older IM version or a bug in vcs?
local nfonts=$(convert -list font | grep '^\s*Font:' | wc -l)
randfont() {
lineno=$(( $(rand) % $nfonts ))
convert -list font | grep -o 'Font:.*' | sed -n "${lineno}{p;q;}" | cut -d' ' -f2
}
 
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=$(awkex "$end - $st")
 
if fptest "$runlen" -lt $(get_interval "$MIN_LENGTH_FOR_END_OFFSET") ; then
# Min length to use end offset not met, it won't be used
inf "End offset won't be used, video too short."
eo=0
elif 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 for the video, 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=$(awkexf "(($end-$st)/2 + 1)")
else
inc=$(awkexf "(($end-$eo-$st)/$tcnumcaps)")
fi
else
error "Internal error"
return $EX_SOFTWARE
fi
# Keep three decimals, round to lower to avoid exceeding the video length
inc=$(keepdecimals_lower $inc 3)
 
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
local bound=$(awkexf "$end - $eo")
while fptest $stamp -le "$bound"; do
assert $LINENO fptest $stamp -ge 0
LTC=( "${LTC[@]}" "$stamp" )
stamp=$(keepdecimals_lower $(awkexf "$stamp + $inc") 3)
done
unset LTC[0] # Discard initial cap (=$st)
TIMECODES=( ${TIMECODES[@]} ${LTC[@]} ) # Don't quote or extra empty stamp!
}
 
# 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 $@
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
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 with ffmpeg
# capture_ffmpeg($1 = inputfile, $2 = outputfile, $3 = timestamp[, $4 = extra opts])
capture_ffmpeg() {
local f=$1
local o=$2
local ts=$3
# 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/ / $ts} -i "$f" ${wa_ss_af/ / $ts} -an \
-dframes 1 -vframes 1 -vcodec png \
-f rawvideo $4 $shoehorned "$o" >"$stdout" 2>"$stderr"
[ -f "$o" ] && [ "0" != "$(du "$o" | cut -f1)" ]
}
 
# Capture a frame with mplayer
# capture_mplayer($1 = inputfile, $2 = UNUSED, $3 = timestamp[, $4 = extra opts])
capture_mplayer() {
# Note mplayer CAN'T set the output filename
local f="$1"
local o=00000005.png
local ts=$3
 
# No point in passing ms to mplayer
ts=$(cut -d'.' -f1 <<<"$ts")
# Capture 5 frames and drop the first 4, fixes a weird bug/feature of mplayer ([M1])
 
{
if [ $DVD_MODE -eq 1 ]; then
"$MPLAYER" -sws 9 -ao null -benchmark -vo "png:z=0" -quiet \
-frames 5 -ss "$ts" $shoehorned -dvd-device "$DVD_FILE" \
$4 "dvd://$DVD_TITLE"
else
"$MPLAYER" -sws 9 -ao null -benchmark -vo "png:z=0" -quiet \
-frames 5 -ss "$ts" $4 $shoehorned "$f"
fi
 
} >"$stdout" 2>"$stderr"
rm -f 0000000{1,2,3,4}.png # Remove the first four
[ -f "$o" ] && [ "0" != "$(du "$o" | cut -f1)" ]
}
 
# 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_mplayer "$f" 'IGNOREME' "$stamp"
elif [ $decoder -eq $DEC_FFMPEG ]; then
# FIXME: ffmpeg can put the temporary file anywhere
capture_ffmpeg "$f" "$VIDCAPFILE" "$stamp"
else
error "Internal error!"
return $EX_SOFTWARE
fi || true
if [ ! -f "$VIDCAPFILE" ] || [ "0" == "$(du "$VIDCAPFILE" | cut -f1)" ]; then
[ $decoder -eq $DEC_MPLAYER ] && stamp=${stamp/%.*}
error "Failed to capture frame at $(pretty_stamp $stamp) (${stamp}s)"
return $EX_SOFTWARE
fi
 
return 0
}
 
# Applies all individual vidcap filters
# filter_vidcap($1 = filename, $2 = timestamp, $3 = width, $4 = height, $5 = context, $6 = index[1..])
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" "$5" "$6" ) -flatten "
done
local t=$(new_temp_file .png)
eval "convert '$1' $cmdopts '$t'"
# If $t doesn't exist returns non-zero
[ -f "$t" ] && mvq "$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, $5 = context, $6 = index)
filt_apply_stamp() {
trace $FUNCNAME $@
local filename=$1 timestamp=$2 width=$3 height=$4 context=$5 index=$6
 
local pts=$pts_tstamps
if [ $height -lt 200 ]; then
pts=$(( $pts_tstamps / 3 ))
elif [ $height -lt 400 ]; then
pts=$(( $pts_tstamps * 2 / 3 ))
fi
# If the size is too small they won't be readable at all
# With the original font 8 was the minimum, with DejaVu 7 is readable
if [ $pts -le 7 ]; then
pts=7
if [ $index -eq 1 ] && [ $context -ne $CTX_EXT ]; then
warn "Very small timestamps in use. Disabling them with -dt might be preferable"
fi
fi
# The last -gravity None is used to "forget" the previous gravity (otherwise it would
# affect stuff like the polaroid frames)
echo -n " \( -box '$bg_tstamps' -fill '$fg_tstamps' -stroke none -pointsize '$pts' "
echo -n " -gravity '$grav_timestamp' -font '$font_tstamps' -strokewidth 3 -annotate +5+5 "
echo " ' $(pretty_stamp $stamp) ' \) -flatten -gravity None "
}
 
# Apply a framed photo-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 ) ) ))
# The border is relative to the input size (since 1.0.99), with a maximum of 6
# Should probably be bigger for really big frames
# Note that only images below 21600px (e.g. 160x120) go below a 6px border
local border=$(( ($3*$4) / 3600 ))
[ $border -lt 7 ] || border=6
echo -n "-bordercolor white -border $border -bordercolor grey60 -border 1 "
}
 
filt_softshadow() {
# Before this was a filter, there was the global (montage) softshadow (50x2+10+10) and the
# photoframe inline softshadow 60x4+4+4
echo -n "\( -background black +clone -shadow 50x2+4+4 -background none \) +swap -flatten -trim +repage "
}
 
 
# Apply a polaroid-like border effect
# Based on filt_photoframe(), with a bigger lower border
# filt_polaroid($1 = filename, $2 = timestamp, $3 = width, $4 = height)
filt_polaroid() {
trace $FUNCNAME $@
# local file="$1" ts=$2 w=$3 h=$4
local border=$(( ($3*$4) / 3600 )) # Read filt_photoframe for details
[ $border -lt 7 ] || border=6
echo -n "-bordercolor white -mattecolor white -frame ${border}x${border} "
# FIXME: This is rather ugly (double-flipping) there's sure a better way
echo -n "\( -flip -splice 0x$(( $border*5 )) \) "
echo "-flip -bordercolor grey60 -border 1 +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=$(( ($(rand) % 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="$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= vpad= splice=
 
# The shadows already add a good amount of padding
if has_filter filt_softshadow ; then
hpad=0
vpad=0
splice=5x10
else
hpad=$HPAD
vpad=$HPAD
splice=0x8
fi
 
montage -background Transparent "$@" -geometry +$hpad+$vpad -tile "$cols"x "$output"
convert "$output" -background Transparent -splice $splice "$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 $(seqr 1 $numrows) ; do
col=0
rowfile=$(new_temp_file .png)
rowfiles=( "${rowfiles[@]}" "$rowfile" )
accoffset=0
cmdopts= # This command is pretty time-consuming, let's make it in a row
 
# Base canvas # Integrated in the row creation since 1.0.99
 
# Step through vidcaps (col=[0..cols-1])
for col in $(seqr 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
cmdopts="$cmdopts '$1' -geometry +${accoffset}+0 -composite "
 
offset=$(( $minoffset + ( $(rand) % $maxoffset ) ))
let 'accoffset=accoffset + w - offset'
shift
done
inf "Composing overlapped row $row/$numrows..."
eval convert -size ${canvasw}x${canvash} xc:transparent -geometry +0+0 "$cmdopts" -trim +repage "'$rowfile'" >&2
done
 
inf "Merging overlapped rows..."
output=$(new_temp_file .png)
 
cmdopts=
accoffset=0
local h
for row in "${rowfiles[@]}" ; do
w=$(imw "$row")
h=$(imh "$row")
minoffset=$(( $h / 8 ))
maxoffset=$(( $h / 4 ))
offset=$(( $minoffset + ( $(rand) % $maxoffset ) ))
# The row is also offset horizontally
cmdopts="$cmdopts '$row' -geometry +$(( $(rand) % $maxoffset ))+$accoffset -composite "
let 'accoffset=accoffset + h - offset'
done
# After the trim the image will be touching the outer borders and the heading and footer,
# older versions (prior to 1.0.99) used -splice 0x10 to correct the heading spacing, 1.0.99
# onwards uses -frame to add spacing in all borders + splice to add a bit more space on the
# upper border. Note splice uses the background colour while frame uses the matte colour
eval convert -size ${canvasw}x$(( $canvash * $cols )) xc:transparent -geometry +0+0 \
$cmdopts -trim +repage -bordercolor Transparent -background Transparent -mattecolor Transparent \
-frame 5x5 -splice 0x5 "$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 I replace spaces by newlines
local s=$1
stonl "$s" | sort -n | uniq
}
 
# Test the video at a given timestamp (to see if it can be reached)
# See safe_length_measure()
# probe_video($1 = input file, $2 = stamp)
probe_video() {
local f="$1"
local ts="$2"
local tempfile=
local ret=0
 
# This time a resize filter is applied to the player to produce smaller
# output
if [ $decoder -eq $DEC_MPLAYER ]; then
tempfile=00000005.png
TEMPSTUFF=( "${TEMPSTUFF[@]}" "$tempfile" )
if ! capture_mplayer "$f" "IGNOREME" "$ts" "-vf scale=96:96"; then
ret=1
fi
elif [ $decoder -eq $DEC_FFMPEG ]; then
tempfile=$(new_temp_file '-safelen.png')
if ! capture_ffmpeg "$f" "$tempfile" "$ts" "-s 96x96"; then
ret=1
fi
else
assert $LINENO false
ret=1
fi
rm -f "$tempfile"
return $ret
}
 
# Try to guess a correct length for the video, taking the reported lengths a
# starting point
# safe_length_measure($1 = filename)
safe_length_measure() {
trace $FUNCNAME $@
local f="$1"
local len=${VID[$LEN]}
local tempfile=
local newlen=$len
local capturefn=
 
if probe_video "$1" $len ; then
inf " File looks fine, suspicion withdrawn"
echo "$len"
return 0
else
# Can't seek to the very end, adjust
warn "Starting safe length measuring (this might take a while)..."
local maxrew=$(min $QUIRKS_MAX_REWIND $(awkex "int($len)")) # At most we'll rewind 20 seconds
# -1 (-WS) => Rewind up to the start
# Might be -2, -4, ... e.g. (-WS -Ws)
if fptest $maxrew -ge $len || fptest "$maxrew" -lt 0 ; then
maxrew=$len
INTERNAL_MAXREWIND_REACHED=1
fi
for rew in $(seqr $QUIRKS_LEN_STEP $maxrew $QUIRKS_LEN_STEP); do
newlen=$(keepdecimals_lower $(awkex "$len - $rew") 3)
warn " ... trying $newlen"
if probe_video "$f" "$newlen" ; then
echo $newlen
return 0
fi
done
fi
# Hitting this line means we're doomed!
return 1
}
 
##### {{{{ Codec names
 
# Codecs TODO: Clean this
# Translates an mplayer codec id/fourcc to its name
get_vcodec_name() {
local vcid="$1"
local vcodec=
# Video codec "prettyfication", see [[R2]], [[R3]], [[R4]]
case "$vcid" in
0x10000001) vcodec="MPEG-1" ;;
0x10000002) vcodec="MPEG-2" ;;
0x00000000) vcodec="Raw video" ;; # How correct is this?
avc1|H264) vcodec="MPEG-4 AVC" ;; # H264 is used in mov/mp4
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" ;; # 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 SCC" ;;
VP6[012F]) 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" ;;
3IV2) vcodec="3ivx Delta 4.0" ;; # Rare but seen
FLV1) vcodec="Sorenson Spark (FLV1)" ;;
 
# 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" ;;
s263) vcodec="H.263" ;; # 3GPP
# Legacy(-er) codecs (haven't seen files in these formats in awhile)
IV3[0-9]) vcodec="Indeo Video 3" ;; # FF only recognises IV31
MSVC) vcodec="Microsoft Video 1" ;;
MRLE) vcodec="Microsoft RLE" ;;
3IV1) vcodec="3ivx Delta" ;;
# "mp4v" is the MPEG-4 fourcc *in mov/mp4/3gp*; but I also found MP4V (Apple's iTunes sample)
#mp4v|MP4V) vcodec="MPEG-4" ;;
mp4v|MP4V) vcodec="MPEG-4" ;;
# Synthetic, used for ffmpeg translations
vcs_divx) vcodec="DivX ;-)" ;;
*) # If not recognized fall back to FourCC
vcodec="$vcid"
;;
esac
echo "$vcodec"
}
 
# Translates an FFmpeg codec id to an MPlayer codec id/fourcc
# TODO: Clean this
translate_ffmpeg_vcodec_id() {
# The list of ffmpeg codecs might be retrieved by looking at the code but I
#+simply used the ffmpeg -formats / ffmpeg -codecs command
# Supported video decoders: $ ffmepg -codecs | grep '^ D.V'
local vcid="$1"
local mpid=
case "$vcid" in
mpeg1video) mpid="0x10000001" ;; # mpeg1video_vdpau?
mpeg2video) mpid="0x10000002" ;;
rawvideo) mpid="0x00000000" ;; # can't distinguish from I420
h264) mpid="avc1" ;;
mjpeg) mpid="MJPG" ;;
msmpeg4v1) mpid="MPG4" ;;
msmpeg4v2) mpid="MP42" ;;
theora) mpid="theo" ;;
camtasia) mpid="tscc" ;;
vp6|vp6a|vp6f) mpid="VP60" ;;
# FIXME List of codec id's I translate but haven't test:
# svq3, rv40, theora, camtasia, vp6*
# MPlayer uses uppercase while FFmpeg uses lowercase
rv10|rv20|rv30|rv40|svq1|svq3|wmv1|wmv2|wmv3) mpid=$(echo $vcid | tr '[a-z]' '[A-Z]') ;;
# FFmpeg doesn't print FourCC's so there's some codecs that can't be told apart
msmpeg4) mpid="vcs_divx" ;; # DIV3 = DIV4 = MP43
# XVID = DIVX = DX50 = FMP4 = ... = mpeg4
mpeg4) mpid="mp4v" ;; # Take advantage of an unamed MPEG-4
 
h263) mpid="s263" ;;
 
vc1) mpid="WVC1" ;; # In FF: WMVA = vc1
flv) mpid="FLV1" ;;
# Not supported (ff just prints the FourCC)
# IV4*, vp4
vp3) mpid="VP30" ;;
vp5) mpid="VP50" ;;
# Legacy(-er) codecs (haven't seen files in these formats in awhile)
# MSVC? MRLE?
indeo3) mpid="IV31" ;;
*) # If not recognized fall back to FourCC
mpid="$vcid"
;;
 
esac
echo $mpid
}
 
get_acodec_name() {
local acid="$1"
local acodec=
 
if grep -q '[ -]' <<<"$acid" ; then
# Won't be recognised anyway
echo "$acid"
return
fi
 
case "$(tolower "$acid")" 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, Ogg, probably others
acodec='Vorbis'
;;
qdm2) acodec="QDesign" ;;
"") acodec="no audio" ;;
samr) acodec="AMR" ;; # AMR-NB/AMR-WB?
# 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="$acid"
;;
esac
echo "$acodec"
}
 
translate_ffmpeg_acodec_id() {
local acid="$1"
local mpid=
# ffmpeg -codecs | grep ^\ D.A
case "$acid" in
mp3) mpid='85' ;;
# Note FF can tell apart mp1/mp2 directly
mp1) mpid='MPEG Layer I (MP1)' ;;
mp2) mpid='MPEG Layer II (MP2)' ;;
aac) mpid='mp4a' ;; # Can aac be MPEG2?
wmav1) mpid='352' ;;
wmav2) mpid='353' ;;
wmapro) mpid='354' ;; # Actually WMA9 Professional
ac3) mpid='8192' ;;
# FF has a ton of pcm variants (sign, endianness, ...)
pcm_*) mpid="1" ;;
vorbis) mpid="vrbs" ;;
 
qdm2) mpid="QDM2" ;;
libopencore_amrnb) mpid="AMR-NB" ;;
libopencore_amrwb) mpid="AMR-WB" ;;
*) # If not recognized show audio id tag
mpid="$acid"
;;
esac
echo "$mpid"
}
 
##### }}}} # Codec names
 
# Try to identify video properties using mplayer
# Fills $MPLAYER_CACHE with the relevant output and $VID_MPLAYER with
# the actual values. See identify_video()
# mplayer_identify($1 = file)
mplayer_identify() {
trace $FUNCNAME $@
[ "$MPLAYER" ] || return
local f="$1"
local mi=( )
# Note to self: Don't change the -vc as it would affect $vdec
if [ $DVD_MODE -eq 0 ]; then
MPLAYER_CACHE=$("$MPLAYER" -benchmark -ao null -vo null -identify -frames 0 \
-quiet "$f" 2>/dev/null | grep ^ID)
else
MPLAYER_CACHE=$("$MPLAYER" -benchmark -ao null -vo null -identify -frames 0 \
-quiet -dvd-device $DVD_FILE dvd://$DVD_TITLE \
2>/dev/null | grep ^ID)
fi
# Note the head -1!
mi[$VCODEC]=$(grep ID_VIDEO_FORMAT <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2) # FourCC
mi[$ACODEC]=$(grep ID_AUDIO_FORMAT <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2)
mi[$VDEC]=$(grep ID_VIDEO_CODEC <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2) # Decoder (!= Codec)
mi[$W]=$(grep ID_VIDEO_WIDTH <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2)
mi[$H]=$(grep ID_VIDEO_HEIGHT <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2)
mi[$FPS]=$(grep ID_VIDEO_FPS <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2)
# For some reason my (one track) samples have two ..._NCH, first one 0
#+Also multichannel is detected as 2 ch
mi[$CHANS]=$(grep ID_AUDIO_NCH <<<"$MPLAYER_CACHE"| grep -v '=0' | cut -d'=' -f2|head -1)
if [ $DVD_MODE -eq 0 ]; then
# For DVDs it prints ID_DVD_TITLE_x_LENGTH and ID_LENGTH.
#+Both appear valid.
mi[$LEN]=$(grep ID_DVD_TITLE_${DVD_TITLE}_LENGTH <<<"$MPLAYER_CACHE"| cut -d'=' -f2)
[ "${mi[$LEN]}" ] || mi[$LEN]=$(grep ID_LENGTH <<<"$MPLAYER_CACHE"| head -1 | cut -d'=' -f2)
else
mi[$LEN]=$(grep ID_DVD_TITLE_${DVD_TITLE}_LENGTH <<<"$MPLAYER_CACHE"| head -1 | cut -d'=' -f2)
fi
# Voodoo :P Remove (one) trailing zero
if [ "${mi[$FPS]:$(( ${#mi[$FPS]} - 1 ))}" == "0" ]; then
mi[$FPS]="${mi[$FPS]:0:$(( ${#mi[$FPS]} - 1 ))}"
fi
mi[$ASPECT]=$(grep ID_VIDEO_ASPECT <<<"$MPLAYER_CACHE" | egrep -v '^0.0000$' | cut -d'=' -f2 | tail -1)
# If none set, delete it
[ "${mi[$ASPECT]}" ] && fptest "${mi[$ASPECT]}" -eq 0.0 && mi[$ASPECT]=''
mi[$VCNAME]=$(get_vcodec_name "${mi[$VCODEC]}")
if [ "${mi[$VDEC]}" == "ffodivx" ] && [ "${mi[$VCNAME]}" != "MPEG-4" ]; then
mi[$VCNAME]="${mi[$VCNAME]} (MPEG-4)"
elif [ "${mi[$VDEC]}" == "ffh264" ]; then # At least two different fourccs use h264, maybe more
mi[$VCNAME]="${mi[$VCNAME]} (h.264)"
fi
mi[$ACNAME]=$(get_acodec_name "${mi[$ACODEC]}")
if [ "${mi[$ACODEC]}" == "samr" ] ; then
local adec=$(grep ID_AUDIO_CODEC <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2)
if [ "$adec" == "ffamrnb" ]; then
mi[$ACNAME]="AMR-NB";
fi
fi
 
# Array assignment
VID_MPLAYER=("${mi[@]}")
}
 
# Try to identify video properties using ffmpeg
# Fills $FFMPEG_CACHE with the relevant output and $VID_FFMPEG with
# the actual values. See identify_video()
# mplayer_identify($1 = file)
ffmpeg_identify() {
trace $FUNCNAME $@
[ "$FFMPEG" ] || return
# (AFAIK) Can't use ffmpeg in DVD Mode
#[ $DVD_MODE -eq 0 ] || return
local f="$1"
# DVD Devices *MUST* be mounted for the identifying process to even start
assert $LINENO "[ $DVD_MODE -eq 0 ] || [ '$DVD_MOUNTP' ]"
[ $DVD_MODE -eq 1 ] && [ "$DVD_DEVICE" ] && {
local vfile="$DVD_MOUNTP/VIDEO_TS/VTS_${DVD_VTS}_1.VOB"
if [ ! -r "$vfile" ]; then
error "Failed to locale mounted DVD. Detection will be less accurate."
return 0 # We can continue anyway
fi
f="$vfile"
}
 
local fi=( ) vs= as= obs= vsid=
# FFmpeg is relatively new, introduced in 1.0.99 so it needs more testing
FFMPEG_CACHE=$("$FFMPEG" -i "$f" -dframes 0 -vframes 0 /dev/null 2>&1 | egrep '(Stream|Duration:|^Seems)')
# Only the first streams of each type are honored. FIXME: Add multi-audio support.
vs=$(grep Stream <<<"$FFMPEG_CACHE" | grep 'Video:' | head -1)
as=$(grep Stream <<<"$FFMPEG_CACHE" | grep 'Audio:' | head -1)
obs=$(grep Seems <<<"$FFMPEG_CACHE")
# Stream #0.0: Video: mpeg4, yuv420p, 624x352 [PAR 1:1 DAR 39:22], 23.98 tbr, 23.98 tbn, 24k tbc
# New and old versions of ffmpeg changed their output considerably, e.g.:
# (same file, Robotica_720.wmv)
# New output:
# Seems stream 1 codec frame rate differs from container frame rate: 1000.00 (1000/1) -> 23.98 (24000/1001)
# [...]
# Duration: 00:00:20.77, start: 3.000000, bitrate: 6250 kb/s
# Stream #0.0(eng): Audio: wmapro, 48000 Hz, 6 channels, s16, 384 kb/s
# Stream #0.1(eng): Video: wmv3, yuv420p, 1280x720, 6500 kb/s, 23.98 tbr, 1k tbn, 1k tbc
# Old output:
# Duration: 00:00:20.7, start: 3.000000, bitrate: 6250 kb/s
# Stream #0.0: Audio: 0x0162, 48000 Hz, 5:1, 384 kb/s
# Stream #0.1: Video: wmv3, yuv420p, 1280x720, 24.00 fps(r)
# TODO: tbr is rounded to two decimals but the actual ratio is printed:
# 24000/1001 = 23.97602
# (older ffmpeg prints 24 fps, 24/1 so no luck here
# **Also seen**: (note the 'tb(r)')
# Stream #0.1: Video: wmv3, yuv420p, 1440x1080 [PAR 4:3 DAR 16:9], 8000 kb/s, 23.98 tb(r)
# **Also seen**: (VOB, latest ffmpeg as of this writing):
# Stream #0.0[0x1e0]: Video: mpeg2video, yuv420p, 720x576 [PAR 64:45 DAR 16:9], 9800 kb/s, 23.53 fps, 25 tbr, 90k tbn, 50 tbc
# **Also seen**: (DVB TS to DX50 in MKV), note the DAR mess, the second one is the correct one
# Stream #0.0: Video: mpeg4, yuv420p, 640x326 [PAR 1:1 DAR 320:163], PAR 231:193 DAR 73920:31459, 25 fps, 25 tbr, 1k tbn, 25 tbc
vsid=$(grep -o '#0.[0-9]' <<<"$vs" | cut -d'.' -f2) # Video Stream ID
fi[$VCODEC]=$(egrep -o 'Video: [^,]*' <<<"$vs" | cut -d' ' -f2-)
# ffmpeg's codec might contain spaces in some cases, i.e. iv4 in mov (see mplayer's bestiary)
#+unless this turns out to be common I won't be handling it specially
# Note unidentified audio codecs will be printed in hexadecimal
fi[$ACODEC]=$(egrep -o 'Audio: [^,]*' <<<"$as" | cut -d' ' -f2)
fi[$VDEC]=''
# The comma is required for cases where the stream id is printed (in hex)
fi[$W]=$(egrep -o ', [0-9]*x[0-9]*' <<<"$vs" | sed 's/^, //' | cut -dx -f1)
fi[$H]=$(egrep -o ', [0-9]*x[0-9]*' <<<"$vs" | cut -dx -f2)
# Newer CHANS and some older...
fi[$CHANS]=$(egrep -o '[0-9]* channels' <<<"$as" | cut -d' ' -f1)
# ...fallback for older
if [ -z "${fi[$CHANS]}" ]; then
local chans=$(egrep -o 'Hz, [^,]*' <<<"$as" | cut -d' ' -f2)
case $chans in
mono) fi[$CHANS]=1 ;;
stereo) fi[$CHANS]=2 ;;
5.1|5:1) fi[$CHANS]=6 ;; # *
*) ;; # Other layouts use 'N channels'
# 5.1 was in the previous version (can't remember if it was empirical).
esac
fi
# Newer FPS...
# tbr/tbn/tbc explanation: tb stands for time base
# n: AVStream, c: AVCodecContext, r: VideoStream (Guessed)
# tbr is the best bet. Note it's common for WMVs to contains "1k tbn, 1k tbc"
# tbr is rounded to two decimals, the values used to derived it might be
# printed in a "Seems ..." line like the one in the example above so it
# can be re-calculated.
fi[$FPS]=$(egrep -o '[0-9]*\.?[0-9]*k? tb(r|\(r\))' <<<"$vs" | cut -d' ' -f1)
# Let's convert e.g. 23.98 into 23.976...:
if [ "${fi[$FPS]}" ] && grep -q '\.' <<<"${fi[$FPS]}" ; then
# Decimals, see if we got better values available
local vsobs=$(grep "stream $vsid" <<<"$obs")
# Observations regarding video stream found
if [ "$vsobs" ] && grep -q " -> ${fi[$FPS]} (.*)" <<<"$vsobs" ; then
# FPS candidate
local newfps=$(egrep -o -- '-> [^ ]* \([0-9]*/[0-9]*' <<<"$vsobs" | cut -d'(' -f2)
is_fraction $newfps && fi[$FPS]=$(keepdecimals "$newfps" 3)
fi
fi
# ...fallback for older. The older version I tried seems to round further, i.e.
# 23.976 became 24 so no fix for this one
if [ -z "${fi[$FPS]}" ]; then
# No k suffix here, 1000 is 1000
fi[$FPS]=$(egrep -o '[0-9]*\.?[0-9]* fps' <<<"$vs" | cut -d' ' -f1)
fi
# Be consistent with mplayer's output: at least two decimals
[ "${fi[$FPS]}" ] && {
fi[$FPS]=$(keepdecimals "${fi[$FPS]}" 3)
fi[$FPS]=${fi[$FPS]/%0} # Strip 0$
}
fi[$LEN]=$(egrep -o 'Duration: [^,]*' <<<"$FFMPEG_CACHE" | cut -d' ' -f2)
if [ "${fi[$LEN]}" == "N/A" ]; then # It might be unable to detect
fi[$LEN]=""
fi
fi[$LEN]=$( get_interval $(echo "${fi[$LEN]}" | sed 's/:/h/' | sed 's/:/m/') )
# Aspect ratio in FFmpeg is only provided in newer ffmpeg
# It might be calculated for files without one (which is ok anyway)
# TODO: Replace tail -1 with some better option (see the double DAR example above)
fi[$ASPECT]=$(egrep -o 'DAR [0-9]*:[0-9]*'<<<"$FFMPEG_CACHE" | tail -1 | cut -d' ' -f2 | sed 's#:#/#')
# Due to calling ffmpeg on a single VOB when in DVD Device mode, the length will be partial
[ $DVD_MODE -eq 1 ] && [ "$DVD_DEVICE" ] && fi[$LEN]=''
fi[$VCNAME]=$(get_vcodec_name $(translate_ffmpeg_vcodec_id "${fi[$VCODEC]}"))
fi[$ACNAME]=$(get_acodec_name $(translate_ffmpeg_acodec_id "${fi[$ACODEC]}"))
VID_FFMPEG=("${fi[@]}")
}
 
# Use the available tools to identify video meta-data
# fills $VID with the values
# Return codes:
# 3: Failed to detect length
# 4: Failed to detect width or height
# identify_video($1 = file)
identify_video() {
trace $FUNCNAME $@
local RET_NOLEN=3 RET_NODIM=4
 
[ "$MPLAYER" ] && mplayer_identify "$1"
# ffmpeg_identify in DVD mode only works when the DVD is mounted:
[ $DVD_MODE -eq 0 ] && [ "$FFMPEG" ] && ffmpeg_identify "$1"
[ $DVD_MODE -eq 1 ] && [ "$FFMPEG" ] && [ "$DVD_MOUNTP" ] && ffmpeg_identify "$1"
 
# Fail early if none detected length
[ -z "${VID_MPLAYER[$LEN]}" ] && [ -z "${VID_FFMPEG[$LEN]}" ] && return $RET_NOLEN
 
# Classic mode, use both mplayer and ffmpeg when available
if [ "$MPLAYER" ] && [ "$FFMPEG" ]; then
# By default take mplayer's values
VID=("${VID_MPLAYER[@]}")
# FFmpeg seems better at getting the correct number of FPS, specially with
# WMVs, where mplayer often accepts 1000fps while ffmpeg notices the
# inconsistency in container vs codec and guesses better, *but* it only
# uses two decimals so 23.976 becomes 23.98. So it is only used when
# the number of decimals seems right.
# When a "Seems..." line is printed the correct FPS can be obtained though.
[ -z "${VID_MPLAYER[$FPS]}" ] && VID[$FPS]=${VID_FFMPEG[$FPS]}
[ "${VID_MPLAYER[$FPS]}" ] && [ "${VID_FFMPEG[$FPS]}" ] && {
# Trust ffmpeg if it has three decimals OR if mplayer is probably-wrong
local ffps=${VID_FFMPEG[$FPS]}
echo $ffps | grep -q '\.[0-9][0-9][0-9]' && VID[$FPS]=$ffps || {
fptest "${VID_MPLAYER[$FPS]}" -gt 500 && VID[$FPS]=$ffps
}
}
# It doesn't appear to need any workarounds for num. channels either
[ "${VID_FFMPEG[$CHANS]}" ] && VID[$CHANS]=${VID_FFMPEG[$CHANS]}
[ "${VID_FFMPEG[$ASPECT]}" ] && VID[$ASPECT]=${VID_FFMPEG[$ASPECT]}
# There's a huge inconsistency with some files, both mplayer vs ffmpeg
# same application on different OSes
local fflen=${VID_FFMPEG[$LEN]} mplen=${VID_MPLAYER[$LEN]} # Shorthands
[ -z "$fflen" ] && fflen=0
# If both report 0, there's no good value...
fptest "$fflen" -eq 0 && fptest "$mplen" -eq 0 && return $RET_NOLEN
if [ $DVD_MODE -eq 0 ] && [ $QUIRKS -eq 0 ]; then # In DVD mode ffmpeg has no length
# Quirks disabled, should be enabled?
local delta=$(abs $(awkex "($fflen - $mplen)"))
# If they don't agree, take the shorter as a starting point,
#+if both are different than zero take min, if one of them is 0, take max to start
if fptest "$fflen" -ne 0 && fptest "$mplen" -ne 0 ; then
VID[$LEN]=$(min $fflen $mplen)
else
VID[$LEN]=$(max $fflen $mplen)
delta=$QUIRKS_LEN_THRESHOLD # Ensure it's considered inconsistent
fi
# If they differ too much, enter safe mode. If one reports 0, they'll differ...
# FIXME: If $decoder reports 0, can it seek??
if fptest "$delta" -ge $QUIRKS_LEN_THRESHOLD ; then
warn "Found inconsistency in reported length. Safe measuring enabled."
QUIRKS=1
fi
fi
elif [ "$MPLAYER" ]; then
# Must do with mplayer only...
VID=("${VID_MPLAYER[@]}")
# Warn if a known pitfall is found
# See above for 1000 fps
[ "${VID[$FPS]}" == "1000.00" ] && \
warn "Possible inaccuracy in FPS detection." && \
warn " Install both mplayer and ffmpeg for better detection."
# Number of channels 0 happened for WMA in non-x86
[ "${VID[$CHANS]}" == "0" ] && \
warn "Failed to detect number of audio channels." && \
warn " Install both mplayer and ffmpeg for better detection."
elif [ "$FFMPEG" ]; then
# Must do with mplayer only...
VID=("${VID_FFMPEG[@]}")
# So far I know of no weird results. Yet.
else
assert $LINENO 'false'
fi
 
# Ensure sanity of the most important values
is_float "${VID[$LEN]}" || return $RET_NOLEN
is_number "${VID[$W]}" && is_number "${VID[$H]}" || return $RET_NODIM
 
if [ "$FFMPEG" ]; then
# FPS at least with two decimals
if [ $(awkex "int(${VID[$FPS]})") == ${VID[$FPS]} ]; then
VID[$FPS]="${VID[$FPS]}.00"
fi
fi
 
local mfps="${VID_MPLAYER[$FPS]}"
if [ $QUIRKS -eq 0 ] && [ "$MPLAYER" ] && fptest "$mfps" -eq 1000 ; then
warn "Suspect file. Safe measuring enabled."
QUIRKS=1
fi
 
# Last safeguard: Try to reach the detected length, if it fails, trigger
# quirks mode
if [ $QUIRKS -eq 0 ]; then
if ! probe_video "$1" "${VID[$LEN]}" ; then
warn "Detected video length can't be reached. Safe measuring enabled."
QUIRKS=1
fi
fi
 
if [ $QUIRKS -eq 1 ]; then
VID[$LEN]=$(safe_length_measure "$1")
if [ -z "${VID[$LEN]}" ]; then
error "Couldn't measure length in a reasonable amount of tries."
if [ $INTERNAL_MAXREWIND_REACHED -eq 1 ]; then
error " Will not be able to capture this file with the current settings."
else
local reqs=$(( $INTERNAL_WS_C + 1 )) reqp=''
[ $reqs -eq 1 ] && reqp=" -WP" || reqp=" -WP$reqs"
[ $reqs -ge 3 ] && reqs=" -WS" || { # Third try => Recommend -WS
[ $reqs -eq 1 ] && reqs=" -Ws" || reqs=" -Ws$reqs"
}
assert 'fptest "$QUIRKS_MAX_REWIND" -gt 0'
local offby=$(pretty_stamp $QUIRKS_MAX_REWIND)
warn " Capturing won't work, video is at least $offby shorter than reported."
local dname='ffmpeg'
[ $decoder -eq $DEC_MPLAYER ] && dname='mplayer'
warn " Does $dname support ${VID[$VCODEC]}?."
warn " Try re-running with$reqs$reqp."
fi
return 1
fi
elif [ $QUIRKS -eq -2 ]; then
warn "Safe mode disabled."
fi
 
# Re-check sanity of the most important values
is_float "${VID[$LEN]}" || return $RET_NOLEN
is_number "${VID[$W]}" && is_number "${VID[$H]}" || return $RET_NODIM
}
 
dump_idinfo() {
trace $FUNCNAME $@
[ "$MPLAYER" ] && echo "Mplayer: $MPLAYER"
[ "$FFMPEG" ] && echo "FFmpeg: $FFMPEG"
[ "$MPLAYER" ] && cat <<-EODUMP
=========== Mplayer Identification ===========
Length: $(pretty_stamp ${VID_MPLAYER[$LEN]})
Video
Codec: ${VID_MPLAYER[$VCODEC]} (${VID_MPLAYER[$VCNAME]})
Dimensions: ${VID_MPLAYER[$W]}x${VID_MPLAYER[$H]}
FPS: ${VID_MPLAYER[$FPS]}
Aspect: ${VID_MPLAYER[$ASPECT]}
Audio
Codec: ${VID_MPLAYER[$ACODEC]} (${VID_MPLAYER[$ACNAME]})
Channels: ${VID_MPLAYER[$CHANS]}
==============================================
 
EODUMP
local ffl="${VID_FFMPEG[$LEN]}"
[ "$ffl" ] && ffl=$(pretty_stamp "$ffl")
[ -z "$ffl" ] && [ $DVD_MODE -eq 1 ] && ffl="(unavailable in DVD mode)"
[ "$FFMPEG" ] && cat <<-EODUMP
=========== FFmpeg Identification ===========
Length: $ffl
Video
Codec: ${VID_FFMPEG[$VCODEC]} (${VID_FFMPEG[$VCNAME]})
Dimensions: ${VID_FFMPEG[$W]}x${VID_FFMPEG[$H]}
FPS: ${VID_FFMPEG[$FPS]}
Aspect: ${VID_FFMPEG[$ASPECT]}
Audio
Codec: ${VID_FFMPEG[$ACODEC]} (${VID_FFMPEG[$ACNAME]})
Channels: ${VID_FFMPEG[$CHANS]}
=============================================
 
EODUMP
local xar=
if [ "${VID[$ASPECT]}" ]; then
xar=$(keepdecimals "${VID[$ASPECT]}" 4)
[ "$xar" ] && xar=" ($xar)"
fi
cat <<-EODUMP
=========== Combined Identification ===========
Length: $(pretty_stamp ${VID[$LEN]})
Video
Codec: ${VID[$VCODEC]} (${VID[$VCNAME]})
Dimensions: ${VID[$W]}x${VID[$H]}
FPS: ${VID[$FPS]}
Aspect: ${VID[$ASPECT]}$xar
Audio
Codec: ${VID[$ACODEC]} (${VID[$ACNAME]})
Channels: ${VID[$CHANS]}
=============================================
EODUMP
 
 
}
 
# Try to pick some font capable of handling non-latin text
set_extended_font() {
trace $FUNCNAME $@
# This selection includes japanese fonts
local candidates=$(identify -list font | grep 'Font: ' | \
egrep -io '[a-z-]*(kochi|mincho|sazanami|ipafont)[a-z-]*')
if [ -z "$candidates" ]; then
error "Unable to auto-select filename font, please provide one (see -fullhelp)"
return 1
else
if [ "$DEBUG" -eq 1 ]; then
local list=$(echo "$candidates" | sed 's/^/ >/g')
inf "Available non-latin fonts detected:"
inf "$list"
fi
fi
 
# Bias towards the Sazanami family
if grep -qi 'sazanami' <<<"$candidates" ; then
FONT_MINCHO=$(grep -i 'sazanami' <<<"$candidates" | head -1)
else
FONT_MINCHO=$(head -1 <<<"$candidates")
fi
}
 
# Checks if the provided arguments make sense and are allowed to be used
# together
coherence_check() {
trace $FUNCNAME $@
# 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)"
return $EX_USAGE
fi
 
if [ $decoder -eq $DEC_MPLAYER ] && [ -z "$MPLAYER" ]; then
inf "No mplayer available. Using ffmpeg only."
decoder=$DEC_FFMPEG
elif [ $decoder -eq $DEC_FFMPEG ] && [ -z "$FFMPEG" ]; then
inf "No ffmpeg available. Using mplayer only."
decoder=$DEC_MPLAYER
fi
 
if [ $DVD_MODE -eq 1 ] ; then
# Currently it's not allowed to use dvd mode with more than one input
# "file" (in this mode, input files are actually dvd titles of the file
# provided in -V)
if [ $multiple_input_files -eq 1 ]; then
error "Only an input file is allowed in DVD mode"
return $EX_UNAVAILABLE
fi
 
# DVD Mode only works with mplayer, the decoder is changed when
# the DVD mode option is found, so if it's ffmpeg at this point,
# it's by user request (i.e. -F after -V)
if [ $decoder -ne $DEC_MPLAYER ]; then
if [ "$MPLAYER" ]; then
warn "DVD mode requires the use of mplayer, falling back to it"
decoder=$DEC_MPLAYER
else
error "DVD mode requires the use of mplayer."
return $EX_UNAVAILABLE
fi
fi
fi
 
local filter=
if [ $DISABLE_TIMESTAMPS -eq 0 ] &&
local -a filts=( )
has_filter filt_polaroid && has_filter filt_apply_stamp ; then
 
for filter in ${FILTERS_IND[@]} ; do
if [ "$filter" == "filt_polaroid" ]; then
filts=( "${filts[@]}" "$filter" filt_apply_stamp )
elif [ "$filter" == "filt_apply_stamp" ]; then
continue;
else
filts=( "${filts[@]}" $filter )
fi
done
FILTERS_IND=( "${filts[@]}" )
unset filts
fi
# The shoftshadow and randrot filters must be in the correct place
# or they will affect the image incorrectly.
# Additionally the default filters can be disabled from the command
# line (with --disable), they're removed from the filter chain here
local -a filts=( ) end_filts=( )
for filter in ${FILTERS_IND[@]} ; do
case "$filter" in
filt_softshadow)
# Note the newer soft shadows code (1.0.99 onwards) behaves slightly
# differently. On previous versions disabling shadows only affected
# the montage shadow (but e.g. the polaroid mode preserved them),
# this is no longer true
if [ $DISABLE_SHADOWS -ne 1 ]; then
end_filts[100]="filt_softshadow"
fi
;;
filt_apply_stamp)
if [ $DISABLE_TIMESTAMPS -ne 1 ]; then
filts=( "${filts[@]}" "$filter" )
fi
;;
filt_randrot) end_filts[200]="filt_randrot" ;;
*) filts=( "${filts[@]}" "$filter" ) ;;
esac
done
FILTERS_IND=( "${filts[@]}" "${end_filts[@]}" )
 
# Override-able options check, in case they were set from overrides instead
#+of equivalent command-line options. Will check the actual selected values,
#+i.e. fail silently if the overrides aren't effective
[ "$USR_th_height" ] && { check_height "$th_height" || exit $? ; }
[ "$USR_numcaps" ] && { check_numcaps "$numcaps" || exit $? ; }
[ "$USR_interval" ] && { check_interval "$interval" || exit $? ; }
# Interval=0 == default interval
fptest "$interval" -eq 0 && interval=$DEFAULT_INTERVAL
 
sanitise_rename_pattern
sanitise_fonts
}
 
# If the OS hasn't registered TTF fonts with IM, try to use a saner value
#+*only* for fonts not overridden
sanitise_fonts() {
# Any default font in use? If all of them are overridden, return
if [ "$USR_font_heading" -a "$USR_font_title" -a "$USR_font_tstamps" -a "$USR_font_sign" ]; then
return
fi
# If the user edits any font in the script, stop messing with this
[ -z "$USR_font_heading" ] && [ "$font_heading" != 'DejaVu-Sans-Book' ] && return
[ -z "$USR_font_title" ] && [ "$font_title" != 'DejaVu-Sans-Book' ] && return
[ -z "$USR_font_tstamps" ] && [ "$font_tstamps" != 'DejaVu-Sans-Book' ] && return
[ -z "$USR_font_sign" ] && [ "$font_sign" != 'DejaVu-Sans-Book' ] && return
# Try to locate DejaVu Sans
[ ! -d /usr/share/fonts ] && return
local dvs=$(find /usr/share/fonts/ -type f -iname 'dejavusans.ttf')
if [ -z "$dvs" ]; then
warn "Unable to locate DejaVu Sans font. Falling back to helvetica."
dvs=helvetica
fi
[ -z "$USR_font_heading" ] && font_heading="$dvs"
[ -z "$USR_font_title" ] && font_title="$dvs"
[ -z "$USR_font_tstamps" ] && font_tstamps="$dvs"
[ -z "$USR_font_sign" ] && font_sign="$dvs"
[ $DEBUG -eq 1 ] || { return 0; }
cat >&2 <<-EOFF
Font Sanitization:
font_heading: $font_heading
font_title : $font_title
font_tstamps: $font_tstamps
font_sign : $font_sign
EOFF
}
 
check_height() { # Acceptable height
if ! is_number "$1" && ! is_percentage "$1" ; then
error "Height must be a (positive) number or a percentage. Got '$1'."
return $EX_USAGE
fi
}
 
check_numcaps() { # Acceptable numcaps
if ! is_number "$1" ; then
error "Number of captures must be a (positive) a number! Got '$1'."
return $EX_USAGE
fi
if [ $1 -eq 0 ]; then
error "Number of captures must be greater than 0! Got '$1'."
return $EX_USAGE
fi
}
 
check_interval() { # Acceptable interval
if ! get_interval "$1" >/dev/null ; then
error "Incorrect interval format. Got '$1'."
return $EX_USAGE
fi
}
 
# Main function.
# Creates the contact sheet.
# process($1 = file)
process() {
trace $FUNCNAME $@
local f=$1
 
local numcols=
# Save variables that will be overwritten and must be reset with multiple files
# pre_* will contain the user-supplied or default values
local pre_quirks=$QUIRKS
local pre_aspect_ratio=$aspect_ratio
local pre_output_format="$output_format"
INTERNAL_MAXREWIND_REACHED=0 # Reset for each file
 
# XXX: Some of this should be moved to coherence_check
if [ $DVD_MODE -eq 1 ]; then # DVD Mode
f="$DVD_FILE"
DVD_DEVICE=
local dvdn=$(realpathr "$f") # dvdn might be a device or an ISO
if [ -f "$dvdn" ]; then
# It's an ISO
DVD_MOUNTP=$(get_dvd_image_mountpoint)
if [ -z "$DVD_MOUNTP" ]; then
# Only in Linux does this matter
if ! is_linux ; then
warn "Video properties detection for ISO files is not accurate"
else
warn "Mount DVD image to get better video properties detection"
fi
fi
elif [ ! -r "$dvdn" ]; then
# It's something else we cannot read
error "Can't access DVD ($f)"
return $EX_NOINPUT
else
# It's a device. Note BSD has no concept of block devices.
# It MUST be mounted to continue. This is required to allow ffmpeg detection
#+and to calculate file size
if ! mount | egrep -q "^$dvdn\ " ; then
error "DVD mode requires device ($f) to be mounted"
return $EX_UNAVAILABLE
fi
DVD_DEVICE="$dvdn"
DVD_MOUNTP=$(mount | grep -o "^$DVD_DEVICE *on [^ ]*" | cut -d' ' -f3)
dvdn="DVD"
fi
inf "Processing $dvdn..."
unset dvdn
if ! is_number "$1" ; then
error "DVD Title must be a number (e.g.: \$ vcs -V /dev/dvd 1)"
exit $EX_USAGE
fi
DVD_TITLE=$1
if [ $DVD_TITLE -eq 0 ]; then
local dt="$(lsdvd "$DVD_FILE" 2>/dev/null | grep 'Longest track:' | \
cut -d' ' -f3- | sed 's/^0*//')"
if ! is_number "$dt" ; then
error "Failed to autodetect longest DVD title"
exit $EX_INTERNAL
fi
DVD_TITLE=$dt
DVD_VTS=$(lsdvd -t$DVD_TITLE -v "$DVD_FILE" 2>/dev/null | grep -o 'VTS: [0-9]*' | cut -d' ' -f2)
unset dt
inf "Using DVD Title #$DVD_TITLE (VTS: $DVD_VTS)"
fi
else # Not DVD Mode:
if [ ! -f "$f" ]; then
error "File \"$f\" doesn't exist"
return $EX_NOINPUT
fi
 
inf "Processing $f..."
fi
 
create_temp_dir
# {{SET_E}} Beware, set -e will break this
identify_video "$f"
local ecode=$?
[ $ecode -eq 0 ] || {
case $ecode in
3) error "Unable to find length of file \"$f\". Can't continue." ;;
4) error "Unable to detect dimensions of file \"$f\". Can't continue." ;;
*) error "Failure while analysing file \"$f\". Can't continue." ;;
esac
return $EX_UNAVAILABLE
}
 
# Identification-only mode?
[ "$UNDFLAG_IDONLY" ] && dump_idinfo && return 0
 
# Vidcap/Thumbnail height
local vidcap_height=$th_height
if is_percentage "$th_height" ; then
local pc=${th_height/%%/} # BASH %% == RE %$
vidcap_height=$(awkex "int ((${VID[$H]} * ${pc}) / 100 + 0.5)")
inf "Height: $th_height of ${VID[$H]} = $vidcap_height"
fi
if ! is_number "$vidcap_height" || [ "$vidcap_height" -eq 0 ]; then
vidcap_height=${VID[$H]}
fi
# -2: DVD Mode autodetection => If ffmpeg/mplayer was unable autodetect, otherwise
#+ honor detected value
[ "-2" == "$aspect_ratio" ] && [ -z "${VID[$ASPECT]}" ] && aspect_ratio=-1
[ "-2" == "$aspect_ratio" ] && [ "${VID[$ASPECT]}" ] && aspect_ratio=0
if [ "0" == "$aspect_ratio" ]; then
if [ "${VID[$ASPECT]}" ]; then
# Aspect ratio in file headers, honor it
aspect_ratio=$(awkex "${VID[$ASPECT]}")
else
aspect_ratio=$(awkex "${VID[$W]} / ${VID[$H]}")
fi
elif [ "-1" == "$aspect_ratio" ]; then
aspect_ratio=$(guess_aspect ${VID[$W]} ${VID[$H]})
inf "Aspect ratio set to $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
 
unset TIMECODES
# Compute the stamps (if in auto mode)...
if [ $manual_mode -eq 1 ]; then
# Note TIMECODES must be set as an array to get the correct count in
# manual mode; in automatic mode it will be set correctly inside
# compute_timecodes()
TIMECODES=( ${initial_stamps[@]} )
else
TIMECODES=${initial_stamps[@]}
compute_timecodes $timecode_from $interval $numcaps || {
return $?
}
fi
 
local output=$(new_temp_file '-preview.png')
local VIDCAPFILE=00000005.png
 
# If the temporal vidcap already exists, abort
if [ -f $VIDCAPFILE ]; then
error "File $VIDCAPFILE 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=( "${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 $CTX_HL $n || {
local r=$?
error "Failed to apply transformations to the capture."
return $r
}
hlcapfile=$(new_temp_file "-hl-$(pad 6 $n).png")
mvq "$VIDCAPFILE" "$hlcapfile"
capfiles=( "${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 $CTX_STD $n || return $?
 
# identified by capture number, padded to 6 characters
capfile=$(new_temp_file "-cap-$(pad 6 $n).png")
mvq "$VIDCAPFILE" "$capfile"
capfiles=( "${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 $CTX_EXT $n || return $?
 
capfile=$(new_temp_file "-excap-$(pad 6 $n).png")
mvq "$VIDCAPFILE" "$capfile"
capfiles=( "${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
 
local vcodec=${VID[$VCNAME]}
local acodec=${VID[$ACNAME]}
 
if [ "${VID[$CHANS]}" ] && is_number "${VID[$CHANS]}" &&[ ${VID[$CHANS]} -ne 2 ]; then
if [ ${VID[$CHANS]} -eq 1 ]; then
acodec="$acodec (mono)"
else
acodec="$acodec (${VID[$CHANS]}ch)"
fi
fi
 
local csw=$(imw "$output") exw= hlw=
local width=$csw
if [ "$HLTIMECODES" ] || [ "$extended_factor" != "0" ]; then
inf "Merging contact sheets..."
if [ "$HLTIMECODES" ]; then
local hlw=$(imw "$hlfile")
if [ $hlw -gt $width ]; then width=$hlw ; fi
fi
if [ "$extended_factor" != "0" ]; then
local exw=$(imw $extoutput)
if [ $exw -gt $width ]; then width=$exw ; fi
fi
fi
if [ $csw -lt $width ]; then
local csh=$(imh "$output")
# Expand the standard set to the maximum width of the sets by padding both sides
# For some reason the more obvious (to me) convert command-lines lose
# the transparency
convert \( -size $(( ($width - $csw) / 2 ))x$csh xc:transparent \) "$output" \
\( -size $(( ($width - $csw) / 2 ))x$csh xc:transparent \) +append "$output"
unset csh
fi
 
# If there were highlights then mix them in
if [ "$HLTIMECODES" ]; then
# For some reason adding the background also adds padding with:
# convert \( -background LightGoldenRod "$hlfile" -flatten \) \
# \( "$output" \) -append "$output"
# replacing it with a "-composite" operation apparently works
# Expand the highlights to the correct size by padding
local hlh=$(imh "$hlfile")
if [ $hlw -lt $width ]; then
convert \( -size $(( ($width - $hlw) / 2 ))x$hlh xc:transparent \) "$hlfile" \
\( -size $(( ($width - $hlw) / 2 ))x$hlh xc:transparent \) +append "$hlfile"
fi
convert \( -size ${width}x${hlh} xc:LightGoldenRod "$hlfile" -composite \) \
\( -size ${width}x1 xc:black \) \
"$output" -append "$output"
unset hlh
fi
# Extended captures
if [ "$extended_factor" != 0 ]; then
# Already set local exw=$(imw "$extoutput")
local exh=$(imh "$extoutput")
if [ $exw -lt $width ]; then
# Expand the extended set to be the correct size
convert \( -size $(( ($width - $exw) / 2 ))x$exh xc:transparent \) "$extoutput" \
\( -size $(( ($width - $exw) / 2 ))x$exh xc:transparent \) +append "$extoutput"
fi
convert "$output" -background Transparent "$extoutput" -append "$output"
fi
# Add the background; -trim added in 1.11. I'm unsure of why but whithout trimmin extra blank
#+space is added at the top
local dotrim=
[ $DISABLE_SHADOWS -eq 1 ] && [ -z "$HLTIMECODES" ] && dotrim=-trim
convert -background "$bg_contact" "$output" -flatten $dotrim "$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=$(imw "$output")
# 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.
local filename_label="Filename"
local filesize_label="File size"
local filename_value=
local filesize_value=
if [ $DVD_MODE -eq 1 ]; then
# lsdvd is guaranteed to be installed if DVD mode is enabled
local dvd_label=$(lsdvd "$DVD_FILE" 2>/dev/null | grep -o 'Disc Title: .*' | cut -d' ' -f3-)
# Need a mountpoint to get the actual *title* size
if [ "$DVD_MOUNTP" ]; then
filename_label="Disc label"
filename_value="$dvd_label"
filesize_label="Title size"
filesize_value="$(get_dvd_size)"
else
# Not mounted. We can get the disc size but this will include any other titles.
# Since 1.11 mounting DVDs is mandatory to get the title size. Both for ISOs and
#+ devices
filename_value="$(basename "$DVD_FILE") $filename_value (DVD Label: $dvd_label)"
is_linux && warn "DVD not mounted: Can't detect title file size."
filesize_label='Disc image size'
filesize_value="$(get_pretty_size $(dur "$f"))"
fi
else
filename_value="$(basename "$f")"
filesize_value="$(get_pretty_file_size "$f")"
fi
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_label: " \
-font "$fn_font" label:"$filename_value" +append \
\) \
-font "$font_heading" \
label:"$filesize_label: $filesize_value" \
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
 
local wanted_name=${OUTPUT_FILES[$FILEIDX]}
[ "$wanted_name" ] && \
if egrep -q '\.[^\.]+$' <<<"$wanted_name" ; then
output_format=$(filext "$wanted_name")
inf "Output format set from output filename"
else # No file extension in wanted_name
wanted_name="$wanted_name.$output_format"
fi
[ "$wanted_name" ] || wanted_name="$(basename "$f").$output_format"
 
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" "$wanted_name" ) || {
error "Failed to write the output file!"
return $EX_CANTCREAT
}
inf "Done. Output wrote to $output_name"
 
let 'FILEIDX++,1' #,1 so that it's always ok
[ "$UNDFLAG_HANG" ] && read -p 'Main loop paused, hit Enter key to continue... '
cleanup
 
# Re-set variables (for multi-file input)
QUIRKS=$pre_quirks
aspect_ratio=$pre_aspect_ratio
output_format="$pre_output_format"
}
 
# }}} # Core functionality
 
# {{{ # Debugging helpers
 
# Tests integrity of some operations.
# Used to test internal changes for consistency.
# It helps me to identify incorrect optimizations.
# internal_integrity_test(). Running with -D triggers this.
internal_integrity_test() {
local t op val ret comm retval=0
 
# Replacements
local SEQ=$(type -pf seq)
local JOT=$(type -pf jot)
local ex rex
if [ "$SEQ" ]; then
ex=$($SEQ 1 10)
elif [ "$JOT" ]; then
ex=$($JOT 10 1)
else
warn "Can't check seqr() correctness, neither seq nor jot found"
fi
if [ "$ex" ]; then
exr=$(seqr 1 10)
if [ "$exr" != "$ex" ]; then
error "Failed test: seqr() not consistent with external result"
let 'retval++,1'
else
inf "Passed test (seq replacement): consistent result"
fi
fi
 
# Textual tests, compare output to expected output
# Tests are in the form "operation arguments correct_result #Description"
TESTS=( # Note bash2 doesn't like this array as a local variable
# TODO: UNIX vs GNU
#"stonl ..."
 
"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"
#bc result: "pyth_th 16 9 18.35755975068581929849 #FP pythagorean theorem"
#perl result: "pyth_th 16 9 18.3575597506858 #FP pythagorean theorem"
"pyth_th 16 9 18.3576 #FP pythagorean theorem"
 
"get_interval 2h 7200 #Hours parsing"
"get_interval 2m 120 #Minutes parsing"
"get_interval 30S 30 #Seconds parsing"
"get_interval .30 .30 #Milliseconds parsing"
# Since now the numbers are passed to perl, leading zeroes become octal
# numbers. Must ensure they are handled correctly
"get_interval 09h010m09s1 33010 #Parsing with leading zeroes"
"get_interval 0400 400 #Parsing shorthand"
# Extended syntax
"get_interval 30m30m1h 7200 #Repeated minutes parsing"
)
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=$(grep -o "[^ ]* #$comm\$"<<<$t | cut -d' ' -f1)
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
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=$(grep -o "[^ ]* #$comm\$"<<<$t | cut -d' ' -f1)
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
local inff=inf
[ "$HAS_COLORS" ] && inff=infplain
$inff "Video Contact Sheet *NIX v${VERSION}, (c) 2007-2010 Toni Corvera" "sgr0"
}
 
# Prints the list of options to stdout
# show_help($1 = long = '')
show_help() {
local P=$(basename $0)
local showlong=$1
local mpchosen= ffchosen= longhelp= funkyex=
[ -z "$MPLAYER" ] && mpchosen=' [Not available]'
[ "$MPLAYER" ] && [ $decoder == $DEC_MPLAYER ] && mpchosen=' [Selected]'
[ -z "$FFMPEG" ] && ffchosen=', Not available'
[ "$FFMPEG" ] && [ $decoder == $DEC_FFMPEG ] && ffchosen=', Selected'
# This portion of help is only shown when in full help mode (--fullhelp)
[ "$showlong" ] && longhelp=\
" --anonymous Disable the 'Preview created by' line in the footer.
-Ij|-Ik|-Ij=fontname|-Ik=fontname
--nonlatin Use an alternate font in the heading for the video file
name. Required to display correctly file names in
some languages (Chinese, Japanese, Hangul,
Cyrillic, ...).
Will try to use a reasonable font. Can also be set
manually like:
$ vcs -Ij=Sazanami-Mincho-Regular file.avi
or
$ vcs -Ij=/usr/share/fonts/ttf/ttf-japanese-mincho.ttf
Use \"identify -list font\" to list the available fonts
-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\"-).
 
Tweaks and workarounds:
-Ws Increase length of safe measuring (try harder). Repeat
to increase further.
-WS Scan all video, if required, to get a safe measuring.
-Wp Increase safe measuring precission (i.e. halve the
probe stepping). Repeat to increase further.
-WP Inverse of -Wp.
-Wo Change ffmpeg's arguments order, might work with some
files that fail otherwise.
-Wc Disable colour in console messages.
Obscure options, debugging tools and workarounds:
-R <file>
--randomsource <file> Use the provided file as a source for random \"values\":
they won't be random anymore, so two runs with the same
source and same arguments will produce the same output
in modes which use using randomisation (e.g. the
\"photos\" and \"polaroid\" modes).
-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.
"
# The --funky help is really long, so make it shorter by default,
# only show the complete help when --fullhelp is used
[ "$showlong" ] && funkyex="
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.
\"polaroidframe\": Use '-kL' or '--funky polaroidframe'
Adds a polaroid picture-like white frame to each
image.
\"photos\": Use '-kc' or '--funky photos'
Combination of rotate, photoframe and overlap.
Same as -kp -kr -ko.
\"polaroid\": Use '-kp' or '--funky polaroid'
Combination of rotate, polaroidframe and overlap.
Same as -kL -kr -ko.
\"film\": Use '-ki' or '--funky film'
Imitates filmstrip look.
\"random\": Use '-kx' or '--funky random'
Randomizes colours and fonts."
[ -z "$showlong" ] && funkyex="
Available: overlap, rotate, photoframe, polaroidframe,
photos, polaroid, film, random
Use --fullhelp for more details."
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.
-o|--output <file> File name of output. When ommited will be derived from
the input filename. Can be repeated for multiple files.
-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.
-T|--title <arg> Add a title above the vidcaps.
-j|--jpeg Output in jpeg (by default output is in png).
-j2|--jpeg 2 Output in jpeg 2000
-V|--dvd <file.iso|dvd_device>
DVD Mode, use file.iso as DVD. In this mode the
<file> argument must point to the title number, e.g.:
$ vcs -V somedvd.iso 1
Passing title 0 will use the default (longest) title.
$ vcs -V /dev/dvd 0
Implies -A (auto aspect ratio)
-M|--mplayer Use Mplayer to capture$mpchosen
-F|--ffmpeg Use FFmpeg to capture [Default$ffchosen]
-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.
-q|--quiet Don't print progess messages just errors. Repeat to
mute completely even on error.
-h|--help Show basic help and exit.
--fullhelp Show the complete help and exit.
-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
* padding: use -dp or --disable padding
(note shadows introduce some extra padding)
-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.
-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.
-k <arg>
--funky <arg> Funky modes:$funkyex
$longhelp
Examples:
Create a contact sheet with default values (vidcaps at intervals of
$DEFAULT_INTERVAL seconds), will be saved to 'video.avi.png':
\$ $P video.avi
 
Create a sheet with vidcaps at intervals of 3 and a half minutes, save to
'output.jpg':
\$ $P -i 3m30 input.wmv -o output.jpg
 
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
 
#### Entry point ####
 
# Important to do this before any message can be thrown
init_feedback
 
# Ensure $GETOPT is GNU/Linux-style getopt
choose_getopt
 
# Execute exithdlr on exit
trap exithdlr EXIT
 
show_vcs_info
 
# Test requirements. Important, must check before looking at the
# command line (since getopt is used for the task)
test_programs
 
# The command-line overrides any configuration. And the configuration
# is able to change the program in charge of parsing options ($GETOPT)
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]]
# TODO: Why does FreeBSD's GNU getopt ignore -n??
TEMP=$("$GETOPT" -n "$0" -s bash \
-o i:n:u:T:f:t:S:j::hFMH:c:ma:l:De::U::qAO:I:k:W:E:d:V:R:Z:o:P: \
--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:,dvd:,randomsource:,undocumented:,output:,fullhelp,profile:,"\
"jpeg2,nonlatin" \
-- "$@")
eval set -- "$TEMP"
 
while true ; do
case "$1" in
-i|--interval)
check_interval "$2"
interval=$(get_interval "$2")
timecode_from=$TC_INTERVAL
USR_interval=$interval
USR_timecode_from=$TC_INTERVAL
shift # Option arg
;;
-n|--numcaps)
check_numcaps "$2"
numcaps="$2"
timecode_from=$TC_NUMCAPS
USR_numcaps="$2"
USR_timecode_from=$TC_NUMCAPS
shift # Option arg
;;
-o|--output)
current=${#OUTPUT_FILES[@]}
OUTPUT_FILES[$current]="$2"
shift ;;
-u|--username) user="$2" ; USR_user="$user" ; shift ;;
-U|--fullname)
# -U accepts an optional 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" ; USR_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
USR_fromtime="$fromtime"
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
USR_end_offset="$end_offset"
shift
;;
-t|--to)
if ! totime=$(get_interval "$2") ; then
error "Ending timestamp must be a valid timecode. Got '$2'."
exit $EX_USAGE
fi
if fptest "$totime" -eq 0 ; then
error "Ending timestamp was set to 0, set to movie length."
totime=-1
fi
USR_totime=$totime
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=( "${HLTIMECODES[@]}" "$temp" )
shift
;;
--jpeg2) # Note --jpeg 2 is also accepted
output_format=jp2
USR_output_format=jp2
;;
-j|--jpeg)
if [ "$2" ]; then # Arg is optional, 2 is for JPEG 2000
# 2000 is also accepted
if [ "$2" != "2" ] && [ "$2" != "2000" ]; then
error "Use -j for jpeg output or -j2 for JPEG 2000 output. Got '-j$2'."
exit $EX_USAGE
fi
output_format=jp2
else
output_format=jpg
fi
USR_output_format="$output_format"
shift
;;
-h|--help) show_help ; exit $EX_OK ;;
--fullhelp) show_help 'full' ; exit $EX_OK ;;
--shoehorn)
warn "$1 is deprecated, please use '--undocumented shoehorn=\"$2\"' instead"
shoehorned="$2"
shift
;;
-F) decoder=$DEC_FFMPEG ; USR_decoder=$decoder ;;
-M) decoder=$DEC_MPLAYER ; USR_decoder=$decoder ;;
-H|--height)
check_height "$2"
th_height="$2"
USR_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"
USR_aspect_ratio="$2"
shift
;;
-A|--autoaspect) aspect_ratio=-1 ; USR_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"
USR_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
USR_extended_factor=$extended_factor
shift
;;
--mincho)
warn "--mincho is deprecated, please use -Ij or --nonlatin instead"
if [ -z "$USR_FONT_MINCHO" ]; then
set_extended_font
inf "Filename font set to '$FONT_MINCHO'"
fi
;;
# Unlike -I, --nonlatin does not accept a font name
--nonlatin)
if [ -z "$USR_FONT_MINCHO" ]; then
set_extended_font
inf "Filename font set to '$FONT_MINCHO'"
fi
;;
-I)
# Extended/non-latin font
# New syntax introduced in 1.11:
# -Ij: Try to pick automatically a CJK font. Might fail and abort
# -Ij='Font name or file': Set font manually
 
if [ "$2" ] ; then
# If an argument is passed, test it is one of the known ones
case "$2" in
k|j|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;
if [ ${#2} -gt 1 ]; then
# j=, k= syntax
FONT_MINCHO="${2:2}"
USR_FONT_MINCHO="$FONT_MINCHO"
inf "Filename font set to '$FONT_MINCHO'"
fi
# If the user didn't pick one, try to select automatically
if [ -z "$USR_FONT_MINCHO" ]; then
set_extended_font
inf "Filename font set to '$FONT_MINCHO'"
fi
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
if grep -q 'GETOPT=' <<<"$2" ; then
# If we're here, getopt has already been found and works, so it makes no
# sense to override it; on the other hand, if it hasn't been correctly
# set/detected we won't reach here
warn "GETOPT can't be overridden from the command line."
else
override "$2" "command line"
fi
shift
;;
-W)
case "$2" in
# (classic) Workaround mode. See wa_ss_* declarations at the start for details
o) wa_ss_af='-ss ' ; wa_ss_be='' ;;
# Console colout
# Once: Disable console colour, use prefixes instead
# Twice: Disable prefixes too
c)
set_feedback_prefixes
[ "$UNDFLAG_NOPREFIX" ] && plain_messages=1
UNDFLAG_NOPREFIX=1
;;
# Double length of video probed in safe measuring
# Semi-undocumented traits:
# - Can be repeated, will double for each instance
# - -Ws -Ws -Ws = -Ws3
s|s[0-9]|s[0-9][0-9])
[ ${#2} -gt 1 ] && n=${2:1} || n=1
QUIRKS_MAX_REWIND=$(awkex "$QUIRKS_MAX_REWIND * (2^$n)")
let 'INTERNAL_WS_C+=n,1'
;;
# Brute force -Ws: Test all the length of the file if required
S) QUIRKS_MAX_REWIND=-1 ;;
# Increase precission of safe length measuring (halve the stepping)
# Like -Ws can be repeated
p|p[0-9]|p[0-9][0-9])
[ ${#2} -gt 1 ] && n=${2:1} || n=1
QUIRKS_LEN_STEP=$(awkex "$QUIRKS_LEN_STEP / (2^$n)")
let 'INTERNAL_WP_C+=n,1'
;;
# Inverse of -Wp: Decrease precission of safe length measuring
# i.e.: will try less times <-> will be quicker but less accurate
# desirable when -Ws or -WS are used.
# Can also be repeated
P|P[0-9]|P[0-9][0-9])
[ ${#2} -gt 1 ] && n=${2:1} || n=1
QUIRKS_LEN_STEP=$(awkex "$QUIRKS_LEN_STEP * (2^$n)")
let 'INTERNAL_WP_C-=n,1'
;;
# -Wb (Semi-undocumented): Disable safe mode. Use this to force accepting
#+broken/partial files. Only makes sense when testing or in combination
#+with stuff like '-Z idonly'
b) QUIRKS=-2 ;; # Quirks < 0 : No safe mode
*)
error "Wrong argument. Use --fullhelp for a list available workarounds. Got -W$2."
exit $EX_USAGE
;;
esac
shift
;;
-k|--funky) # Funky modes
case "$2" in # Note older versions (<1.0.99) were case-insensitive
p|polaroid) # Same as overlap + rotate + polaroid
inf "Changed to polaroid funky mode."
FILTERS_IND=( "${FILTERS_IND[@]}" 'filt_polaroid' 'filt_randrot' )
CSHEET_DELEGATE='csheet_overlap'
# XXX: The newer version has a lot less flexibility with these many
# hardcoded values...
grav_timestamp=South
fg_tstamps=Black
bg_tstamps=Transparent
pts_tstamps=$(( $pts_tstamps * 3 / 2 ))
;;
c|photos) # Same as overlap + rotate + photoframe, this is the older polaroid
inf "Changed to photos funky mode."
FILTERS_IND=( "${FILTERS_IND[@]}" 'filt_photoframe' 'filt_randrot' )
CSHEET_DELEGATE='csheet_overlap'
# The timestamp must change location to be visible most of the time
grav_timestamp=NorthWest
;;
o|overlap) # Random overlap mode
CSHEET_DELEGATE='csheet_overlap'
grav_timestamp=NorthWest
;;
r|rotate) # Random rotation
FILTERS_IND=( "${FILTERS_IND[@]}" 'filt_randrot' )
;;
f|photoframe) # White photo frame
FILTERS_IND=( "${FILTERS_IND[@]}" 'filt_photoframe' )
;;
L|polaroidframe) # White polaroid frame
FILTERS_IND=( "${FILTERS_IND[@]}" 'filt_polaroid ')
grav_timestamp=South
fg_tstamps=Black
bg_tstamps=Transparent
pts_tstamps=$(( $pts_tstamps * 3 / 2 ))
;;
i|film)
inf "Enabled film mode."
FILTERS_IND=( "${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
;;
-P|--profile)
case "$2" in
classic) # Classic colour scheme
bg_heading=YellowGreen bg_sign=SlateGray bg_contact=White
bg_title=White fg_heading=Black fg_sign=Black
;;
1.0) # 1.0a, 1.0.1a and 1.0.2b colourscheme
bg_heading=YellowGreen bg_sign=SandyBrown bg_contact=White
bg_title=White fg_heading=Black fg_sign=Black
;;
esac
shift
;;
-R|--randomsource)
if [ ! -r "$2" ]; then
error "Random source file '$2' can't be read"
exit $EX_USAGE
fi
init_filerand "$2"
inf "Using '$2' as source of semi-random values"
RANDFUNCTION=filerand
shift
;;
-d|--disable) # Disable default features
case $(tolower "$2") in
# timestamp (with no final s) is undocumented but will stay
t|timestamps|timestamp)
if [ $DISABLE_TIMESTAMPS -eq 0 ]; then
inf "Timestamps disabled."
# They'll be removed from the filter chain in coherence_check
DISABLE_TIMESTAMPS=1
fi
;;
s|shadows|shadow)
if [ $DISABLE_SHADOWS -eq 0 ]; then
inf "Shadows disabled."
# They will be removed from the filter chain in coherence_check
DISABLE_SHADOWS=1
fi
;;
p|padding)
if [ $HPAD -ne 0 ] ; then
inf "Padding disabled." # Kinda...
HPAD=0
fi
;;
*)
error "Requested disabling unknown feature. Got '$2'."
exit $EX_USAGE
;;
esac
shift
;;
-V|--dvd)
# XXX; Are there systems with no perl???
if ! type -pf perl >/dev/null ; then
error "DVD support requires perl"
exit $EX_UNAVAILABLE
fi
# DVD Mode requires lsdvd
if ! type -pf lsdvd >/dev/null ; then
error "DVD support requires the lsdvd program"
exit $EX_UNAVAILABLE
fi
DVD_MODE=1
DVD_FILE="$2"
decoder=$DEC_MPLAYER
aspect_ratio=-2 # Special value: Auto detect only if ffmpeg couldn't
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
;;
-Z|--undocumented)
# This is a container for, of course, undocumented functions
# These are used for testing/debugging purposes. Might (and will)
# change between versions, break easily and do no safety checks.
# In short, don't look at them unless told to do so :P
case "$2" in
# AWK was used for a little while in a WiP version
#set_awk=*) AWK="$(cut -d'=' -f2<<<"$2")" ; warn "[U] AWK=$AWK" ;;
# Hang the main process loop just before cleanup.
hang) UNDFLAG_HANG="On" ; warn "[U] Hang flag" ;;
# Print identification results, do nothing else
idonly) UNDFLAG_IDONLY="On" ; warn "[U] Id only" ;;
# ffmpeg path
set_ffmpeg=*)
FFMPEG=$(realpathr "$(cut -d'=' -f2<<<"$2")")
assert $LINENO 'test -x "$FFMPEG"'
warn "[U] FFMPEG=$FFMPEG"
;;
# mplayer path
set_mplayer=*)
MPLAYER=$(realpathr "$(cut -d'=' -f2<<<"$2")")
assert $LINENO 'test -x "$MPLAYER"'
warn "[U] MPLAYER=$MPLAYER"
;;
# Ignore one of the players
disable_ffmpeg)
FFMPEG=''
warn "FFmpeg disabled"
assert $LINENO "[ '$MPLAYER' ] || [ '$FFMPEG' ]"
decoder=$DEC_MPLAYER
;;
disable_mplayer)
MPLAYER=''
warn "Mplayer disabled"
assert $LINENO "[ '$MPLAYER' ] || [ '$FFMPEG' ]"
decoder=$DEC_FFMPEG
;;
# This is an old option from the first versions when the script
# failed a lot more, I haven't used it for years and I don't think
# anyone would need it anymore but I'll keep it at least for
# a few more versions
shoehorn=*)
shoehorned="$(cut -d'=' -f2-<<<"$2")"
;;
*) false ;;
esac
shift
;;
-D) # Repeat to just test consistency
if [ $DEBUGGED -gt 0 ]; then
[ $decoder -eq $DEC_MPLAYER ] && d='mplayer'
[ $decoder -eq $DEC_FFMPEG ] && d='ffmpeg'
infplain '[ svn $Rev$ ]'
cat >&2 <<-EOD
=== Setup ===
GETOPT: $GETOPT
MPLAYER: $MPLAYER
FFMPEG: $FFMPEG
AWK: $(realpathr $(type -pf awk))
Filterchain: [ ${FILTERS_IND[*]} ]
Decoder: $d
Safe step: $QUIRKS_LEN_STEP
=== Versions ===
Bash: $BASH_VERSION
Getopt: $($GETOPT --version)
EOD
# FIXME: Any portable way to print AWK version?
exit
fi
DEBUG=1
inf "Testing internal consistency..."
internal_integrity_test && warn "All tests passed" || error "Some tests failed!"
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
elif [ "$2" ]; then
multiple_input_files=1
fi
# }}} # Command line parsing
 
# The coherence check ensures the processed options are
# not incoherent/incompatible with the input files or with
# other given options
coherence_check || {
exit $?
}
 
set +e # Don't fail automatically. Blocks marked with {{SET_E}} will break if this changes
for arg do process "$arg" ; done
 
# Script ends here, everything below are comments
# ===========================================================================
#
# Bash syntax notes # {{{
# These are some notes for my own reference (or for those trying to read the script)
# regarding bash syntax nuissances.
#
# * herestring redirection, '<<<$string', (used extensively in vcs) was introduced in bash 2.05b
# * sed s/[ ,]/ * /g <=> ${var//[ ,]/ * } [Much faster due to not forking]
# sed s/[ ,]/ * / <=> ${var/[ ,]/ * }
# * bash2: declaring local empty arrays like 'local a=( )' makes bash think they're strings
# 'local -a' must be used instead
# bash3 has no problem with this
# * bash2: 'arr+=( elem )' for array push is not supported, use 'arr=( "${arr[@]}" elem )' instead
# += is a bash3 syntax modification, bash3.1 extended it further, arithmetic += works
# inside let
# * bash2: [*] expands as a string while [@] expands as an array. Both have trouble with spaces
# in elements though
# * performance: bash loops are often slower than awk or perl
# * performance: grep + cut proved faster than an equivalent sed -r s// replacement
# }}} # Bash syntax notes
#
# vim:set ts=4 ai foldmethod=marker nu: #
Property changes:
Added: svn:executable
Added: svn:keywords
+Rev Id Date
\ No newline at end of property
/video-contact-sheet/branches/1.11.2/pkg/Makefile
0,0 → 1,44
# $Id$
 
prefix:=/usr/local
DESTDIR:=/
VERSION:=$(shell head -50 vcs | grep 'declare -r VERSION=' | perl -pe 's/.*"(.*)".*/\1/')
PACKAGER:=$(shell echo $$DEBFULLNAME)
ifeq ($(PACKAGER),)
PACKAGER:=$(shell finger -lp `echo $USER` 2>/dev/null | head -n1 | cut -d: -f3)
endif
ifeq ($(PACKAGER),)
PACKAGER:=$(shell grep ^`id -un` /etc/passwd | cut -d: -f5 | cut -d, -f1)
endif
 
all:
# Nothing to be done
 
dist: vcs.spec PKGBUILD
 
install:
install -D -m755 vcs $(DESTDIR)$(prefix)/bin/vcs
 
uninstall:
$(RM) $(DESTDIR)$(prefix)/bin/vcs
-rmdir -p $(DESTDIR)$(prefix)/bin
 
vcs.spec: rpm/vcs.spec.in
test -f vcs -a "$(VERSION)" # Version (=$(VERSION)) must be detected
@echo "[vcs.spec]"
@cat $< | sed 's!@VERSION@!$(VERSION)!g' | \
sed 's!@PACKAGER@!$(PACKAGER)!g' > $@
 
PKGBUILD: arch/PKGBUILD.in
test -f vcs -a "$(VERSION)" # Version (=$(VERSION)) must be detected
@echo "[PKGBUILD]"
@cat $< | sed -e 's!@VERSION@!$(VERSION)!g' \
-e 's!@MD5@!'\''$(shell bzip2 -c9 < vcs | md5sum -b - | cut -d' ' -f1)'\''!g' \
-e 's!@SHA1@!'\''$(shell bzip2 -c9 < vcs | sha1sum -b - | cut -d' ' -f1)'\''!g' > $@
 
clean:
 
distclean:
-$(RM) vcs.spec PKGBUILD
 
.PHONY: all install clean
Property changes:
Added: svn:keywords
+Rev Id Date
\ No newline at end of property
/video-contact-sheet/branches/1.11.2/pkg/rpm/vcs.spec.in
0,0 → 1,97
#
# spec file for vcs rpm
#
# based on mp3plot's which in turn was based on other sources
#
 
%define is_mandrake %(test -e /etc/mandrake-release && echo 1 || echo 0)
%define is_suse %(test -e /etc/SuSE-release && echo 1 || echo 0)
%define is_fedora 0%{?fedora}
%define is_redhat 0%{?rhl}
%define is_rhel 0%{?rhel}
 
%define distname generic
%define disttag .generic
 
%if %{is_fedora}
%define distname fedora
%define disttag %{dist}
%endif
%if %{is_redhat}
%define distname redhat
%define disttag %{dist}
%endif
%if %{is_mandrake}
%define distname mandrake
%define disttag .mdk
%endif
%if %{is_suse}
%define distname suse
%define disttag .suse
%endif
%if %{is_rhel}
%define distname rhel
%define disttag %{dist}
%endif
 
Name: vcs
Summary: Tool to create contact sheets (previews) from videos
Version: @VERSION@
Release: 1%{?disttag},upstream
License: LGPL
Packager: @PACKAGER@
Group: Applications/Multimedia
Source0: http://p.outlyer.net/%{name}/files/%{name}-%{version}.tar.gz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-build
BuildArch: noarch
# TODO: How to set alternative dependencies? i.e. mplayer/ffmpeg
Requires: mplayer / ffmpeg
Requires: bash >= 2.05b
Requires: ImageMagick >= 6.3.5-7
Requires: coreutils
URL: http://p.outlyer.net/vcs/
#BuildRequires:
#Prereq: /sbin/ldconfig
#Requires:
AutoReqProv: yes
## Allow relocation (e.g. rpm --prefix /opt/vcs)
Prefix: /usr
 
%description
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.
 
%prep
#echo %_target
echo Building %{name}-%{version}-%{release}
 
%setup -q -n %{name}-%{version}
 
%build
 
%install
make DESTDIR=%buildroot prefix=/usr install
 
%clean
[ ${RPM_BUILD_ROOT} != "/" ] && rm -rf ${RPM_BUILD_ROOT}
 
#%post
# postinst
 
#%postun
# postuninst
 
%files
%defattr(-,root,root)
# binary
%{_bindir}/%{name}
# Manpage
#%{_mandir}/man1/%{name}.1.gz
%doc CHANGELOG
 
%changelog
* Sun Mar 07 2010 - outlyer (at) gmail (dot) com
- Initial RPM packaging
 
Property changes:
Added: svn:keywords
+Rev Id Date
\ No newline at end of property
/video-contact-sheet/branches/1.11.2/pkg/debian/changelog
0,0 → 1,56
vcs (1.11.2-upstream.1) experimental; urgency=low
 
* New version.
 
-- Toni Corvera <outlyer@gmail.com> Fri, 12 Mar 2010 01:36:10 +0100
 
vcs (1.11.1-upstream.1) experimental; urgency=low
 
* New version.
 
-- Toni Corvera <outlyer@gmail.com> Tue, 11 Mar 2010 00:07:28 +0100
 
vcs (1.11-upstream.1) experimental; urgency=low
 
* debian/control:
- Added min. bash version
- Rw-worded short description
- Don't Depend on bc anymore
- Remove mktemp (now coreutils) from Depends:, they're essential anyway
- Bumped min IM to 6.3.5-7
 
-- Toni Corvera <outlyer@gmail.com> Sun, 07 Mar 2010 21:47:41 +0100
 
vcs (1.0.100a-upstream.1) experimental; urgency=low
 
* New version
 
-- Toni Corvera <outlyer@gmail.com> Fri, 10 Apr 2009 17:08:33 +0200
 
vcs (1.0.99-upstream.0) experimental; urgency=low
 
* New version.
* debian/control:
- Added lsdvd as recommendation (required for dvd support)
- Using 'Homepage:'
 
-- Toni Corvera <outlyer@gmail.com> Wed, 11 Mar 2009 22:50:25 +0100
 
vcs (1.0.12-upstream.1) experimental; urgency=low
 
* debian/control: Added missing requirement (gsfonts)
 
-- Toni Corvera <outlyer@gmail.com> Mon, 06 Oct 2008 14:26:27 +0200
 
vcs (1.0.12-upstream.0) experimental; urgency=low
 
* New version.
* Added suffix to version number.
 
-- Toni Corvera <outlyer@gmail.com> Wed, 16 Apr 2008 17:59:46 +0200
 
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/branches/1.11.2/pkg/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 prefix=/usr 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/branches/1.11.2/pkg/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
Homepage: http://p.outlyer.net/vcs/
 
Package: vcs
Architecture: all
Depends: bash (>= 2.05b), imagemagick (>= 6.3.5-7), mplayer | ffmpeg
Recommends: lsdvd, ttf-dejavu-core
Description: tool to create contact sheets (previews) 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.
/video-contact-sheet/branches/1.11.2/pkg/debian/copyright
0,0 → 1,35
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'.
 
/video-contact-sheet/branches/1.11.2/pkg/debian/dirs
0,0 → 1,0
usr/bin
/video-contact-sheet/branches/1.11.2/pkg/debian/compat
0,0 → 1,0
5
/video-contact-sheet/branches/1.11.2/pkg/CHANGELOG
0,0 → 1,320
1.11.1: (2010-03-11)
* Added FLV1 codec
* BUGFIXES:
- Deprecate DEFAULT_INTERVAL, DEFAULT_NUMCAPS and DEFAULT_COLS as
overrides, warn about their new names (interval, numcaps and cols)
- Fix ImageMagick version detection
 
1.11: (2010-03-07)
* FEATURES
- Allow setting output filename. With extension will set output format,
without will inherit it.
- Allow percentages in height.
- Require mplayer OR ffmpeg instead of both. Having both is still
recommended for better results.
- Safe mode, for files whose length doesn't get reported correctly.
Completely automated.
Number of tries can be increased with -Ws. Repeat to increase further.
Use -WS to do try as many times as possible.
Accuracy (stepping) can be increased with -Wp. Repeat to increase
accuracy. Decrease with -WP.
Can be deliberately disabled with -Wb to force processing of broken
files. *VCS WITH -Wb WILL FAIL ON BROKEN FILES*
- Added -dp (--disable padding) equivalent to overriding HPAD to 0
* BUGFIXES:
- Don't pass ms to mplayer. It ignores them anyway and in some rare
cases breaks the last capture (possibly due to the 5-frames hack)
- Honor detected aspect ratio if found
- Try to detect files that might fail on the last capture and trigger
safe mode
- Timestamps font was being ignored. As a side effect this produced
italiced timestamps in some systems
- Fixed obscure bug with safe_rename_pattern overrides
* COMPAT: Support for bash 2.05b. This will (probably) be the last version
capable of running under bash 2.
* DVD mode revamp
- Print title file size instead of disc size when possible
- Aspect ratio detection, if available
- Use of FFmpeg if available to get better information
- Mostly x-platform, only ISOs identification is a bit better in Linux
* Added FourCCs: 3IV1, 3IV2 (3ivx); s263 (H.263); mp4v, MP4V, H264
(MPEG-4 and AVC in mov/mp4), VP6F (VP6 Flash Version), AMR
Video codec renamings:
- TechSmith codec name shortened to TechSmith SCC
- Raw RGB renamed to Raw video
* Help cleanup. The default help output is much shorter, the full text
can be displayed with --fullhelp. Also print the decoder choice near
the appropriate option (-M/-F)
* Added --anonymous to help (never was in it)
* Drop requirement on seq/jot and bc, replaced by inline awk
... New requirement: Perl (only for DVDs).
* Adopt new/fixed numbering scheme
<http://p.outlyer.net/dox/vcs:devel:renumbering>
* Check ImageMagick version (must decide which is the real minimum
required)
* Non-latin fonts revamp:
- -I no longer works alone (use -Ij or -Ik instead)
- -Ik, -Ij and --nonlatin try to pick an appropriate font automatically
- -I accepts a font name or font filename like
-Ij=Kochi-Mincho-Regular or
-Ij=/usr/share/fonts/truetype/kochi/kochi-mincho.ttf
* Deprecated options:
--shoehorn: Will be removed unless someone really needs it.
--mincho: Replaced by --nonlatin
* COSMETIC:
- Default font switched to DejaVu Sans.
Font sizes reduced to accomodate the new default.
Should fall back to a sane default if it's not available
- Much tighter padding
- Smaller timestamps font by default
- Print friendlier timestamp when a capture fails
- Print program signature to console without colour
- Use main font by default in timestamps
- Heading background colour toned down
- Added colourised output when tput is not capable (i.e. FreeBSD)
- Added prefixes when colour is not available for console output
- Don't print lsdvd error channel is DVD mode
- Suppress mv errors (e.g. over VFS being unable to preserve)
* Minimum ImageMagick version set to 6.3.5-7
* Better detection of requirements (e.g. disallow decoders without png
support)
* Allow overriding height, number of captures, interval, columns, and
padding
* UNDOCUMENTED/DEBUG:
- Allow stopping the main loop before cleaning up (--undocumented hang)
- Identification-only mode. Might be promoted to an actual feature
(--undocumented idonly)
- Allow setting ffmpeg and mplayer path (--undocumented set_ffmpeg and
set_mplayer)
- Allow disabling either mplayer of ffmpeg (as if they weren't
installed (--undocumented disable_ffmpeg and disable_mplayer)
- Added -Wc to disable console colour, repeat to disable prefixes
* INTERNAL:
- assert()
- Cleanup: correctness checks converted to asserts, removal of old dead
code
- Typos
 
1.0.100a: (2009-04-10) (1.10)
* FEATURE: FreeBSD (7.1-RELEASE) support
* COMPATIBILITY:
- Call bash through env
- Ensure we're using the correct getopt version
- Try to use POSIX sed options when appropriate
- Replaced incompatible sed constructs
- Use mktemp's common GNU/BSD(/POSIX?) syntax
- Use jot instead of seq if required and available
* BUGFIX: Don't fail if tput is unable to change colours
* BUGFIX: Check for requirements before anything else
* INTERNAL: Cache tput output
* FEATURE: Added -R / --randomsource. Mainly useful for debugging,
also to repeat a set of results and compare outputs on different
systems
* Corrected info message in photos mode
 
1.0.99: (2009-03-11) (1.9)
* FEATURE: Experimental support for DVDs (-V)
* FEATURE: Added JPEG 2000 output format (-j2)
* FEATURE/COSMETIC: Polaroid mode now produces a polaroid-like frame, the
older version is now renamed as simply 'photos'
New "funky" modes: newer polaroid, photos (older polaroid),
polaroidframe
* Overrideable variables: DISABLE_SHADOWS and DISABLE_TIMESTAMPS (set to 1
to disable)
* BUGFIX/COSMETIC: Re-added the missed space before filename
* BUGFIX/COSMETIC: Reworked alignment and padding
* Timestamps size is adjusted with smaller captures
* BUGFIX: Fixed polaroid/rotate bug where all images overlapped on the same
position (reported by Aleksandar Urošević, formerly unreproducible)
* Better detection of video/audio features by falling back to ffmpeg when
appropriate
 
1.0.12: (2008-04-16) (1.8)
* BUGFIX/COSMETIC: Corrected 0ms timestamps
* COSMETIC: Re-added the (disabled for long) black border after highlights
* BUGFIX/COSMETIC: Corrected the count of captures in manual-only mode (-m)
* FEATURE: Added a minimun length to use the end offset
* BUGFIX: Fixed the regression on highlights from the last version (extra
padding was being added by IM automatically)
* INTERNAL: Simplified use of IM's identify
* BUGFIX: Fixed parsing of manual timestamps including milliseconds
(when seconds didn't include the s character they were accidentally
multiplied by 10!)
 
1.0.11: (2008-04-08) (1.7)
* 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) (1.6)
* 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
get a quicker response.
 
1.0.9a: (2007-06-10) (1.5.2, -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) (1.5.1, 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) (1.5)
* 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) (1.4.1, 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) (1.4)
* 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 success 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) (1.3)
* 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) (1.2.1, Brown bag hotfix)
* BUGFIX: Don't put the full video path in the heading
 
1.0.2b: (2007-04-14) (1.2)
* 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) (1.1)
* 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) (1.0)
* 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/branches/1.11.2/pkg/arch/PKGBUILD.in
0,0 → 1,28
# Maintainer: Toni Corvera <outlyer@gmail.com>
 
pkgname=vcs
pkgver=@VERSION@
pkgrel=1.upstream
pkgdesc="tool to create contact sheets (previews) from videos"
arch=('any')
url="http://p.outlyer.net/vcs/"
license=('LGPL')
depends=('bash>=2.05b' 'imagemagick>=6.3.5.7' 'mplayer' 'ffmpeg')
makedepends=('bzip2')
optdepends=('lsdvd: for DVD support'
'perl: for DVD support')
backup=()
source=($url/files/$pkgname-$pkgver.bz2)
md5sums=(@MD5@) #generate with 'makepkg -g'
sha1sums=(@SHA1@)
# Debian & Arch don't agree on this (???) sha256sums=()
 
build() {
# cd $srcdir/$pkgname-$pkgver
# bzip2 -dc $pkgname-$pkgver.bz2 > $pkgname-$pkgver
install -D -m755 $pkgname-$pkgver ${pkgdir}/usr/bin/$pkgname || return 1
}
 
#man page (TODO)
# install -D -m644 $pkgname.1 ${pkgdir}/usr/share/man1/$pkgname.1 || return 1
 
/video-contact-sheet/branches/1.11.2/vcs
0,0 → 1,0
link pkg/vcs
Property changes:
Added: svn:special
+*
\ No newline at end of property
/video-contact-sheet/branches/1.11.2/Makefile
0,0 → 1,59
#!/usr/bin/make -f
# $Id$
 
srcdir=pkg
VER=$(shell grep VERSION $(srcdir)/vcs | head -n1 | sed 's/\#.*//' | sed -r 's/.*"(.*)".*/\1/g')
 
all:
@echo "Use $(MAKE) dist"
 
vcs-$(VER).tar.gz:
cp -rvpP pkg/ vcs-$(VER)
cd vcs-$(VER) && make dist
tar zcvf vcs-$(VER).tar.gz --exclude '.svn' --exclude '*.swp' --exclude '*.swo' vcs-$(VER)
$(RM) -r vcs-$(VER)
 
check-no-svn:
#@if [ -d .svn ]; then echo "Don't release from SVN working copy" ; false ; fi
 
check-rel:
@if head -n50 vcs | grep -q 'RELEASE=0' ; then \
echo 'RELEASE is set to 0!' ; false ; fi
 
dist: check-rel check-no-svn \
vcs-$(VER).tar.gz \
vcs-$(VER).gz vcs-$(VER).bz2 vcs-$(VER).bash \
CHANGELOG.gz CHANGELOG \
rpm deb
 
vcs-$(VER).gz: $(srcdir)/vcs
gzip -c9 < vcs > $@
 
vcs-$(VER).bz2: $(srcdir)/vcs
bzip2 -c9 < vcs > $@
 
vcs-$(VER).bash: $(srcdir)/vcs
cat $< > $@
 
CHANGELOG.gz: $(srcdir)/CHANGELOG
gzip -c9 < $< > $@
 
CHANGELOG: $(srcdir)/CHANGELOG
cp $< $@
 
distclean:
$(RM) -ri vcs Makefile *.changes pkg
 
deb:
cd pkg && debuild -us -uc -b && debclean
$(RM) vcs_*.changes vcs_*.build
 
rpm: vcs-$(VER).tar.gz
rpmbuild --clean -tb vcs-$(VER).tar.gz
test -d ~/rpmbuild/RPMS/noarch && ln -s ~/rpmbuild/RPMS/noarch/vcs-$(VER)-*.rpm . || true
test -d ~/RPM/RPMS/noarch && ln -s ~/RPM/RPMS/noarch/vcs-$(VER)-*.rpm . || true
 
clean:
-$(RM) vcs[-_]$(VER)* CHANGELOG*
 
.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/branches/1.11.2/tests/test_funkymodes
0,0 → 1,27
#!/usr/bin/env bash
 
# Allow setting from the environment
[ "$vcs" ] || vcs='vcs'
 
if [ -z "$1" ]; then
echo "Usage: $0 <file>"
exit 1
fi >&2
 
HEIGHT="-H240"
 
echo "Using vcs: $vcs" >&2
 
yes 01234 | head -n200 > randsource
 
BN=$(basename "$1")
echo ">> Standard <<" >&2
$vcs -n4 -c2 $HEIGHT "$1" -o "$BN-std.jpg"
echo ">> Polaroid <<" >&2
$vcs -n6 -c3 -k polaroid $HEIGHT "$1" -R randsource -o "$BN-polaroid.jpg"
echo ">> Photos <<" >&2
$vcs -n6 -c3 -k photos $HEIGHT "$1" -R randsource -o "$BN-photos.jpg"
echo ">> Filmstrip <<" >&2
$vcs -n8 -c2 -k film $HEIGTH "$1" -R randsource -o "$BN-film.jpg"
 
rm -f randsource
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/branches/1.11.2
Property changes:
Added: svn:mergeinfo
Merged /video-contact-sheet/branches/1.0a:r262-263
Merged /video-contact-sheet/tags/1.11:r381,385-387
Merged /video-contact-sheet/branches/1.0.100a:r364-371
Merged /video-contact-sheet/tags/1.0.12:r352-356
Merged /video-contact-sheet/tags/1.0.11:r344-345
Merged /video-contact-sheet/tags/1.0.2b:r274
Merged /video-contact-sheet/tags/1.0.8a:r319-320
Merged /video-contact-sheet/branches/1.11:r375-379,382-383
Merged /video-contact-sheet/tags/0.99a:r261
Merged /video-contact-sheet/branches/1.11.1:r389-390
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.12:r347-350
Merged /video-contact-sheet/branches/1.0.1a:r266-267
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.5b:r284-285
Merged /video-contact-sheet/branches/1.0.4b:r280-281
Merged /video-contact-sheet/branches/1.0.7a:r294-311
Merged /video-contact-sheet/branches/1.0.6b:r289-290
Merged /video-contact-sheet/branches/1.0.9a:r322-325
Merged /video-contact-sheet/branches/1.0.99:r358-361
Merged /video-contact-sheet/branches/1.0.8a:r315-317
/video-contact-sheet/tags/1.11.1/arch-package/PKGBUILD
0,0 → 1,28
# Maintainer: Toni Corvera <outlyer@gmail.com>
 
pkgname=vcs
pkgver=1.11
pkgrel=1.upstream
pkgdesc="tool to create contact sheets (previews) from videos"
arch=('any')
url="http://p.outlyer.net/vcs/"
license=('LGPL')
depends=('bash>=2.05b' 'imagemagick>=6.3.5.7' 'mplayer' 'ffmpeg')
makedepends=('bzip2')
optdepends=('lsdvd: for DVD support'
'perl: for DVD support')
backup=()
source=($url/files/$pkgname-$pkgver.bz2)
md5sums=('c0cc4152278d93836bfc920e9e7a1078') #generate with 'makepkg -g'
sha1sums=('f08f07ba655355942a36a8ee695d88c86965a471')
sha256sums=('056233236a3ed06249af652e5d11e843586d025974b131c173b38a06bd8f2bbd')
 
build() {
# cd $srcdir/$pkgname-$pkgver
# bzip2 -dc $pkgname-$pkgver.bz2 > $pkgname-$pkgver
install -D -m755 $pkgname-$pkgver ${pkgdir}/usr/bin/$pkgname || return 1
}
 
#man page (TODO)
# install -D -m644 $pkgname.1 ${pkgdir}/usr/share/man1/$pkgname.1 || return 1
 
/video-contact-sheet/tags/1.11.1/CHANGELOG
0,0 → 1,320
1.11.1: (2010-03-11)
* Added FLV1 codec
* BUGFIXES:
- Deprecate DEFAULT_INTERVAL, DEFAULT_NUMCAPS and DEFAULT_COLS as
overrides, warn about their new names (interval, numcaps and cols)
- Fix ImageMagick version detection
 
1.11: (2010-03-07)
* FEATURES
- Allow setting output filename. With extension will set output format,
without will inherit it.
- Allow percentages in height.
- Require mplayer OR ffmpeg instead of both. Having both is still
recommended for better results.
- Safe mode, for files whose length doesn't get reported correctly.
Completely automated.
Number of tries can be increased with -Ws. Repeat to increase further.
Use -WS to do try as many times as possible.
Accuracy (stepping) can be increased with -Wp. Repeat to increase
accuracy. Decrease with -WP.
Can be deliberately disabled with -Wb to force processing of broken
files. *VCS WITH -Wb WILL FAIL ON BROKEN FILES*
- Added -dp (--disable padding) equivalent to overriding HPAD to 0
* BUGFIXES:
- Don't pass ms to mplayer. It ignores them anyway and in some rare
cases breaks the last capture (possibly due to the 5-frames hack)
- Honor detected aspect ratio if found
- Try to detect files that might fail on the last capture and trigger
safe mode
- Timestamps font was being ignored. As a side effect this produced
italiced timestamps in some systems
- Fixed obscure bug with safe_rename_pattern overrides
* COMPAT: Support for bash 2.05b. This will (probably) be the last version
capable of running under bash 2.
* DVD mode revamp
- Print title file size instead of disc size when possible
- Aspect ratio detection, if available
- Use of FFmpeg if available to get better information
- Mostly x-platform, only ISOs identification is a bit better in Linux
* Added FourCCs: 3IV1, 3IV2 (3ivx); s263 (H.263); mp4v, MP4V, H264
(MPEG-4 and AVC in mov/mp4), VP6F (VP6 Flash Version), AMR
Video codec renamings:
- TechSmith codec name shortened to TechSmith SCC
- Raw RGB renamed to Raw video
* Help cleanup. The default help output is much shorter, the full text
can be displayed with --fullhelp. Also print the decoder choice near
the appropriate option (-M/-F)
* Added --anonymous to help (never was in it)
* Drop requirement on seq/jot and bc, replaced by inline awk
... New requirement: Perl (only for DVDs).
* Adopt new/fixed numbering scheme
<http://p.outlyer.net/dox/vcs:devel:renumbering>
* Check ImageMagick version (must decide which is the real minimum
required)
* Non-latin fonts revamp:
- -I no longer works alone (use -Ij or -Ik instead)
- -Ik, -Ij and --nonlatin try to pick an appropriate font automatically
- -I accepts a font name or font filename like
-Ij=Kochi-Mincho-Regular or
-Ij=/usr/share/fonts/truetype/kochi/kochi-mincho.ttf
* Deprecated options:
--shoehorn: Will be removed unless someone really needs it.
--mincho: Replaced by --nonlatin
* COSMETIC:
- Default font switched to DejaVu Sans.
Font sizes reduced to accomodate the new default.
Should fall back to a sane default if it's not available
- Much tighter padding
- Smaller timestamps font by default
- Print friendlier timestamp when a capture fails
- Print program signature to console without colour
- Use main font by default in timestamps
- Heading background colour toned down
- Added colourised output when tput is not capable (i.e. FreeBSD)
- Added prefixes when colour is not available for console output
- Don't print lsdvd error channel is DVD mode
- Suppress mv errors (e.g. over VFS being unable to preserve)
* Minimum ImageMagick version set to 6.3.5-7
* Better detection of requirements (e.g. disallow decoders without png
support)
* Allow overriding height, number of captures, interval, columns, and
padding
* UNDOCUMENTED/DEBUG:
- Allow stopping the main loop before cleaning up (--undocumented hang)
- Identification-only mode. Might be promoted to an actual feature
(--undocumented idonly)
- Allow setting ffmpeg and mplayer path (--undocumented set_ffmpeg and
set_mplayer)
- Allow disabling either mplayer of ffmpeg (as if they weren't
installed (--undocumented disable_ffmpeg and disable_mplayer)
- Added -Wc to disable console colour, repeat to disable prefixes
* INTERNAL:
- assert()
- Cleanup: correctness checks converted to asserts, removal of old dead
code
- Typos
 
1.0.100a: (2009-04-10) (1.10)
* FEATURE: FreeBSD (7.1-RELEASE) support
* COMPATIBILITY:
- Call bash through env
- Ensure we're using the correct getopt version
- Try to use POSIX sed options when appropriate
- Replaced incompatible sed constructs
- Use mktemp's common GNU/BSD(/POSIX?) syntax
- Use jot instead of seq if required and available
* BUGFIX: Don't fail if tput is unable to change colours
* BUGFIX: Check for requirements before anything else
* INTERNAL: Cache tput output
* FEATURE: Added -R / --randomsource. Mainly useful for debugging,
also to repeat a set of results and compare outputs on different
systems
* Corrected info message in photos mode
 
1.0.99: (2009-03-11) (1.9)
* FEATURE: Experimental support for DVDs (-V)
* FEATURE: Added JPEG 2000 output format (-j2)
* FEATURE/COSMETIC: Polaroid mode now produces a polaroid-like frame, the
older version is now renamed as simply 'photos'
New "funky" modes: newer polaroid, photos (older polaroid),
polaroidframe
* Overrideable variables: DISABLE_SHADOWS and DISABLE_TIMESTAMPS (set to 1
to disable)
* BUGFIX/COSMETIC: Re-added the missed space before filename
* BUGFIX/COSMETIC: Reworked alignment and padding
* Timestamps size is adjusted with smaller captures
* BUGFIX: Fixed polaroid/rotate bug where all images overlapped on the same
position (reported by Aleksandar Urošević, formerly unreproducible)
* Better detection of video/audio features by falling back to ffmpeg when
appropriate
 
1.0.12: (2008-04-16) (1.8)
* BUGFIX/COSMETIC: Corrected 0ms timestamps
* COSMETIC: Re-added the (disabled for long) black border after highlights
* BUGFIX/COSMETIC: Corrected the count of captures in manual-only mode (-m)
* FEATURE: Added a minimun length to use the end offset
* BUGFIX: Fixed the regression on highlights from the last version (extra
padding was being added by IM automatically)
* INTERNAL: Simplified use of IM's identify
* BUGFIX: Fixed parsing of manual timestamps including milliseconds
(when seconds didn't include the s character they were accidentally
multiplied by 10!)
 
1.0.11: (2008-04-08) (1.7)
* 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) (1.6)
* 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
get a quicker response.
 
1.0.9a: (2007-06-10) (1.5.2, -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) (1.5.1, 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) (1.5)
* 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) (1.4.1, 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) (1.4)
* 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 success 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) (1.3)
* 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) (1.2.1, Brown bag hotfix)
* BUGFIX: Don't put the full video path in the heading
 
1.0.2b: (2007-04-14) (1.2)
* 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) (1.1)
* 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) (1.0)
* 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.11.1/debian-package/debian/changelog
0,0 → 1,50
vcs (1.11.1-upstream.1) experimental; urgency=low
 
* New version.
 
-- Toni Corvera <outlyer@gmail.com> Tue, 11 Mar 2010 00:07:28 +0100
 
vcs (1.11-upstream.1) experimental; urgency=low
 
* debian/control:
- Added min. bash version
- Rw-worded short description
- Don't Depend on bc anymore
- Remove mktemp (now coreutils) from Depends:, they're essential anyway
- Bumped min IM to 6.3.5-7
 
-- Toni Corvera <outlyer@gmail.com> Sun, 07 Mar 2010 21:47:41 +0100
 
vcs (1.0.100a-upstream.1) experimental; urgency=low
 
* New version
 
-- Toni Corvera <outlyer@gmail.com> Fri, 10 Apr 2009 17:08:33 +0200
 
vcs (1.0.99-upstream.0) experimental; urgency=low
 
* New version.
* debian/control:
- Added lsdvd as recommendation (required for dvd support)
- Using 'Homepage:'
 
-- Toni Corvera <outlyer@gmail.com> Wed, 11 Mar 2009 22:50:25 +0100
 
vcs (1.0.12-upstream.1) experimental; urgency=low
 
* debian/control: Added missing requirement (gsfonts)
 
-- Toni Corvera <outlyer@gmail.com> Mon, 06 Oct 2008 14:26:27 +0200
 
vcs (1.0.12-upstream.0) experimental; urgency=low
 
* New version.
* Added suffix to version number.
 
-- Toni Corvera <outlyer@gmail.com> Wed, 16 Apr 2008 17:59:46 +0200
 
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.11.1/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
Homepage: http://p.outlyer.net/vcs/
 
Package: vcs
Architecture: all
Depends: bash (>= 2.05b), imagemagick (>= 6.3.5-7), mplayer | ffmpeg
Recommends: lsdvd, ttf-dejavu-core
Description: tool to create contact sheets (previews) 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.
/video-contact-sheet/tags/1.11.1/debian-package/debian/copyright
0,0 → 1,35
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'.
 
/video-contact-sheet/tags/1.11.1/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.11.1/debian-package/debian/dirs
0,0 → 1,0
usr/bin
/video-contact-sheet/tags/1.11.1/debian-package/debian/compat
0,0 → 1,0
5
/video-contact-sheet/tags/1.11.1/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.11.1/vcs
0,0 → 1,4053
#!/usr/bin/env bash
#
# $Rev$ $Date$
#
# vcs
# Video Contact Sheet *NIX: Generates contact sheets (previews) of videos
#
# Copyright (C) 2007, 2008, 2009, 2010 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>
#
# (Note: The references that used to be here have been moved to
#+ <http://p.outlyer.net/dox/vcs:devel:references>)
 
declare -r VERSION="1.11.1"
declare -r RELEASE=1
 
# {{{ # CHANGELOG
# Last release changes:
# (The full changelog can be found at <http://p.outlyer.net/vcs/files/CHANGELOG>).
#
# 1.11.1:
# * Added FLV1 codec
# * BUGFIXES:
# - Deprecate DEFAULT_INTERVAL, DEFAULT_NUMCAPS and DEFAULT_COLS as
# overrides, warn about their new names (interval, numcaps and cols)
# - Fix ImageMagick version detection
# 1.11:
# * FEATURES
# - Allow setting output filename. With extension will set output format,
# without will inherit it.
# - Allow percentages in height.
# - Require mplayer OR ffmpeg instead of both. Having both is still
# recommended for better results.
# - Safe mode, for files whose length doesn't get reported correctly.
# Completely automated.
# Number of tries can be increased with -Ws. Repeat to increase further.
# Use -WS to do try as many times as possible.
# Accuracy (stepping) can be increased with -Wp. Repeat to increase
# accuracy. Decrease with -WP.
# Can be deliberately disabled with -Wb to force processing of broken
# files. *VCS WITH -Wb WILL FAIL ON BROKEN FILES*
# - Added -dp (--disable padding) equivalent to overriding HPAD to 0
# * BUGFIXES:
# - Don't pass ms to mplayer. It ignores them anyway and in some rare
# cases breaks the last capture (possibly due to the 5-frames hack)
# - Honor detected aspect ratio if found
# - Try to detect files that might fail on the last capture and trigger
# safe mode
# - Timestamps font was being ignored. As a side effect this produced
# italiced timestamps in some systems
# - Fixed obscure bug with safe_rename_pattern overrides
# * COMPAT: Support for bash 2.05b. This will (probably) be the last version
# capable of running under bash 2.
# * DVD mode revamp
# - Print title file size instead of disc size when possible
# - Aspect ratio detection, if available
# - Use of FFmpeg if available to get better information
# - Mostly x-platform, only ISOs identification is a bit better in Linux
# * Added FourCCs: 3IV1, 3IV2 (3ivx); s263 (H.263); mp4v, MP4V, H264
# (MPEG-4 and AVC in mov/mp4), VP6F (VP6 Flash Version), AMR
# Video codec renamings:
# - TechSmith codec name shortened to TechSmith SCC
# - Raw RGB renamed to Raw video
# * Help cleanup. The default help output is much shorter, the full text
# can be displayed with --fullhelp. Also print the decoder choice near
# the appropriate option (-M/-F)
# * Added --anonymous to help (never was in it)
# * Drop requirement on seq/jot and bc, replaced by inline awk
# ... New requirement: Perl (only for DVDs).
# * Adopt new/fixed numbering scheme
# <http://p.outlyer.net/dox/vcs:devel:renumbering>
# * Check ImageMagick version (must decide which is the real minimum
# required)
# * Non-latin fonts revamp:
# - -I no longer works alone (use -Ij or -Ik instead)
# - -Ik, -Ij and --nonlatin try to pick an appropriate font automatically
# - -I accepts a font name or font filename like
# -Ij=Kochi-Mincho-Regular or
# -Ij=/usr/share/fonts/truetype/kochi/kochi-mincho.ttf
# * Deprecated options:
# --shoehorn: Will be removed unless someone really needs it.
# --mincho: Replaced by --nonlatin
# * COSMETIC:
# - Default font switched to DejaVu Sans.
# Font sizes reduced to accomodate the new default.
# Should fall back to a sane default if it's not available
# - Much tighter padding
# - Smaller timestamps font by default
# - Print friendlier timestamp when a capture fails
# - Print program signature to console without colour
# - Use main font by default in timestamps
# - Heading background colour toned down
# - Added colourised output when tput is not capable (i.e. FreeBSD)
# - Added prefixes when colour is not available for console output
# - Don't print lsdvd error channel is DVD mode
# - Suppress mv errors (e.g. over VFS being unable to preserve)
# * Minimum ImageMagick version set to 6.3.5-7
# * Better detection of requirements (e.g. disallow decoders without png
# support)
# * Allow overriding height, number of captures, interval, columns, and
# padding
# * UNDOCUMENTED/DEBUG:
# - Allow stopping the main loop before cleaning up (--undocumented hang)
# - Identification-only mode. Might be promoted to an actual feature
# (--undocumented idonly)
# - Allow setting ffmpeg and mplayer path (--undocumented set_ffmpeg and
# set_mplayer)
# - Allow disabling either mplayer of ffmpeg (as if they weren't
# installed (--undocumented disable_ffmpeg and disable_mplayer)
# - Added -Wc to disable console colour, repeat to disable prefixes
# * INTERNAL:
# - assert()
# - Cleanup: correctness checks converted to asserts, removal of old dead
# code
# - Typos
#
# }}} # CHANGELOG
 
set -e
 
# Fail soon if this version of bash is too old for the syntax, don't expose bash to the newer
# syntax
# See the "Bash syntax notes" section for details
[ "$BASH_VERSINFO" ] && {
# Absolute minimum right now is 2.05b
if [ "${BASH_VERSINFO[0]}" -le 2 ]; then # Only bash <=2 needs extra testing
# I guess we can't expect any new bash2 release
if [ "${BASH_VERSINFO[0]}" -lt 2 ] ||
( [ "${BASH_VERSINFO[0]}" -eq 2 ] && [ "${BASH_VERSINFO[1]}" != '05b' ] ); then
echo "Bash 2.05b or higher is required" >&2
exit 1
fi
fi
}
 
# {{{ # TO-DO
# TODO / FIXME:
# * (1.12) Start replacing 'grep' with bash's '[[ =~ ]]'. Will break bash 2 compatibility for good
# * [[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. =>
#+ SUS v2: egrep is deprecated, grep -E replaces it
# * Change default DVD_TITLE to 0
# * Deprecations:
# OPTION/VAR -> ALTERNATIVE DEPRECATED FROM VERSION REMOVAL ETA
# --shoehorn -> --undocumented shoehorn 1.11 1.12
# --undocumented shoehorn -> NONE 1.12 1.13 or 1.14
# --funky -> --profile ? ?+1
# MIN_LENGTH_FOR_END_OFFSET -> none 1.11 1.12 or 1.13
# * No warning shown in 1.11. 1.12 will switch back default end offset either to 0 or a
# percentage *and* use it always (disable with -E0)
# end offset -> none ? ?+1
# --mincho -> --nonlatin 1.11 1.12
# * Better check for coherence of overrides
# * Variables cleanup:
# Variables will use a more uniform scheme, with prefixes where appropriate:
# - INTERNAL_*: Used internally to adapt messages and the like to the input
# - UNDFLAG_*: Undocumented flags. Used internally to keep track of undocumented modes (-Z)
# - USR_*: Holds values of variables as set by the user, either from overrides or from the
# command-line
# }}} # TO-DO
 
# {{{ # 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 config, most precedence
#
# The variables that can be overriden are below the block of constants ahead.
declare -r CFGFILE=~/.vcs.conf
 
# Default values, use interval, numcaps and cols to override
declare -ri DEFAULT_INTERVAL=300
declare -ri DEFAULT_NUMCAPS=16
declare -ri DEFAULT_COLS=2
 
# 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 ASPECT=8 VCNAME=9 ACNAME=10
# 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
# GETOPT must be correctly set or the script will fail.
# It can be set in the configuration files if it isn't in the path or
# the first getopt in the path isn't the right version.
# A check will be made and a warning with details shown if required.
declare GETOPT=getopt
# Set to 1 to print function calls
declare -i DEBUG=0
# 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='#afcd7a' # Background for meta info (size, codec...)
declare bg_sign=SlateGray #'#a2a9af' # Background for signature
declare bg_title=White # Background for the title (see -T)
declare bg_contact=White # Background for the captures
declare bg_tstamps='#000000aa' # Background for the timestamps box
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 for the title
# Fonts, use identify -list font to get the list, up to IM 6.3.5-7 was '-list type' [[IM1]]
# If a font is not available IM will pick a sane default. In theory it will be silent
# although in practica it prints an error
declare font_tstamps=DejaVu-Sans-Book # Used for timestamps over the thumbnails
declare font_heading=DejaVu-Sans-Book # Used for the meta info heading
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 for the title (see -T)
# Font sizes, in points
declare -i pts_tstamps=14 # Used for the timestamps
declare -i pts_meta=14 # Used for the meta info heading
declare -i pts_sign=10 # Used for the signature
declare -i pts_title=33 # Used for the title (see -T)
# See --shoehorn
declare shoehorned=
# See -E / $end_offset
declare -i DEFAULT_END_OFFSET=60
# If the video is less than this length, end offset won't be used at all
declare MIN_LENGTH_FOR_END_OFFSET=19m30s
# 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
# Verbosity level so far from the command line can only be muted (see -q)
# it can be overridden, though
declare -i verbosity=$V_ALL
# Set to 1 to disable colours in console output
declare -i plain_messages=0
# See coherence_check for more details
declare -i DISABLE_SHADOWS=0
declare -i DISABLE_TIMESTAMPS=0
 
# Introduced in 1.0.7b, revamped in 1.11:
# This font is used to display international names (i.e. CJK names) correctly
# Help from users actually needing this would be appreciated :)
# This variable is filled either automatically through the set_extended_font()
#+function (and option -Ij) or manually (with option -Ij=MyFontName)
# The automatic picks a semi-random one from the fonts believed to support CJK/Cyrillic
#+characters.
declare FONT_MINCHO= # Filename or font name as known to ImageMagick (identify -list font)
# Output of capturing programs is redirected here
declare stdout=/dev/null stderr=/dev/null
declare -i DVD_MODE=0 DVD_TITLE=1
declare DVD_FILE=
 
# Override-able since 1.11:
# Height of the thumbnails, by default use same as input
declare th_height= # *WILL CHANGE NAME*
declare interval=$DEFAULT_INTERVAL # Interval of captures (=numsecs/numcaps)
declare -i numcaps=$DEFAULT_NUMCAPS # Number of captures (=numsecs/interval)
# This is the horizontal padding added to each capture.
# Beware when changing this since extended set's alignment might break.
# When shadows are enabled this is ignored since they already add padding.
declare -i HPAD=2 # *WILL CHANGE NAME*
declare -i cols=$DEFAULT_COLS # Number of output columns
 
# }}} # End of override-able variables
 
# {{{ # Variables
 
# Options and other internal usage variables, no need to mess with this!
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 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
# Identification output from ffmpeg and mplayer for the current video
declare FFMPEG_CACHE=
declare MPLAYER_CACHE=
# This holds the parsed identification values, see also the Indexes in VID
# (defined in the constants block)
declare -a VID=( )
 
# These variables will hold the output of tput, used
# to colourise feedback
declare prefix_err= prefix_inf= prefix_warn= suffix_fback=
 
# 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
 
# Transformations/filters
# Operations are decomposed into independent optional steps, this allows
# to add some intermediate steps (e.g. polaroid/photo mode's frames)
# 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 (currently deprecated)
# * 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, [context, [index]] )
# They're executed in order by filter_vidcap()
declare -a FILTERS_IND=( 'filt_resize' 'filt_apply_stamp' 'filt_softshadow' )
# Deprecated: 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
 
# Sets which function is used to obtain random numbers valid values are
# bashrand and filerand.
# Setting it manually will break it, calling with -R changes this to filerand.
# See rand() for an explanation
declare RANDFUNCTION=bashrand
 
# Which file are we working on (i.e. how many times has process() been called)
declare -i FILEIDX=0
 
# Names for output files, each index is a file name, an empty index will use
# the input file and append an extension to it
declare -a OUTPUT_FILES=( )
 
# Mplayer and FFmpeg binaries. Will be detected.
# Don't set manually, if you need to override set the path temporarily, e.g.:
# $ env PATH=/whatever:$PATH vcs ...
# or use the undocumented (and unchecked!) appropriate option:
# $ vcs --undocumented set_ffmpeg=/mypath/ffmpeg
declare MPLAYER=
declare FFMPEG=
 
# When set to 1 the reported length by mplayer and ffmpeg won't be trusted
# and will trigger some custom tests.
# Enabled automatically on problematic files
declare -i QUIRKS=0
# If the reported lengths differ by at least this much QUIRKS will be enabled
declare QUIRKS_LEN_THRESHOLD=0.2
# When trying to determine the correct length, file will be probed each...:
declare QUIRKS_LEN_STEP=0.5 # ~ 10 frames @ 20fps
# Maximum number of seconds to "rewind" from reported length (after this
# vcs surrenders but processing continues with a rewinded length)
declare QUIRKS_MAX_REWIND=20
 
# Set when the console output will be in color. It doesn't control color!
declare HAS_COLORS=
 
declare -i multiple_input_files=0
 
# Internal counts, used only to adjust messages
declare -i INTERNAL_WS_C=0 # -Ws count
declare -i INTERNAL_WP_C=0 # -Wp count
declare -i INTERNAL_MAXREWIND_REACHED=0 # More -Ws in the command-line won't help
 
# }}} # 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'
'MIN_LENGTH_FOR_END_OFFSET'
'DEBUG'
'DISABLE_.*'
'th_height'
'interval'
'numcaps'
'HPAD'
'cols'
# Note GETOPT doesn't make sense to be overridden from the command-line
'GETOPT'
)
 
# 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 -a 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
 
end_offset=$DEFAULT_END_OFFSET
}
 
# 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=${ALLOWED_OVERRIDES[*]}
compregex=${compregex// /|} # = s ' ' => '|'
 
# Don't allow ';', FIXME: dunno how secure that really is...
# FIXME: ...it doesn't really work 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=$(egrep -o '^[[:space:]]*[a-zA-Z0-9_]*=.' <<<"$o" | cut -d'=' -f1 | egrep -o '[^ ]*')
local varval=$(egrep -o '^[^=]*=.*' <<<"$o" | cut -d'=' -f2-)
if [ "$varname" = "DEFAULT_INTERVAL" ]; then
warn '$DEFAULT_INTERVAL is deprecated, please use $interval instead'
varname=interval
elif [ "$varname" = "DEFAULT_NUMCAPS" ]; then
warn '$DEFAULT_NUMCAPS is deprecated, please use $numcaps instead'
varname=numcaps
elif [ "$varname" = "DEFAULT_COLS" ]; then
warn '$DEFAULT_COLS is deprecated, please use $cols instead'
varname=cols
fi
# FIXME: Security!
local curvarval=
eval curvarval='$'"$varname"
if [ "$curvarval" == "$varval" ]; then
warn "Ignored override '$varname' (already had same value)"
else
eval "$varname=\"$varval\""
eval "USR_$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 is a valid percentage (xx% or xx.yy%)
# is_percentage($1 = input)
is_percentage() {
egrep -q '^([0-9]+\.?([0-9])?+|(\.[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() {
awkex "int(${*//[ ,]/ * }+0.5)" # ' ' = ',' => '*'
}
 
# Like rmultiply() but always rounded upwards
ceilmultiply() {
# TODO: breaks with $@. Why?
awkex "int(${*//[ ,]/ * }+0.99999)" # ' ' = ',' => '*'
}
 
# Basic mathematic stuff
# min($1 = operand1, $2 = operand2)
min() { awk "BEGIN { if (($1) < ($2)) print ($1) ; else print ($2) }" ; }
max() { awk "BEGIN { if (($1) > ($2)) print ($1) ; else print ($2) }" ; }
abs() { awk "BEGIN { if (($1) < (0)) print (($1) * -1) ; else print ($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='!=' ;;
*) assert $LINENO "[ \"'$1' '$2' '$3'\" ] && false" && return $EX_SOFTWARE
esac
# Empty operands
if [ -z "$1" ] || [ -z "$3" ]; then
assert $LINENO "[ \"'$1' '$2' '$3'\" ] && false"
else
awk "BEGIN { if ($1 $op $3) exit 0 ; else exit 1 }"
fi
}
 
# Keep a number of decimals *rounded*
keepdecimals() {
local N="$1" D="$2"
awk "BEGIN { printf \"%.${D}f\", (($N)+0) }"
}
 
# Keep a number of decimals, last decimal rounded to lower
keepdecimals_lower() {
grep -q '\.' <<<"$1" || { echo "$1" ; return ; }
local D=${1/#*.} # Decimals only
echo ${1/%.*}.${D:0:$2} # Integer part + . + Number of decimals
}
 
# Evaluate in AWK. Intended for arithmetic operations.
#+Keep decimals. I.e. 5 = 5.000000...
# awkex($1 = expression)
awkexf() {
# By default awk prints in compact form (scientific notation and/or up to 6 digits/decimals),
# printf is used to avoid this, TODO: Is there any direct way?
# .%20f is clearly overkill but matches the all code (default bc -l)
awk "BEGIN { printf \"%.20f\", ($1)+0 }"
}
 
# Evaluate in AWK. Intended for arithmetic operations.
#+Use default output. I.e. 5 = 5
# awkex($1 = expression)
awkex() {
awk "BEGIN { print ($1)+0 }"
}
 
# converts spaces to newlines in a x-platform way [[FNL]]
# stonl($1 = string)
stonl() {
awk '{gsub(" ", "\n");print}' <<<"$1" | egrep -v '^$'
}
 
# Converts newlines to spaces portably
# nltos($1 = string)
nltos() {
awk '{printf "%s ",$0}' <<<"$1" | sed 's/ *//'
}
 
# bash version of ord() [[ORD]]
# prints the ASCII value of a character
ord() {
printf '%d' "'$1"
}
 
# Get file extension
filext() {
grep -o '\.[^.]*$' <<<"$1" | cut -d. -f2
}
 
# Wrapper around $RANDOM, not called directly, wrapped again in rand().
# See rand() for an explanation.
bashrand() {
echo $RANDOM
}
 
# Prepares for "filerand()" calls
# File descriptor 7 is used to keep a file open, from which data is read
# and then transformed into a number.
# init_filerand($1 = filename)
init_filerand() { # [[FD1]], [[FD2]]
test -r "$1"
exec 7<"$1"
# closed in exithdlr
}
 
# Produce a (not-really-)random number from a file, not called directly wrapped
# in rand()
# Note that once the file end is reached, the random values will always
# be the same (hash_string result for an empty string)
filerand() {
local b=
# "read 5 bytes from file descriptor 7 and put them in $b"
read -n5 -u7 b
hash_string "$b"
}
 
# Produce a random number
# $RANDFUNCTION defines wich one to use (bashrand or filerand).
# Since functions using random values are most often run in subshells
# setting $RANDOM to a given seed has not the desired effect.
# filerand() is used to that effect; it keeps a file open from which bytes
# are read and not-so-random values generated; since file descriptors are
# inherited, subshells will "advance" the random sequence.
# Argument -R enables the filerand() function
rand() {
$RANDFUNCTION
}
 
# produces a numeric value from a string
hash_string() {
local HASH_LIMIT=65536
local v="$1"
local -i hv=15031
local c=
if [ "$v" ]; then
for i in $(seqr 0 ${#v} ); do
c=$( ord ${v:$i:1} )
hv=$(( ( ( $hv << 1 ) + $c ) % $HASH_LIMIT ))
done
fi
echo $hv
}
 
# Applies the Pythagorean Theorem
# pyth_th($1 = cathetus1, $2 = cathetus2)
pyth_th() {
awkex "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() {
trace $FUNCNAME $@
# eval it even if it's numeric to strip leading zeroes. Note the quoting
if is_number "$1" ; then awkex "\"$1\"" ; return 0 ; fi
 
local s=$(tolower "$1") t r n
 
# Only allowed characters
if ! egrep -qi '^[0-9smh.]+$' <<<"$s"; then
return $EX_USAGE;
fi
 
# Two consecutive dots are no longer accepted
if egrep -q '\.\.' <<<"$s" ; then
return $EX_USAGE
fi
 
# Newer parsing code: replaces units by a product
# and feeds the resulting string to awk for evaluation
# Note leading zeroes will lead awk to believe they are octal numbers
# as a quick and dirty fix I'm just wrapping them in quotes, forcing awk
# to re-evaluate them, which appears to be enough to make them decimal.
# This is the only place where leading zeroes have no meaning.
 
# Split into lines of time + unit:
t=
for item in $(echo "$s" | grep -o '[0-9]*[hms]') ;do
n="\"$(echo $item | grep -o '[0-9]*')\"" # Number, quoted
t=$t$n$(echo $item | grep -o '[hms]') # + Number + Unit
done
# Split all ms or s.ms
for item in $(echo "$s" | grep -o '[0-9]*\.[0-9]*') ;do
t="${t}\"$item\" + "
done
# Seconds without unit. They must be preceded by h, m or s at this point
local secs=$(echo $s | egrep -o '.?[0-9]*$')
# When preceded by '.', they're ms
[ "$secs" ] && grep -q '\.'<<<"$secs" && secs=
# Quote and addition. Note BSD grep/egrep wants the anchor ($) or won't match
[ "$secs" ] && secs=" \"$(egrep -o '[0-9]*$'<<<"$secs")\" + "
t=${t//h/ * 3600 + }
t=${t//m/ * 60 + }
t=${t//s/ + }
t="$t$secs"
t=${t/% + /} # Strip empty addition
r=$(awkex "$t" 2>/dev/null)
 
# Negative and empty intervals
assert $LINENO "[ '$r' ] && [ '$t' ]"
assert $LINENO "fptest $r -gt 0"
 
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 -format '%w' "$1"
}
 
# Get Image Height
# imh($1 = file)
imh() {
identify -format '%h' "$1"
}
 
# Prints a number of seconds in a more human readable form
# e.g.: 3600 becomes 1:00:00
# pretty_stamp($1 = seconds)
pretty_stamp() {
assert $LINENO "is_float '$1'"
# Fully implemented in AWK to discard bc.
# As a bonus now it's much faster and compact
awk "BEGIN {
t=$1 ; NOTMS=($DEC_MPLAYER==$decoder);
MS=(t - int(t));
h=int(t / 3600);
t=(t % 3600);
m=int(t / 60);
t=(t % 60);
s=t
if (h != 0) h=h\":\" ; else h=\"\"
if (NOTMS!=1) ms=sprintf(\".%02d\", int(MS*100+0.5));
printf \"%s%02d:%02d%s\", h, m, s, ms
}"
# Note the rounding applied to $MS, it is required to match the precission passed on
# to ffmpeg
}
 
# Prints a given size in human friendly form
get_pretty_size() {
local bytes=$1
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
}
 
# 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_file_size($1 = file)
get_pretty_file_size() {
local f="$1"
local bytes=$(get_file_size "$f")
 
get_pretty_size "$bytes"
}
 
# Clean $safe_rename_pattern, which is override-able
# Since safe_rename() is called from $() it won't be able to affect global variables directly
sanitise_rename_pattern() {
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
# Hashmarks will break the regex used in safe_rename()
if grep -q '#' <<<"$safe_rename_pattern" ; then
warn "Illegal character \"#\" found in safe renaming pattern, resetting it"
safe_rename_pattern=$DEFAULT_SAFE_REN_PATT
fi
}
 
# mv quiet
# Move a file, be quiet about errors.
# Ownership preservation is a common error on vfs, for example
mvq() {
mv -- "$@" 2>/dev/null
}
 
# 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() {
trace $FUNCNAME $@
local from="$1"
local to="$2"
 
# Output extension
local ext=$(filext "$to")
# Output filename without extension
local b=${to%.$ext}
 
local n=1
while [ -f "$to" ]; do # Only executes if $2 exists
# Bash 2 and Bash 3 behave differently with substring replacement (${//}) and '%'
# Sed is a safer bet
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
 
mvq "$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...
# Neither busybox's nor BSD's du allow --bytes.
# Note that using "ls -H" is not an option for portability reasons either.
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"
}
 
# Du replacement. This differs from get_file_size in that it takes multiple arguments
dur() {
for file in $@ ; do
get_file_size "$file"
done
}
 
# Gets the size of the dvd device, in DVD mode
get_dvd_size() {
# FIXME: Case sensivity might break with iso9660
if [ -f "$DVD_MOUNTP/VIDEO_TS/VTS_${DVD_VTS}_1.VOB" ]; then
# Some VOBs available
local vfiles="$DVD_MOUNTP/VIDEO_TS/VTS_${DVD_VTS}_*.VOB"
# Print all sizes, each on a line, add '+' to the end of each line, add 0 to the end.
local feed="$(dur "$DVD_MOUNTP/VIDEO_TS/VTS_${DVD_VTS}_"*".VOB" | cut -f1 | sed 's/$/ + /') 0"
get_pretty_size $(awkex "$(nltos "$feed")")
else
echo "?"
fi
}
 
is_linux() {
uname -s | grep -iq '^Linux$'
}
 
# Get the mountpoint of a mounted image.
# This only works on Linux. *BSD normal users aren't able to use mdconfig -l
# Is there any better way?
get_dvd_image_mountpoint() {
if is_linux ; then
local lodev=$(/sbin/losetup -j "$DVD_FILE" | cut -d':' -f1 | head -1)
mount | grep "^$lodev " | cut -d' ' -f3
fi
}
 
# Tests the presence of all required programs
# test_programs()
test_programs() {
local retval=0 last=0
local nopng=0
 
MPLAYER=$(type -pf mplayer) || true
FFMPEG=$(type -pf ffmpeg) || true
 
# Test we can actually use FFmpeg
[ "$FFMPEG" ] && {
# Newer FF has -codecs, -formats, -protocols, older has only -formats
#+png is a codec so it's on different lists on newer and older
if ! "$FFMPEG" -formats 2>/dev/null | grep -q 'EV.* png' && \
! "$FFMPEG" -codecs 2>/dev/null | grep -q 'EV.* png' ; then
warn "FFmpeg can't output to png, won't be able to use it."
FFMPEG=''
nopng=1
fi
}
# Same for Mplayer
[ "$MPLAYER" ] && {
if ! "$MPLAYER" -vo help 2>&1 | grep -q 'png' ; then
warn "MPlayer can't output to png, won't be able to use it."
MPLAYER=''
nopng=1
fi
}
 
[ "$MPLAYER" ] || [ "$FFMPEG" ] || {
local pngwarn=
[ $nopng -eq 1 ] && pngwarn=', with PNG output support,'
error "mplayer and/or ffmpeg$pngwarn are required!"
let 'retval++,1'
}
 
 
if [ $decoder -eq $DEC_FFMPEG ] && [ -z "$FFMPEG" ]; then
decoder=$DEC_MPLAYER
elif [ $decoder -eq $DEC_MPLAYER ] && [ -z "$MPLAYER" ]; then
decoder=$DEC_FFMPEG
fi
 
# awk is required by SUS/POSIX but just to be sure...
for prog in convert montage identify mktemp grep egrep cut sed awk ; do
if ! type -pf "$prog" ; then
error "Required program $prog not found!"
let 'retval++,1'
fi >/dev/null
done
# TODO: [[x2]]
 
# Early exit
[ $retval -eq 0 ] || return $EX_UNAVAILABLE
 
# ImageMagick version. 6 is a must, I'm probably using some
# features that require a higher minor version
# Versions tested:
# * Fedora 9: IM 6.4.0
local ver
ver=$(identify -version | head -n1 | grep -o 'ImageMagick[[:space:]]*[^ ]*' |\
cut -f 2 -d' ')
if [ "$ver" ]; then
local verx=${ver//-/.}.0 # Extra .0 in case rev doesn't exist
local major=$(cut -d'.' -f1 <<<"$verx")
local minor=$(cut -d'.' -f2 <<<"$verx")
local micro=$(cut -d'.' -f3 <<<"$verx")
local rev=$(cut -d'.' -f4 <<<"$verx")
local serial=$(( $major * 100000 + $minor * 10000 + $micro * 100 + $rev))
if [ "$serial" -lt 630507 ]; then
error "ImageMagick 6.3.5-7 or higher is required. Found $ver." ;
let 'retval++,1'
fi
else
error "Failed to check ImageMagick version."
let 'retval++,1'
fi
 
[ $retval -eq 0 ] || return $EX_UNAVAILABLE
}
 
# Test wether $GETOP is a compatible version; try to choose an alternate if
# possible
choose_getopt() {
if ! type -pf $GETOPT ; then
# getopt not in path
error "Required program getopt not found!"
return $EX_UNAVAILABLE
fi >/dev/null
local goe= gor=0
# Try getopt. If there's more than one in the path, try all of them
for goe in $(type -paf $GETOPT) ; do
"$goe" -T || gor=$?
if [ $gor -eq 4 ]; then
# Correct getopt found
GETOPT="$goe"
break;
fi
done >/dev/null
if [ $gor -ne 4 ]; then
error "No compatible version of getopt in path, can't continue."
error " For details on how to correct this problems, see <http://p.outlyer.net/vcs#getopt>"
return $EX_UNAVAILABLE
fi
return 0
}
 
# 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 VCSTEMPDIR
unset TEMPSTUFF ; declare -a TEMPSTUFF
}
 
# Exit callback. This function is executed on exit (correct, failed or
# interrupted)
# exithdlr()
exithdlr() {
# I don't think that's really required anyway
if [ "$RANDFUNCTION" == "filerand" ]; then
7<&- # Close FD 7
fi
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
[ $plain_messages -eq 0 ] && echo -n "$prefix_err"
# 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$suffix_fback"
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
[ $plain_messages -eq 0 ] && echo -n "$prefix_warn"
echo "$1$suffix_fback"
fi >&2
}
#
# Print an informational message
# inf($1 = text)
inf() {
if [ $verbosity -ge $V_INFO ]; then
[ $plain_messages -eq 0 ] && echo -n "$prefix_inf"
echo "$1$suffix_fback"
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
}
 
#
# Tests if the filter chain contains the provided filter
# has_filter($1 = filtername)
has_filter() {
local filter= ref=$1
for filter in ${FILTERS_IND[@]} ; do
[ "$filter" == "$ref" ] || continue
return 0
done
return 1
}
 
#
# Enables prefixes in console output (instead of colour)
set_feedback_prefixes() {
prefix_err='[E] '
prefix_inf='[i] '
prefix_warn='[w] '
suffix_fback=
}
 
#
# Initialises the variables affecting colourised feedback
init_feedback() {
HAS_COLORS=
 
# tput might be preferable (Linux: man console_codes), but it doesn't
# work on FreeBSD to set colors
 
# Is tput available?
if type -pf tput >/dev/null ; then
# Is it able to set colours?
if tput bold && tput setaf 0 >/dev/null && tput sgr0 ; then
prefix_err=$(tput bold; tput setaf 1)
prefix_warn=$(tput bold; tput setaf 3)
prefix_inf=$(tput bold; tput setaf 2)
suffix_fback=$(tput sgr0)
HAS_COLORS="yes"
fi
fi
 
if [ -z "$HAS_COLORS" ]; then
# tput was not an option, let's try ANSI escape codes instead [[AEC]]
# TODO: Detect support
# Alternatively: $ perl -e 'print "\e[31m\e[1m"'
# echo -e is not portable but echo $'' is bash-specific so it should be fine...
# except when ANSI escape codes aren't supported of course
prefix_err=$(echo $'\033[1m\033[31m')
prefix_warn=$(echo $'\033[1m\033[33m')
prefix_inf=$(echo $'\033[1m\033[32m')
suffix_fback=$(echo $'\033[0m')
HAS_COLORS="yes"
fi
 
# Finally, if there's no colour support, use prefixes instead
if [ -z "$HAS_COLORS" ]; then
set_feedback_prefixes
fi
}
 
#
# seq replacement
# seq is not always present, jot is an alternative on FreeBSD. Instead, this is
# a direct replacement
# Note pure bash is *slower* than the awk (or perl) version
# seqr($1 = from, $2 = to, $3 = increment)
seqr() {
local from=$1 to=$2 inc=$3
[ "$inc" ] || inc=1
awk "BEGIN { for (i=$from;i<=$to;i+=$inc) print i }"
}
 
#
# assertion operator
# assert($1 = line, $* = code)
# TODO: Limit usage to values that will expand correctly always (i.e. not with quotes)
assert() {
[ $RELEASE -eq 1 ] && return
LINE=$1
shift
eval "$@" || {
error "Internal error at line $LINE: $@"
exit $EX_SOFTWARE
}
}
 
# }}} # Convenience functions
 
# {{{ # Core functionality
 
# Creates a new temporary directory
# create_temp_dir()
create_temp_dir() {
trace $FUNCNAME $@
 
[ "$VCSTEMPDIR" ] && return 0
 
# 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.
# Passing a full path template is more x-platform than using
# -t / -p
if [ -d /dev/shm ] && [ -w /dev/shm ]; then
VCSTEMPDIR=$(mktemp -d /dev/shm/vcs.XXXXXX)
else
[ "$TMPDIR" ] || TMPDIR="/tmp"
VCSTEMPDIR=$(env TMPDIR="$TMPDIR" mktemp -d "$TMPDIR/vcs.XXXXXX")
fi
if [ ! -d "$VCSTEMPDIR" ]; then
error "Error creating temporary directory"
return $EX_CANTCREAT
fi
TEMPSTUFF=( "${TEMPSTUFF[@]}" "$VCSTEMPDIR" )
}
 
# Resolve path. Realpath is not always available and readlink [[LC]] behaves differently in
# GNU and BSD. FIXME: Has AWK or bash something similar? This is the only place requiring perl!
realpathr() {
perl -e "use Cwd qw(realpath);print realpath('$1')"
}
 
# Create a new temporal file and print its filename
# new_temp_file($1 = suffix)
new_temp_file() {
trace $FUNCNAME $@
local r=$(env TMPDIR="$VCSTEMPDIR" mktemp "$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=( "${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 + ( $(rand) % $ncolours ) ))
convert -list color | sed -n "${lineno}{p;q;}" | cut -d' ' -f1 # [[R1#11]]
}
else # Pseudo-random mode, WIP!
randccomp() {
# colours are in the 0..65535 range, while RANDOM in 0..32767
echo $(( $(rand) + $(rand) + ($(rand) % 1) ))
}
randcolour() {
echo "rgb($(randccomp),$(randccomp),$(randccomp))"
}
fi
 
# Older IM output was pretty different. Since this is a mode used for testing
# I don't believe it's worth the effort to get it always right
# This used to be -list type. Was this an older IM version or a bug in vcs?
local nfonts=$(convert -list font | grep '^\s*Font:' | wc -l)
randfont() {
lineno=$(( $(rand) % $nfonts ))
convert -list font | grep -o 'Font:.*' | sed -n "${lineno}{p;q;}" | cut -d' ' -f2
}
 
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=$(awkex "$end - $st")
 
if fptest "$runlen" -lt $(get_interval "$MIN_LENGTH_FOR_END_OFFSET") ; then
# Min length to use end offset not met, it won't be used
inf "End offset won't be used, video too short."
eo=0
elif 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 for the video, 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=$(awkexf "(($end-$st)/2 + 1)")
else
inc=$(awkexf "(($end-$eo-$st)/$tcnumcaps)")
fi
else
error "Internal error"
return $EX_SOFTWARE
fi
# Keep three decimals, round to lower to avoid exceeding the video length
inc=$(keepdecimals_lower $inc 3)
 
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
local bound=$(awkexf "$end - $eo")
while fptest $stamp -le "$bound"; do
assert $LINENO fptest $stamp -ge 0
LTC=( "${LTC[@]}" "$stamp" )
stamp=$(keepdecimals_lower $(awkexf "$stamp + $inc") 3)
done
unset LTC[0] # Discard initial cap (=$st)
TIMECODES=( ${TIMECODES[@]} ${LTC[@]} ) # Don't quote or extra empty stamp!
}
 
# 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 $@
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
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 with ffmpeg
# capture_ffmpeg($1 = inputfile, $2 = outputfile, $3 = timestamp[, $4 = extra opts])
capture_ffmpeg() {
local f=$1
local o=$2
local ts=$3
# 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/ / $ts} -i "$f" ${wa_ss_af/ / $ts} -an \
-dframes 1 -vframes 1 -vcodec png \
-f rawvideo $4 $shoehorned "$o" >"$stdout" 2>"$stderr"
[ -f "$o" ] && [ "0" != "$(du "$o" | cut -f1)" ]
}
 
# Capture a frame with mplayer
# capture_mplayer($1 = inputfile, $2 = UNUSED, $3 = timestamp[, $4 = extra opts])
capture_mplayer() {
# Note mplayer CAN'T set the output filename
local f="$1"
local o=00000005.png
local ts=$3
 
# No point in passing ms to mplayer
ts=$(cut -d'.' -f1 <<<"$ts")
# Capture 5 frames and drop the first 4, fixes a weird bug/feature of mplayer ([M1])
 
{
if [ $DVD_MODE -eq 1 ]; then
"$MPLAYER" -sws 9 -ao null -benchmark -vo "png:z=0" -quiet \
-frames 5 -ss "$ts" $shoehorned -dvd-device "$DVD_FILE" \
$4 "dvd://$DVD_TITLE"
else
"$MPLAYER" -sws 9 -ao null -benchmark -vo "png:z=0" -quiet \
-frames 5 -ss "$ts" $4 $shoehorned "$f"
fi
 
} >"$stdout" 2>"$stderr"
rm -f 0000000{1,2,3,4}.png # Remove the first four
[ -f "$o" ] && [ "0" != "$(du "$o" | cut -f1)" ]
}
 
# 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_mplayer "$f" 'IGNOREME' "$stamp"
elif [ $decoder -eq $DEC_FFMPEG ]; then
# FIXME: ffmpeg can put the temporary file anywhere
capture_ffmpeg "$f" "$VIDCAPFILE" "$stamp"
else
error "Internal error!"
return $EX_SOFTWARE
fi || true
if [ ! -f "$VIDCAPFILE" ] || [ "0" == "$(du "$VIDCAPFILE" | cut -f1)" ]; then
[ $decoder -eq $DEC_MPLAYER ] && stamp=${stamp/%.*}
error "Failed to capture frame at $(pretty_stamp $stamp) (${stamp}s)"
return $EX_SOFTWARE
fi
 
return 0
}
 
# Applies all individual vidcap filters
# filter_vidcap($1 = filename, $2 = timestamp, $3 = width, $4 = height, $5 = context, $6 = index[1..])
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" "$5" "$6" ) -flatten "
done
local t=$(new_temp_file .png)
eval "convert '$1' $cmdopts '$t'"
# If $t doesn't exist returns non-zero
[ -f "$t" ] && mvq "$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, $5 = context, $6 = index)
filt_apply_stamp() {
trace $FUNCNAME $@
local filename=$1 timestamp=$2 width=$3 height=$4 context=$5 index=$6
 
local pts=$pts_tstamps
if [ $height -lt 200 ]; then
pts=$(( $pts_tstamps / 3 ))
elif [ $height -lt 400 ]; then
pts=$(( $pts_tstamps * 2 / 3 ))
fi
# If the size is too small they won't be readable at all
# With the original font 8 was the minimum, with DejaVu 7 is readable
if [ $pts -le 7 ]; then
pts=7
if [ $index -eq 1 ] && [ $context -ne $CTX_EXT ]; then
warn "Very small timestamps in use. Disabling them with -dt might be preferable"
fi
fi
# The last -gravity None is used to "forget" the previous gravity (otherwise it would
# affect stuff like the polaroid frames)
echo -n " \( -box '$bg_tstamps' -fill '$fg_tstamps' -stroke none -pointsize '$pts' "
echo -n " -gravity '$grav_timestamp' -font '$font_tstamps' -strokewidth 3 -annotate +5+5 "
echo " ' $(pretty_stamp $stamp) ' \) -flatten -gravity None "
}
 
# Apply a framed photo-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 ) ) ))
# The border is relative to the input size (since 1.0.99), with a maximum of 6
# Should probably be bigger for really big frames
# Note that only images below 21600px (e.g. 160x120) go below a 6px border
local border=$(( ($3*$4) / 3600 ))
[ $border -lt 7 ] || border=6
echo -n "-bordercolor white -border $border -bordercolor grey60 -border 1 "
}
 
filt_softshadow() {
# Before this was a filter, there was the global (montage) softshadow (50x2+10+10) and the
# photoframe inline softshadow 60x4+4+4
echo -n "\( -background black +clone -shadow 50x2+4+4 -background none \) +swap -flatten -trim +repage "
}
 
 
# Apply a polaroid-like border effect
# Based on filt_photoframe(), with a bigger lower border
# filt_polaroid($1 = filename, $2 = timestamp, $3 = width, $4 = height)
filt_polaroid() {
trace $FUNCNAME $@
# local file="$1" ts=$2 w=$3 h=$4
local border=$(( ($3*$4) / 3600 )) # Read filt_photoframe for details
[ $border -lt 7 ] || border=6
echo -n "-bordercolor white -mattecolor white -frame ${border}x${border} "
# FIXME: This is rather ugly (double-flipping) there's sure a better way
echo -n "\( -flip -splice 0x$(( $border*5 )) \) "
echo "-flip -bordercolor grey60 -border 1 +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=$(( ($(rand) % 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="$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= vpad= splice=
 
# The shadows already add a good amount of padding
if has_filter filt_softshadow ; then
hpad=0
vpad=0
splice=5x10
else
hpad=$HPAD
vpad=$HPAD
splice=0x8
fi
 
montage -background Transparent "$@" -geometry +$hpad+$vpad -tile "$cols"x "$output"
convert "$output" -background Transparent -splice $splice "$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 $(seqr 1 $numrows) ; do
col=0
rowfile=$(new_temp_file .png)
rowfiles=( "${rowfiles[@]}" "$rowfile" )
accoffset=0
cmdopts= # This command is pretty time-consuming, let's make it in a row
 
# Base canvas # Integrated in the row creation since 1.0.99
 
# Step through vidcaps (col=[0..cols-1])
for col in $(seqr 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
cmdopts="$cmdopts '$1' -geometry +${accoffset}+0 -composite "
 
offset=$(( $minoffset + ( $(rand) % $maxoffset ) ))
let 'accoffset=accoffset + w - offset'
shift
done
inf "Composing overlapped row $row/$numrows..."
eval convert -size ${canvasw}x${canvash} xc:transparent -geometry +0+0 "$cmdopts" -trim +repage "'$rowfile'" >&2
done
 
inf "Merging overlapped rows..."
output=$(new_temp_file .png)
 
cmdopts=
accoffset=0
local h
for row in "${rowfiles[@]}" ; do
w=$(imw "$row")
h=$(imh "$row")
minoffset=$(( $h / 8 ))
maxoffset=$(( $h / 4 ))
offset=$(( $minoffset + ( $(rand) % $maxoffset ) ))
# The row is also offset horizontally
cmdopts="$cmdopts '$row' -geometry +$(( $(rand) % $maxoffset ))+$accoffset -composite "
let 'accoffset=accoffset + h - offset'
done
# After the trim the image will be touching the outer borders and the heading and footer,
# older versions (prior to 1.0.99) used -splice 0x10 to correct the heading spacing, 1.0.99
# onwards uses -frame to add spacing in all borders + splice to add a bit more space on the
# upper border. Note splice uses the background colour while frame uses the matte colour
eval convert -size ${canvasw}x$(( $canvash * $cols )) xc:transparent -geometry +0+0 \
$cmdopts -trim +repage -bordercolor Transparent -background Transparent -mattecolor Transparent \
-frame 5x5 -splice 0x5 "$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 I replace spaces by newlines
local s=$1
stonl "$s" | sort -n | uniq
}
 
# Test the video at a given timestamp (to see if it can be reached)
# See safe_length_measure()
# probe_video($1 = input file, $2 = stamp)
probe_video() {
local f="$1"
local ts="$2"
local tempfile=
local ret=0
 
# create_temp_dir should have been called
 
# This time a resize filter is applied to the player to produce smaller
# output
if [ $decoder -eq $DEC_MPLAYER ]; then
tempfile=00000005.png
TEMPSTUFF=( "${TEMPSTUFF[@]}" "$tempfile" )
if ! capture_mplayer "$f" "IGNOREME" "$ts" "-vf scale=96:96"; then
ret=1
fi
elif [ $decoder -eq $DEC_FFMPEG ]; then
tempfile=$(new_temp_file '-safelen.png')
if ! capture_ffmpeg "$f" "$tempfile" "$ts" "-s 96x96"; then
ret=1
fi
else
assert $LINENO false
ret=1
fi
rm -f "$tempfile"
return $ret
}
 
# Try to guess a correct length for the video, taking the reported lengths a
# starting point
# safe_length_measure($1 = filename)
safe_length_measure() {
trace $FUNCNAME $@
local f="$1"
local len=${VID[$LEN]}
local tempfile=
local newlen=$len
local capturefn=
 
create_temp_dir
 
if probe_video "$1" $len ; then
inf " File looks fine, suspicion withdrawn"
echo "$len"
return 0
else
# Can't seek to the very end, adjust
warn "Starting safe length measuring (this might take a while)..."
local maxrew=$(min $QUIRKS_MAX_REWIND $(awkex "int($len)")) # At most we'll rewind 20 seconds
# -1 (-WS) => Rewind up to the start
# Might be -2, -4, ... e.g. (-WS -Ws)
if fptest $maxrew -ge $len || fptest "$maxrew" -lt 0 ; then
maxrew=$len
INTERNAL_MAXREWIND_REACHED=1
fi
for rew in $(seqr $QUIRKS_LEN_STEP $maxrew $QUIRKS_LEN_STEP); do
newlen=$(keepdecimals_lower $(awkex "$len - $rew") 3)
warn " ... trying $newlen"
if probe_video "$f" "$newlen" ; then
echo $newlen
return 0
fi
done
fi
# Hitting this line means we're doomed!
return 1
}
 
##### {{{{ Codec names
 
# Codecs TODO: Clean this
# Translates an mplayer codec id/fourcc to its name
get_vcodec_name() {
local vcid="$1"
local vcodec=
# Video codec "prettyfication", see [[R2]], [[R3]], [[R4]]
case "$vcid" in
0x10000001) vcodec="MPEG-1" ;;
0x10000002) vcodec="MPEG-2" ;;
0x00000000) vcodec="Raw video" ;; # How correct is this?
avc1|H264) vcodec="MPEG-4 AVC" ;; # H264 is used in mov/mp4
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" ;; # 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 SCC" ;;
VP6[012F]) 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" ;;
3IV2) vcodec="3ivx Delta 4.0" ;; # Rare but seen
FLV1) vcodec="Sorenson Spark (FLV1)" ;;
 
# 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" ;;
s263) vcodec="H.263" ;; # 3GPP
# Legacy(-er) codecs (haven't seen files in these formats in awhile)
IV3[0-9]) vcodec="Indeo Video 3" ;; # FF only recognises IV31
MSVC) vcodec="Microsoft Video 1" ;;
MRLE) vcodec="Microsoft RLE" ;;
3IV1) vcodec="3ivx Delta" ;;
# "mp4v" is the MPEG-4 fourcc *in mov/mp4/3gp*; but I also found MP4V (Apple's iTunes sample)
#mp4v|MP4V) vcodec="MPEG-4" ;;
mp4v|MP4V) vcodec="MPEG-4" ;;
# Synthetic, used for ffmpeg translations
vcs_divx) vcodec="DivX ;-)" ;;
*) # If not recognized fall back to FourCC
vcodec="$vcid"
;;
esac
echo "$vcodec"
}
 
# Translates an FFmpeg codec id to an MPlayer codec id/fourcc
# TODO: Clean this
translate_ffmpeg_vcodec_id() {
# The list of ffmpeg codecs might be retrieved by looking at the code but I
#+simply used the ffmpeg -formats / ffmpeg -codecs command
# Supported video decoders: $ ffmepg -codecs | grep '^ D.V'
local vcid="$1"
local mpid=
case "$vcid" in
mpeg1video) mpid="0x10000001" ;; # mpeg1video_vdpau?
mpeg2video) mpid="0x10000002" ;;
rawvideo) mpid="0x00000000" ;; # can't distinguish from I420
h264) mpid="avc1" ;;
mjpeg) mpid="MJPG" ;;
msmpeg4v1) mpid="MPG4" ;;
msmpeg4v2) mpid="MP42" ;;
theora) mpid="theo" ;;
camtasia) mpid="tscc" ;;
vp6|vp6a|vp6f) mpid="VP60" ;;
# FIXME List of codec id's I translate but haven't test:
# svq3, rv40, theora, camtasia, vp6*
# MPlayer uses uppercase while FFmpeg uses lowercase
rv10|rv20|rv30|rv40|svq1|svq3|wmv1|wmv2|wmv3) mpid=$(echo $vcid | tr '[a-z]' '[A-Z]') ;;
# FFmpeg doesn't print FourCC's so there's some codecs that can't be told apart
msmpeg4) mpid="vcs_divx" ;; # DIV3 = DIV4 = MP43
# XVID = DIVX = DX50 = FMP4 = ... = mpeg4
mpeg4) mpid="mp4v" ;; # Take advantage of an unamed MPEG-4
 
h263) mpid="s263" ;;
 
vc1) mpid="WVC1" ;; # In FF: WMVA = vc1
flv) mpid="FLV1" ;;
# Not supported (ff just prints the FourCC)
# IV4*, vp4
vp3) mpid="VP30" ;;
vp5) mpid="VP50" ;;
# Legacy(-er) codecs (haven't seen files in these formats in awhile)
# MSVC? MRLE?
indeo3) mpid="IV31" ;;
*) # If not recognized fall back to FourCC
mpid="$vcid"
;;
 
esac
echo $mpid
}
 
get_acodec_name() {
local acid="$1"
local acodec=
 
if grep -q '[ -]' <<<"$acid" ; then
# Won't be recognised anyway
echo "$acid"
return
fi
 
case "$(tolower "$acid")" 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, Ogg, probably others
acodec='Vorbis'
;;
qdm2) acodec="QDesign" ;;
"") acodec="no audio" ;;
samr) acodec="AMR" ;; # AMR-NB/AMR-WB?
# 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="$acid"
;;
esac
echo "$acodec"
}
 
translate_ffmpeg_acodec_id() {
local acid="$1"
local mpid=
# ffmpeg -codecs | grep ^\ D.A
case "$acid" in
mp3) mpid='85' ;;
# Note FF can tell apart mp1/mp2 directly
mp1) mpid='MPEG Layer I (MP1)' ;;
mp2) mpid='MPEG Layer II (MP2)' ;;
aac) mpid='mp4a' ;; # Can aac be MPEG2?
wmav1) mpid='352' ;;
wmav2) mpid='353' ;;
wmapro) mpid='354' ;; # Actually WMA9 Professional
ac3) mpid='8192' ;;
# FF has a ton of pcm variants (sign, endianness, ...)
pcm_*) mpid="1" ;;
vorbis) mpid="vrbs" ;;
 
qdm2) mpid="QDM2" ;;
libopencore_amrnb) mpid="AMR-NB" ;;
libopencore_amrwb) mpid="AMR-WB" ;;
*) # If not recognized show audio id tag
mpid="$acid"
;;
esac
echo "$mpid"
}
 
##### }}}} # Codec names
 
# Try to identify video properties using mplayer
# Fills $MPLAYER_CACHE with the relevant output and $VID_MPLAYER with
# the actual values. See identify_video()
# mplayer_identify($1 = file)
mplayer_identify() {
trace $FUNCNAME $@
[ "$MPLAYER" ] || return
local f="$1"
local mi=( )
# Note to self: Don't change the -vc as it would affect $vdec
if [ $DVD_MODE -eq 0 ]; then
MPLAYER_CACHE=$("$MPLAYER" -benchmark -ao null -vo null -identify -frames 0 \
-quiet "$f" 2>/dev/null | grep ^ID)
else
MPLAYER_CACHE=$("$MPLAYER" -benchmark -ao null -vo null -identify -frames 0 \
-quiet -dvd-device $DVD_FILE dvd://$DVD_TITLE \
2>/dev/null | grep ^ID)
fi
# Note the head -1!
mi[$VCODEC]=$(grep ID_VIDEO_FORMAT <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2) # FourCC
mi[$ACODEC]=$(grep ID_AUDIO_FORMAT <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2)
mi[$VDEC]=$(grep ID_VIDEO_CODEC <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2) # Decoder (!= Codec)
mi[$W]=$(grep ID_VIDEO_WIDTH <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2)
mi[$H]=$(grep ID_VIDEO_HEIGHT <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2)
mi[$FPS]=$(grep ID_VIDEO_FPS <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2)
# For some reason my (one track) samples have two ..._NCH, first one 0
#+Also multichannel is detected as 2 ch
mi[$CHANS]=$(grep ID_AUDIO_NCH <<<"$MPLAYER_CACHE"| grep -v '=0' | cut -d'=' -f2|head -1)
if [ $DVD_MODE -eq 0 ]; then
# For DVDs it prints ID_DVD_TITLE_x_LENGTH and ID_LENGTH.
#+Both appear valid.
mi[$LEN]=$(grep ID_DVD_TITLE_${DVD_TITLE}_LENGTH <<<"$MPLAYER_CACHE"| cut -d'=' -f2)
[ "${mi[$LEN]}" ] || mi[$LEN]=$(grep ID_LENGTH <<<"$MPLAYER_CACHE"| head -1 | cut -d'=' -f2)
else
mi[$LEN]=$(grep ID_DVD_TITLE_${DVD_TITLE}_LENGTH <<<"$MPLAYER_CACHE"| head -1 | cut -d'=' -f2)
fi
# Voodoo :P Remove (one) trailing zero
if [ "${mi[$FPS]:$(( ${#mi[$FPS]} - 1 ))}" == "0" ]; then
mi[$FPS]="${mi[$FPS]:0:$(( ${#mi[$FPS]} - 1 ))}"
fi
mi[$ASPECT]=$(grep ID_VIDEO_ASPECT <<<"$MPLAYER_CACHE" | egrep -v '^0.0000$' | cut -d'=' -f2 | tail -1)
# If none set, delete it
[ "${mi[$ASPECT]}" ] && fptest "${mi[$ASPECT]}" -eq 0.0 && mi[$ASPECT]=''
mi[$VCNAME]=$(get_vcodec_name "${mi[$VCODEC]}")
if [ "${mi[$VDEC]}" == "ffodivx" ] && [ "${mi[$VCNAME]}" != "MPEG-4" ]; then
mi[$VCNAME]="${mi[$VCNAME]} (MPEG-4)"
elif [ "${mi[$VDEC]}" == "ffh264" ]; then # At least two different fourccs use h264, maybe more
mi[$VCNAME]="${mi[$VCNAME]} (h.264)"
fi
mi[$ACNAME]=$(get_acodec_name "${mi[$ACODEC]}")
if [ "${mi[$ACODEC]}" == "samr" ] ; then
local adec=$(grep ID_AUDIO_CODEC <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2)
if [ "$adec" == "ffamrnb" ]; then
mi[$ACNAME]="AMR-NB";
fi
fi
 
# Array assignment
VID_MPLAYER=("${mi[@]}")
}
 
# Try to identify video properties using ffmpeg
# Fills $FFMPEG_CACHE with the relevant output and $VID_FFMPEG with
# the actual values. See identify_video()
# mplayer_identify($1 = file)
ffmpeg_identify() {
trace $FUNCNAME $@
[ "$FFMPEG" ] || return
# (AFAIK) Can't use ffmpeg in DVD Mode
#[ $DVD_MODE -eq 0 ] || return
local f="$1"
# DVD Devices *MUST* be mounted for the identifying process to even start
assert $LINENO "[ $DVD_MODE -eq 0 ] || [ '$DVD_MOUNTP' ]"
[ $DVD_MODE -eq 1 ] && [ "$DVD_DEVICE" ] && {
local vfile="$DVD_MOUNTP/VIDEO_TS/VTS_${DVD_VTS}_1.VOB"
if [ ! -r "$vfile" ]; then
error "Failed to locale mounted DVD. Detection will be less accurate."
return 0 # We can continue anyway
fi
f="$vfile"
}
 
local fi=( ) vs= as= obs= vsid=
# FFmpeg is relatively new, introduced in 1.0.99 so it needs more testing
FFMPEG_CACHE=$("$FFMPEG" -i "$f" -dframes 0 -vframes 0 /dev/null 2>&1 | egrep '(Stream|Duration:|^Seems)')
# Only the first streams of each type are honored. FIXME: Add multi-audio support.
vs=$(grep Stream <<<"$FFMPEG_CACHE" | grep 'Video:' | head -1)
as=$(grep Stream <<<"$FFMPEG_CACHE" | grep 'Audio:' | head -1)
obs=$(grep Seems <<<"$FFMPEG_CACHE")
# Stream #0.0: Video: mpeg4, yuv420p, 624x352 [PAR 1:1 DAR 39:22], 23.98 tbr, 23.98 tbn, 24k tbc
# New and old versions of ffmpeg changed their output considerably, e.g.:
# (same file, Robotica_720.wmv)
# New output:
# Seems stream 1 codec frame rate differs from container frame rate: 1000.00 (1000/1) -> 23.98 (24000/1001)
# [...]
# Duration: 00:00:20.77, start: 3.000000, bitrate: 6250 kb/s
# Stream #0.0(eng): Audio: wmapro, 48000 Hz, 6 channels, s16, 384 kb/s
# Stream #0.1(eng): Video: wmv3, yuv420p, 1280x720, 6500 kb/s, 23.98 tbr, 1k tbn, 1k tbc
# Old output:
# Duration: 00:00:20.7, start: 3.000000, bitrate: 6250 kb/s
# Stream #0.0: Audio: 0x0162, 48000 Hz, 5:1, 384 kb/s
# Stream #0.1: Video: wmv3, yuv420p, 1280x720, 24.00 fps(r)
# TODO: tbr is rounded to two decimals but the actual ratio is printed:
# 24000/1001 = 23.97602
# (older ffmpeg prints 24 fps, 24/1 so no luck here
# **Also seen**: (note the 'tb(r)')
# Stream #0.1: Video: wmv3, yuv420p, 1440x1080 [PAR 4:3 DAR 16:9], 8000 kb/s, 23.98 tb(r)
# **Also seen**: (VOB, latest ffmpeg as of this writing):
# Stream #0.0[0x1e0]: Video: mpeg2video, yuv420p, 720x576 [PAR 64:45 DAR 16:9], 9800 kb/s, 23.53 fps, 25 tbr, 90k tbn, 50 tbc
# **Also seen**: (DVB TS to DX50 in MKV), note the DAR mess, the second one is the correct one
# Stream #0.0: Video: mpeg4, yuv420p, 640x326 [PAR 1:1 DAR 320:163], PAR 231:193 DAR 73920:31459, 25 fps, 25 tbr, 1k tbn, 25 tbc
vsid=$(grep -o '#0.[0-9]' <<<"$vs" | cut -d'.' -f2) # Video Stream ID
fi[$VCODEC]=$(egrep -o 'Video: [^,]*' <<<"$vs" | cut -d' ' -f2-)
# ffmpeg's codec might contain spaces in some cases, i.e. iv4 in mov (see mplayer's bestiary)
#+unless this turns out to be common I won't be handling it specially
# Note unidentified audio codecs will be printed in hexadecimal
fi[$ACODEC]=$(egrep -o 'Audio: [^,]*' <<<"$as" | cut -d' ' -f2)
fi[$VDEC]=''
# The comma is required for cases where the stream id is printed (in hex)
fi[$W]=$(egrep -o ', [0-9]*x[0-9]*' <<<"$vs" | sed 's/^, //' | cut -dx -f1)
fi[$H]=$(egrep -o ', [0-9]*x[0-9]*' <<<"$vs" | cut -dx -f2)
# Newer CHANS and some older...
fi[$CHANS]=$(egrep -o '[0-9]* channels' <<<"$as" | cut -d' ' -f1)
# ...fallback for older
if [ -z "${fi[$CHANS]}" ]; then
local chans=$(egrep -o 'Hz, [^,]*' <<<"$as" | cut -d' ' -f2)
case $chans in
mono) fi[$CHANS]=1 ;;
stereo) fi[$CHANS]=2 ;;
5.1|5:1) fi[$CHANS]=6 ;; # *
*) ;; # Other layouts use 'N channels'
# 5.1 was in the previous version (can't remember if it was empirical).
esac
fi
# Newer FPS...
# tbr/tbn/tbc explanation: tb stands for time base
# n: AVStream, c: AVCodecContext, r: VideoStream (Guessed)
# tbr is the best bet. Note it's common for WMVs to contains "1k tbn, 1k tbc"
# tbr is rounded to two decimals, the values used to derived it might be
# printed in a "Seems ..." line like the one in the example above so it
# can be re-calculated.
fi[$FPS]=$(egrep -o '[0-9]*\.?[0-9]*k? tb(r|\(r\))' <<<"$vs" | cut -d' ' -f1)
# Let's convert e.g. 23.98 into 23.976...:
if [ "${fi[$FPS]}" ] && grep -q '\.' <<<"${fi[$FPS]}" ; then
# Decimals, see if we got better values available
local vsobs=$(grep "stream $vsid" <<<"$obs")
# Observations regarding video stream found
if [ "$vsobs" ] && grep -q " -> ${fi[$FPS]} (.*)" <<<"$vsobs" ; then
# FPS candidate
local newfps=$(egrep -o -- '-> [^ ]* \([0-9]*/[0-9]*' <<<"$vsobs" | cut -d'(' -f2)
is_fraction $newfps && fi[$FPS]=$(keepdecimals "$newfps" 3)
fi
fi
# ...fallback for older. The older version I tried seems to round further, i.e.
# 23.976 became 24 so no fix for this one
if [ -z "${fi[$FPS]}" ]; then
# No k suffix here, 1000 is 1000
fi[$FPS]=$(egrep -o '[0-9]*\.?[0-9]* fps' <<<"$vs" | cut -d' ' -f1)
fi
# Be consistent with mplayer's output: at least two decimals
[ "${fi[$FPS]}" ] && {
fi[$FPS]=$(keepdecimals "${fi[$FPS]}" 3)
fi[$FPS]=${fi[$FPS]/%0} # Strip 0$
}
fi[$LEN]=$(egrep -o 'Duration: [^,]*' <<<"$FFMPEG_CACHE" | cut -d' ' -f2)
if [ "${fi[$LEN]}" == "N/A" ]; then # It might be unable to detect
fi[$LEN]=""
fi
fi[$LEN]=$( get_interval $(echo "${fi[$LEN]}" | sed 's/:/h/' | sed 's/:/m/') )
# Aspect ratio in FFmpeg is only provided in newer ffmpeg
# It might be calculated for files without one (which is ok anyway)
# TODO: Replace tail -1 with some better option (see the double DAR example above)
fi[$ASPECT]=$(egrep -o 'DAR [0-9]*:[0-9]*'<<<"$FFMPEG_CACHE" | tail -1 | cut -d' ' -f2 | sed 's#:#/#')
# Due to calling ffmpeg on a single VOB when in DVD Device mode, the length will be partial
[ $DVD_MODE -eq 1 ] && [ "$DVD_DEVICE" ] && fi[$LEN]=''
fi[$VCNAME]=$(get_vcodec_name $(translate_ffmpeg_vcodec_id "${fi[$VCODEC]}"))
fi[$ACNAME]=$(get_acodec_name $(translate_ffmpeg_acodec_id "${fi[$ACODEC]}"))
VID_FFMPEG=("${fi[@]}")
}
 
# Use the available tools to identify video meta-data
# fills $VID with the values
# Return codes:
# 3: Failed to detect length
# 4: Failed to detect width or height
# identify_video($1 = file)
identify_video() {
trace $FUNCNAME $@
local RET_NOLEN=3 RET_NODIM=4
 
[ "$MPLAYER" ] && mplayer_identify "$1"
# ffmpeg_identify in DVD mode only works when the DVD is mounted:
[ $DVD_MODE -eq 0 ] && [ "$FFMPEG" ] && ffmpeg_identify "$1"
[ $DVD_MODE -eq 1 ] && [ "$FFMPEG" ] && [ "$DVD_MOUNTP" ] && ffmpeg_identify "$1"
 
# Fail early if none detected length
[ -z "${VID_MPLAYER[$LEN]}" ] && [ -z "${VID_FFMPEG[$LEN]}" ] && return $RET_NOLEN
 
# Classic mode, use both mplayer and ffmpeg when available
if [ "$MPLAYER" ] && [ "$FFMPEG" ]; then
# By default take mplayer's values
VID=("${VID_MPLAYER[@]}")
# FFmpeg seems better at getting the correct number of FPS, specially with
# WMVs, where mplayer often accepts 1000fps while ffmpeg notices the
# inconsistency in container vs codec and guesses better, *but* it only
# uses two decimals so 23.976 becomes 23.98. So it is only used when
# the number of decimals seems right.
# When a "Seems..." line is printed the correct FPS can be obtained though.
[ -z "${VID_MPLAYER[$FPS]}" ] && VID[$FPS]=${VID_FFMPEG[$FPS]}
[ "${VID_MPLAYER[$FPS]}" ] && [ "${VID_FFMPEG[$FPS]}" ] && {
# Trust ffmpeg if it has three decimals OR if mplayer is probably-wrong
local ffps=${VID_FFMPEG[$FPS]}
echo $ffps | grep -q '\.[0-9][0-9][0-9]' && VID[$FPS]=$ffps || {
fptest "${VID_MPLAYER[$FPS]}" -gt 500 && VID[$FPS]=$ffps
}
}
# It doesn't appear to need any workarounds for num. channels either
[ "${VID_FFMPEG[$CHANS]}" ] && VID[$CHANS]=${VID_FFMPEG[$CHANS]}
[ "${VID_FFMPEG[$ASPECT]}" ] && VID[$ASPECT]=${VID_FFMPEG[$ASPECT]}
# There's a huge inconsistency with some files, both mplayer vs ffmpeg
# same application on different OSes
local fflen=${VID_FFMPEG[$LEN]} mplen=${VID_MPLAYER[$LEN]} # Shorthands
[ -z "$fflen" ] && fflen=0
# If both report 0, there's no good value...
fptest "$fflen" -eq 0 && fptest "$mplen" -eq 0 && return $RET_NOLEN
if [ $DVD_MODE -eq 0 ] && [ $QUIRKS -eq 0 ]; then # In DVD mode ffmpeg has no length
# Quirks disabled, should be enabled?
local delta=$(abs $(awkex "($fflen - $mplen)"))
# If they don't agree, take the shorter as a starting point,
#+if both are different than zero take min, if one of them is 0, take max to start
if fptest "$fflen" -ne 0 && fptest "$mplen" -ne 0 ; then
VID[$LEN]=$(min $fflen $mplen)
else
VID[$LEN]=$(max $fflen $mplen)
delta=$QUIRKS_LEN_THRESHOLD # Ensure it's considered inconsistent
fi
# If they differ too much, enter safe mode. If one reports 0, they'll differ...
# FIXME: If $decoder reports 0, can it seek??
if fptest "$delta" -ge $QUIRKS_LEN_THRESHOLD ; then
warn "Found inconsistency in reported length. Safe measuring enabled."
QUIRKS=1
fi
fi
elif [ "$MPLAYER" ]; then
# Must do with mplayer only...
VID=("${VID_MPLAYER[@]}")
# Warn if a known pitfall is found
# See above for 1000 fps
[ "${VID[$FPS]}" == "1000.00" ] && \
warn "Possible inaccuracy in FPS detection." && \
warn " Install both mplayer and ffmpeg for better detection."
# Number of channels 0 happened for WMA in non-x86
[ "${VID[$CHANS]}" == "0" ] && \
warn "Failed to detect number of audio channels." && \
warn " Install both mplayer and ffmpeg for better detection."
elif [ "$FFMPEG" ]; then
# Must do with mplayer only...
VID=("${VID_FFMPEG[@]}")
# So far I know of no weird results. Yet.
else
assert $LINENO 'false'
fi
 
# Ensure sanity of the most important values
is_float "${VID[$LEN]}" || return $RET_NOLEN
is_number "${VID[$W]}" && is_number "${VID[$H]}" || return $RET_NODIM
 
if [ "$FFMPEG" ]; then
# FPS at least with two decimals
if [ $(awkex "int(${VID[$FPS]})") == ${VID[$FPS]} ]; then
VID[$FPS]="${VID[$FPS]}.00"
fi
fi
 
local mfps="${VID_MPLAYER[$FPS]}"
if [ $QUIRKS -eq 0 ] && [ "$MPLAYER" ] && fptest "$mfps" -eq 1000 ; then
warn "Suspect file. Safe measuring enabled."
QUIRKS=1
fi
 
# Last safeguard: Try to reach the detected length, if it fails, trigger
# quirks mode
if [ $QUIRKS -eq 0 ]; then
create_temp_dir
if ! probe_video "$1" "${VID[$LEN]}" ; then
warn "Detected video length can't be reached. Safe measuring enabled."
QUIRKS=1
fi
fi
 
if [ $QUIRKS -eq 1 ]; then
VID[$LEN]=$(safe_length_measure "$1")
if [ -z "${VID[$LEN]}" ]; then
error "Couldn't measure length in a reasonable amount of tries."
if [ $INTERNAL_MAXREWIND_REACHED -eq 1 ]; then
error " Will not be able to capture this file with the current settings."
else
local reqs=$(( $INTERNAL_WS_C + 1 )) reqp=''
[ $reqs -eq 1 ] && reqp=" -WP" || reqp=" -WP$reqs"
[ $reqs -ge 3 ] && reqs=" -WS" || { # Third try => Recommend -WS
[ $reqs -eq 1 ] && reqs=" -Ws" || reqs=" -Ws$reqs"
}
assert 'fptest "$QUIRKS_MAX_REWIND" -gt 0'
local offby=$(pretty_stamp $QUIRKS_MAX_REWIND)
warn " Capturing won't work, video is at least $offby shorter than reported."
local dname='ffmpeg'
[ $decoder -eq $DEC_MPLAYER ] && dname='mplayer'
warn " Does $dname support ${VID[$VCODEC]}?."
warn " Try re-running with$reqs$reqp."
fi
return 1
fi
elif [ $QUIRKS -eq -2 ]; then
warn "Safe mode disabled."
fi
 
# Re-check sanity of the most important values
is_float "${VID[$LEN]}" || return $RET_NOLEN
is_number "${VID[$W]}" && is_number "${VID[$H]}" || return $RET_NODIM
}
 
dump_idinfo() {
trace $FUNCNAME $@
[ "$MPLAYER" ] && echo "Mplayer: $MPLAYER"
[ "$FFMPEG" ] && echo "FFmpeg: $FFMPEG"
[ "$MPLAYER" ] && cat <<-EODUMP
=========== Mplayer Identification ===========
Length: $(pretty_stamp ${VID_MPLAYER[$LEN]})
Video
Codec: ${VID_MPLAYER[$VCODEC]} (${VID_MPLAYER[$VCNAME]})
Dimensions: ${VID_MPLAYER[$W]}x${VID_MPLAYER[$H]}
FPS: ${VID_MPLAYER[$FPS]}
Aspect: ${VID_MPLAYER[$ASPECT]}
Audio
Codec: ${VID_MPLAYER[$ACODEC]} (${VID_MPLAYER[$ACNAME]})
Channels: ${VID_MPLAYER[$CHANS]}
==============================================
 
EODUMP
local ffl="${VID_FFMPEG[$LEN]}"
[ "$ffl" ] && ffl=$(pretty_stamp "$ffl")
[ -z "$ffl" ] && [ $DVD_MODE -eq 1 ] && ffl="(unavailable in DVD mode)"
[ "$FFMPEG" ] && cat <<-EODUMP
=========== FFmpeg Identification ===========
Length: $ffl
Video
Codec: ${VID_FFMPEG[$VCODEC]} (${VID_FFMPEG[$VCNAME]})
Dimensions: ${VID_FFMPEG[$W]}x${VID_FFMPEG[$H]}
FPS: ${VID_FFMPEG[$FPS]}
Aspect: ${VID_FFMPEG[$ASPECT]}
Audio
Codec: ${VID_FFMPEG[$ACODEC]} (${VID_FFMPEG[$ACNAME]})
Channels: ${VID_FFMPEG[$CHANS]}
=============================================
 
EODUMP
local xar=
if [ "${VID[$ASPECT]}" ]; then
xar=$(keepdecimals "${VID[$ASPECT]}" 4)
[ "$xar" ] && xar=" ($xar)"
fi
cat <<-EODUMP
=========== Combined Identification ===========
Length: $(pretty_stamp ${VID[$LEN]})
Video
Codec: ${VID[$VCODEC]} (${VID[$VCNAME]})
Dimensions: ${VID[$W]}x${VID[$H]}
FPS: ${VID[$FPS]}
Aspect: ${VID[$ASPECT]}$xar
Audio
Codec: ${VID[$ACODEC]} (${VID[$ACNAME]})
Channels: ${VID[$CHANS]}
=============================================
EODUMP
 
 
}
 
# Try to pick some font capable of handling non-latin text
set_extended_font() {
trace $FUNCNAME $@
# This selection includes japanese fonts
local candidates=$(identify -list font | grep 'Font: ' | \
egrep -io '[a-z-]*(kochi|mincho|sazanami|ipafont)[a-z-]*')
if [ -z "$candidates" ]; then
error "Unable to auto-select filename font, please provide one (see -fullhelp)"
return 1
else
if [ "$DEBUG" -eq 1 ]; then
local list=$(echo "$candidates" | sed 's/^/ >/g')
inf "Available non-latin fonts detected:"
inf "$list"
fi
fi
 
# Bias towards the Sazanami family
if grep -qi 'sazanami' <<<"$candidates" ; then
FONT_MINCHO=$(grep -i 'sazanami' <<<"$candidates" | head -1)
else
FONT_MINCHO=$(head -1 <<<"$candidates")
fi
}
 
# Checks if the provided arguments make sense and are allowed to be used
# together
coherence_check() {
trace $FUNCNAME $@
# 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)"
return $EX_USAGE
fi
 
if [ $decoder -eq $DEC_MPLAYER ] && [ -z "$MPLAYER" ]; then
inf "No mplayer available. Using ffmpeg only."
decoder=$DEC_FFMPEG
elif [ $decoder -eq $DEC_FFMPEG ] && [ -z "$FFMPEG" ]; then
inf "No ffmpeg available. Using mplayer only."
decoder=$DEC_MPLAYER
fi
 
if [ $DVD_MODE -eq 1 ] ; then
# Currently it's not allowed to use dvd mode with more than one input
# "file" (in this mode, input files are actually dvd titles of the file
# provided in -V)
if [ $multiple_input_files -eq 1 ]; then
error "Only an input file is allowed in DVD mode"
return $EX_UNAVAILABLE
fi
 
# DVD Mode only works with mplayer, the decoder is changed when
# the DVD mode option is found, so if it's ffmpeg at this point,
# it's by user request (i.e. -F after -V)
if [ $decoder -ne $DEC_MPLAYER ]; then
if [ "$MPLAYER" ]; then
warn "DVD mode requires the use of mplayer, falling back to it"
decoder=$DEC_MPLAYER
else
error "DVD mode requires the use of mplayer."
return $EX_UNAVAILABLE
fi
fi
fi
 
local filter=
if [ $DISABLE_TIMESTAMPS -eq 0 ] &&
local -a filts=( )
has_filter filt_polaroid && has_filter filt_apply_stamp ; then
 
for filter in ${FILTERS_IND[@]} ; do
if [ "$filter" == "filt_polaroid" ]; then
filts=( "${filts[@]}" "$filter" filt_apply_stamp )
elif [ "$filter" == "filt_apply_stamp" ]; then
continue;
else
filts=( "${filts[@]}" $filter )
fi
done
FILTERS_IND=( "${filts[@]}" )
unset filts
fi
# The shoftshadow and randrot filters must be in the correct place
# or they will affect the image incorrectly.
# Additionally the default filters can be disabled from the command
# line (with --disable), they're removed from the filter chain here
local -a filts=( ) end_filts=( )
for filter in ${FILTERS_IND[@]} ; do
case "$filter" in
filt_softshadow)
# Note the newer soft shadows code (1.0.99 onwards) behaves slightly
# differently. On previous versions disabling shadows only affected
# the montage shadow (but e.g. the polaroid mode preserved them),
# this is no longer true
if [ $DISABLE_SHADOWS -ne 1 ]; then
end_filts[100]="filt_softshadow"
fi
;;
filt_apply_stamp)
if [ $DISABLE_TIMESTAMPS -ne 1 ]; then
filts=( "${filts[@]}" "$filter" )
fi
;;
filt_randrot) end_filts[200]="filt_randrot" ;;
*) filts=( "${filts[@]}" "$filter" ) ;;
esac
done
FILTERS_IND=( "${filts[@]}" "${end_filts[@]}" )
 
# Override-able options check, in case they were set from overrides instead
#+of equivalent command-line options. Will check the actual selected values,
#+i.e. fail silently if the overrides aren't effective
[ "$USR_th_height" ] && { check_height "$th_height" || exit $? ; }
[ "$USR_numcaps" ] && { check_numcaps "$numcaps" || exit $? ; }
[ "$USR_interval" ] && { check_interval "$interval" || exit $? ; }
# Interval=0 == default interval
fptest "$interval" -eq 0 && interval=$DEFAULT_INTERVAL
 
sanitise_rename_pattern
}
 
check_height() { # Acceptable height
if ! is_number "$1" && ! is_percentage "$1" ; then
error "Height must be a (positive) number or a percentage. Got '$1'."
return $EX_USAGE
fi
}
 
check_numcaps() { # Acceptable numcaps
if ! is_number "$1" ; then
error "Number of captures must be a (positive) a number! Got '$1'."
return $EX_USAGE
fi
if [ $1 -eq 0 ]; then
error "Number of captures must be greater than 0! Got '$1'."
return $EX_USAGE
fi
}
 
check_interval() { # Acceptable interval
if ! get_interval "$1" >/dev/null ; then
error "Incorrect interval format. Got '$1'."
return $EX_USAGE
fi
}
 
# Main function.
# Creates the contact sheet.
# process($1 = file)
process() {
trace $FUNCNAME $@
local f=$1
 
local numcols=
# Save variables that will be overwritten and must be reset with multiple files
# pre_* will contain the user-supplied or default values
local pre_quirks=$QUIRKS
local pre_aspect_ratio=$aspect_ratio
local pre_output_format="$output_format"
INTERNAL_MAXREWIND_REACHED=0 # Reset for each file
 
# XXX: Some of this should be moved to coherence_check
if [ $DVD_MODE -eq 1 ]; then # DVD Mode
f="$DVD_FILE"
DVD_DEVICE=
local dvdn=$(realpathr "$f") # dvdn might be a device or an ISO
if [ -f "$dvdn" ]; then
# It's an ISO
DVD_MOUNTP=$(get_dvd_image_mountpoint)
if [ -z "$DVD_MOUNTP" ]; then
# Only in Linux does this matter
if ! is_linux ; then
warn "Video properties detection for ISO files is not accurate"
else
warn "Mount DVD image to get better video properties detection"
fi
fi
elif [ ! -r "$dvdn" ]; then
# It's something else we cannot read
error "Can't access DVD ($f)"
return $EX_NOINPUT
else
# It's a device. Note BSD has no concept of block devices.
# It MUST be mounted to continue. This is required to allow ffmpeg detection
#+and to calculate file size
if ! mount | egrep -q "^$dvdn\ " ; then
error "DVD mode requires device ($f) to be mounted"
return $EX_UNAVAILABLE
fi
DVD_DEVICE="$dvdn"
DVD_MOUNTP=$(mount | grep -o "^$DVD_DEVICE *on [^ ]*" | cut -d' ' -f3)
dvdn="DVD"
fi
inf "Processing $dvdn..."
unset dvdn
if ! is_number "$1" ; then
error "DVD Title must be a number (e.g.: \$ vcs -V /dev/dvd 1)"
exit $EX_USAGE
fi
DVD_TITLE=$1
if [ $DVD_TITLE -eq 0 ]; then
local dt="$(lsdvd "$DVD_FILE" 2>/dev/null | grep 'Longest track:' | \
cut -d' ' -f3- | sed 's/^0*//')"
if ! is_number "$dt" ; then
error "Failed to autodetect longest DVD title"
exit $EX_INTERNAL
fi
DVD_TITLE=$dt
DVD_VTS=$(lsdvd -t$DVD_TITLE -v "$DVD_FILE" 2>/dev/null | grep -o 'VTS: [0-9]*' | cut -d' ' -f2)
unset dt
inf "Using DVD Title #$DVD_TITLE (VTS: $DVD_VTS)"
fi
else # Not DVD Mode:
if [ ! -f "$f" ]; then
error "File \"$f\" doesn't exist"
return $EX_NOINPUT
fi
 
inf "Processing $f..."
fi
 
# {{SET_E}} Beware, set -e will break this
identify_video "$f"
local ecode=$?
[ $ecode -eq 0 ] || {
case $ecode in
3) error "Unable to find length of file \"$f\". Can't continue." ;;
4) error "Unable to detect dimensions of file \"$f\". Can't continue." ;;
*) error "Failure while analysing file \"$f\". Can't continue." ;;
esac
return $EX_UNAVAILABLE
}
 
# Identification-only mode?
[ "$UNDFLAG_IDONLY" ] && dump_idinfo && return 0
 
# Vidcap/Thumbnail height
local vidcap_height=$th_height
if is_percentage "$th_height" ; then
local pc=${th_height/%%/} # BASH %% == RE %$
vidcap_height=$(awkex "int ((${VID[$H]} * ${pc}) / 100 + 0.5)")
inf "Height: $th_height of ${VID[$H]} = $vidcap_height"
fi
if ! is_number "$vidcap_height" || [ "$vidcap_height" -eq 0 ]; then
vidcap_height=${VID[$H]}
fi
# -2: DVD Mode autodetection => If ffmpeg/mplayer was unable autodetect, otherwise
#+ honor detected value
[ "-2" == "$aspect_ratio" ] && [ -z "${VID[$ASPECT]}" ] && aspect_ratio=-1
[ "-2" == "$aspect_ratio" ] && [ "${VID[$ASPECT]}" ] && aspect_ratio=0
if [ "0" == "$aspect_ratio" ]; then
if [ "${VID[$ASPECT]}" ]; then
# Aspect ratio in file headers, honor it
aspect_ratio=$(awkex "${VID[$ASPECT]}")
else
aspect_ratio=$(awkex "${VID[$W]} / ${VID[$H]}")
fi
elif [ "-1" == "$aspect_ratio" ]; then
aspect_ratio=$(guess_aspect ${VID[$W]} ${VID[$H]})
inf "Aspect ratio set to $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
# Compute the stamps (if in auto mode)...
if [ $manual_mode -eq 1 ]; then
# Note TIMECODES must be set as an array to get the correct count in
# manual mode; in automatic mode it will be set correctly inside
# compute_timecodes()
TIMECODES=( ${initial_stamps[@]} )
else
TIMECODES=${initial_stamps[@]}
compute_timecodes $timecode_from $interval $numcaps || {
return $?
}
fi
 
local output=$(new_temp_file '-preview.png')
local VIDCAPFILE=00000005.png
 
# If the temporal vidcap already exists, abort
if [ -f $VIDCAPFILE ]; then
error "File $VIDCAPFILE 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=( "${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 $CTX_HL $n || {
local r=$?
error "Failed to apply transformations to the capture."
return $r
}
hlcapfile=$(new_temp_file "-hl-$(pad 6 $n).png")
mvq "$VIDCAPFILE" "$hlcapfile"
capfiles=( "${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 $CTX_STD $n || return $?
 
# identified by capture number, padded to 6 characters
capfile=$(new_temp_file "-cap-$(pad 6 $n).png")
mvq "$VIDCAPFILE" "$capfile"
capfiles=( "${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 $CTX_EXT $n || return $?
 
capfile=$(new_temp_file "-excap-$(pad 6 $n).png")
mvq "$VIDCAPFILE" "$capfile"
capfiles=( "${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
 
local vcodec=${VID[$VCNAME]}
local acodec=${VID[$ACNAME]}
 
if [ "${VID[$CHANS]}" ] && is_number "${VID[$CHANS]}" &&[ ${VID[$CHANS]} -ne 2 ]; then
if [ ${VID[$CHANS]} -eq 1 ]; then
acodec="$acodec (mono)"
else
acodec="$acodec (${VID[$CHANS]}ch)"
fi
fi
 
local csw=$(imw "$output") exw= hlw=
local width=$csw
if [ "$HLTIMECODES" ] || [ "$extended_factor" != "0" ]; then
inf "Merging contact sheets..."
if [ "$HLTIMECODES" ]; then
local hlw=$(imw "$hlfile")
if [ $hlw -gt $width ]; then width=$hlw ; fi
fi
if [ "$extended_factor" != "0" ]; then
local exw=$(imw $extoutput)
if [ $exw -gt $width ]; then width=$exw ; fi
fi
fi
if [ $csw -lt $width ]; then
local csh=$(imh "$output")
# Expand the standard set to the maximum width of the sets by padding both sides
# For some reason the more obvious (to me) convert command-lines lose
# the transparency
convert \( -size $(( ($width - $csw) / 2 ))x$csh xc:transparent \) "$output" \
\( -size $(( ($width - $csw) / 2 ))x$csh xc:transparent \) +append "$output"
unset csh
fi
 
# If there were highlights then mix them in
if [ "$HLTIMECODES" ]; then
# For some reason adding the background also adds padding with:
# convert \( -background LightGoldenRod "$hlfile" -flatten \) \
# \( "$output" \) -append "$output"
# replacing it with a "-composite" operation apparently works
# Expand the highlights to the correct size by padding
local hlh=$(imh "$hlfile")
if [ $hlw -lt $width ]; then
convert \( -size $(( ($width - $hlw) / 2 ))x$hlh xc:transparent \) "$hlfile" \
\( -size $(( ($width - $hlw) / 2 ))x$hlh xc:transparent \) +append "$hlfile"
fi
convert \( -size ${width}x${hlh} xc:LightGoldenRod "$hlfile" -composite \) \
\( -size ${width}x1 xc:black \) \
"$output" -append "$output"
unset hlh
fi
# Extended captures
if [ "$extended_factor" != 0 ]; then
# Already set local exw=$(imw "$extoutput")
local exh=$(imh "$extoutput")
if [ $exw -lt $width ]; then
# Expand the extended set to be the correct size
convert \( -size $(( ($width - $exw) / 2 ))x$exh xc:transparent \) "$extoutput" \
\( -size $(( ($width - $exw) / 2 ))x$exh xc:transparent \) +append "$extoutput"
fi
convert "$output" -background Transparent "$extoutput" -append "$output"
fi
# Add the background; -trim added in 1.11. I'm unsure of why but whithout trimmin extra blank
#+space is added at the top
local dotrim=
[ $DISABLE_SHADOWS -eq 1 ] && [ -z "$HLTIMECODES" ] && dotrim=-trim
convert -background "$bg_contact" "$output" -flatten $dotrim "$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=$(imw "$output")
# 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.
local filename_label="Filename"
local filesize_label="File size"
local filename_value=
local filesize_value=
if [ $DVD_MODE -eq 1 ]; then
# lsdvd is guaranteed to be installed if DVD mode is enabled
local dvd_label=$(lsdvd "$DVD_FILE" 2>/dev/null | grep -o 'Disc Title: .*' | cut -d' ' -f3-)
# Need a mountpoint to get the actual *title* size
if [ "$DVD_MOUNTP" ]; then
filename_label="Disc label"
filename_value="$dvd_label"
filesize_label="Title size"
filesize_value="$(get_dvd_size)"
else
# Not mounted. We can get the disc size but this will include any other titles.
# Since 1.11 mounting DVDs is mandatory to get the title size. Both for ISOs and
#+ devices
filename_value="$(basename "$DVD_FILE") $filename_value (DVD Label: $dvd_label)"
is_linux && warn "DVD not mounted: Can't detect title file size."
filesize_label='Disc image size'
filesize_value="$(get_pretty_size $(dur "$f"))"
fi
else
filename_value="$(basename "$f")"
filesize_value="$(get_pretty_file_size "$f")"
fi
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_label: " \
-font "$fn_font" label:"$filename_value" +append \
\) \
-font "$font_heading" \
label:"$filesize_label: $filesize_value" \
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
 
local wanted_name=${OUTPUT_FILES[$FILEIDX]}
[ "$wanted_name" ] && \
if egrep -q '\.[^\.]+$' <<<"$wanted_name" ; then
output_format=$(filext "$wanted_name")
inf "Output format set from output filename"
else # No file extension in wanted_name
wanted_name="$wanted_name.$output_format"
fi
[ "$wanted_name" ] || wanted_name="$(basename "$f").$output_format"
 
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" "$wanted_name" ) || {
error "Failed to write the output file!"
return $EX_CANTCREAT
}
inf "Done. Output wrote to $output_name"
 
let 'FILEIDX++,1' #,1 so that it's always ok
[ "$UNDFLAG_HANG" ] && read -p 'Main loop paused, hit Enter key to continue... '
cleanup
 
# Re-set variables (for multi-file input)
QUIRKS=$pre_quirks
aspect_ratio=$pre_aspect_ratio
output_format="$pre_output_format"
}
 
# }}} # Core functionality
 
# {{{ # Debugging helpers
 
# Tests integrity of some operations.
# Used to test internal changes for consistency.
# It helps me to identify incorrect optimizations.
# internal_integrity_test(). Running with -D triggers this.
internal_integrity_test() {
local t op val ret comm retval=0
 
# Replacements
local SEQ=$(type -pf seq)
local JOT=$(type -pf jot)
local ex rex
if [ "$SEQ" ]; then
ex=$($SEQ 1 10)
elif [ "$JOT" ]; then
ex=$($JOT 10 1)
else
warn "Can't check seqr() correctness, neither seq nor jot found"
fi
if [ "$ex" ]; then
exr=$(seqr 1 10)
if [ "$exr" != "$ex" ]; then
error "Failed test: seqr() not consistent with external result"
let 'retval++,1'
else
inf "Passed test (seq replacement): consistent result"
fi
fi
 
# Textual tests, compare output to expected output
# Tests are in the form "operation arguments correct_result #Description"
TESTS=( # Note bash2 doesn't like this array as a local variable
# TODO: UNIX vs GNU
#"stonl ..."
 
"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"
#bc result: "pyth_th 16 9 18.35755975068581929849 #FP pythagorean theorem"
#perl result: "pyth_th 16 9 18.3575597506858 #FP pythagorean theorem"
"pyth_th 16 9 18.3576 #FP pythagorean theorem"
 
"get_interval 2h 7200 #Hours parsing"
"get_interval 2m 120 #Minutes parsing"
"get_interval 30S 30 #Seconds parsing"
"get_interval .30 .30 #Milliseconds parsing"
# Since now the numbers are passed to perl, leading zeroes become octal
# numbers. Must ensure they are handled correctly
"get_interval 09h010m09s1 33010 #Parsing with leading zeroes"
"get_interval 0400 400 #Parsing shorthand"
# Extended syntax
"get_interval 30m30m1h 7200 #Repeated minutes parsing"
)
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=$(grep -o "[^ ]* #$comm\$"<<<$t | cut -d' ' -f1)
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
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=$(grep -o "[^ ]* #$comm\$"<<<$t | cut -d' ' -f1)
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
local inff=inf
[ "$HAS_COLORS" ] && inff=infplain
$inff "Video Contact Sheet *NIX v${VERSION}, (c) 2007-2010 Toni Corvera" "sgr0"
}
 
# Prints the list of options to stdout
# show_help($1 = long = '')
show_help() {
local P=$(basename $0)
local showlong=$1
local mpchosen= ffchosen= longhelp= funkyex=
[ -z "$MPLAYER" ] && mpchosen=' [Not available]'
[ "$MPLAYER" ] && [ $decoder == $DEC_MPLAYER ] && mpchosen=' [Selected]'
[ -z "$FFMPEG" ] && ffchosen=', Not available'
[ "$FFMPEG" ] && [ $decoder == $DEC_FFMPEG ] && ffchosen=', Selected'
# This portion of help is only shown when in full help mode (--fullhelp)
[ "$showlong" ] && longhelp=\
" --anonymous Disable the 'Preview created by' line in the footer.
-Ij|-Ik|-Ij=fontname|-Ik=fontname
--nonlatin Use an alternate font in the heading for the video file
name. Required to display correctly file names in
some languages (Chinese, Japanese, Hangul,
Cyrillic, ...).
Will try to use a reasonable font. Can also be set
manually like:
$ vcs -Ij=Sazanami-Mincho-Regular file.avi
or
$ vcs -Ij=/usr/share/fonts/ttf/ttf-japanese-mincho.ttf
Use \"identify -list font\" to list the available fonts
-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\"-).
 
Tweaks and workarounds:
-Ws Increase length of safe measuring (try harder). Repeat
to increase further.
-WS Scan all video, if required, to get a safe measuring.
-Wp Increase safe measuring precission (i.e. halve the
probe stepping). Repeat to increase further.
-WP Inverse of -Wp.
-Wo Change ffmpeg's arguments order, might work with some
files that fail otherwise.
-Wc Disable colour in console messages.
Obscure options, debugging tools and workarounds:
-R <file>
--randomsource <file> Use the provided file as a source for random \"values\":
they won't be random anymore, so two runs with the same
source and same arguments will produce the same output
in modes which use using randomisation (e.g. the
\"photos\" and \"polaroid\" modes).
-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.
"
# The --funky help is really long, so make it shorter by default,
# only show the complete help when --fullhelp is used
[ "$showlong" ] && funkyex="
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.
\"polaroidframe\": Use '-kL' or '--funky polaroidframe'
Adds a polaroid picture-like white frame to each
image.
\"photos\": Use '-kc' or '--funky photos'
Combination of rotate, photoframe and overlap.
Same as -kp -kr -ko.
\"polaroid\": Use '-kp' or '--funky polaroid'
Combination of rotate, polaroidframe and overlap.
Same as -kL -kr -ko.
\"film\": Use '-ki' or '--funky film'
Imitates filmstrip look.
\"random\": Use '-kx' or '--funky random'
Randomizes colours and fonts."
[ -z "$showlong" ] && funkyex="
Available: overlap, rotate, photoframe, polaroidframe,
photos, polaroid, film, random
Use --fullhelp for more details."
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.
-o|--output <file> File name of output. When ommited will be derived from
the input filename. Can be repeated for multiple files.
-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.
-T|--title <arg> Add a title above the vidcaps.
-j|--jpeg Output in jpeg (by default output is in png).
-j2|--jpeg 2 Output in jpeg 2000
-V|--dvd <file.iso|dvd_device>
DVD Mode, use file.iso as DVD. In this mode the
<file> argument must point to the title number, e.g.:
$ vcs -V somedvd.iso 1
Passing title 0 will use the default (longest) title.
$ vcs -V /dev/dvd 0
Implies -A (auto aspect ratio)
-M|--mplayer Use Mplayer to capture$mpchosen
-F|--ffmpeg Use FFmpeg to capture [Default$ffchosen]
-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.
-q|--quiet Don't print progess messages just errors. Repeat to
mute completely even on error.
-h|--help Show basic help and exit.
--fullhelp Show the complete help and exit.
-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
* padding: use -dp or --disable padding
(note shadows introduce some extra padding)
-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.
-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.
-k <arg>
--funky <arg> Funky modes:$funkyex
$longhelp
Examples:
Create a contact sheet with default values (vidcaps at intervals of
$DEFAULT_INTERVAL seconds), will be saved to 'video.avi.png':
\$ $P video.avi
 
Create a sheet with vidcaps at intervals of 3 and a half minutes, save to
'output.jpg':
\$ $P -i 3m30 input.wmv -o output.jpg
 
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
 
#### Entry point ####
 
# Important to do this before any message can be thrown
init_feedback
 
# Ensure $GETOPT is GNU/Linux-style getopt
choose_getopt
 
# Execute exithdlr on exit
trap exithdlr EXIT
 
show_vcs_info
 
# Test requirements. Important, must check before looking at the
# command line (since getopt is used for the task)
test_programs
 
# The command-line overrides any configuration. And the configuration
# is able to change the program in charge of parsing options ($GETOPT)
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]]
# TODO: Why does FreeBSD's GNU getopt ignore -n??
TEMP=$("$GETOPT" -n "$0" -s bash \
-o i:n:u:T:f:t:S:j::hFMH:c:ma:l:De::U::qAO:I:k:W:E:d:V:R:Z:o:P: \
--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:,dvd:,randomsource:,undocumented:,output:,fullhelp,profile:,"\
"jpeg2,nonlatin" \
-- "$@")
eval set -- "$TEMP"
 
while true ; do
case "$1" in
-i|--interval)
check_interval "$2"
interval=$(get_interval "$2")
timecode_from=$TC_INTERVAL
USR_interval=$interval
USR_timecode_from=$TC_INTERVAL
shift # Option arg
;;
-n|--numcaps)
check_numcaps "$2"
numcaps="$2"
timecode_from=$TC_NUMCAPS
USR_numcaps="$2"
USR_timecode_from=$TC_NUMCAPS
shift # Option arg
;;
-o|--output)
current=${#OUTPUT_FILES[@]}
OUTPUT_FILES[$current]="$2"
shift ;;
-u|--username) user="$2" ; USR_user="$user" ; shift ;;
-U|--fullname)
# -U accepts an optional 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" ; USR_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
USR_fromtime="$fromtime"
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
USR_end_offset="$end_offset"
shift
;;
-t|--to)
if ! totime=$(get_interval "$2") ; then
error "Ending timestamp must be a valid timecode. Got '$2'."
exit $EX_USAGE
fi
if fptest "$totime" -eq 0 ; then
error "Ending timestamp was set to 0, set to movie length."
totime=-1
fi
USR_totime=$totime
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=( "${HLTIMECODES[@]}" "$temp" )
shift
;;
--jpeg2) # Note --jpeg 2 is also accepted
output_format=jp2
USR_output_format=jp2
;;
-j|--jpeg)
if [ "$2" ]; then # Arg is optional, 2 is for JPEG 2000
# 2000 is also accepted
if [ "$2" != "2" ] && [ "$2" != "2000" ]; then
error "Use -j for jpeg output or -j2 for JPEG 2000 output. Got '-j$2'."
exit $EX_USAGE
fi
output_format=jp2
else
output_format=jpg
fi
USR_output_format="$output_format"
shift
;;
-h|--help) show_help ; exit $EX_OK ;;
--fullhelp) show_help 'full' ; exit $EX_OK ;;
--shoehorn)
warn "$1 is deprecated, please use '--undocumented shoehorn=\"$2\"' instead"
shoehorned="$2"
shift
;;
-F) decoder=$DEC_FFMPEG ; USR_decoder=$decoder ;;
-M) decoder=$DEC_MPLAYER ; USR_decoder=$decoder ;;
-H|--height)
check_height "$2"
th_height="$2"
USR_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"
USR_aspect_ratio="$2"
shift
;;
-A|--autoaspect) aspect_ratio=-1 ; USR_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"
USR_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
USR_extended_factor=$extended_factor
shift
;;
--mincho)
warn "--mincho is deprecated, please use -Ij or --nonlatin instead"
if [ -z "$USR_FONT_MINCHO" ]; then
set_extended_font
inf "Filename font set to '$FONT_MINCHO'"
fi
;;
# Unlike -I, --nonlatin does not accept a font name
--nonlatin)
if [ -z "$USR_FONT_MINCHO" ]; then
set_extended_font
inf "Filename font set to '$FONT_MINCHO'"
fi
;;
-I)
# Extended/non-latin font
# New syntax introduced in 1.11:
# -Ij: Try to pick automatically a CJK font. Might fail and abort
# -Ij='Font name or file': Set font manually
 
if [ "$2" ] ; then
# If an argument is passed, test it is one of the known ones
case "$2" in
k|j|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;
if [ ${#2} -gt 1 ]; then
# j=, k= syntax
FONT_MINCHO="${2:2}"
USR_FONT_MINCHO="$FONT_MINCHO"
inf "Filename font set to '$FONT_MINCHO'"
fi
# If the user didn't pick one, try to select automatically
if [ -z "$USR_FONT_MINCHO" ]; then
set_extended_font
inf "Filename font set to '$FONT_MINCHO'"
fi
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
if grep -q 'GETOPT=' <<<"$2" ; then
# If we're here, getopt has already been found and works, so it makes no
# sense to override it; on the other hand, if it hasn't been correctly
# set/detected we won't reach here
warn "GETOPT can't be overridden from the command line."
else
override "$2" "command line"
fi
shift
;;
-W)
case "$2" in
# (classic) Workaround mode. See wa_ss_* declarations at the start for details
o) wa_ss_af='-ss ' ; wa_ss_be='' ;;
# Console colout
# Once: Disable console colour, use prefixes instead
# Twice: Disable prefixes too
c)
set_feedback_prefixes
[ "$UNDFLAG_NOPREFIX" ] && plain_messages=1
UNDFLAG_NOPREFIX=1
;;
# Double length of video probed in safe measuring
# Semi-undocumented traits:
# - Can be repeated, will double for each instance
# - -Ws -Ws -Ws = -Ws3
s|s[0-9]|s[0-9][0-9])
[ ${#2} -gt 1 ] && n=${2:1} || n=1
QUIRKS_MAX_REWIND=$(awkex "$QUIRKS_MAX_REWIND * (2^$n)")
let 'INTERNAL_WS_C+=n,1'
;;
# Brute force -Ws: Test all the length of the file if required
S) QUIRKS_MAX_REWIND=-1 ;;
# Increase precission of safe length measuring (halve the stepping)
# Like -Ws can be repeated
p|p[0-9]|p[0-9][0-9])
[ ${#2} -gt 1 ] && n=${2:1} || n=1
QUIRKS_LEN_STEP=$(awkex "$QUIRKS_LEN_STEP / (2^$n)")
let 'INTERNAL_WP_C+=n,1'
;;
# Inverse of -Wp: Decrease precission of safe length measuring
# i.e.: will try less times <-> will be quicker but less accurate
# desirable when -Ws or -WS are used.
# Can also be repeated
P|P[0-9]|P[0-9][0-9])
[ ${#2} -gt 1 ] && n=${2:1} || n=1
QUIRKS_LEN_STEP=$(awkex "$QUIRKS_LEN_STEP * (2^$n)")
let 'INTERNAL_WP_C-=n,1'
;;
# -Wb (Semi-undocumented): Disable safe mode. Use this to force accepting
#+broken/partial files. Only makes sense when testing or in combination
#+with stuff like '-Z idonly'
b) QUIRKS=-2 ;; # Quirks < 0 : No safe mode
*)
error "Wrong argument. Use --fullhelp for a list available workarounds. Got -W$2."
exit $EX_USAGE
;;
esac
shift
;;
-k|--funky) # Funky modes
case "$2" in # Note older versions (<1.0.99) were case-insensitive
p|polaroid) # Same as overlap + rotate + polaroid
inf "Changed to polaroid funky mode."
FILTERS_IND=( "${FILTERS_IND[@]}" 'filt_polaroid' 'filt_randrot' )
CSHEET_DELEGATE='csheet_overlap'
# XXX: The newer version has a lot less flexibility with these many
# hardcoded values...
grav_timestamp=South
fg_tstamps=Black
bg_tstamps=Transparent
pts_tstamps=$(( $pts_tstamps * 3 / 2 ))
;;
c|photos) # Same as overlap + rotate + photoframe, this is the older polaroid
inf "Changed to photos funky mode."
FILTERS_IND=( "${FILTERS_IND[@]}" 'filt_photoframe' 'filt_randrot' )
CSHEET_DELEGATE='csheet_overlap'
# The timestamp must change location to be visible most of the time
grav_timestamp=NorthWest
;;
o|overlap) # Random overlap mode
CSHEET_DELEGATE='csheet_overlap'
grav_timestamp=NorthWest
;;
r|rotate) # Random rotation
FILTERS_IND=( "${FILTERS_IND[@]}" 'filt_randrot' )
;;
f|photoframe) # White photo frame
FILTERS_IND=( "${FILTERS_IND[@]}" 'filt_photoframe' )
;;
L|polaroidframe) # White polaroid frame
FILTERS_IND=( "${FILTERS_IND[@]}" 'filt_polaroid ')
grav_timestamp=South
fg_tstamps=Black
bg_tstamps=Transparent
pts_tstamps=$(( $pts_tstamps * 3 / 2 ))
;;
i|film)
inf "Enabled film mode."
FILTERS_IND=( "${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
;;
-P|--profile)
case "$2" in
classic) # Classic colour scheme
bg_heading=YellowGreen bg_sign=SlateGray bg_contact=White
bg_title=White fg_heading=Black fg_sign=Black
;;
1.0) # 1.0a, 1.0.1a and 1.0.2b colourscheme
bg_heading=YellowGreen bg_sign=SandyBrown bg_contact=White
bg_title=White fg_heading=Black fg_sign=Black
;;
esac
shift
;;
-R|--randomsource)
if [ ! -r "$2" ]; then
error "Random source file '$2' can't be read"
exit $EX_USAGE
fi
init_filerand "$2"
inf "Using '$2' as source of semi-random values"
RANDFUNCTION=filerand
shift
;;
-d|--disable) # Disable default features
case $(tolower "$2") in
# timestamp (with no final s) is undocumented but will stay
t|timestamps|timestamp)
if [ $DISABLE_TIMESTAMPS -eq 0 ]; then
inf "Timestamps disabled."
# They'll be removed from the filter chain in coherence_check
DISABLE_TIMESTAMPS=1
fi
;;
s|shadows|shadow)
if [ $DISABLE_SHADOWS -eq 0 ]; then
inf "Shadows disabled."
# They will be removed from the filter chain in coherence_check
DISABLE_SHADOWS=1
fi
;;
p|padding)
if [ $HPAD -ne 0 ] ; then
inf "Padding disabled." # Kinda...
HPAD=0
fi
;;
*)
error "Requested disabling unknown feature. Got '$2'."
exit $EX_USAGE
;;
esac
shift
;;
-V|--dvd)
# XXX; Are there systems with no perl???
if ! type -pf perl >/dev/null ; then
error "DVD support requires perl"
exit $EX_UNAVAILABLE
fi
# DVD Mode requires lsdvd
if ! type -pf lsdvd >/dev/null ; then
error "DVD support requires the lsdvd program"
exit $EX_UNAVAILABLE
fi
DVD_MODE=1
DVD_FILE="$2"
decoder=$DEC_MPLAYER
aspect_ratio=-2 # Special value: Auto detect only if ffmpeg couldn't
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
;;
-Z|--undocumented)
# This is a container for, of course, undocumented functions
# These are used for testing/debugging purposes. Might (and will)
# change between versions, break easily and do no safety checks.
# In short, don't look at them unless told to do so :P
case "$2" in
# AWK was used for a little while in a WiP version
#set_awk=*) AWK="$(cut -d'=' -f2<<<"$2")" ; warn "[U] AWK=$AWK" ;;
# Hang the main process loop just before cleanup.
hang) UNDFLAG_HANG="On" ; warn "[U] Hang flag" ;;
# Print identification results, do nothing else
idonly) UNDFLAG_IDONLY="On" ; warn "[U] Id only" ;;
# ffmpeg path
set_ffmpeg=*)
FFMPEG=$(realpathr "$(cut -d'=' -f2<<<"$2")")
assert $LINENO 'test -x "$FFMPEG"'
warn "[U] FFMPEG=$FFMPEG"
;;
# mplayer path
set_mplayer=*)
MPLAYER=$(realpathr "$(cut -d'=' -f2<<<"$2")")
assert $LINENO 'test -x "$MPLAYER"'
warn "[U] MPLAYER=$MPLAYER"
;;
# Ignore one of the players
disable_ffmpeg)
FFMPEG=''
warn "FFmpeg disabled"
assert $LINENO "[ '$MPLAYER' ] || [ '$FFMPEG' ]"
decoder=$DEC_MPLAYER
;;
disable_mplayer)
MPLAYER=''
warn "Mplayer disabled"
assert $LINENO "[ '$MPLAYER' ] || [ '$FFMPEG' ]"
decoder=$DEC_FFMPEG
;;
# This is an old option from the first versions when the script
# failed a lot more, I haven't used it for years and I don't think
# anyone would need it anymore but I'll keep it at least for
# a few more versions
shoehorn=*)
shoehorned="$(cut -d'=' -f2-<<<"$2")"
;;
*) false ;;
esac
shift
;;
-D) # Repeat to just test consistency
if [ $DEBUGGED -gt 0 ]; then
[ $decoder -eq $DEC_MPLAYER ] && d='mplayer'
[ $decoder -eq $DEC_FFMPEG ] && d='ffmpeg'
infplain '[ svn $Rev$ ]'
cat >&2 <<-EOD
=== Setup ===
GETOPT: $GETOPT
MPLAYER: $MPLAYER
FFMPEG: $FFMPEG
AWK: $(type -pf awk)
Filterchain: [ ${FILTERS_IND[*]} ]
Decoder: $d
Safe step: $QUIRKS_LEN_STEP
=== Versions ===
Bash: $BASH_VERSION
Getopt: $($GETOPT --version)
EOD
# FIXME: Any portable way to print AWK version?
exit
fi
DEBUG=1
inf "Testing internal consistency..."
internal_integrity_test && warn "All tests passed" || error "Some tests failed!"
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
elif [ "$2" ]; then
multiple_input_files=1
fi
# }}} # Command line parsing
 
# The coherence check ensures the processed options are
# not incoherent/incompatible with the input files or with
# other given options
coherence_check || {
exit $?
}
 
set +e # Don't fail automatically. Blocks marked with {{SET_E}} will break if this changes
for arg do process "$arg" ; done
 
# Script ends here, everything below are comments
# ===========================================================================
#
# Bash syntax notes # {{{
# These are some notes for my own reference (or for those trying to read the script)
# regarding bash syntax nuissances.
#
# * herestring redirection, '<<<$string', (used extensively in vcs) was introduced in bash 2.05b
# * sed s/[ ,]/ * /g <=> ${var//[ ,]/ * } [Much faster due to not forking]
# sed s/[ ,]/ * / <=> ${var/[ ,]/ * }
# * bash2: declaring local empty arrays like 'local a=( )' makes bash think they're strings
# 'local -a' must be used instead
# bash3 has no problem with this
# * bash2: 'arr+=( elem )' for array push is not supported, use 'arr=( "${arr[@]}" elem )' instead
# += is a bash3 syntax modification, bash3.1 extended it further, arithmetic += works
# inside let
# * bash2: [*] expands as a string while [@] expands as an array. Both have trouble with spaces
# in elements though
# * performance: bash loops are often slower than awk or perl
# * performance: grep + cut proved faster than an equivalent sed -r s// replacement
# }}} # Bash syntax notes
#
# vim:set ts=4 ai foldmethod=marker nu: #
Property changes:
Added: svn:executable
Added: svn:keywords
+Rev Id Date
\ No newline at end of property
/video-contact-sheet/tags/1.11.1/rpm-package/vcs.spec.in
0,0 → 1,97
#
# spec file for vcs rpm
#
# based on mp3plot's which in turn was based on other sources
#
 
%define is_mandrake %(test -e /etc/mandrake-release && echo 1 || echo 0)
%define is_suse %(test -e /etc/SuSE-release && echo 1 || echo 0)
%define is_fedora 0%{?fedora}
%define is_redhat 0%{?rhl}
%define is_rhel 0%{?rhel}
 
%define distname generic
%define disttag .generic
 
%if %{is_fedora}
%define distname fedora
%define disttag %{dist}
%endif
%if %{is_redhat}
%define distname redhat
%define disttag %{dist}
%endif
%if %{is_mandrake}
%define distname mandrake
%define disttag .mdk
%endif
%if %{is_suse}
%define distname suse
%define disttag .suse
%endif
%if %{is_rhel}
%define distname rhel
%define disttag %{dist}
%endif
 
Name: vcs
Summary: Tool to create contact sheets (previews) from videos
Version: @VERSION@
Release: 1%{?disttag},upstream
License: LGPL
Packager: @PACKAGER@
Group: Applications/Multimedia
Source0: http://p.outlyer.net/%{name}/files/%{name}-%{version}.tar.gz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-build
BuildArch: noarch
# TODO: How to set alternative dependencies? i.e. mplayer/ffmpeg
Requires: mplayer / ffmpeg
Requires: bash >= 2.05b
Requires: ImageMagick >= 6.3.5-7
Requires: coreutils
URL: http://p.outlyer.net/vcs/
#BuildRequires:
#Prereq: /sbin/ldconfig
#Requires:
AutoReqProv: yes
## Allow relocation (e.g. rpm --prefix /opt/vcs)
Prefix: /usr
 
%description
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.
 
%prep
#echo %_target
echo Building %{name}-%{version}-%{release}
 
%setup -q -n %{name}-%{version}
 
%build
 
%install
make DESTDIR=%buildroot install
 
%clean
[ ${RPM_BUILD_ROOT} != "/" ] && rm -rf ${RPM_BUILD_ROOT}
 
#%post
# postinst
 
#%postun
# postuninst
 
%files
%defattr(-,root,root)
# binary
%{_bindir}/%{name}
# Manpage
#%{_mandir}/man1/%{name}.1.gz
%doc CHANGELOG
 
%changelog
* Sun Mar 07 2010 - outlyer (at) gmail (dot) com
- Initial RPM packaging
 
Property changes:
Added: svn:keywords
+Rev Id Date
\ No newline at end of property
/video-contact-sheet/tags/1.11.1/rpm-package/Makefile
0,0 → 1,33
# $Id$
 
prefix:=/usr
DESTDIR:=/
VERSION:=$(shell head -50 vcs | grep 'declare -r VERSION=' | perl -pe 's/.*"(.*)".*/\1/')
#PACKAGER=$(shell grep ^`id -nu` /etc/passwd | cut -d: -f5 | cut -d, -f1)
 
all:
@echo 'There'\''s nothing to be built'
@echo 'Available make commands:'
@echo '* Build RPM (and generate spec)'
@echo ' $$ make rpm'
@echo '* Generate spec file'
@echo ' $$ make spec'
@echo '* Install'
@echo ' $$ make install'
 
clean:
$(RM) vcs.spec
 
spec: vcs.spec.in
test "$(VERSION)"
@test "$(PACKAGER)" || echo "No PACKAGER set!"
@test "$(PACKAGER)"
cat vcs.spec.in | sed 's!@VERSION@!$(VERSION)!g' | \
sed 's!@PACKAGER@!$(PACKAGER)!g' > vcs.spec
 
install:
mkdir -p $(DESTDIR)$(prefix)/bin
install -m755 -o0 -g0 vcs $(DESTDIR)$(prefix)/bin
 
 
.PHONY: all install clean spec
Property changes:
Added: svn:keywords
+Rev Id Date
\ No newline at end of property
/video-contact-sheet/tags/1.11.1/Makefile
0,0 → 1,63
#!/usr/bin/make -f
# $Id$
 
VER=$(shell grep VERSION vcs | head -n1 | sed 's/\#.*//' | 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
 
check-rel:
@if head -n50 vcs | grep -q 'RELEASE=0' ; then \
echo 'RELEASE is set to 0!' ; false ; fi
 
prep:
cp vcs CHANGELOG debian-package/
cp vcs rpm-package/
 
dist: check-rel check-no-svn prep gz bz2 plaintext changelog deb rpm cleanup
 
gz:
cp vcs vcs-$(VER)
chmod -x vcs-$(VER)
gzip -9 vcs-$(VER)
 
bz2:
cp vcs vcs-$(VER)
chmod -x vcs-$(VER)
bzip2 -9 vcs-$(VER)
 
plaintext:
cp vcs vcs-$(VER)
chmod -x vcs-$(VER)
 
changelog:
gzip -9 CHANGELOG
gzip -dc CHANGELOG.gz > CHANGELOG
 
cleanup:
$(RM) vcs Makefile *.changes
$(RM) -r debian-package
$(RM) -r rpm-package
 
deb:
cd debian-package/ && dpkg-buildpackage -rfakeroot -us -uc -b
 
rpm: vcs.spec
mkdir rpm-package/vcs-$(VER)/
cp vcs CHANGELOG rpm-package/Makefile rpm-package/vcs-$(VER)/
mv vcs.spec rpm-package/vcs-$(VER)/
cd rpm-package && tar zcvf vcs-$(VER).tar.gz vcs-$(VER)
$(RM) vcs.spec
$(RM) -r rpm-package/vcs-$(VER)
cd rpm-package && fakeroot rpmbuild -tb vcs-$(VER).tar.gz
-ln -s ~/rpmbuild/RPMS/noarch/vcs-$(VER)-*.rpm .
$(RM) rpm-package/vcs-$(VER).tar.gz
 
vcs.spec: rpm-package/vcs.spec.in
cd rpm-package && $(MAKE) -f Makefile spec PACKAGER="$(PACKAGER)"
mv rpm-package/vcs.spec .
 
.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.11.1/tests/test_funkymodes
0,0 → 1,27
#!/usr/bin/env bash
 
# Allow setting from the environment
[ "$vcs" ] || vcs='vcs'
 
if [ -z "$1" ]; then
echo "Usage: $0 <file>"
exit 1
fi >&2
 
HEIGHT="-H240"
 
echo "Using vcs: $vcs" >&2
 
yes 01234 | head -n200 > randsource
 
BN=$(basename "$1")
echo ">> Standard <<" >&2
$vcs -n4 -c2 $HEIGHT "$1" -o "$BN-std.jpg"
echo ">> Polaroid <<" >&2
$vcs -n6 -c3 -k polaroid $HEIGHT "$1" -R randsource -o "$BN-polaroid.jpg"
echo ">> Photos <<" >&2
$vcs -n6 -c3 -k photos $HEIGHT "$1" -R randsource -o "$BN-photos.jpg"
echo ">> Filmstrip <<" >&2
$vcs -n8 -c2 -k film $HEIGTH "$1" -R randsource -o "$BN-film.jpg"
 
rm -f randsource
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.11.1
Property changes:
Added: svn:mergeinfo
Merged /video-contact-sheet/branches/1.0a:r262-263
Merged /video-contact-sheet/tags/1.11:r381,385-387
Merged /video-contact-sheet/branches/1.0.100a:r364-371
Merged /video-contact-sheet/tags/1.0.12:r352-356
Merged /video-contact-sheet/tags/1.0.11:r344-345
Merged /video-contact-sheet/tags/1.0.2b:r274
Merged /video-contact-sheet/tags/1.0.8a:r319-320
Merged /video-contact-sheet/branches/1.11:r375-379,382-383
Merged /video-contact-sheet/tags/0.99a:r261
Merged /video-contact-sheet/branches/1.11.1:r389-390
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.12:r347-350
Merged /video-contact-sheet/branches/1.0.1a:r266-267
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.5b:r284-285
Merged /video-contact-sheet/branches/1.0.4b:r280-281
Merged /video-contact-sheet/branches/1.0.7a:r294-311
Merged /video-contact-sheet/branches/1.0.6b:r289-290
Merged /video-contact-sheet/branches/1.0.9a:r322-325
Merged /video-contact-sheet/branches/1.0.99:r358-361
Merged /video-contact-sheet/branches/1.0.8a:r315-317
/video-contact-sheet/tags/1.11/vcs
0,0 → 1,4030
#!/usr/bin/env bash
#
# $Rev$ $Date$
#
# vcs
# Video Contact Sheet *NIX: Generates contact sheets (previews) of videos
#
# Copyright (C) 2007, 2008, 2009, 2010 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>
#
# (Note: The references that used to be here have been moved to
#+ <http://p.outlyer.net/dox/vcs:devel:references>)
 
declare -r VERSION="1.11"
declare -r RELEASE=1
 
# {{{ # CHANGELOG
# Last release changes:
# (The full changelog can be found at <http://p.outlyer.net/vcs/files/CHANGELOG>).
#
# 1.11:
# * FEAT: Allow setting output filename. With extension will set output
# format, without will inherit it.
# * FEAT: Require mplayer OR ffmpeg instead of both. Having both is still
# recommended for better results.
# * FEAT: Safe mode, for files whose length doesn't get reported correctly.
# Completely automated.
# Number of tries can be increased with -Ws. Repeat to increase
# further. Use -WS to do try as many times as possible.
# Accuracy (stepping) can be increased with -Wp. Repeat to increase
# accuracy. Decrease with -WP.
# Can be deliberately disabled with -Wb to force processing of
# broken files. *VCS WITH -Wb WILL FAIL ON BROKEN FILES*
# * FEAT: Allow percentages in height.
# * BUGFIX: Don't pass ms to mplayer. It ignores them anyway and in some
# rare cases breaks the last capture (possibly due to the 5-frames
# hack)
# * BUGFIX: Honor detected aspect ratio if found
# * BUGFIX: Try to detect files that might fail on the last capture and
# trigger safe mode
# * BUGFIX: Timestamps font was being ignored. As a side effect this produced
# italiced timestamps in some systems.
# * BUGFIX: Fixed obscure bug with safe_rename_pattern overrides
# * COMPAT: Support for bash 2.05b. This will (probably) be the last version
# capable of running under bash 2.
# * DVD mode revamp
# - Print title file size instead of disc size when possible
# - Aspect ratio detection, if available
# - Use of FFmpeg if available to get better information
# - Mostly x-platform, only ISOs identification is a bit better in Linux
# * Added FourCCs: 3IV1, 3IV2 (3ivx); s263 (H.263); mp4v, MP4V, H264
# (MPEG-4 and AVC in mov/mp4), VP6F (VP6 Flash Version), AMR
# Video codec renamings:
# - TechSmith codec name shortened to TechSmith SCC
# - Raw RGB renamed to Raw video
# * Help cleanup. The default help output is much shorter, the full text
# can be displayed with --fullhelp. Also print the decoder choice near
# the appropriate option (-M/-F)
# * Added --anonymous to help (never was in it)
# * Drop requirement on seq/jot and bc, replaced by inline awk
# ... New requirement: Perl. It's required by POSIX anyway
# * Adopt new/fixed numbering scheme
# <http://p.outlyer.net/dox/vcs:devel:renumbering>
# * Check ImageMagick version (must decide which is the real minimum
# required)
# * Non-latin fonts revamp:
# - -I no longer works alone (use -Ij or -Ik instead)
# - -Ik, -Ij and --nonlatin try to pick an appropriate font automatically
# - -I accepts a font name or font filename like
# -Ij=Kochi-Mincho-Regular or
# -Ij=/usr/share/fonts/truetype/kochi/kochi-mincho.ttf
# * Deprecated options:
# --shoehorn: Will be removed unless someone really needs it.
# --mincho: Replaced by --nonlatin
# * COSMETIC:
# - Default font switched to DejaVu Sans.
# Font sizes reduced to accomodate the new default.
# Should fall back to a sane default if it's not available
# - Much tighter padding
# - Smaller timestamps font by default
# - Print friendlier timestamp when a capture fails
# - Print program signature to console without colour
# - Use main font by default in timestamps
# - Heading background colour toned down
# - Added colourised output when tput is not capable (i.e. FreeBSD)
# - Added prefixes when colour is not available for console output
# - Don't print lsdvd error channel is DVD mode
# - Suppress mv errors (e.g. over VFS being unable to preserve)
# * UNDOCUMENTED/DEBUG:
# - Allow stopping the main loop before cleaning up (--undocumented hang)
# - Identification-only mode. Might be promoted to an actual feature
# (--undocumented idonly)
# - Allow setting ffmpeg and mplayer path (--undocumented set_ffmpeg and
# set_mplayer)
# - Allow disabling either mplayer of ffmpeg (as if they weren't
# installed (--undocumented disable_ffmpeg and disable_mplayer)
# - Added -Wc to disable console colour, repeat to disable prefixes
# * INTERNAL: assert()
# * INTERNAL: Cleanup: correctness checks converted to asserts, removal
# of old dead code, fixed typos in comments
# * Fixed various typos and cut out lines in CHANGELOG
# * Minimum ImageMagick version set to 6.3.5-7
# * Better detection of requirements (e.g. disallow decoders without png
# support)
# * Allow overriding height, number of captures, interval, columns, and
# padding
# * Added -dp (--disable padding) equivalent to overriding HPAD to 0
# }}} # CHANGELOG
 
set -e
 
# Fail soon if this version of bash is too old for the syntax, don't expose bash to the newer
# syntax
# See the "Bash syntax notes" section for details
[ "$BASH_VERSINFO" ] && {
# Absolute minimum right now is 2.05b
if [ "${BASH_VERSINFO[0]}" -le 2 ]; then # Only bash <=2 needs extra testing
# I guess we can't expect any new bash2 release
if [ "${BASH_VERSINFO[0]}" -lt 2 ] ||
( [ "${BASH_VERSINFO[0]}" -eq 2 ] && [ "${BASH_VERSINFO[1]}" != '05b' ] ); then
echo "Bash 2.05b or higher is required" >&2
exit 1
fi
fi
}
 
# {{{ # TO-DO
# TODO / FIXME:
# * (1.12) Start replacing 'grep' with bash's '[[ =~ ]]'. Will break bash 2 compatibility for good
# * [[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. =>
#+ SUS v2: egrep is deprecated, grep -E replaces it
# * Change default DVD_TITLE to 0
# * Deprecations:
# OPTION/VAR -> ALTERNATIVE DEPRECATED FROM VERSION REMOVAL ETA
# --shoehorn -> --undocumented shoehorn 1.11 1.12
# --undocumented shoehorn -> NONE 1.12 1.13 or 1.14
# --funky -> --profile ? ?+1
# MIN_LENGTH_FOR_END_OFFSET -> none 1.11 1.12 or 1.13
# * No warning shown in 1.11. 1.12 will switch back default end offset either to 0 or a
# percentage *and* use it always (disable with -E0)
# end offset -> none ? ?+1
# --mincho -> --nonlatin 1.11 1.12
# * Better check for coherence of overrides
# * Variables cleanup:
# Variables will use a more uniform scheme, with prefixes where appropriate:
# - INTERNAL_*: Used internally to adapt messages and the like to the input
# - UNDFLAG_*: Undocumented flags. Used internally to keep track of undocumented modes (-Z)
# - USR_*: Holds values of variables as set by the user, either from overrides or from the
# command-line
# }}} # TO-DO
 
# {{{ # 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 config, 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 ASPECT=8 VCNAME=9 ACNAME=10
# 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
# GETOPT must be correctly set or the script will fail.
# It can be set in the configuration files if it isn't in the path or
# the first getopt in the path isn't the right version.
# A check will be made and a warning with details shown if required.
declare GETOPT=getopt
# 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='#afcd7a' # Background for meta info (size, codec...)
declare bg_sign=SlateGray #'#a2a9af' # Background for signature
declare bg_title=White # Background for the title (see -T)
declare bg_contact=White # Background for the captures
declare bg_tstamps='#000000aa' # Background for the timestamps box
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 for the title
# Fonts, use identify -list font to get the list, up to IM 6.3.5-7 was '-list type' [[IM1]]
# If a font is not available IM will pick a sane default. In theory it will be silent
# although in practica it prints an error
declare font_tstamps=DejaVu-Sans-Book # Used for timestamps over the thumbnails
declare font_heading=DejaVu-Sans-Book # Used for the meta info heading
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 for the title (see -T)
# Font sizes, in points
declare -i pts_tstamps=14 # Used for the timestamps
declare -i pts_meta=14 # Used for the meta info heading
declare -i pts_sign=10 # Used for the signature
declare -i pts_title=33 # Used for the title (see -T)
# See --shoehorn
declare shoehorned=
# See -E / $end_offset
declare -i DEFAULT_END_OFFSET=60
# If the video is less than this length, end offset won't be used at all
declare MIN_LENGTH_FOR_END_OFFSET=19m30s
# 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
# Verbosity level so far from the command line can only be muted (see -q)
# it can be overridden, though
declare -i verbosity=$V_ALL
# Set to 1 to disable colours in console output
declare -i plain_messages=0
# See coherence_check for more details
declare -i DISABLE_SHADOWS=0
declare -i DISABLE_TIMESTAMPS=0
 
# Introduced in 1.0.7b, revamped in 1.11:
# This font is used to display international names (i.e. CJK names) correctly
# Help from users actually needing this would be appreciated :)
# This variable is filled either automatically through the set_extended_font()
#+function (and option -Ij) or manually (with option -Ij=MyFontName)
# The automatic picks a semi-random one from the fonts believed to support CJK/Cyrillic
#+characters.
declare FONT_MINCHO= # Filename or font name as known to ImageMagick (identify -list font)
# Output of capturing programs is redirected here
declare stdout=/dev/null stderr=/dev/null
declare -i DVD_MODE=0 DVD_TITLE=1
declare DVD_FILE=
 
# Override-able since 1.11:
# Height of the thumbnails, by default use same as input
declare th_height= # *WILL CHANGE NAME*
declare interval=$DEFAULT_INTERVAL # Interval of captures (=numsecs/numcaps)
declare -i numcaps=$DEFAULT_NUMCAPS # Number of captures (=numsecs/interval)
# This is the horizontal padding added to each capture.
# Beware when changing this since extended set's alignment might break.
# When shadows are enabled this is ignored since they already add padding.
declare -i HPAD=2 # *WILL CHANGE NAME*
declare -i cols=$DEFAULT_COLS # Number of output columns
 
# }}} # End of override-able variables
 
# {{{ # Variables
 
# Options and other internal usage variables, no need to mess with this!
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 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
# Identification output from ffmpeg and mplayer for the current video
declare FFMPEG_CACHE=
declare MPLAYER_CACHE=
# This holds the parsed identification values, see also the Indexes in VID
# (defined in the constants block)
declare -a VID=( )
 
# These variables will hold the output of tput, used
# to colourise feedback
declare prefix_err= prefix_inf= prefix_warn= suffix_fback=
 
# 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
 
# Transformations/filters
# Operations are decomposed into independent optional steps, this allows
# to add some intermediate steps (e.g. polaroid/photo mode's frames)
# 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 (currently deprecated)
# * 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, [context, [index]] )
# They're executed in order by filter_vidcap()
declare -a FILTERS_IND=( 'filt_resize' 'filt_apply_stamp' 'filt_softshadow' )
# Deprecated: 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
 
# Sets which function is used to obtain random numbers valid values are
# bashrand and filerand.
# Setting it manually will break it, calling with -R changes this to filerand.
# See rand() for an explanation
declare RANDFUNCTION=bashrand
 
# Which file are we working on (i.e. how many times has process() been called)
declare -i FILEIDX=0
 
# Names for output files, each index is a file name, an empty index will use
# the input file and append an extension to it
declare -a OUTPUT_FILES=( )
 
# Mplayer and FFmpeg binaries. Will be detected.
# Don't set manually, if you need to override set the path temporarily, e.g.:
# $ env PATH=/whatever:$PATH vcs ...
# or use the undocumented (and unchecked!) appropriate option:
# $ vcs --undocumented set_ffmpeg=/mypath/ffmpeg
declare MPLAYER=
declare FFMPEG=
 
# When set to 1 the reported length by mplayer and ffmpeg won't be trusted
# and will trigger some custom tests.
# Enabled automatically on problematic files
declare -i QUIRKS=0
# If the reported lengths differ by at least this much QUIRKS will be enabled
declare QUIRKS_LEN_THRESHOLD=0.2
# When trying to determine the correct length, file will be probed each...:
declare QUIRKS_LEN_STEP=0.5 # ~ 10 frames @ 20fps
# Maximum number of seconds to "rewind" from reported length (after this
# vcs surrenders but processing continues with a rewinded length)
declare QUIRKS_MAX_REWIND=20
 
# Set when the console output will be in color. It doesn't control color!
declare HAS_COLORS=
 
declare -i multiple_input_files=0
 
# Internal counts, used only to adjust messages
declare -i INTERNAL_WS_C=0 # -Ws count
declare -i INTERNAL_WP_C=0 # -Wp count
declare -i INTERNAL_MAXREWIND_REACHED=0 # More -Ws in the command-line won't help
 
# }}} # 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'
'MIN_LENGTH_FOR_END_OFFSET'
'DEBUG'
'DISABLE_.*'
'th_height'
'interval'
'numcaps'
'HPAD'
'cols'
# Note GETOPT doesn't make sense to be overridden from the command-line
'GETOPT'
)
 
# 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 -a 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
 
end_offset=$DEFAULT_END_OFFSET
}
 
# 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=${ALLOWED_OVERRIDES[*]}
compregex=${compregex// /|} # = s ' ' => '|'
 
# Don't allow ';', FIXME: dunno how secure that really is...
# FIXME: ...it doesn't really work 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=$(egrep -o '^[[:space:]]*[a-zA-Z0-9_]*=.' <<<"$o" | cut -d'=' -f1 | egrep -o '[^ ]*')
local varval=$(egrep -o '^[^=]*=.*' <<<"$o" | cut -d'=' -f2-)
# FIXME: Security!
local curvarval=
eval curvarval='$'"$varname"
if [ "$curvarval" == "$varval" ]; then
warn "Ignored override '$varname' (already had same value)"
else
eval "$varname=\"$varval\""
eval "USR_$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 is a valid percentage (xx% or xx.yy%)
# is_percentage($1 = input)
is_percentage() {
egrep -q '^([0-9]+\.?([0-9])?+|(\.[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() {
awkex "int(${*//[ ,]/ * }+0.5)" # ' ' = ',' => '*'
}
 
# Like rmultiply() but always rounded upwards
ceilmultiply() {
# TODO: breaks with $@. Why?
awkex "int(${*//[ ,]/ * }+0.99999)" # ' ' = ',' => '*'
}
 
# Basic mathematic stuff
# min($1 = operand1, $2 = operand2)
min() { awk "BEGIN { if (($1) < ($2)) print ($1) ; else print ($2) }" ; }
max() { awk "BEGIN { if (($1) > ($2)) print ($1) ; else print ($2) }" ; }
abs() { awk "BEGIN { if (($1) < (0)) print (($1) * -1) ; else print ($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='!=' ;;
*) assert $LINENO "[ \"'$1' '$2' '$3'\" ] && false" && return $EX_SOFTWARE
esac
# Empty operands
if [ -z "$1" ] || [ -z "$3" ]; then
assert $LINENO "[ \"'$1' '$2' '$3'\" ] && false"
else
awk "BEGIN { if ($1 $op $3) exit 0 ; else exit 1 }"
fi
}
 
# Keep a number of decimals *rounded*
keepdecimals() {
local N="$1" D="$2"
awk "BEGIN { printf \"%.${D}f\", (($N)+0) }"
}
 
# Keep a number of decimals, last decimal rounded to lower
keepdecimals_lower() {
grep -q '\.' <<<"$1" || { echo "$1" ; return ; }
local D=${1/#*.} # Decimals only
echo ${1/%.*}.${D:0:$2} # Integer part + . + Number of decimals
}
 
# Evaluate in AWK. Intended for arithmetic operations.
#+Keep decimals. I.e. 5 = 5.000000...
# awkex($1 = expression)
awkexf() {
# By default awk prints in compact form (scientific notation and/or up to 6 digits/decimals),
# printf is used to avoid this, TODO: Is there any direct way?
# .%20f is clearly overkill but matches the all code (default bc -l)
awk "BEGIN { printf \"%.20f\", ($1)+0 }"
}
 
# Evaluate in AWK. Intended for arithmetic operations.
#+Use default output. I.e. 5 = 5
# awkex($1 = expression)
awkex() {
awk "BEGIN { print ($1)+0 }"
}
 
# converts spaces to newlines in a x-platform way [[FNL]]
# stonl($1 = string)
stonl() {
awk '{gsub(" ", "\n");print}' <<<"$1" | egrep -v '^$'
}
 
# Converts newlines to spaces portably
# nltos($1 = string)
nltos() {
awk '{printf "%s ",$0}' <<<"$1" | sed 's/ *//'
}
 
# bash version of ord() [[ORD]]
# prints the ASCII value of a character
ord() {
printf '%d' "'$1"
}
 
# Get file extension
filext() {
grep -o '\.[^.]*$' <<<"$1" | cut -d. -f2
}
 
# Wrapper around $RANDOM, not called directly, wrapped again in rand().
# See rand() for an explanation.
bashrand() {
echo $RANDOM
}
 
# Prepares for "filerand()" calls
# File descriptor 7 is used to keep a file open, from which data is read
# and then transformed into a number.
# init_filerand($1 = filename)
init_filerand() { # [[FD1]], [[FD2]]
test -r "$1"
exec 7<"$1"
# closed in exithdlr
}
 
# Produce a (not-really-)random number from a file, not called directly wrapped
# in rand()
# Note that once the file end is reached, the random values will always
# be the same (hash_string result for an empty string)
filerand() {
local b=
# "read 5 bytes from file descriptor 7 and put them in $b"
read -n5 -u7 b
hash_string "$b"
}
 
# Produce a random number
# $RANDFUNCTION defines wich one to use (bashrand or filerand).
# Since functions using random values are most often run in subshells
# setting $RANDOM to a given seed has not the desired effect.
# filerand() is used to that effect; it keeps a file open from which bytes
# are read and not-so-random values generated; since file descriptors are
# inherited, subshells will "advance" the random sequence.
# Argument -R enables the filerand() function
rand() {
$RANDFUNCTION
}
 
# produces a numeric value from a string
hash_string() {
local HASH_LIMIT=65536
local v="$1"
local -i hv=15031
local c=
if [ "$v" ]; then
for i in $(seqr 0 ${#v} ); do
c=$( ord ${v:$i:1} )
hv=$(( ( ( $hv << 1 ) + $c ) % $HASH_LIMIT ))
done
fi
echo $hv
}
 
# Applies the Pythagorean Theorem
# pyth_th($1 = cathetus1, $2 = cathetus2)
pyth_th() {
awkex "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() {
trace $FUNCNAME $@
# eval it even if it's numeric to strip leading zeroes. Note the quoting
if is_number "$1" ; then awkex "\"$1\"" ; return 0 ; fi
 
local s=$(tolower "$1") t r n
 
# Only allowed characters
if ! egrep -qi '^[0-9smh.]+$' <<<"$s"; then
return $EX_USAGE;
fi
 
# Two consecutive dots are no longer accepted
if egrep -q '\.\.' <<<"$s" ; then
return $EX_USAGE
fi
 
# Newer parsing code: replaces units by a product
# and feeds the resulting string to awk for evaluation
# Note leading zeroes will lead awk to believe they are octal numbers
# as a quick and dirty fix I'm just wrapping them in quotes, forcing awk
# to re-evaluate them, which appears to be enough to make them decimal.
# This is the only place where leading zeroes have no meaning.
 
# Split into lines of time + unit:
t=
for item in $(echo "$s" | grep -o '[0-9]*[hms]') ;do
n="\"$(echo $item | grep -o '[0-9]*')\"" # Number, quoted
t=$t$n$(echo $item | grep -o '[hms]') # + Number + Unit
done
# Split all ms or s.ms
for item in $(echo "$s" | grep -o '[0-9]*\.[0-9]*') ;do
t="${t}\"$item\" + "
done
# Seconds without unit. They must be preceded by h, m or s at this point
local secs=$(echo $s | egrep -o '.?[0-9]*$')
# When preceded by '.', they're ms
[ "$secs" ] && grep -q '\.'<<<"$secs" && secs=
# Quote and addition. Note BSD grep/egrep wants the anchor ($) or won't match
[ "$secs" ] && secs=" \"$(egrep -o '[0-9]*$'<<<"$secs")\" + "
t=${t//h/ * 3600 + }
t=${t//m/ * 60 + }
t=${t//s/ + }
t="$t$secs"
t=${t/% + /} # Strip empty addition
r=$(awkex "$t" 2>/dev/null)
 
# Negative and empty intervals
assert $LINENO "[ '$r' ] && [ '$t' ]"
assert $LINENO "fptest $r -gt 0"
 
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 -format '%w' "$1"
}
 
# Get Image Height
# imh($1 = file)
imh() {
identify -format '%h' "$1"
}
 
# Prints a number of seconds in a more human readable form
# e.g.: 3600 becomes 1:00:00
# pretty_stamp($1 = seconds)
pretty_stamp() {
assert $LINENO "is_float '$1'"
# Fully implemented in AWK to discard bc.
# As a bonus now it's much faster and compact
awk "BEGIN {
t=$1 ; NOTMS=($DEC_MPLAYER==$decoder);
MS=(t - int(t));
h=int(t / 3600);
t=(t % 3600);
m=int(t / 60);
t=(t % 60);
s=t
if (h != 0) h=h\":\" ; else h=\"\"
if (NOTMS!=1) ms=sprintf(\".%02d\", int(MS*100+0.5));
printf \"%s%02d:%02d%s\", h, m, s, ms
}"
# Note the rounding applied to $MS, it is required to match the precission passed on
# to ffmpeg
}
 
# Prints a given size in human friendly form
get_pretty_size() {
local bytes=$1
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
}
 
# 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_file_size($1 = file)
get_pretty_file_size() {
local f="$1"
local bytes=$(get_file_size "$f")
 
get_pretty_size "$bytes"
}
 
# Clean $safe_rename_pattern, which is override-able
# Since safe_rename() is called from $() it won't be able to affect global variables directly
sanitise_rename_pattern() {
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
# Hashmarks will break the regex used in safe_rename()
if grep -q '#' <<<"$safe_rename_pattern" ; then
warn "Illegal character \"#\" found in safe renaming pattern, resetting it"
safe_rename_pattern=$DEFAULT_SAFE_REN_PATT
fi
}
 
# mv quiet
# Move a file, be quiet about errors.
# Ownership preservation is a common error on vfs, for example
mvq() {
mv -- "$@" 2>/dev/null
}
 
# 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() {
trace $FUNCNAME $@
local from="$1"
local to="$2"
 
# Output extension
local ext=$(filext "$to")
# Output filename without extension
local b=${to%.$ext}
 
local n=1
while [ -f "$to" ]; do # Only executes if $2 exists
# Bash 2 and Bash 3 behave differently with substring replacement (${//}) and '%'
# Sed is a safer bet
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
 
mvq "$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...
# Neither busybox's nor BSD's du allow --bytes.
# Note that using "ls -H" is not an option for portability reasons either.
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"
}
 
# Du replacement. This differs from get_file_size in that it takes multiple arguments
dur() {
for file in $@ ; do
get_file_size "$file"
done
}
 
# Gets the size of the dvd device, in DVD mode
get_dvd_size() {
# FIXME: Case sensivity might break with iso9660
if [ -f "$DVD_MOUNTP/VIDEO_TS/VTS_${DVD_VTS}_1.VOB" ]; then
# Some VOBs available
local vfiles="$DVD_MOUNTP/VIDEO_TS/VTS_${DVD_VTS}_*.VOB"
# Print all sizes, each on a line, add '+' to the end of each line, add 0 to the end.
local feed="$(dur "$DVD_MOUNTP/VIDEO_TS/VTS_${DVD_VTS}_"*".VOB" | cut -f1 | sed 's/$/ + /') 0"
get_pretty_size $(awkex "$(nltos "$feed")")
else
echo "?"
fi
}
 
is_linux() {
uname -s | grep -iq '^Linux$'
}
 
# Get the mountpoint of a mounted image.
# This only works on Linux. *BSD normal users aren't able to use mdconfig -l
# Is there any better way?
get_dvd_image_mountpoint() {
if is_linux ; then
local lodev=$(/sbin/losetup -j "$DVD_FILE" | cut -d':' -f1 | head -1)
mount | grep "^$lodev " | cut -d' ' -f3
fi
}
 
# Tests the presence of all required programs
# test_programs()
test_programs() {
local retval=0 last=0
local nopng=0
 
MPLAYER=$(type -pf mplayer) || true
FFMPEG=$(type -pf ffmpeg) || true
 
# Test we can actually use FFmpeg
[ "$FFMPEG" ] && {
# Newer FF has -codecs, -formats, -protocols, older has only -formats
#+png is a codec so it's on different lists on newer and older
if ! "$FFMPEG" -formats 2>/dev/null | grep -q 'EV.* png' && \
! "$FFMPEG" -codecs 2>/dev/null | grep -q 'EV.* png' ; then
warn "FFmpeg can't output to png, won't be able to use it."
FFMPEG=''
nopng=1
fi
}
# Same for Mplayer
[ "$MPLAYER" ] && {
if ! "$MPLAYER" -vo help 2>&1 | grep -q 'png' ; then
warn "MPlayer can't output to png, won't be able to use it."
MPLAYER=''
nopng=1
fi
}
 
[ "$MPLAYER" ] || [ "$FFMPEG" ] || {
local pngwarn=
[ $nopng -eq 1 ] && pngwarn=', with PNG output support,'
error "mplayer and/or ffmpeg$pngwarn are required!"
let 'retval++,1'
}
 
 
if [ $decoder -eq $DEC_FFMPEG ] && [ -z "$FFMPEG" ]; then
decoder=$DEC_MPLAYER
elif [ $decoder -eq $DEC_MPLAYER ] && [ -z "$MPLAYER" ]; then
decoder=$DEC_FFMPEG
fi
 
# awk is required by SUS/POSIX but just to be sure...
for prog in convert montage identify mktemp grep egrep cut sed awk ; do
if ! type -pf "$prog" ; then
error "Required program $prog not found!"
let 'retval++,1'
fi >/dev/null
done
# TODO: [[x2]]
 
# Early exit
[ $retval -eq 0 ] || return $EX_UNAVAILABLE
 
# ImageMagick version. 6 is a must, I'm probably using some
# features that require a higher minor version
# Versions tested:
# * Fedora 9: IM 6.4.0
local ver
ver=$(identify -version | head -n1 | grep -o 'ImageMagick\s*[^ ]*' |\
cut -f 2 -d' ')
if [ "$ver" ]; then
local verx=${ver//-/.}.0 # Extra .0 in case rev doesn't exist
local major=$(cut -d'.' -f1 <<<"$verx")
local minor=$(cut -d'.' -f2 <<<"$verx")
local micro=$(cut -d'.' -f3 <<<"$verx")
local rev=$(cut -d'.' -f4 <<<"$verx")
local serial=$(( $major * 100000 + $minor * 10000 + $micro * 100 + $rev))
if [ "$serial" -lt 630507 ]; then
error "ImageMagick 6.3.5-7 or higher is required. Found $ver." ;
let 'retval++,1'
fi
else
error "Failed to check ImageMagick version."
let 'retval++,1'
fi
 
[ $retval -eq 0 ] || return $EX_UNAVAILABLE
}
 
# Test wether $GETOP is a compatible version; try to choose an alternate if
# possible
choose_getopt() {
if ! type -pf $GETOPT ; then
# getopt not in path
error "Required program getopt not found!"
return $EX_UNAVAILABLE
fi >/dev/null
local goe= gor=0
# Try getopt. If there's more than one in the path, try all of them
for goe in $(type -paf $GETOPT) ; do
"$goe" -T || gor=$?
if [ $gor -eq 4 ]; then
# Correct getopt found
GETOPT="$goe"
break;
fi
done >/dev/null
if [ $gor -ne 4 ]; then
error "No compatible version of getopt in path, can't continue."
error " For details on how to correct this problems, see <http://p.outlyer.net/vcs#getopt>"
return $EX_UNAVAILABLE
fi
return 0
}
 
# 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 VCSTEMPDIR
unset TEMPSTUFF ; declare -a TEMPSTUFF
}
 
# Exit callback. This function is executed on exit (correct, failed or
# interrupted)
# exithdlr()
exithdlr() {
# I don't think that's really required anyway
if [ "$RANDFUNCTION" == "filerand" ]; then
7<&- # Close FD 7
fi
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
[ $plain_messages -eq 0 ] && echo -n "$prefix_err"
# 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$suffix_fback"
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
[ $plain_messages -eq 0 ] && echo -n "$prefix_warn"
echo "$1$suffix_fback"
fi >&2
}
#
# Print an informational message
# inf($1 = text)
inf() {
if [ $verbosity -ge $V_INFO ]; then
[ $plain_messages -eq 0 ] && echo -n "$prefix_inf"
echo "$1$suffix_fback"
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
}
 
#
# Tests if the filter chain contains the provided filter
# has_filter($1 = filtername)
has_filter() {
local filter= ref=$1
for filter in ${FILTERS_IND[@]} ; do
[ "$filter" == "$ref" ] || continue
return 0
done
return 1
}
 
#
# Enables prefixes in console output (instead of colour)
set_feedback_prefixes() {
prefix_err='[E] '
prefix_inf='[i] '
prefix_warn='[w] '
suffix_fback=
}
 
#
# Initialises the variables affecting colourised feedback
init_feedback() {
HAS_COLORS=
 
# tput might be preferable (Linux: man console_codes), but it doesn't
# work on FreeBSD to set colors
 
# Is tput available?
if type -pf tput >/dev/null ; then
# Is it able to set colours?
if tput bold && tput setaf 0 >/dev/null && tput sgr0 ; then
prefix_err=$(tput bold; tput setaf 1)
prefix_warn=$(tput bold; tput setaf 3)
prefix_inf=$(tput bold; tput setaf 2)
suffix_fback=$(tput sgr0)
HAS_COLORS="yes"
fi
fi
 
if [ -z "$HAS_COLORS" ]; then
# tput was not an option, let's try ANSI escape codes instead [[AEC]]
# TODO: Detect support
# Alternatively: $ perl -e 'print "\e[31m\e[1m"'
# echo -e is not portable but echo $'' is bash-specific so it should be fine...
# except when ANSI escape codes aren't supported of course
prefix_err=$(echo $'\033[1m\033[31m')
prefix_warn=$(echo $'\033[1m\033[33m')
prefix_inf=$(echo $'\033[1m\033[32m')
suffix_fback=$(echo $'\033[0m')
HAS_COLORS="yes"
fi
 
# Finally, if there's no colour support, use prefixes instead
if [ -z "$HAS_COLORS" ]; then
set_feedback_prefixes
fi
}
 
#
# seq replacement
# seq is not always present, jot is an alternative on FreeBSD. Instead, this is
# a direct replacement
# Note pure bash is *slower* than the awk (or perl) version
# seqr($1 = from, $2 = to, $3 = increment)
seqr() {
local from=$1 to=$2 inc=$3
[ "$inc" ] || inc=1
awk "BEGIN { for (i=$from;i<=$to;i+=$inc) print i }"
}
 
#
# assertion operator
# assert($1 = line, $* = code)
# TODO: Limit usage to values that will expand correctly always (i.e. not with quotes)
assert() {
[ $RELEASE -eq 1 ] && return
LINE=$1
shift
eval "$@" || {
error "Internal error at line $LINE: $@"
exit $EX_SOFTWARE
}
}
 
# }}} # Convenience functions
 
# {{{ # Core functionality
 
# Creates a new temporary directory
# create_temp_dir()
create_temp_dir() {
trace $FUNCNAME $@
 
[ "$VCSTEMPDIR" ] && return 0
 
# 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.
# Passing a full path template is more x-platform than using
# -t / -p
if [ -d /dev/shm ] && [ -w /dev/shm ]; then
VCSTEMPDIR=$(mktemp -d /dev/shm/vcs.XXXXXX)
else
[ "$TMPDIR" ] || TMPDIR="/tmp"
VCSTEMPDIR=$(env TMPDIR="$TMPDIR" mktemp -d "$TMPDIR/vcs.XXXXXX")
fi
if [ ! -d "$VCSTEMPDIR" ]; then
error "Error creating temporary directory"
return $EX_CANTCREAT
fi
TEMPSTUFF=( "${TEMPSTUFF[@]}" "$VCSTEMPDIR" )
}
 
# Resolve path. Realpath is not always available and readlink [[LC]] behaves differently in
# GNU and BSD. FIXME: Has AWK or bash something similar? This is the only place requiring perl!
realpathr() {
perl -e "use Cwd qw(realpath);print realpath('$1')"
}
 
# Create a new temporal file and print its filename
# new_temp_file($1 = suffix)
new_temp_file() {
trace $FUNCNAME $@
local r=$(env TMPDIR="$VCSTEMPDIR" mktemp "$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=( "${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 + ( $(rand) % $ncolours ) ))
convert -list color | sed -n "${lineno}{p;q;}" | cut -d' ' -f1 # [[R1#11]]
}
else # Pseudo-random mode, WIP!
randccomp() {
# colours are in the 0..65535 range, while RANDOM in 0..32767
echo $(( $(rand) + $(rand) + ($(rand) % 1) ))
}
randcolour() {
echo "rgb($(randccomp),$(randccomp),$(randccomp))"
}
fi
 
# Older IM output was pretty different. Since this is a mode used for testing
# I don't believe it's worth the effort to get it always right
# This used to be -list type. Was this an older IM version or a bug in vcs?
local nfonts=$(convert -list font | grep '^\s*Font:' | wc -l)
randfont() {
lineno=$(( $(rand) % $nfonts ))
convert -list font | grep -o 'Font:.*' | sed -n "${lineno}{p;q;}" | cut -d' ' -f2
}
 
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=$(awkex "$end - $st")
 
if fptest "$runlen" -lt $(get_interval "$MIN_LENGTH_FOR_END_OFFSET") ; then
# Min length to use end offset not met, it won't be used
inf "End offset won't be used, video too short."
eo=0
elif 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 for the video, 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=$(awkexf "(($end-$st)/2 + 1)")
else
inc=$(awkexf "(($end-$eo-$st)/$tcnumcaps)")
fi
else
error "Internal error"
return $EX_SOFTWARE
fi
# Keep three decimals, round to lower to avoid exceeding the video length
inc=$(keepdecimals_lower $inc 3)
 
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
local bound=$(awkexf "$end - $eo")
while fptest $stamp -le "$bound"; do
assert $LINENO fptest $stamp -ge 0
LTC=( "${LTC[@]}" "$stamp" )
stamp=$(keepdecimals_lower $(awkexf "$stamp + $inc") 3)
done
unset LTC[0] # Discard initial cap (=$st)
TIMECODES=( ${TIMECODES[@]} ${LTC[@]} ) # Don't quote or extra empty stamp!
}
 
# 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 $@
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
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 with ffmpeg
# capture_ffmpeg($1 = inputfile, $2 = outputfile, $3 = timestamp[, $4 = extra opts])
capture_ffmpeg() {
local f=$1
local o=$2
local ts=$3
# 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/ / $ts} -i "$f" ${wa_ss_af/ / $ts} -an \
-dframes 1 -vframes 1 -vcodec png \
-f rawvideo $4 $shoehorned "$o" >"$stdout" 2>"$stderr"
[ -f "$o" ] && [ "0" != "$(du "$o" | cut -f1)" ]
}
 
# Capture a frame with mplayer
# capture_mplayer($1 = inputfile, $2 = UNUSED, $3 = timestamp[, $4 = extra opts])
capture_mplayer() {
# Note mplayer CAN'T set the output filename
local f="$1"
local o=00000005.png
local ts=$3
 
# No point in passing ms to mplayer
ts=$(cut -d'.' -f1 <<<"$ts")
# Capture 5 frames and drop the first 4, fixes a weird bug/feature of mplayer ([M1])
 
{
if [ $DVD_MODE -eq 1 ]; then
"$MPLAYER" -sws 9 -ao null -benchmark -vo "png:z=0" -quiet \
-frames 5 -ss "$ts" $shoehorned -dvd-device "$DVD_FILE" \
$4 "dvd://$DVD_TITLE"
else
"$MPLAYER" -sws 9 -ao null -benchmark -vo "png:z=0" -quiet \
-frames 5 -ss "$ts" $4 $shoehorned "$f"
fi
 
} >"$stdout" 2>"$stderr"
rm -f 0000000{1,2,3,4}.png # Remove the first four
[ -f "$o" ] && [ "0" != "$(du "$o" | cut -f1)" ]
}
 
# 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_mplayer "$f" 'IGNOREME' "$stamp"
elif [ $decoder -eq $DEC_FFMPEG ]; then
# FIXME: ffmpeg can put the temporary file anywhere
capture_ffmpeg "$f" "$VIDCAPFILE" "$stamp"
else
error "Internal error!"
return $EX_SOFTWARE
fi || true
if [ ! -f "$VIDCAPFILE" ] || [ "0" == "$(du "$VIDCAPFILE" | cut -f1)" ]; then
[ $decoder -eq $DEC_MPLAYER ] && stamp=${stamp/%.*}
error "Failed to capture frame at $(pretty_stamp $stamp) (${stamp}s)"
return $EX_SOFTWARE
fi
 
return 0
}
 
# Applies all individual vidcap filters
# filter_vidcap($1 = filename, $2 = timestamp, $3 = width, $4 = height, $5 = context, $6 = index[1..])
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" "$5" "$6" ) -flatten "
done
local t=$(new_temp_file .png)
eval "convert '$1' $cmdopts '$t'"
# If $t doesn't exist returns non-zero
[ -f "$t" ] && mvq "$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, $5 = context, $6 = index)
filt_apply_stamp() {
trace $FUNCNAME $@
local filename=$1 timestamp=$2 width=$3 height=$4 context=$5 index=$6
 
local pts=$pts_tstamps
if [ $height -lt 200 ]; then
pts=$(( $pts_tstamps / 3 ))
elif [ $height -lt 400 ]; then
pts=$(( $pts_tstamps * 2 / 3 ))
fi
# If the size is too small they won't be readable at all
# With the original font 8 was the minimum, with DejaVu 7 is readable
if [ $pts -le 7 ]; then
pts=7
if [ $index -eq 1 ] && [ $context -ne $CTX_EXT ]; then
warn "Very small timestamps in use. Disabling them with -dt might be preferable"
fi
fi
# The last -gravity None is used to "forget" the previous gravity (otherwise it would
# affect stuff like the polaroid frames)
echo -n " \( -box '$bg_tstamps' -fill '$fg_tstamps' -stroke none -pointsize '$pts' "
echo -n " -gravity '$grav_timestamp' -font '$font_tstamps' -strokewidth 3 -annotate +5+5 "
echo " ' $(pretty_stamp $stamp) ' \) -flatten -gravity None "
}
 
# Apply a framed photo-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 ) ) ))
# The border is relative to the input size (since 1.0.99), with a maximum of 6
# Should probably be bigger for really big frames
# Note that only images below 21600px (e.g. 160x120) go below a 6px border
local border=$(( ($3*$4) / 3600 ))
[ $border -lt 7 ] || border=6
echo -n "-bordercolor white -border $border -bordercolor grey60 -border 1 "
}
 
filt_softshadow() {
# Before this was a filter, there was the global (montage) softshadow (50x2+10+10) and the
# photoframe inline softshadow 60x4+4+4
echo -n "\( -background black +clone -shadow 50x2+4+4 -background none \) +swap -flatten -trim +repage "
}
 
 
# Apply a polaroid-like border effect
# Based on filt_photoframe(), with a bigger lower border
# filt_polaroid($1 = filename, $2 = timestamp, $3 = width, $4 = height)
filt_polaroid() {
trace $FUNCNAME $@
# local file="$1" ts=$2 w=$3 h=$4
local border=$(( ($3*$4) / 3600 )) # Read filt_photoframe for details
[ $border -lt 7 ] || border=6
echo -n "-bordercolor white -mattecolor white -frame ${border}x${border} "
# FIXME: This is rather ugly (double-flipping) there's sure a better way
echo -n "\( -flip -splice 0x$(( $border*5 )) \) "
echo "-flip -bordercolor grey60 -border 1 +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=$(( ($(rand) % 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="$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= vpad= splice=
 
# The shadows already add a good amount of padding
if has_filter filt_softshadow ; then
hpad=0
vpad=0
splice=5x10
else
hpad=$HPAD
vpad=$HPAD
splice=0x8
fi
 
montage -background Transparent "$@" -geometry +$hpad+$vpad -tile "$cols"x "$output"
convert "$output" -background Transparent -splice $splice "$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 $(seqr 1 $numrows) ; do
col=0
rowfile=$(new_temp_file .png)
rowfiles=( "${rowfiles[@]}" "$rowfile" )
accoffset=0
cmdopts= # This command is pretty time-consuming, let's make it in a row
 
# Base canvas # Integrated in the row creation since 1.0.99
 
# Step through vidcaps (col=[0..cols-1])
for col in $(seqr 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
cmdopts="$cmdopts '$1' -geometry +${accoffset}+0 -composite "
 
offset=$(( $minoffset + ( $(rand) % $maxoffset ) ))
let 'accoffset=accoffset + w - offset'
shift
done
inf "Composing overlapped row $row/$numrows..."
eval convert -size ${canvasw}x${canvash} xc:transparent -geometry +0+0 "$cmdopts" -trim +repage "'$rowfile'" >&2
done
 
inf "Merging overlapped rows..."
output=$(new_temp_file .png)
 
cmdopts=
accoffset=0
local h
for row in "${rowfiles[@]}" ; do
w=$(imw "$row")
h=$(imh "$row")
minoffset=$(( $h / 8 ))
maxoffset=$(( $h / 4 ))
offset=$(( $minoffset + ( $(rand) % $maxoffset ) ))
# The row is also offset horizontally
cmdopts="$cmdopts '$row' -geometry +$(( $(rand) % $maxoffset ))+$accoffset -composite "
let 'accoffset=accoffset + h - offset'
done
# After the trim the image will be touching the outer borders and the heading and footer,
# older versions (prior to 1.0.99) used -splice 0x10 to correct the heading spacing, 1.0.99
# onwards uses -frame to add spacing in all borders + splice to add a bit more space on the
# upper border. Note splice uses the background colour while frame uses the matte colour
eval convert -size ${canvasw}x$(( $canvash * $cols )) xc:transparent -geometry +0+0 \
$cmdopts -trim +repage -bordercolor Transparent -background Transparent -mattecolor Transparent \
-frame 5x5 -splice 0x5 "$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 I replace spaces by newlines
local s=$1
stonl "$s" | sort -n | uniq
}
 
# Test the video at a given timestamp (to see if it can be reached)
# See safe_length_measure()
# probe_video($1 = input file, $2 = stamp)
probe_video() {
local f="$1"
local ts="$2"
local tempfile=
local ret=0
 
# create_temp_dir should have been called
 
# This time a resize filter is applied to the player to produce smaller
# output
if [ $decoder -eq $DEC_MPLAYER ]; then
tempfile=00000005.png
TEMPSTUFF=( "${TEMPSTUFF[@]}" "$tempfile" )
if ! capture_mplayer "$f" "IGNOREME" "$ts" "-vf scale=96:96"; then
ret=1
fi
elif [ $decoder -eq $DEC_FFMPEG ]; then
tempfile=$(new_temp_file '-safelen.png')
if ! capture_ffmpeg "$f" "$tempfile" "$ts" "-s 96x96"; then
ret=1
fi
else
assert $LINENO false
ret=1
fi
rm -f "$tempfile"
return $ret
}
 
# Try to guess a correct length for the video, taking the reported lengths a
# starting point
# safe_length_measure($1 = filename)
safe_length_measure() {
trace $FUNCNAME $@
local f="$1"
local len=${VID[$LEN]}
local tempfile=
local newlen=$len
local capturefn=
 
create_temp_dir
 
if probe_video "$1" $len ; then
inf " File looks fine, suspicion withdrawn"
echo "$len"
return 0
else
# Can't seek to the very end, adjust
warn "Starting safe length measuring (this might take a while)..."
local maxrew=$(min $QUIRKS_MAX_REWIND $(awkex "int($len)")) # At most we'll rewind 20 seconds
# -1 (-WS) => Rewind up to the start
# Might be -2, -4, ... e.g. (-WS -Ws)
if fptest $maxrew -ge $len || fptest "$maxrew" -lt 0 ; then
maxrew=$len
INTERNAL_MAXREWIND_REACHED=1
fi
for rew in $(seqr $QUIRKS_LEN_STEP $maxrew $QUIRKS_LEN_STEP); do
newlen=$(keepdecimals_lower $(awkex "$len - $rew") 3)
warn " ... trying $newlen"
if probe_video "$f" "$newlen" ; then
echo $newlen
return 0
fi
done
fi
# Hitting this line means we're doomed!
return 1
}
 
##### {{{{ Codec names
 
# Codecs TODO: Clean this
# Translates an mplayer codec id/fourcc to its name
get_vcodec_name() {
local vcid="$1"
local vcodec=
# Video codec "prettyfication", see [[R2]], [[R3]], [[R4]]
case "$vcid" in
0x10000001) vcodec="MPEG-1" ;;
0x10000002) vcodec="MPEG-2" ;;
0x00000000) vcodec="Raw video" ;; # How correct is this?
avc1|H264) vcodec="MPEG-4 AVC" ;; # H264 is used in mov/mp4
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" ;; # 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 SCC" ;;
VP6[012F]) 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" ;;
3IV2) vcodec="3ivx Delta 4.0" ;; # Rare but seen
 
# 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" ;;
s263) vcodec="H.263" ;; # 3GPP
# Legacy(-er) codecs (haven't seen files in these formats in awhile)
IV3[0-9]) vcodec="Indeo Video 3" ;; # FF only recognises IV31
MSVC) vcodec="Microsoft Video 1" ;;
MRLE) vcodec="Microsoft RLE" ;;
3IV1) vcodec="3ivx Delta" ;;
# "mp4v" is the MPEG-4 fourcc *in mov/mp4/3gp*; but I also found MP4V (Apple's iTunes sample)
#mp4v|MP4V) vcodec="MPEG-4" ;;
mp4v|MP4V) vcodec="MPEG-4" ;;
# Synthetic, used for ffmpeg translations
vcs_divx) vcodec="DivX ;-)" ;;
*) # If not recognized fall back to FourCC
vcodec="$vcid"
;;
esac
echo "$vcodec"
}
 
# Translates an FFmpeg codec id to an MPlayer codec id/fourcc
# TODO: Clean this
translate_ffmpeg_vcodec_id() {
# The list of ffmpeg codecs might be retrieved by looking at the code but I
#+simply used the ffmpeg -formats / ffmpeg -codecs command
# Supported video decoders: $ ffmepg -codecs | grep '^ D.V'
local vcid="$1"
local mpid=
case "$vcid" in
mpeg1video) mpid="0x10000001" ;; # mpeg1video_vdpau?
mpeg2video) mpid="0x10000002" ;;
rawvideo) mpid="0x00000000" ;; # can't distinguish from I420
h264) mpid="avc1" ;;
mjpeg) mpid="MJPG" ;;
msmpeg4v1) mpid="MPG4" ;;
msmpeg4v2) mpid="MP42" ;;
theora) mpid="theo" ;;
camtasia) mpid="tscc" ;;
vp6|vp6a|vp6f) mpid="VP60" ;;
# FIXME List of codec id's I translate but haven't test:
# svq3, rv40, theora, camtasia, vp6*
# MPlayer uses uppercase while FFmpeg uses lowercase
rv10|rv20|rv30|rv40|svq1|svq3|wmv1|wmv2|wmv3) mpid=$(echo $vcid | tr '[a-z]' '[A-Z]') ;;
# FFmpeg doesn't print FourCC's so there's some codecs that can't be told apart
msmpeg4) mpid="vcs_divx" ;; # DIV3 = DIV4 = MP43
# XVID = DIVX = DX50 = FMP4 = ... = mpeg4
mpeg4) mpid="mp4v" ;; # Take advantage of an unamed MPEG-4
 
h263) mpid="s263" ;;
 
vc1) mpid="WVC1" ;; # In FF: WMVA = vc1
# Not supported (ff just prints the FourCC)
# IV4*, vp4
vp3) mpid="VP30" ;;
vp5) mpid="VP50" ;;
# Legacy(-er) codecs (haven't seen files in these formats in awhile)
# MSVC? MRLE?
indeo3) mpid="IV31" ;;
*) # If not recognized fall back to FourCC
mpid="$vcid"
;;
 
esac
echo $mpid
}
 
get_acodec_name() {
local acid="$1"
local acodec=
 
if grep -q '[ -]' <<<"$acid" ; then
# Won't be recognised anyway
echo "$acid"
return
fi
 
case "$(tolower "$acid")" 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, Ogg, probably others
acodec='Vorbis'
;;
qdm2) acodec="QDesign" ;;
"") acodec="no audio" ;;
samr) acodec="AMR" ;; # AMR-NB/AMR-WB?
# 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="$acid"
;;
esac
echo "$acodec"
}
 
translate_ffmpeg_acodec_id() {
local acid="$1"
local mpid=
# ffmpeg -codecs | grep ^\ D.A
case "$acid" in
mp3) mpid='85' ;;
# Note FF can tell apart mp1/mp2 directly
mp1) mpid='MPEG Layer I (MP1)' ;;
mp2) mpid='MPEG Layer II (MP2)' ;;
aac) mpid='mp4a' ;; # Can aac be MPEG2?
wmav1) mpid='352' ;;
wmav2) mpid='353' ;;
wmapro) mpid='354' ;; # Actually WMA9 Professional
ac3) mpid='8192' ;;
# FF has a ton of pcm variants (sign, endianness, ...)
pcm_*) mpid="1" ;;
vorbis) mpid="vrbs" ;;
 
qdm2) mpid="QDM2" ;;
libopencore_amrnb) mpid="AMR-NB" ;;
libopencore_amrwb) mpid="AMR-WB" ;;
*) # If not recognized show audio id tag
mpid="$acid"
;;
esac
echo "$mpid"
}
 
##### }}}} # Codec names
 
# Try to identify video properties using mplayer
# Fills $MPLAYER_CACHE with the relevant output and $VID_MPLAYER with
# the actual values. See identify_video()
# mplayer_identify($1 = file)
mplayer_identify() {
trace $FUNCNAME $@
[ "$MPLAYER" ] || return
local f="$1"
local mi=( )
# Note to self: Don't change the -vc as it would affect $vdec
if [ $DVD_MODE -eq 0 ]; then
MPLAYER_CACHE=$("$MPLAYER" -benchmark -ao null -vo null -identify -frames 0 \
-quiet "$f" 2>/dev/null | grep ^ID)
else
MPLAYER_CACHE=$("$MPLAYER" -benchmark -ao null -vo null -identify -frames 0 \
-quiet -dvd-device $DVD_FILE dvd://$DVD_TITLE \
2>/dev/null | grep ^ID)
fi
# Note the head -1!
mi[$VCODEC]=$(grep ID_VIDEO_FORMAT <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2) # FourCC
mi[$ACODEC]=$(grep ID_AUDIO_FORMAT <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2)
mi[$VDEC]=$(grep ID_VIDEO_CODEC <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2) # Decoder (!= Codec)
mi[$W]=$(grep ID_VIDEO_WIDTH <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2)
mi[$H]=$(grep ID_VIDEO_HEIGHT <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2)
mi[$FPS]=$(grep ID_VIDEO_FPS <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2)
# For some reason my (one track) samples have two ..._NCH, first one 0
#+Also multichannel is detected as 2 ch
mi[$CHANS]=$(grep ID_AUDIO_NCH <<<"$MPLAYER_CACHE"| grep -v '=0' | cut -d'=' -f2|head -1)
if [ $DVD_MODE -eq 0 ]; then
# For DVDs it prints ID_DVD_TITLE_x_LENGTH and ID_LENGTH.
#+Both appear valid.
mi[$LEN]=$(grep ID_DVD_TITLE_${DVD_TITLE}_LENGTH <<<"$MPLAYER_CACHE"| cut -d'=' -f2)
[ "${mi[$LEN]}" ] || mi[$LEN]=$(grep ID_LENGTH <<<"$MPLAYER_CACHE"| head -1 | cut -d'=' -f2)
else
mi[$LEN]=$(grep ID_DVD_TITLE_${DVD_TITLE}_LENGTH <<<"$MPLAYER_CACHE"| head -1 | cut -d'=' -f2)
fi
# Voodoo :P Remove (one) trailing zero
if [ "${mi[$FPS]:$(( ${#mi[$FPS]} - 1 ))}" == "0" ]; then
mi[$FPS]="${mi[$FPS]:0:$(( ${#mi[$FPS]} - 1 ))}"
fi
mi[$ASPECT]=$(grep ID_VIDEO_ASPECT <<<"$MPLAYER_CACHE" | egrep -v '^0.0000$' | cut -d'=' -f2 | tail -1)
# If none set, delete it
[ "${mi[$ASPECT]}" ] && fptest "${mi[$ASPECT]}" -eq 0.0 && mi[$ASPECT]=''
mi[$VCNAME]=$(get_vcodec_name "${mi[$VCODEC]}")
if [ "${mi[$VDEC]}" == "ffodivx" ] && [ "${mi[$VCNAME]}" != "MPEG-4" ]; then
mi[$VCNAME]="${mi[$VCNAME]} (MPEG-4)"
elif [ "${mi[$VDEC]}" == "ffh264" ]; then # At least two different fourccs use h264, maybe more
mi[$VCNAME]="${mi[$VCNAME]} (h.264)"
fi
mi[$ACNAME]=$(get_acodec_name "${mi[$ACODEC]}")
if [ "${mi[$ACODEC]}" == "samr" ] ; then
local adec=$(grep ID_AUDIO_CODEC <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2)
if [ "$adec" == "ffamrnb" ]; then
mi[$ACNAME]="AMR-NB";
fi
fi
 
# Array assignment
VID_MPLAYER=("${mi[@]}")
}
 
# Try to identify video properties using ffmpeg
# Fills $FFMPEG_CACHE with the relevant output and $VID_FFMPEG with
# the actual values. See identify_video()
# mplayer_identify($1 = file)
ffmpeg_identify() {
trace $FUNCNAME $@
[ "$FFMPEG" ] || return
# (AFAIK) Can't use ffmpeg in DVD Mode
#[ $DVD_MODE -eq 0 ] || return
local f="$1"
# DVD Devices *MUST* be mounted for the identifying process to even start
assert $LINENO "[ $DVD_MODE -eq 0 ] || [ '$DVD_MOUNTP' ]"
[ $DVD_MODE -eq 1 ] && [ "$DVD_DEVICE" ] && {
local vfile="$DVD_MOUNTP/VIDEO_TS/VTS_${DVD_VTS}_1.VOB"
if [ ! -r "$vfile" ]; then
error "Failed to locale mounted DVD. Detection will be less accurate."
return 0 # We can continue anyway
fi
f="$vfile"
}
 
local fi=( ) vs= as= obs= vsid=
# FFmpeg is relatively new, introduced in 1.0.99 so it needs more testing
FFMPEG_CACHE=$("$FFMPEG" -i "$f" -dframes 0 -vframes 0 /dev/null 2>&1 | egrep '(Stream|Duration:|^Seems)')
# Only the first streams of each type are honored. FIXME: Add multi-audio support.
vs=$(grep Stream <<<"$FFMPEG_CACHE" | grep 'Video:' | head -1)
as=$(grep Stream <<<"$FFMPEG_CACHE" | grep 'Audio:' | head -1)
obs=$(grep Seems <<<"$FFMPEG_CACHE")
# Stream #0.0: Video: mpeg4, yuv420p, 624x352 [PAR 1:1 DAR 39:22], 23.98 tbr, 23.98 tbn, 24k tbc
# New and old versions of ffmpeg changed their output considerably, e.g.:
# (same file, Robotica_720.wmv)
# New output:
# Seems stream 1 codec frame rate differs from container frame rate: 1000.00 (1000/1) -> 23.98 (24000/1001)
# [...]
# Duration: 00:00:20.77, start: 3.000000, bitrate: 6250 kb/s
# Stream #0.0(eng): Audio: wmapro, 48000 Hz, 6 channels, s16, 384 kb/s
# Stream #0.1(eng): Video: wmv3, yuv420p, 1280x720, 6500 kb/s, 23.98 tbr, 1k tbn, 1k tbc
# Old output:
# Duration: 00:00:20.7, start: 3.000000, bitrate: 6250 kb/s
# Stream #0.0: Audio: 0x0162, 48000 Hz, 5:1, 384 kb/s
# Stream #0.1: Video: wmv3, yuv420p, 1280x720, 24.00 fps(r)
# TODO: tbr is rounded to two decimals but the actual ratio is printed:
# 24000/1001 = 23.97602
# (older ffmpeg prints 24 fps, 24/1 so no luck here
# **Also seen**: (note the 'tb(r)')
# Stream #0.1: Video: wmv3, yuv420p, 1440x1080 [PAR 4:3 DAR 16:9], 8000 kb/s, 23.98 tb(r)
# **Also seen**: (VOB, latest ffmpeg as of this writing):
# Stream #0.0[0x1e0]: Video: mpeg2video, yuv420p, 720x576 [PAR 64:45 DAR 16:9], 9800 kb/s, 23.53 fps, 25 tbr, 90k tbn, 50 tbc
# **Also seen**: (DVB TS to DX50 in MKV), note the DAR mess, the second one is the correct one
# Stream #0.0: Video: mpeg4, yuv420p, 640x326 [PAR 1:1 DAR 320:163], PAR 231:193 DAR 73920:31459, 25 fps, 25 tbr, 1k tbn, 25 tbc
vsid=$(grep -o '#0.[0-9]' <<<"$vs" | cut -d'.' -f2) # Video Stream ID
fi[$VCODEC]=$(egrep -o 'Video: [^,]*' <<<"$vs" | cut -d' ' -f2-)
# ffmpeg's codec might contain spaces in some cases, i.e. iv4 in mov (see mplayer's bestiary)
#+unless this turns out to be common I won't be handling it specially
# Note unidentified audio codecs will be printed in hexadecimal
fi[$ACODEC]=$(egrep -o 'Audio: [^,]*' <<<"$as" | cut -d' ' -f2)
fi[$VDEC]=''
# The comma is required for cases where the stream id is printed (in hex)
fi[$W]=$(egrep -o ', [0-9]*x[0-9]*' <<<"$vs" | sed 's/^, //' | cut -dx -f1)
fi[$H]=$(egrep -o ', [0-9]*x[0-9]*' <<<"$vs" | cut -dx -f2)
# Newer CHANS and some older...
fi[$CHANS]=$(egrep -o '[0-9]* channels' <<<"$as" | cut -d' ' -f1)
# ...fallback for older
if [ -z "${fi[$CHANS]}" ]; then
local chans=$(egrep -o 'Hz, [^,]*' <<<"$as" | cut -d' ' -f2)
case $chans in
mono) fi[$CHANS]=1 ;;
stereo) fi[$CHANS]=2 ;;
5.1|5:1) fi[$CHANS]=6 ;; # *
*) ;; # Other layouts use 'N channels'
# 5.1 was in the previous version (can't remember if it was empirical).
esac
fi
# Newer FPS...
# tbr/tbn/tbc explanation: tb stands for time base
# n: AVStream, c: AVCodecContext, r: VideoStream (Guessed)
# tbr is the best bet. Note it's common for WMVs to contains "1k tbn, 1k tbc"
# tbr is rounded to two decimals, the values used to derived it might be
# printed in a "Seems ..." line like the one in the example above so it
# can be re-calculated.
fi[$FPS]=$(egrep -o '[0-9]*\.?[0-9]*k? tb(r|\(r\))' <<<"$vs" | cut -d' ' -f1)
# Let's convert e.g. 23.98 into 23.976...:
if [ "${fi[$FPS]}" ] && grep -q '\.' <<<"${fi[$FPS]}" ; then
# Decimals, see if we got better values available
local vsobs=$(grep "stream $vsid" <<<"$obs")
# Observations regarding video stream found
if [ "$vsobs" ] && grep -q " -> ${fi[$FPS]} (.*)" <<<"$vsobs" ; then
# FPS candidate
local newfps=$(egrep -o -- '-> [^ ]* \([0-9]*/[0-9]*' <<<"$vsobs" | cut -d'(' -f2)
is_fraction $newfps && fi[$FPS]=$(keepdecimals "$newfps" 3)
fi
fi
# ...fallback for older. The older version I tried seems to round further, i.e.
# 23.976 became 24 so no fix for this one
if [ -z "${fi[$FPS]}" ]; then
# No k suffix here, 1000 is 1000
fi[$FPS]=$(egrep -o '[0-9]*\.?[0-9]* fps' <<<"$vs" | cut -d' ' -f1)
fi
# Be consistent with mplayer's output: at least two decimals
[ "${fi[$FPS]}" ] && {
fi[$FPS]=$(keepdecimals "${fi[$FPS]}" 3)
fi[$FPS]=${fi[$FPS]/%0} # Strip 0$
}
fi[$LEN]=$(egrep -o 'Duration: [^,]*' <<<"$FFMPEG_CACHE" | cut -d' ' -f2)
if [ "${fi[$LEN]}" == "N/A" ]; then # It might be unable to detect
fi[$LEN]=""
fi
fi[$LEN]=$( get_interval $(echo "${fi[$LEN]}" | sed 's/:/h/' | sed 's/:/m/') )
# Aspect ratio in FFmpeg is only provided in newer ffmpeg
# It might be calculated for files without one (which is ok anyway)
# TODO: Replace tail -1 with some better option (see the double DAR example above)
fi[$ASPECT]=$(egrep -o 'DAR [0-9]*:[0-9]*'<<<"$FFMPEG_CACHE" | tail -1 | cut -d' ' -f2 | sed 's#:#/#')
# Due to calling ffmpeg on a single VOB when in DVD Device mode, the length will be partial
[ $DVD_MODE -eq 1 ] && [ "$DVD_DEVICE" ] && fi[$LEN]=''
fi[$VCNAME]=$(get_vcodec_name $(translate_ffmpeg_vcodec_id "${fi[$VCODEC]}"))
fi[$ACNAME]=$(get_acodec_name $(translate_ffmpeg_acodec_id "${fi[$ACODEC]}"))
VID_FFMPEG=("${fi[@]}")
}
 
# Use the available tools to identify video meta-data
# fills $VID with the values
# Return codes:
# 3: Failed to detect length
# 4: Failed to detect width or height
# identify_video($1 = file)
identify_video() {
trace $FUNCNAME $@
local RET_NOLEN=3 RET_NODIM=4
 
[ "$MPLAYER" ] && mplayer_identify "$1"
# ffmpeg_identify in DVD mode only works when the DVD is mounted:
[ $DVD_MODE -eq 0 ] && [ "$FFMPEG" ] && ffmpeg_identify "$1"
[ $DVD_MODE -eq 1 ] && [ "$FFMPEG" ] && [ "$DVD_MOUNTP" ] && ffmpeg_identify "$1"
 
# Fail early if none detected length
[ -z "${VID_MPLAYER[$LEN]}" ] && [ -z "${VID_FFMPEG[$LEN]}" ] && return $RET_NOLEN
 
# Classic mode, use both mplayer and ffmpeg when available
if [ "$MPLAYER" ] && [ "$FFMPEG" ]; then
# By default take mplayer's values
VID=("${VID_MPLAYER[@]}")
# FFmpeg seems better at getting the correct number of FPS, specially with
# WMVs, where mplayer often accepts 1000fps while ffmpeg notices the
# inconsistency in container vs codec and guesses better, *but* it only
# uses two decimals so 23.976 becomes 23.98. So it is only used when
# the number of decimals seems right.
# When a "Seems..." line is printed the correct FPS can be obtained though.
[ -z "${VID_MPLAYER[$FPS]}" ] && VID[$FPS]=${VID_FFMPEG[$FPS]}
[ "${VID_MPLAYER[$FPS]}" ] && [ "${VID_FFMPEG[$FPS]}" ] && {
# Trust ffmpeg if it has three decimals OR if mplayer is probably-wrong
local ffps=${VID_FFMPEG[$FPS]}
echo $ffps | grep -q '\.[0-9][0-9][0-9]' && VID[$FPS]=$ffps || {
fptest "${VID_MPLAYER[$FPS]}" -gt 500 && VID[$FPS]=$ffps
}
}
# It doesn't appear to need any workarounds for num. channels either
[ "${VID_FFMPEG[$CHANS]}" ] && VID[$CHANS]=${VID_FFMPEG[$CHANS]}
[ "${VID_FFMPEG[$ASPECT]}" ] && VID[$ASPECT]=${VID_FFMPEG[$ASPECT]}
# There's a huge inconsistency with some files, both mplayer vs ffmpeg
# same application on different OSes
local fflen=${VID_FFMPEG[$LEN]} mplen=${VID_MPLAYER[$LEN]} # Shorthands
[ -z "$fflen" ] && fflen=0
# If both report 0, there's no good value...
fptest "$fflen" -eq 0 && fptest "$mplen" -eq 0 && return $RET_NOLEN
if [ $DVD_MODE -eq 0 ] && [ $QUIRKS -eq 0 ]; then # In DVD mode ffmpeg has no length
# Quirks disabled, should be enabled?
local delta=$(abs $(awkex "($fflen - $mplen)"))
# If they don't agree, take the shorter as a starting point,
#+if both are different than zero take min, if one of them is 0, take max to start
if fptest "$fflen" -ne 0 && fptest "$mplen" -ne 0 ; then
VID[$LEN]=$(min $fflen $mplen)
else
VID[$LEN]=$(max $fflen $mplen)
delta=$QUIRKS_LEN_THRESHOLD # Ensure it's considered inconsistent
fi
# If they differ too much, enter safe mode. If one reports 0, they'll differ...
# FIXME: If $decoder reports 0, can it seek??
if fptest "$delta" -ge $QUIRKS_LEN_THRESHOLD ; then
warn "Found inconsistency in reported length. Safe measuring enabled."
QUIRKS=1
fi
fi
elif [ "$MPLAYER" ]; then
# Must do with mplayer only...
VID=("${VID_MPLAYER[@]}")
# Warn if a known pitfall is found
# See above for 1000 fps
[ "${VID[$FPS]}" == "1000.00" ] && \
warn "Possible inaccuracy in FPS detection." && \
warn " Install both mplayer and ffmpeg for better detection."
# Number of channels 0 happened for WMA in non-x86
[ "${VID[$CHANS]}" == "0" ] && \
warn "Failed to detect number of audio channels." && \
warn " Install both mplayer and ffmpeg for better detection."
elif [ "$FFMPEG" ]; then
# Must do with mplayer only...
VID=("${VID_FFMPEG[@]}")
# So far I know of no weird results. Yet.
else
assert $LINENO 'false'
fi
 
# Ensure sanity of the most important values
is_float "${VID[$LEN]}" || return $RET_NOLEN
is_number "${VID[$W]}" && is_number "${VID[$H]}" || return $RET_NODIM
 
if [ "$FFMPEG" ]; then
# FPS at least with two decimals
if [ $(awkex "int(${VID[$FPS]})") == ${VID[$FPS]} ]; then
VID[$FPS]="${VID[$FPS]}.00"
fi
fi
 
local mfps="${VID_MPLAYER[$FPS]}"
if [ $QUIRKS -eq 0 ] && [ "$MPLAYER" ] && fptest "$mfps" -eq 1000 ; then
warn "Suspect file. Safe measuring enabled."
QUIRKS=1
fi
 
# Last safeguard: Try to reach the detected length, if it fails, trigger
# quirks mode
if [ $QUIRKS -eq 0 ]; then
create_temp_dir
if ! probe_video "$1" "${VID[$LEN]}" ; then
warn "Detected video length can't be reached. Safe measuring enabled."
QUIRKS=1
fi
fi
 
if [ $QUIRKS -eq 1 ]; then
VID[$LEN]=$(safe_length_measure "$1")
if [ -z "${VID[$LEN]}" ]; then
error "Couldn't measure length in a reasonable amount of tries."
if [ $INTERNAL_MAXREWIND_REACHED -eq 1 ]; then
error " Will not be able to capture this file with the current settings."
else
local reqs=$(( $INTERNAL_WS_C + 1 )) reqp=''
[ $reqs -eq 1 ] && reqp=" -WP" || reqp=" -WP$reqs"
[ $reqs -ge 3 ] && reqs=" -WS" || { # Third try => Recommend -WS
[ $reqs -eq 1 ] && reqs=" -Ws" || reqs=" -Ws$reqs"
}
assert 'fptest "$QUIRKS_MAX_REWIND" -gt 0'
local offby=$(pretty_stamp $QUIRKS_MAX_REWIND)
warn " Capturing won't work, video is at least $offby shorter than reported."
local dname='ffmpeg'
[ $decoder -eq $DEC_MPLAYER ] && dname='mplayer'
warn " Does $dname support ${VID[$VCODEC]}?."
warn " Try re-running with$reqs$reqp."
fi
return 1
fi
elif [ $QUIRKS -eq -2 ]; then
warn "Safe mode disabled."
fi
 
# Re-check sanity of the most important values
is_float "${VID[$LEN]}" || return $RET_NOLEN
is_number "${VID[$W]}" && is_number "${VID[$H]}" || return $RET_NODIM
}
 
dump_idinfo() {
trace $FUNCNAME $@
[ "$MPLAYER" ] && echo "Mplayer: $MPLAYER"
[ "$FFMPEG" ] && echo "FFmpeg: $FFMPEG"
[ "$MPLAYER" ] && cat <<-EODUMP
=========== Mplayer Identification ===========
Length: $(pretty_stamp ${VID_MPLAYER[$LEN]})
Video
Codec: ${VID_MPLAYER[$VCODEC]} (${VID_MPLAYER[$VCNAME]})
Dimensions: ${VID_MPLAYER[$W]}x${VID_MPLAYER[$H]}
FPS: ${VID_MPLAYER[$FPS]}
Aspect: ${VID_MPLAYER[$ASPECT]}
Audio
Codec: ${VID_MPLAYER[$ACODEC]} (${VID_MPLAYER[$ACNAME]})
Channels: ${VID_MPLAYER[$CHANS]}
==============================================
 
EODUMP
local ffl="${VID_FFMPEG[$LEN]}"
[ "$ffl" ] && ffl=$(pretty_stamp "$ffl")
[ -z "$ffl" ] && [ $DVD_MODE -eq 1 ] && ffl="(unavailable in DVD mode)"
[ "$FFMPEG" ] && cat <<-EODUMP
=========== FFmpeg Identification ===========
Length: $ffl
Video
Codec: ${VID_FFMPEG[$VCODEC]} (${VID_FFMPEG[$VCNAME]})
Dimensions: ${VID_FFMPEG[$W]}x${VID_FFMPEG[$H]}
FPS: ${VID_FFMPEG[$FPS]}
Aspect: ${VID_FFMPEG[$ASPECT]}
Audio
Codec: ${VID_FFMPEG[$ACODEC]} (${VID_FFMPEG[$ACNAME]})
Channels: ${VID_FFMPEG[$CHANS]}
=============================================
 
EODUMP
local xar=
if [ "${VID[$ASPECT]}" ]; then
xar=$(keepdecimals "${VID[$ASPECT]}" 4)
[ "$xar" ] && xar=" ($xar)"
fi
cat <<-EODUMP
=========== Combined Identification ===========
Length: $(pretty_stamp ${VID[$LEN]})
Video
Codec: ${VID[$VCODEC]} (${VID[$VCNAME]})
Dimensions: ${VID[$W]}x${VID[$H]}
FPS: ${VID[$FPS]}
Aspect: ${VID[$ASPECT]}$xar
Audio
Codec: ${VID[$ACODEC]} (${VID[$ACNAME]})
Channels: ${VID[$CHANS]}
=============================================
EODUMP
 
 
}
 
# Try to pick some font capable of handling non-latin text
set_extended_font() {
trace $FUNCNAME $@
# This selection includes japanese fonts
local candidates=$(identify -list font | grep 'Font: ' | \
egrep -io '[a-z-]*(kochi|mincho|sazanami|ipafont)[a-z-]*')
if [ -z "$candidates" ]; then
error "Unable to auto-select filename font, please provide one (see -fullhelp)"
return 1
else
if [ "$DEBUG" -eq 1 ]; then
local list=$(echo "$candidates" | sed 's/^/ >/g')
inf "Available non-latin fonts detected:"
inf "$list"
fi
fi
 
# Bias towards the Sazanami family
if grep -qi 'sazanami' <<<"$candidates" ; then
FONT_MINCHO=$(grep -i 'sazanami' <<<"$candidates" | head -1)
else
FONT_MINCHO=$(head -1 <<<"$candidates")
fi
}
 
# Checks if the provided arguments make sense and are allowed to be used
# together
coherence_check() {
trace $FUNCNAME $@
# 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)"
return $EX_USAGE
fi
 
if [ $decoder -eq $DEC_MPLAYER ] && [ -z "$MPLAYER" ]; then
inf "No mplayer available. Using ffmpeg only."
decoder=$DEC_FFMPEG
elif [ $decoder -eq $DEC_FFMPEG ] && [ -z "$FFMPEG" ]; then
inf "No ffmpeg available. Using mplayer only."
decoder=$DEC_MPLAYER
fi
 
if [ $DVD_MODE -eq 1 ] ; then
# Currently it's not allowed to use dvd mode with more than one input
# "file" (in this mode, input files are actually dvd titles of the file
# provided in -V)
if [ $multiple_input_files -eq 1 ]; then
error "Only an input file is allowed in DVD mode"
return $EX_UNAVAILABLE
fi
 
# DVD Mode only works with mplayer, the decoder is changed when
# the DVD mode option is found, so if it's ffmpeg at this point,
# it's by user request (i.e. -F after -V)
if [ $decoder -ne $DEC_MPLAYER ]; then
if [ "$MPLAYER" ]; then
warn "DVD mode requires the use of mplayer, falling back to it"
decoder=$DEC_MPLAYER
else
error "DVD mode requires the use of mplayer."
return $EX_UNAVAILABLE
fi
fi
fi
 
local filter=
if [ $DISABLE_TIMESTAMPS -eq 0 ] &&
local -a filts=( )
has_filter filt_polaroid && has_filter filt_apply_stamp ; then
 
for filter in ${FILTERS_IND[@]} ; do
if [ "$filter" == "filt_polaroid" ]; then
filts=( "${filts[@]}" "$filter" filt_apply_stamp )
elif [ "$filter" == "filt_apply_stamp" ]; then
continue;
else
filts=( "${filts[@]}" $filter )
fi
done
FILTERS_IND=( "${filts[@]}" )
unset filts
fi
# The shoftshadow and randrot filters must be in the correct place
# or they will affect the image incorrectly.
# Additionally the default filters can be disabled from the command
# line (with --disable), they're removed from the filter chain here
local -a filts=( ) end_filts=( )
for filter in ${FILTERS_IND[@]} ; do
case "$filter" in
filt_softshadow)
# Note the newer soft shadows code (1.0.99 onwards) behaves slightly
# differently. On previous versions disabling shadows only affected
# the montage shadow (but e.g. the polaroid mode preserved them),
# this is no longer true
if [ $DISABLE_SHADOWS -ne 1 ]; then
end_filts[100]="filt_softshadow"
fi
;;
filt_apply_stamp)
if [ $DISABLE_TIMESTAMPS -ne 1 ]; then
filts=( "${filts[@]}" "$filter" )
fi
;;
filt_randrot) end_filts[200]="filt_randrot" ;;
*) filts=( "${filts[@]}" "$filter" ) ;;
esac
done
FILTERS_IND=( "${filts[@]}" "${end_filts[@]}" )
 
# Override-able options check, in case they were set from overrides instead
#+of equivalent command-line options. Will check the actual selected values,
#+i.e. fail silently if the overrides aren't effective
[ "$USR_th_height" ] && { check_height "$th_height" || exit $? ; }
[ "$USR_numcaps" ] && { check_numcaps "$numcaps" || exit $? ; }
[ "$USR_interval" ] && { check_interval "$interval" || exit $? ; }
# Interval=0 == default interval
fptest "$interval" -eq 0 && interval=$DEFAULT_INTERVAL
 
sanitise_rename_pattern
}
 
check_height() { # Acceptable height
if ! is_number "$1" && ! is_percentage "$1" ; then
error "Height must be a (positive) number or a percentage. Got '$1'."
return $EX_USAGE
fi
}
 
check_numcaps() { # Acceptable numcaps
if ! is_number "$1" ; then
error "Number of captures must be a (positive) a number! Got '$1'."
return $EX_USAGE
fi
if [ $1 -eq 0 ]; then
error "Number of captures must be greater than 0! Got '$1'."
return $EX_USAGE
fi
}
 
check_interval() { # Acceptable interval
if ! get_interval "$1" >/dev/null ; then
error "Incorrect interval format. Got '$1'."
return $EX_USAGE
fi
}
 
# Main function.
# Creates the contact sheet.
# process($1 = file)
process() {
trace $FUNCNAME $@
local f=$1
 
local numcols=
# Save variables that will be overwritten and must be reset with multiple files
# pre_* will contain the user-supplied or default values
local pre_quirks=$QUIRKS
local pre_aspect_ratio=$aspect_ratio
local pre_output_format="$output_format"
INTERNAL_MAXREWIND_REACHED=0 # Reset for each file
 
# XXX: Some of this should be moved to coherence_check
if [ $DVD_MODE -eq 1 ]; then # DVD Mode
f="$DVD_FILE"
DVD_DEVICE=
local dvdn=$(realpathr "$f") # dvdn might be a device or an ISO
if [ -f "$dvdn" ]; then
# It's an ISO
DVD_MOUNTP=$(get_dvd_image_mountpoint)
if [ -z "$DVD_MOUNTP" ]; then
# Only in Linux does this matter
if ! is_linux ; then
warn "Video properties detection for ISO files is not accurate"
else
warn "Mount DVD image to get better video properties detection"
fi
fi
elif [ ! -r "$dvdn" ]; then
# It's something else we cannot read
error "Can't access DVD ($f)"
return $EX_NOINPUT
else
# It's a device. Note BSD has no concept of block devices.
# It MUST be mounted to continue. This is required to allow ffmpeg detection
#+and to calculate file size
if ! mount | egrep -q "^$dvdn\ " ; then
error "DVD mode requires device ($f) to be mounted"
return $EX_UNAVAILABLE
fi
DVD_DEVICE="$dvdn"
DVD_MOUNTP=$(mount | grep -o "^$DVD_DEVICE *on [^ ]*" | cut -d' ' -f3)
dvdn="DVD"
fi
inf "Processing $dvdn..."
unset dvdn
if ! is_number "$1" ; then
error "DVD Title must be a number (e.g.: \$ vcs -V /dev/dvd 1)"
exit $EX_USAGE
fi
DVD_TITLE=$1
if [ $DVD_TITLE -eq 0 ]; then
local dt="$(lsdvd "$DVD_FILE" 2>/dev/null | grep 'Longest track:' | \
cut -d' ' -f3- | sed 's/^0*//')"
if ! is_number "$dt" ; then
error "Failed to autodetect longest DVD title"
exit $EX_INTERNAL
fi
DVD_TITLE=$dt
DVD_VTS=$(lsdvd -t$DVD_TITLE -v "$DVD_FILE" 2>/dev/null | grep -o 'VTS: [0-9]*' | cut -d' ' -f2)
unset dt
inf "Using DVD Title #$DVD_TITLE (VTS: $DVD_VTS)"
fi
else # Not DVD Mode:
if [ ! -f "$f" ]; then
error "File \"$f\" doesn't exist"
return $EX_NOINPUT
fi
 
inf "Processing $f..."
fi
 
# {{SET_E}} Beware, set -e will break this
identify_video "$f"
local ecode=$?
[ $ecode -eq 0 ] || {
case $ecode in
3) error "Unable to find length of file \"$f\". Can't continue." ;;
4) error "Unable to detect dimensions of file \"$f\". Can't continue." ;;
*) error "Failure while analysing file \"$f\". Can't continue." ;;
esac
return $EX_UNAVAILABLE
}
 
# Identification-only mode?
[ "$UNDFLAG_IDONLY" ] && dump_idinfo && return 0
 
# Vidcap/Thumbnail height
local vidcap_height=$th_height
if is_percentage "$th_height" ; then
local pc=${th_height/%%/} # BASH %% == RE %$
vidcap_height=$(awkex "int ((${VID[$H]} * ${pc}) / 100 + 0.5)")
inf "Height: $th_height of ${VID[$H]} = $vidcap_height"
fi
if ! is_number "$vidcap_height" || [ "$vidcap_height" -eq 0 ]; then
vidcap_height=${VID[$H]}
fi
# -2: DVD Mode autodetection => If ffmpeg/mplayer was unable autodetect, otherwise
#+ honor detected value
[ "-2" == "$aspect_ratio" ] && [ -z "${VID[$ASPECT]}" ] && aspect_ratio=-1
[ "-2" == "$aspect_ratio" ] && [ "${VID[$ASPECT]}" ] && aspect_ratio=0
if [ "0" == "$aspect_ratio" ]; then
if [ "${VID[$ASPECT]}" ]; then
# Aspect ratio in file headers, honor it
aspect_ratio=$(awkex "${VID[$ASPECT]}")
else
aspect_ratio=$(awkex "${VID[$W]} / ${VID[$H]}")
fi
elif [ "-1" == "$aspect_ratio" ]; then
aspect_ratio=$(guess_aspect ${VID[$W]} ${VID[$H]})
inf "Aspect ratio set to $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
# Compute the stamps (if in auto mode)...
if [ $manual_mode -eq 1 ]; then
# Note TIMECODES must be set as an array to get the correct count in
# manual mode; in automatic mode it will be set correctly inside
# compute_timecodes()
TIMECODES=( ${initial_stamps[@]} )
else
TIMECODES=${initial_stamps[@]}
compute_timecodes $timecode_from $interval $numcaps || {
return $?
}
fi
 
local output=$(new_temp_file '-preview.png')
local VIDCAPFILE=00000005.png
 
# If the temporal vidcap already exists, abort
if [ -f $VIDCAPFILE ]; then
error "File $VIDCAPFILE 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=( "${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 $CTX_HL $n || {
local r=$?
error "Failed to apply transformations to the capture."
return $r
}
hlcapfile=$(new_temp_file "-hl-$(pad 6 $n).png")
mvq "$VIDCAPFILE" "$hlcapfile"
capfiles=( "${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 $CTX_STD $n || return $?
 
# identified by capture number, padded to 6 characters
capfile=$(new_temp_file "-cap-$(pad 6 $n).png")
mvq "$VIDCAPFILE" "$capfile"
capfiles=( "${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 $CTX_EXT $n || return $?
 
capfile=$(new_temp_file "-excap-$(pad 6 $n).png")
mvq "$VIDCAPFILE" "$capfile"
capfiles=( "${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
 
local vcodec=${VID[$VCNAME]}
local acodec=${VID[$ACNAME]}
 
if [ "${VID[$CHANS]}" ] && is_number "${VID[$CHANS]}" &&[ ${VID[$CHANS]} -ne 2 ]; then
if [ ${VID[$CHANS]} -eq 1 ]; then
acodec="$acodec (mono)"
else
acodec="$acodec (${VID[$CHANS]}ch)"
fi
fi
 
local csw=$(imw "$output") exw= hlw=
local width=$csw
if [ "$HLTIMECODES" ] || [ "$extended_factor" != "0" ]; then
inf "Merging contact sheets..."
if [ "$HLTIMECODES" ]; then
local hlw=$(imw "$hlfile")
if [ $hlw -gt $width ]; then width=$hlw ; fi
fi
if [ "$extended_factor" != "0" ]; then
local exw=$(imw $extoutput)
if [ $exw -gt $width ]; then width=$exw ; fi
fi
fi
if [ $csw -lt $width ]; then
local csh=$(imh "$output")
# Expand the standard set to the maximum width of the sets by padding both sides
# For some reason the more obvious (to me) convert command-lines lose
# the transparency
convert \( -size $(( ($width - $csw) / 2 ))x$csh xc:transparent \) "$output" \
\( -size $(( ($width - $csw) / 2 ))x$csh xc:transparent \) +append "$output"
unset csh
fi
 
# If there were highlights then mix them in
if [ "$HLTIMECODES" ]; then
# For some reason adding the background also adds padding with:
# convert \( -background LightGoldenRod "$hlfile" -flatten \) \
# \( "$output" \) -append "$output"
# replacing it with a "-composite" operation apparently works
# Expand the highlights to the correct size by padding
local hlh=$(imh "$hlfile")
if [ $hlw -lt $width ]; then
convert \( -size $(( ($width - $hlw) / 2 ))x$hlh xc:transparent \) "$hlfile" \
\( -size $(( ($width - $hlw) / 2 ))x$hlh xc:transparent \) +append "$hlfile"
fi
convert \( -size ${width}x${hlh} xc:LightGoldenRod "$hlfile" -composite \) \
\( -size ${width}x1 xc:black \) \
"$output" -append "$output"
unset hlh
fi
# Extended captures
if [ "$extended_factor" != 0 ]; then
# Already set local exw=$(imw "$extoutput")
local exh=$(imh "$extoutput")
if [ $exw -lt $width ]; then
# Expand the extended set to be the correct size
convert \( -size $(( ($width - $exw) / 2 ))x$exh xc:transparent \) "$extoutput" \
\( -size $(( ($width - $exw) / 2 ))x$exh xc:transparent \) +append "$extoutput"
fi
convert "$output" -background Transparent "$extoutput" -append "$output"
fi
# Add the background; -trim added in 1.11. I'm unsure of why but whithout trimmin extra blank
#+space is added at the top
local dotrim=
[ $DISABLE_SHADOWS -eq 1 ] && [ -z "$HLTIMECODES" ] && dotrim=-trim
convert -background "$bg_contact" "$output" -flatten $dotrim "$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=$(imw "$output")
# 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.
local filename_label="Filename"
local filesize_label="File size"
local filename_value=
local filesize_value=
if [ $DVD_MODE -eq 1 ]; then
# lsdvd is guaranteed to be installed if DVD mode is enabled
local dvd_label=$(lsdvd "$DVD_FILE" 2>/dev/null | grep -o 'Disc Title: .*' | cut -d' ' -f3-)
# Need a mountpoint to get the actual *title* size
if [ "$DVD_MOUNTP" ]; then
filename_label="Disc label"
filename_value="$dvd_label"
filesize_label="Title size"
filesize_value="$(get_dvd_size)"
else
# Not mounted. We can get the disc size but this will include any other titles.
# Since 1.11 mounting DVDs is mandatory to get the title size. Both for ISOs and
#+ devices
filename_value="$(basename "$DVD_FILE") $filename_value (DVD Label: $dvd_label)"
is_linux && warn "DVD not mounted: Can't detect title file size."
filesize_label='Disc image size'
filesize_value="$(get_pretty_size $(dur "$f"))"
fi
else
filename_value="$(basename "$f")"
filesize_value="$(get_pretty_file_size "$f")"
fi
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_label: " \
-font "$fn_font" label:"$filename_value" +append \
\) \
-font "$font_heading" \
label:"$filesize_label: $filesize_value" \
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
 
local wanted_name=${OUTPUT_FILES[$FILEIDX]}
[ "$wanted_name" ] && \
if egrep -q '\.[^\.]+$' <<<"$wanted_name" ; then
output_format=$(filext "$wanted_name")
inf "Output format set from output filename"
else # No file extension in wanted_name
wanted_name="$wanted_name.$output_format"
fi
[ "$wanted_name" ] || wanted_name="$(basename "$f").$output_format"
 
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" "$wanted_name" ) || {
error "Failed to write the output file!"
return $EX_CANTCREAT
}
inf "Done. Output wrote to $output_name"
 
let 'FILEIDX++,1' #,1 so that it's always ok
[ "$UNDFLAG_HANG" ] && read -p 'Main loop paused, hit Enter key to continue... '
cleanup
 
# Re-set variables (for multi-file input)
QUIRKS=$pre_quirks
aspect_ratio=$pre_aspect_ratio
output_format="$pre_output_format"
}
 
# }}} # Core functionality
 
# {{{ # Debugging helpers
 
# Tests integrity of some operations.
# Used to test internal changes for consistency.
# It helps me to identify incorrect optimizations.
# internal_integrity_test(). Running with -D triggers this.
internal_integrity_test() {
local t op val ret comm retval=0
 
# Replacements
local SEQ=$(type -pf seq)
local JOT=$(type -pf jot)
local ex rex
if [ "$SEQ" ]; then
ex=$($SEQ 1 10)
elif [ "$JOT" ]; then
ex=$($JOT 10 1)
else
warn "Can't check seqr() correctness, neither seq nor jot found"
fi
if [ "$ex" ]; then
exr=$(seqr 1 10)
if [ "$exr" != "$ex" ]; then
error "Failed test: seqr() not consistent with external result"
let 'retval++,1'
else
inf "Passed test (seq replacement): consistent result"
fi
fi
 
# Textual tests, compare output to expected output
# Tests are in the form "operation arguments correct_result #Description"
TESTS=( # Note bash2 doesn't like this array as a local variable
# TODO: UNIX vs GNU
#"stonl ..."
 
"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"
#bc result: "pyth_th 16 9 18.35755975068581929849 #FP pythagorean theorem"
#perl result: "pyth_th 16 9 18.3575597506858 #FP pythagorean theorem"
"pyth_th 16 9 18.3576 #FP pythagorean theorem"
 
"get_interval 2h 7200 #Hours parsing"
"get_interval 2m 120 #Minutes parsing"
"get_interval 30S 30 #Seconds parsing"
"get_interval .30 .30 #Milliseconds parsing"
# Since now the numbers are passed to perl, leading zeroes become octal
# numbers. Must ensure they are handled correctly
"get_interval 09h010m09s1 33010 #Parsing with leading zeroes"
"get_interval 0400 400 #Parsing shorthand"
# Extended syntax
"get_interval 30m30m1h 7200 #Repeated minutes parsing"
)
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=$(grep -o "[^ ]* #$comm\$"<<<$t | cut -d' ' -f1)
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
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=$(grep -o "[^ ]* #$comm\$"<<<$t | cut -d' ' -f1)
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
local inff=inf
[ "$HAS_COLORS" ] && inff=infplain
$inff "Video Contact Sheet *NIX v${VERSION}, (c) 2007-2010 Toni Corvera" "sgr0"
}
 
# Prints the list of options to stdout
# show_help($1 = long = '')
show_help() {
local P=$(basename $0)
local showlong=$1
local mpchosen= ffchosen= longhelp= funkyex=
[ -z "$MPLAYER" ] && mpchosen=' [Not available]'
[ "$MPLAYER" ] && [ $decoder == $DEC_MPLAYER ] && mpchosen=' [Selected]'
[ -z "$FFMPEG" ] && ffchosen=', Not available'
[ "$FFMPEG" ] && [ $decoder == $DEC_FFMPEG ] && ffchosen=', Selected'
# This portion of help is only shown when in full help mode (--fullhelp)
[ "$showlong" ] && longhelp=\
" --anonymous Disable the 'Preview created by' line in the footer.
-Ij|-Ik|-Ij=fontname|-Ik=fontname
--nonlatin Use an alternate font in the heading for the video file
name. Required to display correctly file names in
some languages (Chinese, Japanese, Hangul,
Cyrillic, ...).
Will try to use a reasonable font. Can also be set
manually like:
$ vcs -Ij=Sazanami-Mincho-Regular file.avi
or
$ vcs -Ij=/usr/share/fonts/ttf/ttf-japanese-mincho.ttf
Use \"identify -list font\" to list the available fonts
-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\"-).
 
Tweaks and workarounds:
-Ws Increase length of safe measuring (try harder). Repeat
to increase further.
-WS Scan all video, if required, to get a safe measuring.
-Wp Increase safe measuring precission (i.e. halve the
probe stepping). Repeat to increase further.
-WP Inverse of -Wp.
-Wo Change ffmpeg's arguments order, might work with some
files that fail otherwise.
-Wc Disable colour in console messages.
Obscure options, debugging tools and workarounds:
-R <file>
--randomsource <file> Use the provided file as a source for random \"values\":
they won't be random anymore, so two runs with the same
source and same arguments will produce the same output
in modes which use using randomisation (e.g. the
\"photos\" and \"polaroid\" modes).
-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.
"
# The --funky help is really long, so make it shorter by default,
# only show the complete help when --fullhelp is used
[ "$showlong" ] && funkyex="
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.
\"polaroidframe\": Use '-kL' or '--funky polaroidframe'
Adds a polaroid picture-like white frame to each
image.
\"photos\": Use '-kc' or '--funky photos'
Combination of rotate, photoframe and overlap.
Same as -kp -kr -ko.
\"polaroid\": Use '-kp' or '--funky polaroid'
Combination of rotate, polaroidframe and overlap.
Same as -kL -kr -ko.
\"film\": Use '-ki' or '--funky film'
Imitates filmstrip look.
\"random\": Use '-kx' or '--funky random'
Randomizes colours and fonts."
[ -z "$showlong" ] && funkyex="
Available: overlap, rotate, photoframe, polaroidframe,
photos, polaroid, film, random
Use --fullhelp for more details."
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.
-o|--output <file> File name of output. When ommited will be derived from
the input filename. Can be repeated for multiple files.
-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.
-T|--title <arg> Add a title above the vidcaps.
-j|--jpeg Output in jpeg (by default output is in png).
-j2|--jpeg 2 Output in jpeg 2000
-V|--dvd <file.iso|dvd_device>
DVD Mode, use file.iso as DVD. In this mode the
<file> argument must point to the title number, e.g.:
$ vcs -V somedvd.iso 1
Passing title 0 will use the default (longest) title.
$ vcs -V /dev/dvd 0
Implies -A (auto aspect ratio)
-M|--mplayer Use Mplayer to capture$mpchosen
-F|--ffmpeg Use FFmpeg to capture [Default$ffchosen]
-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.
-q|--quiet Don't print progess messages just errors. Repeat to
mute completely even on error.
-h|--help Show basic help and exit.
--fullhelp Show the complete help and exit.
-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
* padding: use -dp or --disable padding
(note shadows introduce some extra padding)
-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.
-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.
-k <arg>
--funky <arg> Funky modes:$funkyex
$longhelp
Examples:
Create a contact sheet with default values (vidcaps at intervals of
$DEFAULT_INTERVAL seconds), will be saved to 'video.avi.png':
\$ $P video.avi
 
Create a sheet with vidcaps at intervals of 3 and a half minutes, save to
'output.jpg':
\$ $P -i 3m30 input.wmv -o output.jpg
 
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
 
#### Entry point ####
 
# Important to do this before any message can be thrown
init_feedback
 
# Ensure $GETOPT is GNU/Linux-style getopt
choose_getopt
 
# Execute exithdlr on exit
trap exithdlr EXIT
 
show_vcs_info
 
# Test requirements. Important, must check before looking at the
# command line (since getopt is used for the task)
test_programs
 
# The command-line overrides any configuration. And the configuration
# is able to change the program in charge of parsing options ($GETOPT)
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]]
# TODO: Why does FreeBSD's GNU getopt ignore -n??
TEMP=$("$GETOPT" -n "$0" -s bash \
-o i:n:u:T:f:t:S:j::hFMH:c:ma:l:De::U::qAO:I:k:W:E:d:V:R:Z:o:P: \
--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:,dvd:,randomsource:,undocumented:,output:,fullhelp,profile:,"\
"jpeg2,nonlatin" \
-- "$@")
eval set -- "$TEMP"
 
while true ; do
case "$1" in
-i|--interval)
check_interval "$2"
interval=$(get_interval "$2")
timecode_from=$TC_INTERVAL
USR_interval=$interval
USR_timecode_from=$TC_INTERVAL
shift # Option arg
;;
-n|--numcaps)
check_numcaps "$2"
numcaps="$2"
timecode_from=$TC_NUMCAPS
USR_numcaps="$2"
USR_timecode_from=$TC_NUMCAPS
shift # Option arg
;;
-o|--output)
current=${#OUTPUT_FILES[@]}
OUTPUT_FILES[$current]="$2"
shift ;;
-u|--username) user="$2" ; USR_user="$user" ; shift ;;
-U|--fullname)
# -U accepts an optional 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" ; USR_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
USR_fromtime="$fromtime"
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
USR_end_offset="$end_offset"
shift
;;
-t|--to)
if ! totime=$(get_interval "$2") ; then
error "Ending timestamp must be a valid timecode. Got '$2'."
exit $EX_USAGE
fi
if fptest "$totime" -eq 0 ; then
error "Ending timestamp was set to 0, set to movie length."
totime=-1
fi
USR_totime=$totime
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=( "${HLTIMECODES[@]}" "$temp" )
shift
;;
--jpeg2) # Note --jpeg 2 is also accepted
output_format=jp2
USR_output_format=jp2
;;
-j|--jpeg)
if [ "$2" ]; then # Arg is optional, 2 is for JPEG 2000
# 2000 is also accepted
if [ "$2" != "2" ] && [ "$2" != "2000" ]; then
error "Use -j for jpeg output or -j2 for JPEG 2000 output. Got '-j$2'."
exit $EX_USAGE
fi
output_format=jp2
else
output_format=jpg
fi
USR_output_format="$output_format"
shift
;;
-h|--help) show_help ; exit $EX_OK ;;
--fullhelp) show_help 'full' ; exit $EX_OK ;;
--shoehorn)
warn "$1 is deprecated, please use '--undocumented shoehorn=\"$2\"' instead"
shoehorned="$2"
shift
;;
-F) decoder=$DEC_FFMPEG ; USR_decoder=$decoder ;;
-M) decoder=$DEC_MPLAYER ; USR_decoder=$decoder ;;
-H|--height)
check_height "$2"
th_height="$2"
USR_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"
USR_aspect_ratio="$2"
shift
;;
-A|--autoaspect) aspect_ratio=-1 ; USR_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"
USR_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
USR_extended_factor=$extended_factor
shift
;;
--mincho)
warn "--mincho is deprecated, please use -Ij or --nonlatin instead"
if [ -z "$USR_FONT_MINCHO" ]; then
set_extended_font
inf "Filename font set to '$FONT_MINCHO'"
fi
;;
# Unlike -I, --nonlatin does not accept a font name
--nonlatin)
if [ -z "$USR_FONT_MINCHO" ]; then
set_extended_font
inf "Filename font set to '$FONT_MINCHO'"
fi
;;
-I)
# Extended/non-latin font
# New syntax introduced in 1.11:
# -Ij: Try to pick automatically a CJK font. Might fail and abort
# -Ij='Font name or file': Set font manually
 
if [ "$2" ] ; then
# If an argument is passed, test it is one of the known ones
case "$2" in
k|j|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;
if [ ${#2} -gt 1 ]; then
# j=, k= syntax
FONT_MINCHO="${2:2}"
USR_FONT_MINCHO="$FONT_MINCHO"
inf "Filename font set to '$FONT_MINCHO'"
fi
# If the user didn't pick one, try to select automatically
if [ -z "$USR_FONT_MINCHO" ]; then
set_extended_font
inf "Filename font set to '$FONT_MINCHO'"
fi
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
if grep -q 'GETOPT=' <<<"$2" ; then
# If we're here, getopt has already been found and works, so it makes no
# sense to override it; on the other hand, if it hasn't been correctly
# set/detected we won't reach here
warn "GETOPT can't be overridden from the command line."
else
override "$2" "command line"
fi
shift
;;
-W)
case "$2" in
# (classic) Workaround mode. See wa_ss_* declarations at the start for details
o) wa_ss_af='-ss ' ; wa_ss_be='' ;;
# Console colout
# Once: Disable console colour, use prefixes instead
# Twice: Disable prefixes too
c)
set_feedback_prefixes
[ "$UNDFLAG_NOPREFIX" ] && plain_messages=1
UNDFLAG_NOPREFIX=1
;;
# Double length of video probed in safe measuring
# Semi-undocumented traits:
# - Can be repeated, will double for each instance
# - -Ws -Ws -Ws = -Ws3
s|s[0-9]|s[0-9][0-9])
[ ${#2} -gt 1 ] && n=${2:1} || n=1
QUIRKS_MAX_REWIND=$(awkex "$QUIRKS_MAX_REWIND * (2^$n)")
let 'INTERNAL_WS_C+=n,1'
;;
# Brute force -Ws: Test all the length of the file if required
S) QUIRKS_MAX_REWIND=-1 ;;
# Increase precission of safe length measuring (halve the stepping)
# Like -Ws can be repeated
p|p[0-9]|p[0-9][0-9])
[ ${#2} -gt 1 ] && n=${2:1} || n=1
QUIRKS_LEN_STEP=$(awkex "$QUIRKS_LEN_STEP / (2^$n)")
let 'INTERNAL_WP_C+=n,1'
;;
# Inverse of -Wp: Decrease precission of safe length measuring
# i.e.: will try less times <-> will be quicker but less accurate
# desirable when -Ws or -WS are used.
# Can also be repeated
P|P[0-9]|P[0-9][0-9])
[ ${#2} -gt 1 ] && n=${2:1} || n=1
QUIRKS_LEN_STEP=$(awkex "$QUIRKS_LEN_STEP * (2^$n)")
let 'INTERNAL_WP_C-=n,1'
;;
# -Wb (Semi-undocumented): Disable safe mode. Use this to force accepting
#+broken/partial files. Only makes sense when testing or in combination
#+with stuff like '-Z idonly'
b) QUIRKS=-2 ;; # Quirks < 0 : No safe mode
*)
error "Wrong argument. Use --fullhelp for a list available workarounds. Got -W$2."
exit $EX_USAGE
;;
esac
shift
;;
-k|--funky) # Funky modes
case "$2" in # Note older versions (<1.0.99) were case-insensitive
p|polaroid) # Same as overlap + rotate + polaroid
inf "Changed to polaroid funky mode."
FILTERS_IND=( "${FILTERS_IND[@]}" 'filt_polaroid' 'filt_randrot' )
CSHEET_DELEGATE='csheet_overlap'
# XXX: The newer version has a lot less flexibility with these many
# hardcoded values...
grav_timestamp=South
fg_tstamps=Black
bg_tstamps=Transparent
pts_tstamps=$(( $pts_tstamps * 3 / 2 ))
;;
c|photos) # Same as overlap + rotate + photoframe, this is the older polaroid
inf "Changed to photos funky mode."
FILTERS_IND=( "${FILTERS_IND[@]}" 'filt_photoframe' 'filt_randrot' )
CSHEET_DELEGATE='csheet_overlap'
# The timestamp must change location to be visible most of the time
grav_timestamp=NorthWest
;;
o|overlap) # Random overlap mode
CSHEET_DELEGATE='csheet_overlap'
grav_timestamp=NorthWest
;;
r|rotate) # Random rotation
FILTERS_IND=( "${FILTERS_IND[@]}" 'filt_randrot' )
;;
f|photoframe) # White photo frame
FILTERS_IND=( "${FILTERS_IND[@]}" 'filt_photoframe' )
;;
L|polaroidframe) # White polaroid frame
FILTERS_IND=( "${FILTERS_IND[@]}" 'filt_polaroid ')
grav_timestamp=South
fg_tstamps=Black
bg_tstamps=Transparent
pts_tstamps=$(( $pts_tstamps * 3 / 2 ))
;;
i|film)
inf "Enabled film mode."
FILTERS_IND=( "${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
;;
-P|--profile)
case "$2" in
classic) # Classic colour scheme
bg_heading=YellowGreen bg_sign=SlateGray bg_contact=White
bg_title=White fg_heading=Black fg_sign=Black
;;
1.0) # 1.0a, 1.0.1a and 1.0.2b colourscheme
bg_heading=YellowGreen bg_sign=SandyBrown bg_contact=White
bg_title=White fg_heading=Black fg_sign=Black
;;
esac
shift
;;
-R|--randomsource)
if [ ! -r "$2" ]; then
error "Random source file '$2' can't be read"
exit $EX_USAGE
fi
init_filerand "$2"
inf "Using '$2' as source of semi-random values"
RANDFUNCTION=filerand
shift
;;
-d|--disable) # Disable default features
case $(tolower "$2") in
# timestamp (with no final s) is undocumented but will stay
t|timestamps|timestamp)
if [ $DISABLE_TIMESTAMPS -eq 0 ]; then
inf "Timestamps disabled."
# They'll be removed from the filter chain in coherence_check
DISABLE_TIMESTAMPS=1
fi
;;
s|shadows|shadow)
if [ $DISABLE_SHADOWS -eq 0 ]; then
inf "Shadows disabled."
# They will be removed from the filter chain in coherence_check
DISABLE_SHADOWS=1
fi
;;
p|padding)
if [ $HPAD -ne 0 ] ; then
inf "Padding disabled." # Kinda...
HPAD=0
fi
;;
*)
error "Requested disabling unknown feature. Got '$2'."
exit $EX_USAGE
;;
esac
shift
;;
-V|--dvd)
# XXX; Are there systems with no perl???
if ! type -pf perl >/dev/null ; then
error "DVD support requires perl"
exit $EX_UNAVAILABLE
fi
# DVD Mode requires lsdvd
if ! type -pf lsdvd >/dev/null ; then
error "DVD support requires the lsdvd program"
exit $EX_UNAVAILABLE
fi
DVD_MODE=1
DVD_FILE="$2"
decoder=$DEC_MPLAYER
aspect_ratio=-2 # Special value: Auto detect only if ffmpeg couldn't
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
;;
-Z|--undocumented)
# This is a container for, of course, undocumented functions
# These are used for testing/debugging purposes. Might (and will)
# change between versions, break easily and do no safety checks.
# In short, don't look at them unless told to do so :P
case "$2" in
# AWK was used for a little while in a WiP version
#set_awk=*) AWK="$(cut -d'=' -f2<<<"$2")" ; warn "[U] AWK=$AWK" ;;
# Hang the main process loop just before cleanup.
hang) UNDFLAG_HANG="On" ; warn "[U] Hang flag" ;;
# Print identification results, do nothing else
idonly) UNDFLAG_IDONLY="On" ; warn "[U] Id only" ;;
# ffmpeg path
set_ffmpeg=*)
FFMPEG=$(realpathr "$(cut -d'=' -f2<<<"$2")")
assert $LINENO 'test -x "$FFMPEG"'
warn "[U] FFMPEG=$FFMPEG"
;;
# mplayer path
set_mplayer=*)
MPLAYER=$(realpathr "$(cut -d'=' -f2<<<"$2")")
assert $LINENO 'test -x "$MPLAYER"'
warn "[U] MPLAYER=$MPLAYER"
;;
# Ignore one of the players
disable_ffmpeg)
FFMPEG=''
warn "FFmpeg disabled"
assert $LINENO "[ '$MPLAYER' ] || [ '$FFMPEG' ]"
decoder=$DEC_MPLAYER
;;
disable_mplayer)
MPLAYER=''
warn "Mplayer disabled"
assert $LINENO "[ '$MPLAYER' ] || [ '$FFMPEG' ]"
decoder=$DEC_FFMPEG
;;
# This is an old option from the first versions when the script
# failed a lot more, I haven't used it for years and I don't think
# anyone would need it anymore but I'll keep it at least for
# a few more versions
shoehorn=*)
shoehorned="$(cut -d'=' -f2-<<<"$2")"
;;
*) false ;;
esac
shift
;;
-D) # Repeat to just test consistency
if [ $DEBUGGED -gt 0 ]; then
[ $decoder -eq $DEC_MPLAYER ] && d='mplayer'
[ $decoder -eq $DEC_FFMPEG ] && d='ffmpeg'
infplain '[ svn $Rev$ ]'
cat >&2 <<-EOD
=== Setup ===
GETOPT: $GETOPT
MPLAYER: $MPLAYER
FFMPEG: $FFMPEG
AWK: $(type -pf awk)
Filterchain: [ ${FILTERS_IND[*]} ]
Decoder: $d
Safe step: $QUIRKS_LEN_STEP
=== Versions ===
Bash: $BASH_VERSION
Getopt: $($GETOPT --version)
EOD
# FIXME: Any portable way to print AWK version?
exit
fi
DEBUG=1
inf "Testing internal consistency..."
internal_integrity_test && warn "All tests passed" || error "Some tests failed!"
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
elif [ "$2" ]; then
multiple_input_files=1
fi
# }}} # Command line parsing
 
# The coherence check ensures the processed options are
# not incoherent/incompatible with the input files or with
# other given options
coherence_check || {
exit $?
}
 
set +e # Don't fail automatically. Blocks marked with {{SET_E}} will break if this changes
for arg do process "$arg" ; done
 
# Script ends here, everything below are comments
# ===========================================================================
#
# Bash syntax notes # {{{
# These are some notes for my own reference (or for those trying to read the script)
# regarding bash syntax nuissances.
#
# * herestring redirection, '<<<$string', (used extensively in vcs) was introduced in bash 2.05b
# * sed s/[ ,]/ * /g <=> ${var//[ ,]/ * } [Much faster due to not forking]
# sed s/[ ,]/ * / <=> ${var/[ ,]/ * }
# * bash2: declaring local empty arrays like 'local a=( )' makes bash think they're strings
# 'local -a' must be used instead
# bash3 has no problem with this
# * bash2: 'arr+=( elem )' for array push is not supported, use 'arr=( "${arr[@]}" elem )' instead
# += is a bash3 syntax modification, bash3.1 extended it further, arithmetic += works
# inside let
# * bash2: [*] expands as a string while [@] expands as an array. Both have trouble with spaces
# in elements though
# * performance: bash loops are often slower than awk or perl
# * performance: grep + cut proved faster than an equivalent sed -r s// replacement
# }}} # Bash syntax notes
#
# vim:set ts=4 ai foldmethod=marker nu: #
Property changes:
Added: svn:executable
Added: svn:keywords
+Rev Id Date
\ No newline at end of property
/video-contact-sheet/tags/1.11/rpm-package/vcs.spec.in
0,0 → 1,97
#
# spec file for vcs rpm
#
# based on mp3plot's which in turn was based on other sources
#
 
%define is_mandrake %(test -e /etc/mandrake-release && echo 1 || echo 0)
%define is_suse %(test -e /etc/SuSE-release && echo 1 || echo 0)
%define is_fedora 0%{?fedora}
%define is_redhat 0%{?rhl}
%define is_rhel 0%{?rhel}
 
%define distname generic
%define disttag .generic
 
%if %{is_fedora}
%define distname fedora
%define disttag %{dist}
%endif
%if %{is_redhat}
%define distname redhat
%define disttag %{dist}
%endif
%if %{is_mandrake}
%define distname mandrake
%define disttag .mdk
%endif
%if %{is_suse}
%define distname suse
%define disttag .suse
%endif
%if %{is_rhel}
%define distname rhel
%define disttag %{dist}
%endif
 
Name: vcs
Summary: Tool to create contact sheets (previews) from videos
Version: @VERSION@
Release: 1%{?disttag},upstream
License: LGPL
Packager: @PACKAGER@
Group: Applications/Multimedia
Source0: http://p.outlyer.net/%{name}/files/%{name}-%{version}.tar.gz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-build
BuildArch: noarch
# TODO: How to set alternative dependencies? i.e. mplayer/ffmpeg
Requires: mplayer / ffmpeg
Requires: bash >= 2.05b
Requires: ImageMagick >= 6.3.5-7
Requires: coreutils
URL: http://p.outlyer.net/vcs/
#BuildRequires:
#Prereq: /sbin/ldconfig
#Requires:
AutoReqProv: yes
## Allow relocation (e.g. rpm --prefix /opt/vcs)
Prefix: /usr
 
%description
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.
 
%prep
#echo %_target
echo Building %{name}-%{version}-%{release}
 
%setup -q -n %{name}-%{version}
 
%build
 
%install
make DESTDIR=%buildroot install
 
%clean
[ ${RPM_BUILD_ROOT} != "/" ] && rm -rf ${RPM_BUILD_ROOT}
 
#%post
# postinst
 
#%postun
# postuninst
 
%files
%defattr(-,root,root)
# binary
%{_bindir}/%{name}
# Manpage
#%{_mandir}/man1/%{name}.1.gz
%doc CHANGELOG
 
%changelog
* Sun Mar 07 2010 - outlyer (at) gmail (dot) com
- Initial RPM packaging
 
Property changes:
Added: svn:keywords
+Rev Id Date
\ No newline at end of property
/video-contact-sheet/tags/1.11/rpm-package/Makefile
0,0 → 1,33
# $Id$
 
prefix:=/usr
DESTDIR:=/
VERSION:=$(shell head -50 vcs | grep 'declare -r VERSION=' | perl -pe 's/.*"(.*)".*/\1/')
#PACKAGER=$(shell grep ^`id -nu` /etc/passwd | cut -d: -f5 | cut -d, -f1)
 
all:
@echo 'There'\''s nothing to be built'
@echo 'Available make commands:'
@echo '* Build RPM (and generate spec)'
@echo ' $$ make rpm'
@echo '* Generate spec file'
@echo ' $$ make spec'
@echo '* Install'
@echo ' $$ make install'
 
clean:
$(RM) vcs.spec
 
spec: vcs.spec.in
test "$(VERSION)"
@test "$(PACKAGER)" || echo "No PACKAGER set!"
@test "$(PACKAGER)"
cat vcs.spec.in | sed 's!@VERSION@!$(VERSION)!g' | \
sed 's!@PACKAGER@!$(PACKAGER)!g' > vcs.spec
 
install:
mkdir -p $(DESTDIR)$(prefix)/bin
install -m755 -o0 -g0 vcs $(DESTDIR)$(prefix)/bin
 
 
.PHONY: all install clean spec
Property changes:
Added: svn:keywords
+Rev Id Date
\ No newline at end of property
/video-contact-sheet/tags/1.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
Homepage: http://p.outlyer.net/vcs/
 
Package: vcs
Architecture: all
Depends: bash (>= 2.05b), imagemagick (>= 6.3.5-7), mplayer | ffmpeg
Recommends: lsdvd, ttf-dejavu-core
Description: tool to create contact sheets (previews) 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.
/video-contact-sheet/tags/1.11/debian-package/debian/changelog
0,0 → 1,44
vcs (1.11-upstream.1) experimental; urgency=low
 
* debian/control:
- Added min. bash version
- Rw-worded short description
- Don't Depend on bc anymore
- Remove mktemp (now coreutils) from Depends:, they're essential anyway
- Bumped min IM to 6.3.5-7
 
-- Toni Corvera <outlyer@gmail.com> Sun, 07 Mar 2010 21:47:41 +0100
 
vcs (1.0.100a-upstream.1) experimental; urgency=low
 
* New version
 
-- Toni Corvera <outlyer@gmail.com> Fri, 10 Apr 2009 17:08:33 +0200
 
vcs (1.0.99-upstream.0) experimental; urgency=low
 
* New version.
* debian/control:
- Added lsdvd as recommendation (required for dvd support)
- Using 'Homepage:'
 
-- Toni Corvera <outlyer@gmail.com> Wed, 11 Mar 2009 22:50:25 +0100
 
vcs (1.0.12-upstream.1) experimental; urgency=low
 
* debian/control: Added missing requirement (gsfonts)
 
-- Toni Corvera <outlyer@gmail.com> Mon, 06 Oct 2008 14:26:27 +0200
 
vcs (1.0.12-upstream.0) experimental; urgency=low
 
* New version.
* Added suffix to version number.
 
-- Toni Corvera <outlyer@gmail.com> Wed, 16 Apr 2008 17:59:46 +0200
 
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.11/debian-package/debian/copyright
0,0 → 1,35
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'.
 
/video-contact-sheet/tags/1.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.11/debian-package/debian/dirs
0,0 → 1,0
usr/bin
/video-contact-sheet/tags/1.11/debian-package/debian/compat
0,0 → 1,0
5
/video-contact-sheet/tags/1.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.11/Makefile
0,0 → 1,63
#!/usr/bin/make -f
# $Id$
 
VER=$(shell grep VERSION vcs | head -n1 | sed 's/\#.*//' | 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
 
check-rel:
@if head -n50 vcs | grep -q 'RELEASE=0' ; then \
echo 'RELEASE is set to 0!' ; false ; fi
 
prep:
cp vcs CHANGELOG debian-package/
cp vcs rpm-package/
 
dist: check-rel check-no-svn prep gz bz2 plaintext changelog deb rpm cleanup
 
gz:
cp vcs vcs-$(VER)
chmod -x vcs-$(VER)
gzip -9 vcs-$(VER)
 
bz2:
cp vcs vcs-$(VER)
chmod -x vcs-$(VER)
bzip2 -9 vcs-$(VER)
 
plaintext:
cp vcs vcs-$(VER)
chmod -x vcs-$(VER)
 
changelog:
gzip -9 CHANGELOG
gzip -dc CHANGELOG.gz > CHANGELOG
 
cleanup:
$(RM) vcs Makefile *.changes
$(RM) -r debian-package
$(RM) -r rpm-package
 
deb:
cd debian-package/ && dpkg-buildpackage -rfakeroot -us -uc -b
 
rpm: vcs.spec
mkdir rpm-package/vcs-$(VER)/
cp vcs CHANGELOG rpm-package/Makefile rpm-package/vcs-$(VER)/
mv vcs.spec rpm-package/vcs-$(VER)/
cd rpm-package && tar zcvf vcs-$(VER).tar.gz vcs-$(VER)
$(RM) vcs.spec
$(RM) -r rpm-package/vcs-$(VER)
cd rpm-package && fakeroot rpmbuild -tb vcs-$(VER).tar.gz
-ln -s ~/rpmbuild/RPMS/noarch/vcs-$(VER)-*.rpm .
$(RM) rpm-package/vcs-$(VER).tar.gz
 
vcs.spec: rpm-package/vcs.spec.in
cd rpm-package && $(MAKE) -f Makefile spec PACKAGER="$(PACKAGER)"
mv rpm-package/vcs.spec .
 
.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.11/tests/test_funkymodes
0,0 → 1,27
#!/usr/bin/env bash
 
# Allow setting from the environment
[ "$vcs" ] || vcs='vcs'
 
if [ -z "$1" ]; then
echo "Usage: $0 <file>"
exit 1
fi >&2
 
HEIGHT="-H240"
 
echo "Using vcs: $vcs" >&2
 
yes 01234 | head -n200 > randsource
 
BN=$(basename "$1")
echo ">> Standard <<" >&2
$vcs -n4 -c2 $HEIGHT "$1" -o "$BN-std.jpg"
echo ">> Polaroid <<" >&2
$vcs -n6 -c3 -k polaroid $HEIGHT "$1" -R randsource -o "$BN-polaroid.jpg"
echo ">> Photos <<" >&2
$vcs -n6 -c3 -k photos $HEIGHT "$1" -R randsource -o "$BN-photos.jpg"
echo ">> Filmstrip <<" >&2
$vcs -n8 -c2 -k film $HEIGTH "$1" -R randsource -o "$BN-film.jpg"
 
rm -f randsource
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.11/CHANGELOG
0,0 → 1,312
1.11: (2010-03-07)
* FEAT: Allow setting output filename. With extension will set output
format, without will inherit it.
* FEAT: Require mplayer OR ffmpeg instead of both. Having both is still
recommended for better results.
* FEAT: Safe mode, for files whose length doesn't get reported correctly.
Completely automated.
Number of tries can be increased with -Ws. Repeat to increase
further. Use -WS to do try as many times as possible.
Accuracy (stepping) can be increased with -Wp. Repeat to increase
accuracy. Decrease with -WP.
Can be deliberately disabled with -Wb to force processing of
broken files. *VCS WITH -Wb WILL FAIL ON BROKEN FILES*
* FEAT: Allow percentages in height.
* BUGFIX: Don't pass ms to mplayer. It ignores them anyway and in some
rare cases breaks the last capture (possibly due to the 5-frames
hack)
* BUGFIX: Honor detected aspect ratio if found
* BUGFIX: Try to detect files that might fail on the last capture and
trigger safe mode
* BUGFIX: Timestamps font was being ignored. As a side effect this produced
italiced timestamps in some systems.
* BUGFIX: Fixed obscure bug with safe_rename_pattern overrides
* COMPAT: Support for bash 2.05b. This will (probably) be the last version
capable of running under bash 2.
* DVD mode revamp
- Print title file size instead of disc size when possible
- Aspect ratio detection, if available
- Use of FFmpeg if available to get better information
- Mostly x-platform, only ISOs identification is a bit better in Linux
* Added FourCCs: 3IV1, 3IV2 (3ivx); s263 (H.263); mp4v, MP4V, H264
(MPEG-4 and AVC in mov/mp4), VP6F (VP6 Flash Version), AMR
Video codec renamings:
- TechSmith codec name shortened to TechSmith SCC
- Raw RGB renamed to Raw video
* Help cleanup. The default help output is much shorter, the full text
can be displayed with --fullhelp. Also print the decoder choice near
the appropriate option (-M/-F)
* Added --anonymous to help (never was in it)
* Drop requirement on seq/jot and bc, replaced by inline awk
... New requirement: Perl. It's required by POSIX anyway
* Adopt new/fixed numbering scheme
<http://p.outlyer.net/dox/vcs:devel:renumbering>
* Check ImageMagick version (must decide which is the real minimum
required)
* Non-latin fonts revamp:
- -I no longer works alone (use -Ij or -Ik instead)
- -Ik, -Ij and --nonlatin try to pick an appropriate font automatically
- -I accepts a font name or font filename like
-Ij=Kochi-Mincho-Regular or
-Ij=/usr/share/fonts/truetype/kochi/kochi-mincho.ttf
* Deprecated options:
--shoehorn: Will be removed unless someone really needs it.
--mincho: Replaced by --nonlatin
* COSMETIC:
- Default font switched to DejaVu Sans.
Font sizes reduced to accomodate the new default.
Should fall back to a sane default if it's not available
- Much tighter padding
- Smaller timestamps font by default
- Print friendlier timestamp when a capture fails
- Print program signature to console without colour
- Use main font by default in timestamps
- Heading background colour toned down
- Added colourised output when tput is not capable (i.e. FreeBSD)
- Added prefixes when colour is not available for console output
- Don't print lsdvd error channel is DVD mode
- Suppress mv errors (e.g. over VFS being unable to preserve)
* UNDOCUMENTED/DEBUG:
- Allow stopping the main loop before cleaning up (--undocumented hang)
- Identification-only mode. Might be promoted to an actual feature
(--undocumented idonly)
- Allow setting ffmpeg and mplayer path (--undocumented set_ffmpeg and
set_mplayer)
- Allow disabling either mplayer of ffmpeg (as if they weren't
installed (--undocumented disable_ffmpeg and disable_mplayer)
- Added -Wc to disable console colour, repeat to disable prefixes
* INTERNAL: assert()
* INTERNAL: Cleanup: correctness checks converted to asserts, removal
of old dead code, fixed typos in comments
* Fixed various typos and cut out lines in CHANGELOG
* Minimum ImageMagick version set to 6.3.5-7
* Better detection of requirements (e.g. disallow decoders without png
support)
* Allow overriding height, number of captures, interval, columns, and
padding
* Added -dp (--disable padding) equivalent to overriding HPAD to 0
 
 
1.0.100a: (2009-04-10) (1.10)
* FEATURE: FreeBSD (7.1-RELEASE) support
* COMPATIBILITY:
- Call bash through env
- Ensure we're using the correct getopt version
- Try to use POSIX sed options when appropriate
- Replaced incompatible sed constructs
- Use mktemp's common GNU/BSD(/POSIX?) syntax
- Use jot instead of seq if required and available
* BUGFIX: Don't fail if tput is unable to change colours
* BUGFIX: Check for requirements before anything else
* INTERNAL: Cache tput output
* FEATURE: Added -R / --randomsource. Mainly useful for debugging,
also to repeat a set of results and compare outputs on different
systems
* Corrected info message in photos mode
 
1.0.99: (2009-03-11) (1.9)
* FEATURE: Experimental support for DVDs (-V)
* FEATURE: Added JPEG 2000 output format (-j2)
* FEATURE/COSMETIC: Polaroid mode now produces a polaroid-like frame, the
older version is now renamed as simply 'photos'
New "funky" modes: newer polaroid, photos (older polaroid),
polaroidframe
* Overrideable variables: DISABLE_SHADOWS and DISABLE_TIMESTAMPS (set to 1
to disable)
* BUGFIX/COSMETIC: Re-added the missed space before filename
* BUGFIX/COSMETIC: Reworked alignment and padding
* Timestamps size is adjusted with smaller captures
* BUGFIX: Fixed polaroid/rotate bug where all images overlapped on the same
position (reported by Aleksandar Urošević, formerly unreproducible)
* Better detection of video/audio features by falling back to ffmpeg when
appropriate
 
1.0.12: (2008-04-16) (1.8)
* BUGFIX/COSMETIC: Corrected 0ms timestamps
* COSMETIC: Re-added the (disabled for long) black border after highlights
* BUGFIX/COSMETIC: Corrected the count of captures in manual-only mode (-m)
* FEATURE: Added a minimun length to use the end offset
* BUGFIX: Fixed the regression on highlights from the last version (extra
padding was being added by IM automatically)
* INTERNAL: Simplified use of IM's identify
* BUGFIX: Fixed parsing of manual timestamps including milliseconds
(when seconds didn't include the s character they were accidentally
multiplied by 10!)
 
1.0.11: (2008-04-08) (1.7)
* 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) (1.6)
* 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
get a quicker response.
 
1.0.9a: (2007-06-10) (1.5.2, -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) (1.5.1, 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) (1.5)
* 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) (1.4.1, 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) (1.4)
* 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 success 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) (1.3)
* 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) (1.2.1, Brown bag hotfix)
* BUGFIX: Don't put the full video path in the heading
 
1.0.2b: (2007-04-14) (1.2)
* 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) (1.1)
* 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) (1.0)
* 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.11
Property changes:
Added: svn:mergeinfo
Merged /video-contact-sheet/branches/1.0a:r262-263
Merged /video-contact-sheet/tags/1.11:r381
Merged /video-contact-sheet/branches/1.0.100a:r364-371
Merged /video-contact-sheet/tags/1.0.12:r352-356
Merged /video-contact-sheet/tags/1.0.11:r344-345
Merged /video-contact-sheet/tags/1.0.2b:r274
Merged /video-contact-sheet/tags/1.0.8a:r319-320
Merged /video-contact-sheet/branches/1.11:r375-379,382-383
Merged /video-contact-sheet/tags/0.99a:r261
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.12:r347-350
Merged /video-contact-sheet/branches/1.0.1a:r266-267
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.5b:r284-285
Merged /video-contact-sheet/branches/1.0.4b:r280-281
Merged /video-contact-sheet/branches/1.0.7a:r294-311
Merged /video-contact-sheet/branches/1.0.6b:r289-290
Merged /video-contact-sheet/branches/1.0.9a:r322-325
Merged /video-contact-sheet/branches/1.0.99:r358-361
Merged /video-contact-sheet/branches/1.0.8a:r315-317
/video-contact-sheet/tags/1.0.100a/Makefile
0,0 → 1,41
#!/usr/bin/make -f
# $Id$
 
VER=$(shell grep VERSION vcs|head -n1|sed 's/#.*//'|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.100a/CHANGELOG
0,0 → 1,222
1.0.100a: (2009-04-10) ("1.1.0 RC2")
* FEATURE: FreeBSD (7.1-RELEASE) support
* COMPATIBILITY:
- Call bash through env
- Ensure we're using the correct getopt version
- Try to use POSIX sed options when appropriate
- Replaced incompatible sed constructs
- Use mktemp's common GNU/BSD(/POSIX?) syntax
- Use jot instead of seq if required and available
* BUGFIX: Don't fail if tput is unable to change colours
* BUGFIX: Check for requirements before anything else
* INTERNAL: Cache tput output
* FEATURE: Added -R / --randomsource. Mainly useful for debugging,
also to repeat a set of results and compare outputs on different
systems
* Corrected info message in photos mode
 
1.0.99: (2009-03-11) ("1.1.0 RC")
* FEATURE: Experimental support for DVDs (-V)
* FEATURE: Added JPEG 2000 output format (-j2)
* FEATURE/COSMETIC: Polaroid mode now produces a polaroid-like frame, the
older version is now renamed as simply 'photos'
New "funky" modes: newer polaroid, photos (older polaroid),
polaroidframe.
* Overrideable variables: DISABLE_SHADOWS and DISABLE_TIMESTAMPS (set to 1
* BUGFIX/COSMETIC: Re-added the missed space before filename
* BUGFIX/COSMETIC: Reworked alignment and padding
* Timestamps size is adjusted with smaller captures
* BUGFIX: Fixed polaroid/rotate bug where all images overlapped on the same
position (reported by Aleksandar Urošević, formerly unreproducible)
* Better detection of video/audio features by falling back to ffmpeg when
appropriate
 
1.0.12: (2008-04-16)
* BUGFIX/COSMETIC: Corrected 0ms timestamps
* COSMETIC: Re-added the (disabled for long) black border after highlights
* BUGFIX/COSMETIC: Corrected the count of captures in manual-only mode (-m)
* FEATURE: Added a minimun length to use the end offset
* BUGFIX: Fixed the regression on highlights from the last version (extra
padding was being added my IM automatically)
* INTERNAL: Simplified use of IM's identify
* BUGFIX: Fixed parsing of manual timestamps including milliseconds
(when seconds didn't include the s character they were accidentally
multiplied by 10!)
 
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.100a/debian-package/debian/changelog
0,0 → 1,33
vcs (1.0.100a-upstream.1) experimental; urgency=low
 
* New version
 
-- Toni Corvera <outlyer@gmail.com> Fri, 10 Apr 2009 17:08:33 +0200
 
vcs (1.0.99-upstream.0) experimental; urgency=low
 
* New version.
* debian/control:
- Added lsdvd as recommendation (required for dvd support)
- Using 'Homepage:'
 
-- Toni Corvera <outlyer@gmail.com> Wed, 11 Mar 2009 22:50:25 +0100
 
vcs (1.0.12-upstream.1) experimental; urgency=low
 
* debian/control: Added missing requirement (gsfonts)
 
-- Toni Corvera <outlyer@gmail.com> Mon, 06 Oct 2008 14:26:27 +0200
 
vcs (1.0.12-upstream.0) experimental; urgency=low
 
* New version.
* Added suffix to version number.
 
-- Toni Corvera <outlyer@gmail.com> Wed, 16 Apr 2008 17:59:46 +0200
 
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.100a/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.100a/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
Homepage: http://p.outlyer.net/vcs/
 
Package: vcs
Architecture: all
Depends: bc, bash, grep, imagemagick (>= 6.0), mktemp, mplayer, ffmpeg, gsfonts
Recommends: lsdvd
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.
/video-contact-sheet/tags/1.0.100a/debian-package/debian/dirs
0,0 → 1,0
usr/bin
/video-contact-sheet/tags/1.0.100a/debian-package/debian/compat
0,0 → 1,0
5
/video-contact-sheet/tags/1.0.100a/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.100a/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.100a/vcs
0,0 → 1,2912
#!/usr/bin/env bash
#
# $Rev$ $Date$
#
# vcs
# Video Contact Sheet *NIX: Generates contact sheets (previews) of videos
#
# Copyright (C) 2007, 2008, 2009 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:
# Mainly pages I've taken snippets from or wrote code based on them; or pages
# containing reference/technical data
# 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>
# [FJ] GNU seq’s cousin on FreeBSD is... jot
# <http://www.nevdull.com/2007/09/24/gnu-seqs-cousin-on-freebsd-is-jot/>
# [FNL] Re: Replacing spaces with newlines using awk: msg#00064
# <http://osdir.com/ml/editors.sed.user/2004-07/msg00064.html>
# [FD1] File Descriptors in Bourne shell (sh,ksh,bash).
# <http://mixedvolume.blogspot.com/2004/12/file-descriptors-in-bourne-shell.html>
# [FD2] Redirection [[Bash Hackers Wiki]
# <http://bash-hackers.org/wiki/doku.php/syntax/redirection>
#
 
declare -r VERSION="1.0.100a" # ("1.1.0 RC2")
# {{{ # CHANGELOG
# History (The full changelog can be found at <http://p.outlyer.net/vcs/files/CHANGELOG>).
#
# 1.0.100a: (Focus on FreeBSD support -and hopefully better POSIX compatibility-)
# * FEATURE: FreeBSD (7.1-RELEASE) support
# * COMPATIBILITY:
# - Call bash through env
# - Ensure we're using the correct getopt version
# - Try to use POSIX sed options when appropriate
# - Replaced incompatible sed constructs
# - Use mktemp's common GNU/BSD(/POSIX?) syntax
# - Use jot instead of seq if required and available
# * BUGFIX: Don't fail if tput is unable to change colours
# * BUGFIX: Check for requirements before anything else
# * INTERNAL: Cache tput output
# * FEATURE: Added -R / --randomsource. Mainly useful for debugging,
# also to repeat a set of results and compare outputs on different
# systems
# * Corrected info message in photos mode
#
# }}} # CHANGELOG
 
set -e
 
# This might change the way some commands are used.
# Right now it's unused
#declare -i IS_GNU=1
#grep -qi gnu <<<"$OSTYPE" || IS_GNU=0
 
# {{{ # 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.
# * Better DVD support (e.g. real detection of aspect ratio)
# * Use ffmpeg's detected length if shorter than mplayer's
#
# }}} # 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
# When shadows are enabled, 5 is substracted from this value in csheet_montage
# so keep this in mind!
declare -ri HPAD=8
 
# These are used as constants but will be set from the available system
# programs
# ERESED # see choose_eresed
# SEQ # see choose_seqw
 
# }}} # End of constants
 
# {{{ # Override-able variables
# GETOPT must be correctly set or the script will fail.
# It can be set in the configuration files if it isn't in the path or
# the first getopt in the path isn't the right version.
# A check will be made and a warning with details shown if required.
declare GETOPT=getopt
# 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 bg_tstamps='#000000aa' # Background for the timestamps box
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
# If the video is less than this length, end offset won't be used at all
declare MIN_LENGTH_FOR_END_OFFSET=19m30s
# 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
declare -i DVD_MODE=0 DVD_TITLE=1
declare DVD_FILE=
declare -i multiple_input_files=0
 
# }}} # 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=
declare FFMPEG_CACHE=
# This holds the parsed values of MPLAYER_CACHE, see also the Indexes in VID
# (defined in the constants block)
declare -a VID=
 
# These variables will hold the output of tput, used
# to colourise feedback
declare prefix_err= prefix_inf= prefix_warn= suffix_fback=
 
# 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
 
# Transformations/filters
# Operations are decomposed into independent optional steps, this allows
# to add some intermediate steps (e.g. polaroid/photo mode's frames)
# 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 (currently deprecated)
# * 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, [context, [index]] )
# They're executed in order by filter_vidcap()
declare -a FILTERS_IND=( 'filt_resize' 'filt_apply_stamp' 'filt_softshadow' )
# Deprecated: 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 coherence_check for more details
declare -i DISABLE_SHADOWS=0
declare -i DISABLE_TIMESTAMPS=0
 
# Sets which function is used to obtain random numbers valid values are
# bashrand and filerand.
# Setting it manually will break it, calling with -R changes this to filerand.
# See rand() for an explanation
declare RANDFUNCTION=bashrand
 
# }}} # 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'
'MIN_LENGTH_FOR_END_OFFSET'
'DEBUG'
'DISABLE_.*'
# Note GETOPT doesn't make sense to be overridden from the command-line
'GETOPT'
)
 
# 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 work 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=$($ERESED 's/^[[:space:]]*([a-zA-Z0-9_]*)=.*/\1/'<<<"$o")
local varval=$($ERESED '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")" ]
}
 
# converts spaces to newlines in a x-platform way [[FNL]]
# stonl($1 = string)
stonl() {
# Not pretty, but seems to be standard ('\n' works in GNU
# but not in e.g. FreeBSD)
sed 's/ /\
/g' <<<"$1"
}
 
# bash version of ord() [[ORD]]
# prints the ASCII value of a character
ord() {
printf '%d' "'$1"
}
 
# Wrapper around $RANDOM, not called directly, wrapped again in rand().
# See rand() for an explanation.
bashrand() {
echo $RANDOM
}
 
# Prepares for "filerand()" calls
# File descriptor 7 is used to keep a file open, from which data is read
# and then transformed into a number.
# init_filerand($1 = filename)
init_filerand() { # [[FD1]], [[FD2]]
test -r "$1"
exec 7<"$1"
# closed in exithdlr
}
 
# Produce a (not-really-)random number from a file, not called directly wrapped
# in rand()
# Note that once the file end is reached, the random values will always
# be the same (hash_string result for an empty string)
filerand() {
local b=
# "read 5 bytes from file descriptor 7 and put them in $b"
read -n5 -u7 b
hash_string "$b"
}
 
# Produce a random number
# $RANDFUNCTION defines wich one to use (bashrand or filerand).
# Since functions using random values are most often run in subshells
# setting $RANDOM to a given seed has not the desired effect.
# filerand() is used to that effect; it keeps a file open from which bytes
# are read and not-so-random values generated; since file descriptors are
# inherited, subshells will "advance" the random sequence.
# Argument -R enables the filerand() function
rand() {
$RANDFUNCTION
}
 
# produces a numeric value from a string
hash_string() {
local HASH_LIMIT=65536
local v="$1"
local -i hv=15031
local c=
if [ "$v" ]; then # seqw 0 0 would be catastrophic if SEQ==jot
for i in $(seqw 0 $(( ${#v}-1 ))); do
c=$( ord ${v:$i:1} )
hv=$(( ( ( $hv << 1 ) + $c ) % $HASH_LIMIT ))
done
fi
echo $hv
}
 
# 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() {
trace $FUNCNAME $@
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=$($ERESED 's/([0-9]+)h/ ( \1 * 3600 ) + /g' <<<$t)
t=$($ERESED 's/([0-9]+)m/ ( \1 * 60 ) + /g' <<<$t)
t=$(sed 's/s/ + /g' <<<$t)
t=$($ERESED 's/([0-9])\./\1 + ./g' <<<"$t") # seconds followed by ms, with no "S"
t=$($ERESED 's/\.\.+/./g'<<<$t)
t=$($ERESED 's/\.([0-9]+)/0.\1 + /g' <<<$t)
t=$($ERESED '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 -format '%w' "$1"
}
 
# Get Image Height
# imh($1 = file)
imh() {
identify -format '%h' "$1"
}
 
# 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 ms
if grep -q '\.' <<<"$t" ; then
# Have ms
s=$(cut -d'.' -f1 <<<$t)
ms=$(cut -d'.' -f2 <<<$t)
else
s=$t
ms=0
fi
 
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
$ERESED 's/\.([0-9][0-9]).*/.\1/'<<<$R
}
 
# Prints a given size in human friendly form
get_pretty_size() {
local bytes=$1
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
}
 
# 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_file_size($1 = file)
get_pretty_file_size() {
local f="$1"
local bytes=$(get_file_size "$f")
 
get_pretty_size "$bytes"
}
 
# 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=$($ERESED 's/.*\.(.*)/\1/' <<<$to)
# Input extension
local iext=$($ERESED '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"
}
 
# Gets the size of a block device
get_blockdev_size() {
# This is something I've never done so I'm still looking for the right
# way to do it portably.
# Alternatives:
# * fdisk -s (no need for privileged access, read-only)
# Prints the number of blocks (only in GNU's version?, FreeBSD's
# doesn't, so probably POSIX in general doesn't either).
# In Linux blocks are always 1024 AFAICT, but I'm not sure about
# other unices (the -in disk- block size for DVDs is 2048).
# * hal
# hal-find-by-property --key block.device --string <(REAL)DEVICE>
# hal-get-property --udi <DEVICEID> --key volume.disc.capacity
# Gets byte size but HAL is far from standard (only Linux
# and FreeBSD have it AFAIK. DBUS, on which it relies, wasn't
# enabled byb default on my FreeBSD install).
# FreeBSD has no block devices either.
# * sysfs
# cat /sys/block/<(KERNEL)DEVICE>/size
# Size is given in sectors (512 blocks). Linux only. *BSD has
# sysctl of which I've no clue.
local dev="$1"
# Only GNU systems with block devices are compatible with the current code
if [ ! -b "$1" ] || grep -q gnu <<<"$OSTYPE" ; then
echo "?"
return
fi
 
local numblocks=$(/sbin/fdisk -s "$dev" 2>"$stderr")
# FIXME: When fdisk is replaced by a better alternative this should go away
if is_number "$numblocks" ; then
get_pretty_size $(( 1024 * $numblocks ))
else
echo "?"
fi
}
 
# Tests the presence of all required programs
# test_programs()
test_programs() {
local retval=0 last=0
for prog in mplayer convert montage identify bc \
ffmpeg mktemp sed grep egrep cut $SEQ ; do
if ! type -pf "$prog" ; then
error "Required program $prog not found!"
let 'retval++'
fi >/dev/null
done
# TODO: [x2]
 
return $retval
}
 
# Test wether $GETOP is a compatible version; try to choose an alternate if
# possible
choose_getopt() {
if ! type -pf $GETOPT ; then
# getopt not in path
error "Required program getopt not found!"
return $EX_UNAVAILABLE
fi >/dev/null
local goe= gor=0
# Try getopt. If there's more than one in the path, try all of them
for goe in $(type -paf $GETOPT) ; do
"$goe" -T || gor=$?
if [ $gor -eq 4 ]; then
# Correct getopt found
GETOPT="$goe"
break;
fi
done >/dev/null
if [ $gor -ne 4 ]; then
error "No compatible version of getopt in path, can't continue."
error " For details on how to correct this problems, see <http://p.outlyer.net/vcs#getopt>"
return $EX_UNAVAILABLE
fi
return 0
}
 
# Set the correct argument to pass to sed
# The argument to enable extended regular expressions is different in GNU (-r)
# and POSIX (-E), try to detect it
choose_eresed() {
if [ "a" == "$(sed -r 's/A/a/' 2>/dev/null<<<'A')" ]; then
ERESED='sed -r'
elif [ "a" == "$(sed -E 's/A/a/' 2>/dev/null<<<'A')" ]; then
ERESED='sed -E'
else
error "The version of sed in the system is not supported.
Please, contact the author"
return $EX_SOFTWARE
fi
}
 
# Choose seq or jot, fail if none is present
# The actual program is wrapped in seqw()
choose_seqw() {
if type -pf seq ; then
SEQ='seq'
elif type -pf jot ; then
SEQ='jot'
else
error "Either seq or jot are required"
return $EX_UNAVAILABLE
fi >/dev/null
}
 
# 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() {
# I don't think that's really required anyway
if [ "$RANDFUNCTION" == "filerand" ]; then
7<&- # Close FD 7
fi
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
[ $plain_messages -eq 0 ] && echo -n $prefix_err
# 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$suffix_fback"
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
[ $plain_messages -eq 0 ] && echo -n $prefix_warn
echo "$1$suffix_fback"
fi >&2
}
#
# Print an informational message
# inf($1 = text)
inf() {
if [ $verbosity -ge $V_INFO ]; then
[ $plain_messages -eq 0 ] && echo -n $prefix_inf
echo "$1$suffix_fback"
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
}
 
#
# Tests if the filter chain contains the provided filter
# has_filter($1 = filtername)
has_filter() {
local filter= ref=$1
for filter in ${FILTERS_IND[@]} ; do
[ "$filter" == "$ref" ] || continue
return 0
done
return 1
}
 
#
# Initialises the variables affecting coloured feedback
init_feedback() {
# If tput isn't found simply ignore tput commands
# (no colour support)
if ! type -pf tput >/dev/null ; then
# XXX: Are nested functions supported by older versions?
tput() { cat >/dev/null <<<"$1"; }
elif ! tput bold || # If tput can't tinker with the color, no need to continue
! tput setaf 0 >/dev/null ||
! tput sgr0 ;
then
prefix_err= prefix_inf= prefix_warn=
suffix_fback=
else # tput doesn't fail to change colors
prefix_err=$(tput bold; tput setaf 1)
prefix_warn=$(tput bold; tput setaf 3)
prefix_inf=$(tput bold; tput setaf 2)
suffix_fback=$(tput sgr0)
fi
}
 
#
# seq wrapper/replacement
# seq($1 = from, $2 = to)
seqw() {
if [ "$SEQ" == "seq" ]; then
seq $1 $2
elif [ "$SEQ" == "jot" ]; then
jot $(( 1 + $2 - $1 )) $1
else
error "'$SEQ' is not supported, please change the value of \$SEQ"
return $EX_SOFTWARE
fi
}
 
# }}} # 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.
# Passing a full path template is more x-platform than using
# -t / -p
if [ -d /dev/shm ] && [ -w /dev/shm ]; then
VCSTEMPDIR=$(mktemp -d /dev/shm/vcs.XXXXXX)
else
[ "$TMPDIR" ] || TMPDIR="/tmp"
VCSTEMPDIR=$(env TMPDIR="$TMPDIR" mktemp -d "$TMPDIR/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=$(env TMPDIR="$VCSTEMPDIR" mktemp "$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 + ( $(rand) % $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 $(( $(rand) + $(rand) + ($(rand) % 1) ))
}
randcolour() {
echo "rgb($(randccomp),$(randccomp),$(randccomp))"
}
fi
 
local nfonts=$(( $(convert -list type | wc -l) - 5 ))
randfont() {
lineno=$(( 5 + ( $(rand) % $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 "$runlen" -lt $(get_interval "$MIN_LENGTH_FOR_END_OFFSET") ; then
# Min length to use end offset not met, it won't be used
inf "End offset won't be used, video too short."
eo=0
elif 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 for the video, 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])
{
if [ $DVD_MODE -eq 1 ]; then
mplayer -sws 9 -ao null -benchmark -vo "png:z=0" -quiet \
-frames 5 -ss $stamp $shoehorned -dvd-device "$DVD_FILE" \
"dvd://$DVD_TITLE"
else
mplayer -sws 9 -ao null -benchmark -vo "png:z=0" -quiet \
-frames 5 -ss $stamp $shoehorned "$f"
fi
} >"$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, $5 = context, $6 = index[1..])
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" "$5" "$6" ) -flatten "
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, $5 = context, $6 = index)
filt_apply_stamp() {
trace $FUNCNAME $@
local filename=$1 timestamp=$2 width=$3 height=$4 context=$5 index=$6
 
local pts=$pts_tstamps
if [ $height -lt 200 ]; then
pts=$(( $pts_tstamps / 3 ))
elif [ $height -lt 400 ]; then
pts=$(( $pts_tstamps * 2 / 3 ))
fi
# If the size is too small they won't be readable at all
if [ $pts -le 8 ]; then
pts=8
if [ $index -eq 1 ] && [ $context -ne $CTX_EXT ]; then
warn "Beware, using very small timestamps to accomodate smaller captures,\
you might prefer using -dt to disable them"
fi
fi
# The last -gravity None is used to "forget" the previous gravity (otherwise it would
# affect stuff like the polaroid frames)
echo -n " \( -box '$bg_tstamps' -fill '$fg_tstamps' -pointsize '$pts' "
echo -n " -gravity '$grav_timestamp' -stroke none -strokewidth 3 -annotate +5+5 "
echo " ' $(pretty_stamp $stamp) ' \) -flatten -gravity None "
}
 
# Apply a framed photo-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 ) ) ))
# The border is relative to the input size (since 1.0.99), with a maximum of 6
# Should probably be bigger for really big frames
# Note that only images below 21600px (e.g. 160x120) go below a 6px border
local border=$(( ($3*$4) / 3600 ))
[ $border -lt 7 ] || border=6
echo -n "-bordercolor white -border $border -bordercolor grey60 -border 1 "
}
 
filt_softshadow() {
# Before this was a filter, there was the global (montage) softshadow (50x2+10+10) and the
# photoframe inline softshadow 60x4+4+4
echo -n "\( -background black +clone -shadow 50x2+4+4 -background none \) +swap -flatten -trim +repage "
}
 
 
# Apply a polaroid-like border effect
# Based on filt_photoframe(), with a bigger lower border
# filt_polaroid($1 = filename, $2 = timestamp, $3 = width, $4 = height)
filt_polaroid() {
trace $FUNCNAME $@
# local file="$1" ts=$2 w=$3 h=$4
local border=$(( ($3*$4) / 3600 )) # Read filt_photoframe for details
[ $border -lt 7 ] || border=6
echo -n "-bordercolor white -mattecolor white -frame ${border}x${border} "
# FIXME: This is rather ugly (double-flipping) there's sure a better way
echo -n "\( -flip -splice 0x$(( $border*5 )) \) "
echo "-flip -bordercolor grey60 -border 1 +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=$(( ($(rand) % 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= vpad= splice=
 
# The shadows already add a good amount of padding
if has_filter filt_softshadow ; then
hpad=$(( $HPAD-5 ))
vpad=0
splice=5x10
else
hpad=$HPAD
vpad=4
splice=0x8
fi
 
montage -background Transparent "$@" -geometry +$hpad+$vpad -tile "$cols"x "$output"
# With the shadows moved to a filter, there's not enough spacing to header
convert "$output" -background Transparent -splice $splice "$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 $(seqw 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 # Integrated in the row creation since 1.0.99
 
# Step through vidcaps (col=[0..cols-1])
for col in $(seqw 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
cmdopts="$cmdopts '$1' -geometry +${accoffset}+0 -composite "
 
offset=$(( $minoffset + ( $(rand) % $maxoffset ) ))
let 'accoffset=accoffset + w - offset'
shift
done
inf "Composing overlapped row $row/$numrows..."
eval convert -size ${canvasw}x${canvash} xc:transparent -geometry +0+0 "$cmdopts" -trim +repage "'$rowfile'" >&2
done
 
inf "Merging overlapped rows..."
output=$(new_temp_file .png)
 
cmdopts=
accoffset=0
local h
for row in "${rowfiles[@]}" ; do
w=$(imw "$row")
h=$(imh "$row")
minoffset=$(( $h / 8 ))
maxoffset=$(( $h / 4 ))
offset=$(( $minoffset + ( $(rand) % $maxoffset ) ))
# The row is also offset horizontally
cmdopts="$cmdopts '$row' -geometry +$(( $(rand) % $maxoffset ))+$accoffset -composite "
let 'accoffset=accoffset + h - offset'
done
# After the trim the image will be touching the outer borders and the heading and footer,
# older versions (prior to 1.0.99) used -splice 0x10 to correct the heading spacing, 1.0.99
# onwards uses -frame to add spacing in all borders + splice to add a bit more space on the
# upper border. Note splice uses the background colour while frame uses the matte colour
eval convert -size ${canvasw}x$(( $canvash * $cols )) xc:transparent -geometry +0+0 \
$cmdopts -trim +repage -bordercolor Transparent -background Transparent -mattecolor Transparent \
-frame 5x5 -splice 0x5 "$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 I replace spaces by newlines
local s=$1
stonl "$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
if [ $DVD_MODE -eq 0 ]; then
MPLAYER_CACHE=$(mplayer -benchmark -ao null -vo null -identify -frames 0 \
-quiet "$f" 2>/dev/null | grep ^ID)
# Used as fallback. Introduced in 1.0.99 so expect it to fail :P
FFMPEG_CACHE=$(ffmpeg -i "$f" -dframes 0 -vframes 0 /dev/null 2>&1 | grep Stream)
else
MPLAYER_CACHE=$(mplayer -benchmark -ao null -vo null -identify -frames 0 \
-quiet -dvd-device $DVD_FILE dvd://$DVD_TITLE \
2>/dev/null | grep ^ID)
fi
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)
if [ $DVD_MODE -eq 0 ]; then
VID[$LEN]=$(grep ID_LENGTH <<<"$MPLAYER_CACHE"| cut -d'=' -f2)
else
VID[$LEN]=$(grep ID_DVD_TITLE_${DVD_TITLE}_LENGTH <<<"$MPLAYER_CACHE"| cut -d'=' -f2)
fi
# 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
 
# Fallback for values known to fail often
if [ "$FFMPEG_CACHE" ]; then
# FPS=1000.00 happens often for WMV
if [ "${VID[$FPS]}" == "1000.00" ]; then
local fps2=$(grep Stream <<<"$FFMPEG_CACHE" | grep Video | head -1 | \
egrep -o ', [0-9]+\.[0-9]+ ' | egrep -o '[0-9]+.*[0-9]')
if is_float "$fps2" ; then
VID[$FPS]=$fps2
fi
unset fps2
fi
# Number of channels 0 happens for WMA in non-x86
# Mplayer seems to default to 2 for >2, so ffmpeg might be a better default option
# if [ "${VID[$CHANS]}" ] && ( ! is_number "${VID[$CHANS]}" || [ ${VID[$CHANS]} -eq 0 ] ) ; then
local ch2=$(grep Stream <<<"$FFMPEG_CACHE" | grep Audio | head -1 | cut -d, -f3 | sed 's/^ //')
if [ "$ch2" ]; then
case $ch2 in
mono) VID[$CHANS]=1 ;;
stereo) VID[$CHANS]=2 ;;
5.1) VID[$CHANS]=6 ;;
*) ;;
esac
fi
# fi
fi
 
# Check sanity of the most important values
is_number "${VID[$W]}" && is_number "${VID[$H]}" && is_float "${VID[$LEN]}"
}
 
# Checks if the provided arguments make sense and are allowed to be used
# together
coherence_check() {
# 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)"
return $EX_USAGE
fi
 
# Currently it's not allowed to use dvd mode with more than one input
# "file" (in this mode, input files are actually dvd titles of the file
# provided in -V)
if [ $DVD_MODE -eq 1 ] ; then
if [ $multiple_input_files -eq 1 ]; then
error "Only an input file is allowed in DVD mode"
return $EX_UNAVAILABLE
fi
 
# DVD Mode only works with mplayer, the decoder is changed when
# the DVD mode option is found, so if it's ffmpeg at this point,
# it's by user request (i.e. -F after -V)
if [ $decoder -ne $DEC_MPLAYER ]; then
warn "DVD mode requires the use of mplayer, falling back to it"
decoder=$DEC_MPLAYER
fi
fi
 
local filter=
if [ $DISABLE_TIMESTAMPS -eq 0 ] &&
local filts=( )
has_filter filt_polaroid && has_filter filt_apply_stamp ; then
 
for filter in ${FILTERS_IND[@]} ; do
if [ "$filter" == "filt_polaroid" ]; then
filts+=( $filter )
filts+=( filt_apply_stamp )
elif [ "$filter" == "filt_apply_stamp" ]; then
continue;
else
filts+=( $filter )
fi
done
FILTERS_IND=( ${filts[*]} )
unset filts
fi
# The shoftshadow and randrot filters must be in the correct place
# or they will affect the image incorrectly.
# Additionally the default filters can be disabled from the command
# line (with --disable), they're removed from the filter chain here
local filts=( ) end_filts=( )
for filter in ${FILTERS_IND[@]} ; do
case "$filter" in
filt_softshadow)
# Note the newer soft shadows code (1.0.99 onwards) behaves slightly
# differently. On previous versions disabling shadows only affected
# the montage shadow (but e.g. the polaroid mode preserved them),
# this is no longer true
if [ $DISABLE_SHADOWS -ne 1 ]; then
end_filts[100]="filt_softshadow"
fi
;;
filt_apply_stamp)
if [ $DISABLE_TIMESTAMPS -ne 1 ]; then
filts+=( "$filter" )
fi
;;
filt_randrot) end_filts[200]="filt_randrot" ;;
*) filts+=( "$filter" ) ;;
esac
done
FILTERS_IND=( ${filts[*]} ${end_filts[*]} )
}
 
# Main function.
# Creates the contact sheet.
# process($1 = file)
process() {
trace $FUNCNAME $@
local f=$1
 
local numcols=
 
# XXX: Some of this should be moved to coherence_check
if [ $DVD_MODE -eq 1 ]; then # DVD Mode
f="$DVD_FILE"
local dvdn="$f"
if [ -b "$dvdn" ]; then
dvdn="DVD"
elif [ -c "$dvdn" ]; then
if grep -q bsd <<<"$OSTYPE"; then
inf "Warning: DVD support is even more experimental in *BSD"
else
warn "DVD device is a character device"
fi
dvdn="DVD"
elif [ ! -f "$dvdn" ]; then
error "File \"$dvdn\" doesn't exist"
return $EX_NOINPUT
fi
inf "Processing $dvdn..."
unset dvdn
if ! is_number "$1" ; then
error "DVD Title must be a number (e.g.: \$ vcs -V /dev/dvd 1)"
exit $EX_USAGE
fi
DVD_TITLE=$1
if [ $DVD_TITLE -eq 0 ]; then
local dt="$(lsdvd "$DVD_FILE" | grep 'Longest track:' | \
cut -d' ' -f3- | sed 's/^0*//')"
if ! is_number "$dt" ; then
error "Failed to autodetect longest DVD title"
exit $EX_INTERNAL
fi
DVD_TITLE=$dt
unset dt
inf "Using DVD Title #$DVD_TITLE"
fi
else
if [ ! -f "$f" ]; then
error "File \"$f\" doesn't exist"
return $EX_NOINPUT
fi
 
inf "Processing $f..."
fi
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 $($ERESED '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
# Compute the stamps (if in auto mode)...
if [ $manual_mode -eq 1 ]; then
# Note TIMECODES must be set as an array to get the correct count in
# manual mode; in automatic mode it will be set correctly inside
# compute_timecodes()
TIMECODES=( ${initial_stamps[@]} )
else
TIMECODES=${initial_stamps[@]}
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 $VIDCAPFILE 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 $CTX_HL $n || {
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 $CTX_STD $n || 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 $CTX_EXT $n || 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
 
 
local csw=$(imw "$output") exw= hlw=
local width=$csw
if [ "$HLTIMECODES" ] || [ "$extended_factor" != "0" ]; then
inf "Merging contact sheets..."
if [ "$HLTIMECODES" ]; then
local hlw=$(imw "$hlfile")
if [ $hlw -gt $width ]; then width=$hlw ; fi
fi
if [ "$extended_factor" != "0" ]; then
local exw=$(imw $extoutput)
if [ $exw -gt $width ]; then width=$exw ; fi
fi
fi
if [ $csw -lt $width ]; then
local csh=$(imh "$output")
# Expand the standard set to the maximum width of the sets by padding both sides
# For some reason the more obvious (to me) convert command-lines lose
# the transparency
convert \( -size $(( ($width - $csw) / 2 ))x$csh xc:transparent \) "$output" \
\( -size $(( ($width - $csw) / 2 ))x$csh xc:transparent \) +append "$output"
unset csh
fi
 
# If there were highlights then mix them in
if [ "$HLTIMECODES" ]; then
# For some reason adding the background also adds padding with:
# convert \( -background LightGoldenRod "$hlfile" -flatten \) \
# \( "$output" \) -append "$output"
# replacing it with a "-composite" operation apparently works
# Expand the highlights to the correct size by padding
local hlh=$(imh "$hlfile")
if [ $hlw -lt $width ]; then
convert \( -size $(( ($width - $hlw) / 2 ))x$hlh xc:transparent \) "$hlfile" \
\( -size $(( ($width - $hlw) / 2 ))x$hlh xc:transparent \) +append "$hlfile"
fi
convert \( -size ${width}x${hlh} xc:LightGoldenRod "$hlfile" -composite \) \
\( -size ${width}x1 xc:black \) \
"$output" -append "$output"
unset hlh
fi
# Extended captures
if [ "$extended_factor" != 0 ]; then
# Already set local exw=$(imw "$extoutput")
local exh=$(imh "$extoutput")
if [ $exw -lt $width ]; then
# Expand the extended set to be the correct size
convert \( -size $(( ($width - $exw) / 2 ))x$exh xc:transparent \) "$extoutput" \
\( -size $(( ($width - $exw) / 2 ))x$exh xc:transparent \) +append "$extoutput"
fi
convert "$output" -background Transparent "$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=$(imw "$output")
# 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.
local filename_label="Filename"
local filesize_label="File size"
local filename_value=
local filesize_value=
if [ $DVD_MODE -eq 1 ]; then
local is_dev=0
( test -b "$DVD_FILE" || test -c "$DVD_FILE" ) && is_dev=1
local dvd_label=$(lsdvd "$DVD_FILE" | grep -o 'Disc Title: .*' | cut -d' ' -f3-)
# lsdvd is guaranteed to be installed if DVD mode is enabled
if [ $is_dev -eq 1 ]; then # This is a real DVD, not an iso
filename_label="Disc label"
filename_value="$dvd_label"
filesize_label="Disc size"
filesize_value="$(get_blockdev_size "$DVD_FILE")"
else
filename_value="$(basename "$DVD_FILE") $filename_value (DVD Label: $dvd_label)"
filesize_value="$(get_pretty_file_size "$f")"
fi
else
filename_value="$(basename "$f")"
filesize_value="$(get_pretty_file_size "$f")"
fi
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_label: " \
-font "$fn_font" label:"$filename_value" +append \
\) \
-font "$font_heading" \
label:"$filesize_label: $filesize_value" \
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(). Running with -D triggers this.
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=(
# TODO: UNIX vs GNU
#"stonl ..."
 
"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"
 
"get_interval 2h 7200 #Hours parsing"
"get_interval 2m 120 #Minutes parsing"
"get_interval 30s 30 #Seconds parsing"
"get_interval .30 .30 #Milliseconds parsing"
# Extended syntax
"get_interval 30m30m1h 7200 #Repeated minutes parsing"
)
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=$($ERESED "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=$($ERESED "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-2009 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.
-V|--dvd <file.iso|dvd_device>
DVD Mode, use file.iso as DVD. In this mode the
<file> argument must point to the title number, e.g.:
$ vcs -V somedvd.iso 1
Passing title 0 will use the default (longest) title.
$ vcs -V /dev/dvd 0
Implies -A (auto aspect ratio)
-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).
-j2|--jpeg 2 Output in jpeg 2000
-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.
"polaroidframe": Use '-kL' or '--funky polaroidframe'
Adds a polaroid picture-like white frame to each
image.
"photos": Use '-kc' or '--funky photos'
Combination of rotate, photoframe and overlap.
Same as -kp -kr -ko.
"polaroid": Use '-kp' or '--funky polaroid'
Combination of rotate, polaroidframe and overlap.
Same as -kL -kr -ko.
"film": Use '-ki' or '--funky film'
Imitates filmstrip look.
"random": Use '-kx' or '--funky random'
Randomizes colours and fonts.
-R <file>
--randomsource <file> Use the provided file as a source for random "values":
they won't be random anymore, so two runs with the same
source and same arguments will produce the same output
in modes which use using randomisation (e.g. the
"photos" and "polaroid" modes).
 
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 ####
 
# Important to do this before any message can be thrown
init_feedback
 
# Adjust sed for POSIX/GNU compatibility
choose_eresed
# Adjust seq for POSIX/GNU compatibility
choose_seqw
# Ensure $GETOPT is Linux-style getopt
choose_getopt
 
# Execute exithdlr on exit
trap exithdlr EXIT
 
show_vcs_info
 
# Test requirements. Important, must check before looking at the
# command line (since getopt is used for the task)
test_programs || exit $EX_UNAVAILABLE
 
# The command-line overrides any configuration. And the configuration
# is able to change the program in charge of parsing options ($GETOPT)
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:j::hFMH:c:ma:l:De::U::qAO:I::k:W:E:d:V:R: \
--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:,dvd:,randomsource:,undocumented:" \
-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)
if [ "$2" ]; then # Arg is optional, 2 is for JPEG 2000
# 2000 is also accepted
if [ "$2" != "2" ] && [ "$2" != "2000" ]; then
error "Use -j for jpeg output or -j2 for JPEG 2000 output. Got '-j$2'."
exit $EX_USAGE
fi
output_format=jp2
else
output_format=jpg
fi
shift
;;
-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
if grep -q 'GETOPT=' <<<"$2" ; then
# If we're here, getopt has already been found and works, so it makes no
# sense to override it; on the other hand, if it hasn't been correctly
# set/detected we won't reach here
warn "GETOPT can't be overridden from the command line."
else
override "$2" "command line"
fi
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 "$2" in # Note older versions (<1.0.99) were case-insensitive
p|polaroid) # Same as overlap + rotate + polaroid
inf "Changed to polaroid funky mode."
FILTERS_IND+=( 'filt_polaroid' 'filt_randrot' )
CSHEET_DELEGATE='csheet_overlap'
# XXX: The newer version has a lot less flexibility with these many
# hardcoded values...
grav_timestamp=South
fg_tstamps=Black
bg_tstamps=Transparent
pts_tstamps=$(( $pts_tstamps * 3 / 2 ))
;;
c|photos) # Same as overlap + rotate + photoframe, this is the older polaroid
inf "Changed to photos funky mode."
FILTERS_IND+=( 'filt_photoframe' 'filt_randrot' )
CSHEET_DELEGATE='csheet_overlap'
# The timestamp must change location to be visible most of the time
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' )
;;
L|polaroidframe) # White polaroid frame
FILTERS_IND+=( 'filt_polaroid ')
grav_timestamp=South
fg_tstamps=Black
bg_tstamps=Transparent
pts_tstamps=$(( $pts_tstamps * 3 / 2 ))
;;
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
;;
-R|--randomsource)
if [ ! -r "$2" ]; then
error "Random source file '$2' can't be read"
exit $EX_USAGE
fi
init_filerand "$2"
inf "Using '$2' as source of semi-random values"
RANDFUNCTION=filerand
shift
;;
-d|--disable) # Disable default features
case $(tolower "$2") in
# timestamp (with no final s) is undocumented but will stay
t|timestamps|timestamp)
if [ $DISABLE_TIMESTAMPS -eq 0 ]; then
inf "Timestamps disabled."
# They'll be removed from the filter chain in coherence_check
DISABLE_TIMESTAMPS=1
fi
;;
s|shadows|shadow)
if [ $DISABLE_SHADOWS -eq 0 ]; then
inf "Shadows disabled."
# They will be removed from the filter chain in coherence_check
DISABLE_SHADOWS=1
fi
;;
*)
error "Requested disabling unknown feature. Got '$2'."
exit $EX_USAGE
;;
esac
shift
;;
-V|--dvd)
# DVD Mode requires lsdvd
if ! type -pf lsdvd >/dev/null ; then
error "DVD Support requires the lsdvd program"
exit $EX_UNAVAILABLE
fi
DVD_MODE=1
DVD_FILE="$2"
decoder=$DEC_MPLAYER
aspect_ratio=-1
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
;;
--undocumented)
# This is a container for, of course, undocumented functions
# These are used for testing/debugging purposes. Might (and will)
# change between versions, break easily and do no safety checks.
# In short, don't look at them unless told to do so :P
case "$2" in
# AWK was used for a little while in a WiP version
#set_awk=*) AWK="$(cut -d'=' -f2<<<"$2")" ; warn "[U] AWK=$AWK" ;;
*) false ;;
esac
shift
;;
-D) # Repeat to just test consistency
if [ $DEBUGGED -gt 0 ]; then exit ; fi
DEBUG=1
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
elif [ "$2" ]; then
multiple_input_files=1
fi
# }}} # Command line parsing
 
# The coherence check ensures the processed options are
# not incoherent/incompatible with the input files or with
# other given options
coherence_check || {
exit $?
}
 
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.100a
Property changes:
Added: svn:mergeinfo
Merged /video-contact-sheet/branches/1.0a:r262-263
Merged /video-contact-sheet/branches/1.0.100a:r364-371
Merged /video-contact-sheet/tags/1.0.12:r352-356
Merged /video-contact-sheet/tags/1.0.11:r344-345
Merged /video-contact-sheet/tags/1.0.2b:r274
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.12:r347-350
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.7a:r294-311
Merged /video-contact-sheet/branches/1.0.6b:r289-290
Merged /video-contact-sheet/branches/1.0.9a:r322-325
Merged /video-contact-sheet/branches/1.0.99:r358-361
Merged /video-contact-sheet/branches/1.0.8a:r315-317
/video-contact-sheet/tags/1.0.99/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.99/debian-package/debian/changelog
0,0 → 1,27
vcs (1.0.99-upstream.0) experimental; urgency=low
 
* New version.
* debian/control:
- Added lsdvd as recommendation (required for dvd support)
- Using 'Homepage:'
 
-- Toni Corvera <outlyer@gmail.com> Wed, 11 Mar 2009 22:50:25 +0100
 
vcs (1.0.12-upstream.1) experimental; urgency=low
 
* debian/control: Added missing requirement (gsfonts)
 
-- Toni Corvera <outlyer@gmail.com> Mon, 06 Oct 2008 14:26:27 +0200
 
vcs (1.0.12-upstream.0) experimental; urgency=low
 
* New version.
* Added suffix to version number.
 
-- Toni Corvera <outlyer@gmail.com> Wed, 16 Apr 2008 17:59:46 +0200
 
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.99/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.99/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
Homepage: http://p.outlyer.net/vcs/
 
Package: vcs
Architecture: all
Depends: bc, bash, grep, imagemagick (>= 6.0), mktemp, mplayer, ffmpeg, gsfonts
Recommends: lsdvd
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.
/video-contact-sheet/tags/1.0.99/debian-package/debian/dirs
0,0 → 1,0
usr/bin
/video-contact-sheet/tags/1.0.99/debian-package/debian/compat
0,0 → 1,0
5
/video-contact-sheet/tags/1.0.99/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.99/CHANGELOG
0,0 → 1,205
1.0.99: (2009-03-11) ("1.1.0 RC")
* FEATURE: Experimental support for DVDs (-V)
* FEATURE: Added JPEG 2000 output format (-j2)
* FEATURE/COSMETIC: Polaroid mode now produces a polaroid-like frame, the
older version is now renamed as simply 'photos'
New "funky" modes: newer polaroid, photos (older polaroid),
polaroidframe.
* Overrideable variables: DISABLE_SHADOWS and DISABLE_TIMESTAMPS (set to 1
* BUGFIX/COSMETIC: Re-added the missed space before filename
* BUGFIX/COSMETIC: Reworked alignment and padding
* Timestamps size is adjusted with smaller captures
* BUGFIX: Fixed polaroid/rotate bug where all images overlapped on the same
position (reported by Aleksandar Urošević, formerly unreproducible)
* Better detection of video/audio features by falling back to ffmpeg when
appropriate
 
1.0.12: (2008-04-16)
* BUGFIX/COSMETIC: Corrected 0ms timestamps
* COSMETIC: Re-added the (disabled for long) black border after highlights
* BUGFIX/COSMETIC: Corrected the count of captures in manual-only mode (-m)
* FEATURE: Added a minimun length to use the end offset
* BUGFIX: Fixed the regression on highlights from the last version (extra
padding was being added my IM automatically)
* INTERNAL: Simplified use of IM's identify
* BUGFIX: Fixed parsing of manual timestamps including milliseconds
(when seconds didn't include the s character they were accidentally
multiplied by 10!)
 
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.99/vcs
0,0 → 1,2672
#!/bin/bash
#
# $Rev$ $Date$
#
# vcs
# Video Contact Sheet *NIX: Generates contact sheets (previews) of videos
#
# Copyright (C) 2007, 2008, 2009 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:
# Mainly pages I've taken snippets from or wrote code based on them; or pages
# containing reference/technical data
# 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.99" # ("1.1.0 RC")
# {{{ # CHANGELOG
# History (The full changelog can be found at <http://p.outlyer.net/vcs/files/CHANGELOG>).
#
# 1.0.99: (2009-3-11)
# * FEATURE: Experimental support for DVDs (-V)
# * FEATURE: Added JPEG 2000 output format (-j2)
# * FEATURE/COSMETIC: Polaroid mode now produces a polaroid-like frame, the
# older version is now renamed as simply 'photos'
# New "funky" modes: newer polaroid, photos (older polaroid),
# polaroidframe.
# * Overrideable variables: DISABLE_SHADOWS and DISABLE_TIMESTAMPS (set to
# 1 to disable)
# * BUGFIX/COSMETIC: Re-added the missed space before filename
# * BUGFIX/COSMETIC: Reworked alignment and padding
# * Timestamps size is adjusted with smaller captures
# * BUGFIX: Fixed polaroid/rotate bug where all images overlapped on the
# same position (reported by Aleksandar Urošević, formerly
# unreproducible)
# * Better detection of video/audio features by falling back to ffmpeg when
# appropriate
# }}} # 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.
# * Better DVD support (e.g. real detection of aspect ratio)
# * Use ffmpeg's detected length if shorter than mplayer's
#
 
# }}} # 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
# When shadows are enabled, 5 is substracted from this value in csheet_montage
# so keep this in mind!
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 bg_tstamps='#000000aa' # Background for the timestamps box
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
# If the video is less than this length, end offset won't be used at all
declare MIN_LENGTH_FOR_END_OFFSET=19m30s
# 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
declare -i DVD_MODE=0 DVD_TITLE=1
declare DVD_FILE=
declare -i multiple_input_files=0
 
# }}} # 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=
declare FFMPEG_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
 
# Transformations/filters
# Operations are decomposed into independent optional steps, this allows
# to add some intermediate steps (e.g. polaroid/photo mode's frames)
# 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 (currently deprecated)
# * 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, [context, [index]] )
# They're executed in order by filter_vidcap()
declare -a FILTERS_IND=( 'filt_resize' 'filt_apply_stamp' 'filt_softshadow' )
# Deprecated: 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 coherence_check for more details
declare -i DISABLE_SHADOWS=0
declare -i DISABLE_TIMESTAMPS=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'
'MIN_LENGTH_FOR_END_OFFSET'
'DEBUG'
'DISABLE_.*'
)
 
# 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() {
trace $FUNCNAME $@
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/([0-9])\./\1 + ./g' <<<"$t") # seconds followed by ms, with no "S"
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 -format '%w' "$1"
}
 
# Get Image Height
# imh($1 = file)
imh() {
identify -format '%h' "$1"
}
 
# 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 ms
if grep -q '\.' <<<"$t" ; then
# Have ms
s=$(cut -d'.' -f1 <<<$t)
ms=$(cut -d'.' -f2 <<<$t)
else
s=$t
ms=0
fi
 
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 a given size in human friendly form
get_pretty_size() {
local bytes=$1
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
}
 
# 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_file_size($1 = file)
get_pretty_file_size() {
local f="$1"
local bytes=$(get_file_size "$f")
 
get_pretty_size "$bytes"
}
 
# 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"
}
 
# Gets the size of a block device
get_blockdev_size() {
# This is something I've never done so I'm still looking for the right
# way to do it portably.
# Alternatives:
# * fdisk -s (no need for privileged access, read-only)
# Prints the number of blocks. In Linux they're always 1024 AFAICT,
# but I'm not sure about other unices (the -in disk- block size for
# DVDs is 2048).
# * hal
# hal-find-by-property --key block.device --string <(REAL)DEVICE>
# hal-get-property --udi <DEVICEID> --key volume.disc.capacity
# Gets byte size but HAL is far from standard (only Linux
# and FreeBSD have it AFAIK)
# * sysfs
# cat /sys/block/<(KERNEL)DEVICE>/size
# Size is given in sectors (512 blocks). Linux only. *BSD has
# sysctl of which I've no clue.
local dev="$1"
local numblocks=$(/sbin/fdisk -s "$dev")
get_pretty_size $(( 1024 * $numblocks ))
}
 
# 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
}
 
#
# Tests if the filter chain contains the provided filter
# has_filter($1 = filtername)
has_filter() {
local filter= ref=$1
for filter in ${FILTERS_IND[@]} ; do
[ "$filter" == "$ref" ] || continue
return 0
done
return 1
}
 
# }}} # 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 "$runlen" -lt $(get_interval "$MIN_LENGTH_FOR_END_OFFSET") ; then
# Min length to use end offset not met, it won't be used
inf "End offset won't be used, video too short."
eo=0
elif 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 for the video, 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])
{
if [ $DVD_MODE -eq 1 ]; then
mplayer -sws 9 -ao null -benchmark -vo "png:z=0" -quiet \
-frames 5 -ss $stamp $shoehorned -dvd-device "$DVD_FILE" \
"dvd://$DVD_TITLE"
else
mplayer -sws 9 -ao null -benchmark -vo "png:z=0" -quiet \
-frames 5 -ss $stamp $shoehorned "$f"
fi
} >"$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, $5 = context, $6 = index[1..])
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" "$5" "$6" ) -flatten "
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, $5 = context, $6 = index)
filt_apply_stamp() {
trace $FUNCNAME $@
local filename=$1 timestamp=$2 width=$3 height=$4 context=$5 index=$6
 
local pts=$pts_tstamps
if [ $height -lt 200 ]; then
pts=$(( $pts_tstamps / 3 ))
elif [ $height -lt 400 ]; then
pts=$(( $pts_tstamps * 2 / 3 ))
fi
# If the size is too small they won't be readable at all
if [ $pts -le 8 ]; then
pts=8
if [ $index -eq 1 ] && [ $context -ne $CTX_EXT ]; then
warn "Beware, using very small timestamps to accomodate smaller captures,\
you might prefer using -dt to disable them"
fi
fi
# The last -gravity None is used to "forget" the previous gravity (otherwise it would
# affect stuff like the polaroid frames)
echo -n " \( -box '$bg_tstamps' -fill '$fg_tstamps' -pointsize '$pts' "
echo -n " -gravity '$grav_timestamp' -stroke none -strokewidth 3 -annotate +5+5 "
echo " ' $(pretty_stamp $stamp) ' \) -flatten -gravity None "
}
 
# Apply a framed photo-like effect
# Taken from <http://www.imagemagick.org/Usage/thumbnails/#polaroid>
# filt_photoframe($1 = filename, $2 = timestamp, $3 = width, $4 = height)
filt_photoframe0() {
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
# The border is relative to the input size (since 1.0.99), with a maximum of 6
# Should probably be bigger for really big frames
# Note that only images below 21600px (e.g. 160x120) go below a 6px border
local border=$(( ($3*$4) / 3600 ))
[ $border -lt 7 ] || border=6
echo -n "-bordercolor white -border $border -bordercolor grey60 -border 1 "
echo -n "-background black \( +clone -shadow 60x4+4+4 \) +swap "
echo "-background none -flatten -trim +repage"
}
 
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 ) ) ))
# The border is relative to the input size (since 1.0.99), with a maximum of 6
# Should probably be bigger for really big frames
# Note that only images below 21600px (e.g. 160x120) go below a 6px border
local border=$(( ($3*$4) / 3600 ))
[ $border -lt 7 ] || border=6
echo -n "-bordercolor white -border $border -bordercolor grey60 -border 1 "
}
 
filt_softshadow() {
# Before this was a filter, there was the global (montage) softshadow (50x2+10+10) and the
# photoframe inline softshadow 60x4+4+4
echo -n "\( -background black +clone -shadow 50x2+4+4 -background none \) +swap -flatten -trim +repage "
}
 
 
# Apply a polaroid-like border effect
# Based on filt_photoframe(), with a bigger lower border
# filt_polaroid($1 = filename, $2 = timestamp, $3 = width, $4 = height)
filt_polaroid() {
trace $FUNCNAME $@
# local file="$1" ts=$2 w=$3 h=$4
local border=$(( ($3*$4) / 3600 )) # Read filt_photoframe for details
[ $border -lt 7 ] || border=6
echo -n "-bordercolor white -mattecolor white -frame ${border}x${border} "
# FIXME: This is rather ugly (double-flipping) there's sure a better way
echo -n "\( -flip -splice 0x$(( $border*5 )) \) "
echo "-flip -bordercolor grey60 -border 1 +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= vpad= splice=
 
# The shadows already add a good amount of padding
if has_filter filt_softshadow ; then
hpad=$(( $HPAD-5 ))
vpad=0
splice=5x10
else
hpad=$HPAD
vpad=4
splice=0x8
fi
 
montage -background Transparent "$@" -geometry +$hpad+$vpad -tile "$cols"x "$output"
# With the shadows moved to a filter, there's not enough spacing to header
convert "$output" -background Transparent -splice $splice "$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 # Integrated in the row creation since 1.0.99
 
# 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
cmdopts="$cmdopts '$1' -geometry +${accoffset}+0 -composite "
 
offset=$(( $minoffset + ( $RANDOM % $maxoffset ) ))
let 'accoffset=accoffset + w - offset'
shift
done
inf "Composing overlapped row $row/$numrows..."
eval convert -size ${canvasw}x${canvash} xc:transparent -geometry +0+0 "$cmdopts" -trim +repage "'$rowfile'" >&2
done
 
inf "Merging overlapped rows..."
output=$(new_temp_file .png)
 
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 '$row' -geometry +$(( $RANDOM % $maxoffset ))+$accoffset -composite "
let 'accoffset=accoffset + h - offset'
done
# After the trim the image will be touching the outer borders and the heading and footer,
# older versions (prior to 1.0.99) used -splice 0x10 to correct the heading spacing, 1.0.99
# onwards uses -frame to add spacing in all borders + splice to add a bit more space on the
# upper border. Note splice uses the background colour while frame uses the matte colour
eval convert -size ${canvasw}x$(( $canvash * $cols )) xc:transparent -geometry +0+0 \
$cmdopts -trim +repage -bordercolor Transparent -background Transparent -mattecolor Transparent \
-frame 5x5 -splice 0x5 "$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
if [ $DVD_MODE -eq 0 ]; then
MPLAYER_CACHE=$(mplayer -benchmark -ao null -vo null -identify -frames 0 \
-quiet "$f" 2>/dev/null | grep ^ID)
# Used as fallback. Introduced in 1.0.99 so expect it to fail :P
FFMPEG_CACHE=$(ffmpeg -i "$f" -dframes 0 -vframes 0 /dev/null 2>&1 | grep Stream)
else
MPLAYER_CACHE=$(mplayer -benchmark -ao null -vo null -identify -frames 0 \
-quiet -dvd-device $DVD_FILE dvd://$DVD_TITLE \
2>/dev/null | grep ^ID)
fi
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)
if [ $DVD_MODE -eq 0 ]; then
VID[$LEN]=$(grep ID_LENGTH <<<"$MPLAYER_CACHE"| cut -d'=' -f2)
else
VID[$LEN]=$(grep ID_DVD_TITLE_${DVD_TITLE}_LENGTH <<<"$MPLAYER_CACHE"| cut -d'=' -f2)
fi
# 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
 
# Fallback for values known to fail often
if [ "$FFMPEG_CACHE" ]; then
# FPS=1000.00 happens often for WMV
if [ "${VID[$FPS]}" == "1000.00" ]; then
local fps2=$(grep Stream <<<"$FFMPEG_CACHE" | grep Video | head -1 | \
egrep -o ', [0-9]+\.[0-9]+ ' | egrep -o '[0-9]+.*[0-9]')
if is_float "$fps2" ; then
VID[$FPS]=$fps2
fi
unset fps2
fi
# Number of channels 0 happens for WMA in non-x86
# Mplayer seems to default to 2 for >2, so ffmpeg might be a better default option
# if [ "${VID[$CHANS]}" ] && ( ! is_number "${VID[$CHANS]}" || [ ${VID[$CHANS]} -eq 0 ] ) ; then
local ch2=$(grep Stream <<<"$FFMPEG_CACHE" | grep Audio | head -1 | cut -d, -f3 | sed 's/^ //')
if [ "$ch2" ]; then
case $ch2 in
mono) VID[$CHANS]=1 ;;
stereo) VID[$CHANS]=2 ;;
5.1) VID[$CHANS]=6 ;;
*) ;;
esac
fi
# fi
fi
 
 
# Check sanity of the most important values
is_number "${VID[$W]}" && is_number "${VID[$H]}" && is_float "${VID[$LEN]}"
}
 
# Checks if the provided arguments make sense and are allowed to be used
# together
coherence_check() {
# 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)"
return $EX_USAGE
fi
 
# Currently it's not allowed to use dvd mode with more than one input
# "file" (in this mode, input files are actually dvd titles of the file
# provided in -V)
if [ $DVD_MODE -eq 1 ] ; then
if [ $multiple_input_files -eq 1 ]; then
error "Only an input file is allowed in DVD mode"
return $EX_UNAVAILABLE
fi
 
# DVD Mode only works with mplayer, the decoder is changed when
# the DVD mode option is found, so if it's ffmpeg at this point,
# it's by user request (i.e. -F after -V)
if [ $decoder -ne $DEC_MPLAYER ]; then
warn "DVD mode requires the use of mplayer, falling back to it"
decoder=$DEC_MPLAYER
fi
fi
 
local filter=
if [ $DISABLE_TIMESTAMPS -eq 0 ] &&
local filts=( )
has_filter filt_polaroid && has_filter filt_apply_stamp ; then
 
for filter in ${FILTERS_IND[@]} ; do
if [ "$filter" == "filt_polaroid" ]; then
filts+=( $filter )
filts+=( filt_apply_stamp )
elif [ "$filter" == "filt_apply_stamp" ]; then
continue;
else
filts+=( $filter )
fi
done
FILTERS_IND=( ${filts[*]} )
unset filts
fi
# The shoftshadow and randrot filters must be in the correct place
# or they will affect the image incorrectly.
# Additionally the default filters can be disabled from the command
# line (with --disable), they're removed from the filter chain here
local filts=( ) end_filts=( )
for filter in ${FILTERS_IND[@]} ; do
case "$filter" in
filt_softshadow)
# Note the newer soft shadows code (1.0.99 onwards) behaves slightly
# differently. On previous versions disabling shadows only affected
# the montage shadow (but e.g. the polaroid mode preserved them),
# this is no longer true
if [ $DISABLE_SHADOWS -ne 1 ]; then
end_filts[100]="filt_softshadow"
fi
;;
filt_apply_stamp)
if [ $DISABLE_TIMESTAMPS -ne 1 ]; then
filts+=( "$filter" )
fi
;;
filt_randrot) end_filts[200]="filt_randrot" ;;
*) filts+=( "$filter" ) ;;
esac
done
FILTERS_IND=( ${filts[*]} ${end_filts[*]} )
}
 
# Main function.
# Creates the contact sheet.
# process($1 = file)
process() {
trace $FUNCNAME $@
local f=$1
 
local numcols=
 
# XXX: Some of this should be moved to coherence_check
if [ $DVD_MODE -eq 1 ]; then # DVD Mode
f="$DVD_FILE"
local dvdn="$f"
if [ -b "$dvdn" ]; then
dvdn="DVD"
elif [ ! -f "$dvdn" ]; then
error "File \"$dvdn\" doesn't exist"
return $EX_NOINPUT
fi
inf "Processing $dvdn..."
unset dvdn
if ! is_number "$1" ; then
error "DVD Title must be a number (e.g.: \$ vcs -V /dev/dvd 1)"
exit $EX_USAGE
fi
DVD_TITLE=$1
if [ $DVD_TITLE -eq 0 ]; then
local dt="$(lsdvd "$DVD_FILE" | grep 'Longest track:' | \
cut -d' ' -f3- | sed 's/^0*//')"
if ! is_number "$dt" ; then
error "Failed to autodetect longest DVD title"
exit $EX_INTERNAL
fi
DVD_TITLE=$dt
unset dt
inf "Using DVD Title #$DVD_TITLE"
fi
else
if [ ! -f "$f" ]; then
error "File \"$f\" doesn't exist"
return $EX_NOINPUT
fi
 
inf "Processing $f..."
fi
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
# Compute the stamps (if in auto mode)...
if [ $manual_mode -eq 1 ]; then
# Note TIMECODES must be set as an array to get the correct count in
# manual mode; in automatic mode it will be set correctly inside
# compute_timecodes()
TIMECODES=( ${initial_stamps[@]} )
else
TIMECODES=${initial_stamps[@]}
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 $VIDCAPFILE 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 $CTX_HL $n || {
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 $CTX_STD $n || 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 $CTX_EXT $n || 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
 
 
local csw=$(imw "$output") exw= hlw=
local width=$csw
if [ "$HLTIMECODES" ] || [ "$extended_factor" != "0" ]; then
inf "Merging contact sheets..."
if [ "$HLTIMECODES" ]; then
local hlw=$(imw "$hlfile")
if [ $hlw -gt $width ]; then width=$hlw ; fi
fi
if [ "$extended_factor" != "0" ]; then
local exw=$(imw $extoutput)
if [ $exw -gt $width ]; then width=$exw ; fi
fi
fi
if [ $csw -lt $width ]; then
local csh=$(imh "$output")
# Expand the standard set to the maximum width of the sets by padding both sides
# For some reason the more obvious (to me) convert command-lines lose
# the transparency
convert \( -size $(( ($width - $csw) / 2 ))x$csh xc:transparent \) "$output" \
\( -size $(( ($width - $csw) / 2 ))x$csh xc:transparent \) +append "$output"
unset csh
fi
 
# If there were highlights then mix them in
if [ "$HLTIMECODES" ]; then
# For some reason adding the background also adds padding with:
# convert \( -background LightGoldenRod "$hlfile" -flatten \) \
# \( "$output" \) -append "$output"
# replacing it with a "-composite" operation apparently works
# Expand the highlights to the correct size by padding
local hlh=$(imh "$hlfile")
if [ $hlw -lt $width ]; then
convert \( -size $(( ($width - $hlw) / 2 ))x$hlh xc:transparent \) "$hlfile" \
\( -size $(( ($width - $hlw) / 2 ))x$hlh xc:transparent \) +append "$hlfile"
fi
convert \( -size ${width}x${hlh} xc:LightGoldenRod "$hlfile" -composite \) \
\( -size ${width}x1 xc:black \) \
"$output" -append "$output"
unset hlh
fi
# Extended captures
if [ "$extended_factor" != 0 ]; then
# Already set local exw=$(imw "$extoutput")
local exh=$(imh "$extoutput")
if [ $exw -lt $width ]; then
# Expand the extended set to be the correct size
convert \( -size $(( ($width - $exw) / 2 ))x$exh xc:transparent \) "$extoutput" \
\( -size $(( ($width - $exw) / 2 ))x$exh xc:transparent \) +append "$extoutput"
fi
convert "$output" -background Transparent "$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=$(imw "$output")
# 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.
local filename_label="Filename"
local filesize_label="File size"
local filename_value=
local filesize_value=
if [ $DVD_MODE -eq 1 ]; then
local is_dev=0
test -b "$DVD_FILE" && is_dev=1
local dvd_label=$(lsdvd "$DVD_FILE" | grep -o 'Disc Title: .*' | cut -d' ' -f3-)
# lsdvd is guaranteed to be installed if DVD mode is enabled
if [ $is_dev -eq 1 ]; then # This is a real DVD, not an iso
filename_label="Disc label"
filename_value="$dvd_label"
filesize_label="Disc size"
filesize_value="$(get_blockdev_size "$DVD_FILE")"
else
filename_value="$(basename "$DVD_FILE") $filename_value (DVD Label: $dvd_label)"
filesize_value="$(get_pretty_file_size "$f")"
fi
else
filename_value="$(basename "$f")"
filesize_value="$(get_pretty_file_size "$f")"
fi
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_label: " \
-font "$fn_font" label:"$filename_value" +append \
\) \
-font "$font_heading" \
label:"$filesize_label: $filesize_value" \
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"
 
"get_interval 2h 7200 #Hours parsing"
"get_interval 2m 120 #Minutes parsing"
"get_interval 30s 30 #Seconds parsing"
"get_interval .30 .30 #Milliseconds parsing"
# Extended syntax
"get_interval 30m30m1h 7200 #Repeated minutes parsing"
)
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-2009 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.
-V|--dvd <file.iso|dvd_device>
DVD Mode, use file.iso as DVD. In this mode the
<file> argument must point to the title number, e.g.:
$ vcs -V somedvd.iso 1
Passing title 0 will use the default (longest) title.
$ vcs -V /dev/dvd 0
Implies -A (auto aspect ratio)
-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).
-j2|--jpeg 2 Output in jpeg 2000
-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.
"polaroidframe": Use '-kL' or '--funky polaroidframe'
Adds a polaroid picture-like white frame to each image.
"photos": Use '-kc' or '--funky photos'
Combination of rotate, photoframe and overlap.
Same as -kp -kr -ko.
"polaroid": Use '-kp' or '--funky polaroid'
Combination of rotate, polaroidframe and overlap.
Same as -kL -kr -ko.
"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:j::hFMH:c:ma:l:De::U::qAO:I::k:W:E:d:V: \
--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:,dvd:" \
-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)
if [ "$2" ]; then # Arg is optional, 2 is for JPEG 2000
# 2000 is also accepted
if [ "$2" != "2" ] && [ "$2" != "2000" ]; then
error "Use -j for jpeg output or -j2 for JPEG 2000 output. Got '-j$2'."
exit $EX_USAGE
fi
output_format=jp2
else
output_format=jpg
fi
shift
;;
-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 "$2" in # Note older versions (<1.0.99) were case-insensitive
p|polaroid) # Same as overlap + rotate + polaroid
inf "Changed to polaroid funky mode."
FILTERS_IND+=( 'filt_polaroid' 'filt_randrot' )
CSHEET_DELEGATE='csheet_overlap'
# XXX: The newer version has a lot less flexibility with these many
# hardcoded values...
grav_timestamp=South
fg_tstamps=Black
bg_tstamps=Transparent
pts_tstamps=$(( $pts_tstamps * 3 / 2 ))
;;
c|photos) # Same as overlap + rotate + photoframe, this is the older polaroid
inf "Changed to polaroid funky mode."
FILTERS_IND+=( 'filt_photoframe' 'filt_randrot' )
CSHEET_DELEGATE='csheet_overlap'
# The timestamp must change location to be visible most of the time
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' )
;;
L|polaroidframe) # White polaroid frame
FILTERS_IND+=( 'filt_polaroid ')
grav_timestamp=South
fg_tstamps=Black
bg_tstamps=Transparent
pts_tstamps=$(( $pts_tstamps * 3 / 2 ))
;;
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)
if [ $DISABLE_TIMESTAMPS -eq 0 ]; then
inf "Timestamps disabled."
# They'll be removed from the filter chain in coherence_check
DISABLE_TIMESTAMPS=1
fi
;;
s|shadows|shadow)
if [ $DISABLE_SHADOWS -eq 0 ]; then
inf "Shadows disabled."
# They will be removed from the filter chain in coherence_check
DISABLE_SHADOWS=1
fi
;;
*)
error "Requested disabling unknown feature. Got '$2'."
exit $EX_USAGE
;;
esac
shift
;;
-V|--dvd)
# DVD Mode requires lsdvd
if ! type -pf lsdvd >/dev/null ; then
error "DVD Support requires the lsdvd program"
exit $EX_UNAVAILABLE
fi
DVD_MODE=1
DVD_FILE="$2"
decoder=$DEC_MPLAYER
aspect_ratio=-1
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
DEBUG=1
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
elif [ "$2" ]; then
multiple_input_files=1
fi
 
# }}} # Command line parsing
 
# The coherence check ensures the processed options are
# not incoherent/incompatible with the input files or with
# other given options
coherence_check
 
# Test requirements
test_programs || exit $EX_UNAVAILABLE
 
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.99/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.99
Property changes:
Added: svn:mergeinfo
Merged /video-contact-sheet/branches/1.0a:r262-263
Merged /video-contact-sheet/tags/1.0.12:r352-356
Merged /video-contact-sheet/tags/1.0.11:r344-345
Merged /video-contact-sheet/tags/1.0.2b:r274
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.12:r347-350
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.9a:r322-325
Merged /video-contact-sheet/branches/1.0.99:r358-361
Merged /video-contact-sheet/branches/1.0.8a:r315-317
/video-contact-sheet/tags/1.0.12/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.12/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, gsfonts
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.12/debian-package/debian/changelog
0,0 → 1,18
vcs (1.0.12-upstream.1) experimental; urgency=low
 
* debian/control: Added missing requirement (gsfonts)
 
-- Toni Corvera <outlyer@gmail.com> Mon, 06 Oct 2008 14:26:27 +0200
 
vcs (1.0.12-upstream.0) experimental; urgency=low
 
* New version.
* Added suffix to version number.
 
-- Toni Corvera <outlyer@gmail.com> Wed, 16 Apr 2008 17:59:46 +0200
 
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.12/debian-package/debian/dirs
0,0 → 1,0
usr/bin
/video-contact-sheet/tags/1.0.12/debian-package/debian/compat
0,0 → 1,0
5
/video-contact-sheet/tags/1.0.12/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.12/debian-package/Makefile
0,0 → 1,14
# $Id$
 
prefix:=/usr
DESTDIR:=/
 
all:
clean:
 
install:
install -d "$(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.12/CHANGELOG
0,0 → 1,189
1.0.12: (2008-04-16)
* BUGFIX/COSMETIC: Corrected 0ms timestamps
* COSMETIC: Re-added the (disabled for long) black border after highlights
* BUGFIX/COSMETIC: Corrected the count of captures in manual-only mode (-m)
* FEATURE: Added a minimun length to use the end offset
* BUGFIX: Fixed the regression on highlights from the last version (extra
padding was being added my IM automatically)
* INTERNAL: Simplified use of IM's identify
* BUGFIX: Fixed parsing of manual timestamps including milliseconds
(when seconds didn't include the s character they were accidentally
multiplied by 10!)
 
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.12/vcs
0,0 → 1,2297
#!/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:
# Mainly pages I've taken snippets from or wrote code based on them; or pages
# containing reference/technical data
# 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.12"
# {{{ # CHANGELOG
# History (The full changelog can be found at <http://p.outlyer.net/vcs/files/CHANGELOG>).
#
# 1.0.12: (2008-04-16)
# * BUGFIX/COSMETIC: Corrected 0ms timestamps
# * COSMETIC: Re-added the (disabled for long) black border after highlights
# * BUGFIX/COSMETIC: Corrected the count of captures in manual-only mode (-m)
# * FEATURE: Added a minimun length to use the end offset
# * BUGFIX: Fixed the regression on highlights from the last version (extra
# padding was being added my IM automatically)
# * INTERNAL: Simplified use of IM's identify
# * BUGFIX: Fixed parsing of manual timestamps including milliseconds
# (when seconds didn't include the s character they were accidentally
# multiplied by 10!)
# }}} # 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
# If the video is less than this length, end offset won't be used at all
declare MIN_LENGTH_FOR_END_OFFSET=19m30s
# 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'
'MIN_LENGTH_FOR_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() {
trace $FUNCNAME $@
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/([0-9])\./\1 + ./g' <<<"$t") # seconds followed by ms, with no "S"
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 -format '%w' "$1"
}
 
# Get Image Height
# imh($1 = file)
imh() {
identify -format '%h' "$1"
}
 
# 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 ms
if grep -q '\.' <<<"$t" ; then
# Have ms
s=$(cut -d'.' -f1 <<<$t)
ms=$(cut -d'.' -f2 <<<$t)
else
s=$t
ms=0
fi
 
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 "$runlen" -lt $(get_interval "$MIN_LENGTH_FOR_END_OFFSET") ; then
# Min length to use end offset not met, it won't be used
inf "End offset won't be used, video too short."
eo=0
elif 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 for the video, 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
 
# 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
# Compute the stamps (if in auto mode)...
if [ $manual_mode -eq 1 ]; then
# Note TIMECODES must be set as an array to get the correct count in
# manual mode; in automatic mode it will be set correctly inside
# compute_timecodes()
TIMECODES=( ${initial_stamps[@]} )
else
TIMECODES=${initial_stamps[@]}
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
# For some reason adding the background also adds padding with:
# convert \( -background LightGoldenRod "$hlfile" -flatten \) \
# \( "$output" \) -append "$output"
# replacing it with a "-composite" operation apparently works
local geometry=$(identify -format '%wx%h' "$hlfile")
convert \( -size "$geometry" xc:LightGoldenRod "$hlfile" -composite \) \
\( -size "$(cut -d'x' -f1<<<$geometry)"x1 xc:black \) \
"$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=$(imw "$output")
# 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"
 
"get_interval 2h 7200 #Hours parsing"
"get_interval 2m 120 #Minutes parsing"
"get_interval 30s 30 #Seconds parsing"
"get_interval .30 .30 #Milliseconds parsing"
# Extended syntax
"get_interval 30m30m1h 7200 #Repeated minutes parsing"
)
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
DEBUG=1
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.12/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.12
Property changes:
Added: svn:mergeinfo
Merged /video-contact-sheet/branches/1.0a:r262-263
Merged /video-contact-sheet/tags/1.0.11:r344-345
Merged /video-contact-sheet/tags/1.0.2b:r274
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.12:r347-350
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.9a:r322-325
Merged /video-contact-sheet/branches/1.0.8a:r315-317
/video-contact-sheet/tags/1.0.11/vcs
0,0 → 1,2273
#!/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
# 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
/video-contact-sheet/tags/1.0.10/vcs
0,0 → 1,2186
#!/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@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>
#
 
declare -r VERSION="1.0.10"
# {{{ # 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.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
# get a quicker response.
# }}} # 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
 
# See chseet_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'
)
 
# 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
 
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() {
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 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() {
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 # 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 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 $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. 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.
-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.10/CHANGELOG
0,0 → 1,154
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.10/Makefile
0,0 → 1,19
#!/usr/bin/make -f
 
VER=$(shell grep VERSION vcs|head -n1|sed -r 's/.*"(.*)".*/\1/g')
 
all:
@echo "Use $(MAKE) dist"
 
dist:
if [ -d .svn ]; then echo "Don't release from SVN working copy" ; false ; fi
cp vcs vcs-$(VER)
gzip -9 vcs-$(VER)
cp vcs vcs-$(VER)
bzip2 -9 vcs-$(VER)
mv vcs vcs-$(VER)
gzip -9 CHANGELOG
gzip -dc CHANGELOG.gz > CHANGELOG
rm -i Makefile
 
.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.10
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.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
/video-contact-sheet/tags/1.0.9a/CHANGELOG
0,0 → 1,144
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.9a/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.9a"
# {{{ # 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.9a: (2007-06-10)
# * 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)
# }}} # 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 # 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 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 $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. 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.9a/Makefile
0,0 → 1,19
#!/usr/bin/make -f
 
VER=$(shell grep VERSION vcs|head -n1|sed -r 's/.*"(.*)".*/\1/g')
 
all:
@echo "Use $(MAKE) dist"
 
dist:
if [ -d .svn ]; then echo "Don't release from SVN working copy" ; false ; fi
cp vcs vcs-$(VER)
gzip -9 vcs-$(VER)
cp vcs vcs-$(VER)
bzip2 -9 vcs-$(VER)
mv vcs vcs-$(VER)
gzip -9 CHANGELOG
gzip -dc CHANGELOG.gz > CHANGELOG
rm -i Makefile
 
.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.9a
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.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
/video-contact-sheet/tags/1.0.8a/CHANGELOG
0,0 → 1,138
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.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
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
/video-contact-sheet/tags/1.0.7a/CHANGELOG
0,0 → 1,132
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.7a/vcs
0,0 → 1,2144
#!/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.7a"
# {{{ # 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.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 / $end_offset, used to eliminate some
# seconds from the end
# * FEATURE: Anonymous mode (use --anonymous or -U0)
# }}} # 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=$cols
 
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
 
# Contact sheet minimum cols:
if [ $nc -lt $numcols ]; then
numcols=$nc
fi
 
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 )
 
# Highlighs
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
 
inf "Composing highlights contact sheet..."
hlfile=$( create_contact_sheet $numcols $CTX_HL $vidcap_width $vidcap_height "${capfiles[@]}" )
unset hlcapfile pretty n capfiles
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[@]}"
 
inf "Composing standard contact sheet..."
output=$(create_contact_sheet $numcols $CTX_STD $vidcap_width $vidcap_height "${capfiles[@]}")
unset capfile capfiles pretty n
 
# 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
inf "Composing extended contact sheet..."
extoutput=$( create_contact_sheet $(($numcols * 2)) $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 -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.7a
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/tags/1.0.2b:r274
/video-contact-sheet/tags/1.0.1a/vcs
0,0 → 1,723
#!/bin/bash
 
# $Rev$ $Date$
 
declare -r VERSION="1.0.1a"
#
# History:
#
# 1.0.1a:
# * 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
#
# 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
 
set -e
 
# 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_META=gray # Make the heading gray
#
# The variables that can be overriden are below the block of constants ahead.
declare -r CFGFILE=~/.vcs.conf
 
# Constants {{{
# see $METHOD
declare -ri METHOD_MPLAYER=1 METHOD_FFMPEG=2
# See $derive_from
declare -ri INTERVAL=1 NUMCAPS=3
# These can't be overriden, modify this line if you feel the need
declare -r PROGRAM_SIGNATURE=<<EOT
with Video Contact Sheet *NIX ${VERSION} <http://p.outlyer.net/vcs/>
EOT
 
# }}} # End of constants
 
# Override-able variables {{{
 
declare -i DEFAULT_INTERVAL=300
declare -i DEFAULT_NUMCAPS=16
# Text before the user name in the signature
declare USER_SIGNATURE="Preview created by"
# By default sign as the system's username
declare user=$(id -un)
# Which of the two methods should be used to guess the number of thumbnails
declare -i derive_from=$INTERVAL
# Which of the two vidcappers should be used (see -F, -M)
# mplayer seems to fail for mpeg or WMV9 files, at least on my system
declare -i METHOD=$METHOD_FFMPEG
# Options used in imagemagick, these options set the final aspect
# of the contact sheet
declare OUTFMT=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_META=YellowGreen # Background for meta info (size, codec...)
declare BG_SIGN=SandyBrown # Background for signature
declare FG_META=black # Font colour for meta info box
declare FG_SIGN=black # Font colour for signature
declare FG_STAMPS=white # Font colour for timestamps
# Fonts, see convert -list type to get the list
declare FONT_STAMPS=courier # Used for timestamps behind the thumbnails
declare FONT_META=helvetica # Used for meta info box
declare FONT_SIGN=$FONT_META # Used for the signature box
# Font sizes, in points
declare PS_STAMPS=18 # Used for the timestamps
declare PS_META=16 # Used for the meta info box
declare PS_SIGN=11 # Used for the signature
# See --shoehorn
declare shoehorned=
 
# }}} # End of override-able variables
 
# Options and other internal usage variables, no need to mess with this!
declare -i interval=$DEFAULT_INTERVAL # Interval of captures (= numsecs / numcaps)
declare -i numcaps=$DEFAULT_NUMCAPS # Number of captures (= numsecs / interval)
declare title=""
declare -i fromtime=0 # Starting second (see -f)
declare -i totime=-1 # Ending second (see -t)
declare -a initial_stamps=( ) # Stamps added to the calculated ones (see -S)
declare -i th_height= # Height of the thumbnails, by default use same as input
declare -i cols=2 # Number of output columns
declare -i manual_mode=0 # if 1, only command line timestamps will be used
 
load_config() {
# 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_.*'
'PS_.*'
'FG_.*'
'OUTPUT_QUALITY'
'DEFAULT_INTERVAL'
'DEFAULT_NUMCAPS'
'METHOD'
'OUTFMT'
'shoehorned'
'derive_from'
)
 
if [ ! -f "$CFGFILE" ]; then return 0 ; fi
 
local compregex=$( sed 's/ /|/g' <<<${ALLOWED_OVERRIDES[*]} )
 
while read line ; do # auto variable $line
# Don't allow ';', FIXME: dunno how secure that really is
if ! egrep -q '^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]*=[^;]*' <<<"$line" ; then
continue
fi
if ! egrep -q "^($compregex)=" <<<"$line" ; then
continue
fi
# FIXME: Only print in verbose mose
# FIXME: Only for really overridden ones
echo "Overridden variable" $(sed -r 's/^[[:space:]]*([a-zA-Z0-9_]*)=.*/\1/'<<<$line)
eval $line
done <$CFGFILE
}
 
# {{{ # Convenience functions
 
is_number() {
egrep -q '^[0-9]+$' <<<"$1"
return $?
}
 
# The current code is a tad permissive, it allows e.g. things like
# 10m1h (equivalent to 1h10m)
# 1m1m (equivalent to 2m)
get_interval() {
if is_number "$1" ; then echo $1 ; return 0 ; fi
 
local s=$(tr '[A-Z]' '[a-z]' <<<"$1")
 
# Only allowed characters
if ! grep -q '[0-9smh]' <<<"$s"; then
return 1;
fi
 
# FIXME: Find some cleaner way
local i= c= num= sum=0
for i in $(seq 0 $(( ${#s} - 1)) ); do
c=${s:$i:1}
if is_number $c ; then
num+=$c
else
case $c in
h) num=$(($num * 3600)) ;;
m) num=$(($num * 60)) ;;
s) ;;
*)
return 2
;;
esac
sum=$(($sum + $num))
num=
fi
done
 
# If last element was a number, it's seconds and they weren't added
if is_number $c ; then
sum=$(( $sum + $num ))
fi
 
echo $sum
 
return 0
}
 
pad() {
local len=$1
local str=$2
 
while [ ${#str} -lt $len ]; do
str=0$str
done
 
echo $str
}
 
pretty_stamp() {
if ! is_number "$1" ; then return 1 ; fi
 
local t=$1
local h=$(( $t / 3600 ))
t=$(( $t % 3600 ))
local m=$(( $t / 60 ))
t=$(( $t % 60 ))
local s=$t
 
local R=""
 
if [ $h -gt 0 ]; then
R+="$h:"
fi
R+=$(pad 2 "$m"):$(pad 2 $s)
 
echo $R
}
 
get_pretty_size() {
local f="$1"
 
local bytes=$(du -DL --bytes "$f" | cut -f1)
local size=""
 
if [ "$bytes" -gt $(( 1024**3 )) ]; then
local gibs=$(( $bytes / 1024**3 ))
local mibs=$(( ( $bytes % 1024**3 ) / 1024**2 ))
size="${gibs}.${mibs:0:2} GiB"
elif [ "$bytes" -gt $(( 1024**2)) ]; then
local mibs=$(( $bytes / 1024**2 ))
local kibs=$(( ( $bytes % 1024**2 ) / 1024 ))
size="${mibs}.${kibs:0:2} MiB"
elif [ "$bytes" -gt 1024 ]; then
local kibs=$(( $bytes / 1024 ))
bytes=$(( $bytes % 1024 ))
size="${kibs}.${bytes:0:2} KiB"
else
size="${bytes} B"
fi
 
echo $size
}
 
# Rename a file, if the target exists, try with -1, -2, ...
safe_rename() {
local from="$1"
local to="$2"
 
local ext=$(sed -r 's/.*\.(.*)/\1/g' <<<"$to")
 
local n=1
while [ -f "$to" ]; do
to="$(basename "$2" .$ext)-$n.$ext"
n=$(( $n + 1))
done
 
mv "$from" "$to"
echo "$to" >&2
}
 
test_programs() {
for prog in mplayer convert montage ffmpeg ; do
type -pf "$prog" >/dev/null
local retval=$?
if [ $retval -ne 0 ] ; then
error "Required program $prog not found!"
return $retval
fi
done
}
 
# Print some text to stderr
error() {
echo "$1" >&2
}
 
# }}} # Convenience functions
 
# {{{ # Core functionality
 
process() {
local f=$1
 
local numcols=$cols
 
if [ ! -f "$f" ]; then
error "File \"$f\" doesn't exist"
return 100
fi
echo "Processing $f..." >&2
 
# Meta data extraction
# Don't change the -vc as it would affect $vdec
local mplayer_cache=$(mplayer -benchmark -ao null -vo null -identify -frames 0 -quiet "$f" 2>/dev/null | grep ^ID)
local vcodec=$(grep ID_VIDEO_FORMAT <<<"$mplayer_cache" | cut -d'=' -f2) # FourCC
local acodec=$(grep ID_AUDIO_FORMAT <<<"$mplayer_cache" | cut -d'=' -f2)
local vdec=$(grep ID_VIDEO_CODEC <<<"$mplayer_cache" | cut -d'=' -f2) # Decoder
local width=$(grep ID_VIDEO_WIDTH <<<"$mplayer_cache" | cut -d'=' -f2)
local height=$(grep ID_VIDEO_HEIGHT <<<"$mplayer_cache" | cut -d'=' -f2)
local fps=$(grep ID_VIDEO_FPS <<<"$mplayer_cache" | cut -d'=' -f2)
# Vidcap/Thumbnail height
local vidcap_height=$th_height
if ! is_number "$vidcap_height" || [ "$vidcap_height" -eq 0 ]; then
vidcap_height=$height
fi
 
local numsecs=$(grep ID_LENGTH <<<"$mplayer_cache"| cut -d'=' -f2 | cut -d. -f1)
if ! is_number $numsecs ; then
error "Internal error!"
return 15
fi
 
local nc=$numcaps
local in=$interval
 
# Start bound:
local startsec=0
if [ $startsec -lt $fromtime ]; then
startsec=$fromtime
fi
 
# End bound:
local endsec=$numsecs
if [ $totime -ne -1 ] && [ $endsec -gt $totime ]; then
endsec=$totime
fi
if [ $startsec -ne 0 ] || [ $endsec -ne $numsecs ]; then
echo "Restricting to range [$startsec..$endsec]" >&2
fi
 
local delta=$(( $endsec - $startsec ))
# FIXME: the total # of caps is currently broken when using -f and -t
 
# Note that when numcaps mandates the interval is obtained from
# the actually allowed secods (hence it is not movie_length / numcaps )
# Adjust interval/numcaps:
if [ "$derive_from" -eq "$INTERVAL" ]; then
# Interval rules => it doesn't change
nc=$(( $delta / $interval ))
# If a multiple, an extra vidcap is generated (at the last second)
if [ $(( $delta % $interval )) -eq 0 ]; then
nc=$(( $nc + 1 ))
fi
elif [ "$derive_from" -eq "$NUMCAPS" ]; then
# Numcaps rules => it doesn't change
if [ $numcaps -eq 1 ]; then # If just one cap, center it
in=$(( $numsecs / 2 ))
else
in=$(( $numsecs / $numcaps ))
fi
else
error "Internal error!"
return 145
fi
 
# Let's try to make some sense...
# Minimum interval allowance:
if [ $in -gt $numsecs ]; then
error "The interval is longer than the video length."
error "Use a lower interval or numcaps instead."
error "Skipping \"$f\"."
return 16
fi
# Contact sheet minimum cols:
if [ $nc -lt $numcols ]; then
numcols=$nc
fi
 
# Tempdir
 
local dir=$(mktemp -d -p . vcs.XXXXXX)
if [ "$?" -ne 0 ]; then
error "Error creating temporary directory"
return 17
fi
 
local n=
 
# Get the stamps (if in auto mode)...
 
local stamps=( )
if [ $manual_mode -ne 1 ]; then
 
n=$(( $startsec + $in ))
stamps=( ${initial_stamps[*]} $n )
 
while [ $n -le $endsec ]; do
n=$(( $n + $in ))
if [ $n -gt $endsec ]; then break; fi
 
stamps=( ${stamps[*]} $n )
done
else
stamps=( ${initial_stamps[*]} )
fi
 
n=1
local p=""
local cap=""
local montage_command="montage -font $FONT_STAMPS -pointsize $PS_STAMPS \
-gravity SouthEast -fill white "
local output=$(tempfile --prefix "vcs-" --suffix '-preview.png' -d .)
 
# Let's reorder the stamps, this away user-added stamps get their correct
# position also remove duplicates. Note AFAIK sort only sorts lines, that's
# why y replace spaces by newlines.
#
# Note that stamps keeps being an array and stamps[1..N] still hold their
# old values, although it's treated as a string after this
stamps=$( sed 's/ /\n/g' <<<"${stamps[*]}" | sort -n | uniq )
 
local VIDCAPFILE=00000001.png
 
# If the temporal vidcap already exists, abort
if [ -f $VIDCAPFILE ]; then
error "Temporal vidcap file ($VIDCAPFILE) exists, remove it before running!."
exit 134
fi
 
local NUMSTAMPS=$(wc -w <<<"$stamps")
# TODO: Aspect ratio
for stamp in $stamps; do
# Note that it must be checked against numsecs and not endsec, to allow
# the user manually setting stamps beyond the boundaries
if [ $stamp -gt $numsecs ]; then continue; fi
 
echo "Generating capture #${n}/${NUMSTAMPS}..." >&2
 
if [ $METHOD -eq $METHOD_MPLAYER ]; then
mplayer -sws 9 -ao null -benchmark -vo "png:z=0" -quiet \
-frames 1 -ss $stamp $shoehorned "$f" >/dev/null 2>&1
elif [ $METHOD -eq $METHOD_FFMPEG ]; then
ffmpeg -y -ss $stamp -i "$f" -an -dframes 1 -vframes 1 -vcodec png \
-f rawvideo $shoehorned $VIDCAPFILE >/dev/null 2>&1
else
error "Internal error!"
return 142
fi || {
error "The capturing program failed!"
return 143
}
 
p=$(pad 6 $stamp).png
# mv 00000001.png $dir/$p
n=$(( $n + 1 ))
 
cap=$dir/$p
# Add the timestamp to each vidcap, doing it hear is much powerful/simple
# than with the next montage command
convert -box '#000000aa' \
-fill $FG_STAMPS -pointsize $PS_STAMPS -gravity SouthEast \
-stroke none -strokewidth 3 -annotate +5+5 " $(pretty_stamp $stamp) " \
$VIDCAPFILE "$cap"
 
montage_command+=" $cap"
done
rm -f $VIDCAPFILE
 
# geometry affects the source images, not the target one!
# Note the file name could also be added by using the "-title" option, but I reserved
# it for used set titles
montage_command+=" -geometry x${vidcap_height}+10+5 -tile ${numcols}x -shadow"
if [ "$title" ]; then
montage_command+=" -font $FONT_META -fill $FG_META -title '$title'"
fi
montage_command+=" $output"
 
echo "Composing contact sheet..." >&2
eval $montage_command # eval is required to evaluate correctly the text in quotes!
mv "$output" . 2>/dev/null || true
 
# Codec "prettyfication"
case $( tr '[A-Z]' '[a-z]' <<<$vcodec) in
xvid) vcodec=Xvid ;;
dx50) vcodec="DivX 5" ;;
0x10000001) vcodec="MPEG-1" ;;
0x10000002) vcodec="MPEG-2" ;;
avc1) vcodec="MPEG-4 AVC" ;;
wmv2) vcodec="WMV 8" ;; # WMV2 is v8
esac
if [ "$vdec" == "ffodivx" ]; then
vcodec+=" (MPEG-4)"
elif [ "$vdec" == "ffh264" ]; then
vcodec+=" (h.264)"
fi
 
case $( tr '[A-Z]' '[a-z]' <<<$acodec ) in
85) acodec='MPEG-1 Layer III (MP3)' ;;
80) acodec='MPEG-1 Layer II (MP2)' ;;
mp4a) acodec='MPEG-4 AAC' ;;
353) acodec='WMA 2' ;;
"") acodec="no audio" ;;
esac
 
 
local meta="Filename: $f
File size: $(get_pretty_size "$f")
Length: $(pretty_stamp "$numsecs")"
local meta2="Dimensions: ${width}x${height}
Format: $vcodec / $acodec
FPS: $fps"
local signature="$USER_SIGNATURE $user
$PROGRAM_SIGNATURE"
 
# Now let's add meta info
# This one enlarges the image to add the text, and puts
# meta info in two columns
convert -font $FONT_META -pointsize $PS_META \
-background $BG_META -fill $FG_META -splice 0x$(( $PS_META * 4 )) \
-gravity NorthWest -draw "text 10,10 '$meta'" \
-gravity NorthEast -draw "text 10,10 '$meta2'" \
"$output" "$output"
# Finishing touch, signature
 
convert -gravity South -font $FONT_SIGN -pointsize $PS_SIGN \
-background $BG_SIGN -splice 0x34+0-0 \
-fill $FG_SIGN -draw "text 10,3 '$signature'" "$output" "$output"
rm -rf $dir/
 
if [ $OUTFMT != "png" ]; then
local newout="$(basename "$output" .png).$OUTFMT"
convert -quality $OUTPUT_QUALITY "$output" "$newout"
rm "$output"
output="$newout"
fi
echo -n "Output wrote to " >&2
safe_rename "$output" "$(basename "$f").$OUTFMT"
}
 
show_help() {
local P=$(basename $0)
cat <<EOF
Video Contact Sheet *NIX v${VERSION}, (c) 2007 Toni Corvera
 
Usage: $P [options] <file>
 
Options:
-i|--interval <arg> Set the interval to arg. An optional unit can be
used (case-insensitive) , e.g.:
Seconds: 90
Minutes: 3m
Hours: 1h
Use either -i or -n.
-n|--numcaps <arg> Set the number of captured images to arg. Use either
-i or -n.
-f|--from <arg> Set starting time. No caps before this. Same format
as -i.
-t|--to <arg> Set ending time. No caps beyond this. Same format
as -i.
-T|--title <arg> Add a title above the vidcaps.
-u|--user <arg> Set the username found in the signature to this.
-S|--stamp <arg> Add the image found at the timestamp "arg", same format
as -i.
-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.
-H|--height <arg> Set the output (individual thumbnail) height. Width is
derived accordingly. Note width cannot be manually set.
-j|--jpeg Output in jpeg (by default output is in png).
-h|--help Show this text.
 
Options used for debugging purposes or to tweak the internal workings:
-M|--mplayer Force the usage of mplayer.
-F|--ffmpeg Force the usage of ffmpeg.
--shoehorn <arg> Pass "arg" to mplayer/ffmpeg. You probably don't need it.
 
Examples:
Create a contact sheet with default values (vidcaps at intervals of
$DEFAULT_INTERVAL seconds), the resulting file will be called
input.avi.png:
\$ $P input.avi
 
Create a sheet with vidcaps at intervals of 3 minutes:
\$ $P -i 3m input.avi
 
Create a sheet with vidcaps starting at 3 mins and ending at 18 mins,
add an extra vidcap at 2m and another one at 19m:
\$ $P -f 3m -t 18m -S2m -S 19m input.avi
 
Quirks:
Currently MPEG handling seems to be broken.
 
EOF
}
 
# }}} # Core functionality
 
# Test requirements
test_programs || exit 54
 
load_config
 
# {{{ # Command line parsing
 
# Based on getopt-parse.bash example.
# On debian systems see </usr/share/doc/util-linux/examples/getopt-parse.bash.gz>
 
# FIXME: use username / no name at all with -u noarg, and not -u
TEMP=$(getopt -o i:n:u:T:f:t:S:jhFMH:c:m \
--long "interval:,numcaps:,username:,title:,from:,to:,stamp:,jpeg,help,shoehorn:,mplayer,ffmpeg,height:,columns:,manual" \
-n $0 -- "$@")
 
eval set -- "$TEMP"
 
while true ; do
case "$1" in
-i|--interval)
if ! interval=$(get_interval "$2") ; then
error "Interval must be a number (given $2)"
exit 68
fi
if [ "$interval" -le 0 ]; then
error "Interval must be higher than 0, set to the default $DEFAULT_INTERVAL"
interval=$DEFAULT_INTERVAL
fi
derive_from=$INTERVAL
shift # Option arg
;;
-n|--numcaps)
if ! is_number "$2" ; then
error "Number of caps must be a number! (given $2)"
exit 68
fi
numcaps="$2"
derive_from=$NUMCAPS
shift # Option arg
;;
-u|--username) user="$2" ; shift ;;
-T|--title) title="$2" ; shift ;;
-f|--from)
if ! fromtime=$(get_interval "$2") ; then
error "Starting timestamp must be a valid interval"
exit 68
fi
shift
;;
-t|--to)
if ! totime=$(get_interval "$2") ; then
error "Ending timestamp must be a valid interval"
exit 68
fi
if [ "$totime" -eq 0 ]; then
error "Ending timestamp was set to 0, set to movie length"
totime=-1
fi
shift
;;
-S|--stamp)
if ! temp=$(get_interval "$2") ; then
error "Timestamps must be a valid interval"
exit 68
fi
initial_stamps=( ${initial_stamps[*]} $temp )
shift
;;
-j|--jpeg) OUTFMT=jpg ;;
-h|--help) show_help ; exit 0 ;;
--shoehorn)
shoehorned="$2"
shift
;;
-F) METHOD=$METHOD_FFMPEG ;;
-M) METHOD=$METHOD_MPLAYER ;;
-H|--height)
if ! is_number "$2" ; then
error "Height must be a number (given $2)"
exit 68
fi
th_height="$2"
shift
;;
-c|--columns)
if ! is_number "$2" ; then
error "Columns must be a number (given $2)"
exit 68
fi
cols="$2"
shift
;;
-m|--manual) manual_mode=1 ;;
--) shift ; break ;;
*) error "Internal error! (remaining opts: $@)" ; exit 76 ;
esac
shift
done
 
# Remaining arguments
if [ ! "$1" ]; then
show_help
exit 67
fi
# 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 68
fi
 
for arg do process "$arg" ; done
 
# }}} # Command line parsing
 
 
# vim:set ts=4 ai: #
Property changes:
Added: svn:executable
Added: svn:keywords
+Rev Id Date
\ No newline at end of property
/video-contact-sheet/tags/1.0.1a
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
/video-contact-sheet/tags/1.0a/vcs
0,0 → 1,595
#!/bin/bash
 
# $Rev$ $Date$
 
#
# History:
#
# 1.0a:
# * First release keeping track of history
# * Put vcs' url in the signature
# * Use system username in signature
# * Added --shoehorn (you get the idea, right?) to feed extra commands to
# the cappers. Lowelevel and not intended to be used anyway :P
# * When just a vidcap is requested, take it from the middle of the video
# * Added -H|--height
#
# 0.99.1a: Interim version, renamed to 1.0a
#
# 0.99a:
# * Added shadows
# * More colourful headers
# * Easier change of colours/fonts
#
# 0.5a: * First usable version
# 0.1: * First proof of concept
 
declare -r VERSION="0.99.1a"
 
# Options
declare -i interval=300 # Interval of captures (= numsecs / numcaps)
declare -i numcaps=16 # Number of captures (= numsecs / interval)
 
declare user=$(id -un)
declare -r USER_SIGNATURE="Preview created by"
declare -r PROGRAM_SIGNATURE="with Video Contact Sheet *NIX ${VERSION} <http://p.outlyer.net/vcs/>"
 
# Options used in imagemagick, these options set the final aspect
# of the contact sheet so tweak them to your liking
#
declare -i OUTPUT_QUALITY=92 # Output image quality (only affects the final image,
# and only in lossy formats)
# Colours, see convert -list color
declare -r BG_META=YellowGreen # Background for meta info (size, codec...)
declare -r BG_SIGN=SandyBrown # Background for signature
declare -r FG_META=black # Font colour for meta info box
declare -r FG_SIGN=black # Font colour for signature
declare -r FG_STAMPS=white # Font colour for timestamps
# Fonts, see convert -list type
declare -r FONT_STAMPS=courier # Used for timestamps behind the thumbnails
declare -r FONT_META=helvetica # Used for meta info box
declare -r FONT_SIGN=$FONT_META # Used for the signature box
# Font sizes, in points
declare -i PS_STAMPS=18 # Used for the timestamps
declare -i PS_META=16 # Used for the meta info box
declare -i PS_SIGN=11 # Used for the signature
 
# Constants and other internal usage variables, no need to mess with this!
# Which of the two methods should be used to guess the number of thumbnails
declare -ri INTERVAL=1 NUMCAPS=3
declare -i derive_from=$INTERVAL
declare -ri DEFAULT_INTERVAL=$interval
# Which of the two vidcappers should be used (see -F, -M)
# mplayer seems to fail for mpeg or WMV9 files, at least on my system
declare -ri METHOD_MPLAYER=1
declare -ri METHOD_FFMPEG=2
declare -i METHOD=$METHOD_FFMPEG
 
# Internal variables:
declare title=""
declare -i fromtime=0 # Starting second (see -f)
declare -i totime=-1 # Ending second (see -t)
declare initial_stamps=( ) # Stamps added to the calculated ones (see -S)
declare OUTFMT=png
declare shoehorned= # See --shoehorn
declare -i th_height= # Height of the thumbnails, by default use same as input
declare -i cols=2 # Number of output columns
 
# {{{ # Convenience functions
 
is_number() {
egrep -q '^[0-9]+$' <<<"$1"
return $?
}
 
get_interval() {
if is_number "$1" ; then echo $1 ; return 0 ; fi
 
local s=$(tr '[A-Z]' '[a-z]' <<<"$1")
local len_n=$(( ${#s} - 1 )) # Length of the theoretical numeric part
local u=${s:$len_n}
local n=${s:0:$len_n}
local i=0
 
if ! is_number "$n" ; then return 1 ; fi
 
case "$u" in
s) i=$n ;;
m) i=$((n * 60)) ;;
h) i=$((n * 3600)) ;;
*) return 2 ; break ;;
esac
 
echo $i
}
 
pad() {
local len=$1
local str=$2
 
while [ ${#str} -lt $len ]; do
str=0$str
done
 
echo $str
}
 
pretty_stamp() {
if ! is_number "$1" ; then return 1 ; fi
 
local t=$1
local h=$(( $t / 3600 ))
t=$(( $t % 3600 ))
local m=$(( $t / 60 ))
t=$(( $t % 60 ))
local s=$t
 
local R=""
 
if [ $h -gt 0 ]; then
R+="$h:"
fi
R+=$(pad 2 "$m"):$(pad 2 $s)
 
echo $R
}
 
get_pretty_size() {
local f="$1"
 
local bytes=$(du -DL --bytes "$f" | cut -f1)
local size=""
 
if [ "$bytes" -gt $(( 1024**3 )) ]; then
local gibs=$(( $bytes / 1024**3 ))
local mibs=$(( ( $bytes % 1024**3 ) / 1024**2 ))
size="${gibs}.${mibs:0:2} GiB"
elif [ "$bytes" -gt $(( 1024**2)) ]; then
local mibs=$(( $bytes / 1024**2 ))
local kibs=$(( ( $bytes % 1024**2 ) / 1024 ))
size="${mibs}.${kibs:0:2} MiB"
elif [ "$bytes" -gt 1024 ]; then
local kibs=$(( $bytes / 1024 ))
bytes=$(( $bytes % 1024 ))
size="${kibs}.${bytes:0:2} KiB"
else
size="${bytes} B"
fi
 
echo $size
}
 
# Rename a file, if the target exists, try with -1, -2, ...
safe_rename() {
local from="$1"
local to="$2"
 
local ext=$(sed -r 's/.*\.(.*)/\1/g' <<<"$to")
 
local n=1
while [ -f "$to" ]; do
to="$(basename "$2" .$ext)-$n.$ext"
n=$(( $n + 1))
done
 
mv "$from" "$to"
}
 
test_programs() {
for prog in mplayer convert montage ffmpeg ; do
type -pf "$prog" >/dev/null
local retval=$?
if [ $retval -ne 0 ] ; then
error "Required program $prog not found!"
return $retval
fi
done
}
 
# Print some text to stderr
error() {
echo "$1" >&2
}
 
# }}} # Convenience functions
 
# {{{ # Core functionality
 
process() {
local f=$1
 
local numcols=$cols
 
if [ ! -f "$f" ]; then
error "File \"$f\" doesn't exist"
return 100
fi
echo "Processing $f..." >&2
 
# Meta data extraction
# Don't change the -vc as it would affect $vdec
local mplayer_cache=$(mplayer -benchmark -ao null -vo null -identify -frames 0 -quiet "$f" 2>/dev/null | grep ^ID)
local vcodec=$(grep ID_VIDEO_FORMAT <<<"$mplayer_cache" | cut -d'=' -f2) # FourCC
local acodec=$(grep ID_AUDIO_FORMAT <<<"$mplayer_cache" | cut -d'=' -f2)
local vdec=$(grep ID_VIDEO_CODEC <<<"$mplayer_cache" | cut -d'=' -f2) # Decoder
local width=$(grep ID_VIDEO_WIDTH <<<"$mplayer_cache" | cut -d'=' -f2)
local height=$(grep ID_VIDEO_HEIGHT <<<"$mplayer_cache" | cut -d'=' -f2)
local fps=$(grep ID_VIDEO_FPS <<<"$mplayer_cache" | cut -d'=' -f2)
# Vidcap/Thumbnail height
local vidcap_height=$th_height
if ! is_number "$vidcap_height" || [ "$vidcap_height" -eq 0 ]; then
vidcap_height=$height
fi
 
local numsecs=$(grep ID_LENGTH <<<"$mplayer_cache"| cut -d'=' -f2 | cut -d. -f1)
if ! is_number $numsecs ; then
error "Internal error!"
return 15
fi
 
local nc=$numcaps
local in=$interval
 
# Start bound:
local startsec=0
if [ $startsec -lt $fromtime ]; then
startsec=$fromtime
fi
 
# End bound:
local endsec=$numsecs
if [ $totime -ne -1 ] && [ $endsec -gt $totime ]; then
endsec=$totime
fi
if [ $startsec -ne 0 ] || [ $endsec -ne $numsecs ]; then
echo "Restricting to range [$startsec..$endsec]" >&2
fi
 
local delta=$(( $endsec - $startsec ))
# FIXME: the total # of caps is currently broken when using -f and -t
 
# Note that when numcaps mandates the interval is obtained from
# the actually allowed secods (hence it is not movie_length / numcaps )
# Adjust interval/numcaps:
if [ "$derive_from" -eq "$INTERVAL" ]; then
# Interval rules => it doesn't change
nc=$(( $delta / $interval ))
# If a multiple, an extra vidcap is generated (at the last second)
if [ $(( $delta % $interval )) -eq 0 ]; then
nc=$(( $nc + 1 ))
fi
elif [ "$derive_from" -eq "$NUMCAPS" ]; then
# Numcaps rules => it doesn't change
if [ $numcaps -eq 1 ]; then # If just one cap, center it
in=$(( $numsecs / 2 ))
else
in=$(( $numsecs / $numcaps ))
fi
else
error "Internal error!"
return 145
fi
 
# Let's try to make some sense...
# Minimum interval allowance:
if [ $in -gt $numsecs ]; then
error "The interval is longer than the video length."
error "Use a lower interval or numcaps instead."
error "Skipping \"$f\"."
return 16
fi
# Contact sheet minimum cols:
if [ $nc -lt $numcols ]; then
numcols=$nc
fi
 
# Tempdir
 
local dir=$(mktemp -d -p . vcs.XXXXXX)
if [ "$?" -ne 0 ]; then
error "Error creating temporary directory"
return 17
fi
 
# Get the stamps...
 
local n=$(( $startsec + $in ))
local stamps=( ${initial_stamps[*]} $n )
 
while [ $n -le $endsec ]; do
n=$(( $n + $in ))
if [ $n -gt $endsec ]; then break; fi
 
stamps=( ${stamps[*]} $n )
done
 
n=1
local p=""
local cap=""
local montage_command="montage -font $FONT_STAMPS -pointsize $PS_STAMPS \
-gravity SouthEast -fill white "
local output=$(tempfile --prefix "vcs-" --suffix '-preview.png' -d .)
 
# Let's reorder the stamps, this away user-added stamps get their correct position
local temp=""
for stamp in ${stamps[*]} ; do
temp+="$stamp\n"
done
stamps=$(echo -e $temp | sort -n)
 
local VIDCAPFILE=00000001.png
 
# If the temporal vidcap already exists, abort
if [ -f $VIDCAPFILE ]; then
error "Temporal vidcap file ($VIDCAPFILE) exists, remove it before running!."
exit 134
fi
 
# TODO: Aspect ratio
for stamp in $stamps; do
# Note that it must be checked against numsecs and not endsec, to allow
# the user manually setting stamps beyond the boundaries
if [ $stamp -gt $numsecs ]; then continue; fi
 
echo "Generating capture #${n}/${#stamps[*]}..." >&2
 
if [ $METHOD -eq $METHOD_MPLAYER ]; then
mplayer -sws 9 -ao null -benchmark -vo "png:z=0" -quiet \
-frames 1 -ss $stamp $shoehorned "$f" >/dev/null 2>&1
elif [ $METHOD -eq $METHOD_FFMPEG ]; then
ffmpeg -y -ss $stamp -i "$f" -an -dframes 1 -vframes 1 -vcodec png \
-f rawvideo $shoehorned $VIDCAPFILE >/dev/null 2>&1
else
error "Internal error!"
return 142
fi
 
p=$(pad 6 $stamp).png
# mv 00000001.png $dir/$p
n=$(( $n + 1 ))
 
cap=$dir/$p
# Add the timestamp to each vidcap, doing it hear is much powerful/simple
# than with the next montage command
convert -box '#000000aa' \
-fill $FG_STAMPS -pointsize $PS_STAMPS -gravity SouthEast \
-stroke none -strokewidth 3 -annotate +5+5 " $(pretty_stamp $stamp) " \
$VIDCAPFILE "$cap"
 
montage_command+=" $cap"
done
rm -f $VIDCAPFILE
 
# geometry affects the source images, not the target one!
# Note the file name could also be added by using the "-title" option, but I reserved
# it for used set titles
montage_command+=" -geometry x${vidcap_height}+10+5 -tile ${numcols}x -shadow"
if [ "$title" ]; then
montage_command+=" -font $FONT_META -fill $FG_META -title '$title'"
fi
montage_command+=" $output"
 
echo "Composing contact sheet..." >&2
eval $montage_command # eval is required to evaluate correctly the text in quotes!
mv "$output" . 2>/dev/null || true
 
# Codec "prettyfication"
case $( tr '[A-Z]' '[a-z]' <<<$vcodec) in
xvid) vcodec=Xvid ;;
dx50) vcodec="DivX 5" ;;
0x10000001) vcodec="MPEG-1" ;;
0x10000002) vcodec="MPEG-2" ;;
avc1) vcodec="MPEG-4 AVC" ;;
esac
if [ "$vdec" == "ffodivx" ]; then
vcodec+=" (MPEG-4)"
elif [ "$vdec" == "ffh264" ]; then
vcodec+=" (h.264)"
fi
 
case $( tr '[A-Z]' '[a-z]' <<<$acodec ) in
85) acodec='MPEG-1 Layer III (MP3)' ;;
80) acodec='MPEG-1 Layer II (MP2)' ;;
mp4a) acodec='MPEG-4 AAC' ;;
"") acodec="no audio" ;;
esac
 
 
local meta="Filename: $f
File size: $(get_pretty_size "$f")
Length: $(pretty_stamp "$numsecs")"
local meta2="Dimensions: ${width}x${height}
Format: $vcodec / $acodec
FPS: $fps"
local signature="$USER_SIGNATURE $user
$PROGRAM_SIGNATURE"
 
# Now let's add meta info
# This one enlarges the image to add the text, and puts
# meta info in two columns
convert -font $FONT_META -pointsize $PS_META \
-background $BG_META -fill $FG_META -splice 0x$(( $PS_META * 4 )) \
-gravity NorthWest -draw "text 10,10 '$meta'" \
-gravity NorthEast -draw "text 10,10 '$meta2'" \
"$output" "$output"
# Finishing touch, signature
 
convert -gravity South -font $FONT_SIGN -pointsize $PS_SIGN \
-background $BG_SIGN -splice 0x34+0-0 \
-fill $FG_SIGN -draw "text 10,3 '$signature'" "$output" "$output"
rm -rf $dir/
 
if [ $OUTFMT != "png" ]; then
local newout="$(basename "$output" .png).$OUTFMT"
convert -quality $OUTPUT_QUALITY "$output" "$newout"
rm "$output"
output="$newout"
fi
safe_rename "$output" "$(basename "$f").$OUTFMT"
}
 
show_help() {
local P=$(basename $0)
cat <<EOF
Video Contact Sheet *NIX v${VERSION}, (c) 2007 Toni Corvera
 
Usage: $P [options] <file>
 
Options:
-i|--interval <arg> Set the interval to arg. An optional unit can be
used (case-insensitive) , e.g.:
Seconds: 90
Minutes: 3m
Hours: 1h
Use either -i or -n.
-n|--numcaps <arg> Set the number of captured images to arg. Use either
-i or -n.
-f|--from <arg> Set starting time. No caps before this. Same format
as -i.
-t|--to <arg> Set ending time. No caps beyond this. Same format
as -i.
-T|--title <arg> Add a title above the vidcaps.
-u|--user <arg> Set the username found in the signature to this.
-S|--stamp <arg> Add the image found at the timestamp "arg", same format
as -i.
-H|--height <arg> Set the output (individual thumbnail) height. Width is
derived accordingly. Note width cannot be manually set.
-j|--jpeg Output in jpeg (by default output is in png).
-h|--help Show this text.
 
Options used for debugging purposes or to tweak the internal workings:
-M|--mplayer Force the usage of mplayer.
-F|--ffmpeg Force the usage of ffmpeg.
--shoehorn <arg> Pass "arg" to mplayer/ffmpeg. You probably don't need it.
 
Examples:
Create a contact sheet with default values (vidcaps at intervals of
$DEFAULT_INTERVAL seconds), the resulting file will be called
input.avi.png:
\$ $P input.avi
 
Create a sheet with vidcaps at intervals of 3 minutes:
\$ $P -i 3m input.avi
 
Create a sheet with vidcaps starting at 3 mins and ending at 18 mins,
add an extra vidcap at 2m and another one at 19m:
\$ $P -f 3m -t 18m -S2m -S 19m input.avi
 
Quirks:
Currently MPEG handling seems to be broken.
 
EOF
}
 
# }}} # Core functionality
 
# Test requirements
test_programs || exit 54
 
# {{{ # Command line parsing
 
# Based on getopt-parse.bash example.
# On debian systems see </usr/share/doc/util-linux/examples/getopt-parse.bash.gz>
 
# FIXME: use username / no name at all with -u noarg, and not -u
TEMP=$(getopt -o i:n:u:T:f:t:S:jhFMH:c: \
--long "interval:,numcaps:,username:,title:,from:,to:,stamp:,jpeg,help,shoehorn:,mplayer,ffmpeg,height:,columns:" \
-n $0 -- "$@")
 
eval set -- "$TEMP"
 
while true ; do
case "$1" in
-i|--interval)
if ! interval=$(get_interval "$2") ; then
error "Interval must be a number (given $2)"
exit 68
fi
if [ "$interval" -le 0 ]; then
error "Interval must be higher than 0, set to the default $DEFAULT_INTERVAL"
interval=$DEFAULT_INTERVAL
fi
derive_from=$INTERVAL
shift # Option arg
;;
-n|--numcaps)
if ! is_number "$2" ; then
error "Number of caps must be a number! (given $2)"
exit 68
fi
numcaps="$2"
derive_from=$NUMCAPS
shift # Option arg
;;
-u|--username) user="$2" ; shift ;;
-T|--title) title="$2" ; shift ;;
-f|--from)
if ! fromtime=$(get_interval "$2") ; then
error "Starting timestamp must be a valid interval"
exit 68
fi
shift
;;
-t|--to)
if ! totime=$(get_interval "$2") ; then
error "Ending timestamp must be a valid interval"
exit 68
fi
if [ "$totime" -eq 0 ]; then
error "Ending timestamp was set to 0, set to movie length"
totime=-1
fi
shift
;;
-S|--stamp)
if ! temp=$(get_interval "$2") ; then
error "Timestamps must be a valid interval"
exit 68
fi
initial_stamps=( ${initial_stamps[*]} $temp )
shift
;;
-j|--jpeg) OUTFMT=jpg ;;
-h|--help) show_help ; exit 0 ;;
--shoehorn)
shoehorned="$2"
shift
;;
-F) METHOD=$METHOD_FFMPEG ;;
-M) METHOD=$METHOD_MPLAYER ;;
-H|--height)
if ! is_number "$2" ; then
error "Height must be a number (given $2)"
exit 68
fi
th_height="$2"
shift
;;
-c|--columns)
if ! is_number "$2" ; then
error "Columns must be a number (given $2)"
exit 68
fi
cols="$2"
shift
;;
--) shift ; break ;;
*) error "Internal error! (remaining opts: $@)" ; exit 76 ;
esac
shift
done
 
# Remaining arguments
if [ ! "$1" ]; then
show_help
exit 67
fi
for arg do process "$arg" ; done
 
# }}} # Command line parsing
 
 
# vim:set ts=4 ai: #
Property changes:
Added: svn:executable
Added: svn:keywords
+Rev Id Date
\ No newline at end of property
/video-contact-sheet/tags/1.0a
Property changes:
Added: svn:mergeinfo
Merged /video-contact-sheet/branches/1.0a:r262-263
Merged /video-contact-sheet/tags/0.99a:r261
/video-contact-sheet/tags/0.99a/vcs
0,0 → 1,526
#!/bin/bash
 
# $Rev$ $Date$
 
declare -r VERSION="0.99a"
 
# Options
declare -i interval=300 # Interval of captures (= numsecs / numcaps)
declare -i numcaps=16 # Number of captures (= numsecs / interval)
 
declare user=$(id -un)
declare -r USER_SIGNATURE="Preview created by"
declare -r PROGRAM_SIGNATURE="with Video Contact Sheet *NIX ${VERSION}"
 
# Options used in imagemagick, these options set the final aspect
# of the contact sheet so tweak them to your liking
#
declare -i OUTPUT_QUALITY=92 # Output image quality (only affects the final image,
# and only in lossy formats)
# Colours, see convert -list color
declare -r BG_META=YellowGreen # Background for meta info (size, codec...)
declare -r BG_SIGN=SandyBrown # Background for signature
declare -r FG_META=black # Font colour for meta info box
declare -r FG_SIGN=black # Font colour for signature
declare -r FG_STAMPS=white # Font colour for timestamps
# Fonts, see convert -list type
declare -r FONT_STAMPS=courier # Used for timestamps behind the thumbnails
declare -r FONT_META=helvetica # Used for meta info box
declare -r FONT_SIGN=$FONT_META # Used for the signature box
# Font sizes, in points
declare -i PS_STAMPS=18 # Used for the timestamps
declare -i PS_META=16 # Used for the meta info box
declare -i PS_SIGN=11 # Used for the signature
 
# Constants and other internal usage variables, no need to mess with this!
# Which of the two methods should be used to guess the number of thumbnails
declare -ri INTERVAL=1 NUMCAPS=3
declare -i derive_from=$INTERVAL
declare -ri DEFAULT_INTERVAL=$interval
 
# Internal variables:
declare title=""
declare -i fromtime=0 # Starting second (see -f)
declare -i totime=-1 # Ending second (see -t)
declare initial_stamps=( ) # Stamps added to the calculated ones (see -S)
declare OUTFMT=png
 
# {{{ # Convenience functions
 
is_number() {
egrep -q '^[0-9]+$' <<<"$1"
return $?
}
 
get_interval() {
if is_number "$1" ; then echo $1 ; return 0 ; fi
 
local s=$(tr '[A-Z]' '[a-z]' <<<"$1")
local len_n=$(( ${#s} - 1 )) # Length of the theoretical numeric part
local u=${s:$len_n}
local n=${s:0:$len_n}
local i=0
 
if ! is_number "$n" ; then return 1 ; fi
 
case "$u" in
s) i=$n ;;
m) i=$((n * 60)) ;;
h) i=$((n * 3600)) ;;
*) return 2 ; break ;;
esac
 
echo $i
}
 
pad() {
local len=$1
local str=$2
 
while [ ${#str} -lt $len ]; do
str=0$str
done
 
echo $str
}
 
pretty_stamp() {
if ! is_number "$1" ; then return 1 ; fi
 
local t=$1
local h=$(( $t / 3600 ))
t=$(( $t % 3600 ))
local m=$(( $t / 60 ))
t=$(( $t % 60 ))
local s=$t
 
local R=""
 
if [ $h -gt 0 ]; then
R+="$h:"
fi
R+=$(pad 2 "$m"):$(pad 2 $s)
 
echo $R
}
 
get_pretty_size() {
local f="$1"
 
local bytes=$(du -DL --bytes "$f" | cut -f1)
local size=""
 
if [ "$bytes" -gt $(( 1024**3 )) ]; then
local gibs=$(( $bytes / 1024**3 ))
local mibs=$(( ( $bytes % 1024**3 ) / 1024**2 ))
size="${gibs}.${mibs:0:2} GiB"
elif [ "$bytes" -gt $(( 1024**2)) ]; then
local mibs=$(( $bytes / 1024**2 ))
local kibs=$(( ( $bytes % 1024**2 ) / 1024 ))
size="${mibs}.${kibs:0:2} MiB"
elif [ "$bytes" -gt 1024 ]; then
local kibs=$(( $bytes / 1024 ))
bytes=$(( $bytes % 1024 ))
size="${kibs}.${bytes:0:2} KiB"
else
size="${bytes} B"
fi
 
echo $size
}
 
# Rename a file, if the target exists, try with -1, -2, ...
safe_rename() {
local from="$1"
local to="$2"
 
local ext=$(sed -r 's/.*\.(.*)/\1/g' <<<"$to")
 
local n=1
while [ -f "$to" ]; do
to="$(basename "$2" .$ext)-$n.$ext"
n=$(( $n + 1))
done
 
mv "$from" "$to"
}
 
test_programs() {
for prog in mplayer convert montage ffmpeg ; do
type -pf "$prog" >/dev/null
local retval=$?
if [ $retval -ne 0 ] ; then
error "Required program $prog not found!"
return $retval
fi
done
}
 
# Print some text to stderr
error() {
echo "$1" >&2
}
 
# }}} # Convenience functions
 
# {{{ # Core functionality
 
process() {
local f=$1
 
local COLS=2
 
if [ ! -f "$f" ]; then
error "File \"$f\" doesn't exist"
return 100
fi
echo "Processing $f..." >&2
 
# Meta data extraction
# Don't change the -vc as it would affect $vdec
local mplayer_cache=$(mplayer -benchmark -ao null -vo null -identify -frames 0 -quiet "$f" 2>/dev/null | grep ^ID)
local vcodec=$(grep ID_VIDEO_FORMAT <<<"$mplayer_cache" | cut -d'=' -f2) # FourCC
local acodec=$(grep ID_AUDIO_FORMAT <<<"$mplayer_cache" | cut -d'=' -f2)
local vdec=$(grep ID_VIDEO_CODEC <<<"$mplayer_cache" | cut -d'=' -f2) # Decoder
local width=$(grep ID_VIDEO_WIDTH <<<"$mplayer_cache" | cut -d'=' -f2)
local height=$(grep ID_VIDEO_HEIGHT <<<"$mplayer_cache" | cut -d'=' -f2)
local fps=$(grep ID_VIDEO_FPS <<<"$mplayer_cache" | cut -d'=' -f2)
# Vidcap/Thumbnail width TODO: allow user interaction
local th_width=$width
 
local numsecs=$(grep ID_LENGTH <<<"$mplayer_cache"| cut -d'=' -f2 | cut -d. -f1)
if ! is_number $numsecs ; then
error "Internal error!"
return 15
fi
 
local nc=$numcaps
local in=$interval
 
# Start bound:
local startsec=0
if [ $startsec -lt $fromtime ]; then
startsec=$fromtime
fi
 
# End bound:
local endsec=$numsecs
if [ $totime -ne -1 ] && [ $endsec -gt $totime ]; then
endsec=$totime
fi
 
local delta=$(( $endsec - $startsec ))
# FIXME: the total # of caps is currently broken when using -f and -t
 
# Note that when numcaps mandates the interval is obtained from
# the actually allowed secods (hence it is not movie_length / numcaps )
# Adjust interval/numcaps:
if [ "$derive_from" -eq "$INTERVAL" ]; then
# Interval rules => it doesn't change
nc=$(( $delta / $interval ))
# If a multiple, an extra vidcap is generated (at the last second)
if [ $(( $delta % $interval )) -eq 0 ]; then
nc=$(( $nc + 1 ))
fi
elif [ "$derive_from" -eq "$NUMCAPS" ]; then
# Numcaps rules => it doesn't change
in=$(( $numsecs / $numcaps ))
else
error "Internal error!"
return 145
fi
 
# Let's try to make some sense...
# Minimum interval allowance:
if [ $in -gt $numsecs ]; then
error "The interval is longer than the video length."
error "Use a lower interval or numcaps instead."
error "Skipping \"$f\"."
return 16
fi
# Contact sheet minimum cols:
if [ $nc -lt $COLS ]; then
COLS=$nc
fi
 
# Tempdir
 
local dir=$(mktemp -d -p . vcs.XXXXXX)
if [ "$?" -ne 0 ]; then
error "Error creating temporary directory"
return 17
fi
 
# Get the stamps...
 
local n=$(( $startsec + $in ))
local stamps=( ${initial_stamps[*]} $n )
while [ $n -le $endsec ]; do
n=$(( $n + $in ))
if [ $n -gt $endsec ]; then break; fi
 
stamps=( ${stamps[*]} $n )
done
 
n=1
local p=""
local cap=""
local montage_command="montage -font $FONT_STAMPS -pointsize $PS_STAMPS \
-gravity SouthEast -fill white "
local output=$(tempfile --prefix "vcs-" --suffix '-preview.png' -d .)
 
# Let's reorder the stamps, this away user-added stamps get their correct position
local temp=""
for stamp in ${stamps[*]} ; do
temp+="$stamp\n"
done
stamps=$(echo -e $temp | sort -n)
 
METHOD=2
 
local VIDCAPFILE=00000001.png
 
# If the temporal vidcap already exists, abort
if [ -f $VIDCAPFILE ]; then
error "Temporal vidcap file ($VIDCAPFILE) exists, remove it before running!."
exit 134
fi
 
# TODO: Aspect ratio
for stamp in $stamps; do
# Note that it must be checked against numsecs and not endsec, to allow
# the user manually setting stamps beyond the boundaries
if [ $stamp -gt $numsecs ]; then continue; fi
 
echo "Generating capture #${n}/${#stamps[*]}..." >&2
 
if [ $METHOD -eq 1 ]; then
mplayer -sws 9 -ao null -benchmark -vo "png:z=0" -quiet \
-frames 1 -ss $stamp "$f" >/dev/null 2>&1
elif [ $METHOD -eq 2 ]; then
ffmpeg -y -ss $stamp -i "$f" -an -dframes 1 -vframes 1 -vcodec png \
-f rawvideo $VIDCAPFILE >/dev/null 2>&1
else
error "Internal error!"
return 142
fi
 
p=$(pad 6 $stamp).png
# mv 00000001.png $dir/$p
n=$(( $n + 1 ))
 
cap=$dir/$p
# Add the timestamp to each vidcap, doing it hear is much powerful/simple
# than with the next montage command
convert -box '#000000aa' \
-fill $FG_STAMPS -pointsize $PS_STAMPS -gravity SouthEast \
-stroke none -strokewidth 3 -annotate +5+5 " $(pretty_stamp $stamp) " \
$VIDCAPFILE "$cap"
 
montage_command+=" $cap"
done
rm -f $VIDCAPFILE
 
# geometry affects the source images, not the target one!
# Note the file name could also be added by using the "-title" option, but I reserved
# it for used set titles
montage_command+=" -geometry ${th_width}x+10+5 -tile ${COLS}x -shadow"
if [ "$title" ]; then
montage_command+=" -font $FONT_META -fill $FG_META -title '$title'"
fi
montage_command+=" $output"
 
echo "Composing contact sheet..." >&2
eval $montage_command # eval is required to evaluate correctly the text in quotes!
mv "$output" . 2>/dev/null || true
 
# Codec "prettyfication"
case $( tr '[A-Z]' '[a-z]' <<<$vcodec) in
xvid) vcodec=Xvid ;;
dx50) vcodec="DivX 5" ;;
0x10000001) vcodec="MPEG-1" ;;
0x10000002) vcodec="MPEG-2" ;;
avc1) vcodec="MPEG-4 AVC" ;;
esac
if [ "$vdec" == "ffodivx" ]; then
vcodec+=" (MPEG-4)"
elif [ "$vdec" == "ffh264" ]; then
vcodec+=" (h.264)"
fi
 
case $( tr '[A-Z]' '[a-z]' <<<$acodec ) in
85) acodec='MPEG-1 Layer III (MP3)' ;;
80) acodec='MPEG-1 Layer II (MP2)' ;;
mp4a) acodec='MPEG-4 AAC' ;;
"") acodec="no audio" ;;
esac
 
 
local meta="Filename: $f
File size: $(get_pretty_size "$f")
Length: $(pretty_stamp "$numsecs")"
local meta2="Dimensions: ${width}x${height}
Format: $vcodec / $acodec
FPS: $fps"
local signature="$USER_SIGNATURE $user
$PROGRAM_SIGNATURE"
 
# Now let's add meta info
# This one enlarges the image to add the text, and puts
# meta info in two columns
convert -font $FONT_META -pointsize $PS_META \
-background $BG_META -fill $FG_META -splice 0x$(( $PS_META * 4 )) \
-gravity NorthWest -draw "text 10,10 '$meta'" \
-gravity NorthEast -draw "text 10,10 '$meta2'" \
"$output" "$output"
# Finishing touch, signature
 
convert -gravity South -font $FONT_SIGN -pointsize $PS_SIGN \
-background $BG_SIGN -splice 0x34+0-0 \
-fill $FG_SIGN -draw "text 10,3 '$signature'" "$output" "$output"
rm -rf $dir/
 
if [ $OUTFMT != "png" ]; then
local newout="$(basename "$output" .png).$OUTFMT"
convert -quality $OUTPUT_QUALITY "$output" "$newout"
rm "$output"
output="$newout"
fi
safe_rename "$output" "$(basename "$f").$OUTFMT"
}
 
show_help() {
local P=$(basename $0)
cat <<EOF
Video Contact Sheet *NIX v${VERSION}, (c) 2007 Toni Corvera
 
Usage: $P [options] <file>
 
Options:
-i|--interval <arg> Set the interval to arg. An optional unit can be
used (case-insensitive) , e.g.:
Seconds: 90
Minutes: 3m
Hours: 1h
Use either -i or -n.
-n|--numcaps <arg> Set the number of captured images to arg. Use either
-i or -n.
-f|--from <arg> Set starting time. No caps before this. Same format
as -i.
-t|--to <arg> Set ending time. No caps beyond this. Same format
as -i.
-T|--title <arg> Add a title above the vidcaps.
-u|--user <arg> Set the username found in the signature to this.
-S|--stamp <arg> Add the image found at the timestamp "arg", same format
as -i.
-j|--jpeg Output in jpeg (by default output is in png).
-h|--help Show this text.
 
Examples:
Create a contact sheet with default values (vidcaps at intervals of
$DEFAULT_INTERVAL seconds), the resulting file will be called
input.avi.png:
\$ $P input.avi
 
Create a sheet with vidcaps at intervals of 3 minutes:
\$ $P -i 3m input.avi
 
Create a sheet with vidcaps starting at 3 mins and ending at 18 mins,
add an extra vidcap at 2m and another one at 19m:
\$ $P -f 3m -t 18m -S2m -S 19m input.avi
 
Quirks:
Currently MPEG handling seems to be broken.
 
EOF
}
 
# }}} # Core functionality
 
# Test requirements
test_programs || exit 54
 
# {{{ # Command line parsing
 
# Based on getopt-parse.bash example.
# On debian systems see </usr/share/doc/util-linux/examples/getopt-parse.bash.gz>
 
# FIXME: use username / no name at all with -u noarg, and not -u
TEMP=$(getopt -o i:n:u:T:f:t:S:jh \
--long "interval:,numcaps:,username:,title:,from:,to:,stamp:,jpeg,help" \
-n $0 -- "$@")
 
eval set -- "$TEMP"
 
while true ; do
case "$1" in
-i|--interval)
if ! interval=$(get_interval "$2") ; then
error "Interval must be a number (given $2)"
exit 68
fi
if [ "$interval" -le 0 ]; then
error "Interval must be higher than 0, set to the default $DEFAULT_INTERVAL"
interval=$DEFAULT_INTERVAL
fi
derive_from=$INTERVAL
shift # Option arg
;;
-n|--numcaps)
if ! is_number "$2" ; then
error "Number of caps must be a number! (given $2)"
exit 68
fi
numcaps="$2"
derive_from=$NUMCAPS
shift # Option arg
;;
-u|--username) user="$2" ; shift ;;
-T|--title) title="$2" ; shift ;;
-f|--from)
if ! fromtime=$(get_interval "$2") ; then
error "Starting timestamp must be a valid interval"
exit 68
fi
shift
;;
-t|--to)
if ! totime=$(get_interval "$2") ; then
error "Ending timestamp must be a valid interval"
exit 68
fi
if [ "$totime" -eq 0 ]; then
error "Ending timestamp was set to 0, set to movie length"
totime=-1
fi
shift
;;
-S|--stamp)
if ! temp=$(get_interval "$2") ; then
error "Timestamps must be a valid interval"
exit 68
fi
initial_stamps=( ${initial_stamps[*]} $temp )
shift
;;
-j|--jpeg) OUTFMT=jpg ;;
-h|--help) show_help ; exit 0 ;;
--) shift ; break ;;
*) error "Internal error!" ; exit 76 ;
esac
shift
done
 
# Remaining arguments
if [ ! "$1" ]; then
show_help
exit 67
fi
for arg do process "$arg" ; done
 
# }}} # Command line parsing
 
 
# vim:set ts=4 ai: #
Property changes:
Added: svn:executable
Added: svn:keywords
+Rev Id Date
\ No newline at end of property
/video-contact-sheet/tags/1.0.2b/vcs
0,0 → 1,779
#!/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>
#
 
declare -r VERSION="1.0.2b"
#
# History:
#
# 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
# * 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
#
# 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
 
set -e
 
# 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
#
# The variables that can be overriden are below the block of constants ahead.
declare -r CFGFILE=~/.vcs.conf
 
# Constants {{{
# 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="with Video Contact Sheet *NIX ${VERSION} <http://p.outlyer.net/vcs/>"
 
# }}} # 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
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
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 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
# Fonts, see convert -list type to get the list
declare font_tstamps=courier # Used for timestamps behind the thumbnails
declare font_heading=helvetica # Used for meta info box
declare font_sign=$font_heading # Used for the signature box
# Font sizes, in points
declare pts_tstamps=18 # Used for the timestamps
declare pts_meta=16 # Used for the meta info box
declare pts_sign=11 # Used for the signature
# See --shoehorn
declare shoehorned=
 
# }}} # End of override-able variables
 
# Options and other internal usage variables, no need to mess with this!
declare -i interval=$DEFAULT_INTERVAL # Interval of captures (=numsecs/numcaps)
declare -i numcaps=$DEFAULT_NUMCAPS # Number of captures (=numsecs/interval)
declare title=""
declare -i fromtime=0 # Starting second (see -f)
declare -i 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 -a TEMPSTUFF=( ) # Temporal files
 
# Exit codes, same codes 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
 
 
load_config() {
# 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'
)
 
if [ ! -f "$CFGFILE" ]; then return 0 ; fi
 
local compregex=$( sed 's/ /|/g' <<<${ALLOWED_OVERRIDES[*]} )
 
while read line ; do # auto variable $line
# Don't allow ';', FIXME: dunno how secure that really is
if ! egrep -q '^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]*=[^;]*' <<<"$line" ; then
continue
fi
if ! egrep -q "^($compregex)=" <<<"$line" ; then
continue
fi
# FIXME: Only print in verbose mose
# FIXME: Only for really overridden ones
echo "Overridden variable" $(sed -r 's/^[[:space:]]*([a-zA-Z0-9_]*)=.*/\1/'<<<$line)
eval $line
done <$CFGFILE
}
 
# {{{ # Convenience functions
 
is_number() {
egrep -q '^[0-9]+$' <<<"$1"
return $?
}
 
# The current code is a tad permissive, it allows e.g. things like
# 10m1h (equivalent to 1h10m)
# 1m1m (equivalent to 2m)
get_interval() {
if is_number "$1" ; then echo $1 ; return 0 ; fi
 
local s=$(tr '[A-Z]' '[a-z]' <<<"$1")
 
# Only allowed characters
if ! grep -q '[0-9smh]' <<<"$s"; then
return $EX_USAGE;
fi
 
# FIXME: Find some cleaner way
local i= c= num= sum=0
for i in $(seq 0 $(( ${#s} - 1)) ); do
c=${s:$i:1}
if is_number $c ; then
num+=$c
else
case $c in
h) num=$(($num * 3600)) ;;
m) num=$(($num * 60)) ;;
s) ;;
*)
return $EX_SOFTWARE
;;
esac
sum=$(($sum + $num))
num=
fi
done
 
# If last element was a number, it's seconds and they weren't added
if is_number $c ; then
sum=$(( $sum + $num ))
fi
 
echo $sum
 
return 0
}
 
pad() {
local len=$1
local str=$2
 
while [ ${#str} -lt $len ]; do
str=0$str
done
 
echo $str
}
 
pretty_stamp() {
if ! is_number "$1" ; then return $EX_USAGE ; fi
 
local t=$1
local h=$(( $t / 3600 ))
t=$(( $t % 3600 ))
local m=$(( $t / 60 ))
t=$(( $t % 60 ))
local s=$t
 
local R=""
 
if [ $h -gt 0 ]; then
R+="$h:"
fi
R+=$(pad 2 "$m"):$(pad 2 $s)
 
echo $R
}
 
get_pretty_size() {
local f="$1"
 
local bytes=$(du -DL --bytes "$f" | cut -f1)
local size=""
 
if [ "$bytes" -gt $(( 1024**3 )) ]; then
local gibs=$(( $bytes / 1024**3 ))
local mibs=$(( ( $bytes % 1024**3 ) / 1024**2 ))
size="${gibs}.${mibs:0:2} GiB"
elif [ "$bytes" -gt $(( 1024**2)) ]; then
local mibs=$(( $bytes / 1024**2 ))
local kibs=$(( ( $bytes % 1024**2 ) / 1024 ))
size="${mibs}.${kibs:0:2} MiB"
elif [ "$bytes" -gt 1024 ]; then
local kibs=$(( $bytes / 1024 ))
bytes=$(( $bytes % 1024 ))
size="${kibs}.${bytes:0:2} KiB"
else
size="${bytes} B"
fi
 
echo $size
}
 
# Rename a file, if the target exists, try with -1, -2, ...
safe_rename() {
local from="$1"
local to="$2"
 
local ext=$(sed -r 's/.*\.(.*)/\1/g' <<<"$to")
 
local n=1
while [ -f "$to" ]; do
to="$(basename "$2" .$ext)-$n.$ext"
n=$(( $n + 1))
done
 
mv "$from" "$to"
echo "$to" >&2
}
 
test_programs() {
for prog in mplayer convert montage ffmpeg ; do
type -pf "$prog" >/dev/null
local retval=$?
if [ $retval -ne 0 ] ; then
error "Required program $prog not found!"
return $retval
fi
done
}
 
cleanup() {
if [ -z $TEMPSTUFF ]; then return 0 ; fi
echo "Cleaning up..." >&2 # TODO: Only in verbose mode
rm -rf ${TEMPSTUFF[*]}
TEMPSTUFF=( )
}
 
exithdlr() {
cleanup
}
 
# Print some text to stderr
error() {
echo "$1" >&2
}
 
# }}} # Convenience functions
 
# {{{ # Core functionality
 
process() {
local f=$1
 
local numcols=$cols
 
if [ ! -f "$f" ]; then
error "File \"$f\" doesn't exist"
return $EX_NOINPUT
fi
echo "Processing $f..." >&2
 
# Meta data extraction
# Don't change the -vc as it would affect $vdec
local mplayer_cache=$(mplayer -benchmark -ao null -vo null -identify -frames 0 -quiet "$f" 2>/dev/null | grep ^ID)
local vcodec=$(grep ID_VIDEO_FORMAT <<<"$mplayer_cache" | cut -d'=' -f2) # FourCC
local acodec=$(grep ID_AUDIO_FORMAT <<<"$mplayer_cache" | cut -d'=' -f2)
local vdec=$(grep ID_VIDEO_CODEC <<<"$mplayer_cache" | cut -d'=' -f2) # Decoder
local width=$(grep ID_VIDEO_WIDTH <<<"$mplayer_cache" | cut -d'=' -f2)
local height=$(grep ID_VIDEO_HEIGHT <<<"$mplayer_cache" | cut -d'=' -f2)
local fps=$(grep ID_VIDEO_FPS <<<"$mplayer_cache" | cut -d'=' -f2)
# Vidcap/Thumbnail height
local vidcap_height=$th_height
if ! is_number "$vidcap_height" || [ "$vidcap_height" -eq 0 ]; then
vidcap_height=$height
fi
 
local numsecs=$(grep ID_LENGTH <<<"$mplayer_cache"| cut -d'=' -f2 | cut -d. -f1)
if ! is_number $numsecs ; then
error "Internal error!"
return $EX_SOFTWARE
fi
 
local nc=$numcaps
local in=$interval
 
# Start bound:
local startsec=0
if [ $startsec -lt $fromtime ]; then
startsec=$fromtime
fi
 
# End bound:
local endsec=$numsecs
if [ $totime -ne -1 ] && [ $endsec -gt $totime ]; then
endsec=$totime
fi
if [ $startsec -ne 0 ] || [ $endsec -ne $numsecs ]; then
echo "Restricting to range [$startsec..$endsec]" >&2
fi
 
local delta=$(( $endsec - $startsec ))
# FIXME: the total # of caps is currently broken when using -f and -t
 
# Note that when numcaps mandates the interval is obtained from
# the actually allowed secods (hence it is not movie_length / numcaps )
# Adjust interval/numcaps:
if [ "$timecode_from" -eq "$TC_INTERVAL" ]; then
# Interval rules => it doesn't change
nc=$(( $delta / $interval ))
# If a multiple, an extra vidcap is generated (at the last second)
if [ $(( $delta % $interval )) -eq 0 ]; then
nc=$(( $nc + 1 ))
fi
elif [ "$timecode_from" -eq "$TC_NUMCAPS" ]; then
# Numcaps rules => it doesn't change
if [ $numcaps -eq 1 ]; then # If just one cap, center it
in=$(( $numsecs / 2 ))
else
in=$(( $numsecs / $numcaps ))
fi
else
error "Internal error!"
return $EX_SOFTWARE
fi
 
# Let's try to make some sense...
# Minimum interval allowance:
if [ $in -gt $numsecs ]; then
error "The interval is longer than the video length."
error "Use a lower interval or numcaps instead."
error "Skipping \"$f\"."
return $EX_USAGE
fi
# Contact sheet minimum cols:
if [ $nc -lt $numcols ]; then
numcols=$nc
fi
 
# Tempdir
 
local dir=$(mktemp -d -p . vcs.XXXXXX)
if [ "$?" -ne 0 ]; then
error "Error creating temporary directory"
return $EX_CANTCREAT
fi
TEMPSTUFF+=( "$dir" )
 
local n=
 
# Get the stamps (if in auto mode)...
 
local stamps=( )
if [ $manual_mode -ne 1 ]; then
 
n=$(( $startsec + $in ))
stamps=( ${initial_stamps[*]} $n )
 
while [ $n -le $endsec ]; do
n=$(( $n + $in ))
if [ $n -gt $endsec ]; then break; fi
 
stamps=( ${stamps[*]} $n )
done
else
stamps=( ${initial_stamps[*]} )
fi
 
n=1
local p=""
local cap=""
local montage_command="montage -font $font_tstamps -pointsize $pts_tstamps \
-gravity SouthEast -fill white "
local output=$(tempfile --prefix "vcs-" --suffix '-preview.png' -d .)
TEMPSTUFF+=( "$output" )
 
# Let's reorder the stamps, this away user-added stamps get their correct
# position also remove duplicates. Note AFAIK sort only sorts lines, that's
# why y replace spaces by newlines.
#
# Note that stamps keeps being an array and stamps[1..N] still hold their
# old values, although it's treated as a string after this
stamps=$( sed 's/ /\n/g' <<<"${stamps[*]}" | sort -n | uniq )
 
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
 
local NUMSTAMPS=$(wc -w <<<"$stamps")
TEMPSTUFF+=( $VIDCAPFILE )
# TODO: Aspect ratio
for stamp in $stamps; do
# Note that it must be checked against numsecs and not endsec, to allow
# the user manually setting stamps beyond the boundaries
if [ $stamp -gt $numsecs ]; then continue; fi
 
echo "Generating capture #${n}/${NUMSTAMPS}..." >&2
 
if [ $decoder -eq $DEC_MPLAYER ]; then
mplayer -sws 9 -ao null -benchmark -vo "png:z=0" -quiet \
-frames 1 -ss $stamp $shoehorned "$f" >/dev/null 2>&1
elif [ $decoder -eq $DEC_FFMPEG ]; then
ffmpeg -y -ss $stamp -i "$f" -an -dframes 1 -vframes 1 -vcodec png \
-f rawvideo $shoehorned $VIDCAPFILE >/dev/null 2>&1
else
error "Internal error!"
return $EX_SOFTWARE
fi || {
local retval=$?
error "The capturing program failed!"
return $retval
}
 
p=$(pad 6 $stamp).png
# mv 00000001.png $dir/$p
n=$(( $n + 1 ))
 
cap=$dir/$p
# Add the timestamp to each vidcap, doing it hear is much powerful/simple
# than with the next montage command
convert -box '#000000aa' \
-fill $fg_tstamps -pointsize $pts_tstamps -gravity SouthEast \
-stroke none -strokewidth 3 -annotate +5+5 " $(pretty_stamp $stamp) " \
$VIDCAPFILE "$cap"
 
montage_command+=" $cap"
done
 
# geometry affects the source images, not the target one!
# Note the file name could also be added by using the "-title" option, but I reserved
# it for used set titles
montage_command+=" -geometry x${vidcap_height}+10+5 -tile ${numcols}x -shadow"
if [ "$title" ]; then
montage_command+=" -font $font_heading -fill $fg_heading -title '$title'"
fi
montage_command+=" $output"
 
echo "Composing contact sheet..." >&2
eval $montage_command # eval is required to evaluate correctly the text in quotes!
mv "$output" . 2>/dev/null || true
 
# Codec "prettyfication"
case $( tr '[A-Z]' '[a-z]' <<<$vcodec) in
xvid) vcodec=Xvid ;;
dx50) vcodec="DivX 5" ;;
0x10000001) vcodec="MPEG-1" ;;
0x10000002) vcodec="MPEG-2" ;;
avc1) vcodec="MPEG-4 AVC" ;;
wmv2) vcodec="WMV 8" ;; # WMV2 is v8
esac
if [ "$vdec" == "ffodivx" ]; then
vcodec+=" (MPEG-4)"
elif [ "$vdec" == "ffh264" ]; then
vcodec+=" (h.264)"
fi
 
case $( tr '[A-Z]' '[a-z]' <<<$acodec ) in
85) acodec='MPEG-1 Layer III (MP3)' ;;
80) acodec='MPEG-1 Layer II (MP2)' ;;
mp4a) acodec='MPEG-4 AAC' ;;
353) acodec='WMA 2' ;;
"") acodec="no audio" ;;
esac
 
 
local meta="Filename: $f
File size: $(get_pretty_size "$f")
Length: $(pretty_stamp "$numsecs")"
local meta2="Dimensions: ${width}x${height}
Format: $vcodec / $acodec
FPS: $fps"
local signature="$user_signature $user
$PROGRAM_SIGNATURE"
 
# Now let's add meta info
# This one enlarges the image to add the text, and puts
# meta info in two columns
convert -font $font_heading -pointsize $pts_meta \
-background $bg_heading -fill $fg_heading -splice 0x$(( $pts_meta * 4 )) \
-gravity NorthWest -draw "text 10,10 '$meta'" \
-gravity NorthEast -draw "text 10,10 '$meta2'" \
"$output" "$output"
# Finishing touch, signature
 
convert -gravity South -font $font_sign -pointsize $pts_sign \
-background $bg_sign -splice 0x34+0-0 \
-fill $fg_sign -draw "text 10,3 '$signature'" "$output" "$output"
 
if [ $output_format != "png" ]; then
local newout="$(basename "$output" .png).$output_format"
convert -quality $output_quality "$output" "$newout"
output="$newout"
fi
echo -n "Output wrote to " >&2
safe_rename "$output" "$(basename "$f").$output_format"
 
cleanup
}
 
show_help() {
local P=$(basename $0)
cat <<EOF
Video Contact Sheet *NIX v${VERSION}, (c) 2007 Toni Corvera
 
Usage: $P [options] <file>
 
Options:
-i|--interval <arg> Set the interval to arg. An optional unit can be
used (case-insensitive) , e.g.:
Seconds: 90
Minutes: 3m
Hours: 1h
Use either -i or -n.
-n|--numcaps <arg> Set the number of captured images to arg. Use either
-i or -n.
-f|--from <arg> Set starting time. No caps before this. Same format
as -i.
-t|--to <arg> Set ending time. No caps beyond this. Same format
as -i.
-T|--title <arg> Add a title above the vidcaps.
-u|--user <arg> Set the username found in the signature to this.
-S|--stamp <arg> Add the image found at the timestamp "arg", same format
as -i.
-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.
-H|--height <arg> Set the output (individual thumbnail) height. Width is
derived accordingly. Note width cannot be manually set.
-j|--jpeg Output in jpeg (by default output is in png).
-h|--help Show this text.
 
Options used for debugging purposes or to tweak the internal workings:
-M|--mplayer Force the usage of mplayer.
-F|--ffmpeg Force the usage of ffmpeg.
--shoehorn <arg> Pass "arg" to mplayer/ffmpeg. You probably don't need it.
 
Examples:
Create a contact sheet with default values (vidcaps at intervals of
$DEFAULT_INTERVAL seconds), the resulting file will be called
input.avi.png:
\$ $P input.avi
 
Create a sheet with vidcaps at intervals of 3 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
 
EOF
}
 
# }}} # Core functionality
 
#### Execution starts here ####
 
# Execute exithdlr on exit
trap exithdlr EXIT
 
# Test requirements
test_programs || exit $EX_UNAVAILABLE
 
load_config
 
# {{{ # Command line parsing
 
# Based on getopt-parse.bash example.
# On debian systems see </usr/share/doc/util-linux/examples/getopt-parse.bash.gz>
 
# FIXME: use username / no name at all with -u noarg, and not -u
TEMP=$(getopt -o i:n:u:T:f:t:S:jhFMH:c:m \
--long "interval:,numcaps:,username:,title:,from:,to:,stamp:,jpeg,help,shoehorn:,mplayer,ffmpeg,height:,columns:,manual" \
-n $0 -- "$@")
 
eval set -- "$TEMP"
 
while true ; do
case "$1" in
-i|--interval)
if ! interval=$(get_interval "$2") ; then
error "Interval must be a number (given $2)"
exit $EX_USAGE
fi
if [ "$interval" -le 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 caps must be a number! (given $2)"
exit $EX_USAGE
fi
numcaps="$2"
timecode_from=$TC_NUMCAPS
shift # Option arg
;;
-u|--username) user="$2" ; shift ;;
-T|--title) title="$2" ; shift ;;
-f|--from)
if ! fromtime=$(get_interval "$2") ; then
error "Starting timestamp must be a valid interval"
exit $EX_USAGE
fi
shift
;;
-t|--to)
if ! totime=$(get_interval "$2") ; then
error "Ending timestamp must be a valid interval"
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 interval"
exit $EX_USAGE
fi
initial_stamps=( ${initial_stamps[*]} $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 number (given $2)"
exit $EX_USAGE
fi
th_height="$2"
shift
;;
-c|--columns)
if ! is_number "$2" ; then
error "Columns must be a number (given $2)"
exit $EX_USAGE
fi
cols="$2"
shift
;;
-m|--manual) manual_mode=1 ;;
--) shift ; break ;;
*) error "Internal error! (remaining opts: $@)" ; exit $EX_SOFTWARE ;
esac
shift
done
 
# Remaining arguments
if [ ! "$1" ]; then
show_help
exit $EX_USAGE
fi
# 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
 
# }}} # Command line parsing
 
 
# vim:set ts=4 ai: #
Property changes:
Added: svn:executable
Added: svn:keywords
+Rev Id Date
\ No newline at end of property
/video-contact-sheet/tags/1.0.2b
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
/video-contact-sheet/tags/1.0.3b/vcs
0,0 → 1,782
#!/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>
#
 
declare -r VERSION="1.0.3b"
#
# History:
#
# 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
# * 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
#
# 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
 
set -e
 
# 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
#
# The variables that can be overriden are below the block of constants ahead.
declare -r CFGFILE=~/.vcs.conf
 
# Constants {{{
# 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="with Video Contact Sheet *NIX ${VERSION} <http://p.outlyer.net/vcs/>"
 
# }}} # 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
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
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 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
# Fonts, see convert -list type to get the list
declare font_tstamps=courier # Used for timestamps behind the thumbnails
declare font_heading=helvetica # Used for meta info box
declare font_sign=$font_heading # Used for the signature box
# Font sizes, in points
declare pts_tstamps=18 # Used for the timestamps
declare pts_meta=16 # Used for the meta info box
declare pts_sign=11 # Used for the signature
# See --shoehorn
declare shoehorned=
 
# }}} # End of override-able variables
 
# Options and other internal usage variables, no need to mess with this!
declare -i interval=$DEFAULT_INTERVAL # Interval of captures (=numsecs/numcaps)
declare -i numcaps=$DEFAULT_NUMCAPS # Number of captures (=numsecs/interval)
declare title=""
declare -i fromtime=0 # Starting second (see -f)
declare -i 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 -a TEMPSTUFF=( ) # Temporal files
 
# Exit codes, same codes 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
 
 
load_config() {
# 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'
)
 
if [ ! -f "$CFGFILE" ]; then return 0 ; fi
 
local compregex=$( sed 's/ /|/g' <<<${ALLOWED_OVERRIDES[*]} )
 
while read line ; do # auto variable $line
# Don't allow ';', FIXME: dunno how secure that really is
if ! egrep -q '^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]*=[^;]*' <<<"$line" ; then
continue
fi
if ! egrep -q "^($compregex)=" <<<"$line" ; then
continue
fi
# FIXME: Only print in verbose mose
# FIXME: Only for really overridden ones
echo "Overridden variable" $(sed -r 's/^[[:space:]]*([a-zA-Z0-9_]*)=.*/\1/'<<<$line)
eval $line
done <$CFGFILE
}
 
# {{{ # Convenience functions
 
is_number() {
egrep -q '^[0-9]+$' <<<"$1"
return $?
}
 
# The current code is a tad permissive, it allows e.g. things like
# 10m1h (equivalent to 1h10m)
# 1m1m (equivalent to 2m)
get_interval() {
if is_number "$1" ; then echo $1 ; return 0 ; fi
 
local s=$(tr '[A-Z]' '[a-z]' <<<"$1")
 
# Only allowed characters
if ! grep -q '[0-9smh]' <<<"$s"; then
return $EX_USAGE;
fi
 
# FIXME: Find some cleaner way
local i= c= num= sum=0
for i in $(seq 0 $(( ${#s} - 1)) ); do
c=${s:$i:1}
if is_number $c ; then
num+=$c
else
case $c in
h) num=$(($num * 3600)) ;;
m) num=$(($num * 60)) ;;
s) ;;
*)
return $EX_SOFTWARE
;;
esac
sum=$(($sum + $num))
num=
fi
done
 
# If last element was a number, it's seconds and they weren't added
if is_number $c ; then
sum=$(( $sum + $num ))
fi
 
echo $sum
 
return 0
}
 
pad() {
local len=$1
local str=$2
 
while [ ${#str} -lt $len ]; do
str=0$str
done
 
echo $str
}
 
pretty_stamp() {
if ! is_number "$1" ; then return $EX_USAGE ; fi
 
local t=$1
local h=$(( $t / 3600 ))
t=$(( $t % 3600 ))
local m=$(( $t / 60 ))
t=$(( $t % 60 ))
local s=$t
 
local R=""
 
if [ $h -gt 0 ]; then
R+="$h:"
fi
R+=$(pad 2 "$m"):$(pad 2 $s)
 
echo $R
}
 
get_pretty_size() {
local f="$1"
 
local bytes=$(du -DL --bytes "$f" | cut -f1)
local size=""
 
if [ "$bytes" -gt $(( 1024**3 )) ]; then
local gibs=$(( $bytes / 1024**3 ))
local mibs=$(( ( $bytes % 1024**3 ) / 1024**2 ))
size="${gibs}.${mibs:0:2} GiB"
elif [ "$bytes" -gt $(( 1024**2)) ]; then
local mibs=$(( $bytes / 1024**2 ))
local kibs=$(( ( $bytes % 1024**2 ) / 1024 ))
size="${mibs}.${kibs:0:2} MiB"
elif [ "$bytes" -gt 1024 ]; then
local kibs=$(( $bytes / 1024 ))
bytes=$(( $bytes % 1024 ))
size="${kibs}.${bytes:0:2} KiB"
else
size="${bytes} B"
fi
 
echo $size
}
 
# Rename a file, if the target exists, try with -1, -2, ...
safe_rename() {
local from="$1"
local to="$2"
 
local ext=$(sed -r 's/.*\.(.*)/\1/g' <<<"$to")
 
local n=1
while [ -f "$to" ]; do
to="$(basename "$2" .$ext)-$n.$ext"
n=$(( $n + 1))
done
 
mv "$from" "$to"
echo "$to" >&2
}
 
test_programs() {
for prog in mplayer convert montage ffmpeg ; do
type -pf "$prog" >/dev/null
local retval=$?
if [ $retval -ne 0 ] ; then
error "Required program $prog not found!"
return $retval
fi
done
}
 
cleanup() {
if [ -z $TEMPSTUFF ]; then return 0 ; fi
echo "Cleaning up..." >&2 # TODO: Only in verbose mode
rm -rf ${TEMPSTUFF[*]}
TEMPSTUFF=( )
}
 
exithdlr() {
cleanup
}
 
# Print some text to stderr
error() {
echo "$1" >&2
}
 
# }}} # Convenience functions
 
# {{{ # Core functionality
 
process() {
local f=$1
 
local numcols=$cols
 
if [ ! -f "$f" ]; then
error "File \"$f\" doesn't exist"
return $EX_NOINPUT
fi
echo "Processing $f..." >&2
 
# Meta data extraction
# Don't change the -vc as it would affect $vdec
local mplayer_cache=$(mplayer -benchmark -ao null -vo null -identify -frames 0 -quiet "$f" 2>/dev/null | grep ^ID)
local vcodec=$(grep ID_VIDEO_FORMAT <<<"$mplayer_cache" | cut -d'=' -f2) # FourCC
local acodec=$(grep ID_AUDIO_FORMAT <<<"$mplayer_cache" | cut -d'=' -f2)
local vdec=$(grep ID_VIDEO_CODEC <<<"$mplayer_cache" | cut -d'=' -f2) # Decoder
local width=$(grep ID_VIDEO_WIDTH <<<"$mplayer_cache" | cut -d'=' -f2)
local height=$(grep ID_VIDEO_HEIGHT <<<"$mplayer_cache" | cut -d'=' -f2)
local fps=$(grep ID_VIDEO_FPS <<<"$mplayer_cache" | cut -d'=' -f2)
# Vidcap/Thumbnail height
local vidcap_height=$th_height
if ! is_number "$vidcap_height" || [ "$vidcap_height" -eq 0 ]; then
vidcap_height=$height
fi
 
local numsecs=$(grep ID_LENGTH <<<"$mplayer_cache"| cut -d'=' -f2 | cut -d. -f1)
if ! is_number $numsecs ; then
error "Internal error!"
return $EX_SOFTWARE
fi
 
local nc=$numcaps
local in=$interval
 
# Start bound:
local startsec=0
if [ $startsec -lt $fromtime ]; then
startsec=$fromtime
fi
 
# End bound:
local endsec=$numsecs
if [ $totime -ne -1 ] && [ $endsec -gt $totime ]; then
endsec=$totime
fi
if [ $startsec -ne 0 ] || [ $endsec -ne $numsecs ]; then
echo "Restricting to range [$startsec..$endsec]" >&2
fi
 
local delta=$(( $endsec - $startsec ))
# FIXME: the total # of caps is currently broken when using -f and -t
 
# Note that when numcaps mandates the interval is obtained from
# the actually allowed secods (hence it is not movie_length / numcaps )
# Adjust interval/numcaps:
if [ "$timecode_from" -eq "$TC_INTERVAL" ]; then
# Interval rules => it doesn't change
nc=$(( $delta / $interval ))
# If a multiple, an extra vidcap is generated (at the last second)
if [ $(( $delta % $interval )) -eq 0 ]; then
nc=$(( $nc + 1 ))
fi
elif [ "$timecode_from" -eq "$TC_NUMCAPS" ]; then
# Numcaps rules => it doesn't change
if [ $numcaps -eq 1 ]; then # If just one cap, center it
in=$(( $numsecs / 2 ))
else
in=$(( $numsecs / $numcaps ))
fi
else
error "Internal error!"
return $EX_SOFTWARE
fi
 
# Let's try to make some sense...
# Minimum interval allowance:
if [ $in -gt $numsecs ]; then
error "The interval is longer than the video length."
error "Use a lower interval or numcaps instead."
error "Skipping \"$f\"."
return $EX_USAGE
fi
# Contact sheet minimum cols:
if [ $nc -lt $numcols ]; then
numcols=$nc
fi
 
# Tempdir
 
local dir=$(mktemp -d -p . vcs.XXXXXX)
if [ "$?" -ne 0 ]; then
error "Error creating temporary directory"
return $EX_CANTCREAT
fi
TEMPSTUFF+=( "$dir" )
 
local n=
 
# Get the stamps (if in auto mode)...
 
local stamps=( )
if [ $manual_mode -ne 1 ]; then
 
n=$(( $startsec + $in ))
stamps=( ${initial_stamps[*]} $n )
 
while [ $n -le $endsec ]; do
n=$(( $n + $in ))
if [ $n -gt $endsec ]; then break; fi
 
stamps=( ${stamps[*]} $n )
done
else
stamps=( ${initial_stamps[*]} )
fi
 
n=1
local p=""
local cap=""
local montage_command="montage -font $font_tstamps -pointsize $pts_tstamps \
-gravity SouthEast -fill white "
local output=$(tempfile --prefix "vcs-" --suffix '-preview.png' -d .)
TEMPSTUFF+=( "$output" )
 
# Let's reorder the stamps, this away user-added stamps get their correct
# position also remove duplicates. Note AFAIK sort only sorts lines, that's
# why y replace spaces by newlines.
#
# Note that stamps keeps being an array and stamps[1..N] still hold their
# old values, although it's treated as a string after this
stamps=$( sed 's/ /\n/g' <<<"${stamps[*]}" | sort -n | uniq )
 
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
 
local NUMSTAMPS=$(wc -w <<<"$stamps")
TEMPSTUFF+=( $VIDCAPFILE )
# TODO: Aspect ratio
for stamp in $stamps; do
# Note that it must be checked against numsecs and not endsec, to allow
# the user manually setting stamps beyond the boundaries
if [ $stamp -gt $numsecs ]; then continue; fi
 
echo "Generating capture #${n}/${NUMSTAMPS}..." >&2
 
if [ $decoder -eq $DEC_MPLAYER ]; then
mplayer -sws 9 -ao null -benchmark -vo "png:z=0" -quiet \
-frames 1 -ss $stamp $shoehorned "$f" >/dev/null 2>&1
elif [ $decoder -eq $DEC_FFMPEG ]; then
ffmpeg -y -ss $stamp -i "$f" -an -dframes 1 -vframes 1 -vcodec png \
-f rawvideo $shoehorned $VIDCAPFILE >/dev/null 2>&1
else
error "Internal error!"
return $EX_SOFTWARE
fi || {
local retval=$?
error "The capturing program failed!"
return $retval
}
 
p=$(pad 6 $stamp).png
# mv 00000001.png $dir/$p
n=$(( $n + 1 ))
 
cap=$dir/$p
# Add the timestamp to each vidcap, doing it hear is much powerful/simple
# than with the next montage command
convert -box '#000000aa' \
-fill $fg_tstamps -pointsize $pts_tstamps -gravity SouthEast \
-stroke none -strokewidth 3 -annotate +5+5 " $(pretty_stamp $stamp) " \
$VIDCAPFILE "$cap"
 
montage_command+=" $cap"
done
 
# geometry affects the source images, not the target one!
# Note the file name could also be added by using the "-title" option, but I reserved
# it for used set titles
montage_command+=" -geometry x${vidcap_height}+10+5 -tile ${numcols}x -shadow"
if [ "$title" ]; then
montage_command+=" -font $font_heading -fill $fg_heading -title '$title'"
fi
montage_command+=" $output"
 
echo "Composing contact sheet..." >&2
eval $montage_command # eval is required to evaluate correctly the text in quotes!
mv "$output" . 2>/dev/null || true
 
# Codec "prettyfication"
case $( tr '[A-Z]' '[a-z]' <<<$vcodec) in
xvid) vcodec=Xvid ;;
dx50) vcodec="DivX 5" ;;
0x10000001) vcodec="MPEG-1" ;;
0x10000002) vcodec="MPEG-2" ;;
avc1) vcodec="MPEG-4 AVC" ;;
wmv2) vcodec="WMV 8" ;; # WMV2 is v8
esac
if [ "$vdec" == "ffodivx" ]; then
vcodec+=" (MPEG-4)"
elif [ "$vdec" == "ffh264" ]; then
vcodec+=" (h.264)"
fi
 
case $( tr '[A-Z]' '[a-z]' <<<$acodec ) in
85) acodec='MPEG-1 Layer III (MP3)' ;;
80) acodec='MPEG-1 Layer II (MP2)' ;;
mp4a) acodec='MPEG-4 AAC' ;;
353) acodec='WMA 2' ;;
"") acodec="no audio" ;;
esac
 
 
local meta="Filename: $(basename "$f")
File size: $(get_pretty_size "$f")
Length: $(pretty_stamp "$numsecs")"
local meta2="Dimensions: ${width}x${height}
Format: $vcodec / $acodec
FPS: $fps"
local signature="$user_signature $user
$PROGRAM_SIGNATURE"
 
# Now let's add meta info
# This one enlarges the image to add the text, and puts
# meta info in two columns
convert -font $font_heading -pointsize $pts_meta \
-background $bg_heading -fill $fg_heading -splice 0x$(( $pts_meta * 4 )) \
-gravity NorthWest -draw "text 10,10 '$meta'" \
-gravity NorthEast -draw "text 10,10 '$meta2'" \
"$output" "$output"
# Finishing touch, signature
 
convert -gravity South -font $font_sign -pointsize $pts_sign \
-background $bg_sign -splice 0x34+0-0 \
-fill $fg_sign -draw "text 10,3 '$signature'" "$output" "$output"
 
if [ $output_format != "png" ]; then
local newout="$(basename "$output" .png).$output_format"
convert -quality $output_quality "$output" "$newout"
output="$newout"
fi
echo -n "Output wrote to " >&2
safe_rename "$output" "$(basename "$f").$output_format"
 
cleanup
}
 
show_help() {
local P=$(basename $0)
cat <<EOF
Video Contact Sheet *NIX v${VERSION}, (c) 2007 Toni Corvera
 
Usage: $P [options] <file>
 
Options:
-i|--interval <arg> Set the interval to arg. An optional unit can be
used (case-insensitive) , e.g.:
Seconds: 90
Minutes: 3m
Hours: 1h
Use either -i or -n.
-n|--numcaps <arg> Set the number of captured images to arg. Use either
-i or -n.
-f|--from <arg> Set starting time. No caps before this. Same format
as -i.
-t|--to <arg> Set ending time. No caps beyond this. Same format
as -i.
-T|--title <arg> Add a title above the vidcaps.
-u|--user <arg> Set the username found in the signature to this.
-S|--stamp <arg> Add the image found at the timestamp "arg", same format
as -i.
-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.
-H|--height <arg> Set the output (individual thumbnail) height. Width is
derived accordingly. Note width cannot be manually set.
-j|--jpeg Output in jpeg (by default output is in png).
-h|--help Show this text.
 
Options used for debugging purposes or to tweak the internal workings:
-M|--mplayer Force the usage of mplayer.
-F|--ffmpeg Force the usage of ffmpeg.
--shoehorn <arg> Pass "arg" to mplayer/ffmpeg. You probably don't need it.
 
Examples:
Create a contact sheet with default values (vidcaps at intervals of
$DEFAULT_INTERVAL seconds), the resulting file will be called
input.avi.png:
\$ $P input.avi
 
Create a sheet with vidcaps at intervals of 3 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
 
EOF
}
 
# }}} # Core functionality
 
#### Execution starts here ####
 
# Execute exithdlr on exit
trap exithdlr EXIT
 
# Test requirements
test_programs || exit $EX_UNAVAILABLE
 
load_config
 
# {{{ # Command line parsing
 
# Based on getopt-parse.bash example.
# On debian systems see </usr/share/doc/util-linux/examples/getopt-parse.bash.gz>
 
# FIXME: use username / no name at all with -u noarg, and not -u
TEMP=$(getopt -o i:n:u:T:f:t:S:jhFMH:c:m \
--long "interval:,numcaps:,username:,title:,from:,to:,stamp:,jpeg,help,shoehorn:,mplayer,ffmpeg,height:,columns:,manual" \
-n $0 -- "$@")
 
eval set -- "$TEMP"
 
while true ; do
case "$1" in
-i|--interval)
if ! interval=$(get_interval "$2") ; then
error "Interval must be a number (given $2)"
exit $EX_USAGE
fi
if [ "$interval" -le 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 caps must be a number! (given $2)"
exit $EX_USAGE
fi
numcaps="$2"
timecode_from=$TC_NUMCAPS
shift # Option arg
;;
-u|--username) user="$2" ; shift ;;
-T|--title) title="$2" ; shift ;;
-f|--from)
if ! fromtime=$(get_interval "$2") ; then
error "Starting timestamp must be a valid interval"
exit $EX_USAGE
fi
shift
;;
-t|--to)
if ! totime=$(get_interval "$2") ; then
error "Ending timestamp must be a valid interval"
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 interval"
exit $EX_USAGE
fi
initial_stamps=( ${initial_stamps[*]} $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 number (given $2)"
exit $EX_USAGE
fi
th_height="$2"
shift
;;
-c|--columns)
if ! is_number "$2" ; then
error "Columns must be a number (given $2)"
exit $EX_USAGE
fi
cols="$2"
shift
;;
-m|--manual) manual_mode=1 ;;
--) shift ; break ;;
*) error "Internal error! (remaining opts: $@)" ; exit $EX_SOFTWARE ;
esac
shift
done
 
# Remaining arguments
if [ ! "$1" ]; then
show_help
exit $EX_USAGE
fi
# 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
 
# }}} # Command line parsing
 
 
# vim:set ts=4 ai: #
Property changes:
Added: svn:executable
Added: svn:keywords
+Rev Id Date
\ No newline at end of property
/video-contact-sheet/tags/1.0.3b
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/tags/1.0.2b:r274
/video-contact-sheet/tags/1.0.4b/CHANGELOG
0,0 → 1,58
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.4b/vcs
0,0 → 1,839
#!/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>
#
 
declare -r VERSION="1.0.4b"
#
# History (The full changelog was moved to a separate file and can be found
# at <http://p.outlyer.net/vcs/files/CHANGELOG>).
#
# 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
#
 
set -e
 
# 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
 
# Constants {{{
# 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="with Video Contact Sheet *NIX ${VERSION} <http://p.outlyer.net/vcs/>"
# see $safe_rename_pattern
declare -r DEFAULT_SAFE_REN_PATT="%b-%N.%e"
 
# }}} # 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
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
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 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
# Fonts, see convert -list type to get the list
declare font_tstamps=courier # Used for timestamps behind the thumbnails
declare font_heading=helvetica # Used for meta info box
declare font_sign=$font_heading # Used for the signature box
# Font sizes, in points
declare pts_tstamps=18 # Used for the timestamps
declare pts_meta=16 # Used for the meta info box
declare pts_sign=11 # Used for the signature
# See --shoehorn
declare shoehorned=
# 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"
# 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=
 
 
# }}} # End of override-able variables
 
# Options and other internal usage variables, no need to mess with this!
declare -i interval=$DEFAULT_INTERVAL # Interval of captures (=numsecs/numcaps)
declare -i numcaps=$DEFAULT_NUMCAPS # Number of captures (=numsecs/interval)
declare title=""
declare -i fromtime=0 # Starting second (see -f)
declare -i 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)
 
declare -a TEMPSTUFF=( ) # Temporal files
 
# Exit codes, same codes 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
 
 
load_config() {
# 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'
)
 
local compregex=$( sed 's/ /|/g' <<<${ALLOWED_OVERRIDES[*]} )
 
local basecfg="$(basename "$CFGFILE")"
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
# Don't allow ';', FIXME: dunno how secure that really is
if ! egrep -q '^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]*=[^;]*' <<<"$line" ; then
continue
fi
if ! egrep -q "^($compregex)=" <<<"$line" ; then
continue
fi
# FIXME: Only print in verbose mose
# FIXME: Only for really overridden ones
local varname=$(sed -r 's/^[[:space:]]*([a-zA-Z0-9_]*)=.*/\1/'<<<$line)
echo "Overridden variable $varname from file $cfgfile"
eval $line
done <$cfgfile
done
}
 
# {{{ # Convenience functions
 
# Returns true if input is composed only of numbers
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() {
egrep -q '^([0-9]+\.?([0-9])?+|(\.[0-9]+))$'<<<"$1"
}
 
# Returns true if input is a fraction
# Only accepts XX/YY
is_fraction() {
egrep -q '^[0-9]+/[0-9]+$'<<<"$1"
}
 
# Prints the width correspoding to the input height and the variable
# aspect ratio
# compute_width(height) (=AR*height) (rounded)
compute_width() {
local wfloat=$(bc -lq <<< "$aspect_ratio * $1")
local wint=$(bc -q <<<"($wfloat+0.5)/1")
echo $wint
}
 
# The current code is a tad permissive, it allows e.g. things like
# 10m1h (equivalent to 1h10m)
# 1m1m (equivalent to 2m)
get_interval() {
if is_number "$1" ; then echo $1 ; return 0 ; fi
 
local s=$(tr '[A-Z]' '[a-z]' <<<"$1")
 
# Only allowed characters
if ! grep -q '[0-9smh]' <<<"$s"; then
return $EX_USAGE;
fi
 
# FIXME: Find some cleaner way
local i= c= num= sum=0
for i in $(seq 0 $(( ${#s} - 1)) ); do
c=${s:$i:1}
if is_number $c ; then
num+=$c
else
case $c in
h) num=$(($num * 3600)) ;;
m) num=$(($num * 60)) ;;
s) ;;
*)
return $EX_SOFTWARE
;;
esac
sum=$(($sum + $num))
num=
fi
done
 
# If last element was a number, it's seconds and they weren't added
if is_number $c ; then
sum=$(( $sum + $num ))
fi
 
echo $sum
 
return 0
}
 
pad() {
local len=$1
local str=$2
 
while [ ${#str} -lt $len ]; do
str=0$str
done
 
echo $str
}
 
pretty_stamp() {
if ! is_number "$1" ; then return $EX_USAGE ; fi
 
local t=$1
local h=$(( $t / 3600 ))
t=$(( $t % 3600 ))
local m=$(( $t / 60 ))
t=$(( $t % 60 ))
local s=$t
 
local R=""
 
if [ $h -gt 0 ]; then
R+="$h:"
fi
R+=$(pad 2 "$m"):$(pad 2 $s)
 
echo $R
}
 
get_pretty_size() {
local f="$1"
 
local bytes=$(du -DL --bytes "$f" | cut -f1)
local size=""
 
if [ "$bytes" -gt $(( 1024**3 )) ]; then
local gibs=$(( $bytes / 1024**3 ))
local mibs=$(( ( $bytes % 1024**3 ) / 1024**2 ))
size="${gibs}.${mibs:0:2} GiB"
elif [ "$bytes" -gt $(( 1024**2)) ]; then
local mibs=$(( $bytes / 1024**2 ))
local kibs=$(( ( $bytes % 1024**2 ) / 1024 ))
size="${mibs}.${kibs:0:2} MiB"
elif [ "$bytes" -gt 1024 ]; then
local kibs=$(( $bytes / 1024 ))
bytes=$(( $bytes % 1024 ))
size="${kibs}.${bytes:0:2} KiB"
else
size="${bytes} B"
fi
 
echo $size
}
 
# Rename a file, if the target exists, try with appending numbers to the name
# And print the output name to stderr
safe_rename() {
local from="$1"
local to="$2"
 
# Extension
local ext=$(sed -r 's/.*\.(.*)/\1/g' <<<"$to")
# Basename without extension
local b=$(basename "$2" ".$ext")
 
# 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" >&2
}
 
test_programs() {
for prog in mplayer convert montage ffmpeg bc ; do
type -pf "$prog" >/dev/null
local retval=$?
if [ $retval -ne 0 ] ; then
error "Required program $prog not found!"
return $retval
fi
done
}
 
cleanup() {
if [ -z $TEMPSTUFF ]; then return 0 ; fi
echo "Cleaning up..." >&2 # TODO: Only in verbose mode
rm -rf ${TEMPSTUFF[*]}
TEMPSTUFF=( )
}
 
exithdlr() {
cleanup
}
 
# Print some text to stderr
error() {
echo "$1" >&2
}
 
# }}} # Convenience functions
 
# {{{ # Core functionality
 
MPLAYER_CACHE=
numsecs() {
echo $(grep ID_LENGTH <<<"$MPLAYER_CACHE"| cut -d'=' -f2 | cut -d. -f1)
}
 
compute_timecodes() {
local st=0 numsecs=$(numsecs) end=
end=$numsecs
if [ $st -lt $fromtime ]; then
st=$fromtime
fi
if [ $totime -gt 0 ] && [ $end -gt $totime ]; then
end=$totime
fi
 
local inc=
if [ "$timecode_from" -eq $TC_INTERVAL ]; then
inc=$interval
elif [ "$timecode_from" -eq $TC_NUMCAPS ]; then
# Numcaps mandates: timecodes are obtained dividing the length
# by the number of captures
if [ $numcaps -eq 1 ]; then # Special case, just one capture, center it
inc=$(( ($end-$st) / 2 + 1))
else
inc=$(( ($end-$st) / $numcaps ))
fi
else
error "Internal error"
return $EX_SOFTWARE
fi
 
if [ $inc -gt $numsecs ]; then
error "Interval is longer than video length, skipping $f"
return $EX_USAGE
fi
 
local LTC=( ) stamp=
for stamp in $(seq $st $inc $end); do
LTC+=( $stamp )
done
unset LTC[0] # Initial cap (=$st)
TIMECODES=( ${TIMECODES[*]} ${LTC[*]} )
}
 
process() {
local f=$1
 
local numcols=$cols
 
if [ ! -f "$f" ]; then
error "File \"$f\" doesn't exist"
return $EX_NOINPUT
fi
echo "Processing $f..." >&2
 
# 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)
local vcodec=$(grep ID_VIDEO_FORMAT <<<"$MPLAYER_CACHE" | cut -d'=' -f2) # FourCC
local acodec=$(grep ID_AUDIO_FORMAT <<<"$MPLAYER_CACHE" | cut -d'=' -f2)
local vdec=$(grep ID_VIDEO_CODEC <<<"$MPLAYER_CACHE" | cut -d'=' -f2) # Decoder
local width=$(grep ID_VIDEO_WIDTH <<<"$MPLAYER_CACHE" | cut -d'=' -f2)
local height=$(grep ID_VIDEO_HEIGHT <<<"$MPLAYER_CACHE" | cut -d'=' -f2)
local fps=$(grep ID_VIDEO_FPS <<<"$MPLAYER_CACHE" | cut -d'=' -f2)
# Vidcap/Thumbnail height
local vidcap_height=$th_height
if ! is_number "$vidcap_height" || [ "$vidcap_height" -eq 0 ]; then
vidcap_height=$height
fi
if [ "0" == "$aspect_ratio" ]; then
aspect_ratio=$(bc -lq <<< "$width / $height")
fi
local vidcap_width=$(compute_width $vidcap_height)
 
local numsecs=$(grep ID_LENGTH <<<"$MPLAYER_CACHE"| cut -d'=' -f2 | cut -d. -f1)
if ! is_number $numsecs ; then
error "Internal error!"
return $EX_SOFTWARE
fi
 
local nc=$numcaps
 
# Contact sheet minimum cols:
if [ $nc -lt $numcols ]; then
numcols=$nc
fi
 
# Tempdir
 
local dir=$(mktemp -d -t vcs.XXXXXX)
if [ ! -d "$dir" ]; then
error "Error creating temporary directory"
return $EX_CANTCREAT
fi
TEMPSTUFF+=( "$dir" )
 
local n=
 
# Compute the stamps (if in auto mode)...
TIMECODES=${initial_stamps[*]}
if [ $manual_mode -ne 1 ]; then
compute_timecodes
fi
 
n=1
local p=""
local montage_command="montage -font $font_tstamps -pointsize $pts_tstamps \
-gravity SouthEast -fill white "
local output=$(tempfile --prefix "vcs-" --suffix '-preview.png')
TEMPSTUFF+=( "$output" )
 
# Let's reorder the stamps, this away user-added stamps get their correct
# position also remove duplicates. Note AFAIK sort only sorts lines, that's
# why y replace spaces by newlines.
local stamps=$( sed 's/ /\n/g' <<<"${TIMECODES[*]}" | sort -n | uniq )
 
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
 
local NUMSTAMPS=$(wc -w <<<"$stamps")
TEMPSTUFF+=( $VIDCAPFILE )
local capfile=
# TODO: Aspect ratio
for stamp in $stamps; do
# Note that it must be checked against numsecs and not endsec, to allow
# the user manually setting stamps beyond the boundaries
if [ $stamp -gt $numsecs ]; then continue; fi
 
echo "Generating capture #${n}/${NUMSTAMPS}..." >&2
 
p=$(pad 6 $stamp).png
capfile=$dir/$p
 
if [ $decoder -eq $DEC_MPLAYER ]; then
mplayer -sws 9 -ao null -benchmark -vo "png:z=0" -quiet \
-frames 1 -ss $stamp $shoehorned "$f" >/dev/null 2>&1
elif [ $decoder -eq $DEC_FFMPEG ]; then
ffmpeg -y -ss $stamp -i "$f" -an -dframes 1 -vframes 1 -vcodec png \
-f rawvideo $shoehorned $VIDCAPFILE >/dev/null 2>&1
else
error "Internal error!"
return $EX_SOFTWARE
fi || {
local retval=$?
error "The capturing program failed!"
return $retval
}
if [ "0" == "$(du "$VIDCAPFILE" | cut -f1)" ]; then
error "Failed to capture frame (at second $stamp)"
return $EX_SOFTWARE
fi
 
let 'n++' # $n++
 
# Add the timestamp to each vidcap, doing it here is much powerful/simple
# than with the next montage command
# Note the '!', it is necessary to apply aspect ratio change
convert -box '#000000aa' -geometry ${vidcap_width}x${vidcap_height}! \
-fill $fg_tstamps -pointsize $pts_tstamps -gravity SouthEast \
-stroke none -strokewidth 3 -annotate +5+5 " $(pretty_stamp $stamp) " \
$VIDCAPFILE "$capfile"
if [ ! -f "$capfile" ]; then
error "Failed to process capture"
return $EX_CANTCREAT
fi
 
montage_command+=" \"$capfile\""
done
unset capfile
 
# geometry affects the source images, not the target one!
# Note the file name could also be added by using the "-title" option, but I reserved
# it for used set titles
montage_command+=" -geometry ${vidcap_width}x${vidcap_height}+10+5 -tile ${numcols}x -shadow"
if [ "$title" ]; then
montage_command+=" -font $font_heading -fill $fg_heading -title '$title'"
fi
montage_command+=" \"$output\""
 
echo "Composing contact sheet..." >&2
eval $montage_command # eval is required to evaluate correctly the text in quotes!
 
# Codec "prettyfication"
case $( tr '[A-Z]' '[a-z]' <<<$vcodec) in
xvid) vcodec=Xvid ;;
dx50) vcodec="DivX 5" ;;
0x10000001) vcodec="MPEG-1" ;;
0x10000002) vcodec="MPEG-2" ;;
avc1) vcodec="MPEG-4 AVC" ;;
wmv2) vcodec="WMV8" ;; # v2 is same as v8
wmv3) vcodec="WMV9" ;;
esac
if [ "$vdec" == "ffodivx" ]; then
vcodec+=" (MPEG-4)"
elif [ "$vdec" == "ffh264" ]; then
vcodec+=" (h.264)"
fi
 
case $( tr '[A-Z]' '[a-z]' <<<$acodec ) in
85) acodec='MPEG-1 Layer III (MP3)' ;;
80) acodec='MPEG-1 Layer II (MP2)' ;;
mp4a) acodec='MPEG-4 AAC' ;;
353) acodec='WMA2' ;;
354) acodec='WMA3' ;;
"") acodec="no audio" ;;
esac
 
 
local meta="Filename: $(basename "$f")
File size: $(get_pretty_size "$f")
Length: $(pretty_stamp "$numsecs")"
local meta2="Dimensions: ${width}x${height}
Format: $vcodec / $acodec
FPS: $fps"
local signature="$user_signature $user
$PROGRAM_SIGNATURE"
 
# Now let's add meta info
# This one enlarges the image to add the text, and puts
# meta info in two columns
convert -font $font_heading -pointsize $pts_meta \
-background $bg_heading -fill $fg_heading -splice 0x$(( $pts_meta * 4 )) \
-gravity NorthWest -draw "text 10,10 '$meta'" \
-gravity NorthEast -draw "text 10,10 '$meta2'" \
"$output" "$output"
# Finishing touch, signature
 
convert -gravity South -font $font_sign -pointsize $pts_sign \
-background $bg_sign -splice 0x34+0-0 \
-fill $fg_sign -draw "text 10,3 '$signature'" "$output" "$output"
 
if [ $output_format != "png" ]; then
local newout="$(dirname "$output")/$(basename "$output" .png).$output_format"
convert -quality $output_quality "$output" "$newout"
output="$newout"
fi
echo -n "Output wrote to " >&2
safe_rename "$output" "$(basename "$f").$output_format"
 
cleanup
}
 
show_help() {
local P=$(basename $0)
cat <<EOF
Video Contact Sheet *NIX v${VERSION}, (c) 2007 Toni Corvera
 
Usage: $P [options] <file>
 
Options:
-i|--interval <arg> Set the interval to arg. An optional unit can be
used (case-insensitive) , e.g.:
Seconds: 90
Minutes: 3m
Hours: 1h
Use either -i or -n.
-n|--numcaps <arg> Set the number of captured images to arg. Use either
-i or -n.
-f|--from <arg> Set starting time. No caps before this. Same format
as -i.
-t|--to <arg> Set ending time. No caps beyond this. Same format
as -i.
-T|--title <arg> Add a title above the vidcaps.
-u|--user <arg> Set the username found in the signature to this.
-S|--stamp <arg> Add the image found at the timestamp "arg", same format
as -i.
-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.
-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.
-j|--jpeg Output in jpeg (by default output is in png).
-h|--help Show this text.
 
Options used for debugging purposes or to tweak the internal workings:
-M|--mplayer Force the usage of mplayer.
-F|--ffmpeg Force the usage of ffmpeg.
--shoehorn <arg> Pass "arg" to mplayer/ffmpeg. You probably don't need it.
 
Examples:
Create a contact sheet with default values (vidcaps at intervals of
$DEFAULT_INTERVAL seconds), the resulting file will be called
input.avi.png:
\$ $P input.avi
 
Create a sheet with vidcaps at intervals of 3 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
 
EOF
}
 
# }}} # Core functionality
 
#### Execution starts here ####
 
# Execute exithdlr on exit
trap exithdlr EXIT
 
# Test requirements
test_programs || exit $EX_UNAVAILABLE
 
load_config
 
# {{{ # Command line parsing
 
# Based on getopt-parse.bash example.
# On debian systems see </usr/share/doc/util-linux/examples/getopt-parse.bash.gz>
 
# TODO: use no name at all with -u noarg
#eval set -- "${default_options} ${@}"
TEMP=$(getopt -o i:n:u:T:f:t:S:jhFMH:c:ma: \
--long "interval:,numcaps:,username:,title:,from:,to:,stamp:,jpeg,help,shoehorn:,mplayer,ffmpeg,height:,columns:,manual,aspect" \
-n $0 -- "$@")
 
eval set -- "$TEMP"
 
while true ; do
case "$1" in
-i|--interval)
if ! interval=$(get_interval "$2") ; then
error "Interval must be a number (given $2)"
exit $EX_USAGE
fi
if [ "$interval" -le 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 caps must be a number! (given $2)"
exit $EX_USAGE
fi
numcaps="$2"
timecode_from=$TC_NUMCAPS
shift # Option arg
;;
-u|--username) user="$2" ; shift ;;
-T|--title) title="$2" ; shift ;;
-f|--from)
if ! fromtime=$(get_interval "$2") ; then
error "Starting timestamp must be a valid interval"
exit $EX_USAGE
fi
shift
;;
-t|--to)
if ! totime=$(get_interval "$2") ; then
error "Ending timestamp must be a valid interval"
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 interval"
exit $EX_USAGE
fi
initial_stamps=( ${initial_stamps[*]} $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 number (given $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 floating point "\
"number or as a fraction (ie: 1, 1.33, 4/3, 2.5)"
exit 12
fi
aspect_ratio="$2"
shift
;;
-c|--columns)
if ! is_number "$2" ; then
error "Columns must be a number (given $2)"
exit $EX_USAGE
fi
cols="$2"
shift
;;
-m|--manual) manual_mode=1 ;;
--) shift ; break ;;
*) error "Internal error! (remaining opts: $@)" ; exit $EX_SOFTWARE ;
esac
shift
done
 
# Remaining arguments
if [ ! "$1" ]; then
show_help
exit $EX_USAGE
fi
# 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
 
# }}} # Command line parsing
 
 
# vim:set ts=4 ai: #
Property changes:
Added: svn:executable
Added: svn:keywords
+Rev Id Date
\ No newline at end of property
/video-contact-sheet/tags/1.0.4b
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/tags/1.0.2b:r274
/video-contact-sheet/tags/1.0.5b/CHANGELOG
0,0 → 1,88
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.5b/vcs
0,0 → 1,1320
#!/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>
#
 
declare -r VERSION="1.0.5b"
#
# History (The full changelog was moved to a separate file and can be found
# at <http://p.outlyer.net/vcs/files/CHANGELOG>).
#
# TODO: Support for ms timestamps (ffmpeg supports it e.g. 54.9 is ok and != 54)
#
# 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
#
 
set -e
 
# 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
 
# Constants {{{
# 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="with 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
 
# }}} # 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 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
# Fonts, see convert -list type to get the list
declare font_tstamps=courier # Used for timestamps behind the thumbnails
declare font_heading=helvetica # Used for meta info box
declare font_sign=$font_heading # Used for the signature box
# Font sizes, in points
declare pts_tstamps=18 # Used for the timestamps
declare pts_meta=16 # Used for the meta info box
declare pts_sign=11 # Used for the signature
# See --shoehorn
declare shoehorned=
# 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
 
# }}} # End of override-able variables
 
# Options and other internal usage variables, no need to mess with this!
declare -i interval=$DEFAULT_INTERVAL # Interval of captures (=numsecs/numcaps)
declare -i numcaps=$DEFAULT_NUMCAPS # Number of captures (=numsecs/interval)
declare title=""
declare -i fromtime=0 # Starting second (see -f)
declare -i 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...
declare -a VID=
# ...and these are the indexes in $VID
declare -ri W=0 H=1 FPS=2 LEN=3 VCODEC=4 ACODEC=5 VDEC=6 CHANS=7
 
# Exit codes, same codes 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
 
# 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'
)
 
# 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
}
 
# 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
}
 
# {{{ # 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
# Only accepts XX/YY
# is_fraction($1 = input)
is_fraction() {
egrep -q '^[0-9]+/[0-9]+$'<<<"$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"
}
 
# 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=$(tr '[A-Z]' '[a-z]' <<<"$1")
 
# Only allowed characters
if ! grep -q '[0-9smh]' <<<"$s"; then
return $EX_USAGE;
fi
 
# FIXME: Find some cleaner way
local i= c= num= sum=0
for i in $(seq 0 $(( ${#s} - 1)) ); do
c=${s:$i:1}
if is_number $c ; then
num+=$c
else
case $c in
h) num=$(($num * 3600)) ;;
m) num=$(($num * 60)) ;;
s) ;;
*)
return $EX_SOFTWARE
;;
esac
sum=$(($sum + $num))
num=
fi
done
 
# If last element was a number, it's seconds and they weren't added
if is_number $c ; then
sum=$(( $sum + $num ))
fi
 
echo $sum
 
return 0
}
 
# Pads a string with zeroes on the left until it is at least
# the indicated length
# pad($1 = minimum length, $2 = string)
pad() {
local len=$1
local str=$2
 
while [ ${#str} -lt $len ]; do
str=0$str
done
 
echo $str
}
 
# 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_number "$1" ; then return $EX_USAGE ; fi
 
local t=$1
local h=$(( $t / 3600 ))
t=$(( $t % 3600 ))
local m=$(( $t / 60 ))
t=$(( $t % 60 ))
local s=$t
 
local R=""
 
if [ $h -gt 0 ]; then
R+="$h:"
fi
R+=$(pad 2 "$m"):$(pad 2 $s)
 
echo $R
}
 
# 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)
safe_rename() {
local from="$1"
local to="$2"
 
# Extension
local ext=$(sed -r 's/.*\.(.*)/\1/g' <<<"$to")
# Basename without extension
local b=$(basename "$2" ".$ext")
 
# 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 mplayer convert montage bc ffmpeg ; do
type -pf "$prog" >/dev/null
if [ $? -ne 0 ] ; then
error "Required program $prog not found!"
let 'retval++'
fi
done
 
return $retval
}
 
# Remove any temporal files
# Does nothing if none has been created so far
# cleanup()
cleanup() {
if [ -z $TEMPSTUFF ]; then return 0 ; fi
info "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" >&2 ; tput sgr0
fi
}
#
# 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" >&2 ; tput sgr0
fi
}
#
# Print an informational message
# info($1 = text)
info() {
if [ $verbosity -ge $V_INFO ]; then
if [ $plain_messages -eq 0 ]; then
tput bold ; tput setaf 2;
fi
echo "$1" >&2 ; tput sgr0
fi
}
#
# Same as info but with no colour ever.
# infoplain($1 = text)
infoplain() {
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() {
VCSTEMPDIR=$(mktemp -d -t vcs.XXXXXX)
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=$(tempfile -d "$VCSTEMPDIR" -p "vcs-" -s "$1")
if [ ! -f "$r" ]; then
error "Failed to create temporary file"
return $EX_CANTCREAT
fi
TEMPSTUFF+=( "$r" )
echo "$r"
}
 
# 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
# globals: fromtime, totime, timecode_from, TIMECODES
if [ $st -lt $fromtime ]; then
st=$fromtime
fi
if [ $totime -gt 0 ] && [ $end -gt $totime ]; then
end=$totime
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=$(( ($end-$st) / 2 + 1))
else
inc=$(( ($end-$st) / $tcnumcaps ))
fi
else
error "Internal error"
return $EX_SOFTWARE
fi
 
if [ $inc -gt ${VID[$LEN]} ]; then
error "Interval is longer than video length, skipping $f"
return $EX_USAGE
fi
 
local LTC=( ) stamp=
for stamp in $(seq $st $inc $end); do
LTC+=( $stamp )
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()
guess_aspect() {
# mplayer's ID_ASPECT seems to be always 0 ¿?
local w=${VID[$W]} h=${VID[$H]}
if [ $w -eq 352 ]; then # VCD / DVD @ VCD Res. / Half-D1 / CVD
if [ $h -eq 288 ] || [ $h -eq 240 ]; then
aspect_ratio=4/3
elif [ $h -eq 576 ] || [ $h -eq 480 ]; then # Half-D1 / CVD
aspect_ratio=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
aspect_ratio=4/3
fi
elif [ $w -eq 480 ]; then # SVCD
if [ $h -eq 576 ] || [ $h -eq 480 ]; then
aspect_ratio=4/3
fi
else
warn "Couldn't guess aspect ratio."
aspect_ratio=$(bc -lq <<<"$w / $h")
fi
local AR=$(sed -r 's/(\.[0-9]{2}).*/\1/g'<<<$aspect_ratio)
info "Aspect ratio set to $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" >/dev/null 2>&1
elif [ $decoder -eq $DEC_FFMPEG ]; then
# XXX: For some reason -ss before -i failed on my mkv sample
# while after -i it failed on my wmv9 sample ¿?
ffmpeg -y -ss $stamp -i "$f" -an -dframes 1 -vframes 1 -vcodec png \
-f rawvideo $shoehorned $VIDCAPFILE >/dev/null 2>&1
else
error "Internal error!"
return $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
}
 
# Draw a timestamp in the file
# apply_stamp($1 = filename, $2 = timestamp, $3 = width, $4 = height)
apply_stamp() {
local filename=$1 timestamp=$2 width=$3 height=$4
 
local temp=$(new_temp_file ".png")
mv "$filename" "$temp"
# Add the timestamp to each vidcap, doing it here is much powerful/simple
# than with the next montage command
# Note the '!', it is necessary to apply aspect ratio change
convert -box '#000000aa' -geometry ${width}x${height}! \
-fill $fg_tstamps -pointsize $pts_tstamps -gravity SouthEast \
-stroke none -strokewidth 3 -annotate +5+5 " $(pretty_stamp $stamp) " \
"$temp" "$filename"
if [ ! -f "$filename" ]; then
error "Failed to add timestamp to capture"
mv "$temp" "$filename" # Leave things as before
return $EX_CANTCREAT
fi
}
 
# 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 | cut -d. -f1)
# 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_number "${VID[$LEN]}"
}
 
# Main function.
# Creates the contact sheet.
# process($1 = file)
process() {
local f=$1
 
local numcols=$cols
 
if [ ! -f "$f" ]; then
error "File \"$f\" doesn't exist"
return $EX_NOINPUT
fi
info "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
guess_aspect
fi
local vidcap_width=$(compute_width $vidcap_height)
 
local numsecs=$(grep ID_LENGTH <<<"$MPLAYER_CACHE"| cut -d'=' -f2 | cut -d. -f1)
local nc=$numcaps
 
# Contact sheet minimum cols:
if [ $nc -lt $numcols ]; then
numcols=$nc
fi
 
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 )
 
# Highlighs
local hlfile="$VCSTEMPDIR/highlights.png" n=1 # Must be outside the if!
if [ "$HLTIMECODES" ]; then
local hlmontage_command="montage -gravity SouthEast -texture xc:LightGoldenRod "
local hlcapfile=
local pretty=
for stamp in $(clean_timestamps "${HLTIMECODES[*]}"); do
if [ $stamp -gt $numsecs ]; then let 'n++' && continue ; fi
pretty=$(pretty_stamp $stamp)
info "Generating highlight #${n}/${#HLTIMECODES[*]} ($pretty)..."
 
capture "$f" $stamp || return $?
apply_stamp "$VIDCAPFILE" $pretty $vidcap_width $vidcap_height || return $?
 
hlcapfile=$(new_temp_file "-hl-$(pad 6 $n).png")
mv "$VIDCAPFILE" "$hlcapfile"
hlmontage_command+=" \"$hlcapfile\""
let 'n++'
done
 
#if [ "$title" ]; then
# hlmontage_command+=" -font $font_heading -fill $fg_heading -title '$title'"
#fi
info "Composing highlights contact sheet..."
eval "$hlmontage_command -geometry ${vidcap_width}x${vidcap_height}!+10+5 \
-tile ${numcols}x -shadow \"$hlfile\""
unset hlcapfile hlmontage_command pretty
fi
unset n
 
# Normal captures
# TODO: Don't reference $VIDCAPFILE
local capfile= pretty= n=1 montage_command=$base_montage_command
for stamp in $(clean_timestamps "${TIMECODES[*]}"); do
pretty=$(pretty_stamp $stamp)
# Note that it must be checked against numsecs and not endsec, to allow
# the user manually setting stamps beyond the boundaries
# This shouldn't occur automatically anymore with the new code.
if [ $stamp -gt $numsecs ]; then let 'n++' && continue; fi
 
info "Generating capture #${n}/${#TIMECODES[*]} ($pretty)..."
 
capture "$f" $stamp || return $?
apply_stamp "$VIDCAPFILE" $pretty $vidcap_width $vidcap_height || return $?
capfile=$(new_temp_file "-cap-$(pad 6 $n).png")
# move to tempdir/<frame num>.png, cap num is padded to 6 characters
mv "$VIDCAPFILE" "$capfile"
montage_command+=" \"$capfile\""
let 'n++' # $n++
done
unset capfile pretty n
 
# geometry affects the source images, not the target one!
# Note the file name could also be added by using the "-title" option, but I reserved
# it for used set titles
# FIXME: Title should go before the highlights
montage_command+=" -geometry ${vidcap_width}x${vidcap_height}+10+5 -tile ${numcols}x -shadow"
if [ "$title" ]; then
montage_command+=" -font $font_heading -fill $fg_heading -title '$title'"
fi
montage_command+=" \"$output\""
 
info "Composing standard contact sheet..."
eval $montage_command # eval is required to evaluate correctly the text in quotes!
unset montage_command
 
# Extended mode
local extoutput=
if [ "$extended_factor" != 0 ]; then
# Number of captures. Always rounded to a multiplier of 2
# TODO: Round it to a multiplier of the number of columns
local hlnc=$(bc -q <<<"( (${#TIMECODES[*]} * $extended_factor) / 2 * 2)")
unset TIMECODES # required step to get the right count
TIMECODES=${initial_stamps[*]}
compute_timecodes $TC_NUMCAPS "" $hlnc
unset hlnc
 
local n=1 w= h= capfile= pretty= montage_command=$base_montage_command
extoutput=$(new_temp_file "-extended.png")
 
# 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)
info "Generating capture from extended set: ${n}/${#TIMECODES[*]} ($pretty)..."
capture "$f" $stamp || return $?
apply_stamp "$VIDCAPFILE" $pretty $w $h || return $?
capfile=$(new_temp_file "-excap-$(pad 6 $n).png")
mv "$VIDCAPFILE" "$capfile"
montage_command+=" \"$capfile\""
let 'n++'
done
montage_command+=" -geometry ${w}x${h}+5+2 -tile $(($numcols * 2))x -shadow"
info "Composing extended contact sheet..."
eval $montage_command "$extoutput"
unset montage_command w h capfile pretty n
fi
 
# Codec "prettyfication"
# Official FourCCs: <http://msdn2.microsoft.com/en-us/library/ms867195.aspx>
# Unofficial list: <http://www.fourcc.org/>
# Another software with a list of fourccs -> name mappings:
# <http://webcvs.freedesktop.org/clipart/experimental/rejon/getid3/getid3/module.audio-video.riff.php?view=markup>
local vcodec= acodec=
case $( tr '[A-Z]' '[a-z]' <<<${VID[$VCODEC]}) in
xvid) vcodec=Xvid ;;
dx50) vcodec="DivX 5" ;;
0x10000001) vcodec="MPEG-1" ;;
0x10000002) vcodec="MPEG-2" ;;
0x00000000) vcodec="Raw RGB" ;; # How correct is this?
avc1) vcodec="MPEG-4 AVC" ;;
wmv1) vcodec="WMV7" ;;
wmv2) vcodec="WMV8" ;;
wmv3) vcodec="WMV9" ;;
fmp4) vcodec="FFmpeg" ;; # XXX: Would LAVC be a better name?
mp42) vcodec="MS MPEG-4 v2" ;;
mpg4) vcodec="MS MPEG-4 v1" ;;
mp43) vcodec="MS MPEG-4 v3" ;;
div3) vcodec="DivX ;) Low Motion" ;; # Technically same as mp43
i420) vcodec="Raw I420" ;; # XXX: 420 presumably stands by 4:2:0 ?
rv10) vcodec="RealVideo 1.0/5.0" ;;
rv20) vcodec="RealVideo G2" ;;
svq1) vcodec="Sorenson Video 1" ;;
svq3) vcodec="Sorenson Video 3" ;;
# These are known FourCCs that I haven't tested against so far
wmva) vcodec="WMV9 Advanced Profile" ;; # Not VC1 compliant. Unsupported.
rv30) vcodec="RealVideo 8" ;;
rv40) vcodec="RealVideo 9/10" ;;
div4) vcodec="DivX ;) Fast Motion" ;;
divx) vcodec="DivX" ;; # OpenDivX / DivX 5(?)
iv50) vcodec="Indeo 5.0" ;;
mjpg) vcodec="M-JPEG" ;; # XXX: Actually mJPG != MJPG
# Legacy(-er) codecs (haven't seen files in these formats in awhile)
iv32) vcodec="Indeo 3.2" ;;
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
 
case $( tr '[A-Z]' '[a-z]' <<<${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
 
 
local meta="Filename: $(basename "$f")
File size: $(get_pretty_size "$f")
Length: $(pretty_stamp "${VID[$LEN]}")"
local meta2="Dimensions: ${VID[$W]}x${VID[$H]}
Format: $vcodec / $acodec
FPS: ${VID[$FPS]}"
local signature="$user_signature $user
$PROGRAM_SIGNATURE"
unset acodec vcodec
 
if [ "$HLTIMECODES" ] || [ "$extended_factor" != "0" ]; then
info "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 \( "$hlfile" -background LightGoldenRod \) \
\( "$output" \) -append "$output"
fi
# Extended captures
if [ "$extended_factor" != 0 ]; then
convert "$output" "$extoutput" -append "$output"
fi
 
info "Creating header and footer..."
# Now let's add meta info
# This one enlarges the image to add the text, and puts
# meta info in two columns
convert -font $font_heading -pointsize $pts_meta \
-background $bg_heading -fill $fg_heading -splice 0x$(( $pts_meta * 4 )) \
-gravity NorthWest -draw "text 10,10 '$meta'" \
-gravity NorthEast -draw "text 10,10 '$meta2'" \
"$output" "$output"
# Finishing touch, signature
 
convert -gravity South -font $font_sign -pointsize $pts_sign \
-background $bg_sign -splice 0x34+0-0 \
-fill $fg_sign -draw "text 10,3 '$signature'" "$output" "$output"
 
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
}
info "Done. Output wrote to $output_name"
 
cleanup
}
 
# Prints the program identification to stderr
show_vcs_info() { # Won't be printed in quiet modes
info "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.
-f|--from <arg> Set starting time. No caps before this. Same format
as -i.
-t|--to <arg> Set ending time. No caps beyond this. Same format
as -i.
-T|--title <arg> Add a title above the vidcaps.
-u|--user <arg> Set the username found in the signature to this.
-U|--fullname Use user's full/real name (e.g. John Smith) as found in
/etc/passwd.
-S|--stamp <arg> Add the image found at the timestamp "arg". Same format
as -i.
-l|--highlight <arg> Add the image found at the timestamp "arg" as a
highlight. Same format as -i.
-e[arg] | --extended=[arg]
Enables extended mode and optionally sets the extended
factor. By default it's $DEFAULT_EXT_FACTOR.
-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.
-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.
-A|--autoaspect Try to guess aspect ratio from resolution.
-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.
 
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: Currently just prints the parsed
commandline as the title and to stderr.
 
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
}
 
# }}} # Core functionality
 
#### Execution starts here ####
 
# Execute exithdlr on exit
trap exithdlr EXIT
 
show_vcs_info
 
load_config
 
# {{{ # Command line parsing
 
# Based on getopt-parse.bash example.
# On debian systems see </usr/share/doc/util-linux/examples/getopt-parse.bash.gz>
 
# TODO: use no name at all with -u noarg
#eval set -- "${default_options} ${@}"
TEMP=$(getopt -s bash -o i:n:u:T:f:t:S:jhFMH:c:ma:l:De::UqAO: \
--long "interval:,numcaps:,username:,title:,from:,to:,stamp:,jpeg,help,"\
"shoehorn:,mplayer,ffmpeg,height:,columns:,manual,aspect:,highlight:,"\
"extended::,fullname,quiet,autoaspect,override:" \
-n $0 -- "$@")
 
eval set -- "$TEMP"
 
while true ; do
case "$1" in
-i|--interval)
if ! interval=$(get_interval "$2") ; then
error "Interval must be a (positive) number. Got '$2'."
exit $EX_USAGE
fi
if [ "$interval" -le 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)
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
;;
-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
;;
-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 ;;
-D) echo "Command line: $0 $*" && title="$0 $*" ; ;;
-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
;;
-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
;;
-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
;;
--) shift ; break ;;
*) error "Internal error! (remaining opts: $@)" ; exit $EX_SOFTWARE ;
esac
shift
done
 
# Remaining arguments
if [ ! "$1" ]; then
show_help
exit $EX_USAGE
fi
 
# 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
 
# }}} # Command line parsing
 
# vim:set ts=4 ai: #
Property changes:
Added: svn:executable
Added: svn:keywords
+Rev Id Date
\ No newline at end of property
/video-contact-sheet/tags/1.0.5b
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/tags/1.0.2b:r274
/video-contact-sheet/tags/1.0.6b/CHANGELOG
0,0 → 1,92
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.6b/vcs
0,0 → 1,1298
#!/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>
#
 
declare -r VERSION="1.0.6b"
#
# History (The full changelog was moved to a separate file and can be found
# at <http://p.outlyer.net/vcs/files/CHANGELOG>).
#
# TODO: Support for ms timestamps (ffmpeg supports it e.g. 54.9 is ok and != 54)
#
# 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 ;)
#
 
set -e
 
# 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
 
# Constants {{{
# 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="with 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
 
# }}} # 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 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
# Fonts, see convert -list type to get the list
declare font_tstamps=courier # Used for timestamps behind the thumbnails
declare font_heading=helvetica # Used for meta info box
declare font_sign=$font_heading # Used for the signature box
# Font sizes, in points
declare pts_tstamps=18 # Used for the timestamps
declare pts_meta=16 # Used for the meta info box
declare pts_sign=11 # Used for the signature
# See --shoehorn
declare shoehorned=
# 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
 
# }}} # End of override-able variables
 
# Options and other internal usage variables, no need to mess with this!
declare -i interval=$DEFAULT_INTERVAL # Interval of captures (=numsecs/numcaps)
declare -i numcaps=$DEFAULT_NUMCAPS # Number of captures (=numsecs/interval)
declare title=""
declare -i fromtime=0 # Starting second (see -f)
declare -i 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...
declare -a VID=
# ...and these are the indexes in $VID
declare -ri W=0 H=1 FPS=2 LEN=3 VCODEC=4 ACODEC=5 VDEC=6 CHANS=7
 
# Exit codes, same codes 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
 
# 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'
)
 
# 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
}
 
# 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
}
 
# {{{ # 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
# Only accepts XX/YY
# is_fraction($1 = input)
is_fraction() {
egrep -q '^[0-9]+/[0-9]+$'<<<"$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"
}
 
# 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=$(tr '[A-Z]' '[a-z]' <<<"$1")
 
# Only allowed characters
if ! grep -q '[0-9smh]' <<<"$s"; then
return $EX_USAGE;
fi
 
# FIXME: Find some cleaner way
local i= c= num= sum=0
for i in $(seq 0 $(( ${#s} - 1)) ); do
c=${s:$i:1}
if is_number $c ; then
num+=$c
else
case $c in
h) num=$(($num * 3600)) ;;
m) num=$(($num * 60)) ;;
s) ;;
*)
return $EX_SOFTWARE
;;
esac
sum=$(($sum + $num))
num=
fi
done
 
# If last element was a number, it's seconds and they weren't added
if is_number $c ; then
sum=$(( $sum + $num ))
fi
 
echo $sum
 
return 0
}
 
# Pads a string with zeroes on the left until it is at least
# the indicated length
# pad($1 = minimum length, $2 = string)
pad() {
local len=$1
local str=$2
 
while [ ${#str} -lt $len ]; do
str=0$str
done
 
echo $str
}
 
# 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_number "$1" ; then return $EX_USAGE ; fi
 
local t=$1
local h=$(( $t / 3600 ))
t=$(( $t % 3600 ))
local m=$(( $t / 60 ))
t=$(( $t % 60 ))
local s=$t
 
local R=""
 
if [ $h -gt 0 ]; then
R+="$h:"
fi
R+=$(pad 2 "$m"):$(pad 2 $s)
 
echo $R
}
 
# 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)
safe_rename() {
local from="$1"
local to="$2"
 
# Extension
local ext=$(sed -r 's/.*\.(.*)/\1/g' <<<"$to")
# Basename without extension
local b=$(basename "$2" ".$ext")
 
# 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 mplayer convert montage bc ffmpeg mktemp ; do
type -pf "$prog" >/dev/null
if [ $? -ne 0 ] ; then
error "Required program $prog not found!"
let 'retval++'
fi
done
 
return $retval
}
 
# Remove any temporal files
# Does nothing if none has been created so far
# cleanup()
cleanup() {
if [ -z $TEMPSTUFF ]; then return 0 ; fi
info "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" >&2 ; tput sgr0
fi
}
#
# 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" >&2 ; tput sgr0
fi
}
#
# Print an informational message
# info($1 = text)
info() {
if [ $verbosity -ge $V_INFO ]; then
if [ $plain_messages -eq 0 ]; then
tput bold ; tput setaf 2;
fi
echo "$1" >&2 ; tput sgr0
fi
}
#
# Same as info but with no colour ever.
# infoplain($1 = text)
infoplain() {
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() {
VCSTEMPDIR=$(mktemp -d -t vcs.XXXXXX)
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"
}
 
# 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
# globals: fromtime, totime, timecode_from, TIMECODES
if [ $st -lt $fromtime ]; then
st=$fromtime
fi
if [ $totime -gt 0 ] && [ $end -gt $totime ]; then
end=$totime
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=$(( ($end-$st) / 2 + 1))
else
inc=$(( ($end-$st) / $tcnumcaps ))
fi
else
error "Internal error"
return $EX_SOFTWARE
fi
 
if [ $inc -gt ${VID[$LEN]} ]; then
error "Interval is longer than video length, skipping $f"
return $EX_USAGE
fi
 
local LTC=( ) stamp=
for stamp in $(seq $st $inc $end); do
LTC+=( $stamp )
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()
guess_aspect() {
# mplayer's ID_ASPECT seems to be always 0 ¿?
local w=${VID[$W]} h=${VID[$H]}
if [ $w -eq 352 ]; then # VCD / DVD @ VCD Res. / Half-D1 / CVD
if [ $h -eq 288 ] || [ $h -eq 240 ]; then
aspect_ratio=4/3
elif [ $h -eq 576 ] || [ $h -eq 480 ]; then # Half-D1 / CVD
aspect_ratio=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
aspect_ratio=4/3
fi
elif [ $w -eq 480 ]; then # SVCD
if [ $h -eq 576 ] || [ $h -eq 480 ]; then
aspect_ratio=4/3
fi
else
warn "Couldn't guess aspect ratio."
aspect_ratio=$(bc -lq <<<"$w / $h")
fi
local AR=$(sed -r 's/(\.[0-9]{2}).*/\1/g'<<<$aspect_ratio)
info "Aspect ratio set to $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" >/dev/null 2>&1
elif [ $decoder -eq $DEC_FFMPEG ]; then
# XXX: For some reason -ss before -i failed on my mkv sample
# while after -i it failed on my wmv9 sample ¿?
ffmpeg -y -ss $stamp -i "$f" -an -dframes 1 -vframes 1 -vcodec png \
-f rawvideo $shoehorned $VIDCAPFILE >/dev/null 2>&1
else
error "Internal error!"
return $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
}
 
# Draw a timestamp in the file
# apply_stamp($1 = filename, $2 = timestamp, $3 = width, $4 = height)
apply_stamp() {
local filename=$1 timestamp=$2 width=$3 height=$4
 
local temp=$(new_temp_file ".png")
mv "$filename" "$temp"
# Add the timestamp to each vidcap, doing it here is much powerful/simple
# than with the next montage command
# Note the '!', it is necessary to apply aspect ratio change
convert -box '#000000aa' -geometry ${width}x${height}! \
-fill $fg_tstamps -pointsize $pts_tstamps -gravity SouthEast \
-stroke none -strokewidth 3 -annotate +5+5 " $(pretty_stamp $stamp) " \
"$temp" "$filename"
if [ ! -f "$filename" ]; then
error "Failed to add timestamp to capture"
mv "$temp" "$filename" # Leave things as before
return $EX_CANTCREAT
fi
}
 
# 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 | cut -d. -f1)
# 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_number "${VID[$LEN]}"
}
 
# Main function.
# Creates the contact sheet.
# process($1 = file)
process() {
local f=$1
 
local numcols=$cols
 
if [ ! -f "$f" ]; then
error "File \"$f\" doesn't exist"
return $EX_NOINPUT
fi
info "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
guess_aspect
fi
local vidcap_width=$(compute_width $vidcap_height)
 
local numsecs=$(grep ID_LENGTH <<<"$MPLAYER_CACHE"| cut -d'=' -f2 | cut -d. -f1)
local nc=$numcaps
 
# Contact sheet minimum cols:
if [ $nc -lt $numcols ]; then
numcols=$nc
fi
 
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 )
 
# Highlighs
local hlfile="$VCSTEMPDIR/highlights.png" n=1 # Must be outside the if!
if [ "$HLTIMECODES" ]; then
local hlmontage_command="montage -gravity SouthEast -texture xc:LightGoldenRod "
local hlcapfile=
local pretty=
for stamp in $(clean_timestamps "${HLTIMECODES[*]}"); do
if [ $stamp -gt $numsecs ]; then let 'n++' && continue ; fi
pretty=$(pretty_stamp $stamp)
info "Generating highlight #${n}/${#HLTIMECODES[*]} ($pretty)..."
 
capture "$f" $stamp || return $?
apply_stamp "$VIDCAPFILE" $pretty $vidcap_width $vidcap_height || return $?
 
hlcapfile=$(new_temp_file "-hl-$(pad 6 $n).png")
mv "$VIDCAPFILE" "$hlcapfile"
hlmontage_command+=" \"$hlcapfile\""
let 'n++'
done
 
#if [ "$title" ]; then
# hlmontage_command+=" -font $font_heading -fill $fg_heading -title '$title'"
#fi
info "Composing highlights contact sheet..."
eval "$hlmontage_command -geometry ${vidcap_width}x${vidcap_height}!+10+5 \
-tile ${numcols}x -shadow \"$hlfile\""
unset hlcapfile hlmontage_command pretty
fi
unset n
 
# Normal captures
# TODO: Don't reference $VIDCAPFILE
local capfile= pretty= n=1 montage_command=$base_montage_command
for stamp in $(clean_timestamps "${TIMECODES[*]}"); do
pretty=$(pretty_stamp $stamp)
# Note that it must be checked against numsecs and not endsec, to allow
# the user manually setting stamps beyond the boundaries
# This shouldn't occur automatically anymore with the new code.
if [ $stamp -gt $numsecs ]; then let 'n++' && continue; fi
 
info "Generating capture #${n}/${#TIMECODES[*]} ($pretty)..."
 
capture "$f" $stamp || return $?
apply_stamp "$VIDCAPFILE" $pretty $vidcap_width $vidcap_height || return $?
capfile=$(new_temp_file "-cap-$(pad 6 $n).png")
# move to tempdir/<frame num>.png, cap num is padded to 6 characters
mv "$VIDCAPFILE" "$capfile"
montage_command+=" \"$capfile\""
let 'n++' # $n++
done
unset capfile pretty n
 
# geometry affects the source images, not the target one!
# Note the file name could also be added by using the "-title" option, but I reserved
# it for used set titles
# FIXME: Title should go before the highlights
montage_command+=" -geometry ${vidcap_width}x${vidcap_height}+10+5 -tile ${numcols}x -shadow"
if [ "$title" ]; then
montage_command+=" -font $font_heading -fill $fg_heading -title '$title'"
fi
montage_command+=" \"$output\""
 
info "Composing standard contact sheet..."
eval $montage_command # eval is required to evaluate correctly the text in quotes!
unset montage_command
 
# Extended mode
local extoutput=
if [ "$extended_factor" != 0 ]; then
# Number of captures. Always rounded to a multiplier of 2
# TODO: Round it to a multiplier of the number of columns
local hlnc=$(bc -q <<<"( (${#TIMECODES[*]} * $extended_factor) / 2 * 2)")
unset TIMECODES # required step to get the right count
TIMECODES=${initial_stamps[*]}
compute_timecodes $TC_NUMCAPS "" $hlnc
unset hlnc
 
local n=1 w= h= capfile= pretty= montage_command=$base_montage_command
extoutput=$(new_temp_file "-extended.png")
 
# 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)
info "Generating capture from extended set: ${n}/${#TIMECODES[*]} ($pretty)..."
capture "$f" $stamp || return $?
apply_stamp "$VIDCAPFILE" $pretty $w $h || return $?
capfile=$(new_temp_file "-excap-$(pad 6 $n).png")
mv "$VIDCAPFILE" "$capfile"
montage_command+=" \"$capfile\""
let 'n++'
done
montage_command+=" -geometry ${w}x${h}+5+2 -tile $(($numcols * 2))x -shadow"
info "Composing extended contact sheet..."
eval $montage_command "$extoutput"
unset montage_command w h capfile pretty n
fi
 
# Codec "prettyfication"
# Official FourCCs: <http://msdn2.microsoft.com/en-us/library/ms867195.aspx>
# Unofficial list: <http://www.fourcc.org/>
# Another software with a list of fourccs -> name mappings:
# <http://webcvs.freedesktop.org/clipart/experimental/rejon/getid3/getid3/module.audio-video.riff.php?view=markup>
local vcodec= acodec=
case $( tr '[A-Z]' '[a-z]' <<<${VID[$VCODEC]}) in
xvid) vcodec=Xvid ;;
dx50) vcodec="DivX 5" ;;
0x10000001) vcodec="MPEG-1" ;;
0x10000002) vcodec="MPEG-2" ;;
0x00000000) vcodec="Raw RGB" ;; # How correct is this?
avc1) vcodec="MPEG-4 AVC" ;;
wmv1) vcodec="WMV7" ;;
wmv2) vcodec="WMV8" ;;
wmv3) vcodec="WMV9" ;;
fmp4) vcodec="FFmpeg" ;; # XXX: Would LAVC be a better name?
mp42) vcodec="MS MPEG-4 v2" ;;
mpg4) vcodec="MS MPEG-4 v1" ;;
mp43) vcodec="MS MPEG-4 v3" ;;
div3) vcodec="DivX ;) Low Motion" ;; # Technically same as mp43
i420) vcodec="Raw I420" ;; # XXX: 420 presumably stands by 4:2:0 ?
rv10) vcodec="RealVideo 1.0/5.0" ;;
rv20) vcodec="RealVideo G2" ;;
svq1) vcodec="Sorenson Video 1" ;;
svq3) vcodec="Sorenson Video 3" ;;
# These are known FourCCs that I haven't tested against so far
wmva) vcodec="WMV9 Advanced Profile" ;; # Not VC1 compliant. Unsupported.
rv30) vcodec="RealVideo 8" ;;
rv40) vcodec="RealVideo 9/10" ;;
div4) vcodec="DivX ;) Fast Motion" ;;
divx) vcodec="DivX" ;; # OpenDivX / DivX 5(?)
iv50) vcodec="Indeo 5.0" ;;
mjpg) vcodec="M-JPEG" ;; # XXX: Actually mJPG != MJPG
# Legacy(-er) codecs (haven't seen files in these formats in awhile)
iv32) vcodec="Indeo 3.2" ;;
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
 
case $( tr '[A-Z]' '[a-z]' <<<${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
 
 
local meta="Filename: $(basename "$f")
File size: $(get_pretty_size "$f")
Length: $(pretty_stamp "${VID[$LEN]}")"
local meta2="Dimensions: ${VID[$W]}x${VID[$H]}
Format: $vcodec / $acodec
FPS: ${VID[$FPS]}"
local signature="$user_signature $user
$PROGRAM_SIGNATURE"
unset acodec vcodec
 
if [ "$HLTIMECODES" ] || [ "$extended_factor" != "0" ]; then
info "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 \( "$hlfile" -background LightGoldenRod \) \
\( "$output" \) -append "$output"
fi
# Extended captures
if [ "$extended_factor" != 0 ]; then
convert "$output" "$extoutput" -append "$output"
fi
 
info "Creating header and footer..."
# Now let's add meta info
# This one enlarges the image to add the text, and puts
# meta info in two columns
convert -font $font_heading -pointsize $pts_meta \
-background $bg_heading -fill $fg_heading -splice 0x$(( $pts_meta * 4 )) \
-gravity NorthWest -draw "text 10,10 '$meta'" \
-gravity NorthEast -draw "text 10,10 '$meta2'" \
"$output" "$output"
# Finishing touch, signature
 
convert -gravity South -font $font_sign -pointsize $pts_sign \
-background $bg_sign -splice 0x34+0-0 \
-fill $fg_sign -draw "text 10,3 '$signature'" "$output" "$output"
 
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
}
info "Done. Output wrote to $output_name"
 
cleanup
}
 
# Prints the program identification to stderr
show_vcs_info() { # Won't be printed in quiet modes
info "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.
-f|--from <arg> Set starting time. No caps before this. Same format
as -i.
-t|--to <arg> Set ending time. No caps beyond this. Same format
as -i.
-T|--title <arg> Add a title above the vidcaps.
-u|--user <arg> Set the username found in the signature to this.
-U|--fullname Use user's full/real name (e.g. John Smith) as found in
/etc/passwd.
-S|--stamp <arg> Add the image found at the timestamp "arg". Same format
as -i.
-l|--highlight <arg> Add the image found at the timestamp "arg" as a
highlight. Same format as -i.
-e[arg] | --extended=[arg]
Enables extended mode and optionally sets the extended
factor. By default it's $DEFAULT_EXT_FACTOR.
-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.
-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.
-A|--autoaspect Try to guess aspect ratio from resolution.
-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.
 
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: Currently just prints the parsed
commandline as the title and to stderr.
 
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
}
 
# }}} # Core functionality
 
#### Execution starts here ####
 
# Execute exithdlr on exit
trap exithdlr EXIT
 
show_vcs_info
 
load_config
 
# {{{ # Command line parsing
 
# Based on getopt-parse.bash example.
# On debian systems see </usr/share/doc/util-linux/examples/getopt-parse.bash.gz>
 
# TODO: use no name at all with -u noarg
#eval set -- "${default_options} ${@}"
TEMP=$(getopt -s bash -o i:n:u:T:f:t:S:jhFMH:c:ma:l:De::UqAO: \
--long "interval:,numcaps:,username:,title:,from:,to:,stamp:,jpeg,help,"\
"shoehorn:,mplayer,ffmpeg,height:,columns:,manual,aspect:,highlight:,"\
"extended::,fullname,quiet,autoaspect,override:" \
-n $0 -- "$@")
 
eval set -- "$TEMP"
 
while true ; do
case "$1" in
-i|--interval)
if ! interval=$(get_interval "$2") ; then
error "Interval must be a (positive) number. Got '$2'."
exit $EX_USAGE
fi
if [ "$interval" -le 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)
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
;;
-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
;;
-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 ;;
-D) echo "Command line: $0 $*" && title="$0 $*" ; ;;
-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
;;
-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
;;
-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
;;
--) shift ; break ;;
*) error "Internal error! (remaining opts: $@)" ; exit $EX_SOFTWARE ;
esac
shift
done
 
# Remaining arguments
if [ ! "$1" ]; then
show_help
exit $EX_USAGE
fi
 
# 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
 
# }}} # Command line parsing
 
# vim:set ts=4 ai: #
Property changes:
Added: svn:executable
Added: svn:keywords
+Rev Id Date
\ No newline at end of property
/video-contact-sheet/tags/1.0.6b
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/tags/1.0.2b:r274
/video-contact-sheet/trunk/arch-package/PKGBUILD
0,0 → 1,28
# Maintainer: Toni Corvera <outlyer@gmail.com>
 
pkgname=vcs
pkgver=1.11
pkgrel=1.upstream
pkgdesc="tool to create contact sheets (previews) from videos"
arch=('any')
url="http://p.outlyer.net/vcs/"
license=('LGPL')
depends=('bash>=2.05b' 'imagemagick>=6.3.5.7' 'mplayer' 'ffmpeg')
makedepends=('bzip2')
optdepends=('lsdvd: for DVD support'
'perl: for DVD support')
backup=()
source=($url/files/$pkgname-$pkgver.bz2)
md5sums=('c0cc4152278d93836bfc920e9e7a1078') #generate with 'makepkg -g'
sha1sums=('f08f07ba655355942a36a8ee695d88c86965a471')
sha256sums=('056233236a3ed06249af652e5d11e843586d025974b131c173b38a06bd8f2bbd')
 
build() {
# cd $srcdir/$pkgname-$pkgver
# bzip2 -dc $pkgname-$pkgver.bz2 > $pkgname-$pkgver
install -D -m755 $pkgname-$pkgver ${pkgdir}/usr/bin/$pkgname || return 1
}
 
#man page (TODO)
# install -D -m644 $pkgname.1 ${pkgdir}/usr/share/man1/$pkgname.1 || return 1
 
/video-contact-sheet/trunk/CHANGELOG
0,0 → 1,320
1.11.1: (2010-03-11)
* Added FLV1 codec
* BUGFIXES:
- Deprecate DEFAULT_INTERVAL, DEFAULT_NUMCAPS and DEFAULT_COLS as
overrides, warn about their new names (interval, numcaps and cols)
- Fix ImageMagick version detection
 
1.11: (2010-03-07)
* FEATURES
- Allow setting output filename. With extension will set output format,
without will inherit it.
- Allow percentages in height.
- Require mplayer OR ffmpeg instead of both. Having both is still
recommended for better results.
- Safe mode, for files whose length doesn't get reported correctly.
Completely automated.
Number of tries can be increased with -Ws. Repeat to increase further.
Use -WS to do try as many times as possible.
Accuracy (stepping) can be increased with -Wp. Repeat to increase
accuracy. Decrease with -WP.
Can be deliberately disabled with -Wb to force processing of broken
files. *VCS WITH -Wb WILL FAIL ON BROKEN FILES*
- Added -dp (--disable padding) equivalent to overriding HPAD to 0
* BUGFIXES:
- Don't pass ms to mplayer. It ignores them anyway and in some rare
cases breaks the last capture (possibly due to the 5-frames hack)
- Honor detected aspect ratio if found
- Try to detect files that might fail on the last capture and trigger
safe mode
- Timestamps font was being ignored. As a side effect this produced
italiced timestamps in some systems
- Fixed obscure bug with safe_rename_pattern overrides
* COMPAT: Support for bash 2.05b. This will (probably) be the last version
capable of running under bash 2.
* DVD mode revamp
- Print title file size instead of disc size when possible
- Aspect ratio detection, if available
- Use of FFmpeg if available to get better information
- Mostly x-platform, only ISOs identification is a bit better in Linux
* Added FourCCs: 3IV1, 3IV2 (3ivx); s263 (H.263); mp4v, MP4V, H264
(MPEG-4 and AVC in mov/mp4), VP6F (VP6 Flash Version), AMR
Video codec renamings:
- TechSmith codec name shortened to TechSmith SCC
- Raw RGB renamed to Raw video
* Help cleanup. The default help output is much shorter, the full text
can be displayed with --fullhelp. Also print the decoder choice near
the appropriate option (-M/-F)
* Added --anonymous to help (never was in it)
* Drop requirement on seq/jot and bc, replaced by inline awk
... New requirement: Perl (only for DVDs).
* Adopt new/fixed numbering scheme
<http://p.outlyer.net/dox/vcs:devel:renumbering>
* Check ImageMagick version (must decide which is the real minimum
required)
* Non-latin fonts revamp:
- -I no longer works alone (use -Ij or -Ik instead)
- -Ik, -Ij and --nonlatin try to pick an appropriate font automatically
- -I accepts a font name or font filename like
-Ij=Kochi-Mincho-Regular or
-Ij=/usr/share/fonts/truetype/kochi/kochi-mincho.ttf
* Deprecated options:
--shoehorn: Will be removed unless someone really needs it.
--mincho: Replaced by --nonlatin
* COSMETIC:
- Default font switched to DejaVu Sans.
Font sizes reduced to accomodate the new default.
Should fall back to a sane default if it's not available
- Much tighter padding
- Smaller timestamps font by default
- Print friendlier timestamp when a capture fails
- Print program signature to console without colour
- Use main font by default in timestamps
- Heading background colour toned down
- Added colourised output when tput is not capable (i.e. FreeBSD)
- Added prefixes when colour is not available for console output
- Don't print lsdvd error channel is DVD mode
- Suppress mv errors (e.g. over VFS being unable to preserve)
* Minimum ImageMagick version set to 6.3.5-7
* Better detection of requirements (e.g. disallow decoders without png
support)
* Allow overriding height, number of captures, interval, columns, and
padding
* UNDOCUMENTED/DEBUG:
- Allow stopping the main loop before cleaning up (--undocumented hang)
- Identification-only mode. Might be promoted to an actual feature
(--undocumented idonly)
- Allow setting ffmpeg and mplayer path (--undocumented set_ffmpeg and
set_mplayer)
- Allow disabling either mplayer of ffmpeg (as if they weren't
installed (--undocumented disable_ffmpeg and disable_mplayer)
- Added -Wc to disable console colour, repeat to disable prefixes
* INTERNAL:
- assert()
- Cleanup: correctness checks converted to asserts, removal of old dead
code
- Typos
 
1.0.100a: (2009-04-10) (1.10)
* FEATURE: FreeBSD (7.1-RELEASE) support
* COMPATIBILITY:
- Call bash through env
- Ensure we're using the correct getopt version
- Try to use POSIX sed options when appropriate
- Replaced incompatible sed constructs
- Use mktemp's common GNU/BSD(/POSIX?) syntax
- Use jot instead of seq if required and available
* BUGFIX: Don't fail if tput is unable to change colours
* BUGFIX: Check for requirements before anything else
* INTERNAL: Cache tput output
* FEATURE: Added -R / --randomsource. Mainly useful for debugging,
also to repeat a set of results and compare outputs on different
systems
* Corrected info message in photos mode
 
1.0.99: (2009-03-11) (1.9)
* FEATURE: Experimental support for DVDs (-V)
* FEATURE: Added JPEG 2000 output format (-j2)
* FEATURE/COSMETIC: Polaroid mode now produces a polaroid-like frame, the
older version is now renamed as simply 'photos'
New "funky" modes: newer polaroid, photos (older polaroid),
polaroidframe
* Overrideable variables: DISABLE_SHADOWS and DISABLE_TIMESTAMPS (set to 1
to disable)
* BUGFIX/COSMETIC: Re-added the missed space before filename
* BUGFIX/COSMETIC: Reworked alignment and padding
* Timestamps size is adjusted with smaller captures
* BUGFIX: Fixed polaroid/rotate bug where all images overlapped on the same
position (reported by Aleksandar Urošević, formerly unreproducible)
* Better detection of video/audio features by falling back to ffmpeg when
appropriate
 
1.0.12: (2008-04-16) (1.8)
* BUGFIX/COSMETIC: Corrected 0ms timestamps
* COSMETIC: Re-added the (disabled for long) black border after highlights
* BUGFIX/COSMETIC: Corrected the count of captures in manual-only mode (-m)
* FEATURE: Added a minimun length to use the end offset
* BUGFIX: Fixed the regression on highlights from the last version (extra
padding was being added by IM automatically)
* INTERNAL: Simplified use of IM's identify
* BUGFIX: Fixed parsing of manual timestamps including milliseconds
(when seconds didn't include the s character they were accidentally
multiplied by 10!)
 
1.0.11: (2008-04-08) (1.7)
* 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) (1.6)
* 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
get a quicker response.
 
1.0.9a: (2007-06-10) (1.5.2, -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) (1.5.1, 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) (1.5)
* 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) (1.4.1, 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) (1.4)
* 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 success 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) (1.3)
* 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) (1.2.1, Brown bag hotfix)
* BUGFIX: Don't put the full video path in the heading
 
1.0.2b: (2007-04-14) (1.2)
* 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) (1.1)
* 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) (1.0)
* 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/trunk/debian-package/debian/changelog
0,0 → 1,50
vcs (1.11.1-upstream.1) experimental; urgency=low
 
* New version.
 
-- Toni Corvera <outlyer@gmail.com> Tue, 11 Mar 2010 00:07:28 +0100
 
vcs (1.11-upstream.1) experimental; urgency=low
 
* debian/control:
- Added min. bash version
- Rw-worded short description
- Don't Depend on bc anymore
- Remove mktemp (now coreutils) from Depends:, they're essential anyway
- Bumped min IM to 6.3.5-7
 
-- Toni Corvera <outlyer@gmail.com> Sun, 07 Mar 2010 21:47:41 +0100
 
vcs (1.0.100a-upstream.1) experimental; urgency=low
 
* New version
 
-- Toni Corvera <outlyer@gmail.com> Fri, 10 Apr 2009 17:08:33 +0200
 
vcs (1.0.99-upstream.0) experimental; urgency=low
 
* New version.
* debian/control:
- Added lsdvd as recommendation (required for dvd support)
- Using 'Homepage:'
 
-- Toni Corvera <outlyer@gmail.com> Wed, 11 Mar 2009 22:50:25 +0100
 
vcs (1.0.12-upstream.1) experimental; urgency=low
 
* debian/control: Added missing requirement (gsfonts)
 
-- Toni Corvera <outlyer@gmail.com> Mon, 06 Oct 2008 14:26:27 +0200
 
vcs (1.0.12-upstream.0) experimental; urgency=low
 
* New version.
* Added suffix to version number.
 
-- Toni Corvera <outlyer@gmail.com> Wed, 16 Apr 2008 17:59:46 +0200
 
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/trunk/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
Homepage: http://p.outlyer.net/vcs/
 
Package: vcs
Architecture: all
Depends: bash (>= 2.05b), imagemagick (>= 6.3.5-7), mplayer | ffmpeg
Recommends: lsdvd, ttf-dejavu-core
Description: tool to create contact sheets (previews) 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.
/video-contact-sheet/trunk/debian-package/debian/copyright
0,0 → 1,35
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'.
 
/video-contact-sheet/trunk/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/trunk/debian-package/debian/dirs
0,0 → 1,0
usr/bin
/video-contact-sheet/trunk/debian-package/debian/compat
0,0 → 1,0
5
/video-contact-sheet/trunk/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/trunk/vcs
0,0 → 1,4053
#!/usr/bin/env bash
#
# $Rev$ $Date$
#
# vcs
# Video Contact Sheet *NIX: Generates contact sheets (previews) of videos
#
# Copyright (C) 2007, 2008, 2009, 2010 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>
#
# (Note: The references that used to be here have been moved to
#+ <http://p.outlyer.net/dox/vcs:devel:references>)
 
declare -r VERSION="1.11.1"
declare -r RELEASE=1
 
# {{{ # CHANGELOG
# Last release changes:
# (The full changelog can be found at <http://p.outlyer.net/vcs/files/CHANGELOG>).
#
# 1.11.1:
# * Added FLV1 codec
# * BUGFIXES:
# - Deprecate DEFAULT_INTERVAL, DEFAULT_NUMCAPS and DEFAULT_COLS as
# overrides, warn about their new names (interval, numcaps and cols)
# - Fix ImageMagick version detection
# 1.11:
# * FEATURES
# - Allow setting output filename. With extension will set output format,
# without will inherit it.
# - Allow percentages in height.
# - Require mplayer OR ffmpeg instead of both. Having both is still
# recommended for better results.
# - Safe mode, for files whose length doesn't get reported correctly.
# Completely automated.
# Number of tries can be increased with -Ws. Repeat to increase further.
# Use -WS to do try as many times as possible.
# Accuracy (stepping) can be increased with -Wp. Repeat to increase
# accuracy. Decrease with -WP.
# Can be deliberately disabled with -Wb to force processing of broken
# files. *VCS WITH -Wb WILL FAIL ON BROKEN FILES*
# - Added -dp (--disable padding) equivalent to overriding HPAD to 0
# * BUGFIXES:
# - Don't pass ms to mplayer. It ignores them anyway and in some rare
# cases breaks the last capture (possibly due to the 5-frames hack)
# - Honor detected aspect ratio if found
# - Try to detect files that might fail on the last capture and trigger
# safe mode
# - Timestamps font was being ignored. As a side effect this produced
# italiced timestamps in some systems
# - Fixed obscure bug with safe_rename_pattern overrides
# * COMPAT: Support for bash 2.05b. This will (probably) be the last version
# capable of running under bash 2.
# * DVD mode revamp
# - Print title file size instead of disc size when possible
# - Aspect ratio detection, if available
# - Use of FFmpeg if available to get better information
# - Mostly x-platform, only ISOs identification is a bit better in Linux
# * Added FourCCs: 3IV1, 3IV2 (3ivx); s263 (H.263); mp4v, MP4V, H264
# (MPEG-4 and AVC in mov/mp4), VP6F (VP6 Flash Version), AMR
# Video codec renamings:
# - TechSmith codec name shortened to TechSmith SCC
# - Raw RGB renamed to Raw video
# * Help cleanup. The default help output is much shorter, the full text
# can be displayed with --fullhelp. Also print the decoder choice near
# the appropriate option (-M/-F)
# * Added --anonymous to help (never was in it)
# * Drop requirement on seq/jot and bc, replaced by inline awk
# ... New requirement: Perl (only for DVDs).
# * Adopt new/fixed numbering scheme
# <http://p.outlyer.net/dox/vcs:devel:renumbering>
# * Check ImageMagick version (must decide which is the real minimum
# required)
# * Non-latin fonts revamp:
# - -I no longer works alone (use -Ij or -Ik instead)
# - -Ik, -Ij and --nonlatin try to pick an appropriate font automatically
# - -I accepts a font name or font filename like
# -Ij=Kochi-Mincho-Regular or
# -Ij=/usr/share/fonts/truetype/kochi/kochi-mincho.ttf
# * Deprecated options:
# --shoehorn: Will be removed unless someone really needs it.
# --mincho: Replaced by --nonlatin
# * COSMETIC:
# - Default font switched to DejaVu Sans.
# Font sizes reduced to accomodate the new default.
# Should fall back to a sane default if it's not available
# - Much tighter padding
# - Smaller timestamps font by default
# - Print friendlier timestamp when a capture fails
# - Print program signature to console without colour
# - Use main font by default in timestamps
# - Heading background colour toned down
# - Added colourised output when tput is not capable (i.e. FreeBSD)
# - Added prefixes when colour is not available for console output
# - Don't print lsdvd error channel is DVD mode
# - Suppress mv errors (e.g. over VFS being unable to preserve)
# * Minimum ImageMagick version set to 6.3.5-7
# * Better detection of requirements (e.g. disallow decoders without png
# support)
# * Allow overriding height, number of captures, interval, columns, and
# padding
# * UNDOCUMENTED/DEBUG:
# - Allow stopping the main loop before cleaning up (--undocumented hang)
# - Identification-only mode. Might be promoted to an actual feature
# (--undocumented idonly)
# - Allow setting ffmpeg and mplayer path (--undocumented set_ffmpeg and
# set_mplayer)
# - Allow disabling either mplayer of ffmpeg (as if they weren't
# installed (--undocumented disable_ffmpeg and disable_mplayer)
# - Added -Wc to disable console colour, repeat to disable prefixes
# * INTERNAL:
# - assert()
# - Cleanup: correctness checks converted to asserts, removal of old dead
# code
# - Typos
#
# }}} # CHANGELOG
 
set -e
 
# Fail soon if this version of bash is too old for the syntax, don't expose bash to the newer
# syntax
# See the "Bash syntax notes" section for details
[ "$BASH_VERSINFO" ] && {
# Absolute minimum right now is 2.05b
if [ "${BASH_VERSINFO[0]}" -le 2 ]; then # Only bash <=2 needs extra testing
# I guess we can't expect any new bash2 release
if [ "${BASH_VERSINFO[0]}" -lt 2 ] ||
( [ "${BASH_VERSINFO[0]}" -eq 2 ] && [ "${BASH_VERSINFO[1]}" != '05b' ] ); then
echo "Bash 2.05b or higher is required" >&2
exit 1
fi
fi
}
 
# {{{ # TO-DO
# TODO / FIXME:
# * (1.12) Start replacing 'grep' with bash's '[[ =~ ]]'. Will break bash 2 compatibility for good
# * [[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. =>
#+ SUS v2: egrep is deprecated, grep -E replaces it
# * Change default DVD_TITLE to 0
# * Deprecations:
# OPTION/VAR -> ALTERNATIVE DEPRECATED FROM VERSION REMOVAL ETA
# --shoehorn -> --undocumented shoehorn 1.11 1.12
# --undocumented shoehorn -> NONE 1.12 1.13 or 1.14
# --funky -> --profile ? ?+1
# MIN_LENGTH_FOR_END_OFFSET -> none 1.11 1.12 or 1.13
# * No warning shown in 1.11. 1.12 will switch back default end offset either to 0 or a
# percentage *and* use it always (disable with -E0)
# end offset -> none ? ?+1
# --mincho -> --nonlatin 1.11 1.12
# * Better check for coherence of overrides
# * Variables cleanup:
# Variables will use a more uniform scheme, with prefixes where appropriate:
# - INTERNAL_*: Used internally to adapt messages and the like to the input
# - UNDFLAG_*: Undocumented flags. Used internally to keep track of undocumented modes (-Z)
# - USR_*: Holds values of variables as set by the user, either from overrides or from the
# command-line
# }}} # TO-DO
 
# {{{ # 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 config, most precedence
#
# The variables that can be overriden are below the block of constants ahead.
declare -r CFGFILE=~/.vcs.conf
 
# Default values, use interval, numcaps and cols to override
declare -ri DEFAULT_INTERVAL=300
declare -ri DEFAULT_NUMCAPS=16
declare -ri DEFAULT_COLS=2
 
# 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 ASPECT=8 VCNAME=9 ACNAME=10
# 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
# GETOPT must be correctly set or the script will fail.
# It can be set in the configuration files if it isn't in the path or
# the first getopt in the path isn't the right version.
# A check will be made and a warning with details shown if required.
declare GETOPT=getopt
# Set to 1 to print function calls
declare -i DEBUG=0
# 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='#afcd7a' # Background for meta info (size, codec...)
declare bg_sign=SlateGray #'#a2a9af' # Background for signature
declare bg_title=White # Background for the title (see -T)
declare bg_contact=White # Background for the captures
declare bg_tstamps='#000000aa' # Background for the timestamps box
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 for the title
# Fonts, use identify -list font to get the list, up to IM 6.3.5-7 was '-list type' [[IM1]]
# If a font is not available IM will pick a sane default. In theory it will be silent
# although in practica it prints an error
declare font_tstamps=DejaVu-Sans-Book # Used for timestamps over the thumbnails
declare font_heading=DejaVu-Sans-Book # Used for the meta info heading
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 for the title (see -T)
# Font sizes, in points
declare -i pts_tstamps=14 # Used for the timestamps
declare -i pts_meta=14 # Used for the meta info heading
declare -i pts_sign=10 # Used for the signature
declare -i pts_title=33 # Used for the title (see -T)
# See --shoehorn
declare shoehorned=
# See -E / $end_offset
declare -i DEFAULT_END_OFFSET=60
# If the video is less than this length, end offset won't be used at all
declare MIN_LENGTH_FOR_END_OFFSET=19m30s
# 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
# Verbosity level so far from the command line can only be muted (see -q)
# it can be overridden, though
declare -i verbosity=$V_ALL
# Set to 1 to disable colours in console output
declare -i plain_messages=0
# See coherence_check for more details
declare -i DISABLE_SHADOWS=0
declare -i DISABLE_TIMESTAMPS=0
 
# Introduced in 1.0.7b, revamped in 1.11:
# This font is used to display international names (i.e. CJK names) correctly
# Help from users actually needing this would be appreciated :)
# This variable is filled either automatically through the set_extended_font()
#+function (and option -Ij) or manually (with option -Ij=MyFontName)
# The automatic picks a semi-random one from the fonts believed to support CJK/Cyrillic
#+characters.
declare FONT_MINCHO= # Filename or font name as known to ImageMagick (identify -list font)
# Output of capturing programs is redirected here
declare stdout=/dev/null stderr=/dev/null
declare -i DVD_MODE=0 DVD_TITLE=1
declare DVD_FILE=
 
# Override-able since 1.11:
# Height of the thumbnails, by default use same as input
declare th_height= # *WILL CHANGE NAME*
declare interval=$DEFAULT_INTERVAL # Interval of captures (=numsecs/numcaps)
declare -i numcaps=$DEFAULT_NUMCAPS # Number of captures (=numsecs/interval)
# This is the horizontal padding added to each capture.
# Beware when changing this since extended set's alignment might break.
# When shadows are enabled this is ignored since they already add padding.
declare -i HPAD=2 # *WILL CHANGE NAME*
declare -i cols=$DEFAULT_COLS # Number of output columns
 
# }}} # End of override-able variables
 
# {{{ # Variables
 
# Options and other internal usage variables, no need to mess with this!
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 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
# Identification output from ffmpeg and mplayer for the current video
declare FFMPEG_CACHE=
declare MPLAYER_CACHE=
# This holds the parsed identification values, see also the Indexes in VID
# (defined in the constants block)
declare -a VID=( )
 
# These variables will hold the output of tput, used
# to colourise feedback
declare prefix_err= prefix_inf= prefix_warn= suffix_fback=
 
# 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
 
# Transformations/filters
# Operations are decomposed into independent optional steps, this allows
# to add some intermediate steps (e.g. polaroid/photo mode's frames)
# 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 (currently deprecated)
# * 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, [context, [index]] )
# They're executed in order by filter_vidcap()
declare -a FILTERS_IND=( 'filt_resize' 'filt_apply_stamp' 'filt_softshadow' )
# Deprecated: 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
 
# Sets which function is used to obtain random numbers valid values are
# bashrand and filerand.
# Setting it manually will break it, calling with -R changes this to filerand.
# See rand() for an explanation
declare RANDFUNCTION=bashrand
 
# Which file are we working on (i.e. how many times has process() been called)
declare -i FILEIDX=0
 
# Names for output files, each index is a file name, an empty index will use
# the input file and append an extension to it
declare -a OUTPUT_FILES=( )
 
# Mplayer and FFmpeg binaries. Will be detected.
# Don't set manually, if you need to override set the path temporarily, e.g.:
# $ env PATH=/whatever:$PATH vcs ...
# or use the undocumented (and unchecked!) appropriate option:
# $ vcs --undocumented set_ffmpeg=/mypath/ffmpeg
declare MPLAYER=
declare FFMPEG=
 
# When set to 1 the reported length by mplayer and ffmpeg won't be trusted
# and will trigger some custom tests.
# Enabled automatically on problematic files
declare -i QUIRKS=0
# If the reported lengths differ by at least this much QUIRKS will be enabled
declare QUIRKS_LEN_THRESHOLD=0.2
# When trying to determine the correct length, file will be probed each...:
declare QUIRKS_LEN_STEP=0.5 # ~ 10 frames @ 20fps
# Maximum number of seconds to "rewind" from reported length (after this
# vcs surrenders but processing continues with a rewinded length)
declare QUIRKS_MAX_REWIND=20
 
# Set when the console output will be in color. It doesn't control color!
declare HAS_COLORS=
 
declare -i multiple_input_files=0
 
# Internal counts, used only to adjust messages
declare -i INTERNAL_WS_C=0 # -Ws count
declare -i INTERNAL_WP_C=0 # -Wp count
declare -i INTERNAL_MAXREWIND_REACHED=0 # More -Ws in the command-line won't help
 
# }}} # 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'
'MIN_LENGTH_FOR_END_OFFSET'
'DEBUG'
'DISABLE_.*'
'th_height'
'interval'
'numcaps'
'HPAD'
'cols'
# Note GETOPT doesn't make sense to be overridden from the command-line
'GETOPT'
)
 
# 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 -a 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
 
end_offset=$DEFAULT_END_OFFSET
}
 
# 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=${ALLOWED_OVERRIDES[*]}
compregex=${compregex// /|} # = s ' ' => '|'
 
# Don't allow ';', FIXME: dunno how secure that really is...
# FIXME: ...it doesn't really work 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=$(egrep -o '^[[:space:]]*[a-zA-Z0-9_]*=.' <<<"$o" | cut -d'=' -f1 | egrep -o '[^ ]*')
local varval=$(egrep -o '^[^=]*=.*' <<<"$o" | cut -d'=' -f2-)
if [ "$varname" = "DEFAULT_INTERVAL" ]; then
warn '$DEFAULT_INTERVAL is deprecated, please use $interval instead'
varname=interval
elif [ "$varname" = "DEFAULT_NUMCAPS" ]; then
warn '$DEFAULT_NUMCAPS is deprecated, please use $numcaps instead'
varname=numcaps
elif [ "$varname" = "DEFAULT_COLS" ]; then
warn '$DEFAULT_COLS is deprecated, please use $cols instead'
varname=cols
fi
# FIXME: Security!
local curvarval=
eval curvarval='$'"$varname"
if [ "$curvarval" == "$varval" ]; then
warn "Ignored override '$varname' (already had same value)"
else
eval "$varname=\"$varval\""
eval "USR_$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 is a valid percentage (xx% or xx.yy%)
# is_percentage($1 = input)
is_percentage() {
egrep -q '^([0-9]+\.?([0-9])?+|(\.[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() {
awkex "int(${*//[ ,]/ * }+0.5)" # ' ' = ',' => '*'
}
 
# Like rmultiply() but always rounded upwards
ceilmultiply() {
# TODO: breaks with $@. Why?
awkex "int(${*//[ ,]/ * }+0.99999)" # ' ' = ',' => '*'
}
 
# Basic mathematic stuff
# min($1 = operand1, $2 = operand2)
min() { awk "BEGIN { if (($1) < ($2)) print ($1) ; else print ($2) }" ; }
max() { awk "BEGIN { if (($1) > ($2)) print ($1) ; else print ($2) }" ; }
abs() { awk "BEGIN { if (($1) < (0)) print (($1) * -1) ; else print ($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='!=' ;;
*) assert $LINENO "[ \"'$1' '$2' '$3'\" ] && false" && return $EX_SOFTWARE
esac
# Empty operands
if [ -z "$1" ] || [ -z "$3" ]; then
assert $LINENO "[ \"'$1' '$2' '$3'\" ] && false"
else
awk "BEGIN { if ($1 $op $3) exit 0 ; else exit 1 }"
fi
}
 
# Keep a number of decimals *rounded*
keepdecimals() {
local N="$1" D="$2"
awk "BEGIN { printf \"%.${D}f\", (($N)+0) }"
}
 
# Keep a number of decimals, last decimal rounded to lower
keepdecimals_lower() {
grep -q '\.' <<<"$1" || { echo "$1" ; return ; }
local D=${1/#*.} # Decimals only
echo ${1/%.*}.${D:0:$2} # Integer part + . + Number of decimals
}
 
# Evaluate in AWK. Intended for arithmetic operations.
#+Keep decimals. I.e. 5 = 5.000000...
# awkex($1 = expression)
awkexf() {
# By default awk prints in compact form (scientific notation and/or up to 6 digits/decimals),
# printf is used to avoid this, TODO: Is there any direct way?
# .%20f is clearly overkill but matches the all code (default bc -l)
awk "BEGIN { printf \"%.20f\", ($1)+0 }"
}
 
# Evaluate in AWK. Intended for arithmetic operations.
#+Use default output. I.e. 5 = 5
# awkex($1 = expression)
awkex() {
awk "BEGIN { print ($1)+0 }"
}
 
# converts spaces to newlines in a x-platform way [[FNL]]
# stonl($1 = string)
stonl() {
awk '{gsub(" ", "\n");print}' <<<"$1" | egrep -v '^$'
}
 
# Converts newlines to spaces portably
# nltos($1 = string)
nltos() {
awk '{printf "%s ",$0}' <<<"$1" | sed 's/ *//'
}
 
# bash version of ord() [[ORD]]
# prints the ASCII value of a character
ord() {
printf '%d' "'$1"
}
 
# Get file extension
filext() {
grep -o '\.[^.]*$' <<<"$1" | cut -d. -f2
}
 
# Wrapper around $RANDOM, not called directly, wrapped again in rand().
# See rand() for an explanation.
bashrand() {
echo $RANDOM
}
 
# Prepares for "filerand()" calls
# File descriptor 7 is used to keep a file open, from which data is read
# and then transformed into a number.
# init_filerand($1 = filename)
init_filerand() { # [[FD1]], [[FD2]]
test -r "$1"
exec 7<"$1"
# closed in exithdlr
}
 
# Produce a (not-really-)random number from a file, not called directly wrapped
# in rand()
# Note that once the file end is reached, the random values will always
# be the same (hash_string result for an empty string)
filerand() {
local b=
# "read 5 bytes from file descriptor 7 and put them in $b"
read -n5 -u7 b
hash_string "$b"
}
 
# Produce a random number
# $RANDFUNCTION defines wich one to use (bashrand or filerand).
# Since functions using random values are most often run in subshells
# setting $RANDOM to a given seed has not the desired effect.
# filerand() is used to that effect; it keeps a file open from which bytes
# are read and not-so-random values generated; since file descriptors are
# inherited, subshells will "advance" the random sequence.
# Argument -R enables the filerand() function
rand() {
$RANDFUNCTION
}
 
# produces a numeric value from a string
hash_string() {
local HASH_LIMIT=65536
local v="$1"
local -i hv=15031
local c=
if [ "$v" ]; then
for i in $(seqr 0 ${#v} ); do
c=$( ord ${v:$i:1} )
hv=$(( ( ( $hv << 1 ) + $c ) % $HASH_LIMIT ))
done
fi
echo $hv
}
 
# Applies the Pythagorean Theorem
# pyth_th($1 = cathetus1, $2 = cathetus2)
pyth_th() {
awkex "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() {
trace $FUNCNAME $@
# eval it even if it's numeric to strip leading zeroes. Note the quoting
if is_number "$1" ; then awkex "\"$1\"" ; return 0 ; fi
 
local s=$(tolower "$1") t r n
 
# Only allowed characters
if ! egrep -qi '^[0-9smh.]+$' <<<"$s"; then
return $EX_USAGE;
fi
 
# Two consecutive dots are no longer accepted
if egrep -q '\.\.' <<<"$s" ; then
return $EX_USAGE
fi
 
# Newer parsing code: replaces units by a product
# and feeds the resulting string to awk for evaluation
# Note leading zeroes will lead awk to believe they are octal numbers
# as a quick and dirty fix I'm just wrapping them in quotes, forcing awk
# to re-evaluate them, which appears to be enough to make them decimal.
# This is the only place where leading zeroes have no meaning.
 
# Split into lines of time + unit:
t=
for item in $(echo "$s" | grep -o '[0-9]*[hms]') ;do
n="\"$(echo $item | grep -o '[0-9]*')\"" # Number, quoted
t=$t$n$(echo $item | grep -o '[hms]') # + Number + Unit
done
# Split all ms or s.ms
for item in $(echo "$s" | grep -o '[0-9]*\.[0-9]*') ;do
t="${t}\"$item\" + "
done
# Seconds without unit. They must be preceded by h, m or s at this point
local secs=$(echo $s | egrep -o '.?[0-9]*$')
# When preceded by '.', they're ms
[ "$secs" ] && grep -q '\.'<<<"$secs" && secs=
# Quote and addition. Note BSD grep/egrep wants the anchor ($) or won't match
[ "$secs" ] && secs=" \"$(egrep -o '[0-9]*$'<<<"$secs")\" + "
t=${t//h/ * 3600 + }
t=${t//m/ * 60 + }
t=${t//s/ + }
t="$t$secs"
t=${t/% + /} # Strip empty addition
r=$(awkex "$t" 2>/dev/null)
 
# Negative and empty intervals
assert $LINENO "[ '$r' ] && [ '$t' ]"
assert $LINENO "fptest $r -gt 0"
 
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 -format '%w' "$1"
}
 
# Get Image Height
# imh($1 = file)
imh() {
identify -format '%h' "$1"
}
 
# Prints a number of seconds in a more human readable form
# e.g.: 3600 becomes 1:00:00
# pretty_stamp($1 = seconds)
pretty_stamp() {
assert $LINENO "is_float '$1'"
# Fully implemented in AWK to discard bc.
# As a bonus now it's much faster and compact
awk "BEGIN {
t=$1 ; NOTMS=($DEC_MPLAYER==$decoder);
MS=(t - int(t));
h=int(t / 3600);
t=(t % 3600);
m=int(t / 60);
t=(t % 60);
s=t
if (h != 0) h=h\":\" ; else h=\"\"
if (NOTMS!=1) ms=sprintf(\".%02d\", int(MS*100+0.5));
printf \"%s%02d:%02d%s\", h, m, s, ms
}"
# Note the rounding applied to $MS, it is required to match the precission passed on
# to ffmpeg
}
 
# Prints a given size in human friendly form
get_pretty_size() {
local bytes=$1
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
}
 
# 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_file_size($1 = file)
get_pretty_file_size() {
local f="$1"
local bytes=$(get_file_size "$f")
 
get_pretty_size "$bytes"
}
 
# Clean $safe_rename_pattern, which is override-able
# Since safe_rename() is called from $() it won't be able to affect global variables directly
sanitise_rename_pattern() {
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
# Hashmarks will break the regex used in safe_rename()
if grep -q '#' <<<"$safe_rename_pattern" ; then
warn "Illegal character \"#\" found in safe renaming pattern, resetting it"
safe_rename_pattern=$DEFAULT_SAFE_REN_PATT
fi
}
 
# mv quiet
# Move a file, be quiet about errors.
# Ownership preservation is a common error on vfs, for example
mvq() {
mv -- "$@" 2>/dev/null
}
 
# 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() {
trace $FUNCNAME $@
local from="$1"
local to="$2"
 
# Output extension
local ext=$(filext "$to")
# Output filename without extension
local b=${to%.$ext}
 
local n=1
while [ -f "$to" ]; do # Only executes if $2 exists
# Bash 2 and Bash 3 behave differently with substring replacement (${//}) and '%'
# Sed is a safer bet
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
 
mvq "$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...
# Neither busybox's nor BSD's du allow --bytes.
# Note that using "ls -H" is not an option for portability reasons either.
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"
}
 
# Du replacement. This differs from get_file_size in that it takes multiple arguments
dur() {
for file in $@ ; do
get_file_size "$file"
done
}
 
# Gets the size of the dvd device, in DVD mode
get_dvd_size() {
# FIXME: Case sensivity might break with iso9660
if [ -f "$DVD_MOUNTP/VIDEO_TS/VTS_${DVD_VTS}_1.VOB" ]; then
# Some VOBs available
local vfiles="$DVD_MOUNTP/VIDEO_TS/VTS_${DVD_VTS}_*.VOB"
# Print all sizes, each on a line, add '+' to the end of each line, add 0 to the end.
local feed="$(dur "$DVD_MOUNTP/VIDEO_TS/VTS_${DVD_VTS}_"*".VOB" | cut -f1 | sed 's/$/ + /') 0"
get_pretty_size $(awkex "$(nltos "$feed")")
else
echo "?"
fi
}
 
is_linux() {
uname -s | grep -iq '^Linux$'
}
 
# Get the mountpoint of a mounted image.
# This only works on Linux. *BSD normal users aren't able to use mdconfig -l
# Is there any better way?
get_dvd_image_mountpoint() {
if is_linux ; then
local lodev=$(/sbin/losetup -j "$DVD_FILE" | cut -d':' -f1 | head -1)
mount | grep "^$lodev " | cut -d' ' -f3
fi
}
 
# Tests the presence of all required programs
# test_programs()
test_programs() {
local retval=0 last=0
local nopng=0
 
MPLAYER=$(type -pf mplayer) || true
FFMPEG=$(type -pf ffmpeg) || true
 
# Test we can actually use FFmpeg
[ "$FFMPEG" ] && {
# Newer FF has -codecs, -formats, -protocols, older has only -formats
#+png is a codec so it's on different lists on newer and older
if ! "$FFMPEG" -formats 2>/dev/null | grep -q 'EV.* png' && \
! "$FFMPEG" -codecs 2>/dev/null | grep -q 'EV.* png' ; then
warn "FFmpeg can't output to png, won't be able to use it."
FFMPEG=''
nopng=1
fi
}
# Same for Mplayer
[ "$MPLAYER" ] && {
if ! "$MPLAYER" -vo help 2>&1 | grep -q 'png' ; then
warn "MPlayer can't output to png, won't be able to use it."
MPLAYER=''
nopng=1
fi
}
 
[ "$MPLAYER" ] || [ "$FFMPEG" ] || {
local pngwarn=
[ $nopng -eq 1 ] && pngwarn=', with PNG output support,'
error "mplayer and/or ffmpeg$pngwarn are required!"
let 'retval++,1'
}
 
 
if [ $decoder -eq $DEC_FFMPEG ] && [ -z "$FFMPEG" ]; then
decoder=$DEC_MPLAYER
elif [ $decoder -eq $DEC_MPLAYER ] && [ -z "$MPLAYER" ]; then
decoder=$DEC_FFMPEG
fi
 
# awk is required by SUS/POSIX but just to be sure...
for prog in convert montage identify mktemp grep egrep cut sed awk ; do
if ! type -pf "$prog" ; then
error "Required program $prog not found!"
let 'retval++,1'
fi >/dev/null
done
# TODO: [[x2]]
 
# Early exit
[ $retval -eq 0 ] || return $EX_UNAVAILABLE
 
# ImageMagick version. 6 is a must, I'm probably using some
# features that require a higher minor version
# Versions tested:
# * Fedora 9: IM 6.4.0
local ver
ver=$(identify -version | head -n1 | grep -o 'ImageMagick[[:space:]]*[^ ]*' |\
cut -f 2 -d' ')
if [ "$ver" ]; then
local verx=${ver//-/.}.0 # Extra .0 in case rev doesn't exist
local major=$(cut -d'.' -f1 <<<"$verx")
local minor=$(cut -d'.' -f2 <<<"$verx")
local micro=$(cut -d'.' -f3 <<<"$verx")
local rev=$(cut -d'.' -f4 <<<"$verx")
local serial=$(( $major * 100000 + $minor * 10000 + $micro * 100 + $rev))
if [ "$serial" -lt 630507 ]; then
error "ImageMagick 6.3.5-7 or higher is required. Found $ver." ;
let 'retval++,1'
fi
else
error "Failed to check ImageMagick version."
let 'retval++,1'
fi
 
[ $retval -eq 0 ] || return $EX_UNAVAILABLE
}
 
# Test wether $GETOP is a compatible version; try to choose an alternate if
# possible
choose_getopt() {
if ! type -pf $GETOPT ; then
# getopt not in path
error "Required program getopt not found!"
return $EX_UNAVAILABLE
fi >/dev/null
local goe= gor=0
# Try getopt. If there's more than one in the path, try all of them
for goe in $(type -paf $GETOPT) ; do
"$goe" -T || gor=$?
if [ $gor -eq 4 ]; then
# Correct getopt found
GETOPT="$goe"
break;
fi
done >/dev/null
if [ $gor -ne 4 ]; then
error "No compatible version of getopt in path, can't continue."
error " For details on how to correct this problems, see <http://p.outlyer.net/vcs#getopt>"
return $EX_UNAVAILABLE
fi
return 0
}
 
# 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 VCSTEMPDIR
unset TEMPSTUFF ; declare -a TEMPSTUFF
}
 
# Exit callback. This function is executed on exit (correct, failed or
# interrupted)
# exithdlr()
exithdlr() {
# I don't think that's really required anyway
if [ "$RANDFUNCTION" == "filerand" ]; then
7<&- # Close FD 7
fi
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
[ $plain_messages -eq 0 ] && echo -n "$prefix_err"
# 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$suffix_fback"
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
[ $plain_messages -eq 0 ] && echo -n "$prefix_warn"
echo "$1$suffix_fback"
fi >&2
}
#
# Print an informational message
# inf($1 = text)
inf() {
if [ $verbosity -ge $V_INFO ]; then
[ $plain_messages -eq 0 ] && echo -n "$prefix_inf"
echo "$1$suffix_fback"
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
}
 
#
# Tests if the filter chain contains the provided filter
# has_filter($1 = filtername)
has_filter() {
local filter= ref=$1
for filter in ${FILTERS_IND[@]} ; do
[ "$filter" == "$ref" ] || continue
return 0
done
return 1
}
 
#
# Enables prefixes in console output (instead of colour)
set_feedback_prefixes() {
prefix_err='[E] '
prefix_inf='[i] '
prefix_warn='[w] '
suffix_fback=
}
 
#
# Initialises the variables affecting colourised feedback
init_feedback() {
HAS_COLORS=
 
# tput might be preferable (Linux: man console_codes), but it doesn't
# work on FreeBSD to set colors
 
# Is tput available?
if type -pf tput >/dev/null ; then
# Is it able to set colours?
if tput bold && tput setaf 0 >/dev/null && tput sgr0 ; then
prefix_err=$(tput bold; tput setaf 1)
prefix_warn=$(tput bold; tput setaf 3)
prefix_inf=$(tput bold; tput setaf 2)
suffix_fback=$(tput sgr0)
HAS_COLORS="yes"
fi
fi
 
if [ -z "$HAS_COLORS" ]; then
# tput was not an option, let's try ANSI escape codes instead [[AEC]]
# TODO: Detect support
# Alternatively: $ perl -e 'print "\e[31m\e[1m"'
# echo -e is not portable but echo $'' is bash-specific so it should be fine...
# except when ANSI escape codes aren't supported of course
prefix_err=$(echo $'\033[1m\033[31m')
prefix_warn=$(echo $'\033[1m\033[33m')
prefix_inf=$(echo $'\033[1m\033[32m')
suffix_fback=$(echo $'\033[0m')
HAS_COLORS="yes"
fi
 
# Finally, if there's no colour support, use prefixes instead
if [ -z "$HAS_COLORS" ]; then
set_feedback_prefixes
fi
}
 
#
# seq replacement
# seq is not always present, jot is an alternative on FreeBSD. Instead, this is
# a direct replacement
# Note pure bash is *slower* than the awk (or perl) version
# seqr($1 = from, $2 = to, $3 = increment)
seqr() {
local from=$1 to=$2 inc=$3
[ "$inc" ] || inc=1
awk "BEGIN { for (i=$from;i<=$to;i+=$inc) print i }"
}
 
#
# assertion operator
# assert($1 = line, $* = code)
# TODO: Limit usage to values that will expand correctly always (i.e. not with quotes)
assert() {
[ $RELEASE -eq 1 ] && return
LINE=$1
shift
eval "$@" || {
error "Internal error at line $LINE: $@"
exit $EX_SOFTWARE
}
}
 
# }}} # Convenience functions
 
# {{{ # Core functionality
 
# Creates a new temporary directory
# create_temp_dir()
create_temp_dir() {
trace $FUNCNAME $@
 
[ "$VCSTEMPDIR" ] && return 0
 
# 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.
# Passing a full path template is more x-platform than using
# -t / -p
if [ -d /dev/shm ] && [ -w /dev/shm ]; then
VCSTEMPDIR=$(mktemp -d /dev/shm/vcs.XXXXXX)
else
[ "$TMPDIR" ] || TMPDIR="/tmp"
VCSTEMPDIR=$(env TMPDIR="$TMPDIR" mktemp -d "$TMPDIR/vcs.XXXXXX")
fi
if [ ! -d "$VCSTEMPDIR" ]; then
error "Error creating temporary directory"
return $EX_CANTCREAT
fi
TEMPSTUFF=( "${TEMPSTUFF[@]}" "$VCSTEMPDIR" )
}
 
# Resolve path. Realpath is not always available and readlink [[LC]] behaves differently in
# GNU and BSD. FIXME: Has AWK or bash something similar? This is the only place requiring perl!
realpathr() {
perl -e "use Cwd qw(realpath);print realpath('$1')"
}
 
# Create a new temporal file and print its filename
# new_temp_file($1 = suffix)
new_temp_file() {
trace $FUNCNAME $@
local r=$(env TMPDIR="$VCSTEMPDIR" mktemp "$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=( "${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 + ( $(rand) % $ncolours ) ))
convert -list color | sed -n "${lineno}{p;q;}" | cut -d' ' -f1 # [[R1#11]]
}
else # Pseudo-random mode, WIP!
randccomp() {
# colours are in the 0..65535 range, while RANDOM in 0..32767
echo $(( $(rand) + $(rand) + ($(rand) % 1) ))
}
randcolour() {
echo "rgb($(randccomp),$(randccomp),$(randccomp))"
}
fi
 
# Older IM output was pretty different. Since this is a mode used for testing
# I don't believe it's worth the effort to get it always right
# This used to be -list type. Was this an older IM version or a bug in vcs?
local nfonts=$(convert -list font | grep '^\s*Font:' | wc -l)
randfont() {
lineno=$(( $(rand) % $nfonts ))
convert -list font | grep -o 'Font:.*' | sed -n "${lineno}{p;q;}" | cut -d' ' -f2
}
 
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=$(awkex "$end - $st")
 
if fptest "$runlen" -lt $(get_interval "$MIN_LENGTH_FOR_END_OFFSET") ; then
# Min length to use end offset not met, it won't be used
inf "End offset won't be used, video too short."
eo=0
elif 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 for the video, 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=$(awkexf "(($end-$st)/2 + 1)")
else
inc=$(awkexf "(($end-$eo-$st)/$tcnumcaps)")
fi
else
error "Internal error"
return $EX_SOFTWARE
fi
# Keep three decimals, round to lower to avoid exceeding the video length
inc=$(keepdecimals_lower $inc 3)
 
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
local bound=$(awkexf "$end - $eo")
while fptest $stamp -le "$bound"; do
assert $LINENO fptest $stamp -ge 0
LTC=( "${LTC[@]}" "$stamp" )
stamp=$(keepdecimals_lower $(awkexf "$stamp + $inc") 3)
done
unset LTC[0] # Discard initial cap (=$st)
TIMECODES=( ${TIMECODES[@]} ${LTC[@]} ) # Don't quote or extra empty stamp!
}
 
# 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 $@
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
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 with ffmpeg
# capture_ffmpeg($1 = inputfile, $2 = outputfile, $3 = timestamp[, $4 = extra opts])
capture_ffmpeg() {
local f=$1
local o=$2
local ts=$3
# 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/ / $ts} -i "$f" ${wa_ss_af/ / $ts} -an \
-dframes 1 -vframes 1 -vcodec png \
-f rawvideo $4 $shoehorned "$o" >"$stdout" 2>"$stderr"
[ -f "$o" ] && [ "0" != "$(du "$o" | cut -f1)" ]
}
 
# Capture a frame with mplayer
# capture_mplayer($1 = inputfile, $2 = UNUSED, $3 = timestamp[, $4 = extra opts])
capture_mplayer() {
# Note mplayer CAN'T set the output filename
local f="$1"
local o=00000005.png
local ts=$3
 
# No point in passing ms to mplayer
ts=$(cut -d'.' -f1 <<<"$ts")
# Capture 5 frames and drop the first 4, fixes a weird bug/feature of mplayer ([M1])
 
{
if [ $DVD_MODE -eq 1 ]; then
"$MPLAYER" -sws 9 -ao null -benchmark -vo "png:z=0" -quiet \
-frames 5 -ss "$ts" $shoehorned -dvd-device "$DVD_FILE" \
$4 "dvd://$DVD_TITLE"
else
"$MPLAYER" -sws 9 -ao null -benchmark -vo "png:z=0" -quiet \
-frames 5 -ss "$ts" $4 $shoehorned "$f"
fi
 
} >"$stdout" 2>"$stderr"
rm -f 0000000{1,2,3,4}.png # Remove the first four
[ -f "$o" ] && [ "0" != "$(du "$o" | cut -f1)" ]
}
 
# 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_mplayer "$f" 'IGNOREME' "$stamp"
elif [ $decoder -eq $DEC_FFMPEG ]; then
# FIXME: ffmpeg can put the temporary file anywhere
capture_ffmpeg "$f" "$VIDCAPFILE" "$stamp"
else
error "Internal error!"
return $EX_SOFTWARE
fi || true
if [ ! -f "$VIDCAPFILE" ] || [ "0" == "$(du "$VIDCAPFILE" | cut -f1)" ]; then
[ $decoder -eq $DEC_MPLAYER ] && stamp=${stamp/%.*}
error "Failed to capture frame at $(pretty_stamp $stamp) (${stamp}s)"
return $EX_SOFTWARE
fi
 
return 0
}
 
# Applies all individual vidcap filters
# filter_vidcap($1 = filename, $2 = timestamp, $3 = width, $4 = height, $5 = context, $6 = index[1..])
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" "$5" "$6" ) -flatten "
done
local t=$(new_temp_file .png)
eval "convert '$1' $cmdopts '$t'"
# If $t doesn't exist returns non-zero
[ -f "$t" ] && mvq "$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, $5 = context, $6 = index)
filt_apply_stamp() {
trace $FUNCNAME $@
local filename=$1 timestamp=$2 width=$3 height=$4 context=$5 index=$6
 
local pts=$pts_tstamps
if [ $height -lt 200 ]; then
pts=$(( $pts_tstamps / 3 ))
elif [ $height -lt 400 ]; then
pts=$(( $pts_tstamps * 2 / 3 ))
fi
# If the size is too small they won't be readable at all
# With the original font 8 was the minimum, with DejaVu 7 is readable
if [ $pts -le 7 ]; then
pts=7
if [ $index -eq 1 ] && [ $context -ne $CTX_EXT ]; then
warn "Very small timestamps in use. Disabling them with -dt might be preferable"
fi
fi
# The last -gravity None is used to "forget" the previous gravity (otherwise it would
# affect stuff like the polaroid frames)
echo -n " \( -box '$bg_tstamps' -fill '$fg_tstamps' -stroke none -pointsize '$pts' "
echo -n " -gravity '$grav_timestamp' -font '$font_tstamps' -strokewidth 3 -annotate +5+5 "
echo " ' $(pretty_stamp $stamp) ' \) -flatten -gravity None "
}
 
# Apply a framed photo-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 ) ) ))
# The border is relative to the input size (since 1.0.99), with a maximum of 6
# Should probably be bigger for really big frames
# Note that only images below 21600px (e.g. 160x120) go below a 6px border
local border=$(( ($3*$4) / 3600 ))
[ $border -lt 7 ] || border=6
echo -n "-bordercolor white -border $border -bordercolor grey60 -border 1 "
}
 
filt_softshadow() {
# Before this was a filter, there was the global (montage) softshadow (50x2+10+10) and the
# photoframe inline softshadow 60x4+4+4
echo -n "\( -background black +clone -shadow 50x2+4+4 -background none \) +swap -flatten -trim +repage "
}
 
 
# Apply a polaroid-like border effect
# Based on filt_photoframe(), with a bigger lower border
# filt_polaroid($1 = filename, $2 = timestamp, $3 = width, $4 = height)
filt_polaroid() {
trace $FUNCNAME $@
# local file="$1" ts=$2 w=$3 h=$4
local border=$(( ($3*$4) / 3600 )) # Read filt_photoframe for details
[ $border -lt 7 ] || border=6
echo -n "-bordercolor white -mattecolor white -frame ${border}x${border} "
# FIXME: This is rather ugly (double-flipping) there's sure a better way
echo -n "\( -flip -splice 0x$(( $border*5 )) \) "
echo "-flip -bordercolor grey60 -border 1 +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=$(( ($(rand) % 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="$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= vpad= splice=
 
# The shadows already add a good amount of padding
if has_filter filt_softshadow ; then
hpad=0
vpad=0
splice=5x10
else
hpad=$HPAD
vpad=$HPAD
splice=0x8
fi
 
montage -background Transparent "$@" -geometry +$hpad+$vpad -tile "$cols"x "$output"
convert "$output" -background Transparent -splice $splice "$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 $(seqr 1 $numrows) ; do
col=0
rowfile=$(new_temp_file .png)
rowfiles=( "${rowfiles[@]}" "$rowfile" )
accoffset=0
cmdopts= # This command is pretty time-consuming, let's make it in a row
 
# Base canvas # Integrated in the row creation since 1.0.99
 
# Step through vidcaps (col=[0..cols-1])
for col in $(seqr 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
cmdopts="$cmdopts '$1' -geometry +${accoffset}+0 -composite "
 
offset=$(( $minoffset + ( $(rand) % $maxoffset ) ))
let 'accoffset=accoffset + w - offset'
shift
done
inf "Composing overlapped row $row/$numrows..."
eval convert -size ${canvasw}x${canvash} xc:transparent -geometry +0+0 "$cmdopts" -trim +repage "'$rowfile'" >&2
done
 
inf "Merging overlapped rows..."
output=$(new_temp_file .png)
 
cmdopts=
accoffset=0
local h
for row in "${rowfiles[@]}" ; do
w=$(imw "$row")
h=$(imh "$row")
minoffset=$(( $h / 8 ))
maxoffset=$(( $h / 4 ))
offset=$(( $minoffset + ( $(rand) % $maxoffset ) ))
# The row is also offset horizontally
cmdopts="$cmdopts '$row' -geometry +$(( $(rand) % $maxoffset ))+$accoffset -composite "
let 'accoffset=accoffset + h - offset'
done
# After the trim the image will be touching the outer borders and the heading and footer,
# older versions (prior to 1.0.99) used -splice 0x10 to correct the heading spacing, 1.0.99
# onwards uses -frame to add spacing in all borders + splice to add a bit more space on the
# upper border. Note splice uses the background colour while frame uses the matte colour
eval convert -size ${canvasw}x$(( $canvash * $cols )) xc:transparent -geometry +0+0 \
$cmdopts -trim +repage -bordercolor Transparent -background Transparent -mattecolor Transparent \
-frame 5x5 -splice 0x5 "$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 I replace spaces by newlines
local s=$1
stonl "$s" | sort -n | uniq
}
 
# Test the video at a given timestamp (to see if it can be reached)
# See safe_length_measure()
# probe_video($1 = input file, $2 = stamp)
probe_video() {
local f="$1"
local ts="$2"
local tempfile=
local ret=0
 
# create_temp_dir should have been called
 
# This time a resize filter is applied to the player to produce smaller
# output
if [ $decoder -eq $DEC_MPLAYER ]; then
tempfile=00000005.png
TEMPSTUFF=( "${TEMPSTUFF[@]}" "$tempfile" )
if ! capture_mplayer "$f" "IGNOREME" "$ts" "-vf scale=96:96"; then
ret=1
fi
elif [ $decoder -eq $DEC_FFMPEG ]; then
tempfile=$(new_temp_file '-safelen.png')
if ! capture_ffmpeg "$f" "$tempfile" "$ts" "-s 96x96"; then
ret=1
fi
else
assert $LINENO false
ret=1
fi
rm -f "$tempfile"
return $ret
}
 
# Try to guess a correct length for the video, taking the reported lengths a
# starting point
# safe_length_measure($1 = filename)
safe_length_measure() {
trace $FUNCNAME $@
local f="$1"
local len=${VID[$LEN]}
local tempfile=
local newlen=$len
local capturefn=
 
create_temp_dir
 
if probe_video "$1" $len ; then
inf " File looks fine, suspicion withdrawn"
echo "$len"
return 0
else
# Can't seek to the very end, adjust
warn "Starting safe length measuring (this might take a while)..."
local maxrew=$(min $QUIRKS_MAX_REWIND $(awkex "int($len)")) # At most we'll rewind 20 seconds
# -1 (-WS) => Rewind up to the start
# Might be -2, -4, ... e.g. (-WS -Ws)
if fptest $maxrew -ge $len || fptest "$maxrew" -lt 0 ; then
maxrew=$len
INTERNAL_MAXREWIND_REACHED=1
fi
for rew in $(seqr $QUIRKS_LEN_STEP $maxrew $QUIRKS_LEN_STEP); do
newlen=$(keepdecimals_lower $(awkex "$len - $rew") 3)
warn " ... trying $newlen"
if probe_video "$f" "$newlen" ; then
echo $newlen
return 0
fi
done
fi
# Hitting this line means we're doomed!
return 1
}
 
##### {{{{ Codec names
 
# Codecs TODO: Clean this
# Translates an mplayer codec id/fourcc to its name
get_vcodec_name() {
local vcid="$1"
local vcodec=
# Video codec "prettyfication", see [[R2]], [[R3]], [[R4]]
case "$vcid" in
0x10000001) vcodec="MPEG-1" ;;
0x10000002) vcodec="MPEG-2" ;;
0x00000000) vcodec="Raw video" ;; # How correct is this?
avc1|H264) vcodec="MPEG-4 AVC" ;; # H264 is used in mov/mp4
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" ;; # 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 SCC" ;;
VP6[012F]) 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" ;;
3IV2) vcodec="3ivx Delta 4.0" ;; # Rare but seen
FLV1) vcodec="Sorenson Spark (FLV1)" ;;
 
# 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" ;;
s263) vcodec="H.263" ;; # 3GPP
# Legacy(-er) codecs (haven't seen files in these formats in awhile)
IV3[0-9]) vcodec="Indeo Video 3" ;; # FF only recognises IV31
MSVC) vcodec="Microsoft Video 1" ;;
MRLE) vcodec="Microsoft RLE" ;;
3IV1) vcodec="3ivx Delta" ;;
# "mp4v" is the MPEG-4 fourcc *in mov/mp4/3gp*; but I also found MP4V (Apple's iTunes sample)
#mp4v|MP4V) vcodec="MPEG-4" ;;
mp4v|MP4V) vcodec="MPEG-4" ;;
# Synthetic, used for ffmpeg translations
vcs_divx) vcodec="DivX ;-)" ;;
*) # If not recognized fall back to FourCC
vcodec="$vcid"
;;
esac
echo "$vcodec"
}
 
# Translates an FFmpeg codec id to an MPlayer codec id/fourcc
# TODO: Clean this
translate_ffmpeg_vcodec_id() {
# The list of ffmpeg codecs might be retrieved by looking at the code but I
#+simply used the ffmpeg -formats / ffmpeg -codecs command
# Supported video decoders: $ ffmepg -codecs | grep '^ D.V'
local vcid="$1"
local mpid=
case "$vcid" in
mpeg1video) mpid="0x10000001" ;; # mpeg1video_vdpau?
mpeg2video) mpid="0x10000002" ;;
rawvideo) mpid="0x00000000" ;; # can't distinguish from I420
h264) mpid="avc1" ;;
mjpeg) mpid="MJPG" ;;
msmpeg4v1) mpid="MPG4" ;;
msmpeg4v2) mpid="MP42" ;;
theora) mpid="theo" ;;
camtasia) mpid="tscc" ;;
vp6|vp6a|vp6f) mpid="VP60" ;;
# FIXME List of codec id's I translate but haven't test:
# svq3, rv40, theora, camtasia, vp6*
# MPlayer uses uppercase while FFmpeg uses lowercase
rv10|rv20|rv30|rv40|svq1|svq3|wmv1|wmv2|wmv3) mpid=$(echo $vcid | tr '[a-z]' '[A-Z]') ;;
# FFmpeg doesn't print FourCC's so there's some codecs that can't be told apart
msmpeg4) mpid="vcs_divx" ;; # DIV3 = DIV4 = MP43
# XVID = DIVX = DX50 = FMP4 = ... = mpeg4
mpeg4) mpid="mp4v" ;; # Take advantage of an unamed MPEG-4
 
h263) mpid="s263" ;;
 
vc1) mpid="WVC1" ;; # In FF: WMVA = vc1
flv) mpid="FLV1" ;;
# Not supported (ff just prints the FourCC)
# IV4*, vp4
vp3) mpid="VP30" ;;
vp5) mpid="VP50" ;;
# Legacy(-er) codecs (haven't seen files in these formats in awhile)
# MSVC? MRLE?
indeo3) mpid="IV31" ;;
*) # If not recognized fall back to FourCC
mpid="$vcid"
;;
 
esac
echo $mpid
}
 
get_acodec_name() {
local acid="$1"
local acodec=
 
if grep -q '[ -]' <<<"$acid" ; then
# Won't be recognised anyway
echo "$acid"
return
fi
 
case "$(tolower "$acid")" 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, Ogg, probably others
acodec='Vorbis'
;;
qdm2) acodec="QDesign" ;;
"") acodec="no audio" ;;
samr) acodec="AMR" ;; # AMR-NB/AMR-WB?
# 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="$acid"
;;
esac
echo "$acodec"
}
 
translate_ffmpeg_acodec_id() {
local acid="$1"
local mpid=
# ffmpeg -codecs | grep ^\ D.A
case "$acid" in
mp3) mpid='85' ;;
# Note FF can tell apart mp1/mp2 directly
mp1) mpid='MPEG Layer I (MP1)' ;;
mp2) mpid='MPEG Layer II (MP2)' ;;
aac) mpid='mp4a' ;; # Can aac be MPEG2?
wmav1) mpid='352' ;;
wmav2) mpid='353' ;;
wmapro) mpid='354' ;; # Actually WMA9 Professional
ac3) mpid='8192' ;;
# FF has a ton of pcm variants (sign, endianness, ...)
pcm_*) mpid="1" ;;
vorbis) mpid="vrbs" ;;
 
qdm2) mpid="QDM2" ;;
libopencore_amrnb) mpid="AMR-NB" ;;
libopencore_amrwb) mpid="AMR-WB" ;;
*) # If not recognized show audio id tag
mpid="$acid"
;;
esac
echo "$mpid"
}
 
##### }}}} # Codec names
 
# Try to identify video properties using mplayer
# Fills $MPLAYER_CACHE with the relevant output and $VID_MPLAYER with
# the actual values. See identify_video()
# mplayer_identify($1 = file)
mplayer_identify() {
trace $FUNCNAME $@
[ "$MPLAYER" ] || return
local f="$1"
local mi=( )
# Note to self: Don't change the -vc as it would affect $vdec
if [ $DVD_MODE -eq 0 ]; then
MPLAYER_CACHE=$("$MPLAYER" -benchmark -ao null -vo null -identify -frames 0 \
-quiet "$f" 2>/dev/null | grep ^ID)
else
MPLAYER_CACHE=$("$MPLAYER" -benchmark -ao null -vo null -identify -frames 0 \
-quiet -dvd-device $DVD_FILE dvd://$DVD_TITLE \
2>/dev/null | grep ^ID)
fi
# Note the head -1!
mi[$VCODEC]=$(grep ID_VIDEO_FORMAT <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2) # FourCC
mi[$ACODEC]=$(grep ID_AUDIO_FORMAT <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2)
mi[$VDEC]=$(grep ID_VIDEO_CODEC <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2) # Decoder (!= Codec)
mi[$W]=$(grep ID_VIDEO_WIDTH <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2)
mi[$H]=$(grep ID_VIDEO_HEIGHT <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2)
mi[$FPS]=$(grep ID_VIDEO_FPS <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2)
# For some reason my (one track) samples have two ..._NCH, first one 0
#+Also multichannel is detected as 2 ch
mi[$CHANS]=$(grep ID_AUDIO_NCH <<<"$MPLAYER_CACHE"| grep -v '=0' | cut -d'=' -f2|head -1)
if [ $DVD_MODE -eq 0 ]; then
# For DVDs it prints ID_DVD_TITLE_x_LENGTH and ID_LENGTH.
#+Both appear valid.
mi[$LEN]=$(grep ID_DVD_TITLE_${DVD_TITLE}_LENGTH <<<"$MPLAYER_CACHE"| cut -d'=' -f2)
[ "${mi[$LEN]}" ] || mi[$LEN]=$(grep ID_LENGTH <<<"$MPLAYER_CACHE"| head -1 | cut -d'=' -f2)
else
mi[$LEN]=$(grep ID_DVD_TITLE_${DVD_TITLE}_LENGTH <<<"$MPLAYER_CACHE"| head -1 | cut -d'=' -f2)
fi
# Voodoo :P Remove (one) trailing zero
if [ "${mi[$FPS]:$(( ${#mi[$FPS]} - 1 ))}" == "0" ]; then
mi[$FPS]="${mi[$FPS]:0:$(( ${#mi[$FPS]} - 1 ))}"
fi
mi[$ASPECT]=$(grep ID_VIDEO_ASPECT <<<"$MPLAYER_CACHE" | egrep -v '^0.0000$' | cut -d'=' -f2 | tail -1)
# If none set, delete it
[ "${mi[$ASPECT]}" ] && fptest "${mi[$ASPECT]}" -eq 0.0 && mi[$ASPECT]=''
mi[$VCNAME]=$(get_vcodec_name "${mi[$VCODEC]}")
if [ "${mi[$VDEC]}" == "ffodivx" ] && [ "${mi[$VCNAME]}" != "MPEG-4" ]; then
mi[$VCNAME]="${mi[$VCNAME]} (MPEG-4)"
elif [ "${mi[$VDEC]}" == "ffh264" ]; then # At least two different fourccs use h264, maybe more
mi[$VCNAME]="${mi[$VCNAME]} (h.264)"
fi
mi[$ACNAME]=$(get_acodec_name "${mi[$ACODEC]}")
if [ "${mi[$ACODEC]}" == "samr" ] ; then
local adec=$(grep ID_AUDIO_CODEC <<<"$MPLAYER_CACHE" | head -1 | cut -d'=' -f2)
if [ "$adec" == "ffamrnb" ]; then
mi[$ACNAME]="AMR-NB";
fi
fi
 
# Array assignment
VID_MPLAYER=("${mi[@]}")
}
 
# Try to identify video properties using ffmpeg
# Fills $FFMPEG_CACHE with the relevant output and $VID_FFMPEG with
# the actual values. See identify_video()
# mplayer_identify($1 = file)
ffmpeg_identify() {
trace $FUNCNAME $@
[ "$FFMPEG" ] || return
# (AFAIK) Can't use ffmpeg in DVD Mode
#[ $DVD_MODE -eq 0 ] || return
local f="$1"
# DVD Devices *MUST* be mounted for the identifying process to even start
assert $LINENO "[ $DVD_MODE -eq 0 ] || [ '$DVD_MOUNTP' ]"
[ $DVD_MODE -eq 1 ] && [ "$DVD_DEVICE" ] && {
local vfile="$DVD_MOUNTP/VIDEO_TS/VTS_${DVD_VTS}_1.VOB"
if [ ! -r "$vfile" ]; then
error "Failed to locale mounted DVD. Detection will be less accurate."
return 0 # We can continue anyway
fi
f="$vfile"
}
 
local fi=( ) vs= as= obs= vsid=
# FFmpeg is relatively new, introduced in 1.0.99 so it needs more testing
FFMPEG_CACHE=$("$FFMPEG" -i "$f" -dframes 0 -vframes 0 /dev/null 2>&1 | egrep '(Stream|Duration:|^Seems)')
# Only the first streams of each type are honored. FIXME: Add multi-audio support.
vs=$(grep Stream <<<"$FFMPEG_CACHE" | grep 'Video:' | head -1)
as=$(grep Stream <<<"$FFMPEG_CACHE" | grep 'Audio:' | head -1)
obs=$(grep Seems <<<"$FFMPEG_CACHE")
# Stream #0.0: Video: mpeg4, yuv420p, 624x352 [PAR 1:1 DAR 39:22], 23.98 tbr, 23.98 tbn, 24k tbc
# New and old versions of ffmpeg changed their output considerably, e.g.:
# (same file, Robotica_720.wmv)
# New output:
# Seems stream 1 codec frame rate differs from container frame rate: 1000.00 (1000/1) -> 23.98 (24000/1001)
# [...]
# Duration: 00:00:20.77, start: 3.000000, bitrate: 6250 kb/s
# Stream #0.0(eng): Audio: wmapro, 48000 Hz, 6 channels, s16, 384 kb/s
# Stream #0.1(eng): Video: wmv3, yuv420p, 1280x720, 6500 kb/s, 23.98 tbr, 1k tbn, 1k tbc
# Old output:
# Duration: 00:00:20.7, start: 3.000000, bitrate: 6250 kb/s
# Stream #0.0: Audio: 0x0162, 48000 Hz, 5:1, 384 kb/s
# Stream #0.1: Video: wmv3, yuv420p, 1280x720, 24.00 fps(r)
# TODO: tbr is rounded to two decimals but the actual ratio is printed:
# 24000/1001 = 23.97602
# (older ffmpeg prints 24 fps, 24/1 so no luck here
# **Also seen**: (note the 'tb(r)')
# Stream #0.1: Video: wmv3, yuv420p, 1440x1080 [PAR 4:3 DAR 16:9], 8000 kb/s, 23.98 tb(r)
# **Also seen**: (VOB, latest ffmpeg as of this writing):
# Stream #0.0[0x1e0]: Video: mpeg2video, yuv420p, 720x576 [PAR 64:45 DAR 16:9], 9800 kb/s, 23.53 fps, 25 tbr, 90k tbn, 50 tbc
# **Also seen**: (DVB TS to DX50 in MKV), note the DAR mess, the second one is the correct one
# Stream #0.0: Video: mpeg4, yuv420p, 640x326 [PAR 1:1 DAR 320:163], PAR 231:193 DAR 73920:31459, 25 fps, 25 tbr, 1k tbn, 25 tbc
vsid=$(grep -o '#0.[0-9]' <<<"$vs" | cut -d'.' -f2) # Video Stream ID
fi[$VCODEC]=$(egrep -o 'Video: [^,]*' <<<"$vs" | cut -d' ' -f2-)
# ffmpeg's codec might contain spaces in some cases, i.e. iv4 in mov (see mplayer's bestiary)
#+unless this turns out to be common I won't be handling it specially
# Note unidentified audio codecs will be printed in hexadecimal
fi[$ACODEC]=$(egrep -o 'Audio: [^,]*' <<<"$as" | cut -d' ' -f2)
fi[$VDEC]=''
# The comma is required for cases where the stream id is printed (in hex)
fi[$W]=$(egrep -o ', [0-9]*x[0-9]*' <<<"$vs" | sed 's/^, //' | cut -dx -f1)
fi[$H]=$(egrep -o ', [0-9]*x[0-9]*' <<<"$vs" | cut -dx -f2)
# Newer CHANS and some older...
fi[$CHANS]=$(egrep -o '[0-9]* channels' <<<"$as" | cut -d' ' -f1)
# ...fallback for older
if [ -z "${fi[$CHANS]}" ]; then
local chans=$(egrep -o 'Hz, [^,]*' <<<"$as" | cut -d' ' -f2)
case $chans in
mono) fi[$CHANS]=1 ;;
stereo) fi[$CHANS]=2 ;;
5.1|5:1) fi[$CHANS]=6 ;; # *
*) ;; # Other layouts use 'N channels'
# 5.1 was in the previous version (can't remember if it was empirical).
esac
fi
# Newer FPS...
# tbr/tbn/tbc explanation: tb stands for time base
# n: AVStream, c: AVCodecContext, r: VideoStream (Guessed)
# tbr is the best bet. Note it's common for WMVs to contains "1k tbn, 1k tbc"
# tbr is rounded to two decimals, the values used to derived it might be
# printed in a "Seems ..." line like the one in the example above so it
# can be re-calculated.
fi[$FPS]=$(egrep -o '[0-9]*\.?[0-9]*k? tb(r|\(r\))' <<<"$vs" | cut -d' ' -f1)
# Let's convert e.g. 23.98 into 23.976...:
if [ "${fi[$FPS]}" ] && grep -q '\.' <<<"${fi[$FPS]}" ; then
# Decimals, see if we got better values available
local vsobs=$(grep "stream $vsid" <<<"$obs")
# Observations regarding video stream found
if [ "$vsobs" ] && grep -q " -> ${fi[$FPS]} (.*)" <<<"$vsobs" ; then
# FPS candidate
local newfps=$(egrep -o -- '-> [^ ]* \([0-9]*/[0-9]*' <<<"$vsobs" | cut -d'(' -f2)
is_fraction $newfps && fi[$FPS]=$(keepdecimals "$newfps" 3)
fi
fi
# ...fallback for older. The older version I tried seems to round further, i.e.
# 23.976 became 24 so no fix for this one
if [ -z "${fi[$FPS]}" ]; then
# No k suffix here, 1000 is 1000
fi[$FPS]=$(egrep -o '[0-9]*\.?[0-9]* fps' <<<"$vs" | cut -d' ' -f1)
fi
# Be consistent with mplayer's output: at least two decimals
[ "${fi[$FPS]}" ] && {
fi[$FPS]=$(keepdecimals "${fi[$FPS]}" 3)
fi[$FPS]=${fi[$FPS]/%0} # Strip 0$
}
fi[$LEN]=$(egrep -o 'Duration: [^,]*' <<<"$FFMPEG_CACHE" | cut -d' ' -f2)
if [ "${fi[$LEN]}" == "N/A" ]; then # It might be unable to detect
fi[$LEN]=""
fi
fi[$LEN]=$( get_interval $(echo "${fi[$LEN]}" | sed 's/:/h/' | sed 's/:/m/') )
# Aspect ratio in FFmpeg is only provided in newer ffmpeg
# It might be calculated for files without one (which is ok anyway)
# TODO: Replace tail -1 with some better option (see the double DAR example above)
fi[$ASPECT]=$(egrep -o 'DAR [0-9]*:[0-9]*'<<<"$FFMPEG_CACHE" | tail -1 | cut -d' ' -f2 | sed 's#:#/#')
# Due to calling ffmpeg on a single VOB when in DVD Device mode, the length will be partial
[ $DVD_MODE -eq 1 ] && [ "$DVD_DEVICE" ] && fi[$LEN]=''
fi[$VCNAME]=$(get_vcodec_name $(translate_ffmpeg_vcodec_id "${fi[$VCODEC]}"))
fi[$ACNAME]=$(get_acodec_name $(translate_ffmpeg_acodec_id "${fi[$ACODEC]}"))
VID_FFMPEG=("${fi[@]}")
}
 
# Use the available tools to identify video meta-data
# fills $VID with the values
# Return codes:
# 3: Failed to detect length
# 4: Failed to detect width or height
# identify_video($1 = file)
identify_video() {
trace $FUNCNAME $@
local RET_NOLEN=3 RET_NODIM=4
 
[ "$MPLAYER" ] && mplayer_identify "$1"
# ffmpeg_identify in DVD mode only works when the DVD is mounted:
[ $DVD_MODE -eq 0 ] && [ "$FFMPEG" ] && ffmpeg_identify "$1"
[ $DVD_MODE -eq 1 ] && [ "$FFMPEG" ] && [ "$DVD_MOUNTP" ] && ffmpeg_identify "$1"
 
# Fail early if none detected length
[ -z "${VID_MPLAYER[$LEN]}" ] && [ -z "${VID_FFMPEG[$LEN]}" ] && return $RET_NOLEN
 
# Classic mode, use both mplayer and ffmpeg when available
if [ "$MPLAYER" ] && [ "$FFMPEG" ]; then
# By default take mplayer's values
VID=("${VID_MPLAYER[@]}")
# FFmpeg seems better at getting the correct number of FPS, specially with
# WMVs, where mplayer often accepts 1000fps while ffmpeg notices the
# inconsistency in container vs codec and guesses better, *but* it only
# uses two decimals so 23.976 becomes 23.98. So it is only used when
# the number of decimals seems right.
# When a "Seems..." line is printed the correct FPS can be obtained though.
[ -z "${VID_MPLAYER[$FPS]}" ] && VID[$FPS]=${VID_FFMPEG[$FPS]}
[ "${VID_MPLAYER[$FPS]}" ] && [ "${VID_FFMPEG[$FPS]}" ] && {
# Trust ffmpeg if it has three decimals OR if mplayer is probably-wrong
local ffps=${VID_FFMPEG[$FPS]}
echo $ffps | grep -q '\.[0-9][0-9][0-9]' && VID[$FPS]=$ffps || {
fptest "${VID_MPLAYER[$FPS]}" -gt 500 && VID[$FPS]=$ffps
}
}
# It doesn't appear to need any workarounds for num. channels either
[ "${VID_FFMPEG[$CHANS]}" ] && VID[$CHANS]=${VID_FFMPEG[$CHANS]}
[ "${VID_FFMPEG[$ASPECT]}" ] && VID[$ASPECT]=${VID_FFMPEG[$ASPECT]}
# There's a huge inconsistency with some files, both mplayer vs ffmpeg
# same application on different OSes
local fflen=${VID_FFMPEG[$LEN]} mplen=${VID_MPLAYER[$LEN]} # Shorthands
[ -z "$fflen" ] && fflen=0
# If both report 0, there's no good value...
fptest "$fflen" -eq 0 && fptest "$mplen" -eq 0 && return $RET_NOLEN
if [ $DVD_MODE -eq 0 ] && [ $QUIRKS -eq 0 ]; then # In DVD mode ffmpeg has no length
# Quirks disabled, should be enabled?
local delta=$(abs $(awkex "($fflen - $mplen)"))
# If they don't agree, take the shorter as a starting point,
#+if both are different than zero take min, if one of them is 0, take max to start
if fptest "$fflen" -ne 0 && fptest "$mplen" -ne 0 ; then
VID[$LEN]=$(min $fflen $mplen)
else
VID[$LEN]=$(max $fflen $mplen)
delta=$QUIRKS_LEN_THRESHOLD # Ensure it's considered inconsistent
fi
# If they differ too much, enter safe mode. If one reports 0, they'll differ...
# FIXME: If $decoder reports 0, can it seek??
if fptest "$delta" -ge $QUIRKS_LEN_THRESHOLD ; then
warn "Found inconsistency in reported length. Safe measuring enabled."
QUIRKS=1
fi
fi
elif [ "$MPLAYER" ]; then
# Must do with mplayer only...
VID=("${VID_MPLAYER[@]}")
# Warn if a known pitfall is found
# See above for 1000 fps
[ "${VID[$FPS]}" == "1000.00" ] && \
warn "Possible inaccuracy in FPS detection." && \
warn " Install both mplayer and ffmpeg for better detection."
# Number of channels 0 happened for WMA in non-x86
[ "${VID[$CHANS]}" == "0" ] && \
warn "Failed to detect number of audio channels." && \
warn " Install both mplayer and ffmpeg for better detection."
elif [ "$FFMPEG" ]; then
# Must do with mplayer only...
VID=("${VID_FFMPEG[@]}")
# So far I know of no weird results. Yet.
else
assert $LINENO 'false'
fi
 
# Ensure sanity of the most important values
is_float "${VID[$LEN]}" || return $RET_NOLEN
is_number "${VID[$W]}" && is_number "${VID[$H]}" || return $RET_NODIM
 
if [ "$FFMPEG" ]; then
# FPS at least with two decimals
if [ $(awkex "int(${VID[$FPS]})") == ${VID[$FPS]} ]; then
VID[$FPS]="${VID[$FPS]}.00"
fi
fi
 
local mfps="${VID_MPLAYER[$FPS]}"
if [ $QUIRKS -eq 0 ] && [ "$MPLAYER" ] && fptest "$mfps" -eq 1000 ; then
warn "Suspect file. Safe measuring enabled."
QUIRKS=1
fi
 
# Last safeguard: Try to reach the detected length, if it fails, trigger
# quirks mode
if [ $QUIRKS -eq 0 ]; then
create_temp_dir
if ! probe_video "$1" "${VID[$LEN]}" ; then
warn "Detected video length can't be reached. Safe measuring enabled."
QUIRKS=1
fi
fi
 
if [ $QUIRKS -eq 1 ]; then
VID[$LEN]=$(safe_length_measure "$1")
if [ -z "${VID[$LEN]}" ]; then
error "Couldn't measure length in a reasonable amount of tries."
if [ $INTERNAL_MAXREWIND_REACHED -eq 1 ]; then
error " Will not be able to capture this file with the current settings."
else
local reqs=$(( $INTERNAL_WS_C + 1 )) reqp=''
[ $reqs -eq 1 ] && reqp=" -WP" || reqp=" -WP$reqs"
[ $reqs -ge 3 ] && reqs=" -WS" || { # Third try => Recommend -WS
[ $reqs -eq 1 ] && reqs=" -Ws" || reqs=" -Ws$reqs"
}
assert 'fptest "$QUIRKS_MAX_REWIND" -gt 0'
local offby=$(pretty_stamp $QUIRKS_MAX_REWIND)
warn " Capturing won't work, video is at least $offby shorter than reported."
local dname='ffmpeg'
[ $decoder -eq $DEC_MPLAYER ] && dname='mplayer'
warn " Does $dname support ${VID[$VCODEC]}?."
warn " Try re-running with$reqs$reqp."
fi
return 1
fi
elif [ $QUIRKS -eq -2 ]; then
warn "Safe mode disabled."
fi
 
# Re-check sanity of the most important values
is_float "${VID[$LEN]}" || return $RET_NOLEN
is_number "${VID[$W]}" && is_number "${VID[$H]}" || return $RET_NODIM
}
 
dump_idinfo() {
trace $FUNCNAME $@
[ "$MPLAYER" ] && echo "Mplayer: $MPLAYER"
[ "$FFMPEG" ] && echo "FFmpeg: $FFMPEG"
[ "$MPLAYER" ] && cat <<-EODUMP
=========== Mplayer Identification ===========
Length: $(pretty_stamp ${VID_MPLAYER[$LEN]})
Video
Codec: ${VID_MPLAYER[$VCODEC]} (${VID_MPLAYER[$VCNAME]})
Dimensions: ${VID_MPLAYER[$W]}x${VID_MPLAYER[$H]}
FPS: ${VID_MPLAYER[$FPS]}
Aspect: ${VID_MPLAYER[$ASPECT]}
Audio
Codec: ${VID_MPLAYER[$ACODEC]} (${VID_MPLAYER[$ACNAME]})
Channels: ${VID_MPLAYER[$CHANS]}
==============================================
 
EODUMP
local ffl="${VID_FFMPEG[$LEN]}"
[ "$ffl" ] && ffl=$(pretty_stamp "$ffl")
[ -z "$ffl" ] && [ $DVD_MODE -eq 1 ] && ffl="(unavailable in DVD mode)"
[ "$FFMPEG" ] && cat <<-EODUMP
=========== FFmpeg Identification ===========
Length: $ffl
Video
Codec: ${VID_FFMPEG[$VCODEC]} (${VID_FFMPEG[$VCNAME]})
Dimensions: ${VID_FFMPEG[$W]}x${VID_FFMPEG[$H]}
FPS: ${VID_FFMPEG[$FPS]}
Aspect: ${VID_FFMPEG[$ASPECT]}
Audio
Codec: ${VID_FFMPEG[$ACODEC]} (${VID_FFMPEG[$ACNAME]})
Channels: ${VID_FFMPEG[$CHANS]}
=============================================
 
EODUMP
local xar=
if [ "${VID[$ASPECT]}" ]; then
xar=$(keepdecimals "${VID[$ASPECT]}" 4)
[ "$xar" ] && xar=" ($xar)"
fi
cat <<-EODUMP
=========== Combined Identification ===========
Length: $(pretty_stamp ${VID[$LEN]})
Video
Codec: ${VID[$VCODEC]} (${VID[$VCNAME]})
Dimensions: ${VID[$W]}x${VID[$H]}
FPS: ${VID[$FPS]}
Aspect: ${VID[$ASPECT]}$xar
Audio
Codec: ${VID[$ACODEC]} (${VID[$ACNAME]})
Channels: ${VID[$CHANS]}
=============================================
EODUMP
 
 
}
 
# Try to pick some font capable of handling non-latin text
set_extended_font() {
trace $FUNCNAME $@
# This selection includes japanese fonts
local candidates=$(identify -list font | grep 'Font: ' | \
egrep -io '[a-z-]*(kochi|mincho|sazanami|ipafont)[a-z-]*')
if [ -z "$candidates" ]; then
error "Unable to auto-select filename font, please provide one (see -fullhelp)"
return 1
else
if [ "$DEBUG" -eq 1 ]; then
local list=$(echo "$candidates" | sed 's/^/ >/g')
inf "Available non-latin fonts detected:"
inf "$list"
fi
fi
 
# Bias towards the Sazanami family
if grep -qi 'sazanami' <<<"$candidates" ; then
FONT_MINCHO=$(grep -i 'sazanami' <<<"$candidates" | head -1)
else
FONT_MINCHO=$(head -1 <<<"$candidates")
fi
}
 
# Checks if the provided arguments make sense and are allowed to be used
# together
coherence_check() {
trace $FUNCNAME $@
# 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)"
return $EX_USAGE
fi
 
if [ $decoder -eq $DEC_MPLAYER ] && [ -z "$MPLAYER" ]; then
inf "No mplayer available. Using ffmpeg only."
decoder=$DEC_FFMPEG
elif [ $decoder -eq $DEC_FFMPEG ] && [ -z "$FFMPEG" ]; then
inf "No ffmpeg available. Using mplayer only."
decoder=$DEC_MPLAYER
fi
 
if [ $DVD_MODE -eq 1 ] ; then
# Currently it's not allowed to use dvd mode with more than one input
# "file" (in this mode, input files are actually dvd titles of the file
# provided in -V)
if [ $multiple_input_files -eq 1 ]; then
error "Only an input file is allowed in DVD mode"
return $EX_UNAVAILABLE
fi
 
# DVD Mode only works with mplayer, the decoder is changed when
# the DVD mode option is found, so if it's ffmpeg at this point,
# it's by user request (i.e. -F after -V)
if [ $decoder -ne $DEC_MPLAYER ]; then
if [ "$MPLAYER" ]; then
warn "DVD mode requires the use of mplayer, falling back to it"
decoder=$DEC_MPLAYER
else
error "DVD mode requires the use of mplayer."
return $EX_UNAVAILABLE
fi
fi
fi
 
local filter=
if [ $DISABLE_TIMESTAMPS -eq 0 ] &&
local -a filts=( )
has_filter filt_polaroid && has_filter filt_apply_stamp ; then
 
for filter in ${FILTERS_IND[@]} ; do
if [ "$filter" == "filt_polaroid" ]; then
filts=( "${filts[@]}" "$filter" filt_apply_stamp )
elif [ "$filter" == "filt_apply_stamp" ]; then
continue;
else
filts=( "${filts[@]}" $filter )
fi
done
FILTERS_IND=( "${filts[@]}" )
unset filts
fi
# The shoftshadow and randrot filters must be in the correct place
# or they will affect the image incorrectly.
# Additionally the default filters can be disabled from the command
# line (with --disable), they're removed from the filter chain here
local -a filts=( ) end_filts=( )
for filter in ${FILTERS_IND[@]} ; do
case "$filter" in
filt_softshadow)
# Note the newer soft shadows code (1.0.99 onwards) behaves slightly
# differently. On previous versions disabling shadows only affected
# the montage shadow (but e.g. the polaroid mode preserved them),
# this is no longer true
if [ $DISABLE_SHADOWS -ne 1 ]; then
end_filts[100]="filt_softshadow"
fi
;;
filt_apply_stamp)
if [ $DISABLE_TIMESTAMPS -ne 1 ]; then
filts=( "${filts[@]}" "$filter" )
fi
;;
filt_randrot) end_filts[200]="filt_randrot" ;;
*) filts=( "${filts[@]}" "$filter" ) ;;
esac
done
FILTERS_IND=( "${filts[@]}" "${end_filts[@]}" )
 
# Override-able options check, in case they were set from overrides instead
#+of equivalent command-line options. Will check the actual selected values,
#+i.e. fail silently if the overrides aren't effective
[ "$USR_th_height" ] && { check_height "$th_height" || exit $? ; }
[ "$USR_numcaps" ] && { check_numcaps "$numcaps" || exit $? ; }
[ "$USR_interval" ] && { check_interval "$interval" || exit $? ; }
# Interval=0 == default interval
fptest "$interval" -eq 0 && interval=$DEFAULT_INTERVAL
 
sanitise_rename_pattern
}
 
check_height() { # Acceptable height
if ! is_number "$1" && ! is_percentage "$1" ; then
error "Height must be a (positive) number or a percentage. Got '$1'."
return $EX_USAGE
fi
}
 
check_numcaps() { # Acceptable numcaps
if ! is_number "$1" ; then
error "Number of captures must be a (positive) a number! Got '$1'."
return $EX_USAGE
fi
if [ $1 -eq 0 ]; then
error "Number of captures must be greater than 0! Got '$1'."
return $EX_USAGE
fi
}
 
check_interval() { # Acceptable interval
if ! get_interval "$1" >/dev/null ; then
error "Incorrect interval format. Got '$1'."
return $EX_USAGE
fi
}
 
# Main function.
# Creates the contact sheet.
# process($1 = file)
process() {
trace $FUNCNAME $@
local f=$1
 
local numcols=
# Save variables that will be overwritten and must be reset with multiple files
# pre_* will contain the user-supplied or default values
local pre_quirks=$QUIRKS
local pre_aspect_ratio=$aspect_ratio
local pre_output_format="$output_format"
INTERNAL_MAXREWIND_REACHED=0 # Reset for each file
 
# XXX: Some of this should be moved to coherence_check
if [ $DVD_MODE -eq 1 ]; then # DVD Mode
f="$DVD_FILE"
DVD_DEVICE=
local dvdn=$(realpathr "$f") # dvdn might be a device or an ISO
if [ -f "$dvdn" ]; then
# It's an ISO
DVD_MOUNTP=$(get_dvd_image_mountpoint)
if [ -z "$DVD_MOUNTP" ]; then
# Only in Linux does this matter
if ! is_linux ; then
warn "Video properties detection for ISO files is not accurate"
else
warn "Mount DVD image to get better video properties detection"
fi
fi
elif [ ! -r "$dvdn" ]; then
# It's something else we cannot read
error "Can't access DVD ($f)"
return $EX_NOINPUT
else
# It's a device. Note BSD has no concept of block devices.
# It MUST be mounted to continue. This is required to allow ffmpeg detection
#+and to calculate file size
if ! mount | egrep -q "^$dvdn\ " ; then
error "DVD mode requires device ($f) to be mounted"
return $EX_UNAVAILABLE
fi
DVD_DEVICE="$dvdn"
DVD_MOUNTP=$(mount | grep -o "^$DVD_DEVICE *on [^ ]*" | cut -d' ' -f3)
dvdn="DVD"
fi
inf "Processing $dvdn..."
unset dvdn
if ! is_number "$1" ; then
error "DVD Title must be a number (e.g.: \$ vcs -V /dev/dvd 1)"
exit $EX_USAGE
fi
DVD_TITLE=$1
if [ $DVD_TITLE -eq 0 ]; then
local dt="$(lsdvd "$DVD_FILE" 2>/dev/null | grep 'Longest track:' | \
cut -d' ' -f3- | sed 's/^0*//')"
if ! is_number "$dt" ; then
error "Failed to autodetect longest DVD title"
exit $EX_INTERNAL
fi
DVD_TITLE=$dt
DVD_VTS=$(lsdvd -t$DVD_TITLE -v "$DVD_FILE" 2>/dev/null | grep -o 'VTS: [0-9]*' | cut -d' ' -f2)
unset dt
inf "Using DVD Title #$DVD_TITLE (VTS: $DVD_VTS)"
fi
else # Not DVD Mode:
if [ ! -f "$f" ]; then
error "File \"$f\" doesn't exist"
return $EX_NOINPUT
fi
 
inf "Processing $f..."
fi
 
# {{SET_E}} Beware, set -e will break this
identify_video "$f"
local ecode=$?
[ $ecode -eq 0 ] || {
case $ecode in
3) error "Unable to find length of file \"$f\". Can't continue." ;;
4) error "Unable to detect dimensions of file \"$f\". Can't continue." ;;
*) error "Failure while analysing file \"$f\". Can't continue." ;;
esac
return $EX_UNAVAILABLE
}
 
# Identification-only mode?
[ "$UNDFLAG_IDONLY" ] && dump_idinfo && return 0
 
# Vidcap/Thumbnail height
local vidcap_height=$th_height
if is_percentage "$th_height" ; then
local pc=${th_height/%%/} # BASH %% == RE %$
vidcap_height=$(awkex "int ((${VID[$H]} * ${pc}) / 100 + 0.5)")
inf "Height: $th_height of ${VID[$H]} = $vidcap_height"
fi
if ! is_number "$vidcap_height" || [ "$vidcap_height" -eq 0 ]; then
vidcap_height=${VID[$H]}
fi
# -2: DVD Mode autodetection => If ffmpeg/mplayer was unable autodetect, otherwise
#+ honor detected value
[ "-2" == "$aspect_ratio" ] && [ -z "${VID[$ASPECT]}" ] && aspect_ratio=-1
[ "-2" == "$aspect_ratio" ] && [ "${VID[$ASPECT]}" ] && aspect_ratio=0
if [ "0" == "$aspect_ratio" ]; then
if [ "${VID[$ASPECT]}" ]; then
# Aspect ratio in file headers, honor it
aspect_ratio=$(awkex "${VID[$ASPECT]}")
else
aspect_ratio=$(awkex "${VID[$W]} / ${VID[$H]}")
fi
elif [ "-1" == "$aspect_ratio" ]; then
aspect_ratio=$(guess_aspect ${VID[$W]} ${VID[$H]})
inf "Aspect ratio set to $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
# Compute the stamps (if in auto mode)...
if [ $manual_mode -eq 1 ]; then
# Note TIMECODES must be set as an array to get the correct count in
# manual mode; in automatic mode it will be set correctly inside
# compute_timecodes()
TIMECODES=( ${initial_stamps[@]} )
else
TIMECODES=${initial_stamps[@]}
compute_timecodes $timecode_from $interval $numcaps || {
return $?
}
fi
 
local output=$(new_temp_file '-preview.png')
local VIDCAPFILE=00000005.png
 
# If the temporal vidcap already exists, abort
if [ -f $VIDCAPFILE ]; then
error "File $VIDCAPFILE 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=( "${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 $CTX_HL $n || {
local r=$?
error "Failed to apply transformations to the capture."
return $r
}
hlcapfile=$(new_temp_file "-hl-$(pad 6 $n).png")
mvq "$VIDCAPFILE" "$hlcapfile"
capfiles=( "${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 $CTX_STD $n || return $?
 
# identified by capture number, padded to 6 characters
capfile=$(new_temp_file "-cap-$(pad 6 $n).png")
mvq "$VIDCAPFILE" "$capfile"
capfiles=( "${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 $CTX_EXT $n || return $?
 
capfile=$(new_temp_file "-excap-$(pad 6 $n).png")
mvq "$VIDCAPFILE" "$capfile"
capfiles=( "${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
 
local vcodec=${VID[$VCNAME]}
local acodec=${VID[$ACNAME]}
 
if [ "${VID[$CHANS]}" ] && is_number "${VID[$CHANS]}" &&[ ${VID[$CHANS]} -ne 2 ]; then
if [ ${VID[$CHANS]} -eq 1 ]; then
acodec="$acodec (mono)"
else
acodec="$acodec (${VID[$CHANS]}ch)"
fi
fi
 
local csw=$(imw "$output") exw= hlw=
local width=$csw
if [ "$HLTIMECODES" ] || [ "$extended_factor" != "0" ]; then
inf "Merging contact sheets..."
if [ "$HLTIMECODES" ]; then
local hlw=$(imw "$hlfile")
if [ $hlw -gt $width ]; then width=$hlw ; fi
fi
if [ "$extended_factor" != "0" ]; then
local exw=$(imw $extoutput)
if [ $exw -gt $width ]; then width=$exw ; fi
fi
fi
if [ $csw -lt $width ]; then
local csh=$(imh "$output")
# Expand the standard set to the maximum width of the sets by padding both sides
# For some reason the more obvious (to me) convert command-lines lose
# the transparency
convert \( -size $(( ($width - $csw) / 2 ))x$csh xc:transparent \) "$output" \
\( -size $(( ($width - $csw) / 2 ))x$csh xc:transparent \) +append "$output"
unset csh
fi
 
# If there were highlights then mix them in
if [ "$HLTIMECODES" ]; then
# For some reason adding the background also adds padding with:
# convert \( -background LightGoldenRod "$hlfile" -flatten \) \
# \( "$output" \) -append "$output"
# replacing it with a "-composite" operation apparently works
# Expand the highlights to the correct size by padding
local hlh=$(imh "$hlfile")
if [ $hlw -lt $width ]; then
convert \( -size $(( ($width - $hlw) / 2 ))x$hlh xc:transparent \) "$hlfile" \
\( -size $(( ($width - $hlw) / 2 ))x$hlh xc:transparent \) +append "$hlfile"
fi
convert \( -size ${width}x${hlh} xc:LightGoldenRod "$hlfile" -composite \) \
\( -size ${width}x1 xc:black \) \
"$output" -append "$output"
unset hlh
fi
# Extended captures
if [ "$extended_factor" != 0 ]; then
# Already set local exw=$(imw "$extoutput")
local exh=$(imh "$extoutput")
if [ $exw -lt $width ]; then
# Expand the extended set to be the correct size
convert \( -size $(( ($width - $exw) / 2 ))x$exh xc:transparent \) "$extoutput" \
\( -size $(( ($width - $exw) / 2 ))x$exh xc:transparent \) +append "$extoutput"
fi
convert "$output" -background Transparent "$extoutput" -append "$output"
fi
# Add the background; -trim added in 1.11. I'm unsure of why but whithout trimmin extra blank
#+space is added at the top
local dotrim=
[ $DISABLE_SHADOWS -eq 1 ] && [ -z "$HLTIMECODES" ] && dotrim=-trim
convert -background "$bg_contact" "$output" -flatten $dotrim "$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=$(imw "$output")
# 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.
local filename_label="Filename"
local filesize_label="File size"
local filename_value=
local filesize_value=
if [ $DVD_MODE -eq 1 ]; then
# lsdvd is guaranteed to be installed if DVD mode is enabled
local dvd_label=$(lsdvd "$DVD_FILE" 2>/dev/null | grep -o 'Disc Title: .*' | cut -d' ' -f3-)
# Need a mountpoint to get the actual *title* size
if [ "$DVD_MOUNTP" ]; then
filename_label="Disc label"
filename_value="$dvd_label"
filesize_label="Title size"
filesize_value="$(get_dvd_size)"
else
# Not mounted. We can get the disc size but this will include any other titles.
# Since 1.11 mounting DVDs is mandatory to get the title size. Both for ISOs and
#+ devices
filename_value="$(basename "$DVD_FILE") $filename_value (DVD Label: $dvd_label)"
is_linux && warn "DVD not mounted: Can't detect title file size."
filesize_label='Disc image size'
filesize_value="$(get_pretty_size $(dur "$f"))"
fi
else
filename_value="$(basename "$f")"
filesize_value="$(get_pretty_file_size "$f")"
fi
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_label: " \
-font "$fn_font" label:"$filename_value" +append \
\) \
-font "$font_heading" \
label:"$filesize_label: $filesize_value" \
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
 
local wanted_name=${OUTPUT_FILES[$FILEIDX]}
[ "$wanted_name" ] && \
if egrep -q '\.[^\.]+$' <<<"$wanted_name" ; then
output_format=$(filext "$wanted_name")
inf "Output format set from output filename"
else # No file extension in wanted_name
wanted_name="$wanted_name.$output_format"
fi
[ "$wanted_name" ] || wanted_name="$(basename "$f").$output_format"
 
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" "$wanted_name" ) || {
error "Failed to write the output file!"
return $EX_CANTCREAT
}
inf "Done. Output wrote to $output_name"
 
let 'FILEIDX++,1' #,1 so that it's always ok
[ "$UNDFLAG_HANG" ] && read -p 'Main loop paused, hit Enter key to continue... '
cleanup
 
# Re-set variables (for multi-file input)
QUIRKS=$pre_quirks
aspect_ratio=$pre_aspect_ratio
output_format="$pre_output_format"
}
 
# }}} # Core functionality
 
# {{{ # Debugging helpers
 
# Tests integrity of some operations.
# Used to test internal changes for consistency.
# It helps me to identify incorrect optimizations.
# internal_integrity_test(). Running with -D triggers this.
internal_integrity_test() {
local t op val ret comm retval=0
 
# Replacements
local SEQ=$(type -pf seq)
local JOT=$(type -pf jot)
local ex rex
if [ "$SEQ" ]; then
ex=$($SEQ 1 10)
elif [ "$JOT" ]; then
ex=$($JOT 10 1)
else
warn "Can't check seqr() correctness, neither seq nor jot found"
fi
if [ "$ex" ]; then
exr=$(seqr 1 10)
if [ "$exr" != "$ex" ]; then
error "Failed test: seqr() not consistent with external result"
let 'retval++,1'
else
inf "Passed test (seq replacement): consistent result"
fi
fi
 
# Textual tests, compare output to expected output
# Tests are in the form "operation arguments correct_result #Description"
TESTS=( # Note bash2 doesn't like this array as a local variable
# TODO: UNIX vs GNU
#"stonl ..."
 
"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"
#bc result: "pyth_th 16 9 18.35755975068581929849 #FP pythagorean theorem"
#perl result: "pyth_th 16 9 18.3575597506858 #FP pythagorean theorem"
"pyth_th 16 9 18.3576 #FP pythagorean theorem"
 
"get_interval 2h 7200 #Hours parsing"
"get_interval 2m 120 #Minutes parsing"
"get_interval 30S 30 #Seconds parsing"
"get_interval .30 .30 #Milliseconds parsing"
# Since now the numbers are passed to perl, leading zeroes become octal
# numbers. Must ensure they are handled correctly
"get_interval 09h010m09s1 33010 #Parsing with leading zeroes"
"get_interval 0400 400 #Parsing shorthand"
# Extended syntax
"get_interval 30m30m1h 7200 #Repeated minutes parsing"
)
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=$(grep -o "[^ ]* #$comm\$"<<<$t | cut -d' ' -f1)
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
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=$(grep -o "[^ ]* #$comm\$"<<<$t | cut -d' ' -f1)
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
local inff=inf
[ "$HAS_COLORS" ] && inff=infplain
$inff "Video Contact Sheet *NIX v${VERSION}, (c) 2007-2010 Toni Corvera" "sgr0"
}
 
# Prints the list of options to stdout
# show_help($1 = long = '')
show_help() {
local P=$(basename $0)
local showlong=$1
local mpchosen= ffchosen= longhelp= funkyex=
[ -z "$MPLAYER" ] && mpchosen=' [Not available]'
[ "$MPLAYER" ] && [ $decoder == $DEC_MPLAYER ] && mpchosen=' [Selected]'
[ -z "$FFMPEG" ] && ffchosen=', Not available'
[ "$FFMPEG" ] && [ $decoder == $DEC_FFMPEG ] && ffchosen=', Selected'
# This portion of help is only shown when in full help mode (--fullhelp)
[ "$showlong" ] && longhelp=\
" --anonymous Disable the 'Preview created by' line in the footer.
-Ij|-Ik|-Ij=fontname|-Ik=fontname
--nonlatin Use an alternate font in the heading for the video file
name. Required to display correctly file names in
some languages (Chinese, Japanese, Hangul,
Cyrillic, ...).
Will try to use a reasonable font. Can also be set
manually like:
$ vcs -Ij=Sazanami-Mincho-Regular file.avi
or
$ vcs -Ij=/usr/share/fonts/ttf/ttf-japanese-mincho.ttf
Use \"identify -list font\" to list the available fonts
-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\"-).
 
Tweaks and workarounds:
-Ws Increase length of safe measuring (try harder). Repeat
to increase further.
-WS Scan all video, if required, to get a safe measuring.
-Wp Increase safe measuring precission (i.e. halve the
probe stepping). Repeat to increase further.
-WP Inverse of -Wp.
-Wo Change ffmpeg's arguments order, might work with some
files that fail otherwise.
-Wc Disable colour in console messages.
Obscure options, debugging tools and workarounds:
-R <file>
--randomsource <file> Use the provided file as a source for random \"values\":
they won't be random anymore, so two runs with the same
source and same arguments will produce the same output
in modes which use using randomisation (e.g. the
\"photos\" and \"polaroid\" modes).
-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.
"
# The --funky help is really long, so make it shorter by default,
# only show the complete help when --fullhelp is used
[ "$showlong" ] && funkyex="
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.
\"polaroidframe\": Use '-kL' or '--funky polaroidframe'
Adds a polaroid picture-like white frame to each
image.
\"photos\": Use '-kc' or '--funky photos'
Combination of rotate, photoframe and overlap.
Same as -kp -kr -ko.
\"polaroid\": Use '-kp' or '--funky polaroid'
Combination of rotate, polaroidframe and overlap.
Same as -kL -kr -ko.
\"film\": Use '-ki' or '--funky film'
Imitates filmstrip look.
\"random\": Use '-kx' or '--funky random'
Randomizes colours and fonts."
[ -z "$showlong" ] && funkyex="
Available: overlap, rotate, photoframe, polaroidframe,
photos, polaroid, film, random
Use --fullhelp for more details."
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.
-o|--output <file> File name of output. When ommited will be derived from
the input filename. Can be repeated for multiple files.
-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.
-T|--title <arg> Add a title above the vidcaps.
-j|--jpeg Output in jpeg (by default output is in png).
-j2|--jpeg 2 Output in jpeg 2000
-V|--dvd <file.iso|dvd_device>
DVD Mode, use file.iso as DVD. In this mode the
<file> argument must point to the title number, e.g.:
$ vcs -V somedvd.iso 1
Passing title 0 will use the default (longest) title.
$ vcs -V /dev/dvd 0
Implies -A (auto aspect ratio)
-M|--mplayer Use Mplayer to capture$mpchosen
-F|--ffmpeg Use FFmpeg to capture [Default$ffchosen]
-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.
-q|--quiet Don't print progess messages just errors. Repeat to
mute completely even on error.
-h|--help Show basic help and exit.
--fullhelp Show the complete help and exit.
-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
* padding: use -dp or --disable padding
(note shadows introduce some extra padding)
-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.
-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.
-k <arg>
--funky <arg> Funky modes:$funkyex
$longhelp
Examples:
Create a contact sheet with default values (vidcaps at intervals of
$DEFAULT_INTERVAL seconds), will be saved to 'video.avi.png':
\$ $P video.avi
 
Create a sheet with vidcaps at intervals of 3 and a half minutes, save to
'output.jpg':
\$ $P -i 3m30 input.wmv -o output.jpg
 
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
 
#### Entry point ####
 
# Important to do this before any message can be thrown
init_feedback
 
# Ensure $GETOPT is GNU/Linux-style getopt
choose_getopt
 
# Execute exithdlr on exit
trap exithdlr EXIT
 
show_vcs_info
 
# Test requirements. Important, must check before looking at the
# command line (since getopt is used for the task)
test_programs
 
# The command-line overrides any configuration. And the configuration
# is able to change the program in charge of parsing options ($GETOPT)
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]]
# TODO: Why does FreeBSD's GNU getopt ignore -n??
TEMP=$("$GETOPT" -n "$0" -s bash \
-o i:n:u:T:f:t:S:j::hFMH:c:ma:l:De::U::qAO:I:k:W:E:d:V:R:Z:o:P: \
--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:,dvd:,randomsource:,undocumented:,output:,fullhelp,profile:,"\
"jpeg2,nonlatin" \
-- "$@")
eval set -- "$TEMP"
 
while true ; do
case "$1" in
-i|--interval)
check_interval "$2"
interval=$(get_interval "$2")
timecode_from=$TC_INTERVAL
USR_interval=$interval
USR_timecode_from=$TC_INTERVAL
shift # Option arg
;;
-n|--numcaps)
check_numcaps "$2"
numcaps="$2"
timecode_from=$TC_NUMCAPS
USR_numcaps="$2"
USR_timecode_from=$TC_NUMCAPS
shift # Option arg
;;
-o|--output)
current=${#OUTPUT_FILES[@]}
OUTPUT_FILES[$current]="$2"
shift ;;
-u|--username) user="$2" ; USR_user="$user" ; shift ;;
-U|--fullname)
# -U accepts an optional 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" ; USR_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
USR_fromtime="$fromtime"
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
USR_end_offset="$end_offset"
shift
;;
-t|--to)
if ! totime=$(get_interval "$2") ; then
error "Ending timestamp must be a valid timecode. Got '$2'."
exit $EX_USAGE
fi
if fptest "$totime" -eq 0 ; then
error "Ending timestamp was set to 0, set to movie length."
totime=-1
fi
USR_totime=$totime
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=( "${HLTIMECODES[@]}" "$temp" )
shift
;;
--jpeg2) # Note --jpeg 2 is also accepted
output_format=jp2
USR_output_format=jp2
;;
-j|--jpeg)
if [ "$2" ]; then # Arg is optional, 2 is for JPEG 2000
# 2000 is also accepted
if [ "$2" != "2" ] && [ "$2" != "2000" ]; then
error "Use -j for jpeg output or -j2 for JPEG 2000 output. Got '-j$2'."
exit $EX_USAGE
fi
output_format=jp2
else
output_format=jpg
fi
USR_output_format="$output_format"
shift
;;
-h|--help) show_help ; exit $EX_OK ;;
--fullhelp) show_help 'full' ; exit $EX_OK ;;
--shoehorn)
warn "$1 is deprecated, please use '--undocumented shoehorn=\"$2\"' instead"
shoehorned="$2"
shift
;;
-F) decoder=$DEC_FFMPEG ; USR_decoder=$decoder ;;
-M) decoder=$DEC_MPLAYER ; USR_decoder=$decoder ;;
-H|--height)
check_height "$2"
th_height="$2"
USR_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"
USR_aspect_ratio="$2"
shift
;;
-A|--autoaspect) aspect_ratio=-1 ; USR_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"
USR_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
USR_extended_factor=$extended_factor
shift
;;
--mincho)
warn "--mincho is deprecated, please use -Ij or --nonlatin instead"
if [ -z "$USR_FONT_MINCHO" ]; then
set_extended_font
inf "Filename font set to '$FONT_MINCHO'"
fi
;;
# Unlike -I, --nonlatin does not accept a font name
--nonlatin)
if [ -z "$USR_FONT_MINCHO" ]; then
set_extended_font
inf "Filename font set to '$FONT_MINCHO'"
fi
;;
-I)
# Extended/non-latin font
# New syntax introduced in 1.11:
# -Ij: Try to pick automatically a CJK font. Might fail and abort
# -Ij='Font name or file': Set font manually
 
if [ "$2" ] ; then
# If an argument is passed, test it is one of the known ones
case "$2" in
k|j|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;
if [ ${#2} -gt 1 ]; then
# j=, k= syntax
FONT_MINCHO="${2:2}"
USR_FONT_MINCHO="$FONT_MINCHO"
inf "Filename font set to '$FONT_MINCHO'"
fi
# If the user didn't pick one, try to select automatically
if [ -z "$USR_FONT_MINCHO" ]; then
set_extended_font
inf "Filename font set to '$FONT_MINCHO'"
fi
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
if grep -q 'GETOPT=' <<<"$2" ; then
# If we're here, getopt has already been found and works, so it makes no
# sense to override it; on the other hand, if it hasn't been correctly
# set/detected we won't reach here
warn "GETOPT can't be overridden from the command line."
else
override "$2" "command line"
fi
shift
;;
-W)
case "$2" in
# (classic) Workaround mode. See wa_ss_* declarations at the start for details
o) wa_ss_af='-ss ' ; wa_ss_be='' ;;
# Console colout
# Once: Disable console colour, use prefixes instead
# Twice: Disable prefixes too
c)
set_feedback_prefixes
[ "$UNDFLAG_NOPREFIX" ] && plain_messages=1
UNDFLAG_NOPREFIX=1
;;
# Double length of video probed in safe measuring
# Semi-undocumented traits:
# - Can be repeated, will double for each instance
# - -Ws -Ws -Ws = -Ws3
s|s[0-9]|s[0-9][0-9])
[ ${#2} -gt 1 ] && n=${2:1} || n=1
QUIRKS_MAX_REWIND=$(awkex "$QUIRKS_MAX_REWIND * (2^$n)")
let 'INTERNAL_WS_C+=n,1'
;;
# Brute force -Ws: Test all the length of the file if required
S) QUIRKS_MAX_REWIND=-1 ;;
# Increase precission of safe length measuring (halve the stepping)
# Like -Ws can be repeated
p|p[0-9]|p[0-9][0-9])
[ ${#2} -gt 1 ] && n=${2:1} || n=1
QUIRKS_LEN_STEP=$(awkex "$QUIRKS_LEN_STEP / (2^$n)")
let 'INTERNAL_WP_C+=n,1'
;;
# Inverse of -Wp: Decrease precission of safe length measuring
# i.e.: will try less times <-> will be quicker but less accurate
# desirable when -Ws or -WS are used.
# Can also be repeated
P|P[0-9]|P[0-9][0-9])
[ ${#2} -gt 1 ] && n=${2:1} || n=1
QUIRKS_LEN_STEP=$(awkex "$QUIRKS_LEN_STEP * (2^$n)")
let 'INTERNAL_WP_C-=n,1'
;;
# -Wb (Semi-undocumented): Disable safe mode. Use this to force accepting
#+broken/partial files. Only makes sense when testing or in combination
#+with stuff like '-Z idonly'
b) QUIRKS=-2 ;; # Quirks < 0 : No safe mode
*)
error "Wrong argument. Use --fullhelp for a list available workarounds. Got -W$2."
exit $EX_USAGE
;;
esac
shift
;;
-k|--funky) # Funky modes
case "$2" in # Note older versions (<1.0.99) were case-insensitive
p|polaroid) # Same as overlap + rotate + polaroid
inf "Changed to polaroid funky mode."
FILTERS_IND=( "${FILTERS_IND[@]}" 'filt_polaroid' 'filt_randrot' )
CSHEET_DELEGATE='csheet_overlap'
# XXX: The newer version has a lot less flexibility with these many
# hardcoded values...
grav_timestamp=South
fg_tstamps=Black
bg_tstamps=Transparent
pts_tstamps=$(( $pts_tstamps * 3 / 2 ))
;;
c|photos) # Same as overlap + rotate + photoframe, this is the older polaroid
inf "Changed to photos funky mode."
FILTERS_IND=( "${FILTERS_IND[@]}" 'filt_photoframe' 'filt_randrot' )
CSHEET_DELEGATE='csheet_overlap'
# The timestamp must change location to be visible most of the time
grav_timestamp=NorthWest
;;
o|overlap) # Random overlap mode
CSHEET_DELEGATE='csheet_overlap'
grav_timestamp=NorthWest
;;
r|rotate) # Random rotation
FILTERS_IND=( "${FILTERS_IND[@]}" 'filt_randrot' )
;;
f|photoframe) # White photo frame
FILTERS_IND=( "${FILTERS_IND[@]}" 'filt_photoframe' )
;;
L|polaroidframe) # White polaroid frame
FILTERS_IND=( "${FILTERS_IND[@]}" 'filt_polaroid ')
grav_timestamp=South
fg_tstamps=Black
bg_tstamps=Transparent
pts_tstamps=$(( $pts_tstamps * 3 / 2 ))
;;
i|film)
inf "Enabled film mode."
FILTERS_IND=( "${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
;;
-P|--profile)
case "$2" in
classic) # Classic colour scheme
bg_heading=YellowGreen bg_sign=SlateGray bg_contact=White
bg_title=White fg_heading=Black fg_sign=Black
;;
1.0) # 1.0a, 1.0.1a and 1.0.2b colourscheme
bg_heading=YellowGreen bg_sign=SandyBrown bg_contact=White
bg_title=White fg_heading=Black fg_sign=Black
;;
esac
shift
;;
-R|--randomsource)
if [ ! -r "$2" ]; then
error "Random source file '$2' can't be read"
exit $EX_USAGE
fi
init_filerand "$2"
inf "Using '$2' as source of semi-random values"
RANDFUNCTION=filerand
shift
;;
-d|--disable) # Disable default features
case $(tolower "$2") in
# timestamp (with no final s) is undocumented but will stay
t|timestamps|timestamp)
if [ $DISABLE_TIMESTAMPS -eq 0 ]; then
inf "Timestamps disabled."
# They'll be removed from the filter chain in coherence_check
DISABLE_TIMESTAMPS=1
fi
;;
s|shadows|shadow)
if [ $DISABLE_SHADOWS -eq 0 ]; then
inf "Shadows disabled."
# They will be removed from the filter chain in coherence_check
DISABLE_SHADOWS=1
fi
;;
p|padding)
if [ $HPAD -ne 0 ] ; then
inf "Padding disabled." # Kinda...
HPAD=0
fi
;;
*)
error "Requested disabling unknown feature. Got '$2'."
exit $EX_USAGE
;;
esac
shift
;;
-V|--dvd)
# XXX; Are there systems with no perl???
if ! type -pf perl >/dev/null ; then
error "DVD support requires perl"
exit $EX_UNAVAILABLE
fi
# DVD Mode requires lsdvd
if ! type -pf lsdvd >/dev/null ; then
error "DVD support requires the lsdvd program"
exit $EX_UNAVAILABLE
fi
DVD_MODE=1
DVD_FILE="$2"
decoder=$DEC_MPLAYER
aspect_ratio=-2 # Special value: Auto detect only if ffmpeg couldn't
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
;;
-Z|--undocumented)
# This is a container for, of course, undocumented functions
# These are used for testing/debugging purposes. Might (and will)
# change between versions, break easily and do no safety checks.
# In short, don't look at them unless told to do so :P
case "$2" in
# AWK was used for a little while in a WiP version
#set_awk=*) AWK="$(cut -d'=' -f2<<<"$2")" ; warn "[U] AWK=$AWK" ;;
# Hang the main process loop just before cleanup.
hang) UNDFLAG_HANG="On" ; warn "[U] Hang flag" ;;
# Print identification results, do nothing else
idonly) UNDFLAG_IDONLY="On" ; warn "[U] Id only" ;;
# ffmpeg path
set_ffmpeg=*)
FFMPEG=$(realpathr "$(cut -d'=' -f2<<<"$2")")
assert $LINENO 'test -x "$FFMPEG"'
warn "[U] FFMPEG=$FFMPEG"
;;
# mplayer path
set_mplayer=*)
MPLAYER=$(realpathr "$(cut -d'=' -f2<<<"$2")")
assert $LINENO 'test -x "$MPLAYER"'
warn "[U] MPLAYER=$MPLAYER"
;;
# Ignore one of the players
disable_ffmpeg)
FFMPEG=''
warn "FFmpeg disabled"
assert $LINENO "[ '$MPLAYER' ] || [ '$FFMPEG' ]"
decoder=$DEC_MPLAYER
;;
disable_mplayer)
MPLAYER=''
warn "Mplayer disabled"
assert $LINENO "[ '$MPLAYER' ] || [ '$FFMPEG' ]"
decoder=$DEC_FFMPEG
;;
# This is an old option from the first versions when the script
# failed a lot more, I haven't used it for years and I don't think
# anyone would need it anymore but I'll keep it at least for
# a few more versions
shoehorn=*)
shoehorned="$(cut -d'=' -f2-<<<"$2")"
;;
*) false ;;
esac
shift
;;
-D) # Repeat to just test consistency
if [ $DEBUGGED -gt 0 ]; then
[ $decoder -eq $DEC_MPLAYER ] && d='mplayer'
[ $decoder -eq $DEC_FFMPEG ] && d='ffmpeg'
infplain '[ svn $Rev$ ]'
cat >&2 <<-EOD
=== Setup ===
GETOPT: $GETOPT
MPLAYER: $MPLAYER
FFMPEG: $FFMPEG
AWK: $(type -pf awk)
Filterchain: [ ${FILTERS_IND[*]} ]
Decoder: $d
Safe step: $QUIRKS_LEN_STEP
=== Versions ===
Bash: $BASH_VERSION
Getopt: $($GETOPT --version)
EOD
# FIXME: Any portable way to print AWK version?
exit
fi
DEBUG=1
inf "Testing internal consistency..."
internal_integrity_test && warn "All tests passed" || error "Some tests failed!"
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
elif [ "$2" ]; then
multiple_input_files=1
fi
# }}} # Command line parsing
 
# The coherence check ensures the processed options are
# not incoherent/incompatible with the input files or with
# other given options
coherence_check || {
exit $?
}
 
set +e # Don't fail automatically. Blocks marked with {{SET_E}} will break if this changes
for arg do process "$arg" ; done
 
# Script ends here, everything below are comments
# ===========================================================================
#
# Bash syntax notes # {{{
# These are some notes for my own reference (or for those trying to read the script)
# regarding bash syntax nuissances.
#
# * herestring redirection, '<<<$string', (used extensively in vcs) was introduced in bash 2.05b
# * sed s/[ ,]/ * /g <=> ${var//[ ,]/ * } [Much faster due to not forking]
# sed s/[ ,]/ * / <=> ${var/[ ,]/ * }
# * bash2: declaring local empty arrays like 'local a=( )' makes bash think they're strings
# 'local -a' must be used instead
# bash3 has no problem with this
# * bash2: 'arr+=( elem )' for array push is not supported, use 'arr=( "${arr[@]}" elem )' instead
# += is a bash3 syntax modification, bash3.1 extended it further, arithmetic += works
# inside let
# * bash2: [*] expands as a string while [@] expands as an array. Both have trouble with spaces
# in elements though
# * performance: bash loops are often slower than awk or perl
# * performance: grep + cut proved faster than an equivalent sed -r s// replacement
# }}} # Bash syntax notes
#
# vim:set ts=4 ai foldmethod=marker nu: #
Property changes:
Added: svn:executable
Added: svn:keywords
+Rev Id Date
\ No newline at end of property
/video-contact-sheet/trunk/rpm-package/vcs.spec.in
0,0 → 1,97
#
# spec file for vcs rpm
#
# based on mp3plot's which in turn was based on other sources
#
 
%define is_mandrake %(test -e /etc/mandrake-release && echo 1 || echo 0)
%define is_suse %(test -e /etc/SuSE-release && echo 1 || echo 0)
%define is_fedora 0%{?fedora}
%define is_redhat 0%{?rhl}
%define is_rhel 0%{?rhel}
 
%define distname generic
%define disttag .generic
 
%if %{is_fedora}
%define distname fedora
%define disttag %{dist}
%endif
%if %{is_redhat}
%define distname redhat
%define disttag %{dist}
%endif
%if %{is_mandrake}
%define distname mandrake
%define disttag .mdk
%endif
%if %{is_suse}
%define distname suse
%define disttag .suse
%endif
%if %{is_rhel}
%define distname rhel
%define disttag %{dist}
%endif
 
Name: vcs
Summary: Tool to create contact sheets (previews) from videos
Version: @VERSION@
Release: 1%{?disttag},upstream
License: LGPL
Packager: @PACKAGER@
Group: Applications/Multimedia
Source0: http://p.outlyer.net/%{name}/files/%{name}-%{version}.tar.gz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-build
BuildArch: noarch
# TODO: How to set alternative dependencies? i.e. mplayer/ffmpeg
Requires: mplayer / ffmpeg
Requires: bash >= 2.05b
Requires: ImageMagick >= 6.3.5-7
Requires: coreutils
URL: http://p.outlyer.net/vcs/
#BuildRequires:
#Prereq: /sbin/ldconfig
#Requires:
AutoReqProv: yes
## Allow relocation (e.g. rpm --prefix /opt/vcs)
Prefix: /usr
 
%description
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.
 
%prep
#echo %_target
echo Building %{name}-%{version}-%{release}
 
%setup -q -n %{name}-%{version}
 
%build
 
%install
make DESTDIR=%buildroot install
 
%clean
[ ${RPM_BUILD_ROOT} != "/" ] && rm -rf ${RPM_BUILD_ROOT}
 
#%post
# postinst
 
#%postun
# postuninst
 
%files
%defattr(-,root,root)
# binary
%{_bindir}/%{name}
# Manpage
#%{_mandir}/man1/%{name}.1.gz
%doc CHANGELOG
 
%changelog
* Sun Mar 07 2010 - outlyer (at) gmail (dot) com
- Initial RPM packaging
 
Property changes:
Added: svn:keywords
+Rev Id Date
\ No newline at end of property
/video-contact-sheet/trunk/rpm-package/Makefile
0,0 → 1,33
# $Id$
 
prefix:=/usr
DESTDIR:=/
VERSION:=$(shell head -50 vcs | grep 'declare -r VERSION=' | perl -pe 's/.*"(.*)".*/\1/')
#PACKAGER=$(shell grep ^`id -nu` /etc/passwd | cut -d: -f5 | cut -d, -f1)
 
all:
@echo 'There'\''s nothing to be built'
@echo 'Available make commands:'
@echo '* Build RPM (and generate spec)'
@echo ' $$ make rpm'
@echo '* Generate spec file'
@echo ' $$ make spec'
@echo '* Install'
@echo ' $$ make install'
 
clean:
$(RM) vcs.spec
 
spec: vcs.spec.in
test "$(VERSION)"
@test "$(PACKAGER)" || echo "No PACKAGER set!"
@test "$(PACKAGER)"
cat vcs.spec.in | sed 's!@VERSION@!$(VERSION)!g' | \
sed 's!@PACKAGER@!$(PACKAGER)!g' > vcs.spec
 
install:
mkdir -p $(DESTDIR)$(prefix)/bin
install -m755 -o0 -g0 vcs $(DESTDIR)$(prefix)/bin
 
 
.PHONY: all install clean spec
Property changes:
Added: svn:keywords
+Rev Id Date
\ No newline at end of property
/video-contact-sheet/trunk/Makefile
0,0 → 1,63
#!/usr/bin/make -f
# $Id$
 
VER=$(shell grep VERSION vcs | head -n1 | sed 's/\#.*//' | 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
 
check-rel:
@if head -n50 vcs | grep -q 'RELEASE=0' ; then \
echo 'RELEASE is set to 0!' ; false ; fi
 
prep:
cp vcs CHANGELOG debian-package/
cp vcs rpm-package/
 
dist: check-rel check-no-svn prep gz bz2 plaintext changelog deb rpm cleanup
 
gz:
cp vcs vcs-$(VER)
chmod -x vcs-$(VER)
gzip -9 vcs-$(VER)
 
bz2:
cp vcs vcs-$(VER)
chmod -x vcs-$(VER)
bzip2 -9 vcs-$(VER)
 
plaintext:
cp vcs vcs-$(VER)
chmod -x vcs-$(VER)
 
changelog:
gzip -9 CHANGELOG
gzip -dc CHANGELOG.gz > CHANGELOG
 
cleanup:
$(RM) vcs Makefile *.changes
$(RM) -r debian-package
$(RM) -r rpm-package
 
deb:
cd debian-package/ && dpkg-buildpackage -rfakeroot -us -uc -b
 
rpm: vcs.spec
mkdir rpm-package/vcs-$(VER)/
cp vcs CHANGELOG rpm-package/Makefile rpm-package/vcs-$(VER)/
mv vcs.spec rpm-package/vcs-$(VER)/
cd rpm-package && tar zcvf vcs-$(VER).tar.gz vcs-$(VER)
$(RM) vcs.spec
$(RM) -r rpm-package/vcs-$(VER)
cd rpm-package && fakeroot rpmbuild -tb vcs-$(VER).tar.gz
-ln -s ~/rpmbuild/RPMS/noarch/vcs-$(VER)-*.rpm .
$(RM) rpm-package/vcs-$(VER).tar.gz
 
vcs.spec: rpm-package/vcs.spec.in
cd rpm-package && $(MAKE) -f Makefile spec PACKAGER="$(PACKAGER)"
mv rpm-package/vcs.spec .
 
.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/trunk/tests/test_funkymodes
0,0 → 1,27
#!/usr/bin/env bash
 
# Allow setting from the environment
[ "$vcs" ] || vcs='vcs'
 
if [ -z "$1" ]; then
echo "Usage: $0 <file>"
exit 1
fi >&2
 
HEIGHT="-H240"
 
echo "Using vcs: $vcs" >&2
 
yes 01234 | head -n200 > randsource
 
BN=$(basename "$1")
echo ">> Standard <<" >&2
$vcs -n4 -c2 $HEIGHT "$1" -o "$BN-std.jpg"
echo ">> Polaroid <<" >&2
$vcs -n6 -c3 -k polaroid $HEIGHT "$1" -R randsource -o "$BN-polaroid.jpg"
echo ">> Photos <<" >&2
$vcs -n6 -c3 -k photos $HEIGHT "$1" -R randsource -o "$BN-photos.jpg"
echo ">> Filmstrip <<" >&2
$vcs -n8 -c2 -k film $HEIGTH "$1" -R randsource -o "$BN-film.jpg"
 
rm -f randsource
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/trunk
Property changes:
Added: svn:mergeinfo
Merged /video-contact-sheet/branches/1.0a:r262-263
Merged /video-contact-sheet/tags/1.11:r381,385-387
Merged /video-contact-sheet/branches/1.0.100a:r364-371
Merged /video-contact-sheet/tags/1.0.12:r352-356
Merged /video-contact-sheet/tags/1.0.11:r344-345
Merged /video-contact-sheet/tags/1.0.2b:r274
Merged /video-contact-sheet/tags/1.0.8a:r319-320
Merged /video-contact-sheet/branches/1.11:r375-379,382-383
Merged /video-contact-sheet/tags/0.99a:r261
Merged /video-contact-sheet/branches/1.11.1:r389-390
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.12:r347-350
Merged /video-contact-sheet/branches/1.0.1a:r266-267
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.5b:r284-285
Merged /video-contact-sheet/branches/1.0.4b:r280-281
Merged /video-contact-sheet/branches/1.0.7a:r294-311
Merged /video-contact-sheet/branches/1.0.6b:r289-290
Merged /video-contact-sheet/branches/1.0.9a:r322-325
Merged /video-contact-sheet/branches/1.0.99:r358-361
Merged /video-contact-sheet/branches/1.0.8a:r315-317