Skip to content

Commit a7be416

Browse files
committed
new script: image optimization
1 parent 1a58c53 commit a7be416

4 files changed

Lines changed: 291 additions & 0 deletions

File tree

Formula/pancake.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ class Pancake < Formula
66
def install
77
# keep-sorted start
88
bin.install "aws_china_mfa/aws_china_mfa.sh" => "aws_china_mfa"
9+
bin.install "img_optimize/img_optimize.sh" => "img_optimize"
910
bin.install "op_login_all/op_login_all.sh" => "op_login_all"
1011
bin.install "pritunl_login/pritunl_login.sh" => "pritunl_login"
1112
bin.install "sd_world/sd_world.sh" => "sd_world"
@@ -17,6 +18,8 @@ def install
1718
# keep-sorted start
1819
assert_predicate bin/"aws_china_mfa", :executable?
1920
assert_predicate bin/"aws_china_mfa", :exist?
21+
assert_predicate bin/"img_optimize", :executable?
22+
assert_predicate bin/"img_optimize", :exist?
2023
assert_predicate bin/"op_login_all", :executable?
2124
assert_predicate bin/"op_login_all", :exist?
2225
assert_predicate bin/"pritunl_login", :executable?

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ A Homebrew formula is available in the `Formula/` directory.
1919

2020
<!-- keep-sorted start -->
2121
- **[aws_china_mfa](aws_china_mfa/)** - Authenticate to AWS China using MFA and export temporary session credentials
22+
- **[img_optimize](img_optimize/)** - Optimize images for size while maintaining quality
2223
- **[op_login_all](op_login_all/)** - Automatically log into all your 1Password accounts
2324
- **[pritunl_login](pritunl_login/)** - Connect to Pritunl VPN using credentials stored in 1Password
2425
- **[sd_world](sd_world/)** - Cross-platform full system upgrade script

img_optimize/README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# img_optimize.sh
2+
3+
Optimize images for size while maintaining quality.
4+
5+
## Usage
6+
7+
Optimize a single image:
8+
9+
```bash
10+
./img_optimize.sh photo.jpg
11+
```
12+
13+
Optimize all images in a directory:
14+
15+
```bash
16+
./img_optimize.sh vacation-photos/
17+
```
18+
19+
Optimize with custom quality:
20+
21+
```bash
22+
./img_optimize.sh --quality 90 cat-meme.png
23+
```
24+
25+
Mix files and directories:
26+
27+
```bash
28+
./img_optimize.sh logo.png banner.jpg downloads/
29+
```
30+
31+
## Example Output
32+
33+
```
34+
% ./img_optimize.sh --quality 85 sample-images/
35+
Optimizing images with quality: 85
36+
37+
Processing directory: sample-images/
38+
✓ sample-images/beach-sunset.jpg
39+
2MB → 456KB (saved 1MB, 76%)
40+
✓ sample-images/cat-portrait.png
41+
3MB → 892KB (saved 2MB, 70%)
42+
✓ sample-images/food-photo.webp
43+
1MB → 312KB (saved 780KB, 74%)
44+
45+
Summary:
46+
Processed: 3 images
47+
Total size: 6MB → 1MB
48+
Total saved: 4MB (73%)
49+
```
50+
51+
## How It Works
52+
53+
The script uses ImageMagick to optimize images with lossy compression:
54+
55+
- Strips metadata (EXIF data, thumbnails, etc.) to reduce file size
56+
- Applies quality compression (default: 85%)
57+
- Creates new files with `.optimized` suffix (originals are preserved)
58+
- Supports JPEG, PNG, WebP, and GIF formats
59+
60+
## Installation
61+
62+
Requires ImageMagick:
63+
64+
```bash
65+
# macOS
66+
brew install imagemagick
67+
68+
# Linux (Debian/Ubuntu)
69+
sudo apt-get install imagemagick
70+
71+
# Linux (Fedora/RHEL)
72+
sudo dnf install ImageMagick
73+
```

