# Subtitle QC – SRT/VTT Compliance Checker (`lumaxys/subtitle-qc`) Actor

Validate SRT and VTT subtitle files for reading speed, line length, timing, overlaps, gaps, and formatting. Get a detailed compliance report and, when safe, an automatically corrected subtitle file with all remaining issues clearly listed.

- **URL**: https://apify.com/lumaxys/subtitle-qc.md
- **Developed by:** [François Fernandez](https://apify.com/lumaxys) (community)
- **Categories:** Automation, Developer tools, Videos
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $40.00 / 1,000 subtitle file checkeds

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js/docs.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python/docs.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/integrations/mcp.md).

If your project is in a different language, use the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).


# README

## Subtitle QC – SRT/VTT Compliance Checker

Automatically check subtitle files before delivery. Upload SRT or VTT files and receive a structured quality report covering reading speed, line length, line count, timing, overlaps, gaps, and common formatting problems.

When **Check and correct** is selected, the Actor also produces a corrected subtitle file when it can make safe, measurable changes. It never rewrites dialogue or translation content. Every generated file is then checked again, so the report clearly separates the original problems from the issues that still require manual review.

### Common uses

- Post-production quality control before client or broadcaster delivery
- Localization vendors checking files received from translators
- Video platforms screening subtitle uploads
- Agencies enforcing consistent subtitle specifications across a batch
- Automated Make, Zapier, API, or agent workflows

### Input

| Field | Type | Initial value | Description |
|---|---|---|---|
| `files` | array | empty | Upload SRT/VTT files or select Key-Value Store records. |
| `fileUrls` | array | empty | Direct HTTP(S) file URLs. |
| `kvsKeys` | array | empty | Keys already stored in the run's default Key-Value Store. |
| `example` | string | `clean` in Console | Bundled example used only when no real file is supplied. |
| `profile` | string | `documentary` | Documentary, social, accessibility, or custom thresholds. |
| `specOverrides` | object | `{}` | Override CPS, line length, duration, and gap thresholds. |
| `correctionMode` | select | `check-only` in Console | Choose checking only or checking plus safe automatic correction. |
| `maxCostUsd` | number | `1.50` | Additional safety cap checked before custom event charging. |

#### Correction modes

- `check-only`: produces the report only.
- `check-and-correct`: requests a corrected file. The correction event is charged **only when a genuinely changed file is produced**. A compliant file therefore remains a $0.04 check even if correction was requested.


### What is checked

- Maximum lines per cue
- Maximum displayed characters per line
- Reading speed in characters per second, including CJK-aware width
- Minimum and maximum cue duration
- Overlapping cues
- Gaps that are too short
- Leading, trailing, and repeated whitespace
- Malformed or empty subtitle blocks

### Automatic corrections

Safe correction can:

- Normalize whitespace
- Reflow lines without rewriting words
- Remove empty cues
- Trim overlaps
- Create the requested minimum gap when this does not create a new timing or reading-speed error
- Extend durations when sufficient room exists
- Cap overly long durations when the resulting reading speed remains compliant
- Renumber cues after a real change
- Reanalyse the exact corrected output before delivery

Text that cannot fit within the selected specification without changing the edit is marked for manual splitting instead of being falsely labelled as corrected. The final report includes both **Issues detected in input** and **Issues remaining after automatic correction**.

### Secure file delivery

Generated reports and corrected subtitle files are stored in the run's private key-value store. Open the run's **Storage > Generated files** tab to download them. API users fetch the listed `storageKey` from the run's default key-value store with their own Apify token. The Actor deliberately does not expose unsigned API links.

### Output

Each run stores:

- A structured `OUTPUT` JSON record
- A dataset overview
- A Markdown compliance report
- A corrected SRT/VTT file only when requested and genuinely changed

Example result:

```json
{
  "status": "ok",
  "metrics": {
    "filesProcessed": 1,
    "errors": 9,
    "warnings": 3,
    "compliant": false,
    "finalErrors": 6,
    "finalWarnings": 1,
    "finalCompliant": false,
    "correctionMode": "check-and-correct",
    "correctedFilesProduced": 1,
    "correctedFilesNeedingManualReview": 1
  },
  "postCorrectionIssues": [
    {"code": "CPS_TOO_HIGH", "severity": "error", "message": "Reading speed remains above the selected limit.", "location": {"file": "messy.srt", "artifact": "messy.corrected.srt", "cue": 1}}
  ],
  "artifacts": [
    {"name": "messy.corrected.srt", "kind": "corrected", "storageKey": "subtitle-qc-messy.corrected.srt", "access": "authenticated"},
    {"name": "report.md", "kind": "report", "storageKey": "subtitle-qc-report.md", "access": "authenticated"}
  ],
  "billing": {
    "events": {"file-checked": 1, "file-corrected": 1},
    "costUsd": 0.07
  }
}
````

### Accepted files

- SRT and WebVTT
- Up to 20 files per run
- Up to 8 MB per file and 32 MB combined per run

Social platform page links such as YouTube, TikTok, Instagram, Facebook, or X are not accepted. Use an uploaded subtitle file or a direct file URL.

### Pricing

This Actor uses Pay per event.

| Event | Price | Charged when |
|---|---:|---|
| `file-checked` | $0.04 | A valid SRT/VTT file is parsed and analysed. |
| `file-corrected` | $0.03 | A genuinely changed corrected file is produced. |

Typical custom-event totals:

- Compliant file, report only: **$0.04**
- Compliant file with correction requested but no change needed: **$0.04**
- Non-compliant file with a corrected file produced: **$0.07**
- Unreadable file with no subtitle cues: **$0.00 custom events**

The small automatic Actor-start event is configured separately in Apify Console. Platform usage is included according to the Actor's pricing setup.

### Bundled test examples

- `clean`: compliant SRT, report only
- `messy`: non-compliant SRT with correctable problems
- `vtt_social`: WebVTT checked against the social profile
- `invalid`: invalid SRT content used to test error handling

Real uploaded files always take priority over bundled examples.

### Limitations

This Actor does not check spelling, translation accuracy, dialogue meaning, shot-change alignment, or positioning tags. Automatic correction is deliberately conservative and never rewrites words. A generated file can therefore remain non-compliant when the available timing is physically insufficient; those remaining issues are explicitly listed instead of being hidden.

### Support

Include the Apify run ID and a minimal reproducible input when reporting a problem.

# Actor input Schema

## `files` (type: `array`):

Upload files from your computer, select files from an Apify Key-Value Store, or paste direct file URLs.

## `fileUrls` (type: `array`):

Direct HTTP(S) URLs to files. Social platform page links are not supported.

## `kvsKeys` (type: `array`):

Record keys already stored in the run's default Key-Value Store.

## `example` (type: `string`):

Runs a reproducible file bundled with the Actor. Uploads, URLs and KVS keys always take priority over this field.

## `maxCostUsd` (type: `number`):

Extra safety cap checked before custom event charging. Apify's native max-total-charge limit remains the final authority.

## `profile` (type: `string`):

Preset thresholds. Use 'custom' with specOverrides for a broadcaster-specific spec.

## `specOverrides` (type: `object`):

Any of: max\_lines, max\_chars\_per\_line, max\_cps, max\_cps\_cjk, min\_duration\_ms, max\_duration\_ms, min\_gap\_ms.

## `correctionMode` (type: `string`):

Choose check-only, or request an automatically corrected file when real changes can be produced.

## Actor input object example

```json
{
  "example": "clean",
  "maxCostUsd": 1.5,
  "profile": "documentary",
  "specOverrides": {},
  "correctionMode": "check-only"
}
```

# Actor output Schema

## `runResults` (type: `string`):

Authenticated Console page for this run. Use Output for the structured result and Storage > Generated files for reports and corrected subtitles.

# 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 = {
    "example": "clean",
    "correctionMode": "check-only"
};

// Run the Actor and wait for it to finish
const run = await client.actor("lumaxys/subtitle-qc").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 = {
    "example": "clean",
    "correctionMode": "check-only",
}

# Run the Actor and wait for it to finish
run = client.actor("lumaxys/subtitle-qc").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "example": "clean",
  "correctionMode": "check-only"
}' |
apify call lumaxys/subtitle-qc --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=lumaxys/subtitle-qc",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Subtitle QC – SRT/VTT Compliance Checker",
        "description": "Validate SRT and VTT subtitle files for reading speed, line length, timing, overlaps, gaps, and formatting. Get a detailed compliance report and, when safe, an automatically corrected subtitle file with all remaining issues clearly listed.",
        "version": "0.1",
        "x-build-id": "1VN3Fuf06ms4AkYfZ"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/lumaxys~subtitle-qc/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-lumaxys-subtitle-qc",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/lumaxys~subtitle-qc/runs": {
            "post": {
                "operationId": "runs-sync-lumaxys-subtitle-qc",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/lumaxys~subtitle-qc/run-sync": {
            "post": {
                "operationId": "run-sync-lumaxys-subtitle-qc",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "properties": {
                    "files": {
                        "title": "Files to process",
                        "maxItems": 20,
                        "uniqueItems": true,
                        "type": "array",
                        "description": "Upload files from your computer, select files from an Apify Key-Value Store, or paste direct file URLs.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "fileUrls": {
                        "title": "Direct file URLs (advanced)",
                        "maxItems": 20,
                        "uniqueItems": true,
                        "type": "array",
                        "description": "Direct HTTP(S) URLs to files. Social platform page links are not supported.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "kvsKeys": {
                        "title": "Key-Value Store keys (advanced)",
                        "maxItems": 20,
                        "uniqueItems": true,
                        "type": "array",
                        "description": "Record keys already stored in the run's default Key-Value Store.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "example": {
                        "title": "Bundled example (used only when no file is supplied)",
                        "enum": [
                            "clean",
                            "messy",
                            "vtt_social",
                            "invalid"
                        ],
                        "type": "string",
                        "description": "Runs a reproducible file bundled with the Actor. Uploads, URLs and KVS keys always take priority over this field."
                    },
                    "maxCostUsd": {
                        "title": "Additional custom-event cost cap (USD)",
                        "minimum": 0,
                        "type": "number",
                        "description": "Extra safety cap checked before custom event charging. Apify's native max-total-charge limit remains the final authority.",
                        "default": 1.5
                    },
                    "profile": {
                        "title": "Spec profile",
                        "enum": [
                            "documentary",
                            "social",
                            "accessibility",
                            "custom"
                        ],
                        "type": "string",
                        "description": "Preset thresholds. Use 'custom' with specOverrides for a broadcaster-specific spec.",
                        "default": "documentary"
                    },
                    "specOverrides": {
                        "title": "Spec overrides",
                        "type": "object",
                        "description": "Any of: max_lines, max_chars_per_line, max_cps, max_cps_cjk, min_duration_ms, max_duration_ms, min_gap_ms.",
                        "properties": {
                            "max_lines": {
                                "title": "Maximum lines per subtitle",
                                "description": "Maximum number of text lines allowed in one subtitle cue.",
                                "type": "integer",
                                "minimum": 1,
                                "maximum": 10
                            },
                            "max_chars_per_line": {
                                "title": "Maximum characters per line",
                                "description": "Maximum display width allowed for each subtitle line.",
                                "type": "integer",
                                "minimum": 1,
                                "maximum": 500
                            },
                            "max_cps": {
                                "title": "Maximum reading speed (CPS)",
                                "description": "Maximum characters per second for non-CJK subtitle text.",
                                "type": "number",
                                "minimum": 0.01,
                                "maximum": 200
                            },
                            "max_cps_cjk": {
                                "title": "Maximum CJK reading speed (CPS)",
                                "description": "Maximum characters per second for Chinese, Japanese, and Korean text.",
                                "type": "number",
                                "minimum": 0.01,
                                "maximum": 200
                            },
                            "min_duration_ms": {
                                "title": "Minimum duration (milliseconds)",
                                "description": "Minimum on-screen duration allowed for a subtitle cue.",
                                "type": "integer",
                                "minimum": 0,
                                "maximum": 60000
                            },
                            "max_duration_ms": {
                                "title": "Maximum duration (milliseconds)",
                                "description": "Maximum on-screen duration allowed for a subtitle cue.",
                                "type": "integer",
                                "minimum": 1,
                                "maximum": 300000
                            },
                            "min_gap_ms": {
                                "title": "Minimum gap (milliseconds)",
                                "description": "Minimum time gap required between consecutive subtitle cues.",
                                "type": "integer",
                                "minimum": 0,
                                "maximum": 10000
                            }
                        },
                        "default": {},
                        "additionalProperties": false
                    },
                    "correctionMode": {
                        "title": "Correction mode",
                        "enum": [
                            "check-only",
                            "check-and-correct"
                        ],
                        "type": "string",
                        "description": "Choose check-only, or request an automatically corrected file when real changes can be produced."
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
