generated from hjess/PythonTemplateProject
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 47s
177 lines
5.5 KiB
Python
177 lines
5.5 KiB
Python
import os
|
|
from pathlib import Path
|
|
|
|
from fastapi import HTTPException
|
|
from fastapi.responses import FileResponse
|
|
from fastapi import APIRouter, Request, FastAPI
|
|
from PIL import Image
|
|
|
|
|
|
class FileHandler:
|
|
|
|
def __init__(self, category=None, image_type=None, filename=None):
|
|
self.filename = filename
|
|
self.category = category
|
|
|
|
self.image_type = image_type
|
|
|
|
@property
|
|
def src_file(self) -> str:
|
|
src_path = "data/{category}/images/{filename}"
|
|
return src_path.format( category = self.category, filename = self.filename )
|
|
|
|
@property
|
|
def dest_file(self) -> str:
|
|
base_url = "/images/{category}/{filename}"
|
|
return base_url.format( category = self.category, filename = self.filename )
|
|
|
|
@property
|
|
def dest_filename(self) -> str:
|
|
base_url = "static/images/{category}/{image_type}/{filename}"
|
|
return base_url.format( category = self.category, image_type = self.image_type, filename = self.filename )
|
|
@property
|
|
def dest_filename_webp(self) -> str:
|
|
base_url = "static/images/{category}/{image_type}/{filename}"
|
|
path = Path( base_url.format( category = self.category, image_type = self.image_type, filename = self.filename ) )
|
|
if path.suffix != ".webp":
|
|
path = path.with_suffix( ".webp" )
|
|
return str(path)
|
|
|
|
@property
|
|
def dest_path(self) -> str:
|
|
base_url = "static/images/{category}/{image_type}"
|
|
return base_url.format( category = self.category, image_type = self.image_type )
|
|
|
|
def __str__(self):
|
|
return (
|
|
f"FileHandler(\n"
|
|
f" filename='{self.filename}',\n"
|
|
f" category='{self.category}',\n"
|
|
f" image_type='{self.image_type}',\n"
|
|
f" src_file='{self.src_file}',\n"
|
|
f" dest_file='{self.dest_file}',\n"
|
|
f" dest_filename='{self.dest_filename}'\n"
|
|
f")"
|
|
)
|
|
|
|
def get_category(self, file_path):
|
|
# List all categories in the data directory
|
|
categories = [
|
|
name for name in os.listdir( "data/" )
|
|
if os.path.isdir( os.path.join( "data/", name ) )
|
|
]
|
|
|
|
# Search for the category in the file path
|
|
for category in categories:
|
|
if f"/{category}/" in file_path or f"\\{category}\\" in file_path:
|
|
return category
|
|
|
|
# Return None if no category matches
|
|
return None
|
|
|
|
|
|
class ImageService:
|
|
|
|
def __init__(self,app: FastAPI):
|
|
self.router = APIRouter()
|
|
self.app = app
|
|
self.IMAGE_SIZES = self.app.state.IMAGE_SIZES
|
|
#self._ensure_directories_exist()
|
|
self._add_routes()
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
"""
|
|
Provides a string representation of the class instance.
|
|
"""
|
|
base_paths_str = "\n".join(
|
|
[f"{key}: {value}" for key, value in self.base_paths.items()]
|
|
)
|
|
image_sizes_str = "\n".join(
|
|
[
|
|
f"{key}: width={value['width']}, height={value['height']}"
|
|
for key, value in self.image_sizes.items()
|
|
]
|
|
)
|
|
return f"<Class:ImageService Base Paths:{base_paths_str} Image Sizes:\n{image_sizes_str}"
|
|
|
|
def get_image_size(self, image_type: str) -> dict:
|
|
"""
|
|
Retrieve the width and height for a given image type from the app state.
|
|
|
|
Args:
|
|
request (Request): FastAPI request object.
|
|
image_type (str): The type of the image (e.g., 'thumbnails').
|
|
|
|
Returns:
|
|
dict: A dictionary with 'width' and 'height'.
|
|
"""
|
|
|
|
image_sizes = self.app.state.IMAGE_SIZES
|
|
if image_type not in image_sizes:
|
|
raise ValueError( f"Invalid image type: {image_type}. Must be one of {list( image_sizes.keys() )}" )
|
|
|
|
return image_sizes[image_type]
|
|
|
|
def _add_routes(self):
|
|
self.router.add_api_route(
|
|
"/image/{category}/{type}/{filename}",
|
|
self.get_image,
|
|
methods=["GET"],
|
|
response_class=FileResponse,
|
|
)
|
|
|
|
async def get_image(self, category: str, type: str, filename: str):
|
|
"""
|
|
Retrieve an image file from the specified category and type.
|
|
"""
|
|
file_path = self._resolve_path(category, type, filename)
|
|
return FileResponse(file_path)
|
|
|
|
def validate_image(self, file_path:FileHandler=None, width:int=None, height:int=None, overwrite = True ) -> bool:
|
|
if not os.path.exists( file_path.dest_filename ):
|
|
with Image.open( file_path.src_file ) as img:
|
|
print(file_path.src_file)
|
|
self._resize_image( img, file_path, width, height )
|
|
return True
|
|
|
|
with Image.open( file_path.dest_filename ) as img:
|
|
if img.width != width or img.height != height:
|
|
if overwrite:
|
|
self._resize_image( img, file_path, width, height )
|
|
return False
|
|
return True
|
|
|
|
def _resize_image(self, img: Image.Image, file_path: FileHandler, width: int, height: int):
|
|
resized_img = img.resize( (width, height), Image.Resampling.LANCZOS )
|
|
os.makedirs(file_path.dest_path,exist_ok = True)
|
|
resized_img.save( file_path.dest_filename )
|
|
resized_img.save( file_path.dest_filename_webp, format = "WEBP", quality = 90 ) # Adjust quality as needed
|
|
print(file_path.dest_filename_webp)
|
|
|
|
async def get_image(self, category: str, type: str, filename: str):
|
|
file_path = self._resolve_path( category, type, filename )
|
|
return FileResponse( file_path )
|
|
|
|
def image_tag(self, category: str, image_type: str, filename: str, alt: str = "", width: int = None,
|
|
height: int = None) -> str:
|
|
"""
|
|
Generate an HTML <img> tag with default sizes if dimensions are not provided.
|
|
"""
|
|
# Use default sizes if none are provided
|
|
default_size = self.get_image_size( image_type)
|
|
width = width or default_size.get( "width" )
|
|
height = height or default_size.get( "height" )
|
|
file_path = FileHandler(category = category,image_type = image_type,filename = filename)
|
|
self.validate_image( file_path, width = width,height=height, overwrite = True )
|
|
|
|
tag = f'<img src="/{file_path.dest_filename_webp}" alt="{alt}"'
|
|
if width:
|
|
tag += f' width="{width}"'
|
|
if height:
|
|
tag += f' height="{height}"'
|
|
tag += ">"
|
|
|
|
return tag |