Spaces:
Sleeping
Sleeping
| 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 | |
| # ------------------------------- | |
| 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 | |
| # ------------------------------- | |
| def home(): | |
| return """ | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>TXT Converter</title> | |
| <style> | |
| body { | |
| font-family: Arial; | |
| background:#f7f7f7; | |
| margin:0; | |
| padding:20px; | |
| text-align:center; | |
| } | |
| h2 { | |
| margin-bottom:20px; | |
| } | |
| .drop { | |
| border:2px dashed #aaa; | |
| padding:40px; | |
| border-radius:10px; | |
| cursor:pointer; | |
| background:#fff; | |
| max-width:400px; | |
| margin:auto; | |
| } | |
| .grid { | |
| display:grid; | |
| grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); | |
| gap:10px; | |
| max-width:900px; | |
| margin:30px auto; | |
| } | |
| .btn { | |
| padding:10px; | |
| border:1px solid #ddd; | |
| background:#fff; | |
| cursor:pointer; | |
| transition:0.2s; | |
| } | |
| .btn:hover { | |
| background:#e74c3c; | |
| color:#fff; | |
| } | |
| .active { | |
| background:#e74c3c; | |
| color:#fff; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h2>Convert from TXT</h2> | |
| <div class="drop" onclick="fileInput.click()"> | |
| Click or Drop TXT File | |
| <input type="file" id="fileInput" hidden> | |
| </div> | |
| <div class="grid" id="formats"></div> | |
| <br> | |
| <button onclick="upload()">Convert & Download</button> | |
| <script> | |
| let file; | |
| let selectedFormat = "doc"; | |
| document.getElementById("fileInput").onchange = e => { | |
| file = e.target.files[0]; | |
| }; | |
| // formats list | |
| const formats = [ | |
| "doc","docx","html","md","odt","pdf", | |
| "rtf","tex","azw3","epub","lrf","mobi", | |
| "oeb","pdb","jpg","png" | |
| ]; | |
| const container = document.getElementById("formats"); | |
| formats.forEach(f => { | |
| let btn = document.createElement("div"); | |
| btn.className = "btn"; | |
| btn.innerText = "TXT to " + f.toUpperCase(); | |
| btn.onclick = () => { | |
| document.querySelectorAll(".btn").forEach(b => b.classList.remove("active")); | |
| btn.classList.add("active"); | |
| selectedFormat = f; | |
| }; | |
| container.appendChild(btn); | |
| }); | |
| // upload | |
| async function upload() { | |
| if (!file) { | |
| alert("Upload file first"); | |
| return; | |
| } | |
| let form = new FormData(); | |
| form.append("file", file); | |
| form.append("target", selectedFormat); | |
| let res = await fetch("/doc-converter", { | |
| method: "POST", | |
| body: form | |
| }); | |
| if (!res.ok) { | |
| alert("Conversion failed"); | |
| return; | |
| } | |
| let blob = await res.blob(); | |
| let url = window.URL.createObjectURL(blob); | |
| let a = document.createElement("a"); | |
| a.href = url; | |
| a.download = "converted." + selectedFormat; | |
| a.click(); | |
| } | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| # ------------------------------- | |
| # HEALTH | |
| # ------------------------------- | |
| def health(): | |
| return {"status": "ok"} | |
| # ------------------------------- | |
| # RUN | |
| # ------------------------------- | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run("app:app", host="0.0.0.0", port=7860) |