├── tools ├── __init__.py ├── put_extensions_here ├── wav2srt.py └── slicer2.py ├── Sava_Utils ├── man │ ├── en_US │ │ ├── __init__.py │ │ ├── title.py │ │ ├── help_custom.py │ │ ├── issues.py │ │ ├── changelog.py │ │ ├── README.py │ │ └── help.py │ ├── fr_FR │ │ ├── __init__.py │ │ ├── title.py │ │ ├── README.py │ │ ├── help_custom.py │ │ ├── issues.py │ │ ├── changelog.py │ │ └── help.py │ ├── ja_JP │ │ ├── __init__.py │ │ ├── title.py │ │ ├── changelog.py │ │ ├── README.py │ │ ├── issues.py │ │ └── help.py │ ├── zh_CN │ │ ├── __init__.py │ │ ├── title.py │ │ ├── changelog.py │ │ ├── issues.py │ │ ├── help_custom.py │ │ ├── README.py │ │ ├── help.py │ │ └── extension_dev.py │ └── __init__.py ├── i18nAuto │ ├── translations │ │ ├── __init__.py │ │ └── en_US.py │ └── __init__.py ├── base_component.py ├── translator │ ├── __init__.py │ └── ollama.py ├── __init__.py ├── audio_utils.py ├── extension_loader.py ├── polyphone.py ├── tts_engines │ ├── __init__.py │ └── mstts.py └── subtitle_translation.py ├── Sava_Extensions ├── extension │ ├── put_extension_here │ └── WAV2SRT │ │ └── __init__.py ├── translator │ ├── put_extension_here │ └── youdao │ │ ├── __init__.py │ │ ├── utils │ │ ├── AuthV4Util.py │ │ ├── AuthV3Util.py │ │ └── WebSocketUtil.py │ │ └── TranslateDemo.py ├── tts_engine │ ├── put_extension_here │ ├── CUSTOM_OLD │ │ ├── __init__.py │ │ └── custom.py │ └── BV2 │ │ ├── __init__.py │ │ └── bv2.py ├── extensions_config.json └── .gitignore ├── requirements.txt ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── bug_report.yml └── workflows │ └── stale.yml ├── .gitignore ├── docs ├── zh_CN │ ├── title.md │ ├── changelog.md │ ├── issues.md │ ├── help_custom.md │ ├── README.md │ ├── help.md │ └── extension_dev.md ├── ja_JP │ ├── title.md │ ├── changelog.md │ ├── README.md │ ├── issues.md │ └── help.md ├── en_US │ ├── title.md │ ├── help_custom.md │ ├── issues.md │ ├── changelog.md │ ├── README.md │ └── help.md └── fr_FR │ ├── title.md │ ├── README.md │ ├── help_custom.md │ ├── issues.md │ ├── changelog.md │ └── help.md ├── create_built-in_manual.sh ├── create_build_script.sh ├── GSV_API_launcher.py ├── README.md └── extra └── indexTTS └── indextts2_api.py /tools/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tools/put_extensions_here: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Sava_Utils/man/en_US/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Sava_Utils/man/fr_FR/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Sava_Utils/man/ja_JP/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Sava_Utils/man/zh_CN/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Sava_Utils/i18nAuto/translations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Sava_Extensions/extension/put_extension_here: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Sava_Extensions/translator/put_extension_here: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Sava_Extensions/tts_engine/put_extension_here: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | gradio>=5 2 | soundfile 3 | colorlog 4 | soxr -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bat 2 | *.sh 3 | __pycache__ 4 | SAVAdata 5 | dist 6 | build 7 | *.spec 8 | *.exe 9 | *.7z -------------------------------------------------------------------------------- /Sava_Extensions/extensions_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "tts_engine": { 3 | "BV2": false, 4 | "CUSTOM_OLD": false 5 | }, 6 | "extension": { 7 | "WAV2SRT": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /docs/zh_CN/title.md: -------------------------------------------------------------------------------- 1 | 版本V4.7.1-2509
2 | 仓库地址: [前往此处获取更新](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/releases) | [获取额外内容](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/tree/main/Sava_Extensions) -------------------------------------------------------------------------------- /docs/ja_JP/title.md: -------------------------------------------------------------------------------- 1 | バージョン4.7.1-2509
2 | GitHub: [手動で更新を確認](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/releases) | [拡張機能をインストール](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/tree/main/Sava_Extensions) -------------------------------------------------------------------------------- /docs/en_US/title.md: -------------------------------------------------------------------------------- 1 | Version 4.7.1-2509
2 | GitHub: [Check for updates manully](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/releases) | [Install Extensions](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/tree/main/Sava_Extensions) -------------------------------------------------------------------------------- /Sava_Utils/man/zh_CN/title.py: -------------------------------------------------------------------------------- 1 | title = r""" 2 | 版本V4.7.1-2509
3 | 仓库地址: [前往此处获取更新](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/releases) | [获取额外内容](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/tree/main/Sava_Extensions) 4 | """ 5 | -------------------------------------------------------------------------------- /Sava_Utils/man/ja_JP/title.py: -------------------------------------------------------------------------------- 1 | title = r""" 2 | バージョン4.7.1-2509
3 | GitHub: [手動で更新を確認](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/releases) | [拡張機能をインストール](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/tree/main/Sava_Extensions) 4 | """ 5 | -------------------------------------------------------------------------------- /docs/fr_FR/title.md: -------------------------------------------------------------------------------- 1 | Version 4.7.1-2509
2 | GitHub : [Vérifier manuellement les mises à jour](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/releases) | [Installer des extensions](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/tree/main/Sava_Extensions) -------------------------------------------------------------------------------- /Sava_Utils/man/en_US/title.py: -------------------------------------------------------------------------------- 1 | title = r""" 2 | Version 4.7.1-2509
3 | GitHub: [Check for updates manully](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/releases) | [Install Extensions](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/tree/main/Sava_Extensions) 4 | """ 5 | -------------------------------------------------------------------------------- /Sava_Utils/man/fr_FR/title.py: -------------------------------------------------------------------------------- 1 | title = r""" 2 | Version 4.7.1-2509
3 | GitHub : [Vérifier manuellement les mises à jour](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/releases) | [Installer des extensions](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/tree/main/Sava_Extensions) 4 | """ 5 | -------------------------------------------------------------------------------- /Sava_Extensions/translator/youdao/__init__.py: -------------------------------------------------------------------------------- 1 | def register(context): 2 | globals().update(context) 3 | # print(f'i18n current language: {i18n.get_language()}') 4 | i18n.update({"Youdao": "有道翻译", "APP_KEY": "您的应用ID", "APP_SECRET": "您的应用密钥"}) 5 | from .TranslateDemo import Youdao 6 | return Youdao() -------------------------------------------------------------------------------- /Sava_Extensions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !tts_engine/ 4 | !tts_engine/BV2/ 5 | !tts_engine/BV2/* 6 | !tts_engine/CUSTOM_OLD/ 7 | !tts_engine/CUSTOM_OLD/* 8 | !translator/ 9 | !translator/youdao 10 | !translator/youdao/* 11 | !translator/youdao/utils/* 12 | !extension/ 13 | !extension/WAV2SRT/ 14 | !extension/WAV2SRT/* 15 | -------------------------------------------------------------------------------- /create_built-in_manual.sh: -------------------------------------------------------------------------------- 1 | sed '2d' ./README.md > ./docs/en_US/README.md 2 | for item in $(find ./docs -type f -name '*.md');do 3 | language="$(awk -F '/' '{print($(NF-1))}' <<< $item)" 4 | name="$(basename $item .md)" 5 | mkdir -p "./Sava_Utils/man/$language" 6 | cat <(echo -e "$name = r\"\"\"") $item <(echo -e "\n\"\"\"") > ./Sava_Utils/man/$language/$name.py 7 | #echo $language $name 8 | done -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /Sava_Extensions/tts_engine/CUSTOM_OLD/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | # Sava_Utils/extension_loader.py 3 | # context passed to function 'register' 4 | extension_instance = module.register( 5 | { 6 | "current_path": current_path, 7 | "Base_Component": Base_Component, 8 | "TTSProjet": TTSProjet, 9 | "Traducteur": Traducteur, 10 | "utils": utils, 11 | "audio_utils":audio_utils, 12 | "i18n": i18n, 13 | "MANUAL": MANUAL, 14 | "logger": logger, 15 | "Settings": Settings, 16 | "Shared_Option": Shared_Option, 17 | }, 18 | ) 19 | """ 20 | 21 | def register(context): 22 | globals().update(context) 23 | from .custom import Custom 24 | return Custom() 25 | -------------------------------------------------------------------------------- /Sava_Extensions/tts_engine/BV2/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | # Sava_Utils/extension_loader.py 3 | # context passed to function 'register' 4 | extension_instance = module.register( 5 | { 6 | "current_path": current_path, 7 | "Base_Component": Base_Component, 8 | "TTSProjet": TTSProjet, 9 | "Traducteur": Traducteur, 10 | "utils": utils, 11 | "audio_utils":audio_utils, 12 | "i18n": i18n, 13 | "MANUAL": MANUAL, 14 | "logger": logger, 15 | "Settings": Settings, 16 | "Shared_Option": Shared_Option, 17 | }, 18 | ) 19 | """ 20 | 21 | def register(context): 22 | globals().update(context) # insert to module global namespace 23 | from .bv2 import BV2 24 | return BV2() # Return the instantiated object. 25 | -------------------------------------------------------------------------------- /Sava_Extensions/extension/WAV2SRT/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | # Sava_Utils/extension_loader.py 3 | # context passed to function 'register' 4 | extension_instance = module.register( 5 | { 6 | "current_path": current_path, 7 | "Base_Component": Base_Component, 8 | "TTSProjet": TTSProjet, 9 | "Traducteur": Traducteur, 10 | "utils": utils, 11 | "audio_utils":audio_utils, 12 | "i18n": i18n, 13 | "MANUAL": MANUAL, 14 | "logger": logger, 15 | "Settings": Settings, 16 | "Shared_Option": Shared_Option, 17 | }, 18 | ) 19 | """ 20 | 21 | def register(context): 22 | globals().update(context) # insert to module global namespace 23 | from .wav2srt_webui import WAV2SRT 24 | return WAV2SRT() # Return the instantiated object. 25 | -------------------------------------------------------------------------------- /Sava_Utils/i18nAuto/__init__.py: -------------------------------------------------------------------------------- 1 | import locale 2 | 3 | 4 | class I18n: 5 | def __init__(self, language=None): 6 | if language in ["Auto", None]: 7 | language = locale.getdefaultlocale()[0] 8 | self.language = language 9 | ls = dict() 10 | try: 11 | exec(f"from .translations.{language} import i18n_dict", globals(), ls) 12 | self.language_map = ls["i18n_dict"] 13 | except: 14 | self.language_map = dict() 15 | 16 | def get_language(self): 17 | return self.language 18 | 19 | def update(self, i18n_data: dict[str:str]): 20 | self.language_map.update(i18n_data) 21 | 22 | def __call__(self, key): 23 | return self.language_map.get(key, key) 24 | 25 | def __repr__(self): 26 | return f"Using Language: {self.language}" 27 | -------------------------------------------------------------------------------- /create_build_script.sh: -------------------------------------------------------------------------------- 1 | #ex: $1 = pyinstaller 2 | echo -n "$1 " > build_sava.bat 3 | for i in $(find ./Sava_Utils/man -type f -name '*.py' );do 4 | module_name=$(basename $i .py) 5 | if [ $module_name != "__init__" ];then 6 | dn=$(dirname $i) 7 | dn=$(basename $dn) 8 | module_name="Sava_Utils.man.$dn.$module_name" 9 | echo -n "--hidden-import=$module_name " >> build_sava.bat 10 | fi 11 | done 12 | for i in $(find ./Sava_Utils/i18nAuto/translations -type f -name '*.py');do 13 | module_name=$(basename $i .py) 14 | if [ $module_name != "__init__" ];then 15 | module_name="Sava_Utils.i18nAuto.translations.$module_name" 16 | echo -n "--hidden-import=$module_name " >> build_sava.bat 17 | fi 18 | done 19 | echo '-F Srt-AI-Voice-Assistant.py' >> build_sava.bat 20 | echo 'pause' >> build_sava.bat 21 | -------------------------------------------------------------------------------- /docs/ja_JP/changelog.md: -------------------------------------------------------------------------------- 1 | ## 変更履歴 2 | **This file is translated by AI. And just for reference.** 3 | ### V4-2503アップデート: 4 | #### バージョンをより明確にするため、リリース日付に加えてバージョン番号が付けられます。 5 | #### このアップデート後、前のバージョンの合成履歴と保存された話者は再作成する必要があります。そうしないと、エラーが発生する可能性があります! 6 | 1. 字幕編集 7 | 2. 字幕翻訳 8 | 3. 様々な詳細が改善され、バグが修正されました 9 | 4. CosyVoice2をサポート(GSVパネルを再利用) 10 | 5. (4.0.1) バッチモード 11 | 6. (4.1) サーバーモード 12 | 7. (4.2) 国際化(I18n) 13 | 14 | ### 250214アップデート: 15 | 1. 過去のプロジェクトの読み込みをサポート 16 | 2. 複数話者によるダビングをサポート 17 | 18 | ### 250123アップデート: 19 | 1. 合成後の実際の開始と終了タイムスタンプに一致するSRT字幕ファイルの再エクスポートをサポートします。また、合成のためのTXTテキストファイルの読み込みもサポートし、この場合、段落は文で分割されます。 20 | 2. 将来的な拡張性と簡素化を高めるため、ダウンロードをより便利にする単一のスクリプトファイルの設計を断念せざるを得ません。このバージョンからコードの再設計が始まりました。 21 | 3. いくつかのドキュメントを追加しました。 22 | 23 | ### 240811アップデート: 24 | 1. エラーメッセージをユーザーに通知 25 | 2. TTSプロジェクトの環境を自動検出 26 | 3. api-v1との互換性を回復 27 | 4. メジャーな機能アップデート:特定の行に不満がある場合、それらを再生成する機能をサポート -------------------------------------------------------------------------------- /Sava_Utils/man/ja_JP/changelog.py: -------------------------------------------------------------------------------- 1 | changelog = r""" 2 | ## 変更履歴 3 | **This file is translated by AI. And just for reference.** 4 | ### V4-2503アップデート: 5 | #### バージョンをより明確にするため、リリース日付に加えてバージョン番号が付けられます。 6 | #### このアップデート後、前のバージョンの合成履歴と保存された話者は再作成する必要があります。そうしないと、エラーが発生する可能性があります! 7 | 1. 字幕編集 8 | 2. 字幕翻訳 9 | 3. 様々な詳細が改善され、バグが修正されました 10 | 4. CosyVoice2をサポート(GSVパネルを再利用) 11 | 5. (4.0.1) バッチモード 12 | 6. (4.1) サーバーモード 13 | 7. (4.2) 国際化(I18n) 14 | 15 | ### 250214アップデート: 16 | 1. 過去のプロジェクトの読み込みをサポート 17 | 2. 複数話者によるダビングをサポート 18 | 19 | ### 250123アップデート: 20 | 1. 合成後の実際の開始と終了タイムスタンプに一致するSRT字幕ファイルの再エクスポートをサポートします。また、合成のためのTXTテキストファイルの読み込みもサポートし、この場合、段落は文で分割されます。 21 | 2. 将来的な拡張性と簡素化を高めるため、ダウンロードをより便利にする単一のスクリプトファイルの設計を断念せざるを得ません。このバージョンからコードの再設計が始まりました。 22 | 3. いくつかのドキュメントを追加しました。 23 | 24 | ### 240811アップデート: 25 | 1. エラーメッセージをユーザーに通知 26 | 2. TTSプロジェクトの環境を自動検出 27 | 3. api-v1との互換性を回復 28 | 4. メジャーな機能アップデート:特定の行に不満がある場合、それらを再生成する機能をサポート 29 | """ 30 | -------------------------------------------------------------------------------- /docs/zh_CN/changelog.md: -------------------------------------------------------------------------------- 1 | ## 重大的更新历史 2 | ### 4.6.1主要更新内容: 3 | 1. 可以自定义接入带界面的TTS/翻译API,以及扩展功能插件。Bert-VITS2和旧版自定义API会变成官方插件且默认不启用。 设置页布局也有相应调整。插件开发请参阅文档、本项目源码和现有插件 4 | 2. 增加有道翻译(作为插件) 5 | 3. 自动语速新增慢放倍率调整 6 | 4. 加入高级脚本模式(开发者模式) 7 | * 注意:本次更新后旧版本的说话人会全部失效,对此带来的不便深感抱歉 8 | ### V4-2503更新:
9 | #### 为了让版本更具辨识度,除了标注发布日期外,还分配了版本号。 10 | #### 本次更新后,上一个版本的合成历史和保存的说话人需要重新创建,否则会报错! 11 | 1.字幕编辑 12 | 2.字幕批量翻译 13 | 3.各项细节提升和bug修复 14 | 4.支持CosyVoice2(复用GSV的面板) 15 | 5.(4.0.1)批量模式 16 | 6.(4.1)服务模式 17 | 7.(4.2)I18n 18 | 8.(4.3)新增自动语速和自动去静音功能;现在可从标记文件快速生成多说话人工程 19 | 9.(4.3.1)加入查找和替换;加入一键重新生成按钮 20 | 10.(4.4)可编辑GSV多音字,自动检测模型;ollama允许自定义提示词;可利用自定义模版导出带说话人名称的字幕 21 | 11.(4.5)翻译模块可合并双语字幕;音视频转录模块增加UVR人声分离模型支持;增加视频一键合并 22 | 23 | ### 250214更新:
24 | 1.支持读取历史工程 25 | 2.支持多说话人配音 26 | 3.更完善的文档 27 | 28 | ### 250123更新:
29 | 1.支持在合成完毕后导出符合实际情况的srt字幕文件,同时也支持通过读取txt纯文本文件来进行合成,在这种情况下会按每句来切分段落。 30 | 31 | 2.为了未来的扩展性和简洁性,我不得不放弃了单脚本文件的设计,即使对于下载而言更加方便。代码从现版本逐步开始重构。 32 | 33 | 3.加入一些文档说明 34 | 35 | ### 240811更新:
36 | 1.增加错误提示 37 | 2.自动检测项目路径 38 | 3.再次兼容api-v1。 39 | 4.重大功能更新:支持重新抽卡合成 -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | # This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. 2 | # You can adjust the behavior by modifying this file. 3 | # For more information, see: 4 | # https://github.com/actions/stale 5 | name: Mark stale issues 6 | 7 | on: 8 | schedule: 9 | - cron: '0 12 * * *' 10 | 11 | jobs: 12 | stale: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | issues: write 16 | pull-requests: write 17 | 18 | steps: 19 | - uses: actions/stale@v9 20 | with: 21 | repo-token: ${{ secrets.GITHUB_TOKEN }} 22 | exempt-issue-labels: 'enhancement' 23 | days-before-pr-stale: -1 24 | days-before-pr-close: -1 25 | stale-issue-label: "stale" 26 | days-before-issue-stale: 14 27 | days-before-issue-close: 0 28 | close-issue-message: 'This issue has been automatically closed due to 14 days of inactivity. If it still needs to be addressed, please reopen this issue.' 29 | remove-stale-when-updated: true 30 | enable-statistics: false 31 | 32 | -------------------------------------------------------------------------------- /docs/ja_JP/README.md: -------------------------------------------------------------------------------- 1 | # Srt-AI-Voice-Assistant 2 | **This file is translated by AI. And just for reference.** 3 | ### このプロジェクトは、複数のAI音声合成(TTS)を使用して、字幕ファイルやテキストファイルにダビングすることができます。
また、音声/動画の文字起こしや字幕翻訳など、様々な便利な補助機能を提供します。 4 | 問題に遭遇した場合や新機能のリクエストがある場合は、[Issues](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/issues) をご利用ください。 5 | 6 | ## 機能 7 | - ✅ オープンソースで、使いやすいWebUIインターフェース。ローカルで実行でき、LAN経由でアクセス可能です。 8 | - ✅ 複数のTTSプロジェクトをサポート:BV2、GSV、CosyVoice2、AzureTTSなど、独自のAPIもカスタマイズできます! 9 | - ✅ パーソナル設定とプリセットを保存できます。 10 | - ✅ バッチモードが利用可能です。 11 | - ✅ 字幕編集機能があります。 12 | - ✅ 字幕翻訳機能を備えています。 13 | - ✅ 特定の行を再生成できます。 14 | - ✅ 複数話者によるダビングをサポートします。 15 | - ✅ 字幕を再エクスポートできます。 16 | - ✅ 拡張機能:音声/動画の字幕文字起こし 17 | - ✅ 国際化(I18n)対応 18 | 19 | ## [パッケージ版のみをダウンロード](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/releases) 20 | * 依存関係の競合やインストール問題がある場合のみ、このバージョンを使用してください。 21 | 22 | ## [GPT-SoVITS付きの統合パッケージをダウンロード(Hugging Faceから)](https://huggingface.co/YYuX/GPT-SoVITS-SAVA-windows-package/tree/main) 23 | * GPT-SoVITS統合パッケージにはパッケージ版が含まれており、組み込みモデルや事前学習モデルは削除されていません。コードは公式リポジトリと同じです。 24 | * 注意:GPT-SoVITS統合パッケージに含まれるパッケージ版は最新バージョンでない可能性があります。上書きして更新してください。 -------------------------------------------------------------------------------- /Sava_Utils/man/zh_CN/changelog.py: -------------------------------------------------------------------------------- 1 | changelog = r""" 2 | ## 重大的更新历史 3 | ### 4.6.1主要更新内容: 4 | 1. 可以自定义接入带界面的TTS/翻译API,以及扩展功能插件。Bert-VITS2和旧版自定义API会变成官方插件且默认不启用。 设置页布局也有相应调整。插件开发请参阅文档、本项目源码和现有插件 5 | 2. 增加有道翻译(作为插件) 6 | 3. 自动语速新增慢放倍率调整 7 | 4. 加入高级脚本模式(开发者模式) 8 | * 注意:本次更新后旧版本的说话人会全部失效,对此带来的不便深感抱歉 9 | ### V4-2503更新:
10 | #### 为了让版本更具辨识度,除了标注发布日期外,还分配了版本号。 11 | #### 本次更新后,上一个版本的合成历史和保存的说话人需要重新创建,否则会报错! 12 | 1.字幕编辑 13 | 2.字幕批量翻译 14 | 3.各项细节提升和bug修复 15 | 4.支持CosyVoice2(复用GSV的面板) 16 | 5.(4.0.1)批量模式 17 | 6.(4.1)服务模式 18 | 7.(4.2)I18n 19 | 8.(4.3)新增自动语速和自动去静音功能;现在可从标记文件快速生成多说话人工程 20 | 9.(4.3.1)加入查找和替换;加入一键重新生成按钮 21 | 10.(4.4)可编辑GSV多音字,自动检测模型;ollama允许自定义提示词;可利用自定义模版导出带说话人名称的字幕 22 | 11.(4.5)翻译模块可合并双语字幕;音视频转录模块增加UVR人声分离模型支持;增加视频一键合并 23 | 24 | ### 250214更新:
25 | 1.支持读取历史工程 26 | 2.支持多说话人配音 27 | 3.更完善的文档 28 | 29 | ### 250123更新:
30 | 1.支持在合成完毕后导出符合实际情况的srt字幕文件,同时也支持通过读取txt纯文本文件来进行合成,在这种情况下会按每句来切分段落。 31 | 32 | 2.为了未来的扩展性和简洁性,我不得不放弃了单脚本文件的设计,即使对于下载而言更加方便。代码从现版本逐步开始重构。 33 | 34 | 3.加入一些文档说明 35 | 36 | ### 240811更新:
37 | 1.增加错误提示 38 | 2.自动检测项目路径 39 | 3.再次兼容api-v1。 40 | 4.重大功能更新:支持重新抽卡合成 41 | """ 42 | -------------------------------------------------------------------------------- /Sava_Utils/man/__init__.py: -------------------------------------------------------------------------------- 1 | import locale 2 | from .. import logger 3 | 4 | 5 | class Man: 6 | def __init__(self, language=None): 7 | if language in ["Auto", None]: 8 | language = locale.getdefaultlocale()[0] 9 | ls = dict() 10 | for x in ['README', 'changelog', 'title', 'help_custom', 'issues', 'help', 'extension_dev']: 11 | try: 12 | exec(f"from .{language} import {x}", globals(), ls) 13 | except ImportError: 14 | exec(f"from .en_US import {x}", globals(), ls) 15 | logger.info(f"Manual <{x}> does not support {language}.") 16 | self.Manual_dict = { 17 | "readme": ls["README"].README, 18 | "changelog": ls["changelog"].changelog, 19 | "title": ls["title"].title, 20 | "help_custom": ls["help_custom"].help_custom, 21 | "issues": ls["issues"].issues, 22 | "help": ls["help"].help, 23 | "extension_dev": ls["extension_dev"].extension_dev, 24 | } 25 | 26 | def getInfo(self, key): 27 | return self.Manual_dict.get(key, "") 28 | -------------------------------------------------------------------------------- /Sava_Utils/man/ja_JP/README.py: -------------------------------------------------------------------------------- 1 | README = r""" 2 | # Srt-AI-Voice-Assistant 3 | **This file is translated by AI. And just for reference.** 4 | ### このプロジェクトは、複数のAI音声合成(TTS)を使用して、字幕ファイルやテキストファイルにダビングすることができます。
また、音声/動画の文字起こしや字幕翻訳など、様々な便利な補助機能を提供します。 5 | 問題に遭遇した場合や新機能のリクエストがある場合は、[Issues](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/issues) をご利用ください。 6 | 7 | ## 機能 8 | - ✅ オープンソースで、使いやすいWebUIインターフェース。ローカルで実行でき、LAN経由でアクセス可能です。 9 | - ✅ 複数のTTSプロジェクトをサポート:BV2、GSV、CosyVoice2、AzureTTSなど、独自のAPIもカスタマイズできます! 10 | - ✅ パーソナル設定とプリセットを保存できます。 11 | - ✅ バッチモードが利用可能です。 12 | - ✅ 字幕編集機能があります。 13 | - ✅ 字幕翻訳機能を備えています。 14 | - ✅ 特定の行を再生成できます。 15 | - ✅ 複数話者によるダビングをサポートします。 16 | - ✅ 字幕を再エクスポートできます。 17 | - ✅ 拡張機能:音声/動画の字幕文字起こし 18 | - ✅ 国際化(I18n)対応 19 | 20 | ## [パッケージ版のみをダウンロード](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/releases) 21 | * 依存関係の競合やインストール問題がある場合のみ、このバージョンを使用してください。 22 | 23 | ## [GPT-SoVITS付きの統合パッケージをダウンロード(Hugging Faceから)](https://huggingface.co/YYuX/GPT-SoVITS-SAVA-windows-package/tree/main) 24 | * GPT-SoVITS統合パッケージにはパッケージ版が含まれており、組み込みモデルや事前学習モデルは削除されていません。コードは公式リポジトリと同じです。 25 | * 注意:GPT-SoVITS統合パッケージに含まれるパッケージ版は最新バージョンでない可能性があります。上書きして更新してください。 26 | """ 27 | -------------------------------------------------------------------------------- /docs/zh_CN/issues.md: -------------------------------------------------------------------------------- 1 | # 常见的错误 2 | 3 | ## 1.GPT-SoVITS错误提示404 NOT FOUND 4 | ``` 5 | /tts 404 NOT FOUND 6 | ``` 7 | * 典型的错误原因:使用了非官方标准的代码 8 | (例如刘悦的整合包,其拓展功能多来自官方项目其他人贡献的Pull Requests,且对API代码进行了~~意义不明的~~更改) 9 | * 请您确保使用了花儿不哭官方整合包或者官方仓库的最新代码。 10 | ### 解决方法: 11 | * 1.~~手动拉取官方仓库代码~~ 无视,因为该类整合包受众不会拉代码 12 | * 2.下载本项目readme里提供的整合包(稳定但更新慢) 13 | * 3.前往下面的视频链接获取花儿不哭的官方整合包 14 | ### 【不要用了半天整合包,却不知道对应项目的创始人是谁!!!】官方视频/教程/整合包指路 :[耗时两个月自主研发的低成本AI音色克隆软件,免费送给大家!【GPT-SoVITS】](https://www.bilibili.com/video/BV12g4y1m7Uw/) 15 | 16 | ## 2.目标计算机积极拒绝连接 17 | ``` 18 | 目标计算机积极拒绝连接 19 | ``` 20 | 21 | 您需要检查: 22 | * API服务是否已经启动以及正在运行? 23 | * 请等待API启动完毕后再进行操作。 24 | * 不要关闭API控制台! 25 | * 端口是否正确填写? 26 | 27 | ## 3. 400 Bad Request 28 | ``` 29 | 400 Bad Request 30 | ``` 31 | 查看本程序控制台红色错误日志,通常api会返回报错原因。 32 | 如果没有接收到任何错误信息,可反馈问题。 33 | * 典型的错误原因:参考音频在3-10秒外;填写的模型路径不存在; 34 | 35 | ## 4.音频过长,语音延迟 36 | ``` 37 | 序号合集为 ['xxx'] 的字幕由于之前的音频过长而被延迟 38 | ``` 39 | * 你的字幕时间间隔不合理。 40 | * 考虑将设置项`音频最大加速倍率`调高(大于1视为启用)并打开`去除吸气声和静音` 41 | * 设置里有最小语音间隔,默认0.3秒。以防止这种情况下语音糊在一起,若不需要可调至0。 42 | 43 | ## 5.GPT-SoVITS输出音频有时长但没有声音 44 | ``` 45 | GPT-SoVITS输出音频有时长但没有声音 46 | ``` 47 | * 你的显卡不支持半精度 48 | * 手动修改`GPT_SoVITS\configs\tts_infer.yaml`中的`is_half`为`false` -------------------------------------------------------------------------------- /Sava_Utils/man/zh_CN/issues.py: -------------------------------------------------------------------------------- 1 | issues = r""" 2 | # 常见的错误 3 | 4 | ## 1.GPT-SoVITS错误提示404 NOT FOUND 5 | ``` 6 | /tts 404 NOT FOUND 7 | ``` 8 | * 典型的错误原因:使用了非官方标准的代码 9 | (例如刘悦的整合包,其拓展功能多来自官方项目其他人贡献的Pull Requests,且对API代码进行了~~意义不明的~~更改) 10 | * 请您确保使用了花儿不哭官方整合包或者官方仓库的最新代码。 11 | ### 解决方法: 12 | * 1.~~手动拉取官方仓库代码~~ 无视,因为该类整合包受众不会拉代码 13 | * 2.下载本项目readme里提供的整合包(稳定但更新慢) 14 | * 3.前往下面的视频链接获取花儿不哭的官方整合包 15 | ### 【不要用了半天整合包,却不知道对应项目的创始人是谁!!!】官方视频/教程/整合包指路 :[耗时两个月自主研发的低成本AI音色克隆软件,免费送给大家!【GPT-SoVITS】](https://www.bilibili.com/video/BV12g4y1m7Uw/) 16 | 17 | ## 2.目标计算机积极拒绝连接 18 | ``` 19 | 目标计算机积极拒绝连接 20 | ``` 21 | 22 | 您需要检查: 23 | * API服务是否已经启动以及正在运行? 24 | * 请等待API启动完毕后再进行操作。 25 | * 不要关闭API控制台! 26 | * 端口是否正确填写? 27 | 28 | ## 3. 400 Bad Request 29 | ``` 30 | 400 Bad Request 31 | ``` 32 | 查看本程序控制台红色错误日志,通常api会返回报错原因。 33 | 如果没有接收到任何错误信息,可反馈问题。 34 | * 典型的错误原因:参考音频在3-10秒外;填写的模型路径不存在; 35 | 36 | ## 4.音频过长,语音延迟 37 | ``` 38 | 序号合集为 ['xxx'] 的字幕由于之前的音频过长而被延迟 39 | ``` 40 | * 你的字幕时间间隔不合理。 41 | * 考虑将设置项`音频最大加速倍率`调高(大于1视为启用)并打开`去除吸气声和静音` 42 | * 设置里有最小语音间隔,默认0.3秒。以防止这种情况下语音糊在一起,若不需要可调至0。 43 | 44 | ## 5.GPT-SoVITS输出音频有时长但没有声音 45 | ``` 46 | GPT-SoVITS输出音频有时长但没有声音 47 | ``` 48 | * 你的显卡不支持半精度 49 | * 手动修改`GPT_SoVITS\configs\tts_infer.yaml`中的`is_half`为`false` 50 | """ 51 | -------------------------------------------------------------------------------- /Sava_Extensions/translator/youdao/utils/AuthV4Util.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import time 3 | import uuid 4 | 5 | ''' 6 | 添加鉴权相关参数 - 7 | appKey : 应用ID 8 | salt : 随机值 9 | curtime : 当前时间戳(秒) 10 | signType : 签名版本 11 | sign : 请求签名 12 | 13 | @param appKey 您的应用ID 14 | @param appSecret 您的应用密钥 15 | @param paramsMap 请求参数表 16 | ''' 17 | def addAuthParams(appKey, appSecret, params): 18 | salt = str(uuid.uuid1()) 19 | curtime = str(int(time.time())) 20 | sign = calculateSign(appKey, appSecret, salt, curtime) 21 | params['appKey'] = appKey 22 | params['salt'] = salt 23 | params['curtime'] = curtime 24 | params['signType'] = 'v4' 25 | params['sign'] = sign 26 | 27 | 28 | ''' 29 | 计算鉴权签名 - 30 | 计算方式 : sign = sha256(appKey + input(q) + salt + curtime + appSecret) 31 | @param appKey 您的应用ID 32 | @param appSecret 您的应用密钥 33 | @param salt 随机值 34 | @param curtime 当前时间戳(秒) 35 | @return 鉴权签名sign 36 | ''' 37 | def calculateSign(appKey, appSecret, salt, curtime): 38 | strSrc = appKey + salt + curtime + appSecret 39 | return encrypt(strSrc) 40 | 41 | 42 | def encrypt(strSrc): 43 | hash_algorithm = hashlib.sha256() 44 | hash_algorithm.update(strSrc.encode('utf-8')) 45 | return hash_algorithm.hexdigest() 46 | -------------------------------------------------------------------------------- /docs/ja_JP/issues.md: -------------------------------------------------------------------------------- 1 | # 典型的な問題 2 | **This file is translated by AI. And just for reference.** 3 | ## 1. GPT - SoVITSエラー: 404 NOT FOUND 4 | ``` 5 | /tts 404 NOT FOUND 6 | ``` 7 | * このエラーの典型的な原因: 非公式標準のコードを使用している 8 | * 公式の統合パッケージまたは公式リポジトリの最新のコードを使用していることを確認してください。 9 | 10 | ### 解決策: 11 | * 公式リポジトリのコードを手動で取得してください。 12 | * READMEに記載されている統合パッケージをダウンロードしてください。(安定していますが、更新が遅い場合があります) 13 | 14 | ## 2. ターゲットマシンがアクティブに接続を拒否したため、接続を行うことができませんでした。 15 | ``` 16 | ターゲットマシンがアクティブに接続を拒否したため、接続を行うことができませんでした。 17 | ``` 18 | 以下を確認する必要があります。 19 | * APIサービスが既に起動して実行中ですか? 20 | * 操作を行う前に、APIが完全に起動するのを待ってください。 21 | * APIコンソールを閉じないでください! 22 | * ポートは正しく入力されていますか? 23 | 24 | ## 3. 400 Bad Request 25 | ``` 26 | 400 Bad Request 27 | ``` 28 | このプログラムのコンソールの赤色のエラーログを確認してください。通常、APIはエラーの原因を返します。 29 | エラーメッセージが受信されない場合は、この問題を報告してください。 30 | * 典型的なエラー原因: 参照音声が3 - 10秒の範囲外;モデルパスが存在しません。 31 | 32 | ## 4. 前の音声が長すぎるため、以下の字幕が遅延しています。 33 | ``` 34 | 前の音声が長すぎるため、以下の字幕が遅延しています。 35 | ``` 36 | * 字幕のタイミング間隔が適切ではありません。 37 | * 話す速度を上げるか、字幕のタイミング間隔を手動で増やすことを検討してください。 38 | * 設定には最小音声間隔オプションがあります(デフォルトは0.3秒)。このような場合に音声が重ならないようにするためのものです。必要なければ0に設定できます。 39 | 40 | ## 5. GPT - SoVITSの出力音声には長さがあるが、無音です。 41 | ``` 42 | GPT - SoVITSの出力音声には長さがあるが、無音です。 43 | ``` 44 | * あなたのGPUはfp - 16をサポートしていません。 45 | * `GPT_SoVITS\configs\tts_infer.yaml` の `is_half` の値を `false` に手動で変更してください。 -------------------------------------------------------------------------------- /Sava_Utils/man/ja_JP/issues.py: -------------------------------------------------------------------------------- 1 | issues = r""" 2 | # 典型的な問題 3 | **This file is translated by AI. And just for reference.** 4 | ## 1. GPT - SoVITSエラー: 404 NOT FOUND 5 | ``` 6 | /tts 404 NOT FOUND 7 | ``` 8 | * このエラーの典型的な原因: 非公式標準のコードを使用している 9 | * 公式の統合パッケージまたは公式リポジトリの最新のコードを使用していることを確認してください。 10 | 11 | ### 解決策: 12 | * 公式リポジトリのコードを手動で取得してください。 13 | * READMEに記載されている統合パッケージをダウンロードしてください。(安定していますが、更新が遅い場合があります) 14 | 15 | ## 2. ターゲットマシンがアクティブに接続を拒否したため、接続を行うことができませんでした。 16 | ``` 17 | ターゲットマシンがアクティブに接続を拒否したため、接続を行うことができませんでした。 18 | ``` 19 | 以下を確認する必要があります。 20 | * APIサービスが既に起動して実行中ですか? 21 | * 操作を行う前に、APIが完全に起動するのを待ってください。 22 | * APIコンソールを閉じないでください! 23 | * ポートは正しく入力されていますか? 24 | 25 | ## 3. 400 Bad Request 26 | ``` 27 | 400 Bad Request 28 | ``` 29 | このプログラムのコンソールの赤色のエラーログを確認してください。通常、APIはエラーの原因を返します。 30 | エラーメッセージが受信されない場合は、この問題を報告してください。 31 | * 典型的なエラー原因: 参照音声が3 - 10秒の範囲外;モデルパスが存在しません。 32 | 33 | ## 4. 前の音声が長すぎるため、以下の字幕が遅延しています。 34 | ``` 35 | 前の音声が長すぎるため、以下の字幕が遅延しています。 36 | ``` 37 | * 字幕のタイミング間隔が適切ではありません。 38 | * 話す速度を上げるか、字幕のタイミング間隔を手動で増やすことを検討してください。 39 | * 設定には最小音声間隔オプションがあります(デフォルトは0.3秒)。このような場合に音声が重ならないようにするためのものです。必要なければ0に設定できます。 40 | 41 | ## 5. GPT - SoVITSの出力音声には長さがあるが、無音です。 42 | ``` 43 | GPT - SoVITSの出力音声には長さがあるが、無音です。 44 | ``` 45 | * あなたのGPUはfp - 16をサポートしていません。 46 | * `GPT_SoVITS\configs\tts_infer.yaml` の `is_half` の値を `false` に手動で変更してください。 47 | """ 48 | -------------------------------------------------------------------------------- /docs/zh_CN/help_custom.md: -------------------------------------------------------------------------------- 1 | ## 安全警告:此功能会执行外部代码! 2 | ### 运行前请务必检查代码内容,运行不受信任的代码可能会导致电脑受到攻击! 3 | ### 作者不对此产生的后果负任何责任!! 4 | 5 | ### 将装有python函数的代码文件放在`SAVAdata/presets`下即可被调用 6 | ``` 7 | def custom_api(text):#return: audio content 8 | from gradio_client import Client 9 | client = Client("http://127.0.0.1:7860/") 10 | result = client.predict( 11 | text, # str in '输入文本内容' Textbox component 12 | "神里绫华", # str (Option from: [('神里绫华', '神里绫华')]) in 'Speaker' Dropdown component 13 | 0.1, # int | float (numeric value between 0 and 1) in 'SDP Ratio' Slider component 14 | 0.5, # int | float (numeric value between 0.1 and 2) in 'Noise' Slider component 15 | 0.5, # int | float (numeric value between 0.1 and 2) in 'Noise_W' Slider component 16 | 1, # int | float (numeric value between 0.1 and 2) in 'Length' Slider component 17 | "auto", # str (Option from: [('ZH', 'ZH'), ('JP', 'JP'), ('EN', 'EN'), ('mix', 'mix'), ('auto', 'auto')]) in 'Language' Dropdown component 18 | "", # str (filepath on your computer (or URL) of file) in 'Audio prompt' Audio component 19 | "", # str in 'Text prompt' Textbox component 20 | "", # str in 'Prompt Mode' Radio component 21 | "", # str in '辅助文本' Textbox component 22 | 0, # int | float (numeric value between 0 and 1) in 'Weight' Slider component 23 | fn_index=0 24 | ) 25 | with open(result[1],'rb') as file: 26 | data=file.read() 27 | return data 28 | ``` 29 | 以上是接入Gradio的一个示例代码,请注意:函数的输入值必须是要合成的文本`text`,返回值是音频文件的二进制内容! 30 | -------------------------------------------------------------------------------- /Sava_Utils/man/zh_CN/help_custom.py: -------------------------------------------------------------------------------- 1 | help_custom = r""" 2 | ## 安全警告:此功能会执行外部代码! 3 | ### 运行前请务必检查代码内容,运行不受信任的代码可能会导致电脑受到攻击! 4 | ### 作者不对此产生的后果负任何责任!! 5 | 6 | ### 将装有python函数的代码文件放在`SAVAdata/presets`下即可被调用 7 | ``` 8 | def custom_api(text):#return: audio content 9 | from gradio_client import Client 10 | client = Client("http://127.0.0.1:7860/") 11 | result = client.predict( 12 | text, # str in '输入文本内容' Textbox component 13 | "神里绫华", # str (Option from: [('神里绫华', '神里绫华')]) in 'Speaker' Dropdown component 14 | 0.1, # int | float (numeric value between 0 and 1) in 'SDP Ratio' Slider component 15 | 0.5, # int | float (numeric value between 0.1 and 2) in 'Noise' Slider component 16 | 0.5, # int | float (numeric value between 0.1 and 2) in 'Noise_W' Slider component 17 | 1, # int | float (numeric value between 0.1 and 2) in 'Length' Slider component 18 | "auto", # str (Option from: [('ZH', 'ZH'), ('JP', 'JP'), ('EN', 'EN'), ('mix', 'mix'), ('auto', 'auto')]) in 'Language' Dropdown component 19 | "", # str (filepath on your computer (or URL) of file) in 'Audio prompt' Audio component 20 | "", # str in 'Text prompt' Textbox component 21 | "", # str in 'Prompt Mode' Radio component 22 | "", # str in '辅助文本' Textbox component 23 | 0, # int | float (numeric value between 0 and 1) in 'Weight' Slider component 24 | fn_index=0 25 | ) 26 | with open(result[1],'rb') as file: 27 | data=file.read() 28 | return data 29 | ``` 30 | 以上是接入Gradio的一个示例代码,请注意:函数的输入值必须是要合成的文本`text`,返回值是音频文件的二进制内容! 31 | 32 | """ 33 | -------------------------------------------------------------------------------- /docs/zh_CN/README.md: -------------------------------------------------------------------------------- 1 | # Srt-AI-Voice-Assistant 2 | ### 本项目可利用多个AI-TTS为你的字幕或文本文件配音。
并提供包括字幕识别、翻译在内的多种便捷的辅助功能。 3 | 4 | ### [没有N卡?不会配置环境?点此部署一键启动镜像](https://www.compshare.cn/images/M0sem5L49kmr?referral_code=IHlncJt4RcQDdxKLEZ6pAY&ytag=GPU_YY_YX_sljxjh1011) 5 | 如遇到bug或者有什么建议,可以在 [Issues](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/issues) 上反馈 6 | 7 | ## 特性 8 | - ✅ 代码开源,界面友好,本地运行,可局域网访问 9 | - ✅ 支持多个TTS项目:BV2,GSV,IndexTTS2,CosyVoice2,AzureTTS,以及你可以自定义API! 10 | - ✅ 保存个性化设置和预设 11 | - ✅ 批量模式 12 | - ✅ 字幕编辑 13 | - ✅ 字幕批量翻译 14 | - ✅ 单句重新抽卡 15 | - ✅ 支持多角色配音 16 | - ✅ 字幕重新导出 17 | - ✅ 扩展功能:音视频字幕转录 18 | - ✅ I18n 19 | 20 | ## 安装和启动 21 | ### 用源码运行 22 | ``` 23 | git clone https://github.com/YYuX-1145/Srt-AI-Voice-Assistant.git 24 | cd Srt-AI-Voice-Assistant/ 25 | pip install -r requirements.txt 26 | python Srt-AI-Voice-Assistant.py 27 | ``` 28 | ### 可选命令行启动参数 29 | 你可以用它自定义启动行为: 30 | | 参数 | 描述 | 31 | | ----- | ----- | 32 | | `-p` | 指定启动端口 | 33 | | `--lan` | 启用局域网访问 | 34 | | `--no_ext` | 禁用全部扩展 | 35 | | `--share` | 启动时创建公网访问链接(Colab可能有用) | 36 | | `--server_mode` | 启用服务模式,禁止所有在多用户环境下可能引发冲突的功能 | 37 | 38 | **然后自己准备配置TTS引擎。Windows用户可以下载打包版或使用搭配GPT-SoVITS的整合包** 39 | 40 | **如果本项目不支持你需要的TTS项目,你可以参阅这个[文档](/docs/zh_CN/extension_dev.md)写插件。** 41 | 42 | --- 43 | 44 | ## [仅下载本体(打包版)](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/releases) 45 | * 当依赖冲突或无法正常安装时使用此版本 46 | 47 | 48 | ## [下载配合GPT-SoVITS的整合包(Hugging Face)](https://huggingface.co/YYuX/GPT-SoVITS-SAVA-windows-package/tree/main) 49 | * 整合包内预装打包版本体,内置模型不删减,训练和推理代码和官方仓库一致 50 | * 注意:包内自带的程序可能不是最新版本,覆盖掉以完成更新 -------------------------------------------------------------------------------- /Sava_Utils/man/zh_CN/README.py: -------------------------------------------------------------------------------- 1 | README = r""" 2 | # Srt-AI-Voice-Assistant 3 | ### 本项目可利用多个AI-TTS为你的字幕或文本文件配音。
并提供包括字幕识别、翻译在内的多种便捷的辅助功能。 4 | 5 | ### [没有N卡?不会配置环境?点此部署一键启动镜像](https://www.compshare.cn/images/M0sem5L49kmr?referral_code=IHlncJt4RcQDdxKLEZ6pAY&ytag=GPU_YY_YX_sljxjh1011) 6 | 如遇到bug或者有什么建议,可以在 [Issues](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/issues) 上反馈 7 | 8 | ## 特性 9 | - ✅ 代码开源,界面友好,本地运行,可局域网访问 10 | - ✅ 支持多个TTS项目:BV2,GSV,IndexTTS2,CosyVoice2,AzureTTS,以及你可以自定义API! 11 | - ✅ 保存个性化设置和预设 12 | - ✅ 批量模式 13 | - ✅ 字幕编辑 14 | - ✅ 字幕批量翻译 15 | - ✅ 单句重新抽卡 16 | - ✅ 支持多角色配音 17 | - ✅ 字幕重新导出 18 | - ✅ 扩展功能:音视频字幕转录 19 | - ✅ I18n 20 | 21 | ## 安装和启动 22 | ### 用源码运行 23 | ``` 24 | git clone https://github.com/YYuX-1145/Srt-AI-Voice-Assistant.git 25 | cd Srt-AI-Voice-Assistant/ 26 | pip install -r requirements.txt 27 | python Srt-AI-Voice-Assistant.py 28 | ``` 29 | ### 可选命令行启动参数 30 | 你可以用它自定义启动行为: 31 | | 参数 | 描述 | 32 | | ----- | ----- | 33 | | `-p` | 指定启动端口 | 34 | | `--lan` | 启用局域网访问 | 35 | | `--no_ext` | 禁用全部扩展 | 36 | | `--share` | 启动时创建公网访问链接(Colab可能有用) | 37 | | `--server_mode` | 启用服务模式,禁止所有在多用户环境下可能引发冲突的功能 | 38 | 39 | **然后自己准备配置TTS引擎。Windows用户可以下载打包版或使用搭配GPT-SoVITS的整合包** 40 | 41 | **如果本项目不支持你需要的TTS项目,你可以参阅这个[文档](/docs/zh_CN/extension_dev.md)写插件。** 42 | 43 | --- 44 | 45 | ## [仅下载本体(打包版)](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/releases) 46 | * 当依赖冲突或无法正常安装时使用此版本 47 | 48 | 49 | ## [下载配合GPT-SoVITS的整合包(Hugging Face)](https://huggingface.co/YYuX/GPT-SoVITS-SAVA-windows-package/tree/main) 50 | * 整合包内预装打包版本体,内置模型不删减,训练和推理代码和官方仓库一致 51 | * 注意:包内自带的程序可能不是最新版本,覆盖掉以完成更新 52 | """ 53 | -------------------------------------------------------------------------------- /Sava_Utils/base_component.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from . import settings 3 | 4 | class Base_Component(ABC): 5 | _instances = {} 6 | 7 | def __init__(self, name = "", title = "", config=None): 8 | """ 9 | The name parameter must not be empty for extensions. 10 | """ 11 | self.ui = False 12 | self.server_mode = False 13 | self.name = name 14 | self.title = title if title else name 15 | if config is not None: 16 | self.update_cfg(config) 17 | super().__init__() 18 | 19 | def getUI(self, *args, **kwargs): 20 | if not self.ui: 21 | self.ui = True 22 | return self._UI(*args, **kwargs) 23 | else: 24 | raise "ERR" 25 | 26 | def update_cfg(self, config:settings.Settings): 27 | """ 28 | Receive the global configuration and you can store the desired settings in class members. 29 | You can use config.query(key, default_value) to quickly access shared options. 30 | """ 31 | self.server_mode = config.server_mode 32 | 33 | def register_settings(self) -> list[settings.Shared_Option]: 34 | """ 35 | Returns a list containing settings.Shared_Option objects, to be used for registering shared configuration options. 36 | """ 37 | return [] 38 | 39 | @abstractmethod 40 | def _UI(self): 41 | """ 42 | Define UI here. 43 | """ 44 | raise NotImplementedError 45 | 46 | def __new__(cls, *args, **kwargs): 47 | if cls not in cls._instances: 48 | cls._instances[cls] = super().__new__(cls) 49 | return cls._instances[cls] 50 | -------------------------------------------------------------------------------- /docs/fr_FR/README.md: -------------------------------------------------------------------------------- 1 | # Srt-AI-Voice-Assistant 2 | ### Ce projet peut utiliser plusieurs systèmes de synthèse vocale IA pour doubler vos fichiers de sous-titres ou de texte.
Il propose également diverses fonctions auxiliaires pratiques, comme la transcription audio/vidéo et la traduction de sous-titres. 3 | Si vous rencontrez des problèmes ou souhaitez faire une demande de fonctionnalité, veuillez vous rendre sur [Issues](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/issues). 4 | ## Fonctionnalités 5 | - ✅ Open-source, interface WebUI conviviale, exécution locale et accessible via le réseau local 6 | - ✅ Prend en charge plusieurs projets TTS : BV2, GSV, CosyVoice2, AzureTTS, et vous pouvez même personnaliser vos API ! 7 | - ✅ Enregistrement de paramètres et de presets personnalisés 8 | - ✅ Mode par lots 9 | - ✅ Édition de sous-titres 10 | - ✅ Traduction de sous-titres 11 | - ✅ Régénération de lignes spécifiques 12 | - ✅ Prend en charge le doublage avec plusieurs locuteurs 13 | - ✅ Réexportation de sous-titres 14 | - ✅ Fonctions étendues : transcription de sous-titres pour audio/vidéo 15 | - ✅ I18n 16 | 17 | ## [Télécharger seulement la version empaquetée](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/releases) 18 | * Utilisez cette version seulement en cas de conflits de dépendances ou de problèmes d'installation. 19 | 20 | ## [Télécharger le package intégré avec GPT-SoVITS (Hugging Face)](https://huggingface.co/YYuX/GPT-SoVITS-SAVA-windows-package/tree/main) 21 | * Le package intégré GPT-SoVITS inclut la version empaquetée, sans supprimer aucun modèle intégré ou pré-entraîné, et son code de entraînement et inférence est identique à celui du officiel. 22 | * Note : La version empaquetée incluse dans le package intégré GPT-SoVITS peut ne pas être la version la plus récente ; remplacez-la pour la mettre à jour. -------------------------------------------------------------------------------- /docs/en_US/help_custom.md: -------------------------------------------------------------------------------- 1 | ## Security Warning: This feature will execute external code! 2 | ### Please inspect the code content before running it; executing untrusted code may put your computer at risk! 3 | ### The author bear no responsibility for any consequences! 4 | 5 | ### Place code files containing Python functions in the SAVAdata/presets directory, and they will be callable. 6 | * Here is an example code for Gradio API. 7 | ``` 8 | def custom_api(text): #return: audio content 9 | from gradio_client import Client 10 | client = Client("http://127.0.0.1:7860/") 11 | result = client.predict( 12 | text, # str in '输入文本内容' Textbox component 13 | "神里绫华", # str (Option from: [('神里绫华', '神里绫华')]) in 'Speaker' Dropdown component 14 | 0.1, # int | float (numeric value between 0 and 1) in 'SDP Ratio' Slider component 15 | 0.5, # int | float (numeric value between 0.1 and 2) in 'Noise' Slider component 16 | 0.5, # int | float (numeric value between 0.1 and 2) in 'Noise_W' Slider component 17 | 1, # int | float (numeric value between 0.1 and 2) in 'Length' Slider component 18 | "auto", # str (Option from: [('ZH', 'ZH'), ('JP', 'JP'), ('EN', 'EN'), ('mix', 'mix'), ('auto', 'auto')]) in 'Language' Dropdown component 19 | "", # str (filepath on your computer (or URL) of file) in 'Audio prompt' Audio component 20 | "", # str in 'Text prompt' Textbox component 21 | "", # str in 'Prompt Mode' Radio component 22 | "", # str in '辅助文本' Textbox component 23 | 0, # int | float (numeric value between 0 and 1) in 'Weight' Slider component 24 | fn_index=0 25 | ) 26 | with open(result[1],'rb') as file: 27 | data=file.read() 28 | return data 29 | ``` 30 | **Please note: The input value `text` of the function must be the text to be synthesized, and the return value is the binary content of the audio file!** -------------------------------------------------------------------------------- /docs/fr_FR/help_custom.md: -------------------------------------------------------------------------------- 1 | ## Security Warning: This feature will execute external code! 2 | ### Please inspect the code content before running it; executing untrusted code may put your computer at risk! 3 | ### The author bear no responsibility for any consequences! 4 | 5 | ### Place code files containing Python functions in the SAVAdata/presets directory, and they will be callable. 6 | * Here is an example code for Gradio API. 7 | ``` 8 | def custom_api(text): #return: audio content 9 | from gradio_client import Client 10 | client = Client("http://127.0.0.1:7860/") 11 | result = client.predict( 12 | text, # str in '输入文本内容' Textbox component 13 | "神里绫华", # str (Option from: [('神里绫华', '神里绫华')]) in 'Speaker' Dropdown component 14 | 0.1, # int | float (numeric value between 0 and 1) in 'SDP Ratio' Slider component 15 | 0.5, # int | float (numeric value between 0.1 and 2) in 'Noise' Slider component 16 | 0.5, # int | float (numeric value between 0.1 and 2) in 'Noise_W' Slider component 17 | 1, # int | float (numeric value between 0.1 and 2) in 'Length' Slider component 18 | "auto", # str (Option from: [('ZH', 'ZH'), ('JP', 'JP'), ('EN', 'EN'), ('mix', 'mix'), ('auto', 'auto')]) in 'Language' Dropdown component 19 | "", # str (filepath on your computer (or URL) of file) in 'Audio prompt' Audio component 20 | "", # str in 'Text prompt' Textbox component 21 | "", # str in 'Prompt Mode' Radio component 22 | "", # str in '辅助文本' Textbox component 23 | 0, # int | float (numeric value between 0 and 1) in 'Weight' Slider component 24 | fn_index=0 25 | ) 26 | with open(result[1],'rb') as file: 27 | data=file.read() 28 | return data 29 | ``` 30 | **Please note: The input value `text` of the function must be the text to be synthesized, and the return value is the binary content of the audio file!** -------------------------------------------------------------------------------- /Sava_Utils/man/fr_FR/README.py: -------------------------------------------------------------------------------- 1 | README = r""" 2 | # Srt-AI-Voice-Assistant 3 | ### Ce projet peut utiliser plusieurs systèmes de synthèse vocale IA pour doubler vos fichiers de sous-titres ou de texte.
Il propose également diverses fonctions auxiliaires pratiques, comme la transcription audio/vidéo et la traduction de sous-titres. 4 | Si vous rencontrez des problèmes ou souhaitez faire une demande de fonctionnalité, veuillez vous rendre sur [Issues](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/issues). 5 | ## Fonctionnalités 6 | - ✅ Open-source, interface WebUI conviviale, exécution locale et accessible via le réseau local 7 | - ✅ Prend en charge plusieurs projets TTS : BV2, GSV, CosyVoice2, AzureTTS, et vous pouvez même personnaliser vos API ! 8 | - ✅ Enregistrement de paramètres et de presets personnalisés 9 | - ✅ Mode par lots 10 | - ✅ Édition de sous-titres 11 | - ✅ Traduction de sous-titres 12 | - ✅ Régénération de lignes spécifiques 13 | - ✅ Prend en charge le doublage avec plusieurs locuteurs 14 | - ✅ Réexportation de sous-titres 15 | - ✅ Fonctions étendues : transcription de sous-titres pour audio/vidéo 16 | - ✅ I18n 17 | 18 | ## [Télécharger seulement la version empaquetée](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/releases) 19 | * Utilisez cette version seulement en cas de conflits de dépendances ou de problèmes d'installation. 20 | 21 | ## [Télécharger le package intégré avec GPT-SoVITS (Hugging Face)](https://huggingface.co/YYuX/GPT-SoVITS-SAVA-windows-package/tree/main) 22 | * Le package intégré GPT-SoVITS inclut la version empaquetée, sans supprimer aucun modèle intégré ou pré-entraîné, et son code de entraînement et inférence est identique à celui du officiel. 23 | * Note : La version empaquetée incluse dans le package intégré GPT-SoVITS peut ne pas être la version la plus récente ; remplacez-la pour la mettre à jour. 24 | """ 25 | -------------------------------------------------------------------------------- /docs/zh_CN/help.md: -------------------------------------------------------------------------------- 1 | # 使用指南 2 | 3 | ## 0.配置和使用服务 4 | #### 本项目可调用2个本地项目:Bert-VITS2、GPT-SoVITS 5 | #### 和1个在线项目:微软TTS 6 | * 本地项目只需要在设置页中填写并保存项目和解释器路径,或者**以更简单的方式:将程序放于整合包根目录内即可** ,然后点击第一页右下角对应按钮即可一键启动API服务! 7 | * 对于微软TTS,需要按教程注册账号并将密钥填写在设置页内。请注意每月的免费额度! 8 | 9 | ## 1.开始使用 10 | ### 本项目可以为字幕或者纯文本配音。 11 | * 当前一个字幕过长时,后一个字幕将在其后顺延。你可以在设置里设置最小语音间隔。 12 | * 对于纯文本,将按照结束标点符号和换行切割成每一条字幕。 13 | * 完成生成后,可以在编辑页面导出符合音频实际起止时间的字幕。 14 | ### A.单一说话人的情形 15 | * 1.在`字幕配音`上半页右侧上传字幕或者纯文本文件。 16 | * 2.在中间选择你的项目,调整参数。 17 | * 3.点击下方的`生成`,等待片刻。 18 | * 4.下载你的音频。 19 | 20 | ### B.多说话人的情况 21 | * 1.在`字幕配音`上半页右侧上传字幕或者纯文本文件。 22 | * 1.5. 标记模式:文件内容应如下:`说话人:内容` e.g.`淳平:对不起,让您久等了。` 23 | * 2.点击左侧文件展示下方的按钮`生成多角色项目` 24 | * 3.创建数个说话人: 25 | - a.展开位于编辑页最下方的`多角色配音`栏 26 | - b.选择目标项目 27 | - c.`在选择/创建说话人`框中,输入说话人的名字 28 | - d.调整上方对应参数。全部的参数,包括端口号将作为说话人的配置。然后点击`💾`创建说话人。同名的说话人会覆盖。 29 | * 4.在下拉列表里选中你的说话人,然后勾选对应的字幕,再点击下方的`✅`来应用说话人。你将在第4列文本看到说话人信息。 30 | * 5.上一次点击`✅`时选中的说话人会`自动`应用为`默认说话人`(仅多说话人项目生效,未指派说话人的情况下就使用默认说话人),即使你没有选择任何一条字幕。 31 | * 6.点击`生成多角色配音`,将会开始为所有指定说话人的字幕生成音频 32 | * 注:gsv的预设创建同理。在切换预设时,会自动加载模型。 33 | 34 | ### 如果对某条语音不满意? 35 | * 1.在下半编辑页中通过滑条找到目标字幕 36 | * 2.可以修改文本内容。重新抽卡完成后,字幕内容会存档。 37 | * 3.点击`🔄️`重新生成单条语音。如果你通过单说话人创建工程,在未指派说话人时,参数以当前创建工程所使用的项目的面板为准。若指派了说话人,则按说话人的参数合成。 38 | * 4.通过多说话人创建的工程必须指派说话人。 39 | * 5.在对字幕进行更改后,也可以点击`继续生成`来重新生成经过更改或未成功合成的语音。 40 | * 6.点击`重新拼接内容`,重新合成音频。 41 | 42 | ### C.历史工程的再编辑 43 | * 编辑页上侧栏的合成历史中选择对应工程,然后点击加载。 44 | * 然后应该也不用多说了吧? 45 | 46 | ### D.字幕编辑 47 | #### 1.复制 48 | * 复制选中的字幕。 49 | #### 2.删除 50 | * 删除选中的字幕。 51 | #### 3.合并 52 | * 你需要至少选择2个字幕作为合并的起点和终点。 53 | * 只有选中字幕的id的最大和最小值作为实际有效输入。 54 | #### 以上更改不会立即存档,因此可以通过重新加载当前工程来撤销操作。 55 | 56 | #### 4.更改时间码 57 | * 按srt的时间格式修改字幕的起止时间。 58 | 59 | ## 2.我遇到了无法解决的错误 60 | ### 您需要: 61 | * 详细地描述问题,并指出问题发生前,您做了哪些操作。 62 | * 推荐在评论区和[GitHub-issues](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/issues)反馈。Github-issue的模版会指引您更清晰地反馈问题。 -------------------------------------------------------------------------------- /Sava_Utils/man/en_US/help_custom.py: -------------------------------------------------------------------------------- 1 | help_custom = r""" 2 | ## Security Warning: This feature will execute external code! 3 | ### Please inspect the code content before running it; executing untrusted code may put your computer at risk! 4 | ### The author bear no responsibility for any consequences! 5 | 6 | ### Place code files containing Python functions in the SAVAdata/presets directory, and they will be callable. 7 | * Here is an example code for Gradio API. 8 | ``` 9 | def custom_api(text): #return: audio content 10 | from gradio_client import Client 11 | client = Client("http://127.0.0.1:7860/") 12 | result = client.predict( 13 | text, # str in '输入文本内容' Textbox component 14 | "神里绫华", # str (Option from: [('神里绫华', '神里绫华')]) in 'Speaker' Dropdown component 15 | 0.1, # int | float (numeric value between 0 and 1) in 'SDP Ratio' Slider component 16 | 0.5, # int | float (numeric value between 0.1 and 2) in 'Noise' Slider component 17 | 0.5, # int | float (numeric value between 0.1 and 2) in 'Noise_W' Slider component 18 | 1, # int | float (numeric value between 0.1 and 2) in 'Length' Slider component 19 | "auto", # str (Option from: [('ZH', 'ZH'), ('JP', 'JP'), ('EN', 'EN'), ('mix', 'mix'), ('auto', 'auto')]) in 'Language' Dropdown component 20 | "", # str (filepath on your computer (or URL) of file) in 'Audio prompt' Audio component 21 | "", # str in 'Text prompt' Textbox component 22 | "", # str in 'Prompt Mode' Radio component 23 | "", # str in '辅助文本' Textbox component 24 | 0, # int | float (numeric value between 0 and 1) in 'Weight' Slider component 25 | fn_index=0 26 | ) 27 | with open(result[1],'rb') as file: 28 | data=file.read() 29 | return data 30 | ``` 31 | **Please note: The input value `text` of the function must be the text to be synthesized, and the return value is the binary content of the audio file!** 32 | """ 33 | -------------------------------------------------------------------------------- /Sava_Utils/man/fr_FR/help_custom.py: -------------------------------------------------------------------------------- 1 | help_custom = r""" 2 | ## Security Warning: This feature will execute external code! 3 | ### Please inspect the code content before running it; executing untrusted code may put your computer at risk! 4 | ### The author bear no responsibility for any consequences! 5 | 6 | ### Place code files containing Python functions in the SAVAdata/presets directory, and they will be callable. 7 | * Here is an example code for Gradio API. 8 | ``` 9 | def custom_api(text): #return: audio content 10 | from gradio_client import Client 11 | client = Client("http://127.0.0.1:7860/") 12 | result = client.predict( 13 | text, # str in '输入文本内容' Textbox component 14 | "神里绫华", # str (Option from: [('神里绫华', '神里绫华')]) in 'Speaker' Dropdown component 15 | 0.1, # int | float (numeric value between 0 and 1) in 'SDP Ratio' Slider component 16 | 0.5, # int | float (numeric value between 0.1 and 2) in 'Noise' Slider component 17 | 0.5, # int | float (numeric value between 0.1 and 2) in 'Noise_W' Slider component 18 | 1, # int | float (numeric value between 0.1 and 2) in 'Length' Slider component 19 | "auto", # str (Option from: [('ZH', 'ZH'), ('JP', 'JP'), ('EN', 'EN'), ('mix', 'mix'), ('auto', 'auto')]) in 'Language' Dropdown component 20 | "", # str (filepath on your computer (or URL) of file) in 'Audio prompt' Audio component 21 | "", # str in 'Text prompt' Textbox component 22 | "", # str in 'Prompt Mode' Radio component 23 | "", # str in '辅助文本' Textbox component 24 | 0, # int | float (numeric value between 0 and 1) in 'Weight' Slider component 25 | fn_index=0 26 | ) 27 | with open(result[1],'rb') as file: 28 | data=file.read() 29 | return data 30 | ``` 31 | **Please note: The input value `text` of the function must be the text to be synthesized, and the return value is the binary content of the audio file!** 32 | """ 33 | -------------------------------------------------------------------------------- /GSV_API_launcher.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import json 4 | import time 5 | import yaml 6 | 7 | MAX_P = 3 8 | sava_config = json.load(open("SAVAdata/config.json", encoding="utf-8")) 9 | apath = "api.py" if sava_config['gsv_fallback'] else "api_v2.py" 10 | process_tab = dict() 11 | if __name__ == "__main__": 12 | os.makedirs('SAVAdata/temp', exist_ok=True) 13 | count = 0 14 | for i in [os.path.join('SAVAdata/presets', x) for x in os.listdir('SAVAdata/presets') if os.path.isdir(os.path.join('SAVAdata/presets', x))]: 15 | preset = json.load(open(os.path.join(i, 'info.json'), encoding="utf-8")) 16 | gsv_yml = { 17 | "custom": { 18 | "device": "cuda", 19 | "is_half": True, 20 | "version": "v2", 21 | "t2s_weights_path": preset["gpt_path"], 22 | "vits_weights_path": preset["sovits_path"], 23 | "cnhuhbert_base_path": "GPT_SoVITS/pretrained_models/chinese-hubert-base", 24 | "bert_base_path": "GPT_SoVITS/pretrained_models/chinese-roberta-wwm-ext-large", 25 | } 26 | } 27 | yml_temp_dir = os.path.join('SAVAdata/temp', f'{preset["name"]}.yml') 28 | with open(yml_temp_dir, 'w') as f: 29 | yaml.dump(gsv_yml, f) 30 | # launch api 31 | if preset["port"] not in process_tab: 32 | command = f""" 33 | "{sava_config['gsv_pydir']}" "{os.path.join(sava_config['gsv_dir'],apath)}" -c {os.path.abspath(yml_temp_dir)} -p {preset["port"]} 34 | """.strip() 35 | process_tab[preset["port"]] = subprocess.Popen(command, cwd=sava_config['gsv_dir'], shell=True) 36 | print(f'Run {preset["port"]}') 37 | count += 1 38 | if count >= MAX_P: 39 | break 40 | print('Launched.') 41 | while True: 42 | time.sleep(200) 43 | -------------------------------------------------------------------------------- /Sava_Utils/man/zh_CN/help.py: -------------------------------------------------------------------------------- 1 | help = r""" 2 | # 使用指南 3 | 4 | ## 0.配置和使用服务 5 | #### 本项目可调用2个本地项目:Bert-VITS2、GPT-SoVITS 6 | #### 和1个在线项目:微软TTS 7 | * 本地项目只需要在设置页中填写并保存项目和解释器路径,或者**以更简单的方式:将程序放于整合包根目录内即可** ,然后点击第一页右下角对应按钮即可一键启动API服务! 8 | * 对于微软TTS,需要按教程注册账号并将密钥填写在设置页内。请注意每月的免费额度! 9 | 10 | ## 1.开始使用 11 | ### 本项目可以为字幕或者纯文本配音。 12 | * 当前一个字幕过长时,后一个字幕将在其后顺延。你可以在设置里设置最小语音间隔。 13 | * 对于纯文本,将按照结束标点符号和换行切割成每一条字幕。 14 | * 完成生成后,可以在编辑页面导出符合音频实际起止时间的字幕。 15 | ### A.单一说话人的情形 16 | * 1.在`字幕配音`上半页右侧上传字幕或者纯文本文件。 17 | * 2.在中间选择你的项目,调整参数。 18 | * 3.点击下方的`生成`,等待片刻。 19 | * 4.下载你的音频。 20 | 21 | ### B.多说话人的情况 22 | * 1.在`字幕配音`上半页右侧上传字幕或者纯文本文件。 23 | * 1.5. 标记模式:文件内容应如下:`说话人:内容` e.g.`淳平:对不起,让您久等了。` 24 | * 2.点击左侧文件展示下方的按钮`生成多角色项目` 25 | * 3.创建数个说话人: 26 | - a.展开位于编辑页最下方的`多角色配音`栏 27 | - b.选择目标项目 28 | - c.`在选择/创建说话人`框中,输入说话人的名字 29 | - d.调整上方对应参数。全部的参数,包括端口号将作为说话人的配置。然后点击`💾`创建说话人。同名的说话人会覆盖。 30 | * 4.在下拉列表里选中你的说话人,然后勾选对应的字幕,再点击下方的`✅`来应用说话人。你将在第4列文本看到说话人信息。 31 | * 5.上一次点击`✅`时选中的说话人会`自动`应用为`默认说话人`(仅多说话人项目生效,未指派说话人的情况下就使用默认说话人),即使你没有选择任何一条字幕。 32 | * 6.点击`生成多角色配音`,将会开始为所有指定说话人的字幕生成音频 33 | * 注:gsv的预设创建同理。在切换预设时,会自动加载模型。 34 | 35 | ### 如果对某条语音不满意? 36 | * 1.在下半编辑页中通过滑条找到目标字幕 37 | * 2.可以修改文本内容。重新抽卡完成后,字幕内容会存档。 38 | * 3.点击`🔄️`重新生成单条语音。如果你通过单说话人创建工程,在未指派说话人时,参数以当前创建工程所使用的项目的面板为准。若指派了说话人,则按说话人的参数合成。 39 | * 4.通过多说话人创建的工程必须指派说话人。 40 | * 5.在对字幕进行更改后,也可以点击`继续生成`来重新生成经过更改或未成功合成的语音。 41 | * 6.点击`重新拼接内容`,重新合成音频。 42 | 43 | ### C.历史工程的再编辑 44 | * 编辑页上侧栏的合成历史中选择对应工程,然后点击加载。 45 | * 然后应该也不用多说了吧? 46 | 47 | ### D.字幕编辑 48 | #### 1.复制 49 | * 复制选中的字幕。 50 | #### 2.删除 51 | * 删除选中的字幕。 52 | #### 3.合并 53 | * 你需要至少选择2个字幕作为合并的起点和终点。 54 | * 只有选中字幕的id的最大和最小值作为实际有效输入。 55 | #### 以上更改不会立即存档,因此可以通过重新加载当前工程来撤销操作。 56 | 57 | #### 4.更改时间码 58 | * 按srt的时间格式修改字幕的起止时间。 59 | 60 | ## 2.我遇到了无法解决的错误 61 | ### 您需要: 62 | * 详细地描述问题,并指出问题发生前,您做了哪些操作。 63 | * 推荐在评论区和[GitHub-issues](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/issues)反馈。Github-issue的模版会指引您更清晰地反馈问题。 64 | """ 65 | -------------------------------------------------------------------------------- /Sava_Extensions/translator/youdao/utils/AuthV3Util.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import time 3 | import uuid 4 | 5 | ''' 6 | 添加鉴权相关参数 - 7 | appKey : 应用ID 8 | salt : 随机值 9 | curtime : 当前时间戳(秒) 10 | signType : 签名版本 11 | sign : 请求签名 12 | 13 | @param appKey 您的应用ID 14 | @param appSecret 您的应用密钥 15 | @param paramsMap 请求参数表 16 | ''' 17 | def addAuthParams(appKey, appSecret, params): 18 | q = params.get('q') 19 | if q is None: 20 | q = params.get('img') 21 | q = "".join(q) 22 | salt = str(uuid.uuid1()) 23 | curtime = str(int(time.time())) 24 | sign = calculateSign(appKey, appSecret, q, salt, curtime) 25 | params['appKey'] = appKey 26 | params['salt'] = salt 27 | params['curtime'] = curtime 28 | params['signType'] = 'v3' 29 | params['sign'] = sign 30 | 31 | 32 | def returnAuthMap(appKey, appSecret, q): 33 | salt = str(uuid.uuid1()) 34 | curtime = str(int(time.time())) 35 | sign = calculateSign(appKey, appSecret, q, salt, curtime) 36 | params = {'appKey': appKey, 37 | 'salt': salt, 38 | 'curtime': curtime, 39 | 'signType': 'v3', 40 | 'sign': sign} 41 | return params 42 | 43 | 44 | ''' 45 | 计算鉴权签名 - 46 | 计算方式 : sign = sha256(appKey + input(q) + salt + curtime + appSecret) 47 | @param appKey 您的应用ID 48 | @param appSecret 您的应用密钥 49 | @param q 请求内容 50 | @param salt 随机值 51 | @param curtime 当前时间戳(秒) 52 | @return 鉴权签名sign 53 | ''' 54 | def calculateSign(appKey, appSecret, q, salt, curtime): 55 | strSrc = appKey + getInput(q) + salt + curtime + appSecret 56 | return encrypt(strSrc) 57 | 58 | 59 | def encrypt(strSrc): 60 | hash_algorithm = hashlib.sha256() 61 | hash_algorithm.update(strSrc.encode('utf-8')) 62 | return hash_algorithm.hexdigest() 63 | 64 | 65 | def getInput(input): 66 | if input is None: 67 | return input 68 | inputLen = len(input) 69 | return input if inputLen <= 20 else input[0:10] + str(inputLen) + input[inputLen - 10:inputLen] 70 | -------------------------------------------------------------------------------- /docs/en_US/issues.md: -------------------------------------------------------------------------------- 1 | # Typical Issues 2 | ## 1. GPT-SoVITS Error: 404 NOT FOUND 3 | ``` 4 | /tts 404 NOT FOUND 5 | ``` 6 | * Typical cause of this error: Using non-official standard code 7 | * Please ensure that you are using the official integrated package or the latest code from the official repository. 8 | 9 | ### Solution: 10 | * Manually pull the official repository code. 11 | * Download the integrated package provided in README. (stable but updates may be slow) 12 | 13 | ## 2. No connection could be made because the target machine actively refused it. 14 | ``` 15 | No connection could be made because the target machine actively refused it. 16 | ``` 17 | You need to check: 18 | * Is the API service already started and running? 19 | * Please wait for the API to fully start before performing operations. 20 | * Do not close the API console! 21 | * Is the port correctly filled? 22 | 23 | ## 3. 400 Bad Request 24 | ``` 25 | 400 Bad Request 26 | ``` 27 | Check the red error logs in this program's console; usually, the API will return the cause of the error. 28 | If no error message is received, please report this issue. 29 | * Typical error cause: Reference audio outside the 3-10 second range; model path does not exist; 30 | 31 | ## 4. The following subtitles are delayed due to the previous audio being too long. 32 | ``` 33 | The following subtitles are delayed due to the previous audio being too long. 34 | ``` 35 | * Your subtitle timing intervals are not proper. 36 | * Consider increasing the value of the setting `Maximum audio acceleration ratio` (setting it to a value greater than 1 to enable the feature) and enable `Remove inhalation and silence`. 37 | * There is a minimum voice interval option in the settings (default 0.3 seconds) to prevent voices from overlapping in such cases. If not needed, it can be set to 0. 38 | 39 | ## 5. GPT-SoVITS Output Audio Has Duration But It's Silent 40 | ``` 41 | GPT-SoVITS Output Audio Has Duration But It's Silent 42 | ``` 43 | * Your GPU does not support fp-16. 44 | * Manually modify the value of `is_half` to `false` in `GPT_SoVITS\configs\tts_infer.yaml`. -------------------------------------------------------------------------------- /Sava_Utils/i18nAuto/translations/en_US.py: -------------------------------------------------------------------------------- 1 | i18n_dict = { 2 | # GSV 3 | "DICT_LANGUAGE": { 4 | "Chinese": "all_zh", 5 | "Cantonese": "all_yue", 6 | "English": "en", 7 | "Japanese": "all_ja", 8 | "Korean": "all_ko", 9 | "Chinese-English Mix": "zh", 10 | "Cantonese-English Mix": "yue", 11 | "Japanese-English Mix": "ja", 12 | "Korean-English Mix": "ko", 13 | "Multi-Language Mix": "auto", 14 | "Multi-Language Mix (Cantonese)": "auto_yue", 15 | }, 16 | "CUT_METHOD": { 17 | "No cutting": "cut0", 18 | "Slice once every 4 sentences": "cut1", 19 | "Slice per 50 characters": "cut2", 20 | "Slice by Chinese punct": "cut3", 21 | "Slice by English punct": "cut4", 22 | "Slice by every punct": "cut5", 23 | }, 24 | # MSTTS 25 | "MSTTS_NOTICE": """Microsoft TTS needs Internet Connection. You should fill in your key and specify the server region before gengerating audios. Please pay attention to the monthly free quota.
[【To Get Your Key】](https://learn.microsoft.com/en-US/azure/ai-services/speech-service/get-started-text-to-speech)""", 26 | # Subtitle Translation 27 | "OLLAMA_NOTICE": "⚠️LLMs use much VRAM while they're running and do not forget to select and unload the corresponding model after usage in order to free up VRAM.", 28 | # Polyphone Editor 29 | "POLYPHONE_NOTICE": "⚠️This feature allows you to modify the polyphonic character configuration of GPT-SoVITS. Changes will take effect after saving and restarting the API.⚠️", 30 | # Settings 31 | "ext_safety_notice": "⚠️Extensions can execute arbitrary code. Please install only from trusted sources or carefully review the source code. Malicious extensions may disrupt the application and even do harm to your computer!⚠️", 32 | # EXTENSIONS 33 | # WAV2SRT 34 | "WAV2SRT_INFO": """ 35 | ### This function can be directly used in the GPT-SoVITS integrated package and ffmpeg is required; otherwise, you need to install the corresponding dependencies yourself. 36 | """, 37 | } 38 | -------------------------------------------------------------------------------- /Sava_Utils/man/en_US/issues.py: -------------------------------------------------------------------------------- 1 | issues = r""" 2 | # Typical Issues 3 | ## 1. GPT-SoVITS Error: 404 NOT FOUND 4 | ``` 5 | /tts 404 NOT FOUND 6 | ``` 7 | * Typical cause of this error: Using non-official standard code 8 | * Please ensure that you are using the official integrated package or the latest code from the official repository. 9 | 10 | ### Solution: 11 | * Manually pull the official repository code. 12 | * Download the integrated package provided in README. (stable but updates may be slow) 13 | 14 | ## 2. No connection could be made because the target machine actively refused it. 15 | ``` 16 | No connection could be made because the target machine actively refused it. 17 | ``` 18 | You need to check: 19 | * Is the API service already started and running? 20 | * Please wait for the API to fully start before performing operations. 21 | * Do not close the API console! 22 | * Is the port correctly filled? 23 | 24 | ## 3. 400 Bad Request 25 | ``` 26 | 400 Bad Request 27 | ``` 28 | Check the red error logs in this program's console; usually, the API will return the cause of the error. 29 | If no error message is received, please report this issue. 30 | * Typical error cause: Reference audio outside the 3-10 second range; model path does not exist; 31 | 32 | ## 4. The following subtitles are delayed due to the previous audio being too long. 33 | ``` 34 | The following subtitles are delayed due to the previous audio being too long. 35 | ``` 36 | * Your subtitle timing intervals are not proper. 37 | * Consider increasing the value of the setting `Maximum audio acceleration ratio` (setting it to a value greater than 1 to enable the feature) and enable `Remove inhalation and silence`. 38 | * There is a minimum voice interval option in the settings (default 0.3 seconds) to prevent voices from overlapping in such cases. If not needed, it can be set to 0. 39 | 40 | ## 5. GPT-SoVITS Output Audio Has Duration But It's Silent 41 | ``` 42 | GPT-SoVITS Output Audio Has Duration But It's Silent 43 | ``` 44 | * Your GPU does not support fp-16. 45 | * Manually modify the value of `is_half` to `false` in `GPT_SoVITS\configs\tts_infer.yaml`. 46 | """ 47 | -------------------------------------------------------------------------------- /Sava_Extensions/translator/youdao/utils/WebSocketUtil.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import threading 3 | import urllib.parse 4 | 5 | import websocket 6 | 7 | """ 8 | 初始化websocket连接 9 | """ 10 | def init_connection(url): 11 | ws = websocket.WebSocketApp(url, on_open=ClientThread.on_open, on_message=ClientThread.on_message, 12 | on_close=ClientThread.on_closed, on_error=ClientThread.on_error) 13 | # 异步监听返回结果 14 | client = ClientThread(ws=ws) 15 | client.start() 16 | return client 17 | 18 | 19 | """ 20 | 初始化websocket连接, 并附带相关参数 21 | """ 22 | def init_connection_with_params(url, params): 23 | url_prams_builder = urllib.parse.urlencode(params) 24 | url = url + '?' + url_prams_builder 25 | return init_connection(url) 26 | 27 | 28 | """ 29 | 发送text message 30 | """ 31 | def send_text_message(ws, message): 32 | ws.send(message) 33 | print("send text message: " + message) 34 | 35 | 36 | """ 37 | 发送binary message 38 | """ 39 | def send_binary_message(ws, message): 40 | ws.send(message, websocket.ABNF.OPCODE_BINARY) 41 | print("send binary message length: " + str(len(message))) 42 | 43 | 44 | class ClientThread(threading.Thread): 45 | def __init__(self, ws): 46 | threading.Thread.__init__(self) 47 | self.ws = ws 48 | ws.is_connect = False 49 | 50 | def run(self): 51 | self.ws.run_forever() 52 | 53 | def return_is_connect(self): 54 | return self.ws.is_connect 55 | 56 | def on_message(ws, message): 57 | print("received message: " + message) 58 | # 该判断方式仅用作demo展示, 生产环境请使用json解析 59 | if "\"errorCode\":\"0\"" not in message: 60 | sys.exit() 61 | 62 | def on_open(ws): 63 | print("connection open") 64 | ws.is_connect = True 65 | 66 | def on_closed(ws, close_status_code, close_msg): 67 | if not close_status_code: 68 | close_status_code = 'None' 69 | if not close_msg: 70 | close_msg = 'None' 71 | print("connection closed, code: " + close_status_code + ", reason: " + close_msg) 72 | 73 | def on_error(ws, error): 74 | print(error) -------------------------------------------------------------------------------- /Sava_Utils/translator/__init__.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from ..base_component import Base_Component 3 | import re 4 | 5 | 6 | class Traducteur(Base_Component): 7 | def __init__(self, name, config=None): 8 | self.name = name 9 | super().__init__(name, config=config) 10 | 11 | def update_cfg(self, config): 12 | super().update_cfg(config) 13 | 14 | def construct_tasks(self, subtitles, batch_size: int = 1): 15 | """ 16 | By default, tasks are grouped into batches. 17 | 18 | Example with batch_size = 2: 19 | Subtitle 1: Hello! 20 | Subtitle 2: 你好! 21 | Subtitle 3: Bonjour! 22 | 23 | The returned value will look like: 24 | [['Hello!', '你好!'], ['Bonjour!']] 25 | """ 26 | tasks: list[list[str]] = [[]] 27 | for idx, item in enumerate(subtitles): 28 | tasks[-1].append(re.sub(r'\n+', '\n', item.text).strip()) 29 | if (idx + 1) % batch_size == 0: 30 | tasks.append([]) 31 | if len(tasks[-1]) == 0: 32 | tasks.pop(-1) 33 | return tasks 34 | 35 | @abstractmethod 36 | def api(self, tasks, target_lang, interrupt_flag, *args, file_name: str = "", **kwargs) -> list[str] | tuple[list[str], str]: 37 | """ 38 | This method must be implemented by subclasses. 39 | It returns a list of translated subtitle strings. 40 | 41 | Parameters: 42 | tasks: The result from self.construct_tasks(). 43 | target_lang: The language to translate into. 44 | interrupt_flag: A control object that allows the task to be cancelled. 45 | Check interrupt_flag.is_set() periodically during long-running operations. 46 | *args: Additional arguments from self._UI(), typically user inputs like API keys or options. 47 | file_name: (Optional) You can use it to show a tqdm progress bar labeled with the file name. 48 | **kwargs: Other optional keyword arguments. 49 | 50 | Returns: 51 | A list of translated strings, or a tuple (translated_list, message). 52 | """ 53 | raise NotImplementedError 54 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: "Issue report" 2 | description: Something is not working as expected. 3 | body: 4 | - type: checkboxes 5 | attributes: 6 | label: Self-Checks 7 | options: 8 | - label: I have filled in a clear title that allows others to understand the core issue at a glance, rather than using vague ones like "What's going on", "Error occurs", or "Stuck". | 我已填写了一个清晰的标题,使他人能够一眼看出遇到的问题,而不是使用诸如“这怎么办”、“大佬救命”、“报错”这类模糊的标题。 9 | required: true 10 | - label: I have already read the [documentation](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/tree/main/docs) and couldn't find any useful information to solve my problem. | 我已经阅读完[内置说明](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/tree/main/docs/zh_CN),但仍无法解决问题。 11 | required: true 12 | - label: I have searched for the [existing issues](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/issues?q=is%3Aissue). | 我已经查看过[现有的issue](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/issues?q=is%3Aissue)。 13 | required: true 14 | 15 | - type: textarea 16 | attributes: 17 | label: Describe the problem 18 | description: A clear and concise description of the problem. | 请清晰地描述遇到的问题。 19 | validations: 20 | required: true 21 | 22 | - type: textarea 23 | attributes: 24 | label: System Info 25 | description: OS and any relevant environments? Which TTS project are you using? And its version? | 您的系统环境?您正在使用哪一个TTS项目?以及它的版本? 26 | placeholder: e.g. I'm using the integrated package./I'm running it and using GPT-SoVITS-v3 on WSL2 with Python 3.12. | 我在使用本项目提供的整合包/我在wsl2下使用GSV并用python3.12运行本项目 27 | validations: 28 | required: false 29 | 30 | - type: textarea 31 | attributes: 32 | label: How To Reproduce | 请您提供问题的复现方法 33 | description: Include detailed steps, screenshots(This should both include the console of this application and corresponding API). | 请提供复现问题的步骤,并提供本项目控制台报错截图和对应TTS项目的API的控制台截图。 34 | validations: 35 | required: true 36 | 37 | - type: textarea 38 | attributes: 39 | label: (Optional) Additional context 40 | placeholder: Add any other context about the problem here. | (可选)补充说明 41 | validations: 42 | required: false 43 | -------------------------------------------------------------------------------- /docs/fr_FR/issues.md: -------------------------------------------------------------------------------- 1 | # Problèmes typiques 2 | ## 1. Erreur GPT-SoVITS : 404 NOT FOUND 3 | ``` 4 | /tts 404 NOT FOUND 5 | ``` 6 | * Cause typique de cette erreur : Utilisation de code non officiel standard. 7 | * Veuillez vous assurer que vous utilisez le package intégré officiel ou le code le plus récent du dépôt officiel. 8 | 9 | ### Solution : 10 | * Téléchargez manuellement le code du dépôt officiel. 11 | * Téléchargez le package intégré fourni dans le README. (stable mais les mises à jour peuvent être lentes) 12 | 13 | ## 2. Impossible d'établir de connexion car l'ordinateur cible a expressément refusé celle-ci. 14 | ``` 15 | Impossible d'établir de connexion car l'ordinateur cible a expressément refusé celle-ci. 16 | ``` 17 | Vous devez vérifier : 18 | * Le service API est-il déjà démarré et en cours d'exécution ? 19 | * Veuillez attendre que l'API soit entièrement démarrée avant d'effectuer des opérations. 20 | * Ne fermez pas la console de l'API ! 21 | * Le port est-il correctement renseigné ? 22 | 23 | ## 3. 400 Bad Request 24 | ``` 25 | 400 Bad Request 26 | ``` 27 | Vérifiez les journaux d'erreur en rouge dans la console de ce programme ; généralement, l'API renverra la cause de l'erreur. 28 | Si aucun message d'erreur n'est reçu, veuillez signaler ce problème. 29 | * Cause d'erreur typique : Audio de référence en dehors de la plage de 3 à 10 secondes ; le chemin du modèle n'existe pas. 30 | 31 | ## 4. Les sous-titres suivants sont retardés en raison de la longueur excessive de l'audio précédent. 32 | ``` 33 | Les sous-titres suivants sont retardés en raison de la longueur excessive de l'audio précédent. 34 | ``` 35 | * Vos intervalles de temps des sous-titres ne sont pas appropriés. 36 | * Considérez d'augmenter la valeur de la configuration ` rapport maximal d'accélération audio`(en la fixant à 37 | une valeur supérieure à 1 pour activer la fonction) et activez `Supprimer l'inhalation et le silence`. 38 | * Il existe une option d'intervalle vocal minimum dans les paramètres (par défaut 0,3 seconde) pour éviter que les voix ne se chevauchent dans de tels cas. Si cela n'est pas nécessaire, il peut être égal 0. 39 | 40 | ## 5. Le fichier audio de sortie de GPT-SoVITS a une durée mais est silencieux. 41 | ``` 42 | Le fichier audio de sortie de GPT-SoVITS a une durée mais est silencieux. 43 | ``` 44 | * Votre carte graphique ne supporte pas le fp-16. 45 | * Modifiez manuellement la valeur de `is_half` en `false` dans `GPT_SoVITS\configs\tts_infer.yaml`. -------------------------------------------------------------------------------- /Sava_Utils/man/fr_FR/issues.py: -------------------------------------------------------------------------------- 1 | issues = r""" 2 | # Problèmes typiques 3 | ## 1. Erreur GPT-SoVITS : 404 NOT FOUND 4 | ``` 5 | /tts 404 NOT FOUND 6 | ``` 7 | * Cause typique de cette erreur : Utilisation de code non officiel standard. 8 | * Veuillez vous assurer que vous utilisez le package intégré officiel ou le code le plus récent du dépôt officiel. 9 | 10 | ### Solution : 11 | * Téléchargez manuellement le code du dépôt officiel. 12 | * Téléchargez le package intégré fourni dans le README. (stable mais les mises à jour peuvent être lentes) 13 | 14 | ## 2. Impossible d'établir de connexion car l'ordinateur cible a expressément refusé celle-ci. 15 | ``` 16 | Impossible d'établir de connexion car l'ordinateur cible a expressément refusé celle-ci. 17 | ``` 18 | Vous devez vérifier : 19 | * Le service API est-il déjà démarré et en cours d'exécution ? 20 | * Veuillez attendre que l'API soit entièrement démarrée avant d'effectuer des opérations. 21 | * Ne fermez pas la console de l'API ! 22 | * Le port est-il correctement renseigné ? 23 | 24 | ## 3. 400 Bad Request 25 | ``` 26 | 400 Bad Request 27 | ``` 28 | Vérifiez les journaux d'erreur en rouge dans la console de ce programme ; généralement, l'API renverra la cause de l'erreur. 29 | Si aucun message d'erreur n'est reçu, veuillez signaler ce problème. 30 | * Cause d'erreur typique : Audio de référence en dehors de la plage de 3 à 10 secondes ; le chemin du modèle n'existe pas. 31 | 32 | ## 4. Les sous-titres suivants sont retardés en raison de la longueur excessive de l'audio précédent. 33 | ``` 34 | Les sous-titres suivants sont retardés en raison de la longueur excessive de l'audio précédent. 35 | ``` 36 | * Vos intervalles de temps des sous-titres ne sont pas appropriés. 37 | * Considérez d'augmenter la valeur de la configuration ` rapport maximal d'accélération audio`(en la fixant à 38 | une valeur supérieure à 1 pour activer la fonction) et activez `Supprimer l'inhalation et le silence`. 39 | * Il existe une option d'intervalle vocal minimum dans les paramètres (par défaut 0,3 seconde) pour éviter que les voix ne se chevauchent dans de tels cas. Si cela n'est pas nécessaire, il peut être égal 0. 40 | 41 | ## 5. Le fichier audio de sortie de GPT-SoVITS a une durée mais est silencieux. 42 | ``` 43 | Le fichier audio de sortie de GPT-SoVITS a une durée mais est silencieux. 44 | ``` 45 | * Votre carte graphique ne supporte pas le fp-16. 46 | * Modifiez manuellement la valeur de `is_half` en `false` dans `GPT_SoVITS\configs\tts_infer.yaml`. 47 | """ 48 | -------------------------------------------------------------------------------- /docs/fr_FR/changelog.md: -------------------------------------------------------------------------------- 1 | ## Journal des modifications 2 | 3 | ### Mise à jour V4-0325 : 4 | #### Afin de rendre les versions plus claires, des numéros de version sont attribués plus des dates de publication. 5 | #### Après cette mise à jour, l'historique de synthèse et les locuteurs enregistrés de la version précédente doivent être recréés ; sinon, des erreurs peuvent se produire ! 6 | 1. Édition des sous-titres 7 | 2. Traduction des sous-titres 8 | 3. Amélioration de divers détails et correction de erreurs 9 | 4. Supporter CosyVoice2 (réutilisation du panneau GSV) 10 | 5. (4.0.1) Mode par lots 11 | 6. (4.1) Mode serveur 12 | 7. (4.2) I18n 13 | 8. (4.3) Accélération automatique de l'audio et suppression du silence; Création de projets de doublage à plusieurs locuteurs à partir de textes étiquetés. 14 | 9. (4.3.1) Ajouter la fonction de Recherche et de Remplacement; ajouter un bouton de régénération en un clic. 15 | 10. (4.4) Permet l'édition des caractères polyphoniques pour GPT-SoVITS ainsi que la détection automatique des modèles; Autorise les invites personnalisées pour Ollama; Permet d'exporter des sous-titres avec les noms des locuteurs selon un modèle personnalisable. 16 | 11.(4.5) Le module de traduction permet de fusionner des sous-titres bilingues; le module de transcription audio-vidéo prend en charge le modèle de séparation vocale UVR et une fonction de fusion vidéo en un clic a été ajoutée. 17 | 18 | ### Mise à jour du 140225 : 19 | 1. Prise en charge de la lecture de projets historiques 20 | 2. Prise en charge du doublage avec plusieurs locuteurs 21 | 22 | ### Mise à jour du 230125 : 23 | 1. Prise en charge de la réexportation de fichiers de sous-titres SRT correspondant aux horodatages de début et de fin réels après synthèse ; prise en charge également de la lecture de fichiers texte TXT pour la synthèse, auquel cas les paragraphes sont divisés par phrases. 24 | 2. Afin d'améliorer l'extensibilité à l'avenir et la simplicité, la conception d'un fichier de script unique, qui rendait les téléchargements plus pratiques, a dû être abandonnée. Le code sera refactorisé progressivement à partir de cette version. 25 | 3. Ajout de certaines documentations. 26 | 27 | ### Mise à jour du 110824 : 28 | 1. Notification des utilisateurs du message d'erreur 29 | 2. Détection automatique des environnements TTS-Project 30 | 3. Restauration de la compatibilité avec l'api-v1 31 | 4. Une mise à jour majeure de fonctionnalité : La régénération de lignes spécifiques si vous n'êtes pas satisfaites d'elles. -------------------------------------------------------------------------------- /Sava_Utils/man/fr_FR/changelog.py: -------------------------------------------------------------------------------- 1 | changelog = r""" 2 | ## Journal des modifications 3 | 4 | ### Mise à jour V4-0325 : 5 | #### Afin de rendre les versions plus claires, des numéros de version sont attribués plus des dates de publication. 6 | #### Après cette mise à jour, l'historique de synthèse et les locuteurs enregistrés de la version précédente doivent être recréés ; sinon, des erreurs peuvent se produire ! 7 | 1. Édition des sous-titres 8 | 2. Traduction des sous-titres 9 | 3. Amélioration de divers détails et correction de erreurs 10 | 4. Supporter CosyVoice2 (réutilisation du panneau GSV) 11 | 5. (4.0.1) Mode par lots 12 | 6. (4.1) Mode serveur 13 | 7. (4.2) I18n 14 | 8. (4.3) Accélération automatique de l'audio et suppression du silence; Création de projets de doublage à plusieurs locuteurs à partir de textes étiquetés. 15 | 9. (4.3.1) Ajouter la fonction de Recherche et de Remplacement; ajouter un bouton de régénération en un clic. 16 | 10. (4.4) Permet l'édition des caractères polyphoniques pour GPT-SoVITS ainsi que la détection automatique des modèles; Autorise les invites personnalisées pour Ollama; Permet d'exporter des sous-titres avec les noms des locuteurs selon un modèle personnalisable. 17 | 11.(4.5) Le module de traduction permet de fusionner des sous-titres bilingues; le module de transcription audio-vidéo prend en charge le modèle de séparation vocale UVR et une fonction de fusion vidéo en un clic a été ajoutée. 18 | 19 | ### Mise à jour du 140225 : 20 | 1. Prise en charge de la lecture de projets historiques 21 | 2. Prise en charge du doublage avec plusieurs locuteurs 22 | 23 | ### Mise à jour du 230125 : 24 | 1. Prise en charge de la réexportation de fichiers de sous-titres SRT correspondant aux horodatages de début et de fin réels après synthèse ; prise en charge également de la lecture de fichiers texte TXT pour la synthèse, auquel cas les paragraphes sont divisés par phrases. 25 | 2. Afin d'améliorer l'extensibilité à l'avenir et la simplicité, la conception d'un fichier de script unique, qui rendait les téléchargements plus pratiques, a dû être abandonnée. Le code sera refactorisé progressivement à partir de cette version. 26 | 3. Ajout de certaines documentations. 27 | 28 | ### Mise à jour du 110824 : 29 | 1. Notification des utilisateurs du message d'erreur 30 | 2. Détection automatique des environnements TTS-Project 31 | 3. Restauration de la compatibilité avec l'api-v1 32 | 4. Une mise à jour majeure de fonctionnalité : La régénération de lignes spécifiques si vous n'êtes pas satisfaites d'elles. 33 | """ 34 | -------------------------------------------------------------------------------- /docs/en_US/changelog.md: -------------------------------------------------------------------------------- 1 | ## ChangeLog 2 | ### V4.6.1 Key Updates: 3 | 1. You can now customize the TTS/translation APIs and extensions with user interfaces. Bert-VITS2 and the old custom API module will become official extensions and be disabled by default. The settings page layout has been adjusted accordingly. For extension development, please refer to the documentation, this project’s source code, and existing extensions. 4 | 2. Automatic speech speed now includes slow-down ratio adjustment. 5 | 3. Added advanced scripting. 6 | * Note: After this update, all speakers from previous versions will be deprecated. Sorry for any inconvenience caused by this update. 7 | ### V4-2503 Update: 8 | #### To make versions more clear, version are assigned in addition to release dates. 9 | #### After this update, the synthesis history and saved speakers from the previous version need to be recreated; otherwise, errors may occur! 10 | 1. Subtitle editing 11 | 2. Subtitle translation 12 | 3. Various details improved and bugs fixed 13 | 4. Supports CosyVoice2 (reusing GSV panel) 14 | 5. (4.0.1) Batch mode 15 | 6. (4.1) Server mode 16 | 7. (4.2) I18n 17 | 8. (4.3) Automatic audio acceleration & silence removing; Creating multi-speaker dubbing project from labeled texts. 18 | 9. (4.3.1) Add Find and Replace; add a one-click regeneration button. 19 | 10. (4.4) Polyphone editing for GPT-SoVITS and automatic model detection; Allow custom prompt for Ollama; Export subtitles with speaker names using customizable templates 20 | 11.(4.5) The translation module now supports merging bilingual subtitles; The audio-video transcription module adds support for the UVR vocal separation model and Video merging. 21 | 22 | ### 250214 Update: 23 | 1. Supports reading historical projects 24 | 2. Supports multi-speaker dubbing 25 | 26 | ### 250123 Update: 27 | 1. Supports re-export SRT subtitle files that match the actual start and end timestamps after synthesis; also supports reading TXT text files for synthesis, in which case paragraphs are split by sentences. 28 | 2. To enhance expandability in the future and simplicity, the design of a single script file, which makes downloads more convenient, had to be abandoned. The code will be refactored step by step starting from this version. 29 | 3. Added some documentations. 30 | 31 | ### 240811 Update: 32 | 1. Notifies users of the error message 33 | 2. Automatic detection of TTS-Project envs 34 | 3. Compatibility with api-v1 restored 35 | 4. A major feature update: Support regenerating specific lines if you're not satisfied with them. -------------------------------------------------------------------------------- /docs/ja_JP/help.md: -------------------------------------------------------------------------------- 1 | # ユーザーガイド 2 | **This file is translated by AI. And just for reference.** 3 | ## 0. サービスの設定と使用方法 4 | #### このプロジェクトは、2つのローカルプロジェクト(Bert-VITS2、GPT-SoVITS)と1つのオンラインサービス(Microsoft TTS)を呼び出すことができます。 5 | * **ローカルのTTSプロジェクトについて**: 6 | * 設定ページでプロジェクトのルートパスと対応するPythonインタープリターのパスを入力して保存します。 7 | * **簡単な方法**:プログラムを統合パッケージのルートディレクトリに配置し、最初のページの対応するボタンをクリックしてAPIサービスを起動します! 8 | 9 | * **Microsoft TTSについて**: 10 | * チュートリアルに従ってアカウントを登録し、設定ページにAPIキーを入力します。 11 | * 月次の無料クォータに注意してください! 12 | 13 | ## 1. 使い始める 14 | ### このプロジェクトは、字幕または平文の吹き替えをサポートしています。 15 | * **字幕の場合**: 16 | * 実際に有効に使用されるのは開始時間のみです。字幕が長すぎる場合、後続の字幕はそれに応じて遅延します。また、設定で最小音声間隔を設定することができます。 17 | 18 | * **平文の場合**: 19 | * テキストは、終了句読点と改行に基づいて字幕エントリに分割されます。 20 | 21 | * 生成後、編集ページで実際の音声タイムスタンプ付きの字幕をエクスポートすることができます。 22 | 23 | ### A. 単一話者のシナリオ 24 | * **I.** `字幕吹き替え`ページの右パネルで字幕またはテキストファイルをアップロードします。 25 | * **II.** 中央のパネルでプロジェクトを選択し、パラメータを調整します。 26 | * **III.** 下部の`音声を生成`ボタンをクリックして待ちます。 27 | * **IV.** 音声をダウンロードします。 28 | 29 | ### B. 複数話者のシナリオ 30 | * **I.** `字幕吹き替え`の右パネルで字幕/テキストファイルをアップロードします。 31 | * **II.** ファイル表示の下にある`複数話者吹き替えプロジェクトを作成`をクリックします。 32 | * **III.** 話者を作成します: 33 | * **a.** 編集ページの下部にある「複数話者吹き替え」セクションを展開します。 34 | * **b.** ターゲットプロジェクトを選択します。 35 | * **c.** 「話者を選択/作成」ボックスに話者名を入力します。 36 | * **d.** パラメータ(ポート番号を含む)を調整し、💾をクリックして保存します。重複する名前は既存の話者を上書きします。 37 | * **IV.** ドロップダウンから話者を選択し、対応する字幕にチェックを入れてから、✅をクリックして適用します。話者情報が4列目に表示されます。 38 | * **V.** 最後に割り当てられた話者がデフォルトの話者になります(複数話者プロジェクトで割り当てられていない字幕に適用されます)。 39 | * **VI.** `複数話者吹き替えを生成`をクリックして生成を開始します。 40 | 41 | ### 特定の行を再生成する 42 | * **I.** 編集ページのスライダーを使用してターゲット字幕を見つけます。 43 | * **II.** 必要に応じてテキストを変更します。再生成後、変更内容は自動的に保存されます。 44 | * **III.** 🔄をクリックして1行を再生成します: 45 | * 割り当てられていない場合は、プロジェクトのパラメータを使用します。 46 | * 割り当てられている場合は、話者固有のパラメータを使用します。 47 | * 複数話者プロジェクトでは、話者を割り当てる必要があります。 48 | * **IV.** `音声を再組み立て`をクリックして、完全な音声を再構成します。 49 | 50 | ### C. 過去のプロジェクトを再編集する 51 | * 上部パネルの合成履歴からプロジェクトを選択し、`読み込み`ボタンをクリックします。 52 | * 残りの手順は自明です。 53 | 54 | ### D. 字幕編集 55 | #### 1. コピー 56 | * 選択した字幕をコピーします。 57 | 58 | #### 2. 削除 59 | * 選択した字幕を削除します。 60 | 61 | #### 3. マージ 62 | * 少なくとも2つの字幕を開始/終了点として選択します。 63 | * 開始点から終了点までの字幕がマージされます。 64 | 65 | ⚠️ 変更はすぐにディスクに自動保存されないため、プロジェクトを再読み込みすることで元に戻すことができます。 66 | 67 | #### 4. タイムスタンプを変更する 68 | * SRT形式で開始/終了時間を編集します。 69 | * `タイムスタンプを適用`をクリックして変更を保存します。 70 | 71 | ⚠️ 適用されていない変更は、ナビゲーション中に失われます。 72 | 73 | ## 2. トラブルシューティング 74 | * 問題を報告する際は、問題を詳細に説明し、エラーが発生する前に行った手順を列挙してください。 75 | * [GitHub-issues](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/issues)にアクセスして、問題を報告またはヘルプを求めてください(Issueテンプレートが適切な報告方法をガイドします)。 -------------------------------------------------------------------------------- /Sava_Utils/man/en_US/changelog.py: -------------------------------------------------------------------------------- 1 | changelog = r""" 2 | ## ChangeLog 3 | ### V4.6.1 Key Updates: 4 | 1. You can now customize the TTS/translation APIs and extensions with user interfaces. Bert-VITS2 and the old custom API module will become official extensions and be disabled by default. The settings page layout has been adjusted accordingly. For extension development, please refer to the documentation, this project’s source code, and existing extensions. 5 | 2. Automatic speech speed now includes slow-down ratio adjustment. 6 | 3. Added advanced scripting. 7 | * Note: After this update, all speakers from previous versions will be deprecated. Sorry for any inconvenience caused by this update. 8 | ### V4-2503 Update: 9 | #### To make versions more clear, version are assigned in addition to release dates. 10 | #### After this update, the synthesis history and saved speakers from the previous version need to be recreated; otherwise, errors may occur! 11 | 1. Subtitle editing 12 | 2. Subtitle translation 13 | 3. Various details improved and bugs fixed 14 | 4. Supports CosyVoice2 (reusing GSV panel) 15 | 5. (4.0.1) Batch mode 16 | 6. (4.1) Server mode 17 | 7. (4.2) I18n 18 | 8. (4.3) Automatic audio acceleration & silence removing; Creating multi-speaker dubbing project from labeled texts. 19 | 9. (4.3.1) Add Find and Replace; add a one-click regeneration button. 20 | 10. (4.4) Polyphone editing for GPT-SoVITS and automatic model detection; Allow custom prompt for Ollama; Export subtitles with speaker names using customizable templates 21 | 11.(4.5) The translation module now supports merging bilingual subtitles; The audio-video transcription module adds support for the UVR vocal separation model and Video merging. 22 | 23 | ### 250214 Update: 24 | 1. Supports reading historical projects 25 | 2. Supports multi-speaker dubbing 26 | 27 | ### 250123 Update: 28 | 1. Supports re-export SRT subtitle files that match the actual start and end timestamps after synthesis; also supports reading TXT text files for synthesis, in which case paragraphs are split by sentences. 29 | 2. To enhance expandability in the future and simplicity, the design of a single script file, which makes downloads more convenient, had to be abandoned. The code will be refactored step by step starting from this version. 30 | 3. Added some documentations. 31 | 32 | ### 240811 Update: 33 | 1. Notifies users of the error message 34 | 2. Automatic detection of TTS-Project envs 35 | 3. Compatibility with api-v1 restored 36 | 4. A major feature update: Support regenerating specific lines if you're not satisfied with them. 37 | """ 38 | -------------------------------------------------------------------------------- /Sava_Utils/man/ja_JP/help.py: -------------------------------------------------------------------------------- 1 | help = r""" 2 | # ユーザーガイド 3 | **This file is translated by AI. And just for reference.** 4 | ## 0. サービスの設定と使用方法 5 | #### このプロジェクトは、2つのローカルプロジェクト(Bert-VITS2、GPT-SoVITS)と1つのオンラインサービス(Microsoft TTS)を呼び出すことができます。 6 | * **ローカルのTTSプロジェクトについて**: 7 | * 設定ページでプロジェクトのルートパスと対応するPythonインタープリターのパスを入力して保存します。 8 | * **簡単な方法**:プログラムを統合パッケージのルートディレクトリに配置し、最初のページの対応するボタンをクリックしてAPIサービスを起動します! 9 | 10 | * **Microsoft TTSについて**: 11 | * チュートリアルに従ってアカウントを登録し、設定ページにAPIキーを入力します。 12 | * 月次の無料クォータに注意してください! 13 | 14 | ## 1. 使い始める 15 | ### このプロジェクトは、字幕または平文の吹き替えをサポートしています。 16 | * **字幕の場合**: 17 | * 実際に有効に使用されるのは開始時間のみです。字幕が長すぎる場合、後続の字幕はそれに応じて遅延します。また、設定で最小音声間隔を設定することができます。 18 | 19 | * **平文の場合**: 20 | * テキストは、終了句読点と改行に基づいて字幕エントリに分割されます。 21 | 22 | * 生成後、編集ページで実際の音声タイムスタンプ付きの字幕をエクスポートすることができます。 23 | 24 | ### A. 単一話者のシナリオ 25 | * **I.** `字幕吹き替え`ページの右パネルで字幕またはテキストファイルをアップロードします。 26 | * **II.** 中央のパネルでプロジェクトを選択し、パラメータを調整します。 27 | * **III.** 下部の`音声を生成`ボタンをクリックして待ちます。 28 | * **IV.** 音声をダウンロードします。 29 | 30 | ### B. 複数話者のシナリオ 31 | * **I.** `字幕吹き替え`の右パネルで字幕/テキストファイルをアップロードします。 32 | * **II.** ファイル表示の下にある`複数話者吹き替えプロジェクトを作成`をクリックします。 33 | * **III.** 話者を作成します: 34 | * **a.** 編集ページの下部にある「複数話者吹き替え」セクションを展開します。 35 | * **b.** ターゲットプロジェクトを選択します。 36 | * **c.** 「話者を選択/作成」ボックスに話者名を入力します。 37 | * **d.** パラメータ(ポート番号を含む)を調整し、💾をクリックして保存します。重複する名前は既存の話者を上書きします。 38 | * **IV.** ドロップダウンから話者を選択し、対応する字幕にチェックを入れてから、✅をクリックして適用します。話者情報が4列目に表示されます。 39 | * **V.** 最後に割り当てられた話者がデフォルトの話者になります(複数話者プロジェクトで割り当てられていない字幕に適用されます)。 40 | * **VI.** `複数話者吹き替えを生成`をクリックして生成を開始します。 41 | 42 | ### 特定の行を再生成する 43 | * **I.** 編集ページのスライダーを使用してターゲット字幕を見つけます。 44 | * **II.** 必要に応じてテキストを変更します。再生成後、変更内容は自動的に保存されます。 45 | * **III.** 🔄をクリックして1行を再生成します: 46 | * 割り当てられていない場合は、プロジェクトのパラメータを使用します。 47 | * 割り当てられている場合は、話者固有のパラメータを使用します。 48 | * 複数話者プロジェクトでは、話者を割り当てる必要があります。 49 | * **IV.** `音声を再組み立て`をクリックして、完全な音声を再構成します。 50 | 51 | ### C. 過去のプロジェクトを再編集する 52 | * 上部パネルの合成履歴からプロジェクトを選択し、`読み込み`ボタンをクリックします。 53 | * 残りの手順は自明です。 54 | 55 | ### D. 字幕編集 56 | #### 1. コピー 57 | * 選択した字幕をコピーします。 58 | 59 | #### 2. 削除 60 | * 選択した字幕を削除します。 61 | 62 | #### 3. マージ 63 | * 少なくとも2つの字幕を開始/終了点として選択します。 64 | * 開始点から終了点までの字幕がマージされます。 65 | 66 | ⚠️ 変更はすぐにディスクに自動保存されないため、プロジェクトを再読み込みすることで元に戻すことができます。 67 | 68 | #### 4. タイムスタンプを変更する 69 | * SRT形式で開始/終了時間を編集します。 70 | * `タイムスタンプを適用`をクリックして変更を保存します。 71 | 72 | ⚠️ 適用されていない変更は、ナビゲーション中に失われます。 73 | 74 | ## 2. トラブルシューティング 75 | * 問題を報告する際は、問題を詳細に説明し、エラーが発生する前に行った手順を列挙してください。 76 | * [GitHub-issues](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/issues)にアクセスして、問題を報告またはヘルプを求めてください(Issueテンプレートが適切な報告方法をガイドします)。 77 | """ 78 | -------------------------------------------------------------------------------- /docs/en_US/README.md: -------------------------------------------------------------------------------- 1 | # Srt-AI-Voice-Assistant 2 | ### This project can use multiple AI-TTS to dub for your subtitle or text files.
And provides various convenient auxiliary functions including audio/video transcription and subtitle translation. 3 | If you have encountered problems or want to create a feature request, please go to [Issues](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/issues) . 4 | ## Features 5 | - ✅ Open-source, Friendly WebUI interface, Run locally and Accessible via LAN 6 | - ✅ Support multiple TTS projects: BV2, GSV, CosyVoice2, AzureTTS, and you can even customize your APIs! 7 | - ✅ Save personalized settings and presets 8 | - ✅ Batch mode 9 | - ✅ Subtitle editing 10 | - ✅ Subtitle translation 11 | - ✅ Regenerating Specific Lines 12 | - ✅ Support multi-speaker dubbing 13 | - ✅ Re-export subtitles 14 | - ✅ Extended functions: subtitle transcription for audio/video 15 | - ✅ I18n 16 | 17 | ## Installation 18 | ### From Source Code 19 | ``` 20 | git clone https://github.com/YYuX-1145/Srt-AI-Voice-Assistant.git 21 | cd Srt-AI-Voice-Assistant/ 22 | pip install -r requirements.txt 23 | python Srt-AI-Voice-Assistant.py 24 | ``` 25 | ### Optional Command Line Arguments 26 | You can customize the behavior of the application with the following command-line arguments: 27 | | Arguments | Description | 28 | | ----- | ----- | 29 | | `-p` | Specify the server port | 30 | | `--lan` | Enable LAN access | 31 | | `--no_ext` | Disable all extensions | 32 | | `--share` | Create a publicly shareable link for the gradio app.| 33 | | `--server_mode` | Activate server mode, which disables all functions that might cause conflicts in multi-user environments. | 34 | 35 | **And then prepare TTS engines yourself. For Windows users, you can download the packaged version or use the integrated package with GPT-SoVITS.** 36 | 37 | **If the required TTS engine is not on the supported list, you can refer to the [documentation](/docs/en_US/extension_dev.md) to write an extension.** 38 | 39 | --- 40 | 41 | ### [Download the packaged version only](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/releases) 42 | * Use this version only when there are dependency conflicts or installation issues. 43 | 44 | ### [Download the integrated package with GPT-SoVITS (From Hugging Face)](https://huggingface.co/YYuX/GPT-SoVITS-SAVA-windows-package/tree/main) 45 | * The GPT-SoVITS integrated package includes the packaged version, without removing any built-in or pretrained models, and its code for finetuning and inference is the same with the official repository. 46 | * **Note:** Packaged Version included in the GPT-SoVITS integrated package may not be the latest version; overwrite it to update. -------------------------------------------------------------------------------- /Sava_Utils/man/en_US/README.py: -------------------------------------------------------------------------------- 1 | README = r""" 2 | # Srt-AI-Voice-Assistant 3 | ### This project can use multiple AI-TTS to dub for your subtitle or text files.
And provides various convenient auxiliary functions including audio/video transcription and subtitle translation. 4 | If you have encountered problems or want to create a feature request, please go to [Issues](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/issues) . 5 | ## Features 6 | - ✅ Open-source, Friendly WebUI interface, Run locally and Accessible via LAN 7 | - ✅ Support multiple TTS projects: BV2, GSV, CosyVoice2, AzureTTS, and you can even customize your APIs! 8 | - ✅ Save personalized settings and presets 9 | - ✅ Batch mode 10 | - ✅ Subtitle editing 11 | - ✅ Subtitle translation 12 | - ✅ Regenerating Specific Lines 13 | - ✅ Support multi-speaker dubbing 14 | - ✅ Re-export subtitles 15 | - ✅ Extended functions: subtitle transcription for audio/video 16 | - ✅ I18n 17 | 18 | ## Installation 19 | ### From Source Code 20 | ``` 21 | git clone https://github.com/YYuX-1145/Srt-AI-Voice-Assistant.git 22 | cd Srt-AI-Voice-Assistant/ 23 | pip install -r requirements.txt 24 | python Srt-AI-Voice-Assistant.py 25 | ``` 26 | ### Optional Command Line Arguments 27 | You can customize the behavior of the application with the following command-line arguments: 28 | | Arguments | Description | 29 | | ----- | ----- | 30 | | `-p` | Specify the server port | 31 | | `--lan` | Enable LAN access | 32 | | `--no_ext` | Disable all extensions | 33 | | `--share` | Create a publicly shareable link for the gradio app.| 34 | | `--server_mode` | Activate server mode, which disables all functions that might cause conflicts in multi-user environments. | 35 | 36 | **And then prepare TTS engines yourself. For Windows users, you can download the packaged version or use the integrated package with GPT-SoVITS.** 37 | 38 | **If the required TTS engine is not on the supported list, you can refer to the [documentation](/docs/en_US/extension_dev.md) to write an extension.** 39 | 40 | --- 41 | 42 | ### [Download the packaged version only](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/releases) 43 | * Use this version only when there are dependency conflicts or installation issues. 44 | 45 | ### [Download the integrated package with GPT-SoVITS (From Hugging Face)](https://huggingface.co/YYuX/GPT-SoVITS-SAVA-windows-package/tree/main) 46 | * The GPT-SoVITS integrated package includes the packaged version, without removing any built-in or pretrained models, and its code for finetuning and inference is the same with the official repository. 47 | * **Note:** Packaged Version included in the GPT-SoVITS integrated package may not be the latest version; overwrite it to update. 48 | """ 49 | -------------------------------------------------------------------------------- /Sava_Extensions/tts_engine/CUSTOM_OLD/custom.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import gradio as gr 3 | import time 4 | import os 5 | from . import * 6 | 7 | current_path = os.environ.get("current_path") 8 | 9 | 10 | class Custom(TTSProjet): # Must inherit from base class. 11 | def __init__(self): 12 | self.custom_api_list = [] 13 | self.refresh_custom_api_list() 14 | super().__init__("custom", title=i18n('Custom API')) 15 | 16 | def api(self, func, text): 17 | return func(text) 18 | 19 | def _UI(self): 20 | with gr.Column(): 21 | gr.Markdown(value=MANUAL.getInfo("help_custom")) 22 | self.choose_custom_api = gr.Dropdown(label=i18n('Choose Custom API Code File'), choices=self.custom_api_list, value=self.custom_api_list[0] if self.custom_api_list != [] else '', allow_custom_value=False, scale=4) 23 | with gr.Row(): 24 | self.gen_btn = gr.Button(value=i18n('Generate Audio'), variant="primary", scale=8) 25 | self.refresh_custom_btn = gr.Button(value="🔄️", scale=1, min_width=40) 26 | self.refresh_custom_btn.click(self.refresh_custom_api_list, outputs=[self.choose_custom_api]) 27 | return [self.choose_custom_api] 28 | 29 | def arg_filter(self, *args): 30 | custom_api = args 31 | if isinstance(custom_api, tuple): 32 | custom_api = custom_api[0] 33 | if custom_api in [None, 'None', '']: 34 | gr.Info(i18n('Please select a valid custom API code file!')) 35 | raise Exception(i18n('Please select a valid custom API code file!')) 36 | return custom_api, dict() 37 | 38 | def before_gen_action(self, custom_api_path, temp_namesp, **kwargs): 39 | logger.info(f"Exec: custom_api_path {custom_api_path}") 40 | with open(os.path.join(current_path, "SAVAdata", "presets", custom_api_path), "r", encoding="utf-8") as f: 41 | code = f.read() 42 | exec(code, temp_namesp) 43 | 44 | def save_action(self, custom_api_path, temp_namesp, text): 45 | return self.api(temp_namesp["custom_api"], text) 46 | 47 | def refresh_custom_api_list(self): 48 | self.custom_api_list = ['None'] 49 | try: 50 | preset_dir = os.path.join(current_path, "SAVAdata", "presets") 51 | if os.path.isdir(preset_dir): 52 | self.custom_api_list += [i for i in os.listdir(preset_dir) if i.endswith(".py")] 53 | else: 54 | logger.info(i18n('No custom API code file found.')) 55 | except Exception as e: 56 | self.custom_api_list = ['None'] 57 | err = f"Error: {e}" 58 | logger.error(err) 59 | gr.Warning(err) 60 | time.sleep(0.1) 61 | return gr.update(value="None", choices=self.custom_api_list) 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Srt-AI-Voice-Assistant 2 | **English** | [**简体中文**](./docs/zh_CN/README.md) | [**Français**](./docs/fr_FR/README.md) | [**Other**](./docs/) |**Documents in other languages are translated by AI and they are provided only for reference.** 3 | ### This project can use multiple AI-TTS to dub for your subtitle or text files.
And provides various convenient auxiliary functions including audio/video transcription and subtitle translation. 4 | If you have encountered problems or want to create a feature request, please go to [Issues](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/issues) . 5 | ## Features 6 | - ✅ Open-source, Friendly WebUI interface, Run locally and Accessible via LAN 7 | - ✅ Support multiple TTS projects: BV2, GSV, CosyVoice2, AzureTTS, and you can even customize your APIs! 8 | - ✅ Save personalized settings and presets 9 | - ✅ Batch mode 10 | - ✅ Subtitle editing 11 | - ✅ Subtitle translation 12 | - ✅ Regenerating Specific Lines 13 | - ✅ Support multi-speaker dubbing 14 | - ✅ Re-export subtitles 15 | - ✅ Extended functions: subtitle transcription for audio/video 16 | - ✅ I18n 17 | 18 | ## Installation 19 | ### From Source Code 20 | ``` 21 | git clone https://github.com/YYuX-1145/Srt-AI-Voice-Assistant.git 22 | cd Srt-AI-Voice-Assistant/ 23 | pip install -r requirements.txt 24 | python Srt-AI-Voice-Assistant.py 25 | ``` 26 | ### Optional Command Line Arguments 27 | You can customize the behavior of the application with the following command-line arguments: 28 | | Arguments | Description | 29 | | ----- | ----- | 30 | | `-p` | Specify the server port | 31 | | `--lan` | Enable LAN access | 32 | | `--no_ext` | Disable all extensions | 33 | | `--share` | Create a publicly shareable link for the gradio app.| 34 | | `--server_mode` | Activate server mode, which disables all functions that might cause conflicts in multi-user environments. | 35 | 36 | **And then prepare TTS engines yourself. For Windows users, you can download the packaged version or use the integrated package with GPT-SoVITS.** 37 | 38 | **If the required TTS engine is not on the supported list, you can refer to the [documentation](/docs/en_US/extension_dev.md) to write an extension.** 39 | 40 | --- 41 | 42 | ### [Download the packaged version only](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/releases) 43 | * Use this version only when there are dependency conflicts or installation issues. 44 | 45 | ### [Download the integrated package with GPT-SoVITS (From Hugging Face)](https://huggingface.co/YYuX/GPT-SoVITS-SAVA-windows-package/tree/main) 46 | * The GPT-SoVITS integrated package includes the packaged version, without removing any built-in or pretrained models, and its code for finetuning and inference is the same with the official repository. 47 | * **Note:** Packaged Version included in the GPT-SoVITS integrated package may not be the latest version; overwrite it to update. -------------------------------------------------------------------------------- /Sava_Utils/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from collections import defaultdict 3 | import os 4 | import json 5 | import argparse 6 | 7 | parser = argparse.ArgumentParser(add_help=True) 8 | parser.add_argument("-p", "--server_port", type=int, help="server_port") 9 | parser.add_argument("-lan", "--lan", dest="LAN_access", action="store_true", default=False, help="Enable LAN access") 10 | parser.add_argument("--no_ext", dest="no_ext", action="store_true", default=False, help="Do not load any extensions") 11 | parser.add_argument("--share", dest="share", action="store_true", default=False, help="set share True") 12 | parser.add_argument("--server_mode", dest="server_mode", action="store_true", default=False, help="activate server mode") 13 | args, unknown = parser.parse_known_args() 14 | no_ext_mode = args.no_ext 15 | 16 | current_path = os.environ.get("current_path") 17 | 18 | log_colors = { 19 | "DEBUG": "white", 20 | "INFO": "green", 21 | "WARNING": "yellow", 22 | "ERROR": "red", 23 | "CRITICAL": "bold_red", 24 | } 25 | logger = logging.getLogger("SAVA") 26 | logger.setLevel(logging.INFO) 27 | try: 28 | import colorlog 29 | 30 | handler = colorlog.StreamHandler() 31 | handler.setFormatter( 32 | colorlog.ColoredFormatter( 33 | fmt="%(log_color)s[%(levelname)s][%(asctime)s]:%(funcName)s: %(message)s", 34 | datefmt="%Y-%m-%d_%H:%M:%S", 35 | log_colors=log_colors, 36 | ) 37 | ) 38 | logger.addHandler(handler) 39 | except ImportError: 40 | handler = logging.StreamHandler() 41 | handler.setLevel(logging.INFO) 42 | formatter = logging.Formatter("[%(levelname)s][%(asctime)s]:%(funcName)s: %(message)s") 43 | handler.setFormatter(formatter) 44 | logger.addHandler(handler) 45 | 46 | from .i18nAuto import I18n 47 | 48 | ext_tab_path = os.path.join(current_path, "Sava_Extensions/extensions_config.json") 49 | if os.path.exists(ext_tab_path): 50 | ext_tab = defaultdict(dict, json.load(open(ext_tab_path, encoding="utf-8"))) 51 | else: 52 | ext_tab = defaultdict(dict) 53 | 54 | config_path = os.path.join(current_path, "SAVAdata", "config.json") 55 | try: 56 | if os.path.isfile(config_path): 57 | x = json.load(open(config_path, encoding="utf-8")) 58 | i18n = I18n(x.get("language")) 59 | else: 60 | x = dict() 61 | i18n = I18n() 62 | from .settings import Settings 63 | 64 | config = Settings.from_dict(x) 65 | del x 66 | except Exception as e: 67 | i18n = I18n() 68 | logger.warning(f"{i18n('Failed to load settings, reset to default')}: {e}") 69 | from .settings import Settings 70 | 71 | config = Settings() 72 | from .man import Man 73 | 74 | MANUAL = Man(language=config.language) 75 | 76 | config.server_mode = args.server_mode or config.server_mode 77 | if config.server_mode: 78 | logger.warning(i18n("Server Mode has been enabled!")) 79 | 80 | from .utils import clear_cache 81 | 82 | if config.clear_tmp: 83 | clear_cache() 84 | -------------------------------------------------------------------------------- /Sava_Utils/audio_utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import soundfile as sf 3 | import soxr 4 | 5 | # obtained form librosa 6 | 7 | 8 | def to_mono(y): 9 | if y.ndim > 1: 10 | y = np.mean(y, axis=tuple(range(y.ndim - 1))) 11 | return y 12 | 13 | 14 | def fix_length(data, *, size, axis=-1, **kwargs): 15 | kwargs.setdefault("mode", "constant") 16 | 17 | n = data.shape[axis] 18 | 19 | if n > size: 20 | slices = [slice(None)] * data.ndim 21 | slices[axis] = slice(0, size) 22 | return data[tuple(slices)] 23 | 24 | elif n < size: 25 | lengths = [(0, 0)] * data.ndim 26 | lengths[axis] = (0, size - n) 27 | return np.pad(data, lengths, **kwargs) 28 | 29 | return data 30 | 31 | 32 | def resample( 33 | y: np.ndarray, 34 | *, 35 | orig_sr: float, 36 | target_sr: float, 37 | res_type: str = "soxr_hq", 38 | fix: bool = True, 39 | scale: bool = False, 40 | axis: int = -1, 41 | **kwargs, 42 | ): 43 | ratio = float(target_sr) / orig_sr 44 | n_samples = int(np.ceil(y.shape[axis] * ratio)) 45 | y_hat = np.apply_along_axis( 46 | soxr.resample, 47 | axis=axis, 48 | arr=y, 49 | in_rate=orig_sr, 50 | out_rate=target_sr, 51 | quality=res_type, 52 | ) 53 | 54 | if fix: 55 | y_hat = fix_length(y_hat, size=n_samples, **kwargs) 56 | 57 | if scale: 58 | y_hat /= np.sqrt(ratio) 59 | 60 | return np.asarray(y_hat, dtype=y.dtype) 61 | 62 | 63 | def get_rms( 64 | y, 65 | frame_length=2048, 66 | hop_length=512, 67 | pad_mode="constant", 68 | ): 69 | padding = (int(frame_length // 2), int(frame_length // 2)) 70 | y = np.pad(y, padding, mode=pad_mode) 71 | 72 | axis = -1 73 | # put our new within-frame axis at the end for now 74 | out_strides = y.strides + tuple([y.strides[axis]]) 75 | # Reduce the shape on the framing axis 76 | x_shape_trimmed = list(y.shape) 77 | x_shape_trimmed[axis] -= frame_length - 1 78 | out_shape = tuple(x_shape_trimmed) + tuple([frame_length]) 79 | xw = np.lib.stride_tricks.as_strided(y, shape=out_shape, strides=out_strides) 80 | if axis < 0: 81 | target_axis = axis - 1 82 | else: 83 | target_axis = axis + 1 84 | xw = np.moveaxis(xw, -1, target_axis) 85 | # Downsample along the target axis 86 | slices = [slice(None)] * xw.ndim 87 | slices[axis] = slice(0, None, hop_length) 88 | x = xw[tuple(slices)] 89 | 90 | # Calculate power 91 | power = np.mean(np.abs(x) ** 2, axis=-2, keepdims=True) 92 | 93 | return np.sqrt(power) 94 | 95 | 96 | def load_audio(filepath, sr=None): 97 | y, sr_native = sf.read(filepath) 98 | y = to_mono(y) 99 | if sr != sr_native and sr not in [None, 0]: 100 | y = resample(y, orig_sr=sr_native, target_sr=sr) 101 | return y, sr 102 | else: 103 | return y, sr_native 104 | 105 | def get_shape_sr_from_bytes(wav_bytes): 106 | sr = int.from_bytes(wav_bytes[24:28], 'little') 107 | channels = int.from_bytes(wav_bytes[22:24], 'little') 108 | bit_depth = int.from_bytes(wav_bytes[34:36], 'little') 109 | data_size = int.from_bytes(wav_bytes[40:44], 'little') 110 | n_frames = data_size // ((bit_depth // 8) * channels) 111 | return (channels,n_frames), sr 112 | -------------------------------------------------------------------------------- /Sava_Utils/extension_loader.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import traceback 4 | import gradio as gr 5 | import importlib.util 6 | from . import i18n, logger, MANUAL, ext_tab, no_ext_mode 7 | from . import utils, audio_utils 8 | from .tts_engines import Base_Component, TTSProjet 9 | from .translator import Traducteur 10 | from .settings import Settings, Shared_Option 11 | 12 | current_path = os.environ.get("current_path") 13 | 14 | 15 | def _load_package_from_dir(dir_path: str): 16 | package_name = f"extension_{os.path.basename(dir_path)}" 17 | init_file = os.path.join(dir_path, "__init__.py") 18 | if not os.path.isfile(init_file): 19 | raise FileNotFoundError(f"{init_file} not found") 20 | spec = importlib.util.spec_from_file_location(package_name, init_file, submodule_search_locations=[dir_path]) 21 | module = importlib.util.module_from_spec(spec) 22 | sys.modules[package_name] = module 23 | spec.loader.exec_module(module) 24 | return module 25 | 26 | 27 | def load_ext_from_dir(roots: list[str], ext_enabled_dict: dict[str:bool]) -> list[Base_Component]: 28 | loaded_ext = [] 29 | if no_ext_mode: 30 | return loaded_ext 31 | for extension_root in roots: 32 | if not os.path.isdir(extension_root): 33 | # does not exist 34 | continue 35 | for entry in os.listdir(extension_root): 36 | ext_enabled = ext_enabled_dict.get(entry, True) 37 | if not ext_enabled: 38 | continue 39 | entry_path = os.path.join(current_path, extension_root, entry) 40 | try: 41 | if not os.path.isdir(entry_path): 42 | continue 43 | module = _load_package_from_dir(entry_path) 44 | assert hasattr(module, "register"), f"{entry}: register() not found" 45 | extension_instance = module.register( 46 | { 47 | "current_path": current_path, 48 | "Base_Component": Base_Component, 49 | "TTSProjet": TTSProjet, 50 | "Traducteur": Traducteur, 51 | "utils": utils, 52 | "audio_utils": audio_utils, 53 | "i18n": i18n, 54 | "MANUAL": MANUAL, 55 | "logger": logger, 56 | "Settings": Settings, 57 | "Shared_Option": Shared_Option, 58 | }, 59 | ) 60 | assert isinstance(extension_instance, Base_Component) 61 | assert hasattr(extension_instance, "name") 62 | setattr(extension_instance, "dirname", entry) 63 | loaded_ext.append(extension_instance) 64 | logger.info(f"{i18n('Loaded extension')}: {entry}") 65 | except: 66 | logger.warning(f"Failed to load extension: {entry}") 67 | traceback.print_exc() 68 | return loaded_ext 69 | 70 | 71 | class Extension_Loader(Base_Component): 72 | def __init__(self): 73 | self.components = load_ext_from_dir(["Sava_Extensions/extension"], ext_enabled_dict=ext_tab["extension"]) 74 | self.extension_dict = {i.name: i for i in self.components} 75 | super().__init__() 76 | 77 | def _UI(self, components): 78 | for i in self.components: 79 | try: 80 | assert i.title, "Title must not be empty." 81 | with gr.TabItem(i.title): 82 | i.getUI(components) 83 | except: 84 | logger.error(f"{i18n('Failed to load Extension UI')}: {i.dirname}") 85 | traceback.print_exc() 86 | 87 | def getUI(self, *args, **kwargs): 88 | super().getUI(*args, **kwargs) 89 | return len(self.components) != 0 90 | -------------------------------------------------------------------------------- /Sava_Extensions/translator/youdao/TranslateDemo.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from tqdm import tqdm 3 | import json 4 | import gradio as gr 5 | import traceback 6 | from .utils.AuthV3Util import addAuthParams 7 | 8 | from . import * 9 | 10 | 11 | LANGUAGE_map = {"中文": "zh-CHS", "English": "en", "日本語": "ja", "한국어": "ko", "Français": "fr"} 12 | 13 | 14 | # 修改自官方示例 15 | # modified from official code demo 16 | class Youdao(Traducteur): 17 | 18 | def __init__(self, config=None): 19 | self.app_key = "" 20 | self.app_secret = "" 21 | super().__init__(i18n("Youdao"), config) 22 | 23 | def update_cfg(self, config): 24 | self.app_key = config.query("yd_app_key", "") 25 | self.app_secret = config.query("yd_app_secret", "") 26 | super().update_cfg(config) 27 | 28 | def register_settings(self): 29 | options = [] 30 | options.append( 31 | Shared_Option( 32 | "yd_app_key", 33 | "", 34 | gr.Textbox, 35 | lambda x, _: x.strip(), 36 | label=i18n('APP_KEY'), 37 | interactive=True, 38 | ) 39 | ) 40 | options.append( 41 | Shared_Option( 42 | "yd_app_secret", 43 | "", 44 | gr.Textbox, 45 | lambda x, _: x.strip(), 46 | label=i18n('APP_SECRET'), 47 | interactive=True, 48 | type="password", 49 | ) 50 | ) 51 | return options 52 | 53 | def _UI(*args, **kwargs): 54 | return [] 55 | 56 | def api(self, tasks, target_lang, interrupt_flag, *args, file_name: str = "", **kwargs) -> list[str] | tuple[list[str], str]: 57 | assert self.app_key 58 | assert self.app_secret 59 | ret = [] 60 | for task in tqdm(tasks, desc=f"{i18n('Translating')}: {file_name}", total=len(tasks)): 61 | if interrupt_flag.is_set(): 62 | return [] 63 | header = {'Content-Type': 'application/x-www-form-urlencoded'} 64 | text = "\n\n".join(task) 65 | # 当然你也可以一条一条无上下文地翻译 66 | # Also, you can choose to translate sentence by sentence with context 67 | data = {'q': text, 'from': 'auto', 'to': LANGUAGE_map[target_lang]} 68 | addAuthParams(self.app_key, self.app_secret, data) 69 | try: 70 | response = requests.post('https://openapi.youdao.com/api', data, header) 71 | response.raise_for_status() 72 | result = json.loads(response.content) 73 | print(result) 74 | ret += result["translation"][0].split("\n\n") 75 | except: 76 | traceback.print_exc() 77 | raise 78 | for _ in range(len(task)): 79 | ret.append("") 80 | return ret 81 | 82 | 83 | # # 您的应用ID 84 | # APP_KEY = '' 85 | # # 您的应用密钥 86 | # APP_SECRET = '' 87 | 88 | 89 | # def createRequest(): 90 | # ''' 91 | # note: 将下列变量替换为需要请求的参数 92 | # ''' 93 | # q = '待翻译文本' 94 | # lang_from = 'auto' 95 | # lang_to = '目标语言语种' 96 | 97 | # data = {'q': q, 'from': lang_from, 'to': lang_to} 98 | 99 | # addAuthParams(APP_KEY, APP_SECRET, data) 100 | 101 | # header = {'Content-Type': 'application/x-www-form-urlencoded'} 102 | # res = doCall('https://openapi.youdao.com/api', header, data, 'post') 103 | # print(str(res.content, 'utf-8')) 104 | 105 | 106 | # def doCall(url, header, params, method): 107 | # if 'get' == method: 108 | # return requests.get(url, params) 109 | # elif 'post' == method: 110 | # return requests.post(url, params, header) 111 | 112 | # # 网易有道智云翻译服务api调用demo 113 | # # api接口: https://openapi.youdao.com/api 114 | # if __name__ == '__main__': 115 | # createRequest() 116 | -------------------------------------------------------------------------------- /Sava_Utils/polyphone.py: -------------------------------------------------------------------------------- 1 | from .base_component import Base_Component 2 | from .settings import Settings 3 | from . import i18n 4 | import gradio as gr 5 | import re 6 | import numpy as np 7 | import os 8 | 9 | PATH = {"ZH": "GPT_SoVITS/text/g2pw/polyphonic.rep", "EN": "GPT_SoVITS/text/engdict-hot.rep"} 10 | CACHE = {"ZH": "GPT_SoVITS/text/g2pw/polyphonic.pickle", "EN": "GPT_SoVITS/text/engdict_cache.pickle"} 11 | 12 | ZH_SINGLE_PY_PATTERN = re.compile(r"[a-z]+[1-5]") 13 | ZH_FORMAT_PATTERN = re.compile(r"^[a-z]+[1-5](?:\s+[a-z]+[1-5])*$") 14 | # Raw: 一丝不苟: ['yi1', 'si1', 'bu4', 'gou3'] 15 | # Userinput: yi1 si1 bu4 gou3 16 | EN_FORMAT_PATTERN = re.compile(r"^[A-Z]+[0-2]{0,1}(?:\s+[A-Z]+[0-2]{0,1})*$") 17 | # CHATGPT CH AE1 T JH IY1 P IY1 T IY1 18 | PATTERN = {"ZH": ZH_FORMAT_PATTERN, "EN": EN_FORMAT_PATTERN} 19 | 20 | 21 | def read_fn_zh(x: str): 22 | key, content_raw = x.split(":") 23 | items = ZH_SINGLE_PY_PATTERN.findall(content_raw) 24 | result = ' '.join(items) 25 | return key.strip(), result 26 | READ_FN = {"ZH": read_fn_zh, "EN": lambda x: [i.strip() for i in x.split(' ', 1)]} 27 | WRITE_FN = {"ZH": lambda x, y: f"{x}: {str(y.split())}\n", "EN": lambda x, y: f"{x} {y}\n"} 28 | 29 | 30 | class Polyphone(Base_Component): 31 | def __init__(self): 32 | super().__init__() 33 | 34 | def update_cfg(self, config:Settings): 35 | self.gsv_dir = config.query("gsv_dir","") 36 | return super().update_cfg(config) 37 | 38 | def _UI(self, *args): 39 | with gr.TabItem(i18n('Polyphone Editor')): 40 | if self.server_mode: 41 | gr.Markdown(i18n('This function has been disabled!')) 42 | return 43 | gr.Markdown(i18n('POLYPHONE_NOTICE')) 44 | self.language = gr.Dropdown(label=i18n('Choose Language'), value=list(PATH.keys())[1], choices=list(PATH.keys()), interactive=True) 45 | self.tab = gr.DataFrame(datatype=["str", "str"], col_count=(2, 'fixed'), type="numpy", interactive=True, show_search='search') 46 | self.overwrite = gr.Checkbox(value=False, label=i18n('Overwrite instead of Append')) 47 | self.language.change(lambda: np.array([['', '']], dtype=str), outputs=[self.tab]) 48 | with gr.Row(): 49 | self.readbtn = gr.Button(value=i18n('Read'), variant="primary") 50 | self.readbtn.click(self.read_file, inputs=[self.language], outputs=[self.tab]) 51 | self.writebtn = gr.Button(value=i18n('Save'), variant="primary") 52 | self.writebtn.click(self.save_file, inputs=[self.language, self.tab, self.overwrite]) 53 | 54 | def read_file(self, lang): 55 | if self.gsv_dir in [None, ""] or not os.path.isdir(self.gsv_dir): 56 | gr.Warning(i18n('GSV root path has been not configured or does not exist.')) 57 | return None 58 | rows = [] 59 | try: 60 | with open(os.path.join(self.gsv_dir, PATH[lang]), 'r', encoding='utf-8') as f: 61 | for line in f: 62 | rows.append(READ_FN[lang](line)) 63 | except Exception as e: 64 | gr.Warning(f"Error: {str(e)}") 65 | if len(rows) == 0: 66 | rows.append(['', '']) 67 | return np.array(rows, dtype=str) 68 | 69 | def save_file(self, lang, map, overwrite): 70 | try: 71 | if overwrite: 72 | content = {} 73 | else: 74 | x = self.read_file(lang) 75 | content = {i[0]: i[-1] for i in x if i[0]} 76 | for i in map: 77 | if i[0]: 78 | i[-1] = i[-1].strip() 79 | if PATTERN[lang].match(i[-1]): 80 | content[i[0]] = i[-1] 81 | else: 82 | gr.Info(f"{i18n('Input format mismatch')}: {i[-1]}") 83 | with open(os.path.join(self.gsv_dir, PATH[lang]), 'w', encoding='utf-8') as f: 84 | for key, value in content.items(): 85 | f.write(WRITE_FN[lang](key, value)) 86 | cachedir = os.path.join(self.gsv_dir, CACHE[lang]) 87 | if os.path.isfile(cachedir): 88 | os.remove(cachedir) 89 | gr.Info(i18n('Done!')) 90 | except Exception as e: 91 | gr.Warning(f"Error: {str(e)}") 92 | -------------------------------------------------------------------------------- /docs/en_US/help.md: -------------------------------------------------------------------------------- 1 | # User Guide 2 | 3 | ## 0. Service Configuration and Usage 4 | #### This project can call 2 local projects: Bert-VITS2, GPT-SoVITS 5 | #### And 1 online service: Microsoft TTS 6 | * **For Local TTS Projects**: 7 | 8 | * Fill in and save the project root path and the corresponding python interpreter path in the settings page. 9 | * **A Simpler method**: Place the program in the root directory of the integrated package, then click the corresponding button on the first page to start the API service! 10 | 11 | * **For Microsoft TTS**: 12 | 13 | * Follow the tutorial to register an account and fill in the API key on the settings page. 14 | * Note the monthly free quota! 15 | 16 | ## 1. Getting Started 17 | ### This project supports dubbing for subtitles or plain text. 18 | * **For subtitles**: 19 | 20 | * When a subtitle is too long, subsequent subtitles will be delayed accordingly.And you can set the minimum speech interval in settings. 21 | 22 | * **For plain text**: 23 | 24 | * The text will be split into subtitle entries based on ending punctuation and line breaks. 25 | 26 | * After generation, you can export subtitles with actual audio timestamps in the editing page. 27 | 28 | ### A. Single Speaker Scenario 29 | * **I.** Upload subtitle or text files in the right panel of the `Subtitle Dubbing` page. 30 | 31 | * **II.** Select your project and adjust parameters in the middle panel. 32 | 33 | * **III.** Click `Generate Audio` Button at the bottom and wait. 34 | 35 | * **IV.** Download your audio. 36 | 37 | ### B. Multi-Speaker Scenario 38 | * **I.** Upload subtitle/text files in the right panel of `Subtitle Dubbing`. 39 | * Marking mode: The content of the file should be as follows: `Speaker:Content`, e.g. `Jerry: Hello.` The mapping table can convert the original speaker in the text file into the corresponding target speaker. 40 | 41 | * **II.** Click `Create Multi-Speaker Dubbing Project` below the file display. 42 | 43 | * **III.** Create speakers: 44 | * **a.** Expand the Multi-Speaker Dubbing section at the bottom of the editing page. 45 | * **b.** Select the target project. 46 | * **c.** In the Select/Create Speaker box, enter a speaker name. 47 | * **d.** Adjust parameters (including port numbers) and click 💾 to save. Duplicate names will overwrite existing speakers. 48 | 49 | * **IV.** Select a speaker from the dropdown, check corresponding subtitles, then click ✅ to apply. Speaker info will appear in Column 4. 50 | 51 | * **V.** The last assigned speaker becomes the default speaker (applies to unassigned subtitles in multi-speaker projects). 52 | 53 | * **VI.** Click Generate Multi-Speaker Dubbing to start generation. 54 | 55 | ### Regenerating Specific Lines 56 | * **I.** Locate the target subtitle using the slider in the editing page. 57 | 58 | * **II.** Modify the text if needed. Changes are auto-saved after regeneration. 59 | 60 | * **III.** Click 🔄 to regenerate a single line: 61 | 62 | * Uses project parameters if unassigned. 63 | * Uses speaker-specific parameters if assigned. 64 | * Multi-speaker projects must have assigned speakers. 65 | 66 | * **IV.** After making changes to the subtitles, you can also click `Continue Generation` to regenerate the audios of the changed subtitles or those that failed to be synthesized. 67 | 68 | * **V.** Click `Reassemble Audio` to recompose full audio. 69 | 70 | ### C. Re-editing Historical Projects 71 | * Select a project from the synthesis history in the top panel. Then click `Load` button. 72 | * The rest is self-explanatory. 73 | 74 | ### D. Subtitle Editing 75 | #### 1. Copy 76 | * Copy selected subtitles. 77 | 78 | #### 2. Delete 79 | * Delete selected subtitles. 80 | 81 | #### 3. Merge 82 | * Select no less than 2 subtitles as start/end points. 83 | * Subtitles from the starting point to the ending point will be merged. 84 | 85 | ⚠️ Changes aren't auto-saved to drive immediately, therefore you can reload the project to undo. 86 | 87 | #### 4. Modify Timestamps 88 | * Edit start/end times in SRT format. 89 | 90 | ## 2. Troubleshooting 91 | * When reporting issues: 92 | Describe the problem in detail and list steps taken before the error occurred. 93 | * Go to [GitHub-issues](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/issues) to report a problem or ask for help (Issue templates will guide proper reporting). -------------------------------------------------------------------------------- /Sava_Utils/man/en_US/help.py: -------------------------------------------------------------------------------- 1 | help = r""" 2 | # User Guide 3 | 4 | ## 0. Service Configuration and Usage 5 | #### This project can call 2 local projects: Bert-VITS2, GPT-SoVITS 6 | #### And 1 online service: Microsoft TTS 7 | * **For Local TTS Projects**: 8 | 9 | * Fill in and save the project root path and the corresponding python interpreter path in the settings page. 10 | * **A Simpler method**: Place the program in the root directory of the integrated package, then click the corresponding button on the first page to start the API service! 11 | 12 | * **For Microsoft TTS**: 13 | 14 | * Follow the tutorial to register an account and fill in the API key on the settings page. 15 | * Note the monthly free quota! 16 | 17 | ## 1. Getting Started 18 | ### This project supports dubbing for subtitles or plain text. 19 | * **For subtitles**: 20 | 21 | * When a subtitle is too long, subsequent subtitles will be delayed accordingly.And you can set the minimum speech interval in settings. 22 | 23 | * **For plain text**: 24 | 25 | * The text will be split into subtitle entries based on ending punctuation and line breaks. 26 | 27 | * After generation, you can export subtitles with actual audio timestamps in the editing page. 28 | 29 | ### A. Single Speaker Scenario 30 | * **I.** Upload subtitle or text files in the right panel of the `Subtitle Dubbing` page. 31 | 32 | * **II.** Select your project and adjust parameters in the middle panel. 33 | 34 | * **III.** Click `Generate Audio` Button at the bottom and wait. 35 | 36 | * **IV.** Download your audio. 37 | 38 | ### B. Multi-Speaker Scenario 39 | * **I.** Upload subtitle/text files in the right panel of `Subtitle Dubbing`. 40 | * Marking mode: The content of the file should be as follows: `Speaker:Content`, e.g. `Jerry: Hello.` The mapping table can convert the original speaker in the text file into the corresponding target speaker. 41 | 42 | * **II.** Click `Create Multi-Speaker Dubbing Project` below the file display. 43 | 44 | * **III.** Create speakers: 45 | * **a.** Expand the Multi-Speaker Dubbing section at the bottom of the editing page. 46 | * **b.** Select the target project. 47 | * **c.** In the Select/Create Speaker box, enter a speaker name. 48 | * **d.** Adjust parameters (including port numbers) and click 💾 to save. Duplicate names will overwrite existing speakers. 49 | 50 | * **IV.** Select a speaker from the dropdown, check corresponding subtitles, then click ✅ to apply. Speaker info will appear in Column 4. 51 | 52 | * **V.** The last assigned speaker becomes the default speaker (applies to unassigned subtitles in multi-speaker projects). 53 | 54 | * **VI.** Click Generate Multi-Speaker Dubbing to start generation. 55 | 56 | ### Regenerating Specific Lines 57 | * **I.** Locate the target subtitle using the slider in the editing page. 58 | 59 | * **II.** Modify the text if needed. Changes are auto-saved after regeneration. 60 | 61 | * **III.** Click 🔄 to regenerate a single line: 62 | 63 | * Uses project parameters if unassigned. 64 | * Uses speaker-specific parameters if assigned. 65 | * Multi-speaker projects must have assigned speakers. 66 | 67 | * **IV.** After making changes to the subtitles, you can also click `Continue Generation` to regenerate the audios of the changed subtitles or those that failed to be synthesized. 68 | 69 | * **V.** Click `Reassemble Audio` to recompose full audio. 70 | 71 | ### C. Re-editing Historical Projects 72 | * Select a project from the synthesis history in the top panel. Then click `Load` button. 73 | * The rest is self-explanatory. 74 | 75 | ### D. Subtitle Editing 76 | #### 1. Copy 77 | * Copy selected subtitles. 78 | 79 | #### 2. Delete 80 | * Delete selected subtitles. 81 | 82 | #### 3. Merge 83 | * Select no less than 2 subtitles as start/end points. 84 | * Subtitles from the starting point to the ending point will be merged. 85 | 86 | ⚠️ Changes aren't auto-saved to drive immediately, therefore you can reload the project to undo. 87 | 88 | #### 4. Modify Timestamps 89 | * Edit start/end times in SRT format. 90 | 91 | ## 2. Troubleshooting 92 | * When reporting issues: 93 | Describe the problem in detail and list steps taken before the error occurred. 94 | * Go to [GitHub-issues](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/issues) to report a problem or ask for help (Issue templates will guide proper reporting). 95 | """ 96 | -------------------------------------------------------------------------------- /docs/fr_FR/help.md: -------------------------------------------------------------------------------- 1 | # Guide de l'utilisateur 2 | 3 | ## 0. Configuration et utilisation du service 4 | #### Ce projet peut appeler deux projets locaux : Bert-VITS2, GPT-SoVITS 5 | #### Et un service en ligne : Microsoft TTS 6 | * **Pour les projets TTS locaux** : 7 | 8 | * Remplissez et enregistrez le chemin racine du projet et le chemin de l'interpréteur Python correspondant sur la page des paramètres. 9 | * **Méthode plus simple** : Placez le programme dans le répertoire racine du paquet intégré, puis cliquez sur le bouton correspondant sur la première page pour démarrer le service API ! 10 | 11 | * **Pour Microsoft TTS** : 12 | 13 | * Suivez le tutoriel pour vous inscrire à un compte et saisissez la clé API sur la page des paramètres. 14 | * Prenez note de la quota mensuelle gratuite ! 15 | 16 | ## 1. Démarrage 17 | ### Ce projet peut doubler pour les sous-titres et les textes bruts. 18 | * **Pour les sous-titres** : 19 | 20 | * Lorsqu'un sous-titre est trop long, les sous-titres suivants seront retardés en conséquence. Et vous pouvez définir l'intervalle de parole minimum dans les paramètres. 21 | 22 | * **Pour le texte brut** : 23 | 24 | * Le texte sera divisé en entrées de sous-titres en fonction des ponctuations de fin et des retours à la ligne. 25 | 26 | * Après la génération, vous pouvez exporter les sous-titres avec les horodatages audio réels sur la page d'édition. 27 | 28 | ### A. Scénario avec un seul locuteur 29 | * **I.** Téléchargez les fichiers de sous-titres ou de texte dans le panneau de droite de la page `Doublage de sous-titres`. 30 | * Mode de balisage : Le contenu du fichier doit être le suivant : `Locuteur : Contenu`, e.g. `Vincent:Bonjour.` Le tableau de correspondance peut convertir le locuteur d'origine dans le fichier de texte en locuteur cible correspondant. 31 | 32 | * **II.** Sélectionnez votre projet et ajustez les paramètres dans le panneau central. 33 | 34 | * **III.** Cliquez sur le bouton `Produire l'audio` en bas et attendez. 35 | 36 | * **IV.** Téléchargez votre audio. 37 | 38 | ### B. Scénario avec plusieurs locuteurs 39 | * **I.** Téléchargez les fichiers de sous-titres/texte dans le panneau de droite de `Doublage de sous-titres`. 40 | 41 | * **II.** Cliquez sur `Créer un projet de doublage avec plusieurs locuteurs` en dessous de l'affichage du fichier. 42 | 43 | * **III.** Créez des locuteurs : 44 | * **a.** Détendez la section Doublure avec plusieurs locuteurs en bas de la page d'édition. 45 | * **b.** Sélectionnez le projet cible. 46 | * **c.** Dans la boîte de sélection/creation de locuteur, saisissez un nom de locuteur. 47 | * **d.** Ajustez les paramètres (y compris les numéros de port) et cliquez sur 💾 pour enregistrer. Les noms dupliqués écraseront les locuteurs existants. 48 | 49 | * **IV.** Sélectionnez un locuteur dans la liste déroulante, cochez les sous-titres correspondants, puis cliquez sur ✅ pour appliquer. Les informations du locuteur apparaîtront dans la colonne 4. 50 | 51 | * **V.** Le dernier locuteur attribué devient le locuteur par défaut (s'applique aux sous-titres non attribués dans les projets avec plusieurs locuteurs). 52 | 53 | * **VI.** Cliquez sur `Lancer la synthèse à plusieurs locuteurs` pour commencer la génération. 54 | 55 | ### Regénérer des lignes spécifiques 56 | * **I.** Localisez le sous-titre cible à l'aide du curseur sur la page d'édition. 57 | 58 | * **II.** Modifiez le texte si nécessaire. Les modifications sont enregistrées automatiquement après la régénération. 59 | 60 | * **III.** Cliquez sur 🔄 pour régénérer une seule ligne : 61 | 62 | * Utilise les paramètres du projet s'il n'est pas attribué. 63 | * Utilise les paramètres spécifiques du locuteur s'il est attribué. 64 | * Les projets avec plusieurs locuteurs doivent avoir des locuteurs attribués. 65 | 66 | * **IV.** Après avoir apporté des modifications aux sous-titres, vous pouvez également cliquer sur `Continuer la Génération` pour régénérer la voix des sous-titres modifiés ou dont la synthèse n'a pas été réussie. 67 | 68 | * **V.** Cliquez sur `Reconstituer l'audio` pour recomposer l'audio complet. 69 | 70 | ### C. Rééditer des projets historiques 71 | * Sélectionnez un projet de l'historique de synthèse dans le panneau supérieur. Ensuite, cliquez sur le bouton `Charger`. 72 | * Le reste est évident. 73 | 74 | ### D. Édition des sous-titres 75 | #### 1. Copier 76 | * Copier les sous-titres sélectionnés. 77 | 78 | #### 2. Supprimer 79 | * Supprimer les sous-titres sélectionnés. 80 | 81 | #### 3. Fusionner 82 | * Sélectionnez au moins 2 sous-titres comme points de départ/fin. 83 | * Les sous-titres du point de départ au point de fin seront fusionnés. 84 | 85 | ⚠️ Les modifications ne sont pas enregistrées automatiquement sur le disque immédiatement, vous pouvez donc recharger le projet pour annuler. 86 | 87 | #### 4. Modifier les horodatages 88 | * Éditez les heures de début/fin au format SRT. 89 | 90 | ## 2. Dépannage 91 | * Lorsque vous trouvez un problème : 92 | Décrivez le problème en détail et répertoriez les étapes effectuées pour reproduire l'erreur. 93 | * Visitez [GitHub-issues](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/issues) pour rapporter un problème ou demander de l'aide (les modèles de Issue vous guideront pour signaler correctement). -------------------------------------------------------------------------------- /Sava_Utils/man/fr_FR/help.py: -------------------------------------------------------------------------------- 1 | help = r""" 2 | # Guide de l'utilisateur 3 | 4 | ## 0. Configuration et utilisation du service 5 | #### Ce projet peut appeler deux projets locaux : Bert-VITS2, GPT-SoVITS 6 | #### Et un service en ligne : Microsoft TTS 7 | * **Pour les projets TTS locaux** : 8 | 9 | * Remplissez et enregistrez le chemin racine du projet et le chemin de l'interpréteur Python correspondant sur la page des paramètres. 10 | * **Méthode plus simple** : Placez le programme dans le répertoire racine du paquet intégré, puis cliquez sur le bouton correspondant sur la première page pour démarrer le service API ! 11 | 12 | * **Pour Microsoft TTS** : 13 | 14 | * Suivez le tutoriel pour vous inscrire à un compte et saisissez la clé API sur la page des paramètres. 15 | * Prenez note de la quota mensuelle gratuite ! 16 | 17 | ## 1. Démarrage 18 | ### Ce projet peut doubler pour les sous-titres et les textes bruts. 19 | * **Pour les sous-titres** : 20 | 21 | * Lorsqu'un sous-titre est trop long, les sous-titres suivants seront retardés en conséquence. Et vous pouvez définir l'intervalle de parole minimum dans les paramètres. 22 | 23 | * **Pour le texte brut** : 24 | 25 | * Le texte sera divisé en entrées de sous-titres en fonction des ponctuations de fin et des retours à la ligne. 26 | 27 | * Après la génération, vous pouvez exporter les sous-titres avec les horodatages audio réels sur la page d'édition. 28 | 29 | ### A. Scénario avec un seul locuteur 30 | * **I.** Téléchargez les fichiers de sous-titres ou de texte dans le panneau de droite de la page `Doublage de sous-titres`. 31 | * Mode de balisage : Le contenu du fichier doit être le suivant : `Locuteur : Contenu`, e.g. `Vincent:Bonjour.` Le tableau de correspondance peut convertir le locuteur d'origine dans le fichier de texte en locuteur cible correspondant. 32 | 33 | * **II.** Sélectionnez votre projet et ajustez les paramètres dans le panneau central. 34 | 35 | * **III.** Cliquez sur le bouton `Produire l'audio` en bas et attendez. 36 | 37 | * **IV.** Téléchargez votre audio. 38 | 39 | ### B. Scénario avec plusieurs locuteurs 40 | * **I.** Téléchargez les fichiers de sous-titres/texte dans le panneau de droite de `Doublage de sous-titres`. 41 | 42 | * **II.** Cliquez sur `Créer un projet de doublage avec plusieurs locuteurs` en dessous de l'affichage du fichier. 43 | 44 | * **III.** Créez des locuteurs : 45 | * **a.** Détendez la section Doublure avec plusieurs locuteurs en bas de la page d'édition. 46 | * **b.** Sélectionnez le projet cible. 47 | * **c.** Dans la boîte de sélection/creation de locuteur, saisissez un nom de locuteur. 48 | * **d.** Ajustez les paramètres (y compris les numéros de port) et cliquez sur 💾 pour enregistrer. Les noms dupliqués écraseront les locuteurs existants. 49 | 50 | * **IV.** Sélectionnez un locuteur dans la liste déroulante, cochez les sous-titres correspondants, puis cliquez sur ✅ pour appliquer. Les informations du locuteur apparaîtront dans la colonne 4. 51 | 52 | * **V.** Le dernier locuteur attribué devient le locuteur par défaut (s'applique aux sous-titres non attribués dans les projets avec plusieurs locuteurs). 53 | 54 | * **VI.** Cliquez sur `Lancer la synthèse à plusieurs locuteurs` pour commencer la génération. 55 | 56 | ### Regénérer des lignes spécifiques 57 | * **I.** Localisez le sous-titre cible à l'aide du curseur sur la page d'édition. 58 | 59 | * **II.** Modifiez le texte si nécessaire. Les modifications sont enregistrées automatiquement après la régénération. 60 | 61 | * **III.** Cliquez sur 🔄 pour régénérer une seule ligne : 62 | 63 | * Utilise les paramètres du projet s'il n'est pas attribué. 64 | * Utilise les paramètres spécifiques du locuteur s'il est attribué. 65 | * Les projets avec plusieurs locuteurs doivent avoir des locuteurs attribués. 66 | 67 | * **IV.** Après avoir apporté des modifications aux sous-titres, vous pouvez également cliquer sur `Continuer la Génération` pour régénérer la voix des sous-titres modifiés ou dont la synthèse n'a pas été réussie. 68 | 69 | * **V.** Cliquez sur `Reconstituer l'audio` pour recomposer l'audio complet. 70 | 71 | ### C. Rééditer des projets historiques 72 | * Sélectionnez un projet de l'historique de synthèse dans le panneau supérieur. Ensuite, cliquez sur le bouton `Charger`. 73 | * Le reste est évident. 74 | 75 | ### D. Édition des sous-titres 76 | #### 1. Copier 77 | * Copier les sous-titres sélectionnés. 78 | 79 | #### 2. Supprimer 80 | * Supprimer les sous-titres sélectionnés. 81 | 82 | #### 3. Fusionner 83 | * Sélectionnez au moins 2 sous-titres comme points de départ/fin. 84 | * Les sous-titres du point de départ au point de fin seront fusionnés. 85 | 86 | ⚠️ Les modifications ne sont pas enregistrées automatiquement sur le disque immédiatement, vous pouvez donc recharger le projet pour annuler. 87 | 88 | #### 4. Modifier les horodatages 89 | * Éditez les heures de début/fin au format SRT. 90 | 91 | ## 2. Dépannage 92 | * Lorsque vous trouvez un problème : 93 | Décrivez le problème en détail et répertoriez les étapes effectuées pour reproduire l'erreur. 94 | * Visitez [GitHub-issues](https://github.com/YYuX-1145/Srt-AI-Voice-Assistant/issues) pour rapporter un problème ou demander de l'aide (les modèles de Issue vous guideront pour signaler correctement). 95 | """ 96 | -------------------------------------------------------------------------------- /Sava_Utils/translator/ollama.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import gradio as gr 3 | import json 4 | import re 5 | import subprocess 6 | from . import Traducteur 7 | from ..settings import Shared_Option 8 | from ..utils import rc_open_window,rc_bg 9 | from .. import logger, i18n 10 | from tqdm import tqdm 11 | 12 | 13 | class Ollama(Traducteur): 14 | def __init__(self): 15 | self.models = [] 16 | super().__init__("Ollama") 17 | 18 | def update_cfg(self, config): 19 | self.ollama_url = config.query("ollama_url", "") 20 | super().update_cfg(config) 21 | 22 | def get_models(self, url): 23 | try: 24 | if self.server_mode: 25 | result = subprocess.run("ollama list", capture_output=True, text=True, shell=True) # consider using awk 26 | lines = result.stdout.strip().split("\n")[1:] 27 | self.models = [i.split()[0] for i in lines] 28 | # print(self.models) 29 | return gr.update(choices=self.models, value=self.models[0] if len(self.models) != 0 else None) 30 | if url in [None, "", "Default"]: 31 | url = self.ollama_url 32 | response = requests.get(f'{url}/api/tags') 33 | response.raise_for_status() 34 | self.models.clear() 35 | for item in json.loads(response.content)["models"]: 36 | self.models.append(item["name"]) 37 | except Exception as e: 38 | gr.Warning(f"{i18n('Failed to get model list from Ollama')}: {str(e)}") 39 | logger.error(f"{i18n('Failed to get model list from Ollama')}: {str(e)}") 40 | return gr.update(choices=self.models, value=self.models[0] if len(self.models) != 0 else None) 41 | 42 | def unload_model(self, model): 43 | if model in [None, [], ""] or self.server_mode: 44 | gr.Warning(i18n('You must specify the model!')) 45 | return None 46 | rc_bg(f"ollama stop {model}") # && exit 47 | 48 | def api(self, tasks, target_lang, interrupt_flag, model_name, url, custom_prompt, num_history, no_think, file_name: str = "") -> tuple[list[str],str]: 49 | num_history = int(num_history) 50 | if url in [None, "", "Default"] or self.server_mode: 51 | url = self.ollama_url 52 | if model_name in [None, [], ""]: 53 | raise ValueError(i18n('You must specify the model!')) 54 | ret:list[str] = [] 55 | msg = "" 56 | request_data = { 57 | "model": model_name, 58 | "messages": [], 59 | "stream": False, 60 | "think": not no_think, 61 | } 62 | # print(request_data) 63 | for task in tqdm(tasks, desc=f"{i18n('Translating')}: {file_name}", total=len(tasks)): 64 | if interrupt_flag.is_set(): 65 | break 66 | text = "\n\n".join(task) 67 | if custom_prompt: 68 | prompt = custom_prompt + '\n' + text 69 | else: 70 | prompt = f"Please translate the following content into {target_lang}. Strictly preserve the original paragraph structure. Do not include any additional comments or explanations---return only the translated text:\n{text}" 71 | data = {"role": "user", "content": prompt} 72 | request_data["messages"].append(data) 73 | response = requests.post(url=f'{url}/api/chat', json=request_data) 74 | response.raise_for_status() 75 | response_dict = json.loads(response.content)["message"] 76 | # print(response_dict["content"]) 77 | result = re.sub(r'.*?', '', response_dict["content"], flags=re.DOTALL).strip() 78 | 79 | request_data["messages"].append(response_dict) 80 | if len(request_data["messages"]) > 2 * num_history: 81 | request_data["messages"].pop(0) 82 | request_data["messages"].pop(0) 83 | 84 | # print(request_data) 85 | batch = result.split("\n\n") 86 | d = len(task) - len(batch) 87 | if d: 88 | msg += f"{i18n('The language model has probably made a mistake')} @{len(ret)+1}-{len(ret)+len(task)}\n" 89 | if d > 0: 90 | batch += ["" for _ in range(d)] 91 | else: 92 | batch = batch[: len(task)] 93 | ret += batch 94 | return ret, msg 95 | 96 | def register_settings(self): 97 | options = [] 98 | options.append( 99 | Shared_Option( 100 | "ollama_url", 101 | "http://localhost:11434", 102 | gr.Textbox, 103 | label=i18n('Default Request Address for Ollama'), 104 | interactive=True, 105 | ) 106 | ) 107 | return options 108 | 109 | def _UI(self): 110 | if self.server_mode: 111 | self.get_models("") 112 | with gr.Column(): 113 | gr.Markdown(i18n('OLLAMA_NOTICE')) 114 | self.select_model = gr.Dropdown(label=i18n('Select Your Model'), choices=self.models, allow_custom_value=False) 115 | self.api_url = gr.Text(value="Default", interactive=not self.server_mode, label="URL", max_lines=1) 116 | with gr.Row(): 117 | self.unload_model_btn = gr.Button(value=i18n('Unload Model'), visible=not self.server_mode, interactive=not self.server_mode) 118 | self.unload_model_btn.click(self.unload_model, inputs=[self.select_model]) 119 | if not self.server_mode: 120 | self.refresh_model_btn = gr.Button(value="🔄️") 121 | self.refresh_model_btn.click(self.get_models, inputs=[self.api_url], outputs=[self.select_model]) 122 | self.prompt = gr.Text(label=i18n('Custom prompt (enabled when filled in)'), value='', placeholder="Directly translate the following content to English:", interactive=True) 123 | self.num_history = gr.Slider(label=i18n('History Message Limit'), value=2, minimum=0, maximum=10, step=1) 124 | self.no_think_mode = gr.Checkbox(label="No Think", value=True, interactive=True) 125 | # self.start_translate_btn = gr.Button(value=i18n('Start Translating'), variant="primary") 126 | ARGS = [ 127 | self.select_model, 128 | self.api_url, 129 | self.prompt, 130 | self.num_history, 131 | self.no_think_mode, 132 | ] 133 | return ARGS 134 | -------------------------------------------------------------------------------- /Sava_Utils/tts_engines/__init__.py: -------------------------------------------------------------------------------- 1 | from ..base_component import Base_Component 2 | from abc import ABC, abstractmethod 3 | import traceback 4 | from .. import i18n, logger, ext_tab 5 | import gradio as gr 6 | 7 | 8 | class TTSProjet(Base_Component): 9 | 10 | def __init__(self, name, title=None, config=None): 11 | """ 12 | The name parameter must not be empty for extensions. 13 | self.gen_btn is a class member representing the generation button, which is not necessary to be defined. 14 | """ 15 | # self.gen_btn = None 16 | super().__init__(name, title, config) 17 | 18 | @abstractmethod 19 | def api(self, *args, **kwargs) -> bytes | None: 20 | """ 21 | Mandatory. Define the API call code here. 22 | Return value must be binary data of a wav file. If there is an error, return None. 23 | Please pay attention to the return type. 24 | If api returns a path, you can use the following example: 25 | with open(path, "rb") as f: return f.read() 26 | """ 27 | raise NotImplementedError 28 | 29 | def arg_filter(self, *args): 30 | """ 31 | Filters and modifies input arguments. You can raise an exception here when encountering illegal arguments. 32 | Typical usage: Verify if a file path exists; read audio file binary data from the path 33 | Must return the modified arguments (even if no changes were made) 34 | """ 35 | return args 36 | 37 | def before_gen_action(self, *args, **kwargs) -> None: 38 | """ 39 | Perform preprocessing operations before calling the API 40 | Typical usage: Switch GSV models, obtain API key for Microsoft TTS 41 | """ 42 | pass 43 | 44 | def save_action(self, *args, **kwargs): 45 | """ 46 | Pass the filtered arguments to the API call method 47 | """ 48 | return self.api(*args, **kwargs) 49 | 50 | def api_launcher(self) -> None: 51 | """ 52 | Define button for launching API here. 53 | Example: 54 | def start_gsv(): 55 | pass 56 | start_gsv_btn = gr.Button(value="GPT-SoVITS") 57 | start_gsv_btn.click(start_gsv) 58 | """ 59 | pass 60 | 61 | def getUI(self, *args, **kwargs): 62 | x: list[gr.components.Component] = super().getUI(*args, **kwargs) 63 | # if self.gen_btn is None: 64 | # self.gen_btn = gr.Button(value=i18n('Generate Audio'), variant="primary", visible=True) 65 | return x 66 | 67 | 68 | from . import gsv, mstts 69 | from .. import extension_loader 70 | 71 | 72 | class TTS_UI_Loader(Base_Component): 73 | def __init__(self): 74 | GSV = gsv.GSV() 75 | MSTTS = mstts.MSTTS() 76 | self.components: list[TTSProjet] = [GSV, MSTTS] 77 | self.components += extension_loader.load_ext_from_dir(["Sava_Extensions/tts_engine"], ext_enabled_dict=ext_tab["tts_engine"]) 78 | self.project_dict = {i.name: i for i in self.components} 79 | super().__init__() 80 | 81 | def _UI(self, *args, **kwargs): 82 | self.TTS_ARGS = [] 83 | for i in self.components: 84 | with gr.TabItem(i.title): 85 | try: 86 | arg_list = i.getUI() 87 | assert isinstance(arg_list, list) 88 | for c in arg_list: 89 | if not isinstance(c, gr.components.Component): 90 | raise TypeError(type(c).__name__) 91 | self.TTS_ARGS.append(arg_list) 92 | ok = True 93 | except: 94 | ok = False 95 | self.TTS_ARGS.append([]) 96 | logger.error(f"{i18n('Failed to load TTS-Engine UI')}: {i.dirname}") 97 | traceback.print_exc() 98 | if ok and not hasattr(i, "gen_btn"): 99 | setattr(i, "gen_btn", gr.Button(value=i18n('Generate Audio'), variant="primary", visible=True)) 100 | 101 | def get_launch_api_btn(self): 102 | for item in self.components: 103 | item.api_launcher() 104 | 105 | def get_btn_visible_dict(self): 106 | BTN_VISIBLE_DICT = {} 107 | for idx, item in enumerate(self.components): 108 | BTN_VISIBLE_DICT[item.name] = [gr.update(visible=(idx == j)) for j in range(len(self.components))] 109 | BTN_VISIBLE_DICT[None] = BTN_VISIBLE_DICT[item.name] 110 | return BTN_VISIBLE_DICT 111 | 112 | def get_regenbtn(self, inputs, outputs, remake): 113 | visible = True 114 | ret = [] 115 | assert len(self.components) == len(self.TTS_ARGS) 116 | for item, ARGS in zip(self.components, self.TTS_ARGS): 117 | regenbtn = gr.Button(value="🔄️", scale=1, min_width=50, visible=visible) 118 | ret.append(regenbtn) 119 | regenbtn.click(remake, inputs=inputs + ARGS, outputs=outputs) 120 | visible = False 121 | return ret 122 | 123 | def get_all_regen_btn(self, inputs, outputs, gen_multispeaker): # outputs=edit_rows 124 | visible = True 125 | for item in self.components: 126 | all_regen_btn = gr.Button(value=i18n('Continue Generation'), variant="primary", visible=visible, interactive=True, min_width=50) 127 | outputs.append(all_regen_btn) 128 | visible = False 129 | for item, ARGS in zip(outputs[-len(self.components) :], self.TTS_ARGS): 130 | item.click(lambda progress=gr.Progress(track_tqdm=True), *args: gen_multispeaker(*args, remake=True), inputs=inputs + ARGS, outputs=outputs) 131 | 132 | def get_save_spk_btn(self, speaker_dropdown, save_spk): 133 | def make_handler(project_name): 134 | return lambda *args: save_spk(*args, project=project_name) 135 | 136 | visible = True 137 | ret = [] 138 | for item, ARGS in zip(self.components, self.TTS_ARGS): 139 | save_spk_btn = gr.Button(value="💾", min_width=60, scale=0, visible=visible) 140 | ret.append(save_spk_btn) 141 | save_spk_btn.click(make_handler(item.name), [speaker_dropdown] + ARGS, speaker_dropdown) 142 | visible = False 143 | return ret 144 | 145 | def activate(self, inputs, outputs, generate_preprocess): 146 | def make_handler(project_name): 147 | return lambda progress=gr.Progress(track_tqdm=True), *args: generate_preprocess(*args, project=project_name) 148 | # avoid late binding 149 | 150 | for item, ARGS in zip(self.components, self.TTS_ARGS): 151 | if hasattr(item, "gen_btn"): 152 | try: 153 | item.gen_btn.click(make_handler(item.name), inputs=[*inputs, *ARGS], outputs=outputs) 154 | # Stability is not ensured due to the mechanism of gradio. 155 | except: 156 | logger.error(f"Failed to activate tts-engine: {item.name}") 157 | traceback.print_exc() 158 | 159 | 160 | TTS_UI_LOADER = TTS_UI_Loader() 161 | -------------------------------------------------------------------------------- /extra/indexTTS/indextts2_api.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import traceback 4 | 5 | now_dir = os.getcwd() 6 | 7 | import argparse 8 | import signal 9 | import numpy as np 10 | import soundfile as sf 11 | from fastapi import FastAPI, Response 12 | from fastapi.responses import StreamingResponse, JSONResponse 13 | import uvicorn 14 | from io import BytesIO 15 | 16 | from indextts.infer_v2 import IndexTTS2 17 | 18 | from pydantic import BaseModel 19 | 20 | """ 21 | import torchaudio 22 | ### monkey patch 23 | _original_torchaudio_save = torchaudio.save 24 | def patched_save(uri, src, sample_rate, format=None, **kwargs): 25 | if format is None: 26 | format = 'wav' 27 | return _original_torchaudio_save(uri, src, sample_rate, format=format, **kwargs) 28 | torchaudio.save = patched_save 29 | ### 30 | """ 31 | 32 | parser = argparse.ArgumentParser() 33 | parser.add_argument("--model_dir", type=str, default="./checkpoints", help="Model checkpoints directory") 34 | parser.add_argument("-a", "--bind_addr", type=str, default="127.0.0.1", help="default: 127.0.0.1") 35 | parser.add_argument("-p", "--port", type=int, default="9880", help="default: 9880") 36 | parser.add_argument("--fp16", action="store_true", default=False, help="Use FP16 for inference if available") 37 | parser.add_argument("--use_deepspeed", action="store_true", default=False, help="Use Deepspeed to accelerate if available") 38 | parser.add_argument("--cuda_kernel", action="store_true", default=False, help="Use cuda kernel for inference if available") 39 | args = parser.parse_args() 40 | 41 | # device = args.device 42 | port = args.port 43 | host = args.bind_addr 44 | argv = sys.argv 45 | 46 | 47 | tts_pipeline = IndexTTS2( 48 | model_dir=args.model_dir, 49 | cfg_path=os.path.join(args.model_dir, "config.yaml"), 50 | use_fp16=args.fp16, 51 | use_deepspeed=args.use_deepspeed, 52 | use_cuda_kernel=args.cuda_kernel, 53 | ) 54 | 55 | APP = FastAPI() 56 | 57 | 58 | class TTS_Request(BaseModel): 59 | text: str = None 60 | emo_text: str = None 61 | ref_audio_path: str = None 62 | emo_ref_audio_path: str = None 63 | top_k: int = 30 64 | top_p: float = 0.8 65 | temperature: float = 0.8 66 | emo_alpha: float = 0.7 67 | emo_vec: list = [] 68 | normalize_emo_vec: bool = False 69 | speed_factor: float = 1.0 70 | seed: int = -1 71 | parallel_infer: bool = True 72 | repetition_penalty: float = 10 73 | 74 | 75 | def pack_wav(io_buffer: BytesIO, data: np.ndarray, rate: int): 76 | io_buffer = BytesIO() 77 | sf.write(io_buffer, data, rate, format="wav") 78 | return io_buffer 79 | 80 | 81 | def handle_control(command: str): 82 | if command == "restart": 83 | os.execl(sys.executable, sys.executable, *argv) 84 | elif command == "exit": 85 | os.kill(os.getpid(), signal.SIGTERM) 86 | exit(0) 87 | 88 | 89 | def check_params(req: dict): 90 | text: str = req.get("text", "") 91 | ref_audio_path: str = req.get("ref_audio_path", "") 92 | if ref_audio_path in [None, ""]: 93 | return JSONResponse(status_code=400, content={"message": "ref_audio_path is required"}) 94 | if text in [None, ""]: 95 | return JSONResponse(status_code=400, content={"message": "text is required"}) 96 | return None 97 | 98 | 99 | async def tts_handle(req: dict): 100 | check_res = check_params(req) 101 | if check_res is not None: 102 | return check_res 103 | try: 104 | emo_text = req["emo_text"] 105 | use_emo_text = bool(req["emo_text"]) 106 | if emo_text == 'auto': 107 | emo_text = None 108 | use_emo_text = True 109 | emo_vec = req["emo_vec"] 110 | if len(emo_vec) == 0: 111 | emo_vec = None 112 | else: 113 | use_emo_text = False 114 | emo_text = None 115 | if req["normalize_emo_vec"]: 116 | emo_vec = tts_pipeline.normalize_emo_vec(emo_vec) 117 | sampling_rate, wav_data = tts_pipeline.infer( 118 | spk_audio_prompt=req["ref_audio_path"], 119 | emo_audio_prompt=req["emo_ref_audio_path"] if req["emo_ref_audio_path"] else None, 120 | text=req["text"], 121 | emo_text=emo_text, 122 | use_emo_text=use_emo_text, 123 | emo_alpha=req["emo_alpha"], 124 | emo_vector=emo_vec, 125 | top_p=req["top_p"], 126 | top_k=req["top_k"], 127 | temperature=req["temperature"], 128 | repetition_penalty=req["repetition_penalty"], 129 | output_path=None, 130 | ) 131 | return Response(pack_wav(BytesIO(), wav_data, sampling_rate).getvalue(), media_type=f"audio/wav") 132 | except Exception as e: 133 | print("Error:", e) 134 | traceback.print_exc() 135 | return JSONResponse(status_code=400, content={"message": "tts failed", "Exception": str(e)}) 136 | 137 | 138 | @APP.get("/control") 139 | async def control(command: str = None): 140 | if command is None: 141 | return JSONResponse(status_code=400, content={"message": "command is required"}) 142 | handle_control(command) 143 | 144 | 145 | @APP.get("/tts") 146 | async def tts_get_endpoint( 147 | text: str = None, 148 | emo_text: str = None, 149 | ref_audio_path: str = None, 150 | emo_ref_audio_path: str = None, 151 | top_k: int = 30, 152 | top_p: float = 0.8, 153 | temperature: float = 0.8, 154 | emo_alpha: float = 0.7, 155 | normalize_emo_vec: bool = False, 156 | speed_factor: float = 1.0, 157 | seed: int = -1, 158 | parallel_infer: bool = True, 159 | repetition_penalty: float = 10, 160 | ): 161 | req = { 162 | "text": text, 163 | "emo_text": emo_text, 164 | "ref_audio_path": ref_audio_path, 165 | "emo_ref_audio_path": emo_ref_audio_path, 166 | "top_k": top_k, 167 | "top_p": top_p, 168 | "temperature": temperature, 169 | "emo_alpha": float(emo_alpha), 170 | "emo_vec": [], 171 | "normalize_emo_vec": normalize_emo_vec, 172 | "speed_factor": float(speed_factor), 173 | "seed": seed, 174 | "parallel_infer": parallel_infer, 175 | "repetition_penalty": float(repetition_penalty), 176 | } 177 | return await tts_handle(req) 178 | 179 | 180 | @APP.post("/tts") 181 | async def tts_post_endpoint(request: TTS_Request): 182 | req = request.dict() 183 | return await tts_handle(req) 184 | 185 | 186 | # @APP.get("/set_gpt_weights") 187 | # async def set_gpt_weights(weights_path: str = None): 188 | # return JSONResponse(status_code=200, content={"message": "index不需要切换模型"}) 189 | 190 | 191 | # @APP.get("/set_sovits_weights") 192 | # async def set_sovits_weights(weights_path: str = None): 193 | # return JSONResponse(status_code=200, content={"message": "index不需要切换模型"}) 194 | 195 | 196 | if __name__ == "__main__": 197 | try: 198 | if host == "None": 199 | host = None 200 | uvicorn.run(app=APP, host=host, port=port) 201 | except Exception as e: 202 | traceback.print_exc() 203 | os.kill(os.getpid(), signal.SIGTERM) 204 | exit(0) 205 | -------------------------------------------------------------------------------- /Sava_Extensions/tts_engine/BV2/bv2.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import gradio as gr 3 | import os 4 | import time 5 | from . import * 6 | 7 | 8 | class BV2(TTSProjet): # Must inherit from base class. 9 | def __init__(self): 10 | super().__init__("Bert-VITS2", "Bert-VITS2-HiyoriUI", None) 11 | 12 | def update_cfg(self, config: Settings): 13 | self.bv2_dir = config.query("bv2_dir") 14 | self.bv2_pydir = config.query("bv2_pydir") 15 | self.bv2_args = config.query("bv2_args") 16 | super().update_cfg(config) 17 | 18 | def api_launcher(self): 19 | def start_hiyoriui(): 20 | if self.bv2_pydir == "": 21 | gr.Warning(i18n('Please go to the settings page to specify the corresponding environment path and do not forget to save it!')) 22 | return 23 | api_path = os.path.join(self.bv2_dir, "hiyoriUI.py") 24 | command = f'"{self.bv2_pydir}" "{api_path}" {self.bv2_args}' 25 | if not os.path.exists(api_path): 26 | raise gr.Error(f'File NOT Found: {api_path}') 27 | utils.rc_open_window(command=command, dir=self.bv2_dir) 28 | time.sleep(0.1) 29 | gr.Info(f"HiyoriUI{i18n(' has been launched, please ensure the configuration is correct.')}") 30 | 31 | start_hiyoriui_btn = gr.Button(value="HiyoriUI") 32 | start_hiyoriui_btn.click(start_hiyoriui) 33 | 34 | def arg_filter(self, *args): 35 | # actually defining this method is not necessarily here. 36 | language, port, mid, spkid, speaker_name, sdp_ratio, noise_scale, noise_scale_w, length_scale, emo_text = args 37 | pargs = (language, port, mid, spkid, speaker_name, sdp_ratio, noise_scale, noise_scale_w, length_scale, emo_text) 38 | return pargs 39 | 40 | def save_action(self, *args, text: str = None): 41 | language, port, mid, sid, speaker_name, sdp_ratio, noise_scale, noise_scale_w, length_scale, emotion_text = args 42 | sid, port, mid = utils.positive_int(sid, port, mid) 43 | if speaker_name is not None and speaker_name != "": 44 | audio = self.api(text=text, mid=mid, spk_name=speaker_name, sid=None, lang=language, length=length_scale, noise=noise_scale, noisew=noise_scale_w, sdp=sdp_ratio, split=False, style_text=None, style_weight=0, port=port, emotion=emotion_text) 45 | else: 46 | audio = self.api(text=text, mid=mid, spk_name=None, sid=sid, lang=language, length=length_scale, noise=noise_scale, noisew=noise_scale_w, sdp=sdp_ratio, split=False, style_text=None, style_weight=0, port=port, emotion=emotion_text) 47 | return audio 48 | 49 | def api(self, text, mid, spk_name, sid, lang, length, noise, noisew, sdp, emotion, split, style_text, style_weight, port): 50 | try: 51 | API_URL = f'http://127.0.0.1:{port}/voice' 52 | data_json = {"model_id": mid, "speaker_name": spk_name, "speaker_id": sid, "language": lang, "length": length, "noise": noise, "noisew": noisew, "sdp_ratio": sdp, "emotion": emotion, "auto_translate": False, "auto_split": split, "style_text": style_text, "style_weight": style_weight, "text": text} 53 | # print(data_json) 54 | response = requests.get(url=API_URL, params=data_json) 55 | response.raise_for_status() 56 | return response.content 57 | except Exception as e: 58 | err = f"{i18n('An error has occurred. Please check if the API is running correctly. Details')}:{e}" 59 | logger.error(err) 60 | return None 61 | 62 | def switch_spk(self, choice): 63 | if choice == "Speaker_ID": 64 | return gr.update(label="Speaker_ID", value=0, visible=True, interactive=True), gr.update(label="Speaker_Name", visible=False, value="", interactive=True) 65 | else: 66 | return gr.update(label="Speaker_ID", value=0, visible=False, interactive=True), gr.update(label="Speaker_Name", visible=True, value="", interactive=True) 67 | 68 | def register_settings(self): 69 | options = [] 70 | 71 | def auto_env_detect(bv2_pydir: str, config: Settings): 72 | bv2_pydir = bv2_pydir.strip('"') 73 | if bv2_pydir != "": 74 | if os.path.isfile(bv2_pydir): 75 | bv2_pydir = os.path.abspath(bv2_pydir) 76 | elif bv2_pydir == 'python': 77 | pass 78 | else: 79 | gr.Warning(f"{i18n('Error, Invalid Path')}:{bv2_pydir}") 80 | bv2_pydir = "" 81 | else: 82 | if os.path.isfile(os.path.join(current_path, "venv\\python.exe")) and "BERT" in current_path.upper(): 83 | bv2_pydir = os.path.join(current_path, "venv\\python.exe") 84 | logger.info(f"{i18n('Env detected')}: GPT-SoVITS") 85 | else: 86 | bv2_pydir = "" 87 | ################### 88 | if bv2_pydir != "" and config.query("bv2_dir", "") == "": 89 | config.shared_opts["bv2_dir"] = os.path.dirname(os.path.dirname(bv2_pydir)) 90 | return bv2_pydir 91 | 92 | options.append( 93 | Shared_Option( 94 | "bv2_pydir", 95 | "", 96 | gr.Textbox, 97 | auto_env_detect, 98 | label=i18n('Python Interpreter Path for BV2'), 99 | interactive=True, 100 | ) 101 | ) 102 | options.append( 103 | Shared_Option( 104 | "bv2_dir", 105 | "", 106 | gr.Textbox, 107 | lambda v, c: v.strip('"'), 108 | label=i18n('Root Path of BV2'), 109 | interactive=True, 110 | ) 111 | ) 112 | options.append( 113 | Shared_Option( 114 | "bv2_args", 115 | "", 116 | gr.Textbox, 117 | label=i18n('Start Parameters'), 118 | interactive=True, 119 | ) 120 | ) 121 | return options 122 | 123 | def _UI(self): 124 | with gr.Row(): 125 | with gr.Column(): 126 | self.spkchoser = gr.Radio(label=i18n('Select Speaker ID or Speaker Name'), choices=['Speaker_ID', 'Speaker_Name'], value="Speaker_ID") 127 | with gr.Row(): 128 | self.model_id = gr.Number(label="Model_id", value=0, visible=True, interactive=True) 129 | self.spkid = gr.Number(label="Speaker_ID", value=0, visible=True, interactive=True) 130 | self.speaker_name = gr.Textbox(label="Speaker_Name", visible=False, interactive=True) 131 | self.language1 = gr.Dropdown(choices=['ZH', 'JP', 'EN', 'AUTO'], value='ZH', label="Language", interactive=True, allow_custom_value=False) 132 | with gr.Accordion(label=i18n('Advanced Parameters'), open=False): 133 | self.sdp_ratio = gr.Slider(minimum=0, maximum=1, value=0.2, step=0.1, label="SDP Ratio") 134 | self.noise_scale = gr.Slider(minimum=0.1, maximum=2, value=0.6, step=0.1, label="Noise Scale") 135 | self.noise_scale_w = gr.Slider(minimum=0.1, maximum=2, value=0.8, step=0.1, label="Noise Scale W") 136 | self.length_scale = gr.Slider(minimum=0.1, maximum=2, value=1, step=0.1, label="Length Scale") 137 | self.emo_text = gr.Textbox(label="text prompt", interactive=True, value="") 138 | with gr.Row(): 139 | self.api_port1 = gr.Number(label="API Port", value=5000, visible=not self.server_mode, interactive=not self.server_mode) 140 | self.spkchoser.change(self.switch_spk, inputs=[self.spkchoser], outputs=[self.spkid, self.speaker_name]) 141 | # self.gen_btn = gr.Button(value=i18n('Generate Audio'), variant="primary", visible=True) 142 | BV2_ARGS = [ 143 | self.language1, 144 | self.api_port1, 145 | self.model_id, 146 | self.spkid, 147 | self.speaker_name, 148 | self.sdp_ratio, 149 | self.noise_scale, 150 | self.noise_scale_w, 151 | self.length_scale, 152 | self.emo_text, 153 | ] 154 | return BV2_ARGS 155 | -------------------------------------------------------------------------------- /docs/zh_CN/extension_dev.md: -------------------------------------------------------------------------------- 1 | # 插件开发文档 2 | 3 | 本项目支持通过继承基类实现插件扩展。开发者或者大语言模型只需派生自己的插件类并实现必要的方法,即可完成业务逻辑的封装与 UI 集成。 4 | 5 | *这个文档有一半以上是GPT写的并经过我的修改 6 | 7 | --- 8 | 9 | ## 1. 插件结构设计 10 | 11 | 插件必须以类的形式实现,并继承于内置的基类之一: 12 | 13 | * 所有插件最基础的基类是 `Base_Component` 14 | * 如果是**文本转语音类插件**,请继承 `TTSProjet` 15 | * 如果是**翻译器类插件**,请继承 `Traducteur` 16 | * 如果是**一般插件(即扩展插件)**,请继承 `Base_Component` 17 | 18 | --- 19 | 20 | ## 2. 提示:Python 类继承与覆写 21 | 22 | 在Python中,**派生类** 可以继承 **基类** 的方法和属性。你可以通过覆写某些方法来自定义行为,未覆写的部分将自动使用基类中的默认实现。 23 | 24 | 例如: 25 | 26 | ```python 27 | class MyPlugin(TTSProjet): 28 | def api(self, text): 29 | return my_custom_tts(text) 30 | ``` 31 | 32 | 上述代码中,`MyPlugin` 继承自 `TTSProjet`,只覆写了 `api()` 方法,其他未覆写的方法如 `arg_filter()`、`before_gen_action()` 等会自动沿用 `TTSProjet` 的实现。 33 | 34 | --- 35 | 36 | ## 3. 基类说明 37 | ### [完整的示例插件代码看这里](/Sava_Extensions/),以及你参阅内置组件的代码也是一样的 38 | 39 | ### 🔧 Base_Component(用于一般插件) 40 | 41 | 这是所有插件组件的最顶层基类,提供统一的配置接口和 UI 接入方式。 42 | 43 | **重要方法与属性:** 44 | 45 | | 方法 | 说明 | 46 | | ------------------------------- | ------------------------------------------------------------ | 47 | | `__init__(name, title = "", config = None)` | 插件初始化时必须提供唯一的 `name`。如果未指定 `title`,将使用 `name` 作为显示标题。 | 48 | | `update_cfg(config)` | 接收全局配置对象 `Settings`,可调用 `config.query(key, default)` 获取共享配置。 | 49 | | `register_settings()` | 返回共享配置项列表(可选),类型为 `list[Shared_Option]`。 | 50 | | `getUI()` | 获取组件 UI,内部会调用 `_UI()`,**不建议覆写此方法**。 | 51 | | `_UI()` | 抽象方法,必须由子类实现,用于构建 UI 组件。**对于TTS引擎和翻译器,必须返回包含gradio组件的列表作为参数输入。** | 52 | | `__new__()` | 实现了单例模式,防止插件被重复实例化,**不得覆写**。 | 53 | 54 | --- 55 | 56 | ### 🔊 TTSProjet(用于 TTS 插件) 57 | `TTSProjet`(**继承自`Base_Component`**)是用于构建文本转语音插件的基础框架,提供 API 调用、参数过滤、执行流程等典型钩子。 58 | 59 | **推荐覆写的方法:** 60 | 61 | | 方法 | 说明 | 62 | | ------------------------------------ | ----------------------------------------- | 63 | |`__init__`等基类方法 |见`Base_Component` | 64 | | `api(*args, **kwargs)` | 必须实现。处理 API 调用,返回音频的二进制数据(如 `.wav` 文件内容)。 | 65 | | `arg_filter(*args)` | 可选。对输入参数进行验证与转换,例如将numpy格式音频转为二进制数据或存储为文件。必须返回一个参数元组,它将被输入`save_action`方法。 | 66 | | `before_gen_action(*args, **kwargs)` | 可选。在调用 `api()` 前执行的预处理逻辑,例如加载模型、配置环境等。 | 67 | | `save_action(*args, **kwargs)` | 默认调用 `self.api()`,可根据需求重写。必须返回wav音频二进制数据或None(如果遭遇错误)| 68 | | `api_launcher()` | (可选)用于创建快捷启动API服务的按钮控件UI, 直接定义按钮并绑定触发事件,无需返回值 | 69 | | `_UI()` | 必须实现,构建 UI 界面布局。你无需定义生成按钮`self.btn`。 **必须返回包含gradio组件的列表作为参数输入。** | 70 | 71 | --- 72 | ### 🌍 Traducteur(用于字幕翻译插件) 73 | 74 | `Traducteur` (**继承自`Base_Component`**)是用于构建翻译插件的基类,适用于处理多段字幕文本的自动翻译流程。它封装了批处理任务构建逻辑,并要求开发者实现核心的 `api()` 方法。 75 | 76 | **推荐覆写的方法:** 77 | 78 | | 方法 | 说明 | 79 | | ------------------------------------------------------------------------ | --------------------------------------------------------------- | 80 | |`__init__`等基类方法 |见`Base_Component` | 81 | | `construct_tasks(subtitles, batch_size=1)` | 将字幕条目分批组织为任务列表,默认按 `batch_size` 聚合。每个任务是一个字符串列表,内容为清洗后的字幕文本。可以选择覆写。 | 82 | | `api(tasks, target_lang, interrupt_flag, *args, file_name="", **kwargs)` | 必须实现。处理翻译逻辑,接收 `construct_tasks()` 返回的任务列表,并返回翻译后的字符串列表(可以附加消息)。 | 83 | 84 | --- 85 | 86 | #### 📦 self.construct_tasks(subtitles, batch_size=1) 87 | 88 | 默认实现是用于将字幕列表拆分为批次任务,但愿能提升一点上下文联系。每个任务是一小组字符串,最终以 `list[list[str]]` 返回: 89 | 90 | ```python 91 | # batch_size = 2 92 | # Subtitle 1 : Hello! 93 | # Subtitle 2 : 你好! 94 | # Subtitle 3 : Bonjour! 95 | [ 96 | ["Hello!", "你好!"], 97 | ["Bonjour!"] 98 | ] 99 | ``` 100 | 101 | **参数说明:** 102 | 103 | | 参数 | 说明 | 104 | | ------------ | ------------------------------ | 105 | | `subtitles` | 输入字幕对象列表,每项需具备 `.text` 字段。 | 106 | | `batch_size` | 每批任务的字幕数目。默认值为 `1`,代表一条字幕一组。 | 107 | | 返回值 | `list[list[str]]`,每组作为翻译任务的输入。 | 108 | 109 | --- 110 | 111 | #### 🧠 self.api(...) 112 | 113 | 插件核心翻译逻辑的实现方法,必须由派生类覆写。 114 | 115 | **参数说明:** 116 | 117 | | 参数 | 说明 | 118 | | ---------------- | ------------------------------------------------------------ | 119 | | `tasks` | 来自 `construct_tasks()` 的任务批次,是一个二维字符串列表。 | 120 | | `target_lang` | 目标语言的字符串表示,未来可能有所调整。["中文", "English", "日本語", "한국어", "Français"] | 121 | | `interrupt_flag` | 中断控制对象,类型为 `Flag`,可使用 `interrupt_flag.is_set()` 判断是否被用户取消任务。 | 122 | | `*args` | 从 `_UI()` 方法中返回的参数输入。用于传入用户输入值,如模型选项等。 | 123 | | `file_name` | 可选文件名,用于在进度提示中标识当前处理的字幕文件。 | 124 | | `**kwargs` | 其他可选参数,现在暂时没用。 | 125 | 126 | **返回值:** 127 | 128 | * 必须返回一个 `list[str]`,对应翻译后的字幕文本(按任务顺序展开); 129 | * 也可返回 `tuple[list[str], str]`,附带提示信息(如 `"模型出现了幻觉"`)。 130 | 131 | --- 132 | 133 | #### 🚦 Flag:中断控制建议工具 134 | 135 | 框架提供了 `Flag` 类用于中断任务,翻译插件应在耗时操作中定期检查是否收到中断请求: 136 | 137 | ```python 138 | if interrupt_flag.is_set(): 139 | return [] 140 | ``` 141 | 142 | 该标志由用户控制任务取消按钮触发,开发者无需自行实现逻辑,只需检查并响应即可。 143 | 144 | --- 145 | 146 | ## 4. 插件注册机制 147 | 148 | ### 📁 插件目录结构(Plugin Directory Structure) 149 | 150 | * 所有插件模块应放置于 `Sava_Extensions/<插件类型>/<插件名称>/` 目录中。 151 | * 每个插件必须包含 `__init__.py` 文件,文件中必须定义 `register(context)` 函数。 152 | 153 | 示例: 154 | 155 | ``` 156 | Sava_Extensions/ 157 | └── tts_engine/ 158 | ├── Custom_OLD/ 159 | │ ├── __init__.py 160 | │ └── custom.py 161 | └── MyPlugin/ 162 | ├── __init__.py 163 | └── ... 164 | ``` 165 | 166 | --- 167 | 168 | 插件需要通过 `__init__.py` 文件中的 `register(context)` 方法进行注册: 169 | 170 | ```python 171 | def register(context): 172 | globals().update(context) # 将依赖注入模块命名空间 173 | from .custom import Custom 174 | return Custom() # 返回插件类实例 175 | ``` 176 | 177 | 框架将调用 `register()` 并传入依赖上下文。 178 | 179 | --- 180 | 181 | ## 5. 插件 UI 构建机制 182 | 183 | 每个插件必须通过实现 `_UI()` 方法来定义自己的 UI 界面结构。该方法通常返回一组 Gradio 组件,例如: 184 | 185 | ```python 186 | def _UI(self): 187 | with gr.Column(): 188 | self.text_input = gr.Textbox(label="Input Text") 189 | self.gen_btn = gr.Button("Generate") 190 | return [self.text_input] #TTS/翻译类插件需要返回参数列表 191 | ``` 192 | 193 | `getUI()` 方法已由基类管理,内部确保 UI 只构建一次,并可自动注入触发按钮(如 `gen_btn`)。 194 | 195 | --- 196 | 197 | ## 6. 共享配置项:Shared_Option 198 | 199 | `Shared_Option` 用于 **声明与注册全局共享配置项**。 200 | 插件或核心组件可以把自己的可配置参数统一暴露给「设置面板」,并在运行期通过 `Settings` 对象取回用户设定的值。 201 | 202 | --- 203 | 204 | ### 1. 工作流程概览 205 | 206 | 1. **声明** 207 | 在插件内部创建 `Shared_Option` 实例,描述该配置项的键名、默认值、UI 组件类型与(可选)校验函数。 208 | 209 | 2. **注册** 210 | 将所有 `Shared_Option` 对象以**列表**形式返回给 `register_settings()`,框架会自动生成对应的 Gradio 表单控件,并把用户输入写入全局 `Settings`。 211 | 212 | 3. **读取** 213 | 在插件任意位置,通过 `config.query("your_key", default)` 读取用户设置;或在校验函数里使用第二个参数 `config` 访问其他共享项。 214 | 215 | --- 216 | 217 | ### 2. 构造函数参数 218 | 219 | | 参数 | 说明 | 220 | | --------------------------------------------------- | ------------------------------------------------------------------------ | 221 | | `key: str` | **唯一键名**,用于存储与检索该配置值。 | 222 | | `default_value: Any` | 默认值。初次运行或重置时生效。 | 223 | | `gr_component_type: gr.components.FormComponent` | 指定 Gradio 表单组件类型(`gr.Textbox`、`gr.Slider`、`gr.Dropdown` 等)。 | 224 | | `validator: Callable[[Any, Settings], Any] \| None` | (可选)验证 / 转换函数。接收 **用户输入值** 与 **全局 Settings**,必须返回最终写入设置的值;如发现非法输入,可抛出异常。 | 225 | | `**gr_kwargs` | 其他将直接传给组件构造函数的关键字参数,如 `label`、`choices`、`interactive` 等。 | 226 | 227 | --- 228 | 229 | ### 3. 典型示例 230 | --- 231 | ```python 232 | # 验证函数,第一个参数接收当前设置项,第二个参数为全局设置项 233 | def validate_path(value, config): 234 | if not os.path.isfile(value): 235 | raise ValueError("Invalid path") # 对非法参数你可以抛出异常,这将让此项回到默认值 236 | else: 237 | value = value.strip() #也可以直接对值进行处理,也可以通过config访问全局设置。 238 | return value # 必须返回修改后的值,无论是否修改过 239 | 240 | def register_settings(self): 241 | return [ 242 | Shared_Option( 243 | key="gsv_pydir", 244 | default_value="", 245 | gr_component_type=gr.Textbox, 246 | validator=validate_path, 247 | label="Path to Python interpreter", 248 | interactive=True, 249 | ) 250 | ] 251 | ``` 252 | * **验证器** `validate_path` 在保存前执行;若抛出异常,该项将重置为默认值。你可以对值进行修改。 253 | * 对同一配置项,如需跨插件共享,可 **使用相同的 `key`**,不同插件读取的将是同一存储值。 254 | * UI 组件所有属性 (`choices`, `value`, `visible` 等) 均可通过 `gr_kwargs` 直接传入,做到「声明即 UI」。 255 | * **提示**:在 `validator` 内也可以调用 `config.query(key,default_value)` 访问其他共享项,实现 **相互依赖校验**(例如多个路径必须位于同一磁盘分区)。 256 | --- 257 | ### 获取更新后的值 258 | ```python 259 | class GSV(TTSProjet): 260 | def update_cfg(self, config: Settings): 261 | self.gsv_fallback = config.query("gsv_fallback") 262 | self.gsv_dir = config.query("gsv_dir") 263 | self.gsv_pydir = config.query("gsv_pydir") 264 | self.gsv_args = config.query("gsv_args") 265 | super().update_cfg(config) 266 | ``` 267 | 按照以上规范即可将任意插件配置项无缝接入到全局设置系统,实现 **统一 UI、集中管理、即时生效**。 268 | 269 | --- 270 | -------------------------------------------------------------------------------- /Sava_Utils/man/zh_CN/extension_dev.py: -------------------------------------------------------------------------------- 1 | extension_dev = r""" 2 | # 插件开发文档 3 | 4 | 本项目支持通过继承基类实现插件扩展。开发者或者大语言模型只需派生自己的插件类并实现必要的方法,即可完成业务逻辑的封装与 UI 集成。 5 | 6 | *这个文档有一半以上是GPT写的并经过我的修改 7 | 8 | --- 9 | 10 | ## 1. 插件结构设计 11 | 12 | 插件必须以类的形式实现,并继承于内置的基类之一: 13 | 14 | * 所有插件最基础的基类是 `Base_Component` 15 | * 如果是**文本转语音类插件**,请继承 `TTSProjet` 16 | * 如果是**翻译器类插件**,请继承 `Traducteur` 17 | * 如果是**一般插件(即扩展插件)**,请继承 `Base_Component` 18 | 19 | --- 20 | 21 | ## 2. 提示:Python 类继承与覆写 22 | 23 | 在Python中,**派生类** 可以继承 **基类** 的方法和属性。你可以通过覆写某些方法来自定义行为,未覆写的部分将自动使用基类中的默认实现。 24 | 25 | 例如: 26 | 27 | ```python 28 | class MyPlugin(TTSProjet): 29 | def api(self, text): 30 | return my_custom_tts(text) 31 | ``` 32 | 33 | 上述代码中,`MyPlugin` 继承自 `TTSProjet`,只覆写了 `api()` 方法,其他未覆写的方法如 `arg_filter()`、`before_gen_action()` 等会自动沿用 `TTSProjet` 的实现。 34 | 35 | --- 36 | 37 | ## 3. 基类说明 38 | ### [完整的示例插件代码看这里](/Sava_Extensions/),以及你参阅内置组件的代码也是一样的 39 | 40 | ### 🔧 Base_Component(用于一般插件) 41 | 42 | 这是所有插件组件的最顶层基类,提供统一的配置接口和 UI 接入方式。 43 | 44 | **重要方法与属性:** 45 | 46 | | 方法 | 说明 | 47 | | ------------------------------- | ------------------------------------------------------------ | 48 | | `__init__(name, title = "", config = None)` | 插件初始化时必须提供唯一的 `name`。如果未指定 `title`,将使用 `name` 作为显示标题。 | 49 | | `update_cfg(config)` | 接收全局配置对象 `Settings`,可调用 `config.query(key, default)` 获取共享配置。 | 50 | | `register_settings()` | 返回共享配置项列表(可选),类型为 `list[Shared_Option]`。 | 51 | | `getUI()` | 获取组件 UI,内部会调用 `_UI()`,**不建议覆写此方法**。 | 52 | | `_UI()` | 抽象方法,必须由子类实现,用于构建 UI 组件。**对于TTS引擎和翻译器,必须返回包含gradio组件的列表作为参数输入。** | 53 | | `__new__()` | 实现了单例模式,防止插件被重复实例化,**不得覆写**。 | 54 | 55 | --- 56 | 57 | ### 🔊 TTSProjet(用于 TTS 插件) 58 | `TTSProjet`(**继承自`Base_Component`**)是用于构建文本转语音插件的基础框架,提供 API 调用、参数过滤、执行流程等典型钩子。 59 | 60 | **推荐覆写的方法:** 61 | 62 | | 方法 | 说明 | 63 | | ------------------------------------ | ----------------------------------------- | 64 | |`__init__`等基类方法 |见`Base_Component` | 65 | | `api(*args, **kwargs)` | 必须实现。处理 API 调用,返回音频的二进制数据(如 `.wav` 文件内容)。 | 66 | | `arg_filter(*args)` | 可选。对输入参数进行验证与转换,例如将numpy格式音频转为二进制数据或存储为文件。必须返回一个参数元组,它将被输入`save_action`方法。 | 67 | | `before_gen_action(*args, **kwargs)` | 可选。在调用 `api()` 前执行的预处理逻辑,例如加载模型、配置环境等。 | 68 | | `save_action(*args, **kwargs)` | 默认调用 `self.api()`,可根据需求重写。必须返回wav音频二进制数据或None(如果遭遇错误)| 69 | | `api_launcher()` | (可选)用于创建快捷启动API服务的按钮控件UI, 直接定义按钮并绑定触发事件,无需返回值 | 70 | | `_UI()` | 必须实现,构建 UI 界面布局。你无需定义生成按钮`self.btn`。 **必须返回包含gradio组件的列表作为参数输入。** | 71 | 72 | --- 73 | ### 🌍 Traducteur(用于字幕翻译插件) 74 | 75 | `Traducteur` (**继承自`Base_Component`**)是用于构建翻译插件的基类,适用于处理多段字幕文本的自动翻译流程。它封装了批处理任务构建逻辑,并要求开发者实现核心的 `api()` 方法。 76 | 77 | **推荐覆写的方法:** 78 | 79 | | 方法 | 说明 | 80 | | ------------------------------------------------------------------------ | --------------------------------------------------------------- | 81 | |`__init__`等基类方法 |见`Base_Component` | 82 | | `construct_tasks(subtitles, batch_size=1)` | 将字幕条目分批组织为任务列表,默认按 `batch_size` 聚合。每个任务是一个字符串列表,内容为清洗后的字幕文本。可以选择覆写。 | 83 | | `api(tasks, target_lang, interrupt_flag, *args, file_name="", **kwargs)` | 必须实现。处理翻译逻辑,接收 `construct_tasks()` 返回的任务列表,并返回翻译后的字符串列表(可以附加消息)。 | 84 | 85 | --- 86 | 87 | #### 📦 self.construct_tasks(subtitles, batch_size=1) 88 | 89 | 默认实现是用于将字幕列表拆分为批次任务,但愿能提升一点上下文联系。每个任务是一小组字符串,最终以 `list[list[str]]` 返回: 90 | 91 | ```python 92 | # batch_size = 2 93 | # Subtitle 1 : Hello! 94 | # Subtitle 2 : 你好! 95 | # Subtitle 3 : Bonjour! 96 | [ 97 | ["Hello!", "你好!"], 98 | ["Bonjour!"] 99 | ] 100 | ``` 101 | 102 | **参数说明:** 103 | 104 | | 参数 | 说明 | 105 | | ------------ | ------------------------------ | 106 | | `subtitles` | 输入字幕对象列表,每项需具备 `.text` 字段。 | 107 | | `batch_size` | 每批任务的字幕数目。默认值为 `1`,代表一条字幕一组。 | 108 | | 返回值 | `list[list[str]]`,每组作为翻译任务的输入。 | 109 | 110 | --- 111 | 112 | #### 🧠 self.api(...) 113 | 114 | 插件核心翻译逻辑的实现方法,必须由派生类覆写。 115 | 116 | **参数说明:** 117 | 118 | | 参数 | 说明 | 119 | | ---------------- | ------------------------------------------------------------ | 120 | | `tasks` | 来自 `construct_tasks()` 的任务批次,是一个二维字符串列表。 | 121 | | `target_lang` | 目标语言的字符串表示,未来可能有所调整。["中文", "English", "日本語", "한국어", "Français"] | 122 | | `interrupt_flag` | 中断控制对象,类型为 `Flag`,可使用 `interrupt_flag.is_set()` 判断是否被用户取消任务。 | 123 | | `*args` | 从 `_UI()` 方法中返回的参数输入。用于传入用户输入值,如模型选项等。 | 124 | | `file_name` | 可选文件名,用于在进度提示中标识当前处理的字幕文件。 | 125 | | `**kwargs` | 其他可选参数,现在暂时没用。 | 126 | 127 | **返回值:** 128 | 129 | * 必须返回一个 `list[str]`,对应翻译后的字幕文本(按任务顺序展开); 130 | * 也可返回 `tuple[list[str], str]`,附带提示信息(如 `"模型出现了幻觉"`)。 131 | 132 | --- 133 | 134 | #### 🚦 Flag:中断控制建议工具 135 | 136 | 框架提供了 `Flag` 类用于中断任务,翻译插件应在耗时操作中定期检查是否收到中断请求: 137 | 138 | ```python 139 | if interrupt_flag.is_set(): 140 | return [] 141 | ``` 142 | 143 | 该标志由用户控制任务取消按钮触发,开发者无需自行实现逻辑,只需检查并响应即可。 144 | 145 | --- 146 | 147 | ## 4. 插件注册机制 148 | 149 | ### 📁 插件目录结构(Plugin Directory Structure) 150 | 151 | * 所有插件模块应放置于 `Sava_Extensions/<插件类型>/<插件名称>/` 目录中。 152 | * 每个插件必须包含 `__init__.py` 文件,文件中必须定义 `register(context)` 函数。 153 | 154 | 示例: 155 | 156 | ``` 157 | Sava_Extensions/ 158 | └── tts_engine/ 159 | ├── Custom_OLD/ 160 | │ ├── __init__.py 161 | │ └── custom.py 162 | └── MyPlugin/ 163 | ├── __init__.py 164 | └── ... 165 | ``` 166 | 167 | --- 168 | 169 | 插件需要通过 `__init__.py` 文件中的 `register(context)` 方法进行注册: 170 | 171 | ```python 172 | def register(context): 173 | globals().update(context) # 将依赖注入模块命名空间 174 | from .custom import Custom 175 | return Custom() # 返回插件类实例 176 | ``` 177 | 178 | 框架将调用 `register()` 并传入依赖上下文。 179 | 180 | --- 181 | 182 | ## 5. 插件 UI 构建机制 183 | 184 | 每个插件必须通过实现 `_UI()` 方法来定义自己的 UI 界面结构。该方法通常返回一组 Gradio 组件,例如: 185 | 186 | ```python 187 | def _UI(self): 188 | with gr.Column(): 189 | self.text_input = gr.Textbox(label="Input Text") 190 | self.gen_btn = gr.Button("Generate") 191 | return [self.text_input] #TTS/翻译类插件需要返回参数列表 192 | ``` 193 | 194 | `getUI()` 方法已由基类管理,内部确保 UI 只构建一次,并可自动注入触发按钮(如 `gen_btn`)。 195 | 196 | --- 197 | 198 | ## 6. 共享配置项:Shared_Option 199 | 200 | `Shared_Option` 用于 **声明与注册全局共享配置项**。 201 | 插件或核心组件可以把自己的可配置参数统一暴露给「设置面板」,并在运行期通过 `Settings` 对象取回用户设定的值。 202 | 203 | --- 204 | 205 | ### 1. 工作流程概览 206 | 207 | 1. **声明** 208 | 在插件内部创建 `Shared_Option` 实例,描述该配置项的键名、默认值、UI 组件类型与(可选)校验函数。 209 | 210 | 2. **注册** 211 | 将所有 `Shared_Option` 对象以**列表**形式返回给 `register_settings()`,框架会自动生成对应的 Gradio 表单控件,并把用户输入写入全局 `Settings`。 212 | 213 | 3. **读取** 214 | 在插件任意位置,通过 `config.query("your_key", default)` 读取用户设置;或在校验函数里使用第二个参数 `config` 访问其他共享项。 215 | 216 | --- 217 | 218 | ### 2. 构造函数参数 219 | 220 | | 参数 | 说明 | 221 | | --------------------------------------------------- | ------------------------------------------------------------------------ | 222 | | `key: str` | **唯一键名**,用于存储与检索该配置值。 | 223 | | `default_value: Any` | 默认值。初次运行或重置时生效。 | 224 | | `gr_component_type: gr.components.FormComponent` | 指定 Gradio 表单组件类型(`gr.Textbox`、`gr.Slider`、`gr.Dropdown` 等)。 | 225 | | `validator: Callable[[Any, Settings], Any] \| None` | (可选)验证 / 转换函数。接收 **用户输入值** 与 **全局 Settings**,必须返回最终写入设置的值;如发现非法输入,可抛出异常。 | 226 | | `**gr_kwargs` | 其他将直接传给组件构造函数的关键字参数,如 `label`、`choices`、`interactive` 等。 | 227 | 228 | --- 229 | 230 | ### 3. 典型示例 231 | --- 232 | ```python 233 | # 验证函数,第一个参数接收当前设置项,第二个参数为全局设置项 234 | def validate_path(value, config): 235 | if not os.path.isfile(value): 236 | raise ValueError("Invalid path") # 对非法参数你可以抛出异常,这将让此项回到默认值 237 | else: 238 | value = value.strip() #也可以直接对值进行处理,也可以通过config访问全局设置。 239 | return value # 必须返回修改后的值,无论是否修改过 240 | 241 | def register_settings(self): 242 | return [ 243 | Shared_Option( 244 | key="gsv_pydir", 245 | default_value="", 246 | gr_component_type=gr.Textbox, 247 | validator=validate_path, 248 | label="Path to Python interpreter", 249 | interactive=True, 250 | ) 251 | ] 252 | ``` 253 | * **验证器** `validate_path` 在保存前执行;若抛出异常,该项将重置为默认值。你可以对值进行修改。 254 | * 对同一配置项,如需跨插件共享,可 **使用相同的 `key`**,不同插件读取的将是同一存储值。 255 | * UI 组件所有属性 (`choices`, `value`, `visible` 等) 均可通过 `gr_kwargs` 直接传入,做到「声明即 UI」。 256 | * **提示**:在 `validator` 内也可以调用 `config.query(key,default_value)` 访问其他共享项,实现 **相互依赖校验**(例如多个路径必须位于同一磁盘分区)。 257 | --- 258 | ### 获取更新后的值 259 | ```python 260 | class GSV(TTSProjet): 261 | def update_cfg(self, config: Settings): 262 | self.gsv_fallback = config.query("gsv_fallback") 263 | self.gsv_dir = config.query("gsv_dir") 264 | self.gsv_pydir = config.query("gsv_pydir") 265 | self.gsv_args = config.query("gsv_args") 266 | super().update_cfg(config) 267 | ``` 268 | 按照以上规范即可将任意插件配置项无缝接入到全局设置系统,实现 **统一 UI、集中管理、即时生效**。 269 | 270 | --- 271 | 272 | """ 273 | -------------------------------------------------------------------------------- /tools/wav2srt.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import subprocess 4 | from tqdm import tqdm 5 | import librosa 6 | import argparse 7 | import numpy as np 8 | import torch 9 | from tools.slicer2 import Slicer 10 | 11 | try: 12 | from tools.uvr5.mdxnet import MDXNetDereverb 13 | from tools.uvr5.vr import AudioPre, AudioPreDeEcho 14 | from tools.uvr5.bsroformer import Roformer_Loader 15 | 16 | UVR5_AVAILABLE = True 17 | except ImportError: 18 | UVR5_AVAILABLE = False 19 | 20 | current_directory = os.path.dirname(os.path.abspath(__file__)) 21 | parser = argparse.ArgumentParser(add_help=False) 22 | parser.add_argument("-input", nargs='+', default=None, type=str) 23 | parser.add_argument("-output_dir", default=None, type=str) 24 | parser.add_argument("-engine", default="whisper", type=str) 25 | parser.add_argument("--uvr_model", default=None, type=str) 26 | parser.add_argument("--whisper_size", default="large-v3-turbo", type=str) 27 | parser.add_argument("--threshold", default=-27, type=float) 28 | parser.add_argument("--min_length", default=2000, type=int) 29 | parser.add_argument("--min_interval", default=300, type=int) 30 | parser.add_argument("--hop_size", default=20, type=int) 31 | parser.add_argument("--max_sil_kept", default=1000, type=int) 32 | args = parser.parse_args() 33 | 34 | 35 | def basename_no_ext(path: str): 36 | return os.path.basename(os.path.splitext(path)[0]) 37 | 38 | 39 | def init_ASRmodels(): 40 | global args, model 41 | if args.engine == "whisper": 42 | import faster_whisper 43 | from faster_whisper import WhisperModel 44 | 45 | model_path = f'tools/asr/models/faster-whisper-{args.whisper_size}' 46 | os.makedirs(model_path, exist_ok=True) 47 | if os.listdir(model_path) == []: 48 | print("Downloading faster whisper model...") 49 | os.makedirs(model_path, exist_ok=True) 50 | faster_whisper.download_model(size_or_id=args.whisper_size, output_dir=model_path) 51 | try: 52 | print("Loading faster whisper model:", model_path) 53 | model = WhisperModel(model_path, device='cuda') 54 | except Exception as e: 55 | print(e) 56 | print("下载或加载出错。如果不能下载,请前往HF镜像站手动下载faster whisper模型") 57 | if args.whisper_size == "large-v3": 58 | model.feature_extractor.mel_filters = model.feature_extractor.get_mel_filters(model.feature_extractor.sampling_rate, model.feature_extractor.n_fft, n_mels=128) 59 | else: 60 | from funasr import AutoModel 61 | 62 | print("Loading FunASR models...") 63 | path_asr = 'tools/asr/models/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-pytorch' 64 | path_asr = path_asr if os.path.exists(path_asr) else "iic/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-pytorch" 65 | model_revision = "v2.0.4" 66 | path_vad = 'tools/asr/models/speech_fsmn_vad_zh-cn-16k-common-pytorch' 67 | path_punc = 'tools/asr/models/punc_ct-transformer_zh-cn-common-vocab272727-pytorch' 68 | path_vad = path_vad if os.path.exists(path_vad) else "iic/speech_fsmn_vad_zh-cn-16k-common-pytorch" 69 | path_punc = path_punc if os.path.exists(path_punc) else "iic/punc_ct-transformer_zh-cn-common-vocab272727-pytorch" 70 | vad_model_revision = punc_model_revision = "v2.0.4" 71 | # sync with gsv 72 | model = AutoModel( 73 | model=path_asr, 74 | model_revision=model_revision, 75 | vad_model=path_vad, 76 | vad_model_revision=vad_model_revision, 77 | punc_model=path_punc, 78 | punc_model_revision=punc_model_revision, 79 | ) 80 | 81 | 82 | def whisper_transcribe(audio, sr): 83 | global model 84 | audio = librosa.resample(audio, orig_sr=sr, target_sr=16000) 85 | # lang = ['zh', 'ja', 'en'] 86 | try: 87 | segments, info = model.transcribe(audio=audio.astype(np.float32), beam_size=5, vad_filter=False, language=None) 88 | text = "" 89 | # assert info.language in lang 90 | for seg in segments: 91 | text += seg.text 92 | return text 93 | except Exception as e: 94 | print(e) 95 | 96 | 97 | def funasr_transcribe(audio, sr): 98 | global model 99 | audio = librosa.resample(audio, orig_sr=sr, target_sr=16000) 100 | text = model.generate(input=audio)[0]["text"] 101 | return text 102 | 103 | 104 | def transcribe(wav_paths, save_root): 105 | for audio_path in tqdm(wav_paths, desc='Transcribing...'): 106 | audio, sr = librosa.load(audio_path, sr=None) 107 | slicer = Slicer( 108 | sr=sr, 109 | threshold=int(args.threshold), # 音量小于这个值视作静音的备选切割点 110 | min_length=int(args.min_length), # 每段最小多长,如果第一段太短一直和后面段连起来直到超过这个值 111 | min_interval=int(args.min_interval), # 最短切割间隔 112 | hop_size=int(args.hop_size), # 怎么算音量曲线,越小精度越大计算量越高(不是精度越大效果越好) 113 | max_sil_kept=int(args.max_sil_kept), # 切完后静音最多留多长 114 | ) 115 | srt = [] 116 | for chunk, start, end in slicer.slice(audio): # start和end是帧数 117 | start = start / sr 118 | end = end / sr 119 | try: 120 | if args.engine == "whisper": 121 | text = whisper_transcribe(chunk, sr) 122 | else: 123 | text = funasr_transcribe(chunk, sr) 124 | except Exception as e: 125 | print(e) 126 | continue 127 | srt.append((start, end, text)) 128 | srt_content = [] 129 | idx = 0 130 | for i in srt: 131 | idx += 1 132 | start, end, text = i 133 | srt_content.append(str(idx) + "\n") 134 | srt_content.append(f"{to_time(start)} --> {to_time(end)}" + "\n") 135 | srt_content.append(text + "\n") 136 | srt_content.append("\n") 137 | 138 | save_path = save_root if save_root is not None else os.path.dirname(audio_path) 139 | if os.path.basename(audio_path).startswith('vocal_'): 140 | savename = os.path.join(save_path, f"{basename_no_ext(audio_path)[6:]}.srt") 141 | else: 142 | savename = os.path.join(save_path, f"{basename_no_ext(audio_path)}.srt") 143 | with open(savename, "w", encoding="utf-8") as f: 144 | f.writelines(srt_content) 145 | 146 | 147 | def uvr(model_name, input_paths, save_root, agg=10, format0='wav'): 148 | if not UVR5_AVAILABLE: 149 | print("UVR5 is not available.") 150 | return input_paths 151 | weight_uvr5_root = "tools/uvr5/uvr5_weights" 152 | try: 153 | is_hp3 = "HP3" in model_name 154 | if model_name == "onnx_dereverb_By_FoxJoy": 155 | pre_fun = MDXNetDereverb(15) 156 | elif "roformer" in model_name.lower(): 157 | func = Roformer_Loader 158 | pre_fun = func( 159 | model_path=os.path.join(weight_uvr5_root, model_name + ".ckpt"), 160 | config_path=os.path.join(weight_uvr5_root, model_name + ".yaml"), 161 | device='cuda' if torch.cuda.is_available() else 'cpu', 162 | is_half=True, 163 | ) 164 | else: 165 | func = AudioPre if "DeEcho" not in model_name else AudioPreDeEcho 166 | pre_fun = func( 167 | agg=int(agg), 168 | model_path=os.path.join(weight_uvr5_root, model_name + ".pth"), 169 | device='cuda' if torch.cuda.is_available() else 'cpu', 170 | is_half=True, 171 | ) 172 | ret = [] 173 | os.makedirs("TEMP", exist_ok=True) 174 | for input_path in tqdm(input_paths, desc='Denoising...'): 175 | save_path = save_root if save_root is not None else os.path.dirname(input_path) 176 | tmp_path = f"TEMP/{basename_no_ext(input_path)}_reformatted.wav" 177 | try: 178 | assert subprocess.run(f'ffmpeg -i "{input_path}" -vn -acodec pcm_s16le -ac 2 -ar 44100 "{tmp_path}" -y', shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE).returncode == 0 179 | except: 180 | print(f"FFmpeg Error: {input_path}") 181 | continue 182 | try: 183 | pre_fun._path_audio_(tmp_path, save_path, save_path, format0, is_hp3) 184 | out_p = os.path.join(save_path, f"vocal_{os.path.basename(tmp_path)}_{agg}.wav") 185 | # (ins/vocal)_audio|_10_reformatted.wav.wav" 17 + 4 + len(agg) 186 | x = -22 if agg < 10 else -23 187 | shutil.move(out_p, out_p[:x] + '.wav') 188 | out_p = os.path.join(save_path, f"instrument_{os.path.basename(tmp_path)}_{agg}.wav") 189 | shutil.move(out_p, out_p[:x] + '.wav') 190 | ret.append(os.path.join(save_path, f"vocal_{basename_no_ext(input_path)}.wav")) 191 | os.remove(tmp_path) 192 | except Exception as e: 193 | print(e) 194 | except Exception as e: 195 | print(e) 196 | finally: 197 | try: 198 | if model_name == "onnx_dereverb_By_FoxJoy": 199 | del pre_fun.pred.model 200 | del pre_fun.pred.model_ 201 | else: 202 | del pre_fun.model 203 | del pre_fun 204 | except Exception as e: 205 | print(e) 206 | print("clean_empty_cache") 207 | if torch.cuda.is_available(): 208 | torch.cuda.empty_cache() 209 | return ret 210 | 211 | 212 | def to_time(time_raw: float): 213 | hours, r = divmod(time_raw, 3600) 214 | minutes, r = divmod(r, 60) 215 | seconds, milliseconds = divmod(r, 1) 216 | return f"{int(hours):02d}:{int(minutes):02d}:{int(seconds):02d},{int(milliseconds*1000):03d}" 217 | 218 | 219 | if __name__ == "__main__": 220 | if args.input is not None: 221 | wav_paths = [os.path.abspath(i.strip('"')) for i in args.input] 222 | else: 223 | wav_paths = [input("enter input audio path: ").strip('"')] 224 | print(wav_paths) 225 | if args.uvr_model not in [None, '', 'None']: 226 | wav_paths = uvr(args.uvr_model, wav_paths, args.output_dir) 227 | wav_paths = [i for i in wav_paths if os.path.exists(i)] 228 | init_ASRmodels() 229 | transcribe(wav_paths, args.output_dir) 230 | -------------------------------------------------------------------------------- /tools/slicer2.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | # This function is obtained from librosa. 5 | def get_rms( 6 | y, 7 | frame_length=2048, 8 | hop_length=512, 9 | pad_mode="constant", 10 | ): 11 | padding = (int(frame_length // 2), int(frame_length // 2)) 12 | y = np.pad(y, padding, mode=pad_mode) 13 | 14 | axis = -1 15 | # put our new within-frame axis at the end for now 16 | out_strides = y.strides + tuple([y.strides[axis]]) 17 | # Reduce the shape on the framing axis 18 | x_shape_trimmed = list(y.shape) 19 | x_shape_trimmed[axis] -= frame_length - 1 20 | out_shape = tuple(x_shape_trimmed) + tuple([frame_length]) 21 | xw = np.lib.stride_tricks.as_strided(y, shape=out_shape, strides=out_strides) 22 | if axis < 0: 23 | target_axis = axis - 1 24 | else: 25 | target_axis = axis + 1 26 | xw = np.moveaxis(xw, -1, target_axis) 27 | # Downsample along the target axis 28 | slices = [slice(None)] * xw.ndim 29 | slices[axis] = slice(0, None, hop_length) 30 | x = xw[tuple(slices)] 31 | 32 | # Calculate power 33 | power = np.mean(np.abs(x) ** 2, axis=-2, keepdims=True) 34 | 35 | return np.sqrt(power) 36 | 37 | 38 | class Slicer: 39 | def __init__( 40 | self, 41 | sr: int, 42 | threshold: float = -40.0, 43 | min_length: int = 5000, 44 | min_interval: int = 300, 45 | hop_size: int = 20, 46 | max_sil_kept: int = 5000, 47 | ): 48 | if not min_length >= min_interval >= hop_size: 49 | raise ValueError( 50 | "The following condition must be satisfied: min_length >= min_interval >= hop_size" 51 | ) 52 | if not max_sil_kept >= hop_size: 53 | raise ValueError( 54 | "The following condition must be satisfied: max_sil_kept >= hop_size" 55 | ) 56 | min_interval = sr * min_interval / 1000 57 | self.threshold = 10 ** (threshold / 20.0) 58 | self.hop_size = round(sr * hop_size / 1000) 59 | self.win_size = min(round(min_interval), 4 * self.hop_size) 60 | self.min_length = round(sr * min_length / 1000 / self.hop_size) 61 | self.min_interval = round(min_interval / self.hop_size) 62 | self.max_sil_kept = round(sr * max_sil_kept / 1000 / self.hop_size) 63 | 64 | def _apply_slice(self, waveform, begin, end): 65 | if len(waveform.shape) > 1: 66 | return waveform[ 67 | :, begin * self.hop_size : min(waveform.shape[1], end * self.hop_size) 68 | ] 69 | else: 70 | return waveform[ 71 | begin * self.hop_size : min(waveform.shape[0], end * self.hop_size) 72 | ] 73 | 74 | # @timeit 75 | def slice(self, waveform): 76 | if len(waveform.shape) > 1: 77 | samples = waveform.mean(axis=0) 78 | else: 79 | samples = waveform 80 | if samples.shape[0] <= self.min_length: 81 | return [waveform] 82 | rms_list = get_rms( 83 | y=samples, frame_length=self.win_size, hop_length=self.hop_size 84 | ).squeeze(0) 85 | sil_tags = [] 86 | silence_start = None 87 | clip_start = 0 88 | for i, rms in enumerate(rms_list): 89 | # Keep looping while frame is silent. 90 | if rms < self.threshold: 91 | # Record start of silent frames. 92 | if silence_start is None: 93 | silence_start = i 94 | continue 95 | # Keep looping while frame is not silent and silence start has not been recorded. 96 | if silence_start is None: 97 | continue 98 | # Clear recorded silence start if interval is not enough or clip is too short 99 | is_leading_silence = silence_start == 0 and i > self.max_sil_kept 100 | need_slice_middle = ( 101 | i - silence_start >= self.min_interval 102 | and i - clip_start >= self.min_length 103 | ) 104 | if not is_leading_silence and not need_slice_middle: 105 | silence_start = None 106 | continue 107 | # Need slicing. Record the range of silent frames to be removed. 108 | if i - silence_start <= self.max_sil_kept: 109 | pos = rms_list[silence_start : i + 1].argmin() + silence_start 110 | if silence_start == 0: 111 | sil_tags.append((0, pos)) 112 | else: 113 | sil_tags.append((pos, pos)) 114 | clip_start = pos 115 | elif i - silence_start <= self.max_sil_kept * 2: 116 | pos = rms_list[ 117 | i - self.max_sil_kept : silence_start + self.max_sil_kept + 1 118 | ].argmin() 119 | pos += i - self.max_sil_kept 120 | pos_l = ( 121 | rms_list[ 122 | silence_start : silence_start + self.max_sil_kept + 1 123 | ].argmin() 124 | + silence_start 125 | ) 126 | pos_r = ( 127 | rms_list[i - self.max_sil_kept : i + 1].argmin() 128 | + i 129 | - self.max_sil_kept 130 | ) 131 | if silence_start == 0: 132 | sil_tags.append((0, pos_r)) 133 | clip_start = pos_r 134 | else: 135 | sil_tags.append((min(pos_l, pos), max(pos_r, pos))) 136 | clip_start = max(pos_r, pos) 137 | else: 138 | pos_l = ( 139 | rms_list[ 140 | silence_start : silence_start + self.max_sil_kept + 1 141 | ].argmin() 142 | + silence_start 143 | ) 144 | pos_r = ( 145 | rms_list[i - self.max_sil_kept : i + 1].argmin() 146 | + i 147 | - self.max_sil_kept 148 | ) 149 | if silence_start == 0: 150 | sil_tags.append((0, pos_r)) 151 | else: 152 | sil_tags.append((pos_l, pos_r)) 153 | clip_start = pos_r 154 | silence_start = None 155 | # Deal with trailing silence. 156 | total_frames = rms_list.shape[0] 157 | if ( 158 | silence_start is not None 159 | and total_frames - silence_start >= self.min_interval 160 | ): 161 | silence_end = min(total_frames, silence_start + self.max_sil_kept) 162 | pos = rms_list[silence_start : silence_end + 1].argmin() + silence_start 163 | sil_tags.append((pos, total_frames + 1)) 164 | # Apply and return slices. 165 | ####音频+起始时间+终止时间 166 | if len(sil_tags) == 0: 167 | return [[waveform,0,int(total_frames*self.hop_size)]] 168 | else: 169 | chunks = [] 170 | if sil_tags[0][0] > 0: 171 | chunks.append([self._apply_slice(waveform, 0, sil_tags[0][0]),0,int(sil_tags[0][0]*self.hop_size)]) 172 | for i in range(len(sil_tags) - 1): 173 | chunks.append( 174 | [self._apply_slice(waveform, sil_tags[i][1], sil_tags[i + 1][0]),int(sil_tags[i][1]*self.hop_size),int(sil_tags[i + 1][0]*self.hop_size)] 175 | ) 176 | if sil_tags[-1][1] < total_frames: 177 | chunks.append( 178 | [self._apply_slice(waveform, sil_tags[-1][1], total_frames),int(sil_tags[-1][1]*self.hop_size),int(total_frames*self.hop_size)] 179 | ) 180 | return chunks 181 | 182 | 183 | def main(): 184 | import os.path 185 | from argparse import ArgumentParser 186 | 187 | import librosa 188 | import soundfile 189 | 190 | parser = ArgumentParser() 191 | parser.add_argument("audio", type=str, help="The audio to be sliced") 192 | parser.add_argument( 193 | "--out", type=str, help="Output directory of the sliced audio clips" 194 | ) 195 | parser.add_argument( 196 | "--db_thresh", 197 | type=float, 198 | required=False, 199 | default=-40, 200 | help="The dB threshold for silence detection", 201 | ) 202 | parser.add_argument( 203 | "--min_length", 204 | type=int, 205 | required=False, 206 | default=5000, 207 | help="The minimum milliseconds required for each sliced audio clip", 208 | ) 209 | parser.add_argument( 210 | "--min_interval", 211 | type=int, 212 | required=False, 213 | default=300, 214 | help="The minimum milliseconds for a silence part to be sliced", 215 | ) 216 | parser.add_argument( 217 | "--hop_size", 218 | type=int, 219 | required=False, 220 | default=10, 221 | help="Frame length in milliseconds", 222 | ) 223 | parser.add_argument( 224 | "--max_sil_kept", 225 | type=int, 226 | required=False, 227 | default=500, 228 | help="The maximum silence length kept around the sliced clip, presented in milliseconds", 229 | ) 230 | args = parser.parse_args() 231 | out = args.out 232 | if out is None: 233 | out = os.path.dirname(os.path.abspath(args.audio)) 234 | audio, sr = librosa.load(args.audio, sr=None, mono=False) 235 | slicer = Slicer( 236 | sr=sr, 237 | threshold=args.db_thresh, 238 | min_length=args.min_length, 239 | min_interval=args.min_interval, 240 | hop_size=args.hop_size, 241 | max_sil_kept=args.max_sil_kept, 242 | ) 243 | chunks = slicer.slice(audio) 244 | if not os.path.exists(out): 245 | os.makedirs(out) 246 | for i, chunk in enumerate(chunks): 247 | if len(chunk.shape) > 1: 248 | chunk = chunk.T 249 | soundfile.write( 250 | os.path.join( 251 | out, 252 | f"%s_%d.wav" 253 | % (os.path.basename(args.audio).rsplit(".", maxsplit=1)[0], i), 254 | ), 255 | chunk, 256 | sr, 257 | ) 258 | 259 | 260 | if __name__ == "__main__": 261 | main() 262 | -------------------------------------------------------------------------------- /Sava_Utils/subtitle_translation.py: -------------------------------------------------------------------------------- 1 | import gradio as gr 2 | import os 3 | import copy 4 | import traceback 5 | import Sava_Utils 6 | from . import i18n, logger, ext_tab 7 | from .subtitle import Subtitle, Subtitles 8 | from .utils import read_file, basename_no_ext 9 | from .base_component import Base_Component 10 | from .translator.ollama import Ollama 11 | from . import extension_loader 12 | 13 | LANGUAGE = ["中文", "English", "日本語", "한국어", "Français"] 14 | 15 | current_path = os.environ.get("current_path") 16 | 17 | 18 | def merge_subtitles(subtitles_main: Subtitles, subtitles_tr: Subtitles): 19 | result = copy.deepcopy(subtitles_main) 20 | for s_main, s_tr in zip(result, subtitles_tr): 21 | s_main.text = s_main.text.strip() + "\n" + s_tr.text.strip() 22 | return result 23 | 24 | 25 | def merge_uploaded_sub(filelist_sup: list, filelist_inf: list, output_dir: str): 26 | if filelist_sup is None or filelist_inf is None: 27 | gr.Info(i18n('Please upload the subtitle file!')) 28 | return None, i18n('Please upload the subtitle file!') 29 | len_s = len(set(basename_no_ext(i.name) for i in filelist_sup)) 30 | len_i = len(set(basename_no_ext(i.name) for i in filelist_inf)) 31 | if len_s != len(filelist_sup) or len_i != len(filelist_inf): 32 | gr.Warning(i18n('Uploading files with the same name is not allowed.')) 33 | return None, i18n('Uploading files with the same name is not allowed.') 34 | if len(filelist_sup) != len(filelist_inf): 35 | gr.Warning(i18n('The number of files must match!')) 36 | return None, i18n('The number of files must match!') 37 | filelist_sup.sort(key=lambda x: basename_no_ext(x.name).rsplit('_', 3)[0]) 38 | filelist_inf.sort(key=lambda x: basename_no_ext(x.name).rsplit('_', 3)[0]) 39 | ret = [] 40 | try: 41 | for f1, f2 in zip(filelist_sup, filelist_inf): 42 | if Sava_Utils.config.server_mode: 43 | output_path = os.path.join(os.path.dirname(f1.name), f"{basename_no_ext(f1.name)}_merged.srt") 44 | else: 45 | output_path = os.path.join(output_dir, f"{basename_no_ext(f1.name)}_merged.srt") 46 | x = merge_subtitles(read_file(f1.name), read_file(f2.name)) 47 | x.export(fp=output_path, open_explorer=False, raw=True) 48 | ret.append(output_path) 49 | except Exception as e: 50 | errmsg = f"{i18n('An error occurred')}: {str(e)}" 51 | gr.Warning(errmsg) 52 | return ret, errmsg 53 | return ret, "OK" 54 | 55 | 56 | class Translation_module(Base_Component): 57 | def __init__(self): 58 | self.ui = False 59 | OLLAMA = Ollama() 60 | self.TRANSLATORS = {OLLAMA.name: OLLAMA} 61 | for ext in extension_loader.load_ext_from_dir(["Sava_Extensions/translator"], ext_enabled_dict=ext_tab["translator"]): 62 | self.TRANSLATORS[ext.name] = ext 63 | self.config = None 64 | self.menu = [] 65 | super().__init__() 66 | 67 | def update_cfg(self, config): 68 | self.config = config 69 | for i in self.TRANSLATORS.values(): 70 | i.update_cfg(config=config) 71 | super().update_cfg(config) 72 | 73 | def getUI(self, *args): 74 | if not self.ui: 75 | self.ui = True 76 | self._UI(*args) 77 | else: 78 | raise "err" 79 | 80 | def start_translation(self, in_files, language: str, batch_size: float, merge: bool, output_dir: str, interrupt_flag: Sava_Utils.utils.Flag, *args, translator=None): 81 | output_list = [] 82 | message = "" 83 | if in_files is None: 84 | gr.Info(i18n('Please upload the subtitle file!')) 85 | return i18n('Please upload the subtitle file!'), output_list 86 | with interrupt_flag: 87 | for in_file in in_files: 88 | subtitle_list_ori = read_file(in_file.name) 89 | subtitle_list_tr = copy.deepcopy(subtitle_list_ori) 90 | tasks = self.TRANSLATORS[translator].construct_tasks(subtitle_list_ori, int(batch_size)) 91 | try: 92 | returned = self.TRANSLATORS[translator].api(tasks, language, interrupt_flag, *args, file_name=os.path.basename(in_file.name)) 93 | if isinstance(returned, tuple) and len(returned) == 2: 94 | result, msg = returned 95 | else: 96 | result = returned 97 | msg = "" 98 | if interrupt_flag.is_set(): 99 | message += i18n('Canceled by user.') 100 | break 101 | if msg: 102 | message += f"{os.path.basename(in_file.name)}: {msg}\n" 103 | for sub, txt in zip(subtitle_list_tr, result): 104 | sub.text = txt 105 | if Sava_Utils.config.server_mode: 106 | output_path = os.path.join(os.path.dirname(in_file.name), f"{basename_no_ext(in_file.name)}_translated_to_{language}.srt") 107 | else: 108 | output_path = os.path.join(output_dir, f"{basename_no_ext(in_file.name)}_translated_to_{language}.srt") 109 | subtitle_list_tr.export(fp=output_path, open_explorer=False, raw=True) 110 | output_list.append(output_path) 111 | if merge: 112 | s_merged = merge_subtitles(subtitle_list_tr, subtitle_list_ori) 113 | op = output_path[:-4] + "_merged.srt" 114 | s_merged.export(fp=op, open_explorer=False, raw=True) 115 | output_list.append(op) 116 | except Exception as e: 117 | traceback.print_exc() 118 | err = f"{i18n('Failed to translate')} {os.path.basename(in_file.name)} :{str(e)}" 119 | gr.Warning(err) 120 | message += err + "\n" 121 | continue 122 | # os.system(f'explorer {output_dir}') 123 | return message.strip() if message else "OK", output_list 124 | 125 | def _UI(self, file_main): 126 | with gr.TabItem(i18n('Subtitle Translation')): 127 | self.INTERRUPT_EVENT = gr.State(value=Sava_Utils.utils.Flag()) 128 | with gr.Row(): 129 | with gr.Column(): 130 | self.translation_upload = gr.File(label=i18n('Upload your subtitle files (multiple allowed).'), file_count="multiple", file_types=[".srt", ".csv", ".txt"]) 131 | with gr.Accordion(i18n('Subtitle Merge Tool'), open=False): 132 | gr.Markdown(value=i18n('When uploading multiple files, the number of files and the filenames must match.')) 133 | with gr.Row(): 134 | self.merge_upload1 = gr.File(label=i18n('Main Subtitle'), file_count="multiple", file_types=[".srt", ".csv", ".txt"]) 135 | self.merge_upload2 = gr.File(label=i18n('Secondary Subtitle'), file_count="multiple", file_types=[".srt", ".csv", ".txt"]) 136 | self.merge_btn = gr.Button(value=i18n('Merge'), variant='primary') 137 | self.output_info = gr.Text(interactive=False, value="", label=i18n('Output Info')) 138 | self.translation_output = gr.File(label=i18n('Output File'), file_count="multiple", interactive=False) 139 | self.send_btn = gr.Button(value=i18n('Send output files to Main Page'), interactive=True) 140 | self.send_btn.click(lambda x: [i.name for i in x if not i.name.endswith('_merged.srt')] if x is not None else x, inputs=[self.translation_output], outputs=[file_main]) 141 | with gr.Column(): 142 | self.translation_target_language = gr.Dropdown(label=i18n('Specify Target Language'), choices=LANGUAGE, value=LANGUAGE[1], interactive=True) 143 | self.batch_size = gr.Number(label="Batch Size", value=5, minimum=1, interactive=True) 144 | self.merge_sub = gr.Checkbox(label=i18n('Generate merged subtitles'), value=False, interactive=True) 145 | self.output_dir = gr.Text(value=os.path.join(current_path, "SAVAdata", "output"), label=i18n('File Output Path'), interactive=not Sava_Utils.config.server_mode, visible=not Sava_Utils.config.server_mode, max_lines=1) 146 | self.translator = gr.Radio(label=i18n('Select Translator'), choices=[i for i in self.TRANSLATORS.keys()], value=list(self.TRANSLATORS.keys())[0]) 147 | BASE_ARGS = [self.translation_upload, self.translation_target_language, self.batch_size, self.merge_sub, self.output_dir, self.INTERRUPT_EVENT] 148 | with gr.Column(): 149 | v = True 150 | assert self.config is not None 151 | for translator in self.TRANSLATORS.keys(): 152 | try: 153 | with gr.Column(visible=v) as tr_ui: 154 | self.TRANSLATORS[translator].update_cfg(config=self.config) 155 | TRANSLATOR_ARGS = self.TRANSLATORS[translator].getUI() 156 | if not hasattr(self.TRANSLATORS[translator], "start_translate_btn"): 157 | setattr(self.TRANSLATORS[translator], "start_translate_btn", gr.Button(value=i18n('Start Translating'), variant="primary")) 158 | def make_handler(tr): 159 | return lambda progress=gr.Progress(track_tqdm=True), *args: self.start_translation(*args, translator=tr) 160 | # avoid late binding 161 | self.TRANSLATORS[translator].start_translate_btn.click(make_handler(translator), inputs=BASE_ARGS + TRANSLATOR_ARGS, outputs=[self.output_info, self.translation_output]) 162 | v = False 163 | self.menu.append(tr_ui) 164 | except: 165 | logger.error(f"{i18n('Failed to load Translator UI')}: {self.TRANSLATORS[translator].dirname}") 166 | traceback.print_exc() 167 | stop_btn = gr.Button(value=i18n('Stop'), variant="stop") 168 | stop_btn.click(lambda x: gr.Info(x.set()), inputs=[self.INTERRUPT_EVENT]) 169 | self.translator.change(lambda x: [gr.update(visible=x == i) for i in self.TRANSLATORS.keys()], inputs=[self.translator], outputs=self.menu) 170 | self.merge_btn.click(merge_uploaded_sub, inputs=[self.merge_upload1, self.merge_upload2, self.output_dir], outputs=[self.translation_output, self.output_info]) 171 | -------------------------------------------------------------------------------- /Sava_Utils/tts_engines/mstts.py: -------------------------------------------------------------------------------- 1 | from . import TTSProjet 2 | from ..settings import Settings, Shared_Option 3 | import os 4 | import re 5 | import json 6 | import requests 7 | import gradio as gr 8 | from .. import logger, i18n 9 | from xml.etree import ElementTree 10 | 11 | current_path = os.environ.get("current_path") 12 | SERVER_Regions = [ 13 | 'southafricanorth', 14 | 'eastasia', 15 | 'southeastasia', 16 | 'australiaeast', 17 | 'centralindia', 18 | 'japaneast', 19 | 'japanwest', 20 | 'koreacentral', 21 | 'canadacentral', 22 | 'northeurope', 23 | 'westeurope', 24 | 'francecentral', 25 | 'germanywestcentral', 26 | 'norwayeast', 27 | 'swedencentral', 28 | 'switzerlandnorth', 29 | 'switzerlandwest', 30 | 'uksouth', 31 | 'uaenorth', 32 | 'brazilsouth', 33 | 'qatarcentral', 34 | 'centralus', 35 | 'eastus', 36 | 'eastus2', 37 | 'northcentralus', 38 | 'southcentralus', 39 | 'westcentralus', 40 | 'westus', 41 | 'westus2', 42 | 'westus3', 43 | ] 44 | 45 | 46 | class MSTTS(TTSProjet): 47 | def __init__(self): 48 | self.ms_speaker_info = {} 49 | self.cfg_ms_region = "" 50 | self.cfg_ms_key = "" 51 | self.ms_lang_option = "" 52 | super().__init__("Azure-TTS(Microsoft)", title="Azure-TTS(Microsoft)") 53 | 54 | def update_cfg(self, config): 55 | self.cfg_ms_region = config.query("ms_region") 56 | self.cfg_ms_key = config.query("ms_key") 57 | self.ms_lang_option = config.query("ms_lang_option") 58 | super().update_cfg(config) 59 | 60 | def register_settings(self): 61 | options = [] 62 | options.append( 63 | Shared_Option( 64 | "ms_region", 65 | "eastasia", 66 | gr.Dropdown, 67 | allow_custom_value=True, 68 | choices=SERVER_Regions, 69 | label="Server Region", 70 | interactive=True, 71 | ) 72 | ) 73 | options.append( 74 | Shared_Option( 75 | "ms_key", 76 | "", 77 | gr.Textbox, 78 | lambda v, c: v.strip(), 79 | label=i18n('API=KEY Warning: Key is stored in plaintext. DO NOT send the key to others or share your configuration file!'), 80 | interactive=True, 81 | type="password", 82 | ) 83 | ) 84 | options.append( 85 | Shared_Option( 86 | "ms_lang_option", 87 | "", 88 | gr.Textbox, 89 | label=i18n('Select required languages, separated by commas or spaces.'), 90 | interactive=True, 91 | placeholder="zh en", 92 | ) 93 | ) 94 | return options 95 | 96 | def getms_speakers(self): 97 | if not os.path.exists(os.path.join(current_path, "SAVAdata", "ms_speaker_info_raw.json")): 98 | try: 99 | assert self.cfg_ms_key, i18n('Please fill in your key to get MSTTS speaker list.') 100 | headers = {"Ocp-Apim-Subscription-Key": self.cfg_ms_key} 101 | url = f"https://{self.cfg_ms_region}.tts.speech.microsoft.com/cognitiveservices/voices/list" 102 | data = requests.get(url=url, headers=headers) 103 | data.raise_for_status() 104 | info = json.loads(data.content) 105 | with open(os.path.join(current_path, "SAVAdata", "ms_speaker_info_raw.json"), "w", encoding="utf-8") as f: 106 | json.dump(info, f, indent=2, ensure_ascii=False) 107 | except Exception as e: 108 | err = f"{i18n('Can not get speaker list of MSTTS. Details')}: {e}" 109 | gr.Warning(err) 110 | logger.warning(err) 111 | self.ms_speaker_info = {} 112 | return None 113 | dataraw = json.load(open(os.path.join(current_path, "SAVAdata", "ms_speaker_info_raw.json"), encoding="utf-8")) # list 114 | classified_info = {} 115 | target_language = re.split(r'(?<=[,,])| ', self.ms_lang_option) 116 | target_language = [x.strip() for x in target_language if x.strip()] 117 | if len(target_language) == 0: 118 | target_language = [""] 119 | for i in dataraw: 120 | if any(lan in i["Locale"] for lan in target_language): 121 | if i["Locale"] not in classified_info: 122 | classified_info[i["Locale"]] = {} 123 | classified_info[i["Locale"]][i["LocalName"]] = i 124 | with open(os.path.join("SAVAdata", "ms_speaker_info.json"), "w", encoding="utf-8") as f: 125 | json.dump(classified_info, f, indent=2, ensure_ascii=False) 126 | self.ms_speaker_info = json.load(open(os.path.join("SAVAdata", "ms_speaker_info.json"), encoding="utf-8")) 127 | 128 | def api(self, language, speaker, style, role, rate, pitch, text, **kwargs): 129 | xml_body = ElementTree.Element("speak", version="1.0") 130 | xml_body.set("xml:lang", "en-US") 131 | voice = ElementTree.SubElement(xml_body, "voice") 132 | voice.set("name", self.ms_speaker_info[language][speaker]["ShortName"]) # Short name 133 | express = ElementTree.SubElement(voice, "express-as") 134 | express.set("style", style) 135 | express.set("role", role) 136 | prosody = ElementTree.SubElement(express, "prosody") 137 | prosody.set("rate", f"{int((rate - 1) * 100)}%") 138 | prosody.set("pitch", f"{int((pitch- 1) * 100)}%") 139 | prosody.text = text 140 | body = ElementTree.tostring(xml_body) 141 | try: 142 | assert self.cfg_ms_key, i18n('Please fill in your key!') 143 | headers = { 144 | "Ocp-Apim-Subscription-Key": self.cfg_ms_key, 145 | "X-Microsoft-OutputFormat": "riff-48khz-16bit-mono-pcm", 146 | "Content-Type": "application/ssml+xml", 147 | "User-Agent": "py_sava", 148 | } 149 | response = requests.post( 150 | url=f"https://{self.cfg_ms_region}.tts.speech.microsoft.com/cognitiveservices/v1", 151 | headers=headers, 152 | data=body, 153 | ) 154 | response.raise_for_status() 155 | return response.content 156 | except Exception as e: 157 | err = f"Error: {e}" 158 | logger.error(err) 159 | return None 160 | 161 | def _UI(self): 162 | self.ms_refresh() 163 | with gr.Column(): 164 | self.ms_refresh_btn = gr.Button(value=i18n('Refresh speakers list'), variant="secondary") 165 | if self.ms_speaker_info == {}: 166 | self.ms_languages = gr.Dropdown(label=i18n('Choose Language'), value=None, choices=[], allow_custom_value=False, interactive=True) 167 | self.ms_speaker = gr.Dropdown(label=i18n('Choose Your Speaker'), value=None, choices=[], allow_custom_value=False, interactive=True) 168 | else: 169 | choices = list(self.ms_speaker_info.keys()) 170 | self.ms_languages = gr.Dropdown(label=i18n('Choose Language'), value=choices[0], choices=choices, allow_custom_value=False, interactive=True) 171 | choices = list(self.ms_speaker_info[choices[0]].keys()) 172 | self.ms_speaker = gr.Dropdown(label=i18n('Choose Your Speaker'), value=None, choices=choices, allow_custom_value=False, interactive=True) 173 | del choices 174 | with gr.Row(): 175 | self.ms_style = gr.Dropdown(label=i18n('Style'), value=None, choices=[], allow_custom_value=False, interactive=True) 176 | self.ms_role = gr.Dropdown(label=i18n('Role'), value=None, choices=[], allow_custom_value=False, interactive=True) 177 | self.ms_speed = gr.Slider(minimum=0.2, maximum=2, step=0.01, label=i18n('Speed'), value=1, interactive=True) 178 | self.ms_pitch = gr.Slider(minimum=0.5, maximum=1.5, step=0.01, label=i18n('Pitch'), value=1, interactive=True) 179 | gr.Markdown(value=i18n('MSTTS_NOTICE')) 180 | self.gen_btn = gr.Button(value=i18n('Generate Audio'), variant="primary", visible=True) 181 | self.ms_refresh_btn.click(self.ms_refresh, outputs=[self.ms_languages]) 182 | self.ms_languages.change(self.display_ms_spk, inputs=[self.ms_languages], outputs=[self.ms_speaker]) 183 | self.ms_speaker.change(self.display_style_role, inputs=[self.ms_languages, self.ms_speaker], outputs=[self.ms_style, self.ms_role]) 184 | MSTTS_ARGS = [self.ms_languages, self.ms_speaker, self.ms_style, self.ms_role, self.ms_speed, self.ms_pitch] 185 | return MSTTS_ARGS 186 | 187 | def save_action(self, *args, text: str = None): 188 | language, speaker, style, role, rate, pitch = args 189 | audio = self.api(language, speaker, style, role, rate, pitch, text) 190 | return audio 191 | 192 | def arg_filter(self, *args): 193 | ms_language, ms_speaker, ms_style, ms_role, ms_speed, ms_pitch = args 194 | if ms_speaker in [None, "", []]: 195 | gr.Info(i18n('Please Select Your Speaker!')) 196 | raise Exception(i18n('Please Select Your Speaker!')) 197 | if self.cfg_ms_key == "": 198 | gr.Warning(i18n('Please fill in your key!')) 199 | raise Exception(i18n('Please fill in your key!')) 200 | pargs = (ms_language, ms_speaker, ms_style, ms_role, ms_speed, ms_pitch) 201 | return pargs 202 | 203 | def ms_refresh(self): # language 204 | self.getms_speakers() 205 | if self.ms_speaker_info == {}: 206 | return gr.update(value=None, choices=[], allow_custom_value=False) 207 | choices = list(self.ms_speaker_info.keys()) 208 | return gr.update(value=choices[0], choices=choices, allow_custom_value=False) 209 | 210 | def display_ms_spk(self, language): # speaker 211 | if language in [None, ""]: 212 | return gr.update(value=None, choices=[], allow_custom_value=False) 213 | choices = list(self.ms_speaker_info[language].keys()) 214 | return gr.update(value=choices[0], choices=choices, allow_custom_value=False) 215 | 216 | def display_style_role(self, language, speaker): 217 | if language in [None, ""] or speaker in [None, ""]: 218 | return gr.update(value=None, choices=[], allow_custom_value=False), gr.update(value=None, choices=[], allow_custom_value=False) 219 | choices1 = ["Default"] + self.ms_speaker_info[language][speaker].get("StyleList", []) 220 | choices2 = ["Default"] + self.ms_speaker_info[language][speaker].get("RolePlayList", []) 221 | return (gr.update(value=choices1[0], choices=choices1, allow_custom_value=False), gr.update(value=choices2[0], choices=choices2, allow_custom_value=False)) 222 | --------------------------------------------------------------------------------