#! /usr/bin/perl -w

# Author: Sébastien Blondeel <sbi@april.org>
# License: GPL version 2

use strict;
use Data::Dumper;
undef $/;
my $DEBUG = 0;

my $MEDIA  = "/dev/null";

$MEDIA = $ENV{"MEDIA_FILE"} if defined $ENV{"MEDIA_FILE"};

my $CONFIG_FILE  = "./.Play_config";
my $MESSAGE_FILE = "./.Play_message";
my $COMMAND      = shift;
my $INPUT        = <>;

my $PLAY_COMMAND    = "mplayer -geometry 100%:0";
my $MESSAGE_COMMAND = "xmessage -geometry -0-0";

die "Unknown command ``$COMMAND''\n" unless $COMMAND =~ m{^(?:
  GotoPreviousInterval
| PlayCurrentInterval
| PlayNextInterval
| GotoNextInterval
| ShortenIntervalLength
| ShortenOverlapLength
| LengthenOverlapLength
| LengthenIntervalLength
| ReduceSpeed
| AccelerateSpeed
| ForwardTwoSeconds
| MarkUpOffset
)$}sx;

&my_system("killall mplayer > /dev/null 2> /dev/null");

my $H = {};

sub file_read {
  my $filename = shift;
  local *FILE;
  local undef $/;
  my $res;
  open (FILE, $filename) || die("Cannot open $filename for reading: $!\n");
  $res = <FILE>;
  close (FILE);
  return $res;
}

sub file_write {
  my ($filename, $content) = @_;
  local *FILE;
  open(FILE, "> $filename") || die("Cannot open $filename for writing: $!\n");
  print FILE $content;
  close FILE;
}

sub parse_length {
  my $len = shift;
  if ($len =~ m{^(\d+)m:(\d+\.\d+)s$}s) {
    my ($m, $s) = ($1, $2);
    return ( ( 60*$m) + $s );
  } else {
    die "parse_length: cannot parse length ``$len''\n";
  }
}

