diff --git a/VOCR.xcodeproj/project.pbxproj b/VOCR.xcodeproj/project.pbxproj index 57685af..0877b07 100644 --- a/VOCR.xcodeproj/project.pbxproj +++ b/VOCR.xcodeproj/project.pbxproj @@ -21,13 +21,11 @@ EA5C7AC82351B3F500D84042 /* OCR.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5C7AC72351B3F500D84042 /* OCR.swift */; }; EA5C7ACA2351B96D00D84042 /* Take Screenshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5C7AC92351B96D00D84042 /* Take Screenshot.swift */; }; EA5C7ACC2351BB6D00D84042 /* Navigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5C7ACB2351BB6D00D84042 /* Navigation.swift */; }; - EA5C7ACE2351C10100D84042 /* MacCamera.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5C7ACD2351C10100D84042 /* MacCamera.swift */; }; EA5C7AD02351C8DE00D84042 /* AXUIElement Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5C7ACF2351C8DE00D84042 /* AXUIElement Extension.swift */; }; EA5C7AD42351D44B00D84042 /* RecognizeVOCursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5C7AD32351D44B00D84042 /* RecognizeVOCursor.swift */; }; EA5C7AD92351D81100D84042 /* VOScreenshot.scpt in Resources */ = {isa = PBXBuildFile; fileRef = EA5C7AD82351D81100D84042 /* VOScreenshot.scpt */; }; EA688D452355BDC500F722C7 /* Bundle Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA688D442355BDC500F722C7 /* Bundle Extension.swift */; }; EA6C26AC2354656D00665D4D /* Classifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6C26AB2354656D00665D4D /* Classifier.swift */; }; - EA76243F243C5576001D665D /* ImportViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA76243E243C5576001D665D /* ImportViewController.swift */; }; EA8AD6412352B4A000D26871 /* Double Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA8AD6402352B4A000D26871 /* Double Extension.swift */; }; EAA12C4F234C89030036AF6A /* NavigationShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA12C4E234C89030036AF6A /* NavigationShortcuts.swift */; }; EAD9CFD22355E11600E7F594 /* say.scpt in Resources */ = {isa = PBXBuildFile; fileRef = EAD9CFD12355E11600E7F594 /* say.scpt */; }; @@ -96,13 +94,11 @@ EA5C7AC72351B3F500D84042 /* OCR.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OCR.swift; sourceTree = ""; }; EA5C7AC92351B96D00D84042 /* Take Screenshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Take Screenshot.swift"; sourceTree = ""; }; EA5C7ACB2351BB6D00D84042 /* Navigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Navigation.swift; sourceTree = ""; }; - EA5C7ACD2351C10100D84042 /* MacCamera.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacCamera.swift; sourceTree = ""; }; EA5C7ACF2351C8DE00D84042 /* AXUIElement Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AXUIElement Extension.swift"; sourceTree = ""; }; EA5C7AD32351D44B00D84042 /* RecognizeVOCursor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecognizeVOCursor.swift; sourceTree = ""; }; EA5C7AD82351D81100D84042 /* VOScreenshot.scpt */ = {isa = PBXFileReference; lastKnownFileType = file; path = VOScreenshot.scpt; sourceTree = ""; }; EA688D442355BDC500F722C7 /* Bundle Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle Extension.swift"; sourceTree = ""; }; EA6C26AB2354656D00665D4D /* Classifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Classifier.swift; sourceTree = ""; }; - EA76243E243C5576001D665D /* ImportViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportViewController.swift; sourceTree = ""; }; EA8AD6402352B4A000D26871 /* Double Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double Extension.swift"; sourceTree = ""; }; EAA12C4E234C89030036AF6A /* NavigationShortcuts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationShortcuts.swift; sourceTree = ""; }; EAD9CFD12355E11600E7F594 /* say.scpt */ = {isa = PBXFileReference; lastKnownFileType = file; path = say.scpt; sourceTree = ""; }; @@ -262,9 +258,7 @@ EA688D442355BDC500F722C7 /* Bundle Extension.swift */, EA6C26AB2354656D00665D4D /* Classifier.swift */, EA8AD6402352B4A000D26871 /* Double Extension.swift */, - EA76243E243C5576001D665D /* ImportViewController.swift */, EADE59BD232EB09A00E2F65A /* Info.plist */, - EA5C7ACD2351C10100D84042 /* MacCamera.swift */, EADE59BA232EB09A00E2F65A /* Main.storyboard */, EA5C7ACB2351BB6D00D84042 /* Navigation.swift */, EAA12C4E234C89030036AF6A /* NavigationShortcuts.swift */, @@ -359,7 +353,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - EA5C7ACE2351C10100D84042 /* MacCamera.swift in Sources */, EADE59B7232EB09A00E2F65A /* AboutViewController.swift in Sources */, EA5C7ACA2351B96D00D84042 /* Take Screenshot.swift in Sources */, EA8AD6412352B4A000D26871 /* Double Extension.swift in Sources */, @@ -376,7 +369,6 @@ EA5C7AD42351D44B00D84042 /* RecognizeVOCursor.swift in Sources */, EA2DA1AD232EB1D600D31031 /* Accessibility.swift in Sources */, EA5C7ACC2351BB6D00D84042 /* Navigation.swift in Sources */, - EA76243F243C5576001D665D /* ImportViewController.swift in Sources */, EA6C26AC2354656D00665D4D /* Classifier.swift in Sources */, EA2DA1A9232EB18800D31031 /* HotKey.swift in Sources */, EAF9C3C82354A66200D4D77C /* Settings.swift in Sources */, @@ -530,7 +522,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 9N598S2535; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -557,7 +549,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 9N598S2535; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( diff --git a/VOCR/Accessibility.swift b/VOCR/Accessibility.swift index e590e57..fcfb398 100644 --- a/VOCR/Accessibility.swift +++ b/VOCR/Accessibility.swift @@ -13,8 +13,9 @@ class Accessibility { static let speech:NSSpeechSynthesizer = NSSpeechSynthesizer() + static func isTrusted(ask:Bool) -> Bool { - let prompt = kAXTrustedCheckOptionPrompt.takeUnretainedValue() as NSString + let prompt = kAXTrustedCheckOptionPrompt.takeUnretainedValue() as NSString let options = [prompt: ask] return AXIsProcessTrustedWithOptions(options as CFDictionary?) } @@ -25,8 +26,14 @@ class Accessibility { NSAccessibility.post(element:element, notification: NSAccessibility.Notification.announcementRequested, userInfo: announcement) } + static func speakWithSynthesizer(_ message:String) { + DispatchQueue.global().async { + speech.startSpeaking(message) + } + } + static func speak(_ message:String) { - // speech.startSpeaking(message) + let bundle = Bundle.main let url = bundle.url(forResource: "say", withExtension: "scpt") let parameters = NSAppleEventDescriptor.list() diff --git a/VOCR/Base.lproj/Main.storyboard b/VOCR/Base.lproj/Main.storyboard index 51b5f10..1bb92bb 100644 --- a/VOCR/Base.lproj/Main.storyboard +++ b/VOCR/Base.lproj/Main.storyboard @@ -688,7 +688,7 @@ - + @@ -741,117 +741,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/VOCR/ImportViewController.swift b/VOCR/ImportViewController.swift deleted file mode 100644 index dc7fefb..0000000 --- a/VOCR/ImportViewController.swift +++ /dev/null @@ -1,79 +0,0 @@ -// -// ImportViewController.swift -// VOCR -// -// Created by Chi Kim on 4/7/20. -// Copyright © 2020 Chi Kim. All rights reserved. -// - -import Cocoa - -class ImportViewController: NSViewController, NSServicesMenuRequestor { - - @IBOutlet var textView: NSTextView! - @IBOutlet var imageView: NSImageView! - - @IBAction func chooseSource(_ sender: NSButton) { - let menu = NSMenu() - menu.addItem(withTitle: "File", action: #selector(importFile), keyEquivalent: "") - NSMenu.popUpContextMenu(menu, with: NSEvent(), for: sender) - } - - @objc func importFile() { - debugPrint("Choose a file") - let openPanel = NSOpenPanel() - openPanel.canChooseFiles = true - openPanel.allowsMultipleSelection = false - openPanel.canChooseDirectories = false - openPanel.canCreateDirectories = false - openPanel.allowedFileTypes = ["jpg","png","pdf","pct", "bmp", "tiff"] - openPanel.begin { response in - if response == .OK { - let image = NSImage(byReferencing: openPanel.url!) - self.process(image) - } - } - } - - override func viewDidLoad() { - super.viewDidLoad() - // Do view setup here. - textView.font = NSFont(name: "Times", size: 20) - self.view.window?.zoom(self) - } - - override func validRequestor(forSendType sendType: NSPasteboard.PasteboardType?, returnType: NSPasteboard.PasteboardType?) -> Any? { - if let pasteboardType = returnType, - NSImage.imageTypes.contains(pasteboardType.rawValue) { - return self - } else { - return super.validRequestor(forSendType: sendType, returnType: returnType) - } - } - - func readSelection(from pasteboard: NSPasteboard) -> Bool { - // Verify that the pasteboard contains image data. - guard pasteboard.canReadItem(withDataConformingToTypes: NSImage.imageTypes) else { - return false - } - // Load the image. - guard let image = NSImage(pasteboard: pasteboard) else { - return false - } - // Incorporate the image into the app. - process(image) - // This method has successfully read the pasteboard data. - return true - } - - func process(_ image:NSImage) { - if image.isValid { - let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) - textView.string = classify(cgImage:cgImage!) - imageView.image = image - } else { - textView.string = "File type is not supported!" - } - - } -} diff --git a/VOCR/Info.plist b/VOCR/Info.plist index 5117f91..ba17728 100644 --- a/VOCR/Info.plist +++ b/VOCR/Info.plist @@ -28,8 +28,6 @@ NSAppleEventsUsageDescription Speak announcements and take Screenshot under VoiceOver cursor - NSCameraUsageDescription - Capture a photo to OCR. NSHumanReadableCopyright Copyright © 2019 Chi Kim. All rights reserved. NSMainStoryboardFile diff --git a/VOCR/MacCamera.swift b/VOCR/MacCamera.swift deleted file mode 100644 index 24e8f41..0000000 --- a/VOCR/MacCamera.swift +++ /dev/null @@ -1,106 +0,0 @@ -// -// Camera.swift -// VOCR -// -// Created by Chi Kim on 10/12/19. -// Copyright © 2019 Chi Kim. All rights reserved. -// - -import AVFoundation -import Cocoa - -class MacCamera:NSObject, AVCapturePhotoCaptureDelegate { - - static let shared = MacCamera() - var captureSession:AVCaptureSession! - var cameraOutput:AVCapturePhotoOutput! - - - func isCameraAllowed() -> Bool { - let cameraPermissionStatus = AVCaptureDevice.authorizationStatus(for: .video) - switch cameraPermissionStatus { - case .authorized: - print("Already Authorized") - return true - case .denied: - print("denied") - return false - case .restricted: - print("restricted") - return false - default: - print("Ask permission") - - var access = false - AVCaptureDevice.requestAccess(for: .video) { granted in - if granted == true { - print("User granted") - } else { - print("User denied") - } - access = granted - } - return access - } - } - - func takePicture() { - for c in stride(from: 3, to: 1, by: -1) { - Accessibility.speak("\(c)") - sleep(1) - } - Accessibility.speak("1") - captureSession = AVCaptureSession() - captureSession.sessionPreset = AVCaptureSession.Preset.photo - cameraOutput = AVCapturePhotoOutput() - - if let device = AVCaptureDevice.default(for: .video), - let input = try? AVCaptureDeviceInput(device: device) { - if (captureSession.canAddInput(input)) { - captureSession.addInput(input) - if (captureSession.canAddOutput(cameraOutput)) { - captureSession.addOutput(cameraOutput) - captureSession.startRunning() - let settings = AVCapturePhotoSettings() - cameraOutput.capturePhoto(with: settings, delegate: self) - } - } else { - print("issue here : captureSesssion.canAddInput") - } - } else { - print("some problem here") - } - } - - func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { - captureSession.stopRunning() - print("photo captured") - if let error = error { - debugPrint(error) - } - - if let dataImage = photo.fileDataRepresentation() { - let dataProvider = CGDataProvider(data: dataImage as CFData) - if let cgImage = CGImage(jpegDataProviderSource: dataProvider!, decode: nil, shouldInterpolate: true, intent: .defaultIntent) { - NSSound(contentsOfFile: "/System/Library/Components/CoreAudio.component/Contents/SharedSupport/SystemSounds/system/Shutter.aif", byReference: true)?.play() - let fileManager = FileManager.default - let home = fileManager.homeDirectoryForCurrentUser - let file = home.appendingPathComponent("Desktop/camera.png") - let url = file.absoluteURL - if Settings.saveCameraImage { - writeCGImage(cgImage, to:url) - } - classify(cgImage:cgImage) - } - } else { - print("some error here") - } - } - - @discardableResult func writeCGImage(_ image: CGImage, to destinationURL: URL) -> Bool { - guard let destination = CGImageDestinationCreateWithURL(destinationURL as CFURL, kUTTypePNG, 1, nil) else { return false } - CGImageDestinationAddImage(destination, image, nil) - return CGImageDestinationFinalize(destination) - } - -} diff --git a/VOCR/Settings.swift b/VOCR/Settings.swift index 7d1f8c8..1edcb66 100644 --- a/VOCR/Settings.swift +++ b/VOCR/Settings.swift @@ -12,8 +12,7 @@ struct Settings { static var positionReset = true static var positionalAudio = false - static var saveCameraImage = false - + static func load() { let defaults = UserDefaults.standard if let positionReset = defaults.object(forKey:"positionReset") { @@ -22,15 +21,12 @@ struct Settings { debugPrint("positionReset \(Settings.positionReset)") Settings.positionalAudio = defaults.bool(forKey:"positionalAudio") debugPrint("positionalAudio \(Settings.positionalAudio)") - Settings.saveCameraImage = defaults.bool(forKey:"saveCameraImage") - debugPrint("saveCameraImage \(Settings.saveCameraImage)") } static func save() { let defaults = UserDefaults.standard defaults.set(Settings.positionReset, forKey:"positionReset") defaults.set(Settings.positionalAudio, forKey:"positionalAudio") - defaults.set(Settings.saveCameraImage, forKey:"saveCameraImage") } } diff --git a/VOCR/VOCR Shortcuts.swift b/VOCR/VOCR Shortcuts.swift index 14e2ef9..8e60fd3 100644 --- a/VOCR/VOCR Shortcuts.swift +++ b/VOCR/VOCR Shortcuts.swift @@ -11,11 +11,9 @@ import Cocoa struct Shortcuts { let window = HotKey(key:.w, modifiers:[.command,.shift, .control]) - let camera = HotKey(key:.c, modifiers:[.command,.shift,.control]) let vo = HotKey(key:.v, modifiers:[.command,.shift, .control]) let resetPosition = HotKey(key:.r, modifiers:[.command,.shift, .control]) let positionalAudio = HotKey(key:.p, modifiers:[.command,.shift, .control]) - let source = HotKey(key:.i, modifiers:[.command,.shift, .control]) init() { window.keyDownHandler = { @@ -29,12 +27,6 @@ struct Shortcuts { } } - camera.keyDownHandler = { - if MacCamera.shared.isCameraAllowed() { - MacCamera.shared.takePicture() - } - } - vo.keyDownHandler = { recognizeVOCursor() } @@ -61,19 +53,6 @@ struct Shortcuts { Settings.save() } - source.keyDownHandler = { - let storyboardName = NSStoryboard.Name(stringLiteral: "Main") - let storyboard = NSStoryboard(name: storyboardName, bundle: nil) - let storyboardID = NSStoryboard.SceneIdentifier(stringLiteral: "importWindowStoryboardID") - if let windowController = storyboard.instantiateController(withIdentifier: storyboardID) as? NSWindowController { - if NSApplication.shared.windows.filter { $0.title.contains("Continuity") && $0.isVisible }.count > 0 { - return - } - NSApplication.shared.activate(ignoringOtherApps: true) - windowController.showWindow(nil) - } - } - } } diff --git a/VOCR/VOCR.entitlements b/VOCR/VOCR.entitlements index c9b49c9..49ad0bb 100644 --- a/VOCR/VOCR.entitlements +++ b/VOCR/VOCR.entitlements @@ -4,7 +4,5 @@ com.apple.security.automation.apple-events - com.apple.security.device.camera - diff --git a/readme.md b/readme.md index 797952c..b05c736 100644 --- a/readme.md +++ b/readme.md @@ -11,7 +11,7 @@ This branch utilizes VisionKit on MacOS Catalina that take advantage of machine This is a standalone app, and it does not rely on Keyboard Maestro, Imagemagick, and Tesseract that the previous VOCR utilized. ## Download -Here is the direct link to download [VOCR v1.0.0-alpha.2.](https://github.com/chigkim/VOCR/releases/download/v1.0.0-alpha.2/VOCR.v1.0.0-alpha.2.zip) +Here is the direct link to download [VOCR v1.0.0-alpha.3.](https://github.com/chigkim/VOCR/releases/download/v1.0.0-alpha.3/VOCR.v1.0.0-alpha.3.zip) ## Upgrade from Previous VOCR With Tesseract You can simply remove VOCR group from Keyboard Maestro and follow the instruction for setup below. @@ -50,13 +50,6 @@ If you want to verify if it works properly, search images on Google image using If everything goes well, VOCR will report the top 5 image categories in confidence order. If VOCR categorizes the image as a document, it will apply OCR. You can review the OCR result the same way as the section above, but this does not work with mouse movement. -## Recognize picture from camera -* Press command+shift+control+c -* If running this feature for the first time, it will display an alert to give VOCR access to your camera -* Give VOCR access to camera in system preference -* Press command+shift+control+c again, and you'll hear a count down. -* Shortly after hearing the camera shutter sound, you should hear the result. - ## Settings Positional audio (command+shift+control+p): As mouse cursor moves you will hear hear audio feedback. Frequency changes responds to vertical move, and pan responds to horizontal move. This feature is useful to explore the interface and discover elements' locations. @@ -65,8 +58,6 @@ Disable/enable reset position (command+shift+control+r): When disabled, the curs ## Shortcuts * OCR Frontmost Window: command+shift+control+w * Recognize image under VoiceOver cursor: command+shift+control+v -* Recognize picture from camera: command+shift+control+c -* Import Image: command+shift+control+i * Toggle reset position after scan: command+shift+control+r * Toggle positional audio feedback: command+shift+control+p @@ -83,7 +74,6 @@ The following shortcuts only works after a scan. ## Troubleshooting * If you hear "nothing found" or just hear the word "the", most likely either you need to turn off VoiceOver screen curtain with vo+shift+f11, or fix accessibility and screen recording permission in security and privacy preference. * If you do not hear anything after recognize image under VoiceOver cursor, most likely you need to give VOCR permissions to 1. send Apple Events, 2. control VoiceOver, and 3. access desktop folder. Usually relaunching VOCR and reissuing the command usually retrigger the alerts to reappear in the system dialogs. -* If you hear just "outdoor" and or "sky", " after issuing camera, you would need to fix the camera access. Lastly, please enjoy and send me your feedback!