feat: implement image path resolution utility to streamline image handling in FastAPI endpoints and PPTX presentation creator
This commit is contained in:
parent
e0d219e5fe
commit
d185d10462
3 changed files with 98 additions and 58 deletions
|
|
@ -9,7 +9,7 @@ from openai import OpenAI
|
|||
from openai import APIError
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, delete, func
|
||||
from utils.asset_directory_utils import get_images_directory
|
||||
from utils.asset_directory_utils import get_images_directory, resolve_image_path_to_filesystem
|
||||
from services.database import get_async_session
|
||||
from models.sql.presentation_layout_code import PresentationLayoutCodeModel
|
||||
from .prompts import (
|
||||
|
|
@ -454,28 +454,10 @@ async def convert_slide_to_html(request: SlideToHtmlRequest):
|
|||
)
|
||||
|
||||
# Resolve image path to actual file system path
|
||||
image_path = request.image
|
||||
|
||||
# Handle different path formats
|
||||
if image_path.startswith("/app_data/images/"):
|
||||
# Remove the /app_data/images/ prefix and join with actual images directory
|
||||
relative_path = image_path[len("/app_data/images/") :]
|
||||
actual_image_path = os.path.join(get_images_directory(), relative_path)
|
||||
elif image_path.startswith("/static/"):
|
||||
# Handle static files
|
||||
relative_path = image_path[len("/static/") :]
|
||||
actual_image_path = os.path.join("static", relative_path)
|
||||
else:
|
||||
# Assume it's already a full path or relative to images directory
|
||||
if os.path.isabs(image_path):
|
||||
actual_image_path = image_path
|
||||
else:
|
||||
actual_image_path = os.path.join(get_images_directory(), image_path)
|
||||
|
||||
# Check if image file exists
|
||||
if not os.path.exists(actual_image_path):
|
||||
actual_image_path = resolve_image_path_to_filesystem(request.image)
|
||||
if not actual_image_path:
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"Image file not found: {image_path}"
|
||||
status_code=404, detail=f"Image file not found: {request.image}"
|
||||
)
|
||||
|
||||
# Read and encode image to base64
|
||||
|
|
@ -546,20 +528,8 @@ async def convert_html_to_react(request: HtmlToReactRequest):
|
|||
image_b64 = None
|
||||
media_type = None
|
||||
if request.image:
|
||||
image_path = request.image
|
||||
if image_path.startswith("/app_data/images/"):
|
||||
relative_path = image_path[len("/app_data/images/") :]
|
||||
actual_image_path = os.path.join(get_images_directory(), relative_path)
|
||||
elif image_path.startswith("/static/"):
|
||||
relative_path = image_path[len("/static/") :]
|
||||
actual_image_path = os.path.join("static", relative_path)
|
||||
else:
|
||||
actual_image_path = (
|
||||
image_path
|
||||
if os.path.isabs(image_path)
|
||||
else os.path.join(get_images_directory(), image_path)
|
||||
)
|
||||
if os.path.exists(actual_image_path):
|
||||
actual_image_path = resolve_image_path_to_filesystem(request.image)
|
||||
if actual_image_path:
|
||||
with open(actual_image_path, "rb") as f:
|
||||
image_b64 = base64.b64encode(f.read()).decode("utf-8")
|
||||
ext = os.path.splitext(actual_image_path)[1].lower()
|
||||
|
|
|
|||
|
|
@ -36,7 +36,9 @@ from models.pptx_models import (
|
|||
PptxTextBoxModel,
|
||||
PptxTextRunModel,
|
||||
)
|
||||
from utils.asset_directory_utils import get_images_directory, resolve_image_path_to_filesystem
|
||||
from utils.download_helpers import download_files
|
||||
from utils.get_env import get_app_data_directory_env
|
||||
from utils.image_utils import (
|
||||
clip_image,
|
||||
create_circle_image,
|
||||
|
|
@ -206,35 +208,36 @@ class PptxPresentationCreator:
|
|||
image_urls = []
|
||||
models_with_network_asset: List[PptxPictureBoxModel] = []
|
||||
|
||||
def _process_image_path(each_shape, image_path):
|
||||
if not image_path.startswith("http"):
|
||||
return
|
||||
if "app_data/" in image_path:
|
||||
relative_path = image_path.split("app_data/")[1]
|
||||
app_data_dir = get_app_data_directory_env()
|
||||
if app_data_dir:
|
||||
each_shape.picture.path = os.path.join(app_data_dir, relative_path)
|
||||
else:
|
||||
each_shape.picture.path = os.path.join("/app_data", relative_path)
|
||||
each_shape.picture.is_network = False
|
||||
return
|
||||
# Resolve HTTP URLs that contain absolute filesystem paths (Mac/Electron)
|
||||
local_path = resolve_image_path_to_filesystem(image_path)
|
||||
if local_path:
|
||||
each_shape.picture.path = local_path
|
||||
each_shape.picture.is_network = False
|
||||
return
|
||||
image_urls.append(image_path)
|
||||
models_with_network_asset.append(each_shape)
|
||||
|
||||
if self._ppt_model.shapes:
|
||||
for each_shape in self._ppt_model.shapes:
|
||||
if isinstance(each_shape, PptxPictureBoxModel):
|
||||
image_path = each_shape.picture.path
|
||||
if image_path.startswith("http"):
|
||||
if "app_data/" in image_path:
|
||||
relative_path = image_path.split("app_data/")[1]
|
||||
each_shape.picture.path = os.path.join(
|
||||
"/app_data", relative_path
|
||||
)
|
||||
each_shape.picture.is_network = False
|
||||
continue
|
||||
image_urls.append(image_path)
|
||||
models_with_network_asset.append(each_shape)
|
||||
_process_image_path(each_shape, each_shape.picture.path)
|
||||
|
||||
for each_slide in self._slide_models:
|
||||
for each_shape in each_slide.shapes:
|
||||
if isinstance(each_shape, PptxPictureBoxModel):
|
||||
image_path = each_shape.picture.path
|
||||
if image_path.startswith("http"):
|
||||
if "app_data" in image_path:
|
||||
relative_path = image_path.split("app_data/")[1]
|
||||
each_shape.picture.path = os.path.join(
|
||||
"/app_data", relative_path
|
||||
)
|
||||
each_shape.picture.is_network = False
|
||||
continue
|
||||
image_urls.append(image_path)
|
||||
models_with_network_asset.append(each_shape)
|
||||
_process_image_path(each_shape, each_shape.picture.path)
|
||||
|
||||
if image_urls:
|
||||
image_paths = await download_files(image_urls, self._temp_dir)
|
||||
|
|
@ -312,6 +315,12 @@ class PptxPresentationCreator:
|
|||
|
||||
def add_picture(self, slide: Slide, picture_model: PptxPictureBoxModel):
|
||||
image_path = picture_model.picture.path
|
||||
# Resolve /app_data/... to actual filesystem path (Electron)
|
||||
if image_path.startswith("/app_data/"):
|
||||
app_data_dir = get_app_data_directory_env()
|
||||
if app_data_dir:
|
||||
relative = image_path[len("/app_data/"):]
|
||||
image_path = os.path.join(app_data_dir, relative)
|
||||
if (
|
||||
picture_model.clip
|
||||
or picture_model.border_radius
|
||||
|
|
|
|||
|
|
@ -1,7 +1,68 @@
|
|||
import os
|
||||
from typing import Optional
|
||||
from urllib.parse import urlparse, unquote
|
||||
|
||||
from utils.get_env import get_app_data_directory_env
|
||||
|
||||
|
||||
def resolve_image_path_to_filesystem(path_or_url: str) -> Optional[str]:
|
||||
"""
|
||||
Resolve an image path or URL to an actual filesystem path.
|
||||
|
||||
Handles:
|
||||
- Path strings: /app_data/images/..., /static/..., absolute paths, relative
|
||||
- HTTP URLs whose path component is an absolute filesystem path (Mac/Electron):
|
||||
When img src is /Users/.../images/xxx.png, browser resolves to
|
||||
http://origin/Users/.../images/xxx.png. Next.js returns 404 for these.
|
||||
|
||||
Returns the filesystem path if the file exists, else None.
|
||||
"""
|
||||
if not path_or_url:
|
||||
return None
|
||||
# Extract path from HTTP URL if needed
|
||||
path = path_or_url
|
||||
if path_or_url.startswith("http"):
|
||||
try:
|
||||
parsed = urlparse(path_or_url)
|
||||
path = unquote(parsed.path)
|
||||
except Exception:
|
||||
return None
|
||||
# Handle /app_data/images/
|
||||
if path.startswith("/app_data/images/"):
|
||||
relative = path[len("/app_data/images/"):]
|
||||
app_data = get_app_data_directory_env()
|
||||
if app_data:
|
||||
actual = os.path.join(app_data, "images", relative)
|
||||
if os.path.isfile(actual):
|
||||
return actual
|
||||
# Fallback: get_images_directory() + relative
|
||||
actual = os.path.join(get_images_directory(), relative)
|
||||
return actual if os.path.isfile(actual) else None
|
||||
# Handle /app_data/ (other subdirs)
|
||||
if path.startswith("/app_data/"):
|
||||
relative = path[len("/app_data/"):]
|
||||
app_data = get_app_data_directory_env()
|
||||
if app_data:
|
||||
actual = os.path.join(app_data, relative)
|
||||
return actual if os.path.isfile(actual) else None
|
||||
# Handle absolute filesystem path (e.g. from HTTP URL path on Mac)
|
||||
if path.startswith("/Users/") or path.startswith("/home/") or path.startswith("/var/"):
|
||||
return path if os.path.isfile(path) else None
|
||||
if "Application Support" in path or ("Library" in path and "images" in path):
|
||||
return path if os.path.isfile(path) else None
|
||||
# Handle /static/
|
||||
if path.startswith("/static/"):
|
||||
relative = path[len("/static/"):]
|
||||
actual = os.path.join("static", relative)
|
||||
return actual if os.path.isfile(actual) else None
|
||||
# Absolute path as-is
|
||||
if os.path.isabs(path):
|
||||
return path if os.path.isfile(path) else None
|
||||
# Relative to images directory
|
||||
actual = os.path.join(get_images_directory(), path)
|
||||
return actual if os.path.isfile(actual) else None
|
||||
|
||||
|
||||
def get_images_directory():
|
||||
images_directory = os.path.join(get_app_data_directory_env(), "images")
|
||||
os.makedirs(images_directory, exist_ok=True)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue