KMOCROperation.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. //
  2. // KMOCROperation.swift
  3. // PDF Reader Pro
  4. //
  5. // Created by liujiajie on 2023/11/15.
  6. //
  7. import Foundation
  8. let KMOCR_API_URL = "https://api.convertio.co/convert"
  9. #if VERSION_DMG
  10. let KMOCR_API_KEY = "d1a0c1a4cc7fcac5eb08730570217f97"
  11. #else
  12. let KMOCR_API_KEY = "d1a0c1a4cc7fcac5eb08730570217f97"
  13. #endif
  14. let KMTIMER_MAXIMUM_CYCLE = 10
  15. @objc(KMOCROperationDelegate)
  16. protocol KMOCROperationDelegate: AnyObject {
  17. @objc optional func OCROperationCancel(_ op: KMOCROperation, atIndex: Int)
  18. @objc optional func OCROperationStart(_ op: KMOCROperation, atIndex: Int)
  19. @objc optional func OCROperationFinish(_ op: KMOCROperation, atIndex: Int, results: Array<Any>)
  20. @objc optional func OCROperationFail(_ op: KMOCROperation, atIndex: Int, err: Error?)
  21. }
  22. @objcMembers class KMOCROperation: Operation{
  23. var operationDelegate: KMOCROperationDelegate?
  24. var selectedLanguages: Array<String>?
  25. var fileType: String = ""
  26. var filePath: String = ""
  27. var timerCycleCount = 0
  28. var imageIndex = 0
  29. var task: URLSessionDataTask?
  30. var fileID: String?
  31. var fileName: String = ""
  32. var orcImage: NSImage?
  33. var fileURL:URL?
  34. override var isExecuting: Bool{
  35. set{
  36. self.willChangeValue(forKey: "isExecuting")
  37. didChangeValue(forKey: "isExecuting")
  38. }
  39. get{
  40. return super.isExecuting
  41. }
  42. }
  43. override var isFinished: Bool{
  44. set{
  45. self.willChangeValue(forKey: "isFinished")
  46. self.isFinished = newValue
  47. didChangeValue(forKey: "isFinished")
  48. }
  49. get{
  50. return super.isFinished
  51. }
  52. }
  53. override var isCancelled: Bool{
  54. set{
  55. self.willChangeValue(forKey: "isCancelled")
  56. self.isCancelled = newValue
  57. didChangeValue(forKey: "isCancelled")
  58. }
  59. get{
  60. return super.isCancelled
  61. }
  62. }
  63. init(recognitionImg:NSImage, imgIndex:Int) {
  64. super.init()
  65. self.timerCycleCount = 0
  66. self.imageIndex = imgIndex
  67. self.orcImage = recognitionImg
  68. self.fileName = fileNameWithDate()
  69. self.fileID = nil
  70. self.fileURL = nil
  71. self.queuePriority = .normal
  72. self.name = self.fileName
  73. self.isExecuting = false
  74. self.isFinished = false
  75. }
  76. func fileNameWithDate() -> String {
  77. let formatter = DateFormatter()
  78. formatter.dateFormat = "YYYY-MM-dd-hh-mm-ss-SSS"
  79. let dateString = formatter.string(from: Date())
  80. let fileName = "\(dateString) \(imageIndex)"
  81. return fileName
  82. }
  83. override func start() {
  84. if p_checkCancelled() { return }
  85. isExecuting = true
  86. Thread.detachNewThreadSelector(#selector(main), toTarget: self, with: nil)
  87. }
  88. @objc override func main() {
  89. do {
  90. if p_checkCancelled() { return }
  91. recognitionImage(image: orcImage ?? NSImage())
  92. while isExecuting {
  93. if p_checkCancelled() {
  94. return
  95. }
  96. }
  97. } catch let e {
  98. Swift.debugPrint("Exception %@", e)
  99. }
  100. }
  101. override func cancel() {
  102. super.cancel()
  103. if task != nil {
  104. task?.cancel()
  105. task = nil
  106. }
  107. self.operationDelegate?.OCROperationCancel?(self, atIndex: self.imageIndex)
  108. if isExecuting {
  109. isExecuting = false
  110. isFinished = true
  111. } else {
  112. isFinished = false
  113. }
  114. isCancelled = true
  115. }
  116. func p_done() {
  117. isExecuting = false
  118. isFinished = true
  119. }
  120. func p_checkCancelled() -> Bool {
  121. if isCancelled {
  122. isFinished = true
  123. return true
  124. }
  125. return false
  126. }
  127. func recognitionImage(image: NSImage) {
  128. guard let binaryImageData = base64EncodeImage(image) else { return }
  129. let url = URL(string: KMOCR_API_URL)!
  130. var request = URLRequest(url: url)
  131. request.httpMethod = "POST"
  132. request.addValue("application/json", forHTTPHeaderField: "Content-Type")
  133. let paramsDictionary: [String: Any] = [
  134. "apikey": KMOCR_API_KEY,
  135. "input": "base64",
  136. "file": binaryImageData,
  137. "outputformat": fileType,
  138. "filename": "1.pdf",
  139. "options": [
  140. "ocr_enabled": true,
  141. "ocr_settings": [
  142. "langs": selectedLanguages
  143. ]
  144. ]
  145. ]
  146. let requestData = try? JSONSerialization.data(withJSONObject: paramsDictionary, options: [])
  147. request.httpBody = requestData
  148. let URLSession = URLSession.shared
  149. let convertTask = URLSession.dataTask(with: request) { [self] (data, response, error) in
  150. guard let data = data else {
  151. return
  152. }
  153. if (error as NSError?)?.code == NSURLErrorCancelled {
  154. return
  155. }
  156. guard let dictionary = try? JSONSerialization.jsonObject(with: data, options: .mutableLeaves) as? [String: Any] else {
  157. return
  158. }
  159. guard let statusStr = dictionary["status"] as? String else {
  160. return
  161. }
  162. if statusStr == "ok" {
  163. let dic = (dictionary["data"] as? NSDictionary)
  164. self.fileID = dic?["id"] as? String
  165. DispatchQueue.main.asyncAfter(deadline: .now() + 15.0) {
  166. self.requestQuery()
  167. }
  168. } else {
  169. self.cancel()
  170. operationDelegate?.OCROperationFail?(self, atIndex: self.imageIndex, err: error)
  171. }
  172. }
  173. convertTask.resume()
  174. }
  175. func errorWithContent(content: String) -> NSError {
  176. let domain = "com.MyCompany.MyApplication.ErrorDomain"
  177. let userInfo = [NSLocalizedDescriptionKey: content]
  178. let error = NSError(domain: domain, code: -101, userInfo: userInfo)
  179. return error
  180. }
  181. func requestQuery() {
  182. let urlString = "\(KMOCR_API_URL)/\(fileID ?? "")/status"
  183. guard let url = URL(string: urlString) else { return }
  184. var request = URLRequest(url: url)
  185. request.httpMethod = "GET"
  186. let URLSession = URLSession.shared
  187. let task = URLSession.dataTask(with: request) { [self] (data, response, error) in
  188. if (error as NSError?)?.code == NSURLErrorCancelled {
  189. return
  190. }
  191. guard let dictionary = try? JSONSerialization.jsonObject(with: data!, options: .mutableLeaves) as? [String: Any] else {
  192. return
  193. }
  194. guard let statusStr = dictionary["status"] as? String else {
  195. return
  196. }
  197. let dic = (dictionary["data"] as? NSDictionary)
  198. self.fileID = dic?["id"] as? String
  199. if statusStr == "ok" {
  200. if let task = self.task {
  201. task.cancel()
  202. self.task = nil
  203. }
  204. if let fileData = dictionary["data"] as? [String: Any], let stepPercent = fileData["step_percent"] as? Int {
  205. if stepPercent == 100 {
  206. let dic = (fileData["output"] as? NSDictionary)
  207. if let fileURLString = dic?["url"] as? String, let fileURL = URL(string: fileURLString) {
  208. self.fileURL = fileURL
  209. self.getResultFileContent()
  210. }
  211. } else {
  212. if self.timerCycleCount >= KMTIMER_MAXIMUM_CYCLE {
  213. self.cancel()
  214. operationDelegate?.OCROperationFail?(self, atIndex: self.imageIndex, err: error)
  215. } else {
  216. self.timerCycleCount += 1
  217. DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
  218. self.requestQuery()
  219. }
  220. }
  221. }
  222. }
  223. } else {
  224. self.cancel()
  225. operationDelegate?.OCROperationFail?(self, atIndex: self.imageIndex, err: error)
  226. }
  227. }
  228. task.resume()
  229. }
  230. func getResultFileContent() {
  231. let urlString = "\(KMOCR_API_URL)/\(fileID ?? "")/dl"
  232. guard let url = URL(string: urlString) else { return }
  233. var request = URLRequest(url: url)
  234. request.httpMethod = "GET"
  235. let URLSession = URLSession.shared
  236. let fileContentTask = URLSession.dataTask(with: request) { (data, response, error) in
  237. if (error as NSError?)?.code == NSURLErrorCancelled {
  238. return
  239. }
  240. var fileData: Data?
  241. guard let dictionary = try? JSONSerialization.jsonObject(with: data!, options: .mutableLeaves) as? [String: Any] else {
  242. return
  243. }
  244. guard let statusStr = dictionary["status"] as? String else {
  245. return
  246. }
  247. if statusStr == "ok" {
  248. fileData = self.responseDataResults((dictionary as NSDictionary) as! [String : Any])
  249. } else {
  250. // err = self.errorWithContent(dictionary["error"])
  251. }
  252. DispatchQueue.main.async { [self] in
  253. if let error = error, fileData == nil {
  254. self.cancel()
  255. if let err = error as NSError? {
  256. operationDelegate?.OCROperationFail?(self, atIndex: self.imageIndex, err: error)
  257. }
  258. } else {
  259. if let fileData = fileData {
  260. let arr = [fileData]
  261. if let delegate = operationDelegate {
  262. delegate.OCROperationFinish?(self, atIndex: self.imageIndex, results: arr)
  263. }
  264. self.p_done()
  265. }
  266. }
  267. }
  268. }
  269. fileContentTask.resume()
  270. }
  271. func base64EncodeImage(_ image: NSImage) -> String? {
  272. guard let data = image.tiffRepresentation else { return nil }
  273. let imageRep = NSBitmapImageRep(data: data)!
  274. imageRep.size = image.size
  275. let imageData = imageRep.representation(using: .png, properties: [:])
  276. // Resize the image if it exceeds the 4MB API limit
  277. if imageData?.count ?? 0 > 4194304 {
  278. let compressedData = compressImageData(imageData!, toMaxFileSize: 4194304)
  279. if let data = compressedData {
  280. return data.base64EncodedString(options: .endLineWithCarriageReturn)
  281. }
  282. }
  283. if let data = imageData {
  284. if #available(macOS 10.9, *) {
  285. return data.base64EncodedString(options: .endLineWithCarriageReturn)
  286. } else {
  287. return data.base64EncodedString(options: [])
  288. }
  289. }
  290. return nil
  291. }
  292. func compressImageData(_ imageData: Data, toMaxFileSize maxFileSize: Int) -> Data? {
  293. var compression: CGFloat = 0.9
  294. let maxCompression: CGFloat = 0.1
  295. var compressImageData = imageData
  296. while compressImageData.count > maxFileSize && compression > maxCompression {
  297. compression -= 0.1
  298. let imageRep = NSBitmapImageRep(data: compressImageData)!
  299. compressImageData = imageRep.representation(using: .jpeg, properties: [NSBitmapImageRep.PropertyKey.compressionFactor: NSNumber(value: Float(compression))])!
  300. }
  301. return compressImageData
  302. }
  303. func responseDataResults(_ dictionary: [String: Any]) -> Data? {
  304. guard let responses = dictionary["data"] as? [String: Any] else { return nil }
  305. guard let stringBase64 = responses["content"] as? String else {
  306. return nil
  307. }
  308. guard let data = Data(base64Encoded: stringBase64, options: .ignoreUnknownCharacters) else {
  309. return nil
  310. }
  311. try? data.write(to: URL(fileURLWithPath: filePath), options: .atomic)
  312. return data
  313. }
  314. }