| """调用 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__) |
|
|
| |
| _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}") |
| |
| 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 |
|
|