.. Copyright (c) 2026 OpenShot Studios, LLC. GPL-3.0-or-later.

.. _cli_ref:

Command Line Interface
======================

Bring OpenShot into your command-line workflow. Import footage, trim and arrange
clips, apply effects, animate properties with keyframes, and export videos from
your terminal. Use a few commands to automate repetitive edits, write a script
for a recurring project, or let an AI agent help assemble and refine a video.

The command line works with the same projects as the editor. You can control an
open editor and review changes as you go, or work in a **headless session** without
opening a window. A session keeps your project open between commands, including
its edits, undo history, and export jobs. Save it as an ``.osp`` project whenever
you want to continue editing later.

Getting started
---------------

Use your installed OpenShot executable followed by ``--cli``. Start with
``intro`` for a short guide to sessions, editing, animation, and saving, or
``help`` for the command overview. These commands do not need an active session.
The examples below require an OpenShot build that includes command-line editing.

.. _cli_appimage_ref:

Linux AppImage
~~~~~~~~~~~~~~

From the directory containing your downloaded AppImage::

   ./OpenShot.AppImage --cli intro
   ./OpenShot.AppImage --cli help

Replace ``OpenShot.AppImage`` with your downloaded filename. If **AppImageLauncher**
shows desktop integration dialogs, set ``APPIMAGELAUNCHER_DISABLE=1`` before
launching the command::

   APPIMAGELAUNCHER_DISABLE=1 ./OpenShot.AppImage --cli intro

For scripts or repeated commands, set it once in your current shell::

   export APPIMAGELAUNCHER_DISABLE=1
   ./OpenShot.AppImage --cli session start

The variable must be set before launching the AppImage: AppImageLauncher can
intercept it before OpenShot receives ``--cli``. Relative media paths and
``@batch.json`` files are resolved from your working directory. Keep the AppImage
file available while its sessions or exports are running.

Linux Snap
~~~~~~~~~~

For the `OpenShot Snap <https://snapcraft.io/openshot-qt>`_, use::

   snap run openshot-qt --cli intro
   snap run openshot-qt --cli help
   snap run openshot-qt --cli session start

Snap's file permissions apply to command-line editing too. Keep media, projects,
and exports in locations the Snap can access, such as your home folder. To allow
access to external drives, connect the removable-media permission::

   sudo snap connect openshot-qt:removable-media

Windows
~~~~~~~

.. important::

   **Use openshot-qt-cmd.exe for command-line editing on Windows.** This console
   executable displays command results, help, and errors in your terminal.
   The desktop executable, ``openshot-qt.exe``, does not display standard output
   in an interactive terminal, leaving commands without visible feedback and
   making the CLI difficult to use.

In PowerShell, from the OpenShot installation directory::

   & .\openshot-qt-cmd.exe --cli intro
   & .\openshot-qt-cmd.exe --cli help
   & .\openshot-qt-cmd.exe --cli session start

If you are in a different directory, use the full path to
``openshot-qt-cmd.exe``. Keep ``--cli`` after the executable name.

macOS
~~~~~

For an installation in Applications::

   "/Applications/OpenShot Video Editor.app/Contents/MacOS/openshot-qt" --cli intro
   "/Applications/OpenShot Video Editor.app/Contents/MacOS/openshot-qt" --cli help

Finding commands
----------------

Throughout this guide, ``openshot-cli`` is shorthand for your installation's
command above, such as ``snap run openshot-qt --cli`` or
``./OpenShot.AppImage --cli``. Some installations also provide an ``openshot-cli``
command directly. When running from source, use ``python3 src/launch.py --cli``
in OpenShot's Python environment.

Bare ``--cli``, ``--cli help``, and ``--cli --help`` show the same overview.
Help becomes more specific as you navigate commands::

   openshot-cli clip help
   openshot-cli clip keyframe help
   openshot-cli clip keyframe set help
   openshot-cli edit help
   openshot-cli text help
   openshot-cli effects list
   openshot-cli effects audiovisualization help
   openshot-cli effects audiovisualization help --property background

Group help explains workflows with examples; operation help describes arguments
and behavior. ``--help`` is equivalent to trailing ``help``. All help commands
work without an editing session.

``effects list`` shows the available effects with short descriptions.
Effect help explains their properties, defaults, choices, and ranges.
``effects instances`` lists effects already attached to clips, with parent clip
IDs. Class names, display names, and underscore aliases identify the same effect.

Working with scripts and AI agents
----------------------------------

AI coding agents such as **Codex** and **Claude Code** can use the CLI to inspect
and edit OpenShot projects through terminal commands. Give your agent the path
to your OpenShot executable, the media or project to work with, and the desired
result: duration, dimensions, animation style, and output location. Ask it to
read ``--cli intro`` before starting. The same commands work when you type them
yourself or run them from a script.

Start with ``intro``, then use command-specific help as needed. A useful workflow
is to select a session, inspect the project, make a small set of changes, review
the result, and save. Commands return structured JSON so scripts and agents can
use object IDs and results directly instead of interpreting screen output.

* **Inspect before editing.** Use returned IDs and property information instead
  of guessing names, track numbers, or timing. Select a session explicitly when
  more than one project is open.
* **Group repeated edits.** Use ``edit apply`` for a sequence of changes or
  ``edit bulk`` to apply a template to selected clips. Validate with ``--dry-run``
  before applying a batch.
* **Check animation.** Choose a coordinate space, start with a few keyframes,
  and use easing presets for familiar motion. ``keyframe preview`` samples the
  curve so you can spot pauses or jumps before exporting. Review rendered motion
  as well; a contact sheet alone cannot show how an animation feels.
