Process a folder of media (photos, videos, audio) into web-optimized assets. Creates a web-ready/ subfolder with everything converted, resized, and sensibly named. Preserves descriptive filenames (slugified) and uses vision to name generic/camera files. Use when you need to prepare raw media for the web.
Process a folder of raw media into web-optimized assets in a web-ready/ subfolder.
$ARGUMENTS: optional path to source folder (defaults to current working directory)which sips # macOS built-in — required for image processing
which ffmpeg # required for video/audio — warn if missing, images still work
which magick # ImageMagick — optional, used for alpha detection in PNGs
ffmpeg is missing, warn the user: "ffmpeg not found — video and audio files will be skipped. Install with: brew install ffmpeg"$ARGUMENTS is provided and is a valid directory, use itRecursively find all files in the source directory (excluding hidden files, .DS_Store, and any existing web-ready/ folder)
Classify each file by extension into categories:
| Category | Extensions |
|---|---|
| image | .jpg, .jpeg, .png, .heic, .heif, .tiff, .tif, .webp, .bmp, .gif, .raw, .cr2, .nef, .arw, .dng |
| video | .mp4, .mov, .avi, .mkv, .webm, .m4v, .mts |
| audio | .mp3, .wav, .aac, .m4a, .flac, .ogg |
| skip | everything else |
Report counts to the user: "Found X images, Y videos, Z audio (N non-media skipped)"
If there are 5+ total media files, create a task list to track progress by phase
mkdir -p "<source>/web-ready"
web-ready/, converting directory names to kebab-case:
More Photos/ → more-photos/Raw Exports/2024/ → raw-exports/2024/For each media file, decide whether to rename or preserve the original name.
A filename is useless (i.e. not descriptive) if it matches any of these patterns:
IMG_NNNN, DSC_NNNN, DSCF_NNNN, P10NNNNN, MVI_NNNN, DSCN_NNNN, SAM_NNNN, CRW_NNNN
^(IMG|DSC|DSCF|DSCN|MVI|SAM|CRW|P\d{2,3})[-_ ]?\d{3,5}\s*$ (case-insensitive)xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ (case-insensitive)2023-07-19 16.34.24, 20240115_163424, 2024-07-10 11.50.54
^\d{4}[-_]?\d{2}[-_]?\d{2}[\s._-]?\d{2}[._:]?\d{2}[._:]?\d{2}$photo-1, image(3), image, photo
^(photo|image|pic|picture|screenshot)[-_ (]*\d*[) ]*$ (case-insensitive)11, 042, 1234
^\d{1,5}$If the filename stem (without extension) is NOT useless:
Upstream_Imports_Coffee_Rwanda-4.jpg → upstream-imports-coffee-rwanda-4.jpg9 DE JULHO (3) Graziela de Moraes and Dona Vera.jpeg → 9-de-julho-3-graziela-de-moraes-and-dona-vera.jpegEl Salvador Rosa Apaneca - 2.jpeg → el-salvador-rosa-apaneca-2.jpegIf the filename IS useless:
Upstream_Imports_Coffee_*.jpg)sips -g allcoffee-cherries-drying-on-raised-beds)image-of-, photo-of-, picture-of-[a-z0-9]+(-[a-z0-9]+)*For video files with useless names:
ffmpeg -i <input> -vframes 1 -ss 00:00:01 /tmp/thumb_<random>.jpgrm /tmp/thumb_<random>.jpg-2, -3, etc. before the extensioncoffee-farm.jpg already exists, the next one becomes coffee-farm-2.jpgmagick/identify is available: identify -format '%A' file.png — if True or Blend, keep as PNGFor each image file:
sips -Z 4000 -s format jpeg -s formatOptions 85 "<input>" --out "<output>"
sips -Z 4000 -s format jpeg -s formatOptions 75 "<input>" --out "<output>"sips -Z 4000 -s format jpeg -s formatOptions 65 "<input>" --out "<output>"sips -Z 4000 -s format jpeg -s formatOptions 50 "<input>" --out "<output>"sips -Z 4000 -s format png "<input>" --out "<output>"
sips -g format)Process images in batches — run the sips commands sequentially but report progress every ~5 files.
For each video file (skip if ffmpeg not available):
ffprobe -v quiet -print_format json -show_streams "<input>"
ffmpeg -i "<input>" -c copy -movflags +faststart "<output>.mp4"
ffmpeg -i "<input>" -c:v libx264 -preset medium -crf 23 -c:a aac -b:a 128k -movflags +faststart "<output>.mp4"
For each audio file (skip if ffmpeg not available):
cp "<input>" "<output>.mp3"
ffmpeg -i "<input>" -c:a libmp3lame -q:a 2 "<output>.mp3"
Create two manifest files in the web-ready/ directory:
manifest.csv — machine-readable:
original_path,new_filename,type,original_size_bytes,new_size_bytes
original_path: relative path from source directorynew_filename: filename in web-ready (including subdirectory if any)type: image, video, or audiooriginal_size_bytes: original file size in bytesnew_size_bytes: processed file size in bytesmanifest.md — human-readable:
# Web-Ready Media Manifest
Processed: YYYY-MM-DD
Source: <source directory name>
## Files
| Original | Web-Ready | Type | Original Size | New Size | Reduction |
|----------|-----------|------|---------------|----------|-----------|
| ... | ... | ... | ... | ... | ...% |
## Summary
- **Images**: X processed
- **Videos**: X processed
- **Audio**: X processed
- **Total original size**: X MB
- **Total new size**: X MB
- **Overall reduction**: X%
Report to the user:
<source>/web-ready/web-ready/ without confirming with the user first (if web-ready/ already exists, ask whether to overwrite or skip existing)[a-z0-9-] only