{
  "openapi": "3.1.1",
  "info": {
    "title": "Subtitle Studio API",
    "version": "1.0.0",
    "summary": "Convert SRT, VTT, ASS and SBV; shift, re-sync, fix overlaps, wrap lines and check CPS.",
    "description": "Every endpoint performs the same calculation as the human page and returns inputs, result, units, assumptions, sources, warnings and computedAt. MCP endpoint: /mcp."
  },
  "servers": [
    {
      "url": "https://subtitlestudio.usefulweb.site"
    }
  ],
  "paths": {
    "/api/v1/calculate": {
      "post": {
        "operationId": "calculate",
        "summary": "Convert SRT, VTT, ASS and SBV; shift, re-sync, fix overlaps, wrap lines and check CPS.",
        "description": "Parses SRT, WebVTT, ASS/SSA (basic) and YouTube SBV subtitles and converts between them. Shift all times, re-sync by two reference points (fixes drifting subtitles), fix overlaps with a minimum gap, wrap or merge lines, remove hearing-impaired tags like [music] or (sighs), and get a reading-speed (characters per second) report and validation errors with line numbers.\n\nMethodology:\n- The format is detected from the content (WEBVTT header, [Script Info]/Dialogue lines, \"-->\" timing lines, or SBV \"start,end\" lines). Files are decoded as UTF-8 or UTF-16 with BOM, falling back to Windows-1252.\n- Each block is validated: missing or out-of-sequence numbers, malformed timing lines, end before start, empty cues, cues longer than 10 s and overlaps are reported with the line number in your file. Broken blocks are skipped, not guessed.\n- Operations run in this order: remove hearing-impaired tags → merge lines → wrap/split long lines → two-point sync → shift → fix overlaps. Two-point sync maps time linearly: t′ = T₁ + (t − S₁) × (T₂ − T₁) ÷ (S₂ − S₁), where S are the current and T the wanted start times of the two reference cues; it corrects both offset and drift (frame-rate mismatch).\n- Line wrapping first tries two balanced lines split near the middle, otherwise wraps greedily; cues needing more lines than allowed are split into consecutive cues with time shared by text length.\n- Reading speed = visible characters (without markup and line breaks) ÷ display time in seconds. ASS output uses one default style; ASS input styles, positions and effects are dropped (italic/bold are kept as <i>/<b>). SRT keeps <i>, <b> and <u>.",
        "tags": [
          "media"
        ],
        "x-mcp-tool": "subtitle_studio",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubtitleStudioInput"
              },
              "examples": {
                "example1": {
                  "summary": "Clean an SRT: remove [music] tags, wrap at 42, fix overlaps, export WebVTT",
                  "value": {
                    "text": "1\n00:00:01,000 --> 00:00:03,200\n[MUSIC PLAYING]\nHello there!\n\n2\n00:00:03,000 --> 00:00:05,500\nJOHN: (sighs) This line is quite a bit longer than forty-two characters and needs wrapping.\n\n3\n00:00:06,000 --> 00:00:06,400\nToo fast to read comfortably.\n",
                    "outputFormat": "vtt",
                    "removeHi": true,
                    "maxLineLength": 42,
                    "fixOverlaps": true,
                    "minGapMs": 80
                  }
                },
                "example2": {
                  "summary": "Re-sync a drifting WebVTT by two points and save as SRT",
                  "value": {
                    "text": "WEBVTT\n\n00:00:10.000 --> 00:00:12.000\nFirst line\n\n00:00:20.000 --> 00:00:22.500\n<v Anna>Second line</v>\n\n00:01:00.000 --> 00:01:02.000\nThird line\n",
                    "outputFormat": "srt",
                    "syncCueA": 1,
                    "syncTimeA": "00:00:11.000",
                    "syncCueB": 3,
                    "syncTimeB": "00:01:03.500"
                  }
                },
                "example3": {
                  "summary": "Shift an SRT 1.5 s earlier",
                  "value": {
                    "text": "1\n00:00:01,000 --> 00:00:03,200\n[MUSIC PLAYING]\nHello there!\n\n2\n00:00:03,000 --> 00:00:05,500\nJOHN: (sighs) This line is quite a bit longer than forty-two characters and needs wrapping.\n\n3\n00:00:06,000 --> 00:00:06,400\nToo fast to read comfortably.\n",
                    "shiftMs": -1500
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubtitleStudioResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tool": {
      "get": {
        "operationId": "getToolDefinition",
        "summary": "Inputs, output schema and examples for this tool",
        "responses": {
          "200": {
            "description": "Tool definition.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details. 422 responses include per-field `errors`.",
        "required": [
          "type",
          "title",
          "status",
          "detail"
        ],
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "field",
                "message"
              ],
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "SubtitleStudioInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "text": {
            "title": "Subtitle text",
            "description": "Paste SRT, WebVTT, ASS/SSA or SBV, or upload a file below.",
            "type": "string",
            "maxLength": 1000000
          },
          "file": {
            "title": "Subtitle file",
            "description": "Max 2 MB. UTF-8, UTF-16 (with BOM) or Windows-1252. Used instead of the text when given. Upload as {filename, contentType?, contentBase64}. Max 2048 KiB. Accepts .srt,.vtt,.ass,.ssa,.sbv,.sub,.txt,text/plain,text/vtt.",
            "type": "object",
            "additionalProperties": false,
            "required": [
              "filename",
              "contentBase64"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "contentType": {
                "type": "string"
              },
              "contentBase64": {
                "type": "string"
              }
            }
          },
          "inputFormat": {
            "title": "Input format",
            "type": "string",
            "enum": [
              "auto",
              "srt",
              "vtt",
              "ass",
              "sbv"
            ],
            "default": "auto"
          },
          "outputFormat": {
            "title": "Output format",
            "type": "string",
            "enum": [
              "same",
              "srt",
              "vtt",
              "ass",
              "sbv"
            ],
            "default": "same"
          },
          "shiftMs": {
            "title": "Shift all times",
            "description": "Positive = later, negative = earlier. 1000 ms = 1 s.",
            "type": "integer",
            "minimum": -36000000,
            "maximum": 36000000,
            "default": 0,
            "x-unit": "ms"
          },
          "syncCueA": {
            "title": "Sync point 1: cue number",
            "description": "Two-point sync fixes subtitles that drift (e.g. 25 vs 23.976 fps). Give two cues and when they should start.",
            "type": "integer",
            "minimum": 1,
            "maximum": 100000
          },
          "syncTimeA": {
            "title": "Sync point 1: should start at",
            "type": "string",
            "maxLength": 20
          },
          "syncCueB": {
            "title": "Sync point 2: cue number",
            "type": "integer",
            "minimum": 1,
            "maximum": 100000
          },
          "syncTimeB": {
            "title": "Sync point 2: should start at",
            "type": "string",
            "maxLength": 20
          },
          "removeHi": {
            "title": "Remove hearing-impaired tags ([music], (sighs), speaker labels like JOHN:, ♪ lines)",
            "type": "boolean",
            "default": false
          },
          "mergeLines": {
            "title": "Merge multi-line cues into one line when they fit the maximum line length",
            "type": "boolean",
            "default": false
          },
          "maxLineLength": {
            "title": "Maximum line length",
            "description": "0 = no wrapping. 42 is a common limit for TV/streaming subtitles.",
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "default": 0,
            "x-unit": "characters"
          },
          "maxLines": {
            "title": "Maximum lines per cue",
            "description": "Longer cues are split into several cues, with time shared by text length.",
            "type": "integer",
            "minimum": 1,
            "maximum": 4,
            "default": 2
          },
          "fixOverlaps": {
            "title": "Fix overlapping cues (shorten the earlier cue)",
            "type": "boolean",
            "default": false
          },
          "minGapMs": {
            "title": "Minimum gap between cues",
            "description": "Used with \"Fix overlapping cues\". 83 ms ≈ 2 frames at 24 fps.",
            "type": "integer",
            "minimum": 0,
            "maximum": 2000,
            "default": 0,
            "x-unit": "ms"
          },
          "maxCps": {
            "title": "Reading speed limit for the report",
            "description": "Cues faster than this are listed. 15–20 characters per second are common guidelines.",
            "type": "number",
            "minimum": 5,
            "maximum": 40,
            "default": 17,
            "x-unit": "chars/s"
          }
        },
        "required": []
      },
      "SubtitleStudioResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "inputFormat",
              "outputFormat",
              "encoding",
              "cuesIn",
              "cuesOut",
              "lastEndMs",
              "operations",
              "issues",
              "issuesAfter",
              "readingSpeed",
              "output",
              "filename"
            ],
            "properties": {
              "inputFormat": {
                "type": "string",
                "enum": [
                  "srt",
                  "vtt",
                  "ass",
                  "sbv"
                ]
              },
              "outputFormat": {
                "type": "string",
                "enum": [
                  "srt",
                  "vtt",
                  "ass",
                  "sbv"
                ]
              },
              "encoding": {
                "type": "string"
              },
              "cuesIn": {
                "type": "integer"
              },
              "cuesOut": {
                "type": "integer"
              },
              "lastEndMs": {
                "type": "integer"
              },
              "operations": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "hearingImpairedChanged": {
                    "type": "integer"
                  },
                  "hearingImpairedRemovedCues": {
                    "type": "integer"
                  },
                  "mergedCues": {
                    "type": "integer"
                  },
                  "rewrappedCues": {
                    "type": "integer"
                  },
                  "splitCues": {
                    "type": "integer"
                  },
                  "syncFactor": {
                    "type": "number"
                  },
                  "syncOffsetMs": {
                    "type": "integer"
                  },
                  "shiftMs": {
                    "type": "integer"
                  },
                  "clampedCues": {
                    "type": "integer"
                  },
                  "droppedCues": {
                    "type": "integer"
                  },
                  "overlapsFixed": {
                    "type": "integer"
                  },
                  "overlapsUnresolved": {
                    "type": "integer"
                  }
                }
              },
              "issues": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "line",
                    "severity",
                    "message"
                  ],
                  "properties": {
                    "line": {
                      "type": "integer"
                    },
                    "severity": {
                      "type": "string",
                      "enum": [
                        "error",
                        "warning"
                      ]
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              },
              "issuesAfter": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "readingSpeed": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "maxCps",
                  "averageCps",
                  "limit",
                  "cuesOverLimit"
                ],
                "properties": {
                  "maxCps": {
                    "type": "number"
                  },
                  "averageCps": {
                    "type": "number"
                  },
                  "limit": {
                    "type": "number"
                  },
                  "cuesOverLimit": {
                    "type": "integer"
                  }
                }
              },
              "output": {
                "type": "string",
                "description": "The converted subtitle file."
              },
              "filename": {
                "type": "string"
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      }
    }
  }
}