sub read_config {
  my $config = "";
  if (-e $CONFIG_FILE) {
    $config = &file_read($CONFIG_FILE); 
  } else {
    $config =  <DATA>;
  }

  while ($config =~ m{([^\n]+)}gs) {
    my $line = $1;
    next unless $line =~ m{\S}s;
    next if $line =~ m{\s*\#}s;
    if ($line =~ m{^\s*(\S+)\s+(\S+)\s*$}s) {
      $H->{$1} = $2;
    } else {
      die "Cannot parse line in configuration:\n$line\n";
    }
  }
  
  my $ogginfo;
  unless (defined $H->{"MediaRunningTime"} && defined $H->{"FrameRate"}) {
    if ($DEBUG) {
      print STDERR "Reading ogg info for media $MEDIA...\n";
      print STDERR "H=".Data::Dumper::Dumper($H);
    }
    $ogginfo = `ogginfo "$MEDIA"`
  }

  unless (defined $H->{"MediaRunningTime"}) {
    # Detecting media running time
    if ($ogginfo =~ m{ \n
Vorbis\x20stream\x20\d+:\n
\t Total \x20 data \x20 length: \x20 \d+ \x20 bytes\n
\t Playback\x20 length: \x20 (\S+) \n
      }sx) {
      $H->{"MediaRunningTime"} = $1;
      $H->{"TotalLength"} = &parse_length($H->{"MediaRunningTime"});
    } else {
      die "Cannot parse ogginfo total length for media $MEDIA:\n$ogginfo\n";
    }
  }

  unless (defined $H->{"FrameRate"}) {
    if ($ogginfo =~ m{\n
      Framerate \x20 \d+/\d+ \x20 \((\d+\.\d+) \x20 fps\)\n
    }sx) {
      $H->{"FrameRate"} = $1;
    } else { # Test it is an audio / a video file?
      # die "Cannot parse ogginfo frame rate for media $MEDIA:\n$ogginfo\n";
      $H->{"FrameRate"} = 0;
    }
  }

}

sub my_system {
  my $cmd = shift;
  print STDERR "SYSTEM: $cmd\n" if $DEBUG;
  system($cmd);
}

sub pretty {
  my $len = shift;
  my $res = "";
  if ($H->{"TotalLength"} >= 3_600) {
    my $hh = sprintf("%d", $len / 3_600);
    $res .= sprintf("%2d:", $hh);
    $len -= 3_600 * $hh;
  }
  if ($H->{"TotalLength"} >= 60) {
    my $mm = sprintf("%d", $len / 60);
    $res .= sprintf("%02d:", $mm);
    $len -= 60 * $mm;
  }
  my $int = int($len);
  $res .= sprintf("%02d", $int);
  $len -= $int;
  if ($len) {
    my $rest = sprintf("%.2f", $len);
    $rest =~ s{^\d+\.}{.}s;
    $res .= $rest;
  }
  return $res;
}

sub write_config {
  my $config = "# Configuration as of ".scalar(localtime(time()))."\n";
  foreach my $key (sort keys %{$H}) {
    $config .= "$key\t$H->{$key}\n";
  }
  &file_write($CONFIG_FILE, $config);
}

&read_config();
my $start = $H->{"IntervalStart"};
my $len   = $H->{"IntervalLength"};
my $LEN   = $H->{"TotalLength"};
my $ovl   = $H->{"IntervalOverlap"};
my $N     = $H->{"TotalLength"} / $len;
my $fps   = $H->{"FrameRate"};
my $speed = $H->{"Speed"};
my $play_command = $PLAY_COMMAND;
my $message = "";
my ($end, $frames);
my $play_flag = 0;

if (0) {
} elsif ($COMMAND eq "GotoPreviousInterval") {
  $start -= $len;
  $start = 0 if $start < 0;
  $H->{"IntervalStart"} = $start;
  $play_flag = 1;
} elsif ($COMMAND eq "PlayCurrentInterval") {
  $play_flag = 1;
} elsif ($COMMAND eq "PlayNextInterval") {
  $start += $len - $ovl;
  $start = int($LEN) - $len if $start >= $LEN;
  $play_flag = 1;
} elsif ($COMMAND eq "GotoNextInterval") {
  $start += $len - $ovl;
  $start = int($LEN) - $len if $start >= $LEN;
  $H->{"IntervalStart"} = $start;
  $play_flag = 1;
} elsif ($COMMAND eq "ShortenIntervalLength") {
  $len--;
  $len = 1 if $len <= 0;
  $ovl = $len if $ovl > $len;
} elsif ($COMMAND eq "ShortenOverlapLength") {
  $ovl--;
  $ovl = 1 if $ovl <= 0;
} elsif ($COMMAND eq "LengthenOverlapLength") {
  $ovl++;
  $ovl = $len if $ovl > $len;
} elsif ($COMMAND eq "LengthenIntervalLength") {
  $len++;
  $len = 1 if $len > $LEN;
} elsif ($COMMAND eq "ReduceSpeed") {
  $speed /= 1.05;
  $speed = .5 if $speed < .5;
  $speed = 1.0 if abs(1.0-$speed) < .03;
} elsif ($COMMAND eq "AccelerateSpeed") {
  $speed *= 1.05;
  $speed = .5 if $speed > 2;
  $speed = 1.0 if abs(1.0-$speed) < .03;
} elsif ($COMMAND eq "ForwardTwoSeconds") {
  $start += 2;
  $start = int($LEN) - $len if $start >= $LEN;
  $H->{"IntervalStart"} = $start;
  $play_flag = 1;
} elsif ($COMMAND eq "MarkUpOffset") {
  my $offset = &pretty($start);
  print "[[$offset]]\n\n";
} else {
  die "Unknown command $COMMAND\n";
}

print $INPUT;

$H->{"IntervalLength"}  = $len;
$H->{"IntervalOverlap"} = $ovl;
$H->{"Speed"}           = $speed;

if ($play_flag) {
  $end           = $start + $len;
  $end           = $LEN if $end > $LEN;
  $frames        = $fps * ( $end - $start );
  if ($frames > 0) {
    $play_command .= " -ss $start -frames $frames";
  } else { # not a video?
    $play_command .= " -ss $start -endpos $len";
  }

  if ($speed != 1) {

    # Needs the package tap-plugins
    # Uses http://mark.santaniello.net/archives/260

    my $pitchshift = -12*log($speed)/log(2);
    $play_command = "LD_LIBRARY_PATH=/usr/lib/ladspa ".$play_command;
    $play_command .= " -speed $speed -af ladspa=tap_pitch:tap_pitch:$pitchshift:0:-90:0";
    
  }

} else {
  $end           = $start;
  $play_command  = "";
  $frames        = 0;
}

my $Start = &pretty($start);
my $End   = &pretty($end);
my $Len   = &pretty($H->{'TotalLength'});
my $ILen  = $H->{'IntervalLength'};
my $Spd   = sprintf("%.02f", $H->{"Speed"});
my $IOvl  = sprintf("%".(length($ILen))."d", $H->{'IntervalOverlap'});

$message .= << "EOMESSAGE";
   Starting time: $Start
     Ending time: $End
    Running time: $Len
           Speed: $Spd
   Frames played: $frames
 Interval length: $ILen
Interval overlap: $IOvl

F1 Go to previous interval
F2 (Re)play current interval
F3 Play next interval
F4 Go to next interval

F5 Shorten interval length (-1 sec.)
F6 Shorten overlap length (-1 sec.)
F7 Lengthen overlap length (+1 sec.)
F8 Lengthen interval length (+1 sec.)

F9  Reduce speed (-5%; min .5)
F10 Accelerate (+5%; max 2)
F11 Advance by two seconds
F12 Markup current position
EOMESSAGE

sub dimensions {
  my $txt = shift;
  my ($w, $h) = (0, 0);
  while ($txt =~ m{([^\n]*)\n?}gs) {
    $w = length($1) if length($1) > $w;
    $h ++;
  }
  return ($w, $h);
}

my ($w, $h) = &dimensions($message);
my $sleep_time = $H->{'IntervalLength'} + 5;

&file_write($MESSAGE_FILE, $message);
# &my_system("killall xmessage; $MESSAGE_COMMAND -file $MESSAGE_FILE \&");
&my_system("xterm +sb -fn 10x20 -fg black -bg grey ".
   "-geometry $w"."x$h-0-0 -e 'cat $MESSAGE_FILE; sleep $sleep_time' \&");
&my_system("$play_command $MEDIA > /dev/null 2> /dev/null \&")
  if length $play_command;

&write_config();

# Include the following lines in the configuration of VIM
my $VIM_CONFIGURATION = << 'EOCONFIGURATION';
:map <F1> !!perl ~/bin/Play.pl GotoPreviousInterval<CR>
:map <F2> !!perl ~/bin/Play.pl PlayCurrentInterval<CR>
:map <F3> !!perl ~/bin/Play.pl PlayNextInterval<CR>A
:map <F4> !!perl ~/bin/Play.pl GotoNextInterval<CR>A
:map <F5> !!perl ~/bin/Play.pl ShortenIntervalLength<CR>
:map <F6> !!perl ~/bin/Play.pl ShortenOverlapLength<CR>
:map <F7> !!perl ~/bin/Play.pl LengthenOverlapLength<CR>
:map <F8> !!perl ~/bin/Play.pl LengthenIntervalLength<CR>
:map <F9> !!perl ~/bin/Play.pl ReduceSpeed<CR>
:map <F10> !!perl ~/bin/Play.pl AccelerateSpeed<CR>
:map <F11> !!perl ~/bin/Play.pl ForwardTwoSeconds<CR>
:map <F12> !!perl ~/bin/Play.pl MarkUpOffset<CR>
EOCONFIGURATION

__DATA__
# Default values
IntervalLength      8
IntervalOverlap     2
Speed               1
IntervalStart       0
LastIntervalNumber  1
