summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
committerThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
commit426377d9ce95606bac741d99b5b0a3e2038924fe (patch)
treee369cfdda7dfd42203aee8100cad25e2033f1cc8
downloadjhead-cdma-import.tar.gz
-rw-r--r--Android.mk38
-rw-r--r--MODULE_LICENSE_PUBLIC_DOMAIN0
-rw-r--r--NOTICE34
-rw-r--r--exif.c1683
-rw-r--r--gpsinfo.c299
-rw-r--r--iptc.c156
-rw-r--r--jhead.c1621
-rw-r--r--jhead.h248
-rw-r--r--jpgfile.c684
-rw-r--r--main.c759
-rw-r--r--makernote.c185
11 files changed, 5707 insertions, 0 deletions
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..1b749a4
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,38 @@
+#
+# Copyright (C) 2008 The Android Open Source Project
+#
+# Licensed 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.
+#
+LOCAL_PATH := $(my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := user development
+
+LOCAL_SRC_FILES:= \
+ main.c \
+ exif.c \
+ gpsinfo.c \
+ iptc.c \
+ jhead.c \
+ jpgfile.c \
+ makernote.c
+
+LOCAL_MODULE := libexif
+
+LOCAL_SHARED_LIBRARIES := \
+ libnativehelper \
+ libcutils \
+ libutils
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/MODULE_LICENSE_PUBLIC_DOMAIN b/MODULE_LICENSE_PUBLIC_DOMAIN
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_PUBLIC_DOMAIN
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..7f33586
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,34 @@
+
+Portions of this source code are in the public domain
+
+
+Copyright (c) 2008, The Android Open Source Project
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of The Android Open Source Project nor the names
+ of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written
+ permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
diff --git a/exif.c b/exif.c
new file mode 100644
index 0000000..b273c45
--- /dev/null
+++ b/exif.c
@@ -0,0 +1,1683 @@
+//--------------------------------------------------------------------------
+// Program to pull the information out of various types of EXIF digital
+// camera files and show it in a reasonably consistent way
+//
+// This module parses the very complicated exif structures.
+//
+// Matthias Wandel
+//--------------------------------------------------------------------------
+#include "jhead.h"
+
+#include <math.h>
+#include <ctype.h>
+#include <utils/Log.h>
+
+static unsigned char * DirWithThumbnailPtrs;
+static double FocalplaneXRes;
+static double FocalplaneUnits;
+static int ExifImageWidth;
+static int MotorolaOrder = 0;
+
+// for fixing the rotation.
+static void * OrientationPtr[2];
+static int OrientationNumFormat[2];
+int NumOrientations = 0;
+
+
+// Define the line below to turn on poor man's debugging output
+#undef SUPERDEBUG
+
+#ifdef SUPERDEBUG
+#define printf LOGE
+#endif
+
+//--------------------------------------------------------------------------
+// Table of Jpeg encoding process names
+static const TagTable_t ProcessTable[] = {
+ { M_SOF0, "Baseline", 0, 0},
+ { M_SOF1, "Extended sequential", 0, 0},
+ { M_SOF2, "Progressive", 0, 0},
+ { M_SOF3, "Lossless", 0, 0},
+ { M_SOF5, "Differential sequential", 0, 0},
+ { M_SOF6, "Differential progressive", 0, 0},
+ { M_SOF7, "Differential lossless", 0, 0},
+ { M_SOF9, "Extended sequential, arithmetic coding", 0, 0},
+ { M_SOF10, "Progressive, arithmetic coding", 0, 0},
+ { M_SOF11, "Lossless, arithmetic coding", 0, 0},
+ { M_SOF13, "Differential sequential, arithmetic coding", 0, 0},
+ { M_SOF14, "Differential progressive, arithmetic coding", 0, 0},
+ { M_SOF15, "Differential lossless, arithmetic coding", 0, 0},
+};
+
+#define PROCESS_TABLE_SIZE (sizeof(ProcessTable) / sizeof(TagTable_t))
+
+// 1 - "The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side."
+// 2 - "The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side."
+// 3 - "The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side."
+// 4 - "The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side."
+
+// 5 - "The 0th row is the visual left-hand side of of the image, and the 0th column is the visual top."
+// 6 - "The 0th row is the visual right-hand side of of the image, and the 0th column is the visual top."
+// 7 - "The 0th row is the visual right-hand side of of the image, and the 0th column is the visual bottom."
+// 8 - "The 0th row is the visual left-hand side of of the image, and the 0th column is the visual bottom."
+
+// Note: The descriptions here are the same as the name of the command line
+// option to pass to jpegtran to right the image
+
+static const char * OrientTab[9] = {
+ "Undefined",
+ "Normal", // 1
+ "flip horizontal", // left right reversed mirror
+ "rotate 180", // 3
+ "flip vertical", // upside down mirror
+ "transpose", // Flipped about top-left <--> bottom-right axis.
+ "rotate 90", // rotate 90 cw to right it.
+ "transverse", // flipped about top-right <--> bottom-left axis
+ "rotate 270", // rotate 270 to right it.
+};
+
+const int BytesPerFormat[] = {0,1,1,2,4,8,1,1,2,4,8,4,8};
+
+//--------------------------------------------------------------------------
+// Describes tag values
+
+#define TAG_MAKE 0x010F
+#define TAG_MODEL 0x0110
+#define TAG_ORIENTATION 0x0112
+#define TAG_DATETIME 0x0132
+#define TAG_THUMBNAIL_OFFSET 0x0201
+#define TAG_THUMBNAIL_LENGTH 0x0202
+#define TAG_EXPOSURETIME 0x829A
+#define TAG_FNUMBER 0x829D
+#define TAG_EXIF_OFFSET 0x8769
+#define TAG_EXPOSURE_PROGRAM 0x8822
+#define TAG_GPSINFO 0x8825
+#define TAG_ISO_EQUIVALENT 0x8827
+#define TAG_DATETIME_ORIGINAL 0x9003
+#define TAG_DATETIME_DIGITIZED 0x9004
+#define TAG_SHUTTERSPEED 0x9201
+#define TAG_APERTURE 0x9202
+#define TAG_EXPOSURE_BIAS 0x9204
+#define TAG_MAXAPERTURE 0x9205
+#define TAG_SUBJECT_DISTANCE 0x9206
+#define TAG_METERING_MODE 0x9207
+#define TAG_LIGHT_SOURCE 0x9208
+#define TAG_FLASH 0x9209
+#define TAG_FOCALLENGTH 0x920A
+#define TAG_MAKER_NOTE 0x927C
+#define TAG_USERCOMMENT 0x9286
+#define TAG_EXIF_IMAGEWIDTH 0xa002
+#define TAG_EXIF_IMAGELENGTH 0xa003
+#define TAG_INTEROP_OFFSET 0xa005
+#define TAG_FOCALPLANEXRES 0xa20E
+#define TAG_FOCALPLANEUNITS 0xa210
+#define TAG_EXPOSURE_INDEX 0xa215
+#define TAG_EXPOSURE_MODE 0xa402
+#define TAG_WHITEBALANCE 0xa403
+#define TAG_DIGITALZOOMRATIO 0xA404
+#define TAG_FOCALLENGTH_35MM 0xa405
+
+// TODO: replace the ", 0" values in this table with the correct format, e.g. ", FMT_USHORT"
+static const TagTable_t TagTable[] = {
+ { 0x001, "InteropIndex", 0, 0},
+ { 0x002, "InteropVersion", 0, 0},
+ { 0x100, "ImageWidth", FMT_USHORT, 1},
+ { 0x101, "ImageLength", FMT_USHORT, 1},
+ { 0x102, "BitsPerSample", FMT_USHORT, 3},
+ { 0x103, "Compression", FMT_USHORT, 1},
+ { 0x106, "PhotometricInterpretation", FMT_USHORT, 1},
+ { 0x10A, "FillOrder", 0, 0},
+ { 0x10D, "DocumentName", 0, 0},
+ { 0x10E, "ImageDescription", 0, 0 },
+ { 0x10F, "Make", FMT_STRING, -1},
+ { 0x110, "Model", FMT_STRING, -1},
+ { 0x111, "StripOffsets", FMT_USHORT, 1},
+ { 0x112, "Orientation", FMT_USHORT, 1},
+ { 0x115, "SamplesPerPixel", FMT_USHORT, 3},
+ { 0x116, "RowsPerStrip", FMT_USHORT, 1},
+ { 0x117, "StripByteCounts", FMT_USHORT, 1},
+ { 0x11A, "XResolution", FMT_URATIONAL, 1},
+ { 0x11B, "YResolution", FMT_URATIONAL, 1},
+ { 0x11C, "PlanarConfiguration", FMT_USHORT, 1},
+ { 0x128, "ResolutionUnit", FMT_USHORT, 1},
+ { 0x12D, "TransferFunction", FMT_USHORT, 768},
+ { 0x131, "Software", FMT_STRING, -1},
+ { 0x132, "DateTime", FMT_STRING, 20},
+ { 0x13B, "Artist", FMT_STRING, -1},
+ { 0x13E, "WhitePoint", FMT_SRATIONAL, 2},
+ { 0x13F, "PrimaryChromaticities", FMT_SRATIONAL, 6},
+ { 0x156, "TransferRange", 0, 0},
+ { 0x200, "JPEGProc", 0, 0},
+ { 0x201, "ThumbnailOffset", 0, 0},
+ { 0x202, "ThumbnailLength", 0, 0},
+ { 0x211, "YCbCrCoefficients", FMT_SRATIONAL, 3},
+ { 0x212, "YCbCrSubSampling", FMT_USHORT, 2},
+ { 0x213, "YCbCrPositioning", FMT_USHORT, 1},
+ { 0x214, "ReferenceBlackWhite", FMT_SRATIONAL, 6},
+ { 0x1001, "RelatedImageWidth", 0, 0},
+ { 0x1002, "RelatedImageLength", 0, 0},
+ { 0x828D, "CFARepeatPatternDim", 0, 0},
+ { 0x828E, "CFAPattern", 0, 0},
+ { 0x828F, "BatteryLevel", 0, 0},
+ { 0x8298, "Copyright", FMT_STRING, -1},
+ { 0x829A, "ExposureTime", FMT_USHORT, 1},
+ { 0x829D, "FNumber", FMT_SRATIONAL, 1},
+ { 0x83BB, "IPTC/NAA", 0, 0},
+ { 0x8769, "ExifOffset", 0, 0},
+ { 0x8773, "InterColorProfile", 0, 0},
+ { 0x8822, "ExposureProgram", FMT_SSHORT, 1},
+ { 0x8824, "SpectralSensitivity", FMT_STRING, -1},
+ { 0x8825, "GPS Dir offset", 0, 0},
+ { 0x8827, "ISOSpeedRatings", FMT_SSHORT, -1},
+ { 0x8828, "OECF", 0, 0},
+ { 0x9000, "ExifVersion", FMT_BYTE, 4},
+ { 0x9003, "DateTimeOriginal", FMT_STRING, 20},
+ { 0x9004, "DateTimeDigitized", FMT_STRING, 20},
+ { 0x9101, "ComponentsConfiguration", FMT_BYTE, 4},
+ { 0x9102, "CompressedBitsPerPixel", FMT_SRATIONAL, 1},
+ { 0x9201, "ShutterSpeedValue", FMT_SRATIONAL, 1},
+ { 0x9202, "ApertureValue", FMT_URATIONAL, 1},
+ { 0x9203, "BrightnessValue", FMT_SRATIONAL, 1},
+ { 0x9204, "ExposureBiasValue", FMT_SRATIONAL, 1},
+ { 0x9205, "MaxApertureValue", FMT_URATIONAL, 1},
+ { 0x9206, "SubjectDistance", FMT_URATIONAL, 1},
+ { 0x9207, "MeteringMode", FMT_USHORT, 1},
+ { 0x9208, "LightSource", FMT_USHORT, 1},
+ { 0x9209, "Flash", FMT_USHORT, 1},
+ { 0x920A, "FocalLength", FMT_URATIONAL, 1},
+ { 0x927C, "MakerNote", FMT_STRING, -1},
+ { 0x9286, "UserComment", FMT_STRING, -1},
+ { 0x9290, "SubSecTime", FMT_STRING, -1},
+ { 0x9291, "SubSecTimeOriginal", FMT_STRING, -1},
+ { 0x9292, "SubSecTimeDigitized", FMT_STRING, -1},
+ { 0xA000, "FlashPixVersion", FMT_BYTE, 4},
+ { 0xA001, "ColorSpace", FMT_USHORT, 1},
+ { 0xA002, "ExifImageWidth", 0, 0},
+ { 0xA003, "ExifImageLength", 0, 0},
+ { 0xA004, "RelatedAudioFile", 0, 0},
+ { 0xA005, "InteroperabilityOffset", 0, 0},
+ { 0xA20B, "FlashEnergy", FMT_URATIONAL, 1},
+ { 0xA20C, "SpatialFrequencyResponse", FMT_STRING, -1},
+ { 0xA20E, "FocalPlaneXResolution", FMT_URATIONAL, 1},
+ { 0xA20F, "FocalPlaneYResolution", FMT_URATIONAL, 1},
+ { 0xA210, "FocalPlaneResolutionUnit", FMT_USHORT, 1},
+ { 0xA214, "SubjectLocation", FMT_USHORT, 2},
+ { 0xA215, "ExposureIndex", FMT_URATIONAL, 1},
+ { 0xA217, "SensingMethod", FMT_USHORT, 1},
+ { 0xA300, "FileSource", 0, 1},
+ { 0xA301, "SceneType", 0, 1},
+ { 0xA301, "CFA Pattern", 0, -1},
+ { 0xA401, "CustomRendered", FMT_USHORT, 1},
+ { 0xA402, "ExposureMode", FMT_USHORT, 1},
+ { 0xA403, "WhiteBalance", FMT_USHORT, 1},
+ { 0xA404, "DigitalZoomRatio", FMT_URATIONAL, 1},
+ { 0xA405, "FocalLengthIn35mmFilm", FMT_USHORT, 1},
+ { 0xA406, "SceneCaptureType", FMT_USHORT, 1},
+ { 0xA407, "GainControl", FMT_URATIONAL, 1},
+ { 0xA408, "Contrast", FMT_USHORT, 1},
+ { 0xA409, "Saturation", FMT_USHORT, 1},
+ { 0xA40a, "Sharpness", FMT_USHORT, 1},
+ { 0xA40c, "SubjectDistanceRange", FMT_USHORT, 1},
+} ;
+
+#define TAG_TABLE_SIZE (sizeof(TagTable) / sizeof(TagTable_t))
+
+int TagNameToValue(const char* tagName)
+{
+ unsigned int i;
+ for (i = 0; i < TAG_TABLE_SIZE; i++) {
+ if (strcmp(TagTable[i].Desc, tagName) == 0) {
+ printf("found tag %s val %d", TagTable[i].Desc, TagTable[i].Tag);
+ return TagTable[i].Tag;
+ }
+ }
+ printf("tag %s NOT FOUND", tagName);
+ return -1;
+}
+
+//--------------------------------------------------------------------------
+// Convert a 16 bit unsigned value to file's native byte order
+//--------------------------------------------------------------------------
+static void Put16u(void * Short, unsigned short PutValue)
+{
+ if (MotorolaOrder){
+ ((uchar *)Short)[0] = (uchar)(PutValue>>8);
+ ((uchar *)Short)[1] = (uchar)PutValue;
+ }else{
+ ((uchar *)Short)[0] = (uchar)PutValue;
+ ((uchar *)Short)[1] = (uchar)(PutValue>>8);
+ }
+}
+
+//--------------------------------------------------------------------------
+// Convert a 16 bit unsigned value from file's native byte order
+//--------------------------------------------------------------------------
+int Get16u(void * Short)
+{
+ if (MotorolaOrder){
+ return (((uchar *)Short)[0] << 8) | ((uchar *)Short)[1];
+ }else{
+ return (((uchar *)Short)[1] << 8) | ((uchar *)Short)[0];
+ }
+}
+
+//--------------------------------------------------------------------------
+// Convert a 32 bit signed value from file's native byte order
+//--------------------------------------------------------------------------
+int Get32s(void * Long)
+{
+ if (MotorolaOrder){
+ return ((( char *)Long)[0] << 24) | (((uchar *)Long)[1] << 16)
+ | (((uchar *)Long)[2] << 8 ) | (((uchar *)Long)[3] << 0 );
+ }else{
+ return ((( char *)Long)[3] << 24) | (((uchar *)Long)[2] << 16)
+ | (((uchar *)Long)[1] << 8 ) | (((uchar *)Long)[0] << 0 );
+ }
+}
+
+//--------------------------------------------------------------------------
+// Convert a 32 bit unsigned value to file's native byte order
+//--------------------------------------------------------------------------
+void Put32u(void * Value, unsigned PutValue)
+{
+ if (MotorolaOrder){
+ ((uchar *)Value)[0] = (uchar)(PutValue>>24);
+ ((uchar *)Value)[1] = (uchar)(PutValue>>16);
+ ((uchar *)Value)[2] = (uchar)(PutValue>>8);
+ ((uchar *)Value)[3] = (uchar)PutValue;
+ }else{
+ ((uchar *)Value)[0] = (uchar)PutValue;
+ ((uchar *)Value)[1] = (uchar)(PutValue>>8);
+ ((uchar *)Value)[2] = (uchar)(PutValue>>16);
+ ((uchar *)Value)[3] = (uchar)(PutValue>>24);
+ }
+}
+
+//--------------------------------------------------------------------------
+// Convert a 32 bit unsigned value from file's native byte order
+//--------------------------------------------------------------------------
+unsigned Get32u(void * Long)
+{
+ return (unsigned)Get32s(Long) & 0xffffffff;
+}
+
+//--------------------------------------------------------------------------
+// Display a number as one of its many formats
+//--------------------------------------------------------------------------
+void PrintFormatNumber(void * ValuePtr, int Format, int ByteCount)
+{
+ int s,n;
+
+ for(n=0;n<20;n++){
+ switch(Format){
+ case FMT_SBYTE:
+ case FMT_BYTE: printf("%02x",*(uchar *)ValuePtr); s=1; break;
+ case FMT_USHORT: printf("%d",Get16u(ValuePtr)); s=2; break;
+ case FMT_ULONG:
+ case FMT_SLONG: printf("%d",Get32s(ValuePtr)); s=4; break;
+ case FMT_SSHORT: printf("%hd",(signed short)Get16u(ValuePtr)); s=2; break;
+ case FMT_URATIONAL:
+ case FMT_SRATIONAL:
+ printf("%d/%d",Get32s(ValuePtr), Get32s(4+(char *)ValuePtr));
+ s = 8;
+ break;
+
+ case FMT_SINGLE: printf("%f",(double)*(float *)ValuePtr); s=8; break;
+ case FMT_DOUBLE: printf("%f",*(double *)ValuePtr); s=8; break;
+ default:
+ printf("Unknown format %d:", Format);
+ return;
+ }
+ ByteCount -= s;
+ if (ByteCount <= 0) break;
+ printf(", ");
+ ValuePtr = (void *)((char *)ValuePtr + s);
+
+ }
+ if (n >= 20) printf("...");
+}
+
+
+//--------------------------------------------------------------------------
+// Evaluate number, be it int, rational, or float from directory.
+//--------------------------------------------------------------------------
+double ConvertAnyFormat(void * ValuePtr, int Format)
+{
+ double Value;
+ Value = 0;
+
+ switch(Format){
+ case FMT_SBYTE: Value = *(signed char *)ValuePtr; break;
+ case FMT_BYTE: Value = *(uchar *)ValuePtr; break;
+
+ case FMT_USHORT: Value = Get16u(ValuePtr); break;
+ case FMT_ULONG: Value = Get32u(ValuePtr); break;
+
+ case FMT_URATIONAL:
+ case FMT_SRATIONAL:
+ {
+ int Num,Den;
+ Num = Get32s(ValuePtr);
+ Den = Get32s(4+(char *)ValuePtr);
+ if (Den == 0){
+ Value = 0;
+ }else{
+ Value = (double)Num/Den;
+ }
+ break;
+ }
+
+ case FMT_SSHORT: Value = (signed short)Get16u(ValuePtr); break;
+ case FMT_SLONG: Value = Get32s(ValuePtr); break;
+
+ // Not sure if this is correct (never seen float used in Exif format)
+ case FMT_SINGLE: Value = (double)*(float *)ValuePtr; break;
+ case FMT_DOUBLE: Value = *(double *)ValuePtr; break;
+
+ default:
+ ErrNonfatal("Illegal format code %d",Format,0);
+ }
+ return Value;
+}
+
+//--------------------------------------------------------------------------
+// Process one of the nested EXIF directories.
+//--------------------------------------------------------------------------
+static void ProcessExifDir(unsigned char * DirStart, unsigned char * OffsetBase,
+ unsigned ExifLength, int NestingLevel)
+{
+ int de;
+ int a;
+ int NumDirEntries;
+ unsigned ThumbnailOffset = 0;
+ unsigned ThumbnailSize = 0;
+ char IndentString[25];
+
+ printf("ProcessExifDir");
+ if (NestingLevel > 4){
+ ErrNonfatal("Maximum directory nesting exceeded (corrupt exif header)", 0,0);
+ return;
+ }
+
+ memset(IndentString, ' ', 25);
+ IndentString[NestingLevel * 4] = '\0';
+
+
+ NumDirEntries = Get16u(DirStart);
+ #define DIR_ENTRY_ADDR(Start, Entry) (Start+2+12*(Entry))
+
+ {
+ unsigned char * DirEnd;
+ DirEnd = DIR_ENTRY_ADDR(DirStart, NumDirEntries);
+ if (DirEnd+4 > (OffsetBase+ExifLength)){
+ if (DirEnd+2 == OffsetBase+ExifLength || DirEnd == OffsetBase+ExifLength){
+ // Version 1.3 of jhead would truncate a bit too much.
+ // This also caught later on as well.
+ }else{
+ ErrNonfatal("Illegally sized directory",0,0);
+ return;
+ }
+ }
+ if (DumpExifMap){
+ printf("Map: %05d-%05d: Directory\n",DirStart-OffsetBase, DirEnd+4-OffsetBase);
+ }
+
+
+ }
+
+ if (ShowTags){
+ printf("(dir has %d entries)\n",NumDirEntries);
+ }
+
+ for (de=0;de<NumDirEntries;de++){
+ int Tag, Format, Components;
+ unsigned char * ValuePtr;
+ int ByteCount;
+ unsigned char * DirEntry;
+ DirEntry = DIR_ENTRY_ADDR(DirStart, de);
+
+ Tag = Get16u(DirEntry);
+ Format = Get16u(DirEntry+2);
+ Components = Get32u(DirEntry+4);
+
+ if ((Format-1) >= NUM_FORMATS) {
+ // (-1) catches illegal zero case as unsigned underflows to positive large.
+ ErrNonfatal("Illegal number format %d for tag %04x", Format, Tag);
+ continue;
+ }
+
+ if ((unsigned)Components > 0x10000){
+ ErrNonfatal("Illegal number of components %d for tag %04x", Components, Tag);
+ continue;
+ }
+
+ ByteCount = Components * BytesPerFormat[Format];
+
+ if (ByteCount > 4){
+ unsigned OffsetVal;
+ OffsetVal = Get32u(DirEntry+8);
+ // If its bigger than 4 bytes, the dir entry contains an offset.
+ if (OffsetVal+ByteCount > ExifLength){
+ // Bogus pointer offset and / or bytecount value
+ ErrNonfatal("Illegal value pointer for tag %04x", Tag,0);
+ continue;
+ }
+ ValuePtr = OffsetBase+OffsetVal;
+
+ if (OffsetVal > ImageInfo.LargestExifOffset){
+ ImageInfo.LargestExifOffset = OffsetVal;
+ }
+
+ if (DumpExifMap){
+ printf("Map: %05d-%05d: Data for tag %04x\n",OffsetVal, OffsetVal+ByteCount, Tag);
+ }
+ }else{
+ // 4 bytes or less and value is in the dir entry itself
+ ValuePtr = DirEntry+8;
+ }
+
+ if (Tag == TAG_MAKER_NOTE){
+ if (ShowTags){
+ printf("%s Maker note: ",IndentString);
+ }
+ ProcessMakerNote(ValuePtr, ByteCount, OffsetBase, ExifLength);
+ continue;
+ }
+
+ if (ShowTags){
+ // Show tag name
+ for (a=0;;a++){
+ if (a >= (int)TAG_TABLE_SIZE){
+ printf(IndentString);
+ printf(" Unknown Tag %04x Value = ", Tag);
+ break;
+ }
+ if (TagTable[a].Tag == Tag){
+ printf(IndentString);
+ printf(" %s = ",TagTable[a].Desc);
+ break;
+ }
+ }
+
+ // Show tag value.
+ switch(Format){
+ case FMT_BYTE:
+ if(ByteCount>1){
+ printf("%.*ls\n", ByteCount/2, (wchar_t *)ValuePtr);
+ }else{
+ PrintFormatNumber(ValuePtr, Format, ByteCount);
+ printf("\n");
+ }
+ break;
+
+ case FMT_UNDEFINED:
+ // Undefined is typically an ascii string.
+
+ case FMT_STRING:
+ // String arrays printed without function call (different from int arrays)
+ {
+ printf("\"%s\"", ValuePtr);
+// int NoPrint = 0;
+// printf("\"");
+// for (a=0;a<ByteCount;a++){
+// if (ValuePtr[a] >= 32){
+// putchar(ValuePtr[a]);
+// NoPrint = 0;
+// }else{
+// // Avoiding indicating too many unprintable characters of proprietary
+// // bits of binary information this program may not know how to parse.
+// if (!NoPrint && a != ByteCount-1){
+// putchar('?');
+// NoPrint = 1;
+// }
+// }
+// }
+// printf("\"\n");
+ }
+ break;
+
+ default:
+ // Handle arrays of numbers later (will there ever be?)
+ PrintFormatNumber(ValuePtr, Format, ByteCount);
+ printf("\n");
+ }
+ }
+
+ // Extract useful components of tag
+ switch(Tag){
+
+ case TAG_MAKE:
+ strncpy(ImageInfo.CameraMake, (char *)ValuePtr, ByteCount < 31 ? ByteCount : 31);
+ break;
+
+ case TAG_MODEL:
+ strncpy(ImageInfo.CameraModel, (char *)ValuePtr, ByteCount < 39 ? ByteCount : 39);
+ break;
+
+ case TAG_DATETIME_ORIGINAL:
+ // If we get a DATETIME_ORIGINAL, we use that one.
+ strncpy(ImageInfo.DateTime, (char *)ValuePtr, 19);
+ // Fallthru...
+
+ case TAG_DATETIME_DIGITIZED:
+ case TAG_DATETIME:
+ if (!isdigit(ImageInfo.DateTime[0])){
+ // If we don't already have a DATETIME_ORIGINAL, use whatever
+ // time fields we may have.
+ strncpy(ImageInfo.DateTime, (char *)ValuePtr, 19);
+ }
+
+ if (ImageInfo.numDateTimeTags >= MAX_DATE_COPIES){
+ ErrNonfatal("More than %d date fields! This is nuts", MAX_DATE_COPIES, 0);
+ break;
+ }
+ ImageInfo.DateTimeOffsets[ImageInfo.numDateTimeTags++] =
+ (char *)ValuePtr - (char *)OffsetBase;
+ break;
+
+
+ case TAG_USERCOMMENT:
+ // Olympus has this padded with trailing spaces. Remove these first.
+ for (a=ByteCount;;){
+ a--;
+ if ((ValuePtr)[a] == ' '){
+ (ValuePtr)[a] = '\0';
+ }else{
+ break;
+ }
+ if (a == 0) break;
+ }
+
+ // Copy the comment
+ if (memcmp(ValuePtr, "ASCII",5) == 0){
+ for (a=5;a<10;a++){
+ int c;
+ c = (ValuePtr)[a];
+ if (c != '\0' && c != ' '){
+ strncpy(ImageInfo.Comments, (char *)ValuePtr+a, 199);
+ break;
+ }
+ }
+
+ }else{
+ strncpy(ImageInfo.Comments, (char *)ValuePtr, 199);
+ }
+ break;
+
+ case TAG_FNUMBER:
+ // Simplest way of expressing aperture, so I trust it the most.
+ // (overwrite previously computd value if there is one)
+ ImageInfo.ApertureFNumber = (float)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_APERTURE:
+ case TAG_MAXAPERTURE:
+ // More relevant info always comes earlier, so only use this field if we don't
+ // have appropriate aperture information yet.
+ if (ImageInfo.ApertureFNumber == 0){
+ ImageInfo.ApertureFNumber
+ = (float)exp(ConvertAnyFormat(ValuePtr, Format)*log(2)*0.5);
+ }
+ break;
+
+ case TAG_FOCALLENGTH:
+ // Nice digital cameras actually save the focal length as a function
+ // of how farthey are zoomed in.
+ ImageInfo.FocalLength = (float)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_SUBJECT_DISTANCE:
+ // Inidcates the distacne the autofocus camera is focused to.
+ // Tends to be less accurate as distance increases.
+ ImageInfo.Distance = (float)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_EXPOSURETIME:
+ // Simplest way of expressing exposure time, so I trust it most.
+ // (overwrite previously computd value if there is one)
+ ImageInfo.ExposureTime = (float)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_SHUTTERSPEED:
+ // More complicated way of expressing exposure time, so only use
+ // this value if we don't already have it from somewhere else.
+ if (ImageInfo.ExposureTime == 0){
+ ImageInfo.ExposureTime
+ = (float)(1/exp(ConvertAnyFormat(ValuePtr, Format)*log(2)));
+ }
+ break;
+
+
+ case TAG_FLASH:
+ ImageInfo.FlashUsed=(int)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_ORIENTATION:
+ if (NumOrientations >= 2){
+ // Can have another orientation tag for the thumbnail, but if there's
+ // a third one, things are stringae.
+ ErrNonfatal("More than two orientation tags!",0,0);
+ break;
+ }
+ OrientationPtr[NumOrientations] = ValuePtr;
+ OrientationNumFormat[NumOrientations] = Format;
+ if (NumOrientations == 0){
+ ImageInfo.Orientation = (int)ConvertAnyFormat(ValuePtr, Format);
+ }
+ if (ImageInfo.Orientation < 0 || ImageInfo.Orientation > 8){
+ ErrNonfatal("Undefined rotation value %d", ImageInfo.Orientation, 0);
+ ImageInfo.Orientation = 0;
+ }
+ NumOrientations += 1;
+ break;
+
+ case TAG_EXIF_IMAGELENGTH:
+ case TAG_EXIF_IMAGEWIDTH:
+ // Use largest of height and width to deal with images that have been
+ // rotated to portrait format.
+ a = (int)ConvertAnyFormat(ValuePtr, Format);
+ if (ExifImageWidth < a) ExifImageWidth = a;
+ break;
+
+ case TAG_FOCALPLANEXRES:
+ FocalplaneXRes = ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_FOCALPLANEUNITS:
+ switch((int)ConvertAnyFormat(ValuePtr, Format)){
+ case 1: FocalplaneUnits = 25.4; break; // inch
+ case 2:
+ // According to the information I was using, 2 means meters.
+ // But looking at the Cannon powershot's files, inches is the only
+ // sensible value.
+ FocalplaneUnits = 25.4;
+ break;
+
+ case 3: FocalplaneUnits = 10; break; // centimeter
+ case 4: FocalplaneUnits = 1; break; // millimeter
+ case 5: FocalplaneUnits = .001; break; // micrometer
+ }
+ break;
+
+ case TAG_EXPOSURE_BIAS:
+ ImageInfo.ExposureBias = (float)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_WHITEBALANCE:
+ ImageInfo.Whitebalance = (int)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_LIGHT_SOURCE:
+ ImageInfo.LightSource = (int)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_METERING_MODE:
+ ImageInfo.MeteringMode = (int)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_EXPOSURE_PROGRAM:
+ ImageInfo.ExposureProgram = (int)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_EXPOSURE_INDEX:
+ if (ImageInfo.ISOequivalent == 0){
+ // Exposure index and ISO equivalent are often used interchangeably,
+ // so we will do the same in jhead.
+ // http://photography.about.com/library/glossary/bldef_ei.htm
+ ImageInfo.ISOequivalent = (int)ConvertAnyFormat(ValuePtr, Format);
+ }
+ break;
+
+ case TAG_EXPOSURE_MODE:
+ ImageInfo.ExposureMode = (int)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_ISO_EQUIVALENT:
+ ImageInfo.ISOequivalent = (int)ConvertAnyFormat(ValuePtr, Format);
+ if ( ImageInfo.ISOequivalent < 50 ){
+ // Fixes strange encoding on some older digicams.
+ ImageInfo.ISOequivalent *= 200;
+ }
+ break;
+
+ case TAG_DIGITALZOOMRATIO:
+ ImageInfo.DigitalZoomRatio = (float)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_THUMBNAIL_OFFSET:
+ ThumbnailOffset = (unsigned)ConvertAnyFormat(ValuePtr, Format);
+ DirWithThumbnailPtrs = DirStart;
+ break;
+
+ case TAG_THUMBNAIL_LENGTH:
+ ThumbnailSize = (unsigned)ConvertAnyFormat(ValuePtr, Format);
+ ImageInfo.ThumbnailSizeOffset = ValuePtr-OffsetBase;
+ break;
+
+ case TAG_EXIF_OFFSET:
+ if (ShowTags) printf("%s Exif Dir:",IndentString);
+
+ case TAG_INTEROP_OFFSET:
+ if (Tag == TAG_INTEROP_OFFSET && ShowTags) printf("%s Interop Dir:",IndentString);
+ {
+ unsigned char * SubdirStart;
+ SubdirStart = OffsetBase + Get32u(ValuePtr);
+ if (SubdirStart < OffsetBase || SubdirStart > OffsetBase+ExifLength){
+ ErrNonfatal("Illegal exif or interop ofset directory link",0,0);
+ }else{
+ ProcessExifDir(SubdirStart, OffsetBase, ExifLength, NestingLevel+1);
+ }
+ continue;
+ }
+ break;
+
+ case TAG_GPSINFO:
+ if (ShowTags) printf("%s GPS info dir:",IndentString);
+ {
+ unsigned char * SubdirStart;
+ SubdirStart = OffsetBase + Get32u(ValuePtr);
+ if (SubdirStart < OffsetBase || SubdirStart > OffsetBase+ExifLength){
+ ErrNonfatal("Illegal GPS directory link",0,0);
+ }else{
+ ProcessGpsInfo(SubdirStart, ByteCount, OffsetBase, ExifLength);
+ }
+ continue;
+ }
+ break;
+
+ case TAG_FOCALLENGTH_35MM:
+ // The focal length equivalent 35 mm is a 2.2 tag (defined as of April 2002)
+ // if its present, use it to compute equivalent focal length instead of
+ // computing it from sensor geometry and actual focal length.
+ ImageInfo.FocalLength35mmEquiv = (unsigned)ConvertAnyFormat(ValuePtr, Format);
+ break;
+ }
+ }
+
+
+ {
+ // In addition to linking to subdirectories via exif tags,
+ // there's also a potential link to another directory at the end of each
+ // directory. this has got to be the result of a committee!
+ unsigned char * SubdirStart;
+ unsigned Offset;
+
+ if (DIR_ENTRY_ADDR(DirStart, NumDirEntries) + 4 <= OffsetBase+ExifLength){
+ printf("DirStart %d offset from dirstart %d", (int)DirStart, 2+12*NumDirEntries);
+ Offset = Get32u(DirStart+2+12*NumDirEntries);
+ if (Offset){
+ SubdirStart = OffsetBase + Offset;
+ if (SubdirStart > OffsetBase+ExifLength || SubdirStart < OffsetBase){
+ printf("SubdirStart %d OffsetBase %d ExifLength %d Offset %d",
+ (int)SubdirStart, (int)OffsetBase, ExifLength, Offset);
+ if (SubdirStart > OffsetBase && SubdirStart < OffsetBase+ExifLength+20){
+ // Jhead 1.3 or earlier would crop the whole directory!
+ // As Jhead produces this form of format incorrectness,
+ // I'll just let it pass silently
+ if (ShowTags) printf("Thumbnail removed with Jhead 1.3 or earlier\n");
+ }else{
+ ErrNonfatal("Illegal subdirectory link",0,0);
+ }
+ }else{
+ if (SubdirStart <= OffsetBase+ExifLength){
+ if (ShowTags) printf("%s Continued directory ",IndentString);
+ ProcessExifDir(SubdirStart, OffsetBase, ExifLength, NestingLevel+1);
+ }
+ }
+ if (Offset > ImageInfo.LargestExifOffset){
+ ImageInfo.LargestExifOffset = Offset;
+ }
+ }
+ }else{
+ // The exif header ends before the last next directory pointer.
+ }
+ }
+
+ if (ThumbnailOffset){
+ ImageInfo.ThumbnailAtEnd = FALSE;
+
+ if (DumpExifMap){
+ printf("Map: %05d-%05d: Thumbnail\n",ThumbnailOffset, ThumbnailOffset+ThumbnailSize);
+ }
+
+ if (ThumbnailOffset <= ExifLength){
+ if (ThumbnailSize > ExifLength-ThumbnailOffset){
+ // If thumbnail extends past exif header, only save the part that
+ // actually exists. Canon's EOS viewer utility will do this - the
+ // thumbnail extracts ok with this hack.
+ ThumbnailSize = ExifLength-ThumbnailOffset;
+ if (ShowTags) printf("Thumbnail incorrectly placed in header\n");
+
+ }
+ // The thumbnail pointer appears to be valid. Store it.
+ ImageInfo.ThumbnailOffset = ThumbnailOffset;
+ ImageInfo.ThumbnailSize = ThumbnailSize;
+
+ if (ShowTags){
+ printf("Thumbnail size: %d bytes\n",ThumbnailSize);
+ }
+ }
+ }
+ printf("returning from ProcessExifDir");
+}
+
+
+//--------------------------------------------------------------------------
+// Process a EXIF marker
+// Describes all the drivel that most digital cameras include...
+//--------------------------------------------------------------------------
+void process_EXIF (unsigned char * ExifSection, unsigned int length)
+{
+ int FirstOffset;
+
+ FocalplaneXRes = 0;
+ FocalplaneUnits = 0;
+ ExifImageWidth = 0;
+ NumOrientations = 0;
+
+ if (ShowTags){
+ printf("Exif header %d bytes long\n",length);
+ }
+
+ { // Check the EXIF header component
+ static uchar ExifHeader[] = "Exif\0\0";
+ if (memcmp(ExifSection+2, ExifHeader,6)){
+ ErrNonfatal("Incorrect Exif header",0,0);
+ return;
+ }
+ }
+
+ if (memcmp(ExifSection+8,"II",2) == 0){
+ if (ShowTags) printf("Exif section in Intel order\n");
+ MotorolaOrder = 0;
+ }else{
+ if (memcmp(ExifSection+8,"MM",2) == 0){
+ if (ShowTags) printf("Exif section in Motorola order\n");
+ MotorolaOrder = 1;
+ }else{
+ ErrNonfatal("Invalid Exif alignment marker.",0,0);
+ return;
+ }
+ }
+
+ // Check the next value for correctness.
+ if (Get16u(ExifSection+10) != 0x2a){
+ ErrNonfatal("Invalid Exif start (1)",0,0);
+ return;
+ }
+
+ FirstOffset = Get32u(ExifSection+12);
+ if (FirstOffset < 8 || FirstOffset > 16){
+ // Usually set to 8, but other values valid too.
+ ErrNonfatal("Suspicious offset of first IFD value",0,0);
+ return;
+ }
+
+ DirWithThumbnailPtrs = NULL;
+
+
+ // First directory starts 16 bytes in. All offset are relative to 8 bytes in.
+ ProcessExifDir(ExifSection+8+FirstOffset, ExifSection+8, length-8, 0);
+
+ ImageInfo.ThumbnailAtEnd = ImageInfo.ThumbnailOffset >= ImageInfo.LargestExifOffset ? TRUE : FALSE;
+#ifdef SUPERDEBUG
+ printf("Thumbnail %s end", (ImageInfo.ThumbnailAtEnd ? "at" : "NOT at"));
+#endif
+ if (DumpExifMap){
+ unsigned a,b;
+ printf("Map: %05d- End of exif\n",length-8);
+// for (a=0;a<length-8;a+= 10){
+// printf("Map: %05d ",a);
+// for (b=0;b<10;b++) printf(" %02x",*(ExifSection+8+a+b));
+// printf("\n");
+// }
+ for (a = 0; a < length - 8; ++a) {
+ unsigned char c = *(ExifSection+8+a);
+ unsigned pc = isprint(c) ? c : ' ';
+ printf("Map: %4d %02x %c", a, c, pc);
+ }
+ }
+
+
+ // Compute the CCD width, in millimeters.
+ if (FocalplaneXRes != 0){
+ // Note: With some cameras, its not possible to compute this correctly because
+ // they don't adjust the indicated focal plane resolution units when using less
+ // than maximum resolution, so the CCDWidth value comes out too small. Nothing
+ // that Jhad can do about it - its a camera problem.
+ ImageInfo.CCDWidth = (float)(ExifImageWidth * FocalplaneUnits / FocalplaneXRes);
+
+ if (ImageInfo.FocalLength && ImageInfo.FocalLength35mmEquiv == 0){
+ // Compute 35 mm equivalent focal length based on sensor geometry if we haven't
+ // already got it explicitly from a tag.
+ ImageInfo.FocalLength35mmEquiv = (int)(ImageInfo.FocalLength/ImageInfo.CCDWidth*36 + 0.5);
+ }
+ }
+}
+
+static const TagTable_t* TagToTagTableEntry(unsigned short tag)
+{
+ unsigned int i;
+ for (i = 0; i < TAG_TABLE_SIZE; i++) {
+ if (TagTable[i].Tag == tag) {
+ printf("found tag %d", tag);
+ int format = TagTable[i].Format;
+ if (format == 0) {
+ printf("tag %s format not defined ***** YOU MUST ADD THE FORMAT TO THE TagTable in exif.c!!!!", TagTable[i].Desc);
+ return NULL;
+ }
+ return &TagTable[i];
+ }
+ }
+ printf("tag %d NOT FOUND", tag);
+ return NULL;
+}
+
+static void writeExifTagAndData(int tag,
+ int format,
+ long components,
+ long value,
+ int valueInString,
+ char* Buffer,
+ int* DirIndex,
+ int* DataWriteIndex) {
+ Put16u(Buffer+ (*DirIndex), tag); // Tag
+ Put16u(Buffer+(*DirIndex) + 2, format); // Format
+ if (format == FMT_STRING && components == -1) {
+ components = strlen((char*)value) + 1; // account for null terminator
+ if (components & 1) ++components; // no odd lengths
+ }
+ Put32u(Buffer+(*DirIndex) + 4, components); // Components
+ printf("# components: %ld", components);
+ if (format == FMT_STRING) {
+ // short strings can fit right in the long, otherwise have to
+ // go in the data area
+ if (components <= 4) {
+ strcpy(Buffer+(*DirIndex) + 8, (char*)value);
+ } else {
+ Put32u(Buffer+(*DirIndex) + 8, (*DataWriteIndex)-8); // Pointer
+ printf("copying value %s to %d", (char*)value, (*DataWriteIndex));
+ strncpy(Buffer+(*DataWriteIndex), (char*)value, components);
+ (*DataWriteIndex) += components;
+ }
+ } else if (!valueInString) {
+ Put32u(Buffer+(*DirIndex) + 8, value); // Value
+ } else {
+ Put32u(Buffer+(*DirIndex) + 8, (*DataWriteIndex)-8); // Pointer
+ char* curElement = strtok((char*)value, ",");
+ int i;
+ for (i = 0; i < components && curElement != NULL; i++) {
+#ifdef SUPERDEBUG
+ printf("processing component %s format %s", curElement, formatStr(format));
+#endif
+ // elements are separated by commas
+ if (format == FMT_URATIONAL) {
+ char* separator = strchr(curElement, '/');
+ if (separator) {
+ unsigned int numerator = atoi(curElement);
+ unsigned int denominator = atoi(separator + 1);
+ Put32u(Buffer+(*DataWriteIndex), numerator);
+ Put32u(Buffer+(*DataWriteIndex) + 4, denominator);
+ (*DataWriteIndex) += 8;
+ }
+ } else if (format == FMT_SRATIONAL) {
+ char* separator = strchr(curElement, '/');
+ if (separator) {
+ int numerator = atoi(curElement);
+ int denominator = atoi(separator + 1);
+ Put32u(Buffer+(*DataWriteIndex), numerator);
+ Put32u(Buffer+(*DataWriteIndex) + 4, denominator);
+ (*DataWriteIndex) += 8;
+ }
+ } else {
+ // TODO: doesn't handle multiple components yet -- if more than one, have to put in data write area.
+ value = atoi(curElement);
+ Put32u(Buffer+(*DirIndex) + 8, value); // Value
+ }
+ curElement = strtok(NULL, ",");
+ }
+ }
+ (*DirIndex) += 12;
+}
+
+#ifdef SUPERDEBUG
+char* formatStr(int format) {
+ switch (format) {
+ case FMT_BYTE: return "FMT_BYTE"; break;
+ case FMT_STRING: return "FMT_STRING"; break;
+ case FMT_USHORT: return "FMT_USHORT"; break;
+ case FMT_ULONG: return "FMT_ULONG"; break;
+ case FMT_URATIONAL: return "FMT_URATIONAL"; break;
+ case FMT_SBYTE: return "FMT_SBYTE"; break;
+ case FMT_UNDEFINED: return "FMT_UNDEFINED"; break;
+ case FMT_SSHORT: return "FMT_SSHORT"; break;
+ case FMT_SLONG: return "FMT_SLONG"; break;
+ case FMT_SRATIONAL: return "FMT_SRATIONAL"; break;
+ case FMT_SINGLE: return "FMT_SINGLE"; break;
+ case FMT_DOUBLE: return "FMT_SINGLE"; break;
+ default: return "UNKNOWN";
+ }
+}
+#endif
+
+//--------------------------------------------------------------------------
+// Create minimal exif header - just date and thumbnail pointers,
+// so that date and thumbnail may be filled later.
+//--------------------------------------------------------------------------
+void create_EXIF(ExifElement_t* elements, int exifTagCount, int gpsTagCount)
+{
+ // TODO: We need to dynamically allocate this buffer and resize it when
+ // necessary while writing so we don't do a buffer overflow.
+ char Buffer[1024];
+
+ unsigned short NumEntries;
+ int DataWriteIndex;
+ int DirIndex;
+ int ThumbnailOffsetDirIndex = 0;
+
+#ifdef SUPERDEBUG
+ LOGE("create_EXIF %d exif elements, %d gps elements", exifTagCount, gpsTagCount);
+#endif
+
+ MotorolaOrder = 0;
+
+ memcpy(Buffer+2, "Exif\0\0II",8);
+ Put16u(Buffer+10, 0x2a);
+
+ DataWriteIndex = 16;
+ Put32u(Buffer+12, DataWriteIndex-8); // first IFD offset. Means start 16 bytes in.
+
+ {
+ DirIndex = DataWriteIndex;
+ NumEntries = 2 + exifTagCount; // the two extra are the datetime and the thumbnail
+ if (gpsTagCount) {
+ ++NumEntries; // allow for the GPS info tag
+ }
+ DataWriteIndex += 2 + NumEntries*12 + 4;
+
+ Put16u(Buffer+DirIndex, NumEntries); // Number of entries
+ DirIndex += 2;
+ // Entries go here....
+ {
+ // Date/time entry
+ char* dateTime = NULL;
+ char dateBuf[20];
+ if (ImageInfo.numDateTimeTags){
+ // If we had a pre-existing exif header, use time from that.
+ dateTime = ImageInfo.DateTime;
+ } else {
+ // Oterwise, use the file's timestamp.
+ FileTimeAsString(dateBuf);
+ dateTime = dateBuf;
+ }
+ writeExifTagAndData(TAG_DATETIME,
+ FMT_STRING,
+ 20,
+ (long)(char*)dateBuf,
+ FALSE,
+ Buffer,
+ &DirIndex,
+ &DataWriteIndex);
+
+ }
+ if (exifTagCount > 0) {
+ int i;
+ for (i = 0; i < exifTagCount + gpsTagCount; i++) {
+ if (elements[i].GpsTag) {
+ continue;
+ }
+ const TagTable_t* entry = TagToTagTableEntry(elements[i].Tag);
+ if (entry == NULL) {
+ continue;
+ }
+#ifdef SUPERDEBUG
+ LOGE("create_EXIF saving tag %x value \"%s\"",elements[i].Tag, elements[i].Value);
+#endif
+ writeExifTagAndData(elements[i].Tag,
+ entry->Format,
+ entry->DataLength,
+ (long)elements[i].Value,
+ TRUE,
+ Buffer,
+ &DirIndex,
+ &DataWriteIndex);
+ }
+ }
+ {
+ if (gpsTagCount) {
+ // Link to gps dir entry
+ writeExifTagAndData(TAG_GPSINFO,
+ FMT_ULONG,
+ 1,
+ DataWriteIndex-8,
+ FALSE,
+ Buffer,
+ &DirIndex,
+ &DataWriteIndex);
+ }
+
+ // Link to exif dir entry
+ int exifDirPtr = DataWriteIndex-8;
+ if (gpsTagCount) {
+ exifDirPtr += 2 + gpsTagCount*12 + 4;
+ }
+ ThumbnailOffsetDirIndex = DirIndex;
+ writeExifTagAndData(TAG_EXIF_OFFSET,
+ FMT_ULONG,
+ 1,
+ exifDirPtr,
+ FALSE,
+ Buffer,
+ &DirIndex,
+ &DataWriteIndex);
+ }
+
+ // End of directory - contains optional link to continued directory.
+ Put32u(Buffer+DirIndex, 0);
+ printf("Ending Exif section DirIndex = %d DataWriteIndex %d", DirIndex, DataWriteIndex);
+ }
+
+ // GPS Section
+ if (gpsTagCount) {
+ DirIndex = DataWriteIndex;
+ printf("Starting GPS section DirIndex = %d", DirIndex);
+ NumEntries = gpsTagCount;
+ DataWriteIndex += 2 + NumEntries*12 + 4;
+
+ Put16u(Buffer+DirIndex, NumEntries); // Number of entries
+ DirIndex += 2;
+ {
+ int i;
+ for (i = 0; i < exifTagCount + gpsTagCount; i++) {
+ if (!elements[i].GpsTag) {
+ continue;
+ }
+ const TagTable_t* entry = GpsTagToTagTableEntry(elements[i].Tag);
+ if (entry == NULL) {
+ continue;
+ }
+#ifdef SUPERDEBUG
+ LOGE("create_EXIF saving GPS tag %x value \"%s\"",elements[i].Tag, elements[i].Value);
+#endif
+ writeExifTagAndData(elements[i].Tag,
+ entry->Format,
+ entry->DataLength,
+ (long)elements[i].Value,
+ TRUE,
+ Buffer,
+ &DirIndex,
+ &DataWriteIndex);
+ }
+ }
+
+ // End of directory - contains optional link to continued directory.
+ Put32u(Buffer+DirIndex, 0);
+ printf("Ending GPS section DirIndex = %d DataWriteIndex %d", DirIndex, DataWriteIndex);
+ }
+
+ {
+ // Now that we know where the Thumbnail section is written, we have to go
+ // back and "poke" the address to point here.
+ Put32u(Buffer+ThumbnailOffsetDirIndex + 8, DataWriteIndex-8); // Pointer or value.
+
+ printf("Starting Thumbnail section DirIndex = %d", DirIndex);
+ DirIndex = DataWriteIndex;
+ NumEntries = 2;
+ DataWriteIndex += 2 + NumEntries*12 + 4;
+
+ Put16u(Buffer+DirIndex, NumEntries); // Number of entries
+ DirIndex += 2;
+ {
+ // Link to exif dir entry
+ writeExifTagAndData(TAG_THUMBNAIL_OFFSET,
+ FMT_ULONG,
+ 1,
+ DataWriteIndex-8,
+ FALSE,
+ Buffer,
+ &DirIndex,
+ &DataWriteIndex);
+ }
+
+ {
+ // Link to exif dir entry
+ writeExifTagAndData(TAG_THUMBNAIL_LENGTH,
+ FMT_ULONG,
+ 1,
+ 0,
+ FALSE,
+ Buffer,
+ &DirIndex,
+ &DataWriteIndex);
+ }
+
+ // End of directory - contains optional link to continued directory.
+ Put32u(Buffer+DirIndex, 0);
+ printf("Ending Thumbnail section DirIndex = %d DataWriteIndex %d", DirIndex, DataWriteIndex);
+ }
+
+ Buffer[0] = (unsigned char)(DataWriteIndex >> 8);
+ Buffer[1] = (unsigned char)DataWriteIndex;
+
+ // Remove old exif section, if there was one.
+ RemoveSectionType(M_EXIF);
+
+ {
+ // Sections need malloced buffers, so do that now, especially because
+ // we now know how big it needs to be allocated.
+ unsigned char * NewBuf = malloc(DataWriteIndex);
+ if (NewBuf == NULL){
+ ErrFatal("Could not allocate memory");
+ }
+ memcpy(NewBuf, Buffer, DataWriteIndex);
+
+ CreateSection(M_EXIF, NewBuf, DataWriteIndex);
+
+ // Re-parse new exif section, now that its in place
+ // otherwise, we risk touching data that has already been freed.
+ process_EXIF(NewBuf, DataWriteIndex);
+ }
+}
+
+//--------------------------------------------------------------------------
+// Cler the rotation tag in the exif header to 1.
+//--------------------------------------------------------------------------
+const char * ClearOrientation(void)
+{
+ int a;
+ if (NumOrientations == 0) return NULL;
+
+ for (a=0;a<NumOrientations;a++){
+ switch(OrientationNumFormat[a]){
+ case FMT_SBYTE:
+ case FMT_BYTE:
+ *(uchar *)(OrientationPtr[a]) = 1;
+ break;
+
+ case FMT_USHORT:
+ Put16u(OrientationPtr[a], 1);
+ break;
+
+ case FMT_ULONG:
+ case FMT_SLONG:
+ memset(OrientationPtr, 0, 4);
+ // Can't be bothered to write generic Put32 if I only use it once.
+ if (MotorolaOrder){
+ ((uchar *)OrientationPtr[a])[3] = 1;
+ }else{
+ ((uchar *)OrientationPtr[a])[0] = 1;
+ }
+ break;
+
+ default:
+ return NULL;
+ }
+ }
+
+ return OrientTab[ImageInfo.Orientation];
+}
+
+
+
+//--------------------------------------------------------------------------
+// Remove thumbnail out of the exif image.
+//--------------------------------------------------------------------------
+int RemoveThumbnail(unsigned char * ExifSection)
+{
+ if (!DirWithThumbnailPtrs ||
+ ImageInfo.ThumbnailOffset == 0 ||
+ ImageInfo.ThumbnailSize == 0){
+ // No thumbnail, or already deleted it.
+ return 0;
+ }
+ if (ImageInfo.ThumbnailAtEnd == FALSE){
+ ErrNonfatal("Thumbnail is not at end of header, can't chop it off", 0, 0);
+ return 0;
+ }
+
+ {
+ int de;
+ int NumDirEntries;
+ NumDirEntries = Get16u(DirWithThumbnailPtrs);
+
+ for (de=0;de<NumDirEntries;de++){
+ int Tag;
+ unsigned char * DirEntry;
+ DirEntry = DIR_ENTRY_ADDR(DirWithThumbnailPtrs, de);
+ Tag = Get16u(DirEntry);
+ if (Tag == TAG_THUMBNAIL_LENGTH){
+ // Set length to zero.
+ if (Get16u(DirEntry+2) != FMT_ULONG){
+ // non standard format encoding. Can't do it.
+ ErrNonfatal("Can't remove thumbnail", 0, 0);
+ return 0;
+ }
+ Put32u(DirEntry+8, 0);
+ }
+ }
+ }
+
+ // This is how far the non thumbnail data went.
+ return ImageInfo.ThumbnailOffset+8;
+
+}
+
+
+//--------------------------------------------------------------------------
+// Convert exif time to Unix time structure
+//--------------------------------------------------------------------------
+int Exif2tm(struct tm * timeptr, char * ExifTime)
+{
+ int a;
+
+ timeptr->tm_wday = -1;
+
+ // Check for format: YYYY:MM:DD HH:MM:SS format.
+ // Date and time normally separated by a space, but also seen a ':' there, so
+ // skip the middle space with '%*c' so it can be any character.
+ a = sscanf(ExifTime, "%d%*c%d%*c%d%*c%d:%d:%d",
+ &timeptr->tm_year, &timeptr->tm_mon, &timeptr->tm_mday,
+ &timeptr->tm_hour, &timeptr->tm_min, &timeptr->tm_sec);
+
+
+ if (a == 6){
+ timeptr->tm_isdst = -1;
+ timeptr->tm_mon -= 1; // Adjust for unix zero-based months
+ timeptr->tm_year -= 1900; // Adjust for year starting at 1900
+ return TRUE; // worked.
+ }
+
+ return FALSE; // Wasn't in Exif date format.
+}
+
+
+//--------------------------------------------------------------------------
+// Show the collected image info, displaying camera F-stop and shutter speed
+// in a consistent and legible fashion.
+//--------------------------------------------------------------------------
+void ShowImageInfo(int ShowFileInfo)
+{
+ if (ShowFileInfo){
+ printf("File name : %s\n",ImageInfo.FileName);
+ printf("File size : %d bytes\n",ImageInfo.FileSize);
+
+ {
+ char Temp[20];
+ FileTimeAsString(Temp);
+ printf("File date : %s\n",Temp);
+ }
+ }
+
+ if (ImageInfo.CameraMake[0]){
+ printf("Camera make : %s\n",ImageInfo.CameraMake);
+ printf("Camera model : %s\n",ImageInfo.CameraModel);
+ }
+ if (ImageInfo.DateTime[0]){
+ printf("Date/Time : %s\n",ImageInfo.DateTime);
+ }
+ printf("Resolution : %d x %d\n",ImageInfo.Width, ImageInfo.Height);
+
+ if (ImageInfo.Orientation > 1){
+ // Only print orientation if one was supplied, and if its not 1 (normal orientation)
+ printf("Orientation : %s\n", OrientTab[ImageInfo.Orientation]);
+ }
+
+ if (ImageInfo.IsColor == 0){
+ printf("Color/bw : Black and white\n");
+ }
+
+ if (ImageInfo.FlashUsed >= 0){
+ if (ImageInfo.FlashUsed & 1){
+ printf("Flash used : Yes");
+ switch (ImageInfo.FlashUsed){
+ case 0x5: printf(" (Strobe light not detected)"); break;
+ case 0x7: printf(" (Strobe light detected) "); break;
+ case 0x9: printf(" (manual)"); break;
+ case 0xd: printf(" (manual, return light not detected)"); break;
+ case 0xf: printf(" (manual, return light detected)"); break;
+ case 0x19:printf(" (auto)"); break;
+ case 0x1d:printf(" (auto, return light not detected)"); break;
+ case 0x1f:printf(" (auto, return light detected)"); break;
+ case 0x41:printf(" (red eye reduction mode)"); break;
+ case 0x45:printf(" (red eye reduction mode return light not detected)"); break;
+ case 0x47:printf(" (red eye reduction mode return light detected)"); break;
+ case 0x49:printf(" (manual, red eye reduction mode)"); break;
+ case 0x4d:printf(" (manual, red eye reduction mode, return light not detected)"); break;
+ case 0x4f:printf(" (red eye reduction mode, return light detected)"); break;
+ case 0x59:printf(" (auto, red eye reduction mode)"); break;
+ case 0x5d:printf(" (auto, red eye reduction mode, return light not detected)"); break;
+ case 0x5f:printf(" (auto, red eye reduction mode, return light detected)"); break;
+ }
+ }else{
+ printf("Flash used : No");
+ switch (ImageInfo.FlashUsed){
+ case 0x18:printf(" (auto)"); break;
+ }
+ }
+ printf("\n");
+ }
+
+
+ if (ImageInfo.FocalLength){
+ printf("Focal length : %4.1fmm",(double)ImageInfo.FocalLength);
+ if (ImageInfo.FocalLength35mmEquiv){
+ printf(" (35mm equivalent: %dmm)", ImageInfo.FocalLength35mmEquiv);
+ }
+ printf("\n");
+ }
+
+ if (ImageInfo.DigitalZoomRatio > 1){
+ // Digital zoom used. Shame on you!
+ printf("Digital Zoom : %1.3fx\n", (double)ImageInfo.DigitalZoomRatio);
+ }
+
+ if (ImageInfo.CCDWidth){
+ printf("CCD width : %4.2fmm\n",(double)ImageInfo.CCDWidth);
+ }
+
+ if (ImageInfo.ExposureTime){
+ if (ImageInfo.ExposureTime < 0.010){
+ printf("Exposure time: %6.4f s ",(double)ImageInfo.ExposureTime);
+ }else{
+ printf("Exposure time: %5.3f s ",(double)ImageInfo.ExposureTime);
+ }
+ if (ImageInfo.ExposureTime <= 0.5){
+ printf(" (1/%d)",(int)(0.5 + 1/ImageInfo.ExposureTime));
+ }
+ printf("\n");
+ }
+ if (ImageInfo.ApertureFNumber){
+ printf("Aperture : f/%3.1f\n",(double)ImageInfo.ApertureFNumber);
+ }
+ if (ImageInfo.Distance){
+ if (ImageInfo.Distance < 0){
+ printf("Focus dist. : Infinite\n");
+ }else{
+ printf("Focus dist. : %4.2fm\n",(double)ImageInfo.Distance);
+ }
+ }
+
+ if (ImageInfo.ISOequivalent){
+ printf("ISO equiv. : %2d\n",(int)ImageInfo.ISOequivalent);
+ }
+
+ if (ImageInfo.ExposureBias){
+ // If exposure bias was specified, but set to zero, presumably its no bias at all,
+ // so only show it if its nonzero.
+ printf("Exposure bias: %4.2f\n",(double)ImageInfo.ExposureBias);
+ }
+
+ switch(ImageInfo.Whitebalance) {
+ case 1:
+ printf("Whitebalance : Manual\n");
+ break;
+ case 0:
+ printf("Whitebalance : Auto\n");
+ break;
+ }
+
+ //Quercus: 17-1-2004 Added LightSource, some cams return this, whitebalance or both
+ switch(ImageInfo.LightSource) {
+ case 1:
+ printf("Light Source : Daylight\n");
+ break;
+ case 2:
+ printf("Light Source : Fluorescent\n");
+ break;
+ case 3:
+ printf("Light Source : Incandescent\n");
+ break;
+ case 4:
+ printf("Light Source : Flash\n");
+ break;
+ case 9:
+ printf("Light Source : Fine weather\n");
+ break;
+ case 11:
+ printf("Light Source : Shade\n");
+ break;
+ default:; //Quercus: 17-1-2004 There are many more modes for this, check Exif2.2 specs
+ // If it just says 'unknown' or we don't know it, then
+ // don't bother showing it - it doesn't add any useful information.
+ }
+
+ if (ImageInfo.MeteringMode){ // 05-jan-2001 vcs
+ switch(ImageInfo.MeteringMode) {
+ case 2:
+ printf("Metering Mode: center weight\n");
+ break;
+ case 3:
+ printf("Metering Mode: spot\n");
+ break;
+ case 5:
+ printf("Metering Mode: matrix\n");
+ break;
+ }
+ }
+
+ if (ImageInfo.ExposureProgram){ // 05-jan-2001 vcs
+ switch(ImageInfo.ExposureProgram) {
+ case 1:
+ printf("Exposure : Manual\n");
+ break;
+ case 2:
+ printf("Exposure : program (auto)\n");
+ break;
+ case 3:
+ printf("Exposure : aperture priority (semi-auto)\n");
+ break;
+ case 4:
+ printf("Exposure : shutter priority (semi-auto)\n");
+ break;
+ case 5:
+ printf("Exposure : Creative Program (based towards depth of field)\n");
+ break;
+ case 6:
+ printf("Exposure : Action program (based towards fast shutter speed)\n");
+ break;
+ case 7:
+ printf("Exposure : Portrait Mode\n");
+ break;
+ case 8:
+ printf("Exposure : LandscapeMode \n");
+ break;
+ default:
+ break;
+ }
+ }
+ switch(ImageInfo.ExposureMode){
+ case 0: // Automatic (not worth cluttering up output for)
+ break;
+ case 1: printf("Exposure Mode: Manual\n");
+ break;
+ case 2: printf("Exposure Mode: Auto bracketing\n");
+ break;
+ }
+
+
+ if (ImageInfo.Process != M_SOF0){
+ // don't show it if its the plain old boring 'baseline' process, but do
+ // show it if its something else, like 'progressive' (used on web sometimes)
+ int a;
+ for (a=0;;a++){
+ if (a >= (int)PROCESS_TABLE_SIZE){
+ // ran off the end of the table.
+ printf("Jpeg process : Unknown\n");
+ break;
+ }
+ if (ProcessTable[a].Tag == ImageInfo.Process){
+ printf("Jpeg process : %s\n",ProcessTable[a].Desc);
+ break;
+ }
+ }
+ }
+
+ if (ImageInfo.GpsInfoPresent){
+ printf("GPS Latitude : %s\n",ImageInfo.GpsLat);
+ printf("GPS Longitude: %s\n",ImageInfo.GpsLong);
+ if (ImageInfo.GpsAlt[0]) printf("GPS Altitude : %s\n",ImageInfo.GpsAlt);
+ }
+
+ // Print the comment. Print 'Comment:' for each new line of comment.
+ if (ImageInfo.Comments[0]){
+ int a,c;
+ printf("Comment : ");
+ for (a=0;a<MAX_COMMENT;a++){
+ c = ImageInfo.Comments[a];
+ if (c == '\0') break;
+ if (c == '\n'){
+ // Do not start a new line if the string ends with a carriage return.
+ if (ImageInfo.Comments[a+1] != '\0'){
+ printf("\nComment : ");
+ }else{
+ printf("\n");
+ }
+ }else{
+ putchar(c);
+ }
+ }
+ printf("\n");
+ }
+ if (ImageInfo.ThumbnailOffset){
+ printf("Map: %05d-%05d: Thumbnail\n",ImageInfo.ThumbnailOffset, ImageInfo.ThumbnailOffset+ImageInfo.ThumbnailSize);
+ } else {
+ printf("NO thumbnail");
+ }
+}
+
+
+//--------------------------------------------------------------------------
+// Summarize highlights of image info on one line (suitable for grep-ing)
+//--------------------------------------------------------------------------
+void ShowConciseImageInfo(void)
+{
+ printf("\"%s\"",ImageInfo.FileName);
+
+ printf(" %dx%d",ImageInfo.Width, ImageInfo.Height);
+
+ if (ImageInfo.ExposureTime){
+ if (ImageInfo.ExposureTime <= 0.5){
+ printf(" (1/%d)",(int)(0.5 + 1/ImageInfo.ExposureTime));
+ }else{
+ printf(" (%1.1f)",ImageInfo.ExposureTime);
+ }
+ }
+
+ if (ImageInfo.ApertureFNumber){
+ printf(" f/%3.1f",(double)ImageInfo.ApertureFNumber);
+ }
+
+ if (ImageInfo.FocalLength35mmEquiv){
+ printf(" f(35)=%dmm",ImageInfo.FocalLength35mmEquiv);
+ }
+
+ if (ImageInfo.FlashUsed >= 0 && ImageInfo.FlashUsed & 1){
+ printf(" (flash)");
+ }
+
+ if (ImageInfo.IsColor == 0){
+ printf(" (bw)");
+ }
+
+ printf("\n");
+}
diff --git a/gpsinfo.c b/gpsinfo.c
new file mode 100644
index 0000000..2a25368
--- /dev/null
+++ b/gpsinfo.c
@@ -0,0 +1,299 @@
+//--------------------------------------------------------------------------
+// Parsing of GPS info from exif header.
+//
+// Matthias Wandel, Dec 1999 - Dec 2002
+//--------------------------------------------------------------------------
+#include "jhead.h"
+
+#include <string.h>
+#include <utils/Log.h>
+
+
+#define TAG_GPS_LAT_REF 1
+#define TAG_GPS_LAT 2
+#define TAG_GPS_LONG_REF 3
+#define TAG_GPS_LONG 4
+#define TAG_GPS_ALT_REF 5
+#define TAG_GPS_ALT 6
+
+
+static TagTable_t GpsTags[]= {
+ { 0x00, "GPSVersionID", FMT_BYTE, 4},
+ { 0x01, "GPSLatitudeRef", FMT_STRING, 2},
+ { 0x02, "GPSLatitude", FMT_URATIONAL, 3},
+ { 0x03, "GPSLongitudeRef", FMT_STRING, 2},
+ { 0x04, "GPSLongitude", FMT_URATIONAL, 3},
+ { 0x05, "GPSAltitudeRef", FMT_BYTE, 1},
+ { 0x06, "GPSAltitude", FMT_SRATIONAL, 1},
+ { 0x07, "GPSTimeStamp", FMT_SRATIONAL, 3},
+ { 0x08, "GPSSatellites", FMT_STRING, -1},
+ { 0x09, "GPSStatus", FMT_STRING, 2},
+ { 0x0A, "GPSMeasureMode", FMT_STRING, 2},
+ { 0x0B, "GPSDOP", FMT_SRATIONAL, 1},
+ { 0x0C, "GPSSpeedRef", FMT_STRING, 2},
+ { 0x0D, "GPSSpeed", FMT_SRATIONAL, 1},
+ { 0x0E, "GPSTrackRef", FMT_STRING, 2},
+ { 0x0F, "GPSTrack", FMT_SRATIONAL, 1},
+ { 0x10, "GPSImgDirectionRef", FMT_STRING, -1},
+ { 0x11, "GPSImgDirection", FMT_SRATIONAL, 1},
+ { 0x12, "GPSMapDatum", FMT_STRING, -1},
+ { 0x13, "GPSDestLatitudeRef", FMT_STRING, 2},
+ { 0x14, "GPSDestLatitude", FMT_SRATIONAL, 3},
+ { 0x15, "GPSDestLongitudeRef", FMT_STRING, 2},
+ { 0x16, "GPSDestLongitude", FMT_SRATIONAL, 3},
+ { 0x17, "GPSDestBearingRef", FMT_STRING, 1},
+ { 0x18, "GPSDestBearing", FMT_SRATIONAL, 1},
+ { 0x19, "GPSDestDistanceRef", FMT_STRING, 2},
+ { 0x1A, "GPSDestDistance", FMT_SRATIONAL, 1},
+ { 0x1B, "GPSProcessingMethod", FMT_STRING, -1},
+ { 0x1C, "GPSAreaInformation", FMT_STRING, -1},
+ { 0x1D, "GPSDateStamp", FMT_STRING, 11},
+ { 0x1E, "GPSDifferential", FMT_SSHORT, 1},
+};
+
+#define MAX_GPS_TAG (sizeof(GpsTags) / sizeof(TagTable_t))
+
+// Define the line below to turn on poor man's debugging output
+#undef SUPERDEBUG
+
+#ifdef SUPERDEBUG
+#define printf LOGE
+#endif
+
+
+int IsGpsTag(const char* tag) {
+ return strstr(tag, "GPS") == tag;
+}
+
+TagTable_t* GpsTagToTagTableEntry(unsigned short tag)
+{
+ unsigned int i;
+ for (i = 0; i < MAX_GPS_TAG; i++) {
+ if (GpsTags[i].Tag == tag) {
+ printf("found tag %d", tag);
+ int format = GpsTags[i].Format;
+ if (format == 0) {
+ printf("tag %s format not defined", GpsTags[i].Desc);
+ return NULL;
+ }
+ return &GpsTags[i];
+ }
+ }
+ printf("tag %d NOT FOUND", tag);
+ return NULL;
+}
+
+int GpsTagToFormatType(unsigned short tag)
+{
+ unsigned int i;
+ for (i = 0; i < MAX_GPS_TAG; i++) {
+ if (GpsTags[i].Tag == tag) {
+ printf("found tag %d", tag);
+ int format = GpsTags[i].Format;
+ if (format == 0) {
+ printf("tag %s format not defined", GpsTags[i].Desc);
+ return -1;
+ }
+ return format;
+ }
+ }
+ printf("tag %d NOT FOUND", tag);
+ return -1;
+}
+
+int GpsTagNameToValue(const char* tagName)
+{
+ unsigned int i;
+ for (i = 0; i < MAX_GPS_TAG; i++) {
+ if (strcmp(GpsTags[i].Desc, tagName) == 0) {
+ printf("found GPS tag %s val %d", GpsTags[i].Desc, GpsTags[i].Tag);
+ return GpsTags[i].Tag;
+ }
+ }
+ printf("GPS tag %s NOT FOUND", tagName);
+ return -1;
+}
+
+
+//--------------------------------------------------------------------------
+// Process GPS info directory
+//--------------------------------------------------------------------------
+void ProcessGpsInfo(unsigned char * DirStart, int ByteCountUnused, unsigned char * OffsetBase, unsigned ExifLength)
+{
+ int de;
+ unsigned a;
+ int NumDirEntries;
+
+ NumDirEntries = Get16u(DirStart);
+ #define DIR_ENTRY_ADDR(Start, Entry) (Start+2+12*(Entry))
+
+ if (ShowTags){
+ printf("(dir has %d entries)\n",NumDirEntries);
+ }
+
+ ImageInfo.GpsInfoPresent = TRUE;
+ strcpy(ImageInfo.GpsLat, "? ?");
+ strcpy(ImageInfo.GpsLong, "? ?");
+ ImageInfo.GpsAlt[0] = 0;
+
+ for (de=0;de<NumDirEntries;de++){
+ unsigned Tag, Format, Components;
+ unsigned char * ValuePtr;
+ int ComponentSize;
+ unsigned ByteCount;
+ unsigned char * DirEntry;
+ DirEntry = DIR_ENTRY_ADDR(DirStart, de);
+
+ if (DirEntry+12 > OffsetBase+ExifLength){
+ ErrNonfatal("GPS info directory goes past end of exif",0,0);
+ return;
+ }
+
+ Tag = Get16u(DirEntry);
+ Format = Get16u(DirEntry+2);
+ Components = Get32u(DirEntry+4);
+
+ if ((Format-1) >= NUM_FORMATS) {
+ // (-1) catches illegal zero case as unsigned underflows to positive large.
+ ErrNonfatal("Illegal number format %d for tag %04x", Format, Tag);
+ continue;
+ }
+
+ ComponentSize = BytesPerFormat[Format];
+ ByteCount = Components * ComponentSize;
+
+#ifdef SUPERDEBUG
+ printf("GPS tag %x format %s #components %d componentsize %d bytecount %d", Tag, formatStr(Format), Components, ComponentSize,
+ ByteCount);
+#endif
+
+ if (ByteCount > 4){
+ unsigned OffsetVal;
+ OffsetVal = Get32u(DirEntry+8);
+ // If its bigger than 4 bytes, the dir entry contains an offset.
+ if (OffsetVal+ByteCount > ExifLength){
+ // Bogus pointer offset and / or bytecount value
+ ErrNonfatal("Illegal value pointer for tag %04x", Tag,0);
+ continue;
+ }
+ ValuePtr = OffsetBase+OffsetVal;
+ }else{
+ // 4 bytes or less and value is in the dir entry itself
+ ValuePtr = DirEntry+8;
+ }
+
+ switch(Tag){
+ char FmtString[21];
+ char TempString[50];
+ double Values[3];
+
+ case TAG_GPS_LAT_REF:
+ ImageInfo.GpsLat[0] = ValuePtr[0];
+ ImageInfo.GpsLatRef[0] = ValuePtr[0];
+ ImageInfo.GpsLatRef[1] = '\0';
+ break;
+
+ case TAG_GPS_LONG_REF:
+ ImageInfo.GpsLong[0] = ValuePtr[0];
+ ImageInfo.GpsLongRef[0] = ValuePtr[0];
+ ImageInfo.GpsLongRef[1] = '\0';
+ break;
+
+ case TAG_GPS_LAT:
+ case TAG_GPS_LONG:
+ if (Format != FMT_URATIONAL){
+ ErrNonfatal("Inappropriate format (%d) for GPS coordinates!", Format, 0);
+ }
+ strcpy(FmtString, "%0.0fd %0.0fm %0.0fs");
+
+ for (a=0;a<3;a++){
+ int den, digits;
+
+ den = Get32s(ValuePtr+4+a*ComponentSize);
+ digits = 0;
+ while (den > 1){
+ den = den / 10;
+ digits += 1;
+ }
+ FmtString[1+a*7] = (char)('2'+digits+(digits ? 1 : 0));
+ FmtString[3+a*7] = (char)('0'+digits);
+
+ Values[a] = ConvertAnyFormat(ValuePtr+a*ComponentSize, Format);
+ }
+ sprintf(TempString, FmtString, Values[0], Values[1], Values[2]);
+
+ if (Tag == TAG_GPS_LAT){
+ strncpy(ImageInfo.GpsLat+2, TempString, 29);
+ }else{
+ strncpy(ImageInfo.GpsLong+2, TempString, 29);
+ }
+
+ sprintf(TempString, "%d/%d,%d/%d,%d/%d",
+ Get32s(ValuePtr), Get32s(4+(char*)ValuePtr),
+ Get32s(8+(char*)ValuePtr), Get32s(12+(char*)ValuePtr),
+ Get32s(16+(char*)ValuePtr), Get32s(20+(char*)ValuePtr));
+ if (Tag == TAG_GPS_LAT){
+ strncpy(ImageInfo.GpsLatRaw, TempString, 31);
+ }else{
+ strncpy(ImageInfo.GpsLongRaw, TempString, 31);
+ }
+ break;
+
+ case TAG_GPS_ALT_REF:
+ ImageInfo.GpsAlt[0] = (char)(ValuePtr[0] ? '-' : ' ');
+ break;
+
+ case TAG_GPS_ALT:
+ sprintf(ImageInfo.GpsAlt + 1, "%dm", Get32s(ValuePtr));
+ break;
+ }
+
+ if (ShowTags){
+ // Show tag value.
+ if (Tag < MAX_GPS_TAG){
+ printf(" %s =", GpsTags[Tag].Desc);
+ }else{
+ // Show unknown tag
+ printf(" Illegal GPS tag %04x=", Tag);
+ }
+
+ switch(Format){
+ case FMT_UNDEFINED:
+ // Undefined is typically an ascii string.
+
+ case FMT_STRING:
+ // String arrays printed without function call (different from int arrays)
+ {
+ printf("\"");
+ for (a=0;a<ByteCount;a++){
+ int ZeroSkipped = 0;
+ if (ValuePtr[a] >= 32){
+ if (ZeroSkipped){
+ printf("?");
+ ZeroSkipped = 0;
+ }
+ putchar(ValuePtr[a]);
+ }else{
+ if (ValuePtr[a] == 0){
+ ZeroSkipped = 1;
+ }
+ }
+ }
+ printf("\"\n");
+ }
+ break;
+
+ default:
+ // Handle arrays of numbers later (will there ever be?)
+ for (a=0;;){
+ PrintFormatNumber(ValuePtr+a*ComponentSize, Format, ByteCount);
+ if (++a >= Components) break;
+ printf(", ");
+ }
+ printf("\n");
+ }
+ }
+ }
+}
+
+
diff --git a/iptc.c b/iptc.c
new file mode 100644
index 0000000..37f7127
--- /dev/null
+++ b/iptc.c
@@ -0,0 +1,156 @@
+//--------------------------------------------------------------------------
+// Process IPTC data.
+//--------------------------------------------------------------------------
+#include "jhead.h"
+
+// Supported IPTC entry types
+#define IPTC_SUPLEMENTAL_CATEGORIES 0x14
+#define IPTC_KEYWORDS 0x19
+#define IPTC_CAPTION 0x78
+#define IPTC_AUTHOR 0x7A
+#define IPTC_HEADLINE 0x69
+#define IPTC_SPECIAL_INSTRUCTIONS 0x28
+#define IPTC_CATEGORY 0x0F
+#define IPTC_BYLINE 0x50
+#define IPTC_BYLINE_TITLE 0x55
+#define IPTC_CREDIT 0x6E
+#define IPTC_SOURCE 0x73
+#define IPTC_COPYRIGHT_NOTICE 0x74
+#define IPTC_OBJECT_NAME 0x05
+#define IPTC_CITY 0x5A
+#define IPTC_STATE 0x5F
+#define IPTC_COUNTRY 0x65
+#define IPTC_TRANSMISSION_REFERENCE 0x67
+#define IPTC_DATE 0x37
+#define IPTC_COPYRIGHT 0x0A
+#define IPTC_COUNTRY_CODE 0x64
+#define IPTC_REFERENCE_SERVICE 0x2D
+
+//--------------------------------------------------------------------------
+// Process and display IPTC marker.
+//
+// IPTC block consists of:
+// - Marker: 1 byte (0xED)
+// - Block length: 2 bytes
+// - IPTC Signature: 14 bytes ("Photoshop 3.0\0")
+// - 8BIM Signature 4 bytes ("8BIM")
+// - IPTC Block start 2 bytes (0x04, 0x04)
+// - IPTC Header length 1 byte
+// - IPTC header Header is padded to even length, counting the length byte
+// - Length 4 bytes
+// - IPTC Data which consists of a number of entries, each of which has the following format:
+// - Signature 2 bytes (0x1C02)
+// - Entry type 1 byte (for defined entry types, see #defines above)
+// - entry length 2 bytes
+// - entry data 'entry length' bytes
+//
+//--------------------------------------------------------------------------
+void show_IPTC (unsigned char* Data, unsigned int itemlen)
+{
+ const char IptcSig1[] = "Photoshop 3.0";
+ const char IptcSig2[] = "8BIM";
+ const char IptcSig3[] = {0x04, 0x04};
+
+ unsigned char * pos = Data + sizeof(short); // position data pointer after length field
+ unsigned char * maxpos = Data+itemlen;
+ char headerLen = 0;
+
+ if (itemlen < 25) goto corrupt;
+
+ // Check IPTC signatures
+ if (memcmp(pos, IptcSig1, sizeof(IptcSig1)-1) != 0) goto badsig;
+ pos += sizeof(IptcSig1); // move data pointer to the next field
+
+ if (memcmp(pos, IptcSig2, sizeof(IptcSig2)-1) != 0) goto badsig;
+ pos += sizeof(IptcSig2)-1; // move data pointer to the next field
+
+ if (memcmp(pos, IptcSig3, sizeof(IptcSig3)) != 0){
+badsig:
+ if (ShowTags){
+ ErrNonfatal("IPTC type signature mismatch\n",0,0);
+ }
+ return;
+ }
+ pos += sizeof(IptcSig3); // move data pointer to the next field
+
+ if (pos >= maxpos) goto corrupt;
+
+ // IPTC section found
+
+ // Skip header
+ headerLen = *pos++; // get header length and move data pointer to the next field
+ pos += headerLen + 1 - (headerLen % 2); // move data pointer to the next field (Header is padded to even length, counting the length byte)
+
+ if (pos+4 >= maxpos) goto corrupt;
+
+ // Get length (from motorola format)
+ //length = (*pos << 24) | (*(pos+1) << 16) | (*(pos+2) << 8) | *(pos+3);
+
+ pos += sizeof(long); // move data pointer to the next field
+
+ printf("======= IPTC data: =======\n");
+
+ // Now read IPTC data
+ while (pos < (Data + itemlen-5)) {
+ short signature;
+ char type = 0;
+ short length = 0;
+ char * description = NULL;
+
+ if (pos+5 > maxpos) goto corrupt;
+
+ signature = (*pos << 8) + (*(pos+1));
+ pos += sizeof(short);
+
+ if (signature != 0x1C02){
+ break;
+ }
+
+ type = *pos++;
+ length = (*pos << 8) + (*(pos+1));
+ pos += sizeof(short); // Skip tag length
+
+ if (pos+length > maxpos) goto corrupt;
+ // Process tag here
+ switch (type) {
+ case IPTC_SUPLEMENTAL_CATEGORIES: description = "SuplementalCategories"; break;
+ case IPTC_KEYWORDS: description = "Keywords"; break;
+ case IPTC_CAPTION: description = "Caption"; break;
+ case IPTC_AUTHOR: description = "Author"; break;
+ case IPTC_HEADLINE: description = "Headline"; break;
+ case IPTC_SPECIAL_INSTRUCTIONS: description = "Spec.Instr."; break;
+ case IPTC_CATEGORY: description = "Category"; break;
+ case IPTC_BYLINE: description = "Byline"; break;
+ case IPTC_BYLINE_TITLE: description = "BylineTitle"; break;
+ case IPTC_CREDIT: description = "Credit"; break;
+ case IPTC_SOURCE: description = "Source"; break;
+ case IPTC_COPYRIGHT_NOTICE: description = "(C)Notice"; break;
+ case IPTC_OBJECT_NAME: description = "ObjectName"; break;
+ case IPTC_CITY: description = "City"; break;
+ case IPTC_STATE: description = "State"; break;
+ case IPTC_COUNTRY: description = "Country"; break;
+ case IPTC_TRANSMISSION_REFERENCE: description = "OriginalTransmissionReference"; break;
+ case IPTC_DATE: description = "DateCreated"; break;
+ case IPTC_COPYRIGHT: description = "(C)Flag"; break;
+ case IPTC_REFERENCE_SERVICE: description = "CountryCode"; break;
+ case IPTC_COUNTRY_CODE: description = "Ref.Service"; break;
+ default:
+ if (ShowTags){
+ printf("Unrecognised IPTC tag: 0x%02x \n", type);
+ }
+ break;
+ }
+ if (description != NULL) {
+ char TempBuf[32];
+ memset(TempBuf, 0, sizeof(TempBuf));
+ memset(TempBuf, ' ', 13);
+ memcpy(TempBuf, description, strlen(description));
+ strcat(TempBuf, ":");
+ printf("%s %*.*s\n", TempBuf, length, length, pos);
+ }
+ pos += length;
+ }
+ return;
+corrupt:
+ ErrNonfatal("Pointer corruption in IPTC\n",0,0);
+}
diff --git a/jhead.c b/jhead.c
new file mode 100644
index 0000000..036eb69
--- /dev/null
+++ b/jhead.c
@@ -0,0 +1,1621 @@
+//--------------------------------------------------------------------------
+// Program to pull the information out of various types of EXIF digital
+// camera files and show it in a reasonably consistent way
+//
+// Version 2.71
+//
+// Compiling under Windows:
+// Make sure you have Microsoft's compiler on the path, then run make.bat
+//
+// Dec 1999 - Feb 2007
+//
+// by Matthias Wandel www.sentex.net/~mwandel
+//--------------------------------------------------------------------------
+#include "jhead.h"
+
+#include <sys/stat.h>
+#include <utils/Log.h>
+
+#define JHEAD_VERSION "2.71"
+
+// This #define turns on features that are too very specific to
+// how I organize my photos. Best to ignore everything inside #ifdef MATTHIAS
+#define MATTHIAS
+
+#ifdef _WIN32
+ #include <io.h>
+#endif
+
+static int FilesMatched;
+static int FileSequence;
+
+static const char * CurrentFile;
+
+static const char * progname; // program name for error messages
+
+//--------------------------------------------------------------------------
+// Command line options flags
+static int TrimExif = FALSE; // Cut off exif beyond interesting data.
+static int RenameToDate = FALSE;
+static int RenameAssociatedFiles = FALSE;
+static char * strftime_args = NULL; // Format for new file name.
+static int Exif2FileTime = FALSE;
+static int DoModify = FALSE;
+static int DoReadAction = FALSE;
+ int ShowTags = FALSE; // Do not show raw by default.
+static int Quiet = FALSE; // Be quiet on success (like unix programs)
+ int DumpExifMap = FALSE;
+static int ShowConcise = FALSE;
+static int CreateExifSection = FALSE;
+static char * ApplyCommand = NULL; // Apply this command to all images.
+static char * FilterModel = NULL;
+static int ExifOnly = FALSE;
+static int PortraitOnly = FALSE;
+static time_t ExifTimeAdjust = 0; // Timezone adjust
+static time_t ExifTimeSet = 0; // Set exif time to a value.
+static char DateSet[11];
+static unsigned DateSetChars = 0;
+static unsigned FileTimeToExif = FALSE;
+
+static int DeleteComments = FALSE;
+static int DeleteExif = FALSE;
+static int DeleteIptc = FALSE;
+static int DeleteUnknown = FALSE;
+static char * ThumbSaveName = NULL; // If not NULL, use this string to make up
+ // the filename to store the thumbnail to.
+
+static char * ThumbInsertName = NULL; // If not NULL, use this string to make up
+ // the filename to retrieve the thumbnail from.
+
+static int RegenThumbnail = FALSE;
+
+static char * ExifXferScrFile = NULL;// Extract Exif header from this file, and
+ // put it into the Jpegs processed.
+
+static int EditComment = FALSE; // Invoke an editor for editing the comment
+static int SupressNonFatalErrors = FALSE; // Wether or not to pint warnings on recoverable errors
+
+static char * CommentSavefileName = NULL; // Save comment to this file.
+static char * CommentInsertfileName = NULL; // Insert comment from this file.
+static char * CommentInsertLiteral = NULL; // Insert this comment (from command line)
+
+static int AutoRotate = FALSE;
+static int ZeroRotateTagOnly = FALSE;
+
+static int ShowFileInfo = TRUE; // Indicates to show standard file info
+ // (file name, file size, file date)
+
+
+
+#ifdef MATTHIAS
+ // This #ifdef to take out less than elegant stuff for editing
+ // the comments in a JPEG. The programs rdjpgcom and wrjpgcom
+ // included with Linux distributions do a better job.
+
+ static char * AddComment = NULL; // Add this tag.
+ static char * RemComment = NULL; // Remove this tag
+ static int AutoResize = FALSE;
+#endif // MATTHIAS
+
+//--------------------------------------------------------------------------
+// Error exit handler
+//--------------------------------------------------------------------------
+void ErrFatal(char * msg)
+{
+ LOGE("Error : %s\n", msg);
+ fprintf(stderr,"Error : %s\n", msg);
+ if (CurrentFile) fprintf(stderr,"in file '%s'\n",CurrentFile);
+ exit(EXIT_FAILURE);
+}
+
+//--------------------------------------------------------------------------
+// Report non fatal errors. Now that microsoft.net modifies exif headers,
+// there's corrupted ones, and there could be more in the future.
+//--------------------------------------------------------------------------
+void ErrNonfatal(char * msg, int a1, int a2)
+{
+ LOGE("Nonfatal Error : ");
+ LOGE(msg, a1, a2);
+ if (SupressNonFatalErrors) return;
+
+ fprintf(stderr,"Nonfatal Error : ");
+ if (CurrentFile) fprintf(stderr,"'%s' ",CurrentFile);
+ fprintf(stderr, msg, a1, a2);
+ fprintf(stderr, "\n");
+}
+
+//--------------------------------------------------------------------------
+// Set file time as exif time.
+//--------------------------------------------------------------------------
+void FileTimeAsString(char * TimeStr)
+{
+ struct tm ts;
+ ts = *localtime(&ImageInfo.FileDateTime);
+ strftime(TimeStr, 20, "%Y:%m:%d %H:%M:%S", &ts);
+}
+
+#if 0 // not used -- possible security risk with use of system, sprintf, etc.
+//--------------------------------------------------------------------------
+// Invoke an editor for editing a string.
+//--------------------------------------------------------------------------
+static int FileEditComment(char * TempFileName, char * Comment, int CommentSize)
+{
+ FILE * file;
+ int a;
+ char QuotedPath[PATH_MAX];
+
+ file = fopen(TempFileName, "w");
+ if (file == NULL){
+ fprintf(stderr, "Can't create file '%s'\n",TempFileName);
+ ErrFatal("could not create temporary file");
+ }
+ fwrite(Comment, CommentSize, 1, file);
+
+ fclose(file);
+
+ fflush(stdout); // So logs are contiguous.
+
+ {
+ char * Editor;
+ Editor = getenv("EDITOR");
+ if (Editor == NULL){
+#ifdef _WIN32
+ Editor = "notepad";
+#else
+ Editor = "vi";
+#endif
+ }
+
+ sprintf(QuotedPath, "%s \"%s\"",Editor, TempFileName);
+ a = system(QuotedPath);
+ }
+
+ if (a != 0){
+ perror("Editor failed to launch");
+ exit(-1);
+ }
+
+ file = fopen(TempFileName, "r");
+ if (file == NULL){
+ ErrFatal("could not open temp file for read");
+ }
+
+ // Read the file back in.
+ CommentSize = fread(Comment, 1, 999, file);
+
+ fclose(file);
+
+ unlink(TempFileName);
+
+ return CommentSize;
+}
+
+#ifdef MATTHIAS
+//--------------------------------------------------------------------------
+// Modify one of the lines in the comment field.
+// This very specific to the photo album program stuff.
+//--------------------------------------------------------------------------
+static char KnownTags[][10] = {"date", "desc","scan_date","author",
+ "keyword","videograb",
+ "show_raw","panorama","titlepix",""};
+
+static int ModifyDescriptComment(char * OutComment, char * SrcComment)
+{
+ char Line[500];
+ int Len;
+ int a,i;
+ unsigned l;
+ int HasScandate = FALSE;
+ int TagExists = FALSE;
+ int Modified = FALSE;
+ Len = 0;
+
+ OutComment[0] = 0;
+
+
+ for (i=0;;i++){
+ if (SrcComment[i] == '\r' || SrcComment[i] == '\n' || SrcComment[i] == 0 || Len >= 199){
+ // Process the line.
+ if (Len > 0){
+ Line[Len] = 0;
+ //printf("Line: '%s'\n",Line);
+ for (a=0;;a++){
+ l = strlen(KnownTags[a]);
+ if (!l){
+ // Unknown tag. Discard it.
+ printf("Error: Unknown tag '%s'\n", Line); // Deletes the tag.
+ Modified = TRUE;
+ break;
+ }
+ if (memcmp(Line, KnownTags[a], l) == 0){
+ if (Line[l] == ' ' || Line[l] == '=' || Line[l] == 0){
+ // Its a good tag.
+ if (Line[l] == ' ') Line[l] = '='; // Use equal sign for clarity.
+ if (a == 2) break; // Delete 'orig_path' tag.
+ if (a == 3) HasScandate = TRUE;
+ if (RemComment){
+ if (strlen(RemComment) == l){
+ if (!memcmp(Line, RemComment, l)){
+ Modified = TRUE;
+ break;
+ }
+ }
+ }
+ if (AddComment){
+ // Overwrite old comment of same tag with new one.
+ if (!memcmp(Line, AddComment, l+1)){
+ TagExists = TRUE;
+ strcpy(Line, AddComment);
+ Modified = TRUE;
+ }
+ }
+ strcat(OutComment, Line);
+ strcat(OutComment, "\n");
+ break;
+ }
+ }
+ }
+ }
+ Line[Len = 0] = 0;
+ if (SrcComment[i] == 0) break;
+ }else{
+ Line[Len++] = SrcComment[i];
+ }
+ }
+
+ if (AddComment && TagExists == FALSE){
+ strcat(OutComment, AddComment);
+ strcat(OutComment, "\n");
+ Modified = TRUE;
+ }
+
+ if (!HasScandate && !ImageInfo.DateTime[0]){
+ // Scan date is not in the file yet, and it doesn't have one built in. Add it.
+ char Temp[30];
+ sprintf(Temp, "scan_date=%s", ctime(&ImageInfo.FileDateTime));
+ strcat(OutComment, Temp);
+ Modified = TRUE;
+ }
+ return Modified;
+}
+//--------------------------------------------------------------------------
+// Automatic make smaller command stuff
+//--------------------------------------------------------------------------
+static int AutoResizeCmdStuff(void)
+{
+ static char CommandString[500];
+ double scale;
+
+ ApplyCommand = CommandString;
+
+ if (ImageInfo.Height <= 1280 && ImageInfo.Width <= 1280){
+ printf("not resizing %dx%x '%s'\n",ImageInfo.Height, ImageInfo.Width, ImageInfo.FileName);
+ return FALSE;
+ }
+
+ scale = 1024.0 / ImageInfo.Height;
+ if (1024.0 / ImageInfo.Width < scale) scale = 1024.0 / ImageInfo.Width;
+
+ if (scale < 0.5) scale = 0.5; // Don't scale down by more than a factor of two.
+
+ sprintf(CommandString, "mogrify -geometry %dx%d -quality 85 &i",(int)(ImageInfo.Width*scale), (int)(ImageInfo.Height*scale));
+ return TRUE;
+}
+
+
+#endif // MATTHIAS
+
+
+//--------------------------------------------------------------------------
+// Apply the specified command to the JPEG file.
+//--------------------------------------------------------------------------
+static void DoCommand(const char * FileName, int ShowIt)
+{
+ int a,e;
+ char ExecString[400];
+ char TempName[200];
+ int TempUsed = FALSE;
+
+ e = 0;
+
+ // Make a temporary file in the destination directory by changing last char.
+ strcpy(TempName, FileName);
+ a = strlen(TempName)-1;
+ TempName[a] = (char)(TempName[a] == 't' ? 'z' : 't');
+
+ // Build the exec string. &i and &o in the exec string get replaced by input and output files.
+ for (a=0;;a++){
+ if (ApplyCommand[a] == '&'){
+ if (ApplyCommand[a+1] == 'i'){
+ // Input file.
+ e += sprintf(ExecString+e, "\"%s\"",FileName);
+ a += 1;
+ continue;
+ }
+ if (ApplyCommand[a+1] == 'o'){
+ // Needs an output file distinct from the input file.
+ e += sprintf(ExecString+e, "\"%s\"",TempName);
+ a += 1;
+ TempUsed = TRUE;
+ unlink(TempName);// Remove any pre-existing temp file
+ continue;
+ }
+ }
+ ExecString[e++] = ApplyCommand[a];
+ if (ApplyCommand[a] == 0) break;
+ }
+
+ if (ShowIt) printf("Cmd:%s\n",ExecString);
+
+ errno = 0;
+ a = system(ExecString);
+
+ if (a || errno){
+ // A command can however fail without errno getting set or system returning an error.
+ if (errno) perror("system");
+ ErrFatal("Problem executing specified command");
+ }
+
+ if (TempUsed){
+ // Don't delete original file until we know a new one was created by the command.
+ struct stat dummy;
+ if (stat(TempName, &dummy) == 0){
+ unlink(FileName);
+ rename(TempName, FileName);
+ }else{
+ ErrFatal("specified command did not produce expected output file");
+ }
+ }
+}
+
+//--------------------------------------------------------------------------
+// check if this file should be skipped based on contents.
+//--------------------------------------------------------------------------
+static int CheckFileSkip(void)
+{
+ // I sometimes add code here to only process images based on certain
+ // criteria - for example, only to convert non progressive Jpegs to progressives, etc..
+
+ if (FilterModel){
+ // Filtering processing by camera model.
+ // This feature is useful when pictures from multiple cameras are colated,
+ // the its found that one of the cameras has the time set incorrectly.
+ if (strstr(ImageInfo.CameraModel, FilterModel) == NULL){
+ // Skip.
+ return TRUE;
+ }
+ }
+
+ if (ExifOnly){
+ // Filtering by EXIF only. Skip all files that have no Exif.
+ if (FindSection(M_EXIF) == NULL){
+ return TRUE;
+ }
+ }
+
+ if (PortraitOnly == 1){
+ if (ImageInfo.Width > ImageInfo.Height) return TRUE;
+ }
+
+ if (PortraitOnly == -1){
+ if (ImageInfo.Width < ImageInfo.Height) return TRUE;
+ }
+
+ return FALSE;
+}
+
+//--------------------------------------------------------------------------
+// Subsititute original name for '&i' if present in specified name.
+// This to allow specifying relative names when manipulating multiple files.
+//--------------------------------------------------------------------------
+static void RelativeName(char * OutFileName, const char * NamePattern, const char * OrigName)
+{
+ // If the filename contains substring "&i", then substitute the
+ // filename for that. This gives flexibility in terms of processing
+ // multiple files at a time.
+ char * Subst;
+ Subst = strstr(NamePattern, "&i");
+ if (Subst){
+ strncpy(OutFileName, NamePattern, Subst-NamePattern);
+ OutFileName[Subst-NamePattern] = 0;
+ strncat(OutFileName, OrigName, PATH_MAX);
+ strncat(OutFileName, Subst+2, PATH_MAX);
+ }else{
+ strcpy(OutFileName, NamePattern);
+ }
+}
+
+
+#ifdef _WIN32
+//--------------------------------------------------------------------------
+// Rename associated files
+//--------------------------------------------------------------------------
+void RenameAssociated(const char * FileName, char * NewBaseName)
+{
+ int a;
+ int PathLen;
+ int ExtPos;
+ char FilePattern[_MAX_PATH];
+ char NewName[_MAX_PATH];
+ struct _finddata_t finddata;
+ long find_handle;
+
+ for(ExtPos = strlen(FileName);FileName[ExtPos-1] != '.';){
+ if (--ExtPos == 0) return; // No extension!
+ }
+
+ memcpy(FilePattern, FileName, ExtPos);
+ FilePattern[ExtPos] = '*';
+ FilePattern[ExtPos+1] = '\0';
+
+ for(PathLen = strlen(FileName);FileName[PathLen-1] != '\\';){
+ if (--PathLen == 0) break;
+ }
+
+ find_handle = _findfirst(FilePattern, &finddata);
+
+ for (;;){
+ if (find_handle == -1) break;
+
+ // Eliminate the obvious patterns.
+ if (!memcmp(finddata.name, ".",2)) goto next_file;
+ if (!memcmp(finddata.name, "..",3)) goto next_file;
+ if (finddata.attrib & _A_SUBDIR) goto next_file;
+
+ strcpy(FilePattern+PathLen, finddata.name); // full name with path
+
+ strcpy(NewName, NewBaseName);
+ for(a = strlen(finddata.name);finddata.name[a] != '.';){
+ if (--a == 0) goto next_file;
+ }
+ strcat(NewName, finddata.name+a); // add extension to new name
+
+ if (rename(FilePattern, NewName) == 0){
+ if (!Quiet){
+ printf("%s --> %s\n",FilePattern, NewName);
+ }
+ }
+
+ next_file:
+ if (_findnext(find_handle, &finddata) != 0) break;
+ }
+ _findclose(find_handle);
+}
+#endif
+
+//--------------------------------------------------------------------------
+// Handle renaming of files by date.
+//--------------------------------------------------------------------------
+static void DoFileRenaming(const char * FileName)
+{
+ int NumAlpha = 0;
+ int NumDigit = 0;
+ int PrefixPart = 0;
+ int ExtensionPart = strlen(FileName);
+ int a;
+ struct tm tm;
+ char NewBaseName[PATH_MAX*2];
+
+ for (a=0;FileName[a];a++){
+ if (FileName[a] == '/' || FileName[a] == '\\'){
+ // Don't count path component.
+ NumAlpha = 0;
+ NumDigit = 0;
+ PrefixPart = a+1;
+ }
+
+ if (FileName[a] == '.') ExtensionPart = a; // Remember where extension starts.
+
+ if (isalpha(FileName[a])) NumAlpha += 1; // Tally up alpha vs. digits to judge wether to rename.
+ if (isdigit(FileName[a])) NumDigit += 1;
+ }
+
+ if (RenameToDate <= 1){
+ // If naming isn't forced, ensure name is mostly digits, or leave it alone.
+ if (NumAlpha > 8 || NumDigit < 4){
+ return;
+ }
+ }
+
+ if (!Exif2tm(&tm, ImageInfo.DateTime)){
+ printf("File '%s' contains no exif date stamp. Using file date\n",FileName);
+ // Use file date/time instead.
+ tm = *localtime(&ImageInfo.FileDateTime);
+ }
+
+
+ strcpy(NewBaseName, FileName); // Get path component of name.
+
+ if (strftime_args){
+ // Complicated scheme for flexibility. Just pass the args to strftime.
+ time_t UnixTime;
+
+ char *s;
+ char pattern[PATH_MAX+20];
+ int n = ExtensionPart - PrefixPart;
+
+ // Call mktime to get weekday and such filled in.
+ UnixTime = mktime(&tm);
+ if ((int)UnixTime == -1){
+ printf("Could not convert %s to unix time",ImageInfo.DateTime);
+ return;
+ }
+
+ // Substitute "%f" for the original name (minus path & extension)
+ pattern[PATH_MAX-1]=0;
+ strncpy(pattern, strftime_args, PATH_MAX-1);
+ while ((s = strstr(pattern, "%f")) && strlen(pattern) + n < PATH_MAX-1){
+ memmove(s + n, s + 2, strlen(s+2) + 1);
+ memmove(s, FileName + PrefixPart, n);
+ }
+
+ {
+ // Sequential number renaming part.
+ // '%i' type pattern becomes sequence number.
+ int ppos = -1;
+ for (a=0;pattern[a];a++){
+ if (pattern[a] == '%'){
+ ppos = a;
+ }else if (pattern[a] == 'i'){
+ if (ppos >= 0 && a<ppos+4){
+ // Replace this part with a number.
+ char pat[8];
+ char num[16];
+ memcpy(pat, pattern+ppos, 4);
+ pat[a-ppos] = 'd'; // Replace 'i' with 'd' for '%d'
+ pat[a-ppos+1] = '\0';
+ sprintf(num, pat, FileSequence); // let printf do the number formatting.
+ memmove(pattern+ppos+strlen(num), pattern+a+1, strlen(pattern+a+1)+1);
+ memcpy(pattern+ppos, num, strlen(num));
+ break;
+ }
+ }else if (!isdigit(pattern[a])){
+ ppos = -1;
+ }
+ }
+ }
+
+ strftime(NewBaseName+PrefixPart, PATH_MAX, pattern, &tm);
+ }else{
+ // My favourite scheme.
+ sprintf(NewBaseName+PrefixPart, "%02d%02d-%02d%02d%02d",
+ tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
+ }
+
+ for (a=0;;a++){
+ char NewName[PATH_MAX];
+ char NameExtra[3];
+ struct stat dummy;
+
+ if (a){
+ // Generate a suffix for the file name if previous choice of names is taken.
+ // depending on wether the name ends in a letter or digit, pick the opposite to separate
+ // it. This to avoid using a separator character - this because any good separator
+ // is before the '.' in ascii, and so sorting the names would put the later name before
+ // the name without suffix, causing the pictures to more likely be out of order.
+ if (isdigit(NewBaseName[strlen(NewBaseName)-1])){
+ NameExtra[0] = (char)('a'-1+a); // Try a,b,c,d... for suffix if it ends in a letter.
+ }else{
+ NameExtra[0] = (char)('0'-1+a); // Try 1,2,3,4... for suffix if it ends in a char.
+ }
+ NameExtra[1] = 0;
+ }else{
+ NameExtra[0] = 0;
+ }
+
+ sprintf(NewName, "%s%s.jpg", NewBaseName, NameExtra);
+
+ if (!strcmp(FileName, NewName)) break; // Skip if its already this name.
+
+ if (stat(NewName, &dummy)){
+ // This name does not pre-exist.
+ if (rename(FileName, NewName) == 0){
+ printf("%s --> %s\n",FileName, NewName);
+#ifdef _WIN32
+ if (RenameAssociatedFiles){
+ sprintf(NewName, "%s%s", NewBaseName, NameExtra);
+ RenameAssociated(FileName, NewName);
+ }
+#endif
+ }else{
+ printf("Error: Couldn't rename '%s' to '%s'\n",FileName, NewName);
+ }
+ break;
+ }
+
+ if (a >= 9){
+ printf("Possible new names for for '%s' already exist\n",FileName);
+ break;
+ }
+ }
+}
+
+//--------------------------------------------------------------------------
+// Rotate the image and its thumbnail
+//--------------------------------------------------------------------------
+static int DoAutoRotate(const char * FileName)
+{
+ if (ImageInfo.Orientation >= 2 && ImageInfo.Orientation <= 8){
+ const char * Argument;
+ Argument = ClearOrientation();
+
+ if (!ZeroRotateTagOnly){
+ char RotateCommand[PATH_MAX*2+50];
+ if (Argument == NULL){
+ ErrFatal("Orientation screwup");
+ }
+
+ sprintf(RotateCommand, "jpegtran -%s -outfile &o &i", Argument);
+ ApplyCommand = RotateCommand;
+ DoCommand(FileName, FALSE);
+ ApplyCommand = NULL;
+
+ // Now rotate the thumbnail, if there is one.
+ if (ImageInfo.ThumbnailOffset &&
+ ImageInfo.ThumbnailSize &&
+ ImageInfo.ThumbnailAtEnd){
+ // Must have a thumbnail that exists and is modifieable.
+
+ char ThumbTempName_in[PATH_MAX+4];
+ char ThumbTempName_out[PATH_MAX+4];
+
+ strcpy(ThumbTempName_in, FileName);
+ strcat(ThumbTempName_in, ".thi");
+ strcpy(ThumbTempName_out, FileName);
+ strcat(ThumbTempName_out, ".tho");
+ SaveThumbnail(ThumbTempName_in);
+ sprintf(RotateCommand,"jpegtran -%s -outfile \"%s\" \"%s\"",
+ Argument, ThumbTempName_out, ThumbTempName_in);
+
+ if (system(RotateCommand) == 0){
+ // Put the thumbnail back in the header
+ ReplaceThumbnail(ThumbTempName_out);
+ }
+
+ unlink(ThumbTempName_in);
+ unlink(ThumbTempName_out);
+ }
+ }
+ return TRUE;
+ }
+ return FALSE;
+}
+
+//--------------------------------------------------------------------------
+// Regenerate the thumbnail using mogrify
+//--------------------------------------------------------------------------
+static int RegenerateThumbnail(const char * FileName)
+{
+ char ThumbnailGenCommand[PATH_MAX*2+50];
+ if (ImageInfo.ThumbnailOffset == 0 || ImageInfo.ThumbnailAtEnd == FALSE){
+ // There is no thumbnail, or the thumbnail is not at the end.
+ return FALSE;
+ }
+
+ sprintf(ThumbnailGenCommand, "mogrify -thumbnail %dx%d \"%s\"",
+ RegenThumbnail, RegenThumbnail, FileName);
+
+ if (system(ThumbnailGenCommand) == 0){
+ // Put the thumbnail back in the header
+ return ReplaceThumbnail(FileName);
+ }else{
+ ErrFatal("Unable to run 'mogrify' command");
+ return FALSE;
+ }
+}
+
+//--------------------------------------------------------------------------
+// Do selected operations to one file at a time.
+//--------------------------------------------------------------------------
+void ProcessFile(const char * FileName)
+{
+ int Modified = FALSE;
+ ReadMode_t ReadMode = READ_METADATA;
+ CurrentFile = FileName;
+ FilesMatched = 1;
+
+ ResetJpgfile();
+
+ // Start with an empty image information structure.
+ memset(&ImageInfo, 0, sizeof(ImageInfo));
+ ImageInfo.FlashUsed = -1;
+ ImageInfo.MeteringMode = -1;
+ ImageInfo.Whitebalance = -1;
+
+ // Store file date/time.
+ {
+ struct stat st;
+ if (stat(FileName, &st) >= 0){
+ ImageInfo.FileDateTime = st.st_mtime;
+ ImageInfo.FileSize = st.st_size;
+ }else{
+ ErrFatal("No such file");
+ }
+ }
+
+ if (DoModify || RenameToDate || Exif2FileTime){
+ if (access(FileName, 2 /*W_OK*/)){
+ printf("Skipping readonly file '%s'\n",FileName);
+ return;
+ }
+ }
+
+ strncpy(ImageInfo.FileName, FileName, PATH_MAX);
+
+
+ if (ApplyCommand || AutoRotate){
+ // Applying a command is special - the headers from the file have to be
+ // pre-read, then the command executed, and then the image part of the file read.
+
+ if (!ReadJpegFile(FileName, READ_METADATA)) return;
+
+ #ifdef MATTHIAS
+ if (AutoResize){
+ // Automatic resize computation - to customize for each run...
+ if (AutoResizeCmdStuff() == 0){
+ DiscardData();
+ return;
+ }
+ }
+ #endif // MATTHIAS
+
+
+ if (CheckFileSkip()){
+ DiscardData();
+ return;
+ }
+
+ DiscardAllButExif();
+
+ if (AutoRotate){
+ if (DoAutoRotate(FileName)){
+ Modified = TRUE;
+ }
+ }else{
+ struct stat dummy;
+ DoCommand(FileName, Quiet ? FALSE : TRUE);
+
+ if (stat(FileName, &dummy)){
+ // The file is not there anymore. Perhaps the command
+ // was a delete or a move. So we are all done.
+ return;
+ }
+ Modified = TRUE;
+ }
+ ReadMode = READ_IMAGE; // Don't re-read exif section again on next read.
+
+ }else if (ExifXferScrFile){
+ char RelativeExifName[PATH_MAX+1];
+
+ // Make a relative name.
+ RelativeName(RelativeExifName, ExifXferScrFile, FileName);
+
+ if(!ReadJpegFile(RelativeExifName, READ_METADATA)) return;
+
+ DiscardAllButExif(); // Don't re-read exif section again on next read.
+
+ Modified = TRUE;
+ ReadMode = READ_IMAGE;
+ }
+
+ if (DoModify){
+ ReadMode |= READ_IMAGE;
+ }
+
+ if (!ReadJpegFile(FileName, ReadMode)) return;
+
+ if (CheckFileSkip()){
+ DiscardData();
+ return;
+ }
+
+ FileSequence += 1; // Count files processed.
+
+ if (ShowConcise){
+ ShowConciseImageInfo();
+ }else{
+ if (!(DoModify || DoReadAction) || ShowTags){
+ ShowImageInfo(ShowFileInfo);
+
+ {
+ // if IPTC section is present, show it also.
+ Section_t * IptcSection;
+ IptcSection = FindSection(M_IPTC);
+
+ if (IptcSection){
+ show_IPTC(IptcSection->Data, IptcSection->Size);
+ }
+ }
+ printf("\n");
+ }
+ }
+
+ if (ThumbSaveName){
+ char OutFileName[PATH_MAX+1];
+ // Make a relative name.
+ RelativeName(OutFileName, ThumbSaveName, FileName);
+
+ if (SaveThumbnail(OutFileName)){
+ printf("Created: '%s'\n", OutFileName);
+ }
+ }
+
+ if (CreateExifSection){
+ // Make a new minimal exif section
+ create_EXIF(NULL, 0, 0);
+ Modified = TRUE;
+ }
+
+ if (RegenThumbnail){
+ if (RegenerateThumbnail(FileName)){
+ Modified = TRUE;
+ }
+ }
+
+ if (ThumbInsertName){
+ char ThumbFileName[PATH_MAX+1];
+ // Make a relative name.
+ RelativeName(ThumbFileName, ThumbInsertName, FileName);
+
+ if (ReplaceThumbnail(ThumbFileName)){
+ Modified = TRUE;
+ }
+ }else if (TrimExif){
+ // Deleting thumbnail is just replacing it with a null thumbnail.
+ if (ReplaceThumbnail(NULL)){
+ Modified = TRUE;
+ }
+ }
+
+ if (
+#ifdef MATTHIAS
+ AddComment || RemComment ||
+#endif
+ EditComment || CommentInsertfileName || CommentInsertLiteral){
+
+ Section_t * CommentSec;
+ char Comment[1001];
+ int CommentSize;
+
+ CommentSec = FindSection(M_COM);
+
+ if (CommentSec == NULL){
+ unsigned char * DummyData;
+
+ DummyData = (uchar *) malloc(3);
+ DummyData[0] = 0;
+ DummyData[1] = 2;
+ DummyData[2] = 0;
+ CommentSec = CreateSection(M_COM, DummyData, 2);
+ }
+
+ CommentSize = CommentSec->Size-2;
+ if (CommentSize > 1000){
+ fprintf(stderr, "Truncating comment at 1000 chars\n");
+ CommentSize = 1000;
+ }
+
+ if (CommentInsertfileName){
+ // Read a new comment section from file.
+ char CommentFileName[PATH_MAX+1];
+ FILE * CommentFile;
+
+ // Make a relative name.
+ RelativeName(CommentFileName, CommentInsertfileName, FileName);
+
+ CommentFile = fopen(CommentFileName,"r");
+ if (CommentFile == NULL){
+ printf("Could not open '%s'\n",CommentFileName);
+ }else{
+ // Read it in.
+ // Replace the section.
+ CommentSize = fread(Comment, 1, 999, CommentFile);
+ fclose(CommentFile);
+ if (CommentSize < 0) CommentSize = 0;
+ }
+ }else if (CommentInsertLiteral){
+ strncpy(Comment, CommentInsertLiteral, 1000);
+ CommentSize = strlen(Comment);
+ }else{
+#ifdef MATTHIAS
+ char CommentZt[1001];
+ memcpy(CommentZt, (char *)CommentSec->Data+2, CommentSize);
+ CommentZt[CommentSize] = '\0';
+ if (ModifyDescriptComment(Comment, CommentZt)){
+ Modified = TRUE;
+ CommentSize = strlen(Comment);
+ }
+ if (EditComment)
+#else
+ memcpy(Comment, (char *)CommentSec->Data+2, CommentSize);
+#endif
+ {
+ char EditFileName[PATH_MAX+4];
+ strcpy(EditFileName, FileName);
+ strcat(EditFileName, ".txt");
+
+ CommentSize = FileEditComment(EditFileName, Comment, CommentSize);
+ }
+ }
+
+ if (strcmp(Comment, (char *)CommentSec->Data+2)){
+ // Discard old comment section and put a new one in.
+ int size;
+ size = CommentSize+2;
+ free(CommentSec->Data);
+ CommentSec->Size = size;
+ CommentSec->Data = malloc(size);
+ CommentSec->Data[0] = (uchar)(size >> 8);
+ CommentSec->Data[1] = (uchar)(size);
+ memcpy((CommentSec->Data)+2, Comment, size-2);
+ Modified = TRUE;
+ }
+ if (!Modified){
+ printf("Comment not modified\n");
+ }
+ }
+
+
+ if (CommentSavefileName){
+ Section_t * CommentSec;
+ CommentSec = FindSection(M_COM);
+
+ if (CommentSec != NULL){
+ char OutFileName[PATH_MAX+1];
+ FILE * CommentFile;
+
+ // Make a relative name.
+ RelativeName(OutFileName, CommentSavefileName, FileName);
+
+ CommentFile = fopen(OutFileName,"w");
+
+ if (CommentFile){
+ fwrite((char *)CommentSec->Data+2, CommentSec->Size-2 ,1, CommentFile);
+ fclose(CommentFile);
+ }else{
+ ErrFatal("Could not write comment file");
+ }
+ }else{
+ printf("File '%s' contains no comment section\n",FileName);
+ }
+ }
+
+ if (ExifTimeAdjust || ExifTimeSet || DateSetChars || FileTimeToExif){
+ if (ImageInfo.numDateTimeTags){
+ struct tm tm;
+ time_t UnixTime;
+ char TempBuf[50];
+ int a;
+ Section_t * ExifSection;
+ if (ExifTimeSet){
+ // A time to set was specified.
+ UnixTime = ExifTimeSet;
+ }else{
+ if (FileTimeToExif){
+ FileTimeAsString(ImageInfo.DateTime);
+ }
+ if (DateSetChars){
+ memcpy(ImageInfo.DateTime, DateSet, DateSetChars);
+ a = 1970;
+ sscanf(DateSet, "%d", &a);
+ if (a < 1970){
+ strcpy(TempBuf, ImageInfo.DateTime);
+ goto skip_unixtime;
+ }
+ }
+ // A time offset to adjust by was specified.
+ if (!Exif2tm(&tm, ImageInfo.DateTime)) goto badtime;
+
+ // Convert to unix 32 bit time value, add offset, and convert back.
+ UnixTime = mktime(&tm);
+ if ((int)UnixTime == -1) goto badtime;
+ UnixTime += ExifTimeAdjust;
+ }
+ tm = *localtime(&UnixTime);
+
+ // Print to temp buffer first to avoid putting null termination in destination.
+ // snprintf() would do the trick, hbut not available everywhere (like FreeBSD 4.4)
+ sprintf(TempBuf, "%04d:%02d:%02d %02d:%02d:%02d",
+ tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec);
+
+skip_unixtime:
+ ExifSection = FindSection(M_EXIF);
+
+ for (a = 0; a < ImageInfo.numDateTimeTags; a++) {
+ uchar * Pointer;
+ Pointer = ExifSection->Data+ImageInfo.DateTimeOffsets[a]+8;
+ memcpy(Pointer, TempBuf, 19);
+ }
+
+ Modified = TRUE;
+ }else{
+ printf("File '%s' contains no Exif timestamp to change\n", FileName);
+ }
+ }
+
+ if (DeleteComments){
+ if (RemoveSectionType(M_COM)) Modified = TRUE;
+ }
+ if (DeleteExif){
+ if (RemoveSectionType(M_EXIF)) Modified = TRUE;
+ }
+ if (DeleteIptc){
+ if (RemoveSectionType(M_IPTC)) Modified = TRUE;
+ }
+ if (DeleteUnknown){
+ if (RemoveUnknownSections()) Modified = TRUE;
+ }
+
+
+ if (Modified){
+ char BackupName[400];
+ struct stat buf;
+
+ if (!Quiet) printf("Modified: %s\n",FileName);
+
+ strcpy(BackupName, FileName);
+ strcat(BackupName, ".t");
+
+ // Remove any .old file name that may pre-exist
+ unlink(BackupName);
+
+ // Rename the old file.
+ rename(FileName, BackupName);
+
+ // Write the new file.
+ if (WriteJpegFile(FileName)) {
+
+ // Copy the access rights from original file
+ if (stat(BackupName, &buf) == 0){
+ // set Unix access rights and time to new file
+ struct utimbuf mtime;
+ chmod(FileName, buf.st_mode);
+
+ mtime.actime = buf.st_mtime;
+ mtime.modtime = buf.st_mtime;
+
+ utime(FileName, &mtime);
+ }
+
+ // Now that we are done, remove original file.
+ unlink(BackupName);
+ } else {
+ // move back the backup file
+ rename(BackupName, FileName);
+ }
+ }
+
+
+ if (Exif2FileTime){
+ // Set the file date to the date from the exif header.
+ if (ImageInfo.numDateTimeTags){
+ // Converte the file date to Unix time.
+ struct tm tm;
+ time_t UnixTime;
+ struct utimbuf mtime;
+ if (!Exif2tm(&tm, ImageInfo.DateTime)) goto badtime;
+
+ UnixTime = mktime(&tm);
+ if ((int)UnixTime == -1){
+ goto badtime;
+ }
+
+ mtime.actime = UnixTime;
+ mtime.modtime = UnixTime;
+
+ if (utime(FileName, &mtime) != 0){
+ printf("Error: Could not change time of file '%s'\n",FileName);
+ }else{
+ if (!Quiet) printf("%s\n",FileName);
+ }
+ }else{
+ printf("File '%s' contains no Exif timestamp\n", FileName);
+ }
+ }
+
+ // Feature to rename image according to date and time from camera.
+ // I use this feature to put images from multiple digicams in sequence.
+
+ if (RenameToDate){
+ DoFileRenaming(FileName);
+ }
+ DiscardData();
+ return;
+badtime:
+ printf("Error: Time '%s': cannot convert to Unix time\n",ImageInfo.DateTime);
+ DiscardData();
+}
+
+//--------------------------------------------------------------------------
+// complain about bad state of the command line.
+//--------------------------------------------------------------------------
+static void Usage (void)
+{
+ printf("Jhead is a program for manipulating settings and thumnails in Exif jpeg headers\n"
+ "used by most Digital Cameras. v"JHEAD_VERSION" Matthias Wandel, April 29 2006.\n"
+ "http://www.sentex.net/~mwandel/jhead\n"
+ "\n");
+
+ printf("Usage: %s [options] files\n", progname);
+ printf("Where:\n"
+ " files path/filenames with or without wildcards\n"
+
+ "[options] are:\n"
+ "\nGENERAL METADATA:\n"
+ " -te <name> Transfer exif header from another image file <name>\n"
+ " Uses same name mangling as '-st' option\n"
+ " -dc Delete comment field (as left by progs like Photoshop & Compupic)\n"
+ " -de Strip Exif section (smaller JPEG file, but lose digicam info)\n"
+ " -di Delete IPTC section (from Photoshop, or Picasa)\n"
+ " -du Delete non image sections except for Exif and comment sections\n"
+ " -purejpg Strip all unnecessary data from jpeg (combines -dc -de and -du)\n"
+ " -mkexif Create new minimal exif section (overwrites pre-existing exif)\n"
+ " -ce Edit comment field. Uses environment variable 'editor' to\n"
+ " determine which editor to use. If editor not set, uses VI\n"
+ " under Unix and notepad with windows\n"
+ " -cs <name> Save comment section to a file\n"
+ " -ci <name> Insert comment section from a file. -cs and -ci use same naming\n"
+ " scheme as used by the -st option\n"
+ " -cl string Insert literal comment string\n"
+
+ "\nDATE / TIME MANIPULATION:\n"
+ " -ft Set file modification time to Exif time\n"
+ " -dsft Set Exif time to file modification time\n"
+ " -n[format-string]\n"
+ " Rename files according to date. Uses exif date if present, file\n"
+ " date otherwise. If the optional format-string is not supplied,\n"
+ " the format is mmdd-hhmmss. If a format-string is given, it is\n"
+ " is passed to the 'strftime' function for formatting\n"
+ " In addition to strftime format codes:\n"
+ " '%%f' as part of the string will include the original file name\n"
+ " '%%i' will include a sequence number, starting from 1. You can\n"
+ " You can specify '%%03i' for example to get leading zeros.\n"
+ " This feature is useful for ordering files from multiple digicams to\n"
+ " sequence of taking. Only renames files whose names are mostly\n"
+ " numerical (as assigned by digicam)\n"
+ " The '.jpg' is automatically added to the end of the name. If the\n"
+ " destination name already exists, a letter or digit is added to \n"
+ " the end of the name to make it unique.\n"
+ " -nf[format-string]\n"
+ " Same as -n, but rename regardless of original name\n"
+ " -a (Windows only) Rename files with same name but different extension\n"
+ " Use together with -n to rename .AVI files from exif in .THM files\n"
+ " for example\n"
+ " -ta<+|->h[:mm[:ss]]\n"
+ " Adjust time by h:mm backwards or forwards. Useful when having\n"
+ " taken pictures with the wrong time set on the camera, such as when\n"
+ " traveling across time zones or DST changes. Dates can be adjusted\n"
+ " by offsetting by 24 hours or more. For large date adjustments,\n"
+ " use the -da option\n"
+ " -da<date>-<date>\n"
+ " Adjust date by large amounts. This is used to fix photos from\n"
+ " cameras where the date got set back to the default camera date\n"
+ " by accident or battery removal.\n"
+ " To deal with different months and years having different numbers of\n"
+ " days, a simple date-month-year offset would result in unexpected\n"
+ " results. Instead, the difference is specified as desired date\n"
+ " minus original date. Date is specified as yyyy:mmm:dd or as date\n"
+ " and time in the format yyyy:mmm:dd/hh:mm:ss\n"
+ " -ts<time> Set the Exif internal time to <time>. <time> is in the format\n"
+ " yyyy:mm:dd-hh:mm:ss\n"
+ " -ds<date> Set the Exif internal date. <date> is in the format YYYY:MM:DD\n"
+ " or YYYY:MM or YYYY\n"
+
+ "\nTHUMBNAIL MANIPULATION:\n"
+ " -dt Remove exif integral thumbnails. Typically trims 10k\n"
+ " -st <name> Save Exif thumbnail, if there is one, in file <name>\n"
+ " If output file name contains the substring \"&i\" then the\n"
+ " image file name is substitute for the &i. Note that quotes around\n"
+ " the argument are required for the '&' to be passed to the program.\n"
+#ifndef _WIN32
+ " An output name of '-' causes thumbnail to be written to stdout\n"
+#endif
+ " -rt <name> Replace Exif thumbnail. Can only be done with headers that\n"
+ " already contain a thumbnail.\n"
+ " -rgt[size] Regnerate exif thumbnail. Only works if image already\n"
+ " contains a thumbail. size specifies maximum height or width of\n"
+ " thumbnail. Relies on 'mogrify' programs to be on path\n"
+
+ "\nROTATION TAG MANIPULATION:\n"
+ " -autorot Invoke jpegtran to rotate images according to Exif orientation tag\n"
+ " Note: Windows users must get jpegtran for this to work\n"
+ " -norot Zero out the rotation tag. This to avoid some browsers from\n"
+ " rotating the image again after you rotated it but neglected to\n"
+ " clear the rotation tag\n"
+
+ "\nOUTPUT VERBOSITY CONTROL:\n"
+ " -h help (this text)\n"
+ " -v even more verbose output\n"
+ " -q Quiet (no messages on success, like Unix)\n"
+ " -V Show jhead version\n"
+ " -exifmap Dump header bytes, annotate. Pipe thru sort for better viewing\n"
+ " -se Supress error messages relating to corrupt exif header structure\n"
+ " -c concise output\n"
+ " -nofinfo Don't show file info (name/size/date)\n"
+
+ "\nFILE MATCHING AND SELECTION:\n"
+ " -model model\n"
+ " Only process files from digicam containing model substring in\n"
+ " camera model description\n"
+ " -exonly Skip all files that don't have an exif header (skip all jpegs that\n"
+ " were not created by digicam)\n"
+ " -cmd command\n"
+ " Apply 'command' to every file, then re-insert exif and command\n"
+ " sections into the image. &i will be substituted for the input file\n"
+ " name, and &o (if &o is used). Use quotes around the command string\n"
+ " This is most useful in conjunction with the free ImageMagick tool. \n"
+ " For example, with my Canon S100, which suboptimally compresses\n"
+ " jpegs I can specify\n"
+ " jhead -cmd \"mogrify -quality 80 &i\" *.jpg\n"
+ " to re-compress a lot of images using ImageMagick to half the size,\n"
+ " and no visible loss of quality while keeping the exif header\n"
+ " Another invocation I like to use is jpegtran (hard to find for\n"
+ " windows). I type:\n"
+ " jhead -cmd \"jpegtran -progressive &i &o\" *.jpg\n"
+ " to convert jpegs to progressive jpegs (Unix jpegtran syntax\n"
+ " differs slightly)\n"
+ " -orp Only operate on 'portrait' aspect ratio images\n"
+ " -orl Only operate on 'landscape' aspect ratio images\n"
+#ifdef _WIN32
+ " -r No longer supported. Use the ** wildcard to recurse directories\n"
+ " with instead.\n"
+ " examples:\n"
+ " jhead **/*.jpg\n"
+ " jhead \"c:\\my photos\\**\\*.jpg\"\n"
+#endif
+
+
+#ifdef MATTHIAS
+ "\n"
+ " -cr Remove comment tag (my way)\n"
+ " -ca Add comment tag (my way)\n"
+ " -ar Auto resize to fit in 1024x1024, but never less than half\n"
+#endif //MATTHIAS
+
+
+ );
+
+ exit(EXIT_FAILURE);
+}
+
+
+//--------------------------------------------------------------------------
+// Parse specified date or date+time from command line.
+//--------------------------------------------------------------------------
+time_t ParseCmdDate(char * DateSpecified)
+{
+ int a;
+ struct tm tm;
+ time_t UnixTime;
+
+ tm.tm_wday = -1;
+ tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
+
+ a = sscanf(DateSpecified, "%d:%d:%d/%d:%d:%d",
+ &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
+ &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
+
+ if (a != 3 && a < 5){
+ // Date must be YYYY:MM:DD, YYYY:MM:DD+HH:MM
+ // or YYYY:MM:DD+HH:MM:SS
+ ErrFatal("Could not parse specified date");
+ }
+ tm.tm_isdst = -1;
+ tm.tm_mon -= 1; // Adjust for unix zero-based months
+ tm.tm_year -= 1900; // Adjust for year starting at 1900
+
+ UnixTime = mktime(&tm);
+ if (UnixTime == -1){
+ ErrFatal("Specified time is invalid or out of range");
+ }
+
+ return UnixTime;
+}
+
+//--------------------------------------------------------------------------
+// The main program.
+//--------------------------------------------------------------------------
+#if 0
+int main (int argc, char **argv)
+{
+ int argn;
+ char * arg;
+ progname = argv[0];
+
+ for (argn=1;argn<argc;argn++){
+ arg = argv[argn];
+ if (arg[0] != '-') break; // Filenames from here on.
+
+ // General metadata options:
+ if (!strcmp(arg,"-te")){
+ ExifXferScrFile = argv[++argn];
+ DoModify = TRUE;
+ }else if (!strcmp(arg,"-dc")){
+ DeleteComments = TRUE;
+ DoModify = TRUE;
+ }else if (!strcmp(arg,"-de")){
+ DeleteExif = TRUE;
+ DoModify = TRUE;
+ }else if (!strcmp(arg,"-di")){
+ DeleteIptc = TRUE;
+ DoModify = TRUE;
+ }else if (!strcmp(arg, "-du")){
+ DeleteUnknown = TRUE;
+ DoModify = TRUE;
+ }else if (!strcmp(arg, "-purejpg")){
+ DeleteExif = TRUE;
+ DeleteComments = TRUE;
+ DeleteIptc = TRUE;
+ DeleteUnknown = TRUE;
+ DoModify = TRUE;
+ }else if (!strcmp(arg,"-ce")){
+ EditComment = TRUE;
+ DoModify = TRUE;
+ }else if (!strcmp(arg,"-cs")){
+ CommentSavefileName = argv[++argn];
+ }else if (!strcmp(arg,"-ci")){
+ CommentInsertfileName = argv[++argn];
+ DoModify = TRUE;
+ }else if (!strcmp(arg,"-cl")){
+ CommentInsertLiteral = argv[++argn];
+ DoModify = TRUE;
+ }else if (!strcmp(arg,"-mkexif")){
+ CreateExifSection = TRUE;
+ DoModify = TRUE;
+
+ // Output verbosity control
+ }else if (!strcmp(arg,"-h")){
+ Usage();
+ }else if (!strcmp(arg,"-v")){
+ ShowTags = TRUE;
+ }else if (!strcmp(arg,"-q")){
+ Quiet = TRUE;
+ }else if (!strcmp(arg,"-V")){
+ printf("Jhead version: "JHEAD_VERSION" Compiled: "__DATE__"\n");
+ exit(0);
+ }else if (!strcmp(arg,"-exifmap")){
+ DumpExifMap = TRUE;
+ }else if (!strcmp(arg,"-se")){
+ SupressNonFatalErrors = TRUE;
+ }else if (!strcmp(arg,"-c")){
+ ShowConcise = TRUE;
+ }else if (!strcmp(arg,"-nofinfo")){
+ ShowFileInfo = 0;
+
+ // Thumbnail manipulation options
+ }else if (!strcmp(arg,"-dt")){
+ TrimExif = TRUE;
+ DoModify = TRUE;
+ }else if (!strcmp(arg,"-st")){
+ ThumbSaveName = argv[++argn];
+ DoReadAction = TRUE;
+ }else if (!strcmp(arg,"-rt")){
+ ThumbInsertName = argv[++argn];
+ DoModify = TRUE;
+ }else if (!memcmp(arg,"-rgt", 4)){
+ RegenThumbnail = 160;
+ sscanf(arg+4, "%d", &RegenThumbnail);
+ if (RegenThumbnail > 320){
+ ErrFatal("Specified thumbnail geometry too big!");
+ }
+ DoModify = TRUE;
+
+ // Rotation tag manipulation
+ }else if (!strcmp(arg,"-autorot")){
+ AutoRotate = 1;
+ DoModify = TRUE;
+ }else if (!strcmp(arg,"-norot")){
+ AutoRotate = 1;
+ ZeroRotateTagOnly = 1;
+ DoModify = TRUE;
+
+ // Date/Time manipulation options
+ }else if (!memcmp(arg,"-n",2)){
+ RenameToDate = 1;
+ DoReadAction = TRUE; // Rename doesn't modify file, so count as read action.
+ arg+=2;
+ if (*arg == 'f'){
+ RenameToDate = 2;
+ arg++;
+ }
+ if (*arg){
+ // A strftime format string is supplied.
+ strftime_args = arg;
+ //printf("strftime_args = %s\n",arg);
+ }
+ }else if (!strcmp(arg,"-a")){
+ RenameAssociatedFiles = TRUE;
+ #ifndef _WIN32
+ ErrFatal("Error: -a only supported in Windows version");
+ #endif
+ }else if (!strcmp(arg,"-ft")){
+ Exif2FileTime = TRUE;
+ DoReadAction = TRUE;
+ }else if (!memcmp(arg,"-ta",3)){
+ // Time adjust feature.
+ int hours, minutes, seconds, n;
+ minutes = seconds = 0;
+ if (arg[3] != '-' && arg[3] != '+'){
+ ErrFatal("Error: -ta must be followed by +/- and a time");
+ }
+ n = sscanf(arg+4, "%d:%d:%d", &hours, &minutes, &seconds);
+
+ if (n < 1){
+ ErrFatal("Error: -ta must be immediately followed by time");
+ }
+ if (ExifTimeAdjust) ErrFatal("Can only use one of -da or -ta options at once");
+ ExifTimeAdjust = hours*3600 + minutes*60 + seconds;
+ if (arg[3] == '-') ExifTimeAdjust = -ExifTimeAdjust;
+ DoModify = TRUE;
+ }else if (!memcmp(arg,"-da",3)){
+ // Date adjust feature (large time adjustments)
+ time_t NewDate, OldDate = 0;
+ char * pOldDate;
+ NewDate = ParseCmdDate(arg+3);
+ pOldDate = strstr(arg+1, "-");
+ if (pOldDate){
+ OldDate = ParseCmdDate(pOldDate+1);
+ }else{
+ ErrFatal("Must specifiy second date for -da option");
+ }
+ if (ExifTimeAdjust) ErrFatal("Can only use one of -da or -ta options at once");
+ ExifTimeAdjust = NewDate-OldDate;
+ DoModify = TRUE;
+ }else if (!memcmp(arg,"-dsft",5)){
+ // Set file time to date/time in exif
+ FileTimeToExif = TRUE;
+ DoModify = TRUE;
+ }else if (!memcmp(arg,"-ds",3)){
+ // Set date feature
+ int a;
+ // Check date validity and copy it. Could be incompletely specified.
+ strcpy(DateSet, "0000:01:01");
+ for (a=0;arg[a+3];a++){
+ if (isdigit(DateSet[a])){
+ if (!isdigit(arg[a+3])){
+ a = 0;
+ break;
+ }
+ }else{
+ if (arg[a+3] != ':'){
+ a=0;
+ break;
+ }
+ }
+ DateSet[a] = arg[a+3];
+ }
+ if (a < 4 || a > 10){
+ ErrFatal("Date must be in format YYYY, YYYY:MM, or YYYY:MM:DD");
+ }
+ DateSetChars = a;
+ DoModify = TRUE;
+ }else if (!memcmp(arg,"-ts",3)){
+ // Set the exif time.
+ // Time must be specified as "yyyy:mm:dd-hh:mm:ss"
+ char * c;
+ struct tm tm;
+
+ c = strstr(arg+1, "-");
+ if (c) *c = ' '; // Replace '-' with a space.
+
+ if (!Exif2tm(&tm, arg+3)){
+ ErrFatal("-ts option must be followed by time in format yyyy:mmm:dd-hh:mm:ss\n"
+ "Example: jhead -ts2001:01:01-12:00:00 foo.jpg");
+ }
+
+ ExifTimeSet = mktime(&tm);
+
+ if ((int)ExifTimeSet == -1) ErrFatal("Time specified is out of range");
+ DoModify = TRUE;
+
+ // File matching and selection
+ }else if (!strcmp(arg,"-model")){
+ if (argn+1 >= argc) Usage(); // No extra argument.
+ FilterModel = argv[++argn];
+ }else if (!strcmp(arg,"-exonly")){
+ ExifOnly = 1;
+ }else if (!strcmp(arg,"-orp")){
+ PortraitOnly = 1;
+ }else if (!strcmp(arg,"-orl")){
+ PortraitOnly = -1;
+ }else if (!strcmp(arg,"-cmd")){
+ if (argn+1 >= argc) Usage(); // No extra argument.
+ ApplyCommand = argv[++argn];
+ DoModify = TRUE;
+
+#ifdef MATTHIAS
+ }else if (!strcmp(arg,"-ca")){
+ // Its a literal comment. Add.
+ AddComment = argv[++argn];
+ DoModify = TRUE;
+ }else if (!strcmp(arg,"-cr")){
+ // Its a literal comment. Remove this keyword.
+ RemComment = argv[++argn];
+ DoModify = TRUE;
+ }else if (!strcmp(arg,"-ar")){
+ AutoResize = TRUE;
+ ShowConcise = TRUE;
+ ApplyCommand = (char *)1; // Must be non null so it does commands.
+ DoModify = TRUE;
+#endif // MATTHIAS
+ }else{
+ printf("Argument '%s' not understood\n",arg);
+ printf("Use jhead -h for list of arguments\n");
+ exit(-1);
+ }
+ if (argn >= argc){
+ // Used an extra argument - becuase the last argument
+ // used up an extr argument.
+ ErrFatal("Extra argument required");
+ }
+ }
+ if (argn == argc){
+ ErrFatal("No files to process. Use -h for help");
+ }
+
+ if (ThumbSaveName != NULL && strcmp(ThumbSaveName, "&i") == 0){
+ printf("Error: By specifying \"&i\" for the thumbail name, your original file\n"
+ " will be overwitten. If this is what you really want,\n"
+ " specify -st \"./&i\" to override this check\n");
+ exit(0);
+ }
+
+ if (RegenThumbnail){
+ if (ThumbSaveName || ThumbInsertName){
+ printf("Error: Cannot regen and save or insert thumbnail in same run\n");
+ exit(0);
+ }
+ }
+
+ if (EditComment){
+ if (CommentSavefileName != NULL || CommentInsertfileName != NULL){
+ printf("Error: Cannot use -ce option in combination with -cs or -ci\n");
+ exit(0);
+ }
+ }
+
+
+ if (ExifXferScrFile){
+ if (FilterModel || ApplyCommand){
+ ErrFatal("Error: Filter by model and/or applying command to files\n"
+ " invalid while transfering Exif headers");
+ }
+ }
+
+ FileSequence = 0;
+ for (;argn<argc;argn++){
+ FilesMatched = FALSE;
+
+ #ifdef _WIN32
+ {
+ int a;
+ for (a=0;;a++){
+ if (argv[argn][a] == '\0') break;
+ if (argv[argn][a] == '/') argv[argn][a] = '\\';
+ }
+ }
+ // Use my globbing module to do fancier wildcard expansion with recursive
+ // subdirectories under Windows.
+ MyGlob(argv[argn], ProcessFile);
+ #else
+ // Under linux, don't do any extra fancy globbing - shell globbing is
+ // pretty fancy as it is - although not as good as myglob.c
+ ProcessFile(argv[argn]);
+ #endif
+
+ if (!FilesMatched){
+ fprintf(stderr, "Error: No files matched '%s'\n",argv[argn]);
+ }
+ }
+
+ if (FileSequence == 0){
+ return EXIT_FAILURE;
+ }else{
+ return EXIT_SUCCESS;
+ }
+}
+#endif
+
+#endif // commented out -- security risk
+
diff --git a/jhead.h b/jhead.h
new file mode 100644
index 0000000..2eca8b8
--- /dev/null
+++ b/jhead.h
@@ -0,0 +1,248 @@
+//--------------------------------------------------------------------------
+// Include file for jhead program.
+//
+// This include file only defines stuff that goes across modules.
+// I like to keep the definitions for macros and structures as close to
+// where they get used as possible, so include files only get stuff that
+// gets used in more than one file.
+//--------------------------------------------------------------------------
+#define _CRT_SECURE_NO_DEPRECATE 1
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <ctype.h>
+
+//--------------------------------------------------------------------------
+
+#ifdef _WIN32
+ #include <sys/utime.h>
+#else
+ #include <utime.h>
+ #include <sys/types.h>
+ #include <unistd.h>
+ #include <errno.h>
+ #include <limits.h>
+#endif
+
+
+typedef unsigned char uchar;
+
+#ifndef TRUE
+ #define TRUE 1
+ #define FALSE 0
+#endif
+
+#define MAX_COMMENT 2000
+
+#ifdef _WIN32
+ #define PATH_MAX _MAX_PATH
+#endif
+
+//--------------------------------------------------------------------------
+// This structure is used to store jpeg file sections in memory.
+typedef struct {
+ uchar * Data;
+ int Type;
+ unsigned Size;
+}Section_t;
+
+extern int ExifSectionIndex;
+
+extern int DumpExifMap;
+
+#define MAX_DATE_COPIES 10
+
+//--------------------------------------------------------------------------
+// This structure stores Exif header image elements in a simple manner
+// Used to store camera data as extracted from the various ways that it can be
+// stored in an exif header
+typedef struct {
+ char FileName [PATH_MAX+1];
+ time_t FileDateTime;
+ unsigned FileSize;
+ char CameraMake [32];
+ char CameraModel [40];
+ char DateTime [20];
+ int Height, Width;
+ int Orientation;
+ int IsColor;
+ int Process;
+ int FlashUsed;
+ float FocalLength;
+ float ExposureTime;
+ float ApertureFNumber;
+ float Distance;
+ float CCDWidth;
+ float ExposureBias;
+ float DigitalZoomRatio;
+ int FocalLength35mmEquiv; // Exif 2.2 tag - usually not present.
+ int Whitebalance;
+ int MeteringMode;
+ int ExposureProgram;
+ int ExposureMode;
+ int ISOequivalent;
+ int LightSource;
+ char Comments[MAX_COMMENT];
+
+ unsigned ThumbnailOffset; // Exif offset to thumbnail
+ unsigned ThumbnailSize; // Size of thumbnail.
+ unsigned LargestExifOffset; // Last exif data referenced (to check if thumbnail is at end)
+
+ char ThumbnailAtEnd; // Exif header ends with the thumbnail
+ // (we can only modify the thumbnail if its at the end)
+ int ThumbnailSizeOffset;
+
+ int DateTimeOffsets[MAX_DATE_COPIES];
+ int numDateTimeTags;
+
+ int GpsInfoPresent;
+ char GpsLat[31];
+ char GpsLatRaw[31];
+ char GpsLatRef[2];
+ char GpsLong[31];
+ char GpsLongRaw[31];
+ char GpsLongRef[2];
+ char GpsAlt[20];
+}ImageInfo_t;
+
+
+
+#define EXIT_FAILURE 1
+#define EXIT_SUCCESS 0
+
+// jpgfile.c functions
+typedef enum {
+ READ_METADATA = 1,
+ READ_IMAGE = 2,
+ READ_ALL = 3
+}ReadMode_t;
+
+
+typedef struct {
+ unsigned short Tag; // tag value, i.e. TAG_MODEL
+ int Format; // format of data
+ char* Value; // value of data in string format
+ int DataLength; // length of string when format says Value is a string
+ int GpsTag; // bool - the tag is related to GPS info
+} ExifElement_t;
+
+
+typedef struct {
+ unsigned short Tag;
+ char * Desc;
+ int Format;
+ int DataLength; // Number of elements in Format. -1 means any length.
+} TagTable_t;
+
+
+// prototypes for jhead.c functions
+void ErrFatal(char * msg);
+void ErrNonfatal(char * msg, int a1, int a2);
+void FileTimeAsString(char * TimeStr);
+
+// Prototypes for exif.c functions.
+int Exif2tm(struct tm * timeptr, char * ExifTime);
+void process_EXIF (unsigned char * CharBuf, unsigned int length);
+int RemoveThumbnail(unsigned char * ExifSection);
+void ShowImageInfo(int ShowFileInfo);
+void ShowConciseImageInfo(void);
+const char * ClearOrientation(void);
+void PrintFormatNumber(void * ValuePtr, int Format, int ByteCount);
+double ConvertAnyFormat(void * ValuePtr, int Format);
+int Get16u(void * Short);
+unsigned Get32u(void * Long);
+int Get32s(void * Long);
+void Put32u(void * Value, unsigned PutValue);
+void create_EXIF(ExifElement_t* elements, int exifTagCount, int gpsTagCount);
+int TagNameToValue(const char* tagName);
+
+//--------------------------------------------------------------------------
+// Exif format descriptor stuff
+extern const int BytesPerFormat[];
+#define NUM_FORMATS 12
+
+#define FMT_BYTE 1
+#define FMT_STRING 2
+#define FMT_USHORT 3
+#define FMT_ULONG 4
+#define FMT_URATIONAL 5
+#define FMT_SBYTE 6
+#define FMT_UNDEFINED 7
+#define FMT_SSHORT 8
+#define FMT_SLONG 9
+#define FMT_SRATIONAL 10
+#define FMT_SINGLE 11
+#define FMT_DOUBLE 12
+
+
+// makernote.c prototypes
+extern void ProcessMakerNote(unsigned char * DirStart, int ByteCount,
+ unsigned char * OffsetBase, unsigned ExifLength);
+
+// gpsinfo.c prototypes
+void ProcessGpsInfo(unsigned char * ValuePtr, int ByteCount,
+ unsigned char * OffsetBase, unsigned ExifLength);
+int IsGpsTag(const char* tag);
+int GpsTagToFormatType(unsigned short tag);
+int GpsTagNameToValue(const char* tagName);
+TagTable_t* GpsTagToTagTableEntry(unsigned short tag);
+
+// iptc.c prototpyes
+void show_IPTC (unsigned char * CharBuf, unsigned int length);
+
+// Prototypes for myglob.c module
+extern void MyGlob(const char * Pattern , void (*FileFuncParm)(const char * FileName));
+
+// Prototypes from jpgfile.c
+int ReadJpegSections (FILE * infile, ReadMode_t ReadMode);
+void DiscardData(void);
+void DiscardAllButExif(void);
+int ReadJpegFile(const char * FileName, ReadMode_t ReadMode);
+int ReplaceThumbnail(const char * ThumbFileName);
+int SaveThumbnail(char * ThumbFileName);
+int RemoveSectionType(int SectionType);
+int RemoveUnknownSections(void);
+int WriteJpegFile(const char * FileName);
+Section_t * FindSection(int SectionType);
+Section_t * CreateSection(int SectionType, unsigned char * Data, int size);
+void ResetJpgfile(void);
+
+// Variables from jhead.c used by exif.c
+extern ImageInfo_t ImageInfo;
+extern int ShowTags;
+extern char* formatStr(int format);
+
+//--------------------------------------------------------------------------
+// JPEG markers consist of one or more 0xFF bytes, followed by a marker
+// code byte (which is not an FF). Here are the marker codes of interest
+// in this program. (See jdmarker.c for a more complete list.)
+//--------------------------------------------------------------------------
+
+#define M_SOF0 0xC0 // Start Of Frame N
+#define M_SOF1 0xC1 // N indicates which compression process
+#define M_SOF2 0xC2 // Only SOF0-SOF2 are now in common use
+#define M_SOF3 0xC3
+#define M_SOF5 0xC5 // NB: codes C4 and CC are NOT SOF markers
+#define M_SOF6 0xC6
+#define M_SOF7 0xC7
+#define M_SOF9 0xC9
+#define M_SOF10 0xCA
+#define M_SOF11 0xCB
+#define M_SOF13 0xCD
+#define M_SOF14 0xCE
+#define M_SOF15 0xCF
+#define M_SOI 0xD8 // Start Of Image (beginning of datastream)
+#define M_EOI 0xD9 // End Of Image (end of datastream)
+#define M_SOS 0xDA // Start Of Scan (begins compressed data)
+#define M_JFIF 0xE0 // Jfif marker
+#define M_EXIF 0xE1 // Exif marker
+#define M_COM 0xFE // COMment
+#define M_DQT 0xDB
+#define M_DHT 0xC4
+#define M_DRI 0xDD
+#define M_IPTC 0xED // IPTC marker
+
+
diff --git a/jpgfile.c b/jpgfile.c
new file mode 100644
index 0000000..6f5cf72
--- /dev/null
+++ b/jpgfile.c
@@ -0,0 +1,684 @@
+//--------------------------------------------------------------------------
+// Program to pull the information out of various types of EXIF digital
+// camera files and show it in a reasonably consistent way
+//
+// This module handles basic Jpeg file handling
+//
+// Matthias Wandel
+//--------------------------------------------------------------------------
+#include <utils/Log.h>
+#include "jhead.h"
+
+// Storage for simplified info extracted from file.
+ImageInfo_t ImageInfo;
+
+
+static Section_t * Sections = NULL;
+static int SectionsAllocated;
+static int SectionsRead;
+static int HaveAll;
+
+// Define the line below to turn on poor man's debugging output
+#undef SUPERDEBUG
+
+#ifdef SUPERDEBUG
+#define printf LOGE
+#endif
+
+
+
+#define PSEUDO_IMAGE_MARKER 0x123; // Extra value.
+//--------------------------------------------------------------------------
+// Get 16 bits motorola order (always) for jpeg header stuff.
+//--------------------------------------------------------------------------
+static int Get16m(const void * Short)
+{
+ return (((uchar *)Short)[0] << 8) | ((uchar *)Short)[1];
+}
+
+
+//--------------------------------------------------------------------------
+// Process a COM marker.
+// We want to print out the marker contents as legible text;
+// we must guard against random junk and varying newline representations.
+//--------------------------------------------------------------------------
+static void process_COM (const uchar * Data, int length)
+{
+ int ch;
+ char Comment[MAX_COMMENT+1];
+ int nch;
+ int a;
+
+ nch = 0;
+
+ if (length > MAX_COMMENT) length = MAX_COMMENT; // Truncate if it won't fit in our structure.
+
+ for (a=2;a<length;a++){
+ ch = Data[a];
+
+ if (ch == '\r' && Data[a+1] == '\n') continue; // Remove cr followed by lf.
+
+ if (ch >= 32 || ch == '\n' || ch == '\t'){
+ Comment[nch++] = (char)ch;
+ }else{
+ Comment[nch++] = '?';
+ }
+ }
+
+ Comment[nch] = '\0'; // Null terminate
+
+ if (ShowTags){
+ printf("COM marker comment: %s\n",Comment);
+ }
+
+ strcpy(ImageInfo.Comments,Comment);
+}
+
+
+//--------------------------------------------------------------------------
+// Process a SOFn marker. This is useful for the image dimensions
+//--------------------------------------------------------------------------
+static void process_SOFn (const uchar * Data, int marker)
+{
+ int data_precision, num_components;
+
+ data_precision = Data[2];
+ ImageInfo.Height = Get16m(Data+3);
+ ImageInfo.Width = Get16m(Data+5);
+ num_components = Data[7];
+
+ if (num_components == 3){
+ ImageInfo.IsColor = 1;
+ }else{
+ ImageInfo.IsColor = 0;
+ }
+
+ ImageInfo.Process = marker;
+
+ if (ShowTags){
+ printf("JPEG image is %uw * %uh, %d color components, %d bits per sample\n",
+ ImageInfo.Width, ImageInfo.Height, num_components, data_precision);
+ }
+}
+
+
+//--------------------------------------------------------------------------
+// Check sections array to see if it needs to be increased in size.
+//--------------------------------------------------------------------------
+void CheckSectionsAllocated(void)
+{
+ if (SectionsRead > SectionsAllocated){
+ ErrFatal("allocation screwup");
+ }
+ if (SectionsRead >= SectionsAllocated){
+ SectionsAllocated += SectionsAllocated/2;
+ Sections = (Section_t *)realloc(Sections, sizeof(Section_t)*SectionsAllocated);
+ if (Sections == NULL){
+ ErrFatal("could not allocate data for entire image");
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------
+// Parse the marker stream until SOS or EOI is seen;
+//--------------------------------------------------------------------------
+int ReadJpegSections (FILE * infile, ReadMode_t ReadMode)
+{
+ int a;
+ int HaveCom = FALSE;
+
+ a = fgetc(infile);
+
+ if (a != 0xff || fgetc(infile) != M_SOI){
+ return FALSE;
+ }
+ for(;;){
+ int itemlen;
+ int marker = 0;
+ int ll,lh, got;
+ uchar * Data;
+
+ CheckSectionsAllocated();
+
+ for (a=0;a<7;a++){
+ marker = fgetc(infile);
+ if (marker != 0xff) break;
+
+ if (a >= 6){
+ fprintf(stderr,"too many padding bytes\n");
+ return FALSE;
+ }
+ }
+
+ if (marker == 0xff){
+ // 0xff is legal padding, but if we get that many, something's wrong.
+ //ErrFatal("too many padding bytes!");
+ LOGE("too many padding bytes!");
+ return FALSE;
+ }
+
+ Sections[SectionsRead].Type = marker;
+
+ // Read the length of the section.
+ lh = fgetc(infile);
+ ll = fgetc(infile);
+
+ itemlen = (lh << 8) | ll;
+
+ if (itemlen < 2){
+// ErrFatal("invalid marker");
+ LOGE("invalid marker");
+ return FALSE;
+ }
+
+ Sections[SectionsRead].Size = itemlen;
+
+ Data = (uchar *)malloc(itemlen);
+ if (Data == NULL){
+ // ErrFatal("Could not allocate memory");
+ LOGE("Could not allocate memory");
+ return 0;
+ }
+ Sections[SectionsRead].Data = Data;
+
+ // Store first two pre-read bytes.
+ Data[0] = (uchar)lh;
+ Data[1] = (uchar)ll;
+
+ got = fread(Data+2, 1, itemlen-2, infile); // Read the whole section.
+ if (got != itemlen-2){
+// ErrFatal("Premature end of file?");
+ LOGE("Premature end of file?");
+ return FALSE;
+ }
+ SectionsRead += 1;
+
+ printf("reading marker %d", marker);
+ switch(marker){
+
+ case M_SOS: // stop before hitting compressed data
+ // If reading entire image is requested, read the rest of the data.
+ if (ReadMode & READ_IMAGE){
+ int cp, ep, size;
+ // Determine how much file is left.
+ cp = ftell(infile);
+ fseek(infile, 0, SEEK_END);
+ ep = ftell(infile);
+ fseek(infile, cp, SEEK_SET);
+
+ size = ep-cp;
+ Data = (uchar *)malloc(size);
+ if (Data == NULL){
+ // ErrFatal("could not allocate data for entire image");
+ LOGE("could not allocate data for entire image");
+ return FALSE;
+ }
+
+ got = fread(Data, 1, size, infile);
+ if (got != size){
+ // ErrFatal("could not read the rest of the image");
+ LOGE("could not read the rest of the image");
+ return FALSE;
+ }
+
+ CheckSectionsAllocated();
+ Sections[SectionsRead].Data = Data;
+ Sections[SectionsRead].Size = size;
+ Sections[SectionsRead].Type = PSEUDO_IMAGE_MARKER;
+ SectionsRead ++;
+ HaveAll = 1;
+ }
+ return TRUE;
+
+ case M_EOI: // in case it's a tables-only JPEG stream
+ fprintf(stderr,"No image in jpeg!\n");
+ return FALSE;
+
+ case M_COM: // Comment section
+ if (HaveCom || ((ReadMode & READ_METADATA) == 0)){
+ // Discard this section.
+ free(Sections[--SectionsRead].Data);
+ }else{
+ process_COM(Data, itemlen);
+ HaveCom = TRUE;
+ }
+ break;
+
+ case M_JFIF:
+ // Regular jpegs always have this tag, exif images have the exif
+ // marker instead, althogh ACDsee will write images with both markers.
+ // this program will re-create this marker on absence of exif marker.
+ // hence no need to keep the copy from the file.
+ free(Sections[--SectionsRead].Data);
+ break;
+
+ case M_EXIF:
+ // Seen files from some 'U-lead' software with Vivitar scanner
+ // that uses marker 31 for non exif stuff. Thus make sure
+ // it says 'Exif' in the section before treating it as exif.
+ if ((ReadMode & READ_METADATA) && memcmp(Data+2, "Exif", 4) == 0){
+ process_EXIF(Data, itemlen);
+ }else{
+ // Discard this section.
+ free(Sections[--SectionsRead].Data);
+ }
+ break;
+
+ case M_IPTC:
+ if (ReadMode & READ_METADATA){
+ if (ShowTags){
+ printf("Image cotains IPTC section, %d bytes long\n", itemlen);
+ }
+ // Note: We just store the IPTC section. Its relatively straightforward
+ // and we don't act on any part of it, so just display it at parse time.
+ }else{
+ free(Sections[--SectionsRead].Data);
+ }
+ break;
+
+ case M_SOF0:
+ case M_SOF1:
+ case M_SOF2:
+ case M_SOF3:
+ case M_SOF5:
+ case M_SOF6:
+ case M_SOF7:
+ case M_SOF9:
+ case M_SOF10:
+ case M_SOF11:
+ case M_SOF13:
+ case M_SOF14:
+ case M_SOF15:
+ process_SOFn(Data, marker);
+ break;
+ default:
+ // Skip any other sections.
+ if (ShowTags){
+ printf("Jpeg section marker 0x%02x size %d\n",marker, itemlen);
+ }
+ break;
+ }
+ }
+ return TRUE;
+}
+
+//--------------------------------------------------------------------------
+// Discard read data.
+//--------------------------------------------------------------------------
+void DiscardData(void)
+{
+ int a;
+
+ for (a=0;a<SectionsRead;a++){
+ free(Sections[a].Data);
+ }
+
+ memset(&ImageInfo, 0, sizeof(ImageInfo));
+ SectionsRead = 0;
+ HaveAll = 0;
+}
+
+//--------------------------------------------------------------------------
+// Read image data.
+//--------------------------------------------------------------------------
+int ReadJpegFile(const char * FileName, ReadMode_t ReadMode)
+{
+ FILE * infile;
+ int ret;
+
+ infile = fopen(FileName, "rb"); // Unix ignores 'b', windows needs it.
+
+ if (infile == NULL) {
+ LOGE("can't open '%s'", FileName);
+ fprintf(stderr, "can't open '%s'\n", FileName);
+ return FALSE;
+ }
+
+ // Scan the JPEG headers.
+ printf("ReadJpegSections");
+ ret = ReadJpegSections(infile, ReadMode);
+ if (!ret){
+ LOGE("Not JPEG: %s", FileName);
+ fprintf(stderr,"Not JPEG: %s\n",FileName);
+ }
+
+ fclose(infile);
+
+ if (ret == FALSE){
+ DiscardData();
+ }
+ return ret;
+}
+
+
+//--------------------------------------------------------------------------
+// Replace or remove exif thumbnail
+//--------------------------------------------------------------------------
+int SaveThumbnail(char * ThumbFileName)
+{
+ FILE * ThumbnailFile;
+
+ if (ImageInfo.ThumbnailOffset == 0 || ImageInfo.ThumbnailSize == 0){
+ fprintf(stderr,"Image contains no thumbnail\n");
+ return FALSE;
+ }
+
+ if (strcmp(ThumbFileName, "-") == 0){
+ // A filename of '-' indicates thumbnail goes to stdout.
+ // This doesn't make much sense under Windows, so this feature is unix only.
+ ThumbnailFile = stdout;
+ }else{
+ ThumbnailFile = fopen(ThumbFileName,"wb");
+ }
+
+ if (ThumbnailFile){
+ uchar * ThumbnailPointer;
+ Section_t * ExifSection;
+ ExifSection = FindSection(M_EXIF);
+ ThumbnailPointer = ExifSection->Data+ImageInfo.ThumbnailOffset+8;
+
+ fwrite(ThumbnailPointer, ImageInfo.ThumbnailSize ,1, ThumbnailFile);
+ fclose(ThumbnailFile);
+ return TRUE;
+ }else{
+ // ErrFatal("Could not write thumbnail file");
+ LOGE("Could not write thumbnail file");
+ return FALSE;
+ }
+}
+
+//--------------------------------------------------------------------------
+// Replace or remove exif thumbnail
+//--------------------------------------------------------------------------
+int ReplaceThumbnail(const char * ThumbFileName)
+{
+ FILE * ThumbnailFile;
+ int ThumbLen, NewExifSize;
+ Section_t * ExifSection;
+ uchar * ThumbnailPointer;
+
+ if (ImageInfo.ThumbnailOffset == 0 || ImageInfo.ThumbnailAtEnd == FALSE){
+ // Adding or removing of thumbnail is not possible - that would require rearranging
+ // of the exif header, which is risky, and jhad doesn't know how to do.
+
+ fprintf(stderr,"Image contains no thumbnail to replace - add is not possible\n");
+#ifdef SUPERDEBUG
+ LOGE("Image contains no thumbnail to replace - add is not possible\n");
+#endif
+ return FALSE;
+ }
+
+ if (ThumbFileName){
+ ThumbnailFile = fopen(ThumbFileName,"rb");
+
+ if (ThumbnailFile == NULL){
+ //ErrFatal("Could not read thumbnail file");
+ LOGE("Could not read thumbnail file");
+ return FALSE;
+ }
+
+ // get length
+ fseek(ThumbnailFile, 0, SEEK_END);
+
+ ThumbLen = ftell(ThumbnailFile);
+ fseek(ThumbnailFile, 0, SEEK_SET);
+
+ if (ThumbLen + ImageInfo.ThumbnailOffset > 0x10000-20){
+ //ErrFatal("Thumbnail is too large to insert into exif header");
+ LOGE("Thumbnail is too large to insert into exif header");
+ return FALSE;
+ }
+ }else{
+ ThumbLen = 0;
+ ThumbnailFile = NULL;
+ }
+
+ ExifSection = FindSection(M_EXIF);
+
+ NewExifSize = ImageInfo.ThumbnailOffset+8+ThumbLen;
+ ExifSection->Data = (uchar *)realloc(ExifSection->Data, NewExifSize);
+
+ ThumbnailPointer = ExifSection->Data+ImageInfo.ThumbnailOffset+8;
+
+ if (ThumbnailFile){
+ fread(ThumbnailPointer, ThumbLen, 1, ThumbnailFile);
+ fclose(ThumbnailFile);
+ }
+
+ ImageInfo.ThumbnailSize = ThumbLen;
+
+ Put32u(ExifSection->Data+ImageInfo.ThumbnailSizeOffset+8, ThumbLen);
+
+ ExifSection->Data[0] = (uchar)(NewExifSize >> 8);
+ ExifSection->Data[1] = (uchar)NewExifSize;
+ ExifSection->Size = NewExifSize;
+
+#ifdef SUPERDEBUG
+ LOGE("ReplaceThumbnail successful thumblen %d", ThumbLen);
+#endif
+ return TRUE;
+}
+
+
+//--------------------------------------------------------------------------
+// Discard everything but the exif and comment sections.
+//--------------------------------------------------------------------------
+void DiscardAllButExif(void)
+{
+ Section_t ExifKeeper;
+ Section_t CommentKeeper;
+ Section_t IptcKeeper;
+ int a;
+
+ memset(&ExifKeeper, 0, sizeof(ExifKeeper));
+ memset(&CommentKeeper, 0, sizeof(CommentKeeper));
+ memset(&IptcKeeper, 0, sizeof(IptcKeeper));
+
+ for (a=0;a<SectionsRead;a++){
+ if (Sections[a].Type == M_EXIF && ExifKeeper.Type == 0){
+ ExifKeeper = Sections[a];
+ }else if (Sections[a].Type == M_COM && CommentKeeper.Type == 0){
+ CommentKeeper = Sections[a];
+ }else if (Sections[a].Type == M_IPTC && IptcKeeper.Type == 0){
+ IptcKeeper = Sections[a];
+ }else{
+ free(Sections[a].Data);
+ }
+ }
+ SectionsRead = 0;
+ if (ExifKeeper.Type){
+ CheckSectionsAllocated();
+ Sections[SectionsRead++] = ExifKeeper;
+ }
+ if (CommentKeeper.Type){
+ CheckSectionsAllocated();
+ Sections[SectionsRead++] = CommentKeeper;
+ }
+ if (IptcKeeper.Type){
+ CheckSectionsAllocated();
+ Sections[SectionsRead++] = IptcKeeper;
+ }
+}
+
+//--------------------------------------------------------------------------
+// Write image data back to disk.
+//--------------------------------------------------------------------------
+int WriteJpegFile(const char * FileName)
+{
+ FILE * outfile;
+ int a;
+
+ if (!HaveAll){
+ LOGE("Can't write back - didn't read all");
+ return FALSE;
+ }
+
+ outfile = fopen(FileName,"wb");
+ if (outfile == NULL){
+ LOGE("Could not open file for write");
+ return FALSE;
+ }
+
+ // Initial static jpeg marker.
+ fputc(0xff,outfile);
+ fputc(0xd8,outfile);
+
+ if (Sections[0].Type != M_EXIF && Sections[0].Type != M_JFIF){
+ // The image must start with an exif or jfif marker. If we threw those away, create one.
+ static uchar JfifHead[18] = {
+ 0xff, M_JFIF,
+ 0x00, 0x10, 'J' , 'F' , 'I' , 'F' , 0x00, 0x01,
+ 0x01, 0x01, 0x01, 0x2C, 0x01, 0x2C, 0x00, 0x00
+ };
+ fwrite(JfifHead, 18, 1, outfile);
+ }
+
+ // Write all the misc sections
+ for (a=0;a<SectionsRead-1;a++){
+ fputc(0xff,outfile);
+ fputc(Sections[a].Type, outfile);
+ fwrite(Sections[a].Data, Sections[a].Size, 1, outfile);
+ }
+
+ // Write the remaining image data.
+ fwrite(Sections[a].Data, Sections[a].Size, 1, outfile);
+
+ fclose(outfile);
+ return TRUE;
+}
+
+
+//--------------------------------------------------------------------------
+// Check if image has exif header.
+//--------------------------------------------------------------------------
+Section_t * FindSection(int SectionType)
+{
+ int a;
+
+ for (a=0;a<SectionsRead;a++){
+ if (Sections[a].Type == SectionType){
+ return &Sections[a];
+ }
+ }
+ // Could not be found.
+ return NULL;
+}
+
+//--------------------------------------------------------------------------
+// Remove a certain type of section.
+//--------------------------------------------------------------------------
+int RemoveSectionType(int SectionType)
+{
+ int a;
+ for (a=0;a<SectionsRead-1;a++){
+ if (Sections[a].Type == SectionType){
+ // Free up this section
+ free (Sections[a].Data);
+ // Move succeding sections back by one to close space in array.
+ memmove(Sections+a, Sections+a+1, sizeof(Section_t) * (SectionsRead-a));
+ SectionsRead -= 1;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+//--------------------------------------------------------------------------
+// Remove sectons not part of image and not exif or comment sections.
+//--------------------------------------------------------------------------
+int RemoveUnknownSections(void)
+{
+ int a;
+ int Modified = FALSE;
+ for (a=0;a<SectionsRead-1;){
+ switch(Sections[a].Type){
+ case M_SOF0:
+ case M_SOF1:
+ case M_SOF2:
+ case M_SOF3:
+ case M_SOF5:
+ case M_SOF6:
+ case M_SOF7:
+ case M_SOF9:
+ case M_SOF10:
+ case M_SOF11:
+ case M_SOF13:
+ case M_SOF14:
+ case M_SOF15:
+ case M_SOI:
+ case M_EOI:
+ case M_SOS:
+ case M_JFIF:
+ case M_EXIF:
+ case M_COM:
+ case M_DQT:
+ case M_DHT:
+ case M_DRI:
+ case M_IPTC:
+ // keep.
+ a++;
+ break;
+ default:
+ // Unknown. Delete.
+ free (Sections[a].Data);
+ // Move succeding sections back by one to close space in array.
+ memmove(Sections+a, Sections+a+1, sizeof(Section_t) * (SectionsRead-a));
+ SectionsRead -= 1;
+ Modified = TRUE;
+ }
+ }
+ return Modified;
+}
+
+//--------------------------------------------------------------------------
+// Add a section (assume it doesn't already exist) - used for
+// adding comment sections and exif sections
+//--------------------------------------------------------------------------
+Section_t * CreateSection(int SectionType, unsigned char * Data, int Size)
+{
+ Section_t * NewSection;
+ int a;
+ int NewIndex;
+ NewIndex = 2;
+
+ if (SectionType == M_EXIF) NewIndex = 0; // Exif alwas goes first!
+
+ // Insert it in third position - seems like a safe place to put
+ // things like comments.
+
+ if (SectionsRead < NewIndex){
+ // ErrFatal("Too few sections!");
+ LOGE("Too few sections!");
+ return FALSE;
+ }
+
+ CheckSectionsAllocated();
+ for (a=SectionsRead;a>NewIndex;a--){
+ Sections[a] = Sections[a-1];
+ }
+ SectionsRead += 1;
+
+ NewSection = Sections+NewIndex;
+
+ NewSection->Type = SectionType;
+ NewSection->Size = Size;
+ NewSection->Data = Data;
+
+ return NewSection;
+}
+
+
+//--------------------------------------------------------------------------
+// Initialisation.
+//--------------------------------------------------------------------------
+void ResetJpgfile(void)
+{
+ if (Sections == NULL){
+ Sections = (Section_t *)malloc(sizeof(Section_t)*5);
+ SectionsAllocated = 5;
+ }
+
+ SectionsRead = 0;
+ HaveAll = 0;
+}
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..12ee58e
--- /dev/null
+++ b/main.c
@@ -0,0 +1,759 @@
+/*
+
+Copyright (c) 2008, The Android Open Source Project
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of Google, Inc. nor the names of its contributors
+ may be used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+*/
+
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/jni.h>
+
+#include <assert.h>
+#include <dlfcn.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <utils/Log.h>
+
+#include "jhead.h"
+
+#ifndef NELEM
+#define NELEM(x) ((int)(sizeof(x) / sizeof((x)[0])))
+#endif
+
+// Define the line below to turn on poor man's debugging output
+#undef SUPERDEBUG
+
+// Various tests
+#undef REALLOCTEST
+#undef OUTOFMEMORYTEST1
+
+static void addExifAttibute(JNIEnv *env, jmethodID putMethod, jobject hashMap, char* key, char* value) {
+ jstring jkey = (*env)->NewStringUTF(env, key);
+ jstring jvalue = (*env)->NewStringUTF(env, value);
+
+ jobject jobject_of_entryset = (*env)->CallObjectMethod(env, hashMap, putMethod, jkey, jvalue);
+
+ (*env)->ReleaseStringUTFChars(env, jkey, key);
+ (*env)->ReleaseStringUTFChars(env, jvalue, value);
+}
+
+extern void ResetJpgfile();
+
+static int loadExifInfo(const char* FileName, int readJPG) {
+#ifdef SUPERDEBUG
+ LOGE("loadExifInfo");
+#endif
+ int Modified = FALSE;
+ ReadMode_t ReadMode = READ_METADATA;
+ if (readJPG) {
+ // Must add READ_IMAGE else we can't write the JPG back out.
+ ReadMode |= READ_IMAGE;
+ }
+
+#ifdef SUPERDEBUG
+ LOGE("ResetJpgfile");
+#endif
+ ResetJpgfile();
+
+ // Start with an empty image information structure.
+ memset(&ImageInfo, 0, sizeof(ImageInfo));
+ ImageInfo.FlashUsed = -1;
+ ImageInfo.MeteringMode = -1;
+ ImageInfo.Whitebalance = -1;
+
+ // Store file date/time.
+ {
+ struct stat st;
+ if (stat(FileName, &st) >= 0) {
+ ImageInfo.FileDateTime = st.st_mtime;
+ ImageInfo.FileSize = st.st_size;
+ }
+ }
+
+ strncpy(ImageInfo.FileName, FileName, PATH_MAX);
+#ifdef SUPERDEBUG
+ LOGE("ReadJpegFile");
+#endif
+ return ReadJpegFile(FileName, ReadMode);
+}
+
+static void saveJPGFile(const char* filename) {
+ char backupName[400];
+ struct stat buf;
+
+#ifdef SUPERDEBUG
+ LOGE("Modified: %s\n", filename);
+#endif
+
+ strncpy(backupName, filename, 395);
+ strcat(backupName, ".t");
+
+ // Remove any .old file name that may pre-exist
+#ifdef SUPERDEBUG
+ LOGE("removing backup %s", backupName);
+#endif
+ unlink(backupName);
+
+ // Rename the old file.
+#ifdef SUPERDEBUG
+ LOGE("rename %s to %s", filename, backupName);
+#endif
+ rename(filename, backupName);
+
+ // Write the new file.
+#ifdef SUPERDEBUG
+ LOGE("WriteJpegFile %s", filename);
+#endif
+ if (WriteJpegFile(filename)) {
+
+ // Copy the access rights from original file
+#ifdef SUPERDEBUG
+ LOGE("stating old file %s", backupName);
+#endif
+ if (stat(backupName, &buf) == 0){
+ // set Unix access rights and time to new file
+ struct utimbuf mtime;
+ chmod(filename, buf.st_mode);
+
+ mtime.actime = buf.st_mtime;
+ mtime.modtime = buf.st_mtime;
+
+ utime(filename, &mtime);
+ }
+
+ // Now that we are done, remove original file.
+#ifdef SUPERDEBUG
+ LOGE("unlinking old file %s", backupName);
+#endif
+ unlink(backupName);
+#ifdef SUPERDEBUG
+ LOGE("returning from saveJPGFile");
+#endif
+ } else {
+#ifdef SUPERDEBUG
+ LOGE("WriteJpegFile failed, restoring from backup file");
+#endif
+ // move back the backup file
+ rename(backupName, filename);
+ }
+}
+
+void copyThumbnailData(uchar* thumbnailData, int thumbnailLen) {
+#ifdef SUPERDEBUG
+ LOGE("******************************** copyThumbnailData\n");
+#endif
+ Section_t* ExifSection = FindSection(M_EXIF);
+ if (ExifSection == NULL) {
+ return;
+ }
+ int NewExifSize = ImageInfo.ThumbnailOffset+8+thumbnailLen;
+ ExifSection->Data = (uchar *)realloc(ExifSection->Data, NewExifSize);
+ if (ExifSection->Data == NULL) {
+ return;
+ }
+ uchar* ThumbnailPointer = ExifSection->Data+ImageInfo.ThumbnailOffset+8;
+
+ memcpy(ThumbnailPointer, thumbnailData, thumbnailLen);
+
+ ImageInfo.ThumbnailSize = thumbnailLen;
+
+ Put32u(ExifSection->Data+ImageInfo.ThumbnailSizeOffset+8, thumbnailLen);
+
+ ExifSection->Data[0] = (uchar)(NewExifSize >> 8);
+ ExifSection->Data[1] = (uchar)NewExifSize;
+ ExifSection->Size = NewExifSize;
+}
+
+static void saveAttributes(JNIEnv *env, jobject jobj, jstring jfilename, jstring jattributes)
+{
+#ifdef SUPERDEBUG
+ LOGE("******************************** saveAttributes\n");
+#endif
+ // format of attributes string passed from java:
+ // "attrCnt attr1=valueLen value1attr2=value2Len value2..."
+ // example input: "4 ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
+ ExifElement_t* exifElementTable = NULL;
+ const char* filename = NULL;
+ uchar* thumbnailData = NULL;
+ int attrCnt = 0;
+ const char* attributes = (*env)->GetStringUTFChars(env, jattributes, NULL);
+ if (attributes == NULL) {
+ goto exit;
+ }
+#ifdef SUPERDEBUG
+ LOGE("attributes %s\n", attributes);
+#endif
+
+ // Get the number of attributes - it's the first number in the string.
+ attrCnt = atoi(attributes);
+ char* attrPtr = strchr(attributes, ' ') + 1;
+#ifdef SUPERDEBUG
+ LOGE("attribute count %d attrPtr %s\n", attrCnt, attrPtr);
+#endif
+
+ // Load all the hash exif elements into a more c-like structure
+ exifElementTable = malloc(sizeof(ExifElement_t) * attrCnt);
+ if (exifElementTable == NULL) {
+ goto exit;
+ }
+#ifdef OUTOFMEMORYTEST1
+ goto exit;
+#endif
+
+ int i;
+ char tag[100];
+ int gpsTagCount = 0;
+ int exifTagCount = 0;
+
+ for (i = 0; i < attrCnt; i++) {
+ // get an element from the attribute string and add it to the c structure
+ // first, extract the attribute name
+ char* tagEnd = strchr(attrPtr, '=');
+ if (tagEnd == 0) {
+#ifdef SUPERDEBUG
+ LOGE("saveAttributes: couldn't find end of tag");
+#endif
+ goto exit;
+ }
+ if (tagEnd - attrPtr > 99) {
+#ifdef SUPERDEBUG
+ LOGE("saveAttributes: attribute tag way too long");
+#endif
+ goto exit;
+ }
+ memcpy(tag, attrPtr, tagEnd - attrPtr);
+ tag[tagEnd - attrPtr] = 0;
+
+ if (IsGpsTag(tag)) {
+ exifElementTable[i].GpsTag = TRUE;
+ exifElementTable[i].Tag = GpsTagNameToValue(tag);
+ ++gpsTagCount;
+ } else {
+ exifElementTable[i].GpsTag = FALSE;
+ exifElementTable[i].Tag = TagNameToValue(tag);
+ ++exifTagCount;
+ }
+ attrPtr = tagEnd + 1;
+
+ // next get the length of the attribute value
+ int valueLen = atoi(attrPtr);
+ attrPtr = strchr(attrPtr, ' ') + 1;
+ if (attrPtr == 0) {
+#ifdef SUPERDEBUG
+ LOGE("saveAttributes: couldn't find end of value len");
+#endif
+ goto exit;
+ }
+ exifElementTable[i].Value = malloc(valueLen + 1);
+ if (exifElementTable[i].Value == NULL) {
+ goto exit;
+ }
+ memcpy(exifElementTable[i].Value, attrPtr, valueLen);
+ exifElementTable[i].Value[valueLen] = 0;
+ exifElementTable[i].DataLength = valueLen;
+
+ attrPtr += valueLen;
+
+#ifdef SUPERDEBUG
+ LOGE("tag %s id %d value %s data length=%d isGps=%d", tag, exifElementTable[i].Tag,
+ exifElementTable[i].Value, exifElementTable[i].DataLength, exifElementTable[i].GpsTag);
+#endif
+ }
+
+ filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
+#ifdef SUPERDEBUG
+ LOGE("Call loadAttributes() with filename is %s. Loading exif info\n", filename);
+#endif
+ loadExifInfo(filename, TRUE);
+
+#ifdef SUPERDEBUG
+// DumpExifMap = TRUE;
+ ShowTags = TRUE;
+ ShowImageInfo(TRUE);
+ LOGE("create exif 2");
+#endif
+
+ // If the jpg file has a thumbnail, preserve it.
+ int thumbnailLength = ImageInfo.ThumbnailSize;
+ if (ImageInfo.ThumbnailOffset) {
+ Section_t* ExifSection = FindSection(M_EXIF);
+ if (ExifSection) {
+ uchar* thumbnailPointer = ExifSection->Data + ImageInfo.ThumbnailOffset + 8;
+ thumbnailData = (uchar*)malloc(ImageInfo.ThumbnailSize);
+ // if the malloc fails, we just won't copy the thumbnail
+ if (thumbnailData) {
+ memcpy(thumbnailData, thumbnailPointer, thumbnailLength);
+ }
+ }
+ }
+
+ create_EXIF(exifElementTable, exifTagCount, gpsTagCount);
+
+ if (thumbnailData) {
+ copyThumbnailData(thumbnailData, thumbnailLength);
+ }
+
+exit:
+#ifdef SUPERDEBUG
+ LOGE("cleaning up now in saveAttributes");
+#endif
+ // try to clean up resources
+ if (attributes) {
+ (*env)->ReleaseStringUTFChars(env, jattributes, attributes);
+ }
+ if (filename) {
+ (*env)->ReleaseStringUTFChars(env, jfilename, filename);
+ }
+ if (exifElementTable) {
+ // free the table
+ for (i = 0; i < attrCnt; i++) {
+ free(exifElementTable[i].Value);
+ }
+ free(exifElementTable);
+ }
+ if (thumbnailData) {
+ free(thumbnailData);
+ }
+#ifdef SUPERDEBUG
+ LOGE("returning from saveAttributes");
+#endif
+
+ DiscardData();
+
+// Temporarily saving these commented out lines because they represent a lot of figuring out
+// patterns for JNI.
+// // Get link to Method "entrySet"
+// jmethodID entrySetMethod = (*env)->GetMethodID(env, jclass_of_hashmap, "entrySet", "()Ljava/util/Set;");
+//
+// // Invoke the "entrySet" method on the HashMap object
+// jobject jobject_of_entryset = (*env)->CallObjectMethod(env, hashMap, entrySetMethod);
+//
+// // Get the Set Class
+// jclass jclass_of_set = (*env)->FindClass(env, "java/util/Set");
+//
+// if (jclass_of_set == 0) {
+// printf("java/util/Set lookup failed\n");
+// return;
+// }
+//
+// // Get link to Method "iterator"
+// jmethodID iteratorMethod = (*env)->GetMethodID(env, jclass_of_set, "iterator", "()Ljava/util/Iterator;");
+//
+// // Invoke the "iterator" method on the jobject_of_entryset variable of type Set
+// jobject jobject_of_iterator = (*env)->CallObjectMethod(env, jobject_of_entryset, iteratorMethod);
+//
+// // Get the "Iterator" class
+// jclass jclass_of_iterator = (*env)->FindClass(env, "java/util/Iterator");
+//
+// // Get link to Method "hasNext"
+// jmethodID hasNextMethod = (*env)->GetMethodID(env, jclass_of_iterator, "hasNext", "()Z");
+//
+// // Invoke - Get the value hasNextMethod
+// jboolean bHasNext = (*env)->CallBooleanMethod(env, jobject_of_iterator, hasNextMethod);
+
+// // Get link to Method "hasNext"
+// jmethodID nextMethod = (*env)->GetMethodID(env, jclass_of_iterator, "next", "()Ljava/util/Map/Entry;");
+//
+// jclass jclass_of_mapentry = (*env)->FindClass(env, "java/util/Map/Entry");
+//
+// jmethodID getKeyMethod = (*env)->GetMethodID(env, jclass_of_mapentry, "getKey", "()Ljava/lang/Object");
+//
+// jmethodID getValueMethod = (*env)->GetMethodID(env, jclass_of_mapentry, "getValue", "()Ljava/lang/Object");
+}
+
+static jboolean appendThumbnail(JNIEnv *env, jobject jobj, jstring jfilename, jstring jthumbnailfilename)
+{
+#ifdef SUPERDEBUG
+ LOGE("******************************** appendThumbnail\n");
+#endif
+
+ const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
+ if (filename == NULL) {
+ return JNI_FALSE;
+ }
+ const char* thumbnailfilename = (*env)->GetStringUTFChars(env, jthumbnailfilename, NULL);
+ if (thumbnailfilename == NULL) {
+ return JNI_FALSE;
+ }
+ #ifdef SUPERDEBUG
+ LOGE("*******before actual call to ReplaceThumbnail\n");
+ ShowImageInfo(TRUE);
+ #endif
+ ReplaceThumbnail(thumbnailfilename);
+ #ifdef SUPERDEBUG
+ ShowImageInfo(TRUE);
+ #endif
+ (*env)->ReleaseStringUTFChars(env, jfilename, filename);
+ (*env)->ReleaseStringUTFChars(env, jthumbnailfilename, thumbnailfilename);
+
+ DiscardData();
+ return JNI_TRUE;
+}
+
+static void commitChanges(JNIEnv *env, jobject jobj, jstring jfilename)
+{
+#ifdef SUPERDEBUG
+ LOGE("******************************** commitChanges\n");
+#endif
+ const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
+ if (filename) {
+ saveJPGFile(filename);
+ (*env)->ReleaseStringUTFChars(env, jfilename, filename);
+ }
+}
+
+static jbyteArray getThumbnail(JNIEnv *env, jobject jobj, jstring jfilename)
+{
+#ifdef SUPERDEBUG
+ LOGE("******************************** getThumbnail\n");
+#endif
+
+ const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
+ if (filename) {
+ loadExifInfo(filename, FALSE);
+ Section_t* ExifSection = FindSection(M_EXIF);
+ if (ExifSection == NULL || ImageInfo.ThumbnailSize == 0) {
+#ifdef SUPERDEBUG
+ LOGE("no exif section or size == 0, so no thumbnail\n");
+#endif
+ goto noThumbnail;
+ }
+ uchar* thumbnailPointer = ExifSection->Data + ImageInfo.ThumbnailOffset + 8;
+
+ jbyteArray byteArray = (*env)->NewByteArray(env, ImageInfo.ThumbnailSize);
+ if (byteArray == NULL) {
+#ifdef SUPERDEBUG
+ LOGE("couldn't allocate thumbnail memory, so no thumbnail\n");
+#endif
+ goto noThumbnail;
+ }
+ jboolean isCopy;
+ jbyte* thumbnailDataPtr = (*env)->GetByteArrayElements(env, byteArray, &isCopy);
+ memcpy(thumbnailDataPtr, thumbnailPointer, ImageInfo.ThumbnailSize);
+#ifdef SUPERDEBUG
+ LOGE("thumbnail size %d\n", ImageInfo.ThumbnailSize);
+#endif
+ if (isCopy == JNI_TRUE) {
+ (*env)->ReleaseByteArrayElements(env, byteArray, thumbnailDataPtr, 0);
+ }
+ (*env)->ReleaseStringUTFChars(env, jfilename, filename);
+ return byteArray;
+ }
+noThumbnail:
+ if (filename) {
+ (*env)->ReleaseStringUTFChars(env, jfilename, filename);
+ }
+ DiscardData();
+ return NULL;
+}
+
+static int attributeCount; // keep track of how many attributes we've added
+
+// returns new buffer length
+static int addKeyValueString(char** buf, int bufLen, const char* key, const char* value) {
+ // Appends to buf like this: "ImageLength=4 1024"
+
+ char valueLen[15];
+ snprintf(valueLen, 15, "=%d ", (int)strlen(value));
+
+ // check to see if buf has enough room to append
+ int len = strlen(key) + strlen(valueLen) + strlen(value);
+ int newLen = strlen(*buf) + len;
+ if (newLen >= bufLen) {
+#ifdef REALLOCTEST
+ bufLen = newLen + 5;
+ LOGE("reallocing to %d", bufLen);
+#else
+ bufLen = newLen + 500;
+#endif
+ *buf = realloc(*buf, bufLen);
+ if (*buf == NULL) {
+ return 0;
+ }
+ }
+ // append the new attribute and value
+ snprintf(*buf + strlen(*buf), bufLen, "%s%s%s", key, valueLen, value);
+#ifdef SUPERDEBUG
+ LOGE("buf %s", *buf);
+#endif
+ ++attributeCount;
+ return bufLen;
+}
+
+// returns new buffer length
+static int addKeyValueInt(char** buf, int bufLen, const char* key, int value) {
+ char valueStr[20];
+ snprintf(valueStr, 20, "%d", value);
+
+ return addKeyValueString(buf, bufLen, key, valueStr);
+}
+
+// returns new buffer length
+static int addKeyValueDouble(char** buf, int bufLen, const char* key, double value, const char* format) {
+ char valueStr[30];
+ snprintf(valueStr, 30, format, value);
+
+ return addKeyValueString(buf, bufLen, key, valueStr);
+}
+
+static jstring getAttributes(JNIEnv *env, jobject jobj, jstring jfilename)
+{
+#ifdef SUPERDEBUG
+ LOGE("******************************** getAttributes\n");
+#endif
+ const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
+ loadExifInfo(filename, FALSE);
+#ifdef SUPERDEBUG
+ ShowImageInfo(TRUE);
+#endif
+ (*env)->ReleaseStringUTFChars(env, jfilename, filename);
+
+ attributeCount = 0;
+#ifdef REALLOCTEST
+ int bufLen = 5;
+#else
+ int bufLen = 1000;
+#endif
+ char* buf = malloc(bufLen);
+ if (buf == NULL) {
+ return NULL;
+ }
+ *buf = 0; // start the string out at zero length
+
+ // save a fake "hasThumbnail" tag to pass to the java ExifInterface
+ bufLen = addKeyValueString(&buf, bufLen, "hasThumbnail",
+ ImageInfo.ThumbnailOffset == 0 || ImageInfo.ThumbnailAtEnd == FALSE || ImageInfo.ThumbnailSize == 0 ?
+ "false" : "true");
+ if (bufLen == 0) return NULL;
+
+ if (ImageInfo.CameraMake[0]) {
+ bufLen = addKeyValueString(&buf, bufLen, "Make", ImageInfo.CameraMake);
+ if (bufLen == 0) return NULL;
+ }
+ if (ImageInfo.CameraModel[0]) {
+ bufLen = addKeyValueString(&buf, bufLen, "Model", ImageInfo.CameraModel);
+ if (bufLen == 0) return NULL;
+ }
+ if (ImageInfo.DateTime[0]) {
+ bufLen = addKeyValueString(&buf, bufLen, "DateTime", ImageInfo.DateTime);
+ if (bufLen == 0) return NULL;
+ }
+ bufLen = addKeyValueInt(&buf, bufLen, "ImageWidth", ImageInfo.Width);
+ if (bufLen == 0) return NULL;
+
+ bufLen = addKeyValueInt(&buf, bufLen, "ImageLength", ImageInfo.Height);
+ if (bufLen == 0) return NULL;
+
+ bufLen = addKeyValueInt(&buf, bufLen, "Orientation", ImageInfo.Orientation);
+ if (bufLen == 0) return NULL;
+
+ bufLen = addKeyValueInt(&buf, bufLen, "Flash", ImageInfo.FlashUsed);
+ if (bufLen == 0) return NULL;
+
+ if (ImageInfo.FocalLength){
+ bufLen = addKeyValueInt(&buf, bufLen, "FocalLength", ImageInfo.FocalLength);
+ if (bufLen == 0) return NULL;
+ }
+
+ if (ImageInfo.DigitalZoomRatio > 1.0){
+ // Digital zoom used. Shame on you!
+ bufLen = addKeyValueDouble(&buf, bufLen, "DigitalZoomRatio", ImageInfo.DigitalZoomRatio, "%1.3f");
+ if (bufLen == 0) return NULL;
+ }
+
+ if (ImageInfo.ExposureTime){
+ const char* format;
+ if (ImageInfo.ExposureTime < 0.010){
+ format = "%6.4f";
+ } else {
+ format = "%5.3f";
+ }
+
+ bufLen = addKeyValueDouble(&buf, bufLen, "ExposureTime", (double)ImageInfo.ExposureTime, format);
+ if (bufLen == 0) return NULL;
+ }
+
+ if (ImageInfo.ApertureFNumber){
+ bufLen = addKeyValueDouble(&buf, bufLen, "FNumber", (double)ImageInfo.ApertureFNumber, "%3.1f");
+ if (bufLen == 0) return NULL;
+ }
+
+ if (ImageInfo.Distance){
+ bufLen = addKeyValueDouble(&buf, bufLen, "SubjectDistance", (double)ImageInfo.Distance, "%4.2f");
+ if (bufLen == 0) return NULL;
+ }
+
+ if (ImageInfo.ISOequivalent){
+ bufLen = addKeyValueInt(&buf, bufLen, "ISOSpeedRatings", ImageInfo.ISOequivalent);
+ if (bufLen == 0) return NULL;
+ }
+
+ if (ImageInfo.ExposureBias){
+ // If exposure bias was specified, but set to zero, presumably its no bias at all,
+ // so only show it if its nonzero.
+ bufLen = addKeyValueDouble(&buf, bufLen, "ExposureBiasValue", (double)ImageInfo.ExposureBias, "%4.2f");
+ if (bufLen == 0) return NULL;
+ }
+
+ bufLen = addKeyValueInt(&buf, bufLen, "WhiteBalance", ImageInfo.Whitebalance);
+ if (bufLen == 0) return NULL;
+
+ bufLen = addKeyValueInt(&buf, bufLen, "LightSource", ImageInfo.LightSource);
+ if (bufLen == 0) return NULL;
+
+
+ if (ImageInfo.MeteringMode) {
+ bufLen = addKeyValueInt(&buf, bufLen, "MeteringMode", ImageInfo.MeteringMode);
+ if (bufLen == 0) return NULL;
+ }
+
+ if (ImageInfo.ExposureProgram) {
+ bufLen = addKeyValueInt(&buf, bufLen, "ExposureProgram", ImageInfo.ExposureProgram);
+ if (bufLen == 0) return NULL;
+ }
+
+ if (ImageInfo.ExposureMode) {
+ bufLen = addKeyValueInt(&buf, bufLen, "ExposureMode", ImageInfo.ExposureMode);
+ if (bufLen == 0) return NULL;
+ }
+
+ if (ImageInfo.GpsInfoPresent) {
+ bufLen = addKeyValueString(&buf, bufLen, "GPSLatitude", ImageInfo.GpsLatRaw);
+ if (bufLen == 0) return NULL;
+ bufLen = addKeyValueString(&buf, bufLen, "GPSLatitudeRef", ImageInfo.GpsLatRef);
+ if (bufLen == 0) return NULL;
+ bufLen = addKeyValueString(&buf, bufLen, "GPSLongitude", ImageInfo.GpsLongRaw);
+ if (bufLen == 0) return NULL;
+ bufLen = addKeyValueString(&buf, bufLen, "GPSLongitudeRef", ImageInfo.GpsLongRef);
+ if (bufLen == 0) return NULL;
+ if (ImageInfo.GpsAlt[0]) {
+ bufLen = addKeyValueString(&buf, bufLen, "GPSAltitude", ImageInfo.GpsAlt);
+ if (bufLen == 0) return NULL;
+ }
+ }
+
+ if (ImageInfo.Comments[0]) {
+ bufLen = addKeyValueString(&buf, bufLen, "UserComment", ImageInfo.Comments);
+ if (bufLen == 0) return NULL;
+ }
+
+ // put the attribute count at the beginnnig of the string
+ int finalBufLen = strlen(buf) + 20;
+ char* finalResult = malloc(finalBufLen);
+ if (finalResult == NULL) {
+ free(buf);
+ return NULL;
+ }
+ snprintf(finalResult, finalBufLen, "%d %s", attributeCount, buf);
+ int k;
+ for (k = 0; k < finalBufLen; k++)
+ if (finalResult[k] > 127)
+ finalResult[k] = '?';
+ free(buf);
+
+#ifdef SUPERDEBUG
+ LOGE("*********Returning result \"%s\"", finalResult);
+#endif
+ jstring result = ((*env)->NewStringUTF(env, finalResult));
+ free(finalResult);
+ DiscardData();
+ return result;
+}
+
+static const char *classPathName = "com/android/camera/ExifInterface";
+
+static JNINativeMethod methods[] = {
+ {"saveAttributesNative", "(Ljava/lang/String;Ljava/lang/String;)V", (void*)saveAttributes },
+ {"getAttributesNative", "(Ljava/lang/String;)Ljava/lang/String;", (void*)getAttributes },
+ {"appendThumbnailNative", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)appendThumbnail },
+ {"commitChangesNative", "(Ljava/lang/String;)V", (void*)commitChanges },
+ {"getThumbnailNative", "(Ljava/lang/String;)[B", (void*)getThumbnail },
+};
+
+/*
+ * Register several native methods for one class.
+ */
+static int registerNativeMethods(JNIEnv* env, const char* className,
+ JNINativeMethod* gMethods, int numMethods)
+{
+ jclass clazz;
+
+ clazz = (*env)->FindClass(env, className);
+ if (clazz == NULL) {
+ fprintf(stderr,
+ "Native registration unable to find class '%s'\n", className);
+ return JNI_FALSE;
+ }
+ if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
+ fprintf(stderr, "RegisterNatives failed for '%s'\n", className);
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
+
+/*
+ * Register native methods for all classes we know about.
+ */
+static int registerNatives(JNIEnv* env)
+{
+ return jniRegisterNativeMethods(env, classPathName,
+ methods, NELEM(methods));
+}
+
+/*
+ * Set some test stuff up.
+ *
+ * Returns the JNI version on success, -1 on failure.
+ */
+__attribute__ ((visibility("default"))) jint JNI_OnLoad(JavaVM* vm, void* reserved)
+{
+ JNIEnv* env = NULL;
+ jint result = -1;
+
+ if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
+ fprintf(stderr, "ERROR: GetEnv failed\n");
+ goto bail;
+ }
+ assert(env != NULL);
+
+ printf("In mgmain JNI_OnLoad\n");
+
+ if (registerNatives(env) < 0) {
+ fprintf(stderr, "ERROR: Exif native registration failed\n");
+ goto bail;
+ }
+
+ /* success -- return valid version number */
+ result = JNI_VERSION_1_4;
+
+bail:
+ return result;
+}
diff --git a/makernote.c b/makernote.c
new file mode 100644
index 0000000..0be4df5
--- /dev/null
+++ b/makernote.c
@@ -0,0 +1,185 @@
+//--------------------------------------------------------------------------
+// Parse some maker specific onformation.
+// (Very limited right now - add maker specific stuff to this module)
+//--------------------------------------------------------------------------
+#include "jhead.h"
+
+//--------------------------------------------------------------------------
+// Process exif format directory, as used by Cannon maker note
+//--------------------------------------------------------------------------
+void ProcessCanonMakerNoteDir(unsigned char * DirStart, unsigned char * OffsetBase,
+ unsigned ExifLength)
+{
+ int de;
+ int a;
+ int NumDirEntries;
+
+ NumDirEntries = Get16u(DirStart);
+ #define DIR_ENTRY_ADDR(Start, Entry) (Start+2+12*(Entry))
+
+ {
+ unsigned char * DirEnd;
+ DirEnd = DIR_ENTRY_ADDR(DirStart, NumDirEntries);
+ if (DirEnd > (OffsetBase+ExifLength)){
+ ErrNonfatal("Illegally sized directory",0,0);
+ return;
+ }
+
+ if (DumpExifMap){
+ printf("Map: %05d-%05d: Directory (makernote)\n",DirStart-OffsetBase, DirEnd-OffsetBase);
+ }
+ }
+
+ if (ShowTags){
+ printf("(dir has %d entries)\n",NumDirEntries);
+ }
+
+ for (de=0;de<NumDirEntries;de++){
+ int Tag, Format, Components;
+ unsigned char * ValuePtr;
+ int ByteCount;
+ unsigned char * DirEntry;
+ DirEntry = DIR_ENTRY_ADDR(DirStart, de);
+
+ Tag = Get16u(DirEntry);
+ Format = Get16u(DirEntry+2);
+ Components = Get32u(DirEntry+4);
+
+ if ((Format-1) >= NUM_FORMATS) {
+ // (-1) catches illegal zero case as unsigned underflows to positive large.
+ ErrNonfatal("Illegal number format %d for tag %04x", Format, Tag);
+ continue;
+ }
+
+ if ((unsigned)Components > 0x10000){
+ ErrNonfatal("Illegal number of components %d for tag %04x", Components, Tag);
+ continue;
+ }
+
+ ByteCount = Components * BytesPerFormat[Format];
+
+ if (ByteCount > 4){
+ unsigned OffsetVal;
+ OffsetVal = Get32u(DirEntry+8);
+ // If its bigger than 4 bytes, the dir entry contains an offset.
+ if (OffsetVal+ByteCount > ExifLength){
+ // Bogus pointer offset and / or bytecount value
+ ErrNonfatal("Illegal value pointer for tag %04x", Tag,0);
+ continue;
+ }
+ ValuePtr = OffsetBase+OffsetVal;
+
+ if (DumpExifMap){
+ printf("Map: %05d-%05d: Data for makernote tag %04x\n",OffsetVal, OffsetVal+ByteCount, Tag);
+ }
+ }else{
+ // 4 bytes or less and value is in the dir entry itself
+ ValuePtr = DirEntry+8;
+ }
+
+ if (ShowTags){
+ // Show tag name
+ printf(" Canon maker tag %04x Value = ", Tag);
+ }
+
+ // Show tag value.
+ switch(Format){
+
+ case FMT_UNDEFINED:
+ // Undefined is typically an ascii string.
+
+ case FMT_STRING:
+ // String arrays printed without function call (different from int arrays)
+ if (ShowTags){
+ printf("\"");
+ for (a=0;a<ByteCount;a++){
+ int ZeroSkipped = 0;
+ if (ValuePtr[a] >= 32){
+ if (ZeroSkipped){
+ printf("?");
+ ZeroSkipped = 0;
+ }
+ putchar(ValuePtr[a]);
+ }else{
+ if (ValuePtr[a] == 0){
+ ZeroSkipped = 1;
+ }
+ }
+ }
+ printf("\"\n");
+ }
+ break;
+
+ default:
+ if (ShowTags){
+ PrintFormatNumber(ValuePtr, Format, ByteCount);
+ printf("\n");
+ }
+ }
+ if (Tag == 1 && Components > 16){
+ int IsoCode = Get16u(ValuePtr + 16*sizeof(unsigned short));
+ if (IsoCode >= 16 && IsoCode <= 24){
+ ImageInfo.ISOequivalent = 50 << (IsoCode-16);
+ }
+ }
+
+ if (Tag == 4 && Format == FMT_USHORT){
+ if (Components > 7){
+ int WhiteBalance = Get16u(ValuePtr + 7*sizeof(unsigned short));
+ switch(WhiteBalance){
+ // 0=Auto, 6=Custom
+ case 1: ImageInfo.LightSource = 1; break; // Sunny
+ case 2: ImageInfo.LightSource = 1; break; // Cloudy
+ case 3: ImageInfo.LightSource = 3; break; // Thungsten
+ case 4: ImageInfo.LightSource = 2; break; // Fourescent
+ case 5: ImageInfo.LightSource = 4; break; // Flash
+ }
+ }
+ if (Components > 19 && ImageInfo.Distance <= 0) {
+ // Inidcates the distance the autofocus camera is focused to.
+ // Tends to be less accurate as distance increases.
+ int temp_dist = Get16u(ValuePtr + 19*sizeof(unsigned short));
+printf("temp dist=%d\n",temp_dist);
+ if (temp_dist != 65535){
+ ImageInfo.Distance = (float)temp_dist/100;
+ }else{
+ ImageInfo.Distance = -1 /* infinity */;
+ }
+ }
+ }
+ }
+}
+
+//--------------------------------------------------------------------------
+// Show generic maker note - just hex bytes.
+//--------------------------------------------------------------------------
+void ShowMakerNoteGeneric(unsigned char * ValuePtr, int ByteCount)
+{
+ int a;
+ for (a=0;a<ByteCount;a++){
+ if (a > 10){
+ printf("...");
+ break;
+ }
+ printf(" %02x",ValuePtr[a]);
+ }
+ printf(" (%d bytes)", ByteCount);
+ printf("\n");
+
+}
+
+//--------------------------------------------------------------------------
+// Process maker note - to the limited extent that its supported.
+//--------------------------------------------------------------------------
+void ProcessMakerNote(unsigned char * ValuePtr, int ByteCount,
+ unsigned char * OffsetBase, unsigned ExifLength)
+{
+ if (strstr(ImageInfo.CameraMake, "Canon")){
+ ProcessCanonMakerNoteDir(ValuePtr, OffsetBase, ExifLength);
+ }else{
+ if (ShowTags){
+ ShowMakerNoteGeneric(ValuePtr, ByteCount);
+ }
+ }
+}
+