5,7 → 5,9 |
# vcs |
# Video Contact Sheet *NIX: Generates contact sheets (previews) of videos |
# |
# Copyright (C) 2007 Toni Corvera |
# 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 |
38,22 → 40,38 |
# <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.10" |
declare -r VERSION="1.0.11" |
# {{{ # CHANGELOG |
# History (The full changelog was moved to a separate file and can be found |
# at <http://p.outlyer.net/vcs/files/CHANGELOG>). |
# History (The full changelog 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. |
# 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 |
112,10 → 130,15 |
# (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 |
223,7 → 246,7 |
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 -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 |
230,9 → 253,9 |
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 -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 |
271,7 → 294,7 |
# Global filters take the form |
# filtall_name( vidcapfile1, vidcapfile2, ... ) |
# They're executed in order by filter_all_vidcaps |
declare -a FILTERS_CS=( ) |
declare -a FILTERS_CS |
# The contact sheet creators take the form |
# csheet_name( number of columns, context, width, height, vidcapfile1, |
# vidcapfile2, ... ) : outputfile |
286,7 → 309,7 |
# 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 |
# See csheet_montage for more details |
declare -i DISABLE_SHADOWS=0 |
|
# }}} # Variables |
324,6 → 347,7 |
'stdout' |
'stderr' |
'DEFAULT_END_OFFSET' |
'DEBUG' |
) |
|
# This is only used to exit when -DD is used |
345,7 → 369,7 |
# Override-able hack, this won't work with command line overrides, though |
end_offset=$DEFAULT_END_OFFSET |
interval=$DEFAULT_INTERVAL |
numcaps=$DEAFULT_NUMCAPS |
numcaps=$DEFAULT_NUMCAPS |
} |
|
# Do an override |
413,7 → 437,6 |
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 |
560,13 → 583,19 |
local R="" |
|
if [ $h -gt 0 ]; then |
R+="$h:" |
R="$h:" |
# Unreproducible bug reported by wdef: Minutes printed as hours |
# fixed with "else R="00:"" |
fi |
# Right pad of decimal seconds |
if [ ${#ms} -lt 2 ]; then |
ms="${ms}0" |
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 |
R+=$(pad 2 "$m"):$(pad 2 $s).$ms |
|
# Trim (most) decimals |
sed -r 's/\.([0-9][0-9]).*/.\1/'<<<$R |
579,7 → 608,7 |
get_pretty_size() { |
local f="$1" |
|
local bytes=$(du -DL --bytes "$f" | cut -f1) |
local bytes=$(get_file_size "$f") |
local size="" |
|
if [ "$bytes" -gt $(( 1024**3 )) ]; then |
637,6 → 666,23 |
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() { |
661,7 → 707,7 |
if [ -z $TEMPSTUFF ]; then return 0 ; fi |
inf "Cleaning up..." |
rm -rf ${TEMPSTUFF[*]} |
TEMPSTUFF=( ) |
unset TEMPSTUFF ; declare -a TEMPSTUFF |
} |
|
# Exit callback. This function is executed on exit (correct, failed or |
721,6 → 767,13 |
fi |
} |
|
# |
# trace($1 = function name = $FUNCNAME, function arguments...) |
trace() { |
if [ "$DEBUG" -ne "1" ]; then return; fi |
echo "[TRACE]: $@" >&2 |
} |
|
# }}} # Convenience functions |
|
# {{{ # Core functionality |
728,6 → 781,7 |
# 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 |
745,6 → 799,7 |
# 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" |
763,7 → 818,7 |
# or colour combination you like. |
# randomize_look() |
randomize_look() { |
|
trace $FUNCNAME $@ |
local mode=f lineno |
|
if [ "f" == $mode ]; then # Random mode |
825,6 → 880,8 |
# 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 |
872,7 → 929,8 |
return $EX_USAGE |
fi |
|
local LTC=( ) stamp=$st |
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!" |
889,6 → 947,7 |
# 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 |
|
935,15 → 994,18 |
# Capture a frame |
# capture($1 = filename, $2 = second) |
capture() { |
trace $FUNCNAME $@ |
local f=$1 stamp=$2 |
local VIDCAPFILE=00000001.png |
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 1 -ss $stamp $shoehorned "$f" |
-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 |
{ |
973,12 → 1035,13 |
# 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+=" $( $filter "$1" "$2" "$3" "$4" ) " |
cmdopts="$cmdopts $( $filter "$1" "$2" "$3" "$4" ) " |
done |
local t=$(new_temp_file .png) |
eval "convert '$1' $cmdopts '$t'" |
993,6 → 1056,7 |
#} |
|
filt_resize() { |
trace $FUNCNAME $@ |
local f="$1" t=$2 w=$3 h=$4 |
|
# Note the '!', required to change the aspect ratio |
1002,6 → 1066,7 |
# 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' " |
1013,6 → 1078,7 |
# 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 ) ) )) |
1026,6 → 1092,7 |
# 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 " |
1034,6 → 1101,7 |
# 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 |
1075,6 → 1143,7 |
# create_contact_sheet($1 = columns, $2 = context, $3 = width, $4 = height, |
# $5...$# = vidcaps) : output |
create_contact_sheet() { |
trace $FUNCNAME $@ |
$CSHEET_DELEGATE "$@" |
} |
|
1082,13 → 1151,13 |
# 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 |
case $ctx in |
$CTX_STD|$CTX_HL) hpad=10 vpad=5 ;; |
$CTX_EXT) hpad=5 vpad=2 ;; |
*) error "Internal error" && return $EX_SOFTWARE ;; |
esac |
# 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" |
|
1108,6 → 1177,7 |
# 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 |
1131,7 → 1201,7 |
|
# Holds the files that will form the full contact sheet |
# each file is a row on the final composition |
local -a rowfiles=( ) |
local -a rowfiles |
|
# Dimensions of the canvas for each row, it should be big enough |
# to hold all snaps. |
1173,7 → 1243,7 |
|
# Stick the vicap in the canvas |
#convert -geometry +${accoffset}+0 "$rowfile" "$1" -composite "$rowfile" |
cmdopts+=" -geometry +${accoffset}+0 '$1' -composite " |
cmdopts="$cmdopts -geometry +${accoffset}+0 '$1' -composite " |
|
offset=$(( $minoffset + ( $RANDOM % $maxoffset ) )) |
let 'accoffset=accoffset + w - offset' |
1199,7 → 1269,7 |
maxoffset=$(( $h / 4 )) |
offset=$(( $minoffset + ( $RANDOM % $maxoffset ) )) |
# The row is also offset horizontally |
cmdopts+=" -geometry +$(( $RANDOM % $maxoffset ))+$accoffset '$row' -composite " |
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 |
1214,6 → 1284,7 |
# 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 |
1222,6 → 1293,7 |
# 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 |
1257,6 → 1329,7 |
# Creates the contact sheet. |
# process($1 = file) |
process() { |
trace $FUNCNAME $@ |
local f=$1 |
|
local numcols= |
1291,6 → 1364,8 |
|
create_temp_dir |
|
unset TIMECODES |
unset HLTIMECODES |
# Compute the stamps (if in auto mode)... |
TIMECODES=${initial_stamps[*]} |
if [ $manual_mode -ne 1 ]; then |
1302,13 → 1377,22 |
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 |
local VIDCAPFILE=00000005.png |
|
# If the temporal vidcap already exists, abort |
if [ -f $VIDCAPFILE ]; then |
error "Temporal vidcap file ($VIDCAPFILE) exists, remove it before running!." |
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 ) |
|
1315,7 → 1399,8 |
# Highlights |
local hlfile n=1 # hlfile Must be outside the if! |
if [ "$HLTIMECODES" ]; then |
local hlcapfile= pretty= capfiles=( ) |
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) |
1349,7 → 1434,8 |
|
# Normal captures |
# TODO: Don't reference $VIDCAPFILE |
local capfile pretty n=1 capfiles=( ) |
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)..." |
1385,13 → 1471,14 |
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 |
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= capfiles=( ) |
# The image size of the extra captures is 1/4 |
let 'w=vidcap_width/2, h=vidcap_height/2' |
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)..." |
1444,10 → 1531,11 |
WMV1) vcodec="WMV7" ;; |
WMV2) vcodec="WMV8" ;; |
WMV3) vcodec="WMV9" ;; |
WMVA) vcodec="WMV9 Advanced Profile" ;; # Not VC1 compliant. Unsupported. |
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" ;; |
1772,8 → 1860,8 |
-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. |
-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 |