1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.commons.imaging.formats.jpeg.xmp; 18 19 import java.io.ByteArrayOutputStream; 20 import java.io.File; 21 import java.io.IOException; 22 import java.io.InputStream; 23 import java.io.OutputStream; 24 import java.nio.charset.StandardCharsets; 25 import java.util.ArrayList; 26 import java.util.List; 27 28 import org.apache.commons.imaging.ImagingException; 29 import org.apache.commons.imaging.bytesource.ByteSource; 30 import org.apache.commons.imaging.formats.jpeg.JpegConstants; 31 32 /** 33 * Interface for Exif write/update/remove functionality for Jpeg/JFIF images. 34 */ 35 public class JpegXmpRewriter extends JpegRewriter { 36 37 /** 38 * Reads a JPEG image, removes all XMP XML (by removing the APP1 segment), and writes the result to a stream. 39 * <p> 40 * 41 * @param src Byte array containing JPEG image data. 42 * @param os OutputStream to write the image to. 43 * @throws ImagingException if it fails to read the JFIF segments 44 * @throws IOException if it fails to read or write the data from the segments 45 */ 46 public void removeXmpXml(final byte[] src, final OutputStream os) throws ImagingException, IOException { 47 final ByteSource byteSource = ByteSource.array(src); 48 removeXmpXml(byteSource, os); 49 } 50 51 /** 52 * Reads a JPEG image, removes all XMP XML (by removing the APP1 segment), and writes the result to a stream. 53 * <p> 54 * 55 * @param byteSource ByteSource containing JPEG image data. 56 * @param os OutputStream to write the image to. 57 * @throws ImagingException if it fails to read the JFIF segments 58 * @throws IOException if it fails to read or write the data from the segments 59 */ 60 public void removeXmpXml(final ByteSource byteSource, final OutputStream os) throws ImagingException, IOException { 61 final JFIFPieces jfifPieces = analyzeJfif(byteSource); 62 List<JFIFPiece> pieces = jfifPieces.pieces; 63 pieces = removeXmpSegments(pieces); 64 writeSegments(os, pieces); 65 } 66 67 /** 68 * Reads a JPEG image, removes all XMP XML (by removing the APP1 segment), and writes the result to a stream. 69 * <p> 70 * 71 * @param src Image file. 72 * @param os OutputStream to write the image to. 73 * 74 * @see java.io.File 75 * @see java.io.OutputStream 76 * @throws ImagingException if it fails to read the JFIF segments 77 * @throws IOException if it fails to read or write the data from the segments 78 */ 79 public void removeXmpXml(final File src, final OutputStream os) throws ImagingException, IOException { 80 final ByteSource byteSource = ByteSource.file(src); 81 removeXmpXml(byteSource, os); 82 } 83 84 /** 85 * Reads a JPEG image, removes all XMP XML (by removing the APP1 segment), and writes the result to a stream. 86 * <p> 87 * 88 * @param src InputStream containing JPEG image data. 89 * @param os OutputStream to write the image to. 90 * @throws ImagingException if it fails to read the JFIF segments 91 * @throws IOException if it fails to read or write the data from the segments 92 */ 93 public void removeXmpXml(final InputStream src, final OutputStream os) throws ImagingException, IOException { 94 final ByteSource byteSource = ByteSource.inputStream(src, null); 95 removeXmpXml(byteSource, os); 96 } 97 98 /** 99 * Reads a JPEG image, replaces the XMP XML and writes the result to a stream. 100 * 101 * @param src Byte array containing JPEG image data. 102 * @param os OutputStream to write the image to. 103 * @param xmpXml String containing XMP XML. 104 * @throws ImagingException if it fails to read the JFIF segments 105 * @throws IOException if it fails to read or write the data from the segments 106 * @throws ImagingException if it fails to write the JFIF segments 107 */ 108 public void updateXmpXml(final byte[] src, final OutputStream os, final String xmpXml) throws ImagingException, IOException, ImagingException { 109 final ByteSource byteSource = ByteSource.array(src); 110 updateXmpXml(byteSource, os, xmpXml); 111 } 112 113 /** 114 * Reads a JPEG image, replaces the XMP XML and writes the result to a stream. 115 * 116 * @param byteSource ByteSource containing JPEG image data. 117 * @param os OutputStream to write the image to. 118 * @param xmpXml String containing XMP XML. 119 * @throws ImagingException if it fails to read the JFIF segments 120 * @throws IOException if it fails to read or write the data from the segments 121 * @throws ImagingException if it fails to write the JFIF segments 122 */ 123 public void updateXmpXml(final ByteSource byteSource, final OutputStream os, final String xmpXml) throws ImagingException, IOException, ImagingException { 124 final JFIFPieces jfifPieces = analyzeJfif(byteSource); 125 List<JFIFPiece> pieces = jfifPieces.pieces; 126 pieces = removeXmpSegments(pieces); 127 128 final List<JFIFPieceSegment> newPieces = new ArrayList<>(); 129 final byte[] xmpXmlBytes = xmpXml.getBytes(StandardCharsets.UTF_8); 130 int index = 0; 131 while (index < xmpXmlBytes.length) { 132 final int segmentSize = Math.min(xmpXmlBytes.length, JpegConstants.MAX_SEGMENT_SIZE); 133 final byte[] segmentData = writeXmpSegment(xmpXmlBytes, index, segmentSize); 134 newPieces.add(new JFIFPieceSegment(JpegConstants.JPEG_APP1_MARKER, segmentData)); 135 index += segmentSize; 136 } 137 138 pieces = insertAfterLastAppSegments(pieces, newPieces); 139 140 writeSegments(os, pieces); 141 } 142 143 /** 144 * Reads a JPEG image, replaces the XMP XML and writes the result to a stream. 145 * 146 * @param src Image file. 147 * @param os OutputStream to write the image to. 148 * @param xmpXml String containing XMP XML. 149 * @throws ImagingException if it fails to read the JFIF segments 150 * @throws IOException if it fails to read or write the data from the segments 151 * @throws ImagingException if it fails to write the JFIF segments 152 */ 153 public void updateXmpXml(final File src, final OutputStream os, final String xmpXml) throws ImagingException, IOException, ImagingException { 154 final ByteSource byteSource = ByteSource.file(src); 155 updateXmpXml(byteSource, os, xmpXml); 156 } 157 158 /** 159 * Reads a JPEG image, replaces the XMP XML and writes the result to a stream. 160 * 161 * @param src InputStream containing JPEG image data. 162 * @param os OutputStream to write the image to. 163 * @param xmpXml String containing XMP XML. 164 * @throws ImagingException if it fails to read the JFIF segments 165 * @throws IOException if it fails to read or write the data from the segments 166 * @throws ImagingException if it fails to write the JFIF segments 167 */ 168 public void updateXmpXml(final InputStream src, final OutputStream os, final String xmpXml) throws ImagingException, IOException, ImagingException { 169 final ByteSource byteSource = ByteSource.inputStream(src, null); 170 updateXmpXml(byteSource, os, xmpXml); 171 } 172 173 private byte[] writeXmpSegment(final byte[] xmpXmlData, final int start, final int length) throws IOException { 174 final ByteArrayOutputStream os = new ByteArrayOutputStream(); 175 176 JpegConstants.XMP_IDENTIFIER.writeTo(os); 177 os.write(xmpXmlData, start, length); 178 179 return os.toByteArray(); 180 } 181 182 }