Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Application settings
APP_VERSION=v1.2.1
APP_VERSION=v1.3.0
FLASK_ENV=Production

# Additional settings can be added here
Expand Down
3 changes: 1 addition & 2 deletions asgi.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import uvicorn
from src.app import app
from src.api.app import app

if __name__ == "__main__":
print("Starting Transparent Image Cropper FastAPI app on http://0.0.0.0:5000")
uvicorn.run(app, host="0.0.0.0", port=5000)
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ Pillow==11.3.0
numpy==2.3.2
python-dotenv==1.1.1
jinja2==3.1.6
python-multipart==0.0.20
python-multipart==0.0.20
colorlog==6.9.0
1 change: 0 additions & 1 deletion src/__init__.py

This file was deleted.

1 change: 1 addition & 0 deletions src/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# API layer: endpoints, interceptors, middlewares, etc.
30 changes: 30 additions & 0 deletions src/api/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from src.api.router_registry import router_registry
from dotenv import load_dotenv
import os
import logging
from pathlib import Path

logging.basicConfig(level=logging.INFO)
load_dotenv()

app = FastAPI(
title="Smart Image Cropper API",
description="API for automatically cropping transparent areas and backgrounds from images",
version="1.0.0"
)

base_dir = Path(__file__).parent.parent

# Mount static files and templates using pathlib for cleaner paths
static_dir = base_dir / "frontend"
app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")

templates_dir = base_dir / "frontend" / "templates"
templates = Jinja2Templates(directory=str(templates_dir))
app.state.templates = templates

# Auto-register all controllers
router_registry.auto_register(app)
Empty file added src/api/controllers/__init__.py
Empty file.
10 changes: 10 additions & 0 deletions src/api/controllers/application_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import os
from fastapi import APIRouter

router = APIRouter()

@router.get("/api/app-info", tags=["App Info"])
def app_info():
environment = os.environ.get('FLASK_ENV', 'unknown environment')
version = os.environ.get('APP_VERSION', 'unknown version')
return {"environment": environment, "version": version}
Original file line number Diff line number Diff line change
@@ -1,44 +1,12 @@
from fastapi import APIRouter, Request, UploadFile, File, HTTPException
from fastapi.responses import JSONResponse, StreamingResponse, HTMLResponse
from src.services.image_service import auto_crop_image
import base64
import io
import os
from fastapi import File, HTTPException, UploadFile
from fastapi.responses import JSONResponse, StreamingResponse
from src.application.services import image_service
from fastapi import APIRouter

router = APIRouter()

@router.get("/", response_class=HTMLResponse, include_in_schema=False)
def index(request: Request):
templates = request.app.state.templates
environment = os.environ.get('FLASK_ENV', 'unknown')
version = os.environ.get('APP_VERSION', 'unknown')
version_url = f"https://github.com/Pianonic/CropTransparent/releases/tag/{version}"
return templates.TemplateResponse("index.html", {
"request": request,
"environment": environment,
"version": version,
"version_url": version_url
})

@router.get("/about", response_class=HTMLResponse, include_in_schema=False)
def about(request: Request):
templates = request.app.state.templates
environment = os.environ.get('FLASK_ENV', 'unknown')
version = os.environ.get('APP_VERSION', 'unknown')
version_url = f"https://github.com/Pianonic/CropTransparent/releases/tag/{version}"
return templates.TemplateResponse("about.html", {
"request": request,
"environment": environment,
"version": version,
"version_url": version_url
})

@router.get("/api/app-info", tags=["App Info"])
def app_info():
environment = os.environ.get('FLASK_ENV', 'unknown environment')
version = os.environ.get('APP_VERSION', 'unknown version')
return {"environment": environment, "version": version}

@router.post("/api/process", tags=["Image Processing"])
async def process_image(file: UploadFile = File(...)):
if not file:
Expand All @@ -47,7 +15,7 @@ async def process_image(file: UploadFile = File(...)):
raise HTTPException(status_code=400, detail="No selected file")
try:
image_data = await file.read()
output_buffer, original_size, cropped_size, crop_method, background_info, output_format = auto_crop_image(image_data)
output_buffer, original_size, cropped_size, crop_method, background_info, output_format = image_service.auto_crop_image(image_data)
encoded = base64.b64encode(output_buffer.getvalue()).decode('utf-8')
output_buffer.seek(0)
filename = file.filename or 'image.png'
Expand Down Expand Up @@ -108,7 +76,4 @@ async def download_image(data: dict):
"Content-Disposition": f"attachment; filename={filename}"
})
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

