package JPEG::JFIF; $JPEG::JFIF::VERSION = '0.12'; use strict; sub new { my ($c, %args) = @_; my $class = ref($c) || $c; my %tagTab = ( object_name => 5, urgency => 10, category => 15, supplemental_categories => 20, photostation_ident => 22, keywords => 25, special_instructions => 40, byline_title => 55, created_time => 60, photostation_orig => 65, byline => 80, city => 90, province_state => 95, country_name => 101, original_transmission_reference => 103, headline => 105, credit => 110, source => 115, copyright_notice => 116, caption => 120, caption_writer => 122, photostation_note => 230, photostation_info => 231, ); $args{tagTab} = \%tagTab; bless \%args, $class; } # # Function to retrieve all possible data from JPEG file # sub getdata_all { my ($cl,$name) = @_; my $jpeg_data; if (!exists($cl->{bim})) { $cl->get8bimheaders(); } # parse every 8BIM foreach my $current_tag ( keys %{ $cl->{tagTab} } ) { my $idsearch = $cl->{tagTab}->{$current_tag}; foreach my $data (values %{ $cl->{bim} } ) { for( my $i = 0 ; $i < length($data) ; $i++) { if ( ( my $id = unpack( "n", substr( $data, $i, 2) ) ) == 0x1C02 ) { $i += 2; if ( unpack("C", substr( $data, $i, 1 ) ) == $idsearch ) { $i++; # length data in that subset my $len = unpack("n", substr( $data, $i, 2) ); $i += 2; # no strict "refs"; my $current_value = substr($data,$i,$len); if ( defined $jpeg_data->{ $current_tag } ) { if ( ref( $jpeg_data->{ $current_tag } ) eq 'ARRAY' ) { my @tmp_array = @{ $jpeg_data->{ $current_tag } }; push @tmp_array, $current_value; $jpeg_data->{ $current_tag } = \@tmp_array; } else { push my @tmp_array, $jpeg_data->{ $current_tag }; push @tmp_array, $current_value; $jpeg_data->{ $current_tag } = \@tmp_array; } } else { $jpeg_data->{ $current_tag } = $current_value; } } } } } } return $jpeg_data; } # # Function to retrieve certain ($name) field from JPEG file # sub getdata { my ($cl,$name) = @_; if (!exists($cl->{tagTab}->{$name})) { print STDERR "Tag \"$name\" not supported or misspelled (use lowercase)\n"; return(-1); }; my $idsearch = $cl->{tagTab}->{$name}; if (!exists($cl->{bim})) { $cl->get8bimheaders(); } # parse every 8BIM foreach my $data (values %{$cl->{bim}}) { for(my $i=0 ; $i{header})) { $header = $cl->getheader(); } my $count = 0; for (my $i=0; $i<$cl->{headsize}; $i++) { if (unpack("N",substr($header,$i,4)) == 0x3842494D) { $i += 4; # 8BIM ID my $id = unpack("n",substr($header,$i,2)); $i += 2; # 8BIM Name my $titlen = unpack("C",substr($header,$i,1)); $i += 1; my $tagname; if ($titlen != 0) { # Photoshop 6.0 $tagname = substr($header,$i,$titlen); $i += $titlen ; # if not parity len then add 0x00 (Adobe bug?!!) if (($titlen % 2) == 0) { $i++; } } else { # Photoshop 5.5 $i += 1; } # 8BIM Length my $bimlen = unpack("N",substr($header,$i,4)); $i += 4; $cl->{bim}->{$count++} = substr($header,$i,$bimlen); $i += $bimlen; # if not parity len then add 0x00 (Adobe bug?!!) if (($bimlen % 2) == 0) { $i--; } } } } sub getheader { my $cl = shift; if (!exists($cl->{filename}) || !exists($cl->{file})) { print STDERR "Read file first!\n"; return(-1); } for (my $i = 0;$i<$cl->{size};$i++) { if (unpack("n",substr($cl->{file},$i,2)) == 0xFFED) { $cl->{headsize} = unpack("n",substr($cl->{file},$i+2,2))-2; $cl->{header} = substr($cl->{file},$i+4,$cl->{headsize}); return($cl->{header}); } } } sub check { my $cl = shift; if (!exists($cl->{filename}) || !exists($cl->{file})) { return(-1); } if (unpack("n",substr($cl->{file},0,2)) != 0xFFD8) { print STDERR "Not JPEG file!\n"; return(-1); } } sub read { my ($cl,$filename) = @_; if (!open(FILE,"<".$filename)) { print STDERR "Couldn't open file $filename!\n"; return(-1); } binmode(FILE); while(read(FILE,my $buffer,1024) != 0) { $cl->{file}.=$buffer; }; $cl->{filename} = $filename; $cl->{size} = (stat(FILE))[7]; close(FILE); $cl->check(); } sub write { my $cl = shift; if (exists($cl->{filename}) || exists($cl->{file})) { open(FILE,">".$cl->{filename}) || print STDERR "Couldn't open file ".$cl->{filename}." for write!\n"; binmode(FILE); print FILE $cl->{file}; close(FILE); } else { print STDERR "Couldn't write file!\n"; } } 1; __END__ =head1 NAME JPEG::JFIF - JFIF/JPEG tags operations. =head1 VERSION JFIF.pm v. 0.12 =head1 CHANGES 0.12 - Closed ticket #40161 0.11 - added function getdata_all to retrieve all data as hash from file and some new fields (by Viljo Marrandi) 0.10 - rewrite code to support older and newest Adobe Photoshop JPEG/JFIF formats, and to have better API. 0.9.3 - another rule to workaround for that stupid 0x00 in APP14 (I couldn't find it in JFIF documentation) 0.9 - fix caption add 0x00 in some situations. I don't know what it is, But have to be. 0.8 - can set comment (Caption) tag correctly (hihi) 0.7 - can read all metatags =head1 SYNOPSIS This module can read additional info that is set by Adobe Photoshop in jpeg files (JFIF/JPEG format) =head1 DESCRIPTION This module can read additional info that is set by Adobe Photoshop in jpeg files (JFIF/JPEG format) Available sections name for getdata(name) are : object_name urgency category supplemental_categories photostation_ident keywords special_instructions byline_title created_time photostation_orig byline city province_state country_name original_transmission_reference headline credit source copyright_notice caption caption_writer photostation_note photostation_info =head1 EXAMPLE #!/usr/bin/perl use JPEG::JFIF; use strict; my $jfif = new JPEG::JFIF; # this give you "caption" tag content. $jfif->read("file.jpg"); print $jfif->getdata("caption"); =head1 COPYRIGHT AND LICENCE Copyright 2002-2008 Marcin Krzyzanowski Licence : Lesser General Public License v. 2.0 =head1 AUTHOR Marcin Krzyzanowski http://www.hakore.com/ =cut