mirror of
				https://github.com/axllent/goptimize.git
				synced 2025-11-04 03:48:28 -05:00 
			
		
		
		
	Compare commits
	
		
			30 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					fbb22582d1 | ||
| 
						 | 
					adac20101d | ||
| 
						 | 
					521f3f24c1 | ||
| 
						 | 
					3f9f09880c | ||
| 
						 | 
					eadd535e4c | ||
| 
						 | 
					f9bf16d6c4 | ||
| 
						 | 
					6c1aabaa70 | ||
| 
						 | 
					8c3e478384 | ||
| 
						 | 
					7ffb944b0f | ||
| 
						 | 
					cb4bde8c4e | ||
| 
						 | 
					49d7b9b12f | ||
| 
						 | 
					fcdbf3e90b | ||
| 
						 | 
					5096e6fbf0 | ||
| 
						 | 
					d7d827fed7 | ||
| 
						 | 
					19cf54b633 | ||
| 
						 | 
					57b1d33129 | ||
| 
						 | 
					c239a54ead | ||
| 
						 | 
					e6296f4719 | ||
| 
						 | 
					ee6aa69087 | ||
| 
						 | 
					8362617920 | ||
| 
						 | 
					b157fcd72b | ||
| 
						 | 
					0c000fe262 | ||
| 
						 | 
					97fa6c94c5 | ||
| 
						 | 
					36e9b4c8fa | ||
| 
						 | 
					5df486c53e | ||
| 
						 | 
					120e5fefde | ||
| 
						 | 
					d2a591a6d9 | ||
| 
						 | 
					8e5e7b6098 | ||
| 
						 | 
					47024e030b | ||
| 
						 | 
					45915d4b42 | 
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,2 +1,2 @@
 | 
			
		||||
/dist/
 | 
			
		||||
goptimize
 | 
			
		||||
goptimize*
 | 
			
		||||
							
								
								
									
										21
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -1,5 +1,26 @@
 | 
			
		||||
# Changelog
 | 
			
		||||
 | 
			
		||||
## [0.2.0]
 | 
			
		||||
 | 
			
		||||
- Add threaded option (`-t`) to use all CPU cores
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## [0.1.0]
 | 
			
		||||
 | 
			
		||||
- Switch to go mods - go (>= 1.11 required)
 | 
			
		||||
- Switch to axllent/semver for app updating
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## [0.0.3]
 | 
			
		||||
 | 
			
		||||
- Detect & skip animated GIFs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## [0.0.2]
 | 
			
		||||
 | 
			
		||||
- Switch to [pflag](https://github.com/spf13/pflag) for better flag management
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## [0.0.1]
 | 
			
		||||
 | 
			
		||||
- Initial release
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								Makefile
									
									
									
									
									
								
							@@ -5,8 +5,7 @@ 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
 | 
			
		||||
goptimize: *.go go.*
 | 
			
		||||
	go build ${LDFLAGS} -o goptimize
 | 
			
		||||
	rm -rf /tmp/go-*
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										66
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										66
									
								
								README.md
									
									
									
									
									
								
							@@ -1,11 +1,12 @@
 | 
			
		||||
# Goptimizer - downscales and optimizes images
 | 
			
		||||
 | 
			
		||||
Goptimizer is a commandline utility written in Golang. It downscales and optimize existing images JPEG, PNG and Gif files.
 | 
			
		||||
[](https://goreportcard.com/report/github.com/axllent/goptimize)
 | 
			
		||||
 | 
			
		||||
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):
 | 
			
		||||
Goptimizer is a commandline utility written in Golang. It downscales and optimizes JPEG, PNG, GIF, TIFF and BMP files.
 | 
			
		||||
 | 
			
		||||
- jpegoptim
 | 
			
		||||
- jpegtran (`libjpeg-turbo-progs`)
 | 
			
		||||
Image downscaling/rotation 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):
 | 
			
		||||
 | 
			
		||||