def register_routes(app):
app.include_router(router)
raise HTTPException(status_code=500, detail=str(e))
32 changes: 32 additions & 0 deletions src/api/controllers/static_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import os
from fastapi import Request
from fastapi.responses import HTMLResponse
from fastapi import APIRouter

router = APIRouter()

@router.get("/", response_class=HTMLResponse, include_in_schema=False)
def index(request: Request):
templates = request.app.state.templates
environment = os.environ.get('FLASK_ENV', 'unknown')
version = os.environ.get('APP_VERSION', 'unknown')
version_url = f"https://github.com/Pianonic/CropTransparent/releases/tag/{version}"
return templates.TemplateResponse("index.html", {
"request": request,
"environment": environment,
"version": version,
"version_url": version_url
})

@router.get("/about", response_class=HTMLResponse, include_in_schema=False)
def about(request: Request):
templates = request.app.state.templates
environment = os.environ.get('FLASK_ENV', 'unknown')
version = os.environ.get('APP_VERSION', 'unknown')
version_url = f"https://github.com/Pianonic/CropTransparent/releases/tag/{version}"
return templates.TemplateResponse("about.html", {
"request": request,
"environment": environment,
"version": version,
"version_url": version_url
})
73 changes: 73 additions & 0 deletions src/api/router_registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import os
import importlib
import logging
from pathlib import Path
from typing import Optional
from fastapi import FastAPI, APIRouter
import colorlog

# Configure FastAPI/Uvicorn-style colored logging
handler = colorlog.StreamHandler()
handler.setFormatter(colorlog.ColoredFormatter(
'%(log_color)sINFO%(reset)s: %(message)s',
log_colors={
'DEBUG': 'blue',
'INFO': 'green',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'bold_red',
}
))

logger = colorlog.getLogger(__name__)
logger.addHandler(handler)
logger.setLevel(logging.INFO)
logger.propagate = False # Prevent duplicate logs

class RouterRegistry:
def __init__(self, controllers_path: str = "src.api.controllers"):
self.controllers_path = controllers_path
self.registered_count = 0

def auto_register(self, app: FastAPI, controllers_dir: Optional[Path] = None) -> int:
"""Auto-discover and register all controller routers"""
if controllers_dir is None:
# Get the directory where this registry file is located
registry_file = Path(__file__).resolve()
# This file is in /api/router_registry.py, controllers are in /api/controllers/
controllers_dir = registry_file.parent / "controllers"

logger.info(f"Scanning controllers directory: {controllers_dir}")

if not controllers_dir.exists():
logger.warning(f"Controllers directory not found: {controllers_dir}")
return 0

self.registered_count = 0

for file_path in controllers_dir.glob("*.py"):
if file_path.name.startswith("__") or file_path.name.startswith("."):
continue

module_name = f"{self.controllers_path}.{file_path.stem}"

try:
module = importlib.import_module(module_name)

if hasattr(module, 'router') and isinstance(module.router, APIRouter):
app.include_router(module.router)
logger.info(f"Registered: {file_path.stem}")
self.registered_count += 1
else:
logger.debug(f"No router found in: {module_name}")

except ImportError as e:
logger.error(f"Import failed: {module_name} - {e}")
except Exception as e:
logger.error(f"Registration error: {module_name} - {e}")

logger.info(f"Successfully registered {self.registered_count} controller(s)")
return self.registered_count

# Global instance for easy use
router_registry = RouterRegistry()
30 changes: 0 additions & 30 deletions src/app.py

This file was deleted.

1 change: 1 addition & 0 deletions src/application/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Application layer: use cases and service interfaces
5 changes: 5 additions & 0 deletions src/application/services/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Services package for image processing functionality."""

from .image_service import auto_crop_image, crop_transparent_image, crop_by_background_color

__all__ = ['auto_crop_image', 'crop_transparent_image', 'crop_by_background_color']
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from PIL import Image
from PIL.Image import Image as PILImage
import numpy as np
import io

Expand Down
1 change: 0 additions & 1 deletion src/controllers/__init__.py

This file was deleted.

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
1 change: 0 additions & 1 deletion src/services/__init__.py

This file was deleted.

Loading