diff options
author | Chih-Chung Chang <> | 2009-03-24 17:29:55 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-24 17:29:55 -0700 |
commit | 33d501413c67140b10498b49e65fd0c942e07db1 (patch) | |
tree | aa4ee74fe5aa90bca71873b85119fc67e4580eee | |
parent | e9eb3b06de1158d0207a4ef3288ccf4ac483d5e4 (diff) | |
download | jhead-33d501413c67140b10498b49e65fd0c942e07db1.tar.gz |
Automated import from //branches/cupcake/...@141562,141562android-sdk-1.5_r3android-sdk-1.5_r1android-sdk-1.5-preandroid-1.5r4android-1.5r3android-1.5r2android-1.5cupcake-releasecupcake
-rw-r--r-- | exif.c | 450 | ||||
-rw-r--r-- | gpsinfo.c | 10 | ||||
-rw-r--r-- | iptc.c | 75 | ||||
-rw-r--r-- | jhead.c | 224 | ||||
-rw-r--r-- | jhead.h | 46 | ||||
-rw-r--r-- | jpgfile.c | 65 |
6 files changed, 580 insertions, 290 deletions
@@ -81,143 +81,219 @@ 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 +#define TAG_INTEROP_INDEX 0x0001 +#define TAG_INTEROP_VERSION 0x0002 +#define TAG_IMAGE_WIDTH 0x0100 +#define TAG_IMAGE_LENGTH 0x0101 +#define TAG_BITS_PER_SAMPLE 0x0102 +#define TAG_COMPRESSION 0x0103 +#define TAG_PHOTOMETRIC_INTERP 0x0106 +#define TAG_FILL_ORDER 0x010A +#define TAG_DOCUMENT_NAME 0x010D +#define TAG_IMAGE_DESCRIPTION 0x010E +#define TAG_MAKE 0x010F +#define TAG_MODEL 0x0110 +#define TAG_SRIP_OFFSET 0x0111 +#define TAG_ORIENTATION 0x0112 +#define TAG_SAMPLES_PER_PIXEL 0x0115 +#define TAG_ROWS_PER_STRIP 0x0116 +#define TAG_STRIP_BYTE_COUNTS 0x0117 +#define TAG_X_RESOLUTION 0x011A +#define TAG_Y_RESOLUTION 0x011B +#define TAG_PLANAR_CONFIGURATION 0x011C +#define TAG_RESOLUTION_UNIT 0x0128 +#define TAG_TRANSFER_FUNCTION 0x012D +#define TAG_SOFTWARE 0x0131 +#define TAG_DATETIME 0x0132 +#define TAG_ARTIST 0x013B +#define TAG_WHITE_POINT 0x013E +#define TAG_PRIMARY_CHROMATICITIES 0x013F +#define TAG_TRANSFER_RANGE 0x0156 +#define TAG_JPEG_PROC 0x0200 +#define TAG_THUMBNAIL_OFFSET 0x0201 +#define TAG_THUMBNAIL_LENGTH 0x0202 +#define TAG_Y_CB_CR_COEFFICIENTS 0x0211 +#define TAG_Y_CB_CR_SUB_SAMPLING 0x0212 +#define TAG_Y_CB_CR_POSITIONING 0x0213 +#define TAG_REFERENCE_BLACK_WHITE 0x0214 +#define TAG_RELATED_IMAGE_WIDTH 0x1001 +#define TAG_RELATED_IMAGE_LENGTH 0x1002 +#define TAG_CFA_REPEAT_PATTERN_DIM 0x828D +#define TAG_CFA_PATTERN1 0x828E +#define TAG_BATTERY_LEVEL 0x828F +#define TAG_COPYRIGHT 0x8298 +#define TAG_EXPOSURETIME 0x829A +#define TAG_FNUMBER 0x829D +#define TAG_IPTC_NAA 0x83BB +#define TAG_EXIF_OFFSET 0x8769 +#define TAG_INTER_COLOR_PROFILE 0x8773 +#define TAG_EXPOSURE_PROGRAM 0x8822 +#define TAG_SPECTRAL_SENSITIVITY 0x8824 +#define TAG_GPSINFO 0x8825 +#define TAG_ISO_EQUIVALENT 0x8827 +#define TAG_OECF 0x8828 +#define TAG_EXIF_VERSION 0x9000 +#define TAG_DATETIME_ORIGINAL 0x9003 +#define TAG_DATETIME_DIGITIZED 0x9004 +#define TAG_COMPONENTS_CONFIG 0x9101 +#define TAG_CPRS_BITS_PER_PIXEL 0x9102 +#define TAG_SHUTTERSPEED 0x9201 +#define TAG_APERTURE 0x9202 +#define TAG_BRIGHTNESS_VALUE 0x9203 +#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_SUBSEC_TIME 0x9290 +#define TAG_SUBSEC_TIME_ORIG 0x9291 +#define TAG_SUBSEC_TIME_DIG 0x9292 + +#define TAG_WINXP_TITLE 0x9c9b // Windows XP - not part of exif standard. +#define TAG_WINXP_COMMENT 0x9c9c // Windows XP - not part of exif standard. +#define TAG_WINXP_AUTHOR 0x9c9d // Windows XP - not part of exif standard. +#define TAG_WINXP_KEYWORDS 0x9c9e // Windows XP - not part of exif standard. +#define TAG_WINXP_SUBJECT 0x9c9f // Windows XP - not part of exif standard. + +#define TAG_FLASH_PIX_VERSION 0xA000 +#define TAG_COLOR_SPACE 0xA001 +#define TAG_EXIF_IMAGEWIDTH 0xA002 +#define TAG_EXIF_IMAGELENGTH 0xA003 +#define TAG_RELATED_AUDIO_FILE 0xA004 +#define TAG_INTEROP_OFFSET 0xA005 +#define TAG_FLASH_ENERGY 0xA20B +#define TAG_SPATIAL_FREQ_RESP 0xA20C +#define TAG_FOCAL_PLANE_XRES 0xA20E +#define TAG_FOCAL_PLANE_YRES 0xA20F +#define TAG_FOCAL_PLANE_UNITS 0xA210 +#define TAG_SUBJECT_LOCATION 0xA214 +#define TAG_EXPOSURE_INDEX 0xA215 +#define TAG_SENSING_METHOD 0xA217 +#define TAG_FILE_SOURCE 0xA300 +#define TAG_SCENE_TYPE 0xA301 +#define TAG_CFA_PATTERN 0xA302 +#define TAG_CUSTOM_RENDERED 0xA401 +#define TAG_EXPOSURE_MODE 0xA402 +#define TAG_WHITEBALANCE 0xA403 +#define TAG_DIGITALZOOMRATIO 0xA404 +#define TAG_FOCALLENGTH_35MM 0xA405 +#define TAG_SCENE_CAPTURE_TYPE 0xA406 +#define TAG_GAIN_CONTROL 0xA407 +#define TAG_CONTRAST 0xA408 +#define TAG_SATURATION 0xA409 +#define TAG_SHARPNESS 0xA40A +#define TAG_DISTANCE_RANGE 0xA40C // 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}, + { TAG_INTEROP_INDEX, "InteropIndex", 0, 0}, + { TAG_INTEROP_VERSION, "InteropVersion", 0, 0}, + { TAG_IMAGE_WIDTH, "ImageWidth", FMT_USHORT, 1}, + { TAG_IMAGE_LENGTH, "ImageLength", FMT_USHORT, 1}, + { TAG_BITS_PER_SAMPLE, "BitsPerSample", FMT_USHORT, 3}, + { TAG_COMPRESSION, "Compression", FMT_USHORT, 1}, + { TAG_PHOTOMETRIC_INTERP, "PhotometricInterpretation", FMT_USHORT, 1}, + { TAG_FILL_ORDER, "FillOrder", 0, 0}, + { TAG_DOCUMENT_NAME, "DocumentName", 0, 0}, + { TAG_IMAGE_DESCRIPTION, "ImageDescription", 0, 0 }, + { TAG_MAKE, "Make", FMT_STRING, -1}, + { TAG_MODEL, "Model", FMT_STRING, -1}, + { TAG_SRIP_OFFSET, "StripOffsets", FMT_USHORT, 1}, + { TAG_ORIENTATION, "Orientation", FMT_USHORT, 1}, + { TAG_SAMPLES_PER_PIXEL, "SamplesPerPixel", FMT_USHORT, 3}, + { TAG_ROWS_PER_STRIP, "RowsPerStrip", FMT_USHORT, 1}, + { TAG_STRIP_BYTE_COUNTS, "StripByteCounts", FMT_USHORT, 1}, + { TAG_X_RESOLUTION, "XResolution", FMT_URATIONAL, 1}, + { TAG_Y_RESOLUTION, "YResolution", FMT_URATIONAL, 1}, + { TAG_PLANAR_CONFIGURATION, "PlanarConfiguration", FMT_USHORT, 1}, + { TAG_RESOLUTION_UNIT, "ResolutionUnit", FMT_USHORT, 1}, + { TAG_TRANSFER_FUNCTION, "TransferFunction", FMT_USHORT, 768}, + { TAG_SOFTWARE, "Software", FMT_STRING, -1}, + { TAG_DATETIME, "DateTime", FMT_STRING, 20}, + { TAG_ARTIST, "Artist", FMT_STRING, -1}, + { TAG_WHITE_POINT, "WhitePoint", FMT_SRATIONAL, 2}, + { TAG_PRIMARY_CHROMATICITIES, "PrimaryChromaticities", FMT_SRATIONAL, 6}, + { TAG_TRANSFER_RANGE, "TransferRange", 0, 0}, + { TAG_JPEG_PROC, "JPEGProc", 0, 0}, + { TAG_THUMBNAIL_OFFSET, "ThumbnailOffset", 0, 0}, + { TAG_THUMBNAIL_LENGTH, "ThumbnailLength", 0, 0}, + { TAG_Y_CB_CR_COEFFICIENTS, "YCbCrCoefficients", FMT_SRATIONAL, 3}, + { TAG_Y_CB_CR_SUB_SAMPLING, "YCbCrSubSampling", FMT_USHORT, 2}, + { TAG_Y_CB_CR_POSITIONING, "YCbCrPositioning", FMT_USHORT, 1}, + { TAG_REFERENCE_BLACK_WHITE, "ReferenceBlackWhite", FMT_SRATIONAL, 6}, + { TAG_RELATED_IMAGE_WIDTH, "RelatedImageWidth", 0, 0}, + { TAG_RELATED_IMAGE_LENGTH, "RelatedImageLength", 0, 0}, + { TAG_CFA_REPEAT_PATTERN_DIM, "CFARepeatPatternDim", 0, 0}, + { TAG_CFA_PATTERN1, "CFAPattern", 0, 0}, + { TAG_BATTERY_LEVEL, "BatteryLevel", 0, 0}, + { TAG_COPYRIGHT, "Copyright", FMT_STRING, -1}, + { TAG_EXPOSURETIME, "ExposureTime", FMT_USHORT, 1}, + { TAG_FNUMBER, "FNumber", FMT_SRATIONAL, 1}, + { TAG_IPTC_NAA, "IPTC/NAA", 0, 0}, + { TAG_EXIF_OFFSET, "ExifOffset", 0, 0}, + { TAG_INTER_COLOR_PROFILE, "InterColorProfile", 0, 0}, + { TAG_EXPOSURE_PROGRAM, "ExposureProgram", FMT_SSHORT, 1}, + { TAG_SPECTRAL_SENSITIVITY, "SpectralSensitivity", FMT_STRING, -1}, + { TAG_GPSINFO, "GPS Dir offset", 0, 0}, + { TAG_ISO_EQUIVALENT, "ISOSpeedRatings", FMT_SSHORT, -1}, + { TAG_OECF, "OECF", 0, 0}, + { TAG_EXIF_VERSION, "ExifVersion", FMT_BYTE, 4}, + { TAG_DATETIME_ORIGINAL, "DateTimeOriginal", FMT_STRING, 20}, + { TAG_DATETIME_DIGITIZED, "DateTimeDigitized", FMT_STRING, 20}, + { TAG_COMPONENTS_CONFIG, "ComponentsConfiguration", FMT_BYTE, 4}, + { TAG_CPRS_BITS_PER_PIXEL, "CompressedBitsPerPixel", FMT_SRATIONAL, 1}, + { TAG_SHUTTERSPEED, "ShutterSpeedValue", FMT_SRATIONAL, 1}, + { TAG_APERTURE, "ApertureValue", FMT_URATIONAL, 1}, + { TAG_BRIGHTNESS_VALUE, "BrightnessValue", FMT_SRATIONAL, 1}, + { TAG_EXPOSURE_BIAS, "ExposureBiasValue", FMT_SRATIONAL, 1}, + { TAG_MAXAPERTURE, "MaxApertureValue", FMT_URATIONAL, 1}, + { TAG_SUBJECT_DISTANCE, "SubjectDistance", FMT_URATIONAL, 1}, + { TAG_METERING_MODE, "MeteringMode", FMT_USHORT, 1}, + { TAG_LIGHT_SOURCE, "LightSource", FMT_USHORT, 1}, + { TAG_FLASH, "Flash", FMT_USHORT, 1}, + { TAG_FOCALLENGTH, "FocalLength", FMT_URATIONAL, 1}, + { TAG_MAKER_NOTE, "MakerNote", FMT_STRING, -1}, + { TAG_USERCOMMENT, "UserComment", FMT_STRING, -1}, + { TAG_SUBSEC_TIME, "SubSecTime", FMT_STRING, -1}, + { TAG_SUBSEC_TIME_ORIG, "SubSecTimeOriginal", FMT_STRING, -1}, + { TAG_SUBSEC_TIME_DIG, "SubSecTimeDigitized", FMT_STRING, -1}, + { TAG_WINXP_TITLE, "Windows-XP Title", 0, 0}, + { TAG_WINXP_COMMENT, "Windows-XP comment", 0, 0}, + { TAG_WINXP_AUTHOR, "Windows-XP author", 0, 0}, + { TAG_WINXP_KEYWORDS, "Windows-XP keywords", 0, 0}, + { TAG_WINXP_SUBJECT, "Windows-XP subject", 0, 0}, + { TAG_FLASH_PIX_VERSION, "FlashPixVersion", FMT_BYTE, 4}, + { TAG_COLOR_SPACE, "ColorSpace", FMT_USHORT, 1}, + { TAG_EXIF_IMAGEWIDTH, "ExifImageWidth", 0, 0}, + { TAG_EXIF_IMAGELENGTH, "ExifImageLength", 0, 0}, + { TAG_RELATED_AUDIO_FILE, "RelatedAudioFile", 0, 0}, + { TAG_INTEROP_OFFSET, "InteroperabilityOffset", 0, 0}, + { TAG_FLASH_ENERGY, "FlashEnergy", FMT_URATIONAL, 1}, + { TAG_SPATIAL_FREQ_RESP, "SpatialFrequencyResponse", FMT_STRING, -1}, + { TAG_FOCAL_PLANE_XRES, "FocalPlaneXResolution", FMT_URATIONAL, 1}, + { TAG_FOCAL_PLANE_YRES, "FocalPlaneYResolution", FMT_URATIONAL, 1}, + { TAG_FOCAL_PLANE_UNITS, "FocalPlaneResolutionUnit", FMT_USHORT, 1}, + { TAG_SUBJECT_LOCATION, "SubjectLocation", FMT_USHORT, 2}, + { TAG_EXPOSURE_INDEX, "ExposureIndex", FMT_URATIONAL, 1}, + { TAG_SENSING_METHOD, "SensingMethod", FMT_USHORT, 1}, + { TAG_FILE_SOURCE, "FileSource", 0, 1}, + { TAG_SCENE_TYPE, "SceneType", 0, 1}, + { TAG_CFA_PATTERN, "CFA Pattern", 0, -1}, + { TAG_CUSTOM_RENDERED, "CustomRendered", FMT_USHORT, 1}, + { TAG_EXPOSURE_MODE, "ExposureMode", FMT_USHORT, 1}, + { TAG_WHITEBALANCE, "WhiteBalance", FMT_USHORT, 1}, + { TAG_DIGITALZOOMRATIO, "DigitalZoomRatio", FMT_URATIONAL, 1}, + { TAG_FOCALLENGTH_35MM, "FocalLengthIn35mmFilm", FMT_USHORT, 1}, + { TAG_SCENE_CAPTURE_TYPE, "SceneCaptureType", FMT_USHORT, 1}, + { TAG_GAIN_CONTROL, "GainControl", FMT_URATIONAL, 1}, + { TAG_CONTRAST, "Contrast", FMT_USHORT, 1}, + { TAG_SATURATION, "Saturation", FMT_USHORT, 1}, + { TAG_SHARPNESS, "Sharpness", FMT_USHORT, 1}, + { TAG_DISTANCE_RANGE, "SubjectDistanceRange", FMT_USHORT, 1}, } ; #define TAG_TABLE_SIZE (sizeof(TagTable) / sizeof(TagTable_t)) @@ -308,7 +384,7 @@ void PrintFormatNumber(void * ValuePtr, int Format, int ByteCount) { int s,n; - for(n=0;n<20;n++){ + for(n=0;n<16;n++){ switch(Format){ case FMT_SBYTE: case FMT_BYTE: printf("%02x",*(uchar *)ValuePtr); s=1; break; @@ -334,7 +410,7 @@ void PrintFormatNumber(void * ValuePtr, int Format, int ByteCount) ValuePtr = (void *)((char *)ValuePtr + s); } - if (n >= 20) printf("..."); + if (n >= 16) printf("..."); } @@ -414,12 +490,12 @@ static void ProcessExifDir(unsigned char * DirStart, unsigned char * OffsetBase, // 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); + ErrNonfatal("Illegally sized exif subdirectory (%d entries)",NumDirEntries,0); return; } } if (DumpExifMap){ - printf("Map: %05d-%05d: Directory\n",DirStart-OffsetBase, DirEnd+4-OffsetBase); + printf("Map: %05d-%05d: Directory\n",(int)(DirStart-OffsetBase), (int)(DirEnd+4-OffsetBase)); } @@ -575,9 +651,28 @@ static void ProcessExifDir(unsigned char * DirStart, unsigned char * OffsetBase, (char *)ValuePtr - (char *)OffsetBase; break; + case TAG_WINXP_COMMENT: + if (ImageInfo.Comments[0]){ // We already have a jpeg comment. + // Already have a comment (probably windows comment), skip this one. + if (ShowTags) printf("Windows XP commend and other comment in header\n"); + break; // Already have a windows comment, skip this one. + } + + if (ByteCount > 1){ + if (ByteCount > MAX_COMMENT_SIZE) ByteCount = MAX_COMMENT_SIZE; + memcpy(ImageInfo.Comments, ValuePtr, ByteCount); + ImageInfo.CommentWidchars = ByteCount/2; + } + break; case TAG_USERCOMMENT: - // Olympus has this padded with trailing spaces. Remove these first. + if (ImageInfo.Comments[0]){ // We already have a jpeg comment. + // Already have a comment (probably windows comment), skip this one. + if (ShowTags) printf("Multiple comments in exif header\n"); + break; // Already have a windows comment, skip this one. + } + + // Comment is often padded with trailing spaces. Remove these first. for (a=ByteCount;;){ a--; if ((ValuePtr)[a] == ' '){ @@ -598,9 +693,8 @@ static void ProcessExifDir(unsigned char * DirStart, unsigned char * OffsetBase, break; } } - }else{ - strncpy(ImageInfo.Comments, (char *)ValuePtr, 199); + strncpy(ImageInfo.Comments, (char *)ValuePtr, MAX_COMMENT_SIZE-1); } break; @@ -679,11 +773,11 @@ static void ProcessExifDir(unsigned char * DirStart, unsigned char * OffsetBase, if (ExifImageWidth < a) ExifImageWidth = a; break; - case TAG_FOCALPLANEXRES: + case TAG_FOCAL_PLANE_XRES: FocalplaneXRes = ConvertAnyFormat(ValuePtr, Format); break; - case TAG_FOCALPLANEUNITS: + case TAG_FOCAL_PLANE_UNITS: switch((int)ConvertAnyFormat(ValuePtr, Format)){ case 1: FocalplaneUnits = 25.4; break; // inch case 2: @@ -791,6 +885,12 @@ static void ProcessExifDir(unsigned char * DirStart, unsigned char * OffsetBase, // computing it from sensor geometry and actual focal length. ImageInfo.FocalLength35mmEquiv = (unsigned)ConvertAnyFormat(ValuePtr, Format); break; + + case TAG_DISTANCE_RANGE: + // Three possible standard values: + // 1 = macro, 2 = close, 3 = distant + ImageInfo.DistanceRange = (int)ConvertAnyFormat(ValuePtr, Format); + break; } } @@ -1073,12 +1173,12 @@ void create_EXIF(ExifElement_t* elements, int exifTagCount, int gpsTagCount) unsigned short NumEntries; int DataWriteIndex; int DirIndex; - int ThumbnailOffsetDirIndex = 0; + int DirContinuation = 0; #ifdef SUPERDEBUG LOGE("create_EXIF %d exif elements, %d gps elements", exifTagCount, gpsTagCount); #endif - + MotorolaOrder = 0; memcpy(Buffer+2, "Exif\0\0II",8); @@ -1097,7 +1197,8 @@ void create_EXIF(ExifElement_t* elements, int exifTagCount, int gpsTagCount) Put16u(Buffer+DirIndex, NumEntries); // Number of entries DirIndex += 2; - // Entries go here.... + + // Entries go here... { // Date/time entry char* dateTime = NULL; @@ -1142,8 +1243,7 @@ void create_EXIF(ExifElement_t* elements, int exifTagCount, int gpsTagCount) &DirIndex, &DataWriteIndex); } - } - { + if (gpsTagCount) { // Link to gps dir entry writeExifTagAndData(TAG_GPSINFO, @@ -1161,7 +1261,7 @@ void create_EXIF(ExifElement_t* elements, int exifTagCount, int gpsTagCount) if (gpsTagCount) { exifDirPtr += 2 + gpsTagCount*12 + 4; } - ThumbnailOffsetDirIndex = DirIndex; + DirContinuation = DirIndex; writeExifTagAndData(TAG_EXIF_OFFSET, FMT_ULONG, 1, @@ -1173,10 +1273,11 @@ void create_EXIF(ExifElement_t* elements, int exifTagCount, int gpsTagCount) } // End of directory - contains optional link to continued directory. - Put32u(Buffer+DirIndex, 0); + DirContinuation = DirIndex; printf("Ending Exif section DirIndex = %d DataWriteIndex %d", DirIndex, DataWriteIndex); } + // GPS Section if (gpsTagCount) { DirIndex = DataWriteIndex; @@ -1216,9 +1317,8 @@ void create_EXIF(ExifElement_t* elements, int exifTagCount, int gpsTagCount) } { - // 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. + //Continuation which links to this directory; + Put32u(Buffer+DirContinuation, DataWriteIndex-8); printf("Starting Thumbnail section DirIndex = %d", DirIndex); DirIndex = DataWriteIndex; @@ -1256,6 +1356,7 @@ void create_EXIF(ExifElement_t* elements, int exifTagCount, int gpsTagCount) printf("Ending Thumbnail section DirIndex = %d DataWriteIndex %d", DirIndex, DataWriteIndex); } + Buffer[0] = (unsigned char)(DataWriteIndex >> 8); Buffer[1] = (unsigned char)DataWriteIndex; @@ -1594,6 +1695,23 @@ void ShowImageInfo(int ShowFileInfo) break; } + if (ImageInfo.DistanceRange) { + printf("Focus range : "); + switch(ImageInfo.DistanceRange) { + case 1: + printf("macro"); + break; + case 2: + printf("close"); + break; + case 3: + printf("distant"); + break; + } + printf("\n"); + } + + if (ImageInfo.Process != M_SOF0){ // don't show it if its the plain old boring 'baseline' process, but do @@ -1622,21 +1740,25 @@ void ShowImageInfo(int ShowFileInfo) 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 : "); + if (!ImageInfo.CommentWidchars){ + for (a=0;a<MAX_COMMENT_SIZE;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{ - printf("\n"); + putchar(c); } - }else{ - putchar(c); } + printf("\n"); + }else{ + printf("%.*ls\n", ImageInfo.CommentWidchars, (wchar_t *)ImageInfo.Comments); } - printf("\n"); } if (ImageInfo.ThumbnailOffset){ printf("Map: %05d-%05d: Thumbnail\n",ImageInfo.ThumbnailOffset, ImageInfo.ThumbnailOffset+ImageInfo.ThumbnailSize); @@ -205,21 +205,22 @@ void ProcessGpsInfo(unsigned char * DirStart, int ByteCountUnused, unsigned char 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){ + while (den > 1 && digits <= 6){ den = den / 10; digits += 1; } + if (digits > 6) digits = 6; 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){ @@ -244,7 +245,8 @@ void ProcessGpsInfo(unsigned char * DirStart, int ByteCountUnused, unsigned char break; case TAG_GPS_ALT: - sprintf(ImageInfo.GpsAlt + 1, "%dm", Get32s(ValuePtr)); + sprintf(ImageInfo.GpsAlt + 1, "%.2fm", + ConvertAnyFormat(ValuePtr, Format)); break; } @@ -256,7 +258,7 @@ void ProcessGpsInfo(unsigned char * DirStart, int ByteCountUnused, unsigned char // Show unknown tag printf(" Illegal GPS tag %04x=", Tag); } - + switch(Format){ case FMT_UNDEFINED: // Undefined is typically an ascii string. @@ -1,9 +1,10 @@ //-------------------------------------------------------------------------- -// Process IPTC data. +// Process IPTC data and XMP data. //-------------------------------------------------------------------------- #include "jhead.h" -// Supported IPTC entry types +// IPTC entry types known to Jhead (there's many more defined) +#define IPTC_RECORD_VERSION 0x00 #define IPTC_SUPLEMENTAL_CATEGORIES 0x14 #define IPTC_KEYWORDS 0x19 #define IPTC_CAPTION 0x78 @@ -25,6 +26,9 @@ #define IPTC_COPYRIGHT 0x0A #define IPTC_COUNTRY_CODE 0x64 #define IPTC_REFERENCE_SERVICE 0x2D +#define IPTC_TIME_CREATED 0x3C +#define IPTC_SUB_LOCATION 0x5C +#define IPTC_IMAGE_TYPE 0x82 //-------------------------------------------------------------------------- // Process and display IPTC marker. @@ -86,21 +90,21 @@ badsig: // 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 + pos += 4; // 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; + unsigned char type = 0; short length = 0; char * description = NULL; if (pos+5 > maxpos) goto corrupt; signature = (*pos << 8) + (*(pos+1)); - pos += sizeof(short); + pos += 2; if (signature != 0x1C02){ break; @@ -108,42 +112,50 @@ badsig: type = *pos++; length = (*pos << 8) + (*(pos+1)); - pos += sizeof(short); // Skip tag length + pos += 2; // Skip tag length if (pos+length > maxpos) goto corrupt; // Process tag here switch (type) { + case IPTC_RECORD_VERSION: + printf("Record vers. : %d\n", (*pos << 8) + (*(pos+1))); + break; + 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_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_BYLINE_TITLE: description = "Byline Title"; 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_OBJECT_NAME: description = "Object Name"; 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; + case IPTC_REFERENCE_SERVICE: description = "Country Code"; break; + case IPTC_COUNTRY_CODE: description = "Ref. Service"; break; + case IPTC_TIME_CREATED: description = "Time Created"; break; + case IPTC_SUB_LOCATION: description = "Sub Location"; break; + case IPTC_IMAGE_TYPE: description = "Image type"; break; + default: if (ShowTags){ - printf("Unrecognised IPTC tag: 0x%02x \n", type); + printf("Unrecognised IPTC tag: %d\n", type ); } break; } if (description != NULL) { char TempBuf[32]; memset(TempBuf, 0, sizeof(TempBuf)); - memset(TempBuf, ' ', 13); + memset(TempBuf, ' ', 14); memcpy(TempBuf, description, strlen(description)); strcat(TempBuf, ":"); printf("%s %*.*s\n", TempBuf, length, length, pos); @@ -154,3 +166,40 @@ badsig: corrupt: ErrNonfatal("Pointer corruption in IPTC\n",0,0); } + + + +//-------------------------------------------------------------------------- +// Dump contents of XMP section +//-------------------------------------------------------------------------- +void ShowXmp(Section_t XmpSection) +{ + unsigned char * Data; + char OutLine[101]; + int OutLineChars; + int NonBlank; + unsigned a; + NonBlank = 0; + Data = XmpSection.Data; + OutLineChars = 0; + + + for (a=0;a<XmpSection.Size;a++){ + if (Data[a] >= 32 && Data[a] < 128){ + OutLine[OutLineChars++] = Data[a]; + if (Data[a] != ' ') NonBlank |= 1; + }else{ + if (Data[a] != '\n'){ + OutLine[OutLineChars++] = '?'; + } + } + if (Data[a] == '\n' || OutLineChars >= 100){ + OutLine[OutLineChars] = 0; + if (NonBlank){ + puts(OutLine); + } + NonBlank = (NonBlank & 1) << 1; + OutLineChars = 0; + } + } +} @@ -2,12 +2,12 @@ // 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 +// Version 2.86 // // Compiling under Windows: // Make sure you have Microsoft's compiler on the path, then run make.bat // -// Dec 1999 - Feb 2007 +// Dec 1999 - Mar 2009 // // by Matthias Wandel www.sentex.net/~mwandel //-------------------------------------------------------------------------- @@ -16,11 +16,11 @@ #include <sys/stat.h> #include <utils/Log.h> -#define JHEAD_VERSION "2.71" +#define JHEAD_VERSION "2.87" // 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 +//#define MATTHIAS #ifdef _WIN32 #include <io.h> @@ -36,8 +36,10 @@ 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 RenameToDate = 0; // 1=rename, 2=rename all. +#ifdef _WIN32 static int RenameAssociatedFiles = FALSE; +#endif static char * strftime_args = NULL; // Format for new file name. static int Exif2FileTime = FALSE; static int DoModify = FALSE; @@ -60,6 +62,7 @@ static unsigned FileTimeToExif = FALSE; static int DeleteComments = FALSE; static int DeleteExif = FALSE; static int DeleteIptc = FALSE; +static int DeleteXmp = 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. @@ -86,7 +89,6 @@ 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 @@ -103,7 +105,6 @@ static int ShowFileInfo = TRUE; // Indicates to show standard file info 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); } @@ -118,7 +119,7 @@ void ErrNonfatal(char * msg, int a1, int a2) LOGE(msg, a1, a2); if (SupressNonFatalErrors) return; - fprintf(stderr,"Nonfatal Error : "); + fprintf(stderr,"\nNonfatal Error : "); if (CurrentFile) fprintf(stderr,"'%s' ",CurrentFile); fprintf(stderr, msg, a1, a2); fprintf(stderr, "\n"); @@ -142,7 +143,7 @@ static int FileEditComment(char * TempFileName, char * Comment, int CommentSize) { FILE * file; int a; - char QuotedPath[PATH_MAX]; + char QuotedPath[PATH_MAX+10]; file = fopen(TempFileName, "w"); if (file == NULL){ @@ -156,8 +157,8 @@ static int FileEditComment(char * TempFileName, char * Comment, int CommentSize) fflush(stdout); // So logs are contiguous. { - char * Editor; - Editor = getenv("EDITOR"); + char * Editor; + Editor = getenv("EDITOR"); if (Editor == NULL){ #ifdef _WIN32 Editor = "notepad"; @@ -165,11 +166,12 @@ static int FileEditComment(char * TempFileName, char * Comment, int CommentSize) Editor = "vi"; #endif } + if (strlen(Editor) > PATH_MAX) ErrFatal("env too long"); sprintf(QuotedPath, "%s \"%s\"",Editor, TempFileName); a = system(QuotedPath); } - + if (a != 0){ perror("Editor failed to launch"); exit(-1); @@ -245,11 +247,11 @@ static int ModifyDescriptComment(char * OutComment, char * SrcComment) // Overwrite old comment of same tag with new one. if (!memcmp(Line, AddComment, l+1)){ TagExists = TRUE; - strcpy(Line, AddComment); + strncpy(Line, AddComment, sizeof(Line)); Modified = TRUE; } } - strcat(OutComment, Line); + strncat(OutComment, Line, MAX_COMMENT_SIZE-5-strlen(OutComment)); strcat(OutComment, "\n"); break; } @@ -264,7 +266,7 @@ static int ModifyDescriptComment(char * OutComment, char * SrcComment) } if (AddComment && TagExists == FALSE){ - strcat(OutComment, AddComment); + strncat(OutComment, AddComment, MAX_COMMENT_SIZE-5-strlen(OutComment)); strcat(OutComment, "\n"); Modified = TRUE; } @@ -273,7 +275,7 @@ static int ModifyDescriptComment(char * OutComment, char * SrcComment) // 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); + strncat(OutComment, Temp, MAX_COMMENT_SIZE-5-strlen(OutComment)); Modified = TRUE; } return Modified; @@ -283,7 +285,7 @@ static int ModifyDescriptComment(char * OutComment, char * SrcComment) //-------------------------------------------------------------------------- static int AutoResizeCmdStuff(void) { - static char CommandString[500]; + static char CommandString[PATH_MAX+1]; double scale; ApplyCommand = CommandString; @@ -307,37 +309,82 @@ static int AutoResizeCmdStuff(void) //-------------------------------------------------------------------------- +// Escape an argument such that it is interpreted literally by the shell +// (returns the number of written characters) +//-------------------------------------------------------------------------- +static int shellescape(char* to, const char* from) +{ + int i, j; + i = j = 0; + + // Enclosing characters in double quotes preserves the literal value of + // all characters within the quotes, with the exception of $, `, and \. + to[j++] = '"'; + while(from[i]) + { +#ifdef _WIN32 + // Under WIN32, there isn't really anything dangerous you can do with + // escape characters, plus windows users aren't as sercurity paranoid. + // Hence, no need to do fancy escaping. + to[j++] = from[i++]; +#else + switch(from[i]) { + case '"': + case '$': + case '`': + case '\\': + to[j++] = '\\'; + // Fallthru... + default: + to[j++] = from[i++]; + } +#endif + if (j >= PATH_MAX) ErrFatal("max path exceeded"); + } + to[j++] = '"'; + return j; +} + + +//-------------------------------------------------------------------------- // 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]; + char ExecString[PATH_MAX*3]; + char TempName[PATH_MAX+10]; 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'); + // Generate an unused temporary file name in the destination directory + // (a is the number of characters to copy from FileName) + a = strlen(FileName)-1; + while(a > 0 && FileName[a-1] != SLASH) a--; + memcpy(TempName, FileName, a); + strcpy(TempName+a, "XXXXXX"); + mktemp(TempName); + if(!TempName[0]) { + ErrFatal("Cannot find available temporary file name"); + } + + // 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); + e += shellescape(ExecString+e, FileName); a += 1; continue; } if (ApplyCommand[a+1] == 'o'){ // Needs an output file distinct from the input file. - e += sprintf(ExecString+e, "\"%s\"",TempName); + e += shellescape(ExecString+e, TempName); a += 1; TempUsed = TRUE; - unlink(TempName);// Remove any pre-existing temp file continue; } } @@ -421,7 +468,7 @@ static void RelativeName(char * OutFileName, const char * NamePattern, const cha strncat(OutFileName, OrigName, PATH_MAX); strncat(OutFileName, Subst+2, PATH_MAX); }else{ - strcpy(OutFileName, NamePattern); + strncpy(OutFileName, NamePattern, PATH_MAX); } } @@ -435,8 +482,8 @@ void RenameAssociated(const char * FileName, char * NewBaseName) int a; int PathLen; int ExtPos; - char FilePattern[_MAX_PATH]; - char NewName[_MAX_PATH]; + char FilePattern[_MAX_PATH+1]; + char NewName[_MAX_PATH+1]; struct _finddata_t finddata; long find_handle; @@ -448,7 +495,7 @@ void RenameAssociated(const char * FileName, char * NewBaseName) FilePattern[ExtPos] = '*'; FilePattern[ExtPos+1] = '\0'; - for(PathLen = strlen(FileName);FileName[PathLen-1] != '\\';){ + for(PathLen = strlen(FileName);FileName[PathLen-1] != SLASH;){ if (--PathLen == 0) break; } @@ -462,13 +509,13 @@ void RenameAssociated(const char * FileName, char * NewBaseName) 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 + strncpy(FilePattern+PathLen, finddata.name, PATH_MAX-PathLen); // 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 + strncat(NewName, finddata.name+a, _MAX_PATH-strlen(NewName)); // add extension to new name if (rename(FilePattern, NewName) == 0){ if (!Quiet){ @@ -490,14 +537,17 @@ static void DoFileRenaming(const char * FileName) { int NumAlpha = 0; int NumDigit = 0; - int PrefixPart = 0; - int ExtensionPart = strlen(FileName); + int PrefixPart = 0; // Where the actual filename starts. + int ExtensionPart; // Where the file extension starts. int a; struct tm tm; char NewBaseName[PATH_MAX*2]; + int AddLetter = 0; + char NewName[PATH_MAX+2]; + ExtensionPart = strlen(FileName); for (a=0;FileName[a];a++){ - if (FileName[a] == '/' || FileName[a] == '\\'){ + if (FileName[a] == SLASH){ // Don't count path component. NumAlpha = 0; NumDigit = 0; @@ -524,7 +574,7 @@ static void DoFileRenaming(const char * FileName) } - strcpy(NewBaseName, FileName); // Get path component of name. + strncpy(NewBaseName, FileName, PATH_MAX); // Get path component of name. if (strftime_args){ // Complicated scheme for flexibility. Just pass the args to strftime. @@ -559,14 +609,17 @@ static void DoFileRenaming(const char * FileName) }else if (pattern[a] == 'i'){ if (ppos >= 0 && a<ppos+4){ // Replace this part with a number. - char pat[8]; - char num[16]; + char pat[8], num[16]; + int l,nl; 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)); + nl = strlen(num); + l = strlen(pattern+a+1); + if (ppos+nl+l+1 >= PATH_MAX) ErrFatal("str overflow"); + memmove(pattern+ppos+nl, pattern+a+1, l+1); + memcpy(pattern+ppos, num, nl); break; } }else if (!isdigit(pattern[a])){ @@ -574,16 +627,19 @@ static void DoFileRenaming(const char * FileName) } } } - - strftime(NewBaseName+PrefixPart, PATH_MAX, pattern, &tm); + strftime(NewName, PATH_MAX, pattern, &tm); }else{ // My favourite scheme. - sprintf(NewBaseName+PrefixPart, "%02d%02d-%02d%02d%02d", + sprintf(NewName, "%02d%02d-%02d%02d%02d", tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); } + NewBaseName[PrefixPart] = 0; + CatPath(NewBaseName, NewName); + + AddLetter = isdigit(NewBaseName[strlen(NewBaseName)-1]); for (a=0;;a++){ - char NewName[PATH_MAX]; + char NewName[PATH_MAX+10]; char NameExtra[3]; struct stat dummy; @@ -593,10 +649,10 @@ static void DoFileRenaming(const char * FileName) // 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. + if (AddLetter){ + NameExtra[0] = (char)('a'-1+a); // Try a,b,c,d... for suffix if it ends in a number. }else{ - NameExtra[0] = (char)('0'-1+a); // Try 1,2,3,4... for suffix if it ends in a char. + NameExtra[0] = (char)('0'-1+a); // Try 0,1,2,3... for suffix if it ends in a latter. } NameExtra[1] = 0; }else{ @@ -607,6 +663,11 @@ static void DoFileRenaming(const char * FileName) if (!strcmp(FileName, NewName)) break; // Skip if its already this name. + if (!EnsurePathExists(NewBaseName)){ + break; + } + + if (stat(NewName, &dummy)){ // This name does not pre-exist. if (rename(FileName, NewName) == 0){ @@ -621,9 +682,10 @@ static void DoFileRenaming(const char * FileName) printf("Error: Couldn't rename '%s' to '%s'\n",FileName, NewName); } break; + } - if (a >= 9){ + if (a > 25 || (!AddLetter && a > 9)){ printf("Possible new names for for '%s' already exist\n",FileName); break; } @@ -645,7 +707,7 @@ static int DoAutoRotate(const char * FileName) ErrFatal("Orientation screwup"); } - sprintf(RotateCommand, "jpegtran -%s -outfile &o &i", Argument); + sprintf(RotateCommand, "jpegtran -trim -%s -outfile &o &i", Argument); ApplyCommand = RotateCommand; DoCommand(FileName, FALSE); ApplyCommand = NULL; @@ -656,15 +718,15 @@ static int DoAutoRotate(const char * FileName) ImageInfo.ThumbnailAtEnd){ // Must have a thumbnail that exists and is modifieable. - char ThumbTempName_in[PATH_MAX+4]; - char ThumbTempName_out[PATH_MAX+4]; + char ThumbTempName_in[PATH_MAX+5]; + char ThumbTempName_out[PATH_MAX+5]; 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\"", + sprintf(RotateCommand,"jpegtran -trim -%s -outfile \"%s\" \"%s\"", Argument, ThumbTempName_out, ThumbTempName_in); if (system(RotateCommand) == 0){ @@ -710,7 +772,14 @@ static int RegenerateThumbnail(const char * FileName) void ProcessFile(const char * FileName) { int Modified = FALSE; - ReadMode_t ReadMode = READ_METADATA; + ReadMode_t ReadMode; + + if (strlen(FileName) >= PATH_MAX-1){ + // Protect against buffer overruns in strcpy / strcat's on filename + ErrFatal("filename too long"); + } + + ReadMode = READ_METADATA; CurrentFile = FileName; FilesMatched = 1; @@ -874,7 +943,7 @@ void ProcessFile(const char * FileName) EditComment || CommentInsertfileName || CommentInsertLiteral){ Section_t * CommentSec; - char Comment[1001]; + char Comment[MAX_COMMENT_SIZE+1]; int CommentSize; CommentSec = FindSection(M_COM); @@ -890,9 +959,9 @@ void ProcessFile(const char * FileName) } CommentSize = CommentSec->Size-2; - if (CommentSize > 1000){ - fprintf(stderr, "Truncating comment at 1000 chars\n"); - CommentSize = 1000; + if (CommentSize > MAX_COMMENT_SIZE){ + fprintf(stderr, "Truncating comment at %d chars\n",MAX_COMMENT_SIZE); + CommentSize = MAX_COMMENT_SIZE; } if (CommentInsertfileName){ @@ -914,11 +983,11 @@ void ProcessFile(const char * FileName) if (CommentSize < 0) CommentSize = 0; } }else if (CommentInsertLiteral){ - strncpy(Comment, CommentInsertLiteral, 1000); + strncpy(Comment, CommentInsertLiteral, MAX_COMMENT_SIZE); CommentSize = strlen(Comment); }else{ #ifdef MATTHIAS - char CommentZt[1001]; + char CommentZt[MAX_COMMENT_SIZE+1]; memcpy(CommentZt, (char *)CommentSec->Data+2, CommentSize); CommentZt[CommentSize] = '\0'; if (ModifyDescriptComment(Comment, CommentZt)){ @@ -930,7 +999,7 @@ void ProcessFile(const char * FileName) memcpy(Comment, (char *)CommentSec->Data+2, CommentSize); #endif { - char EditFileName[PATH_MAX+4]; + char EditFileName[PATH_MAX+5]; strcpy(EditFileName, FileName); strcat(EditFileName, ".txt"); @@ -1027,6 +1096,7 @@ skip_unixtime: Pointer = ExifSection->Data+ImageInfo.DateTimeOffsets[a]+8; memcpy(Pointer, TempBuf, 19); } + memcpy(ImageInfo.DateTime, TempBuf, 19); Modified = TRUE; }else{ @@ -1043,13 +1113,16 @@ skip_unixtime: if (DeleteIptc){ if (RemoveSectionType(M_IPTC)) Modified = TRUE; } + if (DeleteXmp){ + if (RemoveSectionType(M_XMP)) Modified = TRUE; + } if (DeleteUnknown){ if (RemoveUnknownSections()) Modified = TRUE; } if (Modified){ - char BackupName[400]; + char BackupName[PATH_MAX+5]; struct stat buf; if (!Quiet) printf("Modified: %s\n",FileName); @@ -1133,7 +1206,7 @@ badtime: 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" + "used by most Digital Cameras. v"JHEAD_VERSION" Matthias Wandel, Mar 02 2009.\n" "http://www.sentex.net/~mwandel/jhead\n" "\n"); @@ -1148,6 +1221,7 @@ static void Usage (void) " -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" + " -dx Deletex XMP section\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" @@ -1177,6 +1251,8 @@ static void Usage (void) " 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" + " The new name may include a path as part of the name. If this path\n" + " does not exist, it will be created\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" @@ -1195,8 +1271,8 @@ static void Usage (void) " 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" + " minus original date. Date is specified as yyyy:mm:dd or as date\n" + " and time in the format yyyy:mm: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" @@ -1340,6 +1416,9 @@ int main (int argc, char **argv) }else if (!strcmp(arg,"-di")){ DeleteIptc = TRUE; DoModify = TRUE; + }else if (!strcmp(arg,"-dx")){ + DeleteXmp = TRUE; + DoModify = TRUE; }else if (!strcmp(arg, "-du")){ DeleteUnknown = TRUE; DoModify = TRUE; @@ -1348,6 +1427,7 @@ int main (int argc, char **argv) DeleteComments = TRUE; DeleteIptc = TRUE; DeleteUnknown = TRUE; + DeleteXmp = TRUE; DoModify = TRUE; }else if (!strcmp(arg,"-ce")){ EditComment = TRUE; @@ -1422,13 +1502,17 @@ int main (int argc, char **argv) if (*arg){ // A strftime format string is supplied. strftime_args = arg; + #ifdef _WIN32 + SlashToNative(strftime_args); + #endif //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 + RenameAssociatedFiles = TRUE; + #endif }else if (!strcmp(arg,"-ft")){ Exif2FileTime = TRUE; DoReadAction = TRUE; @@ -1588,13 +1672,7 @@ int main (int argc, char **argv) FilesMatched = FALSE; #ifdef _WIN32 - { - int a; - for (a=0;;a++){ - if (argv[argn][a] == '\0') break; - if (argv[argn][a] == '/') argv[argn][a] = '\\'; - } - } + SlashToNative(argv[argn]); // Use my globbing module to do fancier wildcard expansion with recursive // subdirectories under Windows. MyGlob(argv[argn], ProcessFile); @@ -35,12 +35,16 @@ typedef unsigned char uchar; #define FALSE 0 #endif -#define MAX_COMMENT 2000 +#define MAX_COMMENT_SIZE 2000 #ifdef _WIN32 #define PATH_MAX _MAX_PATH + #define SLASH '\\' +#else + #define SLASH '/' #endif + //-------------------------------------------------------------------------- // This structure is used to store jpeg file sections in memory. typedef struct { @@ -85,7 +89,10 @@ typedef struct { int ExposureMode; int ISOequivalent; int LightSource; - char Comments[MAX_COMMENT]; + int DistanceRange; + + char Comments[MAX_COMMENT_SIZE]; + int CommentWidchars; // If nonzer, widechar comment, indicates number of chars. unsigned ThumbnailOffset; // Exif offset to thumbnail unsigned ThumbnailSize; // Size of thumbnail. @@ -191,10 +198,18 @@ int GpsTagNameToValue(const char* tagName); TagTable_t* GpsTagToTagTableEntry(unsigned short tag); // iptc.c prototpyes -void show_IPTC (unsigned char * CharBuf, unsigned int length); +void show_IPTC (unsigned char * CharBuf, unsigned int length); +void ShowXmp(Section_t XmpSection); // Prototypes for myglob.c module -extern void MyGlob(const char * Pattern , void (*FileFuncParm)(const char * FileName)); +#ifdef _WIN32 +void MyGlob(const char * Pattern , void (*FileFuncParm)(const char * FileName)); +void SlashToNative(char * Path); +#endif + +// Prototypes for paths.c module +int EnsurePathExists(const char * FileName); +void CatPath(char * BasePath, const char * FilePath); // Prototypes from jpgfile.c int ReadJpegSections (FILE * infile, ReadMode_t ReadMode); @@ -221,11 +236,11 @@ extern char* formatStr(int format); // 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_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_SOF5 0xC5 // NB: codes C4 and CC are NOT SOF markers #define M_SOF6 0xC6 #define M_SOF7 0xC7 #define M_SOF9 0xC9 @@ -234,15 +249,16 @@ extern char* formatStr(int format); #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_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. Also used for XMP data! +#define M_XMP 0x10E1 // Not a real tag (same value in file as Exif!) +#define M_COM 0xFE // COMment #define M_DQT 0xDB #define M_DHT 0xC4 #define M_DRI 0xDD -#define M_IPTC 0xED // IPTC marker +#define M_IPTC 0xED // IPTC marker @@ -45,13 +45,13 @@ static int Get16m(const void * Short) static void process_COM (const uchar * Data, int length) { int ch; - char Comment[MAX_COMMENT+1]; + char Comment[MAX_COMMENT_SIZE+1]; int nch; int a; nch = 0; - if (length > MAX_COMMENT) length = MAX_COMMENT; // Truncate if it won't fit in our structure. + if (length > MAX_COMMENT_SIZE) length = MAX_COMMENT_SIZE; // Truncate if it won't fit in our structure. for (a=2;a<length;a++){ ch = Data[a]; @@ -72,6 +72,7 @@ static void process_COM (const uchar * Data, int length) } strcpy(ImageInfo.Comments,Comment); + ImageInfo.CommentWidchars = 0; } @@ -141,22 +142,16 @@ int ReadJpegSections (FILE * infile, ReadMode_t ReadMode) CheckSectionsAllocated(); - for (a=0;a<7;a++){ + for (a=0;a<=16;a++){ marker = fgetc(infile); if (marker != 0xff) break; - if (a >= 6){ + if (a >= 16){ 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; @@ -254,15 +249,24 @@ int ReadJpegSections (FILE * infile, ReadMode_t ReadMode) 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); + // There can be different section using the same marker. + if (ReadMode & READ_METADATA){ + if (memcmp(Data+2, "Exif", 4) == 0){ + process_EXIF(Data, itemlen); + break; + }else if (memcmp(Data+2, "http:", 5) == 0){ + Sections[SectionsRead-1].Type = M_XMP; // Change tag for internal purposes. + if (ShowTags){ + printf("Image cotains XMP section, %d bytes long\n", itemlen); + if (ShowTags){ + ShowXmp(Sections[SectionsRead-1]); + } + } + break; + } } + // Oterwise, discard this section. + free(Sections[--SectionsRead].Data); break; case M_IPTC: @@ -399,9 +403,14 @@ int ReplaceThumbnail(const char * ThumbFileName) uchar * ThumbnailPointer; if (ImageInfo.ThumbnailOffset == 0 || ImageInfo.ThumbnailAtEnd == FALSE){ + if (ThumbFileName == NULL){ + // Delete of nonexistent thumbnail (not even pointers present) + // No action, no error. + return 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"); @@ -430,6 +439,10 @@ int ReplaceThumbnail(const char * ThumbFileName) return FALSE; } }else{ + if (ImageInfo.ThumbnailSize == 0){ + return FALSE; + } + ThumbLen = 0; ThumbnailFile = NULL; } @@ -469,15 +482,19 @@ void DiscardAllButExif(void) Section_t ExifKeeper; Section_t CommentKeeper; Section_t IptcKeeper; + Section_t XmpKeeper; int a; memset(&ExifKeeper, 0, sizeof(ExifKeeper)); memset(&CommentKeeper, 0, sizeof(CommentKeeper)); memset(&IptcKeeper, 0, sizeof(IptcKeeper)); + memset(&XmpKeeper, 0, sizeof(IptcKeeper)); for (a=0;a<SectionsRead;a++){ if (Sections[a].Type == M_EXIF && ExifKeeper.Type == 0){ - ExifKeeper = Sections[a]; + ExifKeeper = Sections[a]; + }else if (Sections[a].Type == M_XMP && XmpKeeper.Type == 0){ + XmpKeeper = 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){ @@ -499,6 +516,11 @@ void DiscardAllButExif(void) CheckSectionsAllocated(); Sections[SectionsRead++] = IptcKeeper; } + + if (XmpKeeper.Type){ + CheckSectionsAllocated(); + Sections[SectionsRead++] = XmpKeeper; + } } //-------------------------------------------------------------------------- @@ -537,7 +559,7 @@ int WriteJpegFile(const char * FileName) // Write all the misc sections for (a=0;a<SectionsRead-1;a++){ fputc(0xff,outfile); - fputc(Sections[a].Type, outfile); + fputc((unsigned char)Sections[a].Type, outfile); fwrite(Sections[a].Data, Sections[a].Size, 1, outfile); } @@ -611,6 +633,7 @@ int RemoveUnknownSections(void) case M_SOS: case M_JFIF: case M_EXIF: + case M_XMP: case M_COM: case M_DQT: case M_DHT: |