fixed book markers

This commit is contained in:
2026-02-26 12:09:43 -07:00
parent 6cefc3c862
commit 44bc757f3f
4 changed files with 156 additions and 506 deletions

View File

@ -4,12 +4,17 @@ audiobook_nem.py
Generate the Book of the Nem audiobook — one unique voice per book/section.
Usage:
python audiobook_nem.py
python create_audiobook_nem.py # all enabled books
python create_audiobook_nem.py --list # list available book labels
python create_audiobook_nem.py Introduction
python create_audiobook_nem.py "Book of Hagoth"
python create_audiobook_nem.py Introduction "Book of Hagoth"
To skip a section, comment out its entry in BOOKS below.
To permanently skip a section, comment out its entry in BOOKS below.
Output .wav files are written to OUTPUT_DIR (created automatically).
"""
import argparse
import re
import time
import numpy as np
@ -41,30 +46,30 @@ LANG_CODE = "a" # 'a' = American English
# am_santa American male [downloaded] (not used)
# ── Book definitions ───────────────────────────────────────────────────────────
# Format: (label, start_marker, voice, output_wav)
# start_marker exact text of the FIRST line of the section header in the source
# (leading/trailing whitespace is ignored when matching)
# Format: (label, (start_line1, start_line2), voice, output_wav)
# start_line1 exact text of the FIRST line of the section header
# start_line2 prefix of the SECOND line (used together for unambiguous matching)
# voice Kokoro voice name
# output_wav filename saved inside OUTPUT_DIR
#
# Comment out any line to skip that section entirely.
BOOKS = [
# label start_marker voice output_wav
("Introduction", "Introduction", "af_heart", "00_introduction.wav"),
("Book of Hagoth", "THE BOOK OF HAGOTH", "am_fenrir", "01_hagoth.wav"),
# ("Shi-Tugo I", "THE FIRST BOOK OF SHI-TUGO", "am_eric", "02_shi_tugo_1.wav"),
# ("Sanempet", "THE BOOK OF SANEMPET", "am_liam", "03_sanempet.wav"),
# ("Oug", "THE BOOK OF OUG", "am_michael", "04_oug.wav"),
# ("Temple Writings of Oug", "THE BOOK OF", "am_michael", "05_temple_writings_oug.wav"),
# ("Sacred Temple Writings", "THE SACRED", "am_michael", "06_sacred_temple_writings.wav"),
# ("Samuel the Lamanite I", "THE FIRST BOOK", "am_echo", "07_samuel_lamanite_1.wav"),
# ("Samuel the Lamanite II", "THE SECOND BOOK", "am_echo", "08_samuel_lamanite_2.wav"),
# ("Manti", "THE BOOK OF MANTI", "am_onyx", "09_manti.wav"),
# ("Pa Nat I", "THE FIRST BOOK OF PA NAT", "af_nicole", "10_pa_nat_1.wav"),
# ("Moroni I", "THE FIRST BOOK OF MORONI", "am_adam", "11_moroni_1.wav"),
# ("Moroni II", "THE SECOND BOOK OF MORONI", "am_adam", "12_moroni_2.wav"),
# ("Moroni III", "THE THIRD BOOK OF MORONI", "am_adam", "13_moroni_3.wav"),
# ("Shioni", "THE BOOK OF SHIONI", "am_puck", "14_shioni.wav"),
# label (start_line1, start_line2) voice output_wav
("Introduction", ("Introduction", "The Book of the Nem"), "af_heart", "00_introduction.wav"),
("Book of Hagoth", ("THE BOOK OF HAGOTH", "THE SON OF HAGMENI,"), "am_fenrir", "01_hagoth.wav"),
("Shi-Tugo I", ("THE FIRST BOOK OF SHI-TUGO", "FORMER WARRIOR, AMMONITE"), "am_eric", "02_shi_tugo_1.wav"),
("Sanempet", ("THE BOOK OF SANEMPET", "THE SON OF HAGMENI,"), "am_liam", "03_sanempet.wav"),
("Oug", ("THE BOOK OF OUG", "THE SON OF SANEMPET"), "am_michael", "04_oug.wav"),
("Temple Writings of Oug", ("THE BOOK OF", "THE TEMPLE WRITINGS"), "am_michael", "05_temple_writings_oug.wav"),
("Sacred Temple Writings", ("THE SACRED", "TEMPLE WRITINGS"), "am_michael", "06_sacred_temple_writings.wav"),
("Samuel the Lamanite I", ("THE FIRST BOOK", "OF SAMUEL THE LAMANITE"), "am_echo", "07_samuel_lamanite_1.wav"),
("Samuel the Lamanite II", ("THE SECOND BOOK", "OF SAMUEL THE LAMANITE"), "am_echo", "08_samuel_lamanite_2.wav"),
("Manti", ("THE BOOK OF MANTI", "THE SON OF OUG"), "am_onyx", "09_manti.wav"),
("Pa Nat I", ("THE FIRST BOOK OF PA NAT", "THE DAUGHTER OF SHIMLEI"), "af_nicole", "10_pa_nat_1.wav"),
("Moroni I", ("THE FIRST BOOK OF MORONI", "THE SON OF MORMON,"), "am_adam", "11_moroni_1.wav"),
("Moroni II", ("THE SECOND BOOK OF MORONI", "THE SON OF MORMON,"), "am_adam", "12_moroni_2.wav"),
("Moroni III", ("THE THIRD BOOK OF MORONI", "THE SON OF MORMON,"), "am_adam", "13_moroni_3.wav"),
("Shioni", ("THE BOOK OF SHIONI", "THE SON OF MORONI"), "am_puck", "14_shioni.wav"),
]
# ── Helpers ────────────────────────────────────────────────────────────────────
@ -72,23 +77,24 @@ BOOKS = [
def load_and_split(source: Path, books: list) -> dict[str, str]:
"""
Read the source file and split it into sections keyed by label.
Each section starts at its start_marker line and ends just before the
next section's start_marker.
Each section starts at its (start_line1, start_line2) marker pair and
ends just before the next section's marker.
"""
raw_lines = source.read_text(encoding="utf-8").splitlines()
# Build a mapping: marker_text → index in BOOKS
markers = [(label, marker.strip()) for label, marker, _, _ in books]
# Build a mapping: (label, line1, line2) for each book
markers = [(label, m[0].strip(), m[1].strip()) for label, m, _, _ in books]
# Find the line index of each marker's first occurrence
# Find the line index of each marker's first occurrence (two-line match)
marker_positions: list[tuple[int, int]] = [] # (line_idx, books_idx)
for book_idx, (label, marker) in enumerate(markers):
for line_idx, line in enumerate(raw_lines):
if line.strip() == marker:
for book_idx, (label, m1, m2) in enumerate(markers):
for line_idx, line in enumerate(raw_lines[:-1]):
if (line.strip() == m1 and
raw_lines[line_idx + 1].strip().startswith(m2)):
marker_positions.append((line_idx, book_idx))
break
else:
print(f" ⚠ Marker not found for '{label}': '{marker}' — skipping")
print(f" ⚠ Marker not found for '{label}': '{m1}' / '{m2}' — skipping")
marker_positions.sort(key=lambda x: x[0])
@ -154,6 +160,38 @@ def generate_audio(pipeline: KPipeline, text: str, voice: str,
# ── Main ───────────────────────────────────────────────────────────────────────
def main() -> None:
# ── CLI ────────────────────────────────────────────────────────────
parser = argparse.ArgumentParser(description="Generate Nem audiobook sections.")
parser.add_argument(
"books", nargs="*",
help="Labels of sections to generate (default: all enabled books). "
"Use --list to see available labels."
)
parser.add_argument(
"--list", action="store_true",
help="Print all enabled book labels and exit."
)
args = parser.parse_args()
enabled_labels = [label for label, _, _, _ in BOOKS]
if args.list:
print("Enabled books:")
for label in enabled_labels:
print(f" {label}")
return
# Filter to requested subset, preserving BOOKS order
if args.books:
unknown = [b for b in args.books if b not in enabled_labels]
if unknown:
print(f"Unknown book label(s): {', '.join(unknown)}")
print(f"Run with --list to see available labels.")
return
run_books = [b for b in BOOKS if b[0] in args.books]
else:
run_books = list(BOOKS)
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Device: {device}")
if device == "cuda":
@ -164,8 +202,10 @@ def main() -> None:
print(f"\nSource: '{SOURCE_FILE}'"
+ (" ✓ (TTS fixed)" if SOURCE_FILE == _FIXED_FILE else
" ⚠ (original — run 'Apply Fixes to Text' in the GUI to use phonetic fixes)"))
# Always split using ALL books for correct section boundaries,
# but only generate for run_books.
sections = load_and_split(SOURCE_FILE, BOOKS)
print(f" Found {len(sections)} sections.\n")
print(f" Found {len(sections)} sections ({len(run_books)} selected).\n")
print("Initialising Kokoro pipeline …")
pipeline = KPipeline(lang_code=LANG_CODE)
@ -173,14 +213,26 @@ def main() -> None:
# Pre-compute char counts for all sections so we can estimate ETAs
section_chars: dict[str, int] = {
label: len(clean_text(sections[label]))
for label, _, _, _ in BOOKS
for label, _, _, _ in run_books
if label in sections
}
# Print char count summary before starting
print(f"\n{'' * 52}")
print(f" {'Section':<30} {'Chars':>8}")
print(f"{'' * 52}")
for label, _, _, wav_name in run_books:
if label in section_chars:
print(f" {label:<30} {section_chars[label]:>8,}")
print(f"{'' * 52}")
total_chars = sum(section_chars.values())
print(f" {'TOTAL':<30} {total_chars:>8,}")
print()
chars_per_sec: float | None = None # derived from the first book that finishes
timing_rows: list[tuple[str, int, float]] = [] # (label, chars, elapsed)
for label, marker, voice, wav_name in BOOKS:
for label, _marker, voice, wav_name in run_books:
if label not in sections:
continue

View File

@ -84,8 +84,11 @@ MAUVE = "#cba6f7"
def play_async(path: Path) -> None:
sd.stop()
def _play():
data, sr = sf.read(str(path), dtype="float32")
sd.play(data, sr)
try:
data, sr = sf.read(str(path), dtype="float32")
sd.play(data, sr)
except Exception as exc:
print(f"[audio] playback error: {exc}")
threading.Thread(target=_play, daemon=True).start()
@ -119,11 +122,14 @@ def synth_and_play(text: str, on_ready=None) -> None:
*on_ready(path)* is called on the same thread once the file is written.
"""
def _run():
path = _synth_to_cache(text)
if path:
if on_ready:
on_ready(path)
play_async(path)
try:
path = _synth_to_cache(text)
if path:
if on_ready:
on_ready(path)
play_async(path)
except Exception as exc:
print(f"[synth] error synthesising '{text}': {exc}")
threading.Thread(target=_run, daemon=True).start()
@ -216,6 +222,8 @@ class ProperNounAuditor(tk.Tk):
self._build_ui()
self._refresh_all()
self._alive = True
self.protocol("WM_DELETE_WINDOW", self._on_close)
# Window-level hotkeys (work even when a listbox has keyboard focus)
self.bind("<space>", lambda e: self._replay())
@ -224,6 +232,19 @@ class ProperNounAuditor(tk.Tk):
if self.focus_get() is not self._fix_entry else None)
self.bind("<Escape>", lambda e: self._reset_fix_entry())
def _on_close(self) -> None:
self._alive = False
sd.stop()
self.destroy()
def _safe_after(self, ms: int, func) -> None:
"""Schedule func on the Tk thread; silently no-ops if window is gone."""
if self._alive:
try:
self.after(ms, func)
except RuntimeError:
pass
# ── UI construction ────────────────────────────────────────────────────────
def _build_ui(self) -> None:
@ -457,7 +478,7 @@ class ProperNounAuditor(tk.Tk):
self.fix_var.set(replacement)
self.now_playing_var.set(f"{replacement}")
def _on_ready(_path):
self.after(0, lambda: self.now_playing_var.set(replacement))
self._safe_after(0, lambda: self.now_playing_var.set(replacement))
synth_and_play(replacement, on_ready=_on_ready)
else:
# Correct list — show word in fix entry, play it
@ -515,7 +536,7 @@ class ProperNounAuditor(tk.Tk):
target.unlink()
self.now_playing_var.set(f"… regen {fix_text}")
def _on_ready(_p):
self.after(0, lambda: self.now_playing_var.set(fix_text))
self._safe_after(0, lambda: self.now_playing_var.set(fix_text))
synth_and_play(fix_text, on_ready=_on_ready)
else:
# Re-gen the manifest audio for the review word
@ -528,18 +549,21 @@ class ProperNounAuditor(tk.Tk):
self.now_playing_var.set(f"… regen {word}")
def _regen():
import warnings, numpy as np
pipeline = _get_pipeline()
chunks = []
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=UserWarning)
for _, _, audio in pipeline(word, voice=VOICE):
if audio is not None:
chunks.append(audio)
if chunks:
sf.write(str(wav_path), np.concatenate(chunks), SAMPLE_RATE)
self.after(0, lambda: self.now_playing_var.set(word))
play_async(wav_path)
try:
import warnings, numpy as np
pipeline = _get_pipeline()
chunks = []
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=UserWarning)
for _, _, audio in pipeline(word, voice=VOICE):
if audio is not None:
chunks.append(audio)
if chunks:
sf.write(str(wav_path), np.concatenate(chunks), SAMPLE_RATE)
self._safe_after(0, lambda: self.now_playing_var.set(word))
play_async(wav_path)
except Exception as exc:
print(f"[regen] error for '{word}': {exc}")
threading.Thread(target=_regen, daemon=True).start()
@ -669,17 +693,21 @@ class ProperNounAuditor(tk.Tk):
self._pregen_status_var.set(f"0 / {new_count} new ({already} cached)")
def _run():
done = 0
for rep in replacements:
cache_path = REPLACEMENTS_DIR / f"{_slug(rep)}.wav"
if not cache_path.exists():
_synth_to_cache(rep)
done += 1
self.after(0, lambda d=done, t=new_count:
self._pregen_status_var.set(f"{d} / {t} synthesised…"))
self.after(0, lambda: self._pregen_status_var.set(
f"Done — {total} clips ready"))
self.after(0, lambda: self._pregen_btn.config(state="normal"))
try:
done = 0
for rep in replacements:
cache_path = REPLACEMENTS_DIR / f"{_slug(rep)}.wav"
if not cache_path.exists():
_synth_to_cache(rep)
done += 1
self._safe_after(0, lambda d=done, t=new_count:
self._pregen_status_var.set(f"{d} / {t} synthesised…"))
self._safe_after(0, lambda: self._pregen_status_var.set(
f"Done — {total} clips ready"))
except Exception as exc:
print(f"[pregen] error: {exc}")
finally:
self._safe_after(0, lambda: self._pregen_btn.config(state="normal"))
threading.Thread(target=_run, daemon=True).start()

