-
Notifications
You must be signed in to change notification settings - Fork 2
single video #30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
single video #30
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughConfiguration sets reactStrictMode to false. API routes switch model to Anthropic Claude 3.5 Sonnet. Manim generation route refactors to produce structured Manim code, adjusts execution, logging, and optional voice narration. Sandbox updates Manim CLI flags. Structured generator adds JSON-driven flow, validation/repair, pacing heuristics, and storage usage. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant API as API: /api/generate-manim
participant Gen as generateStructuredManimCode
participant Sand as executeCodeAndListFiles
participant Store as blobStorage
Note over API,Gen: New flow (direct structured code generation)
User->>API: POST topic (+ includeVoice?)
API->>Gen: Generate structured Manim JSON/code
Gen->>Gen: Validate & repair code + pacing
Gen-->>API: formatted Manim code
API->>Sand: Execute code and list video files
alt Execution success
Sand-->>API: Files + logs + status
API->>Store: (optional) Upload video
API-->>User: {manimCode, videoUrl, logs}
opt includeVoice
API->>Gen: Generate educational narration script
API->>Store: Generate/upload audio
API-->>User: + voiceUrl
end
else Execution failure
Sand-->>API: error + logs
API->>API: Log full failure context
API-->>User: Error (includes serialized logs)
end
sequenceDiagram
autonumber
participant Old as Old Flow
participant New as New Flow
Note over Old: Breakdown -> First lesson script -> Validate -> Execute
Note over New: Structured JSON -> Repair/Pace -> Execute
Old->>Old: generateLessonBreakdown
Old->>Old: generate first lesson script
Old->>Old: validateAndFixManimCode
Old->>Old: execute Manim
New->>New: generateStructuredManimCode (JSON)
New->>New: validateAndFixManimCode + pacing
New->>New: executeCodeAndListFiles (enhanced logs)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/utils/structuredManimGenerator.ts (1)
366-408: Dangerous indentation rewriter will break Python block structureThis trims all lines and re-indents uniformly, destroying nested blocks (for/if/with) and causing syntax errors. Remove this step or use a proper formatter (black) in-sandbox; do not heuristically re-indent.
- // Ensure proper indentation - const lines = fixedCode.split("\n"); - const properlyIndentedLines = []; - let inClass = false; - let inMethod = false; - for (const line of lines) { - const trimmedLine = line.trim(); - if (!trimmedLine) { properlyIndentedLines.push(""); continue; } - if (trimmedLine.startsWith("class ") && trimmedLine.includes("(Scene):")) { - inClass = true; inMethod = false; properlyIndentedLines.push(trimmedLine); continue; - } - if (inClass && trimmedLine.startsWith("def ")) { - inMethod = true; properlyIndentedLines.push(" " + trimmedLine); continue; - } - if (inMethod) { properlyIndentedLines.push(" " + trimmedLine); } - else if (inClass) { properlyIndentedLines.push(" " + trimmedLine); } - else { properlyIndentedLines.push(trimmedLine); } - } - fixedCode = properlyIndentedLines.join("\n"); + // Avoid re-indenting; preserve original indentation to keep Python blocks valid + // If formatting is needed, run a formatter (e.g., black) in the sandbox before rendering.
🧹 Nitpick comments (17)
src/app/api/chatbot/route.ts (1)
99-101: Model switch to Claude 3.5 Sonnet: verify access and harden error pathsGood switch, but ensure the OpenRouter org/model is enabled on your key and map 401/403/429 to actionable client errors. Optionally guard missing API key.
Example guard + error mapping:
// Get AI response using OpenRouter - const { text: responseText } = await generateText({ + if (!process.env.OPENROUTER_API_KEY) { + return NextResponse.json({ error: 'Server misconfigured: missing OPENROUTER_API_KEY' }, { status: 500 }); + } + let responseText: string; + try { + const { text } = await generateText({ model: openrouter("anthropic/claude-3.5-sonnet"), messages: promptMessages - }); + }); + responseText = text; + } catch (e: any) { + const msg = e?.message || 'Upstream model error'; + const isAuth = /401|403/.test(msg); + const isRate = /429/.test(msg); + const status = isRate ? 429 : isAuth ? 502 : 500; + return NextResponse.json({ error: msg }, { status }); + }next.config.ts (1)
4-4: Turning off reactStrictMode hides effects-only bugs in devUnless you have a concrete incompatibility, keep it enabled (dev-only cost). Consider env-gating instead of a hard disable.
- reactStrictMode: false, + reactStrictMode: process.env.NEXT_STRICT_MODE !== 'false',src/app/api/generate-article/route.ts (1)
179-191: Model switch OK; consider schema-driven JSON to avoid brittle parsingClaude tends to comply, but using generateObject + zod (as in structuredManimGenerator) would remove the regex/cleaning path.
Minimal sketch:
-const { text: articleJson } = await generateText({ - model: openrouter("anthropic/claude-3.5-sonnet"), - messages: [...], - temperature: 0.3 -}); -// ... manual JSON extraction +const { object } = await generateObject({ + model: openrouter("anthropic/claude-3.5-sonnet"), + schema: /* zod schema mirroring ParsedArticle */, + prompt: systemPrompt + '\nGenerate the JSON now.' +}); +const parsedArticle = object as ParsedArticle;Also, if you keep deduping, collisions can occur when includeAudio=true with different voice/rate/pitch—consider adding those to requestKey.
src/utils/sandbox.ts (2)
196-198: CLI change LGTM; add explicit media/output for deterministic discoveryCurrent -qm is fine. To make file scraping simpler and resilient, set media dir and output name.
- ? `cd /code && manim -qm ${filename} ${sceneClassName}` - : `cd /code && manim -qm ${filename}`; + ? `cd /code && manim -qm --media_dir /code/media -o output ${filename} ${sceneClassName}` + : `cd /code && manim -qm --media_dir /code/media -o output ${filename}`;
315-315: Nit: logging prefix OKThe added "----------" prefix is harmless and improves greppability.
src/utils/structuredManimGenerator.ts (4)
61-88: Adopting generateObject with a Zod schema is the right moveGood shift to schema-constrained JSON and Claude 3.5 Sonnet. Consider setting temperature to 0.2 and a conservative maxTokens to reduce truncation risk.
-const result = await generateObject({ +const result = await generateObject({ model: openrouter("anthropic/claude-3.5-sonnet"), schema: manimCodeSchema, + temperature: 0.2, + maxTokens: 4096, prompt: `...` });
189-222: Sensible fallback pathThe simplified prompt fallback is a solid recovery strategy. Consider logging result.validation errors (if exposed by the SDK) to see why the first pass failed.
433-466: Wait injection after self.play: guard against pathological patternsApproach is fine, but dynamic regex over arbitrary code can be slow and fragile for multiline play calls. Bound replacements and skip overly long matches.
- const playLines = fixedCode.match(/self\.play\(.*?\)/g); + const playLines = fixedCode.match(/self\.play\([^()\n]*\)/g); // single-line plays only if (playLines && playLines.length > 0) { ... - const playLineEscaped = playLine.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const playLineEscaped = playLine.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + if (playLineEscaped.length > 300) continue; // safety bound const hasWaitAfterPlay = new RegExp( `${playLineEscaped}[\\s\\S]*?self\\.wait\\(`, "m" ).test(fixedCode);
311-323: Text replacement heuristic: keep existing kwargsYour Text("...") replace will drop existing kwargs (e.g., color). Use a callback that preserves trailing args.
-.replace(/Text\("([^"]+)"\)/g, (match, text) => { +.replace(/Text\("([^"]+)"(,[^)]+)?\)/g, (match, text, rest = "") => { if (text.length > 20) { - return `Text("${text}", font_size=32)`; + return `Text("${text}", font_size=32${rest})`; } - return `Text("${text}", font_size=36)`; + return `Text("${text}", font_size=36${rest})`; })src/app/api/generate-manim/route.ts (8)
10-13: Nit: clarify the log message.“generated and validated” happens upstream inside
generateStructuredManimCode; rephrase to avoid future confusion if internals change.- console.log("🤖 Manim code generated and validated."); + console.log("🤖 Manim code generated via structured pipeline (validated upstream).");
14-18: Gate and truncate verbose code logging.Dumping full Manim code to prod logs can be noisy and expensive. Gate by env and truncate.
- console.log("Generated Manim code:"); - console.log("=".repeat(50)); - console.log(manimCode); - console.log("=".repeat(50)); + if (process.env.NODE_ENV !== "production") { + console.log("Generated Manim code (truncated):"); + console.log("=".repeat(50)); + const MAX_LOG_CHARS = 4000; + const display = + manimCode.length > MAX_LOG_CHARS + ? manimCode.slice(0, MAX_LOG_CHARS) + + `\n...[${manimCode.length - MAX_LOG_CHARS} more chars]` + : manimCode; + console.log(display); + console.log("=".repeat(50)); + }
26-34: Scope execution-detail logging to non-production.Consider gating or truncating
executionLogsto avoid large payloads in prod logs.- console.log("Execution result details:", { + if (process.env.NODE_ENV !== "production") { + console.log("Execution result details:", { success: executionResult.success, error: executionResult.error, executionError: executionResult.execution?.error, hasVideoFiles: executionResult.videoFiles?.length > 0, videoFilesCount: executionResult.videoFiles?.length || 0, executionLogs: executionResult.execution?.logs || "No execution logs available" - }); + }); + }
51-55: Prefer best-quality video instead of first item.Pick by size (proxy for quality) to avoid returning a lower-res file when multiple outputs exist.
-console.log("📹 Video file generated:", executionResult.videoFiles[0].path); -// 4. The video is already uploaded by executeCodeAndListFiles, so we just get the URL. -const videoUrl = executionResult.videoFiles[0].path; +const bestVideo = [...executionResult.videoFiles].sort((a, b) => (b.size ?? 0) - (a.size ?? 0))[0]; +console.log("📹 Video file selected:", bestVideo?.path); +// 4. The video is already uploaded by executeCodeAndListFiles, so we just get the URL. +const videoUrl = bestVideo?.path as string;
57-57: Strengthen typing forvoiceData.Avoid implicit
anyfromlet voiceData = null.-let voiceData = null; +let voiceData: Awaited<ReturnType<typeof generateVoiceNarration>> | null = null;
59-66: Allow voice options via request for flexibility and cost control.Expose
voice,style, andspeakingRatein the POST body with safe defaults; keep current behavior if not provided.// Example usage after parsing request body: const voiceOptions = body.voiceOptions as { voice?: string; style?: string; speakingRate?: number } | undefined; // ... voiceData = await generateVoiceNarration(topic, script, { voice: voiceOptions?.voice, style: voiceOptions?.style ?? "educational", speakingRate: voiceOptions?.speakingRate, });
80-84: Validate request body with a schema and captopiclength.This route can trigger long-running, costly work; add strict validation (type, length, booleans).
// Example with zod (add `import { z } from "zod"`): const BodySchema = z.object({ topic: z.string().min(1).max(200), includeVoice: z.boolean().optional().default(true), voiceOptions: z .object({ voice: z.string().optional(), style: z.string().optional(), speakingRate: z.number().min(0.5).max(2).optional(), }) .optional(), }); const parsed = BodySchema.safeParse(await request.json()); if (!parsed.success) { return NextResponse.json({ error: "Invalid request", issues: parsed.error.flatten() }, { status: 400 }); } const { topic, includeVoice, voiceOptions } = parsed.data;
78-98: Operational advice: move heavy work off the request thread.Rendering can take minutes. Consider 202 + job enqueue (queue + worker) and polling/webhooks; add auth/rate limiting to prevent abuse; include correlation IDs in logs for traceability.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
next.config.ts(1 hunks)src/app/api/chatbot/route.ts(1 hunks)src/app/api/generate-article/route.ts(1 hunks)src/app/api/generate-manim/route.ts(2 hunks)src/utils/sandbox.ts(2 hunks)src/utils/structuredManimGenerator.ts(5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/app/api/generate-manim/route.ts (3)
src/utils/structuredManimGenerator.ts (1)
generateStructuredManimCode(44-237)src/utils/sandbox.ts (1)
executeCodeAndListFiles(175-477)src/utils/voiceNarration.ts (1)
generateVoiceNarration(189-268)
🪛 ast-grep (0.38.6)
src/utils/structuredManimGenerator.ts
[warning] 518-518: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(escapedOriginal, 'g')
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
🔇 Additional comments (4)
src/app/api/generate-manim/route.ts (4)
4-4: Switch to structured generator import looks good.Centralizing generation via
generateStructuredManimCodesimplifies the route and keeps concerns in one place.
20-21: LGTM: explicit formatting step before execution.Clear separation improves debuggability.
24-24: LGTM: unified sandbox execution.Using
executeCodeAndListFileskeeps the route slim and consistent with the new sandbox behavior.
71-71: Confirm intent to return full Manim code to clients.If not needed by the UI, consider omitting or only returning in non-prod to reduce payload and potential leakage.
- manimCode: formattedManimCode, + // manimCode: formattedManimCode, // Uncomment if UI needs to display/edit code
| if (!executionResult.success || executionResult.videoFiles.length === 0) { | ||
| console.error("Manim execution failed:", executionResult.error || executionResult.execution?.error); | ||
| console.error("Execution logs:", executionResult.execution?.logs || "No logs available"); | ||
|
|
||
| // Log the Manim code that failed for debugging | ||
| console.error("Failed Manim code:"); | ||
| console.error(formattedManimCode); | ||
|
|
||
| const errorMessage = executionResult.error || executionResult.execution?.error || "No video produced."; | ||
| const logs = executionResult.execution?.logs || ["No logs available"]; | ||
|
|
||
| throw new Error( | ||
| `Manim execution failed. Error: ${ | ||
| executionResult.error || "No video produced." | ||
| }` | ||
| `Manim execution failed. Error: ${errorMessage}. Logs: ${JSON.stringify(logs)}` | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not include execution logs in client-facing error messages.
Returning serialized logs to the client leaks internal details and can bloat responses. Log server-side (truncated), return a minimal message.
const errorMessage = executionResult.error || executionResult.execution?.error || "No video produced.";
const logs = executionResult.execution?.logs || ["No logs available"];
- throw new Error(
- `Manim execution failed. Error: ${errorMessage}. Logs: ${JSON.stringify(logs)}`
- );
+ const serializedLogs = (Array.isArray(logs) ? logs.join("\n") : String(logs)).slice(0, 4000);
+ console.error("Execution logs (truncated):", serializedLogs);
+ throw new Error(`Manim execution failed. Error: ${String(errorMessage).slice(0, 500)}`);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (!executionResult.success || executionResult.videoFiles.length === 0) { | |
| console.error("Manim execution failed:", executionResult.error || executionResult.execution?.error); | |
| console.error("Execution logs:", executionResult.execution?.logs || "No logs available"); | |
| // Log the Manim code that failed for debugging | |
| console.error("Failed Manim code:"); | |
| console.error(formattedManimCode); | |
| const errorMessage = executionResult.error || executionResult.execution?.error || "No video produced."; | |
| const logs = executionResult.execution?.logs || ["No logs available"]; | |
| throw new Error( | |
| `Manim execution failed. Error: ${ | |
| executionResult.error || "No video produced." | |
| }` | |
| `Manim execution failed. Error: ${errorMessage}. Logs: ${JSON.stringify(logs)}` | |
| ); | |
| if (!executionResult.success || executionResult.videoFiles.length === 0) { | |
| console.error("Manim execution failed:", executionResult.error || executionResult.execution?.error); | |
| console.error("Execution logs:", executionResult.execution?.logs || "No logs available"); | |
| // Log the Manim code that failed for debugging | |
| console.error("Failed Manim code:"); | |
| console.error(formattedManimCode); | |
| const errorMessage = executionResult.error || executionResult.execution?.error || "No video produced."; | |
| const logs = executionResult.execution?.logs || ["No logs available"]; | |
| const serializedLogs = (Array.isArray(logs) ? logs.join("\n") : String(logs)).slice(0, 4000); | |
| console.error("Execution logs (truncated):", serializedLogs); | |
| throw new Error(`Manim execution failed. Error: ${String(errorMessage).slice(0, 500)}`); | |
| } |
| const waitCalls = fixedCode.match(/self\.wait\(([^)]+)\)/g); | ||
| if (waitCalls) { | ||
| const totalWaitTime = waitCalls.reduce((sum, call) => { | ||
| const match = call.match(/self\.wait\(([^)]+)\)/); | ||
| return sum + (match ? parseFloat(match[1]) : 0); | ||
| }, 0); | ||
|
|
||
| // Count animation calls to estimate total video duration | ||
| const playCalls = fixedCode.match(/self\.play\(/g) || []; | ||
| const animationCount = playCalls.length; | ||
|
|
||
| // Estimate animation duration (each play call typically takes 1-2 seconds) | ||
| const estimatedAnimationTime = animationCount * 1.5; | ||
|
|
||
| // Total estimated video duration | ||
| const estimatedTotalDuration = totalWaitTime + estimatedAnimationTime; | ||
|
|
||
| console.log(`⏱️ Video Duration Analysis: ${animationCount} animations (${estimatedAnimationTime.toFixed(1)}s) + ${totalWaitTime.toFixed(1)}s waits = ${estimatedTotalDuration.toFixed(1)}s total`); | ||
|
|
||
| // Adjust timing to reach 28-second target | ||
| if (estimatedTotalDuration < 26) { | ||
| const timeNeeded = 28 - estimatedTotalDuration; | ||
| console.log(`📈 Extending video duration by ${timeNeeded.toFixed(1)} seconds to reach 28-second target`); | ||
|
|
||
| // Distribute additional time across existing wait calls | ||
| let timeDistributed = 0; | ||
| const waitReplacements = waitCalls.map((call, index) => { | ||
| const match = call.match(/self\.wait\(([^)]+)\)/); | ||
| if (match) { | ||
| const currentWait = parseFloat(match[1]); | ||
| const additionalTime = index === waitCalls.length - 1 ? | ||
| timeNeeded - timeDistributed : // Add remaining time to last wait | ||
| Math.min(timeNeeded * 0.3, 2.0); // Distribute across earlier waits | ||
|
|
||
| timeDistributed += additionalTime; | ||
| const newWait = Math.min(currentWait + additionalTime, 4.0); // Cap at 4 seconds | ||
|
|
||
| return { | ||
| original: call, | ||
| replacement: `self.wait(${newWait.toFixed(1)})` | ||
| }; | ||
| } | ||
| return null; | ||
| }).filter(Boolean); | ||
|
|
||
| // Apply the replacements | ||
| waitReplacements.forEach((item) => { | ||
| if (item) { | ||
| const escapedOriginal = item.original.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); | ||
| fixedCode = fixedCode.replace(new RegExp(escapedOriginal, 'g'), item.replacement); | ||
| } | ||
| }); | ||
|
|
||
| // If still short, add final wait | ||
| if (timeDistributed < timeNeeded) { | ||
| const finalWaitTime = timeNeeded - timeDistributed; | ||
| fixedCode = fixedCode.replace( | ||
| /self\.wait\([^)]+\)$/m, | ||
| `self.wait(${finalWaitTime.toFixed(1)})\n # Final extension to reach 28-second target\n self.wait(${Math.max(0, finalWaitTime - 2).toFixed(1)})` | ||
| ); | ||
| } | ||
|
|
||
| } else if (estimatedTotalDuration > 32) { | ||
| console.log(`⚡ Video too long (${estimatedTotalDuration.toFixed(1)}s), optimizing for 28-second target`); | ||
|
|
||
| // Reduce excessive wait times | ||
| fixedCode = fixedCode.replace(/self\.wait\((4\.?\d*)\)/g, (match, time) => { | ||
| const reducedTime = Math.max(2.0, parseFloat(time) * 0.7); | ||
| return `self.wait(${reducedTime.toFixed(1)})`; | ||
| }); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Duration normalization: avoid global regex replace on user-influenced substrings
The new RegExp(escapedOriginal, 'g') approach (later) can be vulnerable to ReDoS on long inputs. Prefer tokenized adjustments or index-based edits; at minimum cap input sizes.
- waitReplacements.forEach((item) => {
- if (item) {
- const escapedOriginal = item.original.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
- fixedCode = fixedCode.replace(new RegExp(escapedOriginal, 'g'), item.replacement);
- }
- });
+ for (const item of waitReplacements) {
+ if (!item) continue;
+ const idx = fixedCode.indexOf(item.original);
+ if (idx === -1) continue;
+ fixedCode = fixedCode.slice(0, idx) + item.replacement + fixedCode.slice(idx + item.original.length);
+ }🏁 Script executed:
#!/bin/bash
set -eux
# Locate the structuredManimGenerator file
FILE=$(fd structuredManimGenerator.ts | head -n1)
echo "Inspecting: $FILE"
# Find all occurrences of fixedCode to understand its origin and scope
rg -n 'fixedCode' "$FILE"
# Show the section where fixedCode is declared and manipulated (around the waitReplacements logic)
sed -n '430,600p' "$FILE"Length of output: 6724
Prefer index-based replacements over global regex for per-call accuracy
Global new RegExp(escapedOriginal, 'g') will replace all identical wait calls at once—breaking per-call timing adjustments and risking performance issues. Switch to sequential, index-based edits to preserve each replacement’s intended value.
- waitReplacements.forEach((item) => {
- if (item) {
- const escapedOriginal = item.original.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
- fixedCode = fixedCode.replace(new RegExp(escapedOriginal, 'g'), item.replacement);
- }
- });
+ for (const item of waitReplacements) {
+ if (!item) continue;
+ const idx = fixedCode.indexOf(item.original);
+ if (idx === -1) continue;
+ fixedCode = fixedCode.slice(0, idx)
+ + item.replacement
+ + fixedCode.slice(idx + item.original.length);
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const waitCalls = fixedCode.match(/self\.wait\(([^)]+)\)/g); | |
| if (waitCalls) { | |
| const totalWaitTime = waitCalls.reduce((sum, call) => { | |
| const match = call.match(/self\.wait\(([^)]+)\)/); | |
| return sum + (match ? parseFloat(match[1]) : 0); | |
| }, 0); | |
| // Count animation calls to estimate total video duration | |
| const playCalls = fixedCode.match(/self\.play\(/g) || []; | |
| const animationCount = playCalls.length; | |
| // Estimate animation duration (each play call typically takes 1-2 seconds) | |
| const estimatedAnimationTime = animationCount * 1.5; | |
| // Total estimated video duration | |
| const estimatedTotalDuration = totalWaitTime + estimatedAnimationTime; | |
| console.log(`⏱️ Video Duration Analysis: ${animationCount} animations (${estimatedAnimationTime.toFixed(1)}s) + ${totalWaitTime.toFixed(1)}s waits = ${estimatedTotalDuration.toFixed(1)}s total`); | |
| // Adjust timing to reach 28-second target | |
| if (estimatedTotalDuration < 26) { | |
| const timeNeeded = 28 - estimatedTotalDuration; | |
| console.log(`📈 Extending video duration by ${timeNeeded.toFixed(1)} seconds to reach 28-second target`); | |
| // Distribute additional time across existing wait calls | |
| let timeDistributed = 0; | |
| const waitReplacements = waitCalls.map((call, index) => { | |
| const match = call.match(/self\.wait\(([^)]+)\)/); | |
| if (match) { | |
| const currentWait = parseFloat(match[1]); | |
| const additionalTime = index === waitCalls.length - 1 ? | |
| timeNeeded - timeDistributed : // Add remaining time to last wait | |
| Math.min(timeNeeded * 0.3, 2.0); // Distribute across earlier waits | |
| timeDistributed += additionalTime; | |
| const newWait = Math.min(currentWait + additionalTime, 4.0); // Cap at 4 seconds | |
| return { | |
| original: call, | |
| replacement: `self.wait(${newWait.toFixed(1)})` | |
| }; | |
| } | |
| return null; | |
| }).filter(Boolean); | |
| // Apply the replacements | |
| waitReplacements.forEach((item) => { | |
| if (item) { | |
| const escapedOriginal = item.original.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); | |
| fixedCode = fixedCode.replace(new RegExp(escapedOriginal, 'g'), item.replacement); | |
| } | |
| }); | |
| // If still short, add final wait | |
| if (timeDistributed < timeNeeded) { | |
| const finalWaitTime = timeNeeded - timeDistributed; | |
| fixedCode = fixedCode.replace( | |
| /self\.wait\([^)]+\)$/m, | |
| `self.wait(${finalWaitTime.toFixed(1)})\n # Final extension to reach 28-second target\n self.wait(${Math.max(0, finalWaitTime - 2).toFixed(1)})` | |
| ); | |
| } | |
| } else if (estimatedTotalDuration > 32) { | |
| console.log(`⚡ Video too long (${estimatedTotalDuration.toFixed(1)}s), optimizing for 28-second target`); | |
| // Reduce excessive wait times | |
| fixedCode = fixedCode.replace(/self\.wait\((4\.?\d*)\)/g, (match, time) => { | |
| const reducedTime = Math.max(2.0, parseFloat(time) * 0.7); | |
| return `self.wait(${reducedTime.toFixed(1)})`; | |
| }); | |
| } | |
| } | |
| // Apply the replacements | |
| for (const item of waitReplacements) { | |
| if (!item) continue; | |
| const idx = fixedCode.indexOf(item.original); | |
| if (idx === -1) continue; | |
| fixedCode = fixedCode.slice(0, idx) | |
| item.replacement | |
| fixedCode.slice(idx + item.original.length); | |
| } |
🧰 Tools
🪛 ast-grep (0.38.6)
[warning] 518-518: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(escapedOriginal, 'g')
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
Summary by CodeRabbit
New Features
Improvements
Behavior Changes