Skip to content

Improve render.py robustness/performance, add editing helpers#63

Open
eaikarensantos-gif wants to merge 4 commits into
browser-use:mainfrom
eaikarensantos-gif:melhorias-app
Open

Improve render.py robustness/performance, add editing helpers#63
eaikarensantos-gif wants to merge 4 commits into
browser-use:mainfrom
eaikarensantos-gif:melhorias-app

Conversation

@eaikarensantos-gif

@eaikarensantos-gif eaikarensantos-gif commented Jun 12, 2026

Copy link
Copy Markdown

Summary

  • render.py robustness: surface ffmpeg stderr on failure; detect libass support by probing -filters instead of hardcoding Homebrew paths; refuse lossless concat when segment resolutions mismatch (previously produced a corrupt file); escape subtitle paths safely in the filtergraph; -map 0:a? so audioless sources don't fail the composite.
  • render.py performance: match output fps to the source instead of forcing 24fps (no more dropped/duplicated frames on 30/60fps footage); one cached ffprobe per source instead of two per segment; encode segments in parallel (up to 4 ffmpeg processes).
  • New helpers: batch, denoise, reframe, breath detection, subtitle burning (PIL), transcript correction, gap checking, export formats, thumbnails.
  • news_server.py: shared Anthropic client, 5-min RSS cache, model updated to claude-opus-4-8, Flask debug off by default.
  • .gitignore: ignore media files and working folders in the repo root.

Test plan

  • python3 -m py_compile on all edited files
  • Verified ffmpeg/libass detection and source probing (HDR/orientation/fps) against local footage

🤖 Generated with Claude Code


Summary by cubic

Improve helpers/render.py reliability and speed by matching source FPS, parallelizing segment encodes, surfacing ffmpeg errors, and safer subtitle/libass handling. Add a compact toolkit of editing helpers plus a small news_server.py, and ignore large media in .gitignore.

  • New Features

    • New helpers: batch.py, denoise.py, reframe.py, detect_breaths.py, burn_subs_pil.py, correct_transcript.py, check_gaps.py, export_formats.py, thumbnail.py.
    • news_server.py: shared Anthropic client, 5‑min RSS cache, model claude-opus-4-8, debug off by default.
    • .gitignore: ignore common media binaries and working folders at repo root.
  • Bug Fixes

    • Prevent corrupt outputs: refuse lossless concat when segment resolutions differ; escape subtitle paths in the filtergraph; map audio with -map 0:a? to handle audioless sources; auto-pick a libass-capable ffmpeg when burning subs.
    • Performance and robustness: match output FPS to source (no dropped/dupe frames), cache one ffprobe per source, encode segments in parallel (up to 4), and surface ffmpeg stderr on failure.

Written for commit f44ac89. Summary will update on new commits.

Review in cubic

eaikarensantos-gif and others added 4 commits June 12, 2026 00:12
- Surface ffmpeg stderr on failure instead of swallowing it
- Match output fps to the source instead of forcing 24fps
- Probe each source once (HDR + orientation + fps) with a cache
- Encode segments in parallel (up to 4 ffmpeg processes)
- Detect libass support by probing -filters instead of hardcoding paths
- Refuse lossless concat when segment resolutions mismatch
- Escape subtitle paths safely for the filtergraph (brackets, quotes)
- Use -map 0:a? so audioless sources don't fail the composite

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Reuse one Anthropic client instead of per-request instantiation
- Cache TechCrunch headlines for 5 minutes
- Update model to claude-opus-4-8
- Disable Flask debug mode by default (opt in via FLASK_DEBUG=1)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

18 issues found across 12 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".gitignore">

<violation number="1" location=".gitignore:75">
P2: Root-only .gitignore patterns are not anchored with a leading '/', causing them to match directories at any depth despite the explicit "repo root" comment.</violation>
</file>

<file name="helpers/export_formats.py">

<violation number="1" location="helpers/export_formats.py:48">
P2: `get_video_info` does not handle ffprobe failures or malformed output, leading to unhandled `JSONDecodeError`, `KeyError`, or `StopIteration` on invalid/non-video inputs.</violation>

<violation number="2" location="helpers/export_formats.py:70">
P2: ffmpeg global options `-y` and `-loglevel` are placed after the output path, so they may not take effect and non-interactive runs can hang on overwrite prompts</violation>
</file>

<file name="helpers/burn_subs_pil.py">

<violation number="1" location="helpers/burn_subs_pil.py:181">
P1: Silent fallback from yuva420p to yuv420p removes alpha channel, breaking transparent subtitle overlay</violation>
</file>

<file name="helpers/correct_transcript.py">

