/** \file \brief EXIF file library for GPHOTO package \author Copyright 1999 Matthew G. Martin Generic gphoto implementation and extension by Edouard Lafargue. Tag descriptions and an ever-increasing number of structural details Taken from "exifdump.py" by Thierry Bousch Thanks to Paul Wood for sub-ifd parsing. \par License 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; either version 2 of the License, or (at your option) any later version. \par 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. \par You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "exif.h" /* * Conventions: * * "tag number" (tagnum) : ID number of a tag (see list just below). * "tag index" (tagind) : index number of a tag inside an EXIF structure * (for example, tag number one if the first tag, etc..) * "tag name" (tagname) : name of the tag as defined in the EXIF standard. */ static int exif_debug=0; /* Size of various tags */ static const int exif_sizetab[13]={ 0,1,1,2,4,8,1,1,2,4,8,4,8 }; static const struct tagarray{ int num; char* desc; } tagnames[]= { {0xFE, "NewSubFileType"}, {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, "JPEGInterchangeFormat"}, {0x202, "JPEGInterchangeFormatLength"}, {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"}, {0xA20C, "SpatialFrequencyResponse"}, {0xA20E, "FocalPlaneXResolution"}, {0xA20F, "FocalPlaneYResolution"}, {0xA210, "FocalPlaneResolutionUnit"}, {0xA214, "SubjectLocation"}, {0xA215, "ExposureIndex"}, {0xA217, "SensingMethod"}, {0xA300, "FileSource"}, {0xA301, "Scenetype"}, {0,"end"} }; /* * Foward declarations. move to exif.h if you want to export. */ static int gpi_getvalue(unsigned char *data,int tagind); static int gpi_datsize(unsigned char *data,int tagind); static int gpi_tagnum( char *data,int tagind); static int gpi_getintval(unsigned char *data, int tagnum); static long gpi_exif_get_lilend(char *data, int size); static int gpe_theval(char *data,int tagind); static void gpi_setval(char *data,int tagind,long newval); static long exif_next_ifd(unsigned char *exif,int num); static int gpi_exif_get_comment(exifparser *exifdat, char *comment); static int gpi_exif_get_field( int tag_number, int ifd, exifparser *exifdata, ExifData *tag_data); /* * Utility functions: get fields in little-endian format, * initialize data structures, etc. */ /* * Convert to Intel-Endian number. * * data : pointer to the data to convert * size : size of the data to convert * * Returns : the value */ static long gpi_exif_get_lilend(char *data, int size){ long total; total=0; for (--size;size>=0;size--){ total<<=8; total+=(unsigned char)(data[size]); } return(total); } /* * Return "value" of directory entry at tagind */ static int gpe_theval(char *data,int tagind){ return(gpi_exif_get_lilend(data+tagind*12+10,4)); } /* * Set the "value" of directory entry at tagind. */ static void gpi_setval(char *data,int tagind,long newval){ int i; for (i=0;i<4;i++) data[tagind*12+10+i]=0xff&(newval>>(i*8)); if (gpi_getvalue(data,tagind)!=newval) printf("Setptr: error %d inst %ld\n",gpe_theval(data,tagind),newval); } /* * Returns the offset of the next IFD entry after * offset "num". * * exif : exif data * num : start offset * * returns: offset of next IFD. */ static long exif_next_ifd(unsigned char *exif,int num){ int offset=(exif[num]+(exif[num+1]<<8))*12+num+2; if (exif_debug) printf("next_ifd,offset=%d\n",offset); return(gpi_exif_get_lilend(exif+offset,4)); } /* * Parse an exif data structure and initialise the "exifparser". * * exifdat : the exif data * * returns : size of the rest of the file. * -1 if failed. */ static int exif_parse_data(exifparser *exifdat) { long offset=0; ExifData tagdat; exif_debug=1; /* Check that it's not already preparsed */ /* Doesn't work if we don't initialize this first... */ /* if (exifdat->preparsed) return 0; */ if (exif_debug) printf("Parsing exif structure\n"); /* First, verify that we really have an EXIF file */ /* Note: maybe we could also generalize to TIFF... */ if ((strncmp("Exif",exifdat->header+6,4)) || strncmp("\377\330\377\341",exifdat->header,4)){ fprintf(stderr,"Not exif data\n"); return(-1); } exifdat->exiflen = exifdat->header[5] + (exifdat->header[4]<<8) - 8; if (exif_debug) printf("Exif length is %i\n",exifdat->exiflen); /* Count the number of IFD's and register their pointers. */ exifdat->endian=0; if (exifdat->data[0]!='I') { exifdat->endian=1; printf("%c,Intel-Endian format only supported right now!\n", exifdat->data[0]); return(-1); } offset=gpi_exif_get_lilend(exifdat->data+4,4); /*Get offset of first IFD */ exifdat->ifdcnt=-1; /*Step through each IFD */ do { exifdat->ifdcnt++; exifdat->ifds[exifdat->ifdcnt] = exifdat->data+offset; exifdat->ifdtags[exifdat->ifdcnt] = gpi_exif_get_lilend(exifdat->data+offset,2); } while ((offset=exif_next_ifd(exifdat->data,offset))); exifdat->ifdcnt++; /* Look for the EXIF IFD */ exifdat->preparsed=1; /* Indicate that our exifparser is initialized */ /* Has to be done before calling gpi_exif_get_field */ if ( ! gpi_exif_get_field( EXIF_ExifOffset, 0, exifdat, &tagdat)) { printf("No EXIF information stored in this image\n"); } else { if (exif_debug) printf("Offset to the Exif subIFD is %d\n",tagdat.intval); exifdat->ifds[exifdat->ifdcnt] = exifdat->data + tagdat.intval; exifdat->ifdtags[exifdat->ifdcnt] = gpi_exif_get_lilend(exifdat->data+tagdat.intval,2); exifdat->ifdcnt++; } if (exif_debug) printf("Finished parsing exif structure\n"); return exifdat->exiflen; } /* * tag-level functions: get/set tags in the EXIF structure */ /** * Returns the value of a field, identified by its tag and the IFD. * * Specifying an IFD of '-1' will return the first occurence of * the tag in the first IFD where it finds it. * * tag_number: tag that identifies the field * ifd : image file directory where to look for that tag * exifdata : pointer to the EXIF data * tag_data : filled by this function, contains the actual data * * returns : 1 on success, 0 on failure */ static int gpi_exif_get_field( int tag_number, int ifd, exifparser *exifdata, ExifData *tag_data) { int numtags,i, tag; char *ifdp, *data; /* Sanity check first: */ exif_debug=1; if ( !exifdata->preparsed ) { if(exif_parse_data(exifdata) < 0) return 0; } if (ifd == -1) { for (i = 0; i < exifdata->ifdcnt+1; i++){ if (exif_debug) printf("Searching in IFD %d\n",i); if ( gpi_exif_get_field(tag_number, i, exifdata, tag_data) == 1) return 1; } return 0; /* We did not find it. */ } else { /* Find tag in a specific IFD */ ifdp = exifdata->ifds[ifd]; numtags=gpi_exif_get_lilend(ifdp,2); /* How many tags in the IFD */ if (exif_debug) printf("gpi_exif_get_field: %d tags in ifd %d\n",numtags, ifd); i=-1; do { i++; tag = gpi_exif_get_lilend(ifdp+i*12+2, 2); /* Get the tag ID */ /* if (exif_debug) fprintf(stderr,"Found tag %d \n",tag); */ } while ((i < numtags) && (tag != tag_number)); if(tag != tag_number) { if (exif_debug) fprintf(stderr,"Tag %d not found\n",tag_number); return 0; } ifdp = ifdp+i*12+2; /* Place pointer at TAG type position */ tag_data->tag = tag; tag_data->type = gpi_exif_get_lilend(ifdp+2,2); /* tag type */ tag_data->size = exif_sizetab[tag_data->type]*gpi_exif_get_lilend(ifdp+4,4); if (exif_debug) printf("(%d bytes) ",tag_data->size); ifdp += 8; /* place pointer at beginning of the contents of the field */ /* Data types smaller than 4 bytes are stored directly in the IFD field value. Otherwise, that value is an offset to the real data */ if (tag_data->size > 4) { ifdp = exifdata->data + gpi_exif_get_lilend(ifdp,4); } /* Last operation, get the data itself: */ /* Allocate memory, and copy the data */ data=malloc(tag_data->size); if (data==NULL){ fprintf(stderr,"gpi_exif_get_field: could not malloc\n"); return 0; } if (tag_data->type == EXIF_ASCII) { memcpy(data,ifdp,tag_data->size); /* Normally, the exif data includes a terminating 0 */ tag_data->data = data; if(exif_debug) printf("\"%s\"",data); } else { for (i = 0; i < tag_data->size; i += exif_sizetab[tag_data->type]) { /*if (exif_debug) fprintf(stderr,"."); */ if ( tag_data->type % 5 ) { memcpy(data+i,ifdp+i,exif_sizetab[tag_data->type]); } else { /* Fraction */ tag_data->num =gpi_exif_get_lilend(ifdp+i,4); tag_data->den =gpi_exif_get_lilend(ifdp+i+4,4); if(exif_debug) printf("%d/%d=%.3g ",tag_data->num, tag_data->den, (tag_data->den==0)?0:(1.0*tag_data->num/tag_data->den)); } } /* If the value can be put into an int, save the trouble and store */ /* it into "intval" right away... */ if(tag_data->type != EXIF_ASCII && tag_data->type != EXIF_RATIONAL && tag_data->type != EXIF_UNDEFINED && tag_data->type != EXIF_SRATIONAL) { tag_data->intval = gpi_exif_get_lilend(data,exif_sizetab[tag_data->type]); if(exif_debug) printf("'%d'",tag_data->intval); } tag_data->data = data; /* Save the data field */ } if (exif_debug) printf("\n"); /*end of this tag */ return 1; } } /* * Higher-level functions: get/set logical entities, such as * comments, thumbnail, etc. */ /* * Gets the comment field if it exists. */ static int gpi_exif_get_comment(exifparser *exifdat, char *comment) { ExifData tagdat; if (exif_parse_data(exifdat)<0) return 0; /* User Comment is in IFD number 2 */ if ( ! gpi_exif_get_field( EXIF_UserComment, 2, exifdat, &tagdat)) { if (exif_debug) printf("No comment field in this image\n"); return 0; } else { comment = tagdat.data; return tagdat.size; } } /* * Gets the thumbnail of an EXIF image. * The thumbnail size is provided * * The returned thumbnail might be in JPEG or TIFF format. */ unsigned char *gpi_exif_get_thumbnail_and_size(exifparser *exifdat, long *size) { unsigned char *imagedata,*exifimg,*newimg,*curptr; unsigned int entry; long dataptr,dsize,tag,datvec,tmp; int i,j; exif_debug=1; if (exif_parse_data(exifdat)<0) return(NULL); *size = 0; newimg=malloc(exifdat->exiflen); if (newimg==NULL){ fprintf(stderr,"gpi_exif_get_thumbnail: could not malloc\n"); return(NULL); } /* Copy header*/ memcpy(newimg,exifdat->data,8); curptr=newimg+8; *size += 8; if (exif_debug) { ExifData owner; char *comment=NULL; printf("Decoding EXIF fields in thumbnail\n"); gpi_exif_get_field( EXIF_Model, -1, exifdat, &owner); printf("Camera model: %s\n",owner.data); printf("Comment for this picture (%d chars)",gpi_exif_get_comment( exifdat, comment)); /*printf(" -> %s\n",comment); Not OK on Linux */ gpi_exif_get_field( EXIF_SubjectDistance, 2, exifdat, &owner); /* dump_exif(exifdat); */ } /* Skip to Thumbnail image data */ if(exifdat->ifdcnt<2) { if (exif_debug) { fprintf(stderr,"Too few ifds, doesn't look right. Giving up\n"); } *size = 0; return(NULL); /* Things don't look right...*/ } /* Jump to thumbnail image data */ exifimg=exifdat->ifds[1]; /* Copy number of entries */ memcpy(curptr,exifimg,2); curptr+=2; *size += 2; entry=gpi_exif_get_lilend(exifimg,2); if (exif_debug) printf("Entry is %d \n",entry); /* See if thumb is a JPEG */ tmp=gpi_getintval(exifimg,EXIF_JPEGInterchangeFormat); /*imagedata start*/ if (tmp>0) { /* jpeg image */ if (exif_debug) fprintf(stderr,"Found jpeg thumb data\n"); dsize=gpi_getintval(exifimg,EXIF_JPEGInterchangeFormatLength); if (dsize==-1){ fprintf(stderr,"No Jpeg size tag for thumbnail, skipping\n"); *size = 0; return(NULL); } imagedata=exifdat->data+tmp; memcpy(newimg,imagedata,dsize); *size += dsize; return(newimg); } /* Try a TIFF */ tmp=gpi_getintval(exifimg,EXIF_StripOffsets); /*imagedata start*/ if (tmp==-1) { fprintf(stderr,"gpe_get_thumbnail: Tiff or jpeg data not found, skipping\n"); *size = 0; return(NULL); } imagedata=exifdat->data+tmp; dataptr=gpi_getintval(exifimg,EXIF_StripByteCounts); /* imagedata size */ if (dataptr==-1) { printf("Split two\n"); *size = 0; return(NULL); } if (exif_debug) printf("Imagedata size is %ld bytes\n",dataptr); for (i=0;idata[datvec+j]; memcpy(curptr,exifimg+12*i+2,12); curptr+=12; *size += 12; } } } memcpy(curptr,exifimg+12*entry+10,4); /* Write 4 zero bytes */ curptr+=4; memcpy(curptr,imagedata,dataptr);/* ? */ curptr+=dataptr; *size += 4+dataptr; return newimg; } /** * Return tag number of directory entry at tag index * * data : pointer to EXIF data, aligned to the beginning of an IFD * tagind : index of the tag within the IFD * * returns: tag ID number */ static int gpi_tagnum( char *data,int tagind){ return(gpi_exif_get_lilend(data+tagind*12+2,2)); } /* * Get the value of a tag as an integer. * only works for 4 byte values. * * data : exif data, aligned to the beginning of an IFD. * tagnum : tag number. * * returns: -1 if the tag is not found. */ static int gpi_getintval(unsigned char *data, int tagnum) { int numtags,i,tag,tagtype; numtags=gpi_exif_get_lilend(data,2); if (exif_debug) printf("getval:%d tags\n",numtags); i=-1; do { i++; tag=gpi_exif_get_lilend(data+i*12+2,2); } while ((iifdtags[ifdnum];i++){ togphotostr(exifdata,ifdnum,i,datastrings+i*2,datastrings+i*2+1); /* printf("%s = %s\n",datastrings[i*2],datastrings[i*2+1]);*/ } } #endif /** * Return data size of directory entry at tagind */ static int gpi_datsize(unsigned char *data,int tagind){ return(exif_sizetab[gpi_exif_get_lilend(data+tagind*12+4,2)]*gpi_exif_get_lilend(data+tagind*12+6,4)); } int gpi_exif_stat(exifparser *exifdata) { long offset=0; exifdata->endian=0; if (exifdata->data[0]!='I') { exifdata->endian=1; printf("%c,Intel-Endian format only supported right now!\n", exifdata->data[0]); return(-1); } offset=gpi_exif_get_lilend(exifdata->data+4,4); /*Get offset of first IFD*/ exifdata->ifdcnt=-1; /*Step through each IFD (looks like there should be 2 or 3)*/ do{ exifdata->ifdcnt++; exifdata->ifds[exifdata->ifdcnt] = exifdata->data+offset; exifdata->ifdtags[exifdata->ifdcnt] = gpi_exif_get_lilend(exifdata->data+offset,2); }while((offset=exif_next_ifd(exifdata->data,offset))); exifdata->ifdcnt++; exifdata->preparsed=1; return(0); }