tencent_ocr_client.dart
4.89 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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
import 'dart:convert';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:dio/dio.dart';
/// 腾讯云通用印刷体识别客户端。
///
/// 接口说明:https://cloud.tencent.com/document/product/866/33526
class TencentOcrClient {
TencentOcrClient({
required this.secretId,
required this.secretKey,
});
final String secretId;
final String secretKey;
static const String _host = 'ocr.tencentcloudapi.com';
static const String _service = 'ocr';
static const String _action = 'GeneralBasicOCR';
static const String _version = '2018-11-19';
final Dio _dio = Dio(
BaseOptions(
connectTimeout: const Duration(seconds: 15),
receiveTimeout: const Duration(seconds: 30),
responseType: ResponseType.json,
),
);
/// 识别图片中的文字并返回多行文本。
Future<String> recognize(Uint8List imageBytes) async {
if (imageBytes.isEmpty) {
throw const TencentOcrException(0, '图片内容为空');
}
final String payload = jsonEncode(<String, dynamic>{
'ImageBase64': base64Encode(imageBytes),
'LanguageType': 'zh',
});
final int timestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000;
final String date = _utcDate(timestamp);
final String authorization = _authorization(timestamp, date, payload);
final Response<Map<String, dynamic>> response;
try {
response = await _dio.post<Map<String, dynamic>>(
'https://$_host',
data: payload,
options: Options(
headers: <String, String>{
'Authorization': authorization,
'Content-Type': 'application/json; charset=utf-8',
'Host': _host,
'X-TC-Action': _action,
'X-TC-Version': _version,
'X-TC-Timestamp': '$timestamp',
'X-TC-Region': 'ap-guangzhou',
},
contentType: 'application/json; charset=utf-8',
),
);
} on DioException catch (error) {
throw TencentOcrException(
error.response?.statusCode ?? 0,
'图片识别网络请求失败,请检查网络',
);
}
final Map<String, dynamic>? responseMap = response.data?['Response'] as Map<String, dynamic>?;
if (responseMap == null) {
throw TencentOcrException(response.statusCode ?? 0, '图片识别返回数据异常');
}
final Object? errorObj = responseMap['Error'];
if (errorObj is Map) {
throw TencentOcrException(
errorObj['Code']?.toString().hashCode ?? 0,
errorObj['Message']?.toString() ?? '图片识别失败',
);
}
final List<dynamic>? detections = responseMap['TextDetections'] as List<dynamic>?;
if (detections == null) {
return '';
}
final List<String> lines = <String>[];
for (final dynamic item in detections) {
if (item is Map) {
final String text = item['DetectedText']?.toString() ?? '';
if (text.trim().isNotEmpty) {
lines.add(text.trim());
}
}
}
return lines.join('\n');
}
String _authorization(int timestamp, String date, String payload) {
final String canonicalRequest = <String>[
'POST',
'/',
'',
'content-type:application/json; charset=utf-8',
'host:$_host',
'',
'content-type;host',
_sha256Hex(payload),
].join('\n');
final String credentialScope = '$date/$_service/tc3_request';
final String stringToSign = <String>[
'TC3-HMAC-SHA256',
'$timestamp',
credentialScope,
_sha256Hex(canonicalRequest),
].join('\n');
final List<int> secretDate = _hmacSha256(
utf8.encode('TC3$secretKey'),
date,
);
final List<int> secretService = _hmacSha256(secretDate, _service);
final List<int> secretSigning = _hmacSha256(secretService, 'tc3_request');
final String signature = _hexEncode(
_hmacSha256(secretSigning, stringToSign),
);
return 'TC3-HMAC-SHA256 '
'Credential=$secretId/$credentialScope, '
'SignedHeaders=content-type;host, '
'Signature=$signature';
}
String _utcDate(int timestamp) {
final DateTime utc =
DateTime.fromMillisecondsSinceEpoch(timestamp * 1000, isUtc: true);
final String month = utc.month.toString().padLeft(2, '0');
final String day = utc.day.toString().padLeft(2, '0');
return '${utc.year}-$month-$day';
}
List<int> _hmacSha256(List<int> key, String data) {
return Hmac(sha256, key).convert(utf8.encode(data)).bytes;
}
String _sha256Hex(String data) {
return sha256.convert(utf8.encode(data)).toString();
}
String _hexEncode(List<int> bytes) {
return bytes
.map((int byte) => byte.toRadixString(16).padLeft(2, '0'))
.join();
}
}
class TencentOcrException implements Exception {
const TencentOcrException(this.code, this.message);
final int code;
final String message;
@override
String toString() => 'TencentOcrException($code, $message)';
}