Text-Converter / app.py
Avanish11's picture
Update app.py
8a04f15 verified
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 """
<!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
# -------------------------------
@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)