Skip to content

feat(media-server): capcodec H.264 transcode behind a flag - #2446

Merged
richiemcilroy merged 9 commits into
mainfrom
capcodec-encoder-aa45
Oct 10, 2026
Merged

richiemcilroy merged 9 commits into
mainfrom
capcodec-encoder-aa45

Conversation

@richiemcilroy

@richiemcilroy richiemcilroy commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Flag-gated capcodec path for media-server transcodes. It stays off unless CAP_MEDIA_VIDEO_ENCODER=capcodec.

The published images do not include the capcodec binary. Mount one and set CAPCODEC_BIN, as described in apps/media-server/capcodec.md. ffmpeg decodes to limited-range BT.709 yuv420p, capcodec writes a fragmented MP4 (flushing each GOP), and a remux adds audio and faststart. The job's CRF and preset are used unless CAPCODEC_CRF or CAPCODEC_PRESET is set (ultrafast maps to capcodec fast). Encode and mux share one timeout.

The encoder source is in capcodec/. capcodec/capcodec.bundle restores the other branches. The flag runs main.

Benchmarks against x264 veryfast are in apps/media-server/capcodec-benchmarks.md. At matched VMAF the measured encoder is faster on all 12 clips, on CPU and wall clock, at 1 and 4 threads. Quality on that bitstream is about even. Concurrent B-frames are the default; full_motion 4-thread wall was retimed to 1.35–1.40× medium and 1.44–1.50× live. The 4-thread x264 timing used automatic threading (-threads 0).

Test plan

  • bun test src/__tests__/lib/capcodec-encoder.test.ts (8 tests)
  • CI
Open in Web Open in Cursor 

RetriggerConfidence Score: 5/5

The PR appears safe to merge; no outstanding findings remain.

Summary

Adds an optional capcodec H.264 path while keeping libx264 as the default.

  • Media-server jobs can transcode video with capcodec behind a flag.
  • The benchmark tools compare capcodec with x264 on screen-recording clips.
  • The test corpus gives the encoder varied screen scenes to compress.

Reviews (10) · Last reviewed commit: "fix: remove unused bench script declarat..." · Reviewed by Greptile

ffmpeg decodes to raw yuv420p, capcodec encodes, and a remux adds audio.
The path stays off unless CAP_MEDIA_VIDEO_ENCODER=capcodec. Job CRF and
preset apply unless CAPCODEC_CRF or CAPCODEC_PRESET is set. Benchmarks
against x264 veryfast are in capcodec-benchmarks.md.
@cursor

cursor Bot commented Oct 9, 2026

Copy link
Copy Markdown

