/* This file is part of the KDE project * Copyright (C) 2002 Frank Pieczynski , * 2002 Carsten Pfeiffer * 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 #include "kfile_jpeg.h" #include #include #include #include #include #include #include #include #include #include #include #include "exif.h" #define EXIFGROUP "Jpeg EXIF Data" typedef KGenericFactory JpegFactory; K_EXPORT_COMPONENT_FACTORY(kfile_jpeg, JpegFactory("kfile_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 tqunicode 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, 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 "kfile_jpeg.moc"