neoldschool has asked for the wisdom of the Perl Monks concerning the following question:

HI ! I want to add an APP segment in a jpg image with the Image::MetaData::JPEG module.

The following code add an APP2 segment inside the jpg, but it seems to be empty as it only add \0xffe200002 in the file, which means: an APP2 segment (\0xffe2) of length 2 (\0x0002) (the marker plus the length's length = 2, basically an empty segment).

perl run.pl test.jpg
use strict; use warnings; use Image::MetaData::JPEG; use Image::MetaData::JPEG::Record; use Image::MetaData::JPEG::data::Tables qw(:JPEGgrammar :Endianness :RecordTypes); use Data::Dumper; #read the image filname from the command line arguments my $file = new Image::MetaData::JPEG($ARGV[0]); #add MP header information in the APP2 segment in the image my $buffer; my $segref = new Image::MetaData::JPEG::Segment('APP2', $buffer, 'NOPA +RSE'); my $head = "MPF0"; my $recref = $segref->store_record('MP_HEADER', $LONG, \$head, 1); print Dumper $segref; $file->insert_segments($segref); #save $file->save('tmp1.jpg');

Output

$VAR1 = bless( { 'dataref' => \'', 'endianness' => undef, 'error' => undef, 'records' => [ bless( { 'values' => [ 1297106480 ], 'extra' => undef, 'key' => 'MP_HEADER', 'type' => 4 }, 'Image::MetaData::JPEG::Reco +rd' ) ], 'name' => 'APP2' }, 'Image::MetaData::JPEG::Segment' );

How can I add a segement in a JPG with this module, how to use the insert_segments function properly ? especially the $buffer variable. thanks !

Replies are listed 'Best First'.
Re: Add a segment in a JPG image with Image::MetaData::JPEG
by neoldschool (Initiate) on Jun 02, 2015 at 09:59 UTC

    I think I got it. I thought that the segment records where automatically written in the file, but apparently only the $buffer content is!

    This updated code actually write something in the segment, as I get the content of the record and write it to the buffer. So the segments's records are only there to tag/organize the data before write to the buffer ?
    #add MP header information in the APP2 segment for the image my $buffer; my $segref = new Image::MetaData::JPEG::Segment('APP2', \$buffer, 'NOP +ARSE'); my $head = "MPF0"; my $recref = $segref->store_record('MP_HEADER', $LONG, \$head, 1); $buffer = $recref->get_value();
Re: Add a segment in a JPG image with Image::MetaData::JPEG
by Anonymous Monk on Jun 03, 2015 at 09:52 UTC

    Hi, I am the original author of Image::MetaData::JPEG. Unfortunately APP2 segments are not really supported by this package (you can only parse two formats, and cannot modify them at high level).

    In general, once you have modified a segment, added or deleted records, etc., at low level, you have to call the segment update() method, in order to dump the logical representation of the segment into a binary block that will subsequently be written to file. This is not necessary if you use high-level functions to modify well-known blocks, because it is being taken care of by high-level code.

    You can still add APP2 segments if you can provide the binary block yourself though. Have a look at the following commented piece of code:

    use strict; use warnings; use Image::MetaData::JPEG; use Image::MetaData::JPEG::Record; use Image::MetaData::JPEG::data::Tables qw(:JPEGgrammar :Endianness :RecordTypes); #read the image filname from the command line arguments my $image = new Image::MetaData::JPEG($ARGV[0]); # This would be the correct procedure for adding records at low level # if the APP2 update routine existed at all (but it isn't implemented) # 1) create an empty APP2 segment # my $segref = # new Image::MetaData::JPEG::Segment('APP2', undef, 'NOPARSE'); # 2) store a record with a given key and value # my $head = "MPF0"; # $segref->store_record('MP_HEADER', $ASCII, \$head); # 3) IMPORTANT: update the binary block from the in memory # representation (this was the step you were missing) # $segref->update(); # But APP2 can "currently" only be parsed if it contains Flashpix # conversion information ("FPXR") or ICC profiles data. And there is # no update routine at all. So, either you extend the package or # create the entire binary block by hand in a local variable: # you skip parsing and just create the segment. my $buffer = "MPF0\0"; my $segref = new Image::MetaData::JPEG::Segment('APP2', \$buffer, 'NOPARSE'); # then insert it into the image $image->insert_segments($segref); # the segment is there, albeit without description, since unparsed foreach my $s ($image->get_segments('APP')) { print $s->get_description(); } # you can save to file nonetheless, unparsable segments aren't dropped my $new_file_name = 'tmp1.jpg'; $image->save($new_file_name); print "---------------------------------\n"; # now open the new file and check that APP2 is there (I avoid trying # to parse it, since parsing would fail with "Invalid APP2 segment" # in this case) my $new_image = new Image::MetaData::JPEG($new_file_name, 'APP[^2]'); foreach my $s ($new_image->get_segments('APP')) { print $s->get_description(); } print "---------------------------------\n"; # have a look at what's really inside your file: the APP2 segment # should be in the very first lines, unless your APP1 is huge. use Data::HexDump; my $hexdumper = new Data::HexDump; $hexdumper->file($new_file_name); $hexdumper->block_size(1024); print while $_ = $hexdumper->dump;

    For instance, if you generate a test .jpg file with Gimp and subject it to the previous Perl processing, you will obtain something that starts like this: (commented hexadecimal dump)

    FF E1 SOI ("Start of image") FF E0 APP0 segment starts here 00 10 length of APP0 (16, i.e., 14 to follow) ......... 14 data bytes in the APP0 segment FF E2 APP2 segment starts here 00 07 length of APP2 (7, i.e., 5 to follow) 4D 50 46 30 00 "MPF0\000" FF FE COM segment starts here 00 13 length of COM (19, i.e. 17 to follow) ......... "Created with GIMP", and so on

    Unfortunately, this is all I can offer now. It is clear that Image::MetaData::JPEG has fallen behind JPEG development, and a lot of parsing / dumping / modifying routines should be added and maintained, for which I have no time at all. The reason why the package is so conservative and restrictive is that it aims at correctness and information preservation (it never adds invalid bytes, but will also never drop unparsable segments). I would be glad to transfer to a new maintainer if anyone is available.

    Best regards, Stefano Bettelli

      Thanks for your reply. I did manage to write in the segment, by creating a new record with store_record and writing directly the record value in the segment buffer
      use strict; use warnings; use Image::MetaData::JPEG; use Image::MetaData::JPEG::Record; use Image::MetaData::JPEG::data::Tables qw(:JPEGgrammar :Endianness :RecordTypes); #read the image filname from the command line arguments my $file = new Image::MetaData::JPEG($ARGV[0], 1); #create the APP2 segment, do not parse it as it is empty my $buffer; #the buffer that will contains the data to write in the s +egment my $segref = new Image::MetaData::JPEG::Segment('APP2', \$buffer, 'NOP +ARSE'); my $MP_FORMAT_IDENTIFIER = chr(hex '4D').chr(hex '50').chr(hex '46').c +hr(hex '00'); my $recref_mp_fid = $segref->store_record('MP_FORMAT_IDENTIFIER', $ASCII, \$MP_FORMAT_IDENTIFIER, 4); my $NEXT_IFD_OFFSET = chr(hex '42'). chr(hex '00'). chr(hex '00'). chr(hex '00'); my $next_ifd_offset = $segref->store_record('NEXT_IFD_OFFSET', $BYTE, \$NEXT_IFD_OFFSET, 4); #End of the MPIndex IFD ########################################## ########################################## #write all the records to the segment buffer $buffer = $recref_mp_fid->get(); # ... $buffer .= $next_ifd_offset->get();
      It does not matter if APP2 field descriptions are not supported. I only use your module to write chunks of data.