Generaste subtítulos con IA para un video corto y cada frase aparece 1,2 segundos tarde. Corregir línea por línea es innecesario si el error es constante: basta desplazar todas las marcas de tiempo del archivo SRT. Antes de tocarlo, compara un subtítulo cerca del inicio con otro cerca del final. Si ambos llegan 1,2 segundos tarde, el desplazamiento sirve; si el error crece, el problema es de velocidad o de edición y este script no lo arregla.
Un archivo SRT contiene bloques numerados, cada uno con hora de inicio y fin en formato horas:minutos:segundos,milisegundos. Es texto separado del video; por eso puedes cambiar el tiempo sin recomprimir imagen ni audio. La Biblioteca del Congreso de Estados Unidos describe esa estructura y advierte que el formato no fija una única codificación. El ejemplo de abajo lee UTF-8 con o sin BOM y escribe UTF-8.

Primero mide, no adivines
Reproduce el video con el SRT original y pausa donde escuchas el inicio de una frase. Si el sonido comienza en 00:00:10,000 pero el subtítulo aparece en 00:00:11,200, está 1.200 milisegundos tarde. Para adelantarlo, usa un desplazamiento negativo de -1200. Si aparece antes que la voz, usa uno positivo. Repite la medición a mitad y final: un offset constante mueve todos los subtítulos igual; no estira ni comprime el tiempo.
Script probado: desplaza sin pisar el original
Guarda el siguiente código como shift_srt.py. Exige un archivo de salida distinto, rechaza marcas de tiempo inválidas y detiene la operación si algún subtítulo quedaría antes de cero. Mantiene los números y el texto de cada bloque. Está pensado para SRT simple; no procesa WebVTT ni archivos con anotaciones extra en la línea de tiempo.
#!/usr/bin/env python3
"""Shift every SRT cue by a constant number of milliseconds."""
import argparse
import re
from pathlib import Path
TIME = re.compile(r"^(\d{2,}):(\d{2}):(\d{2}),(\d{3})$")
LINE = re.compile(r"^(\S+) --> (\S+)$")
def milliseconds(stamp):
match = TIME.fullmatch(stamp)
if not match:
raise ValueError(f"Invalid SRT timestamp: {stamp}")
hours, minutes, seconds, millis = map(int, match.groups())
if minutes > 59 or seconds > 59:
raise ValueError(f"Invalid SRT timestamp: {stamp}")
return ((hours * 60 + minutes) * 60 + seconds) * 1000 + millis
def timestamp(value):
if value < 0:
raise ValueError("A cue would start before 00:00:00,000")
hours, rem = divmod(value, 3_600_000)
minutes, rem = divmod(rem, 60_000)
seconds, millis = divmod(rem, 1_000)
return f"{hours:02}:{minutes:02}:{seconds:02},{millis:03}"
def shift(text, offset):
result, cues = [], 0
for line in text.splitlines():
if "-->" not in line:
result.append(line)
continue
match = LINE.fullmatch(line)
if not match:
raise ValueError(f"Invalid SRT cue: {line}")
start, end = map(milliseconds, match.groups())
if end <= start:
raise ValueError(f"Cue end must follow start: {line}")
result.append(f"{timestamp(start + offset)} --> {timestamp(end + offset)}")
cues += 1
if not cues:
raise ValueError("No SRT cues found")
return "\n".join(result) + "\n", cues
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("input", type=Path)
parser.add_argument("output", type=Path)
parser.add_argument("--ms", type=int, required=True, help="positive delays, negative advances")
args = parser.parse_args()
if args.input.resolve() == args.output.resolve():
parser.error("Use a different output path to preserve the original")
try:
fixed, cues = shift(args.input.read_text(encoding="utf-8-sig"), args.ms)
args.output.write_text(fixed, encoding="utf-8")
except (OSError, ValueError) as error:
parser.error(str(error))
print(f"Shifted {cues} cues by {args.ms} ms -> {args.output}")
if __name__ == "__main__":
main()
Ejemplo: para adelantar todo 1,2 segundos, ejecuta python3 shift_srt.py original.srt corregido.srt --ms -1200. Para retrasarlo 750 milisegundos, usa --ms 750. No cambies el archivo original hasta validar el resultado en tu editor o reproductor.
Qué comprobé y qué debes comprobar tú
Probé el script localmente con dos bloques: 00:00:02,500 pasó a 00:00:01,300 con -1200, y 00:01:00,000 pasó a 00:00:58,800. También comprobé que un desplazamiento que deja un tiempo negativo se rechaza. Es una prueba del código, no de la sincronía de tu video. Abre el archivo nuevo y escucha al menos tres puntos: inicio, mitad y final. Mira que no se corten frases, que el final de un bloque no se superponga con el siguiente y que la plataforma importe el SRT.
Si al inicio el error es 1,2 segundos y al final 3,4, el desfase es progresivo. Puede deberse a un cambio de velocidad, a un corte de video posterior a la transcripción o a una base temporal distinta. Ahí toca volver a sincronizar por tramos o regenerar subtítulos sobre la versión final del audio. Si el texto está mal transcrito, mover tiempos tampoco lo corrige: escucha y revisa nombres, cifras y citas antes de publicar. Para subtítulos ya quemados en la imagen, necesitas editar el video; un SRT externo no borra esos píxeles.
Esta guía es para creadores que ya tienen un SRT editable. Si todavía necesitas revisar exactitud textual, empieza por nuestra guía para comprobar transcripciones de IA. El valor aquí es una corrección de sincronía medible, reversible y verificable en tu propio material.
