|
|
@ -7,6 +7,9 @@ use utf8; |
|
|
|
|
|
|
|
|
|
|
|
use Getopt::Long; |
|
|
|
use Getopt::Long; |
|
|
|
use Subtitle::Format::SSA; |
|
|
|
use Subtitle::Format::SSA; |
|
|
|
|
|
|
|
use Subtitle::TimePoint; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
my @points; |
|
|
|
|
|
|
|
|
|
|
|
sub usage { |
|
|
|
sub usage { |
|
|
|
my ($msg) = @_; |
|
|
|
my ($msg) = @_; |
|
|
@ -15,9 +18,9 @@ sub usage { |
|
|
|
Usage: ssa-retime <mode> [<options>] -i <file> [ -o <file>] |
|
|
|
Usage: ssa-retime <mode> [<options>] -i <file> [ -o <file>] |
|
|
|
|
|
|
|
|
|
|
|
Modes are: |
|
|
|
Modes are: |
|
|
|
* framerate Retime whole file from one fps to another. |
|
|
|
* framerate Recalculate timing for different fps |
|
|
|
* points Retime file specifying point(s) & time shift for it. |
|
|
|
* shift Shift timing in given points |
|
|
|
* shift Shift whole file or it's part for given time. |
|
|
|
* drift Adjust timing of whole file by time points |
|
|
|
|
|
|
|
|
|
|
|
Common options: |
|
|
|
Common options: |
|
|
|
-h This help. |
|
|
|
-h This help. |
|
|
@ -25,83 +28,77 @@ Common options: |
|
|
|
-o <file> Output file. Default: write to stdout. |
|
|
|
-o <file> Output file. Default: write to stdout. |
|
|
|
-v Be verbose. |
|
|
|
-v Be verbose. |
|
|
|
|
|
|
|
|
|
|
|
Specific options for 'shift' mode: |
|
|
|
|
|
|
|
-S <string> Retime only events with specified style. |
|
|
|
|
|
|
|
This option may be given more than once. |
|
|
|
|
|
|
|
-s <time> Start time of period, that will be changed. |
|
|
|
|
|
|
|
Default: first event. |
|
|
|
|
|
|
|
-e <time> End time of period, that will be changed. |
|
|
|
|
|
|
|
Default: the latest time found in file. |
|
|
|
|
|
|
|
-l <time> Duration of period above. This option require '-s' |
|
|
|
|
|
|
|
You may select either '-o' or '-e' at the same time. |
|
|
|
|
|
|
|
-t <time> Change time for this value. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Specific options for 'framerate' mode: |
|
|
|
Specific options for 'framerate' mode: |
|
|
|
-f <float> Source framerate. Default: 25.0 fps. |
|
|
|
-f <float> Source framerate. Default: 25.0 fps. |
|
|
|
-F <float> Target framerate. |
|
|
|
-F <float> Target framerate. |
|
|
|
|
|
|
|
|
|
|
|
Specific options for 'points' mode: |
|
|
|
Specific options for 'shift' mode: |
|
|
|
|
|
|
|
-m <mode> How to apply shift by points: |
|
|
|
|
|
|
|
* seq -- apply timeshifts one by one to end of file (default) |
|
|
|
|
|
|
|
* rst -- every next point overrides previous |
|
|
|
|
|
|
|
-p <time>/<time> Point of fixup & time shift in it. Option can be |
|
|
|
|
|
|
|
specified more than once. (see man for details) |
|
|
|
|
|
|
|
Both args must be in form [+-][[h:]m:]s[.ms] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Specific options for 'drift' mode: |
|
|
|
-p <time>/<time> Point of fixup & time shift in it. Option can be |
|
|
|
-p <time>/<time> Point of fixup & time shift in it. Option can be |
|
|
|
specified more than once. (see man for details) |
|
|
|
specified more than once. (see man for details) |
|
|
|
Both args must be in form of [-][[h:]m:]s[.ms]. |
|
|
|
Both args must be in form [+-][[h:]m:]s[.ms] |
|
|
|
USAGE |
|
|
|
USAGE |
|
|
|
exit 1; |
|
|
|
exit 1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sub add_point { |
|
|
|
|
|
|
|
my ($opt, $val) = @_; |
|
|
|
|
|
|
|
my $p = Subtitle::TimePoint->new; |
|
|
|
|
|
|
|
my $err = $p->parse($val); |
|
|
|
|
|
|
|
die "$err\n" if $err; |
|
|
|
|
|
|
|
push @points, $p; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
########### init ################### |
|
|
|
########### init ################### |
|
|
|
$| = 1; |
|
|
|
$| = 1; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
unless (@ARGV and $ARGV[0] =~ m{^(framerate|shift|drift)$}oi) { |
|
|
|
|
|
|
|
usage("You should set mode by first arg"); |
|
|
|
|
|
|
|
} |
|
|
|
my $mode = shift @ARGV; |
|
|
|
my $mode = shift @ARGV; |
|
|
|
|
|
|
|
|
|
|
|
my @points; |
|
|
|
my %opts = ( loglevel => 0, inrate => 25.0, amode => 'seq'); |
|
|
|
my %opts = ( loglevel => 0, inrate => 25.0 ); |
|
|
|
|
|
|
|
#GetOptions("qvhi:o:S:f:F:p:t:s:e:l:", \%opts) |
|
|
|
|
|
|
|
GetOptions( |
|
|
|
GetOptions( |
|
|
|
'h|help' => \&usage, |
|
|
|
'h|help' => \&usage, |
|
|
|
'v|verbose+' => \$opts{loglevel}, |
|
|
|
'v|verbose+' => \$opts{loglevel}, |
|
|
|
'i|infile=s' => \$opts{infile}, |
|
|
|
'i|infile=s' => \$opts{infile}, |
|
|
|
'o|outfile=s' => \$opts{outfile}, |
|
|
|
'o|outfile=s' => \$opts{outfile}, |
|
|
|
|
|
|
|
|
|
|
|
'S|style=s' => \$opts{style}, |
|
|
|
'm|amode=s' => \$opts{amode}, |
|
|
|
's|shiftstart=s'=> \$opts{shiftstart}, |
|
|
|
|
|
|
|
'e|shiftend=s' => \$opts{shiftend}, |
|
|
|
|
|
|
|
'l|shiftlen=f' => \$opts{shiftlen}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
't|shifttime=f' => \$opts{shifttime}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
'f|inrate=f' => \$opts{inrate}, |
|
|
|
'f|inrate=f' => \$opts{inrate}, |
|
|
|
'F|outrate=f' => \$opts{outrate}, |
|
|
|
'F|outrate=f' => \$opts{outrate}, |
|
|
|
|
|
|
|
|
|
|
|
'p|point' => \@points, |
|
|
|
'p|point=s' => \&add_point, |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
($opts{infile} and -f $opts{infile}) |
|
|
|
|
|
|
|
or usage("Input file not exists"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ($mode eq 'framerate') { |
|
|
|
if ($mode eq 'framerate') { |
|
|
|
$opts{outrate} |
|
|
|
$opts{outrate} |
|
|
|
or usage("You should set at least '-F' option in this mode"); |
|
|
|
or die "You should set '-F' option in this mode\n"; |
|
|
|
($opts{inrate} != 0.0 and $opts{outrate} != 0.0) |
|
|
|
($opts{inrate} != 0.0 and $opts{outrate} != 0.0) |
|
|
|
or usage("Framerate can't be zero"); |
|
|
|
or die "Framerate can't be zero\n"; |
|
|
|
} elsif ($mode eq 'shift') { |
|
|
|
} elsif ($mode eq 'shift') { |
|
|
|
$opts{shifttime} |
|
|
|
|
|
|
|
or usage("You should set '-t' option in this mode"); |
|
|
|
|
|
|
|
(not $opts{shiftstart} or $opts{shiftstart} > 0.0) |
|
|
|
|
|
|
|
or usage("Shift start must be positive time"); |
|
|
|
|
|
|
|
(not $opts{shiftend} or $opts{shiftend} > 0.0) |
|
|
|
|
|
|
|
or usage("Shift end must be positive time"); |
|
|
|
|
|
|
|
(not $opts{shiftlen} or $opts{shiftlen} > 0.0) |
|
|
|
|
|
|
|
or usage("Shift length must be positive time"); |
|
|
|
|
|
|
|
} elsif ($mode eq 'points') { |
|
|
|
|
|
|
|
scalar(@points) |
|
|
|
scalar(@points) |
|
|
|
or usage("You should specify at least one shift point"); |
|
|
|
or die "You should specify at least one timepoint\n"; |
|
|
|
foreach my $point (@points) { |
|
|
|
($opts{amode} eq 'seq' or $opts{amode} eq 'rst') |
|
|
|
... |
|
|
|
or die "Value of -m option should be 'seq' or 'rst'\n"; |
|
|
|
} |
|
|
|
} elsif ($mode eq 'drift') { |
|
|
|
|
|
|
|
scalar(@points) |
|
|
|
|
|
|
|
or die "You should specify at least one timepoint\n"; |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
usage("Incorrect mode: $mode (see help below)"); |
|
|
|
usage(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$opts{infile} |
|
|
|
|
|
|
|
or die "No input file\n"; |
|
|
|
|
|
|
|
|
|
|
|
my $ssa = Subtitle::Format::SSA->new(debug => !!$opts{loglevel}); |
|
|
|
my $ssa = Subtitle::Format::SSA->new(debug => !!$opts{loglevel}); |
|
|
|
unless ($ssa->from_file($opts{infile})) { |
|
|
|
unless ($ssa->from_file($opts{infile})) { |
|
|
|
foreach my $line (@{ $ssa->{log} }) { |
|
|
|
foreach my $line (@{ $ssa->{log} }) { |
|
|
@ -110,25 +107,45 @@ unless ($ssa->from_file($opts{infile})) { |
|
|
|
exit 1; |
|
|
|
exit 1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (@points) { |
|
|
|
|
|
|
|
@points = sort { $a->time <=> $b->time } @points; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if ($mode eq 'framerate') { |
|
|
|
if ($mode eq 'framerate') { |
|
|
|
my $mod = $opts{inrate} / $opts{outrate}; |
|
|
|
my $mod = $opts{inrate} / $opts{outrate}; |
|
|
|
foreach my $event (@{ $ssa->{events} }) { |
|
|
|
foreach my $e (@{ $ssa->events }) { |
|
|
|
$event->{start} *= $mod; |
|
|
|
$e->t_start($e->t_start * $mod); |
|
|
|
$event->{end} *= $mod; |
|
|
|
$e->t_start($e->t_end * $mod); |
|
|
|
} |
|
|
|
} |
|
|
|
} elsif ($mode eq 'shift') { |
|
|
|
} elsif ($mode eq 'shift') { |
|
|
|
my $style = $opts{style}; |
|
|
|
if ($opts{amode} eq 'seq') { |
|
|
|
my $start = $opts{shiftstart} // 0.0; |
|
|
|
while (my $p = shift @points) { |
|
|
|
my $end = $opts{shiftend} // (($opts{shiftlen}) ? $start + $opts{shiftend} : undef); |
|
|
|
foreach my $e (@{ $ssa->events }) { |
|
|
|
my $mod = $opts{shifttime}; |
|
|
|
next if $p->time > $e->t_start; |
|
|
|
foreach my $event (@{ $ssa->{events} }) { |
|
|
|
$e->t_start($e->t_start + $p->shift); |
|
|
|
next if ($style and $event->{style} ne $style); |
|
|
|
$e->t_end ($e->t_end + $p->shift); |
|
|
|
next if ($start and $event->{start} < $start); |
|
|
|
} # foreach |
|
|
|
next if ($end and $event->{end} > $end); |
|
|
|
} # while |
|
|
|
$event->{start} += $mod; |
|
|
|
} elsif ($opts{amode} eq 'rst') { |
|
|
|
$event->{end} += $mod; |
|
|
|
while (my $p = shift @points) { |
|
|
|
|
|
|
|
# use time of next point as upper limit for current |
|
|
|
|
|
|
|
my $end = @points ? $points[0]->time : 0; |
|
|
|
|
|
|
|
foreach my $e (@{ $ssa->events }) { |
|
|
|
|
|
|
|
next if $p->time > $e->t_start; # too early |
|
|
|
|
|
|
|
next if $end and $end <= $e->t_start; # too late |
|
|
|
|
|
|
|
$e->t_start($e->t_start + $p->shift); |
|
|
|
|
|
|
|
$e->t_end ($e->t_end + $p->shift); |
|
|
|
|
|
|
|
} # foreach |
|
|
|
|
|
|
|
} # while |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} elsif ($mode eq 'drift') { |
|
|
|
|
|
|
|
if ($points[0]->time >= 0.2) { |
|
|
|
|
|
|
|
# add starting point |
|
|
|
|
|
|
|
my $p = Subtitle::TimePoint->new; |
|
|
|
|
|
|
|
$p->parse('0/0'); |
|
|
|
|
|
|
|
unshift @points, $p; |
|
|
|
} |
|
|
|
} |
|
|
|
} elsif ($mode eq 'points') { |
|
|
|
# add endpoint |
|
|
|
...; |
|
|
|
...; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|