2019-08-02 06:24:42 -04:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"regexp"
|
2019-12-13 04:21:00 -05:00
|
|
|
"runtime"
|
2019-08-02 06:24:42 -04:00
|
|
|
"strconv"
|
2019-12-13 04:21:00 -05:00
|
|
|
"sync"
|
2019-08-02 18:23:31 -04:00
|
|
|
|
2019-11-03 01:12:02 -05:00
|
|
|
"github.com/axllent/ghru"
|
2019-08-24 07:17:55 -04:00
|
|
|
"github.com/spf13/pflag"
|
2019-08-02 06:24:42 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2019-08-02 18:23:31 -04:00
|
|
|
quality int
|
|
|
|
maxWidth int
|
|
|
|
maxHeight int
|
|
|
|
outputDir string
|
|
|
|
preserveModTimes bool
|
|
|
|
jpegoptim string
|
|
|
|
jpegtran string
|
|
|
|
optipng string
|
|
|
|
pngquant string
|
|
|
|
gifsicle string
|
2023-11-09 22:39:41 -05:00
|
|
|
copyExif bool
|
2019-12-13 04:21:00 -05:00
|
|
|
threads = 1
|
2019-08-02 18:23:31 -04:00
|
|
|
version = "dev"
|
2019-08-02 06:24:42 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
func main() {
|
2019-08-24 07:17:55 -04:00
|
|
|
// set up new flag instance
|
|
|
|
flag := pflag.NewFlagSet(os.Args[0], pflag.ExitOnError)
|
|
|
|
|
2019-08-02 07:41:13 -04:00
|
|
|
// set the default help
|
2019-08-02 06:24:42 -04:00
|
|
|
flag.Usage = func() {
|
2019-08-02 08:46:32 -04:00
|
|
|
fmt.Println("Goptimize - downscales and optimizes images")
|
2019-08-02 06:24:42 -04:00
|
|
|
fmt.Printf("\nUsage: %s [options] <images>\n", os.Args[0])
|
|
|
|
fmt.Println("\nOptions:")
|
2019-08-24 07:17:55 -04:00
|
|
|
flag.SortFlags = false
|
2019-08-02 06:24:42 -04:00
|
|
|
flag.PrintDefaults()
|
|
|
|
fmt.Println("\nExamples:")
|
2019-08-02 08:37:51 -04:00
|
|
|
fmt.Printf(" %s image.png\n", os.Args[0])
|
2019-08-02 07:41:13 -04:00
|
|
|
fmt.Printf(" %s -m 800x800 *.jpg\n", os.Args[0])
|
|
|
|
fmt.Printf(" %s -o out/ -q 90 -m 1600x1600 *.jpg\n", os.Args[0])
|
2019-08-02 06:24:42 -04:00
|
|
|
|
2019-08-02 07:41:13 -04:00
|
|
|
fmt.Println("\nDetected optimizers:")
|
2022-04-22 06:48:04 -04:00
|
|
|
if err := displayDetectedOptimizer("jpegtran ", jpegtran); err != nil {
|
|
|
|
displayDetectedOptimizer("jpegoptim", jpegoptim)
|
2019-08-02 06:24:42 -04:00
|
|
|
}
|
2022-04-22 06:48:04 -04:00
|
|
|
displayDetectedOptimizer("optipng ", optipng)
|
|
|
|
displayDetectedOptimizer("pngquant ", pngquant)
|
|
|
|
displayDetectedOptimizer("gifsicle ", gifsicle)
|
2019-08-02 06:24:42 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
var maxSizes string
|
2019-12-13 04:21:00 -05:00
|
|
|
var multiThreaded, update, showversion, showhelp bool
|
2019-08-02 06:24:42 -04:00
|
|
|
|
2019-08-24 07:22:19 -04:00
|
|
|
flag.IntVarP(&quality, "quality", "q", 75, "quality, JPEG only")
|
2019-08-24 07:17:55 -04:00
|
|
|
flag.StringVarP(&maxSizes, "max", "m", "", "downscale to a maximum width & height in pixels (<width>x<height>)")
|
|
|
|
flag.StringVarP(&outputDir, "out", "o", "", "output directory (default overwrites original)")
|
|
|
|
flag.BoolVarP(&preserveModTimes, "preserve", "p", true, "preserve file modification times")
|
2023-11-09 22:39:41 -05:00
|
|
|
flag.BoolVarP(©Exif, "exif", "e", false, "copy exif data")
|
2019-08-24 07:17:55 -04:00
|
|
|
flag.BoolVarP(&update, "update", "u", false, "update to latest release")
|
2019-12-13 04:21:00 -05:00
|
|
|
flag.BoolVarP(&multiThreaded, "threaded", "t", false, "run multi-threaded (use all CPU cores)")
|
2019-08-24 07:17:55 -04:00
|
|
|
flag.BoolVarP(&showversion, "version", "v", false, "show version number")
|
|
|
|
flag.BoolVarP(&showhelp, "help", "h", false, "show help")
|
2019-08-02 06:24:42 -04:00
|
|
|
|
|
|
|
// third-party optimizers
|
2019-08-02 07:41:13 -04:00
|
|
|
flag.StringVar(&jpegtran, "jpegtran", "jpegtran", "jpegtran binary")
|
2019-08-24 07:22:19 -04:00
|
|
|
flag.StringVar(&jpegoptim, "jpegoptim", "jpegoptim", "jpegoptim binary")
|
|
|
|
flag.StringVar(&gifsicle, "gifsicle", "gifsicle", "gifsicle binary")
|
2019-08-02 07:41:13 -04:00
|
|
|
flag.StringVar(&pngquant, "pngquant", "pngquant", "pngquant binary")
|
2019-08-24 07:22:19 -04:00
|
|
|
flag.StringVar(&optipng, "optipng", "optipng", "optipng binary")
|
2019-08-02 06:24:42 -04:00
|
|
|
|
2019-08-24 07:17:55 -04:00
|
|
|
flag.SortFlags = false
|
|
|
|
|
|
|
|
// parse args excluding os.Args[0]
|
|
|
|
flag.Parse(os.Args[1:])
|
2019-08-02 06:24:42 -04:00
|
|
|
|
|
|
|
// detect optimizer paths
|
|
|
|
gifsicle, _ = exec.LookPath(gifsicle)
|
|
|
|
jpegoptim, _ = exec.LookPath(jpegoptim)
|
|
|
|
jpegtran, _ = exec.LookPath(jpegtran)
|
|
|
|
optipng, _ = exec.LookPath(optipng)
|
|
|
|
pngquant, _ = exec.LookPath(pngquant)
|
|
|
|
|
2019-08-24 07:17:55 -04:00
|
|
|
if showhelp {
|
|
|
|
flag.Usage()
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
2019-08-02 18:23:31 -04:00
|
|
|
if showversion {
|
|
|
|
fmt.Println(fmt.Sprintf("Version: %s", version))
|
2019-11-03 01:12:02 -05:00
|
|
|
latest, _, _, err := ghru.Latest("axllent/goptimize", "goptimize")
|
|
|
|
if err == nil && ghru.GreaterThan(latest, version) {
|
2019-08-02 18:23:31 -04:00
|
|
|
fmt.Printf("Update available: %s\nRun `%s -u` to update.\n", latest, os.Args[0])
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if update {
|
2019-11-03 01:12:02 -05:00
|
|
|
rel, err := ghru.Update("axllent/goptimize", "goptimize", version)
|
2019-08-02 18:23:31 -04:00
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2019-08-02 18:45:44 -04:00
|
|
|
fmt.Printf("Updated %s to version %s\n", os.Args[0], rel)
|
2019-08-02 18:23:31 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-08-02 06:24:42 -04:00
|
|
|
if len(flag.Args()) < 1 {
|
|
|
|
flag.Usage()
|
2019-08-02 18:23:31 -04:00
|
|
|
os.Exit(1)
|
2019-08-02 06:24:42 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if maxSizes != "" {
|
|
|
|
// calculate max sizes from arg[0]
|
|
|
|
r := regexp.MustCompile(`^(\d+)(x|X|\*|:)(\d+)$`)
|
|
|
|
matches := r.FindStringSubmatch(maxSizes)
|
|
|
|
|
|
|
|
if len(matches) != 4 {
|
|
|
|
flag.Usage()
|
2019-08-02 18:23:31 -04:00
|
|
|
os.Exit(1)
|
2019-08-02 06:24:42 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
maxWidth, _ = strconv.Atoi(matches[1])
|
|
|
|
maxHeight, _ = strconv.Atoi(matches[3])
|
|
|
|
}
|
|
|
|
|
|
|
|
// parse arguments
|
|
|
|
args := flag.Args()
|
|
|
|
|
|
|
|
if outputDir != "" {
|
|
|
|
// ensure the output directory exists
|
|
|
|
if _, err := os.Stat(outputDir); os.IsNotExist(err) {
|
|
|
|
err := os.MkdirAll(outputDir, os.ModePerm)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Cannot create output directory: %s\n", outputDir)
|
2019-08-02 18:23:31 -04:00
|
|
|
os.Exit(1)
|
2019-08-02 06:24:42 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-13 04:21:00 -05:00
|
|
|
if multiThreaded {
|
|
|
|
threads = runtime.NumCPU()
|
|
|
|
}
|
|
|
|
|
|
|
|
processChan := make(chan string)
|
|
|
|
|
|
|
|
wg := &sync.WaitGroup{} // Signal to main goroutine that worker goroutines are working/done working
|
|
|
|
wg.Add(threads)
|
|
|
|
|
|
|
|
for i := 0; i < threads; i++ {
|
|
|
|
go func() {
|
|
|
|
for nextFile := range processChan {
|
|
|
|
Goptimize(nextFile)
|
|
|
|
}
|
|
|
|
// Channel was closed, so we finished this goroutine.
|
|
|
|
wg.Done() // Let main goroutine know we are done.
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2019-08-02 06:24:42 -04:00
|
|
|
for _, img := range args {
|
2019-12-13 04:21:00 -05:00
|
|
|
processChan <- img
|
2019-08-02 06:24:42 -04:00
|
|
|
}
|
2019-12-13 04:21:00 -05:00
|
|
|
|
|
|
|
// Close the channel. This tells the worker goroutines that they can be done.
|
|
|
|
close(processChan)
|
|
|
|
|
|
|
|
// Wait for all worker goroutines to finish processing the IPs
|
|
|
|
wg.Wait()
|
2019-08-02 06:24:42 -04:00
|
|
|
}
|
|
|
|
|
2022-04-22 06:48:04 -04:00
|
|
|
// displayDetectedOptimizer prints whether the optimizer was found
|
|
|
|
func displayDetectedOptimizer(name, bin string) error {
|
2019-08-02 06:24:42 -04:00
|
|
|
exe, err := exec.LookPath(bin)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Printf(" - %s: %s\n", name, exe)
|
|
|
|
return nil
|
|
|
|
}
|