# Video editing

> Trim, join, loop, caption, overlay and assemble video from an AI assistant with Nodaro's editing tools, several of which run without AI and cost no credits.

Source: https://nodaro.ai/docs/mcp/tools/video-editing

The **video editing tools** turn generated clips into a finished video: trim, join, loop and caption them, lay images over them, add or replace their sound, and assemble narrated videos. Most of them render with FFmpeg rather than an AI model, so they are fast, give the same result every time, and cost few or no credits. Every tool needs the `workflows:execute` permission and returns a job id.

| Tool | What it does | Credits |
| --- | --- | --- |
| [`trim_video`](#trim_video) | Cuts a clip by time, by frames, or at its cleanest loop point | 10 |
| [`combine_videos`](#combine_videos) | Joins clips, with transitions or a seamless smart cut | 30 |
| [`loop_video`](#loop_video) | Repeats a clip a number of times or to a length | 10 |
| [`extract_frame`](#extract_frame) | Takes one frame as an image | 10 |
| [`merge_video_audio`](#merge_video_audio) | Adds or replaces the sound of a video | 20 |
| [`add_captions`](#add_captions) | Burns subtitles or word-by-word captions into a video | 30, or 50 for animated or styled captions |
| [`overlay_images`](#overlay_images) | Shows images over a video at set times | 20 |
| [`still_to_video`](#still_to_video) | Turns one image and one audio track into a video | Free |
| [`slideshow`](#slideshow) | Turns 2 to 100 images and optional audio into a slideshow | Free |
| [`gif_to_video`](#gif_to_video) | Converts an animated GIF to MP4 | Free |
| [`assemble_narrated_video`](#assemble_narrated_video) | Fits voice blocks over their clips in one narrated video | 40 |

## `trim_video`

Cuts a clip. Choose one of three modes: by time, by frames, or at the cleanest loop point. It works like the [Trim Video](https://nodaro.ai/docs/nodes/video/trim-video) node.

**Permission:** `workflows:execute`. **Credits:** 10.

| Parameter | Type | Notes |
| --- | --- | --- |
| `video_url` or `video_asset_id` | string | The source clip. |
| `start_time`, `end_time` | number | By time: the window to keep, in seconds. `end_time` must be later than `start_time`. |
| `trim_start_frames`, `trim_end_frames` | integer | By frames: frames to drop from the start or the end. They override the times. Useful when exact frames matter, as with VEO 3.1 clips at 24 fps. |
| `smart_loop_cut` | boolean | Finds the last frame that best matches frame 0 and cuts there, for a clean loop. It overrides the other modes. |
| `smart_loop_cut_lookback` | integer | How many final frames to compare, from 2 to 64. Default `16`. |
| `silent` | boolean | Removes the sound. Default `false`. |

**Returns:** a job id. A smart loop cut reports the chosen frame in `output_data.smartLoopCut`.

## `combine_videos`

Joins two or more clips into one video, with a transition or a seamless smart cut. It works like the [Combine Videos](https://nodaro.ai/docs/nodes/video/combine-videos) node.

**Permission:** `workflows:execute`. **Credits:** 30.

| Parameter | Type | Notes |
| --- | --- | --- |
| `videos` | array | **Required.** At least 2 clips, each `{ url }` or `{ asset_id }`, in play order. |
| `transition` | string | The transition between clips, such as `cut`, `fade`, `dissolve`, `dip-to-black`, `wipe-left`, `slide-up`, `circle-open`, `pixelize` or `zoom-in`. The tool lists about 50. |
| `transition_duration` | number | From 0 to 5 seconds. |
| `audio_mode` | string | `keep`, `crossfade` or `remove`. |
| `audio_crossfade_duration` | number | A crossfade of the sound only, from 0 to 5 seconds. The picture is not changed. By default it follows `transition_duration`. |
| `audio_crossfade_curve` | string | With `crossfade`: `linear`, `equal-power`, `smooth`, `logarithmic` or `exponential`. |
| `smart_cut` | boolean | Compares the last frames of each clip with the first frames of the next and cuts where they match. Seamless for clips generated from the previous clip's last frame. Nodaro Cloud only. |
| `smart_cut_mode` | string | `best-pair` (default), `preroll-keep-prev` or `preroll-keep-next`, which decide which side of an overlap is kept. |
| `smart_cut_frames_prev`, `smart_cut_frames_next` | integer | The search window at the end and at the start of each clip, from 1 to 24 frames. Default `8`. |

**Returns:** a job id.

## `loop_video`

Repeats a clip a number of times, or until it reaches a length. It works like the [Loop Video](https://nodaro.ai/docs/nodes/video/loop-video) node.

**Permission:** `workflows:execute`. **Credits:** 10.

| Parameter | Type | Notes |
| --- | --- | --- |
| `mode` | string | **Required.** `repeat` for a number of copies, or `duration` to loop to a length and trim there. |
| `video_url` or `video_asset_id` | string | The source clip. |
| `repeat_count` | integer | For `repeat`: 2 to 20 copies. |
| `target_duration` | number | For `duration`: the length, from 1 to 300 seconds. |
| `smart_cut_before_repeat` | boolean | Trims the clip to its cleanest loop point before repeating it, so no seam shows at any repeat. Recommended for VEO 3.1 clips made from a first and a last frame. |
| `smart_cut_lookback` | integer | How many final frames to compare, from 2 to 64. Default `16`. |

**Returns:** a job id.

## `extract_frame`

Takes one frame of a video as an image: the first, the last, or the one at a time you choose. Use the last frame of a clip as the first frame of the next one to chain shots. It works like the [Extract Frame](https://nodaro.ai/docs/nodes/image/extract-frame) node.

**Permission:** `workflows:execute`. **Credits:** 10.

| Parameter | Type | Notes |
| --- | --- | --- |
| `video_url` or `video_asset_id` | string | The source video. |
| `mode` | string | `first`, `last` or `timestamp`. |
| `time_seconds` | number | The time of the frame. Passing it implies `timestamp`. |

**Returns:** a job id. The result is an image.

## `merge_video_audio`

Adds sound to a video or replaces it: a voiceover, a soundtrack or a dub. It works like the [Merge Video & Audio](https://nodaro.ai/docs/nodes/video/merge-video-audio) node.

**Permission:** `workflows:execute`. **Credits:** 20.

| Parameter | Type | Notes |
| --- | --- | --- |
| `video_url` or `video_asset_id` | string | The video. |
| `audio_url` or `audio_asset_id` | string | One audio track. |
| `audio_tracks` | array | Several tracks instead, each `{ url, start_time, volume }`: when it starts in the video, in seconds, and its volume from 0 to 200, where 100 is the original. |
| `voiceover_volume` | number | The new audio's volume, from 0 to 200. Default `100`. |
| `keep_original_audio` | boolean | Keeps the video's own sound under the new audio. Default `true`. |
| `background_volume` | number | The volume of the video's own sound, from 0 to 200. Default `30`. |

**Returns:** a job id.

## `add_captions`

Burns captions into a video: one static subtitle block, or animated captions timed word by word. It works like the [Add Captions](https://nodaro.ai/docs/nodes/video/add-captions) node, which documents every style and option.

**Permission:** `workflows:execute`. **Credits:** 30 for a plain subtitle; 50 for an animated style, or for a subtitle with styling options.

| Style | What it shows |
| --- | --- |
| `subtitle` (default) | A static block. With `text`, that text is burned as it is for the whole video and never replaced by a transcript. Without `text`, the speech is captioned. |
| `word-highlight`, `karaoke`, `bouncy` | One line at a time, timed word by word, with the spoken word highlighted |
| `tiktok-words` | Short pages of words. A page never crosses the end of a sentence or a pause, and stays at most 1.5 seconds. |
| `word-pop` | One word at a time. Each word stays until the next one starts, at most 1.5 seconds. |

| Parameter | Type | Notes |
| --- | --- | --- |
| `video_url` or `video_asset_id` | string | The video. |
| `style` | string | One of the styles above. Default `subtitle`. |
| `text` | string | On `subtitle`, the exact text to burn. On an animated style, only a fallback when transcription returns nothing or is off. |
| `captions` | array | Your own timed words, one `{ text, startMs, endMs }` per word, in milliseconds. This is the shape `transcribe` returns. |
| `auto_transcribe` | boolean | Transcribes the video's speech to time the words. |
| `transcribe_provider` | string | `incredibly-fast-whisper` (default), `elevenlabs-stt` or `whisper`. Animated styles need word timings, so `whisper` is refused when transcription is their only source. |
| `look` | string | `outline` (Montserrat 900, uppercase, black outline, yellow spoken word) or `clean`. Unset, it is `outline` on animated styles and `clean` on `subtitle`. |
| `font_family`, `font_size`, `font_weight`, `color`, `uppercase` | | The type. `Rubik`, `Heebo`, `Cairo` and `Tajawal` cover Hebrew and Arabic. |
| `stroke_color`, `stroke_width`, `background_color` | | The outline and the background box. |
| `position`, `position_y` | | `bottom`, `top` or `center`, or the block's center as a percent of the height. About `65` sits below a face and above an app's buttons. |
| `max_words_per_line` | integer | 1 to 20 words per line, or per page of `tiktok-words`. `1` or `2` gives the punchy social look. Unset fits the width. No effect on `word-pop`. |
| `highlight_color`, `animate` | | Animated styles only: the color of the spoken word, and `false` to stop the word motion. |
| `segments` | array | Different treatments for time ranges of the same video, each with `start_ms`, `end_ms`, its own options and optionally its own text. Ranges must not overlap. |

The styling options also work on `subtitle`, but a styled subtitle bills at the animated price. For your own corrected transcript, map the words from [`transcribe`](https://nodaro.ai/docs/mcp/tools/audio#transcribe) into `captions`, one entry per word.

**Returns:** a job id.

## `overlay_images`

Shows 1 to 20 images over a video, each for a time range: a logo, a product shot, a screenshot or a card. No AI model runs, and the video's sound is kept as it is. It works like the [Video Overlay](https://nodaro.ai/docs/nodes/video/video-overlay) node.

**Permission:** `workflows:execute`. **Credits:** 20 on Nodaro Cloud.

| Parameter | Type | Notes |
| --- | --- | --- |
| `video_url` or `video_asset_id` | string | The base video, for example a `combine_videos` result. |
| `layers` | array | **Required.** 1 to 20 layers. |
| `output_aspect` | string | Renders onto a `16:9`, `9:16`, `1:1` or `4:5` canvas instead of the video's own size, with `base_fit` (`cover`, the default, or `contain`) and a `background_color`. |

Each layer takes an image, `url` or `asset_id`, which can be an [`image_overlay`](https://nodaro.ai/docs/mcp/tools/image#image_overlay) result, and these fields:

| Layer field | Notes |
| --- | --- |
| `start`, `end` | **`start` is required.** The time range in seconds. Without `end`, the layer stays to the end of the video. |
| `preset` | `card` (centered, fitted into 78% by 60% of the frame), `corner-badge` (18% wide, near the `corner`, bottom right by default) or `full-frame`. |
| `anchor`, `x`, `y`, `width`, `height`, `fit` | An exact box, in the same percent units as `image_overlay`. A box field overrides the preset. With neither, the layer is a corner badge. |
| `opacity`, `animate`, `z_index` | Opacity, a short fade and scale in and out (on by default), and the stacking order. |

Layers past the end of the video are cut or skipped, and an animated image shows its first frame; the job output lists both as `warnings`. A layer id that cannot be found is refused before anything runs, with its position in the list. Pass the result's job id to `add_captions` as `video_asset_id` to caption it next.

**Returns:** a job id.

## `still_to_video`

Turns one image and one audio track into a video, rendered locally without an AI model. The video lasts exactly as long as the audio. Use it for narrated slides, a cover art video for a song, or a still moment inside a longer edit. For AI motion, use [`animate_image`](https://nodaro.ai/docs/mcp/tools/video#animate_image). It works like the [Still to Video](https://nodaro.ai/docs/nodes/video/still-to-video) node.

**Permission:** `workflows:execute`. **Credits:** free.

| Parameter | Type | Notes |
| --- | --- | --- |
| `image_url` or `image_asset_id` | string | The image. |
| `audio_url` or `audio_asset_id` | string | The audio, which sets the length. |
| `motion` | string | `none` (default), `zoom-in`, `zoom-out`, `pan-left`, `pan-right` or `ken-burns`. |
| `intensity` | integer | The motion strength, from 1 to 10. Default `3`. |
| `resolution` | string | `720p`, `1080p` (default) or `4K`. 4K with motion is slower. |
| `aspect_ratio` | string | `16:9` (default), `9:16`, `1:1` or `4:3`. |
| `fps` | number | `24` or `30` (default). |
| `fit`, `pad_color` | string | `cover` (default, crops to fill) or `contain` (adds bars in `pad_color`, black by default). |

**Returns:** a job id.

## `slideshow`

Turns 2 to 100 images and one optional audio track into a slideshow video, rendered locally without an AI model. It works like the [Slideshow](https://nodaro.ai/docs/nodes/video/slideshow) node.

**Permission:** `workflows:execute`. **Credits:** free.

| Parameter | Type | Notes |
| --- | --- | --- |
| `image_urls` or `image_asset_ids` | array | 2 to 100 images, in order. |
| `audio_url` or `audio_asset_id` | string | Optional. With audio, the video lasts exactly as long as the audio, and the slides share it equally. |
| `image_durations` | array | Seconds for each slide, or `null` for automatic. With audio, totals that do not match are scaled to fit, and the job output says by how much. |
| `per_image_duration` | number | Without audio: seconds per slide, from 0.5 to 60. Default `3`. |
| `transition`, `transition_duration` | | A transition such as `cut`, `fade`, `dissolve`, `dip-to-black` or `wipe-left`, and its length up to 5 seconds. An unknown transition falls back to `cut`. |
| `motion`, `intensity` | | `none`, `zoom-in`, `zoom-out`, `ken-burns` or `alternate`, which switches the zoom on every slide, with a strength from 1 to 10. |
| `resolution`, `aspect_ratio`, `fps`, `fit`, `pad_color` | | As on `still_to_video`. |

For one image, use `still_to_video`. For AI motion between images, use `animate_image`.

**Returns:** a job id.

## `gif_to_video`

Converts an animated GIF into an H.264 MP4, rendered locally without an AI model. Use it to pass a GIF as a motion reference to a video model that does not accept GIFs, such as Seedance. It works like the [Gif to Video](https://nodaro.ai/docs/nodes/video/gif-to-video) node.

**Permission:** `workflows:execute`. **Credits:** free.

| Parameter | Type | Notes |
| --- | --- | --- |
| `gif_url` or `gif_asset_id` | string | The GIF. |
| `loop_to_minimum` | boolean | Loops a short GIF up to `target_duration`. Default `true`. A GIF that does not loop cleanly plays forward and backward instead, so no jump appears. |
| `target_duration` | number | The loop length, from 2 to 8 seconds. Default `3`. |
| `interpolate` | boolean | Adds frames for smooth 24 fps motion. Default `true`. `false` keeps the GIF's original timing. |
| `alpha_background` | string | The background of a transparent GIF: `white` (default) or `black`. |

**Returns:** a job id. A GIF with one frame becomes a short still clip.

## `assemble_narrated_video`

Fits ordered blocks of a clip and a voice into one narrated video. When a voice is shorter than its clip, it is centered with silence around it. When it is longer, the clip slows down to fit, up to a limit, and then holds its last frame. The voice is never cut. It works like the [Assemble Narrated Video](https://nodaro.ai/docs/nodes/video/assemble-narrated-video) node.

**Permission:** `workflows:execute`. **Credits:** 40.

| Parameter | Type | Notes |
| --- | --- | --- |
| `blocks` | array | **Required.** 1 to 60 blocks in play order, each with a video (`video_url` or `video_asset_id`) and an optional voice (`audio_url` or `audio_asset_id`). The clips must contain no dialogue. |
| `voice_volume` | number | The voice's loudness in percent, from 0 to 200. Default `100`. |
| `clip_audio_volume` | number | The clips' own sound under the voice, from 0 to 200. Default `40`. |
| `max_slowdown` | number | How much a clip may slow down for a long voice, from 1 to 2. Default `1.5`. |
| `trim_start_frames`, `trim_end_frames` | integer | Frames trimmed from the start of every block after the first, and from the end of every block before the last, for seamless joins. Up to 120. Default `0`. |

**Returns:** a job id. The `video-explainer` recipe finishes with this tool; see [Content recipes](https://nodaro.ai/docs/mcp/recipes).

## Frequently asked questions

### Which Nodaro video editing tools are free?

still_to_video, gif_to_video and slideshow cost no credits, because they render locally without an AI model. The other editing tools have small fixed prices, such as 10 credits to trim or 30 to combine clips.

### How do I add TikTok-style captions to a video?

Call add_captions with a kinetic style such as word-highlight or tiktok-words. Leave auto_transcribe on, and Nodaro transcribes the speech and times each word. An unset look renders bold uppercase words with the spoken word in yellow.

### How do I put a logo on a video?

Call overlay_images with the video and a layer that holds the logo. Without a placement, the layer becomes a corner badge in the bottom right. Set start and end to show it only for part of the video.

### How do I join clips without a visible jump?

Use combine_videos with smart_cut set to true. It compares the last frames of each clip with the first frames of the next and cuts where they match best, which suits clips generated from each other's last frame.
