/video-contact-sheet/branches/1.13/tests/GNUmakefile |
---|
0,0 → 1,37 |
# $Id$ |
VCS:=../vcs |
#VCS:=../portability/oldvcs/vcs-1.11.2 |
extract=sed -n "/^$*()"'/,/^}$$/p' "$(VCS)" |
TESTS_FILE=src/tests.txt |
TEST_MAKER=src/make_test.bash |
get_interval_reqs = $(addprefix inc/, \ |
$(addsuffix .func.bash,get_interval trace error \ |
is_number tolower assert awkexf fptest \ |
fsimeq notice) \ |
$(addsuffix .inc.bash,constants) \ |
) |
all: get_interval |
inc/constants.inc.bash: $(VCS) |
mkdir -p inc/ |
echo 'declare -r RELEASE=0' > $@ |
echo 'declare DEBUG=1' >> $@ |
echo '$(shell grep -m1 'VERSION=' "$(VCS)")' >> $@ |
sed -n '/{{{ # Constants/,/}}}/p' "$(VCS)" >> $@ |
get_interval: $(TESTS_FILE) $(get_interval_reqs) |
$(TEST_MAKER) $@ $(get_interval_reqs) > $@.test.bash |
chmod +x $@.test.bash |
inc/%.func.bash: $(VCS) |
mkdir -p inc |
$(extract) >$@ |
clean: |
$(RM) inc/* *.test.bash |
-rmdir -p inc/ |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/branches/1.13/tests/src/unittest.bash |
---|
0,0 → 1,33 |
# |
# $Id$ |
# Receives the raw input as found in tests.txt |
# |
TESTNUM=0 |
G=$(tput setaf 2 ; tput bold ) |
R=$(tput setaf 1 ; tput bold) |
CLR=$(tput sgr0) |
RET=0 |
function unittest { |
let 'TESTNUM++' |
a="$@" |
fn=$(cut -d' ' -f1 <<<"$a") |
if [[ $TESTNUM -eq 1 ]]; then |
type $fn |
fi |
args=$(cut -d' ' -f2- <<<"$a" | sed 's/:.*$//' | sed 's/ *$//') |
expected=$(cut -d' ' -f2- <<<"$a" | sed 's/.*://') |
echo "$fn($args) -> $expected" >&2 |
ret=$($fn $args) |
RET=$[ $RET + $?] |
if [[ $ret != $expected ]] && ! fptest "$ret" ~ "$expected" ; then |
echo -n "${R}FAILED => $ret != '$expected'" |
else |
echo -n "${G}PASSED => $ret ~= $expected" |
fi |
echo $CLR |
} |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/branches/1.13/tests/src/tests.txt |
---|
0,0 → 1,15 |
# $Id$ |
# Format: |
# test input [input ...] : expected_result |
# |
get_interval 1h : 3600 |
get_interval 1h1m : 3660 |
get_interval 1h1m1 : 3661 |
get_interval 1h1m1s : 3661 |
get_interval 1.22 : 1.22 |
get_interval 1s.22 : 1.22 |
get_interval .11.11.11 : 0.33 |
get_interval 1s.11.11 : 1.22 |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/branches/1.13/tests/src/make_test.bash |
---|
0,0 → 1,25 |
#!/bin/bash |
# This file can be used to generate a test script |
# The actual tests are contained in tests.txt |
testsfile=$(dirname "$0")/tests.txt |
TESTNAME=$1 |
shift |
REQS=$@ |
echo '#!/bin/bash' |
for req in $REQS; do |
echo "source $req" |
done |
echo "source src/unittest.bash" |
echo 'while read line ; do' |
echo ' unittest $line' |
echo 'done <<< "$(sed "/^[[:space:]]*#/d" "'$testsfile'" | grep "^'${TESTNAME}' ")"' |
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.13/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.13/dist/CHANGELOG |
---|
0,0 → 1,484 |
1.13: |
* Complete manual pages |
* Added 'anonymous' to the list of settings |
* Make "dynamic" settings case-insensitive, i.e. |
bg_heading=$bg_contact can also be written bg_heading=$BG_CONTACT |
* Remove meaningless decimals when generating config files |
* New setting: 'profiles', allows loading profiles automatically and also |
loading profiles from other profiles |
* Change also title colours in 'black' and 'white' profiles |
* Codec identification for Fraps captures [#179] |
* New setting 'capturer' deprecates 'decoder'. Uses actual names (ffmpeg and |
mplayer) instead of variables ($DEC_FFMPEG and $DEC_MPLAYER) |
* Changed default verbosity level to INFO (same output as before) |
* BUGFIXES: |
- Correct extended-set resizing |
- Constraint checking of settings failed silently |
for alias-only names |
- Code typo: Produced error message when extended mode was |
narrower than contact sheet |
- Only warned about command-line GETOPT override when using uppercase |
setting name |
- Fixes for FreeBSD compatibility (regressions introduced in 1.12.3, |
[#189]): |
> Wrong parsing of floats and positions/percentages on |
FreeBSD's bash 4.0.10 (FreeBSD only) |
> Unsupported 'expr match' replaced by awk |
- Fix error when avoiding repeated captures |
* Scheduled code cleanup: |
- Removal of deprecated configuration options: DEFAULT_END_OFFSET, |
shoehorned and safe_rename_pattern |
- Removal of deprecated option '--undocumented shoehorn' |
- Deprecation of '--end_offset' ('--end-offset' should be used instead) |
* COSMETIC: |
- Add '(h.264)' to ffmpeg video codec id when appropriate |
- Correct "Capturing in range..." message |
- Refer to configuration variables as "settings" |
- Print informational messages for each funky mode |
- Pretty-print timestamps when doing safe-length measuring [#177] |
- Colourised tracing |
* OTHER: |
- Help rewordings and clarification |
- Help fixes: |
- Old DVD mode description was still displayed |
- Incorrectly had `--jpeg 2' instead of `--jpeg2' or `--jpeg=2' |
- Added new distribution profile: compact |
- Added new example profiles (black-mosaic and black-compact-chain), the |
latter demonstrating how a profile can load other profiles |
- List also builtin profiles with --profile :list |
* UNDOCUMENTED/DEBUG: |
- Undocumented options: |
- Don't fail on unknown sub-options |
- New sub-options: trace, display and discard |
- Debugging facility: --undocumented trace=funcname |
- Display $POSIXLY_CORRECT and sed's path in 'vcs -DD' output |
- Display awk and sed versions, if possible, in 'vcs -DD' output |
* INTERNAL: |
- Check ImageMagick through convert instead of identify |
- Don't run filters in subshells |
- Fix some typos |
- Bugfix: Actually use passed timestamp in filt_apply_timestamp() |
- Bugfix: Don't accept --shoehorn (was deprecated and unhandled) |
- Set LANG to C |
- Added simeq() and '~' fptest operator |
1.12.3 (2011-07-17): |
* BUGFIX: Actually handle --ffmpeg and --mplayer [#169] |
* BUGFIX: Correct parsing of -U [#187] |
* OTHER: |
- Fix printing of remaining options on command-line error |
- Switch to a minimum of bash 3.1 [#173] |
- Avoid re-capturing the same frame twice [#122] |
- Use getent instead of /etc/passwd when available |
* INTERNAL: |
- Use of Bash's 'caller' in 'assert' and 'trace' |
- 'assert' prints a call trace on error |
- 'assert_if' |
- Don't use mplayer's length as a ceil for timecode removal [#174] |
1.12.2 (2010-08-24): |
* BUGFIX: Fix cleanup of temporary files (regression since 1.11.2). [#167] |
Submitted by Jason Tackaberry. |
* FEATURES: |
- Added 'fg_all', 'bg_all' and 'font_all' config variables. [#156] |
- Added 'nonlatin_filenames' config variable. [#159] |
- Added identification for VP8 (WebM). [#166] |
* OTHER: |
- Print variable names in lowercase when using --generate. |
1.12.1 (2010-04-23): |
* BUGFIXES: |
- Workaround for cases in which GAWK uses comma as decimal separator. |
Any OS with GAWK 3.1.3 to 3.1.5 was affected (where the environment |
language uses commas, e.g. Debian Lenny with many European languages) |
- Don't try to go on in DVD mode with unreadable ISOs |
1.12: (2010-04-10) |
* New features/tweaks: |
- Loading of random configuration files (--config / -C) |
- Profiles: Similar to above but simpler syntax (--profile / -p) |
- Config/Profile generation from command-line (--generate) |
- Adapt heading, title and footer height to font size (fonts that used |
to get cropped should now be fine) |
* DVD mode cleanup: |
- Command-line switched to match "normal" files: |
Before: |
$ vcs --dvd /dev/dvd 0 or $ vcs --dvd /dev/dvd 1 |
Equivalents now: |
$ vcs --dvd /dev/dvd or $ vcs --dvd --dvd-title 1 /dev/dvd |
* New end-offset behaviour: |
- A 5.5% end offset is applied by default |
- Can be disabled with -E0 or end_offset=0 |
- MIN_LENGTH_FOR_END_OFFSET is no longer used |
* Configuration files cleanup: |
- Simplified or more meaningful names where appropriate (the older |
names will continue to work for a while, and users will be warned) |
"vcs --generate" with no other arguments can be used to translate them |
- Validation of configuration options. |
Incorrect values will be discarded and an error shown; processing will |
continue. |
- Configuration searched in ~/.vcs/vcs.conf too |
- Syntax enhancements: |
> Comments can now be included in-line |
> Putting '#' in a value now requires using the "escaped form" '$#' |
> Semicolons (;) also serve to start comments: When one is found the |
rest of the line is ignored, they continue to be disallowed in values |
i.e. 'tl;dr' will be parsed as 'tl' |
* Other: |
- Accept timecodes and percentages in end_offset, both from the |
command-line and in configuration files |
- Print the start and end timestamps in effect before capturing |
- No longer accept interval zero (used to be re-set to default) |
- Tighter printing of overrides and no longer printed as warning |
- Strickter handing of wrong options |
- Fall back to Helvetica also when no fonts dir is located. Look |
in /usr/local too. |
- --end-offset added as an alias to --end_offset |
- Starting with 1.12 a tarball + makefile is also provided |
* BUGFIXES: |
- Avoid possible (unlikely) usage of scientific notation in internal |
calculations |
- Distinguish between default end offset and user's end offset with the |
same value |
- Handle --nonlatin correctly |
- DVD Mode + FFmpeg identification: Check VOB #0 instead of #1 |
- Don't print escape codes to stdout when testing colour printing |
* Options removed: |
--shoehorn, temporary replacement: --undocumented shoehorn. Will be gone |
in 1.13 |
--mincho, replaced by --nonlatin since 1.11 |
MIN_LENGTH_FOR_END_OFFSET, as explained above, no longer needed |
* INTERNAL: |
- $CFGFILE replaced by ~/.vcs.conf |
- Use -p for profiles instead of -P (used, undocumented, in 1.11) |
1.11.2: (2010-03-19) |
* Added Cook and Sipro (RealAudio 4, 5 & 6) codecs |
* BUGFIXES: |
- Remove extra, empty, temporary dir |
- Use standard awk syntax for exponentiation (pyth_th) |
- Workaround for systems that don't register fonts with ImageMagick |
* DEBUG: Print to stderr when probbing with mplayer too |
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 |
# vim:set ts=3 sw=3 et textwidth=80: # |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/branches/1.13/dist/vcs |
---|
0,0 → 1,5223 |
#!/usr/bin/env bash |
# |
# $Rev$ $Date$ |
# |
# vcs |
# Video Contact Sheet *NIX: Generates contact sheets (previews) of videos |
# |
# Copyright (C) 2007, 2008, 2009, 2010, 2011 Toni Corvera |
# with patches from Phil Grundig and suggestions/corrections from |
# many others (see homepage) |
# |
# This program 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 program 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>) |
# |
# The full changelog can be found at <http://p.outlyer.net/vcs/files/CHANGELOG> |
declare -r VERSION="1.13" |
declare -r RELEASE=0 |
set -e |
# GAWK 3.1.3 to 3.1.5 print decimals (with printf) according to locale (i.e. |
#+decimal comma separator in some locales, which is apparently POSIX correct). |
#+Older and newer versions, though, need either POSIXLY_CORRECT to be set (even |
#+be empty), --posix or --use-lc-numeric to honour locale. |
# MAWK appears to always use dots. |
# Info: <http://www.gnu.org/manual/gawk/html_node/Conversion.html> |
#export POSIXLY_CORRECT=1 # Immitate behaviour in newer gawk |
export LC_NUMERIC=C |
# All output from tools is either removed or parsed. |
# Standardise on the C locale. |
export LANG=C |
export LC_COLLATE=C # Ensure collation (e.g. tr a-z A-Z) works as expected |
# 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 3.1 |
if [ "${BASH_VERSINFO[0]}" -lt 3 ] || |
[ "${BASH_VERSINFO[0]}" -eq 3 -a "${BASH_VERSINFO[1]}" -lt 1 ]; then |
echo "Bash 3.1 or higher is required" >&2 |
exit 1 |
fi |
} |
# {{{ # TO-DO |
# * [[x1]] Find out why the order of ffmpeg arguments breaks some files. |
# * Change default DVD_TITLE to 0 |
# * Deprecation schedule: |
# DEPRECATED FROM | EXPECTED REMOVAL | DESCRIPTION |
# ------------------|------------------|------------------------------------------------------ |
# 1.12 1.14 Old names for settings renamed in 1.12. |
# output_format, plain_messages, th_height, |
# hpad, font_mincho |
# In 1.13 the new names start to be used internally. |
# -------------------------------------------------------------------------------------------- |
# 1.13 1.14 --end_offset -> --end-offset |
# 1.13 1.14 auto-loading ./vcs.conf (lesser version of profiles) |
# -C :pwd will stay |
# -------------------------------------------------------------------------------------------- |
# ? ?+1 decoder. Replaced by capturer, the syntax changes |
# ? ?+1 --funky -> --profile |
# * 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. |
# implementation |
# - Global variables will be capitalised while local variables will be lowercase |
# - Setting names (configuration file variables) will be case insensitive, but always |
# displayed and documented in lowercase |
# * Optimisations: |
# - Reduce the number of forks/subshells |
# * Portability notes |
# - 'sed -r' is not portable, works in GNU, FreeBSD equivalent -E |
# - 'grep -o' is not portable, works in GNU and FreeBSD |
# Alternatives: |
# > One match per line: |
# $ sed -n -e 's/.*\(SEARCH\).*/\1/gp |
# > Multiple matches per line: (like grep -o) |
# $ sed -n -e 's/\(SEARCH\)/\1\ |
# /gp' | sed -e 's/.*\(SEARCH\).*/\1/' -e '/SEARCH/!d' |
# The p flag ONLY prints IF a substition succeeded |
# - 'expr' is not a builtin, 'expr match' is not understood in, at least, FreeBSD |
# expr operations should have equivalent bash string manipulation expressions |
# - 'egrep' is deprecated in SUS v2, 'grep -E' replaces it [[x2]] |
# * UNIX filter equivalencies |
# - cut -d: -f1 === awk -F: '{print $1}' === awk '{BEGIN FS=":"}; {print $1}' |
# - grep -v pattern === sed '/pattern/d' |
# }}} # TO-DO |
# {{{ # Constants |
# Use configuration files to modify the behaviour of the |
# script. Using them 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 |
# * ~/.vcs.conf: Per-user conf, second least precedence |
# * ./vcs.conf: Per-dir config, most precedence (deprecated) |
# |
# The variables that can be overriden are below the block of constants ahead. |
# Default value for INTERVAL, setting interval to 0 also re-sets it to this value |
declare -ri DEFAULT_INTERVAL=300 |
# 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/>" |
# Filename pattern for safe renaming (appending numbers until finding a name |
#+not in use). |
# Since 1.13 no longer configurable. Don't mess with it too much. |
# By default "%b-%N.%e" where: |
# %b is the basename (file name without extension) |
# %N is the appended number |
# %e is the extension |
# Will first try %b.%e, then %b-1.%e, %b-2.%e and so on, i.e. |
#+creates outputs like "output.avi-1.png" |
declare -r SAFE_RENAME_PATTERN="%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 |
# 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 |
# Used for feedback |
declare -r NL=$'\012' # Newline |
#declare -r TAB=$'\011' # Tab |
# New in 1.13 |
# Offsets to try when trying to avoid blank frames |
# See capture() and capture_and_evade() |
declare -a EVASION_ALTERNATIVES=( -5 +5 -10 +10 -30 +30 ) |
# }}} # 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 SIGNATURE="Preview created by" |
# By default sign as the system's username (see -u, -U) |
declare USERNAME=$(id -un) |
# Which of the two methods should be used to guess the number of thumbnails |
declare -i TIMECODE_FROM=$TC_INTERVAL |
# New in 1.13. Replaces the old 'decoder' symbolic option. |
# The value is *not* the name of the executable, but a supported capturer, |
#+right now 'ffmpeg' or 'mplayer'. |
# When none is defined, the first available element in CAPTURERS is used. |
declare CAPTURER= |
# Options used in imagemagick, these options set the final aspect |
# of the contact sheet |
declare FORMAT=png # ImageMagick decides the type from the extension |
declare -i 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 practice 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 |
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 -E / $END_OFFSET |
declare -r DEFAULT_END_OFFSET="5.5%" |
# 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_INFO |
# Set to 1 to disable colours in console output |
declare -i SIMPLE_FEEDBACK=0 |
# See coherence_check for more details |
declare -i DISABLE_SHADOWS=0 |
declare -i DISABLE_TIMESTAMPS=0 |
# This font is used to display international names (i.e. CJK names) correctly |
# Help from users who actually need 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 NONLATIN_FONT= # Filename or font name as known to ImageMagick (identify -list font) |
# Introduced in 1.12.2: |
# When true (1) uses $NONLATIN_FONT to print the filename, otherwise the same |
#+font as the heading is used. |
# See -I and --nonlatin |
declare -i NONLATIN_FILENAMES=0 |
# Output of capturing programs is redirected here |
declare STDOUT=/dev/null STDERR=/dev/null |
# Override-able since 1.11: |
# Height of the thumbnails, by default use same as input |
declare HEIGHT='100%' |
declare INTERVAL=$DEFAULT_INTERVAL # Interval of captures (~length/$NUMCAPS) |
declare -i NUMCAPS=16 # Number of captures (~length/$INTERVAL) |
# This is the 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 PADDING=2 |
declare -i COLUMNS=2 # Number of output columns |
# This amount of time is *not* captured from the end of the video |
declare END_OFFSET=$DEFAULT_END_OFFSET |
# When set to 1 the signature won't contain the "Preview created by..." line |
declare -i ANONYMOUS_MODE=0 |
# Profile(s) to load by default |
declare PROFILES= |
# }}} # 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 # Temporary files |
declare -a TIMECODES # Timestamps of the video captures |
declare -a HLTIMECODES # Timestamps of the highlights (see -l) |
declare VCSTEMPDIR= # Temporary directory, all temporary 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= PREFIX_DBG= 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 " |
# 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 must set the variable $RESULT with parameters to add to 'convert', a single |
# call to convert will be issued for each capture like: |
# $ convert vidcap.png $RESULT [...] vidcap.png |
# 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 |
# Holds a list of captured frames (to avoid recapturing) |
# Format <timestamp>:<filename>[NL]<timestamp>:<filename>... |
declare CAPTURES= |
# Gravity of the timestamp |
declare GRAV_TIMESTAMP=SouthEast |
# 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=( ) |
# 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 |
# Starting with 1.13 this value can no longer be overridden directly, |
#+setting 'decoder' actually changes CAPTURER. DECODER is still used |
#+internally. |
declare -i DECODER=$DEC_FFMPEG |
# 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_BIN= |
declare FFMPEG_BIN= |
# 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 |
declare -r UNDFLAG_DISPLAY_COMMAND=eog # Command to run with -Z display |
# Stores the names of variables overridden from the command-line, |
#+see cmdline_override() and "--override" |
declare CMDLINE_OVERRIDES="" |
# Implicit error handling (see die()), obviously inspired by C's errno |
# and PHP's die(). Functions adapted to use them allow uses like: |
# some_function arg || die |
# which will exit with the appropriate exit code and print the error message |
# (Introduced in 1.12, still being retrofitted) |
declare -i ERROR_CODE=0 # Exit code associated with the last error |
declare ERROR_MSG= # Error message associated to the last error |
# Used to buffer feedback (see buffered()) |
declare BUFFER= |
# This is only used to exit when -DD is used |
declare -i DEBUGGED=0 # It will be 1 after using -DD |
# See post_getopt_hooks() |
# Format: Priority:Command[:Arguments] (lower priority run sooner) |
declare -a POST_GETOPT_HOOKS=( ) |
declare -i DVD_MODE=0 DVD_TITLE= |
declare -a DVD_TITLES=( ) # Titles for each input DVD, filled by --dvd-title |
declare DVD_MOUNTP= # Mountpoint for DVD, detected & reset for each DVD |
declare DVD_VTS= # VTS, detected & reset for each DVD |
# New in 1.13: Modularisation of video decoders and identifiers, to ease additions |
# There's two types of video tools supported: capturers and identifiers |
# A capturer is used to extract video frames |
# An identifier is used to extract video information |
# This abstraction provides an interface to allow easy addition of tools and |
#+to handle missing tools with more ease than before. Each tool has a set of |
#+associated functions, some of them optional that provide the same interface. |
# Capturer functions: |
# <name>_capture(in, ts, out): Capture the frame from 'in' at 'ts' to 'out' |
# <name>_dvd_capture(in, ts, out) [optional]: Same for DVDs |
# Identifier functions: |
# <name>_identify(f): Extract information from 'f', fill <NAME>_ID with it |
# also fills RESULT with the same values |
# <name>_probe(file, ts): Try reaching 'ts' (test for video length) |
# Supported capturers. In order of preference. |
# An associated <name>_capturer must be defined |
CAPTURERS=( ffmpeg mplayer ) |
# Supported identifiers. In order of preference |
# An associated <name>_identify must be defined |
# 'classic' is a combination of ffmpeg and mplayer |
IDENTIFIERS=( classic ffmpeg mplayer ) |
# Will be filled with the elements from CAPTURERS found on the system |
# Lookup is done with <name>_check_avail, an associated <NAME>_BIN is to be |
# defined there, i.e. mplayer_test_avail sets MPLAYER_BIN |
CAPTURERS_AVAIL=( ) |
# Like CAPTURERS_AVAIL, for IDENTIFIERS |
IDENTIFIERS_AVAIL=( ) |
# Same for IDENTIFIERS |
IDENTIFIER='' |
# If 1, the selected CAPTURER understands the use of milliseconds |
CAPTURER_HAS_MS=0 |
# This variable is used in functions to avoid running them in a subshell, i.e. |
# instead of |
# ret=$(myfunc) |
# such functions are used as |
# myfunc |
# ret=$RESULT |
# This way 'myfunc' has access to all variables and can modify them. |
# Every function that modifies RESULT should overwrite its value. |
RESULT='' |
# Set by init_filt_film: |
FILMSTRIP= # Filename of the sprocket-holes strip image |
FILMSTRIP_HOLE_HEIGHT= # Height of an individual hole |
# Set by -Z trace=<FILTER>, where <FILTER> is regex to reduce the trace |
# verbosity. Only function names that match it will be printed. |
# 'grep -p' will be used to match |
INTERNAL_TRACE_FILTER= |
INTERNAL_NO_TRACE=0 # When 1, tracing is disabled (used by -DD) |
# }}} # Variables |
# {{{ # Configuration handling |
# New override system: This variable maps configuration variables to actual |
#+variables used in the script. Each item in the array follows the syntax: |
# <cfg variable>:<variable>:<flags>:[type constraints] Where: |
#+ cfg variable: is the name of the configuration file variable |
#+ variable: is the name of the actual variable. If empty or '=', it will be |
#+ the same as cfg variable. |
#+ flags can currently be: |
#+ "deprecated=new name": Will print a deprecation warning and suggest to use |
#+ "new name" instead |
#+ "striked": Variable is marked for removal, will print a warning about it |
#+ directing anyone needing it to contact me. Only used for variables |
#+ believed to be no longer needed |
#+ "gone": Variable removed in the current version |
#+ "alias": Marks an alias, duplicate name intended to stay |
#+ "meta": Special variable that will modify other variables (e.g. font_all |
#+ modifies all font_ variables. |
#+ "=": ignore |
#+ type constraints: a character indicating accepted values: |
# n -> Number (Natural, positive Integer or zero) |
# p -> Number, not zero |
# t -> Timestamp |
# b -> Bool |
# h -> Positive, non-zero, number or percentage |
# f -> Float or fraction |
# D -> only $DEC_* constants |
# T -> only $TC_* constants |
# V -> only $V_* constants |
# I -> interval or percentage |
# x -> Special, variable with a set of possible values |
# Note during the switch to the new system most variables will remain unchanged |
# Also, the new system is case insensitive to variable names |
declare -ra OVERRIDE_MAP=( |
"USER:USERNAME::" |
"EXTENDED_FACTOR:=:=:f" |
"STDOUT::" |
"STDERR::" |
"DEBUG:=:=:b" |
"INTERVAL:=:=:t" |
"NUMCAPS:=:=:p" |
"CAPTURES:NUMCAPS:alias:n" # Alias |
"GETOPT::" # Note it makes no sense as command-line override |
"COLUMNS:=:=:p" |
"COLS:COLUMNS:alias:p" # Traditional name |
"DISABLE_SHADOWS:=:=:b" |
"DISABLE_TIMESTAMPS:=:=:b" |
"BG_HEADING::" |
"BG_SIGN::" |
"BG_TITLE::" |
"BG_CONTACT::" |
"BG_TSTAMPS::" |
"FG_HEADING::" |
"FG_SIGN::" |
"FG_TSTAMPS::" |
"FG_TITLE::" |
"FONT_HEADING::" |
"FONT_SIGN::" |
"FONT_TSTAMPS::" |
"FONT_TITLE::" |
"FONT_ALL:=:meta" # see parse_override |
"BG_ALL:=:meta" |
"FG_ALL:=:meta" |
"PTS_TSTAMPS::" |
"PTS_META::" |
"PTS_SIGN::" |
"PTS_TITLE::" |
# Aliases for cosmetic stuff |
"BG_HEADER:BG_HEADING:alias" |
"BG_SIGNATURE:BG_SIGN:alias" |
"BG_FOOTER:BG_SIGN:alias" |
"BG_SHEET:BG_CONTACT:alias" |
"FG_HEADER:FG_HEADING:alias" |
"FG_SIGNATURE:FG_SIGN:alias" |
"FG_FOOTER:FG_SIGN:alias" |
"FONT_HEADER:FONT_HEADING:alias" |
"FONT_META:FONT_HEADING:alias" |
"FONT_SIGNATURE:FONT_SIGN:alias" |
"FONT_FOOTER:FONT_SIGN:alias" |
"PTS_HEADING:PTS_META:alias" |
"PTS_HEADER:PTS_META:alias" |
"PTS_SIGNATURE:PTS_SIGN:alias" |
"PTS_FOOTER:PTS_SIGN:alias" |
"SIGNATURE:=:" |
"USER_SIGNATURE:SIGNATURE:deprecated=SIGNATURE" # Deprecated since 1.12 |
"QUALITY:=:=:n" |
"OUTPUT_QUALITY:QUALITY:deprecated=QUALITY:n" # Deprecated since 1.12 |
# TODO: These variables are evaluated to constants, would be better to |
# use some symbolic system (e.g. decoder=f instead of decoder=$DEC_FFMPEG) |
"DECODER:=:meta:D" # To be deprecated |
#"CAPTURE_MODE:TIMECODE_FROM:alias:T" |
"TIMECODE_FROM:=:=:T" |
"VERBOSITY:=:=:V" |
"SIMPLE_FEEDBACK:=:=:b" |
"CAPTURER:=:=:x" # Setting this modifies DECODER and CAPTURER_HAS_MS, from pick_tools() |
"HEIGHT:=:=:h" |
"PADDING:=:=:n" |
"NONLATIN_FONT::" |
"NONLATIN_FILENAMES:=:=:b" |
"ANONYMOUS:ANONYMOUS_MODE:=:b" |
"FORMAT::" |
"END_OFFSET:=:=:I" # New, used to have a two-variables assignment before USR_* |
"PROFILES:=:meta:P" # New in 1.13 |
# TODO TBA: |
#"noboldfeedback::" # Colour but not bold |
# Deprecations, all these since 1.12 |
"OUTPUT_FORMAT:FORMAT:deprecated=FORMAT" |
"PLAIN_MESSAGES:SIMPLE_FEEDBACK:deprecated=SIMPLE_FEEDBACK:b" |
"TH_HEIGHT:HEIGHT:deprecated=HEIGHT:h" |
"HPAD:PADDING:deprecated=PADDING:n" |
"FONT_MINCHO:NONLATIN_FONT:deprecated=NONLATIN_FONT" |
# Gone. Since 1.12 |
"MIN_LENGTH_FOR_END_OFFSET::gone:" |
# Gone. Since 1.13 |
"SHOEHORNED::gone" |
"SAFE_RENAME_PATTERN::gone" |
"DEFAULT_END_OFFSET::gone:" |
) |
# Load a configuration file |
# File *MUST* exist |
# Configuration files are a series of variable=value assignment; they'll be |
#+evaluated directly so they can refer to other variables (with their value at |
#+the point of the assignment). |
# Quotes shouldn't be used (they'll be kept) |
# Since 1.12 comments can be placed in-line (i.e. after an assignment), |
# Literal '#' can be written as '$#' |
# ';' can be used to mark an end of line, anything after it will be ignored |
#+(making it equivalent to '#'), there's no way to include a literal ';' |
# load_config_file($1 = file, [$2 = type (description) = 'Settings']) |
load_config_file() { |
trace $@ |
local cfgfile=$1 |
local desc=$2 |
[[ $desc ]] || desc='Settings' |
local por= # Parsed override |
local varname= tmp= flag= bashcode= feedback= ov= |
while read line ; do # auto variable $line |
[[ ! $line =~ ^[[:space:]]*# ]] || continue # Don't feed comments |
parse_override "$line" |
por=$RESULT |
if [[ $por ]]; then |
varname=${por/% *} # Everything up to the first space... |
tmp=${por#* } # Rest of string |
flag=${tmp/% *} |
if [[ $flag == '=' ]]; then |
# No need to override... |
feedback="$varname(=)" |
else |
feedback=$varname |
fi |
ov="$ov, $feedback" |
fi |
done <$cfgfile |
[[ -z $ov ]] || inf "$desc from $cfgfile:$NL ${ov:2}" |
# No loaded overrides but errors/warnings to print, do print the file name |
if [[ ( -z $ov ) && $BUFFER ]]; then |
inf "In $cfgfile:" |
fi |
flush_buffered ' ' |
} |
# Loads the configuration files if present |
# load_config() |
load_config() { |
local -a CONFIGS=( /etc/vcs.conf ~/.vcs.conf ~/.vcs/vcs.conf ./vcs.conf ) |
for cfgfile in "${CONFIGS[@]}" ;do |
[[ -f $cfgfile ]] || continue |
load_config_file "$cfgfile" |
done |
if [[ -f "./vcs.conf" ]]; then |
warn "'./vcs.conf' won't be loaded automatically starting with vcs 1.14" |
warn " use '-C :pwd' to manually load it, or convert it to a profile" |
fi |
} |
# Load a profile, if found; fail otherwise |
# Profiles are just configuration files that can be loaded on demand (whereas |
#+config files are always loaded) and be given a name. |
# See load_config_file() for comments on the syntax |
# Locations to be searched, in order: |
#+ 1) ~/.vcs/profiles/NAME.conf |
#+ 2) /usr/local/share/vcs/profiles/NAME.conf |
#+ 3) /usr/share/vcs/profiles/NAME.conf |
#+i.e. files in ~/.vcs/ will prevent loading files named like them in /usr |
# load_profile($1 = profile name) |
load_profile() { |
trace $@ |
local p=$1 prof= |
local -a PATHS=( ~/.vcs/profiles/ /usr/local/share/vcs/profiles/ /usr/share/vcs/profiles/ ) |
if [[ ${p:0:1} == ':' ]]; then |
case $p in |
:list) |
echo "Builtin profiles:" |
echo ' * classic: Classic colour scheme from previous versions' |
echo ' * 1.0: Initial colour scheme from ancient versions' |
# No need to be efficient here... |
echo "Profiles located:" |
local path= profname= |
# 1) Find all profiles |
# 2) (sed) Extract profile file name |
# 3 & 4) (sort+uniq) Keep only first hits for each name (most precedence) |
# 5) (while) Process each name |
# 6) (for) Re-locate most precedent profile |
# 7) (echo x3) Print <name>[: description] |
# 8) (sed) Indent with ' * ' |
find "${PATHS[@]}" -name '*.conf' 2>/dev/null \ |
| sed -e 's#.*/\(.*\)\.conf#\1#' \ |
| sort | uniq \ |
| while read profname ; do |
for path in "${PATHS[@]}" ; do |
path=$path$profname.conf |
[[ -f $path ]] || continue |
echo -n "$profname" |
# [ ] here contains <space><tab>. Mawk doesn't understand |
# [[:space:]] |
echo -n $(awk 'sub(/#[ ]*vcs:desc:[ ]*/, ": ")' "$path") |
echo |
break |
done |
done \ |
| sed 's/^/ * /' |
exit 0 |
;; |
*) |
ERROR_MSG="Profiles starting with ':' are reserved.$NL" |
ERROR_MSG+=" Use ':list' to list available profiles." |
ERROR_CODE=$EX_USAGE |
return $ERROR_CODE |
esac |
fi |
for prof in "${PATHS[@]}" ; do |
prof="$prof$p.conf" |
[[ -f $prof ]] || continue |
load_config_file "$prof" 'Profile' |
return 0 |
done |
ERROR_MSG="Profile '$p' not found" |
ERROR_CODE=$EX_USAGE |
return $ERROR_CODE |
} |
# Check value for an overrideable variable against the allowed values |
# check_constraint($1 = variable name, $2 = value [, $3 = public_name]) |
# where public_name is the name to be used for error messages |
check_constraint() { |
trace $@ |
local n=$1 v=$2 p=$3 |
# Get constraint... |
local needle=$n |
# ... use the public name to search UNLESS it is a command-line option |
if [[ ( -n $p ) && ! ( $p =~ ^- ) ]]; then |
needle=$p |
fi |
local map=$(echo "${OVERRIDE_MAP[*]}" | stonl | egrep -i "^$needle:") |
[[ $map ]] || return 0 |
local ct=$(cut -d':' -f4 <<<"$map") |
[[ $ct ]] || return 0 |
local checkfn= domain= |
case $ct in |
n) checkfn=is_number ; domain=numbers ;; |
p) checkfn=is_positive ; domain='numbers greater than zero' ;; |
t) checkfn=is_interval ; domain=intervals ;; |
b) checkfn=is_bool ; domain='boolean values (0 or 1)' ;; |
h) checkfn=is_pos_or_percent ; domain='positive numbers or percentages' ;; |
f) checkfn=is_float_or_frac ; domain='positive numbers or fractions' ;; |
D) checkfn=is_decoder ; domain='$DEC_FFMPEG or $DEC_MPLAYER' ;; |
T) checkfn=is_tcfrom ; domain='$TC_INTERVAL or $TC_INTERVAL' ;; |
V) checkfn=is_vlevel ; domain='verbosity levels ($V_.*)' ;; |
I) checkfn=is_interv_or_percent ; domain='intervals or percentages' ;; |
P) checkfn=is_profile_list ; domain='comma-separated profile names' ;; |
x) |
case "$p" in |
capturer) |
checkfn=is_known_capturer |
domain='mplayer or ffmpeg' |
;; |
esac |
esac |
if [[ -n $checkfn ]] && ! $checkfn "$v" ; then |
[[ -n $p ]] || p=$n |
ERROR_MSG="Illegal value for '$p', only $domain are accepted" |
ERROR_CODE=$EX_USAGE |
return $ERROR_CODE |
fi |
return 0 |
} |
# Parse an override and set its value. |
# Input should be a var=value assignment. Also sets USR_<variable>. |
# The global variable $RESULT is set with the format: |
# <variable name> <flag> where |
# * variable name: is the name of the variable to be overridden |
# * flag: is a character indicating the status: "+" for a possible override, |
# "=" for an override that already has the same value |
# Warnings and errors are buffered |
# This function always returns true |
# parse_override($1 = override assignment) |
parse_override() { |
trace $@ |
local o="$1" |
RESULT='' |
# bash 3.1 and 3.2 handle quoted eres differently, using a variable fixes this |
local ERE="^[[:space:]]*[[:alpha:]_][[:alnum:]_]*[[:space:]]*=.*" |
if [[ ! $o =~ $ERE ]] ; then |
return |
fi |
local varname=$(echo "${o/=*}" | sed 's/[[:space:]]//g') # Trim var name |
local lcvarname=$(echo "$varname" | tr A-Z a-z) |
local mapping=$(echo "${OVERRIDE_MAP[*]}" | stonl | egrep -i "^$lcvarname:") |
[[ $mapping ]] || return 0 |
local varval=${o#*=} # No trimming here (yet) |
# 1) Trim from ; (if present) to finish |
# 2) Trim from # (comments) not "escaped" like '$#' |
# 3) Replace '$#' with '#' |
# 4) Trim whitespace on both ends |
varval=$(sed -e 's/;.*//' -e 's/\([^$]\)#.*/\1/g' -e 's/\$#/#/g' \ |
-e 's/^[[:space:]]*//;s/[[:space:]]*$//' <<<"$varval") |
# Is varval empty? |
[[ $varval ]] || return 0 |
local mvar=$(cut -d':' -f1 <<<"$mapping") |
local ivar=$(cut -d':' -f2 <<<"$mapping") |
local flags=$(cut -d':' -f3 <<<"$mapping") |
local constraints=$(cut -d':' -f4 <<<"$mapping") |
{ [[ $ivar && ( $ivar != '=' ) ]] ; } || ivar="$mvar" |
# Evaluate setting names, unlike actual variables they are |
#+case-insensitive and can mapped to different names so |
#+special handling is required |
local token= tokenmap= |
for token in $(echo "$varval" | grep -o '\$[[:alnum:]_]*' | sed 's/^\$//') ; do |
# Locate the mapping |
tokenmap=$(echo "${OVERRIDE_MAP[*]}" | stonl | egrep -i "^$token") || true |
if [[ -z $tokenmap ]]; then |
# No mapping, leave intact |
continue |
fi |
tokenmap=$(echo "$tokenmap" | cut -d':' -f2) |
if [[ -z $tokenmap ]]; then |
# No need to map, but change to uppercase for it to eval correctly |
tokenmap=$(tr a-z A-Z <<<"$token") |
fi |
# Replace all occurences of $token with its mapping |
varval=$(echo "$varval" | sed 's/\$'$token'/$'$tokenmap'/g') |
done |
# Note using "\$(echo $varval)" would allow a more flexible syntax but |
#+enforce special handling of escaping, which with the currently available |
#+settings is not worth the effort |
# Resolve symbolic variables to check their actual value |
eval varval="\"$varval\"" 2>/dev/null || { # Hide eval's errors |
buffered error "Syntax error: '$o'" |
return 0 |
} |
[[ $varval ]] || return 0 # If empty value, ignore it |
local evcode='' |
if [[ $flags && ( $flags != '=' ) && ( $flags != 'alias' ) ]]; then |
local ERE='^deprecated=' |
if [[ $flags =~ $ERE ]]; then |
local new=$(echo "$flags" | sed 's/^deprecated=//' | tr A-Z a-z) |
buffered warn "Setting '$varname' will be removed in the future,$NL please use '$new' instead." |
else |
case "$flags" in |
gone) |
buffered error "Setting '$varname' has been removed." |
return 0 |
;; |
striked) |
buffered error "Setting '$varname' is scheduled to be removed in the next release." |
buffered error " Please contact the author if you absolutely need it." |
;; |
meta) |
if [[ -n $constraints ]] ; then |
if ! check_constraint $ivar "$varval" $varname ; then |
buffered error "$ERROR_MSG" |
return 0 |
fi |
fi |
apply_meta_override "$varname" "$varval" |
RESULT="$varname +" |
return 0; |
;; |
*) return 0 ;; |
esac |
fi |
fi |
[[ -z $constraints ]] || check_constraint $ivar "$varval" $varname || { |
buffered error "$ERROR_MSG" |
return 0 |
} |
eval local curvarval='$'"$ivar" retflag='+' |
if [[ $constraints == 't' ]]; then |
varval=$(get_interval "$varval") |
fi |
# Escape single quotes, since it will be single-quoted: |
varval=${varval//\'/\'\\\'\'} # <<'>> => <<'\''>> |
evcode="USR_$ivar='$varval'" |
if [[ $curvarval == "$varval" ]]; then |
retflag='=' |
else |
evcode="$ivar='$varval'; $evcode" |
fi |
eval "$evcode" |
# varname, as found in the config file |
RESULT="$varname $retflag" |
} |
# Handle meta configuration variables, variables that, when set, modify the |
# value of (various) others |
# apply_meta_override($1 = actual variable name, $2 = value) |
apply_meta_override() { |
trace $@ |
case "$(tolower "$1")" in |
font_all) |
buffered inf "font_all => font_heading, font_sign, font_title, font_tstamps" |
parse_override "FONT_HEADING=$2" |
parse_override "FONT_SIGN=$2" |
parse_override "FONT_TITLE=$2" |
parse_override "FONT_TSTAMPS=$2" |
;; |
fg_all) |
buffered inf "fg_all => fg_heading, fg_sign, fg_title, fg_tstamps" |
parse_override "FG_HEADING=$2" |
parse_override "FG_SIGN=$2" |
parse_override "FG_TSTAMPS=$2" |
parse_override "FG_TITLE=$2" |
;; |
bg_all) |
buffered inf "bg_all => bg_heading, bg_contact, bg_sign, bg_title, bg_tstamps" |
parse_override "BG_HEADING=$2" |
parse_override "BG_CONTACT=$2" |
parse_override "BG_SIGN=$2" |
parse_override "BG_TITLE=$2" |
parse_override "BG_TSTAMPS=$2" |
;; |
profiles) # profiles=[,]prof1[,prof2,...], no spaces |
local profiles=${2//,/ } # === sed 's/,/ /g' |
local ERE='^[[:space:]]*$' |
if [[ $profiles =~ $ERE ]]; then |
return 0 |
fi |
local prof= |
for prof in ${2//,/ } ; do # ${2//,/ } = sed 's/,/ /g' |
load_profile $prof |
done |
;; |
decoder) |
buffered inf "decoder => capturer" |
if [[ $2 -eq $DEC_FFMPEG ]]; then |
parse_override 'CAPTURER=ffmpeg' |
elif [[ $2 -eq $DEC_MPLAYER ]]; then |
parse_override 'CAPTURER=mplayer' |
else |
assert false |
fi |
;; |
esac |
} |
# Do an override from the command line |
# cmdline_override($1 = override assignment) |
#+e.g. cmdline_override 'verbosity=$V_ALL' |
cmdline_override() { |
trace $@ |
parse_override "$1" |
local r=$RESULT |
[[ $r ]] || return 0 |
local varname=${r/% *} # See load_config() |
local tmp=${r#* } |
local flag=${tmp/% *} |
if [[ $flag == '=' ]]; then |
varname="$varname(=)" |
fi |
CMDLINE_OVERRIDES="$CMDLINE_OVERRIDES, $varname" |
} |
# Call any pending commands required by the command-line arguments |
# This is used to defer some calls and to flush buffers |
post_getopt_hooks() { |
local cback= EX=0 |
local funcs=$(echo "${POST_GETOPT_HOOKS[*]}" | stonl | sort -n | uniq |\ |
cut -d':' -f2- ) |
for cback in $funcs ; do |
local fn=${cback/:*} |
local arg=${cback/*:} |
[[ $arg != $cback ]] || arg='' |
$fn $arg |
done |
} |
# Print the list of command-line overrides |
cmdline_overrides_flush() { |
trace $@ |
if [[ $CMDLINE_OVERRIDES ]]; then |
inf "Overridden settings from command line:$NL ${CMDLINE_OVERRIDES:2}" |
fi |
if [[ $BUFFER ]]; then |
[[ $CMDLINE_OVERRIDES ]] || warn "In command-line overrides:" |
flush_buffered ' ' |
fi |
} |
# }}} # Configuration handling |
# {{{ # Convenience functions |
#### {{{{ # Type checkers: Return true if input is of a certain type |
#### All take exactly one argument and print nothing |
## Natural number |
is_number() { |
# With '[[...]]', strings '-eq'uals 0, test if it's actually 0 |
#+or otherwise a valid number. Must return 1 on error. |
[[ ( $1 == '0' ) || ( $1 -gt 0 ) ]] 2>/dev/null || return 1 |
} |
## Number > 0 |
is_positive() { is_number "$1" && [[ $1 -gt 0 ]]; } |
## Bool (0 or 1) |
is_bool() { [[ ($1 == '0') || ($1 == '1') ]] 2>/dev/null ; } |
## Float (XX.YY; XX.; ;.YY) (.24=0.24) |
## XXX: 1.12.3: '^([0-9]+\.?([0-9])?+|(\.[0-9]+))$' |
is_float() { local P='^([0-9]+\.?[0-9]*|\.[0-9]+)$' ; [[ $1 =~ $P ]] ; } |
## Percentage (xx% or xx.yy%) |
## XXX: 1.12.3: '^([0-9]+\.?([0-9])?+|(\.[0-9]+))%$' |
is_percentage() { |
local P='^([0-9]+\.?[0-9]*|\.[0-9]+)%$' |
[[ $1 =~ $P ]] |
} |
## Interval |
is_interval() { |
local i=$(get_interval "$1" || true) |
[[ $i ]] && fptest $i -gt 0 |
} |
## Interval or percentage |
is_interv_or_percent() { |
is_percentage "$1" || is_interval "$1" |
} |
## Positive or percentage |
is_pos_or_percent() { |
is_number "$1" && [[ $1 -gt 0 ]] || is_percentage "$1" |
} |
## Float (>=0) or fraction |
is_float_or_frac() { |
{ is_fraction "$1" || is_float "$1" ; } && fptest "$1" -ge 0 |
} |
## Fraction, strictly (X/Y, but no X; Y!=0) |
is_fraction() { |
local P='^[0-9]+/[0-9]+$' |
[[ $1 =~ $P ]] && { |
local d=${1#*/} # .../X |
[[ $d -ne 0 ]] |
} |
} |
## Decoder ($DEC_* constants) |
is_decoder() { [[ $1 == $DEC_FFMPEG || $1 == $DEC_MPLAYER ]]; } |
is_known_capturer() { |
[[ ( $1 == 'mplayer' ) || ( $1 == 'ffmpeg' ) ]] |
} |
## Time calculation source ($TC_* constants) |
is_tcfrom() { [[ $1 == $TC_INTERVAL || $1 == $TC_NUMCAPS ]]; } |
## Verbosity level ($V_* constants) |
is_vlevel() { |
is_number "$1" && \ |
[[ ($1 -eq $V_ALL) || ($1 -eq $V_NONE) || ($1 -eq $V_ERROR) || \ |
($1 -eq $V_WARN) || ($1 -eq $V_INFO) ]] |
} |
## List of profiles (comma-separated) |
is_profile_list() { |
ERE='^([[:alnum:]]*,?)*$' |
[[ ( -z "$*" ) || ( "$*" =~ $ERE ) ]] |
} |
#### }}}} # End of type checkers |
# Makes a string lowercase |
# tolower($1 = string) |
tolower() { tr '[:upper:]' '[:lower:]' <<<"$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) |
# max($1 = operand1, $2 = operand2) |
# abs($1 = number) |
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) }" ; } |
# 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 |
(( n += ( d - r ) , 1 )) |
fi |
echo $n |
} |
# Numeric test eqivalent for floating point |
# fptest($1 = op1, $2 = operator, $3 = op2) |
# special operator: '~' uses fsimeq() |
fptest() { |
local op= |
# Empty operands |
if [[ ( -z $1 ) || ( -z $3 ) ]]; then |
assert "[[ \"'$1'\" && \"'$3'\" ]] && false" |
fi |
case $2 in |
-gt) op='>' ;; |
-lt) op='<' ;; |
-ge) op='>=' ;; |
-le) op='<=' ;; |
-eq) op='==' ;; |
-ne) op='!=' ;; |
~) |
fsimeq "$1" "$3" |
return $? |
;; |
*) assert "[[ \"'$1' '$2' '$3'\" ]] && false" && return $EX_SOFTWARE |
esac |
awk "BEGIN { if ($1 $op $3) exit 0 ; else exit 1 }" |
} |
# floating point fuzzy equality, like fptest |
# fsimeq($1 = op1, $2 = op2) |
fsimeq() { |
awk "BEGIN { if (($1 - $2)^2 < 0.000000001) exit 0 ; else exit 1 }" |
} |
# Keep a number of decimals *rounded* |
# keepdecimals($1 = num, $2 = number of decimals) |
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() { |
local ERE='\.' |
[[ $1 =~ $ERE ]] || { 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... |
# awkexf($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 old code (default bc -l) |
# TODO: gawk and mawk differ in how to handle stuff like div by zero: |
# gawk errors, mawk prints inf. Should somehow handle inf and nan |
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() { |
if [[ $1 ]]; then |
awk '{gsub(" ", "\n");print}' <<<"$1" | egrep -v '^$' |
else |
awk '{gsub(" ", "\n");print}' | egrep -v '^$' |
fi |
} |
# Converts newlines to spaces portably |
# nltos([$1 = string]) |
nltos() { |
if [[ $1 ]]; then |
awk '{printf "%s ",$0}' <<<"$1" | sed 's/ *//' |
else |
awk '{printf "%s ",$0}' | sed 's/ *//' |
fi |
} |
# bash version of ord() [[ORD]] |
# prints the ASCII value of a character |
ord() { |
printf '%d' "'$1" |
} |
# Get file extension |
filext() { |
grep -q '\.' <<<"$1" || return 0 |
awk -F. '{print $NF}' <<<"$1" |
} |
# Checks if a 'command' is defined either as an available binary, a function |
#+or an alias |
# is_defined($1 = command) |
is_defined() { |
type "$@" >/dev/null 2>&1 |
} |
# Checks if a command is an available binary in the path. |
# is_executable($1 = command) |
is_executable() { |
type -pf "$@" >/dev/null 2>&1 |
} |
# Checks if a variable has been defined (even to empty values). |
# isset($1 = variable name) |
isset() { |
[[ -n ${!1+x} ]] |
} |
# 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() { |
awkexf "sqrt($1 ^ 2 + $2 ^ 2)" |
} |
# Get a percentage |
# percent($1 = value, $2 = percentage) |
percent() { |
local pc=${2/%%/} # BASH %% == RE %$ |
awkexf "($1 * $pc) / 100" |
} |
# Rounded percentage |
# rpercent($1 = value, $2 = percentage) |
rpercent() { |
local pc=${2/%%/} |
awkex "int( ($1 * $pc) / 100 + 0.5 )" |
} |
# 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 $@ |
# eval it even if it's numeric to strip leading zeroes. Note the quoting |
if is_number "$1" ; then awkexf "\"$1\"" ; return 0 ; fi |
local s=$(tolower "$1") t r n |
# Only allowed characters |
local ERE='^[0-9smhSMH.]+$' |
[[ $s =~ $ERE ]] || return $EX_USAGE |
# Two consecutive dots are no longer accepted |
# ([.] required for bash 3.1 + bash 3.2 compat) |
[[ ! $s =~ [.][.] ]] || return $EX_USAGE |
# 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= |
local NEWCODETEST= |
# Newer(-er) 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. |
# sed expressions: |
# 1: add spaces after h,m,s and before '.' |
# 2: add a space at the start (every number will now have a space in front) |
# 3: quote numbers preceded by a space |
# 4: replace h with a product by 3600 and an addition |
# 5: replace m with a product by 60 and an addition |
# 6: replace s with an addition |
# 7: add a '+' between consecutive quoted values |
# 8: remove last empty addition |
NEWCODETEST=$(echo "$s" | sed \ |
-e 's/\([hms]\)/\1 /g' -e 's/\./ ./g' \ |
-e 's/^/ /' \ |
-e 's/ \([0-9.][0-9.]*\)/ "\1"/g' \ |
-e 's/h/ * 3600 + /g' \ |
-e 's/m/ * 60 + /g' \ |
-e 's/s/ + /g' \ |
-e 's/"[[:space:]]*"/" + "/g' \ |
-e 's/+ *$//' \ |
) |
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 |
local ERE='\.' |
[[ $secs && ( $secs =~ $ERE ) ]] && 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=$(awkexf "$t" 2>/dev/null) |
local NEWR=$(awkexf "$NEWCODETEST" 2>/dev/null) || true |
echo "$r =? $NEWR" >&2 |
assert "[[ '$NEWR' == '$r' ]]" |
# Negative and empty intervals |
assert "[[ '$r' && '$t' ]]" |
assert "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() { |
# Must allow non-numbers |
local l; (( l = $1 - ${#2} , 1 )) |
[[ $l -le 0 ]] || printf "%0${l}d" '0' |
echo $2 |
} |
# Get Image Width |
# imw($1 = file) |
imw() { |
identify -format '%w' "$1" |
} |
# Get Image Height |
# imh($1 = file) |
imh() { |
identify -format '%h' "$1" |
} |
# Get the line height used for a certain font and size |
# line_height($1 = font, $2 = size) |
line_height() { |
# Create a small image to see how tall are characters. In my tests, no |
#+matter which character is used it's always the same height. |
convert -font "$1" -pointsize "$2" \ |
label:'F' png:- | identify -format '%h' - |
} |
# 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 "is_float '$1'" |
assert 'isset CAPTURER_HAS_MS' |
# Fully implemented in AWK to discard bc. |
# As a bonus now it's much faster and compact |
awk "BEGIN { |
t=$1 ; NOTMS=!$CAPTURER_HAS_MS; |
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" |
} |
# 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 $@ |
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 -e "s#%b#$b#g" -e "s#%N#$n#g" -e "s#%e#$ext#g" <<<"$SAFE_RENAME_PATTERN") |
(( n++ )); |
done |
assert "[[ -n '$to' ]]" |
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($1 = image file) |
get_dvd_image_mountpoint() { |
if is_linux ; then |
local lodev=$(/sbin/losetup -j "$1" | 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_BIN=$(type -pf mplayer) || true |
FFMPEG_BIN=$(type -pf ffmpeg) || true |
check_avail_tools |
# 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!" |
(( 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=$(convert -version | sed -n -e '1s/.*ImageMagick \([0-9][^ ]*\) .*$/\1/p;q') |
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." ; |
(( retval++ ,1 )) |
fi |
else |
error "Failed to check ImageMagick version." |
(( 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 |
[[ $SIMPLE_FEEDBACK -eq 0 ]] && echo -n "$PREFIX_ERR" |
# sgr0 is always used, this way if |
# a) something prints inbetween messages it isn't affected |
# b) if SIMPLE_FEEDBACK 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 |
[[ $SIMPLE_FEEDBACK -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 |
[[ $SIMPLE_FEEDBACK -eq 0 ]] && echo -n "$PREFIX_INF" |
echo "$1$SUFFIX_FBACK" |
fi >&2 |
} |
# |
# Print a debugging message |
# notice($1 = text) |
notice() { |
if [[ $VERBOSITY -gt $V_INFO ]]; then |
[[ $SIMPLE_FEEDBACK -eq 0 ]] && echo -n "$PREFIX_DBG" |
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 |
} |
# |
# Buffering of feedback, usage: |
# buffered warn "my warning" |
# ... |
# flush_buffered |
# buffered($1 = feedback function, $2 = arguments) |
buffered() { |
local grab=$( $1 "$2" 2>&1 ) |
BUFFER=$BUFFER$grab$NL |
} |
# |
# Print buffered feedback to stderr |
# flush_buffered([$1 = indentation]) |
flush_buffered() { |
[[ ${BUFFER[*]} ]] || return 0 |
echo "$BUFFER" | sed -e '$d' -e "s/^/$1/g" >&2 # sed: delete last line, indent with $1 |
BUFFER='' |
} |
# |
# trace(... = function arguments) |
trace() { |
[[ $DEBUG -eq 1 ]] || return 0 |
[[ $INTERNAL_NO_TRACE -ne 1 ]] || return 0 |
local func=$(caller 0 | cut -d' ' -f2) # caller: <LINE>< ><FUNCTION>< ><FILE> |
if [[ -n $INTERNAL_TRACE_FILTER ]]; then |
if ! grep -Pq "$INTERNAL_TRACE_FILTER" <<<"$func" ; then |
return 0 |
fi |
fi |
notice "[TRACE]: $func ${*}" |
} |
# |
# Print the call stack / execution frames |
# callstack([$1 = first frame]=0) |
callstack() { |
[[ $DEBUG -eq 1 ]] || return 0 |
local frame=$1 c= fn= |
[[ -n $frame ]] || frame=0 |
echo "Callstack:" |
while : ; do |
c=$(caller $frame) || break |
c=${c% *} |
fn=${c#* } |
# Only the last one, main, won't be a function |
if [[ $(type -t $fn) == 'function' ]]; then |
fn="${fn}()" |
fi |
echo " ${fn}:${c% *}" |
(( ++frame )) |
done |
} |
# Print an error message and exit |
# die([$1 = message [, $2 = exit_code]]) |
# If no message is provided, use $ERROR_MSG |
# If no exit_code is provided, use $ERROR_CODE |
die() { |
local m=$1 ec=$2 |
[[ $ec ]] || ec=$ERROR_CODE |
[[ $ec ]] || ec=1 |
[[ $m ]] || m=$ERROR_MSG |
error "$m" |
exit $ec |
} |
# |
# 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] ' |
PREFIX_DBG='' |
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 && tput sgr0 ; then |
PREFIX_ERR=$(tput bold; tput setaf 1) |
PREFIX_WARN=$(tput bold; tput setaf 3) |
PREFIX_INF=$(tput bold; tput setaf 2) |
PREFIX_DBG=$(tput bold; tput setaf 4) |
SUFFIX_FBACK=$(tput sgr0) |
HAS_COLORS="yes" |
fi >/dev/null |
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') |
PREFIX_DBG=$(echo $'\033[1m\033[34m') |
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 |
# Note: Use single quotes for globals, no need to expand in release |
# assert(... = code) |
assert() { |
[[ $RELEASE -eq 0 ]] || { |
function assert { :; } # Redefine to avoid check |
} |
local c=$(caller 0) # <num> <func> <file> |
c=${c% *} # <num> <func> |
local LIN=${c% *} FN=${c#* } |
eval "$@" || { |
error "Internal error at $FN():$LIN: $@" |
local cal=$(caller 1) |
[[ $level ]] && error " Stack trace:" |
local level=2 |
error "$(callstack 1 | sed 's/^/ /')" |
exit $EX_SOFTWARE |
} |
} |
# Conditional assertion |
# assert_if($1 = condition, $2 = assert if $1 true) |
assert_if() { |
[[ $RELEASE -eq 1 ]] && return |
if eval "$1" ; then |
assert "$2" |
fi |
} |
# }}} # Convenience functions |
# {{{ # Core functionality |
# {{{{ # Mplayer support |
# Check for mplayer |
mplayer_test_avail() { |
MPLAYER_BIN=$(type -pf mplayer 2>/dev/null) |
[[ $MPLAYER_BIN ]] && { |
if ! "$MPLAYER_BIN" -vo help 2>&1 | grep -q 'png' ; then |
warn "MPlayer can't output to png, won't be able to use it." |
unset MPLAYER_BIN |
return $EX_UNAVAILABLE |
fi |
} |
} |
# Try to identify video properties using mplayer |
# Fills $MPLAYER_CACHE with the relevant output and $MPLAYER_ID with |
# the actual values. See identify_video() |
# mplayer_identify($1 = file) |
mplayer_identify() { |
trace $FUNCNAME $@ |
assert '[[ $MPLAYER_BIN ]]' |
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_BIN" -benchmark -ao null -vo null -identify -frames 0 \ |
-quiet "$f" 2>"$STDERR" | grep ^ID) |
else |
MPLAYER_CACHE=$("$MPLAYER_BIN" -benchmark -ao null -vo null -identify -frames 0 \ |
-quiet -dvd-device "$f" dvd://$DVD_TITLE \ |
2>"$STDERR" | 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 |
# Warn if a known pitfall is found |
# See above for 1000 fps |
[[ ${mi[$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 |
[[ ${mi[$CHANS]} == '0' ]] && \ |
warn "Failed to detect number of audio channels." && \ |
warn " Install both mplayer and ffmpeg for better detection." |
# Array assignment |
MPLAYER_ID=("${mi[@]}") |
RESULT=("${mi[@]}") |
} |
# Capture a frame with mplayer |
# mplayer_capture($1 = inputfile, $2 = timestamp, $3 = output[, $4 = extra options]) |
mplayer_capture() { |
trace $@ |
# Note mplayer CAN'T set the output filename, newer mplayer can set output |
#+dir though. |
local f="$1" |
local ts=$2 |
local cap=00000005.png o=$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]) |
assert '[[ $DVD_MODE -ne 1 ]]' |
"$MPLAYER_BIN" -sws 9 -ao null -benchmark -vo "png:z=0" -quiet \ |
-frames 5 -ss "$ts" $4 "$f" >"$STDOUT" 2>"$STDERR" |
rm -f 0000000{1,2,3,4}.png # Remove the first four |
[[ ( -f $cap ) && ( '0' != "$(du "$cap" | cut -f1)" ) ]] && { |
[[ $cap == "$o" ]] || mvq "$cap" "$o" |
} |
} |
# Capture a frame with mplayer |
# mplayer_dvd_capture($1 = inputfile, $2 = timestamp, $3 = output) |
mplayer_dvd_capture() { |
trace $FUNCNAME $@ |
# Note mplayer CAN'T set the output filename, newer mplayer can set output |
#+dir though. |
local f="$1" |
local cap=00000005.png o=$3 |
local ts=$2 |
# No point in passing ms to mplayer |
ts=$(cut -d'.' -f1 <<<"$ts") |
assert '[[ $DVD_MODE -eq 1 ]]' |
"$MPLAYER_BIN" -sws 9 -ao null -benchmark -vo "png:z=0" -quiet \ |
-frames 5 -ss "$ts" -dvd-device "$f" \ |
$4 "dvd://$DVD_TITLE" >"$STDOUT" 2>"$STDERR" |
rm -f 0000000{1,2,3,4}.png # Remove the first four |
[[ ( -f $cap ) && ( '0' != "$(du "$cap" | cut -f1)" ) ]] && { |
[[ $cap == "$o" ]] || mvq "$cap" "$o" |
} |
} |
mplayer_probe() { |
local r= f=00000005.png |
if [[ $DVD_MODE -eq 1 ]]; then |
mplayer_dvd_capture "$1" "$2" "$f" "-vf scale=96:96" |
else |
mplayer_capture "$1" "$2" "$f" "-vf scale=96:96" |
fi |
r=$? |
rm -f "$f" # Must be manually removed since this runs before process() |
return $r |
} |
# }}}} # Mplayer support |
# {{{{ # FFmpeg support |
# Check for ffmpeg |
ffmpeg_test_avail() { |
FFMPEG_BIN=$(type -pf ffmpeg 2>/dev/null) |
# Test we can actually use FFmpeg |
[[ $FFMPEG_BIN ]] && { |
# 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_BIN" -formats 2>/dev/null | grep -q 'EV.* png' && \ |
! "$FFMPEG_BIN" -codecs 2>/dev/null | grep -q 'EV.* png' ; then |
warn "FFmpeg can't output to png, won't be able to use it." |
unset FFMPEG_BIN |
return $EX_UNAVAILABLE |
fi |
} |
} |
# Try to identify video properties using ffmpeg |
# Fills $FFMPEG_CACHE with the relevant output and $FFMPEG_ID with |
# the actual values. See identify_video() |
# mplayer_identify($1 = file) |
ffmpeg_identify() { |
trace $FUNCNAME $@ |
assert '[[ $FFMPEG_BIN ]]' |
local f="$1" |
# DVD Devices *MUST* be mounted for the identifying process to even start |
assert '[[ $DVD_MODE -eq 0 || $DVD_MOUNTP ]]' |
if [[ $DVD_MODE -eq 1 ]]; then |
local vfile="$DVD_MOUNTP/VIDEO_TS/VTS_${DVD_VTS}_0.VOB" |
if [[ ! -r $vfile ]]; then |
error "Failed to locate mounted DVD. Detection will be less accurate." |
return 0 # We can continue anyway |
fi |
f="$vfile" |
fi |
# XXX: FFmpeg detects mpeg1video in DVDs?? |
local fi=( ) vs= as= obs= vsid= |
# FFmpeg is relatively new, introduced in 1.0.99 so it needs more testing |
FFMPEG_CACHE=$("$FFMPEG_BIN" -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=$(sed -n -e '/Stream/!d' -e '/Video:/!d' -e '/Video:/p;q' <<<"$FFMPEG_CACHE") |
as=$(sed -n -e '/Stream/!d' -e '/Audio:/!d' -e '/Audio:/p;q' <<<"$FFMPEG_CACHE") |
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=$(sed -n -e 's/^.*#0\.\([0-9]\).*$/\1/p' <<<"$vs") # Video Stream ID |
fi[$VCODEC]=$(sed -n -e 's/^.*Video: \([^,]*\).*$/\1/p' <<<"$vs") |
# 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]=$(sed -n -e 's/^.*Audio: \([^,]*\).*$/\1/p' <<<"$as") |
fi[$VDEC]='' |
# The comma is required for cases where the stream id is printed (in hex) |
fi[$W]=$(sed -n -e 's/^.*, \([0-9]*\)x[0-9].*$/\1/p' <<<"$vs") |
fi[$H]=$(sed -n -e 's/^.*, [0-9]*x\([0-9]*\).*$/\1/p' <<<"$vs") |
# Newer CHANS and some older... |
fi[$CHANS]=$(sed -n -e 's/.*\([0-9][0-9]*\) channels.*/\1/p' <<<"$as") |
# ...fallback for older |
if [[ -z ${fi[$CHANS]} ]]; then |
local chans=$(sed -n -e 's/.*Hz, \([^, ][^, ]*\).*$/\1/p' <<<"$as") |
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]=$(sed -n -e '/Duration: /!d' \ |
-e 's/.*Duration: \([^,][^,]*\).*/\1/p;q' <<<"$FFMPEG_CACHE") |
if [[ ${fi[$LEN]} == 'N/A' ]]; then # It might be unable to detect |
fi[$LEN]="" |
fi |
fi[$LEN]=$( get_interval $(echo "${fi[$LEN]}" | sed -e 's/:/h/' -e 's/:/m/') ) |
# Aspect ratio in FFmpeg is only provided in newer ffmpeg |
# It might be calculated for files without one (which is ok anyway) |
# Must only match the last DAR (see the double DAR example above) |
fi[$ASPECT]=$(sed -n -e '/DAR [0-9]/!d' \ |
-e 's#.*DAR \([0-9]*\):\([0-9]*\).*#\1/\2#p;q' <<<"$FFMPEG_CACHE") |
# Due to calling ffmpeg on a single VOB when in DVD Device mode, the length will be partial |
[[ $DVD_MODE -eq 0 ]] || fi[$LEN]='' |
fi[$VCNAME]=$(get_vcodec_name $(translate_ffmpeg_vcodec_id "${fi[$VCODEC]}")) |
fi[$ACNAME]=$(get_acodec_name $(translate_ffmpeg_acodec_id "${fi[$ACODEC]}")) |
if [[ "${fi[$VCODEC]}" == 'h264' ]]; then |
fi[$VCNAME]="${fi[$VCNAME]} (h.264)" |
fi |
FFMPEG_ID=("${fi[@]}") |
RESULT=("${fi[@]}") |
} |
ffmpeg_probe() { |
local tfile=$(new_temp_file '-probe.png') |
ffmpeg_capture "$1" "$2" "$tfile" "-s 96x96" |
} |
# Capture a frame with ffmpeg |
# ffmpeg_capture($1 = inputfile, $2 = timestamp, $3 = output[, $4 = extra opts]) |
ffmpeg_capture() { |
trace $@ |
local f=$1 |
local ts=$2 |
local o=$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_BIN" -y ${wa_ss_be/ / $ts} -i "$f" ${wa_ss_af/ / $ts} -an \ |
-dframes 1 -vframes 1 -vcodec png \ |
-f rawvideo $4 "$o" >"$STDOUT" 2>"$STDERR" |
[[ ( -f $o ) && ( '0' != "$(du "$o" | cut -f1)" ) ]] |
} |
# }}}} # FFmpeg support |
# {{{{ # Classic identification (combined mplayer & ffmpeg) |
# Test availability |
classic_test_avail() { |
mplayer_test_avail && ffmpeg_test_avail |
} |
# }}}} # Classic identification |
# Sets the tool to use as a capturer |
# Possible tool names: ffmpeg, mplayer |
# set_capturer($1 = tool, [$2 = user picked]=1) |
set_capturer() { |
trace $@ |
local up=$2 |
[[ -n $up ]] || up=1 |
if [[ $up -eq 1 ]] && ! grep -q "$1" <<<"${CAPTURERS_AVAIL[*]}" ; then |
error "Tried to set '$1' as capturer, but not available" |
return 1 |
fi |
if [[ $1 = mplayer ]]; then |
DECODER=$DEC_MPLAYER |
CAPTURER=mplayer |
CAPTURER_HAS_MS=0 |
elif [[ $1 = ffmpeg ]]; then |
DECODER=$DEC_FFMPEG |
CAPTURER=ffmpeg |
CAPTURER_HAS_MS=1 |
else |
assert false |
fi |
if [[ $up -eq 1 ]]; then |
USR_DECODER=$DECODER |
USR_CAPTURER=$CAPTURER |
fi |
} |
# Creates a new temporary directory |
# create_temp_dir() |
create_temp_dir() { |
trace $@ |
[[ -z $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. |
# XXX: Has AWK or bash something similar? This is the only place requiring perl! |
# realpathr($1 = path) -> canonical path |
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 $@ |
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" |
} |
# Randomises 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 $@ |
local mode=f lineno |
if [[ $mode == 'f' ]]; 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 | sed -n -e '/Font: ./!d' -e 's/^.*Font: //' -e "${lineno}{p;q}" |
} |
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 "Randomisation 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 $@ |
local st=0 end=${VID[$LEN]} tcfrom=$1 tcint=$2 tcnumcaps=$3 eo=0 |
local eff_eo= # Effective end_offset (for percentages) |
# 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 is_percentage $END_OFFSET ; then |
eff_eo=$(percent $end $END_OFFSET) |
else |
eff_eo=$(get_interval "$END_OFFSET") |
fi |
if fptest $TOTIME -le 0 ; then # If no totime is set, use END_OFFSET |
eo=$eff_eo |
local runlen=$(awkexf "$end - $st") |
if fptest "($end-$eo-$st)" -le 0 ; then |
if fptest "$eo" -gt 0 && [[ -z $USR_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") |
local last= |
while fptest $stamp -le "$bound"; do |
assert fptest $stamp -ge 0 |
LTC=( "${LTC[@]}" "$stamp" ) |
last=$stamp |
stamp=$(keepdecimals_lower $(awkexf "$stamp + $inc") 3) |
done |
local lower_bound=$(awkexf "$st + $inc") |
inf "Capturing in range [$(pretty_stamp $lower_bound)-$(pretty_stamp $last)]. Total length: $(pretty_stamp ${VID[$LEN]})" |
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 $@ |
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 |
} |
# FIXME: Re-order captures when moved |
# Capture a frame |
# Sets $RESULT to the timestamp actually used |
# capture($1 = filename, $2 = output file, $3 = second, [$4 = disable blank frame evasion]) |
capture() { |
trace $@ |
local f=$1 out=$2 stamp=$3 prevent_evasion=$4 |
local alternatives= alt= delta= |
if [[ $prevent_evasion != '1' ]]; then |
for delta in $EVASION_ALTERNATIVES ; do |
alt=$(awkexf "$stamp + $delta") |
if fptest $alt -gt 0 && fptest $alt -lt "${VID[$LEN]}" ; then |
alternatives+=( $alt ) |
fi |
done |
fi |
capture_and_evade "$1" "$2" "$3" ${alternatives[*]} |
# Correct the timestamp in case it had to be adjusted |
local nstamp=$(echo "$CAPTURES" | tail -2 | head -1 | cut -d':' -f1) |
if fptest "int($stamp)" -ne "int($nstamp)" ; then |
inf " Capture point changed to $( pretty_stamp $nstamp )" |
stamp=$nstamp |
fi |
RESULT=$stamp |
} |
# Capture a frame, retry a few times if a blank frame is detected. Use capture() |
# Appends '$timestamp:$output\n' to $CAPTURES |
# capture_and_evade($1 = filename, $2 = output file, $3 = second, $4... = alternate seconds) |
capture_and_evade() { |
trace $@ |
local f=$1 stamp=$3 ofile=$2 |
shift 2 |
local tscand= |
while [[ -n $1 ]]; do |
tscand=$1 |
shift |
if ! capture_impl "$f" "$tscand" "$ofile" ; then |
error "Failed to capture frame at $(pretty_stamp $stamp) (${stamp}s)" |
return $EX_SOFTWARE |
fi |
# **XXX: EXPERIMENTAL: Blank frame evasion test |
local blank_val=$(convert "$ofile" -colorspace Gray -format '%[fx:image.mean*100]' info:) |
local BLANK_THRESHOLD=10 |
local upper=$(( 100 - $BLANK_THRESHOLD )) |
if fptest $blank_val -lt $BLANK_THRESHOLD || fptest $blank_val -gt $upper ; then |
local msg=" Blank (enough) frame detected." |
if [[ -n $1 ]]; then |
msg+=" Retrying at $(pretty_stamp $1)." |
else |
msg+=" Giving up." |
fi |
warn "$msg" |
else |
# No need to evade |
break |
fi |
# /XXX |
done |
CAPTURES="$CAPTURES$RESULT$NL" |
} |
# Capture a frame, intermediate-level implementation, use capture() instead. |
# Sets $RESULT to '$timestamp:$output' |
# capture_impl($1 = filename, $2 = second, $3 = output file) |
capture_impl() { |
trace $@ |
local f=$1 stamp=$2 ofile=$3 |
RESULT='' |
# Avoid recapturing if timestamp is already captured. |
# The extended set includes the standard set so when using the extended mode |
#+this will avoid some captures, specially with mplayer, since it doesn't |
#+have ms precission |
# FIXME: This often won't work with ffmpeg since there might be a slight |
# difference in ms. |
local key= |
# Normalise key values' decimals |
if [[ $CAPTURER_HAS_MS -eq 0 ]]; then |
key=$(awkex "int($stamp)") |
else |
key=$(awkex $stamp) |
fi |
local cached=$(grep "^$key:" <<<"$CAPTURES") |
if [[ $cached ]]; then |
notice "Skipped capture at $(pretty_stamp $key)" |
cp "${cached#*:}" "$ofile" # TODO: Is 'cp -s' safe? |
else |
local capfn=${CAPTURER}_capture |
if [[ $DVD_MODE -eq 1 ]]; then |
capfn=${CAPTURER}_dvd_capture |
fi |
$capfn "$f" "$stamp" "$ofile" || { |
return $EX_SOFTWARE |
} |
fi |
RESULT="$key:$ofile" |
} |
# Applies all individual vidcap filters |
# filter_vidcap($1 = filename, $2 = timestamp, $3 = width, $4 = height, $5 = context, $6 = index[1..]) |
filter_vidcap() { |
trace $@ |
# For performance purposes each filter adds a set of options |
# to 'convert'. That's less flexible but right enough now for the current |
# filters. |
local f=$1 t=$2 w=$3 h=$4 c=$5 i=$6 |
local cmdopts= |
for filter in ${FILTERS_IND[@]}; do |
$filter "$f" "$t" "$w" "$h" "$c" "$i" # Sets $RESULT |
cmdopts="$cmdopts $RESULT -flatten " |
done |
local t=$(new_temp_file .png) |
eval "convert -background transparent -fill transparent '$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 $@ |
local f="$1" t=$2 w=$3 h=$4 |
# Note the '!', required to change the aspect ratio |
RESULT=" \( -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 $@ |
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) |
RESULT=" \( -box '$BG_TSTAMPS' -fill '$FG_TSTAMPS' -stroke none -pointsize '$pts' " |
RESULT+=" -gravity '$GRAV_TIMESTAMP' -font '$FONT_TSTAMPS' -strokewidth 3 -annotate +5+5 " |
RESULT+=" ' $timestamp ' \) -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 $@ |
# 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 |
RESULT="-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 |
RESULT="\( -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 $@ |
local border=$(( ($3*$4) / 3600 )) # Read filt_photoframe for details |
[[ $border -lt 7 ]] || border=6 |
RESULT="\( -fill white -background white " |
RESULT+=" -bordercolor white -mattecolor white -frame ${border}x${border} " |
# XXX: Double-flipping, there's surely a better way |
RESULT+=" \( -flip -splice 0x$(( $border*5 )) \) " |
RESULT+=" -flip -bordercolor grey60 -border 1 +repage " |
RESULT+="\)" |
} |
# 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 $@ |
# Rotation angle [-18..18] |
local angle=$(( ($(rand) % 37) - 18 )) |
RESULT="-background none -rotate $angle " |
} |
# Create the sprocket-holes pattern |
# init_filt_film($1 = capture_width, $2 = capture_height) |
init_filt_film() { |
trace $@ |
[[ -z $FILMSTRIP ]] || return 0 |
local w=$1 h=$2 |
# Base reel dimensions |
#local rw=$(rmultiply $w,0.08) # 8% width |
local rw=51 |
local rh=29 |
local vspad=10 # Vertical padding between sprocket holes |
# Temporary files |
local reel_strip=$(new_temp_file -reel.png) |
local sprocket_mask=$(new_temp_file -smask.png) |
local sprocket=$(new_temp_file -sprocket.png) |
# Create the film reel pattern... |
local rw2=$(( $rw - 10 )) rh2=$(( $rh - 10 )) |
# Instead, create a big enough strip and then resize |
local must_rescale=0 |
if [[ ( $w -lt 240 ) || ( $h -lt 240 ) ]]; then |
must_rescale=1 |
fi |
# I (still) don't know how to do it in a single step, moving the mask to |
# a parenthesised expression won't work, probably due to -alpha interactions |
# First step: Create a mask: Black border, rounded-corners transparent rectangle |
# (Source: http://www.imagemagick.org/Usage/thumbnails/#rounded) |
local r=4 # 8 -> much more rounded, still mostly rectangular |
convert -size ${rw2}x${rh2} 'xc:black' \ |
\( +clone -alpha extract \ |
-draw "fill black polygon 0,0 0,$r $r,0 fill white circle $r,$r $r,0" \ |
\( +clone -flip \) -compose Multiply -composite \ |
\( +clone -flop \) -compose Multiply -composite \ |
\) -alpha off -compose CopyOpacity -composite \ |
"$sprocket_mask" |
# Second step: Create a bigger rectangle and cut-out the mask above |
convert -size ${rw}x$(( ${rh} + ${vspad} )) 'xc:white' -gravity Center \ |
"$sprocket_mask" -composite -alpha Copy -negate \ |
"$sprocket" |
if [[ $must_rescale -eq 1 ]]; then |
rws=$(( $(rmultiply $w,0.08) )) |
rhs=$(( ( $rws * 4 ) / 7 )) |
convert "$sprocket" -geometry ${rws}x${rhs} "$sprocket" |
rh=$rhs |
fi |
# FIXME: Error handling |
# Repeat it until the height is reached and crop to the exact height |
local repeat=$( ceilmultiply $h/$rh ) |
let 'repeat += 1' |
#$(yes -- '-clone 0 ( -size 1x5 xc:black ) ' | head -n $repeat) \ |
#-append -crop ${rw}x${h}+0+0 \ |
# Can't use "yes -- '-clone 0'" outside GNU |
convert -background black -fill black "$sprocket" \ |
$(yes 'clone 0' | head -$repeat | sed 's/^/-/') \ |
-append \ |
"$reel_strip" |
FILMSTRIP=$reel_strip |
FILMSTRIP_HOLE_HEIGHT=$(imh "$sprocket") |
} |
# This one requires much more work, the results are pretty rough, but ok as |
# a starting point / proof of concept |
filt_film() { |
trace $@ |
local file="$1" ts=$2 w=$3 h=$4 |
init_filt_film $w $h |
assert "[[ -n '$FILMSTRIP' ]]" |
local skew=$(( $RANDOM % $FILMSTRIP_HOLE_HEIGHT )) |
# 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. |
RESULT=" \( '$FILMSTRIP' -crop x${h}+0+$skew \) +append -flop " |
RESULT+="\( '$FILMSTRIP' -crop x${h}+0+$skew \) +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 $@ |
$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 $@ |
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=$PADDING |
vpad=$PADDING |
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 $@ |
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 $@ |
# Note sort works on lines, hence the stonl |
local s=$1 |
echo "$s" | stonl | 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 ret=0 |
# This time a resize filter is applied to the player to produce smaller |
# output |
if [[ $DECODER -eq $DEC_MPLAYER ]]; then |
if ! mplayer_probe "$f" "$ts"; then |
ret=1 |
fi |
elif [[ $DECODER -eq $DEC_FFMPEG ]]; then |
if ! ffmpeg_probe "$f" "$ts" ; then |
ret=1 |
fi |
else |
assert false |
ret=1 |
fi |
return $ret |
} |
# Try to guess a correct length for the video, taking the reported length as a |
# starting point |
# safe_length_measure($1 = filename) |
safe_length_measure() { |
trace $@ |
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 $(awkexf "$len - $rew") 3) |
warn " ... trying $(pretty_stamp $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? |
# H264 is used in mov/mp4. |
# 0x07 was seen in mplayer 1.0rc2-4.2.1 (FreeBSD) |
0x00000007|avc1|H264) 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" ;; # 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" ;; |
VP80) vcodec="VP8" ;; |
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)" ;; |
FPS1) vcodec="Fraps" ;; |
# 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" ;; |
# 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" ;; |
vp8) mpid="VP80" ;; |
# TODO List of codec id's I translate but haven't tested: |
#+ svq3, rv40, theora, camtasia, vp6* |
# MPlayer uses uppercase whereas 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" ;; |
fraps) mpid="FPS1" ;; |
# 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= |
local ERE='[ -]' |
if [[ $acid =~ $ERE ]]; 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" ;; |
# Other versions of R.A. listed at Wikipedia/RealAudio |
sipr) acodec="RealAudio SIPR" ;; # RA 4/5 |
cook) acodec="RealAudio Cook" ;; # RA 6 |
*) # 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 |
### {{{ Modularisation/abstraction of video capturers, TODO: work in progress |
check_avail_tools() { |
local capturer='' identifier='' fn= |
for capturer in ${CAPTURERS[*]}; do |
fn=${capturer}_test_avail |
is_defined $fn || continue |
if $fn ; then |
CAPTURERS_AVAIL=( "${CAPTURERS_AVAIL[@]}" "$capturer" ) |
fi |
done |
for identifier in ${IDENTIFIERS[*]}; do |
fn=${identifier}_test_avail |
is_defined $fn || continue |
if $fn ; then |
IDENTIFIERS_AVAIL=( "${IDENTIFIERS_AVAIL[@]}" $identifier ) |
fi |
done |
CAPTURER=${CAPTURERS_AVAIL[0]} |
IDENTIFIER=${IDENTIFIERS_AVAIL[0]} |
if [[ ( -z $CAPTURER ) || ( -z $IDENTIFIER ) ]]; then |
error "No supported video tools (mplayer, ffmpeg) available" |
return $EX_UNAVAILABLE |
fi |
} |
pick_tools() { |
trace $@ |
# User *wants* a certain decoder |
if [[ $USR_CAPTURER ]]; then |
if ! grep -qi "$CAPTURER" <<<"${CAPTURERS_AVAIL[@]}" ; then |
error "User selected capturing tool ($CAPTURER) is not available" |
return $EX_UNAVAILABLE |
fi |
fi |
# DVD mode is optional, and since 1.12 DVD mode can work with multiple inputs too |
# 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 [[ $DVD_MODE -eq 1 ]] && ! is_defined "${CAPTURER}_dvd_capture" ; then |
# Pick the first available dvd capturer, if any |
CAPTURER= |
local c= |
for c in "${CAPTURERS_AVAIL[@]}"; do |
if is_defined "${c}_dvd_capture" ; then |
CAPTURER="$c" |
break; |
fi |
done |
if [[ -z $CAPTURER ]]; then |
# None available with DVD support |
error "No available capturer has DVD support" |
return $EX_UNAVAILABLE |
fi |
if [[ $USR_CAPTURER != $CAPTURER ]]; then |
# User choose one, we can't use |
warn "$(tolower $USR_CAPTURER) can't capture in DVD mode, switching to $CAPTURER" |
fi |
fi |
# Propagate to the related settings |
local actual=$CAPTURER |
[[ -z $USR_CAPTURER ]] || set_capturer $USR_CAPTURER 1 # Preferred |
set_capturer $actual 0 # Actual |
} |
### }}} |
# Classic identification, uses mplayer and ffmpeg |
# 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 |
# classic_identify($1 = file) |
classic_identify() { |
trace $FUNCNAME $@ |
local RET_NOLEN=3 RET_NODIM=4 |
assert '[[ $MPLAYER_BIN && $FFMPEG_BIN ]]' |
assert 'is_defined mplayer_identify && is_defined ffmpeg_identify' |
mplayer_identify "$1" 2>/dev/null |
# ffmpeg_identify in DVD mode only works when the DVD is mounted: |
[[ ( $DVD_MODE -eq 0 ) && ( $FFMPEG_BIN ) ]] && ffmpeg_identify "$1" |
[[ ( $DVD_MODE -eq 1 ) && ( $FFMPEG_BIN ) && ( $DVD_MOUNTP ) ]] && ffmpeg_identify "$1" |
local fid=( "${FFMPEG_ID[@]}" ) |
# Fail early if none detected length |
[[ ( -z ${MPLAYER_ID[$LEN]} ) && ( -z ${FFMPEG_ID[$LEN]} ) ]] && return $RET_NOLEN |
# By default take mplayer's values |
VID=( "${MPLAYER_ID[@]}" ) |
# 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 ${MPLAYER_ID[$FPS]} ]] && VID[$FPS]=${fid[$FPS]} |
[[ ${MPLAYER_ID[$FPS]} && ${fid[$FPS]} ]] && { |
# Trust ffmpeg if it has three decimals OR if mplayer is probably-wrong |
local ffps=${fid[$FPS]} |
local ERE='\.[0-9][0-9][0-9]' |
if [[ $ffps =~ $ERE ]]; then |
VID[$FPS]=$ffps |
elif fptest "${MPLAYER_ID[$FPS]}" -gt 500; then |
VID[$FPS]=$ffps |
fi |
} |
# It doesn't appear to need any workarounds for num. channels either |
[[ ${fid[$CHANS]} ]] && VID[$CHANS]=${fid[$CHANS]} |
[[ ${fid[$ASPECT]} ]] && VID[$ASPECT]=${fid[$ASPECT]} |
# There's a huge inconsistency with some files, both mplayer vs ffmpeg |
# same application on different OSes |
local fflen=${fid[$LEN]} mplen=${MPLAYER_ID[$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 $(awkexf "($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 |
# 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 |
# Mplayer can identify video as 0x0 |
if [[ ${VID[$W]} -eq 0 ]]; then |
VID[$W]=${FFMPEG_ID[$W]} |
fi |
if [[ ${VID[$H]} -eq 0 ]]; then |
VID[$H]=${FFMPEG_ID[$H]} |
fi |
is_number "${VID[$W]}" && is_number "${VID[$H]}" || return $RET_NODIM |
[[ ${VID[$W]} -gt 0 ]] && [[ ${VID[$H]} -gt 0 ]] || return $RET_NODIM |
# FPS at least with two decimals |
if [[ $(awkex "int(${VID[$FPS]})") == "${VID[$FPS]}" ]]; then |
VID[$FPS]="${VID[$FPS]}.00" |
fi |
local mfps="${MPLAYER_ID[$FPS]}" |
if [[ ( $QUIRKS -eq 0 ) && ( -n $MPLAYER_BIN ) ]] && 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 |
RESULT=( "${VID[@]}" ) |
} |
# Use the selected identifier to extract 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() { |
${IDENTIFIER}_identify "$1" |
VID=( "${RESULT[@]}" ) |
} |
dump_idinfo() { |
trace $@ |
[[ $MPLAYER_BIN ]] && echo "Mplayer: $MPLAYER_BIN" |
[[ $FFMPEG_BIN ]] && echo "FFmpeg: $FFMPEG_BIN" |
[[ $MPLAYER_BIN ]] && cat <<-EODUMP |
=========== Mplayer Identification =========== |
Length: $(pretty_stamp ${MPLAYER_ID[$LEN]}) |
Video |
Codec: ${MPLAYER_ID[$VCODEC]} (${MPLAYER_ID[$VCNAME]}) |
Dimensions: ${MPLAYER_ID[$W]}x${MPLAYER_ID[$H]} |
FPS: ${MPLAYER_ID[$FPS]} |
Aspect: ${MPLAYER_ID[$ASPECT]} |
Audio |
Codec: ${MPLAYER_ID[$ACODEC]} (${MPLAYER_ID[$ACNAME]}) |
Channels: ${MPLAYER_ID[$CHANS]} |
============================================== |
EODUMP |
local ffl="${FFMPEG_ID[$LEN]}" |
[[ $ffl ]] && ffl=$(pretty_stamp "$ffl") |
if [[ ( -z $ffl ) && ( $DVD_MODE -eq 1 ) ]]; then |
ffl="(unavailable in DVD mode)" |
fi |
[[ $FFMPEG_BIN ]] && cat <<-EODUMP |
=========== FFmpeg Identification =========== |
Length: $ffl |
Video |
Codec: ${FFMPEG_ID[$VCODEC]} (${FFMPEG_ID[$VCNAME]}) |
Dimensions: ${FFMPEG_ID[$W]}x${FFMPEG_ID[$H]} |
FPS: ${FFMPEG_ID[$FPS]} |
Aspect: ${FFMPEG_ID[$ASPECT]} |
Audio |
Codec: ${FFMPEG_ID[$ACODEC]} (${FFMPEG_ID[$ACNAME]}) |
Channels: ${FFMPEG_ID[$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 $@ |
# 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:$NL$list" |
fi |
fi |
# Bias towards the Sazanami family |
shopt -s nocasematch |
local ERE='sazanami' |
if [[ $candidates =~ $ERE ]]; then |
NONLATIN_FONT=$(grep -i 'sazanami' <<<"$candidates" | head -1) |
else |
NONLATIN_FONT=$(head -1 <<<"$candidates") |
fi |
shopt -u nocasematch |
} |
# Checks if the provided arguments make sense and are allowed to be used |
#+together. When an incoherence is found, sets some sane values if reasonable |
#+or fails otherwise. |
coherence_check() { |
trace $@ |
# 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 |
# In case it's 0/0 or 0.0 since they aren't rejected |
if fptest "$EXTENDED_FACTOR" -eq 0 ; then |
EXTENDED_FACTOR=0 |
fi |
if [[ ( $DECODER -eq $DEC_MPLAYER ) && ( -z $MPLAYER_BIN ) ]]; then |
inf "Mplayer not available." |
set_capturer ffmpeg 0 |
elif [[ ( $DECODER -eq $DEC_FFMPEG ) && ( -z $FFMPEG_BIN ) ]]; then |
inf "FFmpeg not available." |
set_capturer mplayer 0 |
fi |
local filter= |
local -a filts=( ) |
if [[ $DISABLE_TIMESTAMPS -eq 0 ]] && |
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[@]}" ) |
# Interval=0 == default interval |
fptest "$INTERVAL" -eq 0 && interval=$DEFAULT_INTERVAL |
# If in non-latin mode and no nonlatin font has been picked try to pick one. |
# Should it fail, fallback to latin font. |
if [[ ( $NONLATIN_FILENAMES -eq 1 ) && ( -z $NONLATIN_FONT ) ]]; then |
set_extended_font || { |
# set_extended_font already warns about lack of fonts |
warn " Falling back to latin font" |
NONLATIN_FILENAMES=0 |
NONLATIN_FONT="$FONT_HEADING" |
} |
fi |
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() { |
trace $@ |
# Any default font in use? If all of them are overridden, return |
if [[ $USR_FONT_HEADING && $USR_FONT_TITLE && \ |
$USR_FONT_TSTAMPS && $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 |
local dvs='' |
if [[ -d /usr/local/share/fonts ]]; then |
dvs=$(find /usr/local/share/fonts/ -type f -iname 'dejavusans.ttf') |
fi |
if [[ ( -z $dvs ) && ( -d /usr/share/fonts ) ]]; then |
dvs=$(find /usr/share/fonts/ -type f -iname 'dejavusans.ttf') |
fi |
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 Sanitation: |
font_heading: $FONT_HEADING |
font_title : $FONT_TITLE |
font_tstamps: $FONT_TSTAMPS |
font_sign : $FONT_SIGN |
EOFF |
} |
# Main function. |
# Creates the contact sheet. |
# process($1 = file) |
process() { |
trace $@ |
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_format="$FORMAT" |
INTERNAL_MAXREWIND_REACHED=0 # Reset for each file |
CAPTURES='' |
FILMSTRIP='' # Reset |
DVD_MOUNTP= DVD_TITLE= # Re-set for each file |
if [[ $DVD_MODE -eq 1 ]]; then |
local dvdn=$(realpathr "$f") |
# Is it an ISO? |
if [[ -f $dvdn ]]; then |
DVD_MOUNTP=$(get_dvd_image_mountpoint "$dvdn") |
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 |
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_MOUNTP=$(mount | grep -o "^$dvdn *on [^ ]*" | cut -d' ' -f3) |
dvdn="DVD $f" |
fi |
if [[ ! -r $f ]]; then |
error "Can't access DVD ($f)" |
return $EX_NOINPUT |
fi |
inf "Processing $dvdn..." |
unset dvdn |
DVD_TITLE=${DVD_TITLES[0]} |
DVD_TITLES=( "${DVD_TITLES[@]:1}" ) # shift array |
if [[ ( -z $DVD_TITLE ) || ( $DVD_TITLE == '0' ) ]]; then |
local dt="$(lsdvd "$f" 2>/dev/null | grep 'Longest track:' | \ |
cut -d' ' -f3- | sed 's/^0*//')" |
if ! is_number "$dt" ; then |
error "Failed to autodetect longest DVD title for '$f'" |
exit $EX_INTERNAL |
fi |
DVD_TITLE=$dt |
unset dt |
fi |
DVD_VTS=$(lsdvd -t$DVD_TITLE -v "$f" 2>/dev/null | grep -o 'VTS: [0-9]*' | cut -d' ' -f2) |
inf "Using DVD Title #$DVD_TITLE (VTS: $DVD_VTS) for '$f'" |
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=$HEIGHT |
if is_percentage "$HEIGHT" && [[ $HEIGHT != '100%' ]]; then |
vidcap_height=$(rpercent ${VID[$H]} ${HEIGHT}) |
inf "Height: $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 |
if [[ $ASPECT_RATIO -eq -2 ]]; then |
[[ ${VID[$ASPECT]} ]] && ASPECT_RATIO=0 || ASPECT_RATIO=-1 |
elif [[ $ASPECT_RATIO -eq 0 ]]; then |
if [[ ${VID[$ASPECT]} ]]; then |
# Aspect ratio in file headers, honor it |
ASPECT_RATIO=$(awkexf "${VID[$ASPECT]}") |
else |
ASPECT_RATIO=$(awkexf "${VID[$W]} / ${VID[$H]}") |
fi |
elif [[ $ASPECT_RATIO -eq -1 ]]; 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 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') |
# If the temporal vidcaps for mplayer already exist, abort |
if [[ $DECODER -eq $DEC_MPLAYER ]]; then |
for f_ in 1 2 3 4 5; 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 |
# Assert sanity of decoder |
assert_if '[[ $DVD_MODE -ne 0 ]]' 'is_defined ${CAPTURER}_dvd_capture' |
assert 'is_defined ${CAPTURER}_capture' |
TEMPSTUFF=( "${TEMPSTUFF[@]}" '00000005.png' ) |
# 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 ${VID[$LEN]} ; then (( ++n )) && continue ; fi |
pretty=$(pretty_stamp $stamp) |
inf "Generating highlight #${n}/${#HLTIMECODES[@]} ($pretty)..." |
hlcapfile=$(new_temp_file "-hl-$(pad 6 $n).png") |
capture "$f" "$hlcapfile" $stamp '1' || return $? |
filter_vidcap "$hlcapfile" $pretty $vidcap_width $vidcap_height $CTX_HL $n || { |
local r=$? |
error "Failed to apply transformations to the capture." |
return $r |
} |
capfiles=( "${capfiles[@]}" "$hlcapfile" ) |
(( ++n )) |
done |
assert "[[ '"$n"' -gt 1 ]]" |
(( n-- )) # There's an extra inc |
if [[ $n -lt $COLUMNS ]]; then |
numcols=$n |
else |
numcols=$COLUMNS |
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 |
local capfile pretty n=1 |
unset capfiles ; local -a capfiles ; local tfile= |
for stamp in $(clean_timestamps "${TIMECODES[*]}"); do |
pretty=$(pretty_stamp $stamp) |
inf "Generating capture #${n}/${#TIMECODES[*]} ($pretty)..." |
# identified by capture number, padded to 6 characters |
tfile=$(new_temp_file "-cap-$(pad 6 $n).png") |
capture "$f" "$tfile" $stamp || return $? |
if [[ $RESULT != "$stamp" ]]; then |
stamp=$RESULT |
pretty=$(pretty_stamp $RESULT) |
fi |
filter_vidcap "$tfile" $pretty $vidcap_width $vidcap_height $CTX_STD $n || return $? |
capfiles=( "${capfiles[@]}" "$tfile" ) |
(( n++ )) |
done |
#filter_all_vidcaps "${capfiles[@]}" |
assert "[[ '"$n"' -gt 1 ]]" |
(( n-- )) # there's an extra inc |
if [[ $n -lt $COLUMNS ]]; then |
numcols=$n |
else |
numcols=$COLUMNS |
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 $(awkex "int(${#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 |
(( w=vidcap_width/2-PADDING, h=vidcap_height*w/vidcap_width ,1 )) |
assert "[[ ( '"$w"' -gt 0 ) && ( '"$h"' -gt 0 ) ]]" |
for stamp in $(clean_timestamps "${TIMECODES[*]}"); do |
pretty=$(pretty_stamp $stamp) |
capfile=$(new_temp_file "-excap-$(pad 6 $n).png") |
inf "Generating capture from extended set: ${n}/${#TIMECODES[*]} ($pretty)..." |
capture "$f" "$capfile" $stamp || return $? |
filter_vidcap "$capfile" $pretty $w $h $CTX_EXT $n || return $? |
capfiles=( "${capfiles[@]}" "$capfile" ) |
(( n++ )) |
done |
(( n-- )) # There's an extra inc |
if [[ $n -lt 'COLUMNS*2' ]]; then |
numcols=$n |
else |
numcols=$(( $COLUMNS * 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 [[ -n $HLTIMECODES || ( $EXTENDED_FACTOR != '0' ) ]]; then |
inf "Merging contact sheets..." |
if [[ -n $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 |
local csw2= ; (( csw2 = (width-csw) / 2 )) |
convert \( -size ${csw2}x$csh xc:transparent \) "$output" \ |
\( -size ${csw2}x$csh xc:transparent \) +append "$output" |
unset csh csw2 |
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 |
local hlw2= ; (( hlw2=(width - hlw) / 2 )) |
convert \( -size ${hlw2}x$hlh xc:transparent \) "$hlfile" \ |
\( -size ${hlw2}x$hlh xc:transparent \) +append "$hlfile" |
unset hlw2 |
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 |
local exw2= ; (( exw2=(width - exw) / 2 )) |
convert \( -size ${exw2}x$exh xc:transparent \) "$extoutput" \ |
\( -size ${exw2}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]}" |
meta2="$meta2${NL}Format: $vcodec / $acodec${NL}FPS: ${VID[$FPS]}" |
local signature |
if [[ $ANONYMOUS_MODE -eq 0 ]]; then |
signature="$SIGNATURE $USERNAME${NL}with $PROGRAM_SIGNATURE" |
else |
signature="Created with $PROGRAM_SIGNATURE" |
fi |
local headwidth=$(imw "$output") headheight= |
local heading=$(new_temp_file .png) |
# Add the title if any |
if [[ $TITLE ]]; then |
local tlheight=$(line_height "$FONT_TITLE" "$PTS_TITLE") |
convert \ |
\( \ |
-size ${headwidth}x$tlheight "xc:$BG_TITLE" \ |
-font "$FONT_TITLE" -pointsize "$PTS_TITLE" \ |
-background "$BG_TITLE" -fill "$FG_TITLE" \ |
-gravity Center -annotate 0 "$TITLE" \ |
\) \ |
-flatten \ |
"$output" -append "$output" |
unset tlheight |
fi |
local fn_font= # see $NONLATIN_FILENAMES |
if [[ $NONLATIN_FILENAMES -ne 1 ]]; then |
fn_font=$FONT_HEADING |
else |
fn_font=$NONLATIN_FONT |
fi |
# Create a small image to see how tall are characters. In my tests, no matter |
#+which character is used it's always the same height. |
local lineheight=$(line_height "$FONT_HEADING" "$PTS_META") |
# Since filename can be set in a different font check it too |
if [[ $fn_font != "$FONT_HEADING" ]]; then |
local fnlineheight=$(line_height "$fn_font" "$PTS_META") |
[[ $fnlineheight -le $lineheight ]] || lineheight=$fnlineheight |
unset fnlineheight |
fi |
headheight=$(( lineheight * 3 )) |
# 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 "$f" 2>/dev/null | grep -o 'Disc Title: .*' | cut -d' ' -f3-) |
# There's no guarantee that titles are on separate VTS, I have no idea |
# how to compute the actual title size |
if [[ $DVD_MOUNTP ]]; then |
filename_label="Disc label" |
filename_value="$dvd_label" |
filesize_label="Titleset 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 "$f") $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 |
local signlh=$(line_height "$FONT_SIGN" "$PTS_SIGN") |
local signheight=$(( 4 + ( signlh * 2 ) )) |
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}x$signheight -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 signheight signlh |
local wanted_name=${OUTPUT_FILES[$FILEIDX]} |
if [[ -n $wanted_name ]]; then |
local ERE='\.[^.]+$' |
if [[ $wanted_name =~ $ERE ]]; then |
FORMAT=$(filext "$wanted_name") |
inf "Output format set from output filename" |
else # No file extension in wanted_name |
wanted_name="$wanted_name.$FORMAT" |
fi |
fi |
[[ -n $wanted_name ]] || wanted_name="$(basename "$f").$FORMAT" |
if [[ $FORMAT != 'png' ]]; then |
local newout="$(dirname "$output")/$(basename "$output" .png).$FORMAT" |
convert -quality $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" |
(( FILEIDX++ ,1 )) #,1 so that it's always ok |
if [[ $UNDFLAG_DISPLAY -eq 1 ]]; then |
if type -pf $UNDFLAG_DISPLAY_COMMAND; then |
$UNDFLAG_DISPLAY_COMMAND "$output_name" |
else |
display "$output_name" |
fi |
fi >/dev/null 2>&1 |
[[ $UNDFLAG_DISCARD -eq 1 ]] && TEMPSTUFF+=( "$output_name" ) |
[[ $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 |
FORMAT="$pre_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" |
(( 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.35755975068581946630 #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 |
comm=${t/#*#/} # 's/.*#//' |
t=${t/%#*/} # 's/#.*//' |
# Expected value |
val=$(awk '{print $NF}' <<<$t) |
op=$(sed "s! $val *\$!!" <<<$t) # Don't use delimiter '/', passed in some $val |
[[ -n $comm ]] || comm=unnamed |
ret=$($op) || true |
if [[ $ret != "$val" ]] && fptest "$ret" -ne "$val" ; then |
error "Failed test ($comm): '$op $val'. Got result '$ret'." |
(( ++retval )) |
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" |
"is_pos_or_percent 33 0 #Positive recognition" |
"is_pos_or_percent 33% 0 #Percent recognition" |
"is_pos_or_percent 4/4% 1 #Percent recognition" |
"is_pos_or_percent % 1 #Percent recognition" |
) |
for t in "${TESTS[@]}"; do |
comm=${t/#*#/} # 's/.*#//' |
t=${t/%#*/} # 's/#.*//' |
val=$(awk '{print $NF}' <<<$t) |
op=$(sed "s! $val *\$!!" <<<$t) |
[[ -n $comm ]] || comm=unnamed |
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'" |
(( 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 |
# Don't colourise this |
infplain "Video Contact Sheet *NIX v${VERSION}, (c) 2007-2011 Toni Corvera" |
} |
# 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_BIN ]] && mpchosen=' [Not available]' |
[[ $MPLAYER_BIN && ( $DECODER == $DEC_MPLAYER ) ]] && mpchosen=' [Selected]' |
[[ -z $FFMPEG_BIN ]] && ffchosen=', Not available' |
[[ $FFMPEG_BIN && ( $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\\ |
file.avi |
Use \"identify -list font\" to list the available fonts |
-O|--override <arg> Override a variable (see the homepage for more details). |
The accepted format 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 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 |
* Prints all internal functions as they are called |
" |
# 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 |
Many of these modes are random in nature so using the |
same mode twice will usually lead to 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' |
Randomises 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|--jpeg2 Output in jpeg 2000 |
-V|--dvd DVD Mode. |
In this mode the input <file>s must be the DVD |
device(s) or ISO(s). When in DVD mode all input files |
must be DVDs. |
Implies -A (auto aspect ratio) |
--dvd-title <arg> DVD title to use. Using 0 (the default) will use the |
longest title. |
-M|--mplayer Use Mplayer to capture$mpchosen |
-F|--ffmpeg Use FFmpeg to capture [Default$ffchosen] |
-E|--end-offset <arg> This amount of time is ignored from the end of the |
video. |
Accepts timestamps (same format as -i) and percentages. |
This value is not used when a explicit ending time is |
set. |
The default is $DEFAULT_END_OFFSET. |
-q|--quiet Don't print progress 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 frame found at 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 frame at timestamp "arg" to the set of captures. |
Same format as -i. |
-u|--user <arg> Set the username (included by default in the sheet's |
footer) to this value. |
-U|--fullname Use user's full/real name (e.g. John Smith) as found |
set in the system's list of users. |
-p|--profile <arg> Load profile "arg" |
-C|--config <arg> Load configuration file "arg" |
--generate <config|profile> |
Generate configuration or profile from current settings |
-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 |
# ' # Syntax highlighting bait |
} |
# Print a configuration file generated from the currently active settings |
# generate_config($1 = <config|profile>) |
generate_config() { |
local n=$(echo $1 | tr a-z A-Z) f= t= x= |
cat <<-EOM |
# --- $n STARTS HERE --- |
# This is a sample configuration file for VCS generated automatically |
# from the command-line with the "--generate $1" command-line option |
# Save it to ~/.vcs.conf or ~/.vcs/vcs.conf to make it the default |
# configuration. |
# OR |
# Save it to ~/.vcs/profiles/something.conf to create a profile named |
# "something". To use this profile run vcs with the "--profile something" |
# (or "-p something") option |
# OR |
# Save it to "something.conf" and load it with "--config something.conf" |
# (or "-C something.conf") |
EOM |
echo "${OVERRIDE_MAP[*]}" | stonl | egrep -v '(deprecated=|alias)' | cut -d':' -f1-2 |\ |
while read ovname ; do |
f=${ovname/:*} |
t=${ovname#*:} |
if [[ ( -z $t ) || ( $t == '=' ) ]]; then t=$f ; fi |
eval v=\$USR_$t |
[[ -z $v ]] || { |
# Symbolic values: |
case $( tolower "$t" ) in |
timecode_from) |
x='$TC_NUMCAPS' |
[[ $v -eq $TC_NUMCAPS ]] || x='$TC_INTERVAL' |
v=$x |
;; |
decoder) |
x='$DEC_FFMPEG' |
[[ $v -eq $DEC_FFMPEG ]] || x='$DEC_MPLAYER' |
v=$x |
;; |
verbosity) |
case $v in |
$V_ALL) v='$V_ALL' ;; |
$V_NONE) v='$V_NONE' ;; |
$V_INFO) v='$V_INFO' ;; |
$V_WARN) v='$V_WARN' ;; |
$V_ERROR) v='$V_ERROR' ;; |
esac # verbosity |
;; |
esac |
[[ -z $v ]] || { |
# Don't print unnecessary decimals |
if [[ $v =~ ^[0-9][0-9]*\.[0-9][0-9]*$ ]]; then |
v=$(sed -e 's/0*$//' -e 's/\.$//' <<<"$v") |
fi |
} |
# Print all names in lowercase |
echo "$(tolower "$f")=$v" |
} |
done |
echo "# vcs:conf:$NL# Generated on $(date)$NL# --- $n ENDS HERE --- " |
exit 0 |
} |
# }}} # 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:VR:Z:o:p:C: \ |
--long "interval:,numcaps:,username:,title:,from:,to:,stamp:,jpeg::,help,"\ |
"mplayer,ffmpeg,height:,columns:,manual,aspect:,highlight:"\ |
"extended::,fullname,anonymous,quiet,autoaspect,override:,mincho,funky:,"\ |
"end_offset:,end-offset:,disable:,dvd,dvd-title:,randomsource:,undocumented:,output:,"\ |
"fullhelp,profile:,"\ |
"jpeg2,nonlatin,generate:,config:" \ |
-- "$@") |
eval set -- "$TEMP" |
while true ; do |
case $1 in |
-i|--interval) |
check_constraint 'interval' "$2" "$1" || die |
INTERVAL=$(get_interval $2) |
TIMECODE_FROM=$TC_INTERVAL |
USR_INTERVAL=$INTERVAL |
USR_TIMECODE_FROM=$TC_INTERVAL |
shift # Option arg |
;; |
-n|--numcaps) |
check_constraint 'numcaps' "$2" "$1" || die |
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) USERNAME=$2 ; USR_USERNAME=$USERNAME ; shift ;; |
-U|--fullname) |
# -U accepts an optional argument, 0, to make an anonymous signature |
# --fullname accepts no argument |
if [[ $1 == '-U' ]]; then # -U always provides an argument |
if [[ -n $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 |
USR_ANONYMOUS_MODE=1 |
fi |
shift |
else # No argument, default handling (try to guess real name) |
idname=$(id -un) |
if type -p getent >/dev/null ; then |
USERNAME=$(getent passwd "$idname" | cut -d':' -f5 | sed 's/,.*//g') |
else |
USERNAME=$(grep "^$idname:" /etc/passwd | cut -d':' -f5 | sed 's/,.*//g') |
fi |
if [[ -z $user ]]; then |
USERNAME=$idname |
error "No fullname found, falling back to default ($USERNAME)" |
fi |
unset idname |
fi |
;; |
--anonymous) ANONYMOUS_MODE=1 ; USR_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|--end-offset) |
if [[ $1 == '--end_offset' ]]; then |
warn "Option --end_offset is deprecated and will be removed in the" |
warn " next version, please use --end-offset instead" |
fi |
check_constraint 'end_offset' "$2" "$1" || die |
is_p='y' |
is_percentage "$2" || is_p='' |
if [[ $is_p ]]; then |
END_OFFSET="$2" |
else |
END_OFFSET=$(get_interval "$2") |
fi |
USR_END_OFFSET="$END_OFFSET" |
unset is_i |
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 |
FORMAT=jp2 |
USR_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 |
FORMAT=jp2 |
else |
FORMAT=jpg |
fi |
USR_FORMAT="$FORMAT" |
shift |
;; |
-h|--help) show_help ; exit $EX_OK ;; |
--fullhelp) show_help 'full' ; exit $EX_OK ;; |
-F|--ffmpeg) set_capturer ffmpeg ;; |
-M|--mplayer) set_capturer mplayer ;; |
-H|--height) |
check_constraint 'height' "$2" "$1" || die |
HEIGHT="$2" |
USR_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) |
check_constraint 'columns' "$2" "$1" || die |
COLUMNS="$2" |
USR_COLUMNS="$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 |
if [[ $2 ]]; then |
check_constraint 'extended_factor' "$2" "$1" || die |
EXTENDED_FACTOR="$2" |
else |
EXTENDED_FACTOR=$DEFAULT_EXT_FACTOR |
fi |
USR_EXTENDED_FACTOR=$EXTENDED_FACTOR |
shift |
;; |
# Unlike -I, --nonlatin does not accept a font name |
--nonlatin) |
if [[ -z $USR_NONLATIN_FONT ]]; then |
NONLATIN_FILENAMES=1 |
USR_NONLATIN_FILENAMES=1 |
set_extended_font |
inf "Filename font set to '$NONLATIN_FONT'" |
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 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 |
# It isn't tested for existence because it could also be a font |
# which convert would understand without giving the full path |
NONLATIN_FILENAMES=1 |
USR_NONLATIN_FILENAMES=1 |
if [[ ${#2} -gt 1 ]]; then |
# j=, k= syntax |
NONLATIN_FONT="${2:2}" |
USR_NONLATIN_FONT="$NONLATIN_FONT" |
inf "Filename font set to '$NONLATIN_FONT'" |
fi |
# If the user didn't pick one, try to select automatically |
if [[ -z $USR_NONLATIN_FONT ]]; then |
set_extended_font |
inf "Filename font set to '$NONLATIN_FONT'" |
fi |
shift |
;; |
-O|--override) |
# Rough test |
RE='[a-zA-Z_]+=[^;]*' |
if [[ ! $2 =~ $RE ]]; then |
error "Wrong override format, it should be variable=value. Got '$2'." |
exit $EX_USAGE |
fi |
two=$(tolower "$2") |
RE='^[[:space:]]*getopt=' |
if [[ $two =~ $RE ]] ; then # getopt= |
# 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 "Setting 'getopt' can't be overridden from the command line." |
else |
cmdline_override "$2" |
POST_GETOPT_HOOKS+=( 1:cmdline_overrides_flush ) |
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 |
[[ -n $UNDFLAG_NOPREFIX ]] && SIMPLE_FEEDBACK=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=$(awkexf "$QUIRKS_MAX_REWIND * (2^$n)") |
(( 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=$(awkexf "$QUIRKS_LEN_STEP / (2^$n)") |
(( 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=$(awkexf "$QUIRKS_LEN_STEP * (2^$n)") |
(( 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 "Polaroid mode enabled." |
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 "Photos mode enabled." |
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 |
inf "Overlap mode enabled." |
CSHEET_DELEGATE='csheet_overlap' |
GRAV_TIMESTAMP=NorthWest |
;; |
r|rotate) # Random rotation |
inf "Random rotation of captures enabled." |
FILTERS_IND=( "${FILTERS_IND[@]}" 'filt_randrot' ) |
;; |
f|photoframe) # White photo frame |
inf "Photoframe mode enabled." |
FILTERS_IND=( "${FILTERS_IND[@]}" 'filt_photoframe' ) |
;; |
L|polaroidframe) # White polaroid frame |
inf "Polaroid frame mode enabled." |
FILTERS_IND=( "${FILTERS_IND[@]}" 'filt_polaroid ') |
GRAV_TIMESTAMP=South |
FG_TSTAMPS=Black |
BG_TSTAMPS=Transparent |
PTS_TSTAMPS=$(( $PTS_TSTAMPS * 3 / 2 )) |
;; |
i|film) |
inf "Film mode enabled." |
FILTERS_IND=( "${FILTERS_IND[@]}" 'filt_film' ) |
;; |
x|random) # Random colours/fonts |
inf "Fonts and colours randomisation enabled." |
randomize_look |
;; |
*) |
error "Unknown funky mode requested. 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 |
;; |
*) load_profile "$2" || die |
;; |
esac |
shift |
;; |
-C|--config) |
if [[ $2 =~ ^: ]]; then |
if [[ $2 == ':pwd' ]]; then |
cfg=./vcs.conf |
else |
error "Configuration names starting with ':' are reserved." |
exit $EX_USAGE |
fi |
else |
cfg=$2 |
fi |
[[ -f $cfg ]] || { |
error "Configuration file '$cfg' not found" |
exit $EX_USAGE |
} |
# ./vcs.conf doesn't need the vcs:conf: mark |
if [[ $2 != ':pwd' ]]; then |
head -5 "$cfg" | grep -q '#[[:space:]]*vcs:conf[[:space:]]*:' || \ |
tail -5 "$cfg" | grep -q '#[[:space:]]*vcs:conf[[:space:]]*:' || { |
error "No vcs:conf: mark found in '$cfg'" |
exit $EX_NOINPUT |
} |
fi |
load_config_file "$cfg" 'Custom configuration' |
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 [[ $PADDING -ne 0 ]] ; then |
inf "Padding disabled." # Kinda... |
PADDING=0 |
fi |
;; |
*) |
error "Requested disabling unknown feature. Got '$2'." |
exit $EX_USAGE |
;; |
esac |
shift |
;; |
--dvd-title) |
check_constraint 'dvd_title' "$2" "$1" || die |
DVD_TITLES=( "${DVD_TITLES[@]}" "$2" ) |
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 |
ASPECT_RATIO=-2 # Special value: Auto detect only if ffmpeg couldn't |
;; |
-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 |
USR_VERBOSITY=$VERBOSITY |
;; |
-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_BIN=$(realpathr "$(cut -d'=' -f2<<<"$2")") |
assert '[[ -x $FFMPEG_BIN ]]' |
warn "[U] FFMPEG_BIN=$FFMPEG_BIN" |
;; |
# mplayer path |
set_mplayer=*) |
MPLAYER_BIN=$(realpathr "$(cut -d'=' -f2<<<"$2")") |
assert '[[ -x $MPLAYER_BIN ]]' |
warn "[U] MPLAYER_BIN=$MPLAYER_BIN" |
;; |
# Ignore one of the players |
disable_ffmpeg) |
FFMPEG_BIN='' |
CAPTURERS_AVAIL=( $(sed 's/ffmpeg//'<<<"${CAPTURERS_AVAIL[*]}") ) |
warn "FFmpeg disabled" |
assert '[[ $MPLAYER_BIN ]]' |
set_capturer mplayer |
;; |
disable_mplayer) |
MPLAYER_BIN='' |
CAPTURERS_AVAIL=( $(sed 's/mplayer//'<<<"${CAPTURERS_AVAIL[*]}") ) |
warn "Mplayer disabled" |
assert '[[ $FFMPEG_BIN ]]' |
set_capturer ffmpeg |
;; |
debug) |
warn "[U] debug" |
DEBUG=1 |
;; |
trace=*) # (Implies 'debug'), traces a particular function name |
INTERNAL_TRACE_FILTER=$(cut -d'=' -f2 <<<"$2") |
DEBUG=1 |
warn "[U] debug, tracing '$INTERNAL_TRACE_FILTER'" |
;; |
# Dump user-set variables and exit [since 1.12] |
uservars) |
echo "${OVERRIDE_MAP[*]}" | stonl | egrep -v '(deprecated=|alias)' | cut -d':' -f1-2 |\ |
while read ovname ; do |
f=${ovname/:*} |
t=${ovname#*:} |
if [[ ( $t ) && ( $t != '=' ) ]]; then f="$t" ; fi |
eval v=\$USR_$f |
[[ -z $v ]] || echo "$(tolower $f)=$v" |
done |
exit 0 |
;; |
functest) # Test a function: -Z functest <funcname> <arg> [arg] [...] |
shift 3 # We're quitting anyway |
funcname=$1 |
shift |
if [[ $(type -t "$funcname") != 'function' ]]; then |
error "functest can only test actual functions" |
exit $EX_USAGE |
fi |
inf "Testing $funcname($*)" |
$funcname "$@" |
exit 0 |
;; |
display) UNDFLAG_DISPLAY=1 ;; |
discard) UNDFLAG_DISCARD=1 ;; |
*) |
error "Unknown \`--undocumented $2' option" |
;; |
esac |
shift |
;; |
--generate) |
case "$2" in |
profile|config) |
POST_GETOPT_HOOKS=( "${POST_GETOPT_HOOKS[@]}" \ |
10:generate_config:$2 ) |
;; |
*) |
error "Option --generate must be followed by profile or config" |
exit $EX_USAGE |
;; |
esac |
shift |
;; |
-D) # Repeat to just test consistency |
if [[ $DEBUGGED -gt 0 ]]; then |
pick_tools # Simulate a normal run |
infplain '[ svn $Rev$ ]' |
# Even when empty, POSIXLY_CORRECT has an effect, check if it's |
# set ([[BIS]]) |
if [[ -n ${POSIXLY_CORRECT+x} ]]; then |
pc="'${POSIXLY_CORRECT}'" |
else |
pc='{not set}' |
fi |
# AWK and sed version can't be checked in all variants |
awkv=$(awk --version 2>/dev/null | head -1) || true |
if [[ -n $awkv ]]; then |
awkv="${NL}AWK: $awkv" |
fi |
sedv=$(sed --version 2>/dev/null | head -1) || true |
if [[ -n $sedv ]]; then |
sedv="${NL}sed: $sedv" |
fi |
usrcap= |
if [[ -n $USR_CAPTURER ]]; then |
usrcap=$USR_CAPTURER |
else |
usrcap='{default}' |
fi |
cat >&2 <<-EOD |
=== Setup === |
GETOPT: $GETOPT |
MPLAYER: $MPLAYER_BIN |
FFMPEG: $FFMPEG_BIN |
AWK: $(realpathr $(type -pf awk)) |
sed: $(realpathr $(type -pf sed)) |
POSIXLY_CORRECT: $pc |
Capturers (av.): [ ${CAPTURERS_AVAIL[*]} ] |
Identif. (av.): [ ${IDENTIFIERS_AVAIL[*]} ] |
Capturer: $CAPTURER |
Chosen capturer: $usrcap |
Filterchain: [ ${FILTERS_IND[*]} ] |
Safe step: $QUIRKS_LEN_STEP |
=== Versions === |
Bash: $BASH_VERSION |
Getopt: $($GETOPT --version)$awkv$sedv |
EOD |
exit |
fi |
DEBUG=1 |
VERBOSITY=$V_ALL |
inf "Testing internal consistency..." |
tmp=$INTERNAL_NO_TRACE |
INTERNAL_NO_TRACE=1 # Avoid any tracing during the test |
internal_integrity_test && warn "All tests passed" || error "Some tests failed!" |
INTERNAL_NO_TRACE=$tmp |
unset tmp |
DEBUGGED=1 |
warn "Command line: $0 $ARGS" |
TITLE="$(basename "$0") $ARGS" |
;; |
--) shift ; break ;; |
*) error "Internal error! (remaining opts: $*)" ; exit $EX_SOFTWARE ; |
esac |
shift |
done |
# Avoid coherence_check if there's no arguments and no cmdline post |
# processing |
[[ -n $1 || -n $POST_GETOPT_HOOKS ]] || { |
[[ $VERBOSITY -eq $V_NONE ]] || show_help |
exit $EX_USAGE |
} |
# More than one argument... |
if [[ -n $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 $? |
} |
# Run after coherence check to clean recoverable incorrect values |
post_getopt_hooks |
pick_tools |
# Remaining arguments |
if [[ -z $1 ]]; then |
[[ $VERBOSITY -eq $V_NONE ]] || show_help |
exit $EX_USAGE |
fi |
# TODO: |
# DVD mode + multiple titles is still tricky: |
# --dvd --dvd-title 1 --dvd-title 2 /dev/dvd /dev/dvd |
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. |
# |
# * see http://www.gnu.org/s/bash/manual/html_node/Bash-Variables.html for builtin vars |
# * 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 |
# * bash3: [[ STR =~ EREGEX ]] is faster than grep/egrep (no forking) |
# bash 3.2 changed semantics vs bash 3.1 |
# quoting the ERE poses a problem (newer bash will interpret as plain string, older |
# as ERE), storing the ERE in a variable or writing it unquoted solves this problem |
# * bash4: |& (inherited from csh?) pipes both stdout and stderr |
# * [[ A == $B ]] : $B should be quoted usually, otherwise it will be scanned as a regex |
# * 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.13/dist/docs/src/settings.man.inc.xml |
---|
0,0 → 1,591 |
<!DOCTYPE variablelist PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" |
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ |
<!ENTITY emdash "—"> |
<!ENTITY equiv "≡"> |
<!ENTITY rArr "⇒"> |
<!ENTITY vcsmanpage "<citerefentry><refentrytitle>vcs</refentrytitle><manvolnum>1</manvolnum></citerefentry>"> |
]> |
<!-- $Date: 2011-09-08 04:58:56 +0200 (dj, 08 set 2011) $ --> |
<variablelist id="settings" lang="en-GB"> |
<varlistentry> |
<term id="term-all">All settings</term> |
<listitem> |
<para> |
<!-- |
$ grep '<term' src/settings.man.inc.xml |\ |
sed -r -e '/<term id="term-all/d' \ |
-e 's/^[[:space:]]*//' \ |
-e 's!<term id="(.*)"><literal>.*$!<xref linkend="\1" />,!' \ |
-e 's/^/ /' \ |
-e '/(shoehorned|safe_rename_pattern)/d' |
--> |
<xref linkend="term-anonymous" />, |
<xref linkend="term-bg_all" />, |
<xref linkend="term-bg_heading" />, |
<xref linkend="term-bg_contact" />, |
<xref linkend="term-bg_sign" />, |
<xref linkend="term-bg_title" />, |
<xref linkend="term-bg_tstamps" />, |
<xref linkend="term-capturer" />, |
<xref linkend="term-columns" />, |
<xref linkend="term-debug" />, |
<xref linkend="term-decoder" />, |
<xref linkend="term-disable_shadows" />, |
<xref linkend="term-disable_shadows" />, |
<xref linkend="term-disable_timestamps" />, |
<xref linkend="term-end_offset" />, |
<xref linkend="term-extended_factor" />, |
<xref linkend="term-fg_all" />, |
<xref linkend="term-fg_heading" />, |
<xref linkend="term-fg_sign" />, |
<xref linkend="term-fg_title" />, |
<xref linkend="term-fg_tstamps" />, |
<xref linkend="term-font_all" />, |
<xref linkend="term-font_heading" />, |
<xref linkend="term-font_sign" />, |
<xref linkend="term-font_title" />, |
<xref linkend="term-font_tstamps" />, |
<xref linkend="term-format" />, |
<xref linkend="term-getopt" />, |
<xref linkend="term-height" />, |
<xref linkend="term-interval" />, |
<xref linkend="term-nonlatin_filenames" />, |
<xref linkend="term-nonlatin_font" />, |
<xref linkend="term-numcaps" />, |
<xref linkend="term-padding" />, |
<xref linkend="term-plain_messages" />, |
<xref linkend="term-profiles" />, |
<xref linkend="term-pts_meta" />, |
<xref linkend="term-pts_sign" />, |
<xref linkend="term-pts_title" />, |
<xref linkend="term-pts_tstamps" />, |
<xref linkend="term-quality" />, |
<xref linkend="term-signature" />, |
<xref linkend="term-stderr" />, |
<xref linkend="term-stdout" />, |
<xref linkend="term-timecode_from" />, |
<xref linkend="term-user" />, |
<xref linkend="term-verbosity" /> |
</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-anonymous"><literal>anonymous</literal></term><!-- since 1.13 --> |
<listitem> |
<para>Enables or disables the anonymous mode.</para> |
<para>Set to <literal>1</literal> to enable this mode, in which the contact sheet |
footer won't include the |
«Preview created by <link linkend="term-user"><replaceable>$user</replaceable></link>» |
line.</para> |
<para>Default: <literal>0</literal> (≡ disabled).</para> |
<para>Equivalent command-line option: <option>--anonymous</option>.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-bg_all"><literal>bg_all</literal></term> |
<listitem> |
<para>Sets the value of all <literal>bg_</literal> variables at once |
(<xref linkend="term-bg_contact" />, |
<xref linkend="term-bg_heading" />, |
<xref linkend="term-bg_sign" />, |
<xref linkend="term-bg_tstamps" /> and |
<xref linkend="term-bg_title" />).</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-bg_heading"><literal>bg_heading</literal></term> |
<term id="term-bg_contact"><literal>bg_contact</literal></term> |
<term id="term-bg_sign"><literal>bg_sign</literal></term> |
<term id="term-bg_title"><literal>bg_title</literal></term> |
<term id="term-bg_tstamps"><literal>bg_tstamps</literal></term> |
<listitem> |
<para>These variables control the background colours of each section in the contact sheet.</para> |
<note> |
<para>Valid colour values are those understood by <application>ImageMagick</application>, |
e.g. <ulink url="http://www.imagemagick.org/script/color.php#color_names">colour |
names</ulink> or <acronym>HTML</acronym>/<acronym>CSS</acronym>-style colour |
specifications |
(<replaceable class="parameter">#RRGGBB<optional>AA</optional></replaceable>, |
<replaceable class="parameter">#RGB<optional>A</optional></replaceable>).</para> |
<para>See <ulink url="http://www.imagemagick.org/script/color.php" /> |
for more details and additional formats.</para> |
</note> |
<tip> |
<para>The command <literal>$ <userinput>convert -list color</userinput></literal> |
prints a list of all known colour names.</para> |
</tip> |
<para><literal>bg_heading</literal> &emdash; File meta information (size, codec, etc.). |
Default: <literal>#afcd7a</literal> |
[≡ <literal>RGB(175,205,122)</literal>]</para> |
<para><literal>bg_title</literal> &emdash; Title (with option <option>-T</option>). |
Default: <constant>White</constant> |
[≡ <literal>RGB(255,255,255)</literal>]</para> |
<para><literal>bg_contact</literal> &emdash; Captures. |
Default: <constant>White</constant> |
[≡ <literal>RGB(255,255,255)</literal>]</para> |
<para><literal>bg_tstamps</literal> &emdash; Timestamps boxes. |
Default: <literal>#000000aa</literal> |
[≡ <literal>RGBA(0,0,0,0.67)</literal>]</para> |
<para><literal>bg_sign</literal> &emdash; Footer. |
Default: <constant>SlateGray</constant> |
[≡ <literal>RGB(112,128,144)</literal>]</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-capturer"><literal>capturer</literal></term><!-- since 1.13 --> |
<listitem> |
<para>Controls which capturer to use.</para> |
<para>Symbolic values: <literal><symbol>ffmpeg</symbol></literal> ⇒ FFmpeg, |
<literal><symbol>mplayer</symbol></literal> ⇒ MPlayer</para> |
<para>Default: <literal><symbol>ffmpeg</symbol></literal></para> |
<para>Related command-line options: |
<option>-F</option>, <option>--ffmpeg</option> and |
<option>-M</option>, <option>--mplayer</option> |
</para> |
<warning> |
<para>DVD mode sets the capturer to MPlayer disregarding the value of |
this setting.</para> |
</warning> |
<para role="aside">Since version 1.13</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-columns"><literal>columns</literal></term> |
<listitem> |
<para>Number of columns</para> |
<para>Default: <literal>2</literal></para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-debug"><literal>debug</literal></term> |
<listitem> |
<para>Enable or disable debug mode. Set to <userinput>1</userinput> to enable.</para> |
<para>Default: <literal>0</literal> (disabled).</para> |
<para>Equivalent command-line option: <option>-D</option>.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-decoder"><literal>decoder</literal></term> |
<listitem> |
<warning> |
<para>This setting is <emphasis role="strong">deprecated</emphasis>, use |
<xref linkend="term-capturer" /> instead. Notice <xref linkend="term-capturer" /> |
has a different syntax.</para> |
</warning> |
<para>Controls which capturer to use.</para> |
<para>Symbolic values: <literal><symbol>$DEC_FFMPEG</symbol></literal> ⇒ FFmpeg, |
<literal><symbol>$DEC_MPLAYER</symbol></literal> ⇒ MPlayer</para> |
<para>Default: <literal><symbol>$DEC_FFMPEG</symbol></literal> (FFmpeg) </para> |
<para>Related command-line options: |
<option>-F</option>, <option>--ffmpeg</option> and |
<option>-M</option>, <option>--mplayer</option> |
</para> |
<warning> |
<para>DVD mode sets the capturer to MPlayer disregarding the value of |
this setting.</para> |
</warning> |
</listitem> |
</varlistentry> |
<!-- There is NO such setting, but padding=0 can be used instead |
<varlistentry> |
<term id="term-disable_shadows"><literal>disable_padding</literal></term> |
<listitem> |
<para>Disables padding when set to <literal>1</literal>.</para> |
<para>Default: <literal>0</literal></para> |
<para>Equivalent command-line option: <option>-dp</option>, <option>-disable padding</option>.</para> |
</listitem> |
</varlistentry> |
--> |
<varlistentry> |
<term id="term-disable_shadows"><literal>disable_shadows</literal></term> |
<listitem> |
<para>Disables drop shadows when set to <literal>1</literal>.</para> |
<para>Default: <literal>0</literal></para> |
<para>Equivalent command-line option: <option>-ds</option>, <option>--disable shadows</option>.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-disable_timestamps"><literal>disable_timestamps</literal></term> |
<listitem> |
<para>Disables timestamps on captures when set to <literal>1</literal>.</para> |
<para>Default: <literal>0</literal></para> |
<para>Equivalent command-line option: <option>-dt</option>, <option>--disable timestamps</option>.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-end_offset"><literal>end_offset</literal></term> |
<listitem> |
<para>End offset value (amount of time ignored from the end of videos).</para> |
<para>Can be a percentage (of the detected length of each video) |
or an amount of time, specified in the time syntax specified in &vcsmanpage;.</para> |
<para>Default: <literal>5%</literal></para> |
<para>Equivalent command-line option: <option>-E</option>, <option>--end-offset</option>.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-extended_factor"><literal>extended_factor</literal></term> |
<listitem> |
<para>Extended factor value.</para> |
<para>When set to a value different than <literal>0</literal> enables extended mode.</para> |
<para>Default: <literal>0</literal></para> |
<para>See the <ulink url="http://p.outlyer.net/dox/vcs:extended_mode">extended mode</ulink> |
documentation.</para> |
<para>Equivalent command-line option: <option>-e</option>, <option>--extended</option>.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-fg_all"><literal>fg_all</literal></term> |
<listitem> |
<para>Sets the value of all <literal>fg_</literal> variables at once |
(<xref linkend="term-fg_heading" />, |
<xref linkend="term-fg_sign" />, |
<xref linkend="term-fg_title" /> and |
<xref linkend="term-fg_tstamps" />).</para> |
<para role="aside">Since version 1.12.2</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-fg_heading"><literal>fg_heading</literal></term> |
<term id="term-fg_sign"><literal>fg_sign</literal></term> |
<term id="term-fg_title"><literal>fg_title</literal></term> |
<term id="term-fg_tstamps"><literal>fg_tstamps</literal></term> |
<listitem> |
<para>These variables control the font colours of each section in the contact sheet.</para> |
<note> |
<para>Valid colour values are those understood by <application>ImageMagick</application>, |
e.g. <ulink url="http://www.imagemagick.org/script/color.php#color_names">color |
names</ulink> or HTML/CSS-style color specifications |
(<replaceable class="parameter">#RRGGBB<optional>AA</optional></replaceable>, |
<replaceable class="parameter">#RGB<optional>A</optional></replaceable>).</para> |
<para>See <ulink url="http://www.imagemagick.org/script/color.php" /> |
for more details and additional formats.</para> |
</note> |
<tip> |
<para>The command <literal>$ <userinput>convert -list color</userinput></literal> |
prints a list of all known colour names.</para> |
</tip> |
<para><literal>fg_heading</literal> &emdash; File meta information. |
Default: <constant>Black</constant> |
[≡ RGB(0,0,0)]</para> |
<para><literal>fg_title</literal> &emdash; Title (with option <option>-T</option>). |
Default: <constant>Black</constant> |
[≡ RGB(0,0,0)]</para> |
<para><literal>fg_tstamps</literal> &emdash; Timestamps. |
Default: <constant>White</constant> |
[≡ RGB(255,255,255)]</para> |
<para><literal>fg_sign</literal> &emdash; Footer. |
Default: <constant>Black</constant> |
[≡ RGB(0,0,0)]</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-font_all"><literal>font_all</literal></term> |
<listitem> |
<para>Sets the value of all <literal>font_</literal> variables at once |
(<xref linkend="term-font_heading" />, |
<xref linkend="term-font_sign" />, |
<xref linkend="term-font_title" /> and |
<xref linkend="term-font_tstamps" />)</para> |
<para>Additional details: Since 1.12.2</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-font_heading"><literal>font_heading</literal></term> |
<term id="term-font_sign"><literal>font_sign</literal></term> |
<term id="term-font_title"><literal>font_title</literal></term> |
<term id="term-font_tstamps"><literal>font_tstamps</literal></term> |
<listitem> |
<para>These variables control the fonts used in each section of the contact sheet.</para> |
<para><literal>font_heading</literal> &emdash; File meta information. |
Default: <constant>DejaVu-Sans-Book</constant></para> |
<para><literal>font_title</literal> &emdash; Title (with option <option>-T</option>). |
Default: <constant>DejaVu-Sans-Book</constant></para> |
<para><literal>font_tstamps</literal> &emdash; Used for timestamps over the thumbnails. |
Default: <constant>DejaVu-Sans-Book</constant></para> |
<para><literal>font_sign</literal> &emdash; Footer / signature. |
Default: <constant>DejaVu-Sans-Book</constant></para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-format"><literal>format</literal></term> |
<listitem> |
<para>Output file format</para> |
<para>Default: <literal>png</literal></para> |
<note> |
<para>Should match the extension of a format known by <application>ImageMagick</application>.</para> |
</note> |
<para>Related command-line options: |
<option>-j</option>, <option>--jpeg</option> and |
<option>--jpeg2</option> |
</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-getopt"><literal>getopt</literal></term> |
<listitem> |
<para><acronym>GNU</acronym> <command>getopt</command> command</para> |
<para>Default: <literal>getopt</literal></para> |
<warning> |
<para>The <command>getopt</command> command name must be set correctly or vcs won't work.</para> |
<para>Must be a version compatible with <acronym>GNU</acronym> syntax.</para> |
<para>Can only be set in configuration files (i.e. not from the command-line).</para> |
</warning> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-height"><literal>height</literal></term> |
<listitem> |
<para>Height of individual captures.</para> |
<para>Can be a fixed number of pixels or a percentage.</para> |
<para>The default is the same as input i.e. <literal>100%</literal>.</para> |
<para>Equivalent command-line option: <option>-H</option>, <option>--height</option>.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-interval"><literal>interval</literal></term> |
<listitem> |
<para>Interval between captures, when the mode of operation is to capture |
at fixed intervals.</para> |
<para>Accepts the same format as any option accepting times, see &vcsmanpage; for details |
on the acceptable syntax.</para> |
<para>Default: <literal>300</literal> (≡ 5 minutes).</para> |
<note> |
<para>Unlike its command-line counterpart (<option>-i</option> or <option>--interval</option>), |
changing the value of <symbol>interval</symbol> doesn't automatically |
switch modes to capture at intervals.</para> |
<para>The mode of operation is controlled by <xref linkend="term-timecode_from" />.</para> |
</note> |
<para>Equivalent command-line option: <option>-i</option>, <option>--interval</option>.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-nonlatin_filenames"><literal>nonlatin_filenames</literal></term> |
<listitem> |
<para>Enables or disables the usage of an alternate font to print |
filenames in the contact sheet meta-information section.</para> |
<para>Set to <literal>1</literal> to use <xref linkend="term-nonlatin_font" /> to print filenames.</para> |
<para>Default: <literal>0</literal> |
⇒ use the standard font, <xref linkend="term-font_heading"/>.</para> |
<para role="aside">Since 1.12.2</para> |
<para>Equivalent command-line option: <option>--nonlatin</option>, <option>-Ik</option>, <option>-Ij</option>.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-nonlatin_font"><literal>nonlatin_font</literal></term> |
<listitem> |
<para>Font used for non-Latin filenames when <xref linkend="term-nonlatin_filenames" /> |
is enabled.</para> |
<para>Default: (picked automatically)</para> |
<note> |
<para>This font is, when possible, picked automatically.</para> |
<para>Can be set manually with the <option>-Ik</option> or <option>-Ij</option> option.</para> |
</note> |
<para>Equivalent command-line option: <option>-Ik</option>, <option>-Ij</option>.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-numcaps"><literal>numcaps</literal></term> |
<listitem> |
<para>Number of captures, when the mode of operation is to do a fixed |
number of captures.</para> |
<para>Default: <literal>16</literal>.</para> |
<note> |
<para>Unlike its command-line counterpart (<option>-n</option> or <option>--numcaps</option>), |
changing the value of <symbol>numcaps</symbol> doesn't automatically |
switch modes to do a fixed number of captures.</para> |
<para>The mode of operation is controlled by <xref linkend="term-timecode_from" />.</para> |
</note> |
<para>Equivalent command-line option: <option>-n</option>, <option>--numcaps</option>.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-padding"><literal>padding</literal></term> |
<listitem> |
<para>Number of pixels between captures when placed in the contact sheet.</para> |
<para>Default: <literal>2</literal></para> |
<para>Related command-line option: <option>-dp</option>, <option>--disable padding</option>.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-plain_messages"><literal>plain_messages</literal></term> |
<listitem> |
<para>Allows disabling colourised feedback to the console.</para> |
<para>Set to <literal>1</literal> to print plain, monochrome, feedback.</para> |
<para>Default: <literal>0</literal> (≡ don't disable colours).</para> |
<para>Related command-line option: <option>-Wc</option>.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-profiles"><literal>profiles</literal></term><!-- since 1.13 --> |
<listitem> |
<para>Loads profile(s).</para> |
<para>Its value must be a profile name or a comma-separated list of profile names.</para> |
<informalexample> |
<para>Example: |
<literal>profiles=<symbol>white</symbol>,<symbol>mosaic</symbol></literal> |
will load the <literal>white</literal> and <literal>mosaic</literal> profiles. |
</para> |
</informalexample> |
<para>Default: (empty).</para> |
<para>Equivalent command-line option: <option>-p</option>, <option>--profile</option>.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-pts_meta"><literal>pts_meta</literal></term> |
<term id="term-pts_sign"><literal>pts_sign</literal></term> |
<term id="term-pts_title"><literal>pts_title</literal></term> |
<term id="term-pts_tstamps"><literal>pts_tstamps</literal></term> |
<listitem> |
<para>These variables control font size of each section in the contact sheet.</para> |
<para>These sizes are expressed in <emphasis>points</emphasis>.</para> |
<para><literal>pts_meta</literal> &emdash; File meta-information. |
Default: <literal>14</literal></para> |
<para><literal>pts_title</literal> &emdash; Title (with option <option>-T</option>). |
Default: <literal>33</literal>.</para> |
<para><literal>pts_tstamps</literal> &emdash; Timestamps. |
Default: <literal>14</literal>. |
<note> |
<para>The value of <symbol>pts_tstamps</symbol> is reduced for smaller captures.</para> |
</note> |
</para> |
<para><literal>pts_sign</literal> &emdash; Footer/signature. |
Default: <literal>10</literal></para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-quality"><literal>quality</literal></term> |
<listitem> |
<para>Image quality (level of compression) when outputting to lossy formats.</para> |
<para><literal>0</literal> to <literal>100</literal>, with <literal>100</literal> |
being the best quality (the least compression).</para> |
<para>Default: <literal>92</literal>.</para> |
<note> |
<para>This value only affects the final image.</para> |
</note> |
</listitem> |
</varlistentry> |
<!-- GONE in 1.13 |
<varlistentry> |
<term id="term-safe_rename_pattern"><literal>safe_rename_pattern</literal></term> |
<listitem> |
<para>Pattern used for output files to avoid overwriting existing files.</para> |
<para>Default: <literal>%b-%N.%e</literal></para> |
<para>%b: Basename</para> |
<para>%N: Incremental number</para> |
<para>%e: extension</para> |
<warning> |
<para>Scheduled for removal in 1.13</para> |
</warning> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-shoehorned"><literal>shoehorned</literal></term> |
<listitem> |
<para>Inserts additional parameters into ffmpeg or mplayer capture commands</para> |
<warning> |
<para>Scheduled for removal in 1.13</para> |
</warning> |
</listitem> |
</varlistentry> |
--> |
<varlistentry> |
<term id="term-signature"><literal>signature</literal></term> |
<listitem> |
<para>Text before the user name in the footer.</para> |
<para>Default: <literal>"Preview created by"</literal>.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-stderr"><literal>stderr</literal></term> |
<listitem> |
<para>Standard error of programs when probing and capturing is sent here.</para> |
<para>Default: <filename class="devicefile">/dev/null</filename>.</para> |
<note> |
<para>Setting it to <filename class="devicefile">/dev/stderr</filename> to |
will return capturer programs to their normal behaviour.</para> |
</note> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-stdout"><literal>stdout</literal></term> |
<listitem> |
<para>Standard output of programs when probing and capturing is sent here.</para> |
<para>Default: <filename class="devicefile">/dev/null</filename>.</para> |
<note> |
<para>Setting it to <filename class="devicefile">/dev/stdout</filename> to |
will return capturer programs to their normal behaviour.</para> |
</note> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-timecode_from"><literal>timecode_from</literal></term> |
<listitem> |
<para>Controls the main mode of operation: capture at intervals or capture |
a fixed number of snapshots.</para> |
<para>Possible values are <literal><symbol>$TC_INTERVAL</symbol></literal> to |
capture at intervals (will use <xref linkend="term-interval" />), |
and <literal><symbol>$TC_NUMCAPS</symbol></literal> to capture a fixed |
number of images (will use <xref linkend="term-numcaps" />).</para> |
<para>Default: <literal><symbol>$TC_INTERVAL</symbol></literal>.</para> |
<note> |
<para>This setting is affected by command-line options <option>-i</option> |
and <option>-n</option>.</para> |
</note> |
<para>Related command-line options: |
<option>-i</option>, <option>--interval</option> and |
<option>-n</option>, <option>--numcaps</option> |
</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-user"><literal>user</literal></term> |
<listitem> |
<para>User name for the footer's signature.</para> |
<para>Default: <command>$(id -un)</command> (≡ system user name).</para> |
<para>Related command-line options: |
<option>-u</option>, <option>--user</option> and |
<option>-U</option>, <option>--fullname</option> |
</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term id="term-verbosity"><literal>verbosity</literal></term> |
<listitem> |
<para>Verbosity level.</para> |
<para>Possible values: |
<segmentedlist> |
<?dbhtml list-presentation="table"?> |
<?dbfo list-presentation="table"?> |
<segtitle>Value</segtitle> |
<segtitle>Meaning</segtitle> |
<seglistitem> |
<seg><literal><symbol>$V_ALL</symbol></literal></seg> |
<seg>Print everything. Equivalent to <symbol>$V_NOTICE</symbol>.</seg> |
</seglistitem> |
<seglistitem> |
<seg><literal><symbol>$V_NONE</symbol></literal></seg> |
<seg>Print no feedback at all. Equivalent to command-line option <option>-qq</option>.</seg> |
</seglistitem> |
<seglistitem> |
<seg><literal><symbol>$V_ERROR</symbol></literal></seg> |
<seg>Print only errors.</seg> |
</seglistitem> |
<seglistitem> |
<seg><literal><symbol>$V_WARN</symbol></literal></seg> |
<seg>Print warnings and errors.</seg> |
</seglistitem> |
<seglistitem> |
<seg><literal><symbol>$V_INFO</symbol></literal></seg> |
<seg>Print informational messages, warnings and errors. |
This encompasses all messages, so it is equivalent to <symbol>$V_ALL</symbol>.</seg> |
</seglistitem> |
</segmentedlist> |
</para> |
<para>Default: <literal><symbol>$V_ALL</symbol></literal>.</para> |
<para>Related command-line option: <option>-q</option>, <option>--quiet</option>.</para> |
</listitem> |
</varlistentry> |
</variablelist> |
<!-- vim:set ts=4 et: --> |
/video-contact-sheet/branches/1.13/dist/docs/src/vcs.conf.man.xml |
---|
0,0 → 1,203 |
<?xml version="1.0" encoding="UTF-8" ?> |
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" |
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ |
<!-- |
$Id: vcs.conf.man.xml 2342 2011-09-01 13:19:47Z toni $ |
See vcs.man.xml for comments on docbook+man handling. |
--> |
<!ENTITY firstname "Toni"> |
<!ENTITY surname "Corvera"> |
<!ENTITY fullname "&firstname; &surname;"> |
<!ENTITY email "outlyer@gmail.com"> |
<!ENTITY title "vcs User Manual"> |
<!ENTITY package "vcs.conf"> |
<!ENTITY section "5"> |
<!ENTITY emdash "—"> |
<!ENTITY equiv "≡"> |
<!ENTITY rArr "⇒"> |
<!ENTITY vcsmanpage "<citerefentry><refentrytitle>vcs</refentrytitle><manvolnum>1</manvolnum></citerefentry>"> |
<!-- |
XInclude trickery |
This voodoo is only required for the file to validate, it can be used |
by e.g. xsltproc without all of this |
Reference: http://www.sagehill.net/docbookxsl/ValidXinclude.html#XincludeDTD |
--> |
<!-- Define the xi:include and xi:fallback elements --> |
<!ELEMENT xi:include (xi:fallback?) > |
<!ATTLIST xi:include |
xmlns:xi CDATA #FIXED "http://www.w3.org/2001/XInclude" |
href CDATA #IMPLIED |
parse (xml|text) "xml" |
xpointer CDATA #IMPLIED |
encoding CDATA #IMPLIED |
accept CDATA #IMPLIED |
accept-language CDATA #IMPLIED > |
<!ELEMENT xi:fallback ANY> |
<!ATTLIST xi:fallback |
xmlns:xi CDATA #FIXED "http://www.w3.org/2001/XInclude" > |
<!-- |
Add xi:include to the list of possible children of <refsect1> |
See http://www.oasis-open.org/docbook/xml/4.5/dbhierx.mod for the DTD |
module that defines which elements are allowed inside which. |
Can't allow xi:include in arbitrary places inside <refentry> |
--> |
<!ENTITY % local.refcomponent.mix "| xi:include"> |
]><!--/!DOCTYPE--> |
<refentry lang="en-GB"> |
<refentryinfo> |
<title>&title;</title> |
<productname>&package;</productname> |
<author> |
<firstname>&firstname;</firstname> |
<surname>&surname;</surname> |
<contrib /> |
<address> |
<email>&email;</email> |
<otheraddr> |
<ulink url="http://corvera.eu./" /> |
</otheraddr> |
</address> |
</author> |
<copyright> |
<year>2007-2011</year> |
<holder>&fullname;</holder> |
</copyright> |
<legalnotice> |
<para>Permission is granted to copy, distribute and/or modify this |
document under the terms of the GNU Lesser General Public License, |
Version 2 or (at your option) any later version published by |
the Free Software Foundation.</para> |
</legalnotice> |
<releaseinfo>$Rev: 2342 $</releaseinfo> |
<!--<date>$Date: 2011-09-01 15:19:47 +0200 (dj, 01 set 2011) $</date>--> |
</refentryinfo> |
<refmeta> |
<refentrytitle>&package;</refentrytitle> |
<manvolnum>§ion;</manvolnum> |
</refmeta> |
<refnamediv> |
<refname>&package;</refname> |
<refpurpose>vcs configuration file</refpurpose> |
</refnamediv> |
<refsect1 id="description"> |
<title>DESCRIPTION</title> |
<para>This manual page describes the format and available settings |
in configuration and profile files for |
<citerefentry> |
<refentrytitle>vcs</refentrytitle> |
<manvolnum>1</manvolnum> |
</citerefentry> |
</para> |
<para>There's two types of files that follow this syntax: |
<link linkend="configfiles">configuration files</link> |
(see <xref linkend="configfiles"/>) |
and <link linkend="profiles">profiles</link> |
(see <xref linkend="profiles"/>). They'll be called collectively |
<emphasis>settings files</emphasis> in this manual page.</para> |
<para>Configuration files are meant to be loaded by default, intended to |
set user's preferred options, while |
profiles are meant to be loaded on-demand, intended to allow |
different parallel sets of settings.</para> |
</refsect1><!--/DESCRIPTION--> |
<refsect1 id="syntax"> |
<title>SYNTAX</title> |
<para>Settings files contain a series of |
<replaceable>SETTING</replaceable>=<replaceable>VALUE</replaceable> |
assignments. |
</para> |
<para>Comments can be included by preceding `<literal>#</literal>' to them.</para> |
<refsect2 id="metainfo"> |
<title>META-INFORMATION</title> |
<para>Meta-information fields can be contained in comments. |
They are written as '<literal>vcs:<replaceable>FIELDNAME</replaceable>:</literal>'.</para> |
<para>Currently supported meta-information fields:</para> |
<variablelist> |
<varlistentry> |
<term><literal>vcs:conf:</literal></term> |
<listitem><para>Marks a file as following this format.</para> |
<para>Files without this field will be rejected. |
<footnote> |
<para><filename>./vcs.conf</filename> won't be rejected if this |
field is missing, though it's preferable to include it |
to be ease moving the file to a different location or |
turning it into a profile.</para> |
</footnote> |
</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><literal>vcs:desc:</literal> <replaceable>DESCRIPTION</replaceable></term> |
<listitem><para>Describes this particular file's purpose, |
it is shown e.g. when listing available profiles. |
</para> |
<para>It is currently ignored for configuration files.</para> |
</listitem> |
</varlistentry> |
</variablelist> |
</refsect2><!--/META-INFORMATION--> |
<refsect2 id="syntax-example"> |
<title>SYNTAX EXAMPLE</title> |
<programlisting># vcs:conf: |
# vcs:desc: White-on-black |
bg_all=black # Black background |
fg_all=white # White foreground</programlisting> |
</refsect2><!--/SYNTAX EXAMPLE--> |
</refsect1><!--/SYNTAX--> |
<refsect1 id="configfiles"> |
<title>CONFIGURATION FILES</title> |
<para>There's three configuration files loaded by default if present, in order:</para> |
<itemizedlist> |
<listitem><para><filename>/etc/vcs.conf</filename></para></listitem> |
<listitem><para><filename><envar>${HOME}</envar>/.vcs.conf</filename></para></listitem> |
<listitem><para><filename><envar>${HOME}</envar>/vcs/vcs.conf</filename></para></listitem> |
</itemizedlist> |
<para>Every file in this list overrides the previous when it |
re-defines a setting.</para> |
<para>Configuration files can be loaded manually off of any path by using the |
<option>--config <replaceable>FILENAME</replaceable></option> option.</para> |
</refsect1><!--/CONFIGURATION FILES--> |
<refsect1 id="profiles"> |
<title>PROFILE FILES</title> |
<para>No profile is loaded by default.</para> |
<para>Profiles are searched in three possible locations, in order:</para> |
<itemizedlist id="profile-paths"> |
<listitem><para><filename class="directory"><envar>${HOME}</envar>/.vcs/profiles/</filename></para></listitem> |
<listitem><para><filename class="directory">/usr/local/share/vcs/profiles/</filename></para></listitem> |
<listitem><para><filename class="directory">/usr/share/vcs/profiles/</filename></para></listitem> |
</itemizedlist> |
<para>Only the first profile for each name will be considered. |
Profiles with the same name will be hidden.</para> |
<para><literal>$ <command>vcs --profile :list</command></literal></para> |
<para>can be used to get a list of available profiles.</para> |
<para>Profiles can only be loaded from the <link linkend="profile-paths">listed |
paths</link>.</para> |
</refsect1><!--/PROFILE FILES--> |
<refsect1> |
<title>SETTINGS</title> |
<para>This list details the available settings. Settings are listed in |
alphabetical order.</para> |
<para>A list of available settings, grouped by categories, is also kept |
online at <ulink url="http://p.outlyer.net/dox/vcs:conf_files" /></para> |
<xi:include |
xmlns:xi="http://www.w3.org/2001/XInclude" |
href="./settings.man.inc.xml" /> |
</refsect1> |
<refsect1 id="see_also"> |
<title>SEE ALSO</title> |
<para> |
<citerefentry> |
<refentrytitle>vcs</refentrytitle> |
<manvolnum>1</manvolnum> |
</citerefentry>, |
<citerefentry> |
<refentrytitle>id</refentrytitle> |
<manvolnum>1</manvolnum> |
</citerefentry> |
</para> |
</refsect1><!--/SEE ALSO--> |
</refentry> |
<!-- vim:set ts=4 et: --> |
/video-contact-sheet/branches/1.13/dist/docs/src/vcs.conf.example |
---|
0,0 → 1,159 |
# |
# vcs:conf: $Rev: 2333 $ |
# Example vcs.conf file |
# This example files contains all the default values, commented out. |
# For each setting, where an equivalent command-line option exists it will be |
# listed in the comments |
# |
# Location: |
# Configuration files can be placed at /etc/vcs.conf (site-wide), |
# ~/.vcs.conf (current user, hidden file!), ./vcs.conf (current dir) or |
# ~/.vcs/vcs.conf (current user, new alternative location) |
# |
# Syntax: |
# * Comments start with '#' or ';' |
# * '#' can be used in values by writing $# |
# * Semicolons (;) can't be used in values |
# * Options are of the form name=value |
# - Options can refer to the *current* value of other options, i.e. |
# font_sign=$font_heading will assign to 'font_sign' the same value |
# as 'font_heading'. If font_heading is changed after that, font_sign |
# won't be affected. |
# - See <http://p.outlyer.net/dox/vcs:conf_files> for the full list |
# Height of individual captures. Percentage or fixed size (in pixels) |
height=100% # option -H |
# Amount ignored from the end of the video (think of it as an anti-spoiler |
# measure). Percentage (of video duration) or time (e.g. 2m). Set to 0 |
# to disable it. |
end_offset=5% # option -E |
######################## |
# Contact Sheet Layout # |
######################## |
columns=2 # Number of columns in the contact sheet (option -c) |
interval=1m # Interval between captures (option -i) |
# Number of captures. Note setting this isn't enough to use a fixed number, the |
# mode must also be changed (option 'timecode_from', see below). (option -n) |
captures=16 |
# Mode of operation, can be either $TC_NUMCAPS or $TC_INTERVAL (the default) |
# $TC_NUMCAPS Fix the number of captures, adjust interval accordingly |
# $TC_INTERVAL Fix interval, adjust the number of captures accordingly |
# (options -i and -n set this implicitly) |
timecode_from=$TC_INTERVAL |
# Modifier for extended mode (option -e). |
# Beware, setting it to something different from 0 automatically enables extended mode! |
extended_factor=0 |
# Extra padding added around each capture. |
# This has *no effect* when shadows are enabled (the default). |
# Tweaking this might break alignment when using extended mode (-e) |
padding=2 |
# Anonymous mode, set to 1 to disable the "Preview created by {value of user}" |
# line in the footer. (option --anonymous) |
anonymous=0 |
# Profiles to load by default. (option -p) |
# *MUST* exist. |
profiles= |
############### |
# Output file # |
############### |
# Output format. Use the file extension (e.g. 'png', 'jpg', 'jp2') |
# Any format accepted by ImageMagick can be used here (even pdf or gif, not |
# that they'll look very good though) |
# (options: -j for JPEG, -j2 for JPEG 2000) |
format=png |
quality=92 # Output quality for lossy formats (e.g. jpg) |
#################### |
# Cosmetic touches # |
#################### |
user=`id -un` # User name, will be used in the contact sheet footer (option -u) |
# Used in the signature, e.g. "Preview created by {value of user}" |
signature=Preview created by |
disable_shadows=0 # Disable shadows by default (option -ds) |
disable_timestamps=0 # Disable timestamps by default (option -dt) |
##################### |
# Colours and fonts # |
##################### |
# * Colours can be defined either by their name or through hexcodes |
# use the command 'identify -list color' for a list of known names |
# * Colours can use transparency, although this usually only makes sense |
# for timestamps, which are overlaid on captures. |
# * Beware when specifying colours in hex format color=#hex is ok |
# but color = #hex not, spaces can be used with color = $#hex |
# * Font sizes are expressed in points and hence might need adjustment |
# when the font is changed. |
# * Font names can be either font paths (e.g. /usr/something/ttf/font.ttf) |
# or font names as understood by ImageMagick, use the command |
# 'identify -list font' for a list of fonts known to ImageMagick |
bg_heading=#afcd7a # Heading/meta-information section background colour |
fg_heading=Black # Heading font colour |
font_heading=DejaVu-Sans-Book # Heading font |
pts_heading=14 # Font size for heading |
bg_title=White # Background for the title (if activated with option -T) |
fg_title=Black # Title font colour |
font_title=$font_heading # Title font |
bg_contact=White # Background for the contact sheet |
bg_tstamps=#000000aa # Background for timestamps. Note the use of transparency |
fg_tstamps=White # Timestamps font colour |
font_tstamps=$font_heading # Timestamps font |
pts_tstamps=14 # Font size for timestamps |
# Background for the signature, i.e. the section after the contact sheet with |
# vcs and user identification |
bg_sign=SlateGray |
fg_sign=Black # Font colour for the signature |
font_sign=$font_heading # Font for the signature |
pts_sign=10 # Font size for signature |
# Font to use for file name when the non-latin mode (--nonlatin) is enabled |
# Option: -Ik=fontname |
nonlatin_font= # Autodetected (hopefully) |
###################### |
# Lower level tweaks # |
###################### |
# Decoder to use by default, can be either $DEC_FFMPEG (default) or |
# $DEC_MPLAYER |
# (options: -M for MPlayer, -F for FFMpeg) |
decoder=$DEC_FFMPEG |
# Output from commands, useful to locate errors |
stdout=/dev/null |
stderr=/dev/null |
# Verbosity level, very verbose by default. |
# Possible values: $V_ALL (default), $V_ERROR, $V_WARN, $V_INFO, $V_NONE |
# (options: -q for quietness) |
verbosity=$V_ALL |
# 1 disables colours in console output |
simple_feedback=0 |
debug=0 # When 1, enables debugging mode (option -D) |
getopt=getopt # GNU Getopt executable name |
/video-contact-sheet/branches/1.13/dist/docs/src/vcs.man.xml |
---|
0,0 → 1,850 |
<?xml version="1.0" encoding="UTF-8" ?> |
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" |
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ |
<!-- |
$Id$ |
Useful Docbook References: |
- Creating DocBook Documents - List of elements |
<http://www.docbook.org/tdg5/en/html/ch02.html> |
- Writing with DocBook elements - Useful commands (elements) |
<http://www.ibiblio.org/godoy/sgml/docbook/howto/writing-docbook.html#WRITING-DOCBOOK-COMMANDS> |
- DocBook Guide for Authors of Geant4 User Manuals - Tag Mapping Table - (X)HTML vs. DocBook |
<http://geant4.web.cern.ch/geant4/workAreaUserDocKA/AuthorsInstruction/IntroDocBook.html#TagMap> |
- DocBook 5: The Definitive Guide (includes list of elements) |
<http://docbook.org/tdg51/en/html/docbook.html> |
Generation of man page: |
$ xmlto man manpage.xml |
OR |
$ xsltproc -''-nonet \ |
-''-param man.charmap.use.subset "0" \ |
-''-param make.year.ranges "1" \ |
-''-param make.single.year.ranges "1" \ |
/usr/share/xml/docbook/stylesheet/docbook-xsl/manpages/docbook.xsl \ |
manpage.xml |
Will generate vcs.1. |
View with: |
$ nroff -man vcs.1 | less |
or |
$ man vcs.1 |
Validation: xmllint -''-noout -''-valid manpage.xml |
Spellcheck: aspell -l en-GB -H check FILENAME.xml |
--> |
<!ENTITY firstname "Toni"> |
<!ENTITY surname "Corvera"> |
<!-- fullname could also be set to "&firstname; &surname;". --> |
<!ENTITY fullname "&firstname; &surname;"> |
<!ENTITY email "outlyer@gmail.com"> |
<!ENTITY section "1"> |
<!-- TITLE should be something like "User commands" or similar (see |
http://www.tldp.org/HOWTO/Man-Page/q2.html). --> |
<!ENTITY title "Video Contact Sheet *NIX User Manual"> |
<!ENTITY ucpackage "VCS"> |
<!ENTITY package "vcs"> |
<!ENTITY emdash "—"> |
<!ENTITY xrefinterval 'See the accepted syntax at <xref linkend="interval_format" />.'> |
]> |
<refentry lang="en-GB"> |
<refentryinfo> |
<title>&title;</title> |
<productname>&package;</productname> |
<author> |
<firstname>&firstname;</firstname> |
<surname>&surname;</surname> |
<contrib /> |
<!-- <contrib>VCS author.</contrib> --> |
<address> |
<email>&email;</email> |
<otheraddr> |
<ulink url="http://corvera.eu./" /> |
</otheraddr> |
</address> |
</author> |
<copyright> |
<year>2007-2011</year> |
<holder>&fullname;</holder> |
</copyright> |
<legalnotice> |
<para>Permission is granted to copy, distribute and/or modify this |
document under the terms of the GNU Lesser General Public License, |
Version 2 or (at your option) any later version published by |
the Free Software Foundation.</para> |
</legalnotice> |
<releaseinfo>$Rev$</releaseinfo> |
<!--<date>$Date$</date>--> |
</refentryinfo> |
<refmeta> |
<refentrytitle>&ucpackage;</refentrytitle> |
<manvolnum>§ion;</manvolnum> |
</refmeta> |
<refnamediv> |
<refname>&package;</refname> |
<refpurpose>create contact sheets from videos</refpurpose> |
</refnamediv> |
<refsynopsisdiv> |
<cmdsynopsis> |
<command>&package;</command> |
<arg choice="opt" rep="repeat"><replaceable class="option">OPTION</replaceable></arg> |
<arg choice="plain"><replaceable class="parameter">FILE</replaceable></arg> |
<arg choice="opt" rep="repeat"><replaceable class="parameter">FILE</replaceable></arg> |
</cmdsynopsis> |
<cmdsynopsis> |
<command>&package;</command> |
<arg choice="opt"><option>--output=<replaceable>OUTPUT1</replaceable></option></arg> |
<arg choice="opt"><option>--output=<replaceable>OUTPUT2</replaceable></option></arg> |
<arg choice="opt"><option>...</option></arg> |
<arg choice="plain"><replaceable>INPUT1</replaceable></arg> |
<arg choice="opt" rep="repeat"><replaceable>INPUT2</replaceable></arg> |
</cmdsynopsis> |
<cmdsynopsis> |
<command>&package;</command> |
<group choice="opt"> |
<arg><option>-n <replaceable>20</replaceable></option></arg> |
<arg><option>-i <replaceable>1m</replaceable></option></arg> |
</group> |
<arg><option>-c <replaceable>4</replaceable></option></arg> |
<arg><option>-H <replaceable>120</replaceable></option></arg> |
<arg rep="repeat"></arg> |
<arg choice="plain" rep="repeat"><replaceable>FILE</replaceable></arg> |
</cmdsynopsis> |
<cmdsynopsis> |
<command>&package;</command> |
<!-- Help/test options. |
They stop the program after outputting their related information. --> |
<group choice="opt"> |
<arg choice="plain"> |
<group choice="req"> |
<arg choice="plain"><option>-h</option></arg> |
<arg choice="plain"><option>--help</option></arg> |
</group> |
</arg> |
<arg choice="plain"> |
<arg choice="plain"><option>--fullhelp</option></arg> |
</arg> |
<arg choice="plain"> |
<arg choice="plain"><option>-DD</option></arg> |
</arg> |
</group> |
</cmdsynopsis> |
<cmdsynopsis> |
<command>&package;</command> |
<arg choice="opt" rep="repeat"><replaceable class="option">OPTION</replaceable></arg> |
<arg choice="plain"><option>--generate</option> |
<group choice="req"> |
<arg choice="plain">config</arg> |
<arg choice="plain">profile</arg> |
</group> |
</arg> |
</cmdsynopsis> |
</refsynopsisdiv> |
<refsect1 id="description"> |
<title>DESCRIPTION</title> |
<para><command>&package;</command> creates a preview |
image from videos in a contact sheet-like format (i.e. captures from |
different frames in the video are placed in a mosaic).</para> |
<para>By default the output file will be named like the input file plus the |
png extension. Example: "<filename>file.avi</filename>" will produce |
a contact sheet in the file "<filename>file.avi.png</filename>".</para> |
<para>The default mode of operation is to obtain captures every five minutes in the |
video, so the amount of captures will vary with each file. The command-line |
argument <parameter>--numcaps</parameter> (<parameter>-n</parameter>) can be used |
to change this behaviour or alternatively a configuration file might |
be used to change the mode of operation (see <citerefentry> |
<refentrytitle>vcs.conf</refentrytitle> |
<manvolnum>5</manvolnum> |
</citerefentry>). |
</para> |
<para>This manual page documents <command>&package;</command>, |
further documentation can be found in the |
<ulink url="http://p.outlyer.net/dox/vcs">online documentation</ulink> site.</para> |
</refsect1><!--/DESCRIPTION--> |
<refsect1 id="options"> |
<title>OPTIONS</title> |
<para>The program follows the usual GNU command line syntax, |
with long options starting with two dashes (`-'). A summary of |
options is included below.</para> |
<variablelist> |
<varlistentry> |
<term><option>-n <replaceable>number</replaceable></option></term> |
<term><option>--numcaps=<replaceable>number</replaceable></option></term> |
<listitem> |
<para>Fixes the number of captures to obtain.</para> |
<para>Sets the mode of operation to capture a fixed number of frames.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-i <replaceable>INTERVAL</replaceable></option></term> |
<term><option>--interval=<replaceable>INTERVAL</replaceable></option></term> |
<listitem> |
<para>Sets the interval between captures.</para> |
<para>Sets the mode of operation to capture at fixed intervals.</para> |
<para>The number of captures will depend on the video length.</para> |
<para>&xrefinterval;</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-c <replaceable>NUMBER</replaceable></option></term> |
<term><option>--columns=<replaceable>NUMBER</replaceable></option></term> |
<listitem> |
<para>Number of columns in the contact sheet.</para> |
<para>The number of rows will depend on this value and the number of captures (there's no |
way to set the number of rows).</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-H <replaceable>HEIGHT</replaceable></option></term> |
<term><option>--height=<replaceable>HEIGHT</replaceable></option></term> |
<listitem> |
<para>Height of captures.</para> |
<para>Can be a number (of pixels) or a percentage (of the video height).</para> |
<para>By default the same size as the video is used.</para> |
<note> |
<para>The width is derived from height and aspect ratio.</para> |
</note> |
<tip> |
<para><replaceable>HEIGHT</replaceable> x <replaceable>WIDTH</replaceable> |
can be manually forced by setting both <option>-H</option> and |
<option>-a</option>, e.g. <replaceable>640x480</replaceable>:</para> |
<para><literal>$ <command>vcs -a 640/480 -H 480 <replaceable><optional>...</optional></replaceable></command></literal></para> |
</tip> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-o <replaceable>FILENAME</replaceable></option></term> |
<term><option>--output=<replaceable>FILENAME</replaceable></option></term> |
<listitem> |
<para>Name of output file.</para> |
<para>By default the video file name plus the output |
format is used (e.g. "<filename>video.avi.png</filename>" |
for "<filename>video.avi</filename>").</para> |
<para>If an extension is provided, it will define the output format, otherwise |
PNG will be used. I.e. <filename>sheet.jpg</filename> will produce |
a JPEG file while <filename>sheet</filename> or |
<filename>sheet.png</filename> will produce a PNG file.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-h</option></term> |
<term><option>--help</option></term> |
<listitem> |
<para>Show summary of most common options.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>--fullhelp</option></term> |
<listitem> |
<para>Show summary of all options.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-a <replaceable>ASPECT</replaceable></option></term> |
<term><option>--aspect <replaceable>ASPECT</replaceable></option></term> |
<listitem> |
<para>Aspect ratio.</para> |
<para>Accepts a floating point number or a fraction.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-f <replaceable>TIMESTAMP</replaceable></option></term> |
<term><option>--from <replaceable>TIMESTAMP</replaceable></option></term> |
<listitem> |
<para>Set starting time. No captures will be made before this <replaceable>TIMESTAMP</replaceable>.</para> |
<para>&xrefinterval;</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-t <replaceable>TIMESTAMP</replaceable></option></term> |
<term><option>--to <replaceable>TIMESTAMP</replaceable></option></term> |
<listitem> |
<para>Set ending time. No captures will be made after this TIMESTAMP.</para> |
<para>&xrefinterval;</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-T <replaceable>TITLE</replaceable></option></term> |
<term><option>--title <replaceable>TITLE</replaceable></option></term> |
<listitem> |
<para>Add a title above the captures.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-j</option></term> |
<term><option>--jpeg</option></term> |
<listitem> |
<para>Output file in JPEG format.</para> |
<para>The default output format is PNG.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-j2</option></term> |
<term><option>--jpeg2</option></term> |
<term><option>--jpeg=2</option></term> |
<listitem> |
<para>Output file in JPEG 2000 format.</para> |
<para>The default output format is PNG.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-V</option></term> |
<term><option>--dvd</option></term> |
<listitem> |
<para>DVD mode.</para> |
<para>In this mode the input files must be the DVD |
device(s) or ISO(s).</para> |
<para>When in DVD mode all input files must be DVDs.</para> |
<note> |
<para>Implies <option>-A</option> (auto aspect ratio).</para> |
</note> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>--dvd-title <replaceable>TITLENUM</replaceable></option></term> |
<listitem> |
<para>DVD title to use.</para> |
<para>Using 0 (the default) will use the longest title.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-M</option></term> |
<term><option>--mplayer</option></term> |
<listitem> |
<para>Use Mplayer to capture.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-F</option></term> |
<term><option>--ffmpeg</option></term> |
<listitem> |
<para>Use FFmpeg to capture.</para> |
<para>This is the default, except in DVD mode.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-E <replaceable>OFFSET</replaceable></option></term> |
<term><option>--end-offset <replaceable>OFFSET</replaceable></option></term> |
<listitem> |
<para>This amount of time is ignored from the end of the video.</para> |
<para>This value is not used when a explicit ending time is set (<option>--to</option>).</para> |
<para>Accepted formats:</para> |
<itemizedlist spacing="compact"> |
<listitem><para>Time stamp (&xrefinterval;)</para></listitem> |
<listitem><para>Percentage of video length.</para></listitem> |
</itemizedlist> |
<para>The default is 5.5%.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-q</option></term> |
<term><option>--quiet</option></term> |
<listitem> |
<para>Don't print progress messages just errors.</para> |
<para>Repeat to mute completely, even on error.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-d <replaceable>FEATURE</replaceable></option></term> |
<term><option>--disable <replaceable>FEATURE</replaceable></option></term> |
<listitem> |
<para>Disable some default functionality.</para> |
<para>Features that can be disabled are:</para> |
<itemizedlist spacing="compact"> |
<listitem> |
<para><replaceable>timestamps</replaceable>: use <option>-d<replaceable>t</replaceable></option> or |
<option>--disable <replaceable>timestamps</replaceable></option></para> |
</listitem> |
<listitem> |
<para><replaceable>shadows</replaceable>: use <option>-d<replaceable>s</replaceable></option> |
or <option>--disable <replaceable>shadows</replaceable></option></para> |
</listitem> |
<listitem> |
<para><replaceable>padding</replaceable>: use <option>-d<replaceable>p</replaceable></option> |
or <option>--disable <replaceable>padding</replaceable></option></para> |
</listitem> |
</itemizedlist> |
<note> |
<para>Shadows introduce some extra padding</para> |
</note> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-A</option></term> |
<term><option>--autoaspect</option></term> |
<listitem> |
<para>Try to guess aspect ratio from resolution.</para> |
<para>A rude hard-coded method is used based only on known common dimensions.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-e</option></term> |
<term><option>-e<optional><replaceable>FACTOR</replaceable></optional></option></term> |
<term><option>--extended=<optional><replaceable>FACTOR</replaceable></optional></option></term> |
<listitem> |
<para>Enables extended mode and optionally sets the extended factor.</para> |
<para>When <replaceable>FACTOR</replaceable> is omitted, 4 is used, i.e. <option>-e</option> is the same as <option>-e4</option>.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-l <replaceable>TIMESTAMP</replaceable></option></term> |
<term><option>--highlight <replaceable>TIMESTAMP</replaceable></option></term> |
<listitem> |
<para>Add the frame found at <replaceable>TIMESTAMP</replaceable> as a highlight.</para> |
<para>&xrefinterval;</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-m</option></term> |
<term><option>--manual</option></term> |
<listitem> |
<para>Manual mode.</para> |
<para>In this mode only timestamps indicated by the user are used (use in |
conjunction with <option>-S</option>).</para> |
<para>When using this option, <option>-i</option> and <option>-n</option> are ignored.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-S <replaceable>TIMESTAMP</replaceable></option></term> |
<term><option>--stamp <replaceable>TIMESTAMP</replaceable></option></term> |
<listitem> |
<para>Add the frame at <replaceable>TIMESTAMP</replaceable> to the set of captures.</para> |
<para>&xrefinterval;</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-u <replaceable>NAME</replaceable></option></term> |
<term><option>--user <replaceable>NAME</replaceable></option></term> |
<listitem> |
<para>Set the user name (included by default in the contact sheet's footer) |
to <replaceable>NAME</replaceable>.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-U</option></term> |
<term><option>--fullname</option></term> |
<listitem> |
<para>Use user's full/real name (e.g. John Smith) as set in the system's list of users |
(i.e. in <filename>/etc/passwd</filename> or through <command>getent</command>).</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-p <replaceable>PROFILE</replaceable></option></term> |
<term><option>--profile <replaceable>PROFILE</replaceable></option></term> |
<listitem> |
<para>Load profile named <replaceable>PROFILE</replaceable>.</para> |
<para>Profile names starting with ':' are reserved and have special meanings, currently:</para> |
<itemizedlist> |
<listitem><para><replaceable>:list</replaceable> &emdash; Will list all profiles found in the |
system</para></listitem> |
</itemizedlist> |
<para>If <replaceable>PROFILE</replaceable> doesn't exist, exit with error.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-C <replaceable>CONFIG</replaceable></option></term> |
<term><option>--config <replaceable>CONFIG</replaceable></option></term> |
<listitem> |
<para>Load configuration file <filename><replaceable>CONFIG</replaceable></filename></para> |
<para>Configuration <emphasis>file names</emphasis> starting with ':' are reserved |
and have special meanings, currently:</para> |
<itemizedlist> |
<listitem><para><replaceable>:pwd</replaceable> &emdash; Will try to load |
<filename>./vcs.conf</filename>.</para> |
<para>This file has been loaded by default up to vcs v1.13</para></listitem> |
</itemizedlist> |
<para>If <filename><replaceable>CONFIG</replaceable></filename> doesn't exist, exit with error.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>--generate <replaceable>config|profile</replaceable></option></term> |
<listitem> |
<para>Generate configuration or profile from the current settings and print it.</para> |
<para>All settings changed from the default, by either configuration, profiles or command-line |
options, will be included in the generated text.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-k <replaceable>MODE</replaceable></option></term> |
<term><option>--funky <replaceable>MODE</replaceable></option></term> |
<listitem> |
<para>Funky modes</para> |
<para>These are <emphasis>toy</emphasis> output modes in which the contact sheet |
gets a more informal look.</para> |
<caution> |
<para>Order <emphasis role="strong">IS IMPORTANT</emphasis>, it affects output.</para> |
<para>A bad order will produce a bad result.</para> |
</caution> |
<para>Many of these modes are random in nature so using the same mode twice |
will usually lead to very different results.</para> |
<para>Currently available <emphasis>funky modes</emphasis>:</para> |
<variablelist id="funkymodes"> |
<varlistentry> |
<term><replaceable>overlap</replaceable>: |
Use <option>-k<replaceable>o</replaceable></option> |
or <option>--funky <replaceable>overlap</replaceable></option></term> |
<listitem><para>Randomly overlap captures.</para></listitem> |
</varlistentry> |
<varlistentry> |
<term><replaceable>rotate</replaceable>: |
Use <option>-k<replaceable>r</replaceable></option> |
or <option>--funky <replaceable>rotate</replaceable></option></term> |
<listitem><para>Randomly rotate each image.</para></listitem> |
</varlistentry> |
<varlistentry> |
<term><replaceable>photoframe</replaceable>: |
Use <option>-k<replaceable>f</replaceable></option> |
or <option>--funky <replaceable>photoframe</replaceable></option></term> |
<listitem><para>Adds a photo-like white frame to each image.</para></listitem> |
</varlistentry> |
<varlistentry> |
<term><replaceable>polaroidframe</replaceable>: |
Use <option>-k<replaceable>L</replaceable></option> |
or <option>--funky <replaceable>polaroidframe</replaceable></option></term> |
<listitem><para>Adds a polaroid picture-like white frame to each image.</para></listitem> |
</varlistentry> |
<varlistentry> |
<term><replaceable>photos</replaceable>: |
Use <option>-k<replaceable>c</replaceable></option> |
or <option>--funky <replaceable>photos</replaceable></option></term> |
<listitem><para>Combination of <replaceable>rotate</replaceable>, |
<replaceable>photoframe</replaceable> and <replaceable>overlap</replaceable>.</para> |
<para>Same as <option>-kp -kr -ko</option>.</para></listitem> |
</varlistentry> |
<varlistentry> |
<term><replaceable>polaroid</replaceable>: |
Use <option>-k<replaceable>p</replaceable></option> |
or <option>--funky <replaceable>polaroid</replaceable></option></term> |
<listitem><para>Combination of <replaceable>rotate</replaceable>, |
<replaceable>polaroidframe</replaceable> and <replaceable>overlap</replaceable>.</para> |
<para>Same as <option>-kL -kr -ko</option>.</para></listitem> |
</varlistentry> |
<varlistentry> |
<term><replaceable>film</replaceable>: |
Use <option>-k<replaceable>i</replaceable></option> |
or <option>--funky <replaceable>film</replaceable></option></term> |
<listitem><para>Imitates filmstrip look.</para></listitem> |
</varlistentry> |
<varlistentry> |
<term><replaceable>random</replaceable>: |
Use <option>-k<replaceable>x</replaceable></option> |
or <option>--funky <replaceable>random</replaceable></option></term> |
<listitem><para>Randomises colours and fonts.</para></listitem> |
</varlistentry> |
</variablelist> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>--anonymous</option></term> |
<listitem> |
<para>Disable the «Preview created by <replaceable>USERNAME</replaceable>» line in the footer.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-Ij<optional>=<replaceable>FONTNAME</replaceable></optional></option></term> |
<term><option>-Ik<optional>=<replaceable>FONTNAME</replaceable></optional></option></term> |
<term><option>--nonlatin</option></term> |
<listitem> |
<para>Use an alternate font in the heading for the video file name.</para> |
<para>Required to display correctly file names in some languages with non-Latin |
alphabets (Chinese, Japanese, Hangul, Cyrillic, ...).</para> |
<para>When no font name is given, a reasonable choice will be made if possible.</para> |
<para>When <replaceable>FONTNAME</replaceable> is given, it can be either |
a font name:</para> |
<para><literal>$ <command>vcs -Ij=Sazanami-Mincho-Regular <filename>file.avi</filename></command></literal></para> |
<para>Or a font file name:</para> |
<para><literal>$ <command>vcs -Ij=<filename>/usr/share/fonts/ttf/ttf-japanese-mincho.ttf</filename> <filename>file.avi</filename></command></literal></para> |
<para>A list of available fonts and their names can be obtained with the command |
<command>identify <option>-list font</option></command></para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-O <replaceable>SETTING=VALUE</replaceable></option></term> |
<term><option>--override <replaceable>SETTING=VALUE</replaceable></option></term> |
<listitem> |
<para>Changes the value of SETTING to VALUE, |
as if it was set from a configuration file.</para> |
<para>Some settings can only be changed through configuration files or overrides, while |
others have associated command-line options.</para> |
<para><replaceable>VALUE</replaceable> can be quoted to include spaces:</para> |
<para><literal>$ <command>vcs -O SOME_SETTING="my value" <replaceable>...</replaceable></command></literal></para> |
<para><replaceable>VALUE</replaceable> can also refer to some other setting:</para> |
<para><literal>$ <command>vcs -O SOME_SETTING='$SOME_OTHER_SETTING' <replaceable>...</replaceable></command></literal></para> |
<para>See <citerefentry><refentrytitle>vcs.conf</refentrytitle> <manvolnum>5</manvolnum></citerefentry> |
and the |
<ulink url="http://p.outlyer.net/dox/vcs">online documentation</ulink> for |
a list of possible <replaceable>SETTING</replaceable>s.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-W <replaceable>WORKAROUND</replaceable></option></term> |
<listitem> |
<para>Enables one of the known workarounds for problematic files, or some tweak:</para> |
<variablelist id="workarounds"> |
<varlistentry> |
<term><option>-W<replaceable>s</replaceable></option></term> |
<listitem><para>Increase length of safe measuring (try harder).</para> |
<para>Repeat to increase further.</para></listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-W<replaceable>S</replaceable></option></term> |
<listitem><para>Scan all video, if required, to get a valid length measuring.</para></listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-W<replaceable>p</replaceable></option></term> |
<listitem><para>Increase safe measuring precision (i.e. halve the probe stepping).</para> |
<para>Repeat to increase further.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-W<replaceable>P</replaceable></option></term> |
<listitem><para>Inverse of <option>-Wp</option>.</para></listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-W<replaceable>o</replaceable></option></term> |
<listitem><para>Change FFmpeg's arguments order, might work |
with some files that fail otherwise.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-W<replaceable>c</replaceable></option></term> |
<listitem><para>Disable colour in console messages.</para></listitem> |
</varlistentry> |
</variablelist> |
</listitem> |
</varlistentry> |
</variablelist> |
</refsect1> |
<refsect1 id="debug_options"> |
<title>DEBUGGING OPTIONS</title> |
<variablelist> |
<varlistentry> |
<term><option>-R <replaceable>FILE</replaceable></option></term> |
<term><option>--randomsource <replaceable>FILE</replaceable></option></term> |
<listitem> |
<para>Use FILE as a source for "random" values.</para> |
<para>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 randomisation |
(e.g. the modes triggered by <option>-k <replaceable>photos</replaceable></option> |
and <option>-k <replaceable>polaroid</replaceable></option>). |
</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-D</option></term> |
<listitem> |
<para>Debug mode.</para> |
<para>Used to test features/integrity. It:</para> |
<itemizedlist> |
<listitem><para>Prints the input command line</para></listitem> |
<listitem><para>Sets the title to reflect the command line</para></listitem> |
<listitem><para>Does a basic test of consistency</para></listitem> |
<listitem><para>Prints a trace of all internal functions as they are called</para></listitem> |
</itemizedlist> |
<para>Repeat to just test consistency and exit</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-Z <replaceable>FEATURE</replaceable></option></term> |
<term><option>--undocumented <replaceable>FEATURE</replaceable></option></term> |
<listitem> |
<para>Testbed for experimental and debugging features. Some <replaceable>FEATURE</replaceable>s |
might be <emphasis>promoted</emphasis> in the future to actual command-line |
options.</para> |
<para><replaceable>FEATURE</replaceable>s here are rough implementations |
and have no error-handling.</para> |
<para><replaceable>FEATURE</replaceable> names can be added or removed |
in every version, silently, so don't rely on them.</para> |
<para>Useful for end-users:</para> |
<variablelist> |
<varlistentry> |
<term><replaceable>idonly</replaceable></term> |
<listitem><para>Prints the file probing/identification information and exit.</para></listitem> |
</varlistentry> |
<varlistentry> |
<term><replaceable>display</replaceable></term> |
<listitem><para>Display the generated contact sheet.</para></listitem> |
</varlistentry> |
<varlistentry> |
<term><replaceable>discard</replaceable></term> |
<listitem><para>Remove the created file on exit.</para></listitem> |
</varlistentry> |
</variablelist> |
</listitem> |
</varlistentry> |
</variablelist> |
</refsect1> |
<refsect1 id="files"> |
<title>FILES</title> |
<variablelist> |
<varlistentry> |
<term><filename>/etc/vcs.conf</filename></term> |
<listitem> |
<para>The system-wide configuration file to control the |
behaviour of <application>&package;</application>. See |
<citerefentry> |
<refentrytitle>vcs.conf</refentrytitle> |
<manvolnum>5</manvolnum> |
</citerefentry> for further details.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><filename>${HOME}/.vcs.conf</filename></term> |
<term><filename>${HOME}/.vcs/vcs.conf</filename></term> |
<listitem> |
<para>The per-user configuration file to control the |
behaviour of <application>&package;</application>. See |
<citerefentry> |
<refentrytitle>vcs.conf</refentrytitle> |
<manvolnum>5</manvolnum> |
</citerefentry> for further details.</para> |
</listitem> |
</varlistentry> |
</variablelist> |
</refsect1> |
<refsect1 id="interval_format"> |
<title>INTERVALS</title> |
<para> |
Intervals and timestamps can be specified in seconds or in a human-readable format |
that follows the syntax |
<programlisting><replaceable>HOURS</replaceable>h<replaceable>MINUTES</replaceable>m<replaceable>SECONDS</replaceable>s.<replaceable>MILLISECONDS</replaceable></programlisting> |
where each element is optional.</para> |
<para>See <ulink url="http://p.outlyer.net/dox/vcs:time_syntax" /> for more details.</para> |
<table> |
<title>Interval syntax examples</title> |
<tgroup cols="3"> |
<thead> |
<row> |
<entry>Example</entry> |
<entry>Equivalence</entry> |
<entry>Standard time format</entry> |
</row> |
</thead> |
<tbody> |
<row> |
<entry>1h30m30</entry><entry>1h30m30s.00</entry><entry>1:30:30.00</entry> |
</row> |
<row> |
<entry>30</entry><entry>0h0m30s.00</entry><entry>0:00:30.00</entry> |
</row> |
<row> |
<entry>3600</entry><entry>1h0m0s.00</entry><entry>1:00:00.00</entry> |
</row> |
</tbody> |
</tgroup> |
</table> |
</refsect1> |
<refsect1 id="environment"> |
<title>ENVIRONMENT</title> |
<variablelist> |
<varlistentry> |
<term><envar>TEMPDIR</envar></term> |
<listitem> |
<para>Fallback temporary directory when |
<filename class="directory">/dev/shm</filename> is not available. |
Due to the big size of temporary files, it is recommended to use |
a temporary directory on a fast filesystem.</para> |
</listitem> |
</varlistentry> |
</variablelist> |
</refsect1> |
<refsect1 id="diagnostics"> |
<title>DIAGNOSTICS</title> |
<para>The default verbosity level will print <package>&package;</package>' progress |
and any errors or warnings on <filename class="devicefile">stderr</filename>.</para> |
<para><option>--quiet</option> can be used to reduce verbosity.</para> |
<para>The verbosity level and where to direct <filename class="devicefile">stderr</filename> |
can be controlled through configuration files, see <citerefentry> |
<refentrytitle>vcs.conf</refentrytitle> |
<manvolnum>5</manvolnum> |
</citerefentry>. |
</para> |
<para><command>&package;</command> provides some return codes, they follow |
the semi-standardised values defined in |
<filename class="headerfile">sysexits.h</filename>:</para> |
<segmentedlist> |
<!-- Force table-style presentation instead of list with repeated |
headings. |
<http://www.docbook.org/tdg/en/html/segmentedlist.html> |
--> |
<?dbhtml list-presentation="table"?> |
<?dbfo list-presentation="table"?> |
<segtitle>Code</segtitle> |
<segtitle>Diagnostic</segtitle> |
<seglistitem> |
<seg><errorcode> 0</errorcode> (<errorcode>EX_OK</errorcode>)</seg> |
<seg>Program exited successfully.</seg> |
</seglistitem> |
<seglistitem> |
<seg><errorcode>64</errorcode> (<errorcode>EX_USAGE</errorcode>)</seg> |
<seg>Error in the arguments.</seg> |
</seglistitem> |
<seglistitem> |
<seg><errorcode>66</errorcode> (<errorcode>EX_NOINPUT</errorcode>)</seg> |
<seg>Can't access some input file or it has an incorrect format.</seg> |
</seglistitem> |
<seglistitem> |
<seg><errorcode>69</errorcode> (<errorcode>EX_UNAVAILABLE</errorcode>)</seg> |
<seg>Unsatisfied dependency.</seg> |
</seglistitem> |
<seglistitem> |
<seg><errorcode>70</errorcode> (<errorcode>EX_SOFTWARE</errorcode>)</seg> |
<seg>Internal inconsistency (bug).</seg> |
</seglistitem> |
<seglistitem> |
<seg><errorcode>73</errorcode> (<errorcode>EX_CANTCREAT</errorcode>)</seg> |
<seg>Error creating temporary or output files.</seg> |
</seglistitem> |
</segmentedlist> |
</refsect1> |
<refsect1 id="bugs"> |
<!-- Or use this section to tell about upstream BTS. --> |
<title>BUGS</title> |
<para>The upstream bug tracker system can be found |
at <ulink url="http://b.outlyer.net"/>, bugs can be reported |
through the <ulink url="http://b.outlyer.net"><acronym>BTS</acronym></ulink> |
or through e-mail addressed at <email>outlyer@gmail.com</email>.</para> |
<note> |
<para>Recent versions of <application>ImageMagick</application>, |
<application>mplayer</application> and |
<application>ffmpeg</application> should be used |
for maximum compatibility.</para> |
</note> |
<para>Most testing is done on <systemitem class="osname">Debian Sid</systemitem>, plus |
<systemitem class="osname">FreeBSD</systemitem> for <acronym>BSD</acronym> compatibility |
tests.</para> |
<para>Using <acronym>OS</acronym>es other than |
<systemitem class="osname">Debian Sid</systemitem> |
or <systemitem class="osname">FreeBSD</systemitem> |
might uncover bugs and produce incompatibilities unknown to the author. |
</para> |
</refsect1> |
<refsect1 id="see_also"> |
<title>SEE ALSO</title> |
<!-- In alpabetical order. --> |
<para><citerefentry> |
<refentrytitle>vcs.conf</refentrytitle> |
<manvolnum>5</manvolnum> |
</citerefentry>, <citerefentry> |
<refentrytitle>convert</refentrytitle> |
<manvolnum>1</manvolnum> |
</citerefentry>, <citerefentry> |
<refentrytitle>ffmpeg</refentrytitle> |
<manvolnum>1</manvolnum> |
</citerefentry>, <citerefentry> |
<refentrytitle>mplayer</refentrytitle> |
<manvolnum>1</manvolnum> |
</citerefentry></para> |
</refsect1> |
</refentry> |
<!-- vim:set ts=4 et: --> |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/branches/1.13/dist/docs/src/flatten_settings_xml.bash |
---|
0,0 → 1,33 |
#!/bin/bash |
# |
# This file inlines file included through the XIncludes system. |
# This workaround is used to work with jade (used in PDF |
# creation) since, AFAIK, it doesn't support XIncludes. |
# |
SETTINGS_XML=vcs.conf.man.xml |
IN=0 |
# Preserve leading white-space by reducing IFS to only '\n': |
IFS='\ |
' |
while read -ers line ; do |
if grep -q '<xi:include' <<<"$line" ; then |
IN=1 |
elif [[ $IN -eq 1 ]]; then |
if grep -q 'href=' <<<"$line" ; then |
toinclude=$(sed -r 's/.*href="([^"]*)".*/\1/'<<<"$line") |
docstart=$(egrep -n '^]>$' $toinclude | cut -d':' -f1) |
let 'docstart++' |
sed -n "$docstart,\$p" "$toinclude" |
fi |
fi |
if [[ $IN -ne 1 ]]; then |
echo "$line" |
fi |
if [[ $IN -eq 1 ]] && grep -q '/>' <<<"$line"; then |
IN=0 |
fi |
done <${SETTINGS_XML} |
Property changes: |
Added: svn:executable |
+* |
\ No newline at end of property |
/video-contact-sheet/branches/1.13/dist/docs/GNUmakefile |
---|
0,0 → 1,105 |
# |
# $Id$ |
# |
# This Makefile uses GNU Make syntax. |
# The distribution tarball should already include the files generated |
# here so there's usually no need to use it. |
# |
distdir:=. |
srcdir=src |
ALL=$(addprefix $(distdir)/,vcs.1 vcs.conf.5 \ |
$(addprefix vcs.man,.html .xhtml .pdf) \ |
$(addprefix vcs.conf.man,.html .xhtml .pdf) \ |
) |
INTERMEDIATE=$(addprefix $(srcdir)/, \ |
$(addsuffix .tex, vcs.man vcs.conf.man) \ |
) |
ifeq ($(shell uname),FreeBSD) |
DOCBOOK_XSL:=/usr/local/share/xsl/docbook |
endif |
DOCBOOK_XSL?=/usr/share/xml/docbook/stylesheet/docbook-xsl |
# Common part of command to convert docbook to man |
DOCBOOK_TO_MAN=xsltproc -o $(distdir)/ -nonet \ |
--xinclude \ |
-param man.charmap.use.subset "0" \ |
-param make.year.ranges "1" \ |
-param make.single.year.ranges "1" \ |
$(DOCBOOK_XSL)/manpages/docbook.xsl |
all: $(ALL) |
clean: |
$(RM) $(ALL) $(INTERMEDIATE) |
# man2html produces output closer to man and better formatted but |
# easily broken while xsltproc produces cleaner, more robust, and |
# cross-referenced output |
# sed post processing: |
# add CSS link |
# obfuscate mailto: links |
# obfuscate emails |
$(distdir)/vcs.%.xhtml: $(srcdir)/vcs.%.xml |
xsltproc -nonet \ |
--xinclude \ |
-param man.charmap.use.subset "0" \ |
-param make.year.ranges "1" \ |
-param make.single.year.ranges "1" \ |
$(DOCBOOK_XSL)/xhtml/docbook.xsl \ |
"$<" > "$@" || ( $(RM) "$@" && false ) |
sed -i \ |
-e 's!</head>!<link rel="stylesheet" type="text/css" href="man.css"/></head>!' \ |
-e 's/mailto:\([[:alnum:]]*\)@\([[:alnum:]]*\)\.\([[:alpha:]]*\)/mailto:\1%40\2%2E\3/' \ |
-e 's/\([[:alnum:]]*\)@\([[:alnum:]]*\)\.\([[:alpha:]]*\)/\1\@\2\.\3/' \ |
"$@" |
# The xml.dcl file MUST be included in this order, after options and before inputs |
$(srcdir)/vcs.conf.man.tex: $(srcdir)/vcs.conf.man.xml |
cd $(srcdir) && bash flatten_settings_xml.bash > temp.xml || ( rm temp.xml && false ) |
jade -E0 -t tex \ |
-d /usr/share/sgml/docbook/stylesheet/dsssl/modular/print/docbook.dsl \ |
-o "$@" \ |
/usr/share/sgml/declaration/xml.dcl \ |
$(srcdir)/temp.xml || ( rm $(srcdir)/temp.xml && false ) |
$(RM) $(srcdir)/temp.xml |
$(srcdir)/vcs.man.tex: $(srcdir)/vcs.man.xml |
jade -E0 -t tex \ |
-d /usr/share/sgml/docbook/stylesheet/dsssl/modular/print/docbook.dsl \ |
-o "$@" \ |
/usr/share/sgml/declaration/xml.dcl \ |
"$<" >/dev/null |
$(distdir)/vcs.%.pdf: $(srcdir)/vcs.%.tex |
pdfjadetex -output-directory $(distdir) $< |
$(RM) $(addprefix $(distdir)/vcs.$(*), .log .aux .out) |
# Check all XML files for validity |
lint: |
# XML check |
find . -type f -name '*.xml' -print0 | \ |
xargs -0 xmllint -nonet --xinclude -noout --valid |
# XHTML check |
# Use `$(MAKE) xhtml' before running `$(MAKE) $@' to |
# actually validate XHTML |
find . -type f -name '*.xhtml' -exec bash -c "echo '[ {} ]' && tidy -utf8 -eq '{}'" \; |
xhtml: $(filter %.xhtml, $(ALL)) |
$(distdir)/vcs.man.html: $(distdir)/vcs.1 |
man2html -r "$<" > "$@" |
$(distdir)/vcs.conf.man.html: $(distdir)/vcs.conf.5 |
man2html -r "$<" > "$@" |
$(distdir)/vcs.1: $(srcdir)/vcs.man.xml |
#xmlto -o `dirname $@`/ man $< |
$(DOCBOOK_TO_MAN) "$<" |
$(distdir)/vcs.conf.5: $(srcdir)/vcs.conf.man.xml |
$(DOCBOOK_TO_MAN) "$<" |
.PHONY: all clean lint xhtml |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/branches/1.13/dist/profiles/black.conf |
---|
0,0 → 1,11 |
# vcs:conf: |
# vcs:desc: White-on-Black |
# $Id$ |
bg_contact=Black |
bg_heading=$bg_contact |
bg_title=$bg_contact |
bg_sign=$bg_contact |
fg_heading=White |
fg_sign=$fg_heading |
fg_title=$fg_heading |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/branches/1.13/dist/profiles/white.conf |
---|
0,0 → 1,11 |
# vcs:conf: |
# vcs:desc: Black-on-White profile |
# $Id$ |
bg_contact=White |
bg_heading=$bg_contact |
bg_title=$bg_contact |
bg_sign=$bg_contact |
fg_heading=Black |
fg_title=$fg_heading |
fg_sign=$fg_heading |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/branches/1.13/dist/profiles/compact.conf |
---|
0,0 → 1,11 |
# vcs:conf: |
# vcs:desc: Compact mosaic, 6x12 contact sheet (small) |
# $Id: compact.conf 2331 2011-08-30 02:50:59Z toni $ |
disable_shadows=1 |
disable_timestamps=1 |
padding=0 |
captures=72 |
height=40 |
timecode_from=$TC_NUMCAPS |
columns=12 |
/video-contact-sheet/branches/1.13/dist/profiles/mosaic.conf |
---|
0,0 → 1,12 |
# vcs:conf: |
# vcs:desc: Tight, small, thumbnails |
# <http://p.outlyer.net/dox/vcs:example_configs> |
# $Id$ |
disable_timestamps=1 |
disable_shadows=1 |
height=160 |
captures=20 |
timecode_from=$TC_NUMCAPS |
padding=0 |
columns=4 |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/branches/1.13/dist/common.mk |
---|
0,0 → 1,91 |
# $Id$ |
# |
# To be included from GNUmakefile or BSDmakefile |
# To use it directly set VERSION and PACKAGER |
# e.g. make VERSION=1.x PACKAGER=Me <rule> |
# |
# Notes to self: |
# This file should follow only common/portable make syntax and commands |
# Common pitfalls: |
# - $(shell) -> GNU Make, equivalent BSD make: != |
# - install -D -> GNU only (-d is portable) |
# - $(RM) -> empty by default in BSD, set from BSDmakefile |
prefix:=/usr/local |
DESTDIR:=/ |
TGZ=vcs-$(VERSION).tar.gz |
MANDIR:=$(prefix)/share/man |
all: docs/vcs.1 docs/vcs.conf.5 vcs.spec |
# |
# Automatically detected value: |
# PACKAGER=$(PACKAGER) |
# To set it manually add it to Make's command-line like: |
# $$ $(MAKE) PACKAGER="This Is My Name" |
dist: vcs-$(VERSION).tar.gz |
vcs-$(VERSION).tar.gz: all |
$(RM) -r vcs-$(VERSION) vcs-$(VERSION).tar.gz |
mkdir vcs-$(VERSION) |
tar c --exclude='.svn' \ |
--exclude='*.swp' --exclude='*.swo' \ |
--exclude='vcs-$(VERSION)' . |\ |
tar x -C vcs-$(VERSION) |
tar zcf vcs-$(VERSION).tar.gz vcs-$(VERSION)/ |
$(RM) -r vcs-$(VERSION) |
docs/vcs.1 docs/vcs.conf.5: |
$(GMAKE) -C docs `basename $@` |
# Files installed in packages |
prepackage: examples/vcs.conf.example |
install: |
install -d $(DESTDIR)$(prefix)/bin/ |
install -m755 vcs $(DESTDIR)$(prefix)/bin/vcs |
install -d $(DESTDIR)$(prefix)/share/vcs/profiles |
install -m644 profiles/*.conf $(DESTDIR)$(prefix)/share/vcs/profiles/ |
install -d $(DESTDIR)$(MANDIR)/man1/ $(DESTDIR)$(MANDIR)/man5/ |
install -m644 docs/vcs.1 $(DESTDIR)$(MANDIR)/man1/ |
install -m644 docs/vcs.conf.5 $(DESTDIR)$(MANDIR)/man5/ |
uninstall: |
$(RM) $(DESTDIR)$(prefix)/bin/vcs |
$(RM) $(DESTDIR)$(MANDIR)/man1/vcs.1 $(DESTDIR)$(MANDIR)/man5/vcs.conf.5 |
for file in profiles/*.conf ; do \ |
$(RM) $(DESTDIR)$(prefix)/share/vcs/profiles/`basename $$file` ; \ |
done |
-rmdir -p $(DESTDIR)$(prefix)/bin |
-rmdir -p $(DESTDIR)$(prefix)/share/vcs/profiles |
-rmdir -p $(DESTDIR)$(MANDIR)/man1 $(DESTDIR)$(MANDIR)/man5 |
examples/vcs.conf.example: docs/src/vcs.conf.example |
sed -e 's/^/#/;s/^#$$//;s/^##/#/' < $< > $@ |
vcs.spec: rpm/vcs.spec.in vcs |
test "$(VERSION)" # Version (=$(VERSION)) must be defined |
@echo "[creating vcs.spec]" |
@cat $< | sed 's!@VERSION@!$(VERSION)!g' | \ |
sed 's!@PACKAGER@!$(PACKAGER)!g' > $@ |
# PKGBUILD CAN'T BE INCLUDED in the archive |
PKGBUILD: arch/PKGBUILD.in $(TGZ) vcs |
test "$(VERSION)" # Version (=$(VERSION)) must be detected |
@echo "[PKGBUILD]" |
@MD5=$(shell md5sum -b $(TGZ) | cut -d' ' -f1) ; \ |
SHA1=$(shell sha1sum -b $(TGZ) | cut -d' ' -f1) ; \ |
SHA256=$(shell sha256sum -b $(TGZ) | cut -d' ' -f1) ; \ |
cat $< | sed -e 's!@VERSION@!$(VERSION)!g' \ |
-e "s/@MD5@/$$MD5/g" \ |
-e "s/@SHA1@/$$SHA1/g" -e "s/@SHA256@/$$SHA256/g" > $@ |
clean: |
#-$(RM) examples/vcs.conf.example |
$(MAKE) -C docs clean |
distclean: clean |
-$(RM) vcs.spec PKGBUILD vcs-$(VERSION).tar.gz |
.PHONY: all install clean tgz |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/branches/1.13/dist/BSDmakefile |
---|
0,0 → 1,16 |
# |
# $Id$ |
# Makefile for BSD-make |
# |
VERSION!=sed -n '/VERSION=/s/.*"\([^"]*\)".*/\1/p' vcs |
PACKAGER!=finger -lp `echo $USER` 2>/dev/null | head -n1 | cut -d: -f3 |
.if empty($(PACKAGER)) |
PACKAGER!=getent passwd "`id -un`" | cut -d: -f5 | cut -d, -f1 |
.endif |
GMAKE?=gmake |
RM?=rm -f |
include common.mk |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/branches/1.13/dist/GNUmakefile |
---|
0,0 → 1,15 |
# |
# $Id$ |
# Makefile for GNU-make |
# |
VERSION:=$(shell sed -n '/VERSION=/s/.*"\([^"]*\)".*/\1/p' vcs) |
PACKAGER:=$(shell finger -lp `echo $USER` 2>/dev/null | head -n1 | cut -d: -f3) |
ifeq ($(PACKAGER),) |
PACKAGER:=$(shell getent passwd "`id -un`" | cut -d: -f5 | cut -d, -f1) |
endif |
GMAKE?=make |
include common.mk |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/branches/1.13/dist/examples/vcs.conf.example |
---|
0,0 → 1,159 |
# |
# vcs:conf: $Rev$ |
# Example vcs.conf file |
# This example files contains all the default values, commented out. |
# For each setting, where an equivalent command-line option exists it will be |
# listed in the comments |
# |
# Location: |
# Configuration files can be placed at /etc/vcs.conf (site-wide), |
# ~/.vcs.conf (current user, hidden file!), ./vcs.conf (current dir) or |
# ~/.vcs/vcs.conf (current user, new alternative location) |
# |
# Syntax: |
# * Comments start with '#' or ';' |
# * '#' can be used in values by writing $# |
# * Semicolons (;) can't be used in values |
# * Options are of the form name=value |
# - Options can refer to the *current* value of other options, i.e. |
# font_sign=$font_heading will assign to 'font_sign' the same value |
# as 'font_heading'. If font_heading is changed after that, font_sign |
# won't be affected. |
# - See <http://p.outlyer.net/dox/vcs:conf_files> for the full list |
# Height of individual captures. Percentage or fixed size (in pixels) |
#height=100% # option -H |
# Amount ignored from the end of the video (think of it as an anti-spoiler |
# measure). Percentage (of video duration) or time (e.g. 2m). Set to 0 |
# to disable it. |
#end_offset=5% # option -E |
######################## |
# Contact Sheet Layout # |
######################## |
#columns=2 # Number of columns in the contact sheet (option -c) |
#interval=1m # Interval between captures (option -i) |
# Number of captures. Note setting this isn't enough to use a fixed number, the |
# mode must also be changed (option 'timecode_from', see below). (option -n) |
#captures=16 |
# Mode of operation, can be either $TC_NUMCAPS or $TC_INTERVAL (the default) |
# $TC_NUMCAPS Fix the number of captures, adjust interval accordingly |
# $TC_INTERVAL Fix interval, adjust the number of captures accordingly |
# (options -i and -n set this implicitly) |
#timecode_from=$TC_INTERVAL |
# Modifier for extended mode (option -e). |
# Beware, setting it to something different from 0 automatically enables extended mode! |
#extended_factor=0 |
# Extra padding added around each capture. |
# This has *no effect* when shadows are enabled (the default). |
# Tweaking this might break alignment when using extended mode (-e) |
#padding=2 |
# Anonymous mode, set to 1 to disable the "Preview created by {value of user}" |
# line in the footer. (option --anonymous) |
#anonymous=0 |
# Profiles to load by default. (option -p) |
# *MUST* exist. |
#profiles= |
############### |
# Output file # |
############### |
# Output format. Use the file extension (e.g. 'png', 'jpg', 'jp2') |
# Any format accepted by ImageMagick can be used here (even pdf or gif, not |
# that they'll look very good though) |
# (options: -j for JPEG, -j2 for JPEG 2000) |
#format=png |
#quality=92 # Output quality for lossy formats (e.g. jpg) |
#################### |
# Cosmetic touches # |
#################### |
#user=`id -un` # User name, will be used in the contact sheet footer (option -u) |
# Used in the signature, e.g. "Preview created by {value of user}" |
#signature=Preview created by |
#disable_shadows=0 # Disable shadows by default (option -ds) |
#disable_timestamps=0 # Disable timestamps by default (option -dt) |
##################### |
# Colours and fonts # |
##################### |
# * Colours can be defined either by their name or through hexcodes |
# use the command 'identify -list color' for a list of known names |
# * Colours can use transparency, although this usually only makes sense |
# for timestamps, which are overlaid on captures. |
# * Beware when specifying colours in hex format color=#hex is ok |
# but color = #hex not, spaces can be used with color = $#hex |
# * Font sizes are expressed in points and hence might need adjustment |
# when the font is changed. |
# * Font names can be either font paths (e.g. /usr/something/ttf/font.ttf) |
# or font names as understood by ImageMagick, use the command |
# 'identify -list font' for a list of fonts known to ImageMagick |
#bg_heading=#afcd7a # Heading/meta-information section background colour |
#fg_heading=Black # Heading font colour |
#font_heading=DejaVu-Sans-Book # Heading font |
#pts_heading=14 # Font size for heading |
#bg_title=White # Background for the title (if activated with option -T) |
#fg_title=Black # Title font colour |
#font_title=$font_heading # Title font |
#bg_contact=White # Background for the contact sheet |
#bg_tstamps=#000000aa # Background for timestamps. Note the use of transparency |
#fg_tstamps=White # Timestamps font colour |
#font_tstamps=$font_heading # Timestamps font |
#pts_tstamps=14 # Font size for timestamps |
# Background for the signature, i.e. the section after the contact sheet with |
# vcs and user identification |
#bg_sign=SlateGray |
#fg_sign=Black # Font colour for the signature |
#font_sign=$font_heading # Font for the signature |
#pts_sign=10 # Font size for signature |
# Font to use for file name when the non-latin mode (--nonlatin) is enabled |
# Option: -Ik=fontname |
#nonlatin_font= # Autodetected (hopefully) |
###################### |
# Lower level tweaks # |
###################### |
# Decoder to use by default, can be either $DEC_FFMPEG (default) or |
# $DEC_MPLAYER |
# (options: -M for MPlayer, -F for FFMpeg) |
#decoder=$DEC_FFMPEG |
# Output from commands, useful to locate errors |
#stdout=/dev/null |
#stderr=/dev/null |
# Verbosity level, very verbose by default. |
# Possible values: $V_ALL (default), $V_ERROR, $V_WARN, $V_INFO, $V_NONE |
# (options: -q for quietness) |
#verbosity=$V_ALL |
# 1 disables colours in console output |
#simple_feedback=0 |
#debug=0 # When 1, enables debugging mode (option -D) |
#getopt=getopt # GNU Getopt executable name |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/branches/1.13/dist/examples/black-mosaic.conf |
---|
0,0 → 1,17 |
# vcs:profile: |
# vcs:desc: Tight sheet with white on black |
# <http://p.outlyer.net/dox/vcs:example_configs> |
# $Id: black-mosaic.conf 2323 2011-08-28 23:05:13Z toni $ |
disable_timestamps=1 |
disable_shadows=1 |
height=160 |
numcaps=20 |
timecode_from=$TC_NUMCAPS |
padding=0 |
columns=4 |
bg_contact=Black |
bg_heading=$bg_contact |
bg_sign=$bg_contact |
fg_heading=White |
fg_sign=$fg_heading |
/video-contact-sheet/branches/1.13/dist/examples/black-compact-chain.conf |
---|
0,0 → 1,6 |
# vcs:profile: |
# vcs:desc: Compact mosaic (small) with white on black |
# Exampled of "chained" profiles, profiles loaded from other profiles |
# $Id: black-compact-chain.conf 2323 2011-08-28 23:05:13Z toni $ |
profiles=black,compact |
/video-contact-sheet/branches/1.13/dist/debian/dirs |
---|
0,0 → 1,2 |
usr/bin |
usr/share |
/video-contact-sheet/branches/1.13/dist/debian/docs |
---|
0,0 → 1,2 |
examples/ |
/video-contact-sheet/branches/1.13/dist/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 |
DESTDIR:=$(CURDIR)/debian/vcs |
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) all prepackage |
#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=$(DESTDIR) 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 docs/vcs.1 docs/vcs.conf.5 |
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.13/dist/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 (>= 3.1), 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.13/dist/debian/changelog |
---|
0,0 → 1,82 |
vcs (1.12.3-upstream.1) experimental; urgency=low |
* New version. |
* debian/control: Bump minimum bash version |
-- Toni Corvera <outlyer@gmail.com> Sun, 17 Jul 2011 18:49:56 +0200 |
vcs (1.12.2-upstream.1) experimental; urgency=medium |
* New version. Medium priority due to temporary files cleanup bug. |
-- Toni Corvera <outlyer@gmail.com> Tue, 24 Aug 2010 20:48:41 +0200 |
vcs (1.12.1-upstream.1) experimental; urgency=medium |
* New version. |
-- Toni Corvera <outlyer@gmail.com> Fri, 23 Apr 2010 13:56:58 +0200 |
vcs (1.12-upstream.1) experimental; urgency=low |
* New version. |
* debian/docs: Install vcs.conf.example |
-- Toni Corvera <outlyer@gmail.com> Sat, 10 Apr 2010 00:57:17 +0200 |
vcs (1.11.2-upstream.1) experimental; urgency=low |
* New version. |
-- Toni Corvera <outlyer@gmail.com> Fri, 19 Mar 2010 00:18:51 +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.13/dist/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.13/dist/debian/compat |
---|
0,0 → 1,0 |
5 |
/video-contact-sheet/branches/1.13/dist/rpm/vcs.spec.in |
---|
0,0 → 1,117 |
# |
# $Rev$ |
# |
# 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 >= 3.1 |
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 |
make examples/vcs.conf.example |
%install |
make DESTDIR=%buildroot prefix=%{prefix} install |
%clean |
[ ${RPM_BUILD_ROOT} != "/" ] && rm -rf ${RPM_BUILD_ROOT} |
#%post |
# postinst |
#%postun |
# postuninst |
%files |
%defattr(-,root,root) |
# binary |
%{_bindir}/%{name} |
# Profiles |
%{prefix}/share/vcs/profiles/black.conf |
%{prefix}/share/vcs/profiles/mosaic.conf |
%{prefix}/share/vcs/profiles/white.conf |
# Manpages |
%{_mandir}/man1/%{name}.1.gz |
%{_mandir}/man5/%{name}.conf.5.gz |
%doc CHANGELOG |
# Config example |
%doc examples/vcs.conf.example |
%changelog |
* Sun Aug 28 2011 - outlyer (at) gmail (dot) com |
- Install additional manpage for configuration file |
* Tue Aug 24 2010 - outlyer (at) gmail (dot) com |
- Install manpage |
* Sat Apr 10 2010 - outlyer (at) gmail (dot) com |
- Added profiles and example configuration |
- Use %{prefix} |
* 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.13/dist/arch/PKGBUILD.in |
---|
0,0 → 1,39 |
# |
# $Rev$ |
# |
# Maintainer (Upstream): Toni Corvera <outlyer@gmail.com> |
# |
# Build with '$ makepkg' on the same directory as this file |
# |
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>=3.1' 'imagemagick>=6.3.5.7' 'mplayer' 'ffmpeg') |
makedepends=('bzip2') |
optdepends=('lsdvd: for DVD support' |
'perl: for DVD support') |
backup=() |
options=('docs' 'zipman') |
source=($url/files/$pkgname-$pkgver.tar.gz) |
md5sums=(@MD5@) #generate with 'makepkg -g' |
sha1sums=(@SHA1@) |
# Debian & Arch didn't agree on this on my first try (???) |
sha256sums=(@SHA256@) |
build() { |
cd $srcdir/$pkgname-$pkgver |
make prepackage |
make install DESTDIR=${pkgdir} prefix=/usr |
install -D $srcdir/$pkgname-$pkgver/examples/vcs.conf.example \ |
${pkgdir}/usr/share/doc/$pkgname/vcs.conf.example |
} |
#man page (TODO) |
# install -D -m644 $pkgname.1 ${pkgdir}/usr/share/man1/$pkgname.1 || return 1 |
# vim:set filetype=sh ts=2 et: # |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/branches/1.13/dist/README |
---|
0,0 → 1,39 |
Index |
----- |
1. Files |
2. Installation |
3. Uninstallation |
Files |
----- |
In this package: |
vcs The VCS script |
profiles/ Example profiles: |
mosaic.conf 20 small thumbnails in a 5x4 grid, no padding |
black.conf Black background and white text |
white.conf White background and black text |
examples/vcs.conf Example configuration |
Use "make examples/vcs.conf.example" to create |
a version with all options commented out. |
Installation |
------------ |
$ make install |
Will install under /usr/local |
$ make install prefix=/usr |
Will install under /usr |
Uninstallation |
-------------- |
$ make uninstall |
If you used a prefix during install use it too during uninstall |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/branches/1.13/online_man/Makefile |
---|
0,0 → 1,20 |
# |
# $Id$ |
# |
docsdir=../dist/docs |
all: man.vcs.html man.vcs.conf.html |
man.vcs.html: $(docsdir)/vcs.man.xhtml |
cp $< $@ |
man.vcs.conf.html: $(docsdir)/vcs.conf.man.xhtml |
cp $< $@ |
$(docsdir)/%: |
make -C $(docsdir) $* |
clean: |
$(RM) man.vcs.html man.vcs.conf.html |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/branches/1.13/online_man/man.css |
---|
0,0 → 1,36 |
/*$Rev: 2317 $*/ |
body { |
font-size-adjust:/*0.58*/0.5; |
font-size:12pt; |
background-color:#333; |
color:#eee; |
} |
a:link, a:active { color: #5692c4; } |
a:visited { color: #76b2e4; } |
a:hover { color: #ff6347; /*Tomato;*/ } |
.errorcode { font-family:monospace; } |
.warning, .note, .tip { |
margin-bottom:1ex; |
color:#333; |
} |
.note a:link, .note a:active, .note a:visited, |
.tip a:link, .tip a:active, .tip a:visited { |
color:navy; |
} |
.note a:hover, .tip a:hover { color: #800; } |
.warning { |
border:2px dashed #ffa500; |
background: #fc4 url("/usr/share/icons/gnome/48x48/status/dialog-warning.png") no-repeat 4px 12px; |
padding:0 1em 0 52px; |
} |
.note, .tip { |
border:2px dashed navy; |
background: #69f url("/usr/share/icons/gnome/48x48/status/dialog-information.png") no-repeat 4px 12px; |
padding:0 1em 0 52px; |
} |
.programlisting { |
background:#555; |
padding:1ex; |
width:100ex; |
border:1px solid #222; |
} |
/video-contact-sheet/branches/1.13/online_man/.htaccess |
---|
0,0 → 1,2 |
IndexIgnore man.css |
/video-contact-sheet/branches/1.13/Makefile |
---|
0,0 → 1,114 |
# |
# $Id$ |
# |
srcdir=dist |
#VER=$(shell grep VERSION= $(srcdir)/vcs | sed 's/.*"\([^"]*\)".*/\1/') |
VER=$(shell sed -n '/VERSION=/s/.*"\([^"]*\)".*/\1/p' $(srcdir)/vcs) |
all: |
@echo "-------------------------------------------------------------------------------" |
@echo " Use: " |
@echo " $$ $(MAKE) dist # to create the actual v$(VER) distribution files" |
@echo " $$ $(MAKE) manpages # to create only the manpages (in $(srcdir)/docs)" |
@echo " $$ $(MAKE) docs # to create all documentation formats (in $(srcdir)/docs)" |
@echo |
@echo " $$ $(MAKE) lint # to validate documentation sources" |
@echo " $$ $(MAKE) clean # to clean generated files" |
@echo " $$ $(MAKE) distclean # to clean generated and distribution files" |
@echo " $$ $(MAKE) uploadclean # to clean non-distribution files" |
@echo "------------------------------------------------------------------------------" |
docs: lint |
$(MAKE) -C $(srcdir)/docs all |
manpages: lint |
$(MAKE) -C $(srcdir)/docs vcs.1 vcs.conf.5 |
lint: |
$(MAKE) -C $(srcdir)/docs lint |
tgz: vcs-$(VER).tar.gz |
vcs-$(VER).tar.gz: $(srcdir)/vcs-$(VER).tar.gz |
mv $< $@ |
$(srcdir)/vcs-$(VER).tar.gz: |
make -C $(srcdir) distclean `basename $@` |
check-no-svn: |
@if [ -d .svn ]; then \ |
echo '*************************************************' ; \ |
echo '*************************************************' ; \ |
echo "** Don't release from SVN working copy **" ; \ |
echo '*************************************************' ; \ |
echo '*************************************************' ; \ |
echo ; \ |
fi |
check-rel: |
@if head -n50 vcs | grep -q 'RELEASE=0' ; then \ |
echo '*************************************************' ; \ |
echo '*************************************************' ; \ |
echo '** RELEASE is set to 0! **' ; \ |
echo '*************************************************' ; \ |
echo '*************************************************' ; \ |
echo ; \ |
fi |
dist: check-rel check-no-svn \ |
vcs-$(VER).tar.gz \ |
PKGBUILD-$(VER) \ |
$(addprefix vcs-$(VER), .gz .bz2 .bash) \ |
CHANGELOG.gz CHANGELOG \ |
rpm deb |
# This shouldn't be re-built |
devel_tools/mansrc/settings.man.inc.xml: |
cd `dirname $@` && $(MAKE) |
PKGBUILD-$(VER): vcs-$(VER).tar.gz |
cd $(srcdir) && ln -s ../vcs-$(VER).tar.gz ./ |
make -C $(srcdir) PKGBUILD |
$(RM) $(srcdir)/vcs-$(VER).tar.gz |
mv $(srcdir)/PKGBUILD $@ |
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: clean |
$(RM) PKGBUILD-$(VER) vcs-$(VER).tar.gz $(addprefix vcs-$(VER), .gz .bz2 .bash) \ |
CHANGELOG.gz CHANGELOG *.deb *.rpm |
# That's the old distclean |
uploadclean: |
$(RM) -ri vcs Makefile *.changes pkg |
deb: |
cd pkg && debuild -k0x5812006E -us -uc && 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* |
make -C $(srcdir)/docs clean |
.PHONY: all docs manpages lint clean dist distclean uploadclean \ |
check-no-svn check-rel \ |
deb rpm tgz |
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.13/vcs |
---|
0,0 → 1,0 |
link dist/vcs |
Property changes: |
Added: svn:special |
+* |
\ No newline at end of property |
/video-contact-sheet/branches/1.13 |
---|
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/tags/1.12:r413 |
Merged /video-contact-sheet/branches/1.0.100a:r364-371 |
Merged /video-contact-sheet/tags/1.12.3:r456-457 |
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/branches/1.12:r409-411 |
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.11.2:r393-406 |
Merged /video-contact-sheet/branches/1.12.1:r416-419 |
Merged /video-contact-sheet/branches/1.12.2:r422-431 |
Merged /video-contact-sheet/branches/1.12.3:r435-454 |
Merged /video-contact-sheet/tags/0.99a:r261 |
Merged /video-contact-sheet/branches/1.0.3b:r276-277 |
Merged /video-contact-sheet/branches/1.0.2b:r270-271 |
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/trunk/pkg/vcs |
---|
0,0 → 1,4645 |
#!/usr/bin/env bash |
# |
# $Rev$ $Date$ |
# |
# vcs |
# Video Contact Sheet *NIX: Generates contact sheets (previews) of videos |
# |
# Copyright (C) 2007, 2008, 2009, 2010, 2011 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>) |
# |
# The full changelog can be found at <http://p.outlyer.net/vcs/files/CHANGELOG> |
declare -r VERSION="1.12.3" |
declare -r RELEASE=1 |
set -e |
# GAWK 3.1.3 to 3.1.5 print decimals (with printf) according to locale (i.e. |
#+decimal comma separator in some locales, which is apparently POSIX correct). |
#+Older and newer versions, though, need either POSIXLY_CORRECT=1, --posix or |
#+ --use-lc-numeric to honour locale. |
# MAWK appears to always use dots. |
# Info: <http://www.gnu.org/manual/gawk/html_node/Conversion.html> |
#export POSIXLY_CORRECT=1 # Immitate behaviour in newer gawk |
export LC_NUMERIC=C |
# 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 3.1 |
if [ "${BASH_VERSINFO[0]}" -lt 3 ] || |
[ "${BASH_VERSINFO[0]}" -eq 3 -a "${BASH_VERSINFO[1]}" -lt 1 ]; then |
echo "Bash 3.1 or higher is required" >&2 |
exit 1 |
fi |
} |
# {{{ # TO-DO |
# * [[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 |
# --undocumented shoehorn -> NONE 1.12 1.13 |
# --funky -> --profile ? ?+1 |
# --end_offset -> --end-offset 1.12 (silent), 1.13 (warn) 1.14 |
# (new var names introduced in 1.12) 1.12 1.14 |
# > Usage of ./vcs.conf is also deprecated since it doesn't mesh well with profiles |
# (and it was a bad placeholder for them). Will remain to be loadable with |
# -C:pwd. |
# Loaded by default up to 1.13 (with warning in 1.13). |
# Not loaded from 1.14 onwards |
# * 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. |
# implementation |
# * Optimisations: |
# - Reduce the number of forks |
# }}} # 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 |
# * ~/.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. |
# 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 |
# 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 |
# Used for feedback |
declare -r NL=$'\012' # Newline |
declare -r TAB=$'\011' # Tab |
# }}} # 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 |
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 -r DEFAULT_END_OFFSET="5.5%" |
# 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) |
# Introduced in 1.12.2: |
# When true (1) uses $FONT_MINCHO to print the filename, otherwise the same |
#+font as the heading is used. |
# See -I and --nonlatin |
declare -i NONLATIN_FILENAMES=0 |
# Output of capturing programs is redirected here |
declare stdout=/dev/null stderr=/dev/null |
# Override-able since 1.11: |
# Height of the thumbnails, by default use same as input |
declare th_height='100%' |
declare interval=$DEFAULT_INTERVAL # Interval of captures (~length/numcaps) |
declare -i numcaps=$DEFAULT_NUMCAPS # Number of captures (~length/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 amount of time is *not* captured from the end of the video |
declare 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 |
# Holds a list of captured frames (to avoid recapturing) |
# Format <timestamp>:<filename>[NL]<timestamp>:<filename>... |
declare CAPTURES= |
# 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_BIN= |
declare FFMPEG_BIN= |
# 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 |
# Stores the names of variables overridden from the command-line, |
#+see cmdline_override() and "--override" |
declare CMDLINE_OVERRIDES="" |
# Implicit error handling (see die()), obviously inspired by C's errno |
# and PHP's die(). Functions adapted to use them allow uses like: |
# some_function arg || die |
# which will exit with the appropriate exit code and print the error message |
# (Introduced in 1.12, still being retrofitted) |
declare -i ERROR_CODE=0 # Exit code associated with the last error |
declare ERROR_MSG= # Error message associated to the last error |
# Used to buffer feedback (see buffered()) |
declare BUFFER= |
# This is only used to exit when -DD is used |
declare -i DEBUGGED=0 # It will be 1 after using -DD |
# See post_getopt_hooks() |
# Format: Priority:Command[:Arguments] (lower priority run sooner) |
declare -a POST_GETOPT_HOOKS=( ) |
declare -i DVD_MODE=0 DVD_TITLE= |
declare -a DVD_TITLES=( ) # Titles for each input DVD, filled by --dvd-title |
declare DVD_MOUNTP= # Mountpoint for DVD, detected & reset for each DVD |
declare DVD_VTS= # VTS, detected & reset for each DVD |
# }}} # Variables |
# {{{ # Configuration handling |
# New override system: This variable maps configuration variables to actual |
#+variables used in the script. Each item in the array follows the syntax: |
# <cfg variable>:<variable>:<flags>:[type constraints] Where: |
#+ cfg variable: is the name of the configuration file variable |
#+ variable: is the name of the actual variable. If empty or '=', it will be |
#+ the same as cfg variable. |
#+ flags can currently be: |
#+ "deprecated=new name": Will print a deprecation warning and suggest to use |
#+ "new name" instead |
#+ "striked": Variable is marked for removal, will print a warning about it |
#+ directing anyone needing it to contact me. Only used for variables |
#+ believed to be no longer needed |
#+ "gone": Variable removed in the current version |
#+ "alias": Marks an alias, duplicate name intended to stay |
#+ "meta": Special variable that will modify other variables (e.g. font_all |
#+ modifies all font_ variables. |
#+ "=": ignore |
#+ type constraints: a character indicating accepted values: |
# n -> Number (Natural, positive Integer) |
# p -> Number, not zero |
# t -> Timestamp |
# b -> Bool |
# h -> Positive, non-zero, number or percentage |
# f -> Float or fraction |
# D -> only $DEC_* constants |
# T -> only $TC_* constants |
# V -> only $V_* constants |
# I -> interval or percentage |
# Note during the switch to the new system most variables will remain unchanged |
# Also, the new system is case insensitive to variable names |
# TODO: Allow 'y', 'n' in booleans |
# TODO: Remove extra coherence_check()'s once constraints are implemented |
declare -ra OVERRIDE_MAP=( |
"user:::" |
"extended_factor:=:=:f" |
"stdout::" |
"stderr::" |
"DEBUG:=:=:b" |
"interval:=:=:t" |
"numcaps:=:=:p" |
"captures:numcaps:alias:n" # Alias |
"GETOPT::" # Note it makes no sense as command-line override |
"columns:cols:=:p" |
"cols:=:alias:p" # Alias |
"DISABLE_SHADOWS:=:=:b" |
"DISABLE_TIMESTAMPS:=:=:b" |
"bg_heading::" |
"bg_sign::" |
"bg_title::" |
"bg_contact::" |
"bg_tstamps::" |
"fg_heading::" |
"fg_sign::" |
"fg_tstamps::" |
"fg_title::" |
"font_heading::" |
"font_sign::" |
"font_tstamps::" |
"font_title::" |
"font_all:=:meta" # see parse_override |
"bg_all:=:meta" |
"fg_all:=:meta" |
"pts_tstamps::" |
"pts_meta::" |
"pts_sign::" |
"pts_title::" |
# Aliases for cosmetic stuff |
"bg_header:bg_heading:alias" |
"bg_signature:bg_sign:alias" |
"bg_footer:bg_sign:alias" |
"bg_sheet:bg_contact:alias" |
"fg_header:fg_heading:alias" |
"fg_signature:fg_sign:alias" |
"fg_footer:fg_sign:alias" |
"font_header:font_heading:alias" |
"font_meta:font_heading:alias" |
"font_signature:font_sign:alias" |
"font_footer:font_sign:alias" |
"pts_heading:pts_meta:alias" |
"pts_header:pts_meta:alias" |
"pts_signature:pts_sign:alias" |
"pts_footer:pts_sign:alias" |
"signature:user_signature:" |
"user_signature::deprecated=signature" |
"quality:output_quality:=:n" |
"output_quality::deprecated=quality:n" |
# TODO: These variables are evaluated to constants, would be better to |
# use some symbolic system (e.g. decoder=f instead of decoder=$DEC_FFMPEG) |
"decoder:=:=:D" |
#"capture_mode:timecode_from:alias:T" |
"timecode_from:=:=:T" |
"verbosity:=:=:V" |
"format:output_format:" |
"output_format:=:deprecated=format" |
"simple_feedback:plain_messages:=:b" |
"plain_messages::deprecated=simple_feedback:b" |
"height:th_height:=:h" |
"th_height::deprecated=height:h" |
"padding:HPAD:=:n" |
"HPAD:=:deprecated=padding:n" |
"nonlatin_font:FONT_MINCHO:" |
"FONT_MINCHO::deprecated=nonlatin_font" |
"NONLATIN_FILENAMES:=:=:b" |
"end_offset:=:=:I" # New, used to have a two-variables assignment before USR_* |
"DEFAULT_END_OFFSET:end_offset:deprecated=end_offset:I" |
# TODO TBA: |
#"noboldfeedback::" # Colour but not bold |
"shoehorned::striked" |
"safe_rename_pattern::striked" |
"MIN_LENGTH_FOR_END_OFFSET::gone:" |
) |
# Load a configuration file |
# File *MUST* exist |
# Configuration files are a series of variable=value assignment; they'll be |
#+evaluated directly so they can refer to other variables (with their value at |
#+the point of the assignment). |
# Quotes shouldn't be used (they'll be kept) |
# Since 1.12 comments can be placed in-line (i.e. after an assignment), |
# Literal '#' can be written as '$#' |
# ';' can be used to mark an end of line, anything after it will be ignored |
#+(making it equivalent to '#'), there's no way to include a literal ';' |
# load_config_file($1 = file, [$2 = type (description) = 'Settings']) |
load_config_file() { |
trace $@ |
local cfgfile=$1 |
local desc=$2 |
[[ $desc ]] || desc='Settings' |
local por= # Parsed override |
local varname= tmp= flag= bashcode= feedback= ov= |
while read line ; do # auto variable $line |
[[ ! $line =~ ^[[:space:]]*# ]] || continue # Don't feed comments |
parse_override "$line" |
por=$RESULT |
if [[ $por ]]; then |
varname=${por/% *} # Everything up to the first space... |
tmp=${por#* } # Rest of string |
flag=${tmp/% *} |
if [[ $flag == '=' ]]; then |
# No need to override... |
feedback="$varname(=)" |
else |
feedback=$varname |
fi |
ov="$ov, $feedback" |
fi |
done <$cfgfile |
[[ -z $ov ]] || inf "$desc from $cfgfile:$NL ${ov:2}" |
# No loaded overrides but errors/warnings to print, do print the file name |
if [[ ( -z $ov ) && $BUFFER ]]; then |
inf "In $cfgfile:" |
fi |
flush_buffered ' ' |
} |
# Loads the configuration files if present |
# load_config() |
load_config() { |
local -a CONFIGS=( /etc/vcs.conf ~/.vcs.conf ~/.vcs/vcs.conf ./vcs.conf ) |
for cfgfile in "${CONFIGS[@]}" ;do |
[[ -f "$cfgfile" ]] || continue |
load_config_file "$cfgfile" |
done |
} |
# Load a profile, if found; fail otherwise |
# Profiles are just configuration files that can be loaded on demand (whereas |
#+config files are always loaded) and be given a name. |
# See load_config_file() for comments on the syntax |
# Locations to be searched, in order: |
#+ 1) ~/.vcs/profiles/NAME.conf |
#+ 2) /usr/local/share/vcs/profiles/NAME.conf |
#+ 3) /usr/share/vcs/profiles/NAME.conf |
#+i.e. files in ~/.vcs/ will prevent loading files named like them in /usr |
# load_profile($1 = profile name) |
load_profile() { |
trace $@ |
local p=$1 prof= |
local -a PATHS=( ~/.vcs/profiles/ /usr/local/share/vcs/profiles/ /usr/share/vcs/profiles/ ) |
if [[ ${p:0:1} == ':' ]]; then |
case $p in |
:list) |
# No need to be efficient here... |
echo "Profiles located:" |
local path= profname= |
# 1) Find all profiles |
# 2) (sed) Extract profile file name |
# 3 & 4) (sort+uniq) Keep only first hits for each name (most precedence) |
# 5) (while) Process each name |
# 6) (for) Re-locate most precedent profile |
# 7) (echo x3) Print <name>[: description] |
# 8) (sed) Indent with ' * ' |
find "${PATHS[@]}" -name '*.conf' 2>/dev/null \ |
| sed -e 's#.*/\(.*\)\.conf#\1#' \ |
| sort | uniq \ |
| while read profname ; do |
for path in "${PATHS[@]}" ; do |
path=$path$profname.conf |
[[ -f $path ]] || continue |
echo -n "$profname" |
# [ ] here contains <space><tab>. Mawk doesn't understand |
# [[:space:]] |
echo -n $(awk 'sub(/#[ ]*vcs:desc:[ ]*/, ": ")' "$path") |
echo |
break |
done |
done \ |
| sed 's/^/ * /' |
exit 0 |
;; |
*) |
ERROR_MSG="Profiles starting with ':' are reserved.$NL"\ |
" Use ':list' to list available profiles." |
ERROR_CODE=$EX_USAGE |
return $ERROR_CODE |
esac |
fi |
for prof in "${PATHS[@]}" ; do |
prof="$prof$p.conf" |
[[ -f $prof ]] || continue |
load_config_file "$prof" 'Profile' |
return 0 |
done |
ERROR_MSG="Profile '$p' not found" |
ERROR_CODE=$EX_USAGE |
return $ERROR_CODE |
} |
# Check value for an overrideable variable against the allowed values |
# check_constraint($1 = variable name, $2 = value [, $3 = public_name]) |
# where public_name is the name to be used for error messages |
check_constraint() { |
local n=$1 v=$2 p=$3 |
# Get constraint |
local map=$(echo "${OVERRIDE_MAP[*]}" | stonl | egrep -i "^$n:") |
[[ $map ]] || return 0 |
local ct=$(cut -d':' -f4 <<<"$map") |
[[ $ct ]] || return 0 |
local checkfn= domain= |
case $ct in |
n) checkfn=is_number ; domain=numbers ;; |
p) checkfn=is_positive ; domain='numbers greater than zero' ;; |
t) checkfn=is_interval ; domain=intervals ;; |
b) checkfn=is_bool ; domain='boolean values (0 or 1)' ;; |
h) checkfn=is_pos_or_percent ; domain='positive numbers or percentages' ;; |
f) checkfn=is_float_or_frac ; domain='positive numbers or fractions' ;; |
D) checkfn=is_decoder ; domain='$DEC_FFMPEG or $DEC_MPLAYER' ;; |
T) checkfn=is_tcfrom ; domain='$TC_INTERVAL or $TC_INTERVAL' ;; |
V) checkfn=is_vlevel ; domain='verbosity levels ($V_.*)' ;; |
I) checkfn=is_interv_or_percent ; domain='intervals or percentages' ;; |
esac |
if [[ $checkfn ]] && ! $checkfn "$v" ; then |
[[ $p ]] || p=$n |
ERROR_MSG="Illegal value for '$p', only $domain are accepted" |
ERROR_CODE=$EX_USAGE |
return $ERROR_CODE |
fi |
return 0 |
} |
# Parse an override and set its value. |
# Input should be a var=value assignment. Also sets USR_<variable>. |
# The global variable $RESULT is set with the format: |
# <variable name> <flag> where |
# * variable name: is the name of the variable to be overridden |
# * flag: is a character indicating the status: "+" for a possible override, |
# "=" for an override that already has the same value |
# Warnings and errors are buffered |
# This function always returns true |
# parse_override($1 = override assignment) |
parse_override() { |
trace $@ |
local o="$1" |
RESULT='' |
# bash 3.1 and 3.2 handle quoted eres differently, using a variable fixes this |
local ERE="^[[:space:]]*[[:alpha:]_][[:alnum:]_]*[[:space:]]*=.*" |
if [[ ! $o =~ $ERE ]] ; then |
return |
fi |
local varname=$(echo "${o/=*}" | sed 's/[[:space:]]//g') # Trim var name |
local lcvarname=$(echo "$varname" | tr '[A-Z]' '[a-z]') |
local mapping=$(echo "${OVERRIDE_MAP[*]}" | stonl | egrep -i "^$lcvarname:") |
[[ $mapping ]] || return 0 |
local varval=${o#*=} # No trimming here (yet) |
# 1) Trim from ; (if present) to finish |
# 2) Trim from # (comments) not "escaped" like '$#' |
# 3) Replace '$#' with '#' |
# 4) Trim whitespace on both ends |
varval=$(sed -e 's/;.*//' -e 's/\([^$]\)#.*/\1/g' -e 's/\$#/#/g' \ |
-e 's/^[[:space:]]*//;s/[[:space:]]*$//' <<<"$varval") |
# Is varval empty? |
[[ $varval ]] || return 0 |
local mvar=$(cut -d':' -f1 <<<"$mapping") |
local ivar=$(cut -d':' -f2 <<<"$mapping") |
local flags=$(cut -d':' -f3 <<<"$mapping") |
local constraints=$(cut -d':' -f4 <<<"$mapping") |
{ [[ $ivar && ( $ivar != '=' ) ]] ; } || ivar="$mvar" |
# Note using "\$(echo $varval)" would allow a more flexible syntax but |
#+enforce special handling of escaping, which with the currently available |
#+settings is not worth the effort |
# Resolve symbolic variables to check their actual value |
eval varval="\"$varval\"" 2>/dev/null || { # Hide eval's errors |
buffered error "Syntax error: '$o'" |
return 0 |
} |
[[ $varval ]] || return 0 # If empty value, ignore it |
local evcode='' |
if [[ $flags && ( $flags != '=' ) && ( $flags != 'alias' ) ]]; then |
local ERE='^deprecated=' |
if [[ $flags =~ $ERE ]]; then |
local new=$(echo "$flags" | sed 's/^deprecated=//') |
buffered warn "Variable '$varname' will be removed in the future,$NL please use '$new' instead." |
else |
case "$flags" in |
gone) |
buffered error "Variable '$varname' has been removed." |
return 0 |
;; |
striked) |
buffered error "Variable '$varname' is scheduled to be removed in the next release." |
buffered error " Please contact the author if you absolutely need it." |
;; |
meta) |
apply_meta_override "$varname" "$varval" |
RESULT="$varname +" |
return 0; |
;; |
*) return 0 ;; |
esac |
fi |
fi |
[[ -z $constraints ]] || check_constraint $ivar "$varval" $varname || { |
buffered error "$ERROR_MSG" |
return 0 |
} |
eval local curvarval='$'"$ivar" retflag='+' |
if [[ $curvarval == "$varval" ]]; then |
retflag='=' |
else |
if [[ $constraints == 't' ]]; then |
varval=$(get_interval "$varval") |
fi |
# Escape single quotes, since it will be single-quoted: |
varval=${varval//\'/\'\\\'\'} # <<'>> => <<'\''>> |
evcode="$ivar='$varval'; USR_$ivar='$varval'" |
eval "$evcode" |
fi |
# varname, as found in the config file |
RESULT="$varname $retflag" |
} |
# Handle meta configuration variables, variables that, when set, modify the |
# value of (various) others |
# apply_meta_override($1 = actual variable name, $2 = value) |
apply_meta_override() { |
case "$1" in |
font_all) |
buffered inf "font_all => font_heading, font_sign, font_title, font_tstamps" |
parse_override "font_heading=$2" |
parse_override "font_sign=$2" |
parse_override "font_title=$2" |
parse_override "font_tstamps=$2" |
;; |
fg_all) |
buffered inf "fg_all => fg_heading, fg_sign, fg_title, fg_tstamps" |
parse_override "fg_heading=$2" |
parse_override "fg_sign=$2" |
parse_override "fg_tstamps=$2" |
parse_override "fg_title=$2" |
;; |
bg_all) |
buffered inf "bg_all => bg_heading, bg_contact, bg_sign, bg_title, bg_tstamps" |
parse_override "bg_heading=$2" |
parse_override "bg_contact=$2" |
parse_override "bg_sign=$2" |
parse_override "bg_title=$2" |
parse_override "bg_tstamps=$2" |
;; |
esac |
} |
# Do an override from the command line |
# cmdline_override($1 = override assignment) |
#+e.g. cmdline_override 'decoder=$DEC_FFMPEG' |
cmdline_override() { |
trace $@ |
parse_override "$1" |
local r=$RESULT |
[[ $r ]] || return 0 |
local varname=${r/% *} # See load_config() |
local tmp=${r#* } |
local flag=${tmp/% *} |
if [[ $flag == '=' ]]; then |
varname="$varname(=)" |
fi |
CMDLINE_OVERRIDES="$CMDLINE_OVERRIDES, $varname" |
} |
# Call any pending commands required by the command-line arguments |
# This is used to defer some calls and to flush buffers |
post_getopt_hooks() { |
local cback= EX=0 |
local funcs=$(echo "${POST_GETOPT_HOOKS[*]}" | stonl | sort -n | uniq |\ |
cut -d':' -f2- ) |
for cback in $funcs ; do |
local fn=${cback/:*} |
local arg=${cback/*:} |
[[ $arg != $cback ]] || arg='' |
$fn $arg |
done |
} |
# Print the list of command-line overrides |
cmdline_overrides_flush() { |
trace $@ |
if [[ $CMDLINE_OVERRIDES ]]; then |
inf "Overridden settings from command line:$NL ${CMDLINE_OVERRIDES:2}" |
fi |
if [[ $BUFFER ]]; then |
[[ $CMDLINE_OVERRIDES ]] || warn "In command-line overrides:" |
flush_buffered ' ' |
fi |
} |
# }}} # Configuration handling |
# {{{ # Convenience functions |
#### {{{{ # Type checkers: Return true if input is of a certain type |
#### All take exactly one argument and print nothing |
## Natural number |
is_number() { |
# With '[[...]]', strings '-eq'uals 0, test if it's actually 0 |
#+or otherwise a valid number. Must return 1 on error. |
[[ ( $1 == '0' ) || ( $1 -gt 0 ) ]] 2>/dev/null || return 1 |
} |
## Number > 0 |
is_positive() { is_number "$1" && [[ $1 -gt 0 ]]; } |
## Bool (0 or 1) |
is_bool() { [[ ($1 == '0') || ($1 == '1') ]] 2>/dev/null ; } |
## Float (XX.YY; XX.; ;.YY) (.24=0.24) |
is_float() { local P='^([0-9]+\.?([0-9])?+|(\.[0-9]+))$' ; [[ $1 =~ $P ]] ; } |
## Percentage (xx% or xx.yy%) |
is_percentage() { |
local P='^([0-9]+\.?([0-9])?+|(\.[0-9]+))%$' |
[[ $1 =~ $P ]] |
} |
## Interval |
is_interval() { |
local i=$(get_interval "$1" || true) |
[[ $i ]] && fptest $i -gt 0 |
} |
## Interval or percentage |
is_interv_or_percent() { |
is_percentage "$1" || is_interval "$1" |
} |
## Positive or percentage |
is_pos_or_percent() { |
is_number "$1" && [[ $1 -gt 0 ]] || is_percentage "$1" |
} |
## Float (>=0) or fraction |
is_float_or_frac() { |
{ is_fraction "$1" || is_float "$1" ; } && fptest "$1" -ge 0 |
} |
## Fraction, strictly (X/Y, but no X; Y!=0) |
is_fraction() { |
local P='^[0-9]+/[0-9]+$' |
[[ $1 =~ $P ]] && { |
local d=${1#*/} # .../X |
[[ $d -ne 0 ]] |
} |
} |
## Decoder ($DEC_* constants) |
is_decoder() { [[ $1 == $DEC_FFMPEG || $1 == $DEC_MPLAYER ]]; } |
## Time calculation source ($TC_* constants) |
is_tcfrom() { [[ $1 == $TC_INTERVAL || $1 == $TC_NUMCAPS ]]; } |
### Verbosity level ($V_* constants) |
is_vlevel() { |
is_number "$1" && \ |
[[ ($1 -eq $V_ALL) || ($1 -eq $V_NONE) || ($1 -eq $V_ERROR) || \ |
($1 -eq $V_WARN) || ($1 -eq $V_INFO) ]] |
} |
#### }}}} # End of type checkers |
# 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 |
(( n += ( d - r ) , 1 )) |
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 "[[ \"'$1' '$2' '$3'\" ]] && false" && return $EX_SOFTWARE |
esac |
# Empty operands |
if [[ ( -z $1 ) || ( -z $3 ) ]]; then |
assert "[[ \"'$1'\" && \"'$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() { |
local ERE='\.' |
[[ $1 =~ $ERE ]] || { 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 old code (default bc -l) |
# TODO: gawk and mawk differ in how to handle stuff like div by zero: |
# gawk errors, mawk prints inf. Should somehow handle inf and nan |
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() { |
if [[ $1 ]]; then |
awk '{gsub(" ", "\n");print}' <<<"$1" | egrep -v '^$' |
else |
awk '{gsub(" ", "\n");print}' | egrep -v '^$' |
fi |
} |
# Converts newlines to spaces portably |
# nltos([$1 = string]) |
nltos() { |
if [[ $1 ]]; then |
awk '{printf "%s ",$0}' <<<"$1" | sed 's/ *//' |
else |
awk '{printf "%s ",$0}' | sed 's/ *//' |
fi |
} |
# 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 |
expr match "$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 |
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() { |
awkexf "sqrt($1 ^ 2 + $2 ^ 2)" |
} |
# Get a percentage |
# percent($1 = value, $2 = percentage) |
percent() { |
local pc=${2/%%/} # BASH %% == RE %$ |
awkexf "($1 * $pc) / 100" |
} |
# Rounded percentage |
# rpercent($1 = value, $2 = percentage) |
rpercent() { |
local pc=${2/%%/} |
awkex "int( ($1 * $pc) / 100 + 0.5 )" |
} |
# 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 $@ |
# eval it even if it's numeric to strip leading zeroes. Note the quoting |
if is_number "$1" ; then awkexf "\"$1\"" ; return 0 ; fi |
local s=$(tolower "$1") t r n |
# Only allowed characters |
local ERE='^[0-9smhSMH.]+$' |
[[ $s =~ $ERE ]] || return $EX_USAGE |
# Two consecutive dots are no longer accepted |
# ([.] required for bash 3.1 + bash 3.2 compat) |
[[ ! $s =~ [.][.] ]] || return $EX_USAGE |
# 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 |
local ERE='\.' |
[[ $secs && ( $secs =~ $ERE ) ]] && 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=$(awkexf "$t" 2>/dev/null) |
# Negative and empty intervals |
assert "[[ '$r' && '$t' ]]" |
assert "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() { |
# Must allow non-numbers |
local l; (( l = $1 - ${#2} , 1 )) |
[[ $l -le 0 ]] || printf "%0${l}d" '0' |
echo $2 |
} |
# Get Image Width |
# imw($1 = file) |
imw() { |
identify -format '%w' "$1" |
} |
# Get Image Height |
# imh($1 = file) |
imh() { |
identify -format '%h' "$1" |
} |
# Get the line height used for a certain font and size |
# line_height($1 = font, $2 = size) |
line_height() { |
# Create a small image to see how tall are characters. In my tests, no |
#+matter which character is used it's always the same height. |
convert -font "$1" -pointsize "$2" \ |
label:'F' png:- | identify -format '%h' - |
} |
# 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 "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 soon this won't be needed |
sanitise_rename_pattern() { |
# No quoting! => Regex |
if [[ ! $safe_rename_pattern =~ %e || ! $safe_rename_pattern =~ %N || \ |
! $safe_rename_pattern =~ %b ]]; then |
safe_rename_pattern=$DEFAULT_SAFE_REN_PATT |
fi |
# Hashmarks will break the regex used in safe_rename() |
if [[ $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 $@ |
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 -e "s#%b#$b#g" -e "s#%N#$n#g" -e "s#%e#$ext#g" <<<"$safe_rename_pattern") |
(( 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($1 = image file) |
get_dvd_image_mountpoint() { |
if is_linux ; then |
local lodev=$(/sbin/losetup -j "$1" | 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_BIN=$(type -pf mplayer) || true |
FFMPEG_BIN=$(type -pf ffmpeg) || true |
# Test we can actually use FFmpeg |
[[ $FFMPEG_BIN ]] && { |
# 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_BIN" -formats 2>/dev/null | grep -q 'EV.* png' && \ |
! "$FFMPEG_BIN" -codecs 2>/dev/null | grep -q 'EV.* png' ; then |
warn "FFmpeg can't output to png, won't be able to use it." |
FFMPEG_BIN='' |
nopng=1 |
fi |
} |
# Same for Mplayer |
[[ $MPLAYER_BIN ]] && { |
if ! "$MPLAYER_BIN" -vo help 2>&1 | grep -q 'png' ; then |
warn "MPlayer can't output to png, won't be able to use it." |
MPLAYER_BIN='' |
nopng=1 |
fi |
} |
[[ ( -n $MPLAYER_BIN ) || ( -n $FFMPEG_BIN ) ]] || { |
local pngwarn= |
[[ $nopng -eq 1 ]] && pngwarn=', with PNG output support,' |
error "mplayer and/or ffmpeg$pngwarn are required!" |
(( retval++ ,1 )) |
} |
if [[ ( $decoder -eq $DEC_FFMPEG ) && ( -z $FFMPEG_BIN ) ]]; then |
decoder=$DEC_MPLAYER |
elif [[ ( $decoder -eq $DEC_MPLAYER ) && ( -z $MPLAYER_BIN ) ]]; 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!" |
(( 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." ; |
(( retval++ ,1 )) |
fi |
else |
error "Failed to check ImageMagick version." |
(( 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 |
} |
# |
# Buffering of feedback, usage: |
# buffered warn "my warning" |
# ... |
# flush_buffered |
# buffered($1 = feedback function, $2 = arguments) |
buffered() { |
local grab=$( $1 "$2" 2>&1 ) |
BUFFER=$BUFFER$grab$NL |
} |
# |
# Print buffered feedback to stderr |
# flush_buffered([$1 = indentation]) |
flush_buffered() { |
[[ ${BUFFER[*]} ]] || return 0 |
echo "$BUFFER" | sed -e '$d' -e "s/^/$1/g" >&2 # sed: delete last line, indent with $1 |
BUFFER='' |
} |
# |
# trace(... = function arguments) |
trace() { |
[[ $DEBUG -eq 1 ]] || return 0 |
echo "[TRACE]: $(caller 0 | cut -d' ' -f2) $*" >&2 |
} |
# Print an error message and exit |
# die([$1 = message [, $2 = exit_code]]) |
# If no message is provided, use $ERROR_MSG |
# If no exit_code is provided, use $ERROR_CODE |
die() { |
local m=$1 ec=$2 |
[[ $ec ]] || ec=$ERROR_CODE |
[[ $ec ]] || ec=1 |
[[ $m ]] || m=$ERROR_MSG |
error "$m" |
exit $ec |
} |
# |
# 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 && 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 >/dev/null |
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 |
# Note: Use single quotes for globals, no need to expand in release |
# assert(... = code) |
assert() { |
[[ $RELEASE -eq 0 ]] || { |
function assert { :; } # Redefine to avoid check |
} |
local c=$(caller 0) # <num> <func> <file> |
c=${c% *} # <num> <func> |
local LIN=${c% *} FN=${c#* } |
eval "$@" || { |
error "Internal error at $FN:$LIN: $@" |
local cal=$(caller 1) |
[[ $level ]] && error " Stack trace:" |
local level=2 |
while [[ $cal ]]; do |
cal=${cal% *} |
error " ${cal#* }:${cal% *}" |
cal=$(caller $level) |
(( level++ )) |
done |
exit $EX_SOFTWARE |
} |
} |
# Conditional assertion |
# assert_if($1 = condition, $2 = assert if $1 true) |
assert_if() { |
[[ $RELEASE -eq 1 ]] && return |
if eval "$1" ; then |
assert "$2" |
fi |
} |
# }}} # Convenience functions |
# {{{ # Core functionality |
# Creates a new temporary directory |
# create_temp_dir() |
create_temp_dir() { |
trace $@ |
[[ -z $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 $@ |
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 $@ |
local mode=f lineno |
if [[ $mode == 'f' ]]; 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 $@ |
local st=0 end=${VID[$LEN]} tcfrom=$1 tcint=$2 tcnumcaps=$3 eo=0 |
local eff_eo= # Effective end_offset (for percentages) |
# 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 is_percentage $end_offset ; then |
eff_eo=$(percent $end $end_offset) |
else |
eff_eo=$(get_interval "$end_offset") |
fi |
if fptest $totime -le 0 ; then # If no totime is set, use end_offset |
eo=$eff_eo |
local runlen=$(awkexf "$end - $st") |
if fptest "($end-$eo-$st)" -le 0 ; then |
if fptest "$eo" -gt 0 && [[ -z $USR_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") |
local last= |
while fptest $stamp -le "$bound"; do |
assert fptest $stamp -ge 0 |
LTC=( "${LTC[@]}" "$stamp" ) |
last=$stamp |
stamp=$(keepdecimals_lower $(awkexf "$stamp + $inc") 3) |
done |
inf "Capturing in range [$(pretty_stamp $inc)-$(pretty_stamp $last)]. Total length: $(pretty_stamp ${VID[$LEN]})" |
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 $@ |
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 |
# capture($1 = filename, $2 = second, $3 = output file) |
capture() { |
trace $@ |
local f=$1 stamp=$2 ofile=$3 |
# globals: $shoehorned $decoder |
# Avoid recapturing if timestamp is already captured. |
# The extended set includes the standard set so when using the extended mode |
#+this will avoid some captures, specially with mplayer, since it doesn't |
#+have ms precission |
local key= |
# Normalise key values' decimals |
if [[ $decoder -eq $DEC_MPLAYER ]]; then |
key=$(awkex "int($stamp)") |
else |
key=$(awkex $stamp) |
fi |
local cached=$(grep "^$key:" <<<"$CAPTURES") |
if [[ $cached ]]; then |
cp "${cached#*:}" "$ofile" # TODO: Is 'cp -s' safe? |
return $? |
fi |
assert '[[ $decoder -eq $DEC_MPLAYER || $decoder -eq $DEC_FFMPEG ]]' |
if [[ $decoder -eq $DEC_MPLAYER ]]; then |
mplayer_capture "$f" "$ofile" "$stamp" |
elif [[ $decoder -eq $DEC_FFMPEG ]]; then |
# FIXME: ffmpeg can put the temporary file anywhere |
ffmpeg_capture "$f" "$ofile" "$stamp" |
fi || { |
[[ $decoder -ne $DEC_MPLAYER ]] || stamp=${stamp/%.*} |
error "Failed to capture frame at $(pretty_stamp $stamp) (${stamp}s)" |
return $EX_SOFTWARE |
} |
CAPTURES="$CAPTURES$key:$ofile$NL" |
} |
# Applies all individual vidcap filters |
# filter_vidcap($1 = filename, $2 = timestamp, $3 = width, $4 = height, $5 = context, $6 = index[1..]) |
filter_vidcap() { |
trace $@ |
# 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 $@ |
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 $@ |
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 $@ |
# 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 $@ |
# 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 $@ |
# 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 $@ |
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' " |
(( 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 $@ |
$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 $@ |
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 $@ |
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 $@ |
# 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 ! mplayer_capture "$f" "IGNOREME" "$ts" "-vf scale=96:96"; then |
ret=1 |
fi |
elif [[ $decoder -eq $DEC_FFMPEG ]]; then |
tempfile=$(new_temp_file '-safelen.png') |
if ! ffmpeg_capture "$f" "$tempfile" "$ts" "-s 96x96"; then |
ret=1 |
fi |
else |
assert 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 $@ |
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 $(awkexf "$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" ;; |
VP80) vcodec="VP8" ;; |
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" ;; |
# 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" ;; |
vp8) mpid="VP80" ;; |
# 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= |
local ERE='[ -]' |
if [[ $acid =~ $ERE ]]; 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" ;; |
# Other versions of R.A. listed at Wikipedia/RealAudio |
sipr) acodec="RealAudio SIPR" ;; # RA 4/5 |
cook) acodec="RealAudio Cook" ;; # RA 6 |
*) # 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 |
# {{{{ # Mplayer support |
# Try to identify video properties using mplayer |
# Fills $MPLAYER_CACHE with the relevant output and $MPLAYER_ID with |
# the actual values. See identify_video() |
# mplayer_identify($1 = file) |
mplayer_identify() { |
trace $@ |
[[ $MPLAYER_BIN ]] || 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_BIN" -benchmark -ao null -vo null -identify -frames 0 \ |
-quiet "$f" 2>"$stderr" | grep ^ID) |
else |
MPLAYER_CACHE=$("$MPLAYER_BIN" -benchmark -ao null -vo null -identify -frames 0 \ |
-quiet -dvd-device "$f" dvd://$DVD_TITLE \ |
2>"$stderr" | 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 |
MPLAYER_ID=("${mi[@]}") |
} |
# Capture a frame with mplayer |
# mplayer_capture($1 = inputfile, $2 = output file, $3 = timestamp[, $4 = extra opts]) |
mplayer_capture() { |
trace $@ |
# Note mplayer CAN'T set the output filename, newer mplayer can set output |
#+dir though. |
local f="$1" |
local cap=00000005.png o=$2 |
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_BIN" -sws 9 -ao null -benchmark -vo "png:z=0" -quiet \ |
-frames 5 -ss "$ts" $shoehorned -dvd-device "$f" \ |
$4 "dvd://$DVD_TITLE" |
else |
"$MPLAYER_BIN" -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 $cap ) && ( '0' != "$(du "$cap" | cut -f1)" ) ]] && { |
mvq "$cap" "$o" |
} |
} |
# }}}} # Mplayer support |
# {{{{ # FFmpeg support |
# Try to identify video properties using ffmpeg |
# Fills $FFMPEG_CACHE with the relevant output and $FFMPEG_ID with |
# the actual values. See identify_video() |
# mplayer_identify($1 = file) |
ffmpeg_identify() { |
trace $@ |
[[ $FFMPEG_BIN ]] || return |
local f="$1" |
# DVD Devices *MUST* be mounted for the identifying process to even start |
assert '[[ ( $DVD_MODE -eq 0 ) || ( $DVD_MOUNTP ) ]]' |
if [[ $DVD_MODE -eq 1 ]]; then |
local vfile="$DVD_MOUNTP/VIDEO_TS/VTS_${DVD_VTS}_0.VOB" |
if [[ ! -r $vfile ]]; then |
error "Failed to locate mounted DVD. Detection will be less accurate." |
return 0 # We can continue anyway |
fi |
f="$vfile" |
fi |
# XXX: FFmpeg detects mpeg1video in DVDs?? |
local fi=( ) vs= as= obs= vsid= |
# FFmpeg is relatively new, introduced in 1.0.99 so it needs more testing |
FFMPEG_CACHE=$("$FFMPEG_BIN" -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 -e 's/:/h/' -e '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 0 ]] || fi[$LEN]='' |
fi[$VCNAME]=$(get_vcodec_name $(translate_ffmpeg_vcodec_id "${fi[$VCODEC]}")) |
fi[$ACNAME]=$(get_acodec_name $(translate_ffmpeg_acodec_id "${fi[$ACODEC]}")) |
FFMPEG_ID=("${fi[@]}") |
} |
# Capture a frame with ffmpeg |
# ffmpeg_capture($1 = inputfile, $2 = outputfile, $3 = timestamp[, $4 = extra opts]) |
ffmpeg_capture() { |
trace $@ |
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_BIN" -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)" ) ]] |
} |
# }}}} # FFmpeg support |
# 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 $@ |
local RET_NOLEN=3 RET_NODIM=4 |
[[ $MPLAYER_BIN ]] && mplayer_identify "$1" |
# ffmpeg_identify in DVD mode only works when the DVD is mounted: |
[[ ( $DVD_MODE -eq 0 ) && ( $FFMPEG_BIN ) ]] && ffmpeg_identify "$1" |
[[ ( $DVD_MODE -eq 1 ) && ( $FFMPEG_BIN ) && ( $DVD_MOUNTP ) ]] && ffmpeg_identify "$1" |
# Fail early if none detected length |
[[ ( -z ${MPLAYER_ID[$LEN]} ) && ( -z ${FFMPEG_ID[$LEN]} ) ]] && return $RET_NOLEN |
assert '[[ $MPLAYER_BIN || $FFMPEG_BIN ]]' |
# Classic mode, use both mplayer and ffmpeg when available |
if [[ $MPLAYER_BIN && $FFMPEG_BIN ]]; then |
# By default take mplayer's values |
VID=("${MPLAYER_ID[@]}") |
# 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 ${MPLAYER_ID[$FPS]} ]] && VID[$FPS]=${FFMPEG_ID[$FPS]} |
[[ ( -n ${MPLAYER_ID[$FPS]} ) && ( -n ${FFMPEG_ID[$FPS]} ) ]] && { |
# Trust ffmpeg if it has three decimals OR if mplayer is probably-wrong |
local ffps=${FFMPEG_ID[$FPS]} |
local ERE='\.[0-9][0-9][0-9]' |
if [[ $ffps =~ $ERE ]]; then |
VID[$FPS]=$ffps |
elif fptest "${MPLAYER_ID[$FPS]}" -gt 500; then |
VID[$FPS]=$ffps |
fi |
} |
# It doesn't appear to need any workarounds for num. channels either |
[[ ${FFMPEG_ID[$CHANS]} ]] && VID[$CHANS]=${FFMPEG_ID[$CHANS]} |
[[ ${FFMPEG_ID[$ASPECT]} ]] && VID[$ASPECT]=${FFMPEG_ID[$ASPECT]} |
# There's a huge inconsistency with some files, both mplayer vs ffmpeg |
# same application on different OSes |
local fflen=${FFMPEG_ID[$LEN]} mplen=${MPLAYER_ID[$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 $(awkexf "($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_BIN ]]; then |
# Must do with mplayer only... |
VID=("${MPLAYER_ID[@]}") |
# 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_BIN ]]; then |
# Must do with mplayer only... |
VID=("${FFMPEG_ID[@]}") |
# So far I know of no weird results. Yet. |
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_BIN ]]; then |
# FPS at least with two decimals |
if [[ $(awkex "int(${VID[$FPS]})") == ${VID[$FPS]} ]]; then |
VID[$FPS]="${VID[$FPS]}.00" |
fi |
fi |
local mfps="${MPLAYER_ID[$FPS]}" |
if [[ ( $QUIRKS -eq 0 ) && ( -n $MPLAYER_BIN ) ]] && 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 $@ |
[[ $MPLAYER_BIN ]] && echo "Mplayer: $MPLAYER_BIN" |
[[ $FFMPEG_BIN ]] && echo "FFmpeg: $FFMPEG_BIN" |
[[ $MPLAYER_BIN ]] && cat <<-EODUMP |
=========== Mplayer Identification =========== |
Length: $(pretty_stamp ${MPLAYER_ID[$LEN]}) |
Video |
Codec: ${MPLAYER_ID[$VCODEC]} (${MPLAYER_ID[$VCNAME]}) |
Dimensions: ${MPLAYER_ID[$W]}x${MPLAYER_ID[$H]} |
FPS: ${MPLAYER_ID[$FPS]} |
Aspect: ${MPLAYER_ID[$ASPECT]} |
Audio |
Codec: ${MPLAYER_ID[$ACODEC]} (${MPLAYER_ID[$ACNAME]}) |
Channels: ${MPLAYER_ID[$CHANS]} |
============================================== |
EODUMP |
local ffl="${FFMPEG_ID[$LEN]}" |
[[ $ffl ]] && ffl=$(pretty_stamp "$ffl") |
if [[ ( -z $ffl ) && ( $DVD_MODE -eq 1 ) ]]; then |
ffl="(unavailable in DVD mode)" |
fi |
[[ $FFMPEG_BIN ]] && cat <<-EODUMP |
=========== FFmpeg Identification =========== |
Length: $ffl |
Video |
Codec: ${FFMPEG_ID[$VCODEC]} (${FFMPEG_ID[$VCNAME]}) |
Dimensions: ${FFMPEG_ID[$W]}x${FFMPEG_ID[$H]} |
FPS: ${FFMPEG_ID[$FPS]} |
Aspect: ${FFMPEG_ID[$ASPECT]} |
Audio |
Codec: ${FFMPEG_ID[$ACODEC]} (${FFMPEG_ID[$ACNAME]}) |
Channels: ${FFMPEG_ID[$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 $@ |
# 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:$NL$list" |
fi |
fi |
# Bias towards the Sazanami family |
shopt -s nocasematch |
local ERE='sazanami' |
if [[ $candidates =~ $ERE ]]; then |
FONT_MINCHO=$(grep -i 'sazanami' <<<"$candidates" | head -1) |
else |
FONT_MINCHO=$(head -1 <<<"$candidates") |
fi |
shopt -u nocasematch |
} |
# Checks if the provided arguments make sense and are allowed to be used |
#+together. When an incoherence is found, sets some sane values if reasonable |
#+or fails otherwise. |
coherence_check() { |
trace $@ |
# 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 |
# In case it's 0/0 or 0.0 since they aren't rejected |
if fptest "$extended_factor" -eq 0 ; then |
extended_factor=0 |
fi |
if [[ ( $decoder -eq $DEC_MPLAYER ) && ( -z $MPLAYER_BIN ) ]]; then |
inf "No mplayer available. Using ffmpeg only." |
decoder=$DEC_FFMPEG |
elif [[ ( $decoder -eq $DEC_FFMPEG ) && ( -z $FFMPEG_BIN ) ]]; then |
inf "No ffmpeg available. Using mplayer only." |
decoder=$DEC_MPLAYER |
fi |
if [[ $DVD_MODE -eq 1 ]]; then |
# Since 1.12 DVD mode can work with multiple inputs too |
# 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_BIN ]]; 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= |
local -a filts=( ) |
if [[ $DISABLE_TIMESTAMPS -eq 0 ]] && |
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[@]}" ) |
# Interval=0 == default interval |
fptest "$interval" -eq 0 && interval=$DEFAULT_INTERVAL |
# If in non-latin mode and no nonlatin font has been picked try to pick one. |
# Should it fail, fallback to latin font. |
if [[ ( $NONLATIN_FILENAMES -eq 1 ) && ( -z $FONT_MINCHO ) ]]; then |
set_extended_font || { |
# set_extended_font already warns about lack of fonts |
warn " Falling back to latin font" |
NONLATIN_FILENAMES=0 |
FONT_MINCHO="$font_heading" |
} |
fi |
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() { |
trace $@ |
# Any default font in use? If all of them are overridden, return |
if [[ $USR_font_heading && $USR_font_title && \ |
$USR_font_tstamps && $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 |
local dvs='' |
if [[ -d /usr/local/share/fonts ]]; then |
dvs=$(find /usr/local/share/fonts/ -type f -iname 'dejavusans.ttf') |
fi |
if [[ ( -z $dvs ) && ( -d /usr/share/fonts ) ]]; then |
dvs=$(find /usr/share/fonts/ -type f -iname 'dejavusans.ttf') |
fi |
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 Sanitation: |
font_heading: $font_heading |
font_title : $font_title |
font_tstamps: $font_tstamps |
font_sign : $font_sign |
EOFF |
} |
# Main function. |
# Creates the contact sheet. |
# process($1 = file) |
process() { |
trace $@ |
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 |
CAPTURES='' |
DVD_MOUNTP= DVD_TITLE= # Re-set for each file |
if [[ $DVD_MODE -eq 1 ]]; then |
local dvdn=$(realpathr "$f") |
# Is it an ISO? |
if [[ -f $dvdn ]]; then |
DVD_MOUNTP=$(get_dvd_image_mountpoint "$dvdn") |
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 |
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_MOUNTP=$(mount | grep -o "^$dvdn *on [^ ]*" | cut -d' ' -f3) |
dvdn="DVD $f" |
fi |
if [[ ! -r $f ]]; then |
error "Can't access DVD ($f)" |
return $EX_NOINPUT |
fi |
inf "Processing $dvdn..." |
unset dvdn |
DVD_TITLE=${DVD_TITLES[0]} |
DVD_TITLES=( "${DVD_TITLES[@]:1}" ) # shift array |
if [[ ( -z $DVD_TITLE ) || ( $DVD_TITLE == '0' ) ]]; then |
local dt="$(lsdvd "$f" 2>/dev/null | grep 'Longest track:' | \ |
cut -d' ' -f3- | sed 's/^0*//')" |
if ! is_number "$dt" ; then |
error "Failed to autodetect longest DVD title for '$f'" |
exit $EX_INTERNAL |
fi |
DVD_TITLE=$dt |
unset dt |
fi |
DVD_VTS=$(lsdvd -t$DVD_TITLE -v "$f" 2>/dev/null | grep -o 'VTS: [0-9]*' | cut -d' ' -f2) |
inf "Using DVD Title #$DVD_TITLE (VTS: $DVD_VTS) for '$f'" |
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" && [[ $th_height != '100%' ]]; then |
vidcap_height=$(rpercent ${VID[$H]} ${th_height}) |
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 |
if [[ $aspect_ratio -eq -2 ]]; then |
[[ ${VID[$ASPECT]} ]] && aspect_ratio=0 || aspect_ratio=-1 |
elif [[ $aspect_ratio -eq 0 ]]; then |
if [[ ${VID[$ASPECT]} ]]; then |
# Aspect ratio in file headers, honor it |
aspect_ratio=$(awkexf "${VID[$ASPECT]}") |
else |
aspect_ratio=$(awkexf "${VID[$W]} / ${VID[$H]}") |
fi |
elif [[ $aspect_ratio -eq -1 ]]; 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 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') |
# If the temporal vidcaps for mplayer already exist, abort |
if [[ $decoder -eq $DEC_MPLAYER ]]; then |
for f_ in 1 2 3 4 5; 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[@]}" '00000005.png' ) |
# 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 ${VID[$LEN]} ; then (( ++n )) && continue ; fi |
pretty=$(pretty_stamp $stamp) |
inf "Generating highlight #${n}/${#HLTIMECODES[@]} ($pretty)..." |
hlcapfile=$(new_temp_file "-hl-$(pad 6 $n).png") |
capture "$f" $stamp "$hlcapfile" || return $? |
filter_vidcap "$hlcapfile" $pretty $vidcap_width $vidcap_height $CTX_HL $n || { |
local r=$? |
error "Failed to apply transformations to the capture." |
return $r |
} |
capfiles=( "${capfiles[@]}" "$hlcapfile" ) |
(( ++n )) |
done |
assert "[[ '"$n"' -gt 1 ]]" |
(( 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 |
local capfile pretty n=1 |
unset capfiles ; local -a capfiles ; local tfile= |
for stamp in $(clean_timestamps "${TIMECODES[*]}"); do |
pretty=$(pretty_stamp $stamp) |
inf "Generating capture #${n}/${#TIMECODES[*]} ($pretty)..." |
# identified by capture number, padded to 6 characters |
tfile=$(new_temp_file "-cap-$(pad 6 $n).png") |
capture "$f" $stamp "$tfile" || return $? |
filter_vidcap "$tfile" $pretty $vidcap_width $vidcap_height $CTX_STD $n || return $? |
capfiles=( "${capfiles[@]}" "$tfile" ) |
(( n++ )) |
done |
#filter_all_vidcaps "${capfiles[@]}" |
assert "[[ '"$n"' -gt 1 ]]" |
(( 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 $(awkex "int(${#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 |
(( w=vidcap_width/2-HPAD, h=vidcap_height*w/vidcap_width ,1 )) |
assert "[[ ( '"$w"' -gt 0 ) && ( '"$h"' -gt 0 ) ]]" |
for stamp in $(clean_timestamps "${TIMECODES[*]}"); do |
pretty=$(pretty_stamp $stamp) |
capfile=$(new_temp_file "-excap-$(pad 6 $n).png") |
inf "Generating capture from extended set: ${n}/${#TIMECODES[*]} ($pretty)..." |
capture "$f" $stamp "$capfile" || return $? |
filter_vidcap "$capfile" $pretty $w $h $CTX_EXT $n || return $? |
capfiles=( "${capfiles[@]}" "$capfile" ) |
(( n++ )) |
done |
(( 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 [[ -n $HLTIMECODES || ( $extended_factor != '0' ) ]]; then |
inf "Merging contact sheets..." |
if [[ -n $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 |
local csw2= ; (( csw2 = (width-csw) / 2 )) |
convert \( -size ${csw2}x$csh xc:transparent \) "$output" \ |
\( -size ${csw2}x$csh xc:transparent \) +append "$output" |
unset csh csw2 |
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 |
local hlw2= ; (( hlw2=(width - hlw) / 2 )) |
convert \( -size ${hlw2}x$hlh xc:transparent \) "$hlfile" \ |
\( -size ${hlw2}x$hlh xc:transparent \) +append "$hlfile" |
unset hlw2 |
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 |
local exw2 = ; (( exw2=(width - exv) / 2 )) |
convert \( -size ${exw2}x$exh xc:transparent \) "$extoutput" \ |
\( -size ${exw2}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]}" |
meta2="$meta2${NL}Format: $vcodec / $acodec${NL}FPS: ${VID[$FPS]}" |
local signature |
if [[ $anonymous_mode -eq 0 ]]; then |
signature="$user_signature $user${NL}with $PROGRAM_SIGNATURE" |
else |
signature="Created with $PROGRAM_SIGNATURE" |
fi |
local headwidth=$(imw "$output") headheight= |
local heading=$(new_temp_file .png) |
# Add the title if any |
if [[ $title ]]; then |
local tlheight=$(line_height "$font_title" "$pts_title") |
convert \ |
\( \ |
-size ${headwidth}x$tlheight "xc:$bg_title" \ |
-font "$font_title" -pointsize "$pts_title" \ |
-background "$bg_title" -fill "$fg_title" \ |
-gravity Center -annotate 0 "$title" \ |
\) \ |
-flatten \ |
"$output" -append "$output" |
unset tlheight |
fi |
local fn_font= # see $NONLATIN_FILENAMES |
if [[ $NONLATIN_FILENAMES -ne 1 ]]; then |
fn_font="$font_heading" |
else |
fn_font="$FONT_MINCHO" |
fi |
# Create a small image to see how tall are characters. In my tests, no matter |
#+which character is used it's always the same height. |
local lineheight=$(line_height "$font_heading" "$pts_meta") |
# Since filename can be set in a different font check it too |
if [[ $fn_font != $font_heading ]]; then |
local fnlineheight=$lineheight |
fnlineheight=$(line_height "$fn_font" "$pts_meta") |
[[ $fnlineheight -le $lineheight ]] || lineheight=$fnlineheight |
unset fnlineheight |
fi |
headheight=$(( lineheight * 3 )) |
# 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 "$f" 2>/dev/null | grep -o 'Disc Title: .*' | cut -d' ' -f3-) |
# There's no guarantee that titles are on separate VTS, I have no idea |
# how to compute the actual title size |
if [[ $DVD_MOUNTP ]]; then |
filename_label="Disc label" |
filename_value="$dvd_label" |
filesize_label="Titleset 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 "$f") $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 |
local signlh=$(line_height "$font_sign" "$pts_sign") |
local signheight=$(( 4 + ( signlh * 2 ) )) |
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}x$signheight -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 signheight signlh |
local wanted_name=${OUTPUT_FILES[$FILEIDX]} |
if [[ -n $wanted_name ]]; then |
local ERE='\.[^.]+$' |
if [[ $wanted_name =~ $ERE ]]; 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 |
fi |
[[ -n $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" |
(( 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" |
(( 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.35755975068581946630 #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) |
[[ -n $comm ]] || comm=unnamed |
ret=$($op) || true |
if [[ $ret != "$val" ]] && fptest "$ret" -ne "$val" ; then |
error "Failed test ($comm): '$op $val'. Got result '$ret'." |
(( retval++ ,1 )) # The ,1 ensures '((...))' 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" |
"is_pos_or_percent 33 0 #Positive recognition" |
"is_pos_or_percent 33% 0 #Percent recognition" |
"is_pos_or_percent 4/4% 1 #Percent recognition" |
"is_pos_or_percent % 1 #Percent 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) |
[[ -n $comm ]] || comm=unnamed |
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'" |
(( 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-2011 Toni Corvera" |
} |
# 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_BIN ]] && mpchosen=' [Not available]' |
[[ $MPLAYER_BIN && ( $decoder == $DEC_MPLAYER ) ]] && mpchosen=' [Selected]' |
[[ -z $FFMPEG_BIN ]] && ffchosen=', Not available' |
[[ $FFMPEG_BIN && ( $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 DVD Mode. |
In this mode the input <file>s must be the DVD |
device(s) or ISO(s). When in DVD mode all input files |
must be DVDs. |
Implies -A (auto aspect ratio) |
--dvd-title <arg> DVD title to use. Using 0 (the default) will use the |
longest title. |
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 |
-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. |
-p|--profile <arg> Load profile "arg" |
-C|--config <arg> Load configuration file "arg" |
--generate <config|profile> |
Generate configuration or profile from current settings |
-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 |
# ' # Syntax highlighting bait |
} |
# Print a configuration file generated from the currently active settings |
# generate_config($1 = <config|profile>) |
generate_config() { |
local n=$(echo $1 | tr '[a-z]' '[A-Z]') f= t= x= |
cat <<-EOM |
# --- $n STARTS HERE --- |
# This is a sample configuration file for VCS generated automatically |
# from the command-line with the "--generate $1" command-line option |
# Save it to ~/.vcs.conf or ~/.vcs/vcs.conf to make it the default |
# configuration. |
# OR |
# Save it to ~/.vcs/profiles/something.conf to create a profile named |
# "something". To use this profile run vcs with the "--profile something" |
# (or "-p something") option |
# OR |
# Save it to "something.conf" and load it with "--config something.conf" |
# (or "-C something.conf") |
EOM |
echo "${OVERRIDE_MAP[*]}" | stonl | egrep -v '(deprecated=|alias)' | cut -d':' -f1-2 |\ |
while read ovname ; do |
f=${ovname/:*} |
t=${ovname#*:} |
if [[ ( -z $t ) || ( $t == '=' ) ]]; then t=$f ; fi |
eval v=\$USR_$t |
[[ -z $v ]] || { |
# Symbolic values: |
case "$t" in |
timecode_from) |
x='$TC_NUMCAPS' |
[[ $v -eq $TC_NUMCAPS ]] || x='$TC_INTERVAL' |
v=$x |
;; |
decoder) |
x='$DEC_FFMPEG' |
[[ $v -eq $DEC_FFMPEG ]] || x='$DEC_MPLAYER' |
v=$x |
;; |
verbosity) |
case $v in |
$V_ALL) v='$V_ALL' ;; |
$V_NONE) v='$V_NONE' ;; |
$V_INFO) v='$V_INFO' ;; |
$V_WARN) v='$V_WARN' ;; |
$V_ERROR) v='$V_ERROR' ;; |
esac # verbosity |
;; |
esac |
# Print all names in lowercase |
echo "$(tolower "$f")=$v" |
} |
done |
echo "# vcs:conf:$NL# Generated on $(date)$NL# --- $n ENDS HERE --- " |
exit 0 |
} |
# }}} # 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:VR:Z:o:p:C: \ |
--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:,end-offset:,disable:,dvd,dvd-title:,randomsource:,undocumented:,output:,"\ |
"fullhelp,profile:,"\ |
"jpeg2,nonlatin,generate:,config:" \ |
-- "$@") |
eval set -- "$TEMP" |
while true ; do |
case "$1" in |
-i|--interval) |
check_constraint 'interval' "$2" "$1" || die |
interval=$(get_interval $2) |
timecode_from=$TC_INTERVAL |
USR_interval=$interval |
USR_timecode_from=$TC_INTERVAL |
shift # Option arg |
;; |
-n|--numcaps) |
check_constraint 'numcaps' "$2" "$1" || die |
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 [[ $1 = '-U' ]]; then # -U always provides an argument |
if [[ -n $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 |
fi |
shift |
else # No argument, default handling (try to guess real name) |
username=$(id -un) |
if type -p getent >/dev/null ; then |
user=$(getent passwd "$username" | cut -d':' -f5 |sed 's/,.*//g') |
else |
user=$(grep "^$username:" /etc/passwd | cut -d':' -f5 |sed 's/,.*//g') |
fi |
if [[ -z $user ]]; then |
user=$username |
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|--end-offset) |
check_constraint 'end_offset' "$2" "$1" || die |
is_p='y' |
is_percentage "$2" || is_p='' |
if [[ $is_p ]]; then |
end_offset="$2" |
else |
end_offset=$(get_interval "$2") |
fi |
USR_end_offset="$end_offset" |
unset is_i |
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 ;; |
-F|--ffmpeg) decoder=$DEC_FFMPEG ; USR_decoder=$decoder ;; |
-M|--mplayer) decoder=$DEC_MPLAYER ; USR_decoder=$decoder ;; |
-H|--height) |
check_constraint 'height' "$2" "$1" || die |
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) |
check_constraint 'columns' "$2" "$1" || die |
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 |
if [[ $2 ]]; then |
check_constraint 'extended_factor' "$2" "$1" || die |
extended_factor="$2" |
else |
extended_factor=$DEFAULT_EXT_FACTOR |
fi |
USR_extended_factor=$extended_factor |
shift |
;; |
# Unlike -I, --nonlatin does not accept a font name |
--nonlatin) |
if [[ -z $USR_FONT_MINCHO ]]; then |
NONLATIN_FILENAMES=1 |
USR_NONLATIN_FILENAMES=1 |
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 |
NONLATIN_FILENAMES=1 |
USR_NONLATIN_FILENAMES=1 |
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 |
RE='[a-zA-Z_]+=[^;]*' |
if [[ ! $2 =~ $RE ]]; then |
error "Wrong override format, it should be variable=value. Got '$2'." |
exit $EX_USAGE |
fi |
if [[ $2 =~ 'GETOPT=' ]] ; 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 |
cmdline_override "$2" |
POST_GETOPT_HOOKS=( "${POST_GETOPT_HOOKS[@]}" 1:cmdline_overrides_flush ) |
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 |
[[ -n $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=$(awkexf "$QUIRKS_MAX_REWIND * (2^$n)") |
(( 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=$(awkexf "$QUIRKS_LEN_STEP / (2^$n)") |
(( 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=$(awkexf "$QUIRKS_LEN_STEP * (2^$n)") |
(( 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 |
;; |
*) load_profile "$2" || die |
;; |
esac |
shift |
;; |
-C|--config) |
if echo "$2" | grep -q '^:' ; then |
if [[ $2 = ':pwd' ]]; then |
cfg=./vcs.conf |
else |
error "Configuration names starting with ':' are reserved." |
exit $EX_USAGE |
fi |
else |
cfg=$2 |
fi |
[[ -f $cfg ]] || { |
error "Configuration file '$cfg' not found" |
exit $EX_USAGE |
} |
# ./vcs.conf doesn't need the vcs:conf: mark |
if [[ $2 != ':pwd' ]]; then |
head -5 "$cfg" | grep -q '#[[:space:]]*vcs:conf[[:space:]]*:' || \ |
tail -5 "$cfg" | grep -q '#[[:space:]]*vcs:conf[[:space:]]*:' || { |
error "No vcs:conf: mark found in '$cfg'" |
exit $EX_NOINPUT |
} |
fi |
load_config_file "$cfg" 'Custom configuration' |
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 |
;; |
--dvd-title) |
check_constraint 'dvd_title' "$2" "$1" || die |
DVD_TITLES=( "${DVD_TITLES[@]}" "$2" ) |
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 |
decoder=$DEC_MPLAYER |
aspect_ratio=-2 # Special value: Auto detect only if ffmpeg couldn't |
;; |
-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 |
USR_verbosity=$verbosity |
;; |
-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_BIN=$(realpathr "$(cut -d'=' -f2<<<"$2")") |
assert '[[ -x $FFMPEG_BIN ]]' |
warn "[U] FFMPEG_BIN=$FFMPEG_BIN" |
;; |
# mplayer path |
set_mplayer=*) |
MPLAYER_BIN=$(realpathr "$(cut -d'=' -f2<<<"$2")") |
assert '[[ -x $MPLAYER_BIN ]]' |
warn "[U] MPLAYER_BIN=$MPLAYER_BIN" |
;; |
# Ignore one of the players |
disable_ffmpeg) |
FFMPEG_BIN='' |
warn "FFmpeg disabled" |
assert '[[ $MPLAYER_BIN ]]' |
decoder=$DEC_MPLAYER |
;; |
disable_mplayer) |
MPLAYER_BIN='' |
warn "Mplayer disabled" |
assert '[[ $FFMPEG_BIN ]]' |
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")" |
error "Shoehorning of options is scheduled to be removed in the next version." |
error " Please contact the author if you absolutely need it." |
;; |
debug) |
warn "[U] debug" |
DEBUG=1 |
;; |
# Dump user-set variables and exit [since 1.12] |
uservars) |
echo "${OVERRIDE_MAP[*]}" | stonl | egrep -v '(deprecated=|alias)' | cut -d':' -f1-2 |\ |
while read ovname ; do |
f=${ovname/:*} |
t=${ovname#*:} |
if [[ ( $t ) && ( $t != '=' ) ]]; then f="$t" ; fi |
eval v=\$USR_$f |
[[ -z $v ]] || echo "$(tolower $f)=$v" |
done |
exit 0 |
;; |
*) false ;; |
esac |
shift |
;; |
--generate) |
case "$2" in |
profile|config) |
POST_GETOPT_HOOKS=( "${POST_GETOPT_HOOKS[@]}" \ |
10:generate_config:$2 ) |
;; |
*) |
error "Option --generate must be followed by profile or config" |
exit $EX_USAGE |
;; |
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_BIN |
FFMPEG: $FFMPEG_BIN |
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 |
# Avoid coherence_check if there's no arguments and no cmdline post |
# processing |
[[ -n $1 || -n $POST_GETOPT_HOOKS ]] || { |
[[ $verbosity -eq $V_NONE ]] || show_help |
exit $EX_USAGE |
} |
# More than one argument... |
if [[ -n $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 $? |
} |
# Run after coherence check to clean recoverable incorrect values |
post_getopt_hooks |
# Remaining arguments |
if [[ -z $1 ]]; then |
[[ $verbosity -eq $V_NONE ]] || show_help |
exit $EX_USAGE |
fi |
# TODO: |
# DVD mode + multiple titles is still tricky: |
# --dvd --dvd-title 1 --dvd-title 2 /dev/dvd /dev/dvd |
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 |
# * bash3: [[ STR =~ EREGEX ]] is faster than grep/egrep (no forking) |
# bash 3.2 changed semantics vs bash 3.1 |
# * bash4: |& (inherited from csh?) pipes both stdout and stderr |
# * 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/pkg/rpm/vcs.spec.in |
---|
0,0 → 1,113 |
# |
# $Rev$ |
# |
# 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} |
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 >= 3.1 |
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 |
make examples/vcs.conf.example |
%install |
make DESTDIR=%buildroot prefix=%{prefix} install |
%clean |
[ ${RPM_BUILD_ROOT} != "/" ] && rm -rf ${RPM_BUILD_ROOT} |
#%post |
# postinst |
#%postun |
# postuninst |
%files |
%defattr(-,root,root) |
# binary |
%{_bindir}/%{name} |
# Profiles |
%{prefix}/share/vcs/profiles/black.conf |
%{prefix}/share/vcs/profiles/mosaic.conf |
%{prefix}/share/vcs/profiles/white.conf |
# Manpage |
%{_mandir}/man1/%{name}.1.gz |
%doc CHANGELOG |
# Config example |
%doc examples/vcs.conf.example |
%changelog |
* Tue Aug 24 2010 - outlyer (at) gmail (dot) com |
- Install manpage |
* Sat Apr 10 2010 - outlyer (at) gmail (dot) com |
- Added profiles and example configuration |
- Use %{prefix} |
* 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/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 (>= 3.1), 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/pkg/debian/changelog |
---|
0,0 → 1,82 |
vcs (1.12.3-upstream.1) experimental; urgency=low |
* New version. |
* debian/control: Bump minimum bash version |
-- Toni Corvera <outlyer@gmail.com> Sun, 17 Jul 2011 18:49:56 +0200 |
vcs (1.12.2-upstream.1) experimental; urgency=medium |
* New version. Medium priority due to temporary files cleanup bug. |
-- Toni Corvera <outlyer@gmail.com> Tue, 24 Aug 2010 20:48:41 +0200 |
vcs (1.12.1-upstream.1) experimental; urgency=medium |
* New version. |
-- Toni Corvera <outlyer@gmail.com> Fri, 23 Apr 2010 13:56:58 +0200 |
vcs (1.12-upstream.1) experimental; urgency=low |
* New version. |
* debian/docs: Install vcs.conf.example |
-- Toni Corvera <outlyer@gmail.com> Sat, 10 Apr 2010 00:57:17 +0200 |
vcs (1.11.2-upstream.1) experimental; urgency=low |
* New version. |
-- Toni Corvera <outlyer@gmail.com> Fri, 19 Mar 2010 00:18:51 +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/trunk/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 |
DESTDIR:=$(CURDIR)/debian/vcs |
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) all prepackage |
#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=$(DESTDIR) 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 vcs.1 |
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/pkg/debian/docs |
---|
0,0 → 1,0 |
examples/vcs.conf.example |
/video-contact-sheet/trunk/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/trunk/pkg/debian/dirs |
---|
0,0 → 1,0 |
usr/bin |
/video-contact-sheet/trunk/pkg/debian/compat |
---|
0,0 → 1,0 |
5 |
/video-contact-sheet/trunk/pkg/CHANGELOG |
---|
0,0 → 1,420 |
1.12.3 (2011-07-17): |
* BUGFIX: Actually handle --ffmpeg and --mplayer [#169] |
* BUGFIX: Correct parsing of -U [#187] |
* OTHER: |
- Fix printing of remaining options on command-line error |
- Switch to a minimum of bash 3.1 [#173] |
- Avoid re-capturing the same frame twice [#122] |
- Use getent instead of /etc/passwd when available |
* INTERNAL: |
- Use of Bash's 'caller' in 'assert' and 'trace' |
- 'assert' prints a call trace on error |
- 'assert_if' |
- Don't use mplayer's length as a ceil for timecode removal [#174] |
1.12.2 (2010-08-24): |
* BUGFIX: Fix cleanup of temporary files (regression since 1.11.2). [#167] |
Submitted by Jason Tackaberry. |
* FEATURES: |
- Added 'fg_all', 'bg_all' and 'font_all' config variables. [#156] |
- Added 'nonlatin_filenames' config variable. [#159] |
- Added identification for VP8 (WebM). [#166] |
* OTHER: |
- Print variable names in lowercase when using --generate. |
1.12.1 (2010-04-23): |
* BUGFIXES: |
- Workaround for cases in which GAWK uses comma as decimal separator. |
Any OS with GAWK 3.1.3 to 3.1.5 was affected (where the environment |
language uses commas, e.g. Debian Lenny with many European languages) |
- Don't try to go on in DVD mode with unreadable ISOs |
1.12: (2010-04-10) |
* New features/tweaks: |
- Loading of random configuration files (--config / -C) |
- Profiles: Similar to above but simpler syntax (--profile / -p) |
- Config/Profile generation from command-line (--generate) |
- Adapt heading, title and footer height to font size (fonts that used |
to get cropped should now be fine) |
* DVD mode cleanup: |
- Command-line switched to match "normal" files: |
Before: |
$ vcs --dvd /dev/dvd 0 or $ vcs -dvd /dev/dvd 1 |
Equivalents now: |
$ vcs --dvd /dev/dvd or $ vcs --dvd --dvd-title 1 /dev/dvd |
* New end-offset behaviour: |
- A 5.5% end offset is applied by default |
- Can be disabled with -E0 or end_offset=0 |
- MIN_LENGTH_FOR_END_OFFSET is no longer used |
* Configuration files cleanup: |
- Simplified or more meaningful names where appropriate (the older |
names will continue to work for a while, and users will be warned) |
"vcs --generate" with no other arguments can be used to translate them |
- Validation of configuration options. |
Incorrect values will be discarded and an error shown; processing will |
continue. |
- Configuration searched in ~/.vcs/vcs.conf too |
- Syntax enhancements: |
> Comments can now be included in-line |
> Putting '#' in a value now requires using the "escaped form" '$#' |
> Semicolons (;) also serve to start comments: When one is found the |
rest of the line is ignored, they continue to be disallowed in values |
i.e. 'tl;dr' will be parsed as 'tl' |
* Other: |
- Accept timecodes and percentages in end_offset, both from the |
command-line and in configuration files |
- Print the start and end timestamps in effect before capturing |
- No longer accept interval zero (used to be re-set to default) |
- Tighter printing of overrides and no longer printed as warning |
- Strickter handing of wrong options |
- Fall back to Helvetica also when no fonts dir is located. Look |
in /usr/local too. |
- --end-offset added as an alias to --end_offset |
- Starting with 1.12 a tarball + makefile is also provided |
* BUGFIXES: |
- Avoid possible (unlikely) usage of scientific notation in internal |
calculations |
- Distinguish between default end offset and user's end offset with the |
same value |
- Handle --nonlatin correctly |
- DVD Mode + FFmpeg identification: Check VOB #0 instead of #1 |
- Don't print escape codes to stdout when testing colour printing |
* Options removed: |
--shoehorn, temporary replacement: --undocumented shoehorn. Will be gone |
in 1.13 |
--mincho, replaced by --nonlatin since 1.11 |
MIN_LENGTH_FOR_END_OFFSET, as explained above, no longer needed |
* INTERNAL: |
- $CFGFILE replaced by ~/.vcs.conf |
- Use -p for profiles instead of -P (used, undocumented, in 1.11) |
1.11.2: (2010-03-19) |
* Added Cook and Sipro (RealAudio 4, 5 & 6) codecs |
* BUGFIXES: |
- Remove extra, empty, temporary dir |
- Use standard awk syntax for exponentiation (pyth_th) |
- Workaround for systems that don't register fonts with ImageMagick |
* DEBUG: Print to stderr when probbing with mplayer too |
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 |
# vim:set ts=3 sw=3 et textwidth=80: # |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/trunk/pkg/arch/PKGBUILD.in |
---|
0,0 → 1,39 |
# |
# $Rev$ |
# |
# Maintainer (Upstream): Toni Corvera <outlyer@gmail.com> |
# |
# Build with '$ makepkg' on the same directory as this file |
# |
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>=3.1' 'imagemagick>=6.3.5.7' 'mplayer' 'ffmpeg') |
makedepends=('bzip2') |
optdepends=('lsdvd: for DVD support' |
'perl: for DVD support') |
backup=() |
options=('docs' 'zipman') |
source=($url/files/$pkgname-$pkgver.tar.gz) |
md5sums=(@MD5@) #generate with 'makepkg -g' |
sha1sums=(@SHA1@) |
# Debian & Arch didn't agree on this on my first try (???) |
sha256sums=(@SHA256@) |
build() { |
cd $srcdir/$pkgname-$pkgver |
make prepackage |
make install DESTDIR=${pkgdir} prefix=/usr |
install -D $srcdir/$pkgname-$pkgver/examples/vcs.conf.example \ |
${pkgdir}/usr/share/doc/$pkgname/vcs.conf.example |
} |
#man page (TODO) |
# install -D -m644 $pkgname.1 ${pkgdir}/usr/share/man1/$pkgname.1 || return 1 |
# vim:set filetype=sh ts=2 et: # |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/trunk/pkg/common.mk |
---|
0,0 → 1,72 |
# $Id$ |
# |
# To be included from GNUmakefile or BSDmakefile |
# To use it directly set VERSION and PACKAGER |
# e.g. make VERSION=1.x PACKAGER=Me <rule> |
# |
# Notes to self: |
# This file should follow only common/portable make syntax and commands |
# Common pitfalls: |
# - $(shell) -> GNU Make, equivalent BSD make: != |
# - install -D -> GNU only (-d is portable) |
# - $(RM) -> empty by default in BSD, set from BSDmakefile |
prefix:=/usr/local |
DESTDIR:=/ |
TGZ=vcs-$(VERSION).tar.gz |
MANDIR:=$(prefix)/share/man/man1/ |
all: |
# Nothing to be done |
dist: vcs.spec |
# Files installed in packages but not outside |
prepackage: examples/vcs.conf.example |
install: |
install -d $(DESTDIR)$(prefix)/bin/ |
install -m755 vcs $(DESTDIR)$(prefix)/bin/vcs |
install -d $(DESTDIR)$(prefix)/share/vcs/profiles |
install -m644 profiles/*.conf $(DESTDIR)$(prefix)/share/vcs/profiles/ |
install -d $(DESTDIR)$(MANDIR) |
install -m644 vcs.1 $(DESTDIR)$(MANDIR)/ |
uninstall: |
$(RM) $(DESTDIR)$(prefix)/bin/vcs |
$(RM) $(DESTDIR)$(MANDIR)/vcs.1 |
for file in profiles/*.conf ; do \ |
$(RM) $(DESTDIR)$(prefix)/share/vcs/profiles/`basename $$file` ; \ |
done |
-rmdir -p $(DESTDIR)$(prefix)/bin |
-rmdir -p $(DESTDIR)$(prefix)/share/vcs/profiles |
-rmdir -p $(DESTDIR)$(MANDIR) |
examples/vcs.conf.example: examples/vcs.conf |
sed -e 's/^/#/;s/^#$$//;s/^##/#/' < $< > $@ |
vcs.spec: rpm/vcs.spec.in vcs |
test "$(VERSION)" # Version (=$(VERSION)) must be detected |
@echo "[vcs.spec]" |
@cat $< | sed 's!@VERSION@!$(VERSION)!g' | \ |
sed 's!@PACKAGER@!$(PACKAGER)!g' > $@ |
# PKGBUILD CAN'T BE INCLUDED in the archive |
PKGBUILD: arch/PKGBUILD.in $(TGZ) vcs |
test "$(VERSION)" # Version (=$(VERSION)) must be detected |
@echo "[PKGBUILD]" |
@MD5=$(shell md5sum -b $(TGZ) | cut -d' ' -f1) ; \ |
SHA1=$(shell sha1sum -b $(TGZ) | cut -d' ' -f1) ; \ |
SHA256=$(shell sha256sum -b $(TGZ) | cut -d' ' -f1) ; \ |
cat $< | sed -e 's!@VERSION@!$(VERSION)!g' \ |
-e "s/@MD5@/$$MD5/g" \ |
-e "s/@SHA1@/$$SHA1/g" -e "s/@SHA256@/$$SHA256/g" > $@ |
clean: |
-$(RM) examples/vcs.conf.example |
distclean: clean |
-$(RM) vcs.spec PKGBUILD |
.PHONY: all install clean tgz |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/trunk/pkg/profiles/mosaic.conf |
---|
0,0 → 1,12 |
# vcs:conf: |
# vcs:desc: Tight, small, thumbnails |
# <http://p.outlyer.net/dox/vcs:example_configs> |
# $Id$ |
disable_timestamps=1 |
disable_shadows=1 |
height=160 |
numcaps=20 |
timecode_from=$TC_NUMCAPS |
padding=0 |
columns=4 |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/trunk/pkg/profiles/black.conf |
---|
0,0 → 1,9 |
# vcs:conf: |
# vcs:desc: White-on-Black |
# $Id$ |
bg_contact=Black |
bg_heading=$bg_contact |
bg_sign=$bg_contact |
fg_heading=White |
fg_sign=$fg_heading |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/trunk/pkg/profiles/white.conf |
---|
0,0 → 1,9 |
# vcs:conf: |
# vcs:desc: Black-on-White profile |
# $Id$ |
bg_contact=White |
bg_heading=$bg_contact |
bg_sign=$bg_contact |
fg_heading=Black |
fg_sign=$fg_heading |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/trunk/pkg/BSDmakefile |
---|
0,0 → 1,14 |
VERSION!=head -50 vcs | grep 'declare -r VERSION=' | perl -pe 's/.*"(.*)".*/\1/' |
PACKAGER!=finger -lp `echo $USER` 2>/dev/null | head -n1 | cut -d: -f3 |
.if empty($(PACKAGER)) |
PACKAGER!=grep ^`id -un` /etc/passwd | cut -d: -f5 | cut -d, -f1 |
.endif |
.if empty($(RM)) |
RM=rm -f |
.endif |
include common.mk |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/trunk/pkg/GNUmakefile |
---|
0,0 → 1,13 |
# |
# $Id$ |
# Makefile for GNU-make |
# |
VERSION:=$(shell head -50 vcs | grep 'declare -r VERSION=' | sed -e 's/.*"\(.*\)".*/\1/') |
PACKAGER:=$(shell finger -lp `echo $USER` 2>/dev/null | head -n1 | cut -d: -f3) |
ifeq ($(PACKAGER),) |
PACKAGER:=$(shell grep ^`id -un` /etc/passwd | cut -d: -f5 | cut -d, -f1) |
endif |
include common.mk |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/trunk/pkg/README |
---|
0,0 → 1,39 |
Index |
----- |
1. Files |
2. Installation |
3. Uninstallation |
Files |
----- |
In this package: |
vcs The VCS script |
profiles/ Example profiles: |
mosaic.conf 20 small thumbnails in a 5x4 grid, no padding |
black.conf Black background and white text |
white.conf White background and black text |
examples/vcs.conf Example configuration |
Use "make examples/vcs.conf.example" to create |
a version with all options commented out. |
Installation |
------------ |
$ make install |
Will install under /usr/local |
$ make install prefix=/usr |
Will install under /usr |
Uninstallation |
-------------- |
$ make uninstall |
If you used a prefix during install use it too during uninstall |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/trunk/pkg/examples/vcs.conf |
---|
0,0 → 1,152 |
# |
# vcs:conf: $Rev$ |
# Example vcs.conf file |
# This example files contains all the default values, commented out. |
# For each setting, where an equivalent command-line option exists it will be |
# listed in the comments |
# |
# Location: |
# Configuration files can be placed at /etc/vcs.conf (site-wide), |
# ~/.vcs.conf (current user, hidden file!), ./vcs.conf (current dir) or |
# ~/.vcs/vcs.conf (current user, new alternative location) |
# |
# Syntax: |
# * Comments start with '#' or ';' |
# * '#' can be used in values by writing $# |
# * Semicolons (;) can't be used in values |
# * Options are of the form name=value |
# - Options can refer to the *current* value of other options, i.e. |
# font_sign=$font_heading will assign to 'font_sign' the same value |
# as 'font_heading'. If font_heading is changed after that, font_sign |
# won't be affected. |
# - See <http://p.outlyer.net/dox/vcs:conf_files> for the full list |
# Height of individual captures. Percentage or fixed size (in pixels) |
height=100% # option -H |
# Amount ignored from the end of the video (think of it as an anti-spoiler |
# measure). Percentage (of video duration) or time (e.g. 2m). Set to 0 |
# to disable it. |
end_offset=5% # option -E |
######################## |
# Contact Sheet Layout # |
######################## |
columns=2 # Number of columns in the contact sheet (option -c) |
interval=1m # Interval between captures (option -i) |
# Number of captures. Note setting this isn't enough to use a fixed number, the |
# mode must also be changed (option 'timecode_from', see below). (option -n) |
captures=16 |
# Mode of operation, can be either $TC_NUMCAPS or $TC_INTERVAL (the default) |
# $TC_NUMCAPS Fix the number of captures, adjust interval accordingly |
# $TC_INTERVAL Fix interval, adjust the number of captures accordingly |
# (options -i and -n set this implicitly) |
timecode_from=$TC_INTERVAL |
# Modifier for extended mode (option -e). |
# Beware, setting it to something different from 0 automatically enables extended mode! |
extended_factor=0 |
# Extra padding added around each capture. |
# This has *no effect* when shadows are enabled (the default). |
# Tweaking this might break alignment when using extended mode (-e) |
padding=2 |
############### |
# Output file # |
############### |
# Output format. Use the file extension (e.g. 'png', 'jpg', 'jp2') |
# Any format accepted by ImageMagick can be used here (even pdf or gif, not |
# that they'll look very good though) |
# (options: -j for JPEG, -j2 for JPEG 2000) |
format=png |
quality=92 # Output quality for lossy formats (e.g. jpg) |
#################### |
# Cosmetic touches # |
#################### |
user=`id -un` # User name, will be used in the contact sheet footer (option -u) |
# Used in the signature, e.g. "Preview created by {value of user}" |
signature=Preview created by |
disable_shadows=0 # Disable shadows by default (option -ds) |
disable_timestamps=0 # Disable timestamps by default (option -dt) |
##################### |
# Colours and fonts # |
##################### |
# * Colours can be defined either by their name or through hexcodes |
# use the command 'identify -list color' for a list of known names |
# * Colours can use transparency, although this usually only makes sense |
# for timestamps, which are overlaid on captures. |
# * Beware when specifying colours in hex format color=#hex is ok |
# but color = #hex not, spaces can be used with color = $#hex |
# * Font sizes are expressed in points and hence might need adjustment |
# when the font is changed. |
# * Font names can be either font paths (e.g. /usr/something/ttf/font.ttf) |
# or font names as understood by ImageMagick, use the command |
# 'identify -list font' for a list of fonts known to ImageMagick |
bg_heading=#afcd7a # Heading/meta-information section background colour |
fg_heading=Black # Heading font colour |
font_heading=DejaVu-Sans-Book # Heading font |
pts_heading=14 # Font size for heading |
bg_title=White # Background for the title (if activated with option -T) |
fg_title=Black # Title font colour |
font_title=$font_heading # Title font |
bg_contact=White # Background for the contact sheet |
bg_tstamps=#000000aa # Background for timestamps. Note the use of transparency |
fg_tstamps=White # Timestamps font colour |
font_tstamps=$font_heading # Timestamps font |
pts_tstamps=14 # Font size for timestamps |
# Background for the signature, i.e. the section after the contact sheet with |
# vcs and user identification |
bg_sign=SlateGray |
fg_sign=Black # Font colour for the signature |
font_sign=$font_heading # Font for the signature |
pts_sign=10 # Font size for signature |
# Font to use for file name when the non-latin mode (--nonlatin) is enabled |
# Option: -Ik=fontname |
nonlatin_font= # Autodetected (hopefully) |
###################### |
# Lower level tweaks # |
###################### |
# Decoder to use by default, can be either $DEC_FFMPEG (default) or |
# $DEC_MPLAYER |
# (options: -M for MPlayer, -F for FFMpeg) |
decoder=$DEC_FFMPEG |
# Output from commands, useful to locate errors |
stdout=/dev/null |
stderr=/dev/null |
# Verbosity level, very verbose by default. |
# Possible values: $V_ALL (default), $V_ERROR, $V_WARN, $V_INFO, $V_NONE |
# (options: -q for quietness) |
verbosity=$V_ALL |
# 1 disables colours in console output |
simple_feedback=0 |
debug=0 # When 1, enables debugging mode (option -D) |
getopt=getopt # GNU Getopt executable name |
# $Rev$ # |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/trunk/Makefile |
---|
0,0 → 1,72 |
#!/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" |
pkg/vcs.1: manpage.xml |
xmlto -o pkg man $< |
tgz: vcs-$(VER).tar.gz |
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 \ |
pkg/vcs.1 \ |
vcs-$(VER).tar.gz \ |
PKGBUILD-$(VER) \ |
vcs-$(VER).gz vcs-$(VER).bz2 vcs-$(VER).bash \ |
CHANGELOG.gz CHANGELOG \ |
rpm deb |
PKGBUILD-$(VER): vcs-$(VER).tar.gz |
cd pkg && ln -s ../vcs-$(VER).tar.gz ./ |
cd pkg && make PKGBUILD |
$(RM) pkg/vcs-$(VER).tar.gz |
mv pkg/PKGBUILD $@ |
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/trunk/manpage.xml |
---|
0,0 → 1,382 |
<?xml version='1.0' encoding='UTF-8'?> |
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" |
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ |
<!-- |
$Id$ |
This file based on the default template for debian packages. |
Generation of man page: |
$ xmlto man manpage.xml |
Will generate vcs.1. |
View with: |
$ nroff -man vcs.1 | less |
or |
$ man vcs.1 |
To disable the automatic creation of the AUTHOR(S) and COPYRIGHT sections |
read /usr/share/doc/docbook-xsl/doc/manpages/authors.html. This file can be |
found in the docbook-xsl-doc-html package. |
Validation can be done using: `xmllint -''-noout -''-valid manpage.xml` |
General documentation about man-pages and man-page-formatting: |
man(1), man(7), http://www.tldp.org/HOWTO/Man-Page/ |
--> |
<!-- Fill in your name for FIRSTNAME and SURNAME. --> |
<!ENTITY dhfirstname "Toni"> |
<!ENTITY dhsurname "Corvera"> |
<!-- dhusername could also be set to "&dhfirstname; &dhsurname;". --> |
<!ENTITY dhusername "Toni Corvera"> |
<!ENTITY dhemail "outlyer@gmail.com"> |
<!ENTITY dhsection "1"> |
<!-- TITLE should be something like "User commands" or similar (see |
http://www.tldp.org/HOWTO/Man-Page/q2.html). --> |
<!ENTITY dhtitle "vcs User Manual"> |
<!ENTITY dhucpackage "VCS"> |
<!ENTITY dhpackage "vcs"> |
]> |
<refentry> |
<refentryinfo> |
<title>&dhtitle;</title> |
<productname>&dhpackage;</productname> |
<authorgroup> |
<author> |
<firstname>&dhfirstname;</firstname> |
<surname>&dhsurname;</surname> |
<contrib>VCS author.</contrib> |
<address> |
<email>&dhemail;</email> |
</address> |
</author> |
</authorgroup> |
<copyright> |
<year>2007-2010</year> |
<holder>&dhusername;</holder> |
</copyright> |
<legalnotice> |
<para>Permission is granted to copy, distribute and/or modify this |
document under the terms of the GNU Lesser General Public License, |
Version 2 or (at your option) any later version published by |
the Free Software Foundation.</para> |
</legalnotice> |
<edition>$Rev$</edition> |
</refentryinfo> |
<refmeta> |
<refentrytitle>&dhucpackage;</refentrytitle> |
<manvolnum>&dhsection;</manvolnum> |
</refmeta> |
<refnamediv> |
<refname>&dhpackage;</refname> |
<refpurpose>create contact sheets from videos</refpurpose> |
</refnamediv> |
<refsynopsisdiv> |
<cmdsynopsis> |
<command>&dhpackage;</command> |
<arg choice="opt"><option>options</option></arg> |
<arg choice="plain"><replaceable>FILE</replaceable></arg> |
<arg choice="opt"><replaceable>FILE</replaceable> |
<arg choice="opt"><replaceable>...</replaceable></arg> |
</arg> |
</cmdsynopsis> |
<cmdsynopsis> |
<command>&dhpackage;</command> |
<arg choice="opt"> |
<option>-n<parameter>20</parameter></option> |
<option>-c<parameter>4</parameter></option> |
</arg> |
<arg choice="plain"><replaceable>FILE</replaceable></arg> |
</cmdsynopsis> |
<cmdsynopsis> |
<command>&dhpackage;</command> |
<arg choice="opt"><option>--output=<parameter>OUTPUT1</parameter></option></arg> |
<arg choice="opt"><option>--output=<parameter>OUTPUT2</parameter></option></arg> |
<arg choice="opt"><option>...</option></arg> |
<arg choice="plain"><replaceable>INPUT1</replaceable></arg> |
<arg choice="opt"><replaceable>INPUT2</replaceable> |
<arg choice="opt"><replaceable>...</replaceable></arg> |
</arg> |
</cmdsynopsis> |
<cmdsynopsis> |
<command>&dhpackage;</command> |
<!-- Normally the help and version options make the programs stop |
right after outputting the requested information. --> |
<group choice="opt"> |
<arg choice="plain"> |
<group choice="req"> |
<arg choice="plain"><option>-h</option></arg> |
<arg choice="plain"><option>--help</option></arg> |
</group> |
</arg> |
<arg choice="plain"> |
<arg choice="plain"><option>--fullhelp</option></arg> |
</arg> |
</group> |
</cmdsynopsis> |
</refsynopsisdiv> |
<refsect1 id="description"> |
<title>DESCRIPTION</title> |
<!-- FIXME --> |
<para>This is a work in progress manual page for <command>&dhpackage;</command>, |
use <parameter>--help</parameter>, <parameter>--fullhelp</parameter> |
and the online documentation (located at <ulink url="http://p.outlyer.net/dox/vcs" />) |
for further usage instructions.</para> |
<para><command>&dhpackage;</command> is a program that creates a preview |
image from videos in a contact sheet-like format (i.e. captures from |
different frames in the video are placed in a mosaic).</para> |
<para>By default the output file will be named like the input file plus the |
png extension. Example: "<filename>file.avi</filename>" will produce |
a contact sheet in the file "<filename>file.avi.png</filename>".</para> |
<para>The default mode of operation is to obtain captures every five minutes in the |
video, so the amount of captures will vary with each file. The command-line |
argument <parameter>--numcaps</parameter> (<parameter>-n</parameter>) can be used |
to change this behaviour or alternatively a configuration file might |
be used to change the mode of operation (see <citerefentry> |
<refentrytitle>vcs.conf</refentrytitle> |
<manvolnum>5</manvolnum> |
</citerefentry>). |
</para> |
</refsect1> |
<refsect1 id="options"> |
<title>OPTIONS</title> |
<para>The program follows the usual GNU command line syntax, |
with long options starting with two dashes (`-'). A summary of |
options is included below.</para> |
<variablelist> |
<varlistentry> |
<term><option>-n <replaceable>number</replaceable></option></term> |
<term><option>--numcaps=<replaceable>number</replaceable></option></term> |
<listitem> |
<para>Fixes the number of captures to obtain. Changes the mode of operation |
to capture a fixed number of frames.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-i <replaceable>interval</replaceable></option></term> |
<term><option>--interval=<replaceable>interval</replaceable></option></term> |
<listitem> |
<para>Sets the interval between captures. Changes the mode of operation |
to capture at fixed intervals. The number of captures will depend |
on the video length.</para> |
<para>See <xref linkend="interval_format" /> for the allowed syntax.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-c <replaceable>number</replaceable></option></term> |
<term><option>--columns=<replaceable>number</replaceable></option></term> |
<listitem> |
<para>Number of columns in the contact sheet. The number of rows |
will depend on this and the number of captures (there's no |
way to set the number of rows).</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-H <replaceable>height</replaceable></option></term> |
<term><option>--height=<replaceable>height</replaceable></option></term> |
<listitem> |
<para>Height of captures. Can be a number (of pixels) or a percentage |
(of the video height). By default the same size as the video is used.</para> |
<para>The width is derived from height and aspect ratio.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-o <replaceable>filename</replaceable></option></term> |
<term><option>--output=<replaceable>filename</replaceable></option></term> |
<listitem> |
<para>Name of output file. By default the video file name plus the output |
format is used (e.g. "<filename>video.avi.png</filename>" |
for "<filename>video.avi</filename>").</para> |
<para>If an extension is provided, it will define the output format, otherwise |
PNG will be used. I.e. <filename>sheet.jpg</filename> will produce |
a JPEG file while <filename>sheet</filename> and |
<filename>sheet.png</filename> will produce a PNG file.</para> |
</listitem> |
</varlistentry> |
<!-- |
<varlistentry> |
<term><option>-x <replaceable>t</replaceable></option></term> |
<term><option>- -xy=<replaceable>t</replaceable></option></term> |
<listitem> |
<para>DESC.</para> |
</listitem> |
</varlistentry> |
--> |
<varlistentry> |
<term><option>-h</option></term> |
<term><option>--help</option></term> |
<listitem> |
<para>Show summary of most common options.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>--fullhelp</option></term> |
<listitem> |
<para>Show summary of all options.</para> |
</listitem> |
</varlistentry> |
</variablelist> |
</refsect1> |
<refsect1 id="files"> |
<title>FILES</title> |
<variablelist> |
<varlistentry> |
<term><filename>/etc/vcs.conf</filename></term> |
<listitem> |
<para>The system-wide configuration file to control the |
behaviour of <application>&dhpackage;</application>. See |
<citerefentry> |
<refentrytitle>vcs.conf</refentrytitle> |
<manvolnum>5</manvolnum> |
</citerefentry> for further details.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><filename>${HOME}/.vcs.conf</filename></term> |
<term><filename>${HOME}/.vcs/vcs.conf</filename></term> |
<listitem> |
<para>The per-user configuration file to control the |
behaviour of <application>&dhpackage;</application>. See |
<citerefentry> |
<refentrytitle>vcs.conf</refentrytitle> |
<manvolnum>5</manvolnum> |
</citerefentry> for further details.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><filename>${PWD}/vcs.conf</filename></term> |
<listitem> |
<para>The per-directory configuration file to control the |
behaviour of <application>&dhpackage;</application>. See |
<citerefentry> |
<refentrytitle>vcs.conf</refentrytitle> |
<manvolnum>5</manvolnum> |
</citerefentry> for further details.</para> |
</listitem> |
</varlistentry> |
</variablelist> |
</refsect1> |
<refsect1 id="interval_format"> |
<title>INTERVALS</title> |
<para> |
Intervals and timestamps can be specified in seconds or in a human-readable format |
that follows the syntax |
<replaceable>HOURS</replaceable>h<replaceable>MINUTES</replaceable>m |
<replaceable>SECONDS</replaceable>s.<replaceable>MILLISECONDS</replaceable> |
where each element is optional. |
See <ulink url="http://p.outlyer.net/dox/vcs:time_syntax" /> for more details. |
</para> |
<para> |
<segmentedlist> |
<segtitle>Example</segtitle><segtitle>Equivalence</segtitle> |
<segtitle>Standard time format</segtitle> |
<seglistitem> |
<seg>1h30m30</seg><seg>1h30m30s.00</seg><seg>1:30:30.00</seg> |
</seglistitem> |
<seglistitem> |
<seg>30</seg><seg>0h0m30s.00</seg><seg>0:00:30.00</seg> |
</seglistitem> |
<seglistitem> |
<seg>3600</seg><seg>1h0m0s.00</seg><seg>1:00:00.00</seg> |
</seglistitem> |
</segmentedlist> |
</para> |
</refsect1> |
<refsect1 id="environment"> |
<title>ENVIRONMENT</title> |
<variablelist> |
<varlistentry> |
<term><envar>TEMPDIR</envar></term> |
<listitem> |
<para>Fallback temporary directory when <filename>/dev/shm</filename> is not avaiable. |
Due to the big size of temporary files, it is recommended to use |
a temporary directory on a fast filesystem.</para> |
</listitem> |
</varlistentry> |
</variablelist> |
</refsect1> |
<refsect1 id="diagnostics"> |
<title>DIAGNOSTICS</title> |
<para>The default verbosity level will print <package>vcs</package>' progress |
and any errors or warnings on <filename class="devicefile">stderr</filename>.</para> |
<para><option>--quiet</option> can be used to reduce verbosity.</para> |
<para>The verbosity level and <filename class="devicefile">stderr</filename> can be |
controlled through configuration files, see <citerefentry> |
<refentrytitle>vcs.conf</refentrytitle> |
<manvolnum>5</manvolnum> |
</citerefentry>. |
</para> |
<para><command>&dhpackage;</command> provides some return codes, they follow |
the semi-standardised values defined in <filename>sysexits.h</filename>:</para> |
<segmentedlist> |
<segtitle>Code</segtitle> |
<segtitle>Diagnostic</segtitle> |
<seglistitem> |
<seg><errorcode>0</errorcode> (<errorcode>EX_OK</errorcode>)</seg> |
<seg>Program exited successfully.</seg> |
</seglistitem> |
<seglistitem> |
<seg><errorcode>64</errorcode> (<errorcode>EX_USAGE</errorcode>)</seg> |
<seg>Error in the arguments.</seg> |
</seglistitem> |
<seglistitem> |
<seg><errorcode>66</errorcode> (<errorcode>EX_NOINPUT</errorcode>)</seg> |
<seg>Can't access some input file or it has an incorrect format.</seg> |
</seglistitem> |
<seglistitem> |
<seg><errorcode>69</errorcode> (<errorcode>EX_UNAVAILABLE</errorcode>)</seg> |
<seg>Unsatisfied dependency.</seg> |
</seglistitem> |
<seglistitem> |
<seg><errorcode>70</errorcode> (<errorcode>EX_SOFTWARE</errorcode>)</seg> |
<seg>Internal inconsistency (bug).</seg> |
</seglistitem> |
<seglistitem> |
<seg><errorcode>73</errorcode> (<errorcode>EX_CANTCREAT</errorcode>)</seg> |
<seg>Error creating temporary or output files.</seg> |
</seglistitem> |
</segmentedlist> |
</refsect1> |
<refsect1 id="bugs"> |
<!-- Or use this section to tell about upstream BTS. --> |
<title>BUGS</title> |
<para>Recent versions of <package>ImageMagick</package>, <command>mplayer</command> and |
<command>ffmpeg</command> should be used |
for maximum compatibility.</para> |
<para>The upstream's <acronym>BTS</acronym> can be found |
at <ulink url="http://b.outlyer.net"/>.</para> |
</refsect1> |
<refsect1 id="see_also"> |
<title>SEE ALSO</title> |
<!-- In alpabetical order. --> |
<para><citerefentry> |
<refentrytitle>vcs.conf</refentrytitle> |
<manvolnum>5</manvolnum> |
</citerefentry>, <citerefentry> |
<refentrytitle>convert</refentrytitle> |
<manvolnum>1</manvolnum> |
</citerefentry>, <citerefentry> |
<refentrytitle>ffmpeg</refentrytitle> |
<manvolnum>1</manvolnum> |
</citerefentry>, <citerefentry> |
<refentrytitle>mplayer</refentrytitle> |
<manvolnum>1</manvolnum> |
</citerefentry></para> |
</refsect1> |
</refentry> |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/trunk/vcs |
---|
0,0 → 1,0 |
link pkg/vcs |
Property changes: |
Added: svn:special |
+* |
\ 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/tags/1.12:r413 |
Merged /video-contact-sheet/branches/1.0.100a:r364-371 |
Merged /video-contact-sheet/tags/1.12.3:r456-457 |
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/branches/1.12:r409-411 |
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.11.2:r393-406 |
Merged /video-contact-sheet/branches/1.12.1:r416-419 |
Merged /video-contact-sheet/branches/1.12.2:r422-431 |
Merged /video-contact-sheet/branches/1.12.3:r435-454 |
Merged /video-contact-sheet/tags/0.99a:r261 |
Merged /video-contact-sheet/branches/1.0.3b:r276-277 |
Merged /video-contact-sheet/branches/1.0.2b:r270-271 |
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.12.3/pkg/vcs |
---|
0,0 → 1,4645 |
#!/usr/bin/env bash |
# |
# $Rev$ $Date$ |
# |
# vcs |
# Video Contact Sheet *NIX: Generates contact sheets (previews) of videos |
# |
# Copyright (C) 2007, 2008, 2009, 2010, 2011 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>) |
# |
# The full changelog can be found at <http://p.outlyer.net/vcs/files/CHANGELOG> |
declare -r VERSION="1.12.3" |
declare -r RELEASE=1 |
set -e |
# GAWK 3.1.3 to 3.1.5 print decimals (with printf) according to locale (i.e. |
#+decimal comma separator in some locales, which is apparently POSIX correct). |
#+Older and newer versions, though, need either POSIXLY_CORRECT=1, --posix or |
#+ --use-lc-numeric to honour locale. |
# MAWK appears to always use dots. |
# Info: <http://www.gnu.org/manual/gawk/html_node/Conversion.html> |
#export POSIXLY_CORRECT=1 # Immitate behaviour in newer gawk |
export LC_NUMERIC=C |
# 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 3.1 |
if [ "${BASH_VERSINFO[0]}" -lt 3 ] || |
[ "${BASH_VERSINFO[0]}" -eq 3 -a "${BASH_VERSINFO[1]}" -lt 1 ]; then |
echo "Bash 3.1 or higher is required" >&2 |
exit 1 |
fi |
} |
# {{{ # TO-DO |
# * [[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 |
# --undocumented shoehorn -> NONE 1.12 1.13 |
# --funky -> --profile ? ?+1 |
# --end_offset -> --end-offset 1.12 (silent), 1.13 (warn) 1.14 |
# (new var names introduced in 1.12) 1.12 1.14 |
# > Usage of ./vcs.conf is also deprecated since it doesn't mesh well with profiles |
# (and it was a bad placeholder for them). Will remain to be loadable with |
# -C:pwd. |
# Loaded by default up to 1.13 (with warning in 1.13). |
# Not loaded from 1.14 onwards |
# * 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. |
# implementation |
# * Optimisations: |
# - Reduce the number of forks |
# }}} # 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 |
# * ~/.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. |
# 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 |
# 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 |
# Used for feedback |
declare -r NL=$'\012' # Newline |
declare -r TAB=$'\011' # Tab |
# }}} # 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 |
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 -r DEFAULT_END_OFFSET="5.5%" |
# 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) |
# Introduced in 1.12.2: |
# When true (1) uses $FONT_MINCHO to print the filename, otherwise the same |
#+font as the heading is used. |
# See -I and --nonlatin |
declare -i NONLATIN_FILENAMES=0 |
# Output of capturing programs is redirected here |
declare stdout=/dev/null stderr=/dev/null |
# Override-able since 1.11: |
# Height of the thumbnails, by default use same as input |
declare th_height='100%' |
declare interval=$DEFAULT_INTERVAL # Interval of captures (~length/numcaps) |
declare -i numcaps=$DEFAULT_NUMCAPS # Number of captures (~length/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 amount of time is *not* captured from the end of the video |
declare 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 |
# Holds a list of captured frames (to avoid recapturing) |
# Format <timestamp>:<filename>[NL]<timestamp>:<filename>... |
declare CAPTURES= |
# 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_BIN= |
declare FFMPEG_BIN= |
# 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 |
# Stores the names of variables overridden from the command-line, |
#+see cmdline_override() and "--override" |
declare CMDLINE_OVERRIDES="" |
# Implicit error handling (see die()), obviously inspired by C's errno |
# and PHP's die(). Functions adapted to use them allow uses like: |
# some_function arg || die |
# which will exit with the appropriate exit code and print the error message |
# (Introduced in 1.12, still being retrofitted) |
declare -i ERROR_CODE=0 # Exit code associated with the last error |
declare ERROR_MSG= # Error message associated to the last error |
# Used to buffer feedback (see buffered()) |
declare BUFFER= |
# This is only used to exit when -DD is used |
declare -i DEBUGGED=0 # It will be 1 after using -DD |
# See post_getopt_hooks() |
# Format: Priority:Command[:Arguments] (lower priority run sooner) |
declare -a POST_GETOPT_HOOKS=( ) |
declare -i DVD_MODE=0 DVD_TITLE= |
declare -a DVD_TITLES=( ) # Titles for each input DVD, filled by --dvd-title |
declare DVD_MOUNTP= # Mountpoint for DVD, detected & reset for each DVD |
declare DVD_VTS= # VTS, detected & reset for each DVD |
# }}} # Variables |
# {{{ # Configuration handling |
# New override system: This variable maps configuration variables to actual |
#+variables used in the script. Each item in the array follows the syntax: |
# <cfg variable>:<variable>:<flags>:[type constraints] Where: |
#+ cfg variable: is the name of the configuration file variable |
#+ variable: is the name of the actual variable. If empty or '=', it will be |
#+ the same as cfg variable. |
#+ flags can currently be: |
#+ "deprecated=new name": Will print a deprecation warning and suggest to use |
#+ "new name" instead |
#+ "striked": Variable is marked for removal, will print a warning about it |
#+ directing anyone needing it to contact me. Only used for variables |
#+ believed to be no longer needed |
#+ "gone": Variable removed in the current version |
#+ "alias": Marks an alias, duplicate name intended to stay |
#+ "meta": Special variable that will modify other variables (e.g. font_all |
#+ modifies all font_ variables. |
#+ "=": ignore |
#+ type constraints: a character indicating accepted values: |
# n -> Number (Natural, positive Integer) |
# p -> Number, not zero |
# t -> Timestamp |
# b -> Bool |
# h -> Positive, non-zero, number or percentage |
# f -> Float or fraction |
# D -> only $DEC_* constants |
# T -> only $TC_* constants |
# V -> only $V_* constants |
# I -> interval or percentage |
# Note during the switch to the new system most variables will remain unchanged |
# Also, the new system is case insensitive to variable names |
# TODO: Allow 'y', 'n' in booleans |
# TODO: Remove extra coherence_check()'s once constraints are implemented |
declare -ra OVERRIDE_MAP=( |
"user:::" |
"extended_factor:=:=:f" |
"stdout::" |
"stderr::" |
"DEBUG:=:=:b" |
"interval:=:=:t" |
"numcaps:=:=:p" |
"captures:numcaps:alias:n" # Alias |
"GETOPT::" # Note it makes no sense as command-line override |
"columns:cols:=:p" |
"cols:=:alias:p" # Alias |
"DISABLE_SHADOWS:=:=:b" |
"DISABLE_TIMESTAMPS:=:=:b" |
"bg_heading::" |
"bg_sign::" |
"bg_title::" |
"bg_contact::" |
"bg_tstamps::" |
"fg_heading::" |
"fg_sign::" |
"fg_tstamps::" |
"fg_title::" |
"font_heading::" |
"font_sign::" |
"font_tstamps::" |
"font_title::" |
"font_all:=:meta" # see parse_override |
"bg_all:=:meta" |
"fg_all:=:meta" |
"pts_tstamps::" |
"pts_meta::" |
"pts_sign::" |
"pts_title::" |
# Aliases for cosmetic stuff |
"bg_header:bg_heading:alias" |
"bg_signature:bg_sign:alias" |
"bg_footer:bg_sign:alias" |
"bg_sheet:bg_contact:alias" |
"fg_header:fg_heading:alias" |
"fg_signature:fg_sign:alias" |
"fg_footer:fg_sign:alias" |
"font_header:font_heading:alias" |
"font_meta:font_heading:alias" |
"font_signature:font_sign:alias" |
"font_footer:font_sign:alias" |
"pts_heading:pts_meta:alias" |
"pts_header:pts_meta:alias" |
"pts_signature:pts_sign:alias" |
"pts_footer:pts_sign:alias" |
"signature:user_signature:" |
"user_signature::deprecated=signature" |
"quality:output_quality:=:n" |
"output_quality::deprecated=quality:n" |
# TODO: These variables are evaluated to constants, would be better to |
# use some symbolic system (e.g. decoder=f instead of decoder=$DEC_FFMPEG) |
"decoder:=:=:D" |
#"capture_mode:timecode_from:alias:T" |
"timecode_from:=:=:T" |
"verbosity:=:=:V" |
"format:output_format:" |
"output_format:=:deprecated=format" |
"simple_feedback:plain_messages:=:b" |
"plain_messages::deprecated=simple_feedback:b" |
"height:th_height:=:h" |
"th_height::deprecated=height:h" |
"padding:HPAD:=:n" |
"HPAD:=:deprecated=padding:n" |
"nonlatin_font:FONT_MINCHO:" |
"FONT_MINCHO::deprecated=nonlatin_font" |
"NONLATIN_FILENAMES:=:=:b" |
"end_offset:=:=:I" # New, used to have a two-variables assignment before USR_* |
"DEFAULT_END_OFFSET:end_offset:deprecated=end_offset:I" |
# TODO TBA: |
#"noboldfeedback::" # Colour but not bold |
"shoehorned::striked" |
"safe_rename_pattern::striked" |
"MIN_LENGTH_FOR_END_OFFSET::gone:" |
) |
# Load a configuration file |
# File *MUST* exist |
# Configuration files are a series of variable=value assignment; they'll be |
#+evaluated directly so they can refer to other variables (with their value at |
#+the point of the assignment). |
# Quotes shouldn't be used (they'll be kept) |
# Since 1.12 comments can be placed in-line (i.e. after an assignment), |
# Literal '#' can be written as '$#' |
# ';' can be used to mark an end of line, anything after it will be ignored |
#+(making it equivalent to '#'), there's no way to include a literal ';' |
# load_config_file($1 = file, [$2 = type (description) = 'Settings']) |
load_config_file() { |
trace $@ |
local cfgfile=$1 |
local desc=$2 |
[[ $desc ]] || desc='Settings' |
local por= # Parsed override |
local varname= tmp= flag= bashcode= feedback= ov= |
while read line ; do # auto variable $line |
[[ ! $line =~ ^[[:space:]]*# ]] || continue # Don't feed comments |
parse_override "$line" |
por=$RESULT |
if [[ $por ]]; then |
varname=${por/% *} # Everything up to the first space... |
tmp=${por#* } # Rest of string |
flag=${tmp/% *} |
if [[ $flag == '=' ]]; then |
# No need to override... |
feedback="$varname(=)" |
else |
feedback=$varname |
fi |
ov="$ov, $feedback" |
fi |
done <$cfgfile |
[[ -z $ov ]] || inf "$desc from $cfgfile:$NL ${ov:2}" |
# No loaded overrides but errors/warnings to print, do print the file name |
if [[ ( -z $ov ) && $BUFFER ]]; then |
inf "In $cfgfile:" |
fi |
flush_buffered ' ' |
} |
# Loads the configuration files if present |
# load_config() |
load_config() { |
local -a CONFIGS=( /etc/vcs.conf ~/.vcs.conf ~/.vcs/vcs.conf ./vcs.conf ) |
for cfgfile in "${CONFIGS[@]}" ;do |
[[ -f "$cfgfile" ]] || continue |
load_config_file "$cfgfile" |
done |
} |
# Load a profile, if found; fail otherwise |
# Profiles are just configuration files that can be loaded on demand (whereas |
#+config files are always loaded) and be given a name. |
# See load_config_file() for comments on the syntax |
# Locations to be searched, in order: |
#+ 1) ~/.vcs/profiles/NAME.conf |
#+ 2) /usr/local/share/vcs/profiles/NAME.conf |
#+ 3) /usr/share/vcs/profiles/NAME.conf |
#+i.e. files in ~/.vcs/ will prevent loading files named like them in /usr |
# load_profile($1 = profile name) |
load_profile() { |
trace $@ |
local p=$1 prof= |
local -a PATHS=( ~/.vcs/profiles/ /usr/local/share/vcs/profiles/ /usr/share/vcs/profiles/ ) |
if [[ ${p:0:1} == ':' ]]; then |
case $p in |
:list) |
# No need to be efficient here... |
echo "Profiles located:" |
local path= profname= |
# 1) Find all profiles |
# 2) (sed) Extract profile file name |
# 3 & 4) (sort+uniq) Keep only first hits for each name (most precedence) |
# 5) (while) Process each name |
# 6) (for) Re-locate most precedent profile |
# 7) (echo x3) Print <name>[: description] |
# 8) (sed) Indent with ' * ' |
find "${PATHS[@]}" -name '*.conf' 2>/dev/null \ |
| sed -e 's#.*/\(.*\)\.conf#\1#' \ |
| sort | uniq \ |
| while read profname ; do |
for path in "${PATHS[@]}" ; do |
path=$path$profname.conf |
[[ -f $path ]] || continue |
echo -n "$profname" |
# [ ] here contains <space><tab>. Mawk doesn't understand |
# [[:space:]] |
echo -n $(awk 'sub(/#[ ]*vcs:desc:[ ]*/, ": ")' "$path") |
echo |
break |
done |
done \ |
| sed 's/^/ * /' |
exit 0 |
;; |
*) |
ERROR_MSG="Profiles starting with ':' are reserved.$NL"\ |
" Use ':list' to list available profiles." |
ERROR_CODE=$EX_USAGE |
return $ERROR_CODE |
esac |
fi |
for prof in "${PATHS[@]}" ; do |
prof="$prof$p.conf" |
[[ -f $prof ]] || continue |
load_config_file "$prof" 'Profile' |
return 0 |
done |
ERROR_MSG="Profile '$p' not found" |
ERROR_CODE=$EX_USAGE |
return $ERROR_CODE |
} |
# Check value for an overrideable variable against the allowed values |
# check_constraint($1 = variable name, $2 = value [, $3 = public_name]) |
# where public_name is the name to be used for error messages |
check_constraint() { |
local n=$1 v=$2 p=$3 |
# Get constraint |
local map=$(echo "${OVERRIDE_MAP[*]}" | stonl | egrep -i "^$n:") |
[[ $map ]] || return 0 |
local ct=$(cut -d':' -f4 <<<"$map") |
[[ $ct ]] || return 0 |
local checkfn= domain= |
case $ct in |
n) checkfn=is_number ; domain=numbers ;; |
p) checkfn=is_positive ; domain='numbers greater than zero' ;; |
t) checkfn=is_interval ; domain=intervals ;; |
b) checkfn=is_bool ; domain='boolean values (0 or 1)' ;; |
h) checkfn=is_pos_or_percent ; domain='positive numbers or percentages' ;; |
f) checkfn=is_float_or_frac ; domain='positive numbers or fractions' ;; |
D) checkfn=is_decoder ; domain='$DEC_FFMPEG or $DEC_MPLAYER' ;; |
T) checkfn=is_tcfrom ; domain='$TC_INTERVAL or $TC_INTERVAL' ;; |
V) checkfn=is_vlevel ; domain='verbosity levels ($V_.*)' ;; |
I) checkfn=is_interv_or_percent ; domain='intervals or percentages' ;; |
esac |
if [[ $checkfn ]] && ! $checkfn "$v" ; then |
[[ $p ]] || p=$n |
ERROR_MSG="Illegal value for '$p', only $domain are accepted" |
ERROR_CODE=$EX_USAGE |
return $ERROR_CODE |
fi |
return 0 |
} |
# Parse an override and set its value. |
# Input should be a var=value assignment. Also sets USR_<variable>. |
# The global variable $RESULT is set with the format: |
# <variable name> <flag> where |
# * variable name: is the name of the variable to be overridden |
# * flag: is a character indicating the status: "+" for a possible override, |
# "=" for an override that already has the same value |
# Warnings and errors are buffered |
# This function always returns true |
# parse_override($1 = override assignment) |
parse_override() { |
trace $@ |
local o="$1" |
RESULT='' |
# bash 3.1 and 3.2 handle quoted eres differently, using a variable fixes this |
local ERE="^[[:space:]]*[[:alpha:]_][[:alnum:]_]*[[:space:]]*=.*" |
if [[ ! $o =~ $ERE ]] ; then |
return |
fi |
local varname=$(echo "${o/=*}" | sed 's/[[:space:]]//g') # Trim var name |
local lcvarname=$(echo "$varname" | tr '[A-Z]' '[a-z]') |
local mapping=$(echo "${OVERRIDE_MAP[*]}" | stonl | egrep -i "^$lcvarname:") |
[[ $mapping ]] || return 0 |
local varval=${o#*=} # No trimming here (yet) |
# 1) Trim from ; (if present) to finish |
# 2) Trim from # (comments) not "escaped" like '$#' |
# 3) Replace '$#' with '#' |
# 4) Trim whitespace on both ends |
varval=$(sed -e 's/;.*//' -e 's/\([^$]\)#.*/\1/g' -e 's/\$#/#/g' \ |
-e 's/^[[:space:]]*//;s/[[:space:]]*$//' <<<"$varval") |
# Is varval empty? |
[[ $varval ]] || return 0 |
local mvar=$(cut -d':' -f1 <<<"$mapping") |
local ivar=$(cut -d':' -f2 <<<"$mapping") |
local flags=$(cut -d':' -f3 <<<"$mapping") |
local constraints=$(cut -d':' -f4 <<<"$mapping") |
{ [[ $ivar && ( $ivar != '=' ) ]] ; } || ivar="$mvar" |
# Note using "\$(echo $varval)" would allow a more flexible syntax but |
#+enforce special handling of escaping, which with the currently available |
#+settings is not worth the effort |
# Resolve symbolic variables to check their actual value |
eval varval="\"$varval\"" 2>/dev/null || { # Hide eval's errors |
buffered error "Syntax error: '$o'" |
return 0 |
} |
[[ $varval ]] || return 0 # If empty value, ignore it |
local evcode='' |
if [[ $flags && ( $flags != '=' ) && ( $flags != 'alias' ) ]]; then |
local ERE='^deprecated=' |
if [[ $flags =~ $ERE ]]; then |
local new=$(echo "$flags" | sed 's/^deprecated=//') |
buffered warn "Variable '$varname' will be removed in the future,$NL please use '$new' instead." |
else |
case "$flags" in |
gone) |
buffered error "Variable '$varname' has been removed." |
return 0 |
;; |
striked) |
buffered error "Variable '$varname' is scheduled to be removed in the next release." |
buffered error " Please contact the author if you absolutely need it." |
;; |
meta) |
apply_meta_override "$varname" "$varval" |
RESULT="$varname +" |
return 0; |
;; |
*) return 0 ;; |
esac |
fi |
fi |
[[ -z $constraints ]] || check_constraint $ivar "$varval" $varname || { |
buffered error "$ERROR_MSG" |
return 0 |
} |
eval local curvarval='$'"$ivar" retflag='+' |
if [[ $curvarval == "$varval" ]]; then |
retflag='=' |
else |
if [[ $constraints == 't' ]]; then |
varval=$(get_interval "$varval") |
fi |
# Escape single quotes, since it will be single-quoted: |
varval=${varval//\'/\'\\\'\'} # <<'>> => <<'\''>> |
evcode="$ivar='$varval'; USR_$ivar='$varval'" |
eval "$evcode" |
fi |
# varname, as found in the config file |
RESULT="$varname $retflag" |
} |
# Handle meta configuration variables, variables that, when set, modify the |
# value of (various) others |
# apply_meta_override($1 = actual variable name, $2 = value) |
apply_meta_override() { |
case "$1" in |
font_all) |
buffered inf "font_all => font_heading, font_sign, font_title, font_tstamps" |
parse_override "font_heading=$2" |
parse_override "font_sign=$2" |
parse_override "font_title=$2" |
parse_override "font_tstamps=$2" |
;; |
fg_all) |
buffered inf "fg_all => fg_heading, fg_sign, fg_title, fg_tstamps" |
parse_override "fg_heading=$2" |
parse_override "fg_sign=$2" |
parse_override "fg_tstamps=$2" |
parse_override "fg_title=$2" |
;; |
bg_all) |
buffered inf "bg_all => bg_heading, bg_contact, bg_sign, bg_title, bg_tstamps" |
parse_override "bg_heading=$2" |
parse_override "bg_contact=$2" |
parse_override "bg_sign=$2" |
parse_override "bg_title=$2" |
parse_override "bg_tstamps=$2" |
;; |
esac |
} |
# Do an override from the command line |
# cmdline_override($1 = override assignment) |
#+e.g. cmdline_override 'decoder=$DEC_FFMPEG' |
cmdline_override() { |
trace $@ |
parse_override "$1" |
local r=$RESULT |
[[ $r ]] || return 0 |
local varname=${r/% *} # See load_config() |
local tmp=${r#* } |
local flag=${tmp/% *} |
if [[ $flag == '=' ]]; then |
varname="$varname(=)" |
fi |
CMDLINE_OVERRIDES="$CMDLINE_OVERRIDES, $varname" |
} |
# Call any pending commands required by the command-line arguments |
# This is used to defer some calls and to flush buffers |
post_getopt_hooks() { |
local cback= EX=0 |
local funcs=$(echo "${POST_GETOPT_HOOKS[*]}" | stonl | sort -n | uniq |\ |
cut -d':' -f2- ) |
for cback in $funcs ; do |
local fn=${cback/:*} |
local arg=${cback/*:} |
[[ $arg != $cback ]] || arg='' |
$fn $arg |
done |
} |
# Print the list of command-line overrides |
cmdline_overrides_flush() { |
trace $@ |
if [[ $CMDLINE_OVERRIDES ]]; then |
inf "Overridden settings from command line:$NL ${CMDLINE_OVERRIDES:2}" |
fi |
if [[ $BUFFER ]]; then |
[[ $CMDLINE_OVERRIDES ]] || warn "In command-line overrides:" |
flush_buffered ' ' |
fi |
} |
# }}} # Configuration handling |
# {{{ # Convenience functions |
#### {{{{ # Type checkers: Return true if input is of a certain type |
#### All take exactly one argument and print nothing |
## Natural number |
is_number() { |
# With '[[...]]', strings '-eq'uals 0, test if it's actually 0 |
#+or otherwise a valid number. Must return 1 on error. |
[[ ( $1 == '0' ) || ( $1 -gt 0 ) ]] 2>/dev/null || return 1 |
} |
## Number > 0 |
is_positive() { is_number "$1" && [[ $1 -gt 0 ]]; } |
## Bool (0 or 1) |
is_bool() { [[ ($1 == '0') || ($1 == '1') ]] 2>/dev/null ; } |
## Float (XX.YY; XX.; ;.YY) (.24=0.24) |
is_float() { local P='^([0-9]+\.?([0-9])?+|(\.[0-9]+))$' ; [[ $1 =~ $P ]] ; } |
## Percentage (xx% or xx.yy%) |
is_percentage() { |
local P='^([0-9]+\.?([0-9])?+|(\.[0-9]+))%$' |
[[ $1 =~ $P ]] |
} |
## Interval |
is_interval() { |
local i=$(get_interval "$1" || true) |
[[ $i ]] && fptest $i -gt 0 |
} |
## Interval or percentage |
is_interv_or_percent() { |
is_percentage "$1" || is_interval "$1" |
} |
## Positive or percentage |
is_pos_or_percent() { |
is_number "$1" && [[ $1 -gt 0 ]] || is_percentage "$1" |
} |
## Float (>=0) or fraction |
is_float_or_frac() { |
{ is_fraction "$1" || is_float "$1" ; } && fptest "$1" -ge 0 |
} |
## Fraction, strictly (X/Y, but no X; Y!=0) |
is_fraction() { |
local P='^[0-9]+/[0-9]+$' |
[[ $1 =~ $P ]] && { |
local d=${1#*/} # .../X |
[[ $d -ne 0 ]] |
} |
} |
## Decoder ($DEC_* constants) |
is_decoder() { [[ $1 == $DEC_FFMPEG || $1 == $DEC_MPLAYER ]]; } |
## Time calculation source ($TC_* constants) |
is_tcfrom() { [[ $1 == $TC_INTERVAL || $1 == $TC_NUMCAPS ]]; } |
### Verbosity level ($V_* constants) |
is_vlevel() { |
is_number "$1" && \ |
[[ ($1 -eq $V_ALL) || ($1 -eq $V_NONE) || ($1 -eq $V_ERROR) || \ |
($1 -eq $V_WARN) || ($1 -eq $V_INFO) ]] |
} |
#### }}}} # End of type checkers |
# 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 |
(( n += ( d - r ) , 1 )) |
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 "[[ \"'$1' '$2' '$3'\" ]] && false" && return $EX_SOFTWARE |
esac |
# Empty operands |
if [[ ( -z $1 ) || ( -z $3 ) ]]; then |
assert "[[ \"'$1'\" && \"'$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() { |
local ERE='\.' |
[[ $1 =~ $ERE ]] || { 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 old code (default bc -l) |
# TODO: gawk and mawk differ in how to handle stuff like div by zero: |
# gawk errors, mawk prints inf. Should somehow handle inf and nan |
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() { |
if [[ $1 ]]; then |
awk '{gsub(" ", "\n");print}' <<<"$1" | egrep -v '^$' |
else |
awk '{gsub(" ", "\n");print}' | egrep -v '^$' |
fi |
} |
# Converts newlines to spaces portably |
# nltos([$1 = string]) |
nltos() { |
if [[ $1 ]]; then |
awk '{printf "%s ",$0}' <<<"$1" | sed 's/ *//' |
else |
awk '{printf "%s ",$0}' | sed 's/ *//' |
fi |
} |
# 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 |
expr match "$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 |
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() { |
awkexf "sqrt($1 ^ 2 + $2 ^ 2)" |
} |
# Get a percentage |
# percent($1 = value, $2 = percentage) |
percent() { |
local pc=${2/%%/} # BASH %% == RE %$ |
awkexf "($1 * $pc) / 100" |
} |
# Rounded percentage |
# rpercent($1 = value, $2 = percentage) |
rpercent() { |
local pc=${2/%%/} |
awkex "int( ($1 * $pc) / 100 + 0.5 )" |
} |
# 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 $@ |
# eval it even if it's numeric to strip leading zeroes. Note the quoting |
if is_number "$1" ; then awkexf "\"$1\"" ; return 0 ; fi |
local s=$(tolower "$1") t r n |
# Only allowed characters |
local ERE='^[0-9smhSMH.]+$' |
[[ $s =~ $ERE ]] || return $EX_USAGE |
# Two consecutive dots are no longer accepted |
# ([.] required for bash 3.1 + bash 3.2 compat) |
[[ ! $s =~ [.][.] ]] || return $EX_USAGE |
# 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 |
local ERE='\.' |
[[ $secs && ( $secs =~ $ERE ) ]] && 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=$(awkexf "$t" 2>/dev/null) |
# Negative and empty intervals |
assert "[[ '$r' && '$t' ]]" |
assert "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() { |
# Must allow non-numbers |
local l; (( l = $1 - ${#2} , 1 )) |
[[ $l -le 0 ]] || printf "%0${l}d" '0' |
echo $2 |
} |
# Get Image Width |
# imw($1 = file) |
imw() { |
identify -format '%w' "$1" |
} |
# Get Image Height |
# imh($1 = file) |
imh() { |
identify -format '%h' "$1" |
} |
# Get the line height used for a certain font and size |
# line_height($1 = font, $2 = size) |
line_height() { |
# Create a small image to see how tall are characters. In my tests, no |
#+matter which character is used it's always the same height. |
convert -font "$1" -pointsize "$2" \ |
label:'F' png:- | identify -format '%h' - |
} |
# 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 "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 soon this won't be needed |
sanitise_rename_pattern() { |
# No quoting! => Regex |
if [[ ! $safe_rename_pattern =~ %e || ! $safe_rename_pattern =~ %N || \ |
! $safe_rename_pattern =~ %b ]]; then |
safe_rename_pattern=$DEFAULT_SAFE_REN_PATT |
fi |
# Hashmarks will break the regex used in safe_rename() |
if [[ $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 $@ |
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 -e "s#%b#$b#g" -e "s#%N#$n#g" -e "s#%e#$ext#g" <<<"$safe_rename_pattern") |
(( 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($1 = image file) |
get_dvd_image_mountpoint() { |
if is_linux ; then |
local lodev=$(/sbin/losetup -j "$1" | 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_BIN=$(type -pf mplayer) || true |
FFMPEG_BIN=$(type -pf ffmpeg) || true |
# Test we can actually use FFmpeg |
[[ $FFMPEG_BIN ]] && { |
# 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_BIN" -formats 2>/dev/null | grep -q 'EV.* png' && \ |
! "$FFMPEG_BIN" -codecs 2>/dev/null | grep -q 'EV.* png' ; then |
warn "FFmpeg can't output to png, won't be able to use it." |
FFMPEG_BIN='' |
nopng=1 |
fi |
} |
# Same for Mplayer |
[[ $MPLAYER_BIN ]] && { |
if ! "$MPLAYER_BIN" -vo help 2>&1 | grep -q 'png' ; then |
warn "MPlayer can't output to png, won't be able to use it." |
MPLAYER_BIN='' |
nopng=1 |
fi |
} |
[[ ( -n $MPLAYER_BIN ) || ( -n $FFMPEG_BIN ) ]] || { |
local pngwarn= |
[[ $nopng -eq 1 ]] && pngwarn=', with PNG output support,' |
error "mplayer and/or ffmpeg$pngwarn are required!" |
(( retval++ ,1 )) |
} |
if [[ ( $decoder -eq $DEC_FFMPEG ) && ( -z $FFMPEG_BIN ) ]]; then |
decoder=$DEC_MPLAYER |
elif [[ ( $decoder -eq $DEC_MPLAYER ) && ( -z $MPLAYER_BIN ) ]]; 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!" |
(( 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." ; |
(( retval++ ,1 )) |
fi |
else |
error "Failed to check ImageMagick version." |
(( 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 |
} |
# |
# Buffering of feedback, usage: |
# buffered warn "my warning" |
# ... |
# flush_buffered |
# buffered($1 = feedback function, $2 = arguments) |
buffered() { |
local grab=$( $1 "$2" 2>&1 ) |
BUFFER=$BUFFER$grab$NL |
} |
# |
# Print buffered feedback to stderr |
# flush_buffered([$1 = indentation]) |
flush_buffered() { |
[[ ${BUFFER[*]} ]] || return 0 |
echo "$BUFFER" | sed -e '$d' -e "s/^/$1/g" >&2 # sed: delete last line, indent with $1 |
BUFFER='' |
} |
# |
# trace(... = function arguments) |
trace() { |
[[ $DEBUG -eq 1 ]] || return 0 |
echo "[TRACE]: $(caller 0 | cut -d' ' -f2) $*" >&2 |
} |
# Print an error message and exit |
# die([$1 = message [, $2 = exit_code]]) |
# If no message is provided, use $ERROR_MSG |
# If no exit_code is provided, use $ERROR_CODE |
die() { |
local m=$1 ec=$2 |
[[ $ec ]] || ec=$ERROR_CODE |
[[ $ec ]] || ec=1 |
[[ $m ]] || m=$ERROR_MSG |
error "$m" |
exit $ec |
} |
# |
# 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 && 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 >/dev/null |
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 |
# Note: Use single quotes for globals, no need to expand in release |
# assert(... = code) |
assert() { |
[[ $RELEASE -eq 0 ]] || { |
function assert { :; } # Redefine to avoid check |
} |
local c=$(caller 0) # <num> <func> <file> |
c=${c% *} # <num> <func> |
local LIN=${c% *} FN=${c#* } |
eval "$@" || { |
error "Internal error at $FN:$LIN: $@" |
local cal=$(caller 1) |
[[ $level ]] && error " Stack trace:" |
local level=2 |
while [[ $cal ]]; do |
cal=${cal% *} |
error " ${cal#* }:${cal% *}" |
cal=$(caller $level) |
(( level++ )) |
done |
exit $EX_SOFTWARE |
} |
} |
# Conditional assertion |
# assert_if($1 = condition, $2 = assert if $1 true) |
assert_if() { |
[[ $RELEASE -eq 1 ]] && return |
if eval "$1" ; then |
assert "$2" |
fi |
} |
# }}} # Convenience functions |
# {{{ # Core functionality |
# Creates a new temporary directory |
# create_temp_dir() |
create_temp_dir() { |
trace $@ |
[[ -z $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 $@ |
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 $@ |
local mode=f lineno |
if [[ $mode == 'f' ]]; 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 $@ |
local st=0 end=${VID[$LEN]} tcfrom=$1 tcint=$2 tcnumcaps=$3 eo=0 |
local eff_eo= # Effective end_offset (for percentages) |
# 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 is_percentage $end_offset ; then |
eff_eo=$(percent $end $end_offset) |
else |
eff_eo=$(get_interval "$end_offset") |
fi |
if fptest $totime -le 0 ; then # If no totime is set, use end_offset |
eo=$eff_eo |
local runlen=$(awkexf "$end - $st") |
if fptest "($end-$eo-$st)" -le 0 ; then |
if fptest "$eo" -gt 0 && [[ -z $USR_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") |
local last= |
while fptest $stamp -le "$bound"; do |
assert fptest $stamp -ge 0 |
LTC=( "${LTC[@]}" "$stamp" ) |
last=$stamp |
stamp=$(keepdecimals_lower $(awkexf "$stamp + $inc") 3) |
done |
inf "Capturing in range [$(pretty_stamp $inc)-$(pretty_stamp $last)]. Total length: $(pretty_stamp ${VID[$LEN]})" |
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 $@ |
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 |
# capture($1 = filename, $2 = second, $3 = output file) |
capture() { |
trace $@ |
local f=$1 stamp=$2 ofile=$3 |
# globals: $shoehorned $decoder |
# Avoid recapturing if timestamp is already captured. |
# The extended set includes the standard set so when using the extended mode |
#+this will avoid some captures, specially with mplayer, since it doesn't |
#+have ms precission |
local key= |
# Normalise key values' decimals |
if [[ $decoder -eq $DEC_MPLAYER ]]; then |
key=$(awkex "int($stamp)") |
else |
key=$(awkex $stamp) |
fi |
local cached=$(grep "^$key:" <<<"$CAPTURES") |
if [[ $cached ]]; then |
cp "${cached#*:}" "$ofile" # TODO: Is 'cp -s' safe? |
return $? |
fi |
assert '[[ $decoder -eq $DEC_MPLAYER || $decoder -eq $DEC_FFMPEG ]]' |
if [[ $decoder -eq $DEC_MPLAYER ]]; then |
mplayer_capture "$f" "$ofile" "$stamp" |
elif [[ $decoder -eq $DEC_FFMPEG ]]; then |
# FIXME: ffmpeg can put the temporary file anywhere |
ffmpeg_capture "$f" "$ofile" "$stamp" |
fi || { |
[[ $decoder -ne $DEC_MPLAYER ]] || stamp=${stamp/%.*} |
error "Failed to capture frame at $(pretty_stamp $stamp) (${stamp}s)" |
return $EX_SOFTWARE |
} |
CAPTURES="$CAPTURES$key:$ofile$NL" |
} |
# Applies all individual vidcap filters |
# filter_vidcap($1 = filename, $2 = timestamp, $3 = width, $4 = height, $5 = context, $6 = index[1..]) |
filter_vidcap() { |
trace $@ |
# 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 $@ |
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 $@ |
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 $@ |
# 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 $@ |
# 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 $@ |
# 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 $@ |
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' " |
(( 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 $@ |
$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 $@ |
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 $@ |
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 $@ |
# 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 ! mplayer_capture "$f" "IGNOREME" "$ts" "-vf scale=96:96"; then |
ret=1 |
fi |
elif [[ $decoder -eq $DEC_FFMPEG ]]; then |
tempfile=$(new_temp_file '-safelen.png') |
if ! ffmpeg_capture "$f" "$tempfile" "$ts" "-s 96x96"; then |
ret=1 |
fi |
else |
assert 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 $@ |
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 $(awkexf "$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" ;; |
VP80) vcodec="VP8" ;; |
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" ;; |
# 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" ;; |
vp8) mpid="VP80" ;; |
# 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= |
local ERE='[ -]' |
if [[ $acid =~ $ERE ]]; 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" ;; |
# Other versions of R.A. listed at Wikipedia/RealAudio |
sipr) acodec="RealAudio SIPR" ;; # RA 4/5 |
cook) acodec="RealAudio Cook" ;; # RA 6 |
*) # 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 |
# {{{{ # Mplayer support |
# Try to identify video properties using mplayer |
# Fills $MPLAYER_CACHE with the relevant output and $MPLAYER_ID with |
# the actual values. See identify_video() |
# mplayer_identify($1 = file) |
mplayer_identify() { |
trace $@ |
[[ $MPLAYER_BIN ]] || 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_BIN" -benchmark -ao null -vo null -identify -frames 0 \ |
-quiet "$f" 2>"$stderr" | grep ^ID) |
else |
MPLAYER_CACHE=$("$MPLAYER_BIN" -benchmark -ao null -vo null -identify -frames 0 \ |
-quiet -dvd-device "$f" dvd://$DVD_TITLE \ |
2>"$stderr" | 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 |
MPLAYER_ID=("${mi[@]}") |
} |
# Capture a frame with mplayer |
# mplayer_capture($1 = inputfile, $2 = output file, $3 = timestamp[, $4 = extra opts]) |
mplayer_capture() { |
trace $@ |
# Note mplayer CAN'T set the output filename, newer mplayer can set output |
#+dir though. |
local f="$1" |
local cap=00000005.png o=$2 |
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_BIN" -sws 9 -ao null -benchmark -vo "png:z=0" -quiet \ |
-frames 5 -ss "$ts" $shoehorned -dvd-device "$f" \ |
$4 "dvd://$DVD_TITLE" |
else |
"$MPLAYER_BIN" -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 $cap ) && ( '0' != "$(du "$cap" | cut -f1)" ) ]] && { |
mvq "$cap" "$o" |
} |
} |
# }}}} # Mplayer support |
# {{{{ # FFmpeg support |
# Try to identify video properties using ffmpeg |
# Fills $FFMPEG_CACHE with the relevant output and $FFMPEG_ID with |
# the actual values. See identify_video() |
# mplayer_identify($1 = file) |
ffmpeg_identify() { |
trace $@ |
[[ $FFMPEG_BIN ]] || return |
local f="$1" |
# DVD Devices *MUST* be mounted for the identifying process to even start |
assert '[[ ( $DVD_MODE -eq 0 ) || ( $DVD_MOUNTP ) ]]' |
if [[ $DVD_MODE -eq 1 ]]; then |
local vfile="$DVD_MOUNTP/VIDEO_TS/VTS_${DVD_VTS}_0.VOB" |
if [[ ! -r $vfile ]]; then |
error "Failed to locate mounted DVD. Detection will be less accurate." |
return 0 # We can continue anyway |
fi |
f="$vfile" |
fi |
# XXX: FFmpeg detects mpeg1video in DVDs?? |
local fi=( ) vs= as= obs= vsid= |
# FFmpeg is relatively new, introduced in 1.0.99 so it needs more testing |
FFMPEG_CACHE=$("$FFMPEG_BIN" -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 -e 's/:/h/' -e '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 0 ]] || fi[$LEN]='' |
fi[$VCNAME]=$(get_vcodec_name $(translate_ffmpeg_vcodec_id "${fi[$VCODEC]}")) |
fi[$ACNAME]=$(get_acodec_name $(translate_ffmpeg_acodec_id "${fi[$ACODEC]}")) |
FFMPEG_ID=("${fi[@]}") |
} |
# Capture a frame with ffmpeg |
# ffmpeg_capture($1 = inputfile, $2 = outputfile, $3 = timestamp[, $4 = extra opts]) |
ffmpeg_capture() { |
trace $@ |
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_BIN" -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)" ) ]] |
} |
# }}}} # FFmpeg support |
# 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 $@ |
local RET_NOLEN=3 RET_NODIM=4 |
[[ $MPLAYER_BIN ]] && mplayer_identify "$1" |
# ffmpeg_identify in DVD mode only works when the DVD is mounted: |
[[ ( $DVD_MODE -eq 0 ) && ( $FFMPEG_BIN ) ]] && ffmpeg_identify "$1" |
[[ ( $DVD_MODE -eq 1 ) && ( $FFMPEG_BIN ) && ( $DVD_MOUNTP ) ]] && ffmpeg_identify "$1" |
# Fail early if none detected length |
[[ ( -z ${MPLAYER_ID[$LEN]} ) && ( -z ${FFMPEG_ID[$LEN]} ) ]] && return $RET_NOLEN |
assert '[[ $MPLAYER_BIN || $FFMPEG_BIN ]]' |
# Classic mode, use both mplayer and ffmpeg when available |
if [[ $MPLAYER_BIN && $FFMPEG_BIN ]]; then |
# By default take mplayer's values |
VID=("${MPLAYER_ID[@]}") |
# 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 ${MPLAYER_ID[$FPS]} ]] && VID[$FPS]=${FFMPEG_ID[$FPS]} |
[[ ( -n ${MPLAYER_ID[$FPS]} ) && ( -n ${FFMPEG_ID[$FPS]} ) ]] && { |
# Trust ffmpeg if it has three decimals OR if mplayer is probably-wrong |
local ffps=${FFMPEG_ID[$FPS]} |
local ERE='\.[0-9][0-9][0-9]' |
if [[ $ffps =~ $ERE ]]; then |
VID[$FPS]=$ffps |
elif fptest "${MPLAYER_ID[$FPS]}" -gt 500; then |
VID[$FPS]=$ffps |
fi |
} |
# It doesn't appear to need any workarounds for num. channels either |
[[ ${FFMPEG_ID[$CHANS]} ]] && VID[$CHANS]=${FFMPEG_ID[$CHANS]} |
[[ ${FFMPEG_ID[$ASPECT]} ]] && VID[$ASPECT]=${FFMPEG_ID[$ASPECT]} |
# There's a huge inconsistency with some files, both mplayer vs ffmpeg |
# same application on different OSes |
local fflen=${FFMPEG_ID[$LEN]} mplen=${MPLAYER_ID[$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 $(awkexf "($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_BIN ]]; then |
# Must do with mplayer only... |
VID=("${MPLAYER_ID[@]}") |
# 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_BIN ]]; then |
# Must do with mplayer only... |
VID=("${FFMPEG_ID[@]}") |
# So far I know of no weird results. Yet. |
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_BIN ]]; then |
# FPS at least with two decimals |
if [[ $(awkex "int(${VID[$FPS]})") == ${VID[$FPS]} ]]; then |
VID[$FPS]="${VID[$FPS]}.00" |
fi |
fi |
local mfps="${MPLAYER_ID[$FPS]}" |
if [[ ( $QUIRKS -eq 0 ) && ( -n $MPLAYER_BIN ) ]] && 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 $@ |
[[ $MPLAYER_BIN ]] && echo "Mplayer: $MPLAYER_BIN" |
[[ $FFMPEG_BIN ]] && echo "FFmpeg: $FFMPEG_BIN" |
[[ $MPLAYER_BIN ]] && cat <<-EODUMP |
=========== Mplayer Identification =========== |
Length: $(pretty_stamp ${MPLAYER_ID[$LEN]}) |
Video |
Codec: ${MPLAYER_ID[$VCODEC]} (${MPLAYER_ID[$VCNAME]}) |
Dimensions: ${MPLAYER_ID[$W]}x${MPLAYER_ID[$H]} |
FPS: ${MPLAYER_ID[$FPS]} |
Aspect: ${MPLAYER_ID[$ASPECT]} |
Audio |
Codec: ${MPLAYER_ID[$ACODEC]} (${MPLAYER_ID[$ACNAME]}) |
Channels: ${MPLAYER_ID[$CHANS]} |
============================================== |
EODUMP |
local ffl="${FFMPEG_ID[$LEN]}" |
[[ $ffl ]] && ffl=$(pretty_stamp "$ffl") |
if [[ ( -z $ffl ) && ( $DVD_MODE -eq 1 ) ]]; then |
ffl="(unavailable in DVD mode)" |
fi |
[[ $FFMPEG_BIN ]] && cat <<-EODUMP |
=========== FFmpeg Identification =========== |
Length: $ffl |
Video |
Codec: ${FFMPEG_ID[$VCODEC]} (${FFMPEG_ID[$VCNAME]}) |
Dimensions: ${FFMPEG_ID[$W]}x${FFMPEG_ID[$H]} |
FPS: ${FFMPEG_ID[$FPS]} |
Aspect: ${FFMPEG_ID[$ASPECT]} |
Audio |
Codec: ${FFMPEG_ID[$ACODEC]} (${FFMPEG_ID[$ACNAME]}) |
Channels: ${FFMPEG_ID[$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 $@ |
# 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:$NL$list" |
fi |
fi |
# Bias towards the Sazanami family |
shopt -s nocasematch |
local ERE='sazanami' |
if [[ $candidates =~ $ERE ]]; then |
FONT_MINCHO=$(grep -i 'sazanami' <<<"$candidates" | head -1) |
else |
FONT_MINCHO=$(head -1 <<<"$candidates") |
fi |
shopt -u nocasematch |
} |
# Checks if the provided arguments make sense and are allowed to be used |
#+together. When an incoherence is found, sets some sane values if reasonable |
#+or fails otherwise. |
coherence_check() { |
trace $@ |
# 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 |
# In case it's 0/0 or 0.0 since they aren't rejected |
if fptest "$extended_factor" -eq 0 ; then |
extended_factor=0 |
fi |
if [[ ( $decoder -eq $DEC_MPLAYER ) && ( -z $MPLAYER_BIN ) ]]; then |
inf "No mplayer available. Using ffmpeg only." |
decoder=$DEC_FFMPEG |
elif [[ ( $decoder -eq $DEC_FFMPEG ) && ( -z $FFMPEG_BIN ) ]]; then |
inf "No ffmpeg available. Using mplayer only." |
decoder=$DEC_MPLAYER |
fi |
if [[ $DVD_MODE -eq 1 ]]; then |
# Since 1.12 DVD mode can work with multiple inputs too |
# 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_BIN ]]; 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= |
local -a filts=( ) |
if [[ $DISABLE_TIMESTAMPS -eq 0 ]] && |
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[@]}" ) |
# Interval=0 == default interval |
fptest "$interval" -eq 0 && interval=$DEFAULT_INTERVAL |
# If in non-latin mode and no nonlatin font has been picked try to pick one. |
# Should it fail, fallback to latin font. |
if [[ ( $NONLATIN_FILENAMES -eq 1 ) && ( -z $FONT_MINCHO ) ]]; then |
set_extended_font || { |
# set_extended_font already warns about lack of fonts |
warn " Falling back to latin font" |
NONLATIN_FILENAMES=0 |
FONT_MINCHO="$font_heading" |
} |
fi |
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() { |
trace $@ |
# Any default font in use? If all of them are overridden, return |
if [[ $USR_font_heading && $USR_font_title && \ |
$USR_font_tstamps && $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 |
local dvs='' |
if [[ -d /usr/local/share/fonts ]]; then |
dvs=$(find /usr/local/share/fonts/ -type f -iname 'dejavusans.ttf') |
fi |
if [[ ( -z $dvs ) && ( -d /usr/share/fonts ) ]]; then |
dvs=$(find /usr/share/fonts/ -type f -iname 'dejavusans.ttf') |
fi |
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 Sanitation: |
font_heading: $font_heading |
font_title : $font_title |
font_tstamps: $font_tstamps |
font_sign : $font_sign |
EOFF |
} |
# Main function. |
# Creates the contact sheet. |
# process($1 = file) |
process() { |
trace $@ |
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 |
CAPTURES='' |
DVD_MOUNTP= DVD_TITLE= # Re-set for each file |
if [[ $DVD_MODE -eq 1 ]]; then |
local dvdn=$(realpathr "$f") |
# Is it an ISO? |
if [[ -f $dvdn ]]; then |
DVD_MOUNTP=$(get_dvd_image_mountpoint "$dvdn") |
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 |
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_MOUNTP=$(mount | grep -o "^$dvdn *on [^ ]*" | cut -d' ' -f3) |
dvdn="DVD $f" |
fi |
if [[ ! -r $f ]]; then |
error "Can't access DVD ($f)" |
return $EX_NOINPUT |
fi |
inf "Processing $dvdn..." |
unset dvdn |
DVD_TITLE=${DVD_TITLES[0]} |
DVD_TITLES=( "${DVD_TITLES[@]:1}" ) # shift array |
if [[ ( -z $DVD_TITLE ) || ( $DVD_TITLE == '0' ) ]]; then |
local dt="$(lsdvd "$f" 2>/dev/null | grep 'Longest track:' | \ |
cut -d' ' -f3- | sed 's/^0*//')" |
if ! is_number "$dt" ; then |
error "Failed to autodetect longest DVD title for '$f'" |
exit $EX_INTERNAL |
fi |
DVD_TITLE=$dt |
unset dt |
fi |
DVD_VTS=$(lsdvd -t$DVD_TITLE -v "$f" 2>/dev/null | grep -o 'VTS: [0-9]*' | cut -d' ' -f2) |
inf "Using DVD Title #$DVD_TITLE (VTS: $DVD_VTS) for '$f'" |
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" && [[ $th_height != '100%' ]]; then |
vidcap_height=$(rpercent ${VID[$H]} ${th_height}) |
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 |
if [[ $aspect_ratio -eq -2 ]]; then |
[[ ${VID[$ASPECT]} ]] && aspect_ratio=0 || aspect_ratio=-1 |
elif [[ $aspect_ratio -eq 0 ]]; then |
if [[ ${VID[$ASPECT]} ]]; then |
# Aspect ratio in file headers, honor it |
aspect_ratio=$(awkexf "${VID[$ASPECT]}") |
else |
aspect_ratio=$(awkexf "${VID[$W]} / ${VID[$H]}") |
fi |
elif [[ $aspect_ratio -eq -1 ]]; 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 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') |
# If the temporal vidcaps for mplayer already exist, abort |
if [[ $decoder -eq $DEC_MPLAYER ]]; then |
for f_ in 1 2 3 4 5; 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[@]}" '00000005.png' ) |
# 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 ${VID[$LEN]} ; then (( ++n )) && continue ; fi |
pretty=$(pretty_stamp $stamp) |
inf "Generating highlight #${n}/${#HLTIMECODES[@]} ($pretty)..." |
hlcapfile=$(new_temp_file "-hl-$(pad 6 $n).png") |
capture "$f" $stamp "$hlcapfile" || return $? |
filter_vidcap "$hlcapfile" $pretty $vidcap_width $vidcap_height $CTX_HL $n || { |
local r=$? |
error "Failed to apply transformations to the capture." |
return $r |
} |
capfiles=( "${capfiles[@]}" "$hlcapfile" ) |
(( ++n )) |
done |
assert "[[ '"$n"' -gt 1 ]]" |
(( 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 |
local capfile pretty n=1 |
unset capfiles ; local -a capfiles ; local tfile= |
for stamp in $(clean_timestamps "${TIMECODES[*]}"); do |
pretty=$(pretty_stamp $stamp) |
inf "Generating capture #${n}/${#TIMECODES[*]} ($pretty)..." |
# identified by capture number, padded to 6 characters |
tfile=$(new_temp_file "-cap-$(pad 6 $n).png") |
capture "$f" $stamp "$tfile" || return $? |
filter_vidcap "$tfile" $pretty $vidcap_width $vidcap_height $CTX_STD $n || return $? |
capfiles=( "${capfiles[@]}" "$tfile" ) |
(( n++ )) |
done |
#filter_all_vidcaps "${capfiles[@]}" |
assert "[[ '"$n"' -gt 1 ]]" |
(( 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 $(awkex "int(${#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 |
(( w=vidcap_width/2-HPAD, h=vidcap_height*w/vidcap_width ,1 )) |
assert "[[ ( '"$w"' -gt 0 ) && ( '"$h"' -gt 0 ) ]]" |
for stamp in $(clean_timestamps "${TIMECODES[*]}"); do |
pretty=$(pretty_stamp $stamp) |
capfile=$(new_temp_file "-excap-$(pad 6 $n).png") |
inf "Generating capture from extended set: ${n}/${#TIMECODES[*]} ($pretty)..." |
capture "$f" $stamp "$capfile" || return $? |
filter_vidcap "$capfile" $pretty $w $h $CTX_EXT $n || return $? |
capfiles=( "${capfiles[@]}" "$capfile" ) |
(( n++ )) |
done |
(( 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 [[ -n $HLTIMECODES || ( $extended_factor != '0' ) ]]; then |
inf "Merging contact sheets..." |
if [[ -n $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 |
local csw2= ; (( csw2 = (width-csw) / 2 )) |
convert \( -size ${csw2}x$csh xc:transparent \) "$output" \ |
\( -size ${csw2}x$csh xc:transparent \) +append "$output" |
unset csh csw2 |
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 |
local hlw2= ; (( hlw2=(width - hlw) / 2 )) |
convert \( -size ${hlw2}x$hlh xc:transparent \) "$hlfile" \ |
\( -size ${hlw2}x$hlh xc:transparent \) +append "$hlfile" |
unset hlw2 |
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 |
local exw2 = ; (( exw2=(width - exv) / 2 )) |
convert \( -size ${exw2}x$exh xc:transparent \) "$extoutput" \ |
\( -size ${exw2}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]}" |
meta2="$meta2${NL}Format: $vcodec / $acodec${NL}FPS: ${VID[$FPS]}" |
local signature |
if [[ $anonymous_mode -eq 0 ]]; then |
signature="$user_signature $user${NL}with $PROGRAM_SIGNATURE" |
else |
signature="Created with $PROGRAM_SIGNATURE" |
fi |
local headwidth=$(imw "$output") headheight= |
local heading=$(new_temp_file .png) |
# Add the title if any |
if [[ $title ]]; then |
local tlheight=$(line_height "$font_title" "$pts_title") |
convert \ |
\( \ |
-size ${headwidth}x$tlheight "xc:$bg_title" \ |
-font "$font_title" -pointsize "$pts_title" \ |
-background "$bg_title" -fill "$fg_title" \ |
-gravity Center -annotate 0 "$title" \ |
\) \ |
-flatten \ |
"$output" -append "$output" |
unset tlheight |
fi |
local fn_font= # see $NONLATIN_FILENAMES |
if [[ $NONLATIN_FILENAMES -ne 1 ]]; then |
fn_font="$font_heading" |
else |
fn_font="$FONT_MINCHO" |
fi |
# Create a small image to see how tall are characters. In my tests, no matter |
#+which character is used it's always the same height. |
local lineheight=$(line_height "$font_heading" "$pts_meta") |
# Since filename can be set in a different font check it too |
if [[ $fn_font != $font_heading ]]; then |
local fnlineheight=$lineheight |
fnlineheight=$(line_height "$fn_font" "$pts_meta") |
[[ $fnlineheight -le $lineheight ]] || lineheight=$fnlineheight |
unset fnlineheight |
fi |
headheight=$(( lineheight * 3 )) |
# 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 "$f" 2>/dev/null | grep -o 'Disc Title: .*' | cut -d' ' -f3-) |
# There's no guarantee that titles are on separate VTS, I have no idea |
# how to compute the actual title size |
if [[ $DVD_MOUNTP ]]; then |
filename_label="Disc label" |
filename_value="$dvd_label" |
filesize_label="Titleset 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 "$f") $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 |
local signlh=$(line_height "$font_sign" "$pts_sign") |
local signheight=$(( 4 + ( signlh * 2 ) )) |
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}x$signheight -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 signheight signlh |
local wanted_name=${OUTPUT_FILES[$FILEIDX]} |
if [[ -n $wanted_name ]]; then |
local ERE='\.[^.]+$' |
if [[ $wanted_name =~ $ERE ]]; 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 |
fi |
[[ -n $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" |
(( 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" |
(( 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.35755975068581946630 #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) |
[[ -n $comm ]] || comm=unnamed |
ret=$($op) || true |
if [[ $ret != "$val" ]] && fptest "$ret" -ne "$val" ; then |
error "Failed test ($comm): '$op $val'. Got result '$ret'." |
(( retval++ ,1 )) # The ,1 ensures '((...))' 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" |
"is_pos_or_percent 33 0 #Positive recognition" |
"is_pos_or_percent 33% 0 #Percent recognition" |
"is_pos_or_percent 4/4% 1 #Percent recognition" |
"is_pos_or_percent % 1 #Percent 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) |
[[ -n $comm ]] || comm=unnamed |
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'" |
(( 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-2011 Toni Corvera" |
} |
# 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_BIN ]] && mpchosen=' [Not available]' |
[[ $MPLAYER_BIN && ( $decoder == $DEC_MPLAYER ) ]] && mpchosen=' [Selected]' |
[[ -z $FFMPEG_BIN ]] && ffchosen=', Not available' |
[[ $FFMPEG_BIN && ( $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 DVD Mode. |
In this mode the input <file>s must be the DVD |
device(s) or ISO(s). When in DVD mode all input files |
must be DVDs. |
Implies -A (auto aspect ratio) |
--dvd-title <arg> DVD title to use. Using 0 (the default) will use the |
longest title. |
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 |
-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. |
-p|--profile <arg> Load profile "arg" |
-C|--config <arg> Load configuration file "arg" |
--generate <config|profile> |
Generate configuration or profile from current settings |
-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 |
# ' # Syntax highlighting bait |
} |
# Print a configuration file generated from the currently active settings |
# generate_config($1 = <config|profile>) |
generate_config() { |
local n=$(echo $1 | tr '[a-z]' '[A-Z]') f= t= x= |
cat <<-EOM |
# --- $n STARTS HERE --- |
# This is a sample configuration file for VCS generated automatically |
# from the command-line with the "--generate $1" command-line option |
# Save it to ~/.vcs.conf or ~/.vcs/vcs.conf to make it the default |
# configuration. |
# OR |
# Save it to ~/.vcs/profiles/something.conf to create a profile named |
# "something". To use this profile run vcs with the "--profile something" |
# (or "-p something") option |
# OR |
# Save it to "something.conf" and load it with "--config something.conf" |
# (or "-C something.conf") |
EOM |
echo "${OVERRIDE_MAP[*]}" | stonl | egrep -v '(deprecated=|alias)' | cut -d':' -f1-2 |\ |
while read ovname ; do |
f=${ovname/:*} |
t=${ovname#*:} |
if [[ ( -z $t ) || ( $t == '=' ) ]]; then t=$f ; fi |
eval v=\$USR_$t |
[[ -z $v ]] || { |
# Symbolic values: |
case "$t" in |
timecode_from) |
x='$TC_NUMCAPS' |
[[ $v -eq $TC_NUMCAPS ]] || x='$TC_INTERVAL' |
v=$x |
;; |
decoder) |
x='$DEC_FFMPEG' |
[[ $v -eq $DEC_FFMPEG ]] || x='$DEC_MPLAYER' |
v=$x |
;; |
verbosity) |
case $v in |
$V_ALL) v='$V_ALL' ;; |
$V_NONE) v='$V_NONE' ;; |
$V_INFO) v='$V_INFO' ;; |
$V_WARN) v='$V_WARN' ;; |
$V_ERROR) v='$V_ERROR' ;; |
esac # verbosity |
;; |
esac |
# Print all names in lowercase |
echo "$(tolower "$f")=$v" |
} |
done |
echo "# vcs:conf:$NL# Generated on $(date)$NL# --- $n ENDS HERE --- " |
exit 0 |
} |
# }}} # 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:VR:Z:o:p:C: \ |
--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:,end-offset:,disable:,dvd,dvd-title:,randomsource:,undocumented:,output:,"\ |
"fullhelp,profile:,"\ |
"jpeg2,nonlatin,generate:,config:" \ |
-- "$@") |
eval set -- "$TEMP" |
while true ; do |
case "$1" in |
-i|--interval) |
check_constraint 'interval' "$2" "$1" || die |
interval=$(get_interval $2) |
timecode_from=$TC_INTERVAL |
USR_interval=$interval |
USR_timecode_from=$TC_INTERVAL |
shift # Option arg |
;; |
-n|--numcaps) |
check_constraint 'numcaps' "$2" "$1" || die |
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 [[ $1 = '-U' ]]; then # -U always provides an argument |
if [[ -n $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 |
fi |
shift |
else # No argument, default handling (try to guess real name) |
username=$(id -un) |
if type -p getent >/dev/null ; then |
user=$(getent passwd "$username" | cut -d':' -f5 |sed 's/,.*//g') |
else |
user=$(grep "^$username:" /etc/passwd | cut -d':' -f5 |sed 's/,.*//g') |
fi |
if [[ -z $user ]]; then |
user=$username |
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|--end-offset) |
check_constraint 'end_offset' "$2" "$1" || die |
is_p='y' |
is_percentage "$2" || is_p='' |
if [[ $is_p ]]; then |
end_offset="$2" |
else |
end_offset=$(get_interval "$2") |
fi |
USR_end_offset="$end_offset" |
unset is_i |
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 ;; |
-F|--ffmpeg) decoder=$DEC_FFMPEG ; USR_decoder=$decoder ;; |
-M|--mplayer) decoder=$DEC_MPLAYER ; USR_decoder=$decoder ;; |
-H|--height) |
check_constraint 'height' "$2" "$1" || die |
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) |
check_constraint 'columns' "$2" "$1" || die |
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 |
if [[ $2 ]]; then |
check_constraint 'extended_factor' "$2" "$1" || die |
extended_factor="$2" |
else |
extended_factor=$DEFAULT_EXT_FACTOR |
fi |
USR_extended_factor=$extended_factor |
shift |
;; |
# Unlike -I, --nonlatin does not accept a font name |
--nonlatin) |
if [[ -z $USR_FONT_MINCHO ]]; then |
NONLATIN_FILENAMES=1 |
USR_NONLATIN_FILENAMES=1 |
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 |
NONLATIN_FILENAMES=1 |
USR_NONLATIN_FILENAMES=1 |
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 |
RE='[a-zA-Z_]+=[^;]*' |
if [[ ! $2 =~ $RE ]]; then |
error "Wrong override format, it should be variable=value. Got '$2'." |
exit $EX_USAGE |
fi |
if [[ $2 =~ 'GETOPT=' ]] ; 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 |
cmdline_override "$2" |
POST_GETOPT_HOOKS=( "${POST_GETOPT_HOOKS[@]}" 1:cmdline_overrides_flush ) |
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 |
[[ -n $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=$(awkexf "$QUIRKS_MAX_REWIND * (2^$n)") |
(( 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=$(awkexf "$QUIRKS_LEN_STEP / (2^$n)") |
(( 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=$(awkexf "$QUIRKS_LEN_STEP * (2^$n)") |
(( 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 |
;; |
*) load_profile "$2" || die |
;; |
esac |
shift |
;; |
-C|--config) |
if echo "$2" | grep -q '^:' ; then |
if [[ $2 = ':pwd' ]]; then |
cfg=./vcs.conf |
else |
error "Configuration names starting with ':' are reserved." |
exit $EX_USAGE |
fi |
else |
cfg=$2 |
fi |
[[ -f $cfg ]] || { |
error "Configuration file '$cfg' not found" |
exit $EX_USAGE |
} |
# ./vcs.conf doesn't need the vcs:conf: mark |
if [[ $2 != ':pwd' ]]; then |
head -5 "$cfg" | grep -q '#[[:space:]]*vcs:conf[[:space:]]*:' || \ |
tail -5 "$cfg" | grep -q '#[[:space:]]*vcs:conf[[:space:]]*:' || { |
error "No vcs:conf: mark found in '$cfg'" |
exit $EX_NOINPUT |
} |
fi |
load_config_file "$cfg" 'Custom configuration' |
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 |
;; |
--dvd-title) |
check_constraint 'dvd_title' "$2" "$1" || die |
DVD_TITLES=( "${DVD_TITLES[@]}" "$2" ) |
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 |
decoder=$DEC_MPLAYER |
aspect_ratio=-2 # Special value: Auto detect only if ffmpeg couldn't |
;; |
-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 |
USR_verbosity=$verbosity |
;; |
-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_BIN=$(realpathr "$(cut -d'=' -f2<<<"$2")") |
assert '[[ -x $FFMPEG_BIN ]]' |
warn "[U] FFMPEG_BIN=$FFMPEG_BIN" |
;; |
# mplayer path |
set_mplayer=*) |
MPLAYER_BIN=$(realpathr "$(cut -d'=' -f2<<<"$2")") |
assert '[[ -x $MPLAYER_BIN ]]' |
warn "[U] MPLAYER_BIN=$MPLAYER_BIN" |
;; |
# Ignore one of the players |
disable_ffmpeg) |
FFMPEG_BIN='' |
warn "FFmpeg disabled" |
assert '[[ $MPLAYER_BIN ]]' |
decoder=$DEC_MPLAYER |
;; |
disable_mplayer) |
MPLAYER_BIN='' |
warn "Mplayer disabled" |
assert '[[ $FFMPEG_BIN ]]' |
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")" |
error "Shoehorning of options is scheduled to be removed in the next version." |
error " Please contact the author if you absolutely need it." |
;; |
debug) |
warn "[U] debug" |
DEBUG=1 |
;; |
# Dump user-set variables and exit [since 1.12] |
uservars) |
echo "${OVERRIDE_MAP[*]}" | stonl | egrep -v '(deprecated=|alias)' | cut -d':' -f1-2 |\ |
while read ovname ; do |
f=${ovname/:*} |
t=${ovname#*:} |
if [[ ( $t ) && ( $t != '=' ) ]]; then f="$t" ; fi |
eval v=\$USR_$f |
[[ -z $v ]] || echo "$(tolower $f)=$v" |
done |
exit 0 |
;; |
*) false ;; |
esac |
shift |
;; |
--generate) |
case "$2" in |
profile|config) |
POST_GETOPT_HOOKS=( "${POST_GETOPT_HOOKS[@]}" \ |
10:generate_config:$2 ) |
;; |
*) |
error "Option --generate must be followed by profile or config" |
exit $EX_USAGE |
;; |
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_BIN |
FFMPEG: $FFMPEG_BIN |
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 |
# Avoid coherence_check if there's no arguments and no cmdline post |
# processing |
[[ -n $1 || -n $POST_GETOPT_HOOKS ]] || { |
[[ $verbosity -eq $V_NONE ]] || show_help |
exit $EX_USAGE |
} |
# More than one argument... |
if [[ -n $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 $? |
} |
# Run after coherence check to clean recoverable incorrect values |
post_getopt_hooks |
# Remaining arguments |
if [[ -z $1 ]]; then |
[[ $verbosity -eq $V_NONE ]] || show_help |
exit $EX_USAGE |
fi |
# TODO: |
# DVD mode + multiple titles is still tricky: |
# --dvd --dvd-title 1 --dvd-title 2 /dev/dvd /dev/dvd |
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 |
# * bash3: [[ STR =~ EREGEX ]] is faster than grep/egrep (no forking) |
# bash 3.2 changed semantics vs bash 3.1 |
# * bash4: |& (inherited from csh?) pipes both stdout and stderr |
# * 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.12.3/pkg/rpm/vcs.spec.in |
---|
0,0 → 1,113 |
# |
# $Rev$ |
# |
# 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} |
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 >= 3.1 |
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 |
make examples/vcs.conf.example |
%install |
make DESTDIR=%buildroot prefix=%{prefix} install |
%clean |
[ ${RPM_BUILD_ROOT} != "/" ] && rm -rf ${RPM_BUILD_ROOT} |
#%post |
# postinst |
#%postun |
# postuninst |
%files |
%defattr(-,root,root) |
# binary |
%{_bindir}/%{name} |
# Profiles |
%{prefix}/share/vcs/profiles/black.conf |
%{prefix}/share/vcs/profiles/mosaic.conf |
%{prefix}/share/vcs/profiles/white.conf |
# Manpage |
%{_mandir}/man1/%{name}.1.gz |
%doc CHANGELOG |
# Config example |
%doc examples/vcs.conf.example |
%changelog |
* Tue Aug 24 2010 - outlyer (at) gmail (dot) com |
- Install manpage |
* Sat Apr 10 2010 - outlyer (at) gmail (dot) com |
- Added profiles and example configuration |
- Use %{prefix} |
* 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.12.3/pkg/CHANGELOG |
---|
0,0 → 1,420 |
1.12.3 (2011-07-17): |
* BUGFIX: Actually handle --ffmpeg and --mplayer [#169] |
* BUGFIX: Correct parsing of -U [#187] |
* OTHER: |
- Fix printing of remaining options on command-line error |
- Switch to a minimum of bash 3.1 [#173] |
- Avoid re-capturing the same frame twice [#122] |
- Use getent instead of /etc/passwd when available |
* INTERNAL: |
- Use of Bash's 'caller' in 'assert' and 'trace' |
- 'assert' prints a call trace on error |
- 'assert_if' |
- Don't use mplayer's length as a ceil for timecode removal [#174] |
1.12.2 (2010-08-24): |
* BUGFIX: Fix cleanup of temporary files (regression since 1.11.2). [#167] |
Submitted by Jason Tackaberry. |
* FEATURES: |
- Added 'fg_all', 'bg_all' and 'font_all' config variables. [#156] |
- Added 'nonlatin_filenames' config variable. [#159] |
- Added identification for VP8 (WebM). [#166] |
* OTHER: |
- Print variable names in lowercase when using --generate. |
1.12.1 (2010-04-23): |
* BUGFIXES: |
- Workaround for cases in which GAWK uses comma as decimal separator. |
Any OS with GAWK 3.1.3 to 3.1.5 was affected (where the environment |
language uses commas, e.g. Debian Lenny with many European languages) |
- Don't try to go on in DVD mode with unreadable ISOs |
1.12: (2010-04-10) |
* New features/tweaks: |
- Loading of random configuration files (--config / -C) |
- Profiles: Similar to above but simpler syntax (--profile / -p) |
- Config/Profile generation from command-line (--generate) |
- Adapt heading, title and footer height to font size (fonts that used |
to get cropped should now be fine) |
* DVD mode cleanup: |
- Command-line switched to match "normal" files: |
Before: |
$ vcs --dvd /dev/dvd 0 or $ vcs -dvd /dev/dvd 1 |
Equivalents now: |
$ vcs --dvd /dev/dvd or $ vcs --dvd --dvd-title 1 /dev/dvd |
* New end-offset behaviour: |
- A 5.5% end offset is applied by default |
- Can be disabled with -E0 or end_offset=0 |
- MIN_LENGTH_FOR_END_OFFSET is no longer used |
* Configuration files cleanup: |
- Simplified or more meaningful names where appropriate (the older |
names will continue to work for a while, and users will be warned) |
"vcs --generate" with no other arguments can be used to translate them |
- Validation of configuration options. |
Incorrect values will be discarded and an error shown; processing will |
continue. |
- Configuration searched in ~/.vcs/vcs.conf too |
- Syntax enhancements: |
> Comments can now be included in-line |
> Putting '#' in a value now requires using the "escaped form" '$#' |
> Semicolons (;) also serve to start comments: When one is found the |
rest of the line is ignored, they continue to be disallowed in values |
i.e. 'tl;dr' will be parsed as 'tl' |
* Other: |
- Accept timecodes and percentages in end_offset, both from the |
command-line and in configuration files |
- Print the start and end timestamps in effect before capturing |
- No longer accept interval zero (used to be re-set to default) |
- Tighter printing of overrides and no longer printed as warning |
- Strickter handing of wrong options |
- Fall back to Helvetica also when no fonts dir is located. Look |
in /usr/local too. |
- --end-offset added as an alias to --end_offset |
- Starting with 1.12 a tarball + makefile is also provided |
* BUGFIXES: |
- Avoid possible (unlikely) usage of scientific notation in internal |
calculations |
- Distinguish between default end offset and user's end offset with the |
same value |
- Handle --nonlatin correctly |
- DVD Mode + FFmpeg identification: Check VOB #0 instead of #1 |
- Don't print escape codes to stdout when testing colour printing |
* Options removed: |
--shoehorn, temporary replacement: --undocumented shoehorn. Will be gone |
in 1.13 |
--mincho, replaced by --nonlatin since 1.11 |
MIN_LENGTH_FOR_END_OFFSET, as explained above, no longer needed |
* INTERNAL: |
- $CFGFILE replaced by ~/.vcs.conf |
- Use -p for profiles instead of -P (used, undocumented, in 1.11) |
1.11.2: (2010-03-19) |
* Added Cook and Sipro (RealAudio 4, 5 & 6) codecs |
* BUGFIXES: |
- Remove extra, empty, temporary dir |
- Use standard awk syntax for exponentiation (pyth_th) |
- Workaround for systems that don't register fonts with ImageMagick |
* DEBUG: Print to stderr when probbing with mplayer too |
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 |
# vim:set ts=3 sw=3 et textwidth=80: # |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.3/pkg/arch/PKGBUILD.in |
---|
0,0 → 1,39 |
# |
# $Rev$ |
# |
# Maintainer (Upstream): Toni Corvera <outlyer@gmail.com> |
# |
# Build with '$ makepkg' on the same directory as this file |
# |
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>=3.1' 'imagemagick>=6.3.5.7' 'mplayer' 'ffmpeg') |
makedepends=('bzip2') |
optdepends=('lsdvd: for DVD support' |
'perl: for DVD support') |
backup=() |
options=('docs' 'zipman') |
source=($url/files/$pkgname-$pkgver.tar.gz) |
md5sums=(@MD5@) #generate with 'makepkg -g' |
sha1sums=(@SHA1@) |
# Debian & Arch didn't agree on this on my first try (???) |
sha256sums=(@SHA256@) |
build() { |
cd $srcdir/$pkgname-$pkgver |
make prepackage |
make install DESTDIR=${pkgdir} prefix=/usr |
install -D $srcdir/$pkgname-$pkgver/examples/vcs.conf.example \ |
${pkgdir}/usr/share/doc/$pkgname/vcs.conf.example |
} |
#man page (TODO) |
# install -D -m644 $pkgname.1 ${pkgdir}/usr/share/man1/$pkgname.1 || return 1 |
# vim:set filetype=sh ts=2 et: # |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.3/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 (>= 3.1), 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.12.3/pkg/debian/changelog |
---|
0,0 → 1,82 |
vcs (1.12.3-upstream.1) experimental; urgency=low |
* New version. |
* debian/control: Bump minimum bash version |
-- Toni Corvera <outlyer@gmail.com> Sun, 17 Jul 2011 18:49:56 +0200 |
vcs (1.12.2-upstream.1) experimental; urgency=medium |
* New version. Medium priority due to temporary files cleanup bug. |
-- Toni Corvera <outlyer@gmail.com> Tue, 24 Aug 2010 20:48:41 +0200 |
vcs (1.12.1-upstream.1) experimental; urgency=medium |
* New version. |
-- Toni Corvera <outlyer@gmail.com> Fri, 23 Apr 2010 13:56:58 +0200 |
vcs (1.12-upstream.1) experimental; urgency=low |
* New version. |
* debian/docs: Install vcs.conf.example |
-- Toni Corvera <outlyer@gmail.com> Sat, 10 Apr 2010 00:57:17 +0200 |
vcs (1.11.2-upstream.1) experimental; urgency=low |
* New version. |
-- Toni Corvera <outlyer@gmail.com> Fri, 19 Mar 2010 00:18:51 +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/tags/1.12.3/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 |
DESTDIR:=$(CURDIR)/debian/vcs |
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) all prepackage |
#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=$(DESTDIR) 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 vcs.1 |
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.12.3/pkg/debian/docs |
---|
0,0 → 1,0 |
examples/vcs.conf.example |
/video-contact-sheet/tags/1.12.3/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/tags/1.12.3/pkg/debian/dirs |
---|
0,0 → 1,0 |
usr/bin |
/video-contact-sheet/tags/1.12.3/pkg/debian/compat |
---|
0,0 → 1,0 |
5 |
/video-contact-sheet/tags/1.12.3/pkg/common.mk |
---|
0,0 → 1,72 |
# $Id$ |
# |
# To be included from GNUmakefile or BSDmakefile |
# To use it directly set VERSION and PACKAGER |
# e.g. make VERSION=1.x PACKAGER=Me <rule> |
# |
# Notes to self: |
# This file should follow only common/portable make syntax and commands |
# Common pitfalls: |
# - $(shell) -> GNU Make, equivalent BSD make: != |
# - install -D -> GNU only (-d is portable) |
# - $(RM) -> empty by default in BSD, set from BSDmakefile |
prefix:=/usr/local |
DESTDIR:=/ |
TGZ=vcs-$(VERSION).tar.gz |
MANDIR:=$(prefix)/share/man/man1/ |
all: |
# Nothing to be done |
dist: vcs.spec |
# Files installed in packages but not outside |
prepackage: examples/vcs.conf.example |
install: |
install -d $(DESTDIR)$(prefix)/bin/ |
install -m755 vcs $(DESTDIR)$(prefix)/bin/vcs |
install -d $(DESTDIR)$(prefix)/share/vcs/profiles |
install -m644 profiles/*.conf $(DESTDIR)$(prefix)/share/vcs/profiles/ |
install -d $(DESTDIR)$(MANDIR) |
install -m644 vcs.1 $(DESTDIR)$(MANDIR)/ |
uninstall: |
$(RM) $(DESTDIR)$(prefix)/bin/vcs |
$(RM) $(DESTDIR)$(MANDIR)/vcs.1 |
for file in profiles/*.conf ; do \ |
$(RM) $(DESTDIR)$(prefix)/share/vcs/profiles/`basename $$file` ; \ |
done |
-rmdir -p $(DESTDIR)$(prefix)/bin |
-rmdir -p $(DESTDIR)$(prefix)/share/vcs/profiles |
-rmdir -p $(DESTDIR)$(MANDIR) |
examples/vcs.conf.example: examples/vcs.conf |
sed -e 's/^/#/;s/^#$$//;s/^##/#/' < $< > $@ |
vcs.spec: rpm/vcs.spec.in vcs |
test "$(VERSION)" # Version (=$(VERSION)) must be detected |
@echo "[vcs.spec]" |
@cat $< | sed 's!@VERSION@!$(VERSION)!g' | \ |
sed 's!@PACKAGER@!$(PACKAGER)!g' > $@ |
# PKGBUILD CAN'T BE INCLUDED in the archive |
PKGBUILD: arch/PKGBUILD.in $(TGZ) vcs |
test "$(VERSION)" # Version (=$(VERSION)) must be detected |
@echo "[PKGBUILD]" |
@MD5=$(shell md5sum -b $(TGZ) | cut -d' ' -f1) ; \ |
SHA1=$(shell sha1sum -b $(TGZ) | cut -d' ' -f1) ; \ |
SHA256=$(shell sha256sum -b $(TGZ) | cut -d' ' -f1) ; \ |
cat $< | sed -e 's!@VERSION@!$(VERSION)!g' \ |
-e "s/@MD5@/$$MD5/g" \ |
-e "s/@SHA1@/$$SHA1/g" -e "s/@SHA256@/$$SHA256/g" > $@ |
clean: |
-$(RM) examples/vcs.conf.example |
distclean: clean |
-$(RM) vcs.spec PKGBUILD |
.PHONY: all install clean tgz |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.3/pkg/profiles/mosaic.conf |
---|
0,0 → 1,12 |
# vcs:conf: |
# vcs:desc: Tight, small, thumbnails |
# <http://p.outlyer.net/dox/vcs:example_configs> |
# $Id$ |
disable_timestamps=1 |
disable_shadows=1 |
height=160 |
numcaps=20 |
timecode_from=$TC_NUMCAPS |
padding=0 |
columns=4 |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.3/pkg/profiles/black.conf |
---|
0,0 → 1,9 |
# vcs:conf: |
# vcs:desc: White-on-Black |
# $Id$ |
bg_contact=Black |
bg_heading=$bg_contact |
bg_sign=$bg_contact |
fg_heading=White |
fg_sign=$fg_heading |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.3/pkg/profiles/white.conf |
---|
0,0 → 1,9 |
# vcs:conf: |
# vcs:desc: Black-on-White profile |
# $Id$ |
bg_contact=White |
bg_heading=$bg_contact |
bg_sign=$bg_contact |
fg_heading=Black |
fg_sign=$fg_heading |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.3/pkg/BSDmakefile |
---|
0,0 → 1,14 |
VERSION!=head -50 vcs | grep 'declare -r VERSION=' | perl -pe 's/.*"(.*)".*/\1/' |
PACKAGER!=finger -lp `echo $USER` 2>/dev/null | head -n1 | cut -d: -f3 |
.if empty($(PACKAGER)) |
PACKAGER!=grep ^`id -un` /etc/passwd | cut -d: -f5 | cut -d, -f1 |
.endif |
.if empty($(RM)) |
RM=rm -f |
.endif |
include common.mk |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.3/pkg/GNUmakefile |
---|
0,0 → 1,13 |
# |
# $Id$ |
# Makefile for GNU-make |
# |
VERSION:=$(shell head -50 vcs | grep 'declare -r VERSION=' | sed -e 's/.*"\(.*\)".*/\1/') |
PACKAGER:=$(shell finger -lp `echo $USER` 2>/dev/null | head -n1 | cut -d: -f3) |
ifeq ($(PACKAGER),) |
PACKAGER:=$(shell grep ^`id -un` /etc/passwd | cut -d: -f5 | cut -d, -f1) |
endif |
include common.mk |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.3/pkg/README |
---|
0,0 → 1,39 |
Index |
----- |
1. Files |
2. Installation |
3. Uninstallation |
Files |
----- |
In this package: |
vcs The VCS script |
profiles/ Example profiles: |
mosaic.conf 20 small thumbnails in a 5x4 grid, no padding |
black.conf Black background and white text |
white.conf White background and black text |
examples/vcs.conf Example configuration |
Use "make examples/vcs.conf.example" to create |
a version with all options commented out. |
Installation |
------------ |
$ make install |
Will install under /usr/local |
$ make install prefix=/usr |
Will install under /usr |
Uninstallation |
-------------- |
$ make uninstall |
If you used a prefix during install use it too during uninstall |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.3/pkg/examples/vcs.conf |
---|
0,0 → 1,152 |
# |
# vcs:conf: $Rev$ |
# Example vcs.conf file |
# This example files contains all the default values, commented out. |
# For each setting, where an equivalent command-line option exists it will be |
# listed in the comments |
# |
# Location: |
# Configuration files can be placed at /etc/vcs.conf (site-wide), |
# ~/.vcs.conf (current user, hidden file!), ./vcs.conf (current dir) or |
# ~/.vcs/vcs.conf (current user, new alternative location) |
# |
# Syntax: |
# * Comments start with '#' or ';' |
# * '#' can be used in values by writing $# |
# * Semicolons (;) can't be used in values |
# * Options are of the form name=value |
# - Options can refer to the *current* value of other options, i.e. |
# font_sign=$font_heading will assign to 'font_sign' the same value |
# as 'font_heading'. If font_heading is changed after that, font_sign |
# won't be affected. |
# - See <http://p.outlyer.net/dox/vcs:conf_files> for the full list |
# Height of individual captures. Percentage or fixed size (in pixels) |
height=100% # option -H |
# Amount ignored from the end of the video (think of it as an anti-spoiler |
# measure). Percentage (of video duration) or time (e.g. 2m). Set to 0 |
# to disable it. |
end_offset=5% # option -E |
######################## |
# Contact Sheet Layout # |
######################## |
columns=2 # Number of columns in the contact sheet (option -c) |
interval=1m # Interval between captures (option -i) |
# Number of captures. Note setting this isn't enough to use a fixed number, the |
# mode must also be changed (option 'timecode_from', see below). (option -n) |
captures=16 |
# Mode of operation, can be either $TC_NUMCAPS or $TC_INTERVAL (the default) |
# $TC_NUMCAPS Fix the number of captures, adjust interval accordingly |
# $TC_INTERVAL Fix interval, adjust the number of captures accordingly |
# (options -i and -n set this implicitly) |
timecode_from=$TC_INTERVAL |
# Modifier for extended mode (option -e). |
# Beware, setting it to something different from 0 automatically enables extended mode! |
extended_factor=0 |
# Extra padding added around each capture. |
# This has *no effect* when shadows are enabled (the default). |
# Tweaking this might break alignment when using extended mode (-e) |
padding=2 |
############### |
# Output file # |
############### |
# Output format. Use the file extension (e.g. 'png', 'jpg', 'jp2') |
# Any format accepted by ImageMagick can be used here (even pdf or gif, not |
# that they'll look very good though) |
# (options: -j for JPEG, -j2 for JPEG 2000) |
format=png |
quality=92 # Output quality for lossy formats (e.g. jpg) |
#################### |
# Cosmetic touches # |
#################### |
user=`id -un` # User name, will be used in the contact sheet footer (option -u) |
# Used in the signature, e.g. "Preview created by {value of user}" |
signature=Preview created by |
disable_shadows=0 # Disable shadows by default (option -ds) |
disable_timestamps=0 # Disable timestamps by default (option -dt) |
##################### |
# Colours and fonts # |
##################### |
# * Colours can be defined either by their name or through hexcodes |
# use the command 'identify -list color' for a list of known names |
# * Colours can use transparency, although this usually only makes sense |
# for timestamps, which are overlaid on captures. |
# * Beware when specifying colours in hex format color=#hex is ok |
# but color = #hex not, spaces can be used with color = $#hex |
# * Font sizes are expressed in points and hence might need adjustment |
# when the font is changed. |
# * Font names can be either font paths (e.g. /usr/something/ttf/font.ttf) |
# or font names as understood by ImageMagick, use the command |
# 'identify -list font' for a list of fonts known to ImageMagick |
bg_heading=#afcd7a # Heading/meta-information section background colour |
fg_heading=Black # Heading font colour |
font_heading=DejaVu-Sans-Book # Heading font |
pts_heading=14 # Font size for heading |
bg_title=White # Background for the title (if activated with option -T) |
fg_title=Black # Title font colour |
font_title=$font_heading # Title font |
bg_contact=White # Background for the contact sheet |
bg_tstamps=#000000aa # Background for timestamps. Note the use of transparency |
fg_tstamps=White # Timestamps font colour |
font_tstamps=$font_heading # Timestamps font |
pts_tstamps=14 # Font size for timestamps |
# Background for the signature, i.e. the section after the contact sheet with |
# vcs and user identification |
bg_sign=SlateGray |
fg_sign=Black # Font colour for the signature |
font_sign=$font_heading # Font for the signature |
pts_sign=10 # Font size for signature |
# Font to use for file name when the non-latin mode (--nonlatin) is enabled |
# Option: -Ik=fontname |
nonlatin_font= # Autodetected (hopefully) |
###################### |
# Lower level tweaks # |
###################### |
# Decoder to use by default, can be either $DEC_FFMPEG (default) or |
# $DEC_MPLAYER |
# (options: -M for MPlayer, -F for FFMpeg) |
decoder=$DEC_FFMPEG |
# Output from commands, useful to locate errors |
stdout=/dev/null |
stderr=/dev/null |
# Verbosity level, very verbose by default. |
# Possible values: $V_ALL (default), $V_ERROR, $V_WARN, $V_INFO, $V_NONE |
# (options: -q for quietness) |
verbosity=$V_ALL |
# 1 disables colours in console output |
simple_feedback=0 |
debug=0 # When 1, enables debugging mode (option -D) |
getopt=getopt # GNU Getopt executable name |
# $Rev$ # |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.3/Makefile |
---|
0,0 → 1,72 |
#!/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" |
pkg/vcs.1: manpage.xml |
xmlto -o pkg man $< |
tgz: vcs-$(VER).tar.gz |
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 \ |
pkg/vcs.1 \ |
vcs-$(VER).tar.gz \ |
PKGBUILD-$(VER) \ |
vcs-$(VER).gz vcs-$(VER).bz2 vcs-$(VER).bash \ |
CHANGELOG.gz CHANGELOG \ |
rpm deb |
PKGBUILD-$(VER): vcs-$(VER).tar.gz |
cd pkg && ln -s ../vcs-$(VER).tar.gz ./ |
cd pkg && make PKGBUILD |
$(RM) pkg/vcs-$(VER).tar.gz |
mv pkg/PKGBUILD $@ |
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/tags/1.12.3/manpage.xml |
---|
0,0 → 1,382 |
<?xml version='1.0' encoding='UTF-8'?> |
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" |
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ |
<!-- |
$Id$ |
This file based on the default template for debian packages. |
Generation of man page: |
$ xmlto man manpage.xml |
Will generate vcs.1. |
View with: |
$ nroff -man vcs.1 | less |
or |
$ man vcs.1 |
To disable the automatic creation of the AUTHOR(S) and COPYRIGHT sections |
read /usr/share/doc/docbook-xsl/doc/manpages/authors.html. This file can be |
found in the docbook-xsl-doc-html package. |
Validation can be done using: `xmllint -''-noout -''-valid manpage.xml` |
General documentation about man-pages and man-page-formatting: |
man(1), man(7), http://www.tldp.org/HOWTO/Man-Page/ |
--> |
<!-- Fill in your name for FIRSTNAME and SURNAME. --> |
<!ENTITY dhfirstname "Toni"> |
<!ENTITY dhsurname "Corvera"> |
<!-- dhusername could also be set to "&dhfirstname; &dhsurname;". --> |
<!ENTITY dhusername "Toni Corvera"> |
<!ENTITY dhemail "outlyer@gmail.com"> |
<!ENTITY dhsection "1"> |
<!-- TITLE should be something like "User commands" or similar (see |
http://www.tldp.org/HOWTO/Man-Page/q2.html). --> |
<!ENTITY dhtitle "vcs User Manual"> |
<!ENTITY dhucpackage "VCS"> |
<!ENTITY dhpackage "vcs"> |
]> |
<refentry> |
<refentryinfo> |
<title>&dhtitle;</title> |
<productname>&dhpackage;</productname> |
<authorgroup> |
<author> |
<firstname>&dhfirstname;</firstname> |
<surname>&dhsurname;</surname> |
<contrib>VCS author.</contrib> |
<address> |
<email>&dhemail;</email> |
</address> |
</author> |
</authorgroup> |
<copyright> |
<year>2007-2010</year> |
<holder>&dhusername;</holder> |
</copyright> |
<legalnotice> |
<para>Permission is granted to copy, distribute and/or modify this |
document under the terms of the GNU Lesser General Public License, |
Version 2 or (at your option) any later version published by |
the Free Software Foundation.</para> |
</legalnotice> |
<edition>$Rev$</edition> |
</refentryinfo> |
<refmeta> |
<refentrytitle>&dhucpackage;</refentrytitle> |
<manvolnum>&dhsection;</manvolnum> |
</refmeta> |
<refnamediv> |
<refname>&dhpackage;</refname> |
<refpurpose>create contact sheets from videos</refpurpose> |
</refnamediv> |
<refsynopsisdiv> |
<cmdsynopsis> |
<command>&dhpackage;</command> |
<arg choice="opt"><option>options</option></arg> |
<arg choice="plain"><replaceable>FILE</replaceable></arg> |
<arg choice="opt"><replaceable>FILE</replaceable> |
<arg choice="opt"><replaceable>...</replaceable></arg> |
</arg> |
</cmdsynopsis> |
<cmdsynopsis> |
<command>&dhpackage;</command> |
<arg choice="opt"> |
<option>-n<parameter>20</parameter></option> |
<option>-c<parameter>4</parameter></option> |
</arg> |
<arg choice="plain"><replaceable>FILE</replaceable></arg> |
</cmdsynopsis> |
<cmdsynopsis> |
<command>&dhpackage;</command> |
<arg choice="opt"><option>--output=<parameter>OUTPUT1</parameter></option></arg> |
<arg choice="opt"><option>--output=<parameter>OUTPUT2</parameter></option></arg> |
<arg choice="opt"><option>...</option></arg> |
<arg choice="plain"><replaceable>INPUT1</replaceable></arg> |
<arg choice="opt"><replaceable>INPUT2</replaceable> |
<arg choice="opt"><replaceable>...</replaceable></arg> |
</arg> |
</cmdsynopsis> |
<cmdsynopsis> |
<command>&dhpackage;</command> |
<!-- Normally the help and version options make the programs stop |
right after outputting the requested information. --> |
<group choice="opt"> |
<arg choice="plain"> |
<group choice="req"> |
<arg choice="plain"><option>-h</option></arg> |
<arg choice="plain"><option>--help</option></arg> |
</group> |
</arg> |
<arg choice="plain"> |
<arg choice="plain"><option>--fullhelp</option></arg> |
</arg> |
</group> |
</cmdsynopsis> |
</refsynopsisdiv> |
<refsect1 id="description"> |
<title>DESCRIPTION</title> |
<!-- FIXME --> |
<para>This is a work in progress manual page for <command>&dhpackage;</command>, |
use <parameter>--help</parameter>, <parameter>--fullhelp</parameter> |
and the online documentation (located at <ulink url="http://p.outlyer.net/dox/vcs" />) |
for further usage instructions.</para> |
<para><command>&dhpackage;</command> is a program that creates a preview |
image from videos in a contact sheet-like format (i.e. captures from |
different frames in the video are placed in a mosaic).</para> |
<para>By default the output file will be named like the input file plus the |
png extension. Example: "<filename>file.avi</filename>" will produce |
a contact sheet in the file "<filename>file.avi.png</filename>".</para> |
<para>The default mode of operation is to obtain captures every five minutes in the |
video, so the amount of captures will vary with each file. The command-line |
argument <parameter>--numcaps</parameter> (<parameter>-n</parameter>) can be used |
to change this behaviour or alternatively a configuration file might |
be used to change the mode of operation (see <citerefentry> |
<refentrytitle>vcs.conf</refentrytitle> |
<manvolnum>5</manvolnum> |
</citerefentry>). |
</para> |
</refsect1> |
<refsect1 id="options"> |
<title>OPTIONS</title> |
<para>The program follows the usual GNU command line syntax, |
with long options starting with two dashes (`-'). A summary of |
options is included below.</para> |
<variablelist> |
<varlistentry> |
<term><option>-n <replaceable>number</replaceable></option></term> |
<term><option>--numcaps=<replaceable>number</replaceable></option></term> |
<listitem> |
<para>Fixes the number of captures to obtain. Changes the mode of operation |
to capture a fixed number of frames.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-i <replaceable>interval</replaceable></option></term> |
<term><option>--interval=<replaceable>interval</replaceable></option></term> |
<listitem> |
<para>Sets the interval between captures. Changes the mode of operation |
to capture at fixed intervals. The number of captures will depend |
on the video length.</para> |
<para>See <xref linkend="interval_format" /> for the allowed syntax.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-c <replaceable>number</replaceable></option></term> |
<term><option>--columns=<replaceable>number</replaceable></option></term> |
<listitem> |
<para>Number of columns in the contact sheet. The number of rows |
will depend on this and the number of captures (there's no |
way to set the number of rows).</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-H <replaceable>height</replaceable></option></term> |
<term><option>--height=<replaceable>height</replaceable></option></term> |
<listitem> |
<para>Height of captures. Can be a number (of pixels) or a percentage |
(of the video height). By default the same size as the video is used.</para> |
<para>The width is derived from height and aspect ratio.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-o <replaceable>filename</replaceable></option></term> |
<term><option>--output=<replaceable>filename</replaceable></option></term> |
<listitem> |
<para>Name of output file. By default the video file name plus the output |
format is used (e.g. "<filename>video.avi.png</filename>" |
for "<filename>video.avi</filename>").</para> |
<para>If an extension is provided, it will define the output format, otherwise |
PNG will be used. I.e. <filename>sheet.jpg</filename> will produce |
a JPEG file while <filename>sheet</filename> and |
<filename>sheet.png</filename> will produce a PNG file.</para> |
</listitem> |
</varlistentry> |
<!-- |
<varlistentry> |
<term><option>-x <replaceable>t</replaceable></option></term> |
<term><option>- -xy=<replaceable>t</replaceable></option></term> |
<listitem> |
<para>DESC.</para> |
</listitem> |
</varlistentry> |
--> |
<varlistentry> |
<term><option>-h</option></term> |
<term><option>--help</option></term> |
<listitem> |
<para>Show summary of most common options.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>--fullhelp</option></term> |
<listitem> |
<para>Show summary of all options.</para> |
</listitem> |
</varlistentry> |
</variablelist> |
</refsect1> |
<refsect1 id="files"> |
<title>FILES</title> |
<variablelist> |
<varlistentry> |
<term><filename>/etc/vcs.conf</filename></term> |
<listitem> |
<para>The system-wide configuration file to control the |
behaviour of <application>&dhpackage;</application>. See |
<citerefentry> |
<refentrytitle>vcs.conf</refentrytitle> |
<manvolnum>5</manvolnum> |
</citerefentry> for further details.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><filename>${HOME}/.vcs.conf</filename></term> |
<term><filename>${HOME}/.vcs/vcs.conf</filename></term> |
<listitem> |
<para>The per-user configuration file to control the |
behaviour of <application>&dhpackage;</application>. See |
<citerefentry> |
<refentrytitle>vcs.conf</refentrytitle> |
<manvolnum>5</manvolnum> |
</citerefentry> for further details.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><filename>${PWD}/vcs.conf</filename></term> |
<listitem> |
<para>The per-directory configuration file to control the |
behaviour of <application>&dhpackage;</application>. See |
<citerefentry> |
<refentrytitle>vcs.conf</refentrytitle> |
<manvolnum>5</manvolnum> |
</citerefentry> for further details.</para> |
</listitem> |
</varlistentry> |
</variablelist> |
</refsect1> |
<refsect1 id="interval_format"> |
<title>INTERVALS</title> |
<para> |
Intervals and timestamps can be specified in seconds or in a human-readable format |
that follows the syntax |
<replaceable>HOURS</replaceable>h<replaceable>MINUTES</replaceable>m |
<replaceable>SECONDS</replaceable>s.<replaceable>MILLISECONDS</replaceable> |
where each element is optional. |
See <ulink url="http://p.outlyer.net/dox/vcs:time_syntax" /> for more details. |
</para> |
<para> |
<segmentedlist> |
<segtitle>Example</segtitle><segtitle>Equivalence</segtitle> |
<segtitle>Standard time format</segtitle> |
<seglistitem> |
<seg>1h30m30</seg><seg>1h30m30s.00</seg><seg>1:30:30.00</seg> |
</seglistitem> |
<seglistitem> |
<seg>30</seg><seg>0h0m30s.00</seg><seg>0:00:30.00</seg> |
</seglistitem> |
<seglistitem> |
<seg>3600</seg><seg>1h0m0s.00</seg><seg>1:00:00.00</seg> |
</seglistitem> |
</segmentedlist> |
</para> |
</refsect1> |
<refsect1 id="environment"> |
<title>ENVIRONMENT</title> |
<variablelist> |
<varlistentry> |
<term><envar>TEMPDIR</envar></term> |
<listitem> |
<para>Fallback temporary directory when <filename>/dev/shm</filename> is not avaiable. |
Due to the big size of temporary files, it is recommended to use |
a temporary directory on a fast filesystem.</para> |
</listitem> |
</varlistentry> |
</variablelist> |
</refsect1> |
<refsect1 id="diagnostics"> |
<title>DIAGNOSTICS</title> |
<para>The default verbosity level will print <package>vcs</package>' progress |
and any errors or warnings on <filename class="devicefile">stderr</filename>.</para> |
<para><option>--quiet</option> can be used to reduce verbosity.</para> |
<para>The verbosity level and <filename class="devicefile">stderr</filename> can be |
controlled through configuration files, see <citerefentry> |
<refentrytitle>vcs.conf</refentrytitle> |
<manvolnum>5</manvolnum> |
</citerefentry>. |
</para> |
<para><command>&dhpackage;</command> provides some return codes, they follow |
the semi-standardised values defined in <filename>sysexits.h</filename>:</para> |
<segmentedlist> |
<segtitle>Code</segtitle> |
<segtitle>Diagnostic</segtitle> |
<seglistitem> |
<seg><errorcode>0</errorcode> (<errorcode>EX_OK</errorcode>)</seg> |
<seg>Program exited successfully.</seg> |
</seglistitem> |
<seglistitem> |
<seg><errorcode>64</errorcode> (<errorcode>EX_USAGE</errorcode>)</seg> |
<seg>Error in the arguments.</seg> |
</seglistitem> |
<seglistitem> |
<seg><errorcode>66</errorcode> (<errorcode>EX_NOINPUT</errorcode>)</seg> |
<seg>Can't access some input file or it has an incorrect format.</seg> |
</seglistitem> |
<seglistitem> |
<seg><errorcode>69</errorcode> (<errorcode>EX_UNAVAILABLE</errorcode>)</seg> |
<seg>Unsatisfied dependency.</seg> |
</seglistitem> |
<seglistitem> |
<seg><errorcode>70</errorcode> (<errorcode>EX_SOFTWARE</errorcode>)</seg> |
<seg>Internal inconsistency (bug).</seg> |
</seglistitem> |
<seglistitem> |
<seg><errorcode>73</errorcode> (<errorcode>EX_CANTCREAT</errorcode>)</seg> |
<seg>Error creating temporary or output files.</seg> |
</seglistitem> |
</segmentedlist> |
</refsect1> |
<refsect1 id="bugs"> |
<!-- Or use this section to tell about upstream BTS. --> |
<title>BUGS</title> |
<para>Recent versions of <package>ImageMagick</package>, <command>mplayer</command> and |
<command>ffmpeg</command> should be used |
for maximum compatibility.</para> |
<para>The upstream's <acronym>BTS</acronym> can be found |
at <ulink url="http://b.outlyer.net"/>.</para> |
</refsect1> |
<refsect1 id="see_also"> |
<title>SEE ALSO</title> |
<!-- In alpabetical order. --> |
<para><citerefentry> |
<refentrytitle>vcs.conf</refentrytitle> |
<manvolnum>5</manvolnum> |
</citerefentry>, <citerefentry> |
<refentrytitle>convert</refentrytitle> |
<manvolnum>1</manvolnum> |
</citerefentry>, <citerefentry> |
<refentrytitle>ffmpeg</refentrytitle> |
<manvolnum>1</manvolnum> |
</citerefentry>, <citerefentry> |
<refentrytitle>mplayer</refentrytitle> |
<manvolnum>1</manvolnum> |
</citerefentry></para> |
</refsect1> |
</refentry> |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.3/vcs |
---|
0,0 → 1,0 |
link pkg/vcs |
Property changes: |
Added: svn:special |
+* |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.3/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.12.3 |
---|
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/tags/1.12:r413 |
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/branches/1.12:r409-411 |
Merged /video-contact-sheet/branches/1.11.2:r393-406 |
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.12.1:r416-419 |
Merged /video-contact-sheet/branches/1.12.2:r422-431 |
Merged /video-contact-sheet/branches/1.12.3:r435-454 |
Merged /video-contact-sheet/tags/0.99a:r261 |
Merged /video-contact-sheet/branches/1.0.3b:r276-277 |
Merged /video-contact-sheet/branches/1.0.2b:r270-271 |
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.12.2/Makefile |
---|
0,0 → 1,72 |
#!/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" |
pkg/vcs.1: manpage.xml |
xmlto -o pkg man $< |
tgz: vcs-$(VER).tar.gz |
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 \ |
pkg/vcs.1 \ |
vcs-$(VER).tar.gz \ |
PKGBUILD-$(VER) \ |
vcs-$(VER).gz vcs-$(VER).bz2 vcs-$(VER).bash \ |
CHANGELOG.gz CHANGELOG \ |
rpm deb |
PKGBUILD-$(VER): vcs-$(VER).tar.gz |
cd pkg && ln -s ../vcs-$(VER).tar.gz ./ |
cd pkg && make PKGBUILD |
$(RM) pkg/vcs-$(VER).tar.gz |
mv pkg/PKGBUILD $@ |
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/tags/1.12.2/pkg/vcs |
---|
0,0 → 1,4589 |
#!/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>) |
# |
# The full changelog can be found at <http://p.outlyer.net/vcs/files/CHANGELOG> |
declare -r VERSION="1.12.2" |
declare -r RELEASE=1 |
set -e |
# GAWK 3.1.3 to 3.1.5 print decimals (with printf) according to locale (i.e. |
#+decimal comma separator in some locales, which is apparently POSIX correct). |
#+Older and newer versions, though, need either POSIXLY_CORRECT=1, --posix or |
#+ --use-lc-numeric to honour locale. |
# MAWK appears to always use dots. |
# Info: <http://www.gnu.org/manual/gawk/html_node/Conversion.html> |
#export POSIXLY_CORRECT=1 # Immitate behaviour in newer gawk |
export LC_NUMERIC=C |
# 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 |
# * (1.12 or 1.13) 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 |
# --undocumented shoehorn -> NONE 1.12 1.13 or 1.14 |
# --funky -> --profile ? ?+1 |
# --end_offset -> --end-offset 1.12 (silent), 1.13 (warn) 1.14 |
# > Usage of ./vcs.conf is also deprecated since it doesn't mesh well with profiles |
# (and it was a bad placeholder for them). Will remain to be loadable with |
# -C:pwd. |
# Loaded by default in 1.12 and maybe 1.13. |
# Not loaded from 1.14 onwards (maybe 1.13), a warning may be shown if the file |
# exists |
# * 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. |
# implementation |
# * Optimisations: |
# - Reduce the number of forks |
# }}} # 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 |
# * ~/.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. |
# 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 |
# 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 |
# Used for feedback |
declare -r NL=$'\012' # Newline |
declare -r TAB=$'\011' # Tab |
# }}} # 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 |
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 -r DEFAULT_END_OFFSET="5.5%" |
# 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) |
# Introduced in 1.12.2: |
# When true (1) uses $FONT_MINCHO to print the filename, otherwise the same |
#+font as the heading is used. |
# See -I and --nonlatin |
declare -i NONLATIN_FILENAMES=0 |
# Output of capturing programs is redirected here |
declare stdout=/dev/null stderr=/dev/null |
# Override-able since 1.11: |
# Height of the thumbnails, by default use same as input |
declare th_height='100%' |
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 amount of time is *not* captured from the end of the video |
declare 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 |
# Stores the names of variables overridden from the command-line, |
#+see cmdline_override() and "--override" |
declare CMDLINE_OVERRIDES="" |
# Implicit error handling (see die()), obviously inspired by C's errno |
# and PHP's die(). Functions adapted to use them allow uses like: |
# some_function arg || die |
# which will exit with the appropriate exit code and print the error message |
# (Introduced in 1.12, still being retrofitted) |
declare -i ERROR_CODE=0 # Exit code associated with the last error |
declare ERROR_MSG= # Error message associated to the last error |
# Used to buffer feedback (see buffered()) |
declare BUFFER= |
# This is only used to exit when -DD is used |
declare -i DEBUGGED=0 # It will be 1 after using -DD |
# See post_getopt_hooks() |
# Format: Priority:Command[:Arguments] (lower priority run sooner) |
declare -a POST_GETOPT_HOOKS=( ) |
declare -i DVD_MODE=0 DVD_TITLE= |
declare -a DVD_TITLES=( ) # Titles for each input DVD, filled by --dvd-title |
declare DVD_MOUNTP= # Mountpoint for DVD, detected & reset for each DVD |
declare DVD_VTS= # VTS, detected & reset for each DVD |
# }}} # Variables |
# {{{ # Configuration handling |
# New override system: This variable maps configuration variables to actual |
#+variables used in the script. Each item in the array follows the syntax: |
# <cfg variable>:<variable>:<flags>:[type constraints] Where: |
#+ cfg variable: is the name of the configuration file variable |
#+ variable: is the name of the actual variable. If empty or '=', it will be |
#+ the same as cfg variable. |
#+ flags can currently be: |
#+ "deprecated=new name": Will print a deprecation warning and suggest to use |
#+ "new name" instead |
#+ "striked": Variable is marked for removal, will print a warning about it |
#+ directing anyone needing it to contact me. Only used for variables |
#+ believed to be no longer needed |
#+ "gone": Variable removed in the current version |
#+ "alias": Marks an alias, duplicate name intended to stay |
#+ "meta": Special variable that will modify other variables (e.g. font_all |
#+ modifies all font_ variables. |
#+ "=": ignore |
#+ type constraints: a character indicating accepted values: |
# n -> Number (Natural, positive Integer) |
# p -> Number, not zero |
# t -> Timestamp |
# b -> Bool |
# h -> Positive, non-zero, number or percentage |
# f -> Float or fraction |
# D -> only $DEC_* constants |
# T -> only $TC_* constants |
# V -> only $V_* constants |
# I -> interval or percentage |
# Note during the switch to the new system most variables will remain unchanged |
# Also, the new system is case insensitive to variable names |
# TODO: Allow 'y', 'n' in booleans |
# TODO: Remove extra coherence_check()'s once constraints are implemented |
declare -ra OVERRIDE_MAP=( |
"user:::" |
"extended_factor:=:=:f" |
"stdout::" |
"stderr::" |
"DEBUG:=:=:b" |
"interval:=:=:t" |
"numcaps:=:=:p" |
"captures:numcaps:alias:n" # Alias |
"GETOPT::" # Note it makes no sense as command-line override |
"columns:cols:=:p" |
"cols:=:alias:p" # Alias |
"DISABLE_SHADOWS:=:=:b" |
"DISABLE_TIMESTAMPS:=:=:b" |
"bg_heading::" |
"bg_sign::" |
"bg_title::" |
"bg_contact::" |
"bg_tstamps::" |
"fg_heading::" |
"fg_sign::" |
"fg_tstamps::" |
"fg_title::" |
"font_heading::" |
"font_sign::" |
"font_tstamps::" |
"font_title::" |
"font_all:=:meta" # see parse_override |
"bg_all:=:meta" |
"fg_all:=:meta" |
"pts_tstamps::" |
"pts_meta::" |
"pts_sign::" |
"pts_title::" |
# Aliases for cosmetic stuff |
"bg_header:bg_heading:alias" |
"bg_signature:bg_sign:alias" |
"bg_footer:bg_sign:alias" |
"bg_sheet:bg_contact:alias" |
"fg_header:fg_heading:alias" |
"fg_signature:fg_sign:alias" |
"fg_footer:fg_sign:alias" |
"font_header:font_heading:alias" |
"font_meta:font_heading:alias" |
"font_signature:font_sign:alias" |
"font_footer:font_sign:alias" |
"pts_heading:pts_meta:alias" |
"pts_header:pts_meta:alias" |
"pts_signature:pts_sign:alias" |
"pts_footer:pts_sign:alias" |
"signature:user_signature:" |
"user_signature::deprecated=signature" |
"quality:output_quality:=:n" |
"output_quality::deprecated=quality:n" |
# TODO: These variables are evaluated to constants, would be better to |
# use some symbolic system (e.g. decoder=f instead of decoder=$DEC_FFMPEG) |
"decoder:=:=:D" |
#"capture_mode:timecode_from:alias:T" |
"timecode_from:=:=:T" |
"verbosity:=:=:V" |
"format:output_format:" |
"output_format:=:deprecated=format" |
"simple_feedback:plain_messages:=:b" |
"plain_messages::deprecated=simple_feedback:b" |
"height:th_height:=:h" |
"th_height::deprecated=height:h" |
"padding:HPAD:=:n" |
"HPAD:=:deprecated=padding:n" |
"nonlatin_font:FONT_MINCHO:" |
"FONT_MINCHO::deprecated=nonlatin_font" |
"NONLATIN_FILENAMES:=:=:b" |
"end_offset:=:=:I" # New, used to have a two-variables assignment before USR_* |
"DEFAULT_END_OFFSET:end_offset:deprecated=end_offset:I" |
# TODO TBA: |
#"use_nonlatinfont::" |
#"noboldfeedback::" # Colour but not bold |
"shoehorned::striked" |
"safe_rename_pattern::striked" |
"MIN_LENGTH_FOR_END_OFFSET::gone:" |
) |
# Load a configuration file |
# File *MUST* exist |
# Configuration files are a series of variable=value assignment; they'll be |
#+evaluated directly so they can refer to other variables (with their value at |
#+the point of the assignment). |
# Quotes shouldn't be used (they'll be kept) |
# Since 1.12 comments can be placed in-line (i.e. after an assignment), |
# Literal '#' can be written as '$#' |
# ';' can be used to mark an end of line, anything after it will be ignored |
#+(making it equivalent to '#'), there's no way to include a literal ';' |
# load_config_file($1 = file, [$2 = type (description) = 'Settings']) |
load_config_file() { |
local cfgfile=$1 |
local desc=$2 |
[ "$desc" ] || desc='Settings' |
local por= # Parsed override |
local varname= tmp= flag= bashcode= feedback= ov= |
while read line ; do # auto variable $line |
parse_override "$line" # Feeding it comments should be harmless |
por=$RESULT |
if [ "$por" ]; then |
varname=${por/% *} # Everything up to the first space... |
tmp=${por#* } # Rest of string |
flag=${tmp/% *} |
if [ "$flag" == '=' ]; then |
# No need to override... |
feedback="$varname(=)" |
else |
feedback=$varname |
fi |
ov="$ov, $feedback" |
fi |
done <$cfgfile |
[ -z "$ov" ] || inf "$desc from $cfgfile:$NL ${ov:2}" |
# No loaded overrides but errors/warnings to print, do print the file name |
if [ -z "$ov" -a "$BUFFER" ]; then |
inf "In $cfgfile:" |
fi |
flush_buffered ' ' |
} |
# Loads the configuration files if present |
# load_config() |
load_config() { |
local -a CONFIGS=( /etc/vcs.conf ~/.vcs.conf ~/.vcs/vcs.conf ./vcs.conf ) |
for cfgfile in "${CONFIGS[@]}" ;do |
[ -f "$cfgfile" ] || continue |
load_config_file "$cfgfile" |
done |
} |
# Load a profile, if found; fail otherwise |
# Profiles are just configuration files that can be loaded on demand (whereas |
#+config files are always loaded) and be given a name. |
# See load_config_file() for comments on the syntax |
# Locations to be searched, in order: |
#+ 1) ~/.vcs/profiles/NAME.conf |
#+ 2) /usr/local/share/vcs/profiles/NAME.conf |
#+ 3) /usr/share/vcs/profiles/NAME.conf |
#+i.e. files in ~/.vcs/ will prevent loading files named like them in /usr |
# load_profile($1 = profile name) |
load_profile() { |
local p=$1 prof= |
local -a PATHS=( ~/.vcs/profiles/ /usr/local/share/vcs/profiles/ /usr/share/vcs/profiles/ ) |
if [ ${p:0:1} == ':' ]; then |
case $p in |
:list) |
# No need to be efficient here... |
echo "Profiles located:" |
local path= profname= |
# 1) Find all profiles |
# 2) (sed) Extract profile file name |
# 3 & 4) (sort+uniq) Keep only first hits for each name (most precedence) |
# 5) (while) Process each name |
# 6) (for) Re-locate most precedent profile |
# 7) (echo x3) Print <name>[: description] |
# 8) (sed) Indent with ' * ' |
find "${PATHS[@]}" -name '*.conf' 2>/dev/null \ |
| sed -e 's#.*/\(.*\)\.conf#\1#' \ |
| sort | uniq \ |
| while read profname ; do |
for path in "${PATHS[@]}" ; do |
path=$path$profname.conf |
[ -f "$path" ] || continue |
echo -n "$profname" |
# [ ] here contains <space><tab>. Mawk doesn't understand |
# [[:space:]] |
echo -n $(awk 'sub(/#[ ]*vcs:desc:[ ]*/, ": ")' "$path") |
echo |
break |
done |
done \ |
| sed 's/^/ * /' |
exit 0 |
;; |
*) |
ERROR_MSG="Profiles starting with ':' are reserved.$NL"\ |
" Use ':list' to list available profiles." |
ERROR_CODE=$EX_USAGE |
return $ERROR_CODE |
esac |
fi |
for prof in "${PATHS[@]}" ; do |
prof="$prof$p.conf" |
[ -f "$prof" ] || continue |
load_config_file "$prof" 'Profile' |
return 0 |
done |
ERROR_MSG="Profile '$p' not found" |
ERROR_CODE=$EX_USAGE |
return $ERROR_CODE |
} |
# Check value for an overrideable variable against the allowed values |
# check_constraint($1 = variable name, $2 = value [, $3 = public_name]) |
# where public_name is the name to be used for error messages |
check_constraint() { |
local n=$1 v=$2 p=$3 |
# Get constraint |
local map=$(echo "${OVERRIDE_MAP[*]}" | stonl | egrep -i "^$n:") |
[ "$map" ] || return 0 |
local ct=$(cut -d':' -f4 <<<"$map") |
[ "$ct" ] || return 0 |
local checkfn= domain= |
case $ct in |
n) checkfn=is_number ; domain=numbers ;; |
p) checkfn=is_positive ; domain='numbers greater than zero' ;; |
t) checkfn=is_interval ; domain=intervals ;; |
b) checkfn=is_bool ; domain='boolean values (0 or 1)' ;; |
h) checkfn=is_pos_or_percent ; domain='positive numbers or percentages' ;; |
f) checkfn=is_float_or_frac ; domain='positive numbers or fractions' ;; |
D) checkfn=is_decoder ; domain='$DEC_FFMPEG or $DEC_MPLAYER' ;; |
T) checkfn=is_tcfrom ; domain='$TC_INTERVAL or $TC_INTERVAL' ;; |
V) checkfn=is_vlevel ; domain='verbosity levels ($V_.*)' ;; |
I) checkfn=is_interv_or_percent ; domain='intervals or percentages' ;; |
esac |
if [ "$checkfn" ] && ! $checkfn "$v" ; then |
[ "$p" ] || p=$n |
ERROR_MSG="Illegal value for '$p', only $domain are accepted" |
ERROR_CODE=$EX_USAGE |
return $ERROR_CODE |
fi |
return 0 |
} |
# Parse an override and set its value. |
# Input should be a var=value assignment. Also sets USR_<variable>. |
# The global variable $RESULT is set with the format: |
# <variable name> <flag> where |
# * variable name: is the name of the variable to be overridden |
# * flag: is a character indicating the status: "+" for a possible override, |
# "=" for an override that already has the same value |
# Warnings and errors are buffered |
# This function always returns true |
# parse_override($1 = override assignment) |
parse_override() { |
local o="$1" |
RESULT='' |
if ! egrep -q '^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*=.*' <<<"$o" ; then |
return |
fi |
local varname=$(echo "${o/=*}" | sed 's/[[:space:]]//g') # Trim var name |
local lcvarname=$(echo "$varname" | tr '[A-Z]' '[a-z]') |
local mapping=$(echo "${OVERRIDE_MAP[*]}" | stonl | egrep -i "^$lcvarname:") |
[ "$mapping" ] || return 0 |
local varval=${o#*=} # No trimming here (yet) |
# 1) Trim from ; (if present) to finish |
# 2) Trim from # (comments) not "escaped" like '$#' |
# 3) Replace '$#' with '#' |
# 4) Trim whitespace on both ends |
varval=$(sed -e 's/;.*//' -e 's/\([^$]\)#.*/\1/g' -e 's/\$#/#/g' \ |
-e 's/^[[:space:]]*//;s/[[:space:]]*$//' <<<"$varval") |
# Is varval empty? |
[ "$varval" ] || return 0 |
local mvar=$(cut -d':' -f1 <<<"$mapping") |
local ivar=$(cut -d':' -f2 <<<"$mapping") |
local flags=$(cut -d':' -f3 <<<"$mapping") |
local constraints=$(cut -d':' -f4 <<<"$mapping") |
{ [ "$ivar" ] && [ "$ivar" != "=" ] ; } || ivar="$mvar" |
# Note using "\$(echo $varval)" would allow a more flexible syntax but |
#+enforce special handling of escaping, which with the currently available |
#+settings is not worth the effort |
# Resolve symbolic variables to check their actual value |
eval varval="\"$varval\"" 2>/dev/null || { # Hide eval's errors |
buffered error "Syntax error: '$o'" |
return 0 |
} |
[ "$varval" ] || return 0 # If empty value, ignore it |
local evcode='' |
if [ "$flags" ] && [ $flags != "=" ] && [ $flags != 'alias' ]; then |
if echo "$flags" | grep -q '^deprecated=' ; then |
local new=$(echo "$flags" | sed 's/^deprecated=//') |
buffered warn "Variable '$varname' will be removed in the future,$NL please use '$new' instead." |
else |
case "$flags" in |
gone) |
buffered error "Variable '$varname' has been removed." |
return 0 |
;; |
striked) |
buffered error "Variable '$varname' is scheduled to be removed in the next release." |
buffered error " Please contact the author if you absolutely need it." |
;; |
meta) |
apply_meta_override "$varname" "$varval" |
RESULT="$varname +" |
return 0; |
;; |
*) return 0 ;; |
esac |
fi |
fi |
[ -z "$constraints" ] || check_constraint $ivar "$varval" $varname || { |
buffered error "$ERROR_MSG" |
return 0 |
} |
eval local curvarval='$'"$ivar" retflag='+' |
if [ "$curvarval" == "$varval" ]; then |
retflag='=' |
else |
if [ "$constraints" == "t" ]; then |
varval=$(get_interval "$varval") |
fi |
# Escape single quotes, since it will be single-quoted: |
varval=${varval//\'/\'\\\'\'} # <<'>> => <<'\''>> |
evcode="$ivar='$varval'; USR_$ivar='$varval'" |
eval "$evcode" |
fi |
# varname, as found in the config file |
RESULT="$varname $retflag" |
} |
# Handle meta configuration variables, variables that, when set, modify the |
# value of (various) others |
# apply_meta_override($1 = actual variable name, $2 = value) |
apply_meta_override() { |
case "$1" in |
font_all) |
buffered inf "font_all => font_heading, font_sign, font_title, font_tstamps" |
parse_override "font_heading=$2" |
parse_override "font_sign=$2" |
parse_override "font_title=$2" |
parse_override "font_tstamps=$2" |
;; |
fg_all) |
buffered inf "fg_all => fg_heading, fg_sign, fg_title, fg_tstamps" |
parse_override "fg_heading=$2" |
parse_override "fg_sign=$2" |
parse_override "fg_tstamps=$2" |
parse_override "fg_title=$2" |
;; |
bg_all) |
buffered inf "bg_all => bg_heading, bg_contact, bg_sign, bg_title, bg_tstamps" |
parse_override "bg_heading=$2" |
parse_override "bg_contact=$2" |
parse_override "bg_sign=$2" |
parse_override "bg_title=$2" |
parse_override "bg_tstamps=$2" |
;; |
esac |
} |
# Do an override from the command line |
# cmdline_override($1 = override assignment) |
#+e.g. cmdline_override 'decoder=$DEC_FFMPEG' |
cmdline_override() { |
trace $FUNCNAME $@ |
parse_override "$1" |
local r=$RESULT |
[ "$r" ] || return 0 |
local varname=${r/% *} # See load_config() |
local tmp=${r#* } |
local flag=${tmp/% *} |
if [ "$flag" == '=' ]; then |
varname="$varname(=)" |
fi |
CMDLINE_OVERRIDES="$CMDLINE_OVERRIDES, $varname" |
} |
# Call any pending commands required by the command-line arguments |
# This is used to defer some calls and to flush buffers |
post_getopt_hooks() { |
local cback= EX=0 |
local funcs=$(echo "${POST_GETOPT_HOOKS[*]}" | stonl | sort -n | uniq |\ |
cut -d':' -f2- ) |
for cback in $funcs ; do |
local fn=${cback/:*} |
local arg=${cback/*:} |
[ "$arg" != "$cback" ] || arg='' |
$fn $arg |
done |
} |
# Print the list of command-line overrides |
cmdline_overrides_flush() { |
trace $FUNCNAME $@ |
if [ "$CMDLINE_OVERRIDES" ]; then |
inf "Overridden settings from command line:$NL ${CMDLINE_OVERRIDES:2}" |
fi |
if [ "$BUFFER" ]; then |
[ "$CMDLINE_OVERRIDES" ] || warn "In command-line overrides:" |
flush_buffered ' ' |
fi |
} |
# }}} # Configuration handling |
# {{{ # Convenience functions |
#### {{{{ # Type checkers: Return true if input is of a certain type |
#### All take exactly one argument and print nothing |
## Natural number |
is_number() { |
#egrep -q '^[0-9]+$' <<<"$1" ; |
# From [[abs]], test if '[ ]' can parse input as numbers |
# Returns 2 for failed test, expected to return 1 |
[ "$1" -ne 0 -o "$1" -eq 0 ] 2>/dev/null || return 1 |
} |
## Number > 0 |
is_positive() { is_number "$1" && [ $1 -gt 0 ]; } |
## Bool (0 or 1) |
is_bool() { [ "$1" == "0" -o "$1" == "1" ] 2>/dev/null ; } |
## Float (XX.YY; XX.; ;.YY) (.24=0.24) |
is_float() { egrep -q '^([0-9]+\.?([0-9])?+|(\.[0-9]+))$'<<<"$1" ; } |
## Percentage (xx% or xx.yy%) |
is_percentage() { |
egrep -q '^([0-9]+\.?([0-9])?+|(\.[0-9]+))%$'<<<"$1" |
} |
## Interval |
is_interval() { |
local i=$(get_interval "$1" || true) |
[ "$i" ] && fptest $i -gt 0 |
} |
## Interval or percentage |
is_interv_or_percent() { |
is_percentage "$1" || is_interval "$1" |
} |
## Positive or percentage |
is_pos_or_percent() { |
is_number "$1" && [ "$1" -gt 0 ] || is_percentage "$1" |
} |
## Float (>=0) or fraction |
is_float_or_frac() { |
{ is_fraction "$1" || is_float "$1" ; } && fptest "$1" -ge 0 |
} |
## Fraction, strictly (X/Y, but no X; Y!=0) |
is_fraction() { |
egrep -q '^[0-9]+/[0-9]+$'<<<"$1" && { |
local d=$(echo "$1" | cut -d'/' -f2) |
[ "$d" -ne 0 ] |
} |
} |
## Decoder ($DEC_* constants) |
is_decoder() { [ "$1" == $DEC_FFMPEG -o "$1" == $DEC_MPLAYER ]; } |
## Time calculation source ($TC_* constants) |
is_tcfrom() { [ "$1" == $TC_INTERVAL -o "$1" == $TC_NUMCAPS ]; } |
### Verbosity level ($V_* constants) |
is_vlevel() { |
is_number "$1" && \ |
[ $1 -eq $V_ALL -o $1 -eq $V_NONE -o $1 -eq $V_ERROR -o \ |
$1 -eq $V_WARN -o $1 -eq $V_INFO ] |
} |
#### }}}} # End of type checkers |
# 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 old code (default bc -l) |
# TODO: gawk and mawk differ in how to handle stuff like div by zero: |
# gawk errors, mawk prints inf. Should somehow handle inf and nan |
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() { |
if [ "$1" ]; then |
awk '{gsub(" ", "\n");print}' <<<"$1" | egrep -v '^$' |
else |
awk '{gsub(" ", "\n");print}' | egrep -v '^$' |
fi |
} |
# Converts newlines to spaces portably |
# nltos([$1 = string]) |
nltos() { |
if [ "$1" ]; then |
awk '{printf "%s ",$0}' <<<"$1" | sed 's/ *//' |
else |
awk '{printf "%s ",$0}' | sed 's/ *//' |
fi |
} |
# 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() { |
awkexf "sqrt($1 ^ 2 + $2 ^ 2)" |
} |
# Get a percentage |
# percent($1 = value, $2 = percentage) |
percent() { |
local pc=${2/%%/} # BASH %% == RE %$ |
awkexf "($1 * $pc) / 100" |
} |
# Rounded percentage |
# rpercent($1 = value, $2 = percentage) |
rpercent() { |
local pc=${2/%%/} |
awkex "int( ($1 * $pc) / 100 + 0.5 )" |
} |
# 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 awkexf "\"$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=$(awkexf "$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" |
} |
# Get the line height used for a certain font and size |
# line_height($1 = font, $2 = size) |
line_height() { |
# Create a small image to see how tall are characters. In my tests, no |
#+matter which character is used it's always the same height. |
convert -font "$1" -pointsize "$2" \ |
label:'F' png:- | identify -format '%h' - |
} |
# 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 soon 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($1 = image file) |
get_dvd_image_mountpoint() { |
if is_linux ; then |
local lodev=$(/sbin/losetup -j "$1" | 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 |
} |
# |
# Buffering of feedback, usage: |
# buffered warn "my warning" |
# ... |
# flush_buffered |
# buffered($1 = feedback function, $2 = arguments) |
buffered() { |
local grab=$( $1 "$2" 2>&1 ) |
# BUFFER=( "${BUFFER[@]}" -- "$grab" ) |
BUFFER=$BUFFER$grab$NL |
} |
# |
# Print buffered feedback to stderr |
# flush_buffered([$1 = indentation]) |
flush_buffered() { |
[ "${BUFFER[*]}" ] || return 0 |
echo "$BUFFER" | sed -e '$d' -e "s/^/$1/g" >&2 # sed: delete last line, indent with $1 |
BUFFER='' |
} |
# |
# trace($1 = function name = $FUNCNAME, function arguments...) |
trace() { |
if [ "$DEBUG" -ne "1" ]; then return; fi |
echo "[TRACE]: $@" >&2 |
} |
# Print an error message and exit |
# die([$1 = message [, $2 = exit_code]]) |
# If no message is provided, use $ERROR_MSG |
# If no exit_code is provided, use $ERROR_CODE |
die() { |
local m=$1 ec=$2 |
[ "$ec" ] || ec=$ERROR_CODE |
[ "$ec" ] || ec=1 |
[ "$m" ] || m=$ERROR_MSG |
error "$m" |
exit $ec |
} |
# |
# 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 && 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 >/dev/null |
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 |
local eff_eo= # Effective end_offset (for percentages) |
# 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 is_percentage $end_offset ; then |
eff_eo=$(percent $end $end_offset) |
else |
eff_eo=$(get_interval "$end_offset") |
fi |
if fptest $totime -le 0 ; then # If no totime is set, use end_offset |
eo=$eff_eo |
local runlen=$(awkexf "$end - $st") |
if fptest "($end-$eo-$st)" -le 0 ; then |
if fptest "$eo" -gt 0 && [ -z "$USR_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") |
local last= |
while fptest $stamp -le "$bound"; do |
assert $LINENO fptest $stamp -ge 0 |
LTC=( "${LTC[@]}" "$stamp" ) |
last=$stamp |
stamp=$(keepdecimals_lower $(awkexf "$stamp + $inc") 3) |
done |
inf "Capturing in range [$(pretty_stamp $inc)-$(pretty_stamp $last)]. Total length: $(pretty_stamp ${VID[$LEN]})" |
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() { |
trace $FUNCNAME $@ |
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() { |
trace $FUNCNAME $@ |
# Note mplayer CAN'T set the output filename, newer mplayer can set output |
#+dir though. |
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 "$f" \ |
$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 $(awkexf "$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" ;; |
VP80) vcodec="VP8" ;; |
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" ;; |
# 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" ;; |
vp8) mpid="VP80" ;; |
# 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" ;; |
# Other versions of R.A. listed at Wikipedia/RealAudio |
sipr) acodec="RealAudio SIPR" ;; # RA 4/5 |
cook) acodec="RealAudio Cook" ;; # RA 6 |
*) # 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>"$stderr" | grep ^ID) |
else |
MPLAYER_CACHE=$("$MPLAYER" -benchmark -ao null -vo null -identify -frames 0 \ |
-quiet -dvd-device "$f" dvd://$DVD_TITLE \ |
2>"$stderr" | 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 |
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 ] && { |
local vfile="$DVD_MOUNTP/VIDEO_TS/VTS_${DVD_VTS}_0.VOB" |
if [ ! -r "$vfile" ]; then |
error "Failed to locate mounted DVD. Detection will be less accurate." |
return 0 # We can continue anyway |
fi |
f="$vfile" |
} |
# XXX: FFmpeg detects mpeg1video in DVDs?? |
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 -e 's/:/h/' -e '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 0 ] || 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 $(awkexf "($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") |
if [ -z "$ffl" -a $DVD_MODE -eq 1 ]; then |
ffl="(unavailable in DVD mode)" |
fi |
[ "$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:$NL$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. When an incoherence is found, sets some sane values if reasonable |
#+or fails otherwise. |
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 |
# In case it's 0/0 or 0.0 since they aren't rejected |
if fptest "$extended_factor" -eq 0 ; then |
extended_factor=0 |
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 |
# Since 1.12 DVD mode can work with multiple inputs too |
# 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[@]}" ) |
# Interval=0 == default interval |
fptest "$interval" -eq 0 && interval=$DEFAULT_INTERVAL |
# If in non-latin mode and no nonlatin font has been picked try to pick one. |
# Should it fail, fallback to latin font. |
if [ $NONLATIN_FILENAMES -eq 1 ] && [ -z "$FONT_MINCHO" ]; then |
set_extended_font || { |
# set_extended_font already warns about lack of fonts |
warn " Falling back to latin font" |
NONLATIN_FILENAMES=0 |
FONT_MINCHO="$font_heading" |
} |
fi |
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() { |
trace $FUNCNAME $@ |
# 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 |
local dvs='' |
if [ -d /usr/local/share/fonts ]; then |
dvs=$(find /usr/local/share/fonts/ -type f -iname 'dejavusans.ttf') |
fi |
if [ -z "$dvs" -a -d /usr/share/fonts ]; then |
dvs=$(find /usr/share/fonts/ -type f -iname 'dejavusans.ttf') |
fi |
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 Sanitation: |
font_heading: $font_heading |
font_title : $font_title |
font_tstamps: $font_tstamps |
font_sign : $font_sign |
EOFF |
} |
# 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 |
DVD_MOUNTP= DVD_TITLE= # Re-set for each file |
if [ $DVD_MODE -eq 1 ]; then |
local dvdn=$(realpathr "$f") |
# Is it an ISO? |
if [ -f "$dvdn" ]; then |
DVD_MOUNTP=$(get_dvd_image_mountpoint "$dvdn") |
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 |
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_MOUNTP=$(mount | grep -o "^$dvdn *on [^ ]*" | cut -d' ' -f3) |
dvdn="DVD $f" |
fi |
if [ ! -r "$f" ]; then |
error "Can't access DVD ($f)" |
return $EX_NOINPUT |
fi |
inf "Processing $dvdn..." |
unset dvdn |
DVD_TITLE=${DVD_TITLES[0]} |
DVD_TITLES=( "${DVD_TITLES[@]:1}" ) # shift array |
if [ -z "$DVD_TITLE" -o "$DVD_TITLE" == "0" ]; then |
local dt="$(lsdvd "$f" 2>/dev/null | grep 'Longest track:' | \ |
cut -d' ' -f3- | sed 's/^0*//')" |
if ! is_number "$dt" ; then |
error "Failed to autodetect longest DVD title for '$f'" |
exit $EX_INTERNAL |
fi |
DVD_TITLE=$dt |
unset dt |
fi |
DVD_VTS=$(lsdvd -t$DVD_TITLE -v "$f" 2>/dev/null | grep -o 'VTS: [0-9]*' | cut -d' ' -f2) |
inf "Using DVD Title #$DVD_TITLE (VTS: $DVD_VTS) for '$f'" |
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" && [ "$th_height" != '100%' ]; then |
vidcap_height=$(rpercent ${VID[$H]} ${th_height}) |
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=$(awkexf "${VID[$ASPECT]}") |
else |
aspect_ratio=$(awkexf "${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 $(awkex "int(${#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") headheight= |
local heading=$(new_temp_file .png) |
# Add the title if any |
if [ "$title" ]; then |
local tlheight=$(line_height "$font_title" "$pts_title") |
convert \ |
\( \ |
-size ${headwidth}x$tlheight "xc:$bg_title" \ |
-font "$font_title" -pointsize "$pts_title" \ |
-background "$bg_title" -fill "$fg_title" \ |
-gravity Center -annotate 0 "$title" \ |
\) \ |
-flatten \ |
"$output" -append "$output" |
unset tlheight |
fi |
local fn_font= # see $NONLATIN_FILENAMES |
if [ $NONLATIN_FILENAMES -ne 1 ]; then |
fn_font="$font_heading" |
else |
fn_font="$FONT_MINCHO" |
fi |
# Create a small image to see how tall are characters. In my tests, no matter |
#+which character is used it's always the same height. |
local lineheight=$(line_height "$font_heading" "$pts_meta") |
# Since filename can be set in a different font check it too |
if [ "$fn_font" != "$font_heading" ]; then |
local fnlineheight=$lineheight |
fnlineheight=$(line_height "$fn_font" "$pts_meta") |
[ $fnlineheight -le $lineheight ] || lineheight=$fnlineheight |
unset fnlineheight |
fi |
headheight=$(( $lineheight * 3 )) |
# 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 "$f" 2>/dev/null | grep -o 'Disc Title: .*' | cut -d' ' -f3-) |
# There's no guarantee that titles are on separate VTS, I have no idea |
# how to compute the actual title size |
if [ "$DVD_MOUNTP" ]; then |
filename_label="Disc label" |
filename_value="$dvd_label" |
filesize_label="Titleset 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 "$f") $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 |
local signlh=$(line_height "$font_sign" "$pts_sign") |
local signheight=$(( 4 + ( $signlh * 2 ) )) |
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}x$signheight -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 signheight signlh |
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.35755975068581946630 #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 DVD Mode. |
In this mode the input <file>s must be the DVD |
device(s) or ISO(s). When in DVD mode all input files |
must be DVDs. |
Implies -A (auto aspect ratio) |
--dvd-title <arg> DVD title to use. Using 0 (the default) will use the |
longest title. |
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 |
-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. |
-p|--profile <arg> Load profile "arg" |
-C|--config <arg> Load configuration file "arg" |
--generate <config|profile> |
Generate configuration or profile from current settings |
-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 |
# ' # Syntax highlighting bait |
} |
# Print a configuration file generated from the currently active settings |
# generate_config($1 = <config|profile>) |
generate_config() { |
local n=$(echo $1 | tr '[a-z]' '[A-Z]') f= t= x= |
cat <<-EOM |
# --- $n STARTS HERE --- |
# This is a sample configuration file for VCS generated automatically |
# from the command-line with the "--generate $1" command-line option |
# Save it to ~/.vcs.conf or ~/.vcs/vcs.conf to make it the default |
# configuration. |
# OR |
# Save it to ~/.vcs/profiles/something.conf to create a profile named |
# "something". To use this profile run vcs with the "--profile something" |
# (or "-p something") option |
# OR |
# Save it to "something.conf" and load it with "--config something.conf" |
# (or "-C something.conf") |
EOM |
echo "${OVERRIDE_MAP[*]}" | stonl | egrep -v '(deprecated=|alias)' | cut -d':' -f1-2 |\ |
while read ovname ; do |
f=${ovname/:*} |
t=${ovname#*:} |
if [ -z "$t" ] || [ "$t" == "=" ]; then t=$f ; fi |
eval v=\$USR_$t |
[ -z "$v" ] || { |
# Symbolic values: |
case "$t" in |
timecode_from) |
x='$TC_NUMCAPS' |
[ $v -eq $TC_NUMCAPS ] || x='$TC_INTERVAL' |
v=$x |
;; |
decoder) |
x='$DEC_FFMPEG' |
[ $v -eq $DEC_FFMPEG ] || x='$DEC_MPLAYER' |
v=$x |
;; |
verbosity) |
case $v in |
$V_ALL) v='$V_ALL' ;; |
$V_NONE) v='$V_NONE' ;; |
$V_INFO) v='$V_INFO' ;; |
$V_WARN) v='$V_WARN' ;; |
$V_ERROR) v='$V_ERROR' ;; |
esac # verbosity |
;; |
esac |
# Print all names in lowercase |
echo "$(tolower "$f")=$v" |
} |
done |
echo "# vcs:conf:$NL# Generated on $(date)$NL# --- $n ENDS HERE --- " |
exit 0 |
} |
# }}} # 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:VR:Z:o:p:C: \ |
--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:,end-offset:,disable:,dvd,dvd-title:,randomsource:,undocumented:,output:,"\ |
"fullhelp,profile:,"\ |
"jpeg2,nonlatin,generate:,config:" \ |
-- "$@") |
eval set -- "$TEMP" |
while true ; do |
case "$1" in |
-i|--interval) |
check_constraint 'interval' "$2" "$1" || die |
interval=$(get_interval $2) |
timecode_from=$TC_INTERVAL |
USR_interval=$interval |
USR_timecode_from=$TC_INTERVAL |
shift # Option arg |
;; |
-n|--numcaps) |
check_constraint 'numcaps' "$2" "$1" || die |
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|--end-offset) |
check_constraint 'end_offset' "$2" "$1" || die |
is_p='y' |
is_percentage "$2" || is_p='' |
if [ "$is_p" ]; then |
end_offset="$2" |
else |
end_offset=$(get_interval "$2") |
fi |
USR_end_offset="$end_offset" |
unset is_i |
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 ;; |
-F) decoder=$DEC_FFMPEG ; USR_decoder=$decoder ;; |
-M) decoder=$DEC_MPLAYER ; USR_decoder=$decoder ;; |
-H|--height) |
check_constraint 'height' "$2" "$1" || die |
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) |
check_constraint 'columns' "$2" "$1" || die |
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 |
if [ "$2" ]; then |
check_constraint 'extended_factor' "$2" "$1" || die |
extended_factor="$2" |
else |
extended_factor=$DEFAULT_EXT_FACTOR |
fi |
USR_extended_factor=$extended_factor |
shift |
;; |
# Unlike -I, --nonlatin does not accept a font name |
--nonlatin) |
if [ -z "$USR_FONT_MINCHO" ]; then |
NONLATIN_FILENAMES=1 |
USR_NONLATIN_FILENAMES=1 |
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 |
NONLATIN_FILENAMES=1 |
USR_NONLATIN_FILENAMES=1 |
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 |
cmdline_override "$2" |
POST_GETOPT_HOOKS=( "${POST_GETOPT_HOOKS[@]}" 1:cmdline_overrides_flush ) |
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=$(awkexf "$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=$(awkexf "$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=$(awkexf "$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 |
;; |
*) load_profile "$2" || die |
;; |
esac |
shift |
;; |
-C|--config) |
if echo "$2" | grep -q '^:' ; then |
if [ $2 = ':pwd' ]; then |
cfg=./vcs.conf |
else |
error "Configuration names starting with ':' are reserved." |
exit $EX_USAGE |
fi |
else |
cfg=$2 |
fi |
[ -f "$cfg" ] || { |
error "Configuration file '$cfg' not found" |
exit $EX_USAGE |
} |
# ./vcs.conf doesn't need the vcs:conf: mark |
if [ $2 != ':pwd' ]; then |
head -5 "$cfg" | grep -q '#[[:space:]]*vcs:conf[[:space:]]*:' || \ |
tail -5 "$cfg" | grep -q '#[[:space:]]*vcs:conf[[:space:]]*:' || { |
error "No vcs:conf: mark found in '$cfg'" |
exit $EX_NOINPUT |
} |
fi |
load_config_file "$cfg" 'Custom configuration' |
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 |
;; |
--dvd-title) |
check_constraint 'dvd_title' "$2" "$1" || die |
DVD_TITLES=( "${DVD_TITLES[@]}" "$2" ) |
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 |
decoder=$DEC_MPLAYER |
aspect_ratio=-2 # Special value: Auto detect only if ffmpeg couldn't |
;; |
-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 |
USR_verbosity=$verbosity |
;; |
-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")" |
error "Shoehorning of options is scheduled to be removed in the next version." |
error " Please contact the author if you absolutely need it." |
;; |
debug) |
warn "[U] debug" |
DEBUG=1 |
;; |
# Dump user-set variables and exit [since 1.12] |
uservars) |
echo "${OVERRIDE_MAP[*]}" | stonl | egrep -v '(deprecated=|alias)' | cut -d':' -f1-2 |\ |
while read ovname ; do |
f=${ovname/:*} |
t=${ovname#*:} |
if [ "$t" -a "$t" != "=" ]; then f="$t" ; fi |
eval v=\$USR_$f |
[ -z "$v" ] || echo "$(tolower $f)=$v" |
done |
exit 0 |
;; |
*) false ;; |
esac |
shift |
;; |
--generate) |
case "$2" in |
profile|config) |
POST_GETOPT_HOOKS=( "${POST_GETOPT_HOOKS[@]}" \ |
10:generate_config:$2 ) |
;; |
*) |
error "Option --generate must be followed by profile or config" |
exit $EX_USAGE |
;; |
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 |
# Avoid coherence_check if there's no arguments and no cmdline post |
# processing |
[ "$1" -o "$POST_GETOPT_HOOKS" ] || { |
[ $verbosity -eq $V_NONE ] || show_help |
exit $EX_USAGE |
} |
# More than one argument... |
if [ "$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 $? |
} |
# Run after coherence check to clean recoverable incorrect values |
post_getopt_hooks |
# Remaining arguments |
if [ ! "$1" ]; then |
[ $verbosity -eq $V_NONE ] || show_help |
exit $EX_USAGE |
fi |
# TODO: |
# DVD mode + multiple titles is still tricky: |
# --dvd --dvd-title 1 --dvd-title 2 /dev/dvd /dev/dvd |
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 |
# * bash3: |& (inherited from csh?) pipes both stdout and stderr |
# * 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.12.2/pkg/common.mk |
---|
0,0 → 1,72 |
# $Id$ |
# |
# To be included from GNUmakefile or BSDmakefile |
# To use it directly set VERSION and PACKAGER |
# e.g. make VERSION=1.x PACKAGER=Me <rule> |
# |
# Notes to self: |
# This file should follow only common/portable make syntax and commands |
# Common pitfalls: |
# - $(shell) -> GNU Make, equivalent BSD make: != |
# - install -D -> GNU only (-d is portable) |
# - $(RM) -> empty by default in BSD, set from BSDmakefile |
prefix:=/usr/local |
DESTDIR:=/ |
TGZ=vcs-$(VERSION).tar.gz |
MANDIR:=$(prefix)/share/man/man1/ |
all: |
# Nothing to be done |
dist: vcs.spec |
# Files installed in packages but not outside |
prepackage: examples/vcs.conf.example |
install: |
install -d $(DESTDIR)$(prefix)/bin/ |
install -m755 vcs $(DESTDIR)$(prefix)/bin/vcs |
install -d $(DESTDIR)$(prefix)/share/vcs/profiles |
install -m644 profiles/*.conf $(DESTDIR)$(prefix)/share/vcs/profiles/ |
install -d $(DESTDIR)$(MANDIR) |
install -m644 vcs.1 $(DESTDIR)$(MANDIR)/ |
uninstall: |
$(RM) $(DESTDIR)$(prefix)/bin/vcs |
$(RM) $(DESTDIR)$(MANDIR)/vcs.1 |
for file in profiles/*.conf ; do \ |
$(RM) $(DESTDIR)$(prefix)/share/vcs/profiles/`basename $$file` ; \ |
done |
-rmdir -p $(DESTDIR)$(prefix)/bin |
-rmdir -p $(DESTDIR)$(prefix)/share/vcs/profiles |
-rmdir -p $(DESTDIR)$(MANDIR) |
examples/vcs.conf.example: examples/vcs.conf |
sed -e 's/^/#/;s/^#$$//;s/^##/#/' < $< > $@ |
vcs.spec: rpm/vcs.spec.in vcs |
test "$(VERSION)" # Version (=$(VERSION)) must be detected |
@echo "[vcs.spec]" |
@cat $< | sed 's!@VERSION@!$(VERSION)!g' | \ |
sed 's!@PACKAGER@!$(PACKAGER)!g' > $@ |
# PKGBUILD CAN'T BE INCLUDED in the archive |
PKGBUILD: arch/PKGBUILD.in $(TGZ) vcs |
test "$(VERSION)" # Version (=$(VERSION)) must be detected |
@echo "[PKGBUILD]" |
@MD5=$(shell md5sum -b $(TGZ) | cut -d' ' -f1) ; \ |
SHA1=$(shell sha1sum -b $(TGZ) | cut -d' ' -f1) ; \ |
SHA256=$(shell sha256sum -b $(TGZ) | cut -d' ' -f1) ; \ |
cat $< | sed -e 's!@VERSION@!$(VERSION)!g' \ |
-e "s/@MD5@/$$MD5/g" \ |
-e "s/@SHA1@/$$SHA1/g" -e "s/@SHA256@/$$SHA256/g" > $@ |
clean: |
-$(RM) examples/vcs.conf.example |
distclean: clean |
-$(RM) vcs.spec PKGBUILD |
.PHONY: all install clean tgz |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.2/pkg/rpm/vcs.spec.in |
---|
0,0 → 1,113 |
# |
# $Rev$ |
# |
# 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 |
make examples/vcs.conf.example |
%install |
make DESTDIR=%buildroot prefix=%{prefix} install |
%clean |
[ ${RPM_BUILD_ROOT} != "/" ] && rm -rf ${RPM_BUILD_ROOT} |
#%post |
# postinst |
#%postun |
# postuninst |
%files |
%defattr(-,root,root) |
# binary |
%{_bindir}/%{name} |
# Profiles |
%{prefix}/share/vcs/profiles/black.conf |
%{prefix}/share/vcs/profiles/mosaic.conf |
%{prefix}/share/vcs/profiles/white.conf |
# Manpage |
%{_mandir}/man1/%{name}.1.gz |
%doc CHANGELOG |
# Config example |
%doc examples/vcs.conf.example |
%changelog |
* Tue Aug 24 2010 - outlyer (at) gmail (dot) com |
- Install manpage |
* Sat Apr 10 2010 - outlyer (at) gmail (dot) com |
- Added profiles and example configuration |
- Use %{prefix} |
* 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.12.2/pkg/debian/changelog |
---|
0,0 → 1,75 |
vcs (1.12.2-upstream.1) experimental; urgency=medium |
* New version. Medium priority due to temporary files cleanup bug. |
-- Toni Corvera <outlyer@gmail.com> Tue, 24 Aug 2010 20:48:41 +0200 |
vcs (1.12.1-upstream.1) experimental; urgency=medium |
* New version. |
-- Toni Corvera <outlyer@gmail.com> Fri, 23 Apr 2010 13:56:58 +0200 |
vcs (1.12-upstream.1) experimental; urgency=low |
* New version. |
* debian/docs: Install vcs.conf.example |
-- Toni Corvera <outlyer@gmail.com> Sat, 10 Apr 2010 00:57:17 +0200 |
vcs (1.11.2-upstream.1) experimental; urgency=low |
* New version. |
-- Toni Corvera <outlyer@gmail.com> Fri, 19 Mar 2010 00:18:51 +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/tags/1.12.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 |
DESTDIR:=$(CURDIR)/debian/vcs |
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) all prepackage |
#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=$(DESTDIR) 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 vcs.1 |
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.12.2/pkg/debian/docs |
---|
0,0 → 1,0 |
examples/vcs.conf.example |
/video-contact-sheet/tags/1.12.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/tags/1.12.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/tags/1.12.2/pkg/debian/dirs |
---|
0,0 → 1,0 |
usr/bin |
/video-contact-sheet/tags/1.12.2/pkg/debian/compat |
---|
0,0 → 1,0 |
5 |
/video-contact-sheet/tags/1.12.2/pkg/profiles/mosaic.conf |
---|
0,0 → 1,12 |
# vcs:conf: |
# vcs:desc: Tight, small, thumbnails |
# <http://p.outlyer.net/dox/vcs:example_configs> |
# $Id$ |
disable_timestamps=1 |
disable_shadows=1 |
height=160 |
numcaps=20 |
timecode_from=$TC_NUMCAPS |
padding=0 |
columns=4 |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.2/pkg/profiles/black.conf |
---|
0,0 → 1,9 |
# vcs:conf: |
# vcs:desc: White-on-Black |
# $Id$ |
bg_contact=Black |
bg_heading=$bg_contact |
bg_sign=$bg_contact |
fg_heading=White |
fg_sign=$fg_heading |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.2/pkg/profiles/white.conf |
---|
0,0 → 1,9 |
# vcs:conf: |
# vcs:desc: Black-on-White profile |
# $Id$ |
bg_contact=White |
bg_heading=$bg_contact |
bg_sign=$bg_contact |
fg_heading=Black |
fg_sign=$fg_heading |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.2/pkg/CHANGELOG |
---|
0,0 → 1,406 |
1.12.2 (2010-08-24): |
* BUGFIX: Fix cleanup of temporary files (regression since 1.11.2). [#167] |
Submitted by Jason Tackaberry. |
* FEATURES: |
- Added 'fg_all', 'bg_all' and 'font_all' config variables. [#156] |
- Added 'nonlatin_filenames' config variable. [#159] |
- Added identification for VP8 (WebM). [#166] |
* OTHER: |
- Print variable names in lowercase when using --generate. |
1.12.1 (2010-04-23): |
* BUGFIXES: |
- Workaround for cases in which GAWK uses comma as decimal separator. |
Any OS with GAWK 3.1.3 to 3.1.5 was affected (where the environment |
language uses commas, e.g. Debian Lenny with many European languages) |
- Don't try to go on in DVD mode with unreadable ISOs |
1.12: (2010-04-10) |
* New features/tweaks: |
- Loading of random configuration files (--config / -C) |
- Profiles: Similar to above but simpler syntax (--profile / -p) |
- Config/Profile generation from command-line (--generate) |
- Adapt heading, title and footer height to font size (fonts that used |
to get cropped should now be fine) |
* DVD mode cleanup: |
- Command-line switched to match "normal" files: |
Before: |
$ vcs --dvd /dev/dvd 0 or $ vcs -dvd /dev/dvd 1 |
Equivalents now: |
$ vcs --dvd /dev/dvd or $ vcs --dvd --dvd-title 1 /dev/dvd |
* New end-offset behaviour: |
- A 5.5% end offset is applied by default |
- Can be disabled with -E0 or end_offset=0 |
- MIN_LENGTH_FOR_END_OFFSET is no longer used |
* Configuration files cleanup: |
- Simplified or more meaningful names where appropriate (the older |
names will continue to work for a while, and users will be warned) |
"vcs --generate" with no other arguments can be used to translate them |
- Validation of configuration options. |
Incorrect values will be discarded and an error shown; processing will |
continue. |
- Configuration searched in ~/.vcs/vcs.conf too |
- Syntax enhancements: |
> Comments can now be included in-line |
> Putting '#' in a value now requires using the "escaped form" '$#' |
> Semicolons (;) also serve to start comments: When one is found the |
rest of the line is ignored, they continue to be disallowed in values |
i.e. 'tl;dr' will be parsed as 'tl' |
* Other: |
- Accept timecodes and percentages in end_offset, both from the |
command-line and in configuration files |
- Print the start and end timestamps in effect before capturing |
- No longer accept interval zero (used to be re-set to default) |
- Tighter printing of overrides and no longer printed as warning |
- Strickter handing of wrong options |
- Fall back to Helvetica also when no fonts dir is located. Look |
in /usr/local too. |
- --end-offset added as an alias to --end_offset |
- Starting with 1.12 a tarball + makefile is also provided |
* BUGFIXES: |
- Avoid possible (unlikely) usage of scientific notation in internal |
calculations |
- Distinguish between default end offset and user's end offset with the |
same value |
- Handle --nonlatin correctly |
- DVD Mode + FFmpeg identification: Check VOB #0 instead of #1 |
- Don't print escape codes to stdout when testing colour printing |
* Options removed: |
--shoehorn, temporary replacement: --undocumented shoehorn. Will be gone |
in 1.13 |
--mincho, replaced by --nonlatin since 1.11 |
MIN_LENGTH_FOR_END_OFFSET, as explained above, no longer needed |
* INTERNAL: |
- $CFGFILE replaced by ~/.vcs.conf |
- Use -p for profiles instead of -P (used, undocumented, in 1.11) |
1.11.2: (2010-03-19) |
* Added Cook and Sipro (RealAudio 4, 5 & 6) codecs |
* BUGFIXES: |
- Remove extra, empty, temporary dir |
- Use standard awk syntax for exponentiation (pyth_th) |
- Workaround for systems that don't register fonts with ImageMagick |
* DEBUG: Print to stderr when probbing with mplayer too |
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 |
# vim:set ts=3 sw=3 et textwidth=80: # |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.2/pkg/arch/PKGBUILD.in |
---|
0,0 → 1,39 |
# |
# $Rev$ |
# |
# Maintainer (Upstream): Toni Corvera <outlyer@gmail.com> |
# |
# Build with '$ makepkg' on the same directory as this file |
# |
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=() |
options=('docs' 'zipman') |
source=($url/files/$pkgname-$pkgver.tar.gz) |
md5sums=(@MD5@) #generate with 'makepkg -g' |
sha1sums=(@SHA1@) |
# Debian & Arch didn't agree on this on my first try (???) |
sha256sums=(@SHA256@) |
build() { |
cd $srcdir/$pkgname-$pkgver |
make prepackage |
make install DESTDIR=${pkgdir} prefix=/usr |
install -D $srcdir/$pkgname-$pkgver/examples/vcs.conf.example \ |
${pkgdir}/usr/share/doc/$pkgname/vcs.conf.example |
} |
#man page (TODO) |
# install -D -m644 $pkgname.1 ${pkgdir}/usr/share/man1/$pkgname.1 || return 1 |
# vim:set filetype=sh ts=2 et: # |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.2/pkg/BSDmakefile |
---|
0,0 → 1,14 |
VERSION!=head -50 vcs | grep 'declare -r VERSION=' | perl -pe 's/.*"(.*)".*/\1/' |
PACKAGER!=finger -lp `echo $USER` 2>/dev/null | head -n1 | cut -d: -f3 |
.if empty($(PACKAGER)) |
PACKAGER!=grep ^`id -un` /etc/passwd | cut -d: -f5 | cut -d, -f1 |
.endif |
.if empty($(RM)) |
RM=rm -f |
.endif |
include common.mk |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.2/pkg/GNUmakefile |
---|
0,0 → 1,13 |
# |
# $Id$ |
# Makefile for GNU-make |
# |
VERSION:=$(shell head -50 vcs | grep 'declare -r VERSION=' | sed -e 's/.*"\(.*\)".*/\1/') |
PACKAGER:=$(shell finger -lp `echo $USER` 2>/dev/null | head -n1 | cut -d: -f3) |
ifeq ($(PACKAGER),) |
PACKAGER:=$(shell grep ^`id -un` /etc/passwd | cut -d: -f5 | cut -d, -f1) |
endif |
include common.mk |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.2/pkg/README |
---|
0,0 → 1,39 |
Index |
----- |
1. Files |
2. Installation |
3. Uninstallation |
Files |
----- |
In this package: |
vcs The VCS script |
profiles/ Example profiles: |
mosaic.conf 20 small thumbnails in a 5x4 grid, no padding |
black.conf Black background and white text |
white.conf White background and black text |
examples/vcs.conf Example configuration |
Use "make examples/vcs.conf.example" to create |
a version with all options commented out. |
Installation |
------------ |
$ make install |
Will install under /usr/local |
$ make install prefix=/usr |
Will install under /usr |
Uninstallation |
-------------- |
$ make uninstall |
If you used a prefix during install use it too during uninstall |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.2/pkg/examples/vcs.conf |
---|
0,0 → 1,152 |
# |
# vcs:conf: $Rev$ |
# Example vcs.conf file |
# This example files contains all the default values, commented out. |
# For each setting, where an equivalent command-line option exists it will be |
# listed in the comments |
# |
# Location: |
# Configuration files can be placed at /etc/vcs.conf (site-wide), |
# ~/.vcs.conf (current user, hidden file!), ./vcs.conf (current dir) or |
# ~/.vcs/vcs.conf (current user, new alternative location) |
# |
# Syntax: |
# * Comments start with '#' or ';' |
# * '#' can be used in values by writing $# |
# * Semicolons (;) can't be used in values |
# * Options are of the form name=value |
# - Options can refer to the *current* value of other options, i.e. |
# font_sign=$font_heading will assign to 'font_sign' the same value |
# as 'font_heading'. If font_heading is changed after that, font_sign |
# won't be affected. |
# - See <http://p.outlyer.net/dox/vcs:conf_files> for the full list |
# Height of individual captures. Percentage or fixed size (in pixels) |
height=100% # option -H |
# Amount ignored from the end of the video (think of it as an anti-spoiler |
# measure). Percentage (of video duration) or time (e.g. 2m). Set to 0 |
# to disable it. |
end_offset=5% # option -E |
######################## |
# Contact Sheet Layout # |
######################## |
columns=2 # Number of columns in the contact sheet (option -c) |
interval=1m # Interval between captures (option -i) |
# Number of captures. Note setting this isn't enough to use a fixed number, the |
# mode must also be changed (option 'timecode_from', see below). (option -n) |
captures=16 |
# Mode of operation, can be either $TC_NUMCAPS or $TC_INTERVAL (the default) |
# $TC_NUMCAPS Fix the number of captures, adjust interval accordingly |
# $TC_INTERVAL Fix interval, adjust the number of captures accordingly |
# (options -i and -n set this implicitly) |
timecode_from=$TC_INTERVAL |
# Modifier for extended mode (option -e). |
# Beware, setting it to something different from 0 automatically enables extended mode! |
extended_factor=0 |
# Extra padding added around each capture. |
# This has *no effect* when shadows are enabled (the default). |
# Tweaking this might break alignment when using extended mode (-e) |
padding=2 |
############### |
# Output file # |
############### |
# Output format. Use the file extension (e.g. 'png', 'jpg', 'jp2') |
# Any format accepted by ImageMagick can be used here (even pdf or gif, not |
# that they'll look very good though) |
# (options: -j for JPEG, -j2 for JPEG 2000) |
format=png |
quality=92 # Output quality for lossy formats (e.g. jpg) |
#################### |
# Cosmetic touches # |
#################### |
user=`id -un` # User name, will be used in the contact sheet footer (option -u) |
# Used in the signature, e.g. "Preview created by {value of user}" |
signature=Preview created by |
disable_shadows=0 # Disable shadows by default (option -ds) |
disable_timestamps=0 # Disable timestamps by default (option -dt) |
##################### |
# Colours and fonts # |
##################### |
# * Colours can be defined either by their name or through hexcodes |
# use the command 'identify -list color' for a list of known names |
# * Colours can use transparency, although this usually only makes sense |
# for timestamps, which are overlaid on captures. |
# * Beware when specifying colours in hex format color=#hex is ok |
# but color = #hex not, spaces can be used with color = $#hex |
# * Font sizes are expressed in points and hence might need adjustment |
# when the font is changed. |
# * Font names can be either font paths (e.g. /usr/something/ttf/font.ttf) |
# or font names as understood by ImageMagick, use the command |
# 'identify -list font' for a list of fonts known to ImageMagick |
bg_heading=#afcd7a # Heading/meta-information section background colour |
fg_heading=Black # Heading font colour |
font_heading=DejaVu-Sans-Book # Heading font |
pts_heading=14 # Font size for heading |
bg_title=White # Background for the title (if activated with option -T) |
fg_title=Black # Title font colour |
font_title=$font_heading # Title font |
bg_contact=White # Background for the contact sheet |
bg_tstamps=#000000aa # Background for timestamps. Note the use of transparency |
fg_tstamps=White # Timestamps font colour |
font_tstamps=$font_heading # Timestamps font |
pts_tstamps=14 # Font size for timestamps |
# Background for the signature, i.e. the section after the contact sheet with |
# vcs and user identification |
bg_sign=SlateGray |
fg_sign=Black # Font colour for the signature |
font_sign=$font_heading # Font for the signature |
pts_sign=10 # Font size for signature |
# Font to use for file name when the non-latin mode (--nonlatin) is enabled |
# Option: -Ik=fontname |
nonlatin_font= # Autodetected (hopefully) |
###################### |
# Lower level tweaks # |
###################### |
# Decoder to use by default, can be either $DEC_FFMPEG (default) or |
# $DEC_MPLAYER |
# (options: -M for MPlayer, -F for FFMpeg) |
decoder=$DEC_FFMPEG |
# Output from commands, useful to locate errors |
stdout=/dev/null |
stderr=/dev/null |
# Verbosity level, very verbose by default. |
# Possible values: $V_ALL (default), $V_ERROR, $V_WARN, $V_INFO, $V_NONE |
# (options: -q for quietness) |
verbosity=$V_ALL |
# 1 disables colours in console output |
simple_feedback=0 |
debug=0 # When 1, enables debugging mode (option -D) |
getopt=getopt # GNU Getopt executable name |
# $Rev$ # |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.2/manpage.xml |
---|
0,0 → 1,382 |
<?xml version='1.0' encoding='UTF-8'?> |
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" |
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ |
<!-- |
$Id$ |
This file based on the default template for debian packages. |
Generation of man page: |
$ xmlto man manpage.xml |
Will generate vcs.1. |
View with: |
$ nroff -man vcs.1 | less |
or |
$ man vcs.1 |
To disable the automatic creation of the AUTHOR(S) and COPYRIGHT sections |
read /usr/share/doc/docbook-xsl/doc/manpages/authors.html. This file can be |
found in the docbook-xsl-doc-html package. |
Validation can be done using: `xmllint -''-noout -''-valid manpage.xml` |
General documentation about man-pages and man-page-formatting: |
man(1), man(7), http://www.tldp.org/HOWTO/Man-Page/ |
--> |
<!-- Fill in your name for FIRSTNAME and SURNAME. --> |
<!ENTITY dhfirstname "Toni"> |
<!ENTITY dhsurname "Corvera"> |
<!-- dhusername could also be set to "&dhfirstname; &dhsurname;". --> |
<!ENTITY dhusername "Toni Corvera"> |
<!ENTITY dhemail "outlyer@gmail.com"> |
<!ENTITY dhsection "1"> |
<!-- TITLE should be something like "User commands" or similar (see |
http://www.tldp.org/HOWTO/Man-Page/q2.html). --> |
<!ENTITY dhtitle "vcs User Manual"> |
<!ENTITY dhucpackage "VCS"> |
<!ENTITY dhpackage "vcs"> |
]> |
<refentry> |
<refentryinfo> |
<title>&dhtitle;</title> |
<productname>&dhpackage;</productname> |
<authorgroup> |
<author> |
<firstname>&dhfirstname;</firstname> |
<surname>&dhsurname;</surname> |
<contrib>VCS author.</contrib> |
<address> |
<email>&dhemail;</email> |
</address> |
</author> |
</authorgroup> |
<copyright> |
<year>2007-2010</year> |
<holder>&dhusername;</holder> |
</copyright> |
<legalnotice> |
<para>Permission is granted to copy, distribute and/or modify this |
document under the terms of the GNU Lesser General Public License, |
Version 2 or (at your option) any later version published by |
the Free Software Foundation.</para> |
</legalnotice> |
<edition>$Rev$</edition> |
</refentryinfo> |
<refmeta> |
<refentrytitle>&dhucpackage;</refentrytitle> |
<manvolnum>&dhsection;</manvolnum> |
</refmeta> |
<refnamediv> |
<refname>&dhpackage;</refname> |
<refpurpose>create contact sheets from videos</refpurpose> |
</refnamediv> |
<refsynopsisdiv> |
<cmdsynopsis> |
<command>&dhpackage;</command> |
<arg choice="opt"><option>options</option></arg> |
<arg choice="plain"><replaceable>FILE</replaceable></arg> |
<arg choice="opt"><replaceable>FILE</replaceable> |
<arg choice="opt"><replaceable>...</replaceable></arg> |
</arg> |
</cmdsynopsis> |
<cmdsynopsis> |
<command>&dhpackage;</command> |
<arg choice="opt"> |
<option>-n<parameter>20</parameter></option> |
<option>-c<parameter>4</parameter></option> |
</arg> |
<arg choice="plain"><replaceable>FILE</replaceable></arg> |
</cmdsynopsis> |
<cmdsynopsis> |
<command>&dhpackage;</command> |
<arg choice="opt"><option>--output=<parameter>OUTPUT1</parameter></option></arg> |
<arg choice="opt"><option>--output=<parameter>OUTPUT2</parameter></option></arg> |
<arg choice="opt"><option>...</option></arg> |
<arg choice="plain"><replaceable>INPUT1</replaceable></arg> |
<arg choice="opt"><replaceable>INPUT2</replaceable> |
<arg choice="opt"><replaceable>...</replaceable></arg> |
</arg> |
</cmdsynopsis> |
<cmdsynopsis> |
<command>&dhpackage;</command> |
<!-- Normally the help and version options make the programs stop |
right after outputting the requested information. --> |
<group choice="opt"> |
<arg choice="plain"> |
<group choice="req"> |
<arg choice="plain"><option>-h</option></arg> |
<arg choice="plain"><option>--help</option></arg> |
</group> |
</arg> |
<arg choice="plain"> |
<arg choice="plain"><option>--fullhelp</option></arg> |
</arg> |
</group> |
</cmdsynopsis> |
</refsynopsisdiv> |
<refsect1 id="description"> |
<title>DESCRIPTION</title> |
<!-- FIXME --> |
<para>This is a work in progress manual page for <command>&dhpackage;</command>, |
use <parameter>--help</parameter>, <parameter>--fullhelp</parameter> |
and the online documentation (located at <ulink url="http://p.outlyer.net/dox/vcs" />) |
for further usage instructions.</para> |
<para><command>&dhpackage;</command> is a program that creates a preview |
image from videos in a contact sheet-like format (i.e. captures from |
different frames in the video are placed in a mosaic).</para> |
<para>By default the output file will be named like the input file plus the |
png extension. Example: "<filename>file.avi</filename>" will produce |
a contact sheet in the file "<filename>file.avi.png</filename>".</para> |
<para>The default mode of operation is to obtain captures every five minutes in the |
video, so the amount of captures will vary with each file. The command-line |
argument <parameter>--numcaps</parameter> (<parameter>-n</parameter>) can be used |
to change this behaviour or alternatively a configuration file might |
be used to change the mode of operation (see <citerefentry> |
<refentrytitle>vcs.conf</refentrytitle> |
<manvolnum>5</manvolnum> |
</citerefentry>). |
</para> |
</refsect1> |
<refsect1 id="options"> |
<title>OPTIONS</title> |
<para>The program follows the usual GNU command line syntax, |
with long options starting with two dashes (`-'). A summary of |
options is included below.</para> |
<variablelist> |
<varlistentry> |
<term><option>-n <replaceable>number</replaceable></option></term> |
<term><option>--numcaps=<replaceable>number</replaceable></option></term> |
<listitem> |
<para>Fixes the number of captures to obtain. Changes the mode of operation |
to capture a fixed number of frames.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-i <replaceable>interval</replaceable></option></term> |
<term><option>--interval=<replaceable>interval</replaceable></option></term> |
<listitem> |
<para>Sets the interval between captures. Changes the mode of operation |
to capture at fixed intervals. The number of captures will depend |
on the video length.</para> |
<para>See <xref linkend="interval_format" /> for the allowed syntax.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-c <replaceable>number</replaceable></option></term> |
<term><option>--columns=<replaceable>number</replaceable></option></term> |
<listitem> |
<para>Number of columns in the contact sheet. The number of rows |
will depend on this and the number of captures (there's no |
way to set the number of rows).</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-H <replaceable>height</replaceable></option></term> |
<term><option>--height=<replaceable>height</replaceable></option></term> |
<listitem> |
<para>Height of captures. Can be a number (of pixels) or a percentage |
(of the video height). By default the same size as the video is used.</para> |
<para>The width is derived from height and aspect ratio.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>-o <replaceable>filename</replaceable></option></term> |
<term><option>--output=<replaceable>filename</replaceable></option></term> |
<listitem> |
<para>Name of output file. By default the video file name plus the output |
format is used (e.g. "<filename>video.avi.png</filename>" |
for "<filename>video.avi</filename>").</para> |
<para>If an extension is provided, it will define the output format, otherwise |
PNG will be used. I.e. <filename>sheet.jpg</filename> will produce |
a JPEG file while <filename>sheet</filename> and |
<filename>sheet.png</filename> will produce a PNG file.</para> |
</listitem> |
</varlistentry> |
<!-- |
<varlistentry> |
<term><option>-x <replaceable>t</replaceable></option></term> |
<term><option>- -xy=<replaceable>t</replaceable></option></term> |
<listitem> |
<para>DESC.</para> |
</listitem> |
</varlistentry> |
--> |
<varlistentry> |
<term><option>-h</option></term> |
<term><option>--help</option></term> |
<listitem> |
<para>Show summary of most common options.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><option>--fullhelp</option></term> |
<listitem> |
<para>Show summary of all options.</para> |
</listitem> |
</varlistentry> |
</variablelist> |
</refsect1> |
<refsect1 id="files"> |
<title>FILES</title> |
<variablelist> |
<varlistentry> |
<term><filename>/etc/vcs.conf</filename></term> |
<listitem> |
<para>The system-wide configuration file to control the |
behaviour of <application>&dhpackage;</application>. See |
<citerefentry> |
<refentrytitle>vcs.conf</refentrytitle> |
<manvolnum>5</manvolnum> |
</citerefentry> for further details.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><filename>${HOME}/.vcs.conf</filename></term> |
<term><filename>${HOME}/.vcs/vcs.conf</filename></term> |
<listitem> |
<para>The per-user configuration file to control the |
behaviour of <application>&dhpackage;</application>. See |
<citerefentry> |
<refentrytitle>vcs.conf</refentrytitle> |
<manvolnum>5</manvolnum> |
</citerefentry> for further details.</para> |
</listitem> |
</varlistentry> |
<varlistentry> |
<term><filename>${PWD}/vcs.conf</filename></term> |
<listitem> |
<para>The per-directory configuration file to control the |
behaviour of <application>&dhpackage;</application>. See |
<citerefentry> |
<refentrytitle>vcs.conf</refentrytitle> |
<manvolnum>5</manvolnum> |
</citerefentry> for further details.</para> |
</listitem> |
</varlistentry> |
</variablelist> |
</refsect1> |
<refsect1 id="interval_format"> |
<title>INTERVALS</title> |
<para> |
Intervals and timestamps can be specified in seconds or in a human-readable format |
that follows the syntax |
<replaceable>HOURS</replaceable>h<replaceable>MINUTES</replaceable>m |
<replaceable>SECONDS</replaceable>s.<replaceable>MILLISECONDS</replaceable> |
where each element is optional. |
See <ulink url="http://p.outlyer.net/dox/vcs:time_syntax" /> for more details. |
</para> |
<para> |
<segmentedlist> |
<segtitle>Example</segtitle><segtitle>Equivalence</segtitle> |
<segtitle>Standard time format</segtitle> |
<seglistitem> |
<seg>1h30m30</seg><seg>1h30m30s.00</seg><seg>1:30:30.00</seg> |
</seglistitem> |
<seglistitem> |
<seg>30</seg><seg>0h0m30s.00</seg><seg>0:00:30.00</seg> |
</seglistitem> |
<seglistitem> |
<seg>3600</seg><seg>1h0m0s.00</seg><seg>1:00:00.00</seg> |
</seglistitem> |
</segmentedlist> |
</para> |
</refsect1> |
<refsect1 id="environment"> |
<title>ENVIRONMENT</title> |
<variablelist> |
<varlistentry> |
<term><envar>TEMPDIR</envar></term> |
<listitem> |
<para>Fallback temporary directory when <filename>/dev/shm</filename> is not avaiable. |
Due to the big size of temporary files, it is recommended to use |
a temporary directory on a fast filesystem.</para> |
</listitem> |
</varlistentry> |
</variablelist> |
</refsect1> |
<refsect1 id="diagnostics"> |
<title>DIAGNOSTICS</title> |
<para>The default verbosity level will print <package>vcs</package>' progress |
and any errors or warnings on <filename class="devicefile">stderr</filename>.</para> |
<para><option>--quiet</option> can be used to reduce verbosity.</para> |
<para>The verbosity level and <filename class="devicefile">stderr</filename> can be |
controlled through configuration files, see <citerefentry> |
<refentrytitle>vcs.conf</refentrytitle> |
<manvolnum>5</manvolnum> |
</citerefentry>. |
</para> |
<para><command>&dhpackage;</command> provides some return codes, they follow |
the semi-standardised values defined in <filename>sysexits.h</filename>:</para> |
<segmentedlist> |
<segtitle>Code</segtitle> |
<segtitle>Diagnostic</segtitle> |
<seglistitem> |
<seg><errorcode>0</errorcode> (<errorcode>EX_OK</errorcode>)</seg> |
<seg>Program exited successfully.</seg> |
</seglistitem> |
<seglistitem> |
<seg><errorcode>64</errorcode> (<errorcode>EX_USAGE</errorcode>)</seg> |
<seg>Error in the arguments.</seg> |
</seglistitem> |
<seglistitem> |
<seg><errorcode>66</errorcode> (<errorcode>EX_NOINPUT</errorcode>)</seg> |
<seg>Can't access some input file or it has an incorrect format.</seg> |
</seglistitem> |
<seglistitem> |
<seg><errorcode>69</errorcode> (<errorcode>EX_UNAVAILABLE</errorcode>)</seg> |
<seg>Unsatisfied dependency.</seg> |
</seglistitem> |
<seglistitem> |
<seg><errorcode>70</errorcode> (<errorcode>EX_SOFTWARE</errorcode>)</seg> |
<seg>Internal inconsistency (bug).</seg> |
</seglistitem> |
<seglistitem> |
<seg><errorcode>73</errorcode> (<errorcode>EX_CANTCREAT</errorcode>)</seg> |
<seg>Error creating temporary or output files.</seg> |
</seglistitem> |
</segmentedlist> |
</refsect1> |
<refsect1 id="bugs"> |
<!-- Or use this section to tell about upstream BTS. --> |
<title>BUGS</title> |
<para>Recent versions of <package>ImageMagick</package>, <command>mplayer</command> and |
<command>ffmpeg</command> should be used |
for maximum compatibility.</para> |
<para>The upstream's <acronym>BTS</acronym> can be found |
at <ulink url="http://b.outlyer.net"/>.</para> |
</refsect1> |
<refsect1 id="see_also"> |
<title>SEE ALSO</title> |
<!-- In alpabetical order. --> |
<para><citerefentry> |
<refentrytitle>vcs.conf</refentrytitle> |
<manvolnum>5</manvolnum> |
</citerefentry>, <citerefentry> |
<refentrytitle>convert</refentrytitle> |
<manvolnum>1</manvolnum> |
</citerefentry>, <citerefentry> |
<refentrytitle>ffmpeg</refentrytitle> |
<manvolnum>1</manvolnum> |
</citerefentry>, <citerefentry> |
<refentrytitle>mplayer</refentrytitle> |
<manvolnum>1</manvolnum> |
</citerefentry></para> |
</refsect1> |
</refentry> |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.2/vcs |
---|
0,0 → 1,0 |
link pkg/vcs |
Property changes: |
Added: svn:special |
+* |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.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/tags/1.12.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/tags/1.12:r413 |
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/branches/1.12:r409-411 |
Merged /video-contact-sheet/branches/1.11.2:r393-406 |
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.12.1:r416-419 |
Merged /video-contact-sheet/branches/1.12.2:r422-431 |
Merged /video-contact-sheet/tags/0.99a:r261 |
Merged /video-contact-sheet/branches/1.0.3b:r276-277 |
Merged /video-contact-sheet/branches/1.0.2b:r270-271 |
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.12.1/pkg/debian/changelog |
---|
0,0 → 1,69 |
vcs (1.12.1-upstream.1) experimental; urgency=medium |
* New version. |
-- Toni Corvera <outlyer@gmail.com> Fri, 23 Apr 2010 13:56:58 +0200 |
vcs (1.12-upstream.1) experimental; urgency=low |
* New version. |
* debian/docs: Install vcs.conf.example |
-- Toni Corvera <outlyer@gmail.com> Sat, 10 Apr 2010 00:57:17 +0200 |
vcs (1.11.2-upstream.1) experimental; urgency=low |
* New version. |
-- Toni Corvera <outlyer@gmail.com> Fri, 19 Mar 2010 00:18:51 +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/tags/1.12.1/pkg/debian/docs |
---|
0,0 → 1,0 |
examples/vcs.conf.example |
/video-contact-sheet/tags/1.12.1/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 |
DESTDIR:=$(CURDIR)/debian/vcs |
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) all prepackage |
#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=$(DESTDIR) 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/tags/1.12.1/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/tags/1.12.1/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/tags/1.12.1/pkg/debian/dirs |
---|
0,0 → 1,0 |
usr/bin |
/video-contact-sheet/tags/1.12.1/pkg/debian/compat |
---|
0,0 → 1,0 |
5 |
/video-contact-sheet/tags/1.12.1/pkg/CHANGELOG |
---|
0,0 → 1,395 |
1.12.1: |
* BUGFIXES: |
- Workaround for cases in which GAWK uses comma as decimal separator. |
Any OS with GAWK 3.1.3 to 3.1.5 was affected (e.g. Debian Lenny) |
- Don't try to go on in DVD mode with unreadable ISOs |
1.12: (2010-04-10) |
* New features/tweaks: |
- Loading of random configuration files (--config / -C) |
- Profiles: Similar to above but simpler syntax (--profile / -p) |
- Config/Profile generation from command-line (--generate) |
- Adapt heading, title and footer height to font size (fonts that used |
to get cropped should now be fine) |
* DVD mode cleanup: |
- Command-line switched to match "normal" files: |
Before: |
$ vcs --dvd /dev/dvd 0 or $ vcs -dvd /dev/dvd 1 |
Equivalents now: |
$ vcs --dvd /dev/dvd or $ vcs --dvd --dvd-title 1 /dev/dvd |
* New end-offset behaviour: |
- A 5.5% end offset is applied by default |
- Can be disabled with -E0 or end_offset=0 |
- MIN_LENGTH_FOR_END_OFFSET is no longer used |
* Configuration files cleanup: |
- Simplified or more meaningful names where appropriate (the older |
names will continue to work for a while, and users will be warned) |
"vcs --generate" with no other arguments can be used to translate them |
- Validation of configuration options. |
Incorrect values will be discarded and an error shown; processing will |
continue. |
- Configuration searched in ~/.vcs/vcs.conf too |
- Syntax enhancements: |
> Comments can now be included in-line |
> Putting '#' in a value now requires using the "escaped form" '$#' |
> Semicolons (;) also serve to start comments: When one is found the |
rest of the line is ignored, they continue to be disallowed in values |
i.e. 'tl;dr' will be parsed as 'tl' |
* Other: |
- Accept timecodes and percentages in end_offset, both from the |
command-line and in configuration files |
- Print the start and end timestamps in effect before capturing |
- No longer accept interval zero (used to be re-set to default) |
- Tighter printing of overrides and no longer printed as warning |
- Strickter handing of wrong options |
- Fall back to Helvetica also when no fonts dir is located. Look |
in /usr/local too. |
- --end-offset added as an alias to --end_offset |
- Starting with 1.12 a tarball + makefile is also provided |
* BUGFIXES: |
- Avoid possible (unlikely) usage of scientific notation in internal |
calculations |
- Distinguish between default end offset and user's end offset with the |
same value |
- Handle --nonlatin correctly |
- DVD Mode + FFmpeg identification: Check VOB #0 instead of #1 |
- Don't print escape codes to stdout when testing colour printing |
* Options removed: |
--shoehorn, temporary replacement: --undocumented shoehorn. Will be gone |
in 1.13 |
--mincho, replaced by --nonlatin since 1.11 |
MIN_LENGTH_FOR_END_OFFSET, as explained above, no longer needed |
* INTERNAL: |
- $CFGFILE replaced by ~/.vcs.conf |
- Use -p for profiles instead of -P (used, undocumented, in 1.11) |
1.11.2: (2010-03-19) |
* Added Cook and Sipro (RealAudio 4, 5 & 6) codecs |
* BUGFIXES: |
- Remove extra, empty, temporary dir |
- Use standard awk syntax for exponentiation (pyth_th) |
- Workaround for systems that don't register fonts with ImageMagick |
* DEBUG: Print to stderr when probbing with mplayer too |
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 |
# vim:set ts=3 sw=3 et textwidth=80: # |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.1/pkg/vcs |
---|
0,0 → 1,4544 |
#!/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>) |
# |
# The full changelog can be found at <http://p.outlyer.net/vcs/files/CHANGELOG> |
declare -r VERSION="1.12.1" |
declare -r RELEASE=1 |
set -e |
# GAWK 3.1.3 to 3.1.5 print decimals (with printf) according to locale (i.e. |
#+decimal comma separator in some locales, which is apparently POSIX correct). |
#+Older and newer versions, though, need either POSIXLY_CORRECT=1, --posix or |
#+ --use-lc-numeric to honour locale. |
# MAWK appears to always use dots. |
# Info: <http://www.gnu.org/manual/gawk/html_node/Conversion.html> |
#export POSIXLY_CORRECT=1 # Immitate behaviour in newer gawk |
export LC_NUMERIC=C |
# 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 |
# * (1.12 or 1.13) 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 |
# --undocumented shoehorn -> NONE 1.12 1.13 or 1.14 |
# --funky -> --profile ? ?+1 |
# --end_offset -> --end-offset 1.12 (silent), 1.13 (warn) 1.14 |
# > Usage of ./vcs.conf is also deprecated since it doesn't mesh well with profiles |
# (and it was a bad placeholder for them). Will remain to be loadable with |
# -C:pwd. |
# Loaded by default in 1.12 and maybe 1.13. |
# Not loaded from 1.14 onwards (maybe 1.13), a warning may be shown if the file |
# exists |
# * 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 |
# * Optimisations: |
# - Reduce the number of forks |
# }}} # 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 |
# * ~/.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. |
# 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 |
declare -ri FF_DEFAULT=5 FF_NONLATIN=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 |
# Used for feedback |
declare -r NL=$'\012' # Newline |
declare -r TAB=$'\011' # Tab |
# }}} # 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 -r DEFAULT_END_OFFSET="5.5%" |
# 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 |
# Override-able since 1.11: |
# Height of the thumbnails, by default use same as input |
declare th_height='100%' |
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 amount of time is *not* captured from the end of the video |
declare 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 |
# Stores the names of variables overridden from the command-line, |
#+see cmdline_override() and "--override" |
declare CMDLINE_OVERRIDES="" |
# Implicit error handling (see die()), obviously inspired by C's errno |
# and PHP's die(). Functions adapted to use them allow uses like: |
# some_function arg || die |
# which will exit with the appropriate exit code and print the error message |
# (Introduced in 1.12, still being retrofitted) |
declare -i ERROR_CODE=0 # Exit code associated with the last error |
declare ERROR_MSG= # Error message associated to the last error |
# Used to buffer feedback (see buffered()) |
declare BUFFER= |
# This is only used to exit when -DD is used |
declare -i DEBUGGED=0 # It will be 1 after using -DD |
# See post_getopt_hooks() |
# Format: Priority:Command[:Arguments] (lower priority run sooner) |
declare -a POST_GETOPT_HOOKS=( ) |
declare -i DVD_MODE=0 DVD_TITLE= |
declare -a DVD_TITLES=( ) # Titles for each input DVD, filled by --dvd-title |
declare DVD_MOUNTP= # Mountpoint for DVD, detected & reset for each DVD |
declare DVD_VTS= # VTS, detected & reset for each DVD |
# }}} # Variables |
# {{{ # Configuration handling |
# New override system: This variable maps configuration variables to actual |
#+variables used in the script. Each item in the array follows the syntax: |
# <cfg variable>:<variable>:<flags>:[type constraints] Where: |
#+ cfg variable: is the name of the configuration file variable |
#+ variable: is the name of the actual variable. If empty or '=', it will be |
#+ the same as cfg variable. |
#+ flags can currently be: |
#+ "deprecated=new name": Will print a deprecation warning and suggest to use |
#+ "new name" instead |
#+ "striked": Variable is marked for removal, will print a warning about it |
#+ directing anyone needing it to contact me. Only used for variables |
#+ believed to be no longer needed |
#+ "gone": Variable removed in the current version |
#+ "alias": Marks an alias, duplicate name intended to stay |
#+ "=": ignore |
#+ type constraints: a character indicating accepted values: |
# n -> Number (Natural, positive Integer) |
# p -> Number, not zero |
# t -> Timestamp |
# b -> Bool |
# h -> Positive, non-zero, number or percentage |
# f -> Float or fraction |
# D -> only $DEC_* constants |
# T -> only $TC_* constants |
# V -> only $V_* constants |
# I -> interval or percentage |
# Note during the switch to the new system most variables will remain unchanged |
# Also, the new system is case insensitive to variable names |
# TODO: Allow 'y', 'n' in booleans |
# TODO: Remove extra coherence_check()'s once constraints are implemented |
declare -ra OVERRIDE_MAP=( |
"user:::" |
"extended_factor:=:=:f" |
"stdout::" |
"stderr::" |
"DEBUG:=:=:b" |
"interval:=:=:t" |
"numcaps:=:=:p" |
"captures:numcaps:alias:n" # Alias |
"GETOPT::" # Note it makes no sense as command-line override |
"columns:cols:=:p" |
"cols:=:alias:p" # Alias |
"DISABLE_SHADOWS:=:=:b" |
"DISABLE_TIMESTAMPS:=:=:b" |
"bg_heading::" |
"bg_sign::" |
"bg_title::" |
"bg_contact::" |
"bg_tstamps::" |
"fg_heading::" |
"fg_sign::" |
"fg_tstamps::" |
"fg_title::" |
"font_heading::" |
"font_sign::" |
"font_tstamps::" |
"font_title::" |
"pts_tstamps::" |
"pts_meta::" |
"pts_sign::" |
"pts_title::" |
# Aliases for cosmetic stuff |
"bg_header:bg_heading:alias" |
"bg_signature:bg_sign:alias" |
"bg_footer:bg_sign:alias" |
"bg_sheet:bg_contact:alias" |
"fg_header:fg_heading:alias" |
"fg_signature:fg_sign:alias" |
"fg_footer:fg_sign:alias" |
"font_header:font_heading:alias" |
"font_meta:font_heading:alias" |
"font_signature:font_sign:alias" |
"font_footer:font_sign:alias" |
"pts_heading:pts_meta:alias" |
"pts_header:pts_meta:alias" |
"pts_signature:pts_sign:alias" |
"pts_footer:pts_sign:alias" |
"signature:user_signature:" |
"user_signature::deprecated=signature" |
"quality:output_quality:=:n" |
"output_quality::deprecated=quality:n" |
# TODO: These variables are evaluated to constants, would be better to |
# use some symbolic system (e.g. decoder=f instead of decoder=$DEC_FFMPEG) |
"decoder:=:=:D" |
#"capture_mode:timecode_from:alias:T" |
"timecode_from:=:=:T" |
"verbosity:=:=:V" |
"format:output_format:" |
"output_format:=:deprecated=format" |
"simple_feedback:plain_messages:=:b" |
"plain_messages::deprecated=simple_feedback:b" |
"height:th_height:=:h" |
"th_height::deprecated=height:h" |
"padding:HPAD:=:n" |
"HPAD:=:deprecated=padding:n" |
"nonlatin_font:FONT_MINCHO:" |
"FONT_MINCHO::deprecated=nonlatin_font" |
"end_offset:=:=:I" # New, used to have a two-variables assignment before USR_* |
"DEFAULT_END_OFFSET:end_offset:deprecated=end_offset:I" |
# TODO TBA: |
#"use_nonlatinfont::" |
#"noboldfeedback::" # Colour but not bold |
"shoehorned::striked" |
"safe_rename_pattern::striked" |
"MIN_LENGTH_FOR_END_OFFSET::gone:" |
) |
# Load a configuration file |
# File *MUST* exist |
# Configuration files are a series of variable=value assignment; they'll be |
#+evaluated directly so they can refer to other variables (with their value at |
#+the point of the assignment). |
# Quotes shouldn't be used (they'll be kept) |
# Since 1.12 comments can be placed in-line (i.e. after an assignment), |
# Literal '#' can be written as '$#' |
# ';' can be used to mark an end of line, anything after it will be ignored |
#+(making it equivalent to '#'), there's no way to include a literal ';' |
# load_config_file($1 = file, [$2 = type (description) = 'Settings']) |
load_config_file() { |
local cfgfile=$1 |
local desc=$2 |
[ "$desc" ] || desc='Settings' |
local por= # Parsed override |
local varname= tmp= flag= bashcode= feedback= ov= |
while read line ; do # auto variable $line |
parse_override "$line" # Feeding it comments should be harmless |
por=$RESULT |
if [ "$por" ]; then |
varname=${por/% *} # Everything up to the first space... |
tmp=${por#* } # Rest of string |
flag=${tmp/% *} |
bashcode=${tmp#* } |
if [ "$flag" == '=' ]; then |
# No need to override... |
feedback="$varname(=)" |
else |
feedback=$varname |
eval "$bashcode" |
fi |
ov="$ov, $feedback" |
fi |
done <$cfgfile |
[ -z "$ov" ] || inf "$desc from $cfgfile:$NL ${ov:2}" |
# No loaded overrides but errors/warnings to print, do print the file name |
if [ -z "$ov" -a "$BUFFER" ]; then |
inf "In $cfgfile:" |
fi |
flush_buffered ' ' |
} |
# Loads the configuration files if present |
# load_config() |
load_config() { |
local -a CONFIGS=( /etc/vcs.conf ~/.vcs.conf ~/.vcs/vcs.conf ./vcs.conf ) |
for cfgfile in "${CONFIGS[@]}" ;do |
[ -f "$cfgfile" ] || continue |
load_config_file "$cfgfile" |
done |
} |
# Load a profile, if found; fail otherwise |
# Profiles are just configuration files that can be loaded on demand (whereas |
#+config files are always loaded) and be given a name. |
# See load_config_file() for comments on the syntax |
# Locations to be searched, in order: |
#+ 1) ~/.vcs/profiles/NAME.conf |
#+ 2) /usr/local/share/vcs/profiles/NAME.conf |
#+ 3) /usr/share/vcs/profiles/NAME.conf |
#+i.e. files in ~/.vcs/ will prevent loading files named like them in /usr |
# load_profile($1 = profile name) |
load_profile() { |
local p=$1 prof= |
local -a PATHS=( ~/.vcs/profiles/ /usr/local/share/vcs/profiles/ /usr/share/vcs/profiles/ ) |
if [ ${p:0:1} == ':' ]; then |
case $p in |
:list) |
# No need to be efficient here... |
echo "Profiles located:" |
local path= profname= |
# 1) Find all profiles |
# 2) (sed) Extract profile file name |
# 3 & 4) (sort+uniq) Keep only first hits for each name (most precedence) |
# 5) (while) Process each name |
# 6) (for) Re-locate most precedent profile |
# 7) (echo x3) Print <name>[: description] |
# 8) (sed) Indent with ' * ' |
find "${PATHS[@]}" -name '*.conf' 2>/dev/null \ |
| sed -e 's#.*/\(.*\)\.conf#\1#' \ |
| sort | uniq \ |
| while read profname ; do |
for path in "${PATHS[@]}" ; do |
path=$path$profname.conf |
[ -f "$path" ] || continue |
echo -n "$profname" |
# [ ] here contains <space><tab>. Mawk doesn't understand |
# [[:space:]] |
echo -n $(awk 'sub(/#[ ]*vcs:desc:[ ]*/, ": ")' "$path") |
echo |
break |
done |
done \ |
| sed 's/^/ * /' |
exit 0 |
;; |
*) |
ERROR_MSG="Profiles starting with ':' are reserved.$NL"\ |
" Use ':list' to list available profiles." |
ERROR_CODE=$EX_USAGE |
return $ERROR_CODE |
esac |
fi |
for prof in "${PATHS[@]}" ; do |
prof="$prof$p.conf" |
[ -f "$prof" ] || continue |
load_config_file "$prof" 'Profile' |
return 0 |
done |
ERROR_MSG="Profile '$p' not found" |
ERROR_CODE=$EX_USAGE |
return $ERROR_CODE |
} |
# Check value for an overrideable variable against the allowed values |
# check_constraint($1 = variable name, $2 = value [, $3 = public_name]) |
# where public_name is the name to be used for error messages |
check_constraint() { |
local n=$1 v=$2 p=$3 |
# Get constraint |
local map=$(echo "${OVERRIDE_MAP[*]}" | stonl | egrep -i "^$n:") |
[ "$map" ] || return 0 |
local ct=$(cut -d':' -f4 <<<"$map") |
[ "$ct" ] || return 0 |
local checkfn= domain= |
case $ct in |
n) checkfn=is_number ; domain=numbers ;; |
p) checkfn=is_positive ; domain='numbers greater than zero' ;; |
t) checkfn=is_interval ; domain=intervals ;; |
b) checkfn=is_bool ; domain='boolean values (0 or 1)' ;; |
h) checkfn=is_pos_or_percent ; domain='positive numbers or percentages' ;; |
f) checkfn=is_float_or_frac ; domain='positive numbers or fractions' ;; |
D) checkfn=is_decoder ; domain='$DEC_FFMPEG or $DEC_MPLAYER' ;; |
T) checkfn=is_tcfrom ; domain='$TC_INTERVAL or $TC_INTERVAL' ;; |
V) checkfn=is_vlevel ; domain='verbosity levels ($V_.*)' ;; |
I) checkfn=is_interv_or_percent ; domain='intervals or percentages' ;; |
esac |
if [ "$checkfn" ] && ! $checkfn "$v" ; then |
[ "$p" ] || p=$n |
ERROR_MSG="Illegal value for '$p', only $domain are accepted" |
ERROR_CODE=$EX_USAGE |
return $ERROR_CODE |
fi |
return 0 |
} |
# Parse an override |
# Input should be a var=value assignment, result, stored in the global variable $RESULT, |
# will be in the format: |
# <variable name> <flag> <evaluable code> where |
# * variable name: is the name of the variable to be overridden |
# * flag: is a character indicating the status: "+" for a possible override, |
# "=" for an override that already has the same value |
# * evaluable code: is a piece of bash code to be feed to eval to change |
# the variable, it also sets the related USR_* variable |
# Warnings and errors are buffered |
# This function always returns true |
# parse_override($1 = override assignment) |
parse_override() { |
local o="$1" |
RESULT='' |
if ! egrep -q '^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*=.*' <<<"$o" ; then |
return |
fi |
local varname=$(echo "${o/=*}" | sed 's/[[:space:]]//g') # Trim var name |
local lcvarname=$(echo "$varname" | tr '[A-Z]' '[a-z]') |
local mapping=$(echo "${OVERRIDE_MAP[*]}" | stonl | egrep -i "^$lcvarname:") |
[ "$mapping" ] || return 0 |
local varval=${o#*=} # No trimming here (yet) |
# 1) Trim from ; (if present) to finish |
# 2) Trim from # (comments) not "escaped" like '$#' |
# 3) Replace '$#' with '#' |
# 4) Trim whitespace on both ends |
varval=$(sed -e 's/;.*//' -e 's/\([^$]\)#.*/\1/g' -e 's/\$#/#/g' \ |
-e 's/^[[:space:]]*//;s/[[:space:]]*$//' <<<"$varval") |
# Is varval empty? |
[ "$varval" ] || return 0 |
local mvar=$(cut -d':' -f1 <<<"$mapping") |
local ivar=$(cut -d':' -f2 <<<"$mapping") |
local flags=$(cut -d':' -f3 <<<"$mapping") |
local constraints=$(cut -d':' -f4 <<<"$mapping") |
{ [ "$ivar" ] && [ "$ivar" != "=" ] ; } || ivar="$mvar" |
# Note using "\$(echo $varval)" would allow a more flexible syntax but |
#+enforce special handling of escaping, which with the currently available |
#+settings is not worth the effort |
# Resolve symbolic variables to check their actual value |
eval varval="\"$varval\"" 2>/dev/null || { # Hide eval's errors |
buffered error "Syntax error: '$o'" |
return 0 |
} |
[ "$varval" ] || return 0 # If empty value, ignore it |
local evcode='' |
if [ "$flags" ] && [ $flags != "=" ] && [ $flags != 'alias' ]; then |
if echo "$flags" | grep -q '^deprecated=' ; then |
local new=$(echo "$flags" | sed 's/^deprecated=//') |
buffered warn "Variable '$varname' will be removed in the future,$NL please use '$new' instead." |
else |
case "$flags" in |
gone) |
buffered error "Variable '$varname' has been removed." |
return 0 |
;; |
striked) |
buffered error "Variable '$varname' is scheduled to be removed in the next release." |
buffered error " Please contact the author if you absolutely need it." |
;; |
*) return 0 ;; |
esac |
fi |
fi |
[ -z "$constraints" ] || check_constraint $ivar "$varval" $varname || { |
buffered error "$ERROR_MSG" |
return 0 |
} |
eval local curvarval='$'"$ivar" retflag='+' |
if [ "$curvarval" == "$varval" ]; then |
retflag='=' |
else |
if [ "$constraints" == "t" ]; then |
varval=$(get_interval "$varval") |
fi |
# Escape single quotes, since it will be single-quoted: |
varval=${varval//\'/\'\\\'\'} # <<'>> => <<'\''>> |
evcode="$ivar='$varval'; USR_$ivar='$varval'" |
fi |
# varname, as found in the config file |
RESULT="$varname $retflag $evcode" |
} |
# Do an override from the command line |
# cmdline_override($1 = override assignment) |
#+e.g. cmdline_override 'decoder=$DEC_FFMPEG' |
cmdline_override() { |
trace $FUNCNAME $@ |
parse_override "$1" |
local r=$RESULT |
[ "$r" ] || return 0 |
local varname=${r/% *} # See load_config() |
local tmp=${r#* } |
local flag=${tmp/% *} |
local bashcode=${tmp#* } |
if [ "$flag" == '=' ]; then |
varname="$varname(=)" |
else |
eval "$bashcode" |
fi |
CMDLINE_OVERRIDES="$CMDLINE_OVERRIDES, $varname" |
} |
# Call any pending commands required by the command-line arguments |
# This is used to defer some calls and to flush buffers |
post_getopt_hooks() { |
local cback= EX=0 |
local funcs=$(echo "${POST_GETOPT_HOOKS[*]}" | stonl | sort -n | uniq |\ |
cut -d':' -f2- ) |
for cback in $funcs ; do |
local fn=${cback/:*} |
local arg=${cback/*:} |
[ "$arg" != "$cback" ] || arg='' |
$fn $arg |
done |
} |
# Print the list of command-line overrides |
cmdline_overrides_flush() { |
trace $FUNCNAME $@ |
if [ "$CMDLINE_OVERRIDES" ]; then |
inf "Overridden settings from command line:$NL ${CMDLINE_OVERRIDES:2}" |
fi |
if [ "$BUFFER" ]; then |
[ "$CMDLINE_OVERRIDES" ] || warn "In command-line overrides:" |
flush_buffered ' ' |
fi |
} |
# }}} # Configuration handling |
# {{{ # Convenience functions |
#### {{{{ # Type checkers: Return true if input is of a certain type |
#### All take exactly one argument and print nothing |
## Natural number |
is_number() { |
#egrep -q '^[0-9]+$' <<<"$1" ; |
# From [[abs]], test if '[ ]' can parse input as numbers |
# Returns 2 for failed test, expected to return 1 |
[ "$1" -ne 0 -o "$1" -eq 0 ] 2>/dev/null || return 1 |
} |
## Number > 0 |
is_positive() { is_number "$1" && [ $1 -gt 0 ]; } |
## Bool (0 or 1) |
is_bool() { [ "$1" == "0" -o "$1" == "1" ] 2>/dev/null ; } |
## Float (XX.YY; XX.; ;.YY) (.24=0.24) |
is_float() { egrep -q '^([0-9]+\.?([0-9])?+|(\.[0-9]+))$'<<<"$1" ; } |
## Percentage (xx% or xx.yy%) |
is_percentage() { |
egrep -q '^([0-9]+\.?([0-9])?+|(\.[0-9]+))%$'<<<"$1" |
} |
## Interval |
is_interval() { |
local i=$(get_interval "$1" || true) |
[ "$i" ] && fptest $i -gt 0 |
} |
## Interval or percentage |
is_interv_or_percent() { |
is_percentage "$1" || is_interval "$1" |
} |
## Positive or percentage |
is_pos_or_percent() { |
is_number "$1" && [ "$1" -gt 0 ] || is_percentage "$1" |
} |
## Float (>=0) or fraction |
is_float_or_frac() { |
{ is_fraction "$1" || is_float "$1" ; } && fptest "$1" -ge 0 |
} |
## Fraction, strictly (X/Y, but no X; Y!=0) |
is_fraction() { |
egrep -q '^[0-9]+/[0-9]+$'<<<"$1" && { |
local d=$(echo "$1" | cut -d'/' -f2) |
[ "$d" -ne 0 ] |
} |
} |
## Decoder ($DEC_* constants) |
is_decoder() { [ "$1" == $DEC_FFMPEG -o "$1" == $DEC_MPLAYER ]; } |
## Time calculation source ($TC_* constants) |
is_tcfrom() { [ "$1" == $TC_INTERVAL -o "$1" == $TC_NUMCAPS ]; } |
### Verbosity level ($V_* constants) |
is_vlevel() { |
is_number "$1" && \ |
[ $1 -eq $V_ALL -o $1 -eq $V_NONE -o $1 -eq $V_ERROR -o \ |
$1 -eq $V_WARN -o $1 -eq $V_INFO ] |
} |
#### }}}} # End of type checkers |
# 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 old code (default bc -l) |
# TODO: gawk and mawk differ in how to handle stuff like div by zero: |
# gawk errors, mawk prints inf. Should somehow handle inf and nan |
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() { |
if [ "$1" ]; then |
awk '{gsub(" ", "\n");print}' <<<"$1" | egrep -v '^$' |
else |
awk '{gsub(" ", "\n");print}' | egrep -v '^$' |
fi |
} |
# Converts newlines to spaces portably |
# nltos([$1 = string]) |
nltos() { |
if [ "$1" ]; then |
awk '{printf "%s ",$0}' <<<"$1" | sed 's/ *//' |
else |
awk '{printf "%s ",$0}' | sed 's/ *//' |
fi |
} |
# 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() { |
awkexf "sqrt($1 ^ 2 + $2 ^ 2)" |
} |
# Get a percentage |
# percent($1 = value, $2 = percentage) |
percent() { |
local pc=${2/%%/} # BASH %% == RE %$ |
awkexf "($1 * $pc) / 100" |
} |
# Rounded percentage |
# rpercent($1 = value, $2 = percentage) |
rpercent() { |
local pc=${2/%%/} |
awkex "int( ($1 * $pc) / 100 + 0.5 )" |
} |
# 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 awkexf "\"$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=$(awkexf "$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" |
} |
# Get the line height used for a certain font and size |
# line_height($1 = font, $2 = size) |
line_height() { |
# Create a small image to see how tall are characters. In my tests, no |
#+matter which character is used it's always the same height. |
convert -font "$1" -pointsize "$2" \ |
label:'F' png:- | identify -format '%h' - |
} |
# 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 soon 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($1 = image file) |
get_dvd_image_mountpoint() { |
if is_linux ; then |
local lodev=$(/sbin/losetup -j "$1" | 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 |
} |
# |
# Buffering of feedback, usage: |
# buffered warn "my warning" |
# ... |
# flush_buffered |
# buffered($1 = feedback function, $2 = arguments) |
buffered() { |
local grab=$( $1 "$2" 2>&1 ) |
# BUFFER=( "${BUFFER[@]}" -- "$grab" ) |
BUFFER=$BUFFER$grab$NL |
} |
# |
# Print buffered feedback to stderr |
# flush_buffered([$1 = indentation]) |
flush_buffered() { |
[ "${BUFFER[*]}" ] || return 0 |
echo "$BUFFER" | sed -e '$d' -e "s/^/$1/g" >&2 # sed: delete last line, indent with $1 |
BUFFER='' |
} |
# |
# trace($1 = function name = $FUNCNAME, function arguments...) |
trace() { |
if [ "$DEBUG" -ne "1" ]; then return; fi |
echo "[TRACE]: $@" >&2 |
} |
# Print an error message and exit |
# die([$1 = message [, $2 = exit_code]]) |
# If no message is provided, use $ERROR_MSG |
# If no exit_code is provided, use $ERROR_CODE |
die() { |
local m=$1 ec=$2 |
[ "$ec" ] || ec=$ERROR_CODE |
[ "$ec" ] || ec=1 |
[ "$m" ] || m=$ERROR_MSG |
error "$m" |
exit $ec |
} |
# |
# 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 && 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 >/dev/null |
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 |
local eff_eo= # Effective end_offset (for percentages) |
# 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 is_percentage $end_offset ; then |
eff_eo=$(percent $end $end_offset) |
else |
eff_eo=$(get_interval "$end_offset") |
fi |
if fptest $totime -le 0 ; then # If no totime is set, use end_offset |
eo=$eff_eo |
local runlen=$(awkexf "$end - $st") |
if fptest "($end-$eo-$st)" -le 0 ; then |
if fptest "$eo" -gt 0 && [ -z "$USR_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") |
local last= |
while fptest $stamp -le "$bound"; do |
assert $LINENO fptest $stamp -ge 0 |
LTC=( "${LTC[@]}" "$stamp" ) |
last=$stamp |
stamp=$(keepdecimals_lower $(awkexf "$stamp + $inc") 3) |
done |
inf "Capturing in range [$(pretty_stamp $inc)-$(pretty_stamp $last)]. Total length: $(pretty_stamp ${VID[$LEN]})" |
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() { |
trace $FUNCNAME $@ |
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() { |
trace $FUNCNAME $@ |
# Note mplayer CAN'T set the output filename, newer mplayer can set output |
#+dir though. |
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 "$f" \ |
$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 $(awkexf "$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" ;; |
# 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" ;; |
# Other versions of R.A. listed at Wikipedia/RealAudio |
sipr) acodec="RealAudio SIPR" ;; # RA 4/5 |
cook) acodec="RealAudio Cook" ;; # RA 6 |
*) # 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>"$stderr" | grep ^ID) |
else |
MPLAYER_CACHE=$("$MPLAYER" -benchmark -ao null -vo null -identify -frames 0 \ |
-quiet -dvd-device "$f" dvd://$DVD_TITLE \ |
2>"$stderr" | 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 |
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 ] && { |
local vfile="$DVD_MOUNTP/VIDEO_TS/VTS_${DVD_VTS}_0.VOB" |
if [ ! -r "$vfile" ]; then |
error "Failed to locate mounted DVD. Detection will be less accurate." |
return 0 # We can continue anyway |
fi |
f="$vfile" |
} |
# XXX: FFmpeg detects mpeg1video in DVDs?? |
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 -e 's/:/h/' -e '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 0 ] || 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 $(awkexf "($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") |
if [ -z "$ffl" -a $DVD_MODE -eq 1 ]; then |
ffl="(unavailable in DVD mode)" |
fi |
[ "$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:$NL$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 |
# In case it's 0/0 or 0.0 since they aren't rejected |
if fptest "$extended_factor" -eq 0 ; then |
extended_factor=0 |
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 |
# Since 1.12 DVD mode can work with multiple inputs too |
# 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[@]}" ) |
# 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() { |
trace $FUNCNAME $@ |
# 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 |
local dvs='' |
if [ -d /usr/local/share/fonts ]; then |
dvs=$(find /usr/local/share/fonts/ -type f -iname 'dejavusans.ttf') |
fi |
if [ -z "$dvs" -a -d /usr/share/fonts ]; then |
dvs=$(find /usr/share/fonts/ -type f -iname 'dejavusans.ttf') |
fi |
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 Sanitation: |
font_heading: $font_heading |
font_title : $font_title |
font_tstamps: $font_tstamps |
font_sign : $font_sign |
EOFF |
} |
# 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 |
DVD_MOUNTP= DVD_TITLE= # Re-set for each file |
if [ $DVD_MODE -eq 1 ]; then |
local dvdn=$(realpathr "$f") |
# Is it an ISO? |
if [ -f "$dvdn" ]; then |
DVD_MOUNTP=$(get_dvd_image_mountpoint "$dvdn") |
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 |
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_MOUNTP=$(mount | grep -o "^$dvdn *on [^ ]*" | cut -d' ' -f3) |
dvdn="DVD $f" |
fi |
if [ ! -r "$f" ]; then |
error "Can't access DVD ($f)" |
return $EX_NOINPUT |
fi |
inf "Processing $dvdn..." |
unset dvdn |
DVD_TITLE=${DVD_TITLES[0]} |
DVD_TITLES=( "${DVD_TITLES[@]:1}" ) # shift array |
if [ -z "$DVD_TITLE" -o "$DVD_TITLE" == "0" ]; then |
local dt="$(lsdvd "$f" 2>/dev/null | grep 'Longest track:' | \ |
cut -d' ' -f3- | sed 's/^0*//')" |
if ! is_number "$dt" ; then |
error "Failed to autodetect longest DVD title for '$f'" |
exit $EX_INTERNAL |
fi |
DVD_TITLE=$dt |
unset dt |
fi |
DVD_VTS=$(lsdvd -t$DVD_TITLE -v "$f" 2>/dev/null | grep -o 'VTS: [0-9]*' | cut -d' ' -f2) |
inf "Using DVD Title #$DVD_TITLE (VTS: $DVD_VTS) for '$f'" |
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" && [ "$th_height" != '100%' ]; then |
vidcap_height=$(rpercent ${VID[$H]} ${th_height}) |
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=$(awkexf "${VID[$ASPECT]}") |
else |
aspect_ratio=$(awkexf "${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 $(awkex "int(${#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") headheight= |
local heading=$(new_temp_file .png) |
# Add the title if any |
if [ "$title" ]; then |
local tlheight=$(line_height "$font_title" "$pts_title") |
convert \ |
\( \ |
-size ${headwidth}x$tlheight "xc:$bg_title" \ |
-font "$font_title" -pointsize "$pts_title" \ |
-background "$bg_title" -fill "$fg_title" \ |
-gravity Center -annotate 0 "$title" \ |
\) \ |
-flatten \ |
"$output" -append "$output" |
unset tlheight |
fi |
local fn_font= # see $font_filename |
case $font_filename in |
$FF_DEFAULT) fn_font="$font_heading" ;; |
$FF_NONLATIN) fn_font="$FONT_MINCHO" ;; |
*) |
warn "\$font_filename was overridden with an incorrect value, using default." |
fn_font="$font_heading" |
;; |
esac |
# Create a small image to see how tall are characters. In my tests, no matter |
#+which character is used it's always the same height. |
local lineheight=$(line_height "$font_heading" "$pts_meta") |
# Since filename can be set in a different font check it too |
if [ "$fn_font" != "$font_heading" ]; then |
local fnlineheight=$lineheight |
fnlineheight=$(line_height "$fn_font" "$pts_meta") |
[ $fnlineheight -le $lineheight ] || lineheight=$fnlineheight |
unset fnlineheight |
fi |
headheight=$(( $lineheight * 3 )) |
# 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 "$f" 2>/dev/null | grep -o 'Disc Title: .*' | cut -d' ' -f3-) |
# There's no guarantee that titles are on separate VTS, I have no idea |
# how to compute the actual title size |
if [ "$DVD_MOUNTP" ]; then |
filename_label="Disc label" |
filename_value="$dvd_label" |
filesize_label="Titleset 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 "$f") $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 |
local signlh=$(line_height "$font_sign" "$pts_sign") |
local signheight=$(( 4 + ( $signlh * 2 ) )) |
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}x$signheight -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 signheight signlh |
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.35755975068581946630 #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 DVD Mode. |
In this mode the input <file>s must be the DVD |
device(s) or ISO(s). When in DVD mode all input files |
must be DVDs. |
Implies -A (auto aspect ratio) |
--dvd-title <arg> DVD title to use. Using 0 (the default) will use the |
longest title. |
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 |
-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. |
-p|--profile <arg> Load profile "arg" |
-C|--config <arg> Load configuration file "arg" |
--generate <config|profile> |
Generate configuration or profile from current settings |
-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 |
# ' # Syntax highlighting bait |
} |
# Print a configuration file generated from the currently active settings |
# generate_config($1 = <config|profile>) |
generate_config() { |
local n=$(echo $1 | tr '[a-z]' '[A-Z]') f= t= x= |
cat <<-EOM |
# --- $n STARTS HERE --- |
# This is a sample configuration file for VCS generated automatically |
# from the command-line with the "--generate $1" command-line option |
# Save it to ~/.vcs.conf or ~/.vcs/vcs.conf to make it the default |
# configuration. |
# OR |
# Save it to ~/.vcs/profiles/something.conf to create a profile named |
# "something". To use this profile run vcs with the "--profile something" |
# (or "-p something") option |
# OR |
# Save it to "something.conf" and load it with "--config something.conf" |
# (or "-C something.conf") |
EOM |
echo "${OVERRIDE_MAP[*]}" | stonl | egrep -v '(deprecated=|alias)' | cut -d':' -f1-2 |\ |
while read ovname ; do |
f=${ovname/:*} |
t=${ovname#*:} |
if [ -z "$t" ] || [ "$t" == "=" ]; then t=$f ; fi |
eval v=\$USR_$t |
[ -z "$v" ] || { |
# Symbolic values: |
case "$t" in |
timecode_from) |
x='$TC_NUMCAPS' |
[ $v -eq $TC_NUMCAPS ] || x='$TC_INTERVAL' |
v=$x |
;; |
decoder) |
x='$DEC_FFMPEG' |
[ $v -eq $DEC_FFMPEG ] || x='$DEC_MPLAYER' |
v=$x |
;; |
verbosity) |
case $v in |
$V_ALL) v='$V_ALL' ;; |
$V_NONE) v='$V_NONE' ;; |
$V_INFO) v='$V_INFO' ;; |
$V_WARN) v='$V_WARN' ;; |
$V_ERROR) v='$V_ERROR' ;; |
esac # verbosity |
;; |
esac |
echo "$f=$v" |
} |
done |
echo "# vcs:conf:$NL# Generated on $(date)$NL# --- $n ENDS HERE --- " |
exit 0 |
} |
# }}} # 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:VR:Z:o:p:C: \ |
--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:,end-offset:,disable:,dvd,dvd-title:,randomsource:,undocumented:,output:,"\ |
"fullhelp,profile:,"\ |
"jpeg2,nonlatin,generate:,config:" \ |
-- "$@") |
eval set -- "$TEMP" |
while true ; do |
case "$1" in |
-i|--interval) |
check_constraint 'interval' "$2" "$1" || die |
interval=$(get_interval $2) |
timecode_from=$TC_INTERVAL |
USR_interval=$interval |
USR_timecode_from=$TC_INTERVAL |
shift # Option arg |
;; |
-n|--numcaps) |
check_constraint 'numcaps' "$2" "$1" || die |
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|--end-offset) |
check_constraint 'end_offset' "$2" "$1" || die |
is_p='y' |
is_percentage "$2" || is_p='' |
if [ "$is_p" ]; then |
end_offset="$2" |
else |
end_offset=$(get_interval "$2") |
fi |
USR_end_offset="$end_offset" |
unset is_i |
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 ;; |
-F) decoder=$DEC_FFMPEG ; USR_decoder=$decoder ;; |
-M) decoder=$DEC_MPLAYER ; USR_decoder=$decoder ;; |
-H|--height) |
check_constraint 'height' "$2" "$1" || die |
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) |
check_constraint 'columns' "$2" "$1" || die |
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 |
if [ "$2" ]; then |
check_constraint 'extended_factor' "$2" "$1" || die |
extended_factor="$2" |
else |
extended_factor=$DEFAULT_EXT_FACTOR |
fi |
USR_extended_factor=$extended_factor |
shift |
;; |
# Unlike -I, --nonlatin does not accept a font name |
--nonlatin) |
if [ -z "$USR_FONT_MINCHO" ]; then |
font_filename=$FF_NONLATIN |
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_NONLATIN |
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 |
cmdline_override "$2" |
POST_GETOPT_HOOKS=( "${POST_GETOPT_HOOKS[@]}" 1:cmdline_overrides_flush ) |
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=$(awkexf "$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=$(awkexf "$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=$(awkexf "$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 |
;; |
*) load_profile "$2" || die |
;; |
esac |
shift |
;; |
-C|--config) |
if echo "$2" | grep -q '^:' ; then |
if [ $2 = ':pwd' ]; then |
cfg=./vcs.conf |
else |
error "Configuration names starting with ':' are reserved." |
exit $EX_USAGE |
fi |
else |
cfg=$2 |
fi |
[ -f "$cfg" ] || { |
error "Configuration file '$cfg' not found" |
exit $EX_USAGE |
} |
# ./vcs.conf doesn't need the vcs:conf: mark |
if [ $2 != ':pwd' ]; then |
head -5 "$cfg" | grep -q '#[[:space:]]*vcs:conf[[:space:]]*:' || \ |
tail -5 "$cfg" | grep -q '#[[:space:]]*vcs:conf[[:space:]]*:' || { |
error "No vcs:conf: mark found in '$cfg'" |
exit $EX_NOINPUT |
} |
fi |
load_config_file "$cfg" 'Custom configuration' |
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 |
;; |
--dvd-title) |
check_constraint 'dvd_title' "$2" "$1" || die |
DVD_TITLES=( "${DVD_TITLES[@]}" "$2" ) |
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 |
decoder=$DEC_MPLAYER |
aspect_ratio=-2 # Special value: Auto detect only if ffmpeg couldn't |
;; |
-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 |
USR_verbosity=$verbosity |
;; |
-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")" |
error "Shoehorning of options is scheduled to be removed in the next version." |
error " Please contact the author if you absolutely need it." |
;; |
debug) |
warn "[U] debug" |
DEBUG=1 |
;; |
# Dump user-set variables and exit [since 1.12] |
uservars) |
echo "${OVERRIDE_MAP[*]}" | stonl | egrep -v '(deprecated=|alias)' | cut -d':' -f1-2 |\ |
while read ovname ; do |
f=${ovname/:*} |
t=${ovname#*:} |
if [ "$t" -a "$t" != "=" ]; then f="$t" ; fi |
eval v=\$USR_$f |
[ -z "$v" ] || echo "$f=$v" |
done |
exit 0 |
;; |
*) false ;; |
esac |
shift |
;; |
--generate) |
case "$2" in |
profile|config) |
POST_GETOPT_HOOKS=( "${POST_GETOPT_HOOKS[@]}" \ |
10:generate_config:$2 ) |
;; |
*) |
error "Option --generate must be followed by profile or config" |
exit $EX_USAGE |
;; |
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 |
# Avoid coherence_check if there's no arguments and no cmdline post |
# processing |
[ "$1" -o "$POST_GETOPT_HOOKS" ] || { |
[ $verbosity -eq $V_NONE ] || show_help |
exit $EX_USAGE |
} |
# More than one argument... |
if [ "$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 $? |
} |
# Run after coherence check to clean recoverable incorrect values |
post_getopt_hooks |
# Remaining arguments |
if [ ! "$1" ]; then |
[ $verbosity -eq $V_NONE ] || show_help |
exit $EX_USAGE |
fi |
# TODO: |
# DVD mode + multiple titles is still tricky: |
# --dvd --dvd-title 1 --dvd-title 2 /dev/dvd /dev/dvd |
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 |
# * bash3: |& (inherited from csh?) pipes both stdout and stderr |
# * 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.12.1/pkg/arch/PKGBUILD.in |
---|
0,0 → 1,39 |
# |
# $Rev$ |
# |
# Maintainer (Upstream): Toni Corvera <outlyer@gmail.com> |
# |
# Build with '$ makepkg' on the same directory as this file |
# |
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=() |
options=('docs' 'zipman') |
source=($url/files/$pkgname-$pkgver.tar.gz) |
md5sums=(@MD5@) #generate with 'makepkg -g' |
sha1sums=(@SHA1@) |
# Debian & Arch didn't agree on this on my first try (???) |
sha256sums=(@SHA256@) |
build() { |
cd $srcdir/$pkgname-$pkgver |
make prepackage |
make install DESTDIR=${pkgdir} prefix=/usr |
install -D $srcdir/$pkgname-$pkgver/examples/vcs.conf.example \ |
${pkgdir}/usr/share/doc/$pkgname/vcs.conf.example |
} |
#man page (TODO) |
# install -D -m644 $pkgname.1 ${pkgdir}/usr/share/man1/$pkgname.1 || return 1 |
# vim:set filetype=sh ts=2 et: # |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.1/pkg/profiles/mosaic.conf |
---|
0,0 → 1,11 |
# vcs:conf: |
# vcs:desc: Tight, small, thumbnails |
# <http://p.outlyer.net/dox/vcs:example_configs> |
disable_timestamps=1 |
disable_shadows=1 |
height=160 |
numcaps=20 |
timecode_from=$TC_NUMCAPS |
padding=0 |
columns=4 |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.1/pkg/profiles/black.conf |
---|
0,0 → 1,8 |
# vcs:conf: |
# vcs:desc: White-on-Black |
bg_contact=Black |
bg_heading=$bg_contact |
bg_sign=$bg_contact |
fg_heading=White |
fg_sign=$fg_heading |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.1/pkg/profiles/white.conf |
---|
0,0 → 1,8 |
# vcs:conf: |
# vcs:desc: Black-on-White profile |
bg_contact=White |
bg_heading=$bg_contact |
bg_sign=$bg_contact |
fg_heading=Black |
fg_sign=$fg_heading |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.1/pkg/BSDmakefile |
---|
0,0 → 1,14 |
VERSION!=head -50 vcs | grep 'declare -r VERSION=' | perl -pe 's/.*"(.*)".*/\1/' |
PACKAGER!=finger -lp `echo $USER` 2>/dev/null | head -n1 | cut -d: -f3 |
.if empty($(PACKAGER)) |
PACKAGER!=grep ^`id -un` /etc/passwd | cut -d: -f5 | cut -d, -f1 |
.endif |
.if empty($(RM)) |
RM=rm -f |
.endif |
include common.mk |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.1/pkg/GNUmakefile |
---|
0,0 → 1,13 |
# |
# $Id$ |
# Makefile for GNU-make |
# |
VERSION:=$(shell head -50 vcs | grep 'declare -r VERSION=' | sed -e 's/.*"\(.*\)".*/\1/') |
PACKAGER:=$(shell finger -lp `echo $USER` 2>/dev/null | head -n1 | cut -d: -f3) |
ifeq ($(PACKAGER),) |
PACKAGER:=$(shell grep ^`id -un` /etc/passwd | cut -d: -f5 | cut -d, -f1) |
endif |
include common.mk |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.1/pkg/README |
---|
0,0 → 1,39 |
Index |
----- |
1. Files |
2. Installation |
3. Uninstallation |
Files |
----- |
In this package: |
vcs The VCS script |
profiles/ Example profiles: |
mosaic.conf 20 small thumbnails in a 5x4 grid, no padding |
black.conf Black background and white text |
white.conf White background and black text |
examples/vcs.conf Example configuration |
Use "make examples/vcs.conf.example" to create |
a version with all options commented out. |
Installation |
------------ |
$ make install |
Will install under /usr/local |
$ make install prefix=/usr |
Will install under /usr |
Uninstallation |
-------------- |
$ make uninstall |
If you used a prefix during install use it too during uninstall |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.1/pkg/rpm/vcs.spec.in |
---|
0,0 → 1,110 |
# |
# $Rev$ |
# |
# 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 |
make examples/vcs.conf.example |
%install |
make DESTDIR=%buildroot prefix=%{prefix} install |
%clean |
[ ${RPM_BUILD_ROOT} != "/" ] && rm -rf ${RPM_BUILD_ROOT} |
#%post |
# postinst |
#%postun |
# postuninst |
%files |
%defattr(-,root,root) |
# binary |
%{_bindir}/%{name} |
# Profiles |
%{prefix}/share/vcs/profiles/black.conf |
%{prefix}/share/vcs/profiles/mosaic.conf |
%{prefix}/share/vcs/profiles/white.conf |
# Manpage |
#%{_mandir}/man1/%{name}.1.gz |
%doc CHANGELOG |
# Config example |
%doc examples/vcs.conf.example |
%changelog |
* Sat Apr 10 2010 - outlyer (at) gmail (dot) com |
- Added profiles and example configuration |
- Use %{prefix} |
* 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.12.1/pkg/common.mk |
---|
0,0 → 1,66 |
# $Id$ |
# |
# To be included from GNUmakefile or BSDmakefile |
# To use it directly set VERSION and PACKAGER |
# e.g. make VERSION=1.x PACKAGER=Me <rule> |
# |
# Notes to self: |
# This file should follow only common/portable make syntax and commands |
# Common pitfalls: |
# - $(shell) -> GNU Make, equivalent BSD make: != |
# - install -D -> GNU only (-d is portable) |
# - $(RM) -> empty by default in BSD, set from BSDmakefile |
prefix:=/usr/local |
DESTDIR:=/ |
TGZ=vcs-$(VERSION).tar.gz |
all: |
# Nothing to be done |
dist: vcs.spec |
# Files installed in packages but not outside |
prepackage: examples/vcs.conf.example |
install: |
install -d $(DESTDIR)$(prefix)/bin/ |
install -m755 vcs $(DESTDIR)$(prefix)/bin/vcs |
install -d $(DESTDIR)$(prefix)/share/vcs/profiles |
install -m644 profiles/*.conf $(DESTDIR)$(prefix)/share/vcs/profiles/ |
uninstall: |
$(RM) $(DESTDIR)$(prefix)/bin/vcs |
for file in profiles/*.conf ; do \ |
$(RM) $(DESTDIR)$(prefix)/share/vcs/profiles/`basename $$file` ; \ |
done |
-rmdir -p $(DESTDIR)$(prefix)/bin |
-rmdir -p $(DESTDIR)$(prefix)/share/vcs/profiles |
examples/vcs.conf.example: examples/vcs.conf |
sed -e 's/^/#/;s/^#$$//;s/^##/#/' < $< > $@ |
vcs.spec: rpm/vcs.spec.in vcs |
test "$(VERSION)" # Version (=$(VERSION)) must be detected |
@echo "[vcs.spec]" |
@cat $< | sed 's!@VERSION@!$(VERSION)!g' | \ |
sed 's!@PACKAGER@!$(PACKAGER)!g' > $@ |
# PKGBUILD CAN'T BE INCLUDED in the archive |
PKGBUILD: arch/PKGBUILD.in $(TGZ) vcs |
test "$(VERSION)" # Version (=$(VERSION)) must be detected |
@echo "[PKGBUILD]" |
@MD5=$(shell md5sum -b $(TGZ) | cut -d' ' -f1) ; \ |
SHA1=$(shell sha1sum -b $(TGZ) | cut -d' ' -f1) ; \ |
SHA256=$(shell sha256sum -b $(TGZ) | cut -d' ' -f1) ; \ |
cat $< | sed -e 's!@VERSION@!$(VERSION)!g' \ |
-e "s/@MD5@/$$MD5/g" \ |
-e "s/@SHA1@/$$SHA1/g" -e "s/@SHA256@/$$SHA256/g" > $@ |
clean: |
-$(RM) examples/vcs.conf.example |
distclean: clean |
-$(RM) vcs.spec PKGBUILD |
.PHONY: all install clean tgz |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.1/pkg/examples/vcs.conf |
---|
0,0 → 1,152 |
# |
# vcs:conf: $Rev$ |
# Example vcs.conf file |
# This example files contains all the default values, commented out. |
# For each setting, where an equivalent command-line option exists it will be |
# listed in the comments |
# |
# Location: |
# Configuration files can be placed at /etc/vcs.conf (site-wide), |
# ~/.vcs.conf (current user, hidden file!), ./vcs.conf (current dir) or |
# ~/.vcs/vcs.conf (current user, new alternative location) |
# |
# Syntax: |
# * Comments start with '#' or ';' |
# * '#' can be used in values by writing $# |
# * Semicolons (;) can't be used in values |
# * Options are of the form name=value |
# - Options can refer to the *current* value of other options, i.e. |
# font_sign=$font_heading will assign to 'font_sign' the same value |
# as 'font_heading'. If font_heading is changed after that, font_sign |
# won't be affected. |
# - See <http://p.outlyer.net/dox/vcs:conf_files> for the full list |
# Height of individual captures. Percentage or fixed size (in pixels) |
height=100% # option -H |
# Amount ignored from the end of the video (think of it as an anti-spoiler |
# measure). Percentage (of video duration) or time (e.g. 2m). Set to 0 |
# to disable it. |
end_offset=5% # option -E |
######################## |
# Contact Sheet Layout # |
######################## |
columns=2 # Number of columns in the contact sheet (option -c) |
interval=1m # Interval between captures (option -i) |
# Number of captures. Note setting this isn't enough to use a fixed number, the |
# mode must also be changed (option 'timecode_from', see below). (option -n) |
captures=16 |
# Mode of operation, can be either $TC_NUMCAPS or $TC_INTERVAL (the default) |
# $TC_NUMCAPS Fix the number of captures, adjust interval accordingly |
# $TC_INTERVAL Fix interval, adjust the number of captures accordingly |
# (options -i and -n set this implicitly) |
timecode_from=$TC_INTERVAL |
# Modifier for extended mode (option -e). |
# Beware, setting it to something different from 0 automatically enables extended mode! |
extended_factor=0 |
# Extra padding added around each capture. |
# This has *no effect* when shadows are enabled (the default). |
# Tweaking this might break alignment when using extended mode (-e) |
padding=2 |
############### |
# Output file # |
############### |
# Output format. Use the file extension (e.g. 'png', 'jpg', 'jp2') |
# Any format accepted by ImageMagick can be used here (even pdf or gif, not |
# that they'll look very good though) |
# (options: -j for JPEG, -j2 for JPEG 2000) |
format=png |
quality=92 # Output quality for lossy formats (e.g. jpg) |
#################### |
# Cosmetic touches # |
#################### |
user=`id -un` # User name, will be used in the contact sheet footer (option -u) |
# Used in the signature, e.g. "Preview created by {value of user}" |
signature=Preview created by |
disable_shadows=0 # Disable shadows by default (option -ds) |
disable_timestamps=0 # Disable timestamps by default (option -dt) |
##################### |
# Colours and fonts # |
##################### |
# * Colours can be defined either by their name or through hexcodes |
# use the command 'identify -list color' for a list of known names |
# * Colours can use transparency, although this usually only makes sense |
# for timestamps, which are overlaid on captures. |
# * Beware when specifying colours in hex format color=#hex is ok |
# but color = #hex not, spaces can be used with color = $#hex |
# * Font sizes are expressed in points and hence might need adjustment |
# when the font is changed. |
# * Font names can be either font paths (e.g. /usr/something/ttf/font.ttf) |
# or font names as understood by ImageMagick, use the command |
# 'identify -list font' for a list of fonts known to ImageMagick |
bg_heading=#afcd7a # Heading/meta-information section background colour |
fg_heading=Black # Heading font colour |
font_heading=DejaVu-Sans-Book # Heading font |
pts_heading=14 # Font size for heading |
bg_title=White # Background for the title (if activated with option -T) |
fg_title=Black # Title font colour |
font_title=$font_heading # Title font |
bg_contact=White # Background for the contact sheet |
bg_tstamps=#000000aa # Background for timestamps. Note the use of transparency |
fg_tstamps=White # Timestamps font colour |
font_tstamps=$font_heading # Timestamps font |
pts_tstamps=14 # Font size for timestamps |
# Background for the signature, i.e. the section after the contact sheet with |
# vcs and user identification |
bg_sign=SlateGray |
fg_sign=Black # Font colour for the signature |
font_sign=$font_heading # Font for the signature |
pts_sign=10 # Font size for signature |
# Font to use for file name when the non-latin mode (--nonlatin) is enabled |
# Option: -Ik=fontname |
nonlatin_font= # Autodetected (hopefully) |
###################### |
# Lower level tweaks # |
###################### |
# Decoder to use by default, can be either $DEC_FFMPEG (default) or |
# $DEC_MPLAYER |
# (options: -M for MPlayer, -F for FFMpeg) |
decoder=$DEC_FFMPEG |
# Output from commands, useful to locate errors |
stdout=/dev/null |
stderr=/dev/null |
# Verbosity level, very verbose by default. |
# Possible values: $V_ALL (default), $V_ERROR, $V_WARN, $V_INFO, $V_NONE |
# (options: -q for quietness) |
verbosity=$V_ALL |
# 1 disables colours in console output |
simple_feedback=0 |
debug=0 # When 1, enables debugging mode (option -D) |
getopt=getopt # GNU Getopt executable name |
# $Rev$ # |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.1/Makefile |
---|
0,0 → 1,68 |
#!/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" |
tgz: vcs-$(VER).tar.gz |
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 \ |
PKGBUILD-$(VER) \ |
vcs-$(VER).gz vcs-$(VER).bz2 vcs-$(VER).bash \ |
CHANGELOG.gz CHANGELOG \ |
rpm deb |
PKGBUILD-$(VER): vcs-$(VER).tar.gz |
cd pkg && ln -s ../vcs-$(VER).tar.gz ./ |
cd pkg && make PKGBUILD |
$(RM) pkg/vcs-$(VER).tar.gz |
mv pkg/PKGBUILD $@ |
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/tags/1.12.1/vcs |
---|
0,0 → 1,0 |
link pkg/vcs |
Property changes: |
Added: svn:special |
+* |
\ No newline at end of property |
/video-contact-sheet/tags/1.12.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.12.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/tags/1.12:r413 |
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/branches/1.12:r409-411 |
Merged /video-contact-sheet/branches/1.11.2:r393-406 |
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.12.1:r416-419 |
Merged /video-contact-sheet/tags/0.99a:r261 |
Merged /video-contact-sheet/branches/1.0.3b:r276-277 |
Merged /video-contact-sheet/branches/1.0.2b:r270-271 |
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.12/pkg/debian/changelog |
---|
0,0 → 1,63 |
vcs (1.12-upstream.1) experimental; urgency=low |
* New version. |
* debian/docs: Install vcs.conf.example |
-- Toni Corvera <outlyer@gmail.com> Sat, 10 Apr 2010 00:57:17 +0200 |
vcs (1.11.2-upstream.1) experimental; urgency=low |
* New version. |
-- Toni Corvera <outlyer@gmail.com> Fri, 19 Mar 2010 00:18:51 +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/tags/1.12/pkg/debian/docs |
---|
0,0 → 1,0 |
examples/vcs.conf.example |
/video-contact-sheet/tags/1.12/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 |
DESTDIR:=$(CURDIR)/debian/vcs |
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) all prepackage |
#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=$(DESTDIR) 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/tags/1.12/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/tags/1.12/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/tags/1.12/pkg/debian/dirs |
---|
0,0 → 1,0 |
usr/bin |
/video-contact-sheet/tags/1.12/pkg/debian/compat |
---|
0,0 → 1,0 |
5 |
/video-contact-sheet/tags/1.12/pkg/profiles/mosaic.conf |
---|
0,0 → 1,11 |
# vcs:conf: |
# vcs:desc: Tight, small, thumbnails |
# <http://p.outlyer.net/dox/vcs:example_configs> |
disable_timestamps=1 |
disable_shadows=1 |
height=160 |
numcaps=20 |
timecode_from=$TC_NUMCAPS |
padding=0 |
columns=4 |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12/pkg/profiles/black.conf |
---|
0,0 → 1,8 |
# vcs:conf: |
# vcs:desc: White-on-Black |
bg_contact=Black |
bg_heading=$bg_contact |
bg_sign=$bg_contact |
fg_heading=White |
fg_sign=$fg_heading |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12/pkg/profiles/white.conf |
---|
0,0 → 1,8 |
# vcs:conf: |
# vcs:desc: Black-on-White profile |
bg_contact=White |
bg_heading=$bg_contact |
bg_sign=$bg_contact |
fg_heading=Black |
fg_sign=$fg_heading |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12/pkg/BSDmakefile |
---|
0,0 → 1,14 |
VERSION!=head -50 vcs | grep 'declare -r VERSION=' | perl -pe 's/.*"(.*)".*/\1/' |
PACKAGER!=finger -lp `echo $USER` 2>/dev/null | head -n1 | cut -d: -f3 |
.if empty($(PACKAGER)) |
PACKAGER!=grep ^`id -un` /etc/passwd | cut -d: -f5 | cut -d, -f1 |
.endif |
.if empty($(RM)) |
RM=rm -f |
.endif |
include common.mk |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12/pkg/CHANGELOG |
---|
0,0 → 1,389 |
1.12: (2010-04-10) |
* New features/tweaks: |
- Loading of random configuration files (--config / -C) |
- Profiles: Similar to above but simpler syntax (--profile / -p) |
- Config/Profile generation from command-line (--generate) |
- Adapt heading, title and footer height to font size (fonts that used |
to get cropped should now be fine) |
* DVD mode cleanup: |
- Command-line switched to match "normal" files: |
Before: |
$ vcs --dvd /dev/dvd 0 or $ vcs -dvd /dev/dvd 1 |
Equivalents now: |
$ vcs --dvd /dev/dvd or $ vcs --dvd --dvd-title 1 /dev/dvd |
* New end-offset behaviour: |
- A 5.5% end offset is applied by default |
- Can be disabled with -E0 or end_offset=0 |
- MIN_LENGTH_FOR_END_OFFSET is no longer used |
* Configuration files cleanup: |
- Simplified or more meaningful names where appropriate (the older |
names will continue to work for a while, and users will be warned) |
"vcs --generate" with no other arguments can be used to translate them |
- Validation of configuration options. |
Incorrect values will be discarded and an error shown; processing will |
continue. |
- Configuration searched in ~/.vcs/vcs.conf too |
- Syntax enhancements: |
> Comments can now be included in-line |
> Putting '#' in a value now requires using the "escaped form" '$#' |
> Semicolons (;) also serve to start comments: When one is found the |
rest of the line is ignored, they continue to be disallowed in values |
i.e. 'tl;dr' will be parsed as 'tl' |
* Other: |
- Accept timecodes and percentages in end_offset, both from the |
command-line and in configuration files |
- Print the start and end timestamps in effect before capturing |
- No longer accept interval zero (used to be re-set to default) |
- Tighter printing of overrides and no longer printed as warning |
- Strickter handing of wrong options |
- Fall back to Helvetica also when no fonts dir is located. Look |
in /usr/local too. |
- --end-offset added as an alias to --end_offset |
- Starting with 1.12 a tarball + makefile is also provided |
* BUGFIXES: |
- Avoid possible (unlikely) usage of scientific notation in internal |
calculations |
- Distinguish between default end offset and user's end offset with the |
same value |
- Handle --nonlatin correctly |
- DVD Mode + FFmpeg identification: Check VOB #0 instead of #1 |
- Don't print escape codes to stdout when testing colour printing |
* Options removed: |
--shoehorn, temporary replacement: --undocumented shoehorn. Will be gone |
in 1.13 |
--mincho, replaced by --nonlatin since 1.11 |
MIN_LENGTH_FOR_END_OFFSET, as explained above, no longer needed |
* INTERNAL: |
- $CFGFILE replaced by ~/.vcs.conf |
- Use -p for profiles instead of -P (used, undocumented, in 1.11) |
1.11.2: (2010-03-19) |
* Added Cook and Sipro (RealAudio 4, 5 & 6) codecs |
* BUGFIXES: |
- Remove extra, empty, temporary dir |
- Use standard awk syntax for exponentiation (pyth_th) |
- Workaround for systems that don't register fonts with ImageMagick |
* DEBUG: Print to stderr when probbing with mplayer too |
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 |
# vim:set ts=3 sw=3 et textwidth=80: # |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12/pkg/GNUmakefile |
---|
0,0 → 1,13 |
# |
# $Id$ |
# Makefile for GNU-make |
# |
VERSION:=$(shell head -50 vcs | grep 'declare -r VERSION=' | sed -e 's/.*"\(.*\)".*/\1/') |
PACKAGER:=$(shell finger -lp `echo $USER` 2>/dev/null | head -n1 | cut -d: -f3) |
ifeq ($(PACKAGER),) |
PACKAGER:=$(shell grep ^`id -un` /etc/passwd | cut -d: -f5 | cut -d, -f1) |
endif |
include common.mk |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12/pkg/vcs |
---|
0,0 → 1,4534 |
#!/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>) |
# |
# The full changelog can be found at <http://p.outlyer.net/vcs/files/CHANGELOG> |
declare -r VERSION="1.12" |
declare -r RELEASE=1 |
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 |
# * (1.12 or 1.13) 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 |
# --undocumented shoehorn -> NONE 1.12 1.13 or 1.14 |
# --funky -> --profile ? ?+1 |
# --end_offset -> --end-offset 1.12 (silent), 1.13 (warn) 1.14 |
# > Usage of ./vcs.conf is also deprecated since it doesn't mesh well with profiles |
# (and it was a bad placeholder for them). Will remain to be loadable with |
# -C:pwd. |
# Loaded by default in 1.12 and maybe 1.13. |
# Not loaded from 1.14 onwards (maybe 1.13), a warning may be shown if the file |
# exists |
# * 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 |
# * Optimisations: |
# - Reduce the number of forks |
# }}} # 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 |
# * ~/.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. |
# 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 |
declare -ri FF_DEFAULT=5 FF_NONLATIN=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 |
# Used for feedback |
declare -r NL=$'\012' # Newline |
declare -r TAB=$'\011' # Tab |
# }}} # 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 -r DEFAULT_END_OFFSET="5.5%" |
# 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 |
# Override-able since 1.11: |
# Height of the thumbnails, by default use same as input |
declare th_height='100%' |
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 amount of time is *not* captured from the end of the video |
declare 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 |
# Stores the names of variables overridden from the command-line, |
#+see cmdline_override() and "--override" |
declare CMDLINE_OVERRIDES="" |
# Implicit error handling (see die()), obviously inspired by C's errno |
# and PHP's die(). Functions adapted to use them allow uses like: |
# some_function arg || die |
# which will exit with the appropriate exit code and print the error message |
# (Introduced in 1.12, still being retrofitted) |
declare -i ERROR_CODE=0 # Exit code associated with the last error |
declare ERROR_MSG= # Error message associated to the last error |
# Used to buffer feedback (see buffered()) |
declare BUFFER= |
# This is only used to exit when -DD is used |
declare -i DEBUGGED=0 # It will be 1 after using -DD |
# See post_getopt_hooks() |
# Format: Priority:Command[:Arguments] (lower priority run sooner) |
declare -a POST_GETOPT_HOOKS=( ) |
declare -i DVD_MODE=0 DVD_TITLE= |
declare -a DVD_TITLES=( ) # Titles for each input DVD, filled by --dvd-title |
declare DVD_MOUNTP= # Mountpoint for DVD, detected & reset for each DVD |
declare DVD_VTS= # VTS, detected & reset for each DVD |
# }}} # Variables |
# {{{ # Configuration handling |
# New override system: This variable maps configuration variables to actual |
#+variables used in the script. Each item in the array follows the syntax: |
# <cfg variable>:<variable>:<flags>:[type constraints] Where: |
#+ cfg variable: is the name of the configuration file variable |
#+ variable: is the name of the actual variable. If empty or '=', it will be |
#+ the same as cfg variable. |
#+ flags can currently be: |
#+ "deprecated=new name": Will print a deprecation warning and suggest to use |
#+ "new name" instead |
#+ "striked": Variable is marked for removal, will print a warning about it |
#+ directing anyone needing it to contact me. Only used for variables |
#+ believed to be no longer needed |
#+ "gone": Variable removed in the current version |
#+ "alias": Marks an alias, duplicate name intended to stay |
#+ "=": ignore |
#+ type constraints: a character indicating accepted values: |
# n -> Number (Natural, positive Integer) |
# p -> Number, not zero |
# t -> Timestamp |
# b -> Bool |
# h -> Positive, non-zero, number or percentage |
# f -> Float or fraction |
# D -> only $DEC_* constants |
# T -> only $TC_* constants |
# V -> only $V_* constants |
# I -> interval or percentage |
# Note during the switch to the new system most variables will remain unchanged |
# Also, the new system is case insensitive to variable names |
# TODO: Allow 'y', 'n' in booleans |
# TODO: Remove extra coherence_check()'s once constraints are implemented |
declare -ra OVERRIDE_MAP=( |
"user:::" |
"extended_factor:=:=:f" |
"stdout::" |
"stderr::" |
"DEBUG:=:=:b" |
"interval:=:=:t" |
"numcaps:=:=:p" |
"captures:numcaps:alias:n" # Alias |
"GETOPT::" # Note it makes no sense as command-line override |
"columns:cols:=:p" |
"cols:=:alias:p" # Alias |
"DISABLE_SHADOWS:=:=:b" |
"DISABLE_TIMESTAMPS:=:=:b" |
"bg_heading::" |
"bg_sign::" |
"bg_title::" |
"bg_contact::" |
"bg_tstamps::" |
"fg_heading::" |
"fg_sign::" |
"fg_tstamps::" |
"fg_title::" |
"font_heading::" |
"font_sign::" |
"font_tstamps::" |
"font_title::" |
"pts_tstamps::" |
"pts_meta::" |
"pts_sign::" |
"pts_title::" |
# Aliases for cosmetic stuff |
"bg_header:bg_heading:alias" |
"bg_signature:bg_sign:alias" |
"bg_footer:bg_sign:alias" |
"bg_sheet:bg_contact:alias" |
"fg_header:fg_heading:alias" |
"fg_signature:fg_sign:alias" |
"fg_footer:fg_sign:alias" |
"font_header:font_heading:alias" |
"font_meta:font_heading:alias" |
"font_signature:font_sign:alias" |
"font_footer:font_sign:alias" |
"pts_heading:pts_meta:alias" |
"pts_header:pts_meta:alias" |
"pts_signature:pts_sign:alias" |
"pts_footer:pts_sign:alias" |
"signature:user_signature:" |
"user_signature::deprecated=signature" |
"quality:output_quality:=:n" |
"output_quality::deprecated=quality:n" |
# TODO: These variables are evaluated to constants, would be better to |
# use some symbolic system (e.g. decoder=f instead of decoder=$DEC_FFMPEG) |
"decoder:=:=:D" |
#"capture_mode:timecode_from:alias:T" |
"timecode_from:=:=:T" |
"verbosity:=:=:V" |
"format:output_format:" |
"output_format:=:deprecated=format" |
"simple_feedback:plain_messages:=:b" |
"plain_messages::deprecated=simple_feedback:b" |
"height:th_height:=:h" |
"th_height::deprecated=height:h" |
"padding:HPAD:=:n" |
"HPAD:=:deprecated=padding:n" |
"nonlatin_font:FONT_MINCHO:" |
"FONT_MINCHO::deprecated=nonlatin_font" |
"end_offset:=:=:I" # New, used to have a two-variables assignment before USR_* |
"DEFAULT_END_OFFSET:end_offset:deprecated=end_offset:I" |
# TODO TBA: |
#"use_nonlatinfont::" |
#"noboldfeedback::" # Colour but not bold |
"shoehorned::striked" |
"safe_rename_pattern::striked" |
"MIN_LENGTH_FOR_END_OFFSET::gone:" |
) |
# Load a configuration file |
# File *MUST* exist |
# Configuration files are a series of variable=value assignment; they'll be |
#+evaluated directly so they can refer to other variables (with their value at |
#+the point of the assignment). |
# Quotes shouldn't be used (they'll be kept) |
# Since 1.12 comments can be placed in-line (i.e. after an assignment), |
# Literal '#' can be written as '$#' |
# ';' can be used to mark an end of line, anything after it will be ignored |
#+(making it equivalent to '#'), there's no way to include a literal ';' |
# load_config_file($1 = file, [$2 = type (description) = 'Settings']) |
load_config_file() { |
local cfgfile=$1 |
local desc=$2 |
[ "$desc" ] || desc='Settings' |
local por= # Parsed override |
local varname= tmp= flag= bashcode= feedback= ov= |
while read line ; do # auto variable $line |
parse_override "$line" # Feeding it comments should be harmless |
por=$RESULT |
if [ "$por" ]; then |
varname=${por/% *} # Everything up to the first space... |
tmp=${por#* } # Rest of string |
flag=${tmp/% *} |
bashcode=${tmp#* } |
if [ "$flag" == '=' ]; then |
# No need to override... |
feedback="$varname(=)" |
else |
feedback=$varname |
eval "$bashcode" |
fi |
ov="$ov, $feedback" |
fi |
done <$cfgfile |
[ -z "$ov" ] || inf "$desc from $cfgfile:$NL ${ov:2}" |
# No loaded overrides but errors/warnings to print, do print the file name |
if [ -z "$ov" -a "$BUFFER" ]; then |
inf "In $cfgfile:" |
fi |
flush_buffered ' ' |
} |
# Loads the configuration files if present |
# load_config() |
load_config() { |
local -a CONFIGS=( /etc/vcs.conf ~/.vcs.conf ~/.vcs/vcs.conf ./vcs.conf ) |
for cfgfile in "${CONFIGS[@]}" ;do |
[ -f "$cfgfile" ] || continue |
load_config_file "$cfgfile" |
done |
} |
# Load a profile, if found; fail otherwise |
# Profiles are just configuration files that can be loaded on demand (whereas |
#+config files are always loaded) and be given a name. |
# See load_config_file() for comments on the syntax |
# Locations to be searched, in order: |
#+ 1) ~/.vcs/profiles/NAME.conf |
#+ 2) /usr/local/share/vcs/profiles/NAME.conf |
#+ 3) /usr/share/vcs/profiles/NAME.conf |
#+i.e. files in ~/.vcs/ will prevent loading files named like them in /usr |
# load_profile($1 = profile name) |
load_profile() { |
local p=$1 prof= |
local -a PATHS=( ~/.vcs/profiles/ /usr/local/share/vcs/profiles/ /usr/share/vcs/profiles/ ) |
if [ ${p:0:1} == ':' ]; then |
case $p in |
:list) |
# No need to be efficient here... |
echo "Profiles located:" |
local path= profname= |
# 1) Find all profiles |
# 2) (sed) Extract profile file name |
# 3 & 4) (sort+uniq) Keep only first hits for each name (most precedence) |
# 5) (while) Process each name |
# 6) (for) Re-locate most precedent profile |
# 7) (echo x3) Print <name>[: description] |
# 8) (sed) Indent with ' * ' |
find "${PATHS[@]}" -name '*.conf' 2>/dev/null \ |
| sed -e 's#.*/\(.*\)\.conf#\1#' \ |
| sort | uniq \ |
| while read profname ; do |
for path in "${PATHS[@]}" ; do |
path=$path$profname.conf |
[ -f "$path" ] || continue |
echo -n "$profname" |
# [ ] here contains <space><tab>. Mawk doesn't understand |
# [[:space:]] |
echo -n $(awk 'sub(/#[ ]*vcs:desc:[ ]*/, ": ")' "$path") |
echo |
break |
done |
done \ |
| sed 's/^/ * /' |
exit 0 |
;; |
*) |
ERROR_MSG="Profiles starting with ':' are reserved.$NL"\ |
" Use ':list' to list available profiles." |
ERROR_CODE=$EX_USAGE |
return $ERROR_CODE |
esac |
fi |
for prof in "${PATHS[@]}" ; do |
prof="$prof$p.conf" |
[ -f "$prof" ] || continue |
load_config_file "$prof" 'Profile' |
return 0 |
done |
ERROR_MSG="Profile '$p' not found" |
ERROR_CODE=$EX_USAGE |
return $ERROR_CODE |
} |
# Check value for an overrideable variable against the allowed values |
# check_constraint($1 = variable name, $2 = value [, $3 = public_name]) |
# where public_name is the name to be used for error messages |
check_constraint() { |
local n=$1 v=$2 p=$3 |
# Get constraint |
local map=$(echo "${OVERRIDE_MAP[*]}" | stonl | egrep -i "^$n:") |
[ "$map" ] || return 0 |
local ct=$(cut -d':' -f4 <<<"$map") |
[ "$ct" ] || return 0 |
local checkfn= domain= |
case $ct in |
n) checkfn=is_number ; domain=numbers ;; |
p) checkfn=is_positive ; domain='numbers greater than zero' ;; |
t) checkfn=is_interval ; domain=intervals ;; |
b) checkfn=is_bool ; domain='boolean values (0 or 1)' ;; |
h) checkfn=is_pos_or_percent ; domain='positive numbers or percentages' ;; |
f) checkfn=is_float_or_frac ; domain='positive numbers or fractions' ;; |
D) checkfn=is_decoder ; domain='$DEC_FFMPEG or $DEC_MPLAYER' ;; |
T) checkfn=is_tcfrom ; domain='$TC_INTERVAL or $TC_INTERVAL' ;; |
V) checkfn=is_vlevel ; domain='verbosity levels ($V_.*)' ;; |
I) checkfn=is_interv_or_percent ; domain='intervals or percentages' ;; |
esac |
if [ "$checkfn" ] && ! $checkfn "$v" ; then |
[ "$p" ] || p=$n |
ERROR_MSG="Illegal value for '$p', only $domain are accepted" |
ERROR_CODE=$EX_USAGE |
return $ERROR_CODE |
fi |
return 0 |
} |
# Parse an override |
# Input should be a var=value assignment, result, stored in the global variable $RESULT, |
# will be in the format: |
# <variable name> <flag> <evaluable code> where |
# * variable name: is the name of the variable to be overridden |
# * flag: is a character indicating the status: "+" for a possible override, |
# "=" for an override that already has the same value |
# * evaluable code: is a piece of bash code to be feed to eval to change |
# the variable, it also sets the related USR_* variable |
# Warnings and errors are buffered |
# This function always returns true |
# parse_override($1 = override assignment) |
parse_override() { |
local o="$1" |
RESULT='' |
if ! egrep -q '^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*=.*' <<<"$o" ; then |
return |
fi |
local varname=$(echo "${o/=*}" | sed 's/[[:space:]]//g') # Trim var name |
local lcvarname=$(echo "$varname" | tr '[A-Z]' '[a-z]') |
local mapping=$(echo "${OVERRIDE_MAP[*]}" | stonl | egrep -i "^$lcvarname:") |
[ "$mapping" ] || return 0 |
local varval=${o#*=} # No trimming here (yet) |
# 1) Trim from ; (if present) to finish |
# 2) Trim from # (comments) not "escaped" like '$#' |
# 3) Replace '$#' with '#' |
# 4) Trim whitespace on both ends |
varval=$(sed -e 's/;.*//' -e 's/\([^$]\)#.*/\1/g' -e 's/\$#/#/g' \ |
-e 's/^[[:space:]]*//;s/[[:space:]]*$//' <<<"$varval") |
# Is varval empty? |
[ "$varval" ] || return 0 |
local mvar=$(cut -d':' -f1 <<<"$mapping") |
local ivar=$(cut -d':' -f2 <<<"$mapping") |
local flags=$(cut -d':' -f3 <<<"$mapping") |
local constraints=$(cut -d':' -f4 <<<"$mapping") |
{ [ "$ivar" ] && [ "$ivar" != "=" ] ; } || ivar="$mvar" |
# Note using "\$(echo $varval)" would allow a more flexible syntax but |
#+enforce special handling of escaping, which with the currently available |
#+settings is not worth the effort |
# Resolve symbolic variables to check their actual value |
eval varval="\"$varval\"" 2>/dev/null || { # Hide eval's errors |
buffered error "Syntax error: '$o'" |
return 0 |
} |
[ "$varval" ] || return 0 # If empty value, ignore it |
local evcode='' |
if [ "$flags" ] && [ $flags != "=" ] && [ $flags != 'alias' ]; then |
if echo "$flags" | grep -q '^deprecated=' ; then |
local new=$(echo "$flags" | sed 's/^deprecated=//') |
buffered warn "Variable '$varname' will be removed in the future,$NL please use '$new' instead." |
else |
case "$flags" in |
gone) |
buffered error "Variable '$varname' has been removed." |
return 0 |
;; |
striked) |
buffered error "Variable '$varname' is scheduled to be removed in the next release." |
buffered error " Please contact the author if you absolutely need it." |
;; |
*) return 0 ;; |
esac |
fi |
fi |
[ -z "$constraints" ] || check_constraint $ivar "$varval" $varname || { |
buffered error "$ERROR_MSG" |
return 0 |
} |
eval local curvarval='$'"$ivar" retflag='+' |
if [ "$curvarval" == "$varval" ]; then |
retflag='=' |
else |
if [ "$constraints" == "t" ]; then |
varval=$(get_interval "$varval") |
fi |
# Escape single quotes, since it will be single-quoted: |
varval=${varval//\'/\'\\\'\'} # <<'>> => <<'\''>> |
evcode="$ivar='$varval'; USR_$ivar='$varval'" |
fi |
# varname, as found in the config file |
RESULT="$varname $retflag $evcode" |
} |
# Do an override from the command line |
# cmdline_override($1 = override assignment) |
#+e.g. cmdline_override 'decoder=$DEC_FFMPEG' |
cmdline_override() { |
trace $FUNCNAME $@ |
parse_override "$1" |
local r=$RESULT |
[ "$r" ] || return 0 |
local varname=${r/% *} # See load_config() |
local tmp=${r#* } |
local flag=${tmp/% *} |
local bashcode=${tmp#* } |
if [ "$flag" == '=' ]; then |
varname="$varname(=)" |
else |
eval "$bashcode" |
fi |
CMDLINE_OVERRIDES="$CMDLINE_OVERRIDES, $varname" |
} |
# Call any pending commands required by the command-line arguments |
# This is used to defer some calls and to flush buffers |
post_getopt_hooks() { |
local cback= EX=0 |
local funcs=$(echo "${POST_GETOPT_HOOKS[*]}" | stonl | sort -n | uniq |\ |
cut -d':' -f2- ) |
for cback in $funcs ; do |
local fn=${cback/:*} |
local arg=${cback/*:} |
[ "$arg" != "$cback" ] || arg='' |
$fn $arg |
done |
} |
# Print the list of command-line overrides |
cmdline_overrides_flush() { |
trace $FUNCNAME $@ |
if [ "$CMDLINE_OVERRIDES" ]; then |
inf "Overridden settings from command line:$NL ${CMDLINE_OVERRIDES:2}" |
fi |
if [ "$BUFFER" ]; then |
[ "$CMDLINE_OVERRIDES" ] || warn "In command-line overrides:" |
flush_buffered ' ' |
fi |
} |
# }}} # Configuration handling |
# {{{ # Convenience functions |
#### {{{{ # Type checkers: Return true if input is of a certain type |
#### All take exactly one argument and print nothing |
## Natural number |
is_number() { |
#egrep -q '^[0-9]+$' <<<"$1" ; |
# From [[abs]], test if '[ ]' can parse input as numbers |
# Returns 2 for failed test, expected to return 1 |
[ "$1" -ne 0 -o "$1" -eq 0 ] 2>/dev/null || return 1 |
} |
## Number > 0 |
is_positive() { is_number "$1" && [ $1 -gt 0 ]; } |
## Bool (0 or 1) |
is_bool() { [ "$1" == "0" -o "$1" == "1" ] 2>/dev/null ; } |
## Float (XX.YY; XX.; ;.YY) (.24=0.24) |
is_float() { egrep -q '^([0-9]+\.?([0-9])?+|(\.[0-9]+))$'<<<"$1" ; } |
## Percentage (xx% or xx.yy%) |
is_percentage() { |
egrep -q '^([0-9]+\.?([0-9])?+|(\.[0-9]+))%$'<<<"$1" |
} |
## Interval |
is_interval() { |
local i=$(get_interval "$1" || true) |
[ "$i" ] && fptest $i -gt 0 |
} |
## Interval or percentage |
is_interv_or_percent() { |
is_percentage "$1" || is_interval "$1" |
} |
## Positive or percentage |
is_pos_or_percent() { |
is_number "$1" && [ "$1" -gt 0 ] || is_percentage "$1" |
} |
## Float (>=0) or fraction |
is_float_or_frac() { |
{ is_fraction "$1" || is_float "$1" ; } && fptest "$1" -ge 0 |
} |
## Fraction, strictly (X/Y, but no X; Y!=0) |
is_fraction() { |
egrep -q '^[0-9]+/[0-9]+$'<<<"$1" && { |
local d=$(echo "$1" | cut -d'/' -f2) |
[ "$d" -ne 0 ] |
} |
} |
## Decoder ($DEC_* constants) |
is_decoder() { [ "$1" == $DEC_FFMPEG -o "$1" == $DEC_MPLAYER ]; } |
## Time calculation source ($TC_* constants) |
is_tcfrom() { [ "$1" == $TC_INTERVAL -o "$1" == $TC_NUMCAPS ]; } |
### Verbosity level ($V_* constants) |
is_vlevel() { |
is_number "$1" && \ |
[ $1 -eq $V_ALL -o $1 -eq $V_NONE -o $1 -eq $V_ERROR -o \ |
$1 -eq $V_WARN -o $1 -eq $V_INFO ] |
} |
#### }}}} # End of type checkers |
# 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 old code (default bc -l) |
# TODO: gawk and mawk differ in how to handle stuff like div by zero: |
# gawk errors, mawk prints inf. Should somehow handle inf and nan |
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() { |
if [ "$1" ]; then |
awk '{gsub(" ", "\n");print}' <<<"$1" | egrep -v '^$' |
else |
awk '{gsub(" ", "\n");print}' | egrep -v '^$' |
fi |
} |
# Converts newlines to spaces portably |
# nltos([$1 = string]) |
nltos() { |
if [ "$1" ]; then |
awk '{printf "%s ",$0}' <<<"$1" | sed 's/ *//' |
else |
awk '{printf "%s ",$0}' | sed 's/ *//' |
fi |
} |
# 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() { |
awkexf "sqrt($1 ^ 2 + $2 ^ 2)" |
} |
# Get a percentage |
# percent($1 = value, $2 = percentage) |
percent() { |
local pc=${2/%%/} # BASH %% == RE %$ |
awkexf "($1 * $pc) / 100" |
} |
# Rounded percentage |
# rpercent($1 = value, $2 = percentage) |
rpercent() { |
local pc=${2/%%/} |
awkex "int( ($1 * $pc) / 100 + 0.5 )" |
} |
# 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 awkexf "\"$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=$(awkexf "$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" |
} |
# Get the line height used for a certain font and size |
# line_height($1 = font, $2 = size) |
line_height() { |
# Create a small image to see how tall are characters. In my tests, no |
#+matter which character is used it's always the same height. |
convert -font "$1" -pointsize "$2" \ |
label:'F' png:- | identify -format '%h' - |
} |
# 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 soon 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($1 = image file) |
get_dvd_image_mountpoint() { |
if is_linux ; then |
local lodev=$(/sbin/losetup -j "$1" | 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 |
} |
# |
# Buffering of feedback, usage: |
# buffered warn "my warning" |
# ... |
# flush_buffered |
# buffered($1 = feedback function, $2 = arguments) |
buffered() { |
local grab=$( $1 "$2" 2>&1 ) |
# BUFFER=( "${BUFFER[@]}" -- "$grab" ) |
BUFFER=$BUFFER$grab$NL |
} |
# |
# Print buffered feedback to stderr |
# flush_buffered([$1 = indentation]) |
flush_buffered() { |
[ "${BUFFER[*]}" ] || return 0 |
echo "$BUFFER" | sed -e '$d' -e "s/^/$1/g" >&2 # sed: delete last line, indent with $1 |
BUFFER='' |
} |
# |
# trace($1 = function name = $FUNCNAME, function arguments...) |
trace() { |
if [ "$DEBUG" -ne "1" ]; then return; fi |
echo "[TRACE]: $@" >&2 |
} |
# Print an error message and exit |
# die([$1 = message [, $2 = exit_code]]) |
# If no message is provided, use $ERROR_MSG |
# If no exit_code is provided, use $ERROR_CODE |
die() { |
local m=$1 ec=$2 |
[ "$ec" ] || ec=$ERROR_CODE |
[ "$ec" ] || ec=1 |
[ "$m" ] || m=$ERROR_MSG |
error "$m" |
exit $ec |
} |
# |
# 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 && 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 >/dev/null |
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 |
local eff_eo= # Effective end_offset (for percentages) |
# 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 is_percentage $end_offset ; then |
eff_eo=$(percent $end $end_offset) |
else |
eff_eo=$(get_interval "$end_offset") |
fi |
if fptest $totime -le 0 ; then # If no totime is set, use end_offset |
eo=$eff_eo |
local runlen=$(awkexf "$end - $st") |
if fptest "($end-$eo-$st)" -le 0 ; then |
if fptest "$eo" -gt 0 && [ -z "$USR_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") |
local last= |
while fptest $stamp -le "$bound"; do |
assert $LINENO fptest $stamp -ge 0 |
LTC=( "${LTC[@]}" "$stamp" ) |
last=$stamp |
stamp=$(keepdecimals_lower $(awkexf "$stamp + $inc") 3) |
done |
inf "Capturing in range [$(pretty_stamp $inc)-$(pretty_stamp $last)]. Total length: $(pretty_stamp ${VID[$LEN]})" |
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() { |
trace $FUNCNAME $@ |
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() { |
trace $FUNCNAME $@ |
# Note mplayer CAN'T set the output filename, newer mplayer can set output |
#+dir though. |
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 "$f" \ |
$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 $(awkexf "$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" ;; |
# 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" ;; |
# Other versions of R.A. listed at Wikipedia/RealAudio |
sipr) acodec="RealAudio SIPR" ;; # RA 4/5 |
cook) acodec="RealAudio Cook" ;; # RA 6 |
*) # 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>"$stderr" | grep ^ID) |
else |
MPLAYER_CACHE=$("$MPLAYER" -benchmark -ao null -vo null -identify -frames 0 \ |
-quiet -dvd-device "$f" dvd://$DVD_TITLE \ |
2>"$stderr" | 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 |
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 ] && { |
local vfile="$DVD_MOUNTP/VIDEO_TS/VTS_${DVD_VTS}_0.VOB" |
if [ ! -r "$vfile" ]; then |
error "Failed to locate mounted DVD. Detection will be less accurate." |
return 0 # We can continue anyway |
fi |
f="$vfile" |
} |
# XXX: FFmpeg detects mpeg1video in DVDs?? |
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 -e 's/:/h/' -e '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 0 ] || 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 $(awkexf "($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") |
if [ -z "$ffl" -a $DVD_MODE -eq 1 ]; then |
ffl="(unavailable in DVD mode)" |
fi |
[ "$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:$NL$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 |
# In case it's 0/0 or 0.0 since they aren't rejected |
if fptest "$extended_factor" -eq 0 ; then |
extended_factor=0 |
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 |
# Since 1.12 DVD mode can work with multiple inputs too |
# 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[@]}" ) |
# 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() { |
trace $FUNCNAME $@ |
# 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 |
local dvs='' |
if [ -d /usr/local/share/fonts ]; then |
dvs=$(find /usr/local/share/fonts/ -type f -iname 'dejavusans.ttf') |
fi |
if [ -z "$dvs" -a -d /usr/share/fonts ]; then |
dvs=$(find /usr/share/fonts/ -type f -iname 'dejavusans.ttf') |
fi |
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 Sanitation: |
font_heading: $font_heading |
font_title : $font_title |
font_tstamps: $font_tstamps |
font_sign : $font_sign |
EOFF |
} |
# 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 |
DVD_MOUNTP= DVD_TITLE= # Re-set for each file |
if [ $DVD_MODE -eq 1 ]; then |
local dvdn=$(realpathr "$f") |
# Is it an ISO? |
if [ -f "$dvdn" ]; then |
DVD_MOUNTP=$(get_dvd_image_mountpoint "$dvdn") |
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 # Not an ISO, is it readable? |
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_MOUNTP=$(mount | grep -o "^$dvdn *on [^ ]*" | cut -d' ' -f3) |
dvdn="DVD $f" |
fi |
inf "Processing $dvdn..." |
unset dvdn |
DVD_TITLE=${DVD_TITLES[0]} |
DVD_TITLES=( "${DVD_TITLES[@]:1}" ) # shift array |
if [ -z "$DVD_TITLE" -o "$DVD_TITLE" == "0" ]; then |
local dt="$(lsdvd "$f" 2>/dev/null | grep 'Longest track:' | \ |
cut -d' ' -f3- | sed 's/^0*//')" |
if ! is_number "$dt" ; then |
error "Failed to autodetect longest DVD title for '$f'" |
exit $EX_INTERNAL |
fi |
DVD_TITLE=$dt |
unset dt |
fi |
DVD_VTS=$(lsdvd -t$DVD_TITLE -v "$f" 2>/dev/null | grep -o 'VTS: [0-9]*' | cut -d' ' -f2) |
inf "Using DVD Title #$DVD_TITLE (VTS: $DVD_VTS) for '$f'" |
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" && [ "$th_height" != '100%' ]; then |
vidcap_height=$(rpercent ${VID[$H]} ${th_height}) |
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=$(awkexf "${VID[$ASPECT]}") |
else |
aspect_ratio=$(awkexf "${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 $(awkex "int(${#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") headheight= |
local heading=$(new_temp_file .png) |
# Add the title if any |
if [ "$title" ]; then |
local tlheight=$(line_height "$font_title" "$pts_title") |
convert \ |
\( \ |
-size ${headwidth}x$tlheight "xc:$bg_title" \ |
-font "$font_title" -pointsize "$pts_title" \ |
-background "$bg_title" -fill "$fg_title" \ |
-gravity Center -annotate 0 "$title" \ |
\) \ |
-flatten \ |
"$output" -append "$output" |
unset tlheight |
fi |
local fn_font= # see $font_filename |
case $font_filename in |
$FF_DEFAULT) fn_font="$font_heading" ;; |
$FF_NONLATIN) fn_font="$FONT_MINCHO" ;; |
*) |
warn "\$font_filename was overridden with an incorrect value, using default." |
fn_font="$font_heading" |
;; |
esac |
# Create a small image to see how tall are characters. In my tests, no matter |
#+which character is used it's always the same height. |
local lineheight=$(line_height "$font_heading" "$pts_meta") |
# Since filename can be set in a different font check it too |
if [ "$fn_font" != "$font_heading" ]; then |
local fnlineheight=$lineheight |
fnlineheight=$(line_height "$fn_font" "$pts_meta") |
[ $fnlineheight -le $lineheight ] || lineheight=$fnlineheight |
unset fnlineheight |
fi |
headheight=$(( $lineheight * 3 )) |
# 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 "$f" 2>/dev/null | grep -o 'Disc Title: .*' | cut -d' ' -f3-) |
# There's no guarantee that titles are on separate VTS, I have no idea |
# how to compute the actual title size |
if [ "$DVD_MOUNTP" ]; then |
filename_label="Disc label" |
filename_value="$dvd_label" |
filesize_label="Titleset 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 "$f") $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 |
local signlh=$(line_height "$font_sign" "$pts_sign") |
local signheight=$(( 4 + ( $signlh * 2 ) )) |
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}x$signheight -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 signheight signlh |
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.35755975068581946630 #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 DVD Mode. |
In this mode the input <file>s must be the DVD |
device(s) or ISO(s). When in DVD mode all input files |
must be DVDs. |
Implies -A (auto aspect ratio) |
--dvd-title <arg> DVD title to use. Using 0 (the default) will use the |
longest title. |
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 |
-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. |
-p|--profile <arg> Load profile "arg" |
-C|--config <arg> Load configuration file "arg" |
--generate <config|profile> |
Generate configuration or profile from current settings |
-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 |
# ' # Syntax highlighting bait |
} |
# Print a configuration file generated from the currently active settings |
# generate_config($1 = <config|profile>) |
generate_config() { |
local n=$(echo $1 | tr '[a-z]' '[A-Z]') f= t= x= |
cat <<-EOM |
# --- $n STARTS HERE --- |
# This is a sample configuration file for VCS generated automatically |
# from the command-line with the "--generate $1" command-line option |
# Save it to ~/.vcs.conf or ~/.vcs/vcs.conf to make it the default |
# configuration. |
# OR |
# Save it to ~/.vcs/profiles/something.conf to create a profile named |
# "something". To use this profile run vcs with the "--profile something" |
# (or "-p something") option |
# OR |
# Save it to "something.conf" and load it with "--config something.conf" |
# (or "-C something.conf") |
EOM |
echo "${OVERRIDE_MAP[*]}" | stonl | egrep -v '(deprecated=|alias)' | cut -d':' -f1-2 |\ |
while read ovname ; do |
f=${ovname/:*} |
t=${ovname#*:} |
if [ -z "$t" ] || [ "$t" == "=" ]; then t=$f ; fi |
eval v=\$USR_$t |
[ -z "$v" ] || { |
# Symbolic values: |
case "$t" in |
timecode_from) |
x='$TC_NUMCAPS' |
[ $v -eq $TC_NUMCAPS ] || x='$TC_INTERVAL' |
v=$x |
;; |
decoder) |
x='$DEC_FFMPEG' |
[ $v -eq $DEC_FFMPEG ] || x='$DEC_MPLAYER' |
v=$x |
;; |
verbosity) |
case $v in |
$V_ALL) v='$V_ALL' ;; |
$V_NONE) v='$V_NONE' ;; |
$V_INFO) v='$V_INFO' ;; |
$V_WARN) v='$V_WARN' ;; |
$V_ERROR) v='$V_ERROR' ;; |
esac # verbosity |
;; |
esac |
echo "$f=$v" |
} |
done |
echo "# vcs:conf:$NL# Generated on $(date)$NL# --- $n ENDS HERE --- " |
exit 0 |
} |
# }}} # 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:VR:Z:o:p:C: \ |
--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:,end-offset:,disable:,dvd,dvd-title:,randomsource:,undocumented:,output:,"\ |
"fullhelp,profile:,"\ |
"jpeg2,nonlatin,generate:,config:" \ |
-- "$@") |
eval set -- "$TEMP" |
while true ; do |
case "$1" in |
-i|--interval) |
check_constraint 'interval' "$2" "$1" || die |
interval=$(get_interval $2) |
timecode_from=$TC_INTERVAL |
USR_interval=$interval |
USR_timecode_from=$TC_INTERVAL |
shift # Option arg |
;; |
-n|--numcaps) |
check_constraint 'numcaps' "$2" "$1" || die |
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|--end-offset) |
check_constraint 'end_offset' "$2" "$1" || die |
is_p='y' |
is_percentage "$2" || is_p='' |
if [ "$is_p" ]; then |
end_offset="$2" |
else |
end_offset=$(get_interval "$2") |
fi |
USR_end_offset="$end_offset" |
unset is_i |
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 ;; |
-F) decoder=$DEC_FFMPEG ; USR_decoder=$decoder ;; |
-M) decoder=$DEC_MPLAYER ; USR_decoder=$decoder ;; |
-H|--height) |
check_constraint 'height' "$2" "$1" || die |
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) |
check_constraint 'columns' "$2" "$1" || die |
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 |
if [ "$2" ]; then |
check_constraint 'extended_factor' "$2" "$1" || die |
extended_factor="$2" |
else |
extended_factor=$DEFAULT_EXT_FACTOR |
fi |
USR_extended_factor=$extended_factor |
shift |
;; |
# Unlike -I, --nonlatin does not accept a font name |
--nonlatin) |
if [ -z "$USR_FONT_MINCHO" ]; then |
font_filename=$FF_NONLATIN |
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_NONLATIN |
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 |
cmdline_override "$2" |
POST_GETOPT_HOOKS=( "${POST_GETOPT_HOOKS[@]}" 1:cmdline_overrides_flush ) |
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=$(awkexf "$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=$(awkexf "$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=$(awkexf "$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 |
;; |
*) load_profile "$2" || die |
;; |
esac |
shift |
;; |
-C|--config) |
if echo "$2" | grep -q '^:' ; then |
if [ $2 = ':pwd' ]; then |
cfg=./vcs.conf |
else |
error "Configuration names starting with ':' are reserved." |
exit $EX_USAGE |
fi |
else |
cfg=$2 |
fi |
[ -f "$cfg" ] || { |
error "Configuration file '$cfg' not found" |
exit $EX_USAGE |
} |
# ./vcs.conf doesn't need the vcs:conf: mark |
if [ $2 != ':pwd' ]; then |
head -5 "$cfg" | grep -q '#[[:space:]]*vcs:conf[[:space:]]*:' || \ |
tail -5 "$cfg" | grep -q '#[[:space:]]*vcs:conf[[:space:]]*:' || { |
error "No vcs:conf: mark found in '$cfg'" |
exit $EX_NOINPUT |
} |
fi |
load_config_file "$cfg" 'Custom configuration' |
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 |
;; |
--dvd-title) |
check_constraint 'dvd_title' "$2" "$1" || die |
DVD_TITLES=( "${DVD_TITLES[@]}" "$2" ) |
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 |
decoder=$DEC_MPLAYER |
aspect_ratio=-2 # Special value: Auto detect only if ffmpeg couldn't |
;; |
-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 |
USR_verbosity=$verbosity |
;; |
-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")" |
error "Shoehorning of options is scheduled to be removed in the next version." |
error " Please contact the author if you absolutely need it." |
;; |
debug) |
warn "[U] debug" |
DEBUG=1 |
;; |
# Dump user-set variables and exit [since 1.12] |
uservars) |
echo "${OVERRIDE_MAP[*]}" | stonl | egrep -v '(deprecated=|alias)' | cut -d':' -f1-2 |\ |
while read ovname ; do |
f=${ovname/:*} |
t=${ovname#*:} |
if [ "$t" -a "$t" != "=" ]; then f="$t" ; fi |
eval v=\$USR_$f |
[ -z "$v" ] || echo "$f=$v" |
done |
exit 0 |
;; |
*) false ;; |
esac |
shift |
;; |
--generate) |
case "$2" in |
profile|config) |
POST_GETOPT_HOOKS=( "${POST_GETOPT_HOOKS[@]}" \ |
10:generate_config:$2 ) |
;; |
*) |
error "Option --generate must be followed by profile or config" |
exit $EX_USAGE |
;; |
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 |
# Avoid coherence_check if there's no arguments and no cmdline post |
# processing |
[ "$1" -o "$POST_GETOPT_HOOKS" ] || { |
[ $verbosity -eq $V_NONE ] || show_help |
exit $EX_USAGE |
} |
# More than one argument... |
if [ "$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 $? |
} |
# Run after coherence check to clean recoverable incorrect values |
post_getopt_hooks |
# Remaining arguments |
if [ ! "$1" ]; then |
[ $verbosity -eq $V_NONE ] || show_help |
exit $EX_USAGE |
fi |
# TODO: |
# DVD mode + multiple titles is still tricky: |
# --dvd --dvd-title 1 --dvd-title 2 /dev/dvd /dev/dvd |
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 |
# * bash3: |& (inherited from csh?) pipes both stdout and stderr |
# * 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.12/pkg/arch/PKGBUILD.in |
---|
0,0 → 1,39 |
# |
# $Rev$ |
# |
# Maintainer (Upstream): Toni Corvera <outlyer@gmail.com> |
# |
# Build with '$ makepkg' on the same directory as this file |
# |
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=() |
options=('docs' 'zipman') |
source=($url/files/$pkgname-$pkgver.tar.gz) |
md5sums=(@MD5@) #generate with 'makepkg -g' |
sha1sums=(@SHA1@) |
# Debian & Arch didn't agree on this on my first try (???) |
sha256sums=(@SHA256@) |
build() { |
cd $srcdir/$pkgname-$pkgver |
make prepackage |
make install DESTDIR=${pkgdir} prefix=/usr |
install -D $srcdir/$pkgname-$pkgver/examples/vcs.conf.example \ |
${pkgdir}/usr/share/doc/$pkgname/vcs.conf.example |
} |
#man page (TODO) |
# install -D -m644 $pkgname.1 ${pkgdir}/usr/share/man1/$pkgname.1 || return 1 |
# vim:set filetype=sh ts=2 et: # |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12/pkg/README |
---|
0,0 → 1,39 |
Index |
----- |
1. Files |
2. Installation |
3. Uninstallation |
Files |
----- |
In this package: |
vcs The VCS script |
profiles/ Example profiles: |
mosaic.conf 20 small thumbnails in a 5x4 grid, no padding |
black.conf Black background and white text |
white.conf White background and black text |
examples/vcs.conf Example configuration |
Use "make examples/vcs.conf.example" to create |
a version with all options commented out. |
Installation |
------------ |
$ make install |
Will install under /usr/local |
$ make install prefix=/usr |
Will install under /usr |
Uninstallation |
-------------- |
$ make uninstall |
If you used a prefix during install use it too during uninstall |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12/pkg/rpm/vcs.spec.in |
---|
0,0 → 1,110 |
# |
# $Rev$ |
# |
# 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 |
make examples/vcs.conf.example |
%install |
make DESTDIR=%buildroot prefix=%{prefix} install |
%clean |
[ ${RPM_BUILD_ROOT} != "/" ] && rm -rf ${RPM_BUILD_ROOT} |
#%post |
# postinst |
#%postun |
# postuninst |
%files |
%defattr(-,root,root) |
# binary |
%{_bindir}/%{name} |
# Profiles |
%{prefix}/share/vcs/profiles/black.conf |
%{prefix}/share/vcs/profiles/mosaic.conf |
%{prefix}/share/vcs/profiles/white.conf |
# Manpage |
#%{_mandir}/man1/%{name}.1.gz |
%doc CHANGELOG |
# Config example |
%doc examples/vcs.conf.example |
%changelog |
* Sat Apr 10 2010 - outlyer (at) gmail (dot) com |
- Added profiles and example configuration |
- Use %{prefix} |
* 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.12/pkg/common.mk |
---|
0,0 → 1,66 |
# $Id$ |
# |
# To be included from GNUmakefile or BSDmakefile |
# To use it directly set VERSION and PACKAGER |
# e.g. make VERSION=1.x PACKAGER=Me <rule> |
# |
# Notes to self: |
# This file should follow only common/portable make syntax and commands |
# Common pitfalls: |
# - $(shell) -> GNU Make, equivalent BSD make: != |
# - install -D -> GNU only (-d is portable) |
# - $(RM) -> empty by default in BSD, set from BSDmakefile |
prefix:=/usr/local |
DESTDIR:=/ |
TGZ=vcs-$(VERSION).tar.gz |
all: |
# Nothing to be done |
dist: vcs.spec |
# Files installed in packages but not outside |
prepackage: examples/vcs.conf.example |
install: |
install -d $(DESTDIR)$(prefix)/bin/ |
install -m755 vcs $(DESTDIR)$(prefix)/bin/vcs |
install -d $(DESTDIR)$(prefix)/share/vcs/profiles |
install -m644 profiles/*.conf $(DESTDIR)$(prefix)/share/vcs/profiles/ |
uninstall: |
$(RM) $(DESTDIR)$(prefix)/bin/vcs |
for file in profiles/*.conf ; do \ |
$(RM) $(DESTDIR)$(prefix)/share/vcs/profiles/`basename $$file` ; \ |
done |
-rmdir -p $(DESTDIR)$(prefix)/bin |
-rmdir -p $(DESTDIR)$(prefix)/share/vcs/profiles |
examples/vcs.conf.example: examples/vcs.conf |
sed -e 's/^/#/;s/^#$$//;s/^##/#/' < $< > $@ |
vcs.spec: rpm/vcs.spec.in vcs |
test "$(VERSION)" # Version (=$(VERSION)) must be detected |
@echo "[vcs.spec]" |
@cat $< | sed 's!@VERSION@!$(VERSION)!g' | \ |
sed 's!@PACKAGER@!$(PACKAGER)!g' > $@ |
# PKGBUILD CAN'T BE INCLUDED in the archive |
PKGBUILD: arch/PKGBUILD.in $(TGZ) vcs |
test "$(VERSION)" # Version (=$(VERSION)) must be detected |
@echo "[PKGBUILD]" |
@MD5=$(shell md5sum -b $(TGZ) | cut -d' ' -f1) ; \ |
SHA1=$(shell sha1sum -b $(TGZ) | cut -d' ' -f1) ; \ |
SHA256=$(shell sha256sum -b $(TGZ) | cut -d' ' -f1) ; \ |
cat $< | sed -e 's!@VERSION@!$(VERSION)!g' \ |
-e "s/@MD5@/$$MD5/g" \ |
-e "s/@SHA1@/$$SHA1/g" -e "s/@SHA256@/$$SHA256/g" > $@ |
clean: |
-$(RM) examples/vcs.conf.example |
distclean: clean |
-$(RM) vcs.spec PKGBUILD |
.PHONY: all install clean tgz |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12/pkg/examples/vcs.conf |
---|
0,0 → 1,152 |
# |
# vcs:conf: $Rev$ |
# Example vcs.conf file |
# This example files contains all the default values, commented out. |
# For each setting, where an equivalent command-line option exists it will be |
# listed in the comments |
# |
# Location: |
# Configuration files can be placed at /etc/vcs.conf (site-wide), |
# ~/.vcs.conf (current user, hidden file!), ./vcs.conf (current dir) or |
# ~/.vcs/vcs.conf (current user, new alternative location) |
# |
# Syntax: |
# * Comments start with '#' or ';' |
# * '#' can be used in values by writing $# |
# * Semicolons (;) can't be used in values |
# * Options are of the form name=value |
# - Options can refer to the *current* value of other options, i.e. |
# font_sign=$font_heading will assign to 'font_sign' the same value |
# as 'font_heading'. If font_heading is changed after that, font_sign |
# won't be affected. |
# - See <http://p.outlyer.net/dox/vcs:conf_files> for the full list |
# Height of individual captures. Percentage or fixed size (in pixels) |
height=100% # option -H |
# Amount ignored from the end of the video (think of it as an anti-spoiler |
# measure). Percentage (of video duration) or time (e.g. 2m). Set to 0 |
# to disable it. |
end_offset=5% # option -E |
######################## |
# Contact Sheet Layout # |
######################## |
columns=2 # Number of columns in the contact sheet (option -c) |
interval=1m # Interval between captures (option -i) |
# Number of captures. Note setting this isn't enough to use a fixed number, the |
# mode must also be changed (option 'timecode_from', see below). (option -n) |
captures=16 |
# Mode of operation, can be either $TC_NUMCAPS or $TC_INTERVAL (the default) |
# $TC_NUMCAPS Fix the number of captures, adjust interval accordingly |
# $TC_INTERVAL Fix interval, adjust the number of captures accordingly |
# (options -i and -n set this implicitly) |
timecode_from=$TC_INTERVAL |
# Modifier for extended mode (option -e). |
# Beware, setting it to something different from 0 automatically enables extended mode! |
extended_factor=0 |
# Extra padding added around each capture. |
# This has *no effect* when shadows are enabled (the default). |
# Tweaking this might break alignment when using extended mode (-e) |
padding=2 |
############### |
# Output file # |
############### |
# Output format. Use the file extension (e.g. 'png', 'jpg', 'jp2') |
# Any format accepted by ImageMagick can be used here (even pdf or gif, not |
# that they'll look very good though) |
# (options: -j for JPEG, -j2 for JPEG 2000) |
format=png |
quality=92 # Output quality for lossy formats (e.g. jpg) |
#################### |
# Cosmetic touches # |
#################### |
user=`id -un` # User name, will be used in the contact sheet footer (option -u) |
# Used in the signature, e.g. "Preview created by {value of user}" |
signature=Preview created by |
disable_shadows=0 # Disable shadows by default (option -ds) |
disable_timestamps=0 # Disable timestamps by default (option -dt) |
##################### |
# Colours and fonts # |
##################### |
# * Colours can be defined either by their name or through hexcodes |
# use the command 'identify -list color' for a list of known names |
# * Colours can use transparency, although this usually only makes sense |
# for timestamps, which are overlaid on captures. |
# * Beware when specifying colours in hex format color=#hex is ok |
# but color = #hex not, spaces can be used with color = $#hex |
# * Font sizes are expressed in points and hence might need adjustment |
# when the font is changed. |
# * Font names can be either font paths (e.g. /usr/something/ttf/font.ttf) |
# or font names as understood by ImageMagick, use the command |
# 'identify -list font' for a list of fonts known to ImageMagick |
bg_heading=#afcd7a # Heading/meta-information section background colour |
fg_heading=Black # Heading font colour |
font_heading=DejaVu-Sans-Book # Heading font |
pts_heading=14 # Font size for heading |
bg_title=White # Background for the title (if activated with option -T) |
fg_title=Black # Title font colour |
font_title=$font_heading # Title font |
bg_contact=White # Background for the contact sheet |
bg_tstamps=#000000aa # Background for timestamps. Note the use of transparency |
fg_tstamps=White # Timestamps font colour |
font_tstamps=$font_heading # Timestamps font |
pts_tstamps=14 # Font size for timestamps |
# Background for the signature, i.e. the section after the contact sheet with |
# vcs and user identification |
bg_sign=SlateGray |
fg_sign=Black # Font colour for the signature |
font_sign=$font_heading # Font for the signature |
pts_sign=10 # Font size for signature |
# Font to use for file name when the non-latin mode (--nonlatin) is enabled |
# Option: -Ik=fontname |
nonlatin_font= # Autodetected (hopefully) |
###################### |
# Lower level tweaks # |
###################### |
# Decoder to use by default, can be either $DEC_FFMPEG (default) or |
# $DEC_MPLAYER |
# (options: -M for MPlayer, -F for FFMpeg) |
decoder=$DEC_FFMPEG |
# Output from commands, useful to locate errors |
stdout=/dev/null |
stderr=/dev/null |
# Verbosity level, very verbose by default. |
# Possible values: $V_ALL (default), $V_ERROR, $V_WARN, $V_INFO, $V_NONE |
# (options: -q for quietness) |
verbosity=$V_ALL |
# 1 disables colours in console output |
simple_feedback=0 |
debug=0 # When 1, enables debugging mode (option -D) |
getopt=getopt # GNU Getopt executable name |
# $Rev$ # |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/video-contact-sheet/tags/1.12/Makefile |
---|
0,0 → 1,68 |
#!/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" |
tgz: vcs-$(VER).tar.gz |
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 \ |
PKGBUILD-$(VER) \ |
vcs-$(VER).gz vcs-$(VER).bz2 vcs-$(VER).bash \ |
CHANGELOG.gz CHANGELOG \ |
rpm deb |
PKGBUILD-$(VER): vcs-$(VER).tar.gz |
cd pkg && ln -s ../vcs-$(VER).tar.gz ./ |
cd pkg && make PKGBUILD |
$(RM) pkg/vcs-$(VER).tar.gz |
mv pkg/PKGBUILD $@ |
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/tags/1.12/vcs |
---|
0,0 → 1,0 |
link pkg/vcs |
Property changes: |
Added: svn:special |
+* |
\ No newline at end of property |
/video-contact-sheet/tags/1.12/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.12 |
---|
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/branches/1.12:r409-411 |
Merged /video-contact-sheet/tags/0.99a:r261 |
Merged /video-contact-sheet/branches/1.11.2:r393-406 |
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.3b:r276-277 |
Merged /video-contact-sheet/branches/1.0.2b:r270-271 |
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.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/tags/1.11.2/pkg/vcs |
---|
0,0 → 1,4091 |
#!/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: |
# * Added Cook and Sipro (RealAudio 4, 5 & 6) codecs |
# * BUGFIXES: |
# - Remove extra, empty, temporary dir |
# - Use standard awk syntax for exponentiation (pyth_th) |
# - Workaround for systems that don't register fonts with ImageMagick |
# * DEBUG: Print to stderr when probbing with mplayer too |
# 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" ;; |
# 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" ;; |
# Other versions of R.A. listed at Wikipedia/RealAudio |
sipr) acodec="RealAudio SIPR" ;; # RA 4/5 |
cook) acodec="RealAudio Cook" ;; # RA 6 |
*) # 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>"$stderr" | grep ^ID) |
else |
MPLAYER_CACHE=$("$MPLAYER" -benchmark -ao null -vo null -identify -frames 0 \ |
-quiet -dvd-device $DVD_FILE dvd://$DVD_TITLE \ |
2>"$stderr" | 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 Sanitation: |
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/tags/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/tags/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/tags/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/tags/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/tags/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/tags/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/tags/1.11.2/pkg/debian/dirs |
---|
0,0 → 1,0 |
usr/bin |
/video-contact-sheet/tags/1.11.2/pkg/debian/compat |
---|
0,0 → 1,0 |
5 |
/video-contact-sheet/tags/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/tags/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/tags/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/tags/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/tags/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.2:r393-406 |
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.3b:r276-277 |
Merged /video-contact-sheet/branches/1.0.2b:r270-271 |
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.2pre2/pkg/vcs |
---|
0,0 → 1,4088 |
#!/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) |
# - Workaround for systems that don't register fonts with ImageMagick |
# * DEBUG: Print to stderr when probbing with mplayer too |
# 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>"$stderr" | grep ^ID) |
else |
MPLAYER_CACHE=$("$MPLAYER" -benchmark -ao null -vo null -identify -frames 0 \ |
-quiet -dvd-device $DVD_FILE dvd://$DVD_TITLE \ |
2>"$stderr" | 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/tags/1.11.2pre2/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/tags/1.11.2pre2/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/tags/1.11.2pre2/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/tags/1.11.2pre2/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/tags/1.11.2pre2/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/tags/1.11.2pre2/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/tags/1.11.2pre2/pkg/debian/dirs |
---|
0,0 → 1,0 |
usr/bin |
/video-contact-sheet/tags/1.11.2pre2/pkg/debian/compat |
---|
0,0 → 1,0 |
5 |
/video-contact-sheet/tags/1.11.2pre2/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/tags/1.11.2pre2/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/tags/1.11.2pre2/vcs |
---|
0,0 → 1,0 |
link pkg/vcs |
Property changes: |
Added: svn:special |
+* |
\ No newline at end of property |
/video-contact-sheet/tags/1.11.2pre2/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/tags/1.11.2pre2/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.2pre2 |
---|
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.2pre1/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/tags/1.11.2pre1/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/tags/1.11.2pre1/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/tags/1.11.2pre1/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/tags/1.11.2pre1/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/tags/1.11.2pre1/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/tags/1.11.2pre1/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/tags/1.11.2pre1/pkg/debian/dirs |
---|
0,0 → 1,0 |
usr/bin |
/video-contact-sheet/tags/1.11.2pre1/pkg/debian/compat |
---|
0,0 → 1,0 |
5 |
/video-contact-sheet/tags/1.11.2pre1/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/tags/1.11.2pre1/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/tags/1.11.2pre1/vcs |
---|
0,0 → 1,0 |
link pkg/vcs |
Property changes: |
Added: svn:special |
+* |
\ No newline at end of property |
/video-contact-sheet/tags/1.11.2pre1/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/tags/1.11.2pre1/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.2pre1 |
---|
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 |