View File

@ -1,15 +1,13 @@
[
"Ninety-Two",
"Gilgal",
"Nat",
"Monoriah",
"Akim",
"Amoron",
"Migdan Idi",
"Migdan",
"Midgan-Idi",
"Midgan Idi",
"Midgan",
"Mic",
"Mi",
"Mentina",
"Hemeacum",
"Micah",
@ -21,7 +19,6 @@
"Kishkumen",
"Kee",
"Kayith",
"Kay",
"Pah",
"Kamiakim",
"Corian-Co-Hah",
@ -29,9 +26,6 @@
"Chunish",
"Chu",
"Cheem",
"Co",
"Thanksgiving",
"Way",
"Zoreth",
"Zoramites",
"Zoramite",
@ -42,374 +36,120 @@
"Zen",
"Zeezret",
"Zedekiah",
"King",
"Zarahemla",
"Yourselves",
"Yourself",
"Yours",
"Kingdom",
"Day",
"Young",
"Yohks",
"Yesterday",
"Yay",
"Writings",
"Write",
"Worthy",
"Worlds",
"World",
"Worketh",
"Word",
"Women",
"Woman",
"Woe",
"Wives",
"Winter",
"Winebag",
"Winding",
"Wind",
"Willow",
"Wife",
"Whom",
"Whereupon",
"Wherefore",
"Whatsoever",
"Western",
"West",
"Weeks",
"Wee",
"Ways",
"Waylit",
"Wayat",
"Waters",
"Water",
"Washing",
"Wards",
"War",
"Wallohitwah",
"Walk",
"Wah",
"Voice",
"Virtue",
"Vineyards",
"Vineyard",
"Verily",
"Veil",
"Heaven",
"Valley",
"Urim",
"Unquenchable",
"Fire",
"Universe",
"Universal",
"United",
"Two-Fold",
"Two",
"Thousand",
"Months",
"Hundred",
"Days",
"Twins",
"Twenty",
"Five",
"Years",
"Twelve",
"Tugo",
"Truth",
"True",
"Tree",
"Traveling",
"Councils",
"Peli",
"Tornit",
"Torieth",
"Tor",
"Toniah Lotnah",
"Toniah",
"Tomorrow",
"Token",
"Today",
"Tithe",
"Timothy",
"Son",
"Brethren",
"Thunder",
"Thummim",
"Three",
"Quarters",
"Thousands",
"Miles",
"Knewest",
"Perilous",
"Year",
"Present",
"Land",
"Northward",
"Holy",
"Spirit",
"Place",
"Ghost",
"Great",
"Council",
"Everlasting",
"Covenant",
"Thirteen",
"Third",
"Last",
"Men",
"Spirits",
"Firstborn",
"River",
"Sea",
"Mountains",
"Anointing",
"Wasatch",
"Front",
"Warm",
"Mob",
"Law",
"Restoration",
"Order",
"Sons",
"Daughters",
"Elohim",
"Heavenly",
"People",
"Life",
"Knowledge",
"Good",
"Evil",
"Thirty",
"Eighth",
"Book",
"Lodge",
"Terrible",
"Terrestrial",
"Temple",
"Lord",
"Eve",
"Bountiful",
"Hill",
"Tamahu-Ah",
"Sweat",
"Sure",
"Sign",
"Nail",
"Summer",
"Spring",
"Levi",
"Father",
"Snake",
"Sixty",
"Ninth",
"Sixth",
"Shortest",
"Seventy",
"Fifth",
"Seventh",
"Sacred",
"Direction",
"Sees",
"Habitation",
"Seeks",
"Second",
"Endowment",
"Season",
"Coast",
"Salten",
"Record",
"Pipe",
"Herbs",
"Hearth",
"Garments",
"Directions",
"Animals",
"Sabbath",
"Robe",
"Priesthood",
"Akish",
"Right",
"Prayer",
"Remnant",
"House",
"Israel",
"Jacob",
"Purification",
"Ammonites",
"Melchizedek",
"Power",
"Mother",
"God",
"Sacrifice",
"Adam",
"Ceremony",
"Gulf",
"Hagoth",
"Corianton",
"Ammon",
"Patriarchal",
"Grip",
"Path",
"Creator",
"Past",
"Ten",
"Orders",
"North",
"Country",
"Ninety",
"Night",
"Next",
"Newborn",
"Children",
"New",
"Nemenha",
"Nem",
"Volume",
"Another",
"Testament",
"Jesus",
"Christ",
"Prophet",
"Living",
"Morning",
"Middle",
"Archives",
"Medicine",
"Dance",
"Man",
"Higher",
"Lord'S",
"Harvest",
"Looks",
"Light",
"Levitical",
"Laying",
"Hands",
"Laws",
"Gospel",
"Revelation",
"Obedience",
"Consecration",
"Chastity",
"Latter-Day",
"Latter",
"Lands",
"Southward",
"Jerusalem",
"Desolation",
"Lake",
"Islands",
"Hour",
"Hosts",
"Host",
"City",
"High",
"Priest",
"Region",
"Guide",
"Star",
"Growing",
"Salt",
"Healer",
"Peace",
"Gift",
"Healing",
"General",
"Nespelem",
"Friends",
"Fourth",
"Four",
"Forty-Two",
"Fortieth",
"Following",
"Sixty-Seventh",
"First",
"Home",
"Pa",
"Firmament",
"Final",
"Festival",
"Lights",
"Fair",
"Ones",
"End",
"Elect",
"Eighty",
"Eastern",
"East",
"Earth",
"Dreadful",
"Coriantumr",
"Savior",
"Mayan",
"Elders",
"Mothers",
"Community",
"Church",
"Certain",
"Common",
"Consent",
"Cold",
"Josh",
"Gad",
"Enoch",
"Saints",
"Chiefs",
"Indians",
"Celestial",
"Glory",
"Canyons",
"Buffalo",
"Breath",
"Brass",
"Plates",
"Bowl",
"Incense",
"Mormon",
"Ether",
"Ancient",
"Quarter",
"Tenth",
"Tens",
"Tempter",
"Temnet",
"Telleth",
"Teemkt",
"Tee",
"Tay",
"Tarramarhah",
"Tan",
"Tamahu",
"Talking",
"Talk",
"Tah",
"Sweetgrass",
"Supposeth",
"Supper",
"Superior",
"Sun",
"Sufficeth",
"Subdue",
"Lucifer",
"Stones",
"Stephat",
"Stars",
"Stakes",
"Spiritual",
"Spine",
"Speaketh",
"Speaker",
"Sovereign",
"South",
"Time",
"Ago",
"Later",
"Date",
"Future",
"Sky",
"Sixteen",
"Age",
"Six",
"Sineth",
"Simeon",
"Sidon",
@ -429,81 +169,50 @@
"Shadowing",
"Sha",
"Sevim",
"Several",
"Hours",
"Seventeen",
"Seven",
"Setteth",
"Set",
"Session",
"Servant",
"Seer",
"Seeketh",
"Seek",
"Seat",
"Sealing",
"Saviors",
"Satan",
"Sanith",
"Saneth",
"San",
"Stretch",
"Spake",
"Preach",
"Answered",
"Samuel",
"Samith",
"Samal",
"Salamander",
"Safety",
"Plants",
"Manner",
"Sacrament",
"Sabel-Nah",
"Sabel",
"Sabbaths",
"Room",
"Robbers",
"Rhen",
"Resurrected",
"Resume",
"Repent",
"Remove",
"Rejoice",
"Redeemer",
"Recall",
"Queens",
"Prophets",
"Moroni",
"Prophecy",
"Promise",
"Priests",
"Priestesses",
"Priestess",
"President",
"Preacher",
"Prayeth",
"Pray",
"Potal",
"Porinor",
"Por",
"Plan",
"Plains",
"Places",
"Pingwit",
"Pharaoh",
"Peter",
"James",
"John",
"Person",
"Perisheth",
"Altar",
"Penith",
"Pel",
"Pee",
"Peacemaker",
"Parim",
"Parents",
"Parah",
"Panith-Het",
"Panith",
@ -517,101 +226,50 @@
"Hem",
"Pagwit",
"Pac-Sineth",
"Pac",
"Ordinance",
"One",
"Forty",
"Half",
"Omega",
"Old",
"Ohmer",
"Oh",
"Observe",
"Nu",
"Notwithstanding",
"Nomiah-Min",
"Nomiah",
"Min",
"Noahs",
"Noah",
"Ninety-Two Years",
"Nine",
"Nin-Shepa",
"Nin",
"Week",
"Name",
"Hills",
"Nespelite",
"Nay",
"Nathah",
"Nemenhah",
"Nemen",
"Neighbor",
"Nature",
"Natural",
"Narrator",
"Myself",
"Chosen",
"Mulekites",
"Mulekite",
"Mulek",
"Muel",
"Moveth",
"Mouth",
"Mouse",
"Mountain",
"Mount",
"Fathers",
"Moses",
"Morrow",
"Morin",
"Moriantum",
"Moon",
"Millions",
"Michael",
"Methuselah",
"Messengers",
"Melek",
"Meet",
"Fall",
"Master",
"Manna",
"Maker",
"Love",
"Lords",
"Look",
"Lone",
"Lodges",
"Lo",
"Lives",
"Limhi",
"Lightning",
"Levites",
"Levite",
"Lay",
"Lamb",
"Lakes",
"Korim",
"Knoweth",
"Know",
"Kinsman",
"Kings",
"Mosiah",
"Keen",
"Judgment",
"Judgeth",
"Judge",
"Judah",
"Joseph",
"Joram",
"Revelator",
"Beloved",
"Jews",
"Himself",
"Jehovah",
"Jaredites",
"Jaredite",
"Jared",
"Jaguar",
"Jacobite",
"Israelite",
@ -621,50 +279,26 @@
"Isaiah",
"Isabel",
"Isaac",
"Instructor",
"Instruction",
"Inner",
"Indigenous",
"Americans",
"Husbands",
"Husband",
"Hundredth",
"Hundreds",
"Hudson",
"Bay",
"Howbeit",
"Hosanna",
"Hopeth",
"Child",
"Holiness",
"Hold",
"Hin",
"Himneth",
"Hez",
"Heth",
"Het",
"Heroes",
"Hero",
"Heirs",
"Hebrew",
"Heavens",
"Beings",
"Heart",
"Hearken",
"Healers",
"Head",
"Hay",
"Harlot",
"Ham",
"Hemen",
"Guides",
"Guardian",
"Groweth",
"Grandmother",
"Grandfathers",
"Grandfather",
"Governor",
"Gods",
"Godliness",
"Godhead",
"Gideon",
@ -672,51 +306,28 @@
"Gentile",
"Gee",
"Gaudy",
"Gate",
"Garden",
"Fourteen",
"Forty-Nine",
"Forgetfulness",
"Minutes",
"Fifty",
"Finisher",
"Findeth",
"Figure",
"Feather",
"Farewell",
"Falleth",
"Faith",
"Exalted",
"Being",
"Exaltation",
"Evening",
"Eternal",
"Ephraim",
"Envieth",
"English",
"Else",
"Eleven",
"Election",
"Eight",
"Egyptus",
"Egyptian",
"Egypt",
"Eden",
"Earlier",
"Eagle",
"Dogwood",
"Doeth",
"Disciples",
"Disciple",
"Discernment",
"Devil",
"Desolate",
"Desireth",
"Depart",
"Deep",
"Deaths",
"Days'",
"Short",
"Aaron",
"Abel",
"Abraham",
@ -724,65 +335,28 @@
"Adamant",
"Adversary",
"Afar",
"Afternoon",
"Alpha",
"America",
"Amulek",
"Appointed",
"Angel",
"Angels",
"Anith",
"Anointed",
"Anti",
"Apostle",
"Arise",
"Arm",
"Little",
"Least",
"Atonement",
"Availeth",
"Babylon",
"Baptism",
"Baptist",
"Begin",
"Begotten",
"Belay",
"Believeth",
"Bend",
"Benjamites",
"Big",
"Blessed",
"Blessing",
"Body",
"Break",
"Brigham",
"Bringeth",
"Meekness",
"Brother",
"Bush",
"Cain",
"Calling",
"Captain",
"Cedar",
"Celebration",
"Celebrations",
"Chapters",
"Chief",
"Churches",
"Cities",
"Laman",
"Claimeth",
"Clan",
"Coasts",
"Come",
"Comforter",
"Condemn",
"Corian",
"Cosmos",
"Counsel",
"County",
"Creation",
"Cried",
"Daily",
"Dead"
"Cosmos"
]

