Files
Adolfo Reyna e9c396df66 feat(BibleVerse): immersive offline bible app - ESP32 color screen
- Single verse fullscreen, auto-advances every minute, tap toggles chrome
- Offline WEB bible split per-book: books.json + 66 bin/idx pairs (~4.1MB)
  - bin = NUL-terminated UTF-8 concatenation, idx = {offset,cnum,vnum}
  - RAM tiny: loads one book at a time, biggest Psalms ~215KB vs 3.8MB monolith
- Adaptive text scaling by length: <40 ultra-large (Jesus wept), <100 large,
  100-200 default, 200-350 small, 350+ compact (Esther 8:9 492c)
- Persistence via user_data_path: progress.txt + favorites.txt (like BookPlayer)
- LVGL fixes: use lvgl_get_text_font(FONT_SIZE_*) - direct montserrat not exported,
  no gradients (bg_grad_color not exported), LV_OPA_95 -> OPA_COVER, format-truncation werror silenced
- Deploys to 192.168.68.112 via PUT /api/apps/install (dashboard port 80, dev 6666 closed)

Tested: build esp32s3 local-sdk 0 missing symbols, 4.2MB .app, screenshot Gen 2:10

Refs: tools/convert_bible.py regenerates assets from WEB source
2026-07-11 17:08:22 -04:00

110 lines
4.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Convert sajeevavahini Zefania XML bible -> per-book .bin/.idx format for BibleVerse app.
Usage:
python tools/convert_bible.py --src "World English Bible.xml" --dst assets/bible
# or auto-download:
python tools/convert_bible.py --download WEB --dst assets/bible
"""
import argparse, os, sys, json, struct, pathlib, urllib.request, xml.etree.ElementTree as ET
# mapping for --download shortcut to raw urls
DL_MAP = {
"WEB": "https://raw.githubusercontent.com/sajeevavahini/bibles/main/World%20English%20Bible.xml",
"KJV": "https://raw.githubusercontent.com/sajeevavahini/bibles/main/King%20James%20Version.xml",
"ASV": "https://raw.githubusercontent.com/sajeevavahini/bibles/main/American%20Standard%20Version%20(1901).xml",
}
def download(url, dst_path):
print(f"Downloading {url} -> {dst_path}")
urllib.request.urlretrieve(url, dst_path)
print(f" done {os.path.getsize(dst_path)} bytes")
def parse_and_write(src_xml, dst_dir):
os.makedirs(dst_dir, exist_ok=True)
print(f"Parsing {src_xml} ...")
books_data = {} # bnum -> {bname, chapters: {cnum: [(vnum,text)]}}
cur_bnum = 0; cur_bname=""; cur_cnum=0
# iterparse for low memory
ctx = ET.iterparse(src_xml, events=("start","end"))
for event, elem in ctx:
if event=="start":
if elem.tag=="BIBLEBOOK":
cur_bnum=int(elem.attrib.get("bnumber","0"))
cur_bname=elem.attrib.get("bname","").strip()
if cur_bnum not in books_data:
books_data[cur_bnum]={"bname":cur_bname, "chapters":{}}
elif elem.tag=="CHAPTER":
cur_cnum=int(elem.attrib.get("cnumber","0"))
if cur_bnum in books_data:
books_data[cur_bnum]["chapters"].setdefault(cur_cnum, [])
else:
if elem.tag=="VERS":
vnum=int(elem.attrib.get("vnumber","0"))
text=(elem.text or "").strip()
if text and cur_bnum in books_data:
books_data[cur_bnum]["chapters"].setdefault(cur_cnum, []).append((vnum,text))
if elem.tag in ("VERS","CHAPTER","BIBLEBOOK"):
elem.clear()
print(f"Found {len(books_data)} books")
book_list=[]
total=0
for bnum in sorted(books_data.keys()):
binfo=books_data[bnum]
bname=binfo["bname"]
chapters=binfo["chapters"]
entries=[]
for cnum in sorted(chapters.keys()):
for vnum, txt in sorted(chapters[cnum], key=lambda x: x[0]):
entries.append((cnum,vnum,txt))
total+=len(entries)
bin_path=os.path.join(dst_dir, f"{bnum:02d}.bin")
idx_path=os.path.join(dst_dir, f"{bnum:02d}.idx")
with open(bin_path,"wb") as bf, open(idx_path,"wb") as ix:
ix.write(struct.pack("<4sHI", b'BIBK', 1, len(entries)))
off=0
for cnum,vnum,txt in entries:
data=txt.encode('utf-8')
bf.write(data+b'\x00')
ix.write(struct.pack("<IHH", off, cnum, vnum))
off+=len(data)+1
book_list.append({"bnumber":bnum,"bname":bname,"verses":len(entries),"chapters":len(chapters)})
print(f" {bnum:02d} {bname:20s} {len(chapters)} ch {len(entries)} vs")
manifest={"books":book_list,"total_verses":total,"version":1,"translation":os.path.basename(src_xml)}
with open(os.path.join(dst_dir,"books.json"),"w") as f:
json.dump(manifest,f,indent=2)
print(f"Total {total} verses, wrote to {dst_dir}")
def main():
ap=argparse.ArgumentParser()
ap.add_argument("--src", help="Source XML file")
ap.add_argument("--dst", default="assets/bible", help="Destination folder")
ap.add_argument("--download", choices=list(DL_MAP.keys()), help="Download preset: WEB, KJV, ASV")
args=ap.parse_args()
src=args.src
if args.download:
# download to temp then use
os.makedirs(os.path.dirname(args.dst) or ".", exist_ok=True)
tmp=os.path.join(args.dst, "_tmp_download.xml") if os.path.isdir(args.dst) else args.dst+"_tmp_download.xml"
# ensure parent exists
pathlib.Path(args.dst).mkdir(parents=True, exist_ok=True)
tmp_path=os.path.join(args.dst, f"_{args.download}.xml")
if not os.path.exists(tmp_path) or os.path.getsize(tmp_path)<100000:
download(DL_MAP[args.download], tmp_path)
src=tmp_path
if not src:
print("Need --src or --download")
sys.exit(1)
if not os.path.exists(src):
print(f"Source not found: {src}")
sys.exit(1)
parse_and_write(src, args.dst)
if __name__=="__main__":
main()