img_optimize/img_optimize.sh

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
usage() {
5+
cat << EOF
6+
Usage: $0 [OPTIONS] IMAGE|DIRECTORY...
7+
8+
Optimize images for size while maintaining quality. Supports JPEG, PNG, WebP, and GIF formats.
9+
10+
OPTIONS:
11+
-h, --help Show this help message and exit
12+
-q, --quality NUM Set quality level (1-100, default: 85)
13+
14+
DESCRIPTION:
15+
Optimizes one or more image files using ImageMagick. Creates new files with
16+
the '.optimized' suffix (e.g., photo.jpg becomes photo.optimized.jpg).
17+
18+
If a directory is provided, recursively processes all supported images within it.
19+
20+
The script strips metadata and applies lossy compression to reduce file size
21+
while maintaining good visual quality.
22+
23+
PREREQUISITES:
24+
- ImageMagick must be installed ('magick' or 'convert' command)
25+
26+
EXAMPLES:
27+
$0 vacation-selfie.jpg
28+
$0 --quality 90 cat-meme.png
29+
$0 photos/summer-2024/
30+
$0 logo.png banner.jpg downloads/
31+
32+
EXIT CODES:
33+
0 All images optimized successfully
34+
1 Error occurred during processing
35+
EOF
36+
}
37+
38+
# Default settings
39+
QUALITY=85
40+
41+
# Parse arguments
42+
POSITIONAL_ARGS=()
43+
44+
while [[ $# -gt 0 ]]; do
45+
case $1 in
46+
-h|--help)
47+
usage
48+
exit 0
49+
;;
50+
-q|--quality)
51+
QUALITY="$2"
52+
shift 2
53+
;;
54+
*)
55+
POSITIONAL_ARGS+=("$1")
56+
shift
57+
;;
58+
esac
59+
done
60+
61+
# Reset positional parameters
62+
if [[ ${#POSITIONAL_ARGS[@]} -gt 0 ]]; then
63+
set -- "${POSITIONAL_ARGS[@]}"
64+
else
65+
set --
66+
fi
67+
68+
check_dependencies() {
69+
# Try both 'magick' (v7) and 'convert' (v6) commands
70+
if ! command -v magick &> /dev/null && ! command -v convert &> /dev/null; then
71+
echo "Error: Missing required dependencies: ImageMagick (magick or convert)"
72+
echo "Install ImageMagick via: brew install imagemagick"
73+
exit 1
74+
fi
75+
}
76+
77+
get_file_size() {
78+
local file="$1"
79+
# Try GNU stat first, then BSD stat
80+
if stat -c%s "$file" 2>/dev/null; then
81+
return
82+
elif stat -f%z "$file" 2>/dev/null; then
83+
return
84+
else
85+
echo "0"
86+
fi
87+
}
88+
89+
format_size() {
90+
local size=$1
91+
if [[ $size -lt 1024 ]]; then
92+
echo "${size}B"
93+
elif [[ $size -lt 1048576 ]]; then
94+
echo "$(( size / 1024 ))KB"
95+
else
96+
echo "$(( size / 1048576 ))MB"
97+
fi
98+
}
99+
100+
optimize_image() {
101+
local input_file="$1"
102+
local basename="${input_file%.*}"
103+
local extension="${input_file##*.}"
104+
local output_file="${basename}.optimized.${extension}"
105+
106+
# Skip if already optimized
107+
if [[ "$input_file" == *".optimized."* ]]; then
108+
return 0
109+
fi
110+
111+
# Get original size
112+
local original_size
113+
original_size=$(get_file_size "$input_file")
114+
115+
# Optimize using ImageMagick
116+
local magick_cmd="magick"
117+
if ! command -v magick &> /dev/null; then
118+
magick_cmd="convert"
119+
fi
120+
121+
if ! "$magick_cmd" "$input_file" -strip -quality "$QUALITY" "$output_file" 2>/dev/null; then
122+
echo "✗ Failed to optimize: $input_file"
123+
return 1
124+
fi
125+
126+
# Get new size and calculate savings
127+
local new_size
128+
new_size=$(get_file_size "$output_file")
129+
local saved=$((original_size - new_size))
130+
local percent=0
131+
if [[ $original_size -gt 0 ]]; then
132+
percent=$(( (saved * 100) / original_size ))
133+
fi
134+
135+
echo "$input_file"
136+
echo " $(format_size "$original_size")$(format_size "$new_size") (saved $(format_size "$saved"), ${percent}%)"
137+
138+
# Update global counters
139+
TOTAL_ORIGINAL=$((TOTAL_ORIGINAL + original_size))
140+
TOTAL_NEW=$((TOTAL_NEW + new_size))
141+
PROCESSED_COUNT=$((PROCESSED_COUNT + 1))
142+
}
143+
144+
process_path() {
145+
local path="$1"
146+
147+
if [[ -f "$path" ]]; then
148+
# Process single file if it's a supported image format
149+
local ext="${path##*.}"
150+
ext=$(echo "$ext" | tr '[:upper:]' '[:lower:]')
151+
case "$ext" in
152+
jpg|jpeg|png|webp|gif)
153+
optimize_image "$path"
154+
;;
155+
*)
156+
echo "Skipping unsupported file: $path"
157+
;;
158+
esac
159+
elif [[ -d "$path" ]]; then
160+
# Process directory recursively
161+
echo "Processing directory: $path"
162+
while IFS= read -r -d '' file; do
163+
optimize_image "$file"
164+
done < <(find "$path" -type f \( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.webp" -o -iname "*.gif" \) -print0)
165+
else
166+
echo "Error: Path not found: $path"
167+
exit 1
168+
fi
169+
}
170+
171+
main() {
172+
check_dependencies
173+
174+
# Validate quality parameter
175+
if [[ ! "$QUALITY" =~ ^[0-9]+$ ]] || [[ "$QUALITY" -lt 1 ]] || [[ "$QUALITY" -gt 100 ]]; then
176+
echo "Error: Quality must be between 1 and 100"
177+
exit 1
178+
fi
179+
180+
# Check if we have any arguments
181+
if [[ $# -eq 0 ]]; then
182+
echo "Error: No input files or directories specified"
183+
usage
184+
exit 1
185+
fi
186+
187+
echo "Optimizing images with quality: $QUALITY"
188+
echo ""
189+
190+
# Global counters
191+
TOTAL_ORIGINAL=0
192+
TOTAL_NEW=0
193+
PROCESSED_COUNT=0
194+
195+
# Process each argument
196+
for arg in "$@"; do
197+
process_path "$arg"
198+
done
199+
200+
echo ""
201+
echo "Summary:"
202+
echo "Processed: $PROCESSED_COUNT images"
203+
if [[ $PROCESSED_COUNT -gt 0 ]]; then
204+
local total_saved=$((TOTAL_ORIGINAL - TOTAL_NEW))
205+
local total_percent=0
206+
if [[ $TOTAL_ORIGINAL -gt 0 ]]; then
207+
total_percent=$(( (total_saved * 100) / TOTAL_ORIGINAL ))
208+
fi
209+
echo "Total size: $(format_size "$TOTAL_ORIGINAL")$(format_size "$TOTAL_NEW")"
210+
echo "Total saved: $(format_size "$total_saved") (${total_percent}%)"
211+
fi
212+
}
213+
214+
main "$@"

0 commit comments

Comments
 (0)