Giter VIP home page Giter VIP logo

imagemeta's Introduction

Imagemeta

License Godoc ReportCard Coverage Status Build

Image Metadata (Exif and XMP) extraction for JPEG, HEIC, AVIF, TIFF, and Camera Raw in golang. Imagetype identifcation. Zero allocation Perceptual Image Hash. Goal is features that are precise and performance oriented for working with images.

Documentation

See Documentation for more information.

Example Usage

Example usage:

    package main

    import (
    	"fmt"
    	"os"

    	"github.com/evanoberholster/imagemeta"
    )

	f, err := os.Open("image.jpg")
	if err != nil {
		panic(err)
	}
	defer f.Close()

	e, err := imagemeta.Decode(f)
	if err != nil {
		panic(err)
	}
	fmt.Println(e)

Imagehash

Zero allocation PerceptualHash algorithm (64Bit and 256Bit) github.com/evanoberholster/imagemeta/imagehash. Adapted from https://github.com/corona10/goimagehash. Image will need to be resized to 64x64 prior to image hashing.

Contributing

Issues, Suggestions and Pull Requests are welcome.

Benchmarks

See BENCHMARK.md To run your own benchmarks see bench_test.go

Imagetype Identification

Images can be identified with: "github.com/evanoberholster/imagemeta/imagetype" package.

TODO

  • Stabilize ImageTypes API
  • Add Exif parsing for individual image types (jpg, heic, cr2, tiff, dng)
  • Add CR3 and Heic image metadata support.
  • Add Avif image metadata support
  • Add Canon Exif Makernote support
  • Add Nikon Exif Makernote support
  • Add Camera Make and Model Lookup tables
  • Add Preview Image extraction
  • Refactor XMP parsing as "xmp" package
  • Stabalize Imagemeta API
  • Improve test coverage
  • Add Webp image metadata support
  • Add CRW image metadata support (ciff format images)
  • Documentation

Based on and Inspired by

Inspired by Phil Harvey http://exiftool.org, go-exif https://github.com/dsoprea/go-exif, and RW Carlsen https://github.com/rwcarlsen/goexif

Special Thanks to:

Contributors

LICENSE

Copyright (c) 2020-2023, Evan Oberholster & Contributors

imagemeta's People

Contributors

abrander avatar evanoberholster avatar maddosaurus avatar manneaber avatar matrixik avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

imagemeta's Issues

About exiftool name

Hello,

on https://discuss.pixls.us/t/looking-for-sample-raw-photos-from-many-camera-manufacturers-for-testing-exif-extraction/18833/7?u=matrixik
there is actually good comment about this library name. It's the same as exiftool.org (that you even link in README).

Going forward problem with this name could be that it's confusing to users, if you create cmd tool using this name when installed it will clash with exiftool.org version (because of the same name), impossible to find your library/tool in Google/any other search engine because all links will be referring to exiftool.org tool (because it's a lot older and have wide usage) and probably more I'm not thinking now.

Incorrect Tiff Header ImageType

Hi Evan,

I really like this GO package and I appreciate your constant work to update and progress the codebase in it. I'm using it in tandem with Canon's own API (CCAPI) on their cameras to automate photo upload, naming, and tagging. I ran into an issue(possibly a bug), whilst trying to retrieve the * rating imbedded in the exif/tiff data. (NOT the ISOSPEEDRATING)

Without bogging you down with too much code, I have a function that calls both imageType.Scan() as well as tiff.ScanTiffHeader() listed below:

// GetImageType returns the type of file passed in through the 'path' parameter
func GetImageType(path string) (string, utils.HTTPError) {
	file, err := os.Open(path)
	if err != nil {
		return "", utils.HTTPError{
			Error:      utils.ErrorTrace(err, "Error Opening Specified File Path"+path),
			StatusCode: 500,
		}
	}
	defer file.Close()

	fileType, err := imagetype.Scan(file)
	if err != nil {
		return "", utils.HTTPError{
			Error:      utils.ErrorTrace(err, "Error Determining ImageType of"+path),
			StatusCode: 500,
		}
	}

	unformattedImageType := fileType.String()
	returnImageType := (strings.Trim(unformattedImageType, "image/"))
	if returnImageType == "jp" || returnImageType == "jpg" {
		returnImageType = "jpeg" // Renamed for Consistency
	}

	fmt.Println("\nImage type from imageType.Scan():")
	fmt.Println(fileType.String())

	br := bufio.NewReader(file)

	// Same result if imageType is harded-coded ↓↓↓
	// header, err := tiff.ScanTiffHeader(br, imagetype.ImageCR3)
	header, err := tiff.ScanTiffHeader(br, fileType)
	if err != nil {
		return "", utils.HTTPError{
			Error:      utils.ErrorTrace(err, "Error Creating Tiff Header"),
			StatusCode: 500,
		}
	}

	fmt.Println("\n\nTiff Header Values from ScanTiffHeader():")
	fmt.Println(header.String())

	fmt.Println("\n\nCheck if Header is valid:")
	fmt.Println(header.IsValid())

	return returnImageType, utils.HTTPError{Error: nil, StatusCode: 200}
}

It successfully retrieves the fileType, however the diff header always returns the incorrect fileType, even if it's hard-coded in. Here's a terminal output of the above code. (It's from a RESTful API)

[GIN-debug] Listening and serving HTTP on :8080


Image type from imageType.Scan():
image/x-canon-cr3


Tiff Header Values from ScanTiffHeader():
ByteOrder: LittleEndian, Ifd: Ifd, Offset: 0x0008 TiffOffset: 0x0128 Length: 0 Imagetype: application/octet-stream


Check if Header is valid:
true


2022/08/19 15:29:06 No Lens Make found for file: C:/Users/Daniel/Desktop/upload/IMGL2080.cr3

2022/08/19 15:29:06 GetMetadata() Finished Successfully. Total Time Elapsed:113.5163ms

[GIN] 2022/08/19 - 15:29:06 |←[97;42m 200 ←[0m|    115.5137ms |          ::1 |←[97;44m GET     ←[0m "/photo/metadata"

The image I referenced using the path variable in the code is in .CR3 raw format, and can be found here

Map for iterating over extracted EXIF fields

Is there a way to get all the extracted EXIF fields? Like a map I can iterate over?

It's OK if these fields don't have any special interpreation: just a key-value store of whatever fields were discovered. This would be easier for me to work with than a huge struct with hard-coded fields. (I think some struct fields are OK, especially if they are common or need interpretation to be useful -- but I'd still like a map of everything that was parsed anyway.)

Originally posted by @mholt in #48 (comment)

Creating logs when used as library

Hello, after 55123c7 your library started outputing logs when used as library. I think by default libraries should not generate any logs but allow users to turn it on if necessary for debugging.

Will the `develop` branch have a `Parse()` function too?

Hi, I'm dipping my toes back into this library, which I'm really looking forward to (the develop branch).

I notice the README for v1 shows a function, imagemeta.Parse(). Will or does the develop branch have something like this too?

Basically, "here's a file, parse out all the metadata you can find, whatever the format."

That would make this an absolute dream. 🌤️

Thanks for working on this lib!

PS. Will there be a way to iterate all metadata fields/values? I know currently there's a lot of getter methods, but do you think we will be able to enumerate them all? Even if they're uncommon I'd like to access whatever is in there.

Timezone support in DateTime()

Timestamps are stored in an undefined timezone in EXIF. In DateTime() imagemeta assumes UTC. There is a note mentioning missing support for OffsetTimeOriginal and OffsetTimeDigitized, but I have yet to see those in the wild.

The current implementation assuming UTC is almost always wrong.

I have a few suggestions for fixing this.

  1. Add a timezone argument to DateTime(). This will break API-compatibility and will push the problem to the user of the package.

  2. Add an additional function like DateTimeZone(tz *time.Location). This will provide a more correct function while maintaining backwards compatibility for using using DateTime().

  3. Add a SetTimeZone() method to exif.Data, and let it default to time.UTC. This will provide backwards compatibility and not expose another helper function.

  4. Assume local timezone using time.Local. This will not provide backwards compatibility, and not really fix the problem, but it will probably guess correct more often than assuming UTC.

Would you be interested in a PR implementing one of those suggestions?

PNG image inclussion

Hi! Thanks for making this package, it is very useful, i was wondering if there is a chance to add PNG images into the library, i was really looking for it

jpg image hitting an endless loop in jpegReader::nextMarker

I have an image from a Nikon scanner that's somehow hitting an endless loop in the nextMarker method. jr.err = nil, and jr.pos = 0, it's passing the isMarkerFirstByte check, failing the isSOIMarker check, and then as jr.pos is still 0, the loop continues indefinitely.

The nature of this loop means I can't recover from this situation without changing the code in some possibly unhelpful way, like naively setting a max loop count or something like that. Someone want to take a whack at figuring out the correct way to limit this loop?

`xmp: error no XMP Tag found` even though multiple XMP tags are found

I'm using the example from the readme on a JPEG file:

m, err := imagemeta.Parse(f)
if err != nil {
    return nil
}
fmt.Println(m.Xmp())

but I get an error of xmp: error no XMP Tag found (i.e. EOF), even though this file has two XMP sections, with these offsets from 0:

$ grep --binary --text --byte-offset --only-matching '<x:xmpmeta' PXL_20230104_032015182.MP.jpg 
8557:<x:xmpmeta
10480:<x:xmpmeta

The first one is for the JPEG, the second one I believe is for an embedded video (motion picture).

When I add some fmt.Printf() lines, I see that the XmpHeader after parsing is showing an offset of 75971, which... is either clearly wrong, or is not offset from 0.

I've cloned the repo and am trying to get a grasp for how the readers work, but there's lots of sectioning and ReadAt() and it's a little hard for me to follow.

Any ideas or troubleshooting tips?

Thanks for this package!

Interesting work

I've written my own exif parser recently. Maybe we can consolidate work?
My library is quite simple- Keep in mind I learned about exif last week and all of it is quite new to me.

https://github.com/soypat/exif

Benchmarks show your library is around 2 times faster for parsingexif metadata.

BenchmarkThisPackage_SmallImage-12    	  156421	      7274 ns/op	    2512 B/op	      52 allocs/op
BenchmarkImageMeta_SmallImage-12      	  286161	      3742 ns/op	     921 B/op	       5 allocs/op

convert cr2 -> jpg

I know that this might not be exactly what this library is intended to do, but i just wanted to know if i've wandered too far off the path or should this work?

package main

import (
	"fmt"
	"image/jpeg"
	"os"

	"github.com/evanoberholster/imagemeta"
)

const imageFilename = "~/Alpaca/IMG_6179.CR2"

func main() {
	var err error

	f, err := os.Open(imageFilename)
	if err != nil {
		panic(err)
	}
	defer f.Close()

	m, err := imagemeta.Parse(f)
	if err != nil {
		panic(err)
	}
	fmt.Println(m.Exif())
	fmt.Println(m.Xmp())
	fmt.Println(m.ImageType())
	fmt.Println(m.Dimensions())
	img, err := jpeg.Decode(m.PreviewImage())  // panic happens here
	if err != nil {
		panic(err)
	}

	out, err := os.Create("./out.jpg")
	if err != nil {
		panic(err)
	}
	defer out.Close()

	err = jpeg.Encode(out, img, &jpeg.Options{Quality: 90})
	if err != nil {
		panic(err)
	}
}
&{0xc000134000 map[16777472:{5472 1 256 3 1} 16777473:{3648 1 257 3 1} 16777474:{238 3 258 3 1} 16777475:{6 1 259 3 1} 16777487:{244 6 271 2 1} 16777488:{250 13 272 2 1} 16777489:{96900 1 273 4 1} 16777490:{1 1 274 3 1} 16777495:{1667194 1 279 4 1} 16777498:{282 1 282 5 1} 16777499:{290 1 283 5 1} 16777512:{2 1 296 3 1} 16777522:{298 20 306 2 1} 16777531:{318 13 315 2 1} 16777916:{71876 8192 700 1 1} 16810648:{382 13 33432 2 1} 16843265:{80068 1 513 4 1} 16843266:{16832 1 514 4 1} 16908544:{464 1 256 3 1} 16908545:{309 1 257 3 1} 16908546:{71734 3 258 3 1} 16908547:{1 1 259 3 1} 16908550:{2 1 262 3 1} 16908561:{1764096 1 273 4 1} 16908565:{3 1 277 3 1} 16908566:{309 1 278 3 1} 16908567:{860256 1 279 4 1} 16908572:{1 1 284 3 1} 16958937:{2 1 50649 4 1} 16959173:{3 1 50885 4 1} 16959196:{71740 4 50908 4 1} 16974080:{5568 1 256 3 1} 16974081:{3708 1 257 3 1} 16974083:{6 1 259 3 1} 16974097:{2624352 1 273 4 1} 16974103:{21736030 1 279 4 1} 17024472:{1 1 50648 4 1} 17024480:{1 1 50656 4 1} 17024576:{71870 3 50752 3 1} 17024709:{1 1 50885 4 1} 50365082:{908 1 33434 5 3} 50365085:{916 1 33437 5 3} 50366498:{2 1 34850 3 3} 50366503:{800 1 34855 3 3} 50366512:{2 1 34864 3 3} 50366514:{800 1 34866 4 3} 50368512:{808661552 4 36864 7 3} 50368515:{924 20 36867 2 3} 50368516:{944 20 36868 2 3} 50368769:{197121 4 37121 7 3} 50369025:{964 1 37377 10 3} 50369026:{972 1 37378 5 3} 50369028:{980 1 37380 10 3} 50369031:{2 1 37383 3 3} 50369033:{16 1 37385 3 3} 50369034:{988 1 37386 5 3} 50369158:{69256 264 37510 7 3} 50369168:{12336 3 37520 2 3} 50369169:{12336 3 37521 2 3} 50369170:{12336 3 37522 2 3} 50372608:{808464688 4 40960 7 3} 50372609:{1 1 40961 3 3} 50372610:{5472 1 40962 3 3} 50372611:{3648 1 40963 3 3} 50372613:{69520 1 40965 4 3} 50373134:{69550 1 41486 5 3} 50373135:{69558 1 41487 5 3} 50373136:{2 1 41488 3 3} 50373633:{0 1 41985 3 3} 50373634:{0 1 41986 3 3} 50373635:{0 1 41987 3 3} 50373638:{0 1 41990 3 3} 50373680:{69566 13 42032 2 3} 50373681:{69598 13 42033 2 3} 50373682:{69630 4 42034 5 3} 50373684:{69662 29 42036 2 3} 50373685:{69736 11 42037 2 3} 67108864:{770 4 0 1 4} 67108865:{78 2 1 2 4} 67108866:{70126 3 2 5 4} 67108867:{87 2 3 2 4} 67108868:{70150 3 4 5 4} 67108869:{0 1 5 1 4} 67108870:{70174 1 6 5 4} 67108871:{70182 3 7 5 4} 67108872:{12849 3 8 2 4} 67108873:{65 2 9 2 4} 67108874:{51 2 10 2 4} 67108875:{70666 1 11 5 4} 67108882:{70698 7 18 2 4} 67108893:{71530 11 29 2 4} 100663297:{1506 49 1 3 6} 100663298:{1604 4 2 3 6} 100663299:{1612 4 3 3 6} 100663300:{1620 34 4 3 6} 100663302:{1688 13 6 2 6} 100663303:{1720 24 7 2 6} 100663305:{1744 32 9 2 6} 100663309:{1776 1536 13 7 6} 100663312:{2147484418 1 16 4 6} 100663315:{3312 4 19 3 6} 100663321:{1 1 25 3 6} 100663334:{3320 56 38 3 6} 100663349:{3432 4 53 4 6} 100663443:{3448 44 147 3 6} 100663445:{3536 74 149 2 6} 100663446:{3610 16 150 2 6} 100663447:{3626 1024 151 7 6} 100663448:{4650 4 152 3 6} 100663449:{4658 106 153 4 6} 100663450:{5082 5 154 4 6} 100663456:{5102 14 160 3 6} 100663466:{5130 6 170 3 6} 100663476:{1 1 180 3 6} 100663504:{0 1 208 4 6} 100663520:{5142 17 224 3 6} 100679681:{5176 1313 16385 3 6} 100679682:{7802 43636 16386 7 6} 100679685:{51438 16792 16389 7 6} 100679688:{68230 3 16392 3 6} 100679689:{68236 3 16393 3 6} 100679696:{68242 32 16400 2 6} 100679697:{68274 252 16401 7 6} 100679698:{68526 32 16402 2 6} 100679699:{68558 11 16403 4 6} 100679701:{68602 456 16405 7 6} 100679702:{69058 7 16406 4 6} 100679704:{69086 7 16408 4 6} 100679705:{69114 30 16409 7 6} 100679712:{69144 7 16416 4 6} 100679713:{69172 5 16417 4 6} 100679717:{69192 9 16421 4 6} 100679719:{69228 5 16423 4 6}] Canon Canon EOS 6D 5472 3648 230 16} <nil>
{{   0  0  0/0 } { 0 0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC 0 Not Defined Auto 0/0 0 {false 0 false false 0} Unknown 0 0.00mm 0 0 0 0 0001-01-01 00:00:00 +0000 UTC} {   [] [] 0 0 Unknown} {0001-01-01 00:00:00 +0000 UTC   0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC 0} {[]  [] 0001-01-01 00:00:00 +0000 UTC [] application/octet-stream  [] []  [] [] []} {} {00000000-0000-0000-0000-000000000000 00000000-0000-0000-0000-000000000000 00000000-0000-0000-0000-000000000000 [] }} xmp: error no XMP Tag found
image/x-canon-cr2
width: 5472, height: 3648
panic: invalid JPEG format: missing SOI marker

goroutine 1 [running]:
main.main()
	main.go:32 +0x509
exit status 2

img.zip

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.