mirror of
https://github.com/coalaura/ffwebp.git
synced 2025-09-08 13:59:54 +00:00
temp
This commit is contained in:
6
go.mod
6
go.mod
@@ -21,19 +21,17 @@ require (
|
|||||||
github.com/spakin/netpbm v1.3.2
|
github.com/spakin/netpbm v1.3.2
|
||||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c
|
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c
|
||||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef
|
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef
|
||||||
github.com/stretchr/testify v1.10.0
|
|
||||||
github.com/xyproto/xbm v1.0.0
|
github.com/xyproto/xbm v1.0.0
|
||||||
golang.org/x/image v0.30.0
|
golang.org/x/image v0.30.0
|
||||||
|
gotest.tools/v3 v3.5.2
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/ebitengine/purego v0.8.4 // indirect
|
github.com/ebitengine/purego v0.8.4 // indirect
|
||||||
|
github.com/google/go-cmp v0.5.9 // indirect
|
||||||
github.com/gopherjs/gopherjs v1.17.2 // indirect
|
github.com/gopherjs/gopherjs v1.17.2 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
github.com/sergeymakinen/go-bmp v1.0.0 // indirect
|
github.com/sergeymakinen/go-bmp v1.0.0 // indirect
|
||||||
github.com/tetratelabs/wazero v1.9.0 // indirect
|
github.com/tetratelabs/wazero v1.9.0 // indirect
|
||||||
golang.org/x/net v0.43.0 // indirect
|
golang.org/x/net v0.43.0 // indirect
|
||||||
golang.org/x/text v0.28.0 // indirect
|
golang.org/x/text v0.28.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
6
go.sum
6
go.sum
@@ -14,6 +14,8 @@ github.com/gen2brain/jpegxl v0.4.5 h1:TWpVEn5xkIfsswzkjHBArd0Cc9AE0tbjBSoa0jDsrb
|
|||||||
github.com/gen2brain/jpegxl v0.4.5/go.mod h1:4kWYJ18xCEuO2vzocYdGpeqNJ990/Gjy3uLMg5TBN6I=
|
github.com/gen2brain/jpegxl v0.4.5/go.mod h1:4kWYJ18xCEuO2vzocYdGpeqNJ990/Gjy3uLMg5TBN6I=
|
||||||
github.com/gen2brain/webp v0.5.5 h1:MvQR75yIPU/9nSqYT5h13k4URaJK3gf9tgz/ksRbyEg=
|
github.com/gen2brain/webp v0.5.5 h1:MvQR75yIPU/9nSqYT5h13k4URaJK3gf9tgz/ksRbyEg=
|
||||||
github.com/gen2brain/webp v0.5.5/go.mod h1:xOSMzp4aROt2KFW++9qcK/RBTOVC2S9tJG66ip/9Oc0=
|
github.com/gen2brain/webp v0.5.5/go.mod h1:xOSMzp4aROt2KFW++9qcK/RBTOVC2S9tJG66ip/9Oc0=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
||||||
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
||||||
github.com/hullerob/go.farbfeld v0.0.0-20181222022525-3661193c725f h1:1LkiAnH6RhOEbQAcfcEcixM5IsegqFi6IH0Nz0ZGqYs=
|
github.com/hullerob/go.farbfeld v0.0.0-20181222022525-3661193c725f h1:1LkiAnH6RhOEbQAcfcEcixM5IsegqFi6IH0Nz0ZGqYs=
|
||||||
@@ -56,7 +58,7 @@ golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
|||||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||||
|
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||||
|
@@ -1,21 +1,19 @@
|
|||||||
|
// integration/integration_test.go
|
||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"image/jpeg"
|
"image/jpeg"
|
||||||
"io/fs"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"gotest.tools/v3/icmd"
|
||||||
)
|
)
|
||||||
|
|
||||||
// skip encoding tests for these extensions
|
// Skip encoding tests for these extensions (same semantics as your original)
|
||||||
var encodeOnly = map[string]bool{
|
var encodeOnly = map[string]bool{
|
||||||
"heic": true,
|
"heic": true,
|
||||||
"heif": true,
|
"heif": true,
|
||||||
@@ -23,135 +21,130 @@ var encodeOnly = map[string]bool{
|
|||||||
"svg": true,
|
"svg": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFFWebP(t *testing.T) {
|
const (
|
||||||
// build a fresh executable with "full" tags
|
fixturesDir = "images" // integration/images/
|
||||||
executable, err := buildExecutable()
|
exampleJPEG = "example.jpeg" // integration/example.jpeg (256x256)
|
||||||
require.NoError(t, err)
|
)
|
||||||
|
|
||||||
defer os.Remove(executable)
|
var ffwebpBin string // set in TestMain
|
||||||
|
|
||||||
// resolve all test files
|
func TestMain(m *testing.M) {
|
||||||
files, err := listFiles("images")
|
// Build the CLI once with -tags=full into a temp dir, reusing it across tests.
|
||||||
require.NoError(t, err)
|
tmp, err := os.MkdirTemp("", "ffwebp-bin-*")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "mktemp:", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmp)
|
||||||
|
|
||||||
// test all extensions
|
exe := "ffwebp"
|
||||||
for _, path := range files {
|
if runtime.GOOS == "windows" {
|
||||||
ext := strings.TrimLeft(filepath.Ext(path), ".")
|
exe += ".exe"
|
||||||
|
}
|
||||||
|
ffwebpBin = filepath.Join(tmp, exe)
|
||||||
|
|
||||||
decoded := "decoded.jpeg"
|
root, err := findRepoRoot()
|
||||||
encoded := fmt.Sprintf("encoded.%s", ext)
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
// test if we can convert from the codec to jpeg
|
// Build from repo root so relative paths to ./cmd/ffwebp resolve.
|
||||||
t.Run(fmt.Sprintf("decode %s", ext), func(t *testing.T) {
|
build := icmd.Command("go", "build", "-tags=full", "-o", ffwebpBin, "./cmd/ffwebp")
|
||||||
defer os.Remove(decoded)
|
build.Dir = root
|
||||||
|
res := icmd.RunCmd(build)
|
||||||
|
if res.ExitCode != 0 {
|
||||||
|
fmt.Fprintln(os.Stderr, res.Combined())
|
||||||
|
os.Exit(res.ExitCode)
|
||||||
|
}
|
||||||
|
|
||||||
err = runCommand(executable, "-i", path, "-o", decoded)
|
os.Exit(m.Run())
|
||||||
require.NoError(t, err)
|
}
|
||||||
|
|
||||||
err = validateJPEG(decoded, 0)
|
func TestCLI_CodecDecodeAndRoundTrip(t *testing.T) {
|
||||||
require.NoError(t, err)
|
// Discover input samples: integration/images/image.<ext>
|
||||||
|
entries, err := os.ReadDir(fixturesDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read %s: %v", fixturesDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range entries {
|
||||||
|
if e.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
inPath := filepath.Join(fixturesDir, e.Name())
|
||||||
|
ext := strings.TrimPrefix(filepath.Ext(e.Name()), ".")
|
||||||
|
|
||||||
|
// 1) <codec> -> jpeg
|
||||||
|
t.Run("decode_"+ext, func(t *testing.T) {
|
||||||
|
tmp := t.TempDir()
|
||||||
|
out := filepath.Join(tmp, "decoded.jpeg")
|
||||||
|
|
||||||
|
run(t, icmd.Command(ffwebpBin, "-s", "-i", inPath, "-o", out))
|
||||||
|
|
||||||
|
w, h := jpegSize(t, out)
|
||||||
|
if w == 0 || h == 0 {
|
||||||
|
t.Fatalf("decoded JPEG has invalid size: %dx%d", w, h)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 2) jpeg -> <codec> -> jpeg (dimension stays 256x256), unless encodeOnly
|
||||||
if encodeOnly[ext] {
|
if encodeOnly[ext] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// test if we can convert from jpeg to the codec and then back to jpeg
|
t.Run("encode_"+ext, func(t *testing.T) {
|
||||||
t.Run(fmt.Sprintf("encode %s", ext), func(t *testing.T) {
|
tmp := t.TempDir()
|
||||||
defer os.Remove(encoded)
|
mid := filepath.Join(tmp, "encoded."+ext)
|
||||||
defer os.Remove(decoded)
|
back := filepath.Join(tmp, "roundtrip.jpeg")
|
||||||
|
|
||||||
err = runCommand(executable, "-i", "example.jpeg", "-o", encoded)
|
run(t, icmd.Command(ffwebpBin, "-s", "-i", exampleJPEG, "-o", mid))
|
||||||
require.NoError(t, err)
|
run(t, icmd.Command(ffwebpBin, "-s", "-i", mid, "-o", back))
|
||||||
|
|
||||||
err = runCommand(executable, "-i", encoded, "-o", decoded)
|
w, h := jpegSize(t, back)
|
||||||
require.NoError(t, err)
|
if w != 256 || h != 256 {
|
||||||
|
t.Fatalf("roundtrip dimension mismatch: got %dx%d, want 256x256", w, h)
|
||||||
err = validateJPEG(decoded, 256)
|
}
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildExecutable() (string, error) {
|
func run(t *testing.T, cmd icmd.Cmd) {
|
||||||
if runtime.GOOS == "windows" {
|
t.Helper()
|
||||||
err := runCommand("go", "build", "-tags", "full", "-o", "ffwebp.exe", "..\\cmd\\ffwebp")
|
res := icmd.RunCmd(cmd)
|
||||||
if err != nil {
|
// On failure, icmd prints the command line + stdout/stderr, which is handy.
|
||||||
return "", err
|
res.Assert(t, icmd.Success)
|
||||||
}
|
}
|
||||||
|
|
||||||
return "./ffwebp.exe", nil
|
func jpegSize(t *testing.T, path string) (int, int) {
|
||||||
|
t.Helper()
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("open %s: %v", path, err)
|
||||||
}
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
err := runCommand("go", "build", "-tags", "full", "-o", "ffwebp", "../cmd/ffwebp")
|
cfg, err := jpeg.DecodeConfig(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("decode jpeg config %s: %v", path, err)
|
||||||
|
}
|
||||||
|
return cfg.Width, cfg.Height
|
||||||
|
}
|
||||||
|
|
||||||
|
func findRepoRoot() (string, error) {
|
||||||
|
// Walk up to find go.mod so we can run `go build ./cmd/ffwebp` reliably.
|
||||||
|
dir, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
for {
|
||||||
err = runCommand("chmod", "+x", "ffwebp")
|
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
|
||||||
if err != nil {
|
return dir, nil
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return "./ffwebp", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func listFiles(directory string) ([]string, error) {
|
|
||||||
var files []string
|
|
||||||
|
|
||||||
err := filepath.Walk(directory, func(path string, info fs.FileInfo, err error) error {
|
|
||||||
if err != nil || info.IsDir() {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
parent := filepath.Dir(dir)
|
||||||
files = append(files, path)
|
if parent == dir {
|
||||||
|
return "", fmt.Errorf("could not locate go.mod starting from %s", dir)
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func runCommand(command string, args ...string) error {
|
|
||||||
cmd := exec.Command(command, args...)
|
|
||||||
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
if len(out) > 0 {
|
|
||||||
return errors.New(string(out))
|
|
||||||
}
|
}
|
||||||
|
dir = parent
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateJPEG(path string, requireSize int) error {
|
|
||||||
file, err := os.OpenFile(path, os.O_RDONLY, 0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
img, err := jpeg.Decode(file)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
bounds := img.Bounds()
|
|
||||||
|
|
||||||
if bounds.Dx() == 0 || bounds.Dy() == 0 {
|
|
||||||
return fmt.Errorf("invalid dimensions: %dx%d", bounds.Dx(), bounds.Dy())
|
|
||||||
}
|
|
||||||
|
|
||||||
if requireSize != 0 && (bounds.Dx() != requireSize || bounds.Dy() != requireSize) {
|
|
||||||
return fmt.Errorf("mismatched size: %dx%dx", bounds.Dx(), bounds.Dy())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user