KINOPIPE WITH RUST

Video editing from Rust, without FFmpeg on your machine.

Trim, resize, compress, convert, caption and transcribe by calling one HTTP endpoint per operation. Parameters are validated before anything renders, so a bad call fails as a typed error rather than a broken encode.

What running FFmpeg from Rust usually costs

  • The maintained bindings wrap the C libraries, so building needs the FFmpeg development headers present on the build machine.
  • That requirement follows you into cross-compilation and static linking, which are often the reasons Rust was chosen in the first place.
  • Shelling out instead avoids the linking but puts a binary back into the image and turns errors into stderr to parse.

Trim a clip and wait for the file

Rust
use serde_json::{json, Value};
use std::{env, thread, time::Duration};

const BASE: &str = "https://kinopipe.com/api/v1";

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let key = env::var("KINOPIPE_API_KEY")?;
    let client = reqwest::blocking::Client::new();

    let started: Value = client
        .post(format!("{BASE}/tools/trim-video"))
        .bearer_auth(&key)
        .json(&json!({
            "inputs": [{ "id": "main", "url": "https://example.com/interview.mp4" }],
            "options": { "start": 12, "end": 48 }
        }))
        .send()?
        .json()?;

    let job_id = started["jobId"].as_str().ok_or("no job id in the response")?;

    loop {
        let job: Value = client
            .get(format!("{BASE}/jobs/{job_id}"))
            .bearer_auth(&key)
            .send()?
            .json()?;

        match job["status"].as_str() {
            Some("succeeded") => {
                println!("{}", job["result"]["output"]["downloadUrl"]);
                return Ok(());
            }
            Some("failed") => return Err(job["errorMessage"].to_string().into()),
            _ => thread::sleep(Duration::from_secs(2)),
        }
    }
}
  • Two crates, reqwest and serde_json, and no system library. The async version is the same code with await on each call.
  • A split job returns every segment under outputs rather than output, which matters if you call split-video or split-video-by-scenes.
  • Send an Idempotency-Key header when you retry, and the retry returns the original job instead of paying for a second render.
Or let an agent write the call
The same operations are exposed over MCP, so an agent in your editor discovers them and calls them directly.

FAQ

What about ffmpeg-next?

It is the right tool when you need frame-level access in process and accept building against the C libraries. This is for the case where you only want the finished file.

Does this work in a scratch container?

Yes. Nothing native is linked and nothing is spawned, so the binary stays self-contained.

Can I run edits concurrently?

Yes. Jobs are independent, so drive them from tasks and poll each. Credits are reserved per job and refunded when a render fails.