jhead-3.04/0000775000175000017500000000000013565770126012371 5ustar mwandelmwandeljhead-3.04/exif.c0000444000175000017500000017164413565770126013501 0ustar mwandelmwandel//-------------------------------------------------------------------------- // 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 static unsigned char * DirWithThumbnailPtrs; static double FocalplaneXRes; static double FocalplaneUnits; static int ExifImageWidth; int MotorolaOrder = 0; // for fixing the rotation. static void * OrientationPtr[2]; static int OrientationNumFormat[2]; int NumOrientations = 0; typedef struct { unsigned short Tag; char * Desc; }TagTable_t; //-------------------------------------------------------------------------- // Table of Jpeg encoding process names static const TagTable_t ProcessTable[] = { { M_SOF0, "Baseline"}, { M_SOF1, "Extended sequential"}, { M_SOF2, "Progressive"}, { M_SOF3, "Lossless"}, { M_SOF5, "Differential sequential"}, { M_SOF6, "Differential progressive"}, { M_SOF7, "Differential lossless"}, { M_SOF9, "Extended sequential, arithmetic coding"}, { M_SOF10, "Progressive, arithmetic coding"}, { M_SOF11, "Lossless, arithmetic coding"}, { M_SOF13, "Differential sequential, arithmetic coding"}, { M_SOF14, "Differential progressive, arithmetic coding"}, { M_SOF15, "Differential lossless, arithmetic coding"}, }; #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_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_SUBJECTAREA 0x9214 #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_PIXEL_X_DIMENSION 0xA002 #define TAG_PIXEL_Y_DIMENSION 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 #define TAG_IMAGE_UNIQUE_ID 0xA420 static const TagTable_t TagTable[] = { { TAG_INTEROP_INDEX, "InteropIndex"}, { TAG_INTEROP_VERSION, "InteropVersion"}, { TAG_IMAGE_WIDTH, "ImageWidth"}, { TAG_IMAGE_LENGTH, "ImageLength"}, { TAG_BITS_PER_SAMPLE, "BitsPerSample"}, { TAG_COMPRESSION, "Compression"}, { TAG_PHOTOMETRIC_INTERP, "PhotometricInterpretation"}, { TAG_FILL_ORDER, "FillOrder"}, { TAG_DOCUMENT_NAME, "DocumentName"}, { TAG_IMAGE_DESCRIPTION, "ImageDescription"}, { TAG_MAKE, "Make"}, { TAG_MODEL, "Model"}, { TAG_SRIP_OFFSET, "StripOffsets"}, { TAG_ORIENTATION, "Orientation"}, { TAG_SAMPLES_PER_PIXEL, "SamplesPerPixel"}, { TAG_ROWS_PER_STRIP, "RowsPerStrip"}, { TAG_STRIP_BYTE_COUNTS, "StripByteCounts"}, { TAG_X_RESOLUTION, "XResolution"}, { TAG_Y_RESOLUTION, "YResolution"}, { TAG_PLANAR_CONFIGURATION, "PlanarConfiguration"}, { TAG_RESOLUTION_UNIT, "ResolutionUnit"}, { TAG_TRANSFER_FUNCTION, "TransferFunction"}, { TAG_SOFTWARE, "Software"}, { TAG_DATETIME, "DateTime"}, { TAG_ARTIST, "Artist"}, { TAG_WHITE_POINT, "WhitePoint"}, { TAG_PRIMARY_CHROMATICITIES, "PrimaryChromaticities"}, { TAG_TRANSFER_RANGE, "TransferRange"}, { TAG_JPEG_PROC, "JPEGProc"}, { TAG_THUMBNAIL_OFFSET, "ThumbnailOffset"}, { TAG_THUMBNAIL_LENGTH, "ThumbnailLength"}, { TAG_Y_CB_CR_COEFFICIENTS, "YCbCrCoefficients"}, { TAG_Y_CB_CR_SUB_SAMPLING, "YCbCrSubSampling"}, { TAG_Y_CB_CR_POSITIONING, "YCbCrPositioning"}, { TAG_REFERENCE_BLACK_WHITE, "ReferenceBlackWhite"}, { TAG_RELATED_IMAGE_WIDTH, "RelatedImageWidth"}, { TAG_RELATED_IMAGE_LENGTH, "RelatedImageLength"}, { TAG_CFA_REPEAT_PATTERN_DIM, "CFARepeatPatternDim"}, { TAG_CFA_PATTERN1, "CFAPattern"}, { TAG_BATTERY_LEVEL, "BatteryLevel"}, { TAG_COPYRIGHT, "Copyright"}, { TAG_EXPOSURETIME, "ExposureTime"}, { TAG_FNUMBER, "FNumber"}, { TAG_IPTC_NAA, "IPTC/NAA"}, { TAG_EXIF_OFFSET, "ExifOffset"}, { TAG_INTER_COLOR_PROFILE, "InterColorProfile"}, { TAG_EXPOSURE_PROGRAM, "ExposureProgram"}, { TAG_SPECTRAL_SENSITIVITY, "SpectralSensitivity"}, { TAG_GPSINFO, "GPS Dir offset"}, { TAG_ISO_EQUIVALENT, "ISOSpeedRatings"}, { TAG_OECF, "OECF"}, { TAG_EXIF_VERSION, "ExifVersion"}, { TAG_DATETIME_ORIGINAL, "DateTimeOriginal"}, { TAG_DATETIME_DIGITIZED, "DateTimeDigitized"}, { TAG_COMPONENTS_CONFIG, "ComponentsConfiguration"}, { TAG_CPRS_BITS_PER_PIXEL, "CompressedBitsPerPixel"}, { TAG_SHUTTERSPEED, "ShutterSpeedValue"}, { TAG_APERTURE, "ApertureValue"}, { TAG_BRIGHTNESS_VALUE, "BrightnessValue"}, { TAG_EXPOSURE_BIAS, "ExposureBiasValue"}, { TAG_MAXAPERTURE, "MaxApertureValue"}, { TAG_SUBJECT_DISTANCE, "SubjectDistance"}, { TAG_METERING_MODE, "MeteringMode"}, { TAG_LIGHT_SOURCE, "LightSource"}, { TAG_FLASH, "Flash"}, { TAG_FOCALLENGTH, "FocalLength"}, { TAG_MAKER_NOTE, "MakerNote"}, { TAG_USERCOMMENT, "UserComment"}, { TAG_SUBSEC_TIME, "SubSecTime"}, { TAG_SUBSEC_TIME_ORIG, "SubSecTimeOriginal"}, { TAG_SUBSEC_TIME_DIG, "SubSecTimeDigitized"}, { TAG_WINXP_TITLE, "Windows-XP Title"}, { TAG_WINXP_COMMENT, "Windows-XP comment"}, { TAG_WINXP_AUTHOR, "Windows-XP author"}, { TAG_WINXP_KEYWORDS, "Windows-XP keywords"}, { TAG_WINXP_SUBJECT, "Windows-XP subject"}, { TAG_FLASH_PIX_VERSION, "FlashPixVersion"}, { TAG_COLOR_SPACE, "ColorSpace"}, { TAG_PIXEL_X_DIMENSION, "ExifImageWidth"}, { TAG_PIXEL_Y_DIMENSION, "ExifImageLength"}, { TAG_RELATED_AUDIO_FILE, "RelatedAudioFile"}, { TAG_INTEROP_OFFSET, "InteroperabilityOffset"}, { TAG_FLASH_ENERGY, "FlashEnergy"}, { TAG_SPATIAL_FREQ_RESP, "SpatialFrequencyResponse"}, { TAG_FOCAL_PLANE_XRES, "FocalPlaneXResolution"}, { TAG_FOCAL_PLANE_YRES, "FocalPlaneYResolution"}, { TAG_FOCAL_PLANE_UNITS, "FocalPlaneResolutionUnit"}, { TAG_SUBJECT_LOCATION, "SubjectLocation"}, { TAG_EXPOSURE_INDEX, "ExposureIndex"}, { TAG_SENSING_METHOD, "SensingMethod"}, { TAG_FILE_SOURCE, "FileSource"}, { TAG_SCENE_TYPE, "SceneType"}, { TAG_CFA_PATTERN, "CFA Pattern"}, { TAG_CUSTOM_RENDERED, "CustomRendered"}, { TAG_EXPOSURE_MODE, "ExposureMode"}, { TAG_WHITEBALANCE, "WhiteBalance"}, { TAG_DIGITALZOOMRATIO, "DigitalZoomRatio"}, { TAG_FOCALLENGTH_35MM, "FocalLengthIn35mmFilm"}, { TAG_SUBJECTAREA, "SubjectArea"}, { TAG_SCENE_CAPTURE_TYPE, "SceneCaptureType"}, { TAG_GAIN_CONTROL, "GainControl"}, { TAG_CONTRAST, "Contrast"}, { TAG_SATURATION, "Saturation"}, { TAG_SHARPNESS, "Sharpness"}, { TAG_DISTANCE_RANGE, "SubjectDistanceRange"}, { TAG_IMAGE_UNIQUE_ID, "ImageUniqueId"}, } ; #define TAG_TABLE_SIZE (sizeof(TagTable) / sizeof(TagTable_t)) //-------------------------------------------------------------------------- // 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<16;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: printf("%u/%u",Get32s(ValuePtr), Get32s(4+(char *)ValuePtr)); s = 8; break; 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 >= 16) 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{ if (Format == FMT_SRATIONAL){ Value = (double)Num/Den; }else{ Value = (double)(unsigned)Num/(double)(unsigned)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 in Exif header",Format,0); } return Value; } //-------------------------------------------------------------------------- // Process one of the nested EXIF directories. //-------------------------------------------------------------------------- static void ProcessExifDir(unsigned char * DirStart, unsigned char * OffsetBase, int ExifLength, int NestingLevel) { int de; int a; int NumDirEntries; int ThumbnailOffset = 0; int ThumbnailSize = 0; char IndentString[25]; if (NestingLevel > 4){ ErrNonfatal("Maximum Exif 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 Exif subdirectory (%d entries)",NumDirEntries,0); return; } } if (DumpExifMap){ printf("Map: %05u-%05u: Directory\n",(int)(DirStart-OffsetBase), (int)(DirEnd+4-OffsetBase)); } } if (ShowTags){ printf("(dir has %d entries)\n",NumDirEntries); } for (de=0;de= NUM_FORMATS) { // (-1) catches illegal zero case as unsigned underflows to positive large. ErrNonfatal("Illegal number format %d for tag %04x in Exif", Format, Tag); continue; } if ((unsigned)Components > 0x10000){ ErrNonfatal("Too many components %d for tag %04x in Exif", Components, Tag); continue; } ByteCount = Components * BytesPerFormat[Format]; if (ByteCount > 4){ int OffsetVal; OffsetVal = Get32u(DirEntry+8); // If its bigger than 4 bytes, the dir entry contains an offset. if (OffsetVal+ByteCount > ExifLength || OffsetVal < 0 || OffsetVal > 65536){ // Bogus pointer offset and / or bytecount value ErrNonfatal("Illegal value pointer for tag %04x in Exif", Tag,0); continue; } ValuePtr = OffsetBase+OffsetVal; if (OffsetVal > ImageInfo.LargestExifOffset){ ImageInfo.LargestExifOffset = OffsetVal; } if (DumpExifMap){ printf("Map: %05u-%05u: 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 >= TAG_TABLE_SIZE){ printf("%s Unknown Tag %04x Value = ", IndentString, Tag); break; } if (TagTable[a].Tag == Tag){ printf("%s %s = ",IndentString, 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) { int NoPrint = 0; printf("\""); for (a=0;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 in Exif. This is nuts", MAX_DATE_COPIES, 0); break; } ImageInfo.DateTimeOffsets[ImageInfo.numDateTimeTags++] = (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.CommentWidthchars = ByteCount/2; } break; case TAG_USERCOMMENT: 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] == ' '){ (ValuePtr)[a] = '\0'; }else{ break; } if (a == 0) break; } // Copy the comment { int msiz = ExifLength - (ValuePtr-OffsetBase); if (msiz > ByteCount) msiz = ByteCount; if (msiz > MAX_COMMENT_SIZE-1) msiz = MAX_COMMENT_SIZE-1; if (msiz > 5 && memcmp(ValuePtr, "ASCII",5) == 0){ for (a=5;a<10 && a < msiz;a++){ int c = (ValuePtr)[a]; if (c != '\0' && c != ' '){ strncpy(ImageInfo.Comments, (char *)ValuePtr+a, msiz-a); break; } } }else{ strncpy(ImageInfo.Comments, (char *)ValuePtr, msiz); } } 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 strange. ErrNonfatal("More than two orientation in Exif",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 in Exif", ImageInfo.Orientation, 0); } NumOrientations += 1; break; case TAG_PIXEL_Y_DIMENSION: case TAG_PIXEL_X_DIMENSION: // 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_FOCAL_PLANE_XRES: FocalplaneXRes = ConvertAnyFormat(ValuePtr, Format); break; case TAG_FOCAL_PLANE_UNITS: 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); 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 in Exif",0,0); }else{ ProcessGpsInfo(SubdirStart, 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; case TAG_DISTANCE_RANGE: // Three possible standard values: // 1 = macro, 2 = close, 3 = distant ImageInfo.DistanceRange = (int)ConvertAnyFormat(ValuePtr, Format); break; case TAG_X_RESOLUTION: if (NestingLevel==0) {// Only use the values from the top level directory ImageInfo.xResolution = (float)ConvertAnyFormat(ValuePtr,Format); // if yResolution has not been set, use the value of xResolution if (ImageInfo.yResolution == 0.0) ImageInfo.yResolution = ImageInfo.xResolution; } break; case TAG_Y_RESOLUTION: if (NestingLevel==0) {// Only use the values from the top level directory ImageInfo.yResolution = (float)ConvertAnyFormat(ValuePtr,Format); // if xResolution has not been set, use the value of yResolution if (ImageInfo.xResolution == 0.0) ImageInfo.xResolution = ImageInfo.yResolution; } break; case TAG_RESOLUTION_UNIT: if (NestingLevel==0) {// Only use the values from the top level directory ImageInfo.ResolutionUnit = (int) 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; int Offset; if (DIR_ENTRY_ADDR(DirStart, NumDirEntries) + 4 <= OffsetBase+ExifLength){ Offset = Get32u(DirStart+2+12*NumDirEntries); if (Offset){ SubdirStart = OffsetBase + Offset; if (SubdirStart > OffsetBase+ExifLength || SubdirStart < OffsetBase){ 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 in Exif header",0,0); } }else{ if (SubdirStart+2 <= 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: %u bytes\n",ThumbnailSize); } } } } void Clear_EXIF () { FocalplaneXRes = 0; FocalplaneUnits = 0; ExifImageWidth = 0; NumOrientations = 0; MotorolaOrder = 0; OrientationPtr[0] = OrientationPtr[1] = NULL; OrientationNumFormat[0] = OrientationNumFormat[1] = 0; } //-------------------------------------------------------------------------- // Process a EXIF marker // Describes all the drivel that most digital cameras include... //-------------------------------------------------------------------------- void process_EXIF (unsigned char * ExifSection, int length) { int FirstOffset; Clear_EXIF(); if (ShowTags){ printf("Exif header %u 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 = (int)Get32u(ExifSection+12); if (FirstOffset < 8 || FirstOffset > 16){ if (FirstOffset < 16 || FirstOffset > length-16 || length < 16){ ErrNonfatal("invalid offset for first Exif IFD value",0,0); return; } // Usually set to 8, but other values valid too. ErrNonfatal("Suspicious offset of first Exif IFD value",0,0); } 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; if (DumpExifMap){ int a,b; printf("Map: %05d- End of exif\n",length-8); for (a=0;a> 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. // Returns NULL if no orietnation tag exists. //-------------------------------------------------------------------------- const char * ClearOrientation(void) { int a; if (NumOrientations == 0) return NULL; for (a=0;a= 1 && ImageInfo.Orientation <= 8){ return OrientTab[ImageInfo.Orientation]; }else{ return ""; } } //-------------------------------------------------------------------------- // 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. timeptr->tm_sec = 0; 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 >= 5){ if (timeptr->tm_year <= 12 && timeptr->tm_mday > 2000 && ExifTime[2] == '.'){ // LG Electronics VX-9700 seems to encode the date as 'MM.DD.YYYY HH:MM' // can't these people read the standard? At least they left enough room // in the header to put an Exif format date in there. int tmp; tmp = timeptr->tm_year; timeptr->tm_year = timeptr->tm_mday; timeptr->tm_mday = timeptr->tm_mon; timeptr->tm_mon = tmp; } // Accept five or six parameters. Some cameras do not store seconds. 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 && ImageInfo.Orientation <=8){ // 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 > 0){ // 05-jan-2001 vcs printf("Metering Mode: "); switch(ImageInfo.MeteringMode) { case 1: printf("average\n"); break; case 2: printf("center weight\n"); break; case 3: printf("spot\n"); break; case 4: printf("multi spot\n"); break; case 5: printf("pattern\n"); break; case 6: printf("partial\n"); break; case 255: printf("other\n"); break; default: printf("unknown (%d)\n",ImageInfo.MeteringMode); 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.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 // show it if its something else, like 'progressive' (used on web sometimes) unsigned a; for (a=0;;a++){ if (a >= 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); } if (ImageInfo.QualityGuess){ printf("JPEG Quality : %d\n", ImageInfo.QualityGuess); } // Print the comment. Print 'Comment:' for each new line of comment. if (ImageInfo.Comments[0]){ int a,c; printf("Comment : "); if (!ImageInfo.CommentWidthchars){ for (a=0;a= 0 && ImageInfo.FlashUsed & 1){ printf(" (flash)"); } if (ImageInfo.IsColor == 0){ printf(" (bw)"); } printf("\n"); } jhead-3.04/make.bat0000555000175000017500000000002713565770126013774 0ustar mwandelmwandelnmake -f makefile-win32jhead-3.04/jpgfile.c0000444000175000017500000006037113565770126014160 0ustar mwandelmwandel//-------------------------------------------------------------------------- // 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 "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 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_SIZE+1]; int nch; int a; nch = 0; if (length > MAX_COMMENT_SIZE) length = MAX_COMMENT_SIZE; // Truncate if it won't fit in our structure. for (a=2;a= 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); ImageInfo.CommentWidthchars = 0; } //-------------------------------------------------------------------------- // 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. //-------------------------------------------------------------------------- static 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; } ImageInfo.JfifHeader.XDensity = ImageInfo.JfifHeader.YDensity = 300; ImageInfo.JfifHeader.ResolutionUnits = 1; for(;;){ int itemlen; int prev; int marker = 0; int ll,lh, got; uchar * Data; CheckSectionsAllocated(); prev = 0; for (a=0;;a++){ marker = fgetc(infile); if (marker != 0xff && prev == 0xff) break; if (marker == EOF){ ErrFatal("Unexpected end of file"); } prev = marker; } if (a > 10){ ErrNonfatal("Extraneous %d padding bytes before section %02X",a-1,marker); } Sections[SectionsRead].Type = marker; // Read the length of the section. lh = fgetc(infile); ll = fgetc(infile); if (lh == EOF || ll == EOF){ ErrFatal("Unexpected end of file"); } itemlen = (lh << 8) | ll; if (itemlen < 2){ ErrFatal("invalid marker"); } Sections[SectionsRead].Size = itemlen; Data = (uchar *)malloc(itemlen); if (Data == NULL){ ErrFatal("Could not allocate memory"); } 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?"); } SectionsRead += 1; 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"); } got = fread(Data, 1, size, infile); if (got != size){ ErrFatal("could not read the rest of the image"); } CheckSectionsAllocated(); Sections[SectionsRead].Data = Data; Sections[SectionsRead].Size = size; Sections[SectionsRead].Type = PSEUDO_IMAGE_MARKER; SectionsRead ++; HaveAll = 1; } return TRUE; case M_DQT: // Use for jpeg quality guessing process_DQT(Data, itemlen); break; case M_DHT: // Use for jpeg quality guessing process_DHT(Data, itemlen); break; 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. if (itemlen < 16){ fprintf(stderr,"Jfif header too short\n"); goto ignore; } if (memcmp(Data+2, "JFIF\0",5)){ fprintf(stderr,"Header missing JFIF marker\n"); } ImageInfo.JfifHeader.Present = TRUE; ImageInfo.JfifHeader.ResolutionUnits = Data[9]; ImageInfo.JfifHeader.XDensity = (Data[10]<<8) | Data[11]; ImageInfo.JfifHeader.YDensity = (Data[12]<<8) | Data[13]; if (ShowTags){ printf("JFIF SOI marker: Units: %d ",ImageInfo.JfifHeader.ResolutionUnits); switch(ImageInfo.JfifHeader.ResolutionUnits){ case 0: printf("(aspect ratio)"); break; case 1: printf("(dots per inch)"); break; case 2: printf("(dots per cm)"); break; default: printf("(unknown)"); break; } printf(" X-density=%d Y-density=%d\n",ImageInfo.JfifHeader.XDensity, ImageInfo.JfifHeader.YDensity); if (Data[14] || Data[15]){ fprintf(stderr,"Ignoring jfif header thumbnail\n"); } } ignore: free(Sections[--SectionsRead].Data); break; case M_EXIF: // 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 contains 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: if (ReadMode & READ_METADATA){ if (ShowTags){ printf("Image contains 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: if (itemlen < 8){ fprintf(stderr,"Section too short\n"); break; } 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;aData+ImageInfo.ThumbnailOffset+8; fwrite(ThumbnailPointer, ImageInfo.ThumbnailSize ,1, ThumbnailFile); fclose(ThumbnailFile); return TRUE; }else{ ErrFatal("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){ 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"); return FALSE; } if (ThumbFileName){ ThumbnailFile = fopen(ThumbFileName,"rb"); if (ThumbnailFile == NULL){ noread: ErrFatal("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"); } }else{ if (ImageInfo.ThumbnailSize == 0){ return FALSE; } 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){ if (fread(ThumbnailPointer, 1, ThumbLen, ThumbnailFile) != ThumbLen){ goto noread; } 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; return TRUE; } //-------------------------------------------------------------------------- // Discard everything but the exif and comment sections. //-------------------------------------------------------------------------- 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> 8); JfifHead[13] = (uchar)ImageInfo.JfifHeader.XDensity; JfifHead[14] = (uchar)(ImageInfo.JfifHeader.YDensity >> 8); JfifHead[15] = (uchar)ImageInfo.JfifHeader.YDensity; fwrite(JfifHead, 18, 1, outfile); // use the values from the exif data for the jfif header, if we have found values if (ImageInfo.ResolutionUnit != 0) { // JFIF.ResolutionUnit is {1,2}, EXIF.ResolutionUnit is {2,3} JfifHead[11] = (uchar)ImageInfo.ResolutionUnit - 1; } if (ImageInfo.xResolution > 0.0 && ImageInfo.yResolution > 0.0) { JfifHead[12] = (uchar)((int)ImageInfo.xResolution>>8); JfifHead[13] = (uchar)((int)ImageInfo.xResolution); JfifHead[14] = (uchar)((int)ImageInfo.yResolution>>8); JfifHead[15] = (uchar)((int)ImageInfo.yResolution); } } // Write all the misc sections for (a=0;aNewIndex;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; } jhead-3.04/iptc.c0000444000175000017500000002114113565770126013467 0ustar mwandelmwandel//-------------------------------------------------------------------------- // Process IPTC data and XMP data. //-------------------------------------------------------------------------- #include "jhead.h" // 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 #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 #define IPTC_TIME_CREATED 0x3C #define IPTC_SUB_LOCATION 0x5C #define IPTC_IMAGE_TYPE 0x82 //-------------------------------------------------------------------------- // 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; unsigned char headerLen = 0; unsigned char dataLen = 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 while (memcmp(pos, IptcSig3, sizeof(IptcSig3)) != 0) { // loop on valid Photoshop blocks pos += sizeof(IptcSig3); // move data pointer to the Header Length // Skip header headerLen = *pos; // get header length and move data pointer to the next field pos += (headerLen & 0xfe) + 2; // move data pointer to the next field (Header is padded to even length, counting the length byte) pos += 3; // move data pointer to length, assume only one byte, TODO: use all 4 bytes dataLen = *pos++; pos += dataLen; // skip data section if (memcmp(pos, IptcSig2, sizeof(IptcSig2) - 1) != 0) { badsig: if (ShowTags) { ErrNonfatal("IPTC type signature mismatch\n", 0, 0); } return; } pos += sizeof(IptcSig2) - 1; // move data pointer to the next field } 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 += 4; // move data pointer to the next field printf("======= IPTC data: =======\n"); // Now read IPTC data while (pos < (Data + itemlen-5)) { short signature; unsigned char type = 0; short length = 0; const char * description = NULL; if (pos+5 > maxpos) goto corrupt; signature = (*pos << 8) + (*(pos+1)); pos += 2; if (signature != 0x1C01 && signature != 0x1c02) break; type = *pos++; length = (*pos << 8) + (*(pos+1)); if (length < 1) goto corrupt; 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_CATEGORY: description = "Category"; break; case IPTC_BYLINE: description = "Byline"; 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 = "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 = "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: %d\n", type ); } break; } if (description != NULL) { char TempBuf[32]; memset(TempBuf, 0, sizeof(TempBuf)); memset(TempBuf, ' ', 14); 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); } //-------------------------------------------------------------------------- // 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= 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; } } } jhead-3.04/changes.txt0000444000175000017500000002633013565770126014542 0ustar mwandelmwandel**************************************************** ***** Detailed change log - since version 1.2 ***** ***** In cronological order, oldest to newest ***** **************************************************** Jun 3 2001: Added -dc option for deleting all comments Jun 5 2001: No longer clip comment length at 200 characters on display Jul 16 2001 Follow TIFF link correctly Save thumbnail option added (Thanks Michal D Hughes for his help mdh(a)logcabin.woods.bridge.com) Aug 8 & 9 2001 Transfer exif header option added. Moved relative path & discard all but jpeg stuff in separate functions Changed ISO heuristic to deal with ISo < 80 better (Thanks Harry TsaiHarryTsai(a)alum.mit.edu) Agust 12 2001 Testing under Linux, minor fixups. -------Released version 1.3------- August 26 2001 Fixed problem where thumbnails in big endian files were not saved right. (thanks Joe Steele [joe(a)madewell.com]) Sept 1 2001 Added command line option to remove exif section entirely. Added time / time-zone adjust option. Sept 9 2001 Avoid renaming file with right name again with -n option Change name of SetFileTime variable to not conflict with windows. Oct 9 2001 Added option to set exif timestamp to absolute time Added option to save thumbnail to stdout (unix only) (thanks Nathan Schmidt [mailto:nathan(a)cs.stanford.edu]) Fixed bug in parsing truncated exif headers (thanks Joachim.Geyer(a)djh-freeweb.de) Got rid of strnprintf (not avilable on FreeBSD 4.4) -------Released version 1.4------- Oct 10 2001 More improved handling of truncated exif headers - as may be produced by -dt option in older versions of this program. Oct 18 2001 Fixed bug in -ts option where '-' causes scanf to treat parms as negative. (thanks Pete Ashdon [mailto:pashdown(a)xmission.com]) Oct 25 2001 Added -ce option -------Released version 1.5------- Dec 26 2001 Added -V (version) option Added -exonly (exif only) option Jan 2 2002: Fixed lots of typos (Thanks, David Baker [mailto:dave(a)dsb3.com]) Jan 12: 2002 Use EDITOR environment variable to pick which editor (Instead of notpead or VI) Jan 13: 2002 Improved thumbnail deletion feature - now not just shortens the header, but also removes pointers to the thumbnail form the exif header. -------Released version 1.6------- Jan 29 2002 Use adjusted date instead of original date when -ta is used in combination with -ft or -n options Feb 25 2002 Added image orientation display to summary April 22 2002 Changed 35mm equivalent focal calculation to use 36mm instead of 35mm for 35mm negative width (35 mm negative frames are 36 mm wide) April 28 2002 Split jhead.c int jhead.c and jpgfile.c. Jpgfile.c contains jpeg manipulation code that is reusable outside of jhead, while jhead.c contains code specific to jhead (command line parsing, display) May 11 2002 Fix bug in -dt option that rears its ugly head if the directories are in the exif header out of order. -------Released version 1.7------- June 3 2002 Ignore undefined bits of "flash used" tag, as cannon sets them nonzero, causing jhead to indicate flash used when it wasn't with some Canon models. Jul 7 2002 -------Released version 1.8------- Sept 8 2002 makehtml now also lists .avi files as video clips Sept 11 2002 Handle first IFD offset != 8, as comes out of Pentax Optio 230 camera. Sept 13 2002 Show 4 digits of past decimal if exposrure time is less than 0.01 sec. Sept 25 2002 Integrate patch from James R Van Zandt to allow inclusion of original name when '%f' is part of rename format string. Dec 11 2002 -------Released version 1.9------- Oct 8 2002 Minor changes where newlines are printed. Added check to warn about wiping out the originals with the -st option. Oct 9 2002 Fixed display of "flash used=no" for exif headers generated by photoshop. Oct 13 2002 Added -ci and -cs options. March 3 2003 Limit directory recursion depth to avoid crashing on circularly linked directories within the Exif header. April 6 2003 Added automatic rottion (-autorotate) to right-up images that contain a rotation tag from the camera. *Finally* wrote a nice MAN page for jhead. -------Released version 2.0 -- April 2003 ------- Dec ?? 2003 Set all copies of the date to same value when setting or modifying. (I intentionally only set one, but too may people considered this a bug) Dec 28 2003 fixed unix makefile Dec 29 2003 added -cl (insert comment literal) option Jan 8 2004 Added -norot (zero out rotation tag) option -------Released version 2.1 -- Jan 2004 ------- Jan 12 Added handling of explicit 35mm equivalent tag fixed inconsistency in computing 35 mm equivalent focal lengths between concise and regular output. Jan 17 Impelemented optoin to supress file date display, for regression tests. Feb 1 Better indentatin of verbose option, rudementary canon maker not parsing March Various spelling errors fixed in output strings, and jpeg --> JPEG, exif --> Exif April 13 Use '-outfile' command line option of jpegtran when launching jpegtran to do rotation so that syntax of launched command is same on Windows and Unix. April 19 Various spelling fixes in manapge. Jun 20 Added ability to do sequencial renaming ('%i' in format string for -n option) -------Released version 2.2 -- Jun 2004 ---------- Handle some oddities - like '/' separators in date fields, or images with an orientation tag for the thumbnail. Increase maximum number of jpeg sections to 40 from 20 Dec 3 Don't try to write to readonly files. Use changed copy of date/time for rename and file time set operations Dec 25 Added -purejpg and -du options Dec 28 More details on flash usage. Show digital zoom ratio Don't show jpeg process if it's baseline (almost always is) -------Released version 2.3 -- Jan 2005 ---------- Jan 14 2005 Display GPS info if included in image Feb 27 2004 Fix some time reference confusion bugs relating to -ta option May 29 2005 Added -da option for easier adjusting of date/time by large amounts. -------Released version 2.4 -- May 2005 ---------- Jun 06 2005 Fix -da option -------Released version 2.4-1 -- Jun 09 2005 -------- Jun 10 2005 Removed some debug printf I accidentally left in! -------Released version 2.4-2 -- Jun 10 2005 -------- August 8 2005 Avoid duplicating exif header on some commands Sept 11 2005 Fix up return codes. Oct 8 2005 Preserve file permissions and time when files are modified. Oct 29 2005 Read ISO euqivalnt and white balance from canon makernote Nov 5 2005 Added -rt (replace thumbnail) feature, and rotate the thumbnail also when using the -autorot feature Dec 28 2005 Added -rgt (regenerate thumbnail) feature. Added -orp and -orl options -------Released version 2.5 -- Jan 8 2006 -------- Jan 28 2006 Fix typecast issue run itno with GCC 4 Feb 17 2006 Fix shutter speed display in '-c' mode for very long shutter speeds Feb 26 2006 Fix some nitpicks from Debian folks Mar 6 2006 Fix a bug in autorot when rotating filenames with spaces in them. April 2 2006 Improved handling of corrupt exif linkages in exif header April 3 2006 Added -a (rename associated files) options -------Released version 2.6 -- April 29 2006 -------- Sept 9 2006 Remove maximum jpeg sections limit Sept 10 2006 Added -ds option Oct 18 2006 On clearing rotation, clear the image and the optinoal thumbnail rotation tags. (some viewers use the wrong tag) Dec 29 2006 Add -mkexif option to make a new exif header. -------Released version 2.7 -- Jan 11 2007 -------- Feb 10 2007 Added IPTC handling Feb 11 2007 Added -q option Feb 17 2007 Fix handling of corrupted GPS directory. Feb 18 2007 Extract focus distance from canon makernote. Jun 3 2007 Extract subject range (pentax and fuji cameras) -------Released version 2.8 -- Nov 13 2007 -------- Feb 14 2008 Fix it so it no longer deletex XMP sections Improve IPTC handling a little March 3 2008 Change how date is encoded with -mkexif section to make it more compatible. Make jhead aware of XMP data and not delete it. -------Released version 2.82 -- Apr 03 2008 -------- May 8 2008 Decode more exif tags for '-v' mode. Sep 23 2008 Fix a bunch of potential string overflows Oct 1 2008 Fix bug where IPTC sction was not deleted by -purejpg Fix GPS altitude decode bug -------Released version 2.84 -- Oct 4 2008 -------- Jan 15 2008 Fix bug with -ce introduced as a result of putting all the security checks the debian people wanted. Feb 2 2008 Added the ability to move files with the -n option. (makes directories if necessary) Various minor typo and documentation fixes. -------Released version 2.86 -- Feb 14 2009 -------- Fixed an #ifdef that I had defined the wrong way, causing the -ce option to fail. -------Released version 2.87 -- Mar 03 2009 -------- May 2009: A few more tags recognized with jhead -v Accept strange date encoding of LG VX-9700 Fix metering mode display Fix crash bug on corrupted jpeg Deal better with extra padding bytes between jpeg markers Nov 3 2009: Now preserve resoltuion units of jfif header, or set them with info from exif header. -------Released version 2.88 -- Nov 6 2009 -------- Dec 16 2009: Handle slightly different signature in IPTC as well. Jan 25 2010: Handle mixted-endian-ness files from newer Canon point ant shoot cameras. Jan 26 2010: More handling of IPTC variants. -------Released version 2.90 -- Feb 5 2010 -------- May 7 2010: Fix a compiler warning Dec 29 2010: Make -n behave like -nf, and get rid of that option. Dec 2 2011: Handle IPTC in UTF8 (as per photoshop CS5) -------Released version 2.93 -- Dec 3 2011 -------- Jan 24 2011: Fixed bug in jhead -cmd that caused metatdata to be deleted. -------Released version 2.94 -- Jan 24 2012 -------- Mar 3 2011: Handle very large unsigned rational numbers in exif header -------Released version 2.95 -- Mar 16 2012 -------- Jun 18 2012: Fix printing file info when -ft option is used Do not skip readonle files with -st option -------Released version 2.96 -- Jun 22 2012 -------- Jul 9 2012: Make it compile clean with visual studio 10 Jul 28 2012: Various cleanups from debian folks. Oct 19 2012: Add feature to show quality of jpeg, (by Andy Spiegel) Dec 27 2012: Fix crash on some corrupt files bug, clarify time adjustment syntax in help -------Released version 2.97 -- Jan 30 2013 -------- Jun 10 2013: Make max comment size 16000 Oct 25 2013: Added "-zt" option to trim 32k of trailing zeroes from Nikon 1 J2 and J3 images. Sep 28 2014: Add ability to reset invalid rotation tag (from Moultrie game cameras) -------Released version 3.0 -- Feb 2 2015 -------- Ma4 5 2015: Add option to set exif date from date from another file. Jul 28 2015: Remove some unnecessary warnings with some types of GPS data Aug 4 2015: Remove multiple copies of the same type of section when deleting section types Aug 11 2011: Bug fixes relating to fuzz testing. Aug 1 2017: Fix bug when no orientation tag is present Aug 12 2018: Fix bug of not clearing exif information when processing images with an without exif data in one invocation. -------Released version 3.02 -- Dec 11 2018 -------- Dec 21 2018: Fix bug where thumbnail replacement DID NOT WORK. (broke while fixing compiler warnings for 3.02 release) -------Released version 3.03 -- Dec 31 2018 -------- Nov 20 2019: Apply a whole bunch of patches from Debian. Spell check and fuzz test stuff from Debian, nothing useful to human users. -------Released version 3.04 -- Nov 22 2019 -------- jhead-3.04/usage.html0000444000175000017500000005654113565770126014372 0ustar mwandelmwandel

