├── 15A2426A-6614-4325-9014-873CE2FCAEEE.png ├── 8C1ABC6C-1ED1-4959-BAE0-E1027F44CBEF.png ├── 9D05394A-A5F5-4A2B-8AA7-3C06528411D3.png ├── LICENSE ├── README.md ├── alfred_validate_cache.sh ├── cleanshot.sh ├── clipsaver.png ├── clock.png ├── cs.py ├── error.png ├── generic.png ├── get_image_hashes.py ├── grid.png ├── icon.png ├── info.plist ├── process_clip.bash ├── save_multi.sh ├── save_multi_time.sh ├── save_single.sh └── wf_common.py /15A2426A-6614-4325-9014-873CE2FCAEEE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luckman212/alfred_clipsaver_workflow/4960833959aa35c7d612f3a3cf1e8851672cc905/15A2426A-6614-4325-9014-873CE2FCAEEE.png -------------------------------------------------------------------------------- /8C1ABC6C-1ED1-4959-BAE0-E1027F44CBEF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luckman212/alfred_clipsaver_workflow/4960833959aa35c7d612f3a3cf1e8851672cc905/8C1ABC6C-1ED1-4959-BAE0-E1027F44CBEF.png -------------------------------------------------------------------------------- /9D05394A-A5F5-4A2B-8AA7-3C06528411D3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luckman212/alfred_clipsaver_workflow/4960833959aa35c7d612f3a3cf1e8851672cc905/9D05394A-A5F5-4A2B-8AA7-3C06528411D3.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Luke Hamburg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ClipSaver 2 | 3 | This workflow is triggered with the `cs` keyword by default. You can change the keyword if desired via the configuration panel. 4 | 5 | It presents images from Alfred's clipboard history via a Script Filter, and allows selecting images to be extracted, converted to a format of your choice (default is PNG) and placed in a folder of your choosing (default is on the Desktop, in a folder called `saved_clips`). You can optionally set the `save_to_current` environment variable to have the workflow save to the currently active Finder window. 6 | 7 | Upon success, the original clipboard entry as well as the .tiff from Alfred's database will be removed if you have `delete_after_convert` set to true (File List objects in the clipboard history will remain untouched). 8 | 9 | Some features require [CleanShot](https://cleanshot.com/) to be installed. If you don't have CleanShot, you can still use the other features of the workflow. 10 | - Holding ⇧shift while actioning from the Grid View will Pin (float) the selected images to the screen 11 | - Holding ⌥option will open the images in the Annotation (edit) tool. 12 | 13 | ## Grid View (Alfred 5.5 only) 14 | 15 | As of version 2.4, the workflow also supports Alfred 5.5's new "Grid View" layout for previewing and actioning images. You can drag and drop images out of the grid with the mouse. While in Grid mode, press ⌘0 to toggle a larger view. You can use the File Buffer to select multiple images and action them. 16 | 17 | > _If the File Buffer contains contents, they will be used instead of the "last selection"._ 18 | 19 | ## Workflow Configuration 20 | 21 | All configuration is optional. The workflow should work "out of the box" without configuring any of these items. They are here for advanced users only. 22 | 23 | - `db_name` - override to specify a custom clipboard database file if needed 24 | - `db_path` - override the default clipboard database path 25 | - `dest_dir` where to save images if `save_to_current == false`, defaults to `~/Desktop/saved_clips` 26 | - `sf_clip_limit` (default: empty) an optional limit to constrain the number of results displayed in the Script Filter 27 | - `save_to_current` (default: false) - set to `true` if you want the workflow to put saved images in the directory of the "frontmost" Finder window 28 | - `reveal_after` (default: true) - set to `false` if you do not want the Finder to reveal the saved clips after the workflow runs. 29 | - `default_format` (default: png) - set to e.g. `jpg` etc. You can override per invocation by passing as an argument (use `sips --formats` to see all available formats) 30 | - `delete_after_convert` (default: false) - set to `true` if you want the source images deleted after successful conversion 31 | 32 | ## Usage Tips 33 | 34 | - Tap `⇧SHIFT` while navigating through Script Filter results to get a QuickLook preview of the image. While QL is displayed, you can use the Arrow keys to flip through results. 35 | - Tap `⌥OPTION` to show the path to the app that created that image in the subtitle 36 | - Hold `⌘CMD` when actioning the item to Reveal the original image in Alfred's db folder 37 | - Pass a number as an argument to the script to save X number of clips in bulk 38 | - Pass an integer followed by `s|m|h|d` (sec,min,hrs,days) e.g. `90m` to save the clips captured in the last 90 minutes 39 | - Pass an image format e.g. `jpeg` as an argument to override your default format 40 | 41 | ## Screenshot: 42 | 43 | 44 | ## Download: 45 | https://github.com/luckman212/alfred_clipsaver_workflow/releases/latest/ 46 | 47 | ## Forum topic: 48 | https://www.alfredforum.com/topic/14400-clipsaver-save-images-from-clipboard-history-to-png-files/ 49 | -------------------------------------------------------------------------------- /alfred_validate_cache.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # cleans up orphan image files that no longer exist in Alfred's db 4 | 5 | if [[ -z $db_path ]]; then 6 | db_path='Library/Application Support/Alfred/Databases/' 7 | fi 8 | 9 | if [[ -z $db_name ]]; then 10 | db_name='clipboard.alfdb' 11 | fi 12 | 13 | orphan_dir=$HOME/Desktop/orphan_images 14 | 15 | function open_orphan_dir() { 16 | if [[ -d $orphan_dir ]]; then 17 | open -a Finder "$orphan_dir" 18 | fi 19 | } 20 | 21 | case $1 in 22 | -o|--open) #open dirs 23 | open -a Finder "$db_path/$db_name.data" 24 | open_orphan_dir 25 | exit 26 | ;; 27 | esac 28 | 29 | # populate array with list of image clips from database 30 | clipArray=() 31 | while IFS= read -r line; do clipArray+=("$line"); done < <(/usr/bin/sqlite3 "$db_path/$db_name" "SELECT dataHash FROM clipboard WHERE dataType = 1;") 32 | 33 | # loop thru all tiffs in database storage folder, and validate 34 | cd "$db_path/$db_name.data" || { echo "could not cd to db storage folder"; exit 1; } 35 | 36 | shopt -s nullglob # enable 37 | for i in *.tiff *.png ; do 38 | if [[ ! " ${clipArray[@]} " =~ " ${i} " ]]; then 39 | echo "orphan: $i" 40 | if [[ ! -d $orphan_dir ]]; then 41 | if ! mkdir -p "$orphan_dir"; then 42 | echo "failed to create $orphan_dir" 43 | exit 1 44 | fi 45 | fi 46 | mv "$i" "$orphan_dir/" 47 | fi 48 | done 49 | shopt -u nullglob # disable 50 | 51 | open_orphan_dir 52 | -------------------------------------------------------------------------------- /cleanshot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh --no-rcs 2 | 3 | # URI scheme: https://cleanshot.com/docs-api 4 | 5 | # ensure deps 6 | if ! hash pngpaste &>/dev/null; then brew install --quiet pngpaste; fi 7 | 8 | # workaround for CleanShot bug 9 | # open-annotate verb does not work for TIFFs (bugreport has been filed!) 10 | _convert_to_png() { 11 | temp_dir=${temp_dir:-/private/tmp/converted_images} 12 | if [[ ! -d ${temp_dir} ]]; then 13 | mkdir -p "${temp_dir}" 14 | else 15 | find "${temp_dir}" -type f -ctime +10m -delete 2>/dev/null 16 | fi 17 | for img_pathname in "$@"; do 18 | [[ -r ${img_pathname} ]] || continue 19 | [[ ${(L)img_pathname} == *.png ]] && continue 20 | img_basename=${img_pathname##*/} 21 | img_noext="${img_basename%.*}" 22 | out_fname="$temp_dir/${img_noext}.png" 23 | if /usr/bin/sips 2>&1 \ 24 | --setProperty format png \ 25 | --out "${out_fname}" \ 26 | "${img_pathname}"; then 27 | EXCLUDE+=( "$img_pathname" ) 28 | FILEARRAY+=( "$out_fname" ) 29 | fi 30 | done 31 | } 32 | 33 | _process() { 34 | echo 1>&2 "processing: $1" 35 | open "cleanshot://${VERB}?filepath=$1" 36 | } 37 | 38 | if [[ -z $alfred_workflow_uid ]]; then 39 | echo "this script is meant to be used from within an Alfred workflow" 40 | exit 41 | fi 42 | 43 | #normalize input 44 | IFS=$'\t' read -A FILEARRAY <<<"$1" 45 | shift 46 | for a in $@ ; do 47 | FILEARRAY+=( $a ) 48 | done 49 | 50 | action=${action:-edit} 51 | #echo 1>&2 "action: $action" 52 | EXCLUDE=() 53 | 54 | case "$action" in 55 | clipboard) 56 | imgfile='/private/tmp/clipboard.png' 57 | VERB='open-annotate' 58 | if /opt/homebrew/bin/pngpaste 2>/dev/null $imgfile ; then 59 | _process $imgfile 60 | else 61 | /usr/bin/afplay /System/Library/Sounds/Bottle.aiff 62 | fi 63 | exit 64 | ;; 65 | float|pin) 66 | VERB='pin' 67 | ;; 68 | edit|annotate) 69 | VERB='open-annotate' 70 | _convert_to_png "${FILEARRAY[@]}" 71 | ;; 72 | esac 73 | 74 | #about the zsh ${FOO:|BAR} syntax (array subtraction expression) 75 | #returns elements of $FILEARRAY that are not present in $EXCLUDE 76 | #https://zsh.sourceforge.io/Doc/Release/Expansion.html#Parameter-Expansion 77 | #https://chatgpt.com/c/177a7413-03a8-47a0-a3d4-92660eee188a 78 | for f in ${FILEARRAY:|EXCLUDE}; do 79 | _process "$f" 80 | done 81 | -------------------------------------------------------------------------------- /clipsaver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luckman212/alfred_clipsaver_workflow/4960833959aa35c7d612f3a3cf1e8851672cc905/clipsaver.png -------------------------------------------------------------------------------- /clock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luckman212/alfred_clipsaver_workflow/4960833959aa35c7d612f3a3cf1e8851672cc905/clock.png -------------------------------------------------------------------------------- /cs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import re 5 | import sys 6 | import json 7 | import sqlite3 8 | import plistlib 9 | import datetime as dt 10 | from contextlib import contextmanager 11 | from wf_common import * 12 | #from pathlib import Path 13 | 14 | @contextmanager 15 | def database(path): 16 | db = sqlite3.connect(path) 17 | yield db 18 | db.close() 19 | 20 | def append_item(fn, img, title, sub, srcapp, ctime): 21 | global uidSeed, fmt, items 22 | if not os.path.exists(img): 23 | return 24 | il = img.lower() 25 | if not any(i['arg'] == il for i in items): 26 | items.append({ 27 | "uid": ''.join([ uidSeed, '.', fn ]), 28 | "variables": { "uidSeed": uidSeed }, 29 | "title": title, 30 | "subtitle": f'{ctime} ↩ save as {fmt.upper()}', 31 | "arg": il, 32 | "type": "file:skipcheck", 33 | "variables": { 34 | "format": fmt.lower(), 35 | "dataHash": fn, 36 | "action": 'single' 37 | }, 38 | "icon": { "path": img }, 39 | "mods": { 40 | "ctrl": { 41 | "arg": img, 42 | "subtitle": f'{ctime} ↩ copy to clipboard', 43 | "variables": { "action": 'copy' } 44 | }, 45 | "alt": { 46 | "arg": il, 47 | "subtitle": f'from: {sub}' 48 | }, 49 | "cmd": { 50 | "arg": img, 51 | "subtitle": f'{ctime} ↩ reveal in Finder', 52 | "variables": { "action": 'reveal' } 53 | } 54 | }, 55 | "quicklookurl": img 56 | }) 57 | 58 | def listitems(fmt='png', num=1, since=None, human=None): 59 | if since and human: 60 | atleast = int(dt.datetime.now().timestamp()) - since - 978307200 61 | items.append({ 62 | "title": f'↩ save time-filtered clips below (last {human}) as {fmt.upper()}', 63 | "arg": atleast, 64 | "icon": { "path": "clock.png" }, 65 | "variables": { 66 | "action": 'multisave_time', 67 | "format": fmt.lower() 68 | }, 69 | "quicklookurl": '' 70 | }) 71 | else: 72 | atleast = 0 73 | if num > 1: 74 | items.append({ 75 | "title": f'↩ save last {num} clips as {fmt.upper()}', 76 | "arg": num, 77 | "variables": { 78 | "action": 'multisave', 79 | "format": fmt.lower() 80 | }, 81 | "quicklookurl": '' 82 | }) 83 | with database(db_res) as db: 84 | #0=filename, 1=title, 2=src app, 3=time, 4=type (1=image from db,2=files) 85 | rows = db.execute("SELECT dataHash,item,apppath,ts,dataType from clipboard WHERE dataType IN (1,2) AND ts >= ? ORDER BY rowid DESC LIMIT ?", [atleast, sf_clip_limit]) 86 | for r in rows: 87 | (fn, title, srcapp, ts, dtype) = r 88 | ts += 978307200 89 | ctime = dt.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M') 90 | img = os.path.join(i_path, fn) 91 | if dtype == 2: 92 | try: 93 | with open(img, "rb") as fp: 94 | plistobj = plistlib.load(fp) 95 | except: 96 | continue 97 | imgfiles = [i for i in plistobj if os.path.exists(i) and i.split(os.extsep)[-1].lower() in img_exts] 98 | for f in imgfiles: 99 | title = ' '.join([ "File:", f ]) 100 | sub = f"File List {fn}" 101 | append_item(fn,f,title,sub,srcapp,ctime) 102 | else: 103 | sub = srcapp or "(unknown)" 104 | append_item(fn,img,title,sub,srcapp,ctime) 105 | 106 | try: 107 | args = sys.argv[1].split() 108 | except: 109 | args = [] 110 | pass 111 | 112 | items = [] 113 | arg = fmt = sr_n = sr_str = None 114 | num = 1 115 | 116 | # poor man's arg parser 117 | for a in args: 118 | try: 119 | num = int(a) 120 | continue 121 | except: 122 | pass 123 | try: 124 | sr = re.match(r'([0-9]+)([smhd])', a, re.IGNORECASE) 125 | sr_str = sr.group(0) 126 | sr_n = int(sr.group(1)) 127 | sr_u = sr.group(2).lower() 128 | if sr_u == 'm': 129 | sr_n *= 60 130 | if sr_u == 'h': 131 | sr_n *= 3600 132 | if sr_u == 'd': 133 | sr_n *= 86400 134 | continue 135 | except: 136 | pass 137 | fmt = a.lower() 138 | 139 | if num < 1: 140 | num = 1 141 | if fmt is None or len(str(fmt)) < 3: 142 | fmt = default_format 143 | if fmt and fmt.upper() == 'JPG': 144 | fmt = 'jpeg' 145 | 146 | """ 147 | print(f'fmt:{fmt}',file=sys.stderr) 148 | print(f'num:{num}',file=sys.stderr) 149 | print(f'sr_n:{sr_n}',file=sys.stderr) 150 | """ 151 | 152 | listitems(fmt=fmt, num=num, since=sr_n, human=sr_str) 153 | 154 | if not items: 155 | items = [{ 156 | "title": "No image clips found", 157 | "subtitle": "clipboard history may be empty", 158 | "icon": { "path": "error.png" }, 159 | "valid": False 160 | }] 161 | 162 | variables = { 163 | 'uidSeed': uidSeed, 164 | 'delete_after_convert': delete_after_convert, 165 | 'save_to_current': save_to_current, 166 | 'db_name': db_name, 167 | 'db_path': db_path, 168 | 'dest_dir': dest_dir, 169 | 'default_format': default_format 170 | } 171 | 172 | items.insert(0, { 173 | "title": "Grid View", 174 | "subtitle": "Display results in enlarged Grid View", 175 | "arg": "", 176 | "icon": { "path": "grid.png" }, 177 | "variables": { "action": "grid" } 178 | }) 179 | 180 | output = { 181 | "variables": variables, 182 | "skipknowledge": True, 183 | "items": items 184 | } 185 | 186 | json.dump(output, sys.stdout) 187 | -------------------------------------------------------------------------------- /error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luckman212/alfred_clipsaver_workflow/4960833959aa35c7d612f3a3cf1e8851672cc905/error.png -------------------------------------------------------------------------------- /generic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luckman212/alfred_clipsaver_workflow/4960833959aa35c7d612f3a3cf1e8851672cc905/generic.png -------------------------------------------------------------------------------- /get_image_hashes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | import sqlite3 6 | import plistlib 7 | import datetime as dt 8 | from contextlib import contextmanager 9 | from wf_common import * 10 | 11 | @contextmanager 12 | def database(path): 13 | db = sqlite3.connect(path) 14 | yield db 15 | db.close() 16 | 17 | def get_image_hashes(atleast=0, num_clips=None): 18 | with database(db_res) as db: 19 | rows = db.execute("SELECT dataHash,dataType from clipboard WHERE dataType IN (1,2) AND ts >= ? ORDER BY rowid DESC", [atleast]) 20 | rc = 0 21 | for r in rows: 22 | if num_clips and rc >= num_clips: 23 | break 24 | (fn, dtype) = r 25 | img = os.path.join(i_path, fn) 26 | if dtype == 2: 27 | try: 28 | with open(img, "rb") as fp: 29 | plistobj = plistlib.load(fp) 30 | except: 31 | continue 32 | imgfiles = [i for i in plistobj if os.path.exists(i) and i.split(os.extsep)[-1].lower() in img_exts] 33 | for f in imgfiles: 34 | rc += 1 35 | print(f) 36 | if num_clips and rc >= num_clips: 37 | break 38 | else: 39 | rc += 1 40 | print(fn) 41 | if num_clips and rc >= num_clips: 42 | break 43 | 44 | if __name__ == '__main__': 45 | args = sys.argv[1:] 46 | if args[0] == '--atleast': 47 | get_image_hashes(atleast=int(args[1])) 48 | elif args[0] == '--num_clips': 49 | get_image_hashes(num_clips=int(args[1])) 50 | else: 51 | exit(1) 52 | -------------------------------------------------------------------------------- /grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luckman212/alfred_clipsaver_workflow/4960833959aa35c7d612f3a3cf1e8851672cc905/grid.png -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luckman212/alfred_clipsaver_workflow/4960833959aa35c7d612f3a3cf1e8851672cc905/icon.png -------------------------------------------------------------------------------- /info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bundleid 6 | com.alfredapp.clipsaver 7 | connections 8 | 9 | 0C8B45F4-FEAE-493C-8531-48AE675F5DE1 10 | 11 | 12 | destinationuid 13 | FF2B9F42-D9A9-42D6-ABDB-8691AC88D747 14 | modifiers 15 | 0 16 | modifiersubtext 17 | 18 | vitoclose 19 | 20 | 21 | 22 | 15A2426A-6614-4325-9014-873CE2FCAEEE 23 | 24 | 25 | destinationuid 26 | 4E6033E8-BC67-4836-B299-F64154D52F1A 27 | modifiers 28 | 0 29 | modifiersubtext 30 | 31 | vitoclose 32 | 33 | 34 | 35 | 2601B1DD-8EAB-4B3C-AEE2-5BFFE45ED5E5 36 | 37 | 38 | destinationuid 39 | CCD4FC35-EA9E-47A0-97D7-123CE12B9F47 40 | modifiers 41 | 0 42 | modifiersubtext 43 | 44 | vitoclose 45 | 46 | 47 | 48 | 2C3159FB-317C-4678-993E-1BBB9141095B 49 | 50 | 51 | destinationuid 52 | FF2B9F42-D9A9-42D6-ABDB-8691AC88D747 53 | modifiers 54 | 0 55 | modifiersubtext 56 | 57 | vitoclose 58 | 59 | 60 | 61 | 330B881A-5B6F-4415-B32E-4F62B9A7864E 62 | 63 | 64 | destinationuid 65 | 33CD6F02-65A3-4497-9C85-E38FDE97FC8D 66 | modifiers 67 | 0 68 | modifiersubtext 69 | 70 | vitoclose 71 | 72 | 73 | 74 | destinationuid 75 | AB178842-6707-4C5B-88D1-FC317394D8F9 76 | modifiers 77 | 1048576 78 | modifiersubtext 79 | Reveal 80 | vitoclose 81 | 82 | 83 | 84 | destinationuid 85 | A3FBF3CE-3218-4FC1-A8AC-9D056C1C948B 86 | modifiers 87 | 262144 88 | modifiersubtext 89 | Copy image to clipboard 90 | vitoclose 91 | 92 | 93 | 94 | destinationuid 95 | 4E6033E8-BC67-4836-B299-F64154D52F1A 96 | modifiers 97 | 131072 98 | modifiersubtext 99 | Float (Pin) this image using CleanShot 100 | vitoclose 101 | 102 | 103 | 104 | 33CD6F02-65A3-4497-9C85-E38FDE97FC8D 105 | 106 | 107 | destinationuid 108 | 519C25B7-FDE4-4EA5-AF9B-7460EEDC70CE 109 | modifiers 110 | 0 111 | modifiersubtext 112 | 113 | vitoclose 114 | 115 | 116 | 117 | 395E6EB2-E24E-42B0-891C-2AE7E16160F2 118 | 119 | 120 | destinationuid 121 | 7D984E52-7568-4C03-B70F-92EFA7067021 122 | modifiers 123 | 0 124 | modifiersubtext 125 | 126 | vitoclose 127 | 128 | 129 | 130 | 40ABDE7E-4F25-4424-94E4-9489906D568D 131 | 132 | 133 | destinationuid 134 | 4F366E5F-E737-440C-B9A3-FC859781F3DE 135 | disabled 136 | 137 | modifiers 138 | 0 139 | modifiersubtext 140 | 141 | vitoclose 142 | 143 | 144 | 145 | 510F5A09-C26E-49C1-8C80-4AE3C0411E36 146 | 147 | 148 | destinationuid 149 | 70A3AE8F-5B75-4470-89FC-73298C06596C 150 | modifiers 151 | 0 152 | modifiersubtext 153 | 154 | vitoclose 155 | 156 | 157 | 158 | destinationuid 159 | F47F1B80-82C9-49BA-9CF2-DBD52631E160 160 | disabled 161 | 162 | modifiers 163 | 0 164 | modifiersubtext 165 | 166 | vitoclose 167 | 168 | 169 | 170 | 519C25B7-FDE4-4EA5-AF9B-7460EEDC70CE 171 | 172 | 173 | destinationuid 174 | E3B4F7E0-C8D4-4C51-A42A-25FF8B353E84 175 | modifiers 176 | 0 177 | modifiersubtext 178 | 179 | sourceoutputuid 180 | 264D1DEE-5F29-4719-BF23-EE32ACFA308E 181 | vitoclose 182 | 183 | 184 | 185 | destinationuid 186 | C8999852-47D6-4E54-ACBA-03F7186AF696 187 | modifiers 188 | 0 189 | modifiersubtext 190 | 191 | sourceoutputuid 192 | AAF941DB-A595-4072-BD25-6BCC55A93AE2 193 | vitoclose 194 | 195 | 196 | 197 | 5CC78342-FA2A-459C-A878-E2DC3621154B 198 | 199 | 200 | destinationuid 201 | EE17BA6D-5748-4F20-BE74-602EB8B9849F 202 | modifiers 203 | 1048576 204 | modifiersubtext 205 | Reveal in Finder 206 | vitoclose 207 | 208 | 209 | 210 | destinationuid 211 | 330B881A-5B6F-4415-B32E-4F62B9A7864E 212 | modifiers 213 | 0 214 | modifiersubtext 215 | 216 | vitoclose 217 | 218 | 219 | 220 | destinationuid 221 | E30762D4-CD07-433B-8B2D-AD06552A2225 222 | modifiers 223 | 262144 224 | modifiersubtext 225 | Copy image to clipboard 226 | vitoclose 227 | 228 | 229 | 230 | destinationuid 231 | 0C8B45F4-FEAE-493C-8531-48AE675F5DE1 232 | modifiers 233 | 524288 234 | modifiersubtext 235 | Press ↵ to edit (annotate) selected images using CleanShot 236 | vitoclose 237 | 238 | 239 | 240 | destinationuid 241 | 2C3159FB-317C-4678-993E-1BBB9141095B 242 | modifiers 243 | 131072 244 | modifiersubtext 245 | Press ↵ to float (pin) selected images using CleanShot 246 | vitoclose 247 | 248 | 249 | 250 | 5E9A0126-8C80-41E7-AD49-590FE0777A91 251 | 252 | 253 | destinationuid 254 | A8AB7CFC-4F7B-436E-8EAD-C48244230F84 255 | modifiers 256 | 0 257 | modifiersubtext 258 | 259 | vitoclose 260 | 261 | 262 | 263 | 68014C82-BAD9-4CFC-A6C1-A10A58F10E79 264 | 265 | 266 | destinationuid 267 | 4E6033E8-BC67-4836-B299-F64154D52F1A 268 | modifiers 269 | 0 270 | modifiersubtext 271 | 272 | vitoclose 273 | 274 | 275 | 276 | 6C55B9DC-6A05-4228-AD55-2311DD125885 277 | 278 | 279 | destinationuid 280 | 40ABDE7E-4F25-4424-94E4-9489906D568D 281 | modifiers 282 | 0 283 | modifiersubtext 284 | 285 | vitoclose 286 | 287 | 288 | 289 | destinationuid 290 | C8999852-47D6-4E54-ACBA-03F7186AF696 291 | modifiers 292 | 0 293 | modifiersubtext 294 | 295 | vitoclose 296 | 297 | 298 | 299 | 70A3AE8F-5B75-4470-89FC-73298C06596C 300 | 301 | 302 | destinationuid 303 | A0D95AE6-FB7D-4C9D-BD5E-835FA2DC88F0 304 | modifiers 305 | 0 306 | modifiersubtext 307 | 308 | sourceoutputuid 309 | F6522B23-6C5B-497A-AE5B-26E913E3BD50 310 | vitoclose 311 | 312 | 313 | 314 | destinationuid 315 | 2601B1DD-8EAB-4B3C-AEE2-5BFFE45ED5E5 316 | modifiers 317 | 0 318 | modifiersubtext 319 | 320 | vitoclose 321 | 322 | 323 | 324 | 71A0EF86-42D5-4C0A-8656-CB6FEAD6F857 325 | 326 | 327 | destinationuid 328 | F8D8F9B0-A655-474F-9D3C-68866A8D85E6 329 | modifiers 330 | 0 331 | modifiersubtext 332 | 333 | vitoclose 334 | 335 | 336 | 337 | 7D984E52-7568-4C03-B70F-92EFA7067021 338 | 339 | 340 | destinationuid 341 | 5CC78342-FA2A-459C-A878-E2DC3621154B 342 | modifiers 343 | 0 344 | modifiersubtext 345 | 346 | vitoclose 347 | 348 | 349 | 350 | 9D05394A-A5F5-4A2B-8AA7-3C06528411D3 351 | 352 | 353 | destinationuid 354 | 68014C82-BAD9-4CFC-A6C1-A10A58F10E79 355 | modifiers 356 | 0 357 | modifiersubtext 358 | 359 | vitoclose 360 | 361 | 362 | 363 | A02D5916-1E4A-4207-80F9-AE36DF096158 364 | 365 | 366 | destinationuid 367 | A8AB7CFC-4F7B-436E-8EAD-C48244230F84 368 | modifiers 369 | 0 370 | modifiersubtext 371 | 372 | vitoclose 373 | 374 | 375 | 376 | A0D95AE6-FB7D-4C9D-BD5E-835FA2DC88F0 377 | 378 | 379 | destinationuid 380 | CCD4FC35-EA9E-47A0-97D7-123CE12B9F47 381 | modifiers 382 | 0 383 | modifiersubtext 384 | 385 | vitoclose 386 | 387 | 388 | 389 | A8AB7CFC-4F7B-436E-8EAD-C48244230F84 390 | 391 | 392 | destinationuid 393 | 6C55B9DC-6A05-4228-AD55-2311DD125885 394 | modifiers 395 | 0 396 | modifiersubtext 397 | 398 | sourceoutputuid 399 | 57CB54B3-8B1E-4F86-8390-AC626FA235F7 400 | vitoclose 401 | 402 | 403 | 404 | destinationuid 405 | E3B4F7E0-C8D4-4C51-A42A-25FF8B353E84 406 | modifiers 407 | 0 408 | modifiersubtext 409 | 410 | sourceoutputuid 411 | 2A316F1C-9FE9-43A3-A61D-F5CEFDF2420D 412 | vitoclose 413 | 414 | 415 | 416 | destinationuid 417 | C8999852-47D6-4E54-ACBA-03F7186AF696 418 | modifiers 419 | 0 420 | modifiersubtext 421 | 422 | sourceoutputuid 423 | AAF941DB-A595-4072-BD25-6BCC55A93AE2 424 | vitoclose 425 | 426 | 427 | 428 | C8999852-47D6-4E54-ACBA-03F7186AF696 429 | 430 | 431 | destinationuid 432 | 40ABDE7E-4F25-4424-94E4-9489906D568D 433 | modifiers 434 | 0 435 | modifiersubtext 436 | 437 | sourceoutputuid 438 | 49A1510D-1004-4FAD-B281-0A8703BD274F 439 | vitoclose 440 | 441 | 442 | 443 | destinationuid 444 | F34B0F25-D875-426D-8071-3FACA31F0AE1 445 | modifiers 446 | 0 447 | modifiersubtext 448 | 449 | sourceoutputuid 450 | 49A1510D-1004-4FAD-B281-0A8703BD274F 451 | vitoclose 452 | 453 | 454 | 455 | CCD4FC35-EA9E-47A0-97D7-123CE12B9F47 456 | 457 | 458 | destinationuid 459 | 4E6033E8-BC67-4836-B299-F64154D52F1A 460 | modifiers 461 | 0 462 | modifiersubtext 463 | 464 | vitoclose 465 | 466 | 467 | 468 | E30762D4-CD07-433B-8B2D-AD06552A2225 469 | 470 | 471 | destinationuid 472 | A3FBF3CE-3218-4FC1-A8AC-9D056C1C948B 473 | modifiers 474 | 0 475 | modifiersubtext 476 | 477 | vitoclose 478 | 479 | 480 | 481 | E3B4F7E0-C8D4-4C51-A42A-25FF8B353E84 482 | 483 | 484 | destinationuid 485 | 40ABDE7E-4F25-4424-94E4-9489906D568D 486 | modifiers 487 | 0 488 | modifiersubtext 489 | 490 | vitoclose 491 | 492 | 493 | 494 | EE17BA6D-5748-4F20-BE74-602EB8B9849F 495 | 496 | 497 | destinationuid 498 | AB178842-6707-4C5B-88D1-FC317394D8F9 499 | modifiers 500 | 0 501 | modifiersubtext 502 | 503 | vitoclose 504 | 505 | 506 | 507 | F34B0F25-D875-426D-8071-3FACA31F0AE1 508 | 509 | F8D8F9B0-A655-474F-9D3C-68866A8D85E6 510 | 511 | 512 | destinationuid 513 | A02D5916-1E4A-4207-80F9-AE36DF096158 514 | modifiers 515 | 0 516 | modifiersubtext 517 | 518 | sourceoutputuid 519 | B3345938-CA67-4E6A-B476-62E1ADB3B7EF 520 | vitoclose 521 | 522 | 523 | 524 | destinationuid 525 | 5E9A0126-8C80-41E7-AD49-590FE0777A91 526 | modifiers 527 | 0 528 | modifiersubtext 529 | 530 | sourceoutputuid 531 | 0D12874B-03FB-467B-BCBB-6BECAB99484A 532 | vitoclose 533 | 534 | 535 | 536 | destinationuid 537 | 33CD6F02-65A3-4497-9C85-E38FDE97FC8D 538 | modifiers 539 | 0 540 | modifiersubtext 541 | 542 | sourceoutputuid 543 | 133259D6-46D2-493B-9A52-1D6DA9ACA303 544 | vitoclose 545 | 546 | 547 | 548 | destinationuid 549 | EE17BA6D-5748-4F20-BE74-602EB8B9849F 550 | modifiers 551 | 0 552 | modifiersubtext 553 | 554 | sourceoutputuid 555 | 951960C5-0D7C-4BE6-8488-FE4C72278C90 556 | vitoclose 557 | 558 | 559 | 560 | destinationuid 561 | 395E6EB2-E24E-42B0-891C-2AE7E16160F2 562 | modifiers 563 | 0 564 | modifiersubtext 565 | 566 | sourceoutputuid 567 | 2F385C67-1D18-4D25-8AA7-EF492F9A4E7B 568 | vitoclose 569 | 570 | 571 | 572 | destinationuid 573 | E30762D4-CD07-433B-8B2D-AD06552A2225 574 | modifiers 575 | 0 576 | modifiersubtext 577 | 578 | sourceoutputuid 579 | B9B2E13B-5FFF-4E6B-98AA-4A849E116285 580 | vitoclose 581 | 582 | 583 | 584 | FF2B9F42-D9A9-42D6-ABDB-8691AC88D747 585 | 586 | 587 | destinationuid 588 | 510F5A09-C26E-49C1-8C80-4AE3C0411E36 589 | modifiers 590 | 0 591 | modifiersubtext 592 | 593 | vitoclose 594 | 595 | 596 | 597 | 598 | createdby 599 | luckman212 600 | description 601 | Saves images from your clipboard history 602 | disabled 603 | 604 | name 605 | ClipSaver 606 | objects 607 | 608 | 609 | config 610 | 611 | concurrently 612 | 613 | escaping 614 | 0 615 | script 616 | 617 | scriptargtype 618 | 1 619 | scriptfile 620 | save_multi_time.sh 621 | type 622 | 8 623 | 624 | type 625 | alfred.workflow.action.script 626 | uid 627 | A02D5916-1E4A-4207-80F9-AE36DF096158 628 | version 629 | 2 630 | 631 | 632 | config 633 | 634 | lastpathcomponent 635 | 636 | onlyshowifquerypopulated 637 | 638 | removeextension 639 | 640 | text 641 | {var:errmsg} 642 | title 643 | Warning 644 | 645 | type 646 | alfred.workflow.output.notification 647 | uid 648 | 6C55B9DC-6A05-4228-AD55-2311DD125885 649 | version 650 | 1 651 | 652 | 653 | config 654 | 655 | concurrently 656 | 657 | escaping 658 | 0 659 | script 660 | 661 | scriptargtype 662 | 1 663 | scriptfile 664 | save_multi.sh 665 | type 666 | 8 667 | 668 | type 669 | alfred.workflow.action.script 670 | uid 671 | 5E9A0126-8C80-41E7-AD49-590FE0777A91 672 | version 673 | 2 674 | 675 | 676 | config 677 | 678 | conditions 679 | 680 | 681 | inputstring 682 | {var:exitcode} 683 | matchcasesensitive 684 | 685 | matchmode 686 | 0 687 | matchstring 688 | 1 689 | outputlabel 690 | warning 691 | uid 692 | 57CB54B3-8B1E-4F86-8390-AC626FA235F7 693 | 694 | 695 | inputstring 696 | {var:exitcode} 697 | matchcasesensitive 698 | 699 | matchmode 700 | 2 701 | matchstring 702 | 1 703 | outputlabel 704 | failure 705 | uid 706 | 2A316F1C-9FE9-43A3-A61D-F5CEFDF2420D 707 | 708 | 709 | inputstring 710 | {var:exitcode} 711 | matchcasesensitive 712 | 713 | matchmode 714 | 0 715 | matchstring 716 | 0 717 | outputlabel 718 | success 719 | uid 720 | AAF941DB-A595-4072-BD25-6BCC55A93AE2 721 | 722 | 723 | elselabel 724 | 725 | hideelse 726 | 727 | 728 | type 729 | alfred.workflow.utility.conditional 730 | uid 731 | A8AB7CFC-4F7B-436E-8EAD-C48244230F84 732 | version 733 | 1 734 | 735 | 736 | config 737 | 738 | conditions 739 | 740 | 741 | inputstring 742 | {var:action} 743 | matchcasesensitive 744 | 745 | matchmode 746 | 0 747 | matchstring 748 | multisave_time 749 | outputlabel 750 | multisave_time 751 | uid 752 | B3345938-CA67-4E6A-B476-62E1ADB3B7EF 753 | 754 | 755 | inputstring 756 | {var:action} 757 | matchcasesensitive 758 | 759 | matchmode 760 | 0 761 | matchstring 762 | multisave 763 | outputlabel 764 | multisave 765 | uid 766 | 0D12874B-03FB-467B-BCBB-6BECAB99484A 767 | 768 | 769 | inputstring 770 | {var:action} 771 | matchcasesensitive 772 | 773 | matchmode 774 | 0 775 | matchstring 776 | single 777 | outputlabel 778 | single 779 | uid 780 | 133259D6-46D2-493B-9A52-1D6DA9ACA303 781 | 782 | 783 | inputstring 784 | {var:action} 785 | matchcasesensitive 786 | 787 | matchmode 788 | 0 789 | matchstring 790 | reveal 791 | outputlabel 792 | reveal 793 | uid 794 | 951960C5-0D7C-4BE6-8488-FE4C72278C90 795 | 796 | 797 | inputstring 798 | {var:action} 799 | matchcasesensitive 800 | 801 | matchmode 802 | 0 803 | matchstring 804 | copy 805 | outputlabel 806 | copy 807 | uid 808 | B9B2E13B-5FFF-4E6B-98AA-4A849E116285 809 | 810 | 811 | inputstring 812 | {var:action} 813 | matchcasesensitive 814 | 815 | matchmode 816 | 0 817 | matchstring 818 | grid 819 | outputlabel 820 | grid 821 | uid 822 | 2F385C67-1D18-4D25-8AA7-EF492F9A4E7B 823 | 824 | 825 | elselabel 826 | 827 | hideelse 828 | 829 | 830 | type 831 | alfred.workflow.utility.conditional 832 | uid 833 | F8D8F9B0-A655-474F-9D3C-68866A8D85E6 834 | version 835 | 1 836 | 837 | 838 | config 839 | 840 | alfredfiltersresults 841 | 842 | alfredfiltersresultsmatchmode 843 | 2 844 | argumenttreatemptyqueryasnil 845 | 846 | argumenttrimmode 847 | 0 848 | argumenttype 849 | 1 850 | escaping 851 | 0 852 | keyword 853 | {var:kw_trigger} 854 | queuedelaycustom 855 | 3 856 | queuedelayimmediatelyinitially 857 | 858 | queuedelaymode 859 | 0 860 | queuemode 861 | 1 862 | runningsubtext 863 | Fetching clipboard items 864 | script 865 | 866 | scriptargtype 867 | 1 868 | scriptfile 869 | cs.py 870 | subtext 871 | 872 | title 873 | Save images from clipboard history 874 | type 875 | 8 876 | withspace 877 | 878 | 879 | type 880 | alfred.workflow.input.scriptfilter 881 | uid 882 | 71A0EF86-42D5-4C0A-8656-CB6FEAD6F857 883 | version 884 | 3 885 | 886 | 887 | config 888 | 889 | lastpathcomponent 890 | 891 | onlyshowifquerypopulated 892 | 893 | removeextension 894 | 895 | text 896 | Clipboard history image conversion failed {var:errmsg} 897 | title 898 | Error 899 | 900 | type 901 | alfred.workflow.output.notification 902 | uid 903 | E3B4F7E0-C8D4-4C51-A42A-25FF8B353E84 904 | version 905 | 1 906 | 907 | 908 | config 909 | 910 | concurrently 911 | 912 | escaping 913 | 0 914 | script 915 | 916 | scriptargtype 917 | 1 918 | scriptfile 919 | save_single.sh 920 | type 921 | 8 922 | 923 | type 924 | alfred.workflow.action.script 925 | uid 926 | 33CD6F02-65A3-4497-9C85-E38FDE97FC8D 927 | version 928 | 2 929 | 930 | 931 | config 932 | 933 | argument 934 | '{query}', {variables} 935 | cleardebuggertext 936 | 937 | processoutputs 938 | 939 | 940 | type 941 | alfred.workflow.utility.debug 942 | uid 943 | 4F366E5F-E737-440C-B9A3-FC859781F3DE 944 | version 945 | 1 946 | 947 | 948 | type 949 | alfred.workflow.utility.junction 950 | uid 951 | 40ABDE7E-4F25-4424-94E4-9489906D568D 952 | version 953 | 1 954 | 955 | 956 | config 957 | 958 | conditions 959 | 960 | 961 | inputstring 962 | {var:exitcode} 963 | matchcasesensitive 964 | 965 | matchmode 966 | 2 967 | matchstring 968 | 0 969 | outputlabel 970 | failure 971 | uid 972 | 264D1DEE-5F29-4719-BF23-EE32ACFA308E 973 | 974 | 975 | inputstring 976 | {var:exitcode} 977 | matchcasesensitive 978 | 979 | matchmode 980 | 0 981 | matchstring 982 | 0 983 | outputlabel 984 | success 985 | uid 986 | AAF941DB-A595-4072-BD25-6BCC55A93AE2 987 | 988 | 989 | elselabel 990 | 991 | hideelse 992 | 993 | 994 | type 995 | alfred.workflow.utility.conditional 996 | uid 997 | 519C25B7-FDE4-4EA5-AF9B-7460EEDC70CE 998 | version 999 | 1 1000 | 1001 | 1002 | config 1003 | 1004 | openwith 1005 | /System/Library/CoreServices/Finder.app 1006 | sourcefile 1007 | {var:dest_dir} 1008 | 1009 | type 1010 | alfred.workflow.action.openfile 1011 | uid 1012 | F34B0F25-D875-426D-8071-3FACA31F0AE1 1013 | version 1014 | 3 1015 | 1016 | 1017 | config 1018 | 1019 | conditions 1020 | 1021 | 1022 | inputstring 1023 | {var:reveal_after} 1024 | matchcasesensitive 1025 | 1026 | matchmode 1027 | 5 1028 | matchstring 1029 | 1030 | outputlabel 1031 | reveal 1032 | uid 1033 | 49A1510D-1004-4FAD-B281-0A8703BD274F 1034 | 1035 | 1036 | elselabel 1037 | 1038 | hideelse 1039 | 1040 | 1041 | type 1042 | alfred.workflow.utility.conditional 1043 | uid 1044 | C8999852-47D6-4E54-ACBA-03F7186AF696 1045 | version 1046 | 1 1047 | 1048 | 1049 | config 1050 | 1051 | path 1052 | {query} 1053 | 1054 | type 1055 | alfred.workflow.action.revealfile 1056 | uid 1057 | AB178842-6707-4C5B-88D1-FC317394D8F9 1058 | version 1059 | 1 1060 | 1061 | 1062 | type 1063 | alfred.workflow.utility.junction 1064 | uid 1065 | EE17BA6D-5748-4F20-BE74-602EB8B9849F 1066 | version 1067 | 1 1068 | 1069 | 1070 | config 1071 | 1072 | imageresizemode 1073 | 0 1074 | stackview 1075 | 1076 | 1077 | type 1078 | alfred.workflow.userinterface.image 1079 | uid 1080 | 330B881A-5B6F-4415-B32E-4F62B9A7864E 1081 | version 1082 | 1 1083 | 1084 | 1085 | config 1086 | 1087 | columncount 1088 | 4 1089 | filterable 1090 | 1091 | fixedorder 1092 | 1093 | imageaspect 1094 | 0 1095 | inputfile 1096 | 1097 | inputtype 1098 | 0 1099 | loadingtext 1100 | 1101 | showsubtitles 1102 | 1103 | showtitles 1104 | 1105 | subtitlesinfooter 1106 | 1107 | titlesinfooter 1108 | 1109 | 1110 | type 1111 | alfred.workflow.userinterface.grid 1112 | uid 1113 | 5CC78342-FA2A-459C-A878-E2DC3621154B 1114 | version 1115 | 1 1116 | 1117 | 1118 | config 1119 | 1120 | concurrently 1121 | 1122 | escaping 1123 | 102 1124 | script 1125 | /usr/bin/sqlite3 \ 1126 | "${HOME}/Library/Application Support/Alfred/Databases/clipboard.alfdb" \ 1127 | ".parameter set :img_dir '~/Library/Application Support/Alfred/Databases/clipboard.alfdb.data/'" \ 1128 | "SELECT JSON_OBJECT('items', JSON_GROUP_ARRAY(JSON_OBJECT( 1129 | -- 978307200 is timeIntervalSinceReferenceDate in Unix time 1130 | 'title', strftime('%Y/%m/%d %H:%M:%S', datetime(ts + 978307200, 'unixepoch')), 1131 | 'subtitle', app, 1132 | 'arg', :img_dir || datahash, 1133 | 'match', app, 1134 | 'type', 'file:skipcheck', 1135 | 'icon', JSON_OBJECT('path', :img_dir || datahash)))) 1136 | AS JSON_RESULT FROM ( 1137 | SELECT ts, app, datahash 1138 | FROM clipboard 1139 | WHERE dataType IS 1 1140 | ORDER BY ts 1141 | DESC)" 1142 | scriptargtype 1143 | 1 1144 | scriptfile 1145 | 1146 | type 1147 | 11 1148 | 1149 | type 1150 | alfred.workflow.action.script 1151 | uid 1152 | 395E6EB2-E24E-42B0-891C-2AE7E16160F2 1153 | version 1154 | 2 1155 | 1156 | 1157 | config 1158 | 1159 | tasksettings 1160 | 1161 | taskuid 1162 | com.alfredapp.automation.core/macOS/clipboard.set.image.data 1163 | 1164 | type 1165 | alfred.workflow.automation.task 1166 | uid 1167 | A3FBF3CE-3218-4FC1-A8AC-9D056C1C948B 1168 | version 1169 | 1 1170 | 1171 | 1172 | config 1173 | 1174 | json 1175 | { 1176 | "alfredworkflow" : { 1177 | "arg": "{query}", 1178 | "config" : { 1179 | "columncount" : "{var:grid_size}" 1180 | } 1181 | } 1182 | } 1183 | 1184 | type 1185 | alfred.workflow.utility.json 1186 | uid 1187 | 7D984E52-7568-4C03-B70F-92EFA7067021 1188 | version 1189 | 1 1190 | 1191 | 1192 | config 1193 | 1194 | acceptsfiles 1195 | 1196 | acceptsmulti 1197 | 1 1198 | acceptstext 1199 | 1200 | acceptsurls 1201 | 1202 | name 1203 | Edit in CleanShot 1204 | 1205 | type 1206 | alfred.workflow.trigger.universalaction 1207 | uid 1208 | 15A2426A-6614-4325-9014-873CE2FCAEEE 1209 | version 1210 | 1 1211 | 1212 | 1213 | type 1214 | alfred.workflow.utility.junction 1215 | uid 1216 | E30762D4-CD07-433B-8B2D-AD06552A2225 1217 | version 1218 | 1 1219 | 1220 | 1221 | config 1222 | 1223 | concurrently 1224 | 1225 | escaping 1226 | 0 1227 | script 1228 | 1229 | scriptargtype 1230 | 1 1231 | scriptfile 1232 | ~/Sync/Scripts/cleanshot.sh 1233 | type 1234 | 8 1235 | 1236 | type 1237 | alfred.workflow.action.script 1238 | uid 1239 | 4E6033E8-BC67-4836-B299-F64154D52F1A 1240 | version 1241 | 2 1242 | 1243 | 1244 | config 1245 | 1246 | json 1247 | { 1248 | "alfredworkflow" : { 1249 | "arg" : "", 1250 | "config" : { 1251 | }, 1252 | "variables" : { 1253 | "selection": "{query}", 1254 | "buffer": "", 1255 | "action": "edit" 1256 | } 1257 | } 1258 | } 1259 | 1260 | type 1261 | alfred.workflow.utility.json 1262 | uid 1263 | 0C8B45F4-FEAE-493C-8531-48AE675F5DE1 1264 | version 1265 | 1 1266 | 1267 | 1268 | config 1269 | 1270 | addfilestobuffer 1271 | 1272 | clearbuffer 1273 | 1274 | outputtype 1275 | 0 1276 | 1277 | type 1278 | alfred.workflow.action.buffer 1279 | uid 1280 | FF2B9F42-D9A9-42D6-ABDB-8691AC88D747 1281 | version 1282 | 1 1283 | 1284 | 1285 | type 1286 | alfred.workflow.utility.junction 1287 | uid 1288 | CCD4FC35-EA9E-47A0-97D7-123CE12B9F47 1289 | version 1290 | 1 1291 | 1292 | 1293 | config 1294 | 1295 | json 1296 | { "alfredworkflow" : { "arg" : "{var:selection}" } } 1297 | 1298 | type 1299 | alfred.workflow.utility.json 1300 | uid 1301 | A0D95AE6-FB7D-4C9D-BD5E-835FA2DC88F0 1302 | version 1303 | 1 1304 | 1305 | 1306 | config 1307 | 1308 | conditions 1309 | 1310 | 1311 | inputstring 1312 | {var:buffer} 1313 | matchcasesensitive 1314 | 1315 | matchmode 1316 | 6 1317 | matchstring 1318 | 1319 | outputlabel 1320 | selection 1321 | uid 1322 | F6522B23-6C5B-497A-AE5B-26E913E3BD50 1323 | 1324 | 1325 | elselabel 1326 | buffer 1327 | hideelse 1328 | 1329 | 1330 | type 1331 | alfred.workflow.utility.conditional 1332 | uid 1333 | 70A3AE8F-5B75-4470-89FC-73298C06596C 1334 | version 1335 | 1 1336 | 1337 | 1338 | config 1339 | 1340 | json 1341 | { 1342 | "alfredworkflow" : { 1343 | "arg" : "", 1344 | "config" : { 1345 | }, 1346 | "variables" : { 1347 | "buffer": "{query}" 1348 | } 1349 | } 1350 | } 1351 | 1352 | type 1353 | alfred.workflow.utility.json 1354 | uid 1355 | 510F5A09-C26E-49C1-8C80-4AE3C0411E36 1356 | version 1357 | 1 1358 | 1359 | 1360 | config 1361 | 1362 | argument 1363 | {query} 1364 | passthroughargument 1365 | 1366 | variables 1367 | 1368 | action 1369 | pin 1370 | 1371 | 1372 | type 1373 | alfred.workflow.utility.argument 1374 | uid 1375 | 68014C82-BAD9-4CFC-A6C1-A10A58F10E79 1376 | version 1377 | 1 1378 | 1379 | 1380 | config 1381 | 1382 | json 1383 | { "alfredworkflow" : { "arg" : "{var:buffer}" } } 1384 | 1385 | type 1386 | alfred.workflow.utility.json 1387 | uid 1388 | 2601B1DD-8EAB-4B3C-AEE2-5BFFE45ED5E5 1389 | version 1390 | 1 1391 | 1392 | 1393 | config 1394 | 1395 | acceptsfiles 1396 | 1397 | acceptsmulti 1398 | 1 1399 | acceptstext 1400 | 1401 | acceptsurls 1402 | 1403 | name 1404 | Float (Pin) image on screen 1405 | 1406 | type 1407 | alfred.workflow.trigger.universalaction 1408 | uid 1409 | 9D05394A-A5F5-4A2B-8AA7-3C06528411D3 1410 | version 1411 | 1 1412 | 1413 | 1414 | config 1415 | 1416 | json 1417 | { 1418 | "alfredworkflow" : { 1419 | "arg" : "", 1420 | "config" : { 1421 | }, 1422 | "variables" : { 1423 | "selection": "{query}", 1424 | "buffer": "", 1425 | "action": "float" 1426 | } 1427 | } 1428 | } 1429 | 1430 | type 1431 | alfred.workflow.utility.json 1432 | uid 1433 | 2C3159FB-317C-4678-993E-1BBB9141095B 1434 | version 1435 | 1 1436 | 1437 | 1438 | config 1439 | 1440 | argument 1441 | '{query}', {variables} 1442 | cleardebuggertext 1443 | 1444 | processoutputs 1445 | 1446 | 1447 | type 1448 | alfred.workflow.utility.debug 1449 | uid 1450 | F47F1B80-82C9-49BA-9CF2-DBD52631E160 1451 | version 1452 | 1 1453 | 1454 | 1455 | readme 1456 | # ClipSaver 1457 | 1458 | This workflow is triggered with the `cs` keyword by default. You can change the keyword if desired via the configuration window. 1459 | 1460 | It presents images from Alfred's clipboard history via a Script Filter, and allows selecting an image to be extracted, converted to a format of your choice (default is PNG) and placed in a folder of your choosing (default is on the Desktop, in a folder called `saved_clips`). You can optionally set the `save_to_current` environment variable to have the workflow save to the currently active Finder window. 1461 | 1462 | Upon success, the original clipboard entry as well as the .tiff from Alfred's database will be removed if you have `delete_after_convert` set to true (File List objects in the clipboard history will remain untouched). 1463 | 1464 | Some features require [CleanShot](https://cleanshot.com/) to be installed. If you don't have CleanShot, you can still use the other features of the workflow. 1465 | - Holding ⇧shift while actioning from the Grid View will Pin (float) the selected images to the screen 1466 | - Holding ⌥option will open the images in the Annotation (edit) tool. 1467 | 1468 | ## Grid View (Alfred 5.5 only) 1469 | 1470 | As of version 2.4, the workflow also supports Alfred 5.5's new "Grid View" layout for previewing and actioning images. You can drag and drop images out of the grid with the mouse. While in Grid mode, press ⌘0 to toggle a larger view. You can use the File Buffer to select multiple images and action them. 1471 | 1472 | > _If the File Buffer contains contents, they will be used instead of the "last selection"._ 1473 | 1474 | ## Workflow Configuration 1475 | 1476 | All configuration is optional. The workflow should work "out of the box" without configuring any of these items. They are here for advanced users only. 1477 | 1478 | - `db_name` - override to specify a custom clipboard database file if needed 1479 | - `db_path` - override the default clipboard database path 1480 | - `dest_dir` where to save images if `save_to_current == false`, defaults to `~/Desktop/saved_clips` 1481 | - `sf_clip_limit` (default: empty) an optional limit to constrain the number of results displayed in the Script Filter 1482 | - `save_to_current` (default: false) - set to `true` if you want the workflow to put saved images in the directory of the "frontmost" Finder window 1483 | - `reveal_after` (default: true) - set to `false` if you do not want the Finder to reveal the saved clips after the workflow runs. 1484 | - `default_format` (default: png) - set to e.g. `jpg` etc. You can override per invocation by passing as an argument (use `sips --formats` to see all available formats) 1485 | - `delete_after_convert` (default: false) - set to `true` if you want the source images deleted after successful conversion 1486 | 1487 | ## Usage Tips 1488 | 1489 | - Tap `⇧SHIFT` while navigating through Script Filter results to get a QuickLook preview of the image. While QL is displayed, you can use the Arrow keys to flip through results. 1490 | - Tap `⌥OPTION` to show the path to the app that created that image in the subtitle 1491 | - Hold `⌘CMD` when actioning the item to Reveal the original image in Alfred's db folder 1492 | - Pass a number as an argument to the script to save X number of clips in bulk 1493 | - Pass an integer followed by `s|m|h|d` (sec,min,hrs,days) e.g. `90m` to save the clips captured in the last 90 minutes 1494 | - Pass an image format e.g. `jpeg` as an argument to override your default format 1495 | 1496 | ## Screenshot: 1497 | ![](./clipsaver.png) 1498 | 1499 | ## Download: 1500 | https://github.com/luckman212/alfred_clipsaver_workflow/releases/latest/ 1501 | 1502 | ## Forum topic: 1503 | https://www.alfredforum.com/topic/14400-clipsaver-save-images-from-clipboard-history-to-png-files/ 1504 | uidata 1505 | 1506 | 0C8B45F4-FEAE-493C-8531-48AE675F5DE1 1507 | 1508 | note 1509 | edit 1510 | xpos 1511 | 435 1512 | ypos 1513 | 860 1514 | 1515 | 15A2426A-6614-4325-9014-873CE2FCAEEE 1516 | 1517 | xpos 1518 | 1030 1519 | ypos 1520 | 705 1521 | 1522 | 2601B1DD-8EAB-4B3C-AEE2-5BFFE45ED5E5 1523 | 1524 | xpos 1525 | 910 1526 | ypos 1527 | 935 1528 | 1529 | 2C3159FB-317C-4678-993E-1BBB9141095B 1530 | 1531 | note 1532 | float 1533 | xpos 1534 | 435 1535 | ypos 1536 | 940 1537 | 1538 | 330B881A-5B6F-4415-B32E-4F62B9A7864E 1539 | 1540 | xpos 1541 | 690 1542 | ypos 1543 | 580 1544 | 1545 | 33CD6F02-65A3-4497-9C85-E38FDE97FC8D 1546 | 1547 | note 1548 | save_single 1549 | xpos 1550 | 575 1551 | ypos 1552 | 270 1553 | 1554 | 395E6EB2-E24E-42B0-891C-2AE7E16160F2 1555 | 1556 | xpos 1557 | 210 1558 | ypos 1559 | 580 1560 | 1561 | 40ABDE7E-4F25-4424-94E4-9489906D568D 1562 | 1563 | xpos 1564 | 1175 1565 | ypos 1566 | 285 1567 | 1568 | 4E6033E8-BC67-4836-B299-F64154D52F1A 1569 | 1570 | note 1571 | cleanshot actions 1572 | xpos 1573 | 1215 1574 | ypos 1575 | 825 1576 | 1577 | 4F366E5F-E737-440C-B9A3-FC859781F3DE 1578 | 1579 | note 1580 | enable dotted line to debug 1581 | xpos 1582 | 1315 1583 | ypos 1584 | 285 1585 | 1586 | 510F5A09-C26E-49C1-8C80-4AE3C0411E36 1587 | 1588 | xpos 1589 | 700 1590 | ypos 1591 | 900 1592 | 1593 | 519C25B7-FDE4-4EA5-AF9B-7460EEDC70CE 1594 | 1595 | xpos 1596 | 740 1597 | ypos 1598 | 290 1599 | 1600 | 5CC78342-FA2A-459C-A878-E2DC3621154B 1601 | 1602 | xpos 1603 | 450 1604 | ypos 1605 | 580 1606 | 1607 | 5E9A0126-8C80-41E7-AD49-590FE0777A91 1608 | 1609 | note 1610 | save_multi 1611 | xpos 1612 | 575 1613 | ypos 1614 | 145 1615 | 1616 | 68014C82-BAD9-4CFC-A6C1-A10A58F10E79 1617 | 1618 | xpos 1619 | 1145 1620 | ypos 1621 | 905 1622 | 1623 | 6C55B9DC-6A05-4228-AD55-2311DD125885 1624 | 1625 | colorindex 1626 | 3 1627 | xpos 1628 | 940 1629 | ypos 1630 | 130 1631 | 1632 | 70A3AE8F-5B75-4470-89FC-73298C06596C 1633 | 1634 | xpos 1635 | 795 1636 | ypos 1637 | 890 1638 | 1639 | 71A0EF86-42D5-4C0A-8656-CB6FEAD6F857 1640 | 1641 | xpos 1642 | 30 1643 | ypos 1644 | 235 1645 | 1646 | 7D984E52-7568-4C03-B70F-92EFA7067021 1647 | 1648 | note 1649 | set grid size 1650 | xpos 1651 | 370 1652 | ypos 1653 | 610 1654 | 1655 | 9D05394A-A5F5-4A2B-8AA7-3C06528411D3 1656 | 1657 | xpos 1658 | 1000 1659 | ypos 1660 | 935 1661 | 1662 | A02D5916-1E4A-4207-80F9-AE36DF096158 1663 | 1664 | note 1665 | save_multi 1666 | xpos 1667 | 575 1668 | ypos 1669 | 20 1670 | 1671 | A0D95AE6-FB7D-4C9D-BD5E-835FA2DC88F0 1672 | 1673 | xpos 1674 | 910 1675 | ypos 1676 | 885 1677 | 1678 | A3FBF3CE-3218-4FC1-A8AC-9D056C1C948B 1679 | 1680 | xpos 1681 | 1030 1682 | ypos 1683 | 590 1684 | 1685 | A8AB7CFC-4F7B-436E-8EAD-C48244230F84 1686 | 1687 | xpos 1688 | 740 1689 | ypos 1690 | 150 1691 | 1692 | AB178842-6707-4C5B-88D1-FC317394D8F9 1693 | 1694 | xpos 1695 | 940 1696 | ypos 1697 | 470 1698 | 1699 | C8999852-47D6-4E54-ACBA-03F7186AF696 1700 | 1701 | xpos 1702 | 980 1703 | ypos 1704 | 400 1705 | 1706 | CCD4FC35-EA9E-47A0-97D7-123CE12B9F47 1707 | 1708 | xpos 1709 | 1025 1710 | ypos 1711 | 885 1712 | 1713 | E30762D4-CD07-433B-8B2D-AD06552A2225 1714 | 1715 | note 1716 | copy 1717 | xpos 1718 | 735 1719 | ypos 1720 | 725 1721 | 1722 | E3B4F7E0-C8D4-4C51-A42A-25FF8B353E84 1723 | 1724 | colorindex 1725 | 1 1726 | xpos 1727 | 940 1728 | ypos 1729 | 255 1730 | 1731 | EE17BA6D-5748-4F20-BE74-602EB8B9849F 1732 | 1733 | note 1734 | reveal 1735 | xpos 1736 | 735 1737 | ypos 1738 | 500 1739 | 1740 | F34B0F25-D875-426D-8071-3FACA31F0AE1 1741 | 1742 | note 1743 | if reveal_after == true 1744 | xpos 1745 | 1135 1746 | ypos 1747 | 370 1748 | 1749 | F47F1B80-82C9-49BA-9CF2-DBD52631E160 1750 | 1751 | xpos 1752 | 795 1753 | ypos 1754 | 960 1755 | 1756 | F8D8F9B0-A655-474F-9D3C-68866A8D85E6 1757 | 1758 | xpos 1759 | 195 1760 | ypos 1761 | 195 1762 | 1763 | FF2B9F42-D9A9-42D6-ABDB-8691AC88D747 1764 | 1765 | note 1766 | save selection and file buffer into vars 1767 | xpos 1768 | 540 1769 | ypos 1770 | 870 1771 | 1772 | 1773 | userconfigurationconfig 1774 | 1775 | 1776 | config 1777 | 1778 | default 1779 | cs 1780 | placeholder 1781 | cs 1782 | required 1783 | 1784 | trim 1785 | 1786 | 1787 | description 1788 | 1789 | label 1790 | Trigger keyword 1791 | type 1792 | textfield 1793 | variable 1794 | kw_trigger 1795 | 1796 | 1797 | config 1798 | 1799 | default 1800 | ~/Desktop 1801 | filtermode 1802 | 1 1803 | placeholder 1804 | ~/Desktop 1805 | required 1806 | 1807 | 1808 | description 1809 | Folder in which to save converted images 1810 | label 1811 | Destination 1812 | type 1813 | filepicker 1814 | variable 1815 | dest_dir 1816 | 1817 | 1818 | config 1819 | 1820 | default 1821 | png 1822 | pairs 1823 | 1824 | 1825 | PNG 1826 | png 1827 | 1828 | 1829 | JPG 1830 | jpg 1831 | 1832 | 1833 | GIF 1834 | gif 1835 | 1836 | 1837 | 1838 | description 1839 | Default image format to convert to 1840 | label 1841 | Default format 1842 | type 1843 | popupbutton 1844 | variable 1845 | default_format 1846 | 1847 | 1848 | config 1849 | 1850 | default 1851 | 1852 | required 1853 | 1854 | text 1855 | Delete source images after conversion 1856 | 1857 | description 1858 | 1859 | label 1860 | 1861 | type 1862 | checkbox 1863 | variable 1864 | delete_after_convert 1865 | 1866 | 1867 | config 1868 | 1869 | default 1870 | 1871 | required 1872 | 1873 | text 1874 | Save images to current Finder window 1875 | 1876 | description 1877 | 1878 | label 1879 | 1880 | type 1881 | checkbox 1882 | variable 1883 | save_to_current 1884 | 1885 | 1886 | config 1887 | 1888 | default 1889 | 1890 | required 1891 | 1892 | text 1893 | Reveal clips after saving 1894 | 1895 | description 1896 | 1897 | label 1898 | 1899 | type 1900 | checkbox 1901 | variable 1902 | reveal_after 1903 | 1904 | 1905 | config 1906 | 1907 | default 1908 | 1909 | placeholder 1910 | unlimited 1911 | required 1912 | 1913 | trim 1914 | 1915 | 1916 | description 1917 | Enter an integer to limit # of results returned from the database 1918 | label 1919 | Limit 1920 | type 1921 | textfield 1922 | variable 1923 | sf_clip_limit 1924 | 1925 | 1926 | config 1927 | 1928 | defaultvalue 1929 | 5 1930 | markercount 1931 | 12 1932 | maxvalue 1933 | 12 1934 | minvalue 1935 | 1 1936 | onlystoponmarkers 1937 | 1938 | showmarkers 1939 | 1940 | 1941 | description 1942 | Number of columns to display in Grid View 1943 | label 1944 | Grid Size 1945 | type 1946 | slider 1947 | variable 1948 | grid_size 1949 | 1950 | 1951 | config 1952 | 1953 | default 1954 | 1955 | placeholder 1956 | ~/Library/Application Support/Alfred/Databases 1957 | required 1958 | 1959 | trim 1960 | 1961 | 1962 | description 1963 | Path to clipboard database 1964 | label 1965 | Database path 1966 | type 1967 | textfield 1968 | variable 1969 | db_path 1970 | 1971 | 1972 | config 1973 | 1974 | default 1975 | 1976 | placeholder 1977 | clipboard.alfdb 1978 | required 1979 | 1980 | trim 1981 | 1982 | 1983 | description 1984 | Filename of clipboard history database 1985 | label 1986 | Database filename 1987 | type 1988 | textfield 1989 | variable 1990 | db_name 1991 | 1992 | 1993 | variablesdontexport 1994 | 1995 | version 1996 | 2.7.0 1997 | webaddress 1998 | https://github.com/luckman212/alfred_clipsaver_workflow 1999 | 2000 | 2001 | -------------------------------------------------------------------------------- /process_clip.bash: -------------------------------------------------------------------------------- 1 | #shellcheck shell=bash 2 | 3 | # this is meant to be sourced from save_multi{_time}.sh 4 | 5 | if [[ $src_fname == */* ]]; then 6 | # from plist 7 | src_basename=${src_fname##*/} 8 | src_basename_noext=${src_basename%.*} 9 | if [[ -r $src_fname ]]; then 10 | if ! /usr/bin/sips -s format $fmt "$src_fname" --out "$dest_dir/${src_basename_noext}.$fmt" &>/dev/null; then 11 | errmsg="failed: $src_basename\n$errmsg" 12 | fi 13 | else 14 | errmsg="not found: $src_basename\n$errmsg" 15 | fi 16 | else 17 | # from Alfred db 18 | tiff_name=$db_path/${db_name}.data/$src_fname 19 | src_basename=${src_fname%.tiff} 20 | if [[ -r $tiff_name ]]; then 21 | if /usr/bin/sips -s format $fmt "$tiff_name" --out "$dest_dir/${src_basename}.$fmt" &>/dev/null; then 22 | if [[ ${delete_after_convert} == true ]]; then 23 | /usr/bin/sqlite3 "$db_path/$db_name" "DELETE FROM clipboard WHERE dataHash = \"$src_fname\" AND dataType = 1 LIMIT 1;" 24 | rm "$tiff_name" &>/dev/null 25 | fi 26 | else 27 | errmsg="failed: $src_basename\n$errmsg" 28 | fi 29 | else 30 | errmsg="not found: $src_basename\n$errmsg" 31 | fi 32 | fi 33 | -------------------------------------------------------------------------------- /save_multi.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | exec 3>&1 4 | 5 | function break_err() { 6 | exec 1>&3 7 | cat </dev/null 21 | 22 | unset errmsg 23 | num_clips=$1 24 | max_clips=$(/usr/bin/sqlite3 "$db_path/$db_name" "SELECT COUNT(dataHash) FROM clipboard WHERE dataType = 1;") 25 | 26 | re='^[0-9]+$' 27 | if ! [[ $num_clips =~ $re ]] ; then 28 | break_err 2 "not a number (NaN)" 29 | fi 30 | 31 | if [[ $num_clips -eq 0 ]] ; then 32 | break_err 2 "num_clips must be >0" 33 | fi 34 | 35 | if [[ $max_clips -eq 0 ]]; then 36 | break_err 2 "no image clips found in the database" 37 | fi 38 | 39 | if [[ $max_clips -lt "$num_clips" ]]; then 40 | errmsg="not enough clips to fulfill request\nrequested: $num_clips, available: $max_clips (adjusted)" 41 | num_clips=$max_clips 42 | fi 43 | 44 | if [[ $save_to_current == "true" ]]; then 45 | if fwin=$(osascript -e 'tell application "Finder" to get POSIX path of (insertion location as alias)' 2>/dev/null); then 46 | dest_dir=$fwin 47 | fi 48 | fi 49 | 50 | if [[ ${dest_dir:0:1} != '/' ]]; then 51 | dest_dir=$(tr '[:upper:]' '[:lower:]' <<<"$HOME/$dest_dir") 52 | fi 53 | 54 | # make sure dest_dir exists 55 | if [[ ! -d $dest_dir ]]; then 56 | if ! /bin/mkdir -p "$dest_dir"; then 57 | break_err 2 "failed to create dest_dir:\n$dest_dir" 58 | fi 59 | fi 60 | 61 | fmt=${format:-png} 62 | 63 | # fetch image clips from database, loop and process 64 | while read -r src_fname ; do 65 | #echo 1>&2 "processing: [$src_fname]" 66 | source ./process_clip.bash 67 | done < <(./get_image_hashes.py --num_clips "$num_clips") 68 | 69 | # if ALL images were processed, run cleanup script 70 | if [[ $num_clips -eq $max_clips ]]; then 71 | ./alfred_validate_cache.sh 72 | fi 73 | 74 | if [[ -n $errmsg ]]; then 75 | break_err 1 "$errmsg" 76 | fi 77 | 78 | break_err 0 79 | -------------------------------------------------------------------------------- /save_multi_time.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | exec 3>&1 4 | 5 | function break_err() { 6 | exec 1>&3 7 | cat </dev/null 21 | 22 | unset errmsg 23 | atleast=$1 24 | 25 | re='^[0-9]+$' 26 | if ! [[ $atleast =~ $re ]] ; then 27 | break_err 2 "not a number (NaN)" 28 | fi 29 | 30 | if [[ $atleast -eq 0 ]] ; then 31 | break_err 2 "atleast must be >0" 32 | fi 33 | 34 | if [[ $save_to_current == "true" ]]; then 35 | if fwin=$(osascript -e 'tell application "Finder" to get POSIX path of (insertion location as alias)' 2>/dev/null); then 36 | dest_dir=$fwin 37 | fi 38 | fi 39 | 40 | if [[ ${dest_dir:0:1} != '/' ]]; then 41 | dest_dir=$(tr '[:upper:]' '[:lower:]' <<<"$HOME/$dest_dir") 42 | fi 43 | 44 | # make sure dest_dir exists 45 | if [[ ! -d $dest_dir ]]; then 46 | if ! /bin/mkdir -p "$dest_dir"; then 47 | break_err 2 "failed to create dest_dir:\n$dest_dir" 48 | fi 49 | fi 50 | 51 | fmt=${format:-png} 52 | 53 | # fetch image clips from database, loop and process 54 | while read -r src_fname ; do 55 | #echo 1>&2 "processing: [$src_fname]" 56 | source ./process_clip.bash 57 | done < <(./get_image_hashes.py --atleast "$atleast") 58 | 59 | if [[ -n $errmsg ]]; then 60 | break_err 1 "$errmsg" 61 | fi 62 | 63 | break_err 0 64 | -------------------------------------------------------------------------------- /save_single.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | exec 3>&1 4 | 5 | function break_err() { 6 | exec 1>&3 7 | cat </dev/null 21 | 22 | unset errmsg 23 | img_pathname=$1 24 | img_basename=${img_pathname##*/} 25 | img_noext=${img_basename%.*} 26 | 27 | if [[ ${dest_dir:0:1} != '/' ]]; then 28 | dest_dir=$(tr '[:upper:]' '[:lower:]' <<<"$HOME/$dest_dir") 29 | fi 30 | 31 | if [[ ${save_to_current} == true ]]; then 32 | if fwin=$(osascript -e 'tell application "Finder" to get POSIX path of (insertion location as alias)' 2>/dev/null); then 33 | dest_dir=$fwin 34 | fi 35 | fi 36 | 37 | if [[ -r $img_pathname ]]; then 38 | # make sure dest_dir exists 39 | if [[ ! -d $dest_dir ]]; then 40 | /bin/mkdir -p "$dest_dir" || break_err 1 "failed to create output dir" 41 | fi 42 | fmt=${format:-png} 43 | destfile=$dest_dir/${img_noext}.$fmt 44 | if [[ ${img_pathname} == "${destfile}" ]]; then 45 | break_err 1 "(source and destination filename were the same)" 46 | fi 47 | if /usr/bin/sips -s format $fmt "$img_pathname" --out "$dest_dir/${img_noext}.$fmt"; then 48 | if [[ ${delete_after_convert} == true ]] && [[ -n $dataHash ]]; then 49 | /usr/bin/sqlite3 "$db_path/$db_name" "DELETE FROM clipboard WHERE dataHash = \"$dataHash\" AND dataType = 1 LIMIT 1;" 50 | rm "$img_pathname" 51 | fi 52 | break_err 0 53 | else 54 | break_err 1 55 | fi 56 | else 57 | break_err 1 58 | fi 59 | -------------------------------------------------------------------------------- /wf_common.py: -------------------------------------------------------------------------------- 1 | """ 2 | Common Functions, Vars, Constants 3 | """ 4 | 5 | import os 6 | import time 7 | 8 | def envvar(v: str, dv: str) -> str: 9 | return str(os.getenv(v) or dv) 10 | 11 | #for checkboxes - unchecked aka `0` returns `false` 12 | def envvar_to_bool(v: str) -> str: 13 | try: 14 | b = int(os.getenv(v)) 15 | except: 16 | b = 0 17 | return str(bool(b)).lower() 18 | 19 | def envvar_to_int(v: str, dv: int=0) -> int: 20 | try: 21 | return int(os.getenv(v)) 22 | except: 23 | pass 24 | try: 25 | return int(dv) 26 | except: 27 | return 0 28 | 29 | #wf vars 30 | db_name = envvar('db_name', 'clipboard.alfdb') 31 | db_path = os.path.expanduser(envvar('db_path', '~/Library/Application Support/Alfred/Databases')) 32 | dest_dir = os.path.expanduser(envvar('dest_dir', '~/Desktop/saved_clips')).lower() 33 | default_format = envvar('default_format', 'png').lower() 34 | delete_after_convert = envvar_to_bool('delete_after_convert') 35 | save_to_current = envvar_to_bool('save_to_current') 36 | sf_clip_limit = envvar_to_int('sf_clip_limit', -1) 37 | 38 | #home_path = os.getenv('HOME') 39 | db_res = os.path.join(db_path, db_name) 40 | i_path = f'{db_res}.data' 41 | uidSeed = str(os.getenv('uidSeed', time.time())) 42 | img_exts = [ 'png', 'gif', 'jpg', 'jpeg', 'tiff', 'tif', 'bmp' ] 43 | --------------------------------------------------------------------------------