ForcePilot/backend/package/yuxi/channel/extensions/bilibili/streaming.py

75 lines
2.4 KiB
Python
Raw Normal View History

from __future__ import annotations
import asyncio
import time
class BilibiliBlockChunker:
def __init__(
self,
outbound,
target_id: str,
anti_risk,
chunk_min_chars: int = 200,
chunk_max_chars: int = 1200,
coalesce_min_chars: int = 80,
coalesce_max_chars: int = 400,
coalesce_idle_ms: int = 500,
):
self._outbound = outbound
self._target_id = target_id
self._anti_risk = anti_risk
self._chunk_min = chunk_min_chars
self._chunk_max = chunk_max_chars
self._coalesce_min = coalesce_min_chars
self._coalesce_max = coalesce_max_chars
self._coalesce_idle = coalesce_idle_ms / 1000.0
self._buffer = ""
self._last_flush = time.time()
async def append(self, text: str) -> None:
self._buffer += text
should_flush = len(self._buffer) >= self._chunk_max or (
len(self._buffer) >= self._chunk_min and time.time() - self._last_flush > self._coalesce_idle
)
if should_flush:
await self._flush()
async def finalize(self) -> None:
if self._buffer:
await self._flush()
async def _flush(self) -> None:
chunks = self._split_chunks(self._buffer, self._chunk_max)
for chunk in chunks:
await self._outbound.send_text(to=self._target_id, text=chunk)
await asyncio.sleep(0.5)
self._buffer = ""
self._last_flush = time.time()
def _split_chunks(self, text: str, limit: int) -> list[str]:
if len(text) <= limit:
return [text] if text else []
chunks = []
remaining = text
while len(remaining) > limit:
window = remaining[:limit]
last_para = window.rfind("\n\n")
if last_para > limit * 0.5:
chunks.append(window[:last_para].strip())
remaining = remaining[last_para + 2 :]
continue
for sep in ["\n", "", "", "", "\n", " "]:
idx = window.rfind(sep)
if idx > limit * 0.3:
chunks.append(window[: idx + len(sep)].strip())
remaining = remaining[idx + len(sep) :]
break
else:
chunks.append(window.strip())
remaining = remaining[limit:]
if remaining.strip():
chunks.append(remaining.strip())
return chunks