commons-user mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Joakim Knudsen <joakim.gr...@gmail.com>
Subject Re: Sanselan support for writing IPTC fields
Date Sat, 18 Mar 2017 08:28:45 GMT
Hi

I'm also using Sanselan to read and write both IPTC and EXIF data (in an
Android app setting).
Seems to be working as it should, but maybe we can help each other out if I
share my code?

I did a bit of research on EXIF tags, and got help from Phil Harvey at
http://www.sno.phy.queensu.ca/~phil/exiftool/index.html
I've also got a lot of hassle dealing with charsets, encoding and
"international characters" since I'm working from a Norwegian locale.
Letters like "æ", "ø", and "å" required extra care. What I learned was to
set UTF-16 encoding, and make sure to add proper Unicode markers to the
text to write:

byte[] unicodeMarker = new byte[]{ 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44,
0x45, 0x00 };
byte[] comment = textToSet.getBytes(ENCODING_UTF16); // OR UTF-16BE if
the file is big-endian!
byte[] bytesComment = new byte[unicodeMarker.length + comment.length];
System.arraycopy(unicodeMarker, 0, bytesComment, 0, unicodeMarker.length);
System.arraycopy(comment, 0, bytesComment, unicodeMarker.length,
comment.length);
TiffOutputField exif_comment = new
TiffOutputField(TiffConstants.EXIF_TAG_USER_COMMENT,
        TiffFieldTypeConstants.FIELD_TYPE_UNDEFINED,
bytesComment.length, bytesComment);

You can read more here: http://u88.n24.queensu.ca/exiftool/forum/index.php/
topic,7273.msg36826.html#msg36826



Since your question is on IPTC fields, I'll share the code I'm using for
"mirroring" tags in IPTC fields (I support both EXIF and IPTC, and maybe
later XMP). I have not done nearly as much research on IPTC as I did on
EXIF, so the following code is entirely based on sample/example code I
found online. It does seem to work, though

[image: Inline images 1]
If you fix your problems, probably we can improve the code together? Please
share what you learn!

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import android.content.ContentValues;
import android.util.Log;

import com.example.android.galleri.app.data.PhotoContract;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.sanselan.ImageReadException;
import org.apache.sanselan.ImageWriteException;
import org.apache.sanselan.Sanselan;
import org.apache.sanselan.common.IImageMetadata;
import org.apache.sanselan.common.byteSources.ByteSource;
import org.apache.sanselan.common.byteSources.ByteSourceFile;
import org.apache.sanselan.formats.jpeg.JpegImageMetadata;
import org.apache.sanselan.formats.jpeg.JpegImageParser;
import org.apache.sanselan.formats.jpeg.JpegPhotoshopMetadata;
import org.apache.sanselan.formats.jpeg.exifRewrite.ExifRewriter;
import org.apache.sanselan.formats.jpeg.iptc.IPTCRecord;
import org.apache.sanselan.formats.jpeg.iptc.IPTCType;
import org.apache.sanselan.formats.jpeg.iptc.JpegIptcRewriter;
import org.apache.sanselan.formats.jpeg.iptc.PhotoshopApp13Data;
import org.apache.sanselan.formats.tiff.TiffImageMetadata;
import org.apache.sanselan.formats.tiff.constants.TiffConstants;
import org.apache.sanselan.formats.tiff.constants.TiffFieldTypeConstants;
import org.apache.sanselan.formats.tiff.write.TiffOutputDirectory;
import org.apache.sanselan.formats.tiff.write.TiffOutputField;
import org.apache.sanselan.formats.tiff.write.TiffOutputSet;
import org.apache.sanselan.util.IOUtils;

public class ExifWriter
{

    private static String ENCODING_UTF8 = "UTF-8";
    private static String ENCODING_UTF16 = "UTF-16LE";

    private static boolean WRITE_IPTC_DATA = true;

    /**
     * This example illustrates how to add/update EXIF metadata in a JPEG file.
     *
     * @param jpegImageFile
     *            A source image file.
     * @param values
     *            tags to set + value
     * @throws IOException
     * @throws ImageReadException
     * @throws ImageWriteException
     */
    public static void changeExifMetadata(File jpegImageFile,
ContentValues values)
            throws IOException, ImageReadException, ImageWriteException
    {

        // read source file into byte array, and use another impl. of
updateExifMetadataLossless()
        byte[] data = FileUtils.readFileToByteArray(jpegImageFile);
        // NB! Make sure to read file (create byte array) BEFORE
opening output stream (to same file)!

        OutputStream os = null;
        try
        {
            TiffOutputSet outputSet = null;

            // note that metadata might be null if no metadata is found.
            IImageMetadata metadata = Sanselan.getMetadata(jpegImageFile);
            JpegImageMetadata jpegMetadata = (JpegImageMetadata) metadata;
            JpegPhotoshopMetadata PSmetadata = ((JpegImageMetadata)
metadata).getPhotoshop();

            if (null != jpegMetadata)
            {
                // note that exif might be null if no Exif metadata is found.
                TiffImageMetadata exif = jpegMetadata.getExif();

                if (null != exif)
                {
                    // TiffImageMetadata class is immutable (read-only).
                    // TiffOutputSet class represents the Exif data to write.
                    //
                    // Usually, we want to update existing Exif metadata by
                    // changing
                    // the values of a few fields, or adding a field.
                    // In these cases, it is easiest to use getOutputSet() to
                    // start with a "copy" of the fields read from the image.
                    outputSet = exif.getOutputSet();
                }
            }


            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            if (data != null)
                new ExifRewriter().updateExifMetadataLossless(data,
outputStream, outputSet);  // this closes outputstream (os)


            ByteArrayOutputStream dest = new ByteArrayOutputStream();
            if (!WRITE_IPTC_DATA) {
                // copy outputStream to dest
                dest = outputStream;

            } else {

                //
http://www.massapi.com/class/org/apache/sanselan/formats/jpeg/iptc/JpegIptcRewriter.html
                // showed how to initialize ListArrays (blocks,
records etc) when the JPEG does not have an IPTC block yet
                // 27/12-2016
                PhotoshopApp13Data newData;
                List<?> newBlocks = null;

                Map params = new HashMap();
                boolean ignoreImageData = false;
//isPhilHarveyTestImage(imageFile);
                boolean PARAM_KEY_READ_THUMBNAILS = false;
                params
                        .put(PARAM_KEY_READ_THUMBNAILS, new Boolean(
                                !ignoreImageData));
                // params.put(PARAM_KEY_VERBOSE, Boolean.TRUE);
                // params.put(PARAM_KEY_VERBOSE, Boolean.TRUE);

                if (PSmetadata != null)
                    newBlocks =
PSmetadata.photoshopApp13Data.getNonIptcBlocks();
                else
                    newBlocks = new ArrayList<>();

                // begin by getting ALL existing records (unless
PSmetadata == null)  <-- meaning, no IPTC blocks (APP14) yet
                List<IPTCRecord> newRecords;
                List removeRecords = new ArrayList();
                if (PSmetadata != null) {
                    newRecords = PSmetadata.photoshopApp13Data.getRecords();
                } else {
                    newRecords = new ArrayList<IPTCRecord>();
                }

                for (Map.Entry<String, Object> item : values.valueSet()) {
                    String key = item.getKey(); // getting key
                    Object o = values.get(key);  // may be null
                    if (o != null) {
                        String textToSet = "IPTC Caption/Abstract: " +
o.toString();

                        switch (key) {
                            case PhotoContract.PhotoEntry.COLUMN_IPTC_CAPTION: {

                                IPTCType captionAbstract120 = new
IPTCType(120, "Caption/Abstract");

                                if (PSmetadata != null) {
                                    for (int j = 0; j <
newRecords.size(); j++) {
                                        IPTCRecord record =
(IPTCRecord) newRecords.get(j);

                                        if (record.iptcType.type ==
captionAbstract120.type) {
                                            removeRecords.add(record);
                                        }
                                    }
                                }
                                newRecords.add(new
IPTCRecord(captionAbstract120, textToSet));

                                break;
                            }

                            default:
                                break;
                        }
                    }
                }

                if (PSmetadata != null) {
                    // remove old tags:
                    for (int j = 0; j < removeRecords.size(); j++) {
                        IPTCRecord record = (IPTCRecord) removeRecords.get(j);
                        newRecords.remove(record);
                    }
                }

                newData = new PhotoshopApp13Data(newRecords, newBlocks);

                // enrich the partially clothed byte[] with IPTC metadata
                InputStream src = new
ByteArrayInputStream(outputStream.toByteArray());

                new JpegIptcRewriter().writeIPTC(src, dest, newData);

            }

            // write output stream "dest" to file
            try {
                os = new FileOutputStream(jpegImageFile);
                os = new BufferedOutputStream(os);
                dest.writeTo(os);
            } catch(IOException ioe) {
                // Handle exception here
                ioe.printStackTrace();
            } finally {
                os.close();
            }


        } finally
        {
            if (os != null)
                try
                {
                    os.close();
                } catch (IOException e)
                {

                }
        }
    }


}



Joakim



On 3 March 2017 at 14:46, László Fenyvesi <fenyvesi.laszlo.78@gmail.com>
wrote:

> Dear Friends,
> I'm using Christopher Blunck's code to copy iptc/exif metadata:
> http://mail-archives.apache.org/mod_mbox/incubator-sanselan-
> dev/200811.mbox/%3C5609ED95-E659-4DE7-9EA0-EA2DBB76EE98@thebluncks.com%3E
> (Sanselan=Imaging)
> It usually works fine but fails on a lot of jpgs.
> E.g.:
> Enriched jpg:
>     Title: 'Mouth checkup'
>     Subject: 'Close-up of patient’s open mouth before oral checkup with
> mirror near by'
> Result jpg:
>     Title: 'Close-up of patient?s open mouth before oral checkup with
> mirror near by'
>     Subject: 'Close-up of patient?s open mouth before oral checkup with
> mirror near by'
>
> Here are the pics:
> http://movelex.hu/fenyoapa/user_commons_apache_org_imaging/
> iptc_exif_copy.html
>
> As you can see the result's Title and Subject became the same and ’ became
> ?.
>
> Can anybody tell me how to fix this?
> Thank you.
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: user-unsubscribe@commons.apache.org
> For additional commands, e-mail: user-help@commons.apache.org
>
>

Mime
  • Unnamed multipart/related (inline, None, 0 bytes)
View raw message