Refactor runner and installation scripts for improved functionality
- Removed the `--disable-hiprt` flag from the runner command, simplifying the rendering options for users. - Updated the `jiggablend-runner` script and README to reflect the removal of the HIPRT control flag, enhancing clarity in usage instructions. - Enhanced the installation script to provide clearer examples for running the jiggablend manager and runner, improving user experience during setup. - Implemented a more robust GPU backend detection mechanism, allowing for better compatibility with various hardware configurations.
This commit is contained in:
123
pkg/blendfile/version.go
Normal file
123
pkg/blendfile/version.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package blendfile
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// ParseVersionFromReader parses the Blender version from a reader.
|
||||
// Returns major and minor version numbers.
|
||||
//
|
||||
// Blend file header layout (12 bytes):
|
||||
//
|
||||
// "BLENDER" (7) + pointer-size (1: '-'=64, '_'=32) + endian (1: 'v'=LE, 'V'=BE)
|
||||
// + version (3 digits, e.g. "402" = 4.02)
|
||||
//
|
||||
// Supports uncompressed, gzip-compressed, and zstd-compressed blend files.
|
||||
func ParseVersionFromReader(r io.ReadSeeker) (major, minor int, err error) {
|
||||
header := make([]byte, 12)
|
||||
n, err := r.Read(header)
|
||||
if err != nil || n < 12 {
|
||||
return 0, 0, fmt.Errorf("failed to read blend file header: %w", err)
|
||||
}
|
||||
|
||||
if string(header[:7]) != "BLENDER" {
|
||||
r.Seek(0, 0)
|
||||
return parseCompressedVersion(r)
|
||||
}
|
||||
|
||||
return parseVersionDigits(header[9:12])
|
||||
}
|
||||
|
||||
// ParseVersionFromFile opens a blend file and parses the Blender version.
|
||||
func ParseVersionFromFile(blendPath string) (major, minor int, err error) {
|
||||
file, err := os.Open(blendPath)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("failed to open blend file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
return ParseVersionFromReader(file)
|
||||
}
|
||||
|
||||
// VersionString returns a formatted version string like "4.2".
|
||||
func VersionString(major, minor int) string {
|
||||
return fmt.Sprintf("%d.%d", major, minor)
|
||||
}
|
||||
|
||||
func parseVersionDigits(versionBytes []byte) (major, minor int, err error) {
|
||||
if len(versionBytes) != 3 {
|
||||
return 0, 0, fmt.Errorf("expected 3 version digits, got %d", len(versionBytes))
|
||||
}
|
||||
fmt.Sscanf(string(versionBytes[0]), "%d", &major)
|
||||
fmt.Sscanf(string(versionBytes[1:3]), "%d", &minor)
|
||||
return major, minor, nil
|
||||
}
|
||||
|
||||
func parseCompressedVersion(r io.ReadSeeker) (major, minor int, err error) {
|
||||
magic := make([]byte, 4)
|
||||
if _, err := r.Read(magic); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
r.Seek(0, 0)
|
||||
|
||||
// gzip: 0x1f 0x8b
|
||||
if magic[0] == 0x1f && magic[1] == 0x8b {
|
||||
gzReader, err := gzip.NewReader(r)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("failed to create gzip reader: %w", err)
|
||||
}
|
||||
defer gzReader.Close()
|
||||
|
||||
header := make([]byte, 12)
|
||||
n, err := gzReader.Read(header)
|
||||
if err != nil || n < 12 {
|
||||
return 0, 0, fmt.Errorf("failed to read compressed blend header: %w", err)
|
||||
}
|
||||
if string(header[:7]) != "BLENDER" {
|
||||
return 0, 0, fmt.Errorf("invalid blend file format")
|
||||
}
|
||||
return parseVersionDigits(header[9:12])
|
||||
}
|
||||
|
||||
// zstd: 0x28 0xB5 0x2F 0xFD
|
||||
if magic[0] == 0x28 && magic[1] == 0xb5 && magic[2] == 0x2f && magic[3] == 0xfd {
|
||||
return parseZstdVersion(r)
|
||||
}
|
||||
|
||||
return 0, 0, fmt.Errorf("unknown blend file format")
|
||||
}
|
||||
|
||||
func parseZstdVersion(r io.ReadSeeker) (major, minor int, err error) {
|
||||
r.Seek(0, 0)
|
||||
|
||||
cmd := exec.Command("zstd", "-d", "-c")
|
||||
cmd.Stdin = r
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("failed to create zstd stdout pipe: %w", err)
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return 0, 0, fmt.Errorf("failed to start zstd decompression: %w", err)
|
||||
}
|
||||
|
||||
header := make([]byte, 12)
|
||||
n, readErr := io.ReadFull(stdout, header)
|
||||
|
||||
cmd.Process.Kill()
|
||||
cmd.Wait()
|
||||
|
||||
if readErr != nil || n < 12 {
|
||||
return 0, 0, fmt.Errorf("failed to read zstd compressed blend header: %v", readErr)
|
||||
}
|
||||
if string(header[:7]) != "BLENDER" {
|
||||
return 0, 0, fmt.Errorf("invalid blend file format in zstd archive")
|
||||
}
|
||||
|
||||
return parseVersionDigits(header[9:12])
|
||||
}
|
||||
96
pkg/blendfile/version_test.go
Normal file
96
pkg/blendfile/version_test.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package blendfile
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func makeBlendHeader(major, minor int) []byte {
|
||||
header := make([]byte, 12)
|
||||
copy(header[:7], "BLENDER")
|
||||
header[7] = '-'
|
||||
header[8] = 'v'
|
||||
header[9] = byte('0' + major)
|
||||
header[10] = byte('0' + minor/10)
|
||||
header[11] = byte('0' + minor%10)
|
||||
return header
|
||||
}
|
||||
|
||||
func TestParseVersionFromReader_Uncompressed(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
major int
|
||||
minor int
|
||||
wantMajor int
|
||||
wantMinor int
|
||||
}{
|
||||
{"Blender 4.02", 4, 2, 4, 2},
|
||||
{"Blender 3.06", 3, 6, 3, 6},
|
||||
{"Blender 2.79", 2, 79, 2, 79},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
header := makeBlendHeader(tt.major, tt.minor)
|
||||
r := bytes.NewReader(header)
|
||||
|
||||
major, minor, err := ParseVersionFromReader(r)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseVersionFromReader: %v", err)
|
||||
}
|
||||
if major != tt.wantMajor || minor != tt.wantMinor {
|
||||
t.Errorf("got %d.%d, want %d.%d", major, minor, tt.wantMajor, tt.wantMinor)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseVersionFromReader_GzipCompressed(t *testing.T) {
|
||||
header := makeBlendHeader(4, 2)
|
||||
// Pad to ensure gzip has enough data for a full read
|
||||
data := make([]byte, 128)
|
||||
copy(data, header)
|
||||
|
||||
var buf bytes.Buffer
|
||||
gz := gzip.NewWriter(&buf)
|
||||
gz.Write(data)
|
||||
gz.Close()
|
||||
|
||||
r := bytes.NewReader(buf.Bytes())
|
||||
major, minor, err := ParseVersionFromReader(r)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseVersionFromReader (gzip): %v", err)
|
||||
}
|
||||
if major != 4 || minor != 2 {
|
||||
t.Errorf("got %d.%d, want 4.2", major, minor)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseVersionFromReader_InvalidMagic(t *testing.T) {
|
||||
data := []byte("NOT_BLEND_DATA_HERE")
|
||||
r := bytes.NewReader(data)
|
||||
|
||||
_, _, err := ParseVersionFromReader(r)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for invalid magic, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseVersionFromReader_TooShort(t *testing.T) {
|
||||
data := []byte("SHORT")
|
||||
r := bytes.NewReader(data)
|
||||
|
||||
_, _, err := ParseVersionFromReader(r)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for short data, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionString(t *testing.T) {
|
||||
got := VersionString(4, 2)
|
||||
want := "4.2"
|
||||
if got != want {
|
||||
t.Errorf("VersionString(4, 2) = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user