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  }