-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathheic_cgo.go
More file actions
146 lines (120 loc) · 3.42 KB
/
heic_cgo.go
File metadata and controls
146 lines (120 loc) · 3.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//go:build cgo && !windows
package main
import (
"fmt"
"image"
"io"
"os"
"path/filepath"
"sync"
"syscall"
"github.com/klippa-app/go-libheif"
"github.com/klippa-app/go-libheif/library"
)
var (
heicInitOnce sync.Once
heicInitErr error
devNull *os.File
)
func init() {
// Override the default - HEIC is available with CGO
isHEICAvailableDefault = true
// Suppress debug output from hashicorp/go-plugin used by go-libheif
os.Setenv("HCLOG_LEVEL", "OFF")
// Open /dev/null for redirecting stderr during HEIC operations
var err error
devNull, err = os.OpenFile("/dev/null", os.O_WRONLY, 0)
if err != nil {
devNull = nil
}
}
// suppressOutput temporarily redirects both stdout and stderr to /dev/null and returns a restore function
func suppressOutput() func() {
if devNull == nil {
return func() {} // no-op if /dev/null couldn't be opened
}
// Save original stdout and stderr
originalStdout, err1 := syscall.Dup(int(os.Stdout.Fd()))
originalStderr, err2 := syscall.Dup(int(os.Stderr.Fd()))
if err1 != nil || err2 != nil {
if err1 == nil {
syscall.Close(originalStdout)
}
if err2 == nil {
syscall.Close(originalStderr)
}
return func() {}
}
// Redirect stdout and stderr to /dev/null
syscall.Dup2(int(devNull.Fd()), int(os.Stdout.Fd()))
syscall.Dup2(int(devNull.Fd()), int(os.Stderr.Fd()))
// Return restore function
return func() {
syscall.Dup2(originalStdout, int(os.Stdout.Fd()))
syscall.Dup2(originalStderr, int(os.Stderr.Fd()))
syscall.Close(originalStdout)
syscall.Close(originalStderr)
}
}
// CGOHEICDecoder provides go-libheif-based HEIC decoding
type CGOHEICDecoder struct {
initialized bool
}
// createHEICDecoder creates a new CGO-based HEIC decoder
func createHEICDecoder() HEICDecoder {
decoder := &CGOHEICDecoder{}
// Initialize go-libheif once
heicInitOnce.Do(func() {
// Determine worker binary path
// Worker should be built and placed alongside main binary
execPath, err := os.Executable()
if err != nil {
heicInitErr = fmt.Errorf("failed to get executable path: %v", err)
return
}
workerPath := filepath.Join(filepath.Dir(execPath), "heic_worker")
// Check if worker exists
if _, err := os.Stat(workerPath); os.IsNotExist(err) {
// Try current directory as fallback
workerPath = "./heic_worker"
if _, err := os.Stat(workerPath); os.IsNotExist(err) {
heicInitErr = fmt.Errorf("HEIC worker binary not found (looked in %s and ./heic_worker)", filepath.Dir(execPath))
return
}
}
// Initialize libheif with worker (suppress debug output)
restore := suppressOutput()
defer restore()
config := libheif.Config{
LibraryConfig: library.Config{
Command: library.Command{
BinPath: workerPath,
Args: []string{},
},
},
}
if err := libheif.Init(config); err != nil {
heicInitErr = fmt.Errorf("failed to initialize libheif: %v", err)
return
}
decoder.initialized = true
})
return decoder
}
func (d *CGOHEICDecoder) IsAvailable() bool {
return d.initialized && heicInitErr == nil
}
func (d *CGOHEICDecoder) Decode(r io.Reader) (image.Image, error) {
if !d.IsAvailable() {
return nil, fmt.Errorf("HEIC decoder not available: %v", heicInitErr)
}
// Suppress debug output during decoding
restore := suppressOutput()
defer restore()
// Use libheif to decode the image
img, err := libheif.DecodeImage(r)
if err != nil {
return nil, fmt.Errorf("failed to decode HEIC image: %v", err)
}
return img, nil
}