Jhead is a command line driven program for manipulating the non-image parts of Exif flavour JPEG files that most digital cameras produce.

Windows / Mac users: Jhead has no Graphical User Interface. Clicking on it with the mouse from Windows or Mac OS-X won't do anything for you - you have to use it from the Command prompt

Jhead v3.03 program Features

  • Extracting camera settings from Exif image files
  • Able to set and/or adjust the Exif time field
  • Manipulation (extract, replace, regenerate) of Exif integral thumbnails
  • Transplant Exif image header from one JPEG to another
  • Edit JPEG comment fields
  • Automatically rotate images upright (using jpegtran) according to "orientation" tag.
  • Manage running programs on large batches of Jpegs and restoring Exif header information afterwards.
  • Display embedded GPS info (if present)

General metadata options

-te <name> Transplant Exif header from image <name> into specified image. This option is useful if you like to edit the photos but still want the Exif header on your photos. As most photo editing programs will wipe out the Exif header, this option can be used to re-transplant them back in after editing the photos.
This feature has an interesting 'relative path' option for specifying the thumbnail name. Whenever the <name> contains the characters '&i', jhead will substitute the original filename for this name. This allows creating a 'relative name' when doing a whole batch of files. For example, the incantation:
    jhead -te "originals\&i" *.jpg
