forked from mostlygeek/go-exiftool
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstayopen.go
128 lines (103 loc) · 2.84 KB
/
stayopen.go
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
package exiftool
import (
"bufio"
"bytes"
"fmt"
"io"
"os/exec"
"strconv"
"sync"
"github.com/pkg/errors"
)
// Stayopen abstracts running exiftool with `-stay_open` to greatly improve
// performance. Remember to call Stayopen.Stop() to signal exiftool to shutdown
// to avoid zombie perl processes
type Stayopen struct {
l sync.Mutex
cmd *exec.Cmd
stdin io.WriteCloser
stdout io.ReadCloser
scanner *bufio.Scanner
}
// Extract calls exiftool on the supplied filename
func (e *Stayopen) Extract(filename string) ([]byte, error) {
return e.ExtractFlags(filename)
}
func (e *Stayopen) ExtractFlags(filename string, flags ...string) ([]byte, error) {
e.l.Lock()
defer e.l.Unlock()
if e.cmd == nil {
return nil, errors.New("Stopped")
}
if !strconv.CanBackquote(filename) {
return nil, ErrFilenameInvalid
}
// send the request
for _, f := range flags {
fmt.Fprintln(e.stdin, f)
}
fmt.Fprintln(e.stdin, filename)
fmt.Fprintln(e.stdin, "-execute")
if !e.scanner.Scan() {
return nil, errors.New("Failed to read output")
} else {
results := e.scanner.Bytes()
sendResults := make([]byte, len(results), len(results))
copy(sendResults, results)
return sendResults, nil
}
}
func (e *Stayopen) Stop() {
e.l.Lock()
defer e.l.Unlock()
// write message telling it to close
// but don't actually wait for the command to stop
fmt.Fprintln(e.stdin, "-stay_open")
fmt.Fprintln(e.stdin, "False")
fmt.Fprintln(e.stdin, "-execute")
e.cmd = nil
}
func NewStayOpen(exiftool string, flags ...string) (*Stayopen, error) {
flags = append([]string{"-stay_open", "True", "-@", "-", "-common_args"}, flags...)
stayopen := &Stayopen{}
stayopen.cmd = exec.Command(exiftool, flags...)
stdin, err := stayopen.cmd.StdinPipe()
if err != nil {
return nil, errors.Wrap(err, "Failed getting stdin pipe")
}
stdout, err := stayopen.cmd.StdoutPipe()
if err != nil {
return nil, errors.Wrap(err, "Failed getting stdout pipe")
}
stayopen.stdin = stdin
stayopen.stdout = stdout
stayopen.scanner = bufio.NewScanner(stdout)
stayopen.scanner.Split(splitReadyToken)
if err := stayopen.cmd.Start(); err != nil {
return nil, errors.Wrap(err, "Failed starting exiftool in stay_open mode")
}
// wait for both go-routines to startup
return stayopen, nil
}
func splitReadyToken(data []byte, atEOF bool) (int, []byte, error) {
delimPos := bytes.Index(data, []byte("{ready}\n"))
delimSize := 8
// maybe we are on Windows?
if delimPos == -1 {
delimPos = bytes.Index(data, []byte("{ready}\r\n"))
delimSize = 9
}
if delimPos == -1 { // still no token found
if atEOF {
return 0, data, io.EOF
} else {
return 0, nil, nil
}
} else {
if atEOF && len(data) == (delimPos+delimSize) { // nothing left to scan
return delimPos + delimSize, data[:delimPos], bufio.ErrFinalToken
} else {
return delimPos + delimSize, data[:delimPos], nil
}
}
}