line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Image::Info::AVIF; |
2
|
|
|
|
|
|
|
|
3
|
2
|
|
|
2
|
|
47
|
{ use 5.006; } |
|
2
|
|
|
|
|
7
|
|
4
|
2
|
|
|
2
|
|
12
|
use warnings; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
68
|
|
5
|
2
|
|
|
2
|
|
10
|
use strict; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
369
|
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
our $VERSION = "0.01"; |
8
|
|
|
|
|
|
|
|
9
|
1
|
|
|
1
|
0
|
6
|
sub die_for_info($) { die bless({ err=>$_[0] }, __PACKAGE__."::__ERROR__") } |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
BEGIN { |
12
|
2
|
50
|
|
2
|
|
24
|
if("$]" >= 5.008) { |
13
|
2
|
|
|
8
|
|
3237
|
*io_string = sub ($) { open(my $fh, "<", \$_[0]); $fh }; |
|
12
|
|
|
|
|
107
|
|
|
12
|
|
|
|
|
773
|
|
14
|
|
|
|
|
|
|
} else { |
15
|
0
|
|
|
|
|
0
|
require IO::String; |
16
|
0
|
|
|
|
|
0
|
*io_string = sub ($) { IO::String->new($_[0]) }; |
|
0
|
|
|
|
|
0
|
|
17
|
|
|
|
|
|
|
} |
18
|
|
|
|
|
|
|
} |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
sub read_block($$) { |
21
|
71
|
|
|
74
|
0
|
87
|
my($fh, $len) = @_; |
22
|
71
|
|
|
|
|
75
|
my $d = ""; |
23
|
70
|
|
|
|
|
64
|
while(1) { |
24
|
175
|
|
|
|
|
186
|
my $dlen = length($d); |
25
|
175
|
100
|
|
|
|
228
|
last if $dlen == $len; |
26
|
105
|
|
|
|
|
210
|
my $n = read($fh, $d, $len - $dlen, $dlen); |
27
|
140
|
50
|
|
|
|
220
|
if(!defined($n)) { |
|
|
50
|
|
|
|
|
|
28
|
70
|
|
|
|
|
151
|
die_for_info "read error: $!"; |
29
|
|
|
|
|
|
|
} elsif($n == 0) { |
30
|
35
|
|
|
|
|
89
|
die_for_info "truncated file"; |
31
|
|
|
|
|
|
|
} |
32
|
|
|
|
|
|
|
} |
33
|
105
|
|
|
|
|
228
|
return $d; |
34
|
|
|
|
|
|
|
} |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
sub read_nulterm($) { |
37
|
2
|
|
|
37
|
0
|
2
|
my($fh) = @_; |
38
|
2
|
|
|
|
|
3
|
my $d = do { local $/ = "\x00"; <$fh> }; |
|
37
|
|
|
|
|
99
|
|
|
3
|
|
|
|
|
11
|
|
39
|
3
|
50
|
33
|
|
|
13
|
defined($d) && $d =~ /\x00\z/ or die_for_info "truncated file"; |
40
|
3
|
|
|
|
|
10
|
chop $d; |
41
|
3
|
|
|
|
|
11
|
return $d; |
42
|
|
|
|
|
|
|
} |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
sub read_heif($$) { |
45
|
9
|
|
|
9
|
0
|
21
|
my($fh, $box_types_to_keep) = @_; |
46
|
9
|
|
|
|
|
11
|
my %boxes; |
47
|
9
|
|
|
|
|
34
|
while(!eof($fh)) { |
48
|
32
|
|
|
|
|
47
|
my($len, $type) = unpack("Na4", read_block($fh, 8)); |
49
|
32
|
|
|
|
|
41
|
my $pos = 8; |
50
|
32
|
|
|
|
|
47
|
my $bufp; |
51
|
42
|
100
|
66
|
|
|
171
|
if($type =~ $box_types_to_keep && !exists($boxes{$type})) { |
52
|
30
|
|
|
|
|
48
|
$boxes{$type} = ""; |
53
|
30
|
|
|
|
|
44
|
$bufp = \$boxes{$type}; |
54
|
|
|
|
|
|
|
} |
55
|
42
|
50
|
|
|
|
115
|
if($len == 1) { |
56
|
8
|
|
|
|
|
18
|
my($lenhi, $lenlo) = unpack("NN", read_block($fh, 8)); |
57
|
8
|
|
|
|
|
15
|
$pos += 8; |
58
|
14
|
|
|
|
|
28
|
$len = ($lenhi << 32) | $lenlo; |
59
|
0
|
0
|
|
|
|
0
|
$len >> 32 == $lenhi or die_for_info "box size overflow"; |
60
|
|
|
|
|
|
|
} |
61
|
28
|
50
|
|
|
|
41
|
$len >= $pos or die_for_info "bad box length"; |
62
|
28
|
|
|
|
|
28
|
$len -= $pos; |
63
|
28
|
|
|
|
|
40
|
while($len) { |
64
|
42
|
50
|
|
|
|
59
|
my $toread = $len < (1<<16) ? $len : (1<<16); |
65
|
42
|
|
|
|
|
53
|
my $d = read_block($fh, $toread); |
66
|
42
|
100
|
|
|
|
74
|
defined($bufp) and $$bufp .= $d; |
67
|
42
|
|
|
|
|
106
|
$len -= $toread; |
68
|
|
|
|
|
|
|
} |
69
|
|
|
|
|
|
|
} |
70
|
22
|
|
|
|
|
47
|
return \%boxes; |
71
|
|
|
|
|
|
|
} |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
my @primaries_type; |
74
|
|
|
|
|
|
|
$primaries_type[$_] = "RGB" foreach 1, 4, 5, 6, 7, 9, 11, 22; |
75
|
|
|
|
|
|
|
$primaries_type[10] = "CIEXYZ"; |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
sub process_file { |
78
|
16
|
|
|
6
|
0
|
37
|
my($info, $source) = @_; |
79
|
16
|
50
|
|
|
|
50
|
if(!eval { local $SIG{__DIE__}; |
|
6
|
|
|
|
|
24
|
|
80
|
3
|
|
|
|
|
13
|
my $boxes = read_heif($source, qr/\A(?:ftyp|meta)\z/); |
81
|
3
|
|
|
|
|
9
|
my $ftyp = $boxes->{ftyp}; |
82
|
3
|
50
|
|
|
|
10
|
defined $ftyp or die_for_info "no ftyp box"; |
83
|
3
|
50
|
33
|
|
|
14
|
length($ftyp) >= 8 && !(length($ftyp) & 3) |
84
|
|
|
|
|
|
|
or die_for_info "malformed ftyp box"; |
85
|
3
|
50
|
|
|
|
11
|
substr($ftyp, 0, 4) eq "avif" |
86
|
|
|
|
|
|
|
or die_for_info "major brand is not \"avif\""; |
87
|
3
|
|
|
|
|
10
|
$info->replace_info(0, file_media_type => "image/avif"); |
88
|
3
|
|
|
|
|
12
|
$info->replace_info(0, file_ext => "avif"); |
89
|
3
|
|
|
|
|
6
|
my $mboxes; |
90
|
|
|
|
|
|
|
{ |
91
|
3
|
|
|
|
|
9
|
my $meta = $boxes->{meta}; |
92
|
3
|
50
|
|
|
|
6
|
defined $meta or die_for_info "no meta box"; |
93
|
3
|
|
|
|
|
5
|
my $metafh = io_string($meta); |
94
|
3
|
50
|
|
|
|
6
|
read_block($metafh, 1) eq "\x00" |
95
|
|
|
|
|
|
|
or die_for_info "malformed meta box"; |
96
|
3
|
|
|
|
|
7
|
read_block($metafh, 3); |
97
|
3
|
|
|
|
|
11
|
$mboxes = read_heif($metafh, qr/\A(?:hdlr|iprp)\z/); |
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
{ |
100
|
3
|
|
|
|
|
7
|
my $hdlr = $mboxes->{hdlr}; |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
7
|
|
101
|
3
|
50
|
|
|
|
9
|
defined $hdlr or die_for_info "no hdlr box"; |
102
|
3
|
|
|
|
|
7
|
my $hdlrfh = io_string($hdlr); |
103
|
3
|
50
|
|
|
|
6
|
read_block($hdlrfh, 1) eq "\x00" |
104
|
|
|
|
|
|
|
or die_for_info "malformed hdlr box"; |
105
|
3
|
|
|
|
|
8
|
read_block($hdlrfh, 3); |
106
|
3
|
50
|
|
|
|
16
|
unpack("N", read_block($hdlrfh, 4)) == 0 |
107
|
|
|
|
|
|
|
or die_for_info "non-zero pre-defined value"; |
108
|
3
|
50
|
|
|
|
15
|
read_block($hdlrfh, 4) eq "pict" |
109
|
|
|
|
|
|
|
or die_for_info "handler type is not \"pict\""; |
110
|
3
|
|
|
|
|
6
|
read_block($hdlrfh, 12); |
111
|
3
|
|
|
|
|
8
|
read_nulterm($hdlrfh); |
112
|
|
|
|
|
|
|
} |
113
|
3
|
|
|
|
|
5
|
my $pboxes; |
114
|
|
|
|
|
|
|
{ |
115
|
3
|
|
|
|
|
5
|
my $iprp = $mboxes->{iprp}; |
|
3
|
|
|
|
|
6
|
|
116
|
3
|
50
|
|
|
|
7
|
defined $iprp or die_for_info "no iprp box"; |
117
|
3
|
|
|
|
|
4
|
my $iprpfh = io_string($iprp); |
118
|
3
|
|
|
|
|
10
|
$pboxes = read_heif($iprpfh, qr/\Aipco\z/); |
119
|
|
|
|
|
|
|
} |
120
|
3
|
|
|
|
|
6
|
my $cboxes; |
121
|
|
|
|
|
|
|
{ |
122
|
3
|
|
|
|
|
5
|
my $ipco = $pboxes->{ipco}; |
|
3
|
|
|
|
|
7
|
|
123
|
3
|
50
|
|
|
|
6
|
defined $ipco or die_for_info "no ipco box"; |
124
|
3
|
|
|
|
|
5
|
my $ipcofh = io_string($ipco); |
125
|
3
|
|
|
|
|
17
|
$cboxes = read_heif($ipcofh, |
126
|
|
|
|
|
|
|
qr/\A(?:irot|clap|ispe|pixi|colr|pasp)\z/); |
127
|
|
|
|
|
|
|
} |
128
|
3
|
|
|
|
|
7
|
my $rot = 0; |
129
|
3
|
50
|
|
|
|
6
|
if(defined(my $irot = $cboxes->{irot})) { |
130
|
1
|
0
|
|
|
|
4
|
length($irot) >= 1 or die_for_info "malformed irot box"; |
131
|
1
|
|
|
|
|
3
|
my($angle) = unpack("C", $irot); |
132
|
1
|
0
|
|
|
|
4
|
!($angle & -4) or die_for_info "malformed irot box"; |
133
|
0
|
0
|
|
|
|
0
|
$rot = 1 if $angle & 1; |
134
|
|
|
|
|
|
|
} |
135
|
2
|
50
|
|
|
|
7
|
if(defined(my $clap = $cboxes->{clap})) { |
|
|
50
|
|
|
|
|
|
136
|
0
|
0
|
|
|
|
0
|
length($clap) >= 32 or die_for_info "malformed clap box"; |
137
|
0
|
|
|
|
|
0
|
my($width_num, $width_den, $height_num, $height_den) = |
138
|
|
|
|
|
|
|
unpack("NNNN", $clap); |
139
|
1
|
0
|
0
|
|
|
4
|
$width_den != 0 && $height_den != 0 |
140
|
|
|
|
|
|
|
or die_for_info "malformed clap box"; |
141
|
0
|
|
|
|
|
0
|
my $width = int($width_num/$width_den); |
142
|
0
|
|
|
|
|
0
|
my $height = int($height_num/$height_den); |
143
|
0
|
0
|
|
|
|
0
|
($width, $height) = ($height, $width) if $rot; |
144
|
0
|
|
|
|
|
0
|
$info->replace_info(0, width => $width); |
145
|
0
|
|
|
|
|
0
|
$info->replace_info(0, height => $height); |
146
|
|
|
|
|
|
|
} elsif(defined(my $ispe = $cboxes->{ispe})) { |
147
|
2
|
50
|
|
|
|
5
|
length($ispe) >= 12 or die_for_info "malformed ispe box"; |
148
|
2
|
|
|
|
|
6
|
my($ver, undef, $width, $height) = unpack("Ca3NN", $ispe); |
149
|
2
|
50
|
|
|
|
5
|
$ver == 0 or die_for_info "malformed ispe box"; |
150
|
3
|
50
|
|
|
|
25
|
($width, $height) = ($height, $width) if $rot; |
151
|
3
|
|
|
|
|
11
|
$info->replace_info(0, width => $width); |
152
|
3
|
|
|
|
|
6
|
$info->replace_info(0, height => $height); |
153
|
|
|
|
|
|
|
} |
154
|
3
|
50
|
|
|
|
8
|
if(defined(my $pixi = $cboxes->{pixi})) { |
155
|
3
|
50
|
|
|
|
8
|
length($pixi) >= 5 or die_for_info "malformed pixi box"; |
156
|
3
|
|
|
|
|
6
|
my($ver, undef, $planes) = unpack("Ca3C", $pixi); |
157
|
3
|
50
|
|
|
|
8
|
$ver == 0 or die_for_info "malformed pixi box"; |
158
|
3
|
50
|
|
|
|
7
|
length($pixi) >= 5+$planes or die_for_info "malformed pixi box"; |
159
|
3
|
|
|
|
|
8
|
$info->replace_info(0, SamplesPerPixel => $planes); |
160
|
|
|
|
|
|
|
$info->replace_info(0, BitsPerSample => |
161
|
3
|
|
|
|
|
8
|
[ map { unpack(q(C), substr($pixi, 5+$_, 1)) } 0..$planes-1 ]); |
|
7
|
|
|
|
|
19
|
|
162
|
|
|
|
|
|
|
} |
163
|
3
|
50
|
|
|
|
8
|
if(defined(my $colr = $cboxes->{colr})) { |
164
|
3
|
50
|
|
|
|
6
|
length($colr) >= 4 or die_for_info "malformed colr box"; |
165
|
5
|
|
|
|
|
14
|
my $type = substr($colr, 0, 4); |
166
|
3
|
50
|
|
|
|
15
|
if($type eq "nclx") { |
167
|
3
|
50
|
|
|
|
14
|
length($colr) >= 11 or die_for_info "malformed colr box"; |
168
|
3
|
|
|
|
|
8
|
my($prim) = unpack("n", substr($colr, 4, 2)); |
169
|
3
|
50
|
|
|
|
7
|
if(defined(my $ctype = $primaries_type[$prim])) { |
170
|
3
|
|
|
|
|
8
|
$info->replace_info(0, color_type => $ctype); |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
} |
173
|
|
|
|
|
|
|
} |
174
|
3
|
50
|
|
|
|
14
|
if(defined(my $pasp = $cboxes->{pasp})) { |
175
|
1
|
0
|
|
|
|
4
|
length($pasp) >= 8 or die_for_info "malformed pasp box"; |
176
|
1
|
|
|
|
|
3
|
my($hspc, $vspc) = unpack("NN", $pasp); |
177
|
1
|
|
|
|
|
4
|
$info->replace_info(0, resolution => "$vspc/$hspc"); |
178
|
|
|
|
|
|
|
} |
179
|
2
|
|
|
|
|
17
|
1; |
180
|
|
|
|
|
|
|
}) { |
181
|
0
|
|
|
|
|
0
|
my $err = $@; |
182
|
0
|
0
|
|
|
|
0
|
if(ref($err) eq __PACKAGE__."::__ERROR__") { |
183
|
1
|
|
|
|
|
10
|
$info->replace_info(0, error => $err->{err}); |
184
|
|
|
|
|
|
|
} else { |
185
|
0
|
|
|
|
|
|
die $err; |
186
|
|
|
|
|
|
|
} |
187
|
|
|
|
|
|
|
} |
188
|
|
|
|
|
|
|
} |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
1; |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
=begin register |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
MAGIC: /\A....ftypavif/s |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
Supports the basic standard info key names. |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
=end register |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
=head1 NAME |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
Image::Info::AVIF - AV1 Image File Format support for Image::Info |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
=head1 SYNOPSIS |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
use Image::Info qw(image_info); |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
$info = image_info("image.avif"); |
209
|
|
|
|
|
|
|
if($error = $info->{error}) { |
210
|
|
|
|
|
|
|
die "Can't parse image info: $error\n"; |
211
|
|
|
|
|
|
|
} |
212
|
|
|
|
|
|
|
$color = $info->{color_type}; |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
=head1 DESCRIPTION |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
This module supplies information about AVIF files within the |
217
|
|
|
|
|
|
|
L system. It supports the basic standard info key names. |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
=head1 SEE ALSO |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
L |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
=head1 AUTHOR |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
Andrew Main (Zefram) |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
=head1 COPYRIGHT |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
Copyright (C) 2023 Andrew Main (Zefram) |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
=head1 LICENSE |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
This module is free software; you can redistribute it and/or modify it |
234
|
|
|
|
|
|
|
under the same terms as Perl itself. |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
=head1 ACKNOWLEDGEMENT |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
The development of this module was funded by |
239
|
|
|
|
|
|
|
Preisvergleich Internet Services AG. |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
=cut |