#!/usr/bin/env python3 """LibreOffice converter — Flask HTTP service. .doc → .docx with UI + API.""" import subprocess, tempfile, os, base64 from flask import Flask, request, send_file, render_template_string app = Flask(__name__) VERSION = "1.0.0" # Логотип как data URI (как в Сверке — через img, не inline SVG) _LOGO_SVG = """""" LOGO_DATA_URI = "data:image/svg+xml;base64," + base64.b64encode(_LOGO_SVG.encode()).decode() HTML = """ Конвертер документов — Nubes
Nubes КОНВЕРТЕР v__VERSION__

Конвертация старых документов Word

Загрузите файл в формате .doc (Microsoft Word 97–2003), .rtf или .odt — получите современный .docx. Конвертация происходит на сервере через LibreOffice, исходный файл никуда не сохраняется.

📦 Конвертировать файл
🔌 API для разработчиков
Адрес: https://liberta.containerk8s.dev.nubes.ru/convert
Метод: POST, файл в multipart/form-data, поле file.
Успех: в ответ придёт готовый .docx — сразу сохраняйте.
Ошибка: {"ok": false, "error": "..."} с кодом 500.
Проверка: GET /health{"ok": true}

Пример curl:
curl -X POST -F "file=@документ.doc" https://liberta.containerk8s.dev.nubes.ru/convert -o результат.docx
""" def _convert_file(f): """Convert one file to .docx, return (bytes, download_name) or raise.""" with tempfile.NamedTemporaryFile(suffix=".doc", delete=False) as tmp: f.save(tmp) doc_path = tmp.name tmpdir = tempfile.mkdtemp() try: subprocess.run( ["libreoffice", "--headless", "--convert-to", "docx", "--outdir", tmpdir, doc_path], timeout=120, capture_output=True, check=True ) docx_files = [x for x in os.listdir(tmpdir) if x.endswith(".docx")] if not docx_files: raise ValueError("conversion produced no .docx") with open(os.path.join(tmpdir, docx_files[0]), "rb") as out: return out.read(), os.path.splitext(f.filename)[0] + ".docx" finally: if os.path.exists(doc_path): os.unlink(doc_path) for x in os.listdir(tmpdir): os.unlink(os.path.join(tmpdir, x)) os.rmdir(tmpdir) @app.route("/", methods=["GET"]) def index(): return render_template_string(HTML.replace("__VERSION__", VERSION)) @app.route("/convert", methods=["POST"]) def convert(): if "file" not in request.files: return {"ok": False, "error": "no file"}, 400 f = request.files["file"] if not f.filename: return {"ok": False, "error": "empty filename"}, 400 try: data, name = _convert_file(f) return send_file( io.BytesIO(data), mimetype="application/vnd.openxmlformats-officedocument.wordprocessingml.document", as_attachment=True, download_name=name, ) except subprocess.TimeoutExpired: return {"ok": False, "error": "conversion timeout"}, 500 except subprocess.CalledProcessError as e: err = e.stderr.decode() if e.stderr else "unknown" return {"ok": False, "error": f"libreoffice: {err}"}, 500 except ValueError as e: return {"ok": False, "error": str(e)}, 500 @app.route("/health", methods=["GET"]) def health(): return {"ok": True, "version": VERSION} @app.route("/static/logo.svg") def logo(): from flask import Response return Response(_LOGO_SVG, mimetype="image/svg+xml") if __name__ == "__main__": import io app.run(host="0.0.0.0", port=5000)