<commit_before>/**
 * Copyright (c) 2014 by <NAME>.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Any modifications to this file must keep this entire header intact.
 *
 * Change History - most recent changes go on top of previous changes
 *
 * Exif.java
 *
 * Who   Date       Description
 * ====  =======    =================================================
 * WY    14Jan2015  Moved thumbnail related code to ExifThumbnail
 * WY    06May2014  Complete rewrite to support adding thumbnail IFD
 */

package cafe.image.meta.exif;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import cafe.image.jpeg.Marker;
import cafe.image.meta.Metadata;
import cafe.image.meta.MetadataReader;
import cafe.image.meta.MetadataType;
import cafe.image.tiff.ASCIIField;
import cafe.image.tiff.IFD;
import cafe.image.tiff.LongField;
import cafe.image.tiff.RationalField;
import cafe.image.tiff.ShortField;
import cafe.image.tiff.Tag;
import cafe.image.tiff.TiffField;
import cafe.image.tiff.TiffTag;
import cafe.io.IOUtils;
import cafe.io.MemoryCacheRandomAccessOutputStream;
import cafe.io.RandomAccessOutputStream;
import cafe.io.WriteStrategyMM;

/**
 * EXIF wrapper
 *  
 * @author <NAME>, <EMAIL>
 * @version 1.0 01/08/2014
 */
public class Exif extends Metadata {
	// EXIF thumbnail flavors
	public static final int EXIF_FLAVOR_JPG = 0xFFD8;//JPEG SOI
	public static final int EXIF_FLAVOR_TIFF = 0x4D4D;//TIFF MM
	
	private int flavor;	
	private IFD imageIFD;
	private IFD exifSubIFD;
	private IFD gpsSubIFD;
	private ExifThumbnail thumbnail;
	private int firstIFDOffset = 0x08;
	
	private MetadataReader reader;
	
	private boolean isThumbnailRequired;
	
	public Exif(int flavor) {
		super(MetadataType.EXIF, null); 
		if(flavor != Exif.EXIF_FLAVOR_JPG && flavor != Exif.EXIF_FLAVOR_TIFF)
			throw new IllegalArgumentException("Unknown EXIF flavor: " + flavor);
		this.flavor = flavor;
		if(flavor == Exif.EXIF_FLAVOR_JPG)
			createImageIFD();		
	}
	
	public Exif(byte[] data) {
		super(MetadataType.EXIF, data);
		this.reader = new ExifReader(data);
	}
	
	public Exif(InputStream is) throws IOException {
		this(IOUtils.inputStreamToByteArray(is));
	}
	
	private void createImageIFD() {
		// Create Image IFD (IFD0)
		imageIFD = new IFD();
		TiffField<?> tiffField = new ASCIIField(TiffTag.IMAGE_DESCRIPTION.getValue(), "Exif created by JPEGTweaker\0");
		imageIFD.addField(tiffField);
		tiffField = new ASCIIField(TiffTag.MAKE.getValue(), "Yu's\0");
		imageIFD.addField(tiffField);
		tiffField = new ASCIIField(TiffTag.MODEL.getValue(), "JPEGTweaker 1.0\0");
		imageIFD.addField(tiffField);
		tiffField = new ShortField(TiffTag.ORIENTATION.getValue(), new short[]{1});
		imageIFD.addField(tiffField);
		tiffField = new RationalField(TiffTag.X_RESOLUTION.getValue(), new int[] {300, 1});
		imageIFD.addField(tiffField);
		tiffField = new RationalField(TiffTag.Y_RESOLUTION.getValue(), new int[] {300, 1});
		imageIFD.addField(tiffField);
		tiffField = new ShortField(TiffTag.RESOLUTION_UNIT.getValue(), new short[]{2});
		imageIFD.addField(tiffField);
		String softWare = "JPEGTweaker 1.0\0";
		tiffField = new ASCIIField(TiffTag.SOFTWARE.getValue(), softWare);
		imageIFD.addField(tiffField);
		DateFormat formatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
		tiffField = new ASCIIField(TiffTag.DATETIME.getValue(), formatter.format(new Date()) + '\0');
		imageIFD.addField(tiffField);
		tiffField = new LongField(TiffTag.EXIF_SUB_IFD.getValue(), new int[]{0}); // Place holder
		imageIFD.addField(tiffField);
	}
	
