bigcat88 / pillow_heif

Python library for working with HEIF images and plugin for Pillow.
BSD 3-Clause "New" or "Revised" License
219 stars 17 forks source link

Support converting HEIC ByteIO to PNG Using Image.open with pillow_heif #292

Closed Chhunneng closed 1 month ago

Chhunneng commented 1 month ago

Describe why it is important and where it will be useful

When trying to convert a HEIC file to PNG using a BytesIO object as the input, the Image.open() function throws the following error:

"cannot identify image file <_io.BytesIO object at 0x106f5fb50>"

This issue occurs only when passing BytesIO. The same code works fine when providing the path to the HEIC file. The following code snippet is used:

import io
from PIL import Image, ImageOps
from pillow_heif import register_heif_opener, register_avif_opener

register_heif_opener()
register_avif_opener()

def convert_from_blob_to_png(blob, save_to):
    try:
        with Image.open(blob) as image:
            fixed_image = ImageOps.exif_transpose(image)
            converted_image = fixed_image.convert("RGB")
            converted_image.save(save_to, format="PNG")
    except Exception as e:
        print(e)

When this is run, the error occurs at the Image.open(blob) line when blob is a BytesIO object containing HEIC data.

In case you need image for testing I have this image for my testing, but I need to convert it to BytesIO first. The image URl: https://gofile.io/d/T2dKQg

Describe your proposed solution

It appears that pillow_heif is unable to correctly identify HEIC files when passed as a BytesIO object. A potential solution could involve extending pillow_heif's ability to recognize HEIC/AVIF files from in-memory objects like BytesIO.

The solution should allow HEIC data in BytesIO to be opened and processed in the same way that path-based HEIC files are handled.

Describe alternatives you've considered, if relevant

No response

Additional context

The issue might be related to how pillow_heif handles HEIC header identification when the file is passed as a BytesIO stream. Solutions that enable pillow_heif to handle in-memory objects for HEIC conversion will make it more versatile for various types of image processing pipelines.

bigcat88 commented 1 month ago

I need to see the full script to quickly understand what level the error is at.

Can you upload your file somewhere where it can be downloaded directly?

This code works:


import io
import requests
from PIL import Image, ImageOps
from pillow_heif import register_heif_opener, register_avif_opener

# Register HEIF and AVIF format openers
register_heif_opener()
register_avif_opener()

def download_file_from_gofile(url):
    try:
        response = requests.get(url)
        response.raise_for_status()
        return io.BytesIO(response.content)
    except Exception as e:
        print(f"Error downloading file: {e}")
        return None

def convert_from_blob_to_png(blob, save_to):
    try:
        with Image.open(blob) as image:
            fixed_image = ImageOps.exif_transpose(image)
            converted_image = fixed_image.convert("RGB")
            converted_image.save(save_to, format="PNG")
            converted_image.show()
    except Exception as e:
        print(f"Error converting image: {e}")

# URL of the file to be downloaded from Gofile
file_url = "https://github.com/bigcat88/pillow_heif/raw/refs/heads/master/tests/images/heif/RGB_10__128x128.heif"

# Download the file
blob = download_file_from_gofile(file_url)

# Convert the downloaded file to PNG (if download was successful)
if blob:
    # blob.seek(0)
    convert_from_blob_to_png(blob, "output_image.png")
Chhunneng commented 1 month ago

The image link is already attached with my description, can you check on that image.

In case you need image for testing I have this image for my testing, but I need to convert it to BytesIO first. The image URl: https://gofile.io/d/T2dKQg

bigcat88 commented 1 month ago

Please read carefully what I wrote. It's not about pillow_heif.

I specifically uploaded the file to GitHub so that you could see for yourself, here's the link:

# URL of the file to be downloaded from Gofile
file_url = "https://github.com/bigcat88/mediadc-dataset/raw/refs/heads/main/IMG_7908.heic"

You can check it yourself, replace the URL in the script above with this one and run it.

Chhunneng commented 1 month ago

Thank you for your help. Actually the image store in the S3 AWS. After using function get_object, I read the result from body which is StreamingBody to BytesIO. It may miss some metadata from the AWS response. Do you have any idea to check on which metadata that makes sure it is heic for pillow_heif?

Here is the code that I read from S3

def get_object_from_s3(
    key: str,
    bucket=settings.AWS_STORAGE_BUCKET_NAME,
    s3_client=boto3.client("s3"),
    *args,
    **kwargs,
) -> dict:
    response = s3_client.get_object(Bucket=bucket, Key=key, *args, **kwargs)
    return BytesIO(response['Body'].read())
Chhunneng commented 1 month ago

I found the solution, it was my fault. I could use response['Body'] instead of BytesIO as I see the code check the hasattr(fp, "read"):

def _get_bytes(fp, length=None) -> bytes:
    if isinstance(fp, (str, Path)):
        with builtins.open(fp, "rb") as file:
            return file.read(length or -1)
    if hasattr(fp, "read"):
        offset = fp.tell() if hasattr(fp, "tell") else None
        result = fp.read(length or -1)
        if offset is not None and hasattr(fp, "seek"):
            fp.seek(offset)
        return result
    return bytes(fp)[:length]