├── .gitignore ├── icon.png ├── screenshots ├── after-1.png ├── after-2.png ├── after-3.png ├── before-1.png └── before-2.png ├── .github ├── ISSUE_TEMPLATE │ └── bug-report.md └── workflows │ └── build.yml ├── ocr.py ├── README.md ├── ocr.swift └── info.plist /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.alfredworkflow 3 | Tesseract.* 4 | wfbuild -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mr-pennyworth/alfred-clipboard-ocr/HEAD/icon.png -------------------------------------------------------------------------------- /screenshots/after-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mr-pennyworth/alfred-clipboard-ocr/HEAD/screenshots/after-1.png -------------------------------------------------------------------------------- /screenshots/after-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mr-pennyworth/alfred-clipboard-ocr/HEAD/screenshots/after-2.png -------------------------------------------------------------------------------- /screenshots/after-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mr-pennyworth/alfred-clipboard-ocr/HEAD/screenshots/after-3.png -------------------------------------------------------------------------------- /screenshots/before-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mr-pennyworth/alfred-clipboard-ocr/HEAD/screenshots/before-1.png -------------------------------------------------------------------------------- /screenshots/before-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mr-pennyworth/alfred-clipboard-ocr/HEAD/screenshots/before-2.png -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Not working as expected? Errors? Report here! 4 | title: "[BUG]" 5 | labels: '' 6 | assignees: mr-pennyworth 7 | 8 | --- 9 | 10 | **Prerequisite** 11 | - Make sure you have run `.clipboard-history-ocr-install` 12 | in Alfred to install the workflow. 13 | 14 | **Describe the bug** 15 | A clear and concise description of what the bug is. 16 | 17 | **When...** 18 | Step-by-step details of what you did. 19 | 20 | **It should...** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **But it...** 24 | A clear and concise description of what actually happened. 25 | 26 | **Screenshots** 27 | Any screenshots you think would help us investigate. 28 | 29 | **Logs** 30 | Attach or paste contents of `/tmp/alfred-clipboard-history-ocr.out` 31 | 32 | **System** 33 | - macOS version 34 | - Alfred version 35 | -------------------------------------------------------------------------------- /ocr.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sqlite3 3 | import subprocess 4 | import sys 5 | 6 | HOME = os.path.expanduser('~') 7 | DB_DIR = HOME + '/Library/Application Support/Alfred/Databases' 8 | IMAGES_DIR = DB_DIR + '/clipboard.alfdb.data' 9 | DB_PATH = DB_DIR + '/clipboard.alfdb' 10 | 11 | WORKFLOW_DIR = sys.path[0] 12 | 13 | def imgtxt(img_filename): 14 | img_filepath = IMAGES_DIR + '/' + img_filename 15 | if img_filepath.endswith('tiff'): 16 | tmp_path = f'/tmp/{os.path.basename(img_filepath)}.png' 17 | subprocess.check_call([ 18 | 'sips', 19 | '-s', 'format', 'png', 20 | img_filepath, 21 | '--out', tmp_path, 22 | ]) 23 | img_filepath = tmp_path 24 | return subprocess.check_output([ 25 | 'swift', f'{WORKFLOW_DIR}/ocr.swift', img_filepath 26 | ]) 27 | 28 | 29 | db = sqlite3.connect(DB_PATH) 30 | db.text_factory = str 31 | 32 | rows = db.execute(''' 33 | SELECT item, dataHash 34 | FROM clipboard 35 | WHERE dataType == 1 AND item LIKE "Image:%"''' 36 | ).fetchall() 37 | 38 | 39 | for (txt, img_filename) in rows: 40 | if img_filename is None: continue 41 | new_txt = txt.replace('Image:', 'img:') 42 | ocr_txt = imgtxt(img_filename).strip() 43 | if ocr_txt: 44 | new_txt = 'img: ' + ocr_txt.decode() 45 | 46 | db.execute('UPDATE clipboard SET item = ? WHERE dataHash == ?', 47 | (new_txt, img_filename)) 48 | 49 | db.commit() 50 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release 2 | 3 | on: push 4 | 5 | jobs: 6 | build-and-release: 7 | name: Build Alfred workflow and release 8 | runs-on: macos-latest 9 | steps: 10 | 11 | - name: Checkout code 12 | uses: actions/checkout@v2 13 | 14 | - name: Get Workflow Version 15 | run: echo "WF_VERSION=$(plutil -extract version xml1 -o - info.plist | sed -n 's/.*\(.*\)<\/string>.*/\1/p')" >> $GITHUB_ENV 16 | 17 | - name: Create Release 18 | id: create_release 19 | uses: actions/create-release@v1 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | with: 23 | tag_name: ${{ env.WF_VERSION }} 24 | release_name: ${{ env.WF_VERSION }} 25 | draft: false 26 | prerelease: false 27 | 28 | - name: Build Workflow 29 | run: python3 build-scripts/mkworkflow.py 30 | 31 | - name: Get Workflow Filename 32 | run: echo "WF_FILENAME=$(ls *.alfredworkflow)" >> $GITHUB_ENV 33 | 34 | - name: Upload Release Asset 35 | id: upload-release-asset 36 | uses: actions/upload-release-asset@v1 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | with: 40 | upload_url: ${{ steps.create_release.outputs.upload_url }} 41 | asset_path: ./${{ env.WF_FILENAME }} 42 | asset_name: ${{ env.WF_FILENAME }} 43 | asset_content_type: application/zip 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 |
5 | Download
7 |
8 | Clipboard History OCR for Alfred 9 |

