#!/usr/bin/perl -w

use POSIX;
use File::Copy;

if($#ARGV!=1) {
  print "Usage: perl resync_subs.pl <frame reference> <dir>\n";
  exit;
}
my $frame_ref=uc($ARGV[0]);
my $sourcedir=$ARGV[1];
my $destdir=$sourcedir."-resync-".$frame_ref;
my $source_format="xml";
my $filmname;
my $framerate="23.976";
my $speed=1;
my %adjhash=();
my $last_adj="0:00:00,000";

# adjustment strings are a hash-delimited list of adjustments, in the format timecode1=offset1#timecode2=offset2...
# an optional SPEED timecode specifies an overall speed adjustment (<1=faster, >1=slower) on all subsequent adjustments
# timecodes are the point in the NTSC GOUT at which the difference occurs
# offsets indicate how many seconds of video are dropped (-) or added (+) in the other frame reference
# the PALGOUT differences aren't significant enough to warrant differently-timed subtitles
# but they do serve to illustrate how the adjustment string should be formatted, so they're included
$adjhash{'PALGOUT-SW'}='1:40:08.258=+0.042';
$adjhash{'PALGOUT-ESB'}='1:44:24.765=+0.083';
$adjhash{'PALGOUT-ROTJ'}='47:43.864=+0.083#1:38:33,413=-0.042';
$adjhash{'STEREO2MONO-SW'}='16:58=+0.57#17:02=-0.57#22:47.450=+0.85#22:49=-0.475#22:50.05=-0.375#42:40=-0.938#42:44=+0.938#42:48.2=-0.569#42:50=+0.569#42:53=-0.6#42:56=+0.6#42:58=-0.6#43:01.15=+0.6#43:03.5=-0.6#43:05=+0.6#43:07=-0.6#43:11=+0.6';
$adjhash{'STEREO2MONO-ESB'}='';
$adjhash{'STEREO2MONO-ROTJ'}='';
$adjhash{'PUGGO-SW'}='0=-1.3#35=+1.8#2:00=-6.9#38:50=+4.3#40:50=-0.3#1:09:08=-0.3#1:19:00=+5.9#1:46:15=-0.2#1:52:40=-0.2';
$adjhash{'PUGGO-ESB'}='0=+2:23.900#27=+0.2#40=+2.0#1:35=-1.8#5:00=-0.3#41:30=+4#1:06:02=-0.2#1:13:00=+0.2#1:24:36=+4.3#1:36:00=-0.3';
$adjhash{'PUGGO-ROTJ'}='';
$adjhash{'NEG1-SW'}='0=+42.099#29:36.485=-.501#31:23.258=-.167#38:45.075=-.209#38:59.214=-.042#39:34.291=-.042#40:38.939=-.167#42:09.655=-.042#45:01.493=-.042#49:00.691=-.083#54:40.948=-.042#58:00.48=-.167#1:00:14.364=-2.419#1:19:00.115=-.083#1:19:00.24=-.167#1:40:08.133=-.083#1:40:08.258=-.125';
$adjhash{'NEG1-ESB'}='0=+47.3#28=+0.2#40=+1.5#2:00=-1.1#4:30=-0.3#21:46=-0.3#42:00=-0.5#1:00:40=-0.2#1:05:20=-0.3#1:24:40=-1#1:44:16=-0.3#1:54:48=-0.2';
$adjhash{'NEG1-ROTJ'}='';

if(!exists($adjhash{$frame_ref."-SW"}) && !exists($adjhash{$frame_ref."-ESB"}) && !exists($adjhash{$frame_ref."-ROTJ"})) {
  print "Error: Unknown frame reference: $frame_ref\n";
  exit;
}

if($^O eq "MSWin32") {
  @filelist=glob("\"$sourcedir\\\*.xml\"");
} else {
  @filelist=glob("\"$sourcedir/\*.xml\"");
}
my $listsize=@filelist;
if($listsize==0) {
  $source_format="srt";
  if($^O eq "MSWin32") {
    @filelist=glob("\"$sourcedir\\\*.srt\"");
  } else {
    @filelist=glob("\"$sourcedir/\*.srt\"");
  }
  $listsize=@filelist;
  if($listsize==0) {
    print "Error: No SRT or XML files found in $sourcedir\n";
    exit;
  }
}

sub round {
  $_[0] > 0 ? int($_[0] + .5) : -int(-$_[0] + .5)
}

