Skip to content

Commit

Permalink
support multiple entries for same jpeg marker
Browse files Browse the repository at this point in the history
observed a file with two JPEG_APP0+1 entries, one Exif, another XMP
  • Loading branch information
dk committed Aug 26, 2024
1 parent acf6877 commit 970acb7
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 26 deletions.
45 changes: 39 additions & 6 deletions Prima/Image/Exif.pm
Original file line number Diff line number Diff line change
Expand Up @@ -993,9 +993,28 @@ sub read_jpeg
exists $i->{extras}->{appdata} and
$data = $i->{extras}->{appdata}->[1];

return ( $2, "XMP data" )
if $data =~ /^($XMP_HEADER)(.*)/s;
if (ref($data) ne 'ARRAY') {
return { "XMP data" => $2 }
if $data =~ /^($XMP_HEADER)(.*)/s;
return parse_datum( $class, $data, %opt );
} else {
my %res;
for my $datum ( @$data ) {
if ($datum =~ /^($XMP_HEADER)(.*)/s) {
$res{"XMP data"} = $2;
} else {
my ($r, $e) = parse_datum( $class, $datum, %opt );
return ($r, $e) unless ref $r;
%res = (%res, %$r);
}
}
return \%res;
}
}

sub parse_datum
{
my ( $class, $data, %opt ) = @_;
my ( $res, $error) = $class->parse($data);
if ( ref($res) ) {
if ( $res->{thumbnail} && $opt{load_thumbnail} ) {
Expand Down Expand Up @@ -1037,6 +1056,16 @@ sub write_jpeg

my $e = $i->{extras};
my %data = %$data;
my $xmp;

if ( exists $data{"XMP data"}) {
$xmp = delete $data{"XMP data"};
unless ( keys %data ) {
$i->{extras}->{appdata}->[1] = $xmp;
return 1;
}
}

if ( $data{thumbnail} && ref($data{thumbnail})) {
my $t = '';
open my $f, ">", \$t;
Expand All @@ -1059,6 +1088,9 @@ sub write_jpeg

my ($compiled, $error) = $class->compile(\%data);
return (undef, $error) if !defined $compiled;

$compiled = [$compiled, $xmp] if defined $xmp;

$i->{extras}->{appdata}->[1] = $compiled;
return 1;
}
Expand Down Expand Up @@ -1125,9 +1157,9 @@ extra appdata hash field.
tag_as_string => 1
);
if ( $error eq 'XMP data' && defined $data ) {
if ( $data && exists $data->{'XMP data') {
require XML::LibXML;
my $xml = XML::LibXML->load_xml(string => $data);
my $xml = XML::LibXML->load_xml(string => $data->{'XMP data'});
for my $node ($xml->findnodes('//*')) {
my @p = $node->childNodes;
next unless @p == 1;
Expand All @@ -1137,7 +1169,8 @@ extra appdata hash field.
$p =~ s{^\.}{};
print "$p: $p[0]\n";
}
($data, $error) = ({}, undef);
delete $data->{'XMP data'};
undef $error;
}
die "cannot read exif: $error\n" if defined $error;
Expand Down Expand Up @@ -1233,7 +1266,7 @@ L<Prima::image-load>
The module does not provide comprehensive support for tags and their values.
For a more thorough dive see these modules: L<Image::ExifTool>, L<Image::EXIF>.
The module doesn't parse XMP records but warns if get supplied these.
The module doesn't parse XMP records but returns them as raw strings.
The XMP records are easily parsed by any XML parser, f ex L<XML::LibXML>.
=head1 AUTHOR
Expand Down
9 changes: 3 additions & 6 deletions examples/exif.pl
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,10 @@ sub usage
my ($e, $ok) = Prima::Image::Exif->read_extras($i, tag_as_string => 1, load_thumbnail => 1);
die "Cannot read exif data in $ARGV[0]: $ok\n" unless $e;

if ( $ok eq 'XMP data') {
print $e;
exit;
}

for my $k ( sort keys %$e ) {
if ( $k eq 'thumbnail' ) {
if ( $k eq 'XMP data') {
print "$k:\n==================\n$e->{$k}\n=====================\n";
} elsif ( $k eq 'thumbnail' ) {
my $i = $e->{$k};
if ( ! ref $i) {
print "** warning: thumbnail present but cannot be loaded: $e->{$k}\n";
Expand Down
74 changes: 60 additions & 14 deletions img/codec_jpeg.c
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,18 @@ j_read_profile(j_decompress_ptr jpeg_info)
}
}

av_store( av, marker, newSVpv( name, length));
if (( sv = av_fetch( av, marker, 0)) != NULL) {
AV *av2;
if ( SvROK( *sv) && SvTYPE( SvRV( *sv)) == SVt_PVAV)
av2 = (AV*) SvRV( *sv);
else {
av2 = newAV();
av_push(av2, newSVsv( *sv));
av_store( av, marker, newRV_noinc((SV*) av2));
}
av_push(av2, newSVpv( name, length));
} else
av_store( av, marker, newSVpv( name, length));

free( name);
return true;
Expand Down Expand Up @@ -449,29 +460,51 @@ exif_find_angle_tag( unsigned char * c, STRLEN len, int wipe)
return 1;
}

static Bool
exif_extract_orientation(SV *sv, Bool wipe, int *ret)
{
STRLEN len;
int orientation;
unsigned char * c;
c = (unsigned char *) SvPV( sv, len );
if ((orientation = exif_find_orientation_tag( c, len, wipe )) > 0) {
if ( *ret == 0 ) *ret = orientation;
if ( !wipe ) return true;
}
if ((orientation = exif_find_angle_tag( c, len, wipe )) > 0) {
if ( *ret == 0 ) *ret = orientation;
if ( !wipe ) return true;
}
return false;
}

static int
exif_detect_orientation( HV * fp, int wipe )
{
int i, orientation, ret = 0;
int i, ret = 0;
AV * av;
SV ** sv = hv_fetch( fp, "appdata", 7, 0);
if ( !( sv && SvROK( *sv) && SvTYPE( SvRV( *sv)) == SVt_PVAV)) return 1;
av = (AV*) SvRV(*sv);
for ( i = 0; i <= av_len(av); i++) {
unsigned char * c;
STRLEN len;
SV ** ssv = av_fetch( av, i, 0);
if ( !ssv || !SvPOK( *ssv)) continue;
c = (unsigned char *) SvPV( *ssv, len );
if ((orientation = exif_find_orientation_tag( c, len, wipe )) > 0) {
if ( ret == 0 ) ret = orientation;
if ( !wipe ) break;
}
if ((orientation = exif_find_angle_tag( c, len, wipe )) > 0) {
if ( ret == 0 ) ret = orientation;
if ( !wipe ) break;
if ( !ssv ) continue;

if ( SvROK( *ssv) && SvTYPE( SvRV( *ssv)) == SVt_PVAV) {
int j;
AV *av2 = (AV*)(SvRV(*ssv));
for ( j = 0; j <= av_len(av2); j++) {
ssv = av_fetch( av2, j, 0);
if ( !ssv || !SvPOK(*ssv)) continue;
if ( exif_extract_orientation(*ssv, wipe, &ret))
goto STOP;
}
} else if ( SvPOK(*ssv)) {
if ( exif_extract_orientation(*ssv, wipe, &ret))
break;
}
}
STOP:
return ret;
}

Expand Down Expand Up @@ -945,9 +978,22 @@ save( PImgCodec instance, PImgSaveFileInstance fi)
SV ** sv;
for ( marker = 1; marker < 16; marker++) {
sv = av_fetch( appdata, marker, 0);
if ( sv && *sv && SvOK( *sv))
if ( !( sv && *sv && SvOK( *sv)))
continue;
if ( SvROK( *sv) && SvTYPE( SvRV( *sv)) == SVt_PVAV) {
int i;
AV *av2 = (AV*)(SvRV(*sv));
for ( i = 0; i <= av_len(av2); i++) {
sv = av_fetch( av2, i, 0);
if ( !( sv && *sv && SvOK( *sv)))
continue;
if ( !j_write_extras( fi, &l-> c, JPEG_APP0 + marker, *sv))
return false;
}
} else {
if ( !j_write_extras( fi, &l-> c, JPEG_APP0 + marker, *sv))
return false;
}
}
}

Expand Down

0 comments on commit 970acb7

Please sign in to comment.