Spaces:
Runtime error
Runtime error
| import streamlit as st | |
| from pptx import Presentation | |
| from pptx.enum.shapes import MSO_SHAPE | |
| from pptx.enum.shapes import MSO_SHAPE_TYPE | |
| from pptx.dml.color import RGBColor | |
| from pptx.util import Inches, Pt | |
| from pptx.enum.chart import XL_CHART_TYPE | |
| from io import BytesIO | |
| import base64 | |
| import os | |
| import requests | |
| from PIL import Image | |
| import tempfile | |
| from visual_elements import ( | |
| add_shape_to_slide, | |
| add_image_to_slide, | |
| analyze_slide_for_visuals, | |
| apply_visuals_to_pptx_slide, | |
| download_image, | |
| get_random_placeholder_image, | |
| STANDARD_SHAPES, | |
| ICON_CATEGORIES | |
| ) | |
| def enhanced_create_ppt(slides_content, template_name): | |
| """Create an enhanced PowerPoint presentation with visual elements""" | |
| from utils import TEMPLATES | |
| # Get template info | |
| template = TEMPLATES.get(template_name, TEMPLATES["professional"]) | |
| # Check if it's a custom template | |
| if template_name == "custom" and "custom_template" in st.session_state: | |
| # Use the uploaded template | |
| prs = load_custom_template() | |
| else: | |
| # Create a basic presentation | |
| prs = Presentation() | |
| # Parse template colors for use in slides | |
| colors = template["colors"] | |
| primary_color = colors.get("primary", "#0F52BA") | |
| secondary_color = colors.get("secondary", "#E6E6E6") | |
| accent_color = colors.get("accent", "#D4AF37") | |
| text_color = colors.get("text", "#333333") | |
| # Title slide | |
| slide_layout = prs.slide_layouts[0] # Title Slide layout | |
| slide = prs.slides.add_slide(slide_layout) | |
| # Add title text | |
| title = slide.shapes.title | |
| if title: | |
| title.text = slides_content[0]["title"] | |
| # Find subtitle placeholder | |
| subtitle = None | |
| for shape in slide.placeholders: | |
| if shape.placeholder_format.type == 2: # subtitle | |
| subtitle = shape | |
| break | |
| if subtitle: | |
| subtitle.text = "Created with SlideGator.AI" | |
| # Add logo and branding to title slide | |
| logo_shape = slide.shapes.add_shape( | |
| MSO_SHAPE.ROUNDED_RECTANGLE, | |
| Inches(8.5), | |
| Inches(6.5), | |
| Inches(1.5), | |
| Inches(0.6) | |
| ) | |
| logo_shape.fill.solid() | |
| # Convert hex color to RGB | |
| if primary_color.startswith('#'): | |
| r = int(primary_color[1:3], 16) | |
| g = int(primary_color[3:5], 16) | |
| b = int(primary_color[5:7], 16) | |
| logo_shape.fill.fore_color.rgb = RGBColor(r, g, b) | |
| # Add text to logo shape | |
| text_frame = logo_shape.text_frame | |
| text_frame.text = "SlideGator.AI" | |
| text_frame.paragraphs[0].font.color.rgb = RGBColor(255, 255, 255) | |
| text_frame.paragraphs[0].font.size = Pt(14) | |
| text_frame.paragraphs[0].font.bold = True | |
| # Process each content slide | |
| for i, slide_content in enumerate(slides_content[1:], 1): | |
| # Determine best layout based on content or explicit design | |
| layout_type = "standard" | |
| if "design" in slide_content and "layout" in slide_content["design"]: | |
| layout_type = slide_content["design"]["layout"].lower() | |
| # Select appropriate slide layout | |
| if "two column" in layout_type or "comparison" in layout_type: | |
| slide_layout = prs.slide_layouts[3] if len(prs.slide_layouts) > 3 else prs.slide_layouts[1] | |
| elif "title only" in layout_type or "quote" in layout_type: | |
| slide_layout = prs.slide_layouts[5] if len(prs.slide_layouts) > 5 else prs.slide_layouts[0] | |
| elif "picture" in layout_type: | |
| slide_layout = prs.slide_layouts[6] if len(prs.slide_layouts) > 6 else prs.slide_layouts[1] | |
| else: | |
| slide_layout = prs.slide_layouts[1] # Default: Title and Content | |
| # Create the slide | |
| slide = prs.slides.add_slide(slide_layout) | |
| # Add title | |
| title_shape = slide.shapes.title | |
| if title_shape: | |
| title_shape.text = slide_content["title"] | |
| # Apply template styling to title | |
| for paragraph in title_shape.text_frame.paragraphs: | |
| paragraph.font.size = Pt(28) | |
| # Apply accent color to title if specified | |
| if "design" in slide_content and "accent_color" in slide_content["design"]: | |
| custom_color = slide_content["design"]["accent_color"] | |
| if custom_color.startswith('#'): | |
| r = int(custom_color[1:3], 16) | |
| g = int(custom_color[3:5], 16) | |
| b = int(custom_color[5:7], 16) | |
| paragraph.font.color.rgb = RGBColor(r, g, b) | |
| else: | |
| # Use template accent color | |
| if accent_color.startswith('#'): | |
| r = int(accent_color[1:3], 16) | |
| g = int(accent_color[3:5], 16) | |
| b = int(accent_color[5:7], 16) | |
| paragraph.font.color.rgb = RGBColor(r, g, b) | |
| # Get content placeholders | |
| content_placeholders = [shape for shape in slide.placeholders | |
| if shape.placeholder_format.type != 1] # 1 is title | |
| # Add content based on layout and available placeholders | |
| if content_placeholders: | |
| content_placeholder = content_placeholders[0] | |
| # Format content based on what's available and layout type | |
| if "content" in slide_content: | |
| if isinstance(slide_content["content"], list): | |
| if "two column" in layout_type and len(content_placeholders) > 1: | |
| # Split content for two columns | |
| left_placeholder = content_placeholders[0] | |
| right_placeholder = content_placeholders[1] | |
| mid_point = len(slide_content["content"]) // 2 | |
| left_content = slide_content["content"][:mid_point] | |
| right_content = slide_content["content"][mid_point:] | |
| # Add content to left column | |
| tf = left_placeholder.text_frame | |
| tf.clear() | |
| for point in left_content: | |
| p = tf.add_paragraph() | |
| p.text = point | |
| p.level = 0 | |
| # Add content to right column | |
| tf = right_placeholder.text_frame | |
| tf.clear() | |
| for point in right_content: | |
| p = tf.add_paragraph() | |
| p.text = point | |
| p.level = 0 | |
| else: | |
| # Standard bullet points | |
| tf = content_placeholder.text_frame | |
| tf.clear() | |
| for point in slide_content["content"]: | |
| p = tf.add_paragraph() | |
| p.text = point | |
| p.level = 0 | |
| else: | |
| # Plain text | |
| content_placeholder.text = slide_content["content"] | |
| else: | |
| content_placeholder.text = "Content to be added" | |
| # Add visual elements | |
| if "visuals" in slide_content: | |
| visuals = slide_content["visuals"] | |
| # Add chart if specified | |
| if "chart" in visuals: | |
| chart_info = visuals["chart"] | |
| chart_type = chart_info.get("type", "column") | |
| # Map chart type to XL_CHART_TYPE enum | |
| chart_types = { | |
| "column": XL_CHART_TYPE.COLUMN_CLUSTERED, | |
| "bar": XL_CHART_TYPE.BAR_CLUSTERED, | |
| "line": XL_CHART_TYPE.LINE, | |
| "pie": XL_CHART_TYPE.PIE, | |
| "area": XL_CHART_TYPE.AREA, | |
| "scatter": XL_CHART_TYPE.SCATTER | |
| } | |
| xl_chart_type = chart_types.get(chart_type, XL_CHART_TYPE.COLUMN_CLUSTERED) | |
| # Position chart | |
| chart_left = Inches(1) | |
| chart_top = Inches(2.5) | |
| chart_width = Inches(8) | |
| chart_height = Inches(3) | |
| # Create chart on slide | |
| chart = slide.shapes.add_chart( | |
| xl_chart_type, | |
| chart_left, chart_top, chart_width, chart_height | |
| ).chart | |
| # Sample data (normally would come from actual data) | |
| chart.has_legend = True | |
| chart.has_title = True | |
| chart.chart_title.text_frame.text = f"{chart_type.capitalize()} Chart" | |
| # Set up chart data | |
| chart_data = chart.chart_data | |
| # Sample data | |
| if chart_type == "pie": | |
| chart_data.categories = ["Segment 1", "Segment 2", "Segment 3", "Segment 4"] | |
| chart_data.series[0].name = "Series 1" | |
| chart_data.series[0].values = [4.2, 2.8, 3.7, 5.4] | |
| else: | |
| chart_data.categories = ["Q1", "Q2", "Q3", "Q4"] | |
| chart_data.series[0].name = "2023" | |
| chart_data.series[0].values = [4.2, 2.8, 3.7, 5.4] | |
| # Add second series | |
| series2 = chart_data.series.add() | |
| series2.name = "2024" | |
| series2.values = [2.7, 3.2, 4.1, 5.0] | |
| # Add image if specified | |
| if "image" in visuals: | |
| image_info = visuals["image"] | |
| image_source = image_info.get("source", "") | |
| if image_source == "stock" and "url" in image_info: | |
| # Download stock image | |
| image_url = image_info["url"] | |
| try: | |
| image_data = download_image(image_url) | |
| if image_data: | |
| # Position image | |
| img_left = Inches(2) | |
| img_top = Inches(3) | |
| img_width = Inches(6) | |
| img_height = Inches(3.5) | |
| # Add image to slide | |
| slide.shapes.add_picture( | |
| image_data, | |
| img_left, img_top, | |
| img_width, img_height | |
| ) | |
| except Exception as e: | |
| st.error(f"Error adding stock image: {str(e)}") | |
| elif image_source == "upload" and "data" in image_info: | |
| # Use uploaded image | |
| try: | |
| # Convert base64 to image | |
| img_data = base64.b64decode(image_info["data"]) | |
| img_stream = BytesIO(img_data) | |
| # Position image | |
| img_left = Inches(2) | |
| img_top = Inches(3) | |
| img_width = Inches(6) | |
| img_height = Inches(3.5) | |
| # Add image to slide | |
| slide.shapes.add_picture( | |
| img_stream, | |
| img_left, img_top, | |
| img_width, img_height | |
| ) | |
| except Exception as e: | |
| st.error(f"Error adding uploaded image: {str(e)}") | |
| # Add AI-generated image if specified | |
| if "ai_image" in visuals: | |
| # In a real implementation, this would use the generated image | |
| # For now, use a placeholder | |
| try: | |
| placeholder_image = get_random_placeholder_image() | |
| # Position image | |
| img_left = Inches(2) | |
| img_top = Inches(3) | |
| img_width = Inches(6) | |
| img_height = Inches(3.5) | |
| # Add image to slide | |
| slide.shapes.add_picture( | |
| placeholder_image, | |
| img_left, img_top, | |
| img_width, img_height | |
| ) | |
| except Exception as e: | |
| st.error(f"Error adding AI image placeholder: {str(e)}") | |
| # Add icons if specified | |
| if "icons" in visuals: | |
| icons = visuals["icons"] | |
| for i, icon_name in enumerate(icons): | |
| # Skip if not a valid shape | |
| if icon_name not in STANDARD_SHAPES: | |
| continue | |
| # Position icons in a row | |
| icon_left = Inches(1 + (i * 1.5)) | |
| icon_top = Inches(5.5) | |
| icon_width = Inches(1) | |
| icon_height = Inches(1) | |
| # Add icon shape | |
| add_shape_to_slide( | |
| slide, | |
| icon_name, | |
| left=icon_left, | |
| top=icon_top, | |
| width=icon_width, | |
| height=icon_height, | |
| fill_color=accent_color | |
| ) | |
| # Apply intelligent design if no explicit visuals are defined | |
| if "visuals" not in slide_content: | |
| apply_visuals_to_pptx_slide(slide, slide_content, colors) | |
| # Add notes if available | |
| if "notes" in slide_content and slide_content["notes"]: | |
| notes_slide = slide.notes_slide | |
| notes_slide.notes_text_frame.text = slide_content["notes"] | |
| # Save to BytesIO | |
| output = BytesIO() | |
| prs.save(output) | |
| output.seek(0) | |
| return output | |
| def load_custom_template(): | |
| """Load a custom template from session state""" | |
| try: | |
| # Read the template from session state | |
| template_data = st.session_state.custom_template | |
| # Create a BytesIO object | |
| template_stream = BytesIO(template_data) | |
| # Load the presentation | |
| prs = Presentation(template_stream) | |
| return prs |