1,6 → 1,6 |
#!/bin/bash |
# |
# $Rev: 272 $ $Date: 2007-04-17 04:31:12 +0200 (dt, 17 abr 2007) $ |
# $Rev: 291 $ $Date: 2007-04-21 02:21:32 +0200 (ds, 21 abr 2007) $ |
# |
# vcs |
# Video Contact Sheet *NIX: Generates contact sheets (previews) of videos |
24,28 → 24,43 |
# Author: Toni Corvera <outlyer@outlyer.net> |
# |
|
declare -r VERSION="1.0.4b" |
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>). |
# |
# 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 |
# 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 |
|
76,6 → 91,10 |
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 |
|
86,12 → 105,14 |
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 |
# 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 |
128,6 → 149,11 |
# |
# 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 |
135,8 → 161,13 |
# 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! |
150,45 → 181,58 |
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() { |
# 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 |
195,25 → 239,50 |
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 |
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" |
} |
220,6 → 289,7 |
|
# 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" |
} |
226,22 → 296,40 |
|
# 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(height) (=AR*height) (rounded) |
# compute_width($1 = height) (=AR*height) (rounded) |
compute_width() { |
local wfloat=$(bc -lq <<< "$aspect_ratio * $1") |
local wint=$(bc -q <<<"($wfloat+0.5)/1") |
echo $wint |
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 |
|
282,6 → 370,9 |
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 |
293,6 → 384,9 |
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 |
|
313,6 → 407,10 |
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" |
|
339,7 → 437,9 |
} |
|
# Rename a file, if the target exists, try with appending numbers to the name |
# And print the output name to stderr |
# 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" |
366,48 → 466,122 |
done |
|
mv "$from" "$to" |
echo "$to" >&2 |
echo "$to" |
} |
|
# Tests the presence of all required programs |
# test_programs() |
test_programs() { |
for prog in mplayer convert montage ffmpeg bc ; do |
local retval=0 last=0 |
for prog in mplayer convert montage bc ffmpeg ; do |
type -pf "$prog" >/dev/null |
local retval=$? |
if [ $retval -ne 0 ] ; then |
if [ $? -ne 0 ] ; then |
error "Required program $prog not found!" |
return $retval |
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 |
echo "Cleaning up..." >&2 # TODO: Only in verbose mode |
info "Cleaning up..." |
rm -rf ${TEMPSTUFF[*]} |
TEMPSTUFF=( ) |
} |
|
# Exit callback. This function is executed on exit (correct, failed or |
# interrupted) |
# exithdlr() |
exithdlr() { |
cleanup |
} |
|
# Print some text to stderr |
# 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() { |
echo "$1" >&2 |
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 |
|
MPLAYER_CACHE= |
numsecs() { |
echo $(grep ID_LENGTH <<<"$MPLAYER_CACHE"| cut -d'=' -f2 | cut -d. -f1) |
# 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 numsecs=$(numsecs) end= |
end=$numsecs |
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 |
416,15 → 590,15 |
fi |
|
local inc= |
if [ "$timecode_from" -eq $TC_INTERVAL ]; then |
inc=$interval |
elif [ "$timecode_from" -eq $TC_NUMCAPS ]; then |
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 [ $numcaps -eq 1 ]; then # Special case, just one capture, center it |
if [ $tcnumcaps -eq 1 ]; then # Special case, just one capture, center it |
inc=$(( ($end-$st) / 2 + 1)) |
else |
inc=$(( ($end-$st) / $numcaps )) |
inc=$(( ($end-$st) / $tcnumcaps )) |
fi |
else |
error "Internal error" |
431,7 → 605,7 |
return $EX_SOFTWARE |
fi |
|
if [ $inc -gt $numsecs ]; then |
if [ $inc -gt ${VID[$LEN]} ]; then |
error "Interval is longer than video length, skipping $f" |
return $EX_USAGE |
fi |
440,10 → 614,136 |
for stamp in $(seq $st $inc $end); do |
LTC+=( $stamp ) |
done |
unset LTC[0] # Initial cap (=$st) |
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 |
|
453,34 → 753,27 |
error "File \"$f\" doesn't exist" |
return $EX_NOINPUT |
fi |
echo "Processing $f..." >&2 |
info "Processing $f..." |
|
# 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) |
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=$height |
vidcap_height=${VID[$H]} |
fi |
if [ "0" == "$aspect_ratio" ]; then |
aspect_ratio=$(bc -lq <<< "$width / $height") |
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) |
|
if ! is_number $numsecs ; then |
error "Internal error!" |
return $EX_SOFTWARE |
fi |
|
local nc=$numcaps |
|
# Contact sheet minimum cols: |
488,35 → 781,19 |
numcols=$nc |
fi |
|
# Tempdir |
create_temp_dir |
|
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 |
compute_timecodes $timecode_from $interval $numcaps || { |
return $? |
} |
fi |
|
n=1 |
local p="" |
local montage_command="montage -font $font_tstamps -pointsize $pts_tstamps \ |
local base_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 output=$(new_temp_file '-preview.png') |
local VIDCAPFILE=00000001.png |
|
# If the temporal vidcap already exists, abort |
525,60 → 802,64 |
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 |
# 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)..." |
|
p=$(pad 6 $stamp).png |
capfile=$dir/$p |
capture "$f" $stamp || return $? |
apply_stamp "$VIDCAPFILE" $pretty $vidcap_width $vidcap_height || return $? |
|
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 |
hlcapfile=$(new_temp_file "-hl-$(pad 6 $n).png") |
mv "$VIDCAPFILE" "$hlcapfile" |
hlmontage_command+=" \"$hlcapfile\"" |
let 'n++' |
done |
|
let 'n++' # $n++ |
#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 |
|
# 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 |
# 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 |
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'" |
585,44 → 866,159 |
fi |
montage_command+=" \"$output\"" |
|
echo "Composing contact sheet..." >&2 |
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" |
case $( tr '[A-Z]' '[a-z]' <<<$vcodec) in |
# 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" ;; |
wmv2) vcodec="WMV8" ;; # v2 is same as v8 |
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 [ "$vdec" == "ffodivx" ]; then |
if [ "${VID[$VDEC]}" == "ffodivx" ]; then |
vcodec+=" (MPEG-4)" |
elif [ "$vdec" == "ffh264" ]; then |
elif [ "${VID[$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' ;; |
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 "$numsecs")" |
local meta2="Dimensions: ${width}x${height} |
Length: $(pretty_stamp "${VID[$LEN]}")" |
local meta2="Dimensions: ${VID[$W]}x${VID[$H]} |
Format: $vcodec / $acodec |
FPS: $fps" |
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 |
643,25 → 1039,33 |
output="$newout" |
fi |
|
echo -n "Output wrote to " >&2 |
safe_rename "$output" "$(basename "$f").$output_format" |
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 |
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 |
-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. |
671,21 → 1075,34 |
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 |
-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|--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 probably don't need it. |
--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 |
700,6 → 1117,8 |
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 |
} |
|
710,8 → 1129,7 |
# Execute exithdlr on exit |
trap exithdlr EXIT |
|
# Test requirements |
test_programs || exit $EX_UNAVAILABLE |
show_vcs_info |
|
load_config |
|
722,8 → 1140,10 |
|
# 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" \ |
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" |
732,7 → 1152,7 |
case "$1" in |
-i|--interval) |
if ! interval=$(get_interval "$2") ; then |
error "Interval must be a number (given $2)" |
error "Interval must be a (positive) number. Got '$2'." |
exit $EX_USAGE |
fi |
if [ "$interval" -le 0 ]; then |
744,18 → 1164,29 |
;; |
-n|--numcaps) |
if ! is_number "$2" ; then |
error "Number of caps must be a number! (given $2)" |
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 interval" |
error "Starting timestamp must be a valid timecode. Got '$2'." |
exit $EX_USAGE |
fi |
shift |
762,11 → 1193,11 |
;; |
-t|--to) |
if ! totime=$(get_interval "$2") ; then |
error "Ending timestamp must be a valid interval" |
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" |
error "Ending timestamp was set to 0, set to movie length." |
totime=-1 |
fi |
shift |
773,12 → 1204,20 |
;; |
-S|--stamp) |
if ! temp=$(get_interval "$2") ; then |
error "Timestamps must be a valid interval" |
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) |
789,7 → 1228,7 |
-M) decoder=$DEC_MPLAYER ;; |
-H|--height) |
if ! is_number "$2" ; then |
error "Height must be a number (given $2)" |
error "Height must be a (positive) number. Got '$2'." |
exit $EX_USAGE |
fi |
th_height="$2" |
797,16 → 1236,17 |
;; |
-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 |
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 number (given $2)" |
error "Columns must be a (positive) number. Got '$2'." |
exit $EX_USAGE |
fi |
cols="$2" |
813,6 → 1253,44 |
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 |
824,6 → 1302,10 |
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)" |
835,5 → 1317,4 |
|
# }}} # Command line parsing |
|
|
# vim:set ts=4 ai: # |