desoto-photoapp 0.4.7 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -19
- data/bin/imageOptim +86 -0
- data/bin/imageOptimAppleScriptLib +251 -0
- data/bin/imageOptimBashLib +534 -0
- data/bin/pngquant +0 -0
- data/bin/process.sh +3 -0
- data/exe/photoapp +24 -12
- data/lib/photoapp.rb +88 -22
- data/lib/photoapp/photo.rb +1 -1
- data/lib/photoapp/version.rb +1 -1
- metadata +7 -9
- data/assets/photos-action-installer.pkg +0 -0
- data/lib/adjust-image.workflow/Contents/Info.plist +0 -8
- data/lib/adjust-image.workflow/Contents/QuickLook/Preview.png +0 -0
- data/lib/adjust-image.workflow/Contents/document.wflow +0 -345
- data/lib/import-photos.workflow/Contents/Info.plist +0 -8
- data/lib/import-photos.workflow/Contents/QuickLook/Thumbnail.png +0 -0
- data/lib/import-photos.workflow/Contents/document.wflow +0 -346
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0d179b8286429cb3e024ca35397e545a9fc7bec6
|
4
|
+
data.tar.gz: 2a5ffe60691b4d23ae1e11d8f683b36949e6d5a2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9859dc880a300ae8562918c7bf2fc17640ba3377ef45ef97d046be9b6e7a859deccb8e94d4f045d766efbf219422c8167c3527a0d71078704b0343ddd1501a2a
|
7
|
+
data.tar.gz: f365b6b4b5bd127ca84abf773af83492ac7ddad1b665b65c1bee1d462c55c8d08aad46c389af6aa7120422a39fa3ec210dd0c8c37fc26d864832be0f83051892
|
data/README.md
CHANGED
@@ -22,9 +22,6 @@ And then execute:
|
|
22
22
|
|
23
23
|
$ bundle
|
24
24
|
|
25
|
-
Install the [Automator actions](https://photosautomation.com/index.html) for importing photos. Download the [installer here](https://photosautomation.com/installer.zip).
|
26
|
-
|
27
|
-
|
28
25
|
Be sure your shell profile loads in executables.
|
29
26
|
|
30
27
|
```
|
@@ -46,9 +43,8 @@ $ photoapp setup
|
|
46
43
|
This will do the following:
|
47
44
|
|
48
45
|
- Installs `imagemagick` with (using Homebrew).
|
49
|
-
- Installs [Automator actions](https://photosautomation.com/index.html) for importing pictures into Photos.app [Manually install from here](https://photosautomation.com/installer.zip)
|
50
46
|
- Copies the image processing Automator workflow to `~/Library/Workflows/Applications/Folder\ Actions/`
|
51
|
-
- Installs the `Reprint.app`
|
47
|
+
- Installs the `Reprint.app`, `Update.app`, and `Upload.app` to `/Applications`.
|
52
48
|
|
53
49
|
Finally you'll need to enable the folder action to trigger photo processing when photos are added to the import folder. Launch the folder actions setup app:
|
54
50
|
|
@@ -88,18 +84,6 @@ $ photoapp config printer
|
|
88
84
|
These are the default settings for an Epson Stylus Photo R280. If using a different printer, use whatever settings are equivalent.
|
89
85
|
|
90
86
|
```
|
91
|
-
|
92
|
-
|
93
|
-
Media Type: Ulta Premium Photo Paper Glossy
|
94
|
-
|
95
|
-
... further down ...
|
96
|
-
|
97
|
-
MediaType: Ulta Premium Photo Paper Glossy
|
98
|
-
Media Size: 5x7 in (Sheet Feeder - Borderless)
|
87
|
+
Paper Size: 5x7
|
88
|
+
Media Type: Glossy
|
99
89
|
```
|
100
|
-
|
101
|
-
## Development
|
102
|
-
|
103
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
|
104
|
-
|
105
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
data/bin/imageOptim
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# ------------------------------------
|
4
|
+
# LOCATE THIS SCRIPT
|
5
|
+
# ------------------------------------
|
6
|
+
|
7
|
+
CLI_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
8
|
+
|
9
|
+
# ------------------------------------
|
10
|
+
# IMPORT BASH LIBRARY
|
11
|
+
# ------------------------------------
|
12
|
+
|
13
|
+
source "${CLI_PATH}/imageOptimBashLib"
|
14
|
+
|
15
|
+
# ------------------------------------
|
16
|
+
# INGEST COMMAND LINE ARGUMENTS
|
17
|
+
# ------------------------------------
|
18
|
+
|
19
|
+
while [ "$1" != "" ]; do
|
20
|
+
case $1 in
|
21
|
+
-d | --directory )
|
22
|
+
shift;
|
23
|
+
DIRECTORY_OPTION="$1"
|
24
|
+
;;
|
25
|
+
-a | --image-alpha )
|
26
|
+
USE_IMAGE_ALPHA="true"
|
27
|
+
;;
|
28
|
+
-j | --jpeg-mini )
|
29
|
+
USE_JPEGMINI="true"
|
30
|
+
;;
|
31
|
+
-m | --min-quality )
|
32
|
+
shift;
|
33
|
+
MIN_QUALITY="$1"
|
34
|
+
;;
|
35
|
+
-s | --skip-if-larger )
|
36
|
+
SKIP_IF_LARGER="true"
|
37
|
+
;;
|
38
|
+
-q | --quit )
|
39
|
+
QUIT_ON_COMPLETE="true"
|
40
|
+
;;
|
41
|
+
-c | --no-color )
|
42
|
+
NO_COLOR="true"
|
43
|
+
;;
|
44
|
+
-h | --help )
|
45
|
+
usage;
|
46
|
+
exit 0
|
47
|
+
;;
|
48
|
+
-e | --examples )
|
49
|
+
examples;
|
50
|
+
exit 0
|
51
|
+
;;
|
52
|
+
--verbose )
|
53
|
+
IS_VERBOSE="true"
|
54
|
+
;;
|
55
|
+
-v | --version )
|
56
|
+
echo $VERSION;
|
57
|
+
exit 0
|
58
|
+
;;
|
59
|
+
* )
|
60
|
+
usage
|
61
|
+
exit 1
|
62
|
+
esac
|
63
|
+
shift
|
64
|
+
done
|
65
|
+
|
66
|
+
# ------------------------------------
|
67
|
+
# POPULATE VARIABLES
|
68
|
+
# ------------------------------------
|
69
|
+
|
70
|
+
# Determine which Applications are installed.
|
71
|
+
HAS_IMAGE_OPTIM=$(has_imageoptim)
|
72
|
+
HAS_JPEGMINI=$(has_jpegmini)
|
73
|
+
HAS_IMAGEMAGICK=$(has_imagemagick)
|
74
|
+
HAS_GUI_SCRIPT=`osascript -e 'tell application "System Events" to get UI elements enabled'`
|
75
|
+
JPEGMINI_NAME=$(get_jpegmini_app_name)
|
76
|
+
|
77
|
+
# ------------------------------------
|
78
|
+
# BEGIN PROCESSING
|
79
|
+
# ------------------------------------
|
80
|
+
|
81
|
+
start
|
82
|
+
run_image_alpha
|
83
|
+
run_jpegmini
|
84
|
+
run_image_optim
|
85
|
+
output_savings
|
86
|
+
finish
|
@@ -0,0 +1,251 @@
|
|
1
|
+
#!/bin/osascript
|
2
|
+
|
3
|
+
-- https://raw.githubusercontent.com/JamieMason/LICENSES/master/LICENSE-MIT
|
4
|
+
|
5
|
+
global DIRECTORY_GIF
|
6
|
+
global DIRECTORY_JPG
|
7
|
+
global DIRECTORY_PNG
|
8
|
+
global DIRECTORY_TEMP
|
9
|
+
global ID_IMAGEOPTIM
|
10
|
+
global ID_JPEGMINI
|
11
|
+
global ID_JPEGMINI_RETAIL
|
12
|
+
global ID_JPEGMINI_LITE
|
13
|
+
global ID_JPEGMINI_LITE_RETAIL
|
14
|
+
global ID_JPEGMINI_PRO
|
15
|
+
global ID_JPEGMINI_PRO_RETAIL
|
16
|
+
global JPEGMINI_APP_NAME
|
17
|
+
|
18
|
+
(*
|
19
|
+
* @description
|
20
|
+
* Is the app with this bundle id installed somewhere on this machine?
|
21
|
+
*
|
22
|
+
* @param {String} bundleId eg. ID_IMAGEOPTIM
|
23
|
+
* @return {Boolean}
|
24
|
+
*)
|
25
|
+
on isInstalled(bundleId)
|
26
|
+
try
|
27
|
+
tell application "Finder" to get application file id bundleId
|
28
|
+
return true
|
29
|
+
on error
|
30
|
+
return false
|
31
|
+
end try
|
32
|
+
end isInstalled
|
33
|
+
|
34
|
+
(*
|
35
|
+
* @description
|
36
|
+
* Check whether access for assistive devices is enabled under System
|
37
|
+
* Preferences, needed to perform synthetic user actions so JPEGmini can be
|
38
|
+
* automated.
|
39
|
+
*
|
40
|
+
* Enable access for assistive devices in Mavericks;
|
41
|
+
* http://www.tekrevue.com/how-to-enable-access-for-assistive-devices-in-os-x-mavericks/
|
42
|
+
*
|
43
|
+
* Enable access for assistive devices in Yosemite;
|
44
|
+
* https://thequantumself.wordpress.com/2014/06/26/enable-access-for-assistive-devices-in-osx-yosmite/
|
45
|
+
*
|
46
|
+
* @return {Boolean}
|
47
|
+
*)
|
48
|
+
on supportsAssistiveDevices()
|
49
|
+
try
|
50
|
+
-- return do shell script "osascript -e 'tell application \"System Events\" to get UI elements enabled'"
|
51
|
+
tell application "System Events" to get UI elements enabled
|
52
|
+
on error
|
53
|
+
return "ERROR_GUISCRIPT_UNREADABLE"
|
54
|
+
end try
|
55
|
+
end supportsAssistiveDevices
|
56
|
+
|
57
|
+
(*
|
58
|
+
* @description
|
59
|
+
* Find the appropriate JPEGmini app name, starting with the paid-for plans
|
60
|
+
* down to the free one.
|
61
|
+
*
|
62
|
+
* @return {String} eg. "JPEGmini Pro"
|
63
|
+
*)
|
64
|
+
on getJpegMiniAppName()
|
65
|
+
if isInstalled(ID_JPEGMINI_PRO) or isInstalled(ID_JPEGMINI_PRO_RETAIL) then
|
66
|
+
return "JPEGmini Pro"
|
67
|
+
else if isInstalled(ID_JPEGMINI) or isInstalled(ID_JPEGMINI_RETAIL) then
|
68
|
+
return "JPEGmini"
|
69
|
+
else if isInstalled(ID_JPEGMINI_LITE) or isInstalled(ID_JPEGMINI_LITE_RETAIL) then
|
70
|
+
return "JPEGmini Lite"
|
71
|
+
end if
|
72
|
+
return "false"
|
73
|
+
end getJpegMiniAppName
|
74
|
+
|
75
|
+
on hasJPEGmini()
|
76
|
+
if getJpegMiniAppName() is "false" then
|
77
|
+
return false
|
78
|
+
else
|
79
|
+
return true
|
80
|
+
end if
|
81
|
+
end hasJPEGmini
|
82
|
+
|
83
|
+
on hasImageOptim()
|
84
|
+
return isInstalled(ID_IMAGEOPTIM)
|
85
|
+
end hasImageOptim
|
86
|
+
|
87
|
+
(*
|
88
|
+
* @description
|
89
|
+
* Run JPEGmini <variant> over the contents of DIRECTORY_JPG.
|
90
|
+
*)
|
91
|
+
on runJPEGmini()
|
92
|
+
(* DETERMINE WHICH VERSION IS INSTALLED *)
|
93
|
+
set JPEGMINI_APP_NAME to getJpegMiniAppName()
|
94
|
+
(* QUIT IF NOT INSTALLED *)
|
95
|
+
if JPEGMINI_APP_NAME is "false" then
|
96
|
+
return "ERROR_JPEGMINI_UNAVAILABLE"
|
97
|
+
end if
|
98
|
+
(* OPEN AND FOCUS JPEGMINI *)
|
99
|
+
tell application JPEGMINI_APP_NAME
|
100
|
+
activate
|
101
|
+
delay 3
|
102
|
+
activate
|
103
|
+
end tell
|
104
|
+
(* SPAWN THE FILE > OPEN MENU *)
|
105
|
+
tell application "System Events"
|
106
|
+
keystroke "o" using {command down}
|
107
|
+
delay 3
|
108
|
+
keystroke "g" using {command down, shift down}
|
109
|
+
delay 2
|
110
|
+
end tell
|
111
|
+
(* NAVIGATE TO OUR FOLDER OF IMAGES *)
|
112
|
+
tell application "System Events"
|
113
|
+
tell process JPEGMINI_APP_NAME
|
114
|
+
(* < SIERRA, FILE PATH SELECTOR IS TEXT INPUT *)
|
115
|
+
if text field 1 of sheet 1 of sheet 1 of window 1 exists then
|
116
|
+
set value of text field 1 of sheet 1 of sheet 1 of window 1 to DIRECTORY_JPG
|
117
|
+
repeat
|
118
|
+
if (value of text field 1 of sheet 1 of sheet 1 of window 1) is not equal to DIRECTORY_JPG then
|
119
|
+
delay 1
|
120
|
+
else
|
121
|
+
exit repeat
|
122
|
+
end if
|
123
|
+
end repeat
|
124
|
+
end if
|
125
|
+
(* = SIERRA, FILE PATH SELECTOR IS COMBO BOX *)
|
126
|
+
if combo box 1 of sheet 1 of sheet 1 of window 1 exists then
|
127
|
+
set value of combo box 1 of sheet 1 of sheet 1 of window 1 to DIRECTORY_JPG
|
128
|
+
repeat
|
129
|
+
if (value of combo box 1 of sheet 1 of sheet 1 of window 1) is not equal to DIRECTORY_JPG then
|
130
|
+
delay 1
|
131
|
+
else
|
132
|
+
exit repeat
|
133
|
+
end if
|
134
|
+
end repeat
|
135
|
+
end if
|
136
|
+
(* >= HIGH SIERRA *)
|
137
|
+
if combo box 1 of sheet 1 of window 1 exists then
|
138
|
+
set value of combo box 1 of sheet 1 of window 1 to DIRECTORY_JPG
|
139
|
+
repeat
|
140
|
+
if (value of combo box 1 of sheet 1 of window 1) is not equal to DIRECTORY_JPG then
|
141
|
+
delay 1
|
142
|
+
else
|
143
|
+
exit repeat
|
144
|
+
end if
|
145
|
+
end repeat
|
146
|
+
end if
|
147
|
+
-- give Finder time to resolve the path
|
148
|
+
delay 2
|
149
|
+
keystroke return
|
150
|
+
delay 2
|
151
|
+
keystroke return
|
152
|
+
-- start optimising (>= Yosemite)
|
153
|
+
-- click button "Go" of sheet 1 of window 1
|
154
|
+
-- start optimising (<= Mavericks)
|
155
|
+
-- click button "Open" of sheet 1 of window 1
|
156
|
+
end tell
|
157
|
+
end tell
|
158
|
+
(* WAIT FOR JPEGMINI TO FINISH RUNNING *)
|
159
|
+
tell application "System Events"
|
160
|
+
set timesIdle to 0
|
161
|
+
repeat
|
162
|
+
-- get all process information | filtered to JPEGmini
|
163
|
+
set getRawProcess to "ps aux | grep '/Applications/" & JPEGMINI_APP_NAME & "'"
|
164
|
+
-- filter out JPEGmini grep | get column 3 of output (% CPU)
|
165
|
+
set filterRawProcess to "grep -v grep | awk '{print $3}'"
|
166
|
+
-- store above pipe chain in a variable
|
167
|
+
set getRawCpu to "RAWCPU=$(" & getRawProcess & " | " & filterRawProcess & ")"
|
168
|
+
-- round that variable to a whole number
|
169
|
+
set outputRoundedCpu to "$(printf \"%.0f\" $(echo \"scale=2;$RAWCPU\" | bc))"
|
170
|
+
-- join the two commands and echo it out to applescript
|
171
|
+
set getCpuPercent to getRawCpu & " && echo " & outputRoundedCpu
|
172
|
+
-- get raw terminal string output
|
173
|
+
set cpuPercent to (do shell script getCpuPercent) as number
|
174
|
+
-- give the app a little time to work
|
175
|
+
delay 0.5
|
176
|
+
-- if the app is idle
|
177
|
+
if (cpuPercent) < 1 then
|
178
|
+
-- increment number of times we've found the app consecutively idle
|
179
|
+
set timesIdle to timesIdle + 1
|
180
|
+
-- if it's been idle for long enough we can exit
|
181
|
+
if (timesIdle) > 5 then
|
182
|
+
exit repeat
|
183
|
+
end if
|
184
|
+
end if
|
185
|
+
-- (implied else: by not exiting we repeat again)
|
186
|
+
end repeat
|
187
|
+
delay 0.5
|
188
|
+
end tell
|
189
|
+
end runJPEGmini
|
190
|
+
|
191
|
+
(*
|
192
|
+
* @param {String} appName eg. "ImageOptim"
|
193
|
+
*)
|
194
|
+
on quitApp(appName)
|
195
|
+
tell application appName to quit
|
196
|
+
end quitApp
|
197
|
+
|
198
|
+
on quitImageOptim()
|
199
|
+
quitApp("ImageOptim")
|
200
|
+
end runImageOptim
|
201
|
+
|
202
|
+
on quitJPEGmini()
|
203
|
+
quitApp(getJpegMiniAppName())
|
204
|
+
end runImageOptim
|
205
|
+
|
206
|
+
(*
|
207
|
+
* @description
|
208
|
+
* Handle command-line arguments.
|
209
|
+
*
|
210
|
+
* @example
|
211
|
+
* ./imageOptimAppleScriptLib "<action>" "<quitAfter>"
|
212
|
+
*)
|
213
|
+
on run argv
|
214
|
+
|
215
|
+
(* INITIALISE GLOBALS *)
|
216
|
+
|
217
|
+
set DIRECTORY_TEMP to (do shell script "echo $TMPDIR") & "imageoptim-cli/"
|
218
|
+
set DIRECTORY_GIF to DIRECTORY_TEMP & "gif"
|
219
|
+
set DIRECTORY_JPG to DIRECTORY_TEMP & "jpg"
|
220
|
+
set DIRECTORY_PNG to DIRECTORY_TEMP & "png"
|
221
|
+
set ID_IMAGEOPTIM to "net.pornel.ImageOptim"
|
222
|
+
set ID_JPEGMINI to "com.icvt.JPEGmini"
|
223
|
+
set ID_JPEGMINI_RETAIL to "com.icvt.JPEGmini-retail"
|
224
|
+
set ID_JPEGMINI_LITE to "com.icvt.JPEGminiLite"
|
225
|
+
set ID_JPEGMINI_LITE_RETAIL to "com.icvt.JPEGminiLite-retail"
|
226
|
+
set ID_JPEGMINI_PRO to "com.icvt.JPEGmini-Pro"
|
227
|
+
set ID_JPEGMINI_PRO_RETAIL to "com.icvt.JPEGmini-Pro-retail"
|
228
|
+
|
229
|
+
(* HANDLE COMMAND LINE ARGUMENTS *)
|
230
|
+
|
231
|
+
set action to item 1 of argv
|
232
|
+
|
233
|
+
(* PERFORM ACTION *)
|
234
|
+
|
235
|
+
if action is "quitImageOptim" then
|
236
|
+
return quitImageOptim()
|
237
|
+
else if action is "quitJPEGmini" then
|
238
|
+
return quitJPEGmini()
|
239
|
+
else if action is "runJPEGmini" then
|
240
|
+
return runJPEGmini()
|
241
|
+
else if action is "supportsAssistiveDevices" then
|
242
|
+
return supportsAssistiveDevices()
|
243
|
+
else if action is "getJpegMiniAppName" then
|
244
|
+
return getJpegMiniAppName()
|
245
|
+
else if action is "hasJPEGmini" then
|
246
|
+
return hasJPEGmini()
|
247
|
+
else if action is "hasImageOptim" then
|
248
|
+
return hasImageOptim()
|
249
|
+
end if
|
250
|
+
|
251
|
+
end run
|
@@ -0,0 +1,534 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# https://raw.githubusercontent.com/JamieMason/LICENSES/master/LICENSE-MIT
|
4
|
+
|
5
|
+
# ------------------------------------
|
6
|
+
# DEFAULT / ENVIRONMENT VARIABLES
|
7
|
+
# ------------------------------------
|
8
|
+
|
9
|
+
# current version of ImageOptim-CLI from package.json
|
10
|
+
VERSION="1.15.4"
|
11
|
+
|
12
|
+
# {DirectoryPath} Absolute file system path to this shell script.
|
13
|
+
CLI_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
14
|
+
|
15
|
+
# {DirectoryPath} Absolute file system path to where this shell script was invoked from.
|
16
|
+
EXEC_PATH="$PWD"
|
17
|
+
|
18
|
+
# {FilePath} The location of our Applescript library file.
|
19
|
+
APPLESCRIPT_BIN="$CLI_PATH/imageOptimAppleScriptLib"
|
20
|
+
|
21
|
+
# {FilePath} The location of stat (see issue #59 with stat & homebrew)
|
22
|
+
STAT_BIN="/usr/bin/stat"
|
23
|
+
|
24
|
+
# {DirectoryPath} Where we'll copy all images to be processed, compared, then returned to their
|
25
|
+
# original locations.
|
26
|
+
TEMP_PATH="${TMPDIR}imageoptim-cli"
|
27
|
+
|
28
|
+
# {FilePath} A list of all images we'll be operating on.
|
29
|
+
INDEX_FILE="${TEMP_PATH}/index.txt"
|
30
|
+
|
31
|
+
# {FilePath[]} The original locations of every image to be processed.
|
32
|
+
INDEX_ARRAY=[]
|
33
|
+
|
34
|
+
# {Number} How many images in total being processed.
|
35
|
+
TOTAL_IMAGES=0
|
36
|
+
|
37
|
+
# {DirectoryPath|String} The optionally provided -d or --directory command line argument.
|
38
|
+
DIRECTORY_OPTION="false"
|
39
|
+
|
40
|
+
# {Number} Optinally overriden with -m or --min-quality
|
41
|
+
MIN_QUALITY="75"
|
42
|
+
|
43
|
+
# {BooleanString} Optionally use -s or --skip-if-larger with pngquant
|
44
|
+
SKIP_IF_LARGER="false"
|
45
|
+
|
46
|
+
# {BooleanString} Optionally overridden with -a or --image-alpha.
|
47
|
+
USE_IMAGE_ALPHA="false"
|
48
|
+
|
49
|
+
# {BooleanString} Optionally overridden with -j or --jpeg-mini.
|
50
|
+
USE_JPEGMINI="false"
|
51
|
+
|
52
|
+
# {BooleanString} Optionally overridden with -q or --quit.
|
53
|
+
QUIT_ON_COMPLETE="false"
|
54
|
+
|
55
|
+
# {RegEx} Supported file extensions for each application.
|
56
|
+
FILE_TYPES_REGEX_IMAGE_ALPHA=".*(png)$"
|
57
|
+
FILE_TYPES_REGEX_IMAGE_OPTIM=".*(bmp|gif|jpeg|jpg|pcx|png|pnm|tga|tiff)$"
|
58
|
+
FILE_TYPES_REGEX_JPEGMINI=".*(jpg|jpeg)$"
|
59
|
+
|
60
|
+
# {FilePath} Location of pngquant executable within ImageAlpha.
|
61
|
+
PNGQUANT_PATH="${CLI_PATH}/pngquant"
|
62
|
+
|
63
|
+
# {FilePath} Location of executable within ImageOptim.
|
64
|
+
IMAGEOPTIM_BIN_PATH="/Applications/ImageOptim.app/Contents/MacOS/ImageOptim"
|
65
|
+
|
66
|
+
# {BooleanString} Whether we can compare file quality afterwards, will be set to
|
67
|
+
# true accordingly during initialisation.
|
68
|
+
HAS_IMAGEMAGICK="false"
|
69
|
+
|
70
|
+
# {BooleanString} Whether to disable ANSI colour output.
|
71
|
+
NO_COLOR="false"
|
72
|
+
|
73
|
+
# {BooleanString} Whether to print detailed information about the optimizations
|
74
|
+
IS_VERBOSE="false"
|
75
|
+
|
76
|
+
# ------------------------------------
|
77
|
+
# LOGGING UTILS
|
78
|
+
# ------------------------------------
|
79
|
+
|
80
|
+
# Display a red error message and quit
|
81
|
+
# param {String} $1 message
|
82
|
+
function fatal {
|
83
|
+
if [ $NO_COLOR == "false" ]; then
|
84
|
+
printf "\e[31m✘ $1\033[0m\n"
|
85
|
+
else
|
86
|
+
echo "Error: $1"
|
87
|
+
fi
|
88
|
+
exit 1
|
89
|
+
}
|
90
|
+
|
91
|
+
# Display a message in green with a tick by it
|
92
|
+
# param {String} $1 message
|
93
|
+
function success {
|
94
|
+
if [ $NO_COLOR == "false" ]; then
|
95
|
+
printf "\e[32m✔ $1\033[0m\n"
|
96
|
+
else
|
97
|
+
echo "Success: $1"
|
98
|
+
fi
|
99
|
+
}
|
100
|
+
|
101
|
+
# Display an info message
|
102
|
+
# param {String} $1 message
|
103
|
+
function info {
|
104
|
+
if [ $NO_COLOR == "false" ]; then
|
105
|
+
printf "\e[90m$1\e[39m\n"
|
106
|
+
else
|
107
|
+
echo $1
|
108
|
+
fi
|
109
|
+
}
|
110
|
+
|
111
|
+
# Display a message in green
|
112
|
+
# param {String} $1 message
|
113
|
+
function format_green {
|
114
|
+
if [ $NO_COLOR == "false" ]; then
|
115
|
+
echo "\e[32m$1\e[39m"
|
116
|
+
else
|
117
|
+
echo $1
|
118
|
+
fi
|
119
|
+
}
|
120
|
+
|
121
|
+
# Display a message in red
|
122
|
+
# param {String} $1 message
|
123
|
+
function format_red {
|
124
|
+
if [ $NO_COLOR == "false" ]; then
|
125
|
+
echo "\e[31m$1\e[39m"
|
126
|
+
else
|
127
|
+
echo $1
|
128
|
+
fi
|
129
|
+
}
|
130
|
+
|
131
|
+
# Display usage information
|
132
|
+
function usage {
|
133
|
+
echo "Usage: imageOptim [options]"
|
134
|
+
echo ""
|
135
|
+
echo "Options:"
|
136
|
+
echo ""
|
137
|
+
echo " -d, --directory directory of images to process"
|
138
|
+
echo " -a, --image-alpha pre-process PNGs with ImageAlpha.app *"
|
139
|
+
echo " -j, --jpeg-mini pre-process JPGs with JPEGmini.app **"
|
140
|
+
echo " -m, --min-quality pngquant min quality parameter"
|
141
|
+
echo " -s, --skip-if-larger pngquant use --skip-if-larger"
|
142
|
+
echo " -q, --quit quit all apps when complete"
|
143
|
+
echo " -c, --no-color disable color output"
|
144
|
+
echo " -h, --help display this usage information"
|
145
|
+
echo " -e, --examples display some example commands and uses"
|
146
|
+
echo " -v, --version display the version number"
|
147
|
+
echo " --verbose display detailed, per-file info on optimizations"
|
148
|
+
echo ""
|
149
|
+
echo "* http://pngmini.com"
|
150
|
+
echo "** https://itunes.apple.com/us/app/jpegmini/id498944723"
|
151
|
+
echo ""
|
152
|
+
}
|
153
|
+
|
154
|
+
# Display usage examples
|
155
|
+
function examples {
|
156
|
+
echo "### Optimise a directory of images"
|
157
|
+
echo ""
|
158
|
+
echo "This command will optimise all image files in your Awesome project."
|
159
|
+
echo ""
|
160
|
+
echo " imageOptim --directory ~/Sites/Awesome # [options]"
|
161
|
+
echo ""
|
162
|
+
echo "### Optimise a filtered set of images"
|
163
|
+
echo ""
|
164
|
+
echo "This command will optimise just the .jpg files in your Awesome project."
|
165
|
+
echo ""
|
166
|
+
echo " find ~/Sites/Awesome -name '*.jpg' | imageOptim # [options]"
|
167
|
+
echo ""
|
168
|
+
echo "### Passing additional options"
|
169
|
+
echo ""
|
170
|
+
echo "The long format for enabling options is as follows;"
|
171
|
+
echo ""
|
172
|
+
echo " imageOptim --jpeg-mini --image-alpha --quit --no-color --directory path/to/images"
|
173
|
+
echo ""
|
174
|
+
echo "The equivalent of the above in short format is as follows;"
|
175
|
+
echo ""
|
176
|
+
echo " imageOptim -j -a -q -c -d path/to/images"
|
177
|
+
echo ""
|
178
|
+
echo "### Adding to git pre-commit hook"
|
179
|
+
echo ""
|
180
|
+
echo "Adding the below to **your_project/.git/hooks/pre-commit** will run imageoptim-CLI"
|
181
|
+
echo "each time you commit new and changed files into your project. Any files which"
|
182
|
+
echo "aren't images will be ignored."
|
183
|
+
echo ""
|
184
|
+
echo " images=$(git diff --exit-code --cached --name-only --diff-filter=ACM -- '*.png' '*.jpg')"
|
185
|
+
echo " $(exit $?) || (echo "$images" | imageOptim && git add $images)"
|
186
|
+
echo ""
|
187
|
+
}
|
188
|
+
|
189
|
+
# Display the before/after result of a file
|
190
|
+
# param {String} $1 label
|
191
|
+
# param {Number} $2 size_before
|
192
|
+
# param {Number} $3 size_after
|
193
|
+
# param {Number} $4 quality_loss
|
194
|
+
function render_result {
|
195
|
+
let local savings=$2-$3
|
196
|
+
local savings_percent=$(bc <<< "scale=2; $savings/$2 * 100")
|
197
|
+
local size_before=$(format_red "$(toKb $2)kb")
|
198
|
+
local size_after=$(format_green "$(toKb $3)kb")
|
199
|
+
local savings=$(format_green "$(toKb $savings)kb")
|
200
|
+
local savings_percent=$(format_green "$savings_percent%%")
|
201
|
+
local quality_loss=$(bc <<< "scale=2; 100-$4")
|
202
|
+
local quality_loss=$(format_green "$quality_loss%%")
|
203
|
+
local output="$1 was: $size_before now: $size_after saving: $savings ($savings_percent)"
|
204
|
+
if [[ $HAS_IMAGEMAGICK != "false" ]]; then
|
205
|
+
local output="$output quality: $quality_loss"
|
206
|
+
fi
|
207
|
+
printf "$output\n"
|
208
|
+
}
|
209
|
+
|
210
|
+
# Display the before/after results of all files
|
211
|
+
function output_savings {
|
212
|
+
local total_before=0
|
213
|
+
local total_after=0
|
214
|
+
local perfect_quality=0
|
215
|
+
local actual_quality=0
|
216
|
+
for source_file in "${INDEX_ARRAY[@]}"; do
|
217
|
+
local image_type=$(get_image_type "$source_file")
|
218
|
+
if [[ $image_type != "unsupported" ]]; then
|
219
|
+
local temp_file=$(get_temp_path_for_file "$source_file")
|
220
|
+
local size_before=$(sizeInBytes "$source_file")
|
221
|
+
local size_after=$(sizeInBytes "$temp_file")
|
222
|
+
let local total_before=$total_before+$size_before
|
223
|
+
let local total_after=$total_after+$size_after
|
224
|
+
local perfect_quality=$(bc <<< "scale=2; $perfect_quality+100")
|
225
|
+
local quality_loss="false"
|
226
|
+
if [[ $HAS_IMAGEMAGICK == "true" ]]; then
|
227
|
+
local quality_loss=$(compare -metric MSE "$source_file" "$temp_file" /dev/null 2>&1 >/dev/null | awk '{print $1}')
|
228
|
+
if [[ $quality_loss == *:* ]]; then
|
229
|
+
quality_loss=0
|
230
|
+
fi
|
231
|
+
local actual_quality=$(bc <<< "scale=2; $actual_quality+$quality_loss")
|
232
|
+
fi
|
233
|
+
if [[ $IS_VERBOSE == "true" ]]; then
|
234
|
+
render_result "$source_file" $size_before $size_after $quality_loss
|
235
|
+
fi
|
236
|
+
fi
|
237
|
+
done
|
238
|
+
render_result "TOTAL" $total_before $total_after $quality_loss
|
239
|
+
}
|
240
|
+
|
241
|
+
# ------------------------------------
|
242
|
+
# DETERMINE WHAT EXISTS AT A GIVEN LOCATION
|
243
|
+
# ------------------------------------
|
244
|
+
|
245
|
+
# State whether the entity at path is a "file", "directory", or "other".
|
246
|
+
# param {EntityPath} $1 entity
|
247
|
+
# return {String} file_type
|
248
|
+
function get_entity_type {
|
249
|
+
if [ -f "$1" ]; then
|
250
|
+
echo "file"
|
251
|
+
elif [ -d "$1" ]; then
|
252
|
+
echo "directory"
|
253
|
+
else
|
254
|
+
echo "other"
|
255
|
+
fi
|
256
|
+
}
|
257
|
+
|
258
|
+
# State whether the entity at path is a "png", "jpg", "other", or "unsupported" image file.
|
259
|
+
# param {EntityPath} $1 entity
|
260
|
+
# return {String} image_type
|
261
|
+
function get_image_type {
|
262
|
+
shopt -s nocasematch
|
263
|
+
if [[ "$1" =~ $FILE_TYPES_REGEX_IMAGE_ALPHA ]]; then
|
264
|
+
echo "png"
|
265
|
+
elif [[ "$1" =~ $FILE_TYPES_REGEX_JPEGMINI ]]; then
|
266
|
+
echo "jpg"
|
267
|
+
elif [[ "$1" =~ $FILE_TYPES_REGEX_IMAGE_OPTIM ]]; then
|
268
|
+
echo "other"
|
269
|
+
else
|
270
|
+
echo "unsupported"
|
271
|
+
fi
|
272
|
+
shopt -u nocasematch
|
273
|
+
}
|
274
|
+
|
275
|
+
# ------------------------------------
|
276
|
+
# GATHER PATHS TO IMAGES
|
277
|
+
# ------------------------------------
|
278
|
+
|
279
|
+
# Add file received via stdin to our index file.
|
280
|
+
# param {FilePath} $1 file
|
281
|
+
function add_file_to_index {
|
282
|
+
local image_type=$(get_image_type "$1")
|
283
|
+
if [ $image_type != "unsupported" ]; then
|
284
|
+
echo "$1" >> $INDEX_FILE
|
285
|
+
else
|
286
|
+
info "Ignored as an unsupported file type: $LINE"
|
287
|
+
fi
|
288
|
+
}
|
289
|
+
|
290
|
+
# Add all files in a directory to our index file.
|
291
|
+
# param {DirectoryPath} $1 directory
|
292
|
+
function add_directory_to_index {
|
293
|
+
find -E "$1" -iregex $FILE_TYPES_REGEX_IMAGE_OPTIM -type f -print0 | while read -d '' -r file; do
|
294
|
+
add_file_to_index "$file"
|
295
|
+
done
|
296
|
+
}
|
297
|
+
|
298
|
+
# Read files received via stdin into an index which will outlive the LINE variable.
|
299
|
+
function add_stdin_to_index {
|
300
|
+
if [ "$DIRECTORY_OPTION" == "false" ]; then
|
301
|
+
while read LINE; do
|
302
|
+
local entity_type=$(get_entity_type "$LINE")
|
303
|
+
if [ $entity_type == "file" ]; then
|
304
|
+
add_file_to_index "$LINE"
|
305
|
+
elif [ $entity_type == "directory" ]; then
|
306
|
+
add_directory_to_index "$LINE"
|
307
|
+
else
|
308
|
+
info "Ignored as neither file or directory: $LINE"
|
309
|
+
fi
|
310
|
+
done
|
311
|
+
fi
|
312
|
+
}
|
313
|
+
|
314
|
+
# If -d or --directory were supplied, add the contents of that directory to our processing index.
|
315
|
+
function add_directory_option_to_index {
|
316
|
+
if [ "$DIRECTORY_OPTION" != "false" ]; then
|
317
|
+
if [ -d "$DIRECTORY_OPTION" ]; then
|
318
|
+
add_directory_to_index "$DIRECTORY_OPTION"
|
319
|
+
else
|
320
|
+
fatal "Value for --directory is not a directory: $LINE"
|
321
|
+
fi
|
322
|
+
fi
|
323
|
+
}
|
324
|
+
|
325
|
+
# Remove any duplicate files in our index, which may have occurred when importing directories whose
|
326
|
+
# images have already been gathered by other means.
|
327
|
+
function remove_duplicate_indexes {
|
328
|
+
LC_ALL=C sort -uf "$INDEX_FILE" > "$INDEX_FILE.uniq.txt"
|
329
|
+
mv "$INDEX_FILE.uniq.txt" "$INDEX_FILE"
|
330
|
+
}
|
331
|
+
|
332
|
+
# Check we actually got some files
|
333
|
+
# test -s means exists and size > zero
|
334
|
+
# See: man bash | less '+/^\s*CONDITIONAL'
|
335
|
+
function check_index_exists {
|
336
|
+
if [ ! -s "$INDEX_FILE" ]; then
|
337
|
+
fatal "No files found by the find command or in the provided directory"
|
338
|
+
fi
|
339
|
+
}
|
340
|
+
|
341
|
+
# Read our index file into an Array.
|
342
|
+
function parse_index {
|
343
|
+
IFS=$'\n' read -d '' -r -a INDEX_ARRAY < "$INDEX_FILE"
|
344
|
+
TOTAL_IMAGES=${#INDEX_ARRAY[@]}
|
345
|
+
}
|
346
|
+
|
347
|
+
# Construct a clean Array containing sorted, unique paths to every image we should process.
|
348
|
+
function gather_paths_to_images {
|
349
|
+
add_stdin_to_index
|
350
|
+
add_directory_option_to_index
|
351
|
+
check_index_exists
|
352
|
+
remove_duplicate_indexes
|
353
|
+
parse_index
|
354
|
+
}
|
355
|
+
|
356
|
+
# ------------------------------------
|
357
|
+
# PREPARE A TEMPORARY DIRECTORY
|
358
|
+
# ------------------------------------
|
359
|
+
|
360
|
+
function clean {
|
361
|
+
rm -rf "$TEMP_PATH"
|
362
|
+
}
|
363
|
+
|
364
|
+
# Automating JPEGmini is particularily difficult and unusably slow when working with arbitrary sets
|
365
|
+
# of files. Instead, we create a temporary directory and pass that once to JPEGmini rather than
|
366
|
+
# each file individually.
|
367
|
+
function init_temp_directory {
|
368
|
+
clean
|
369
|
+
mkdir -p "$TEMP_PATH"
|
370
|
+
chmod 777 "$TEMP_PATH"
|
371
|
+
}
|
372
|
+
|
373
|
+
# Get the absolute path of an entity
|
374
|
+
# param {EntityPath} $1 entity
|
375
|
+
# return {EntityPath} absolute_path
|
376
|
+
function to_absolute {
|
377
|
+
local present_dir="$PWD"
|
378
|
+
local file_dir=$(dirname "$1")
|
379
|
+
local file_name=$(basename "$1")
|
380
|
+
cd "$file_dir"
|
381
|
+
local absolute_dir="$PWD"
|
382
|
+
cd "$present_dir"
|
383
|
+
echo "${absolute_dir}/${file_name}"
|
384
|
+
}
|
385
|
+
|
386
|
+
# Determine the location in our temp directory a given file should be held at.
|
387
|
+
# param {FilePath} $1 file
|
388
|
+
# return {FilePath} temp_file
|
389
|
+
function get_temp_path_for_file {
|
390
|
+
local absolute_path=$(to_absolute "$1")
|
391
|
+
local image_type=$(get_image_type "$absolute_path")
|
392
|
+
echo "${TEMP_PATH}/${image_type}/$absolute_path"
|
393
|
+
}
|
394
|
+
|
395
|
+
# Copy all files received to a temp directory, grouped by file extension.
|
396
|
+
function populate_temp_directories {
|
397
|
+
local image_type
|
398
|
+
local temp_file
|
399
|
+
for source_file in "${INDEX_ARRAY[@]}"; do
|
400
|
+
image_type=$(get_image_type "$source_file")
|
401
|
+
temp_file=$(get_temp_path_for_file "$source_file")
|
402
|
+
if [[ $image_type != "unsupported" ]]; then
|
403
|
+
ditto "$source_file" "$temp_file"
|
404
|
+
fi
|
405
|
+
done
|
406
|
+
}
|
407
|
+
|
408
|
+
# @TODO: DRY these two methods
|
409
|
+
|
410
|
+
# Copy all files received back to their original locations.
|
411
|
+
function replace_originals {
|
412
|
+
local image_type
|
413
|
+
local temp_file
|
414
|
+
for source_file in "${INDEX_ARRAY[@]}"; do
|
415
|
+
image_type=$(get_image_type "$source_file")
|
416
|
+
temp_file=$(get_temp_path_for_file "$source_file")
|
417
|
+
if [[ $image_type != "unsupported" ]]; then
|
418
|
+
ditto "$temp_file" "$source_file"
|
419
|
+
rm "$temp_file"
|
420
|
+
fi
|
421
|
+
done
|
422
|
+
}
|
423
|
+
|
424
|
+
# Gather paths to all images we should process and prepare a temporary directory containing copies.
|
425
|
+
function prepare_images {
|
426
|
+
gather_paths_to_images
|
427
|
+
if [ $TOTAL_IMAGES -gt 0 ]; then
|
428
|
+
populate_temp_directories
|
429
|
+
fi
|
430
|
+
}
|
431
|
+
|
432
|
+
function start {
|
433
|
+
init_temp_directory
|
434
|
+
prepare_images
|
435
|
+
}
|
436
|
+
|
437
|
+
function finish {
|
438
|
+
replace_originals
|
439
|
+
clean
|
440
|
+
}
|
441
|
+
|
442
|
+
# ------------------------------------
|
443
|
+
# INVOKE IMAGE APPLICATIONS
|
444
|
+
# ------------------------------------
|
445
|
+
|
446
|
+
# Determine whether GUI Scripting is enabled, which is needed to automate JPEGmini.
|
447
|
+
function has_gui_script {
|
448
|
+
echo `osascript "$APPLESCRIPT_BIN" supportsAssistiveDevices`
|
449
|
+
}
|
450
|
+
|
451
|
+
# State whether eg node, brew, gem etc is installed
|
452
|
+
# param {CliApp} $1 bin
|
453
|
+
# return {BooleanString} is_installed
|
454
|
+
function has_imagemagick {
|
455
|
+
local is_installed="true"
|
456
|
+
type compare >/dev/null 2>&1 || { local is_installed="false"; }
|
457
|
+
echo "$is_installed"
|
458
|
+
}
|
459
|
+
|
460
|
+
function has_imageoptim {
|
461
|
+
echo `osascript "$APPLESCRIPT_BIN" hasImageOptim`
|
462
|
+
}
|
463
|
+
|
464
|
+
function has_jpegmini {
|
465
|
+
echo `osascript "$APPLESCRIPT_BIN" hasJPEGmini`
|
466
|
+
}
|
467
|
+
|
468
|
+
function get_jpegmini_app_name {
|
469
|
+
echo `osascript "$APPLESCRIPT_BIN" getJpegMiniAppName`
|
470
|
+
}
|
471
|
+
|
472
|
+
# If enabled and installed, run ImageAlpha on our temp directory
|
473
|
+
function run_image_alpha {
|
474
|
+
if [ "true" == "$USE_IMAGE_ALPHA" ]; then
|
475
|
+
info "Running ImageAlpha..."
|
476
|
+
local added_options
|
477
|
+
if [ "true" == "$SKIP_IF_LARGER" ]; then
|
478
|
+
added_options="--skip-if-larger"
|
479
|
+
fi
|
480
|
+
find "${TEMP_PATH}/png" -type f -print0 | xargs -n10 -P8 -0 "$PNGQUANT_PATH" --ext=.png --force --speed=1 --quality="$MIN_QUALITY"-100 ${added_options} --
|
481
|
+
fi
|
482
|
+
}
|
483
|
+
|
484
|
+
# If enabled and installed, run JPEGmini on our temp directory
|
485
|
+
function run_jpegmini {
|
486
|
+
if [ "true" == "$USE_JPEGMINI" ]; then
|
487
|
+
if [ "true" == "$HAS_JPEGMINI" ]; then
|
488
|
+
if [ "true" != "$HAS_GUI_SCRIPT" ]; then
|
489
|
+
fatal "To automate JPEGmini we need to add Terminal.app (or iTerm.app etc) to the 'support for assistive devices' whitelist. Please see README for more details."
|
490
|
+
fi
|
491
|
+
info "Running ${JPEGMINI_NAME}..."
|
492
|
+
local location=`osascript -e "POSIX path of (path to application \"${JPEGMINI_NAME}\")"`
|
493
|
+
open "${location}"
|
494
|
+
osascript "$APPLESCRIPT_BIN" runJPEGmini
|
495
|
+
if [ "true" == "$QUIT_ON_COMPLETE" ]; then
|
496
|
+
osascript "$APPLESCRIPT_BIN" quitJPEGmini
|
497
|
+
fi
|
498
|
+
else
|
499
|
+
fatal "JPEGmini is not installed (https://itunes.apple.com/us/app/jpegmini/id498944723)"
|
500
|
+
fi
|
501
|
+
fi
|
502
|
+
}
|
503
|
+
|
504
|
+
# If enabled and installed, run ImageOptim on our temp directory
|
505
|
+
function run_image_optim {
|
506
|
+
info "Running ImageOptim..."
|
507
|
+
# find where imageoptim is installed (opens app)
|
508
|
+
local location=`osascript -e 'POSIX path of (path to application "ImageOptim")'`
|
509
|
+
# close duplicate app
|
510
|
+
osascript "$APPLESCRIPT_BIN" quitImageOptim
|
511
|
+
# run images over the app
|
512
|
+
"${location}Contents/MacOS/ImageOptim" 2> /dev/null "${TEMP_PATH}"
|
513
|
+
if [ "true" == "$QUIT_ON_COMPLETE" ]; then
|
514
|
+
osascript "$APPLESCRIPT_BIN" quitImageOptim
|
515
|
+
fi
|
516
|
+
}
|
517
|
+
|
518
|
+
# ------------------------------------
|
519
|
+
# GET AND MANAGE FILE SIZES
|
520
|
+
# ------------------------------------
|
521
|
+
|
522
|
+
# Get the size of a file, in bytes.
|
523
|
+
# param {FilePath} $1 file
|
524
|
+
# return {Number} file_size
|
525
|
+
function sizeInBytes {
|
526
|
+
echo $($STAT_BIN -f %z "$1")
|
527
|
+
}
|
528
|
+
|
529
|
+
# Convert a value in bytes to kilobytes in 3 decimal places, 1b is 0.001kb for example.
|
530
|
+
# param {Number} $1 bytes
|
531
|
+
# return {Number} kilobytes
|
532
|
+
function toKb {
|
533
|
+
echo $(bc <<< "scale=3; $1/1000")
|
534
|
+
}
|