| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Image::Animated::JPEG; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 3 |  |  | 3 |  | 226213 | use strict; | 
|  | 3 |  |  |  |  | 24 |  | 
|  | 3 |  |  |  |  | 88 |  | 
| 4 | 3 |  |  | 3 |  | 14 | use warnings; | 
|  | 3 |  |  |  |  | 4 |  | 
|  | 3 |  |  |  |  | 72 |  | 
| 5 |  |  |  |  |  |  |  | 
| 6 | 3 |  |  | 3 |  | 1624 | use Encode; | 
|  | 3 |  |  |  |  | 31044 |  | 
|  | 3 |  |  |  |  | 8072 |  | 
| 7 |  |  |  |  |  |  |  | 
| 8 |  |  |  |  |  |  | our $VERSION = '0.03'; | 
| 9 |  |  |  |  |  |  |  | 
| 10 |  |  |  |  |  |  | sub index { | 
| 11 | 4 |  |  | 4 | 1 | 3536 | my $io_file = shift; | 
| 12 | 4 |  |  |  |  | 6 | my $args = shift; | 
| 13 |  |  |  |  |  |  |  | 
| 14 | 4 |  |  |  |  | 7 | my @frames; | 
| 15 |  |  |  |  |  |  |  | 
| 16 | 4 | 0 | 33 |  |  | 10 | print "Parsing file...\n" if $args && $args->{debug}; | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | # check | 
| 19 | 4 |  |  |  |  | 10 | my $soi = my_read($io_file, 2); | 
| 20 | 4 | 100 |  |  |  | 11 | unless ($soi eq "\xFF\xD8") { | 
| 21 | 1 |  |  |  |  | 12 | die "Does not look like a JPEG file: SOI missing at start of file"; | 
| 22 |  |  |  |  |  |  | } | 
| 23 |  |  |  |  |  |  |  | 
| 24 |  |  |  |  |  |  | # first frame | 
| 25 | 3 |  |  |  |  | 5 | my $cnt = 1; | 
| 26 | 3 | 0 | 33 |  |  | 7 | print " frame $cnt begins at 0\n" if $args && $args->{debug}; | 
| 27 | 3 |  |  |  |  | 12 | push(@frames, { | 
| 28 |  |  |  |  |  |  | offset	=> 0, | 
| 29 |  |  |  |  |  |  | io_file	=> $io_file, # hand over, per frame (might be multiple files) | 
| 30 |  |  |  |  |  |  | }); | 
| 31 |  |  |  |  |  |  |  | 
| 32 |  |  |  |  |  |  | # subsequent frames | 
| 33 | 3 |  |  |  |  | 13 | local $/ = "\xFF\xD9\xFF\xD8"; # we look for EOI+SOI marker to avoid false-positives with embedded thumbs | 
| 34 | 3 |  |  |  |  | 27 | while(my $chunk = <$io_file>){ | 
| 35 | 5 |  |  |  |  | 13 | my $pos = tell($io_file) - 2; # -2: compensate for inclusion of begin-marker | 
| 36 | 5 | 0 | 33 |  |  | 9 | print " frame $cnt ends at $pos\n" if $args && $args->{debug}; | 
| 37 | 5 |  |  |  |  | 13 | push(@frames, { | 
| 38 |  |  |  |  |  |  | offset	=> $pos, | 
| 39 |  |  |  |  |  |  | io_file	=> $io_file, # a quirk for playajpeg: hand over the fh, per frame, as playajpeg may play a sequence of jpegs as animation | 
| 40 |  |  |  |  |  |  | }); | 
| 41 | 5 |  |  |  |  | 12 | $frames[$cnt - 1]->{length} = $pos - $frames[$cnt - 1]->{offset}; | 
| 42 | 5 |  |  |  |  | 21 | $cnt++; | 
| 43 |  |  |  |  |  |  | } | 
| 44 | 3 |  |  |  |  | 21 | seek($io_file, 0,0); # rewind fh | 
| 45 | 3 |  |  |  |  | 7 | pop(@frames); # last boundary is not beginning of a new frame | 
| 46 | 3 |  |  |  |  | 24 | $frames[-1]->{length} += 2; # last frame won't end with EOI+SOI | 
| 47 |  |  |  |  |  |  |  | 
| 48 | 3 |  |  |  |  | 18 | return \@frames; | 
| 49 |  |  |  |  |  |  | } | 
| 50 |  |  |  |  |  |  |  | 
| 51 |  |  |  |  |  |  | sub process {	# mostly from Image::Info | 
| 52 | 5 |  |  | 5 | 1 | 18255 | my $args = $_[1]; | 
| 53 |  |  |  |  |  |  |  | 
| 54 | 5 |  |  |  |  | 9 | my $fh; | 
| 55 | 5 | 100 |  |  |  | 15 | if(ref($_[0]) eq 'SCALAR'){ | 
| 56 | 2 |  |  |  |  | 524 | require IO::String; | 
| 57 | 2 | 50 |  |  |  | 4035 | $fh = IO::String->new($_[0]) or die "Error using scalar-ref for reading: $!"; | 
| 58 |  |  |  |  |  |  | }else{ | 
| 59 | 3 | 100 |  |  |  | 145 | open($fh, "<", $_[0]) or die "Error opening file for reading: $!"; | 
| 60 | 2 |  |  |  |  | 11 | binmode($fh); | 
| 61 |  |  |  |  |  |  | } | 
| 62 |  |  |  |  |  |  |  | 
| 63 | 4 | 50 | 33 |  |  | 123 | my $soi = my_read($fh, 2, ($args && $args->{debug} ? 1 : undef)); | 
| 64 | 4 | 100 |  |  |  | 14 | unless ($soi eq "\xFF\xD8") { | 
| 65 | 1 |  |  |  |  | 3 | my $offset = tell($fh) - 2; | 
| 66 | 1 |  |  |  |  | 19 | die "Does not look like a JPEG file: SOI missing at offset $offset"; | 
| 67 |  |  |  |  |  |  | } | 
| 68 |  |  |  |  |  |  |  | 
| 69 | 3 |  |  |  |  | 7 | my %markers; | 
| 70 |  |  |  |  |  |  | my @warnings; | 
| 71 | 3 |  |  |  |  | 6 | while (1) { | 
| 72 | 9 | 50 | 33 |  |  | 28 | my($ff, $mark) = unpack("CC", my_read($fh, 2, ($args && $args->{debug} ? 1 : undef))); | 
| 73 | 9 | 50 |  |  |  | 20 | last if !defined $ff; | 
| 74 |  |  |  |  |  |  |  | 
| 75 | 9 | 50 |  |  |  | 18 | if ($ff != 0xFF) {	# enter when processing a chunk | 
| 76 | 0 |  |  |  |  | 0 | my $corrupt_bytes = 2; | 
| 77 | 0 |  |  |  |  | 0 | while(1) { | 
| 78 | 0 | 0 | 0 |  |  | 0 | my($ff) = unpack("C", my_read($fh,1, ($args && $args->{debug} ? 1 : undef))); | 
| 79 | 0 | 0 |  |  |  | 0 | return if !defined $ff; | 
| 80 | 0 | 0 |  |  |  | 0 | last if $ff == 0xFF; | 
| 81 | 0 |  |  |  |  | 0 | $corrupt_bytes++; | 
| 82 |  |  |  |  |  |  | } | 
| 83 | 0 | 0 | 0 |  |  | 0 | $mark = unpack("C", my_read($fh,1, ($args && $args->{debug} ? 1 : undef))); | 
| 84 | 0 |  |  |  |  | 0 | push(@warnings, sprintf("Corrupt JPEG data, $corrupt_bytes extraneous bytes before marker 0x%02x", $mark)); | 
| 85 |  |  |  |  |  |  | } | 
| 86 | 9 | 50 |  |  |  | 16 | if ($mark == 0xFF) {	# munge FFs (JPEG markers can be padded with unlimited 0xFF's) | 
| 87 | 0 |  |  |  |  | 0 | for (;;) { | 
| 88 | 0 | 0 | 0 |  |  | 0 | ($mark) = unpack("C", my_read($fh, 1, ($args && $args->{debug} ? 1 : undef))); | 
| 89 | 0 | 0 |  |  |  | 0 | last if $mark != 0xFF; | 
| 90 |  |  |  |  |  |  | } | 
| 91 |  |  |  |  |  |  | } | 
| 92 |  |  |  |  |  |  |  | 
| 93 | 9 | 50 | 33 |  |  | 31 | last if $mark == 0xDA || $mark == 0xD9;  # exit once we reach a SOS marker, or EOI (end of image) | 
| 94 |  |  |  |  |  |  |  | 
| 95 | 9 | 0 | 33 |  |  | 14 | print "marker: FF ".sprintf("%#x",$mark)." \n" if $args && $args->{debug}; | 
| 96 | 9 |  |  |  |  | 15 | my $marker_pos = tell($fh) - 2; | 
| 97 | 9 | 50 | 33 |  |  | 48 | my($len) = unpack("n", my_read($fh, 2, ($args && $args->{debug} ? 1 : undef))); # we found a marker, read its size | 
| 98 | 9 | 50 |  |  |  | 18 | last if $len < 2; # data-less marker | 
| 99 |  |  |  |  |  |  |  | 
| 100 | 9 | 100 |  |  |  | 16 | last if $mark < 0xE0; # data-less marker | 
| 101 |  |  |  |  |  |  |  | 
| 102 |  |  |  |  |  |  | # process_chunk($info, $img_no, $mark, my_read($fh, $len - 2)); | 
| 103 | 6 | 100 |  |  |  | 10 | if($mark == 0xE0){ | 
| 104 | 5 | 50 | 33 |  |  | 16 | my $data = my_read($fh, $len - 2, ($args && $args->{debug} ? 1 : undef)); | 
| 105 | 5 | 0 | 33 |  |  | 10 | print "APP0 at ". $marker_pos ." len:$len \n" if $args && $args->{debug}; | 
| 106 |  |  |  |  |  |  |  | 
| 107 |  |  |  |  |  |  | # get_name($fh); | 
| 108 | 5 |  |  |  |  | 6 | my $name; | 
| 109 | 5 |  |  |  |  | 5 | my $rel_offset = 0; | 
| 110 | 5 |  |  |  |  | 12 | for(0..10){ # app-identifiers may be arbitrarily long, but let's stop after 10 bytes | 
| 111 | 27 |  |  |  |  | 47 | my ($value) = unpack("C", read_bytes(\$data, \$rel_offset, 1)); | 
| 112 | 27 | 100 |  |  |  | 55 | last if $value == 0x00; | 
| 113 | 22 |  |  |  |  | 30 | $name .= chr($value); | 
| 114 |  |  |  |  |  |  | } | 
| 115 |  |  |  |  |  |  |  | 
| 116 | 5 |  |  |  |  | 28 | $markers{$name} = { | 
| 117 |  |  |  |  |  |  | type => 'APP0', | 
| 118 |  |  |  |  |  |  | offset => $marker_pos, | 
| 119 |  |  |  |  |  |  | length => $len, | 
| 120 |  |  |  |  |  |  | data_offset => ($marker_pos + 4) + (length($name) + 1), | 
| 121 |  |  |  |  |  |  | data_length => ($len - 2) - (length($name) + 1) | 
| 122 |  |  |  |  |  |  | }; | 
| 123 |  |  |  |  |  |  | }else{ | 
| 124 | 1 |  |  |  |  | 14 | seek($fh,$len - 2,1); | 
| 125 |  |  |  |  |  |  | } | 
| 126 |  |  |  |  |  |  |  | 
| 127 |  |  |  |  |  |  | } | 
| 128 |  |  |  |  |  |  |  | 
| 129 |  |  |  |  |  |  | #	print Dumper(\%markers,\@warnings); | 
| 130 | 3 |  |  |  |  | 44 | return \%markers; | 
| 131 |  |  |  |  |  |  | } | 
| 132 |  |  |  |  |  |  |  | 
| 133 |  |  |  |  |  |  | sub my_read {	# from Image::Info | 
| 134 | 31 |  |  | 31 | 0 | 48 | my($fh, $len) = @_; | 
| 135 | 31 | 50 |  |  |  | 49 | print " my_read: len:$len \n" if $_[2]; | 
| 136 | 31 |  |  |  |  | 32 | my $buf; | 
| 137 | 31 |  |  |  |  | 204 | my $n = read($fh, $buf, $len); | 
| 138 | 31 | 50 |  |  |  | 220 | die "read failed: $!" unless defined $n; | 
| 139 | 31 | 50 |  |  |  | 46 | die "short read ($len/$n) at pos " . tell($fh) unless $n == $len; | 
| 140 | 31 |  |  |  |  | 86 | $buf; | 
| 141 |  |  |  |  |  |  | } | 
| 142 |  |  |  |  |  |  |  | 
| 143 |  |  |  |  |  |  |  | 
| 144 |  |  |  |  |  |  | sub encode_ajpeg_marker { | 
| 145 | 0 |  |  | 0 | 0 | 0 | return "AJPEG\000" . encode_ajpeg_data(@_); | 
| 146 |  |  |  |  |  |  | } | 
| 147 |  |  |  |  |  |  |  | 
| 148 |  |  |  |  |  |  | sub decode_ajpeg_marker { | 
| 149 | 0 |  |  | 0 | 0 | 0 | return decode_ajpeg_data( substr($_[0],6) ); | 
| 150 |  |  |  |  |  |  | } | 
| 151 |  |  |  |  |  |  |  | 
| 152 |  |  |  |  |  |  | #   +------------- segment --------------+ | 
| 153 |  |  |  |  |  |  | #   | APP0-marker	  		 | | 
| 154 |  |  |  |  |  |  | #   |		  +--"marker" / data ----+ | 
| 155 |  |  |  |  |  |  | #   | 		  | AJPEG-marker	 | | 
| 156 |  |  |  |  |  |  | #   | 		  | 			 | | 
| 157 |  |  |  |  |  |  | #   \x00 | 
| 158 |  |  |  |  |  |  | #    FF E0   00 00   AJPEG       00  ... | 
| 159 |  |  |  |  |  |  |  | 
| 160 |  |  |  |  |  |  | ## expects a hashref | 
| 161 |  |  |  |  |  |  | ## encodes hash keys according to the AJPEG schema | 
| 162 |  |  |  |  |  |  | sub encode_ajpeg_data { | 
| 163 | 4 |  |  | 4 | 1 | 14349 | my $ref = shift; | 
| 164 | 4 |  |  |  |  | 6 | my $args = shift; # debug, future: version | 
| 165 |  |  |  |  |  |  |  | 
| 166 | 4 | 50 | 33 |  |  | 23 | die "encode_ajpeg_data expects a hash-ref" unless $ref && ref($ref) eq 'HASH'; | 
| 167 |  |  |  |  |  |  |  | 
| 168 | 4 |  |  |  |  | 5 | my $binary; | 
| 169 |  |  |  |  |  |  |  | 
| 170 | 4 |  |  |  |  | 7 | $binary = pack("C1",0); # version:0 | 
| 171 |  |  |  |  |  |  |  | 
| 172 | 4 |  |  |  |  | 16 | for my $key (keys %$ref){ | 
| 173 | 37 | 50 |  |  |  | 58 | unless(defined($ref->{$key})){ | 
| 174 | 0 | 0 | 0 |  |  | 0 | warn "encode_ajpeg_data: $key value is undef and will be ignored!" if $args && $args->{debug}; | 
| 175 | 0 |  |  |  |  | 0 | next; | 
| 176 |  |  |  |  |  |  | } | 
| 177 | 37 | 100 |  |  |  | 55 | if($key eq 'version'){ | 
| 178 | 4 | 50 | 33 |  |  | 18 | warn "encode_ajpeg_data: Only format version 0 is currenty implemented!" if $args && $args->{debug} && $ref->{$key} != 0; | 
|  |  |  | 33 |  |  |  |  | 
| 179 | 4 |  |  |  |  | 7 | next; | 
| 180 |  |  |  |  |  |  | } | 
| 181 | 33 | 100 |  |  |  | 101 | if($key eq 'delay'){ | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 182 | 4 | 100 |  |  |  | 39 | if($ref->{$key} <= 255){ | 
|  |  | 100 |  |  |  |  |  | 
| 183 | 2 | 50 | 33 |  |  | 11 | print "encode_ajpeg_data: delay $ref->{$key} (byte)\n" if $args && $args->{debug}; | 
| 184 | 2 |  |  |  |  | 8 | $binary .= "\x01" . pack("C",$ref->{$key}); | 
| 185 |  |  |  |  |  |  | }elsif($ref->{$key} <= 65535){ | 
| 186 | 1 | 50 | 33 |  |  | 6 | print "encode_ajpeg_data: delay $ref->{$key} (short)\n" if $args && $args->{debug}; | 
| 187 | 1 |  |  |  |  | 4 | $binary .= "\x02" . pack("n",$ref->{$key}); | 
| 188 |  |  |  |  |  |  | }else{ | 
| 189 | 1 | 50 | 33 |  |  | 5 | print "encode_ajpeg_data: delay $ref->{$key} (long)\n" if $args && $args->{debug}; | 
| 190 | 1 |  |  |  |  | 5 | $binary .= "\x04" . pack("N",$ref->{$key}); | 
| 191 |  |  |  |  |  |  | } | 
| 192 |  |  |  |  |  |  | }elsif($key eq 'repeat'){ | 
| 193 | 4 | 100 |  |  |  | 9 | if($ref->{$key} <= 255){ | 
|  |  | 50 |  |  |  |  |  | 
| 194 | 2 | 50 | 33 |  |  | 9 | print "encode_ajpeg_data: repeat $ref->{$key} (byte)\n" if $args && $args->{debug}; | 
| 195 | 2 |  |  |  |  | 9 | $binary .= "\x11" . pack("C",$ref->{$key}); | 
| 196 |  |  |  |  |  |  | }elsif($ref->{$key} <= 65535){ | 
| 197 | 2 | 50 | 33 |  |  | 8 | print "encode_ajpeg_data: repeat $ref->{$key} (short)\n" if $args && $args->{debug}; | 
| 198 | 2 |  |  |  |  | 6 | $binary .= "\x12" . pack("n",$ref->{$key}); | 
| 199 |  |  |  |  |  |  | }else{ | 
| 200 | 0 |  |  |  |  | 0 | die "repeat values must be <= 65535"; | 
| 201 |  |  |  |  |  |  | } | 
| 202 |  |  |  |  |  |  | }elsif($key eq 'parse_next'){ | 
| 203 | 4 | 100 |  |  |  | 12 | if($ref->{$key} <= 255){ | 
|  |  | 50 |  |  |  |  |  | 
| 204 | 2 |  |  |  |  | 7 | $binary .= "\x21" . pack("C",$ref->{$key}); | 
| 205 |  |  |  |  |  |  | }elsif($ref->{$key} <= 65535){ | 
| 206 | 2 |  |  |  |  | 5 | $binary .= "\x22" . pack("n",$ref->{$key}); | 
| 207 |  |  |  |  |  |  | }else{ | 
| 208 | 0 |  |  |  |  | 0 | die "parse_next values must be <= 65535"; | 
| 209 |  |  |  |  |  |  | } | 
| 210 |  |  |  |  |  |  | }elsif($key eq 'length'){ | 
| 211 | 4 | 100 |  |  |  | 11 | if($ref->{$key} <= 255){ | 
|  |  | 100 |  |  |  |  |  | 
| 212 | 1 |  |  |  |  | 3 | $binary .= "\x31" . pack("C",$ref->{$key}); | 
| 213 |  |  |  |  |  |  | }elsif($ref->{$key} <= 65535){ | 
| 214 | 2 |  |  |  |  | 6 | $binary .= "\x32" . pack("n",$ref->{$key}); | 
| 215 |  |  |  |  |  |  | }else{ | 
| 216 | 1 |  |  |  |  | 4 | $binary .= "\x34" . pack("N",$ref->{$key}); | 
| 217 |  |  |  |  |  |  | } | 
| 218 |  |  |  |  |  |  | }elsif($key eq 'previous'){ | 
| 219 | 4 | 100 |  |  |  | 10 | if($ref->{$key} <= 255){ | 
|  |  | 100 |  |  |  |  |  | 
| 220 | 1 |  |  |  |  | 3 | $binary .= "\x41" . pack("C",$ref->{$key}); | 
| 221 |  |  |  |  |  |  | }elsif($ref->{$key} <= 65535){ | 
| 222 | 2 |  |  |  |  | 5 | $binary .= "\x42" . pack("n",$ref->{$key}); | 
| 223 |  |  |  |  |  |  | }else{ | 
| 224 | 1 |  |  |  |  | 8 | $binary .= "\x44" . pack("N",$ref->{$key}); | 
| 225 |  |  |  |  |  |  | } | 
| 226 |  |  |  |  |  |  | }elsif($key eq 'x_offset'){ | 
| 227 | 4 | 100 |  |  |  | 12 | if($ref->{$key} <= 255){ | 
|  |  | 50 |  |  |  |  |  | 
| 228 | 2 |  |  |  |  | 5 | $binary .= "\x51" . pack("C",$ref->{$key}); | 
| 229 |  |  |  |  |  |  | }elsif($ref->{$key} <= 65535){ | 
| 230 | 2 |  |  |  |  | 5 | $binary .= "\x52" . pack("n",$ref->{$key}); | 
| 231 |  |  |  |  |  |  | }else{ | 
| 232 | 0 |  |  |  |  | 0 | die "x_offset values must be <= 65535"; | 
| 233 |  |  |  |  |  |  | } | 
| 234 |  |  |  |  |  |  | }elsif($key eq 'y_offset'){ | 
| 235 | 4 | 100 |  |  |  | 11 | if($ref->{$key} <= 255){ | 
|  |  | 50 |  |  |  |  |  | 
| 236 | 2 |  |  |  |  | 6 | $binary .= "\x61" . pack("C",$ref->{$key}); | 
| 237 |  |  |  |  |  |  | }elsif($ref->{$key} <= 65535){ | 
| 238 | 2 |  |  |  |  | 7 | $binary .= "\x62" . pack("n",$ref->{$key}); | 
| 239 |  |  |  |  |  |  | }else{ | 
| 240 | 0 |  |  |  |  | 0 | die "x_offset values must be <= 65535"; | 
| 241 |  |  |  |  |  |  | } | 
| 242 |  |  |  |  |  |  | }elsif($key eq 'dispose_op'){ | 
| 243 | 4 | 50 |  |  |  | 7 | if($ref->{$key} <= 2){ | 
| 244 | 4 |  |  |  |  | 11 | $binary .= "\x71" . pack("C",$ref->{$key}); | 
| 245 |  |  |  |  |  |  | }else{ | 
| 246 | 0 |  |  |  |  | 0 | die "dispose_op values must be <= 2"; | 
| 247 |  |  |  |  |  |  | } | 
| 248 |  |  |  |  |  |  | }elsif($key eq 'metadata'){ | 
| 249 |  |  |  |  |  |  | # skip, for later | 
| 250 |  |  |  |  |  |  | }else{ | 
| 251 | 0 |  |  |  |  | 0 | warn "encode_ajpeg_data: '$key' is not recognized"; | 
| 252 |  |  |  |  |  |  | } | 
| 253 |  |  |  |  |  |  | } | 
| 254 |  |  |  |  |  |  |  | 
| 255 |  |  |  |  |  |  | # specs do not require it, but it may make sense to sort | 
| 256 |  |  |  |  |  |  | # "potentially longer" byte segments, like metadata, to the end | 
| 257 |  |  |  |  |  |  | # of the AJPEG segmemt | 
| 258 | 4 | 100 |  |  |  | 14 | if($ref->{'metadata'}){ # A0 | 
| 259 | 1 | 50 | 33 |  |  | 7 | print "encode_ajpeg_data: metadata \n" if $args && $args->{debug}; | 
| 260 | 1 |  |  |  |  | 3 | for my $mkey (keys %{ $ref->{'metadata'} }){ | 
|  | 1 |  |  |  |  | 4 |  | 
| 261 | 4 | 50 | 33 |  |  | 17 | print " metadata: ". $mkey .":". $ref->{'metadata'}->{$mkey} ."\n" if $args && $args->{debug}; | 
| 262 | 4 |  |  |  |  | 9 | my $mkey_utf8 = encode('utf-8',$mkey); | 
| 263 | 4 |  |  |  |  | 359 | my $mvalue_utf8 = encode('utf-8',$ref->{'metadata'}->{$mkey}); | 
| 264 |  |  |  |  |  |  |  | 
| 265 | 4 |  |  |  |  | 157 | $binary .= pack("C",160) . pack("C",length($mkey_utf8)) . $mkey_utf8 . pack("C",length($mvalue_utf8)) . $mvalue_utf8; | 
| 266 |  |  |  |  |  |  | } | 
| 267 |  |  |  |  |  |  | } | 
| 268 |  |  |  |  |  |  |  | 
| 269 | 4 |  |  |  |  | 11 | return $binary; | 
| 270 |  |  |  |  |  |  | } | 
| 271 |  |  |  |  |  |  |  | 
| 272 |  |  |  |  |  |  |  | 
| 273 |  |  |  |  |  |  | sub read_bytes { | 
| 274 | 118 |  |  | 118 | 0 | 125 | my $binary_ref = shift; | 
| 275 | 118 |  |  |  |  | 117 | my $offset_ref = shift; # quirk: offset as ref, so a read_bytes(), like core::read, advances a pointer/offset | 
| 276 | 118 |  |  |  |  | 115 | my $length = shift; | 
| 277 |  |  |  |  |  |  |  | 
| 278 |  |  |  |  |  |  | # print "  read $length bytes at ${$offset_ref} \n"; | 
| 279 | 118 |  |  |  |  | 102 | my $data = substr(${$binary_ref}, ${$offset_ref}, $length); | 
|  | 118 |  |  |  |  | 128 |  | 
|  | 118 |  |  |  |  | 147 |  | 
| 280 | 118 |  |  |  |  | 157 | ${$offset_ref} += $length; | 
|  | 118 |  |  |  |  | 127 |  | 
| 281 |  |  |  |  |  |  |  | 
| 282 | 118 |  |  |  |  | 272 | return $data; | 
| 283 |  |  |  |  |  |  | } | 
| 284 |  |  |  |  |  |  |  | 
| 285 |  |  |  |  |  |  | ## expects a scalar holding binary data | 
| 286 |  |  |  |  |  |  | # decodes AJPEG schema binary encoded keys and values | 
| 287 |  |  |  |  |  |  | sub decode_ajpeg_data { | 
| 288 | 5 |  |  | 5 | 1 | 3593 | my $binary = shift; | 
| 289 | 5 |  |  |  |  | 5 | my $args = shift; # debug | 
| 290 |  |  |  |  |  |  |  | 
| 291 | 5 | 50 |  |  |  | 13 | die "decode_ajpeg_data expects a scalar with binary data" unless defined($binary); | 
| 292 |  |  |  |  |  |  |  | 
| 293 | 5 |  |  |  |  | 9 | my $length = length($binary); | 
| 294 | 5 |  |  |  |  | 6 | my $offset = 0; | 
| 295 |  |  |  |  |  |  |  | 
| 296 | 5 |  |  |  |  | 6 | my %ref; | 
| 297 |  |  |  |  |  |  |  | 
| 298 | 5 | 50 | 33 |  |  | 23 | print "decode_ajpeg_data: length:$length \n" if $args && $args->{debug}; | 
| 299 | 5 |  |  |  |  | 15 | $ref{version} = unpack("C", read_bytes(\$binary,\$offset,1)); | 
| 300 | 5 | 50 | 33 |  |  | 24 | print "decode_ajpeg_data: version:$ref{version} \n" if $args && $args->{debug}; | 
| 301 |  |  |  |  |  |  |  | 
| 302 | 5 |  |  |  |  | 6 | my $cnt; | 
| 303 | 5 |  |  |  |  | 6 | for(;;){ | 
| 304 | 37 |  |  |  |  | 39 | $cnt++; | 
| 305 |  |  |  |  |  |  |  | 
| 306 | 37 |  |  |  |  | 73 | my $byte = read_bytes(\$binary,\$offset, 1); | 
| 307 | 37 | 50 |  |  |  | 61 | if($byte){ # an "empty AJPEG marker" will only hold version, read_bytes beyond that will return undef (thus we skip $key_num/properties decoding) | 
| 308 | 37 |  |  |  |  | 49 | my $key_num = unpack("C", $byte); | 
| 309 | 37 | 100 |  |  |  | 200 | if($key_num == 1){ | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 310 | 3 |  |  |  |  | 7 | $ref{'delay'} = unpack("C", read_bytes(\$binary,\$offset,1)); | 
| 311 | 3 | 50 | 33 |  |  | 16 | print "decode_ajpeg_data: delay: $ref{'delay'} (byte) \n" if $args && $args->{debug}; | 
| 312 |  |  |  |  |  |  | }elsif($key_num == 2){ | 
| 313 | 1 |  |  |  |  | 4 | $ref{'delay'} = unpack("n", read_bytes(\$binary,\$offset,2)); | 
| 314 | 1 | 50 | 33 |  |  | 6 | print "decode_ajpeg_data: delay: $ref{'delay'} (short) \n" if $args && $args->{debug}; | 
| 315 |  |  |  |  |  |  | }elsif($key_num == 4){ | 
| 316 | 1 |  |  |  |  | 4 | $ref{'delay'} = unpack("N1", read_bytes(\$binary,\$offset,4)); | 
| 317 | 1 | 50 | 33 |  |  | 5 | print "decode_ajpeg_data: delay: $ref{'delay'} (long) \n" if $args && $args->{debug}; | 
| 318 |  |  |  |  |  |  | }elsif($key_num == 17){ | 
| 319 | 2 |  |  |  |  | 5 | $ref{'repeat'} = unpack("C", read_bytes(\$binary,\$offset,1)); | 
| 320 | 2 | 50 | 33 |  |  | 14 | print "decode_ajpeg_data: repeat: $ref{'repeat'} (byte) \n" if $args && $args->{debug}; | 
| 321 |  |  |  |  |  |  | }elsif($key_num == 18){ | 
| 322 | 2 |  |  |  |  | 11 | $ref{'repeat'} = unpack("n", read_bytes(\$binary,\$offset,2)); | 
| 323 | 2 | 50 | 33 |  |  | 8 | print "decode_ajpeg_data: repeat: $ref{'repeat'} (short) \n" if $args && $args->{debug}; | 
| 324 |  |  |  |  |  |  | }elsif($key_num == 33){ | 
| 325 | 2 |  |  |  |  | 11 | $ref{'parse_next'} = unpack("C", read_bytes(\$binary,\$offset,1)); | 
| 326 |  |  |  |  |  |  | }elsif($key_num == 34){ | 
| 327 | 2 |  |  |  |  | 4 | $ref{'parse_next'} = unpack("n", read_bytes(\$binary,\$offset,2)); | 
| 328 |  |  |  |  |  |  | }elsif($key_num == 49){ | 
| 329 | 1 |  |  |  |  | 4 | $ref{'length'} = unpack("C", read_bytes(\$binary,\$offset,1)); | 
| 330 |  |  |  |  |  |  | }elsif($key_num == 50){ | 
| 331 | 2 |  |  |  |  | 5 | $ref{'length'} = unpack("n", read_bytes(\$binary,\$offset,2)); | 
| 332 |  |  |  |  |  |  | }elsif($key_num == 52){ | 
| 333 | 1 |  |  |  |  | 5 | $ref{'length'} = unpack("N", read_bytes(\$binary,\$offset,4)); | 
| 334 |  |  |  |  |  |  | }elsif($key_num == 65){ | 
| 335 | 1 |  |  |  |  | 3 | $ref{'previous'} = unpack("C", read_bytes(\$binary,\$offset,1)); | 
| 336 |  |  |  |  |  |  | }elsif($key_num == 66){ | 
| 337 | 2 |  |  |  |  | 4 | $ref{'previous'} = unpack("n", read_bytes(\$binary,\$offset,2)); | 
| 338 |  |  |  |  |  |  | }elsif($key_num == 68){ | 
| 339 | 1 |  |  |  |  | 3 | $ref{'previous'} = unpack("N", read_bytes(\$binary,\$offset,4)); | 
| 340 |  |  |  |  |  |  | }elsif($key_num == 81){ | 
| 341 | 2 |  |  |  |  | 4 | $ref{'x_offset'} = unpack("C", read_bytes(\$binary,\$offset,1)); | 
| 342 |  |  |  |  |  |  | }elsif($key_num == 82){ | 
| 343 | 2 |  |  |  |  | 3 | $ref{'x_offset'} = unpack("n", read_bytes(\$binary,\$offset,2)); | 
| 344 |  |  |  |  |  |  | }elsif($key_num == 97){ | 
| 345 | 2 |  |  |  |  | 7 | $ref{'y_offset'} = unpack("C", read_bytes(\$binary,\$offset,1)); | 
| 346 |  |  |  |  |  |  | }elsif($key_num == 98){ | 
| 347 | 2 |  |  |  |  | 5 | $ref{'y_offset'} = unpack("n", read_bytes(\$binary,\$offset,2)); | 
| 348 |  |  |  |  |  |  | }elsif($key_num == 113){ | 
| 349 | 4 |  |  |  |  | 9 | $ref{'dispose_op'} = unpack("C", read_bytes(\$binary,\$offset,1)); | 
| 350 |  |  |  |  |  |  | }elsif($key_num == 160){ # 0xA0 | 
| 351 | 4 |  |  |  |  | 7 | my $mkey_utf8 = read_bytes(\$binary,\$offset, unpack('C',read_bytes(\$binary,\$offset,1)) ); | 
| 352 | 4 |  |  |  |  | 12 | my $mvalue_utf8 = read_bytes(\$binary,\$offset, unpack('C',read_bytes(\$binary,\$offset,1)) ); | 
| 353 |  |  |  |  |  |  |  | 
| 354 | 4 | 50 |  |  |  | 15 | my $mkey = decode('utf-8', $mkey_utf8) if defined($mkey_utf8); | 
| 355 | 4 | 50 |  |  |  | 194 | my $mvalue = decode('utf-8', $mvalue_utf8) if defined($mvalue_utf8); | 
| 356 | 4 | 50 | 33 |  |  | 166 | print "decode_ajpeg_data: metadata: ". $mkey .":". $mvalue ." \n" if $args && $args->{debug}; | 
| 357 | 4 |  |  |  |  | 12 | $ref{'metadata'}->{$mkey} = $mvalue; | 
| 358 |  |  |  |  |  |  | } | 
| 359 |  |  |  |  |  |  | } | 
| 360 | 37 | 100 |  |  |  | 70 | last if $offset >= $length; | 
| 361 |  |  |  |  |  |  | } | 
| 362 |  |  |  |  |  |  |  | 
| 363 | 5 |  |  |  |  | 16 | return \%ref; | 
| 364 |  |  |  |  |  |  | } | 
| 365 |  |  |  |  |  |  |  | 
| 366 |  |  |  |  |  |  | 1; | 
| 367 |  |  |  |  |  |  |  | 
| 368 |  |  |  |  |  |  | __END__ |