Skip to content

feat(kdenlive): add export render so projects can actually be rendered - #461

Open
Kenbin5 wants to merge 1 commit into
HKUDS:mainfrom
Kenbin5:fix/kdenlive-export-render
Open

feat(kdenlive): add export render so projects can actually be rendered#461
Kenbin5 wants to merge 1 commit into
HKUDS:mainfrom
Kenbin5:fix/kdenlive-export-render

Conversation

@Kenbin5

@Kenbin5 Kenbin5 commented Sep 9, 2026

Copy link
Copy Markdown

Problem

The kdenlive harness can import clips, build a timeline, add filters and transitions, and emit MLT XML — but it cannot produce a video. The export group has only two commands:

Commands:
  presets  List available render presets.
  xml      Generate Kdenlive/MLT XML.

So export presets advertises h264_hq, h265_hq, codecs, bitrates and container extensions for output that no command can generate. A user or agent reading the preset list reasonably expects a render path; there isn't one.

Cause

utils/melt_backend.py already implements render_mlt() with codec allow-list validation, melt invocation, return-code checking and output verification. Nothing imports it — grep -rl melt_backend across non-test sources returns nothing. The renderer was written and then never wired up.

Change

core/export.py gains render_project(), and kdenlive_cli.py gains the command:

export render out.mp4 --preset h264_fast --overwrite [--keep-mlt path.mlt] [--timeout 300]

It serialises the project to MLT XML, hands it to melt through the existing backend, and removes the temporary .mlt in a finally. --keep-mlt writes the intermediate XML instead of discarding it, which is useful for inspecting what was sent to the renderer.

Rendering goes through melt rather than an ffmpeg concat, per HARNESS.md's "Rendering Gap" section — a concat demuxer reads the raw source clips and silently ignores project-level filters and transitions. Unknown presets raise with the list of valid names. melt is a hard dependency; find_melt() raises with install instructions.

generate_kdenlive_xml() and list_render_presets() are unchanged.

Verification

macOS, melt 7.40.0. Sources generated with ffmpeg lavfi: testsrc and smptebars, 3s each at 320x240.

Timeline: clip A trimmed to 2s at position 0, clip B trimmed to 2s at position 2.

check result
export render --preset h264_fast method: melt, 98,335 bytes
ffprobe 4.010667s, h264 1920x1080 + aac — matches 2s + 2s
emitted MLT (--keep-mlt) references both source files
frame at t=1s testsrc pattern — clip A present
frame at t=3s SMPTE bars — clip B present
suite 179 passed

Both source clips reach the output, so the timeline is genuinely rendered rather than a black or single-clip result.

🤖 Generated with Claude Code

The kdenlive harness could build a timeline and emit MLT XML, but had no way
to turn either into a video. The `export` group exposed only `xml` and
`presets`, so `export presets` advertised codecs, bitrates and container
extensions for output the CLI could not produce.

`utils/melt_backend.py` already implements `render_mlt()` — codec allow-list
validation, melt invocation, return-code checking and output verification — but
nothing in the package imported it. `grep -rl melt_backend` over non-test
sources returned nothing.

Adds `render_project()` to core/export.py and an `export render` command:

    export render out.mp4 --preset h264_fast --overwrite

The project is serialised to MLT XML, handed to melt, and the temp .mlt is
removed afterwards. `--keep-mlt PATH` writes the intermediate XML instead of
discarding it. Rendering goes through melt rather than an ffmpeg concat so
project-level filters and transitions are applied instead of silently dropped.
Unknown presets fail with the list of valid names; melt is a hard dependency
and `find_melt()` raises with install instructions.

Verified on macOS with melt 7.40.0, sources generated by ffmpeg lavfi
(testsrc and smptebars, 3s each at 320x240):
- two 2s segments placed at 0s and 2s, rendered with preset h264_fast
- output: 4.01s h264/aac, 98,335 bytes, method: melt
- frame at 1s is the testsrc pattern, frame at 3s is SMPTE bars — both source
  clips reach the output, and the emitted MLT references both files
- suite: 179 passed

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added the existing-cli-fix Fixes or improves an existing CLI harness label Sep 9, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cc97e693d6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

try:
result = melt_backend.render_mlt(
mlt_path, output_path,
vcodec=p["vcodec"], acodec=p["acodec"],

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 Badge Handle codec-less presets before calling the backend

When --preset audio_only or --preset gif is selected, this forwards the literal codec value "none". melt_backend.render_mlt() validates every nonempty codec against its allowlists, which do not contain "none", so both advertised presets always raise ValueError before melt starts. Represent the disabled video/audio stream using backend-supported options rather than passing "none" as a codec.

Useful? React with 👍 / 👎.

Comment on lines +143 to +144
vcodec=p["vcodec"], acodec=p["acodec"],
overwrite=overwrite, timeout=timeout,

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 Badge Forward bitrate settings from the selected preset

The preset definitions distinguish quality levels through vbitrate and abitrate, but this call forwards only codecs, overwrite, and timeout. Consequently h264_hq and h264_fast invoke render_mlt() with identical encoding settings, while the advertised rates for H.265 and VP9 are also ignored. Pass the selected preset's bitrate settings to the renderer so the output honors the chosen preset.

Useful? React with 👍 / 👎.

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

Labels

existing-cli-fix Fixes or improves an existing CLI harness

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant