# Cut Video: Video Trimmer, Clip Cutter & Video to GIF (`data-done-right/video-trimmer`) Actor

Cut video clips from video file URLs: trim by start/end or duration, split into equal segments, or make a GIF or MP3. Fast stream copy or frame-accurate re-encode, mp4/webm/gif/mp3 out, signed download links. Direct files only, no YouTube.

- **URL**: https://apify.com/data-done-right/video-trimmer.md
- **Developed by:** [Data Done Right](https://apify.com/data-done-right) (community)
- **Categories:** Videos, Developer tools, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 clip createds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Cut Video: Video Trimmer, Clip Cutter & Video to GIF

This **video trimmer** lets you **cut video** clips out of direct video file URLs: give it a start and end time (or a start and a duration), or split a whole video into equal segments, and it returns a download link for every clip as **MP4, WebM, animated GIF or MP3**. It runs ffmpeg in the cloud, so there is nothing to install and no API key.

> **What it does not do.** It reads **direct video files** (mp4, mov, webm, mkv, avi) from a public URL. It **does not download from YouTube, Vimeo, TikTok, Instagram or other streaming sites** and rejects those links without charge. HLS/DASH playlists (.m3u8, .mpd) are not supported. It cuts, it does not merge, caption or edit. Use it only on videos you own or have the right to process.

**What you get**

- One dataset row per requested clip: source URL, clip number, start, end, measured duration, format, bytes, width, height, a signed download link, and a status or clear error.
- Frame-accurate cuts (re-encode) or an instant stream copy (keyframe-aligned).
- Failed clips are free, never stop the run, and say exactly why they failed.

### How to cut video clips from a URL

1. Paste one or more **direct video URLs** in `videoUrls`.
2. Add the **clips** you want from each video: `[{"start": 5, "end": 12}, {"start": "00:01:05.250", "duration": 4}]`. Times are seconds (`12.5`) or `HH:MM:SS.mmm`. Leave out `end` and `duration` to cut to the end of the video.
3. Run it. Open the dataset: every row has a `url` to the clip file. The `SUMMARY` record lists each video's duration, resolution and any notes.

### How to turn a video into a GIF

Set `"outputFormat": "gif"`, pick the moment with `start` and `end` (or `duration`, at most 60 s), and tune `gifFps` (default 12) and `gifWidth` (default 480 px). The GIF uses a generated colour palette, so it looks much better than a plain conversion. GIF files have no sound and can be large: lower the fps or width to shrink them.

### How to split a video into equal segments

Leave `clips` empty and set `segmentSeconds`, for example `30`. Every video becomes 30-second parts (a leftover tail under a quarter second is dropped). Each part is one clip and one charge, and `maxClipsPerRun` caps the total.

### How to cut a list of clips from a dataset

Put one row per clip in a dataset (`url`, `start`, `end` or `duration`, optional `name`), pick it in `cutListDatasetId`, and rows of the same URL are cut from a single download. You can also pass per-video clips in `videos`: `[{"url": "https://example.com/a.mp4", "clips": [{"start": 5, "end": 12}]}]`.

### Accurate or copy mode

| | `accurate` (default) | `copy` |
|---|---|---|
| How | re-encodes (H.264 + AAC for MP4, VP9 + Opus for WebM) | copies the compressed streams, no re-encode |
| Cut position | exactly at start and end | starts at the keyframe at or before `start`, ends at about `end` |
| Speed / quality | slower, one generation of re-compression (`crf` sets quality) | very fast, no quality loss |
| Output formats | mp4, webm, gif, mp3 | mp4, webm (GIF and MP3 are always re-encoded) |

In copy mode the clip can **begin earlier than you asked**: the row shows the real start in `actualStart` and the measured `durationSeconds`, and a note explains it. In a local test on a 5 s sample with sparse keyframes, copy-mode segments overlapped each other; use `accurate` for segments and exact cuts. If a stream copy is not possible (for example H.264 into WebM) the clip is re-encoded and the row says so.

### Input

| Field | Default | What it does |
|---|---|---|
| `videoUrls` | sample video | Direct video file links. |
| `clips` | 2 sample clips | Clips cut from every video: `start`, `end` or `duration`, optional `name`. |
| `videos` | empty | Per-video clips: `[{"url", "clips": [...]}]`. |
| `cutListDatasetId` | empty | Dataset with one row per clip. |
| `segmentSeconds` | 0 (off) | Split videos without explicit clips into equal parts. |
| `outputFormat` | `mp4` | `mp4`, `webm`, `gif` or `mp3`. |
| `mode` | `accurate` | `accurate` or `copy`. |
| `crf` | 23 | Quality for mp4/webm (16 best, 35 smallest). |
| `width` | 0 | Max width for mp4/webm (0 = original, never upscales). |
| `gifFps` / `gifWidth` | 12 / 480 | GIF frame rate and width. |
| `audioBitrateKbps` | 192 | MP3 bitrate. |
| `maxClipSeconds` | 600 | Longer clips are rejected (free). |
| `maxClipsPerRun` | 100 | Hard cap on clips per run (max 2000). |
| `maxVideoSizeMb` | 500 | Source size cap (max 2048). |
| `maxDurationMinutes` | 240 | Source duration cap. |
| `videoTimeoutSecs` | 600 | Per download and per clip. |
| `outputStoreName` | empty | Save clips in a named store that is kept until you delete it. |

Input example:

```json
{
  "videoUrls": ["https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"],
  "clips": [
    {"start": 0, "end": 2, "name": "intro"},
    {"start": 3, "duration": 1.5},
    {"start": 4, "end": 99}
  ],
  "outputFormat": "mp4",
  "mode": "accurate"
}
```

### Output

From a real local run of the input above (the third clip is longer than the 5.055 s video, so it is an error row and is not charged):

```json
{
  "sourceUrl": "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4",
  "videoIndex": 1, "clipIndex": 1, "name": "intro",
  "start": 0.0, "end": 2.0, "startTimecode": "00:00:00.000", "endTimecode": "00:00:02.000",
  "actualStart": 0.0, "durationSeconds": 2.002, "format": "mp4", "mode": "accurate",
  "bytes": 133989, "width": 960, "height": 540,
  "url": "https://api.apify.com/v2/key-value-stores/<store>/records/v01-clip-001.mp4",
  "key": "v01-clip-001.mp4", "status": "ok", "error": null, "note": null
}
```

```json
{
  "sourceUrl": "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4",
  "videoIndex": 1, "clipIndex": 3, "start": 4.0, "end": 99.0,
  "status": "error",
  "error": "end 00:01:39.000 is past the end of the video (duration 00:00:05.055)",
  "url": null
}
```

On Apify the `url` is a signed link that downloads without a token. `status` is `ok`, `error` or `skipped` (spending limit or `maxClipsPerRun` reached).

### Use with the Apify API, MCP, n8n and Make

Python:

```python
from apify_client import ApifyClient

run = ApifyClient("<APIFY_TOKEN>").actor("data-done-right/video-trimmer").call(run_input={
    "videoUrls": ["https://example.com/talk.mp4"],
    "clips": [{"start": "00:10:00", "end": "00:10:45", "name": "highlight"}],
    "outputFormat": "mp4"})
for row in ApifyClient("<APIFY_TOKEN>").dataset(run["defaultDatasetId"]).iterate_items():
    print(row["status"], row["url"] or row["error"])
```

JavaScript:

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<APIFY_TOKEN>' });
const run = await client.actor('data-done-right/video-trimmer').call({
  videoUrls: ['https://example.com/talk.mp4'], segmentSeconds: 60 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

In n8n or Make use the Apify node with the actor name and the same JSON. With Apify's MCP server (mcp.apify.com) an AI assistant can call it: "cut 10 to 20 seconds from this mp4 as a GIF". Only a video URL is needed.

### Use cases

- Cut highlight clips from your own recordings, webinars or product demos.
- Turn a moment of a screen recording into a GIF for docs, tickets or chat.
- Split a long recording into fixed-length parts for review or upload limits.
- Extract the audio of a clip as MP3 for transcription or podcasts.
- Cut a list of timestamps (from a transcript or a scene detector) into files in one run.

### How much does it cost

Pay per event: **$0.025 per clip created** (`clip-created`, $25 per 1,000). Failed and skipped clips, rejected links and the `SUMMARY` record are free. Plan discounts apply (Silver about 10%, Gold and higher about 20%).

- 1,000 clips: $25.
- 10,000 clips: $250.
- 100,000 clips: $2,500.

`maxClipsPerRun` caps a run, and if you set a maximum spend per run the Actor stops cleanly when it is reached and removes the clip it could not charge for. The cost is per clip whatever its length or format; Apify platform usage is included in this price.

### FAQ

#### Can it cut YouTube videos?

No. YouTube, Vimeo, TikTok, Instagram and other streaming-site links are rejected with a message and no charge. Use a direct link to a video file (for example a file on your own server or a public file host) that you have the rights to.

#### Is the cut frame-accurate?

In `accurate` mode yes: it re-encodes and cuts at your start and end. In `copy` mode no: it starts at the keyframe at or before your start, which can be seconds earlier, and reports the real start in `actualStart`.

#### What happens if a time is outside the video?

The Actor reads the real duration with ffprobe. A start past the end, an end more than 50 ms past the end, an end before the start, or an unreadable time gives an error row that names the problem and the video's duration. The other clips still run and nothing is charged for the failed one.

#### Which limits apply?

Source files up to 2 GB (default 500 MB) and 4 hours by default, clips up to 1 hour (default 10 minutes), GIFs up to 60 s, up to 2000 clips per run. Links to private or internal addresses are blocked. The whole source file is downloaded once per video, so a very large file takes time even for a short clip.

#### Which formats can it read and write?

It reads common video containers (mp4, mov, webm, mkv, avi, flv, mpeg, ts) with codecs ffmpeg can decode. It writes MP4 (H.264/AAC), WebM (VP9/Opus), GIF and MP3. A video without sound cuts fine to MP4, WebM and GIF; MP3 gives an error row for it.

#### How long are the clip links kept?

Clips live in the run's key-value store, which expires with your plan's retention period. Set `outputStoreName` to keep them in a named store until you delete it.

### Related tools

- **Video Frame Extractor: Keyframes, Scenes & Thumbnails for AI** pulls still images from the same videos.
- **All-in-One File Converter: PDF, Office, Images, Audio & Video** converts the clips to other formats.
- **Transcribe Audio to Text: Files, Podcasts & Video URLs** turns the audio of a clip into a transcript.

# Actor input Schema

## `videoUrls` (type: `array`):

Direct links to video files (mp4, mov, webm, mkv, avi). Example: https://example.com/clip.mp4. Each video gets the clips listed under 'Clips' (or equal segments if you set 'Segment length'). YouTube, Vimeo, TikTok and other streaming-site page links are rejected, with no charge.

## `clips` (type: `array`):

List of clips, each {"start": ..., "end": ...} or {"start": ..., "duration": ...}. Times are seconds (12.5) or HH:MM:SS.mmm ("00:01:05.250"). Omit end and duration to cut to the end of the video. Optional "name" labels the clip. Example: \[{"start": 0, "end": 2}, {"start": "00:00:03", "duration": 1.5, "name": "intro"}]

## `videos` (type: `array`):

Different clips per video: \[{"url": "https://example.com/a.mp4", "clips": \[{"start": 5, "end": 12}]}, ...]. Used in addition to 'Video URLs'. A URL listed in both uses the clips given here.

## `cutListDatasetId` (type: `string`):

Optional dataset with one row per clip: url (or videoUrl / sourceUrl), start, end or duration, and optional name. Rows of the same URL are cut from one download.

## `segmentSeconds` (type: `number`):

Split each video that has no explicit clips into equal parts of this length, for example 30 for 30-second segments. 0 = off. Each segment is one charged clip. Capped by 'Max clips per run'.

## `outputFormat` (type: `string`):

File type of each clip. GIF clips can be at most 60 s long. MP3 needs a video with an audio track.

## `mode` (type: `string`):

Accurate re-encodes and cuts exactly at your start and end. Copy does not re-encode (very fast, no quality loss) but starts at the keyframe at or before 'start', so a clip can begin up to a few seconds early. GIF and MP3 are always re-encoded.

## `crf` (type: `integer`):

Accurate mode, MP4/WebM. Lower = better quality and bigger files: 18 is near-lossless, 23 is the default, 28 is small. WebM uses a matching VP9 scale.

## `width` (type: `integer`):

Accurate mode, MP4/WebM: shrink clips to this width, keeping the aspect ratio. 0 = original size. Never upscales. Example: 1280.

## `gifFps` (type: `integer`):

GIF output only. 10 to 15 is a good balance of smoothness and file size.

## `gifWidth` (type: `integer`):

GIF output only: clips wider than this are shrunk to it, keeping the aspect ratio. Example: 480.

## `audioBitrateKbps` (type: `integer`):

MP3 output only. 128 is fine for speech, 192 to 320 for music.

## `maxClipSeconds` (type: `number`):

Longer clips are rejected with an error row and no charge. Example: 600 = 10 minutes.

## `maxClipsPerRun` (type: `integer`):

Hard cap on clips cut in one run (and so on what you can be charged). Clips past the cap are listed as skipped.

## `maxVideoSizeMb` (type: `integer`):

Source files above this size are rejected before the download finishes.

## `maxDurationMinutes` (type: `number`):

Longer source videos are rejected without charge. Example: 240 = 4 hours.

## `videoTimeoutSecs` (type: `integer`):

Applies separately to each download and to the cutting of each clip.

## `outputStoreName` (type: `string`):

Optional key-value store name. Named stores are kept until you delete them, while a run's own store expires after your plan's retention period. Example: my-clips.

## Actor input object example

```json
{
  "videoUrls": [
    "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
  ],
  "clips": [
    {
      "start": 0,
      "end": 2,
      "name": "intro"
    },
    {
      "start": 3,
      "duration": 1.5
    }
  ],
  "segmentSeconds": 0,
  "outputFormat": "mp4",
  "mode": "accurate",
  "crf": 23,
  "width": 0,
  "gifFps": 12,
  "gifWidth": 480,
  "audioBitrateKbps": 192,
  "maxClipSeconds": 600,
  "maxClipsPerRun": 100,
  "maxVideoSizeMb": 500,
  "maxDurationMinutes": 240,
  "videoTimeoutSecs": 600
}
```

# Actor output Schema

## `results` (type: `string`):

One row per requested clip with its download link, times, size and status, as JSON, CSV, Excel and more.

## `records` (type: `string`):

Key-value store records: the clip files, the run summary and notes on any problems.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "videoUrls": [
        "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
    ],
    "clips": [
        {
            "start": 0,
            "end": 2,
            "name": "intro"
        },
        {
            "start": 3,
            "duration": 1.5
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("data-done-right/video-trimmer").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "videoUrls": ["https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"],
    "clips": [
        {
            "start": 0,
            "end": 2,
            "name": "intro",
        },
        {
            "start": 3,
            "duration": 1.5,
        },
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("data-done-right/video-trimmer").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "videoUrls": [
    "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
  ],
  "clips": [
    {
      "start": 0,
      "end": 2,
      "name": "intro"
    },
    {
      "start": 3,
      "duration": 1.5
    }
  ]
}' |
apify call data-done-right/video-trimmer --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,data-done-right/video-trimmer"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/VzepcSYKsvKazGuZi/builds/92jRXdkeP64OadiqW/openapi.json
