Check video loop seam compatibility from frame colors
A convincing video loop depends on its ending returning naturally to its beginning.
Run — free
Runs in your browser. Free, unlimited — your data never leaves this page.
This checker compares the average red, green, blue, and brightness values of the first and last frames, converts their differences into a clear similarity score, and flags whether that score meets your chosen threshold. It gives editors, motion designers, and automated media pipelines a fast, repeatable screen for likely visual seams before anyone spends time watching exports frame by frame or publishing a loop that flashes at its join.
Prepare representative frame averages
Start by calculating one average red, green, blue, and brightness value for the first frame and the same four values for the last frame. Every channel must use the common 0 to 255 scale, including brightness, so the comparison treats both summaries consistently. Use the decoded frames that viewers will actually see, after color conversion, filters, overlays, and export compression have been applied. Comparing source frames before a final grade can hide a seam introduced later. Likewise, avoid sampling thumbnails generated by different services, because resizing and color management may shift their averages independently. The checker deliberately requires every channel in both sets: accepting an incomplete color would create a reassuring score that ignores part of the visible image. Frame averages reduce an image to a compact signal, which makes this test quick and deterministic, but they do not describe the location of objects or edges. Treat the result as an initial compatibility screen. A passing score says the overall color and light level return closely; it does not claim that motion, composition, or texture aligns perfectly at the cut.
Understand the score and threshold
The calculation measures Euclidean distance between the two RGB averages and normalizes it against the largest possible RGB distance. It separately measures the absolute brightness difference and normalizes that on the same 0 to 255 range. Color contributes eighty percent of the final similarity and brightness contributes twenty percent, reflecting that a broad hue change is usually more obvious than a modest luminance shift while still giving a flash or dip meaningful weight. The resulting score runs from 0 to 100, where 100 means all four supplied values match exactly. The default threshold is 90. A score at or above the selected threshold is marked seamless; a lower score is flagged for review. Choose a higher threshold for subtle ambient loops, product backdrops, or large displays where a jump is distracting. A lower threshold may be reasonable for fast cuts, glitch styles, or small decorative previews. Keep the same threshold across a batch if you want results that can be compared. The response also includes raw color distance and brightness difference, helping you identify whether grading or exposure is the stronger source of the mismatch.
Use the result in an editing workflow
Run the check after each candidate export, then route failed clips back to an editor or an automated correction step. If brightness difference dominates, matching exposure, adding a short crossfade, or adjusting the end of a fade can improve the join. If color distance is large, inspect white balance, lighting changes, animated gradients, and compression behavior near the boundary. After making a change, calculate the frame averages again rather than reusing earlier measurements. In a pipeline, store the score, threshold, and component differences beside the export so reviewers can understand why a clip passed or failed. The API costs $0.002 per item, while the browser version is useful for individual checks. Always preview important loops even when they pass. Two frames can share the same average color while placing bright and dark areas in opposite corners, and average values cannot test subject position, camera motion, optical flow, or audio continuity. This tool is strongest as a cheap quality gate that catches broad flashes and color jumps early. Pair it with visual inspection or spatial frame comparison when a seamless result is critical to the final experience.
What you can do with it
Screen social media loops
Flag exported animations whose final frame changes color or brightness before they are scheduled for publication.
Validate motion-design batches
Apply one threshold to many background loops and send only likely mismatches to a human reviewer.
Tune ambient display content
Compare iterations of lobby, kiosk, or stage visuals where a recurring flash at the join would be conspicuous.
FAQ
What values do I need?
Provide average red, green, blue, and brightness values from 0 through 255 for both the first and last frames.
What happens if a channel is missing?
The request fails with an invalid-input error rather than calculating a partial or misleading score.
What score counts as seamless?
The default threshold is 90, and you may select any threshold from 0 through 100 to match your review policy.
Does a passing score guarantee a perfect loop?
No. It measures global average color and brightness, not object position, motion, texture alignment, or audio continuity.
How much does the API check cost?
Each item costs $0.002 through the API. The in-browser checker is available for individual interactive checks.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/video/loop-seamless-check \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"first_frame":{"red":124,"green":118,"blue":110,"brightness":117.6},"last_frame":{"red":126,"green":117,"blue":112,"brightness":118.1}}'const res = await fetch("https://api.kit.forhosting.com/video/loop-seamless-check", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"first_frame": {
"red": 124,
"green": 118,
"blue": 110,
"brightness": 117.6
},
"last_frame": {
"red": 126,
"green": 117,
"blue": 112,
"brightness": 118.1
}
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/video/loop-seamless-check",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"first_frame": {
"red": 124,
"green": 118,
"blue": 110,
"brightness": 117.6
},
"last_frame": {
"red": 126,
"green": 117,
"blue": 112,
"brightness": 118.1
}
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/video/loop-seamless-check", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"first_frame":{"red":124,"green":118,"blue":110,"brightness":117.6},"last_frame":{"red":126,"green":117,"blue":112,"brightness":118.1}}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"first_frame":{"red":124,"green":118,"blue":110,"brightness":117.6},"last_frame":{"red":126,"green":117,"blue":112,"brightness":118.1}}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/video/loop-seamless-check", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"first_frame": {
"red": 124,
"green": 118,
"blue": 110,
"brightness": 117.6
},
"last_frame": {
"red": 126,
"green": 117,
"blue": 112,
"brightness": 118.1
}
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "video.loop_seamless_check",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
max_mb | 500 |
max_minutes | 60 |
max_megapixels | 3.9 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |