Files
pulse-memory/skills/vision/scripts/script.sh
T
Pulse 483bcbf92e chore: inicializacao agente auto-melhoria + nova-self-improver + .learnings/ + MEMORY.md
- Adiciona .learnings/ completo (LEARNINGS, ERRORS, FEATURE_REQUESTS, PATTERN_COUNTER)
- Cria MEMORY.md, SESSION-STATE.md, USER.md template
- Cria memory/2026-05-19.md (log diario)
- cria IDENTITY.md (Pulse )
- Atualiza AGENTS.md, SOUL.md, TOOLS.md com regras de auto-melhoria
- Instala nova-self-improver v1.0.0 via clawhub
- Skills totais: 6 instaladas
2026-05-19 20:42:04 -03:00

521 lines
16 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
###############################################################################
# vision - Image Processing Toolkit
# Version: 3.0.0
# Author: BytesAgain
#
# Commands: resize, crop, convert, optimize, info, watermark
# Requires: ImageMagick (convert/identify/mogrify commands)
###############################################################################
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
JSON_OUTPUT=false
# ---------------------------------------------------------------------------
# Utilities
# ---------------------------------------------------------------------------
die() { echo "ERROR: $*" >&2; exit 1; }
log() { echo "[vision] $*" >&2; }
check_imagemagick() {
if ! command -v convert &>/dev/null && ! command -v magick &>/dev/null; then
die "ImageMagick is required but not installed. Install with: apt install imagemagick / brew install imagemagick"
fi
}
# Wrapper to handle both ImageMagick 6 (convert) and 7 (magick)
im_convert() {
if command -v magick &>/dev/null; then
magick "$@"
else
convert "$@"
fi
}
im_identify() {
if command -v magick &>/dev/null; then
magick identify "$@"
else
identify "$@"
fi
}
parse_global_flags() {
local args=()
for arg in "$@"; do
if [[ "$arg" == "--json" ]]; then
JSON_OUTPUT=true
else
args+=("$arg")
fi
done
REMAINING_ARGS=("${args[@]+"${args[@]}"}")
}
get_extension() {
local filename="$1"
echo "${filename##*.}" | tr '[:upper:]' '[:lower:]'
}
get_basename() {
local filename="$1"
local base
base=$(basename "$filename")
echo "${base%.*}"
}
get_dirname() {
dirname "$1"
}
auto_output_name() {
local input="$1"
local suffix="$2"
local ext="${3:-$(get_extension "$input")}"
local dir
dir=$(get_dirname "$input")
local base
base=$(get_basename "$input")
echo "${dir}/${base}_${suffix}.${ext}"
}
validate_input() {
local input="$1"
[[ -n "$input" ]] || die "No input file specified. Use --input"
[[ -f "$input" ]] || die "File not found: $input"
}
# ---------------------------------------------------------------------------
# cmd_help - List all commands
# ---------------------------------------------------------------------------
cmd_help() {
cat <<'EOF'
vision - Image Processing Toolkit
Usage: bash script.sh <command> [options]
Commands:
resize Resize image to dimensions or percentage
crop Crop image to a region
convert Convert between png, jpg, webp formats
optimize Compress image to reduce file size
info Read EXIF and image metadata
watermark Add text watermark to image
help Show this help message
Common options:
--input Input image file (required for all commands)
--output Output file path (auto-generated if omitted)
--json JSON output for info command
Requires: ImageMagick (apt install imagemagick)
Examples:
bash script.sh resize --input photo.jpg --width 800
bash script.sh convert --input photo.png --to webp
bash script.sh info --input photo.jpg --json
EOF
}
# ---------------------------------------------------------------------------
# cmd_resize - Resize image
# ---------------------------------------------------------------------------
cmd_resize() {
check_imagemagick
local input="" output="" width="" height="" percent=""
while [[ $# -gt 0 ]]; do
case "$1" in
--input) shift; input="$1" ;;
--output) shift; output="$1" ;;
--width) shift; width="$1" ;;
--height) shift; height="$1" ;;
--percent) shift; percent="$1" ;;
*) die "Unknown option for resize: $1" ;;
esac
shift
done
validate_input "$input"
[[ -n "$output" ]] || output=$(auto_output_name "$input" "resized")
local geometry=""
if [[ -n "$percent" ]]; then
geometry="${percent}%"
elif [[ -n "$width" && -n "$height" ]]; then
geometry="${width}x${height}!"
elif [[ -n "$width" ]]; then
geometry="${width}x"
elif [[ -n "$height" ]]; then
geometry="x${height}"
else
die "resize requires --width, --height, or --percent"
fi
im_convert "$input" -resize "$geometry" "$output"
# Report results
local orig_size new_size
orig_size=$(stat -c%s "$input" 2>/dev/null || stat -f%z "$input")
new_size=$(stat -c%s "$output" 2>/dev/null || stat -f%z "$output")
local orig_dims new_dims
orig_dims=$(im_identify -format "%wx%h" "$input")
new_dims=$(im_identify -format "%wx%h" "$output")
echo "Resized: $input$output"
echo " Original: ${orig_dims} ($(( orig_size / 1024 )) KB)"
echo " Resized: ${new_dims} ($(( new_size / 1024 )) KB)"
}
# ---------------------------------------------------------------------------
# cmd_crop - Crop image
# ---------------------------------------------------------------------------
cmd_crop() {
check_imagemagick
local input="" output="" width="" height="" x="0" y="0" gravity=""
while [[ $# -gt 0 ]]; do
case "$1" in
--input) shift; input="$1" ;;
--output) shift; output="$1" ;;
--width) shift; width="$1" ;;
--height) shift; height="$1" ;;
--x) shift; x="$1" ;;
--y) shift; y="$1" ;;
--gravity) shift; gravity="$1" ;;
*) die "Unknown option for crop: $1" ;;
esac
shift
done
validate_input "$input"
[[ -n "$width" ]] || die "crop requires --width"
[[ -n "$height" ]] || die "crop requires --height"
[[ -n "$output" ]] || output=$(auto_output_name "$input" "cropped")
if [[ -n "$gravity" ]]; then
im_convert "$input" -gravity "$gravity" -crop "${width}x${height}+0+0" +repage "$output"
else
im_convert "$input" -crop "${width}x${height}+${x}+${y}" +repage "$output"
fi
local new_dims
new_dims=$(im_identify -format "%wx%h" "$output")
echo "Cropped: $input$output"
echo " Region: ${width}x${height}+${x}+${y}"
echo " Result: ${new_dims}"
}
# ---------------------------------------------------------------------------
# cmd_convert - Format conversion
# ---------------------------------------------------------------------------
cmd_convert() {
check_imagemagick
local input="" output="" to="" quality=""
while [[ $# -gt 0 ]]; do
case "$1" in
--input) shift; input="$1" ;;
--output) shift; output="$1" ;;
--to) shift; to="$1" ;;
--quality) shift; quality="$1" ;;
*) die "Unknown option for convert: $1" ;;
esac
shift
done
validate_input "$input"
[[ -n "$to" ]] || die "convert requires --to (png, jpg, webp)"
# Normalize format name
case "$to" in
jpeg) to="jpg" ;;
esac
[[ "$to" =~ ^(png|jpg|webp)$ ]] || die "Supported formats: png, jpg, webp"
if [[ -z "$output" ]]; then
local dir base
dir=$(get_dirname "$input")
base=$(get_basename "$input")
output="${dir}/${base}.${to}"
fi
local cmd_args=("$input")
[[ -n "$quality" ]] && cmd_args+=(-quality "$quality")
cmd_args+=("$output")
im_convert "${cmd_args[@]}"
local orig_size new_size
orig_size=$(stat -c%s "$input" 2>/dev/null || stat -f%z "$input")
new_size=$(stat -c%s "$output" 2>/dev/null || stat -f%z "$output")
local orig_ext
orig_ext=$(get_extension "$input")
echo "Converted: $input ($orig_ext) → $output ($to)"
echo " Original: $(( orig_size / 1024 )) KB"
echo " Output: $(( new_size / 1024 )) KB"
local ratio
ratio=$(awk "BEGIN {printf \"%.1f\", $new_size * 100 / $orig_size}")
echo " Size: ${ratio}% of original"
}
# ---------------------------------------------------------------------------
# cmd_optimize - Compress image
# ---------------------------------------------------------------------------
cmd_optimize() {
check_imagemagick
local input="" output="" quality="85"
while [[ $# -gt 0 ]]; do
case "$1" in
--input) shift; input="$1" ;;
--output) shift; output="$1" ;;
--quality) shift; quality="$1" ;;
*) die "Unknown option for optimize: $1" ;;
esac
shift
done
validate_input "$input"
[[ -n "$output" ]] || output=$(auto_output_name "$input" "optimized")
local ext
ext=$(get_extension "$input")
local orig_size
orig_size=$(stat -c%s "$input" 2>/dev/null || stat -f%z "$input")
case "$ext" in
jpg|jpeg)
im_convert "$input" -quality "$quality" -sampling-factor 4:2:0 -strip \
-interlace JPEG "$output"
;;
png)
im_convert "$input" -quality "$quality" -strip -define png:compression-level=9 "$output"
;;
webp)
im_convert "$input" -quality "$quality" -define webp:method=6 "$output"
;;
*)
im_convert "$input" -quality "$quality" -strip "$output"
;;
esac
local new_size
new_size=$(stat -c%s "$output" 2>/dev/null || stat -f%z "$output")
local saved=$((orig_size - new_size))
local ratio
ratio=$(awk "BEGIN {printf \"%.1f\", $new_size * 100 / $orig_size}")
echo "Optimized: $input$output"
echo " Original: $(( orig_size / 1024 )) KB"
echo " Output: $(( new_size / 1024 )) KB"
echo " Saved: $(( saved / 1024 )) KB (${ratio}% of original)"
echo " Quality: $quality"
}
# ---------------------------------------------------------------------------
# cmd_info - Read EXIF and metadata
# ---------------------------------------------------------------------------
cmd_info() {
check_imagemagick
local input=""
while [[ $# -gt 0 ]]; do
case "$1" in
--input) shift; input="$1" ;;
*) die "Unknown option for info: $1" ;;
esac
shift
done
validate_input "$input"
local format width height colorspace depth filesize
format=$(im_identify -format "%m" "$input" 2>/dev/null | head -1)
width=$(im_identify -format "%w" "$input" 2>/dev/null | head -1)
height=$(im_identify -format "%h" "$input" 2>/dev/null | head -1)
colorspace=$(im_identify -format "%[colorspace]" "$input" 2>/dev/null | head -1)
depth=$(im_identify -format "%z" "$input" 2>/dev/null | head -1)
filesize=$(stat -c%s "$input" 2>/dev/null || stat -f%z "$input")
# Try to get EXIF data
local exif_make="" exif_model="" exif_date="" exif_exposure="" exif_iso="" exif_focal=""
if command -v exiftool &>/dev/null; then
exif_make=$(exiftool -Make -s3 "$input" 2>/dev/null || true)
exif_model=$(exiftool -Model -s3 "$input" 2>/dev/null || true)
exif_date=$(exiftool -DateTimeOriginal -s3 "$input" 2>/dev/null || true)
exif_exposure=$(exiftool -ExposureTime -s3 "$input" 2>/dev/null || true)
exif_iso=$(exiftool -ISO -s3 "$input" 2>/dev/null || true)
exif_focal=$(exiftool -FocalLength -s3 "$input" 2>/dev/null || true)
else
# Fallback: try ImageMagick verbose output
local verbose
verbose=$(im_identify -verbose "$input" 2>/dev/null || true)
exif_make=$(echo "$verbose" | grep -i "exif:Make:" | head -1 | sed 's/.*: *//' || true)
exif_model=$(echo "$verbose" | grep -i "exif:Model:" | head -1 | sed 's/.*: *//' || true)
exif_date=$(echo "$verbose" | grep -i "exif:DateTimeOriginal:" | head -1 | sed 's/.*: *//' || true)
exif_exposure=$(echo "$verbose" | grep -i "exif:ExposureTime:" | head -1 | sed 's/.*: *//' || true)
exif_iso=$(echo "$verbose" | grep -i "exif:ISOSpeedRatings:" | head -1 | sed 's/.*: *//' || true)
exif_focal=$(echo "$verbose" | grep -i "exif:FocalLength:" | head -1 | sed 's/.*: *//' || true)
fi
local filesize_kb=$(( filesize / 1024 ))
local filesize_mb=""
[[ $filesize -gt 1048576 ]] && filesize_mb=" ($(awk "BEGIN {printf \"%.1f\", $filesize / 1048576}") MB)"
if [[ "$JSON_OUTPUT" == true ]]; then
printf '{'
printf '"file":"%s",' "$(basename "$input")"
printf '"format":"%s",' "$format"
printf '"width":%s,' "$width"
printf '"height":%s,' "$height"
printf '"colorspace":"%s",' "$colorspace"
printf '"depth":%s,' "$depth"
printf '"filesize":%s,' "$filesize"
printf '"filesize_kb":%s,' "$filesize_kb"
printf '"exif":{'
printf '"make":"%s",' "${exif_make:-}"
printf '"model":"%s",' "${exif_model:-}"
printf '"date":"%s",' "${exif_date:-}"
printf '"exposure":"%s",' "${exif_exposure:-}"
printf '"iso":"%s",' "${exif_iso:-}"
printf '"focal_length":"%s"' "${exif_focal:-}"
printf '}}\n'
else
echo "=== Image Info ==="
echo ""
echo "File: $(basename "$input")"
echo "Format: $format"
echo "Dimensions: ${width}x${height}"
echo "Color space: $colorspace"
echo "Bit depth: $depth"
echo "File size: ${filesize_kb} KB${filesize_mb}"
echo ""
if [[ -n "$exif_make" || -n "$exif_model" || -n "$exif_date" ]]; then
echo "EXIF Data:"
[[ -n "$exif_make" ]] && echo " Camera make: $exif_make"
[[ -n "$exif_model" ]] && echo " Camera model: $exif_model"
[[ -n "$exif_date" ]] && echo " Date taken: $exif_date"
[[ -n "$exif_exposure" ]] && echo " Exposure: $exif_exposure"
[[ -n "$exif_iso" ]] && echo " ISO: $exif_iso"
[[ -n "$exif_focal" ]] && echo " Focal length: $exif_focal"
else
echo "EXIF Data: (none found)"
fi
fi
}
# ---------------------------------------------------------------------------
# cmd_watermark - Add text watermark
# ---------------------------------------------------------------------------
cmd_watermark() {
check_imagemagick
local input="" output="" text="" position="southeast" opacity="50" size="24" color="white"
while [[ $# -gt 0 ]]; do
case "$1" in
--input) shift; input="$1" ;;
--output) shift; output="$1" ;;
--text) shift; text="$1" ;;
--position) shift; position="$1" ;;
--opacity) shift; opacity="$1" ;;
--size) shift; size="$1" ;;
--color) shift; color="$1" ;;
*) die "Unknown option for watermark: $1" ;;
esac
shift
done
validate_input "$input"
[[ -n "$text" ]] || die "watermark requires --text"
[[ -n "$output" ]] || output=$(auto_output_name "$input" "watermarked")
# Map position to ImageMagick gravity
local gravity
case "$position" in
northwest|nw) gravity="NorthWest" ;;
north|n) gravity="North" ;;
northeast|ne) gravity="NorthEast" ;;
west|w) gravity="West" ;;
center|c) gravity="Center" ;;
east|e) gravity="East" ;;
southwest|sw) gravity="SouthWest" ;;
south|s) gravity="South" ;;
southeast|se) gravity="SouthEast" ;;
*) gravity="SouthEast" ;;
esac
# Calculate dissolve value (ImageMagick uses 0-100)
local dissolve="$opacity"
im_convert "$input" \
\( -size "$(im_identify -format '%w' "$input")x$(im_identify -format '%h' "$input")" xc:none \
-gravity "$gravity" \
-fill "$color" \
-pointsize "$size" \
-annotate +10+10 "$text" \) \
-gravity "$gravity" \
-compose dissolve -define "compose:args=${dissolve}" \
-composite "$output"
echo "Watermark added: $input$output"
echo " Text: \"$text\""
echo " Position: $position ($gravity)"
echo " Opacity: ${opacity}%"
echo " Size: ${size}pt"
echo " Color: $color"
}
# ---------------------------------------------------------------------------
# Main dispatch
# ---------------------------------------------------------------------------
main() {
[[ $# -ge 1 ]] || { cmd_help; exit 0; }
local command="$1"
shift
parse_global_flags "$@"
set -- "${REMAINING_ARGS[@]+"${REMAINING_ARGS[@]}"}"
case "$command" in
resize) cmd_resize "$@" ;;
crop) cmd_crop "$@" ;;
convert) cmd_convert "$@" ;;
optimize) cmd_optimize "$@" ;;
info) cmd_info "$@" ;;
watermark) cmd_watermark "$@" ;;
help) cmd_help ;;
*) die "Unknown command: $command. Run 'help' for usage." ;;
esac
}
main "$@"