diff --git a/pkg/images/images.go b/pkg/images/images.go index 85dd951c0..7e7b7fa25 100644 --- a/pkg/images/images.go +++ b/pkg/images/images.go @@ -28,7 +28,7 @@ import ( _ "image/gif" _ "image/png" - "github.com/camlistore/goexif/exif" + "camlistore.org/third_party/github.com/camlistore/goexif/exif" ) // The FlipDirection type is used by the Flip option in DecodeOpts diff --git a/third_party/github.com/camlistore/goexif/LICENSE b/third_party/github.com/camlistore/goexif/LICENSE new file mode 100644 index 000000000..aa6250465 --- /dev/null +++ b/third_party/github.com/camlistore/goexif/LICENSE @@ -0,0 +1,24 @@ + +Copyright (c) 2012, Robert Carlsen & Contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/third_party/github.com/camlistore/goexif/README.md b/third_party/github.com/camlistore/goexif/README.md new file mode 100644 index 000000000..3b55e5e6c --- /dev/null +++ b/third_party/github.com/camlistore/goexif/README.md @@ -0,0 +1,59 @@ +goexif +====== + +Provides decoding of basic exif and tiff encoded data. Still in alpha - no guarantees. +Suggestions and pull requests are welcome. Functionality is split into two packages - "exif" and "tiff" +The exif package depends on the tiff package. +Documentation can be found at http://go.pkgdoc.org/github.com/camlistore/goexif + +To install, in a terminal type: + +``` +go get github.com/camlistore/goexif/exif +``` + +Or if you just want the tiff package: + +``` +go get github.com/camlistore/goexif/tiff +``` + +Example usage: + +```go +package main + +import ( + "os" + "log" + "fmt" + + "github.com/camlistore/goexif/exif" +) + +func main() { + fname := "sample1.jpg" + + f, err := os.Open(fname) + if err != nil { + log.Fatal(err) + } + + x, err := exif.Decode(f) + f.Close() + if err != nil { + log.Fatal(err) + } + + camModel, _ := x.Get("Model") + date, _ := x.Get("DateTimeOriginal") + fmt.Println(camModel.StringVal()) + fmt.Println(date.StringVal()) + + focal, _ := x.Get("FocalLength") + numer, denom := focal.Rat2(0) // retrieve first (only) rat. value + fmt.Printf("%v/%v", numer, denom) +} +``` + + diff --git a/third_party/github.com/camlistore/goexif/exif/example_test.go b/third_party/github.com/camlistore/goexif/exif/example_test.go new file mode 100644 index 000000000..afef2f187 --- /dev/null +++ b/third_party/github.com/camlistore/goexif/exif/example_test.go @@ -0,0 +1,32 @@ +package exif_test + +import ( + "fmt" + "log" + "os" + + "camlistore.org/third_party/github.com/camlistore/goexif/exif" +) + +func ExampleDecode() { + fname := "sample1.jpg" + + f, err := os.Open(fname) + if err != nil { + log.Fatal(err) + } + + x, err := exif.Decode(f) + if err != nil { + log.Fatal(err) + } + + camModel, _ := x.Get("Model") + date, _ := x.Get("DateTimeOriginal") + fmt.Println(camModel.StringVal()) + fmt.Println(date.StringVal()) + + focal, _ := x.Get("FocalLength") + numer, denom := focal.Rat2(0) // retrieve first (only) rat. value + fmt.Printf("%v/%v", numer, denom) +} diff --git a/third_party/github.com/camlistore/goexif/exif/exif.go b/third_party/github.com/camlistore/goexif/exif/exif.go new file mode 100644 index 000000000..95833b632 --- /dev/null +++ b/third_party/github.com/camlistore/goexif/exif/exif.go @@ -0,0 +1,232 @@ +// Package exif implements decoding of EXIF data as defined in the EXIF 2.2 +// specification. +package exif + +import ( + "bufio" + "bytes" + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "io" + + "camlistore.org/third_party/github.com/camlistore/goexif/tiff" +) + +const ( + exifPointer = 0x8769 + gpsPointer = 0x8825 + interopPointer = 0xA005 +) + +// A TagNotPresentError is returned when the requested field is not +// present in the EXIF. +type TagNotPresentError FieldName + +func (tag TagNotPresentError) Error() string { + return fmt.Sprintf("exif: tag %q is not present", string(tag)) +} + +func isTagNotPresentErr(err error) bool { + _, ok := err.(TagNotPresentError) + return ok +} + +type Exif struct { + tif *tiff.Tiff + + main map[uint16]*tiff.Tag +} + +// Decode parses EXIF-encoded data from r and returns a queryable Exif object. +func Decode(r io.Reader) (*Exif, error) { + sec, err := newAppSec(0xE1, r) + if err != nil { + return nil, err + } + er, err := sec.exifReader() + if err != nil { + return nil, err + } + tif, err := tiff.Decode(er) + if err != nil { + return nil, errors.New("exif: decode failed: " + err.Error()) + } + + // build an exif structure from the tiff + x := &Exif{ + main: map[uint16]*tiff.Tag{}, + tif: tif, + } + + ifd0 := tif.Dirs[0] + for _, tag := range ifd0.Tags { + x.main[tag.Id] = tag + } + + // recurse into exif, gps, and interop sub-IFDs + if err = x.loadSubDir(er, exifPointer); err != nil { + return x, err + } + if err = x.loadSubDir(er, gpsPointer); err != nil { + return x, err + } + if err = x.loadSubDir(er, interopPointer); err != nil { + return x, err + } + + return x, nil +} + +func (x *Exif) loadSubDir(r *bytes.Reader, tagId uint16) error { + tag, ok := x.main[tagId] + if !ok { + return nil + } + offset := tag.Int(0) + + _, err := r.Seek(offset, 0) + if err != nil { + return errors.New("exif: seek to sub-IFD failed: " + err.Error()) + } + subDir, _, err := tiff.DecodeDir(r, x.tif.Order) + if err != nil { + return errors.New("exif: sub-IFD decode failed: " + err.Error()) + } + for _, tag := range subDir.Tags { + x.main[tag.Id] = tag + } + return nil +} + +// Get retrieves the EXIF tag for the given field name. +// +// If the tag is not known or not present, an error is returned. If the +// tag name is known, the error will be a TagNotPresentError. +func (x *Exif) Get(name FieldName) (*tiff.Tag, error) { + id, ok := fields[name] + if !ok { + return nil, fmt.Errorf("exif: invalid tag name %q", name) + } + if tg, ok := x.main[id]; ok { + return tg, nil + } + return nil, TagNotPresentError(name) +} + +// Walker is the interface used to traverse all exif fields of an Exif object. +// Returning a non-nil error aborts the walk/traversal. +type Walker interface { + Walk(name FieldName, tag *tiff.Tag) error +} + +// Walk calls the Walk method of w with the name and tag for every non-nil exif +// field. +func (x *Exif) Walk(w Walker) error { + for name, _ := range fields { + tag, err := x.Get(name) + if isTagNotPresentErr(err) { + continue + } else if err != nil { + panic("field list access/construction is broken - this should never happen") + } + + err = w.Walk(name, tag) + if err != nil { + return err + } + } + return nil +} + +// String returns a pretty text representation of the decoded exif data. +func (x *Exif) String() string { + var buf bytes.Buffer + for name, id := range fields { + if tag, ok := x.main[id]; ok { + fmt.Fprintf(&buf, "%s: %s\n", name, tag) + } + } + return buf.String() +} + +func (x Exif) MarshalJSON() ([]byte, error) { + m := map[string]interface{}{} + + for name, id := range fields { + if tag, ok := x.main[id]; ok { + m[string(name)] = tag + } + } + + return json.Marshal(m) +} + +type appSec struct { + marker byte + data []byte +} + +// newAppSec finds marker in r and returns the corresponding application data +// section. +func newAppSec(marker byte, r io.Reader) (*appSec, error) { + app := &appSec{marker: marker} + + buf := bufio.NewReader(r) + + // seek to marker + for { + b, err := buf.ReadByte() + if err != nil { + return nil, err + } + n, err := buf.Peek(1) + if b == 0xFF && n[0] == marker { + buf.ReadByte() + break + } + } + + // read section size + var dataLen uint16 + err := binary.Read(buf, binary.BigEndian, &dataLen) + if err != nil { + return nil, err + } + dataLen -= 2 // subtract length of the 2 byte size marker itself + + // read section data + nread := 0 + for nread < int(dataLen) { + s := make([]byte, int(dataLen)-nread) + n, err := buf.Read(s) + if err != nil { + return nil, err + } + nread += n + app.data = append(app.data, s...) + } + + return app, nil +} + +// reader returns a reader on this appSec. +func (app *appSec) reader() *bytes.Reader { + return bytes.NewReader(app.data) +} + +// exifReader returns a reader on this appSec with the read cursor advanced to +// the start of the exif's tiff encoded portion. +func (app *appSec) exifReader() (*bytes.Reader, error) { + // read/check for exif special mark + if len(app.data) < 6 { + return nil, errors.New("exif: failed to find exif intro marker") + } + + exif := app.data[:6] + if string(exif) != "Exif"+string([]byte{0x00, 0x00}) { + return nil, errors.New("exif: failed to find exif intro marker") + } + return bytes.NewReader(app.data[6:]), nil +} diff --git a/third_party/github.com/camlistore/goexif/exif/exif_test.go b/third_party/github.com/camlistore/goexif/exif/exif_test.go new file mode 100644 index 000000000..e887bba4a --- /dev/null +++ b/third_party/github.com/camlistore/goexif/exif/exif_test.go @@ -0,0 +1,80 @@ +package exif + +import ( + "os" + "testing" + + "camlistore.org/third_party/github.com/camlistore/goexif/tiff" +) + +func TestDecode(t *testing.T) { + name := "sample1.jpg" + f, err := os.Open(name) + if err != nil { + t.Fatalf("%v\n", err) + } + + x, err := Decode(f) + if err != nil { + t.Fatal(err) + } + if x == nil { + t.Fatal("No error and yet %v was not decoded\n", name) + } + + val, err := x.Get("Model") + t.Logf("Model: %v", val) + t.Log(x) +} + +type walker struct { + t *testing.T +} + +func (w *walker) Walk(name FieldName, tag *tiff.Tag) error { + w.t.Logf("%v: %v", name, tag) + return nil +} + +func TestWalk(t *testing.T) { + name := "sample1.jpg" + f, err := os.Open(name) + if err != nil { + t.Fatalf("%v\n", err) + } + + x, err := Decode(f) + if err != nil { + t.Error(err) + } + if x == nil { + t.Fatal("bad err") + } + + x.Walk(&walker{t}) + +} + +func TestMarshal(t *testing.T) { + name := "sample1.jpg" + f, err := os.Open(name) + if err != nil { + t.Fatalf("%v\n", err) + } + defer f.Close() + + x, err := Decode(f) + if err != nil { + t.Fatal(err) + } + if x == nil { + t.Fatal("bad err") + } + + b, err := x.MarshalJSON() + if err != nil { + t.Fatal(err) + } + + t.Logf("%s", b) +} diff --git a/third_party/github.com/camlistore/goexif/exif/fields.go b/third_party/github.com/camlistore/goexif/exif/fields.go new file mode 100644 index 000000000..123bd02e0 --- /dev/null +++ b/third_party/github.com/camlistore/goexif/exif/fields.go @@ -0,0 +1,153 @@ +package exif + +type FieldName string + +const ( + ImageWidth FieldName = "ImageWidth" + ImageLength FieldName = "ImageLength" // height + Orientation FieldName = "Orientation" +) + +var fields = map[FieldName]uint16{ + ///////////////////////////////////// + ////////// IFD 0 //////////////////// + ///////////////////////////////////// + + // image data structure + "ImageWidth": 0x0100, + "ImageLength": 0x0101, + "BitsPerSample": 0x0102, + "Compression": 0x0103, + "PhotometricInterpretation": 0x0106, + "Orientation": 0x0112, + "SamplesPerPixel": 0x0115, + "PlanarConfiguration": 0x011C, + "YCbCrSubSampling": 0x0212, + "YCbCrPositioning": 0x0213, + "XResolution": 0x011A, + "YResolution": 0x011B, + "ResolutionUnit": 0x0128, + + // Other tags + "DateTime": 0x0132, + "ImageDescription": 0x010E, + "Make": 0x010F, + "Model": 0x0110, + "Software": 0x0131, + "Artist": 0x010e, + "Copyright": 0x010e, + + // private tags + "ExifIFDPointer": exifPointer, + + ///////////////////////////////////// + ////////// Exif sub IFD ///////////// + ///////////////////////////////////// + + "GPSInfoIFDPointer": gpsPointer, + "InteroperabilityIFDPointer": interopPointer, + + "ExifVersion": 0x9000, + "FlashpixVersion": 0xA000, + + "ColorSpace": 0xA001, + + "ComponentsConfiguration": 0x9101, + "CompressedBitsPerPixel": 0x9102, + "PixelXDimension": 0xA002, + "PixelYDimension": 0xA003, + + "MakerNote": 0x927C, + "UserComment": 0x9286, + + "RelatedSoundFile": 0xA004, + "DateTimeOriginal": 0x9003, + "DateTimeDigitized": 0x9004, + "SubSecTime": 0x9290, + "SubSecTimeOriginal": 0x9291, + "SubSecTimeDigitized": 0x9292, + + "ImageUniqueID": 0xA420, + + // picture conditions + "ExposureTime": 0x829A, + "FNumber": 0x829D, + "ExposureProgram": 0x8822, + "SpectralSensitivity": 0x8824, + "ISOSpeedRatings": 0x8827, + "OECF": 0x8828, + "ShutterSpeedValue": 0x9201, + "ApertureValue": 0x9202, + "BrightnessValue": 0x9203, + "ExposureBiasValue": 0x9204, + "MaxApertureValue": 0x9205, + "SubjectDistance": 0x9206, + "MeteringMode": 0x9207, + "LightSource": 0x9208, + "Flash": 0x9209, + "FocalLength": 0x920A, + "SubjectArea": 0x9214, + "FlashEnergy": 0xA20B, + "SpatialFrequencyResponse": 0xA20C, + "FocalPlaneXResolution": 0xA20E, + "FocalPlaneYResolution": 0xA20F, + "FocalPlaneResolutionUnit": 0xA210, + "SubjectLocation": 0xA214, + "ExposureIndex": 0xA215, + "SensingMethod": 0xA217, + "FileSource": 0xA300, + "SceneType": 0xA301, + "CFAPattern": 0xA302, + "CustomRendered": 0xA401, + "ExposureMode": 0xA402, + "WhiteBalance": 0xA403, + "DigitalZoomRatio": 0xA404, + "FocalLengthIn35mmFilm": 0xA405, + "SceneCaptureType": 0xA406, + "GainControl": 0xA407, + "Contrast": 0xA408, + "Saturation": 0xA409, + "Sharpness": 0xA40A, + "DeviceSettingDescription": 0xA40B, + "SubjectDistanceRange": 0xA40C, + + ///////////////////////////////////// + //// GPS sub-IFD //////////////////// + ///////////////////////////////////// + "GPSVersionID": 0x0, + "GPSLatitudeRef": 0x1, + "GPSLatitude": 0x2, + "GPSLongitudeRef": 0x3, + "GPSLongitude": 0x4, + "GPSAltitudeRef": 0x5, + "GPSAltitude": 0x6, + "GPSTimeStamp": 0x7, + "GPSSatelites": 0x8, + "GPSStatus": 0x9, + "GPSMeasureMode": 0xA, + "GPSDOP": 0xB, + "GPSSpeedRef": 0xC, + "GPSSpeed": 0xD, + "GPSTrackRef": 0xE, + "GPSTrack": 0xF, + "GPSImgDirectionRef": 0x10, + "GPSImgDirection": 0x11, + "GPSMapDatum": 0x12, + "GPSDestLatitudeRef": 0x13, + "GPSDestLatitude": 0x14, + "GPSDestLongitudeRef": 0x15, + "GPSDestLongitude": 0x16, + "GPSDestBearingRef": 0x17, + "GPSDestBearing": 0x18, + "GPSDestDistanceRef": 0x19, + "GPSDestDistance": 0x1A, + "GPSProcessingMethod": 0x1B, + "GPSAreaInformation": 0x1C, + "GPSDateStamp": 0x1D, + "GPSDifferential": 0x1E, + + ///////////////////////////////////// + //// Interoperability sub-IFD /////// + ///////////////////////////////////// + "InteroperabilityIndex": 0x1, +} diff --git a/third_party/github.com/camlistore/goexif/exif/fields/fields.go b/third_party/github.com/camlistore/goexif/exif/fields/fields.go new file mode 100644 index 000000000..30c32fff1 --- /dev/null +++ b/third_party/github.com/camlistore/goexif/exif/fields/fields.go @@ -0,0 +1,134 @@ +package fields + +type Std struct { + ImageWidth + ImageLength + BitsPerSample + Compression + PhotometricInterpretation + Orientation + SamplesPerPixel + PlanarConfiguration + YCbCrSubSampling + YCbCrPositioning + XResolution + YResolution + ResolutionUnit + DateTime + ImageDescription + Make + Model + Software + Artist + Copyright + ExifIFDPointer +} + +type Sub struct { + GPSInfoIFDPointer + InteroperabilityIFDPointer + ExifVersion + FlashpixVersion + ColorSpace + ComponentsConfiguration + CompressedBitsPerPixel + PixelXDimension + PixelYDimension + MakerNote + UserComment + RelatedSoundFile + DateTimeOriginal + DateTimeDigitized + SubSecTime + SubSecTimeOriginal + SubSecTimeDigitized + ImageUniqueID + ExposureTime + FNumber + ExposureProgram + SpectralSensitivity + ISOSpeedRatings + OECF + ShutterSpeedValue + ApertureValue + BrightnessValue + ExposureBiasValue + MaxApertureValue + SubjectDistance + MeteringMode + LightSource + Flash + FocalLength + SubjectArea + FlashEnergy + SpatialFrequencyResponse + FocalPlaneXResolution + FocalPlaneYResolution + FocalPlaneResolutionUnit + SubjectLocation + ExposureIndex + SensingMethod + FileSource + SceneType + CFAPattern + CustomRendered + ExposureMode + WhiteBalance + DigitalZoomRatio + FocalLengthIn35mmFilm + SceneCaptureType + GainControl + Contrast + Saturation + Sharpness + DeviceSettingDescription + SubjectDistanceRange +} + +type GPS struct { + GPSVersionID + GPSLatitudeRef + GPSLatitude + GPSLongitudeRef + GPSLongitude + GPSAltitudeRef + GPSAltitude + GPSTimeStamp + GPSSatelites + GPSStatus + GPSMeasureMode + GPSDOP + GPSSpeedRef + GPSSpeed + GPSTrackRef + GPSTrack + GPSImgDirectionRef + GPSImgDirection + GPSMapDatum + GPSDestLatitudeRef + GPSDestLatitude + GPSDestLongitudeRef + GPSDestLongitude + GPSDestBearingRef + GPSDestBearing + GPSDestDistanceRef + GPSDestDistance + GPSProcessingMethod + GPSAreaInformation + GPSDateStamp + GPSDifferential +} + +type InterOp struct { + InteroperabilityIndex +} + +type Fields struct { + Std + Sub + GPS +} + +func New(x exif.Exif) *Fields { + +} diff --git a/third_party/github.com/camlistore/goexif/exif/sample1.jpg b/third_party/github.com/camlistore/goexif/exif/sample1.jpg new file mode 100644 index 000000000..87bcf8e33 Binary files /dev/null and b/third_party/github.com/camlistore/goexif/exif/sample1.jpg differ diff --git a/third_party/github.com/camlistore/goexif/tiff/sample1.tif b/third_party/github.com/camlistore/goexif/tiff/sample1.tif new file mode 100644 index 000000000..fe51399c5 Binary files /dev/null and b/third_party/github.com/camlistore/goexif/tiff/sample1.tif differ diff --git a/third_party/github.com/camlistore/goexif/tiff/tag.go b/third_party/github.com/camlistore/goexif/tiff/tag.go new file mode 100644 index 000000000..d4c74f2ec --- /dev/null +++ b/third_party/github.com/camlistore/goexif/tiff/tag.go @@ -0,0 +1,321 @@ +package tiff + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "math/big" + "strings" + "unicode" + "unicode/utf8" +) + +type FormatType int + +const ( + IntVal FormatType = iota + FloatVal + RatVal + StringVal + UndefVal + OtherVal +) + +var fmtSize = map[uint16]uint32{ + 1: 1, + 2: 1, + 3: 2, + 4: 4, + 5: 8, + 6: 1, + 7: 1, + 8: 2, + 9: 4, + 10: 8, + 11: 4, + 12: 8, +} + +// Tag reflects the parsed content of a tiff IFD tag. +type Tag struct { + // Id is the 2-byte tiff tag identifier + Id uint16 + // Fmt is an integer (1 through 12) indicating the tag value's format. + Fmt uint16 + // Ncomp is the number of type Fmt stored in the tag's value (i.e. the tag's + // value is an array of type Fmt and length Ncomp). + Ncomp uint32 + // Val holds the bytes that represent the tag's value. + Val []byte + + order binary.ByteOrder + + intVals []int64 + floatVals []float64 + ratVals [][]int64 + strVal string +} + +// DecodeTag parses a tiff-encoded IFD tag from r and returns Tag object. The +// first read from r should be the first byte of the tag. ReadAt offsets should +// be relative to the beginning of the tiff structure (not relative to the +// beginning of the tag). +func DecodeTag(r ReadAtReader, order binary.ByteOrder) (*Tag, error) { + t := new(Tag) + t.order = order + + err := binary.Read(r, order, &t.Id) + if err != nil { + return nil, errors.New("tiff: tag id read failed: " + err.Error()) + } + + err = binary.Read(r, order, &t.Fmt) + if err != nil { + return nil, errors.New("tiff: tag format read failed: " + err.Error()) + } + + err = binary.Read(r, order, &t.Ncomp) + if err != nil { + return nil, errors.New("tiff: tag component count read failed: " + err.Error()) + } + + valLen := fmtSize[t.Fmt] * t.Ncomp + var offset uint32 + if valLen > 4 { + binary.Read(r, order, &offset) + t.Val = make([]byte, valLen) + n, err := r.ReadAt(t.Val, int64(offset)) + if n != int(valLen) || err != nil { + return nil, errors.New("tiff: tag value read failed: " + err.Error()) + } + } else { + val := make([]byte, valLen) + n, err := r.Read(val) + if err != nil || n != int(valLen) { + return nil, errors.New("tiff: tag offset read failed: " + err.Error()) + } + + n, err = r.Read(make([]byte, 4-valLen)) + if err != nil || n != 4-int(valLen) { + return nil, errors.New("tiff: tag offset read failed: " + err.Error()) + } + + t.Val = val + } + + t.convertVals() + + return t, nil +} + +func (t *Tag) convertVals() { + r := bytes.NewReader(t.Val) + + switch t.Fmt { + case 2: // ascii string + t.strVal = string(t.Val) + case 1: + var v uint8 + t.intVals = make([]int64, int(t.Ncomp)) + for i := 0; i < int(t.Ncomp); i++ { + err := binary.Read(r, t.order, &v) + panicOn(err) + t.intVals[i] = int64(v) + } + case 3: + var v uint16 + t.intVals = make([]int64, int(t.Ncomp)) + for i := 0; i < int(t.Ncomp); i++ { + err := binary.Read(r, t.order, &v) + panicOn(err) + t.intVals[i] = int64(v) + } + case 4: + var v uint32 + t.intVals = make([]int64, int(t.Ncomp)) + for i := 0; i < int(t.Ncomp); i++ { + err := binary.Read(r, t.order, &v) + panicOn(err) + t.intVals[i] = int64(v) + } + case 6: + var v int8 + t.intVals = make([]int64, int(t.Ncomp)) + for i := 0; i < int(t.Ncomp); i++ { + err := binary.Read(r, t.order, &v) + panicOn(err) + t.intVals[i] = int64(v) + } + case 8: + var v int16 + t.intVals = make([]int64, int(t.Ncomp)) + for i := 0; i < int(t.Ncomp); i++ { + err := binary.Read(r, t.order, &v) + panicOn(err) + t.intVals[i] = int64(v) + } + case 9: + var v int32 + t.intVals = make([]int64, int(t.Ncomp)) + for i := 0; i < int(t.Ncomp); i++ { + err := binary.Read(r, t.order, &v) + panicOn(err) + t.intVals[i] = int64(v) + } + case 5: // unsigned rational + t.ratVals = make([][]int64, int(t.Ncomp)) + for i := 0; i < int(t.Ncomp); i++ { + var n, d uint32 + err := binary.Read(r, t.order, &n) + panicOn(err) + err = binary.Read(r, t.order, &d) + panicOn(err) + t.ratVals[i] = []int64{int64(n), int64(d)} + } + case 10: // signed rational + t.ratVals = make([][]int64, int(t.Ncomp)) + for i := 0; i < int(t.Ncomp); i++ { + var n, d int32 + err := binary.Read(r, t.order, &n) + panicOn(err) + err = binary.Read(r, t.order, &d) + panicOn(err) + t.ratVals[i] = []int64{int64(n), int64(d)} + } + case 11: // float32 + for i := 0; i < int(t.Ncomp); i++ { + t.floatVals = make([]float64, int(t.Ncomp)) + var v float32 + err := binary.Read(r, t.order, &v) + panicOn(err) + t.floatVals[i] = float64(v) + } + case 12: // float64 (double) + for i := 0; i < int(t.Ncomp); i++ { + t.floatVals = make([]float64, int(t.Ncomp)) + var u float64 + err := binary.Read(r, t.order, &u) + panicOn(err) + t.floatVals[i] = u + } + } +} + +// Format returns a value indicating which method can be called to retrieve the +// tag's value properly typed (e.g. integer, rational, etc.). +func (t *Tag) Format() FormatType { + switch t.Fmt { + case 1, 3, 4, 6, 8, 9: + return IntVal + case 5, 10: + return RatVal + case 11, 12: + return FloatVal + case 2: + return StringVal + case 7: + return UndefVal + } + return OtherVal +} + +// Rat returns the tag's i'th value as a rational number. It panics if the tag format +// is not RatVal, if the denominator is zero, or if the tag has no i'th +// component. If a denominator could be zero, use Rat2. +func (t *Tag) Rat(i int) *big.Rat { + n, d := t.Rat2(i) + return big.NewRat(n, d) +} + +// Rat2 returns the tag's i'th value as a rational number represented by a +// numerator-denominator pair. It panics if the tag format is not RatVal +// or if the tag value has no i'th component. +func (t *Tag) Rat2(i int) (num, den int64) { + if t.Format() != RatVal { + panic("Tag format is not 'rational'") + } + return t.ratVals[i][0], t.ratVals[i][1] +} + +// Int returns the tag's i'th value as an integer. It panics if the tag format is not +// IntVal or if the tag value has no i'th component. +func (t *Tag) Int(i int) int64 { + if t.Format() != IntVal { + panic("Tag format is not 'int'") + } + return t.intVals[i] +} + +// Float returns the tag's i'th value as a float. It panics if the tag format is not +// FloatVal or if the tag value has no i'th component. +func (t *Tag) Float(i int) float64 { + if t.Format() != FloatVal { + panic("Tag format is not 'float'") + } + return t.floatVals[i] +} + +// StringVal returns the tag's value as a string. It panics if the tag +// format is not StringVal +func (t *Tag) StringVal() string { + if t.Format() != StringVal { + panic("Tag format is not 'ascii string'") + } + return t.strVal +} + +// String returns a nicely formatted version of the tag. +func (t *Tag) String() string { + data, err := t.MarshalJSON() + panicOn(err) + val := string(data) + return fmt.Sprintf("{Id: %X, Val: %v}", t.Id, val) +} + +func (t *Tag) MarshalJSON() ([]byte, error) { + f := t.Format() + + switch f { + case StringVal, UndefVal: + return nullString(t.Val), nil + case OtherVal: + panic(fmt.Sprintf("Unhandled type Fmt=%v", t.Fmt)) + } + + rv := []string{} + for i := 0; i < int(t.Ncomp); i++ { + switch f { + case RatVal: + n, d := t.Rat2(i) + rv = append(rv, fmt.Sprintf(`"%v/%v"`, n, d)) + case FloatVal: + rv = append(rv, fmt.Sprintf("%v", t.Float(i))) + case IntVal: + rv = append(rv, fmt.Sprintf("%v", t.Int(i))) + } + } + return []byte(fmt.Sprintf(`[%s]`, strings.Join(rv, ","))), nil +} + +func nullString(in []byte) []byte { + rv := bytes.Buffer{} + rv.WriteByte('"') + for _, b := range in { + if unicode.IsPrint(rune(b)) { + rv.WriteByte(b) + } + } + rv.WriteByte('"') + rvb := rv.Bytes() + if utf8.Valid(rvb) { + return rvb + } + return []byte(`""`) +} + +func panicOn(err error) { + if err != nil { + panic("unexpected error: " + err.Error()) + } +} diff --git a/third_party/github.com/camlistore/goexif/tiff/tiff.go b/third_party/github.com/camlistore/goexif/tiff/tiff.go new file mode 100644 index 000000000..4affc687f --- /dev/null +++ b/third_party/github.com/camlistore/goexif/tiff/tiff.go @@ -0,0 +1,144 @@ +// Package tiff implements TIFF decoding as defined in TIFF 6.0 specification. +package tiff + +import ( + "bytes" + "encoding/binary" + "errors" + "io" + "io/ioutil" +) + +// ReadAtReader is used when decoding Tiff tags and directories +type ReadAtReader interface { + io.Reader + io.ReaderAt +} + +// Tiff provides access to decoded tiff data. +type Tiff struct { + // Dirs is an ordered slice of the tiff's Image File Directories (IFDs). + // The IFD at index 0 is IFD0. + Dirs []*Dir + // The tiff's byte-encoding (i.e. big/little endian). + Order binary.ByteOrder +} + +// Decode parses tiff-encoded data from r and returns a Tiff that reflects the +// structure and content of the tiff data. The first read from r should be the +// first byte of the tiff-encoded data (not necessarily the first byte of an +// os.File object). +func Decode(r io.Reader) (*Tiff, error) { + data, err := ioutil.ReadAll(r) + if err != nil { + return nil, errors.New("tiff: could not read data") + } + buf := bytes.NewReader(data) + + t := new(Tiff) + + // read byte order + bo := make([]byte, 2) + n, err := buf.Read(bo) + if n < len(bo) || err != nil { + return nil, errors.New("tiff: could not read tiff byte order") + } else { + if string(bo) == "II" { + t.Order = binary.LittleEndian + } else if string(bo) == "MM" { + t.Order = binary.BigEndian + } else { + return nil, errors.New("tiff: could not read tiff byte order") + } + } + + // check for special tiff marker + var sp int16 + err = binary.Read(buf, t.Order, &sp) + if err != nil || 0x002A != sp { + return nil, errors.New("tiff: could not find special tiff marker") + } + + // load offset to first IFD + var offset int32 + err = binary.Read(buf, t.Order, &offset) + if err != nil { + return nil, errors.New("tiff: could not read offset to first IFD") + } + + // load IFD's + var d *Dir + for offset != 0 { + // seek to offset + _, err := buf.Seek(int64(offset), 0) + if err != nil { + return nil, errors.New("tiff: seek to IFD failed") + } + + if buf.Len() == 0 { + return nil, errors.New("tiff: seek offset after EOF") + } + + // load the dir + d, offset, err = DecodeDir(buf, t.Order) + if err != nil { + return nil, err + } + t.Dirs = append(t.Dirs, d) + } + + return t, nil +} + +func (tf *Tiff) String() string { + s := "Tiff{" + for _, d := range tf.Dirs { + s += d.String() + ", " + } + return s + "}" +} + +// Dir reflects the parsed content of a tiff Image File Directory (IFD). +type Dir struct { + Tags []*Tag +} + +// DecodeDir parses a tiff-encoded IFD from r and returns a Dir object. offset +// is the offset to the next IFD. The first read from r should be at the first +// byte of the IFD. ReadAt offsets should be relative to the beginning of the +// tiff structure (not relative to the beginning of the IFD). +func DecodeDir(r ReadAtReader, order binary.ByteOrder) (d *Dir, offset int32, err error) { + d = new(Dir) + + // get num of tags in ifd + var nTags int16 + err = binary.Read(r, order, &nTags) + if err != nil { + return nil, 0, errors.New("tiff: falied to read IFD tag count: " + err.Error()) + } + + // load tags + for n := 0; n < int(nTags); n++ { + t, err := DecodeTag(r, order) + if err != nil { + return nil, 0, err + } + d.Tags = append(d.Tags, t) + } + + // get offset to next ifd + err = binary.Read(r, order, &offset) + if err != nil { + return nil, 0, errors.New("tiff: falied to read offset to next IFD: " + err.Error()) + } + + return d, offset, nil +} + +func (d *Dir) String() string { + s := "Dir{" + for _, t := range d.Tags { + s += t.String() + ", " + } + return s + "}" +} diff --git a/third_party/github.com/camlistore/goexif/tiff/tiff_test.go b/third_party/github.com/camlistore/goexif/tiff/tiff_test.go new file mode 100644 index 000000000..2dd2982db --- /dev/null +++ b/third_party/github.com/camlistore/goexif/tiff/tiff_test.go @@ -0,0 +1,228 @@ +package tiff + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "os" + "testing" +) + +type input struct { + tgId string + tpe string + nVals string + offset string + val string +} + +type output struct { + id uint16 + format uint16 + count uint32 + val []byte +} + +type tagTest struct { + big input // big endian + little input // little endian + out output +} + +/////////////////////////////////////////////// +//// Big endian Tests ///////////////////////// +/////////////////////////////////////////////// +var set1 = []tagTest{ + //////////// string type ////////////// + tagTest{ + // {"TgId", "TYPE", "N-VALUES", "OFFSET--", "VAL..."}, + input{"0003", "0002", "00000002", "11000000", ""}, + input{"0300", "0200", "02000000", "11000000", ""}, + output{0x0003, 0x0002, 0x0002, []byte{0x11, 0x00}}, + }, + tagTest{ + input{"0001", "0002", "00000006", "00000012", "111213141516"}, + input{"0100", "0200", "06000000", "12000000", "111213141516"}, + output{0x0001, 0x0002, 0x0006, []byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16}}, + }, + //////////// int (1-byte) type //////////////// + tagTest{ + input{"0001", "0001", "00000001", "11000000", ""}, + input{"0100", "0100", "01000000", "11000000", ""}, + output{0x0001, 0x0001, 0x0001, []byte{0x11}}, + }, + tagTest{ + input{"0001", "0001", "00000005", "00000010", "1112131415"}, + input{"0100", "0100", "05000000", "10000000", "1112131415"}, + output{0x0001, 0x0001, 0x0005, []byte{0x11, 0x12, 0x13, 0x14, 0x15}}, + }, + tagTest{ + input{"0001", "0006", "00000001", "11000000", ""}, + input{"0100", "0600", "01000000", "11000000", ""}, + output{0x0001, 0x0006, 0x0001, []byte{0x11}}, + }, + tagTest{ + input{"0001", "0006", "00000005", "00000010", "1112131415"}, + input{"0100", "0600", "05000000", "10000000", "1112131415"}, + output{0x0001, 0x0006, 0x0005, []byte{0x11, 0x12, 0x13, 0x14, 0x15}}, + }, + //////////// int (2-byte) types //////////////// + tagTest{ + input{"0001", "0003", "00000002", "11111212", ""}, + input{"0100", "0300", "02000000", "11111212", ""}, + output{0x0001, 0x0003, 0x0002, []byte{0x11, 0x11, 0x12, 0x12}}, + }, + tagTest{ + input{"0001", "0003", "00000003", "00000010", "111213141516"}, + input{"0100", "0300", "03000000", "10000000", "111213141516"}, + output{0x0001, 0x0003, 0x0003, []byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16}}, + }, + tagTest{ + input{"0001", "0008", "00000001", "11120000", ""}, + input{"0100", "0800", "01000000", "11120000", ""}, + output{0x0001, 0x0008, 0x0001, []byte{0x11, 0x12}}, + }, + tagTest{ + input{"0001", "0008", "00000003", "00000100", "111213141516"}, + input{"0100", "0800", "03000000", "00100000", "111213141516"}, + output{0x0001, 0x0008, 0x0003, []byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16}}, + }, + //////////// int (4-byte) types //////////////// + tagTest{ + input{"0001", "0004", "00000001", "11121314", ""}, + input{"0100", "0400", "01000000", "11121314", ""}, + output{0x0001, 0x0004, 0x0001, []byte{0x11, 0x12, 0x13, 0x14}}, + }, + tagTest{ + input{"0001", "0004", "00000002", "00000010", "1112131415161718"}, + input{"0100", "0400", "02000000", "10000000", "1112131415161718"}, + output{0x0001, 0x0004, 0x0002, []byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18}}, + }, + tagTest{ + input{"0001", "0009", "00000001", "11121314", ""}, + input{"0100", "0900", "01000000", "11121314", ""}, + output{0x0001, 0x0009, 0x0001, []byte{0x11, 0x12, 0x13, 0x14}}, + }, + tagTest{ + input{"0001", "0009", "00000002", "00000011", "1112131415161819"}, + input{"0100", "0900", "02000000", "11000000", "1112131415161819"}, + output{0x0001, 0x0009, 0x0002, []byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x18, 0x19}}, + }, + //////////// rational types //////////////////// + tagTest{ + input{"0001", "0005", "00000001", "00000010", "1112131415161718"}, + input{"0100", "0500", "01000000", "10000000", "1112131415161718"}, + output{0x0001, 0x0005, 0x0001, []byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18}}, + }, + tagTest{ + input{"0001", "000A", "00000001", "00000011", "1112131415161819"}, + input{"0100", "0A00", "01000000", "11000000", "1112131415161819"}, + output{0x0001, 0x000A, 0x0001, []byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x18, 0x19}}, + }, + //////////// float types /////////////////////// + tagTest{ + input{"0001", "0005", "00000001", "00000010", "1112131415161718"}, + input{"0100", "0500", "01000000", "10000000", "1112131415161718"}, + output{0x0001, 0x0005, 0x0001, []byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18}}, + }, + tagTest{ + input{"0101", "000A", "00000001", "00000011", "1112131415161819"}, + input{"0101", "0A00", "01000000", "11000000", "1112131415161819"}, + output{0x0101, 0x000A, 0x0001, []byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x18, 0x19}}, + }, +} + +func TestDecodeTag(t *testing.T) { + for i, tst := range set1 { + testSingle(t, binary.BigEndian, tst.big, tst.out, i) + testSingle(t, binary.LittleEndian, tst.little, tst.out, i) + } +} + +func testSingle(t *testing.T, order binary.ByteOrder, in input, out output, i int) { + data := buildInput(in, order) + buf := bytes.NewReader(data) + tg, err := DecodeTag(buf, order) + if err != nil { + t.Errorf("(%v) tag %v%+v decode failed: %v", order, i, in, err) + return + } + + if tg.Id != out.id { + t.Errorf("(%v) tag %v id decode: expected %v, got %v", order, i, out.id, tg.Id) + } + if tg.Fmt != out.format { + t.Errorf("(%v) tag %v format decode: expected %v, got %v", order, i, out.format, tg.Fmt) + } + if tg.Ncomp != out.count { + t.Errorf("(%v) tag %v N-components decode: expected %v, got %v", order, i, out.count, tg.Ncomp) + } + if !bytes.Equal(tg.Val, out.val) { + t.Errorf("(%v) tag %v value decode: expected %v, got %v", order, i, out.val, tg.Val) + } +} + +// buildInputBig creates a byte-slice based on big-endian ordered input +func buildInput(in input, order binary.ByteOrder) []byte { + data := make([]byte, 0) + d, _ := hex.DecodeString(in.tgId) + data = append(data, d...) + d, _ = hex.DecodeString(in.tpe) + data = append(data, d...) + d, _ = hex.DecodeString(in.nVals) + data = append(data, d...) + d, _ = hex.DecodeString(in.offset) + data = append(data, d...) + + if in.val != "" { + off := order.Uint32(d) + for i := 0; i < int(off)-12; i++ { + data = append(data, 0xFF) + } + + d, _ = hex.DecodeString(in.val) + data = append(data, d...) + } + + return data +} + +func TestDecode(t *testing.T) { + name := "sample1.tif" + f, err := os.Open(name) + if err != nil { + t.Fatalf("%v\n", err) + } + + tif, err := Decode(f) + if err != nil { + t.Fatal(err) + } + + t.Log(tif) +} + +func TestDecodeTag_blob(t *testing.T) { + buf := bytes.NewReader(data()) + buf.Seek(10, 1) + tg, err := DecodeTag(buf, binary.LittleEndian) + if err != nil { + t.Fatalf("tag decode failed: %v", err) + } + + t.Logf("tag: %v+\n", tg) + n, d := tg.Rat2(0) + t.Logf("tag rat val: %v\n", n, d) +} + +func data() []byte { + s1 := "49492A000800000002001A0105000100" + s1 += "00002600000069870400010000001102" + s1 += "0000000000004800000001000000" + + dat, err := hex.DecodeString(s1) + if err != nil { + panic("invalid string fixture") + } + return dat +}