diff --git a/runtime/binding/python/CMakeLists.txt b/runtime/binding/python/CMakeLists.txt new file mode 100644 index 000000000..fdd7b5a80 --- /dev/null +++ b/runtime/binding/python/CMakeLists.txt @@ -0,0 +1,57 @@ +cmake_minimum_required(VERSION 3.14 FATAL_ERROR) + +project(wenet VERSION 0.1) + +option(TORCH "whether to build with Torch" ON) +option(ONNX "whether to build with ONNX" OFF) +option(ITN "whether to use WeTextProcessing" ON) + +set(CXX11_ABI OFF) +set(FST_HAVE_BIN OFF) +set(CMAKE_VERBOSE_MAKEFILE OFF) + +include(FetchContent) +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_EXTENSIONS OFF) +set(FETCHCONTENT_QUIET OFF) +get_filename_component(fc_base "fc_base" REALPATH BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") +set(FETCHCONTENT_BASE_DIR ${fc_base}) + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) + +if(NOT MSVC) + # Keep the same with openfst, -fPIC or -fpic + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -pthread -fPIC") +else() + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) + add_compile_options("$<$:/utf-8>") +endif() + +if(TORCH) + include(libtorch) +endif() +if(ONNX) + include(onnx) +endif() +include(openfst) +include(pybind11) + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/kaldi +) + +if(ITN) + include(wetextprocessing) +endif() + +add_subdirectory(utils) +add_subdirectory(frontend) +add_subdirectory(post_processor) +add_subdirectory(kaldi) # kaldi: wfst based decoder +add_subdirectory(decoder) +add_subdirectory(api) + +# wenet api +pybind11_add_module(_wenet cpp/binding.cc) +target_link_libraries(_wenet PRIVATE wenet_api) diff --git a/runtime/binding/python/MANIFEST.in b/runtime/binding/python/MANIFEST.in new file mode 100644 index 000000000..941f67e28 --- /dev/null +++ b/runtime/binding/python/MANIFEST.in @@ -0,0 +1,2 @@ +include README.md +recursive-include py *.* diff --git a/runtime/binding/python/README.md b/runtime/binding/python/README.md new file mode 100644 index 000000000..238d54f1f --- /dev/null +++ b/runtime/binding/python/README.md @@ -0,0 +1,114 @@ +# Python Binding + +This is a python binding of WeNet. + +WeNet is a production first and production ready end-to-end speech recognition toolkit. + +The best things of the binding are: + +1. Multiple languages supports, including English, Chinese. Other languages are in development. +2. Non-streaming and streaming API +3. N-best, contextual biasing, and timestamp supports, which are very important for speech productions. +4. Alignment support. You can get phone level alignments this tool, on developing. + +## Install + +Python 3.6+ is required. + +``` sh +pip3 install wenetruntime +``` + +## Command-line usage + +``` +wenetruntime audio.wav +``` + +We support mainstream audio formats, such as wav, mp3, flac and so on. + +You can specify the language using the `--language` option, +currently we support `en` (English) and `chs` (中文). + +## Programming usage + +Note: + +1. For macOS, wenetruntime packed `libtorch.so`, so we can't import torch and wenetruntime at the same time. +2. For Windows and Linux, wenetruntime depends on torch. Please install and import the same version `torch` as wenetruntime. + +### Non-streaming Usage + +``` python +import sys +import wenetruntime as wenet + +audio_file = sys.argv[1] # support wav, mp3, flac, etc +decoder = wenet.Decoder(lang='chs') +ans = decoder.decode(audio_file) +print(ans) +``` + +You can also specify the following parameter in `wenet.Decoder` + +* `lang` (str): The language you used, `chs` for Chinese, and `en` for English. +* `model_dir` (str): is the `Runtime Model` directory, it contains the following files. + If not provided, official model for specific `lang` will be downloaded automatically. + + * `final.zip`: runtime TorchScript ASR model. + * `units.txt`: modeling units file + * `TLG.fst`: optional, it means decoding with LM when `TLG.fst` is given. + * `words.txt`: optional, word level symbol table for decoding with `TLG.fst` + + Please refer https://github.com/wenet-e2e/wenet/blob/main/docs/pretrained_models.md for the details of `Runtime Model`. + +* `nbest` (int): Output the top-n best result. +* `enable_timestamp` (bool): Whether to enable the word level timestamp. +* `context` (List[str]): a list of context biasing words. +* `context_score` (float): context bonus score. +* `continuous_decoding` (bool): Whether to enable continuous(long) decoding. + +For example: +``` python +decoder = wenet.Decoder(model_dir, + lang='chs', + nbest=5, + enable_timestamp=True, + context=['不忘初心', '牢记使命'], + context_score=3.0) +``` + +### Streaming Usage + +``` python +import sys +import wave +import wenetruntime as wenet + +test_wav = sys.argv[1] + +with wave.open(test_wav, 'rb') as fin: + assert fin.getnchannels() == 1 + wav = fin.readframes(fin.getnframes()) + +decoder = wenet.Decoder(lang='chs', streaming=True) +# We suppose the wav is 16k, 16bits, and decode every 0.5 seconds +interval = int(0.5 * 16000) * 2 +for i in range(0, len(wav), interval): + last = False if i + interval < len(wav) else True + chunk_wav = wav[i: min(i + interval, len(wav))] + ans = decoder.decode(chunk_wav, last) + print(ans) +``` + +You can use the same parameters as we introduced above to control the behavior of `wenet.Decoder` + + +## Build on Your Local Machine + +``` sh +git clone https://github.com/wenet-e2e/wenet.git +cd wenet/runtime/binding/python +python setup.py install +``` + diff --git a/runtime/binding/python/api b/runtime/binding/python/api new file mode 120000 index 000000000..933c69f35 --- /dev/null +++ b/runtime/binding/python/api @@ -0,0 +1 @@ +../../core/api \ No newline at end of file diff --git a/runtime/binding/python/cmake b/runtime/binding/python/cmake new file mode 120000 index 000000000..d8d87a6d1 --- /dev/null +++ b/runtime/binding/python/cmake @@ -0,0 +1 @@ +../../core/cmake \ No newline at end of file diff --git a/runtime/binding/python/cpp/binding.cc b/runtime/binding/python/cpp/binding.cc new file mode 100644 index 000000000..e3dbb3e02 --- /dev/null +++ b/runtime/binding/python/cpp/binding.cc @@ -0,0 +1,40 @@ +// Copyright (c) 2022 Binbin Zhang(binbzha@qq.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "api/wenet_api.h" + +namespace py = pybind11; + +PYBIND11_MODULE(_wenet, m) { + m.doc() = "wenet pybind11 plugin"; // optional module docstring + m.def("wenet_init", &wenet_init, py::return_value_policy::reference, + "wenet init"); + m.def("wenet_free", &wenet_free, "wenet free"); + m.def("wenet_reset", &wenet_reset, "wenet reset"); + m.def("wenet_decode", &wenet_decode, py::return_value_policy::copy, + "wenet decode"); + m.def("wenet_set_log_level", &wenet_set_log_level, "set log level"); + m.def("wenet_set_nbest", &wenet_set_nbest, "set nbest"); + m.def("wenet_set_timestamp", &wenet_set_timestamp, "set timestamp flag"); + m.def("wenet_add_context", &wenet_add_context, "add one context word"); + m.def("wenet_set_context_score", &wenet_set_context_score, + "set context bonus score"); + m.def("wenet_set_language", &wenet_set_language, "set language"); + m.def("wenet_set_continuous_decoding", &wenet_set_continuous_decoding, + "enable continuous decoding or not"); + m.def("wenet_set_chunk_size", &wenet_set_chunk_size, + "set decoding chunk size"); +} diff --git a/runtime/binding/python/decoder b/runtime/binding/python/decoder new file mode 120000 index 000000000..db11f3741 --- /dev/null +++ b/runtime/binding/python/decoder @@ -0,0 +1 @@ +../../core/decoder \ No newline at end of file diff --git a/runtime/binding/python/frontend b/runtime/binding/python/frontend new file mode 120000 index 000000000..51809c84a --- /dev/null +++ b/runtime/binding/python/frontend @@ -0,0 +1 @@ +../../core/frontend \ No newline at end of file diff --git a/runtime/binding/python/kaldi b/runtime/binding/python/kaldi new file mode 120000 index 000000000..f2322aa0e --- /dev/null +++ b/runtime/binding/python/kaldi @@ -0,0 +1 @@ +../../core/kaldi \ No newline at end of file diff --git a/runtime/binding/python/patch b/runtime/binding/python/patch new file mode 120000 index 000000000..2b73806cf --- /dev/null +++ b/runtime/binding/python/patch @@ -0,0 +1 @@ +../../core/patch \ No newline at end of file diff --git a/runtime/binding/python/post_processor b/runtime/binding/python/post_processor new file mode 120000 index 000000000..d0d891f2a --- /dev/null +++ b/runtime/binding/python/post_processor @@ -0,0 +1 @@ +../../core/post_processor \ No newline at end of file diff --git a/runtime/binding/python/setup.py b/runtime/binding/python/setup.py new file mode 100644 index 000000000..604eb23f2 --- /dev/null +++ b/runtime/binding/python/setup.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020 Xiaomi Corporation (author: Fangjun Kuang) +# 2022 Binbin Zhang(binbzha@qq.com) + +import glob +import os +import shutil +import sys + +import setuptools +from setuptools.command.build_ext import build_ext + + +def cmake_extension(name, *args, **kwargs) -> setuptools.Extension: + kwargs["language"] = "c++" + sources = [] + return setuptools.Extension(name, sources, *args, **kwargs) + + +class BuildExtension(build_ext): + + def build_extension(self, ext: setuptools.extension.Extension): + os.makedirs(self.build_temp, exist_ok=True) + os.makedirs(self.build_lib, exist_ok=True) + + cmake_args = os.environ.get("WENET_CMAKE_ARGS", + "-DCMAKE_BUILD_TYPE=Release") + if "PYTHON_EXECUTABLE" not in cmake_args: + print(f"Setting PYTHON_EXECUTABLE to {sys.executable}") + cmake_args += f" -DPYTHON_EXECUTABLE={sys.executable}" + + src_dir = os.path.dirname(os.path.abspath(__file__)) + os.system(f"cmake {cmake_args} -B {self.build_temp} -S {src_dir}") + ret = os.system(f""" + cmake --build {self.build_temp} --target _wenet --config Release + """) + if ret != 0: + raise Exception( + "\nBuild wenet failed. Please check the error message.\n" + "You can ask for help by creating an issue on GitHub.\n" + "\nClick:\n https://github.com/wenet-e2e/wenet/issues/new\n" + ) + + libs = [] + for ext in ['so', 'pyd']: + libs.extend( + glob.glob(f"{self.build_temp}/**/_wenet*.{ext}", + recursive=True)) + for ext in ['so', 'dylib', 'dll']: + libs.extend( + glob.glob(f"{self.build_temp}/**/*wenet_api.{ext}", + recursive=True)) + + for lib in libs: + print(f"Copying {lib} to {self.build_lib}/") + shutil.copy(f"{lib}", f"{self.build_lib}/") + + +def read_long_description(): + with open("README.md", encoding="utf8") as f: + readme = f.read() + return readme + + +setuptools.setup( + name="wenetruntime", + version='1.14.0', + author="Binbin Zhang", + author_email="binbzha@qq.com", + packages=setuptools.find_packages(), + url="https://github.com/wenet-e2e/wenet", + long_description=read_long_description(), + long_description_content_type="text/markdown", + ext_modules=[cmake_extension("_wenet")], + cmdclass={"build_ext": BuildExtension}, + zip_safe=False, + setup_requires=["tqdm"], + install_requires=["torch>=1.10.0", "librosa", "tqdm"] if "ONNX=ON" + not in os.environ.get("WENET_CMAKE_ARGS", "") else ["librosa", "tqdm"], + entry_points={ + "console_scripts": [ + "wenetruntime = wenetruntime.main:main", + ] + }, + classifiers=[ + "Programming Language :: C++", + "Programming Language :: Python :: 3", + "Operating System :: OS Independent", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + ], + license="Apache licensed, as found in the LICENSE file", +) diff --git a/runtime/binding/python/utils b/runtime/binding/python/utils new file mode 120000 index 000000000..a7940cdc5 --- /dev/null +++ b/runtime/binding/python/utils @@ -0,0 +1 @@ +../../core/utils \ No newline at end of file diff --git a/runtime/binding/python/wenetruntime/__init__.py b/runtime/binding/python/wenetruntime/__init__.py new file mode 100644 index 000000000..58cd2aef8 --- /dev/null +++ b/runtime/binding/python/wenetruntime/__init__.py @@ -0,0 +1,2 @@ +from .decoder import Decoder # noqa +from _wenet import wenet_set_log_level as set_log_level # noqa diff --git a/runtime/binding/python/wenetruntime/__main__.py b/runtime/binding/python/wenetruntime/__main__.py new file mode 100644 index 000000000..2ffe93737 --- /dev/null +++ b/runtime/binding/python/wenetruntime/__main__.py @@ -0,0 +1,4 @@ +from wenetruntime.main import main + +if __name__ == "__main__": + main() diff --git a/runtime/binding/python/wenetruntime/decoder.py b/runtime/binding/python/wenetruntime/decoder.py new file mode 100644 index 000000000..20c1f7ca0 --- /dev/null +++ b/runtime/binding/python/wenetruntime/decoder.py @@ -0,0 +1,132 @@ +# Copyright (c) 2022 Binbin Zhang(binbzha@qq.com) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +from typing import List, Optional, Union + +import librosa +import numpy as np +# import torch to avoid libtorch.so not found error +import torch # noqa + +import _wenet + +from wenetruntime.hub import Hub + + +class Decoder: + + def __init__(self, + model_dir: Optional[str] = None, + lang: str = 'chs', + nbest: int = 1, + enable_timestamp: bool = False, + context: Optional[List[str]] = None, + context_score: float = 3.0, + continuous_decoding: bool = False, + streaming: bool = False): + """ Init WeNet decoder + Args: + lang: language type of the model + nbest: nbest number for the final result + enable_timestamp: whether to enable word level timestamp + for the final result + context: context words + context_score: bonus score when the context is matched + continuous_decoding: enable countinous decoding or not + streaming: streaming mode + """ + if model_dir is None: + model_dir = Hub.get_model_by_lang(lang) + + self.d = _wenet.wenet_init(model_dir) + + self.set_language(lang) + self.set_nbest(nbest) + self.enable_timestamp(enable_timestamp) + if context is not None: + self.add_context(context) + self.set_context_score(context_score) + self.set_continuous_decoding(continuous_decoding) + chunk_size = 16 if streaming else -1 + self.set_chunk_size(chunk_size) + + def __del__(self): + _wenet.wenet_free(self.d) + + def reset(self): + """ Reset status for next decoding """ + _wenet.wenet_reset(self.d) + + def set_nbest(self, n: int): + assert n >= 1 + assert n <= 10 + _wenet.wenet_set_nbest(self.d, n) + + def enable_timestamp(self, flag: bool): + tag = 1 if flag else 0 + _wenet.wenet_set_timestamp(self.d, tag) + + def add_context(self, contexts: List[str]): + for c in contexts: + assert isinstance(c, str) + _wenet.wenet_add_context(self.d, c) + + def set_context_score(self, score: float): + _wenet.wenet_set_context_score(self.d, score) + + def set_language(self, lang: str): + assert lang in ['chs', 'en'] + _wenet.wenet_set_language(self.d, lang) + + def set_continuous_decoding(self, continuous_decoding: bool): + flag = 1 if continuous_decoding else 0 + _wenet.wenet_set_continuous_decoding(self.d, flag) + + def set_chunk_size(self, chunk_size: int): + _wenet.wenet_set_chunk_size(self.d, chunk_size) + + def decode(self, + audio: Union[str, bytes, np.ndarray], + last: bool = True) -> str: + """ Decode the input audio + + Args: + audio: string, bytes, or np.ndarray + last: if it is the last package of the data, only for streaming + """ + if isinstance(audio, str): + data, _ = librosa.load(audio, sr=16000) + data = data * (1 << 15) + data = data.astype(np.int16).tobytes() + finish = 1 + elif isinstance(audio, np.ndarray): + finish = 1 if last else 0 + if audio.max() < 1: # the audio is normalized + data = data * (1 << 15) + data = data.astype(np.int16).tobytes() + elif isinstance(audio, bytes): + finish = 1 if last else 0 + data = audio + else: + print('Unsupport audio type {}'.format(type(audio))) + sys.exit(-1) + result = _wenet.wenet_decode(self.d, data, len(data), finish) + if last: # Reset status for next decoding automatically + self.reset() + return result + + def decode_wav(self, wav_file: str) -> str: + """ Deprecated, will remove soon """ + return self.decode(wav_file) diff --git a/runtime/binding/python/wenetruntime/hub.py b/runtime/binding/python/wenetruntime/hub.py new file mode 100644 index 000000000..c973968d5 --- /dev/null +++ b/runtime/binding/python/wenetruntime/hub.py @@ -0,0 +1,103 @@ +# Copyright (c) 2022 Mddct(hamddct@gmail.com) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import tarfile +from pathlib import Path +from urllib.request import urlretrieve + +import tqdm + + +def download(url: str, dest: str, only_child=True): + """ download from url to dest + """ + assert os.path.exists(dest) + print('Downloading {} to {}'.format(url, dest)) + + def progress_hook(t): + last_b = [0] + + def update_to(b=1, bsize=1, tsize=None): + if tsize not in (None, -1): + t.total = tsize + displayed = t.update((b - last_b[0]) * bsize) + last_b[0] = b + return displayed + return update_to + + # *.tar.gz + name = url.split("/")[-1] + tar_path = os.path.join(dest, name) + with tqdm.tqdm(unit='B', + unit_scale=True, + unit_divisor=1024, + miniters=1, + desc=(name)) as t: + urlretrieve(url, + filename=tar_path, + reporthook=progress_hook(t), + data=None) + t.total = t.n + + with tarfile.open(tar_path) as f: + if not only_child: + f.extractall(dest) + else: + for tarinfo in f: + if "/" not in tarinfo.name: + continue + name = os.path.basename(tarinfo.name) + fileobj = f.extractfile(tarinfo) + with open(os.path.join(dest, name), "wb") as writer: + writer.write(fileobj.read()) + + +class Hub(object): + """Hub for wenet pretrain runtime model + """ + # TODO(Mddct): make assets class to support other language + Assets = { + # wenetspeech + "chs": + "https://github.com/wenet-e2e/wenet/releases/download/v2.0.1/chs.tar.gz", + # gigaspeech + "en": + "https://github.com/wenet-e2e/wenet/releases/download/v2.0.1/en.tar.gz" + } + + def __init__(self) -> None: + pass + + @staticmethod + def get_model_by_lang(lang: str) -> str: + assert lang in Hub.Assets.keys() + # NOTE(Mddct): model_dir structure + # Path.Home()/.went + # - chs + # - units.txt + # - final.zip + # - en + # - units.txt + # - final.zip + model_url = Hub.Assets[lang] + model_dir = os.path.join(Path.home(), ".wenet", lang) + if not os.path.exists(model_dir): + os.makedirs(model_dir) + # TODO(Mddct): model metadata + if set(["final.zip", + "units.txt"]).issubset(set(os.listdir(model_dir))): + return model_dir + download(model_url, model_dir, only_child=True) + return model_dir diff --git a/runtime/binding/python/wenetruntime/main.py b/runtime/binding/python/wenetruntime/main.py new file mode 100644 index 000000000..5c49d149b --- /dev/null +++ b/runtime/binding/python/wenetruntime/main.py @@ -0,0 +1,52 @@ +# Copyright (c) 2023 Binbin Zhang(binbzha@qq.com) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse + +from wenetruntime.decoder import Decoder +from _wenet import wenet_set_log_level as set_log_level # noqa + + +def get_args(): + parser = argparse.ArgumentParser(description='wenet') + parser.add_argument('--language', + default='chs', + choices=['chs', 'en'], + help='select language') + parser.add_argument('-c', + '--chunk_size', + default=-1, + type=int, + help='set decoding chunk size') + parser.add_argument('-v', + '--verbose', + default=0, + type=int, + help='set log(glog backend) level') + parser.add_argument('audio', help='input audio file') + args = parser.parse_args() + return args + + +def main(): + args = get_args() + set_log_level(args.verbose) + decoder = Decoder(lang=args.language) + decoder.set_chunk_size(args.chunk_size) + result = decoder.decode(args.audio) + print(result) + + +if __name__ == '__main__': + main() diff --git a/runtime/web/README.md b/runtime/web/README.md index 1c15c43e8..c48c7617e 100644 --- a/runtime/web/README.md +++ b/runtime/web/README.md @@ -3,3 +3,6 @@ * How to install? `pip install -r requirements.txt` * How to start? - Non-streaming: `python app.py` + - Streaming: `python streaming_app.py` +* Doc for `wenetruntime`? please see [doc](../../runtime/binding/python/README.md) for more usage. + diff --git a/runtime/web/streaming_app.py b/runtime/web/streaming_app.py new file mode 100644 index 000000000..71b0e8943 --- /dev/null +++ b/runtime/web/streaming_app.py @@ -0,0 +1,39 @@ +import json +import gradio as gr +import numpy as np +import torch +import wenetruntime as wenet + +torch.manual_seed(777) # for lint + +wenet.set_log_level(2) +decoder = wenet.Decoder(lang='chs') + +def recognition(audio): + sr, y = audio + assert sr in [48000, 16000] + if sr == 48000: # Optional resample to 16000 + y = (y / max(np.max(y), 1) * 32767)[::3].astype("int16") + ans = decoder.decode(y.tobytes(), False) + if ans == "": + return ans + ans = json.loads(ans) + text = ans["nbest"][0]["sentence"] + return text + +print("\n===> Loading the ASR model ...") +print("===> Warming up by 100 randomly-generated audios ... Please wait ...\n") +for i in range(100): + audio_len = np.random.randint(16000 * 3, 16000 * 10) # 3~10s + audio = np.random.randint(-32768, 32768, size=audio_len, dtype=np.int16) + ans = decoder.decode(audio.tobytes(), True) + print("Processed the {}-th audio.".format(i + 1)) + +with gr.Blocks() as demo: + gr.Markdown("Streaming Speech Recognition in WeNet | 基于 WeNet 的流式语音识别") + with gr.Row(): + inputs = gr.Microphone(streaming=True) + outputs = gr.Textbox(label="Output:") + inputs.stream(fn=recognition, inputs=inputs, outputs=outputs, + show_progress="hidden") +demo.launch()