/** * Baidu OCR Provider * 特点:准确率高、速度快、国内访问快 * 缺点:需要付费 (有免费额度)、需要网络连接 * 官方文档: https://cloud.baidu.com/doc/OCR/index.html */ import { BaseOCRProvider, IImageSource, OCRRecognitionResult, OCRProviderConfig } from './base.provider'; import fs from 'fs'; interface BaiduOCRResponse { words_result: Array<{ words: string; probability?: { average: number; variance: number; min: number; }; }>; error_code?: number; error_msg?: string; } interface BaiduTokenResponse { access_token: string; expires_in: number; } export class BaiduProvider extends BaseOCRProvider { private apiKey: string; private secretKey: string; private accessToken: string | null = null; private tokenExpireTime: number = 0; private apiUrl = 'https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic'; constructor(config: OCRProviderConfig & { apiKey?: string; secretKey?: string } = {}) { super(config); this.apiKey = config.apiKey || process.env.BAIDU_OCR_API_KEY || ''; this.secretKey = config.secretKey || process.env.BAIDU_OCR_SECRET_KEY || ''; } getName(): string { return 'Baidu OCR'; } getType(): 'local' | 'cloud' { return 'cloud'; } /** * 检查配置是否完整 */ async isAvailable(): Promise { return !!(this.apiKey && this.secretKey); } /** * 获取访问令牌 */ private async getAccessToken(): Promise { // 检查缓存 if (this.accessToken && Date.now() < this.tokenExpireTime) { return this.accessToken; } const url = `https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=${this.apiKey}&client_secret=${this.secretKey}`; const response = await fetch(url); const data = (await response.json()) as BaiduTokenResponse; if (!data.access_token) { throw new Error('获取百度 OCR access_token 失败'); } this.accessToken = data.access_token; this.tokenExpireTime = Date.now() + (data.expires_in - 300) * 1000; // 提前 5 分钟过期 return this.accessToken; } /** * 执行 OCR 识别 */ async recognize( source: IImageSource, options?: OCRProviderConfig ): Promise { const startTime = Date.now(); // 检查配置 const available = await this.isAvailable(); if (!available) { throw new Error('百度 OCR 未配置。请设置 BAIDU_OCR_API_KEY 和 BAIDU_OCR_SECRET_KEY 环境变量'); } // 获取图片 Base64 const imageBase64 = await this.getImageBase64(source); // 获取访问令牌 const token = await this.getAccessToken(); // 调用 OCR API const response = await this.withTimeout( fetch(`${this.apiUrl}?access_token=${token}`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: `image=${encodeURIComponent(imageBase64)}`, }), options?.timeout || this.config.timeout || 10000 ); const data = (await response.json()) as BaiduOCRResponse; const duration = Date.now() - startTime; // 检查错误 if (data.error_code) { throw new Error(`百度 OCR 错误: ${data.error_msg} (${data.error_code})`); } // 提取文本和置信度 const words = data.words_result || []; const text = words.map((w) => w.words).join('\n'); // 计算平均置信度 let confidence = 0.9; // 默认置信度 if (words.length > 0 && words[0].probability) { const sum = words.reduce((acc, w) => acc + (w.probability?.average || 0), 0); confidence = (sum / words.length) / 100; } return { text: text.trim(), confidence, duration, extra: { provider: 'baidu', wordCount: words.length, }, }; } getRecommendations() { return { maxImageSize: 4 * 1024 * 1024, // 4MB 限制 supportedFormats: ['jpg', 'jpeg', 'png', 'bmp'], notes: '百度 OCR 标准版 QPS 限制为 2,每日免费额度 1000 次。适合高精度需求场景。', }; } /** * 获取图片 Base64 */ private async getImageBase64(source: IImageSource): Promise { if (source.base64) { // 移除 data URL 前缀 return source.base64.replace(/^data:image\/\w+;base64,/, ''); } if (source.buffer) { return source.buffer.toString('base64'); } if (source.path) { // 使用基类的路径解析方法 const fullPath = this.resolveImagePath(source.path); const buffer = fs.readFileSync(fullPath); return buffer.toString('base64'); } throw new Error('无效的图片来源'); } /** * 超时包装 */ private async withTimeout(promise: Promise, timeout: number): Promise { return Promise.race([ promise, new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout) ), ]); } } // 导出单例实例 export const baiduProvider = new BaiduProvider();