File Coverage

lib/Image/Animated/JPEG.pm
Criterion Covered Total %
statement 179 200 89.5
branch 134 200 67.0
condition 28 96 29.1
subroutine 9 11 81.8
pod 4 8 50.0
total 354 515 68.7


line stmt bran cond sub pod time code
1             package Image::Animated::JPEG;
2              
3 3     3   326798 use strict;
  3         44  
  3         133  
4 3     3   26 use warnings;
  3         7  
  3         137  
5              
6 3     3   2051 use Encode;
  3         44183  
  3         11962  
7              
8             our $VERSION = '0.02';
9              
10             sub index {
11 4     4 1 3835 my $io_file = shift;
12 4         6 my $args = shift;
13              
14 4         6 my @frames;
15              
16 4 0 33     10 print "Parsing file...\n" if $args && $args->{debug};
17              
18             # check
19 4         11 my $soi = my_read($io_file, 2);
20 4 100       11 unless ($soi eq "\xFF\xD8") {
21 1         9 die "Does not look like a JPEG file: SOI missing at start of file";
22             }
23              
24             # first frame
25 3         6 my $cnt = 1;
26 3 0 33     6 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         15 local $/ = "\xFF\xD9\xFF\xD8"; # we look for EOI+SOI marker to avoid false-positives with embedded thumbs
34 3         28 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     7 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         18 $cnt++;
43             }
44 3         20 seek($io_file, 0,0); # rewind fh
45 3         6 pop(@frames); # last boundary is not beginning of a new frame
46 3         22 $frames[-1]->{length} += 2; # last frame won't end with EOI+SOI
47              
48 3         15 return \@frames;
49             }
50              
51             sub process { # mostly from Image::Info
52 5     5 1 22762 my $args = $_[1];
53              
54 5         18 my $fh;
55 5 100       23 if(ref($_[0]) eq 'SCALAR'){
56 2         573 require IO::String;
57 2 50       4559 $fh = IO::String->new($_[0]) or die "Error using scalar-ref for reading: $!";
58             }else{
59 3 100       231 open($fh, "<", $_[0]) or die "Error opening file for reading: $!";
60 2         17 binmode($fh);
61             }
62              
63 4 50 33     138 my $soi = my_read($fh, 2, ($args && $args->{debug} ? 1 : undef));
64 4 100       16 unless ($soi eq "\xFF\xD8") {
65 1         4 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         10 my %markers;
70             my @warnings;
71 3         8 while (1) {
72 9 50 33     45 my($ff, $mark) = unpack("CC", my_read($fh, 2, ($args && $args->{debug} ? 1 : undef)));
73 9 50       26 last if !defined $ff;
74              
75 9 50       21 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       23 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     42 last if $mark == 0xDA || $mark == 0xD9; # exit once we reach a SOS marker, or EOI (end of image)
94              
95 9 0 33     20 print "marker: FF ".sprintf("%#x",$mark)." \n" if $args && $args->{debug};
96 9         23 my $marker_pos = tell($fh) - 2;
97 9 50 33     57 my($len) = unpack("n", my_read($fh, 2, ($args && $args->{debug} ? 1 : undef))); # we found a marker, read its size
98 9 50       30 last if $len < 2; # data-less marker
99              
100 9 100       24 last if $mark < 0xE0; # data-less marker
101              
102             # process_chunk($info, $img_no, $mark, my_read($fh, $len - 2));
103 6 100       15 if($mark == 0xE0){
104 5 50 33     23 my $data = my_read($fh, $len - 2, ($args && $args->{debug} ? 1 : undef));
105 5 0 33     15 print "APP0 at ". $marker_pos ." len:$len \n" if $args && $args->{debug};
106              
107             # get_name($fh);
108 5         8 my $name;
109 5         9 my $rel_offset = 0;
110 5         15 for(0..10){ # app-identifiers may be arbitrarily long, but let's stop after 10 bytes
111 27         54 my ($value) = unpack("C", read_bytes(\$data, \$rel_offset, 1));
112 27 100       70 last if $value == 0x00;
113 22         40 $name .= chr($value);
114             }
115              
116 5         43 $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         17 seek($fh,$len - 2,1);
125             }
126              
127             }
128              
129             # print Dumper(\%markers,\@warnings);
130 3         61 return \%markers;
131             }
132              
133             sub my_read { # from Image::Info
134 31     31 0 60 my($fh, $len) = @_;
135 31 50       63 print " my_read: len:$len \n" if $_[2];
136 31         39 my $buf;
137 31         181 my $n = read($fh, $buf, $len);
138 31 50       223 die "read failed: $!" unless defined $n;
139 31 50       58 die "short read ($len/$n) at pos " . tell($fh) unless $n == $len;
140 31         113 $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 19428 my $ref = shift;
164 4         13 my $args = shift; # debug, future: version
165              
166 4 50 33     38 die "encode_ajpeg_data expects a hash-ref" unless $ref && ref($ref) eq 'HASH';
167              
168 4         10 my $binary;
169              
170 4         13 $binary = pack("C1",0); # version:0
171              
172 4         26 for my $key (keys %$ref){
173 37 50       104 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       83 if($key eq 'version'){
178 4 50 33     33 warn "encode_ajpeg_data: Only format version 0 is currenty implemented!" if $args && $args->{debug} && $ref->{$key} != 0;
      33        
179 4         11 next;
180             }
181 33 100       155 if($key eq 'delay'){
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
182 4 100       49 if($ref->{$key} <= 255){
    100          
183 2 50 33     17 print "encode_ajpeg_data: delay $ref->{$key} (byte)\n" if $args && $args->{debug};
184 2         12 $binary .= "\x01" . pack("C",$ref->{$key});
185             }elsif($ref->{$key} <= 65535){
186 1 50 33     8 print "encode_ajpeg_data: delay $ref->{$key} (short)\n" if $args && $args->{debug};
187 1         6 $binary .= "\x02" . pack("n",$ref->{$key});
188             }else{
189 1 50 33     10 print "encode_ajpeg_data: delay $ref->{$key} (long)\n" if $args && $args->{debug};
190 1         6 $binary .= "\x04" . pack("N",$ref->{$key});
191             }
192             }elsif($key eq 'repeat'){
193 4 100       18 if($ref->{$key} <= 255){
    50          
194 2 50 33     17 print "encode_ajpeg_data: repeat $ref->{$key} (byte)\n" if $args && $args->{debug};
195 2         10 $binary .= "\x11" . pack("C",$ref->{$key});
196             }elsif($ref->{$key} <= 65535){
197 2 50 33     16 print "encode_ajpeg_data: repeat $ref->{$key} (short)\n" if $args && $args->{debug};
198 2         10 $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       20 if($ref->{$key} <= 255){
    50          
204 2         9 $binary .= "\x21" . pack("C",$ref->{$key});
205             }elsif($ref->{$key} <= 65535){
206 2         8 $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       18 if($ref->{$key} <= 255){
    100          
212 1         6 $binary .= "\x31" . pack("C",$ref->{$key});
213             }elsif($ref->{$key} <= 65535){
214 2         12 $binary .= "\x32" . pack("n",$ref->{$key});
215             }else{
216 1         6 $binary .= "\x34" . pack("N",$ref->{$key});
217             }
218             }elsif($key eq 'previous'){
219 4 100       21 if($ref->{$key} <= 255){
    100          
220 1         6 $binary .= "\x41" . pack("C",$ref->{$key});
221             }elsif($ref->{$key} <= 65535){
222 2         11 $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       16 if($ref->{$key} <= 255){
    50          
228 2         13 $binary .= "\x51" . pack("C",$ref->{$key});
229             }elsif($ref->{$key} <= 65535){
230 2         11 $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       20 if($ref->{$key} <= 255){
    50          
236 2         15 $binary .= "\x61" . pack("C",$ref->{$key});
237             }elsif($ref->{$key} <= 65535){
238 2         9 $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       15 if($ref->{$key} <= 2){
244 4         24 $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       21 if($ref->{'metadata'}){ # A0
259 1 50 33     12 print "encode_ajpeg_data: metadata \n" if $args && $args->{debug};
260 1         4 for my $mkey (keys %{ $ref->{'metadata'} }){
  1         6  
261 4 50 33     26 print " metadata: ". $mkey .":". $ref->{'metadata'}->{$mkey} ."\n" if $args && $args->{debug};
262 4         18 my $mkey_utf8 = encode('utf-8',$mkey);
263 4         483 my $mvalue_utf8 = encode('utf-8',$ref->{'metadata'}->{$mkey});
264              
265 4         240 $binary .= pack("C",160) . pack("C",length($mkey_utf8)) . $mkey_utf8 . pack("C",length($mvalue_utf8)) . $mvalue_utf8;
266             }
267             }
268              
269 4         21 return $binary;
270             }
271              
272              
273             sub read_bytes {
274 118     118 0 192 my $binary_ref = shift;
275 118         168 my $offset_ref = shift; # quirk: offset as ref, so a read_bytes(), like core::read, advances a pointer/offset
276 118         143 my $length = shift;
277              
278             # print " read $length bytes at ${$offset_ref} \n";
279 118         169 my $data = substr(${$binary_ref}, ${$offset_ref}, $length);
  118         185  
  118         211  
280 118         223 ${$offset_ref} += $length;
  118         179  
281              
282 118         369 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 3280 my $binary = shift;
289 5         12 my $args = shift; # debug
290              
291 5 50       18 die "decode_ajpeg_data expects a scalar with binary data" unless defined($binary);
292              
293 5         13 my $length = length($binary);
294 5         10 my $offset = 0;
295              
296 5         10 my %ref;
297              
298 5 50 33     32 print "decode_ajpeg_data: length:$length \n" if $args && $args->{debug};
299 5         25 $ref{version} = unpack("C", read_bytes(\$binary,\$offset,1));
300 5 50 33     31 print "decode_ajpeg_data: version:$ref{version} \n" if $args && $args->{debug};
301              
302 5         12 my $cnt;
303 5         19 for(;;){
304 37         67 $cnt++;
305              
306 37         87 my $byte = read_bytes(\$binary,\$offset, 1);
307 37 50       95 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         89 my $key_num = unpack("C", $byte);
309 37 100       291 if($key_num == 1){
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
310 3         12 $ref{'delay'} = unpack("C", read_bytes(\$binary,\$offset,1));
311 3 50 33     41 print "decode_ajpeg_data: delay: $ref{'delay'} (byte) \n" if $args && $args->{debug};
312             }elsif($key_num == 2){
313 1         5 $ref{'delay'} = unpack("n", read_bytes(\$binary,\$offset,2));
314 1 50 33     11 print "decode_ajpeg_data: delay: $ref{'delay'} (short) \n" if $args && $args->{debug};
315             }elsif($key_num == 4){
316 1         6 $ref{'delay'} = unpack("N1", read_bytes(\$binary,\$offset,4));
317 1 50 33     11 print "decode_ajpeg_data: delay: $ref{'delay'} (long) \n" if $args && $args->{debug};
318             }elsif($key_num == 17){
319 2         9 $ref{'repeat'} = unpack("C", read_bytes(\$binary,\$offset,1));
320 2 50 33     17 print "decode_ajpeg_data: repeat: $ref{'repeat'} (byte) \n" if $args && $args->{debug};
321             }elsif($key_num == 18){
322 2         8 $ref{'repeat'} = unpack("n", read_bytes(\$binary,\$offset,2));
323 2 50 33     16 print "decode_ajpeg_data: repeat: $ref{'repeat'} (short) \n" if $args && $args->{debug};
324             }elsif($key_num == 33){
325 2         10 $ref{'parse_next'} = unpack("C", read_bytes(\$binary,\$offset,1));
326             }elsif($key_num == 34){
327 2         8 $ref{'parse_next'} = unpack("n", read_bytes(\$binary,\$offset,2));
328             }elsif($key_num == 49){
329 1         6 $ref{'length'} = unpack("C", read_bytes(\$binary,\$offset,1));
330             }elsif($key_num == 50){
331 2         9 $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         5 $ref{'previous'} = unpack("C", read_bytes(\$binary,\$offset,1));
336             }elsif($key_num == 66){
337 2         8 $ref{'previous'} = unpack("n", read_bytes(\$binary,\$offset,2));
338             }elsif($key_num == 68){
339 1         7 $ref{'previous'} = unpack("N", read_bytes(\$binary,\$offset,4));
340             }elsif($key_num == 81){
341 2         9 $ref{'x_offset'} = unpack("C", read_bytes(\$binary,\$offset,1));
342             }elsif($key_num == 82){
343 2         8 $ref{'x_offset'} = unpack("n", read_bytes(\$binary,\$offset,2));
344             }elsif($key_num == 97){
345 2         11 $ref{'y_offset'} = unpack("C", read_bytes(\$binary,\$offset,1));
346             }elsif($key_num == 98){
347 2         8 $ref{'y_offset'} = unpack("n", read_bytes(\$binary,\$offset,2));
348             }elsif($key_num == 113){
349 4         18 $ref{'dispose_op'} = unpack("C", read_bytes(\$binary,\$offset,1));
350             }elsif($key_num == 160){ # 0xA0
351 4         15 my $mkey_utf8 = read_bytes(\$binary,\$offset, unpack('C',read_bytes(\$binary,\$offset,1)) );
352 4         15 my $mvalue_utf8 = read_bytes(\$binary,\$offset, unpack('C',read_bytes(\$binary,\$offset,1)) );
353              
354 4 50       27 my $mkey = decode('utf-8', $mkey_utf8) if defined($mkey_utf8);
355 4 50       295 my $mvalue = decode('utf-8', $mvalue_utf8) if defined($mvalue_utf8);
356 4 50 33     250 print "decode_ajpeg_data: metadata: ". $mkey .":". $mvalue ." \n" if $args && $args->{debug};
357 4         21 $ref{'metadata'}->{$mkey} = $mvalue;
358             }
359             }
360 37 100       134 last if $offset >= $length;
361             }
362              
363 5         26 return \%ref;
364             }
365              
366             1;
367              
368             __END__