feat(assets): seven asset commands + Run dispatch/helpers
check-dupes, clean-dupes, check-mdl, fix-mdl, convert, upscale, compile behind a single runner indirection, mirroring internal/depot's Run shape. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
package assets
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var upscaleBackends = []string{"upscayl-bin", "upscayl", "realesrgan-ncnn-vulkan", "waifu2x-ncnn-vulkan"}
|
||||
|
||||
// runUpscale upscales textures in place through an installed ncnn-vulkan
|
||||
// backend. DDS inputs are bridged through PNG so the NWN flip stays correct.
|
||||
func runUpscale(args []string, stdout, stderr io.Writer) int {
|
||||
fs := flag.NewFlagSet("upscale", flag.ContinueOnError)
|
||||
fs.SetOutput(stderr)
|
||||
scale := fs.Int("scale", 4, "upscale factor")
|
||||
backend := fs.String("backend", "", "override the upscaler binary")
|
||||
nonRecursive := fs.Bool("non-recursive", false, "do not descend into subdirectories")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return exitUsage
|
||||
}
|
||||
dirs := fs.Args()
|
||||
if len(dirs) == 0 {
|
||||
fmt.Fprintln(stderr, "assets upscale: usage: upscale [--scale N] <dir>...")
|
||||
return exitUsage
|
||||
}
|
||||
|
||||
candidates := upscaleBackends
|
||||
if *backend != "" {
|
||||
candidates = []string{*backend}
|
||||
}
|
||||
tool := look(candidates...)
|
||||
if tool == "" {
|
||||
fmt.Fprintf(stderr, "assets upscale: no upscaler found — install one of: %s\n", strings.Join(upscaleBackends, ", "))
|
||||
return exitTool
|
||||
}
|
||||
|
||||
files, err := walk(dirs, textureExts, !*nonRecursive)
|
||||
if err != nil {
|
||||
fmt.Fprintln(stderr, "assets upscale:", err)
|
||||
return exitUsage
|
||||
}
|
||||
|
||||
// magick is only needed if a .dds input is present; resolve lazily.
|
||||
magick := ""
|
||||
failed := false
|
||||
for _, src := range files {
|
||||
var upErr error
|
||||
if strings.EqualFold(filepath.Ext(src), ".dds") {
|
||||
if magick == "" {
|
||||
if magick = look("magick"); magick == "" {
|
||||
fmt.Fprintln(stderr, "assets upscale: .dds input needs ImageMagick (magick) for the png bridge")
|
||||
return exitTool
|
||||
}
|
||||
}
|
||||
upErr = upscaleDDS(tool, magick, src, *scale)
|
||||
} else {
|
||||
upErr = upscaleImage(tool, src, src, *scale)
|
||||
}
|
||||
if upErr != nil {
|
||||
fmt.Fprintf(stderr, "assets upscale: %s: %v\n", src, upErr)
|
||||
failed = true
|
||||
}
|
||||
}
|
||||
if failed {
|
||||
return exitFail
|
||||
}
|
||||
return exitOK
|
||||
}
|
||||
|
||||
// upscaleImage runs the ncnn-vulkan backend to upscale src into dst (may be the
|
||||
// same path).
|
||||
func upscaleImage(tool, src, dst string, scale int) error {
|
||||
tmp := dst + ".upscaled.png"
|
||||
out, err := runner("", nil, tool, "-i", src, "-o", tmp, "-s", strconv.Itoa(scale))
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %v: %s", filepath.Base(tool), err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
return os.Rename(tmp, dst)
|
||||
}
|
||||
|
||||
// upscaleDDS bridges a DDS through PNG: dds->png (flip), upscale, png->dds
|
||||
// (flip back), yielding a correctly-flipped upscaled DDS.
|
||||
func upscaleDDS(tool, magick, src string, scale int) error {
|
||||
tmpPNG := src + ".bridge.png"
|
||||
if err := ddsToPNG(magick, src, tmpPNG); err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tmpPNG)
|
||||
if err := upscaleImage(tool, tmpPNG, tmpPNG, scale); err != nil {
|
||||
return err
|
||||
}
|
||||
return pngToDDS(magick, tmpPNG, src)
|
||||
}
|
||||
Reference in New Issue
Block a user