123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350 |
- //
- // KMOCROperation.swift
- // PDF Reader Pro
- //
- // Created by liujiajie on 2023/11/15.
- //
- import Foundation
- let KMOCR_API_URL = "https://api.convertio.co/convert"
- #if VERSION_DMG
- let KMOCR_API_KEY = "d1a0c1a4cc7fcac5eb08730570217f97"
- #else
- let KMOCR_API_KEY = "d1a0c1a4cc7fcac5eb08730570217f97"
- #endif
- let KMTIMER_MAXIMUM_CYCLE = 10
- @objc(KMOCROperationDelegate)
- protocol KMOCROperationDelegate: AnyObject {
- @objc optional func OCROperationCancel(_ op: KMOCROperation, atIndex: Int)
- @objc optional func OCROperationStart(_ op: KMOCROperation, atIndex: Int)
- @objc optional func OCROperationFinish(_ op: KMOCROperation, atIndex: Int, results: Array<Any>)
- @objc optional func OCROperationFail(_ op: KMOCROperation, atIndex: Int, err: Error?)
- }
- @objcMembers class KMOCROperation: Operation{
- var operationDelegate: KMOCROperationDelegate?
- var selectedLanguages: Array<String>?
- var fileType: String = ""
- var filePath: String = ""
- var timerCycleCount = 0
- var imageIndex = 0
- var task: URLSessionDataTask?
- var fileID: String?
- var fileName: String = ""
- var orcImage: NSImage?
- var fileURL:URL?
- override var isExecuting: Bool{
- set{
- self.willChangeValue(forKey: "isExecuting")
- didChangeValue(forKey: "isExecuting")
- }
- get{
- return super.isExecuting
- }
- }
- override var isFinished: Bool{
- set{
- self.willChangeValue(forKey: "isFinished")
- self.isFinished = newValue
- didChangeValue(forKey: "isFinished")
- }
- get{
- return super.isFinished
- }
- }
- override var isCancelled: Bool{
- set{
- self.willChangeValue(forKey: "isCancelled")
- self.isCancelled = newValue
- didChangeValue(forKey: "isCancelled")
- }
- get{
- return super.isCancelled
- }
- }
- init(recognitionImg:NSImage, imgIndex:Int) {
- super.init()
- self.timerCycleCount = 0
- self.imageIndex = imgIndex
- self.orcImage = recognitionImg
- self.fileName = fileNameWithDate()
- self.fileID = nil
- self.fileURL = nil
- self.queuePriority = .normal
- self.name = self.fileName
- self.isExecuting = false
- self.isFinished = false
- }
- func fileNameWithDate() -> String {
- let formatter = DateFormatter()
- formatter.dateFormat = "YYYY-MM-dd-hh-mm-ss-SSS"
- let dateString = formatter.string(from: Date())
- let fileName = "\(dateString) \(imageIndex)"
- return fileName
- }
- override func start() {
- if p_checkCancelled() { return }
- isExecuting = true
-
- Thread.detachNewThreadSelector(#selector(main), toTarget: self, with: nil)
- }
- @objc override func main() {
- do {
- if p_checkCancelled() { return }
- recognitionImage(image: orcImage ?? NSImage())
- while isExecuting {
- if p_checkCancelled() {
- return
- }
- }
- } catch let e {
- Swift.debugPrint("Exception %@", e)
- }
- }
- override func cancel() {
- super.cancel()
- if task != nil {
- task?.cancel()
- task = nil
- }
-
- self.operationDelegate?.OCROperationCancel?(self, atIndex: self.imageIndex)
-
- if isExecuting {
- isExecuting = false
- isFinished = true
- } else {
- isFinished = false
- }
- isCancelled = true
- }
- func p_done() {
- isExecuting = false
- isFinished = true
- }
- func p_checkCancelled() -> Bool {
- if isCancelled {
- isFinished = true
- return true
- }
- return false
- }
-
- func recognitionImage(image: NSImage) {
- guard let binaryImageData = base64EncodeImage(image) else { return }
- let url = URL(string: KMOCR_API_URL)!
- var request = URLRequest(url: url)
- request.httpMethod = "POST"
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
-
- let paramsDictionary: [String: Any] = [
- "apikey": KMOCR_API_KEY,
- "input": "base64",
- "file": binaryImageData,
- "outputformat": fileType,
- "filename": "1.pdf",
- "options": [
- "ocr_enabled": true,
- "ocr_settings": [
- "langs": selectedLanguages
- ]
- ]
- ]
-
- let requestData = try? JSONSerialization.data(withJSONObject: paramsDictionary, options: [])
- request.httpBody = requestData
-
- let URLSession = URLSession.shared
- let convertTask = URLSession.dataTask(with: request) { [self] (data, response, error) in
- guard let data = data else {
- return
- }
-
- if (error as NSError?)?.code == NSURLErrorCancelled {
- return
- }
-
- guard let dictionary = try? JSONSerialization.jsonObject(with: data, options: .mutableLeaves) as? [String: Any] else {
- return
- }
-
- guard let statusStr = dictionary["status"] as? String else {
- return
- }
-
- if statusStr == "ok" {
- let dic = (dictionary["data"] as? NSDictionary)
- self.fileID = dic?["id"] as? String
- DispatchQueue.main.asyncAfter(deadline: .now() + 15.0) {
- self.requestQuery()
- }
- } else {
- self.cancel()
- operationDelegate?.OCROperationFail?(self, atIndex: self.imageIndex, err: error)
- }
- }
- convertTask.resume()
- }
- func errorWithContent(content: String) -> NSError {
- let domain = "com.MyCompany.MyApplication.ErrorDomain"
- let userInfo = [NSLocalizedDescriptionKey: content]
- let error = NSError(domain: domain, code: -101, userInfo: userInfo)
- return error
- }
- func requestQuery() {
- let urlString = "\(KMOCR_API_URL)/\(fileID ?? "")/status"
- guard let url = URL(string: urlString) else { return }
- var request = URLRequest(url: url)
- request.httpMethod = "GET"
-
- let URLSession = URLSession.shared
- let task = URLSession.dataTask(with: request) { [self] (data, response, error) in
- if (error as NSError?)?.code == NSURLErrorCancelled {
- return
- }
-
- guard let dictionary = try? JSONSerialization.jsonObject(with: data!, options: .mutableLeaves) as? [String: Any] else {
- return
- }
-
- guard let statusStr = dictionary["status"] as? String else {
- return
- }
-
- let dic = (dictionary["data"] as? NSDictionary)
- self.fileID = dic?["id"] as? String
-
- if statusStr == "ok" {
- if let task = self.task {
- task.cancel()
- self.task = nil
- }
-
- if let fileData = dictionary["data"] as? [String: Any], let stepPercent = fileData["step_percent"] as? Int {
- if stepPercent == 100 {
- let dic = (fileData["output"] as? NSDictionary)
- if let fileURLString = dic?["url"] as? String, let fileURL = URL(string: fileURLString) {
- self.fileURL = fileURL
- self.getResultFileContent()
- }
- } else {
- if self.timerCycleCount >= KMTIMER_MAXIMUM_CYCLE {
- self.cancel()
- operationDelegate?.OCROperationFail?(self, atIndex: self.imageIndex, err: error)
- } else {
- self.timerCycleCount += 1
- DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
- self.requestQuery()
- }
- }
- }
- }
- } else {
- self.cancel()
- operationDelegate?.OCROperationFail?(self, atIndex: self.imageIndex, err: error)
- }
- }
- task.resume()
- }
-
- func getResultFileContent() {
- let urlString = "\(KMOCR_API_URL)/\(fileID ?? "")/dl"
- guard let url = URL(string: urlString) else { return }
- var request = URLRequest(url: url)
- request.httpMethod = "GET"
-
- let URLSession = URLSession.shared
- let fileContentTask = URLSession.dataTask(with: request) { (data, response, error) in
- if (error as NSError?)?.code == NSURLErrorCancelled {
- return
- }
-
- var fileData: Data?
- guard let dictionary = try? JSONSerialization.jsonObject(with: data!, options: .mutableLeaves) as? [String: Any] else {
- return
- }
-
- guard let statusStr = dictionary["status"] as? String else {
- return
- }
-
- if statusStr == "ok" {
- fileData = self.responseDataResults((dictionary as NSDictionary) as! [String : Any])
- } else {
- // err = self.errorWithContent(dictionary["error"])
- }
-
- DispatchQueue.main.async { [self] in
- if let error = error, fileData == nil {
- self.cancel()
- if let err = error as NSError? {
- operationDelegate?.OCROperationFail?(self, atIndex: self.imageIndex, err: error)
- }
- } else {
- if let fileData = fileData {
- let arr = [fileData]
- if let delegate = operationDelegate {
- delegate.OCROperationFinish?(self, atIndex: self.imageIndex, results: arr)
- }
- self.p_done()
- }
- }
- }
- }
- fileContentTask.resume()
- }
-
- func base64EncodeImage(_ image: NSImage) -> String? {
- guard let data = image.tiffRepresentation else { return nil }
- let imageRep = NSBitmapImageRep(data: data)!
- imageRep.size = image.size
- let imageData = imageRep.representation(using: .png, properties: [:])
- // Resize the image if it exceeds the 4MB API limit
- if imageData?.count ?? 0 > 4194304 {
- let compressedData = compressImageData(imageData!, toMaxFileSize: 4194304)
- if let data = compressedData {
- return data.base64EncodedString(options: .endLineWithCarriageReturn)
- }
- }
-
- if let data = imageData {
- if #available(macOS 10.9, *) {
- return data.base64EncodedString(options: .endLineWithCarriageReturn)
- } else {
- return data.base64EncodedString(options: [])
- }
- }
-
- return nil
- }
-
- func compressImageData(_ imageData: Data, toMaxFileSize maxFileSize: Int) -> Data? {
- var compression: CGFloat = 0.9
- let maxCompression: CGFloat = 0.1
- var compressImageData = imageData
- while compressImageData.count > maxFileSize && compression > maxCompression {
- compression -= 0.1
- let imageRep = NSBitmapImageRep(data: compressImageData)!
- compressImageData = imageRep.representation(using: .jpeg, properties: [NSBitmapImageRep.PropertyKey.compressionFactor: NSNumber(value: Float(compression))])!
- }
- return compressImageData
- }
- func responseDataResults(_ dictionary: [String: Any]) -> Data? {
- guard let responses = dictionary["data"] as? [String: Any] else { return nil }
- guard let stringBase64 = responses["content"] as? String else {
- return nil
- }
-
- guard let data = Data(base64Encoded: stringBase64, options: .ignoreUnknownCharacters) else {
- return nil
- }
-
- try? data.write(to: URL(fileURLWithPath: filePath), options: .atomic)
- return data
- }
- }
|