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