Initial commit: organize Primary song slides repo
One folder per song under songs/. Tracks PPTX slide files and Python generation scripts. PDFs and videos excluded (too large for git; originals are in Google Drive). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
# Large binary files — store in Google Drive, not git
|
||||||
|
*.mp4
|
||||||
|
*.mov
|
||||||
|
*.avi
|
||||||
|
*.mkv
|
||||||
|
*.pdf
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# Primary Song Slides
|
||||||
|
|
||||||
|
Slideshows and source materials for LDS Primary singing time.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
Each song gets its own folder under `songs/`. Files include PowerPoint presentations (`.pptx`), PDFs (flip charts, lyric sheets), and any other source materials.
|
||||||
|
|
||||||
|
```
|
||||||
|
songs/
|
||||||
|
a-childs-prayer/
|
||||||
|
as-i-search-the-holy-scriptures/
|
||||||
|
because-singing-time/
|
||||||
|
choose-to-serve-the-lord/
|
||||||
|
dearest-mother-i-love-you/
|
||||||
|
holy-places/
|
||||||
|
i-will-be-valiant/
|
||||||
|
i-will-follow-gods-plan/
|
||||||
|
keep-the-commandments/
|
||||||
|
my-dad/
|
||||||
|
primary-singing-time/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Source Files
|
||||||
|
|
||||||
|
All slides were originally created as Google Slides presentations and exported to `.pptx` format for archival. Source presentations remain in Google Drive.
|
||||||
|
|
||||||
|
| Song | Files |
|
||||||
|
|------|-------|
|
||||||
|
| As I Search the Holy Scriptures | AsISearchTheHolyScriptures.pptx |
|
||||||
|
| I Will Be Valiant | IWillBeValiant.pptx |
|
||||||
|
| My Dad | my-dad.pptx |
|
||||||
|
| A Child's Prayer | *(pending)* |
|
||||||
|
| Choose to Serve the Lord | *(pending — file >10MB)* |
|
||||||
|
| Holy Places | *(pending)* |
|
||||||
|
| Keep the Commandments | *(pending)* |
|
||||||
|
| I Will Follow God's Plan | *(pending)* |
|
||||||
|
| Because Singing Time | *(pending)* |
|
||||||
|
| Dearest Mother I Love You | *(pending)* |
|
||||||
|
| Primary Singing Time | *(pending)* |
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import fitz
|
||||||
|
from pptx import Presentation
|
||||||
|
from pptx.util import Emu, Inches
|
||||||
|
import io
|
||||||
|
|
||||||
|
PDF_PATH = r"C:\Users\jhodgkin\Pictures\Holy Places Flip Chart.pdf"
|
||||||
|
OUT_PATH = r"C:\Users\jhodgkin\Pictures\Holy Places Flip Chart.pptx"
|
||||||
|
DPI = 150
|
||||||
|
|
||||||
|
SLIDE_W = Inches(13.333) # widescreen landscape
|
||||||
|
SLIDE_H = Inches(7.5)
|
||||||
|
GAP = Inches(0.1)
|
||||||
|
|
||||||
|
doc = fitz.open(PDF_PATH)
|
||||||
|
prs = Presentation()
|
||||||
|
prs.slide_width = SLIDE_W
|
||||||
|
prs.slide_height = SLIDE_H
|
||||||
|
blank_layout = prs.slide_layouts[6]
|
||||||
|
|
||||||
|
def render_page(page):
|
||||||
|
mat = fitz.Matrix(DPI / 72, DPI / 72)
|
||||||
|
pix = page.get_pixmap(matrix=mat, alpha=False)
|
||||||
|
return io.BytesIO(pix.tobytes("png")), pix.width, pix.height
|
||||||
|
|
||||||
|
def add_single_slide(page, label):
|
||||||
|
img, pw, ph = render_page(page)
|
||||||
|
slide = prs.slides.add_slide(blank_layout)
|
||||||
|
aspect = pw / ph
|
||||||
|
h = SLIDE_H
|
||||||
|
w = int(h * aspect)
|
||||||
|
if w > SLIDE_W:
|
||||||
|
w = SLIDE_W
|
||||||
|
h = int(w / aspect)
|
||||||
|
left = (SLIDE_W - w) // 2
|
||||||
|
top = (SLIDE_H - h) // 2
|
||||||
|
slide.shapes.add_picture(img, left, top, width=w, height=h)
|
||||||
|
print(f" Slide: {label}")
|
||||||
|
|
||||||
|
def add_pair_slide(page_a, page_b, label):
|
||||||
|
img_a, pw_a, ph_a = render_page(page_a)
|
||||||
|
img_b, pw_b, ph_b = render_page(page_b)
|
||||||
|
slide = prs.slides.add_slide(blank_layout)
|
||||||
|
|
||||||
|
usable_w = (SLIDE_W - GAP) // 2
|
||||||
|
|
||||||
|
def fit(pw, ph):
|
||||||
|
aspect = pw / ph
|
||||||
|
h = SLIDE_H
|
||||||
|
w = int(h * aspect)
|
||||||
|
if w > usable_w:
|
||||||
|
w = usable_w
|
||||||
|
h = int(w / aspect)
|
||||||
|
return w, h
|
||||||
|
|
||||||
|
wa, ha = fit(pw_a, ph_a)
|
||||||
|
wb, hb = fit(pw_b, ph_b)
|
||||||
|
|
||||||
|
top_a = (SLIDE_H - ha) // 2
|
||||||
|
top_b = (SLIDE_H - hb) // 2
|
||||||
|
|
||||||
|
# Center the pair horizontally
|
||||||
|
total_w = wa + GAP + wb
|
||||||
|
left_a = (SLIDE_W - total_w) // 2
|
||||||
|
left_b = left_a + wa + GAP
|
||||||
|
|
||||||
|
slide.shapes.add_picture(img_a, left_a, top_a, width=wa, height=ha)
|
||||||
|
slide.shapes.add_picture(img_b, left_b, top_b, width=wb, height=hb)
|
||||||
|
print(f" Slide: {label}")
|
||||||
|
|
||||||
|
pages = list(doc)
|
||||||
|
|
||||||
|
# Page 1 alone
|
||||||
|
add_single_slide(pages[0], "page 1")
|
||||||
|
|
||||||
|
# Pages 2+ in pairs
|
||||||
|
for i in range(1, len(pages), 2):
|
||||||
|
if i + 1 < len(pages):
|
||||||
|
add_pair_slide(pages[i], pages[i + 1], f"pages {i+1}+{i+2}")
|
||||||
|
else:
|
||||||
|
add_single_slide(pages[i], f"page {i+1}")
|
||||||
|
|
||||||
|
prs.save(OUT_PATH)
|
||||||
|
print(f"\nSaved: {OUT_PATH}")
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,162 @@
|
|||||||
|
import fitz
|
||||||
|
from pptx import Presentation
|
||||||
|
from pptx.util import Inches, Pt
|
||||||
|
from pptx.dml.color import RGBColor
|
||||||
|
from pptx.enum.text import PP_ALIGN
|
||||||
|
import io
|
||||||
|
|
||||||
|
PDF_PATH = r"C:\Users\jhodgkin\Documents\dev\primary-song-slides\BookOfMormonStories.pdf"
|
||||||
|
OUT_PATH = r"C:\Users\jhodgkin\Documents\dev\primary-song-slides\BookOfMormonStories.pptx"
|
||||||
|
DPI = 96
|
||||||
|
|
||||||
|
SLIDE_W = Inches(13.333)
|
||||||
|
SLIDE_H = Inches(7.5)
|
||||||
|
TEXT_BOX_H = Inches(2.1)
|
||||||
|
FONT_SIZE = Pt(44)
|
||||||
|
TEXT_COLOR = RGBColor(0x4A, 0x27, 0x11) # deep warm brown
|
||||||
|
|
||||||
|
# PDF pages 0-31 = illustration cards; page 32 = title page (skipped)
|
||||||
|
# Verse 1 lyrics are from CS 118. Fill in the remaining verses with correct lyrics.
|
||||||
|
#
|
||||||
|
# Image guide (0-based page → subject):
|
||||||
|
# 0 = teacher reading to children (intro)
|
||||||
|
# 1 = Lehi's family on ancient scroll
|
||||||
|
# 2 = three men sailing
|
||||||
|
# 3 = righteous family with halos in jungle
|
||||||
|
# 4 = large group gathering in jungle
|
||||||
|
# 5 = lush jungle / hut
|
||||||
|
# 6 = children springing from open Book of Mormon
|
||||||
|
# 7 = family with halos (closer)
|
||||||
|
# 8 = young man in red sash (Nephi? Alma?)
|
||||||
|
# 9 = angel with wings
|
||||||
|
# 10 = three men around a fallen figure (Alma struck down)
|
||||||
|
# 11 = righteous youth with halo in jungle
|
||||||
|
# 12 = man in chains
|
||||||
|
# 13 = pile of chains
|
||||||
|
# 14 = wicked king
|
||||||
|
# 15 = gravestone with flowers
|
||||||
|
# 16 = shepherd with sheep and staff
|
||||||
|
# 17 = flock of many sheep
|
||||||
|
# 18 = slingshot and sword
|
||||||
|
# 19 = righteous shepherd with halo
|
||||||
|
# 20 = four young warriors (shields and spears, no halos)
|
||||||
|
# 21 = Jesus Christ in red robe, arms open
|
||||||
|
# 22 = four warriors smiling
|
||||||
|
# 23 = four warriors with halos in jungle
|
||||||
|
# 24 = man standing triumphantly on stone wall
|
||||||
|
# 25 = three arrows flying
|
||||||
|
# 26 = young man preaching, arms wide
|
||||||
|
# 27 = young man preaching with halo
|
||||||
|
# 28 = crucifixion (three crosses)
|
||||||
|
# 29 = Jesus hugging many children
|
||||||
|
# 30 = portrait of Jesus Christ
|
||||||
|
# 31 = five children with halos in jungle
|
||||||
|
SLIDES = [
|
||||||
|
# Verse 1 — Lehi's family / ancient Lamanites
|
||||||
|
(0, "Book of Mormon stories that my teacher tells to me,"),
|
||||||
|
(1, "Are about the Lamanites in ancient history."),
|
||||||
|
(2, "Long ago their fathers came from far across the sea,"),
|
||||||
|
(3, "Giv'n the land if they lived righteously."),
|
||||||
|
|
||||||
|
# Verse 2 — Lamanites and others in the promised land
|
||||||
|
(4, "Lamanites met others who were seeking liberty,"),
|
||||||
|
(5, "And the land soon welcomed all who wanted to be free."),
|
||||||
|
(6, "Book of Mormon stories say that we must brothers be,"),
|
||||||
|
(7, "Giv'n the land if we live righteously."),
|
||||||
|
|
||||||
|
# Verse 3 — Alma the Younger
|
||||||
|
(8, "Alma was rebellious, and he fought against the right."),
|
||||||
|
(9, "Then one day an angel came to turn him to the light."),
|
||||||
|
(10, "Struck before his brethren, Alma learned humility."),
|
||||||
|
(11, "Then he taught in the land righteously."),
|
||||||
|
|
||||||
|
# Verse 4 — Abinadi before King Noah
|
||||||
|
(12, "Don't forget Abinadi, who stood before the king."),
|
||||||
|
(13, "All chained up from head to toe, the gospel he did bring."),
|
||||||
|
(14, "If he would deny it, then the king would set him free."),
|
||||||
|
(15, "He was true, and he died righteously."),
|
||||||
|
|
||||||
|
# Verse 5 — Ammon the missionary
|
||||||
|
(16, "Ammon was a missionary serving Lamanites,"),
|
||||||
|
(17, "Tending King Lamoni's sheep for several days and nights."),
|
||||||
|
(18, "Robbers came; he saved the sheep by fighting fearlessly."),
|
||||||
|
(19, "He had learned he could live righteously."),
|
||||||
|
|
||||||
|
# Verse 6 — 2000 Stripling Warriors
|
||||||
|
# Reordered: warriors (20) -> smiling march (22) -> Christ (21) -> halos (23)
|
||||||
|
(20, "Once two thousand sons of God were called to fight the foe."),
|
||||||
|
(22, "Marching as an army into battle they did go."),
|
||||||
|
(21, "They believed that Christ the Lord their guardian would be."),
|
||||||
|
(23, "They had learned they should live righteously."),
|
||||||
|
|
||||||
|
# Verse 7 — Samuel the Lamanite
|
||||||
|
# Reordered: wall (24) -> preaching (26) -> arrows (25) -> righteous (27)
|
||||||
|
(24, "Samuel the Lamanite, high on the city wall,"),
|
||||||
|
(26, "Came to warn the people, and repentance was his call."),
|
||||||
|
(25, "Arrows could not hit him, for a man of God was he,"),
|
||||||
|
(27, "And he taught in the land righteously."),
|
||||||
|
|
||||||
|
# Verse 8 — Christ's atonement and visit to the Americas
|
||||||
|
# Reordered: crucifixion (28) -> portrait (30) -> children (29) -> people (31)
|
||||||
|
(28, "After Christ was crucified and died for you and me,"),
|
||||||
|
(30, "He came forth to teach the truth to all who would be free."),
|
||||||
|
(29, "Hands were laid upon each child. He blessed them tenderly,"),
|
||||||
|
(31, "And they lived in the land righteously."),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def render_page(page):
|
||||||
|
mat = fitz.Matrix(DPI / 72, DPI / 72)
|
||||||
|
pix = page.get_pixmap(matrix=mat, alpha=False)
|
||||||
|
return io.BytesIO(pix.tobytes("jpeg", jpg_quality=85)), pix.width, pix.height
|
||||||
|
|
||||||
|
|
||||||
|
def add_slide(doc, prs, blank_layout, page_idx, lyric):
|
||||||
|
page = doc[page_idx]
|
||||||
|
img_bytes, pw, ph = render_page(page)
|
||||||
|
slide = prs.slides.add_slide(blank_layout)
|
||||||
|
|
||||||
|
aspect = pw / ph
|
||||||
|
h = SLIDE_H
|
||||||
|
w = int(h * aspect)
|
||||||
|
if w > SLIDE_W:
|
||||||
|
w = SLIDE_W
|
||||||
|
h = int(w / aspect)
|
||||||
|
left = (SLIDE_W - w) // 2
|
||||||
|
top = (SLIDE_H - h) // 2
|
||||||
|
slide.shapes.add_picture(img_bytes, left, top, width=w, height=h)
|
||||||
|
|
||||||
|
tb_top = SLIDE_H - TEXT_BOX_H
|
||||||
|
txBox = slide.shapes.add_textbox(Inches(0.25), tb_top, SLIDE_W - Inches(0.5), TEXT_BOX_H)
|
||||||
|
tf = txBox.text_frame
|
||||||
|
tf.word_wrap = True
|
||||||
|
|
||||||
|
lines = lyric.split("\n")
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
|
||||||
|
p.alignment = PP_ALIGN.CENTER
|
||||||
|
run = p.add_run()
|
||||||
|
run.text = line
|
||||||
|
run.font.bold = True
|
||||||
|
run.font.size = FONT_SIZE
|
||||||
|
run.font.color.rgb = TEXT_COLOR
|
||||||
|
|
||||||
|
print(f" Slide {len(prs.slides):2d} (pg {page_idx:2d}): {lyric[:60]}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
doc = fitz.open(PDF_PATH)
|
||||||
|
prs = Presentation()
|
||||||
|
prs.slide_width = SLIDE_W
|
||||||
|
prs.slide_height = SLIDE_H
|
||||||
|
blank_layout = prs.slide_layouts[6]
|
||||||
|
|
||||||
|
for page_idx, lyric in SLIDES:
|
||||||
|
add_slide(doc, prs, blank_layout, page_idx, lyric)
|
||||||
|
|
||||||
|
prs.save(OUT_PATH)
|
||||||
|
print(f"\nSaved: {OUT_PATH} ({len(SLIDES)} slides)")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
from reportlab.pdfgen import canvas
|
||||||
|
from reportlab.lib.pagesizes import letter
|
||||||
|
from reportlab.lib.units import inch
|
||||||
|
from reportlab.pdfbase.pdfmetrics import stringWidth
|
||||||
|
|
||||||
|
OUT_PATH = r"C:\Users\jhodgkin\Pictures\Dearest Mother I Love You - Initials.pdf"
|
||||||
|
|
||||||
|
LINES = [
|
||||||
|
"Gentle words I hear you say.",
|
||||||
|
"Your kind hands help me each day.",
|
||||||
|
"You're my mother kind and true;",
|
||||||
|
"Dearest mother, I love you.",
|
||||||
|
]
|
||||||
|
|
||||||
|
W, H = letter
|
||||||
|
MARGIN = 0.5 * inch
|
||||||
|
FONT = "Helvetica-Bold"
|
||||||
|
|
||||||
|
def first_letters(line):
|
||||||
|
return " ".join(word[0].upper() for word in line.split())
|
||||||
|
|
||||||
|
def fit_font_size(c, text, max_width, start=200):
|
||||||
|
size = start
|
||||||
|
while size > 1:
|
||||||
|
if stringWidth(text, FONT, size) <= max_width:
|
||||||
|
return size
|
||||||
|
size -= 1
|
||||||
|
return size
|
||||||
|
|
||||||
|
def make_pdf():
|
||||||
|
c = canvas.Canvas(OUT_PATH, pagesize=letter)
|
||||||
|
|
||||||
|
initials = [first_letters(line) for line in LINES]
|
||||||
|
max_w = W - 2 * MARGIN
|
||||||
|
|
||||||
|
# Find a single font size that fits all lines
|
||||||
|
size = min(fit_font_size(c, line, max_w) for line in initials)
|
||||||
|
|
||||||
|
# Distribute lines evenly across the page height
|
||||||
|
usable_h = H - 2 * MARGIN
|
||||||
|
slot = usable_h / len(initials)
|
||||||
|
|
||||||
|
for i, text in enumerate(initials):
|
||||||
|
# Center vertically within each slot
|
||||||
|
y = H - MARGIN - (i + 0.65) * slot
|
||||||
|
c.setFont(FONT, size)
|
||||||
|
c.drawCentredString(W / 2, y, text)
|
||||||
|
|
||||||
|
c.save()
|
||||||
|
print(f"Saved: {OUT_PATH}")
|
||||||
|
print(f"Font size: {size}pt")
|
||||||
|
for line, init in zip(LINES, initials):
|
||||||
|
print(f" {line!r} -> {init!r}")
|
||||||
|
|
||||||
|
make_pdf()
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
from reportlab.pdfgen import canvas
|
||||||
|
from reportlab.lib.pagesizes import letter
|
||||||
|
from reportlab.lib.colors import HexColor
|
||||||
|
from reportlab.lib.units import inch
|
||||||
|
|
||||||
|
OUT_PATH = r"C:\Users\jhodgkin\Pictures\Dearest Mother I Love You.pdf"
|
||||||
|
|
||||||
|
LINES = [
|
||||||
|
"Gentle words I hear you say.",
|
||||||
|
"Your kind hands help me each day.",
|
||||||
|
"You’re my mother kind and true;",
|
||||||
|
"Dearest mother, I love you.",
|
||||||
|
]
|
||||||
|
|
||||||
|
TITLE = "Dearest Mother, I Love You"
|
||||||
|
|
||||||
|
W, H = letter # 8.5 x 11 inches
|
||||||
|
|
||||||
|
PINK = HexColor("#F48FB1")
|
||||||
|
SOFT_PINK = HexColor("#FCE4EC")
|
||||||
|
DEEP_ROSE = HexColor("#C2185B")
|
||||||
|
GOLD = HexColor("#F9A825")
|
||||||
|
|
||||||
|
def draw_flower(c, cx, cy, r=18, petals=6, petal_color=None, center_color=None):
|
||||||
|
import math
|
||||||
|
petal_color = petal_color or PINK
|
||||||
|
center_color = center_color or GOLD
|
||||||
|
for i in range(petals):
|
||||||
|
angle = math.radians(i * 360 / petals)
|
||||||
|
px = cx + math.cos(angle) * r * 1.1
|
||||||
|
py = cy + math.sin(angle) * r * 1.1
|
||||||
|
c.setFillColor(petal_color)
|
||||||
|
c.circle(px, py, r * 0.65, fill=1, stroke=0)
|
||||||
|
c.setFillColor(center_color)
|
||||||
|
c.circle(cx, cy, r * 0.45, fill=1, stroke=0)
|
||||||
|
|
||||||
|
def make_pdf():
|
||||||
|
c = canvas.Canvas(OUT_PATH, pagesize=letter)
|
||||||
|
|
||||||
|
# Corner flowers
|
||||||
|
corners = [
|
||||||
|
(0.65*inch, 0.65*inch),
|
||||||
|
(W - 0.65*inch, 0.65*inch),
|
||||||
|
(0.65*inch, H - 0.65*inch),
|
||||||
|
(W - 0.65*inch, H - 0.65*inch),
|
||||||
|
]
|
||||||
|
for cx, cy in corners:
|
||||||
|
draw_flower(c, cx, cy)
|
||||||
|
|
||||||
|
# Title
|
||||||
|
c.setFillColor(DEEP_ROSE)
|
||||||
|
c.setFont("Helvetica-BoldOblique", 28)
|
||||||
|
c.drawCentredString(W / 2, H - 1.5*inch, TITLE)
|
||||||
|
|
||||||
|
# Divider line under title
|
||||||
|
c.setStrokeColor(PINK)
|
||||||
|
c.setLineWidth(2)
|
||||||
|
c.line(1.2*inch, H - 1.75*inch, W - 1.2*inch, H - 1.75*inch)
|
||||||
|
|
||||||
|
# Lyrics — evenly spaced
|
||||||
|
c.setFillColor(HexColor("#4A148C"))
|
||||||
|
c.setFont("Helvetica-Bold", 26)
|
||||||
|
top = H - 2.4*inch
|
||||||
|
spacing = 1.0*inch
|
||||||
|
for i, line in enumerate(LINES):
|
||||||
|
y = top - i * spacing
|
||||||
|
c.drawCentredString(W / 2, y, line)
|
||||||
|
|
||||||
|
# Small decorative flowers between lines
|
||||||
|
for i in range(len(LINES) - 1):
|
||||||
|
y = top - i * spacing - spacing / 2
|
||||||
|
draw_flower(c, W / 2, y, r=8)
|
||||||
|
|
||||||
|
# Footer
|
||||||
|
c.setFillColor(DEEP_ROSE)
|
||||||
|
c.setFont("Helvetica-Oblique", 14)
|
||||||
|
c.drawCentredString(W / 2, 0.75*inch, "Happy Mother’s Day")
|
||||||
|
|
||||||
|
c.save()
|
||||||
|
print(f"Saved: {OUT_PATH}")
|
||||||
|
|
||||||
|
make_pdf()
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,150 @@
|
|||||||
|
from pptx import Presentation
|
||||||
|
from pptx.util import Inches, Pt
|
||||||
|
from pptx.dml.color import RGBColor
|
||||||
|
from pptx.enum.text import PP_ALIGN
|
||||||
|
from pptx.oxml.ns import qn
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
OUT_PATH = r"C:\Users\jhodgkin\Documents\dev\primary-song-slides\MyCountryTisOfThee.pptx"
|
||||||
|
|
||||||
|
SLIDE_W = Inches(13.333)
|
||||||
|
SLIDE_H = Inches(7.5)
|
||||||
|
|
||||||
|
NAVY = RGBColor(0x0D, 0x27, 0x6D)
|
||||||
|
GOLD = RGBColor(0xF5, 0xC5, 0x18)
|
||||||
|
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
|
||||||
|
|
||||||
|
VERSES = [
|
||||||
|
(
|
||||||
|
"Verse 1",
|
||||||
|
"My country, 'tis of thee,\n"
|
||||||
|
"Sweet land of liberty,\n"
|
||||||
|
"Of thee I sing;\n"
|
||||||
|
"Land where my fathers died,\n"
|
||||||
|
"Land of the pilgrims' pride,\n"
|
||||||
|
"From ev'ry mountainside\n"
|
||||||
|
"Let freedom ring!"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Verse 4",
|
||||||
|
"Our fathers' God, to thee,\n"
|
||||||
|
"Author of liberty,\n"
|
||||||
|
"To thee we sing;\n"
|
||||||
|
"Long may our land be bright\n"
|
||||||
|
"With freedom's holy light.\n"
|
||||||
|
"Protect us by thy might,\n"
|
||||||
|
"Great God, our King!"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def set_background_color(slide, color):
|
||||||
|
"""Fill slide background with a solid color."""
|
||||||
|
bg = slide.background
|
||||||
|
fill = bg.fill
|
||||||
|
fill.solid()
|
||||||
|
fill.fore_color.rgb = color
|
||||||
|
|
||||||
|
|
||||||
|
def add_text_paragraph(tf, text, font_size, color, bold=False, space_before=0):
|
||||||
|
p = tf.add_paragraph()
|
||||||
|
p.alignment = PP_ALIGN.CENTER
|
||||||
|
if space_before:
|
||||||
|
pPr = p._pPr
|
||||||
|
if pPr is None:
|
||||||
|
pPr = p._p.get_or_add_pPr()
|
||||||
|
pPr.set(qn("a:spcBef"), None)
|
||||||
|
spcBef = etree.SubElement(pPr, qn("a:spcBef"))
|
||||||
|
spcPts = etree.SubElement(spcBef, qn("a:spcPts"))
|
||||||
|
spcPts.set("val", str(space_before * 100))
|
||||||
|
run = p.add_run()
|
||||||
|
run.text = text
|
||||||
|
run.font.size = font_size
|
||||||
|
run.font.color.rgb = color
|
||||||
|
run.font.bold = bold
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
def add_slide(prs, blank_layout, verse_label, verse_text):
|
||||||
|
slide = prs.slides.add_slide(blank_layout)
|
||||||
|
set_background_color(slide, NAVY)
|
||||||
|
|
||||||
|
# Gold decorative rule above title
|
||||||
|
rule_top = Inches(0.55)
|
||||||
|
rule_h = Inches(0.06)
|
||||||
|
rule = slide.shapes.add_shape(
|
||||||
|
1, # MSO_SHAPE_TYPE.RECTANGLE
|
||||||
|
Inches(1.5), rule_top, SLIDE_W - Inches(3.0), rule_h
|
||||||
|
)
|
||||||
|
rule.fill.solid()
|
||||||
|
rule.fill.fore_color.rgb = GOLD
|
||||||
|
rule.line.fill.background()
|
||||||
|
|
||||||
|
# Song title
|
||||||
|
title_box = slide.shapes.add_textbox(Inches(0.5), Inches(0.65), SLIDE_W - Inches(1.0), Inches(0.85))
|
||||||
|
tf = title_box.text_frame
|
||||||
|
tf.word_wrap = False
|
||||||
|
p = tf.paragraphs[0]
|
||||||
|
p.alignment = PP_ALIGN.CENTER
|
||||||
|
run = p.add_run()
|
||||||
|
run.text = "My Country, 'Tis of Thee"
|
||||||
|
run.font.size = Pt(36)
|
||||||
|
run.font.color.rgb = GOLD
|
||||||
|
run.font.bold = True
|
||||||
|
|
||||||
|
# Verse label
|
||||||
|
label_box = slide.shapes.add_textbox(Inches(0.5), Inches(1.45), SLIDE_W - Inches(1.0), Inches(0.5))
|
||||||
|
tf2 = label_box.text_frame
|
||||||
|
p2 = tf2.paragraphs[0]
|
||||||
|
p2.alignment = PP_ALIGN.CENTER
|
||||||
|
run2 = p2.add_run()
|
||||||
|
run2.text = verse_label
|
||||||
|
run2.font.size = Pt(22)
|
||||||
|
run2.font.color.rgb = WHITE
|
||||||
|
run2.font.bold = False
|
||||||
|
run2.font.italic = True
|
||||||
|
|
||||||
|
# Gold rule below label
|
||||||
|
rule2 = slide.shapes.add_shape(
|
||||||
|
1,
|
||||||
|
Inches(1.5), Inches(1.95), SLIDE_W - Inches(3.0), rule_h
|
||||||
|
)
|
||||||
|
rule2.fill.solid()
|
||||||
|
rule2.fill.fore_color.rgb = GOLD
|
||||||
|
rule2.line.fill.background()
|
||||||
|
|
||||||
|
# Verse lyrics
|
||||||
|
lyric_box = slide.shapes.add_textbox(Inches(0.5), Inches(2.1), SLIDE_W - Inches(1.0), Inches(5.0))
|
||||||
|
tf3 = lyric_box.text_frame
|
||||||
|
tf3.word_wrap = True
|
||||||
|
lines = verse_text.split("\n")
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if i == 0:
|
||||||
|
p = tf3.paragraphs[0]
|
||||||
|
else:
|
||||||
|
p = tf3.add_paragraph()
|
||||||
|
p.alignment = PP_ALIGN.CENTER
|
||||||
|
run = p.add_run()
|
||||||
|
run.text = line
|
||||||
|
run.font.size = Pt(40)
|
||||||
|
run.font.color.rgb = WHITE
|
||||||
|
run.font.bold = False
|
||||||
|
|
||||||
|
print(f" Added: {verse_label}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
prs = Presentation()
|
||||||
|
prs.slide_width = SLIDE_W
|
||||||
|
prs.slide_height = SLIDE_H
|
||||||
|
blank_layout = prs.slide_layouts[6]
|
||||||
|
|
||||||
|
for label, text in VERSES:
|
||||||
|
add_slide(prs, blank_layout, label, text)
|
||||||
|
|
||||||
|
prs.save(OUT_PATH)
|
||||||
|
print(f"\nSaved: {OUT_PATH} ({len(VERSES)} slides)")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,86 @@
|
|||||||
|
import fitz
|
||||||
|
from pptx import Presentation
|
||||||
|
from pptx.util import Inches, Pt
|
||||||
|
from pptx.dml.color import RGBColor
|
||||||
|
from pptx.enum.text import PP_ALIGN
|
||||||
|
import io
|
||||||
|
|
||||||
|
PDF_PATH = r"C:\Users\jhodgkin\Documents\dev\primary-song-slides\MyFlagMyFlag-Color.pdf"
|
||||||
|
OUT_PATH = r"C:\Users\jhodgkin\Documents\dev\primary-song-slides\MyFlagMyFlag.pptx"
|
||||||
|
DPI = 96
|
||||||
|
|
||||||
|
SLIDE_W = Inches(13.333)
|
||||||
|
SLIDE_H = Inches(7.5)
|
||||||
|
TEXT_BOX_H = Inches(2.1)
|
||||||
|
FONT_SIZE = Pt(44)
|
||||||
|
TEXT_COLOR = RGBColor(0x0D, 0x27, 0x6D) # deep navy blue
|
||||||
|
|
||||||
|
# PDF pages 1-8 (0-based) map to the 8 lyric lines
|
||||||
|
# Page 0 is the overview thumbnail sheet — skip it
|
||||||
|
SLIDES = [
|
||||||
|
(1, "My flag, my flag, my country's flag,"),
|
||||||
|
(2, "I love to see you wave;"),
|
||||||
|
(3, "My flag, my flag, my country's flag,"),
|
||||||
|
(4, "The banner of the brave."),
|
||||||
|
(5, "Wave on, wave on forever,"),
|
||||||
|
(6, "The banner of the free;"),
|
||||||
|
(7, "Wave on, wave on forever,"),
|
||||||
|
(8, "The flag of liberty."),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def render_page(page):
|
||||||
|
mat = fitz.Matrix(DPI / 72, DPI / 72)
|
||||||
|
pix = page.get_pixmap(matrix=mat, alpha=False)
|
||||||
|
return io.BytesIO(pix.tobytes("jpeg", jpg_quality=85)), pix.width, pix.height
|
||||||
|
|
||||||
|
|
||||||
|
def add_slide(doc, prs, blank_layout, page_idx, lyric):
|
||||||
|
page = doc[page_idx]
|
||||||
|
img_bytes, pw, ph = render_page(page)
|
||||||
|
slide = prs.slides.add_slide(blank_layout)
|
||||||
|
|
||||||
|
aspect = pw / ph
|
||||||
|
h = SLIDE_H
|
||||||
|
w = int(h * aspect)
|
||||||
|
if w > SLIDE_W:
|
||||||
|
w = SLIDE_W
|
||||||
|
h = int(w / aspect)
|
||||||
|
left = (SLIDE_W - w) // 2
|
||||||
|
top = (SLIDE_H - h) // 2
|
||||||
|
slide.shapes.add_picture(img_bytes, left, top, width=w, height=h)
|
||||||
|
|
||||||
|
tb_top = SLIDE_H - TEXT_BOX_H
|
||||||
|
txBox = slide.shapes.add_textbox(Inches(0.25), tb_top, SLIDE_W - Inches(0.5), TEXT_BOX_H)
|
||||||
|
tf = txBox.text_frame
|
||||||
|
tf.word_wrap = True
|
||||||
|
|
||||||
|
lines = lyric.split("\n")
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
|
||||||
|
p.alignment = PP_ALIGN.CENTER
|
||||||
|
run = p.add_run()
|
||||||
|
run.text = line
|
||||||
|
run.font.bold = True
|
||||||
|
run.font.size = FONT_SIZE
|
||||||
|
run.font.color.rgb = TEXT_COLOR
|
||||||
|
|
||||||
|
print(f" Slide {len(prs.slides):2d} (pg {page_idx+1:2d}): {lyric[:60]}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
doc = fitz.open(PDF_PATH)
|
||||||
|
prs = Presentation()
|
||||||
|
prs.slide_width = SLIDE_W
|
||||||
|
prs.slide_height = SLIDE_H
|
||||||
|
blank_layout = prs.slide_layouts[6]
|
||||||
|
|
||||||
|
for page_idx, lyric in SLIDES:
|
||||||
|
add_slide(doc, prs, blank_layout, page_idx, lyric)
|
||||||
|
|
||||||
|
prs.save(OUT_PATH)
|
||||||
|
print(f"\nSaved: {OUT_PATH} ({len(SLIDES)} slides)")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Binary file not shown.
@@ -0,0 +1,124 @@
|
|||||||
|
import fitz
|
||||||
|
from pptx import Presentation
|
||||||
|
from pptx.util import Inches, Pt
|
||||||
|
from pptx.dml.color import RGBColor
|
||||||
|
from pptx.enum.text import PP_ALIGN
|
||||||
|
import io
|
||||||
|
|
||||||
|
PDF_PATH = r"C:\Users\jhodgkin\Documents\dev\primary-song-slides\EDITABLEMyOwnSacredGrove.pdf"
|
||||||
|
OUT_PATH = r"C:\Users\jhodgkin\Documents\dev\primary-song-slides\MyOwnSacredGrove.pptx"
|
||||||
|
DPI = 96
|
||||||
|
|
||||||
|
SLIDE_W = Inches(13.333) # 16:9 widescreen
|
||||||
|
SLIDE_H = Inches(7.5)
|
||||||
|
TEXT_BOX_H = Inches(2.1)
|
||||||
|
FONT_SIZE = Pt(44)
|
||||||
|
TEXT_COLOR = RGBColor(0x1B, 0x43, 0x32) # deep forest green
|
||||||
|
|
||||||
|
# (0-based PDF page index, lyric line)
|
||||||
|
# Pages: 0=boy→grove, 1=boy reading+praying, 2=boy praying+pillar of light,
|
||||||
|
# 3=HF+Jesus plain, 4=HF+Jesus in clouds, 5=two kids praying by bed (warm),
|
||||||
|
# 6=child with headphones/globe, 7=two kids praying+HF in clouds,
|
||||||
|
# 8=two kids praying by bed (same scene), 9=confused boy+arrows,
|
||||||
|
# 10=boy at fork in road, 11=wheelchair child thinking of Jesus,
|
||||||
|
# 12=Black child praying+golden light, 13=girl reading+boy pondering+stars,
|
||||||
|
# 14=girl praying+HF in clouds, 15=boy with clock, 16=three children,
|
||||||
|
# 17=girl holding light+stars, 18=girl by window, 19=two kids praying in grove
|
||||||
|
SLIDES = [
|
||||||
|
# Verse 1
|
||||||
|
(0, "Joseph Smith went to a grove full of trees"),
|
||||||
|
(1, "Seeking God's wisdom, he fell to his knees"),
|
||||||
|
(2, "As he pled with the heavens, the sky filled with light"),
|
||||||
|
(3, "And the Father appeared with His Son Jesus Christ"),
|
||||||
|
(4, "Standing above in the air\nComing to answer his prayer"),
|
||||||
|
# Chorus
|
||||||
|
(19, "I will find my own sacred grove"),
|
||||||
|
(6, "Away from all of the noise of the world"),
|
||||||
|
(8, "I will turn to prayer"),
|
||||||
|
(7, "For I know He's there"),
|
||||||
|
(19, "I will find my own sacred grove"),
|
||||||
|
# Verse 2
|
||||||
|
(9, "So many choices with so much at stake"),
|
||||||
|
(10, "Life's full of pathways, but which should I take?"),
|
||||||
|
(12, "If I lift up in prayer in the name of the Son"),
|
||||||
|
(13, "Through the power of the Holy Ghost, answers will come"),
|
||||||
|
(14, "Heavenly Father is there\nReady to answer my prayer"),
|
||||||
|
# Chorus
|
||||||
|
(19, "I will find my own sacred grove"),
|
||||||
|
(6, "Away from all of the noise of the world"),
|
||||||
|
(5, "I will turn to prayer"),
|
||||||
|
(7, "For I know He's there"),
|
||||||
|
(19, "I will find my own sacred grove"),
|
||||||
|
# Bridge
|
||||||
|
(15, "Sometimes the answers take time"),
|
||||||
|
(16, "So I'll listen in heart and mind"),
|
||||||
|
(17, "Revelation will come my way"),
|
||||||
|
(18, "As I wait patiently in faith"),
|
||||||
|
# Chorus
|
||||||
|
(19, "I will find my own sacred grove"),
|
||||||
|
(6, "Away from all of the noise of the world"),
|
||||||
|
(8, "I will turn to prayer"),
|
||||||
|
(7, "For I know He's there"),
|
||||||
|
(19, "I will find my own sacred grove"),
|
||||||
|
# Ending
|
||||||
|
(11, "I will find my own sacred grove"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def render_page(page):
|
||||||
|
mat = fitz.Matrix(DPI / 72, DPI / 72)
|
||||||
|
pix = page.get_pixmap(matrix=mat, alpha=False)
|
||||||
|
return io.BytesIO(pix.tobytes("jpeg", jpg_quality=85)), pix.width, pix.height
|
||||||
|
|
||||||
|
|
||||||
|
def add_slide(doc, prs, blank_layout, page_idx, lyric):
|
||||||
|
page = doc[page_idx]
|
||||||
|
img_bytes, pw, ph = render_page(page)
|
||||||
|
slide = prs.slides.add_slide(blank_layout)
|
||||||
|
|
||||||
|
# Scale image to fit within slide, centered
|
||||||
|
aspect = pw / ph
|
||||||
|
h = SLIDE_H
|
||||||
|
w = int(h * aspect)
|
||||||
|
if w > SLIDE_W:
|
||||||
|
w = SLIDE_W
|
||||||
|
h = int(w / aspect)
|
||||||
|
left = (SLIDE_W - w) // 2
|
||||||
|
top = (SLIDE_H - h) // 2
|
||||||
|
slide.shapes.add_picture(img_bytes, left, top, width=w, height=h)
|
||||||
|
|
||||||
|
# Lyric text box at the bottom of the slide
|
||||||
|
tb_top = SLIDE_H - TEXT_BOX_H
|
||||||
|
txBox = slide.shapes.add_textbox(Inches(0.25), tb_top, SLIDE_W - Inches(0.5), TEXT_BOX_H)
|
||||||
|
tf = txBox.text_frame
|
||||||
|
tf.word_wrap = True
|
||||||
|
|
||||||
|
lines = lyric.split("\n")
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
|
||||||
|
p.alignment = PP_ALIGN.CENTER
|
||||||
|
run = p.add_run()
|
||||||
|
run.text = line
|
||||||
|
run.font.bold = True
|
||||||
|
run.font.size = FONT_SIZE
|
||||||
|
run.font.color.rgb = TEXT_COLOR
|
||||||
|
|
||||||
|
print(f" Slide {len(prs.slides):2d} (pg {page_idx+1:2d}): {lyric[:55]}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
doc = fitz.open(PDF_PATH)
|
||||||
|
prs = Presentation()
|
||||||
|
prs.slide_width = SLIDE_W
|
||||||
|
prs.slide_height = SLIDE_H
|
||||||
|
blank_layout = prs.slide_layouts[6]
|
||||||
|
|
||||||
|
for page_idx, lyric in SLIDES:
|
||||||
|
add_slide(doc, prs, blank_layout, page_idx, lyric)
|
||||||
|
|
||||||
|
prs.save(OUT_PATH)
|
||||||
|
print(f"\nSaved: {OUT_PATH} ({len(SLIDES)} slides)")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user