Spaces:
Running
Running
| import os | |
| import shutil | |
| import subprocess | |
| import tempfile | |
| import threading | |
| from pathlib import Path | |
| from typing import Iterable | |
| import gradio as gr | |
| PORT = int(os.environ.get("PORT", "7860")) | |
| OUTPUT_TTL_SECONDS = 300 | |
| def _copy_inputs(paths: Iterable[str], destination: Path) -> None: | |
| for raw_path in paths: | |
| if not raw_path: | |
| continue | |
| source_path = Path(raw_path) | |
| shutil.copy(source_path, destination / source_path.name) | |
| def _run_command(command: list[str], cwd: Path, timeout: int) -> subprocess.CompletedProcess[str]: | |
| return subprocess.run( | |
| command, | |
| cwd=cwd, | |
| capture_output=True, | |
| text=True, | |
| check=True, | |
| timeout=timeout, | |
| ) | |
| def _schedule_cleanup(file_path: Path) -> None: | |
| def _cleanup() -> None: | |
| try: | |
| file_path.unlink(missing_ok=True) | |
| file_path.parent.rmdir() | |
| except OSError: | |
| pass | |
| threading.Timer(OUTPUT_TTL_SECONDS, _cleanup).start() | |
| def compile_latex( | |
| tex_file_path: str, | |
| attachment_paths: list[str] | None, | |
| double_render: bool, | |
| ): | |
| if not tex_file_path: | |
| raise gr.Error("Upload a .tex file to compile.") | |
| source_path = Path(tex_file_path) | |
| if source_path.suffix.lower() != ".tex": | |
| raise gr.Error("Only .tex files are supported.") | |
| with tempfile.TemporaryDirectory() as temp_dir: | |
| temp_path = Path(temp_dir) | |
| working_tex = temp_path / source_path.name | |
| _copy_inputs([tex_file_path], temp_path) | |
| _copy_inputs(attachment_paths or [], temp_path) | |
| latexmk_command = [ | |
| "latexmk", | |
| "-pdf", | |
| "-interaction=nonstopmode", | |
| "-halt-on-error", | |
| working_tex.name, | |
| ] | |
| pdflatex_command = [ | |
| "pdflatex", | |
| "-interaction=nonstopmode", | |
| "-halt-on-error", | |
| "-recorder", | |
| working_tex.name, | |
| ] | |
| try: | |
| result = _run_command(latexmk_command, temp_path, 120) | |
| if double_render: | |
| extra_result = _run_command(pdflatex_command, temp_path, 120) | |
| result = subprocess.CompletedProcess( | |
| args=pdflatex_command, | |
| returncode=extra_result.returncode, | |
| stdout=(result.stdout or "") + "\n\n" + (extra_result.stdout or ""), | |
| stderr=(result.stderr or "") + "\n\n" + (extra_result.stderr or ""), | |
| ) | |
| except subprocess.CalledProcessError as exc: | |
| log_output = (exc.stdout or "") + "\n" + (exc.stderr or "") | |
| raise gr.Error( | |
| "LaTeX compilation failed.\n\n" | |
| f"{log_output.strip() or 'No compiler output was captured.'}" | |
| ) from exc | |
| except subprocess.TimeoutExpired as exc: | |
| raise gr.Error("LaTeX compilation timed out after 120 seconds.") from exc | |
| pdf_path = working_tex.with_suffix(".pdf") | |
| if not pdf_path.exists(): | |
| compiler_output = (result.stdout or "") + "\n" + (result.stderr or "") | |
| raise gr.Error( | |
| "Compilation finished without producing a PDF.\n\n" | |
| f"{compiler_output.strip() or 'No compiler output was captured.'}" | |
| ) | |
| final_output = Path(tempfile.mkdtemp()) / f"rendered-{source_path.stem}.pdf" | |
| shutil.copy(pdf_path, final_output) | |
| _schedule_cleanup(final_output) | |
| return str(final_output) | |
| with gr.Blocks(title="Latexee") as demo: | |
| gr.Markdown( | |
| """ | |
| # Latexee | |
| Upload a main `.tex` file plus any supporting assets like `.jpg`, `.png`, | |
| `.bib`, `.sty`, or extra TeX files, and get back the rendered PDF. | |
| """ | |
| ) | |
| with gr.Row(): | |
| tex_input = gr.File( | |
| label="LaTeX source", | |
| file_count="single", | |
| file_types=[".tex"], | |
| type="filepath", | |
| ) | |
| attachment_input = gr.File( | |
| label="Supporting files", | |
| file_count="multiple", | |
| type="filepath", | |
| ) | |
| double_render_input = gr.Checkbox( | |
| label="Double render", | |
| info="Run one extra pdflatex pass after the normal compile.", | |
| value=False, | |
| ) | |
| pdf_output = gr.File(label="Rendered PDF") | |
| compile_button = gr.Button("Render PDF", variant="primary") | |
| compile_button.click( | |
| fn=compile_latex, | |
| inputs=[tex_input, attachment_input, double_render_input], | |
| outputs=pdf_output, | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch(server_name="0.0.0.0", server_port=PORT) | |