|
|
from fastapi import FastAPI, Request, File, UploadFile, HTTPException, Form, Query |
|
|
from fastapi.responses import HTMLResponse, JSONResponse, FileResponse |
|
|
from fastapi.staticfiles import StaticFiles |
|
|
from jinja2 import Environment, FileSystemLoader, select_autoescape |
|
|
from custom_logger import logger_config |
|
|
from image.main import is_process_running as is_image_process_running |
|
|
from image.image_base import ImageBase |
|
|
from pydantic import BaseModel |
|
|
from image.converter import Converter |
|
|
import mimetypes |
|
|
|
|
|
app = FastAPI(title="Tools Collection", description="Collection of utility tools") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
template_dirs = [".", "./image"] |
|
|
env = Environment( |
|
|
loader=FileSystemLoader(template_dirs), |
|
|
autoescape=select_autoescape(['html', 'xml']) |
|
|
) |
|
|
|
|
|
|
|
|
FEATURES = { |
|
|
"image": { |
|
|
"name": "Image Tools", |
|
|
"description": "HEIC to PNG/JPG conversion and metadata removal", |
|
|
"icon": "๐ผ๏ธ", |
|
|
"features": ["convert", "remove_metadata"], |
|
|
"folder": "image", |
|
|
"tags": ["image", "heic", "png", "jpg", "convert", "metadata"], |
|
|
"is_process_running": is_image_process_running |
|
|
}, |
|
|
"pdf": { |
|
|
"name": "PDF Tools", |
|
|
"description": "Convert images to PDF, merge PDFs, and more", |
|
|
"icon": "๐", |
|
|
"features": ["images_to_pdf"], |
|
|
"folder": "pdf", |
|
|
"tags": ["pdf", "merge", "convert", "images", "document"], |
|
|
"coming_soon": True, |
|
|
"is_process_running": is_image_process_running |
|
|
}, |
|
|
"audio": { |
|
|
"name": "Audio Tools", |
|
|
"description": "Convert audio formats and compress audio files", |
|
|
"icon": "๐ต", |
|
|
"features": ["convert_audio", "compress_audio"], |
|
|
"folder": "audio", |
|
|
"tags": ["audio", "music", "convert", "compress", "mp3", "wav"], |
|
|
"coming_soon": True, |
|
|
"is_process_running": is_image_process_running |
|
|
}, |
|
|
"video": { |
|
|
"name": "Video Tools", |
|
|
"description": "Basic video editing and format conversion", |
|
|
"icon": "๐ฌ", |
|
|
"features": ["convert_video", "compress_video"], |
|
|
"folder": "video", |
|
|
"tags": ["video", "convert", "compress", "mp4", "avi", "editing"], |
|
|
"coming_soon": True, |
|
|
"is_process_running": is_image_process_running |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@app.get("/", response_class=HTMLResponse) |
|
|
async def index(request: Request): |
|
|
template = env.get_template("index.html") |
|
|
html_content = template.render(request=request) |
|
|
return HTMLResponse(content=html_content) |
|
|
|
|
|
@app.get("/image/convert", response_class=HTMLResponse) |
|
|
async def image_tools(request: Request): |
|
|
template = env.get_template("image/index.html") |
|
|
html_content = template.render(request=request) |
|
|
return HTMLResponse(content=html_content) |
|
|
|
|
|
@app.post("/image/upload") |
|
|
async def upload_image( |
|
|
id: str = Form(...), |
|
|
image: UploadFile = File(...) |
|
|
): |
|
|
try: |
|
|
image_base = ImageBase() |
|
|
image_base.upload(id, image) |
|
|
|
|
|
|
|
|
return JSONResponse({ |
|
|
"success": True, |
|
|
"message": "Image uploaded successfully" |
|
|
}) |
|
|
|
|
|
except ValueError as ve: |
|
|
logger_config.error(f"Validation error: {str(ve)}") |
|
|
raise HTTPException( |
|
|
status_code=400, |
|
|
detail=str(ve) |
|
|
) |
|
|
except Exception as e: |
|
|
logger_config.error(f"Unexpected error during upload: {str(e)}") |
|
|
raise HTTPException( |
|
|
status_code=500, |
|
|
detail=f"Internal server error: {str(e)}" |
|
|
) |
|
|
|
|
|
@app.post("/image/convert") |
|
|
async def convert_image( |
|
|
id: str = Form(...), |
|
|
to_format: str = Form(...) |
|
|
): |
|
|
try: |
|
|
converter = Converter() |
|
|
output_path = converter.convert_image(id, to_format) |
|
|
|
|
|
|
|
|
return JSONResponse({ |
|
|
"success": True, |
|
|
"message": "Image uploaded successfully", |
|
|
"new_filename": output_path.split("/")[-1] |
|
|
}) |
|
|
|
|
|
except ValueError as ve: |
|
|
logger_config.error(f"Validation error: {str(ve)}") |
|
|
raise HTTPException( |
|
|
status_code=400, |
|
|
detail=str(ve) |
|
|
) |
|
|
except Exception as e: |
|
|
logger_config.error(f"Unexpected error during upload: {str(e)}") |
|
|
raise HTTPException( |
|
|
status_code=500, |
|
|
detail=f"Internal server error: {str(e)}" |
|
|
) |
|
|
|
|
|
@app.get("/image/download") |
|
|
async def download_converted_image( |
|
|
id: str = Query(...) |
|
|
): |
|
|
try: |
|
|
image_base = ImageBase() |
|
|
file_path = image_base.download_url(id) |
|
|
|
|
|
mime_type, _ = mimetypes.guess_type(file_path) |
|
|
|
|
|
return FileResponse(file_path, media_type=mime_type, filename=id) |
|
|
|
|
|
except ValueError as ve: |
|
|
logger_config.error(f"Validation error: {str(ve)}") |
|
|
raise HTTPException( |
|
|
status_code=400, |
|
|
detail=str(ve) |
|
|
) |
|
|
except Exception as e: |
|
|
logger_config.error(f"Unexpected error during upload: {str(e)}") |
|
|
raise HTTPException( |
|
|
status_code=500, |
|
|
detail=f"Internal server error: {str(e)}" |
|
|
) |
|
|
|
|
|
class DeleteRequest(BaseModel): |
|
|
ids: list[str] |
|
|
|
|
|
@app.post("/image/delete") |
|
|
async def delete_images(request: DeleteRequest): |
|
|
try: |
|
|
image_base = ImageBase() |
|
|
image_base.delete(request.ids) |
|
|
|
|
|
|
|
|
return JSONResponse({ |
|
|
"success": True, |
|
|
"message": "Image deleted successfully" |
|
|
}) |
|
|
|
|
|
except ValueError as ve: |
|
|
logger_config.error(f"Validation error: {str(ve)}") |
|
|
raise HTTPException( |
|
|
status_code=400, |
|
|
detail=str(ve) |
|
|
) |
|
|
except Exception as e: |
|
|
logger_config.error(f"Unexpected error during upload: {str(e)}") |
|
|
raise HTTPException( |
|
|
status_code=500, |
|
|
detail=f"Internal server error: {str(e)}" |
|
|
) |
|
|
|
|
|
@app.get("/api/features") |
|
|
async def get_features(): |
|
|
"""Get available features""" |
|
|
return {"features": FEATURES} |
|
|
|
|
|
@app.get("/api/search") |
|
|
async def search_features(q: str = ""): |
|
|
"""Search features by name, description, or tags""" |
|
|
if not q: |
|
|
return {"features": FEATURES} |
|
|
|
|
|
q = q.lower() |
|
|
filtered_features = {} |
|
|
|
|
|
for key, feature in FEATURES.items(): |
|
|
|
|
|
search_text = f"{feature['name']} {feature['description']} {' '.join(feature.get('tags', []))}".lower() |
|
|
if q in search_text: |
|
|
filtered_features[key] = feature |
|
|
|
|
|
return {"features": filtered_features} |
|
|
|
|
|
@app.get("/api/status") |
|
|
async def get_feature_status(): |
|
|
"""Get feature status""" |
|
|
features_status = {} |
|
|
for key in FEATURES.keys(): |
|
|
process_data = FEATURES[key]["is_process_running"]() |
|
|
features_status[key] = { |
|
|
"feature_name": process_data["feature"] if process_data else None, |
|
|
"is_busy": process_data is not None, |
|
|
"process_id": process_data["process_id"] if process_data else None |
|
|
} |
|
|
|
|
|
return features_status |
|
|
|
|
|
if __name__ == "__main__": |
|
|
import uvicorn |
|
|
uvicorn.run(app, host="0.0.0.0", port=8000) |