sub srttime2msec {
  my $t=$_[0];
  my $ms=0;
  my @arr=split(/:/,$t);
  $arr[-1]=~s/,/./g;
  $ms=round($arr[-1]*1000);
  if($#arr>0) {
    $ms=$ms+$arr[-2]*60000;
  }
  if($#arr>1) {
    $ms=$ms+$arr[-3]*3600000;
  }
  return $ms;
}

sub xml2srt {
  my $tc=$_[0];
  my $msecs=0;
  my @arr=split(/:/,$tc);
  $msecs=round(1000*$arr[-1]/round($framerate));
  $msecs=$msecs+$arr[-2]*1000;
  $msecs=$msecs+$arr[-3]*60000;
  $msecs=$msecs+$arr[-4]*3600000;
  $tc=round($msecs*round($framerate)/$framerate);
  my $hours=floor($tc/3600000);
  my $mins=floor(($tc-($hours*3600000))/60000);
  my $secs=floor(($tc-($hours*3600000)-($mins*60000))/1000);
  $msecs=$tc-($hours*3600000)-($mins*60000)-($secs*1000);
  $tc=sprintf("%02s:%02s:%02s,%03s",$hours,$mins,$secs,$msecs);
  return $tc;
}

sub srt2xml {
  my $tc=$_[0];
  my $msecs=srttime2msec($tc);
  $tc=round($msecs*$framerate/round($framerate));
  my $hours=floor($tc/3600000);
  my $mins=floor(($tc-($hours*3600000))/60000);
  my $secs=floor(($tc-($hours*3600000)-($mins*60000))/1000);
  $msecs=round((($tc-($hours*3600000)-($mins*60000)-($secs*1000))*round($framerate))/1000);
  $tc=sprintf("%02s:%02s:%02s:%02s",$hours,$mins,$secs,$msecs);
  return $tc;
}

sub timecode_greater_or_equal {
  my $tc1=$_[0];
  my $tc2=$_[1];
  $tc1=srttime2msec($tc1);
  $tc2=srttime2msec($tc2);
  if($tc1>=$tc2) {
    return 1;
  }
  return 0;
}

