video-educativo
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseVideo Educativo - Ecuademy
教育视频 - Ecuademy
Genera videos educativos de matemáticas para Ecuademy (academia de matemáticas virtual).
Skills requeridas: ,
/elevenlabs-tts/remotion为Ecuademy(虚拟数学学院)生成数学教育视频。
所需技能: 、
/elevenlabs-tts/remotionÍndice de Tareas (Orden de Ejecución)
任务索引(执行顺序)
El agente DEBE seguir este orden de tareas:
| # | Tarea | Tipo | Sección |
|---|---|---|---|
| 1 | Crear estructura del proyecto Remotion | Secuencial | §1 |
| 2 | Crear componente Whiteboard.tsx | Secuencial | §2 |
| 3 | Escribir guiones de cada escena | Secuencial | §3 |
| 4 | Crear planificación visual (PLANIFICACION.md) | Secuencial | §3 |
| 5 | PAUSA: Esperar aprobación del usuario | PAUSA | §3.1 |
| 6 | Generar audios con ElevenLabs TTS | Secuencial | §4 |
| 7 | Acelerar audios con ffmpeg | Secuencial | §4 |
| 8 | Transcribir audios con Whisper | Secuencial | §5 |
| 9 | Generar componentes de escenas | Secuencial | §6 |
| 10 | Crear MainComposition.tsx y Root.tsx | Secuencial | §7 |
| 11 | Verificar compilación TypeScript | Secuencial | §8 |
| 12 | Renderizar video | Secuencial | §8 |
代理必须遵循以下任务顺序:
| # | 任务 | 类型 | 章节 |
|---|---|---|---|
| 1 | 创建Remotion项目结构 | 顺序执行 | §1 |
| 2 | 创建Whiteboard.tsx组件 | 顺序执行 | §2 |
| 3 | 编写每个场景的脚本 | 顺序执行 | §3 |
| 4 | 创建视觉规划文件(PLANIFICACION.md) | 顺序执行 | §3 |
| 5 | 暂停:等待用户批准 | 暂停 | §3.1 |
| 6 | 使用ElevenLabs TTS生成音频 | 顺序执行 | §4 |
| 7 | 使用ffmpeg加速音频 | 顺序执行 | §4 |
| 8 | 使用Whisper转录音频 | 顺序执行 | §5 |
| 9 | 生成场景组件 | 顺序执行 | §6 |
| 10 | 创建MainComposition.tsx和Root.tsx | 顺序执行 | §7 |
| 11 | 验证TypeScript编译 | 顺序执行 | §8 |
| 12 | 渲染视频 | 顺序执行 | §8 |
§1. Estructura del Proyecto
§1. 项目结构
proyecto/
├── public/
│ ├── audio/
│ │ ├── scene1.mp3
│ │ └── ...
│ └── logo_cuadrado_ecuademy.png
├── src/
│ ├── components/
│ │ ├── scenes/
│ │ │ ├── Scene1Problema.tsx
│ │ │ ├── Scene2Datos.tsx
│ │ │ └── ...
│ │ └── shared/
│ │ └── Whiteboard.tsx
│ ├── data/
│ │ ├── scene1_timestamps.json
│ │ └── ...
│ ├── Root.tsx
│ └── MainComposition.tsx
├── scripts/
│ ├── transcribe_audio.py
│ ├── guiones/
│ │ ├── scene1.txt
│ │ └── ...
│ └── PLANIFICACION.md
└── package.jsonDependencias necesarias:
bash
npm install remotion @remotion/cli @remotion/bundler @remotion/renderer @remotion/media-utils @remotion/transitions @remotion/google-fonts react react-dom typescriptproyecto/
├── public/
│ ├── audio/
│ │ ├── scene1.mp3
│ │ └── ...
│ └── logo_cuadrado_ecuademy.png
├── src/
│ ├── components/
│ │ ├── scenes/
│ │ │ ├── Scene1Problema.tsx
│ │ │ ├── Scene2Datos.tsx
│ │ │ └── ...
│ │ └── shared/
│ │ └── Whiteboard.tsx
│ ├── data/
│ │ ├── scene1_timestamps.json
│ │ └── ...
│ ├── Root.tsx
│ └── MainComposition.tsx
├── scripts/
│ ├── transcribe_audio.py
│ ├── guiones/
│ │ ├── scene1.txt
│ │ └── ...
│ └── PLANIFICACION.md
└── package.json所需依赖:
bash
npm install remotion @remotion/cli @remotion/bundler @remotion/renderer @remotion/media-utils @remotion/transitions @remotion/google-fonts react react-dom typescript§2. Componente Whiteboard.tsx
§2. Whiteboard.tsx组件
Crear en :
src/components/shared/Whiteboard.tsxtypescript
import React from 'react';
import { AbsoluteFill, Img, staticFile } from 'remotion';
interface WhiteboardProps {
children: React.ReactNode;
}
export const Whiteboard: React.FC<WhiteboardProps> = ({ children }) => {
return (
<AbsoluteFill
style={{
backgroundColor: '#FAFAFA',
backgroundImage: `
linear-gradient(90deg, rgba(200,200,200,0.03) 1px, transparent 1px),
linear-gradient(rgba(200,200,200,0.03) 1px, transparent 1px)
`,
backgroundSize: '20px 20px',
}}
>
{/* Marco superior */}
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 12, backgroundColor: '#146A4B' }} />
{/* Marco inferior */}
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 12, backgroundColor: '#146A4B' }} />
{/* Contenido */}
<div style={{ padding: '30px 60px', height: '100%', boxSizing: 'border-box' }}>
{children}
</div>
{/* Logo Ecuademy */}
<Img
src={staticFile('logo_cuadrado_ecuademy.png')}
style={{ position: 'absolute', bottom: 30, right: 40, width: 70, height: 70, opacity: 0.85 }}
/>
</AbsoluteFill>
);
};在中创建:
src/components/shared/Whiteboard.tsxtypescript
import React from 'react';
import { AbsoluteFill, Img, staticFile } from 'remotion';
interface WhiteboardProps {
children: React.ReactNode;
}
export const Whiteboard: React.FC<WhiteboardProps> = ({ children }) => {
return (
<AbsoluteFill
style={{
backgroundColor: '#FAFAFA',
backgroundImage: `
linear-gradient(90deg, rgba(200,200,200,0.03) 1px, transparent 1px),
linear-gradient(rgba(200,200,200,0.03) 1px, transparent 1px)
`,
backgroundSize: '20px 20px',
}}
>
{/* 顶部边框 */}
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 12, backgroundColor: '#146A4B' }} />
{/* 底部边框 */}
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 12, backgroundColor: '#146A4B' }} />
{/* 内容区域 */}
<div style={{ padding: '30px 60px', height: '100%', boxSizing: 'border-box' }}>
{children}
</div>
{/* Ecuademy标志 */}
<Img
src={staticFile('logo_cuadrado_ecuademy.png')}
style={{ position: 'absolute', bottom: 30, right: 40, width: 70, height: 70, opacity: 0.85 }}
/>
</AbsoluteFill>
);
};Identidad Visual Ecuademy
Ecuademy视觉标识
| Elemento | Color | Uso |
|---|---|---|
| Títulos/Énfasis | | Verde Ecuademy |
| Texto principal | | Texto normal |
| Resultados | | Verde para respuestas |
| Importante | | Rojo para destacar |
| Cajas resaltadas | | Fondo verde claro |
| 元素 | 颜色 | 用途 |
|---|---|---|
| 标题/强调内容 | | Ecuademy绿色 |
| 主要文本 | | 普通文本 |
| 结果内容 | | 答案用绿色 |
| 重要内容 | | 红色突出显示 |
| 高亮框 | | 浅绿色背景 |
§3. Planificación y Guiones
§3. 规划与脚本
Planifica el contenido del vídeo. El vídeo debe ayudar al alumno a comprender cómo se resuelve el ejercicio, ten en cuenta que el alumno ya ha intentado resolver el ejercicio y ha tenido dificultados, por lo que explicale todo bien para que lo comprenda. El alumno es un estudiante de secundaria en España, no des por hecho que tiene conocimientos que no tiene.
规划视频内容。视频应帮助学生理解如何解决习题,请注意学生已经尝试过解决该习题但遇到了困难,因此要详细讲解以便其理解。该学生是西班牙的一名中学生,不要假设他拥有未知的知识。
Estructura obligatoria del video
视频必填结构
- Escena 1 - Presentación del problema: Enunciado completo
- Escenas intermedias: Desarrollo paso a paso
- Escena final: Respuesta y conclusión
- 场景1 - 问题介绍:完整题目
- 中间场景:分步讲解
- 最终场景:答案与总结
Archivos a crear
需创建的文件
- Guiones en - Solo texto de narración
scripts/guiones/sceneN.txt - Planificación en - Descripción visual de cada escena
scripts/PLANIFICACION.md
- 脚本 位于- 仅包含旁白文本
scripts/guiones/sceneN.txt - 规划文件 位于- 每个场景的视觉描述
scripts/PLANIFICACION.md
§3.1. PAUSA: Aprobación de Planificación (OBLIGATORIO)
§3.1. 暂停:规划批准(必填)
IMPORTANTE: El agente DEBE detenerse aquí y esperar la aprobación del usuario.
Después de crear los guiones y la planificación visual, el agente debe:
-
Mostrar un resumen de lo creado:
- Número de escenas planificadas
- Duración estimada del video
- Estructura general del contenido
-
Indicar los archivos a revisar:
- - Diseño visual detallado de cada escena
scripts/PLANIFICACION.md - - Textos de narración
scripts/guiones/scene*.txt
-
Preguntar explícitamente al usuario:"He completado la planificación visual y los guiones. Por favor revisa los archivos:
scripts/PLANIFICACION.mdscripts/guiones/
¿Está todo correcto? ¿Puedo continuar con la generación de audios y escenas?" ¿Cuánto debo acelerar el vídeo? -
Esperar respuesta antes de continuar con §4.
重要提示:代理必须在此处暂停并等待用户批准。
创建完脚本和视觉规划后,代理必须:
-
展示创建内容的摘要:
- 规划的场景数量
- 视频预估时长
- 内容整体结构
-
指出需审核的文件:
- - 每个场景的详细视觉设计
scripts/PLANIFICACION.md - - 旁白文本
scripts/guiones/scene*.txt
-
明确询问用户:"我已完成视觉规划和脚本的创建,请审核以下文件:
scripts/PLANIFICACION.mdscripts/guiones/
一切是否正确?我可以继续生成音频和场景吗?" 您希望将视频音频加速多少倍? -
等待回复后再继续§4的内容。
Qué revisar en la planificación
规划审核要点
El usuario debe verificar:
- Correctitud matemática del problema y solución
- Orden lógico de las escenas
- Descripción clara de elementos visuales
- Posición correcta de figuras, etiquetas y símbolos
- Colores y estilos apropiados
用户需验证:
- 问题与解答的数学正确性
- 场景的逻辑顺序
- 视觉元素描述清晰
- 图形、标签和符号的位置正确
- 颜色和风格合适
Continuar solo si
仅在以下情况继续
El usuario responde afirmativamente (ej: "ok", "continúa", "aprobado", "sí", "correcto").
Si el usuario indica cambios, el agente debe modificar los guiones/planificación y volver a pedir aprobación.
El usuario indicará cuanto quiere acelerar el audio del vídeo, tenlo en cuenta para el siguiente paso.
用户给出肯定答复(例如:"好的"、"继续"、"批准"、"是"、"正确")。
如果用户要求修改,代理需修改脚本/规划并再次请求批准。用户会指明希望将音频加速多少倍,请在下一步中注意该设置。
§4. Generación de Audio
§4. 音频生成
Paso 1: Generar con ElevenLabs
步骤1:使用ElevenLabs生成
Usar para cada guion:
/elevenlabs-ttsbash
python3 scripts/generate_audio.py "texto del guion" public/audio/sceneN_original.mp3为每个脚本使用:
/elevenlabs-ttsbash
python3 scripts/generate_audio.py "texto del guion" public/audio/sceneN_original.mp3Paso 2: Acelerar con ffmpeg
步骤2:使用ffmpeg加速
bash
for f in public/audio/*_original.mp3; do
output="${f/_original/}"
ffmpeg -y -i "$f" -filter:a "atempo=1.5" "$output"
donebash
for f in public/audio/*_original.mp3; do
output="${f/_original/}"
ffmpeg -y -i "$f" -filter:a "atempo=1.5" "$output"
donePaso 3: Obtener duraciones
步骤3:获取时长
bash
undefinedbash
undefinedmacOS
macOS
afinfo public/audio/scene1.mp3 | grep duration
afinfo public/audio/scene1.mp3 | grep duration
Linux
Linux
ffprobe -i public/audio/scene1.mp3 -show_entries format=duration -v quiet -of csv="p=0"
---ffprobe -i public/audio/scene1.mp3 -show_entries format=duration -v quiet -of csv="p=0"
---§5. Transcripción con Whisper
§5. 使用Whisper转录
Crear :
scripts/transcribe_audio.pypython
import whisper, json, glob
from pathlib import Path
model = whisper.load_model("base")
for audio_path in sorted(glob.glob("public/audio/scene*.mp3")):
if "_original" in audio_path:
continue
scene_name = Path(audio_path).stem
result = model.transcribe(audio_path, language="es", word_timestamps=True)
words = [{"word": w["word"].strip(), "start": round(w["start"], 2)}
for seg in result["segments"] if "words" in seg for w in seg["words"]]
output = {"scene": scene_name, "text": result["text"], "words": words}
Path("src/data").mkdir(parents=True, exist_ok=True)
with open(f"src/data/{scene_name}_timestamps.json", "w") as f:
json.dump(output, f, ensure_ascii=False, indent=2)Ejecutar:
python3 scripts/transcribe_audio.py创建:
scripts/transcribe_audio.pypython
import whisper, json, glob
from pathlib import Path
model = whisper.load_model("base")
for audio_path in sorted(glob.glob("public/audio/scene*.mp3")):
if "_original" in audio_path:
continue
scene_name = Path(audio_path).stem
result = model.transcribe(audio_path, language="es", word_timestamps=True)
words = [{"word": w["word"].strip(), "start": round(w["start"], 2)}
for seg in result["segments"] if "words" in seg for w in seg["words"]]
output = {"scene": scene_name, "text": result["text"], "words": words}
Path("src/data").mkdir(parents=True, exist_ok=True)
with open(f"src/data/{scene_name}_timestamps.json", "w") as f:
json.dump(output, f, ensure_ascii=False, indent=2)执行:
python3 scripts/transcribe_audio.py§6. Generación de Escenas
§6. 场景生成
Crear cada componente de escena en siguiendo el patrón obligatorio.
src/components/scenes/在中创建每个场景组件,必须遵循以下模板。
src/components/scenes/Patrón obligatorio para cada escena
每个场景的必填模板
typescript
import { Audio, interpolate, useCurrentFrame, useVideoConfig, staticFile } from 'remotion';
import { loadFont } from '@remotion/google-fonts/Caveat';
import { Whiteboard } from '../shared/Whiteboard';
const { fontFamily: caveat } = loadFont();
export const SceneNNombre: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const currentTime = frame / fps;
const TIMESTAMPS = { /* extraídos de Whisper */ };
// Fade in simple
const getOpacity = (start: number) => {
return interpolate(currentTime, [start, start + 0.3], [0, 1],
{ extrapolateLeft: 'clamp', extrapolateRight: 'clamp' });
};
// ✍️ ANIMACIÓN DE ESCRITURA EN PIZARRA
// Simula que el texto se escribe carácter por carácter
const writeText = (text: string, start: number, charsPerSecond: number = 15) => {
const duration = text.length / charsPerSecond;
const progress = interpolate(currentTime, [start, start + duration], [0, text.length],
{ extrapolateLeft: 'clamp', extrapolateRight: 'clamp' });
return text.substring(0, Math.floor(progress));
};
return (
<Whiteboard>
<Audio src={staticFile('audio/sceneN.mp3')} />
<svg width="100%" height="100%" viewBox="0 0 1920 1080">
{/* Contenido con efecto escritura */}
</svg>
</Whiteboard>
);
};typescript
import { Audio, interpolate, useCurrentFrame, useVideoConfig, staticFile } from 'remotion';
import { loadFont } from '@remotion/google-fonts/Caveat';
import { Whiteboard } from '../shared/Whiteboard';
const { fontFamily: caveat } = loadFont();
export const SceneNNombre: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const currentTime = frame / fps;
const TIMESTAMPS = { /* 从Whisper提取 */ };
// 简单淡入效果
const getOpacity = (start: number) => {
return interpolate(currentTime, [start, start + 0.3], [0, 1],
{ extrapolateLeft: 'clamp', extrapolateRight: 'clamp' });
};
// ✍️ 白板书写动画
// 模拟文本逐字符书写的效果
const writeText = (text: string, start: number, charsPerSecond: number = 15) => {
const duration = text.length / charsPerSecond;
const progress = interpolate(currentTime, [start, start + duration], [0, text.length],
{ extrapolateLeft: 'clamp', extrapolateRight: 'clamp' });
return text.substring(0, Math.floor(progress));
};
return (
<Whiteboard>
<Audio src={staticFile('audio/sceneN.mp3')} />
<svg width="100%" height="100%" viewBox="0 0 1920 1080">
{/* 带书写效果的内容 */}
</svg>
</Whiteboard>
);
};Animación de Escritura en Pizarra
白板书写动画
IMPORTANTE: Todo texto matemático debe aparecer con efecto de escritura, NO instantáneamente.
重要提示: 所有数学文本必须以书写效果呈现,不能直接显示。
Función writeText
writeTextwriteText
函数
writeTexttypescript
// Parámetros:
// - text: El texto a escribir
// - start: Timestamp de inicio (segundos)
// - charsPerSecond: Velocidad de escritura (default: 15 chars/s)
const writeText = (text: string, start: number, charsPerSecond: number = 15) => {
const duration = text.length / charsPerSecond;
const progress = interpolate(currentTime, [start, start + duration], [0, text.length],
{ extrapolateLeft: 'clamp', extrapolateRight: 'clamp' });
return text.substring(0, Math.floor(progress));
};typescript
// 参数:
// - text: 要书写的文本
// - start: 开始时间戳(秒)
// - charsPerSecond: 书写速度(默认:15字符/秒)
const writeText = (text: string, start: number, charsPerSecond: number = 15) => {
const duration = text.length / charsPerSecond;
const progress = interpolate(currentTime, [start, start + duration], [0, text.length],
{ extrapolateLeft: 'clamp', extrapolateRight: 'clamp' });
return text.substring(0, Math.floor(progress));
};Uso en SVG
在SVG中使用
tsx
// Texto simple
<text x={100} y={200} fontFamily={caveat} fontSize={48} fill="#2C3E50">
{writeText("y = 2x + 3", TIMESTAMPS.ecuacion)}
</text>
// Ecuación en pasos (cada paso empieza cuando termina el anterior)
const paso1 = "2x + 5 = 11";
const paso2 = "2x = 11 - 5";
const paso3 = "2x = 6";
const paso4 = "x = 3";
<text x={100} y={200}>{writeText(paso1, TIMESTAMPS.paso1)}</text>
<text x={100} y={280}>{writeText(paso2, TIMESTAMPS.paso2)}</text>
<text x={100} y={360}>{writeText(paso3, TIMESTAMPS.paso3)}</text>
<text x={100} y={440}>{writeText(paso4, TIMESTAMPS.resultado, 10)}</text> {/* más lento */}tsx
// 简单文本
<text x={100} y={200} fontFamily={caveat} fontSize={48} fill="#2C3E50">
{writeText("y = 2x + 3", TIMESTAMPS.ecuacion)}
</text>
// 分步方程式(每一步在前一步结束后开始)
const paso1 = "2x + 5 = 11";
const paso2 = "2x = 11 - 5";
const paso3 = "2x = 6";
const paso4 = "x = 3";
<text x={100} y={200}>{writeText(paso1, TIMESTAMPS.paso1)}</text>
<text x={100} y={280}>{writeText(paso2, TIMESTAMPS.paso2)}</text>
<text x={100} y={360}>{writeText(paso3, TIMESTAMPS.paso3)}</text>
<text x={100} y={440}>{writeText(paso4, TIMESTAMPS.resultado, 10)}</text> {/* 更慢 */}Velocidades recomendadas
推荐速度
| Tipo de contenido | charsPerSecond | Ejemplo |
|---|---|---|
| Títulos | 20-25 | Rápido, impactante |
| Ecuaciones | 12-15 | Velocidad normal |
| Resultados importantes | 8-10 | Lento, énfasis |
| Explicaciones largas | 18-20 | Fluido |
| 内容类型 | charsPerSecond | 示例 |
|---|---|---|
| 标题 | 20-25 | 快速、有冲击力 |
| 方程式 | 12-15 | 正常速度 |
| 重要结果 | 8-10 | 慢速、强调 |
| 长篇讲解 | 18-20 | 流畅自然 |
Fuente Caveat (estilo manuscrito)
Caveat字体(手写风格)
Usar siempre la fuente Caveat para simular escritura a mano:
typescript
import { loadFont } from '@remotion/google-fonts/Caveat';
const { fontFamily: caveat } = loadFont();
// En SVG
<text fontFamily={caveat} fontSize={48}>...</text>
// En HTML/CSS
<p style={{ fontFamily: caveat, fontSize: 48 }}>...</p>始终使用Caveat字体模拟手写效果:
typescript
import { loadFont } from '@remotion/google-fonts/Caveat';
const { fontFamily: caveat } = loadFont();
// 在SVG中
<text fontFamily={caveat} fontSize={48}>...</text>
// 在HTML/CSS中
<p style={{ fontFamily: caveat, fontSize: 48 }}>...</p>§7. Composición Final
§7. 最终合成
Crear :
src/MainComposition.tsxtypescript
import { TransitionSeries, linearTiming } from '@remotion/transitions';
import { fade } from '@remotion/transitions/fade';
const FPS = 30;
const SCENE_DURATIONS = {
scene1: Math.ceil(DURACION_AUDIO_1 * FPS) + 15,
scene2: Math.ceil(DURACION_AUDIO_2 * FPS) + 15,
// ...
};
export const MainComposition: React.FC = () => {
return (
<TransitionSeries>
<TransitionSeries.Sequence durationInFrames={SCENE_DURATIONS.scene1}>
<Scene1Problema />
</TransitionSeries.Sequence>
<TransitionSeries.Transition presentation={fade()} timing={linearTiming({durationInFrames: 15})} />
{/* ... más escenas */}
</TransitionSeries>
);
};Crear y para registrar la composición.
src/Root.tsxsrc/index.ts创建:
src/MainComposition.tsxtypescript
import { TransitionSeries, linearTiming } from '@remotion/transitions';
import { fade } from '@remotion/transitions/fade';
const FPS = 30;
const SCENE_DURATIONS = {
scene1: Math.ceil(DURACION_AUDIO_1 * FPS) + 15,
scene2: Math.ceil(DURACION_AUDIO_2 * FPS) + 15,
// ...
};
export const MainComposition: React.FC = () => {
return (
<TransitionSeries>
<TransitionSeries.Sequence durationInFrames={SCENE_DURATIONS.scene1}>
<Scene1Problema />
</TransitionSeries.Sequence>
<TransitionSeries.Transition presentation={fade()} timing={linearTiming({durationInFrames: 15})} />
{/* ... 更多场景 */}
</TransitionSeries>
);
};创建和以注册合成组件。
src/Root.tsxsrc/index.ts§8. Verificación y Renderizado
§8. 验证与渲染
Verificar TypeScript
验证TypeScript
bash
npx tsc --noEmitbash
npx tsc --noEmitIniciar Studio (preview)
启动Studio(预览)
bash
npx remotion studiobash
npx remotion studioRenderizar video final
渲染最终视频
bash
npx remotion render MainComposition out/video.mp4bash
npx remotion render MainComposition out/video.mp4§9. Troubleshooting
§9. 故障排除
| Problema | Causa | Solución |
|---|---|---|
| Audio se corta | Duración en frames < duración audio | |
| Animaciones antes del audio | Timestamps incorrectos | Verificar JSON de Whisper |
| Fuente no carga | Import incorrecto | |
| SVG cortado | viewBox incorrecto | |
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 音频被截断 | 帧时长 < 音频时长 | |
| 动画早于音频播放 | 时间戳错误 | 检查Whisper生成的JSON文件 |
| 字体未加载 | 导入错误 | |
| SVG被截断 | viewBox错误 | |
Checklist Final
最终检查清单
- §1: Estructura de proyecto creada
- §2: Whiteboard.tsx creado
- §3: Guiones y PLANIFICACION.md creados
- §3.1: APROBACIÓN DEL USUARIO RECIBIDA ← OBLIGATORIO
- §4: Audios generados y acelerados
- §5: Timestamps extraídos con Whisper
- §6: Escenas generadas
- §7: MainComposition y Root creados
- §8: TypeScript compila sin errores
- §8: Video renderizado
- §1: 已创建项目结构
- §2: 已创建Whiteboard.tsx
- §3: 已创建脚本和PLANIFICACION.md
- §3.1: 已获得用户批准 ← 必填
- §4: 已生成并加速音频
- §5: 已使用Whisper提取时间戳
- §6: 已生成场景组件
- §7: 已创建MainComposition和Root
- §8: TypeScript编译无错误
- §8: 已渲染视频