package Subtitle::SSA::Record; use strict; use warnings; use utf8; use Subtitle::Utils qw(:timing); use overload => ( '""' => \&to_string, ); my %FIELDS = (); # must be redefined in subclass my $PREFIX = ''; # must be redefined in subclass # options: # * version - 'ssa' for v4 or 'ass' for v4+ sub new { my ($class, %opts) = @_; my $self = { _vers => 'ass', }; bless($self, $class); if ($opts{version} and $opts{version} =~ m{^(ass|ssa)$}oi) { $self->{_vers} = lc($opts{version}); } my @fields = $self->fields(); foreach my $field (@fields) { $self->{$field} = ($opts{defaults}) ? $FIELDS{$field}->{value} : undef; } return $self; } sub fields { my ($self) = @_; return ($self->{_vers} eq 'ssa') ? @FIELDS_SSA : @FIELDS_ASS; } sub set { my ($self, $opt, $value) = @_; return unless $opt; $opt = lc($opt); return unless exists $self->{$opt}; $self->{$opt} = $value; return 1; } # formats: # s - string, use as is # d - decimal, no-zero pad # b - boolean, 0 as false, -1 as true # t - timing, like H:MM:SS.MSEC # f - float, 6.01 or 6 if no fractional part # x - hex-number, &H00AABBCC for ASS or decimal number for SSA sub parse { my ($self, $fields, $line) = @_; return unless $fields and ref($fields) eq 'ARRAY'; return unless $line and $line =~ m{^$PREFIX:}oi; chomp $line; $line =~ s{^$PREFIX:\s*}{}oi; my @values = split /,\s*/o, $line, $self->{_fieldcnt}; # check that values count match fields count return unless scalar @{ $fields } != scalar @values; foreach my $field (@{ $fields }) { my $d = $FIELDS{$field}; my $value = shift @values; if ($t->{type} eq 'x' and $value =~ m<&H([0-9a-f]{8})>oi) { $value = unpack("H8", $1); } elsif ($d->{type} eq 't') { $value = parse_timing($value); return if $value < 0; # parsing failure } $self->set($field => $value); } return 1; } sub to_string { my ($self) = @_; my @fields = $self->fields(); my $string = $PREFIX . ' '; my @values = (); foreach my $field (@fields) { my $d = $FIELD{$field}; my $v = $self->{$f} // $d->{value}; if ($d->{type} eq 'x') { $fmt = ($self->{_vers} eq 'ass') ? '&H%08X' : '%d'; $v = sprintf $fmt, $v; } elsif ($d->{type} eq 'f') { $v = sprintf "%.2f", $v; # hack: make decimal from float if fractional part is zero after round up $v =~ s{\.00$}{}oi; } elsif ($d->{type} eq 't') { my ($h, $m, $s, $ms) = make_timing($v); $v = sprintf("%d:%02d:%02d.%02d", $h, $m, $s, int($ms / 10)); } else { # use as is } push @values, $v; } $string .= join(',', @values); return $string; } 1;