Files
wehub-resource-sync c56bef871b
CodeQL / Analyze (python) (push) Has been cancelled
Update Platform Components Table / update (push) Has been cancelled
Docker image release / Build base image (push) Has been cancelled
Sync docs with Docusaurus / sync (push) Has been cancelled
Tests / Check if changed (push) Has been cancelled
Tests / format (push) Has been cancelled
Tests / check-imports (push) Has been cancelled
Tests / Unit / macos-latest (push) Has been cancelled
Tests / Unit / ubuntu-latest (push) Has been cancelled
Tests / Unit / windows-latest (push) Has been cancelled
Tests / mypy (push) Has been cancelled
Tests / Integration / ubuntu-latest (push) Has been cancelled
Tests / Integration / macos-latest (push) Has been cancelled
Tests / Integration / windows-latest (push) Has been cancelled
Tests / notify-slack-on-failure (push) Has been cancelled
Tests / Mark tests as completed (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:22:28 +08:00

151 lines
5.8 KiB
Python

# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
#
# SPDX-License-Identifier: Apache-2.0
import mimetypes
from dataclasses import replace
from pathlib import Path
from typing import Any, Literal
from haystack import component, logging
from haystack.components.converters.image.image_utils import _encode_image_to_base64
from haystack.components.converters.utils import get_bytestream_from_source, normalize_metadata
from haystack.dataclasses import ByteStream
from haystack.dataclasses.image_content import ImageContent
from haystack.lazy_imports import LazyImport
with LazyImport(
"The 'size' parameter is set. "
"Image resizing will be applied, which requires the Pillow library. "
"Run 'pip install pillow'"
) as pillow_import:
import PIL # noqa: F401
logger = logging.getLogger(__name__)
_EMPTY_BYTE_STRING = b""
@component
class ImageFileToImageContent:
"""
Converts image files to ImageContent objects.
### Usage example
```python
from haystack.components.converters.image import ImageFileToImageContent
converter = ImageFileToImageContent()
sources = ["image.jpg", "another_image.png"]
image_contents = converter.run(sources=sources)["image_contents"]
print(image_contents)
# [ImageContent(base64_image='...',
# mime_type='image/jpeg',
# detail=None,
# meta={'file_path': 'image.jpg'}),
# ...]
```
"""
def __init__(
self, *, detail: Literal["auto", "high", "low"] | None = None, size: tuple[int, int] | None = None
) -> None:
"""
Create the ImageFileToImageContent component.
:param detail: Optional detail level of the image (only supported by OpenAI). One of "auto", "high", or "low".
This will be passed to the created ImageContent objects.
:param size: If provided, resizes the image to fit within the specified dimensions (width, height) while
maintaining aspect ratio. This reduces file size, memory usage, and processing time, which is beneficial
when working with models that have resolution constraints or when transmitting images to remote services.
"""
self.detail = detail
self.size = size
if self.size is not None:
pillow_import.check()
@component.output_types(image_contents=list[ImageContent])
def run(
self,
sources: list[str | Path | ByteStream],
meta: dict[str, Any] | list[dict[str, Any]] | None = None,
*,
detail: Literal["auto", "high", "low"] | None = None,
size: tuple[int, int] | None = None,
) -> dict[str, list[ImageContent]]:
"""
Converts files to ImageContent objects.
:param sources:
List of file paths or ByteStream objects to convert.
:param meta:
Optional metadata to attach to the ImageContent objects.
This value can be a list of dictionaries or a single dictionary.
If it's a single dictionary, its content is added to the metadata of all produced ImageContent objects.
If it's a list, its length must match the number of sources as they're zipped together.
For ByteStream objects, their `meta` is added to the output ImageContent objects.
:param detail:
Optional detail level of the image (only supported by OpenAI). One of "auto", "high", or "low".
This will be passed to the created ImageContent objects.
If not provided, the detail level will be the one set in the constructor.
:param size: If provided, resizes the image to fit within the specified dimensions (width, height) while
maintaining aspect ratio. This reduces file size, memory usage, and processing time, which is beneficial
when working with models that have resolution constraints or when transmitting images to remote services.
If not provided, the size value will be the one set in the constructor.
:returns:
A dictionary with the following keys:
- `image_contents`: A list of ImageContent objects.
"""
if not sources:
return {"image_contents": []}
resolved_detail = detail or self.detail
resolved_size = size or self.size
# Check import
if resolved_size:
pillow_import.check()
image_contents = []
meta_list = normalize_metadata(meta, sources_count=len(sources))
for source, metadata in zip(sources, meta_list, strict=True):
if isinstance(source, str):
source = Path(source)
try:
bytestream = get_bytestream_from_source(source)
except Exception as e:
logger.warning("Could not read {source}. Skipping it. Error: {error}", source=source, error=e)
continue
if bytestream.mime_type is None and isinstance(source, Path):
bytestream = replace(bytestream, mime_type=mimetypes.guess_type(source.as_posix())[0])
if bytestream.data == _EMPTY_BYTE_STRING:
logger.warning("File {source} is empty. Skipping it.", source=source)
continue
try:
inferred_mime_type, base64_image = _encode_image_to_base64(bytestream=bytestream, size=resolved_size)
except Exception as e:
logger.warning(
"Could not convert file {source}. Skipping it. Error message: {error}", source=source, error=e
)
continue
merged_metadata = {**bytestream.meta, **metadata}
image_content = ImageContent(
base64_image=base64_image, mime_type=inferred_mime_type, meta=merged_metadata, detail=resolved_detail
)
image_contents.append(image_content)
return {"image_contents": image_contents}