1a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling/*
2a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling * Copyright (C) 2013 The Android Open Source Project
3a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling *
4a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling * Licensed under the Apache License, Version 2.0 (the "License");
5a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling * you may not use this file except in compliance with the License.
6a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling * You may obtain a copy of the License at
7a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling *
8a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling *      http://www.apache.org/licenses/LICENSE-2.0
9a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling *
10a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling * Unless required by applicable law or agreed to in writing, software
11a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling * distributed under the License is distributed on an "AS IS" BASIS,
12a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling * See the License for the specific language governing permissions and
14a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling * limitations under the License.
15a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling */
16a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling
17a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberlingpackage com.android.camera.util;
18a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling
19a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberlingimport com.adobe.xmp.XMPException;
20a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberlingimport com.adobe.xmp.XMPMeta;
21a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberlingimport com.adobe.xmp.XMPMetaFactory;
22a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberlingimport com.adobe.xmp.options.SerializeOptions;
232bca210e5fc8a77685775ffb403096167b017dceAngus Kongimport com.android.camera.debug.Log;
24a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling
25a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberlingimport java.io.FileInputStream;
26a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberlingimport java.io.FileNotFoundException;
27a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberlingimport java.io.FileOutputStream;
28a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberlingimport java.io.IOException;
29a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberlingimport java.io.InputStream;
30a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberlingimport java.io.OutputStream;
31a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberlingimport java.io.UnsupportedEncodingException;
32a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberlingimport java.util.ArrayList;
33a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberlingimport java.util.List;
34a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling
35a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling/**
36a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling * Util class to read/write xmp from a jpeg image file. It only supports jpeg
37a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling * image format, and doesn't support extended xmp now.
38a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling * To use it:
39a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling * XMPMeta xmpMeta = XmpUtil.extractOrCreateXMPMeta(filename);
40a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling * xmpMeta.setProperty(PanoConstants.GOOGLE_PANO_NAMESPACE, "property_name", "value");
41a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling * XmpUtil.writeXMPMeta(filename, xmpMeta);
42a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling *
43a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling * Or if you don't care the existing XMP meta data in image file:
44a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling * XMPMeta xmpMeta = XmpUtil.createXMPMeta();
45a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling * xmpMeta.setPropertyBoolean(PanoConstants.GOOGLE_PANO_NAMESPACE, "bool_property_name", "true");
46a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling * XmpUtil.writeXMPMeta(filename, xmpMeta);
47a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling */
48a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberlingpublic class XmpUtil {
492bca210e5fc8a77685775ffb403096167b017dceAngus Kong  private static final Log.Tag TAG = new Log.Tag("XmpUtil");
50a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  private static final int XMP_HEADER_SIZE = 29;
51a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  private static final String XMP_HEADER = "http://ns.adobe.com/xap/1.0/\0";
52a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  private static final int MAX_XMP_BUFFER_SIZE = 65502;
53a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling
54a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  private static final String GOOGLE_PANO_NAMESPACE = "http://ns.google.com/photos/1.0/panorama/";
55a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  private static final String PANO_PREFIX = "GPano";
56a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling
57a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  private static final int M_SOI = 0xd8; // File start marker.
58a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  private static final int M_APP1 = 0xe1; // Marker for Exif or XMP.
59a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  private static final int M_SOS = 0xda; // Image data marker.
60a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling
61a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  // Jpeg file is composed of many sections and image data. This class is used
62a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  // to hold the section data from image file.
63a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  private static class Section {
64a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    public int marker;
65a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    public int length;
66a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    public byte[] data;
67a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  }
68a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling
69a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  static {
70a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    try {
71a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      XMPMetaFactory.getSchemaRegistry().registerNamespace(
72a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          GOOGLE_PANO_NAMESPACE, PANO_PREFIX);
73a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    } catch (XMPException e) {
74a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      e.printStackTrace();
75a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    }
76a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  }
77a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling
78a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  /**
79a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * Extracts XMPMeta from JPEG image file.
80a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   *
81a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * @param filename JPEG image file name.
82a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * @return Extracted XMPMeta or null.
83a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   */
84a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  public static XMPMeta extractXMPMeta(String filename) {
85a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    if (!filename.toLowerCase().endsWith(".jpg")
86a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        && !filename.toLowerCase().endsWith(".jpeg")) {
87a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      Log.d(TAG, "XMP parse: only jpeg file is supported");
88a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      return null;
89a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    }
90a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling
91a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    try {
92a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      return extractXMPMeta(new FileInputStream(filename));
93a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    } catch (FileNotFoundException e) {
94a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      Log.e(TAG, "Could not read file: " + filename, e);
95a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      return null;
96a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    }
97a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  }
98a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling
99a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  /**
100a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   *  Extracts XMPMeta from a JPEG image file stream.
101a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   *
102a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * @param is the input stream containing the JPEG image file.
103a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * @return Extracted XMPMeta or null.
104a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   */
105a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  public static XMPMeta extractXMPMeta(InputStream is) {
106a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    List<Section> sections = parse(is, true);
107a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    if (sections == null) {
108a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      return null;
109a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    }
110a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    // Now we don't support extended xmp.
111a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    for (Section section : sections) {
112a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      if (hasXMPHeader(section.data)) {
113a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        int end = getXMPContentEnd(section.data);
114a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        byte[] buffer = new byte[end - XMP_HEADER_SIZE];
115a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        System.arraycopy(
116a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling            section.data, XMP_HEADER_SIZE, buffer, 0, buffer.length);
117a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        try {
118a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          XMPMeta result = XMPMetaFactory.parseFromBuffer(buffer);
119a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          return result;
120a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        } catch (XMPException e) {
121a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          Log.d(TAG, "XMP parse error", e);
122a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          return null;
123a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        }
124a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      }
125a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    }
126a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    return null;
127a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  }
128a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling
129a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  /**
130a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * Creates a new XMPMeta.
131a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   */
132a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  public static XMPMeta createXMPMeta() {
133a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    return XMPMetaFactory.create();
134a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  }
135a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling
136a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  /**
137a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * Tries to extract XMP meta from image file first, if failed, create one.
138a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   */
139a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  public static XMPMeta extractOrCreateXMPMeta(String filename) {
140a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    XMPMeta meta = extractXMPMeta(filename);
141a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    return meta == null ? createXMPMeta() : meta;
142a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  }
143a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling
144a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  /**
145a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * Writes the XMPMeta to the jpeg image file.
146a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   */
147a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  public static boolean writeXMPMeta(String filename, XMPMeta meta) {
148a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    if (!filename.toLowerCase().endsWith(".jpg")
149a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        && !filename.toLowerCase().endsWith(".jpeg")) {
150a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      Log.d(TAG, "XMP parse: only jpeg file is supported");
151a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      return false;
152a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    }
153a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    List<Section> sections = null;
154a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    try {
155a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      sections = parse(new FileInputStream(filename), false);
156a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      sections = insertXMPSection(sections, meta);
157a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      if (sections == null) {
158a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        return false;
159a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      }
160a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    } catch (FileNotFoundException e) {
161a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      Log.e(TAG, "Could not read file: " + filename, e);
162a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      return false;
163a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    }
164a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    FileOutputStream os = null;
165a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    try {
166a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      // Overwrite the image file with the new meta data.
167a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      os = new FileOutputStream(filename);
168a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      writeJpegFile(os, sections);
169a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    } catch (IOException e) {
170a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      Log.d(TAG, "Write file failed:" + filename, e);
171a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      return false;
172a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    } finally {
173a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      if (os != null) {
174a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        try {
175a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          os.close();
176a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        } catch (IOException e) {
177a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          // Ignore.
178a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        }
179a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      }
180a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    }
181a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    return true;
182a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  }
183a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling
184a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  /**
185a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * Updates a jpeg file from inputStream with XMPMeta to outputStream.
186a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   */
187a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  public static boolean writeXMPMeta(InputStream inputStream, OutputStream outputStream,
188a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      XMPMeta meta) {
189a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    List<Section> sections = parse(inputStream, false);
190a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      sections = insertXMPSection(sections, meta);
191a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      if (sections == null) {
192a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        return false;
193a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      }
194a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    try {
195a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      // Overwrite the image file with the new meta data.
196a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      writeJpegFile(outputStream, sections);
197a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    } catch (IOException e) {
198a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      Log.d(TAG, "Write to stream failed", e);
199a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      return false;
200a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    } finally {
201a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      if (outputStream != null) {
202a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        try {
203a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          outputStream.close();
204a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        } catch (IOException e) {
205a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          // Ignore.
206a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        }
207a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      }
208a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    }
209a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    return true;
210a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  }
211a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling
212a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  /**
213a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * Write a list of sections to a Jpeg file.
214a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   */
215a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  private static void writeJpegFile(OutputStream os, List<Section> sections)
216a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      throws IOException {
217a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    // Writes the jpeg file header.
218a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    os.write(0xff);
219a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    os.write(M_SOI);
220a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    for (Section section : sections) {
221a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      os.write(0xff);
222a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      os.write(section.marker);
223a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      if (section.length > 0) {
224a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        // It's not the image data.
225a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        int lh = section.length >> 8;
226a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        int ll = section.length & 0xff;
227a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        os.write(lh);
228a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        os.write(ll);
229a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      }
230a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      os.write(section.data);
231a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    }
232a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  }
233a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling
234a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  private static List<Section> insertXMPSection(
235a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      List<Section> sections, XMPMeta meta) {
236a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    if (sections == null || sections.size() <= 1) {
237a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      return null;
238a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    }
239a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    byte[] buffer;
240a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    try {
241a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      SerializeOptions options = new SerializeOptions();
242a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      options.setUseCompactFormat(true);
243a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      // We have to omit packet wrapper here because
244a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      // javax.xml.parsers.DocumentBuilder
245a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      // fails to parse the packet end <?xpacket end="w"?> in android.
246a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      options.setOmitPacketWrapper(true);
247a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      buffer = XMPMetaFactory.serializeToBuffer(meta, options);
248a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    } catch (XMPException e) {
249a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      Log.d(TAG, "Serialize xmp failed", e);
250a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      return null;
251a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    }
252a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    if (buffer.length > MAX_XMP_BUFFER_SIZE) {
253a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      // Do not support extended xmp now.
254a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      return null;
255a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    }
256a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    // The XMP section starts with XMP_HEADER and then the real xmp data.
257a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    byte[] xmpdata = new byte[buffer.length + XMP_HEADER_SIZE];
258a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    System.arraycopy(XMP_HEADER.getBytes(), 0, xmpdata, 0, XMP_HEADER_SIZE);
259a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    System.arraycopy(buffer, 0, xmpdata, XMP_HEADER_SIZE, buffer.length);
260a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    Section xmpSection = new Section();
261a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    xmpSection.marker = M_APP1;
262a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    // Adds the length place (2 bytes) to the section length.
263a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    xmpSection.length = xmpdata.length + 2;
264a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    xmpSection.data = xmpdata;
265a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling
266a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    for (int i = 0; i < sections.size(); ++i) {
267a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      // If we can find the old xmp section, replace it with the new one.
268a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      if (sections.get(i).marker == M_APP1
269a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          && hasXMPHeader(sections.get(i).data)) {
270a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        // Replace with the new xmp data.
271a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        sections.set(i, xmpSection);
272a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        return sections;
273a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      }
274a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    }
275a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    // If the first section is Exif, insert XMP data before the second section,
276a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    // otherwise, make xmp data the first section.
277a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    List<Section> newSections = new ArrayList<Section>();
278a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    int position = (sections.get(0).marker == M_APP1) ? 1 : 0;
279a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    newSections.addAll(sections.subList(0, position));
280a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    newSections.add(xmpSection);
281a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    newSections.addAll(sections.subList(position, sections.size()));
282a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    return newSections;
283a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  }
284a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling
285a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  /**
286a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * Checks whether the byte array has XMP header. The XMP section contains
287a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * a fixed length header XMP_HEADER.
288a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   *
289a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * @param data Xmp metadata.
290a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   */
291a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  private static boolean hasXMPHeader(byte[] data) {
292a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    if (data.length < XMP_HEADER_SIZE) {
293a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      return false;
294a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    }
295a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    try {
296a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      byte[] header = new byte[XMP_HEADER_SIZE];
297a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      System.arraycopy(data, 0, header, 0, XMP_HEADER_SIZE);
298a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      if (new String(header, "UTF-8").equals(XMP_HEADER)) {
299a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        return true;
300a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      }
301a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    } catch (UnsupportedEncodingException e) {
302a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      return false;
303a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    }
304a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    return false;
305a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  }
306a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling
307a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  /**
308a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * Gets the end of the xmp meta content. If there is no packet wrapper,
309a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * return data.length, otherwise return 1 + the position of last '>'
310a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * without '?' before it.
311a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * Usually the packet wrapper end is "<?xpacket end="w"?> but
312a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * javax.xml.parsers.DocumentBuilder fails to parse it in android.
313a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   *
314a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * @param data xmp metadata bytes.
315a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * @return The end of the xmp metadata content.
316a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   */
317a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  private static int getXMPContentEnd(byte[] data) {
318a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    for (int i = data.length - 1; i >= 1; --i) {
319a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      if (data[i] == '>') {
320a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        if (data[i - 1] != '?') {
321a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          return i + 1;
322a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        }
323a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      }
324a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    }
325a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    // It should not reach here for a valid xmp meta.
326a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    return data.length;
327a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  }
328a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling
329a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  /**
330a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * Parses the jpeg image file. If readMetaOnly is true, only keeps the Exif
331a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * and XMP sections (with marker M_APP1) and ignore others; otherwise, keep
332a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * all sections. The last section with image data will have -1 length.
333a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   *
334a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * @param is Input image data stream.
335a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * @param readMetaOnly Whether only reads the metadata in jpg.
336a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   * @return The parse result.
337a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling   */
338a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  private static List<Section> parse(InputStream is, boolean readMetaOnly) {
339a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    try {
340a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      if (is.read() != 0xff || is.read() != M_SOI) {
341a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        return null;
342a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      }
343a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      List<Section> sections = new ArrayList<Section>();
344a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      int c;
345a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      while ((c = is.read()) != -1) {
346a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        if (c != 0xff) {
347a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          return null;
348a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        }
349a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        // Skip padding bytes.
350a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        while ((c = is.read()) == 0xff) {
351a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        }
352a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        if (c == -1) {
353a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          return null;
354a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        }
355a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        int marker = c;
356a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        if (marker == M_SOS) {
357a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          // M_SOS indicates the image data will follow and no metadata after
358a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          // that, so read all data at one time.
359a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          if (!readMetaOnly) {
360a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling            Section section = new Section();
361a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling            section.marker = marker;
362a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling            section.length = -1;
363a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling            section.data = new byte[is.available()];
364a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling            is.read(section.data, 0, section.data.length);
365a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling            sections.add(section);
366a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          }
367a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          return sections;
368a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        }
369a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        int lh = is.read();
370a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        int ll = is.read();
371a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        if (lh == -1 || ll == -1) {
372a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          return null;
373a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        }
374a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        int length = lh << 8 | ll;
375a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        if (!readMetaOnly || c == M_APP1) {
376a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          Section section = new Section();
377a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          section.marker = marker;
378a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          section.length = length;
379a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          section.data = new byte[length - 2];
380a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          is.read(section.data, 0, length - 2);
381a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          sections.add(section);
382a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        } else {
383a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          // Skip this section since all exif/xmp meta will be in M_APP1
384a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          // section.
385a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          is.skip(length - 2);
386a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        }
387a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      }
388a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      return sections;
389a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    } catch (IOException e) {
390a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      Log.d(TAG, "Could not parse file.", e);
391a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      return null;
392a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    } finally {
393a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      if (is != null) {
394a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        try {
395a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          is.close();
396a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        } catch (IOException e) {
397a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling          // Ignore.
398a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling        }
399a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling      }
400a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling    }
401a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  }
402a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling
403a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling  private XmpUtil() {}
404a5a08d7642a1fdf961b057cc90e76c4c93103c15Sascha Haeberling}
405