Attest tools · Subtitle tools: convert, retime, merge and read caption files

Subtitle frame rate conversion 25 to 24 fps

Stretch subtitle timings by 25/24 to match a clip slowed from 25 fps to 24 fps.

1 credit per successful call

Input JSON

{
  "operation": "shift",
  "from_format": "srt",
  "scale": 1.0416666666666667,
  "text": "1\n00:00:01,000 --> 00:00:03,000\nWelcome to the workshop.\n\n2\n00:00:04,000 --> 00:00:06,500\nCheck the label before washing.\n"
}

Captured output JSON

{
  "operation": "shift",
  "from_format": "srt",
  "to_format": "srt",
  "cues": 2,
  "duration_seconds": 6.771,
  "result_text": "1\n00:00:01,042 --> 00:00:03,125\nWelcome to the workshop.\n\n2\n00:00:04,167 --> 00:00:06,771\nCheck the label before washing.\n",
  "truncated": false,
  "applied": {
    "offset_seconds": 0,
    "scale": 1.0416666666666667,
    "merge_gap_seconds": null,
    "strip_tags": false,
    "files": 1
  },
  "info": {
    "cues": 2,
    "characters": 55,
    "words": 9,
    "lines": 2,
    "first_start_seconds": 1.042,
    "last_end_seconds": 6.771,
    "total_cue_seconds": 4.687,
    "coverage_ratio": 0.8181,
    "longest_cue_seconds": 2.604,
    "shortest_cue_seconds": 2.083,
    "max_characters_per_line": 31,
    "max_characters_per_second": 10.4,
    "overlapping_cues": 0,
    "zero_length_cues": 0
  },
  "warnings": [],
  "limits": {
    "max_file_bytes": 4194304,
    "max_file_base64_characters": 5592408,
    "max_text_characters": 4000000,
    "max_files": 20,
    "max_total_bytes": 8388608,
    "max_cues": 200000,
    "max_result_bytes": 2000000,
    "max_fetch_seconds": 120
  },
  "sources": [
    {
      "name": "W3C WebVTT: The Web Video Text Tracks Format",
      "url": "https://www.w3.org/TR/webvtt1/",
      "used_for": "WebVTT parsing and writing: the header, NOTE, STYLE and REGION blocks, cue identifiers, cue settings, the timestamp grammar and the cue payload tags"
    },
    {
      "name": "SubRip (.srt) — a de facto format with no published specification; the shape used here is the one every player accepts",
      "url": "https://en.wikipedia.org/wiki/SubRip",
      "used_for": "SubRip parsing and writing"
    },
    {
      "name": "Advanced SubStation Alpha (.ass/.ssa) — community specification, with the override tag list from the Aegisub manual",
      "url": "https://aegisub.org/docs/latest/ass_tags/",
      "used_for": "reading [Events] Dialogue lines, their H:MM:SS.cc timings and the \\N, \\n, \\h, \\i, \\b, \\u, \\p and \\r override tags"
    },
    {
      "name": "YouTube SBV (.sbv) — the caption format YouTube exports, H:MM:SS.mmm start and end joined by a comma",
      "url": "https://support.google.com/youtube/answer/2734698",
      "used_for": "SBV parsing"
    }
  ],
  "notice": "General information about the subtitle file you supplied. Timings are read from the file and transformed exactly as requested; nothing here checks the file against the video it belongs to. The file is processed in memory and is not stored.",
  "checked_at": "2026-10-20T20:47:48.843Z"
}

API call

curl -s -X POST https://getattest.com.au/api/v1/subtitles \
  -H "authorization: Bearer $ATTEST_API_KEY" \
  -H "content-type: application/json" \
  --data-raw '{"operation":"shift","from_format":"srt","scale":1.0416666666666667,"text":"1\n00:00:01,000 --> 00:00:03,000\nWelcome to the workshop.\n\n2\n00:00:04,000 --> 00:00:06,500\nCheck the label before washing.\n"}'

Run this call with your own bearer key. Issue an API key.

About this example

The input was validated and run locally through the registered service. The output above is a display preview of the captured service result. File bytes are described instead of printed, and strings longer than 400 characters are truncated; the HTTP API also adds call_id, credits_charged and credits_remaining. Timestamps and generated identifiers will vary.

Source: apps/api/svc/subtitle_tools.mjs

Tool reference and limits · API key guide