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
This commit is contained in:
Executable
+109
@@ -0,0 +1,109 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user