KMOCROperation.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. //
  2. // KMOCROperation.m
  3. // PDF Reader Pro Edition
  4. //
  5. // Created by 万军 on 2020/3/23.
  6. //
  7. #import "KMOCROperation.h"
  8. #import "KMGOCRManager.h"
  9. #import <AppKit/AppKit.h>
  10. #define KMOCR_API_URL @"https://api.convertio.co/convert"
  11. #if VERSION_FREE
  12. #define KMOCR_API_KEY @"d1a0c1a4cc7fcac5eb08730570217f97"
  13. #else
  14. #define KMOCR_API_KEY @"d1a0c1a4cc7fcac5eb08730570217f97"
  15. #endif
  16. #define KMTIMER_MAXIMUM_CYCLE 10
  17. @interface KMOCROperation ()
  18. @property (assign, nonatomic, getter = isExecuting) BOOL executing;
  19. @property (assign, nonatomic, getter = isFinished) BOOL finished;
  20. @property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
  21. @property (nonatomic, assign) NSInteger timerCycleCount;
  22. @property (nonatomic, assign) NSInteger imageIndex;
  23. @property (nonatomic, strong) NSURLSessionDataTask *task;
  24. @property (nonatomic, strong) NSString *fileID;
  25. @property (nonatomic, strong) NSString *fileName;
  26. @property (nonatomic, strong) NSImage *orcImage;
  27. @property (nonatomic, strong) NSURL *fileURL;
  28. @end
  29. @implementation KMOCROperation
  30. - (void)dealloc {
  31. }
  32. - (instancetype)initWithRecognitionImage:(NSImage *)image withImageIndex:(NSInteger)imageIndex {
  33. self = [super init];
  34. if (self) {
  35. self.timerCycleCount = 0;
  36. self.orcImage = image;
  37. self.imageIndex = imageIndex;
  38. self.fileName = [self fileNameWithDate];
  39. self.fileID = nil;
  40. self.fileURL = nil;
  41. self.queuePriority = NSOperationQueuePriorityNormal;
  42. self.name = self.fileName;
  43. self.executing = NO;
  44. self.finished = NO;
  45. }
  46. return self;
  47. }
  48. #pragma mark - overwrite
  49. - (void)start {
  50. if ([self p_checkCancelled]) {
  51. return;
  52. }
  53. self.executing = YES;
  54. [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
  55. }
  56. - (void)main {
  57. @try {
  58. if ([self p_checkCancelled]) {
  59. return;
  60. }
  61. [self recognitionImage:_orcImage];
  62. while (self.executing) {
  63. if ([self p_checkCancelled]) {
  64. return;
  65. }
  66. }
  67. }
  68. @catch (NSException * e) {
  69. NSLog(@"Exception %@", e);
  70. }
  71. }
  72. - (void)cancel
  73. {
  74. [super cancel];
  75. if (self.task) {
  76. [self.task cancel];
  77. self.task = nil;
  78. }
  79. if ([self.operationDelegate respondsToSelector:@selector(OCROperation:cancelOCRImageAtIndex:)]) {
  80. [self.operationDelegate OCROperation:self cancelOCRImageAtIndex:_imageIndex];
  81. }
  82. if (self.executing) {
  83. self.executing = NO;
  84. self.finished = YES;
  85. } else {
  86. self.finished = NO;
  87. }
  88. self.cancelled = YES;
  89. }
  90. #pragma mark - private methods
  91. - (void)p_done
  92. {
  93. self.executing = NO;
  94. self.finished = YES;
  95. }
  96. - (BOOL)p_checkCancelled
  97. {
  98. if (self.cancelled) {
  99. self.finished = YES;
  100. return YES;
  101. }
  102. return NO;
  103. }
  104. - (void)recognitionImage:(NSImage *)image {
  105. __weak typeof(self)weakSelf = self;
  106. NSString *binaryImageData = [self base64EncodeImage:image];
  107. if (!binaryImageData) {
  108. return;
  109. }
  110. NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:KMOCR_API_URL]];
  111. [request setHTTPMethod: @"POST"];
  112. [request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
  113. NSDictionary *paramsDictionary = @{@"apikey":KMOCR_API_KEY,
  114. @"input":@"base64",
  115. @"file":binaryImageData,
  116. @"outputformat":self.fileType,
  117. @"filename":@"1.pdf",
  118. @"options":@{@"ocr_enabled":@(true),
  119. @"ocr_settings":@{@"langs":self.selectedLanguages}}};
  120. NSError *error;
  121. NSData *requestData = [NSJSONSerialization dataWithJSONObject:paramsDictionary options:0 error:&error];
  122. [request setHTTPBody:requestData];
  123. NSURLSession *URLSession = [NSURLSession sharedSession];
  124. NSURLSessionDataTask *convertTask = [URLSession dataTaskWithRequest:request completionHandler:^ (NSData *data, NSURLResponse *response, NSError *error) {
  125. if (!data) {
  126. return;
  127. }
  128. if (NSURLErrorCancelled == error.code) {
  129. return ;
  130. }
  131. NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
  132. NSString *statusStr = dictionary[@"status"];
  133. NSLog(@"dictionary == %@",dictionary);
  134. if ([statusStr isEqualToString:@"ok"]) {
  135. weakSelf.fileID = dictionary[@"data"][@"id"];
  136. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(15.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  137. [weakSelf requestQuery];
  138. });
  139. } else {
  140. [weakSelf cancel];
  141. // NSError *err = [self errorWithContent:dictionary[@"error"]];
  142. if ([weakSelf.operationDelegate respondsToSelector:@selector(OCROperation:failureOCRImageAtIndex:error:)]) {
  143. [weakSelf.operationDelegate OCROperation:weakSelf failureOCRImageAtIndex:weakSelf.imageIndex error:error];
  144. }
  145. }
  146. }];
  147. [convertTask resume];
  148. }
  149. #pragma mark - Private Methods
  150. - (NSString *)fileNameWithDate
  151. {
  152. NSDateFormatter *formatter = [[NSDateFormatter alloc ] init];
  153. [formatter setDateFormat:@"YYYY-MM-dd-hh-mm-ss-SSS"];
  154. NSString *dateString = [formatter stringFromDate:[NSDate date]];
  155. NSString *fileName = [NSString stringWithFormat:@"%@ %ld",dateString, (long)_imageIndex];
  156. return fileName;
  157. }
  158. - (NSData *)compressImageData:(NSData *)imageData toMaxFileSize:(NSInteger)maxFileSize {
  159. CGFloat compression = 0.9f;
  160. CGFloat maxCompression = 0.1f;
  161. NSData *compressImageData = imageData;
  162. while ([imageData length] > maxFileSize && compression > maxCompression) {
  163. compression -= 0.1;
  164. NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:imageData];
  165. compressImageData = [imageRep representationUsingType:NSJPEGFileType
  166. properties:@{NSImageCompressionFactor:@(compression)}];
  167. }
  168. return compressImageData;
  169. }
  170. - (NSString *)base64EncodeImage:(NSImage *)image {
  171. NSData *data = [image TIFFRepresentation];
  172. NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:data];
  173. [imageRep setSize:[image size]];
  174. NSData *imagedata = [imageRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
  175. // Resize the image if it exceeds the 4MB API limit
  176. if ([imagedata length] > 4194304) {
  177. imagedata = [self compressImageData:imagedata toMaxFileSize:4194304];
  178. }
  179. NSString *base64String = nil;
  180. if ([imagedata respondsToSelector:@selector(base64EncodedStringWithOptions:)]) {
  181. base64String = [imagedata base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithCarriageReturn];
  182. } else {
  183. base64String = [imagedata base64Encoding];
  184. }
  185. return base64String;
  186. }
  187. - (NSError *)errorWithContent:(NSString *)content {
  188. NSError *error;
  189. NSString *domain = @"com.MyCompany.MyApplication.ErrorDomain";
  190. NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : content };
  191. error = [NSError errorWithDomain:domain code:-101 userInfo:userInfo];
  192. return error;
  193. }
  194. #pragma mark - NSTimer
  195. - (void)requestQuery {
  196. __weak typeof(self)weakSelf = self;
  197. NSString *urlString = [NSString stringWithFormat:@"%@/%@/status",KMOCR_API_URL,_fileID];
  198. NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
  199. [request setHTTPMethod: @"GET"];
  200. NSURLSession *URLSession = [NSURLSession sharedSession];
  201. self.task = [URLSession dataTaskWithRequest:request completionHandler:^ (NSData *data, NSURLResponse *response, NSError *error) {
  202. if (NSURLErrorCancelled == error.code) {
  203. return ;
  204. }
  205. NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
  206. NSString *statusStr = dictionary[@"status"];
  207. NSLog(@"dictionary == %@",dictionary);
  208. weakSelf.fileID = dictionary[@"data"][@"id"];
  209. if ([statusStr isEqualToString:@"ok"]) {
  210. if (weakSelf.task) {
  211. [weakSelf.task cancel];
  212. weakSelf.task = nil;
  213. }
  214. NSDictionary *fileData = dictionary[@"data"];
  215. if ([fileData[@"step_percent"] intValue] == 100) {
  216. weakSelf.fileURL = [NSURL URLWithString:fileData[@"output"][@"url"]];
  217. [weakSelf getResultFileContent];
  218. } else {
  219. if (weakSelf.timerCycleCount >= KMTIMER_MAXIMUM_CYCLE) {
  220. [weakSelf cancel];
  221. // NSError *err = [self errorWithContent:@"Network Timeout"];
  222. if ([weakSelf.operationDelegate respondsToSelector:@selector(OCROperation:failureOCRImageAtIndex:error:)]) {
  223. [weakSelf.operationDelegate OCROperation:weakSelf failureOCRImageAtIndex:weakSelf.imageIndex error:error];
  224. }
  225. } else {
  226. weakSelf.timerCycleCount++;
  227. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  228. [weakSelf requestQuery];
  229. });
  230. }
  231. }
  232. } else {
  233. [weakSelf cancel];
  234. // NSError *err = [self errorWithContent:dictionary[@"error"]];
  235. if ([weakSelf.operationDelegate respondsToSelector:@selector(OCROperation:failureOCRImageAtIndex:error:)]) {
  236. [weakSelf.operationDelegate OCROperation:weakSelf failureOCRImageAtIndex:weakSelf.imageIndex error:error];
  237. }
  238. }
  239. }];
  240. [self.task resume];
  241. }
  242. #pragma mark - Get Result File Content
  243. - (void)getResultFileContent {
  244. __weak typeof(self)weakSelf = self;
  245. NSString *urlString = [NSString stringWithFormat:@"%@/%@/dl",KMOCR_API_URL,_fileID];
  246. NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
  247. [request setHTTPMethod: @"GET"];
  248. NSURLSession *URLSession = [NSURLSession sharedSession];
  249. NSURLSessionDataTask *fileContentTask = [URLSession dataTaskWithRequest:request completionHandler:^ (NSData *data, NSURLResponse *response, NSError *error) {
  250. if (NSURLErrorCancelled == error.code) {
  251. return ;
  252. }
  253. NSData *fileData;
  254. NSError *err;
  255. NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
  256. NSLog(@"dictionary == %@",dictionary);
  257. NSString *statusStr = dictionary[@"status"];
  258. if ([statusStr isEqualToString:@"ok"]) {
  259. fileData = [self responseDataResults:dictionary];
  260. } else {
  261. // err = [self errorWithContent:dictionary[@"error"]];
  262. }
  263. dispatch_async(dispatch_get_main_queue(), ^{
  264. if (error || !fileData) {
  265. [self cancel];
  266. if ([weakSelf.operationDelegate respondsToSelector:@selector(OCROperation:failureOCRImageAtIndex:error:)]) {
  267. [weakSelf.operationDelegate OCROperation:self failureOCRImageAtIndex:weakSelf.imageIndex error:error];
  268. }
  269. } else {
  270. NSArray *arr = @[fileData];
  271. NSLog(@"self.imageIndex == %ld",(long)self.imageIndex);
  272. if ([weakSelf.operationDelegate respondsToSelector:@selector(OCROperation:finishOCRImageAtIndex:results:)]) {
  273. [weakSelf.operationDelegate OCROperation:weakSelf finishOCRImageAtIndex:weakSelf.imageIndex results:arr];
  274. }
  275. [self p_done];
  276. }
  277. });
  278. // if ([statusStr isEqualToString:@"ok"]) {
  279. // [self p_done];
  280. // } else {
  281. // [self cancel];
  282. // }
  283. }];
  284. [fileContentTask resume];
  285. }
  286. - (NSData *)responseDataResults:(NSDictionary *)dictionary {
  287. NSDictionary *responses = [dictionary isKindOfClass:[NSDictionary class]] ? [dictionary objectForKey:@"data"] : nil;
  288. NSString *stringBase64 = responses[@"content"];
  289. NSData *data = [[NSData alloc] initWithBase64EncodedString:stringBase64 options:0];
  290. [data writeToFile:self.filePath atomically:YES];
  291. return data;
  292. }
  293. @end