package Subtitle::Format::SSA::File; use strict; use warnings; use bytes; sub new { my ($class, %opts) = @_; my $self = { err => undef, eol => "\n", data => '', type => 'font', name => 'unnamed_0.ttf', }; return bless($self, $class); } ## accessors sub size { my ($self) = @_; return _length_bytes($self->{data}); } ## accessors / setters sub error { my ($self, $text) = @_; if (defined $text) { $self->{err} = $text; return; } return $self->{err}; } sub type { my ($self, $type) = @_; if (defined $type) { if ($type eq 'file' or $type eq 'font') { $self->{type} = $type; } else { die "wrong 'type' value for ASS file: $type\n"; } } return $self->{type}; } sub name { my ($self, $name) = @_; if (defined $name) { if ($self->type eq 'font') { if ($name =~ m<^(.+)_(B|BI|I)?(\d+)\.ttf$>i) { # $1 - fontname, $2 - flags (bold/italic), $3 - encoding $self->{name} = sprintf '%s_%s%d.ttf', $1, uc($2 // ''), $3; } else { die "wrong name format for ASS font: $name\n"; } } else { $self->{name} = $name; } } return $self->{name}; } ## import functions sub from_binary { my ($self, $bin) = @_; return unless defined $bin and length($bin) > 0; $self->{data} = _encode_uue($bin); return 1; } sub add_uue_line { my ($self, $line) = @_; my $len = length($line); if ($len == 80) { # optimization $self->{data} .= $line; return 60; } elsif ($len % 4 == 1) { # 1-char uue chunk with 6 significant bits can't hold 1 byte with 8 bits return $self->error("bad uuencoded data (odd length)\n"); } elsif ($len == 0) { return 0; } # else... chomp $line; $self->{data} .= $line; return _length_bytes($line); } ## export functions sub to_binary { my ($self) = @_; return _decode_uue($self->{data}); } sub uue_string { my ($self) = @_; return $self->{data}; } sub uue_block { my ($self) = @_; my $uue = $self->uue_string; my $cap = ($self->{type} eq 'font') ? 'fontname' : 'filename'; my $out = sprintf '%s: %s%s', $cap, $self->{name}, $self->{eol}; while (my $line = substr($uue, 0, 80, '')) { $out .= $line; $out .= $self->{eol}; } return $out; } ## private UUE functions sub _length_bytes { my ($uue) = @_; my $len = length $uue; my $tail = $len % 4; my $size = int($len / 4) * 3; return $size + int(($tail * 6) / 8); } sub _decode_uue { my ($uue) = @_; my $bytes = ''; while (1) { my $chars = substr($uue, 0, 4, ''); my $cnt = length $chars; last if $cnt <= 0; my @chars = split(//, $chars); if ($cnt == 4) { my $t = ((ord($chars[0]) - 33) << 18) + ((ord($chars[1]) - 33) << 12) + ((ord($chars[2]) - 33) << 6) + ((ord($chars[3]) - 33)); $bytes .= pack('C*', ($t & 0xFF0000) >> 16, ($t & 0xFF00) >> 8, ($t & 0xFF)); } elsif ($cnt == 3) { my $t = ((ord($chars[0]) - 33) << 18) + ((ord($chars[1]) - 33) << 12) + ((ord($chars[2]) - 33) << 6); $bytes .= pack('C*', ($t & 0xFF0000) >> 16, ($t & 0xFF00) >> 8); } elsif ($cnt == 2) { my $t = ((ord($chars[0]) - 33) << 18) + ((ord($chars[1]) - 33) << 12); $bytes .= pack('C*', ($t & 0xFF0000) >> 16); } else { # $cnt == 1 or >= 5 die "error in SSA UUE decode: bad chunk length\n"; } } return $bytes; } sub _encode_uue { my ($binary) = @_; my $uue = ''; while (1) { my $bytes = substr($binary, 0, 3, ''); my $cnt = length $bytes; last if $cnt <= 0; my @bytes = split(//, $bytes); if ($cnt == 3) { my $t = (ord($bytes[0]) << 16) + (ord($bytes[1]) << 8) + (ord($bytes[2]) << 0); $uue .= pack('C*', (($t >> 18) & 0x3F) + 33, (($t >> 12) & 0x3F) + 33, (($t >> 6) & 0x3F) + 33, (($t >> 0) & 0x3F) + 33); } elsif ($cnt == 2) { my $t = (ord($bytes[0]) << 16) + (ord($bytes[1]) << 8); $uue .= pack('C*', (($t >> 18) & 0x3F) + 33, (($t >> 12) & 0x3F) + 33, (($t >> 6) & 0x3F) + 33); } elsif ($cnt == 1) { my $t = (ord($bytes[0]) << 16); $uue .= pack('C*', (($t >> 18) & 0x3F) + 33, (($t >> 12) & 0x3F) + 33); } else { die "error in SSA UUE encode: bad chunk length\n"; } } return $uue; } 1;