A reference image can look perfectly fine in a browser and still cause problems in an AI video workflow.
Perhaps a character portrait was accidentally exported at 320 × 320 pixels. Two reference files might contain exactly the same image under different filenames. Or a damaged image might still exist in the project folder even though it can no longer be opened properly.
These are problems we can catch before sending anything to a video generation tool.
In this tutorial, we'll build a small Python validator that checks image formats, dimensions, aspect ratios, file integrity, and duplicate content.
We'll also generate test images to verify that the script actually works.
The solution uses Pillow and Python's standard library. No API key or AI video service is required.
1. Why Validate Reference Images?
Reference-guided video generation often involves several assets:
- Character portraits
- Product photographs
- Background images
- Visual style references
- Storyboard frames
As a project grows, these files may be renamed, resized, replaced, or duplicated.
A basic validation step can catch some of these mistakes before generation begins.
For this example, our validator will check five things:
- Whether the image uses a supported format.
- Whether it can be opened and decoded.
- Whether its dimensions meet a configurable minimum.
- Whether its aspect ratio matches an optional requirement.
- Whether another file has identical contents.
These checks do not guarantee better AI video output. They simply help ensure the input files meet a known set of requirements.
2. Set Up the Project
Install Pillow:
python -m pip install Pillow
Create the following project structure:
video-project/
├── references/
├── check_references.py
└── make_test_images.py
We'll first create the validator, then generate several images to test it.
3. Build the Image Validator
Create check_references.py:
import argparse
import hashlib
import json
import warnings
from pathlib import Path
from PIL import Image, UnidentifiedImageError
ALLOWED = {
".png": "PNG",
".jpg": "JPEG",
".jpeg": "JPEG",
".webp": "WEBP",
}
def fingerprint(path):
h = hashlib.sha256()
with path.open("rb") as fh:
for chunk in iter(
lambda: fh.read(1024 * 1024), b""
):
h.update(chunk)
return h.hexdigest()
def inspect(path, minimum, ratio):
errors = []
try:
expected = ALLOWED.get(
path.suffix.lower()
)
if expected is None:
return {
"file": path.name,
"errors": ["Unsupported extension"],
}
# Check file structure and decode image data.
with warnings.catch_warnings():
warnings.simplefilter(
"error",
Image.DecompressionBombWarning
)
with Image.open(path) as image:
actual = image.format
width, height = image.size
image.verify()
# Reopen after verify() for decoding.
with Image.open(path) as image:
image.load()
if actual != expected:
errors.append(
f"Extension/format mismatch: {actual}"
)
if min(width, height) < minimum:
errors.append(
f"Too small: {width}x{height}"
)
if ratio is not None:
actual_ratio = width / height
if abs(actual_ratio - ratio) > 0.03:
errors.append(
f"Unexpected ratio: {actual_ratio:.3f}"
)
return {
"file": path.name,
"size": [width, height],
"sha256": fingerprint(path),
"errors": errors,
}
except (
OSError,
ValueError,
UnidentifiedImageError,
Image.DecompressionBombError,
Image.DecompressionBombWarning,
) as exc:
return {
"file": path.name,
"errors": [f"Unreadable image: {exc}"],
}
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
"directory",
type=Path
)
parser.add_argument(
"--min-side",
type=int,
default=512
)
parser.add_argument(
"--ratio",
type=float
)
args = parser.parse_args()
if (
args.min_side < 1
or (
args.ratio is not None
and args.ratio <= 0
)
):
parser.error("Invalid size or ratio")
if not args.directory.is_dir():
parser.error("Directory not found")
paths = sorted(
path
for path in args.directory.iterdir()
if path.is_file()
)
if not paths:
parser.error("Directory is empty")
seen = {}
results = []
for path in paths:
item = inspect(
path,
args.min_side,
args.ratio
)
digest = item.get("sha256")
if digest:
if digest in seen:
item["errors"].append(
f"Byte-identical to {seen[digest]}"
)
else:
seen[digest] = path.name
results.append(item)
print(json.dumps(results, indent=2))
return (
1 if any(item["errors"] for item in results)
else 0
)
if __name__ == "__main__":
raise SystemExit(main())
The script processes each file in the selected directory and generates a JSON report.
It returns exit code 0 when every file passes, or 1 when one or more files fail.
That makes it usable in local scripts and automated pipelines.
4. Generate Test Images
We should not assume the validator works just because the code runs.
Let's create files that exercise its main checks.
Create make_test_images.py:
from pathlib import Path
from shutil import copyfile
from PIL import Image
folder = Path("references")
folder.mkdir(exist_ok=True)
# A valid image.
Image.new(
"RGB",
(1024, 1024),
"white"
).save(folder / "valid.png")
# An undersized image.
Image.new(
"RGB",
(320, 320),
"red"
).save(folder / "small.png")
# A byte-identical duplicate.
copyfile(
folder / "valid.png",
folder / "valid_copy.png"
)
# A deliberately invalid image file.
(folder / "broken.png").write_bytes(
b"not a real image"
)
Run:
python make_test_images.py
Your references directory should now contain four files:
references/
├── valid.png
├── valid_copy.png
├── small.png
└── broken.png
These test assets allow us to check several error conditions without downloading external images.
5. Run the Validator
Execute:
python check_references.py references
The validator should identify:
| File | Expected result |
valid.png | Valid size and format |
valid_copy.png | Exact duplicate detected |
small.png | Too small: 320x320 |
broken.png | Unreadable image |
The duplicate may be reported against either filename, depending on the order in which the files are processed.
What happens when everything is valid?
Remove the broken, small, and duplicate files, leaving only valid.png.
Run the validator again:
python check_references.py references
The result should contain an empty errors list:
{
"file": "valid.png",
"size": [1024, 1024],
"sha256": "...",
"errors": []
}
The SHA-256 value is abbreviated here for readability. The actual script prints the complete hash.
The process should also exit successfully.
Test a custom minimum size
You can change the minimum dimensions:
python check_references.py references --min-side 768
This requires both dimensions to be at least 768 pixels.
It is a project-level rule, not a universal requirement imposed by AI video models.
A character portrait and a wide landscape background may need different dimension policies.
Test an aspect ratio
For a project expecting roughly 16:9 images:
python check_references.py references --ratio 1.777778
The script allows a difference of 0.03 between the actual and requested width-to-height ratios.
This is useful for a consistent background-image set, but shouldn't automatically be applied to every reference image.
Portraits, objects, and backgrounds often need different framing.
6. Why Use SHA-256 for Duplicate Detection?
The validator calculates a SHA-256 fingerprint for every successfully inspected image.
If two files have identical bytes, their hashes match.
This catches a common asset-management mistake: copying an image under another filename and accidentally including both files in a reference set.
However, SHA-256 is not a visual similarity detector.
For example:
- Two identical files with different names will be detected.
- A JPEG recompressed with different settings will usually produce a different hash.
- An image with modified metadata may have a different hash.
- Two images showing the same subject at different resolutions will generally not match.
For visual similarity detection, teams would need a separate method such as perceptual hashing.
The choice depends on the problem being solved.
If the goal is to find exact duplicate files, SHA-256 is simple and reliable.
7. Use Validation Before AI Video Generation
Reference-heavy video projects often use separate images to guide characters, environments, objects, and camera composition.
Platforms such as Kling 4.0 describe reference-guided generation and multi-keyframe controls, making reference preparation a relevant part of the workflow.
The validator operates entirely on local files. It does not connect to Kling 4.0 or require a provider-specific API.
It can detect invalid or duplicate files before upload, while actual reference limits, accepted formats, and model requirements must be confirmed through the selected service.
A practical workflow might be:
- Collect reference images in a project directory.
- Validate file formats and dimensions.
- Identify duplicates and corrupted files.
- Review asset permissions.
- Upload only the approved references.
- Review the generated video for visual accuracy.
Local validation reduces avoidable input problems, but it does not guarantee that a generated character or object will remain consistent.
8. Production Considerations
This validator is deliberately small.
For a production asset pipeline, several additions may be useful.
EXIF orientation
Some photographs include orientation metadata that changes how they are displayed. The script checks stored image dimensions rather than applying EXIF-based rotation.
File-size and pixel limits
Large or malicious image files can consume significant memory during decoding. Production systems handling untrusted files should enforce resource limits and use isolated processing environments.
Different reference roles
A character portrait, product image, and background scene should not necessarily share the same minimum dimensions or aspect-ratio rules.
Asset permissions
A technically valid reference is not automatically authorized for upload.
Teams should confirm that they have permission to use customer photographs, internal screenshots, copyrighted artwork, and images of recognizable people.
Removing private information does not necessarily make confidential business assets eligible for external processing.
These issues matter just as much as file dimensions when a tool becomes part of a real production workflow.
Conclusion
AI video generation involves creative uncertainty, but basic reference-file validation is predictable.
Before using generation credits, a team can check whether its images are readable, large enough for the intended purpose, correctly formatted, and free of exact duplicates.
The process does not guarantee better motion, perfect character continuity, or accurate generated details.
It simply removes a few preventable mistakes before the creative work begins.
For teams handling many reference files, that's a useful place to introduce a small amount of automation.