Message Notes -> ProPresenter tool (v1.1)
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>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
dist/
|
||||
build/
|
||||
__pycache__/
|
||||
tests/out/
|
||||
.DS_Store
|
||||
@@ -0,0 +1,100 @@
|
||||
# Message Notes → ProPresenter
|
||||
|
||||
Turns the pastor's sermon notes (`Slides - <message>.rtf`, or a Word file) into the message
|
||||
presentation in ProPresenter's **Messages** library, built from the series or generic template.
|
||||
These are the same slides you'd otherwise make by copy and paste.
|
||||
|
||||
## Using it (on the ProPresenter Mac)
|
||||
|
||||
Double-click **Message Notes to ProPresenter.command** on the Desktop. It works everything out:
|
||||
|
||||
- **Notes**: the newest `Slides - ….rtf`. The email script saves these into Google Drive >
|
||||
My Drive > **Message Notes**; it also checks Downloads, Desktop and Documents.
|
||||
- **Speaker**: from the email ("Pastor Mehdi will be preaching live from NA…"), matched to the
|
||||
speakers in your library; otherwise the template's speaker.
|
||||
- **Template**: series when the title matches the series template's sample title, else generic.
|
||||
- **Date and playlist**: the Planning Center playlist for this message, e.g. "Where is Your Faith? -
|
||||
Part 8 - October 18, 2026". Its date gives the name: `261018 Where Is Your Faith Part 8`.
|
||||
- **YouTube**: Claude writes the title and description in the house style.
|
||||
|
||||
It shows one short summary, plus notes only where something changed or needs a look: a scripture
|
||||
split over two slides, text that is too long somewhere, picture slides, lines left off the slides.
|
||||
**Font sizes are never changed**; if text is too long, the note says what size would fit.
|
||||
|
||||
**Enter** then does it all:
|
||||
1. Saves the presentation in the Messages library.
|
||||
2. Links it into the playlist's **Message** section (Planning Center's "Resi Message" item).
|
||||
ProPresenter only reads playlists when it starts, so the tool quits ProPresenter, adds the link,
|
||||
and reopens the same version, about 30 s. It won't do that on a Sunday morning or while a slide
|
||||
is on screen, or while the Message timer runs; it tells you to drag it in instead. If the
|
||||
playlist isn't imported yet, it asks you to import it from Planning Center first. Each change
|
||||
backs the playlist file up to `…/MessageNotes/backups` (last 20 kept).
|
||||
3. Opens the YouTube title and description in TextEdit, ready to paste into Restream. They're
|
||||
saved as `…/MessageNotes/youtube-<name>.txt`.
|
||||
|
||||
Keys at the summary: `e` edit the slides in TextEdit · `t` `s` `n` `d` `f` change template /
|
||||
speaker / name / date / notes file · `v` view the description · `y` setup · `q` quit.
|
||||
|
||||
**One-time setup (`y`)**, done at the ProPresenter Mac:
|
||||
- **Claude API key** (console.anthropic.com → API keys). It goes straight into the Keychain.
|
||||
- **Email pickup**: paste `apps-script/SaveSermonNotes.gs` into script.google.com as
|
||||
media@waterschurch.org, run `install`, Allow. It checks every 10 minutes and saves the notes and
|
||||
the email text into Drive > Message Notes.
|
||||
- The first restart asks "Terminal wants to control ProPresenter". Click OK.
|
||||
|
||||
PICTURE / CLEAR PICTURE slides are always kept. When the notes mark a picture ("[PICTURE: …]",
|
||||
"<pic - ….jpg>"), they go there with the picture named in the slide notes.
|
||||
|
||||
## How notes become slides
|
||||
|
||||
| In the notes | On the slides |
|
||||
|---|---|
|
||||
| first line / second line / bare reference | **Title** slide (title, subtitle, main passage) |
|
||||
| paragraph starting with a reference (`John 1:12 ESV …`) | **SCRIPTURE**. Bold words stay bold. |
|
||||
| a line on its own | **Point** |
|
||||
| lead-in (`…:` / `…?` / italic) + one line | one **Point**, lead-in in the italic style |
|
||||
| 3+ lines together, a list, or after "(one at a time)" | **\*STREAM DECK\*** slide + **Build** (one step per line) |
|
||||
| `Sermon in a Sentence:` + sentence | **SERMON IN A SENTENCE** |
|
||||
| `(…)`, `[…]`, `// …`, coloured non-scripture notes | left off, shown in the review |
|
||||
|
||||
Formatting always comes from the template's own sample slides. To change how something looks,
|
||||
change the template in ProPresenter and the tool follows. Templates are found by name: any
|
||||
`_TEMPLATE … DO NOT DELETE …` presentation in Messages, with `GENERIC` marking the generic one.
|
||||
|
||||
**Fitting.** Every text is laid out with macOS's text system (the one ProPresenter uses) in each
|
||||
place it will be seen:
|
||||
- the slide itself
|
||||
- each themed screen of the Look the template's SPEAKER NAME slide triggers (today *Message*:
|
||||
**Stream Lower Thirds** on the stream, **Center LED Wall** on the LED wall)
|
||||
|
||||
On themed screens ProPresenter uses the theme's font size, and only words styled differently
|
||||
from the rest of their box keep bold/italic.
|
||||
- Scriptures too long anywhere are split at verse numbers into as few, even slides as possible,
|
||||
all with the same reference.
|
||||
- Other text too big for the slide is reduced in 1pt steps.
|
||||
- Text too long for a theme box is flagged, since the theme fixes the size there.
|
||||
|
||||
## Files
|
||||
|
||||
- `src/`: the program (pure Python 3.9, no installs)
|
||||
- `notes.py` reads the notes and decides the slides
|
||||
- `deck.py` copies template slides and fills them in
|
||||
- `themes.py` finds the stream and LED wall theme boxes
|
||||
- `measure.py` does the text layout
|
||||
- `rtf.py`, `pbwire.py` + `schema.json` read and write the RTF and ProPresenter files
|
||||
- `plantext.py` is the TextEdit format
|
||||
- `playlist.py` + `ppctl.py` handle the playlist link and the safe ProPresenter restart
|
||||
- `youtube.py` writes the YouTube text (Claude API over HTTPS); `secrets_store.py` keeps the key in the Keychain
|
||||
- `docxnum.py` keeps Word list numbers
|
||||
- `app.py` is the Terminal app
|
||||
- `apps-script/SaveSermonNotes.gs` is the Google Apps Script for email pickup.
|
||||
- `tools/build.sh` builds `dist/message-notes.pyz` and the launcher. `tools/deploy.sh` builds
|
||||
and installs both on the ProPresenter Mac, via the ingest Mac's SSH key.
|
||||
- `tools/gen_schema.py` regenerates `schema.json` from the community ProPresenter `.proto` files
|
||||
(github.com/greyshirtguy/ProPresenter7-Proto, `autogen-proto`) after a ProPresenter upgrade.
|
||||
- `tests/run_tests.py` runs offline checks against real notes and presentations. Last Sunday's
|
||||
notes must rebuild last Sunday's deck. `tests/gen.py` builds a deck from a laptop, measuring
|
||||
on the ProPresenter Mac.
|
||||
|
||||
On the ProPresenter Mac: app in `~/Library/Application Support/MessageNotes/`, log in
|
||||
`~/Library/Logs/message-notes.log`, last preview page in `…/MessageNotes/last-run/index.html`.
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Message Notes - saves the sermon notes from email into Google Drive.
|
||||
*
|
||||
* Runs every 10 minutes in the Google account it is installed in (media@waterschurch.org).
|
||||
* When a "Notes/Slides - ..." email arrives with notes attached, it saves the attachment(s)
|
||||
* plus the email text (used to know who is preaching) into the Drive folder "Message Notes".
|
||||
* Google Drive on the ProPresenter Mac syncs that folder, and the Message Notes tool picks
|
||||
* up the newest file from it.
|
||||
*
|
||||
* Setup: paste this into a new project at https://script.google.com (signed in as media@),
|
||||
* save, choose the function "install" and click Run, then click Allow.
|
||||
*/
|
||||
var FOLDER_NAME = 'Message Notes';
|
||||
var QUERY = '(subject:"Notes/Slides" OR to:sermonslides@waterschurch.org) has:attachment newer_than:21d';
|
||||
var KEEP = 300; // how many processed message ids to remember
|
||||
|
||||
function saveSermonNotes() {
|
||||
var props = PropertiesService.getScriptProperties();
|
||||
var done = JSON.parse(props.getProperty('done') || '[]');
|
||||
var folder = getFolder_();
|
||||
var threads = GmailApp.search(QUERY, 0, 30);
|
||||
threads.forEach(function (thread) {
|
||||
thread.getMessages().forEach(function (msg) {
|
||||
var id = msg.getId();
|
||||
if (done.indexOf(id) >= 0) return;
|
||||
var saved = false;
|
||||
msg.getAttachments({includeInlineImages: false}).forEach(function (att) {
|
||||
var name = att.getName();
|
||||
if (!/\.(rtf|docx?)$/i.test(name)) return;
|
||||
replaceFile_(folder, name, att.copyBlob());
|
||||
var base = name.replace(/\.[^.]+$/, '');
|
||||
var info = 'Subject: ' + msg.getSubject() + '\nFrom: ' + msg.getFrom() + '\nDate: ' +
|
||||
msg.getDate().toISOString() + '\n\n' + msg.getPlainBody();
|
||||
replaceFile_(folder, base + '.email.txt', Utilities.newBlob(info, 'text/plain', base + '.email.txt'));
|
||||
saved = true;
|
||||
});
|
||||
done.push(id);
|
||||
});
|
||||
});
|
||||
props.setProperty('done', JSON.stringify(done.slice(-KEEP)));
|
||||
}
|
||||
|
||||
function getFolder_() {
|
||||
var it = DriveApp.getRootFolder().getFoldersByName(FOLDER_NAME);
|
||||
return it.hasNext() ? it.next() : DriveApp.getRootFolder().createFolder(FOLDER_NAME);
|
||||
}
|
||||
|
||||
function replaceFile_(folder, name, blob) {
|
||||
var old = folder.getFilesByName(name);
|
||||
while (old.hasNext()) old.next().setTrashed(true);
|
||||
folder.createFile(blob.setName(name));
|
||||
}
|
||||
|
||||
/** Run once: saves anything recent now and checks every 10 minutes from then on. */
|
||||
function install() {
|
||||
ScriptApp.getProjectTriggers().forEach(function (t) {
|
||||
if (t.getHandlerFunction() === 'saveSermonNotes') ScriptApp.deleteTrigger(t);
|
||||
});
|
||||
ScriptApp.newTrigger('saveSermonNotes').timeBased().everyMinutes(10).create();
|
||||
saveSermonNotes();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import sys
|
||||
|
||||
import app
|
||||
|
||||
sys.exit(app.main())
|
||||
+798
@@ -0,0 +1,798 @@
|
||||
"""Message Notes -> ProPresenter.
|
||||
|
||||
Run on the ProPresenter Mac. Picks up the newest sermon notes, builds the message
|
||||
presentation from the series or generic template, links it into the service playlist's
|
||||
Message section, and (when set up) updates the stream title and description. Shows one
|
||||
short summary first; nothing is written until Enter.
|
||||
"""
|
||||
import argparse
|
||||
import datetime
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
import deck
|
||||
import docxnum
|
||||
import measure as measure_mod
|
||||
import notes as notesmod
|
||||
import plantext
|
||||
import playlist as playlist_mod
|
||||
import ppctl
|
||||
import themes
|
||||
import youtube
|
||||
|
||||
VERSION = "1.1.0"
|
||||
LIBRARY_CANDIDATES = ["/Volumes/MEDIA/ProPresenter/Libraries/Messages",
|
||||
"~/Documents/ProPresenter/Libraries/Messages"]
|
||||
API = "http://127.0.0.1:1025"
|
||||
SUPPORT = os.path.expanduser("~/Library/Application Support/MessageNotes")
|
||||
LOG = os.path.expanduser("~/Library/Logs/message-notes.log")
|
||||
# where the email script saves the notes (checked first), then the usual places
|
||||
NOTES_INBOX = ["~/Library/CloudStorage/GoogleDrive-media@waterschurch.org/My Drive/Message Notes",
|
||||
"~/Library/CloudStorage/GoogleDrive-christian@waterschurch.org/My Drive/Message Notes"]
|
||||
NOTE_DIRS = ["~/Downloads", "~/Desktop", "~/Documents",
|
||||
"~/Library/Mobile Documents/com~apple~TextEdit/Documents",
|
||||
"~/Library/CloudStorage/*/My Drive"]
|
||||
NOTE_EXT = (".rtf", ".rtfd", ".docx", ".doc", ".odt")
|
||||
PLAYLIST_BACKUPS = 20
|
||||
|
||||
TTY = sys.stdout.isatty()
|
||||
B = "\033[1m" if TTY else ""
|
||||
DIM = "\033[2m" if TTY else ""
|
||||
GRN = "\033[32m" if TTY else ""
|
||||
YEL = "\033[33m" if TTY else ""
|
||||
RED = "\033[31m" if TTY else ""
|
||||
R = "\033[0m" if TTY else ""
|
||||
|
||||
KIND_LABEL = {"title": "Title", "point": "Point", "subpoint": "Subpoint", "build": "Build",
|
||||
"scripture": "Scripture", "quote": "Quote", "sentence": "Sermon in a Sentence",
|
||||
"picture": "Picture", "clearpicture": "Clear Picture"}
|
||||
|
||||
|
||||
class Cancel(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def say(msg=""):
|
||||
print(msg)
|
||||
|
||||
|
||||
def log(msg):
|
||||
try:
|
||||
os.makedirs(os.path.dirname(LOG), exist_ok=True)
|
||||
with open(LOG, "a") as f:
|
||||
f.write("%s %s\n" % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), msg))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def ask(prompt, default=None):
|
||||
shown = " [%s]" % default if default not in (None, "") else ""
|
||||
try:
|
||||
ans = input("%s%s: " % (prompt, shown)).strip()
|
||||
except EOFError:
|
||||
raise Cancel()
|
||||
if ans.lower() in ("q", "quit", "cancel"):
|
||||
raise Cancel()
|
||||
return ans if ans else (default if default is not None else "")
|
||||
|
||||
|
||||
def width():
|
||||
return max(60, min(shutil.get_terminal_size((100, 30)).columns, 160))
|
||||
|
||||
|
||||
def wrap(text, w, indent):
|
||||
out, line = [], ""
|
||||
for word in text.split(" "):
|
||||
if line and len(line) + 1 + len(word) > w:
|
||||
out.append(line)
|
||||
line = word
|
||||
else:
|
||||
line = (line + " " + word) if line else word
|
||||
out.append(line)
|
||||
return ("\n" + " " * indent).join(out)
|
||||
|
||||
|
||||
def short(text, n):
|
||||
text = re.sub(r"\s+", " ", text).strip()
|
||||
return text if len(text) <= n else text[:n - 1] + "\u2026"
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------- environment
|
||||
|
||||
def find_library(explicit=None):
|
||||
for c in ([explicit] if explicit else LIBRARY_CANDIDATES):
|
||||
p = os.path.expanduser(c)
|
||||
if os.path.isdir(p):
|
||||
return p
|
||||
for p in glob.glob("/Volumes/*/ProPresenter/Libraries/Messages"):
|
||||
return p
|
||||
return None
|
||||
|
||||
|
||||
def find_templates(library):
|
||||
out = []
|
||||
for f in sorted(os.listdir(library)):
|
||||
if f.lower().endswith(".pro") and not f.startswith("._") and re.match(r"^_?template", f, re.I) \
|
||||
and "do not delete" in f.lower():
|
||||
out.append(os.path.join(library, f))
|
||||
series = sorted([p for p in out if "generic" not in os.path.basename(p).lower()], key=os.path.getmtime, reverse=True)
|
||||
generic = sorted([p for p in out if "generic" in os.path.basename(p).lower()], key=os.path.getmtime, reverse=True)
|
||||
return series, generic
|
||||
|
||||
|
||||
def recent_notes(limit=8, days=21):
|
||||
"""Newest notes files. A copy that came from the email inbox wins over the same file
|
||||
elsewhere (it carries the email, which says who is preaching); newest email/file first."""
|
||||
best = {}
|
||||
cutoff = time.time() - days * 86400
|
||||
for rank, dirs in enumerate((NOTES_INBOX, NOTE_DIRS)):
|
||||
for d in dirs:
|
||||
for folder in glob.glob(os.path.expanduser(d)):
|
||||
try:
|
||||
names = os.listdir(folder)
|
||||
except OSError:
|
||||
continue
|
||||
for n in names:
|
||||
p = os.path.join(folder, n)
|
||||
if n.startswith(".") or not n.lower().endswith(NOTE_EXT):
|
||||
continue
|
||||
if rank and not re.search(r"slides|notes|message|sermon", n, re.I):
|
||||
continue
|
||||
try:
|
||||
m = email_date(p) or os.path.getmtime(p)
|
||||
except OSError:
|
||||
continue
|
||||
if m < cutoff:
|
||||
continue
|
||||
key = n.lower()
|
||||
if key not in best or rank < best[key][0]:
|
||||
best[key] = (rank, m, p)
|
||||
found = sorted(best.values(), key=lambda x: (0 if re.match(r"slides", os.path.basename(x[2]), re.I) else 1, -x[1]))
|
||||
return [p for _, _, p in found[:limit]]
|
||||
|
||||
|
||||
def read_notes(path):
|
||||
"""Notes file -> RTF bytes (converting Word with macOS textutil)."""
|
||||
low = path.lower()
|
||||
if low.endswith(".rtf"):
|
||||
with open(path, "rb") as f:
|
||||
return f.read()
|
||||
src, tmpdir = path, None
|
||||
if low.endswith(".docx"):
|
||||
tmpdir = tempfile.mkdtemp(prefix="msgnotes-")
|
||||
src = os.path.join(tmpdir, "notes.docx")
|
||||
try:
|
||||
docxnum.number_lists(path, src)
|
||||
except Exception:
|
||||
src = path
|
||||
try:
|
||||
p = subprocess.run(["/usr/bin/textutil", "-convert", "rtf", "-stdout", src], capture_output=True, timeout=60)
|
||||
finally:
|
||||
if tmpdir:
|
||||
shutil.rmtree(tmpdir, ignore_errors=True)
|
||||
if p.returncode != 0 or not p.stdout:
|
||||
raise SystemExit("Could not read %s (%s)" % (path, p.stderr.decode(errors="replace").strip()))
|
||||
return p.stdout
|
||||
|
||||
|
||||
def email_text(notes_path):
|
||||
"""The email the notes came with, saved next to them by the email script (if any)."""
|
||||
for cand in (os.path.splitext(notes_path)[0] + ".email.txt",):
|
||||
try:
|
||||
with open(cand, encoding="utf-8", errors="replace") as f:
|
||||
return f.read()
|
||||
except OSError:
|
||||
pass
|
||||
return ""
|
||||
|
||||
|
||||
def scan_speakers(library, limit=80):
|
||||
"""Recent (role, name) pairs from the SPEAKER NAME slides in the library, newest first."""
|
||||
files = [os.path.join(library, f) for f in os.listdir(library)
|
||||
if f.lower().endswith(".pro") and not f.startswith(("._", "_"))]
|
||||
files.sort(key=lambda p: os.path.getmtime(p), reverse=True)
|
||||
seen, out = set(), []
|
||||
for p in files[:limit]:
|
||||
try:
|
||||
with open(p, "rb") as f:
|
||||
data = f.read()
|
||||
if b"Preacher" not in data:
|
||||
continue
|
||||
pres = deck.C.decode(data, "rv.data.Presentation")
|
||||
for c in deck.C.all(pres, "cues"):
|
||||
if deck.classify(c) == "speaker":
|
||||
role, name = deck.speaker_of(c)
|
||||
key = (role.strip().upper(), name.strip().upper())
|
||||
if name.strip() and key not in seen:
|
||||
seen.add(key)
|
||||
out.append((role.strip(), name.strip()))
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
return out
|
||||
|
||||
|
||||
SPEAK_RE = re.compile(r"\b(preach\w*|bring\w*|speak\w*|shar\w*|teach\w*|deliver\w*|message)\b", re.I)
|
||||
|
||||
|
||||
def speaker_from_email(text, known):
|
||||
"""'Pastor Mehdi will be preaching live from NA ...' -> ('LOCATION PASTOR', 'Mehdi Chniouli')"""
|
||||
if not text:
|
||||
return None
|
||||
body = text.split("\n\n", 1)[1] if text.startswith("Subject:") and "\n\n" in text else text
|
||||
body = re.sub(r"[*_]", "", re.sub(r"\s+", " ", body)) # emails wrap lines mid-sentence
|
||||
best = None
|
||||
for m in re.finditer(r"(?:Pastor|Pr\.?|Rev\.?|Bishop|Elder)\s+([A-Z][\w'\-]+)(?:\s+([A-Z][\w'\-]+))?([^.!?]*)", body):
|
||||
first, last, rest = m.group(1), m.group(2) or "", m.group(3)
|
||||
if last and SPEAK_RE.match(last):
|
||||
last = ""
|
||||
if not SPEAK_RE.search(rest):
|
||||
continue
|
||||
here = re.search(r"\b(NA|North Attleboro|all locations|all other)\b", rest, re.I) is not None
|
||||
score = 2 if here else 1
|
||||
if best is None or score > best[0]:
|
||||
best = (score, first, last)
|
||||
if not best:
|
||||
return None
|
||||
_, first, last = best
|
||||
for role, name in known:
|
||||
parts = name.split()
|
||||
if parts and parts[0].lower() == first.lower() and (not last or last.lower() in name.lower()):
|
||||
return role, name
|
||||
return ("PASTOR", ("%s %s" % (first, last)).strip().upper())
|
||||
|
||||
|
||||
def email_date(notes_path):
|
||||
"""When the notes email arrived (from the saved email text), else None."""
|
||||
m = re.search(r"^Date: (\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})", email_text(notes_path), re.M)
|
||||
if not m:
|
||||
return None
|
||||
try:
|
||||
return (datetime.datetime.strptime(m.group(1), "%Y-%m-%dT%H:%M:%S")
|
||||
.replace(tzinfo=datetime.timezone.utc).timestamp())
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def api_json(path, timeout=5):
|
||||
with urllib.request.urlopen(API + path, timeout=timeout) as r:
|
||||
return json.loads(r.read().decode("utf-8"))
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------- choices
|
||||
|
||||
def next_service_date(today=None):
|
||||
today = today or datetime.date.today()
|
||||
return today + datetime.timedelta(days=(6 - today.weekday()) % 7)
|
||||
|
||||
|
||||
def parse_date(text, default):
|
||||
t = text.strip().lower()
|
||||
if not t:
|
||||
return default
|
||||
today = datetime.date.today()
|
||||
if t == "today":
|
||||
return today
|
||||
if t == "tomorrow":
|
||||
return today + datetime.timedelta(days=1)
|
||||
for k, d in enumerate(["mon", "tue", "wed", "thu", "fri", "sat", "sun"]):
|
||||
if t.startswith(d):
|
||||
return today + datetime.timedelta(days=(k - today.weekday()) % 7)
|
||||
m = re.match(r"^(\d{2})(\d{2})(\d{2})$", t)
|
||||
if m:
|
||||
return datetime.date(2000 + int(m.group(1)), int(m.group(2)), int(m.group(3)))
|
||||
m = re.match(r"^(\d{2})(\d{2})$", t) or re.match(r"^(\d{1,2})[/.-](\d{1,2})$", t)
|
||||
if m:
|
||||
d = datetime.date(today.year, int(m.group(1)), int(m.group(2)))
|
||||
if d < today - datetime.timedelta(days=60):
|
||||
d = datetime.date(today.year + 1, d.month, d.day)
|
||||
return d
|
||||
raise ValueError(text)
|
||||
|
||||
|
||||
def default_name(date, title):
|
||||
t = re.sub(r"\s+[-\u2013\u2014]\s+", " ", title.strip())
|
||||
t = re.sub(r"[/:\\]", "-", t)
|
||||
return "%s %s" % (date.strftime("%y%m%d"), t)
|
||||
|
||||
|
||||
def unique_name(library, name):
|
||||
existing = {f[:-4].lower() for f in os.listdir(library) if f.lower().endswith(".pro")}
|
||||
if name.lower() not in existing:
|
||||
return name
|
||||
k = 2
|
||||
while ("%s %d" % (name, k)).lower() in existing:
|
||||
k += 1
|
||||
return "%s %d" % (name, k)
|
||||
|
||||
|
||||
def series_name(sample_title):
|
||||
return re.split(r"\s+[-\u2013\u2014]\s+|\s+part\b|\s+pt\.?\s*\d", sample_title, flags=re.I)[0].strip().lower()
|
||||
|
||||
|
||||
def default_template(series, generic, title):
|
||||
for p in series:
|
||||
try:
|
||||
sname = series_name(deck.Template(open(p, "rb").read(), p).sample_title)
|
||||
except Exception:
|
||||
continue
|
||||
if sname and title.lower().startswith(sname):
|
||||
return p
|
||||
return (generic or series)[0]
|
||||
|
||||
|
||||
def choose(prompt, options, default=1):
|
||||
for k, label in enumerate(options, 1):
|
||||
say(" %d) %s" % (k, label))
|
||||
while True:
|
||||
a = ask(prompt, str(default))
|
||||
if a.isdigit() and 1 <= int(a) <= len(options):
|
||||
return int(a) - 1
|
||||
return a
|
||||
|
||||
|
||||
def browse_notes():
|
||||
p = subprocess.run(["/usr/bin/osascript", "-e",
|
||||
'POSIX path of (choose file with prompt "Pick the sermon notes" '
|
||||
'default location (path to downloads folder))'], capture_output=True, text=True)
|
||||
return p.stdout.strip() if p.returncode == 0 and p.stdout.strip() else None
|
||||
|
||||
|
||||
def edit_in_textedit(plan, name):
|
||||
os.makedirs(os.path.join(SUPPORT, "edit"), exist_ok=True)
|
||||
path = os.path.join(SUPPORT, "edit", re.sub(r"[^\w .-]", "_", name) + ".txt")
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.write(plantext.dump(plan, name))
|
||||
subprocess.run(["/usr/bin/open", "-e", path])
|
||||
say("")
|
||||
say(" The slide list is open in TextEdit. Change anything, %ssave it (\u2318S)%s, come back and press Enter." % (B, R))
|
||||
while True:
|
||||
ask(" Press Enter when you've saved")
|
||||
try:
|
||||
with open(path, encoding="utf-8") as f:
|
||||
return plantext.load(f.read())
|
||||
except plantext.ParseError as e:
|
||||
say(" %sCan't read the edited list: %s%s - fix it, save, and press Enter again." % (RED, e, R))
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------- doing it
|
||||
|
||||
def find_outputs(tpl, library):
|
||||
themes_dir = os.path.join(os.path.dirname(os.path.dirname(library)), "Themes")
|
||||
try:
|
||||
return themes.outputs_for(tpl, API, themes_dir), None
|
||||
except (OSError, ValueError, KeyError) as e:
|
||||
return [], "Couldn't read the Looks/Themes from ProPresenter (%s) - text was only checked on the slide itself" % e
|
||||
|
||||
|
||||
def write_presentation(library, name, data):
|
||||
final = os.path.join(library, name + ".pro")
|
||||
fd, tmp = tempfile.mkstemp(prefix=".msgnotes-", suffix=".tmp", dir=library)
|
||||
with os.fdopen(fd, "wb") as f:
|
||||
f.write(data)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
os.rename(tmp, final)
|
||||
return final
|
||||
|
||||
|
||||
def link_playlist(library, title, date, name, pro_path):
|
||||
"""Write the link into ProPresenter's playlist file. Returns (ok, message).
|
||||
Only call while ProPresenter is NOT running - it doesn't re-read the file."""
|
||||
path = playlist_mod.playlist_file(library)
|
||||
try:
|
||||
data, doc = playlist_mod.load(path)
|
||||
except OSError as e:
|
||||
return False, "couldn't read ProPresenter's playlists (%s)" % e
|
||||
d, pl = playlist_mod.find_service(doc, title)
|
||||
if pl is None or (date and d != date):
|
||||
return False, "no Planning Center playlist for %s in ProPresenter" % date.strftime("%a %b %-d")
|
||||
k, how = playlist_mod.link(doc, pl, name, pro_path, os.path.dirname(os.path.dirname(library)))
|
||||
if k is None:
|
||||
return False, "%s: %s" % (deck.C.string(pl, "name"), how)
|
||||
out = deck.C.encode(doc)
|
||||
bdir = os.path.join(SUPPORT, "backups")
|
||||
os.makedirs(bdir, exist_ok=True)
|
||||
shutil.copy2(path, os.path.join(bdir, "Playlists-Library-%s" % datetime.datetime.now().strftime("%Y%m%d-%H%M%S")))
|
||||
for old in sorted(glob.glob(os.path.join(bdir, "Playlists-Library-*")))[:-PLAYLIST_BACKUPS]:
|
||||
os.remove(old)
|
||||
tmp = os.path.join(os.path.dirname(path), ".Library.msgnotes.tmp")
|
||||
with open(tmp, "wb") as f:
|
||||
f.write(out)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
os.rename(tmp, path)
|
||||
return True, "%s \u2192 Message (%s)" % (deck.C.string(pl, "name"), how)
|
||||
|
||||
|
||||
def do_playlist(library, title, date, name, pro_path):
|
||||
"""Get the message into the service playlist. Returns (ok, message)."""
|
||||
while True:
|
||||
d, pl_name, slot = service_info(library, title)
|
||||
if pl_name and d == date:
|
||||
break
|
||||
say("")
|
||||
say("%sThere's no playlist for %s in ProPresenter yet.%s" % (YEL, date.strftime("%A %b %-d"), R))
|
||||
say(" In ProPresenter, import that service from Planning Center, then come back here.")
|
||||
try:
|
||||
a = ask(" Press Enter once it's imported (s = skip the playlist)", "").strip().lower()
|
||||
except Cancel:
|
||||
return False, "skipped - drag %s into the playlist yourself" % name
|
||||
if a == "s":
|
||||
return False, "skipped - drag %s into the playlist yourself" % name
|
||||
time.sleep(3) # give ProPresenter a moment to save the imported playlist
|
||||
if not ppctl.pid():
|
||||
return link_playlist(library, title, date, name, pro_path)
|
||||
why = ppctl.busy_reason(API)
|
||||
if why:
|
||||
return False, "not linked because %s - drag %s into the Message section, or run this again later" % (why, name)
|
||||
try:
|
||||
return ppctl.restart_with(lambda: link_playlist(library, title, date, name, pro_path), API, say)
|
||||
except ppctl.Failed as e:
|
||||
return False, str(e)
|
||||
|
||||
|
||||
class YouTubeJob:
|
||||
"""Writes the YouTube title + description in the background: (title, description, problem)."""
|
||||
|
||||
def __init__(self, plan, speaker):
|
||||
self.result = None
|
||||
self.prep = youtube.prepare(plan, speaker)
|
||||
self.thread = threading.Thread(target=self._run, daemon=True)
|
||||
self.thread.start()
|
||||
|
||||
def _run(self):
|
||||
try:
|
||||
t, d = youtube.write_prepared(self.prep)
|
||||
self.result = (t, d, None)
|
||||
except youtube.NotSetUp:
|
||||
self.result = (None, None, "not set up - type y to set it up")
|
||||
except youtube.Failed as e:
|
||||
self.result = (None, None, str(e))
|
||||
except Exception as e:
|
||||
self.result = (None, None, "couldn't write it (%s)" % e)
|
||||
|
||||
def done(self):
|
||||
return not self.thread.is_alive()
|
||||
|
||||
def get(self):
|
||||
if not self.done():
|
||||
say("Finishing the YouTube title and description...")
|
||||
self.thread.join()
|
||||
return self.result
|
||||
|
||||
|
||||
def setup_youtube():
|
||||
say("")
|
||||
if not youtube.secrets_store.get(youtube.KEY_SERVICE, "api-key"):
|
||||
say("Claude API key (from https://console.anthropic.com/settings/keys) - it goes straight into the Keychain:")
|
||||
youtube.secrets_store.prompt_put(youtube.KEY_SERVICE, "api-key")
|
||||
if ask("Set up picking the notes up from email now? (Enter = yes, s = skip)", "").lower() != "s":
|
||||
import pkgutil
|
||||
gs = os.path.join(SUPPORT, "SaveSermonNotes.gs")
|
||||
os.makedirs(SUPPORT, exist_ok=True)
|
||||
with open(gs, "wb") as f:
|
||||
f.write(pkgutil.get_data("app", "SaveSermonNotes.gs"))
|
||||
with open(gs, "rb") as f:
|
||||
subprocess.run(["/usr/bin/pbcopy"], input=f.read()) # script is ready to paste
|
||||
subprocess.run(["/usr/bin/open", "https://script.google.com/create"])
|
||||
say("")
|
||||
say(" The script is copied. In the browser page that just opened (Google Apps Script):")
|
||||
say(" 1. Check the round picture top right is media@waterschurch.org (switch account if not).")
|
||||
say(" 2. Click in the big code area, press Cmd-A then Cmd-V (replaces the sample code).")
|
||||
say(" 3. Press Cmd-S to save.")
|
||||
say(" 4. At the top, change the dropdown next to 'Debug' from 'myFunction' to 'install', click Run.")
|
||||
say(" 5. A box says 'Authorization required': Review permissions > media@ > Allow")
|
||||
say(" (if it says 'Google hasn't verified this app': Advanced > Go to ... > Allow).")
|
||||
say(" Done when the bottom panel says 'Execution completed'.")
|
||||
ask(" Press Enter when that's done", "")
|
||||
|
||||
|
||||
def service_info(library, title):
|
||||
try:
|
||||
_, doc = playlist_mod.load(playlist_mod.playlist_file(library))
|
||||
except OSError:
|
||||
return None, None, None
|
||||
d, pl = playlist_mod.find_service(doc, title)
|
||||
if pl is None:
|
||||
return None, None, None
|
||||
h, k = playlist_mod.message_slot(pl)
|
||||
if h is None:
|
||||
slot = "no Message section!"
|
||||
elif k is not None:
|
||||
slot = "Message \u2192 '%s'" % (playlist_mod.pco_name(playlist_mod.items_of(pl)[k]) or
|
||||
deck.C.string(playlist_mod.items_of(pl)[k], "name"))
|
||||
else:
|
||||
slot = "Message (added under the header)"
|
||||
return d, deck.C.string(pl, "name"), slot
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------- main
|
||||
|
||||
def main(argv=None):
|
||||
ap = argparse.ArgumentParser(description="Sermon notes -> ProPresenter message presentation")
|
||||
ap.add_argument("notes", nargs="?", help="notes file (RTF or Word)")
|
||||
ap.add_argument("--notes", dest="notes_opt")
|
||||
ap.add_argument("--template", help="series, generic, or a path")
|
||||
ap.add_argument("--speaker", help="'ROLE | Name'")
|
||||
ap.add_argument("--date", help="service date (YYMMDD, MMDD, tue, today...)")
|
||||
ap.add_argument("--name", help="presentation name")
|
||||
ap.add_argument("--library", help="Messages library folder")
|
||||
ap.add_argument("--out", help="write the .pro here instead of the library (testing)")
|
||||
ap.add_argument("--no-playlist", action="store_true", help="don't link into the service playlist")
|
||||
ap.add_argument("--no-youtube", action="store_true", help="don't write the YouTube title and description")
|
||||
ap.add_argument("--setup", action="store_true", help="set up the Claude API key and email pickup")
|
||||
ap.add_argument("--yes", action="store_true", help="don't ask for confirmation")
|
||||
ap.add_argument("--version", action="version", version=VERSION)
|
||||
args = ap.parse_args(argv)
|
||||
args.notes = args.notes or args.notes_opt
|
||||
try:
|
||||
return run(args)
|
||||
except Cancel:
|
||||
say("\n%sCancelled - nothing was written.%s" % (YEL, R))
|
||||
return 1
|
||||
except KeyboardInterrupt:
|
||||
say("\n%sStopped - nothing was written.%s" % (YEL, R))
|
||||
return 1
|
||||
|
||||
|
||||
class State:
|
||||
pass
|
||||
|
||||
|
||||
def load_notes(st, path):
|
||||
st.notes_path = path
|
||||
st.plan = notesmod.parse(read_notes(path))
|
||||
parts = st.plan.title_parts()
|
||||
st.title = parts["title"] or os.path.splitext(os.path.basename(path))[0]
|
||||
st.parts = parts
|
||||
st.email = email_text(path)
|
||||
|
||||
|
||||
def run(args):
|
||||
say("%s\u2501\u2501 Message Notes \u2192 ProPresenter %s \u2501\u2501%s" % (B, VERSION, R))
|
||||
if args.setup:
|
||||
setup_youtube()
|
||||
return 0
|
||||
library = find_library(args.library)
|
||||
if not library:
|
||||
raise SystemExit("Can't find the ProPresenter Messages library (is the MEDIA drive connected?).")
|
||||
series, generic = find_templates(library)
|
||||
if not (series or generic):
|
||||
raise SystemExit("No message templates found (files named '_TEMPLATE ... DO NOT DELETE ...pro').")
|
||||
st = State()
|
||||
|
||||
path = os.path.expanduser(args.notes) if args.notes else None
|
||||
if not path:
|
||||
cands = recent_notes()
|
||||
path = cands[0] if cands else None
|
||||
if not path:
|
||||
say("No recent notes found - pick the file.")
|
||||
path = browse_notes() or ask("Notes file (drag it here)").strip().strip("'\"").replace("\\ ", " ")
|
||||
load_notes(st, path)
|
||||
known = scan_speakers(library)
|
||||
|
||||
def pick_template_default():
|
||||
if args.template:
|
||||
t = args.template.lower()
|
||||
if t in ("series", "s") and series:
|
||||
return series[0]
|
||||
if t in ("generic", "g") and generic:
|
||||
return generic[0]
|
||||
return args.template
|
||||
return default_template(series, generic, st.title)
|
||||
|
||||
tpl_path = pick_template_default()
|
||||
tpl = deck.Template(open(tpl_path, "rb").read(), tpl_path)
|
||||
speaker_src = ""
|
||||
if args.speaker:
|
||||
role, _, who = args.speaker.partition("|")
|
||||
speaker = (role.strip(), who.strip())
|
||||
else:
|
||||
sp = speaker_from_email(st.email, known)
|
||||
if sp:
|
||||
speaker, speaker_src = sp, "from the email"
|
||||
else:
|
||||
speaker, speaker_src = tpl.speaker(), "template default"
|
||||
svc_date, svc_name, svc_slot = service_info(library, st.title)
|
||||
date = parse_date(args.date, None) if args.date else (svc_date or next_service_date())
|
||||
name = args.name or default_name(date, st.title)
|
||||
outputs, out_warn = find_outputs(tpl, library)
|
||||
yt_job = None
|
||||
yt_dirty = True
|
||||
|
||||
while True:
|
||||
deck.ensure_pictures(tpl, st.plan)
|
||||
try:
|
||||
layout = deck.fit(tpl, st.plan, measure_mod.measure, outputs)
|
||||
measured = True
|
||||
except measure_mod.Unavailable as e:
|
||||
layout, measured = None, False
|
||||
out_warn = "Couldn't check text sizes on this Mac (%s)" % e
|
||||
final_name = unique_name(library, name) if not args.out else name
|
||||
svc_date, svc_name, svc_slot = service_info(library, st.title)
|
||||
if yt_dirty and not args.no_youtube:
|
||||
yt_job = YouTubeJob(st.plan, speaker)
|
||||
yt_dirty = False
|
||||
show_summary(st, tpl, speaker, speaker_src, final_name, date, svc_date, svc_name, svc_slot,
|
||||
outputs, out_warn, args, yt_job)
|
||||
if args.yes:
|
||||
break
|
||||
a = ask("%sEnter%s = do it" % (B, R), "").strip().lower()
|
||||
if a == "":
|
||||
break
|
||||
if a == "v":
|
||||
t_, d_, p_ = yt_job.get() if yt_job else (None, None, "turned off")
|
||||
say("")
|
||||
say(t_ or "(no title: %s)" % p_)
|
||||
say("")
|
||||
say(d_ or "")
|
||||
ask("%sPress Enter to go back%s" % (DIM, R), "")
|
||||
continue
|
||||
if a == "y":
|
||||
setup_youtube()
|
||||
yt_dirty = True
|
||||
continue
|
||||
if a in ("e", "s", "f"):
|
||||
yt_dirty = True
|
||||
if a == "e":
|
||||
newplan = edit_in_textedit(st.plan, final_name)
|
||||
newplan.skipped = st.plan.skipped if not newplan.skipped else newplan.skipped
|
||||
st.plan = newplan
|
||||
elif a == "t":
|
||||
opts = [("Series " if p in series else "Generic ") + os.path.basename(p) for p in series + generic]
|
||||
k = choose("Template", opts, (series + generic).index(tpl_path) + 1)
|
||||
if isinstance(k, int):
|
||||
tpl_path = (series + generic)[k]
|
||||
tpl = deck.Template(open(tpl_path, "rb").read(), tpl_path)
|
||||
outputs, out_warn = find_outputs(tpl, library)
|
||||
elif a == "s":
|
||||
opts = ["%-22s %s" % (n, r) for r, n in known[:9]]
|
||||
say(" (or type a new one as ROLE | Name)")
|
||||
k = choose("Speaker", opts, 1)
|
||||
if isinstance(k, int):
|
||||
speaker, speaker_src = known[k], "picked"
|
||||
elif "|" in k:
|
||||
r_, _, n_ = k.partition("|")
|
||||
speaker, speaker_src = (r_.strip(), n_.strip()), "typed"
|
||||
elif a == "n":
|
||||
name = ask("Presentation name", final_name)
|
||||
elif a == "d":
|
||||
try:
|
||||
date = parse_date(ask("Service date (e.g. 1018, sun, today)", date.strftime("%m%d")), date)
|
||||
if not args.name:
|
||||
name = default_name(date, st.title)
|
||||
except ValueError:
|
||||
say(" %sDidn't understand that date.%s" % (RED, R))
|
||||
elif a == "f":
|
||||
cands = recent_notes()
|
||||
opts = [os.path.basename(p) for p in cands] + ["browse..."]
|
||||
k = choose("Notes", opts, 1)
|
||||
if isinstance(k, int):
|
||||
p = cands[k] if k < len(cands) else browse_notes()
|
||||
if p:
|
||||
load_notes(st, p)
|
||||
if not args.name:
|
||||
name = default_name(date, st.title)
|
||||
else:
|
||||
say(" %sDidn't understand that.%s" % (RED, R))
|
||||
|
||||
# ---- do it
|
||||
sp_runs = None
|
||||
if measured:
|
||||
try:
|
||||
sp = deck.speaker_rtfs(tpl, speaker[0], speaker[1])
|
||||
res = measure_mod.measure([{"rtf": d, "w": s.w} for nm, s, d in sp])
|
||||
sp_runs = {nm: r["runs"] for (nm, s, d), r in zip(sp, res)}
|
||||
except measure_mod.Unavailable:
|
||||
pass
|
||||
data = deck.build(tpl, st.plan, final_name, speaker, layout, sp_runs)
|
||||
if args.out:
|
||||
with open(args.out, "wb") as f:
|
||||
f.write(data)
|
||||
say("%sWrote %s%s" % (GRN, args.out, R))
|
||||
return 0
|
||||
path = write_presentation(library, final_name, data)
|
||||
log("created %r from %r with %r speaker=%r" % (path, st.notes_path, os.path.basename(tpl_path), speaker))
|
||||
say("")
|
||||
say("%s\u2713%s Created %s%s%s in Messages" % (GRN, R, B, final_name, R))
|
||||
rc = 0
|
||||
if not args.no_playlist:
|
||||
ok, msg = do_playlist(library, st.title, date, final_name, path)
|
||||
log("playlist: %s %s" % (ok, msg))
|
||||
if ok:
|
||||
say("%s\u2713%s Playlist: %s" % (GRN, R, msg))
|
||||
else:
|
||||
say("%s\u26a0 Playlist: %s%s" % (YEL, msg, R))
|
||||
rc = 2
|
||||
yt_title, yt_desc, yt_problem = yt_job.get() if yt_job else (None, None, None)
|
||||
if yt_problem and not args.no_youtube:
|
||||
say("%s\u26a0 YouTube text: %s%s" % (YEL, yt_problem, R))
|
||||
if yt_title and not args.no_youtube:
|
||||
os.makedirs(SUPPORT, exist_ok=True)
|
||||
yt_file = os.path.join(SUPPORT, "youtube-%s.txt" % final_name)
|
||||
with open(yt_file, "w", encoding="utf-8") as f:
|
||||
f.write("TITLE\n\n%s\n\n\nDESCRIPTION\n\n%s\n" % (yt_title, yt_desc))
|
||||
subprocess.run(["/usr/bin/open", "-e", yt_file])
|
||||
say("%s\u2713%s YouTube title and description are open in TextEdit - paste them into Restream" % (GRN, R))
|
||||
return rc
|
||||
|
||||
|
||||
def show_summary(st, tpl, speaker, speaker_src, name, date, svc_date, svc_name, svc_slot, outputs, out_warn, args, yt_job=None):
|
||||
w = width()
|
||||
say("")
|
||||
emailed = email_date(st.notes_path)
|
||||
when = ("emailed " if emailed else "") + datetime.datetime.fromtimestamp(
|
||||
emailed or os.path.getmtime(st.notes_path)).strftime("%a %b %-d %-I:%M%p")
|
||||
rows = [
|
||||
("Notes", "%s %s(%s)%s" % (os.path.basename(st.notes_path), DIM, when, R)),
|
||||
("Message", " \u00b7 ".join(x for x in (st.parts["title"], st.parts["subtitle"], st.parts["reference"]) if x)),
|
||||
("Speaker", "%s %s(%s%s)%s" % (speaker[1], DIM, speaker[0], (", " + speaker_src) if speaker_src else "", R)),
|
||||
("Template", "%s %s%s%s" % ("Generic" if "generic" in os.path.basename(tpl.path).lower() else "Series",
|
||||
DIM, os.path.basename(tpl.path), R)),
|
||||
("Creates", "%s%s%s %s(%d slides)%s" % (B, name, R, DIM, deck.slide_count(tpl, st.plan), R)),
|
||||
]
|
||||
if args.no_playlist:
|
||||
rows.append(("Playlist", "%s(not linking)%s" % (DIM, R)))
|
||||
elif svc_name and svc_date == date:
|
||||
rows.append(("Playlist", "%s \u2192 %s%s" % (svc_name, svc_slot,
|
||||
(" %s(ProPresenter restarts for ~30s)%s" % (DIM, R)) if ppctl.pid() else "")))
|
||||
else:
|
||||
rows.append(("Playlist", "%sno playlist for %s in ProPresenter yet - you'll be asked to import it%s" % (
|
||||
YEL, date.strftime("%a %b %-d"), R)))
|
||||
if not args.no_youtube and yt_job is not None:
|
||||
if not yt_job.done():
|
||||
rows.append(("YouTube", "%sbeing written - opens in TextEdit at the end%s" % (DIM, R)))
|
||||
else:
|
||||
t, d_, problem = yt_job.result
|
||||
rows.append(("YouTube", ("%s %s(opens in TextEdit at the end)%s" % (t, DIM, R)) if t
|
||||
else "%s%s%s" % (YEL, problem or "-", R)))
|
||||
for k, v in rows:
|
||||
say(" %-9s %s" % (k, v))
|
||||
notes = summary_notes(st.plan, tpl)
|
||||
if out_warn:
|
||||
notes.insert(0, out_warn)
|
||||
if notes:
|
||||
say("")
|
||||
for n in notes:
|
||||
say(" %s\u2022%s %s" % (YEL, R, wrap(re.sub(r"\s+", " ", n), w - 6, 4)))
|
||||
say("")
|
||||
say(" %sEnter = do it e = edit slides t/s/n/d/f = change template/speaker/name/date/notes v = view description q = quit%s" % (DIM, R))
|
||||
|
||||
|
||||
def summary_notes(plan, tpl):
|
||||
"""Only what changed or needs a look: splits, things that don't fit, pictures, left-off notes."""
|
||||
out = []
|
||||
nums = deck.slide_numbers(tpl, plan)
|
||||
for s, num in zip(plan.slides, nums):
|
||||
label = KIND_LABEL.get(s.kind, s.kind)
|
||||
what = (" " + s.ref) if s.ref else ""
|
||||
for f in s.flags:
|
||||
if f.startswith("Long scripture"):
|
||||
if s.part and s.part[0] == 1:
|
||||
out.append("Slides %d-%d: %s%s split over %d slides (too long for one)" % (
|
||||
num, num + s.part[1] - 1, label, what, s.part[1]))
|
||||
continue
|
||||
out.append("Slide %d (%s%s): %s" % (num, label, what, f))
|
||||
if s.kind == "picture" and s.text:
|
||||
out.append("Slide %d: PICTURE slide for \"%s\" - add the picture in ProPresenter" % (num, short(s.text, 60)))
|
||||
for w in plan.warnings:
|
||||
out.append(w)
|
||||
left = [x[1] for x in plan.skipped]
|
||||
if left:
|
||||
out.append("Left off the slides: " + "; ".join(short(t, 50) for t in left[:6]) + (" ..." if len(left) > 6 else ""))
|
||||
return out
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
+874
@@ -0,0 +1,874 @@
|
||||
"""Template + slide plan -> ProPresenter presentation.
|
||||
|
||||
Every slide is a copy of the matching slide in the template (same boxes, fonts, looks,
|
||||
macros, media), with fresh ids, as if someone had duplicated it in ProPresenter and typed
|
||||
the text in. Text takes the template's own formatting: the template's sample text decides
|
||||
which paragraph style a title, lead-in, point or verse gets.
|
||||
"""
|
||||
import json
|
||||
import pkgutil
|
||||
import re
|
||||
import struct
|
||||
import uuid as uuidlib
|
||||
|
||||
import notes as notesmod
|
||||
import pbwire
|
||||
import rtf
|
||||
|
||||
C = pbwire.Codec(json.loads(pkgutil.get_data("deck", "schema.json").decode("utf-8")))
|
||||
|
||||
CONTENT = ("point", "subpoint", "scripture", "quote", "prebuild", "build", "picture", "clearpicture")
|
||||
TEXT_ELEMENT = {"point": "point", "subpoint": "subpoint", "quote": "quote", "build": "build",
|
||||
"sentence": "sermoninasentence", "title": "title"}
|
||||
|
||||
# uuids that identify something inside a slide (regenerated when a slide is copied);
|
||||
# every other uuid points at something shared (a macro, look, transition favourite...)
|
||||
IDENTITY = {("rv.data.Cue", "uuid"), ("rv.data.Action", "uuid"), ("rv.data.Slide", "uuid"),
|
||||
("rv.data.Graphics.Element", "uuid"), ("rv.data.Slide.Element.Build", "uuid"),
|
||||
("rv.data.Slide.Element.ChildBuild", "uuid"), ("rv.data.AlignmentGuide", "uuid"),
|
||||
("rv.data.Media", "uuid"), ("rv.data.Effect", "uuid"), ("rv.data.Group", "uuid")}
|
||||
|
||||
|
||||
class TemplateError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def new_uuid():
|
||||
return str(uuidlib.uuid4()).upper()
|
||||
|
||||
|
||||
def uuid_node(s):
|
||||
return pbwire.Node("rv.data.UUID", [[1, 2, s.encode()]])
|
||||
|
||||
|
||||
def uuid_str(node):
|
||||
return C.string(node, "string") if node is not None else ""
|
||||
|
||||
|
||||
def dbl(node, name):
|
||||
return C.double(node, name) if node is not None else 0.0
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------- template
|
||||
|
||||
def slide_action(cue):
|
||||
for a in C.all(cue, "actions"):
|
||||
if C.get(a, "slide") is not None and C.path(a, "slide", "presentation") is not None:
|
||||
return a
|
||||
return None
|
||||
|
||||
|
||||
def action_label(a):
|
||||
lab = C.get(a, "label") if a is not None else None
|
||||
return C.string(lab, "text") if lab is not None else ""
|
||||
|
||||
|
||||
def base_slide(a):
|
||||
return C.path(a, "slide", "presentation", "base_slide")
|
||||
|
||||
|
||||
def slide_elements(a):
|
||||
bs = base_slide(a)
|
||||
return C.all(bs, "elements") if bs is not None else []
|
||||
|
||||
|
||||
def element_name(el):
|
||||
return C.string(C.get(el, "element"), "name")
|
||||
|
||||
|
||||
def element_text(el):
|
||||
return C.path(el, "element", "text")
|
||||
|
||||
|
||||
class ElementStyle:
|
||||
"""Formatting of one template text box, paragraph by paragraph."""
|
||||
|
||||
def __init__(self, el, theme=False):
|
||||
self.name = element_name(el)
|
||||
text = element_text(el)
|
||||
data = C.get(text, "rtf_data", b"")
|
||||
doc = rtf.read(data)
|
||||
self.header = doc.header
|
||||
self.paras = []
|
||||
for p in doc.paras:
|
||||
runs = [r for r in p.runs if r.text.strip()] or p.runs
|
||||
first = dict(runs[0].state) if runs else dict(p.end_state or {})
|
||||
self.paras.append((p.par_codes, first, dict(p.end_state or first), p.text))
|
||||
if theme or not self.paras or "\\fonttbl\\f" not in self.header:
|
||||
# a theme box formats all ordinary text one way: its box-level attributes
|
||||
self._from_attributes(C.get(text, "attributes"))
|
||||
b = C.path(el, "element", "bounds", "size")
|
||||
self.w, self.h = dbl(b, "width"), dbl(b, "height")
|
||||
o = C.path(el, "element", "bounds", "origin")
|
||||
self.y = dbl(o, "y")
|
||||
self.valign = C.get(text, "vertical_alignment", 0) # 0 top, 1 middle, 2 bottom
|
||||
self.slide_h = 1080.0
|
||||
# fallback font records from ProPresenter's own run list
|
||||
self.fonts = []
|
||||
attrs = C.get(text, "attributes")
|
||||
for ca in (C.all(attrs, "custom_attributes") if attrs is not None else []):
|
||||
f = C.get(ca, "original_font")
|
||||
if f is not None:
|
||||
self.fonts.append(f)
|
||||
self.base_font = C.get(attrs, "font") if attrs is not None else None
|
||||
|
||||
def _from_attributes(self, attrs):
|
||||
font = C.get(attrs, "font") if attrs is not None else None
|
||||
name = C.string(font, "name") or "Helvetica"
|
||||
size = C.double(font, "size") if font is not None else 12.0
|
||||
ps = C.get(attrs, "paragraph_style") if attrs is not None else None
|
||||
align = C.get(ps, "alignment", 0) if ps is not None else 0
|
||||
kern = C.double(attrs, "kerning") if attrs is not None else 0.0
|
||||
lig = C.get(attrs, "ligature_style", 0) if attrs is not None else 0
|
||||
self.header = ("{\\rtf1\\ansi\\ansicpg1252\\cocoartf2867\n\\cocoatextscaling0\\cocoaplatform0"
|
||||
"{\\fonttbl\\f0\\fnil\\fcharset0 %s;}\n{\\colortbl;\\red255\\green255\\blue255;"
|
||||
"\\red255\\green255\\blue255;}\n{\\*\\expandedcolortbl;;\\cssrgb\\c100000\\c100000"
|
||||
"\\c100000;}\n\\deftab1680\n" % name)
|
||||
codes = [("pard", None), ("pardeftab", 1680), ("pardirnatural", None),
|
||||
({0: "ql", 1: "qr", 2: "qc", 3: "qj"}.get(align, "ql"), None), ("partightenfactor", 0)]
|
||||
st = {"f": 0, "fs": int(round(size * 2)), "cf": 2}
|
||||
if kern:
|
||||
st.update({"kerning": 1, "expnd": int(round(kern * 4)), "expndtw": int(round(kern * 20))})
|
||||
if lig == 1:
|
||||
st["CocoaLigature"] = 0
|
||||
self.paras = [(codes, st, dict(st), "")]
|
||||
|
||||
def para(self, k):
|
||||
if k < 0:
|
||||
k = len(self.paras) + k
|
||||
return self.paras[max(0, min(k, len(self.paras) - 1))]
|
||||
|
||||
|
||||
class Template:
|
||||
def __init__(self, data, path=""):
|
||||
self.path = path
|
||||
self.pres = C.decode(data, "rv.data.Presentation")
|
||||
self.name = C.string(self.pres, "name")
|
||||
cues = {uuid_str(C.get(c, "uuid")): c for c in C.all(self.pres, "cues")}
|
||||
self.order = []
|
||||
for g in C.all(self.pres, "cue_groups"):
|
||||
for cid in C.all(g, "cue_identifiers"):
|
||||
c = cues.get(uuid_str(cid))
|
||||
if c is not None:
|
||||
self.order.append(c)
|
||||
self.kinds = [classify(c) for c in self.order]
|
||||
self.sample = {}
|
||||
for c, k in zip(self.order, self.kinds):
|
||||
self.sample.setdefault(k, c)
|
||||
if "title" not in self.sample:
|
||||
raise TemplateError("The template has no Title slide (a text box named 'Title').")
|
||||
if "scripture" not in self.sample or "point" not in self.sample:
|
||||
raise TemplateError("The template needs a SCRIPTURE slide and a Point slide.")
|
||||
ti = self.kinds.index("title")
|
||||
self.head = list(range(0, ti + 1))
|
||||
if "sentence" in self.kinds:
|
||||
si = self.kinds.index("sentence")
|
||||
self.tail = list(range(si + 1, len(self.order)))
|
||||
else:
|
||||
last = max(i for i, k in enumerate(self.kinds) if k in CONTENT)
|
||||
self.tail = list(range(last + 1, len(self.order)))
|
||||
self.styles = {}
|
||||
for k, c in self.sample.items():
|
||||
a = slide_action(c)
|
||||
size = C.get(base_slide(a), "size") if base_slide(a) is not None else None
|
||||
for el in slide_elements(a):
|
||||
if element_text(el) is not None and C.get(element_text(el), "rtf_data"):
|
||||
st = ElementStyle(el)
|
||||
if size is not None and dbl(size, "height"):
|
||||
st.slide_h = dbl(size, "height")
|
||||
self.styles[(k, element_name(el).lower())] = st
|
||||
self.notes_styles = {}
|
||||
default = None
|
||||
for c, k in zip(self.order, self.kinds):
|
||||
n = C.path(slide_action(c), "slide", "presentation", "notes")
|
||||
if n is not None and C.get(n, "rtf_data"):
|
||||
st = notes_style(C.get(n, "rtf_data"))
|
||||
self.notes_styles.setdefault(k, st)
|
||||
if default is None and k in ("build", "other", "speaker"):
|
||||
default = st
|
||||
self.default_notes = default or next(iter(self.notes_styles.values()), None)
|
||||
# the series name shown in the template's sample title ("Where Is Your Faith")
|
||||
self.sample_title = ""
|
||||
st = self.styles.get(("title", "title"))
|
||||
if st and st.paras:
|
||||
self.sample_title = st.paras[0][3].strip()
|
||||
|
||||
def style(self, kind, element):
|
||||
return self.styles.get((kind, element.lower()))
|
||||
|
||||
def speaker(self):
|
||||
"""(role, name) as typed in the template's SPEAKER NAME slide."""
|
||||
return speaker_of(self.sample.get("speaker"))
|
||||
|
||||
|
||||
def speaker_of(cue):
|
||||
out = {}
|
||||
if cue is not None:
|
||||
for el in slide_elements(slide_action(cue)):
|
||||
t = element_text(el)
|
||||
data = C.get(t, "rtf_data") if t is not None else None
|
||||
if data:
|
||||
out[element_name(el).lower()] = " ".join(p.text for p in rtf.read(data).paras).strip()
|
||||
return out.get("role", ""), out.get("preacher", "")
|
||||
|
||||
|
||||
def classify(cue):
|
||||
a = slide_action(cue)
|
||||
if a is None:
|
||||
return "other"
|
||||
label = action_label(a).lower()
|
||||
names = {element_name(el).lower() for el in slide_elements(a)}
|
||||
if "sermon in a sentence" in label or "sermoninasentence" in names:
|
||||
return "sentence"
|
||||
if "role" in names and "preacher" in names:
|
||||
return "speaker"
|
||||
if "reference" in names and "scripture" in names:
|
||||
return "scripture"
|
||||
if "build" in names:
|
||||
return "build"
|
||||
if "title" in names:
|
||||
return "title"
|
||||
if "subpoint" in names:
|
||||
return "subpoint"
|
||||
if "point" in names:
|
||||
return "point"
|
||||
if "quote" in names:
|
||||
return "quote"
|
||||
if "stream deck" in label:
|
||||
return "prebuild"
|
||||
if label == "clear picture":
|
||||
return "clearpicture"
|
||||
if label == "picture":
|
||||
return "picture"
|
||||
if label == "quiet":
|
||||
return "quiet"
|
||||
return "other"
|
||||
|
||||
|
||||
def notes_style(data):
|
||||
doc = rtf.read(data)
|
||||
if doc.paras:
|
||||
p = doc.paras[0]
|
||||
st = dict((p.runs[0].state if p.runs else p.end_state) or {})
|
||||
return doc.header, p.par_codes, st
|
||||
return doc.header, [("pard", None)], {"f": 0, "fs": 24}
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------- text
|
||||
|
||||
def scaled(state, scale):
|
||||
if scale == 1.0 or "fs" not in state:
|
||||
return state
|
||||
s = dict(state)
|
||||
s["fs"] = max(2, int(round(state["fs"] * scale)))
|
||||
return s
|
||||
|
||||
|
||||
def with_emphasis(state, bold, italic):
|
||||
s = dict(state)
|
||||
if bold:
|
||||
s["b"] = None
|
||||
if italic:
|
||||
s["i"] = None
|
||||
return s
|
||||
|
||||
|
||||
def make_rtf(style, paras, scale=1.0):
|
||||
"""paras: [(template paragraph index, [(text, bold, italic), ...]), ...]"""
|
||||
out = []
|
||||
for k, (pi, segs) in enumerate(paras):
|
||||
codes, first, end, _ = style.para(pi)
|
||||
runs = [(t, scaled(with_emphasis(first, b, i), scale)) for t, b, i in segs if t]
|
||||
if not runs:
|
||||
runs = [("", scaled(first, scale))]
|
||||
out.append((codes, runs))
|
||||
return rtf.write(style.header, out)
|
||||
|
||||
|
||||
def notes_rtf(nstyle, text):
|
||||
header, codes, st = nstyle
|
||||
paras = [(codes, [(line, st)]) for line in text.split("\n")]
|
||||
return rtf.write(header, paras)
|
||||
|
||||
|
||||
def slide_texts(tpl, s):
|
||||
"""What goes into each text box of one plan slide: {element: [(para idx, segs)]}."""
|
||||
L = s.lines
|
||||
if s.kind in ("picture", "clearpicture"):
|
||||
return {}
|
||||
if s.kind == "title":
|
||||
roles = notesmod.title_roles(L)
|
||||
idx = {"title": 0, "subtitle": 1, "reference": -1}
|
||||
return {"title": [(idx[r], ln.segs_plain()) for r, ln in zip(roles, L)]}
|
||||
if s.kind == "point":
|
||||
two = len(tpl.style("point", "point").paras) > 1
|
||||
return {"point": [((0 if (ln.lead and two) else -1), ln.segs_plain()) for ln in L]}
|
||||
if s.kind == "subpoint":
|
||||
return {"subpoint": [(0, ln.segs_plain()) for ln in L]}
|
||||
if s.kind == "quote":
|
||||
out = []
|
||||
for ln in L:
|
||||
attrib = re.match(r"^\s*[-\u2013\u2014~]", ln.text) is not None
|
||||
out.append((-1 if attrib else 0, ln.segs_plain()))
|
||||
return {"quote": out}
|
||||
if s.kind == "build":
|
||||
return {"build": [(0, ln.segs_plain()) for ln in L]}
|
||||
if s.kind == "sentence":
|
||||
st = tpl.style("sentence", "sermoninasentence")
|
||||
label = st.paras[0][3].strip() if st and len(st.paras) > 1 else ""
|
||||
out = [(0, [(label, False, False)])] if label else []
|
||||
return {"sermoninasentence": out + [(-1, ln.segs_plain()) for ln in L]}
|
||||
if s.kind == "scripture":
|
||||
# the template's sample reference ends with a space ("Exodus 8:1 ESV "); keep that habit
|
||||
ref_st = tpl.style("scripture", "reference")
|
||||
tail = " " if ref_st and ref_st.paras[0][3].endswith(" ") and not s.ref.endswith(" ") else ""
|
||||
return {"reference": [(0, [(s.ref + tail, False, False)])],
|
||||
"scripture": [(0, ln.segs) for ln in L] or [(0, [("", False, False)])]}
|
||||
return {}
|
||||
|
||||
|
||||
def slide_notes(tpl, s):
|
||||
if s.kind in ("picture", "clearpicture"):
|
||||
return s.text
|
||||
if s.kind == "title":
|
||||
return " - ".join(l.text for l in s.lines)
|
||||
if s.kind == "scripture":
|
||||
return (s.ref + " " + " ".join(l.text for l in s.lines)).strip()
|
||||
if s.kind == "sentence":
|
||||
st = tpl.style("sentence", "sermoninasentence")
|
||||
label = st.paras[0][3].strip() if st and len(st.paras) > 1 else "Sermon in a Sentence:"
|
||||
return (label + " " + " ".join(l.text for l in s.lines)).strip()
|
||||
return "\n".join(l.text for l in s.lines)
|
||||
|
||||
|
||||
def kind_for(tpl, s):
|
||||
k = s.kind
|
||||
if k in ("picture", "clearpicture"):
|
||||
return k
|
||||
if k not in tpl.sample:
|
||||
k = "point"
|
||||
return k
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------- fitting
|
||||
|
||||
def element_rtfs(tpl, s):
|
||||
"""[(element name, ElementStyle, rtf bytes)] for one plan slide at its current scale."""
|
||||
out = []
|
||||
kind = kind_for(tpl, s)
|
||||
texts = slide_texts(tpl, s) if kind == s.kind else _as_point(tpl, s)
|
||||
for name, paras in texts.items():
|
||||
st = tpl.style(kind, name)
|
||||
if st is None:
|
||||
continue
|
||||
out.append((name, st, make_rtf(st, paras, s.scale.get(name, 1.0))))
|
||||
return out
|
||||
|
||||
|
||||
def _as_point(tpl, s):
|
||||
return {"point": [(-1, ln.segs_plain()) for ln in s.lines]}
|
||||
|
||||
|
||||
def effective(st, paras):
|
||||
"""Paragraph runs as they really look on the slide: [[(text, bold, italic)]]."""
|
||||
out = []
|
||||
for pi, segs in paras:
|
||||
_, first, _, _ = st.para(pi)
|
||||
base_b = "b" in first
|
||||
base_i = "i" in first
|
||||
out.append([(t, base_b or b, base_i or i) for t, b, i in segs if t])
|
||||
return out
|
||||
|
||||
|
||||
def output_rtf(tpl, kind, name, st_out, paras):
|
||||
"""The same text as a themed screen draws it: the theme's font and size, with only the
|
||||
'special' words (styled differently from the rest of the box) keeping bold/italic."""
|
||||
st_slide = tpl.style(kind, name)
|
||||
eff = effective(st_slide, paras) if st_slide is not None else [[(t, b, i) for t, b, i in segs] for _, segs in paras]
|
||||
count = {}
|
||||
for para in eff:
|
||||
for t, b, i in para:
|
||||
count[(b, i)] = count.get((b, i), 0) + len(t)
|
||||
base = max(count, key=count.get) if count else (False, False)
|
||||
out = []
|
||||
for para in eff:
|
||||
segs = [(t, b, i) if (b, i) != base else (t, False, False) for t, b, i in para]
|
||||
out.append((0, segs))
|
||||
return make_rtf(st_out, out)
|
||||
|
||||
|
||||
SAFE_BOTTOM = 12.0 / 1080.0 # text must end at least this far above the slide's bottom edge
|
||||
|
||||
|
||||
def fits(st, h):
|
||||
if h > st.h + 0.5:
|
||||
return False
|
||||
if st.valign == 1:
|
||||
bottom = st.y + st.h / 2.0 + h / 2.0
|
||||
elif st.valign == 2:
|
||||
bottom = st.y + st.h
|
||||
else:
|
||||
bottom = st.y + h
|
||||
return bottom <= st.slide_h * (1 - SAFE_BOTTOM) + 0.5
|
||||
|
||||
|
||||
def checks(tpl, s, outputs):
|
||||
"""Every place one plan slide's text will be drawn:
|
||||
[(where, element, ElementStyle, rtf)] - where is None for the slide itself."""
|
||||
out = [(None, name, st, data) for name, st, data in element_rtfs(tpl, s)]
|
||||
kind = kind_for(tpl, s)
|
||||
texts = slide_texts(tpl, s) if kind == s.kind else _as_point(tpl, s)
|
||||
for o in outputs or []:
|
||||
for name, paras in texts.items():
|
||||
st = o.styles.get(name)
|
||||
if st is not None:
|
||||
out.append((o.name, name, st, output_rtf(tpl, kind, name, st, paras)))
|
||||
return out
|
||||
|
||||
|
||||
def measure_all(tpl, plan, outputs, measure):
|
||||
items, where = [], []
|
||||
for si, s in enumerate(plan.slides):
|
||||
for w, name, st, data in checks(tpl, s, outputs):
|
||||
items.append({"rtf": data, "w": st.w})
|
||||
where.append((si, w, name, st))
|
||||
return measure(items), where
|
||||
|
||||
|
||||
def line_height(st, measure_cache, measure):
|
||||
key = id(st)
|
||||
if key not in measure_cache:
|
||||
r = measure([{"rtf": make_rtf(st, [(0, [("Xg", False, False)])]), "w": st.w}])[0]
|
||||
measure_cache[key] = max(1.0, r["h"])
|
||||
return measure_cache[key]
|
||||
|
||||
|
||||
def fit(tpl, plan, measure, outputs=None, min_scale=0.7):
|
||||
"""Check every text everywhere it is shown. Font sizes are never changed.
|
||||
|
||||
Scriptures that are too long anywhere are split at verse numbers. Anything else that
|
||||
doesn't fit is reported on the slide (s.flags): for the slide's own box with the size it
|
||||
would need, for a theme box (stream, LED wall) with the lines it needs and has room for.
|
||||
Returns (results, where) for the slide-level boxes, used for ProPresenter's run list.
|
||||
"""
|
||||
for s in plan.slides:
|
||||
s.flags = [f for f in s.flags if not f.startswith(("Font reduced", "Text is too long", "Too long for"))]
|
||||
s.scale = {}
|
||||
lh_cache = {}
|
||||
for _round in range(6):
|
||||
res, where = measure_all(tpl, plan, outputs, measure)
|
||||
over = [(w, r) for w, r in zip(where, res) if not fits(w[3], r["h"])]
|
||||
split = sorted({w[0] for w, r in over if w[2] == "scripture"
|
||||
and plan.slides[w[0]].kind == "scripture" and not getattr(plan.slides[w[0]], "_nosplit", False)})
|
||||
if split:
|
||||
new = []
|
||||
for si, s in enumerate(plan.slides):
|
||||
new.extend(split_scripture(tpl, s, measure, outputs) if si in split else [s])
|
||||
plan.slides = new
|
||||
continue
|
||||
break
|
||||
# slide-level boxes: find the size that would fit, but only report it
|
||||
own = [(w, r) for w, r in over if w[1] is None]
|
||||
cands, cwhere = [], []
|
||||
for (si, _, name, st), r in own:
|
||||
s = plan.slides[si]
|
||||
base = max([p[1].get("fs", 24) for p in st.paras] or [24])
|
||||
for dd in range(1, int(base * (1 - min_scale) / 2) + 2):
|
||||
sc = (base - 2 * dd) / float(base)
|
||||
if sc < min_scale - 1e-9:
|
||||
break
|
||||
s2 = notesmod.Slide(s.kind, s.lines, s.ref)
|
||||
s2.scale = {name: sc}
|
||||
for nm, st2, data in element_rtfs(tpl, s2):
|
||||
if nm == name:
|
||||
cands.append({"rtf": data, "w": st2.w})
|
||||
cwhere.append((si, name, sc, st2))
|
||||
cres = measure(cands) if cands else []
|
||||
best = {}
|
||||
for (si, name, sc, st2), r in zip(cwhere, cres):
|
||||
if fits(st2, r["h"]) and sc > best.get((si, name), 0):
|
||||
best[(si, name)] = sc
|
||||
for (si, w, name, st), r in over:
|
||||
s = plan.slides[si]
|
||||
if w is None:
|
||||
base = max([p[1].get("fs", 24) for p in st.paras] or [24]) / 2.0
|
||||
if (si, name) in best:
|
||||
msg = "Too long for the slide's own box at %gpt (fits at %gpt) - font NOT changed" % (
|
||||
base, round(base * best[(si, name)]))
|
||||
else:
|
||||
msg = "Too long for the slide's own box at %gpt, even at %gpt - font NOT changed" % (
|
||||
base, round(base * min_scale))
|
||||
else:
|
||||
lh = line_height(st, lh_cache, measure)
|
||||
need = int(round(r["h"] / lh))
|
||||
room = max(1, int((min(st.h, st.slide_h * (1 - SAFE_BOTTOM) - st.y) + 0.5) // lh))
|
||||
msg = "Too long for %s: %d lines, room for %d" % (w, need, room)
|
||||
if msg not in s.flags:
|
||||
s.flags.append(msg)
|
||||
return res, where
|
||||
|
||||
|
||||
_VERSE_START = re.compile(r"(?<=[\s\u201d\u2019\"'.;:,!?)\]])(?=\d{1,3}[a-c]?\s+[\u201c\u2018\"'(A-Za-z])")
|
||||
_SENT_END = re.compile(r"[.!?;:][\u201d\u2019\"']?\s+(?=[\u201c\u2018\"'A-Z0-9])")
|
||||
_CLAUSE = re.compile(r"(?<=[,\u2014])\s*")
|
||||
|
||||
|
||||
def split_scripture(tpl, s, measure, outputs=None):
|
||||
"""One long scripture -> as few slides as possible that fit everywhere, cut at verse
|
||||
numbers if possible (then sentences, then clauses), pieces as even as possible."""
|
||||
flat = [] # (text, bold, italic) with "\n" between paragraphs
|
||||
for k, ln in enumerate(s.lines):
|
||||
if k:
|
||||
flat.append(("\n", False, False))
|
||||
flat.extend(ln.segs)
|
||||
text = "".join(t for t, _, _ in flat)
|
||||
for level in (_VERSE_START, _SENT_END, _CLAUSE):
|
||||
bounds = {0, len(text)} | {i + 1 for i, ch in enumerate(text) if ch == "\n"}
|
||||
bounds |= {m.start() if level is _VERSE_START else m.end() for m in level.finditer(text)}
|
||||
bounds = sorted(b for b in bounds if 0 <= b <= len(text))
|
||||
if len(bounds) <= 2:
|
||||
continue
|
||||
pairs = [(i, j) for i in range(len(bounds)) for j in range(i + 1, len(bounds))]
|
||||
items, owner = [], []
|
||||
for pi, (i, j) in enumerate(pairs):
|
||||
piece = slice_segs(flat, bounds[i], bounds[j])
|
||||
ps = notesmod.Slide("scripture", [notesmod.Line(l) for l in split_lines(piece)], ref=s.ref)
|
||||
for w, name, st, data in checks(tpl, ps, outputs):
|
||||
if name == "scripture":
|
||||
items.append({"rtf": data, "w": st.w})
|
||||
owner.append((pi, st))
|
||||
res = measure(items)
|
||||
ok = {p: True for p in range(len(pairs))}
|
||||
for (pi, st), r in zip(owner, res):
|
||||
if not fits(st, r["h"]):
|
||||
ok[pi] = False
|
||||
okp = {pairs[p]: v for p, v in ok.items()}
|
||||
n = len(bounds)
|
||||
best = {0: (0, 0, [])}
|
||||
for j in range(1, n):
|
||||
for i in range(j):
|
||||
if i in best and okp[(i, j)]:
|
||||
cnt, worst, path = best[i]
|
||||
cand = (cnt + 1, max(worst, bounds[j] - bounds[i]), path + [j])
|
||||
if j not in best or cand[:2] < best[j][:2]:
|
||||
best[j] = cand
|
||||
if n - 1 in best:
|
||||
cuts = [0] + best[n - 1][2]
|
||||
total = len(cuts) - 1
|
||||
out = []
|
||||
for k in range(total):
|
||||
piece = slice_segs(flat, bounds[cuts[k]], bounds[cuts[k + 1]])
|
||||
ns = notesmod.Slide("scripture", [notesmod.Line(l) for l in split_lines(piece)],
|
||||
ref=s.ref, src=s.src)
|
||||
ns.part = (k + 1, total)
|
||||
ns.flags = [f for f in s.flags if not f.startswith("Long scripture")] + \
|
||||
["Long scripture - split over %d slides (%d/%d)" % (total, k + 1, total)]
|
||||
out.append(ns)
|
||||
return out
|
||||
s._nosplit = True
|
||||
s.flags.append("Scripture is too long for one slide and could not be split - check it")
|
||||
return [s]
|
||||
|
||||
|
||||
def slice_segs(flat, a, b):
|
||||
out, pos = [], 0
|
||||
for t, bo, it in flat:
|
||||
x, y = pos, pos + len(t)
|
||||
pos = y
|
||||
lo, hi = max(a, x), min(b, y)
|
||||
if lo < hi:
|
||||
out.append((t[lo - x:hi - x], bo, it))
|
||||
while out and not out[0][0].strip():
|
||||
out.pop(0)
|
||||
while out and not out[-1][0].strip():
|
||||
out.pop()
|
||||
if out:
|
||||
out[0] = (out[0][0].lstrip(),) + out[0][1:]
|
||||
out[-1] = (out[-1][0].rstrip(),) + out[-1][1:]
|
||||
return out
|
||||
|
||||
|
||||
def split_lines(segs):
|
||||
lines, cur = [], []
|
||||
for t, b, i in segs:
|
||||
parts = t.split("\n")
|
||||
for k, part in enumerate(parts):
|
||||
if k:
|
||||
lines.append(cur)
|
||||
cur = []
|
||||
if part:
|
||||
cur.append((part, b, i))
|
||||
lines.append(cur)
|
||||
return [l for l in lines if l]
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------- building
|
||||
|
||||
def clone_cue(cue):
|
||||
c = C.copy(cue)
|
||||
mapping = {}
|
||||
|
||||
def collect(node):
|
||||
spec = C.schema.get(node.type, {})
|
||||
for num, wt, val in node.fields:
|
||||
if isinstance(val, pbwire.Node):
|
||||
f = spec.get(str(num))
|
||||
if val.type == "rv.data.UUID" and f and (node.type, f[0]) in IDENTITY:
|
||||
s = uuid_str(val)
|
||||
if s and s not in mapping:
|
||||
mapping[s] = new_uuid()
|
||||
collect(val)
|
||||
|
||||
def remap(node):
|
||||
for fld in node.fields:
|
||||
val = fld[2]
|
||||
if isinstance(val, pbwire.Node):
|
||||
if val.type == "rv.data.UUID":
|
||||
s = uuid_str(val)
|
||||
if s in mapping:
|
||||
C.set(val, "string", mapping[s].encode())
|
||||
else:
|
||||
remap(val)
|
||||
|
||||
collect(c)
|
||||
remap(c)
|
||||
return c
|
||||
|
||||
|
||||
def set_repeated(node, name, values):
|
||||
n = C.num(node, name)
|
||||
pos = None
|
||||
keep = []
|
||||
for k, f in enumerate(node.fields):
|
||||
if f[0] == n:
|
||||
if pos is None:
|
||||
pos = len(keep)
|
||||
else:
|
||||
keep.append(f)
|
||||
if pos is None:
|
||||
pos = len(keep)
|
||||
for k, f in enumerate(keep):
|
||||
if f[0] > n:
|
||||
pos = k
|
||||
break
|
||||
node.fields = keep[:pos] + [[n, 2, v] for v in values] + keep[pos:]
|
||||
|
||||
|
||||
def font_node(name, size, bold, italic, family):
|
||||
f = pbwire.Node("rv.data.Font", [])
|
||||
C.set(f, "name", name.encode())
|
||||
C.set_double(f, "size", float(size))
|
||||
if italic:
|
||||
C.set(f, "italic", 1, wt=0)
|
||||
if bold:
|
||||
C.set(f, "bold", 1, wt=0)
|
||||
C.set(f, "family", family.encode())
|
||||
return f
|
||||
|
||||
|
||||
def set_text(el, data, runs=None, scale=1.0, style=None):
|
||||
"""Put new RTF into a slide element and keep ProPresenter's run list in step."""
|
||||
text = element_text(el)
|
||||
C.set(text, "rtf_data", data)
|
||||
attrs = C.get(text, "attributes")
|
||||
if attrs is None:
|
||||
return
|
||||
cas = []
|
||||
if runs:
|
||||
for r in runs:
|
||||
ca = pbwire.Node("rv.data.Graphics.Text.Attributes.CustomAttribute", [])
|
||||
rng = pbwire.Node("rv.data.IntRange", [])
|
||||
if r["loc"]:
|
||||
C.set(rng, "start", r["loc"], wt=0)
|
||||
C.set(rng, "end", r["loc"] + r["len"], wt=0)
|
||||
C.set(ca, "range", rng)
|
||||
C.set(ca, "original_font", font_node(r["name"], r["size"], r["bold"], r["italic"], r["family"]))
|
||||
cas.append(ca)
|
||||
first = runs[0]
|
||||
C.set(attrs, "font", font_node(first["name"], first["size"], first["bold"], first["italic"],
|
||||
first["family"]))
|
||||
elif style is not None and style.fonts:
|
||||
# no text layout available: reuse the template's own font records
|
||||
n16 = len(rtf_plain(data).encode("utf-16-le")) // 2
|
||||
f = C.copy(style.fonts[-1] if len(style.fonts) > 1 else style.fonts[0])
|
||||
sz = C.double(f, "size") * scale
|
||||
C.set_double(f, "size", sz)
|
||||
ca = pbwire.Node("rv.data.Graphics.Text.Attributes.CustomAttribute", [])
|
||||
rng = pbwire.Node("rv.data.IntRange", [])
|
||||
C.set(rng, "end", n16, wt=0)
|
||||
C.set(ca, "range", rng)
|
||||
C.set(ca, "original_font", f)
|
||||
cas.append(ca)
|
||||
set_repeated(attrs, "custom_attributes", cas)
|
||||
|
||||
|
||||
def rtf_plain(data):
|
||||
return "\n".join(p.text for p in rtf.read(data).paras)
|
||||
|
||||
|
||||
def set_notes(a, nstyle, text):
|
||||
ps = C.path(a, "slide", "presentation")
|
||||
if ps is None or nstyle is None:
|
||||
return
|
||||
n = C.get(ps, "notes")
|
||||
if n is None:
|
||||
n = pbwire.Node("rv.data.PresentationSlide.Notes", [])
|
||||
C.set(n, "attributes", pbwire.Node("rv.data.Graphics.Text.Attributes", []))
|
||||
C.set(ps, "notes", n)
|
||||
C.set(n, "rtf_data", notes_rtf(nstyle, text))
|
||||
|
||||
|
||||
def rebuild_builds(a, el, nlines):
|
||||
"""One build step per line, the way ProPresenter numbers them."""
|
||||
bi = C.get(el, "build_in")
|
||||
if bi is None:
|
||||
return
|
||||
proto = (C.all(el, "childBuilds") or [None])[0]
|
||||
kids = []
|
||||
for k in range(max(0, nlines - 1)):
|
||||
ch = C.copy(proto) if proto is not None else pbwire.Node("rv.data.Slide.Element.ChildBuild", [])
|
||||
C.set(ch, "uuid", uuid_node(new_uuid()))
|
||||
if k:
|
||||
C.set(ch, "index", k, wt=0)
|
||||
else:
|
||||
C.remove(ch, "index")
|
||||
kids.append(ch)
|
||||
set_repeated(el, "childBuilds", kids)
|
||||
bs = base_slide(a)
|
||||
order = []
|
||||
for e in C.all(bs, "elements"):
|
||||
b = C.get(e, "build_in")
|
||||
if b is not None:
|
||||
order.append(uuid_node(uuid_str(C.get(b, "uuid"))))
|
||||
for ch in C.all(e, "childBuilds"):
|
||||
order.append(uuid_node(uuid_str(C.get(ch, "uuid"))))
|
||||
set_repeated(bs, "element_build_order", order)
|
||||
|
||||
|
||||
def build(tpl, plan, name, speaker, layout=None, speaker_runs=None):
|
||||
"""-> .pro bytes. layout: (results, where) from fit(), for exact font runs."""
|
||||
runs = {}
|
||||
if layout:
|
||||
res, where = layout
|
||||
for (si, w, el_name, st), r in zip(where, res):
|
||||
if w is None:
|
||||
runs[(si, el_name)] = r["runs"]
|
||||
pres = C.copy(tpl.pres)
|
||||
C.set(pres, "uuid", uuid_node(new_uuid()))
|
||||
C.set(pres, "name", name.encode("utf-8"))
|
||||
out = []
|
||||
|
||||
def fill(cue, s, si):
|
||||
kind = kind_for(tpl, s)
|
||||
a = slide_action(cue)
|
||||
texts = slide_texts(tpl, s) if kind == s.kind else _as_point(tpl, s)
|
||||
rtfs = {nm: data for nm, st, data in element_rtfs(tpl, s)}
|
||||
for el in slide_elements(a):
|
||||
nm = element_name(el).lower()
|
||||
if nm in rtfs:
|
||||
set_text(el, rtfs[nm], runs.get((si, nm)), s.scale.get(nm, 1.0), tpl.style(kind, nm))
|
||||
if nm == "build":
|
||||
rebuild_builds(a, el, len(texts.get("build", [])))
|
||||
set_notes(a, tpl.notes_styles.get(kind) or tpl.default_notes, slide_notes(tpl, s))
|
||||
|
||||
role, preacher = speaker
|
||||
title_slide = None
|
||||
for si, s in enumerate(plan.slides):
|
||||
if s.kind == "title":
|
||||
title_slide = (si, s)
|
||||
break
|
||||
for i in tpl.head:
|
||||
cue = clone_cue(tpl.order[i])
|
||||
k = tpl.kinds[i]
|
||||
if k == "speaker":
|
||||
fill_speaker(tpl, cue, role, preacher, speaker_runs)
|
||||
elif k == "title" and title_slide is not None:
|
||||
fill(cue, title_slide[1], title_slide[0])
|
||||
out.append(cue)
|
||||
for si, s in enumerate(plan.slides):
|
||||
if s.kind == "title":
|
||||
continue
|
||||
kind = kind_for(tpl, s)
|
||||
if kind not in tpl.sample:
|
||||
continue # e.g. a picture slide with a template that has none
|
||||
if kind == "build" and "prebuild" in tpl.sample:
|
||||
out.append(clone_cue(tpl.sample["prebuild"]))
|
||||
cue = clone_cue(tpl.sample[kind])
|
||||
if kind in ("picture", "clearpicture"):
|
||||
if s.text.strip():
|
||||
set_notes(slide_action(cue), tpl.notes_styles.get(kind) or tpl.default_notes, s.text)
|
||||
else:
|
||||
fill(cue, s, si)
|
||||
out.append(cue)
|
||||
for i in tpl.tail:
|
||||
out.append(clone_cue(tpl.order[i]))
|
||||
|
||||
groups = C.all(pres, "cue_groups")
|
||||
g = C.copy(groups[0]) if groups else pbwire.Node("rv.data.Presentation.CueGroup", [])
|
||||
grp = C.get(g, "group")
|
||||
if grp is not None:
|
||||
C.set(grp, "uuid", uuid_node(new_uuid()))
|
||||
set_repeated(g, "cue_identifiers", [uuid_node(uuid_str(C.get(c, "uuid"))) for c in out])
|
||||
set_repeated(pres, "cue_groups", [g])
|
||||
set_repeated(pres, "cues", out)
|
||||
for f in ("arrangements", "selected_arrangement"):
|
||||
C.remove(pres, f)
|
||||
return C.encode(pres)
|
||||
|
||||
|
||||
def slide_numbers(tpl, plan):
|
||||
"""The ProPresenter slide number (1-based) each plan slide will have in the built deck."""
|
||||
nums = []
|
||||
n = len(tpl.head)
|
||||
title_at = tpl.head.index(tpl.kinds.index("title")) + 1
|
||||
for s in plan.slides:
|
||||
if s.kind == "title":
|
||||
nums.append(title_at)
|
||||
continue
|
||||
kind = kind_for(tpl, s)
|
||||
if kind not in tpl.sample:
|
||||
nums.append(None)
|
||||
continue
|
||||
if kind == "build" and "prebuild" in tpl.sample:
|
||||
n += 1
|
||||
n += 1
|
||||
nums.append(n)
|
||||
return nums
|
||||
|
||||
|
||||
def slide_count(tpl, plan):
|
||||
nums = [x for x in slide_numbers(tpl, plan) if x]
|
||||
return max(nums + [len(tpl.head)]) + len(tpl.tail)
|
||||
|
||||
|
||||
def ensure_pictures(tpl, plan):
|
||||
"""Keep the template's PICTURE / CLEAR PICTURE pair even when the notes have no picture,
|
||||
just before the Sermon in a Sentence (where the template has it)."""
|
||||
if any(s.kind == "picture" for s in plan.slides) or "picture" not in tpl.sample:
|
||||
return
|
||||
at = next((k for k, s in enumerate(plan.slides) if s.kind == "sentence"), len(plan.slides))
|
||||
pair = [notesmod.Slide("picture", [])]
|
||||
if "clearpicture" in tpl.sample:
|
||||
pair.append(notesmod.Slide("clearpicture", []))
|
||||
plan.slides[at:at] = pair
|
||||
plan.skipped = [x[:3] + ((x[3] + len(pair)) if x[3] > at else x[3],) if len(x) > 3 else x
|
||||
for x in plan.skipped]
|
||||
|
||||
|
||||
def speaker_rtfs(tpl, role, preacher):
|
||||
out = []
|
||||
for nm, text in (("role", role), ("preacher", preacher)):
|
||||
st = tpl.style("speaker", nm)
|
||||
if st is not None and text:
|
||||
out.append((nm, st, make_rtf(st, [(0, [(text, False, False)])])))
|
||||
return out
|
||||
|
||||
|
||||
def fill_speaker(tpl, cue, role, preacher, runs_by_name=None):
|
||||
a = slide_action(cue)
|
||||
data = {nm: (st, d) for nm, st, d in speaker_rtfs(tpl, role, preacher)}
|
||||
for el in slide_elements(a):
|
||||
nm = element_name(el).lower()
|
||||
if nm in data:
|
||||
st, d = data[nm]
|
||||
set_text(el, d, (runs_by_name or {}).get(nm), 1.0, st)
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
"""Make Word's automatic list numbers real text before converting a .docx.
|
||||
|
||||
macOS textutil turns numbered lists in a .docx into bullets, which would drop the "1." /
|
||||
"A." in front of points. This writes the numbers ProPresenter should show into a copy of
|
||||
the document (and removes the list formatting) so they survive the conversion.
|
||||
"""
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
_P = re.compile(r"<w:p\b[^>]*?(?:/>|>.*?</w:p>)", re.S)
|
||||
_NUMPR = re.compile(r"<w:numPr>.*?</w:numPr>", re.S)
|
||||
_VAL = r'<w:%s\b[^>]*w:val="([^"]*)"'
|
||||
|
||||
|
||||
def _val(tag, xml, default=None):
|
||||
m = re.search(_VAL % tag, xml)
|
||||
return m.group(1) if m else default
|
||||
|
||||
|
||||
def _roman(n):
|
||||
out = ""
|
||||
for v, s in ((1000, "M"), (900, "CM"), (500, "D"), (400, "CD"), (100, "C"), (90, "XC"), (50, "L"),
|
||||
(40, "XL"), (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I")):
|
||||
while n >= v:
|
||||
out += s
|
||||
n -= v
|
||||
return out
|
||||
|
||||
|
||||
def _letters(n):
|
||||
s = ""
|
||||
while n > 0:
|
||||
n, r = divmod(n - 1, 26)
|
||||
s = chr(65 + r) + s
|
||||
return s
|
||||
|
||||
|
||||
def _fmt(n, kind):
|
||||
if kind == "upperLetter":
|
||||
return _letters(n)
|
||||
if kind == "lowerLetter":
|
||||
return _letters(n).lower()
|
||||
if kind == "upperRoman":
|
||||
return _roman(n)
|
||||
if kind == "lowerRoman":
|
||||
return _roman(n).lower()
|
||||
if kind in ("bullet", "none"):
|
||||
return None
|
||||
return str(n)
|
||||
|
||||
|
||||
def _levels(numbering):
|
||||
"""numId -> {ilvl: (start, numFmt, lvlText)}"""
|
||||
abstract = {}
|
||||
for m in re.finditer(r'<w:abstractNum\b[^>]*w:abstractNumId="(\d+)"[^>]*>(.*?)</w:abstractNum>', numbering, re.S):
|
||||
lv = {}
|
||||
for l in re.finditer(r'<w:lvl\b[^>]*w:ilvl="(\d+)"[^>]*>(.*?)</w:lvl>', m.group(2), re.S):
|
||||
body = l.group(2)
|
||||
lv[int(l.group(1))] = (int(_val("start", body, "1")), _val("numFmt", body, "decimal"),
|
||||
_val("lvlText", body, "%1."))
|
||||
abstract[m.group(1)] = lv
|
||||
nums = {}
|
||||
for m in re.finditer(r'<w:num\b[^>]*w:numId="(\d+)"[^>]*>(.*?)</w:num>', numbering, re.S):
|
||||
aid = _val("abstractNumId", m.group(2))
|
||||
lv = dict(abstract.get(aid, {}))
|
||||
for o in re.finditer(r'<w:lvlOverride\b[^>]*w:ilvl="(\d+)"[^>]*>(.*?)</w:lvlOverride>', m.group(2), re.S):
|
||||
so = _val("startOverride", o.group(2))
|
||||
k = int(o.group(1))
|
||||
if so is not None and k in lv:
|
||||
lv[k] = (int(so),) + lv[k][1:]
|
||||
nums[m.group(1)] = lv
|
||||
return nums
|
||||
|
||||
|
||||
def number_lists(src, dst):
|
||||
"""Copy src .docx to dst with list numbers written out. Returns how many were written."""
|
||||
with zipfile.ZipFile(src) as z:
|
||||
names = z.namelist()
|
||||
files = {n: z.read(n) for n in names}
|
||||
if "word/document.xml" not in files or "word/numbering.xml" not in files:
|
||||
with open(dst, "wb") as f, open(src, "rb") as g:
|
||||
f.write(g.read())
|
||||
return 0
|
||||
doc = files["word/document.xml"].decode("utf-8")
|
||||
nums = _levels(files["word/numbering.xml"].decode("utf-8"))
|
||||
counters = {}
|
||||
written = [0]
|
||||
|
||||
def para(m):
|
||||
p = m.group(0)
|
||||
np_ = _NUMPR.search(p)
|
||||
if not np_:
|
||||
return p
|
||||
num_id = _val("numId", np_.group(0))
|
||||
ilvl = int(_val("ilvl", np_.group(0), "0"))
|
||||
lv = nums.get(num_id, {})
|
||||
if num_id in (None, "0") or ilvl not in lv:
|
||||
return p
|
||||
c = counters.setdefault(num_id, {})
|
||||
for deeper in [k for k in c if k > ilvl]:
|
||||
del c[deeper]
|
||||
start, kind, text = lv[ilvl]
|
||||
c[ilvl] = c.get(ilvl, start - 1) + 1
|
||||
if _fmt(1, kind) is None:
|
||||
return p # bullets: leave as they are
|
||||
label = text
|
||||
for k in range(9):
|
||||
if "%%%d" % (k + 1) in label:
|
||||
s, kd, _ = lv.get(k, (1, "decimal", ""))
|
||||
label = label.replace("%%%d" % (k + 1), _fmt(c.get(k, s), kd) or "")
|
||||
p = p.replace(np_.group(0), "", 1)
|
||||
run = '<w:r><w:t xml:space="preserve">%s</w:t></w:r><w:r><w:tab/></w:r>' % (
|
||||
label.replace("&", "&").replace("<", "<"))
|
||||
if "</w:pPr>" in p:
|
||||
p = p.replace("</w:pPr>", "</w:pPr>" + run, 1)
|
||||
else:
|
||||
p = re.sub(r"(<w:p\b[^>]*>)", r"\1" + run.replace("\\", "\\\\"), p, count=1)
|
||||
written[0] += 1
|
||||
return p
|
||||
|
||||
files["word/document.xml"] = _P.sub(para, doc).encode("utf-8")
|
||||
with zipfile.ZipFile(dst, "w", zipfile.ZIP_DEFLATED) as z:
|
||||
for n in names:
|
||||
z.writestr(n, files[n])
|
||||
return written[0]
|
||||
@@ -0,0 +1,81 @@
|
||||
"""Lay text out with macOS's own text system (the one ProPresenter draws with).
|
||||
|
||||
measure(items) takes [{"rtf": bytes, "w": width}] and returns, for each item, the height
|
||||
the text needs at that width and the font of every run (UTF-16 offsets, as ProPresenter
|
||||
stores them). It runs one osascript (JavaScript for Automation) process for the whole
|
||||
batch, so it only works on a Mac that has the slide fonts installed - the ProPresenter Mac.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
JXA = r"""
|
||||
ObjC.import('AppKit'); ObjC.import('Foundation');
|
||||
function run(argv) {
|
||||
var data = $.NSData.dataWithContentsOfFile(argv[0]);
|
||||
var items = JSON.parse($.NSString.alloc.initWithDataEncoding(data, $.NSUTF8StringEncoding).js);
|
||||
var fm = $.NSFontManager.sharedFontManager;
|
||||
var out = items.map(function (it) {
|
||||
var d = $(it.rtf).dataUsingEncoding($.NSISOLatin1StringEncoding);
|
||||
var as = $.NSAttributedString.alloc.initWithRTFDocumentAttributes(d, null);
|
||||
if (!as || as.isNil()) return {error: 'rtf'};
|
||||
var ts = $.NSTextStorage.alloc.init; ts.setAttributedString(as);
|
||||
var lm = $.NSLayoutManager.alloc.init;
|
||||
var tc = $.NSTextContainer.alloc.initWithSize($.NSMakeSize(it.w, 1000000));
|
||||
tc.lineFragmentPadding = 0; lm.addTextContainer(tc); ts.addLayoutManager(lm);
|
||||
lm.glyphRangeForTextContainer(tc);
|
||||
var used = lm.usedRectForTextContainer(tc);
|
||||
var runs = [], n = Number(as.length);
|
||||
as.enumerateAttributeInRangeOptionsUsingBlock($.NSFontAttributeName, $.NSMakeRange(0, n), 0,
|
||||
function (f, range, stop) {
|
||||
var t = (!f || f.isNil()) ? 0 : Number(fm.traitsOfFont(f));
|
||||
runs.push({loc: Number(range.location), len: Number(range.length),
|
||||
name: (!f || f.isNil()) ? '' : f.fontName.js,
|
||||
family: (!f || f.isNil()) ? '' : f.familyName.js,
|
||||
size: (!f || f.isNil()) ? 0 : Number(f.pointSize),
|
||||
bold: (t & 2) != 0, italic: (t & 1) != 0});
|
||||
});
|
||||
return {h: Number(used.size.height), w: Number(used.size.width), len: n, runs: runs};
|
||||
});
|
||||
return JSON.stringify(out);
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
class Unavailable(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def measure(items, timeout=120):
|
||||
if not items:
|
||||
return []
|
||||
tmp = tempfile.mkdtemp(prefix="msgnotes-")
|
||||
try:
|
||||
src = os.path.join(tmp, "m.js")
|
||||
inp = os.path.join(tmp, "in.json")
|
||||
with open(src, "w") as f:
|
||||
f.write(JXA)
|
||||
with open(inp, "w") as f:
|
||||
json.dump([{"rtf": it["rtf"].decode("latin-1"), "w": it["w"]} for it in items], f)
|
||||
try:
|
||||
p = subprocess.run(["/usr/bin/osascript", "-l", "JavaScript", src, inp],
|
||||
capture_output=True, text=True, timeout=timeout)
|
||||
except (OSError, subprocess.TimeoutExpired) as e:
|
||||
raise Unavailable(str(e))
|
||||
if p.returncode != 0:
|
||||
raise Unavailable(p.stderr.strip()[-400:])
|
||||
out = json.loads(p.stdout)
|
||||
if len(out) != len(items) or any("error" in o for o in out):
|
||||
raise Unavailable("text layout returned an unexpected result")
|
||||
return out
|
||||
finally:
|
||||
for fn in ("m.js", "in.json"):
|
||||
try:
|
||||
os.remove(os.path.join(tmp, fn))
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
os.rmdir(tmp)
|
||||
except OSError:
|
||||
pass
|
||||
+537
@@ -0,0 +1,537 @@
|
||||
"""Sermon notes (RTF) -> a slide plan.
|
||||
|
||||
The notes follow a loose house style:
|
||||
* line 1 is the message title (largest), line 2 the subtitle, line 3 the main passage
|
||||
* a scripture is a paragraph that starts with a reference ("John 1:12 ESV ...");
|
||||
bold words inside the verse text are emphasis and stay bold on the slide
|
||||
* other text is a point; lines with no blank line between them belong together:
|
||||
a lead-in ("...:" / "...?" / italic) plus one point make one slide, three or more
|
||||
lines (or a list) make a build
|
||||
* "Sermon in a Sentence:" introduces the closing sentence
|
||||
* text in (parentheses), after //, or in colour without a reference is an
|
||||
instruction for the team, never slide content
|
||||
"""
|
||||
import re
|
||||
|
||||
import rtf
|
||||
|
||||
BOOKS = [
|
||||
"Genesis", "Exodus", "Leviticus", "Numbers", "Deuteronomy", "Joshua", "Judges", "Ruth",
|
||||
"1 Samuel", "2 Samuel", "1 Kings", "2 Kings", "1 Chronicles", "2 Chronicles", "Ezra",
|
||||
"Nehemiah", "Esther", "Job", "Psalms", "Psalm", "Proverbs", "Ecclesiastes",
|
||||
"Song of Solomon", "Song of Songs", "Isaiah", "Jeremiah", "Lamentations", "Ezekiel",
|
||||
"Daniel", "Hosea", "Joel", "Amos", "Obadiah", "Jonah", "Micah", "Nahum", "Habakkuk",
|
||||
"Zephaniah", "Haggai", "Zechariah", "Malachi", "Matthew", "Mark", "Luke", "John", "Acts",
|
||||
"Romans", "1 Corinthians", "2 Corinthians", "Galatians", "Ephesians", "Philippians",
|
||||
"Colossians", "1 Thessalonians", "2 Thessalonians", "1 Timothy", "2 Timothy", "Titus",
|
||||
"Philemon", "Hebrews", "James", "1 Peter", "2 Peter", "1 John", "2 John", "3 John", "Jude",
|
||||
"Revelation",
|
||||
# common abbreviations
|
||||
"Gen", "Ex", "Exod", "Lev", "Num", "Deut", "Josh", "Judg", "1 Sam", "2 Sam", "1 Kgs",
|
||||
"2 Kgs", "1 Chr", "2 Chr", "Neh", "Esth", "Ps", "Psa", "Prov", "Eccl", "Eccles", "Isa",
|
||||
"Jer", "Lam", "Ezek", "Dan", "Hos", "Obad", "Mic", "Nah", "Hab", "Zeph", "Hag", "Zech",
|
||||
"Mal", "Matt", "Mt", "Mk", "Lk", "Jn", "Rom", "1 Cor", "2 Cor", "Gal", "Eph", "Phil",
|
||||
"Col", "1 Thess", "2 Thess", "1 Tim", "2 Tim", "Philem", "Heb", "Jas", "1 Pet", "2 Pet",
|
||||
"1 Jn", "2 Jn", "3 Jn", "Rev",
|
||||
]
|
||||
VERSIONS = ["ESV", "NLT", "NIV", "KJV", "NKJV", "NASB", "NASB95", "NASB1995", "NASB2020", "CSB",
|
||||
"HCSB", "MSG", "AMP", "AMPC", "TPT", "NRSV", "NRSVUE", "RSV", "GNT", "GNB", "NET",
|
||||
"CEV", "ERV", "NCV", "NIRV", "NLV", "ICB", "WEB", "YLT", "ASV", "BSB", "LSB", "MEV",
|
||||
"VOICE", "CEB", "TLB", "PHILLIPS", "GW", "ISV", "CJB", "LEB", "NABRE", "NJB", "JB",
|
||||
"TEV", "EHV", "NOG", "RGT", "TLV", "WEB", "DLNT", "NMB", "ESVUK", "NIVUK", "BBE"]
|
||||
|
||||
|
||||
def _book_re():
|
||||
names = sorted(BOOKS, key=len, reverse=True)
|
||||
parts = []
|
||||
for b in names:
|
||||
m = re.match(r"([123]) (.*)", b)
|
||||
if m:
|
||||
n = m.group(1)
|
||||
roman = {"1": "I", "2": "II", "3": "III"}[n]
|
||||
word = {"1": "First", "2": "Second", "3": "Third"}[n]
|
||||
parts.append(r"(?:%s|%s|%s)\s*%s" % (n, roman, word, re.escape(m.group(2))))
|
||||
else:
|
||||
parts.append(re.escape(b))
|
||||
return "(?:%s)" % "|".join(parts)
|
||||
|
||||
|
||||
_DASH = r"[-\u2010\u2011\u2012\u2013\u2014]"
|
||||
_VERSE = r"\d{1,3}[a-c]?"
|
||||
_RANGE = r"%s(?:\s*%s\s*\d{1,3}(?::\d{1,3})?[a-c]?)?" % (_VERSE, _DASH)
|
||||
REF_RE = re.compile(
|
||||
r"^(?P<ref>(?P<book>%s)\.?\s+(?P<chap>\d{1,3})"
|
||||
r"(?:\s*:\s*(?P<v1>%s)(?:\s*%s\s*(?:\d{1,3}\s*:\s*)?\d{1,3}[a-c]?)?(?:\s*[,;]\s*(?:\d{1,3}:)?%s)*)?"
|
||||
r"(?:\s*%s\s*\d{1,3}(?::\d{1,3})?)?"
|
||||
r"(?:\s*\(?\s*(?P<ver>%s)\s*\)?)?)"
|
||||
r"(?=$|[\s\u00a0,.;:\u201c\u201d\u2018\u2019\"'(\u2026\u2014-])" % (
|
||||
_book_re(), _VERSE, _DASH, _RANGE, _DASH, "|".join(sorted(set(VERSIONS), key=len, reverse=True))),
|
||||
re.I)
|
||||
|
||||
SENTENCE_RE = re.compile(r"^\s*sermon\s+in\s+a\s+sentence\s*[:\-\u2013\u2014]?\s*", re.I)
|
||||
NUMBERED_RE = re.compile(r"^\s*(?:\d{1,2}|[A-Z])[.)]\s+")
|
||||
BULLETS = "\u25cf\u2022\u2043\u25aa\u25e6\u25a0\u2219\u00b7\u27a2\u2013\u2014-*\u25ba\u2794\u2192"
|
||||
LIST_MARK_RE = re.compile(r"^\t?\s*([%s]|\d{1,2}[.)]|[A-Za-z][.)])\t+" % re.escape(BULLETS))
|
||||
QUOTE_OPEN = "\u201c\"\u2018'"
|
||||
|
||||
|
||||
class Line:
|
||||
"""One paragraph of slide text: segments of (text, bold, italic); lead = lead-in style."""
|
||||
__slots__ = ("segs", "lead")
|
||||
|
||||
def __init__(self, segs, lead=False):
|
||||
self.segs = [s for s in segs if s[0]]
|
||||
self.lead = lead
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return "".join(s[0] for s in self.segs)
|
||||
|
||||
def segs_plain(self):
|
||||
return [(self.text, False, False)]
|
||||
|
||||
def __repr__(self):
|
||||
return "Line(%r%s)" % (self.text, ", lead" if self.lead else "")
|
||||
|
||||
|
||||
class Slide:
|
||||
__slots__ = ("kind", "lines", "ref", "flags", "src", "part", "scale", "notes")
|
||||
|
||||
def __init__(self, kind, lines=None, ref="", flags=None, src=None):
|
||||
self.kind = kind # title point subpoint build scripture quote sentence
|
||||
self.lines = lines or []
|
||||
self.ref = ref
|
||||
self.flags = flags or [] # human-readable remarks shown in the review
|
||||
self.src = src # paragraph number in the notes
|
||||
self.part = None # (k, n) when a long scripture was split
|
||||
self.scale = {} # element name -> font scale applied to fit
|
||||
self.notes = None
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return "\n".join(l.text for l in self.lines)
|
||||
|
||||
def __repr__(self):
|
||||
return "Slide(%s, %r%s)" % (self.kind, self.text[:60], (", ref=%r" % self.ref) if self.ref else "")
|
||||
|
||||
|
||||
class Plan:
|
||||
def __init__(self):
|
||||
self.slides = []
|
||||
self.skipped = [] # (paragraph no, text, why)
|
||||
self.warnings = []
|
||||
|
||||
@property
|
||||
def title_slide(self):
|
||||
for s in self.slides:
|
||||
if s.kind == "title":
|
||||
return s
|
||||
return None
|
||||
|
||||
def title_parts(self):
|
||||
t = self.title_slide
|
||||
out = {"title": "", "subtitle": "", "reference": ""}
|
||||
if t:
|
||||
roles = title_roles(t.lines)
|
||||
for ln, role in zip(t.lines, roles):
|
||||
out[role] = ln.text
|
||||
return out
|
||||
|
||||
|
||||
def title_roles(lines):
|
||||
n = len(lines)
|
||||
if n == 0:
|
||||
return []
|
||||
if n == 1:
|
||||
return ["title"]
|
||||
if n == 2:
|
||||
return ["title", "reference" if parse_ref(lines[1].text, ref_only=True) else "subtitle"]
|
||||
return ["title", "subtitle", "reference"] + ["subtitle"] * (n - 3)
|
||||
|
||||
|
||||
def parse_ref(text, ref_only=False):
|
||||
"""Return (reference, rest) if text starts with a Bible reference, else None."""
|
||||
t = text.replace("\u00a0", " ").strip()
|
||||
m = REF_RE.match(t)
|
||||
if not m:
|
||||
return None
|
||||
ref = m.group("ref").strip()
|
||||
rest = t[m.end():].strip()
|
||||
has_verse = m.group("v1") is not None
|
||||
if ref_only:
|
||||
return (ref, "") if not rest else None
|
||||
if not has_verse and not m.group("ver") and rest:
|
||||
return None # "Romans 8 is ..." is a sentence, not a reference
|
||||
return ref, rest
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------- reading
|
||||
|
||||
class P:
|
||||
"""A notes paragraph with the features the classifier looks at."""
|
||||
|
||||
def __init__(self, idx, doc, para):
|
||||
self.idx = idx
|
||||
self.runs = []
|
||||
for r in para.runs:
|
||||
fname = doc.fonts.get(r.state.get("f", 0), "").lower()
|
||||
bold = "b" in r.state or any(k in fname for k in ("bold", "black", "heavy", "semibold"))
|
||||
ital = "i" in r.state or "italic" in fname or "oblique" in fname
|
||||
ci = r.state.get("cf", 0) or 0
|
||||
col = doc.colors[ci] if ci < len(doc.colors) else None
|
||||
if col in ((0, 0, 0), (255, 255, 255)):
|
||||
col = None
|
||||
self.runs.append((r.text, bold, ital, (r.state.get("fs", 24) or 24) / 2.0, col))
|
||||
self.raw = "".join(r[0] for r in self.runs)
|
||||
self.text = clean(self.raw)
|
||||
vis = [r for r in self.runs if r[0].strip()]
|
||||
self.size = max([r[3] for r in vis] or [12])
|
||||
self.bold = bool(vis) and all(r[1] for r in vis)
|
||||
self.italic = bool(vis) and all(r[2] for r in vis)
|
||||
self.color = vis[0][4] if vis else None
|
||||
m = LIST_MARK_RE.match(self.raw)
|
||||
self.list_mark = m.group(1) if m else None
|
||||
|
||||
@property
|
||||
def blank(self):
|
||||
return not self.text.strip()
|
||||
|
||||
|
||||
def clean(text):
|
||||
t = text.replace("\u2028", "\n").replace("\u2029", "\n")
|
||||
t = t.replace("\u2026", "...").replace("\u00a0", " ")
|
||||
t = re.sub(r"[ \t]+\n", "\n", t)
|
||||
return t.strip()
|
||||
|
||||
|
||||
def segments(runs, start_char=0, emphasis=True):
|
||||
"""Runs -> [(text, bold, italic)] starting at character offset start_char."""
|
||||
out, pos = [], 0
|
||||
for text, bold, ital, size, col in runs:
|
||||
a, b = pos, pos + len(text)
|
||||
pos = b
|
||||
if b <= start_char:
|
||||
continue
|
||||
piece = text[max(0, start_char - a):]
|
||||
if not emphasis:
|
||||
bold = ital = False
|
||||
out.append([piece, bold, ital])
|
||||
# verse numbers are never emphasis
|
||||
for s in out:
|
||||
if s[1] and re.fullmatch(r"\s*\d{1,3}\s*", s[0]):
|
||||
s[1] = False
|
||||
# whitespace between two bold words is bold too ("**Love one another**")
|
||||
for k in range(1, len(out) - 1):
|
||||
if not out[k][1] and not out[k][0].strip() and out[k - 1][1] and out[k + 1][1]:
|
||||
out[k][1] = True
|
||||
merged = []
|
||||
for s in out:
|
||||
s[0] = s[0].replace("\u2026", "...").replace("\u2028", " ").replace("\u00a0", " ")
|
||||
if merged and merged[-1][1] == s[1] and merged[-1][2] == s[2]:
|
||||
merged[-1][0] += s[0]
|
||||
else:
|
||||
merged.append(s)
|
||||
# trim outer whitespace
|
||||
while merged and not merged[0][0].strip():
|
||||
merged.pop(0)
|
||||
while merged and not merged[-1][0].strip():
|
||||
merged.pop()
|
||||
if merged:
|
||||
merged[0][0] = merged[0][0].lstrip()
|
||||
merged[-1][0] = merged[-1][0].rstrip()
|
||||
return [tuple(s) for s in merged if s[0]]
|
||||
|
||||
|
||||
def strip_list_mark(text):
|
||||
m = LIST_MARK_RE.match(text)
|
||||
if not m:
|
||||
t = text.strip()
|
||||
mb = re.match(r"^[%s]\s+(?=\S)" % re.escape(BULLETS.replace("*", "")), t)
|
||||
if mb and not re.match(r"^[\u2013\u2014-]\s*\d", t):
|
||||
return t[mb.end():], t[0]
|
||||
return t, None
|
||||
mark = m.group(1)
|
||||
rest = text[m.end():].strip()
|
||||
if mark in BULLETS:
|
||||
return rest, mark
|
||||
return "%s %s" % (mark if mark[-1] in ".)" else mark + ".", rest), mark
|
||||
|
||||
|
||||
def looks_like_point(text):
|
||||
"""House style marks emphasis with ALL-CAPS words; notes to the team don't."""
|
||||
caps = [w for w in re.findall(r"[A-Za-z\u2019']+", text) if len(w) >= 3 and w.isupper()
|
||||
and w not in ("ESV", "NLT", "NIV", "KJV", "LORD", "GOD", "MSG", "NKJV", "AMP")]
|
||||
return len(caps) >= 2
|
||||
|
||||
|
||||
PICTURE_RE = re.compile(r"^\W*(pic|pics|picture|pictures|photo|photos|image|images|graphic|graphics|img)\b"
|
||||
r"|\.(jpe?g|png|heic|gif|tiff?)\b", re.I)
|
||||
|
||||
|
||||
def is_picture(text):
|
||||
"""'[PICTURE: Dog Boy]', '<pic - ...GossipsBridle.jpg>' mark where a picture goes."""
|
||||
return bool(PICTURE_RE.search(text.strip()))
|
||||
|
||||
|
||||
def is_annotation(p):
|
||||
t = p.text.strip()
|
||||
if not t:
|
||||
return False
|
||||
if t.startswith("//"):
|
||||
return True
|
||||
if (t[0] == "(" and t.endswith(")")) or (t[0] == "[" and t.endswith("]")):
|
||||
return True
|
||||
if re.match(r"^\(?\s*(PP|STAGE MANAGER|SM|TECH|SLIDE[S]?|NOTE|AV|LIGHTS?)\b\s*[-:\u2013\u2014]", t, re.I):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def parse(data):
|
||||
"""RTF bytes -> Plan."""
|
||||
doc = rtf.read(data)
|
||||
paras = [P(i, doc, p) for i, p in enumerate(doc.paras)]
|
||||
plan = Plan()
|
||||
|
||||
# ---- header: title, subtitle, main passage
|
||||
body = [p for p in paras]
|
||||
k = 0
|
||||
while k < len(body) and (body[k].blank or is_annotation(body[k])):
|
||||
if is_annotation(body[k]):
|
||||
plan.skipped.append((body[k].idx, body[k].text, "instruction", 0))
|
||||
k += 1
|
||||
head = []
|
||||
if k < len(body):
|
||||
head.append(("title", body[k]))
|
||||
k += 1
|
||||
# subtitle: the next paragraph unless it is clearly content
|
||||
j = k
|
||||
while j < len(body) and body[j].blank and j - k < 1:
|
||||
j += 1
|
||||
if j < len(body) and not body[j].blank:
|
||||
cand = body[j]
|
||||
if parse_ref(cand.text, ref_only=True):
|
||||
head.append(("reference", cand))
|
||||
k = j + 1
|
||||
elif (not parse_ref(cand.text) and not SENTENCE_RE.match(cand.text)
|
||||
and not NUMBERED_RE.match(cand.text) and not cand.list_mark
|
||||
and not is_annotation(cand) and j == k):
|
||||
head.append(("subtitle", cand))
|
||||
k = j + 1
|
||||
j = k
|
||||
while j < len(body) and body[j].blank and j - k < 1:
|
||||
j += 1
|
||||
if j < len(body) and parse_ref(body[j].text, ref_only=True):
|
||||
head.append(("reference", body[j]))
|
||||
k = j + 1
|
||||
title = Slide("title", src=head[0][1].idx if head else None)
|
||||
for role, p in head:
|
||||
title.lines.append(Line([(p.text.replace("\n", " "), False, False)]))
|
||||
if head:
|
||||
plan.slides.append(title)
|
||||
else:
|
||||
plan.warnings.append("No title found at the top of the notes.")
|
||||
|
||||
# ---- body
|
||||
group = [] # statement paragraphs waiting to become a point/build
|
||||
force_build = False # "(come on one at a time)"
|
||||
i = k
|
||||
n = len(body)
|
||||
|
||||
def flush():
|
||||
nonlocal group, force_build
|
||||
if group:
|
||||
plan.slides.extend(statements_to_slides(group, force_build))
|
||||
group = []
|
||||
force_build = False
|
||||
|
||||
while i < n:
|
||||
p = body[i]
|
||||
if p.blank:
|
||||
flush()
|
||||
i += 1
|
||||
continue
|
||||
if (is_annotation(p) or p.color) and is_picture(p.text) and not parse_ref(p.text):
|
||||
flush()
|
||||
plan.slides.append(Slide("picture", [Line([(p.text.strip(), False, False)])], src=p.idx))
|
||||
i += 1
|
||||
continue
|
||||
if is_annotation(p):
|
||||
flush()
|
||||
plan.skipped.append((p.idx, p.text, "instruction", len(plan.slides)))
|
||||
if re.search(r"one at a time|build", p.text, re.I):
|
||||
flush()
|
||||
force_build = True
|
||||
i += 1
|
||||
continue
|
||||
m = SENTENCE_RE.match(p.text)
|
||||
if m:
|
||||
flush()
|
||||
rest = p.text[m.end():].strip()
|
||||
lines = []
|
||||
if rest:
|
||||
lines.append(Line([(rest, False, False)]))
|
||||
i += 1
|
||||
if not lines:
|
||||
# following paragraphs up to the next blank line
|
||||
while i < n and body[i].blank:
|
||||
i += 1
|
||||
while i < n and not body[i].blank and not parse_ref(body[i].text):
|
||||
if not is_annotation(body[i]):
|
||||
lines.append(Line([(body[i].text.replace("\n", " "), False, False)]))
|
||||
i += 1
|
||||
s = Slide("sentence", lines, src=p.idx)
|
||||
if not lines:
|
||||
s.flags.append("No sentence found after 'Sermon in a Sentence'")
|
||||
plan.slides.append(s)
|
||||
continue
|
||||
r = parse_ref(p.text)
|
||||
if r and r[1]:
|
||||
flush()
|
||||
ref, rest = r
|
||||
s = scripture_slide(p, ref)
|
||||
i += 1
|
||||
# continuation paragraphs (poetry, multi-paragraph quotes)
|
||||
while i < n and not body[i].blank:
|
||||
q = body[i]
|
||||
if parse_ref(q.text) or is_annotation(q) or SENTENCE_RE.match(q.text):
|
||||
break
|
||||
if q.bold and not any(seg[0].strip() and not seg[1] for seg in segments(q.runs)):
|
||||
break # an all-bold line is a point, not more verse text
|
||||
if q.color != p.color or q.size > p.size + 1:
|
||||
break
|
||||
s.lines.append(Line(segments(q.runs)))
|
||||
i += 1
|
||||
plan.slides.append(s)
|
||||
continue
|
||||
if r and not r[1]:
|
||||
# a bare reference in the body: keep it as a scripture slide without text
|
||||
flush()
|
||||
s = Slide("scripture", [], ref=r[0], src=p.idx)
|
||||
s.flags.append("Reference with no verse text")
|
||||
plan.slides.append(s)
|
||||
i += 1
|
||||
continue
|
||||
if p.color and not force_build and not looks_like_point(p.text) and not NUMBERED_RE.match(p.text):
|
||||
# coloured text that is not scripture is almost always a note to the team
|
||||
flush()
|
||||
plan.skipped.append((p.idx, p.text, "coloured note", len(plan.slides)))
|
||||
i += 1
|
||||
continue
|
||||
group.append(p)
|
||||
i += 1
|
||||
flush()
|
||||
|
||||
if not any(s.kind == "sentence" for s in plan.slides):
|
||||
plan.warnings.append("No 'Sermon in a Sentence' found - that slide will be left out.")
|
||||
add_clear_pictures(plan)
|
||||
return plan
|
||||
|
||||
|
||||
def add_clear_pictures(plan):
|
||||
"""A CLEAR PICTURE slide after every run of picture slides (as the template pairs them)."""
|
||||
out = []
|
||||
for k, s in enumerate(plan.slides):
|
||||
out.append(s)
|
||||
nxt = plan.slides[k + 1] if k + 1 < len(plan.slides) else None
|
||||
if s.kind == "picture" and (nxt is None or nxt.kind not in ("picture", "clearpicture")):
|
||||
out.append(Slide("clearpicture", [], src=s.src))
|
||||
# keep "left off" notes pointing at the same slide
|
||||
if len(out) != len(plan.slides):
|
||||
mapping, j = {}, 0
|
||||
for k, s in enumerate(out):
|
||||
if j < len(plan.slides) and s is plan.slides[j]:
|
||||
mapping[j] = k
|
||||
j += 1
|
||||
mapping[len(plan.slides)] = len(out)
|
||||
plan.skipped = [x[:3] + (mapping.get(x[3], x[3]),) if len(x) > 3 else x for x in plan.skipped]
|
||||
plan.slides = out
|
||||
|
||||
|
||||
def scripture_slide(p, ref):
|
||||
# character offset where the verse text starts (after the reference)
|
||||
raw = p.raw.replace("\u00a0", " ")
|
||||
lead_ws = len(raw) - len(raw.lstrip())
|
||||
m = REF_RE.match(raw.strip())
|
||||
start = lead_ws + (m.end() if m else 0)
|
||||
segs = segments(p.runs, start)
|
||||
# a verse number repeated right after the reference ("1 Timothy 2:1-4 ESV 1 First...")
|
||||
v1 = m.group("v1") if m else None
|
||||
if segs and v1:
|
||||
t = segs[0][0]
|
||||
mm = re.match(r"^%s\s+" % re.escape(v1.rstrip("abc")), t)
|
||||
if mm:
|
||||
segs[0] = (t[mm.end():],) + tuple(segs[0][1:])
|
||||
if segs and segs[0][0].startswith((":", "-", "\u2013")):
|
||||
segs[0] = (segs[0][0][1:].lstrip(),) + tuple(segs[0][1:])
|
||||
lines = [Line([s for s in segs if s[0]])]
|
||||
return Slide("scripture", lines, ref=normalize_ref(ref), src=p.idx)
|
||||
|
||||
|
||||
def normalize_ref(ref):
|
||||
r = re.sub(r"\s+", " ", ref.replace("\u00a0", " ")).strip()
|
||||
r = re.sub(r"\(\s*([A-Z0-9]{2,9})\s*\)$", r"\1", r) # "James 1:1 (ESV)" -> "James 1:1 ESV"
|
||||
r = re.sub(r"\s*:\s*", ":", r) # "Proverbs 3: 5-6" -> "Proverbs 3:5-6"
|
||||
r = re.sub(r"(\d[a-c]?)([A-Z]{2,})$", r"\1 \2", r) # "James 1:14ESV" -> "James 1:14 ESV"
|
||||
return r
|
||||
|
||||
|
||||
def statements_to_slides(group, force_build=False):
|
||||
"""Consecutive statement paragraphs -> Point / Build slides."""
|
||||
lines = []
|
||||
for p in group:
|
||||
for part in p.text.split("\n"):
|
||||
if not part.strip():
|
||||
continue
|
||||
text, mark = strip_list_mark(part)
|
||||
lines.append((text, p, mark))
|
||||
if not lines:
|
||||
return []
|
||||
src = group[0].idx
|
||||
lead = is_lead(lines[0][0], lines[0][1]) and len(lines) > 1
|
||||
marked = [l for l in lines if l[2] or NUMBERED_RE.match(l[0])]
|
||||
quote = quote_slide(lines, src)
|
||||
if quote:
|
||||
return [quote]
|
||||
n = len(lines)
|
||||
if force_build and n >= 2:
|
||||
kind = "build"
|
||||
elif n == 1:
|
||||
kind = "point"
|
||||
elif n == 2 and lead:
|
||||
kind = "point"
|
||||
elif n == 2:
|
||||
a, b = lines[0][0], lines[1][0]
|
||||
parallel = (len(marked) == 2 or a.split(" ")[0].lower() == b.split(" ")[0].lower())
|
||||
kind = "build" if parallel else "point"
|
||||
else:
|
||||
kind = "build"
|
||||
out_lines = []
|
||||
for k, (text, p, mark) in enumerate(lines):
|
||||
out_lines.append(Line([(text, False, False)], lead=(k == 0 and lead and kind == "point")))
|
||||
s = Slide(kind, out_lines, src=src)
|
||||
plain = [p for p in group if not p.bold and not p.italic]
|
||||
if plain and all(p.size <= 15 for p in plain):
|
||||
s.flags.append("Small plain text in the notes - check it belongs on a slide")
|
||||
if kind == "build" and n > 5:
|
||||
s.flags.append("%d lines in one build" % n)
|
||||
return [s]
|
||||
|
||||
|
||||
def is_lead(text, p):
|
||||
t = text.strip()
|
||||
return t.endswith(":") or t.endswith("?") or (p.italic and not p.bold) or (p.italic and p.bold)
|
||||
|
||||
|
||||
def quote_slide(lines, src):
|
||||
if not lines or lines[0][0][:1] not in QUOTE_OPEN:
|
||||
return None
|
||||
first = lines[0]
|
||||
m = re.match(r"^(.*[\u201d\"][.,]?)\s*[-\u2013\u2014~]\s*([^\u201d\"]{2,60})$", first[0])
|
||||
if len(lines) == 1 and m:
|
||||
return Slide("quote", [Line([(m.group(1).strip(), False, False)]),
|
||||
Line([("\u2014 " + m.group(2).strip(), False, False)])], src=src)
|
||||
attribution = [l for l in lines[1:] if re.match(r"^\s*[-\u2013\u2014~]\s*\S", l[0])]
|
||||
if not (first[1].italic or attribution):
|
||||
return None
|
||||
out = [Line([(l[0], False, False)]) for l in lines]
|
||||
return Slide("quote", out, src=src)
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
"""Schema-guided protobuf wire codec that keeps every field (known or not) in order."""
|
||||
import struct
|
||||
|
||||
|
||||
def _varint(buf, i):
|
||||
shift = result = 0
|
||||
while True:
|
||||
b = buf[i]
|
||||
i += 1
|
||||
result |= (b & 0x7F) << shift
|
||||
if not b & 0x80:
|
||||
return result, i
|
||||
shift += 7
|
||||
|
||||
|
||||
def _put_varint(n):
|
||||
out = bytearray()
|
||||
n &= (1 << 64) - 1
|
||||
while True:
|
||||
b = n & 0x7F
|
||||
n >>= 7
|
||||
if n:
|
||||
out.append(b | 0x80)
|
||||
else:
|
||||
out.append(b)
|
||||
return bytes(out)
|
||||
|
||||
|
||||
class Node:
|
||||
__slots__ = ("type", "fields")
|
||||
|
||||
def __init__(self, type_, fields=None):
|
||||
self.type = type_
|
||||
self.fields = fields if fields is not None else [] # [num, wiretype, value]
|
||||
|
||||
|
||||
class Codec:
|
||||
def __init__(self, schema):
|
||||
self.schema = schema
|
||||
self.names = {m: {v[0]: int(k) for k, v in f.items()} for m, f in schema.items()}
|
||||
|
||||
# ---- wire ----
|
||||
def decode(self, buf, mtype):
|
||||
fields, i, n = [], 0, len(buf)
|
||||
spec = self.schema.get(mtype, {})
|
||||
while i < n:
|
||||
key, i = _varint(buf, i)
|
||||
num, wt = key >> 3, key & 7
|
||||
if wt == 0:
|
||||
val, i = _varint(buf, i)
|
||||
elif wt == 1:
|
||||
val, i = buf[i:i + 8], i + 8
|
||||
elif wt == 5:
|
||||
val, i = buf[i:i + 4], i + 4
|
||||
elif wt == 2:
|
||||
ln, i = _varint(buf, i)
|
||||
val, i = buf[i:i + ln], i + ln
|
||||
f = spec.get(str(num))
|
||||
if f and f[1] in self.schema:
|
||||
val = self.decode(val, f[1])
|
||||
else:
|
||||
raise ValueError("unsupported wire type %d in %s" % (wt, mtype))
|
||||
fields.append([num, wt, val])
|
||||
return Node(mtype, fields)
|
||||
|
||||
def encode(self, node):
|
||||
out = bytearray()
|
||||
for num, wt, val in node.fields:
|
||||
out += _put_varint((num << 3) | wt)
|
||||
if wt == 0:
|
||||
out += _put_varint(val)
|
||||
elif wt in (1, 5):
|
||||
out += val
|
||||
else:
|
||||
b = self.encode(val) if isinstance(val, Node) else val
|
||||
out += _put_varint(len(b)) + b
|
||||
return bytes(out)
|
||||
|
||||
# ---- access by field name ----
|
||||
def num(self, node, name):
|
||||
return self.names[node.type][name]
|
||||
|
||||
def all(self, node, name):
|
||||
n = self.num(node, name)
|
||||
return [f[2] for f in node.fields if f[0] == n]
|
||||
|
||||
def get(self, node, name, default=None):
|
||||
v = self.all(node, name)
|
||||
return v[0] if v else default
|
||||
|
||||
def path(self, node, *names):
|
||||
for nm in names:
|
||||
if node is None:
|
||||
return None
|
||||
node = self.get(node, nm)
|
||||
return node
|
||||
|
||||
def remove(self, node, name):
|
||||
n = self.num(node, name)
|
||||
node.fields = [f for f in node.fields if f[0] != n]
|
||||
|
||||
def set(self, node, name, value, wt=None):
|
||||
"""Replace (or insert, keeping field-number order) a singular field."""
|
||||
n = self.num(node, name)
|
||||
if wt is None:
|
||||
wt = 2 if isinstance(value, (bytes, Node)) else 0
|
||||
for f in node.fields:
|
||||
if f[0] == n:
|
||||
f[1], f[2] = wt, value
|
||||
return
|
||||
idx = len(node.fields)
|
||||
for k, f in enumerate(node.fields):
|
||||
if f[0] > n:
|
||||
idx = k
|
||||
break
|
||||
node.fields.insert(idx, [n, wt, value])
|
||||
|
||||
def new(self, mtype):
|
||||
return Node(mtype, [])
|
||||
|
||||
# ---- typed helpers ----
|
||||
def string(self, node, name, default=""):
|
||||
v = self.get(node, name)
|
||||
return v.decode("utf-8") if isinstance(v, (bytes, bytearray)) else default
|
||||
|
||||
def double(self, node, name, default=0.0):
|
||||
v = self.get(node, name)
|
||||
return struct.unpack("<d", v)[0] if v is not None else default
|
||||
|
||||
def set_double(self, node, name, x):
|
||||
self.set(node, name, struct.pack("<d", x), wt=1)
|
||||
|
||||
def walk(self, node, fn, parent_field=None):
|
||||
"""fn(node, parent_field_name) for every message node, depth first."""
|
||||
fn(node, parent_field)
|
||||
spec = self.schema.get(node.type, {})
|
||||
for num, wt, val in node.fields:
|
||||
if isinstance(val, Node):
|
||||
f = spec.get(str(num))
|
||||
self.walk(val, fn, f[0] if f else None)
|
||||
|
||||
def copy(self, node):
|
||||
return self.decode(self.encode(node), node.type)
|
||||
|
||||
|
||||
def dump(codec, node, indent=0, out=None, maxbytes=400):
|
||||
"""Readable text dump (similar to protobuf text format)."""
|
||||
lines = out if out is not None else []
|
||||
spec = codec.schema.get(node.type, {})
|
||||
pad = " " * indent
|
||||
for num, wt, val in node.fields:
|
||||
f = spec.get(str(num))
|
||||
name = f[0] if f else "#%d" % num
|
||||
if isinstance(val, Node):
|
||||
lines.append("%s%s {" % (pad, name))
|
||||
dump(codec, val, indent + 1, lines, maxbytes)
|
||||
lines.append(pad + "}")
|
||||
elif wt == 0:
|
||||
lines.append("%s%s: %d" % (pad, name, val))
|
||||
elif wt == 1:
|
||||
lines.append("%s%s: %r" % (pad, name, struct.unpack("<d", val)[0] if f and f[1] == "double" else val))
|
||||
elif wt == 5:
|
||||
lines.append("%s%s: %r" % (pad, name, struct.unpack("<f", val)[0] if f and f[1] == "float" else val))
|
||||
else:
|
||||
try:
|
||||
s = val.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
s = repr(val)
|
||||
if len(s) > maxbytes:
|
||||
s = s[:maxbytes] + "...(%d)" % len(s)
|
||||
lines.append("%s%s: %r" % (pad, name, s))
|
||||
return lines
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
"""The slide plan as plain text, for editing in TextEdit, and back."""
|
||||
import re
|
||||
|
||||
import notes as notesmod
|
||||
|
||||
HEADER = """# Slide list for: {name}
|
||||
# Change anything, then SAVE (Cmd-S) and go back to the Terminal window and press Enter.
|
||||
#
|
||||
# Every slide starts with its type in [brackets]:
|
||||
# [Title] [Point] [Build] [Subpoint] [Quote] [Sentence] [Scripture] <reference>
|
||||
# [Picture] <what picture> [Clear Picture]
|
||||
# The lines under it are the slide's lines (one line = one paragraph; in a Build, one step).
|
||||
# In a Point, start a line with > to make it the italic lead-in line.
|
||||
# In a Scripture, put **double asterisks** around bold words and *single ones* around italic words.
|
||||
# Delete a slide by deleting its whole block. Blank lines and lines starting with "# " are ignored.
|
||||
# Lines starting with #! are notes that were left off the slides (shown for reference).
|
||||
"""
|
||||
|
||||
KINDS = {"title": "title", "point": "point", "subpoint": "subpoint", "sub point": "subpoint",
|
||||
"build": "build", "scripture": "scripture", "quote": "quote", "sentence": "sentence",
|
||||
"sermon in a sentence": "sentence", "picture": "picture", "clear picture": "clearpicture",
|
||||
"clearpicture": "clearpicture"}
|
||||
NAMES = {"title": "Title", "point": "Point", "subpoint": "Subpoint", "build": "Build",
|
||||
"scripture": "Scripture", "quote": "Quote", "sentence": "Sentence", "picture": "Picture",
|
||||
"clearpicture": "Clear Picture"}
|
||||
|
||||
|
||||
class ParseError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def dump(plan, name=""):
|
||||
out = [HEADER.format(name=name)]
|
||||
left = {}
|
||||
for item in plan.skipped:
|
||||
left.setdefault(item[3] if len(item) > 3 else len(plan.slides), []).append(item)
|
||||
for k, s in enumerate(plan.slides):
|
||||
for item in left.get(k, []):
|
||||
out.append("#! %s" % item[1].replace("\n", " "))
|
||||
out.append("")
|
||||
if s.kind in ("picture", "clearpicture"):
|
||||
out.append(("[%s] %s" % (NAMES[s.kind], s.text.replace("\n", " "))).rstrip())
|
||||
continue
|
||||
if s.kind == "scripture":
|
||||
out.append("[Scripture] %s" % s.ref)
|
||||
for ln in s.lines:
|
||||
out.append("".join(mark(t, b, i) for t, b, i in ln.segs))
|
||||
else:
|
||||
out.append("[%s]" % NAMES.get(s.kind, "Point"))
|
||||
for ln in s.lines:
|
||||
out.append(("> " if (ln.lead and s.kind == "point") else "") + ln.text)
|
||||
for item in left.get(len(plan.slides), []):
|
||||
out.append("#! %s" % item[1].replace("\n", " "))
|
||||
return "\n".join(out) + "\n"
|
||||
|
||||
|
||||
def mark(t, b, i):
|
||||
m = "***" if (b and i) else "**" if b else "*" if i else ""
|
||||
if not m or not t.strip():
|
||||
return t
|
||||
lead, core, trail = re.match(r"^(\s*)(.*?)(\s*)$", t, re.S).groups()
|
||||
return "%s%s%s%s%s" % (lead, m, core, m, trail)
|
||||
|
||||
|
||||
def parse_bold(text):
|
||||
segs, pos = [], 0
|
||||
for m in re.finditer(r"\*\*\*(.+?)\*\*\*|\*\*(.+?)\*\*|\*(.+?)\*", text):
|
||||
if m.start() > pos:
|
||||
segs.append((text[pos:m.start()], False, False))
|
||||
if m.group(1):
|
||||
segs.append((m.group(1), True, True))
|
||||
elif m.group(2):
|
||||
segs.append((m.group(2), True, False))
|
||||
else:
|
||||
segs.append((m.group(3), False, True))
|
||||
pos = m.end()
|
||||
if pos < len(text):
|
||||
segs.append((text[pos:], False, False))
|
||||
return [s for s in segs if s[0]]
|
||||
|
||||
|
||||
def load(text):
|
||||
plan = notesmod.Plan()
|
||||
cur = None
|
||||
for no, raw in enumerate(text.splitlines(), 1):
|
||||
line = raw.rstrip()
|
||||
if line.startswith("#!"):
|
||||
plan.skipped.append((None, line[2:].strip(), "left off", len(plan.slides) + (1 if cur else 0)))
|
||||
continue
|
||||
if not line.strip() or line.strip() == "#" or line.lstrip().startswith("# "):
|
||||
continue
|
||||
m = re.match(r"^\s*\[([^\]]+)\]\s*(.*)$", line)
|
||||
if m:
|
||||
kind = KINDS.get(m.group(1).strip().lower())
|
||||
if not kind:
|
||||
raise ParseError("line %d: unknown slide type [%s] - use Title, Point, Build, "
|
||||
"Subpoint, Quote, Sentence or Scripture" % (no, m.group(1)))
|
||||
if cur is not None:
|
||||
plan.slides.append(cur)
|
||||
cur = notesmod.Slide(kind, [], src=None)
|
||||
if kind == "scripture":
|
||||
ref = m.group(2).strip()
|
||||
if not ref:
|
||||
raise ParseError("line %d: [Scripture] needs the reference after it, "
|
||||
"e.g. [Scripture] John 3:16 ESV" % no)
|
||||
cur.ref = ref
|
||||
elif m.group(2).strip():
|
||||
cur.lines.append(notesmod.Line([(m.group(2).strip(), False, False)]))
|
||||
continue
|
||||
if cur is None:
|
||||
raise ParseError("line %d: text before the first [Slide type] line: %r" % (no, line.strip()[:60]))
|
||||
t = line.strip()
|
||||
if cur.kind == "scripture":
|
||||
cur.lines.append(notesmod.Line(parse_bold(t)))
|
||||
elif cur.kind == "point" and t.startswith(">"):
|
||||
cur.lines.append(notesmod.Line([(t[1:].strip(), False, False)], lead=True))
|
||||
else:
|
||||
cur.lines.append(notesmod.Line([(t.replace("**", ""), False, False)]))
|
||||
if cur is not None:
|
||||
plan.slides.append(cur)
|
||||
titles = [s for s in plan.slides if s.kind == "title"]
|
||||
if len(titles) > 1:
|
||||
raise ParseError("there is more than one [Title] slide")
|
||||
for s in plan.slides:
|
||||
if not s.lines and s.kind not in ("scripture", "picture", "clearpicture"):
|
||||
raise ParseError("a [%s] slide has no text - delete the block or add text" % NAMES[s.kind])
|
||||
if not any(s.kind == "sentence" for s in plan.slides):
|
||||
plan.warnings.append("No [Sentence] slide - the Sermon in a Sentence slide will be left out.")
|
||||
return plan
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
"""Put the new message into the service playlist (a Planning Center plan in ProPresenter).
|
||||
|
||||
The service playlist has a "Message" header followed by the item the message lives in
|
||||
(e.g. Planning Center's "Resi Message"). Linking stores the presentation's path in that
|
||||
item - exactly what dragging the presentation onto it in ProPresenter does.
|
||||
"""
|
||||
import datetime
|
||||
import os
|
||||
import re
|
||||
import urllib.parse
|
||||
import uuid as uuidlib
|
||||
|
||||
import deck
|
||||
import pbwire
|
||||
|
||||
C = deck.C
|
||||
HEADER = 3
|
||||
|
||||
|
||||
def playlist_file(library):
|
||||
return os.path.join(os.path.dirname(os.path.dirname(library)), "Playlists", "Library")
|
||||
|
||||
|
||||
def load(path):
|
||||
with open(path, "rb") as f:
|
||||
data = f.read()
|
||||
return data, C.decode(data, "rv.data.PlaylistDocument")
|
||||
|
||||
|
||||
def walk(pl):
|
||||
yield pl
|
||||
arr = C.get(pl, "playlists")
|
||||
for ch in (C.all(arr, "playlists") if arr is not None else []):
|
||||
yield from walk(ch)
|
||||
|
||||
|
||||
def plan_date(pl):
|
||||
plan = C.get(pl, "pco_plan")
|
||||
text = C.string(plan, "date_list") if plan is not None else ""
|
||||
text = text or C.string(pl, "name")
|
||||
for m in re.finditer(r"([A-Z][a-z]+ \d{1,2}, \d{4})", text):
|
||||
try:
|
||||
return datetime.datetime.strptime(m.group(1), "%B %d, %Y").date()
|
||||
except ValueError:
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
def service_playlists(doc):
|
||||
"""Planning Center playlists with a date: [(date, playlist)]"""
|
||||
out = []
|
||||
for pl in walk(C.get(doc, "root_node")):
|
||||
if C.get(pl, "pco_plan") is None or C.get(pl, "items") is None:
|
||||
continue
|
||||
d = plan_date(pl)
|
||||
if d:
|
||||
out.append((d, pl))
|
||||
return out
|
||||
|
||||
|
||||
def _norm(t):
|
||||
return re.sub(r"[^a-z0-9]+", " ", t.lower()).strip()
|
||||
|
||||
|
||||
def find_service(doc, title, today=None):
|
||||
"""The playlist for this message: the one whose series/part matches the notes' title,
|
||||
else the next upcoming service. Returns (date, playlist) or (None, None)."""
|
||||
today = today or datetime.date.today()
|
||||
cands = service_playlists(doc)
|
||||
t = _norm(title)
|
||||
part = re.search(r"\bpart (\d+)\b", t)
|
||||
best = None
|
||||
for d, pl in cands:
|
||||
plan = C.get(pl, "pco_plan")
|
||||
series = _norm(C.string(plan, "series_title"))
|
||||
ptitle = _norm(C.string(plan, "plan_title"))
|
||||
name = _norm(C.string(pl, "name"))
|
||||
score = 0
|
||||
if series and t.startswith(series[:max(6, len(series) - 2)]):
|
||||
score += 2
|
||||
if part and (re.search(r"\bpart %s\b" % part.group(1), ptitle) or re.search(r"\bpart %s\b" % part.group(1), name)):
|
||||
score += 2
|
||||
if d >= today:
|
||||
score += 1
|
||||
if score >= 4 and (best is None or score > best[0] or (score == best[0] and d < best[1])):
|
||||
best = (score, d, pl)
|
||||
if best:
|
||||
return best[1], best[2]
|
||||
upcoming = sorted((d, pl) for d, pl in cands if d >= today)
|
||||
return upcoming[0] if upcoming else (None, None)
|
||||
|
||||
|
||||
def items_of(pl):
|
||||
its = C.get(pl, "items")
|
||||
return C.all(its, "items") if its is not None else []
|
||||
|
||||
|
||||
def is_header(it):
|
||||
if C.get(it, "header") is not None:
|
||||
return True
|
||||
pc = C.get(it, "planning_center")
|
||||
if pc is not None:
|
||||
if C.get(C.get(pc, "item"), "item_type", 0) == HEADER:
|
||||
return True
|
||||
ld = C.get(pc, "linked_data")
|
||||
if ld is not None and C.get(ld, "header") is not None:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def pco_name(it):
|
||||
pc = C.get(it, "planning_center")
|
||||
return C.string(C.get(pc, "item"), "name") if pc is not None else ""
|
||||
|
||||
|
||||
def linked_path(it):
|
||||
pc = C.get(it, "planning_center")
|
||||
src = C.get(pc, "linked_data") if pc is not None else it
|
||||
loc = C.path(src, "presentation", "document_path", "local") if src is not None else None
|
||||
return C.string(loc, "path") if loc is not None else ""
|
||||
|
||||
|
||||
def message_slot(pl):
|
||||
"""(index of the Message header, index of the item the message goes in or None)."""
|
||||
items = items_of(pl)
|
||||
h = None
|
||||
for k, it in enumerate(items):
|
||||
if is_header(it) and "message" in (C.string(it, "name") + " " + pco_name(it)).lower():
|
||||
h = k
|
||||
break
|
||||
if h is None:
|
||||
return None, None
|
||||
section = []
|
||||
for k in range(h + 1, len(items)):
|
||||
if is_header(items[k]):
|
||||
break
|
||||
section.append(k)
|
||||
named = [k for k in section if "message" in pco_name(items[k]).lower()]
|
||||
if named:
|
||||
return h, named[0]
|
||||
empty = [k for k in section if C.get(items[k], "planning_center") is not None and not linked_path(items[k])]
|
||||
if empty:
|
||||
return h, empty[0]
|
||||
return h, None
|
||||
|
||||
|
||||
def presentation_item(name, rel_path, root):
|
||||
abs_url = "file://" + urllib.parse.quote(os.path.join(root, rel_path))
|
||||
url = pbwire.Node("rv.data.URL", [])
|
||||
C.set(url, "absolute_string", abs_url.encode())
|
||||
C.set(url, "platform", 1, wt=0)
|
||||
local = pbwire.Node("rv.data.URL.LocalRelativePath", [])
|
||||
C.set(local, "root", 10, wt=0) # relative to the ProPresenter folder
|
||||
C.set(local, "path", rel_path.encode())
|
||||
C.set(url, "local", local)
|
||||
pres = pbwire.Node("rv.data.PlaylistItem.Presentation", [])
|
||||
C.set(pres, "document_path", url)
|
||||
key = pbwire.Node("rv.data.MusicKeyScale", [])
|
||||
C.set(key, "music_key", 7, wt=0)
|
||||
C.set(pres, "user_music_key", key)
|
||||
it = pbwire.Node("rv.data.PlaylistItem", [])
|
||||
C.set(it, "uuid", deck.uuid_node(str(uuidlib.uuid4()).upper()))
|
||||
C.set(it, "name", name.encode("utf-8"))
|
||||
C.set(it, "presentation", pres)
|
||||
return it
|
||||
|
||||
|
||||
def link(doc, pl, name, pro_path, pp_root):
|
||||
"""Link the presentation into the playlist's Message section. Returns a description."""
|
||||
rel = os.path.relpath(pro_path, pp_root)
|
||||
h, k = message_slot(pl)
|
||||
items_node = C.get(pl, "items")
|
||||
if h is None:
|
||||
return None, "no 'Message' section in the playlist"
|
||||
items = items_of(pl)
|
||||
if k is not None:
|
||||
it = items[k]
|
||||
pc = C.get(it, "planning_center")
|
||||
if pc is not None:
|
||||
old = linked_path(it)
|
||||
C.set(pc, "linked_data", presentation_item(name, rel, pp_root))
|
||||
C.set(it, "name", name.encode("utf-8"))
|
||||
return k, ("replaced %s" % os.path.basename(old) if old else "linked into '%s'" % pco_name(it))
|
||||
# no Planning Center item to use: add the presentation right under the header
|
||||
new = presentation_item(name, rel, pp_root)
|
||||
num = C.num(items_node, "items")
|
||||
pos = [i for i, f in enumerate(items_node.fields) if f[0] == num][h] + 1
|
||||
items_node.fields.insert(pos, [num, 2, new])
|
||||
return h + 1, "added under the Message header"
|
||||
@@ -0,0 +1,95 @@
|
||||
"""Restarting ProPresenter safely, for the one change it only reads at launch (playlists).
|
||||
|
||||
ProPresenter keeps playlists in memory and never re-reads its playlist file while running,
|
||||
so linking the message into a Planning Center playlist means: quit ProPresenter (it saves
|
||||
its playlists as it quits), write the link, open the same ProPresenter again. This never
|
||||
happens while slides are on screen or on a Sunday morning.
|
||||
"""
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
import urllib.request
|
||||
|
||||
BUNDLE = "com.renewedvision.propresenter"
|
||||
|
||||
|
||||
class Failed(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def pid():
|
||||
p = subprocess.run(["/usr/bin/pgrep", "-x", "ProPresenter"], capture_output=True, text=True)
|
||||
out = p.stdout.split()
|
||||
return int(out[0]) if out else None
|
||||
|
||||
|
||||
def app_path(p):
|
||||
"""The .app bundle of the running ProPresenter (several versions are installed)."""
|
||||
out = subprocess.run(["/bin/ps", "-o", "comm=", "-p", str(p)], capture_output=True, text=True).stdout.strip()
|
||||
i = out.find(".app/")
|
||||
return out[:i + 4] if i >= 0 else None
|
||||
|
||||
|
||||
def _api(api, path):
|
||||
with urllib.request.urlopen(api + path, timeout=4) as r:
|
||||
return json.loads(r.read().decode("utf-8") or "null")
|
||||
|
||||
|
||||
def busy_reason(api, now=None):
|
||||
"""Why a restart would disturb someone right now, or None."""
|
||||
now = now or datetime.datetime.now()
|
||||
if now.weekday() == 6 and 6 <= now.hour < 14:
|
||||
return "it's Sunday morning"
|
||||
try:
|
||||
layers = _api(api, "/v1/status/layers") or {}
|
||||
slide = _api(api, "/v1/status/slide") or {}
|
||||
cur = (slide.get("current") or {})
|
||||
if layers.get("slide") and (cur.get("text") or "").strip():
|
||||
return "a slide is on screen"
|
||||
if layers.get("video_input"):
|
||||
return "a video input is on screen"
|
||||
for t in _api(api, "/v1/timers/current") or []:
|
||||
if t.get("state") in ("running", "overrunning") and "message" in (t.get("id", {}).get("name", "").lower()):
|
||||
return "the Message timer is running"
|
||||
except (OSError, ValueError):
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def wait_api(api, timeout=120):
|
||||
end = time.time() + timeout
|
||||
while time.time() < end:
|
||||
try:
|
||||
_api(api, "/version")
|
||||
return True
|
||||
except (OSError, ValueError):
|
||||
time.sleep(2)
|
||||
return False
|
||||
|
||||
|
||||
def restart_with(change, api, say=print, quit_timeout=45):
|
||||
"""Quit ProPresenter, run change(), open the same ProPresenter again.
|
||||
If ProPresenter doesn't quit, nothing is changed."""
|
||||
p = pid()
|
||||
path = app_path(p) if p else None
|
||||
if p:
|
||||
say("Restarting ProPresenter to add it to the playlist (about 30 seconds)...")
|
||||
subprocess.run(["/usr/bin/osascript", "-e", 'tell application id "%s" to quit' % BUNDLE],
|
||||
capture_output=True, text=True, timeout=quit_timeout)
|
||||
end = time.time() + quit_timeout
|
||||
while time.time() < end and pid():
|
||||
time.sleep(1)
|
||||
if pid():
|
||||
raise Failed("ProPresenter didn't quit (is a dialog open on that Mac?) - nothing was changed")
|
||||
try:
|
||||
result = change()
|
||||
finally:
|
||||
if path and os.path.isdir(path):
|
||||
subprocess.run(["/usr/bin/open", "-a", path])
|
||||
elif p:
|
||||
subprocess.run(["/usr/bin/open", "-b", BUNDLE])
|
||||
if p and not wait_api(api):
|
||||
say("ProPresenter is starting slowly - give it a moment.")
|
||||
return result
|
||||
+329
@@ -0,0 +1,329 @@
|
||||
"""RTF reading and writing.
|
||||
|
||||
read(data) turns RTF (Cocoa, Word, Google Docs) into paragraphs of styled runs.
|
||||
Each run carries the full character state that was in effect, and each paragraph
|
||||
the paragraph-format control words that opened it, so a template's own formatting
|
||||
can be reproduced exactly by write().
|
||||
"""
|
||||
import re
|
||||
|
||||
_TOKEN = re.compile(
|
||||
r"\\([a-zA-Z]+)(-?\d+)? ?" # control word
|
||||
r"|\\'([0-9a-fA-F]{2})" # hex char
|
||||
r"|\\(\r\n|\n|\r)" # escaped newline = \par
|
||||
r"|\\(.)" # control symbol
|
||||
r"|([{}])"
|
||||
r"|(\r\n|\n|\r)"
|
||||
r"|([^\\{}\r\n]+)", re.S)
|
||||
|
||||
# Destinations whose text is not document content.
|
||||
_SKIP = {"fonttbl", "colortbl", "expandedcolortbl", "stylesheet", "info", "listtable",
|
||||
"listoverridetable", "pict", "header", "headerl", "headerr", "headerf", "footer",
|
||||
"footerl", "footerr", "footerf", "footnote", "fldinst", "themedata",
|
||||
"colorschememapping", "latentstyles", "datastore", "generator", "xmlnstbl",
|
||||
"rsidtbl", "mmathPr", "pgdsctbl", "NeXTGraphic", "object", "shppict",
|
||||
"nonshppict", "bkmkstart", "bkmkend", "txfieldtext", "annotation", "atnid",
|
||||
"atnauthor", "comment", "revtbl", "filetbl"}
|
||||
|
||||
# Character formatting words (kept per run). Everything else between \pard and the
|
||||
# first text counts as paragraph formatting.
|
||||
_CHAR = {"f", "fs", "b", "i", "ul", "ulnone", "uld", "uldb", "ulw", "ulc", "strike",
|
||||
"striked", "cf", "cb", "chcbpat", "highlight", "kerning", "expnd", "expndtw",
|
||||
"CocoaLigature", "up", "dn", "super", "sub", "nosupersub", "outl", "shad",
|
||||
"strokewidth", "strokec", "charscalex", "caps", "scaps", "v", "lang",
|
||||
"langfe", "langnp", "langfenp", "loch", "hich", "dbch", "af", "afs", "ab",
|
||||
"ai", "cs", "kerning", "embo", "impr", "shadow"}
|
||||
_IGNORE = {"uc", "rtf", "ansi", "ansicpg", "mac", "pc", "pca", "deff", "deflang",
|
||||
"deflangfe", "cocoartf", "cocoatextscaling", "cocoaplatform", "viewkind",
|
||||
"vieww", "viewh", "margl", "margr", "margt", "margb", "paperw", "paperh",
|
||||
"deftab", "widowctrl", "ftnbj", "aenddoc", "fet", "sectd", "pgwsxn",
|
||||
"pghsxn", "marglsxn", "margrsxn", "sbknone", "linex", "endnhere", "titlepg",
|
||||
"nouicompat", "viewscale", "readonlyrecommended", "hyphauto", "pgnstarts",
|
||||
"linestarts"}
|
||||
|
||||
PARA_BREAK = "\n"
|
||||
LINE_SEP = "\u2028"
|
||||
|
||||
|
||||
class Run:
|
||||
__slots__ = ("text", "state")
|
||||
|
||||
def __init__(self, text, state):
|
||||
self.text, self.state = text, state
|
||||
|
||||
def __repr__(self):
|
||||
return "Run(%r, %r)" % (self.text, self.state)
|
||||
|
||||
|
||||
class Para:
|
||||
__slots__ = ("par_codes", "runs", "end_state")
|
||||
|
||||
def __init__(self, par_codes):
|
||||
self.par_codes = list(par_codes) # [(word, arg), ...] starting with ('pard', None)
|
||||
self.runs = []
|
||||
self.end_state = None # char state at the paragraph mark
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return "".join(r.text for r in self.runs)
|
||||
|
||||
def __repr__(self):
|
||||
return "Para(%r, %r)" % (self.par_codes, self.runs)
|
||||
|
||||
|
||||
class Doc:
|
||||
def __init__(self):
|
||||
self.fonts = {} # index -> font name
|
||||
self.colors = [] # index -> (r, g, b) or None (auto)
|
||||
self.paras = []
|
||||
self.header = "" # raw RTF up to the first \pard (for writing with a template)
|
||||
|
||||
|
||||
def read(data):
|
||||
if isinstance(data, (bytes, bytearray)):
|
||||
data = bytes(data).decode("latin-1")
|
||||
doc = Doc()
|
||||
m = data.find("\\pard")
|
||||
doc.header = data[:m] if m >= 0 else ""
|
||||
|
||||
state = {}
|
||||
stack = []
|
||||
dest = None # current destination name if inside one we treat specially
|
||||
skip = False
|
||||
star = False
|
||||
uc = 1
|
||||
pending_skip = 0
|
||||
font_idx, font_name = None, ""
|
||||
color_cur = {}
|
||||
par_codes = [("pard", None)]
|
||||
para = Para(par_codes)
|
||||
in_listtext = False
|
||||
|
||||
def emit(text):
|
||||
nonlocal para
|
||||
if skip or not text:
|
||||
return
|
||||
st = dict(state)
|
||||
if para.runs and para.runs[-1].state == st:
|
||||
para.runs[-1].text += text
|
||||
else:
|
||||
para.runs.append(Run(text, st))
|
||||
|
||||
def end_para():
|
||||
nonlocal para
|
||||
para.end_state = dict(state)
|
||||
doc.paras.append(para)
|
||||
para = Para(par_codes)
|
||||
|
||||
def flush_font():
|
||||
nonlocal font_idx, font_name
|
||||
if font_idx is not None:
|
||||
nm = font_name.strip().rstrip(";").strip()
|
||||
if nm:
|
||||
doc.fonts[font_idx] = nm
|
||||
font_idx, font_name = None, ""
|
||||
|
||||
for m in _TOKEN.finditer(data):
|
||||
word, arg, hexch, esc_nl, sym, brace, nl, text = m.groups()
|
||||
if pending_skip and (hexch or text):
|
||||
if hexch:
|
||||
pending_skip -= 1
|
||||
continue
|
||||
k = min(pending_skip, len(text))
|
||||
text = text[k:]
|
||||
pending_skip -= k
|
||||
if not text:
|
||||
continue
|
||||
if brace == "{":
|
||||
stack.append((dict(state), dest, skip, uc, in_listtext))
|
||||
star = False
|
||||
continue
|
||||
if brace == "}":
|
||||
if dest == "fonttbl":
|
||||
flush_font()
|
||||
if stack:
|
||||
state, dest, skip, uc, in_listtext = stack.pop()
|
||||
continue
|
||||
if word:
|
||||
a = int(arg) if arg is not None else None
|
||||
if star:
|
||||
star = False
|
||||
if word not in ("listtext",):
|
||||
dest, skip = word, True
|
||||
continue
|
||||
if word in ("fonttbl", "colortbl"):
|
||||
dest, skip = word, True
|
||||
continue
|
||||
if word in _SKIP:
|
||||
dest, skip = word, True
|
||||
continue
|
||||
if dest == "fonttbl":
|
||||
if word == "f":
|
||||
flush_font()
|
||||
font_idx = a or 0
|
||||
continue
|
||||
if dest == "colortbl":
|
||||
if word in ("red", "green", "blue"):
|
||||
color_cur[word] = a or 0
|
||||
continue
|
||||
if skip:
|
||||
if word == "u":
|
||||
pending_skip = uc
|
||||
continue
|
||||
if word == "listtext":
|
||||
in_listtext = True
|
||||
continue
|
||||
if word == "par":
|
||||
end_para()
|
||||
elif word == "pard":
|
||||
par_codes = [("pard", None)]
|
||||
para.par_codes = list(par_codes) if not para.runs else para.par_codes
|
||||
elif word == "line":
|
||||
emit(LINE_SEP)
|
||||
elif word == "tab":
|
||||
emit("\t")
|
||||
elif word == "uc":
|
||||
uc = a if a is not None else 1
|
||||
elif word == "u":
|
||||
cp = a + 65536 if a < 0 else a
|
||||
emit(chr(cp))
|
||||
pending_skip = uc
|
||||
elif word == "plain":
|
||||
state = {}
|
||||
elif word == "ulnone":
|
||||
state.pop("ul", None)
|
||||
elif word in _CHAR:
|
||||
if word in ("b", "i", "ul", "strike", "outl", "shad", "caps", "scaps", "v") and a == 0:
|
||||
state.pop(word, None)
|
||||
else:
|
||||
state[word] = a
|
||||
elif word in ("emdash", "endash", "bullet", "lquote", "rquote", "ldblquote",
|
||||
"rdblquote", "emspace", "enspace", "qmspace"):
|
||||
emit({"emdash": "\u2014", "endash": "\u2013", "bullet": "\u2022",
|
||||
"lquote": "\u2018", "rquote": "\u2019", "ldblquote": "\u201c",
|
||||
"rdblquote": "\u201d", "emspace": "\u2003", "enspace": "\u2002",
|
||||
"qmspace": "\u2005"}[word])
|
||||
elif word in _IGNORE:
|
||||
pass
|
||||
elif word == "sect" or word == "page":
|
||||
end_para()
|
||||
else:
|
||||
# paragraph-level formatting word
|
||||
par_codes.append((word, a))
|
||||
if not para.runs:
|
||||
para.par_codes = list(par_codes)
|
||||
continue
|
||||
if hexch:
|
||||
if dest == "fonttbl":
|
||||
font_name += bytes([int(hexch, 16)]).decode("cp1252", "replace")
|
||||
continue
|
||||
if not skip:
|
||||
emit(bytes([int(hexch, 16)]).decode("cp1252", "replace"))
|
||||
continue
|
||||
if esc_nl:
|
||||
if not skip:
|
||||
end_para()
|
||||
continue
|
||||
if sym:
|
||||
if sym == "*":
|
||||
star = True
|
||||
elif sym in "\\{}":
|
||||
if dest == "fonttbl":
|
||||
font_name += sym
|
||||
else:
|
||||
emit(sym)
|
||||
elif sym == "~":
|
||||
emit("\u00a0")
|
||||
elif sym == "_":
|
||||
emit("\u2011")
|
||||
continue
|
||||
if nl:
|
||||
continue
|
||||
if text:
|
||||
if dest == "fonttbl":
|
||||
for ch in text:
|
||||
if ch == ";":
|
||||
flush_font()
|
||||
elif font_idx is not None:
|
||||
font_name += ch
|
||||
continue
|
||||
if dest == "colortbl":
|
||||
for _ in range(text.count(";")):
|
||||
doc.colors.append((color_cur.get("red", 0), color_cur.get("green", 0),
|
||||
color_cur.get("blue", 0)) if color_cur else None)
|
||||
color_cur = {}
|
||||
continue
|
||||
emit(text)
|
||||
if para.runs:
|
||||
end_para()
|
||||
return doc
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------- writing
|
||||
|
||||
def escape(text):
|
||||
out = []
|
||||
for ch in text:
|
||||
o = ord(ch)
|
||||
if ch in "\\{}":
|
||||
out.append("\\" + ch)
|
||||
elif ch == "\t":
|
||||
out.append("\\tab ")
|
||||
elif ch == LINE_SEP:
|
||||
out.append("\\uc0\\u8232 ")
|
||||
elif ch == "\n":
|
||||
out.append("\\\n")
|
||||
elif 32 <= o < 128:
|
||||
out.append(ch)
|
||||
else:
|
||||
try:
|
||||
b = ch.encode("cp1252")
|
||||
out.append("\\'%02x" % b[0])
|
||||
except UnicodeEncodeError:
|
||||
if o > 0xFFFF:
|
||||
hi = 0xD800 + ((o - 0x10000) >> 10)
|
||||
lo = 0xDC00 + ((o - 0x10000) & 0x3FF)
|
||||
out.append("\\uc0\\u%d \\u%d " % (hi - 65536 if hi > 32767 else hi,
|
||||
lo - 65536 if lo > 32767 else lo))
|
||||
else:
|
||||
out.append("\\uc0\\u%d " % (o - 65536 if o > 32767 else o))
|
||||
return "".join(out)
|
||||
|
||||
|
||||
_ORDER = ["f", "b", "i", "ul", "strike", "fs", "cf", "cb", "kerning", "expnd", "expndtw",
|
||||
"CocoaLigature", "up", "dn", "outl", "strokewidth", "strokec", "shad", "charscalex",
|
||||
"caps", "scaps"]
|
||||
|
||||
|
||||
def char_codes(st):
|
||||
"""Absolute character formatting for a run (every flag stated explicitly)."""
|
||||
s = ["\\f%d" % st.get("f", 0)]
|
||||
s.append("\\b" if "b" in st else "\\b0")
|
||||
s.append("\\i" if "i" in st else "\\i0")
|
||||
s.append("\\ul" if "ul" in st else "\\ulnone")
|
||||
if "strike" in st:
|
||||
s.append("\\strike")
|
||||
for k in _ORDER:
|
||||
if k in ("f", "b", "i", "ul", "strike"):
|
||||
continue
|
||||
if k in st:
|
||||
v = st[k]
|
||||
s.append("\\%s%s" % (k, "" if v is None else v))
|
||||
return "".join(s) + " "
|
||||
|
||||
|
||||
def par_codes_str(codes):
|
||||
return "".join("\\%s%s" % (w, "" if a is None else a) for w, a in codes)
|
||||
|
||||
|
||||
def write(header, paras):
|
||||
"""paras: [(par_codes, [(text, state), ...]), ...] -> RTF bytes (latin-1)."""
|
||||
body = []
|
||||
for k, (codes, runs) in enumerate(paras):
|
||||
body.append(par_codes_str(codes) + "\n\n")
|
||||
for text, st in runs:
|
||||
body.append(char_codes(st) + escape(text))
|
||||
if k < len(paras) - 1:
|
||||
body.append("\\\n")
|
||||
rtf = header + "".join(body) + "}"
|
||||
return rtf.encode("latin-1")
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,25 @@
|
||||
"""Secrets in the macOS login Keychain (readable while someone is logged in at the Mac)."""
|
||||
import subprocess
|
||||
|
||||
SECURITY = "/usr/bin/security"
|
||||
|
||||
|
||||
def get(service, account):
|
||||
p = subprocess.run([SECURITY, "find-generic-password", "-s", service, "-a", account, "-w"],
|
||||
capture_output=True, text=True)
|
||||
return p.stdout.strip() if p.returncode == 0 and p.stdout.strip() else None
|
||||
|
||||
|
||||
def put(service, account, secret):
|
||||
p = subprocess.run([SECURITY, "add-generic-password", "-U", "-s", service, "-a", account, "-w", secret],
|
||||
capture_output=True, text=True)
|
||||
return p.returncode == 0
|
||||
|
||||
|
||||
def prompt_put(service, account):
|
||||
"""Let `security` ask for the secret itself, so it is typed straight into the Keychain."""
|
||||
return subprocess.run([SECURITY, "add-generic-password", "-U", "-s", service, "-a", account, "-w"]).returncode == 0
|
||||
|
||||
|
||||
def delete(service, account):
|
||||
subprocess.run([SECURITY, "delete-generic-password", "-s", service, "-a", account], capture_output=True)
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
"""The outputs a message is actually seen on.
|
||||
|
||||
The template's SPEAKER NAME slide switches to a Look (e.g. "Message"). That Look puts a
|
||||
theme slide on each screen ("Stream Lower Thirds" on the stream, "Center LED Wall" on the
|
||||
LED wall). On a themed screen ProPresenter re-draws the slide's text inside the theme's
|
||||
text box with the same name, using the theme's font and size; only "special" words -
|
||||
bold/italic/underlined differently from the rest of their box - keep their own style.
|
||||
|
||||
This module finds those theme slides so every text can be checked in every output box.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import urllib.request
|
||||
|
||||
import deck
|
||||
import pbwire
|
||||
|
||||
C = deck.C
|
||||
|
||||
|
||||
class Output:
|
||||
"""One themed screen: element name -> ElementStyle of the theme's text box."""
|
||||
|
||||
def __init__(self, name, styles, screen):
|
||||
self.name, self.styles, self.screen = name, styles, screen
|
||||
|
||||
def __repr__(self):
|
||||
return "Output(%r, screen %s, %d boxes)" % (self.name, self.screen, len(self.styles))
|
||||
|
||||
|
||||
def template_looks(tpl):
|
||||
out = []
|
||||
for cue in tpl.order:
|
||||
for a in C.all(cue, "actions"):
|
||||
al = C.get(a, "audience_look")
|
||||
if al is None:
|
||||
continue
|
||||
ident = C.get(al, "identification")
|
||||
uid = deck.uuid_str(C.get(ident, "parameter_uuid")) if ident is not None else ""
|
||||
name = C.string(ident, "parameter_name") if ident is not None else ""
|
||||
if (uid, name) not in out:
|
||||
out.append((uid, name))
|
||||
return out
|
||||
|
||||
|
||||
def _get(api, path, timeout=5):
|
||||
with urllib.request.urlopen(api + path, timeout=timeout) as r:
|
||||
return json.loads(r.read().decode("utf-8"))
|
||||
|
||||
|
||||
def outputs_for(tpl, api, themes_dir):
|
||||
"""[Output] for every themed screen in the template's Look(s). Raises OSError/ValueError
|
||||
when ProPresenter's API can't be reached."""
|
||||
looks = _get(api, "/v1/looks")
|
||||
themes = _get(api, "/v1/themes").get("themes", [])
|
||||
slide_index = {}
|
||||
for t in themes:
|
||||
tname = t.get("id", {}).get("name", "")
|
||||
for s in t.get("slides", []) or []:
|
||||
slide_index[s["id"]["uuid"]] = (tname, s["id"].get("name", ""))
|
||||
for g in t.get("groups", []) or []:
|
||||
for s in g.get("slides", []) or []:
|
||||
slide_index[s["id"]["uuid"]] = (tname, s["id"].get("name", ""))
|
||||
out, seen = [], set()
|
||||
for uid, name in template_looks(tpl):
|
||||
look = None
|
||||
for l in looks:
|
||||
if l["id"]["uuid"] == uid or (not look and l["id"]["name"] == name):
|
||||
look = l
|
||||
if not look:
|
||||
continue
|
||||
for k, scr in enumerate(look.get("screens", [])):
|
||||
ts = scr.get("presentation") or ""
|
||||
if not ts or not scr.get("slide") or ts in seen:
|
||||
continue
|
||||
seen.add(ts)
|
||||
tname, sname = slide_index.get(ts, ("", ""))
|
||||
styles = theme_slide_styles(os.path.join(themes_dir, tname, "Theme"), ts, sname)
|
||||
if styles:
|
||||
out.append(Output(sname or tname, styles, k))
|
||||
return out
|
||||
|
||||
|
||||
def theme_slide_styles(path, slide_uuid, slide_name):
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
doc = C.decode(f.read(), "rv.data.Template.Document")
|
||||
except OSError:
|
||||
return None
|
||||
slides = C.all(doc, "slides")
|
||||
pick = None
|
||||
for s in slides:
|
||||
bs = C.get(s, "base_slide")
|
||||
if bs is not None and deck.uuid_str(C.get(bs, "uuid")) == slide_uuid:
|
||||
pick = s
|
||||
if pick is None:
|
||||
for s in slides:
|
||||
if C.string(s, "name") == slide_name:
|
||||
pick = s
|
||||
if pick is None:
|
||||
return None
|
||||
bs = C.get(pick, "base_slide")
|
||||
size = C.get(bs, "size")
|
||||
h = deck.dbl(size, "height") or 1080.0
|
||||
styles = {}
|
||||
for el in C.all(bs, "elements"):
|
||||
if deck.element_text(el) is None or not deck.element_name(el):
|
||||
continue
|
||||
st = deck.ElementStyle(el, theme=True)
|
||||
st.slide_h = h
|
||||
styles[deck.element_name(el).lower()] = st
|
||||
return styles
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
"""YouTube/Facebook title and description for the live stream, written from the notes.
|
||||
|
||||
Claude writes the parts that need writing (the hook phrase in the title, the opening
|
||||
question, the summary, the "you'll discover" lines, the closing line); the hashtags,
|
||||
scripture lines and the links footer are filled in here so they are always exact.
|
||||
|
||||
Uses the Claude API over HTTPS with the standard library: the tool runs on the booth
|
||||
Mac's built-in Python 3.9 with nothing installed, and the current SDK needs 3.10+.
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
import secrets_store
|
||||
|
||||
API_URL = "https://api.anthropic.com/v1/messages"
|
||||
MODEL = "claude-opus-5-5"
|
||||
KEY_SERVICE = "MessageNotes Anthropic"
|
||||
|
||||
FOOTER = """\U0001f64f Connect with Waters Church
|
||||
|
||||
\U0001f310 Website: https://waterschurch.org
|
||||
|
||||
\U0001f4d6 Message Notes: https://waterschurch.guide/messages/
|
||||
|
||||
\U0001f64f Give: https://waterschurch.org/give
|
||||
|
||||
\U0001f3e0 Find a Location: https://waterschurch.org/locations/
|
||||
|
||||
\U0001f535 Pastor Tim's Resources: https://timhatchlive.com
|
||||
|
||||
\U0001f4f2 Follow Us:
|
||||
|
||||
YouTube: https://youtube.com/waterschurch
|
||||
|
||||
Facebook: https://facebook.com/waterschurchna
|
||||
|
||||
Instagram: https://instagram.com/waterschurch
|
||||
|
||||
\U0001f44d Like, \U0001f4ac Comment, and \U0001f514 Subscribe for more biblical teaching from Waters Church!"""
|
||||
|
||||
EXAMPLE_TITLE = "Faith That Gets You Out | How to Live With Eternity in Mind | Hebrews 11:28-31"
|
||||
EXAMPLE_BODY = """Are you truly free from your past, or are you still living under the weight of what God has already delivered you from?
|
||||
|
||||
In Faith That Gets You Out, Pastor Tim Hatch teaches from Hebrews 11:28\u201331, revealing how faith does more than save us from judgment\u2014it leads us out of bondage and into a new life with God.
|
||||
|
||||
Through the Passover, the crossing of the Red Sea, and Rahab's rescue from Jericho, we discover how the blood of Jesus covers our sins, how God breaks the power of our old life, and how His family welcomes us in.
|
||||
|
||||
In this message, you'll discover:
|
||||
|
||||
Why the blood of Jesus is our only hope of salvation.
|
||||
|
||||
How the Passover points to Christ's sacrifice.
|
||||
|
||||
Why freedom requires leaving our old master behind.
|
||||
|
||||
How baptism represents our new life in Christ.
|
||||
|
||||
Why your past cannot disqualify you from God's family.
|
||||
|
||||
Three truths to remember:
|
||||
|
||||
When God sees the BLOOD, judgment passes OVER.
|
||||
|
||||
When I go through the WATER, my old master can't follow me THROUGH.
|
||||
|
||||
When I come out, God's FAMILY welcomes me IN.
|
||||
|
||||
Faith doesn't just save you from the world. It walks you OUT of it."""
|
||||
|
||||
SYSTEM = """You write the YouTube and Facebook live-stream text for Waters Church's weekly message.
|
||||
Match the church's house style exactly - here is a past example.
|
||||
|
||||
TITLE: {title}
|
||||
|
||||
BODY:
|
||||
{body}
|
||||
|
||||
Rules:
|
||||
- Use only what is in the sermon notes you are given. Don't invent stories, quotes or claims.
|
||||
- title_hook: the middle part of the title - a short, searchable phrase (3-8 words) that says what the
|
||||
message helps people do or understand, in Title Case. Not the message title itself.
|
||||
- body: the same shape as the example - an opening question, a paragraph naming the message, speaker and
|
||||
main passage, a paragraph on what the message walks through, "In this message, you'll discover:" with
|
||||
4-5 one-sentence lines, then the main points under a heading like "Three truths to remember:" (use the
|
||||
real count, written as a word) with the numbering removed, then one closing line built from the Sermon in a Sentence.
|
||||
Keep the speaker's capitalised emphasis words (BLOOD, OVER) in the points. Blank line between every line, no
|
||||
bullets, no markdown, no hashtags, no emoji, no links.
|
||||
- additional_scriptures: the supporting passages worth listing (not the main one), grouped by chapter when
|
||||
several come from the same chapter, separated by "; ", e.g. "Exodus 12; Exodus 14; Romans 5:9". At most 8."""
|
||||
|
||||
SCHEMA = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title_hook": {"type": "string"},
|
||||
"body": {"type": "string"},
|
||||
"additional_scriptures": {"type": "string"},
|
||||
},
|
||||
"required": ["title_hook", "body", "additional_scriptures"],
|
||||
"additionalProperties": False,
|
||||
}
|
||||
|
||||
|
||||
class NotSetUp(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Failed(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def camel(text):
|
||||
words = re.findall(r"[A-Za-z0-9]+", text.replace("\u2019", "").replace("'", ""))
|
||||
return "".join(w[:1].upper() + w[1:] for w in words)
|
||||
|
||||
|
||||
def notes_digest(plan, speaker):
|
||||
"""The notes as plain text for the prompt."""
|
||||
lines = []
|
||||
parts = plan.title_parts()
|
||||
lines.append("Message title: %s" % (parts["subtitle"] or parts["title"]))
|
||||
lines.append("Series / heading: %s" % parts["title"])
|
||||
if parts["reference"]:
|
||||
lines.append("Main passage: %s" % parts["reference"])
|
||||
who = speaker[1].title() if speaker[1].isupper() else speaker[1]
|
||||
role = speaker[0].title() if speaker[0].isupper() else speaker[0]
|
||||
lines.append("Speaker: %s (%s)" % (who, role))
|
||||
lines.append("")
|
||||
for s in plan.slides:
|
||||
if s.kind == "title" or s.kind in ("picture", "clearpicture"):
|
||||
continue
|
||||
if s.kind == "scripture":
|
||||
if s.part and s.part[0] > 1:
|
||||
lines[-1] += " " + " ".join(l.text for l in s.lines)
|
||||
continue
|
||||
lines.append("SCRIPTURE %s: %s" % (s.ref, " ".join(l.text for l in s.lines)))
|
||||
elif s.kind == "sentence":
|
||||
lines.append("SERMON IN A SENTENCE: %s" % " ".join(l.text for l in s.lines))
|
||||
elif s.kind == "build":
|
||||
lines.append("LIST: %s" % " / ".join(l.text for l in s.lines))
|
||||
else:
|
||||
lines.append("POINT: %s" % " / ".join(l.text for l in s.lines))
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main_reference(plan):
|
||||
ref = plan.title_parts()["reference"]
|
||||
versions = [re.search(r"\b([A-Z]{2,6})$", s.ref) for s in plan.slides if s.kind == "scripture"]
|
||||
versions = [m.group(1) for m in versions if m]
|
||||
ver = max(set(versions), key=versions.count) if versions else "ESV"
|
||||
return ref, ver
|
||||
|
||||
|
||||
def call_claude(system, user, api_key, timeout=300):
|
||||
body = {
|
||||
"model": MODEL,
|
||||
"max_tokens": 16000,
|
||||
"fallbacks": "default", # if a request is ever declined, Anthropic retries it on its recommended model
|
||||
"thinking": {"type": "adaptive"},
|
||||
"output_config": {"effort": "medium", "format": {"type": "json_schema", "schema": SCHEMA}},
|
||||
"system": system,
|
||||
"messages": [{"role": "user", "content": user}],
|
||||
}
|
||||
req = urllib.request.Request(API_URL, data=json.dumps(body).encode("utf-8"), method="POST", headers={
|
||||
"content-type": "application/json",
|
||||
"x-api-key": api_key,
|
||||
"anthropic-version": "2023-06-01",
|
||||
"anthropic-beta": "server-side-fallback-2026-07-01",
|
||||
})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=timeout) as r:
|
||||
resp = json.loads(r.read().decode("utf-8"))
|
||||
except urllib.error.HTTPError as e:
|
||||
detail = e.read().decode("utf-8", "replace")[:300]
|
||||
if e.code in (401, 403):
|
||||
raise Failed("the Claude API key was rejected (%d) - run the setup again" % e.code)
|
||||
raise Failed("Claude API error %d: %s" % (e.code, detail))
|
||||
except (urllib.error.URLError, OSError) as e:
|
||||
raise Failed("couldn't reach the Claude API (%s)" % e)
|
||||
if resp.get("stop_reason") == "refusal":
|
||||
raise Failed("Claude declined to write this one")
|
||||
if resp.get("stop_reason") == "max_tokens":
|
||||
raise Failed("Claude's answer was cut off")
|
||||
text = "".join(b.get("text", "") for b in resp.get("content", []) if b.get("type") == "text")
|
||||
try:
|
||||
return json.loads(text)
|
||||
except ValueError:
|
||||
raise Failed("Claude's answer wasn't in the expected format")
|
||||
|
||||
|
||||
def prepare(plan, speaker):
|
||||
"""Everything taken from the notes, done up front (quick), so the slow part can run
|
||||
in the background while the plan keeps changing."""
|
||||
parts = plan.title_parts()
|
||||
ref, ver = main_reference(plan)
|
||||
return {"digest": notes_digest(plan, speaker), "parts": dict(parts), "ref": ref, "ver": ver,
|
||||
"speaker": tuple(speaker)}
|
||||
|
||||
|
||||
def write(plan, speaker):
|
||||
return write_prepared(prepare(plan, speaker))
|
||||
|
||||
|
||||
def write_prepared(prep):
|
||||
"""-> (title, description). Raises NotSetUp / Failed."""
|
||||
key = secrets_store.get(KEY_SERVICE, "api-key")
|
||||
if not key:
|
||||
raise NotSetUp("no Claude API key yet")
|
||||
parts, ref, ver, speaker = prep["parts"], prep["ref"], prep["ver"], prep["speaker"]
|
||||
message_title = parts["subtitle"] or parts["title"]
|
||||
out = call_claude(SYSTEM.format(title=EXAMPLE_TITLE, body=EXAMPLE_BODY), prep["digest"], key)
|
||||
hook = out["title_hook"].strip().strip("|").strip()
|
||||
title_parts = [message_title, hook] + ([ref] if ref else [])
|
||||
title = " | ".join(p for p in title_parts if p)
|
||||
if len(title) > 100: # YouTube's limit
|
||||
title = " | ".join(p for p in [message_title, ref] if p)[:100]
|
||||
series = re.split(r"\s+[-\u2013\u2014]\s+part\b|\s+part\s+\d", parts["title"], flags=re.I)[0] if parts["subtitle"] else ""
|
||||
pastor = "Pastor" + camel(speaker[1].title()) if speaker and speaker[1] else ""
|
||||
tags1 = " ".join("#" + t for t in (camel(series), camel(message_title), pastor) if t and t != "Pastor")
|
||||
book = re.match(r"^((?:[123] )?[A-Za-z ]+?)\s+(\d+)", ref or "")
|
||||
tags2 = tags1 + ((" #%s%s" % (camel(book.group(1)), book.group(2))) if book else "") + " #WatersChurch"
|
||||
key_line = ("\U0001f4d6 Key Scripture: %s (%s)" % (ref.replace("-", "\u2013"), ver)) if ref else ""
|
||||
extra = out["additional_scriptures"].strip().rstrip(".")
|
||||
pieces = [tags1, out["body"].strip(), key_line,
|
||||
("\U0001f4d6 Additional Scriptures: %s." % extra) if extra else "", FOOTER, tags2]
|
||||
desc = "\n\n".join(p for p in pieces if p)
|
||||
desc = desc.replace("<", "").replace(">", "") # YouTube rejects angle brackets
|
||||
if len(desc) > 5000:
|
||||
desc = desc[:4990].rsplit("\n", 1)[0]
|
||||
return title, desc
|
||||
@@ -0,0 +1,46 @@
|
||||
"""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))
|
||||
@@ -0,0 +1,187 @@
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2761
|
||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica-Bold;\f1\fswiss\fcharset0 Helvetica;\f2\fnil\fcharset0 Verdana-Bold;
|
||||
\f3\fnil\fcharset0 Verdana;}
|
||||
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;\red0\green0\blue0;\red252\green16\blue27;
|
||||
\red253\green128\blue8;\red0\green23\blue255;\red251\green2\blue255;\red0\green0\blue255;}
|
||||
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;\csgray\c0\c0;\cssrgb\c100000\c17519\c13069;
|
||||
\cssrgb\c100000\c57637\c0;\cssrgb\c1229\c23499\c100000;\cssrgb\c100000\c25279\c100000;\cssrgb\c1680\c19835\c100000;}
|
||||
\margl1440\margr1440\vieww18800\viewh20480\viewkind0
|
||||
\deftab720
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs54 \cf2 \cb3 James - Part 1\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\fs32 \cf2 The Christians Field Guide to Trials\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\i0\fs28 \cf4 James 1:1-12\
|
||||
\
|
||||
\
|
||||
\
|
||||
\cf5 slide for Waters U\cf4 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2 \cf6 James 1:1 (ESV)
|
||||
\f3\b0 James, a servant of God and of the Lord Jesus Christ, To the twelve tribes in the Dispersion: Greetings.\
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f2\b \cf2 What we need in times of trial:
|
||||
\f3\b0 \cf6 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 1. JOYFUL OPTIMISM.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf6 James 1:2\'963 (ESV) Count
|
||||
\f3\b0 it all joy, my brothers, when you meet trials of various kinds, 3 for you
|
||||
\f2\b know
|
||||
\f3\b0 that the testing of your faith
|
||||
\f2\b produces
|
||||
\f3\b0 steadfastness.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf7 <slides only>\cf6 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf4 John 16:33 (ESV)
|
||||
\f3\b0 I have said these things to you, that in me you may have peace. In the world you will have tribulation. But
|
||||
\f2\b take
|
||||
\f3\b0
|
||||
\f2\b heart
|
||||
\f3\b0 ; I have overcome the world.\'94\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf7 <slides only>\cf4 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf8 1 Peter 4:12\'9613 (ESV)
|
||||
\f3\b0 Beloved, do not be surprised at the fiery trial when it comes upon you to test you, as though something strange were happening to you. 13 But
|
||||
\f2\b rejoice
|
||||
\f3\b0 insofar as you share Christ\'92s sufferings, that you may also rejoice and be glad when his glory is revealed.\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf7 <slides only>\cf8 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf6 Romans 5:3\'965 (ESV)
|
||||
\f3\b0 we
|
||||
\f2\b rejoice
|
||||
\f3\b0 in our sufferings, knowing that suffering produces endurance, 4 and endurance produces character, and character produces hope, 5 and hope does not put us to shame, because God\'92s love has been poured into our hearts through the Holy Spirit who has been given to us.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 \'93Our VALUES determine our EVALUATIONS.\'94\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf2 What we need in times of trial:
|
||||
\f0\fs34 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\fs36 \cf2 2. A SURRENDERED will.
|
||||
\f1\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b\fs28 \cf6 James 1:4 (ESV)
|
||||
\f3\b0 And
|
||||
\f2\b let
|
||||
\f3\b0 steadfastness have its full effect, that you may be perfect and complete,
|
||||
\f2\b lacking
|
||||
\f3\b0 in
|
||||
\f2\b nothing
|
||||
\f3\b0 .\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 \
|
||||
\
|
||||
\
|
||||
\
|
||||
SURRENDER means letting the TEST take you where GOD wants you.
|
||||
\f1\b0 \
|
||||
\pard\pardeftab720\sl280\partightenfactor0
|
||||
|
||||
\f2\b\fs28 \cf2 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf2 What we need in times of trial:\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\fs36 \cf2 3. A HUMBLE dependence on God.
|
||||
\f1\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b\fs28 \cf6 James 1:5 (ESV)
|
||||
\f3\b0 If any of you lacks wisdom, let him ask God, who gives generously to all without reproach, and it will be given him. \cf2 \
|
||||
\pard\pardeftab720\sl280\partightenfactor0
|
||||
|
||||
\f2\b \cf2 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf6 Proverbs 16:16 (ESV)
|
||||
\f3\b0 How much better to get wisdom than gold! To get understanding is to be chosen rather than silver.\
|
||||
\pard\pardeftab720\sl280\partightenfactor0
|
||||
|
||||
\f2\b \cf2 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf6 James 1:6-7 (ESV)
|
||||
\f3\b0 But let him ask in faith, with no doubting, for the one who doubts is like a wave of the sea that is driven and tossed by the wind. 7 For that person must not suppose that he will receive anything from the Lord; 8 he is a double-minded man, unstable in all his ways.\
|
||||
\pard\pardeftab720\sl280\partightenfactor0
|
||||
|
||||
\f2\b \cf2 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf6 James 1:9\'9611 (ESV)
|
||||
\f3\b0 Let the lowly brother boast in his exaltation, 10 and the rich in his humiliation, because like a flower of the grass he will pass away. 11 For the sun rises with its scorching heat and withers the grass; its flower falls, and its beauty perishes. So also will the rich man fade away in the midst of his pursuits.\
|
||||
\pard\pardeftab720\sl280\partightenfactor0
|
||||
|
||||
\f2\b \cf2 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf2 What we need in times of trial:\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\fs36 \cf2 4. An EXPECTATION of blessing.
|
||||
\f1\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b\fs28 \cf6 James 1:12 (ESV)
|
||||
\f3\b0
|
||||
\f2\b Blessed
|
||||
\f3\b0 is the man who remains steadfast under trial, for when he has stood the test he will receive the crown of life, which God has promised to those who
|
||||
\f2\b love
|
||||
\f3\b0 him.
|
||||
\f2\b \
|
||||
\pard\pardeftab720\sl280\partightenfactor0
|
||||
\cf2 \
|
||||
\
|
||||
Sermon in a Sentence:\
|
||||
\
|
||||
I can face every TRIAL with JOY as I SURRENDER and HUMBLE myself to God\'92s plan for BLESSING and MATURITY in faith.\
|
||||
\
|
||||
\
|
||||
\
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2761
|
||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica-Bold;\f1\fswiss\fcharset0 Helvetica;\f2\fnil\fcharset0 Verdana-Bold;
|
||||
\f3\fnil\fcharset0 Verdana;}
|
||||
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;\red0\green0\blue0;\red0\green0\blue255;
|
||||
\red0\green23\blue255;\red252\green16\blue27;}
|
||||
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;\csgray\c0\c0;\cssrgb\c1680\c19835\c100000;
|
||||
\cssrgb\c1229\c23499\c100000;\cssrgb\c100000\c17519\c13069;}
|
||||
\margl1440\margr1440\vieww26100\viewh20580\viewkind0
|
||||
\deftab720
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs54 \cf2 \cb3 James - Part 2\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\fs32 \cf2 The Christians Field Guide to Temptation\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\i0\fs28 \cf4 James 1\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2 \cf2 The difference between TRIALS and TEMPTATIONS:
|
||||
\f3\b0 \cf5 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 God TESTS me to bring out my BEST.\
|
||||
Sin TEMPTS me to bring out my WORST.
|
||||
\fs28 \cf4 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2 \cf5 James 1:13 (ESV)
|
||||
\f3\b0 Let no one say when he is tempted, \'93I am being tempted by God,\'94 for God cannot be tempted with evil, and he himself tempts no one.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 When I am TRIED on the OUTSIDE, I will be TEMPTED on the INSIDE.
|
||||
\f1\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\fs28 \cf5 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 1. When TEMPTED, consider the TRAJECTORY.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 James 1:14(ESV)
|
||||
\f3\b0 But each person is
|
||||
\f2\b tempted
|
||||
\f3\b0 when he is
|
||||
\f2\b lured
|
||||
\f3\b0 and
|
||||
\f2\b enticed
|
||||
\f3\b0 by his own
|
||||
\f2\b desire
|
||||
\f3\b0 .\
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 Sin is PROGRESSIVE and POSSESSIVE and POISONOUS.
|
||||
\f1\b0 \
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f0\b Lust is a NORMAL DESIRE gone HAYWIRE. \
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 James 1:15 (ESV)
|
||||
\f3\b0 Then desire when it has conceived gives birth to sin, and sin when it is fully grown brings forth
|
||||
\f2\b death
|
||||
\f3\b0 .\cf2 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 2. When TEMPTED, get TRUTH about GOD.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 James 1:16\'9617 (ESV)
|
||||
\f3\b0 Do not be
|
||||
\f2\b deceived
|
||||
\f3\b0 , my beloved brothers. 17 Every good gift and every perfect gift is from above, coming down from the Father of lights, with whom there is no variation or shadow due to change.\cf2 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 Here\'92s the foundational lie in
|
||||
\f1\i all
|
||||
\f0\i0 temptation:\
|
||||
God is NOT GOOD to me.\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 Genesis 3:4\'965 (ESV)
|
||||
\f3\b0 But the serpent said to the woman, \'93You will not surely die. 5 For God knows that when you eat of it your eyes will be opened, and you will be like God, knowing good and evil.\'94\cf2 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f2\b \cf5 Psalm 34:8 (ESV)
|
||||
\f3\b0 Oh, taste and see that the LORD is good! Blessed is the man who takes refuge in Him!\
|
||||
|
||||
\f2\b \
|
||||
\
|
||||
\
|
||||
Psalm 145:9 (ESV)
|
||||
\f3\b0 The LORD is good to all, and His mercy is over all that He has made.\
|
||||
|
||||
\f2\b \
|
||||
\
|
||||
\
|
||||
Psalm 84:11 (ESV)
|
||||
\f3\b0 For the LORD God is a sun and shield; the LORD bestows favor and honor. No good thing does He withhold from those who walk uprightly.\
|
||||
|
||||
\f2\b \
|
||||
\
|
||||
\
|
||||
1 Chronicles 16:34 (ESV)
|
||||
\f3\b0 Oh give thanks to the LORD, for He is good; for His steadfast love endures forever!\cf2 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 Praise PRIORITIZES and PRODUCES the GOODNESS of God in my life.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 Psalm 23:6 (ESV)
|
||||
\f3\b0 Surely
|
||||
\f2\b goodness
|
||||
\f3\b0 and mercy shall follow me
|
||||
\f2\b all
|
||||
\f3\b0 the days of my life, and I shall dwell in the house of the LORD forever.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\fs36 \cf2 3. When TEMPTED, remember I am TRANSFORMED.
|
||||
\f1\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b\fs28 \cf5 James 1:18 (ESV)
|
||||
\f3\b0 Of His own will He brought us forth by the word of truth, that we should be a kind of firstfruits of His creatures.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf6 John 3:3 (ESV)
|
||||
\f3\b0 Jesus answered him, \'93Truly, truly, I say to you, unless one is born again he cannot see the kingdom of God.\'94\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf5 1 Peter 1:3 (ESV)
|
||||
\f3\b0 Blessed be the God and Father of our Lord Jesus Christ! According to His great mercy, He has
|
||||
\f2\b caused
|
||||
\f3\b0 us to be born again to a living hope through the resurrection of Jesus Christ from the dead\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 The same God who SAVED me is the ONE who SANCTIFIES me.
|
||||
\f1\b0 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b\fs28 \cf5 1 Corinthians 10:13 (ESV)
|
||||
\f3\b0 No temptation has overtaken you that is not common to man. God is faithful, and He will not let you be tempted beyond your ability, but with the temptation He will also provide the way of escape, that you may be able to endure it.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf2 Sermon in a Sentence:\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\fs34 \cf2 SIN does not have to CONTROL my life. God is FOR me and WITH me and ready to DELIVER me in Jesus\'92 Name.}
|
||||
@@ -0,0 +1,247 @@
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2818
|
||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica-Bold;\f1\fswiss\fcharset0 Helvetica;\f2\fnil\fcharset0 Verdana-Bold;
|
||||
\f3\fnil\fcharset0 Verdana;\f4\fnil\fcharset0 Verdana-BoldItalic;\f5\fnil\fcharset0 Verdana-Italic;
|
||||
\f6\fnil\fcharset0 .SFNS-Bold;}
|
||||
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;\red0\green0\blue0;\red0\green0\blue255;
|
||||
\red251\green2\blue255;\red251\green2\blue7;\red0\green23\blue255;}
|
||||
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;\csgray\c0\c0;\cssrgb\c1680\c19835\c100000;
|
||||
\cssrgb\c100000\c25279\c100000;\cssrgb\c100000\c14913\c0;\cssrgb\c1229\c23499\c100000;}
|
||||
\margl1440\margr1440\vieww8460\viewh24580\viewkind0
|
||||
\deftab720
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs54 \cf2 \cb3 James - Part 3\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\fs32 \cf2 The Christians Field Guide to Transformation\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\i0\fs28 \cf4 James 1:19-25\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\fs34 \cf2 The WORD of God fuels TRANSFORMATION.\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\b0\fs26 \cf5 (PP - These points will go quickly, put \'93God\'92s Word is:\'94 above them all, or have them all on the same slide but have them come up ONE at a time)
|
||||
\f0\i0\b\fs34 \cf2 \
|
||||
God\'92s Word is:\
|
||||
1. A SWORD -
|
||||
\f2\fs28 \cf4 Hebrews 4:12 / Ephesians 6:17
|
||||
\f3\b0 \
|
||||
\pard\pardeftab720\sl280\partightenfactor0
|
||||
\cf4 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 2. A LAMP -
|
||||
\f2\fs28 \cf4 Psalm 119:105
|
||||
\f3\b0 \
|
||||
\pard\pardeftab720\sl280\partightenfactor0
|
||||
\cf4 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 \
|
||||
3. A FIRE & HAMMER
|
||||
\f1\b0 -
|
||||
\f2\b\fs28 \cf4 Jeremiah 23:29
|
||||
\f3\b0 \
|
||||
\pard\pardeftab720\sl280\partightenfactor0
|
||||
\cf2 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 \
|
||||
4. A SEED
|
||||
\f1\b0 -
|
||||
\f2\b\fs28 \cf6 Luke 8:11
|
||||
\f3\b0 \cf4 \
|
||||
\pard\pardeftab720\sl280\partightenfactor0
|
||||
\cf2 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 \
|
||||
5. MILK
|
||||
\f1\b0 -
|
||||
\f2\b\fs28 \cf4 1 Peter 2:2
|
||||
\f3\b0 \cf2 \
|
||||
|
||||
\f0\b\fs34 \
|
||||
\
|
||||
6. FOOD
|
||||
\f1\b0 -
|
||||
\f2\b\fs28 \cf6 Matthew 4:4
|
||||
\f3\b0 \
|
||||
\pard\pardeftab720\sl280\partightenfactor0
|
||||
\cf2 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 7. WATER
|
||||
\f1\b0 -
|
||||
\f2\b\fs28 \cf4 Ephesians 5:26
|
||||
\f3\b0 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 If you want to CHANGE, get ENGAGED with God\'92s Word. \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\b0\fs28 \cf4 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\sl280\partightenfactor0
|
||||
|
||||
\f4\i\b \cf2 Transformation
|
||||
\f5\b0 in the Word happens when:
|
||||
\f3\i0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 1. I QUIET my OPINIONS.
|
||||
\f1\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b\fs28 \cf7 James 1:19-20 ESV
|
||||
\f3\b0 Know this, my beloved brothers: let every person be quick to hear, slow to speak, slow to anger; 20 for the anger of man does not produce the righteousness of God. \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\fs26 \cf5 (PP - Slides only)
|
||||
\f3\i0\fs28 \cf7 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf7 Psalm 101:7 NLT
|
||||
\f3\b0 I will
|
||||
\f2\b not
|
||||
\f3\b0 allow
|
||||
\f2\b deceivers
|
||||
\f3\b0 to serve in my house, and
|
||||
\f2\b liars
|
||||
\f3\b0 will not
|
||||
\f2\b stay
|
||||
\f3\b0 in my presence.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf4 \
|
||||
\
|
||||
|
||||
\f1\i\fs26 \cf5 (PP - Slides only)
|
||||
\f3\i0\fs28 \cf4 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf7 James 1:18 ESV
|
||||
\f3\b0 Of His own will He brought us forth by the
|
||||
\f2\b word
|
||||
\f3\b0 of
|
||||
\f2\b truth
|
||||
\f3\b0 , that we should be a kind of firstfruits of His creatures.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf4 \
|
||||
\
|
||||
|
||||
\f1\i\fs26 \cf5 (PP - Slides only)
|
||||
\f3\i0\fs28 \cf4 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf7 James 1:21 ESV
|
||||
\f3\b0 Therefore put away all filthiness and rampant wickedness and
|
||||
\f2\b receive
|
||||
\f3\b0 with meekness the implanted
|
||||
\f2\b word
|
||||
\f3\b0 \'85\cf2 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf4 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf7 James 1:19 ESV
|
||||
\f3\b0 Know this, my beloved brothers: let every person be quick to hear, slow to speak, slow to anger;\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf4 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf7 1 Timothy 5:22 ESV
|
||||
\f3\b0 Do not be
|
||||
\f2\b hasty
|
||||
\f3\b0 in the
|
||||
\f2\b laying
|
||||
\f3\b0 on of
|
||||
\f2\b hands
|
||||
\f3\b0 , nor take part in the sins of others; keep yourself pure.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf4 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\sl280\partightenfactor0
|
||||
|
||||
\f4\i\b \cf2 Transformation
|
||||
\f5\b0 in the Word happens when:
|
||||
\f3\i0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 2. I seek PURITY of my heart.
|
||||
\f1\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b\fs28 \cf7 James 1:21 ESV
|
||||
\f3\b0 Therefore put away all filthiness and rampant wickedness and receive with meekness the implanted word, which is able to
|
||||
\f2\b save
|
||||
\f3\b0 your souls.\cf2 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf4 \
|
||||
\
|
||||
\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0
|
||||
|
||||
\f6\b\fs34 \cf0 \cb1 We come to God\'92s Word for TRANSFORMATION, not just INSPIRATION.
|
||||
\f2\fs38 \cf4 \cb3 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\b0\fs28 \cf4 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\sl280\partightenfactor0
|
||||
|
||||
\f4\i\b \cf2 Transformation
|
||||
\f5\b0 in the Word happens when:\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\i0\b\fs36 \cf2 3. I PUT it into PRACTICE.
|
||||
\f1\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b\fs28 \cf7 James 1:22 ESV
|
||||
\f3\b0 But be doers of the word, and not hearers only, deceiving yourselves.\
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f2\b James 1:23\'9624 ESV
|
||||
\f3\b0 For if anyone is a hearer of the word and not a doer, he is like a man who looks intently at his natural face in a mirror. 24 For he looks at himself and goes away and at once forgets what he was like.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf4 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf7 James 1:25 ESV
|
||||
\f3\b0 But the one who looks into the perfect law, the law of liberty, and perseveres, being no hearer who forgets but a doer who acts,
|
||||
\f2\b he will be blessed in his doing.
|
||||
\f3\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf4 \
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2761
|
||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica-Bold;\f1\fswiss\fcharset0 Helvetica;\f2\fnil\fcharset0 Verdana-Bold;
|
||||
\f3\fnil\fcharset0 Verdana;}
|
||||
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;\red0\green0\blue0;\red0\green0\blue255;
|
||||
\red0\green23\blue255;\red251\green2\blue255;\red252\green16\blue27;}
|
||||
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;\csgray\c0\c0;\cssrgb\c1680\c19835\c100000;
|
||||
\cssrgb\c1229\c23499\c100000;\cssrgb\c100000\c25279\c100000;\cssrgb\c100000\c17519\c13069;}
|
||||
\margl1440\margr1440\vieww21300\viewh19580\viewkind0
|
||||
\deftab720
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs54 \cf2 \cb3 James - Part 4\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\fs32 \cf2 The Four Tests of True Religion\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\i0\fs28 \cf4 James 2\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\fs36 \cf2 \
|
||||
1. True religion SERVES those whom God SERVES.
|
||||
\f1\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b\fs28 \cf5 James 1:26\'9627 (ESV)
|
||||
\f3\b0 If anyone thinks he is religious and does not bridle his tongue but deceives his heart, this person\'92s religion is worthless. 27 Religion that is pure and undefiled before God the Father is this: to visit orphans and widows in their affliction, and to keep oneself unstained from the world.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\fs36 \cf2 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 I\'92m SERVING like Jesus when I expect NOTHING in RETURN.
|
||||
\f1\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\fs36 \cf2 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f0\b 2. True religion SEES people as God SEES them.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 James 2:1 (ESV)
|
||||
\f3\b0 My brothers, show no
|
||||
\f2\b partiality
|
||||
\f3\b0 as you hold the faith in our Lord Jesus Christ, the Lord of glory.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 Partiality is JUDGMENT based on APPEARANCE. \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 James 2:2-3 (ESV)
|
||||
\f3\b0 For if a man wearing a gold ring and fine clothing comes into your assembly, and a poor man in shabby clothing also comes in,
|
||||
\f2\b 3
|
||||
\f3\b0 and if you pay attention to the one who wears the fine clothing and say, \'93You sit here in a good place,\'94 while you say to the poor man, \'93You stand over there,\'94 or, \'93Sit down at my feet,\'94 \
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f2\b James 2:4 (ESV)
|
||||
\f3\b0 have you not then made distinctions among yourselves and become judges with evil thoughts?\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 The Problems with Partiality:\
|
||||
A. Partiality treats OTHERS as a COMMODITY.\
|
||||
\
|
||||
\
|
||||
\
|
||||
B. Partiality assesses SOMEONE based on incomplete INFORMATION.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 1 Samuel 16:7 (ESV)
|
||||
\f3\b0 the LORD said to Samuel, \'93Do not look on his appearance or on the height of his stature, because I have rejected him. For the LORD sees not as man sees: man looks on the outward appearance, but the LORD looks on the heart.\'94\cf2 \
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f2\b \cf5 Ephesians 3:20 (ESV)
|
||||
\f3\b0 Now to him who is able to do far more abundantly than all that we ask or think, according to the power at work within us,\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 C. Partiality assumes the OUTSIDE reflects the INSIDE.\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\fs26 \cf6 (slides only as this is the second instance)
|
||||
\f0\i0\fs34 \cf2 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 James 2:2 (ESV)
|
||||
\f3\b0 For if a man wearing a gold ring and fine clothing comes into your assembly, and a poor man in shabby clothing also comes in,\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf7 John 7:24 (ESV)
|
||||
\f3\b0 Do not judge by appearances, but judge with right judgment.\'94\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 \
|
||||
\
|
||||
\
|
||||
D. Partiality makes ME a JUDGE instead of a MINISTER.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\fs26 \cf6 (slides only as this is the second instance)
|
||||
\f2\i0\fs28 \cf5 \
|
||||
James 2:4 (ESV)
|
||||
\f3\b0 have you not then made distinctions among yourselves and become judges with evil thoughts?\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\fs36 \cf2 3. True religion VALUES those whom God VALUES.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 James 2:5 (ESV)
|
||||
\f3\b0 Listen, my beloved brothers, has not God chosen those who are poor in the world to be rich in faith and heirs of the kingdom, which he has promised to those who love him?\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 1 Corinthians 1:26\'9629 (ESV)
|
||||
\f3\b0 For consider your calling, brothers: not many of you were wise according to worldly standards, not many were powerful, not many were of noble birth. 27 But God chose what is foolish in the world to shame the wise; God chose what is weak in the world to shame the strong; 28 God chose what is low and despised in the world, even things that are not, to bring to nothing things that are, 29 so that no human being might boast in the presence of God.\cf2 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\fs36 \cf2 4. True religion LIVES for God\'92s APPROVAL, not the WORLD\'92s.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 James 2:8 (ESV)
|
||||
\f3\b0 If you really fulfill the royal law according to the Scripture, \'93You shall love your neighbor as yourself,\'94 you are doing well.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 James 2:9 (ESV)
|
||||
\f3\b0 But if you show partiality, you are committing sin and are convicted by the law as transgressors.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 James 2:10\'9611 (ESV)
|
||||
\f3\b0 For whoever keeps the whole law but fails in one point has become guilty of all of it. 11 For he who said, \'93Do not commit adultery,\'94 also said, \'93Do not murder.\'94 If you do not commit adultery but do murder, you have become a transgressor of the law.\cf2 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 James 2:12 (ESV)
|
||||
\f3\b0 So speak and so act as those who are to be judged under the law of liberty. 13 For judgment is without mercy to one who has shown no mercy. Mercy triumphs over judgment.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 Romans 8:1\'962 (ESV)
|
||||
\f3\b0 There is therefore now no condemnation for those who are in Christ Jesus. 2 For the law of the Spirit of life has set you free in Christ Jesus from the law of sin and death. \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 \
|
||||
\
|
||||
When I believe the GOSPEL, I am APPROVED by God.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2818
|
||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Bold;\f1\fnil\fcharset0 HelveticaNeue;\f2\fnil\fcharset0 HelveticaNeue-BoldItalic;
|
||||
\f3\fnil\fcharset0 HelveticaNeue-Italic;}
|
||||
{\colortbl;\red255\green255\blue255;\red0\green0\blue255;}
|
||||
{\*\expandedcolortbl;;\cspthree\c7746\c19489\c96071;}
|
||||
\margl1440\margr1440\vieww13180\viewh24580\viewkind0
|
||||
\deftab560
|
||||
\pard\pardeftab560\partightenfactor0
|
||||
|
||||
\f0\b\fs40 \cf0 James - Part 5\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f1\b0\fs26 \cf0 How Faith Comes Alive\
|
||||
James 2\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf0 \
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f2\i\b \cf0 (Slides only)\
|
||||
What do you do when FAITH has FLATLINED?\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf0 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
\cf0 (STAGE MANAGER - When I quote miracles Max, \'93mostly dead\'94 \'97wheel out the defibrillator.)\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
|
||||
\f1\i0\b0 \cf0 \
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf2 James 1:22 ESV
|
||||
\f1\b0 But be doers of the word, and not hearers only, deceiving yourselves.\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf2 \
|
||||
\
|
||||
\pard\pardeftab560\sa40\partightenfactor0
|
||||
|
||||
\f0\b\fs32 \cf0 If faith WITHOUT works is DEAD, faith WITH works is ALIVE.\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
|
||||
\fs26 \cf0 \
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f2\i \cf0 (STAGE MANAGER - When I make the point above \'97come remove the defibrillator.)
|
||||
\f0\i0 \
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf0 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\sa40\partightenfactor0
|
||||
|
||||
\fs32 \cf0 1. Faith comes ALIVE when it BENEFITS me.\cf2 \
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\fs26 \cf2 James 2:14 ESV
|
||||
\f1\b0 What
|
||||
\f0\b good
|
||||
\f1\b0 is it, my brothers, if someone says he has faith but does not have works? Can that faith
|
||||
\f3\i \ul \ulc2 save him
|
||||
\f1\i0 \ulnone ?\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf2 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf2 Philippians 2:12\'9613 ESV
|
||||
\f1\b0 \'85
|
||||
\f0\b work out
|
||||
\f1\b0 your own salvation with fear and trembling,
|
||||
\f0\b 13
|
||||
\f1\b0 for it is
|
||||
\f0\b God who works in you,
|
||||
\f1\b0 both to will and to work for his good pleasure.\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf2 \
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f2\i\b \cf0 (Come up one at a time)
|
||||
\f1\i0\b0 \cf2 \
|
||||
\pard\pardeftab560\sa40\partightenfactor0
|
||||
|
||||
\f0\b\fs32 \cf0 WORKS of FAITH that BENEFIT me:\
|
||||
A. BAPTISM & COMMUNION\
|
||||
B. PRAYER & BIBLE READING\
|
||||
C. HEARING the PREACHING of the Word\
|
||||
D. FELLOWSHIP with CHRISTIANS\cf2 \
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f1\b0\fs26 \cf2 \
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf0 \
|
||||
\pard\pardeftab560\sa40\partightenfactor0
|
||||
|
||||
\f0\b\fs32 \cf0 2. Faith comes ALIVE when it BENEFITS people OTHER than ME\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\fs26 \cf2 \
|
||||
James 2:15\'9617 ESV
|
||||
\f1\b0 If a brother or sister is poorly clothed and lacking in daily food, 16 and one of you says to them, \'93Go in peace, be warmed and filled,\'94 without giving them the things needed for the body, what good is that? 17 So also faith by itself, if it does not have works, is dead.\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf2 \
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f2\i\b \cf0 (Come up one at a time)
|
||||
\f1\i0\b0 \cf2 \
|
||||
\pard\pardeftab560\sa40\partightenfactor0
|
||||
|
||||
\f0\b\fs32 \cf0 WORKS of FAITH that BENEFIT others:\cf2 \
|
||||
\cf0 A. SERVING\
|
||||
B. GIVING\
|
||||
C. FORGIVING\
|
||||
D. WITNESSING\cf2 \
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
|
||||
\f1\b0\fs26 \cf2 \
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf2 James 2:18 NLT
|
||||
\f1\b0 Now someone may argue, \'93Some people have faith; others have good deeds.\'94 But I say, \'93How can you show me your faith if you don\'92t have good deeds? I will show you my faith by my good deeds.\'94\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf2 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\sa40\partightenfactor0
|
||||
|
||||
\f0\b\fs32 \cf0 3. Faith comes ALIVE when I BRING my BEST to God.\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\fs26 \cf2 \
|
||||
James 2:19 ESV
|
||||
\f1\b0 You believe that God is one; you do well. Even the demons believe\'97and shudder!\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf2 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf0 James {\field{\*\fldinst{HYPERLINK "x-apple-data-detectors://embedded-result/1765"}}{\fldrslt 2:20-23}} ESV
|
||||
\f1\b0 Do you want to be shown, you foolish person, that faith apart from works is useless? 21 Was not Abraham our father justified by works when he
|
||||
\f0\b offered
|
||||
\f1\b0 up his son Isaac on the altar? 22 You see that faith was active along with his works, and faith was completed by his works; 23 and the Scripture was fulfilled that says, \'93Abraham believed God, and it was counted to him as righteousness\'94\'97and he was called a friend of God.\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf0 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf0 Hebrews 11:19 ESV
|
||||
\f1\b0 He considered that God was able even to
|
||||
\f0\b raise him from the dead
|
||||
\f1\b0 , from \
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf0 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf2 James 2:24 ESV
|
||||
\f1\b0 You see that a person is justified by works and not by faith alone.\
|
||||
|
||||
\f0\b Romans 3:28 ESV
|
||||
\f1\b0 For we hold that
|
||||
\f0\b one is justified by faith apart from works
|
||||
\f1\b0 of the law.\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf2 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f2\i\b \cf0 (Come up one at a time)
|
||||
\f1\i0\b0 \cf2 \
|
||||
\pard\pardeftab560\sa40\partightenfactor0
|
||||
|
||||
\f0\b\fs32 \cf0 Two Axis of JUSTIFICATION:\
|
||||
1. VERTICAL justification: God DOES it, we BELIEVE it.\
|
||||
2. HORIZONTAL justification: We LIVE it, people SEE it.\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
|
||||
\f1\b0\fs26 \cf2 \
|
||||
\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
|
||||
\f0\b \cf0 Ephesians 2:8-10 ESV
|
||||
\f1\b0 For by grace you have been saved through faith. And this is not your own doing; it is the gift of God, 9 not a result of works, so that no one may boast. 10 For we are his workmanship, created in Christ Jesus for good works, which God prepared beforehand, that we should walk in them. \cf2 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\sa40\pardirnatural\partightenfactor0
|
||||
|
||||
\f0\b\fs32 \cf0 I\'92m saved by grace through faith FOR WORKS, not of the LAW, but from LOVE.\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
|
||||
\f1\b0\fs26 \cf0 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\sa40\partightenfactor0
|
||||
|
||||
\f0\b\fs32 \cf0 4. Faith comes ALIVE when I BREAK from what\'92s COMFORTABLE\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\fs26 \cf0 \
|
||||
\
|
||||
\
|
||||
James 2:25 ESV
|
||||
\f1\b0 And in the same way was not also Rahab the prostitute justified by works when she received the messengers and sent them out by another way?\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf0 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf2 James 2:26 ESV
|
||||
\f1\b0 For as the body apart from the spirit is dead, so also faith apart from works is dead.}
|
||||
@@ -0,0 +1,152 @@
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2870
|
||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Bold;\f1\fswiss\fcharset204 Helvetica;\f2\fnil\fcharset0 Verdana-Bold;
|
||||
\f3\fnil\fcharset0 Verdana;\f4\fnil\fcharset0 .SFNS-Semibold;\f5\fnil\fcharset0 .SFNS-Regular;
|
||||
}
|
||||
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;\red251\green2\blue7;\red0\green0\blue255;
|
||||
\red251\green2\blue7;\red252\green16\blue27;\red17\green128\blue2;\red0\green23\blue255;}
|
||||
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;\cssrgb\c100000\c14913\c0;\cssrgb\c1680\c19835\c100000;
|
||||
\cspthree\c91917\c25275\c14500;\cssrgb\c100000\c17519\c13069;\cspthree\c24445\c55172\c15103;\cssrgb\c1229\c23499\c100000;}
|
||||
{\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{decimal\}.}{\leveltext\leveltemplateid1\'02\'00.;}{\levelnumbers\'01;}\fi-360\li720\lin720 }{\listname ;}\listid1}}
|
||||
{\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}}
|
||||
\margl1440\margr1440\vieww11980\viewh22420\viewkind0
|
||||
\deftab560
|
||||
\pard\pardeftab560\partightenfactor0
|
||||
|
||||
\f0\b\fs42 \cf2 Pressure Has a Purpose\
|
||||
\pard\pardeftab560\sa40\partightenfactor0
|
||||
|
||||
\fs32 \cf2 Forge Night - September 2026\
|
||||
\pard\pardeftab560\partightenfactor0
|
||||
|
||||
\fs26 \cf3 Mark 6
|
||||
\f1\fs28 \cf4 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf4 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\fs34 \cf2 September is PRESSURE season.
|
||||
\fs28 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\partightenfactor0
|
||||
|
||||
\f2 \cf5 Mark 6:45\'9652 ESV
|
||||
\f3\b0 Immediately he made his disciples get into the boat and go before him to the other side, to Bethsaida, while he dismissed the crowd.
|
||||
\f2\b 46
|
||||
\f3\b0 And after he had taken leave of them, he went up on the mountain to pray.
|
||||
\f2\b 47
|
||||
\f3\b0 And when evening came, the boat was out on the sea, and he was alone on the land.
|
||||
\f2\b 48
|
||||
\f3\b0 And he saw that they were making headway painfully, for the wind was against them. And about the fourth watch of the night he came to them, walking on the sea. He meant to pass by them,
|
||||
\f2\b 49
|
||||
\f3\b0 but when they saw him walking on the sea they thought it was a ghost, and cried out,
|
||||
\f2\b 50
|
||||
\f3\b0 for they all saw him and were terrified. But immediately he spoke to them and said, \'93Take heart; it is I. Do not be afraid.\'94
|
||||
\f2\b 51
|
||||
\f3\b0 And he got into the boat with them, and the wind ceased. And they were utterly astounded,
|
||||
\f2\b 52
|
||||
\f3\b0 for they did not understand about the loaves, but their hearts were hardened.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\partightenfactor0
|
||||
|
||||
\f2\b \cf6 Mark 6:42\'9644 ESV
|
||||
\f3\b0 And they all ate and were satisfied. 43 And they took up twelve baskets full of broken pieces and of the fish. 44 And those who ate the loaves were five thousand men.\
|
||||
\
|
||||
|
||||
\fs34 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\b \cf2 Two types of Seasons:\
|
||||
\pard\tx20\tx470\pardeftab720\li470\fi-471\partightenfactor0
|
||||
\ls1\ilvl0\cf2 {\listtext 1. }FREE BREAD seasons.\
|
||||
{\listtext 2. }ROUGH SEA seasons.\
|
||||
\pard\tx20\tx470\pardeftab720\li470\fi-471\partightenfactor0
|
||||
|
||||
\fs30 \cf2 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 Mark 6:45 ESV
|
||||
\f3\b0 \'93Immediately
|
||||
\f2\b He made
|
||||
\f3\b0 His disciples get into the boat\'85\'94\
|
||||
\pard\pardeftab560\partightenfactor0
|
||||
\cf2 The word is
|
||||
\f2\b \cf7 anankaz\uc0\u333 \'97to compel, constrain, strongly urge.\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\fs36 \cf2 1. PRESSURE puts us in a \'93WEAK to be STRONG\'94 position.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf8 2 Corinthians 12:9\'9610 ESV
|
||||
\f3\b0 But he said to me, \'93My grace is sufficient for you, for my power is made perfect in weakness.\'94 Therefore I will boast all the more gladly of my
|
||||
\f2\b weaknesses
|
||||
\f3\b0 , so that the
|
||||
\f2\b power
|
||||
\f3\b0 of Christ may rest upon me.
|
||||
\f2\b 10
|
||||
\f3\b0 For the sake of Christ, then, I am content with weaknesses, insults, hardships, persecutions, and calamities. For when I am
|
||||
\f2\b weak
|
||||
\f3\b0 , then I am
|
||||
\f2\b strong
|
||||
\f3\b0 .\
|
||||
\
|
||||
|
||||
\f2\b Ecclesiastes 4:9\'9610 ESV
|
||||
\f3\b0 Two are better than one, because they have a good reward for their toil. 10 For if they fall, one will lift up his fellow. But woe to him who is alone when he falls and has not another to lift him up!\
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\b\fs34 \cf2 God does MORE when we think of ourselves LESS\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\fs36 \cf2 2. PRESSURE forces us into the right PRIORITIES.\
|
||||
\
|
||||
\pard\pardeftab560\partightenfactor0
|
||||
|
||||
\f3\b0\fs28 \cf2 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\b\fs34 \cf2 What is your PRESSURE POINT right now?\
|
||||
\
|
||||
\
|
||||
What if it\'92s POINTING to an unnecessary PRIORITY?
|
||||
\fs36 \
|
||||
\
|
||||
\
|
||||
\
|
||||
3. PRESSURE makes us PROGRESS.\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf8 2 Corinthians 1:8\'969 ESV
|
||||
\f3\b0 For we do not want you to be unaware, brothers, of the affliction we experienced in Asia. For we were so utterly burdened beyond our strength that we despaired of life itself. 9 Indeed, we felt that we had received the sentence of death. But that was to make us rely not on ourselves but on God who raises the dead.
|
||||
\f1\b\fs36 \cf2 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\fs34 \cf2 If I want more of JESUS, I must EXPECT more PRESSURED experiences.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\fs36 \cf2 \
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0
|
||||
|
||||
\f4\fs28 \cf0 Jeremiah 12:5 ESV
|
||||
\f5\b0 \'93If you have raced with men on foot, and they have wearied you, how will you compete with horses? And if in a safe land you are so trusting, what will you do in the thicket of the Jordan?
|
||||
\f1\b\fs36 \cf2 \
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2870
|
||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica-Bold;\f1\fswiss\fcharset0 Helvetica-BoldOblique;\f2\fnil\fcharset0 Verdana-Bold;
|
||||
\f3\fnil\fcharset0 Verdana;\f4\fnil\fcharset0 Verdana-BoldItalic;\f5\fswiss\fcharset0 Helvetica;
|
||||
}
|
||||
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;\red0\green0\blue0;\red252\green16\blue27;
|
||||
\red251\green2\blue255;\red0\green23\blue255;}
|
||||
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;\csgray\c0\c0;\cssrgb\c100000\c17519\c13069;
|
||||
\cssrgb\c100000\c25279\c100000;\cssrgb\c1229\c23499\c100000;}
|
||||
\margl1440\margr1440\vieww14500\viewh12460\viewkind0
|
||||
\deftab720
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs54 \cf2 \cb3 7 Letters - Part 2\
|
||||
|
||||
\f1\i\fs32 Don\'92t Let Religion Replace Relationship\
|
||||
|
||||
\f0\i0\fs28 \cf4 Revelation 2
|
||||
\fs34 \cf2 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf4 \
|
||||
\
|
||||
Revelation 2:1 ESV
|
||||
\f3\b0 \'93To the angel of the church in
|
||||
\f2\b Ephesus
|
||||
\f3\b0 write: \'91The words of him who holds the seven stars in his right hand, who walks among the seven golden lampstands.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f4\i\b\fs24 \cf5 (slides only)
|
||||
\f3\i0\b0\fs28 \cf4 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf4 Revelation 1:20 ESV
|
||||
\f3\b0 As for the mystery of the seven stars that you saw in my right hand, and the seven golden lampstands,
|
||||
\f2\b the seven stars are the angels of the seven
|
||||
\f3\b0 churches, and
|
||||
\f2\b the seven lampstands are the seven churches.
|
||||
\f3\b0 \cf2 \
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f2\b \cf4 Revelation 2:2\'963 ESV
|
||||
\f3\b0 \'93\'91I know your works, your toil and your patient endurance, and how you cannot bear with those who are evil, but have tested those who call themselves apostles and are not, and found them to be false. 3 I know you are enduring patiently and bearing up for my name\'92s sake, and you have not grown weary.\
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f2\b Revelation 2:6 ESV
|
||||
\f3\b0 Yet this you have: you hate the works of the
|
||||
\f2\b Nicolaitans
|
||||
\f3\b0 , which I also hate.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f2\b Revelation 2:4 ESV
|
||||
\f3\b0 But I have this against you, that you have
|
||||
\f2\b abandoned
|
||||
\f3\b0 the love you had at first.\cf2 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 1. It is possible to be DUTIFUL but not LOVING.
|
||||
\f5\b0 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b\fs28 \cf4 Luke 10:40 ESV
|
||||
\f3\b0 Martha was distracted with much serving. And she went up to him and said, \'93Lord, do you not care that my sister has left me to serve alone? Tell her then to help me.\'94\
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f2\b Luke 10:41\'9642 ESV
|
||||
\f3\b0 But the Lord answered her, \'93Martha, Martha, you are anxious and troubled about many things, 42 but one thing is necessary. Mary has chosen the good portion, which will not be taken away from her.\'94\cf2 \
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f2\b \cf4 Revelation 2:5 ESV
|
||||
\f3\b0 do the works you did at first. If not, I will come to you
|
||||
\f2\b and remove your lampstand
|
||||
\f3\b0 from its place, unless you repent.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 2. It\'92s impossible to faithfully REPRESENT Jesus without LOVE.
|
||||
\f5\b0 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b\fs28 \cf4 Revelation 2:4 NLT
|
||||
\f3\b0 \'93But I have this complaint against you. You don\'92t love me or each other as you did at first!\
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f2\b John 13:34\'9635 ESV
|
||||
\f3\b0 A new commandment I give to you, that you love one another: just as I have loved you, you also are to love one another. 35
|
||||
\f2\b By this
|
||||
\f3\b0 all people will know that you are my disciples,
|
||||
\f2\b if you have love for one another
|
||||
\f3\b0 .\'94\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf2 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf6 1 John 4:20\'9621 ESV
|
||||
\f3\b0 If anyone says, \'93I love God,\'94 and hates his brother, he is a liar; for he who does not love his brother whom he has seen cannot love God whom he has not seen. 21 And this commandment we have from him: whoever loves God must also love his brother.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf2 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf6 Romans 12:9\'9610 ESV
|
||||
\f3\b0 Let love be
|
||||
\f2\b genuine
|
||||
\f3\b0 . Abhor what is evil; hold fast to what is good. 10
|
||||
\f2\b Love
|
||||
\f3\b0
|
||||
\f2\b one
|
||||
\f3\b0
|
||||
\f2\b another
|
||||
\f3\b0 with
|
||||
\f2\b brotherly
|
||||
\f3\b0
|
||||
\f2\b affection
|
||||
\f3\b0 . Outdo one another in showing honor.\cf2 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf2 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\fs34 \cf2 LOVE is the SOIL where our CONNECTION to God and others GROWS.
|
||||
\f5\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b\fs28 \cf2 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\fs36 \cf2 3. It\'92s possible to GROW deeper in LOVE with God and EACH OTHER.
|
||||
\f5\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b\fs28 \cf4 Revelation 2:5 ESV
|
||||
\f3\b0 Remember therefore from where you have fallen; repent, and do the works you did at first. If not, I will come to you and remove your lampstand from its place, unless you repent.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf2 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\fs34 \cf2 A. DESTROY the things that SEPARATE you from God.
|
||||
\f5\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b\fs28 \cf6 Acts 19:18\'9619 ESV
|
||||
\f3\b0 Also many of those who were now believers came, confessing and divulging their practices. 19 And a number of those who had practiced magic arts brought their books together and burned them in the sight of all. And they counted the value of them and found it came to fifty thousand pieces of silver.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf2 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\fs34 \cf2 B. PARTICIPATE in BIG and SMALL community.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf6 Acts 20:18\'9620 ESV
|
||||
\f3\b0 \'93You yourselves know how I lived among you the whole time from the first day that I set foot in Asia, 19 serving the Lord with all humility and with tears and with trials that happened to me through the plots of the Jews; 20 how I did not shrink from declaring to you anything that was profitable,
|
||||
\f2\b and teaching you in \ul \ulc6 public\ulnone and from \ul house to house\ulnone ,
|
||||
\f3\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf2 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\fs34 \cf2 C. Visibly EXPRESS your BOND with each other.
|
||||
\f5\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b\fs28 \cf6 Acts 20:36\'9638 ESV
|
||||
\f3\b0 And when he had said these things, he knelt down and prayed with them all. 37 And there was
|
||||
\f2\b much weeping
|
||||
\f3\b0 on the part of all; they
|
||||
\f2\b embraced
|
||||
\f3\b0 Paul and
|
||||
\f2\b kissed
|
||||
\f3\b0 him, 38 being
|
||||
\f2\b sorrowful
|
||||
\f3\b0 most of all because of the word he had spoken, that they would not see his face again. And they accompanied him to the ship.\
|
||||
\
|
||||
\
|
||||
|
||||
\f2\b Romans 12:9 NLT
|
||||
\f3\b0 Don\'92t just pretend to love others. Really love them.\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf4 Revelation 2:7 ESV
|
||||
\f3\b0 He who has an ear, let him hear what the Spirit says to the churches. To the one who conquers I will
|
||||
\f2\b grant to eat of the tree of life, which is in the paradise of God.\'92
|
||||
\f3\b0 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf2 Sermon in a sentence:\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\fs34 \cf2 Jesus loves His church too much to let our DUTY replace our DEVOTION.\
|
||||
\
|
||||
\
|
||||
\
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2761
|
||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Bold;\f1\fnil\fcharset0 HelveticaNeue;}
|
||||
{\colortbl;\red255\green255\blue255;\red0\green0\blue255;\red0\green0\blue255;\red0\green0\blue0;
|
||||
\red251\green2\blue7;}
|
||||
{\*\expandedcolortbl;;\cssrgb\c1680\c19835\c100000;\cspthree\c7746\c19489\c96071;\cssrgb\c0\c0\c0;
|
||||
\cssrgb\c100000\c14913\c0;}
|
||||
{\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{hyphen\}}{\leveltext\leveltemplateid1\'01\uc0\u8259 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid1}}
|
||||
{\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}}
|
||||
\margl1440\margr1440\vieww8720\viewh26880\viewkind0
|
||||
\deftab560
|
||||
\pard\pardeftab560\sa40\partightenfactor0
|
||||
|
||||
\f0\b\fs32 \cf0 Men Who Lift Their Hands\
|
||||
Forge - January - 2026\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
|
||||
\f1\b0\fs26 \cf0 \
|
||||
\pard\pardeftab560\sa40\partightenfactor0
|
||||
|
||||
\f0\b\fs32 \cf0 \
|
||||
1. When men lift hands - we APPEAL to the POWER of the Lord.\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\fs26 \cf2 Psalm 28:2 ESV
|
||||
\f1\b0 Hear the voice of my pleas for mercy, when I cry to you for help, when I lift up my hands toward your most holy sanctuary.\cf0 \
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
|
||||
\f0\b \cf2 Psalm 141:2 ESV
|
||||
\f1\b0 Let my prayer be counted as incense before you, and the lifting up of my hands as the evening sacrifice!\cf0 \
|
||||
\
|
||||
Lifting hands:\
|
||||
\pard\pardeftab560\pardirnatural\partightenfactor0
|
||||
\ls1\ilvl0
|
||||
\fs24 \cf0 {\listtext \uc0\u8259 }
|
||||
\fs26 Cry for HELP\
|
||||
\ls1\ilvl0
|
||||
\fs24 {\listtext \uc0\u8259 }
|
||||
\fs26 Acknowledging NEED\
|
||||
\ls1\ilvl0
|
||||
\fs24 {\listtext \uc0\u8259 }
|
||||
\fs26 Anticipating God\'92s STRENGTH\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf0 \
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf2 Psalm 143:6 ESV
|
||||
\f1\b0 I stretch out my hands to You; my soul thirsts for You like a parched land.\cf0 \
|
||||
\
|
||||
\pard\pardeftab560\sa40\partightenfactor0
|
||||
|
||||
\f0\b\fs32 \cf0 2. When men lift hands - we SUBMIT to the Lord\'92s AUTHORITY.\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f1\b0\fs26 \cf0 \
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf0 Lifting hands employs the agency the ENEMY wants to MISALIGN.\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f1\b0 \cf0 \
|
||||
|
||||
\f0\b \cf2 Micah 2:1 ESV
|
||||
\f1\b0 Woe to those who devise wickedness and work evil on their beds! When the morning dawns, they perform it, because it is in the power of their hand.\cf0 \
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf0 Men
|
||||
\f1\b0
|
||||
\f0\b must get UNDER what God has put OVER them in order to get OVER what God has put UNDER them.
|
||||
\f1\b0 \
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf2 Proverbs 16:32 ESV
|
||||
\f1\b0 he who rules his spirit than he who takes a city.\cf0 \
|
||||
\cf2 \
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf0 \
|
||||
\pard\pardeftab560\sa40\partightenfactor0
|
||||
|
||||
\f0\b\fs32 \cf0 3. When men lift hands - we engage in SPIRITUAL WARFARE against the ENEMY.\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f1\b0\fs26 \cf0 \
|
||||
When Joshua was sent to fight the Amalekites:\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf3 Exodus 17:8\'9613 ESV
|
||||
\f1\b0 Then Amalek came and fought with Israel at Rephidim.
|
||||
\f0\b 9
|
||||
\f1\b0 So Moses said to Joshua, \'93Choose for us men, and go out and fight with Amalek. Tomorrow I will stand on the top of the hill with the staff of God in my hand.\'94
|
||||
\f0\b 10
|
||||
\f1\b0 So Joshua did as Moses told him, and fought with Amalek, while Moses, Aaron, and Hur went up to the top of the hill.
|
||||
\f0\b 11
|
||||
\f1\b0 Whenever Moses
|
||||
\f0\b held up his hand
|
||||
\f1\b0 , Israel prevailed, and whenever he lowered his hand, Amalek prevailed.
|
||||
\f0\b 12
|
||||
\f1\b0 But Moses\'92 hands grew weary, so they took a stone and put it under him, and he sat on it, while Aaron and Hur held up his hands, one on one side, and the other on the other side. So his hands were steady until the going down of the sun.
|
||||
\f0\b 13
|
||||
\f1\b0 And Joshua overwhelmed Amalek and his people with the sword.\cf0 \
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf0 This is not an EMOTIONAL response to God. This is a BATTLE CRY for the Lord.
|
||||
\f1\b0 \
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf2 Psalm 10:12 ESV
|
||||
\f1\b0 Arise, O LORD; O God, lift up your hand; forget not the afflicted.\cf0 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\sa40\partightenfactor0
|
||||
|
||||
\f0\b\fs32 \cf0 4. When men lift hands - we ENGAGE in our PRIESTLY RESPONSIBILITY.\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\fs26 \cf2 \
|
||||
1 Timothy 2:1\'964 ESV 1
|
||||
\f1\b0 First of all, then, I urge that supplications, prayers, intercessions, and thanksgivings be made for all people,
|
||||
\f0\b 2
|
||||
\f1\b0 for kings and all who are in high positions, that we may lead a peaceful and quiet life, godly and dignified in every way.
|
||||
\f0\b 3
|
||||
\f1\b0 This is good, and it is pleasing in the sight of God our Savior,
|
||||
\f0\b 4
|
||||
\f1\b0 who desires all people to be saved and to come to the knowledge of the truth.\
|
||||
\
|
||||
|
||||
\f0\b 1 Timothy 2:8 ESV
|
||||
\f1\b0 I desire then that in every place the men should pray, lifting holy hands without anger or quarreling;\
|
||||
|
||||
\f0\b \
|
||||
1 Timothy 2:9\'9610 ESV
|
||||
\f1\b0 likewise also that women should adorn themselves in respectable apparel, with modesty and self-control, not with braided hair and gold or pearls or costly attire,
|
||||
\f0\b 10
|
||||
\f1\b0 but with what is proper for women who profess godliness\'97with good works.\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
\cf4 \
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf4 A priest STANDS in the GAP.
|
||||
\f1\b0 \
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf2 Lamentations 2:19 NLT
|
||||
\f1\b0 Rise during the night and cry out. Pour out your hearts like water to the Lord.
|
||||
\f0\b Lift up your hands to him in prayer, pleading for your children
|
||||
\f1\b0 , for in every street they are faint with hunger.
|
||||
\f0\b \
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
|
||||
\f1\b0 \cf0 \
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf2 Leviticus 9:22 ESV
|
||||
\f1\b0 Then Aaron lifted up his hands toward the people and blessed them, and he came down from offering the sin offering and the burnt offering and the peace offerings.\
|
||||
\cf0 \
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf5 Luke 24:50 ESV
|
||||
\f1\b0 And he led them out as far as Bethany, and lifting up his hands he
|
||||
\f0\b blessed
|
||||
\f1\b0 them.\cf0 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2820
|
||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica-Bold;\f1\fswiss\fcharset0 Helvetica;\f2\fnil\fcharset0 Verdana-Bold;
|
||||
\f3\fnil\fcharset0 Verdana;}
|
||||
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;\red0\green0\blue0;\red0\green0\blue255;
|
||||
\red0\green23\blue255;\red251\green2\blue255;\red177\green79\blue4;\red252\green16\blue27;}
|
||||
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;\csgray\c0\c0;\cssrgb\c1680\c19835\c100000;
|
||||
\cssrgb\c1229\c23499\c100000;\cssrgb\c100000\c25279\c100000;\cssrgb\c75494\c39056\c0;\cssrgb\c100000\c17519\c13069;}
|
||||
\margl1440\margr1440\vieww20380\viewh24580\viewkind0
|
||||
\deftab720
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs54 \cf2 \cb3 James - Part 10\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\fs32 \cf2 Planning the Future with God in Mind\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\i0\fs28 \cf4 James 4:13-17\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2 \cf5 James 4:10 ESV
|
||||
\f3\b0 Humble yourselves before the Lord, and he will exalt you.\
|
||||
\
|
||||
\
|
||||
\cf6 // my eyes only\cf5 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b\fs30 \cf7 Let\'92s stand and read James 4:13-17\
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\fs36 \cf2 Point 1: Identify my OVERCONFIDENCE.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 James 4:13 ESV
|
||||
\f3\b0 Come now, you who say, \'93Today or tomorrow we will go into such and such a town and spend a year there and trade and make a profit\'94\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 The ultimate OVERCONFIDENCE is planning as if God doesn\'92t EXIST. \
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 Proverbs 21:5 ESV
|
||||
\f3\b0 The plans of the diligent lead surely to abundance, but everyone who is hasty comes only to poverty.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf8 Luke 14:28-30 ESV
|
||||
\f3\b0 For which of you, desiring to build a tower, does not first sit down and count the cost, whether he has enough to complete it? 29 Otherwise, when he has laid a foundation and is not able to finish, all who see it begin to mock him, 30 saying, \'91This man began to build and was not able to finish.\'92\
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 Point 2: Acknowledge my VULNERABILITY.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 James 4:14 ESV
|
||||
\f3\b0 yet you do not know what tomorrow will bring.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 3 areas of VULNERABILITY:\
|
||||
A. I have no GUARANTEE of TOMORROW. \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf8 Luke 12:18\'9621 ESV
|
||||
\f3\b0 \'91I will do this: I will tear down my barns and build larger ones, and there I will store all my grain and my goods. 19 And I will say to my soul, \'93Soul, you have ample goods laid up for many years; relax, eat, drink, be merry.\'94\'a0\'92 20 But God said to him, \'91Fool! This night your soul is required of you, and the things you have prepared, whose will they be?\'92 21 So is the one who lays up treasure for himself and is not rich toward God.\'94\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf5 Proverbs 27:1 ESV
|
||||
\f3\b0 Do not boast about tomorrow, for you do not know what a day may bring.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 B. I\'92m not the CENTER of the WORLD. \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 James 4:14b ESV
|
||||
\f3\b0 What is your life? For you are a mist that appears for a little time and then vanishes.\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 C. I\'92m PRONE to cause PROBLEMS.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 James 4:16 ESV
|
||||
\f3\b0 As it is, you boast in your arrogance. All such boasting is
|
||||
\f2\b evil
|
||||
\f3\b0 .\
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f2\b James 4:17 ESV
|
||||
\f3\b0 So whoever knows the right thing to do and fails to do it, for him it is
|
||||
\f2\b sin
|
||||
\f3\b0 .\
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 Point 3: EMBRACE God\'92s AGREEABILITY.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 James 4:15 ESV
|
||||
\f3\b0 Instead you ought to say, \'93If the Lord wills, we will live and do this or that.\'94\
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf8 John 5:30 ESV
|
||||
\f3\b0 \'93I can do nothing on my own. As I hear, I judge, and my judgment is just, because
|
||||
\f2\b I seek not my own will but the will of Him who sent me.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
John 10:10 ESV
|
||||
\f3\b0 The thief comes only to steal and kill and destroy. I came that they may have life and have it abundantly.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 Confession 1: God\'92s will is a GOOD thing.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\b0\fs28 \cf4 \'93If the Lord wills\'94
|
||||
\f0\b\fs34 \cf2 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 Jeremiah 32:41 ESV
|
||||
\f3\b0 I will rejoice in doing them good, and I will plant them in this land in faithfulness, with all my heart and all my soul.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 Confession 2: If God\'92s not IN IT, I won\'92t WANT IT.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\b0\fs28 \cf4 \'93we will live\'94\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf5 Colossians 1:17 ESV
|
||||
\f3\b0 He is before all things, and in him all things hold together.\
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f2\b Hebrews 1:3 ESV
|
||||
\f3\b0 He upholds the universe by the word of His power. \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 Confession 3: When God IS IN IT, I will ultimately ENJOY it.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf4 James 4:15
|
||||
\f3\b0 \'93\'85and do this or that.\'94\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf5 1 Peter 4:1\'962 ESV
|
||||
\f3\b0 Since therefore Christ suffered in the flesh, arm yourselves with the same way of thinking, for whoever has suffered in the flesh has ceased from sin, 2 so as to live for the rest of the time in the flesh no longer for human passions but for the will of God.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 God\'92s will is not a BARRIER to your SUCCESS; it\'92s the BRIDGE to BLESSING.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\fs36 \cf2 Point 4: Move FORWARD with ACCOUNTABILITY.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 James 4:17 ESV
|
||||
\f3\b0 So whoever knows the right thing to do and fails to do it, for him it is sin.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 Hebrews 13:20\'9621 ESV
|
||||
\f3\b0 Now may the God of peace who brought again from the dead our Lord Jesus, the great shepherd of the sheep, by the blood of the eternal covenant, 21
|
||||
\f2\b equip you with everything good that you may do His will
|
||||
\f3\b0 , working in us that which is pleasing in his sight, through Jesus Christ, to whom be glory forever and ever. Amen.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf2 Sermon in a sentence:\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\fs34 \cf2 Plan with HUMILITY, trust God\'92s SOVEREIGNTY, and enjoy the reality that JESUS is LORD.\
|
||||
\
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2818
|
||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Bold;\f1\fnil\fcharset0 HelveticaNeue;}
|
||||
{\colortbl;\red255\green255\blue255;\red0\green0\blue255;\red0\green0\blue255;\red251\green2\blue255;
|
||||
\red251\green2\blue7;\red0\green0\blue0;}
|
||||
{\*\expandedcolortbl;;\cssrgb\c1680\c19835\c100000;\cspthree\c7746\c19489\c96071;\cssrgb\c100000\c25279\c100000;
|
||||
\cspthree\c91917\c25275\c14500;\cssrgb\c0\c0\c0;}
|
||||
\margl1440\margr1440\vieww12600\viewh24580\viewkind0
|
||||
\deftab560
|
||||
\pard\pardeftab560\partightenfactor0
|
||||
|
||||
\f0\b\fs40 \cf0 James - Part 6\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f1\i\fs26 \cf0 The Power of Your Words\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\i0 \cf2 James 3
|
||||
\f1\b0 \
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf2 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf3 James 3:1 ESV
|
||||
\f1\b0 Not many of you should become teachers, my brothers, for you know that we who teach will be judged with greater strictness.\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf3 \
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf3 James 3:2 ESV
|
||||
\f1\b0 For we all stumble in many ways. And if anyone does not stumble in what he says, he is a perfect man, able also to bridle his whole body.\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf3 \
|
||||
\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
|
||||
\f0\b \cf3 Psalm 141:3 ESV
|
||||
\f1\b0 Set a guard, O LORD, over my mouth; keep watch over the door of my lips!\cf0 \
|
||||
\
|
||||
\
|
||||
|
||||
\f0\b \cf3 Proverbs 21:23 ESV
|
||||
\f1\b0 Whoever keeps his mouth and his tongue keeps himself out of trouble.\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf0 The POTENTIAL:\
|
||||
\pard\pardeftab560\sa40\partightenfactor0
|
||||
|
||||
\fs34 \cf0 #1. My tongue DETERMINES the COURSE of my LIFE.\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
|
||||
\fs26 \cf0 \
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\fs30 \cf0 A. The tongue as a BIT \'96 DIRECTS my moments.\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\fs26 \cf3 James 3:3 ESV
|
||||
\f1\b0 If we put bits into the mouths of horses so that they obey us, we guide their whole bodies as well.\cf0 \
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
|
||||
\f0\b \cf0 \
|
||||
\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf4 //come up one at a time:\cf0 \
|
||||
- A SHORT word can DETERMINE a DAY of my life.
|
||||
\f1\b0 \
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf3 Proverbs 15:1 ESV
|
||||
\f1\b0 A soft answer turns away wrath, but a harsh word stirs up anger.\cf0 \
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf0 - A STUPID word can DESTROY a DAY in my life.
|
||||
\f1\b0 \
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
|
||||
\f0\b \cf3 Proverbs 12:18 ESV
|
||||
\f1\b0 There is one whose rash words are like sword thrusts, but the tongue of the wise brings healing.\cf0 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf5 Matthew 5:22 ESV
|
||||
\f1\b0 I say to you that everyone who is angry with his brother will be liable to judgment; whoever insults his brother will be liable to the council; and whoever says, \'91You fool!\'92 will be liable to the hell of fire.\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf5 \
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b\fs30 \cf0 B. The Tongue is a RUDDER \'96 DETERMINES my future.\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\fs26 \cf3 James 3:4 ESV
|
||||
\f1\b0 Look at the ships also: though they are so large and are driven by strong winds, they are guided by a very small rudder wherever the will of the pilot directs. \
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf3 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
|
||||
\f0\b \cf3 1 Peter 3:10 ESV
|
||||
\f1\b0 For \'93Whoever desires to love life and see good days, let him keep his tongue from evil and his lips from speaking deceit;\
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\sa40\partightenfactor0
|
||||
|
||||
\f0\b\fs32 \cf0 WORDS change WORLDS.\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
|
||||
\f1\b0\fs26 \cf0 \
|
||||
\
|
||||
|
||||
\f0\b \cf3 Ephesians 4:29 ESV
|
||||
\f1\b0 Let no corrupting talk come out of your mouths, but only such as is good for building up, as fits the occasion, that it may give grace to those who hear.\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
|
||||
\f0\b \cf5 Matthew 12:36\'9637 ESV
|
||||
\f1\b0 I tell you, on the day of judgment people will give account for every careless word they speak, 37 for by your words you will be justified, and by your words you will be condemned.\'94\
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf0 The PROBLEM:
|
||||
\f1\b0 \
|
||||
\pard\pardeftab560\sa40\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf0 #2. My tongue can DESTROY the CHRONICLE of my LIFE\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
|
||||
\f1\b0\fs26 \cf0 \
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf3 James 3:5b\'966a ESV
|
||||
\f1\b0 How great a forest is set ablaze by such a small fire! 6 The tongue is set among our members\'85 \
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf3 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
|
||||
\f0\b \cf6 A. My SPEECH creates a SYSTEM for my life.\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
\cf3 James 3:6a ESV
|
||||
\f1\b0 And the tongue is a fire, a
|
||||
\f0\b world
|
||||
\f1\b0 of unrighteousness\'85\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
|
||||
\f0\b \cf6 \
|
||||
\
|
||||
\
|
||||
B. My SPEECH STAINS my life.
|
||||
\f1\b0 \cf3 \
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
|
||||
\f0\b \cf3 James 3:6b ESV
|
||||
\f1\b0 \'85staining the whole body, setting on fire the entire course of life, and set on fire by hell.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf0 The PRESCRIPTION:\
|
||||
\pard\pardeftab560\sa40\partightenfactor0
|
||||
|
||||
\fs34 \cf0 #3. My tongue needs the DIRECTION of CHRIST.\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\fs26 \cf3 James 3:7 ESV
|
||||
\f1\b0 For every kind of beast and bird, of reptile and sea creature, can be tamed and has been tamed by mankind\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf3 \
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf3 James 3:8 ESV
|
||||
\f1\b0 but no human being can tame the tongue. It is a restless evil, full of deadly poison.\cf0 \
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf3 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\sa40\partightenfactor0
|
||||
|
||||
\f0\b\fs32 \cf0 My tongue is the THERMOMETER of my HEART. \
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\fs26 \cf5 Matthew 12:34 ESV
|
||||
\f1\b0 You brood of vipers! How can you speak good, when you are evil? For out of the abundance of the heart the mouth speaks.\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf0 \
|
||||
\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf3 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf3 James 3:11\'9612 ESV
|
||||
\f1\b0 Does a spring pour forth from the same opening both fresh and salt water? 12 Can a fig tree, my brothers, bear olives, or a grapevine produce figs? Neither can a salt pond yield fresh water.\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf3 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf5 Matthew 12:33 ESV
|
||||
\f1\b0 \'93Either make the tree good and its fruit good, or make the tree bad and its fruit bad, for the tree is known by its fruit.\
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf3 \
|
||||
\pard\pardeftab560\slleading20\pardirnatural\partightenfactor0
|
||||
\cf0 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab560\slleading20\partightenfactor0
|
||||
|
||||
\f0\b \cf0 Sermon in a Sentence:\
|
||||
\pard\pardeftab560\sa40\partightenfactor0
|
||||
|
||||
\fs32 \cf0 CONTROL your tongue because your tongue will CONTROL you; \
|
||||
but we can only CONTROL our tongue through CHRIST.\
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2761
|
||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica-Bold;\f1\fswiss\fcharset0 Helvetica;\f2\fnil\fcharset0 Verdana-Bold;
|
||||
\f3\fnil\fcharset0 Verdana;\f4\fnil\fcharset0 Verdana-Italic;}
|
||||
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;\red0\green0\blue0;\red0\green0\blue255;
|
||||
\red0\green23\blue255;\red251\green2\blue255;\red252\green16\blue27;}
|
||||
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;\csgray\c0\c0;\cssrgb\c1680\c19835\c100000;
|
||||
\cssrgb\c1229\c23499\c100000;\cssrgb\c100000\c25279\c100000;\cssrgb\c100000\c17519\c13069;}
|
||||
\margl1440\margr1440\vieww17660\viewh20480\viewkind0
|
||||
\deftab720
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs54 \cf2 \cb3 James - Part 7\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\fs32 \cf2 Finding Peace in Difficult Relationships\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\i0\fs28 \cf4 James 3\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2 \cf5 James 1:5 ESV
|
||||
\f3\b0 If any of you lacks wisdom, let him ask God, who gives generously to all without reproach, and it will be given him.\cf2 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 I need heavenly WISDOM in my earthly RELATIONSHIPS.\
|
||||
\
|
||||
\
|
||||
\
|
||||
Two KINDS of wisdom:\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\fs30 \cf2 1. HUMAN wisdom makes SENSE.\
|
||||
2. HEAVENLY wisdom challenges our SENSIBILITIES.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 James 3:13 ESV
|
||||
\f3\b0 Who is wise and understanding among you? By his good conduct let him show his works in the meekness of
|
||||
\f2\b wisdom
|
||||
\f3\b0 .\
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f2\b James 3:14 ESV
|
||||
\f3\b0 But if you have
|
||||
\f2\b bitter
|
||||
\f3\b0
|
||||
\f2\b jealousy
|
||||
\f3\b0 and
|
||||
\f2\b selfish
|
||||
\f3\b0
|
||||
\f2\b ambition
|
||||
\f3\b0 in your hearts, do not boast and be false to the truth. 15 This is
|
||||
\f2\b not
|
||||
\f3\b0 the
|
||||
\f2\b wisdom
|
||||
\f3\b0 that comes down from above\'85\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 Three Facets of Worldly Wisdom:\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\b0\fs28 \cf5 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 1. Worldly wisdom ORIENTS around the SELF.
|
||||
\f1\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b\fs28 \cf5 James 3:14 ESV
|
||||
\f3\b0 if you have
|
||||
\f2\b bitter
|
||||
\f3\b0
|
||||
\f2\b jealousy
|
||||
\f3\b0 and
|
||||
\f2\b selfish
|
||||
\f3\b0
|
||||
\f2\b ambition
|
||||
\f3\b0 in your hearts\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f4\i \cf6 (SLIDES ONLY)
|
||||
\f3\i0 \cf2 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf5 James 3:16 ESV
|
||||
\f3\b0 where
|
||||
\f2\b jealousy
|
||||
\f3\b0 and
|
||||
\f2\b selfish
|
||||
\f3\b0
|
||||
\f2\b ambition
|
||||
\f3\b0 exist\'85\
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f2\b James 3:16
|
||||
\f3\b0
|
||||
\f2\b ESV
|
||||
\f3\b0 For where
|
||||
\f2\b jealousy
|
||||
\f3\b0 and
|
||||
\f2\b selfish ambition
|
||||
\f3\b0 exist, there will be disorder and every vile practice.\cf2 \
|
||||
\cf5 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 2. Worldly wisdom OBSCURES the SELF.
|
||||
\f1\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b\fs28 \cf5 James 3:14 ESV
|
||||
\f3\b0 But if you have bitter jealousy and selfish ambition in your hearts,
|
||||
\f2\b do not boast and be false to the truth.\
|
||||
|
||||
\f3\b0 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 3. Worldly wisdom OBSESSES over this LIFE.
|
||||
\f1\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b\fs28 \cf5 James 3:15 ESV
|
||||
\f3\b0 This is
|
||||
\f2\b not
|
||||
\f3\b0 the
|
||||
\f2\b wisdom
|
||||
\f3\b0 that comes down from above, but is
|
||||
\f2\b earthly
|
||||
\f3\b0 ,
|
||||
\f2\b unspiritual
|
||||
\f3\b0 ,
|
||||
\f2\b demonic
|
||||
\f3\b0 .\
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f2\b Isaiah 14:12\'9615 ESV
|
||||
\f3\b0 \'93How you are fallen from heaven, O Day Star, son of Dawn! How you are cut down to the ground, you who laid the nations low! 13 You said in your heart, \'91
|
||||
\f2\b I will ascend
|
||||
\f3\b0 to heaven; above the stars of God
|
||||
\f2\b I will set my throne on high
|
||||
\f3\b0 ;
|
||||
\f2\b I will sit on the mount of assembly
|
||||
\f3\b0 in the far reaches of the north; 14
|
||||
\f2\b I will ascend
|
||||
\f3\b0 above the heights of the clouds;
|
||||
\f2\b I will make myself like the Most High
|
||||
\f3\b0 .\'92 15 But you are brought down to Sheol, to the far reaches of the pit.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 Three Facets of Heavenly Wisdom:\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\b0\fs28 \cf5 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 1. Heavenly wisdom ORIENTS around GOD.
|
||||
\f1\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b\fs28 \cf5 James 3:17 ESV
|
||||
\f3\b0 But the wisdom from above is first
|
||||
\f2\b pure\
|
||||
|
||||
\f3\b0 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf7 Matthew 6:33 ESV
|
||||
\f3\b0 But seek first the kingdom of God and his righteousness, and all these things will be added to you.\cf2 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf5 \
|
||||
\
|
||||
\
|
||||
|
||||
\f2\b Psalm 37:4\'965 ESV
|
||||
\f3\b0 Delight yourself in the LORD, and he will give you the desires of your heart. 5 Commit your way to the LORD; trust in him, and he will act.\cf2 \
|
||||
\cf5 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 2. Heavenly wisdom OPENS life to OTHERS.
|
||||
\f1\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b\fs28 \cf5 James 3:17 ESV
|
||||
\f3\b0 then peaceable, gentle, open to reason, full of mercy and good fruits, impartial and sincere.\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f4\i \cf6 (Come up one at a time use two slides if you need to)
|
||||
\f3\i0 \cf5 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf4 Peaceable: \cf2 SEEKS to RESOLVE conflict and HEAL the relationship. \
|
||||
\cf4 Gentle\cf2 : CONSIDERATE and GRACIOUS toward others.\
|
||||
\cf4 Open to reason\cf2 : READY to HEAR others out.\
|
||||
\cf4 Full of mercy and good fruits\cf2 : VISIBLY KIND toward others.
|
||||
\f1\b0 \
|
||||
|
||||
\f0\b \cf4 Impartial and sincere\cf2 : Playing NO FAVORITES without SCHEMING.
|
||||
\f1\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\fs28 \cf5 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 3. Heavenly wisdom OVERFLOWS in PEACE.
|
||||
\f1\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b\fs28 \cf5 James 3:18 ESV
|
||||
\f3\b0 And a harvest of righteousness is sown in peace by those who make peace.\
|
||||
\
|
||||
\
|
||||
\
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2761
|
||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica-Bold;\f1\fswiss\fcharset0 Helvetica;\f2\fnil\fcharset0 Verdana-BoldItalic;
|
||||
\f3\fnil\fcharset0 Verdana-Bold;\f4\fnil\fcharset0 Verdana;}
|
||||
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;\red0\green0\blue0;\red0\green0\blue255;
|
||||
\red0\green23\blue255;\red251\green2\blue255;\red252\green16\blue27;\red128\green0\blue2;\red254\green220\blue79;
|
||||
}
|
||||
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;\csgray\c0\c0;\cssrgb\c1680\c19835\c100000;
|
||||
\cssrgb\c1229\c23499\c100000;\cssrgb\c100000\c25279\c100000;\cssrgb\c100000\c17519\c13069;\cssrgb\c58072\c6673\c0;\cssrgb\c100000\c87985\c38085;
|
||||
}
|
||||
\margl1440\margr1440\vieww25100\viewh20340\viewkind0
|
||||
\deftab720
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs54 \cf2 \cb3 James - Part 8\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\fs32 \cf2 Reclaiming Unity in a Conflicted World\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\i0\fs28 \cf4 James 4\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\fs34 \cf2 CONFLICT comes standard in the human CONDITION.\
|
||||
\
|
||||
\
|
||||
\
|
||||
I own 50% of the PROBLEMS in every RELATIONSHIP.
|
||||
\f1\i \
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardeftab720\sl250\slmult1\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf2 The Cause of Conflict:\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\i0\fs36 \cf2 1. I CRAVE.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\fs28 \cf5 James 4:1 ESV
|
||||
\f4\b0 What causes quarrels and what causes fights among you? Is it not this, that your passions are at war within you?
|
||||
\f3\b \
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
James 4:2 ESV
|
||||
\f4\b0 You desire and do not have, so you
|
||||
\f3\b murder
|
||||
\f4\b0 . You covet and cannot obtain, so you fight and quarrel. You do not have, because you do not ask.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\cf6 (Slides only)\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\b \cf7 Matthew 5:21\'9622 ESV
|
||||
\f4\b0 \'93You have heard that it was said to those of old, \'91You shall not murder; and whoever murders will be liable to judgment.\'92 22 But I say to you that everyone who is angry with his brother will be liable to judgment\'85\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\b \cf5 James 4:3 ESV
|
||||
\f4\b0 You ask and do not receive, because you ask wrongly, to spend it on your passions.
|
||||
\f3\b \
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\tqr\tx100\tx260\pardeftab720\sl250\slmult1\partightenfactor0
|
||||
|
||||
\f2\i \cf2 The Cause of Conflict:\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\i0\fs36 \cf2 2. I COMPROMISE. \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\fs28 \cf5 James 4:4 ESV
|
||||
\f4\b0 You adulterous people! Do you not know that friendship with the world is enmity with God? Therefore whoever wishes to be a friend of the world makes himself an enemy of God.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\cf6 (Come up ONE at a time)
|
||||
\f0\b\fs34 \cf2 \
|
||||
4 PROBLEMS of a COMPROMISED Christian: \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\fs32 \cf2 1. Continual CONFLICT.
|
||||
\f1\i\b0 \cf4 \'93you quarrel and fight, you murder\'94\
|
||||
|
||||
\f0\i0\b \cf2 2. COVETING what OTHERS have.
|
||||
\f1\i\b0 \cf4 \'93you covet and do not have\'94
|
||||
\f0\i0\b \cf2 \
|
||||
3. CONSISTENT discontentment.
|
||||
\f1\i\b0 \cf4 \'93You do not have\'94
|
||||
\f0\i0\b \cf2 \
|
||||
4. CORRUPTED prayers.
|
||||
\f1\i\b0 \cf4 \'93you ask with wrong motives\'94\
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\i0\b\fs28 \cf5 James 4:4b ESV
|
||||
\f4\b0 whoever wishes to be a
|
||||
\f3\b friend
|
||||
\f4\b0 of the world makes himself an
|
||||
\f3\b enemy
|
||||
\f4\b0 of God.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\fs32 \cf4 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\tqr\tx100\tx260\pardeftab720\li260\fi-260\sl250\slmult1\partightenfactor0
|
||||
|
||||
\f2\b\fs28 \cf2 The Solution to Conflict:\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\i0\fs36 \cf2 3. God CALLS me to SURRENDER.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\fs28 \cf5 James 4:6 ESV
|
||||
\f4\b0 But He gives more grace.
|
||||
\f3\b \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\b0\fs32 \cf4 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\i0\b\fs28 \cf5 James 4:6b ESV
|
||||
\f4\b0 Therefore it says, \'93God opposes the proud but gives grace to the humble.\'94
|
||||
\f3\b \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\b0\fs32 \cf4 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\i0\b\fs28 \cf5 James 4:7 ESV
|
||||
\f4\b0 Submit yourselves therefore to God.
|
||||
\f3\b Resist the devil
|
||||
\f4\b0 , and he will flee from you.
|
||||
\f3\b \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\b0\fs32 \cf4 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\i0\b\fs34 \cf2 What are we RESISTING when we RESIST the Devil?\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\b0\fs32 \cf4 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\i0\b\fs34 \cf2 The Devil aims at SEPARATING you from the FATHER.
|
||||
\f1\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\i\fs32 \cf4 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\i0\b\fs34 \cf2 Satan wants you SEPARATED from God.\
|
||||
The Father wants you CONNECTED to Him.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\b0\fs32 \cf4 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\i0\b\fs28 \cf5 James 4:8a ESV
|
||||
\f4\b0 Draw near to God, and he will draw near to you.
|
||||
\f3\b \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\b0\fs32 \cf4 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\i0\b\fs34 \cf2 Satan is FULLY resisted when we DRAW CLOSE to the FATHER.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\b0\fs32 \cf4 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\sl360\partightenfactor0
|
||||
|
||||
\f4\i0\fs28 \cf8 \cb9 <What\'92sNext>ProPresenter>James>LionKingClip.mp4>\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\fs32 \cf4 \cb3 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardeftab720\sl250\slmult1\partightenfactor0
|
||||
|
||||
\f2\b\fs28 \cf2 The Solution to Conflict:\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\i0\fs36 \cf2 4. I COOPERATE in spiritual CLEANSING.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\fs28 \cf5 James 4:8\'969 ESV
|
||||
\f4\b0 Draw near to God, and he will draw near to you. Cleanse your hands, you sinners, and purify your hearts, you double-minded. 9 Be wretched and mourn and weep. Let your laughter be turned to mourning and your joy to gloom.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\fs32 \cf4 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\i0\b\fs28 \cf5 James 4:10 ESV
|
||||
\f4\b0 Humble yourselves before the Lord, and he will exalt you.
|
||||
\f3\b \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\b0\fs32 \cf4 \
|
||||
\
|
||||
\
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2639
|
||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica-Bold;\f1\fswiss\fcharset0 Helvetica-BoldOblique;\f2\fnil\fcharset0 Verdana-Bold;
|
||||
\f3\fnil\fcharset0 Verdana;\f4\fswiss\fcharset0 Helvetica-Oblique;\f5\fnil\fcharset0 Verdana-Italic;
|
||||
}
|
||||
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;\red0\green0\blue0;\red0\green0\blue255;
|
||||
\red0\green23\blue255;\red251\green2\blue255;\red177\green79\blue4;\red252\green16\blue27;}
|
||||
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;\csgray\c0\c0;\cssrgb\c1680\c19835\c100000;
|
||||
\cssrgb\c1229\c23499\c100000;\cssrgb\c100000\c25279\c100000;\cssrgb\c75494\c39056\c0;\cssrgb\c100000\c17519\c13069;}
|
||||
\margl1440\margr1440\vieww11520\viewh16000\viewkind0
|
||||
\deftab720
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs54 \cf2 \cb3 James - Part 9\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\fs32 \cf2 Words Must Not Be Weapons\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\i0\fs28 \cf4 James 4:11-12\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2 \cf4 James 3:6 ESV
|
||||
\f3\b0 the tongue is a fire, a world of unrighteousness. The tongue is set among our members, staining the whole body, setting on fire the entire course of life, and set on fire by hell.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 Words can become WEAPONS and lead to WAR.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 James 4:11 ESV
|
||||
\f3\b0 Do not \ul \ulc5 speak evil\ulnone against one another, brothers. \
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 To \cf4 \'93Speak Evil\'94\cf2 is to SLANDER someone.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f4\i\b0\fs30 \cf6 (slides only)
|
||||
\f0\i0\b\fs34 \cf2 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 2 Samuel 10:1\'962 ESV
|
||||
\f3\b0 After this the king of the Ammonites died, and Hanun his son reigned in his place. 2 And David said, \'93I will deal loyally with Hanun the son of Nahash, as his father dealt loyally with me.\'94 So David sent by his servants to console him concerning his father. And David\'92s servants came into the land of the Ammonites.\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f4\i\fs30 \cf6 (slides only)
|
||||
\f3\i0\fs28 \cf5 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf5 2 Samuel 10:3 ESV
|
||||
\f3\b0 But the princes of the Ammonites said to Hanun their lord, \'93Do you think, because David has sent comforters to you, that he is honoring your father? Has not David sent his servants to you to search the city and to spy it out and to overthrow it?\'94\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f4\i\fs30 \cf6 (slides only)
|
||||
\f3\i0\fs28 \cf5 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf5 2 Samuel 10:18 ESV
|
||||
\f3\b0 And the Syrians fled before Israel, and David killed of the Syrians the men of 700 chariots, and 40,000 horsemen, and wounded Shobach the commander of their army, so that he died there.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f5\i \cf6 (back to both)
|
||||
\f3\i0 \cf5 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf5 James 4:10 ESV
|
||||
\f3\b0 Humble yourselves before the Lord, and he will exalt you.\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\fs36 \cf2 1. Slander SEPARATES us from people we NEED.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 James 4:11 ESV
|
||||
\f3\b0 Do not speak evil against one another,
|
||||
\f2\b brothers
|
||||
\f3\b0 . The one who speaks against a
|
||||
\f2\b brother
|
||||
\f3\b0 or judges his
|
||||
\f2\b brother
|
||||
\f3\b0 , speaks evil against the law and judges the law.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf7 <pic - What\'92sNext>ProPresenter>James>GossipsBridle.jpg>\
|
||||
<pic - What\'92sNext>ProPresenter>James>GossipsBridle2.jpg>\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf6 (*** please do not leave the pictures up for a long time.)\cf5 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b \cf5 Deuteronomy 19:16-19 ESV
|
||||
\f3\b0 If a
|
||||
\f2\b malicious
|
||||
\f3\b0
|
||||
\f2\b witness
|
||||
\f3\b0 arises to accuse a person of wrongdoing, 17 then both parties to the dispute shall appear before the LORD, before the priests and the judges who are in office in those days. 18 The judges shall inquire diligently, and if the witness is a false witness and has accused his brother falsely, 19
|
||||
\f2\b then you shall do to him as he had meant to do to his brother.
|
||||
\f3\b0 So you shall purge the evil from your midst.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f2\b James 4:11 ESV
|
||||
\f3\b0 The one who speaks against a brother or
|
||||
\f2\b \ul judges
|
||||
\f3\b0 \ulnone his brother, speaks evil against the law and judges the law.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 To \cf4 \'93judge\'94\cf2 is to CONDEMN someone.\
|
||||
\
|
||||
\
|
||||
\
|
||||
We don\'92t judge for CONDEMNATION. \
|
||||
We must judge for EVALUATION.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf8 Luke 18:11 ESV
|
||||
\f3\b0 The Pharisee, standing by himself, prayed thus: \'91God, I thank you that I am not like other men, extortioners, unjust, adulterers, or even like this tax collector.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 2. Slander sets our HEARTS in OPPOSITION to God.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 James 4:11b ESV
|
||||
\f3\b0 But if you judge the law, you are not a doer of the law but a judge.\
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f2\b James 2:8 ESV
|
||||
\f3\b0 If you really fulfill the royal
|
||||
\f2\b law
|
||||
\f3\b0 according to the Scripture, \'93
|
||||
\f2\b You shall love your neighbor as yourself\'94
|
||||
\f3\b0 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 3. God\'92s WORD JOINS us to people we NEED.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 James 4:12 ESV
|
||||
\f3\b0 There is only one lawgiver and judge, he who is able to save and to destroy. But who are you to judge your neighbor?\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf6 (come on screen one at a time.)\cf5 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 When the relationship is rough:
|
||||
\f3\b0\fs28 \cf5 \
|
||||
|
||||
\f0\b\fs34 \cf2 A. Go TALK to THEM.\
|
||||
B. Be FOR THEM. \
|
||||
\pard\tx20\tx360\pardeftab720\li360\fi-360\partightenfactor0
|
||||
\cf2 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf8 Matthew 18:15 ESV
|
||||
\f3\b0 \'93If your brother sins against you, go and tell him his fault, between you and him alone. If he listens to you, you have
|
||||
\f2\b gained
|
||||
\f3\b0 your brother.\
|
||||
\pard\tx20\tx360\pardeftab720\li360\fi-360\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf2 C. Let God be the FINAL JUDGE of THEM.\
|
||||
\pard\tx20\tx360\pardeftab720\li360\fi-360\partightenfactor0
|
||||
\cf2 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\fs36 \cf2 4. God\'92s WORD joins US to His SALVATION.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 James 4:12 ESV
|
||||
\f3\b0 There is only one lawgiver and judge, He who is able to
|
||||
\f2\b save
|
||||
\f3\b0 and to
|
||||
\f2\b destroy
|
||||
\f3\b0 . But who are you to judge your \
|
||||
neighbor?\
|
||||
\pard\tx20\tx360\pardeftab720\li360\fi-360\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf8 John 3:17 ESV
|
||||
\f3\b0 For God did not send his Son into the world to condemn the world, but in order that the world might be saved through him.\
|
||||
\pard\tx20\tx360\pardeftab720\li360\fi-360\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 \
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2868
|
||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Arial-BoldMT;\f1\froman\fcharset0 Times-Roman;\f2\fswiss\fcharset0 ArialMT;
|
||||
}
|
||||
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;\red0\green0\blue255;}
|
||||
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;\cssrgb\c0\c9020\c100000;}
|
||||
\margl1440\margr1440\vieww28860\viewh18120\viewkind0
|
||||
\deftab720
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs72 \cf2 Prepared Men Know the Word
|
||||
\f1\b0\fs24 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs48 \cf2 How Do I Hear God?\'a0
|
||||
\f1\b0\fs24 \
|
||||
\
|
||||
|
||||
\f0\b\fs48 If you don\'92t know the \ul WORD\ulnone , you don\'92t know your \ul DIRECTION\ulnone .
|
||||
\f1\b0\fs24 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs37\fsmilli18667 \cf3 Psalm 119:105 ESV
|
||||
\f2\b0 Your word is a lamp to my feet and a light to my path.\'a0\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\fs24 \cf2 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs45\fsmilli22667 \cf2 1. The Word gives \ul DIRECTION\ulnone .
|
||||
\f1\b0\fs24 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs37\fsmilli18667 \cf3 Proverbs 3:5\'966 ESV
|
||||
\f2\b0 Trust in the Lord with all your heart, and do not lean on your own understanding. 6 In all your ways acknowledge him, and he will make straight your paths.
|
||||
\f1\fs24 \cf2 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs48 \cf2 A man without the Word may have \ul CONFIDENCE\ulnone \'97but he doesn\'92t have \ul DIRECTION\ulnone .
|
||||
\f1\b0\fs24 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs45\fsmilli22667 \cf2 2. The Word builds \ul STABILITY.
|
||||
\f1\b0\fs24 \ulnone \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs37\fsmilli18667 \cf3 Psalm 1:1-3 ESV
|
||||
\f2\b0 Blessed is the man who walks not in the counsel of the wicked, nor stands in the way of sinners, nor sits in the seat of scoffers; 2 but his delight is in the law of the Lord, and on his law he meditates day and night. 3 He is like a tree\uc0\u8232 planted by streams of water that yields its fruit in its season, and its leaf does not wither. In all that he does, he prospers.
|
||||
\f1\fs24 \cf2 \
|
||||
\
|
||||
|
||||
\f0\b\fs37\fsmilli18667 \cf3 Ephesians 6:17 ESV
|
||||
\f2\b0 and take the helmet of salvation, and the sword of the Spirit, which is the word of God,
|
||||
\f1\fs24 \cf2 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs48 \cf2 You don\'92t rise to the level of your \ul INTENTIONS\ulnone \'97you fall to the level of your \ul FOUNDATION\ulnone .
|
||||
\f1\b0\fs24 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs45\fsmilli22667 \cf2 3. The Word brings \ul TRANSFORMATION.
|
||||
\f1\b0\fs24 \ulnone \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs37\fsmilli18667 \cf3 Romans 12:2\'a0 ESV
|
||||
\f2\b0 Do not be conformed to this world, but be transformed by the renewal of your mind, that by testing you may discern what is the will of God, what is good and acceptable and perfect.
|
||||
\f1\fs24 \cf2 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs48 \cf2 You can\'92t live a \ul TRANSFORMED\ulnone life with an \ul UNRENEWED MIND\ulnone .
|
||||
\f1\b0\fs24 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs37\fsmilli18667 \cf2 EXCUSES\'a0
|
||||
\f1\b0\fs24 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs37\fsmilli18667 \cf2 Excuse 1:
|
||||
\f0\b NO TIME
|
||||
\f2\b0 \uc0\u8594 You make time for what matters.
|
||||
\f1\fs24 \
|
||||
|
||||
\f2\fs37\fsmilli18667 Excuse 2:
|
||||
\f0\b DON\'92T UNDERSTAND
|
||||
\f2\b0 \uc0\u8594 Understanding comes through reading.
|
||||
\f1\fs24 \
|
||||
|
||||
\f2\fs37\fsmilli18667 Excuse 3:
|
||||
\f0\b DON\'92T KNOW HOW
|
||||
\f2\b0 \uc0\u8594 Start simple and stay consistent.}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,192 @@
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2761
|
||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica-Bold;\f1\fswiss\fcharset0 Helvetica-BoldOblique;\f2\fswiss\fcharset0 Helvetica-Oblique;
|
||||
\f3\fswiss\fcharset0 Helvetica;\f4\fnil\fcharset0 Verdana-Bold;\f5\fnil\fcharset0 Verdana;
|
||||
\f6\fnil\fcharset0 Verdana-Italic;}
|
||||
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;\red0\green0\blue0;\red0\green0\blue255;
|
||||
\red251\green2\blue255;\red0\green23\blue255;\red252\green16\blue27;}
|
||||
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;\csgray\c0\c0;\cssrgb\c1680\c19835\c100000;
|
||||
\cssrgb\c100000\c25279\c100000;\cssrgb\c1229\c23499\c100000;\cssrgb\c100000\c17519\c13069;}
|
||||
\margl1440\margr1440\vieww6720\viewh25160\viewkind0
|
||||
\deftab720
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs54 \cf2 \cb3 Nehemiah - Part 2\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\fs32 \cf2 How To Step into Your Purpose\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\i0\fs28 \cf4 Nehemiah 2\
|
||||
\
|
||||
\
|
||||
|
||||
\f2\i\b0 \cf5 (Come on one at a time)
|
||||
\f0\i0\b \cf4 \
|
||||
|
||||
\fs36 \cf2 Three Big Ideas on Purpose
|
||||
\fs28 \cf4 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\fs34 \cf2 A. PURPOSE is often birthed out of PAIN\
|
||||
B. PURPOSE is better than PRIVILEGE. \
|
||||
C. PURPOSE is about helping PEOPLE.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\fs36 \cf2 1. WAIT in PRAYER.
|
||||
\f3\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f4\b\fs28 \cf6 Nehemiah 2:1 ESV
|
||||
\f5\b0
|
||||
\f4\b In the month of Nisan
|
||||
\f5\b0 , in the twentieth year of King Artaxerxes\'85\cf2 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 2. PLAN in PRAYER. \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f4\fs28 \cf6 Nehemiah 2:1b ESV
|
||||
\f5\b0 \'85 when wine was before him, I took up the wine and gave it to the king. Now I had not been sad in his presence.\cf2 \
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f4\b \cf6 Esther 4:2 ESV
|
||||
\f5\b0 no one was allowed to enter the king\'92s gate clothed in sackcloth.
|
||||
\f4\b \
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
Nehemiah 2:2 ESV
|
||||
\f5\b0 And the king said to me, \'93Why is your face sad, seeing you are not sick? This is nothing but sadness of the heart.\'94 Then I was very much
|
||||
\f4\b afraid
|
||||
\f5\b0 .\
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f4\b Nehemiah 2:3 ESV
|
||||
\f5\b0 I said to the king, \'93Let the king live forever! Why should not my face be sad, when the city, the place of my fathers\'92 graves, lies in ruins, and its gates have been destroyed by fire?\'94
|
||||
\f4\b \
|
||||
|
||||
\f5\b0 \
|
||||
\
|
||||
\
|
||||
|
||||
\f4\b Nehemiah 2:4\'965 ESV
|
||||
\f5\b0 Then the king said to me, \'93What are you requesting?\'94 So I prayed to the God of heaven. 5 And I said to the king, \'93If it pleases the king, and if your servant has found favor in your sight, that you send me to Judah, to the city of my fathers\'92 graves, that I may rebuild it.\'94\
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f4\b Nehemiah 2:6\'967 ESV
|
||||
\f5\b0 And the king said to me (the queen sitting beside him), \'93How long will you be gone, and when will you return?\'94 So it pleased the king to send me when I had given him a time. 7 And I said to the king, \'93If it pleases the king, let letters be given me to the governors of the province Beyond the River, that they may let me pass through until I come to Judah,\
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 What am I WORKING on while I WAIT for God?
|
||||
\f3\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f5\fs28 \cf6 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 3. EVALUATE in PRIVATE.
|
||||
\f3\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f5\fs28 \cf6 \
|
||||
\
|
||||
\
|
||||
|
||||
\f4\b Nehemiah 2:12\'9613 ESV
|
||||
\f5\b0 Then I arose in the
|
||||
\f4\b night
|
||||
\f5\b0 , I and a
|
||||
\f4\b few
|
||||
\f5\b0 men with me. And I
|
||||
\f4\b told no one
|
||||
\f5\b0 what my God had put into my heart to do for Jerusalem. There was no animal with me but the one on which I rode. 13 I went out by night by the Valley Gate to the Dragon Spring and to the Dung Gate, and I inspected the walls of Jerusalem that were broken down and its gates that had been destroyed by fire.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f4\b Nehemiah 2:16 ESV
|
||||
\f5\b0 And the officials did not know where I had gone or what I was doing, and I had not yet told the Jews, the priests, the nobles, the officials, and the rest who were to do the work.
|
||||
\f4\b \
|
||||
|
||||
\f5\b0 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f4\b \cf7 Mark 1:35 ESV
|
||||
\f5\b0 And rising very early in the morning, while it was still dark, he departed and went out to a desolate place, and there he prayed.\cf2 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf6 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f4\b \cf7 Luke 14:28 ESV
|
||||
\f5\b0 For which of you, desiring to build a tower, does not
|
||||
\f4\b first sit down
|
||||
\f5\b0 and
|
||||
\f4\b count the cost
|
||||
\f5\b0 , whether he has enough to complete it?\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf6 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 4. IDENTIFY with PEOPLE in their PROBLEM.
|
||||
\f3\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f4\b\fs28 \cf6 Nehemiah 2:17 ESV
|
||||
\f5\b0 Then I said to them, \'93You see the trouble we are in, how Jerusalem lies in ruins with its gates burned. Come, let us build the wall of Jerusalem, that we may no longer suffer derision.\'94 \
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f4\b \
|
||||
Nehemiah 2:18 ESV
|
||||
\f5\b0 And I told them of the hand of my God that had been upon me for good, and also of the words that the king had spoken to me. And
|
||||
\f4\b they said, \'93Let us rise up and build.
|
||||
\f5\b0 \'94
|
||||
\f4\b So they strengthened their hands for the good work. \
|
||||
|
||||
\f5\b0 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f6\i \cf5 (Slides only)
|
||||
\f5\i0 \cf6 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 Every God-given PURPOSE is about helping PEOPLE.
|
||||
\f3\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f5\fs28 \cf6 \
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2870
|
||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica-Bold;\f1\fswiss\fcharset0 Helvetica-BoldOblique;\f2\fnil\fcharset0 Verdana-Bold;
|
||||
\f3\fnil\fcharset0 Verdana;\f4\fswiss\fcharset0 Helvetica;\f5\fswiss\fcharset0 Helvetica-Oblique;
|
||||
}
|
||||
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;\red0\green0\blue241;\red251\green0\blue7;
|
||||
\red0\green23\blue255;\red0\green0\blue255;}
|
||||
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;\cspthree\c0\c4769\c91973;\cssrgb\c100000\c12195\c0;
|
||||
\cssrgb\c1229\c23499\c100000;\cssrgb\c1680\c19835\c100000;}
|
||||
\vieww9260\viewh20100\viewkind0
|
||||
\deftab720
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs54 \cf2 Where Is Your Faith - Part 7 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\fs32 \cf2 Faith That Gets You Out \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\i0\fs28 \cf3 Hebrews 11:28-31\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\fs36 \cf2 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf4 John 17:15-16 ESV
|
||||
\f3\b0 \cf4 I do not ask that you take them out of the world, but that you keep them from the evil one. 16 They are not of the world, just as I am not of the world.\
|
||||
\cf5 \
|
||||
\
|
||||
\
|
||||
\
|
||||
|
||||
\f2\b Galatians 1:4 ESV
|
||||
\f3\b0 who gave himself for our sins to deliver us from the present evil age, according to the will of our God and Father.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 Everybody in this room is carrying 3 questions:\
|
||||
1. Am I okay with God?\
|
||||
2. Can I ever really get free of my old life?\
|
||||
3. Where do I belong?\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\b0\fs28 \cf5 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\b\fs34 \cf2 How does our faith in God get us out of the world?
|
||||
\f3\i0\b0\fs28 \cf5 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 1. When God sees the BLOOD, judgment passes OVER. \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 Exodus 12:12-13 ESV
|
||||
\f3\b0 For I will pass through the land of Egypt that night, and I will strike all the firstborn in the land of Egypt, both man and beast; and on all the gods of Egypt I will execute judgments: I am the LORD. 13 The blood shall be a sign for you, on the houses where you are. And when I see the blood, I will pass over you, and no plague will befall you to destroy you, when I strike the land of Egypt.
|
||||
\f2\b \
|
||||
|
||||
\f3\b0 \cf2 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 God doesn\'92t check the HOUSE. He checks the DOOR.\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\b0\fs28 \cf2 \
|
||||
\pard\pardeftab560\partightenfactor0
|
||||
|
||||
\f2\b \cf6 Leviticus 17:11 ESV
|
||||
\f3\b0 For the life of the flesh is in the blood, and I have given it for you on the altar to make atonement for your souls, for it is the blood that makes atonement by the life.
|
||||
\f2\b \
|
||||
\
|
||||
Hebrews 9:22 ESV
|
||||
\f3\b0 Indeed, under the law almost everything is purified with blood, and without the shedding of blood there is no forgiveness of sins.
|
||||
\f2\b \
|
||||
\
|
||||
\cf4 John 1:29 ESV
|
||||
\f3\b0 \cf4 The next day he saw Jesus coming toward him, and said, "Behold, the Lamb of God, who takes away the sin of the world!"
|
||||
\f2\b \cf4 \
|
||||
\cf6 \
|
||||
Romans 5:9 ESV
|
||||
\f3\b0 Since, therefore, we have now been justified by his blood, much more shall we be saved by him from the wrath of God.
|
||||
\f2\b \
|
||||
\
|
||||
Exodus 12:22 ESV
|
||||
\f3\b0 ...None of you shall go out of the door of his house until the morning.
|
||||
\f2\b \
|
||||
\
|
||||
Exodus 12:11 ESV
|
||||
\f3\b0 In this manner you shall eat it: with your belt fastened, your sandals on your feet, and your staff in your hand. And you shall eat it in haste. It is the LORD's Passover.
|
||||
\f2\b \
|
||||
|
||||
\f3\b0 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\b\fs34 \cf2 How does our faith in God get us out of the world?\
|
||||
|
||||
\f0\i0 2. When I go through the WATER, my old master can't follow me THROUGH. \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 Exodus 14:7-8 NLT
|
||||
\f3\b0 He took with him 600 of Egypt\'92s best chariots, along with the rest of the chariots of Egypt, each with its commander. 8\'a0The Lord hardened the heart of Pharaoh, the king of Egypt, so he chased after the people of Israel.
|
||||
\f2\b \
|
||||
\
|
||||
Exodus 14:11-12 ESV
|
||||
\f3\b0 Is it because there were no graves in Egypt that you have taken us away to die in the wilderness?\'8512 It would have been better for us to serve the Egyptians than to die in the wilderness.
|
||||
\f2\b \
|
||||
\
|
||||
Exodus 14:13-14 ESV
|
||||
\f3\b0 And Moses said to the people, "Fear not, stand firm, and see the salvation of the LORD, which he will work for you today. For the Egyptians whom you see today, you shall never see again. 14 The LORD will fight for you, and you have only to be silent."
|
||||
\f2\b \
|
||||
\
|
||||
Exodus 14:30 ESV
|
||||
\f3\b0 Thus the LORD saved Israel that day from the hand of the Egyptians, and Israel saw the Egyptians dead on the seashore.\cf2 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 The blood handled the judgment. The WATER cut them off from the old master.\ul \
|
||||
\ulnone \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 1 Corinthians 10:1-2 ESV
|
||||
\f3\b0 For I do not want you to be unaware, brothers, that our fathers were all under the cloud, and all passed through the sea, 2 and all were baptized into Moses in the cloud and in the sea.
|
||||
\f2\b \
|
||||
\
|
||||
Colossians 2:12 ESV
|
||||
\f3\b0 having been buried with him in baptism, in which you were also raised with him through faith in the powerful working of God, who raised him from the dead.
|
||||
\f2\b \
|
||||
\
|
||||
Galatians 3:27 ESV
|
||||
\f3\b0 For as many of you as were baptized into Christ have put on Christ.\cf2 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\b\fs34 \cf2 How does our faith in God get us out of the world?
|
||||
\f3\i0\b0\fs28 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 3. When I come out, God's FAMILY welcomes me IN. \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\fs28 \cf5 Joshua 2:15 ESV
|
||||
\f3\b0 Then she let them down by a rope through the window, for her house was built into the city wall, so that she lived in the wall.
|
||||
\f2\b \
|
||||
\
|
||||
Joshua 2:9-11 ESV
|
||||
\f3\b0 I know that the LORD has given you the land... 10 For we have heard how the LORD dried up the water of the Red Sea before you when you came out of Egypt\'8511 for the LORD your God, he is God in the heavens above and on the earth beneath.
|
||||
\f2\b \
|
||||
\
|
||||
Hebrews 11:31 ESV
|
||||
\f3\b0 By faith Rahab the prostitute did not perish with those who were disobedient, because she had given a friendly welcome to the spies.
|
||||
\f2\b \
|
||||
\
|
||||
Joshua 2:18 ESV
|
||||
\f3\b0 Behold, when we come into the land, you shall tie this scarlet cord in the window through which you let us down, and you shall gather into your house your father and mother, your brothers, and all your father's household.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 Walls FALL to OBEDIENCE that looks RIDICULOUS.\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f4\b0 \cf2 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b\fs28 \cf5 Joshua 6:25 ESV
|
||||
\f3\b0 But Rahab the prostitute and her father's household and all who belonged to her, Joshua saved alive. And she has lived in Israel to this day, because she hid the messengers whom Joshua sent to spy out Jericho.
|
||||
\f2\b \
|
||||
\
|
||||
\cf4 Matthew 1:5 ESV
|
||||
\f3\b0 \cf4 and Salmon the father of Boaz by Rahab, and Boaz the father of Obed by Ruth, and Obed the father of Jesse.
|
||||
\f2\b \cf4 \
|
||||
\
|
||||
John 1:12 ESV
|
||||
\f3\b0 \cf4 But to all who did receive him, who believed in his name, he gave the right to become children of God.
|
||||
\f2\b \cf5 \
|
||||
\
|
||||
Hebrews 10:24-25 ESV
|
||||
\f3\b0 And let us consider how to stir up one another to love and good works, 25 not neglecting to meet together, as is the habit of some, but encouraging one another, and all the more as you see the Day drawing near.
|
||||
\f2\b \
|
||||
\
|
||||
Hebrews 13:2 ESV
|
||||
\f3\b0 Do not neglect to show hospitality to strangers, for thereby some have entertained angels unawares.
|
||||
\f2\b \
|
||||
\
|
||||
Romans 15:7 ESV
|
||||
\f3\b0 Therefore welcome one another as Christ has welcomed you, for the glory of God.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f5\i\fs34 \cf2 Sermon in a Sentence:
|
||||
\f0\i0\b \
|
||||
Faith doesn't just save me from the world, it walks me OUT of it: Jesus' blood passes judgment OVER me, the water brings me THROUGH and cuts off my old master, and God's family welcomes me IN.\
|
||||
\
|
||||
\
|
||||
|
||||
\f4\b0 \
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2759
|
||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica-Bold;\f1\fswiss\fcharset0 Helvetica;\f2\fnil\fcharset0 Verdana;
|
||||
\f3\fnil\fcharset0 Verdana-Bold;}
|
||||
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;\red0\green0\blue0;\red0\green0\blue255;
|
||||
\red159\green242\blue92;\red0\green0\blue0;\red236\green255\blue191;\red34\green255\blue6;\red0\green23\blue255;
|
||||
\red252\green16\blue27;\red25\green60\blue255;}
|
||||
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;\csgray\c0\c0;\cssrgb\c1680\c19835\c100000;
|
||||
\cssrgb\c67668\c94348\c43431;\cssrgb\c0\c0\c0\c0;\cssrgb\c94001\c100000\c79269;\cssrgb\c0\c100000\c0;\cssrgb\c1229\c23499\c100000;
|
||||
\cssrgb\c100000\c17519\c13069;\cssrgb\c12594\c35385\c100000;}
|
||||
\margl1440\margr1440\vieww12360\viewh27180\viewkind0
|
||||
\deftab720
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs54 \cf2 \cb3 It\'92s Not About The Money - Part 3\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\fs32 \cf2 It\'92s All About Trust\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\i0\fs28 \cf4 Proverbs 3:1-10\
|
||||
\
|
||||
\cf5 \cb6 // Slides Only\cf7 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\fs34 \cf2 \cb3 \'93Good decisions come from experience, and experience comes from bad decisions\'94. - Author Unknown
|
||||
\fs28 \cf4 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\b0 \cf8 \cb6 //
|
||||
\f0\b \cf4 \cb3 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\b0\fs34 \cf2 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs28 \cf5 \cb6 // Appear one at a time //\
|
||||
|
||||
\fs36 \cf2 \cb3 A Trustworthy person is: AUTHENTIC, CONSISTENT, COMPASSIONATE, RESOURCEFUL, HUMBLE, AVAILABLE and KIND.
|
||||
\f2\b0\fs28 \cf9 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf9 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\b\fs32 \cf2 5 Benefits of Trusting God:
|
||||
\f2\i0\b0\fs28 \cf9 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 1. Trust God for a SATISFYING and ABUNDANT life.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\fs28 \cf9 Proverbs 3:1-3 ESV
|
||||
\f2\b0 My son, do not forget my teaching, but let your heart keep my commandments, 2\'a0for length of days and years of life and peace they will add to you.
|
||||
\f3\b \
|
||||
\
|
||||
Psalm 91:16 ESV
|
||||
\f2\b0 With long life I will satisfy him and show him my salvation.
|
||||
\f3\b \
|
||||
\
|
||||
John 10:10 ESV\'a0
|
||||
\f2\b0 The thief comes only to steal and kill and destroy. I came that they may have life and have it
|
||||
\f3\b abundantly
|
||||
\f2\b0 .
|
||||
\f3\b \
|
||||
|
||||
\f2\b0 \
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\b\fs32 \cf2 5 Benefits of Trusting God:
|
||||
\f2\i0\b0\fs28 \cf9 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 2. Trust God and GAIN FAVOR with HIM and PEOPLE.
|
||||
\f1\b0\fs34 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\b\fs28 \cf9 Proverbs 3:3-4 ESV
|
||||
\f2\b0 Let not steadfast love and faithfulness forsake you;\'a0bind them around your neck; write them on the tablet of your heart. 4\'a0So you will find favor and good success in the sight of God and man.
|
||||
\f3\b \
|
||||
\
|
||||
Luke 2:40 ESV
|
||||
\f2\b0 And the child grew and became strong, filled with wisdom. And the favor of God was upon him.
|
||||
\f3\b \
|
||||
\
|
||||
Luke 2:52 ESV
|
||||
\f2\b0 And Jesus increased in wisdom and in stature and in favor with God and man.\
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\b\fs32 \cf2 5 Benefits of Trusting God:
|
||||
\f2\i0\b0\fs28 \cf9 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 3. Trust God and He will DIRECT your DECISIONS.
|
||||
\f1\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\b\fs28 \cf9 Proverbs 3: 5-6 ESV
|
||||
\f2\b0 Trust in the Lord with all your heart, and do not lean on your own understanding. 6\'a0In all your ways acknowledge him, and he will make straight your paths.
|
||||
\f3\b \
|
||||
|
||||
\f2\b0 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\b \cf8 \cb6 \
|
||||
\
|
||||
[VIDEO: Ants Spiral of Death]
|
||||
\f2\b0 \cf9 \cb3 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\b \cf9 James 1:5 ESV
|
||||
\f2\b0 If any of you lacks wisdom, let him ask God, who gives generously to all without reproach, and it will be given him.\cf10 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf10 \
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f1\i\b\fs32 \cf2 5 Benefits of Trusting God:
|
||||
\f2\i0\b0\fs28 \cf9 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 4. Trust God and He will SAVE you from FOOLISHNESS.
|
||||
\f2\b0\fs28 \cf10 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\b \cf11 \cb6 Proverbs 3:7-8 ESV
|
||||
\f2\b0 \cf11 Be not wise in your own eyes; fear the Lord, and turn away from evil. 8\'a0It will be healing to your
|
||||
\f3\b \cf11 flesh
|
||||
\f2\b0 \cf11 and refreshment to your bones.
|
||||
\f3\b \cf11 \
|
||||
\
|
||||
Proverbs 28:26 ESV
|
||||
\f2\b0 \cf11 Whoever trusts in his own mind is a
|
||||
\f3\b \cf11 fool
|
||||
\f2\b0 \cf11 , but he who walks in wisdom will be delivered.
|
||||
\f3\b \cf11 \
|
||||
\
|
||||
Matthew 15:19 ESV
|
||||
\f2\b0 \cf11 For out of the heart come evil thoughts, murder, adultery, sexual immorality, theft, false witness, slander.
|
||||
\f3\b \cf11 \
|
||||
\
|
||||
Romans 1:22-23 ESV
|
||||
\f2\b0 \cf11 Claiming to be wise, they became fools, 23\'a0and exchanged the glory of the immortal God for images resembling mortal man and birds and animals and creeping things.\
|
||||
\
|
||||
\
|
||||
|
||||
\f3\b \cf8 \
|
||||
\
|
||||
[PICTURE: Dog Boy]\cf11 \
|
||||
|
||||
\f2\b0 \cf10 \cb3 \
|
||||
\
|
||||
\
|
||||
\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs36 \cf2 5 Benefits of Trusting God:\
|
||||
5. Trust GOD and He will PROVIDE for you.\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f3\fs28 \cf9 Proverbs 3:9-10 ESV
|
||||
\f2\b0 Honor the Lord with your wealth and with the first-fruits of all your produce; 10\'a0then your barns will be filled with plenty, and your vats will be bursting with wine.\
|
||||
|
||||
\f3\b \
|
||||
Malachi 3:10-12 ESV\'a0
|
||||
\f2\b0 Bring the full tithe into the storehouse, that there may be food in my house. And thereby put me to the
|
||||
\f3\b test
|
||||
\f2\b0 , says the Lord of hosts, if I will not open the windows of heaven for you and pour down for you a blessing until there is no more need.
|
||||
\f3\b \
|
||||
\
|
||||
Philippians 4:19 ESV
|
||||
\f2\b0 And my God will supply every need of yours according to his riches in glory in Christ Jesus.
|
||||
\f3\b \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f2\b0 \cf2 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
\cf10 \
|
||||
\
|
||||
\pard\pardeftab720\sl280\partightenfactor0
|
||||
|
||||
\f3\b \cf2 Sermon in a Sentence:
|
||||
\f2\b0 \cf10 \
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
|
||||
\f0\b\fs34 \cf2 If you TRUST GOD, you will have an ABUNDANT LIFE now and an ABUNDANT LIFE in ETERNITY.
|
||||
\f2\b0\fs28 \cf10 \
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,105 @@
|
||||
"""Offline checks (no ProPresenter, no fonts needed). Run: python3 tests/run_tests.py"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, os.path.join(HERE, "..", "src"))
|
||||
import deck # noqa: E402
|
||||
import notes # noqa: E402
|
||||
import pbwire # noqa: E402
|
||||
import plantext # noqa: E402
|
||||
import rtf # noqa: E402
|
||||
|
||||
NOTES = os.path.join(HERE, "notes")
|
||||
PRO = os.path.join(HERE, "pro")
|
||||
SERIES = os.path.join(PRO, "_TEMPLATE - DO NOT DELETE 2608 Lower Thirds.pro")
|
||||
GENERIC = os.path.join(PRO, "_TEMPLATE_GENERIC - DO NOT DELETE 2609.pro")
|
||||
failures = []
|
||||
|
||||
|
||||
def check(name, cond, detail=""):
|
||||
print("%-6s %s%s" % ("ok" if cond else "FAIL", name, (" -- " + detail) if (detail and not cond) else ""))
|
||||
if not cond:
|
||||
failures.append(name)
|
||||
|
||||
|
||||
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"):
|
||||
a = deck.slide_action(cues[deck.uuid_str(cid)])
|
||||
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:
|
||||
s = " / ".join(p_.text for p_ in rtf.read(d).paras).strip()
|
||||
if s:
|
||||
texts.append((deck.element_name(el), s))
|
||||
out.append((deck.action_label(a), texts))
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
# 1. every .pro file survives decode/encode byte for byte
|
||||
for f in sorted(os.listdir(PRO)):
|
||||
if f.endswith(".pro"):
|
||||
b = open(os.path.join(PRO, f), "rb").read()
|
||||
check("roundtrip %s" % f, deck.C.encode(deck.C.decode(b, "rv.data.Presentation")) == b)
|
||||
|
||||
# 2. every notes file parses; the slide list survives the TextEdit format
|
||||
for f in sorted(os.listdir(NOTES)):
|
||||
try:
|
||||
p = notes.parse(open(os.path.join(NOTES, f), "rb").read())
|
||||
p2 = plantext.load(plantext.dump(p, f))
|
||||
same = [(s.kind, s.ref, [(l.text, l.lead) for l in s.lines]) for s in p.slides] == \
|
||||
[(s.kind, s.ref, [(l.text, l.lead) for l in s.lines]) for s in p2.slides]
|
||||
check("notes %s (%d slides)" % (f, len(p.slides)), same and p.title_slide is not None)
|
||||
except Exception:
|
||||
check("notes %s" % f, False, traceback.format_exc().splitlines()[-1])
|
||||
|
||||
# 3. Part 7 from its notes matches the hand-made Part 7 (apart from the known differences)
|
||||
tpl = deck.Template(open(SERIES, "rb").read())
|
||||
plan = notes.parse(open(os.path.join(NOTES, "Slides - Where Is Your Faith - Part 7.rtf"), "rb").read())
|
||||
data = deck.build(tpl, plan, "TEST", ("LOCATION PASTOR", "Mehdi Chniouli"))
|
||||
gen = slides_of(data)
|
||||
hand = slides_of(open(os.path.join(PRO, "261011 Where Is Your Faith Part 7.pro"), "rb").read())
|
||||
check("part 7 slide count", len(gen) == len(hand), "%d vs %d" % (len(gen), len(hand)))
|
||||
check("part 7 labels", [g[0] for g in gen] == [h[0] for h in hand])
|
||||
norm = lambda s: re.sub(r"\s+", " ", s.replace(" ", " ")).strip()
|
||||
diffs = []
|
||||
for k, (g, h) in enumerate(zip(gen, hand), 1):
|
||||
if [(n, norm(t)) for n, t in g[1]] != [(n, norm(t)) for n, t in h[1]]:
|
||||
diffs.append(k)
|
||||
# 17: the operator added a manual line break ("can't follow / me THROUGH")
|
||||
check("part 7 texts identical except slide 17", diffs == [17], "differs on %s" % diffs)
|
||||
|
||||
# 4. both templates build without text measurement, and the result decodes
|
||||
for path in (SERIES, GENERIC):
|
||||
t = deck.Template(open(path, "rb").read())
|
||||
for f in ("Slides - 7 Letters - Part 2.rtf", "Slides - May 2026 Forge Night.rtf"):
|
||||
p = notes.parse(open(os.path.join(NOTES, f), "rb").read())
|
||||
d = deck.build(t, p, "TEST", t.speaker())
|
||||
out = slides_of(d)
|
||||
n_build = sum(1 for s in p.slides if s.kind == "build")
|
||||
want = len(t.head) + sum(1 for s in p.slides if s.kind != "title") + n_build + len(t.tail)
|
||||
check("build %s + %s" % (os.path.basename(path)[:22], f), len(out) == want, "%d vs %d" % (len(out), want))
|
||||
|
||||
# 5. fresh ids everywhere, references to shared things untouched
|
||||
tu = set(re.findall(rb"[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}", open(SERIES, "rb").read()))
|
||||
gu = set(re.findall(rb"[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}", data))
|
||||
shared = tu & gu
|
||||
check("only shared references reused (%d)" % len(shared), all(
|
||||
x.decode() in open(SERIES, "rb").read().decode("latin-1") for x in shared) and len(shared) <= 12)
|
||||
|
||||
print()
|
||||
print("FAILED: %s" % failures if failures else "all checks passed")
|
||||
return 1 if failures else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
#==============================================================================
|
||||
# MESSAGE NOTES -> PROPRESENTER
|
||||
#
|
||||
# Turns the pastor's notes ("Slides - <message>.rtf", or a Word file) into the
|
||||
# message presentation, built from the series or generic template in the
|
||||
# Messages library - the same slides you'd make by copy and paste.
|
||||
#
|
||||
# Double-click, then answer the questions:
|
||||
# 1. which notes file (newest ones are listed; or drag a file in)
|
||||
# 2. which template (series / generic - it suggests one)
|
||||
# 3. who is speaking (recent speakers are listed)
|
||||
# 4. date + presentation name (e.g. "261018 Where Is Your Faith Part 8")
|
||||
# Then every slide is listed for review. Nothing is written until you press
|
||||
# Enter at "Create this presentation?". Type q at any prompt to quit.
|
||||
#
|
||||
# Afterwards it checks the new presentation through ProPresenter's API and
|
||||
# opens a page of slide previews drawn by ProPresenter itself.
|
||||
#
|
||||
# Program: ~/Library/Application Support/MessageNotes/message-notes.pyz
|
||||
# Log: ~/Library/Logs/message-notes.log
|
||||
#==============================================================================
|
||||
APP="$HOME/Library/Application Support/MessageNotes/message-notes.pyz"
|
||||
clear
|
||||
if [ ! -f "$APP" ]; then
|
||||
echo "Can't find $APP"
|
||||
read -r -p "Press Enter to close." _
|
||||
exit 1
|
||||
fi
|
||||
/usr/bin/python3 "$APP" "$@"
|
||||
status=$?
|
||||
echo
|
||||
read -r -p "Press Enter to close this window." _
|
||||
exit $status
|
||||
@@ -0,0 +1,9 @@
|
||||
"""Rewrite .py files so they are pure ASCII (non-ASCII characters become \\u / \\U escapes)."""
|
||||
import glob, sys
|
||||
for f in sys.argv[1:] or glob.glob("*.py"):
|
||||
s = open(f, encoding="utf-8").read()
|
||||
if all(ord(c) < 128 for c in s):
|
||||
continue
|
||||
out = "".join(c if ord(c) < 128 else ("\\u%04x" % ord(c) if ord(c) <= 0xFFFF else "\\U%08x" % ord(c)) for c in s)
|
||||
open(f, "w", encoding="ascii").write(out)
|
||||
print("escaped", f)
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
# Build dist/message-notes.pyz (zipapp) and copy the Desktop launcher next to it.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
rm -rf dist build/pyz && mkdir -p dist build/pyz
|
||||
cp src/*.py src/schema.json apps-script/SaveSermonNotes.gs build/pyz/
|
||||
rm -rf build/pyz/__pycache__
|
||||
/usr/bin/python3 -m zipapp build/pyz -o dist/message-notes.pyz -p "/usr/bin/env python3"
|
||||
cp "tools/Message Notes to ProPresenter.command" dist/
|
||||
chmod +x "dist/Message Notes to ProPresenter.command"
|
||||
rm -rf build
|
||||
ls -l dist
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
# Build and install on the ProPresenter Mac (via the ingest Mac, which holds the SSH key).
|
||||
# app -> ~/Library/Application Support/MessageNotes/message-notes.pyz
|
||||
# launcher -> ~/Desktop/Message Notes to ProPresenter.command
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
tools/build.sh >/dev/null
|
||||
HOP=checklist-ingest
|
||||
PROP=napropresenter@10.1.14.225
|
||||
scp -q dist/message-notes.pyz "dist/Message Notes to ProPresenter.command" "$HOP:/tmp/"
|
||||
ssh "$HOP" "scp -q -i ~/.ssh/id_status_panel /tmp/message-notes.pyz '/tmp/Message Notes to ProPresenter.command' $PROP:/tmp/ \
|
||||
&& rm -f /tmp/message-notes.pyz '/tmp/Message Notes to ProPresenter.command' \
|
||||
&& ssh -i ~/.ssh/id_status_panel $PROP 'set -e; D=\"\$HOME/Library/Application Support/MessageNotes\"; mkdir -p \"\$D\"; \
|
||||
mv /tmp/message-notes.pyz \"\$D/message-notes.pyz\"; \
|
||||
mv \"/tmp/Message Notes to ProPresenter.command\" \"\$HOME/Desktop/Message Notes to ProPresenter.command\"; \
|
||||
chmod +x \"\$HOME/Desktop/Message Notes to ProPresenter.command\"; \
|
||||
/usr/bin/python3 \"\$D/message-notes.pyz\" --version; ls -l \"\$D/message-notes.pyz\" \"\$HOME/Desktop/Message Notes to ProPresenter.command\"'"
|
||||
@@ -0,0 +1,164 @@
|
||||
"""Read .proto files as text and build a field table: {message: {num: [name, type, repeated]}}.
|
||||
|
||||
Types are either a scalar keyword ('string', 'bytes', 'double', 'int32', 'bool', ...),
|
||||
'enum', or the fully qualified name of a message ('rv.data.UUID').
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
SCALARS = {"double", "float", "int32", "int64", "uint32", "uint64", "sint32", "sint64",
|
||||
"fixed32", "fixed64", "sfixed32", "sfixed64", "bool", "string", "bytes"}
|
||||
|
||||
|
||||
def tokenize(src):
|
||||
src = re.sub(r"//[^\n]*", "", src)
|
||||
src = re.sub(r"/\*.*?\*/", "", src, flags=re.S)
|
||||
return re.findall(r'"(?:[^"\\]|\\.)*"|[A-Za-z_][\w.]*|\.[A-Za-z_][\w.]*|-?\d+(?:\.\d+)?|[{}\[\]();=<>,]', src)
|
||||
|
||||
|
||||
def parse_file(path, messages, enums):
|
||||
toks = tokenize(open(path).read())
|
||||
pkg = ""
|
||||
i = 0
|
||||
scope = [] # stack of (kind, fqname)
|
||||
pending = [] # fields with unresolved types: (msg_fq, num, name, rawtype, repeated, scope_names)
|
||||
|
||||
def cur_msg():
|
||||
for kind, fq in reversed(scope):
|
||||
if kind == "message":
|
||||
return fq
|
||||
return None
|
||||
|
||||
while i < len(toks):
|
||||
t = toks[i]
|
||||
if t == "package":
|
||||
pkg = toks[i + 1]
|
||||
i += 3
|
||||
continue
|
||||
if t in ("syntax", "import", "option"):
|
||||
while toks[i] != ";":
|
||||
i += 1
|
||||
i += 1
|
||||
continue
|
||||
if t in ("message", "enum", "oneof", "service"):
|
||||
name = toks[i + 1]
|
||||
parent = scope[-1][1] if scope else pkg
|
||||
fq = parent + "." + name if parent else name
|
||||
if t == "oneof":
|
||||
fq = scope[-1][1] # fields belong to the enclosing message
|
||||
scope.append((t, fq))
|
||||
if t == "message":
|
||||
messages.setdefault(fq, {})
|
||||
elif t == "enum":
|
||||
enums.add(fq)
|
||||
i += 3 # name {
|
||||
continue
|
||||
if t == "}":
|
||||
scope.pop()
|
||||
i += 1
|
||||
continue
|
||||
if t == "reserved" or t == "extensions":
|
||||
while toks[i] != ";":
|
||||
i += 1
|
||||
i += 1
|
||||
continue
|
||||
if scope and scope[-1][0] == "enum":
|
||||
while toks[i] != ";":
|
||||
i += 1
|
||||
i += 1
|
||||
continue
|
||||
if scope and scope[-1][0] in ("message", "oneof"):
|
||||
repeated = False
|
||||
if t in ("repeated", "optional", "required"):
|
||||
repeated = t == "repeated"
|
||||
i += 1
|
||||
t = toks[i]
|
||||
if t == "map":
|
||||
# map<k, v> name = n;
|
||||
j = toks.index(";", i)
|
||||
name = toks[j - 3]
|
||||
num = int(toks[j - 1]) if toks[j - 1] != "]" else None
|
||||
vt = toks[i + 4]
|
||||
pending.append((cur_msg(), int(toks[toks.index("=", i) + 1]), name, "map:" + vt, True,
|
||||
[fq for _, fq in scope]))
|
||||
i = j + 1
|
||||
continue
|
||||
rawtype = t
|
||||
name = toks[i + 1]
|
||||
num = int(toks[i + 3])
|
||||
j = i + 4
|
||||
if toks[j] == "[":
|
||||
while toks[j] != "]":
|
||||
j += 1
|
||||
j += 1
|
||||
pending.append((cur_msg(), num, name, rawtype, repeated, [fq for _, fq in scope]))
|
||||
i = j + 1
|
||||
continue
|
||||
i += 1
|
||||
return pending
|
||||
|
||||
|
||||
def resolve(rawtype, scopes, messages, enums):
|
||||
if rawtype in SCALARS:
|
||||
return rawtype
|
||||
if rawtype.startswith("."):
|
||||
cand = rawtype[1:]
|
||||
if cand in messages:
|
||||
return cand
|
||||
if cand in enums:
|
||||
return "enum"
|
||||
raise KeyError(rawtype)
|
||||
# search enclosing scopes from innermost outwards
|
||||
for sc in reversed(scopes):
|
||||
parts = sc.split(".")
|
||||
for k in range(len(parts), -1, -1):
|
||||
cand = ".".join(parts[:k] + [rawtype]) if k else rawtype
|
||||
if cand in messages:
|
||||
return cand
|
||||
if cand in enums:
|
||||
return "enum"
|
||||
raise KeyError(rawtype)
|
||||
|
||||
|
||||
def build(proto_dir):
|
||||
messages, enums, pending = {}, set(), []
|
||||
for fn in sorted(os.listdir(proto_dir)):
|
||||
if fn.endswith(".proto"):
|
||||
pending += parse_file(os.path.join(proto_dir, fn), messages, enums)
|
||||
for gdir in ("google/protobuf",):
|
||||
d = os.path.join(proto_dir, gdir)
|
||||
if os.path.isdir(d):
|
||||
for fn in sorted(os.listdir(d)):
|
||||
if fn.endswith(".proto"):
|
||||
pending += parse_file(os.path.join(d, fn), messages, enums)
|
||||
for msg, num, name, rawtype, rep, scopes in pending:
|
||||
if rawtype.startswith("map:"):
|
||||
messages[msg][num] = [name, "map", True]
|
||||
continue
|
||||
messages[msg][num] = [name, resolve(rawtype, scopes, messages, enums), rep]
|
||||
return messages
|
||||
|
||||
|
||||
def reachable(messages, root):
|
||||
out, todo = {}, [root]
|
||||
while todo:
|
||||
m = todo.pop()
|
||||
if m in out:
|
||||
continue
|
||||
out[m] = messages[m]
|
||||
for name, typ, rep in messages[m].values():
|
||||
if typ in messages and typ not in out:
|
||||
todo.append(typ)
|
||||
return out
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
msgs = build(sys.argv[1])
|
||||
sub = reachable(msgs, "rv.data.Presentation")
|
||||
sub.update(reachable(msgs, "rv.data.Template.Document"))
|
||||
sub.update(reachable(msgs, "rv.data.PlaylistDocument"))
|
||||
json.dump({k: {str(n): v for n, v in f.items()} for k, f in sorted(sub.items())},
|
||||
open(sys.argv[2], "w"), separators=(",", ":"), sort_keys=True)
|
||||
print(len(msgs), "messages parsed;", len(sub), "reachable from Presentation + Template.Document")
|
||||
@@ -0,0 +1,23 @@
|
||||
"""Dev helper: run measure.JXA on the ProPresenter Mac (via the ingest Mac) from a laptop."""
|
||||
import json, os, subprocess, sys, tempfile
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "src"))
|
||||
import measure as M
|
||||
|
||||
HOP = "checklist-ingest"
|
||||
PROP = "napropresenter@10.1.14.225"
|
||||
KEY = "~/.ssh/id_status_panel"
|
||||
|
||||
|
||||
def measure(items):
|
||||
if not items:
|
||||
return []
|
||||
payload = json.dumps([{"rtf": it["rtf"].decode("latin-1"), "w": it["w"]} for it in items])
|
||||
script = M.JXA
|
||||
remote = ("d=$(mktemp -d); cat > $d/in.json; printf '%%s' %s | base64 -D > $d/m.js; "
|
||||
"osascript -l JavaScript $d/m.js $d/in.json; rm -rf $d") % (
|
||||
__import__("shlex").quote(__import__("base64").b64encode(script.encode()).decode()))
|
||||
cmd = ["ssh", HOP, "ssh -i %s -o BatchMode=yes %s %s" % (KEY, PROP, __import__("shlex").quote(remote))]
|
||||
p = subprocess.run(cmd, input=payload, capture_output=True, text=True, timeout=300)
|
||||
if p.returncode != 0 or not p.stdout.strip():
|
||||
raise M.Unavailable(p.stderr[-500:])
|
||||
return json.loads(p.stdout)
|
||||
@@ -0,0 +1,46 @@
|
||||
"""One-off check on the ProPresenter Mac: link a message into a playlist via a restart.
|
||||
|
||||
Usage (on the ProP Mac):
|
||||
/usr/bin/python3 test_restart_link.py <message-notes.pyz> "<notes title>" YYYY-MM-DD "<presentation name>"
|
||||
Refuses to run if anything is on screen. Prints whether the link is in ProPresenter's
|
||||
playlist file after ProPresenter is running again (and still there 60 s later).
|
||||
"""
|
||||
import datetime
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
sys.path.insert(0, sys.argv[1])
|
||||
import app # noqa: E402
|
||||
import playlist # noqa: E402
|
||||
import ppctl # noqa: E402
|
||||
|
||||
title, day, name = sys.argv[2], sys.argv[3], sys.argv[4]
|
||||
date = datetime.date(*map(int, day.split("-")))
|
||||
library = app.find_library()
|
||||
pro = os.path.join(library, name + ".pro")
|
||||
assert os.path.isfile(pro), pro
|
||||
|
||||
why = ppctl.busy_reason(app.API)
|
||||
if why:
|
||||
print("NOT RUN: %s" % why)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def linked():
|
||||
_, doc = playlist.load(playlist.playlist_file(library))
|
||||
d, pl = playlist.find_service(doc, title)
|
||||
if pl is None:
|
||||
return "no playlist"
|
||||
h, k = playlist.message_slot(pl)
|
||||
it = playlist.items_of(pl)[k] if k is not None else None
|
||||
return playlist.linked_path(it) if it is not None else "no slot"
|
||||
|
||||
|
||||
print("before:", repr(linked()))
|
||||
t0 = time.time()
|
||||
print(ppctl.restart_with(lambda: app.link_playlist(library, title, date, name, pro), app.API, print))
|
||||
print("ProPresenter back after %.0fs, running: %s" % (time.time() - t0, ppctl.app_path(ppctl.pid()) if ppctl.pid() else None))
|
||||
print("after:", repr(linked()))
|
||||
time.sleep(60)
|
||||
print("after 60s:", repr(linked()))
|
||||
Reference in New Issue
Block a user