Skip to content
Rapcsány Krisztián Rapcsány Krisztián
hu
← All projects Service and API

Media and file delivery system (in-house CDN)

A standalone service for images, videos and files. It takes over uploading, processing and serving from the platform, so the main system does not have to deal with large content.

PHP Laravel 12 MySQL Message queues FFmpeg HLS streaming REST API OpenAPI
The challenge
My role

I designed and built it, starting in March 2026.

An events platform carries a huge amount of images and video: club logos, covers, galleries, stories, promo videos. If the main system stores and serves them, the site slows down, and large uploads often break.

I wanted a separate, secure service that optimises content, serves it quickly, reliably accepts large videos, and works through a simple API.

The solution
01

Images, automatically

On upload, WebP conversion and thumbnails are produced, with per-profile sizes and quality (logo, cover, gallery, story and more).

02

Video streaming

In the background it builds an adaptive stream (480p, 720p, 1080p), an easily embeddable MP4, a thumbnail and a poster image, with volume normalisation.

03

Upload pages

One-time, time-limited upload links with drag-and-drop and a progress bar, for images, video and any file.

04

Large files without breaking

Uploads go in chunks with per-chunk retries, so even a gigabyte-sized video arrives reliably.

05

Private and public content

Private files are only reachable through signed, time-limited addresses, while public ones can be served directly.

06

API and feedback

Key-based access with per-key permissions, a documented API, status queries and a callback (webhook) when processing ends.

Screenshots
A documented API

Endpoints for images, files and upload sessions, with key-based access and an OpenAPI description.

With brand and customer names and personal data removed (except the app store screenshots).

What makes it special

Adaptive video

480p, 720p and 1080p streams that only scale down, and work for both portrait and landscape video. A plain MP4 is made too, for anyone who just wants to embed it.

One-time upload links

Uploads go through a link valid for 30 minutes, with drag-and-drop and a progress bar.

Private files with signed links

A private file is only reachable through a signed, time-limited address, 15 minutes by default.

Challenges I ran into
01

The layer in between killed large uploads

The upload went in a single request, but the network layer in between cut it off at around 100 MB and 100 seconds. Now I upload in 20 MB chunks, and the server assembles them in the last step and checks the size. Resending a chunk is safe, so the browser retries four times, with longer and longer waits.

02

An upload must not be able to do harm

I blocked executable and server-side extensions (exe, php, js and the rest). I check type and size after assembly, and there is a limit on video length.

03

Do not lose the failure

Video processing runs in the background. On success and on failure it sends a callback to the operating system, retrying five times, and if processing fails, an alert goes out.

Under the hood

Chunked upload

20 MB chunks that the server assembles and verifies in the final step. Resending a chunk is safe. This was needed because an intermediate network layer limits request size and duration, which broke large video uploads.

Background processing chain

Video processing runs on a message queue in several steps, with alerts on failure. The callback arrives on success and on failure, with retries at increasing intervals.

Security

Blocking executable and server-side file types, type and size checks, API-key permissions, signed URLs and a maximum video length.

Storage management

Hourly cleanup of expired upload sessions, and optionally deleting the originals of processed videos after a set time to save storage.

Real code

Upload that survives interruptions

Parts arrive by index, and resending the same index overwrites the part, so a retry is harmless. After assembly it verifies the size and cleans up on failure.

UploadChunkController.php
// Re-sending the same index overwrites the part, so a retry is idempotent.
$request->file('chunk')->move($dir, sprintf('%06d.part', $index));

// ... later, in complete(): assemble the parts in order
$out = fopen($assembled, 'wb');

for ($i = 0; $i < $info['total_chunks']; $i++) {
    $part = $dir.'/'.sprintf('%06d.part', $i);

    if (! is_file($part)) {
        fclose($out);
        return response()->json(['success' => false, 'message' => "Missing chunk: {$i}"], 422);
    }

    $in = fopen($part, 'rb');
    stream_copy_to_stream($in, $out);
    fclose($in);
}
fclose($out);

if (filesize($assembled) !== $info['size']) {
    File::deleteDirectory($dir);
    return response()->json(['success' => false, 'message' => 'Size mismatch.'], 422);
}
Result

The platform and the admin manage content through one API, the heavy processing runs in the background, and large files arrive without breaking.

Are images and videos slowing your system down?

If uploads break, or the site is slow because of large content, it is worth a look.

More projects

Get a quote →