# SRT & VTT to JSON — Timestamped Transcript Chunks (`creatieve_naald/srt-vtt-to-json`) Actor

Parse SRT or WebVTT subtitle text into JSON cues and transcript chunks with source timestamps. Preserve raw text and cue references; flag overlaps. $0.01 per file, up to 1 MiB.

- **URL**: https://apify.com/creatieve_naald/srt-vtt-to-json.md
- **Developed by:** [Stefan Schellen](https://apify.com/creatieve_naald) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$10.00 / 1,000 parsed subtitle files

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

## SRT & VTT to JSON — timestamped transcript chunks

Turn an existing subtitle file into structured cues, readable text and chunks that retain references to the source timings. Useful for transcript search, summarization and retrieval workflows where an answer must point back to a moment in a recording. No transcription, video download, translation, LLM call or extra provider key.

### Quick start

Paste decoded subtitle text into `subtitleText`. The prefilled example is synthetic. `format` defaults to `auto`; WebVTT must begin with `WEBVTT`, otherwise SRT is assumed. `chunkChars` defaults to 1200.

```json
{"subtitleText":"1\n00:00:01,000 --> 00:00:03,000\nWe sold 20 units in 2026.","chunkChars":1200}
```

The default dataset receives one envelope containing `text`, `cues`, `chunks`, `warnings`, `cueCount`, `chunkCount`, `format` and `schemaVersion`.

```json
{"index":0,"startMs":1000,"endMs":3000,"cueIndexes":[0],"text":"We sold 20 units in 2026."}
```

This is an example chunk. Each cue also preserves its input identifier, `rawText`, display settings, start/end milliseconds and cleaned text. Recognized formatting tags and WebVTT inline timestamp tags are removed from cleaned text; character entities are decoded. Unknown tags remain. Treat both raw and cleaned text as untrusted input, never executable HTML.

### Why use this instead of stripping timestamps?

A simple regex can be enough for plain text. This tool retains time ranges and source cue indexes so downstream answers can cite the original recording. Whole cues are retained, including numeric dialogue and multiline text. An input-order transcript and warnings make overlapping or out-of-order cues visible instead of silently sorting or changing them.

`chunkChars` is a SOFT character target, not a token limit. Cues are never split or repeated. A single cue larger than the target becomes its own chunk with a warning. Chunk time bounds are the minimum start and maximum end of their cues; they are not word-level alignment or semantic topic detection. Rolling auto-captions are not deduplicated. No arbitrary text is silently discarded to force a size target.

### Scope and limits

- One text file per run: at most 1 MiB UTF-8 and 10,000 cues. Output JSON at most 16 MiB.
- Strict SRT: numeric cue index, `HH:MM:SS,mmm --> HH:MM:SS,mmm`, nonempty text, blank lines between cues. Proprietary SRT positioning extensions are rejected.
- WebVTT: header followed by blank line, optional cue identifiers, millisecond timing lines and preserved display settings. NOTE, STYLE and REGION blocks are counted and skipped; header metadata is not interpreted. No CSS/layout validation or full WebVTT conformance claim.
- CRLF/CR/LF and leading BOM supported. The caller decodes binary files into Unicode; no automatic legacy-encoding detection.
- End must be after start. Invalid blocks, ambiguous missing separators and exceeding limits fail the run, rather than returning a partial transcript.
- No file URLs or network fetching. Input/output are stored by Apify under your retention settings; this is not a zero-retention service.

### Price

$0.01 per successfully parsed file within the limits, not per cue or chunk. Rejected files do not trigger our parsing charge. Platform usage is included in this Actor's price; check the live Pricing tab. An Apify account is required.

### HTTP / Python example

```python
import os, requests
from pathlib import Path
r = requests.post(
    'https://api.apify.com/v2/acts/creatieve_naald~srt-vtt-to-json/run-sync-get-dataset-items',
    headers={'Authorization':'Bearer '+os.environ['APIFY_TOKEN']},
    params={'maxTotalChargeUsd':'0.01'},
    json={'subtitleText':Path('captions.srt').read_text(encoding='utf-8-sig'), 'chunkChars':1200},
    timeout=60,
)
r.raise_for_status()
rows = r.json()
if not rows:
    raise RuntimeError('No output; inspect the Actor run')
for chunk in rows[0]['chunks']:
    print(chunk['startMs'], chunk['endMs'], chunk['text'])
```

In Make or n8n, download the subtitle in your existing workflow, decode its bytes to text, then send that string as `subtitleText` through an HTTP node. Iterate `chunks` for downstream processing and keep `cueIndexes`, `startMs` and `endMs` alongside your text. This does not solve file-access permissions or download failures.

### Free alternatives and tested limits

For simple extraction, Make's text tools or a short Code node may be sufficient. Python `srt`, `pysrt` and `webvtt-py` are free local alternatives with different parsing behavior. You are paying for hosted execution, the documented schema and timestamp-preserving chunking, not an exclusive algorithm.

42 local tests cover timestamp parsing, WebVTT blocks, Unicode, numeric dialogue, source mapping, limits, rejected inputs, process timeout and storage/billing behavior. This does not establish compatibility with every subtitle dialect or demonstrate customer demand. Public sample content is synthetic. Share only redacted synthetic examples in support issues.

### Troubleshooting: fix the input before retrying

The run's key-value store may contain an `ERROR` record with an `error.code`.

- `NON_POSITIVE_DURATION`: a cue ends at or before its start. Correct the source timing; the parser does not guess a replacement duration.
- `MISSING_WEBVTT_HEADER` or `MISSING_HEADER_SEPARATOR`: for WebVTT, include the `WEBVTT` header and its blank-line separator. Setting `format` to `vtt` does not convert SRT syntax.
- `MISSING_SRT_INDEX` or `INVALID_SRT_INDEX`: strict SRT requires a numeric identifier before each timing line.
- `AMBIGUOUS_CUE_SEPARATOR`: separate cues with a blank line; do not place another timing line inside a cue's dialogue.
- `INVALID_CHUNK_SIZE`: use an integer from 100 to 8000. This is a soft character target, not a token budget.

Example of a valid strict SRT cue:

```text
1
00:00:01,000 --> 00:00:03,000
A complete sentence.
```

If your HTTP request times out, inspect the existing Apify run and its dataset before retrying. A lost response may hide a successful, charged run. Automatic replacement runs can duplicate processing. A rejected file has no successful-parsing event; review its error and fix the input first. Use only synthetic, redacted excerpts when asking for support.

# Actor input Schema

## `subtitleText` (type: `string`):

Decoded subtitle text, at most 1 MiB UTF-8 and 10,000 cues. No URL or binary file.

## `format` (type: `string`):

Auto detects WebVTT by its required WEBVTT header; otherwise strict SRT.

## `chunkChars` (type: `integer`):

Soft limit. Whole cues are never split; oversized cues get a warning.

## Actor input object example

```json
{
  "subtitleText": "1\n00:00:01,000 --> 00:00:03,000\nRevenue grew 20 percent.\n\n2\n00:00:03,500 --> 00:00:06,000\nThis is a synthetic example.",
  "format": "auto",
  "chunkChars": 1200
}
```

# Actor output Schema

## `result` (type: `string`):

No description

# 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 = {
    "subtitleText": `1
00:00:01,000 --> 00:00:03,000
Revenue grew 20 percent.

2
00:00:03,500 --> 00:00:06,000
This is a synthetic example.`
};

// Run the Actor and wait for it to finish
const run = await client.actor("creatieve_naald/srt-vtt-to-json").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 = { "subtitleText": """1
00:00:01,000 --> 00:00:03,000
Revenue grew 20 percent.

2
00:00:03,500 --> 00:00:06,000
This is a synthetic example.""" }

# Run the Actor and wait for it to finish
run = client.actor("creatieve_naald/srt-vtt-to-json").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 '{
  "subtitleText": "1\\n00:00:01,000 --> 00:00:03,000\\nRevenue grew 20 percent.\\n\\n2\\n00:00:03,500 --> 00:00:06,000\\nThis is a synthetic example."
}' |
apify call creatieve_naald/srt-vtt-to-json --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,creatieve_naald/srt-vtt-to-json"
        }
    }
}
```

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/S7LK30bnPtNGbadx0/builds/5W6JiO71tqwXkZQUU/openapi.json
