"""调用 Node.js 子进程运行 afp.js 生成音频指纹。 通过 stdin/stdout 通信,避免 Node 进程频繁启动开销(每次仍会重启 wasm, 但实测 93ms 足够快)。 """ from __future__ import annotations import asyncio import base64 import logging import os from typing import Optional logger = logging.getLogger(__name__) # afp_runner.js 路径(相对于本文件) _RUNNER_PATH = os.path.join(os.path.dirname(__file__), "afp", "afp_runner.js") async def generate_fp(pcm_s16le: bytes) -> str: """生成音频指纹。 Args: pcm_s16le: 8kHz 单声道 PCM s16le 字节 Returns: base64 编码的指纹字符串 """ b64_input = base64.b64encode(pcm_s16le).decode("ascii") logger.info("[fp] spawn node, input b64=%d chars", len(b64_input)) proc = await asyncio.create_subprocess_exec( "node", _RUNNER_PATH, stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) stdout, stderr = await proc.communicate(input=b64_input.encode("ascii")) if proc.returncode != 0: err = stderr.decode("utf-8", errors="replace")[-500:] raise RuntimeError(f"afp_runner exit {proc.returncode}: {err}") # 把 stderr 日志输出到 logger if stderr: for line in stderr.decode("utf-8", errors="replace").splitlines(): logger.info("[fp] %s", line) fp = stdout.decode("ascii").strip() logger.info("[fp] generated, len=%d", len(fp)) return fp