e9c396df66
- 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
110 lines
4.6 KiB
Python
Executable File
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()
|