sub add_timecode {
  my $tc1=$_[0];
  my $tc2=$_[1];
  my $speed_adj=$_[2];
  my $subtr=0;
  my $negative=0;
  my @arr=split(//,$tc1);
  if($arr[0] eq '-') {
    $negative=1;
    splice(@arr,0,1);
    $tc1=join('',@arr);
  }
  $tc1=round($speed_adj*srttime2msec($tc1));
  if($negative==1) {
    $tc1=0-$tc1;
  }
  @arr=split(//,$tc2);
  if($arr[0] eq '+') {
    splice(@arr,0,1);
    $tc2=join('',@arr);
  }
  if($arr[0] eq '-') {
    splice(@arr,0,1);
    $tc2=join('',@arr);
    $subtr=1;
  }
  $tc2=round($speed_adj*srttime2msec($tc2));
  if($subtr==1) {
    $tc1=$tc1-$tc2;
  } else {
    $tc1=$tc1+$tc2;
  }
  $negative=0;
  if($tc1<0) {
    $negative=1;
    $tc1=0-$tc1;
  }
  my $hours=floor($tc1/3600000);
  my $mins=floor(($tc1-($hours*3600000))/60000);
  my $secs=floor(($tc1-($hours*3600000)-($mins*60000))/1000);
  my $msecs=$tc1-($hours*3600000)-($mins*60000)-($secs*1000);
  if($negative==1) {
    $tc2=sprintf("-%02s:%02s:%02s,%03s",$hours,$mins,$secs,$msecs);
  } else {
    $tc2=sprintf("%02s:%02s:%02s,%03s",$hours,$mins,$secs,$msecs);
  }
  return $tc2;
}

sub apply_diff {
  my $tc=$_[0];
  my $diff=$_[1];
  my @all_adj=split(/#/,$diff);
  my $adjustment="0";
  for $current_adj (@all_adj) {
    my @arr=split(/=/,$current_adj);
    if(uc($arr[0]) eq "SPEED") {
      $speed=$arr[1];
    } else {
      if(timecode_greater_or_equal($tc,$arr[0])) {
        $adjustment=add_timecode($adjustment,$arr[1],1);
      }
    }
  }
  $tc=add_timecode($tc,$adjustment,$speed);
  return $tc;
}

sub adjust_timecode {
  my $ref=$_[0];
  my $film=$_[1];
  my $timecode=$_[2];
  my $fmt=$_[3];
  my @timearr=split(/:/,$timecode);
  my $diff_string=$adjhash{$ref."-".$film};
  if(length($diff_string)==0) {
    return $timecode;
  }
  if($fmt eq "xml") {
    $timecode=xml2srt($timecode);
  }
  $timecode=apply_diff($timecode,$diff_string);
  $last_adj=$timecode;
  if($fmt eq "xml") {
    $timecode=srt2xml($timecode);
  }
  return $timecode;
}

mkdir $destdir;

if($source_format eq "xml") {
  print "Copying PNG files...\n";
  my @pnglist=();
  if($^O eq "MSWin32") {
    @pnglist=glob("\"$sourcedir\\\*.png\"");
  } else {
    @pnglist=glob("\"$sourcedir/\*.png\"");
  }
  for $pngfile (@pnglist) {
    my @arr;
    if($^O eq "MSWin32") {
      @arr=split(/\\/,$pngfile);
    } else {
      @arr=split(/\//,$pngfile);
    }
    my $fname=$arr[-1];
    if($^O eq "MSWin32") {
      copy($sourcedir."\\".$fname,$destdir."\\".$fname);
    } else {
      copy($sourcedir."/".$fname,$destdir."/".$fname);
    }
  }
}

FILELOOP: for $sourcefile (@filelist) {
  $last_adj="0:00:00,000";
  my $prev_adj="0:00:00,000";
  my @arr;
  $speed=1;
  if($^O eq "MSWin32") {
    @arr=split(/\\/,$sourcefile);
  } else {
    @arr=split(/\//,$sourcefile);
  }
  my $fname=$arr[-1];
  print "Processing ".$fname."...\n";
  open(OLDFILE, "<".$sourcefile);
  @arr=split(/-/,$fname);
  my $filmname=$arr[0];
  if(!exists($adjhash{$frame_ref."-".$filmname})) {
    next;
  }
  my $in_timecode="";
  my $out_timecode="";
  my $token1="";
  my $token2="";
  if($^O eq "MSWin32") {
    open(NEWFILE, '>' . $destdir . '\\' . $fname);
  } else {
    open(NEWFILE, '>' . $destdir . '/' . $fname);
  }
  foreach my $line (<OLDFILE>) {
    chomp($line);
    if($source_format eq "srt") {
      if (index($line, ' --> ') != -1) {
        @arr=split(/ --> /,$line);
        $in_timecode=$arr[0];
        $out_timecode=$arr[1];
        $prev_adj=$last_adj;
        $token1=adjust_timecode($frame_ref,$filmname,$in_timecode,$source_format);
        $token2=adjust_timecode($frame_ref,$filmname,$out_timecode,$source_format);
        if(timecode_greater_or_equal($prev_adj,$token1)) {
# we warn for this, the problem is either in the SRT file or the adjustment string
# either way, it should be fixed manually
          print "WARNING: Overlapping timecode at $token1\n";
        }
        $line=~s/$in_timecode/$token1/;
        $line=~s/$out_timecode/$token2/;
      }
    }
    if($source_format eq "xml") {
      if (index($line, '<Format ') != -1) {
        if (index($line, "FrameRate=") != -1) {
          @arr=split(/FrameRate="/,$line);
          $token1=$arr[1];
          @arr=split(/"/,$token1);
          $framerate=$arr[0];
        }
      }
      if (index($line, '<Event ') != -1) {
        if (index($line, "InTC=") != -1) {
          @arr=split(/InTC="/,$line);
          $in_timecode=$arr[1];
          @arr=split(/"/,$in_timecode);
          $in_timecode=$arr[0];
        }
        if (index($line, "OutTC=") != -1) {
          @arr=split(/OutTC="/,$line);
          $out_timecode=$arr[1];
          @arr=split(/"/,$out_timecode);
          $out_timecode=$arr[0];
        }
        $prev_adj=$last_adj;
        $token1=adjust_timecode($frame_ref,$filmname,$in_timecode,$source_format);
        $token2=adjust_timecode($frame_ref,$filmname,$out_timecode,$source_format);
        if(timecode_greater_or_equal($prev_adj,xml2srt($token1))) {
# subtitles overlaps in xml files can be caused by rounding/precision issues
# if it can be fixed by shifting the subtitle by one frame, just fix it and don't warn
          $token1=srt2xml(add_timecode(xml2srt($token1),"+".(1/$framerate),$speed));
          if(timecode_greater_or_equal($prev_adj,xml2srt($token1))) {
            print "WARNING: Overlapping timecode at $token1\n";
          }
        }
        $line=~s/$in_timecode/$token1/;
        $line=~s/$out_timecode/$token2/;
      }
    }
    print NEWFILE $line."\n";
  }
  close(OLDFILE);
  close(NEWFILE);
}

print "Process complete. Adjusted subtitles can be found in $destdir.\n";
