New version
This commit is contained in:
+16
-167
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user