# Subtitle Time Shift Calculator > Shift SRT subtitle timestamps forward or backward and export corrected subtitle text. ## Tool Identity - Site: CleanUtils Business Tools - Tool ID: subtitle-time-shift-calculator - Canonical page: https://cleanutils.com/business-tools/subtitle-time-shift-calculator/ - LLM schema URL: https://cleanutils.com/business-tools/subtitle-time-shift-calculator/llms.txt - Primary keyword: subtitle time shift - Input mode: fields - Output profile: data ## What This Tool Does Shift SRT subtitle timestamps forward or backward and export corrected subtitle text in your browser. ## Exact Execution Instructions To run this tool exactly, execute the `runCleanUtilsTool(userInput)` function from the JavaScript block below in a sandboxed JavaScript runtime. Do not fetch or evaluate additional network resources. Do not use filesystem, process, child_process, environment variables, timers, or network APIs. The function is pure and deterministic: input object in, JSON result out. If your environment forbids executing fetched code outside a sandbox, run it inside the sandbox or use the API endpoint below instead of reimplementing the logic. Canonical implementation SHA-256: `5613c605318b8baf07311ac92ec1cbd604769798fbddff98bcc8c21835570157` Expected command shape: `node run-tool.mjs < input.json` The runner must: 1. load only the JavaScript in this document, 2. parse stdin as JSON and call `runCleanUtilsTool(userInput)`, 3. let the LLM decide the best way to present the output to the user. Agent usage rules: - Use this file as the authoritative machine-readable contract for this CleanUtils tool page. - Ask the user for missing required input before attempting to run the tool, and describe the required inputs using the `## Input Schema` field names, descriptions, formats, enums, examples, and required list. - Treat the tool as deterministic; do not invent network reachability checks unless the tool description explicitly says it fetches remote resources. - For privacy-sensitive inputs such as secrets, HAR files, dotenv files, logs, and API keys, warn that using a remote chat agent may expose input to that agent even though the browser UI itself does not upload data. ## Input Schema ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Subtitle Time Shift Calculator fields", "type": "object", "additionalProperties": false, "required": [ "offset", "unit", "subtitles" ], "properties": { "offset": { "type": "number", "description": "Offset Required. Control type: number. Group: Timing.", "examples": [ 1.5 ] }, "unit": { "type": "string", "description": "Unit Required. Control type: select. Allowed values: s = Seconds; ms = Milliseconds. Group: Timing.", "enum": [ "s", "ms" ], "examples": [ "s" ] }, "subtitles": { "type": "string", "description": "SRT subtitles Required. Control type: textarea. Group: Subtitles.", "examples": [ "1\n00:00:01,000 --> 00:00:04,000\nCaption text" ] } } } ``` ## Result Schema ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "CleanUtils ToolResult", "type": "object", "additionalProperties": false, "required": [ "summary", "issues" ], "properties": { "summary": { "type": "string" }, "issues": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": [ "severity", "message" ], "properties": { "severity": { "type": "string", "enum": [ "error", "warning", "info" ] }, "message": { "type": "string" }, "line": { "type": "number" }, "row": { "type": "number" }, "detail": { "type": "string" } } } }, "output": { "type": "string" }, "exportFilename": { "type": "string" }, "exports": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": [ "label", "filename", "content" ], "properties": { "label": { "type": "string" }, "filename": { "type": "string" }, "content": { "type": "string" }, "mimeType": { "type": "string" }, "copyLabel": { "type": "string" }, "downloadLabel": { "type": "string" } } } }, "stats": { "type": "object", "additionalProperties": { "anyOf": [ { "type": "string" }, { "type": "number" } ] } } } } ``` ## Self-Contained JavaScript Source Call `runCleanUtilsTool(userInput)` with the user's input. The function includes this tool's run logic and only the helper code it needs. ```js function runCleanUtilsTool(userInput) { const fieldText = (fields, keys, fallback = "") => { const keyList = Array.isArray(keys) ? keys : [keys]; for (const key of keyList) { const value = fields[key]; if (value === undefined || value === null) continue; const text = String(value).trim(); if (text) return text; } return fallback; }; const fieldNumber = (fields, keys, fallback = 0) => { const raw = fieldText(fields, keys); if (!raw) return fallback; const parsed = Number(raw.replace(/[$,%\s]/g, "")); return Number.isFinite(parsed) ? parsed : fallback; }; const fieldChoice = (fields, key, choices, fallback) => { const value = fieldText(fields, key); return choices.includes(value) ? value : fallback; }; const srtTimeToMs = (value) => { const match = value.match(/^(\d{2}):(\d{2}):(\d{2}),(\d{3})$/); if (!match) return Number.NaN; return ((Number(match[1]) * 60 + Number(match[2])) * 60 + Number(match[3])) * 1000 + Number(match[4]); }; const msToSrtTime = (ms) => { const safe = Math.max(0, Math.round(ms)); const hours = Math.floor(safe / 3_600_000); const minutes = Math.floor((safe % 3_600_000) / 60_000); const seconds = Math.floor((safe % 60_000) / 1000); const millis = safe % 1000; return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")},${String(millis).padStart(3, "0")}`; }; const shiftSrtSubtitleTiming = (input) => { const unit = fieldChoice(input, "unit", ["s", "ms"], "s"); const amount = fieldNumber(input, "offset", 0); const offsetMs = unit === "ms" ? amount : amount * 1000; const body = fieldText(input, "subtitles"); let shiftedCount = 0; const output = body.replace(/(\d{2}:\d{2}:\d{2},\d{3})\s+-->\s+(\d{2}:\d{2}:\d{2},\d{3})/g, (_match, start, end) => { shiftedCount += 1; return `${msToSrtTime(srtTimeToMs(start) + offsetMs)} --> ${msToSrtTime(srtTimeToMs(end) + offsetMs)}`; }); return { summary: `${shiftedCount} subtitle timestamp row${shiftedCount === 1 ? "" : "s"} shifted by ${offsetMs} ms.`, issues: shiftedCount ? [] : [{ severity: "warning", message: "No SRT timestamp rows found." }], output, exportFilename: "shifted-subtitles.srt", stats: { timestampRows: shiftedCount, offsetMs } }; }; const __userInput = userInput == null ? {} : userInput; const __run = (fields) => shiftSrtSubtitleTiming(fields); const __fields = __userInput && typeof __userInput === "object" && "fields" in __userInput && __userInput.fields && typeof __userInput.fields === "object" && !Array.isArray(__userInput.fields) ? __userInput.fields : (__userInput && typeof __userInput === "object" && !Array.isArray(__userInput) ? __userInput : {}); const __normalizedFields = Object.fromEntries(Object.entries(__fields).map(([key, value]) => [key, value == null ? "" : (["string", "number", "boolean"].includes(typeof value) ? value : String(value))])); return __run(__normalizedFields); } ``` ## Checks - Offset amount: Seconds or milliseconds are converted into a single millisecond offset. - SRT timestamp rows: Every HH:MM:SS,mmm to HH:MM:SS,mmm row is shifted. - Forward and backward shifts: Positive offsets delay captions; negative offsets move them earlier without going below zero. - File text preservation: Non-timestamp subtitle text is kept as-is in the output. - Timing-only scope: The tool does not fix line length, resync drifting subtitles, or inspect video/audio timing. ## Related Tools - [SRT Subtitle Line Length Checker](/business-tools/srt-subtitle-line-length-checker/): Check SRT captions for long lines, too many text rows, and readability issues before publishing.