10 | 11 | Make images in [Alfred](https://alfredapp.com)'s clipboard history 12 | searchable by their text content. 13 | 14 | Alfred's clipboard history includes images too. 15 | However, there's no way to search through them. 16 | For example, in the screenshots below, are two images in the clipboard history, 17 | but the only "searchable" information about them is dimensions and size. 18 | ![](screenshots/before-1.png) ![](screenshots/before-2.png) 19 | 20 | This tool runs OCR every time an image is copied to clipboard, 21 | and makes the image searchable using that text. 22 | ![](screenshots/after-1.png) ![](screenshots/after-2.png) 23 | ![](screenshots/after-3.png) 24 | 25 | ### Setup 26 | 1. Download the [latest release](https://github.com/mr-pennyworth/alfred-clipboard-ocr/releases/latest/download/Clipboard.History.OCR.alfredworkflow). 27 | 2. Type `.clipboard-history-ocr-install` 28 | 3. Done! 29 | 30 | 31 | ### Uninstall 32 | 1. Run `.clipboard-history-ocr-uninstall` in Alfred. 33 | 2. Delete the workflow from Alfred. 34 | 35 | 36 | ### Credits 37 | - [Tesseract OCR](https://github.com/tesseract-ocr/tesseract) 38 | - [Mac dilyb bundler](https://github.com/auriamg/macdylibbundler) 39 | - Icon made by combining icons from [flaticon](https://www.flaticon.com) 40 | by [Pixel Perfect](https://www.flaticon.com/authors/pixel-perfect) and 41 | [Dimitry Miroliubov](https://www.flaticon.com/authors/dimitry-miroliubov). 42 | -------------------------------------------------------------------------------- /ocr.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Vision 3 | import CoreGraphics 4 | 5 | func performOCR(on image: CGImage) { 6 | // Create a request handler using the image 7 | let requestHandler = VNImageRequestHandler(cgImage: image, options: [:]) 8 | 9 | // Create an OCR request 10 | let request = VNRecognizeTextRequest(completionHandler: { (request, error) in 11 | guard let observations = request.results as? [VNRecognizedTextObservation] else { 12 | print("Failed to perform OCR.") 13 | return 14 | } 15 | 16 | // Extract recognized text from observations 17 | let recognizedText = observations.compactMap { observation in 18 | observation.topCandidates(1).first?.string 19 | } 20 | 21 | // Print the recognized text 22 | print(recognizedText.joined(separator: "\n")) 23 | }) 24 | 25 | // Set the recognition level to accurate for better results 26 | request.recognitionLevel = .accurate 27 | 28 | // Perform the OCR request 29 | do { 30 | try requestHandler.perform([request]) 31 | } catch { 32 | print("Error performing OCR: \(error)") 33 | } 34 | } 35 | 36 | // Check if the command-line argument for the image file path is provided 37 | guard let imagePath = CommandLine.arguments.dropFirst().first else { 38 | print("Please provide the image file path as an argument.") 39 | exit(1) 40 | } 41 | 42 | // Load the image from the provided file path 43 | guard let imageURL = URL(string: "file://\(imagePath)"), 44 | let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, nil), 45 | let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) else { 46 | print("Failed to load the image.") 47 | exit(1) 48 | } 49 | 50 | // Perform OCR on the image 51 | performOCR(on: image) 52 | -------------------------------------------------------------------------------- /info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bundleid 6 | mr.pennyworth.AlfredClipboardHistoryOCR 7 | category 8 | Tools 9 | connections 10 | 11 | 61D39B58-738E-40C6-9BEC-86CF43B078CF 12 | 13 | 14 | destinationuid 15 | D6A9F931-55FC-42F1-A4CB-EAD888770940 16 | modifiers 17 | 0 18 | modifiersubtext 19 | 20 | vitoclose 21 | 22 | 23 | 24 | 7C7978CF-711C-4286-806C-23CEB886129A 25 | 26 | 27 | destinationuid 28 | 9D3D44B8-97A9-48E5-8912-3D32CB8805C9 29 | modifiers 30 | 0 31 | modifiersubtext 32 | 33 | vitoclose 34 | 35 | 36 | 37 | 9D3D44B8-97A9-48E5-8912-3D32CB8805C9 38 | 39 | 40 | createdby 41 | Mr. Pennyworth 42 | description 43 | Make images in clipboard history searchable by their text content 44 | disabled 45 | 46 | name 47 | Clipboard History OCR 48 | objects 49 | 50 | 51 | config 52 | 53 | argumenttype 54 | 2 55 | keyword 56 | .clipboard-history-ocr-install 57 | subtext 58 | .clipboard-history-ocr-install 59 | text 60 | Install Clipboard History OCR LaunchAgent 61 | withspace 62 | 63 | 64 | type 65 | alfred.workflow.input.keyword 66 | uid 67 | 7C7978CF-711C-4286-806C-23CEB886129A 68 | version 69 | 1 70 | 71 | 72 | config 73 | 74 | concurrently 75 | 76 | escaping 77 | 102 78 | script 79 | mkdir ~/Library/LaunchAgents 80 | 81 | cat <<EOF > ~/Library/LaunchAgents/alfred-clipboard-history-ocr.plist 82 | <?xml version="1.0" encoding="UTF-8"?> 83 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 84 | <plist version="1.0"> 85 | <dict> 86 | <key>Label</key> 87 | <string>alfred-clipboard-history-ocr</string> 88 | <key>ProgramArguments</key> 89 | <array> 90 | <string>/usr/bin/python3</string> 91 | <string>$(pwd)/ocr.py</string> 92 | </array> 93 | <key>RunAtLoad</key> 94 | <true/> 95 | <key>StandardErrorPath</key> 96 | <string>/tmp/alfred-clipboard-history-ocr.out</string> 97 | <key>StandardOutPath</key> 98 | <string>/tmp/alfred-clipboard-history-ocr.out</string> 99 | <key>WatchPaths</key> 100 | <array> 101 | <string>$HOME/Library/Application Support/Alfred/Databases/clipboard.alfdb.data</string> 102 | </array> 103 | </dict> 104 | </plist> 105 | EOF 106 | 107 | launchctl unload ~/Library/LaunchAgents/alfred-clipboard-history-ocr.plist 108 | launchctl load -wF ~/Library/LaunchAgents/alfred-clipboard-history-ocr.plist 109 | scriptargtype 110 | 1 111 | scriptfile 112 | 113 | type 114 | 0 115 | 116 | type 117 | alfred.workflow.action.script 118 | uid 119 | 9D3D44B8-97A9-48E5-8912-3D32CB8805C9 120 | version 121 | 2 122 | 123 | 124 | config 125 | 126 | argumenttype 127 | 2 128 | keyword 129 | .clipboard-history-ocr-uninstall 130 | subtext 131 | .clipboard-history-ocr-uninstall 132 | text 133 | Uninstall Clipboard History OCR LaunchAgent 134 | withspace 135 | 136 | 137 | type 138 | alfred.workflow.input.keyword 139 | uid 140 | 61D39B58-738E-40C6-9BEC-86CF43B078CF 141 | version 142 | 1 143 | 144 | 145 | config 146 | 147 | concurrently 148 | 149 | escaping 150 | 102 151 | script 152 | launchctl unload ~/Library/LaunchAgents/alfred-clipboard-history-ocr.plist 153 | rm ~/Library/LaunchAgents/alfred-clipboard-history-ocr.plist 154 | scriptargtype 155 | 1 156 | scriptfile 157 | 158 | type 159 | 0 160 | 161 | type 162 | alfred.workflow.action.script 163 | uid 164 | D6A9F931-55FC-42F1-A4CB-EAD888770940 165 | version 166 | 2 167 | 168 | 169 | readme 170 | 171 | uidata 172 | 173 | 61D39B58-738E-40C6-9BEC-86CF43B078CF 174 | 175 | xpos 176 | 30 177 | ypos 178 | 175 179 | 180 | 7C7978CF-711C-4286-806C-23CEB886129A 181 | 182 | xpos 183 | 30 184 | ypos 185 | 15 186 | 187 | 9D3D44B8-97A9-48E5-8912-3D32CB8805C9 188 | 189 | note 190 | Create and load LaunchAgent for ocr.py 191 | xpos 192 | 195 193 | ypos 194 | 15 195 | 196 | D6A9F931-55FC-42F1-A4CB-EAD888770940 197 | 198 | note 199 | delete the LaunchAgent 200 | xpos 201 | 195 202 | ypos 203 | 175 204 | 205 | 206 | userconfigurationconfig 207 | 208 | variablesdontexport 209 | 210 | version 211 | 0.1.2 212 | webaddress 213 | https://github.com/mr-pennyworth/alfred-clipboard-ocr#readme 214 | 215 | 216 | --------------------------------------------------------------------------------