Spaces:
Running
Running
Update content_utils.py
Browse files- content_utils.py +313 -262
content_utils.py
CHANGED
@@ -1510,280 +1510,331 @@ def create_pptx_file(results: List[Dict], topic: str, template_name: str,
|
|
1510 |
theme_name: str = "Minimal Light", design_themes: Dict = None) -> str:
|
1511 |
"""Create PPTX file with speaker notes"""
|
1512 |
print(f"[PPTX] Creating file... Theme: {theme_name}")
|
|
|
1513 |
|
1514 |
-
|
1515 |
-
|
1516 |
-
|
1517 |
-
|
1518 |
-
|
1519 |
-
# Get selected theme
|
1520 |
-
theme = design_themes.get(theme_name, design_themes["Minimal Light"]) if design_themes else {}
|
1521 |
-
|
1522 |
-
# Add slides
|
1523 |
-
for i, result in enumerate(results):
|
1524 |
-
if not result.get("success", False):
|
1525 |
-
continue
|
1526 |
-
|
1527 |
-
slide_data = result.get("slide_data", {})
|
1528 |
|
1529 |
-
#
|
1530 |
-
|
1531 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1532 |
|
1533 |
-
|
1534 |
-
|
1535 |
-
# Add background image
|
1536 |
-
if slide_data.get('image'):
|
1537 |
-
try:
|
1538 |
-
img_buffer = BytesIO()
|
1539 |
-
slide_data['image'].save(img_buffer, format='PNG')
|
1540 |
-
img_buffer.seek(0)
|
1541 |
-
|
1542 |
-
# Full screen background image
|
1543 |
-
pic = slide.shapes.add_picture(
|
1544 |
-
img_buffer,
|
1545 |
-
0, 0,
|
1546 |
-
width=prs.slide_width,
|
1547 |
-
height=prs.slide_height
|
1548 |
-
)
|
1549 |
-
# Send to back
|
1550 |
-
slide.shapes._spTree.remove(pic._element)
|
1551 |
-
slide.shapes._spTree.insert(2, pic._element)
|
1552 |
-
except Exception as e:
|
1553 |
-
print(f"[PPTX] Failed to add title image: {str(e)}")
|
1554 |
-
|
1555 |
-
# Title background box (semi-transparent)
|
1556 |
-
title_bg = slide.shapes.add_shape(
|
1557 |
-
MSO_SHAPE.ROUNDED_RECTANGLE,
|
1558 |
-
Inches(2), Inches(2.8),
|
1559 |
-
Inches(12), Inches(3.2)
|
1560 |
-
)
|
1561 |
-
title_bg.fill.solid()
|
1562 |
-
title_bg.fill.fore_color.rgb = RGBColor(255, 255, 255)
|
1563 |
-
title_bg.fill.transparency = 0.8
|
1564 |
-
title_bg.line.fill.background()
|
1565 |
-
|
1566 |
-
# Shadow effect
|
1567 |
-
shadow = title_bg.shadow
|
1568 |
-
shadow.visible = True
|
1569 |
-
shadow.distance = Pt(6)
|
1570 |
-
shadow.size = 100
|
1571 |
-
shadow.blur_radius = Pt(12)
|
1572 |
-
shadow.transparency = 0.8
|
1573 |
-
shadow.angle = 45
|
1574 |
-
|
1575 |
-
# Title text
|
1576 |
-
title_box = slide.shapes.add_textbox(
|
1577 |
-
Inches(2), Inches(3.2),
|
1578 |
-
Inches(12), Inches(1.5)
|
1579 |
-
)
|
1580 |
-
title_frame = title_box.text_frame
|
1581 |
-
title_frame.text = topic
|
1582 |
-
title_para = title_frame.paragraphs[0]
|
1583 |
-
title_para.font.size = Pt(48)
|
1584 |
-
title_para.font.bold = True
|
1585 |
-
title_para.font.color.rgb = RGBColor(0, 0, 0)
|
1586 |
-
title_para.alignment = PP_ALIGN.CENTER
|
1587 |
-
|
1588 |
-
# Subtitle
|
1589 |
-
subtitle_box = slide.shapes.add_textbox(
|
1590 |
-
Inches(2), Inches(4.3),
|
1591 |
-
Inches(12), Inches(1.0)
|
1592 |
-
)
|
1593 |
-
subtitle_frame = subtitle_box.text_frame
|
1594 |
-
subtitle_frame.text = slide_data.get('subtitle', f'{template_name} - AI Presentation')
|
1595 |
-
subtitle_para = subtitle_frame.paragraphs[0]
|
1596 |
-
subtitle_para.font.size = Pt(28)
|
1597 |
-
subtitle_para.font.color.rgb = RGBColor(33, 37, 41)
|
1598 |
-
subtitle_para.alignment = PP_ALIGN.CENTER
|
1599 |
-
|
1600 |
-
# Thank You slide
|
1601 |
-
elif slide_data.get('title') == 'Thank You':
|
1602 |
-
# Add background image
|
1603 |
-
if slide_data.get('image'):
|
1604 |
-
try:
|
1605 |
-
img_buffer = BytesIO()
|
1606 |
-
slide_data['image'].save(img_buffer, format='PNG')
|
1607 |
-
img_buffer.seek(0)
|
1608 |
-
|
1609 |
-
# Full screen background image
|
1610 |
-
pic = slide.shapes.add_picture(
|
1611 |
-
img_buffer,
|
1612 |
-
0, 0,
|
1613 |
-
width=prs.slide_width,
|
1614 |
-
height=prs.slide_height
|
1615 |
-
)
|
1616 |
-
# Send to back
|
1617 |
-
slide.shapes._spTree.remove(pic._element)
|
1618 |
-
slide.shapes._spTree.insert(2, pic._element)
|
1619 |
-
except Exception as e:
|
1620 |
-
print(f"[PPTX] Failed to add Thank You image: {str(e)}")
|
1621 |
-
|
1622 |
-
# Thank You background box
|
1623 |
-
thanks_bg = slide.shapes.add_shape(
|
1624 |
-
MSO_SHAPE.ROUNDED_RECTANGLE,
|
1625 |
-
Inches(2), Inches(3.5),
|
1626 |
-
Inches(12), Inches(2.5)
|
1627 |
-
)
|
1628 |
-
thanks_bg.fill.solid()
|
1629 |
-
thanks_bg.fill.fore_color.rgb = RGBColor(255, 255, 255)
|
1630 |
-
thanks_bg.fill.transparency = 0.8
|
1631 |
-
thanks_bg.line.fill.background()
|
1632 |
-
|
1633 |
-
# Shadow effect
|
1634 |
-
shadow = thanks_bg.shadow
|
1635 |
-
shadow.visible = True
|
1636 |
-
shadow.distance = Pt(6)
|
1637 |
-
shadow.size = 100
|
1638 |
-
shadow.blur_radius = Pt(12)
|
1639 |
-
shadow.transparency = 0.8
|
1640 |
-
shadow.angle = 45
|
1641 |
-
|
1642 |
-
# Thank You text (conclusion phrase)
|
1643 |
-
thanks_box = slide.shapes.add_textbox(
|
1644 |
-
Inches(2), Inches(4),
|
1645 |
-
Inches(12), Inches(1.5)
|
1646 |
-
)
|
1647 |
-
thanks_frame = thanks_box.text_frame
|
1648 |
-
thanks_frame.text = slide_data.get('subtitle', 'Thank You')
|
1649 |
-
thanks_para = thanks_frame.paragraphs[0]
|
1650 |
-
thanks_para.font.size = Pt(42)
|
1651 |
-
thanks_para.font.bold = True
|
1652 |
-
thanks_para.font.color.rgb = RGBColor(0, 0, 0)
|
1653 |
-
thanks_para.alignment = PP_ALIGN.CENTER
|
1654 |
-
|
1655 |
-
# Regular slides
|
1656 |
else:
|
1657 |
-
|
1658 |
-
|
1659 |
-
|
1660 |
-
|
1661 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1662 |
|
1663 |
-
#
|
1664 |
-
|
1665 |
-
|
1666 |
-
|
1667 |
-
Inches(15.4), Inches(1.0)
|
1668 |
-
)
|
1669 |
-
title_box_bg.fill.solid()
|
1670 |
-
title_box_bg.fill.fore_color.rgb = theme.get("box_fill", RGBColor(255, 255, 255))
|
1671 |
-
title_box_bg.fill.transparency = 1 - theme.get("box_opacity", 0.95)
|
1672 |
|
1673 |
-
#
|
1674 |
-
if
|
1675 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1676 |
shadow.visible = True
|
1677 |
-
shadow.distance = Pt(
|
1678 |
shadow.size = 100
|
1679 |
-
shadow.blur_radius = Pt(
|
1680 |
-
shadow.transparency = 0.
|
1681 |
shadow.angle = 45
|
1682 |
-
|
1683 |
-
|
1684 |
-
|
1685 |
-
|
1686 |
-
|
1687 |
-
|
1688 |
-
|
1689 |
-
|
1690 |
-
|
1691 |
-
|
1692 |
-
|
1693 |
-
|
1694 |
-
|
1695 |
-
|
1696 |
-
|
1697 |
-
|
1698 |
-
|
1699 |
-
|
1700 |
-
|
1701 |
-
|
1702 |
-
|
1703 |
-
|
1704 |
-
|
1705 |
-
|
1706 |
-
|
1707 |
-
|
1708 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1709 |
shadow.visible = True
|
1710 |
-
shadow.distance = Pt(
|
1711 |
shadow.size = 100
|
1712 |
-
shadow.blur_radius = Pt(
|
1713 |
-
shadow.transparency = 0.
|
1714 |
shadow.angle = 45
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1715 |
|
1716 |
-
|
1717 |
-
|
1718 |
-
|
1719 |
-
|
1720 |
-
|
1721 |
-
|
1722 |
-
|
1723 |
-
|
1724 |
-
|
1725 |
-
|
1726 |
-
|
1727 |
-
|
1728 |
-
|
1729 |
-
|
1730 |
-
|
1731 |
-
|
1732 |
-
|
1733 |
-
|
1734 |
-
|
1735 |
-
|
1736 |
-
|
1737 |
-
|
1738 |
-
|
1739 |
-
|
1740 |
-
p.text = clean_text
|
1741 |
-
p.font.size = Pt(16)
|
1742 |
-
p.font.color.rgb = theme.get("text_color", RGBColor(73, 80, 87))
|
1743 |
-
p.level = 0
|
1744 |
-
p.space_after = Pt(12)
|
1745 |
-
p.line_spacing = 1.5
|
1746 |
-
p.left_indent = Pt(0)
|
1747 |
|
1748 |
-
|
1749 |
-
|
1750 |
-
|
1751 |
-
|
1752 |
-
|
1753 |
-
|
1754 |
-
|
1755 |
-
|
1756 |
-
|
1757 |
-
Inches(8.5), Inches(1.6),
|
1758 |
-
width=Inches(6.8), height=Inches(6.4)
|
1759 |
-
)
|
1760 |
-
|
1761 |
-
pic.line.fill.background()
|
1762 |
-
|
1763 |
-
except Exception as e:
|
1764 |
-
print(f"[PPTX] Failed to add image: {str(e)}")
|
1765 |
|
1766 |
-
|
1767 |
-
|
1768 |
-
|
1769 |
-
|
1770 |
-
|
1771 |
-
page_frame = page_num.text_frame
|
1772 |
-
page_frame.text = str(i + 1)
|
1773 |
-
page_para = page_frame.paragraphs[0]
|
1774 |
-
page_para.font.size = Pt(12)
|
1775 |
-
page_para.font.color.rgb = theme.get("text_color", RGBColor(73, 80, 87))
|
1776 |
-
page_para.alignment = PP_ALIGN.RIGHT
|
1777 |
-
|
1778 |
-
# Add speaker notes
|
1779 |
-
notes_slide = slide.notes_slide
|
1780 |
-
notes_slide.notes_text_frame.text = slide_data.get('speaker_notes', '')
|
1781 |
-
|
1782 |
-
# Save file
|
1783 |
-
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
1784 |
-
filename = f"presentation_{timestamp}.pptx"
|
1785 |
-
filepath = os.path.join("/tmp", filename)
|
1786 |
-
prs.save(filepath)
|
1787 |
-
|
1788 |
-
print(f"[PPTX] File created: {filename}")
|
1789 |
-
return filepath
|
|
|
1510 |
theme_name: str = "Minimal Light", design_themes: Dict = None) -> str:
|
1511 |
"""Create PPTX file with speaker notes"""
|
1512 |
print(f"[PPTX] Creating file... Theme: {theme_name}")
|
1513 |
+
print(f"[PPTX] Processing {len(results)} slides")
|
1514 |
|
1515 |
+
try:
|
1516 |
+
# Create presentation (16:9 ratio)
|
1517 |
+
prs = Presentation()
|
1518 |
+
prs.slide_width = Inches(16)
|
1519 |
+
prs.slide_height = Inches(9)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1520 |
|
1521 |
+
# Get selected theme with proper defaults
|
1522 |
+
default_theme = {
|
1523 |
+
"background": RGBColor(250, 250, 252),
|
1524 |
+
"title_color": RGBColor(33, 37, 41),
|
1525 |
+
"subtitle_color": RGBColor(52, 58, 64),
|
1526 |
+
"text_color": RGBColor(73, 80, 87),
|
1527 |
+
"accent_color": RGBColor(0, 123, 255),
|
1528 |
+
"box_fill": RGBColor(255, 255, 255),
|
1529 |
+
"box_opacity": 0.95,
|
1530 |
+
"shadow": True,
|
1531 |
+
"gradient": False
|
1532 |
+
}
|
1533 |
|
1534 |
+
if design_themes and theme_name in design_themes:
|
1535 |
+
theme = design_themes[theme_name]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1536 |
else:
|
1537 |
+
theme = default_theme
|
1538 |
+
print(f"[PPTX] Using default theme as {theme_name} not found")
|
1539 |
+
|
1540 |
+
# Add slides
|
1541 |
+
slide_count = 0
|
1542 |
+
for i, result in enumerate(results):
|
1543 |
+
if not result.get("success", False):
|
1544 |
+
print(f"[PPTX] Skipping slide {i+1} - not successful")
|
1545 |
+
continue
|
1546 |
+
|
1547 |
+
slide_data = result.get("slide_data", {})
|
1548 |
+
print(f"[PPTX] Adding slide {i+1}: {slide_data.get('title', 'Unknown')}")
|
1549 |
|
1550 |
+
# Use blank layout
|
1551 |
+
blank_layout = prs.slide_layouts[6]
|
1552 |
+
slide = prs.slides.add_slide(blank_layout)
|
1553 |
+
slide_count += 1
|
|
|
|
|
|
|
|
|
|
|
1554 |
|
1555 |
+
# Title slide
|
1556 |
+
if slide_data.get('title') in ['Cover', '표지']:
|
1557 |
+
# Add background image
|
1558 |
+
if slide_data.get('image'):
|
1559 |
+
try:
|
1560 |
+
img_buffer = BytesIO()
|
1561 |
+
slide_data['image'].save(img_buffer, format='PNG')
|
1562 |
+
img_buffer.seek(0)
|
1563 |
+
|
1564 |
+
# Full screen background image
|
1565 |
+
pic = slide.shapes.add_picture(
|
1566 |
+
img_buffer,
|
1567 |
+
0, 0,
|
1568 |
+
width=prs.slide_width,
|
1569 |
+
height=prs.slide_height
|
1570 |
+
)
|
1571 |
+
# Send to back
|
1572 |
+
slide.shapes._spTree.remove(pic._element)
|
1573 |
+
slide.shapes._spTree.insert(2, pic._element)
|
1574 |
+
print(f"[PPTX] Added title background image")
|
1575 |
+
except Exception as e:
|
1576 |
+
print(f"[PPTX] Failed to add title image: {str(e)}")
|
1577 |
+
|
1578 |
+
# Title background box (semi-transparent)
|
1579 |
+
title_bg = slide.shapes.add_shape(
|
1580 |
+
MSO_SHAPE.ROUNDED_RECTANGLE,
|
1581 |
+
Inches(2), Inches(2.8),
|
1582 |
+
Inches(12), Inches(3.2)
|
1583 |
+
)
|
1584 |
+
title_bg.fill.solid()
|
1585 |
+
title_bg.fill.fore_color.rgb = RGBColor(255, 255, 255)
|
1586 |
+
title_bg.fill.transparency = 0.8
|
1587 |
+
title_bg.line.fill.background()
|
1588 |
+
|
1589 |
+
# Shadow effect
|
1590 |
+
shadow = title_bg.shadow
|
1591 |
shadow.visible = True
|
1592 |
+
shadow.distance = Pt(6)
|
1593 |
shadow.size = 100
|
1594 |
+
shadow.blur_radius = Pt(12)
|
1595 |
+
shadow.transparency = 0.8
|
1596 |
shadow.angle = 45
|
1597 |
+
|
1598 |
+
# Title text
|
1599 |
+
title_box = slide.shapes.add_textbox(
|
1600 |
+
Inches(2), Inches(3.2),
|
1601 |
+
Inches(12), Inches(1.5)
|
1602 |
+
)
|
1603 |
+
title_frame = title_box.text_frame
|
1604 |
+
title_frame.text = topic
|
1605 |
+
title_para = title_frame.paragraphs[0]
|
1606 |
+
title_para.font.size = Pt(48)
|
1607 |
+
title_para.font.bold = True
|
1608 |
+
title_para.font.color.rgb = RGBColor(0, 0, 0)
|
1609 |
+
title_para.alignment = PP_ALIGN.CENTER
|
1610 |
+
|
1611 |
+
# Subtitle
|
1612 |
+
subtitle_box = slide.shapes.add_textbox(
|
1613 |
+
Inches(2), Inches(4.3),
|
1614 |
+
Inches(12), Inches(1.0)
|
1615 |
+
)
|
1616 |
+
subtitle_frame = subtitle_box.text_frame
|
1617 |
+
subtitle_frame.text = slide_data.get('subtitle', f'{template_name} - AI Presentation')
|
1618 |
+
subtitle_para = subtitle_frame.paragraphs[0]
|
1619 |
+
subtitle_para.font.size = Pt(28)
|
1620 |
+
subtitle_para.font.color.rgb = RGBColor(33, 37, 41)
|
1621 |
+
subtitle_para.alignment = PP_ALIGN.CENTER
|
1622 |
+
|
1623 |
+
# Thank You slide
|
1624 |
+
elif slide_data.get('title') == 'Thank You':
|
1625 |
+
# Add background image
|
1626 |
+
if slide_data.get('image'):
|
1627 |
+
try:
|
1628 |
+
img_buffer = BytesIO()
|
1629 |
+
slide_data['image'].save(img_buffer, format='PNG')
|
1630 |
+
img_buffer.seek(0)
|
1631 |
+
|
1632 |
+
# Full screen background image
|
1633 |
+
pic = slide.shapes.add_picture(
|
1634 |
+
img_buffer,
|
1635 |
+
0, 0,
|
1636 |
+
width=prs.slide_width,
|
1637 |
+
height=prs.slide_height
|
1638 |
+
)
|
1639 |
+
# Send to back
|
1640 |
+
slide.shapes._spTree.remove(pic._element)
|
1641 |
+
slide.shapes._spTree.insert(2, pic._element)
|
1642 |
+
except Exception as e:
|
1643 |
+
print(f"[PPTX] Failed to add Thank You image: {str(e)}")
|
1644 |
+
|
1645 |
+
# Thank You background box
|
1646 |
+
thanks_bg = slide.shapes.add_shape(
|
1647 |
+
MSO_SHAPE.ROUNDED_RECTANGLE,
|
1648 |
+
Inches(2), Inches(3.5),
|
1649 |
+
Inches(12), Inches(2.5)
|
1650 |
+
)
|
1651 |
+
thanks_bg.fill.solid()
|
1652 |
+
thanks_bg.fill.fore_color.rgb = RGBColor(255, 255, 255)
|
1653 |
+
thanks_bg.fill.transparency = 0.8
|
1654 |
+
thanks_bg.line.fill.background()
|
1655 |
+
|
1656 |
+
# Shadow effect
|
1657 |
+
shadow = thanks_bg.shadow
|
1658 |
shadow.visible = True
|
1659 |
+
shadow.distance = Pt(6)
|
1660 |
shadow.size = 100
|
1661 |
+
shadow.blur_radius = Pt(12)
|
1662 |
+
shadow.transparency = 0.8
|
1663 |
shadow.angle = 45
|
1664 |
+
|
1665 |
+
# Thank You text (conclusion phrase)
|
1666 |
+
thanks_box = slide.shapes.add_textbox(
|
1667 |
+
Inches(2), Inches(4),
|
1668 |
+
Inches(12), Inches(1.5)
|
1669 |
+
)
|
1670 |
+
thanks_frame = thanks_box.text_frame
|
1671 |
+
thanks_frame.text = slide_data.get('subtitle', 'Thank You')
|
1672 |
+
thanks_para = thanks_frame.paragraphs[0]
|
1673 |
+
thanks_para.font.size = Pt(42)
|
1674 |
+
thanks_para.font.bold = True
|
1675 |
+
thanks_para.font.color.rgb = RGBColor(0, 0, 0)
|
1676 |
+
thanks_para.alignment = PP_ALIGN.CENTER
|
1677 |
+
|
1678 |
+
# Regular slides
|
1679 |
+
else:
|
1680 |
+
# Background color
|
1681 |
+
background = slide.background
|
1682 |
+
fill = background.fill
|
1683 |
+
fill.solid()
|
1684 |
+
fill.fore_color.rgb = theme.get("background", RGBColor(250, 250, 252))
|
1685 |
+
|
1686 |
+
# Slide title background box
|
1687 |
+
title_box_bg = slide.shapes.add_shape(
|
1688 |
+
MSO_SHAPE.ROUNDED_RECTANGLE,
|
1689 |
+
Inches(0.3), Inches(0.2),
|
1690 |
+
Inches(15.4), Inches(1.0)
|
1691 |
+
)
|
1692 |
+
title_box_bg.fill.solid()
|
1693 |
+
title_box_bg.fill.fore_color.rgb = theme.get("box_fill", RGBColor(255, 255, 255))
|
1694 |
+
title_box_bg.fill.transparency = 1 - theme.get("box_opacity", 0.95)
|
1695 |
+
|
1696 |
+
# Shadow effect
|
1697 |
+
if theme.get("shadow", True):
|
1698 |
+
shadow = title_box_bg.shadow
|
1699 |
+
shadow.visible = True
|
1700 |
+
shadow.distance = Pt(4)
|
1701 |
+
shadow.size = 100
|
1702 |
+
shadow.blur_radius = Pt(8)
|
1703 |
+
shadow.transparency = 0.75
|
1704 |
+
shadow.angle = 45
|
1705 |
+
|
1706 |
+
title_box_bg.line.fill.background()
|
1707 |
+
|
1708 |
+
# Slide title
|
1709 |
+
title_box = slide.shapes.add_textbox(
|
1710 |
+
Inches(0.5), Inches(0.3),
|
1711 |
+
Inches(15), Inches(0.8)
|
1712 |
+
)
|
1713 |
+
title_frame = title_box.text_frame
|
1714 |
+
title_frame.text = f"{slide_data.get('title', '')}"
|
1715 |
+
title_para = title_frame.paragraphs[0]
|
1716 |
+
title_para.font.size = Pt(28)
|
1717 |
+
title_para.font.bold = True
|
1718 |
+
title_para.font.color.rgb = theme.get("title_color", RGBColor(33, 37, 41))
|
1719 |
+
|
1720 |
+
# Left text area background box
|
1721 |
+
text_box_bg = slide.shapes.add_shape(
|
1722 |
+
MSO_SHAPE.ROUNDED_RECTANGLE,
|
1723 |
+
Inches(0.3), Inches(1.4),
|
1724 |
+
Inches(7.8), Inches(6.8)
|
1725 |
+
)
|
1726 |
+
text_box_bg.fill.solid()
|
1727 |
+
text_box_bg.fill.fore_color.rgb = theme.get("box_fill", RGBColor(255, 255, 255))
|
1728 |
+
text_box_bg.fill.transparency = 1 - theme.get("box_opacity", 0.95)
|
1729 |
+
|
1730 |
+
if theme.get("shadow", True):
|
1731 |
+
shadow = text_box_bg.shadow
|
1732 |
+
shadow.visible = True
|
1733 |
+
shadow.distance = Pt(5)
|
1734 |
+
shadow.size = 100
|
1735 |
+
shadow.blur_radius = Pt(10)
|
1736 |
+
shadow.transparency = 0.7
|
1737 |
+
shadow.angle = 45
|
1738 |
+
|
1739 |
+
text_box_bg.line.fill.background()
|
1740 |
+
|
1741 |
+
# Left text area
|
1742 |
+
text_box = slide.shapes.add_textbox(
|
1743 |
+
Inches(0.8), Inches(1.8),
|
1744 |
+
Inches(7.0), Inches(6.0)
|
1745 |
+
)
|
1746 |
+
text_frame = text_box.text_frame
|
1747 |
+
text_frame.word_wrap = True
|
1748 |
+
|
1749 |
+
# Subtitle
|
1750 |
+
subtitle_para = text_frame.paragraphs[0]
|
1751 |
+
subtitle_para.text = slide_data.get('subtitle', '')
|
1752 |
+
subtitle_para.font.size = Pt(20)
|
1753 |
+
subtitle_para.font.bold = True
|
1754 |
+
subtitle_para.font.color.rgb = theme.get("subtitle_color", RGBColor(52, 58, 64))
|
1755 |
+
subtitle_para.space_after = Pt(20)
|
1756 |
+
|
1757 |
+
# Bullet points
|
1758 |
+
bullet_points = slide_data.get('bullet_points', [])
|
1759 |
+
for point in bullet_points:
|
1760 |
+
p = text_frame.add_paragraph()
|
1761 |
+
# Remove • and add text only (keep emojis)
|
1762 |
+
clean_text = point.replace('•', '').strip()
|
1763 |
+
p.text = clean_text
|
1764 |
+
p.font.size = Pt(16)
|
1765 |
+
p.font.color.rgb = theme.get("text_color", RGBColor(73, 80, 87))
|
1766 |
+
p.level = 0
|
1767 |
+
p.space_after = Pt(12)
|
1768 |
+
p.line_spacing = 1.5
|
1769 |
+
p.left_indent = Pt(0)
|
1770 |
+
|
1771 |
+
# Right image
|
1772 |
+
if slide_data.get('image'):
|
1773 |
+
try:
|
1774 |
+
img_buffer = BytesIO()
|
1775 |
+
slide_data['image'].save(img_buffer, format='PNG')
|
1776 |
+
img_buffer.seek(0)
|
1777 |
+
|
1778 |
+
pic = slide.shapes.add_picture(
|
1779 |
+
img_buffer,
|
1780 |
+
Inches(8.5), Inches(1.6),
|
1781 |
+
width=Inches(6.8), height=Inches(6.4)
|
1782 |
+
)
|
1783 |
+
|
1784 |
+
pic.line.fill.background()
|
1785 |
+
|
1786 |
+
except Exception as e:
|
1787 |
+
print(f"[PPTX] Failed to add image: {str(e)}")
|
1788 |
+
|
1789 |
+
# Page number
|
1790 |
+
page_num = slide.shapes.add_textbox(
|
1791 |
+
Inches(15), Inches(8.5),
|
1792 |
+
Inches(1), Inches(0.5)
|
1793 |
+
)
|
1794 |
+
page_frame = page_num.text_frame
|
1795 |
+
page_frame.text = str(i + 1)
|
1796 |
+
page_para = page_frame.paragraphs[0]
|
1797 |
+
page_para.font.size = Pt(12)
|
1798 |
+
page_para.font.color.rgb = theme.get("text_color", RGBColor(73, 80, 87))
|
1799 |
+
page_para.alignment = PP_ALIGN.RIGHT
|
1800 |
|
1801 |
+
# Add speaker notes
|
1802 |
+
notes_slide = slide.notes_slide
|
1803 |
+
notes_slide.notes_text_frame.text = slide_data.get('speaker_notes', '')
|
1804 |
+
print(f"[PPTX] Added speaker notes for slide {i+1}")
|
1805 |
+
|
1806 |
+
print(f"[PPTX] Total slides added: {slide_count}")
|
1807 |
+
|
1808 |
+
# Save file using tempfile for better compatibility
|
1809 |
+
import tempfile
|
1810 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
1811 |
+
filename = f"presentation_{timestamp}.pptx"
|
1812 |
+
|
1813 |
+
# Create a temporary file
|
1814 |
+
with tempfile.NamedTemporaryFile(mode='wb', suffix='.pptx', delete=False) as tmp_file:
|
1815 |
+
prs.save(tmp_file)
|
1816 |
+
temp_path = tmp_file.name
|
1817 |
+
print(f"[PPTX] File saved to temporary path: {temp_path}")
|
1818 |
+
|
1819 |
+
# Verify file exists and has content
|
1820 |
+
if os.path.exists(temp_path):
|
1821 |
+
file_size = os.path.getsize(temp_path)
|
1822 |
+
print(f"[PPTX] File created successfully: {filename}")
|
1823 |
+
print(f"[PPTX] File size: {file_size} bytes")
|
1824 |
+
print(f"[PPTX] File path: {temp_path}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1825 |
|
1826 |
+
if file_size > 0:
|
1827 |
+
return temp_path
|
1828 |
+
else:
|
1829 |
+
print(f"[PPTX] ERROR: File created but has 0 bytes")
|
1830 |
+
os.remove(temp_path)
|
1831 |
+
return None
|
1832 |
+
else:
|
1833 |
+
print(f"[PPTX] ERROR: File was not created at {temp_path}")
|
1834 |
+
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1835 |
|
1836 |
+
except Exception as e:
|
1837 |
+
print(f"[PPTX] CRITICAL ERROR during file creation: {str(e)}")
|
1838 |
+
import traceback
|
1839 |
+
traceback.print_exc()
|
1840 |
+
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|