KMOCROperation.swift 12 KB

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