Files
my_bookmate/backend/app/epub/chunker.py
T

97 lines
2.5 KiB
Python

import re
def chunk_paragraphs(
paragraphs: list[dict],
heading_path: str,
cfi_base: str,
target_chars: int = 1500,
max_chars: int = 4000,
min_chars: int = 200,
) -> list[dict]:
"""将段落列表按语义边界聚类,不按固定字符数切。
策略:
- 章节总字数 < target_chars: 整章一块
- otherwise: 按段落聚类,目标 target_chars/块,允许±50%浮动
"""
total_chars = sum(len(p["text"]) for p in paragraphs)
if total_chars <= target_chars:
merged_text = "\n\n".join(p["text"] for p in paragraphs)
prefix = f"《heading_path》" if heading_path else ""
return [{
"text": f"{prefix}\n{merged_text}" if prefix else merged_text,
"char_count": len(merged_text),
"cfi": cfi_base,
"heading_path": heading_path,
}]
chunks = []
buf = []
buf_chars = 0
def flush():
nonlocal buf_chars
if not buf:
return
merged = "\n\n".join(p["text"] for p in buf)
prefix = f"《heading_path》" if heading_path else ""
text = f"{prefix}\n{merged}" if prefix else merged
chunks.append({
"text": text,
"char_count": len(merged),
"cfi": cfi_base,
"heading_path": heading_path,
})
buf.clear()
buf_chars = 0
for i, p in enumerate(paragraphs):
p_chars = len(p["text"])
if p["tag"] in ("h1", "h2", "h3", "h4"):
flush()
prefix = f"《heading_path》" if heading_path else ""
text = f"{prefix}\n{p['text']}" if prefix else p["text"]
chunks.append({
"text": text,
"char_count": p_chars,
"cfi": cfi_base,
"heading_path": heading_path,
})
continue
if buf_chars + p_chars > target_chars * 1.5:
flush()
buf.append(p)
buf_chars += p_chars
flush()
for i in range(len(chunks)):
chunks[i]["cfi"] = f"{cfi_base}!chunk-{i:04d}"
return chunks
def chapter_to_chunks(
paragraphs: list[dict],
heading_path: str,
cfi_start: str,
book_char_total: int,
) -> list[dict]:
"""根据书的总规模决定切块策略"""
if book_char_total < 50_000:
target = 2000
elif book_char_total < 500_000:
target = 1500
else:
target = 1000
return chunk_paragraphs(
paragraphs,
heading_path=heading_path,
cfi_base=cfi_start,
target_chars=target,
)