Added loading funcs for images
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
from typing import List, Iterator, Tuple, Dict, Optional, Union, Any
|
||||
from enum import Enum
|
||||
import os
|
||||
import tempfile
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
from PIL import Image as PILImage
|
||||
from .inline import Word, FormattedSpan
|
||||
|
||||
|
||||
@@ -1219,6 +1224,160 @@ class Image(Block):
|
||||
height = max_height
|
||||
|
||||
return (width, height)
|
||||
|
||||
def _is_url(self, source: str) -> bool:
|
||||
"""
|
||||
Check if the source is a URL.
|
||||
|
||||
Args:
|
||||
source: The source string to check
|
||||
|
||||
Returns:
|
||||
True if the source appears to be a URL, False otherwise
|
||||
"""
|
||||
parsed = urllib.parse.urlparse(source)
|
||||
return bool(parsed.scheme and parsed.netloc)
|
||||
|
||||
def _download_to_temp(self, url: str) -> str:
|
||||
"""
|
||||
Download an image from a URL to a temporary file.
|
||||
|
||||
Args:
|
||||
url: The URL to download from
|
||||
|
||||
Returns:
|
||||
Path to the temporary file
|
||||
|
||||
Raises:
|
||||
urllib.error.URLError: If the download fails
|
||||
"""
|
||||
# Create a temporary file
|
||||
temp_fd, temp_path = tempfile.mkstemp(suffix='.tmp')
|
||||
|
||||
try:
|
||||
# Download the image
|
||||
with urllib.request.urlopen(url) as response:
|
||||
# Write the response data to the temporary file
|
||||
with os.fdopen(temp_fd, 'wb') as temp_file:
|
||||
temp_file.write(response.read())
|
||||
|
||||
return temp_path
|
||||
except:
|
||||
# Clean up the temporary file if download fails
|
||||
try:
|
||||
os.close(temp_fd)
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
os.unlink(temp_path)
|
||||
except:
|
||||
pass
|
||||
raise
|
||||
|
||||
def load_image_data(self, auto_update_dimensions: bool = True) -> Tuple[Optional[str], Optional[PILImage.Image]]:
|
||||
"""
|
||||
Load image data using PIL, handling both local files and URLs.
|
||||
|
||||
Args:
|
||||
auto_update_dimensions: If True, automatically update width and height from the loaded image
|
||||
|
||||
Returns:
|
||||
Tuple of (file_path, PIL_Image_object). For URLs, file_path is the temporary file path.
|
||||
Returns (None, None) if loading fails.
|
||||
"""
|
||||
if not self._source:
|
||||
return None, None
|
||||
|
||||
file_path = None
|
||||
temp_file = None
|
||||
|
||||
try:
|
||||
if self._is_url(self._source):
|
||||
# Download to temporary file
|
||||
temp_file = self._download_to_temp(self._source)
|
||||
file_path = temp_file
|
||||
else:
|
||||
# Use local file path
|
||||
file_path = self._source
|
||||
|
||||
# Open with PIL
|
||||
with PILImage.open(file_path) as img:
|
||||
# Load the image data
|
||||
img.load()
|
||||
|
||||
# Update dimensions if requested
|
||||
if auto_update_dimensions:
|
||||
self._width, self._height = img.size
|
||||
|
||||
# Return a copy to avoid issues with the context manager
|
||||
return file_path, img.copy()
|
||||
|
||||
except Exception as e:
|
||||
# Clean up temporary file on error
|
||||
if temp_file and os.path.exists(temp_file):
|
||||
try:
|
||||
os.unlink(temp_file)
|
||||
except:
|
||||
pass
|
||||
return None, None
|
||||
|
||||
def get_image_info(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Get detailed information about the image using PIL.
|
||||
|
||||
Returns:
|
||||
Dictionary containing image information including format, mode, size, etc.
|
||||
Returns empty dict if image cannot be loaded.
|
||||
"""
|
||||
file_path, img = self.load_image_data(auto_update_dimensions=False)
|
||||
|
||||
if img is None:
|
||||
return {}
|
||||
|
||||
# Try to determine format from the image, file extension, or source
|
||||
img_format = img.format
|
||||
if img_format is None:
|
||||
# Try to determine format from file extension
|
||||
format_map = {
|
||||
'.jpg': 'JPEG',
|
||||
'.jpeg': 'JPEG',
|
||||
'.png': 'PNG',
|
||||
'.gif': 'GIF',
|
||||
'.bmp': 'BMP',
|
||||
'.tiff': 'TIFF',
|
||||
'.tif': 'TIFF'
|
||||
}
|
||||
|
||||
# First try the actual file path if available
|
||||
if file_path:
|
||||
ext = os.path.splitext(file_path)[1].lower()
|
||||
img_format = format_map.get(ext)
|
||||
|
||||
# If still no format and we have a URL source, try the original URL
|
||||
if img_format is None and self._is_url(self._source):
|
||||
ext = os.path.splitext(urllib.parse.urlparse(self._source).path)[1].lower()
|
||||
img_format = format_map.get(ext)
|
||||
|
||||
info = {
|
||||
'format': img_format,
|
||||
'mode': img.mode,
|
||||
'size': img.size,
|
||||
'width': img.width,
|
||||
'height': img.height,
|
||||
}
|
||||
|
||||
# Add additional info if available
|
||||
if hasattr(img, 'info'):
|
||||
info['info'] = img.info
|
||||
|
||||
# Clean up temporary file if it was created
|
||||
if file_path and self._is_url(self._source):
|
||||
try:
|
||||
os.unlink(file_path)
|
||||
except:
|
||||
pass
|
||||
|
||||
return info
|
||||
|
||||
|
||||
class HorizontalRule(Block):
|
||||
|
||||
Reference in New Issue
Block a user