* **Save and follow exports.** Save the project explicitly and check the export
  job's status before treating an output as complete. If a request times out,
  inspect its status before repeating an edit.

The sections below explain these workflows and their timing conventions.

Sessions and outputs
--------------------

Starting or finding a session
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Start one headless session (without an editor window)::

   openshot-cli session start

To open an existing project at startup, add ``--project "/path/project.osp"``.
The successful JSON response contains ``result.session_id``. Copy that value.
If OpenShot is already running, or you have forgotten the ID, find available
sessions with::

   openshot-cli session list

Choose the session for the project you intend to edit. You do not need to start
a new session for each command.

.. _cli_session_selection_ref:

Selecting a session
~~~~~~~~~~~~~~~~~~~

.. tip::

   **Set OPENSHOT_SESSION once, then run commands normally.** Your terminal
   remembers the selected session, so you do not need to pass ``--session``
   with every command. Use the PowerShell or Bash example below.

Replace ``SESSION_ID`` below with the ``session_id`` returned by
``session start`` or ``session list``.

Windows PowerShell::

   $env:OPENSHOT_SESSION = "SESSION_ID"
   & .\openshot-qt-cmd.exe --cli timeline view
   & .\openshot-qt-cmd.exe --cli export start "C:\path\test.mp4" --wait

Linux Bash::

   export OPENSHOT_SESSION="SESSION_ID"
   ./OpenShot.AppImage --cli timeline view
   ./OpenShot.AppImage --cli export start "/path/test.mp4" --wait

On macOS, use the same ``export`` syntax. For macOS or Snap commands, substitute
your installation's executable from Getting started. The selection lasts for
this terminal and commands launched from it. Set it again in a new terminal,
or update it when switching to a different session.

**The remaining editing and export examples assume OPENSHOT_SESSION is set.**
The next subsection shows how to capture the ID automatically for scripts.

To target a different session for just one command, use ``--session``. It
overrides ``OPENSHOT_SESSION`` without changing your terminal's selection::

   openshot-cli --session OTHER_SESSION_ID project get

With a packaged executable, put this override after ``--cli`` and before the
command group: ``--cli --session OTHER_SESSION_ID project get``.

Automatic selection only works when exactly one reachable session exists.
If you see ``session_required``, run ``session list`` and explicitly select the
intended session. If it is no longer listed, start it again and use the new ID.
Help, ``intro``, and session discovery do not require a selected session.

Capturing the session ID automatically
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Scripts can read ``result.session_id`` directly from the JSON returned by
``session start`` and set ``OPENSHOT_SESSION`` for subsequent commands. There is
no need to copy the ID manually or read a log file. Check that startup succeeded
before using the returned ID.

Windows PowerShell (set the executable path for your installation)::

   $openshot = "C:\Program Files\OpenShot Video Editor\openshot-qt-cmd.exe"
   $reply = & $openshot --cli session start
   if ($LASTEXITCODE -ne 0) { throw "OpenShot session startup failed" }
   $session = $reply | ConvertFrom-Json
   if (-not $session.ok -or -not $session.result.session_id) {
       throw "OpenShot did not return a session ID"
   }
   $env:OPENSHOT_SESSION = $session.result.session_id
   & $openshot --cli timeline view

Linux Bash (requires ``jq`` to read JSON)::

   openshot="/full/path/to/OpenShot.AppImage"
   export APPIMAGELAUNCHER_DISABLE=1
   if reply=$("$openshot" --cli session start) &&
      session_id=$(printf '%s' "$reply" | jq -er 'select(.ok == true) | .result.session_id | strings | select(length > 0)'); then
       export OPENSHOT_SESSION="$session_id"
       "$openshot" --cli timeline view
   else
       echo "OpenShot session startup failed" >&2
   fi

The environment variable selects a session for commands launched from this
terminal; it does not change a system-wide setting. Starting a session does not
set the variable automatically. After stopping the session, clear the selection
with ``Remove-Item Env:OPENSHOT_SESSION`` in PowerShell or
``unset OPENSHOT_SESSION`` in Bash.

Showing and stopping a session
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

To show the selected session in the editor::

   openshot-cli session show

On a desktop, ``session show`` creates the editor in the same process and keeps
the live project, IDs, history, export jobs, and connection. Repeated calls raise
the existing window. ``session start --show`` opens it immediately. Sessions
started without a display, or with ``QT_QPA_PLATFORM=offscreen``/``minimal``,
cannot later show a desktop window. For those sessions, save and open the project
in a new desktop session. Once shown, close the session
through the editor window.

Global options (``--session``, ``--timeout``) go before the command.
``session serve`` stays in the foreground for supervisors; ``session start`` returns after a background
host registers. ``session stop`` refuses unsaved changes and active exports.
Use ``--discard`` only to intentionally discard unsaved edits.

Command output
~~~~~~~~~~~~~~

Results are JSON on stdout, except the default text timeline view. Errors are JSON on stderr with exit status 1;
argument-parser errors exit 2. Mutations return compact summaries with an
``inspect`` command. ``get`` returns native object JSON. Project replies include
a revision; effect catalog replies do not require a session. Logs from headless
sessions live beside their registry records. ``OPENSHOT_SESSION_DIR`` overrides
the default ``~/.openshot_qt/sessions`` directory.

Sessions accept commands locally on your computer. Keep the session registry
private, since it contains the credentials used to access your editing sessions.
Exports work from a snapshot, so you can continue editing while a job runs.

Opening and saving projects
---------------------------

An OpenShot project (``.osp``) stores your editable timeline. Exporting creates a
finished video; saving the project lets you return to its clips, effects, and
keyframes later. Save your project separately even when you also export a video.

Opening a project
~~~~~~~~~~~~~~~~~

Start a session with an existing project::

   openshot-cli session start --project "/home/me/Videos/My Project.osp"

Add ``--show`` to open its editor window too. To use an editor that is already
running, find it with ``session list``. Set ``OPENSHOT_SESSION`` to its returned session ID, then open
or inspect a project::

   openshot-cli project open "/home/me/Videos/My Project.osp"
   openshot-cli project get

Replace the example path with your own, and quote paths containing spaces.
``project open`` replaces the selected session's current project. It refuses to
discard unsaved edits unless you explicitly add ``--discard``; save those edits
first if you want to keep them. Opening through the CLI does not show interactive
missing-media dialogs, so keep the project's source media available.

For a new, empty project, use ``session start`` without ``--project``, then import
media and add clips as described under Editing.

Saving a project or a copy
~~~~~~~~~~~~~~~~~~~~~~~~~~

Save to a new file, or create a separate version before making more changes::

   openshot-cli project save "/home/me/Videos/My Project v2.osp"

To update an existing file, explicitly allow replacement::

   openshot-cli project save "/home/me/Videos/My Project v2.osp" --overwrite

Always supply the destination path. Saving an ``.osp`` does not package all your
source footage into that file; keep the referenced media available when moving
or reopening the project.

Exporting a video
-----------------

Automate rendering and exporting from scripts or AI agents, using the project's
settings or choosing a different output profile for each render. Export jobs can
run in the background or wait for completion before your script continues.
To render an MP4 using the project's resolution and frame rate::

   openshot-cli export start "/home/me/Videos/video.mp4" --wait

By default, OpenShot uses H.264 video and AAC audio. Replace the example path
with your destination, using an existing folder and a new filename. Existing
outputs are never overwritten. Exporting does not save your editable ``.osp``
project; use ``project save`` separately to keep your editing work.

The ``--wait`` option keeps the command running until the export completes or
reports a failure. Review the finished video before sharing it.

Checking export progress
~~~~~~~~~~~~~~~~~~~~~~~~

Leave off ``--wait`` to start an export in the background. The response includes
a job ID you can use to check its progress::

   openshot-cli export start "/home/me/Videos/video.mp4"
   openshot-cli export status JOB_ID
   openshot-cli export status JOB_ID --wait
   openshot-cli export list

Replace ``JOB_ID`` with the returned ID. If waiting times out, the export keeps
running; check the same job instead of starting another export. To stop a job,
use ``export cancel JOB_ID``. Each job captures the timeline when it starts,
so later edits do not change an export already in progress.

Waiting defaults to 600 seconds; adjust this with ``--wait-timeout``. This is
separate from the global per-request ``--timeout``. Export failures,
cancellation, and wait timeouts exit with status 1 and include job details.

Exporting in multiple profiles
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Export the same timeline at different resolutions or aspect ratios by choosing
an export profile for each output. **An export profile applies only to that job**;
it does not change the editing project's profile. A profile sets dimensions,
frame rate, and aspect ratio; an export preset selects encoding settings such
as MP4/H.264.

Find the available profiles and inspect their settings::

   openshot-cli profile list --search 720
   openshot-cli profile list --search 1080
   openshot-cli profile get builtin:01280x0720p0030_16-09

For example, export landscape versions at 720p and 1080p, then a square version
at 1080 by 1080. These profiles all use 30 fps::

   openshot-cli export start "/home/me/Videos/video-720p.mp4" --profile builtin:01280x0720p0030_16-09 --wait
   openshot-cli export start "/home/me/Videos/video-1080p.mp4" --profile builtin:01920x1080p0030_16-09 --wait
   openshot-cli export start "/home/me/Videos/video-square.mp4" --profile builtin:01080x1080p0030_01-01 --wait

Use a different output filename for each version. Keep edits unchanged between
jobs when you want matching versions. Changing aspect ratio can change framing:
review each output, especially titles and animated subjects.
For a separately composed square or portrait version, save a project copy, change
its project profile, and adjust the clips before exporting. See Profiles and
the export settings below for custom profiles and encoding options.

Batch exporting multiple projects
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Reuse one headless session to render several saved projects in sequence. Open a
project, export it with ``--wait``, then open the next project. The examples below
use each project's own resolution and frame rate and create MP4 files beside the
``.osp`` files. Add ``--profile PROFILE_ID`` to the export command to use one
output profile for every project.

First run the matching automatic session-ID example above successfully, in the
same terminal. It defines ``openshot`` and selects the new session. Replace the
project paths below with your own. Use distinct output names that do not already
exist; the examples check all destinations before starting the batch.

Windows PowerShell::

   $projects = @(
       "C:\VideoProjects\First.osp",
       "C:\VideoProjects\Second.osp"
   )
   foreach ($project in $projects) {
       if (-not (Test-Path -LiteralPath $project -PathType Leaf)) {
           throw "Project not found: $project"
       }
       $output = [System.IO.Path]::ChangeExtension($project, ".mp4")
       if (Test-Path -LiteralPath $output) { throw "Output already exists: $output" }
   }
   foreach ($project in $projects) {
       $output = [System.IO.Path]::ChangeExtension($project, ".mp4")
       & $openshot --cli project open $project
       if ($LASTEXITCODE -ne 0) { throw "Could not open: $project" }
       & $openshot --cli export start $output --wait --wait-timeout 86400
       if ($LASTEXITCODE -ne 0) { throw "Export needs attention: $output" }
   }
   & $openshot --cli session stop
   if ($LASTEXITCODE -ne 0) { throw "Could not stop the session" }
   Remove-Item Env:OPENSHOT_SESSION

Linux Bash (the parentheses keep error handling local to the batch)::

   (
       set -e
       : "${OPENSHOT_SESSION:?Run the session startup example first}"
       projects=(
           "/home/me/VideoProjects/First.osp"
           "/home/me/VideoProjects/Second.osp"
       )
       for project in "${projects[@]}"; do
           test -f "$project" || { echo "Project not found: $project" >&2; exit 1; }
           output="${project%.osp}.mp4"
           test ! -e "$output" || { echo "Output already exists: $output" >&2; exit 1; }
       done
       for project in "${projects[@]}"; do
           "$openshot" --cli project open "$project"
           "$openshot" --cli export start "${project%.osp}.mp4" --wait --wait-timeout 86400
       done
       "$openshot" --cli session stop
   ) && unset OPENSHOT_SESSION

Both recipes stop the batch on a command failure and leave the session available
for inspection. The wait limit here is 24 hours per export. A wait timeout leaves
the render running: use ``export list`` and ``export status JOB_ID --wait`` before
retrying. Completed videos remain on disk; remove completed projects from the
batch before resuming. After resolving a failure, stop the session and clear its
environment variable when you are finished.

Choosing presets and encoding settings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Discover the installed export presets and supported settings::

   openshot-cli export preset list
   openshot-cli export preset get builtin:format_mp4_x264
   openshot-cli export capabilities
   openshot-cli export start /tmp/reel.mp4 --preset builtin:format_mp4_x264 --quality med --wait

Export also accepts ``--from-file FILE_ID`` with optional ``--create-custom``.
Settings resolve in this order: project defaults, preset, advanced JSON, explicit
profile/quality/range flags. Advanced JSON exposes video/audio codecs and bitrate,
audio layout/rate/channels, native codec options, hardware device settings, and
thread counts.

For example, select an encoder option through advanced JSON::

   openshot-cli export start /tmp/small.mp4 --profile builtin:00640x0360p0030_16-09 --json '{"video_options":{"preset":"fast"}}' --wait

Validating export settings
~~~~~~~~~~~~~~~~~~~~~~~~~~

Check settings before starting a render::

   openshot-cli export validate /tmp/reel.mp4 --preset builtin:format_mp4_x264

Capabilities report compiled codec availability, not physical GPU availability.
Validation goes further: it opens the encoders (including hardware encoders) and
the container with the exact settings, in a separate process, without writing a
file. A combination the container can't hold fails with ``incompatible_export``,
FFmpeg's own explanation, and the setting to change (``details.cause.path``)::

   openshot-cli export validate /tmp/song.mp3 --json '{"mode":"audio","format":"mp3","audio_codec":"aac"}'

The export worker runs the same check before rendering.

Rendering a frame range
~~~~~~~~~~~~~~~~~~~~~~~

Use ``--start-frame`` and ``--end-frame`` to render part of the timeline.
Ranges are inclusive and one-based at the **export profile's FPS**. For example,
render the first 90 frames at 30 fps (three seconds)::

   openshot-cli export start /tmp/excerpt.mp4 --profile builtin:01280x0720p0030_16-09 --start-frame 1 --end-frame 90 --wait

Audio-only exports and image sequences
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Choose an output mode with advanced JSON::

   openshot-cli export start /tmp/music.wav --json '{"mode":"audio","format":"wav","audio_codec":"pcm_s16le"}' --wait
   openshot-cli export start /tmp/frames --json '{"mode":"image_sequence","format":"png"}' --end-frame 3 --wait

Image sequences require a new output directory. Existing outputs are never
overwritten.

Editing
-------

Adding clips and effects
~~~~~~~~~~~~~~~~~~~~~~~~

Use the IDs returned by import/add/list, and discover layer numbers with
``project get``::

   openshot-cli file import /absolute/path/video.mp4
   openshot-cli clip add FILE_ID --layer 2000000 --position 3 --start 10 --end 17 --volume 0
   openshot-cli clip set CLIP_ID --json '{"volume":0.5}'
   openshot-cli clip keyframe set CLIP_ID --property volume --frame 301 --value 0
   openshot-cli clip keyframe set CLIP_ID --property volume --frame 331 --value 1
   openshot-cli effects add CLIP_ID saturation
   openshot-cli effects set EFFECT_ID --json '{"saturation":1.2}'
   openshot-cli project save /absolute/path/edit.osp

Choose the project profile early when possible; populated projects also support
profile changes through the existing FPS conversion and undo system. ``--audio-only`` on clip creation
suppresses video, including embedded cover art in music files. To use that clip
for Audio Visualization instead, enable its video and audio, and follow the
effect's help for transparent overlays and layer placement.

Understanding clip timing
~~~~~~~~~~~~~~~~~~~~~~~~~

The example keyframes assume a 30fps project and a clip trimmed at source 10s.
Position is in timeline seconds; start/end are source seconds, with end exclusive.
Frames start at 1. File thumbnail frames use source FPS. Timeline frames use
project FPS. Clip keyframes use source coordinates at project FPS, so trimming
the clip does not move its first visible keyframe to frame 1.

Changing properties
~~~~~~~~~~~~~~~~~~~

``set`` merges top-level properties. Scalars turn keyframed properties into
constants. A supplied nested curve replaces that curve; use ``keyframe set`` to
change just one point. JSON arguments accept inline JSON, ``@file.json``, or
``-`` for stdin. Property names, types, enums, keyframes, references, and timing
are validated before editing the project. Errors identify the field,
expected value, received value, and a correction where possible. GUI slider
ranges are advisory where the engine supports values beyond them.

Adding fades
~~~~~~~~~~~~

``clip fade ID --in 0.5 --out 0.5`` calculates source-trim keyframe coordinates
automatically. It replaces the alpha curve; ``--property volume --value 0.25``
instead creates an audio fade with a 0.25 peak gain. Durations must fit between
the first and last visible frames; fade-out reaches zero on the last frame.

Splitting clips
~~~~~~~~~~~~~~~

``clip split ID --at 6.5`` uses timeline seconds, snaps to the nearest project
frame, and keeps both halves. It retains source-coordinate curves and duplicates
effects with fresh IDs on the right half, following the editor's slicing behavior.
The left half retains its ID. The response reports requested/actual split times
and both summaries; a named batch result supports ``{"$ref":"cut.right.id"}``.
Splits reject endpoints and locked tracks. Undo restores the original clip in
one transaction.

Clip menu presets
~~~~~~~~~~~~~~~~~

The editor's clip menu presets use the same code from the command line. Each
command lists its presets in its help, and each one is a single undo step:

.. code-block:: bash

   openshot-cli clip motion CLIP_ID --preset slide-in-left
   openshot-cli clip motion CLIP_ID --preset pulse --at 4.5
   openshot-cli clip look CLIP_ID --effect ColorGrade --preset warm_up
   openshot-cli clip look CLIP_ID --effect all --preset none
   openshot-cli clip speed CLIP_ID --mode forward --factor 2
   openshot-cli clip speed CLIP_ID --mode freeze --at 3 --duration 2
   openshot-cli clip repeat CLIP_ID --passes 3 --pattern pingpong
   openshot-cli clip waveform CLIP_ID

Motion presets are relative to the clip's current transform. Look presets add
or replace one effect, and ``none`` removes it. Speed and repeat retime every
keyframe and change the clip's length; ``clip speed --mode reset`` restores
normal speed. Other menu actions are plain edits: rotate and layout with
``clip set``, crop with ``effects add Crop``. The commands also work in
``edit apply`` and ``edit bulk``.

.. _cli_text_ref:

Adding and styling text
~~~~~~~~~~~~~~~~~~~~~~~

``text add`` creates a text and places it as a clip, using the same text as the editor's
**Text** tool. Options you leave out use the editor's default style:

.. code-block:: bash

   openshot-cli text add --text "Opening Night" --layer 5000000 --position 2 --duration 4 \
       --place lower-third --font "Noto Sans" --size 72 --bold --color "#ffffff" \
       --background "#80000000" --background-padding 16 --background-corner 12
   openshot-cli text get CLIP_ID
   openshot-cli text set CLIP_ID --stroke black --stroke-width 3
   openshot-cli text set CLIP_ID --text $'Two\nlines' --line-spacing 1.4
   openshot-cli text fonts --search noto
   openshot-cli text list --search opening

The words, font, size, alignment, box width and default colors belong to the text in
Project Files and are shared by every clip that uses it, just like in the editor.
``text set`` on one clip changes all of them and lists them in ``changed_clip_ids``;
``--this-clip-only`` gives that clip its own copy first. ``clip add FILE_ID`` places another
clip of a text from ``text list``. Sizes are in pixels at the project resolution, and colors
are ``#RRGGBB``, ``#AARRGGBB`` (with alpha), a CSS name, or ``transparent``. ``--place``
puts the box at the center, an edge or corner, or a lower third. ``--line-spacing`` is a
multiple of the normal distance between lines (1 is normal), and ``--background-corner`` rounds
the background and border corners by that many pixels.

A text clip is an ordinary clip. Position, scale, rotation, opacity, timing, fades, motion
presets and effects all use the normal ``clip`` commands. Font size, padding, box width, and
letter and line spacing set the size of the text box, so they cannot be keyframed; to animate the size
of text, keyframe the clip's scale. Each clip can animate its own text colors and outline and
border widths with the same keyframe commands as any other property:

.. code-block:: bash

   openshot-cli clip keyframe list CLIP_ID
   openshot-cli clip keyframe set CLIP_ID --property text_color.alpha --space clip --unit seconds --at 0 --value 0
   openshot-cli clip keyframe set CLIP_ID --property text_color.alpha --space clip --unit seconds --at 1 --value 255

The animated properties are ``text_color``, ``text_background``, ``text_stroke`` and
``text_border`` (colors, with ``.red``, ``.green``, ``.blue`` and ``.alpha`` from 0 to 255),
plus ``text_stroke_width``, ``text_border_width`` and ``text_background_corner``. A clip's own animation wins over the
shared style, and ``text get`` lists which styles a clip animates. ``text reset CLIP_ID``
returns a clip to the default style, like **Text → Reset Style**; ``--keyframes-only`` just
removes the clip's own animation. To style many texts at once, select them with
``{"text":true}``:

.. code-block:: bash

   openshot-cli edit bulk --select '{"text":true}' --json '[{"method":"text.set","params":{"font_family":"Noto Sans","stroke_width":2}}]'

Applying a batch of edits
~~~~~~~~~~~~~~~~~~~~~~~~~

