forked from kashifshah-mirrors/goptimize
Merge branch 'release/0.0.1'
This commit is contained in:
commit
5e04891863
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/dist/
|
||||||
|
goptimize
|
5
CHANGELOG.md
Normal file
5
CHANGELOG.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [0.0.1]
|
||||||
|
|
||||||
|
- Initial release
|
16
LICENSE
Normal file
16
LICENSE
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
Copyright 2019 Ralph Slooten
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||||
|
and associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||||
|
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||||
|
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or
|
||||||
|
substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||||
|
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
Makefile
Normal file
24
Makefile
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
TAG=`git describe --tags`
|
||||||
|
VERSION ?= `git describe --tags`
|
||||||
|
LDFLAGS=-ldflags "-s -w -X main.version=${VERSION}"
|
||||||
|
|
||||||
|
build = echo "\n\nBuilding $(1)-$(2)" && GOOS=$(1) GOARCH=$(2) go build ${LDFLAGS} -o dist/goptimize_${VERSION}_$(1)_$(2) \
|
||||||
|
&& bzip2 dist/goptimize_${VERSION}_$(1)_$(2)
|
||||||
|
|
||||||
|
goptimize: *.go
|
||||||
|
go get github.com/disintegration/imaging
|
||||||
|
go build ${LDFLAGS} -o goptimize
|
||||||
|
rm -rf /tmp/go-*
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f goptimize
|
||||||
|
|
||||||
|
release:
|
||||||
|
mkdir -p dist
|
||||||
|
rm -f dist/goptimize_${VERSION}_*
|
||||||
|
$(call build,linux,amd64)
|
||||||
|
$(call build,linux,386)
|
||||||
|
$(call build,linux,arm)
|
||||||
|
$(call build,linux,arm64)
|
||||||
|
$(call build,darwin,amd64)
|
||||||
|
$(call build,darwin,386)
|
56
README.md
Normal file
56
README.md
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
# Goptimizer - downscales and optimizes images
|
||||||
|
|
||||||
|
Goptimizer is a commandline utility written in Golang. It downscales and optimize existing images JPEG, PNG and Gif files.
|
||||||
|
|
||||||
|
Image downscaling is done within Goptimize (`-m <width>x<height>`, see [Usage](#usage-options)), however optimization is done using the following additional tools (if they are installed):
|
||||||
|
|
||||||
|
- jpegoptim
|
||||||
|
- jpegtran (`libjpeg-turbo-progs`)
|
||||||
|
- optipng
|
||||||
|
- pngquant
|
||||||
|
- gifsicle
|
||||||
|
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Both `jpegoptim` & `jpegtran` have almost identical optimization, so if both are installed then just `jpegtran` is used for JPG optimization. PNG optimization however will run through both `optipng` & `pngquant` (if installed) as this has definite advantages.
|
||||||
|
|
||||||
|
It is highly recommended to install the necessary optimization tools, however they are not required to run goptimize.
|
||||||
|
|
||||||
|
Goptimize will remove all exif data from JPEG files, auto-rotating those that relied on it.
|
||||||
|
|
||||||
|
It will also preserve (by default) the file's original modification times (`-p=false` to disable).
|
||||||
|
|
||||||
|
|
||||||
|
## Usage options
|
||||||
|
|
||||||
|
```
|
||||||
|
Usage: ./goptimize [options] <images>
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-gifsicle string
|
||||||
|
gifsicle binary (default "gifsicle")
|
||||||
|
-jpegoptim string
|
||||||
|
jpegoptim binary (default "jpegoptim")
|
||||||
|
-jpegtran string
|
||||||
|
jpegtran binary (default "jpegtran")
|
||||||
|
-m string
|
||||||
|
downscale to a maximum width & height in pixels (<width>x<height>)
|
||||||
|
-o string
|
||||||
|
output directory (default overwrites original)
|
||||||
|
-optipng string
|
||||||
|
optipng binary (default "optipng")
|
||||||
|
-p preserve file modification times (default true)
|
||||||
|
-pngquant string
|
||||||
|
pngquant binary (default "pngquant")
|
||||||
|
-q int
|
||||||
|
quality - JPEG only (default 75)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
- `./goptimize image.png` - optimize a PNG file
|
||||||
|
- `./goptimize -m 800x800 *` - optimize and downscale all image files to a maximum size of 800x800px
|
||||||
|
- `./goptimize -m 1200x0 image.jpg` - optimize and downscale a JPG file to a maximum size of width of 1200px
|
||||||
|
- `./goptimize -o out/ image.jpg` - optimize a JPG file and save it to `out/`
|
291
goptimize.go
Normal file
291
goptimize.go
Normal file
|
@ -0,0 +1,291 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image/gif"
|
||||||
|
"image/jpeg"
|
||||||
|
"image/png"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/disintegration/imaging"
|
||||||
|
"golang.org/x/image/bmp"
|
||||||
|
"golang.org/x/image/tiff"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Goptimize downscales and optimizes an existing image
|
||||||
|
func Goptimize(file string) {
|
||||||
|
info, err := os.Stat(file)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s doesn't exist\n", file)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.Mode().IsRegular() {
|
||||||
|
// not a file
|
||||||
|
fmt.Printf("%s is not a file\n", file)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// open original, rotate if neccesary
|
||||||
|
src, err := imaging.Open(file, imaging.AutoOrientation(true))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%v (%s)\n", err, file)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
format, err := imaging.FormatFromFilename(file)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Cannot detect format: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
outFilename := filepath.Base(file)
|
||||||
|
outDir := filepath.Dir(file)
|
||||||
|
dstFile := filepath.Join(outDir, outFilename)
|
||||||
|
|
||||||
|
if outputDir != "" {
|
||||||
|
dstFile = filepath.Join(outputDir, outFilename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get original image size
|
||||||
|
srcBounds := src.Bounds()
|
||||||
|
srcW := srcBounds.Dx()
|
||||||
|
srcH := srcBounds.Dy()
|
||||||
|
|
||||||
|
// Ensure scaling does not upscale image
|
||||||
|
imgMaxW := maxWidth
|
||||||
|
if imgMaxW == 0 || imgMaxW > srcW {
|
||||||
|
imgMaxW = srcW
|
||||||
|
}
|
||||||
|
|
||||||
|
imgMaxH := maxHeight
|
||||||
|
if imgMaxH == 0 || imgMaxH > srcH {
|
||||||
|
imgMaxH = srcH
|
||||||
|
}
|
||||||
|
|
||||||
|
resized := imaging.Fit(src, imgMaxW, imgMaxH, imaging.Lanczos)
|
||||||
|
dstBounds := resized.Bounds()
|
||||||
|
resultW := dstBounds.Dx()
|
||||||
|
resultH := dstBounds.Dy()
|
||||||
|
|
||||||
|
tmpFile, err := ioutil.TempFile(os.TempDir(), "Goptimized-")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Cannot create temporary file: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer os.Remove(tmpFile.Name())
|
||||||
|
|
||||||
|
if format.String() == "JPEG" {
|
||||||
|
err = jpeg.Encode(tmpFile, resized, &jpeg.Options{Quality: quality})
|
||||||
|
} else if format.String() == "PNG" {
|
||||||
|
err = png.Encode(tmpFile, resized)
|
||||||
|
} else if format.String() == "GIF" {
|
||||||
|
err = gif.Encode(tmpFile, resized, nil)
|
||||||
|
} else if format.String() == "TIFF" {
|
||||||
|
err = tiff.Encode(tmpFile, resized, nil)
|
||||||
|
} else if format.String() == "BMP" {
|
||||||
|
err = bmp.Encode(tmpFile, resized)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Unsupported file type %s\n", file)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error saving output file: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the temporary filename before closing
|
||||||
|
tmpFilename := tmpFile.Name()
|
||||||
|
|
||||||
|
// immediately close the temp file to release pointers
|
||||||
|
// so we can modify it with system processes
|
||||||
|
tmpFile.Close()
|
||||||
|
|
||||||
|
// Run through optimizers
|
||||||
|
if format.String() == "JPEG" {
|
||||||
|
// run one or the other, running both has no advantage
|
||||||
|
if jpegtran != "" {
|
||||||
|
RunOptimiser(tmpFilename, true, jpegtran, "-optimize", "-outfile")
|
||||||
|
} else if jpegoptim != "" {
|
||||||
|
RunOptimiser(tmpFilename, false, jpegoptim, "-f", "-s", "-o")
|
||||||
|
}
|
||||||
|
} else if format.String() == "PNG" {
|
||||||
|
if pngquant != "" {
|
||||||
|
RunOptimiser(tmpFilename, true, pngquant, "-f", "--output")
|
||||||
|
}
|
||||||
|
if optipng != "" {
|
||||||
|
RunOptimiser(tmpFilename, true, optipng, "-out")
|
||||||
|
}
|
||||||
|
} else if format.String() == "GIF" && gifsicle != "" {
|
||||||
|
RunOptimiser(tmpFilename, true, gifsicle, "-o")
|
||||||
|
}
|
||||||
|
|
||||||
|
// re-open modified temporary file
|
||||||
|
tmpFile, err = os.Open(tmpFilename)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error reopening temporary file: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer tmpFile.Close()
|
||||||
|
|
||||||
|
// get th eoriginal file stats
|
||||||
|
srcStat, _ := os.Stat(file)
|
||||||
|
srcSize := srcStat.Size()
|
||||||
|
// get the optimized file stats
|
||||||
|
dstStat, _ := tmpFile.Stat()
|
||||||
|
dstSize := dstStat.Size()
|
||||||
|
|
||||||
|
// get the original modification time for later
|
||||||
|
mtime := srcStat.ModTime()
|
||||||
|
atime := mtime // use mtime as we cannot get atime
|
||||||
|
|
||||||
|
// calculate saved percent
|
||||||
|
savedPercent := 100 - math.Round(float64(dstSize)/float64(srcSize)*100)
|
||||||
|
|
||||||
|
if savedPercent > 0 {
|
||||||
|
// (over)write the file - not all filesystems support
|
||||||
|
// cross-filesystem moving so we overwrite the original
|
||||||
|
out, err := os.Create(dstFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error opening original file: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
if _, err := io.Copy(out, tmpFile); err != nil {
|
||||||
|
fmt.Printf("Error ovewriting original file: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if preserveModTimes {
|
||||||
|
// transfer original modification times
|
||||||
|
if err := os.Chtimes(dstFile, atime, mtime); err != nil {
|
||||||
|
fmt.Printf("Error setting file timestamp: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Goptimized %s (%dx%d %s > %s %v%%)\n", dstFile, resultW, resultH, ByteCountSI(srcSize), ByteCountSI(dstSize), savedPercent)
|
||||||
|
} else {
|
||||||
|
// If the output directory is not the same,
|
||||||
|
// then write a copy of the original file
|
||||||
|
if outputDir != "" {
|
||||||
|
out, err := os.Create(dstFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error opening original file: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
orig, _ := os.Open(file)
|
||||||
|
|
||||||
|
defer orig.Close()
|
||||||
|
|
||||||
|
if _, err := io.Copy(out, orig); err != nil {
|
||||||
|
fmt.Printf("Error ovewriting original file: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if preserveModTimes {
|
||||||
|
// transfer original modification times
|
||||||
|
if err := os.Chtimes(dstFile, atime, mtime); err != nil {
|
||||||
|
fmt.Printf("Error setting file timestamp: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Copied %s (%dx%d %s %v%%)\n", dstFile, srcW, srcH, ByteCountSI(srcSize), 0)
|
||||||
|
} else {
|
||||||
|
// we didn't actually change anything
|
||||||
|
fmt.Printf("Skipped %s (%dx%d %s %v%%)\n", dstFile, srcW, srcH, ByteCountSI(srcSize), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunOptimiser will run the specified command on a copy of the temporary file,
|
||||||
|
// and overwrite it if the output is smaller than the original
|
||||||
|
func RunOptimiser(src string, outFileArg bool, args ...string) {
|
||||||
|
// create a new temp file
|
||||||
|
tmpFile, err := ioutil.TempFile(os.TempDir(), "Goptimized-")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Cannot create temporary file: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer os.Remove(tmpFile.Name())
|
||||||
|
|
||||||
|
source, err := os.Open(src)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Cannot open temporary file: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer source.Close()
|
||||||
|
|
||||||
|
if _, err := io.Copy(tmpFile, source); err != nil {
|
||||||
|
fmt.Printf("Cannot copy source file: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the filename to the args
|
||||||
|
args = append(args, tmpFile.Name())
|
||||||
|
if outFileArg {
|
||||||
|
// most commands require a second filename arg to overwrite the original
|
||||||
|
args = append(args, tmpFile.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute the command
|
||||||
|
cmd := exec.Command(args[0], args[1:]...)
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
fmt.Printf("%s: %v\n", args[0], err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpFilename := tmpFile.Name()
|
||||||
|
|
||||||
|
srcStat, _ := source.Stat()
|
||||||
|
srcSize := srcStat.Size()
|
||||||
|
dstStat, _ := os.Stat(tmpFilename)
|
||||||
|
dstSize := dstStat.Size()
|
||||||
|
|
||||||
|
// ensure file pointers are closed before renaming
|
||||||
|
tmpFile.Close()
|
||||||
|
source.Close()
|
||||||
|
|
||||||
|
if dstSize < srcSize {
|
||||||
|
if err := os.Rename(tmpFilename, src); err != nil {
|
||||||
|
fmt.Printf("Error renaming file: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByteCountSI returns a human readable size from int64 bytes
|
||||||
|
func ByteCountSI(b int64) string {
|
||||||
|
const unit = 1000
|
||||||
|
if b < unit {
|
||||||
|
return fmt.Sprintf("%dB", b)
|
||||||
|
}
|
||||||
|
div, exp := int64(unit), 0
|
||||||
|
for n := b / unit; n >= unit; n /= unit {
|
||||||
|
div *= unit
|
||||||
|
exp++
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%.1f%cB", float64(b)/float64(div), "kMGTPE"[exp])
|
||||||
|
}
|
142
main.go
Normal file
142
main.go
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/axllent/gitrel"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
quality int
|
||||||
|
maxWidth int
|
||||||
|
maxHeight int
|
||||||
|
outputDir string
|
||||||
|
preserveModTimes bool
|
||||||
|
jpegoptim string
|
||||||
|
jpegtran string
|
||||||
|
optipng string
|
||||||
|
pngquant string
|
||||||
|
gifsicle string
|
||||||
|
version = "dev"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// set the default help
|
||||||
|
flag.Usage = func() {
|
||||||
|
fmt.Println("Goptimize - downscales and optimizes images")
|
||||||
|
fmt.Printf("\nUsage: %s [options] <images>\n", os.Args[0])
|
||||||
|
fmt.Println("\nOptions:")
|
||||||
|
flag.PrintDefaults()
|
||||||
|
fmt.Println("\nExamples:")
|
||||||
|
fmt.Printf(" %s image.png\n", os.Args[0])
|
||||||
|
fmt.Printf(" %s -m 800x800 *.jpg\n", os.Args[0])
|
||||||
|
fmt.Printf(" %s -o out/ -q 90 -m 1600x1600 *.jpg\n", os.Args[0])
|
||||||
|
|
||||||
|
fmt.Println("\nDetected optimizers:")
|
||||||
|
if err := displayDelectedOptimizer("jpegtran ", jpegtran); err != nil {
|
||||||
|
displayDelectedOptimizer("jpegoptim", jpegoptim)
|
||||||
|
}
|
||||||
|
displayDelectedOptimizer("optipng ", optipng)
|
||||||
|
displayDelectedOptimizer("pngquant ", pngquant)
|
||||||
|
displayDelectedOptimizer("gifsicle ", gifsicle)
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxSizes string
|
||||||
|
var update, showversion bool
|
||||||
|
|
||||||
|
flag.IntVar(&quality, "q", 75, "quality - JPEG only")
|
||||||
|
flag.StringVar(&outputDir, "o", "", "output directory (default overwrites original)")
|
||||||
|
flag.BoolVar(&preserveModTimes, "p", true, "preserve file modification times")
|
||||||
|
flag.StringVar(&maxSizes, "m", "", "downscale to a maximum width & height in pixels (<width>x<height>)")
|
||||||
|
flag.BoolVar(&update, "u", false, "update to latest release")
|
||||||
|
flag.BoolVar(&showversion, "v", false, "show version number")
|
||||||
|
|
||||||
|
// third-party optimizers
|
||||||
|
flag.StringVar(&gifsicle, "gifsicle", "gifsicle", "gifsicle binary")
|
||||||
|
flag.StringVar(&jpegoptim, "jpegoptim", "jpegoptim", "jpegoptim binary")
|
||||||
|
flag.StringVar(&jpegtran, "jpegtran", "jpegtran", "jpegtran binary")
|
||||||
|
flag.StringVar(&optipng, "optipng", "optipng", "optipng binary")
|
||||||
|
flag.StringVar(&pngquant, "pngquant", "pngquant", "pngquant binary")
|
||||||
|
|
||||||
|
// parse flags
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// detect optimizer paths
|
||||||
|
gifsicle, _ = exec.LookPath(gifsicle)
|
||||||
|
jpegoptim, _ = exec.LookPath(jpegoptim)
|
||||||
|
jpegtran, _ = exec.LookPath(jpegtran)
|
||||||
|
optipng, _ = exec.LookPath(optipng)
|
||||||
|
pngquant, _ = exec.LookPath(pngquant)
|
||||||
|
|
||||||
|
if showversion {
|
||||||
|
fmt.Println(fmt.Sprintf("Version: %s", version))
|
||||||
|
latest, _, _, err := gitrel.Latest("axllent/goptimize", "goptimize")
|
||||||
|
if err == nil && latest != version {
|
||||||
|
fmt.Printf("Update available: %s\nRun `%s -u` to update.\n", latest, os.Args[0])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if update {
|
||||||
|
rel, err := gitrel.Update("axllent/goptimize", "goptimize", version)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Printf("Updated %s to version %s", os.Args[0], rel)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(flag.Args()) < 1 {
|
||||||
|
flag.Usage()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, img := range args {
|
||||||
|
Goptimize(img)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// displayDelectedOptimizer prints whether the optimizer was found
|
||||||
|
func displayDelectedOptimizer(name, bin string) error {
|
||||||
|
exe, err := exec.LookPath(bin)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf(" - %s: %s\n", name, exe)
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user