from fastapi import FastAPI, UploadFile, File, Request from fastapi.responses import HTMLResponse, JSONResponse, PlainTextResponse from fastapi.templating import Jinja2Templates from fastapi.staticfiles import StaticFiles from markitdown import MarkItDown from markitdown._stream_info import StreamInfo import io, os, mimetypes app = FastAPI(title="MarkItDown Converter") templates = Jinja2Templates(directory="templates") # markitdown supports: PDF, DOCX, PPTX, XLSX, HTML, CSV, JSON, XML, # images (with OCR via LLM), audio, ZIP, EPUB, YouTube URLs, Wikipedia, etc. ALLOWED_EXTENSIONS = { ".pdf", ".docx", ".doc", ".pptx", ".ppt", ".xlsx", ".xls", ".html", ".htm", ".csv", ".json", ".xml", ".epub", ".txt", ".md", ".rst", ".zip", ".msg", ".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".mp3", ".wav", ".m4a", } MAX_SIZE_MB = 50 @app.get("/", response_class=HTMLResponse) async def index(request: Request): return templates.TemplateResponse("index.html", {"request": request}) @app.post("/convert") async def convert(file: UploadFile = File(...)): # Validate extension _, ext = os.path.splitext(file.filename or "") ext = ext.lower() if ext not in ALLOWED_EXTENSIONS: return JSONResponse( {"error": f"Unsupported file type: '{ext}'. Supported: {', '.join(sorted(ALLOWED_EXTENSIONS))}"}, status_code=400 ) # Read into memory data = await file.read() size_mb = len(data) / (1024 * 1024) if size_mb > MAX_SIZE_MB: return JSONResponse( {"error": f"File too large ({size_mb:.1f} MB). Max: {MAX_SIZE_MB} MB"}, status_code=413 ) try: md = MarkItDown() stream = io.BytesIO(data) mime = mimetypes.types_map.get(ext, "application/octet-stream") result = md.convert_stream( stream, stream_info=StreamInfo( mimetype=mime, extension=ext, filename=file.filename, ) ) markdown_text = result.text_content or "" return JSONResponse({ "markdown": markdown_text, "filename": file.filename, "size_kb": round(len(data) / 1024, 1), "chars": len(markdown_text), }) except Exception as e: return JSONResponse({"error": f"Conversion failed: {str(e)}"}, status_code=500)