mirror of
https://github.com/axllent/goptimize.git
synced 2024-12-21 09:36:23 -05:00
Overwrite files instead of moving
This commit is contained in:
parent
c2a4f94de5
commit
f84a1252dc
133
goptimize.go
133
goptimize.go
@ -20,23 +20,28 @@ import (
|
||||
// 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
|
||||
@ -45,6 +50,7 @@ func Goptimize(file string) {
|
||||
outFilename := filepath.Base(file)
|
||||
outDir := filepath.Dir(file)
|
||||
dstFile := filepath.Join(outDir, outFilename)
|
||||
|
||||
if outputDir != "" {
|
||||
dstFile = filepath.Join(outputDir, outFilename)
|
||||
}
|
||||
@ -59,22 +65,24 @@ func Goptimize(file string) {
|
||||
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" {
|
||||
@ -88,29 +96,30 @@ func Goptimize(file string) {
|
||||
} else if format.String() == "BMP" {
|
||||
err = bmp.Encode(tmpFile, resized)
|
||||
} else {
|
||||
fmt.Printf("Cannot Goptimize %s files\n", format.String())
|
||||
fmt.Printf("Unsupported file type %s\n", file)
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Error saving output file: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// get the tempoary filename before closing
|
||||
// get the temporary filename before closing
|
||||
tmpFilename := tmpFile.Name()
|
||||
// close the temp file to release pointers so we can
|
||||
// modify it with system processes
|
||||
|
||||
// immediately close the temp file to release pointers
|
||||
// so we can modify it with system processes
|
||||
tmpFile.Close()
|
||||
|
||||
// optimize
|
||||
// Run through optimizers
|
||||
if format.String() == "JPEG" {
|
||||
// run one or the other, both don't do anything
|
||||
// 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")
|
||||
@ -118,13 +127,11 @@ func Goptimize(file string) {
|
||||
if optipng != "" {
|
||||
RunOptimiser(tmpFilename, true, optipng, "-out")
|
||||
}
|
||||
} else if format.String() == "GIF" {
|
||||
if gifsicle != "" {
|
||||
} else if format.String() == "GIF" && gifsicle != "" {
|
||||
RunOptimiser(tmpFilename, true, gifsicle, "-o")
|
||||
}
|
||||
}
|
||||
|
||||
// re-open potentiall modified temporary file
|
||||
// re-open modified temporary file
|
||||
tmpFile, err = os.Open(tmpFilename)
|
||||
if err != nil {
|
||||
fmt.Printf("Error reopening temporary file: %v\n", err)
|
||||
@ -133,69 +140,101 @@ func Goptimize(file string) {
|
||||
|
||||
defer tmpFile.Close()
|
||||
|
||||
// original file stats
|
||||
// get th eoriginal file stats
|
||||
srcStat, _ := os.Stat(file)
|
||||
srcSize := srcStat.Size()
|
||||
// optimized file stats
|
||||
// get the optimized file stats
|
||||
dstStat, _ := tmpFile.Stat()
|
||||
dstSize := dstStat.Size()
|
||||
|
||||
// transfer the original file permissions to the new file
|
||||
if err = os.Chmod(tmpFile.Name(), srcStat.Mode()); err != nil {
|
||||
fmt.Printf("Error setting file permissions: %v\n", err)
|
||||
// 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 dstSize < srcSize {
|
||||
// (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 !skipPreserveModTimes {
|
||||
// transfer original modification times
|
||||
mtime := srcStat.ModTime()
|
||||
atime := mtime // use mtime as we cannot get atime
|
||||
if err := os.Chtimes(tmpFile.Name(), atime, mtime); err != nil {
|
||||
if err := os.Chtimes(dstFile, atime, mtime); err != nil {
|
||||
fmt.Printf("Error setting file timestamp: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
savedPercent := 100 - math.Round(float64(dstSize)/float64(srcSize)*100)
|
||||
|
||||
if dstSize < srcSize {
|
||||
// output is smaller
|
||||
if err := os.Rename(tmpFile.Name(), dstFile); err != nil {
|
||||
fmt.Printf("Error renaming file: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Goptimized %s (%dx%d %s/%s %v%%)\n", dstFile, resultW, resultH, ByteCountSI(dstSize), ByteCountSI(srcSize), savedPercent)
|
||||
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 != "" {
|
||||
// just copy the original
|
||||
if err := os.Rename(file, dstFile); err != nil {
|
||||
fmt.Printf("Error renaming file: %v\n", err)
|
||||
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 !skipPreserveModTimes {
|
||||
// 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->%s %v%%)\n", dstFile, srcW, srcH, ByteCountSI(srcSize), ByteCountSI(srcSize), 0)
|
||||
} else {
|
||||
// we didn't actually change anything
|
||||
fmt.Printf("Skipped %s (%dx%d %s->%s %v%%)\n", dstFile, srcW, srcH, ByteCountSI(srcSize), ByteCountSI(srcSize), 0)
|
||||
}
|
||||
// we didn't actually any scaling optimizing
|
||||
fmt.Printf("Goptimized %s (%dx%d %s/%s %v%%)\n", dstFile, srcW, srcH, ByteCountSI(srcSize), ByteCountSI(srcSize), 0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// RunOptimiser will run the specified command on a copy of the original file
|
||||
// and overwrite if the output is smaller than the original
|
||||
func RunOptimiser(src string, outfile bool, args ...string) {
|
||||
// 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)
|
||||
// s, _ := source.Stat()
|
||||
// log.Printf("%v\n", s.Size())
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Cannot open temporary file: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
defer source.Close()
|
||||
|
||||
if _, err := io.Copy(tmpFile, source); err != nil {
|
||||
@ -205,23 +244,18 @@ func RunOptimiser(src string, outfile bool, args ...string) {
|
||||
|
||||
// add the filename to the args
|
||||
args = append(args, tmpFile.Name())
|
||||
if outfile {
|
||||
// most commands require a second filename to overwrite the original
|
||||
if outFileArg {
|
||||
// most commands require a second filename arg to overwrite the original
|
||||
args = append(args, tmpFile.Name())
|
||||
}
|
||||
|
||||
// fmt.Println(args)
|
||||
|
||||
// execute the command
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
err = cmd.Run()
|
||||
// out, err := cmd.Output()
|
||||
if err != nil {
|
||||
// there was an error
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Printf("%s: %v\n", args[0], err)
|
||||
return
|
||||
}
|
||||
// fmt.Println(string(out))
|
||||
|
||||
tmpFilename := tmpFile.Name()
|
||||
|
||||
@ -239,9 +273,6 @@ func RunOptimiser(src string, outfile bool, args ...string) {
|
||||
fmt.Printf("Error renaming file: %v\n", err)
|
||||
return
|
||||
}
|
||||
// fmt.Println(args[0], "=", srcSize, "to", dstSize, "(wrote to", source.Name(), ")")
|
||||
} else {
|
||||
// fmt.Println(args[0], "!=", srcSize, "to", dstSize)
|
||||
}
|
||||
}
|
||||
|
||||
|
22
main.go
22
main.go
@ -23,17 +23,17 @@ var (
|
||||
)
|
||||
|
||||
func main() {
|
||||
// modify the default help
|
||||
// set the default help
|
||||
flag.Usage = func() {
|
||||
fmt.Println("Re-save & resample images, with optional optimization.")
|
||||
fmt.Println("Goptimize - Resample optimized images")
|
||||
fmt.Printf("\nUsage: %s [options] <images>\n", os.Args[0])
|
||||
fmt.Println("\nOptions:")
|
||||
flag.PrintDefaults()
|
||||
fmt.Println("\nExamples:")
|
||||
fmt.Printf(" %s -s 800x800 *.jpg\n", os.Args[0])
|
||||
fmt.Printf(" %s -o out/ -q 90 -s 1600x1600 *.jpg\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("\nOtimizers:")
|
||||
fmt.Println("\nDetected optimizers:")
|
||||
if err := displayDelectedOptimizer("jpegtran ", jpegtran); err != nil {
|
||||
displayDelectedOptimizer("jpegoptim", jpegoptim)
|
||||
}
|
||||
@ -44,17 +44,17 @@ func main() {
|
||||
|
||||
var maxSizes string
|
||||
|
||||
flag.IntVar(&quality, "q", 75, "Quality - affects jpeg only")
|
||||
flag.IntVar(&quality, "q", 75, "Quality - JPEG only")
|
||||
flag.StringVar(&outputDir, "o", "", "Output directory (default overwrites original)")
|
||||
flag.BoolVar(&skipPreserveModTimes, "n", false, "Do not preserve file modification times")
|
||||
flag.StringVar(&maxSizes, "m", "", "Scale down to a maximum width & height. Format must be <width>x<height>.")
|
||||
|
||||
// third-party optimizers
|
||||
flag.StringVar(&gifsicle, "gifsicle", "gifsicle", "Alternative gifsicle name")
|
||||
flag.StringVar(&jpegoptim, "jpegoptim", "jpegoptim", "Alternative jpegoptim name")
|
||||
flag.StringVar(&jpegtran, "jpegtran", "jpegtran", "Alternative jpegtran name")
|
||||
flag.StringVar(&optipng, "optipng", "optipng", "Alternative optipng name")
|
||||
flag.StringVar(&pngquant, "pngquant", "pngquant", "Alternative pngquant name")
|
||||
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()
|
||||
|
Loading…
Reference in New Issue
Block a user