``edit apply --json @edits.json --expect-revision 12`` accepts up to 256 ordered
mutation commands, using dotted command names such as ``clip.set`` and
``effects.add`` (the older ``effect.add`` spelling also works)::

   [
     {"method":"project.profile.set","params":{"id":"builtin:01280x0720p0024_16-09"}},
     {"method":"file.import","as":"media","params":{"path":"/tmp/video.mp4"}},
     {"method":"clip.add","as":"shot","params":{"file_id":{"$ref":"media.id"},"layer":1000000,"start":10,"end":15}},
     {"method":"clip.fade","params":{"id":{"$ref":"shot.id"},"fade_in":0.5,"fade_out":0.5}}
   ]

The optional ``as`` binds a result for later commands in the same batch. A
``{"$ref":"shot.id"}`` object substitutes that result's field. References only
resolve earlier names; misspellings, forward references, and duplicate names
fail before live updates. The response includes ordered compact results and a
``refs`` map of actual IDs. All changes in a successful batch form one undo/redo step.
``project.profile.set`` also supports populated timelines. Opening, saving,
exporting, and inspection commands stay outside edit batches. Each
command's help lists its JSON parameter names, including the underscore spelling
of shell flags (for example, ``--fps-num`` becomes ``fps_num``).

Validating batches and handling errors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

All commands in edit batches preflight against a detached project before any live updates.
Invalid input leaves the live project and revision unchanged. Valid batches form
one undo group. Unexpected runtime failures can still produce partial changes;
errors report completed operations. A timeout does not imply cancellation, so
inspect state before retrying mutations.

Precise keyframe editing
~~~~~~~~~~~~~~~~~~~~~~~~

``clip``, ``transition``, and ``effects`` support ``keyframe list``, ``set``,
``remove``, ``clear``, ``transform``, ``copy``, and ``paste``. Property paths can
address nested curves, such as ``color.red`` on a Chroma Key effect. Editing a
nested curve preserves sibling channels. Run ``keyframe list ID`` without a
property to discover curve paths and point counts. Discovery and point listings
include trim/position timing context; point listings also report native frame
coordinates. ``get`` and ``properties`` expose the complete native object and
property metadata. Errors identify invalid fields, suggest close property names,
and explain coordinate corrections.

Choose coordinates explicitly with ``--space native|clip|timeline`` and
``--unit frames|seconds``. All frames use project FPS and are 1-based; seconds
start at zero and snap to the nearest frame. Native is the existing engine
keyframe coordinate (including source trim). Clip coordinates start at the
visible clip in-point; timeline coordinates start at project time zero. Attached
effects use their parent clip's timing. These coordinates address animation time,
not the media frames selected by a time-remapping curve.

For a clip positioned at 5 seconds, trimmed at 2 seconds, in a 30 fps project,
clip frame 1, timeline frame 151, and native frame 61 address the same point.
Position and trim snap independently to the project frame grid. Listing an entire
curve can include hidden points with nonpositive clip/timeline coordinates.
Legacy ``--frame`` still means a native frame and cannot combine with ``--at``.

Examples::

   openshot-cli clip keyframe set CLIP --property location_x --space clip --unit seconds --at 0 --value -1
   openshot-cli clip keyframe set CLIP --property location_x --space clip --unit seconds --at 1 --value 0 --interpolation bezier --handles '{"handle_left":{"X":0.2,"Y":0.9}}'
   openshot-cli effects keyframe set EFFECT --property color.red --space clip --at 31 --value 200
   openshot-cli clip keyframe list CLIP --property location_x --space clip --unit seconds
   openshot-cli clip keyframe transform CLIP --property location_x --space clip --start 1 --end 31 --scale 2 --offset 15
   openshot-cli clip keyframe copy TARGET --property location_x --source-id SOURCE --space clip --start 1 --end 31 --at 1 --mode merge
   openshot-cli clip keyframe remove CLIP --property location_x --space clip --at 31
   openshot-cli clip keyframe clear CLIP --property location_x --value 0

Interpolation, easing, and preview
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Interpolation describes the segment **into the destination point**.
Linear gives a steady rate between points. Constant holds the preceding
value until the destination, then jumps. Native default Bezier handles
ease at both ends of each segment. Adding an intermediate point can therefore
introduce an unintended slowdown; for one eased move, start with two endpoints.
Normally leave handles alone and choose a named preset for a specific style.

Discover the same easing presets used by the editor's menus, without a session::

   openshot-cli clip keyframe presets

Apply a preset to the incoming segment by setting its destination::

   openshot-cli clip keyframe set CLIP --property scale_x --space clip --unit seconds --at 0 --value 1
   openshot-cli clip keyframe set CLIP --property scale_x --space clip --unit seconds --at 4 --value 1.2 --easing ease-in-out

The easing option implies Bezier. It sets the previous point's outgoing handle
and the destination's incoming handle together, preserving other handles and
point values. An earlier point must exist. Do not combine it with custom
handles or a different interpolation. Presets work in batches and bulk templates.
The GUI preset **ease** ("Ease (Default)") differs from the native handles used
by plain Bezier interpolation. Back presets can overshoot endpoint values.

Inspect motion **before rendering**, using the native curve evaluator::

   openshot-cli clip keyframe preview CLIP --property scale_x --space clip --unit seconds --start 0 --end 4
   openshot-cli clip keyframe preview CLIP --property scale_x --space clip --unit seconds --at 2

Preview is read-only and works identically for effects and transitions.
It returns values and signed incoming_rate / outgoing_rate in property units
per second, calculated from immediately adjacent frames. These are finite
differences, not analytic derivatives. At native frame 1 the incoming rate is
null. Adjacent-frame rates can inspect outside the requested range.
Near-zero rates around an interior point reveal a pause; differing rates reveal
a speed change. Constant interpolation reveals a jump at the destination.