hey @greptileai please rereview

): CapcodecOptions | null {
if (env.CAP_MEDIA_VIDEO_ENCODER !== "capcodec") return null;
return {
binary: env.CAPCODEC_BIN?.trim() || "capcodec",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Shipped images lack the encoder

Enabling CAP_MEDIA_VIDEO_ENCODER=capcodec in either shipped media-server image selects an executable that the image does not contain. Both Dockerfiles install FFmpeg and copy the application, but neither supplies capcodec. Without a separately supplied executable, video jobs that need conversion fail at startup. Include a pinned executable in the images, or document the deployment step that supplies CAPCODEC_BIN.

Knowledge Base Used: Media-server runtime service

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/media-server/src/lib/capcodec-encoder.ts
Line: 81

Comment:
**Shipped images lack the encoder**

Enabling `CAP_MEDIA_VIDEO_ENCODER=capcodec` in either shipped media-server image selects an executable that the image does not contain. Both Dockerfiles install FFmpeg and copy the application, but neither supplies `capcodec`. Without a separately supplied executable, video jobs that need conversion fail at startup. Include a pinned executable in the images, or document the deployment step that supplies `CAPCODEC_BIN`.

**Knowledge Base Used:** [Media-server runtime service](https://app.greptile.com/cap/-/custom-context/knowledge-base/capsoftware/cap/-/docs/media-server-and-web-cluster.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +284 to +285
const encoder = registerSubprocess(
spawn({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Failed starts skip decoder cleanup

If the encoder cannot start—for example, because CAPCODEC_BIN names a missing executable—the decoder has already started, but execution never reaches the try or abort listener. Nothing on this error path stops or unregisters the decoder. This can leave FFmpeg running without a consumer and stale entries in the process registry. Move encoder startup inside a cleanup-protected block so every failure stops the decoder.

Knowledge Base Used: Media-server runtime service

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/media-server/src/lib/capcodec-encoder.ts
Line: 284-285

Comment:
**Failed starts skip decoder cleanup**

If the encoder cannot start—for example, because `CAPCODEC_BIN` names a missing executable—the decoder has already started, but execution never reaches the `try` or abort listener. Nothing on this error path stops or unregisters the decoder. This can leave FFmpeg running without a consumer and stale entries in the process registry. Move encoder startup inside a cleanup-protected block so every failure stops the decoder.

**Knowledge Base Used:** [Media-server runtime service](https://app.greptile.com/cap/-/custom-context/knowledge-base/capsoftware/cap/-/docs/media-server-and-web-cluster.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

abortSignal: controller.signal,
}),
processTimeoutMs,
async () => controller.abort(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Timeout cleanup returns too early

The timeout callback only calls controller.abort(); it does not wait for either process to exit. Their abort handler starts terminateProcess without awaiting it, so withTimeout returns and videoOnly.cleanup() runs while encoding can still be active. Timed-out jobs can keep using CPU after their job slot is released, and the output file can be deleted before its writer exits. Keep the encode promise and await its cleanup after aborting, before deleting the file or returning.

Knowledge Base Used: Media-server runtime service

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/media-server/src/lib/media-video.ts
Line: 1729

Comment:
**Timeout cleanup returns too early**

The timeout callback only calls `controller.abort()`; it does not wait for either process to exit. Their abort handler starts `terminateProcess` without awaiting it, so `withTimeout` returns and `videoOnly.cleanup()` runs while encoding can still be active. Timed-out jobs can keep using CPU after their job slot is released, and the output file can be deleted before its writer exits. Keep the encode promise and await its cleanup after aborting, before deleting the file or returning.

**Knowledge Base Used:** [Media-server runtime service](https://app.greptile.com/cap/-/custom-context/knowledge-base/capsoftware/cap/-/docs/media-server-and-web-cluster.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

…fails

The media-server images do not ship capcodec. Jobs check CAPCODEC_BIN
before ffmpeg starts, and capcodec.md describes the mount. A failed
encoder start stops the decoder, and a timeout waits for both processes
to exit before deleting the output.
@cursor

cursor Bot commented Oct 9, 2026

Copy link
Copy Markdown

hey @greptileai please rereview

job: CapcodecEncodeJob,
): Promise<void> {
if (job.abortSignal?.aborted) throw cancelled();
await assertCapcodecBinary(job.options.binary);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Cancelled jobs keep encoding

If a job is cancelled while assertCapcodecBinary awaits the file check, encodeVideoWithCapcodec still starts both processes. The abort listener is added after the signal has fired, and the next cancellation check waits until both stderr streams close. The job can encode the whole video before stopping, and timeout cleanup now waits for that work to finish.

Recheck job.abortSignal.aborted immediately after the binary check, before starting either process.

Suggested change
await assertCapcodecBinary(job.options.binary);
await assertCapcodecBinary(job.options.binary);
if (job.abortSignal?.aborted) throw cancelled();

Knowledge Base Used: Media-server runtime service

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/media-server/src/lib/capcodec-encoder.ts
Line: 290

Comment:
**Cancelled jobs keep encoding**

If a job is cancelled while `assertCapcodecBinary` awaits the file check, `encodeVideoWithCapcodec` still starts both processes. The abort listener is added after the signal has fired, and the next cancellation check waits until both stderr streams close. The job can encode the whole video before stopping, and timeout cleanup now waits for that work to finish.

Recheck `job.abortSignal.aborted` immediately after the binary check, before starting either process.

```suggestion
	await assertCapcodecBinary(job.options.binary);
	if (job.abortSignal?.aborted) throw cancelled();
```

**Knowledge Base Used:** [Media-server runtime service](https://app.greptile.com/cap/-/custom-context/knowledge-base/capsoftware/cap/-/docs/media-server-and-web-cluster.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

A cancel during the binary check used to launch ffmpeg and capcodec,
because the abort listener was attached only after both had started.
@cursor

cursor Bot commented Oct 9, 2026

Copy link
Copy Markdown

hey @greptileai please rereview

The Tov encoder lived only on the build machine. This adds the main
tree, a bundle of the other branches, and keeps Biome from linting it.
@cursor

cursor Bot commented Oct 9, 2026

Copy link
Copy Markdown

hey @greptileai please rereview

Comment thread capcodec/bench/corpus/pages/common/video.js Fixed
Comment thread capcodec/bench/corpus/pages/common/wiki.js Fixed
Comment thread capcodec/bench/corpus/pages/common/video.js Fixed
CodeQL treated the corpus fixtures as application JavaScript. The video
page now takes its file URL from the generator, and the wiki preview
writes link text as text.
@cursor

cursor Bot commented Oct 9, 2026

Copy link
Copy Markdown

hey @greptileai please rereview

Comment thread capcodec/src/mux/mp4.tov
w.u32(this.data.length + 8)
w.fourcc("mdat")
}
w.bytes(this.data, 0, this.data.length)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Long conversions exhaust memory

Mp4Muxer keeps every encoded byte in data, then copies the whole movie into another buffer in progressive(). The media server always selects this path through its .mp4 temporary file. Long recordings can therefore use gigabytes of memory and fail before the output is written. Write samples to disk as they arrive, or use the existing fragmented output before the final remux.

Knowledge Base Used: Media delivery services

Prompt To Fix With AI
This is a comment left during a code review.
Path: capcodec/src/mux/mp4.tov
Line: 389

Comment:
**Long conversions exhaust memory**

`Mp4Muxer` keeps every encoded byte in `data`, then copies the whole movie into another buffer in `progressive()`. The media server always selects this path through its `.mp4` temporary file. Long recordings can therefore use gigabytes of memory and fail before the output is written. Write samples to disk as they arrive, or use the existing fragmented output before the final remux.

**Knowledge Base Used:** [Media delivery services](https://app.greptile.com/cap/-/custom-context/knowledge-base/capsoftware/cap/-/docs/media-delivery-services.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread capcodec/src/mux/mp4.tov
Comment on lines +175 to +179
w.fourcc("nclx")
w.u16(1)
w.u16(1)
w.u16(1)
w.u8(0)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Converted videos show wrong colors

writeStsd() labels every output as BT.709 with limited range, and writeSps() makes the same assumption. The media server accepts other color formats, but its decode filter does not convert their color matrix to BT.709. A BT.601 source therefore keeps its original pixel values with different color labels, so playback shows shifted colors. Convert incoming frames to BT.709 or carry the source's color settings into both writers.

Prompt To Fix With AI
This is a comment left during a code review.
Path: capcodec/src/mux/mp4.tov
Line: 175-179

Comment:
**Converted videos show wrong colors**

`writeStsd()` labels every output as BT.709 with limited range, and `writeSps()` makes the same assumption. The media server accepts other color formats, but its decode filter does not convert their color matrix to BT.709. A BT.601 source therefore keeps its original pixel values with different color labels, so playback shows shifted colors. Convert incoming frames to BT.709 or carry the source's color settings into both writers.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +344 to +345
jobs.append((f"x264_t{t}", x264_cmd(src, a.x264_crf, mode, 0 if t > 1 else 1, work / "x.mp4",
a.keyint1)))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Thread comparisons are mislabeled

The four-thread comparison passes -threads 0 to x264, while capcodec receives --threads 4. Every requested count above one takes this same automatic x264 setting. The report still labels both results with the requested count, so it does not measure equal thread limits. Pass t to x264_cmd() or label x264 as using automatic threading.

Prompt To Fix With AI
This is a comment left during a code review.
Path: capcodec/bench/speedcmp.py
Line: 344-345

Comment:
**Thread comparisons are mislabeled**

The four-thread comparison passes `-threads 0` to x264, while capcodec receives `--threads 4`. Every requested count above one takes this same automatic x264 setting. The report still labels both results with the requested count, so it does not measure equal thread limits. Pass `t` to `x264_cmd()` or label x264 as using automatic threading.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +1 to +2
import { processVideo } from "/workspace/apps/media-server/src/lib/media-video";
import { probeVideoFile } from "/workspace/apps/media-server/src/lib/media-probe";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Integration driver cannot load

media-server-driver.ts imports from /workspace instead of this checkout. If the checkout lives elsewhere, running the supplied driver fails before it can probe or convert a video. Use relative imports so the driver measures the media-server code shipped alongside it.

Prompt To Fix With AI
This is a comment left during a code review.
Path: capcodec/bench/integration/media-server-driver.ts
Line: 1-2

Comment:
**Integration driver cannot load**

`media-server-driver.ts` imports from `/workspace` instead of this checkout. If the checkout lives elsewhere, running the supplied driver fails before it can probe or convert a video. Use relative imports so the driver measures the media-server code shipped alongside it.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread capcodec/bench/bench.py Outdated


def committed_best() -> dict | None:
r = subprocess.run(["git", "-C", str(ROOT), "show", "HEAD:bench/best.json"], capture_output=True, text=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Smoke check uses edited baseline

committed_best() still reads HEAD:bench/best.json, but the committed file now lives at capcodec/bench/best.json. Running Git from capcodec/ does not change that tree path. The lookup fails and reads the working copy instead, so an uncommitted baseline edit can hide a regression. Read the correct committed path while retaining support for a restored standalone repository.

Prompt To Fix With AI
This is a comment left during a code review.
Path: capcodec/bench/bench.py
Line: 21

Comment:
**Smoke check uses edited baseline**

`committed_best()` still reads `HEAD:bench/best.json`, but the committed file now lives at `capcodec/bench/best.json`. Running Git from `capcodec/` does not change that tree path. The lookup fails and reads the working copy instead, so an uncommitted baseline edit can hide a regression. Read the correct committed path while retaining support for a restored standalone repository.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread capcodec/bench/harness/runner.py Outdated
Comment on lines +639 to +641
if s is None:
msgs.append(f"{GATE_ENCODER} not run; gate checks decode only")
return ok, msgs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Smoke passes without capcodec

A missing capcodec-medium binary is skipped, then gate() returns success if the remaining x264 runs pass. Developers can therefore get a passing encoder check without testing capcodec at all. Require both the smoke encoder and its reference before accepting the run.

Prompt To Fix With AI
This is a comment left during a code review.
Path: capcodec/bench/harness/runner.py
Line: 639-641

Comment:
**Smoke passes without capcodec**

A missing `capcodec-medium` binary is skipped, then `gate()` returns success if the remaining x264 runs pass. Developers can therefore get a passing encoder check without testing capcodec at all. Require both the smoke encoder and its reference before accepting the run.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +4 to +5
inbox: '<path d="M3 13l3-8h12l3 8v6H3z"/><path d="M3 13h5l1 3h6l1-3h5"/>',
user: '<circle cx="12" cy="8" r="4"/><path d="M4 21c1-4 4-6 8-6s7 2 8 6"/>',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 JavaScript quotes break repository rule

The new JavaScript uses single-quoted strings, contrary to the repository guide's requirement for double-quoted JS/TS strings. This appears throughout board.js and also in workbench.js. Format these files before merging; excluding capcodec from Biome does not remove the repository requirement.

Context Used: AGENTS.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: capcodec/bench/corpus/pages/common/board.js
Line: 4-5

Comment:
**JavaScript quotes break repository rule**

The new JavaScript uses single-quoted strings, contrary to the repository guide's requirement for double-quoted JS/TS strings. This appears throughout `board.js` and also in `workbench.js`. Format these files before merging; excluding `capcodec` from Biome does not remove the repository requirement.

**Context Used:** AGENTS.md ([source](https://github.com/capsoftware/cap/blob/main/AGENTS.md))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

These single quotes are the form Biome emits. quoteStyle is double, and biome ci rewrites "<path d=\"M3\"/>" back to '<path d="M3"/>' because the string contains a double quote. Every remaining single-quoted literal in the capcodec scripts is an SVG or HTML fragment in that situation. Escaping them would fail the format check.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You're right. Biome's quoteStyle: "double" is a preference, not an unconditional requirement: for literals containing double quotes, such as these SVG/HTML fragments, it emits single quotes to avoid escaping, and biome ci accepts that output.

The quote finding should therefore be withdrawn; no source change is needed. The related “exclude all of capcodec” concern was also overstated: the configuration excludes only specific non-JS capcodec artifacts, while the JavaScript files remain covered by Biome.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

Decode into limited-range BT.709 before encode, and write a fragmented
MP4 so a long transcode flushes each GOP. The bench gate requires both
encoders, the baseline lookup works inside this repo, and the speed
report states that x264's higher thread count is automatic.
@cursor

cursor Bot commented Oct 9, 2026

Copy link
Copy Markdown

hey @greptileai please rereview

Comment thread capcodec/src/mux/mp4.tov
w.u32(this.data.length + 8)
w.fourcc("mdat")
}
w.bytes(this.data, 0, this.data.length)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Long conversions exhaust memory

The media-server always selects progressive MP4 output. Mp4Muxer keeps the entire encoded video in data, then copies it into another full buffer before writing. Long recordings can use more than twice their encoded size in RAM and exhaust the container's memory.

Write samples to disk as they arrive, or use fragmented output for the intermediate file.

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: capcodec/src/mux/mp4.tov
Line: 389

Comment:
**Long conversions exhaust memory**

The media-server always selects progressive MP4 output. `Mp4Muxer` keeps the entire encoded video in `data`, then copies it into another full buffer before writing. Long recordings can use more than twice their encoded size in RAM and exhaust the container's memory.

Write samples to disk as they arrive, or use fragmented output for the intermediate file.

**Knowledge Base Used:**
- [Media delivery services](https://app.greptile.com/cap/-/custom-context/knowledge-base/capsoftware/cap/-/docs/media-delivery-services.md)
- [Media-server runtime service](https://app.greptile.com/cap/-/custom-context/knowledge-base/capsoftware/cap/-/docs/media-server-and-web-cluster.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread capcodec/src/mux/mp4.tov
Comment on lines +174 to +179
const c = w.begin("colr")
w.fourcc("nclx")
w.u16(1)
w.u16(1)
w.u16(1)
w.u8(0)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Converted videos change colors

writeStsd always labels the output as BT.709 with limited range. The media-server decoder only scales and requests yuv420p; it does not convert other color matrices to BT.709. A BT.601 source that needs conversion is therefore encoded with the wrong color label, so players show shifted colors.

Convert the raw frames to the declared color settings or carry the source settings into the encoder.

Prompt To Fix With AI
This is a comment left during a code review.
Path: capcodec/src/mux/mp4.tov
Line: 174-179

Comment:
**Converted videos change colors**

`writeStsd` always labels the output as BT.709 with limited range. The media-server decoder only scales and requests `yuv420p`; it does not convert other color matrices to BT.709. A BT.601 source that needs conversion is therefore encoded with the wrong color label, so players show shifted colors.

Convert the raw frames to the declared color settings or carry the source settings into the encoder.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +174 to +181
def __enter__(self):
if self.enabled:
for _ in range(20):
new = [p for p in freezable() if p not in self.stopped]
if not new:
break
self.stop(new)
time.sleep(0.05)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Cancelled benchmarks leave jobs frozen

Cancelling speedcmp.py during Quiet.__enter__ can leave unrelated processes stopped. Startup already sends SIGSTOP, but __exit__ does not run when __enter__ raises. The signal handler cannot resume them either: timed still holds the lock that thaw_leftovers tries to acquire.

Catch startup failures inside __enter__ and resume every process already stopped.

Prompt To Fix With AI
This is a comment left during a code review.
Path: capcodec/bench/speedcmp.py
Line: 174-181

Comment:
**Cancelled benchmarks leave jobs frozen**

Cancelling `speedcmp.py` during `Quiet.__enter__` can leave unrelated processes stopped. Startup already sends `SIGSTOP`, but `__exit__` does not run when `__enter__` raises. The signal handler cannot resume them either: `timed` still holds the lock that `thaw_leftovers` tries to acquire.

Catch startup failures inside `__enter__` and resume every process already stopped.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread capcodec/bench/harness/runner.py Outdated
Comment on lines +639 to +641
if s is None:
msgs.append(f"{GATE_ENCODER} not run; gate checks decode only")
return ok, msgs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Smoke checks skip the encoder

The smoke check succeeds when capcodec-medium was never run. A missing binary removes its adapter, and this branch returns the still-true ok value. Missing reference quality data also does not fail the check. Developers can get a successful result without testing the intended encoder.

Require the target encoder, reference encoder, and required comparison results before reporting success.

Prompt To Fix With AI
This is a comment left during a code review.
Path: capcodec/bench/harness/runner.py
Line: 639-641

Comment:
**Smoke checks skip the encoder**

The smoke check succeeds when `capcodec-medium` was never run. A missing binary removes its adapter, and this branch returns the still-true `ok` value. Missing reference quality data also does not fail the check. Developers can get a successful result without testing the intended encoder.

Require the target encoder, reference encoder, and required comparison results before reporting success.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread capcodec/bench/harness/runner.py Outdated
"speed_wall_fps_median": statistics.median([r["speed_wall_fps"] for r in sr if r.get("speed_wall_fps")]) if sr else None,
"decode_failures": sum(1 for r in er if r.get("decode_ffmpeg_ok") is False or r.get("decode_jm_ok") is False
or r.get("recon_match") is False or r.get("jm_match") is False),
"errors": sum(1 for r in rows if r["encoder"] == e and r.get("error")),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Failed quality checks pass

measure returns metrics_error when a quality measurement fails, but summarize only counts error and decode failures. If decoding succeeds and the measurement process fails, the smoke check can pass without a usable quality comparison.

Count metrics_error as a failed check so developers do not miss broken measurements.

Prompt To Fix With AI
This is a comment left during a code review.
Path: capcodec/bench/harness/runner.py
Line: 591

Comment:
**Failed quality checks pass**

`measure` returns `metrics_error` when a quality measurement fails, but `summarize` only counts `error` and decode failures. If decoding succeeds and the measurement process fails, the smoke check can pass without a usable quality comparison.

Count `metrics_error` as a failed check so developers do not miss broken measurements.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +344 to +345
jobs.append((f"x264_t{t}", x264_cmd(src, a.x264_crf, mode, 0 if t > 1 else 1, work / "x.mp4",
a.keyint1)))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Speed reports mislabel thread counts

Every requested thread count above one runs x264 with -threads 0, while capcodec receives the requested count. The report still labels both results with that count. For example, --threads 4 compares four capcodec threads with x264's automatic thread count, so readers cannot use the labels to compare how the encoders scale.

Pass t to both encoders or label the automatic comparison accurately.

Prompt To Fix With AI
This is a comment left during a code review.
Path: capcodec/bench/speedcmp.py
Line: 344-345

Comment:
**Speed reports mislabel thread counts**

Every requested thread count above one runs x264 with `-threads 0`, while capcodec receives the requested count. The report still labels both results with that count. For example, `--threads 4` compares four capcodec threads with x264's automatic thread count, so readers cannot use the labels to compare how the encoders scale.

Pass `t` to both encoders or label the automatic comparison accurately.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +1 to +2
import { processVideo } from "/workspace/apps/media-server/src/lib/media-video";
import { probeVideoFile } from "/workspace/apps/media-server/src/lib/media-probe";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Driver needs a fixed checkout

The integration driver imports the media-server from absolute /workspace paths. Running its advertised command from a checkout elsewhere fails before reading the input.

Use imports relative to this file so developers can run the copied driver from their own checkout.

Prompt To Fix With AI
This is a comment left during a code review.
Path: capcodec/bench/integration/media-server-driver.ts
Line: 1-2

Comment:
**Driver needs a fixed checkout**

The integration driver imports the media-server from absolute `/workspace` paths. Running its advertised command from a checkout elsewhere fails before reading the input.

Use imports relative to this file so developers can run the copied driver from their own checkout.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +22 to +25
How to reproduce (from the `capcodec/` directory in this repository):

- Quality: `CAPCODEC_BIN_UNCHECKED=<binary> python3 bench/bench.py standard --quality-only --encoders capcodec-medium,capcodec-live,capcodec-live-zerolatency,x264-veryfast,x264-veryfast-zerolatency --out <dir>` (1080p, 10 s per clip; code_4k is scaled to 1080p here). capcodec's CRF ladder runs 20-44 so its VMAF range covers x264's CRF 18-33; the overlap column shows how much of the two curves BD-rate compares (it was 15-24% on screen clips with the old 20-36 ladder).
- Speed: `bench/lane.sh start`, then `bench/lane.sh run "python3 bench/speedcmp.py --binary <binary> --clips all --modes medium,live,zerolatency --retime bench/out/speedcmp-fff504d-v2 --out <dir>"`. The rerun reuses the CRF that matched x264 CRF 23 (bitstreams are unchanged, so the match stands). 5 shuffled, interleaved rounds at 1 and 4 threads; every other process is stopped for each timed run (about 690 processes stopped, frozen median). code_4k runs at native 4K for 5 s. Output: `bench/out/speedcmp-00fecf7`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Reproduction needs missing results

The speed command requires bench/out/speedcmp-fff504d-v2/results.json, but that output directory is excluded from the repository. speedcmp.py reads it unconditionally when --retime is supplied, so the command fails in a fresh checkout. The same command appears in SCOREBOARD.md.

Remove --retime from the fresh-run instructions or supply the saved results separately.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/media-server/capcodec-benchmarks.md
Line: 22-25

Comment:
**Reproduction needs missing results**

The speed command requires `bench/out/speedcmp-fff504d-v2/results.json`, but that output directory is excluded from the repository. `speedcmp.py` reads it unconditionally when `--retime` is supplied, so the command fails in a fresh checkout. The same command appears in `SCOREBOARD.md`.

Remove `--retime` from the fresh-run instructions or supply the saved results separately.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread biome.json Outdated
"!apps/web/public/gif.worker.js",
"!packages/ui-solid/src/auto-imports.d.ts"
"!packages/ui-solid/src/auto-imports.d.ts",
"!capcodec"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 New files bypass required checks

Excluding all of capcodec from Biome bypasses the repository guide's directive not to override the root Biome rules locally. The new JavaScript also violates its double-quote requirement, for example in dashboard.js.

Keep supported files under the shared checks and fix their formatting instead of excluding the whole directory. This repository requirement must be satisfied before merging.

Context Used: AGENTS.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: biome.json
Line: 17

Comment:
**New files bypass required checks**

Excluding all of `capcodec` from Biome bypasses the repository guide's directive not to override the root Biome rules locally. The new JavaScript also violates its double-quote requirement, for example in `dashboard.js`.

Keep supported files under the shared checks and fix their formatting instead of excluding the whole directory. This repository requirement must be satisfied before merging.

**Context Used:** AGENTS.md ([source](https://github.com/capsoftware/cap/blob/main/AGENTS.md))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread capcodec/bench/harness/adapters/base.py Outdated


class FFmpegAdapter(Adapter):
"""An encoder driven through ffmpeg: codec_args(src, params, speed) returns the codec options."""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Comments repeat the code

The added FFmpegAdapter docstring only restates what the class does. The repository guide bans comments that narrate code or paraphrase names and types. The module introductions in generate.py and scenarios.py follow the same pattern, which also appears elsewhere.

Remove this narration and keep comments that explain non-obvious constraints. This repository requirement must be satisfied before merging.

Context Used: AGENTS.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: capcodec/bench/harness/adapters/base.py
Line: 83

Comment:
**Comments repeat the code**

The added `FFmpegAdapter` docstring only restates what the class does. The repository guide bans comments that narrate code or paraphrase names and types. The module introductions in `generate.py` and `scenarios.py` follow the same pattern, which also appears elsewhere.

Remove this narration and keep comments that explain non-obvious constraints. This repository requirement must be satisfied before merging.

**Context Used:** AGENTS.md ([source](https://github.com/capsoftware/cap/blob/main/AGENTS.md))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

A cancelled speed run resumes processes it already stopped. The smoke
gate needs both encoders and a BD-rate, and a failed quality measurement
fails the gate. Biome now formats the capcodec scripts it understands.
@cursor

cursor Bot commented Oct 9, 2026

Copy link
Copy Markdown

hey @greptileai please rereview

continue;
}
}
const sm = (lang === "py" ? /^[rRbBuUfF]{0,2}("|")/ : /^(\"|")/).exec(rest);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Single-quoted strings lose their colors

The formatting change removes single quotes from both string-matching expressions in tokenize(). Python strings such as 'Match' and C character literals now fall through as ordinary code, so regenerated benchmark clips show them in the wrong colors.

Restore the single-quote alternatives. The double-quote rule applies to JavaScript string literals, not characters matched by a regular expression.

Prompt To Fix With AI
This is a comment left during a code review.
Path: capcodec/bench/corpus/pages/common/editor.js
Line: 143

Comment:
**Single-quoted strings lose their colors**

The formatting change removes single quotes from both string-matching expressions in `tokenize()`. Python strings such as `'Match'` and C character literals now fall through as ordinary code, so regenerated benchmark clips show them in the wrong colors.

Restore the single-quote alternatives. The double-quote rule applies to JavaScript string literals, not characters matched by a regular expression.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@cursor

cursor Bot commented Oct 9, 2026

Copy link
Copy Markdown

hey @greptileai please rereview

Comment on lines +157 to +159
const sm = (lang === "py" ? /^[rRbBuUfF]{0,2}("|")/ : /^("|")/).exec(
rest,
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Single-quoted strings lose their colors

The changed tokenize regex matches " twice and no longer matches '. Single-quoted Python strings and C character literals now appear as ordinary text or names. This changes the editor and minimap colors in regenerated benchmark clips.

Restore the single-quote alternative in both branches. Quotes inside a regex are not JavaScript string delimiters.

Suggested change
const sm = (lang === "py" ? /^[rRbBuUfF]{0,2}("|")/ : /^("|")/).exec(
rest,
);
const sm = (lang === "py" ? /^[rRbBuUfF]{0,2}("|')/ : /^("|')/).exec(
rest,
);
Prompt To Fix With AI
This is a comment left during a code review.
Path: capcodec/bench/corpus/pages/common/editor.js
Line: 157-159

Comment:
**Single-quoted strings lose their colors**

The changed `tokenize` regex matches `"` twice and no longer matches `'`. Single-quoted Python strings and C character literals now appear as ordinary text or names. This changes the editor and minimap colors in regenerated benchmark clips.

Restore the single-quote alternative in both branches. Quotes inside a regex are not JavaScript string delimiters.

```suggestion
			const sm = (lang === "py" ? /^[rRbBuUfF]{0,2}("|')/ : /^("|')/).exec(
				rest,
			);
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +14 to +18
const PY_BUILTINS = new Set(
"len range isinstance min max sum enumerate zip sorted reversed print super any all map filter iter next abs hash id repr getattr setattr hasattr divmod round".split(
" ",
),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Unused code breaks shared rules

editor.js declares PY_BUILTINS but never reads it. The local n in tokenize() is also unused. These files now fall under the shared Biome checks, and the repository guide requires the recommended lint rules, including unused-variable checks, to be followed.

Remove the unused declarations rather than suppressing the checks. This repository requirement must be satisfied before merging.

Context Used: AGENTS.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: capcodec/bench/corpus/pages/common/editor.js
Line: 14-18

Comment:
**Unused code breaks shared rules**

`editor.js` declares `PY_BUILTINS` but never reads it. The local `n` in `tokenize()` is also unused. These files now fall under the shared Biome checks, and the repository guide requires the recommended lint rules, including unused-variable checks, to be followed.

Remove the unused declarations rather than suppressing the checks. This repository requirement must be satisfied before merging.

**Context Used:** AGENTS.md ([source](https://github.com/capsoftware/cap/blob/main/AGENTS.md))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@cursor

cursor Bot commented Oct 9, 2026

Copy link
Copy Markdown

hey @greptileai please rereview

@cursor

cursor Bot commented Oct 10, 2026

Copy link
Copy Markdown

hey @greptileai please rereview

@richiemcilroy
richiemcilroy merged commit c2bf582 into main Oct 10, 2026
31 checks passed

This branch was successfully deployed

1 active deployment
Preview — 8768ac24 Deployed Oct 9, 2026 by vercel[bot]
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.

2 participants