diff --git a/Dockerfile b/Dockerfile index 1b364a6..96acdab 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,13 @@ -FROM python:3.12-slim +FROM python:3.11-slim WORKDIR /app -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt +RUN apt-get update && apt-get install -y \ + libxml2 libxslt1.1 libjpeg62-turbo && \ + pip install --no-cache-dir fastapi uvicorn python-pptx pydantic -COPY app ./app +COPY app /app -EXPOSE 8000 +EXPOSE 5000 -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "5000"] \ No newline at end of file diff --git a/app/main.py b/app/main.py index 0d275de..ab939e1 100644 --- a/app/main.py +++ b/app/main.py @@ -1,172 +1,21 @@ -from fastapi import FastAPI, HTTPExceptionfrom fastapi import FastAPI, HTTP(BaseModel): - filename: Optional[str] = "presentation" - title: str = "Présentation" - subtitle: Optional[str] = None - slides: List[SlideData] = Field(default_factory=list) +from fastapi import FastAPI +from fastapi.responses import FileResponse +from pydantic import BaseModel +from pptx_generator import generate_pptx +import uuid +import os +app = FastAPI() -# ---------------------------- -# Utilitaires -# ---------------------------- - -def sanitize_filename(name: str) -> str: - cleaned = re.sub(r"[^a-zA-Z0-9_-]", "_", name.strip()) - return cleaned or "presentation" - - -def add_title_slide(prs: Presentation, title: str, subtitle: Optional[str]) -> None: - slide_layout = prs.slide_layouts[6] # blank - slide = prs.slides.add_slide(slide_layout) - - # Fond visuel simple via zone blanche implicite - title_box = slide.shapes.add_textbox(Inches(0.6), Inches(1.0), Inches(11.5), Inches(1.0)) - tf = title_box.text_frame - p = tf.paragraphs[0] - p.text = title - p.font.size = Pt(24) - p.font.bold = True - p.alignment = PP_ALIGN.LEFT - - if subtitle: - subtitle_box = slide.shapes.add_textbox(Inches(0.6), Inches(1.8), Inches(11.0), Inches(0.8)) - tf2 = subtitle_box.text_frame - p2 = tf2.paragraphs[0] - p2.text = subtitle - p2.font.size = Pt(14) - p2.alignment = PP_ALIGN.LEFT - - -def add_content_slide(prs: Presentation, slide_data: SlideData) -> None: - slide_layout = prs.slide_layouts[6] # blank - slide = prs.slides.add_slide(slide_layout) - - # Titre - title_box = slide.shapes.add_textbox(Inches(0.5), Inches(0.4), Inches(12.0), Inches(0.8)) - tf_title = title_box.text_frame - p_title = tf_title.paragraphs[0] - p_title.text = slide_data.title - p_title.font.size = Pt(22) - p_title.font.bold = True - p_title.alignment = PP_ALIGN.LEFT - - current_top = 1.3 - - # Texte libre - if slide_data.text: - text_box = slide.shapes.add_textbox(Inches(0.7), Inches(current_top), Inches(11.0), Inches(1.2)) - tf_text = text_box.text_frame - tf_text.word_wrap = True - p_text = tf_text.paragraphs[0] - p_text.text = slide_data.text - p_text.font.size = Pt(16) - p_text.alignment = PP_ALIGN.LEFT - current_top += 1.0 - - # Liste à puces - if slide_data.bullets: - bullets_box = slide.shapes.add_textbox(Inches(1.0), Inches(current_top), Inches(10.8), Inches(4.0)) - tf_bullets = bullets_box.text_frame - tf_bullets.word_wrap = True - - first = True - for bullet in slide_data.bullets: - if first: - p = tf_bullets.paragraphs[0] - first = False - else: - p = tf_bullets.add_paragraph() - - p.text = bullet - p.level = 0 - p.font.size = Pt(18) - p.alignment = PP_ALIGN.LEFT - p.bullet = True - - -def generate_pptx(data: PresentationRequest) -> str: - prs = Presentation() - prs.slide_width = Inches(13.333) # 16:9 - prs.slide_height = Inches(7.5) - - # Métadonnées - prs.core_properties.title = data.title - prs.core_properties.subject = data.title - prs.core_properties.author = "M365 Copilot" - prs.core_properties.company = "Votre organisation" - - # Slide de titre - add_title_slide(prs, data.title, data.subtitle) - - # Slides de contenu - for slide in data.slides: - add_content_slide(prs, slide) - - filename = sanitize_filename(data.filename) - tmp_dir = tempfile.gettempdir() - full_path = os.path.join(tmp_dir, f"{filename}_{uuid.uuid4().hex}.pptx") - prs.save(full_path) - return full_path - - -# ---------------------------- -# Endpoints -# ---------------------------- - -@app.get("/health") -def health(): - return {"status": "ok"} +class Slide(BaseModel): + title: str + bullets: list[str] +class PresentationData(BaseModel): + slides: list[Slide] @app.post("/generate") -def generate_presentation(payload: PresentationRequest): - try: - output_path = generate_pptx(payload) - download_name = f"{sanitize_filename(payload.filename)}.pptx" - - return FileResponse( - path=output_path, - media_type="application/vnd.openxmlformats-officedocument.presentationml.presentation", - filename=download_name - ) - except Exception as e: - raise HTTPException(status_code=500, detail=f"Erreur lors de la génération du PPTX : {str(e)}") - - -@app.get("/") -def root(): - return JSONResponse({ - "service": "pptx-service", - "status": "running", - "endpoints": { - "health": "/health", - "generate": "/generate" - } - }) -from fastapi.responses import FileResponse, JSONResponse -from pydantic import BaseModel, Field -from typing import List, Optional -from pptx import Presentation -from pptx.util import Inches, Pt -from pptx.enum.text import PP_ALIGN -import tempfile -import os -import re -import uuid - -app = FastAPI( - title="PPTX JSON Service", - description="Génère un fichier PowerPoint (.pptx) à partir d'un JSON", - version="1.0.0" -) - - -# ---------------------------- -# Modèles de données -# ---------------------------- - -class SlideData(BaseModel): - title: str - text: Optional[str] = None - bullets: Optional[List[str]] = Field(default_factory=list) - - +def generate(data: PresentationData): + output_file = f"/tmp/{uuid.uuid4()}.pptx" + generate_pptx(data.slides, output_file) + return FileResponse(output_file, media_type="application/vnd.openxmlformats-officedocument.presentationml.presentation") diff --git a/app/pptx_generator.py b/app/pptx_generator.py new file mode 100644 index 0000000..3cf68c5 --- /dev/null +++ b/app/pptx_generator.py @@ -0,0 +1,21 @@ +from pptx import Presentation + +def generate_pptx(slides, output_file): + prs = Presentation("template.pptx") + + for slide_data in slides: + slide_layout = prs.slide_layouts[1] # Title + Content + slide = prs.slides.add_slide(slide_layout) + + title = slide.shapes.title + body = slide.placeholders[1].text_frame + + title.text = slide_data.title + body.clear() + + for bullet in slide_data.bullets: + p = body.add_paragraph() + p.text = bullet + p.level = 0 + + prs.save(output_file) diff --git a/app/template.pptx b/app/template.pptx new file mode 100644 index 0000000..3e9f084 Binary files /dev/null and b/app/template.pptx differ