diff options
Diffstat (limited to 'tdefile-plugins/jpeg')
-rw-r--r-- | tdefile-plugins/jpeg/CMakeLists.txt | 35 | ||||
-rw-r--r-- | tdefile-plugins/jpeg/Makefile.am | 24 | ||||
-rw-r--r-- | tdefile-plugins/jpeg/README | 35 | ||||
-rw-r--r-- | tdefile-plugins/jpeg/exif.cpp | 961 | ||||
-rw-r--r-- | tdefile-plugins/jpeg/exif.h | 127 | ||||
-rw-r--r-- | tdefile-plugins/jpeg/tdefile_jpeg.cpp | 531 | ||||
-rw-r--r-- | tdefile-plugins/jpeg/tdefile_jpeg.desktop | 64 | ||||
-rw-r--r-- | tdefile-plugins/jpeg/tdefile_jpeg.h | 44 | ||||
-rw-r--r-- | tdefile-plugins/jpeg/tdefile_setcomment.cpp | 536 |
9 files changed, 2357 insertions, 0 deletions
diff --git a/tdefile-plugins/jpeg/CMakeLists.txt b/tdefile-plugins/jpeg/CMakeLists.txt new file mode 100644 index 00000000..3e99eba7 --- /dev/null +++ b/tdefile-plugins/jpeg/CMakeLists.txt @@ -0,0 +1,35 @@ +################################################# +# +# (C) 2010-2011 Calvin Morrison +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +#### other data ################################# + +install( FILES tdefile_jpeg.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) + + +#### tdefile_jpeg (module) ######################## + +tde_add_kpart( tdefile_jpeg AUTOMOC + SOURCES tdefile_jpeg.cpp exif.cpp tdefile_setcomment.cpp + LINK tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdefile-plugins/jpeg/Makefile.am b/tdefile-plugins/jpeg/Makefile.am new file mode 100644 index 00000000..76e9e32d --- /dev/null +++ b/tdefile-plugins/jpeg/Makefile.am @@ -0,0 +1,24 @@ +## Makefile.am for jpeg file meta info plugin + +KDE_CXXFLAGS = $(USE_EXCEPTIONS) + +# set the include path for X, qt and KDE +INCLUDES = $(all_includes) + +# these are the headers for your project +noinst_HEADERS = tdefile_jpeg.h exif.h + +kde_module_LTLIBRARIES = tdefile_jpeg.la + +tdefile_jpeg_la_SOURCES = tdefile_jpeg.cpp exif.cpp tdefile_setcomment.cpp +tdefile_jpeg_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) +tdefile_jpeg_la_LIBADD = $(LIB_KIO) + +# let automoc handle all of the meta source files (moc) +METASOURCES = AUTO + +messages: + $(XGETTEXT) *.cpp -o $(podir)/tdefile_jpeg.pot + +services_DATA = tdefile_jpeg.desktop +servicesdir = $(kde_servicesdir) diff --git a/tdefile-plugins/jpeg/README b/tdefile-plugins/jpeg/README new file mode 100644 index 00000000..6b714426 --- /dev/null +++ b/tdefile-plugins/jpeg/README @@ -0,0 +1,35 @@ +------------------------------------------------------------------------------- +Thanks to: +Matthias Wandel <[email protected]> +jhead (http://www.sentex.net/~mwandel/jhead/) + +Groult Richard <[email protected]> +showimg (http://ric.jalix.org/) + +Bryce Nesbitt for setcomment +based on wrjpgcom.c, Copyright (C) 1994-1997, Thomas G. Lane. + +Frank Pieczynski <[email protected]> + +EXIF spec: +http://www.pima.net/standards/it10/PIMA15740/exif.htm +http://www.ba.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html + +------------------------------------------------------------------------------- +Bugs: + 0) Just showing the meta info can cause the file to be rewritten!!! + 1) utf-8 paths must be tested and supported. + 2) Display of EXIF comments need to be tested. + Both Unicode & ASCII need testing. + +New features needed: + 1) Should switch to a multi-line editor for comments. Support "\n". + 2) Should autodetect if the jpeg COM is in utf-8 format. + 3) Allow editing the EXIF "Orientation" flag (without rewriting the exif header). + 4) Extract and return the EXIF thumbnail, oriented properly, as an alternative + to the much slower process of generating a new thumbnail. + +Future features: + 1) Allow the user to erase or move comment in either the EXIF or jpeg COM. + 2) Play or return audio files associated with the exif. + diff --git a/tdefile-plugins/jpeg/exif.cpp b/tdefile-plugins/jpeg/exif.cpp new file mode 100644 index 00000000..60c689bd --- /dev/null +++ b/tdefile-plugins/jpeg/exif.cpp @@ -0,0 +1,961 @@ +//-------------------------------------------------------------------------- +// Program to pull the information out of various types of EFIF digital +// camera files and show it in a reasonably consistent way +// +// This module parses the very complicated exif structures. +// +// Matthias Wandel, Dec 1999 - August 2000 +//-------------------------------------------------------------------------- + + +#include "exif.h" +#include <tqwmatrix.h> +#include <kglobal.h> + + +static unsigned char * LastExifRefd; +static int ExifSettingsLength; +static double FocalplaneXRes; +static double FocalplaneUnits; +static int MotorolaOrder = 0; +static int SectionsRead; +//static int HaveAll; + +//-------------------------------------------------------------------------- +// Table of Jpeg encoding process names + +#define M_SOF0 0xC0 // Start Of Frame N +#define M_SOF1 0xC1 // N indicates which compression process +#define M_SOF2 0xC2 // Only SOF0-SOF2 are now in common use +#define M_SOF3 0xC3 +#define M_SOF5 0xC5 // NB: codes C4 and CC are NOT SOF markers +#define M_SOF6 0xC6 +#define M_SOF7 0xC7 +#define M_SOF9 0xC9 +#define M_SOF10 0xCA +#define M_SOF11 0xCB +#define M_SOF13 0xCD +#define M_SOF14 0xCE +#define M_SOF15 0xCF +#define M_SOI 0xD8 // Start Of Image (beginning of datastream) +#define M_EOI 0xD9 // End Of Image (end of datastream) +#define M_SOS 0xDA // Start Of Scan (begins compressed data) +#define M_JFIF 0xE0 // Jfif marker +#define M_EXIF 0xE1 // Exif marker +#define M_COM 0xFE // COMment + + +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"}, + { 0, "Unknown"} +}; + + + +//-------------------------------------------------------------------------- +// Describes format descriptor +static int BytesPerFormat[] = {0,1,1,2,4,8,1,1,2,4,8,4,8}; +#define NUM_FORMATS 12 + +#define FMT_BYTE 1 +#define FMT_STRING 2 +#define FMT_USHORT 3 +#define FMT_ULONG 4 +#define FMT_URATIONAL 5 +#define FMT_SBYTE 6 +#define FMT_UNDEFINED 7 +#define FMT_SSHORT 8 +#define FMT_SLONG 9 +#define FMT_SRATIONAL 10 +#define FMT_SINGLE 11 +#define FMT_DOUBLE 12 + +//-------------------------------------------------------------------------- +// Describes tag values + +#define TAG_EXIF_OFFSET 0x8769 +#define TAG_INTEROP_OFFSET 0xa005 + +#define TAG_MAKE 0x010F +#define TAG_MODEL 0x0110 +#define TAG_ORIENTATION 0x0112 + +#define TAG_EXPOSURETIME 0x829A +#define TAG_FNUMBER 0x829D + +#define TAG_SHUTTERSPEED 0x9201 +#define TAG_APERTURE 0x9202 +#define TAG_MAXAPERTURE 0x9205 +#define TAG_FOCALLENGTH 0x920A + +#define TAG_DATETIME_ORIGINAL 0x9003 +#define TAG_USERCOMMENT 0x9286 + +#define TAG_SUBJECT_DISTANCE 0x9206 +#define TAG_FLASH 0x9209 + +#define TAG_FOCALPLANEXRES 0xa20E +#define TAG_FOCALPLANEUNITS 0xa210 +#define TAG_EXIF_IMAGEWIDTH 0xA002 +#define TAG_EXIF_IMAGELENGTH 0xA003 + +// the following is added 05-jan-2001 vcs +#define TAG_EXPOSURE_BIAS 0x9204 +#define TAG_WHITEBALANCE 0x9208 +#define TAG_METERING_MODE 0x9207 +#define TAG_EXPOSURE_PROGRAM 0x8822 +#define TAG_ISO_EQUIVALENT 0x8827 +#define TAG_COMPRESSION_LEVEL 0x9102 + +#define TAG_THUMBNAIL_OFFSET 0x0201 +#define TAG_THUMBNAIL_LENGTH 0x0202 + + +/*static TagTable_t TagTable[] = { + { 0x100, "ImageWidth"}, + { 0x101, "ImageLength"}, + { 0x102, "BitsPerSample"}, + { 0x103, "Compression"}, + { 0x106, "PhotometricInterpretation"}, + { 0x10A, "FillOrder"}, + { 0x10D, "DocumentName"}, + { 0x10E, "ImageDescription"}, + { 0x10F, "Make"}, + { 0x110, "Model"}, + { 0x111, "StripOffsets"}, + { 0x112, "Orientation"}, + { 0x115, "SamplesPerPixel"}, + { 0x116, "RowsPerStrip"}, + { 0x117, "StripByteCounts"}, + { 0x11A, "XResolution"}, + { 0x11B, "YResolution"}, + { 0x11C, "PlanarConfiguration"}, + { 0x128, "ResolutionUnit"}, + { 0x12D, "TransferFunction"}, + { 0x131, "Software"}, + { 0x132, "DateTime"}, + { 0x13B, "Artist"}, + { 0x13E, "WhitePoint"}, + { 0x13F, "PrimaryChromaticities"}, + { 0x156, "TransferRange"}, + { 0x200, "JPEGProc"}, + { 0x201, "ThumbnailOffset"}, + { 0x202, "ThumbnailLength"}, + { 0x211, "YCbCrCoefficients"}, + { 0x212, "YCbCrSubSampling"}, + { 0x213, "YCbCrPositioning"}, + { 0x214, "ReferenceBlackWhite"}, + { 0x828D, "CFARepeatPatternDim"}, + { 0x828E, "CFAPattern"}, + { 0x828F, "BatteryLevel"}, + { 0x8298, "Copyright"}, + { 0x829A, "ExposureTime"}, + { 0x829D, "FNumber"}, + { 0x83BB, "IPTC/NAA"}, + { 0x8769, "ExifOffset"}, + { 0x8773, "InterColorProfile"}, + { 0x8822, "ExposureProgram"}, + { 0x8824, "SpectralSensitivity"}, + { 0x8825, "GPSInfo"}, + { 0x8827, "ISOSpeedRatings"}, + { 0x8828, "OECF"}, + { 0x9000, "ExifVersion"}, + { 0x9003, "DateTimeOriginal"}, + { 0x9004, "DateTimeDigitized"}, + { 0x9101, "ComponentsConfiguration"}, + { 0x9102, "CompressedBitsPerPixel"}, + { 0x9201, "ShutterSpeedValue"}, + { 0x9202, "ApertureValue"}, + { 0x9203, "BrightnessValue"}, + { 0x9204, "ExposureBiasValue"}, + { 0x9205, "MaxApertureValue"}, + { 0x9206, "SubjectDistance"}, + { 0x9207, "MeteringMode"}, + { 0x9208, "LightSource"}, + { 0x9209, "Flash"}, + { 0x920A, "FocalLength"}, + { 0x927C, "MakerNote"}, + { 0x9286, "UserComment"}, + { 0x9290, "SubSecTime"}, + { 0x9291, "SubSecTimeOriginal"}, + { 0x9292, "SubSecTimeDigitized"}, + { 0xA000, "FlashPixVersion"}, + { 0xA001, "ColorSpace"}, + { 0xA002, "ExifImageWidth"}, + { 0xA003, "ExifImageLength"}, + { 0xA005, "InteroperabilityOffset"}, + { 0xA20B, "FlashEnergy"}, // 0x920B in TIFF/EP + { 0xA20C, "SpatialFrequencyResponse"}, // 0x920C - - + { 0xA20E, "FocalPlaneXResolution"}, // 0x920E - - + { 0xA20F, "FocalPlaneYResolution"}, // 0x920F - - + { 0xA210, "FocalPlaneResolutionUnit"}, // 0x9210 - - + { 0xA214, "SubjectLocation"}, // 0x9214 - - + { 0xA215, "ExposureIndex"}, // 0x9215 - - + { 0xA217, "SensingMethod"}, // 0x9217 - - + { 0xA300, "FileSource"}, + { 0xA301, "SceneType"}, + { 0, NULL} +} ; +*/ + + +//-------------------------------------------------------------------------- +// Parse the marker stream until SOS or EOI is seen; +//-------------------------------------------------------------------------- +int ExifData::ReadJpegSections (TQFile & infile, ReadMode_t ReadMode) +{ + int a; + + a = infile.getch(); + + if (a != 0xff || infile.getch() != M_SOI) { + SectionsRead = 0; + return false; + } + for(SectionsRead = 0; SectionsRead < MAX_SECTIONS-1; ){ + int marker = 0; + int got; + unsigned int ll,lh; + unsigned int itemlen; + uchar * Data; + + for (a=0;a<7;a++){ + marker = infile.getch(); + if (marker != 0xff) break; + + if (a >= 6){ + + kdDebug(7034) << "too many padding bytes\n"; + return false; + + } + } + + if (marker == 0xff){ + // 0xff is legal padding, but if we get that many, something's wrong. + throw FatalError("too many padding bytes!"); + } + + Sections[SectionsRead].Type = marker; + + // Read the length of the section. + lh = (uchar) infile.getch(); + ll = (uchar) infile.getch(); + + itemlen = (lh << 8) | ll; + + if (itemlen < 2) { + throw FatalError("invalid marker"); + } + + Sections[SectionsRead].Size = itemlen; + + Data = (uchar *)malloc(itemlen+1); // Add 1 to allow sticking a 0 at the end. + Sections[SectionsRead].Data = Data; + + // Store first two pre-read bytes. + Data[0] = (uchar)lh; + Data[1] = (uchar)ll; + + got = infile.readBlock((char*)Data+2, itemlen-2); // Read the whole section. + if (( unsigned ) got != itemlen-2){ + throw FatalError("reading from file"); + } + SectionsRead++; + + switch(marker){ + + case M_SOS: // stop before hitting compressed data + // If reading entire image is requested, read the rest of the data. + if (ReadMode & READ_IMAGE){ + unsigned long size; + + size = kMax( 0ul, (unsigned long)(infile.size()-infile.at()) ); + Data = (uchar *)malloc(size); + if (Data == NULL){ + throw FatalError("could not allocate data for entire image"); + } + + got = infile.readBlock((char*)Data, size); + if (( unsigned ) got != size){ + throw FatalError("could not read the rest of the image"); + } + + Sections[SectionsRead].Data = Data; + Sections[SectionsRead].Size = size; + Sections[SectionsRead].Type = PSEUDO_IMAGE_MARKER; + SectionsRead ++; + //HaveAll = 1; + } + return true; + + case M_EOI: // in case it's a tables-only JPEG stream + kdDebug(7034) << "No image in jpeg!\n"; + return false; + + case M_COM: // Comment section + // pieczy 2002-02-12 + // now the User comment goes to UserComment + // so we can store a Comment section also in READ_EXIF mode + process_COM(Data, itemlen); + break; + + case M_JFIF: + // Regular jpegs always have this tag, exif images have the exif + // marker instead, althogh ACDsee will write images with both markers. + // this program will re-create this marker on absence of exif marker. + // hence no need to keep the copy from the file. + free(Sections[--SectionsRead].Data); + break; + + case M_EXIF: + // Seen files from some 'U-lead' software with Vivitar scanner + // that uses marker 31 for non exif stuff. Thus make sure + // it says 'Exif' in the section before treating it as exif. + if ((ReadMode & READ_EXIF) && memcmp(Data+2, "Exif", 4) == 0){ + process_EXIF((uchar *)Data, itemlen); // FIXME: This call + // requires Data to be array of at least 8 bytes. Code + // above only checks for itemlen < 2. + }else{ + // Discard this section. + free(Sections[--SectionsRead].Data); + } + break; + + case M_SOF0: + case M_SOF1: + case M_SOF2: + case M_SOF3: + case M_SOF5: + case M_SOF6: + case M_SOF7: + case M_SOF9: + case M_SOF10: + case M_SOF11: + case M_SOF13: + case M_SOF14: + case M_SOF15: + process_SOFn(Data, marker); //FIXME: This call requires Data to + // be array of at least 8 bytes. Code above only checks for + // itemlen < 2. + break; + default: + break; + } + } + return true; +} + + +//-------------------------------------------------------------------------- +// Discard read data. +//-------------------------------------------------------------------------- +void ExifData::DiscardData(void) +{ + for (int a=0; a < SectionsRead; a++) + free(Sections[a].Data); + SectionsRead = 0; +} + +//-------------------------------------------------------------------------- +// Convert a 16 bit unsigned value from file's native byte order +//-------------------------------------------------------------------------- +int ExifData::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 ExifData::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 from file's native byte order +//-------------------------------------------------------------------------- +unsigned ExifData::Get32u(void * Long) +{ + return (unsigned)Get32s(Long) & 0xffffffff; +} + +//-------------------------------------------------------------------------- +// Evaluate number, be it int, rational, or float from directory. +//-------------------------------------------------------------------------- +double ExifData::ConvertAnyFormat(void * ValuePtr, int Format) +{ + double Value; + Value = 0; + + switch(Format){ + case FMT_SBYTE: Value = *(signed char *)ValuePtr; break; + case FMT_BYTE: Value = *(uchar *)ValuePtr; break; + + case FMT_USHORT: Value = Get16u(ValuePtr); break; + + case FMT_ULONG: Value = Get32u(ValuePtr); break; + + case FMT_URATIONAL: + case FMT_SRATIONAL: + { + int Num,Den; + Num = Get32s(ValuePtr); + Den = Get32s(4+(char *)ValuePtr); + if (Den == 0){ + Value = 0; + }else{ + Value = (double)Num/Den; + } + break; + } + + case FMT_SSHORT: Value = (signed short)Get16u(ValuePtr); break; + case FMT_SLONG: Value = Get32s(ValuePtr); break; + + // Not sure if this is correct (never seen float used in Exif format) + case FMT_SINGLE: Value = (double)*(float *)ValuePtr; break; + case FMT_DOUBLE: Value = *(double *)ValuePtr; break; + } + return Value; +} + +//-------------------------------------------------------------------------- +// Process one of the nested EXIF directories. +//-------------------------------------------------------------------------- +void ExifData::ProcessExifDir(unsigned char * DirStart, unsigned char * OffsetBase, unsigned ExifLength, unsigned NestingLevel) +{ + int de; + int a; + int NumDirEntries; + unsigned ThumbnailOffset = 0; + unsigned ThumbnailSize = 0; + + if ( NestingLevel > 4) + throw FatalError("Maximum directory nesting exceeded (corrupt exif header)"); + + 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{ + // Note: Files that had thumbnails trimmed with jhead 1.3 or earlier + // might trigger this. + throw FatalError("Illegally sized directory"); + } + } + if (DirEnd < LastExifRefd) LastExifRefd = DirEnd; + } + + for (de=0;de<NumDirEntries;de++){ + int Tag, Format, Components; + unsigned char * ValuePtr; + unsigned ByteCount; + char * DirEntry; + DirEntry = (char *)DIR_ENTRY_ADDR(DirStart, de); + + Tag = Get16u(DirEntry); + Format = Get16u(DirEntry+2); + Components = Get32u(DirEntry+4); + + if ((Format-1) >= NUM_FORMATS) { + // (-1) catches illegal zero case as unsigned underflows to positive large. + throw FatalError("Illegal format code in EXIF dir"); + } + + if ((unsigned)Components > 0x10000) { + throw FatalError("Illegal number of components for tag"); + continue; + } + + ByteCount = Components * BytesPerFormat[Format]; + + if (ByteCount > 4){ + unsigned OffsetVal; + OffsetVal = Get32u(DirEntry+8); + // If its bigger than 4 bytes, the dir entry contains an offset. + if (OffsetVal+ByteCount > ExifLength){ + // Bogus pointer offset and / or bytecount value + //printf("Offset %d bytes %d ExifLen %d\n",OffsetVal, ByteCount, ExifLength); + + throw FatalError("Illegal pointer offset value in EXIF"); + } + ValuePtr = OffsetBase+OffsetVal; + }else{ + // 4 bytes or less and value is in the dir entry itself + ValuePtr = (unsigned char *)DirEntry+8; + } + + if (LastExifRefd < ValuePtr+ByteCount){ + // Keep track of last byte in the exif header that was actually referenced. + // That way, we know where the discardable thumbnail data begins. + LastExifRefd = ValuePtr+ByteCount; + } + + // Extract useful components of tag + switch(Tag){ + + case TAG_MAKE: + ExifData::CameraMake = TQString::fromLatin1((const char*)ValuePtr, 31); + break; + + case TAG_MODEL: + ExifData::CameraModel = TQString::fromLatin1((const char*)ValuePtr, 39); + break; + + case TAG_ORIENTATION: + Orientation = (int)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_DATETIME_ORIGINAL: + DateTime = TQString::fromLatin1((const char*)ValuePtr, 19); + break; + + case TAG_USERCOMMENT: + // Olympus has this padded with trailing spaces. Remove these first. + for (a=ByteCount;;){ + a--; + if ((ValuePtr)[a] == ' '){ + (ValuePtr)[a] = '\0'; + }else{ + break; + } + if (a == 0) break; + } + + // Copy the comment + if (memcmp(ValuePtr, "ASCII",5) == 0){ + for (a=5;a<10;a++){ + int c; + c = (ValuePtr)[a]; + if (c != '\0' && c != ' '){ + UserComment = TQString::fromLatin1((const char*)(a+ValuePtr), 199); + break; + } + } + }else{ + UserComment = TQString::fromLatin1((const char*)ValuePtr, 199); + } + break; + + case TAG_FNUMBER: + // Simplest way of expressing aperture, so I trust it the most. + // (overwrite previously computd value if there is one) + ExifData::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 (ExifData::ApertureFNumber == 0){ + ExifData::ApertureFNumber + = (float)exp(ConvertAnyFormat(ValuePtr, Format)*log(2.0)*0.5); + } + break; + + case TAG_FOCALLENGTH: + // Nice digital cameras actually save the focal length as a function + // of how far they are zoomed in. + ExifData::FocalLength = (float)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_SUBJECT_DISTANCE: + // Inidcates the distacne the autofocus camera is focused to. + // Tends to be less accurate as distance increases. + ExifData::Distance = (float)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_EXPOSURETIME: + // Simplest way of expressing exposure time, so I trust it most. + // (overwrite previously computd value if there is one) + ExifData::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 (ExifData::ExposureTime == 0){ + ExifData::ExposureTime + = (float)(1/exp(ConvertAnyFormat(ValuePtr, Format)*log(2.0))); + } + break; + + case TAG_FLASH: + ExifData::FlashUsed = (int)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_EXIF_IMAGELENGTH: + ExifImageLength = (int)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_EXIF_IMAGEWIDTH: + ExifImageWidth = (int)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_FOCALPLANEXRES: + FocalplaneXRes = ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_FOCALPLANEUNITS: + switch((int)ConvertAnyFormat(ValuePtr, Format)){ + case 1: FocalplaneUnits = 25.4; break; // inch + case 2: + // According to the information I was using, 2 means meters. + // But looking at the Cannon powershot's files, inches is the only + // sensible value. + FocalplaneUnits = 25.4; + break; + + case 3: FocalplaneUnits = 10; break; // centimeter + case 4: FocalplaneUnits = 1; break; // milimeter + case 5: FocalplaneUnits = .001; break; // micrometer + } + break; + + // Remaining cases contributed by: Volker C. Schoech ([email protected]) + + case TAG_EXPOSURE_BIAS: + ExifData::ExposureBias = (float)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_WHITEBALANCE: + ExifData::Whitebalance = (int)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_METERING_MODE: + ExifData::MeteringMode = (int)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_EXPOSURE_PROGRAM: + ExifData::ExposureProgram = (int)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_ISO_EQUIVALENT: + ExifData::ISOequivalent = (int)ConvertAnyFormat(ValuePtr, Format); + if ( ExifData::ISOequivalent < 50 ) ExifData::ISOequivalent *= 200; + break; + + case TAG_COMPRESSION_LEVEL: + ExifData::CompressionLevel = (int)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_THUMBNAIL_OFFSET: + ThumbnailOffset = (unsigned)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_THUMBNAIL_LENGTH: + ThumbnailSize = (unsigned)ConvertAnyFormat(ValuePtr, Format); + break; + + } + + if (Tag == TAG_EXIF_OFFSET || Tag == TAG_INTEROP_OFFSET){ + unsigned char * SubdirStart; + SubdirStart = OffsetBase + Get32u(ValuePtr); + if (SubdirStart <= OffsetBase || SubdirStart >= OffsetBase+ExifLength){ + throw FatalError("Illegal subdirectory link"); + } + ProcessExifDir(SubdirStart, OffsetBase, ExifLength, NestingLevel+1); + continue; + } + } + + { + // 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 comitee! + unsigned char * SubdirStart; + unsigned Offset; + + if (DIR_ENTRY_ADDR(DirStart, NumDirEntries) + 4 <= OffsetBase+ExifLength){ + Offset = Get32u(DIR_ENTRY_ADDR(DirStart, NumDirEntries)); + // There is at least one jpeg from an HP camera having an Offset of almost MAXUINT. + // Adding OffsetBase to it produces an overflow, so compare with ExifLength here. + // See http://bugs.kde.org/show_bug.cgi?id=54542 + if (Offset && Offset < ExifLength){ + SubdirStart = OffsetBase + Offset; + if (SubdirStart > OffsetBase+ExifLength){ + if (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 + kdDebug(7034) << "Thumbnail removed with Jhead 1.3 or earlier\n"; + }else{ + throw FatalError("Illegal subdirectory link 2"); + } + }else{ + if (SubdirStart <= OffsetBase+ExifLength){ + ProcessExifDir(SubdirStart, OffsetBase, ExifLength, NestingLevel+1); + } + } + } + }else{ + // The exif header ends before the last next directory pointer. + } + } + + if (ThumbnailSize && ThumbnailOffset){ + if (ThumbnailSize + ThumbnailOffset < ExifLength){ + // The thumbnail pointer appears to be valid. Store it. + Thumbnail.loadFromData(OffsetBase + ThumbnailOffset, ThumbnailSize, "JPEG"); + } + } +} + +//-------------------------------------------------------------------------- +// Process a COM marker. We want to leave the bytes unchanged. The +// progam that displays this text may decide to remove blanks, convert +// newlines, or otherwise modify the text. In particular we want to be +// safe for passing utf-8 text. +//-------------------------------------------------------------------------- +void ExifData::process_COM (const uchar * Data, int length) +{ + Comment = TQString::fromUtf8((char *)Data+2, (length-2)); +} + + +//-------------------------------------------------------------------------- +// Process a SOFn marker. This is useful for the image dimensions +//-------------------------------------------------------------------------- +void ExifData::process_SOFn (const uchar * Data, int marker) +{ + int data_precision, num_components; + + data_precision = Data[2]; + ExifData::Height = Get16m(Data+3); + ExifData::Width = Get16m(Data+5); + num_components = Data[7]; + + if (num_components == 3){ + ExifData::IsColor = 1; + }else{ + ExifData::IsColor = 0; + } + + ExifData::Process = marker; + +} + +//-------------------------------------------------------------------------- +// Get 16 bits motorola order (always) for jpeg header stuff. +//-------------------------------------------------------------------------- +int ExifData::Get16m(const void * Short) +{ + return (((uchar *)Short)[0] << 8) | ((uchar *)Short)[1]; +} + + +//-------------------------------------------------------------------------- +// Process a EXIF marker +// Describes all the drivel that most digital cameras include... +//-------------------------------------------------------------------------- +void ExifData::process_EXIF(unsigned char * CharBuf, unsigned int length) +{ + ExifData::FlashUsed = 0; // If it s from a digicam, and it used flash, it says so. + + FocalplaneXRes = 0; + FocalplaneUnits = 0; + ExifImageWidth = 0; + ExifImageLength = 0; + + { // Check the EXIF header component + static const uchar ExifHeader[] = "Exif\0\0"; + if (memcmp(CharBuf+2, ExifHeader,6)){ + throw FatalError("Incorrect Exif header"); + } + } + + if (memcmp(CharBuf+8,"II",2) == 0){ + // printf("Exif section in Intel order\n"); + MotorolaOrder = 0; + }else{ + if (memcmp(CharBuf+8,"MM",2) == 0){ + // printf("Exif section in Motorola order\n"); + MotorolaOrder = 1; + }else{ + throw FatalError("Invalid Exif alignment marker."); + } + } + + // Check the next two values for correctness. + if (Get16u(CharBuf+10) != 0x2a){ + throw FatalError("Invalid Exif start (1)"); + } + + long IFDoffset = Get32u(CharBuf+12); + + LastExifRefd = CharBuf; + + // First directory starts 16 bytes in. Offsets start at 8 bytes in. + ProcessExifDir(&CharBuf[8+IFDoffset], CharBuf+8, length-6, 0); + + // This is how far the interesting (non thumbnail) part of the exif went. + ExifSettingsLength = LastExifRefd - CharBuf; + + // Compute the CCD width, in milimeters. + if (FocalplaneXRes != 0){ + kdDebug(7034) << "ExifImageWidth " << ExifImageWidth << " FocalplaneUnits " << FocalplaneUnits << " FocalplaneXRes " << FocalplaneXRes << endl; + ExifData::CCDWidth = (float)(ExifImageWidth * FocalplaneUnits / FocalplaneXRes); + } +} + +//-------------------------------------------------------------------------- +// Convert exif time to Unix time structure +//-------------------------------------------------------------------------- +int ExifData::Exif2tm(struct tm * timeptr, char * ExifTime) +{ + int a; + + timeptr->tm_wday = -1; + + // Check for format: YYYY:MM:DD HH:MM:SS format. + a = sscanf(ExifTime, "%d:%d:%d %d:%d:%d", + &timeptr->tm_year, &timeptr->tm_mon, &timeptr->tm_mday, + &timeptr->tm_hour, &timeptr->tm_min, &timeptr->tm_sec); + + if (a == 6){ + timeptr->tm_isdst = -1; + timeptr->tm_mon -= 1; // Adjust for unix zero-based months + timeptr->tm_year -= 1900; // Adjust for year starting at 1900 + return true; // worked. + } + + return false; // Wasn't in Exif date format. +} + +//-------------------------------------------------------------------------- +// Contructor for initialising +//-------------------------------------------------------------------------- +ExifData::ExifData() +{ + ExifData::Whitebalance = -1; + ExifData::MeteringMode = -1; + ExifData::FlashUsed = 0; + Orientation = 0; + Height = 0; + Width = 0; + IsColor = 0; + Process = 0; + FocalLength = 0; + ExposureTime = 0; + ApertureFNumber = 0; + Distance = 0; + CCDWidth = 0; + ExposureBias = 0; + ExposureProgram = 0; + ISOequivalent = 0; + CompressionLevel = 0; +} + +//-------------------------------------------------------------------------- +// process a EXIF jpeg file +//-------------------------------------------------------------------------- +bool ExifData::scan(const TQString & path) +{ + int ret; + + TQFile f(path); + if ( !f.open(IO_ReadOnly) ) + return false; + + try { + // Scan the JPEG headers. + ret = ReadJpegSections(f, READ_EXIF); + } + catch (FatalError& e) { + e.debug_print(); + f.close(); + return false; + } + + if (ret == false){ + kdDebug(7034) << "Not JPEG file!\n"; + DiscardData(); + f.close(); + return false; + } + f.close(); + DiscardData(); + + //now make the strings clean, + // for exmaple my Casio is a "QV-4000 " + CameraMake = CameraMake.stripWhiteSpace(); + CameraModel = CameraModel.stripWhiteSpace(); + UserComment = UserComment.stripWhiteSpace(); + Comment = Comment.stripWhiteSpace(); + return true; +} + +//-------------------------------------------------------------------------- +// Does the embedded thumbnail match the jpeg image? +//-------------------------------------------------------------------------- +#ifndef JPEG_TOL +#define JPEG_TOL 0.02 +#endif +bool ExifData::isThumbnailSane() { + if (Thumbnail.isNull()) return false; + + // check whether thumbnail dimensions match the image + // not foolproof, but catches some altered images (jpegtran -rotate) + if (ExifImageLength != 0 && ExifImageLength != Height) return false; + if (ExifImageWidth != 0 && ExifImageWidth != Width) return false; + if (Thumbnail.width() == 0 || Thumbnail.height() == 0) return false; + if (Height == 0 || Width == 0) return false; + double d = (double)Height/Width*Thumbnail.width()/Thumbnail.height(); + return (1-JPEG_TOL < d) && (d < 1+JPEG_TOL); +} + + +//-------------------------------------------------------------------------- +// return a thumbnail that respects the orientation flag +// only if it seems sane +//-------------------------------------------------------------------------- +TQImage ExifData::getThumbnail() { + if (!isThumbnailSane()) return NULL; + if (!Orientation || Orientation == 1) return Thumbnail; + + // now fix orientation + TQWMatrix M; + TQWMatrix flip= TQWMatrix(-1,0,0,1,0,0); + switch (Orientation) { // notice intentional fallthroughs + case 2: M = flip; break; + case 4: M = flip; + case 3: M.rotate(180); break; + case 5: M = flip; + case 6: M.rotate(90); break; + case 7: M = flip; + case 8: M.rotate(270); break; + default: break; // should never happen + } + return Thumbnail.xForm(M); +} diff --git a/tdefile-plugins/jpeg/exif.h b/tdefile-plugins/jpeg/exif.h new file mode 100644 index 00000000..f8eb13ef --- /dev/null +++ b/tdefile-plugins/jpeg/exif.h @@ -0,0 +1,127 @@ +#ifndef __EXIF_H__ +#define __EXIF_H__ + +/** + exif.h +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <time.h> + +#include "tqstring.h" +#include "tqfile.h" +#include "tqimage.h" +#include <kdebug.h> + +typedef enum { + READ_EXIF = 1, + READ_IMAGE = 2, + READ_ALL = 3 +}ReadMode_t; + +//-------------------------------------------------------------------------- +// This structure is used to store jpeg file sections in memory. +typedef struct { + uchar * Data; + int Type; + unsigned Size; +}Section_t; + +typedef unsigned char uchar; + +typedef struct { + unsigned short Tag; + const char*const Desc; +}TagTable_t; + +#define MAX_SECTIONS 20 +#define PSEUDO_IMAGE_MARKER 0x123; // Extra value. + +class ExifData { + Section_t Sections[MAX_SECTIONS]; + + TQString CameraMake; + TQString CameraModel; + TQString DateTime; + int Orientation; + int Height, Width; + int ExifImageLength, ExifImageWidth; + int IsColor; + int Process; + int FlashUsed; + float FocalLength; + float ExposureTime; + float ApertureFNumber; + float Distance; + int Whitebalance; + int MeteringMode; + float CCDWidth; + float ExposureBias; + int ExposureProgram; + int ISOequivalent; + int CompressionLevel; + TQString UserComment; + TQString Comment; + TQImage Thumbnail; + + int ReadJpegSections (TQFile & infile, ReadMode_t ReadMode); + void DiscardData(void); + int Get16u(void * Short); + int Get32s(void * Long); + unsigned Get32u(void * Long); + double ConvertAnyFormat(void * ValuePtr, int Format); + void ProcessExifDir(unsigned char * DirStart, unsigned char * OffsetBase, unsigned ExifLength, + unsigned NestingLevel); + void process_COM (const uchar * Data, int length); + void process_SOFn (const uchar * Data, int marker); + int Get16m(const void * Short); + void process_EXIF(unsigned char * CharBuf, unsigned int length); + int Exif2tm(struct tm * timeptr, char * ExifTime); + +public: + ExifData(); + bool scan(const TQString &); + TQString getCameraMake() { return CameraMake; } + TQString getCameraModel() { return CameraModel; } + TQString getDateTime() { return DateTime; } + int getOrientation() { return Orientation; } + int getHeight() { return Height; } + int getWidth() { return Width; } + int getIsColor() { return IsColor; } + int getProcess() { return Process; } + int getFlashUsed() { return FlashUsed; } + float getFocalLength() { return FocalLength; } + float getExposureTime() { return ExposureTime; } + float getApertureFNumber() { return ApertureFNumber; } + float getDistance() { return Distance; } + int getWhitebalance() { return Whitebalance; } + int getMeteringMode() { return MeteringMode; } + float getCCDWidth() { return CCDWidth; } + float getExposureBias() { return ExposureBias; } + int getExposureProgram() { return ExposureProgram; } + int getISOequivalent() { return ISOequivalent; } + int getCompressionLevel() { return CompressionLevel; } + TQString getUserComment() { return UserComment; } + TQString getComment() { return Comment; } + TQImage getThumbnail(); + bool isThumbnailSane(); + bool isNullThumbnail() { return !isThumbnailSane(); } +}; + +class FatalError { + const char* ex; +public: + FatalError(const char* s) { ex = s; } + void debug_print() const { kdDebug(7034) << "exception: " << ex << endl; } +}; + +extern TagTable_t ProcessTable[]; + +//-------------------------------------------------------------------------- +// Define comment writing code, impelemented in setcomment.c +extern int safe_copy_and_modify( const char * original_filename, const char * comment ); + +#endif + diff --git a/tdefile-plugins/jpeg/tdefile_jpeg.cpp b/tdefile-plugins/jpeg/tdefile_jpeg.cpp new file mode 100644 index 00000000..c902b99b --- /dev/null +++ b/tdefile-plugins/jpeg/tdefile_jpeg.cpp @@ -0,0 +1,531 @@ +/* This file is part of the KDE project + * Copyright (C) 2002 Frank Pieczynski <[email protected]>, + * 2002 Carsten Pfeiffer <[email protected]> + * based on the jhead tool of Matthias Wandel (see below) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + + +#include <stdlib.h> +#include "tdefile_jpeg.h" + +#include <kurl.h> +#include <kprocess.h> +#include <klocale.h> +#include <kgenericfactory.h> +#include <kdebug.h> + +#include <tqcstring.h> +#include <tqfile.h> +#include <tqdatetime.h> +#include <tqdict.h> +#include <tqvalidator.h> +#include <tqimage.h> + +#include "exif.h" + +#define EXIFGROUP "Jpeg EXIF Data" + +typedef KGenericFactory<KJpegPlugin> JpegFactory; + +K_EXPORT_COMPONENT_FACTORY(tdefile_jpeg, JpegFactory("tdefile_jpeg")) + +KJpegPlugin::KJpegPlugin(TQObject *parent, const char *name, + const TQStringList &args ) + : KFilePlugin(parent, name, args) +{ + kdDebug(7034) << "jpeg plugin\n"; + + // + // define all possible meta info items + // + KFileMimeTypeInfo *info = addMimeTypeInfo("image/jpeg"); + KFileMimeTypeInfo::GroupInfo *exifGroup = addGroupInfo( info, EXIFGROUP, + i18n("JPEG Exif") ); + KFileMimeTypeInfo::ItemInfo* item; + + item = addItemInfo( exifGroup, "Comment", i18n("Comment"), TQVariant::String); + setAttributes( item, + KFileMimeTypeInfo::Modifiable | + KFileMimeTypeInfo::Addable | + KFileMimeTypeInfo::MultiLine ); + + item = addItemInfo( exifGroup, "Manufacturer", i18n("Camera Manufacturer"), + TQVariant::String ); + + item = addItemInfo( exifGroup, "Model", i18n("Camera Model"), + TQVariant::String ); + + item = addItemInfo( exifGroup, "Date/time", i18n("Date/Time"), + TQVariant::DateTime ); + + item = addItemInfo( exifGroup, "CreationDate", i18n("Creation Date"), + TQVariant::Date ); + + item = addItemInfo( exifGroup, "CreationTime", i18n("Creation Time"), + TQVariant::Time ); + + item = addItemInfo( exifGroup, "Dimensions", i18n("Dimensions"), + TQVariant::Size ); + setHint( item, KFileMimeTypeInfo::Size ); + setUnit( item, KFileMimeTypeInfo::Pixels ); + + item = addItemInfo( exifGroup, "Orientation", i18n("Orientation"), + TQVariant::Int ); + + item = addItemInfo( exifGroup, "ColorMode", i18n("Color Mode"), + TQVariant::String ); + + item = addItemInfo( exifGroup, "Flash used", i18n("Flash Used"), + TQVariant::String ); + item = addItemInfo( exifGroup, "Focal length", i18n("Focal Length"), + TQVariant::String ); + setUnit( item, KFileMimeTypeInfo::Millimeters ); + + item = addItemInfo( exifGroup, "35mm equivalent", i18n("35mm Equivalent"), + TQVariant::Int ); + setUnit( item, KFileMimeTypeInfo::Millimeters ); + + item = addItemInfo( exifGroup, "CCD width", i18n("CCD Width"), + TQVariant::String ); + setUnit( item, KFileMimeTypeInfo::Millimeters ); + + item = addItemInfo( exifGroup, "Exposure time", i18n("Exposure Time"), + TQVariant::String ); + setHint( item, KFileMimeTypeInfo::Seconds ); + + item = addItemInfo( exifGroup, "Aperture", i18n("Aperture"), + TQVariant::String ); + + item = addItemInfo( exifGroup, "Focus dist.", i18n("Focus Dist."), + TQVariant::String ); + + item = addItemInfo( exifGroup, "Exposure bias", i18n("Exposure Bias"), + TQVariant::String ); + + item = addItemInfo( exifGroup, "Whitebalance", i18n("Whitebalance"), + TQVariant::String ); + + item = addItemInfo( exifGroup, "Metering mode", i18n("Metering Mode"), + TQVariant::String ); + + item = addItemInfo( exifGroup, "Exposure", i18n("Exposure"), + TQVariant::String ); + + item = addItemInfo( exifGroup, "ISO equiv.", i18n("ISO Equiv."), + TQVariant::String ); + + item = addItemInfo( exifGroup, "JPEG quality", i18n("JPEG Quality"), + TQVariant::String ); + + item = addItemInfo( exifGroup, "User comment", i18n("User Comment"), + TQVariant::String ); + setHint(item, KFileMimeTypeInfo::Description); + + item = addItemInfo( exifGroup, "JPEG process", i18n("JPEG Process"), + TQVariant::String ); + + item = addItemInfo( exifGroup, "Thumbnail", i18n("Thumbnail"), + TQVariant::Image ); + setHint( item, KFileMimeTypeInfo::Thumbnail ); + +// ### +// exifGroup.setSupportsVariableKeys(true); +} + +TQValidator* KJpegPlugin::createValidator(const KFileMetaInfoItem& /*item*/, + TQObject */*parent*/, + const char */*name*/ ) const +{ + // no need to return a validator that validates everything as OK :) +// if (item.isEditable()) +// return new TQRegExpValidator(TQRegExp(".*"), parent, name); +// else + return 0L; +} + +bool KJpegPlugin::writeInfo( const KFileMetaInfo& info ) const +{ + TQString comment = info[EXIFGROUP].value("Comment").toString(); + TQString path = info.path(); + + kdDebug(7034) << "exif writeInfo: " << info.path() << " \"" << comment << "\"\n"; + + /* + Do a strictly safe insertion of the comment: + + Scan original to verify it's a proper jpeg + Open a unique temporary file in this directory + Write temporary, replacing all COM blocks with this one. + Scan temporary, to verify it's a proper jpeg + Rename original to another unique name + Rename temporary to original + Unlink original + */ + /* + The jpeg standard does not regulate the contents of the COM block. + I'm assuming the best thing to do here is write as unicode utf-8, + which is fully backwards compatible with readers expecting ascii. + Readers expecting a national character set are out of luck... + */ + if( safe_copy_and_modify( TQFile::encodeName( path ), comment.utf8() ) ) { + return false; + } + return true; +} + +bool KJpegPlugin::readInfo( KFileMetaInfo& info, uint what ) +{ + const TQString path( info.path() ); + if ( path.isEmpty() ) // remote file + return false; + + TQString tag; + ExifData ImageInfo; + + // parse the jpeg file now + try { + if ( !ImageInfo.scan(info.path()) ) { + kdDebug(7034) << "Not a JPEG file!\n"; + return false; + } + } + catch (FatalError& e) { // malformed exif data? + kdDebug(7034) << "Exception caught while parsing Exif data of: " << info.path() << endl; + e.debug_print(); + return false; + } + + KFileMetaInfoGroup exifGroup = appendGroup( info, EXIFGROUP ); + + tag = ImageInfo.getComment(); + if ( tag.length() ) { + kdDebug(7034) << "exif inserting Comment: " << tag << "\n"; + appendItem( exifGroup, "Comment", tag ); + } else { + appendItem( exifGroup, "Comment", tag ); // So user can add new comment + } + + tag = ImageInfo.getCameraMake(); + if (tag.length()) + appendItem( exifGroup, "Manufacturer", tag ); + + tag = ImageInfo.getCameraModel(); + if (tag.length()) + appendItem( exifGroup, "Model", tag ); + + tag = ImageInfo.getDateTime(); + if (tag.length()){ + TQDateTime dt = parseDateTime( tag.stripWhiteSpace() ); + if ( dt.isValid() ) { + appendItem( exifGroup, "Date/time", dt ); + appendItem( exifGroup, "CreationDate", dt.date() ); + appendItem( exifGroup, "CreationTime", dt.time() ); + } + } + + appendItem( exifGroup,"Dimensions", TQSize( ImageInfo.getWidth(), + ImageInfo.getHeight() ) ); + + if ( ImageInfo.getOrientation() ) + appendItem( exifGroup, "Orientation", ImageInfo.getOrientation() ); + + appendItem( exifGroup, "ColorMode", ImageInfo.getIsColor() ? + i18n("Color") : i18n("Black and white") ); + + int flashUsed = ImageInfo.getFlashUsed(); // -1, <set> + if ( flashUsed >= 0 ) { + TQString flash = i18n("Flash", "(unknown)"); + switch ( flashUsed ) { + case 0: flash = i18n("Flash", "No"); + break; + case 1: + case 5: + case 7: + flash = i18n("Flash", "Fired"); + break; + case 9: + case 13: + case 15: + flash = i18n( "Flash", "Fill Fired" ); + break; + case 16: + flash = i18n( "Flash", "Off" ); + break; + case 24: + flash = i18n( "Flash", "Auto Off" ); + break; + case 25: + case 29: + case 31: + flash = i18n( "Flash", "Auto Fired" ); + break; + case 32: + flash = i18n( "Flash", "Not Available" ); + break; + default: + break; + } + appendItem( exifGroup, "Flash used", + flash ); + } + + if (ImageInfo.getFocalLength()){ + appendItem( exifGroup, "Focal length", + TQString(TQString().sprintf("%4.1f", ImageInfo.getFocalLength()) )); + + if (ImageInfo.getCCDWidth()){ + appendItem( exifGroup, "35mm equivalent", + (int)(ImageInfo.getFocalLength()/ImageInfo.getCCDWidth()*35 + 0.5) ); + } + } + + if (ImageInfo.getCCDWidth()){ + appendItem( exifGroup, "CCD width", + TQString(TQString().sprintf("%4.2f", ImageInfo.getCCDWidth()) )); + } + + if (ImageInfo.getExposureTime()){ + tag=TQString().sprintf("%6.3f", ImageInfo.getExposureTime()); + float exposureTime = ImageInfo.getExposureTime(); + if (exposureTime > 0 && exposureTime <= 0.5){ + tag+=TQString().sprintf(" (1/%d)", (int)(0.5 + 1/exposureTime) ); + } + appendItem( exifGroup, "Exposure time", tag ); + } + + if (ImageInfo.getApertureFNumber()){ + appendItem( exifGroup, "Aperture", + TQString(TQString().sprintf("f/%3.1f", + (double)ImageInfo.getApertureFNumber()))); + } + + if (ImageInfo.getDistance()){ + if (ImageInfo.getDistance() < 0){ + tag=i18n("Infinite"); + }else{ + tag=TQString().sprintf("%5.2fm",(double)ImageInfo.getDistance()); + } + appendItem( exifGroup, "Focus dist.", tag ); + } + + if (ImageInfo.getExposureBias()){ + appendItem( exifGroup, "Exposure bias", + TQString(TQString().sprintf("%4.2f", + (double)ImageInfo.getExposureBias()) )); + } + + if (ImageInfo.getWhitebalance() != -1){ + switch(ImageInfo.getWhitebalance()) { + case 0: + tag=i18n("Unknown"); + break; + case 1: + tag=i18n("Daylight"); + break; + case 2: + tag=i18n("Fluorescent"); + break; + case 3: + //tag=i18n("incandescent"); + tag=i18n("Tungsten"); + break; + case 17: + tag=i18n("Standard light A"); + break; + case 18: + tag=i18n("Standard light B"); + break; + case 19: + tag=i18n("Standard light C"); + break; + case 20: + tag=i18n("D55"); + break; + case 21: + tag=i18n("D65"); + break; + case 22: + tag=i18n("D75"); + break; + case 255: + tag=i18n("Other"); + break; + default: + //23 to 254 = reserved + tag=i18n("Unknown"); + } + appendItem( exifGroup, "Whitebalance", tag ); + } + + if (ImageInfo.getMeteringMode() != -1){ + switch(ImageInfo.getMeteringMode()) { + case 0: + tag=i18n("Unknown"); + break; + case 1: + tag=i18n("Average"); + break; + case 2: + tag=i18n("Center weighted average"); + break; + case 3: + tag=i18n("Spot"); + break; + case 4: + tag=i18n("MultiSpot"); + break; + case 5: + tag=i18n("Pattern"); + break; + case 6: + tag=i18n("Partial"); + break; + case 255: + tag=i18n("Other"); + break; + default: + // 7 to 254 = reserved + tag=i18n("Unknown"); + } + appendItem( exifGroup, "Metering mode", tag ); + } + + if (ImageInfo.getExposureProgram()){ + switch(ImageInfo.getExposureProgram()) { + case 0: + tag=i18n("Not defined"); + break; + case 1: + tag=i18n("Manual"); + break; + case 2: + tag=i18n("Normal program"); + break; + case 3: + tag=i18n("Aperture priority"); + break; + case 4: + tag=i18n("Shutter priority"); + break; + case 5: + tag=i18n("Creative program\n(biased toward fast shutter speed)"); + break; + case 6: + tag=i18n("Action program\n(biased toward fast shutter speed)"); + break; + case 7: + tag=i18n("Portrait mode\n(for closeup photos with the background out of focus)"); + break; + case 8: + tag=i18n("Landscape mode\n(for landscape photos with the background in focus)"); + break; + default: + // 9 to 255 = reserved + tag=i18n("Unknown"); + } + appendItem( exifGroup, "Exposure", tag ); + } + + if (ImageInfo.getISOequivalent()){ + appendItem( exifGroup, "ISO equiv.", + TQString(TQString().sprintf("%2d", + (int)ImageInfo.getISOequivalent()) )); + } + + if (ImageInfo.getCompressionLevel()){ + switch(ImageInfo.getCompressionLevel()) { + case 1: + tag=i18n("Basic"); + break; + case 2: + tag=i18n("Normal"); + break; + case 4: + tag=i18n("Fine"); + break; + default: + tag=i18n("Unknown"); + } + appendItem( exifGroup, "JPEG quality", tag ); + } + + tag = ImageInfo.getUserComment(); + if (tag.length()){ + appendItem( exifGroup, "EXIF comment", tag ); + } + + int a; + for (a=0;;a++){ + if (ProcessTable[a].Tag == ImageInfo.getProcess() || ProcessTable[a].Tag == 0){ + appendItem( exifGroup, "JPEG process", + TQString::fromUtf8( ProcessTable[a].Desc) ); + break; + } + } + + if ( what & KFileMetaInfo::Thumbnail && !ImageInfo.isNullThumbnail() ){ + appendItem( exifGroup, "Thumbnail", ImageInfo.getThumbnail() ); + } + + return true; +} + +// format of the string is: +// YYYY:MM:DD HH:MM:SS +TQDateTime KJpegPlugin::parseDateTime( const TQString& string ) +{ + TQDateTime dt; + if ( string.length() != 19 ) + return dt; + + TQString year = string.left( 4 ); + TQString month = string.mid( 5, 2 ); + TQString day = string.mid( 8, 2 ); + TQString hour = string.mid( 11, 2 ); + TQString minute = string.mid( 14, 2 ); + TQString seconds = string.mid( 17, 2 ); + + bool ok; + bool allOk = true; + int y = year.toInt( &ok ); + allOk &= ok; + + int mo = month.toInt( &ok ); + allOk &= ok; + + int d = day.toInt( &ok ); + allOk &= ok; + + int h = hour.toInt( &ok ); + allOk &= ok; + + int mi = minute.toInt( &ok ); + allOk &= ok; + + int s = seconds.toInt( &ok ); + allOk &= ok; + + if ( allOk ) { + dt.setDate( TQDate( y, mo, d ) ); + dt.setTime( TQTime( h, mi, s ) ); + } + + return dt; +} + +#include "tdefile_jpeg.moc" diff --git a/tdefile-plugins/jpeg/tdefile_jpeg.desktop b/tdefile-plugins/jpeg/tdefile_jpeg.desktop new file mode 100644 index 00000000..d2cfa0ff --- /dev/null +++ b/tdefile-plugins/jpeg/tdefile_jpeg.desktop @@ -0,0 +1,64 @@ +[Desktop Entry] +Type=Service +Name=JPEG EXIF Info +Name[ar]=معلومات JPEG EXIF +Name[br]=Titouroù EXIF JPEG +Name[ca]=Informació de JPEG EXIF +Name[cs]=JPEG EXIF info +Name[cy]=Gwybodaeth JPEG EXIF +Name[da]=JPEG EXIF-info +Name[de]=JPEG EXIF-Info +Name[el]=Πληροφορίες JPEG EXIF +Name[eo]=JPEG-EXIF-informo +Name[es]=Info JPEG EXIF +Name[et]=JPEG EXIF info +Name[fa]=اطلاعات JPEG EXIF +Name[fi]=JPEG EXIF -tiedot +Name[fr]=Informations JPEG EXIF +Name[gl]=Inf. JPEG EXIF +Name[he]=מידע JPEG EXIF +Name[hi]=JPEG EXIF जानकारी +Name[hr]=JPEG EXIF Informacije +Name[hu]=JPEG EXIF-jellemzők +Name[is]=JPEG EXIF upplýsingar +Name[it]=Informazioni JPEG EXIF +Name[ja]=JPEG EXIF 情報 +Name[kk]=JPEG EXIF мәліметі +Name[km]=ព័ត៌មាន JPEG EXIF +Name[lt]=JPEG EXIF informacija +Name[ms]=Maklumat JPEG EXIF +Name[nds]=JPEG-EXIF-Info +Name[ne]=JPEG EXIF सूचना +Name[nl]=JPEG EXIF-info +Name[nn]=JPEG EXIF-info +Name[nso]=TshedimoJPEG EXIF Info +Name[pa]=JPEG EXIF ਜਾਣਕਾਰੀ +Name[pl]=Informacja o pliku JPEG EXIF +Name[pt]=Informação do JPEG EXIF +Name[pt_BR]=Informação sobre JPEG EXIF +Name[ro]=Informaţii EXIF JPEG +Name[ru]=Информация о JPEG EXIF +Name[se]=JPEG EXIF-dieđut +Name[sl]=Podatki o JPEG EXIF +Name[sr]=JPEG EXIF информације +Name[sr@Latn]=JPEG EXIF informacije +Name[sv]=JPEG EXIF-information +Name[ta]=JPEG EXIF தகவல் +Name[tg]=Иттилоот оиди JPEG EXIF +Name[th]=ข้อมูลแฟ้ม JPEG EXIF +Name[tr]=JPEG EXIF Bilgisi +Name[uk]=Інформація про JPEG EXIF +Name[uz]=JPEG EXIF haqida maʼlumot +Name[uz@cyrillic]=JPEG EXIF ҳақида маълумот +Name[ven]=Mafhungo a JPEG EXIF +Name[wa]=Informåcion sol imådje JPEG EXIF +Name[xh]=Ulwazi lwe JPEG EXIF +Name[zh_CN]=JPEG EXIF 信息 +Name[zh_HK]=JPEG EXIF 資訊 +Name[zh_TW]=JPEG EXIF 資訊 +Name[zu]=Ulwazi lwe-JPEG EXIF +ServiceTypes=KFilePlugin +X-TDE-Library=tdefile_jpeg +MimeType=image/jpeg +PreferredItems=User comment,CreationDate,CreationTime,Dimensions,Exposure time,JPEG quality,Comment +SupportsThumbnail=true diff --git a/tdefile-plugins/jpeg/tdefile_jpeg.h b/tdefile-plugins/jpeg/tdefile_jpeg.h new file mode 100644 index 00000000..979ae515 --- /dev/null +++ b/tdefile-plugins/jpeg/tdefile_jpeg.h @@ -0,0 +1,44 @@ +/* This file is part of the KDE project + * Copyright (C) 2002 Frank Pieczynski + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#ifndef __KFILE_JPEG_H__ +#define __KFILE_JPEG_H__ + +#include <tqdatetime.h> +#include <tdefilemetainfo.h> + +class KJpegPlugin: public KFilePlugin +{ + Q_OBJECT + + +public: + KJpegPlugin( TQObject *parent, const char *name, + const TQStringList& args ); + + virtual bool readInfo ( KFileMetaInfo& info, uint what ); + virtual bool writeInfo( const KFileMetaInfo& info ) const; + virtual TQValidator* createValidator( const KFileMetaInfoItem& item, + TQObject* parent, const char* name) const; + +private: + TQDateTime parseDateTime( const TQString& string ); +}; + +#endif diff --git a/tdefile-plugins/jpeg/tdefile_setcomment.cpp b/tdefile-plugins/jpeg/tdefile_setcomment.cpp new file mode 100644 index 00000000..07dca273 --- /dev/null +++ b/tdefile-plugins/jpeg/tdefile_setcomment.cpp @@ -0,0 +1,536 @@ +/* + * setcomment.cpp + * + * Copyright 2002 Bryce Nesbitt + * + * Based on wrjpgcom.c, Copyright (C) 1994-1997, Thomas G. Lane. + * Part of the Independent JPEG Group's software release 6b of 27-Mar-1998 + * + * This file contains a very simple stand-alone application that inserts + * user-supplied text as a COM (comment) marker in a JPEG/JFIF file. + * This may be useful as an example of the minimum logic needed to parse + * JPEG markers. + * + * There can be an arbitrary number of COM blocks in each jpeg file, with + * up to 64K of data each. We, however, write just one COM and blow away + * the rest. + * + ***************** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#undef STANDALONE_COMPILE + +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> +#include "config.h" + +extern int safe_copy_and_modify( const char * original_filename, const char * comment ); + +#ifdef DONT_USE_B_MODE /* define mode parameters for fopen() */ +#define READ_BINARY "r" +#define WRITE_BINARY "w" +#else +#ifdef VMS /* VMS is very nonstandard */ +#define READ_BINARY "rb", "ctx=stm" +#define WRITE_BINARY "wb", "ctx=stm" +#else /* standard ANSI-compliant case */ +#define READ_BINARY "rb" +#define WRITE_BINARY "wb" +#endif +#endif + +#define WARNING_GARBAGE 1 /* Original file had some unspecified content */ +#define ERROR_NOT_A_JPEG 5 /* Original file not a proper jpeg (must be 1st) */ +#define ERROR_TEMP_FILE 6 /* Problem writing temporay file */ +#define ERROR_SCREWUP 7 /* Original file is now damaged. Ooops. */ +#define ERROR_PREMATURE_EOF 8 /* Unexpected end of file */ +#define ERROR_BAD_MARKER 9 /* Marker with illegal length */ +#define ERROR_MARKER_ORDER 10 /* File seems to be mixed up */ + +static int global_error; /* global error flag. Once set, we're dead. */ + +/****************************************************************************/ +/* + * These macros are used to read the input file and write the output file. + * To reuse this code in another application, you might need to change these. + */ +static FILE * infile; /* input JPEG file */ + +/* Return next input byte, or EOF if no more */ +#define NEXTBYTE() getc(infile) + +static FILE * outfile; /* output JPEG file */ + +/* Emit an output byte */ +#define PUTBYTE(x) putc((x), outfile) + + +/****************************************************************************/ +/* Read one byte, testing for EOF */ +static int +read_1_byte (void) +{ + int c; + + c = NEXTBYTE(); + if (c == EOF) { + global_error = ERROR_PREMATURE_EOF; + } + return c; +} + +/* Read 2 bytes, convert to unsigned int */ +/* All 2-byte quantities in JPEG markers are MSB first */ +static unsigned int +read_2_bytes (void) +{ + int c1, c2; + + c1 = NEXTBYTE(); + if (c1 == EOF) + global_error = ERROR_PREMATURE_EOF; + c2 = NEXTBYTE(); + if (c2 == EOF) + global_error = ERROR_PREMATURE_EOF; + return (((unsigned int) c1) << 8) + ((unsigned int) c2); +} + + +/****************************************************************************/ +/* Routines to write data to output file */ +static void +write_1_byte (int c) +{ + PUTBYTE(c); +} + +static void +write_2_bytes (unsigned int val) +{ + PUTBYTE((val >> 8) & 0xFF); + PUTBYTE(val & 0xFF); +} + +static void +write_marker (int marker) +{ + PUTBYTE(0xFF); + PUTBYTE(marker); +} + +static void +copy_rest_of_file (void) +{ + int c; + + while ((c = NEXTBYTE()) != EOF) + PUTBYTE(c); +} + + +/****************************************************************************/ +/* + * JPEG markers consist of one or more 0xFF bytes, followed by a marker + * code byte (which is not an FF). Here are the marker codes of interest + * in this program. (See jdmarker.c for a more complete list.) + */ + +#define M_SOF0 0xC0 /* Start Of Frame N */ +#define M_SOF1 0xC1 /* N indicates which compression process */ +#define M_SOF2 0xC2 /* Only SOF0-SOF2 are now in common use */ +#define M_SOF3 0xC3 +#define M_SOF5 0xC5 /* NB: codes C4 and CC are NOT SOF markers */ +#define M_SOF6 0xC6 +#define M_SOF7 0xC7 +#define M_SOF9 0xC9 +#define M_SOF10 0xCA +#define M_SOF11 0xCB +#define M_SOF13 0xCD +#define M_SOF14 0xCE +#define M_SOF15 0xCF +#define M_SOI 0xD8 /* Start Of Image (beginning of datastream) */ +#define M_EOI 0xD9 /* End Of Image (end of datastream) */ +#define M_SOS 0xDA /* Start Of Scan (begins compressed data) */ +#define M_COM 0xFE /* COMment */ + + +/* + * Find the next JPEG marker and return its marker code. + * We expect at least one FF byte, possibly more if the compressor used FFs + * to pad the file. (Padding FFs will NOT be replicated in the output file.) + * There could also be non-FF garbage between markers. The treatment of such + * garbage is unspecified; we choose to skip over it but emit a warning msg. + * NB: this routine must not be used after seeing SOS marker, since it will + * not deal correctly with FF/00 sequences in the compressed image data... + */ +static int +next_marker (void) +{ + int c; + int discarded_bytes = 0; + + /* Find 0xFF byte; count and skip any non-FFs. */ + c = read_1_byte(); + while (c != 0xFF) { + discarded_bytes++; + c = read_1_byte(); + } + /* Get marker code byte, swallowing any duplicate FF bytes. Extra FFs + * are legal as pad bytes, so don't count them in discarded_bytes. + */ + do { + c = read_1_byte(); + } while (c == 0xFF); + + if (discarded_bytes != 0) { + global_error = WARNING_GARBAGE; + } + + return c; +} + + +/* + * Most types of marker are followed by a variable-length parameter segment. + * This routine skips over the parameters for any marker we don't otherwise + * want to process. + * Note that we MUST skip the parameter segment explicitly in order not to + * be fooled by 0xFF bytes that might appear within the parameter segment; + * such bytes do NOT introduce new markers. + */ +static void +copy_variable (void) +/* Copy an unknown or uninteresting variable-length marker */ +{ + unsigned int length; + + /* Get the marker parameter length count */ + length = read_2_bytes(); + write_2_bytes(length); + /* Length includes itself, so must be at least 2 */ + if (length < 2) { + global_error = ERROR_BAD_MARKER; + length = 2; + } + length -= 2; + /* Skip over the remaining bytes */ + while (length > 0) { + write_1_byte(read_1_byte()); + length--; + } +} + +static void +skip_variable (void) +/* Skip over an unknown or uninteresting variable-length marker */ +{ + unsigned int length; + + /* Get the marker parameter length count */ + length = read_2_bytes(); + /* Length includes itself, so must be at least 2 */ + if (length < 2) { + global_error = ERROR_BAD_MARKER; + length = 2; + } + length -= 2; + /* Skip over the remaining bytes */ + while (length > 0) { + (void) read_1_byte(); + length--; + } +} + + +static int +scan_JPEG_header (int keep_COM) +/* + * Parse & copy the marker stream until SOFn or EOI is seen; + * copy data to output, but discard COM markers unless keep_COM is true. + */ +{ + int c1, c2; + int marker; + + /* + * Read the initial marker, which should be SOI. + * For a JFIF file, the first two bytes of the file should be literally + * 0xFF M_SOI. To be more general, we could use next_marker, but if the + * input file weren't actually JPEG at all, next_marker might read the whole + * file and then return a misleading error message... + */ + c1 = NEXTBYTE(); + c2 = NEXTBYTE(); + if (c1 != 0xFF || c2 != M_SOI) { + global_error = ERROR_NOT_A_JPEG; + return EOF; + } + + write_marker(M_SOI); + + /* Scan miscellaneous markers until we reach SOFn. */ + for (;;) { + marker = next_marker(); + switch (marker) { + /* Note that marker codes 0xC4, 0xC8, 0xCC are not, and must not be, + * treated as SOFn. C4 in particular is actually DHT. + */ + case M_SOF0: /* Baseline */ + case M_SOF1: /* Extended sequential, Huffman */ + case M_SOF2: /* Progressive, Huffman */ + case M_SOF3: /* Lossless, Huffman */ + case M_SOF5: /* Differential sequential, Huffman */ + case M_SOF6: /* Differential progressive, Huffman */ + case M_SOF7: /* Differential lossless, Huffman */ + case M_SOF9: /* Extended sequential, arithmetic */ + case M_SOF10: /* Progressive, arithmetic */ + case M_SOF11: /* Lossless, arithmetic */ + case M_SOF13: /* Differential sequential, arithmetic */ + case M_SOF14: /* Differential progressive, arithmetic */ + case M_SOF15: /* Differential lossless, arithmetic */ + return marker; + + case M_SOS: /* should not see compressed data before SOF */ + global_error = ERROR_MARKER_ORDER; + break; + + case M_EOI: /* in case it's a tables-only JPEG stream */ + return marker; + + case M_COM: /* Existing COM: conditionally discard */ + if (keep_COM) { + write_marker(marker); + copy_variable(); + } else { + skip_variable(); + } + break; + + default: /* Anything else just gets copied */ + write_marker(marker); + copy_variable(); /* we assume it has a parameter count... */ + break; + } + } /* end loop */ +} + + +/****************************************************************************/ +/* + Verify we know how to set the comment on this type of file. + + TODO: The actual check! This should verify + the image size promised in the headers matches the file, + and that all markers are properly formatted. +*/ +static int validate_image_file( const char * filename ) +{ +int status = 1; +int c1, c2; + + if ( (infile = fopen(filename, READ_BINARY)) ) { + c1 = NEXTBYTE(); + c2 = NEXTBYTE(); + if (c1 != 0xFF || c2 != M_SOI) + status = ERROR_NOT_A_JPEG; + else + status = 0; + fclose( infile ); + } + return( status ); +} + + +/****************************************************************************/ +/* + Modify the file in place, but be paranoid and safe about it. + It's worth a few extra CPU cycles to make sure we never + destory an original image: + 1) Validate the input file. + 2) Open a temporary file. + 3) Copy the data, writing a new comment block. + 4) Validate the temporary file. + 5) Move the temporary file over the original. + + To be even more paranoid & safe we could: + 5) Rename the original to a different temporary name. + 6) Rename the temporary to the original. + 7) Delete the original. +*/ +extern int safe_copy_and_modify( const char * original_filename, const char * comment ) +{ +char * temp_filename; +int temp_filename_length; +int comment_length = 0; +int marker; +int i; +struct stat statbuf; + + global_error = 0; + + /* + * Make sure we're dealing with a proper input file. Safety first! + */ + if( validate_image_file( original_filename ) ) { + fprintf(stderr, "error validating original file %s\n", original_filename); + return(ERROR_NOT_A_JPEG); + } + + /* Get a unique temporary file in the same directory. Hopefully + * if things go wrong, this file will still be left for recovery purposes. + * + * NB: I hate these stupid string functions in C... the buffer length is too + * hard to manage... + */ + outfile = NULL; + temp_filename_length = strlen( original_filename) + 4; + temp_filename = (char *)calloc( temp_filename_length, 1 ); + for( i=0; i<10; i++ ) { + snprintf( temp_filename, temp_filename_length, "%s%d", original_filename, i ); + if( stat( temp_filename, &statbuf ) ) { + outfile = fopen(temp_filename, WRITE_BINARY); + break; + } + } + if( !outfile ) { + fprintf(stderr, "failed opening temporary file %s\n", temp_filename); + free(temp_filename); + return(ERROR_TEMP_FILE); + } + + + /* + * Let's rock and roll! + */ + if ((infile = fopen(original_filename, READ_BINARY)) == NULL) { + fprintf(stderr, "can't open input file %s\n", original_filename); + free(temp_filename); + return(ERROR_NOT_A_JPEG); + } + /* Copy JPEG headers until SOFn marker; + * we will insert the new comment marker just before SOFn. + * This (a) causes the new comment to appear after, rather than before, + * existing comments; and (b) ensures that comments come after any JFIF + * or JFXX markers, as required by the JFIF specification. + */ + marker = scan_JPEG_header(0); + /* Insert the new COM marker, but only if nonempty text has been supplied */ + if (comment) { + comment_length = strlen( comment ); + } + if (comment_length > 0) { + write_marker(M_COM); + write_2_bytes(comment_length + 2); + while (comment_length > 0) { + write_1_byte(*comment++); + comment_length--; + } + } + /* Duplicate the remainder of the source file. + * Note that any COM markers occurring after SOF will not be touched. + * + * :TODO: Discard COM markers occurring after SOF + */ + write_marker(marker); + copy_rest_of_file(); + fclose( infile ); + fsync( fileno( outfile) ); /* Make sure its really on disk first. !!!VERY IMPORTANT!!! */ + if ( fclose( outfile ) ) { + fprintf(stderr, "error in temporary file %s\n", temp_filename); + free(temp_filename); + return(ERROR_TEMP_FILE); + } + + + /* + * Make sure we did it right. We've already fsync()'ed the file. Safety first! + */ + if( validate_image_file( temp_filename ) ) { + fprintf(stderr, "error in temporary file %s\n", temp_filename); + free(temp_filename); + return(ERROR_TEMP_FILE); + } + + if( global_error >= ERROR_NOT_A_JPEG ) { + fprintf(stderr, "error %d processing %s\n", global_error, original_filename); + free(temp_filename); + return(ERROR_NOT_A_JPEG); + } + + if( rename( temp_filename, original_filename ) ) { + fprintf(stderr, "error renaming %s to %s\n", temp_filename, original_filename); + free(temp_filename); + return(ERROR_TEMP_FILE); + } + free(temp_filename); + + return(0); +} + + +#ifdef STANDALONE_COMPILE +int +main (int argc, char **argv) +{ + char * progname; /* program name for error messages */ + char * filename; + char * comment; + FILE * fp; + int error; + + /* Process command line arguments... */ + progname = argv[0]; + if (progname == NULL || progname[0] == 0) + progname = "writejpgcomment"; /* in case C library doesn't provide it */ + if( argc != 3) { + fprintf(stderr, "Usage: %s <filename> \"<comment>\"\nOverwrites the comment in a image file with the given comment.\n", progname); + return(5); + } + filename = argv[1]; + comment = argv[2]; + + + /* Check if file is readable... */ + if ((fp = fopen(filename, READ_BINARY)) == NULL) { + fprintf(stderr, "Error: Can't open file %s\n", filename); + fclose(fp); + return(5); + } + fclose(fp); + + /* Check if we really have a commentable image file here... */ + if( validate_image_file( filename ) ) { + fprintf(stderr, "Error: file %s is not of a supported type\n", filename); + return(5); + } + + /* Lets do it... modify the comment in place */ + if ((error = safe_copy_and_modify( filename, comment ) )) { + fprintf(stderr, "Error: %d setting jpg comment\n", error); + return(10); + } + + + /* TODO: Read comment back out of jpg and display it */ + return( 0 ); +} +#endif |