Cómo verificar un video tras aumentar la tasa de fotogramas en Runway
Flujo completo desde preparar y subir un video local hasta enviar enhance_frame_rate, consultar la tarea, guardar la salida y verificar FPS, artefactos y audio.
Índice

Puede que tengas un video local terminado, pero todavía no una salida de Runway con la nueva tasa de fotogramas. Antes de verificar la entrega debes preparar y subir el archivo, enviar targetFramerate, esperar la tarea y guardar el resultado. Esta guía cubre todo ese flujo REST y después comprueba FPS, artefactos de movimiento, cortes y sincronía de audio.
Runway incorporó enhance_frame_rate a Runway Dev el 17 de septiembre de 2026. La operación utiliza el video upscale endpoint y acepta 24, 25, 30, 48, 50, 60, 120, 23_98 (23,98 fps), 29_97 (29,97 fps) y 59_94 (59,94 fps); cada entrada está limitada a 300 segundos y la nota de lanzamiento indica 1 credit por cada 2 segundos.
No apruebes el resultado solo porque se ve más fluido. Elige primero la cadencia exacta del destino, después revisa metadatos, movimientos de riesgo, cortes y sincronía al principio, en el centro y al final, y termina probando el archivo en la línea de tiempo y plataforma reales.
Elige primero la cadencia de entrega; detente si no hay una especificación exacta
Elige el valor exacto de la línea de tiempo, el broadcaster, la plataforma publicitaria o la especificación del cliente antes de enviar nada. 29_97 y 30, igual que 59_94 y 60, parecen casi iguales, pero una sustitución incorrecta puede obligarte a transcodificar de nuevo o repetir el proceso en trabajos largos, broadcast y proyectos con fuentes mixtas.
| Objetivo | Motivo habitual para elegirlo | Confirma antes de entregar |
|---|---|---|
23_98 / 24 | La línea de tiempo o el cliente exige una cadencia cinematográfica | Si se requiere exactamente 23,98 y no 24 entero |
25 / 50 | Flujo de producción a 25/50 fps o especificación regional | Que línea de tiempo, subtítulos, audio y demás material usen la misma cadencia |
29_97 / 30 | El sistema de destino nombra explícitamente uno de los dos | No sustituir uno por otro sin aprobación |
59_94 / 60 | Contenido con mucho movimiento o una plataforma que pide alta tasa | Una reproducción más fluida no significa recuperar detalle perdido |
48 / 120 | Línea de tiempo específica, cámara lenta o entrega de alta tasa | Usarlo solo si el destino lo exige; un número mayor no es siempre mejor |
Si el encargo solo dice “hazlo más fluido”, pide la especificación final. De lo contrario, puedes crear un archivo válido a 60 fps que no encaje correctamente en una línea de tiempo de 59,94 fps.
Guarda la línea base para poder localizar cualquier fallo
Registra antes del proceso la tasa, duración, códec y pistas de audio del original. Sin esa referencia será difícil saber si una pista perdida, una duración distinta o una cola congelada provienen del original, de la salida de Runway o de una transcodificación posterior.
- Nombre del archivo, duración, dimensiones, códec y tasa original.
- Si la fuente usa tasa constante (CFR) o variable (VFR).
- Número de pistas de audio, sample rate, canales y duración aproximada.
- Tasa objetivo y origen del requisito, por ejemplo: “el cliente exige
59_94”. - De tres a cinco timecodes de riesgo: paneos rápidos, manos, líneas finas, bordes de oclusión, destellos, transiciones, subtítulos o UI.
- Al menos tres anclas de sincronización cerca del inicio, centro y final.
Esta línea base evita que la revisión se reduzca a “se ve más fluido” mientras pasan inadvertidos un cambio de duración, una pista perdida o una incompatibilidad de entrega.
Confirma primero que el video local puede entrar en el flujo
Comprueba formato, duración y tamaño antes de llamar a la API. Una entrada de enhance_frame_rate no puede superar 300 segundos y una carga efímera debe ocupar entre 512 bytes y 200 MB. Prioriza contenedores y códecs compatibles, como MP4 con H.264, H.265 o AV1, y divide una pieza más larga en cortes naturales.
Si el video ya está en almacenamiento de objetos, puedes pasar su URL HTTPS directamente como videoUri. La URL debe usar un dominio, no una IP, admitir HEAD, devolver Content-Type y Content-Length válidos y no depender de redirecciones; el límite de video por URL es 32 MB. Para un máster local normal, la carga efímera evita esos requisitos de alojamiento.
Confirma también que la cuenta tenga credits comprados. La nota de lanzamiento indica 1 credit por 2 segundos, pero no explica el redondeo de fracciones, por lo que conviene guardar estimatedCost al enviar y el cost final de la tarea.
Usa este script para subir, enviar, esperar y descargar
El ejemplo usa REST y deja visibles todos los pasos críticos. Solicita la API Key sin mostrarla, valida duración y tamaño, crea una carga efímera, transfiere el archivo, envía la tarea, consulta cada cinco segundos y descarga la salida correcta.
Instala la dependencia de Python y comprueba que ffprobe esté disponible:
python3 -m pip install requests
Guarda lo siguiente como runway_fps.py:
from __future__ import annotations
import getpass, json, os, random, subprocess, sys, time
from pathlib import Path
import requests
API = "https://api.dev.runwayml.com"
FPS = {"24", "25", "30", "48", "50", "60", "120", "23_98", "29_97", "59_94"}
RETRYABLE = {429, 502, 503, 504}
def api(session, method, path, body=None):
for attempt in range(6):
response = session.request(method, API + path, json=body, timeout=60)
if response.status_code < 400:
return response
if response.status_code in RETRYABLE and attempt < 5:
time.sleep((2**attempt) * (1 + random.random() * 0.5))
continue
raise RuntimeError(f"HTTP {response.status_code}: {response.text}")
raise RuntimeError("RETRY_LIMIT_REACHED")
def main():
if len(sys.argv) not in {3, 4}:
raise SystemExit("python runway_fps.py INPUT_VIDEO TARGET_FPS [OUTPUT_VIDEO]")
source = Path(sys.argv[1])
target = sys.argv[2]
output = Path(sys.argv[3]) if len(sys.argv) == 4 else Path(f"runway-{target}fps.mp4")
if target not in FPS:
raise SystemExit(f"UNSUPPORTED_TARGET_FRAMERATE: {target}")
if not source.is_file():
raise SystemExit(f"INPUT_NOT_FOUND: {source}")
if not 512 <= source.stat().st_size <= 200 * 1024 * 1024:
raise SystemExit(f"INVALID_UPLOAD_SIZE_BYTES: {source.stat().st_size}")
duration = float(subprocess.run(
["ffprobe", "-v", "error", "-show_entries", "format=duration",
"-of", "default=noprint_wrappers=1:nokey=1", str(source)],
check=True, capture_output=True, text=True,
).stdout.strip())
if not 0 < duration <= 300:
raise SystemExit(f"INVALID_DURATION_SECONDS: {duration}")
key = os.getenv("RUNWAYML_API_SECRET") or getpass.getpass("RUNWAYML_API_SECRET: ")
session = requests.Session()
session.headers.update({
"Authorization": f"Bearer {key}",
"X-Runway-Version": "2024-11-06",
"Content-Type": "application/json",
})
upload_init = api(session, "POST", "/v1/uploads", {
"filename": source.name,
"type": "ephemeral",
}).json()
with source.open("rb") as handle:
upload = requests.post(
upload_init["uploadUrl"],
data=upload_init["fields"],
files={"file": (source.name, handle)},
timeout=300,
)
if upload.status_code >= 400:
raise RuntimeError(
f"UPLOAD_FAILED_REQUEST_NEW_UPLOAD: HTTP {upload.status_code}: {upload.text}"
)
created = api(session, "POST", "/v1/video_upscale", {
"model": "enhance_frame_rate",
"videoUri": upload_init["runwayUri"],
"targetFramerate": target,
}).json()
task_id = created["id"]
print(json.dumps({"id": task_id, "estimatedCost": created.get("estimatedCost")}, indent=2))
while True:
task = api(session, "GET", f"/v1/tasks/{task_id}").json()
status = task["status"]
if status in {"PENDING", "THROTTLED", "RUNNING"}:
time.sleep(5)
continue
if status == "SUCCEEDED":
urls = task.get("output") or []
if not urls:
raise RuntimeError("SUCCEEDED_WITHOUT_OUTPUT")
with requests.get(urls[0], stream=True, timeout=300) as download:
download.raise_for_status()
with output.open("wb") as saved:
for chunk in download.iter_content(1024 * 1024):
if chunk:
saved.write(chunk)
break
if status == "FAILED":
raise RuntimeError(json.dumps({
"status": status,
"failure": task.get("failure"),
"failureCode": task.get("failureCode"),
"cost": task.get("cost"),
}, ensure_ascii=False))
if status == "CANCELLED":
raise RuntimeError(json.dumps({"status": status, "cost": task.get("cost")}))
raise RuntimeError(f"UNKNOWN_TASK_STATUS: {status}")
subprocess.run([
"ffprobe", "-v", "error", "-show_entries",
"stream=codec_name,width,height,r_frame_rate,avg_frame_rate,time_base,duration:format=duration",
"-of", "json", str(output),
], check=True)
print(output.resolve())
if __name__ == "__main__":
main()
Ejemplo para convertir input.mp4 a 60 fps y guardar output-60fps.mp4:
python3 runway_fps.py input.mp4 60 output-60fps.mp4
Introduce la clave solo cuando aparezca RUNWAYML_API_SECRET:. El valor no se muestra ni entra en el historial del shell, y el script no lo escribe en disco. Si la variable de entorno ya está configurada de forma segura, se usa directamente.
Entiende las tres etapas de API del script
La generación solo termina cuando las tres etapas tienen éxito. Los nombres distinguen mayúsculas y minúsculas: el JSON REST debe usar videoUri y targetFramerate.
| Etapa | Solicitud | Contenido obligatorio | Señal de éxito |
|---|---|---|---|
| Inicializar carga | POST https://api.dev.runwayml.com/v1/uploads | filename, type: "ephemeral" | Devuelve uploadUrl, fields, runwayUri |
| Enviar mejora | POST https://api.dev.runwayml.com/v1/video_upscale | model: "enhance_frame_rate", videoUri, targetFramerate | Devuelve id y estimatedCost |
| Consultar tarea | GET https://api.dev.runwayml.com/v1/tasks/{id} | ID en la ruta | status: "SUCCEEDED" y output no vacío |
Después de inicializar, envía un POST multipart a uploadUrl, conserva todos los valores de fields y adjunta el video con el nombre de campo file. runwayUri solo queda listo después de esa transferencia; caduca a las 24 horas.
Descarga y guarda el resultado en cuanto tenga éxito
Sigue esperando con PENDING, THROTTLED o RUNNING; Runway indica que no debe esperarse más de una actualización de la misma tarea cada cinco segundos. Lee output[0] solo con SUCCEEDED. FAILED y CANCELLED son estados terminales no válidos.
Las URLs de salida suelen caducar en 24–48 horas. Descarga el archivo enseguida a almacenamiento persistente y no entregues la URL temporal al usuario final. Si caduca, vuelve a consultar la misma tarea para obtener otra URL antes de pagar por una nueva generación. Descargar correctamente solo confirma el trabajo de API; aún faltan las comprobaciones siguientes.
Trata cada fallo según su tipo
Si falla el POST multipart a uploadUrl, no reutilices esa carga firmada: vuelve a llamar a /v1/uploads. Para 400, 401, 404 o 405, corrige entrada, clave, recurso o método. El script solo reintenta 429, 502, 503 y 504 con espera exponencial y jitter.
Cuando la tarea esté FAILED, guarda failure, failureCode y cost: no reintentes SAFETY.*; corrige el medio antes de reenviar ASSET.INVALID; revisa la entrada antes de repetir INTERNAL.BAD_OUTPUT.*; espera antes de reintentar INPUT_PREPROCESSING.INTERNAL, INTERNAL, un código ausente o THIRD_PARTY.UNAVAILABLE. No repitas indefinidamente la misma solicitud.
Paso 1: usa ffprobe para confirmar que el archivo cumple el objetivo
Usa ffprobe para verificar tasa media, time base, recuento real, duración y pistas de audio antes de juzgar la imagen. Una sola etiqueta FPS del sistema operativo o del reproductor no basta para aceptar el archivo.
ffprobe -v error -select_streams v:0 \
-show_entries stream=codec_name,width,height,r_frame_rate,avg_frame_rate,time_base,duration \
-of json output.mp4
ffprobe -v error -select_streams v:0 -count_frames \
-show_entries stream=nb_read_frames,avg_frame_rate,r_frame_rate,duration \
-of json output.mp4
ffprobe -v error \
-show_entries format=duration:stream=index,codec_type,codec_name,sample_rate,channels,duration \
-of json output.mp4
Comprueba:
avg_frame_ratecoincide con el objetivo o con una representación racional equivalente.r_frame_rateyavg_frame_rateno presentan un conflicto sin explicación; una diferencia grande requiere investigar VFR.- En un archivo cercano a CFR,
nb_read_framesse aproxima a duración multiplicada por fps objetivo. - La duración de salida coincide con la fuente, sin recorte al final ni cola congelada añadida.
- Dimensiones, códec y pistas de audio cumplen la especificación de entrega.
- La duración del audio no difiere de forma inesperada de la del video.
Para 29,97 y 59,94, es normal ver fracciones como 30000/1001 y 60000/1001. La representación fraccionaria no es un fallo.
Paso 2: revisa primero los planos con mayor riesgo
Empieza por movimiento rápido, bordes de oclusión, texto fino y puntos de corte, porque ahí aparecen antes los fallos de interpolación. Revisa lo siguiente al 100 %, fotograma a fotograma o a velocidad reducida:
- paneos, seguimientos y objetos rápidos;
- manos, dedos, pelo, monturas de gafas y labios;
- rejas, persianas, cuadrículas, texto pequeño y líneas finas de UI;
- objetos en primer plano que cruzan o descubren bordes del fondo;
- agua, humo, partículas, hojas y texturas de alta frecuencia;
- destellos, cortes duros, disolvencias y fotogramas alrededor de un cambio de plano.
Busca defectos reproducibles, no una impresión vaga de nitidez: contornos dobles, ghosting, bordes deformados, objetos que desaparecen durante un fotograma, textura pulsante, extremidades deformadas, fotogramas mezclados en un corte o vibración de texto estático.
Cuando encuentres un problema, registra el timecode exacto, la tasa objetivo y los fragmentos de origen y salida. Así podrás separar un defecto preexistente de uno nuevo o de una diferencia del decodificador.
Paso 3: comprueba la sincronía al principio, en el centro y al final
Que el principio esté sincronizado no demuestra que toda la pieza lo esté; revisa principio, centro y final para distinguir un desplazamiento fijo de una deriva progresiva. Sigue este orden:
- Busca una ancla clara al principio: una palmada, una consonante explosiva, un impacto, un aterrizaje o un corte visual.
- Repite la comprobación en el centro y al final.
- Un desplazamiento similar en los tres puntos sugiere un retraso fijo.
- Un error que crece hacia el final apunta a duración, time base o interpretación de la tasa.
- Para lip sync, revisa principio, centro y final de una frase continua, no una sola sílaba.
La nota de lanzamiento no describe el tratamiento del audio. No supongas que la pista siempre se conserva sin cambios ni que queda sincronizada automáticamente: evalúa el archivo entregado.
Paso 4: vuelve a probar en la línea de tiempo y plataforma reales
Prueba siempre el archivo en la línea de tiempo y plataforma finales, porque un NLE o una segunda transcodificación pueden reinterpretar la cadencia, cambiar la velocidad o perder una pista. Haz al menos estas dos comprobaciones:
- Colócalo en la línea de tiempo prevista y confirma que el NLE no lo reinterpreta, cambia su velocidad ni pierde una pista.
- Pruébalo en la plataforma o dispositivo final y confirma que una segunda transcodificación no altera cadencia, subtítulos o sincronía.
Si la plataforma vuelve a transcodificar, conserva tanto la salida de Runway como la versión de la plataforma. Inspecciónalas por separado antes de atribuir el problema al archivo anterior.
Usa esta tabla para aprobar, rehacer o conservar un fragmento original
Aprueba el archivo solo cuando todos los puntos críticos cumplan la especificación. Si falla un solo plano, rehace ese segmento o conserva el original antes de volver a procesar toda la pieza.
| Comprobación | Condición de aprobado | Acción si falla |
|---|---|---|
| Tasa objetivo | Cadencia exacta; 29,97/59,94 no se etiquetan como 30/60 | Corregir el objetivo o la interpretación de la línea de tiempo |
| Duración y fotogramas | Duración igual a la fuente; en CFR el recuento se acerca al esperado | Investigar VFR, truncado, cola congelada y time base |
| Dimensiones y códec | Cumplen los requisitos del editor o canal | Reempaquetar o transcodificar según la especificación |
| Movimiento rápido | Sin dobles imágenes, deformación o desapariciones inaceptables | Marcar timecodes; probar otro objetivo o conservar el fragmento original |
| Cortes y destellos | Sin fotogramas mezclados, repetidos ni parpadeos anómalos | Dividir en un corte natural, reprocesar y revisar la unión |
| Texto y UI | Glifos, líneas finas y overlays estáticos permanecen estables | Volver a aplicar los gráficos en posproducción |
| Sincronía de audio | Sin desplazamiento ni deriva visibles en inicio, centro y final | Comparar duraciones/time base y después realinear o transcodificar |
| Integridad | Decodificación completa, final intacto y todas las pistas presentes | Descargar de nuevo, reempaquetar o repetir la tarea |
No pases de 60 a 120 fps en estos casos
Quédate en la tasa más baja que cumpla la entrega cuando 120 fps solo añada tamaño y trabajo posterior. No aumentes la tasa en los siguientes casos:
- el destino solo exige 24, 25, 29,97 o 30 fps;
- la fuente ya tiene ghosting, bloques de compresión o motion blur fuertes;
- subtítulos, UI o líneas finas se vuelven menos estables;
- todavía no se ha explicado un problema de sincronía;
- la plataforma final forzará una transcodificación a menor tasa;
- no hay beneficio visible, pero aumentan almacenamiento, decodificación o transcodificación posterior.
La tasa de fotogramas es un parámetro de entrega, no una puntuación de calidad aislada. El criterio es “cumple la cadencia requerida sin defectos nuevos inaceptables”, no “tiene el número más alto”.
Completa la entrega en diez pasos
El orden con menos retrabajo es especificación y referencia, carga y envío, espera y almacenamiento, y después revisión técnica, visual y real.
- Elige el
targetFramerateexacto según el destino. - Guarda tasa, duración, audio y timecodes de riesgo con
ffprobe; confirma un máximo de 300 segundos. - Para un archivo local, llama a
POST /v1/uploadsy conservauploadUrl,fields,runwayUri. - Envía el formulario multipart a
uploadUrl; pide una carga nueva si falla. - Llama a
POST /v1/video_upscaleconmodel,videoUri,targetFramerate. - Guarda el
idde tarea yestimatedCost. - Consulta
GET /v1/tasks/{id}cada cinco segundos hasta un estado terminal. - Con
SUCCEEDED, descarga y conservaoutput; trataFAILEDoCANCELLEDpor tipo de error. - Verifica cadencia real, duración, artefactos, cortes y audio con
ffprobey revisión fotograma a fotograma. - Prueba en la línea de tiempo y plataforma finales antes de entregar.
Referencias oficiales: Models, Inputs, Uploads, Video upscale API Reference, Task API Reference, Outputs, HTTP errors, Task failures y API Changelog. Campos y pasos verificados el 26 de septiembre de 2026.