CLI & REST API Reference
Overview
The pme CLI lets you transcode, probe media files, manage presets, and run a headless encoding server, all from the terminal. It ships inside the application bundle.
The same binary is also an MCP server for AI assistants (pme mcp). Setup, tools and example prompts are in the MCP server guide.
Installation
Install the CLI from within the app: Parallel Media Encoder → Install CLI Tool… in the menu bar. This creates the symlink automatically. See the Getting Started guide for details.
Or create the symlink manually:
ln -s "/Applications/Parallel Media Encoder.app/Contents/MacOS/pme" /usr/local/bin/pme License requirement
The transcode and watch commands require an active Pro license. The probe, inspect and service commands work on the free tier; the service applies the same per-job free-tier rules as the app (standard codecs, no RAW, LUT, overlays, DNx or custom presets) and returns 403 for a Pro feature on the free tier. The free tier is licensed for personal, educational, and evaluation use; paid work needs a Pro license per Mac.
Accepting the License Terms
Before pme does any work on a Mac, the License Terms must have been accepted there, either by choosing Agree in the app or with pme terms accept. Until then every working command prints a notice and exits with code 6 and the error terms_not_accepted; --help, pme terms, pme license status, pme service status and the pme mcp install commands still run. A background service or MCP server never accepts on your behalf.
Quick check
pme --version pme --help pme transcode
Transcode one or more media files. Requires a Pro license.
Multiple inputs encode in parallel using the same scheduler as the app, up to the hardware session limit (or --concurrency), with RAW formats held to their per-format isolation caps. Invalid flag values are rejected up front with exit code 2; they are never silently ignored.
Flags
| Flag | Description |
|---|---|
inputs (positional) | Input file path(s) |
-o, --output | Output file or directory. An image sequence is a folder of frames: a directory gets <dir>/<source name>/, and a name without a file extension (out/shot010) is the sequence folder itself. A file name such as clip.dpx is refused |
--container | Output container: mov, mp4 (H.264/HEVC) or mxf (DNx). Applies to every input, so a folder of camera MP4s can come out as MOV in one command. Default: H.264/HEVC keep the source's container when it is MOV or MP4 (otherwise MOV); ProRes is MOV; DNx is MXF, or MOV when --output names a .mov file or --container mov is given. A codec that can't be written to the container, or an --output file with another extension, is rejected before anything is encoded |
--codec | Output codec (see Available Codecs) |
--image-bit-depth | Image sequence sample depth (--codec dpx, tiff, jpeg or exr): DPX 8, 10, 12 or 16 (default 10); TIFF 8, 16 or float (default 16); JPEG 8; OpenEXR half or float (default half). A depth the format can't write is refused with the ones it can. A depth above the source's is written as asked, and the job notes that it carries no extra precision |
--image-compression | TIFF deflate (default), lzw or none; OpenEXR zip (default) or none. DPX is uncompressed and JPEG uses --jpeg-quality |
--jpeg-quality | JPEG sequence quality, 1–100. Default: 90 |
--frame-numbering | Where a sequence's frame numbers start: one (default: 1, 2, 3…), zero, source_timecode (each frame is numbered by its source timecode as a frame count, so the number is the timecode, the convention VFX pulls expect) or custom (from --custom-start-frame) |
--custom-start-frame | First frame number for --frame-numbering custom (0 or more). On its own it implies custom numbering, e.g. --custom-start-frame 1001 |
--frame-padding | Digits in each frame number, 4–8. Default: 7 (clip.0000001.dpx) |
--quality | Quality 0.0 – 1.0. Encodes with the encoder’s quality setting instead of a bitrate target (automatic mode) |
--bitrate | Target bitrate in Kbps (manual mode) |
--bitrate-scaling | on: the bitrate is a ceiling set for a 1920×1080 picture. A smaller output gets less, (pixels ÷ 1920×1080)0.75 of it, and never more than 1.5× the source's own video bitrate, so a small proxy isn't bigger than its original; it is never raised above the bitrate. off: the bitrate exactly. Default: the preset's (on in LucidLink (Proxy), off everywhere else). With --bitrate and scaling on, the value you pass is the ceiling. The job reports bitrate_requested and bitrate_effective, and the CLI prints the scaled figure |
--rate-control | auto (quality-driven), vbr (average bitrate, 1-pass), vbr-constrained (true VBR with a hard peak and VBV buffer, on macOS 26 or later; older systems encode as vbr and print a warning), or vbr-2pass (H.264 only). Default: vbr when --bitrate is given, otherwise auto |
--vbv-max | Peak bitrate in Kbps for vbr-constrained. Default: 1.5× --bitrate |
--vbv-buffer | VBV buffer duration in seconds (0.5–10) for vbr-constrained. Default: 2.5 |
--encoder-preset | Apple encoder tuning for H.264/HEVC: default (PME tuning), quality, balanced, or speed. macOS 26 or later; older systems use default and print a warning |
--frame-rate | Output frame rate, e.g. 23.976, 25, 59.94. Omit to keep the source rate |
--frame-timing | Frame timing when --frame-rate is omitted. auto (default): a constant-frame-rate source is written on its exact cadence (camera micro-jitter cleaned up), and a variable-frame-rate source (iPhone, screen recording, livestream) keeps its own frame timing, so picture and sound stay in sync. resample: constant frame rate by real time, with frames repeated or dropped where the source's timing varies, the length kept. renumber: constant frame rate, every frame restamped one frame apart (how presets saved before frame timing behave); a variable source's length changes and the picture drifts against the sound, and the job warns. source: decoded timestamps passed through unchanged. A source is variable when a constant grid would put some frame half a frame or more from its real time; pme probe reports it. DNx and GIF can't carry variable timing, so auto resamples there: DNx at a constant rate its muxer holds exactly (24, 29.97, 29…), a GIF taking each frame from the source frame on screen at that moment (renumber and source keep the old behaviour). MKV/WebM sources have no sample table: unless the setting is source, their packets' timestamps are scanned before encoding (no decode, about 1 s for 6.4 GB on an internal SSD; files over 16 GiB behave as before, logged as timing unknown), so pme probe doesn't report them. --segment-parallel splits a variable source at its own frame times (or on the resample grid) and stitches it back without changing its timing |
--frc | New frames when --frame-rate is higher than the source: duplicate (repeat the nearest frame; default) or interpolate (VideoToolbox ML frame-rate conversion, on macOS 15.4 or later; older systems duplicate and print a warning) |
--upscale | Scaler when the output is larger than the source: standard (default) or super-resolution (VideoToolbox ML upscale, on macOS 26 or later, whole-number factors the Mac supports, sources up to 1920×1080; otherwise standard with a warning) |
--denoise | ML temporal denoise strength 0.0–1.0 before encoding (VideoToolbox temporal noise filter, on macOS 26 or later; older systems skip it with a warning) |
--preset | Apply a saved preset by name |
--resolution | Output resolution, e.g. 1920x1080, or source. H.264 and HEVC write even sizes (HEVC main422-10 an even width): an odd dimension rounds down, with a warning (1921×1081 is written as 1920×1080). ProRes, DNxHR and GIF keep odd sizes. |
--resize-mode | Resize mode when aspect ratio differs: stretch, fit, or fill (default: stretch) |
--rotate | Rotate video: cw90, 180, or ccw90 (alias cw270) |
--audio-codec | aac, alac, or pcm. CLI has no separate track selector flag. AAC carries mono, stereo, 3.0, quad, 5.0, 5.1, 6.1 and 7.1; channels without speaker labels (spatial/ambisonic such as Insta360, discrete, or unlabelled camera audio) are kept in order under speaker labels, or mixed to stereo when AAC can't carry them exactly (6–8 unlabelled channels). PME prints a warning whenever it relabels or folds channels; pcm keeps every channel as recorded. Bit rates outside AAC's range for the channel count are clamped (e.g. 224 kbps minimum for 8 channels). |
--audio-channels | source, mono, or stereo. Mono/stereo downmix multichannel audio (e.g. 5.1 → stereo). Channels without speaker labels follow a fixed rule unless --audio-map picks others: discrete or unlabelled channels take channel 1 left and channel 2 right (mono averages them); separate mono tracks (Sony/ARRI MXF, Canon CRM, multi-mono MOV/MKV) likewise take tracks 1 and 2; an Insta360 track mixes channels 1 and 4 to both sides; other first-order ambisonics decode to left = W + Y, right = W − Y (mono = W). Default: source |
--audio-map | Which source channels make the mono/stereo output of a track without speaker labels, as TRACK:CHANNELS (1-based): 1:3,4 = stereo with channel 3 left and 4 right (the M&E pair of an 8-channel master), 1:2 = mono from channel 2 (the lav on a boom/lav track), 1:1+2 = mono averaging 1 and 2, 1:all,all = every channel to both sides; separate tracks with ; (1:1,2;2:3,4). Separate mono tracks are mixed as one track, so 1:3,4 takes tracks 3 and 4. Sets --audio-channels from the map and re-encodes (AAC unless --audio-codec says otherwise). A track with speaker labels (5.1…), a mono track, or a channel the track doesn't have is an error, never a silent fallback. The app has the same choice as Mix From in the audio settings. |
--audio-passthrough | Pass audio through unchanged (default) |
--copy-audio | Copy audio-only sources byte-for-byte instead of transcoding them to .m4a. Still images and camera-raw stills are always copied. |
--lut | Path to a .cube LUT file |
--proresraw-color | ProRes RAW color mode: flat (V-Log for grading) or rec709 (V-Log + Rec.709 LUT for delivery). Default: flat |
--arri-color | ARRI RAW color target: rec709, rec2020pq, rec2020sdr, rec2020hlg, p3d65, logc3 (AWG3/LogC3), logc4 (AWG4/LogC4), or native-logc (flat pass-through). Default: native-logc |
--canon-color | Canon Cinema RAW (.CRM) color as gamma:colorspace. Gamma: rec709, canonlog2, canonlog3, widedr, pq, hlg, cmtpq, cmthlg, cmtdci, cmt709, canon709. Color space: rec709, cinemagamut, bt2020, dcip3. Default: canonlog2:cinemagamut (flat log, matches Premiere Pro); use canonlog2:rec709 for a baked Rec.709 look matching DaVinci Resolve |
--red-color | RED R3D and Nikon N-RAW color (both decode through the RED SDK, IPP2) as gamma or gamma:colorspace. Gamma: log3g10, log3g12, redlogfilm, bt1886, rec709, srgb, gamma22, gamma26, pq, hlg, linear. Color space: rwg (REDWideGamutRGB), rec2020, rec709, srgb, adobe1998, dcip3, p3d65, prophoto. A gamma alone takes rwg for log and linear, rec2020 for pq/hlg, and rec709 otherwise. Default: the clip’s As Shot develop |
--raw-exposure-ev | RAW exposure adjustment in EV stops, -3.0 to 3.0 (BRAW, R3D and Nikon N-RAW sources). Default: 0 |
--hdr | HDR source handling: passthrough (keep HDR when the output codec supports it; default), passthrough-sdr-safe (HDR pass-through plus SDR-range-preserving metadata for players that ignore HDR, on macOS 26 or later, HEVC with PQ sources; otherwise standard HDR metadata with a warning), or tonemap (convert to SDR) |
--in | In-point (HH:MM:SS:FF, ; before the frames for drop-frame): an offset into the clip (00:00:10:00 = ten seconds in) or the source's own timecode as pme probe reports it (see --trim-reference). Encoding starts at this frame. Default: start of file |
--out | Out-point, read the same way as --in. Encoding ends at this frame; past the end of the clip it stops at the end. Default: end of file |
--trim-reference | How --in/--out are read: auto (default; source timecode when the in-point lies inside the clip's timecode range, otherwise an offset into the clip), clip (always offsets) or source (always source timecode; an error if the source has none) |
--profile | H.264/HEVC profile: baseline, main or high for H.264; main, main10 or main422-10 (needs an HEVC 4:2:2 capable Mac) for HEVC. Default: the preset's, else H.264 High, and HEVC Main10 for a source above 8 bits (Main for 8-bit sources and with --bitrate). HDR pass-through and camera RAW sources upgrade HEVC Main to Main10 on their own. An 8-bit profile for a 10-bit source is honoured with a warning |
--audit | Verify each output against its source after encoding (frame count, duration, frame timing, timecode, audio, colour, bit depth) and print the verdict: verified, needs review or failed. The batch audit report is written next to the outputs, one per output folder (or into --audit-report-dir), and the --json complete event carries an audit object |
--audit-report-dir | Folder for the audit report (PDF, CSV, JSON), created if missing, so reports stay out of the output folder. Default: the app's report folder setting, else next to the outputs |
--timecode-burnin | Burn in source timecode overlay |
--timecode <value> | Output start timecode: source (default), zero (00:00:00:00), or a custom HH:MM:SS:FF value. Drop-frame (HH:MM:SS;FF) is accepted only for 29.97 and 59.94 fps output, and a label drop-frame skips (such as 00:01:00;00) is rejected. With --frame-rate, a source timecode moves to the new rate by real time: 11:08:17:21 at 23.976 becomes 11:08:17:26 at 29.97 (the hours, minutes and seconds stay within a rate family; from 23.976 to 25 the label follows the clock, about 3.6 s per hour). A custom value keeps its label. MOV and MP4 outputs both carry the timecode track. A source without timecode gets a 00:00:00:00 track in source mode unless --missing-timecode none (or the app setting) says otherwise |
--missing-timecode <value> | What a source without timecode (phone, screen recording, web file) gets in source mode: zero, a timecode track starting at 00:00:00:00, or none, no timecode track. Default: the app's Write a 00:00:00:00 timecode track when the source has none setting (Settings → General → Timecode, on). Sources with timecode and --timecode zero/custom always get a track; MXF always carries a start timecode (00:00:00:00) |
--watermark | Path to a transparent PNG to use as watermark (always centered) |
--watermark-scale | Watermark scale as percentage of source PNG size (1–400, default 100) |
--interlaced | Flag ProRes or DNxHD output as interlaced, top field first, e.g. for 1080i broadcast masters or broadcast server ingest (DNxHD in MXF). DNxHR, H.264 and HEVC have no interlaced output. Without this flag an interlaced source is flagged progressive, with a warning |
--json | Machine-readable NDJSON output |
--overwrite | Overwrite existing output files |
--concurrency | Max parallel encodes for multi-file batches, 1 up to the hardware session limit. Default: auto (hardware limit). RAW formats keep their own tighter isolation caps. |
--allow-oversize-encode | Bypass the app-side resolution gate so the hardware encoder can attempt resolutions above Apple's documented limits (capped at 12288×6480) |
--segment-parallel | Split a single long encode into time-range segments that run in parallel, then re-mux (experimental). Forces the feature on for this run. |
--max-segments | Upper bound on parallel segments per job with --segment-parallel (1–8). Default: auto (chip-tier + codec caps) |
Examples
pme transcode input.mxf -o output.mov --codec hevc --quality 0.8 pme transcode input.mxf -o output.mov --preset "YouTube 4K H.265" pme transcode input.mxf -o output.mov --codec prores422hq --lut /path/to/look.cube pme transcode input.braw -o output.mov --codec prores422hq --proresraw-color rec709 pme transcode A001C001.CRM -o output.mov --codec prores422hq --canon-color canonlog2:cinemagamut pme transcode shot.mxf -o output.mov --codec prores4444 --arri-color logc4 pme transcode *.mxf -o /output/folder/ pme transcode input.mxf -o output.mov --json pme transcode input.braw -o output.mov --codec hevc --rotate cw90 pme transcode input.R3D -o output.mov --codec prores422hq --rotate ccw90 --resolution 1080x1920 pme transcode input.mov -o output.mov --resolution 1920x1080 --resize-mode fit pme transcode input.mxf -o output.mov --rotate cw90 --resolution 1080x1920 --resize-mode fill pme transcode input.mov -o clip.mov --in 00:01:30:00 --out 00:02:15:00 --codec hevc pme transcode input.mov -o clip.mov --in 00:05:00:00 pme transcode A038C026.MXF -o clip.mov --codec prores422 --in '01:22:03;26' --out '01:23:13;30' pme transcode input.mov -o output.mov --codec hevc --bitrate 20000 --profile main10 --audit pme transcode IMG_2266.MOV livestream.mp4 -o proxies/ --preset "LucidLink (Proxy)" --audit
# Warning: livestream.mp4 is variable frame rate (variable: avg 27.866 fps, 29 fps base, 4702 held / 0 short frames); its frame timing is kept.
# Transcoding: livestream.mp4 → livestream.mp4
# Bitrate: 2,148 Kbps (14,000 Kbps ceiling scaled to 320×640; capped at 1.5× the source's 1,432 Kbps) pme transcode livestream.mp4 -o proxy.mov --preset "LucidLink (Proxy)" --frame-timing resample pme transcode input.mov -o output.mov --watermark /path/to/logo.png --watermark-scale 50 pme transcode input.mov -o output.gif --codec gif pme transcode input.mov -o clip.gif --codec gif --in 00:00:10:00 --out 00:00:13:00 pme transcode A001C003.braw -o /Volumes/RAID/Pulls/ --codec dpx --image-bit-depth 10 --frame-numbering source_timecode pme transcode shot.mov -o /Volumes/RAID/VFX/sh010 --codec exr --custom-start-frame 1001 --frame-padding 4 --audit pme transcode interview.mp4 -o stills/ --codec jpeg --jpeg-quality 85 --in 00:00:10:00 --out 00:00:12:00 pme probe
Inspect a media file and print its metadata. Works without a Pro license.
pme probe /path/to/video.mxf pme probe --json /path/to/video.mxf Example JSON response:
{
"videoCodec": "H.264",
"width": 3840,
"height": 2160,
"frameRate": 59.94,
"duration": 60.5,
"container": "MXF",
"audioTracks": [
{
"index": 0,
"codec": "PCM",
"channels": 1,
"sampleRate": 48000,
"bitDepth": 24
},
{
"index": 1,
"codec": "PCM",
"channels": 1,
"sampleRate": 48000,
"bitDepth": 24
}
],
"fileSize": 1073741824
} MOV and MP4 files also carry frameTiming, read from the file's sample table (no decoding): whether the file is variable frame rate, its base cadence and average rate, how many frames are held or short, and how far a constant grid would be off.
"frameTiming": {
"variableFrameRate": true,
"baseFrameRate": 29,
"averageFrameRate": 27.866,
"heldFrames": 4702,
"shortFrames": 0,
"cadenceErrorSeconds": 22.3784,
"summary": "variable: avg 27.866 fps, 29 fps base, 4702 held / 0 short frames"
} pme inspect
Display full metadata for a media file, including codec, resolution, bit depth, chroma subsampling, timecode, HDR info, and more. Works without a Pro license.
This is a deeper analysis than pme probe. Use probe for fast scripting; use inspect for detailed human inspection.
pme inspect /path/to/video.mxf pme inspect --json /path/to/video.braw Flags
| Flag | Description |
|---|---|
input (positional) | Path to the media file to inspect |
--json | Output metadata as JSON |
pme presets
Manage encoding presets. Presets are shared with the app, so a preset created via the CLI appears in the GUI and vice versa. Built-in presets cannot be modified or deleted.
List
pme presets list pme presets list --json --detailed adds every setting under the pme transcode option it corresponds to (codec, container, resolution, frame rate and frame timing, rate control and bitrate (with bitrate_scaling on when the bitrate is a ceiling scaled to the picture), profile, keyframe interval, audio codec, channels and bit depth, timecode, LUT, audit), so you can see what a preset writes before using it; --name shows one preset that way. Two values are descriptive: container source (H.264/HEVC keep a MOV or MP4 source's container; override with --container) and resolution source ÷ 2 (a preset scale-down that --resolution has no form for).
pme presets list --name "LucidLink (Proxy)" Create
Creates a custom preset, starting from an existing preset's settings when --base is given, otherwise from app defaults. --codec is required unless --base is given. Requires a Pro license.
| Flag | Description |
|---|---|
name (positional) | Name for the new preset |
--base | Existing preset (built-in or custom) to copy settings from |
--codec | Output codec (see Available Codecs) |
--image-bit-depth | Image sequence depth, as for pme transcode |
--image-compression | TIFF deflate, lzw or none; OpenEXR zip or none |
--jpeg-quality | JPEG sequence quality, 1–100 |
--frame-numbering | one, zero, source_timecode or custom |
--custom-start-frame | First frame number for custom numbering |
--frame-padding | Digits in each frame number, 4–8 |
--quality | Quality 0.0–1.0 (automatic mode) |
--bitrate | Average video bitrate in kbps (manual mode) |
--bitrate-scaling | on: --bitrate is a ceiling scaled down for smaller pictures (never above 1.5× the source's video bitrate); off (default): used exactly |
--rate-control | auto, vbr, vbr-constrained (macOS 26 or later), or vbr-2pass (H.264 only) |
--vbv-max | Peak bitrate in kbps for vbr-constrained (default 1.5× bitrate) |
--vbv-buffer | VBV buffer in seconds, 0.5–10 (default 2.5) |
--encoder-preset | default, quality, balanced, or speed (macOS 26 or later) |
--frc | duplicate (default) or interpolate (ML, macOS 15.4 or later) |
--upscale | standard (default) or super-resolution (ML, macOS 26 or later) |
--denoise | ML temporal denoise strength 0.0–1.0 (macOS 26 or later); 0 = off |
--resolution | WIDTHxHEIGHT or source |
--resize-mode | stretch, fit, or fill |
--frame-rate | Output frame rate (e.g. 23.976, 25). Omit to keep the source rate |
--frame-timing | auto (default), resample, renumber or source, as pme transcode --frame-timing. Presets saved before frame timing existed keep producing the same export: on → renumber, off → source |
--audio-codec | aac, alac, pcm, or passthrough |
--audio-bitrate | Audio bitrate in kbps (AAC) |
--audio-channels | source, mono, or stereo |
--audio-map | Channel map stored in the preset, as for pme transcode --audio-map (e.g. 1:3,4) |
--folder | Folder to file the preset under in the preset list |
pme presets create "Proxy 1080p" --codec prores422proxy --resolution 1920x1080 pme presets create "YouTube 4K Stereo" --base "YouTube 4K H.265" --audio-channels stereo Rename, delete, export, import
pme presets rename "Proxy 1080p" "Proxy FHD" pme presets delete "Proxy FHD" pme presets export "YouTube 4K H.265" ~/Desktop/youtube-4k.json pme presets import ~/Desktop/youtube-4k.json pme license
Manage your Pro license from the command line. Activation, deactivation and renewals (each time the app or the background service starts, and about once a day while it runs) go through the Parallel Media Encoder license server, which keeps track of which Macs hold a license's seats.
pme license status
Prints the tier, the scope it is licensed for, the validation status and the terms link. On Pro it also prints the edition, licensee, where the key came from, the seats in use ("This Mac: 1 of 5 seats") and the date until which the license works offline. It reports the stored license without contacting the server; --refresh asks the license server first, so a seat released from another Mac shows at once (offline, the stored license still answers).
--json adds scope, terms_url, terms_version, terms_accepted and, once accepted, terms_accepted_at and terms_accepted_via. A Pro license adds source (direct for a PME key, gumroad), edition (pro, education, nfr or trial), licensee, seats, seats_used, lease_expires (how long it works offline) and, for a license with an end date, license_expires. When a license stops unlocking Pro, lapse says why: check_pending, deactivated, seat_limit, suspended, revoked, expired or key_not_recognized. With seat_limit, machines lists the Macs holding every seat (id, name, model, last_seen). An education license unlocks Pro features for non-commercial use only.
Tier: free
Scope: Personal, educational, and evaluation use. Paid work requires a Pro license per Mac.
Status: No license key found. Use 'pme license activate <key>' to activate.
Terms: https://parallelmediaencoder.com/license/ pme license activate PME-XXXXX-XXXXX-XXXXX-XXXXX
Takes a PME- key from us or a key bought on Gumroad. Activation needs an internet connection and the License Terms accepted on this Mac first. Run pme terms accept, or add --accept-license-terms to accept them as part of the activation. Activation sends your license key, this Mac's name and a machine identifier to the Parallel Media Encoder license server, as described in the Privacy Policy. After that, Pro works offline for up to 30 days at a time. If every seat on the key is in use, the key is kept and the command lists the Macs holding them, with the ids pme license release takes.
pme license deactivate
Releases this Mac's seat on the license server and removes the key here, so the license can be activated on another Mac. Offline, Pro turns off at once and the seat is released the next time pme or the app can reach the server. This is Deactivate This Mac in Settings → License.
pme license release <mac-id>
For a PME- key whose seats are all in use: frees the seat held by the Mac with that id (listed by pme license status), then activates this Mac. The Mac you replace switches to Free the next time it checks in, and pme license status on it names the Mac that released it. This is Replace in Settings → License. Keys bought on Gumroad can't release seats, and a key can release up to 5 Macs in 24 hours.
pme license add-seats [--seats 2]
Opens a checkout page in your browser that adds seats to the PME- key stored on this Mac. The key itself doesn't change, and this Mac doesn't need a seat of its own, so it also works when every seat is in use. --seats sets the starting quantity (1 to 10; you can change it on the checkout page). --no-open prints the link instead of opening it, and --json prints url and expires_at. After paying, run pme license status --refresh to see the new seat count, or pme license activate again if this Mac was refused for seats. This is Add Seats in Settings → License. Keys bought on Gumroad, education keys and keys with an end date can't buy seats online: contact us instead. Without the app, you can also add seats on the web.
pme terms
Show, accept, or check acceptance of the License Terms. Acceptance is recorded once per terms version on this Mac and shared with the app; the record holds the version, the date, the document checksum and which command or button recorded it, never your license key.
pme terms show pme terms accept pme terms accept --yes pme terms status --json pme terms accept without --yes refuses to run when standard input is not a terminal, so an automated install never accepts silently.
pme service
Run Parallel Media Encoder as a headless HTTP server. This enables remote job submission, queue management, and real-time progress monitoring through the REST API. The service runs on any tier; licensing is enforced per submitted job.
Start & stop
pme service start
The default socket is ~/Library/Application Support/ParallelMediaEncoder/pme.sock. If you pass a custom --socket, keep the path short: macOS limits Unix socket paths to 104 characters.
pme service start --port 8080 pme service start --no-auth pme service stop pme service status The status names the host (the app or the headless daemon), its version and build, and built_at, when the running binary was built, which tells apart two local builds with the same version and build number.
Auto-start with launchd
pme service install pme service uninstall Auth tokens
pme service token pme service token --regenerate pme watch
Manage watch folders for automatic transcoding. Drop media into a folder and PME picks it up, applies a preset, and transcodes to your output folder. Requires a Pro license. Your AI assistant can do the same through the MCP server.
While the Parallel Media Encoder app (or pme service) is running, it owns watch folders: every pme watch command goes through it, so a folder you add or change from the terminal appears in the app at once and is never overwritten by it. With nothing running, the commands edit ~/Library/Application Support/ParallelMediaEncoder/WatchFolders.json directly, and pme watch start runs the folders in the terminal.
How it works
When a watch folder is active, PME scans it (subfolders included) at a configurable interval (1–60 minutes). A new file is picked up once it has stopped changing across two scans: it is moved into a PickedUp subfolder (or a custom destination on the same volume) and queued, so it is never processed twice. The source of an encode that fails for good is moved to a Failed folder next to PickedUp. Media already in the folder is picked up on the first scan; --dry-run lists it first. To re-process a file, move it back into the watch folder.
The output folder cannot be inside or the same as the watch folder (this would create an infinite loop). Watch folders also cannot overlap (a new watch folder cannot be a parent or subfolder of an existing one), and one watch folder's output or PickedUp folder can't sit inside another watch folder.
A folder is watched by one process at a time. If the app is watching it, pme watch start --foreground skips it (and the other way round); pme watch status says who is watching.
For network shares (SMB, NFS), PME stores credentials in the Keychain and remounts the share automatically if it disconnects; pme watch mount reconnects it on demand.
Add a watch folder
| Flag | Description |
|---|---|
--folder | Folder to watch for new media (required for add; edit moves the watch folder) |
--output | Output directory for transcoded files (required for add; created if missing) |
--preset | Preset name or id (required for add) |
--interval | Polling interval in minutes, 1–60 (default: 5) |
--suffix | Suffix to append to output filenames (edit --no-suffix removes it) |
--overwrite | Allow overwriting existing output files (edit --no-overwrite turns it off) |
--picked-up-path | Custom destination for picked-up files, on the same volume as the watch folder (edit --clear-picked-up-path goes back to the subfolder) |
--picked-up-folder-name | Name of the PickedUp subfolder inside the watch folder |
--network-user / --output-network-user | Username for the source / output network share |
--network-password-stdin / --output-network-password-stdin | Read the share password from standard input; stored in the Keychain |
--start | (add) Start watching right away |
--dry-run | Check the settings and list media already in the folder, without saving |
--json | Output JSON |
pme watch add \
--folder /Volumes/Ingest/DailyRushes \
--output /Volumes/Export/Proxies \
--preset "ProRes 422 Proxy" \
--interval 5 \
--suffix "-Proxy" --dry-run pme watch add \
--folder /Volumes/Ingest/DailyRushes \
--output /Volumes/Export/Proxies \
--preset "ProRes 422 Proxy" \
--interval 5 \
--suffix "-Proxy" --start security find-generic-password -w -s studio-nas | pme watch add \
--folder /Volumes/NAS/Ingest \
--output /Volumes/NAS/Transcoded \
--preset "ProRes 422 HQ" \
--network-user "[email protected]" \
--network-password-stdin
Read passwords from standard input: a password passed as a flag is visible to other processes and stays in your shell history. (--network-password still works, with a warning.)
pme watch add \
--folder /Volumes/Rushes/Camera_A \
--output /Volumes/Rushes/Proxies \
--preset "ProRes 422 Proxy" \
--picked-up-path /Volumes/Rushes/Processed Change a watch folder
pme watch edit changes only the flags you pass, with the same flags as add. A folder that is being watched restarts with the new settings. The watch folder and its PickedUp destination can't move while its jobs are still queued or encoding.
pme watch edit DailyRushes --interval 1 --preset "LucidLink (Proxy)"
pme watch edit a1b2c3d4 --no-suffix --dry-run A watch folder is named by its id, a unique id prefix (4 or more characters), its folder name, or its path.
List and status
pme watch list
pme watch list --json • a1b2c3d4 DailyRushes: idle in the app · /Volumes/Ingest/DailyRushes → /Volumes/Export/Proxies · ProRes 422 Proxy · every 5 min · next scan in 3 min · jobs: 12 completed, 1 encoding
• b5c6d7e8 Ingest: error · /Volumes/NAS/Ingest → /Volumes/NAS/Transcoded · ProRes 422 HQ · every 5 min
Error: Source network share disconnected
Reconnect the source share with the watch folder's mount action (… CLI pme watch mount b5c6d7e8). pme watch status DailyRushes --activity 20
pme watch status a1b2c3d4 --json status shows what a folder is doing: its state and who watches it, errors with what to do about them, the last and next scan, files still being copied or sent back to the folder (with the reason PME couldn't transcode them), the ids of its jobs, the Failed folder count, network share state, and recent activity kept by the app or service. --json returns the same status object as the REST API; pme watch list --json keeps its array format (plus state and watched_by).
Start, stop, scan
When the app or service is running, start starts the folder there and returns straight away. Otherwise, or with --foreground, the folders run in the terminal and print events as files are picked up; press Ctrl+C to stop.
pme watch start DailyRushes
pme watch start all
pme watch start all --foreground --json # run here, JSON event stream PME watch folders running. Press Ctrl+C to stop.
[a1b2c3d4] /Volumes/Ingest/DailyRushes (every 5 min)
[14:32:01] [a1b2c3d4] Scanning /Volumes/Ingest/DailyRushes...
[14:32:02] [a1b2c3d4] 3 files picked up (3 total)
[14:37:01] [a1b2c3d4] Scanning /Volumes/Ingest/DailyRushes... pme watch stop DailyRushes # stop watching (jobs already queued keep running)
pme watch stop all
pme watch scan DailyRushes # scan now instead of waiting for the interval
pme watch mount Ingest # reconnect the source share (--output for the output share) Remove
pme watch remove a1b2c3d4
pme watch remove /Volumes/Ingest/DailyRushes Removing a watch folder stops it and deletes its stored network credentials from the Keychain. No files are moved or deleted: media in the folder, PickedUp and Failed stay where they are, and queued jobs finish.
pme mcp
A Model Context Protocol server for AI assistants (Claude Desktop, Claude Code, Codex and Gemini CLI). The AI app launches pme mcp itself and talks to it over stdio; encodes are handed to pme service, which it starts when needed. Free on every tier. Full guide: MCP Server for AI Assistants.
pme mcp install # register PME with every detected AI app
pme mcp install --client codex # one app (repeatable), even if not detected
pme mcp install --dry-run # show what would change
pme mcp status # installed / not installed / stale per app
pme mcp uninstall # remove the entry everywhere
pme mcp bridges # running bridges (one per AI session) and the service socket
pme mcp bridges --stop-stale # stop bridges whose assistant quit or that run an older pme
pme mcp bridges --stop <pid> # stop one bridge (repeatable)
All of these accept --json. pme mcp with no subcommand (or pme mcp serve) runs the server; --socket <path> picks the service socket and --no-autostart makes encode tools fail instead of starting the service. What makes a bridge stale is explained under Connected assistants.
pme report
Report a problem to the developer from Terminal. PME builds the report itself: your PME and macOS versions, your Mac's model, chip and memory, and, for the jobs you name, their settings, errors, warnings, audit results and log lines. File and folder names are replaced by placeholders. Free on every tier.
pme report # describe it, review it, send it
pme report --job 44E65B81-2DB1-431B-8375-7FBBF1FE82FE --summary "HEVC encode fails at 80%"
pme report --log latest # attach the newest session log instead of the newest failed one
pme report --summary "Test" --dry-run --json # print exactly what would be sent, and exit
pme report list # drafts waiting to be sent, and recent reports
pme report show <id> # a draft exactly as it would be sent
pme report send <id> # review and send a saved draft
Before anything is sent, pme report shows the report, asks for an optional email address (only if you'd like a reply) and whether to include file names (names only, never folders), then waits for you to type send. Pressing Enter keeps the report as a draft. Sending needs an interactive terminal: run from a script, or by an AI assistant running commands for you, pme report only saves a draft and prints how to send it. When pme transcode fails at a terminal, it saves a draft of the failure and prints the pme report send command. Drafts are kept for 7 days; drafts an AI assistant opens through MCP appear here too.
REST API
When the service is running, all endpoints are available over HTTP. Every request requires an Authorization: Bearer <token> header unless the server was started with --no-auth.
Endpoints
| Endpoint | Method | Description |
|---|---|---|
/api/v1/health | GET | Health check: version, build, built_at, API version, tier and host (app or service) |
/api/v1/jobs | POST | Submit transcode job(s) |
/api/v1/jobs | GET | List all jobs; ?request_id= lists the jobs of one submission (every job of a POST /api/v1/jobs carries the same request_id) |
/api/v1/jobs/:id | GET | Get job status |
/api/v1/jobs/:id | DELETE | Cancel a job |
/api/v1/jobs/release | POST | Release held jobs: {"job_ids": [...], "start_queue": true}. Reports released, not_held and missing ids |
/api/v1/jobs/clear | POST | Remove finished jobs from the list: {"states": ["completed", "failed", "cancelled"], "dry_run": false}. A dry run reports removed: 0 and would_remove |
/api/v1/queue/start | POST | Start encoding |
/api/v1/queue/pause | POST | Pause all encodes |
/api/v1/queue/status | GET | Queue summary |
/api/v1/presets | GET | List presets |
/api/v1/presets | POST | Save pme transcode options as a custom preset (Pro): {"name": "…", "options": {…}, "overwrite": false}; options.preset names a base preset |
/api/v1/watch-folders | GET | Every watch folder with its status; ?include=activity adds recent activity |
/api/v1/watch-folders/:id | GET | One watch folder's status and recent activity. :id is an id, id prefix, folder name or percent-encoded path |
/api/v1/watch-folders | POST | Create a watch folder (Pro): watch_path, output_path, preset, optional interval_minutes, suffix, overwrite, picked_up_path/picked_up_folder_name, network_username, enabled, dry_run (lists media already in the folder) |
/api/v1/watch-folders/:id | PATCH | Change only the fields given (null clears a suffix, custom PickedUp path or username); enabled starts or stops it |
/api/v1/watch-folders/:id | DELETE | Remove a watch folder's settings; no files are moved or deleted |
/api/v1/watch-folders/:id/start | POST | Also /stop, /scan and /mount ({"endpoint": "source" | "output"}); :id may be all for start and stop |
/api/v1/hardware | GET | Hardware info |
/api/v1/license | GET | License status: tier, message, scope, terms_url, terms_version, terms_accepted (plus terms_accepted_at and terms_accepted_via once accepted), and on Pro source, edition, licensee, seats, seats_used, lease_expires and license_expires; lapse when a license stops unlocking Pro, and machines when every seat is in use (see pme license) |
/api/v1/reports | POST | Draft a problem report for the user to review: {"summary": "…", "job_ids": [...], "details": "…", "category": "encode_failed"}. Returns draft_id. There is no send endpoint: sending takes the user's click in the app or pme report send at a terminal |
/api/v1/events | GET (SSE) | Real-time progress stream |
Watch folder errors carry error, a stable code (invalid_value, unsafe_path, exists, overlaps, busy, not_found, not_watching, watched_elsewhere, pro_required) and the field they concern. The API refuses to watch your home folder, Desktop, Documents, Downloads, Movies, Pictures or Music folder, system folders and whole volumes (unsafe_path), because pickup moves every media file it finds; pme watch may. Network share passwords are never accepted over the API. Jobs a watch folder makes carry watch_folder_id.
Submit a job
All fields except inputs are optional. Every pme transcode override is accepted under its snake_case name with the same values, so anything that works on the command line works here. Invalid values are rejected with a 400/404 that names the field; they are never silently ignored.
| Field | Type | Description |
|---|---|---|
inputs | string[] | Absolute input file paths (required) |
output_dir | string | Output directory; filenames derive from the inputs |
output | string | Output file path (single input only; may not equal the input). An existing directory behaves like output_dir |
overwrite | bool | Replace an existing output file. Default false: an existing file is a 409 and nothing is queued |
codec | string | Codec ID (see Available Codecs). Overrides the preset's codec. Default: hevc |
preset | string | Preset name to apply (case-insensitive); 404 if unknown |
quality | number | 0.0–1.0, automatic quality mode. Mutually exclusive with bitrate |
bitrate | number | Target bitrate in Kbps, manual mode |
bitrate_scaling | string | on or off, as --bitrate-scaling |
resolution | string | WIDTHxHEIGHT, e.g. 1920x1080 |
audio_codec | string | aac, alac, or pcm. Default: passthrough |
lut | string | Path to a .cube LUT file |
timecode_burnin | bool | Burn in source timecode overlay |
raw_exposure_ev | number | RAW exposure in EV stops, -3.0 to 3.0 (BRAW/R3D) |
hdr | string | passthrough (default), passthrough-sdr-safe or tonemap |
rate_control | string | auto, vbr, vbr-constrained, vbr-2pass; bitrate modes need bitrate |
vbv_max | number | Peak Kbps for vbr-constrained |
vbv_buffer | number | VBV buffer seconds (0.5–10) for vbr-constrained |
encoder_preset | string | default, quality, balanced, speed (macOS 26+) |
frame_rate | number | Output frame rate, e.g. 23.976, 25, 59.94 |
frame_timing | string | auto, resample, renumber or source, as --frame-timing |
frc | string | duplicate or interpolate when raising the frame rate |
upscale | string | standard or super-resolution |
denoise | number | ML temporal denoise strength 0.0–1.0 |
resize_mode | string | stretch, fit, fill |
audio_channels | string | source, mono, stereo |
audio_map | string | Channel map, as --audio-map (e.g. "1:3,4"); a map the probed tracks can't honour is a 400 naming the track |
audio_passthrough | bool | Copy audio unchanged (cannot combine with audio_codec) |
rotate | string | cw90, 180, ccw90 |
timecode | string | source, zero, or HH:MM:SS:FF |
missing_timecode | string | zero or none, as --missing-timecode. Default: the app setting |
in | string | In-point HH:MM:SS:FF: a clip offset or source timecode, see trim_reference |
out | string | Out-point HH:MM:SS:FF, read the same way as in |
trim_reference | string | auto (default), clip or source, as --trim-reference |
container | string | mov, mp4 (H.264/HEVC) or mxf (DNx), as --container; applies to every input, output_dir included |
profile | string | baseline, main, high, main10, main422-10; checked against the job's final codec |
audit | bool | Verify the output against the source after encoding. Omitted or false: the app's Auto-audit setting decides |
audit_report_dir | string | Folder for this request's audit report, as --audit-report-dir; created if missing, must be writable |
watermark | string | Path to a transparent PNG (centred) |
watermark_scale | number | Watermark scale percent, 1–400 |
proresraw_color | string | flat or rec709 |
arri_color | string | native-logc, rec709, rec2020pq, rec2020sdr, rec2020hlg, p3d65, logc3, logc4 |
image_bit_depth | string | Image sequence depth: 8, 10, 12, 16, half or float, per format as for --image-bit-depth |
image_compression | string | deflate, lzw or none (TIFF); zip or none (OpenEXR) |
jpeg_quality | number | JPEG sequence quality, 1–100 (default 90) |
frame_numbering | string | one (default), zero, source_timecode or custom |
custom_start_frame | number | First frame number for custom numbering; on its own it implies custom |
frame_padding | number | Digits in each frame number, 4–8 (default 7) |
canon_color | string | gamma or gamma:colorspace, e.g. canonlog2:cinemagamut |
red_color | string | gamma or gamma:colorspace for R3D and Nikon N-RAW, e.g. log3g10:rwg or bt1886 |
interlaced | bool | Flag ProRes or DNxHD output as interlaced (TFF), as --interlaced |
auto_start | bool | Start encoding. Default: true. false holds the jobs: they are queued (and shown in the app with a Held badge) but never start, even while other jobs run, until released with POST /api/v1/jobs/release or Release in the app |
{
"inputs": ["/path/to/input1.mxf", "/path/to/input2.mov"],
"output_dir": "/path/to/output/",
"codec": "hevc",
"quality": 0.8,
"auto_start": true
} 201 returns an array of job objects; each carries id, state, progress, input_path, output_path, requested vs. effective rate_control_*, encoder_preset_*, profile_*, frame_timing_* (e.g. auto → variable (kept), 29 fps base) and bitrate_* (with bitrate_scaling_note saying why a scaled bitrate is lower), started_at/completed_at (ISO 8601), output_file_size, retry_attempt, and its own warnings: every notice for the job, each once, covering what is known at submit (ML frame processing an FFmpeg-decoded source can't use, SDR-range preservation on an HLG source, a variable-frame-rate source, an odd size) and what the engine records while encoding. warning_message is the same list as one string. An image sequence job's output_path is the folder of frames, frame_pattern names the frames inside it (clip/clip.%07d.dpx), output_file_size is the frames' total, a finished job's frames_written counts them, and the sequence values it writes with are echoed under the option names (image_bit_depth, frame_numbering, ...). Trimmed jobs echo trim_reference, trim_in/trim_out (clip offsets) and source_start_timecode. held marks an auto_start: false job, and a queued job's wait_reason/wait_message say why it hasn't started (held, queue_paused, session_limit, arri_isolation, ...). Audited jobs carry audit_status (pending, then verified, needs_review or failed), safe_to_archive, audit_mismatches and audit_report_path; a job never reports completed with its audit still unknown. Each request gets one audit report, written when its last job finishes; until then its audited jobs carry audit_report_pending: true. 400 is an invalid value, 404 a missing input or preset, 409 an existing output without overwrite, and 403 a Pro feature on the free tier ({"code": "pro_required", "features": [...], "upgrade_url": ...}) or License Terms not yet accepted on this Mac ({"code": "terms_not_accepted", "terms_url": ...}; accept in the app or with pme terms accept). Poll GET /api/v1/jobs/:id or subscribe to events for progress.
By default the service listens on a Unix domain socket:
curl -X POST \
--unix-socket "$HOME/Library/Application Support/ParallelMediaEncoder/pme.sock" \
http://localhost/api/v1/jobs \
-H "Authorization: Bearer $(pme service token)" \
-H "Content-Type: application/json" \
-d '{"inputs": ["/path/to/file.mxf"], "output_dir": "/output/", "codec": "hevc"}' With pme service start --port 8080, the same API is available over TCP (localhost only):
curl -X POST http://localhost:8080/api/v1/jobs \
-H "Authorization: Bearer $(pme service token)" \
-H "Content-Type: application/json" \
-d '{"inputs": ["/path/to/file.mxf"], "output_dir": "/output/", "codec": "hevc"}' Server-Sent Events
Connect to /api/v1/events for real-time updates. Because EventSource can't set headers, this endpoint also accepts the token as a query parameter: /api/v1/events?token=<token>. The stream emits three event types:
progress: per-file encoding progress (percentage, speed, ETA)state_change: job state transitions (queued, encoding, completed, failed)queue_status: overall queue summary updates
Available Codecs
Pass these IDs to --codec. DNxHR/DNxHD codecs and DPX, TIFF and OpenEXR sequences require a Pro license.
| ID | Codec | Notes |
|---|---|---|
h264 | H.264/AVC | Default profiles |
hevc | H.265/HEVC | Default codec |
prores422proxy | ProRes 422 Proxy | |
prores422lt | ProRes 422 LT | |
prores422 | ProRes 422 | |
prores422hq | ProRes 422 HQ | |
prores4444 | ProRes 4444 | |
prores4444xq | ProRes 4444 XQ | |
gif | Animated GIF | 15 fps default |
dpx | DPX image sequence | 8, 10 (default), 12 or 16-bit RGB, with per-frame source timecode in the header. Pro |
tiff | TIFF image sequence | 8 or 16-bit (default) or 32-bit float, Deflate (default), LZW or uncompressed. Keeps a ProRes 4444 source's alpha (straight). Pro |
jpeg | JPEG image sequence | 8-bit, quality 90 default. Free |
exr | OpenEXR image sequence | Half (default) or 32-bit float, ZIP (default) or uncompressed. Keeps a ProRes 4444 source's alpha (premultiplied). Only where macOS can write OpenEXR: the hardware listing (GET /api/v1/hardware, pme_hardware_info) shows it among the software codecs, and elsewhere lists it as unavailable with the reason in unavailable_reasons; the job is refused. Pro |
dnxhr-lb | DNxHR LB | Pro |
dnxhr-sq | DNxHR SQ | Pro |
dnxhr-hq | DNxHR HQ | Pro |
dnxhr-hqx | DNxHR HQX | Pro |
dnxhr-444 | DNxHR 444 | Pro |
dnxhd-lb | DNxHD LB | Pro |
dnxhd-sq | DNxHD SQ | Pro |
dnxhd-hq | DNxHD HQ | Pro |
dnxhd-hqx | DNxHD HQX | Pro |
dnxhd-444 | DNxHD 444 | Pro |
Containers: H.264/HEVC/ProRes write .mov. Passing a DNx codec via --codec writes .mxf; DNx via a preset follows the preset's container setting (.mov wrapper by default, matching the app).
Exit Codes
Use these codes to handle errors in scripts and CI pipelines.
| Code | Meaning |
|---|---|
0 | Success |
1 | General error |
2 | Usage error: Pro license required/invalid, or an invalid flag value or combination |
3 | Input, LUT or watermark file not found |
5 | Encoding failed |
6 | License Terms not accepted on this Mac (terms_not_accepted): run pme terms accept or open the app |
10 | Partial failure (batch: some succeeded, some failed) |