<violation number="1" location="helpers/correct_transcript.py:94">
P1: Invalid `ensure_ascii=False` passed to built-in `open()`, causing a runtime TypeError before the corrected JSON is written.</violation>
</file>

<file name="helpers/batch.py">

<violation number="1" location="helpers/batch.py:89">
P2: Using only `Path(video_path).stem` for generated artifact paths causes collisions when inputs share the same base name with different extensions (e.g., `clip.mp4` and `clip.mov`). The second input reuses or overwrites the first's transcript, EDL, and final output, silently corrupting results.</violation>
</file>

<file name="helpers/thumbnail.py">

<violation number="1" location="helpers/thumbnail.py:35">
P2: Uncaught `ValueError` when ffprobe returns non-numeric duration (e.g., `N/A`) crashes the script instead of exiting gracefully.</violation>

<violation number="2" location="helpers/thumbnail.py:51">
P1: Missing validation for non-positive `--interval` causes infinite loop in frame extraction</violation>

<violation number="3" location="helpers/thumbnail.py:193">
P1: `saved.append` runs unconditionally even when `cv2.imwrite` was skipped, causing `make_contact_sheet` to crash on `FileNotFoundError` when it tries to open missing thumbnail files.</violation>
</file>

<file name="helpers/detect_breaths.py">

<violation number="1" location="helpers/detect_breaths.py:47">
P2: Audio extraction reads WAV data with a hardcoded 44-byte header offset, which can mis-parse valid WAV files that contain extra chunks (e.g., LIST, fact, metadata) before the data chunk. Non-audio bytes would then be interpreted as samples, corrupting RMS calculations and breath detection results.</violation>

<violation number="2" location="helpers/detect_breaths.py:57">
P1: Missing edge-case guards can crash on invalid `--window-ms` or very short audio: `window` may be 0 causing `ZeroDivisionError`, and empty RMS causes `np.percentile` to crash on empty array.</violation>

<violation number="3" location="helpers/detect_breaths.py:77">
P2: Minimum breath duration filtering uses `int()` (floor) for `min_windows`, allowing regions shorter than the user-configured `--min-ms`.</violation>
</file>

<file name="helpers/denoise.py">

<violation number="1" location="helpers/denoise.py:57">
P2: curl model download lacks `--fail`, allowing HTTP error pages to be silently saved as the model file, breaking `arnndn` denoise mode persistently.</violation>
</file>

<file name="news_server.py">

<violation number="1" location="news_server.py:102">
P2: request.json can raise BadRequest/UnsupportedMediaType before the route's try/except, causing the API to return framework error responses instead of its own JSON error contract.</violation>
</file>

<file name="helpers/reframe.py">

<violation number="1" location="helpers/reframe.py:128">
P2: Missing guard for empty sampled positions can crash `reframe.py` with `ValueError` when processing a corrupt or zero-frame video that opens but yields no frames.</violation>

<violation number="2" location="helpers/reframe.py:187">
P2: ffmpeg global options `-y` and `-loglevel` placed after output file may be ignored</violation>
</file>

<file name="helpers/check_gaps.py">

<violation number="1" location="helpers/check_gaps.py:27">
P2: Using `Path.glob()` with raw user/data-derived `source_name` can produce incorrect matches if the source name contains glob metacharacters (`*`, `?`, `[]`). Furthermore, selecting `candidates[0]` without sorting makes the file choice nondeterministic when multiple files match.</violation>

<violation number="2" location="helpers/check_gaps.py:28">
P2: Missing transcript files are silently ignored, leading to false "no gaps found" reports when segments were never actually checked.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread helpers/burn_subs_pil.py
result = subprocess.run(cmd, capture_output=True)
if result.returncode != 0:
# Fallback: yuv420p se yuva420p não suportado
cmd[cmd.index("yuva420p")] = "yuv420p"

@cubic-dev-ai cubic-dev-ai Bot Jun 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Silent fallback from yuva420p to yuv420p removes alpha channel, breaking transparent subtitle overlay

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At helpers/burn_subs_pil.py, line 181:

<comment>Silent fallback from yuva420p to yuv420p removes alpha channel, breaking transparent subtitle overlay</comment>

<file context>
@@ -0,0 +1,195 @@
+        result = subprocess.run(cmd, capture_output=True)
+        if result.returncode != 0:
+            # Fallback: yuv420p se yuva420p não suportado
+            cmd[cmd.index("yuva420p")] = "yuv420p"
+            result2 = subprocess.run(cmd, capture_output=True)
+            if result2.returncode != 0:
</file context>
Fix with cubic

data["words"] = correct_words(data["words"], api_key=args.api_key)

