98 lines
2.1 KiB
Go
98 lines
2.1 KiB
Go
package media
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
)
|
|
|
|
// JPEGMeta is what a JPEG's EXIF block says that matters here: which way up
|
|
// the picture goes, and whether it carries the place it was taken.
|
|
type JPEGMeta struct {
|
|
Orientation int // 1-8 as EXIF defines them; 1 when absent
|
|
GPS bool // a GPS block with at least one entry
|
|
EXIF bool // any EXIF block at all
|
|
}
|
|
|
|
// ReadJPEGMeta walks a JPEG's segments up to the image data and reads the
|
|
// EXIF block, if there is one. It never fails: anything it can't make sense
|
|
// of is treated as absent.
|
|
func ReadJPEGMeta(data []byte) JPEGMeta {
|
|
m := JPEGMeta{Orientation: 1}
|
|
if len(data) < 4 || data[0] != 0xFF || data[1] != 0xD8 {
|
|
return m
|
|
}
|
|
for i := 2; i+4 <= len(data); {
|
|
if data[i] != 0xFF {
|
|
return m
|
|
}
|
|
marker := data[i+1]
|
|
if marker == 0xFF { // fill byte
|
|
i++
|
|
continue
|
|
}
|
|
if marker == 0xDA || marker == 0xD9 { // image data, or the end
|
|
return m
|
|
}
|
|
if marker >= 0xD0 && marker <= 0xD7 || marker == 0x01 { // no length
|
|
i += 2
|
|
continue
|
|
}
|
|
n := int(binary.BigEndian.Uint16(data[i+2:]))
|
|
if n < 2 || i+2+n > len(data) {
|
|
return m
|
|
}
|
|
seg := data[i+4 : i+2+n]
|
|
if marker == 0xE1 && bytes.HasPrefix(seg, []byte("Exif\x00\x00")) {
|
|
m.EXIF = true
|
|
readTIFF(seg[6:], &m)
|
|
}
|
|
i += 2 + n
|
|
}
|
|
return m
|
|
}
|
|
|
|
func readTIFF(t []byte, m *JPEGMeta) {
|
|
if len(t) < 8 {
|
|
return
|
|
}
|
|
var bo binary.ByteOrder
|
|
switch string(t[:2]) {
|
|
case "II":
|
|
bo = binary.LittleEndian
|
|
case "MM":
|
|
bo = binary.BigEndian
|
|
default:
|
|
return
|
|
}
|
|
if bo.Uint16(t[2:]) != 42 {
|
|
return
|
|
}
|
|
entries := func(off uint32) [][]byte {
|
|
if off < 8 || int(off)+2 > len(t) {
|
|
return nil
|
|
}
|
|
count := int(bo.Uint16(t[off:]))
|
|
var out [][]byte
|
|
for k := 0; k < count; k++ {
|
|
p := int(off) + 2 + k*12
|
|
if p+12 > len(t) {
|
|
break
|
|
}
|
|
out = append(out, t[p:p+12])
|
|
}
|
|
return out
|
|
}
|
|
for _, e := range entries(bo.Uint32(t[4:])) {
|
|
switch bo.Uint16(e) {
|
|
case 0x0112: // Orientation, a SHORT held in the entry itself
|
|
if o := int(bo.Uint16(e[8:])); o >= 1 && o <= 8 {
|
|
m.Orientation = o
|
|
}
|
|
case 0x8825: // pointer to the GPS block
|
|
if len(entries(bo.Uint32(e[8:]))) > 0 {
|
|
m.GPS = true
|
|
}
|
|
}
|
|
}
|
|
}
|