would transfer the Exif header for each .jpg file in the originals directory by the same name, Both Win32 and most UNIX shells treat the '&' character in a special way, so you have to put quotes around that command line option for the '&' to even be passed to the program.
-dc Delete comment field from the JPEG header. Note that the comment is not part of the Exif header.
-de Delete the Exif header entirely. This leaves other sections (IPTC, XMP, comment) intact
-di Delete IPTC section (if present). Leaves other sections intact.
-dx Delete XMP section (if present). Leaves other sections intact.
-du Delete any sections that jhead doesn't know about. Leaves Exif, XMP, IPTC and comment sections intact.
-purejpg Delete all JPEG sections that aren't necessary for rendering the image. Strips any metadata that various applications may have left in the image. A combination of the -de -dc and -du options.
-mkexif Creates minimal Exif header. Exif header contains date/time, and empty thumbnail fields only. Date/time set to file time by default. use with -rgt option if you want the Exif header to contain a thumbnail. Note that Exif header creation is very limited at this time, and no other fields can be added to the Exif header this way.
-ce Edit the JPEG header comment field (note, this comment field is outside the Exif structure and can be part of Exif and non Exif style JPEG images).
A temporary file containing the comment is created and a text editor is launched to edit the file. The editor is specified in the EDITOR environment variable. If none is specified notepad or vi are used under Windows and UNIX respectively. After the editor exits, the data is transferred back into the image, and the temporary file deleted.
-cs <name> Save comment section to a file
-ci <name> Replace comment with text from file.
-cl <comment> Replace comment with comment from command line.

