501 lines
22 KiB
Python
501 lines
22 KiB
Python
from __future__ import annotations
|
|
|
|
import base64
|
|
import io
|
|
import json
|
|
import os
|
|
import tempfile
|
|
import traceback
|
|
import uuid
|
|
from typing import Any, Dict, Optional
|
|
|
|
import torch
|
|
from flask import Flask, jsonify, request, Response
|
|
import PIL.Image
|
|
|
|
# Marker imports
|
|
from marker.config.parser import ConfigParser
|
|
from marker.converters.pdf import PdfConverter
|
|
from marker.models import create_model_dict
|
|
from marker.output import text_from_rendered
|
|
from marker.providers.registry import load_extensions
|
|
from marker.settings import settings as marker_settings
|
|
|
|
# Ollama / OCR fallback configuration
|
|
OLLAMA_HOST = os.environ.get("OLLAMA_HOST", "http://10.0.1.127:11434")
|
|
DEESEEK_OCR_MODEL = os.environ.get("DEESEEK_OCR_MODEL", "deepseek-ocr")
|
|
|
|
AMD_COMPUTE = os.environ.get("AMD_COMPUTE", "false").lower() in ("true", "1", "yes")
|
|
TORCH_DEVICE = os.environ.get("TORCH_DEVICE", "")
|
|
MODEL_DTYPE = os.environ.get("MODEL_DTYPE", "float32")
|
|
|
|
_marker_dict: Optional[Dict[str, Any]] = None
|
|
|
|
# Collect supported file extensions
|
|
SUPPORTED_EXTENSIONS = set()
|
|
for provider_type in ("image", "pdf", "epub", "doc", "xls", "ppt"):
|
|
SUPPORTED_EXTENSIONS.update(load_extensions(provider_type))
|
|
SUPPORTED_DISPLAY = sorted({ext.lstrip(".") for ext in SUPPORTED_EXTENSIONS})
|
|
|
|
# Env-var overrides for default LLM service
|
|
DEFAULT_LLM_SERVICE = os.environ.get("LLM_SERVICE", "marker.services.ollama.OllamaService")
|
|
DEFAULT_USE_LLM = os.environ.get("USE_LLM", "false").lower() in ("true", "1", "yes")
|
|
DEFAULT_OPENAI_BASE_URL = os.environ.get("OPENAI_BASE_URL", "")
|
|
DEFAULT_OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "")
|
|
DEFAULT_OPENAI_MODEL = os.environ.get("OPENAI_MODEL", "")
|
|
|
|
|
|
def _configure_env():
|
|
"""Apply AMD GPU / environment overrides before torch loads."""
|
|
if AMD_COMPUTE and not TORCH_DEVICE:
|
|
os.environ["TORCH_DEVICE"] = "cuda"
|
|
os.environ["TORCH_DEVICE_MODEL"] = "cuda"
|
|
os.environ["HSA_OVERRIDE_GFX_VERSION"] = "9.0.6"
|
|
if MODEL_DTYPE == "bfloat16":
|
|
os.environ["MODEL_DTYPE"] = "bfloat16"
|
|
if torch_device_override := os.environ.get("TORCH_DEVICE"):
|
|
os.environ["TORCH_DEVICE"] = torch_device_override
|
|
os.environ["TORCH_DEVICE_MODEL"] = torch_device_override
|
|
|
|
|
|
def get_model_dict() -> Dict[str, Any]:
|
|
global _marker_dict
|
|
if _marker_dict is None:
|
|
_marker_dict = create_model_dict()
|
|
return _marker_dict
|
|
|
|
|
|
def build_options(**extra: Any) -> Dict[str, Any]:
|
|
opts: Dict[str, Any] = {}
|
|
for k, v in extra.items():
|
|
if v is not None:
|
|
opts[k] = v
|
|
|
|
# Ensure defaults are set
|
|
opts.setdefault("output_format", "markdown")
|
|
opts.setdefault("force_ocr", False)
|
|
opts.setdefault("paginate_output", False)
|
|
opts.setdefault("page_range", None)
|
|
opts.setdefault("disable_multiprocessing", True)
|
|
opts.setdefault("disable_image_extraction", False)
|
|
opts.setdefault("output_dir", marker_settings.OUTPUT_DIR)
|
|
opts.setdefault("llm_service", DEFAULT_LLM_SERVICE)
|
|
opts.setdefault("use_llm", DEFAULT_USE_LLM)
|
|
|
|
# Pass default LLM settings from environment if not explicitly provided
|
|
if DEFAULT_OPENAI_BASE_URL:
|
|
opts.setdefault("openai_base_url", DEFAULT_OPENAI_BASE_URL)
|
|
if DEFAULT_OPENAI_API_KEY:
|
|
opts.setdefault("openai_api_key", DEFAULT_OPENAI_API_KEY)
|
|
if DEFAULT_OPENAI_MODEL:
|
|
opts.setdefault("openai_model", DEFAULT_OPENAI_MODEL)
|
|
if OLLAMA_HOST:
|
|
opts.setdefault("ollama_base_url", OLLAMA_HOST)
|
|
if DEESEEK_OCR_MODEL:
|
|
opts.setdefault("ollama_model", DEESEEK_OCR_MODEL)
|
|
|
|
config_parser = ConfigParser(opts)
|
|
return config_parser.generate_config_dict()
|
|
|
|
|
|
def convert_file_bytes(raw_bytes: bytes, filename: str, **opts: Any) -> dict:
|
|
"""Convert in-memory file bytes to markdown (or other format)."""
|
|
|
|
tmp = tempfile.NamedTemporaryFile(delete=False, suffix="." + filename.rsplit(".", 1)[-1] if "." in filename else "")
|
|
try:
|
|
tmp.write(raw_bytes)
|
|
tmp.close()
|
|
filepath = tmp.name
|
|
|
|
config_dict = build_options(**opts)
|
|
config_dict["disable_tqdm"] = True
|
|
|
|
model_dict = get_model_dict()
|
|
|
|
# Build parsed options, preserving server default LLM config when client omits it
|
|
parsed_opts = {k: v for k, v in opts.items() if v is not None}
|
|
for key in ("llm_service", "openai_base_url", "openai_api_key", "openai_model"):
|
|
if key not in parsed_opts and key in config_dict:
|
|
parsed_opts[key] = config_dict[key]
|
|
parsed = ConfigParser(parsed_opts)
|
|
|
|
converter = PdfConverter(
|
|
config=config_dict,
|
|
artifact_dict=model_dict,
|
|
processor_list=parsed.get_processors(),
|
|
renderer=parsed.get_renderer(),
|
|
llm_service=parsed.get_llm_service(),
|
|
)
|
|
rendered = converter(filepath)
|
|
text, _, images = text_from_rendered(rendered)
|
|
|
|
except Exception as exc:
|
|
traceback.print_exc()
|
|
return {"success": False, "error": str(exc)}
|
|
finally:
|
|
tmp.close()
|
|
if os.path.exists(tmp.name):
|
|
os.remove(tmp.name)
|
|
|
|
encoded_images: Dict[str, str] = {}
|
|
img_fmt = marker_settings.OUTPUT_IMAGE_FORMAT
|
|
for k, img in images.items():
|
|
buf = io.BytesIO()
|
|
img.save(buf, format=img_fmt)
|
|
encoded_images[k] = base64.b64encode(buf.getvalue()).decode("utf-8")
|
|
|
|
return {
|
|
"format": opts.get("output_format", "markdown"),
|
|
"output": text,
|
|
"images_b64": encoded_images,
|
|
"metadata": rendered.metadata if 'rendered' in dir() else {},
|
|
"success": True,
|
|
}
|
|
|
|
|
|
def create_app() -> Flask:
|
|
app = Flask(__name__)
|
|
|
|
# ---- docs page ----
|
|
HTML_DOCS = r"""<html><head><title>marker-api</title>
|
|
<style>body{font-family:sans-serif;max-width:900px;margin:40px auto;padding:0 20px}
|
|
pre{background:#f4f4f4;padding:12px;border-radius:4px;overflow-x:auto}
|
|
table{border-collapse:collapse;width:100%;margin:16px 0}th,td{border:1px solid #ccc;padding:8px;text-align:left}
|
|
th{background:#f0f0f0}</style></head><body>
|
|
<h1>marker-api</h1>
|
|
<p>Convert PDFs, EPUBs, DOCX, XLSX, PPTX, HTML, and images to Markdown.</p>
|
|
<h2>Endpoints</h2>
|
|
<table>
|
|
<tr><th>Endpoint</th><th>Method</th><th>Description</th></tr>
|
|
<tr><td>/</td><td>GET</td><td>This documentation page</td></tr>
|
|
<tr><td>/health</td><td>GET</td><td>Health check with configuration</td></tr>
|
|
<tr><td>/marker</td><td>POST</td><td>Convert a file (sync, returns result)</td></tr>
|
|
<tr><td>/v1/conversions</td><td>POST</td><td>Convert a file (async-style, returns directly)</td></tr>
|
|
<tr><td>/v1/files/convert</td><td>POST</td><td>Convert a file via JSON body with base64</td></tr>
|
|
</table>
|
|
|
|
<h2>POST /marker <small>(multipart/form-data)</small></h2>
|
|
<pre><code>curl -X POST http://localhost:8000/marker \
|
|
-F "file=@document.pdf" \
|
|
-F "force_ocr=false" \
|
|
-F "page_range=0,5-10" \
|
|
-F "output_format=markdown"</code></pre>
|
|
<p>Returns the converted content directly as a file download.</p>
|
|
|
|
<h2>POST /marker <small>(application/json)</small></h2>
|
|
<pre><code>curl -X POST http://localhost:8000/marker \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"file_b64": "base64-encoded-file-content",
|
|
"filename": "document.pdf",
|
|
"output_format": "markdown",
|
|
"force_ocr": false
|
|
}'</code></pre>
|
|
|
|
<h2>POST /v1/files/convert <small>(application/json)</small></h2>
|
|
<pre><code>curl -X POST http://localhost:8000/v1/files/convert \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"file_b64": "base64-encoded-file-content",
|
|
"filename": "document.pdf",
|
|
"output_format": "json",
|
|
"page_range": "0,5-10"
|
|
}'</code></pre>
|
|
|
|
<h3>Options</h3>
|
|
<table>
|
|
<tr><th>Parameter</th><th>Type</th><th>Default</th><th>Description</th></tr>
|
|
<tr><td><b>file</b> / <b>file_b64</b></td><td>file / string</td><td><b>required</b></td><td>The file to convert</td></tr>
|
|
<tr><td>force_ocr</td><td>bool</td><td>false</td><td>Force OCR on all pages</td></tr>
|
|
<tr><td>paginate_output</td><td>bool</td><td>false</td><td>Separate pages with horizontal rules</td></tr>
|
|
<tr><td>output_format</td><td>string</td><td>markdown</td><td>markdown, json, html, chunks</td></tr>
|
|
<tr><td>page_range</td><td>string</td><td>all</td><td>Comma-separated page numbers/ranges: "0,5-10"</td></tr>
|
|
<tr><td>disable_image_extraction</td><td>bool</td><td>false</td><td>Disable extraction of embedded images</td></tr>
|
|
<tr><td>processors</td><td>string</td><td>auto</td><td>Comma-separated full module paths</td></tr>
|
|
<tr><td>config_json</td><td>string</td><td>none</td><td>Path to JSON file with additional config</td></tr>
|
|
<tr><td>converter_cls</td><td>string</td><td>auto-detected</td><td>Full module path of converter class</td></tr>
|
|
<tr><td>use_llm</td><td>bool</td><td>false</td><td>Use an LLM to improve accuracy (requires LLM service)</td></tr>
|
|
<tr><td>llm_service</td><td>string</td><td>marker.services.<br>ollama.OllamaService</td><td>LLM service class path: gemini, vertex, claude, openai, azure_openai, ollama</td></tr>
|
|
<tr><td>block_correction_prompt</td><td>string</td><td>none</td><td>Custom prompt for LLM block correction</td></tr>
|
|
<tr><td>redo_inline_math</td><td>bool</td><td>false</td><td>Re-process inline math with LLM</td></tr>
|
|
<tr><td>strip_existing_ocr</td><td>bool</td><td>false</td><td>Remove all existing OCR text and re-OCR</td></tr>
|
|
<tr><td>debug</td><td>bool</td><td>false</td><td>Enable debug mode with additional logging</td></tr>
|
|
</table>
|
|
|
|
<h3>Supported Formats</h3>
|
|
<p><code>{formats}</code></p>
|
|
|
|
<h3>Environment Variables</h3>
|
|
<table>
|
|
<tr><th>Variable</th><th>Default</th><th>Description</th></tr>
|
|
<tr><td>OLLAMA_HOST</td><td>http://10.0.1.127:11434</td><td>Ollama instance for OCR fallback</td></tr>
|
|
<tr><td>DEESEEK_OCR_MODEL</td><td>deepseek-ocr</td><td>OCR model name in Ollama</td></tr>
|
|
<tr><td>AMD_COMPUTE</td><td>false</td><td>Enable AMD ROCm GPU compute (set to "true")</td></tr>
|
|
<tr><td>TORCH_DEVICE</td><td>auto</td><td>PyTorch device: rocm, cuda, cpu</td></tr>
|
|
<tr><td>MODEL_DTYPE</td><td>float32</td><td>Model dtype: float32, bfloat16</td></tr>
|
|
<tr><td>PORT</td><td>8000</td><td>Listening port</td></tr>
|
|
<tr><td>HOST</td><td>0.0.0.0</td><td>Listening host</td></tr>
|
|
<tr><td>LLM_SERVICE</td><td>marker.services.ollama.OllamaService</td><td>Default LLM service class for use_llm</td></tr>
|
|
<tr><td>USE_LLM</td><td>false</td><td>Default use_llm flag (true/false)</td></tr>
|
|
<tr><td>OPENAI_BASE_URL</td><td></td><td>Base URL for OpenAI-compatible LLM service</td></tr>
|
|
<tr><td>OPENAI_API_KEY</td><td></td><td>API key for OpenAI-compatible LLM service</td></tr>
|
|
<tr><td>OPENAI_MODEL</td><td></td><td>Model name for OpenAI-compatible LLM service</td></tr>
|
|
</table>
|
|
</body></html>"""
|
|
|
|
@app.route("/")
|
|
def docs():
|
|
body = HTML_DOCS.replace("{formats}", ", ".join(SUPPORTED_DISPLAY))
|
|
return body
|
|
|
|
@app.route("/health")
|
|
def health():
|
|
try:
|
|
device = TORCH_DEVICE or (marker_settings.TORCH_DEVICE_MODEL if hasattr(marker_settings, 'TORCH_DEVICE_MODEL') else 'auto')
|
|
except Exception:
|
|
device = 'unknown'
|
|
return jsonify({
|
|
"status": "ok",
|
|
"ollama": OLLAMA_HOST,
|
|
"ocr_model": DEESEEK_OCR_MODEL,
|
|
"amd_compute": AMD_COMPUTE,
|
|
"torch_device": device,
|
|
"supported_formats": SUPPORTED_DISPLAY,
|
|
"provider": "flask",
|
|
"default_llm_service": DEFAULT_LLM_SERVICE,
|
|
"default_use_llm": DEFAULT_USE_LLM,
|
|
"openai_base_url": DEFAULT_OPENAI_BASE_URL or None,
|
|
"openai_model": DEFAULT_OPENAI_MODEL or None,
|
|
})
|
|
|
|
@app.route("/marker", methods=["POST"])
|
|
def convert_sync():
|
|
if "file" in request.files:
|
|
file = request.files["file"]
|
|
filename = file.filename or "file"
|
|
raw = file.read()
|
|
|
|
fmt = request.form.get("output_format", "markdown")
|
|
opts = {
|
|
"page_range": request.form.get("page_range"),
|
|
"paginate_output": request.form.get("paginate_output", "false").lower() == "true",
|
|
"force_ocr": request.form.get("force_ocr", "false").lower() == "true",
|
|
"output_format": fmt,
|
|
"disable_image_extraction": request.form.get("disable_image_extraction", "false").lower() == "true",
|
|
"processors": request.form.get("processors"),
|
|
"config_json": request.form.get("config_json"),
|
|
"converter_cls": request.form.get("converter_cls"),
|
|
"use_llm": request.form.get("use_llm", "false").lower() == "true",
|
|
"llm_service": request.form.get("llm_service"),
|
|
"block_correction_prompt": request.form.get("block_correction_prompt"),
|
|
"redo_inline_math": request.form.get("redo_inline_math", "false").lower() == "true",
|
|
"strip_existing_ocr": request.form.get("strip_existing_ocr", "false").lower() == "true",
|
|
"debug": request.form.get("debug", "false").lower() == "true",
|
|
}
|
|
result = convert_file_bytes(raw, filename, **opts)
|
|
|
|
if not result["success"]:
|
|
return jsonify(result), 500
|
|
|
|
if fmt == "markdown":
|
|
return Response(
|
|
result["output"],
|
|
mimetype="text/plain",
|
|
headers={"Content-Disposition": f'attachment; filename="{filename.rsplit(".", 1)[0]}.md"'},
|
|
)
|
|
return jsonify(result)
|
|
|
|
if request.is_json:
|
|
data = request.get_json()
|
|
if "file_b64" not in data or not data.get("filename"):
|
|
return jsonify({"error": "JSON body must include 'file_b64' and 'filename'"}), 400
|
|
raw = base64.b64decode(data["file_b64"])
|
|
fmt = data.get("output_format", "markdown")
|
|
opts = {
|
|
"page_range": data.get("page_range"),
|
|
"paginate_output": data.get("paginate_output", False),
|
|
"force_ocr": data.get("force_ocr", False),
|
|
"output_format": fmt,
|
|
"disable_image_extraction": data.get("disable_image_extraction", False),
|
|
"processors": data.get("processors"),
|
|
"config_json": data.get("config_json"),
|
|
"converter_cls": data.get("converter_cls"),
|
|
"use_llm": data.get("use_llm", False),
|
|
"llm_service": data.get("llm_service"),
|
|
"block_correction_prompt": data.get("block_correction_prompt"),
|
|
"redo_inline_math": data.get("redo_inline_math", False),
|
|
"strip_existing_ocr": data.get("strip_existing_ocr", False),
|
|
"debug": data.get("debug", False),
|
|
}
|
|
result = convert_file_bytes(raw, data["filename"], **opts)
|
|
return jsonify(result)
|
|
|
|
return jsonify({"error": "No file provided. Use multipart/form-data or JSON with 'file_b64'."}), 400
|
|
|
|
@app.route("/v1/conversions", methods=["POST"])
|
|
def convert_async_style():
|
|
"""Convert a file, returns the result directly (async-style naming for API compatibility)."""
|
|
if request.is_json:
|
|
data = request.get_json()
|
|
if "file_b64" not in data or not data.get("filename"):
|
|
return jsonify({"error": "JSON body must include 'file_b64' and 'filename'"}), 400
|
|
|
|
raw = base64.b64decode(data["file_b64"])
|
|
fmt = data.get("output_format", "markdown")
|
|
opts = {
|
|
"page_range": data.get("page_range"),
|
|
"paginate_output": data.get("paginate_output", False),
|
|
"force_ocr": data.get("force_ocr", False),
|
|
"output_format": fmt,
|
|
"disable_image_extraction": data.get("disable_image_extraction", False),
|
|
"processors": data.get("processors"),
|
|
"config_json": data.get("config_json"),
|
|
"converter_cls": data.get("converter_cls"),
|
|
"use_llm": data.get("use_llm", False),
|
|
"llm_service": data.get("llm_service"),
|
|
"block_correction_prompt": data.get("block_correction_prompt"),
|
|
"redo_inline_math": data.get("redo_inline_math", False),
|
|
"strip_existing_ocr": data.get("strip_existing_ocr", False),
|
|
"debug": data.get("debug", False),
|
|
}
|
|
result = convert_file_bytes(raw, data["filename"], **opts)
|
|
|
|
if not result["success"]:
|
|
return jsonify(result), 500
|
|
|
|
return jsonify({
|
|
"id": str(uuid.uuid4()),
|
|
"filename": data["filename"],
|
|
"format": fmt,
|
|
"output": result["output"],
|
|
"success": True,
|
|
"images_b64": result.get("images_b64", {}),
|
|
"metadata": result.get("metadata", {}),
|
|
})
|
|
|
|
if "file" in request.files:
|
|
file = request.files["file"]
|
|
filename = file.filename or "file"
|
|
raw = file.read()
|
|
fmt = request.form.get("output_format", "markdown")
|
|
opts = {
|
|
"page_range": request.form.get("page_range"),
|
|
"paginate_output": request.form.get("paginate_output", "false").lower() == "true",
|
|
"force_ocr": request.form.get("force_ocr", "false").lower() == "true",
|
|
"output_format": fmt,
|
|
"disable_image_extraction": request.form.get("disable_image_extraction", "false").lower() == "true",
|
|
"processors": request.form.get("processors"),
|
|
"config_json": request.form.get("config_json"),
|
|
"converter_cls": request.form.get("converter_cls"),
|
|
"use_llm": request.form.get("use_llm", "false").lower() == "true",
|
|
"llm_service": request.form.get("llm_service"),
|
|
"block_correction_prompt": request.form.get("block_correction_prompt"),
|
|
"redo_inline_math": request.form.get("redo_inline_math", "false").lower() == "true",
|
|
"strip_existing_ocr": request.form.get("strip_existing_ocr", "false").lower() == "true",
|
|
"debug": request.form.get("debug", "false").lower() == "true",
|
|
}
|
|
result = convert_file_bytes(raw, filename, **opts)
|
|
|
|
if not result["success"]:
|
|
return jsonify(result), 500
|
|
|
|
return jsonify({
|
|
"id": str(uuid.uuid4()),
|
|
"filename": filename,
|
|
"format": fmt,
|
|
"output": result["output"],
|
|
"success": True,
|
|
"images_b64": result.get("images_b64", {}),
|
|
"metadata": result.get("metadata", {}),
|
|
})
|
|
|
|
return jsonify({"error": "No file provided."}), 400
|
|
|
|
@app.route("/v1/files/convert", methods=["POST"])
|
|
def convert_files():
|
|
"""Convert a file via JSON body with base64."""
|
|
if "file" in request.files:
|
|
file = request.files["file"]
|
|
filename = file.filename or "file"
|
|
raw = file.read()
|
|
fmt = request.form.get("output_format", "markdown")
|
|
opts = {
|
|
"page_range": request.form.get("page_range"),
|
|
"paginate_output": request.form.get("paginate_output", False),
|
|
"force_ocr": request.form.get("force_ocr", False),
|
|
"output_format": fmt,
|
|
"disable_image_extraction": request.form.get("disable_image_extraction", False),
|
|
"processors": request.form.get("processors"),
|
|
"config_json": request.form.get("config_json"),
|
|
"converter_cls": request.form.get("converter_cls"),
|
|
"use_llm": request.form.get("use_llm", "false").lower() == "true",
|
|
"llm_service": request.form.get("llm_service"),
|
|
"block_correction_prompt": request.form.get("block_correction_prompt"),
|
|
"redo_inline_math": request.form.get("redo_inline_math", "false").lower() == "true",
|
|
"strip_existing_ocr": request.form.get("strip_existing_ocr", "false").lower() == "true",
|
|
"debug": request.form.get("debug", "false").lower() == "true",
|
|
}
|
|
result = convert_file_bytes(raw, filename, **opts)
|
|
elif request.is_json:
|
|
data = request.get_json()
|
|
if "file_b64" not in data or not data.get("filename"):
|
|
return jsonify({"error": "JSON body must include 'file_b64' and 'filename'"}), 400
|
|
|
|
raw = base64.b64decode(data["file_b64"])
|
|
fmt = data.get("output_format", "markdown")
|
|
opts = {
|
|
"page_range": data.get("page_range"),
|
|
"paginate_output": data.get("paginate_output", False),
|
|
"force_ocr": data.get("force_ocr", False),
|
|
"output_format": fmt,
|
|
"disable_image_extraction": data.get("disable_image_extraction", False),
|
|
"processors": data.get("processors"),
|
|
"config_json": data.get("config_json"),
|
|
"converter_cls": data.get("converter_cls"),
|
|
"use_llm": data.get("use_llm", False),
|
|
"llm_service": data.get("llm_service"),
|
|
"block_correction_prompt": data.get("block_correction_prompt"),
|
|
"redo_inline_math": data.get("redo_inline_math", False),
|
|
"strip_existing_ocr": data.get("strip_existing_ocr", False),
|
|
"debug": data.get("debug", False),
|
|
}
|
|
result = convert_file_bytes(raw, data["filename"], **opts)
|
|
fmt = fmt
|
|
else:
|
|
return jsonify({"error": "No file provided."}), 400
|
|
|
|
if not result["success"]:
|
|
return jsonify(result), 500
|
|
|
|
_filename = data.get("filename") if request.is_json else filename
|
|
return jsonify({
|
|
"filename": _filename,
|
|
"format": fmt,
|
|
"output": result["output"],
|
|
"images_b64": result.get("images_b64", {}),
|
|
"metadata": result.get("metadata", {}),
|
|
})
|
|
|
|
return app
|
|
|
|
|
|
app_instance = create_app()
|
|
|
|
if __name__ == "__main__":
|
|
_configure_env()
|
|
_marker_dict = create_model_dict() # pre-warm models
|
|
|
|
port = int(os.environ.get("PORT", "8000"))
|
|
host = os.environ.get("HOST", "0.0.0.0")
|
|
print("=" * 60)
|
|
print("marker-api starting")
|
|
print(f" OLLAMA_HOST = {OLLAMA_HOST}")
|
|
print(f" OCR_MODEL = {DEESEEK_OCR_MODEL}")
|
|
print(f" AMD_COMPUTE = {AMD_COMPUTE}")
|
|
print(f" TORCH_DEVICE = {TORCH_DEVICE or '(auto)'}")
|
|
print(f" MODEL_DTYPE = {MODEL_DTYPE}")
|
|
print(f" LISTENING ON = {host}:{port}")
|
|
print(f" FORMATS = {', '.join(SUPPORTED_DISPLAY)}")
|
|
print("=" * 60)
|
|
app_instance.run(host=host, port=port, debug=(os.environ.get("FLASK_DEBUG", "0") == "1"), threaded=True)
|