Initial commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
image-transcoder.exe
|
||||||
|
main.log
|
12
codec/coder.go
Normal file
12
codec/coder.go
Normal file
@@ -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)
|
||||||
|
}
|
23
codec/jpeg.go
Normal file
23
codec/jpeg.go
Normal file
@@ -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
|
||||||
|
}
|
34
codec/png.go
Normal file
34
codec/png.go
Normal file
@@ -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)
|
||||||
|
}
|
26
codec/webp.go
Normal file
26
codec/webp.go
Normal file
@@ -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
|
||||||
|
}
|
8
go.mod
Normal file
8
go.mod
Normal file
@@ -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
|
||||||
|
)
|
4
go.sum
Normal file
4
go.sum
Normal file
@@ -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=
|
116
main.go
Normal file
116
main.go
Normal file
@@ -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()
|
||||||
|
}
|
Reference in New Issue
Block a user