Date / Time manipulation options

-ft Sets the file's system time stamp to what is stored in the Exif header.
-dsft Sets the Exif timestamp to the file's timestamp. Requires an Exif header to pre-exist. Use -mkexif option to create one if needed.
-n[<fmt-string>] This option causes files to be renamed and/or moved according to the Exif header "DateTimeOriginal" field. If the file is not an Exif file, or the DateTimeOriginal does not contain a valid value, the file date is used.

If the name includes '/' or '\' (under windows), this is interpreted as a new path for the file. If the new path does not exist, the path will be created.

If the [fmt-string] is omitted, the file will be renamed to MMDD-HHMMSS.
If a [fmt-string] is provided, the fmt-string will be passed to the strftime function for formatting. In addition, if the format string contains '%f', this will substitute the original name of the file (minus extension).
A sequence number may also be included by including '%i' in the format string. Leading zeros can be specified. '%03i' for example will pad the numbers to '001', '002'... this works just like printf in C, but with '%i' instead of '%d'.
If the target name already exists, the name will be appended with "a", "b", "c", etc, unless the name ends with a letter, in which case it will be appended with "0", "1", "2", etc.
This feature is especially useful if more than one digital camera was used to take pictures of an event. By renaming them to a scheme according to date, they will automatically appear in order of taking when viewed with some sort of viewer like Xnview or AcdSee, and sorted by name. Or you could use the -ft option and view the images sorted by date. Typically, one of the camera's date will be set not quite right, in which case you may have to use the -ta or -da options on those files first.

Some of the more useful arguments for strftime are:
%d   Day of month as decimal number (01 – 31)
%HHour in 24-hour format (00 – 23)
%jDay of year as decimal number (001 – 366)
%mMonth as decimal number (01 – 12)
%MMinute as decimal number (00 – 59)
%SSecond as decimal number (00 – 59)
%UWeek of year as decimal number, with Sunday as first day of week (00 – 53)
%wWeekday as decimal number (0 – 6; Sunday is 0)
%yYear without century, as decimal number (00 – 99)
%YYear with century, as decimal number

Example:
    jhead -n%Y%m%d-%H%M%S *.jpg

This will rename files matched by *.jpg according to YYYYMMDD-HHMMSS

Note to Windows batch file users: '%' is used to deliminate macros in Windows batch files. You must use %% to get one % passed to the program. So from a batch file, you would have to write "jhead -n%%Y%%m%%d-%%H%%M%%S *.jpg"

For a full listing of strftime arguments, look up the strftime function. Note that some arguments to the strftime function (not listed here) produce strings with characters such as '/' and ':' that may not be valid as part of a filename on various systems.

-a (Windows only option). Rename files with the same name but different extension as well. This is useful for renaming .AVI files based on Exif file in .THM, or to rename sound annotation files or raw files with jpeg files. Use together with '-n' option.
-ta<timediff> Adjust time stored in the Exif header by h:mm forwards or backwards. Useful when having taken pictures with the wrong time set on the camera, such as after travelling across time zones, or when daylight savings time has changed. This option uses the time from the "DateTimeOriginal" (tag 0x9003) field, but sets all the time fields in the Exif header to the new value.

Examples:
Adjust time one hour forward (you would use this after you forgot to set daylight savings time on the camera)

    jhead -ta+1:00 *.jpg
