captcha-ocr 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.md +223 -0
- package/README.zh-CN.md +223 -0
- package/dist/arithmetic-CUs9apP8.d.cts +113 -0
- package/dist/arithmetic-CUs9apP8.d.ts +113 -0
- package/dist/index.cjs +475 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +40 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +423 -0
- package/dist/index.js.map +1 -0
- package/dist/rules/arithmetic.cjs +176 -0
- package/dist/rules/arithmetic.cjs.map +1 -0
- package/dist/rules/arithmetic.d.cts +1 -0
- package/dist/rules/arithmetic.d.ts +1 -0
- package/dist/rules/arithmetic.js +145 -0
- package/dist/rules/arithmetic.js.map +1 -0
- package/package.json +66 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
CaptchaRecognizer: () => CaptchaRecognizer,
|
|
34
|
+
DEFAULT_OPERATORS: () => DEFAULT_OPERATORS,
|
|
35
|
+
ImageInputError: () => ImageInputError,
|
|
36
|
+
arithmeticProfile: () => arithmeticProfile,
|
|
37
|
+
arithmeticRule: () => arithmeticRule,
|
|
38
|
+
calculate: () => calculate,
|
|
39
|
+
closeDefaultRecognizer: () => closeDefaultRecognizer,
|
|
40
|
+
createArithmeticRule: () => createArithmeticRule,
|
|
41
|
+
createRecognizer: () => createRecognizer,
|
|
42
|
+
getImageSource: () => getImageSource,
|
|
43
|
+
getPreprocessProfile: () => getPreprocessProfile,
|
|
44
|
+
normalizeOcrText: () => normalizeOcrText,
|
|
45
|
+
parseExpression: () => parseExpression,
|
|
46
|
+
preprocessImage: () => preprocessImage,
|
|
47
|
+
recognizeCaptcha: () => recognizeCaptcha,
|
|
48
|
+
toImageBuffer: () => toImageBuffer
|
|
49
|
+
});
|
|
50
|
+
module.exports = __toCommonJS(src_exports);
|
|
51
|
+
|
|
52
|
+
// src/recognizer.ts
|
|
53
|
+
var import_sharp2 = __toESM(require("sharp"), 1);
|
|
54
|
+
|
|
55
|
+
// src/input.ts
|
|
56
|
+
var ImageInputError = class extends Error {
|
|
57
|
+
code = "INVALID_IMAGE_INPUT";
|
|
58
|
+
constructor(message) {
|
|
59
|
+
super(message);
|
|
60
|
+
this.name = "ImageInputError";
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
function toImageBuffer(input) {
|
|
64
|
+
if (Buffer.isBuffer(input)) {
|
|
65
|
+
return input;
|
|
66
|
+
}
|
|
67
|
+
if (input instanceof Uint8Array) {
|
|
68
|
+
return Buffer.from(input);
|
|
69
|
+
}
|
|
70
|
+
if (typeof input !== "string") {
|
|
71
|
+
throw new ImageInputError(
|
|
72
|
+
"\u56FE\u7247\u8F93\u5165\u5FC5\u987B\u662F Buffer\u3001Uint8Array\u3001Data URL \u6216 Base64 \u5B57\u7B26\u4E32"
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
const value = input.trim();
|
|
76
|
+
const dataUrlMatch = value.match(
|
|
77
|
+
/^data:image\/(?:png|jpeg|webp);base64,([A-Za-z0-9+/=\s]+)$/i
|
|
78
|
+
);
|
|
79
|
+
const payload = (dataUrlMatch?.[1] ?? value).replace(/\s+/g, "");
|
|
80
|
+
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(payload) || payload.length % 4 === 1) {
|
|
81
|
+
throw new ImageInputError("\u56FE\u7247\u5B57\u7B26\u4E32\u4E0D\u662F\u6709\u6548\u7684 Base64 \u6216 Data URL");
|
|
82
|
+
}
|
|
83
|
+
const buffer = Buffer.from(payload, "base64");
|
|
84
|
+
if (buffer.length === 0) {
|
|
85
|
+
throw new ImageInputError("\u56FE\u7247\u8F93\u5165\u4E3A\u7A7A");
|
|
86
|
+
}
|
|
87
|
+
return buffer;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/ocr.ts
|
|
91
|
+
var import_tesseract = require("tesseract.js");
|
|
92
|
+
var TesseractOcr = class {
|
|
93
|
+
constructor(profile, logger) {
|
|
94
|
+
this.profile = profile;
|
|
95
|
+
this.logger = logger;
|
|
96
|
+
}
|
|
97
|
+
profile;
|
|
98
|
+
logger;
|
|
99
|
+
workerPromise;
|
|
100
|
+
getWorker() {
|
|
101
|
+
if (!this.workerPromise) {
|
|
102
|
+
const workerOptions = this.logger ? {
|
|
103
|
+
logger: (entry) => {
|
|
104
|
+
this.logger?.(entry);
|
|
105
|
+
}
|
|
106
|
+
} : {};
|
|
107
|
+
this.workerPromise = (0, import_tesseract.createWorker)(
|
|
108
|
+
this.profile.language,
|
|
109
|
+
1,
|
|
110
|
+
workerOptions
|
|
111
|
+
).then(async (worker) => {
|
|
112
|
+
const typedWorker = worker;
|
|
113
|
+
await typedWorker.setParameters({
|
|
114
|
+
tessedit_char_whitelist: this.profile.whitelist,
|
|
115
|
+
tessedit_pageseg_mode: String(this.profile.pageSegMode),
|
|
116
|
+
preserve_interword_spaces: this.profile.preserveInterwordSpaces ? "1" : "0"
|
|
117
|
+
});
|
|
118
|
+
return typedWorker;
|
|
119
|
+
}).catch((error) => {
|
|
120
|
+
this.workerPromise = void 0;
|
|
121
|
+
throw error;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return this.workerPromise;
|
|
125
|
+
}
|
|
126
|
+
async recognize(image) {
|
|
127
|
+
const worker = await this.getWorker();
|
|
128
|
+
const result = await worker.recognize(image);
|
|
129
|
+
return result.data?.text ?? "";
|
|
130
|
+
}
|
|
131
|
+
async close() {
|
|
132
|
+
if (!this.workerPromise) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const worker = await this.workerPromise;
|
|
136
|
+
await worker.terminate();
|
|
137
|
+
this.workerPromise = void 0;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// src/preprocess.ts
|
|
142
|
+
var import_sharp = __toESM(require("sharp"), 1);
|
|
143
|
+
var DEFAULT_SCALE = 4;
|
|
144
|
+
var DEFAULT_THRESHOLD = 160;
|
|
145
|
+
async function preprocessImage(input, options = {}) {
|
|
146
|
+
const scale = options.scale ?? DEFAULT_SCALE;
|
|
147
|
+
const threshold = options.threshold ?? DEFAULT_THRESHOLD;
|
|
148
|
+
if (scale < 1 || scale > 10) {
|
|
149
|
+
throw new RangeError("scale must be between 1 and 10");
|
|
150
|
+
}
|
|
151
|
+
if (threshold < 1 || threshold > 254) {
|
|
152
|
+
throw new RangeError("threshold must be between 1 and 254");
|
|
153
|
+
}
|
|
154
|
+
const source = (0, import_sharp.default)(input, { failOn: "error" });
|
|
155
|
+
const metadata = await source.metadata();
|
|
156
|
+
const width = metadata.width ? Math.round(metadata.width * scale) : void 0;
|
|
157
|
+
const height = metadata.height ? Math.round(metadata.height * scale) : void 0;
|
|
158
|
+
const buffer = await source.rotate().resize({
|
|
159
|
+
width,
|
|
160
|
+
height,
|
|
161
|
+
fit: "fill",
|
|
162
|
+
kernel: import_sharp.default.kernel.lanczos3
|
|
163
|
+
}).flatten({ background: "#ffffff" }).grayscale().threshold(threshold).png().toBuffer();
|
|
164
|
+
return {
|
|
165
|
+
buffer,
|
|
166
|
+
source: getImageSource(metadata),
|
|
167
|
+
options: {
|
|
168
|
+
mode: "threshold",
|
|
169
|
+
scale,
|
|
170
|
+
threshold
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function getImageSource(metadata) {
|
|
175
|
+
return {
|
|
176
|
+
width: metadata.width ?? null,
|
|
177
|
+
height: metadata.height ?? null,
|
|
178
|
+
format: metadata.format ?? null
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function getPreprocessProfile(profile) {
|
|
182
|
+
return {
|
|
183
|
+
scale: profile?.scale ?? DEFAULT_SCALE,
|
|
184
|
+
thresholds: profile?.thresholds ?? [DEFAULT_THRESHOLD]
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/recognizer.ts
|
|
189
|
+
var DEFAULT_OCR = {
|
|
190
|
+
language: "eng",
|
|
191
|
+
whitelist: "0123456789+-*/=?xX\xD7\xF7",
|
|
192
|
+
pageSegMode: 7,
|
|
193
|
+
preserveInterwordSpaces: false
|
|
194
|
+
};
|
|
195
|
+
var CaptchaRecognizer = class {
|
|
196
|
+
constructor(profile, logger) {
|
|
197
|
+
this.profile = profile;
|
|
198
|
+
this.ocr = new TesseractOcr(
|
|
199
|
+
{
|
|
200
|
+
...DEFAULT_OCR,
|
|
201
|
+
...profile.ocr
|
|
202
|
+
},
|
|
203
|
+
logger
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
profile;
|
|
207
|
+
ocr;
|
|
208
|
+
async recognize(input, options = {}) {
|
|
209
|
+
const startedAt = Date.now();
|
|
210
|
+
const image = toImageBuffer(input);
|
|
211
|
+
const original = await loadOriginal(image);
|
|
212
|
+
const originalAttempt = await this.runAttempt(original);
|
|
213
|
+
if (originalAttempt.ok) {
|
|
214
|
+
return this.formatResult(originalAttempt, startedAt, options);
|
|
215
|
+
}
|
|
216
|
+
const preprocessProfile = getPreprocessProfile(this.profile.preprocess);
|
|
217
|
+
const thresholds = options.thresholds ?? preprocessProfile.thresholds;
|
|
218
|
+
const scale = options.scale ?? preprocessProfile.scale;
|
|
219
|
+
let lastAttempt = originalAttempt;
|
|
220
|
+
for (const threshold of thresholds) {
|
|
221
|
+
const prepared = await preprocessImage(image, { scale, threshold });
|
|
222
|
+
const attempt = await this.runAttempt(prepared);
|
|
223
|
+
lastAttempt = attempt;
|
|
224
|
+
if (attempt.ok) {
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return this.formatResult(lastAttempt, startedAt, options);
|
|
229
|
+
}
|
|
230
|
+
async close() {
|
|
231
|
+
await this.ocr.close();
|
|
232
|
+
}
|
|
233
|
+
async runAttempt(prepared) {
|
|
234
|
+
const ocrText = await this.ocr.recognize(prepared.buffer);
|
|
235
|
+
const parsed = this.profile.rule.parse(ocrText);
|
|
236
|
+
if (!parsed.ok) {
|
|
237
|
+
return {
|
|
238
|
+
prepared,
|
|
239
|
+
ocrText: ocrText.trim(),
|
|
240
|
+
normalizedText: parsed.normalized,
|
|
241
|
+
expression: null,
|
|
242
|
+
answer: null,
|
|
243
|
+
error: parsed.error,
|
|
244
|
+
ok: false
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
try {
|
|
248
|
+
return {
|
|
249
|
+
prepared,
|
|
250
|
+
ocrText: ocrText.trim(),
|
|
251
|
+
normalizedText: parsed.normalized,
|
|
252
|
+
expression: parsed.expression,
|
|
253
|
+
answer: this.profile.rule.evaluate(parsed.parsed),
|
|
254
|
+
error: null,
|
|
255
|
+
ok: true
|
|
256
|
+
};
|
|
257
|
+
} catch (error) {
|
|
258
|
+
return {
|
|
259
|
+
prepared,
|
|
260
|
+
ocrText: ocrText.trim(),
|
|
261
|
+
normalizedText: parsed.normalized,
|
|
262
|
+
expression: parsed.expression,
|
|
263
|
+
answer: null,
|
|
264
|
+
error: {
|
|
265
|
+
code: "EVALUATION_FAILED",
|
|
266
|
+
message: error instanceof Error ? error.message : "\u7B54\u6848\u8BA1\u7B97\u5931\u8D25"
|
|
267
|
+
},
|
|
268
|
+
ok: false
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
formatResult(attempt, startedAt, options) {
|
|
273
|
+
const result = {
|
|
274
|
+
ok: attempt.ok,
|
|
275
|
+
profileId: this.profile.id,
|
|
276
|
+
ocrText: attempt.ocrText,
|
|
277
|
+
normalizedText: attempt.normalizedText,
|
|
278
|
+
expression: attempt.expression,
|
|
279
|
+
answer: attempt.answer,
|
|
280
|
+
error: attempt.error,
|
|
281
|
+
ocrVariant: attempt.prepared.options.mode,
|
|
282
|
+
durationMs: Date.now() - startedAt,
|
|
283
|
+
source: attempt.prepared.source,
|
|
284
|
+
preprocess: attempt.prepared.options
|
|
285
|
+
};
|
|
286
|
+
if (options.includeProcessedImage) {
|
|
287
|
+
result.processedImage = attempt.prepared.buffer;
|
|
288
|
+
}
|
|
289
|
+
return result;
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
function createRecognizer(options) {
|
|
293
|
+
return new CaptchaRecognizer(options.profile, options.logger);
|
|
294
|
+
}
|
|
295
|
+
async function loadOriginal(input) {
|
|
296
|
+
const metadata = await (0, import_sharp2.default)(input, { failOn: "error" }).metadata();
|
|
297
|
+
return {
|
|
298
|
+
buffer: input,
|
|
299
|
+
source: getImageSource(metadata),
|
|
300
|
+
options: {
|
|
301
|
+
mode: "original",
|
|
302
|
+
scale: 1,
|
|
303
|
+
threshold: null
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/rules/arithmetic.ts
|
|
309
|
+
var DEFAULT_OPERATORS = {
|
|
310
|
+
"+": {
|
|
311
|
+
evaluate: (left, right) => left + right
|
|
312
|
+
},
|
|
313
|
+
"-": {
|
|
314
|
+
evaluate: (left, right) => left - right
|
|
315
|
+
},
|
|
316
|
+
"*": {
|
|
317
|
+
aliases: ["x", "X", "\xD7", "\xB7"],
|
|
318
|
+
evaluate: (left, right) => left * right
|
|
319
|
+
},
|
|
320
|
+
"/": {
|
|
321
|
+
aliases: ["\xF7"],
|
|
322
|
+
evaluate: (left, right) => left / right,
|
|
323
|
+
validate: (left, right) => right === 0 ? {
|
|
324
|
+
code: "DIVISION_BY_ZERO",
|
|
325
|
+
message: "\u9664\u6570\u4E0D\u80FD\u4E3A 0"
|
|
326
|
+
} : null
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
function createArithmeticRule(options = {}) {
|
|
330
|
+
const operators = options.operators ?? DEFAULT_OPERATORS;
|
|
331
|
+
const aliasToOperator = /* @__PURE__ */ new Map();
|
|
332
|
+
for (const [symbol, definition] of Object.entries(operators)) {
|
|
333
|
+
aliasToOperator.set(symbol, symbol);
|
|
334
|
+
for (const alias of definition.aliases ?? []) {
|
|
335
|
+
aliasToOperator.set(alias, symbol);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
const operatorPattern = Object.keys(operators).sort((left, right) => right.length - left.length).map(escapeRegExp).join("|");
|
|
339
|
+
const expressionPattern = new RegExp(
|
|
340
|
+
`^(\\d{1,6})(${operatorPattern})(\\d{1,6})(?:=)?\\??$`
|
|
341
|
+
);
|
|
342
|
+
const normalize = (text) => {
|
|
343
|
+
let normalized = text.normalize("NFKC").replace(/\s+/g, "");
|
|
344
|
+
for (const [alias, symbol] of [...aliasToOperator.entries()].sort(
|
|
345
|
+
([left], [right]) => right.length - left.length
|
|
346
|
+
)) {
|
|
347
|
+
normalized = normalized.split(alias).join(symbol);
|
|
348
|
+
}
|
|
349
|
+
normalized = normalized.replace(/[Oo]/g, "0").replace(/[Il|]/g, "1");
|
|
350
|
+
return normalized;
|
|
351
|
+
};
|
|
352
|
+
const parse = (text) => {
|
|
353
|
+
const normalized = normalize(text);
|
|
354
|
+
const match = normalized.match(expressionPattern);
|
|
355
|
+
if (!match) {
|
|
356
|
+
return {
|
|
357
|
+
ok: false,
|
|
358
|
+
normalized,
|
|
359
|
+
error: {
|
|
360
|
+
code: "EXPRESSION_NOT_MATCHED",
|
|
361
|
+
message: "\u65E0\u6CD5\u8BC6\u522B\u4E3A\u201C\u6570\u5B57 \u8FD0\u7B97\u7B26 \u6570\u5B57 =?\u201D\u683C\u5F0F"
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
const parsed = {
|
|
366
|
+
left: Number(match[1]),
|
|
367
|
+
operator: match[2],
|
|
368
|
+
right: Number(match[3])
|
|
369
|
+
};
|
|
370
|
+
const validationError = operators[parsed.operator].validate?.(parsed.left, parsed.right) ?? null;
|
|
371
|
+
if (validationError) {
|
|
372
|
+
return {
|
|
373
|
+
ok: false,
|
|
374
|
+
normalized,
|
|
375
|
+
error: validationError
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
ok: true,
|
|
380
|
+
normalized,
|
|
381
|
+
expression: `${parsed.left}${parsed.operator}${parsed.right}`,
|
|
382
|
+
parsed
|
|
383
|
+
};
|
|
384
|
+
};
|
|
385
|
+
return {
|
|
386
|
+
id: options.id ?? "arithmetic-v1",
|
|
387
|
+
normalize,
|
|
388
|
+
parse,
|
|
389
|
+
evaluate: (parsed) => operators[parsed.operator].evaluate(parsed.left, parsed.right)
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
var arithmeticRule = createArithmeticRule();
|
|
393
|
+
function normalizeOcrText(text) {
|
|
394
|
+
return arithmeticRule.normalize(text);
|
|
395
|
+
}
|
|
396
|
+
function parseExpression(text) {
|
|
397
|
+
const parsed = arithmeticRule.parse(text);
|
|
398
|
+
if (!parsed.ok) {
|
|
399
|
+
return {
|
|
400
|
+
ok: false,
|
|
401
|
+
normalized: parsed.normalized,
|
|
402
|
+
expression: null,
|
|
403
|
+
answer: null,
|
|
404
|
+
error: parsed.error
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
ok: true,
|
|
409
|
+
normalized: parsed.normalized,
|
|
410
|
+
expression: parsed.expression,
|
|
411
|
+
answer: arithmeticRule.evaluate(parsed.parsed),
|
|
412
|
+
error: null
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
function calculate(left, operator, right) {
|
|
416
|
+
const definition = DEFAULT_OPERATORS[operator];
|
|
417
|
+
if (!definition) {
|
|
418
|
+
throw new Error(`\u4E0D\u652F\u6301\u7684\u8FD0\u7B97\u7B26: ${operator}`);
|
|
419
|
+
}
|
|
420
|
+
const validationError = definition.validate?.(left, right);
|
|
421
|
+
if (validationError) {
|
|
422
|
+
throw new Error(validationError.message);
|
|
423
|
+
}
|
|
424
|
+
return definition.evaluate(left, right);
|
|
425
|
+
}
|
|
426
|
+
var arithmeticProfile = {
|
|
427
|
+
id: "arithmetic-v1",
|
|
428
|
+
ocr: {
|
|
429
|
+
language: "eng",
|
|
430
|
+
whitelist: "0123456789+-*/=?xX\xD7\xF7",
|
|
431
|
+
pageSegMode: 7,
|
|
432
|
+
preserveInterwordSpaces: false
|
|
433
|
+
},
|
|
434
|
+
preprocess: {
|
|
435
|
+
scale: 4,
|
|
436
|
+
thresholds: [160, 120, 180, 200]
|
|
437
|
+
},
|
|
438
|
+
rule: arithmeticRule
|
|
439
|
+
};
|
|
440
|
+
function escapeRegExp(value) {
|
|
441
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// src/index.ts
|
|
445
|
+
var defaultRecognizer;
|
|
446
|
+
function recognizeCaptcha(input, options = {}) {
|
|
447
|
+
if (!defaultRecognizer) {
|
|
448
|
+
defaultRecognizer = createRecognizer({ profile: arithmeticProfile });
|
|
449
|
+
}
|
|
450
|
+
return defaultRecognizer.recognize(input, options);
|
|
451
|
+
}
|
|
452
|
+
async function closeDefaultRecognizer() {
|
|
453
|
+
await defaultRecognizer?.close();
|
|
454
|
+
defaultRecognizer = void 0;
|
|
455
|
+
}
|
|
456
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
457
|
+
0 && (module.exports = {
|
|
458
|
+
CaptchaRecognizer,
|
|
459
|
+
DEFAULT_OPERATORS,
|
|
460
|
+
ImageInputError,
|
|
461
|
+
arithmeticProfile,
|
|
462
|
+
arithmeticRule,
|
|
463
|
+
calculate,
|
|
464
|
+
closeDefaultRecognizer,
|
|
465
|
+
createArithmeticRule,
|
|
466
|
+
createRecognizer,
|
|
467
|
+
getImageSource,
|
|
468
|
+
getPreprocessProfile,
|
|
469
|
+
normalizeOcrText,
|
|
470
|
+
parseExpression,
|
|
471
|
+
preprocessImage,
|
|
472
|
+
recognizeCaptcha,
|
|
473
|
+
toImageBuffer
|
|
474
|
+
});
|
|
475
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/recognizer.ts","../src/input.ts","../src/ocr.ts","../src/preprocess.ts","../src/rules/arithmetic.ts"],"sourcesContent":["import {\n CaptchaRecognizer,\n createRecognizer\n} from './recognizer.js';\nimport {\n arithmeticProfile,\n type ArithmeticExpression\n} from './rules/arithmetic.js';\nimport type {\n ImageInput,\n RecognitionResult,\n RecognizeOptions\n} from './types.js';\n\nexport * from './types.js';\nexport * from './input.js';\nexport * from './preprocess.js';\nexport * from './recognizer.js';\nexport * from './rules/arithmetic.js';\n\nexport { arithmeticProfile, createRecognizer, CaptchaRecognizer };\n\nlet defaultRecognizer:\n | CaptchaRecognizer<ArithmeticExpression, number>\n | undefined;\n\nexport function recognizeCaptcha(\n input: ImageInput,\n options: RecognizeOptions = {}\n): Promise<RecognitionResult<number>> {\n if (!defaultRecognizer) {\n defaultRecognizer = createRecognizer({ profile: arithmeticProfile });\n }\n\n return defaultRecognizer.recognize(input, options);\n}\n\nexport async function closeDefaultRecognizer(): Promise<void> {\n await defaultRecognizer?.close();\n defaultRecognizer = undefined;\n}\n","import sharp from 'sharp';\nimport { toImageBuffer } from './input.js';\nimport { TesseractOcr } from './ocr.js';\nimport {\n getImageSource,\n getPreprocessProfile,\n preprocessImage,\n type PreparedImage\n} from './preprocess.js';\nimport type {\n CaptchaProfile,\n ImageInput,\n OcrLogEntry,\n OcrProfile,\n RecognitionError,\n RecognitionResult,\n RecognizeOptions,\n RuleParseResult\n} from './types.js';\n\nconst DEFAULT_OCR: OcrProfile = {\n language: 'eng',\n whitelist: '0123456789+-*/=?xX×÷',\n pageSegMode: 7,\n preserveInterwordSpaces: false\n};\n\ninterface Attempt<TAnswer> {\n prepared: PreparedImage;\n ocrText: string;\n normalizedText: string;\n expression: string | null;\n answer: TAnswer | null;\n error: RecognitionError | null;\n ok: boolean;\n}\n\nexport class CaptchaRecognizer<TParsed, TAnswer> {\n private readonly ocr: TesseractOcr;\n\n constructor(\n public readonly profile: CaptchaProfile<TParsed, TAnswer>,\n logger?: (entry: OcrLogEntry) => void\n ) {\n this.ocr = new TesseractOcr(\n {\n ...DEFAULT_OCR,\n ...profile.ocr\n },\n logger\n );\n }\n\n async recognize(\n input: ImageInput,\n options: RecognizeOptions = {}\n ): Promise<RecognitionResult<TAnswer>> {\n const startedAt = Date.now();\n const image = toImageBuffer(input);\n const original = await loadOriginal(image);\n const originalAttempt = await this.runAttempt(original);\n\n if (originalAttempt.ok) {\n return this.formatResult(originalAttempt, startedAt, options);\n }\n\n const preprocessProfile = getPreprocessProfile(this.profile.preprocess);\n const thresholds = options.thresholds ?? preprocessProfile.thresholds;\n const scale = options.scale ?? preprocessProfile.scale;\n let lastAttempt = originalAttempt;\n\n for (const threshold of thresholds) {\n const prepared = await preprocessImage(image, { scale, threshold });\n const attempt = await this.runAttempt(prepared);\n lastAttempt = attempt;\n if (attempt.ok) {\n break;\n }\n }\n\n return this.formatResult(lastAttempt, startedAt, options);\n }\n\n async close(): Promise<void> {\n await this.ocr.close();\n }\n\n private async runAttempt(prepared: PreparedImage): Promise<Attempt<TAnswer>> {\n const ocrText = await this.ocr.recognize(prepared.buffer);\n const parsed = this.profile.rule.parse(ocrText);\n\n if (!parsed.ok) {\n return {\n prepared,\n ocrText: ocrText.trim(),\n normalizedText: parsed.normalized,\n expression: null,\n answer: null,\n error: parsed.error,\n ok: false\n };\n }\n\n try {\n return {\n prepared,\n ocrText: ocrText.trim(),\n normalizedText: parsed.normalized,\n expression: parsed.expression,\n answer: this.profile.rule.evaluate(parsed.parsed),\n error: null,\n ok: true\n };\n } catch (error) {\n return {\n prepared,\n ocrText: ocrText.trim(),\n normalizedText: parsed.normalized,\n expression: parsed.expression,\n answer: null,\n error: {\n code: 'EVALUATION_FAILED',\n message: error instanceof Error ? error.message : '答案计算失败'\n },\n ok: false\n };\n }\n }\n\n private formatResult(\n attempt: Attempt<TAnswer>,\n startedAt: number,\n options: RecognizeOptions\n ): RecognitionResult<TAnswer> {\n const result: RecognitionResult<TAnswer> = {\n ok: attempt.ok,\n profileId: this.profile.id,\n ocrText: attempt.ocrText,\n normalizedText: attempt.normalizedText,\n expression: attempt.expression,\n answer: attempt.answer,\n error: attempt.error,\n ocrVariant: attempt.prepared.options.mode,\n durationMs: Date.now() - startedAt,\n source: attempt.prepared.source,\n preprocess: attempt.prepared.options\n };\n\n if (options.includeProcessedImage) {\n result.processedImage = attempt.prepared.buffer;\n }\n\n return result;\n }\n}\n\nexport function createRecognizer<TParsed, TAnswer>(options: {\n profile: CaptchaProfile<TParsed, TAnswer>;\n logger?: (entry: OcrLogEntry) => void;\n}): CaptchaRecognizer<TParsed, TAnswer> {\n return new CaptchaRecognizer(options.profile, options.logger);\n}\n\nasync function loadOriginal(input: Buffer): Promise<PreparedImage> {\n const metadata = await sharp(input, { failOn: 'error' }).metadata();\n return {\n buffer: input,\n source: getImageSource(metadata),\n options: {\n mode: 'original',\n scale: 1,\n threshold: null\n }\n };\n}\n","import type { ImageInput } from './types.js';\n\nexport class ImageInputError extends Error {\n readonly code = 'INVALID_IMAGE_INPUT';\n\n constructor(message: string) {\n super(message);\n this.name = 'ImageInputError';\n }\n}\n\nexport function toImageBuffer(input: ImageInput): Buffer {\n if (Buffer.isBuffer(input)) {\n return input;\n }\n\n if (input instanceof Uint8Array) {\n return Buffer.from(input);\n }\n\n if (typeof input !== 'string') {\n throw new ImageInputError(\n '图片输入必须是 Buffer、Uint8Array、Data URL 或 Base64 字符串'\n );\n }\n\n const value = input.trim();\n const dataUrlMatch = value.match(\n /^data:image\\/(?:png|jpeg|webp);base64,([A-Za-z0-9+/=\\s]+)$/i\n );\n const payload = (dataUrlMatch?.[1] ?? value).replace(/\\s+/g, '');\n\n if (!/^[A-Za-z0-9+/]+={0,2}$/.test(payload) || payload.length % 4 === 1) {\n throw new ImageInputError('图片字符串不是有效的 Base64 或 Data URL');\n }\n\n const buffer = Buffer.from(payload, 'base64');\n if (buffer.length === 0) {\n throw new ImageInputError('图片输入为空');\n }\n\n return buffer;\n}\n","import { createWorker } from 'tesseract.js';\nimport type { OcrLogEntry, OcrProfile } from './types.js';\n\ninterface OcrWorker {\n setParameters(parameters: Record<string, string>): Promise<unknown>;\n recognize(image: Buffer): Promise<{ data?: { text?: string } }>;\n terminate(): Promise<unknown>;\n}\n\nexport class TesseractOcr {\n private workerPromise?: Promise<OcrWorker>;\n\n constructor(\n private readonly profile: OcrProfile,\n private readonly logger?: (entry: OcrLogEntry) => void\n ) {}\n\n private getWorker(): Promise<OcrWorker> {\n if (!this.workerPromise) {\n const workerOptions = this.logger\n ? {\n logger: (entry: unknown) => {\n this.logger?.(entry as OcrLogEntry);\n }\n }\n : {};\n\n this.workerPromise = createWorker(\n this.profile.language,\n 1,\n workerOptions\n )\n .then(async (worker) => {\n const typedWorker = worker as unknown as OcrWorker;\n await typedWorker.setParameters({\n tessedit_char_whitelist: this.profile.whitelist,\n tessedit_pageseg_mode: String(this.profile.pageSegMode),\n preserve_interword_spaces: this.profile.preserveInterwordSpaces\n ? '1'\n : '0'\n });\n return typedWorker;\n })\n .catch((error) => {\n this.workerPromise = undefined;\n throw error;\n });\n }\n\n return this.workerPromise;\n }\n\n async recognize(image: Buffer): Promise<string> {\n const worker = await this.getWorker();\n const result = await worker.recognize(image);\n return result.data?.text ?? '';\n }\n\n async close(): Promise<void> {\n if (!this.workerPromise) {\n return;\n }\n\n const worker = await this.workerPromise;\n await worker.terminate();\n this.workerPromise = undefined;\n }\n}\n","import sharp, { type Metadata } from 'sharp';\nimport type {\n ImageSource,\n PreprocessInfo,\n PreprocessProfile\n} from './types.js';\n\nconst DEFAULT_SCALE = 4;\nconst DEFAULT_THRESHOLD = 160;\n\nexport interface PreparedImage {\n buffer: Buffer;\n source: ImageSource;\n options: PreprocessInfo;\n}\n\nexport async function preprocessImage(\n input: Buffer,\n options: { scale?: number; threshold?: number } = {}\n): Promise<PreparedImage> {\n const scale = options.scale ?? DEFAULT_SCALE;\n const threshold = options.threshold ?? DEFAULT_THRESHOLD;\n\n if (scale < 1 || scale > 10) {\n throw new RangeError('scale must be between 1 and 10');\n }\n if (threshold < 1 || threshold > 254) {\n throw new RangeError('threshold must be between 1 and 254');\n }\n\n const source = sharp(input, { failOn: 'error' });\n const metadata = await source.metadata();\n const width = metadata.width ? Math.round(metadata.width * scale) : undefined;\n const height = metadata.height\n ? Math.round(metadata.height * scale)\n : undefined;\n\n const buffer = await source\n .rotate()\n .resize({\n width,\n height,\n fit: 'fill',\n kernel: sharp.kernel.lanczos3\n })\n .flatten({ background: '#ffffff' })\n .grayscale()\n .threshold(threshold)\n .png()\n .toBuffer();\n\n return {\n buffer,\n source: getImageSource(metadata),\n options: {\n mode: 'threshold',\n scale,\n threshold\n }\n };\n}\n\nexport function getImageSource(\n metadata: Metadata\n): ImageSource {\n return {\n width: metadata.width ?? null,\n height: metadata.height ?? null,\n format: metadata.format ?? null\n };\n}\n\nexport function getPreprocessProfile(\n profile: PreprocessProfile | undefined\n): Required<PreprocessProfile> {\n return {\n scale: profile?.scale ?? DEFAULT_SCALE,\n thresholds: profile?.thresholds ?? [DEFAULT_THRESHOLD]\n };\n}\n","import type {\n CaptchaRule,\n CaptchaProfile,\n RecognitionError,\n RuleParseResult\n} from '../types.js';\n\nexport interface ArithmeticExpression {\n left: number;\n operator: string;\n right: number;\n}\n\nexport interface ArithmeticOperator {\n aliases?: string[];\n evaluate(left: number, right: number): number;\n validate?(left: number, right: number): RecognitionError | null;\n}\n\nexport const DEFAULT_OPERATORS: Record<string, ArithmeticOperator> = {\n '+': {\n evaluate: (left, right) => left + right\n },\n '-': {\n evaluate: (left, right) => left - right\n },\n '*': {\n aliases: ['x', 'X', '×', '·'],\n evaluate: (left, right) => left * right\n },\n '/': {\n aliases: ['÷'],\n evaluate: (left, right) => left / right,\n validate: (left, right) =>\n right === 0\n ? {\n code: 'DIVISION_BY_ZERO',\n message: '除数不能为 0'\n }\n : null\n }\n};\n\nexport interface ArithmeticRuleOptions {\n id?: string;\n whitelist?: string;\n operators?: Record<string, ArithmeticOperator>;\n}\n\nexport function createArithmeticRule(\n options: ArithmeticRuleOptions = {}\n): CaptchaRule<ArithmeticExpression, number> {\n const operators = options.operators ?? DEFAULT_OPERATORS;\n const aliasToOperator = new Map<string, string>();\n\n for (const [symbol, definition] of Object.entries(operators)) {\n aliasToOperator.set(symbol, symbol);\n for (const alias of definition.aliases ?? []) {\n aliasToOperator.set(alias, symbol);\n }\n }\n\n const operatorPattern = Object.keys(operators)\n .sort((left, right) => right.length - left.length)\n .map(escapeRegExp)\n .join('|');\n const expressionPattern = new RegExp(\n `^(\\\\d{1,6})(${operatorPattern})(\\\\d{1,6})(?:=)?\\\\??$`\n );\n\n const normalize = (text: string): string => {\n let normalized = text.normalize('NFKC').replace(/\\s+/g, '');\n for (const [alias, symbol] of [...aliasToOperator.entries()].sort(\n ([left], [right]) => right.length - left.length\n )) {\n normalized = normalized.split(alias).join(symbol);\n }\n normalized = normalized.replace(/[Oo]/g, '0').replace(/[Il|]/g, '1');\n return normalized;\n };\n\n const parse = (text: string): RuleParseResult<ArithmeticExpression> => {\n const normalized = normalize(text);\n const match = normalized.match(expressionPattern);\n\n if (!match) {\n return {\n ok: false,\n normalized,\n error: {\n code: 'EXPRESSION_NOT_MATCHED',\n message: '无法识别为“数字 运算符 数字 =?”格式'\n }\n };\n }\n\n const parsed: ArithmeticExpression = {\n left: Number(match[1]),\n operator: match[2],\n right: Number(match[3])\n };\n const validationError =\n operators[parsed.operator].validate?.(parsed.left, parsed.right) ?? null;\n\n if (validationError) {\n return {\n ok: false,\n normalized,\n error: validationError\n };\n }\n\n return {\n ok: true,\n normalized,\n expression: `${parsed.left}${parsed.operator}${parsed.right}`,\n parsed\n };\n };\n\n return {\n id: options.id ?? 'arithmetic-v1',\n normalize,\n parse,\n evaluate: (parsed) =>\n operators[parsed.operator].evaluate(parsed.left, parsed.right)\n };\n}\n\nexport const arithmeticRule = createArithmeticRule();\n\nexport interface LegacyExpressionSuccess {\n ok: true;\n normalized: string;\n expression: string;\n answer: number;\n error: null;\n}\n\nexport interface LegacyExpressionFailure {\n ok: false;\n normalized: string;\n expression: null;\n answer: null;\n error: RecognitionError;\n}\n\nexport type LegacyExpressionResult =\n | LegacyExpressionSuccess\n | LegacyExpressionFailure;\n\nexport function normalizeOcrText(text: string): string {\n return arithmeticRule.normalize(text);\n}\n\nexport function parseExpression(text: string): LegacyExpressionResult {\n const parsed = arithmeticRule.parse(text);\n if (!parsed.ok) {\n return {\n ok: false,\n normalized: parsed.normalized,\n expression: null,\n answer: null,\n error: parsed.error\n };\n }\n\n return {\n ok: true,\n normalized: parsed.normalized,\n expression: parsed.expression,\n answer: arithmeticRule.evaluate(parsed.parsed),\n error: null\n };\n}\n\nexport function calculate(\n left: number,\n operator: string,\n right: number\n): number {\n const definition = DEFAULT_OPERATORS[operator];\n if (!definition) {\n throw new Error(`不支持的运算符: ${operator}`);\n }\n const validationError = definition.validate?.(left, right);\n if (validationError) {\n throw new Error(validationError.message);\n }\n return definition.evaluate(left, right);\n}\n\nexport const arithmeticProfile: CaptchaProfile<\n ArithmeticExpression,\n number\n> = {\n id: 'arithmetic-v1',\n ocr: {\n language: 'eng',\n whitelist: '0123456789+-*/=?xX×÷',\n pageSegMode: 7,\n preserveInterwordSpaces: false\n },\n preprocess: {\n scale: 4,\n thresholds: [160, 120, 180, 200]\n },\n rule: arithmeticRule\n};\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n"],"mappings":"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","names":["import_sharp","sharp","sharp"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { C as CaptchaProfile, O as OcrLogEntry, I as ImageInput, R as RecognizeOptions, a as RecognitionResult, b as ImageSource, P as PreprocessInfo, c as PreprocessProfile } from './arithmetic-CUs9apP8.cjs';
|
|
2
|
+
export { A as ArithmeticExpression, d as ArithmeticOperator, e as ArithmeticRuleOptions, f as CaptchaRule, D as DEFAULT_OPERATORS, L as LegacyExpressionFailure, g as LegacyExpressionResult, h as LegacyExpressionSuccess, i as OcrProfile, j as RecognitionError, k as RuleParseFailure, l as RuleParseResult, m as RuleParseSuccess, n as arithmeticProfile, o as arithmeticRule, p as calculate, q as createArithmeticRule, r as normalizeOcrText, s as parseExpression } from './arithmetic-CUs9apP8.cjs';
|
|
3
|
+
import { Metadata } from 'sharp';
|
|
4
|
+
|
|
5
|
+
declare class CaptchaRecognizer<TParsed, TAnswer> {
|
|
6
|
+
readonly profile: CaptchaProfile<TParsed, TAnswer>;
|
|
7
|
+
private readonly ocr;
|
|
8
|
+
constructor(profile: CaptchaProfile<TParsed, TAnswer>, logger?: (entry: OcrLogEntry) => void);
|
|
9
|
+
recognize(input: ImageInput, options?: RecognizeOptions): Promise<RecognitionResult<TAnswer>>;
|
|
10
|
+
close(): Promise<void>;
|
|
11
|
+
private runAttempt;
|
|
12
|
+
private formatResult;
|
|
13
|
+
}
|
|
14
|
+
declare function createRecognizer<TParsed, TAnswer>(options: {
|
|
15
|
+
profile: CaptchaProfile<TParsed, TAnswer>;
|
|
16
|
+
logger?: (entry: OcrLogEntry) => void;
|
|
17
|
+
}): CaptchaRecognizer<TParsed, TAnswer>;
|
|
18
|
+
|
|
19
|
+
declare class ImageInputError extends Error {
|
|
20
|
+
readonly code = "INVALID_IMAGE_INPUT";
|
|
21
|
+
constructor(message: string);
|
|
22
|
+
}
|
|
23
|
+
declare function toImageBuffer(input: ImageInput): Buffer;
|
|
24
|
+
|
|
25
|
+
interface PreparedImage {
|
|
26
|
+
buffer: Buffer;
|
|
27
|
+
source: ImageSource;
|
|
28
|
+
options: PreprocessInfo;
|
|
29
|
+
}
|
|
30
|
+
declare function preprocessImage(input: Buffer, options?: {
|
|
31
|
+
scale?: number;
|
|
32
|
+
threshold?: number;
|
|
33
|
+
}): Promise<PreparedImage>;
|
|
34
|
+
declare function getImageSource(metadata: Metadata): ImageSource;
|
|
35
|
+
declare function getPreprocessProfile(profile: PreprocessProfile | undefined): Required<PreprocessProfile>;
|
|
36
|
+
|
|
37
|
+
declare function recognizeCaptcha(input: ImageInput, options?: RecognizeOptions): Promise<RecognitionResult<number>>;
|
|
38
|
+
declare function closeDefaultRecognizer(): Promise<void>;
|
|
39
|
+
|
|
40
|
+
export { CaptchaProfile, CaptchaRecognizer, ImageInput, ImageInputError, ImageSource, OcrLogEntry, type PreparedImage, PreprocessInfo, PreprocessProfile, RecognitionResult, RecognizeOptions, closeDefaultRecognizer, createRecognizer, getImageSource, getPreprocessProfile, preprocessImage, recognizeCaptcha, toImageBuffer };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { C as CaptchaProfile, O as OcrLogEntry, I as ImageInput, R as RecognizeOptions, a as RecognitionResult, b as ImageSource, P as PreprocessInfo, c as PreprocessProfile } from './arithmetic-CUs9apP8.js';
|
|
2
|
+
export { A as ArithmeticExpression, d as ArithmeticOperator, e as ArithmeticRuleOptions, f as CaptchaRule, D as DEFAULT_OPERATORS, L as LegacyExpressionFailure, g as LegacyExpressionResult, h as LegacyExpressionSuccess, i as OcrProfile, j as RecognitionError, k as RuleParseFailure, l as RuleParseResult, m as RuleParseSuccess, n as arithmeticProfile, o as arithmeticRule, p as calculate, q as createArithmeticRule, r as normalizeOcrText, s as parseExpression } from './arithmetic-CUs9apP8.js';
|
|
3
|
+
import { Metadata } from 'sharp';
|
|
4
|
+
|
|
5
|
+
declare class CaptchaRecognizer<TParsed, TAnswer> {
|
|
6
|
+
readonly profile: CaptchaProfile<TParsed, TAnswer>;
|
|
7
|
+
private readonly ocr;
|
|
8
|
+
constructor(profile: CaptchaProfile<TParsed, TAnswer>, logger?: (entry: OcrLogEntry) => void);
|
|
9
|
+
recognize(input: ImageInput, options?: RecognizeOptions): Promise<RecognitionResult<TAnswer>>;
|
|
10
|
+
close(): Promise<void>;
|
|
11
|
+
private runAttempt;
|
|
12
|
+
private formatResult;
|
|
13
|
+
}
|
|
14
|
+
declare function createRecognizer<TParsed, TAnswer>(options: {
|
|
15
|
+
profile: CaptchaProfile<TParsed, TAnswer>;
|
|
16
|
+
logger?: (entry: OcrLogEntry) => void;
|
|
17
|
+
}): CaptchaRecognizer<TParsed, TAnswer>;
|
|
18
|
+
|
|
19
|
+
declare class ImageInputError extends Error {
|
|
20
|
+
readonly code = "INVALID_IMAGE_INPUT";
|
|
21
|
+
constructor(message: string);
|
|
22
|
+
}
|
|
23
|
+
declare function toImageBuffer(input: ImageInput): Buffer;
|
|
24
|
+
|
|
25
|
+
interface PreparedImage {
|
|
26
|
+
buffer: Buffer;
|
|
27
|
+
source: ImageSource;
|
|
28
|
+
options: PreprocessInfo;
|
|
29
|
+
}
|
|
30
|
+
declare function preprocessImage(input: Buffer, options?: {
|
|
31
|
+
scale?: number;
|
|
32
|
+
threshold?: number;
|
|
33
|
+
}): Promise<PreparedImage>;
|
|
34
|
+
declare function getImageSource(metadata: Metadata): ImageSource;
|
|
35
|
+
declare function getPreprocessProfile(profile: PreprocessProfile | undefined): Required<PreprocessProfile>;
|
|
36
|
+
|
|
37
|
+
declare function recognizeCaptcha(input: ImageInput, options?: RecognizeOptions): Promise<RecognitionResult<number>>;
|
|
38
|
+
declare function closeDefaultRecognizer(): Promise<void>;
|
|
39
|
+
|
|
40
|
+
export { CaptchaProfile, CaptchaRecognizer, ImageInput, ImageInputError, ImageSource, OcrLogEntry, type PreparedImage, PreprocessInfo, PreprocessProfile, RecognitionResult, RecognizeOptions, closeDefaultRecognizer, createRecognizer, getImageSource, getPreprocessProfile, preprocessImage, recognizeCaptcha, toImageBuffer };
|