ref: ec5795bea63188b508fe1079174510072e7297cf
author: Alex Musolino <musolinoa@gmail.com>
date: Sun Nov 29 02:18:48 EST 2020
initial commit
--- /dev/null
+++ b/exif.c
@@ -1,0 +1,657 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+
+typedef struct IfdRec IfdRec;
+typedef struct ExifData ExifData;
+typedef struct EnumVal EnumVal;
+
+enum{
+ BigEndian,
+ LittleEndian,
+};
+
+enum{
+ ExifTag=0x8769,
+ GpsTag=0x8825,
+};
+
+enum{
+ ImageDescription=0x010e,
+ Make=0x010f,
+ Model=0x0110,
+ Orientation=0x0112,
+ XResolution=0x011a,
+ YResolution=0x011b,
+ ResolutionUnit=0x0128,
+ Software=0x0131,
+ DateTime=0x0132,
+ Artist=0x013b,
+ WhitePoint=0x013e,
+ PrimaryChromaticities=0x013f,
+ YCbCrCoefficients=0x0211,
+ YCbCrPositioning=0x0213,
+ ReferenceBlackWhite=0x0214,
+ Copyright=0x8298,
+ ExposureTime=0x829a,
+ FNumber=0x829d,
+ ExposureProgram=0x8822,
+ ISOSpeedRatings=0x8827,
+ SensitivityType=0x8830,
+ DateTimeOriginal=0x9003,
+ DateTimeDigitized=0x9004,
+ CompressedBitsPerPixel=0x9102,
+ ExposureBiasValue=0x9204,
+ MaxApertureValue=0x9205,
+ MeteringMode=0x9207,
+ LightSource=0x9208,
+ Flash=0x9209,
+ FocalLength=0x920a,
+ MakerNote=0x927c,
+ ColorSpace=0xa001,
+ PixelXDimension=0xa002,
+ PixelYDimension=0xa003,
+ InteroperabilityTag=0xa005,
+ SensingMethod=0xa217,
+ CustomRendered=0xa401,
+ ExposureMode,
+ WhiteBalance,
+ DigitalZoomRatio,
+ FocalLengthIn35mmFilm,
+ SceneCaptureType,
+ GainControl,
+ Contrast,
+ Saturation,
+ Sharpness,
+};
+
+enum{
+ Tbyte = 1,
+ Tascii = 2,
+ Tshort = 3,
+ Tlong = 4,
+ Tratio = 5,
+ Tundef = 7,
+ Tslong = 9,
+ Tsratio = 10,
+};
+
+static int prenum(IfdRec*, int, int, void*);
+static int prascii(IfdRec*, int, int, void*);
+static int prbyte(IfdRec*, int, Biobuf*, void*);
+static int prshort(IfdRec*, int, int, void*);
+static int prlong(IfdRec*, int, int, void*);
+static int prratio(IfdRec*, int, int, void*);
+static int prslong(IfdRec*, int, int, void*);
+static int prsratio(IfdRec*, int, int, void*);
+static int prdec(IfdRec*, int, int, void*);
+static int prundef(IfdRec*, int, int, void*);
+static int prifdrec(IfdRec*, int, Biobuf*);
+
+struct IfdRec
+{
+ u16int tag;
+ u16int fmt;
+ u32int cnt;
+ u8int val[4];
+ u32int ival;
+};
+
+struct ExifData
+{
+ u16int tag;
+ char str[32];
+ uchar show;
+ int (*print)(IfdRec*, int, int, void*);
+ void *aux;
+};
+
+struct EnumVal
+{
+ u32int val;
+ char *str;
+};
+
+int aflag, uflag, rflag;
+
+static EnumVal orientations[] = {
+ { 1, "upper left" },
+ { 3, "lower right" },
+ { 6, "upper right" },
+ { 8, "lower left" },
+ { 9, "undefined" },
+ { 0, nil }
+};
+
+static EnumVal resunits[] = {
+ { 1, "no unit" },
+ { 2, "inches" },
+ { 3, "centimeters" },
+ { 0, nil }
+};
+
+static EnumVal ycbcrpos[] = {
+ { 1, "centered" },
+ { 2, "co-sited" },
+ { 0, nil }
+};
+
+static ExifData exiftab[] = {
+ { Artist, "Artist", 0 },
+ { DateTime, "DateTime", 1 },
+ { ImageDescription, "ImageDescription", 1 },
+ { Make, "Make", 1 },
+ { Model, "Model", 1 },
+ { Orientation, "Orientation", 0, prenum, orientations },
+ { ResolutionUnit, "ResolutionUnit", 0, prenum, resunits },
+ { Software, "Software", 1 },
+ { XResolution, "XResolution", 0, prdec },
+ { YResolution, "YResolution", 0, prdec },
+ { YCbCrPositioning, "YCbCrPositioning", 0, prenum, ycbcrpos },
+ { ExposureTime, "ExposureTime", 0, prdec, "s" },
+ { FNumber, "FNumber", 0, prdec },
+ { ExposureProgram, "ExposureProgram" },
+ { ISOSpeedRatings, "ISOSpeedRatings" },
+ { SensitivityType, "SensitivityType" },
+ { DateTimeOriginal, "DateTimeOriginal" },
+ { DateTimeDigitized, "DateTimeDigitized" },
+ { CompressedBitsPerPixel, "CompressedBitsPerPixel", 0, prdec },
+ { ExposureBiasValue, "ExposureBiasValue", 0, prdec },
+ { MaxApertureValue, "MaxApertureValue", 0, prdec },
+ { MeteringMode, "MeteringMode" },
+ { LightSource, "LightSource" },
+ { Flash, "Flash" },
+ { FocalLength, "FocalLength", 0, prdec, "mm" },
+ { ColorSpace, "ColorSpace" },
+ { PixelXDimension, "PixelXDimension" },
+ { PixelYDimension, "PixelYDimension" },
+ { InteroperabilityTag, "InteroperabilityTag" },
+ { SensingMethod, "SensingMethod" },
+ { CustomRendered, "CustomRendered" },
+ { ExposureMode, "ExposureMode" },
+ { WhiteBalance, "WhiteBalance" },
+ { DigitalZoomRatio, "DigitalZoomRatio", 0, prdec },
+ { FocalLengthIn35mmFilm, "FocalLengthIn35mmFilm" },
+ { SceneCaptureType, "SceneCaptureType" },
+ { GainControl, "GainControl" },
+ { Contrast, "Contrast" },
+ { Saturation, "Saturation" },
+ { Sharpness, "Sharpness" },
+
+ { 0 }
+};
+
+static u16int
+get16(uchar *b, int e)
+{
+ if(e == BigEndian)
+ return (u16int)b[0]<<8 | b[1];
+ return (u16int)b[1]<<8 | b[0];
+}
+
+static int
+read16(Biobuf *r, int e, u16int *v)
+{
+ u8int b[2];
+
+ if(Bread(r, b, 2) != 2)
+ return -1;
+ if(v != nil)
+ *v = get16(b, e);
+ return 2;
+}
+
+static u32int
+get32(uchar *b, int bo)
+{
+ u32int h1, h2;
+
+ h1 = get16(&b[0], bo);
+ h2 = get16(&b[2], bo);
+ if(bo == BigEndian)
+ return h1<<16 | h2;
+ return h2<<16 | h1;
+}
+
+static int
+read32(Biobuf *r, int e, u32int *v)
+{
+ u8int b[4];
+
+ if(Bread(r, b, 4) != 4)
+ return -1;
+ if(v != nil)
+ *v = get32(b, e);
+ return 4;
+}
+
+static int
+prenum(IfdRec *r, int, int, void *aux)
+{
+ EnumVal *e;
+
+ e = aux;
+ if(r->cnt == 1){
+ while(e->str != nil){
+ if(e->val == r->ival)
+ return print("%s", e->str);
+ e++;
+ }
+ }
+ return print("unknown");
+}
+
+static char*
+fmtstr(u8int fmt)
+{
+ switch(fmt){
+ case Tbyte: return "byte";
+ case Tascii: return "ascii";
+ case Tshort: return "short";
+ case Tlong: return "long";
+ case Tratio: return "ratio";
+ case Tundef: return "undef";
+ case Tslong: return "slong";
+ case Tsratio: return "sratio";
+ }
+ return "inval";
+}
+
+static int
+prifdrec(IfdRec *r, int bo, Biobuf *b)
+{
+ ExifData *e;
+
+ for(e = exiftab; e->tag != 0; e++){
+ if(e->tag != r->tag)
+ continue;
+ break;
+ }
+ if(e->show || aflag){
+ if(*e->str != 0){
+ print("%s: ", e->str);
+ }else if(uflag)
+ print("unknown(0x%04uhx,%s): ", r->tag, fmtstr(r->fmt));
+ else
+ return 0;
+
+ if(rflag == 0 && e->print)
+ e->print(r, bo, b->fid, e->aux);
+ else
+ switch(r->fmt){
+ case Tbyte:
+ prbyte(r, bo, b, nil);
+ break;
+ case Tascii:
+ prascii(r, bo, b->fid, nil);
+ break;
+ case Tshort:
+ prshort(r, bo, b->fid, nil);
+ break;
+ case Tlong:
+ prlong(r, bo, b->fid, nil);
+ break;
+ case Tratio:
+ prratio(r, bo, b->fid, nil);
+ break;
+ case Tslong:
+ prslong(r, bo, b->fid, nil);
+ break;
+ case Tsratio:
+ prsratio(r, bo, b->fid, nil);
+ break;
+ case Tundef:
+ prundef(r, bo, b->fid, nil);
+ break;
+ }
+ print("\n");
+ }
+ return 0;
+}
+
+static int
+prlong0(IfdRec *r, int bo, int fd, void*, int s)
+{
+ int i;
+ uchar buf[4];
+ char *fmt;
+
+ fmt = s ? "%d" : "%ud";
+ switch(r->cnt){
+ case 0:
+ return 0;
+ case 1:
+ return print(fmt, get32(r->val, bo));
+ }
+ for(i = 0; i < r->cnt; i++){
+ if(pread(fd, buf, 2, r->ival + 12 + 4*i) < 0)
+ return -1;
+ print(fmt, get32(buf, bo));
+ }
+ return 0;
+}
+
+static int
+prslong(IfdRec *r, int bo, int fd, void *aux)
+{
+ return prlong0(r, bo, fd, aux, 1);
+}
+
+static int
+prlong(IfdRec *r, int bo, int fd, void *aux)
+{
+ return prlong0(r, bo, fd, aux, 0);
+}
+
+static int
+prshort(IfdRec *r, int bo, int fd, void*)
+{
+ int i;
+ uchar buf[2];
+
+ switch(r->cnt){
+ case 0:
+ return 0;
+ case 1:
+ return print("%uhd", get16(r->val, bo));
+ case 2:
+ return print("%uhd %uhd", get16(r->val, bo), get16(r->val+2, bo));
+ }
+ for(i = 0; i < r->cnt; i++){
+ if(pread(fd, buf, 2, r->ival + 12 + 2*i) < 0)
+ return -1;
+ print("%uhd", get16(buf, bo));
+ }
+ return 0;
+}
+
+static int
+prratio0(IfdRec *r, int bo, int fd, void*, int s)
+{
+ int i;
+ long n;
+ uchar buf[256];
+
+ n = sizeof(buf);
+ if(8*r->cnt < n)
+ n = 8*r->cnt;
+ if((n = pread(fd, buf, n, r->ival + 12)) < 0)
+ return -1;
+ for(i = 0; i < n; i += 8){
+ if(i > 0)
+ print(" ");
+ print(s ? "%d/%d" : "%ud/%ud", get32(&buf[i], bo), get32(&buf[i+4], bo));
+ }
+ return 0;
+}
+
+static int
+prsratio(IfdRec *r, int bo, int fd, void *aux)
+{
+ return prratio0(r, bo, fd, aux, 1);
+}
+
+static int
+prratio(IfdRec *r, int bo, int fd, void *aux)
+{
+ return prratio0(r, bo, fd, aux, 0);
+}
+
+static int
+prdec0(IfdRec *r, int bo, int fd, void *aux, int s)
+{
+ int i;
+ char *suffix;
+ uchar buf[8];
+ u32int n, d;
+ float f;
+
+ suffix = aux;
+ if(suffix == nil)
+ suffix = "";
+ for(i = 0; i < r->cnt; i++){
+ if(pread(fd, buf, 8, r->ival + 12 + 8*i) < 0)
+ return -1;
+ if(i > 0)
+ print(" ");
+ n = get32(&buf[i], bo);
+ d = get32(&buf[i+4], bo);
+ f = s ? (float)(long)n/(long)d : (float)n/d;
+ print("%g%s", f, suffix);
+ }
+ return 0;
+}
+
+static int
+prdec(IfdRec *r, int bo, int fd, void *aux)
+{
+ return prdec0(r, bo, fd, aux, r->fmt == Tsratio);
+}
+
+static int
+prbyte(IfdRec *r, int, Biobuf *b, void*)
+{
+ int c, i;
+
+ if(r->cnt <= 4)
+ return print("%c %c %c %c",
+ r->val[0], r->val[1], r->val[2], r->val[3]);
+ for(i = 0; i < r->cnt; i++){
+ if((c = Bgetc(b)) < 0)
+ return -1;
+ if(i > 0)
+ print(" ");
+ print("%c", (char)c);
+ }
+ return 0;
+}
+
+static int
+prascii(IfdRec *r, int, int fd, void*)
+{
+ long n;
+ char buf[256];
+
+ if(r->cnt <= 4){
+ if(r->cnt > 1)
+ return write(1, r->val, r->cnt - 1);
+ return 0;
+ }
+ n = sizeof(buf);
+ if(r->cnt < n)
+ n = r->cnt;
+ if((n = pread(fd, buf, n, r->ival + 12)) < 0)
+ return -1;
+ return write(1, buf, n);
+}
+
+static int
+prundef(IfdRec *r, int, int, void*)
+{
+ return print("cnt=%uhd val=(0x%uhhx 0x%uhhx 0x%uhhx 0x%uhhx)",
+ r->cnt, r->val[0], r->val[1], r->val[2], r->val[3]);
+}
+
+static void
+usage(void)
+{
+ fprint(2, "usage: %s file...\n", argv0);
+ exits("usage");
+}
+
+static int
+vparse(Biobuf *r, int e, char *fmt, va_list a)
+{
+ int c, i, n;
+ u8int *x;
+ u16int w;
+ u32int dw;
+
+ for(;;){
+ n = 0;
+ switch(*fmt++){
+ case '\0':
+ return 0;
+ case 'b':
+ if((c = Bgetc(r)) < 0)
+ return -1;
+ *va_arg(a, uchar*) = (uchar)c;
+ break;
+ case 'w':
+ if(read16(r, e, &w) < 0)
+ return -1;
+ *va_arg(a, u16int*) = w;
+ break;
+ case 'W':
+ if(read32(r, e, &dw) < 0)
+ return -1;
+ *va_arg(a, u32int*) = dw;
+ break;
+ case 'X':
+ n += 2;
+ case 'x':
+ n += 2;
+ x = va_arg(a, uchar*);
+ for(i = 0; i < n; i++){
+ if((c = Bgetc(r)) < 0)
+ return -1;
+ *x++ = (uchar)c;
+ }
+ break;
+ }
+ }
+}
+
+static int
+parse(Biobuf *r, int e, char *fmt, ...)
+{
+ int n;
+ va_list a;
+
+ va_start(a, fmt);
+ n = vparse(r, e, fmt, a);
+ va_end(a);
+ return n;
+}
+
+static int
+dumpinfo(Biobuf *r)
+{
+ char buf[64];
+ u16int w, soi, appn, len, ver, nents;
+ u32int ifdoff, exiftag, gpstag;
+ int i, bo, total;
+ IfdRec rec;
+
+ bo = BigEndian;
+ total = 0;
+ if(parse(r, bo, "www", &soi, &appn, &len) < 0)
+ goto Badfmt;
+ if(soi != 0xffd8 || (appn & 0xfff0) != 0xffe0)
+ goto Badfmt;
+ switch(appn & 0xf){
+ case 1:
+ break;
+ case 0:
+ fprint(2, "JFIF\n");
+ default:
+ goto Badfmt;
+ }
+ if(Bread(r, buf, 6) != 6 || strncmp(buf, "Exif", 6) != 0)
+ goto Badfmt;
+ if(read16(r, bo, &w) < 0)
+ goto Badfmt;
+ switch(w){
+ case 0x4949:
+ bo = LittleEndian;
+ break;
+ case 0x4d4d:
+ bo = BigEndian;
+ break;
+ default:
+ goto Badfmt;
+ }
+ if(parse(r, bo, "wW", &ver, &ifdoff) < 0)
+ goto Badfmt;
+ if(ver != 42 || ifdoff != 8)
+ goto Badfmt;
+ exiftag = 0;
+ gpstag = 0;
+ for(;;){
+ if(parse(r, bo, "w", &nents) < 0)
+ goto Badfmt;
+ for(i = 0; i < nents; i++){
+ memset(&rec, 0, sizeof(rec));
+ if(parse(r, bo, "wwWX", &rec.tag, &rec.fmt, &rec.cnt, rec.val) < 0)
+ goto Badfmt;
+ rec.ival = get32(rec.val, bo);
+ switch(rec.tag){
+ case ExifTag:
+ exiftag = rec.ival;
+ break;
+ case GpsTag:
+ gpstag = rec.ival;
+ break;
+ default:
+ prifdrec(&rec, bo, r);
+ break;
+ }
+ total++;
+ }
+ if(parse(r, bo, "w", &ifdoff) < 0)
+ goto Badfmt;
+ if(ifdoff == 0){
+ if(exiftag != 0){
+ ifdoff = exiftag;
+ exiftag = 0;
+ }else if(gpstag != 0){
+ ifdoff = gpstag;
+ gpstag = 0;
+ }else
+ break;
+ }
+ Bseek(r, ifdoff+12, 0);
+ }
+ return total;
+Badfmt:
+ werrstr("unknown file format");
+ return -1;
+}
+
+void
+main(int argc, char **argv)
+{
+ int i;
+ Biobuf *r;
+
+ ARGBEGIN{
+ case 'a':
+ aflag++;
+ break;
+ case 'u':
+ uflag++;
+ break;
+ case 'r':
+ rflag++;
+ break;
+ default:
+ usage();
+ }ARGEND;
+ if(argc < 1)
+ usage();
+ for(i = 0; i < argc; i++){
+ r = Bopen(argv[i], OREAD);
+ if(r == nil)
+ sysfatal("open: %r");
+ if(i > 0)
+ print("\n");
+ print("File: %s\n", argv[i]);
+ switch(dumpinfo(r)){
+ case -1:
+ sysfatal("%r");
+ case 0:
+ fprint(2, "no exif data\n");
+ }
+ Bterm(r);
+ }
+}
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,7 @@
+</$objtype/mkfile
+
+TARG=exif
+OFILES=\
+ exif.$O\
+
+</sys/src/cmd/mkone