Spaces:
				
			
			
	
			
			
					
		Running
		
	
	
	
			
			
	
	
	
	
		
		
					
		Running
		
	Add a new template (with user-edited master slides); get slide placeholders and index properly for this new template
Browse files- .gitattributes +1 -0
- global_config.py +8 -4
- helpers/pptx_helper.py +215 -104
- pptx_templates/Minimalist_sales_pitch.pptx +3 -0
    	
        .gitattributes
    CHANGED
    
    | @@ -34,3 +34,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text | |
| 34 | 
             
            *.zst filter=lfs diff=lfs merge=lfs -text
         | 
| 35 | 
             
            *tfevents* filter=lfs diff=lfs merge=lfs -text
         | 
| 36 | 
             
            *.pptx filter=lfs diff=lfs merge=lfs -text
         | 
|  | 
|  | |
| 34 | 
             
            *.zst filter=lfs diff=lfs merge=lfs -text
         | 
| 35 | 
             
            *tfevents* filter=lfs diff=lfs merge=lfs -text
         | 
| 36 | 
             
            *.pptx filter=lfs diff=lfs merge=lfs -text
         | 
| 37 | 
            +
            pptx_templates/Minimalist_sales_pitch.pptx filter=lfs diff=lfs merge=lfs -text
         | 
    	
        global_config.py
    CHANGED
    
    | @@ -38,16 +38,20 @@ class GlobalConfig: | |
| 38 | 
             
                PPTX_TEMPLATE_FILES = {
         | 
| 39 | 
             
                    'Basic': {
         | 
| 40 | 
             
                        'file': 'pptx_templates/Blank.pptx',
         | 
| 41 | 
            -
                        'caption': 'A good start (Uses [photos](https://unsplash.com/photos/AFZ-qBPEceA) by [cetteup](https://unsplash.com/@cetteup?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash) on [Unsplash](https://unsplash.com/photos/a-foggy-forest-filled-with-lots-of-trees-d3ci37Gcgxg?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash))'
         | 
|  | |
|  | |
|  | |
|  | |
| 42 | 
             
                    },
         | 
| 43 | 
             
                    'Ion Boardroom': {
         | 
| 44 | 
             
                        'file': 'pptx_templates/Ion_Boardroom.pptx',
         | 
| 45 | 
            -
                        'caption': 'Make some bold decisions'
         | 
| 46 | 
             
                    },
         | 
| 47 | 
             
                    'Urban Monochrome': {
         | 
| 48 | 
             
                        'file': 'pptx_templates/Urban_monochrome.pptx',
         | 
| 49 | 
            -
                        'caption': 'Marvel in a monochrome dream'
         | 
| 50 | 
            -
                    }
         | 
| 51 | 
             
                }
         | 
| 52 |  | 
| 53 | 
             
                # This is a long text, so not incorporated as a string in `strings.json`
         | 
|  | |
| 38 | 
             
                PPTX_TEMPLATE_FILES = {
         | 
| 39 | 
             
                    'Basic': {
         | 
| 40 | 
             
                        'file': 'pptx_templates/Blank.pptx',
         | 
| 41 | 
            +
                        'caption': '🟠 A good start (Uses [photos](https://unsplash.com/photos/AFZ-qBPEceA) by [cetteup](https://unsplash.com/@cetteup?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash) on [Unsplash](https://unsplash.com/photos/a-foggy-forest-filled-with-lots-of-trees-d3ci37Gcgxg?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash))'
         | 
| 42 | 
            +
                    },
         | 
| 43 | 
            +
                    'Minimalist Sales Pitch': {
         | 
| 44 | 
            +
                        'file': 'pptx_templates/Minimalist_sales_pitch.pptx',
         | 
| 45 | 
            +
                        'caption': '⚫ In high contrast'
         | 
| 46 | 
             
                    },
         | 
| 47 | 
             
                    'Ion Boardroom': {
         | 
| 48 | 
             
                        'file': 'pptx_templates/Ion_Boardroom.pptx',
         | 
| 49 | 
            +
                        'caption': '🔴 Make some bold decisions'
         | 
| 50 | 
             
                    },
         | 
| 51 | 
             
                    'Urban Monochrome': {
         | 
| 52 | 
             
                        'file': 'pptx_templates/Urban_monochrome.pptx',
         | 
| 53 | 
            +
                        'caption': '⚪ Marvel in a monochrome dream'
         | 
| 54 | 
            +
                    },
         | 
| 55 | 
             
                }
         | 
