You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

158 lines
4.2 KiB

#!/usr/bin/env perl
use strict;
use warnings;
use lib 'lib';
use utf8;
use Getopt::Long;
use Subtitle::Format::SSA;
use Subtitle::TimePoint;
my @points;
sub usage {
my ($msg) = @_;
print $msg, "\n" if $msg;
print <<USAGE;
Usage: ssa-retime <mode> [<options>] -i <file> [ -o <file>]
Modes are:
* framerate Recalculate timing for different fps
* shift Shift timing in given points
* drift Adjust timing of whole file by time points
Common options:
-h This help.
-i <file> Input file. (mandatory).
-o <file> Output file. Default: write to stdout.
-v Be verbose.
Specific options for 'framerate' mode:
-f <float> Source framerate. Default: 25.0 fps.
-F <float> Target framerate.
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
specified more than once. (see man for details)
Both args must be in form [+-][[h:]m:]s[.ms]
USAGE
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 ###################
$| = 1;
unless (@ARGV and $ARGV[0] =~ m{^(framerate|shift|drift)$}oi) {
usage("You should set mode by first arg");
}
my $mode = shift @ARGV;
my %opts = ( loglevel => 0, inrate => 25.0, amode => 'seq');
GetOptions(
'h|help' => \&usage,
'v|verbose+' => \$opts{loglevel},
'i|infile=s' => \$opts{infile},
'o|outfile=s' => \$opts{outfile},
'm|amode=s' => \$opts{amode},
'f|inrate=f' => \$opts{inrate},
'F|outrate=f' => \$opts{outrate},
'p|point=s' => \&add_point,
);
if ($mode eq 'framerate') {
$opts{outrate}
or die "You should set '-F' option in this mode\n";
($opts{inrate} != 0.0 and $opts{outrate} != 0.0)
or die "Framerate can't be zero\n";
} elsif ($mode eq 'shift') {
scalar(@points)
or die "You should specify at least one timepoint\n";
($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 {
usage();
}
$opts{infile}
or die "No input file\n";
my $ssa = Subtitle::Format::SSA->new(debug => !!$opts{loglevel});
unless ($ssa->from_file($opts{infile})) {
foreach my $line (@{ $ssa->{log} }) {
print $line, "\n";
}
exit 1;
}
if (@points) {
@points = sort { $a->time <=> $b->time } @points;
}
if ($mode eq 'framerate') {
my $mod = $opts{inrate} / $opts{outrate};
foreach my $e (@{ $ssa->events }) {
$e->t_start($e->t_start * $mod);
$e->t_start($e->t_end * $mod);
}
} elsif ($mode eq 'shift') {
if ($opts{amode} eq 'seq') {
while (my $p = shift @points) {
foreach my $e (@{ $ssa->events }) {
next if $p->time > $e->t_start;
$e->t_start($e->t_start + $p->shift);
$e->t_end ($e->t_end + $p->shift);
} # foreach
} # while
} elsif ($opts{amode} eq 'rst') {
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;
}
# add endpoint
...;
}
if ($opts{outfile}) {
print $ssa->to_file($opts{outfile});
} else {
print $ssa->to_string;
}
exit 0;