	public boolean hasThumbnail() {
		return thumbnail != null && thumbnail.containsImage();
	}
	
	public void addExif(IFD exifSubIFD) {
		if(imageIFD != null)
			imageIFD.addChild(TiffTag.EXIF_SUB_IFD, exifSubIFD);
		else
			this.exifSubIFD = exifSubIFD;
	}
	
	public void addGPS(IFD gpsSubIFD) {
		if(imageIFD != null)
			imageIFD.addChild(TiffTag.GPS_SUB_IFD, gpsSubIFD);
		else
			this.gpsSubIFD = gpsSubIFD;
	}
	
	/**
	 * @param thumbnail optional thumbnail image. If null, will be generated from the input image
	 */
	public void addThumbnail(BufferedImage thumbnail) {
		this.thumbnail = new ExifThumbnail(flavor, thumbnail);
		this.isThumbnailRequired = true;
	}
	
	public boolean isThumbnailRequired() {
		return isThumbnailRequired;
	}	
	
	public void setThumbnail(BufferedImage thumbnail) {
		this.thumbnail.setImage(thumbnail);
	}
	
	public IFD getIFD(Tag tag) {
		if(tag.getValue() == TiffTag.EXIF_SUB_IFD.getValue()) {
			return exifSubIFD;
		} else if(tag.getValue() == TiffTag.GPS_SUB_IFD.getValue()) {<commit_msg>Added code to set IFD for TIFF EXIF image IFD<commit_after>/**
 * Copyright (c) 2014 by <NAME>.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Any modifications to this file must keep this entire header intact.
 *
 * Change History - most recent changes go on top of previous changes
 *
 * Exif.java
 *
 * Who   Date       Description
 * ====  =======    =================================================
 * WY    14Jan2015  Moved thumbnail related code to ExifThumbnail
 * WY    06May2014  Complete rewrite to support adding thumbnail IFD
 */

package cafe.image.meta.exif;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import cafe.image.jpeg.Marker;
import cafe.image.meta.Metadata;
import cafe.image.meta.MetadataReader;
import cafe.image.meta.MetadataType;
import cafe.image.tiff.ASCIIField;
import cafe.image.tiff.IFD;
import cafe.image.tiff.LongField;
import cafe.image.tiff.RationalField;
import cafe.image.tiff.ShortField;
import cafe.image.tiff.Tag;
import cafe.image.tiff.TiffField;
import cafe.image.tiff.TiffTag;
import cafe.io.IOUtils;
import cafe.io.MemoryCacheRandomAccessOutputStream;
import cafe.io.RandomAccessOutputStream;
import cafe.io.WriteStrategyMM;

/**
 * EXIF wrapper
 *  
 * @author <NAME>, <EMAIL>
 * @version 1.0 01/08/2014
 */
public class Exif extends Metadata {
	// EXIF thumbnail flavors
	public static final int EXIF_FLAVOR_JPG = 0xFFD8;//JPEG SOI
	public static final int EXIF_FLAVOR_TIFF = 0x4D4D;//TIFF MM
	
	private int flavor;	
	private IFD imageIFD;
	private IFD exifSubIFD;
	private IFD gpsSubIFD;
	private ExifThumbnail thumbnail;
	private int firstIFDOffset = 0x08;
	
	private MetadataReader reader;
	
	private boolean isThumbnailRequired;
	
	public Exif(int flavor) {
		super(MetadataType.EXIF, null); 
		if(flavor != Exif.EXIF_FLAVOR_JPG && flavor != Exif.EXIF_FLAVOR_TIFF)
			throw new IllegalArgumentException("Unknown EXIF flavor: " + flavor);
		this.flavor = flavor;
		if(flavor == Exif.EXIF_FLAVOR_JPG)
			createImageIFD();		
	}
	
	public Exif(byte[] data) {
		super(MetadataType.EXIF, data);
		this.reader = new ExifReader(data);
	}
	
	public Exif(InputStream is) throws IOException {
		this(IOUtils.inputStreamToByteArray(is));
	}
	
	public Exif(IFD imageIFD) {
		this(Exif.EXIF_FLAVOR_TIFF);
		this.reader = new ExifReader(imageIFD);
	}
	
	private void createImageIFD() {
		// Create Image IFD (IFD0)
		imageIFD = new IFD();
		TiffField<?> tiffField = new ASCIIField(TiffTag.IMAGE_DESCRIPTION.getValue(), "Exif created by JPEGTweaker\0");
		imageIFD.addField(tiffField);
		tiffField = new ASCIIField(TiffTag.MAKE.getValue(), "Yu's\0");
		imageIFD.addField(tiffField);
		tiffField = new ASCIIField(TiffTag.MODEL.getValue(), "JPEGTweaker 1.0\0");
		imageIFD.addField(tiffField);
		tiffField = new ShortField(TiffTag.ORIENTATION.getValue(), new short[]{1});
		imageIFD.addField(tiffField);
		tiffField = new RationalField(TiffTag.X_RESOLUTION.getValue(), new int[] {300, 1});
		imageIFD.addField(tiffField);
		tiffField = new RationalField(TiffTag.Y_RESOLUTION.getValue(), new int[] {300, 1});
		imageIFD.addField(tiffField);
		tiffField = new ShortField(TiffTag.RESOLUTION_UNIT.getValue(), new short[]{2});
		imageIFD.addField(tiffField);
		String softWare = "JPEGTweaker 1.0\0";
		tiffField = new ASCIIField(TiffTag.SOFTWARE.getValue(), softWare);
		imageIFD.addField(tiffField);
		DateFormat formatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
		tiffField = new ASCIIField(TiffTag.DATETIME.getValue(), formatter.format(new Date()) + '\0');
		imageIFD.addField(tiffField);
		tiffField = new LongField(TiffTag.EXIF_SUB_IFD.getValue(), new int[]{0}); // Place holder
		imageIFD.addField(tiffField);
	}
	
	public boolean hasThumbnail() {
		return thumbnail != null && thumbnail.containsImage();
	}
	
	public void addExif(IFD exifSubIFD) {
		if(imageIFD != null)
			imageIFD.addChild(TiffTag.EXIF_SUB_IFD, exifSubIFD);
		else
			this.exifSubIFD = exifSubIFD;
	}
	
	public void addGPS(IFD gpsSubIFD) {
		if(imageIFD != null)
			imageIFD.addChild(TiffTag.GPS_SUB_IFD, gpsSubIFD);
		else
			this.gpsSubIFD = gpsSubIFD;
	}
	
	/**
	 * @param thumbnail optional thumbnail image. If null, will be generated from the input image
	 */
	public void addThumbnail(BufferedImage thumbnail) {
		this.thumbnail = new ExifThumbnail(flavor, thumbnail);
		this.isThumbnailRequired = true;
	}
	
	public boolean isThumbnailRequired() {
		return isThumbnailRequired;
	}
	
	public void setThumbnail(BufferedImage thumbnail) {
		this.thumbnail.setImage(thumbnail);
	}
	
	public IFD getIFD(Tag tag) {
		if(tag.getValue() == TiffTag.EXIF_SUB_IFD.getValue()) {
			return exifSubIFD;
		} else if(tag.getValue() == TiffTag.GPS_SUB_IFD.getValue()) {
{'fraction_non_alphanumeric': 0.12350517535926038, 'fraction_numerical': 0.011858104713094161, 'mean_word_length': 4.745958429561201, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5881073', 'n_tokens_mistral': 3777, 'n_tokens_neox': 3449, 'n_words': 770}