Bezelbub
Bezelbub wraps your screenshots and screen recordings in realistic Apple device bezels โ iPhone, iPad, Mac, and Apple TV โ producing pixel-perfect device mockups as framed images and videos. It ships three ways:
- A macOS and iOS app: drop in a screenshot or video, Bezelbub auto-detects the matching device and composites the frame.
- A headless
bezelbub CLI (brew install cwooddgr/tap/bezelbub): frame a screenshot in an iPhone bezel from the command line, add a device frame to a screen recording, or export a transparent HEVC-with-alpha video (plus a VP9/WebM copy for Chrome/Firefox) โ designed for scripts, CI, and AI agents. Jump to the CLI docs.
- A UI-free Swift package,
BezelbubKit, for embedding the framing engine in your own tools.
Download the latest macOS release (DMG)
Features
- Auto-detection โ Matches screenshots and videos to the correct device: iPhone and iPad by exact resolution, and Macs, iMac, and Apple TV by aspect ratio so captures taken at any scaled ("More Space") resolution still match and fill the bezel. Keeps your current device and color when you load another screenshot that fits the same device
- Drag and drop (macOS) โ Drop a screenshot or screen recording onto the app window or dock icon
- Paste (macOS) โ Paste a screenshot straight from the clipboard with โV, or via Edit โธ Paste
- Photos & Share Extension (iOS) โ Import from Photos, or frame images directly from any app via the system share sheet
- Video framing โ Export MOV/MP4 screen recordings with device bezels overlaid, audio preserved
- Transparent video export โ Export a framed screen recording with a fully transparent background as HEVC-with-alpha (QuickTime
.mov), for compositing device-framed video over any web page or presentation
- Rotation โ Rotate videos to fix incorrect orientation (Option-click for counter-clockwise)
- Device colors โ Choose from all available device color options
- Export size controls โ Adjust Width, Height, or Scale (%) before saving, with per-mode size limits (images up to 16,384px, videos up to 7,680px)
- Copy & Save โ Copy framed images to clipboard, save as PNG, or export framed videos as MOV/MP4
- Landscape & Portrait โ Supports both orientations for iPhone and iPad
Supported Devices
- iPhone โ 14, 14 Plus, 14 Pro, 14 Pro Max, 15, 15 Plus, 15 Pro, 15 Pro Max, 16, 16 Plus, 16 Pro, 16 Pro Max, 17, 17 Pro, 17 Pro Max, Air
- iPad โ iPad, iPad (A16), iPad Air 11"/13" M2, iPad Air 11"/13" M4, iPad mini, iPad mini (A17 Pro), iPad Pro 11"/13" M4, iPad Pro 11"/13" M5
- Mac โ MacBook Air 13", MacBook Air 13"/15" M5, MacBook Pro 14", MacBook Pro 16", MacBook Pro 14"/16" M5, MacBook Neo, iMac 24" (matched by aspect ratio, so any scaled display resolution works)
- Apple TV โ Apple TV 4K (1080p and 4K screenshots)
Headless CLI (bezelbub)
bezelbub is a command-line tool that composites Apple device bezels onto screenshots and screen recordings โ no GUI, no interactive prompts โ so shell scripts, CI pipelines, and AI agents can generate device mockups. Every input is a flag with a sensible default, output is available as JSON, and errors go to stderr with distinct nonzero exit codes and concrete suggestions (valid ids, matching devices, nearest screen sizes), so a failed call tells the caller how to fix the next one.
Install via Homebrew:
brew install cwooddgr/tap/bezelbub
Quickstart
bezelbub frame --input shot.png
bezelbub frame --input demo.mp4
bezelbub frame --input demo.mp4 --background transparent
bezelbub frame --input demo.mp4 --background transparent --webm
bezelbub devices [--json]
bezelbub devices --input shot.png
bezelbub frame --input shot.png --device iphone17pro \
--color "Cosmic Orange" \
--orientation landscape \
--background "#1D1D1F" \
--output-size 50% \
--output framed.png --json
frame is the default subcommand, so bezelbub --input shot.png works too.
Device auto-detection
Omit --device and the CLI detects the device from the input's pixel dimensions. iPhones and iPads match by exact screen resolution (ยฑ1px); display devices (Macs, iMac, Apple TV) match by aspect ratio, so screenshots taken at any scaled resolution still work and are rescaled to the bezel's screen. Detection succeeds when exactly one device matches; if several share the resolution, the error lists the candidates so you can re-run with --device <id>, and if none match, the nearest devices by aspect ratio are suggested. bezelbub devices --input <path> (or --dimensions WxH) answers "which devices fit this input" without framing anything.
Transparent video and WebM (alpha-channel screen recordings)
--background transparent on a video input exports HEVC with an alpha channel in a QuickTime .mov instead of MP4 โ a device-framed screen recording with a fully transparent background, ready to composite over anything. HEVC-with-alpha plays in Safari and Apple frameworks (AVFoundation, AppKit/UIKit) only; Chrome and Firefox don't decode it.
For those browsers, add --webm to also write a VP9/WebM copy that keeps the alpha channel. The CLI renders a temporary ProRes 4444 master and feeds that to ffmpeg (which must be on your PATH) โ deliberately not the HEVC .mov, because ffmpeg builds older than 8.0 cannot decode HEVC's alpha layer and silently produce an opaque WebM. (ffmpeg 8+ decodes HEVC alpha correctly, but the ProRes bridge works on any build.) Serve both files and let the browser pick:
<video autoplay loop muted playsinline>
<source src="demo-framed.webm" type="video/webm" />
<source src="demo-framed.mov" type="video/quicktime" />
</video>
An explicit --output for a transparent export must end in .mov; the WebM lands beside it with a .webm extension.
Flag reference
bezelbub frame --input <path> [options]
bezelbub devices [--input <path> | --dimensions WxH] [--json]
frame options:
| Flag | Meaning |
|---|
--input, -i | Input screenshot (PNG/JPEG/HEIC) or video (.mov/.mp4/.m4v, routed by extension). Required. |
--device, -d | Device id (see bezelbub devices). Omit to auto-detect from pixel size. |
--color, -c | Color name or id, case-insensitive. Defaults to the device's default color. |
--orientation | portrait | landscape | auto (default: infer from the input's aspect). |
--background | Hex color (#RRGGBB / #RRGGBBAA) or transparent. Default: transparent for images, black for video. transparent on video switches to HEVC-with-alpha .mov. |
--output-size | Scale preserving the bezel's aspect: a width (1920), exact WxH (must match the aspect), or a percentage (50%). Limits: 16โ16,384 px images, 16โ7,680 px video. |
--output, -o | Output path. Default: <input>-framed.png / .mp4 / .mov beside the input. |
--webm | Also write a VP9/WebM copy with alpha (video + --background transparent only; needs ffmpeg on PATH). |
--json | Machine-readable JSON result on stdout instead of a text summary. |
devices lists the full catalog (ids, display names, colors, orientations, screen sizes), or filters to the devices matching an --input file or bare --dimensions. Filtering always exits 0 โ an empty matches array is the signal, with nearest (by aspect ratio) filled in when nothing matches.
JSON output
frame --json emits one object:
{
"color" : "Cosmic Orange",
"device" : "iphone17pro",
"height" : 2760,
"kind" : "image",
"orientation" : "portrait",
"output" : "/path/shot-framed.png",
"width" : 1350
}
kind is "image" or "video"; video results add "transparent": true|false and, when --webm ran, the "webm" output path. devices --json emits an array of {id, displayName, defaultColor, colors, landscapeOnly, hasPortraitBezel, screenWidth, screenHeight}; with --input/--dimensions it emits {width, height, matches, nearest} using the same device objects (nearest is filled only when matches is empty).
Exit codes
Stable and distinct, so scripts can branch on failure type instead of parsing stderr:
| Code | Meaning |
|---|
| 0 | Success |
| 1 | Invalid flag value (e.g. malformed --background or --output-size) |
| 2 | Unknown, ambiguous, or undetectable device (stderr lists candidates) |
| 3 | Unknown color (stderr lists the device's valid colors) |
| 4 | Input image or video unreadable |
| 5 | Compositing or video export failed |
| 6 | Output could not be written |
| 7 | --webm conversion failed (ffmpeg missing from PATH or errored) |
| 64 | Malformed arguments (standard EX_USAGE) |
For AI agents
The CLI is designed for non-interactive, programmatic use โ by LLM agents (Claude Code, MCP-style tool wrappers, CI bots) as much as by humans:
- Nothing ever prompts. Every input is a flag with a default; a call either completes or fails immediately.
--json on both subcommands gives machine-readable results (shapes above).
- Errors are self-correcting: stderr messages include did-you-mean device/color ids, the devices matching the input's pixel size, and nearest sizes โ enough for an agent to fix the next invocation without a human.
- Exit codes 2โ7 distinguish failure types (table above), so an agent can branch without parsing text.
- Clean pipes: video-export progress is written to stderr only when it's a TTY, so captured output stays parseable.
- A typical agent flow:
bezelbub devices --input shot.png --json to check the match, then bezelbub frame --input shot.png --json and read output from the result.
This repo also ships a ready-made Claude Code skill at skills/bezelbub-cli/ that teaches an agent the full workflow. To install it for all your projects, copy it into your user skills directory:
cp -R skills/bezelbub-cli ~/.claude/skills/
Requirements
- macOS 14.0+ / iOS 17.0+
- Xcode 16.0+
- XcodeGen
Architecture
The device-framing engine lives in BezelbubKit, a UI-free Swift package (BezelbubKit/). It does the pure transformation โ screenshot + device id + orientation โ framed image โ with no SwiftUI, app state, or GUI session, so it runs fully offscreen. Video framing lives in a sibling product, BezelbubVideoKit (AVFoundation-based), so still-image consumers like the Share Extension don't pull in the video pipeline. The macOS app, the iOS app, the Share Extension, and the bezelbub CLI are all thin clients of these packages; bezel/mask assets ship inside BezelbubKit and resolve via Bundle.module.
Building
The apps are generated with XcodeGen:
xcodegen generate
open Bezelbub.xcodeproj
Schemes:
- Bezelbub โ macOS app
- Bezelbub-iOS โ iOS app and Share Extension
The engine and CLI build with SwiftPM:
cd BezelbubKit
swift build
swift test
License
Copyright 2026 DGR Labs, LLC. All rights reserved.