Without a range, preview spans the curve's first and last points. Ranges are
inclusive, with 17 evenly spaced samples by default (--count: 2–256).
Keyframes and their immediate neighbors inside the range are also included,
so a sparse sample count will not hide a keyframe stall. The maximum is 1024
samples; larger requests require a narrower range. --at samples one frame.
Preview reports requested-space coordinates and actual native frames;
seconds snap to project frames as they do for editing.

For advanced custom handles, both X and Y are normalized to the segment:
X is a fraction of its frame span; Y is a fraction of its value change.
The first control comes from the previous point's handle_right, the second
from the destination's handle_left. Native defaults are right=(0.5, 0),
left=(0.5, 1), with flat endpoint tangents. Changing handle_type alone does not
recalculate a continuous tangent through surrounding points. Existing handles
are preserved when moving a point.

Keyframe ranges are **inclusive**, unlike clip-selection time ranges. List emits
``points`` whose ``co.X`` values use the requested coordinate space/unit. Pass that
array to ``keyframe paste --json @points.json`` with the same space/unit, choosing
``--mode merge`` (replace matching frames, retain others) or ``--mode replace``
(replace the entire curve). ``copy`` accepts a source of the same object kind,
an optional ``--source-property``, and anchors the first selected source point at
``--at`` on the target. Other curves and source objects remain unchanged.

``transform`` applies a positive time scale around the first selected point, or
``--pivot``, followed by a signed ``--offset`` in the chosen unit. Frame snapping
that creates duplicate coordinates is rejected, including collisions with points
outside the selected range. Native coordinates must remain at least 1. Native
normalized Bézier handles and interpolation travel with each point; handles are
not absolute frame coordinates. ``set --handles`` changes only supplied handles.
Interpolation belongs to the destination point.

``clear`` retains a constant base point at native frame 1: specify ``--value`` or
sample the existing curve with ``--at`` (default native frame 1). Removing the last
point also retains a constant base, so subsequent keyframe additions remain valid.

Selecting and editing many clips
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Use ``edit apply --dry-run`` to validate a batch without changing the project.
Use ``edit bulk`` to repeat a small command template for each selected clip,
with all changes grouped into one undo step.

First preview a selection::

   openshot-cli edit select --json '{"layer":1000000,"start":5,"end":20,"effect":"Saturation"}'

Selectors support ``ids`` (unique clip ID array), ``layer``, ``start``/``end``
(timeline seconds), ``file_id``, ``effect`` (class name or alias), and ``text``
(``true`` selects text clips). Filters
combine with AND. Time ranges select clips overlapping ``[start, end)``; they do
not trim those clips. Results are ordered by position, layer, then ID, and include
IDs, summaries, lock state, and the current revision. Unknown IDs are errors.
An empty selector is rejected; use ``{"all":true}`` explicitly for every clip.
Selections are bounded to 256 clips and are never silently truncated.

For example, save this array as ``templates.json``::

   [
     {"method":"clip.adjust","params":{"values":{"position":{"add":2},"volume":{"multiply":0.5}}}},
     {"method":"effects.add","as":"look","params":{"type":"Saturation"}},
     {"method":"effects.keyframe.set","params":{"id":{"$ref":"look.id"},"property":"saturation","space":"clip","at":1,"value":0}},
     {"method":"effects.keyframe.set","params":{"id":{"$ref":"look.id"},"property":"saturation","space":"clip","at":31,"value":1.2}}
   ]

Validate it, then apply against the revision returned by selection::

   openshot-cli edit bulk --select '{"ids":["CLIP_A","CLIP_B"]}' --json @templates.json --dry-run
   openshot-cli edit bulk --select '{"ids":["CLIP_A","CLIP_B"]}' --json @templates.json --expect-revision 42

Existing-clip commands inherit ``id``; ``effects.add`` inherits ``clip_id``.
Omit these target parameters in templates. Effect edits must reference an earlier
named result. Names are local to each selected clip, so ``look.id`` refers to a
different effect for each clip. To change existing effect instances by explicit
ID, continue using ``edit apply``. The selected ID set is fixed before expansion;
changes to position or effects do not change the remaining targets.

``clip set`` applies absolute properties; ``clip adjust`` applies ``multiply``
then ``add`` to a numeric property or every value in a curve, preserving point
timing, interpolation, and normalized handles. It validates resulting values,
including nonnegative positions and volume. Structural fields remain protected.

Bulk operations reject empty selections, locked target tracks, and more than 256
expanded commands. Every expanded operation passes detached preflight before any
live edit. Dry runs do not reserve generated IDs. Use ``--expect-revision`` to
reject stale selections. Successful batches are one undo step, including after
save/reopen. GUI refreshes are batched. Unexpected runtime/listener errors can
still leave a partial transaction; the error reports completed operations and
undo reverts that group, as with ``edit apply``.

Profiles
--------

Discover profiles and match imported video without guessing IDs::

   openshot-cli profile list --search 720
   openshot-cli profile get builtin:01280x0720p0024_16-09
   openshot-cli profile match --file FILE_ID
   openshot-cli project profile get
   openshot-cli project profile set --from-file FILE_ID
   openshot-cli project profile set builtin:01280x0720p0024_16-09
   openshot-cli project profile set --json '{"width":640,"height":352,"display_ratio":{"num":20,"den":11},"fps":{"num":24000,"den":1001}}'

