Command line (picmal-cli)
Picmal ships with a command-line companion, picmal-cli, bundled inside the app. Same conversion engine as the UI, so the output is identical — you just get flags, JSON, and exit codes you can drop into a script.
Installation
Section titled “Installation”From the menu bar, choose Picmal → Install Command Line Tool…. Picmal asks for your administrator password and creates a symlink at /usr/local/bin/picmal-cli, which is on the default macOS PATH for both bash and zsh. After that, picmal-cli works from any terminal.

To remove it later, use Picmal → Uninstall Command Line Tool…. Only the symlink is removed; the app stays put.
If you’d rather invoke the binary directly without installing it, it lives inside the app bundle:
/Applications/Picmal.app/Contents/MacOS/picmal-cliconvert, compress, combine, combine-videos, split-pdf, images-to-pdf, merge-audio, split-chapters, remove-background, and watch need a valid Picmal license. The read-only commands (presets, formats, license-status, version) work without one.
Synopsis
Section titled “Synopsis”picmal-cli <subcommand> [options]| Subcommand | What it does |
|---|---|
convert | Convert files to a target format |
compress | Compress files keeping the same format |
combine | Combine two or more PDFs into one |
split-pdf | Split a PDF into one document per page range |
images-to-pdf | Create a multi-page PDF from images |
merge-audio | Merge two or more audio files into one |
combine-videos | Combine two or more videos into one |
split-chapters | Split a chaptered audiobook into per-chapter files |
remove-background | Cut the background out of images (transparent PNG) |
presets | List or inspect saved presets (built-in + custom) |
formats | List supported formats by category |
watch | Add, list, and remove watched folders |
license-status | Check whether the host app is licensed |
version | Print the bundled app version |
Global options
Section titled “Global options”These flags work on every subcommand:
| Flag | Description |
|---|---|
--json | Force NDJSON output to stdout (one JSON object per line). Auto-on when piped. |
--text | Force human-readable output even when piped. |
-q, --quiet | Suppress per-file status lines (final summary still emitted). Where available. |
--verbose | Pass underlying tool stderr through. Useful for debugging. |
convert
Section titled “convert”Convert one or more files to a target format.
picmal-cli convert -i photo.heic -f jpgpicmal-cli convert -i ~/Pics -f webp -o ~/out --quality 80picmal-cli convert -i a.png b.png -f avif --strip-metadatapicmal-cli convert -i clip.mov -f mp4 --quality 70picmal-cli convert -i photo.jpg -f jpg --resize 100kBpicmal-cli convert -i photo.jpg -f jpg --copyright "© 2026 Jane Doe" --artist "Jane Doe"picmal-cli convert -i icon.icns -f png -o ~/outcat list.txt | picmal-cli convert -i - -f webp -o ~/out| Flag | Description |
|---|---|
-i, --input <paths> | One or more input paths. Use - to read newline-separated paths from stdin. |
-f, --format <ext> | Target format extension (e.g. jpg, webp, mp4, mp3). |
-o, --output <path> | Output file or directory. Defaults to alongside each input. |
-w, --overwrite | Overwrite existing outputs. |
--quality <n> | Quality 0–100. Format-specific defaults if omitted. Overrides --preset. |
--preset <name> | Apply a saved preset by name (see Presets). |
--resize <value> | Resize geometry (1920x1080, 1920x, x1080, 50%) or a target file size (100kB, 2MB) — images are scaled down to fit the budget. |
--color-space <name> | One of srgb, p3, adobergb, gray, cmyk. |
--metadata <mode> | Preset: stripall, essential, or keepall. See Metadata. |
--keep <categories> | Comma-separated categories to keep; everything else is stripped. |
--strip <categories> | Comma-separated categories to strip; everything else is kept. |
-s, --strip-metadata | Strip EXIF/IPTC/XMP from output (alias for --metadata stripall). |
--copyright <text> | Copyright notice to embed (EXIF, IPTC, and XMP). Written even with --strip-metadata. |
--artist <text> | Artist / creator name to embed (EXIF Artist, IPTC byline, XMP creator). |
--credit <text> | Credit line to embed (IPTC credit, XMP credit). |
--contact <text> | Contact or URL to embed (XMP rights web statement, IPTC contact). |
--pdf-dpi <n> | DPI for PDF rasterization (also tags output density on raster outputs). |
--watermark <path> | Watermark image path. |
--watermark-opacity <n> | 0.0–1.0. Default 0.5. |
--watermark-position | tl, tr, bl, br, or center. Default br. |
The copyright flags are independent of --strip-metadata: pass both to strip a photo’s camera/GPS data while still stamping your own ownership tags onto the output. Empty flags are skipped; values you pass always overwrite any existing tag.
An .icns input fans out to one output per embedded size, named <name>-<W>x<H>.<ext> — the same convention split-pdf uses for pages. --quality, --resize, and --watermark apply to each size. See Apple icon files.
Metadata
Section titled “Metadata”The metadata flags mirror the app’s Metadata settings. Use a preset, or take per-category control with --keep / --strip:
picmal-cli convert -i photo.jpg -f jpg --metadata essentialpicmal-cli convert -i photo.jpg -f jpg --keep copyright,colorProfilepicmal-cli convert -i photo.jpg -f jpg --strip location,identifiersCategories: location, camera, dateTime, copyright, colorProfile, orientation, identifiers, thumbnail. Friendly aliases work too — gps, icc, date, serial, lens, thumb. --keep lists what survives; --strip lists what comes off and keeps the rest. Precedence is --keep > --strip > --metadata > --strip-metadata.
These flags work the same on compress and watch add.
compress
Section titled “compress”Compress one or more files keeping the same format. Output defaults to <basename>_compressed.<ext> next to each input.
picmal-cli compress -i big.jpg --quality 70picmal-cli compress -i ~/Pics -o ~/out --strip-metadatapicmal-cli compress -i clip.mp4 --quality 60 -o clip-small.mp4picmal-cli compress -i portfolio.jpg --copyright "© 2026 My Studio" --contact "hello@example.com"picmal-cli compress -i podcast.mp3 --preset "Voice memo"| Flag | Description |
|---|---|
-i, --input <paths> | One or more input paths. Use - for stdin. |
-o, --output <path> | Output file or directory. |
--quality <n> | Quality 0–100. Format-specific defaults if omitted. Overrides --preset. |
--preset <name> | Apply a saved preset by name (see Presets). |
-s, --strip-metadata | Strip EXIF/IPTC/XMP from output. |
--copyright <text> | Copyright notice to embed (EXIF, IPTC, and XMP). Written even with --strip-metadata. |
--artist <text> | Artist / creator name to embed. |
--credit <text> | Credit line to embed. |
--contact <text> | Contact or URL to embed. |
-w, --overwrite | Overwrite existing outputs. |
If your --preset is an audio or video preset and you don’t pass --output, picmal-cli picks the right container for you (Opus → .opus, AAC → .m4a, FLAC → .flac, VP9 → .webm). Pass --output if you want a specific path.
combine
Section titled “combine”Combine two or more PDFs into a single document. Inputs are merged in the order you pass them.
picmal-cli combine a.pdf b.pdfpicmal-cli combine chapter*.pdf -o book.pdf| Flag | Description |
|---|---|
<inputs...> | PDF files to combine, in page order (positional, at least two). |
-o, --output <path> | Output file or directory. Default: <first basename> (combined).pdf next to the first input. |
-w, --overwrite | Overwrite an existing output file. |
Password-protected inputs are refused (exit 11) — unlock them first. Uses PDFKit, macOS’s native PDF engine, so pages are copied losslessly.
split-pdf
Section titled “split-pdf”Split a PDF into one document per page range. Each comma-separated range in --pages becomes its own file; with no --pages, every page is split into a separate file.
picmal-cli split-pdf book.pdf --pages 1-3,5,8-picmal-cli split-pdf book.pdfpicmal-cli split-pdf *.pdf --pages 1-2 -o out/| Flag | Description |
|---|---|
<inputs...> | PDF files to split (positional, at least one). Each is split by the same ranges. |
--pages <ranges> | Page ranges, 1-based: N, N-M, N- (N to end), -M (start to M), comma-separated. Default: one file per page. |
-o, --output <dir> | Output directory. Default: next to each input, as <basename>-1.pdf, <basename>-2.pdf, … |
-w, --overwrite | Overwrite existing output files. |
Like combine, it uses PDFKit and copies pages losslessly; password-protected inputs are refused — unlock them first.
images-to-pdf
Section titled “images-to-pdf”Create a multi-page PDF from images, one image per page, in the order you pass them. Accepts the formats macOS decodes natively (jpg, png, heic, tiff, gif, bmp, webp, avif, jp2, jxl). EXIF orientation is applied, so sideways phone photos come out upright.
picmal-cli images-to-pdf scan1.jpg scan2.jpgpicmal-cli images-to-pdf *.png --page-size a4 --quality 70 -o album.pdfpicmal-cli images-to-pdf report.png --password secret| Flag | Description |
|---|---|
<inputs...> | Image files, in page order (positional, at least one). |
--page-size <size> | fit (page wraps each image, default), a4, letter, or <W>x<H>mm (e.g. 210x297mm). Fixed sizes auto-rotate to each image’s orientation. |
--quality <n> | JPEG quality for embedded images, 40–100. Default 85. |
--password <text> | Password required to open the PDF. |
-o, --output <path> | Output file or directory. Default: <first basename>.pdf next to the first input. |
-w, --overwrite | Overwrite an existing output file. |
app-icon
Section titled “app-icon”Turn a single image into ready-to-ship app icons: a macOS .icns, a Windows .ico, and an iOS .appiconset you can drag into Xcode. Pass no format flag to generate all three. A non-square source is fit into the square without cropping, and the iOS icon is flattened onto an opaque background.
picmal-cli app-icon logo.png # all three formatspicmal-cli app-icon logo.png --macos # just .icnspicmal-cli app-icon logo.png --ios --windows -o icons/| Flag | Description |
|---|---|
<input> | Source image to generate icons from (positional). Use a square image ≥ 1024×1024 for crisp results. |
--macos | Generate the macOS .icns. |
--windows | Generate the Windows .ico. |
--ios | Generate the iOS .appiconset. |
-o, --output <path> | Output directory. Default: a <name> App Icons folder next to the source. |
-w, --overwrite | Overwrite existing output files. |
Pass any combination of --macos, --windows, and --ios to pick formats; with none, all three are generated. See App Icons.
remove-background
Section titled “remove-background”Cut the background out of one or more images and write each subject to a transparent PNG. It runs entirely on your Mac using Apple’s Vision framework — nothing is uploaded.
picmal-cli remove-background photo.jpgpicmal-cli remove-background *.png -o cutouts/| Flag | Description |
|---|---|
<inputs...> | Source image(s) to remove the background from (positional). |
-o, --output <path> | Output directory. Default: <name>_removed.png next to each source. |
-w, --overwrite | Overwrite existing output files. |
--json | Force JSON output. |
An image with no clear subject is skipped and reported, and the rest of the batch still runs. See Remove Background.
merge-audio
Section titled “merge-audio”Join two or more audio files into one, in the order you pass them. When every input shares the same codec, sample rate, and channels the streams are copied losslessly; mix formats and they’re re-encoded to the output format instead.
picmal-cli merge-audio part1.mp3 part2.mp3picmal-cli merge-audio intro.wav track.wav outro.wav -o full.wav| Flag | Description |
|---|---|
<inputs...> | Audio files to merge, in order (positional, at least two). |
-o, --output <path> | Output file or directory. The extension sets the format. Default: <first basename> (merged).<ext> next to the first input, in that file’s format. |
-w, --overwrite | Overwrite an existing output file. |
Uses the same bundled ffmpeg as Convert and Compress. Decode-only formats (like APE) are refused as output targets — pick a format Picmal can write.
combine-videos
Section titled “combine-videos”Join two or more videos into one, in the order you pass them. When every input shares the same codec, resolution, and frame rate the streams are copied losslessly; mix formats or sizes and each clip is scaled onto the first one’s frame and re-encoded to match.
picmal-cli combine-videos clip1.mp4 clip2.mp4picmal-cli combine-videos intro.mov main.mp4 outro.webm -o full.mp4| Flag | Description |
|---|---|
<inputs...> | Video files to combine, in order (positional, at least two). |
-o, --output <path> | Output file or directory. The extension sets the format. Default: <first basename> (combined).<ext> next to the first input, in that file’s format. |
-w, --overwrite | Overwrite an existing output file. |
Uses the same bundled ffmpeg as Convert and Compress. H.264 for MP4/MOV/MKV, VP9 for WebM; on Apple Silicon it encodes in hardware. Decode-only formats are refused as output targets — pick a format Picmal can write.
split-chapters
Section titled “split-chapters”Split a chaptered audiobook into one file per chapter. Picmal reads the chapter markers in an M4B/M4A audiobook (or any audio file that carries them) and writes one file per chapter, tagged with its title and track number. Pass several files to split them all.
picmal-cli split-chapters book.m4b # one m4b per chapterpicmal-cli split-chapters book.m4b --format mp3 # convert to mp3picmal-cli split-chapters *.m4b --format mp3 -o out/| Flag | Description |
|---|---|
<inputs...> | Audiobook file(s) to split (positional). |
--format <fmt> | Output format, e.g. mp3. Default: keep the input’s format. |
-o, --output <dir> | Output directory. Default: next to each input. |
-w, --overwrite | Overwrite existing output files. |
Outputs are named <book> - 01 - <chapter title>.<ext>. Files with no chapter markers are reported and skipped.
presets
Section titled “presets”Browse the presets you’ve made in the app, plus the built-ins that ship with Picmal. Read-only — make new ones in the app.
picmal-cli presets listpicmal-cli presets list --kind imagepicmal-cli presets show "Balanced"| Flag | Description |
|---|---|
--kind <image|audio|video> | Only show presets of this kind. |
Text mode groups results under Built-in and Custom. JSON mode emits a presets array where each row carries id, name, kind, source ("built-in" or "custom"), and description.
picmal-cli presets show "Maximum quality"Takes a preset name (case-insensitive) and prints everything it contains:
- Image presets: per-format quality (
jpegQuality,webpQuality,pngLevel,heicQuality, …) plus any resize / color / DPI sections. - Audio presets: codec, bitrate mode, target bitrate, sample rate, bit depth, mono flag.
- Video presets: codec, CRF, encoding speed.
If a name exists across kinds (the built-in “Maximum quality” exists for image, audio, and video), show prints every match. Custom presets win over built-ins of the same name within the same kind.
Using presets in compress / convert
Section titled “Using presets in compress / convert”Pass --preset NAME to either subcommand. The kind is matched against the input’s category (image / audio / video), and the preset’s values feed into the engine:
- Image preset → per-output-format quality, resize geometry, target color space, DPI.
- Audio preset → ffmpeg codec, bitrate, mono flag, sample rate, bit depth.
- Video preset → ffmpeg codec, CRF, encoding speed. Input audio is stream-copied.
Explicit flags always beat preset values. So --preset "Maximum compression" --quality 95 gives you a high-quality output, not a tiny one.
You can’t create custom presets from the CLI. Make them in the app first (see image compression and audio and video).
formats
Section titled “formats”List the formats Picmal can read and write, grouped by category.
picmal-cli formatspicmal-cli formats --category imagepicmal-cli formats --json | jq '.formats.image'| Flag | Description |
|---|---|
--category <image|audio|video|document> | Restrict output to one category. |
Manage Picmal’s watched folders from the terminal. The host app does the actual processing; picmal-cli watch only edits the list.
picmal-cli watch add ~/Screenshots --format webp --strip-metadatapicmal-cli watch listpicmal-cli watch remove ~/Screenshots --yeswatch add
Section titled “watch add”| Flag | Description |
|---|---|
<folder> | Folder path to watch (positional). |
-o, --output <dir> | Output destination. Defaults to the watched folder. |
-f, --format <ext> | Target format extension. Implies convert mode. |
-r, --recurse | Watch subfolders recursively. |
-s, --strip-metadata | Strip metadata from processed files. |
watch list
Section titled “watch list”Lists every configured watched folder, with mode, format, output, and UUID.
watch remove
Section titled “watch remove”picmal-cli watch remove <uuid-or-path> [--yes]Pass either the folder path or the UUID printed by watch list. --yes (-y) skips the confirmation prompt, which is required when stdout isn’t a terminal.
license-status
Section titled “license-status”Prints whether the host app is licensed.
picmal-cli license-statuspicmal-cli license-status --jsonversion
Section titled “version”Prints the bundled app version.
picmal-cli versionExit codes
Section titled “Exit codes”picmal-cli uses stable exit codes so scripts can branch on them:
| Code | Meaning |
|---|---|
0 | Success. |
1 | Generic failure (e.g. underlying tool reported a non-zero exit). |
2 | Usage error — bad arguments or missing required flag. |
3 | Unlicensed. |
4 | Input missing. |
5 | Unsupported format. |
7 | Cancelled (Ctrl+C). |
8 | Output write failure (output already exists without -w, disk full, …). |
9 | Partial success: some files succeeded, some failed in a batch. |
10 | A bundled tool (magick, ffmpeg) couldn’t be located. Reinstall fixes it. |
11 | Input is corrupt or cannot be decoded. |
JSON output
Section titled “JSON output”When stdout isn’t a terminal (piped or redirected), picmal-cli emits NDJSON: one JSON object per line. Force it with --json, force text with --text.
Every event carries an event discriminator and an RFC 3339 ts. Successful file outputs go to stdout; errors, warnings, and progress go to stderr. The schema version is 1 and is included in the started event so consumers can detect breakages early.
Example pipeline:
picmal-cli compress -i ~/Pics -o ~/out --preset Balanced --json \ | jq -c 'select(.event=="file") | {input, output, savedPct: (1 - .bytesOut/.bytesIn)*100}'