Browse Source

* Subtitle::SSA : global update

master
Alex 'AdUser' Z 9 years ago
parent
commit
fa7dffa174
  1. 209
      lib/Subtitle/SSA.pm

209
lib/Subtitle/SSA.pm

@ -4,20 +4,13 @@ use strict;
use warnings; use warnings;
use utf8; use utf8;
use Subtitle::Utils qw(:string :timing); use Subtitle::SSA::Header;
use Subtitle::SSA::Style;
use Subtitle::SSA::Event;
use base 'Subtitle::BASE'; use Subtitle::Utils qw(:string);
use constant SSA_STYLE_FMT => [qw(Name Fontname Fontsize PrimaryColour SecondaryColour
TertiaryColour BackColour Bold Italic
BorderStyle Outline Shadow Alignment MarginL MarginR MarginV AlphaLevel Encoding)];
use constant ASS_STYLE_FMT => [qw(Name Fontname Fontsize PrimaryColour SecondaryColour
OutlineColour BackColour Bold Italic Underline Strikeout ScaleX ScaleY Spacing Angle
BorderStyle Outline Shadow Alignment MarginL MarginR MarginV Encoding)];
use constant SSA_EVENT_FMT => [qw(Marked Start End Style Name MarginL MarginR MarginV Effect Text)]; use base 'Subtitle::BASE';
use constant ASS_EVENT_FMT => [qw(Layer Start End Style Name MarginL MarginR MarginV Effect Text)];
sub new { sub new {
my ($class, %args) = @_; my ($class, %args) = @_;
@ -26,97 +19,16 @@ sub new {
eol => "\n", eol => "\n",
type => undef, type => undef,
%args, %args,
h_order => [], # headers order
headers => {}, headers => {},
styles => [], styles => [],
events => [], events => [],
log => [], log => [],
timing_fmt => "%d:%02d:%02d.%s",
}; };
return bless($self, $class); return bless($self, $class);
} }
sub parse_header {
my ($self, $linenum, $line) = @_;
return if $line =~ m/^\s*;/; # comment
my ($name, $value) = ($line =~ m/^(.+):\s*(.*)$/o);
if ($name and not defined $value) {
$self->log(debug => "Header line with empty value at $linenum, skipped: $line");
return;
}
unless ($name) {
$self->log(debug => "Can't parse header line at $linenum: $line");
return;
}
$self->log(warn => "Duplicate header $name at line $linenum, overwriting")
if (exists $self->{headers}->{$name});
$self->{headers}->{$name} = $value;
if (lc($name) eq 'scripttype') {
$self->{type} = (lc($value) eq 'v4.00+') ? 'ass' : 'ssa';
$self->log(debug => "Set type to $self->{type} because of line $linenum: $line");
}
return;
}
sub parse_style {
my ($self, $linenum, $line) = @_;
my ($rest) = ($line =~ m/^\s*Style:\s*(.+)/oi);
unless ($rest) {
$self->log(warn => "Can't parse style at line $linenum: $line");
return;
}
my $fields = scalar @{ $self->{style_fmt} };
my @values = split /\s*,\s*/, $rest;
if($fields != scalar @values) {
$self->log(warn => "Style line fields at $linenum not equals number of style format fields");
return;
}
my $style = {};
for (my $i = 0; $i < $fields; $i++) {
$style->{$self->{style_fmt}->[$i]} = $values[$i];
}
push @{ $self->{styles} }, $style;
return;
}
sub parse_event {
my ($self, $linenum, $line) = @_;
my ($type, $rest) = ($line =~ m/^\s*(Dialogue):\s*(.+)/oi);
unless ($rest) {
$self->log(warn => "Can't parse style at line $linenum: $line");
return;
}
my $fields = scalar @{ $self->{event_fmt} };
my @values = split /\s*,\s*/, $rest, $fields;
if($fields > scalar @values) {
$self->log(warn => "Event line fields at $linenum less than number of style format fields ($fields)");
return;
}
my $event = { type => lc($type) };
for (my $i = 0; $i < $fields; $i++) {
$event->{$self->{event_fmt}->[$i]} = $values[$i];
}
foreach my $key (qw(start end)) {
next unless $event->{$key};
$event->{$key} = parse_timing($event->{$key});
}
$event->{marked} =~ s/^Marked=\s*//oi
if ($self->{type} eq 'ssa');
push @{ $self->{events} }, $event;
return;
}
sub new_event { ... } sub new_event { ... }
sub parse { sub parse {
@ -145,57 +57,65 @@ sub parse {
$self->log(warn => "Set type to $self->{type} because of $linenum (fallback mode)"); $self->log(warn => "Set type to $self->{type} because of $linenum (fallback mode)");
} }
} else { } else {
undef $section;
$self->log(debug => "Unknown section at line $linenum: $line"); $self->log(debug => "Unknown section at line $linenum: $line");
} }
next; next;
} }
# #
unless ($section) { unless ($section) {
$self->log(warn => "Line $linenum outside any section: $line"); $self->log(warn => "Line $linenum outside any section, skip");
next; next;
} }
# TODO: fonts section
# skip comments
next if $line =~ m{^ \s* ;}xo;
if ($section eq 'header') { if ($section eq 'header') {
$self->parse_header($linenum, $line); my $header = Subtitle::SSA::Header->new;
unless ($header->parse($line)) {
$self->log(error => "Can't parse header at line $linenum: $line");
next; next;
} }
if ($section eq 'styles') { my $key = $header->hash_key();
if ($line =~ m/^\s*Format:\s*(.*)/oi) { if (exists $self->{headers}->{$key}) {
if ($self->{style_fmt}) { $self->log(warn => "Duplicate header at line $linenum: $header->{name}, replacing");
$self->log(error => "Style format found at line $linenum, but already set before"); } else {
push @{ $self->{h_order} }, $key;
}
$self->{headers}->{$key} = $header;
next; next;
if ($key eq 'scripttype') {
my $type = index($header->{type}, "+") >= 0 ? 'ass' : 'ssa';
$self->log(info => "File recognized as '$type' type from header at $linenum");
$self->{type} = $type;
} }
my @fmt = map { lc($_) } split(/\s*,\s*/o, $1); }
$self->{style_fmt} = \@fmt; if ($section eq 'styles') {
if ($line =~ m/^\s*Format:/oi) {
$self->log(debug => "Set style format from line $linenum"); $self->log(debug => "Set style format from line $linenum");
next; next;
} }
unless ($self->{style_fmt}) { my $style = Subtitle::SSA::Style->new(type => $self->{type});
$self->log(warn => "Style format still not set, assuming default for $self->{type} type"); if ($style->parse($line)) {
my @fmt = ($self->{type} and $self->{type} eq 'ass') push @{ $self->{styles} }, $style;
? ASS_STYLE_FMT : SSA_STYLE_FMT; } else {
$self->{style_fmt} = [ map { lc($_) } @fmt ] ; my $error = $style->error();
$self->log(error => "Can't parse style at line $linenum: $error");
} }
$self->parse_style($linenum, $line);
next; next;
} }
if ($section eq 'events') { if ($section eq 'events') {
if ($line =~ m/^\s*Format:\s*(.*)/oi) { if ($line =~ m/^\s*Format:/oi) {
if ($self->{event_fmt}) {
$self->log(error => "Event format found at line $linenum, but already set before");
next;
}
my @fmt = map { lc($_) } split(/\s*,\s*/o, $1);
$self->{event_fmt} = \@fmt;
$self->log(debug => "Set event format from line $linenum"); $self->log(debug => "Set event format from line $linenum");
next; next;
} }
unless ($self->{event_fmt}) { my $event = Subtitle::SSA::Event->new(type => $self->{type});
$self->log(warn => "Event format still not set, assuming default for $self->{type} type"); if ($event->parse($line)) {
my @fmt = ($self->{type} and $self->{type} eq 'ass') push @{ $self->{events} }, $event;
? ASS_EVENT_FMT : SSA_EVENT_FMT; } else {
$self->{event_fmt} = [ map { lc($_) } @fmt ]; my $error = $event->error();
$self->log(error => "Can't parse event at line $linenum: $error");
} }
$self->parse_event($linenum, $line);
next; next;
} }
$self->log(warn => "unrecognized line at $linenum: $line"); $self->log(warn => "unrecognized line at $linenum: $line");
@ -206,49 +126,40 @@ sub parse {
sub build { sub build {
my ($self) = @_; my ($self) = @_;
my $out = "[Script Info]\n"; my (@lines, $out);
# headers # headers
foreach my $h (sort keys(%{ $self->{headers} })) { push @lines, "[Script Info]";
$out .= sprintf "%s: %s\n", $h, $self->{headers}->{$h}; push @lines, "; generated with Subtitle::SSA";
foreach my $key (@{ $self->{h_order} }) {
my $h = $self->{headers}->{$key};
push @lines, $h->to_string();
} }
$out .= "\n"; push @lines, "";
# styles # styles
if ($self->{type} eq 'ass') { if ($self->{type} eq 'ass') {
$out .= "[V4+ Styles]\n"; push @lines, "[V4+ Styles]";
$out .= sprintf "Format: %s\n", join(', ' => @{ $self->ASS_STYLE_FMT });
} else { } else {
$out .= "[V4 Styles]\n"; push @lines, "[V4 Styles]";
$out .= sprintf "Format: %s\n", join(', ' => @{ $self->SSA_STYLE_FMT });
} }
my @fields = @{ $self->{style_fmt} }; push @lines, Subtitle::SSA::Style->new(type => $self->{type})->get_format_line();
foreach my $s (@{ $self->{styles} }) { foreach my $s (@{ $self->{styles} }) {
$out .= sprintf "Style: %s\n", join(",", @{$s}{@fields}); push @lines, $s->to_string();
} }
$out .= "\n"; push @lines, "";
# events # events
$out .= "[Events]\n"; push @lines, "[Events]";
if ($self->{type} eq 'ass') { push @lines, Subtitle::SSA::Event->new(type => $self->{type})->get_format_line();
$out .= sprintf "Format: %s\n", join(', ' => @{ $self->ASS_EVENT_FMT });
} else {
$out .= sprintf "Format: %s\n", join(', ' => @{ $self->SSA_EVENT_FMT });
}
@fields = @{ $self->{event_fmt} };
my ($hrs, $min, $sec, $msec);
foreach my $e (@{ $self->{events} }) { foreach my $e (@{ $self->{events} }) {
my %event = %{$e}; push @lines, $e->to_string();
($hrs, $min, $sec, $msec) = make_timing($e->{start});
$event{start} = sprintf $self->{timing_fmt}, $hrs, $min, $sec, int($msec / 10);
($hrs, $min, $sec, $msec) = make_timing($e->{end});
$event{end} = sprintf $self->{timing_fmt}, $hrs, $min, $sec, int($msec / 10);
$event{marked} =~ s/^/Marked=/o if $self->{type} eq 'ssa';
$out .= sprintf "%s: %s\n", ucfirst($e->{type}), join(',' => @event{@fields});
} }
$out .= "\n"; push @lines, "";
# TODO: fonts
return $out; return join($self->{eol}, @lines);
} }
1; 1;

Loading…
Cancel
Save