- jpegtran (`libjpeg-turbo-progs`) or jpegoptim
 | 
			
		||||
- optipng
 | 
			
		||||
- pngquant
 | 
			
		||||
- gifsicle
 | 
			
		||||
@@ -13,14 +14,16 @@ Image downscaling is done within Goptimize (`-m <width>x<height>`, see [Usage](#
 | 
			
		||||
 | 
			
		||||
## 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.
 | 
			
		||||
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 can result in better optimization.
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
Goptimize will remove all exif data from JPEG files, auto-rotating those that depend on it for orientation.
 | 
			
		||||
 | 
			
		||||
It will also preserve (by default) the file's original modification times (`-p=false` to disable).
 | 
			
		||||
 | 
			
		||||
Animated GIF files are not supported and automatically get skipped.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Usage options
 | 
			
		||||
 | 
			
		||||
@@ -28,23 +31,19 @@ It will also preserve (by default) the file's original modification times (`-p=f
 | 
			
		||||
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)
 | 
			
		||||
  -q, --quality int        quality, JPEG only (default 75)
 | 
			
		||||
  -m, --max string         downscale to a maximum width & height in pixels (<width>x<height>)
 | 
			
		||||
  -o, --out string         output directory (default overwrites original)
 | 
			
		||||
  -p, --preserve           preserve file modification times (default true)
 | 
			
		||||
  -t, --threaded           run multi-threaded (use all CPU cores)
 | 
			
		||||
  -u, --update             update to latest release
 | 
			
		||||
  -v, --version            show version number
 | 
			
		||||
  -h, --help               show help
 | 
			
		||||
      --jpegtran string    jpegtran binary (default "jpegtran")
 | 
			
		||||
      --jpegoptim string   jpegoptim binary (default "jpegoptim")
 | 
			
		||||
      --gifsicle string    gifsicle binary (default "gifsicle")
 | 
			
		||||
      --pngquant string    pngquant binary (default "pngquant")
 | 
			
		||||
      --optipng string     optipng binary (default "optipng")
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -54,3 +53,24 @@ Options:
 | 
			
		||||
- `./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/`
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Install
 | 
			
		||||
 | 
			
		||||
Download the appropriate binary from the [releases](https://github.com/axllent/goptimize/releases/latest), or if you have golang installed 
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Build requirements
 | 
			
		||||
 | 
			
		||||
Go >= 1.11 required.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
go get github.com/axllent/goptimize
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## TODO
 | 
			
		||||
 | 
			
		||||
Some ideas for the future:
 | 
			
		||||
 | 
			
		||||
- Dry run
 | 
			
		||||
- Option to copy exif data (how?)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
module github.com/axllent/goptimize
 | 
			
		||||
 | 
			
		||||
go 1.13
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/axllent/ghru v1.1.3
 | 
			
		||||
	github.com/disintegration/imaging v1.6.1
 | 
			
		||||
	github.com/spf13/pflag v1.0.5
 | 
			
		||||
	golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										12
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
github.com/axllent/ghru v1.1.3 h1:n0jYsuqCYaHHAR6DraXZl8hpBY4j0XV47y5Lyym/jGo=
 | 
			
		||||
github.com/axllent/ghru v1.1.3/go.mod h1:rFvMhcO1UAv2Cv6bXscS8EOc7qqNpfe8ZLp23utzs88=
 | 
			
		||||
github.com/axllent/semver v0.0.0-20191103011746-394cefa91ee9 h1:LHNcCfePzgC/agAJs5a/5K3hFo8uW04bEdDKDkoX4do=
 | 
			
		||||
github.com/axllent/semver v0.0.0-20191103011746-394cefa91ee9/go.mod h1:2xSPzvG8n9mRfdtxSvWvfTfQGWfHsMsHO1iZnKATMSc=
 | 
			
		||||
github.com/disintegration/imaging v1.6.1 h1:JnBbK6ECIZb1NsWIikP9pd8gIlTIRx7fuDNpU9fsxOE=
 | 
			
		||||
github.com/disintegration/imaging v1.6.1/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ=
 | 
			
		||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 | 
			
		||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 | 
			
		||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
 | 
			
		||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
 | 
			
		||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 | 
			
		||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
			
		||||
							
								
								
									
										76
									
								
								goptimize.go
									
									
									
									
									
								
							
							
						
						
									
										76
									
								
								goptimize.go
									
									
									
									
									
								
							@@ -22,31 +22,38 @@ func Goptimize(file string) {
 | 
			
		||||
	info, err := os.Stat(file)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Printf("%s doesn't exist\n", file)
 | 
			
		||||
		fmt.Printf("Error: %s doesn't exist\n", file)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !info.Mode().IsRegular() {
 | 
			
		||||
		// not a file
 | 
			
		||||
		fmt.Printf("%s is not a file\n", file)
 | 
			
		||||
		fmt.Printf("Error: %s is not a file\n", file)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// open original, rotate if neccesary
 | 
			
		||||
	// open original, rotate if necessary
 | 
			
		||||
	src, err := imaging.Open(file, imaging.AutoOrientation(true))
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Printf("%v (%s)\n", err, file)
 | 
			
		||||
		fmt.Printf("Error: %v (%s)\n", err, file)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	format, err := imaging.FormatFromFilename(file)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Printf("Cannot detect format: %v\n", err)
 | 
			
		||||
		fmt.Printf("Error: cannot detect format: %v\n", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if format.String() == "GIF" {
 | 
			
		||||
		// return if GIF is animated - unsupported
 | 
			
		||||
		if err := IsGIFAnimated(file); err != nil {
 | 
			
		||||
			fmt.Printf("Error: animated GIF not supported (%v)\n", file)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	outFilename := filepath.Base(file)
 | 
			
		||||
	outDir := filepath.Dir(file)
 | 
			
		||||
	dstFile := filepath.Join(outDir, outFilename)
 | 
			
		||||
@@ -60,7 +67,7 @@ func Goptimize(file string) {
 | 
			
		||||
	srcW := srcBounds.Dx()
 | 
			
		||||
	srcH := srcBounds.Dy()
 | 
			
		||||
 | 
			
		||||
	// Ensure scaling does not upscale image
 | 
			
		||||
	// do not upscale image
 | 
			
		||||
	imgMaxW := maxWidth
 | 
			
		||||
	if imgMaxW == 0 || imgMaxW > srcW {
 | 
			
		||||
		imgMaxW = srcW
 | 
			
		||||
@@ -79,24 +86,25 @@ func Goptimize(file string) {
 | 
			
		||||
	tmpFile, err := ioutil.TempFile(os.TempDir(), "Goptimized-")
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Printf("Cannot create temporary file: %v\n", err)
 | 
			
		||||
		fmt.Printf("Error: cannot create temporary file: %v\n", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer os.Remove(tmpFile.Name())
 | 
			
		||||
 | 
			
		||||
	if format.String() == "JPEG" {
 | 
			
		||||
	switch imgType := format.String(); imgType {
 | 
			
		||||
	case "JPEG":
 | 
			
		||||
		err = jpeg.Encode(tmpFile, resized, &jpeg.Options{Quality: quality})
 | 
			
		||||
	} else if format.String() == "PNG" {
 | 
			
		||||
	case "PNG":
 | 
			
		||||
		err = png.Encode(tmpFile, resized)
 | 
			
		||||
	} else if format.String() == "GIF" {
 | 
			
		||||
	case "GIF":
 | 
			
		||||
		err = gif.Encode(tmpFile, resized, nil)
 | 
			
		||||
	} else if format.String() == "TIFF" {
 | 
			
		||||
	case "TIFF":
 | 
			
		||||
		err = tiff.Encode(tmpFile, resized, nil)
 | 
			
		||||
	} else if format.String() == "BMP" {
 | 
			
		||||
	case "BMP":
 | 
			
		||||
		err = bmp.Encode(tmpFile, resized)
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Printf("Unsupported file type %s\n", file)
 | 
			
		||||
	default:
 | 
			
		||||
		fmt.Printf("Error: unsupported file type (%s)\n", file)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -112,23 +120,23 @@ func Goptimize(file string) {
 | 
			
		||||
	// so we can modify it with system processes
 | 
			
		||||
	tmpFile.Close()
 | 
			
		||||
 | 
			
		||||
	// Run through optimizers
 | 
			
		||||
	// 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")
 | 
			
		||||
			RunOptimizer(tmpFilename, true, jpegtran, "-optimize", "-outfile")
 | 
			
		||||
		} else if jpegoptim != "" {
 | 
			
		||||
			RunOptimiser(tmpFilename, false, jpegoptim, "-f", "-s", "-o")
 | 
			
		||||
			RunOptimizer(tmpFilename, false, jpegoptim, "-f", "-s", "-o")
 | 
			
		||||
		}
 | 
			
		||||
	} else if format.String() == "PNG" {
 | 
			
		||||
		if pngquant != "" {
 | 
			
		||||
			RunOptimiser(tmpFilename, true, pngquant, "-f", "--output")
 | 
			
		||||
			RunOptimizer(tmpFilename, true, pngquant, "-f", "--output")
 | 
			
		||||
		}
 | 
			
		||||
		if optipng != "" {
 | 
			
		||||
			RunOptimiser(tmpFilename, true, optipng, "-out")
 | 
			
		||||
			RunOptimizer(tmpFilename, true, optipng, "-out")
 | 
			
		||||
		}
 | 
			
		||||
	} else if format.String() == "GIF" && gifsicle != "" {
 | 
			
		||||
		RunOptimiser(tmpFilename, true, gifsicle, "-o")
 | 
			
		||||
		RunOptimizer(tmpFilename, true, gifsicle, "-o")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// re-open modified temporary file
 | 
			
		||||
@@ -166,7 +174,7 @@ func Goptimize(file string) {
 | 
			
		||||
		defer out.Close()
 | 
			
		||||
 | 
			
		||||
		if _, err := io.Copy(out, tmpFile); err != nil {
 | 
			
		||||
			fmt.Printf("Error ovewriting original file: %v\n", err)
 | 
			
		||||
			fmt.Printf("Error overwriting original file: %v\n", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -179,7 +187,7 @@ func Goptimize(file string) {
 | 
			
		||||
 | 
			
		||||
		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,
 | 
			
		||||
		// if the output directory is not the same,
 | 
			
		||||
		// then write a copy of the original file
 | 
			
		||||
		if outputDir != "" {
 | 
			
		||||
			out, err := os.Create(dstFile)
 | 
			
		||||
@@ -215,9 +223,9 @@ func Goptimize(file string) {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RunOptimiser will run the specified command on a copy of the temporary file,
 | 
			
		||||
// RunOptimizer 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) {
 | 
			
		||||
func RunOptimizer(src string, outFileArg bool, args ...string) {
 | 
			
		||||
	// create a new temp file
 | 
			
		||||
	tmpFile, err := ioutil.TempFile(os.TempDir(), "Goptimized-")
 | 
			
		||||
 | 
			
		||||
@@ -289,3 +297,21 @@ func ByteCountSI(b int64) string {
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%.1f%cB", float64(b)/float64(div), "kMGTPE"[exp])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsGIFAnimated will return an error if the GIF file has more than 1 frame
 | 
			
		||||
func IsGIFAnimated(gifFile string) error {
 | 
			
		||||
	file, _ := os.Open(gifFile)
 | 
			
		||||
	defer file.Close()
 | 
			
		||||
 | 
			
		||||
	g, err := gif.DecodeAll(file)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Single frame = OK
 | 
			
		||||
	if len(g.Image) == 1 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fmt.Errorf("Animated gif")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										81
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										81
									
								
								main.go
									
									
									
									
									
								
							@@ -1,14 +1,16 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"github.com/axllent/gitrel"
 | 
			
		||||
	"github.com/axllent/ghru"
 | 
			
		||||
	"github.com/spf13/pflag"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
@@ -22,15 +24,20 @@ var (
 | 
			
		||||
	optipng          string
 | 
			
		||||
	pngquant         string
 | 
			
		||||
	gifsicle         string
 | 
			
		||||
	threads          = 1
 | 
			
		||||
	version          = "dev"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	// set up new flag instance
 | 
			
		||||
	flag := pflag.NewFlagSet(os.Args[0], pflag.ExitOnError)
 | 
			
		||||
 | 
			
		||||
	// 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.SortFlags = false
 | 
			
		||||
		flag.PrintDefaults()
 | 
			
		||||
		fmt.Println("\nExamples:")
 | 
			
		||||
		fmt.Printf("  %s image.png\n", os.Args[0])
 | 
			
		||||
@@ -47,24 +54,28 @@ func main() {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var maxSizes string
 | 
			
		||||
	var update, showversion bool
 | 
			
		||||
	var multiThreaded, update, showversion, showhelp 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")
 | 
			
		||||
	flag.IntVarP(&quality, "quality", "q", 75, "quality, JPEG only")
 | 
			
		||||
	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")
 | 
			
		||||
	flag.BoolVarP(&update, "update", "u", false, "update to latest release")
 | 
			
		||||
	flag.BoolVarP(&multiThreaded, "threaded", "t", false, "run multi-threaded (use all CPU cores)")
 | 
			
		||||
	flag.BoolVarP(&showversion, "version", "v", false, "show version number")
 | 
			
		||||
	flag.BoolVarP(&showhelp, "help", "h", false, "show help")
 | 
			
		||||
 | 
			
		||||
	// 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(&jpegoptim, "jpegoptim", "jpegoptim", "jpegoptim binary")
 | 
			
		||||
	flag.StringVar(&gifsicle, "gifsicle", "gifsicle", "gifsicle binary")
 | 
			
		||||
	flag.StringVar(&pngquant, "pngquant", "pngquant", "pngquant binary")
 | 
			
		||||
	flag.StringVar(&optipng, "optipng", "optipng", "optipng binary")
 | 
			
		||||
 | 
			
		||||
	// parse flags
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
	flag.SortFlags = false
 | 
			
		||||
 | 
			
		||||
	// parse args excluding os.Args[0]
 | 
			
		||||
	flag.Parse(os.Args[1:])
 | 
			
		||||
 | 
			
		||||
	// detect optimizer paths
 | 
			
		||||
	gifsicle, _ = exec.LookPath(gifsicle)
 | 
			
		||||
@@ -73,22 +84,27 @@ func main() {
 | 
			
		||||
	optipng, _ = exec.LookPath(optipng)
 | 
			
		||||
	pngquant, _ = exec.LookPath(pngquant)
 | 
			
		||||
 | 
			
		||||
	if showhelp {
 | 
			
		||||
		flag.Usage()
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if showversion {
 | 
			
		||||
		fmt.Println(fmt.Sprintf("Version: %s", version))
 | 
			
		||||
		latest, _, _, err := gitrel.Latest("axllent/goptimize", "goptimize")
 | 
			
		||||
		if err == nil && latest != version {
 | 
			
		||||
		latest, _, _, err := ghru.Latest("axllent/goptimize", "goptimize")
 | 
			
		||||
		if err == nil && ghru.GreaterThan(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)
 | 
			
		||||
		rel, err := ghru.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)
 | 
			
		||||
		fmt.Printf("Updated %s to version %s\n", os.Args[0], rel)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -125,9 +141,34 @@ func main() {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, img := range args {
 | 
			
		||||
		Goptimize(img)
 | 
			
		||||
	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.
 | 
			
		||||
		}()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, img := range args {
 | 
			
		||||
		processChan <- img
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 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()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// displayDelectedOptimizer prints whether the optimizer was found
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user