Adjust time back by 23 seconds (you would use this to get the timestamps from two cameras in sync after you found that they didn't quite align)
    jhead -ta-0:00:23 *.jpg
Adjust time forward by 2 days and 1 hour (49 hours)
    jhead -ta+49 *.jpg
-da<date>-<date> Works like -ta, but for specifying large date offsets, to be used when fixing dates from cameras where the date was set incorrectly, such as having date and time reset by battery removal on some cameras. This feature is best for adjusting dates on pictures taken over a large range of dates. For pictures all taken the same date, the "-ds" option is often easier to use.

Because different months and years have different numbers of days in them, a simple offset for months, days, years would lead to unexpected results at times. The time offset is thus specified as a difference between two dates, so that jhead can figure out exactly how many days the timestamp needs to be adjusted by, including leap years and daylight savings time changes. The dates are specified as yyyy:mm:dd. For sub-day adjustments, a time of day can also be included, by specifying yyyy:nn:dd/hh:mm or yyyy:mm:dd/hh:mm:ss

Examples:
Year on camera was set to 2004 instead of 2005 for pictures taken in April

    jhead -da2005:03:01-2004:03:01
Default camera date is 2002:01:01, and date was reset on 2005:05:29 at 11:21 am
    jhead -da2005:05:29/11:21-2002:01:01
-ts<date-time> Sets the date and time stored in the Exif header to what is specified on the command line. This option changes all the date fields in the Exif header. Time must be specified as:
    yyyy:mm:dd-hh:mm:ss

-tf <filename> Sets the date stored in the Exif header to the modification time from a file. This option changes all the date fields in the Exif header.
-ds<date-time> Sets the date stored in the Exif header to what is specified on the command line. Can be used to set date, just year and month, or just year. Date is specified as:
    yyyy:mm:dd, yyyy:mm, or yyyy

Thumbnail manipulation options

-dt Delete thumbnails from the Exif header, but leave the interesting parts intact. This option truncates the thumbnail from the Exif header, provided that the thumbnail is the last part of the Exif header (which so far as I know is always the case). Exif headers have a built-in thumbnail, which is typically 240x160 and 10k in size. This thumbnail is used by digital cameras. Windows XP, as well as various photo viewing software may also use this thumbnail if present, but work just fine if it isn't.
-st <name> Save the built in thumbnail from Jpegs that came from a digital camera. The thumbnail lives inside the Exif header, and is a very low-res JPEG image. Note that making any changes to a photo, except for with some programs, generally wipes out the Exif header and with it the thumbnail.
I implemented this option because I kept getting asked about having such an option. I don't consider the built in thumbnails to be all that useful - too low res. However, now you can see for yourself. I always generate my thumbnails using ImageMagick (see end of this page).
Like the '-te' option, this feature has the 'relative path' option for specifying the thumbnail name. Whenever the <name> contains the characters '&i', jhead will substitute the original filename for this name. This allows creating a 'relative name' when doing a whole batch of files. For example, the incantation:
    jhead -st "thumbnails\&i" *.jpg
would create a thumbnail for each .jpg file in the thumbnails directory by the same name, (provided that the thumbnails directory exists, of course). Both Win32 and most UNIX shells treat the '&' character in a special way, so you have to put quotes around that command line option for the '&' to even be passed to the program.

If a '-' is specified for the output file, the thumbnail is sent to stdout. (UNIX build only)

-rt <name> Replace thumbnails from the Exif header. This only works if the Exif header already contains an Exif header a thumbnail.
-rgt[size] Regenerate Exif thumbnail. 'size' specifies maximum height or width of thumbnail. I added this option because I had a lot of images that I had rotated with various tools that don't update the Exif header. But newer image browsers such as XnView make use of the Exif thumbnail, and so the thumbnails would be different from the image itself. Note that the rotation tag also needed to be cleared (-norot option).
Typically, only images that are shot in portrait orientation are afflicted with this. You can use the -orp option to tell jhead to only operate on images that are upright.

This option relies on 'mogrify' program (from ImageMagick) to regenerate the thumbnail. Linux users often already have this tool pre-installed. Windows users have to go and download it. This option only works if the image already contains a thumbnail.

Rotation tag manipulation

-autorot Using the 'Orientation' tag of the Exif header, rotate the image so that it is upright. The program 'jpegtran' is used to perform the rotation. This program is present in most Linux distributions. For windows, you need to get a copy of it. After rotation, the orientation tag of the Exif header is set to '1' (normal orientation). The Exif thumbnail is also rotated. Other fields of the Exif header, including dimensions are untouched, but the JPEG height/width are adjusted.
This feature is especially useful with newer digital cameras, which set the orientation field in the Exif header automatically using a built in orientation sensor in the camera.
-norot Clears the Exif header rotation tag without altering the image. You may find that your images have rotation tags in them from your camera, but you already rotated them with some lossless tool without clearing the rotation tag. Now your friendly browser rotates the images on you again because the image rotation tag still indicates the image should be rotated. Use this option to fix this problem. You may also want to regenerate the thumbnail using the -rgt option.

Output verbosity control

-h Displays summary of command line options.
-v Makes the program even more verbose than it already is. Like DOS programs, and unlike UNIX programs, Jhead gives feedback as to what it is doing, even when nothing goes wrong. Windows user that I am, when something doesn't give me feedback for 20 seconds, I assume its crashed.
-q Makes the program not spit out messages on success - more like the "Silence is golden" Unix way. its crashed.
-V Print version info and compilation date.
-exifmap Show a map of the bytes in the Exif header. Useful when analyzing strange Exif headers, not of much use to non software developers.
-se Suppress error messages relating to corrupt Exif header structure.
-c Concise output. This causes picture info to be summarized on one line instead of several. Useful for grep-ing through images, as well as importing into spread sheets (data is space delimited with quotes as text qualifier).

File matching and selection

-model <model> Restricts processing of files to those whose camera model, as indicated by the Exif image information, contains the substring specified in the argument after '-model'. For example, the following command will list only images that are from an S100 camera:

jhead -model S100 *.jpg

I use this option to restrict my JPEG re-compressing to those images that came from my Cannon S100 camera, (see the -cmd option).

-quality <nn> Restricts processing of files to those whose estimated quality factor is equal to or higher than the specified number nn.
-exonly Skip all files that don't have an Exif header. This skips all files that did not come directly from the digital camera, as most photo editing software does not preserve the Exif header when saving pictures.
-cmd<command> Executes the specified command on each Jpeg file to be processed.

The Exif section of each file is read before running the command, and re-inserted after the command finishes.

This is useful for using Jhead's file globbing capability for processing a whole directory tree of files.

It's also useful for restoring the exif header after operations that wipe out the Exif metadata. Most programs today however will keep the Exif metadata intact, so this is less important than it used to be.

-orp, -orl Operate only on images with portrait (-orp) or landscape (-orl) aspect ratio.
Please note that this is solely based on jpeg width and height values. Some browsers may auto rotate the image on displaying it based on the Exif orientation tag, so that images shot in portrait mode are displayed as portrait. However, the image itself may not be stored in portrait orientation. The -autorot and -norot options are useful for dealing with rotation issues.
-r The recursive feature of version 1.0 never worked to my satisfaction, and I replaced it with my recursive file globbing code in the Windows version. See below.

Bugs and Shortcomings

    After Jhead runs a program to rotate or resize an image, the image dimensions and thumbnail in the Exif header are not adjusted.

    Modifying of Exif header data is very limited, as Jhead internally only has a read only implementation of the file system contained in the Exif header. For the most part, Exif can only modify pre-existing fixed-length fields in the header.

    Most Canon digital SLR cameras fail to adjust the effective sensor resolution when shooting at less than full resolution, causing Jhead to incorrectly miscalculate the sensor width and 35mm equivalent focal length. The same can result from resizing photos with Photoshop, which will manipulate parts of the Exif header. This is often reported as a bug in Jhead, but Jhead can't do much about incorrect data.

Name globbing and recursive directories under Windows

    Name globbing means matching wildcard patterns to actual file names. If you know what this term means, you are probably annoyed at how programs on Windows typically handle this. The Win32 version of this program goes beyond the pattern matching that Windows provides, and allows you to specify fancy UNIX-like patterns such as:

      jhead c:\pix\199*\*\*.jpg

    This program goes one step beyond beyond that in that "**" as a path component means any level of subdirectories. The invocation

      jhead c:\**\*.jpg

    will find ALL Jpegs files on the c: drive, including those in the root directory. The ** only works if it is the only part of that path component. For example, the path 'c:\a**\*.jpg' will not recurse. The '**' recursive expansion is ONLY supported on the Windows version. The code is in the module 'myglob.c', if you want to reuse it (I certainly intend to reuse that code for other applications). Under UNIX, the shell's wildcard expansion is pretty decent already, and dealing with the convoluted nature of some UNIX file layouts, doing better would have been much more of a challenge.

Programs I use with Jhead

ImageMagick
    I use the MOGIRIFY command from ImageMagick to do batch conversions and re-compresses of images. If you use Linux, you probably already have ImageMagick on your system (just type 'mogrify' at the command prompt to see if you have it). For Windows users, you have to download it from: http://www.imagemagick.org

    Image Magick is one of those programs that preserves comment and Exif headers, although older versions do not.

JPEGTRAN
    If you use Linux you probably also already have this program. For windows, it's hard to find a pre-built binary on the web. The Independent JPEG Group's website only has the source code.

    There's a fancier version, with pre-built Windows binaries and a lossless cropping feature added at: http://sylvana.net/jpegcrop.

XnView
    XnView is an excellent, small, fast, and free graphical image browser. It makes use of jpeg thumbnails for the thumbnail view. On account of Xnview, I added options to fix or regenerate the thumbnails to jhead, so that I could regenerate the thumbnails for images where the thumbnail had gotten out of sync with the image.
    Mac and Linux versions of XnView are also available.
wrjpgcom / rdjpgcom
    You can use these programs to write and extract COM markers from JPEG images respectively. Although I always use my jhead program for this feature, the wrjpgcom and rdjpgcom programs are extremely simple and very suitable for use with shell scripts to process lots of images. These programs are part of most Linux distributions as part of the libjpg package (along with jpegtran)


If you are looking for a feature that Jhead doesn't have, try Exiftool. Exiftool is actively maintained and has a LOT of features. A downside of Exiftool is that it's much bigger and slower.

For those who need a programming interface, there are: libexif and exifv2.

Jhead homeage: http://www.sentex.net/~mwandel/jhead
Last Updated: Dec 31 2018

jhead-3.04/gpsinfo.c0000444000175000017500000001626213565770126014205 0ustar mwandelmwandel//-------------------------------------------------------------------------- // Parsing of GPS info from exif header. // // Matthias Wandel, Dec 1999 - Dec 2002 //-------------------------------------------------------------------------- #include "jhead.h" #define MAX_GPS_TAG 0x1e #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 const char * GpsTags[MAX_GPS_TAG+1]= { "VersionID ",//0x00 "LatitudeRef ",//0x01 "Latitude ",//0x02 "LongitudeRef ",//0x03 "Longitude ",//0x04 "AltitudeRef ",//0x05 "Altitude ",//0x06 "TimeStamp ",//0x07 "Satellites ",//0x08 "Status ",//0x09 "MeasureMode ",//0x0A "DOP ",//0x0B "SpeedRef ",//0x0C "Speed ",//0x0D "TrackRef ",//0x0E "Track ",//0x0F "ImgDirectionRef ",//0x10 "ImgDirection ",//0x11 "MapDatum ",//0x12 "DestLatitudeRef ",//0x13 "DestLatitude ",//0x14 "DestLongitudeRef",//0x15 "DestLongitude ",//0x16 "DestBearingRef ",//0x17 "DestBearing ",//0x18 "DestDistanceRef ",//0x19 "DestDistance ",//0x1A "ProcessingMethod",//0x1B "AreaInformation ",//0x1C "DateStamp ",//0x1D "Differential ",//0x1E }; //-------------------------------------------------------------------------- // Process GPS info directory //-------------------------------------------------------------------------- void ProcessGpsInfo(unsigned char * DirStart, 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 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 Exif gps tag %04x", Format, Tag); continue; } ComponentSize = BytesPerFormat[Format]; ByteCount = Components * ComponentSize; if (ByteCount > 4){ unsigned OffsetVal; OffsetVal = Get32u(DirEntry+8); // If its bigger than 4 bytes, the dir entry contains an offset. if (OffsetVal > 0x1000000 || OffsetVal+ByteCount > ExifLength){ // Max exif in jpeg is 64k, so any offset bigger than that is bogus. // Bogus pointer offset and / or bytecount value ErrNonfatal("Illegal value pointer for Exif gps 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]; break; case TAG_GPS_LONG_REF: ImageInfo.GpsLong[0] = ValuePtr[0]; break; case TAG_GPS_LAT: case TAG_GPS_LONG: if (Format != FMT_URATIONAL){ ErrNonfatal("Inappropriate format (%d) for Exif 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 && 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); } snprintf(TempString, sizeof(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); } break; case TAG_GPS_ALT_REF: ImageInfo.GpsAlt[0] = (char)(ValuePtr[0] ? '-' : ' '); break; case TAG_GPS_ALT: snprintf(ImageInfo.GpsAlt+1, sizeof(ImageInfo.GpsAlt)-1, "%.2fm", ConvertAnyFormat(ValuePtr, Format)); break; } if (ShowTags){ // Show tag value. if (Tag < MAX_GPS_TAG){ printf(" GPS%s =", GpsTags[Tag]); }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= 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"); } } } } jhead-3.04/jhead.10000444000175000017500000003637413565770126013537 0ustar mwandelmwandel.TH JHEAD 1 "22 Nov 2019" "jhead 3.04" .SH NAME jhead \- Digicam JPEG Exif header manipulation tool .SH SYNOPSIS .B jhead [ .I options ] [ .I file\.\.\. ] .LP .SH DESCRIPTION .LP .B jhead is used to display and manipulate data contained in the Exif header of JPEG images from digital cameras. By default, jhead displays the more useful camera settings from the file in a user-friendly format. .PP .B jhead can also be used to manipulate some aspects of the image relating to JPEG and Exif headers, such as changing the internal timestamps, removing the thumbnail, or transferring Exif headers back into edited images after graphical editors deleted the Exif header. .B jhead can also be used to launch other programs, similar in style to the UNIX .B find command, but much simpler. .SH GENERAL METADATA OPTIONS .TP .BI \-\^te \ file Transplant Exif header from a JPEG (with Exif header) in .I file into the image that is manipulated. This option is useful if you like to edit the photos but still want the Exif header on your photos. As most photo editing programs will wipe out the Exif header, this option can be used to re-copy them back from original copies after editing the photos. This feature has an interesting 'relative path' option for specifying the thumbnail name. Whenever the contains the characters '&i', will substitute the original filename for this name. This allows creating a jhead 'relative name' when doing a whole batch of files. For example, the incantation: .I jhead \-te """originals/&i"" *.jpg would transfer the exif header for each .jpg file in the originals directory by the same name, Both Win32 and most Unix shells treat the '&' character in a special way, so you have to put quotes around that command line option for the '&' to even be passed to the program. .TP .B \-dc Delete comment field from the JPEG header. Note that the comment is not part of the Exif header. .TP .B \-de Delete the Exif header entirely. Leaves other metadata sections intact. .TP .B \-di Delete the IPTC section, if present. Leaves other metadata sections intact. .TP .B \-dx Delete the XMP section, if present. Leaves other metadata sections intact. .TP .B \-du Delete sections of jpeg that are not Exif, not comment, and otherwise not contributing to the image either - such as data that photoshop might leave in the image. .TP .B \-purejpg Delete all JPEG sections that aren't necessary for rendering the image. Strips any metadata that various applications may have left in the image. A combination of the \-de \-dc and \-du options. .TP .B \-mkexif Creates minimal exif header. Exif header contains date/time, and empty thumbnail fields only. Date/time set to file time by default. Use with \-rgt option if you want the exif header to contain a thumbnail. Note that exif header creation is very limited at this time, and no other fields can be added to the exif header this way. .TP .B \-ce Edit the JPEG header comment field (note, this comment field is outside the Exif structure and can be part of Exif and non Exif style JPEG images). A temporary file containing the comment is created and a text editor is launched to edit the file. The editor is specified in the EDITOR environment variable. If none is specified notepad or vi are used under Windows and Unix respectively. After the editor exits, the data is transferred back into the image, and the temporary file deleted. .TP .BI \-\^cs \ file Save comment section to a .I file .TP .BI \-\^ci \ file Replace comment with text from .I file .TP .BI \-\^cl \ string Replace comment with specified string from command line .SH DATE / TIME MANIPULATION OPTIONS .TP .B \-ft Sets the file's system time stamp to what is stored in the Exif header. .TP .B \-dsft Sets the Exif timestamp to the file's timestamp. Requires an Exif header to pre-exist. Use \-mkexif option to create one if needed. .TP .BI \-\^n [format_string] This option causes files to be renamed and/ or mmoved using the date information from the Exif header "DateTimeOriginal" field. If the file is not an Exif file, or the DateTimeOriginal does not contain a valid value, the file date is used. If the new name contains a '/', this will be interpreted as a new path, and the file will be moved accordingly. If the .I format_string is omitted, the file will be renamed to MMDD-HHMMSS. Note that this scheme doesn't include the year (I never have photos from different years together anyway). If a .I format_string is provided, it will be passed to the strftime function as the format string. In addition, if the format string contains '%f', this will substitute the original name of the file (minus extension). '%i' will substitute a sequence number. Leading zeros can be specified like with printf - i.e. '%04i' pads the number to 4 digits using leading zeros. If the name includes '/', this is interpreted as a new path for the file. If the new path does not exist, the path will be created. If the target name already exists, the name will be appended with "a", "b", "c", etc, unless the name ends with a letter, in which case it will be appended with "0", "1", "2", etc. This feature is especially useful if more than one digital camera was used to take pictures of an event. By renaming them to a scheme according to date, they will automatically appear in order of taking in most directory listings and image browsers. Alternatively, if your image browser supports listing by file time, you can use the \-ft option to set the file time to the time the photo was taken. Some of the more useful arguments for strftime are: .BR %H \ Hour\ in\ 24-hour\ format\ (00\ -\ 23) .br .BR %j \ Day\ of\ year\ as\ decimal\ number\ (001\ -\ 366) .br .BR %m \ Month\ as\ decimal\ number\ (01\ -\ 12) .br .BR %M \ Minute\ as\ decimal\ number\ (00\ -\ 59) .br .BR %S \ Second\ as\ decimal\ number\ (00\ -\ 59) .br .BR %w \ Weekday\ as\ decimal\ number\ (0\ -\ 6;\ Sunday\ is\ 0) .br .BR %y \ Year\ without\ century,\ as\ decimal\ number\ (00\ -\ 99) .br .BR %Y \ Year\ with\ century,\ as\ decimal\ number Example: .I jhead \-n%Y%m%d\-%H%M%S *.jpg This will rename files matched by *.jpg in the format YYYYMMDD\-HHMMSS For a full listing of strftime arguments, look up the strftime in them man pages. Note that some arguments to the strftime function (not listed here) produce strings with characters such as ':' that may not be valid as part of a filename on some systems. .TP .B \-ta<+|\-> Adjust time stored in the Exif header by h:mm forwards or backwards. Useful when having taken pictures with the wrong time set on the camera, such as after travelling across time zones, or when daylight savings time has changed. Examples: Add 1 hourand 5 minutes to the time .br jhead \-ta+1:05 Decrease time by one second: .br jhead \-ta-0:0:1 This option changes all Date/time fields in the exif header, including "DateTimeOriginal" (tag 0x9003) and "DateTimeDigitized" (tag 0x9004). .TP .B \-da\- Works like \-ta, but for specifying large date offsets, to be used when fixing dates from cameras where the date was set incorrectly, such as having date and time reset by battery removal on some cameras Because different months and years have different numbers of days in them, a simple offset for months, days, years would lead to unexpected results at times. The time offset is thus specified as a difference between two dates, so that jhead can figure out exactly how many days the timestamp needs to be adjusted by, including leap years and daylight savings time changes. The dates are specified as yyyy:mm:dd. For sub-day adjustments, a time of day can also be included, by specifying yyyy:nn:dd/hh:mm or yyyy:mm:dd/hh:mm:ss Examples: Year on camera was set to 2005 instead of 2004 for pictures taken in April .br jhead \-da2004:03:01\-2005:03:01 Default camera date is 2002:01:01, and date was reset on 2005:05:29 at 11:21 am .br jhead \-da2005:05:29/11:21\-2002:01:01 .TP .B \-ts Sets the time stored in the Exif header to what is specified on the command line. Time must be specified as: .I yyyy:mm:dd\-hh:mm:ss .TP .B \-ds Sets the date stored in the Exif header to what is specified on the command line. Can be used to set date, just year and month, or just year. Date is specified as: .I yyyy:mm:dd, yyyy:mm, or yyyy .SH THUMBNAIL MANIPULATION OPTIONS .TP .B \-dt Delete thumbnails from the Exif header, but leave the interesting parts intact. This option truncates the thumbnail from the Exif header, provided that the thumbnail is the last part of the Exif header (which so far as I know is always the case). Exif headers have a built-in thumbnail, which typically occupies around 10k of space. This thumbnail is used by digital cameras. Windows XP may also use this thumbnail if present (but it doesn't need it). The thumbnails are too small to use even full screen on the digicam's LCD. I have not encountered any adverse side effects of deleting the thumbnails, even from the software provided with my old Olympus digicam. Use with caution. .TP .BI \-\^st \ file Save the integral thumbnail to .I file The thumbnail lives inside the Exif header, and is a very low-res JPEG image. Note that making any changes to a photo, except for with some programs, generally wipes out the Exif header and with it the thumbnail. The thumbnail is too low res to really use for very much. This feature has an interesting 'relative path' option for specifying the thumbnail name. Whenever the name for .I file contains the characters '&i', .B jhead will substitute the original filename for this name. This allows creating a 'relative name' when doing a whole batch of files. For example, the incantation: .I jhead \-st """thumbnails/&i"" *.jpg would create a thumbnail for each .jpg file in the thumbnails directory by the same name, (provided that the thumbnails directory exists, of course). Both Win32 and UNIX shells treat the '&'character in a special way, so you have to put quotes around that command line option for the '&' to even be passed to the program. If a '\-' is specified for the output file, the thumbnail is sent to stdout. (UNIX build only) .TP .B \-rt Replace thumbnails from the Exif header. This only works if the exif header already contains a thumbnail, and the thumbnail is at the end of the header (both always the case if the photo came from a digital camera) .TP .BI \-\^rgt \ size Regenerate exif thumbnail. 'size' specifies maximum height or width of thumbnail. Relies on 'mogrify' program (from ImageMagick) to regenerate the thumbnail. This only works if the image already contains a thumbnail. .SH ROTATION OPTIONS .TP .B \-autorot Using the 'Orientation' tag of the Exif header, rotate the image so that it is upright. The program .B jpegtran is used to perform the rotation. This program is present in most Linux distributions. For windows, you need to get a copy of it. After rotation, the orientation tag of the Exif header is set to '1' (normal orientation). The thumbnail is also rotated. Other fields of the Exif header, including dimensions are untouched, but the JPEG height/width are adjusted. This feature is especially useful with newer Canon cameras, that set the orientation tag automatically using a gravity sensor. .TP .B \-norot Clears the rotation field in the Exif header without altering the image. Useful if the images were previously rotated without clearing the Exif rotation tag, as some image browsers will auto rotate images when the rotation tag is set. Sometimes, thumbnails and rotation tags can get very out of sync from manipulation with various tools. To reset it all use \-norot with \-rgt to clear this out. .SH OUTPUT VERBOSITY CONTROL .TP .B \-h Displays summary of command line options. .TP .B \-v Makes the program even more verbose than it already is. Like DOS programs, and unlike UNIX programs, Jhead gives feedback as to what it is doing, even when nothing goes wrong. Windows user that I am, when something doesn't give me feedback for 20 seconds, I assume its crashed. .TP .B \-q No output on success, more like Unix programs. .TP .B \-V Print version info and compilation date. .B \-exifmap Show a map of the bytes in the exif header. Useful when analyzing strange exif headers, not of much use to non software developers. .TP .B \-se Suppress error messages relating to corrupt Exif header structure. .TP .B \-c Concise output. This causes picture info to be summarized on one line instead of several. Useful for grep-ing through images, as well as importing into spread sheets (data is space delimited with quotes as text qualifier). .SH FILE MATCHING OPTIONS .TP .B \-model Restricts processing of files to those whose camera model, as indicated by the Exif image information, contains the substring specified in the argument after '\-model'. For example, the following command will list only images that are from an S100 camera: .I jhead \-model S100 *.jpg I use this option to restrict my JPEG recompensing to those images that came from my Canon S100 digicam, (see the \-cmd option). .TP .B \-exonly Skip all files that don't have an Exif header. Photos straight from a digital camera have an Exif header, whereas many photo manipulation tools discard the Exif header. .TP .B \-cmd Executes the specified command on each JPEG file to be processed. The Exif section of each file is read before running the command, and reinserted after the command finishes. The specified command invoked separately for each JPEG that is processed, even if multiple files are specified (explicitly or by wild card). Example use: Having a whole directory of photos from my S100, I run the following commands: .I jhead \-cmd """mogrify \-quality 80 &i"" \-model S100 *.jpg .br .I jhead \-cmd """jpegtran \-progressive &i > &o"" *.jpg The first command mogrifies all JPEGs in the tree that indicate that they are from a Canon S100 in their Exif header to 80% quality at the same resolution. This is a 'lossy' process, so I only run it on files that are from the Canon, and only run it once. The next command then takes a JPEGs and converts them to progressive JPEGs. The result is the same images, with no discernible differences, stored in half the space. This produces substantial savings on some cameras. .SH SEE ALSO .BR jpegtran (1), .BR mogrify (1), .BR rdjpgcom (1), .BR wrjpgcom (1) .SH AUTHOR Matthias Wandel .SH BUGS After jhead runs a program to rotate or resize an image, the image dimensions and thumbnail in the Exif header are not adjusted. .PP Modifying of Exif header data is very limited, as Jhead internally only has a read only implementation of the file system contained in the Exif header. For example, there is no way to replace the thumbnail or edit the Exif comment in the Exif header. There is also no way to create minimal exif headers. .PP Some Canon digital SLR cameras fail to adjust the effective sensor resolution when shooting at less than full resolution, causing jhead to incorrectly miscalculate the sensor width and 35mm equivalent focal length. The same can result from resizing photos with Photoshop, which will manipulate parts of the exif header. This is often reported as a bug in Jhead, but Jhead can't do much about incorrect data. .PP Send bug reports to mwandel at sentex dot net. .SH COPYING PERMISSIONS Jhead is 'public domain'. You may freely copy jhead, and reuse part or all of its code in free or proprietary programs. I do however request that you do not post my e-mail address in ways that spam robots can harvest it. jhead-3.04/jhead.h0000444000175000017500000002026213565770126013613 0ustar mwandelmwandel//-------------------------------------------------------------------------- // 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. //-------------------------------------------------------------------------- #include #include #include #include #include #include //-------------------------------------------------------------------------- #ifdef _WIN32 #include // Make the Microsoft Visual c 10 deprecate warnings go away. // The _CRT_SECURE_NO_DEPRECATE doesn't do the trick like it should. #define unlink _unlink #define chmod _chmod #define access _access #define mktemp _mktemp #if _MSC_VER && _MSC_VER <= 1500 // The 2007 vintage compiler I use on windows doesn't have snprintf #define snprintf(dest, len, format,...) sprintf (dest, format, __VA_ARGS__) #endif #else #include #include #include #include #include #endif typedef unsigned char uchar; #ifndef TRUE #define TRUE 1 #define FALSE 0 #endif #define MAX_COMMENT_SIZE 16000 #ifdef _WIN32 #define PATH_MAX _MAX_PATH #define SLASH '\\' #else #ifndef PATH_MAX #define PATH_MAX 1024 #endif #define SLASH '/' #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; struct { // Info in the jfif header. // This info is not used much - jhead used to just replace it with default // values, and over 10 years, only two people pointed this out. char Present; char ResolutionUnits; short XDensity; short YDensity; }JfifHeader; unsigned FileSize; char CameraMake [32]; char CameraModel [40]; char DateTime [20]; unsigned 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; int DistanceRange; float xResolution; float yResolution; int ResolutionUnit; char Comments[MAX_COMMENT_SIZE]; int CommentWidthchars; // If nonzero, widechar comment, indicates number of chars. int ThumbnailOffset; // Exif offset to thumbnail int ThumbnailSize; // Size of thumbnail. int 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 GpsLong[31]; char GpsAlt[20]; int QualityGuess; }ImageInfo_t; #ifndef EXIT_FAILURE #define EXIT_FAILURE 1 #endif #define EXIT_SUCCESS 0 // jpgfile.c functions typedef enum { READ_METADATA = 1, READ_IMAGE = 2, READ_ALL = 3, READ_ANY = 5 // Don't abort on non-jpeg files. }ReadMode_t; // prototypes for jhead.c functions void ErrFatal(const char * msg); void ErrNonfatal(const char * msg, int a1, int a2); void FileTimeAsString(char * TimeStr); // Prototypes for exif.c functions. int Exif2tm(struct tm * timeptr, char * ExifTime); void Clear_EXIF(); void process_EXIF (unsigned char * CharBuf, int length); 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(void); //-------------------------------------------------------------------------- // 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, unsigned char * OffsetBase, unsigned ExifLength); // iptc.c prototpyes void show_IPTC (unsigned char * CharBuf, unsigned int length); void ShowXmp(Section_t XmpSection); // Prototypes for myglob.c module #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); 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); void WriteJpegFile(const char * FileName); Section_t * FindSection(int SectionType); Section_t * CreateSection(int SectionType, unsigned char * Data, int size); void ResetJpgfile(void); // Prototypes from jpgqguess.c void process_DQT (const uchar * Data, int length); void process_DHT (const uchar * Data, int length); // Variables from jhead.c used by exif.c extern ImageInfo_t ImageInfo; extern int ShowTags; //-------------------------------------------------------------------------- // 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. 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 Quantization Table #define M_DHT 0xC4 // Define Huffmann Table #define M_DRI 0xDD #define M_IPTC 0xED // IPTC marker jhead-3.04/makefile0000444000175000017500000000113013565770126014060 0ustar mwandelmwandel#-------------------------------- # jhead makefile for Unix #-------------------------------- OBJ=. SRC=. CPPFLAGS:=$(shell dpkg-buildflags --get CPPFLAGS) CFLAGS:=$(shell dpkg-buildflags --get CFLAGS) LDFLAGS:=$(shell dpkg-buildflags --get LDFLAGS) all: jhead objs = $(OBJ)/jhead.o $(OBJ)/jpgfile.o $(OBJ)/jpgqguess.o $(OBJ)/paths.o \ $(OBJ)/exif.o $(OBJ)/iptc.o $(OBJ)/gpsinfo.o $(OBJ)/makernote.o $(OBJ)/%.o:$(SRC)/%.c ${CC} $(CFLAGS) $(CPPFLAGS) -c $< -o $@ jhead: $(objs) jhead.h ${CC} $(LDFLAGS) -o jhead $(objs) -lm clean: rm -f $(objs) jhead install: cp jhead ${DESTDIR}/usr/bin/ jhead-3.04/readme.txt0000444000175000017500000000435513565770126014372 0ustar mwandelmwandel Some notes: When I first wrote Jhead back in 1999, there wasn't much software around for looking inside Exif headers, so I wrote jhead for that task. Since then, a lot of much more sophisticated programs for looking inside Exif headers have been written, many with GUIs, and features that Jhead lacks. Seeing that Jhead does everything I need it to do, My goal is not to have every feature imaginable. Rather, I want Jhead to be a small, simple, easy to understand program. My goal is that if you need to understand Exif internals, or add Exif capability to your program, Jhead is the place to cut and paste code from. As a result, Jhead may not have your pet feature. Feel free to add your pet feature to Jhead - its meant to be hacked. If you send me your changes, I might integrate it, but only if its simple. If you find that it dies on a certain jpeg file, send it to me, and I will look at it. Compiling: Windows: Make sure visual C is on your path (I use version 6 from 1998, but it shouldn't matter much). Run the batch file make.bat Linux & Unices: type 'make'. Portability: Although I have never done so myself, people tell me it compiles under platforms as diverse as such as Mac OS-X, or NetBSD on Mac68k. Jhead doesn't care about the endian-ness of your CPU, and should not have problems with processors that do not handle unaligned data, such as ARM or Alpha. The main portability problem is the use of C++ style '//' comments. This is intentional, and won't change. Jhead has also made its way into various Linux distributions and ports trees, so you might already have it on your system without knowing. Note that I am a windows weenie myself. License: Jhead is public domain software - that is, you can do whatever you want with it, and include it software that is licensed under the GNU or the BSD license, or whatever other licence you chose, including proprietary closed source licenses. Although not part of the license, I do expect common courtesy, please. If you do integrate the code into some software of yours, I'd appreciate knowing about it though. Matthias Wandel jhead-3.04/jhead.c0000444000175000017500000017611513565770126013617 0ustar mwandelmwandel//-------------------------------------------------------------------------- // Program to pull the information out of various types of EXIF digital // camera files and show it in a reasonably consistent way // // Version 3.04 // // Compiling under Windows: // Make sure you have Microsoft's compiler on the path, then run make.bat // // Dec 1999 - Nov 2019 // // by Matthias Wandel www.sentex.net/~mwandel //-------------------------------------------------------------------------- #ifdef _WIN32 #include #endif #include "jhead.h" #include #define JHEAD_VERSION "3.04" // 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 // Bitmasks for DoModify: #define MODIFY_ANY 1 #define READ_ANY 2 #define JPEGS_ONLY 4 #define MODIFY_JPEG 5 #define READ_JPEG 6 static int DoModify = FALSE; 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 = 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; 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 int TrimExifTrailingZeroes = FALSE; static char * ApplyCommand = NULL; // Apply this command to all images. static char * FilterModel = NULL; static int FilterQuality = 0; 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 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. 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 SuppressNonFatalErrors = 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(const char * msg) { fprintf(stderr,"\nError : %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(const char * msg, int a1, int a2) { if (SuppressNonFatalErrors) return; fprintf(stderr,"\nNonfatal Error : "); if (CurrentFile) fprintf(stderr,"'%s' ",CurrentFile); fprintf(stderr, msg, a1, a2); fprintf(stderr, "\n"); } //-------------------------------------------------------------------------- // Invoke an editor for editing a string. //-------------------------------------------------------------------------- static int FileEditComment(char * TempFileName, char * Comment, int CommentSize) { FILE * file; int a; char QuotedPath[PATH_MAX+10]; 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 } 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); } file = fopen(TempFileName, "r"); if (file == NULL){ ErrFatal("could not open temp file for read"); } // Read the file back in. CommentSize = fread(Comment, 1, MAX_COMMENT_SIZE, 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; strncpy(Line, AddComment, sizeof(Line)); Line[sizeof(Line)-1]='\0'; Modified = TRUE; } } strncat(OutComment, Line, MAX_COMMENT_SIZE-5-strlen(OutComment)); strcat(OutComment, "\n"); break; } } } } Line[Len = 0] = 0; if (SrcComment[i] == 0) break; }else{ Line[Len++] = SrcComment[i]; } } if (AddComment && TagExists == FALSE){ strncat(OutComment, AddComment, MAX_COMMENT_SIZE-5-strlen(OutComment)); 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[40]; sprintf(Temp, "scan_date=%s", ctime(&ImageInfo.FileDateTime)); strncat(OutComment, Temp, MAX_COMMENT_SIZE-5-strlen(OutComment)); Modified = TRUE; } return Modified; } //-------------------------------------------------------------------------- // Automatic make smaller command stuff //-------------------------------------------------------------------------- static int AutoResizeCmdStuff(void) { static char CommandString[PATH_MAX+1]; double scale; float TargetSize = 1800; ApplyCommand = CommandString; scale = TargetSize / ImageInfo.Width; if (scale > TargetSize / ImageInfo.Height) scale = TargetSize / ImageInfo.Height; if (scale > 0.8){ if (ImageInfo.QualityGuess >= 93){ // Re-compress at lower quality. sprintf(CommandString, "mogrify -quality 86 &i"); return TRUE; } printf("not resizing %dx%x '%s'\n",ImageInfo.Height, ImageInfo.Width, ImageInfo.FileName); return FALSE; } if (scale < 0.4) scale = 0.4; // Don't scale down by too much. sprintf(CommandString, "mogrify -geometry %dx%d -quality 85 &i",(int)(ImageInfo.Width*scale+0.5), (int)(ImageInfo.Height*scale+0.5)); return TRUE; } #endif // MATTHIAS //-------------------------------------------------------------------------- // 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[PATH_MAX*3]; char TempName[PATH_MAX+10]; int TempUsed = FALSE; e = 0; // 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"); // Note: Compiler will warn about mkstemp. but I need a filename, not a file. // I could just then get the file name from what mkstemp made, and pass that // to the executable, but that would make for the exact same vulnerability // as mktemp - that is, that between getting the random name, and making the file // some other program could snatch that exact same name! // also, not all platforms support mkstemp. 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 += shellescape(ExecString+e, FileName); a += 1; continue; } if (ApplyCommand[a+1] == 'o'){ // Needs an output file distinct from the input file. e += shellescape(ExecString+e, TempName); a += 1; TempUsed = TRUE; 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){ struct stat buf; int stat_result = stat(FileName, &buf); unlink(FileName); rename(TempName, FileName); if (stat_result == 0){ // set Unix access rights and time to new file struct utimbuf mtime; chmod(FileName, buf.st_mode); mtime.actime = buf.st_atime; mtime.modtime = buf.st_mtime; utime(FileName, &mtime); } }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 (FilterQuality > 0){ //Filter by above threshold quality if (ImageInfo.QualityGuess < FilterQuality){ 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{ strncpy(OutFileName, NamePattern, PATH_MAX); } } #ifdef _WIN32 //-------------------------------------------------------------------------- // Rename associated files //-------------------------------------------------------------------------- void RenameAssociated(const char * FileName, char * NewBaseName) { int a; int PathLen; int ExtPos; char FilePattern[_MAX_PATH+1]; char NewName[_MAX_PATH+1]; 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] != SLASH;){ 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; 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; } strncat(NewName, finddata.name+a, _MAX_PATH-strlen(NewName)); // 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 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] == SLASH){ // Don't count path component. PrefixPart = a+1; } if (FileName[a] == '.') ExtensionPart = a; // Remember where extension starts. } 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); } strncpy(NewBaseName, FileName, PATH_MAX); // 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= 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])){ ppos = -1; } } } strftime(NewName, PATH_MAX, pattern, &tm); }else{ // My favourite scheme. 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+10]; 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 (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 0,1,2,3... for suffix if it ends in a latter. } NameExtra[1] = 0; }else{ NameExtra[0] = 0; } snprintf(NewName, sizeof(NewName), "%s%s.jpg", NewBaseName, NameExtra); 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){ 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 > 25 || (!AddLetter && 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 != 1){ const char * Argument; Argument = ClearOrientation(); if (Argument == NULL) return FALSE; // orientation tag in image, nothing changed. if (!ZeroRotateTagOnly){ char RotateCommand[PATH_MAX*2+50]; if (strlen(Argument) == 0){ // Unknown orientation, but still modified. return TRUE; // Image is still modified. } sprintf(RotateCommand, "jpegtran -trim -%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+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 -trim -%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 -quality 80 \"%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; } } //-------------------------------------------------------------------------- // 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); } //-------------------------------------------------------------------------- // Do selected operations to one file at a time. //-------------------------------------------------------------------------- static void ProcessFile(const char * FileName) { int Modified = FALSE; 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; ResetJpgfile(); Clear_EXIF(); // 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 & MODIFY_ANY) || 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. } 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; } if (TrimExifTrailingZeroes){ if (ImageInfo.ThumbnailAtEnd){ Section_t * ExifSection; int NumRedundant, WasRedundant; unsigned char * StartRedundant; //printf("Exif: Thumbnail %d - %d\n",ImageInfo.ThumbnailOffset, ImageInfo.ThumbnailOffset+ImageInfo.ThumbnailSize); ExifSection = FindSection(M_EXIF); StartRedundant = ExifSection->Data + 8 + ImageInfo.ThumbnailOffset+ImageInfo.ThumbnailSize; WasRedundant = NumRedundant = (ExifSection->Size) - (ImageInfo.ThumbnailOffset + ImageInfo.ThumbnailSize + 8); //printf("Exif length: %d Wasted: %d\n",ExifSection->Size, NumRedundant); for(;NumRedundant > 0 && StartRedundant[NumRedundant-1] == 0;NumRedundant--);// Only remove trailing bytes if they are zero. if (NumRedundant != WasRedundant){ int NewExifSize; printf("Trimming %d bytes from exif in %s\n", WasRedundant-NumRedundant, FileName); NewExifSize = ImageInfo.ThumbnailOffset + ImageInfo.ThumbnailSize + 8 + NumRedundant; ExifSection->Data[0] = (uchar)(NewExifSize >> 8); // Must write new length into exif data. ExifSection->Data[1] = (uchar)NewExifSize; ExifSection->Size = NewExifSize; Modified = TRUE; }else{ //printf("Noting to remove from %s\n", FileName); } } } FileSequence += 1; // Count files processed. if (ShowConcise){ ShowConciseImageInfo(); }else{ if (!(DoModify) || 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(); 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[MAX_COMMENT_SIZE+1]; 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 > MAX_COMMENT_SIZE){ fprintf(stderr, "Truncating comment at %d chars\n",MAX_COMMENT_SIZE); CommentSize = MAX_COMMENT_SIZE; } 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, MAX_COMMENT_SIZE, CommentFile); fclose(CommentFile); if (CommentSize < 0) CommentSize = 0; } }else if (CommentInsertLiteral){ strncpy(Comment, CommentInsertLiteral, MAX_COMMENT_SIZE); CommentSize = strlen(Comment); }else{ #ifdef MATTHIAS char CommentZt[MAX_COMMENT_SIZE+1]; 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+5]; 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); } memcpy(ImageInfo.DateTime, 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 (DeleteXmp){ if (RemoveSectionType(M_XMP)) Modified = TRUE; } if (DeleteUnknown){ if (RemoveUnknownSections()) Modified = TRUE; } if (Modified){ char BackupName[PATH_MAX+5]; 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. 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); } 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 thumbnails in Exif jpeg headers\n" "used by most Digital Cameras. v"JHEAD_VERSION" Matthias Wandel, Nov 22 2019.\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 Transfer exif header from another image file \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" " -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" " -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 Save comment section to a file\n" " -ci 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" " -zt Trim exif header trailing zeroes (Nikon 1 wastes 30k that way)\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" " %%d Day of month %%H Hour (24-hour)\n" " %%m Month number %%M Minute %%S Second\n" " %%y Year (2 digit 00 - 99) %%Y Year (4 digit 1980-2036)\n" " For more arguments, look up the 'strftime' function.\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.\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" " The new name may include a path as part of the name. If this path\n" " does not exist, it will be created\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 forwards or backwards. 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-\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:mm:dd or as date\n" " and time in the format yyyy:mm:dd/hh:mm:ss\n" " -ts