KINOPIPE WITH PHP

Video editing from PHP, 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 PHP usually costs

  • Shared hosting commonly disables shell_exec and proc_open, which rules out every FFmpeg wrapper at once.
  • Where it is allowed, the render runs inside a request and hits max_execution_time long before a real video is finished.
  • Escaping a filter graph through the shell is its own category of bug, and it fails at render time rather than at review time.

Trim a clip and wait for the file

PHP
<?php
$KINOPIPE = 'https://kinopipe.com/api/v1';
$headers = [
    'Authorization: Bearer ' . getenv('KINOPIPE_API_KEY'),
    'Content-Type: application/json',
];

function kinopipe(string $url, array $headers, ?array $body = null): array {
    $curl = curl_init($url);
    curl_setopt_array($curl, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_TIMEOUT => 30,
    ]);
    if ($body !== null) {
        curl_setopt($curl, CURLOPT_POST, true);
        curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($body));
    }
    $response = curl_exec($curl);
    curl_close($curl);
    return json_decode($response, true);
}

$started = kinopipe("$KINOPIPE/tools/trim-video", $headers, [
    'inputs' => [['id' => 'main', 'url' => 'https://example.com/interview.mp4']],
    'options' => ['start' => 12, 'end' => 48],
]);

do {
    sleep(2);
    $job = kinopipe("$KINOPIPE/jobs/{$started['jobId']}", $headers);
} while (!in_array($job['status'], ['succeeded', 'failed'], true));

if ($job['status'] === 'failed') {
    throw new RuntimeException($job['errorMessage'] ?? 'the job did not finish');
}

echo $job['result']['output']['downloadUrl'];
  • Nothing here needs an extension beyond cURL, which shared hosting keeps enabled even when it forbids running processes.
  • For a long render, store the job id and poll it from a cron task rather than holding the request open.
  • 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

My host disables shell_exec. Does this still work?

Yes. Nothing is executed on your server: the code makes HTTP requests, which is exactly what shared hosting is happy to allow.

How do I avoid max_execution_time?

Submit the job, save the id, and return. A cron task or a webhook picks up the finished file, so no request waits for an encode.

What about php-ffmpeg?

php-ffmpeg drives a local binary through the shell. It is the right tool on a server you control, and unavailable on most hosting where PHP actually runs.