commit b168c688cd425440f8b0ad60bab15241ceaa5b36 Author: PhatPhuckDave Date: Thu Aug 29 11:32:55 2024 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..de10fe8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +image-transcoder.exe +main.log diff --git a/codec/coder.go b/codec/coder.go new file mode 100644 index 0000000..114402a --- /dev/null +++ b/codec/coder.go @@ -0,0 +1,12 @@ +package codec + +import ( + "bytes" + "image" + "io" +) + +type Coder interface { + Decode(io.Reader) (image.Image, error) + Encode(image.Image, int) (bytes.Buffer, error) +} diff --git a/codec/jpeg.go b/codec/jpeg.go new file mode 100644 index 0000000..814e512 --- /dev/null +++ b/codec/jpeg.go @@ -0,0 +1,23 @@ +package codec + +import ( + "bytes" + "image" + "image/jpeg" + "io" +) + +type JPGCoder struct{} + +func (coder JPGCoder) Decode(r io.Reader) (image.Image, error) { + i, err := jpeg.Decode(r) + if err != nil { + return nil, err + } + return i, nil +} + +func (coder JPGCoder) Encode(i image.Image, quality int) (buf bytes.Buffer, err error) { + err = jpeg.Encode(&buf, i, &jpeg.Options{Quality: quality}) + return buf, err +} diff --git a/codec/png.go b/codec/png.go new file mode 100644 index 0000000..522a605 --- /dev/null +++ b/codec/png.go @@ -0,0 +1,34 @@ +package codec + +import ( + "bytes" + "github.com/foobaz/lossypng/lossypng" + "image" + "image/png" + "io" +) + +type PNGCoder struct{} + +const qMax = 20 + +func (coder PNGCoder) Decode(r io.Reader) (image.Image, error) { + i, err := png.Decode(r) + if err != nil { + return nil, err + } + return i, nil +} + +func (coder PNGCoder) Encode(i image.Image, quality int) (buf bytes.Buffer, err error) { + c := lossypng.Compress(i, 2, qualityFactor(quality)) + err = png.Encode(&buf, c) + return buf, err +} + +// qualityFactor normalizes the PNG quality factor from a max of 20, where 0 is +// no conversion. +func qualityFactor(q int) int { + f := q / 100 + return qMax - (f * qMax) +} diff --git a/codec/webp.go b/codec/webp.go new file mode 100644 index 0000000..a978419 --- /dev/null +++ b/codec/webp.go @@ -0,0 +1,26 @@ +package codec + +import ( + "bytes" + "image" + "io" + + "github.com/chai2010/webp" +) + +type WebpCoder struct{} + +func (coder WebpCoder) Decode(r io.Reader) (image.Image, error) { + i, err := webp.Decode(r) + if err != nil { + return nil, err + } + return i, nil +} + +func (coder WebpCoder) Encode(i image.Image, quality int) (buf bytes.Buffer, err error) { + if err = webp.Encode(&buf, i, &webp.Options{Lossless: true, Quality: float32(quality)}); err != nil { + return buf, err + } + return buf, nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a2247ee --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module image-transcoder + +go 1.23.0 + +require ( + github.com/chai2010/webp v1.1.1 + github.com/foobaz/lossypng v0.0.0-20200814224715-48fa8819852a +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..cae58d6 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk= +github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU= +github.com/foobaz/lossypng v0.0.0-20200814224715-48fa8819852a h1:0TYY/syyvt/+y5PWAkybgG2o6zHY+UrI3fuixaSeRoI= +github.com/foobaz/lossypng v0.0.0-20200814224715-48fa8819852a/go.mod h1:wRxTcIExb9GZAgOr1wrQuOZBkyoZNQi7znUmeyKTciA= diff --git a/main.go b/main.go new file mode 100644 index 0000000..5fc3e7c --- /dev/null +++ b/main.go @@ -0,0 +1,116 @@ +package main + +import ( + "flag" + "fmt" + "image-transcoder/codec" + "io" + "log" + "os" + "path/filepath" + "strings" + "sync" +) + +var codecs = map[string]codec.Coder{ + ".jpg": codec.JPGCoder{}, + ".jpeg": codec.JPGCoder{}, + ".png": codec.PNGCoder{}, + ".webp": codec.WebpCoder{}, +} + +var Error *log.Logger +var Warning *log.Logger + +func init() { + log.SetFlags(log.Lmicroseconds | log.Lshortfile) + logger := io.MultiWriter(os.Stdout) + log.SetOutput(logger) + + Error = log.New(io.MultiWriter(os.Stderr, os.Stdout), + fmt.Sprintf("%sERROR:%s ", "\033[0;101m", "\033[0m"), + log.Lmicroseconds|log.Lshortfile) + Warning = log.New(io.MultiWriter(os.Stdout), + fmt.Sprintf("%sWarning:%s ", "\033[0;93m", "\033[0m"), + log.Lmicroseconds|log.Lshortfile) +} + +func main() { + quality := flag.Int("quality", 80, "Quality of the image") + to := flag.String("to", ".jpg", "Extension to transcode to") + nosafe := flag.Bool("nosafe", false, "Prevents backup of the original before encoding") + flag.Parse() + + if _, ok := codecs[*to]; !ok { + Error.Println("Invalid extension") + return + } + + log.Printf("Using quality: %d", *quality) + log.Printf("Transcoding to: %s", *to) + log.Printf("Nosafe mode: %v", *nosafe) + + var wg sync.WaitGroup + for _, file := range flag.Args() { + wg.Add(1) + go func(file string) { + defer wg.Done() + file = filepath.ToSlash(file) + file = filepath.Clean(file) + ext := filepath.Ext(file) + coder, ok := codecs[ext] + if !ok { + Error.Printf("No encoder found for file: %s", file) + return + } + log.Printf("%#v", coder) + + filename := filepath.Base(file) + if *nosafe { + bckp := filename + ".bak" + err := os.Link(file, bckp) + if err != nil { + Error.Printf("Failed to backup file: %v", err) + return + } + } + + filehandle, err := os.Open(file) + if err != nil { + Error.Printf("Failed to open file: %v", err) + return + } + + log.Printf("Decoding %s", file) + image, err := coder.Decode(filehandle) + if err != nil { + Error.Printf("Failed to decode file: %v", err) + return + } + filehandle.Close() + + newfile := strings.Replace(filename, ext, *to, 1) + log.Printf("Encoding %s to %s", file, newfile) + + buf, err := coder.Encode(image, *quality) + if err != nil { + Error.Printf("Failed to encode image: %v", err) + return + } + + filehandle, err = os.Create(newfile) + if err != nil { + Error.Printf("Failed to create file %s: %v", newfile, err) + return + } + + n, err := filehandle.Write(buf.Bytes()) + if n != len(buf.Bytes()) || err != nil { + Error.Printf("Failed to write file %s: %v", newfile, err) + return + } + log.Printf("Wrote %d bytes to %s", n, newfile) + }(file) + } + wg.Wait() +} \ No newline at end of file diff --git a/sync b/sync new file mode 100644 index 0000000..f49cfc3 --- /dev/null +++ b/sync @@ -0,0 +1 @@ +image-transcoder.exe,"C:\Program Files\Git\usr\bin\itrans.exe" \ No newline at end of file