Re-download or re-upload the file; it is incomplete. If you need to process partial files you trust (for example, a
camera card with a damaged photo), set ImageFile.LOAD_TRUNCATED_IMAGES = True before opening; Pillow then
fills the missing area with grey.
What the error looks like
We cut the last 40% off a JPEG. Opening still works, because only the header is read; the error comes on first use:
from PIL import Image
img = Image.open("cut.jpg") # a JPEG whose last 40% is missing
print("opened:", img.format, img.size)
img.thumbnail((400, 400)) # first real use of the pixels
opened: JPEG (1600, 1067)
Traceback (most recent call last):
...
OSError: image file is truncated (48 bytes not processed)
"N bytes not processed" is the leftover data the decoder could not finish. Any operation that needs pixels triggers it:
load(), resize(), thumbnail(), convert(), save() and so on.
Common causes
- Interrupted downloads: a timeout, a dropped connection, or reading a streamed response before it finished.
- Uploads cut short: request size limits on the web server or proxy (for example, nginx's
client_max_body_size), or mobile connections dropping. - Copying a file that is still being written: watching a folder and processing a file before the writer has closed it.
- Damaged storage: corrupted SD cards and disks.
Recover what is there (trusted files only)
from PIL import Image, ImageFile
ImageFile.LOAD_TRUNCATED_IMAGES = True # decode whatever is there
img = Image.open("cut.jpg")
img.load()
img.save("recovered.png")
print("bottom-right pixel:", img.getpixel((img.width - 1, img.height - 1)))
bottom-right pixel: (128, 128, 128)
Only enable this for files you trust and for one-off recovery. In a service that accepts uploads, a truncated file should be rejected, not silently half-processed.
Detect truncated files before they break a pipeline
Fully decode each file inside a try block. In this test folder, the 90% file and the 10% file both fail:
from pathlib import Path
from PIL import Image
data = open("photo.jpg", "rb").read()
Path("check").mkdir()
Path("check/good.jpg").write_bytes(data)
Path("check/cut_90.jpg").write_bytes(data[: int(len(data) * 0.9)])
Path("check/cut_10.jpg").write_bytes(data[: int(len(data) * 0.1)])
def check(path):
try:
with Image.open(path) as img:
img.load() # full decode; verify() does not catch truncation
return "ok"
except OSError as e:
return f"BAD: {e}"
for p in sorted(Path("check").iterdir()):
print(f"{p.name:<12} {check(p)}")
cut_10.jpg BAD: image file is truncated (28 bytes not processed)
cut_90.jpg BAD: image file is truncated (108 bytes not processed)
good.jpg ok
verify() does not catch it
Image.verify() checks structure without decoding, so it is fast but misses truncation:
from PIL import Image
data = open("photo.jpg", "rb").read()
open("cut.jpg", "wb").write(data[: int(len(data) * 0.9)])
with Image.open("cut.jpg") as img:
img.verify() # header and structure checks only
print("verify() passed")
with Image.open("cut.jpg") as img:
img.load()
verify() passed
Traceback (most recent call last):
...
OSError: image file is truncated (108 bytes not processed)
For a cheap pre-check on JPEGs, you can test that the file ends with the end-of-image marker:
data = open("photo.jpg", "rb").read()
cut = data[: int(len(data) * 0.9)]
for name, blob in [("complete", data), ("truncated", cut)]:
print(f"{name:<10} ends with JPEG end marker FF D9: {blob.endswith(bytes([0xFF, 0xD9]))}")
complete ends with JPEG end marker FF D9: True
truncated ends with JPEG end marker FF D9: False
Fix the download, not the image
When the files come from your own code, the real fix is upstream: check response.raise_for_status(), compare
the downloaded size with the Content-Length header, and write to a temporary name and rename only after the
download completes. The cannot identify image file page covers the
related case where even the header is missing.
Related
The outputs and images on this page are the real results of running every example with Pillow 12.3.0 on Python 3.13 (Linux) on 2026-10-15. Every example is also re-run automatically on macOS (Darwin 25.6.0, Pillow 12.3.0), Linux (Linux 6.17.0-1022-azure, Pillow 12.3.0), Windows (Windows 2025Server, Pillow 12.3.0); the line under each example shows the result per operating system. The DejaVu Sans font files sit next to each script so font examples behave the same everywhere. Sample photos: NASA/Sam Lott, public domain. Spotted something wrong? Tell us.