From 35b97b400495b39d7b7496c8ef3577dbee0efa69 Mon Sep 17 00:00:00 2001 From: Rob Allen Date: Sat, 30 May 2020 20:15:26 +0100 Subject: [PATCH] Retrieve metadata for an image using exiftool The exiftool can return json using the -json flag, so extract the EXIF and ITPC data from an image file using it. --- commands/info.go | 39 ++++++++++++------------ commands/root.go | 2 +- commands/upload.go | 43 ++++++++++++--------------- go.mod | 1 - go.sum | 2 -- internal/metadata.go | 70 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 109 insertions(+), 48 deletions(-) create mode 100644 internal/metadata.go diff --git a/commands/info.go b/commands/info.go index c30969c..f30c564 100644 --- a/commands/info.go +++ b/commands/info.go @@ -13,10 +13,10 @@ image to Flickr. package commands import ( - "github.com/akrabat/Golem/exif" + "github.com/akrabat/rodeo/internal" "fmt" "github.com/spf13/cobra" - "log" + "github.com/spf13/viper" "os" "path/filepath" "sort" @@ -50,36 +50,35 @@ var infoCmd = &cobra.Command{ os.Exit(2) } + exiftool := viper.GetString("cmd.exiftool") + if exiftool == "" { + fmt.Println("Error: cmd.exiftool needs to be configured.") + fmt.Println("Config file:", viper.ConfigFileUsed(), "\n") + os.Exit(2) + } + for _, filename := range args { - fileInfo(filename) + fileInfo(filename, exiftool) fmt.Printf("\n") } }, } -func fileInfo(filename string) { +func fileInfo(filename string, exiftool string) { fmt.Printf("%v:\n", filepath.Base(filename)) - f, err := os.Open(filename) + info, err := internal.GetImageInfo(filename, exiftool) if err != nil { - log.Fatal(err) - } - - image, err := ImgMeta.ReadJpeg(f) - if err != nil { - fmt.Println(err.Error()) return } - basicInfo := ImgMeta.GetBasicInfo(image) - - fmt.Printf(" Title: %v\n", basicInfo.Title) - fmt.Printf(" Description: %v\n", basicInfo.Descr) + fmt.Printf(" Title: %v\n", info.Title) + fmt.Printf(" Description: %v\n", info.Description) - sort.Sort(sort.StringSlice(basicInfo.Keywords[:])) - fmt.Printf(" Keywords: %v\n", strings.Join(basicInfo.Keywords[:], ", ")) + sort.Sort(sort.StringSlice(info.Keywords[:])) + fmt.Printf(" Keywords: %v\n", strings.Join(info.Keywords[:], ", ")) - fmt.Printf(" Dimensions: width:%v, height:%v\n", basicInfo.Width, basicInfo.Height) - fmt.Printf(" Camera: %v %v\n", basicInfo.Make, basicInfo.Model) - fmt.Printf(" Exposure: %vs, f/%v, ISO%v\n", basicInfo.ShutterSpeed, basicInfo.Aperture, basicInfo.ISO) + fmt.Printf(" Dimensions: width:%v, height:%v\n", info.Width, info.Height) + fmt.Printf(" Camera: %v %v\n", info.Make, info.Model) + fmt.Printf(" Exposure: %vs, f/%v, ISO%v\n", info.ShutterSpeed, info.Aperture, info.ISO) } diff --git a/commands/root.go b/commands/root.go index fa11b61..032f579 100644 --- a/commands/root.go +++ b/commands/root.go @@ -24,7 +24,7 @@ var cfgFile string // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ - Version: "0.0.1", + Version: "0.0.2", Use: "rodeo", Short: "Flickr command line tool", Long: `A command line tool to work with Flickr and images. diff --git a/commands/upload.go b/commands/upload.go index 6e6b899..2935139 100644 --- a/commands/upload.go +++ b/commands/upload.go @@ -13,13 +13,12 @@ image to Flickr. package commands import ( - "github.com/akrabat/Golem/exif" + "github.com/akrabat/rodeo/internal" "fmt" "github.com/spf13/cobra" "github.com/spf13/viper" "gopkg.in/masci/flickr.v2" "gopkg.in/masci/flickr.v2/photosets" - "log" "os" "os/exec" "path/filepath" @@ -77,23 +76,6 @@ var uploadCmd = &cobra.Command{ func uploadFile(filename string) string { fmt.Println("Processing " + filename) - f, err := os.Open(filename) - if err != nil { - log.Fatal(err) - } - - image, err := ImgMeta.ReadJpeg(f) - if err != nil { - fmt.Println(err.Error()) - return "" - } - - if err := f.Close(); err != nil { - log.Fatal(err) - } - - basicInfo := ImgMeta.GetBasicInfo(image) - apiKey := viper.GetString("flickr.api_key") apiSecret := viper.GetString("flickr.api_secret") oauthToken := viper.GetString("flickr.oauth_token") @@ -102,6 +84,18 @@ func uploadFile(filename string) string { fmt.Println("Unable to continue. Please run the 'rodeo authenticate' command first") } + exiftool := viper.GetString("cmd.exiftool") + if exiftool == "" { + fmt.Println("Error: cmd.exiftool needs to be configured.") + fmt.Println("Config file:", viper.ConfigFileUsed(), "\n") + os.Exit(2) + } + + info, err := internal.GetImageInfo(filename, exiftool) + if err != nil { + return "" + } + // process keyword settings from config var keywordsToRemove []string var keywordsToAdd []string @@ -115,7 +109,7 @@ func uploadFile(filename string) string { // - delete: if `true`, then do not include this keyword as a Flickr tag // - album_id: if set, then add this photo to that album // - permissions: if set, then set permissions on this photo - for _, keyword := range basicInfo.Keywords { + for _, keyword := range info.Keywords { addKeyword := true settings, ok := keywordSettings[keyword].(map[string]interface{}) if ok { @@ -155,7 +149,7 @@ func uploadFile(filename string) string { } } } else { - keywordsToAdd = basicInfo.Keywords + keywordsToAdd = info.Keywords } // Upload file to Flickr @@ -164,7 +158,7 @@ func uploadFile(filename string) string { client.OAuthToken = oauthToken client.OAuthTokenSecret = oauthTokenSecret - title := strings.Trim(basicInfo.Title, " ") + title := strings.Trim(info.Title, " ") if title == "" { // no title - use filename (without extension) title = filepath.Base(filename) @@ -182,8 +176,8 @@ func uploadFile(filename string) string { Hidden: 1, // not hidden SafetyLevel: 1, // safe } - if basicInfo.Descr != "" { - params.Description = basicInfo.Descr + if info.Description != "" { + params.Description = info.Description } response, err := flickr.UploadFile(client, filename, ¶ms) @@ -199,6 +193,7 @@ func uploadFile(filename string) string { for _, albumId := range albumsToAddTo { respAdd, err := photosets.AddPhoto(client, albumId, photoId) if err != nil { + //noinspection GoNilness fmt.Println("Failed adding photo to the set:"+albumId, err, respAdd.ErrorMsg()) } else { fmt.Println("Added photo", photoId, "to set", albumId) diff --git a/go.mod b/go.mod index f020a8d..89843b0 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/akrabat/rodeo go 1.14 require ( - github.com/akrabat/Golem v0.0.0-20200530144310-560a738b360b github.com/mitchellh/go-homedir v1.1.0 github.com/spf13/cobra v1.0.0 github.com/spf13/viper v1.7.0 diff --git a/go.sum b/go.sum index 1edf016..20769b9 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,6 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/akrabat/Golem v0.0.0-20200530144310-560a738b360b h1:CT+0W8ZUETIi1NwF7pVWHq0B7ry+2dyGV+4+DJ8/t3E= -github.com/akrabat/Golem v0.0.0-20200530144310-560a738b360b/go.mod h1:+aPpItGwX8uMzY9h0uUJNbNHNTWfFJPzAjiRTcRjsIs= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= diff --git a/internal/metadata.go b/internal/metadata.go new file mode 100644 index 0000000..ae9eed4 --- /dev/null +++ b/internal/metadata.go @@ -0,0 +1,70 @@ +package internal + +import ( + "encoding/json" + "fmt" + "log" + "os/exec" +) + +type ImageInfo struct { + Width uint `json:"ExifImageWidth"` + Height uint `json:"ExifImageHeight"` + Title string `json:"ObjectName"` + Description string `json:"Caption-Abstract"` + Keywords stringArray `json:"Keywords"` + DateCreated string `json:"DateTimeOriginal"` + Make string `json:"Make"` + Model string `json:"Model"` + ShutterSpeed string `json:"ShutterSpeedValue"` + Aperture json.Number `json:"ApertureValue"` + ISO json.Number `json:"ISO"` +} + +// A stringArray is an array of strings that has been unmarshalled from a JSON +// property that could be either a string or an array of string +type stringArray []string + +func (sa *stringArray) UnmarshalJSON(data []byte) error { + if len(data) > 0 { + switch data[0] { + case '"': + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + *sa = stringArray([]string{s}) + case '[': + var s []string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + *sa = stringArray(s) + } + } + return nil +} + +// Read metadata (Exif/IPTC) from image using exiftool +func GetImageInfo(filename string, exiftool string) (*ImageInfo, error) { + + cmd := exec.Command(exiftool, "-j", "-exif:*", "-iptc:*", filename) + + out, err := cmd.Output() + if err != nil { + fmt.Println("Error: ", err) + return nil, err + } + + // Exiftool always returns an array but as we only passed in one filename we know that there's only one element. + // Remove the surrounding `[` and `]` from the JSON string to convert to a single object. + out = out[1 : len(out)-2] + + info := ImageInfo{} + err = json.Unmarshal(out, &info) + if err != nil { + log.Println(err) + } + + return &info, nil +} \ No newline at end of file