#!/usr/bin/perl -w # timed copy program - copies from stdin to stdout, for given amount of time # use for capturing video output, such as with the ivtv drivers # ------ # timing is not exact - could be a second or so off. # timing depends on precision of alarm() call, and if # non-default very large buffers are specified, there may also be # additonal few seconds delay, in pre-allocating buffer at startup # ------ # Version 1.30: Oct 2006: # By default, uses alarm(), but there is a bug Time::HiRes, which # did not handle alarm() if total in microseconds > 2^32. To get around # this, created the --clock option which does not use alarm, but instead # keeps looking at wall clock elapsed time. Later this was eventually # discovered to be a Time::HiRes issue, am now using the working # alarm() from the default library, and not including Time::HiRes # Time::HiRes Version 1.86 (tested Oct 2006) issue: # 71.666 minutes == 4300 seconds becomes 4300*10^6 % 2^32 == 5.035 secs # So, --clock should never be needed, now that Time::HiRes is not used. # --clock keeps checking time after each read() call, and will exit when # amount of time (seconds) is more than specified time. # ------ # Version 1.35: Oct 2006: # added code to continue to try to read on EBUSY: "Device or resource busy" # error, which may occur in back-to-back scheduled commands. # added printout of start and end times, using Date::Format on -v flag #---------------------------------------------------------------------- # ivtv page had a simple script, this is a extension to that script: # http://ivtvdriver.org/index.php/Example_script_to_schedule_recordings # use the at or crontab entries to drive this, for example, to record a # program for 30 minutes starting at 1:30AM on Sep 12, using at: # at -v 1:30AM Sep 12, and enter the command: # timed-copy -v -m 30 < /dev/video0 > 2006-09-12-0100.mpg # Can run a script with at, to setup codec, tune to channel, as needed. #---------------------------------------------------------------------- use warnings; use strict; my $version = "1.38"; my $kilo = 1024; my $mega = $kilo * $kilo; my $readSize = $kilo*512; # default buffer size for read/write my $minutes = 30; # default time to run, if no argument provided my $verbose = 0; # verbosity level - 0 no messages, 1 some, 2 more. my $busySleep = 1; # number of seconds to sleep and try again if can't read from input device, usually needed on startup of back-to-back runs my $useClock = 0; # if enabled: don't use alarm(), use wall clock to time script my $timeTmpl = "%Y-%m-%d %T"; # Date::Time time2str template for printing my $usage = "Version $version, Usage: $0: copies from stdin to stdout, for given amount of time $0 [-v] [-b ] [-m ] < input > output --verbose or -v: be verbose, use twice to get debug messages also: -v -v --buffer= or -b : size in bytes of the read/write buffer (default: $readSize) --minutes= or -m : run for this long (default: $minutes) --clock or -c : don't use alarm, but use wall clock to time program (default: $useClock)"; #---------------------------------------------------------------------- use Getopt::Long; # use Time::HiRes qw ( time alarm ); # Oct 2006: don't use has a problem in # alarm(), it can't handle the time if it requires over 32-bit arithmetic # when using microseconds, for example: # 71.666 minutes == 4300 seconds; alarms in 4300*10^6 % 2^32 == 5.035 secs # Time::HiRes Version 1.86 (tested Oct 2006) issue. use Errno qw(EINTR EAGAIN EBUSY); use Date::Format; unless ( GetOptions ( "verbose|v+" => \$verbose, "minutes|m=f" => \$minutes, "buffer|b=i" => \$readSize, "clock|c" => \$useClock, )) { die "*** Invalid arguments\n$usage\n"; } die "*** Error: Invalid or no time specified ($minutes)\n$usage\n" unless $minutes > 0; die "*** Error: Invalid buffer size ($readSize)\n$usage\n" unless $readSize > 0; #---------------------------------------------------------------------- my $timerExpired = 0; $SIG{'ALRM'} = sub { $timerExpired = 1; }; my $seconds = int($minutes * 60 + 0.999); # simple ceiling print STDERR "...will read using $readSize bytes buffer, for $seconds seconds\n" if ($verbose > 1); #---------------------------------------------------------------------- alarm($seconds) unless ($useClock); binmode(STDIN, ':raw'); binmode(STDOUT, ':raw'); my ($readTotal, $readCount, $writeCount, $writeTotal) = (0,0,0,0); my $buffer = 'a' x $readSize; # pre-allocate buffer my $t1 = time(); # script start time my $t2 = 0; # capture start time my $busySleepTotal = 0; print STDERR "Script Started At: ", time2str($timeTmpl, $t1), "\n" if ($verbose > 1); # first loop, wait until input device is ready - not busy # this may happen in back-to-back runs of this script while (1) { $t2 = time(); $readCount = sysread(STDIN, $buffer, $readSize); if (!defined $readCount) { if ($! == EAGAIN || $! == EBUSY) { print STDERR " --warning: capture delayed: $!. Will sleep and try again periodically.\n" if ($verbose && $busySleepTotal <= 0); # print message once only sleep($busySleep); $busySleepTotal += $busySleep; } } last if (defined $readCount); if ($useClock) { my $t3 = time(); last if (($t3 - $t1) > $seconds); } else { if ($timerExpired) { print STDERR " --debug: busy loop, timer expired.\n" if ($verbose > 1); last; } } } if ($verbose) { print STDERR " --warning: capture delayed by $busySleepTotal seconds, device was busy\n" if ($busySleepTotal > 0); print STDERR "Capture Started At: ", time2str($timeTmpl, $t2), "\n"; } # main capture loop while (1) { if (!defined $readCount) { if ($! == EINTR) { print STDERR " --debug: sysread interrupted, exiting: $!\n" if ($verbose > 1); } else { print STDERR " **error: sysread failed: $!\n" } last; } if ($readCount == 0) { # EOF print STDERR " --warning: sysread end-of-file reached\n" if ($verbose); last; } $readTotal += $readCount; $writeCount = syswrite(STDOUT, $buffer, $readCount); if (!defined $writeCount) { print STDERR " **error: syswrite failed: $!\n" unless ($! == EINTR); print STDERR " --debug: syswrite error-ed out, ok if interrupted: $!\n" if ($verbose > 1); last; } $writeTotal += $writeCount; if ($writeCount != $readCount) { print STDERR " --debug: syswrite wrote only $writeCount, needed to write $readCount bytes (last write, timer expired?)\n" if ($verbose > 1); } if ($useClock) { my $t3 = time(); last if (($t3 - $t1) > $seconds); } else { if ($timerExpired) { print STDERR " --debug: timer expired.\n" if ($verbose > 1); last; } } $readCount = sysread(STDIN, $buffer, $readSize); } my $t3 = time(); #---------------------------------------------------------------------- print STDERR "Capture Finished At: ", time2str($timeTmpl, $t3), "\n" if ($verbose); printf STDERR "...read %.0f, wrote %.0f bytes, ran for %d seconds (using %s)\n", $readTotal/1.0, $writeTotal/1.0, ($t3-$t1), $useClock?"wall clock timer":"alarm timer" if ($verbose > 1); printf STDERR "...read %.2f, wrote %.2f MBytes, ran for %.2f minutes (using %s)\n", $readTotal/$mega, $writeTotal/$mega, ($t3-$t1)/60, $useClock?"wall clock timer":"alarm timer" if ($verbose); #----------------------------------------------------------------------