Frame device screenshots and screen recordings with Apple product bezels from the command line. Auto-detects devices, supports colors, merging, and batch processing. Based on the Apple Frames shortcut by me for MacStories.net, not affiliated with Apple.
- Python 3.8+
- Pillow (Python imaging library)
- ffmpeg 5.1+ and ffprobe 5.1+ for video framing (
frames setupchecks this and can install ffmpeg with Homebrew on macOS)
git clone https://github.com/viticci/frames-cli.git
cd frames-cli
pip3 install PillowThen symlink the script into a directory that's already in your PATH:
# Check which bin directory is in your PATH (use the first one that exists)
# Common locations: ~/.local/bin, ~/bin, /usr/local/bin
# Create the directory if needed, then symlink
mkdir -p ~/.local/bin
ln -s "$(pwd)/frames" ~/.local/bin/framesIf ~/.local/bin isn't in your PATH yet, add it to ~/.zshrc (or ~/.bashrc):
export PATH="$HOME/.local/bin:$PATH"Then restart your terminal or run source ~/.zshrc.
Verify it works:
frames --versionpip3 install Pillow
mkdir -p ~/.local/bin
curl -o ~/.local/bin/frames https://raw.githubusercontent.com/viticci/frames-cli/main/frames
chmod +x ~/.local/bin/framesIf ~/.local/bin isn't in your PATH yet, add it to ~/.zshrc (or ~/.bashrc):
export PATH="$HOME/.local/bin:$PATH"Then restart your terminal or run source ~/.zshrc.
The CLI will automatically detect and download Apple Frames 4 assets on first run. Setup also checks video requirements and, on macOS, can install ffmpeg for you with Homebrew if it is missing. You can also set up manually:
# Guided download (interactive — downloads ~40 MB from cdn.macstories.net)
frames setup
# Or point to an existing assets folder
frames setup /path/to/FramesThe guided setup downloads the asset pack, extracts it, and saves the path to ~/.config/frames/config.json. If assets get corrupted or you need a fresh copy, run frames setup again to re-download.
You can also set the FRAMES_ASSETS environment variable instead of using the config file.
# Frame a screenshot — auto-detects device, saves as name_framed.png
frames screenshot.png
# Frame all PNGs in a directory
frames *.png
# Frame with a specific color
frames -c "Cosmic Orange" screenshot.png
# Frame with random colors
frames -c random *.png
# Assign colors per input
frames --colors "Silver,Space Black,random" one.png two.png three.png
# Tip: frame a screen recording with the same auto-detected device bezel
frames video recording.mp4
# Tip: tune MP4 export size/quality; best is the default
frames video --preset compact recording.mp4
frames video --preset balanced recording.mp4
frames video --preset best recording.mp4
# Tip: inspect the video match before spending time rendering
frames --json video-info recording.mp4
# Tip: merge framed videos and play them left to right
frames video -m --playback-offset 1.mp4 2.mp4
# Frame and merge side by side
frames -m screenshot1.png screenshot2.png
# Merge in batches of 3 (15 files → 5 merged images)
frames -b 3 *.png
# Copy framed result to clipboard (macOS)
frames --copy screenshot.png
# Save to /framed/ subfolder
frames -f screenshot.png
# Save to custom subfolder
frames --subfolder mockups *.png
# Show device info without framing
frames info screenshot.png
# List all supported devices
frames listFrame one or more screenshots. The frame keyword is optional — passing files directly uses it automatically.
frames screenshot.png
frames frame screenshot.png # same thing
# With flags
frames -c "Desert Titanium" -o ~/output/ *.png
frames -m -s 80 screenshot1.png screenshot2.pngOutput naming: originalname_framed.png in the same directory as the source. Merged output is merged_framed.png.
Device detection: Automatic from screenshot pixel width. When multiple devices share a width, height disambiguates. The newest device frame is used when multiple generations share a resolution — override with --device.
Color resolution: --colors per-input value > --color flag > user default (set via colors command) > first color in device's list.
Specify a frame color by exact name, 1-based index, default, or random.
frames -c "Cosmic Orange" screenshot.png
frames -c 2 screenshot.png
frames -c random *.png
frames --colors "Silver,Space Black,random" one.png two.png three.png--color random randomizes independently per input. --colors maps comma-separated values to expanded inputs by order, and it cannot be combined with --color. Use list-colors to see what's available for a device.
Frame screen recordings or videos with the same Apple Frames assets used for screenshots.
frames video recording.mp4
frames video -c Silver recording.mp4
frames video --colors "Silver,random" 1.mp4 2.mp4
frames video --strip-audio recording.mp4
frames video --preset compact recording.mp4
frames video --preset balanced recording.mp4
frames video --preset best recording.mp4
frames video -m --alpha 1.mp4 2.mp4
frames video -m --background transparent 1.mp4 2.mp4Video support requires ffmpeg 5.1+ and ffprobe 5.1+. frames setup checks for both and can install ffmpeg with Homebrew on macOS. Supported input extensions are .mp4, .mov, and .m4v.
Single-video output is originalname_framed.mp4 by default, or .mov for --alpha, --codec prores, or --background transparent. Audio is preserved unless --strip-audio is passed.
Use --alpha or --background transparent to create transparent ProRes 4444 .mov output. This works for single videos and merged videos. --alpha defaults the canvas to transparent unless you explicitly pass another --background; MP4/H.264 and MP4/HEVC outputs do not support alpha. If you pass an explicit output file for transparent output, it must use a .mov extension.
Interactive video renders show a live progress bar. JSON and non-interactive runs stay quiet for scripting. Completed video exports report output size and source-vs-output savings in both human output and JSON.
Video presets tune MP4 export size and quality. best is the default. balanced and compact lower H.264/HEVC bitrate for hardware encoding and use higher CRF values for software encoding. --quality N remains an expert CRF override for software encoders only; lower is higher quality.
Common video recipes:
| Goal | Command |
|---|---|
| Check the match before rendering | frames --json video-info recording.mp4 |
| Frame one video | frames video recording.mp4 |
| Use compact MP4 export | frames video --preset compact recording.mp4 |
| Use balanced MP4 export | frames video --preset balanced recording.mp4 |
| Use best MP4 export | frames video --preset best recording.mp4 |
| Frame silently | frames video --strip-audio recording.mp4 |
| Merge videos simultaneously | frames video -m 1.mp4 2.mp4 |
| Play merged videos left to right | frames video -m --playback-offset 1.mp4 2.mp4 |
| Assign per-input colors | frames video --colors "Silver,random" 1.mp4 2.mp4 |
| Transparent ProRes MOV | frames video --alpha recording.mp4 |
| Transparent merged ProRes MOV | frames video -m --alpha 1.mp4 2.mp4 |
| Transparent merged canvas | frames video -m --background transparent 1.mp4 2.mp4 |
# Transparent ProRes MOV
frames video --alpha recording.mp4
# Transparent merged ProRes MOV
frames video -m --alpha 1.mp4 2.mp4
frames video -m --background transparent 1.mp4 2.mp4
# HEVC output
frames video --codec hevc recording.mp4
# Compact HEVC output
frames video --codec hevc --preset compact recording.mp4
# Custom background
frames video --background "#f5f5f5" recording.mp4Merge multiple framed videos into a horizontal canvas. By default, videos play simultaneously and the output duration is the longest input.
frames video -m 1.mp4 2.mp4
frames video -m --no-scale 1.mp4 2.mp4
frames video -m --alpha 1.mp4 2.mp4
frames video -m --background transparent 1.mp4 2.mp4When merging different devices, videos are proportionally scaled using the same physical-height model as image merges and bottom-aligned.
Use --playback-offset to play videos one at a time from left to right. Inactive videos hold on their first frame before playback and their final frame after playback.
frames video -m --playback-offset 1.mp4 2.mp4With --playback-offset, audio is concatenated sequentially and videos without audio contribute silence. Simultaneous video merges omit mixed audio in this version.
Transparent merges output .mov using ProRes 4444 with yuva444p10le pixels. Use this when you want the merged devices floating over transparency for Final Cut Pro, Keynote, or another compositor.
Probe videos and report matching Apple frame metadata without rendering.
frames video-info recording.mp4
frames --json video-info recording.mp4
frames --json video-info --colors "Silver,random" 1.mp4 2.mp4video-info uses the same device, variant, color, ffmpeg, and ffprobe checks as frames video. It reports dimensions, duration, fps, codec, audio state, matched device, selected color, frame size, mask state, and resize metadata.
frames video --preset compact|balanced|best controls MP4 export size/quality. best is the default. Presets affect H.264 and HEVC bitrate for hardware encoders and CRF for software encoders; ProRes/alpha output keeps ProRes settings. --quality N is still available as a software CRF override.
frames video --alpha ... and frames video --background transparent ... return transparent ProRes .mov output. In JSON, alpha is true and background is transparent unless an explicit opaque background is provided.
frames --json video ... returns the selected preset plus output_size_bytes, output_size, source_size_bytes, source_size, savings_bytes, savings, and savings_percent after export. Merged JSON output includes the same top-level size and savings fields.
Merge all framed images into a single horizontal strip. Default spacing between frames is 60px.
When merging different devices, frames are automatically scaled to reflect real-world physical proportions and bottom-aligned. An iPhone next to an iPad will be proportionally shorter, just like in real life. Same-device merges are unaffected.
frames -m screenshot1.png screenshot2.png screenshot3.png
frames -m -s 120 screenshot1.png screenshot2.pngThe merged output is saved as merged_framed.png in the output directory.
Disable proportional scaling when merging. All frames render at native pixel size and are center-aligned (pre-v1.2 behavior).
frames -m --no-scale iphone.png ipad.pngMerge screenshots in sequential batches of N. Produces multiple merged images instead of one.
# 15 screenshots → 5 merged images of 3
frames -b 3 *.png
# Batch merge with custom spacing and output directory
frames -b 4 -s 80 -o /output/ *.png
# Batch merge with random colors
frames -b 3 -c random *.pngBatch size must be at least 2. If the total isn't evenly divisible, the last batch contains the remainder. Output files are named merged_1_framed.png, merged_2_framed.png, etc.
--batch implies --merge — no need to pass both. JSON output includes a batches array with per-batch counts and paths.
Save framed images to a subfolder relative to the source file's directory, instead of next to the originals. Two modes:
-f(shorthand) saves to a/framed/subfolder--subfolder NAMEsaves to a custom-named subfolder
frames -f screenshot.png
# saves to ./framed/screenshot_framed.png
frames -f *.png
# saves all to ./framed/
frames --subfolder mockups *.png
# saves all to ./mockups/To make subfolder mode the default, run frames setup --subfolder. To revert, run frames setup --no-subfolder.
Save framed images to a specific output directory.
frames -o ~/Desktop/framed/ screenshot.png
frames -o /tmp/output/ *.pngCopy the framed image directly to the macOS clipboard. Works with a single image only. The success/failure message prints to stderr, so it won't corrupt --json output.
frames --copy screenshot.png
frames --json --copy screenshot.png # valid JSON on stdoutForce a specific device frame instead of auto-detecting from the screenshot dimensions. Skips automatic variant resolution, so the exact device you specify is used. Useful when multiple devices share a resolution.
frames -d "iPhone 17 Pro Portrait" screenshot.png
frames -d "MacBook Pro M5 14" screenshot.pngUse frames list to see exact device names.
Output machine-readable JSON instead of human-readable text. Designed for AI agent pipelines and scripting.
frames --json screenshot.png
# → {"source": "screenshot.png", "device": "iPhone 17 Pro Portrait", "color": "Cosmic Orange", "output": "/path/to/screenshot_framed.png", ...}
frames --json -m screenshot1.png screenshot2.png
# → {"merged": "/path/to/merged_framed.png", "count": 2, "frames": [...]}
frames --json info screenshot.png
# → {"file": "screenshot.png", "device": "iPhone 17 Pro Portrait", "width": 1290, "height": 2796, ...}List all supported devices grouped by category. Shows pixel dimensions and available color counts.
frames listInteractive TUI color picker (curses). Navigate with arrow keys, select with space, confirm with enter. Sets per-device default colors stored in ~/.config/frames/config.json.
frames colorsShow all available colors for a specific device. Supports partial name matching.
frames list-colors "17 Pro"
frames list-colors "MacBook"
frames list-colors "Watch Ultra"Detect the device for a screenshot without framing it. Shows device name, pixel dimensions, available colors, mask and resize info.
frames info screenshot.png
frames --json info screenshot.pngDownload assets or configure the assets folder path. Without arguments, starts an interactive download from cdn.macstories.net (~40 MB). With a path, points the CLI at an existing assets folder.
# Download assets interactively (first-time setup or re-download)
frames setup
# Point to an existing assets folder
frames setup /path/to/Frames
# With subfolder mode
frames setup --subfolder /path/to/Frames # enable subfolder mode by default
frames setup --no-subfolder /path/to/Frames # disable subfolder mode (default)If assets are missing or outdated when you run any command, the CLI will automatically offer to download them.
The setup and colors commands write to ~/.config/frames/config.json:
{
"assets_path": "/path/to/Frames",
"use_subfolder": false,
"default_colors": {
"iPhone 17 Pro": "Deep Blue",
"MacBook Pro M5 14": "Space Black"
}
}Assets priority order: --assets flag > FRAMES_ASSETS env var > config file > default iCloud Shortcuts path.
Color priority order: --color flag > config default (set via colors command) > first color in device list.
The FRAMES_ASSETS environment variable takes precedence over the config file and is useful for CI or non-standard setups:
FRAMES_ASSETS=/path/to/assets frames screenshot.pngThe --json flag makes frames pipeline-friendly. All output goes to stdout; errors go to stderr.
# Frame a screenshot, capture the output path
OUTPUT=$(frames --json screenshot.png | python3 -c "import sys,json; print(json.load(sys.stdin)['output'])")
# Get device info as JSON
frames --json info screenshot.png
# Frame and merge, get merged path
frames --json -m *.pngBatch processing patterns:
# Frame everything into a separate directory
frames -o ~/framed/ ~/screenshots/*.png
# Frame and merge all into one image
frames -m -o ~/framed/ ~/screenshots/*.png
# Random colors for visual variety
frames -c random -o ~/framed/ ~/screenshots/*.png
# Subfolder mode — outputs land in ./framed/ next to sources
frames -f ~/screenshots/*.pngClaude Code skill: A skill file is included in skill/SKILL.md. Install it to ~/.claude/skills/frames-cli/SKILL.md to give Claude Code native awareness of the CLI, its flags, and batch patterns.
| Category | Devices | Notes |
|---|---|---|
| iPhone 17 | 17, 17 Pro, 17 Pro Max | Portrait + landscape |
| iPhone Air | Air | Portrait + landscape |
| iPhone 16 | 16, 16 Plus, 16 Pro, 16 Pro Max | Portrait + landscape |
| iPhone 12-13 | 12/13 mini, 12/13, 12/13 Pro, 12/13 Pro Max | Portrait + landscape |
| iPhone 8 / SE | iPhone 8, SE | Portrait |
| iPad | Pro 11" / 13" (2018-2024), Air, mini | Portrait + landscape |
| MacBook | Neo, Air M5 13"/15", Pro M5 14"/16", Pro 2021, Air 2020-2022, Pro 13 | Front-facing |
| iMac | iMac M4 | 7 colors (Silver default) |
| Studio Display | Studio Display, Studio Display XDR | 2 colors each (Light default); XDR is a variant |
| Apple Watch | Ultra 3, Ultra 2024, Series 11, Series 10, Series 7 | Including band combinations |
Watch Ultra 3 supports 13 case + band combinations. Watch Series 11 supports 22 case + band combinations per size. All devices that have landscape variants support both orientations.
by Federico Viticci, MacStories.net