View File

@ -1,11 +1,8 @@
{
"Gadianton Robbers": "Gadeeantun Robbers",
"Gadianton": "Gadeeantun",
"Coriantumr": "Coryantomer",
"Laman": "Layman",
"Lehi And Nephi": "Leehi And Nephi",
"Lehi": "Leehi",
"Lehi Mathonihah": "Leehi Mathonihah",
"Lehis": "Leehis",
"Lehies": "Leehis",
"Liahona": "Leeahona",
@ -14,15 +11,14 @@
"Gadiantons": "Gadeeantuns",
"Laban": "Layban",
"Mosiah": "Moziah",
"Mosiah The King": "Moziah The King",
"Nehors": "Kneehores",
"Samuel The Lamanite": "Samuel The Laymanite",
"Tarry": "Tarery",
"The Lamanite Twins": "The Laymanite Twins",
"The Lamanites Of Ammon": "The Laymanites Of Ammon",
"The Lamanites Of The Land Of Zarahemla": "The Laymanites Of The Land Of Zarahemla",
"The Lamanites Of The Land Southward": "The Laymanites Of The Land Southward",
"The Lamanites Of The People Of Ammon": "The Laymanites Of The People Of Ammon",
"The Lamb'S Book Of Life": "The Lamb's Book Of Life",
"The Land Of Nephi": "The Land Of Kneefi"
"Nephihah": "Kneefihah",
"Nephihet": "Kneefihet",
"Nephite": "Kneefite",
"Nephites": "Kneefites",
"Nephi-Im": "Kneefi-Im",
"Nephitish": "Kneefitish",
"Zenephi": "Zekneefi",
"Nephi": "Kneefi"
}