Catalog entries include stable IDs, descriptions, dimensions, rational FPS,
aspect ratios, and scan settings. List/search is paginated. Source matching
returns exact IDs and ranked alternatives with explicit differences. Setting
from a file requires an exact match unless ``--create-custom`` is specified;
it never silently chooses a nearby profile. Still images and audio-only sources
cannot define a video profile. Matching uses declared source FPS.

Custom settings are embedded in the saved project, including rational rates,
so reopening does not require installing a separate profile file. Project profile
changes use existing frame-rate conversion and one undo transaction. Undo/redo
restores clip timing and keyframes, including rounding-sensitive positions.
Export can independently select a profile without changing the project.

Saved undo history follows the editor's history-limit preference. The limit is
a soft action budget: all records belonging to retained transactions are kept,
so saving cannot truncate a large batch into a partial undo action. Zero disables
saved history. The separate 256-command batch request bound still applies.

Inspecting media and timelines
------------------------------

Inspect clip timing and track layout as text or JSON, or render images to review
composition and framing before exporting.

Timeline summaries
~~~~~~~~~~~~~~~~~~

Use a text view for a quick overview, or structured JSON for scripts and agents::

   openshot-cli timeline view --start 5 --end 12
   openshot-cli timeline view --start 5 --end 12 --format json
   openshot-cli timeline overview --start 5 --end 12

``timeline view`` defaults to compact text with used tracks, object IDs,
timeline/source intervals, stream state, gain, and gaps/overlaps. Use
``--detail summary|clips|full`` to choose detail, ``--format json`` for structured
context, and ``--start/--end``, ``--layer``, and pagination to focus the result.

``timeline overview`` supplies compact structured context without image bytes:
ordered IDs, sequence per track, timeline/source intervals, visible trim ranges,
frame coordinates, audio/video state, effects, and neighboring gaps or overlaps.
Intervals and frame spans are end-exclusive; frame numbers are one-based.
Source frames use source FPS; timeline and keyframe coordinates use project FPS.
Use ``--layer`` and ``--start/--end`` to focus, and ``--limit/--offset`` to paginate
(default 100, maximum 500 items). Time-remapped clips explicitly withhold inferred
source mappings. Use the returned revision with ``edit apply --expect-revision``.

Frame previews and contact sheets
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Render a single frame with ``thumbnail`` or sample a range with ``strip``.
Use ``file`` to inspect source media and ``timeline`` to inspect the composed
video, including effects and layered clips::

   openshot-cli file thumbnail FILE_ID --frame 301 --output /tmp/source.png
   openshot-cli file strip FILE_ID --start 10 --end 20 --count 12 --output /tmp/source-strip.png
   openshot-cli timeline thumbnail --frame 91 --output /tmp/timeline-frame.png
   openshot-cli timeline strip --start 0 --end 20 --count 12 --output /tmp/timeline-strip.png

Frame images default to 320x180 pixels; contact sheets default to 160x90 per
tile, plus timecode labels. Use ``--width`` and ``--height`` to choose output
pixels. Responses include exact sample/frame metadata. Contact sheets help
review composition across a sequence; use keyframe previews and rendered video
to assess motion between those samples.

Timeline images
~~~~~~~~~~~~~~~

Capture the editor's track layout, including its ruler, thumbnails, effects,
transitions, and waveforms::

   openshot-cli timeline view --start 0 --end 20 --pixels-per-second 80 --output /tmp/tracks.png

Specifying ``--output`` selects image format; ``--format image`` also selects it
explicitly. The capture does not change the editor's viewport or project.
Responses include clip IDs and timing alongside pixel bounds.

Images default to 1280 pixels wide, with height fitted to used tracks. Use
``--width``, ``--height``, or ``--all-tracks`` to adjust the layout, and
``--track-size`` or ``--track-label-width`` to adjust tracks and labels.
The start/end range determines zoom; ``--pixels-per-second`` instead derives
the width from the time range.

Audio waveform views
~~~~~~~~~~~~~~~~~~~~

Choose ``--mode waveforms`` to inspect audio envelopes on the timeline::

   openshot-cli timeline view --start 5 --end 12 --mode waveforms --output /tmp/waves.png

``--mode auto`` uses the existing cached waveform display; ``--mode thumbnails``
shows thumbnails instead. Waveforms represent source peak/RMS levels with clip
volume and timing adjustments, before audio effects or mixing.

Uncached waveform extraction decodes whole sources, limited to 600 seconds per
source and 1200 source-seconds per request. Narrowing the visible range does not
reduce that decoding work. For longer sources, generate waveforms in the editor
first or use thumbnail mode. Subsequent requests can reuse cached waveforms.

Image output and limits
~~~~~~~~~~~~~~~~~~~~~~~

Image paths must be new PNG files in existing directories. Images are limited
to 4096 pixels per axis, 8,388,608 total pixels, and 64 contact-sheet samples.
Requests beyond these limits fail before rendering.

Images are written to files; JSON responses include their paths and
``image_bytes`` sizes. Inspection waits for the image to finish, and large
requests can briefly occupy the editor.

Handling command timeouts
-------------------------

A request timeout does not mean a mutation failed. Use the operation ID in the
error with ``operation get ID`` before retrying. ``operation list`` discovers
recent mutations and their states, revisions, and outcomes. Completed operations
retain their results when small enough, including returned object IDs. Storage
is bounded to 100 operations and 4 MiB, with individual result bodies omitted
above 256 KiB. These records last only for the running session. They do not
restore projects after a crash or prevent a repeated command from applying the
same edit again. Export jobs have their own status/list commands.
