From 970acb7344dc5e88b56aa159f31f109779646d5c Mon Sep 17 00:00:00 2001 From: Dmitry Karasik Date: Mon, 26 Aug 2024 22:58:56 +0200 Subject: [PATCH] support multiple entries for same jpeg marker observed a file with two JPEG_APP0+1 entries, one Exif, another XMP --- Prima/Image/Exif.pm | 45 +++++++++++++++++++++++---- examples/exif.pl | 9 ++---- img/codec_jpeg.c | 74 ++++++++++++++++++++++++++++++++++++--------- 3 files changed, 102 insertions(+), 26 deletions(-) diff --git a/Prima/Image/Exif.pm b/Prima/Image/Exif.pm index 97ffe2aa2..cb193fa95 100644 --- a/Prima/Image/Exif.pm +++ b/Prima/Image/Exif.pm @@ -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} ) { @@ -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; @@ -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; } @@ -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; @@ -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; @@ -1233,7 +1266,7 @@ L The module does not provide comprehensive support for tags and their values. For a more thorough dive see these modules: L, L. -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. =head1 AUTHOR diff --git a/examples/exif.pl b/examples/exif.pl index 54389752a..9f3fc12b0 100644 --- a/examples/exif.pl +++ b/examples/exif.pl @@ -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"; diff --git a/img/codec_jpeg.c b/img/codec_jpeg.c index f5763ad48..dce4e2d9e 100644 --- a/img/codec_jpeg.c +++ b/img/codec_jpeg.c @@ -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; @@ -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; } @@ -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; + } } }