# Video to Shorts - Vertical 9:16 Clips with AI Captions (`ent-e/video-to-shorts-captions`) Actor

Video to shorts API. Send a long video URL and timestamps, get a vertical 9:16 MP4 for TikTok, Reels or YouTube Shorts with burned-in word-by-word captions, a thumbnail and an SRT. Blur, crop or pad reframing. Batch clips. MCP, n8n, Make, API. Failed clips free.

- **URL**: https://apify.com/ent-e/video-to-shorts-captions.md
- **Developed by:** [ENT-E](https://apify.com/ent-e) (community)
- **Categories:** Videos, Social media, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $80.00 / 1,000 short createds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## Video to Shorts - Vertical 9:16 Clips with AI Captions

The Video to Shorts API from ENT-E turns a long video URL into vertical 9:16 clips for TikTok, Instagram Reels and YouTube Shorts, with burned-in animated captions. One call per short, with no editor, no timeline and no manual subtitles.

### Video to shorts API: long video in, vertical 9:16 short with captions out

Tell it where the highlight starts and ends. The Actor then:

1. reframes the clip to 9:16 (`blur`, `crop` or `pad`),
2. transcribes the speech with Whisper,
3. burns in word-by-word captions with the current word highlighted,
4. returns a ready-to-post 30 fps H.264/AAC MP4, plus a JPEG thumbnail and an `.srt` of the captions.

The Actor doesn't pick highlights for you. You (or your agent, from a transcript) choose `startSeconds` and `endSeconds`; it does the editing work.

### What you get

- **Vertical video 9:16** at 1080×1920 (default) or 720×1280.
- **Three reframe modes:**
  - `blur` (default): the full frame sits over a blurred copy of itself, so nothing is cut off. Good for slides, gameplay and two-person shots.
  - `crop`: fills the screen. Aim it with `cropPosition` from 0 (left) to 1 (right).
  - `pad`: the full frame with solid bars in `padColor`.
- **Burned-in captions included in the price.** Pick a preset `captionStyle` (`bold`, `pop`, `boxed` or `minimal`), then fine-tune `font`, `fontSize`, colors, outline, box, uppercase, words per caption, karaoke highlighting and position. With `captionPosition: "auto"`, captions sit in the free area under the video in blur/pad mode, so they don't cover faces.
- **Six bundled fonts:** `poppins`, `anton`, `bebas`, `luckiest`, `archivo` and `dejavu`.
- **Up to 180 s per short** (`maxClipSeconds`, default 60).
- **Batch** with `clips`: many highlights from one long video in one run, each with its own settings.
- **Failed clips are free.**

### Use cases

#### Long video to TikTok, Reels and YouTube Shorts

```json
{
  "url": "https://example.com/webinars/q3-product-update.mp4",
  "startSeconds": 754,
  "endSeconds": 812
}
```

Defaults: blur reframe, 1080×1920, `bold` captions, auto language.

#### Repurpose podcast clips into shorts (batch)

Cut several highlights from one episode in one run. Top-level settings apply to every clip, and each clip can override them.

```json
{
  "mode": "crop",
  "captionStyle": "pop",
  "language": "en",
  "clips": [
    { "url": "https://example.com/podcast/ep-88.mp4", "startSeconds": 312, "endSeconds": 355 },
    { "url": "https://example.com/podcast/ep-88.mp4", "startSeconds": 1490, "endSeconds": 1548, "cropPosition": 0.7 },
    { "url": "https://example.com/podcast/ep-88.mp4", "startSeconds": 2710, "endSeconds": 2766, "cropPosition": 0.3 }
  ]
}
```

Use `cropPosition` to keep each speaker in frame when they sit left or right of center.

#### Burned-in captions with your brand style

```json
{
  "url": "https://example.com/videos/tutorial-12.mp4",
  "startSeconds": 60,
  "endSeconds": 105,
  "captionStyle": "bold",
  "font": "anton",
  "textColor": "#FFFFFF",
  "highlightColor": "#3DFF5A",
  "wordsPerCaption": 2,
  "captionPosition": "center"
}
```

#### Vertical video 9:16 without captions (reframe and trim only)

```json
{
  "url": "https://example.com/footage/b-roll-wide.mov",
  "startSeconds": 10,
  "endSeconds": 25,
  "mode": "pad",
  "padColor": "#FFFFFF",
  "captions": false
}
```

#### Agent workflow: transcript, then pick moments, then render shorts

1. Get a timed transcript of the long video with [Video to Captions](https://apify.com/ent-e/video-to-captions-whisper) (or any Whisper-based transcription tool).
2. Let your LLM choose 3–5 moments with start and end times.
3. Send them here as `clips`.

### Pricing (pay per event)

| Event | Price |
|---|---|
| Short created (per successful short; includes captions, thumbnail and SRT) | $0.08 |
| Clip minute (per started minute of **output** video) | $0.12 |
| Actor start (per run, one event per GB of memory, minimum one; default 4 GB = 4 events) | $0.00005 per event |

There are no platform usage fees on top. Captions cost the same with every `model`. Failed clips are free. You pay for the short you get, not for the length of the source video.

**Worked example: one podcast episode, five shorts**

- Each short is 30–60 s, which is 1 started minute: $0.08 + $0.12 = **$0.20 per short**.
- Five shorts: **$1.00**, plus $0.0002 actor start for the batch run.
- Ten 60-second shorts: **$2.00**.

**Longer shorts:**

- A 61–120 s short: $0.08 + 2 × $0.12 = **$0.32**.
- A full 180 s short: $0.08 + 3 × $0.12 = **$0.44**.

Billing rounds up to the next started minute of output, ignoring up to 0.5 s of encoder padding, so a 60 s short (even if it renders at 60.03 s) is 1 minute and a 61 s short is 2.

### Input reference

| Field | Type | Default | Notes |
|---|---|---|---|
| `url` | string | (none) | Direct link to one video file, up to 4 GB (not a YouTube/TikTok page) |
| `clips` | array | (none) | Batch. Items are `{ "url": ..., <any setting below> }` or plain URL strings. |
| `startSeconds` | number | `0` | Start of the short in the source (decimals allowed) |
| `endSeconds` | number | (none) | End of the short. If empty, the clip runs to the end of the source or to `maxClipSeconds`. |
| `maxClipSeconds` | integer | `60` | Hard cap on output length, 5–180 |
| `mode` | string | `blur` | `blur`, `crop`, `pad` |
| `cropPosition` | number | `0.5` | `crop` only. 0 = left, 1 = right. |
| `blurStrength` | integer | `20` | `blur` only. 4–80. |
| `padColor` | string | `#000000` | `pad` only |
| `resolution` | string | `1080x1920` | `1080x1920` or `720x1280` |
| `quality` | string | `standard` | `high` (CRF 18), `standard` (CRF 21), `small` (CRF 25) |
| `captions` | boolean | `true` | Burn in captions and return an SRT |
| `model` | string | `small` | `base`, `small`, `large-v3-turbo` (same price) |
| `language` | string | `auto` | ISO 639-1 code or `auto` |
| `captionStyle` | string | `bold` | `bold`, `pop`, `boxed`, `minimal` |
| `font` | string | from style | `poppins`, `anton`, `bebas`, `luckiest`, `archivo`, `dejavu` |
| `fontSize` | integer | from style | 20–200 px at 1080 width |
| `textColor`, `highlightColor`, `outlineColor` | string | from style | Hex colors |
| `outlineWidth` | number | from style | 0–20 |
| `captionBox` | boolean | from style | Semi-transparent box behind the text |
| `uppercase` | boolean | from style | |
| `wordsPerCaption` | integer | from style | 1–12 words on screen |
| `highlightWords` | boolean | from style | Karaoke-style current-word highlight |
| `captionPosition` | string | from style | `auto`, `bottom`, `center`, `top` |

You must provide either `url` or `clips`.

### Output reference

Each short produces one dataset row. Here is a real 60 s test render (URLs and transcript shortened):

```json
{
  "sourceUrl": "https://example.com/webinars/q3-product-update.mp4",
  "startSeconds": 60,
  "mode": "blur",
  "resolution": "1080x1920",
  "videoUrl": "https://api.apify.com/v2/key-value-stores/<storeId>/records/4e353b7c87de.mp4?signature=<sig>",
  "thumbnailUrl": "https://api.apify.com/v2/key-value-stores/<storeId>/records/4e353b7c87de.jpg?signature=<sig>",
  "captionsSrtUrl": "https://api.apify.com/v2/key-value-stores/<storeId>/records/4e353b7c87de.srt?signature=<sig>",
  "status": "ok",
  "durationSeconds": 60.03,
  "endSeconds": 120,
  "width": 1080,
  "height": 1920,
  "sizeBytes": 37856647,
  "captioned": true,
  "captionStyle": "bold",
  "language": "en",
  "captionCount": 47,
  "transcript": "…",
  "sourceDurationSeconds": 580.174,
  "billedMinutes": 1
}
```

If the clip has no speech, or no audio stream, the short still renders without captions, and the row says so in `warning`. A failed clip returns `sourceUrl`, `startSeconds`, `mode`, `resolution`, `status: "error"` and `error`.

### Use it from AI agents and automations

**MCP (Claude, Cursor, other MCP clients).** Add `https://mcp.apify.com?tools=ent-e/video-to-shorts-captions` as a remote MCP server and sign in with OAuth. Your agent passes a URL and timestamps and gets back `videoUrl`. A 60 s short at 1080×1920 takes about 2–4 minutes to render, and 720×1280 is roughly twice as fast, so the agent should wait for the run to finish before reading the dataset.

**n8n.** Use the Apify node (`@apify/n8n-nodes-apify`). Use **Run an Actor**, then **Get Dataset Items**, and post `videoUrl` to your scheduler or storage. **Run an Actor and get dataset** also works for a single short.

**Make.** Use the Apify app. Renders usually take longer than the 120 s sync wait, so:

1. Run **Run an Actor** asynchronously.
2. Add the **Watch Actor Runs** trigger.
3. Then **Get Dataset Items**.

**Zapier.** Use the Apify app:

- **Run Actor and Fetch Results** for a single short (under 5 minutes).
- For batches, use **Run Actor**, then the **Finished Actor Run** trigger and **Fetch Dataset Items**.

**API.**

```bash
curl -X POST "https://api.apify.com/v2/acts/ent-e~video-to-shorts-captions/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/webinars/q3-product-update.mp4","startSeconds":754,"endSeconds":812}'
## then: GET https://api.apify.com/v2/datasets/<defaultDatasetId>/items?token=$APIFY_TOKEN
```

### FAQ

**Does it find the best moments automatically?**
No. You choose `startSeconds` and `endSeconds`. That keeps the output predictable and the cost fixed. To automate it, get a transcript first, let an LLM pick the moments, then send them as `clips`.

**Does it track faces?**
No. There's no automatic face tracking. Use `crop` with `cropPosition` to frame a speaker, or `blur` to keep the whole frame visible.

**Can I paste a YouTube or TikTok link?**
No. The Actor needs a direct link to the video file (cloud storage, CDN or file host), up to 4 GB.

**Which languages do the captions support?**
Speech is transcribed by Whisper in the spoken language (auto-detected or set with `language`). The bundled fonts cover Latin scripts. Use `dejavu` for Cyrillic and Greek. CJK, Arabic and Indic scripts aren't supported yet. For those, set `"captions": false` and add subtitles in your own editor or player.

**How long can a short be?**
5 to 180 seconds (`maxClipSeconds`). The default of 60 s fits the common TikTok, Reels and Shorts length. You pay per started minute of output (up to 0.5 s of padding is ignored), so 60 s costs less than 61 s.

**Am I charged if a clip fails?**
No. A bad URL, an input without video, or an invalid setting returns a free error row. A short that renders without captions (because the clip has no speech) is still a delivered short and is billed normally.

### ENT-E media toolkit

Every tool takes a public file URL, needs nothing installed, charges per file, doesn't charge for failures, and works from MCP, n8n, Make, Zapier and the API.

- **[Media Converter for AI Agents (FFmpeg)](https://apify.com/ent-e/media-converter-ffmpeg)**: audio/video to MP3, Whisper-ready 16 kHz WAV, MP4, WebM, GIF or thumbnails, with trim and batch.
- **[Image Converter & Compressor](https://apify.com/ent-e/image-converter-compressor)**: convert to WebP or AVIF, compress to a target file size, resize, strip EXIF/GPS.
- **[Podcast Loudness Normalizer](https://apify.com/ent-e/podcast-loudness-normalizer)**: normalize to -16, -14, -23 or -24 LUFS with a true-peak ceiling, plus a before/after report.
- **[Video to Captions](https://apify.com/ent-e/video-to-captions-whisper)**: SRT, VTT, TXT and word-level JSON from any video or audio URL, with Whisper.
- **[Video to Shorts](https://apify.com/ent-e/video-to-shorts-captions)**: vertical 9:16 clips with burned-in animated captions for TikTok, Reels and Shorts.

# Actor input Schema

## `url` (type: `string`):

Direct, publicly downloadable URL of ONE video file (http/https; redirects followed). Any FFmpeg-readable format: mp4, mov, mkv, webm, avi... Max 4 GB. Must be the media file itself, not a YouTube/TikTok/web page URL. Horizontal (16:9), square or already-vertical sources all work. Example: https://download.samplelib.com/mp4/sample-30s.mp4

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

Optional batch: make several shorts in one run (e.g. several highlights from one long video). Array of objects {"url": string (required), plus any of startSeconds, endSeconds, maxClipSeconds, mode, cropPosition, blurStrength, padColor, resolution, captions, model, language, captionStyle, font, fontSize, textColor, highlightColor, outlineColor, outlineWidth, captionBox, uppercase, wordsPerCaption, highlightWords, captionPosition, quality to override the top-level value for that clip}. Plain URL strings are accepted. Example: \[{"url":"https://example.com/podcast.mp4","startSeconds":125,"endSeconds":170},{"url":"https://example.com/podcast.mp4","startSeconds":900,"endSeconds":955,"captionStyle":"pop"}]

## `startSeconds` (type: `number`):

Where the short starts in the source video, in seconds (decimals allowed, e.g. 75.5 = 1:15.5). Default 0 (beginning). Use with endSeconds to cut a highlight out of a long video.

## `endSeconds` (type: `number`):

Where the short ends in the source video, in seconds. Leave empty to run until the source ends or maxClipSeconds is reached, whichever comes first. Must be greater than startSeconds.

## `maxClipSeconds` (type: `integer`):

Hard cap on the output length; the clip is cut at startSeconds + maxClipSeconds even if endSeconds is later. 60 fits YouTube Shorts classic limit and Instagram Reels/TikTok recommendations; up to 180 (3 minutes, the current YouTube Shorts maximum). Default 60.

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

How a horizontal video becomes 9:16. 'blur' (default): the whole original frame is shown in the middle over a blurred, zoomed copy of itself - nothing is cut off, best for slides, gameplay, wide shots, multiple speakers. 'crop': scales to fill the full 9:16 screen and cuts the sides - best for a single centered speaker/talking head; steer it with cropPosition. 'pad': whole frame with solid bars (padColor) above and below.

## `cropPosition` (type: `number`):

Only for mode='crop': horizontal position of the 9:16 window inside the wider source, 0 = keep the far left, 0.5 = center (default), 1 = far right. Example: speaker sits on the right third -> 0.7.

## `blurStrength` (type: `integer`):

Only for mode='blur': how blurred the background copy is. 4 = subtle, 20 = default, 60+ = very soft.

## `padColor` (type: `string`):

Only for mode='pad': hex color of the bars above/below the video, e.g. #000000 (black) or #FFFFFF.

## `resolution` (type: `string`):

Output frame size (always 9:16, 30 fps, H.264 + AAC MP4 ready for TikTok, Instagram Reels, YouTube Shorts, Snapchat). Default 1080x1920.

## `quality` (type: `string`):

H.264 quality/size trade-off: 'high' (CRF 18), 'standard' (CRF 21, default), 'small' (CRF 25, for messaging/previews).

## `captions` (type: `boolean`):

Transcribe the clip's speech with Whisper AI and burn animated captions into the video (also returned as an .srt file). Turn off for a plain reframe/trim. Captions are included in the price.

## `model` (type: `string`):

Whisper model used for captions. 'small' (default) is accurate for English and major languages; 'base' is faster; 'large-v3-turbo' improves accuracy for accents, noisy audio and less common languages but transcribes about 2-3x slower and is downloaded at run start. Same price for every model.

## `language` (type: `string`):

ISO 639-1 code of the spoken language (en, es, de, fr, pt, hi, ja, ...) or 'auto' (default) to detect it. Setting it avoids rare mis-detections on short clips. Captions are written in the spoken language.

## `captionStyle` (type: `string`):

Starting look for captions; any of the style fields below override parts of it. 'bold' (default): Poppins ExtraBold, UPPERCASE, 3 words at a time, current word highlighted yellow, placed automatically (under the video in blur mode, lower third in crop mode). 'pop': Luckiest Guy, 2 words, green highlight. 'boxed': DejaVu Sans on a semi-transparent black box, ~6 words, bottom. 'minimal': Anton with thin outline, 4 words, bottom.

## `font` (type: `string`):

Overrides the preset's font. All fonts are bundled. For Cyrillic/Greek and other non-Latin scripts use 'dejavu'. CJK, Arabic and Indic scripts are not covered by the bundled fonts.

## `fontSize` (type: `integer`):

Overrides the preset's font size, in pixels at 1080 px width (scaled automatically for 720x1280). Presets use 60-100.

## `textColor` (type: `string`):

Overrides the caption text color, hex like #FFFFFF.

## `highlightColor` (type: `string`):

Overrides the color of the currently spoken word when word highlighting is on, hex like #FFE600 (yellow) or #3DFF5A (green).

## `outlineColor` (type: `string`):

Overrides the text outline color, hex like #000000.

## `outlineWidth` (type: `number`):

Overrides the outline thickness in pixels at 1080 width (0 = none). Presets use 3-7.

## `captionBox` (type: `boolean`):

Overrides whether captions sit on a semi-transparent black box (like the 'boxed' preset) instead of an outline.

## `uppercase` (type: `boolean`):

Overrides whether caption text is shown in capitals.

## `wordsPerCaption` (type: `integer`):

Overrides how many words are on screen at once (1-12). 1-3 = fast punchy style; 6-10 = subtitle style.

## `highlightWords` (type: `boolean`):

Overrides karaoke-style highlighting: the word being spoken changes to highlightColor.

## `captionPosition` (type: `string`):

Overrides vertical placement. 'auto' (used by the bold/pop presets): in blur/pad mode captions go in the free area just under the video so they never cover faces; in crop mode they sit in the lower third. 'bottom' sits above the TikTok/Reels UI safe zone (20% from the bottom); 'center' is mid-screen; 'top' is 14% from the top.

## Actor input object example

```json
{
  "url": "https://download.samplelib.com/mp4/sample-30s.mp4",
  "maxClipSeconds": 60,
  "mode": "blur",
  "cropPosition": 0.5,
  "blurStrength": 20,
  "padColor": "#000000",
  "resolution": "1080x1920",
  "quality": "standard",
  "captions": true,
  "model": "small",
  "language": "auto",
  "captionStyle": "bold"
}
```

# Actor output Schema

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

One dataset row per clip: status, MP4 download URL, thumbnail, captions .srt URL, transcript, timing, or an error message.

## `files` (type: `string`):

Key-value store holding the rendered MP4 shorts, JPG thumbnails and SRT captions.

# 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 = {
    "url": "https://download.samplelib.com/mp4/sample-30s.mp4"
};

// Run the Actor and wait for it to finish
const run = await client.actor("ent-e/video-to-shorts-captions").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 = { "url": "https://download.samplelib.com/mp4/sample-30s.mp4" }

# Run the Actor and wait for it to finish
run = client.actor("ent-e/video-to-shorts-captions").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 '{
  "url": "https://download.samplelib.com/mp4/sample-30s.mp4"
}' |
apify call ent-e/video-to-shorts-captions --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ent-e/video-to-shorts-captions"
        }
    }
}
```

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/6RDE4pDQIYVzmuoeu/builds/mANKaZzeIp3za25V1/openapi.json
