go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/projects/blogctl/pkg/engine/util.go (about) 1 /* 2 3 Copyright (c) 2023 - Present. Will Charczuk. All rights reserved. 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository. 5 6 */ 7 8 package engine 9 10 import ( 11 "bytes" 12 "fmt" 13 "image" 14 "io/ioutil" 15 "os" 16 "path/filepath" 17 "strings" 18 "time" 19 20 // these are needed to read an image metadata 21 _ "image/jpeg" 22 _ "image/png" 23 24 "gopkg.in/yaml.v3" 25 26 "go.charczuk.com/projects/blogctl/pkg/exif" 27 "go.charczuk.com/projects/blogctl/pkg/model" 28 ) 29 30 // ListDirectory returns all the file infos within a given directory by path. 31 func ListDirectory(path string) (files []os.FileInfo, err error) { 32 err = filepath.Walk(path, func(currentPath string, info os.FileInfo, err error) error { 33 if err != nil { 34 return err 35 } 36 if currentPath == path { 37 return nil 38 } 39 if info.IsDir() { 40 return filepath.SkipDir 41 } 42 files = append(files, info) 43 return nil 44 }) 45 return 46 } 47 48 // ReadYAML reads a yaml file into a given object reference. 49 func ReadYAML(path string, obj interface{}) error { 50 f, err := os.Open(path) 51 if err != nil { 52 return err 53 } 54 defer f.Close() 55 return yaml.NewDecoder(f).Decode(obj) 56 } 57 58 // HasExtension returns if a given filename has any of a given set of extensions. 59 func HasExtension(filename string, extensions ...string) bool { 60 for _, ext := range extensions { 61 if strings.HasSuffix(filename, ext) { 62 return true 63 } 64 } 65 return false 66 } 67 68 // ReadImage reads image metadata. 69 func ReadImage(path string) (output model.Image, err error) { 70 var contents []byte 71 contents, err = ioutil.ReadFile(path) 72 if err != nil { 73 return 74 } 75 76 var imgcfg image.Config 77 imgcfg, _, err = image.DecodeConfig(bytes.NewBuffer(contents)) 78 if err != nil { 79 return 80 } 81 82 var rawExifData *exif.Exif 83 rawExifData, err = exif.Decode(bytes.NewBuffer(contents)) 84 if err != nil { 85 return 86 } 87 88 exifData, _ := GetExifData(rawExifData) 89 return model.Image{ 90 SourcePath: path, 91 Width: imgcfg.Width, 92 Height: imgcfg.Height, 93 Exif: exifData, 94 }, nil 95 } 96 97 // GetExifData generates the parsed exif data for the post. 98 func GetExifData(exifData *exif.Exif) (data model.Exif, err error) { 99 // fnumber 100 if tag, tagErr := exifData.Get(exif.FNumber); tagErr == nil { 101 nominator, denominator, ratErr := tag.Rat2(0) 102 if ratErr != nil { 103 err = fmt.Errorf("cannot generate rational value for fnumber tag: %w", ratErr) 104 return 105 } 106 if denominator != 0 { 107 data.FNumber = fmt.Sprintf("F%g", float64(nominator)/float64(denominator)) 108 } 109 } 110 111 if tag, tagErr := exifData.Get(exif.FocalLength); tagErr == nil { 112 nominator, denominator, ratErr := tag.Rat2(0) 113 if ratErr != nil { 114 err = fmt.Errorf("cannot generate rational value for focal length tag: %w", ratErr) 115 return 116 } 117 if denominator != 0 { 118 data.FocalLength = fmt.Sprintf("%gmm", float64(nominator)/float64(denominator)) 119 } 120 } 121 122 if tag, tagErr := exifData.Get(exif.ExposureTime); tagErr == nil { 123 data.ExposureTime = StripQuotes(tag.String()) + " sec" 124 } 125 if tag, tagErr := exifData.Get(exif.ISOSpeedRatings); tagErr == nil { 126 data.ISOSpeedRatings = StripQuotes(tag.String()) 127 } 128 if tag, tagErr := exifData.Get(exif.Make); tagErr == nil { 129 data.CameraMake = StripQuotes(tag.String()) 130 } 131 if tag, tagErr := exifData.Get(exif.Model); tagErr == nil { 132 data.CameraModel = StripQuotes(tag.String()) 133 } 134 if tag, tagErr := exifData.Get(exif.LensModel); tagErr == nil { 135 data.LensModel = StripQuotes(tag.String()) 136 } 137 138 // date time ... 139 data.CaptureDate, _ = exifData.DateTime() 140 return 141 } 142 143 // GenerateExifData gets exif data from a file on disk. 144 func GenerateExifData(imagePath string) (*exif.Exif, error) { 145 contents, err := ioutil.ReadFile(imagePath) 146 if err != nil { 147 return nil, err 148 } 149 150 rawExifData, err := exif.Decode(bytes.NewBuffer(contents)) 151 if err != nil { 152 return nil, err 153 } 154 155 return rawExifData, nil 156 } 157 158 // ExtractCaptureDate extracts the capture date from an image file by path. 159 func ExtractCaptureDate(imagePath string) (captureDate time.Time, err error) { 160 var exifData *exif.Exif 161 exifData, err = GenerateExifData(imagePath) 162 if err != nil { 163 return 164 } 165 captureDate, err = exifData.DateTime() 166 return 167 } 168 169 // MakeDir creates a new directory. 170 func MakeDir(path string) error { 171 return os.MkdirAll(path, 0755) 172 } 173 174 // WriteFile writes a file with default perms. 175 func WriteFile(path string, contents []byte) error { 176 return ioutil.WriteFile(path, contents, 0666) 177 } 178 179 // WriteYAML writes an object as yaml to disk. 180 func WriteYAML(path string, obj interface{}) error { 181 contents, err := yaml.Marshal(obj) 182 if err != nil { 183 return err 184 } 185 return ioutil.WriteFile(path, contents, 0666) 186 } 187 188 // Exists returns if a given file exists. 189 func Exists(path string) bool { 190 _, err := os.Stat(path) 191 return err == nil 192 } 193 194 // StripQuotes strips leading or trailing quotes. 195 func StripQuotes(v string) string { 196 v = strings.TrimSpace(v) 197 v = strings.TrimPrefix(v, "\"") 198 v = strings.TrimSuffix(v, "\"") 199 return v 200 }