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
|