| 56 |  | 
| 57 | 
             
                # This is a long text, so not incorporated as a string in `strings.json`
         | 
    	
        helpers/pptx_helper.py
    CHANGED
    
    | @@ -73,11 +73,6 @@ def generate_powerpoint_presentation( | |
| 73 |  | 
| 74 | 
             
                # The structured "JSON" might contain trailing commas, so using json5
         | 
| 75 | 
             
                parsed_data = json5.loads(structured_data)
         | 
| 76 | 
            -
             | 
| 77 | 
            -
                logger.debug(
         | 
| 78 | 
            -
                    '*** Using PPTX template: %s',
         | 
| 79 | 
            -
                    GlobalConfig.PPTX_TEMPLATE_FILES[slides_template]['file']
         | 
| 80 | 
            -
                )
         | 
| 81 | 
             
                presentation = pptx.Presentation(GlobalConfig.PPTX_TEMPLATE_FILES[slides_template]['file'])
         | 
| 82 | 
             
                slide_width_inch, slide_height_inch = _get_slide_width_height_inches(presentation)
         | 
| 83 |  | 
| @@ -88,14 +83,17 @@ def generate_powerpoint_presentation( | |
| 88 | 
             
                subtitle = slide.placeholders[1]
         | 
| 89 | 
             
                title.text = parsed_data['title']
         | 
| 90 | 
             
                logger.info(
         | 
| 91 | 
            -
                    'PPT title: %s | #slides: %d',
         | 
| 92 | 
            -
                    title.text, len(parsed_data['slides'])
         | 
|  | |
| 93 | 
             
                )
         | 
| 94 | 
             
                subtitle.text = 'by Myself and SlideDeck AI :)'
         | 
| 95 | 
             
                all_headers = [title.text, ]
         | 
| 96 |  | 
| 97 | 
             
                # Add content in a loop
         | 
| 98 | 
             
                for a_slide in parsed_data['slides']:
         | 
|  | |
|  | |
| 99 | 
             
                    is_processing_done = _handle_double_col_layout(
         | 
| 100 | 
             
                        presentation=presentation,
         | 
| 101 | 
             
                        slide_json=a_slide,
         | 
| @@ -151,6 +149,47 @@ def get_flat_list_of_contents(items: list, level: int) -> List[Tuple]: | |
| 151 | 
             
                return flat_list
         | 
| 152 |  | 
| 153 |  | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 154 | 
             
            def _handle_default_display(
         | 
| 155 | 
             
                    presentation: pptx.Presentation,
         | 
| 156 | 
             
                    slide_json: dict,
         | 
| @@ -193,7 +232,13 @@ def _handle_default_display( | |
| 193 |  | 
| 194 | 
             
                shapes = slide.shapes
         | 
| 195 | 
             
                title_shape = shapes.title
         | 
| 196 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 197 | 
             
                title_shape.text = remove_slide_number_from_heading(slide_json['heading'])
         | 
| 198 | 
             
                text_frame = body_shape.text_frame
         | 
| 199 |  | 
| @@ -238,12 +283,31 @@ def _handle_display_image__in_foreground( | |
| 238 | 
             
                img_keywords = slide_json['img_keywords'].strip()
         | 
| 239 | 
             
                slide = presentation.slide_layouts[8]  # Picture with Caption
         | 
| 240 | 
             
                slide = presentation.slides.add_slide(slide)
         | 
|  | |
| 241 |  | 
| 242 | 
             
                title_placeholder = slide.shapes.title
         | 
| 243 | 
             
                title_placeholder.text = remove_slide_number_from_heading(slide_json['heading'])
         | 
| 244 |  | 
| 245 | 
            -
                 | 
| 246 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 247 | 
             
                flat_items_list = get_flat_list_of_contents(slide_json['bullet_points'], level=0)
         | 
| 248 |  | 
| 249 | 
             
                for idx, an_item in enumerate(flat_items_list):
         | 
| @@ -305,9 +369,15 @@ def _handle_display_image__in_background( | |
| 305 |  | 
| 306 | 
             
                # Add a photo in the background, text in the foreground
         | 
| 307 | 
             
                slide = presentation.slides.add_slide(presentation.slide_layouts[1])
         | 
| 308 | 
            -
             | 
| 309 | 
             
                title_shape = slide.shapes.title
         | 
| 310 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 311 | 
             
                title_shape.text = remove_slide_number_from_heading(slide_json['heading'])
         | 
| 312 |  | 
| 313 | 
             
                flat_items_list = get_flat_list_of_contents(slide_json['bullet_points'], level=0)
         | 
| @@ -418,39 +488,73 @@ def _handle_double_col_layout( | |
| 418 | 
             
                    ) and isinstance(double_col_content[0], dict) and isinstance(double_col_content[1], dict):
         | 
| 419 | 
             
                        slide = presentation.slide_layouts[4]
         | 
| 420 | 
             
                        slide = presentation.slides.add_slide(slide)
         | 
|  | |
| 421 |  | 
| 422 | 
             
                        shapes = slide.shapes
         | 
| 423 | 
             
                        title_placeholder = shapes.title
         | 
| 424 | 
             
                        title_placeholder.text = remove_slide_number_from_heading(slide_json['heading'])
         | 
| 425 |  | 
| 426 | 
            -
                         | 
| 427 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 428 | 
             
                        left_col_frame, right_col_frame = left_col.text_frame, right_col.text_frame
         | 
| 429 |  | 
| 430 | 
            -
                        if 'heading' in double_col_content[0]:
         | 
| 431 | 
             
                            left_heading.text = double_col_content[0]['heading']
         | 
| 432 | 
             
                        if 'bullet_points' in double_col_content[0]:
         | 
| 433 | 
             
                            flat_items_list = get_flat_list_of_contents(
         | 
| 434 | 
             
                                double_col_content[0]['bullet_points'], level=0
         | 
| 435 | 
             
                            )
         | 
| 436 |  | 
|  | |
|  | |
|  | |
| 437 | 
             
                            for idx, an_item in enumerate(flat_items_list):
         | 
| 438 | 
            -
                                if idx == 0:
         | 
| 439 | 
             
                                    left_col_frame.text = an_item[0].removeprefix(STEP_BY_STEP_PROCESS_MARKER)
         | 
| 440 | 
             
                                else:
         | 
| 441 | 
             
                                    paragraph = left_col_frame.add_paragraph()
         | 
| 442 | 
             
                                    paragraph.text = an_item[0].removeprefix(STEP_BY_STEP_PROCESS_MARKER)
         | 
| 443 | 
             
                                    paragraph.level = an_item[1]
         | 
| 444 |  | 
| 445 | 
            -
                        if 'heading' in double_col_content[1]:
         | 
| 446 | 
             
                            right_heading.text = double_col_content[1]['heading']
         | 
| 447 | 
             
                        if 'bullet_points' in double_col_content[1]:
         | 
| 448 | 
             
                            flat_items_list = get_flat_list_of_contents(
         | 
| 449 | 
             
                                double_col_content[1]['bullet_points'], level=0
         | 
| 450 | 
             
                            )
         | 
| 451 |  | 
|  | |
|  | |
|  | |
| 452 | 
             
                            for idx, an_item in enumerate(flat_items_list):
         | 
| 453 | 
            -
                                if idx == 0:
         | 
| 454 | 
             
                                    right_col_frame.text = an_item[0].removeprefix(STEP_BY_STEP_PROCESS_MARKER)
         | 
| 455 | 
             
                                else:
         | 
| 456 | 
             
                                    paragraph = right_col_frame.add_paragraph()
         | 
| @@ -614,153 +718,160 @@ def _get_slide_width_height_inches(presentation: pptx.Presentation) -> Tuple[flo | |
| 614 | 
             
                return slide_width_inch, slide_height_inch
         | 
| 615 |  | 
| 616 |  | 
| 617 | 
            -
            def print_placeholder_names(slide: pptx.slide.Slide):
         | 
| 618 | 
            -
                """
         | 
| 619 | 
            -
                Display the placeholder details of a given slide.
         | 
| 620 | 
            -
             | 
| 621 | 
            -
                :param slide: The slide.
         | 
| 622 | 
            -
                """
         | 
| 623 | 
            -
             | 
| 624 | 
            -
                for shape in slide.placeholders:
         | 
| 625 | 
            -
                    print(f'{shape.placeholder_format.idx=}, {shape.name=}')
         | 
| 626 | 
            -
             | 
| 627 | 
            -
             | 
| 628 | 
             
            if __name__ == '__main__':
         | 
| 629 | 
             
                _JSON_DATA = '''
         | 
| 630 | 
             
                {
         | 
| 631 | 
            -
                "title": " | 
| 632 | 
             
                "slides": [
         | 
| 633 | 
             
                    {
         | 
| 634 | 
            -
                        "heading": "Introduction to  | 
| 635 | 
             
                        "bullet_points": [
         | 
| 636 | 
            -
                            " | 
| 637 | 
            -
                             | 
| 638 | 
            -
             | 
|  | |
|  | |
|  | |
| 639 | 
             
                        ],
         | 
| 640 | 
            -
                        "key_message": "",
         | 
| 641 | 
            -
                        "img_keywords": " | 
| 642 | 
             
                    },
         | 
| 643 | 
             
                    {
         | 
| 644 | 
            -
                        "heading": " | 
| 645 | 
             
                        "bullet_points": [
         | 
| 646 | 
            -
                            " | 
| 647 | 
            -
                            " | 
| 648 | 
            -
                             | 
| 649 | 
            -
                                "Squares: Special type of rectangle with equal sides",
         | 
| 650 | 
            -
                                "Rounded Rectangles: Rectangles with rounded corners"
         | 
| 651 | 
            -
                            ],
         | 
| 652 | 
            -
                            "Circles: Round shapes with no corners",
         | 
| 653 | 
            -
                            "Ovals: Elliptical shapes"
         | 
| 654 | 
             
                        ],
         | 
| 655 | 
            -
                        "key_message": "",
         | 
| 656 | 
            -
                        "img_keywords": " | 
| 657 | 
             
                    },
         | 
| 658 | 
             
                    {
         | 
| 659 | 
            -
                        "heading": " | 
| 660 | 
             
                        "bullet_points": [
         | 
| 661 | 
            -
                            ">>  | 
| 662 | 
            -
                            ">>  | 
| 663 | 
            -
                            ">>  | 
| 664 | 
            -
                            ">>  | 
| 665 | 
            -
                            ">>  | 
|  | |
|  | |
|  | |
| 666 | 
             
                        ],
         | 
| 667 | 
            -
                        "key_message": " | 
| 668 | 
            -
                        "img_keywords": " | 
| 669 | 
             
                    },
         | 
| 670 | 
             
                    {
         | 
| 671 | 
            -
                        "heading": " | 
| 672 | 
             
                        "bullet_points": [
         | 
| 673 | 
             
                            {
         | 
| 674 | 
            -
                                "heading": " | 
| 675 | 
             
                                "bullet_points": [
         | 
| 676 | 
            -
                                    " | 
| 677 | 
            -
                                    " | 
| 678 | 
             
                                ]
         | 
| 679 | 
             
                            },
         | 
| 680 | 
             
                            {
         | 
| 681 | 
            -
                                "heading": " | 
| 682 | 
             
                                "bullet_points": [
         | 
| 683 | 
            -
                                    " | 
| 684 | 
            -
                                    " | 
| 685 | 
             
                                ]
         | 
| 686 | 
             
                            }
         | 
| 687 | 
             
                        ],
         | 
| 688 | 
            -
                        "key_message": " | 
| 689 | 
            -
                        "img_keywords": " | 
| 690 | 
             
                    },
         | 
| 691 | 
             
                    {
         | 
| 692 | 
            -
                        "heading": " | 
| 693 | 
             
                        "bullet_points": [
         | 
| 694 | 
            -
                            " | 
| 695 | 
            -
                            " | 
| 696 | 
            -
                            " | 
| 697 | 
             
                        ],
         | 
| 698 | 
            -
                        "key_message": "",
         | 
| 699 | 
            -
                        "img_keywords": " | 
| 700 | 
             
                    },
         | 
| 701 | 
             
                    {
         | 
| 702 | 
            -
                        "heading": " | 
| 703 | 
             
                        "bullet_points": [
         | 
| 704 | 
            -
                            " | 
| 705 | 
            -
                            " | 
| 706 | 
            -
                            " | 
|  | |
|  | |
| 707 | 
             
                        ],
         | 
| 708 | 
            -
                        "key_message": " | 
| 709 | 
            -
                        "img_keywords": " | 
| 710 | 
             
                    },
         | 
| 711 | 
             
                    {
         | 
| 712 | 
            -
                        "heading": " | 
| 713 | 
             
                        "bullet_points": [
         | 
| 714 | 
            -
                            " | 
| 715 | 
            -
                            " | 
| 716 | 
            -
                            " | 
|  | |
| 717 | 
             
                        ],
         | 
| 718 | 
            -
                        "key_message": " | 
| 719 | 
            -
                        "img_keywords": " | 
| 720 | 
             
                    },
         | 
| 721 | 
             
                    {
         | 
| 722 | 
            -
                        "heading": " | 
| 723 | 
             
                        "bullet_points": [
         | 
| 724 | 
             
                            {
         | 
| 725 | 
            -
                                "heading": " | 
| 726 | 
             
                                "bullet_points": [
         | 
| 727 | 
            -
                                    " | 
| 728 | 
            -
                                    " | 
|  | |
| 729 | 
             
                                ]
         | 
| 730 | 
             
                            },
         | 
| 731 | 
             
                            {
         | 
| 732 | 
            -
                                "heading": " | 
| 733 | 
             
                                "bullet_points": [
         | 
| 734 | 
            -
                                    " | 
| 735 | 
            -
                                    " | 
|  | |
| 736 | 
             
                                ]
         | 
| 737 | 
             
                            }
         | 
| 738 | 
             
                        ],
         | 
| 739 | 
            -
                        "key_message": " | 
| 740 | 
            -
                        "img_keywords": " | 
| 741 | 
             
                    },
         | 
| 742 | 
             
                    {
         | 
| 743 | 
            -
                        "heading": " | 
| 744 | 
             
                        "bullet_points": [
         | 
| 745 | 
            -
                             | 
| 746 | 
            -
             | 
| 747 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 748 | 
             
                        ],
         | 
| 749 | 
            -
                        "key_message": " | 
| 750 | 
            -
                        "img_keywords": " | 
| 751 | 
             
                    },
         | 
| 752 | 
             
                    {
         | 
| 753 | 
             
                        "heading": "Conclusion",
         | 
| 754 | 
             
                        "bullet_points": [
         | 
| 755 | 
            -
                            " | 
| 756 | 
            -
                            " | 
| 757 | 
            -
                            "Practice  | 
| 758 | 
             
                        ],
         | 
| 759 | 
            -
                        "key_message": " | 
| 760 | 
            -
                        "img_keywords": " | 
| 761 | 
             
                    }
         | 
| 762 | 
             
                ]
         | 
| 763 | 
            -
            } | 
|  | |
| 764 |  | 
| 765 | 
             
                temp = tempfile.NamedTemporaryFile(delete=False, suffix='.pptx')
         | 
| 766 | 
             
                path = pathlib.Path(temp.name)
         | 
| @@ -768,7 +879,7 @@ if __name__ == '__main__': | |
| 768 | 
             
                generate_powerpoint_presentation(
         | 
| 769 | 
             
                    json5.loads(_JSON_DATA),
         | 
| 770 | 
             
                    output_file_path=path,
         | 
| 771 | 
            -
                    slides_template=' | 
| 772 | 
             
                )
         | 
| 773 | 
             
                print(f'File path: {path}')
         | 
| 774 |  | 
|  | |
| 73 |  | 
| 74 | 
             
                # The structured "JSON" might contain trailing commas, so using json5
         | 
| 75 | 
             
                parsed_data = json5.loads(structured_data)
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
| 76 | 
             
                presentation = pptx.Presentation(GlobalConfig.PPTX_TEMPLATE_FILES[slides_template]['file'])
         | 
| 77 | 
             
                slide_width_inch, slide_height_inch = _get_slide_width_height_inches(presentation)
         | 
| 78 |  | 
|  | |
| 83 | 
             
                subtitle = slide.placeholders[1]
         | 
| 84 | 
             
                title.text = parsed_data['title']
         | 
| 85 | 
             
                logger.info(
         | 
| 86 | 
            +
                    'PPT title: %s | #slides: %d | template: %s',
         | 
| 87 | 
            +
                    title.text, len(parsed_data['slides']),
         | 
| 88 | 
            +
                    GlobalConfig.PPTX_TEMPLATE_FILES[slides_template]['file']
         | 
| 89 | 
             
                )
         | 
| 90 | 
             
                subtitle.text = 'by Myself and SlideDeck AI :)'
         | 
| 91 | 
             
                all_headers = [title.text, ]
         | 
| 92 |  | 
| 93 | 
             
                # Add content in a loop
         | 
| 94 | 
             
                for a_slide in parsed_data['slides']:
         | 
| 95 | 
            +
                    # The loop has a bug:
         | 
| 96 | 
            +
                    # if any of this functions fail (i.e., returns False), an empty slide would still exist
         | 
| 97 | 
             
                    is_processing_done = _handle_double_col_layout(
         | 
| 98 | 
             
                        presentation=presentation,
         | 
| 99 | 
             
                        slide_json=a_slide,
         | 
|  | |
| 149 | 
             
                return flat_list
         | 
| 150 |  | 
| 151 |  | 
| 152 | 
            +
            def get_slide_placeholders(
         | 
| 153 | 
            +
                    slide: pptx.slide.Slide,
         | 
| 154 | 
            +
                    layout_number: int,
         | 
| 155 | 
            +
                    is_debug: bool = False
         | 
| 156 | 
            +
            ) -> List[Tuple[int, str]]:
         | 
| 157 | 
            +
                """
         | 
| 158 | 
            +
                Return the index and name (lower case) of all placeholders present in a slide, except
         | 
| 159 | 
            +
                the title placeholder.
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                A placeholder in a slide is a place to add content. Each placeholder has a name and an index.
         | 
| 162 | 
            +
                This index is NOT a list index, rather a set of keys used to look up a dict. So, `idx` is
         | 
| 163 | 
            +
                non-contiguous. Also, the title placeholder of a slide always has index 0. User-added
         | 
| 164 | 
            +
                placeholder get indices assigned starting from 10.
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                With user-edited or added placeholders, their index may be difficult to track. This function
         | 
| 167 | 
            +
                returns the placeholders name as well, which could be useful to distinguish between the
         | 
| 168 | 
            +
                different placeholder.
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                :param slide: The slide.
         | 
| 171 | 
            +
                :param layout_number: The layout number used by the slide.
         | 
| 172 | 
            +
                :param is_debug: Whether to print debugging statements.
         | 
| 173 | 
            +
                :return: A list containing placeholders (idx, name) tuples, except the title placeholder.
         | 
| 174 | 
            +
                """
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                if is_debug:
         | 
| 177 | 
            +
                    print(
         | 
| 178 | 
            +
                        f'Slide layout #{layout_number}:'
         | 
| 179 | 
            +
                        f' # of placeholders: {len(slide.shapes.placeholders)} (including the title)'
         | 
| 180 | 
            +
                    )
         | 
| 181 | 
            +
             | 
| 182 | 
            +
                placeholders = [
         | 
| 183 | 
            +
                    (shape.placeholder_format.idx, shape.name.lower()) for shape in slide.shapes.placeholders
         | 
| 184 | 
            +
                ]
         | 
| 185 | 
            +
                placeholders.pop(0)  # Remove the title placeholder
         | 
| 186 | 
            +
             | 
| 187 | 
            +
                if is_debug:
         | 
| 188 | 
            +
                    print(placeholders)
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                return placeholders
         | 
| 191 | 
            +
             | 
| 192 | 
            +
             | 
| 193 | 
             
            def _handle_default_display(
         | 
| 194 | 
             
                    presentation: pptx.Presentation,
         | 
| 195 | 
             
                    slide_json: dict,
         | 
|  | |
| 232 |  | 
| 233 | 
             
                shapes = slide.shapes
         | 
| 234 | 
             
                title_shape = shapes.title
         | 
| 235 | 
            +
             | 
| 236 | 
            +
                try:
         | 
| 237 | 
            +
                    body_shape = shapes.placeholders[1]
         | 
| 238 | 
            +
                except KeyError:
         | 
| 239 | 
            +
                    placeholders = get_slide_placeholders(slide, layout_number=1)
         | 
| 240 | 
            +
                    body_shape = shapes.placeholders[placeholders[0][0]]
         | 
| 241 | 
            +
             | 
| 242 | 
             
                title_shape.text = remove_slide_number_from_heading(slide_json['heading'])
         | 
| 243 | 
             
                text_frame = body_shape.text_frame
         | 
| 244 |  | 
|  | |
| 283 | 
             
                img_keywords = slide_json['img_keywords'].strip()
         | 
| 284 | 
             
                slide = presentation.slide_layouts[8]  # Picture with Caption
         | 
| 285 | 
             
                slide = presentation.slides.add_slide(slide)
         | 
| 286 | 
            +
                placeholders = None
         | 
| 287 |  | 
| 288 | 
             
                title_placeholder = slide.shapes.title
         | 
| 289 | 
             
                title_placeholder.text = remove_slide_number_from_heading(slide_json['heading'])
         | 
| 290 |  | 
| 291 | 
            +
                try:
         | 
| 292 | 
            +
                    pic_col: PicturePlaceholder = slide.shapes.placeholders[1]
         | 
| 293 | 
            +
                except KeyError:
         | 
| 294 | 
            +
                    placeholders = get_slide_placeholders(slide, layout_number=8)
         | 
| 295 | 
            +
                    pic_col = None
         | 
| 296 | 
            +
                    for idx, name in placeholders:
         | 
| 297 | 
            +
                        if 'picture' in name:
         | 
| 298 | 
            +
                            pic_col: PicturePlaceholder = slide.shapes.placeholders[idx]
         | 
| 299 | 
            +
             | 
| 300 | 
            +
                try:
         | 
| 301 | 
            +
                    text_col: SlidePlaceholder = slide.shapes.placeholders[2]
         | 
| 302 | 
            +
                except KeyError:
         | 
| 303 | 
            +
                    text_col = None
         | 
| 304 | 
            +
                    if not placeholders:
         | 
| 305 | 
            +
                        placeholders = get_slide_placeholders(slide, layout_number=8)
         | 
| 306 | 
            +
             | 
| 307 | 
            +
                    for idx, name in placeholders:
         | 
| 308 | 
            +
                        if 'content' in name:
         | 
| 309 | 
            +
                            text_col: SlidePlaceholder = slide.shapes.placeholders[idx]
         | 
| 310 | 
            +
             | 
| 311 | 
             
                flat_items_list = get_flat_list_of_contents(slide_json['bullet_points'], level=0)
         | 
| 312 |  | 
| 313 | 
             
                for idx, an_item in enumerate(flat_items_list):
         | 
|  | |
| 369 |  | 
| 370 | 
             
                # Add a photo in the background, text in the foreground
         | 
| 371 | 
             
                slide = presentation.slides.add_slide(presentation.slide_layouts[1])
         | 
|  | |
| 372 | 
             
                title_shape = slide.shapes.title
         | 
| 373 | 
            +
             | 
| 374 | 
            +
                try:
         | 
| 375 | 
            +
                    body_shape = slide.shapes.placeholders[1]
         | 
| 376 | 
            +
                except KeyError:
         | 
| 377 | 
            +
                    placeholders = get_slide_placeholders(slide, layout_number=1)
         | 
| 378 | 
            +
                    # Layout 1 usually has two placeholders, including the title
         | 
| 379 | 
            +
                    body_shape = slide.shapes.placeholders[placeholders[0][0]]
         | 
| 380 | 
            +
             | 
| 381 | 
             
                title_shape.text = remove_slide_number_from_heading(slide_json['heading'])
         | 
| 382 |  | 
| 383 | 
             
                flat_items_list = get_flat_list_of_contents(slide_json['bullet_points'], level=0)
         | 
|  | |
| 488 | 
             
                    ) and isinstance(double_col_content[0], dict) and isinstance(double_col_content[1], dict):
         | 
| 489 | 
             
                        slide = presentation.slide_layouts[4]
         | 
| 490 | 
             
                        slide = presentation.slides.add_slide(slide)
         | 
| 491 | 
            +
                        placeholders = None
         | 
| 492 |  | 
| 493 | 
             
                        shapes = slide.shapes
         | 
| 494 | 
             
                        title_placeholder = shapes.title
         | 
| 495 | 
             
                        title_placeholder.text = remove_slide_number_from_heading(slide_json['heading'])
         | 
| 496 |  | 
| 497 | 
            +
                        try:
         | 
| 498 | 
            +
                            left_heading, right_heading = shapes.placeholders[1], shapes.placeholders[3]
         | 
| 499 | 
            +
                        except KeyError:
         | 
| 500 | 
            +
                            # For manually edited/added master slides, the placeholder idx numbers in the dict
         | 
| 501 | 
            +
                            # will be different (>= 10)
         | 
| 502 | 
            +
                            left_heading, right_heading = None, None
         | 
| 503 | 
            +
                            placeholders = get_slide_placeholders(slide, layout_number=4)
         | 
| 504 | 
            +
             | 
| 505 | 
            +
                            for idx, name in placeholders:
         | 
| 506 | 
            +
                                if 'text placeholder' in name:
         | 
| 507 | 
            +
                                    if not left_heading:
         | 
| 508 | 
            +
                                        left_heading = shapes.placeholders[idx]
         | 
| 509 | 
            +
                                    elif not right_heading:
         | 
| 510 | 
            +
                                        right_heading = shapes.placeholders[idx]
         | 
| 511 | 
            +
             | 
| 512 | 
            +
                        try:
         | 
| 513 | 
            +
                            left_col, right_col = shapes.placeholders[2], shapes.placeholders[4]
         | 
| 514 | 
            +
                        except KeyError:
         | 
| 515 | 
            +
                            left_col, right_col = None, None
         | 
| 516 | 
            +
                            if not placeholders:
         | 
| 517 | 
            +
                                placeholders = get_slide_placeholders(slide, layout_number=4)
         | 
| 518 | 
            +
             | 
| 519 | 
            +
                            for idx, name in placeholders:
         | 
| 520 | 
            +
                                if 'content placeholder' in name:
         | 
| 521 | 
            +
                                    if not left_col:
         | 
| 522 | 
            +
                                        left_col = shapes.placeholders[idx]
         | 
| 523 | 
            +
                                    elif not right_col:
         | 
| 524 | 
            +
                                        right_col = shapes.placeholders[idx]
         | 
| 525 | 
            +
             | 
| 526 | 
             
                        left_col_frame, right_col_frame = left_col.text_frame, right_col.text_frame
         | 
| 527 |  | 
| 528 | 
            +
                        if 'heading' in double_col_content[0] and left_heading:
         | 
| 529 | 
             
                            left_heading.text = double_col_content[0]['heading']
         | 
| 530 | 
             
                        if 'bullet_points' in double_col_content[0]:
         | 
| 531 | 
             
                            flat_items_list = get_flat_list_of_contents(
         | 
| 532 | 
             
                                double_col_content[0]['bullet_points'], level=0
         | 
| 533 | 
             
                            )
         | 
| 534 |  | 
| 535 | 
            +
                            if not left_heading:
         | 
| 536 | 
            +
                                left_col_frame.text = double_col_content[0]['heading']
         | 
| 537 | 
            +
             | 
| 538 | 
             
                            for idx, an_item in enumerate(flat_items_list):
         | 
| 539 | 
            +
                                if left_heading and idx == 0:
         | 
| 540 | 
             
                                    left_col_frame.text = an_item[0].removeprefix(STEP_BY_STEP_PROCESS_MARKER)
         | 
| 541 | 
             
                                else:
         | 
| 542 | 
             
                                    paragraph = left_col_frame.add_paragraph()
         | 
| 543 | 
             
                                    paragraph.text = an_item[0].removeprefix(STEP_BY_STEP_PROCESS_MARKER)
         | 
| 544 | 
             
                                    paragraph.level = an_item[1]
         | 
| 545 |  | 
| 546 | 
            +
                        if 'heading' in double_col_content[1] and right_heading:
         | 
| 547 | 
             
                            right_heading.text = double_col_content[1]['heading']
         | 
| 548 | 
             
                        if 'bullet_points' in double_col_content[1]:
         | 
| 549 | 
             
                            flat_items_list = get_flat_list_of_contents(
         | 
| 550 | 
             
                                double_col_content[1]['bullet_points'], level=0
         | 
| 551 | 
             
                            )
         | 
| 552 |  | 
| 553 | 
            +
                            if not right_heading:
         | 
| 554 | 
            +
                                right_col_frame.text = double_col_content[1]['heading']
         | 
| 555 | 
            +
             | 
| 556 | 
             
                            for idx, an_item in enumerate(flat_items_list):
         | 
| 557 | 
            +
                                if right_col_frame and idx == 0:
         | 
| 558 | 
             
                                    right_col_frame.text = an_item[0].removeprefix(STEP_BY_STEP_PROCESS_MARKER)
         | 
| 559 | 
             
                                else:
         | 
| 560 | 
             
                                    paragraph = right_col_frame.add_paragraph()
         | 
|  | |
| 718 | 
             
                return slide_width_inch, slide_height_inch
         | 
| 719 |  | 
| 720 |  | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 721 | 
             
            if __name__ == '__main__':
         | 
| 722 | 
             
                _JSON_DATA = '''
         | 
| 723 | 
             
                {
         | 
| 724 | 
            +
                "title": "The Fascinating World of Chess",
         | 
| 725 | 
             
                "slides": [
         | 
| 726 | 
             
                    {
         | 
| 727 | 
            +
                        "heading": "Introduction to Chess",
         | 
| 728 | 
             
                        "bullet_points": [
         | 
| 729 | 
            +
                            "Chess is a strategic board game played between two players.",
         | 
| 730 | 
            +
                            [
         | 
| 731 | 
            +
                                "Each player begins the game with 16 pieces: one king, one queen, two rooks, two knights, two bishops, and eight pawns.",
         | 
| 732 | 
            +
                                "The goal of the game is to checkmate your opponent's king. This means the king is in a position to be captured (in 'check') but has no move to escape (mate)."
         | 
| 733 | 
            +
                            ],
         | 
| 734 | 
            +
                            "Chess is believed to have originated in northern India in the 6th century AD."
         | 
| 735 | 
             
                        ],
         | 
| 736 | 
            +
                        "key_message": "Understanding the basics of chess is crucial before delving into strategies.",
         | 
| 737 | 
            +
                        "img_keywords": "chessboard, chess pieces, king, queen, rook, knight, bishop, pawn"
         | 
| 738 | 
             
                    },
         | 
| 739 | 
             
                    {
         | 
| 740 | 
            +
                        "heading": "The Chessboard",
         | 
| 741 | 
             
                        "bullet_points": [
         | 
| 742 | 
            +
                            "The chessboard is made up of 64 squares in an 8x8 grid.",
         | 
| 743 | 
            +
                            "Each player starts with their pieces on their home rank (row).",
         | 
| 744 | 
            +
                            "The board is divided into two camps: one for each player."
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
| 745 | 
             
                        ],
         | 
| 746 | 
            +
                        "key_message": "Knowing the layout of the chessboard is essential for understanding piece movement.",
         | 
| 747 | 
            +
                        "img_keywords": "chessboard layout, 8x8 grid, home rank, player camps"
         | 
| 748 | 
             
                    },
         | 
| 749 | 
             
                    {
         | 
| 750 | 
            +
                        "heading": "Movement of Pieces",
         | 
| 751 | 
             
                        "bullet_points": [
         | 
| 752 | 
            +
                            ">> Each piece moves differently. Learning these movements is key to playing chess.",
         | 
| 753 | 
            +
                            ">> The king moves one square in any direction.",
         | 
| 754 | 
            +
                            ">> The queen combines the moves of the rook and bishop.",
         | 
| 755 | 
            +
                            ">> The rook moves horizontally or vertically along a rank or file.",
         | 
| 756 | 
            +
                            ">> The bishop moves diagonally.",
         | 
| 757 | 
            +
                            ">> The knight moves in an L-shape: two squares in a horizontal or vertical direction, then one square perpendicular to that.",
         | 
| 758 | 
            +
                            ">> The pawn moves forward one square, but captures diagonally.",
         | 
| 759 | 
            +
                            ">> Pawns have the initial option of moving two squares forward on their first move."
         | 
| 760 | 
             
                        ],
         | 
| 761 | 
            +
                        "key_message": "Understanding how each piece moves is fundamental to playing chess.",
         | 
| 762 | 
            +
                        "img_keywords": "chess piece movements, king, queen, rook, bishop, knight, pawn"
         | 
| 763 | 
             
                    },
         | 
| 764 | 
             
                    {
         | 
| 765 | 
            +
                        "heading": "Special Moves",
         | 
| 766 | 
             
                        "bullet_points": [
         | 
| 767 | 
             
                            {
         | 
| 768 | 
            +
                                "heading": "Castling",
         | 
| 769 | 
             
                                "bullet_points": [
         | 
| 770 | 
            +
                                    "Castling is a unique move involving the king and a rook.",
         | 
| 771 | 
            +
                                    "It involves moving the king two squares towards a rook, then moving that rook to the square the king skipped over."
         | 
| 772 | 
             
                                ]
         | 
| 773 | 
             
                            },
         | 
| 774 | 
             
                            {
         | 
| 775 | 
            +
                                "heading": "En Passant",
         | 
| 776 | 
             
                                "bullet_points": [
         | 
| 777 | 
            +
                                    "En passant is a special pawn capture move.",
         | 
| 778 | 
            +
                                    "It occurs when a pawn moves two squares forward from its starting position and lands beside an opponent's pawn, which could have captured it if the first pawn had only moved one square forward."
         | 
| 779 | 
             
                                ]
         | 
| 780 | 
             
                            }
         | 
| 781 | 
             
                        ],
         | 
| 782 | 
            +
                        "key_message": "Understanding these special moves can add depth to your chess strategy.",
         | 
| 783 | 
            +
                        "img_keywords": "castling, en passant, special chess moves"
         | 
| 784 | 
             
                    },
         | 
| 785 | 
             
                    {
         | 
| 786 | 
            +
                        "heading": "Chess Notation",
         | 
| 787 | 
             
                        "bullet_points": [
         | 
| 788 | 
            +
                            "Chess notation is a system used to record and communicate chess games.",
         | 
| 789 | 
            +
                            "It uses algebraic notation, where each square on the board is identified by a letter and a number.",
         | 
| 790 | 
            +
                            "Pieces are identified by their initial letters: K for king, Q for queen, R for rook, B for bishop, N for knight, and P for pawn."
         | 
| 791 | 
             
                        ],
         | 
| 792 | 
            +
                        "key_message": "Learning chess notation is helpful for recording, analyzing, and discussing games.",
         | 
| 793 | 
            +
                        "img_keywords": "chess notation, algebraic notation, chess symbols"
         | 
| 794 | 
             
                    },
         | 
| 795 | 
             
                    {
         | 
| 796 | 
            +
                        "heading": "Chess Strategies",
         | 
| 797 | 
             
                        "bullet_points": [
         | 
| 798 | 
            +
                            "Develop your pieces quickly and efficiently.",
         | 
| 799 | 
            +
                            "Control the center of the board.",
         | 
| 800 | 
            +
                            "Castle early to protect your king.",
         | 
| 801 | 
            +
                            "Keep your king safe.",
         | 
| 802 | 
            +
                            "Think ahead and plan your moves."
         | 
| 803 | 
             
                        ],
         | 
| 804 | 
            +
                        "key_message": "Following these strategies can help improve your chess skills.",
         | 
| 805 | 
            +
                        "img_keywords": "chess strategies, piece development, center control, king safety, planning ahead"
         | 
| 806 | 
             
                    },
         | 
| 807 | 
             
                    {
         | 
| 808 | 
            +
                        "heading": "Chess Tactics",
         | 
| 809 | 
             
                        "bullet_points": [
         | 
| 810 | 
            +
                            "Fork: attacking two enemy pieces with the same move.",
         | 
| 811 | 
            +
                            "Pin: restricting the movement of an enemy piece.",
         | 
| 812 | 
            +
                            "Skewer: forcing an enemy piece to move away from a threatened piece.",
         | 
| 813 | 
            +
                            "Discovered attack: moving a piece to reveal an attack by another piece behind it."
         | 
| 814 | 
             
                        ],
         | 
| 815 | 
            +
                        "key_message": "Mastering these tactics can help you gain an advantage in games.",
         | 
| 816 | 
            +
                        "img_keywords": "chess tactics, fork, pin, skewer, discovered attack"
         | 
| 817 | 
             
                    },
         | 
| 818 | 
             
                    {
         | 
| 819 | 
            +
                        "heading": "Chess Openings",
         | 
| 820 | 
             
                        "bullet_points": [
         | 
| 821 | 
             
                            {
         | 
| 822 | 
            +
                                "heading": "Italian Game",
         | 
| 823 | 
             
                                "bullet_points": [
         | 
| 824 | 
            +
                                    "1. e4 e5",
         | 
| 825 | 
            +
                                    "2. Nf3 Nc6",
         | 
| 826 | 
            +
                                    "3. Bc4 Bc5"
         | 
| 827 | 
             
                                ]
         | 
| 828 | 
             
                            },
         | 
| 829 | 
             
                            {
         | 
| 830 | 
            +
                                "heading": "Ruy Lopez",
         | 
| 831 | 
             
                                "bullet_points": [
         | 
| 832 | 
            +
                                    "1. e4 e5",
         | 
| 833 | 
            +
                                    "2. Nf3 Nc6",
         | 
| 834 | 
            +
                                    "3. Bb5"
         | 
| 835 | 
             
                                ]
         | 
| 836 | 
             
                            }
         | 
| 837 | 
             
                        ],
         | 
| 838 | 
            +
                        "key_message": "Learning popular chess openings can help you start games effectively.",
         | 
| 839 | 
            +
                        "img_keywords": "chess openings, Italian Game, Ruy Lopez"
         | 
| 840 | 
             
                    },
         | 
| 841 | 
             
                    {
         | 
| 842 | 
            +
                        "heading": "Chess Endgames",
         | 
| 843 | 
             
                        "bullet_points": [
         | 
| 844 | 
            +
                            {
         | 
| 845 | 
            +
                                "heading": "King and Pawn Endgame",
         | 
| 846 | 
            +
                                "bullet_points": [
         | 
| 847 | 
            +
                                    "This endgame involves a king and one or more pawns against a lone king.",
         | 
| 848 | 
            +
                                    "The goal is to promote a pawn to a new queen."
         | 
| 849 | 
            +
                                ]
         | 
| 850 | 
            +
                            },
         | 
| 851 | 
            +
                            {
         | 
| 852 | 
            +
                                "heading": "Rook Endgame",
         | 
| 853 | 
            +
                                "bullet_points": [
         | 
| 854 | 
            +
                                    "This endgame involves a rook against a lone king.",
         | 
| 855 | 
            +
                                    "The goal is to checkmate the opponent's king using the rook."
         | 
| 856 | 
            +
                                ]
         | 
| 857 | 
            +
                            }
         | 
| 858 | 
             
                        ],
         | 
| 859 | 
            +
                        "key_message": "Understanding common chess endgames can help you win games.",
         | 
| 860 | 
            +
                        "img_keywords": "chess endgames, king and pawn endgame, rook endgame"
         | 
| 861 | 
             
                    },
         | 
| 862 | 
             
                    {
         | 
| 863 | 
             
                        "heading": "Conclusion",
         | 
| 864 | 
             
                        "bullet_points": [
         | 
| 865 | 
            +
                            "Chess is a complex game that requires strategy, tactics, and planning.",
         | 
| 866 | 
            +
                            "Understanding the rules, piece movements, and common strategies can help improve your chess skills.",
         | 
| 867 | 
            +
                            "Practice regularly to improve your game."
         | 
| 868 | 
             
                        ],
         | 
| 869 | 
            +
                        "key_message": "To excel at chess, one must understand its fundamentals and practice regularly.",
         | 
| 870 | 
            +
                        "img_keywords": "chess fundamentals, chess improvement, regular practice"
         | 
| 871 | 
             
                    }
         | 
| 872 | 
             
                ]
         | 
| 873 | 
            +
            }
         | 
| 874 | 
            +
            '''
         | 
| 875 |  | 
| 876 | 
             
                temp = tempfile.NamedTemporaryFile(delete=False, suffix='.pptx')
         | 
| 877 | 
             
                path = pathlib.Path(temp.name)
         | 
|  | |
| 879 | 
             
                generate_powerpoint_presentation(
         | 
| 880 | 
             
                    json5.loads(_JSON_DATA),
         | 
| 881 | 
             
                    output_file_path=path,
         | 
| 882 | 
            +
                    slides_template='Minimalist Sales Pitch'
         | 
| 883 | 
             
                )
         | 
| 884 | 
             
                print(f'File path: {path}')
         | 
| 885 |  | 
    	
        pptx_templates/Minimalist_sales_pitch.pptx
    ADDED
    
    | @@ -0,0 +1,3 @@ | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            version https://git-lfs.github.com/spec/v1
         | 
| 2 | 
            +
            oid sha256:2a6da48abf9a44bde11fa8337519435d51fe5f161e605d8f5ab00d4a914fc964
         | 
| 3 | 
            +
            size 1298028
         |