mirror of
https://github.com/dsoprea/go-exif.git
synced 2025-05-31 11:41:57 +00:00
We now keep track of the further offset that we have accessed (though not necessarily the furthest offset of data that is unparseable by us). It sometimes supports debugging efforts to have a ballpark idea of how much EXIF data there is, rather than just "from the signature to the end of the file" or however big the EXIF container is in the enclosing image format. The latter can be buggy or ambiguous.
185 lines
4.7 KiB
Go
185 lines
4.7 KiB
Go
// This tool dumps EXIF information from images.
|
|
//
|
|
// Example command-line:
|
|
//
|
|
// exif-read-tool -filepath <file-path>
|
|
//
|
|
// Example Output:
|
|
//
|
|
// IFD=[IfdIdentity<PARENT-NAME=[] NAME=[IFD]>] ID=(0x010f) NAME=[Make] COUNT=(6) TYPE=[ASCII] VALUE=[Canon]
|
|
// IFD=[IfdIdentity<PARENT-NAME=[] NAME=[IFD]>] ID=(0x0110) NAME=[Model] COUNT=(22) TYPE=[ASCII] VALUE=[Canon EOS 5D Mark III]
|
|
// IFD=[IfdIdentity<PARENT-NAME=[] NAME=[IFD]>] ID=(0x0112) NAME=[Orientation] COUNT=(1) TYPE=[SHORT] VALUE=[1]
|
|
// IFD=[IfdIdentity<PARENT-NAME=[] NAME=[IFD]>] ID=(0x011a) NAME=[XResolution] COUNT=(1) TYPE=[RATIONAL] VALUE=[72/1]
|
|
// ...
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
|
|
"encoding/json"
|
|
"io/ioutil"
|
|
|
|
"github.com/dsoprea/go-logging"
|
|
|
|
"github.com/dsoprea/go-exif/v2"
|
|
"github.com/dsoprea/go-exif/v2/common"
|
|
"github.com/dsoprea/go-exif/v2/undefined"
|
|
)
|
|
|
|
var (
|
|
mainLogger = log.NewLogger("main.main")
|
|
)
|
|
|
|
var (
|
|
filepathArg = ""
|
|
printAsJsonArg = false
|
|
printLoggingArg = false
|
|
)
|
|
|
|
type IfdEntry struct {
|
|
IfdPath string `json:"ifd_path"`
|
|
FqIfdPath string `json:"fq_ifd_path"`
|
|
IfdIndex int `json:"ifd_index"`
|
|
TagId uint16 `json:"tag_id"`
|
|
TagName string `json:"tag_name"`
|
|
TagTypeId exifcommon.TagTypePrimitive `json:"tag_type_id"`
|
|
TagTypeName string `json:"tag_type_name"`
|
|
UnitCount uint32 `json:"unit_count"`
|
|
Value interface{} `json:"value"`
|
|
ValueString string `json:"value_string"`
|
|
}
|
|
|
|
func main() {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err := log.Wrap(state.(error))
|
|
log.PrintErrorf(err, "Program error.")
|
|
os.Exit(1)
|
|
}
|
|
}()
|
|
|
|
flag.StringVar(&filepathArg, "filepath", "", "File-path of image")
|
|
flag.BoolVar(&printAsJsonArg, "json", false, "Print JSON")
|
|
flag.BoolVar(&printLoggingArg, "verbose", false, "Print logging")
|
|
|
|
flag.Parse()
|
|
|
|
if filepathArg == "" {
|
|
fmt.Printf("Please provide a file-path for an image.\n")
|
|
os.Exit(1)
|
|
}
|
|
|
|
if printLoggingArg == true {
|
|
cla := log.NewConsoleLogAdapter()
|
|
log.AddAdapter("console", cla)
|
|
|
|
scp := log.NewStaticConfigurationProvider()
|
|
scp.SetLevelName(log.LevelNameDebug)
|
|
|
|
log.LoadConfiguration(scp)
|
|
}
|
|
|
|
f, err := os.Open(filepathArg)
|
|
log.PanicIf(err)
|
|
|
|
data, err := ioutil.ReadAll(f)
|
|
log.PanicIf(err)
|
|
|
|
rawExif, err := exif.SearchAndExtractExif(data)
|
|
if err != nil {
|
|
if err == exif.ErrNoExif {
|
|
fmt.Printf("No EXIF data.\n")
|
|
os.Exit(1)
|
|
}
|
|
|
|
log.Panic(err)
|
|
}
|
|
|
|
mainLogger.Debugf(nil, "EXIF blob is (%d) bytes.", len(rawExif))
|
|
|
|
// Run the parse.
|
|
|
|
im := exif.NewIfdMappingWithStandard()
|
|
ti := exif.NewTagIndex()
|
|
|
|
entries := make([]IfdEntry, 0)
|
|
visitor := func(fqIfdPath string, ifdIndex int, ite *exif.IfdTagEntry) (err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
log.Panic(err)
|
|
}
|
|
}()
|
|
|
|
tagId := ite.TagId()
|
|
tagType := ite.TagType()
|
|
|
|
ifdPath, err := im.StripPathPhraseIndices(fqIfdPath)
|
|
log.PanicIf(err)
|
|
|
|
it, err := ti.Get(ifdPath, tagId)
|
|
if err != nil {
|
|
if log.Is(err, exif.ErrTagNotFound) {
|
|
mainLogger.Warningf(nil, "Unknown tag: [%s] (%04x)", ifdPath, tagId)
|
|
return nil
|
|
} else {
|
|
log.Panic(err)
|
|
}
|
|
}
|
|
|
|
value, err := ite.Value()
|
|
if err != nil {
|
|
if log.Is(err, exifcommon.ErrUnhandledUndefinedTypedTag) == true {
|
|
mainLogger.Warningf(nil, "Skipping non-standard undefined tag: [%s] (%04x)", ifdPath, tagId)
|
|
return nil
|
|
} else if err == exifundefined.ErrUnparseableValue {
|
|
mainLogger.Warningf(nil, "Skipping unparseable undefined tag: [%s] (%04x)", ifdPath, tagId)
|
|
return nil
|
|
}
|
|
|
|
log.Panic(err)
|
|
}
|
|
|
|
valueString, err := ite.FormatFirst()
|
|
log.PanicIf(err)
|
|
|
|
entry := IfdEntry{
|
|
IfdPath: ifdPath,
|
|
FqIfdPath: fqIfdPath,
|
|
IfdIndex: ifdIndex,
|
|
TagId: tagId,
|
|
TagName: it.Name,
|
|
TagTypeId: tagType,
|
|
TagTypeName: tagType.String(),
|
|
UnitCount: ite.UnitCount(),
|
|
Value: value,
|
|
ValueString: valueString,
|
|
}
|
|
|
|
entries = append(entries, entry)
|
|
|
|
return nil
|
|
}
|
|
|
|
_, furthestOffset, err := exif.Visit(exifcommon.IfdStandard, im, ti, rawExif, visitor)
|
|
log.PanicIf(err)
|
|
|
|
mainLogger.Debugf(nil, "EXIF blob is approximately (%d) bytes.", furthestOffset)
|
|
|
|
if printAsJsonArg == true {
|
|
data, err := json.MarshalIndent(entries, "", " ")
|
|
log.PanicIf(err)
|
|
|
|
fmt.Println(string(data))
|
|
} else {
|
|
for _, entry := range entries {
|
|
fmt.Printf("IFD-PATH=[%s] ID=(0x%04x) NAME=[%s] COUNT=(%d) TYPE=[%s] VALUE=[%s]\n", entry.IfdPath, entry.TagId, entry.TagName, entry.UnitCount, entry.TagTypeName, entry.ValueString)
|
|
}
|
|
|
|
fmt.Printf("\n")
|
|
fmt.Printf("EXIF blob is approximately (%d) bytes.\n", furthestOffset)
|
|
}
|
|
}
|