Turns the pastor's sermon notes into the message presentation in ProPresenter's Messages library, built from the series or generic template. - Notes (RTF/Word) are parsed into title, scriptures (with bold emphasis), points, builds, quotes, picture slides and the Sermon in a Sentence; team instructions are left off and shown in the summary - Slides are cloned from the template with fresh ids and the template's own formatting; a dependency-free protobuf codec reads/writes .pro files - Text is checked to fit on the slide and in the stream and LED wall theme boxes; long scriptures split at verse numbers, font sizes never change - Links the presentation into the Planning Center playlist's Message section (restarting ProPresenter safely, never while anything is live) - Picks the newest notes from the email inbox folder (Google Apps Script), reads the speaker from the email, and writes the YouTube title and description with the Claude API for pasting into Restream - Offline tests rebuild last Sunday's deck from its notes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
47 lines
2.3 KiB
Python
47 lines
2.3 KiB
Python
"""Generate a deck from notes + template (measuring on the ProP Mac) and print its slides."""
|
|
import os, sys, time
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, os.path.join(HERE, "..", "src")); sys.path.insert(0, os.path.join(HERE, "..", "tools"))
|
|
import deck, notes, rtf, remote_measure, themes
|
|
|
|
def slides_of(data):
|
|
p = deck.C.decode(data, "rv.data.Presentation")
|
|
cues = {deck.uuid_str(deck.C.get(c, "uuid")): c for c in deck.C.all(p, "cues")}
|
|
out = []
|
|
for g in deck.C.all(p, "cue_groups"):
|
|
for cid in deck.C.all(g, "cue_identifiers"):
|
|
c = cues[deck.uuid_str(cid)]
|
|
a = deck.slide_action(c)
|
|
texts = []
|
|
for el in deck.slide_elements(a):
|
|
t = deck.element_text(el)
|
|
d = deck.C.get(t, "rtf_data") if t is not None else None
|
|
if d:
|
|
doc = rtf.read(d)
|
|
s = " / ".join("".join(("<b>%s</b>" % r.text) if ("b" in r.state) else r.text for r in para.runs) for para in doc.paras)
|
|
if s.strip():
|
|
texts.append("%s: %s" % (deck.element_name(el), s))
|
|
n = deck.C.path(a, "slide", "presentation", "notes")
|
|
nt = " / ".join(pp.text for pp in rtf.read(deck.C.get(n, "rtf_data")).paras) if n is not None else ""
|
|
out.append((deck.action_label(a), texts, nt))
|
|
return out
|
|
|
|
if __name__ == "__main__":
|
|
notes_path, tpl_path, out_path = sys.argv[1:4]
|
|
tpl = deck.Template(open(tpl_path, "rb").read(), tpl_path)
|
|
plan = notes.parse(open(notes_path, "rb").read())
|
|
t0 = time.time()
|
|
outs = themes.outputs_for(tpl, "http://10.1.14.225:1025", os.environ.get("THEMES_DIR", ""))
|
|
print("outputs:", outs)
|
|
layout = deck.fit(tpl, plan, remote_measure.measure, outs)
|
|
print("fit took %.1fs" % (time.time() - t0))
|
|
role, who = tpl.speaker()
|
|
sp = deck.speaker_rtfs(tpl, role, who)
|
|
spres = remote_measure.measure([{"rtf": d, "w": st.w} for nm, st, d in sp])
|
|
data = deck.build(tpl, plan, os.path.splitext(os.path.basename(out_path))[0], (role, who), layout,
|
|
{nm: r["runs"] for (nm, st, d), r in zip(sp, spres)})
|
|
open(out_path, "wb").write(data)
|
|
for k, s in enumerate(plan.slides):
|
|
if s.flags: print("FLAG slide %d (%s): %s" % (k + 1, s.kind, "; ".join(s.flags)))
|
|
print("wrote", out_path, len(data))
|