out_path = args.output or str(Path(args.transcript).with_suffix(".corrected.json"))
with open(out_path, "w", ensure_ascii=False) as f:

@cubic-dev-ai cubic-dev-ai Bot Jun 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Invalid ensure_ascii=False passed to built-in open(), causing a runtime TypeError before the corrected JSON is written.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At helpers/correct_transcript.py, line 94:

<comment>Invalid `ensure_ascii=False` passed to built-in `open()`, causing a runtime TypeError before the corrected JSON is written.</comment>

<file context>
@@ -0,0 +1,101 @@
+    data["words"] = correct_words(data["words"], api_key=args.api_key)
+
+    out_path = args.output or str(Path(args.transcript).with_suffix(".corrected.json"))
+    with open(out_path, "w", ensure_ascii=False) as f:
+        json.dump(data, f, ensure_ascii=False, indent=2)
+
</file context>
Fix with cubic

Comment thread helpers/thumbnail.py
Comment on lines +193 to +195
saved.append((ts, dst, sc, box))
print(f" #{rank} t={ts:.1f}s score={sc:.1f} → {dst}")

@cubic-dev-ai cubic-dev-ai Bot Jun 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: saved.append runs unconditionally even when cv2.imwrite was skipped, causing make_contact_sheet to crash on FileNotFoundError when it tries to open missing thumbnail files.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At helpers/thumbnail.py, line 193:

<comment>`saved.append` runs unconditionally even when `cv2.imwrite` was skipped, causing `make_contact_sheet` to crash on `FileNotFoundError` when it tries to open missing thumbnail files.</comment>

<file context>
@@ -0,0 +1,206 @@
+            img = cv2.imread(img_path)
+            if img is not None:
+                cv2.imwrite(dst, img, [cv2.IMWRITE_JPEG_QUALITY, 95])
+            saved.append((ts, dst, sc, box))
+            print(f"  #{rank}  t={ts:.1f}s  score={sc:.1f}  → {dst}")
+
</file context>
Suggested change
saved.append((ts, dst, sc, box))
print(f" #{rank} t={ts:.1f}s score={sc:.1f}{dst}")
if img is not None:
cv2.imwrite(dst, img, [cv2.IMWRITE_JPEG_QUALITY, 95])
saved.append((ts, dst, sc, box))
print(f" #{rank} t={ts:.1f}s score={sc:.1f}{dst}")
Fix with cubic

Comment thread helpers/thumbnail.py
], check=False)
if os.path.exists(out_path):
frames.append((t, out_path))
t = round(t + interval_s, 3)

@cubic-dev-ai cubic-dev-ai Bot Jun 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Missing validation for non-positive --interval causes infinite loop in frame extraction

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At helpers/thumbnail.py, line 51:

<comment>Missing validation for non-positive `--interval` causes infinite loop in frame extraction</comment>

<file context>
@@ -0,0 +1,206 @@
+        ], check=False)
+        if os.path.exists(out_path):
+            frames.append((t, out_path))
+        t = round(t + interval_s, 3)
+    return frames
+
</file context>
Fix with cubic

Comment thread helpers/detect_breaths.py

def compute_rms(samples, sr, window_ms=50):
"""Calcula RMS por janelas de window_ms milissegundos."""
window = int(sr * window_ms / 1000)

@cubic-dev-ai cubic-dev-ai Bot Jun 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Missing edge-case guards can crash on invalid --window-ms or very short audio: window may be 0 causing ZeroDivisionError, and empty RMS causes np.percentile to crash on empty array.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At helpers/detect_breaths.py, line 57:

<comment>Missing edge-case guards can crash on invalid `--window-ms` or very short audio: `window` may be 0 causing `ZeroDivisionError`, and empty RMS causes `np.percentile` to crash on empty array.</comment>

