|
|
|
package Subtitle::Format::MSub;
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
use utf8;
|
|
|
|
|
|
|
|
use Subtitle::Event;
|
|
|
|
use Subtitle::Utils qw(:string round);
|
|
|
|
|
|
|
|
use base 'Subtitle::Format';
|
|
|
|
|
|
|
|
sub new {
|
|
|
|
my ($class, %args) = @_;
|
|
|
|
my $self = {
|
|
|
|
debug => $args{debug} || 0,
|
|
|
|
fps => $args{fps} || 25.0,
|
|
|
|
eol => $args{eol} || "\n",
|
|
|
|
events => [],
|
|
|
|
log => [],
|
|
|
|
};
|
|
|
|
|
|
|
|
return bless($self, $class);
|
|
|
|
}
|
|
|
|
|
|
|
|
sub new_event { return Subtitle::Event->new; }
|
|
|
|
|
|
|
|
sub parse {
|
|
|
|
my ($self, $lines) = @_;
|
|
|
|
my $linenum = 0;
|
|
|
|
my $event;
|
|
|
|
|
|
|
|
foreach my $line (@{ $lines }) {
|
|
|
|
$linenum++;
|
|
|
|
chomp_all($line);
|
|
|
|
trim($line);
|
|
|
|
next unless $line;
|
|
|
|
my ($start, $end, $rest) = ($line =~ m/{(\d+)}\s*{(\d+)}\s*(.+)/o);
|
|
|
|
# expected: garbage
|
|
|
|
unless ($start and $end and $rest) {
|
|
|
|
$self->log(warn => "Unrecognized line at $linenum: $line");
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
# expected: valid line
|
|
|
|
# special case - '{1}{1}XX.YY' line sets fps
|
|
|
|
if ($start == 1 and $end == 1) {
|
|
|
|
my ($fps) = ($rest =~ m/(\d+(?:\.\d+))/o);
|
|
|
|
unless ($fps) {
|
|
|
|
$self->log(error => "Expected fps at line $linenum, but found: $rest");
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
$self->log(debug => "Set fps to $fps and line $linenum");
|
|
|
|
$self->{fps} = $fps;
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
trim($rest);
|
|
|
|
$rest =~ s/\|/$self->{eol}/og;
|
|
|
|
my $event = $self->new_event;
|
|
|
|
$event->t_start($start / $self->{fps});
|
|
|
|
$event->t_end ($end / $self->{fps});
|
|
|
|
$event->text($rest);
|
|
|
|
push @{ $self->{events} }, $event;
|
|
|
|
undef $event;
|
|
|
|
}
|
|
|
|
|
|
|
|
return scalar @{ $self->{events} };
|
|
|
|
}
|
|
|
|
|
|
|
|
sub build {
|
|
|
|
my ($self) = @_;
|
|
|
|
my @lines;
|
|
|
|
|
|
|
|
push @lines, sprintf "{1}{1}%.3f", $self->{fps};
|
|
|
|
foreach my $e (@{ $self->{events} }) {
|
|
|
|
my $show = round($e->t_start * $self->{fps}, 0);
|
|
|
|
my $hide = round($e->t_end * $self->{fps}, 0);
|
|
|
|
my $text = ($e->{text} =~ s{$self->{eol}}{|}gr);
|
|
|
|
push @lines, sprintf "{%d}{%d}%s", $show, $hide, $text;
|
|
|
|
}
|
|
|
|
push @lines, "";
|
|
|
|
|
|
|
|
return join($self->{eol} => @lines);
|
|
|
|
}
|
|
|
|
|
|
|
|
1;
|
|
|
|
|
|
|
|
=pod
|
|
|
|
|
|
|
|
=head1 NAME
|
|
|
|
|
|
|
|
Subtitle::MSub -- micsrosub subtitle format
|
|
|
|
|
|
|
|
=head1 METHODS
|
|
|
|
|
|
|
|
=head2 C<new>
|
|
|
|
|
|
|
|
my $msub = Subtitle::MSub->new(%opts);
|
|
|
|
|
|
|
|
Creates new object, takes list of options as argument.
|
|
|
|
Recognized keys are:
|
|
|
|
|
|
|
|
* debug -- write debug messages to log (default: 0)
|
|
|
|
* eol -- replace "|" symbol within subtitle text with this chars (default: "\n")
|
|
|
|
* fps -- sets FPS for subtitle (default: 25.0, try detect from file)
|
|
|
|
* convert_timing -- convert frame numbers to seconds (default: 1)
|
|
|
|
|
|
|
|
=head2 C<parse>
|
|
|
|
|
|
|
|
my $events_count = $msub->parse(\@lines);
|
|
|
|
|
|
|
|
Parse subtitle from array of lines.
|
|
|
|
Returns parsed events count on success, -1 on error
|
|
|
|
|
|
|
|
=head2 C<build>
|
|
|
|
|
|
|
|
my $text = $msub->build();
|
|
|
|
|
|
|
|
Builds parsed subtitle to string.
|
|
|
|
|
|
|
|
=cut
|