野生流动开发者文档
语音识别调用(ASR)

双引擎语音识别

上传 FLAC 音频,同时获得 VibeVoice 与 faster-whisper 的转写文本和段级时间戳。

wildflow/dual-asr-v1 是野生流动的通用语音识别模型。一次任务运行两个固定引擎,并返回一个可下载、 可校验的 JSON Artifact:

  • microsoft/VibeVoice-ASR 负责段级正文、说话人和非语音标签;
  • Systran/faster-whisper-large-v3 提供另一份转写与段级时间戳。

本地电脑不需要安装模型或 NVIDIA 环境,只需要 ffmpeg、curl、python3 和自己的野生流动 API Key。 该接口只提供识别结果,不包含字幕排版、剪辑方案、OCR 或视频渲染。

只需要一个引擎时,可使用Whisper 与 VibeVoice 独立入口。原双引擎模型名、请求与价格不变。

账号、权限与计费

  • 已注册账号可以在控制台创建普通 API Key;
  • 普通 API Key 不需要管理员单独授权;调用方主动配置的模型范围仍然生效;
  • 模型会出现在模型广场和鉴权目录 GET /v1/models 中,接口类型为 wildflow-jobs;
  • 目录价格为 ¥0.05 / 音频分钟,提交时按 120 分钟上限预授权,成功后按实际时长结算并退回差额;
  • 平台并发槽位为 8,超出执行容量的任务排队;这不代表所有提交都会同时推理。实时可用状态以模型广场为准,不承诺实时流式识别或对外 SLA。

批量提交时,每条任务仍预授权 6 元,账户余额需要覆盖待处理任务的预授权总额。新 ASR 任务的排队与 执行期限为 14 天,这不是完成时延承诺。保留每条任务的 ID 和幂等键,等待时继续轮询原任务,不要换键重复提交。

快速调用

以下命令适用于 macOS / Linux,需要 Python 3 和 cURL。每次新任务生成新的幂等键,重试同一任务时保留原键。

1. 准备音频

export WILDFLOW_API_KEY="替换为控制台生成的密钥"
export INPUT_MEDIA="/绝对路径/音视频文件.mp4"

ffmpeg -hide_banner -y \
  -i "$INPUT_MEDIA" \
  -vn -ac 1 -ar 16000 -sample_fmt s16 -c:a flac \
  input.flac

export INPUT_SHA256="$(shasum -a 256 input.flac | awk '{print $1}')"

接口只接受 audio/flac;其他媒体类型会返回 input must be audio/flac。单个文件硬上限为 256 MiB, 音频时长硬上限为 120 分钟(60 分钟音频在范围内)。文件大小和时长必须同时满足, 256 MiB 等于 268,435,456 字节。推荐转换为 16 kHz、单声道、16-bit FLAC; 60 分钟同规格未压缩 PCM 约 110 MiB,120 分钟约 220 MiB,FLAC 大小随内容变化。 高采样率、多声道文件可能先触及大小上限,上传前应检查实际字节数。 建议先用几分钟的代表性内容确认术语和时间戳质量。

超过文件上限返回 HTTP 413。需要分段时可按约 10 分钟一段、相邻重叠 5–10 秒处理; 合并时自行叠加每段实际起点并去除重叠文字,复核跨段说话人编号。

2. 上传输入 Artifact

curl --fail-with-body --silent --show-error \
  -X POST https://api.wildflow.cn/v1/input-artifacts \
  -H "Authorization: Bearer $WILDFLOW_API_KEY" \
  -H "Content-Type: audio/flac" \
  -H "X-WildFlow-Content-SHA256: $INPUT_SHA256" \
  --data-binary @input.flac \
  --output input-artifact.json

export INPUT_ARTIFACT_ID="$(python3 -c \
  'import json; print(json.load(open("input-artifact.json"))["artifact"]["id"])')"

上传成功返回 HTTP 201。响应里的 size_bytes 和 sha256 应与本地文件一致。

3. 提交 ASR Job

每段真正的新内容使用一个稳定且唯一的幂等键:

export WILDFLOW_IDEMPOTENCY_KEY="$(python3 -c 'import uuid; print(uuid.uuid4())')"

python3 - "$INPUT_ARTIFACT_ID" > asr-request.json <<'PY'
import json
import sys

print(json.dumps({
    "model": "wildflow/dual-asr-v1",
    "input_artifact_ids": [sys.argv[1]],
    "parameters": {
        "language": "zh",
        "context": "行业访谈与产品讨论",
        "hotwords": ["在这里填写品牌名", "在这里填写专业术语"]
    }
}, ensure_ascii=False))
PY

curl --fail-with-body --silent --show-error \
  -D asr-response-headers.txt \
  -X POST https://api.wildflow.cn/v1/jobs \
  -H "Authorization: Bearer $WILDFLOW_API_KEY" \
  -H "Idempotency-Key: $WILDFLOW_IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  --data-binary @asr-request.json \
  --output operation.json

首次接收成功返回 HTTP 202。网络中断时使用完全相同的请求正文和幂等键重放,不要换新键重复提交 同一段音频。

4. 轮询并下载结果

export RESULT_FILE="dual-asr-result.json"
export RESULT_MEDIA_TYPE="application/json"

下面的命令使用 Python 3,读取上一步保存的 operation.json,等待任务完成后下载结果。等待超过两小时会停止;再次执行本段会继续查询原任务,不会重新提交。