<file context>
@@ -0,0 +1,227 @@
+
+def compute_rms(samples, sr, window_ms=50):
+    """Calcula RMS por janelas de window_ms milissegundos."""
+    window = int(sr * window_ms / 1000)
+    n_windows = len(samples) // window
+    rms = np.array([
</file context>
Fix with cubic

Comment thread news_server.py

@app.route("/api/generate", methods=["POST"])
def generate():
data = request.json or {}

@cubic-dev-ai cubic-dev-ai Bot Jun 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: request.json can raise BadRequest/UnsupportedMediaType before the route's try/except, causing the API to return framework error responses instead of its own JSON error contract.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At news_server.py, line 102:

<comment>request.json can raise BadRequest/UnsupportedMediaType before the route's try/except, causing the API to return framework error responses instead of its own JSON error contract.</comment>

<file context>
@@ -0,0 +1,133 @@
+
+@app.route("/api/generate", methods=["POST"])
+def generate():
+    data = request.json or {}
+    headline = data.get("headline", "").strip()
+    summary  = data.get("summary", "").strip()
</file context>
Fix with cubic

Comment thread helpers/reframe.py
crop_h = int(src_w * target_h / target_w)

# posição X do centro de interesse (mediana suavizada)
median_cx = float(np.median([cx for _, cx in smooth_pos]))

@cubic-dev-ai cubic-dev-ai Bot Jun 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Missing guard for empty sampled positions can crash reframe.py with ValueError when processing a corrupt or zero-frame video that opens but yields no frames.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At helpers/reframe.py, line 128:

<comment>Missing guard for empty sampled positions can crash `reframe.py` with `ValueError` when processing a corrupt or zero-frame video that opens but yields no frames.</comment>

<file context>
@@ -0,0 +1,198 @@
+        crop_h = int(src_w * target_h / target_w)
+
+    # posição X do centro de interesse (mediana suavizada)
+    median_cx = float(np.median([cx for _, cx in smooth_pos]))
+    center_x = int(median_cx * src_w)
+
</file context>
Fix with cubic

Comment thread helpers/reframe.py
Comment on lines +187 to +195
out_path, "-y", "-loglevel", "error"
]
result = subprocess.run(cmd)
if result.returncode == 0:
size_mb = os.path.getsize(out_path) / 1024 / 1024
print(f"\ndone: {out_path} ({size_mb:.1f} MB)")
else:
sys.exit("Erro no reframe.")

@cubic-dev-ai cubic-dev-ai Bot Jun 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: ffmpeg global options -y and -loglevel placed after output file may be ignored

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At helpers/reframe.py, line 187:

<comment>ffmpeg global options `-y` and `-loglevel` placed after output file may be ignored</comment>

<file context>
@@ -0,0 +1,198 @@
+        "-vf", vf,
+        "-c:v", "libx264", "-crf", "20", "-preset", "fast",
+        "-c:a", "copy",
+        out_path, "-y", "-loglevel", "error"
+    ]
+    result = subprocess.run(cmd)
</file context>
Suggested change
out_path, "-y", "-loglevel", "error"
]
result = subprocess.run(cmd)
if result.returncode == 0:
size_mb = os.path.getsize(out_path) / 1024 / 1024
print(f"\ndone: {out_path} ({size_mb:.1f} MB)")
else:
sys.exit("Erro no reframe.")
cmd = [
"ffmpeg", "-y", "-loglevel", "error",
"-i", video_path,
"-vf", vf,
"-c:v", "libx264", "-crf", "20", "-preset", "fast",
"-c:a", "copy",
out_path,
]
Fix with cubic

Comment thread helpers/check_gaps.py
# index transcripts by source name
transcripts = {}
for source_name in edl["sources"]:
candidates = list(Path(transcripts_dir).glob(f"{source_name}.json"))

@cubic-dev-ai cubic-dev-ai Bot Jun 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Using Path.glob() with raw user/data-derived source_name can produce incorrect matches if the source name contains glob metacharacters (*, ?, []). Furthermore, selecting candidates[0] without sorting makes the file choice nondeterministic when multiple files match.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At helpers/check_gaps.py, line 27:

<comment>Using `Path.glob()` with raw user/data-derived `source_name` can produce incorrect matches if the source name contains glob metacharacters (`*`, `?`, `[]`). Furthermore, selecting `candidates[0]` without sorting makes the file choice nondeterministic when multiple files match.</comment>

<file context>
@@ -0,0 +1,87 @@
+    # index transcripts by source name
+    transcripts = {}
+    for source_name in edl["sources"]:
+        candidates = list(Path(transcripts_dir).glob(f"{source_name}.json"))
+        if candidates:
+            transcripts[source_name] = load_words(str(candidates[0]))
</file context>
Fix with cubic

Comment thread helpers/check_gaps.py
transcripts = {}
for source_name in edl["sources"]:
candidates = list(Path(transcripts_dir).glob(f"{source_name}.json"))
if candidates:

@cubic-dev-ai cubic-dev-ai Bot Jun 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Missing transcript files are silently ignored, leading to false "no gaps found" reports when segments were never actually checked.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At helpers/check_gaps.py, line 28:

<comment>Missing transcript files are silently ignored, leading to false "no gaps found" reports when segments were never actually checked.</comment>

<file context>
@@ -0,0 +1,87 @@
+    transcripts = {}
+    for source_name in edl["sources"]:
+        candidates = list(Path(transcripts_dir).glob(f"{source_name}.json"))
+        if candidates:
+            transcripts[source_name] = load_words(str(candidates[0]))
+
</file context>
Fix with cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant