From 34d5532119fcfee5e98096e244bb4b8dc4682cfd Mon Sep 17 00:00:00 2001 From: viccy Date: Sun, 7 Jun 2026 17:58:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(subtitle):=20=E6=96=B0=E5=A2=9E=20FireRedA?= =?UTF-8?q?SR2=20=E6=9C=AC=E5=9C=B0=20ASR=20=E5=90=8E=E7=AB=AF=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加 FireRedASR2 本地 ASR 转写后端的完整支持: 1. 新增配置参数与数据模型字段 2. 更新示例配置文件,添加默认本地服务地址 3. 完善任务服务中的转写逻辑,支持 FireRedASR 后端 4. 更新 WebUI 界面,新增对应配置选项 5. 补充中英文多语言翻译 6. 新增本地 FireRedASR 服务的单元测试 --- app/models/schema.py | 1 + app/services/fun_asr_subtitle.py | 168 +++++++++++++++++- app/services/task.py | 15 +- .../test_fun_asr_subtitle_unittest.py | 86 +++++++++ config.example.toml | 3 +- webui/components/script_settings.py | 40 ++++- webui/components/subtitle_settings.py | 24 ++- webui/i18n/en.json | 11 +- webui/i18n/zh.json | 11 +- 9 files changed, 331 insertions(+), 28 deletions(-) diff --git a/app/models/schema.py b/app/models/schema.py index e0547e7..b492bb1 100644 --- a/app/models/schema.py +++ b/app/models/schema.py @@ -201,6 +201,7 @@ class VideoClipParams(BaseModel): subtitle_auto_transcribe_enabled: bool = False subtitle_auto_transcribe_backend: str = "local" subtitle_auto_transcribe_api_url: str = "" + subtitle_auto_transcribe_firered_api_url: str = "" subtitle_auto_transcribe_api_key: str = "" subtitle_auto_transcribe_hotword: str = "" subtitle_auto_transcribe_enable_spk: bool = False diff --git a/app/services/fun_asr_subtitle.py b/app/services/fun_asr_subtitle.py index 2567bc8..f6042d4 100644 --- a/app/services/fun_asr_subtitle.py +++ b/app/services/fun_asr_subtitle.py @@ -24,6 +24,7 @@ TRANSCRIPTION_URL = f"{DASHSCOPE_BASE_URL}/api/v1/services/audio/asr/transcripti TASK_URL_TEMPLATE = f"{DASHSCOPE_BASE_URL}/api/v1/tasks/{{task_id}}" MODEL_NAME = "fun-asr" LOCAL_FUN_ASR_API_URL = "http://127.0.0.1:7860" +LOCAL_FIRERED_ASR_API_URL = "http://127.0.0.1:7867" TERMINAL_FAILED_STATUSES = {"FAILED", "CANCELED", "UNKNOWN"} PUNCTUATION_BREAKS = set(",。!?;,.!?;") @@ -131,7 +132,11 @@ def _absolute_local_download_url(api_url: str, download_url: str) -> str: return urljoin(f"{_local_base_url(api_url)}/", download_url) -def _raise_for_local_http(response: requests.Response, action: str) -> None: +def _raise_for_local_http( + response: requests.Response, + action: str, + service_name: str = "本地 FunASR-Pack 服务", +) -> None: status_code = getattr(response, "status_code", 200) if status_code and status_code >= 400: detail = "" @@ -142,16 +147,20 @@ def _raise_for_local_http(response: requests.Response, action: str) -> None: except Exception: detail = "" suffix = f": {detail}" if detail else "" - raise FunAsrError(f"{action}失败{suffix},请确认本地 FunASR-Pack 服务可用") + raise FunAsrError(f"{action}失败{suffix},请确认{service_name}可用") try: response.raise_for_status() except Exception as exc: - raise FunAsrError(f"{action}失败,请确认本地 FunASR-Pack 服务可用") from exc + raise FunAsrError(f"{action}失败,请确认{service_name}可用") from exc -def _local_json(response: requests.Response, action: str) -> dict[str, Any]: - _raise_for_local_http(response, action) +def _local_json( + response: requests.Response, + action: str, + service_name: str = "本地 FunASR-Pack 服务", +) -> dict[str, Any]: + _raise_for_local_http(response, action, service_name=service_name) try: data = response.json() except Exception as exc: @@ -520,6 +529,19 @@ def request_local_fun_asr_health(api_url: str = LOCAL_FUN_ASR_API_URL, session=r return _local_json(response, "检查本地 FunASR-Pack 服务") +def request_local_firered_asr_health( + api_url: str = LOCAL_FIRERED_ASR_API_URL, + session=requests, +) -> dict[str, Any]: + """Fetch FireRedASR2-AED-Pack health metadata from the local service.""" + response = _session_get(session, f"{_local_base_url(api_url)}/health", timeout=10) + return _local_json( + response, + "检查本地 FireRedASR2-AED-Pack 服务", + service_name="本地 FireRedASR2-AED-Pack 服务", + ) + + def request_local_fun_asr( local_file: str, api_url: str = LOCAL_FUN_ASR_API_URL, @@ -548,21 +570,61 @@ def request_local_fun_asr( return _local_json(response, "调用本地 FunASR-Pack ASR API") +def request_local_firered_asr( + local_file: str, + api_url: str = LOCAL_FIRERED_ASR_API_URL, + enable_vad: Optional[bool] = True, + enable_lid: Optional[bool] = True, + enable_punc: Optional[bool] = True, + return_timestamp: Optional[bool] = True, + timeout: float = 600.0, + session=requests, +) -> dict[str, Any]: + """Call the local FireRedASR2-AED-Pack `/asr` API and return its JSON result.""" + _require_local_file(local_file) + data: dict[str, str] = {} + options = { + "enable_vad": enable_vad, + "enable_lid": enable_lid, + "enable_punc": enable_punc, + "return_timestamp": return_timestamp, + } + for key, value in options.items(): + if value is not None: + data[key] = "true" if value else "false" + + with open(local_file, "rb") as file_obj: + files = {"file": (_safe_upload_name(local_file), file_obj)} + response = _session_post( + session, + _local_asr_url(api_url), + data=data, + files=files, + timeout=timeout, + ) + return _local_json( + response, + "调用本地 FireRedASR2-AED-Pack ASR API", + service_name="本地 FireRedASR2-AED-Pack 服务", + ) + + def download_local_srt( download_url: str, api_url: str = LOCAL_FUN_ASR_API_URL, subtitle_file: str = "", session=requests, + service_name: str = "本地 FunASR-Pack 服务", ) -> str: """Download an SRT exposed by FunASR-Pack and save it as a NarratoAI subtitle.""" absolute_url = _absolute_local_download_url(api_url, download_url) if not absolute_url: raise FunAsrError("本地 FunASR-Pack 结果缺少 SRT 下载地址") response = _session_get(session, absolute_url, timeout=60) - _raise_for_local_http(response, "下载本地 FunASR-Pack SRT") + _raise_for_local_http(response, "下载本地 SRT", service_name=service_name) srt_content = _response_text(response) if not srt_content.strip(): - raise FunAsrError("本地 FunASR-Pack 返回了空 SRT") + raise FunAsrError(f"{service_name}返回了空 SRT") return write_srt_file(srt_content, subtitle_file) @@ -665,6 +727,45 @@ def local_fun_asr_result_to_srt( return "\n".join(lines).rstrip() + "\n" +def firered_asr_result_to_srt(result_json: dict[str, Any]) -> str: + """Convert a FireRedASR2-AED-Pack JSON response into SRT when no SRT URL is returned.""" + blocks: list[dict[str, Any]] = [] + sentences = result_json.get("sentences") + if isinstance(sentences, list): + for sentence in sentences: + if not isinstance(sentence, dict): + continue + text = str(sentence.get("text") or "").strip() + if not text: + continue + start = sentence.get("start_ms", sentence.get("begin_time", sentence.get("start_time", 0))) + end = sentence.get("end_ms", sentence.get("end_time")) + start_ms = _timestamp_ms(start, "firered.sentence.start_ms") + end_ms = _timestamp_ms(end, "firered.sentence.end_ms") if end is not None else start_ms + 500 + blocks.append({"start": start_ms, "end": end_ms, "text": text}) + + if not blocks: + return local_fun_asr_result_to_srt(result_json) + + lines = [] + for index, block in enumerate(blocks, start=1): + lines.append(_srt_block(index, block["start"], block["end"], block["text"])) + return "\n".join(lines).rstrip() + "\n" + + +def _get_local_srt_download_url(result_json: dict[str, Any]) -> str: + downloads = result_json.get("downloads") or {} + if isinstance(downloads, dict): + download_url = downloads.get("srt") + if download_url: + return str(download_url) + for key in ("srt_url", "srt_download_url", "download_url"): + download_url = result_json.get(key) + if download_url: + return str(download_url) + return "" + + def create_with_local_fun_asr( local_file: str, subtitle_file: str = "", @@ -689,8 +790,7 @@ def create_with_local_fun_asr( if isinstance(srt_file, str) and srt_file and os.path.isfile(srt_file): output_file = copy_srt_file(srt_file, subtitle_file) else: - downloads = result_json.get("downloads") or {} - download_url = downloads.get("srt") if isinstance(downloads, dict) else "" + download_url = _get_local_srt_download_url(result_json) if download_url: output_file = download_local_srt( download_url, @@ -710,6 +810,56 @@ def create_with_local_fun_asr( raise FunAsrError("本地 FunASR-Pack 字幕转写失败,请检查服务地址、文件或模型状态") from exc +def create_with_local_firered_asr( + local_file: str, + subtitle_file: str = "", + api_url: str = LOCAL_FIRERED_ASR_API_URL, + enable_vad: Optional[bool] = True, + enable_lid: Optional[bool] = True, + enable_punc: Optional[bool] = True, + return_timestamp: Optional[bool] = True, + timeout: float = 600.0, + session=requests, +) -> Optional[str]: + """Create an SRT file through a locally running FireRedASR2-AED-Pack API.""" + service_name = "本地 FireRedASR2-AED-Pack 服务" + try: + result_json = request_local_firered_asr( + local_file=local_file, + api_url=api_url, + enable_vad=enable_vad, + enable_lid=enable_lid, + enable_punc=enable_punc, + return_timestamp=return_timestamp, + timeout=timeout, + session=session, + ) + + srt_file = result_json.get("srt_file") + if isinstance(srt_file, str) and srt_file and os.path.isfile(srt_file): + output_file = copy_srt_file(srt_file, subtitle_file) + else: + download_url = _get_local_srt_download_url(result_json) + if download_url: + output_file = download_local_srt( + download_url, + api_url=api_url, + subtitle_file=subtitle_file, + session=session, + service_name=service_name, + ) + else: + srt_content = firered_asr_result_to_srt(result_json) + output_file = write_srt_file(srt_content, subtitle_file) + + logger.info(f"本地 FireRedASR2-AED-Pack 字幕文件已生成: {output_file}") + return output_file + except FunAsrError: + raise + except Exception as exc: + raise FunAsrError("本地ASR字幕转写失败,请检查 FireRedASR2-AED-Pack 服务地址、文件或模型状态") from exc + + def create_with_fun_asr( local_file: str, subtitle_file: str = "", diff --git a/app/services/task.py b/app/services/task.py index 28b05ea..74e7804 100644 --- a/app/services/task.py +++ b/app/services/task.py @@ -24,7 +24,7 @@ def _is_auto_transcription_enabled(params: VideoClipParams) -> bool: def _get_auto_transcription_backend(params: VideoClipParams) -> str: backend = str(getattr(params, "subtitle_auto_transcribe_backend", "") or "").strip().lower() - if backend not in {"local", "bailian"}: + if backend not in {"local", "firered", "bailian"}: backend = "local" return backend @@ -80,6 +80,19 @@ def _transcribe_final_video(task_id: str, video_path: str, params: VideoClipPara hotword=str(getattr(params, "subtitle_auto_transcribe_hotword", "") or "").strip(), enable_spk=bool(getattr(params, "subtitle_auto_transcribe_enable_spk", False)), ) + elif backend == "firered": + api_url = str( + getattr(params, "subtitle_auto_transcribe_firered_api_url", "") + or config.fun_asr.get("firered_api_url", fun_asr_subtitle.LOCAL_FIRERED_ASR_API_URL) + ).strip() + if not api_url: + raise ValueError("请先输入本地ASR API 地址") + + generated_path = fun_asr_subtitle.create_with_local_firered_asr( + local_file=video_path, + subtitle_file=subtitle_file, + api_url=api_url, + ) else: api_key = str( getattr(params, "subtitle_auto_transcribe_api_key", "") diff --git a/app/services/test_fun_asr_subtitle_unittest.py b/app/services/test_fun_asr_subtitle_unittest.py index d59550d..d5a3ccd 100644 --- a/app/services/test_fun_asr_subtitle_unittest.py +++ b/app/services/test_fun_asr_subtitle_unittest.py @@ -481,6 +481,91 @@ class LocalFunAsrServiceTests(unittest.TestCase): self.assertIn("世界。", srt) +class LocalFireRedAsrServiceTests(unittest.TestCase): + def test_request_local_firered_asr_posts_file_and_options(self): + class LocalSession: + def __init__(self): + self.calls = [] + + def post(self, url, **kwargs): + self.calls.append(("POST", url, kwargs)) + return FakeResponse({"text": "你好", "srt_url": "/outputs/out.srt"}) + + with tempfile.TemporaryDirectory() as tmp_dir: + local_file = Path(tmp_dir) / "audio.wav" + local_file.write_bytes(b"audio") + session = LocalSession() + + result = fasr.request_local_firered_asr( + str(local_file), + api_url="127.0.0.1:7867", + enable_vad=True, + enable_lid=False, + enable_punc=True, + return_timestamp=True, + timeout=456, + session=session, + ) + + self.assertEqual("你好", result["text"]) + self.assertEqual("POST", session.calls[0][0]) + self.assertEqual("http://127.0.0.1:7867/asr", session.calls[0][1]) + self.assertEqual( + { + "enable_vad": "true", + "enable_lid": "false", + "enable_punc": "true", + "return_timestamp": "true", + }, + session.calls[0][2]["data"], + ) + self.assertEqual(456, session.calls[0][2]["timeout"]) + self.assertIn("file", session.calls[0][2]["files"]) + + def test_create_with_local_firered_asr_downloads_srt_url(self): + class LocalSession: + def __init__(self): + self.calls = [] + + def post(self, url, **kwargs): + self.calls.append(("POST", url, kwargs)) + return FakeResponse({"text": "你好", "srt_url": "/outputs/result.srt"}) + + def get(self, url, **kwargs): + self.calls.append(("GET", url, kwargs)) + return FakeResponse(text="1\n00:00:00,000 --> 00:00:01,000\n你好\n") + + with tempfile.TemporaryDirectory() as tmp_dir: + local_file = Path(tmp_dir) / "audio.wav" + local_file.write_bytes(b"audio") + subtitle_file = Path(tmp_dir) / "out.srt" + session = LocalSession() + + result_path = fasr.create_with_local_firered_asr( + str(local_file), + subtitle_file=str(subtitle_file), + api_url="http://127.0.0.1:7867", + session=session, + ) + + self.assertEqual(str(subtitle_file), result_path) + self.assertEqual("http://127.0.0.1:7867/outputs/result.srt", session.calls[1][1]) + self.assertIn("你好", subtitle_file.read_text(encoding="utf-8")) + + def test_firered_asr_result_to_srt_uses_sentence_timestamps(self): + result = { + "sentences": [ + {"text": "你好。", "start_ms": 40, "end_ms": 900}, + {"text": "欢迎观看。", "start_ms": 900, "end_ms": 2100}, + ] + } + + srt = fasr.firered_asr_result_to_srt(result) + + self.assertIn("1\n00:00:00,040 --> 00:00:00,900\n你好。", srt) + self.assertIn("2\n00:00:00,900 --> 00:00:02,100\n欢迎观看。", srt) + + class FunAsrConfigTests(unittest.TestCase): def test_save_config_persists_fun_asr_section(self): original_config_file = cfg.config_file @@ -503,6 +588,7 @@ class FunAsrConfigTests(unittest.TestCase): config_data = tomllib.loads(Path("config.example.toml").read_text(encoding="utf-8")) self.assertEqual("local", config_data["fun_asr"]["backend"]) self.assertEqual("http://127.0.0.1:7860", config_data["fun_asr"]["api_url"]) + self.assertEqual("http://127.0.0.1:7867", config_data["fun_asr"]["firered_api_url"]) self.assertEqual("fun-asr", config_data["fun_asr"]["model"]) self.assertIn("api_key", config_data["fun_asr"]) diff --git a/config.example.toml b/config.example.toml index 547724e..3c815c3 100644 --- a/config.example.toml +++ b/config.example.toml @@ -110,10 +110,11 @@ [fun_asr] # Fun-ASR 字幕转录配置 - # backend = "local" 使用本地 FunASR-Pack API;backend = "bailian" 使用阿里百炼在线 fun-asr + # backend = "local" 使用本地 FunASR-Pack API;backend = "firered" 使用本地 FireRedASR2-AED-Pack API;backend = "bailian" 使用阿里百炼在线 fun-asr auto_transcribe_enabled = false backend = "local" api_url = "http://127.0.0.1:7860" + firered_api_url = "http://127.0.0.1:7867" hotword = "" enable_spk = false # 使用阿里百炼在线 fun-asr 时,访问 https://bailian.console.aliyun.com/?tab=model#/api-key 获取 API Key diff --git a/webui/components/script_settings.py b/webui/components/script_settings.py index d73eba0..555e1e1 100644 --- a/webui/components/script_settings.py +++ b/webui/components/script_settings.py @@ -996,11 +996,12 @@ def render_fun_asr_transcription(tr): backend_options = { tr("Local FunASR-Pack API"): "local", + tr("Local FireRedASR API"): "firered", tr("Ali Bailian Online Fun-ASR"): "bailian", tr("上传字幕文件"): "upload", } saved_backend = str(config.fun_asr.get("backend", "")).strip().lower() - if saved_backend not in {"local", "bailian", "upload"}: + if saved_backend not in {"local", "firered", "bailian", "upload"}: saved_backend = ( "bailian" if config.fun_asr.get("api_key") and not config.fun_asr.get("api_url") @@ -1012,6 +1013,7 @@ def render_fun_asr_transcription(tr): backend = saved_backend api_key = "" api_url = config.fun_asr.get("api_url", fun_asr_subtitle.LOCAL_FUN_ASR_API_URL) + firered_api_url = config.fun_asr.get("firered_api_url", fun_asr_subtitle.LOCAL_FIRERED_ASR_API_URL) hotword = config.fun_asr.get("hotword", "") enable_spk = bool(config.fun_asr.get("enable_spk", False)) media_paths = _selected_video_paths() @@ -1020,11 +1022,10 @@ def render_fun_asr_transcription(tr): with subtitle_cols[0]: with st.expander(tr("Ali Bailian Fun-ASR Subtitle Transcription"), expanded=False): - backend_label = st.radio( + backend_label = st.selectbox( tr("Subtitle Processing Method"), options=backend_labels, index=backend_values.index(saved_backend), - horizontal=True, key="fun_asr_backend", ) backend = backend_options[backend_label] @@ -1051,6 +1052,14 @@ def render_fun_asr_transcription(tr): help=tr("Enable speaker diarization Help"), key="fun_asr_enable_spk", ) + elif backend == "firered": + st.caption(tr("Local FireRed-ASR upload caption")) + firered_api_url = st.text_input( + tr("Local FireRedASR API URL"), + value=firered_api_url, + help=tr("Local FireRedASR API URL Help"), + key="fun_asr_firered_api_url", + ) else: st.caption(tr("Fun-ASR upload caption")) st.markdown( @@ -1166,6 +1175,10 @@ def render_fun_asr_transcription(tr): clear_fun_asr_subtitle_state() st.error(tr("Please enter local FunASR-Pack API URL")) return + if backend == "firered" and not str(firered_api_url).strip(): + clear_fun_asr_subtitle_state() + st.error(tr("Please enter local FireRedASR API URL")) + return missing_paths = [path for path in media_paths if not os.path.exists(path)] if not media_paths or missing_paths: clear_fun_asr_subtitle_state() @@ -1184,22 +1197,25 @@ def render_fun_asr_transcription(tr): config.fun_asr["backend"] = backend config.fun_asr["api_url"] = str(api_url).strip() + config.fun_asr["firered_api_url"] = str(firered_api_url).strip() config.fun_asr["api_key"] = api_key.strip() config.fun_asr["hotword"] = str(hotword).strip() config.fun_asr["enable_spk"] = bool(enable_spk) config.fun_asr["model"] = "fun-asr" config.save_config() - spinner_text = ( - tr("Transcribing with local FunASR-Pack...") - if backend == "local" - else tr("Transcribing with Fun-ASR...") - ) + if backend == "local": + spinner_text = tr("Transcribing with local FunASR-Pack...") + elif backend == "firered": + spinner_text = tr("Transcribing with local FireRedASR...") + else: + spinner_text = tr("Transcribing with Fun-ASR...") with st.spinner(spinner_text): progress_bar = st.progress(0) if len(media_paths) > 1 else None generated_paths = [] for index, media_path in enumerate(media_paths, start=1): - subtitle_name = f"{os.path.splitext(os.path.basename(media_path))[0]}_fun_asr.srt" + subtitle_suffix = "firered_asr" if backend == "firered" else "fun_asr" + subtitle_name = f"{os.path.splitext(os.path.basename(media_path))[0]}_{subtitle_suffix}.srt" subtitle_path = _unique_file_path(utils.subtitle_dir(), subtitle_name) if backend == "local": @@ -1210,6 +1226,12 @@ def render_fun_asr_transcription(tr): hotword=str(hotword).strip(), enable_spk=bool(enable_spk), ) + elif backend == "firered": + generated_path = fun_asr_subtitle.create_with_local_firered_asr( + local_file=media_path, + subtitle_file=subtitle_path, + api_url=str(firered_api_url).strip(), + ) else: generated_path = fun_asr_subtitle.create_with_fun_asr( local_file=media_path, diff --git a/webui/components/subtitle_settings.py b/webui/components/subtitle_settings.py index 96a7a7a..2c0355d 100644 --- a/webui/components/subtitle_settings.py +++ b/webui/components/subtitle_settings.py @@ -457,7 +457,7 @@ def render_subtitle_mask_settings(tr): def _get_saved_auto_transcribe_backend(): saved_backend = str(config.fun_asr.get("backend", "")).strip().lower() - if saved_backend not in {"local", "bailian"}: + if saved_backend not in {"local", "firered", "bailian"}: saved_backend = ( "bailian" if config.fun_asr.get("api_key") and not config.fun_asr.get("api_url") @@ -481,6 +481,7 @@ def render_auto_transcription_settings(tr): backend = _get_saved_auto_transcribe_backend() api_url = config.fun_asr.get("api_url", fun_asr_subtitle.LOCAL_FUN_ASR_API_URL) + firered_api_url = config.fun_asr.get("firered_api_url", fun_asr_subtitle.LOCAL_FIRERED_ASR_API_URL) hotword = config.fun_asr.get("hotword", "") enable_spk = bool(config.fun_asr.get("enable_spk", False)) api_key = config.fun_asr.get("api_key", "") @@ -488,6 +489,7 @@ def render_auto_transcription_settings(tr): if not auto_transcribe_enabled: st.session_state['subtitle_auto_transcribe_backend'] = backend st.session_state['subtitle_auto_transcribe_api_url'] = api_url + st.session_state['subtitle_auto_transcribe_firered_api_url'] = firered_api_url st.session_state['subtitle_auto_transcribe_hotword'] = hotword st.session_state['subtitle_auto_transcribe_enable_spk'] = enable_spk st.session_state['subtitle_auto_transcribe_api_key'] = api_key @@ -495,17 +497,17 @@ def render_auto_transcription_settings(tr): backend_options = { tr("Local FunASR-Pack API"): "local", + tr("Local FireRedASR API"): "firered", tr("Ali Bailian Online Fun-ASR"): "bailian", } backend_values = list(backend_options.values()) backend_labels = list(backend_options.keys()) - backend_label = st.radio( + backend_label = st.selectbox( tr("Subtitle Processing Method"), options=backend_labels, index=backend_values.index(backend), - horizontal=True, - key="subtitle_auto_transcribe_backend_radio", + key="subtitle_auto_transcribe_backend_select", ) backend = backend_options[backend_label] @@ -529,6 +531,14 @@ def render_auto_transcription_settings(tr): help=tr("Enable speaker diarization Help"), key="subtitle_auto_transcribe_enable_spk_checkbox", ) + elif backend == "firered": + st.caption(tr("Auto Transcription FireRed Caption")) + firered_api_url = st.text_input( + tr("Local FireRedASR API URL"), + value=firered_api_url, + help=tr("Local FireRedASR API URL Help"), + key="subtitle_auto_transcribe_firered_api_url_input", + ) else: st.caption(tr("Auto Transcription Online Caption")) st.markdown( @@ -546,6 +556,7 @@ def render_auto_transcription_settings(tr): config.fun_asr["backend"] = backend config.fun_asr["api_url"] = str(api_url).strip() + config.fun_asr["firered_api_url"] = str(firered_api_url).strip() config.fun_asr["api_key"] = str(api_key).strip() config.fun_asr["hotword"] = str(hotword).strip() config.fun_asr["enable_spk"] = bool(enable_spk) @@ -553,6 +564,7 @@ def render_auto_transcription_settings(tr): st.session_state['subtitle_auto_transcribe_backend'] = backend st.session_state['subtitle_auto_transcribe_api_url'] = str(api_url).strip() + st.session_state['subtitle_auto_transcribe_firered_api_url'] = str(firered_api_url).strip() st.session_state['subtitle_auto_transcribe_api_key'] = str(api_key).strip() st.session_state['subtitle_auto_transcribe_hotword'] = str(hotword).strip() st.session_state['subtitle_auto_transcribe_enable_spk'] = bool(enable_spk) @@ -692,6 +704,10 @@ def get_subtitle_params(): 'subtitle_auto_transcribe_api_url', config.fun_asr.get("api_url", "") ), + 'subtitle_auto_transcribe_firered_api_url': st.session_state.get( + 'subtitle_auto_transcribe_firered_api_url', + config.fun_asr.get("firered_api_url", "") + ), 'subtitle_auto_transcribe_api_key': st.session_state.get( 'subtitle_auto_transcribe_api_key', config.fun_asr.get("api_key", "") diff --git a/webui/i18n/en.json b/webui/i18n/en.json index e4de584..fae8c95 100644 --- a/webui/i18n/en.json +++ b/webui/i18n/en.json @@ -414,14 +414,19 @@ "Ali Bailian Fun-ASR Subtitle Transcription": "Subtitle Processing", "Subtitle Processing Method": "Subtitle Processing Method", "Fun-ASR Backend": "Fun-ASR Backend", - "Local FunASR-Pack API": "Local", - "Ali Bailian Online Fun-ASR": "Online", + "Local FunASR-Pack API": "FunASR (Local)", + "Local FireRedASR API": "FireRedASR2 (Local)", + "Ali Bailian Online Fun-ASR": "FunASR (Online)", "Local Fun-ASR upload caption": "The current video above will be converted to SRT subtitles through the locally running FunASR-Pack API.", + "Local FireRed-ASR upload caption": "The current video above will be converted to SRT subtitles through the locally running FireRedASR2-AED-Pack API.", "Fun-ASR upload caption": "The current video above will be uploaded to temporary Ali Bailian storage and converted to SRT subtitles with fun-asr.", "Auto Transcription Local Caption": "After the final video is merged, it will be converted to SRT subtitles through the locally running FunASR-Pack API.", + "Auto Transcription FireRed Caption": "After the final video is merged, it will be converted to SRT subtitles through the locally running FireRedASR2-AED-Pack API.", "Auto Transcription Online Caption": "After the final video is merged, it will be uploaded to temporary Ali Bailian storage and converted to SRT subtitles with fun-asr.", "Local FunASR-Pack API URL": "Local FunASR-Pack API URL", "Local FunASR-Pack API URL Help": "For example, http://127.0.0.1:7860. A full /asr endpoint URL is also supported.", + "Local FireRedASR API URL": "Local ASR API URL", + "Local FireRedASR API URL Help": "For example, http://127.0.0.1:7867. A full /asr endpoint URL is also supported.", "Fun-ASR Hotword": "Hotword", "Fun-ASR Hotword Help": "Optional hotwords passed to the local FunASR-Pack API.", "Enable speaker diarization": "Enable speaker diarization", @@ -439,8 +444,10 @@ "Calibrate subtitles": "Calibrate Subtitles", "Please enter Ali Bailian API Key": "Please enter the Ali Bailian API Key first", "Please enter local FunASR-Pack API URL": "Please enter the local FunASR-Pack API URL first", + "Please enter local FireRedASR API URL": "Please enter the local ASR API URL first", "Please upload media to transcribe": "Please upload the audio or video file to transcribe first", "Transcribing with local FunASR-Pack...": "Transcribing subtitles with local FunASR-Pack, please wait...", + "Transcribing with local FireRedASR...": "Transcribing subtitles with local ASR, please wait...", "Transcribing with Fun-ASR...": "Transcribing subtitles with Ali Bailian Fun-ASR, please wait...", "Fun-ASR failed without subtitle file": "Fun-ASR transcription failed: no subtitle file was generated", "Subtitle transcription succeeded": "Subtitle transcription succeeded: {file}", diff --git a/webui/i18n/zh.json b/webui/i18n/zh.json index e400adb..63eac0e 100644 --- a/webui/i18n/zh.json +++ b/webui/i18n/zh.json @@ -365,14 +365,19 @@ "Ali Bailian Fun-ASR Subtitle Transcription": "字幕处理", "Subtitle Processing Method": "字幕处理方式", "Fun-ASR Backend": "Fun-ASR 后端", - "Local FunASR-Pack API": "本地转写", - "Ali Bailian Online Fun-ASR": "在线转写", + "Local FunASR-Pack API": "FunASR(本地部署)", + "Local FireRedASR API": "FireRedASR2(本地部署)", + "Ali Bailian Online Fun-ASR": "FunASR(在线服务)", "Local Fun-ASR upload caption": "将使用上方当前视频,通过本机运行的 FunASR-Pack API 生成 SRT 字幕。", + "Local FireRed-ASR upload caption": "将使用上方当前视频,通过本机运行的 FireRedASR2-AED-Pack API 生成 SRT 字幕。", "Fun-ASR upload caption": "将使用上方当前视频,自动上传到阿里百炼临时存储并通过 fun-asr 生成 SRT 字幕。", "Auto Transcription Local Caption": "将在最终视频合并完成后,通过本机运行的 FunASR-Pack API 生成 SRT 字幕。", + "Auto Transcription FireRed Caption": "将在最终视频合并完成后,通过本机运行的 FireRedASR2-AED-Pack API 生成 SRT 字幕。", "Auto Transcription Online Caption": "将在最终视频合并完成后,自动上传到阿里百炼临时存储并通过 fun-asr 生成 SRT 字幕。", "Local FunASR-Pack API URL": "本地 FunASR-Pack API 地址", "Local FunASR-Pack API URL Help": "例如 http://127.0.0.1:7860;也可以直接填到 /asr 的完整地址。", + "Local FireRedASR API URL": "本地ASR API 地址", + "Local FireRedASR API URL Help": "例如 http://127.0.0.1:7867;也可以直接填到 /asr 的完整地址。", "Fun-ASR Hotword": "热词", "Fun-ASR Hotword Help": "可选,传给本地 FunASR-Pack 的热词参数。", "Enable speaker diarization": "启用说话人分段", @@ -390,8 +395,10 @@ "Calibrate subtitles": "校准字幕", "Please enter Ali Bailian API Key": "请先输入阿里百炼 API Key", "Please enter local FunASR-Pack API URL": "请先输入本地 FunASR-Pack API 地址", + "Please enter local FireRedASR API URL": "请先输入本地ASR API 地址", "Please upload media to transcribe": "请先上传需要转录的音频或视频文件", "Transcribing with local FunASR-Pack...": "正在使用本地 FunASR-Pack 转写字幕,请稍候...", + "Transcribing with local FireRedASR...": "正在使用本地ASR转写字幕,请稍候...", "Transcribing with Fun-ASR...": "正在使用阿里百炼 Fun-ASR 转写字幕,请稍候...", "Fun-ASR failed without subtitle file": "Fun-ASR 转写失败:未生成字幕文件", "Subtitle transcription succeeded": "字幕转写成功: {file}",