pax_global_header00006660000000000000000000000064144376550130014522gustar00rootroot0000000000000052 comment=4d04ac965632e35a65709c7f92a857a749e71811 jhead-3.08/000077500000000000000000000000001443765501300125275ustar00rootroot00000000000000jhead-3.08/.gitignore000066400000000000000000000001161443765501300145150ustar00rootroot00000000000000*.bak *.o tests/results-txt/* tests/results-bin/* tests/temp/ tests/movetest/ jhead-3.08/buildrpms000077500000000000000000000007171443765501300144630ustar00rootroot00000000000000#!/bin/sh # Shell script for building RPMS and copying to website. fdir=jhead-2.3 release=2 cp "$fdir.tar.gz" /usr/src/redhat/SOURCES/ cp jhead.spec /usr/src/redhat/SPECS/ rpmbuild -ba /usr/src/redhat/SPECS/jhead.spec cp "$fdir.tar.gz" ../website/jhead cp "/usr/src/redhat/SRPMS/$fdir-$release.src.rpm" ../website/jhead/ cp "/usr/src/redhat/RPMS/i386/$fdir-$release.i386.rpm" ../website/jhead/ make cp jhead ../website/jhead/ cp changes.txt ../website/jhead/ jhead-3.08/changes.txt000066400000000000000000000303661443765501300147100ustar00rootroot00000000000000************************************************************* ***** Detailed change log - since version 1.2 ***** ***** In chronological order, oldest to newest ***** ***** For detailed changes see jhead on github: ***** ****** https://github.com/Matthias-Wandel/jhead ***** ************************************************************* Jun 6 2023: Bumped version number to 3.08 after going thru some accumulated github issues. Jun 3 2021 - Jun 2023: Fix various "issues" people have found with fuzz testing. These can only be produced when running jhead in some memory access testing setup such as ASAN and throwing carefully crafted garbage at it, causing jhead to read some bytes past memory it malloced. no real life consequences. Also some spelling fixes and other stuff, but no new features. 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) August 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 available 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 notepad 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 exposure 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 rotation (-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 Implemented option to suppress file date display, for regression tests. Feb 1 Better indentation of verbose option, rudimentary 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 manpage. Jun 20 Added ability to do sequential 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 equivalent 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 optional 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 deletes 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 section 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 resolution 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 mixed-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 metadata 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 readonly 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 -------- Migrated to GitHub. Mostly remove support for windows XP specific tags (nobody used them, didn't work on linux) Make tests suite run on linux, included in github. Bumped version number to 3.05 Various fixes to fuzz test fails -- all fails involve reading beyond allocated memory when fed specific garbage files, and only fail with memory tools tools like "electric fence" to catch them. -------Bumped version to 3.06 -- Mar 24 2021 jhead-3.08/exif.c000066400000000000000000001723051443765501300136360ustar00rootroot00000000000000//-------------------------------------------------------------------------- // 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: { float f; int tmp = *(int*)ValuePtr; f = *(float *)&tmp; printf("%f",f); } s=4; 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; // Never seen floats used in actual exif format, // this code only ever hit with fuzz testing. // This code may not necessarily print correct values if float *were* // to be used in exif, as it doesn't define which floating point // standard is to be used. case FMT_SINGLE: { int tmp = *(int*)ValuePtr; Value = *(float *)&tmp; } 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]; unsigned OffsetVal; 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 0x10000){ //Components count too large could cause overflow on subsequent check ErrNonfatal("Bad components count %x", Components,0); continue; } if ((Format-1) >= 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){ OffsetVal = Get32u(DirEntry+8); // If its bigger than 4 bytes, the dir entry contains an offset. if (OffsetVal+ByteCount > (unsigned)ExifLength || 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 > (unsigned)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){ 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: case TAG_DATETIME_DIGITIZED: case TAG_DATETIME: if (ValuePtr+19 >= OffsetBase+ExifLength){ ErrNonfatal("Incomplete time",0,0); continue; } if (Tag == TAG_DATETIME_ORIGINAL || !isdigit(ImageInfo.DateTime[0])){ // If we don't already have a DATETIME_ORIGINAL, use whatever // time fields we may have. But if ORIGINAL tag comes later, use that one. 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++] = (int)((char *)ValuePtr - (char *)OffsetBase); 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 = (int)(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 computed 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 far they are zoomed in. ImageInfo.FocalLength = (float)ConvertAnyFormat(ValuePtr, Format); break; case TAG_SUBJECT_DISTANCE: // Indicates the distance 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 computed 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 = (int)(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... //-------------------------------------------------------------------------- int process_EXIF (unsigned char * ExifSection, int length) { int FirstOffset; Clear_EXIF(); if (ShowTags){ printf("Exif header %u bytes long\n",length); } if (length < 16){ ErrFatal("exif section too short"); } { // Check the EXIF header component static uchar ExifHeader[] = "Exif\0\0"; if (memcmp(ExifSection+2, ExifHeader,6)){ ErrNonfatal("Incorrect Exif header",0,0); return 0; } } 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 0; } } // Check the next value for correctness. if (Get16u(ExifSection+10) != 0x2a){ ErrNonfatal("Invalid Exif start (1)",0,0); return 0; } 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 0; } // 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); } } //-------------------------------------------------------------------------- // Clear the rotation tag in the exif header to 1. // Returns NULL if no orientation 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 : "); for (a=0;a= 0 && ImageInfo.FlashUsed & 1){ printf(" (flash)"); } if (ImageInfo.IsColor == 0){ printf(" (bw)"); } printf("\n"); } jhead-3.08/exifgaps.py000066400000000000000000000027101443765501300147070ustar00rootroot00000000000000# A python script for checking for allocation gaps in the exif header. # Any data unaccounted for could potentially be referenced by the maker notes # This to help investigate the feasibility of rewriting the whole exif header. # # Dec 27 2005 Matthias Wandel import sys, os, string # Run jhead. Must be compiled with EXIF_MAP option turned on in jhead.h os.system("jhead -exifmap "+sys.argv[1]+" > foo.txt") ExifData = []; ExifMap = []; # read in the output from jhead for line in open("foo.txt"): if line[0:4] != "Map:": continue ExifData.append(line) if line[10] == '-': if line.find("makernote") >= 0: continue ExifMap.append(line); if line.find("End of exif") >= 0: continue if line[5:9] != line[11:15]: ExifData.append("Map: "+line[11:16]+"*End "+line[17:]) ExifMap.sort(); # Find unallocated pieces of the exif header usedto = "00008" for line in ExifMap: segs = line[5:10] sege = line[11:16] if usedto < segs: print ("Map: "+ usedto+"-"+segs+ " Gap!!!\n") ExifData.append("Map: "+ usedto+"-"+segs+ " Gap!!!\n") ExifData.append("Map: "+segs+" End of gap\n"); if usedto > segs: print ("Map: "+ usedto+"-"+segs+ " Negative Gap!!!\n"); ExifData.append("Map: "+ usedto+"-"+segs+ " Negative Gap!!!\n"); usedto = sege print ExifData.sort() # Print the exif data, as well as any gaps found in it. for line in ExifData: print (line.rstrip("\n")) jhead-3.08/gpsinfo.c000066400000000000000000000170411443765501300143430ustar00rootroot00000000000000//-------------------------------------------------------------------------- // 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 (Components > 0x10000){ //Components count too large could cause overflow on subsequent check ErrNonfatal("Bad components count %x", Components,0); continue; } 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+ByteCount > ExifLength || OffsetVal > 65536){ // 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.08/how-to-make-rpm.txt000066400000000000000000000007071443765501300162200ustar00rootroot00000000000000Don't forget to check version in: * Manpage * usage.html * index.html (2 places!) * changes.txt * jhead.c * jhead.spec * command line help (and date!) * rpmprep Also uncomment #define MATTIAS! put the jhead-2.3.tar.gz into /usr/src/redhat/SOURCES put the spec file in /usr/src/redhat/SPECS from the SPECS directory do: rpmbuild -ba jhead.spec The source RPM will be under /usr/src/redhat/SRPPMS the binary rpm will be under /usr/src/redhat/RPMS/i386 jhead-3.08/iptc.c000066400000000000000000000214211443765501300136320ustar00rootroot00000000000000//-------------------------------------------------------------------------- // 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) if (pos+16 > maxpos) goto corrupt; pos += 3; // move data pointer to length, assume only one byte, TODO: use all 4 bytes dataLen = *pos++; pos += dataLen; // skip data section if (pos+16 > maxpos) goto corrupt; 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+16 >= 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+8 >= 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+5 < maxpos) { 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.08/iptc.h000066400000000000000000000007271443765501300136450ustar00rootroot00000000000000#ifndef __IPTC_H #define __IPTC_H typedef unsigned char BOOLEAN; #ifndef TRUE #define TRUE 1 #define FALSE 0 #endif // IPTC Info - an array of tag/description string pairs typedef struct { char* tag; char* val; } T_IPTC_INFO; #define MAX_NUM_IPTC_ENTRIES 100 T_IPTC_INFO IptcInfo [MAX_NUM_IPTC_ENTRIES]; // IPTC functions int process_IPTC (unsigned char * CharBuf, unsigned int length); void ShowIptcInfo (void); #endif // __IPTC_H jhead-3.08/jhead.1000066400000000000000000000373331443765501300136750ustar00rootroot00000000000000.TH JHEAD 1 "24 Mar 2021" "jhead 3.06" .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 moved 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 \-proc Skip all but files matching process. Specifying "-proc 0" for example will only process files with baseline encoding. Specifying "-proc 2" will only process files with progressive encoding. This option is useful when converting files to progressive jpegs using jpegtran, to skip those files already encoded as progressive jpegs. For example, the following command converts only files as baseline to progressive: jhead -proc 0 -cmd "jpegtran -progressive -outfile &o &i" *.jpg .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 matthias at woodgears dot ca .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.08/jhead.c000066400000000000000000002023321443765501300137500ustar00rootroot00000000000000//-------------------------------------------------------------------------- // Program to pull the information out of various types of EXIF digital // camera files and show it in a reasonably consistent way // // Version 3.06 // // Compiling under Windows: // Make sure you have Microsoft's compiler on the path, then run make.bat // // Dec 1999 - Jun 2023 // // by Matthias Wandel http://woodgears.ca //-------------------------------------------------------------------------- #ifdef _WIN32 #include #endif #include "jhead.h" #include #define JHEAD_VERSION "3.08" // 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; // Only do images with exif header static int ProcessOnly = -1; // 0 for baseline, 2 for progressive only, -1 for all images. static int PortraitOnly = FALSE; // Only do images with portrait orientation. 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; // Whether 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[2*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"); // Disallow characters in the editor or filename that could be used to execute arbitrary // shell commands with system() below. if (strpbrk(TempFileName, "\";'&|`$")) { ErrFatal("Filename has invalid characters"); } if (strpbrk(Editor, "\";'&|`$")) { ErrFatal("Editor has invalid characters"); } int num = snprintf(QuotedPath, sizeof(QuotedPath), "%s \"%s\"",Editor, TempFileName); if(num > sizeof(QuotedPath)) { ErrFatal("Quoted path to edit would be too long"); } 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 security 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' || ApplyCommand[a+1] == 'o'){ if (e > PATH_MAX * 2) ErrFatal("Specified command line too long"); } 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(ProcessOnly >= 0 && (ImageInfo.Process & 0x0f) != ProcessOnly){ // ProcessOnly == 0 means skip baseline oencoded jpegs // ProcessOnly == 2 means skip progressive oencoded jpegs return TRUE; } if (FilterModel){ // Filtering processing by camera model. // This feature is useful when pictures from multiple cameras are collated, // 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; } //-------------------------------------------------------------------------- // Substitute 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 > NamePattern+PATH_MAX-10){ ErrFatal("Bad file name pattern"); } if (Subst){ strncpy(OutFileName, NamePattern, Subst-NamePattern); OutFileName[Subst-NamePattern] = 0; strncat(OutFileName, OrigName, PATH_MAX-1-strlen(OutFileName)); strncat(OutFileName, Subst+2, PATH_MAX-1-strlen(OutFileName)); }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 (ExtensionPart < PrefixPart) { // no extension found ExtensionPart = strlen(FileName); } 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*2+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 whether 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 modifiable. 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); // Disallow characters in the filenames that could be used to execute arbitrary // shell commands with system() below. if (strpbrk(FileName, "\";'&|`$")) { ErrNonfatal("Command has invalid characters.", 0, 0); unlink(ThumbTempName_in); return FALSE; } 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; } // Disallow characters in the filename that could be used to execute arbitrary // shell commands with system() below. if(strpbrk(FileName, "\";'&|`$")) { ErrNonfatal("Filename has invalid characters.", 0, 0); return FALSE; } snprintf(ThumbnailGenCommand, sizeof(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, but 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){ // Convert 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, Oct 5 2020.\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 Delete 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