diff --git a/teetime/teetime b/teetime/teetime index 331f441..dad3481 100755 --- a/teetime/teetime +++ b/teetime/teetime @@ -44,7 +44,7 @@ Play back I times faster. 1.0 = actual speed. =item B<-i> -Read I as input. +Read I as input. Use - to read from standard input. =item B<--maxwait> I @@ -53,19 +53,39 @@ Read I as input. Wait at most I seconds. - - =back + =head1 EXAMPLES - (sleep 0.5; echo After 0.5s; sleep 1.5; echo After 2s) | teetime myfile - (sleep 0.5; echo After 2.5s; sleep 1.5; echo After 4s) | teetime -a myfile - teetime -i myfile + (sleep 0.5; echo After 0.5s; sleep 1.5; echo After 2s) | + teetime myfile.tt + (sleep 0.5; echo After 2.5s; sleep 1.5; echo After 4s) | + teetime -a myfile.tt + teetime -i myfile.tt + +Play file using stdin: + + cat myfile.tt | teetime -i - + +Play it faster: + + teetime -f 2 -i myfile.tt + +=head1 File format + +The .tt-format is simply: + + Version: "TTM1" + Repeat: ( + Wait ms: Unsigned 32-bit + Length of string: Unsigned 32-bit + String: string + ) =head1 AUTHOR -Copyright (C) 2020 Ole Tange, +Copyright (C) 2020-2023 Ole Tange, http://ole.tange.dk and Free Software Foundation, Inc. @@ -99,16 +119,39 @@ B =cut +sub bashtest1 { + q{ + rand -s 8 | head -c 1G | teetime - |mbuffer -q | teetime -i - | pv | md5sum + rand -s 8 | head -c 1G | pv | md5sum + }; +} + +sub bashtest2 { + q{ + doit() { + diff <(rand -s $1 | + head -c$1 | + md5sum) <(rand -s $1 | + teetime - | + teetime -i - | + head -c$1 | + md5sum); + } + export -f doit + find-first-fail -v doit + }; +} + use strict; use Getopt::Long; sub now { - # Returns time since epoch as in seconds with 3 decimals + # Returns time since epoch in ms # Uses: # @Global::use # Returns: - # $time = time now with millisecond accuracy + # $time = in milliseconds if(not $Global::use{"Time::HiRes"}) { if(eval "use Time::HiRes qw ( time );") { eval "sub TimeHiRestime { return Time::HiRes::time };"; @@ -122,22 +165,34 @@ sub now { } sub readstdin { + # Read stdin and save it in .tt-format + # If no file: save to stdout, but do not tee output my $file = shift; - my $last_time = now(); + my $last_time = now(); my $rin = ''; my $in; - my $twogb = 2**31; my ($read, $time, $delta); - open(my $fh, ($opt::append ? ">>" : ">"), $file) || die; - + my $fh; + my $save_to_stdout; + if(not defined $file or $file eq "-") { + $save_to_stdout = 1; + } + if($save_to_stdout) { + $fh = *STDOUT; + $| = 1; + } else { + open($fh, ($opt::append ? ">>" : ">"), $file) || die; + } vec($rin, fileno(STDIN), 1) = 1; + # print FourCC file identifier + print $fh "TTM1"; while(1) { select($rin,undef,undef,undef); - $read = sysread(STDIN,$in,$twogb); + $read = sysread(STDIN,$in,1000000); $time = now(); $delta = $time - $last_time; print $fh pack("L*",$delta,length $in),$in; - print STDOUT $in; + if(not $save_to_stdout) { print STDOUT $in; } $last_time = $time; if(not $read) { # Select says there is something to read, @@ -162,17 +217,39 @@ sub min(@) { } sub readfile { + # Input is in .tt-format my $file = shift; - open(my $fh, "<", $file) || die; + my $fh; + if($file eq "-") { + $fh = *STDIN; + } else { + open($fh, "<", $file) || die; + } + my $fileformat; + if(sysread($fh,$fileformat,4)) { + if($fileformat eq "TTM1") { + read_ttm1($fh); + } else { + error("Unsupported file format: $fileformat"); + } + } +} + +sub read_ttm1 { + my $fh = shift; + my $in; + my $nread; while(1) { - my $in; - if(sysread($fh,$in,8)) { + if($nread = sysread($fh,$in,8)) { + if($nread != 8) { die; } # time in ms, length in bytes my ($delta,$length) = unpack("L*",$in); $delta = min($delta/$opt::factor,$opt::maxwait*1000); select(undef,undef,undef,$delta/1000); - sysread($fh,$in,$length); - print $in; + while($nread = sysread($fh,$in,$length)) { + $length -= $nread; + print $in; + } } else { # Blocking sysread says there is something read, # but there is not => eof @@ -206,7 +283,7 @@ sub help() { "teetime [-m max] [-f factor] -i file", "", "-a append to file", - "-f playback speed", + "-f playback speed factor", "-i read from file", "-m max wait seconds", "", @@ -214,6 +291,19 @@ sub help() { "",); } +sub error(@) { + my @w = @_; + my $prog = $Global::progname || "teetime"; + status(map { ($prog.": Error: ". $_); } @w); +} + +sub status(@) { + my @w = @_; + my $fh = *STDERR; + print $fh map { ($_, "\n") } @w; + flush $fh; +} + sub debug { $opt::D or return; @_ = grep { defined $_ ? $_ : "" } @_; @@ -252,5 +342,3 @@ if(GetOptions("debug|D=s" => \$opt::D, help(); exit(1); } - -