forked from sajari/docconv
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpdf_ocr.go
212 lines (169 loc) · 4.57 KB
/
pdf_ocr.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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
//go:build ocr
package docconv
import (
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"sync"
)
var (
exts = []string{".jpg", ".tif", ".tiff", ".png", ".pbm"}
)
func compareExt(ext string, exts []string) bool {
for _, e := range exts {
if ext == e {
return true
}
}
return false
}
func ConvertPDFImages(path string) (BodyResult, error) {
bodyResult := BodyResult{}
tmp, err := os.MkdirTemp(os.TempDir(), "tmp-imgs-")
if err != nil {
bodyResult.err = err
return bodyResult, err
}
tmpDir := fmt.Sprintf("%s/", tmp)
defer func() {
_ = os.RemoveAll(tmpDir) // ignore error
}()
_, err = exec.Command("pdfimages", "-j", path, tmpDir).Output()
if err != nil {
return bodyResult, err
}
filePaths := []string{}
walkFunc := func(path string, info os.FileInfo, err error) error {
path, err = filepath.Abs(path)
if err != nil {
return err
}
if compareExt(filepath.Ext(path), exts) {
filePaths = append(filePaths, path)
}
return nil
}
filepath.Walk(tmpDir, walkFunc)
fileLength := len(filePaths)
if fileLength < 1 {
return bodyResult, nil
}
var wg sync.WaitGroup
data := make(chan string, fileLength)
wg.Add(fileLength)
for _, p := range filePaths {
go func(pathFile string) {
defer wg.Done()
f, err := os.Open(pathFile)
if err != nil {
return
}
defer f.Close()
out, _, err := ConvertImage(f)
if err != nil {
return
}
data <- out
}(p)
}
wg.Wait()
close(data)
for str := range data {
bodyResult.body += str + " "
}
return bodyResult, nil
}
// PdfHasImage verify if `path` (PDF) has images
/*func PDFHasImage(path string) (bool, error) {
cmd := "pdffonts -l 5 %s | tail -n +3 | cut -d' ' -f1 | sort | uniq"
out, err := exec.Command("bash", "-c", fmt.Sprintf(cmd, shellEscape(path))).CombinedOutput()
if err != nil {
return false, err
}
if string(out) == "" {
return true, nil
}
return false, nil
}*/
// PDFHasImage checks if the PDF at the given path contains images.
// It returns true if no fonts are found within the first 5 pages, indicating the presence of images.
func PDFHasImage(path string, toolPath string) (bool, error) {
// Execute the pdffonts command to list fonts in the first 5 pages of the PDF
cmd := exec.Command(toolPath+"pdffonts", "-l", "5", path)
var out bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &out
err := cmd.Run()
if err != nil {
return false, fmt.Errorf("error running pdffonts: %w, output: %s", err, out.String())
}
// Split the output into lines
lines := strings.Split(out.String(), "\n")
// If there are fewer than 3 lines, the PDF contains no fonts
if len(lines) < 3 {
return true, nil
}
// Extract the actual font names, starting from the third line
fonts := make(map[string]struct{})
for _, line := range lines[2:] {
// Extract the first field (font name) by splitting the line
fields := strings.Fields(line)
if len(fields) > 0 {
fonts[fields[0]] = struct{}{}
}
}
// If there are no unique fonts found, assume there are images
return len(fonts) == 0, nil
}
func ConvertPDF(r io.Reader, toolPath string) (string, map[string]string, error) {
f, err := NewLocalFile(r)
if err != nil {
return "", nil, fmt.Errorf("error creating local file: %v", err)
}
defer f.Done()
bodyResult, metaResult, textConvertErr := ConvertPDFText(f.Name(), toolPath)
if textConvertErr != nil {
return "", nil, textConvertErr
}
if bodyResult.err != nil {
return "", nil, bodyResult.err
}
if metaResult.err != nil {
return "", nil, metaResult.err
}
hasImage, err := PDFHasImage(f.Name(), toolPath)
if err != nil {
return "", nil, fmt.Errorf("could not check if PDF has image: %w", err)
}
if !hasImage {
return bodyResult.body, metaResult.meta, nil
}
imageConvertResult, imageConvertErr := ConvertPDFImages(f.Name())
if imageConvertErr != nil {
return bodyResult.body, metaResult.meta, nil // ignore error, return what we have
}
if imageConvertResult.err != nil {
return bodyResult.body, metaResult.meta, nil // ignore error, return what we have
}
fullBody := strings.Join([]string{bodyResult.body, imageConvertResult.body}, " ")
return fullBody, metaResult.meta, nil
}
var shellEscapePattern *regexp.Regexp
func init() {
shellEscapePattern = regexp.MustCompile(`[^\w@%+=:,./-]`)
}
// shellEscape returns a shell-escaped version of the string s. The returned value
// is a string that can safely be used as one token in a shell command line.
func shellEscape(s string) string {
if len(s) == 0 {
return "''"
}
if shellEscapePattern.MatchString(s) {
return "'" + strings.Replace(s, "'", "'\"'\"'", -1) + "'"
}
return s
}