diff --git a/web-pages/product-site/content/legacy-manifest.json b/web-pages/product-site/content/legacy-manifest.json index 57eb38c0c..0007f06ea 100644 --- a/web-pages/product-site/content/legacy-manifest.json +++ b/web-pages/product-site/content/legacy-manifest.json @@ -24,9 +24,10 @@ "blog/funclip-v2-1-0-video-clipping-release.html": "88f6c44e5332d1746c4db0fc97d755ef82f12e46d8f1c1c0ad9351152ff9dfc1", "blog/funclip-v2-2-0-moss-speaker-clipping.html": "0d50747d3992301fb3062ad2cdf903bd427bec3db1f1e4fab1f06de207928bc9", "blog/generate-subtitles-srt-vtt-from-audio-video.html": "f1133235673d441cc654310581f59a319f182314ab7c0b0824af87da9f4a0591", - "blog/index.html": "8cbf35e65aff908023ac299ddef94f157cf57235b271d79da59c59f7146d1500", + "blog/index.html": "92af7f61f70b676eb942c889b4501cab5c328a0eb099bad487b9adb9b8d2ef16", "blog/japanese-speech-recognition.html": "399f5ce84e68ac00854bdf70b52b1cde6795efca7c800fd6492035a37a7c1b68", "blog/lightweight-speech-recognition-cpu.html": "d6270066222ed5baef0df108e6a4155f4a87bb5482169f23f218794d371f3281", + "blog/meeting-transcript-acceptance.html": "3e35d1f283c7005977ad8cbd079e38c51e14b51f04f50cedb5636e2d4baf38bd", "blog/punctuation-restoration-python.html": "6cd26e03bf75b4343afd7b351c2681ebd513b058fb549c5888802be9ff3a1229", "blog/self-hosted-deepgram-assemblyai-alternative.html": "13b4287b5ef0d3159bf0af09ece3bc1fea66f13390b13c7f33b8b5e85351a1f5", "blog/self-hosted-google-aws-azure-speech-to-text.html": "8e4000fb9e2b33a48a3907d4bd0f55db57ba74121a89b5fa12b412c3c963f49a", @@ -36,6 +37,7 @@ "blog/speech-to-text-python-transcribe-audio.html": "87335710235dfdec9e01288f03a9a294c7bc3f730110e8d06f2b4c9d4906e284", "blog/speech-to-text-timestamps-python.html": "78364db117dfbce45b03739679764cec7ab1d0a860f321c67154f33392fc6b8d", "blog/subtitle-edit-fun-asr-sensevoice-local-subtitles.html": "f8d5870b6cf9dbdfd4e943fdccb7ae8f091472f1dcbd46bd7db90bd36e3e7846", + "blog/transcript-audit.py": "1b77ee66d8e2ef734277a13689aa225598020c23193391a0a3eff598148f43c2", "blog/voice-activity-detection-python.html": "9055206633caecb83c11031f273c8c9297852f44dfe50984e03ab59a7deda6b5", "blog/which-funasr-model.html": "0109fe2d66f85c3cc917c822671ec0fb137247ee407f6c33459e6f2cadd05b5d", "css/742.48d54c7d.css": "38906de250e9a46352e178d2744831db647ee8dfa8cb6d70cbe68f40e2dfcb5b", @@ -64,9 +66,10 @@ "en/blog/funclip-v2-1-0-video-clipping-release.html": "229baf59adf2c3290541d9b3c8a6243992406ba84b712714e9d14599205cb1d4", "en/blog/funclip-v2-2-0-moss-speaker-clipping.html": "644159267f0fa4e7aab9d0eda98f68cd2b6365a9985038b639c626a5ea5ea7e8", "en/blog/generate-subtitles-srt-vtt-from-audio-video.html": "1530d4b9e94820a0b60d801e8d1c19b7aeb031b2f2b76c092657392e47706c6d", - "en/blog/index.html": "622a53a47a49834e1720fe1cad96ebb4768248324ef41ea528dc1c0c05145e41", + "en/blog/index.html": "d5a5787e0cf9868752e4c94ad4e699f616f6d81b1b9bc9e38332f8690cd2a017", "en/blog/japanese-speech-recognition.html": "c476adcc2be1ed7c19e2345cc91b8ee6a0e04efd794b9b6476dd5d9b3a2c8b04", "en/blog/lightweight-speech-recognition-cpu.html": "0e02c0e0853b12613d32c250f593c05c2052f18231207b63ee01d6e6600a86f7", + "en/blog/meeting-transcript-acceptance.html": "3183682d2cae0ce18f68e3bd33e22b9effb4de699f6a4d821938cc03dd6835bf", "en/blog/punctuation-restoration-python.html": "d6e30049f48d9dc6e6bc22eb567830013e4161eca51215616738b675dd1ffc40", "en/blog/self-hosted-deepgram-assemblyai-alternative.html": "73b2fc101101fd2408ed49fdb2b0995ada59dec0016a91ed49efb856ebb8de53", "en/blog/self-hosted-google-aws-azure-speech-to-text.html": "4a8453451722b3ec8735224a586744ddfbfec9458cedd433f9fc908351027e9a", @@ -110,7 +113,7 @@ "models.html": "01209193cd48363f00789061fbfad0a2b387582e2507ae5e875687773ef52fbc", "quickstart.html": "a543bf07c2cfc9a69ef3831f15c9e9d3e571a826a73cf7e4bdc8e9eb7b910254", "robots.txt": "1305b0f680b3e9f73b01301dc474423230dffab85731a379a694f30c73c07f2d", - "sitemap.xml": "4ea239675f5966ec8bc1408300c6aca472e83974fedd78d48c021e2b21822b7e", + "sitemap.xml": "1fc88be1d337d7fac6acf0dd61e6b3416f6808f5d6e8fc0006fb9b15cba6e039", "static/liveplayer/liveplayer-component.min.js": "ea10fda35574cc3a77a3e7187095467d1a409009a2d39690051d33d0e7055d30", "static/liveplayer/liveplayer-lib.min.js": "ad67b4e1188c586ec218674a6db9968daf71ed4fbc262258f061c1f931d54250", "static/offline/index.html": "868781c621ddabe5d3f088d6b739e0c08e6cdc150587af6515cc323852018e62", diff --git a/web-pages/product-site/legacy/blog/index.html b/web-pages/product-site/legacy/blog/index.html index f41d33975..77ac27e84 100644 --- a/web-pages/product-site/legacy/blog/index.html +++ b/web-pages/product-site/legacy/blog/index.html @@ -49,7 +49,7 @@ footer a{color:#94a3b8}
2026-08-31 · FunClip v2.2.0

MOSS 多说话人转写进入 FunClip:从一句话到带说话人的片段

通过经真实双说话人音频验证的 vLLM 路径,把第三方 MOSS 模型的段级时间戳和说话人标签直接用于 SRT 与说话人片段导出。

查看部署与剪辑边界 →
-
2026-09-04 · 正式 Release

FunASR v1.4.14:可移植源码包、MOSS 入口与十平台运行时

修复 Git archive 与源码安装的可移植性,补齐 MOSS 服务入口,并提供 PyPI 包、十平台运行时与 SHA-256 清单。

2026-08-28 · 正式 Release

FunASR v1.4.5:更轻的 Python 推理与九平台 llama.cpp 运行时

默认安装不再硬依赖 torchaudio,可选 kaldi-native-fbank,并提供 PyPI 包、九平台运行时与 SHA-256 清单。

2026-08-21 · 正式 Release

FunASR v1.4.3:可选 Silero VAD 与大规模固定 K 说话人聚类

新增毫秒级 Silero VAD 适配器,降低已知说话人数的大规模聚类内存压力,并提供 12 个带 SHA-256 的发布资产。

2026-07-31 · 正式 Release

FunASR v1.4.0:更完整的 PyPI 安装包与更安全的 AutoModel 参数

修复 SenseVoice 与 RWKV-BAT 包数据,提前拒绝 vda_model 误拼写,并提供 12 个带 SHA-256 的发布资产。

+
2026-09-09 · 应用工程

HTTP 200 不等于转写成功:会议录音的验收清单

区分文本、匿名说话人、时间戳和尾部覆盖;用经过测试的 Python 检查器开始验收,不把结构合法当作质量合格。

2026-09-04 · 正式 Release

FunASR v1.4.14:可移植源码包、MOSS 入口与十平台运行时

修复 Git archive 与源码安装的可移植性,补齐 MOSS 服务入口,并提供 PyPI 包、十平台运行时与 SHA-256 清单。

2026-08-28 · 正式 Release

FunASR v1.4.5:更轻的 Python 推理与九平台 llama.cpp 运行时

默认安装不再硬依赖 torchaudio,可选 kaldi-native-fbank,并提供 PyPI 包、九平台运行时与 SHA-256 清单。

2026-08-21 · 正式 Release

FunASR v1.4.3:可选 Silero VAD 与大规模固定 K 说话人聚类

新增毫秒级 Silero VAD 适配器,降低已知说话人数的大规模聚类内存压力,并提供 12 个带 SHA-256 的发布资产。

2026-07-31 · 正式 Release

FunASR v1.4.0:更完整的 PyPI 安装包与更安全的 AutoModel 参数

修复 SenseVoice 与 RWKV-BAT 包数据,提前拒绝 vda_model 误拼写,并提供 12 个带 SHA-256 的发布资产。

2026-08-03 · 生态集成

Subtitle Edit 5.2:Fun-ASR Nano / SenseVoice 本地视频字幕

在 Windows、macOS 与 Linux 的成熟字幕工作区中直接选择本地 ASR 引擎,支持 Q4、Q8、F16。

FunASR 技术博客

2026-06-23

语音识别带时间戳(字级 timestamps)Python 实战:每个字精确到毫秒

FunASR Paraformer 原生字级时间戳:每个字带 [起始毫秒,结束毫秒],一次调用即有。可做逐字高亮、点击跳转、字幕对齐。附真实实测+配对代码。

2026-06-23

标点恢复 Python 实战:给无标点文本/ASR 结果自动加标点

FunASR ct-punc 开源标点恢复,中英双语,3 行 Python 补全 ,。?(英文还做句首大写),也能一行挂到 ASR 上。附真实实测。

2026-06-22

中文语音识别(普通话)Python 实战:用 FunASR 又快又准

FunASR 专为中文打造:默认旗舰 Fun-ASR-Nano(CER 8.06%),CPU 用 SenseVoice(7.81%)/ Paraformer(10.18%,时间戳/热词),都远好于 Whisper(~20%)。3 行 Python。

2026-06-22

自托管语音转文字:Google / AWS / Azure 云语音 API 的免费开源替代

开源免费(MIT)、本地推理、不按分钟计费、数据不出内网、中文强,OpenAI 兼容改 base_url 即可迁移。附实测代码 + FunASR vs 云 API 对比 + 成本分析。

2026-06-22

Python 语音活动检测(VAD):检测语音、去静音、按停顿切分音频

FunASR fsmn-vad 3 行代码返回每段语音的起止毫秒。实测 13s 录音 0.12s 切成 2 段、去除 18% 静音;可去静音、切分长录音、给 Whisper 等做预处理防幻觉。

2026-06-22

日语语音识别:SenseVoice 一个模型搞定日语转写+标点+情感

同一段日语音频实测:SenseVoice 写对同音字 転売、自动加标点,Whisper-small 误为 天売、无标点。原生 ja 支持 + 自动语种识别 + 情感事件,3 行 Python。

2026-06-23

自托管替代 Deepgram / AssemblyAI

开源免费、自托管、音频不出本地、中文领先,自带 OpenAI 兼容 API——客户端只改 base_url 即可替代按分钟付费的云 STT。

2026-06-23

选哪个 FunASR 模型?Nano vs MLT-Nano vs SenseVoice vs Paraformer

模型选型表 + 场景代码:中英日及中文方言/口音选旗舰 Nano,31 语种选独立 MLT-Nano,CPU 再选 SenseVoice 或 Paraformer。

2026-06-22

轻量语音识别:CPU 上约 250MB 跑中文 ASR

单二进制 + 254MB q8 模型,无需 GPU/Python,CPU 0.16s,中文 CER 7.99%——比 whisper.cpp small 还小且准 3 倍。

2026-06-21

粤语语音识别:SenseVoice 原生支持粤语口语(Whisper 会转成普通话)

同一段粤语音频实测:SenseVoice 保留 呢/唔/嘅,Whisper 转成普通话书面语。原生 yue 支持 + 自动语种识别,3 行 Python。

2026-06-21

FunASR vs faster-whisper:中文与粤语实测对比

粤语被 faster-whisper 误判为普通话、日语同音字错;SenseVoice 原生支持粤语+语种识别,中文 CER 低约 2.7 倍。实测。

2026-06-20

FunASR 跑进 llama.cpp:中文 ASR 的 whisper.cpp 替代品(CPU/零 Python)

单个自包含二进制、内置 VAD、吃任意音频,下载即用转写中文;中文 CPU 上比 whisper.cpp 准约 2.7 倍。3 步实测。

2026-06-18

Python 语音转文字:用 FunASR 本地免费转写音频

几行 Python 把音频转成文本,带时间戳/说话人/批量;本地、免费、无 API key、中文强。

2026-06-18

用 FunASR 自动生成字幕:音频/视频一键出 SRT 和 VTT

一行命令出 SRT,Python 同时导出 VTT,带说话人和真实时间戳;本地、免费、中文强。

2026-06-18

自托管 OpenAI Whisper API 替代:FunASR 起兼容 /v1/audio/transcriptions 服务

funasr-server 暴露 OpenAI 兼容接口,OpenAI SDK 只改 base_url 就能用;本地、免费、隐私、中文更准。

2026-06-18

用 FunASR 命令行转写音频:文本/JSON/SRT 字幕

一行命令出文字/字幕/JSON,--spk 带说话人;还能 funasr-server 起 OpenAI 兼容 API。

2026-06-17

用 FunASR 转写超长音频:1 小时一次搞定

Whisper 限 30 秒,FunASR 内置 VAD 一次吃下任意时长;实测 13 分钟 4.3 秒转完(186x)。

2026-06-17

用 FunASR 实现实时流式语音识别(边说边出字)

600ms 级低延迟流式 ASR:分块+cache 边说边出字,含 2-pass(流式+离线)最佳实践。

2026-06-17

超越转写:用 SenseVoice 识别语言、情感与声学事件

一次非自回归前向同时输出转写+语种+情感+音频事件,Whisper 做不到的四合一。

2026-06-17

用 FunASR 做说话人分离:谁在何时说了什么

一次 generate 调用同时输出转写+说话人标签+时间戳,替代 pyannote+Whisper,无需 HF 授权。

2026-06-16

FunASR vs Whisper 实测对比:谁更快更准

184 中文文件 H100 实测:SenseVoice 169.6x、CER 7.81%,完整速度+准确率数据。

2026-06-16

Fun-ASR-Nano 使用指南:800M 端到端语音识别大模型

主力旗舰,中英日 + 7 大中文方言/26 种口音,热词/流式/说话人分离;31 语种请用 MLT-Nano。

2026-06-16

SenseVoice 部署指南:五语种识别、情感与音频事件

3 行代码跑通多语言识别,含语种/情感/事件检测、VAD、GPU/CPU。

更多:快速上手 · 模型

diff --git a/web-pages/product-site/legacy/blog/meeting-transcript-acceptance.html b/web-pages/product-site/legacy/blog/meeting-transcript-acceptance.html new file mode 100644 index 000000000..942019194 --- /dev/null +++ b/web-pages/product-site/legacy/blog/meeting-transcript-acceptance.html @@ -0,0 +1,70 @@ + + + +HTTP 200 不等于转写成功:会议录音的验收清单 | FunASR + + + + + + + +
+

HTTP 200 不等于转写成功:会议录音的验收清单

+

2026-09-09 · 应用工程 · 阅读约 8 分钟

+

一次请求返回了文本,离“这份会议记录可以交给同事”还有多远?把 ASR 接进字幕、会议纪要或按说话人剪辑时,需要分别验收内容、说话人、时间和处理完整性。下面用 FunASR 的 MOSS 结果讲清这些层次,并提供一个只依赖 Python 标准库的结构检查脚本。

+
FunClip 的视频、字幕与剪辑工作区
应用出口示意:已有 FunClip 工作区。不是下文合成样例的运行截图。
+

先分清四个问题

+ + + + + +
检查层次要回答的问题不能用什么代替
请求与结构请求完成了吗?字段、单位、顺序是否符合约定?HTTP 200、非空 JSON 不能证明内容正确。
文本名字、数字、否定词和关键决策是否转对?语句流畅不能证明没有漏句或幻觉。
说话人每次轮次和重叠发言分给了谁?输出了两个标签,不等于两位人物分得正确。
时间与覆盖字幕能定位到正确声音吗?开头、中段和结尾有无遗漏?最后一个时间戳接近总时长,不等于全程转写完整。
+

三项常见混淆值得单独拆开。VAD 检测哪些时间段可能有语音;ASR 给出说了什么;说话人分离(diarization) 给匿名说话人划分发言区间。它们不是同一个评测目标。S01 是当前录音内的标签,不是姓名、已注册声纹或跨录音身份。

+

给应用选对输出,而不只是选模型

+

MOSS-Transcribe-Diarize 是 OpenMOSS 维护的第三方模型。FunASR 集成把它的输出整理成文本和带匿名标签的时间段;采用这条路径不需要再外接 VAD 或说话人模型。不要为了“加上 VAD”先把会议任意切成独立请求,然后假定每段的 S01 都是同一个人。

+ +

具体服务版本和安装命令集中维护在 MOSS 部署页及 集成指南。本文不另写一套会随版本漂移的安装流程。FunClip 的能力与限制见 说话人剪辑实践;模型选型从 模型页进入。

+

一个能自动化、但不冒充质量评估的检查器

+

下载 transcript-audit.py,也可以直接阅读源码。它只读文件,不访问网络,不修改转写结果。输入是 FunASR AutoModel.generate(...)[0] 的单个 MOSS 结果对象,或仓库离线示例写出的 .moss-vllm.json;不是原始 vLLM diarized_json、SRT,也不是 Qwen3 示例的 segments 格式。

+

以下是人为构造的 12 秒录音结果,不是真实模型输出,也没有准确率含义:

+
{
+  "text": "Let's start. Agreed.",
+  "sentence_info": [
+    {"start": 1000, "end": 3500, "spk": "S01", "text": "Let's start."},
+    {"start": 6000, "end": 8000, "spk": "S02", "text": "Agreed."}
+  ]
+}
+

把示例 JSON 作为 result.json。检查器使用毫秒,12 秒应传 12000,不是 12:

+
python3 transcript-audit.py result.json --duration-ms 12000
+

输出的 segment_count 是 2,last_end_ms 是 8000,tail_not_covered_ms 是 4000。quality_verified 始终为 false:程序没有听过音频,更没有人工参考答案,因此不能给出质量通过证书。Python 3.12 下的合成样例、非法字段和命令行行为有回归测试;它们验证检查代码,不验证模型。检查器选择拒绝零长度段,这是本文应用验收的额外约束,不表示模型适配器承诺所有时间段都大于零。

+ +

从结构检查走到人工验收

+
    +
  1. 固定输入和配置。保留原音频散列、时长、采样率/声道、模型 revision、服务版本、请求格式和生成上限。记录重采样、混音与裁剪;裁掉静音后必须保存偏移映射,否则时间戳无法回到原视频。
  2. +
  3. 回听高风险位置。开头与结尾、长静音两侧、说话人交替、重叠发言、低音量以及关键数字。抽查能发现问题,但抽查通过不等于整段准确。
  4. +
  5. 把文本与说话人分开评估。有人工参考转写时,可按统一的标点、大小写及分词规则计算 CER/WER;有人工说话人时间标注时,再使用说话人指标。不要把“输出了两个标签”作为 diarization 的通过条件。
  6. +
  7. 冻结说话人评测口径。pyannote.metrics 提供说话人错误率等指标;边界宽容区间(collar)和是否计入重叠语音会影响结果。比较系统时必须使用同一标注与配置,指标数字不能脱离这些前提。
  8. +
  9. 完整性异常先定位,再调参。检查响应是否因生成上限中止、结构是否完整、客户端是否漏读。增大 token 上限只可能缓解某些截断,不保证召回;不能用“接口正常返回”关闭用户的问题。
  10. +
+

上线前留下什么

+

遵循授权范围和保留期限,只保存必要的数据,并限制对录音与说话人标注的访问。一份可复查的交付应包含原音频标识、固定配置、原始结果、结构报告和人工确认记录。会议纪要保留到原文的回链;字幕检查可读性、定位及隐私;剪辑回看实际片段,不能只看时间段长度。涉及已知人物身份时另行设计授权、登记和验证流程,不把匿名聚类标签改名后当作身份识别。

+

这篇文章的检查器是进入验收流程的第一道结构检查,不是模型排行榜,也不会替代回听与标注。下一步可以沿 部署中心跑通实际工作负载,再到 FunASR 仓库查看实现或提交带最小复现的问题。

+

来源与复现入口

+ +
+ diff --git a/web-pages/product-site/legacy/blog/transcript-audit.py b/web-pages/product-site/legacy/blog/transcript-audit.py new file mode 100644 index 000000000..861a84927 --- /dev/null +++ b/web-pages/product-site/legacy/blog/transcript-audit.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +"""Structural audit for one FunASR MOSS result, not a quality evaluator.""" +import argparse +import json +import math +from pathlib import Path + + +def number(value, name): + if isinstance(value, bool) or not isinstance(value, (int, float)): + raise ValueError(f"{name} must be a finite number in milliseconds") + try: + finite = math.isfinite(value) + except OverflowError: + finite = False + if not finite: + raise ValueError(f"{name} must be finite") + return value + + +def audit(payload, duration_ms): + duration = number(duration_ms, "duration_ms") + if duration <= 0: + raise ValueError("duration_ms must be positive") + if not isinstance(payload, dict) or not isinstance(payload.get("sentence_info"), list): + raise ValueError("expected one result object with sentence_info") + segments = payload["sentence_info"] + last_end, previous_start = 0, -1 + speakers, empty, unordered, overlaps = set(), [], [], [] + intervals = [] + for index, segment in enumerate(segments): + if not isinstance(segment, dict): + raise ValueError(f"segment {index} must be an object") + start = number(segment.get("start"), f"segment {index} start") + end = number(segment.get("end"), f"segment {index} end") + if not 0 <= start < end <= duration: + raise ValueError(f"segment {index} must satisfy 0 <= start < end <= duration_ms") + speaker, text = segment.get("spk"), segment.get("text") + if not isinstance(speaker, str) or not speaker.strip() or not isinstance(text, str): + raise ValueError(f"segment {index} needs a nonempty spk label and string text") + speakers.add(speaker) + if not text.strip(): + empty.append(index) + if start < previous_start: + unordered.append(index) + intervals.append((start, end, index)) + previous_start, last_end = start, max(last_end, end) + # Sweep chronologically, but report indices from the unchanged input. + frontier = 0 + for start, end, index in sorted(intervals, key=lambda item: (item[0], item[2])): + if start < frontier: + overlaps.append(index) + frontier = max(frontier, end) + return { + "segment_count": len(segments), + "speaker_labels": sorted(speakers), + "last_end_ms": last_end, + "tail_not_covered_ms": duration - last_end, + "empty_text_indices": empty, + "out_of_order_indices": unordered, + "overlap_indices": sorted(overlaps), + "quality_verified": False, + } + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("result", type=Path) + parser.add_argument("--duration-ms", type=float, required=True) + args = parser.parse_args() + try: + report = audit(json.loads(args.result.read_text(encoding="utf-8")), args.duration_ms) + except (OSError, ValueError) as error: + parser.error(str(error)) + print(json.dumps(report, ensure_ascii=False, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/web-pages/product-site/legacy/en/blog/index.html b/web-pages/product-site/legacy/en/blog/index.html index 0a304c83f..35439db1c 100644 --- a/web-pages/product-site/legacy/en/blog/index.html +++ b/web-pages/product-site/legacy/en/blog/index.html @@ -49,7 +49,7 @@ footer a{color:#94a3b8}
August 31, 2026 · FunClip v2.2.0

MOSS Multi-Speaker Transcription in FunClip: From One Prompt to Speaker Clips

Use the vLLM path validated on real two-speaker audio to turn the third-party MOSS model's segment timestamps and speaker labels into SRT and speaker-specific clip exports.

Read the deployment and clipping boundaries →
-
September 4, 2026 · Stable release

FunASR v1.4.14: Portable Source Archives, MOSS Discovery, and Ten Runtimes

Portable Git archives and source installs, clearer MOSS service discovery, plus PyPI packages, ten runtimes, and SHA-256 checksums.

August 28, 2026 · Stable release

FunASR v1.4.5: Lighter Python Inference and Nine llama.cpp Runtimes

No hard torchaudio dependency in the default install, optional kaldi-native-fbank, plus PyPI packages, nine runtimes, and SHA-256 checksums.

August 21, 2026 · Stable release

FunASR v1.4.3: Optional Silero VAD and Fixed-K Speaker Clustering

Millisecond Silero VAD segments, lower-memory clustering for large known-speaker workloads, and 12 SHA-256-addressed release assets.

July 31, 2026 · Stable release

FunASR v1.4.0: Complete PyPI Packages and Safer AutoModel Arguments

SenseVoice and RWKV-BAT package-data fixes, an early vda_model typo guard, and 12 SHA-256-addressed release assets.

+
2026-09-09 · Application engineering

HTTP 200 is not a usable transcript: a meeting-audio acceptance checklist

Separate words, anonymous speakers, timestamps and tail coverage. Start with a tested Python checker without mistaking structural validity for quality.

September 4, 2026 · Stable release

FunASR v1.4.14: Portable Source Archives, MOSS Discovery, and Ten Runtimes

Portable Git archives and source installs, clearer MOSS service discovery, plus PyPI packages, ten runtimes, and SHA-256 checksums.

August 28, 2026 · Stable release

FunASR v1.4.5: Lighter Python Inference and Nine llama.cpp Runtimes

No hard torchaudio dependency in the default install, optional kaldi-native-fbank, plus PyPI packages, nine runtimes, and SHA-256 checksums.

August 21, 2026 · Stable release

FunASR v1.4.3: Optional Silero VAD and Fixed-K Speaker Clustering

Millisecond Silero VAD segments, lower-memory clustering for large known-speaker workloads, and 12 SHA-256-addressed release assets.

July 31, 2026 · Stable release

FunASR v1.4.0: Complete PyPI Packages and Safer AutoModel Arguments

SenseVoice and RWKV-BAT package-data fixes, an early vda_model typo guard, and 12 SHA-256-addressed release assets.

August 3, 2026 · Ecosystem

Subtitle Edit 5.2: Local Video Subtitles with Fun-ASR Nano or SenseVoice

Select a local ASR engine inside a mature subtitle workspace on Windows, macOS, or Linux, with Q4, Q8, and F16 models.

FunASR Blog

2026-06-23

Speech-to-Text with Word/Character-Level Timestamps in Python

FunASR Paraformer gives native character timestamps: every char has [start_ms, end_ms] in one call. Build word highlighting, click-to-seek, subtitle alignment. Real output + pairing code.

2026-06-23

Punctuation Restoration in Python — Add Punctuation to Text / ASR Output

FunASR ct-punc: open-source bilingual (zh+en) punctuation restoration. 3 lines of Python to add ,。? (and capitalize English), or attach to ASR in one line. Real output.

2026-06-22

Chinese (Mandarin) Speech Recognition in Python — Fast & Accurate with FunASR

Purpose-built for Chinese: default flagship Fun-ASR-Nano (CER 8.06%), with SenseVoice (7.81%) / Paraformer (10.18%, timestamps/hotwords) for CPU — all far better than Whisper (~20%).

2026-06-22

Self-Hosted Speech-to-Text — Free Alternative to Google / AWS / Azure Cloud Speech APIs

Open-source (MIT), local inference, no per-minute billing, audio stays on your network, strong Chinese, OpenAI-compatible (migrate via base_url). Runnable code + FunASR vs cloud comparison + cost analysis.

2026-06-22

Voice Activity Detection in Python — Detect Speech, Remove Silence, Split Audio

FunASR fsmn-vad returns millisecond speech spans in 3 lines. Measured: a 13s clip split into 2 segments in 0.12s, 18% silence removed. Trim silence, split long audio, preprocess Whisper to cut hallucinations.

2026-06-22

Japanese Speech Recognition in Python — SenseVoice: Transcription + Punctuation + Emotion in One Pass

Real side-by-side on one Japanese clip: SenseVoice writes 転売 correctly and adds punctuation; Whisper-small gives 天売 with none. Native ja support + auto language ID + emotion/events, 3 lines of Python.

2026-06-23

Self-Hosted Deepgram / AssemblyAI Alternative

Open-source, free, self-hosted, audio stays local, Chinese-leading, with an OpenAI-compatible API — replace per-minute cloud STT by changing base_url.

2026-06-23

Which FunASR Model? Nano vs MLT-Nano vs SenseVoice vs Paraformer

A decision table with code: flagship Nano for zh/en/ja plus Chinese dialects/accents, separate MLT-Nano for 31 languages, and CPU choices SenseVoice or Paraformer.

2026-06-22

Lightweight Speech Recognition: Chinese ASR in ~250MB on CPU

One binary + a 254MB q8 model, no GPU/Python, 0.16s on CPU, 7.99% CER — smaller than whisper.cpp small and ~3x more accurate.

2026-06-21

Cantonese Speech Recognition in Python — SenseVoice Keeps Real Cantonese (Whisper Doesn't)

Real side-by-side on one Cantonese clip: SenseVoice keeps 呢/唔/嘅, Whisper rewrites to Mandarin. Native yue support + auto language ID, 3 lines of Python.

2026-06-21

FunASR vs faster-whisper: Chinese & Cantonese Compared

faster-whisper mislabels Cantonese as Mandarin + Japanese homophone errors; SenseVoice handles Cantonese natively, ~2.7x lower CER on Chinese.

2026-06-20

FunASR on llama.cpp — a whisper.cpp Alternative for Chinese ASR (CPU, no Python)

One self-contained binary, built-in VAD, any audio — download and transcribe Chinese; ~2.7x more accurate than whisper.cpp on CPU.

2026-06-18

Speech to Text in Python with FunASR

Transcribe audio in a few lines of Python — timestamps, speakers, batching. Local, free, no API key.

2026-06-18

Auto-Generate Subtitles (SRT & VTT) from Audio or Video with FunASR

One command for SRT, Python for VTT too, with speaker labels and real timestamps. Local, free, strong on Chinese.

2026-06-18

Self-Hosted OpenAI Whisper API Alternative with FunASR

funasr-server exposes an OpenAI-compatible /v1/audio/transcriptions; the OpenAI SDK works by changing only base_url. Local, free, private.

2026-06-18

Transcribe Audio from the Command Line with FunASR

One command -> text/SRT/JSON, --spk for speakers; plus funasr-server for an OpenAI-compatible API.

2026-06-17

Transcribe Long Audio with FunASR: Hours in One Call

Whisper caps at 30s; FunASR ingests any length via built-in VAD - 13 min in 4.3s (186x).

2026-06-17

Real-Time Streaming Speech-to-Text with FunASR

~600ms low-latency streaming ASR with chunks + cache, plus the 2-pass (streaming + offline) best practice.

2026-06-17

Beyond Transcription: Language, Emotion & Audio Events with SenseVoice

Transcription + language + emotion + audio events in one pass — the 4-in-1 Whisper cannot do.

2026-06-17

Speaker Diarization with FunASR: Who Spoke When

Transcription + speaker labels + timestamps in one generate() call. A pyannote+Whisper alternative, no HF gated access.

2026-06-16

FunASR vs Whisper: Real Chinese ASR Benchmark

Measured on 184 Chinese files (H100): SenseVoice 169.6x, 7.81% CER — full speed & accuracy data.

2026-06-16

Fun-ASR-Nano Guide: 800M End-to-End ASR LLM

Flagship for zh/en/ja plus 7 Chinese dialect groups and 26 accents; choose MLT-Nano for 31 languages.

2026-06-16

SenseVoice Deployment Guide: Five-Language ASR, Emotion and Audio Events

Multilingual ASR in 3 lines, with language/emotion/event tags, VAD, GPU/CPU.

More: Quickstart · Models

diff --git a/web-pages/product-site/legacy/en/blog/meeting-transcript-acceptance.html b/web-pages/product-site/legacy/en/blog/meeting-transcript-acceptance.html new file mode 100644 index 000000000..6d202db53 --- /dev/null +++ b/web-pages/product-site/legacy/en/blog/meeting-transcript-acceptance.html @@ -0,0 +1,70 @@ + + + +HTTP 200 is not a usable transcript: a meeting-audio acceptance checklist | FunASR + + + + + + + +
+

HTTP 200 is not a usable transcript: a meeting-audio acceptance checklist

+

2026-09-09 · Application engineering · 8 minute read

+

A transcription request returned text. Is it ready for meeting notes, subtitles or speaker-based editing? Treat transport, words, speakers and timing as separate acceptance questions. This article uses a FunASR MOSS result and a small standard-library Python checker to show what can be automated, and what still needs listening.

+
FunClip video, subtitle and editing workspace
An existing FunClip workspace illustrates the downstream application. This is not a screenshot of the synthetic example below.
+

Four questions, four kinds of evidence

+ + + + + +
LayerQuestionNot a substitute
Request and structureDid the request complete with the expected fields, units and ordering?HTTP 200 and nonempty JSON do not establish correctness.
WordsAre names, numbers, negations and decisions transcribed correctly?Fluent text can still omit or invent speech.
SpeakersAre turns and overlapping speech assigned consistently?Two output labels do not prove correct separation of two people.
Timing and coverageDo subtitles locate the right sound? Are the beginning, middle and end covered?An end timestamp near the recording duration does not prove complete transcription.
+

VAD detects regions that may contain speech; ASR transcribes words; speaker diarization assigns anonymous speakers to speech regions. They answer different evaluation questions. A label such as S01 is local to a recording, not a name, an enrolled voiceprint or a cross-recording identity.

+

Choose the output contract for the application

+

MOSS-Transcribe-Diarize is a third-party model maintained by OpenMOSS. FunASR normalizes its output into text and timed segments with anonymous speaker labels. This path does not require an additional external VAD or speaker model. Do not arbitrarily split a meeting into independent requests and then assume that every chunk's S01 identifies the same person.

+ +

Version-specific installation belongs in the maintained MOSS deployment page and integration guide, not a second recipe that drifts independently. See the speaker-clipping walkthrough for FunClip boundaries and the model catalogue for selection.

+

A structural checker, not a quality evaluator

+

Download transcript-audit.py or read its source. It reads one file, makes no network requests and does not modify results. The input is one MOSS result object from FunASR AutoModel.generate(...)[0], also written as .moss-vllm.json by the repository's offline example. It is not raw vLLM diarized_json, SRT or the Qwen3 example's segments schema.

+

This deliberately constructed result represents a hypothetical 12-second recording. It is not model output and carries no accuracy claim:

+
{
+  "text": "Let's start. Agreed.",
+  "sentence_info": [
+    {"start": 1000, "end": 3500, "spk": "S01", "text": "Let's start."},
+    {"start": 6000, "end": 8000, "spk": "S02", "text": "Agreed."}
+  ]
+}
+

Use the example JSON as result.json. Units are milliseconds: pass 12000 for 12 seconds, not 12.

+
python3 transcript-audit.py result.json --duration-ms 12000
+

The report has segment_count=2, last_end_ms=8000 and tail_not_covered_ms=4000. It always returns quality_verified=false: it has neither heard the audio nor received human reference annotations. Synthetic cases, invalid fields and CLI behavior are regression-tested on Python 3.12. Those tests validate the checker, not the model. Rejecting zero-length segments is an additional acceptance policy here, not a guarantee that the model adapter always emits positive-length intervals.

+ +

Move from structural checks to acceptance

+
    +
  1. Freeze input and configuration. Retain the audio hash, duration, sample rate/channels, model revision, server version, response format and generation limit. Record resampling, channel mixing and trimming. Removing silence requires an offset map if timestamps must locate the original video.
  2. +
  3. Listen at high-risk positions. Check the start and end, both sides of long silence, speaker changes, interruptions, quiet speech and critical numbers. Sampling can reveal problems; a successful sample is not proof of whole-recording accuracy.
  4. +
  5. Evaluate text and speakers separately. With a human reference transcript, measure CER/WER under consistent punctuation, case and tokenization rules. With human speaker/time annotations, evaluate diarization. The number of distinct output labels is not a diarization acceptance test.
  6. +
  7. Freeze the diarization scoring policy. pyannote.metrics provides speaker metrics. Boundary collars and whether overlapping speech is scored affect results. Compare systems on the same reference and configuration, not detached numbers.
  8. +
  9. Diagnose incomplete output before tuning. Inspect termination due to generation limits, structural completeness and whether the client consumed the whole response. Raising a token limit may address some truncations, but cannot guarantee recall. A successful request is not grounds to close an unresolved user issue.
  10. +
+

What should travel with the result?

+

Follow consent and retention requirements, store only necessary data, and restrict access to audio and speaker annotations. Retain the source-audio identifier, fixed configuration, original output, structural report and human acceptance notes. Link summaries back to transcript passages. Check subtitle readability, positioning and privacy. Replay edited clips instead of only checking interval lengths. Known-person identification needs a separate consent, enrollment and verification design; renaming anonymous cluster labels does not provide it.

+

This checker is an entry point into acceptance, not a leaderboard or a substitute for listening and annotation. Start a real workload through the deployment centre, then use the FunASR repository for implementation details or a minimal reproducible issue.

+

Sources and reproduction

+ +
+ diff --git a/web-pages/product-site/legacy/sitemap.xml b/web-pages/product-site/legacy/sitemap.xml index 2eb1d2afa..533bfc1b4 100644 --- a/web-pages/product-site/legacy/sitemap.xml +++ b/web-pages/product-site/legacy/sitemap.xml @@ -74,4 +74,6 @@ https://www.funasr.com/en/blog/punctuation-restoration-python.html0.8monthly https://www.funasr.com/blog/speech-to-text-timestamps-python.html0.8monthly https://www.funasr.com/en/blog/speech-to-text-timestamps-python.html0.8monthly + https://www.funasr.com/blog/meeting-transcript-acceptance.html2026-09-09 + https://www.funasr.com/en/blog/meeting-transcript-acceptance.html2026-09-09 diff --git a/web-pages/product-site/tests/browser/product-site.spec.ts b/web-pages/product-site/tests/browser/product-site.spec.ts index 6025424c9..ceac84609 100644 --- a/web-pages/product-site/tests/browser/product-site.spec.ts +++ b/web-pages/product-site/tests/browser/product-site.spec.ts @@ -436,7 +436,16 @@ for (const viewport of [ page.locator(`.launch-feature a[href="${release.article}"]`), ).toBeVisible(); const history = page.locator('.previous-release .post-card'); - await expect(history).toHaveCount(4); + const historySlugs = [ + 'meeting-transcript-acceptance.html', + 'funasr-v1-4-14-portable-source-release.html', + 'funasr-v1-4-5-pypi-llama-cpp-release.html', + 'funasr-v1-4-3-pypi-release.html', + 'funasr-v1-4-0-pypi-release.html', + ]; + await expect(history).toHaveCount(historySlugs.length); + expect(await history.evaluateAll(cards => cards.map(card => card.getAttribute('href')))) + .toEqual(historySlugs.map(slug => `${release.index}${slug}`)); await expect( page.locator(`.previous-release a[href="${release.previous}"]`), ).toBeVisible(); @@ -445,7 +454,7 @@ for (const viewport of [ rows: new Set(cards.map((card) => Math.round(card.getBoundingClientRect().top))).size, })); expect(indexLayout.overflow).toBeLessThanOrEqual(1); - expect(indexLayout.rows).toBe(viewport.name === 'mobile' ? 4 : 1); + expect(indexLayout.rows).toBe(Math.ceil(historySlugs.length / (viewport.name === 'mobile' ? 1 : 4))); await page.goto(release.article); await expect(page.locator('h1')).toContainText('FunClip v2.2.0'); diff --git a/web-pages/product-site/tests/test_transcript_acceptance_blog.py b/web-pages/product-site/tests/test_transcript_acceptance_blog.py new file mode 100644 index 000000000..66a096b87 --- /dev/null +++ b/web-pages/product-site/tests/test_transcript_acceptance_blog.py @@ -0,0 +1,117 @@ +"""Exercise the downloadable article example, not an untested prose copy.""" +import json +import runpy +import subprocess +import sys +from pathlib import Path + +import pytest +from bs4 import BeautifulSoup + +SITE = Path(__file__).resolve().parents[1] +SCRIPT = SITE / "legacy/blog/transcript-audit.py" +SLUG = "meeting-transcript-acceptance.html" + + +@pytest.fixture +def audit(): + return runpy.run_path(str(SCRIPT))["audit"] + + +def segment(start=0, end=1000, spk="S01", text="hello"): + return dict(start=start, end=end, spk=spk, text=text) + + +def test_reports_tail_without_claiming_missing_speech_or_quality(audit): + report = audit({"sentence_info": [segment(end=469200)]}, 480240) + assert report["tail_not_covered_ms"] == 11040 + assert report["quality_verified"] is False + assert report["speaker_labels"] == ["S01"] + + +def test_overlap_and_cross_recording_labels_are_not_identity_proof(audit): + data = [segment(0, 2000), segment(1000, 3000, "S02"), segment(4000, 5000)] + result = audit({"sentence_info": data}, 5000) + assert result["tail_not_covered_ms"] == 0 + assert result["overlap_indices"] == [1] + assert result["quality_verified"] is False + + +def test_empty_and_unordered_segments_are_visible(audit): + assert audit({"sentence_info": []}, 1000)["segment_count"] == 0 + result = audit({"sentence_info": [segment(500, 800), segment(0, 200, text=" ")]}, 1000) + assert result["out_of_order_indices"] == [1] + assert result["empty_text_indices"] == [1] + assert result["last_end_ms"] == 800 + assert result["overlap_indices"] == [] + + +def test_unordered_overlaps_preserve_original_indices(audit): + data = [segment(1000, 3000), segment(0, 2000, "S02")] + assert audit({"sentence_info": data}, 4000)["overlap_indices"] == [0] + + +def test_touching_boundaries_are_not_overlap(audit): + data = [segment(1000, 2000), segment(0, 1000)] + assert audit({"sentence_info": data}, 2000)["overlap_indices"] == [] + + +@pytest.mark.parametrize("bad", [None, [], {}, {"sentence_info": None}, + {"sentence_info": [None]}, {"sentence_info": [{}]}]) +def test_rejects_missing_schema(audit, bad): + with pytest.raises(ValueError): + audit(bad, 1000) + + +@pytest.mark.parametrize("duration", [True, 0, -1, "1000", float("nan"), float("inf"), 10**400]) +def test_rejects_invalid_duration(audit, duration): + with pytest.raises(ValueError): + audit({"sentence_info": []}, duration) + + +@pytest.mark.parametrize("changes", [ + {"start": -1}, {"end": 0}, {"end": 1001}, {"start": True}, + {"end": float("inf")}, {"end": float("nan")}, {"start": "0"}, + {"spk": ""}, {"spk": 1}, {"text": None}, +]) +def test_rejects_bad_segments(audit, changes): + with pytest.raises(ValueError): + audit({"sentence_info": [segment(**changes)]}, 1000) + + +def test_cli_success_and_failure_do_not_modify_source(tmp_path): + path = tmp_path / "result.json" + original = json.dumps({"sentence_info": [segment()]}).encode() + path.write_bytes(original) + command = [sys.executable, str(SCRIPT), str(path), "--duration-ms", "1200"] + good = subprocess.run(command, capture_output=True, text=True) + assert good.returncode == 0, good.stderr + assert json.loads(good.stdout)["tail_not_covered_ms"] == 200 + bad = subprocess.run(command[:-1] + ["nan"], capture_output=True, text=True) + assert bad.returncode != 0 + assert not bad.stdout + assert path.read_bytes() == original + + +def test_articles_index_sources_example_and_asset_are_consistent(audit): + snippets = [] + for language in ["zh", "en"]: + prefix = "" if language == "zh" else "en/" + page = SITE / "legacy" / prefix / "blog" / SLUG + soup = BeautifulSoup(page.read_text(), "html.parser") + metadata = json.loads(soup.select_one('script[type="application/ld+json"]').string) + assert metadata["datePublished"] == "2026-09-09" + assert metadata["dateModified"] == "2026-09-09" + assert soup.select_one("h1") + assert soup.select_one('a[href="/blog/transcript-audit.py"]') + assert soup.select_one('a[href*="OpenMOSS/MOSS-Transcribe-Diarize"]') + assert soup.select_one('a[href*="pyannote/pyannote-metrics"]') + assert soup.select_one('a[href*="/deploy/moss-transcribe-diarize.html"]') + example = json.loads(soup.select_one('[data-example="audit-input"]').get_text()) + result = audit(example, 12000) + assert result["tail_not_covered_ms"] == 4000 + assert result["quality_verified"] is False + snippets.append(example) + index = BeautifulSoup((SITE / "legacy" / prefix / "blog/index.html").read_text(), "html.parser") + assert index.select_one(f'a[href="/{prefix}blog/{SLUG}"]') + assert snippets[0] == snippets[1]