diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
commit | 426377d9ce95606bac741d99b5b0a3e2038924fe (patch) | |
tree | e369cfdda7dfd42203aee8100cad25e2033f1cc8 | |
download | jhead-cdma-import.tar.gz |
Initial Contributionandroid-1.0release-1.0cdma-import
-rw-r--r-- | Android.mk | 38 | ||||
-rw-r--r-- | MODULE_LICENSE_PUBLIC_DOMAIN | 0 | ||||
-rw-r--r-- | NOTICE | 34 | ||||
-rw-r--r-- | exif.c | 1683 | ||||
-rw-r--r-- | gpsinfo.c | 299 | ||||
-rw-r--r-- | iptc.c | 156 | ||||
-rw-r--r-- | jhead.c | 1621 | ||||
-rw-r--r-- | jhead.h | 248 | ||||
-rw-r--r-- | jpgfile.c | 684 | ||||
-rw-r--r-- | main.c | 759 | ||||
-rw-r--r-- | makernote.c | 185 |
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 @@ -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. + @@ -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"); + } + } + } +} + + @@ -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); +} @@ -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 + @@ -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; +} @@ -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); + } + } +} + |