python3 - <<'PY'
import hashlib
import json
import os
import time
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime
from pathlib import Path
from urllib.error import HTTPError
from urllib.parse import quote
from urllib.request import Request, urlopen

base = "https://api.wildflow.cn"
headers = {"Authorization": "Bearer " + os.environ["WILDFLOW_API_KEY"]}
operation = json.loads(Path("operation.json").read_text())
operation_id = operation["id"]
status_url = base + "/v1/jobs/" + quote(operation_id, safe="")
output = Path(os.environ["RESULT_FILE"])
expected_media_type = os.environ["RESULT_MEDIA_TYPE"]
deadline = time.monotonic() + 7200

def pause(value):
    try:
        seconds = float(value)
    except (TypeError, ValueError):
        try:
            seconds = (parsedate_to_datetime(value) - datetime.now(timezone.utc)).total_seconds()
        except (TypeError, ValueError, OverflowError):
            seconds = 3
    seconds = max(1, seconds)
    if time.monotonic() + seconds >= deadline:
        raise SystemExit("等待超时。保留 operation.json,稍后重新执行本段继续查询。")
    time.sleep(seconds)

while time.monotonic() < deadline:
    try:
        with urlopen(Request(status_url, headers=headers), timeout=60) as response:
            status = json.load(response)
            retry_after = response.headers.get("Retry-After", "3")
    except HTTPError as error:
        if error.code in (429, 503):
            pause(error.headers.get("Retry-After", "3"))
            continue
        raise SystemExit(f"查询失败:HTTP {error.code}。保留原任务,检查错误后继续查询。")

    state = status["state"]
    print("任务状态:", state, flush=True)
    if state == "succeeded":
        artifacts = status.get("artifacts", [])
        if not artifacts:
            raise SystemExit("任务成功但缺少结果文件,请保留任务 ID 并联系支持。")
        artifact = artifacts[0]
        if artifact["media_type"] != expected_media_type:
            raise SystemExit("结果格式与预期不同,请检查任务和模型。")
        download_url = base + "/v1/artifacts/" + quote(artifact["id"], safe="") + "/content"
        temporary = output.with_name(output.name + ".part")
        digest = hashlib.sha256()
        size = 0
        with urlopen(Request(download_url, headers=headers), timeout=60) as response:
            with temporary.open("wb") as file:
                while chunk := response.read(1024 * 1024):
                    file.write(chunk)
                    digest.update(chunk)
                    size += len(chunk)
        if size != artifact["size_bytes"] or digest.hexdigest() != artifact["sha256"]:
            raise SystemExit("文件校验失败,保留原任务并重新执行本段下载。")
        temporary.replace(output)
        print("已保存:", output)
        break
    if state not in ("queued", "running", "submitting"):
        raise SystemExit(f"任务已停止:{state}。请保留任务 ID;不要换幂等键重复提交。")
    pause(retry_after)
else:
    raise SystemExit("等待超时。保留 operation.json,稍后继续查询原任务。")
PY

网络中断时可重新执行本段。failed、cancelled、recovery_required 或未知状态会停止下载;需要帮助时查看任务恢复说明。

下载后可运行 python3 -m json.tool dual-asr-result.json 查看 JSON。

参数

参数必需边界说明
language否1–16 个字符默认 zh,当前仅传给 Whisper;VibeVoice 自动识别
context否最多 20,000 个字符兼容接收;当前 HTTP 路径未传给引擎,不承诺增强效果
hotwords否最多 200 个;每个 1–100 字符兼容接收;当前 HTTP 路径未传给引擎,不承诺热词增强
source_offset_seconds否0–7200记录在 timeline;消费方自行叠加偏移,不自动改写时间戳

每个 Job 必须恰好提供一个 input_artifact_ids。不要传服务器路径、下载 URL、启动命令或未经转换的 媒体文件。

结果结构

{
  "schema_version": 1,
  "source_artifact_id": "输入 Artifact ID",
  "timeline": {"source_offset_seconds": 0},
  "engines": {
    "segment_text": {
      "model_id": "microsoft/VibeVoice-ASR",
      "model_revision": "固定 revision",
      "segments": []
    },
    "word_timestamps": {
      "model_id": "Systran/faster-whisper-large-v3",
      "model_revision": "固定 revision",
      "segments": []
    }
  }
}

从结果的 engines 对象中,读取 segment_text.segments 和 word_timestamps.segments 获取两份转写及段级时间戳。 word_timestamps 是历史角色名;当前生产响应未提供 segments[].words,不承诺逐词时间戳。两种结果 职责不同,不要用其中一份直接覆盖另一份,也不要仅凭识别结果自动删除原始内容。

当前限制

  • ASR 可能出现错字、漏词、音乐段幻觉或时间戳偏差,事实、数字、专有名词和实际处理点必须人工复核;
  • 120 分钟是输入硬上限,不是处理时延承诺;
  • 超过 10 分钟的音频会分段推理后合并时间轴;分段边界附近的文字、时间戳和说话人连续性需要人工复核;
  • 模型暂不可用时目录会显示 callable=false,此时不要继续创建新任务;
  • 上传内容和结果属于耐久 Artifact。不要上传无权处理的录音或不必要的敏感信息。

统一的幂等、状态、恢复和 Artifact 规则见任务与结果文件。

相关文档

获取 API Key · 价格与计费 · 常见问题