import os import shutil import tempfile import subprocess from io import BytesIO from fastapi import FastAPI, UploadFile, File, Form, HTTPException from fastapi.responses import StreamingResponse, HTMLResponse app = FastAPI(title="Universal TXT Converter") # ------------------------------- # UTIL FUNCTIONS # ------------------------------- def tool_exists(name): return shutil.which(name) is not None def run_cmd(cmd): result = subprocess.run(cmd, shell=True, capture_output=True) if result.returncode != 0: raise Exception(result.stderr.decode()) return result.stdout # ------------------------------- # FULL CONVERSION LOGIC # ------------------------------- def convert_file(input_path, output_format): base = os.path.splitext(input_path)[0] output_path = f"{base}.{output_format}" input_ext = os.path.splitext(input_path)[1].replace(".", "").lower() # TXT → DOC/DOCX/ODT/PDF/RTF if input_ext == "txt" and output_format in ["doc", "docx", "odt", "pdf", "rtf"]: if not tool_exists("libreoffice"): raise Exception("LibreOffice required") run_cmd(f'libreoffice --headless --convert-to {output_format} "{input_path}" --outdir "{os.path.dirname(input_path)}"') return output_path # TXT → HTML / MD / TEX / EPUB if input_ext == "txt" and output_format in ["html", "md", "tex", "epub"]: if not tool_exists("pandoc"): raise Exception("Pandoc required") run_cmd(f'pandoc "{input_path}" -o "{output_path}"') return output_path # EPUB → MOBI / AZW3 / LRF / OEB / PDB if output_format in ["mobi", "azw3", "lrf", "oeb", "pdb"]: if not tool_exists("ebook-convert"): raise Exception("Calibre required") run_cmd(f'ebook-convert "{input_path}" "{output_path}"') return output_path # TXT → IMAGE if input_ext == "txt" and output_format in ["jpg", "png"]: from PIL import Image, ImageDraw with open(input_path, "r", encoding="utf-8", errors="ignore") as f: text = f.read()[:3000] img = Image.new("RGB", (1000, 800), "white") draw = ImageDraw.Draw(img) draw.text((20, 20), text, fill="black") img.save(output_path) return output_path raise Exception(f"Unsupported conversion: {input_ext} → {output_format}") # ------------------------------- # API ENDPOINT # ------------------------------- @app.post("/doc-converter") async def convert(file: UploadFile = File(...), target: str = Form(...)): try: tmpdir = tempfile.mkdtemp() input_path = os.path.join(tmpdir, file.filename) with open(input_path, "wb") as f: f.write(await file.read()) output_path = convert_file(input_path, target) if not os.path.exists(output_path): raise HTTPException(500, "Conversion failed") with open(output_path, "rb") as f: data = f.read() shutil.rmtree(tmpdir, ignore_errors=True) return StreamingResponse( BytesIO(data), media_type="application/octet-stream", headers={ "Content-Disposition": f'attachment; filename="{os.path.basename(output_path)}"' } ) except Exception as e: raise HTTPException(500, str(e)) # ------------------------------- # RESPONSIVE UI # ------------------------------- @app.get("/", response_class=HTMLResponse) def home(): return """ TXT Converter

Convert from TXT

Click or Drop TXT File

""" # ------------------------------- # HEALTH # ------------------------------- @app.get("/health") def health(): return {"status": "ok"} # ------------------------------- # RUN # ------------------------------- if __name__ == "__main__": import uvicorn uvicorn.run("app:app", host="0.0.0.0", port=7860)