├── .github ├── scripts │ └── lzy_web.py └── workflows │ ├── automerge.yml │ ├── debug.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── README.md ├── app ├── .gitignore ├── build.gradle ├── libs │ ├── README.md │ └── rhino-1.7.13-1.jar ├── proguard-rules.pro ├── schemas │ └── com.github.jing332.tts_server_android.data.AppDatabase │ │ ├── 1.json │ │ ├── 10.json │ │ ├── 11.json │ │ ├── 12.json │ │ ├── 13.json │ │ ├── 14.json │ │ ├── 15.json │ │ ├── 16.json │ │ ├── 17.json │ │ ├── 18.json │ │ ├── 19.json │ │ ├── 2.json │ │ ├── 20.json │ │ ├── 21.json │ │ ├── 22.json │ │ ├── 23.json │ │ ├── 24.json │ │ ├── 3.json │ │ ├── 4.json │ │ ├── 5.json │ │ ├── 6.json │ │ ├── 7.json │ │ ├── 8.json │ │ └── 9.json └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── github │ │ └── jing332 │ │ └── tts_server_android │ │ ├── DirectUploadEngineTest.kt │ │ ├── GoLibTest.kt │ │ ├── HttpTtsUrlTest.kt │ │ ├── OkHttpTest.kt │ │ └── RhinoEngineTest.kt │ ├── debug │ └── res │ │ └── values │ │ └── strings.xml │ ├── dev │ └── res │ │ └── values │ │ └── strings.xml │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── defaultData │ │ │ ├── direct_link_upload.js │ │ │ ├── list.json │ │ │ ├── plugin-azure.js │ │ │ └── speech_rule.js │ │ ├── help │ │ │ └── app.md │ │ └── textmate │ │ │ ├── abyss.json │ │ │ ├── darcula.json │ │ │ ├── javascript │ │ │ ├── language-configuration.json │ │ │ └── syntaxes │ │ │ │ └── JavaScript.tmLanguage.json │ │ │ ├── quietlight.json │ │ │ └── solarized_drak.json │ ├── ic_new_app-playstore.png │ ├── ic_new_app_launcher-playstore.png │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── jing332 │ │ │ └── tts_server_android │ │ │ ├── App.kt │ │ │ ├── AppLocale.kt │ │ │ ├── CrashHandler.kt │ │ │ ├── ShortCuts.kt │ │ │ ├── bean │ │ │ ├── EdgeVoiceBean.kt │ │ │ ├── GithubReleaseApiBean.kt │ │ │ └── LegadoHttpTts.kt │ │ │ ├── compose │ │ │ ├── AboutDialog.kt │ │ │ ├── AppUpdateDialog.kt │ │ │ ├── AutoUpdateCheckerDialog.kt │ │ │ ├── LibrariesActivity.kt │ │ │ ├── MainActivity.kt │ │ │ ├── ShadowReorderableItem.kt │ │ │ ├── backup │ │ │ │ ├── BackupDialog.kt │ │ │ │ ├── BackupRestoreActivity.kt │ │ │ │ ├── BackupRestoreViewModel.kt │ │ │ │ ├── RestoreDialog.kt │ │ │ │ └── Type.kt │ │ │ ├── codeeditor │ │ │ │ ├── CodeEditor.kt │ │ │ │ ├── CodeEditorHelper.kt │ │ │ │ ├── CodeEditorScreen.kt │ │ │ │ ├── CodeEditorViewModel.kt │ │ │ │ ├── LoggerBottomSheet.kt │ │ │ │ ├── RemoteSyncSettings.kt │ │ │ │ └── ThemeSettingsDialog.kt │ │ │ ├── forwarder │ │ │ │ ├── BasicConfigScreen.kt │ │ │ │ ├── BasicForwarderScreen.kt │ │ │ │ ├── ConfigViewModel.kt │ │ │ │ ├── ForwarderTopAppBar.kt │ │ │ │ ├── WebScreen.kt │ │ │ │ ├── ms │ │ │ │ │ └── MsTtsForwarderScreen.kt │ │ │ │ └── systts │ │ │ │ │ └── SystemTtsForwarderScreen.kt │ │ │ ├── nav │ │ │ │ ├── NavRoutes.kt │ │ │ │ └── NavTopAppBar.kt │ │ │ ├── settings │ │ │ │ ├── SettingsScreen.kt │ │ │ │ ├── SettingsWidgets.kt │ │ │ │ └── ThemeSelectionDialog.kt │ │ │ ├── systts │ │ │ │ ├── AuditionDialog.kt │ │ │ │ ├── ConfigDeleteDialog.kt │ │ │ │ ├── ConfigExportBottomSheet.kt │ │ │ │ ├── ConfigImportBottomSheet.kt │ │ │ │ ├── GroupItem.kt │ │ │ │ ├── ListSortSettingsDialog.kt │ │ │ │ ├── LogScreen.kt │ │ │ │ ├── SystemTtsScreen.kt │ │ │ │ ├── SystemTtsViewModel.kt │ │ │ │ ├── TtsLogScreen.kt │ │ │ │ ├── TtsLogViewModel.kt │ │ │ │ ├── directlink │ │ │ │ │ ├── LinkUploadRuleActivity.kt │ │ │ │ │ └── LinkUploadSelectionDialog.kt │ │ │ │ ├── list │ │ │ │ │ ├── BasicAudioParamsDialog.kt │ │ │ │ │ ├── BgmSettingsDialog.kt │ │ │ │ │ ├── GlobalAudioParamsDialog.kt │ │ │ │ │ ├── Group.kt │ │ │ │ │ ├── GroupAudioParamsDialog.kt │ │ │ │ │ ├── IntSlider.kt │ │ │ │ │ ├── InternalPlayerDialog.kt │ │ │ │ │ ├── Item.kt │ │ │ │ │ ├── ListExportBottomSheet.kt │ │ │ │ │ ├── ListImportBottomSheet.kt │ │ │ │ │ ├── ListManagerScreen.kt │ │ │ │ │ ├── ListManagerViewModel.kt │ │ │ │ │ ├── MenuMoreOptions.kt │ │ │ │ │ ├── PluginSelectionDialog.kt │ │ │ │ │ ├── SortDialog.kt │ │ │ │ │ └── edit │ │ │ │ │ │ ├── BasicInfoEditScreen.kt │ │ │ │ │ │ ├── QuickEditBottomSheet.kt │ │ │ │ │ │ ├── TagDataClearConfirmDialog.kt │ │ │ │ │ │ ├── TtsEditContainerScreen.kt │ │ │ │ │ │ └── ui │ │ │ │ │ │ ├── BgmTtsUI.kt │ │ │ │ │ │ ├── LocalTtsUI.kt │ │ │ │ │ │ ├── LocalTtsViewModel.kt │ │ │ │ │ │ ├── MsTtsUI.kt │ │ │ │ │ │ ├── MsTtsViewModel.kt │ │ │ │ │ │ ├── PluginTtsUI.kt │ │ │ │ │ │ ├── PluginTtsViewModel.kt │ │ │ │ │ │ ├── SaveHandler.kt │ │ │ │ │ │ ├── TtsUI.kt │ │ │ │ │ │ ├── TtsUiFactory.kt │ │ │ │ │ │ └── widgets │ │ │ │ │ │ ├── AuditionTextField.kt │ │ │ │ │ │ ├── BaseViewModel.kt │ │ │ │ │ │ ├── InternalPlayerDialog.kt │ │ │ │ │ │ └── TtsTopAppBar.kt │ │ │ │ ├── plugin │ │ │ │ │ ├── NavRoutes.kt │ │ │ │ │ ├── PluginEditScreen.kt │ │ │ │ │ ├── PluginEditViewModel.kt │ │ │ │ │ ├── PluginExportBottomSheet.kt │ │ │ │ │ ├── PluginImportBottomSheet.kt │ │ │ │ │ ├── PluginManagerActivity.kt │ │ │ │ │ ├── PluginManagerScreen.kt │ │ │ │ │ ├── PluginManagerViewModel.kt │ │ │ │ │ ├── PluginPreviewActivity.kt │ │ │ │ │ └── PluginVarsBottomSheet.kt │ │ │ │ ├── replace │ │ │ │ │ ├── Group.kt │ │ │ │ │ ├── GroupEditDialog.kt │ │ │ │ │ ├── Item.kt │ │ │ │ │ ├── NavRoutes.kt │ │ │ │ │ ├── ReplaceManagerActivity.kt │ │ │ │ │ ├── ReplaceRuleExportBottomSheet.kt │ │ │ │ │ ├── ReplaceRuleImportBottomSheet.kt │ │ │ │ │ ├── ReplaceRuleManagerScreen.kt │ │ │ │ │ ├── ReplaceRuleManagerViewModel.kt │ │ │ │ │ ├── SearchTextField.kt │ │ │ │ │ ├── SortDialog.kt │ │ │ │ │ └── edit │ │ │ │ │ │ ├── ReplaceRuleEditScreen.kt │ │ │ │ │ │ ├── RuleEditViewModel.kt │ │ │ │ │ │ ├── SoftKeyboardToolbar.kt │ │ │ │ │ │ ├── ToolBarSettingsDialog.kt │ │ │ │ │ │ └── TtsConfigSelectDialog.kt │ │ │ │ └── speechrule │ │ │ │ │ ├── NavRoutes.kt │ │ │ │ │ ├── SpeechRuleEditScreen.kt │ │ │ │ │ ├── SpeechRuleEditViewModel.kt │ │ │ │ │ ├── SpeechRuleExportBottomSheet.kt │ │ │ │ │ ├── SpeechRuleImportBottomSheet.kt │ │ │ │ │ ├── SpeechRuleManagerActivity.kt │ │ │ │ │ └── SpeechRuleManagerScreen.kt │ │ │ ├── theme │ │ │ │ ├── AppTheme.kt │ │ │ │ ├── AppThemeConfiguration.kt │ │ │ │ ├── Color.kt │ │ │ │ ├── Color2.kt │ │ │ │ ├── Theme.kt │ │ │ │ └── Type.kt │ │ │ └── widgets │ │ │ │ ├── AppBottomSheet.kt │ │ │ │ ├── AppDialog.kt │ │ │ │ ├── AppLauncherIcon.kt │ │ │ │ ├── AppSelectionDialog.kt │ │ │ │ ├── AppSpinner.kt │ │ │ │ ├── AppTooltip.kt │ │ │ │ ├── ButtonToggleGroup.kt │ │ │ │ ├── CheckMenuItem.kt │ │ │ │ ├── DenseTextField.kt │ │ │ │ ├── DropdownTextField.kt │ │ │ │ ├── EmptyTextToolbar.kt │ │ │ │ ├── ErrorDialog.kt │ │ │ │ ├── ExpandableText.kt │ │ │ │ ├── HtmlText.kt │ │ │ │ ├── LabelSlider.kt │ │ │ │ ├── LazyListIndexStateSaver.kt │ │ │ │ ├── LoadingAnimation.kt │ │ │ │ ├── LoadingContent.kt │ │ │ │ ├── LoadingDialog.kt │ │ │ │ ├── LongClickIconButton.kt │ │ │ │ ├── Markdown.kt │ │ │ │ ├── SelectableText.kt │ │ │ │ ├── SwitchFloatingAction.kt │ │ │ │ ├── TextCheckBox.kt │ │ │ │ ├── TextFieldDialog.kt │ │ │ │ ├── Widgets.kt │ │ │ │ └── htmlcompose │ │ │ │ ├── CharacterStyle.kt │ │ │ │ ├── HtmlText.kt │ │ │ │ └── MetricAffectingSpan.kt │ │ │ ├── conf │ │ │ ├── AppConfig.kt │ │ │ ├── CodeEditorConfig.kt │ │ │ ├── DirectUploadConfig.kt │ │ │ ├── MsForwarderConfig.kt │ │ │ ├── MsTtsForwarderConfig.kt │ │ │ ├── PluginConfig.kt │ │ │ ├── ReplaceRuleConfig.kt │ │ │ ├── SpeechRuleConfig.kt │ │ │ ├── SysTtsConfig.kt │ │ │ ├── SystemTtsConfig.kt │ │ │ └── SysttsForwarderConfig.kt │ │ │ ├── constant │ │ │ ├── AppConst.kt │ │ │ ├── AppPattern.kt │ │ │ ├── CnLocalMap.kt │ │ │ ├── CodeEditorTheme.kt │ │ │ ├── ConfigType.kt │ │ │ ├── FilePickerMode.kt │ │ │ ├── KeyConst.kt │ │ │ ├── LogLevel.kt │ │ │ ├── MsTtsApiType.kt │ │ │ ├── PreferKey.kt │ │ │ ├── ReplaceExecution.kt │ │ │ ├── SpeechTarget.kt │ │ │ └── SystemNotificationConst.kt │ │ │ ├── data │ │ │ ├── AppDataBase.kt │ │ │ ├── DataBaseMigration.kt │ │ │ ├── dao │ │ │ │ ├── PluginDao.kt │ │ │ │ ├── ReplaceRuleDao.kt │ │ │ │ ├── SpeechRuleDao.kt │ │ │ │ ├── SysTtsDao.kt │ │ │ │ └── SystemTtsDao.kt │ │ │ └── entities │ │ │ │ ├── AbstractListGroup.kt │ │ │ │ ├── MapConverters.kt │ │ │ │ ├── SpeechRule.kt │ │ │ │ ├── TypeConverterUtils.kt │ │ │ │ ├── plugin │ │ │ │ └── Plugin.kt │ │ │ │ ├── replace │ │ │ │ ├── GroupWithReplaceRule.kt │ │ │ │ ├── ReplaceRule.kt │ │ │ │ └── ReplaceRuleGroup.kt │ │ │ │ └── systts │ │ │ │ ├── AudioParams.kt │ │ │ │ ├── CompatSystemTts.kt │ │ │ │ ├── GroupWithSystemTts.kt │ │ │ │ ├── SpeechRuleInfo.kt │ │ │ │ ├── SystemTts.kt │ │ │ │ └── SystemTtsGroup.kt │ │ │ ├── help │ │ │ ├── ByteArrayBinder.kt │ │ │ ├── ConfigImportHelper.kt │ │ │ ├── LocalTtsEngineHelper.kt │ │ │ └── audio │ │ │ │ ├── AudioDecoder.kt │ │ │ │ ├── AudioDecoderException.kt │ │ │ │ ├── AudioPlayer.kt │ │ │ │ ├── ByteArrayMediaDataSource.kt │ │ │ │ ├── ExoAudioPlayer.kt │ │ │ │ ├── ExoPlayerHelper.kt │ │ │ │ ├── InputStreamMediaDataSource.kt │ │ │ │ ├── PcmAudioPlayer.kt │ │ │ │ ├── Sonic.java │ │ │ │ └── exo │ │ │ │ ├── DecoderAudioSink.kt │ │ │ │ ├── ExoAudioDecoder.kt │ │ │ │ └── InputStreamDataSource.kt │ │ │ ├── model │ │ │ ├── AnalyzeUrl.kt │ │ │ ├── EdgeTtsLib.kt │ │ │ ├── LocalTtsEngineHelper.kt │ │ │ ├── MsTtsEditRepository.kt │ │ │ ├── hanlp │ │ │ │ └── HanlpManager.kt │ │ │ ├── rhino │ │ │ │ ├── ExceptionExt.kt │ │ │ │ ├── core │ │ │ │ │ ├── BaseScriptEngine.kt │ │ │ │ │ ├── BaseScriptEngineContext.kt │ │ │ │ │ ├── Logger.kt │ │ │ │ │ ├── ext │ │ │ │ │ │ ├── JsCrypto.kt │ │ │ │ │ │ ├── JsExtensions.kt │ │ │ │ │ │ ├── JsNet.kt │ │ │ │ │ │ └── JsUserInterface.kt │ │ │ │ │ └── type │ │ │ │ │ │ ├── JClass.kt │ │ │ │ │ │ ├── ui │ │ │ │ │ │ ├── Item.kt │ │ │ │ │ │ ├── JSeekBar.kt │ │ │ │ │ │ ├── JSpinner.kt │ │ │ │ │ │ └── JTextInput.kt │ │ │ │ │ │ └── ws │ │ │ │ │ │ ├── JWebSocket.kt │ │ │ │ │ │ └── internal │ │ │ │ │ │ ├── IWebSocketEvent.kt │ │ │ │ │ │ ├── WebSocketClient.kt │ │ │ │ │ │ └── WebSocketException.kt │ │ │ │ ├── direct_link_upload │ │ │ │ │ ├── DirectUploadEngine.kt │ │ │ │ │ └── DirectUploadFunction.kt │ │ │ │ ├── speech_rule │ │ │ │ │ ├── ScriptEngineContext.kt │ │ │ │ │ └── SpeechRuleEngine.kt │ │ │ │ └── tts │ │ │ │ │ ├── EngineContext.kt │ │ │ │ │ ├── TtsPluginEngine.kt │ │ │ │ │ └── TtsPluginUiEngine.kt │ │ │ ├── speech │ │ │ │ ├── ITextToSpeechSynthesizer.kt │ │ │ │ ├── SynthesizerException.kt │ │ │ │ ├── TtsTextSegment.kt │ │ │ │ └── tts │ │ │ │ │ ├── BaseAudioFormat.kt │ │ │ │ │ ├── BgmTTS.kt │ │ │ │ │ ├── EdgeTtsWS.kt │ │ │ │ │ ├── HttpTTS.kt │ │ │ │ │ ├── ITextToSpeechEngine.kt │ │ │ │ │ ├── LocalTTS.kt │ │ │ │ │ ├── LocalTtsParameter.kt │ │ │ │ │ ├── MsTTS.kt │ │ │ │ │ ├── MsTtsAudioFormat.kt │ │ │ │ │ ├── MsTtsFormatManger.kt │ │ │ │ │ ├── PlayerParams.kt │ │ │ │ │ ├── PluginTTS.kt │ │ │ │ │ └── TtsInfo.kt │ │ │ └── updater │ │ │ │ ├── AppUpdateChecker.kt │ │ │ │ ├── Github.kt │ │ │ │ ├── UpdateResult.kt │ │ │ │ └── WorkflowRuns.kt │ │ │ ├── service │ │ │ ├── forwarder │ │ │ │ ├── AbsForwarderService.kt │ │ │ │ ├── ForwarderServiceManager.kt │ │ │ │ ├── ms │ │ │ │ │ ├── MsTtsForwarderService.kt │ │ │ │ │ └── QSTileService.kt │ │ │ │ └── system │ │ │ │ │ ├── EngineInfo.kt │ │ │ │ │ ├── QSTileService.kt │ │ │ │ │ ├── SysTtsForwarderService.kt │ │ │ │ │ └── VoiceInfo.kt │ │ │ └── systts │ │ │ │ ├── CheckVoiceData.kt │ │ │ │ ├── SystemTtsService.kt │ │ │ │ └── help │ │ │ │ ├── BgmPlayer.kt │ │ │ │ ├── SpeechRuleHelper.kt │ │ │ │ ├── TextReplacer.kt │ │ │ │ ├── TextToSpeechManager.kt │ │ │ │ └── exception │ │ │ │ ├── ConfigLoadException.kt │ │ │ │ ├── PlayException.kt │ │ │ │ ├── RequestException.kt │ │ │ │ ├── SpeechRuleException.kt │ │ │ │ ├── SynthesisException.kt │ │ │ │ ├── TextReplacerException.kt │ │ │ │ └── TtsManagerException.kt │ │ │ ├── ui │ │ │ ├── AppActivityResultContracts.kt │ │ │ ├── AppHelpDocumentActivity.kt │ │ │ ├── AppLog.kt │ │ │ ├── ExoPlayerActivity.kt │ │ │ ├── FilePickerActivity.kt │ │ │ ├── ImportConfigActivity.kt │ │ │ ├── forwarder │ │ │ │ ├── MsForwarderSwitchActivity.kt │ │ │ │ └── SystemForwarderSwitchActivity.kt │ │ │ ├── systts │ │ │ │ ├── ImportConfigFactory.kt │ │ │ │ └── KeyBoardToolPop.kt │ │ │ └── view │ │ │ │ ├── AppDialogs.kt │ │ │ │ ├── Attributes.kt │ │ │ │ ├── BigTextView.kt │ │ │ │ ├── ErrorDialogActivity.kt │ │ │ │ ├── ErrorDialogViewModel.kt │ │ │ │ └── widget │ │ │ │ └── AppTextInputLayout.kt │ │ │ └── utils │ │ │ ├── ASFUriUtils.kt │ │ │ ├── ClipBoardUtils.kt │ │ │ ├── ConfigurationExtensions.kt │ │ │ ├── DecimalUtils.kt │ │ │ ├── EncoderUtils.kt │ │ │ ├── ExtensionUtils.kt │ │ │ ├── FileUtils.kt │ │ │ ├── GcManager.kt │ │ │ ├── HandlerUtils.kt │ │ │ ├── LiveDataNoStickyExt.kt │ │ │ ├── MD5Utils.kt │ │ │ ├── MyTools.kt │ │ │ ├── ParcelUtils.kt │ │ │ ├── SizeUtils.kt │ │ │ ├── SoftKeyboardUtils.kt │ │ │ ├── StringUtils.kt │ │ │ ├── ThrottleUtil.kt │ │ │ ├── ThrowableUtils.kt │ │ │ ├── ToastUtils.kt │ │ │ ├── VarDelegate.kt │ │ │ └── ZipUtils.kt │ ├── kotlin │ │ └── androidx │ │ │ └── compose │ │ │ └── material3 │ │ │ └── pullrefresh │ │ │ ├── PullRefresh.kt │ │ │ ├── PullRefreshIndicator.kt │ │ │ ├── PullRefreshIndicatorTransform.kt │ │ │ └── PullRefreshState.kt │ └── res │ │ ├── color │ │ ├── bg_text_btn_color_selector.xml │ │ └── tab_text_color.xml │ │ ├── drawable │ │ ├── baseline_error_24.xml │ │ ├── baseline_tag_24.xml │ │ ├── bg_spinner_item_selector.xml │ │ ├── ic_app_launcher_foreground.xml │ │ ├── ic_app_notification.xml │ │ ├── ic_baseline_add_24.xml │ │ ├── ic_baseline_compare_arrows_24.xml │ │ ├── ic_baseline_file_open_24.xml │ │ ├── ic_baseline_input_24.xml │ │ ├── ic_baseline_insert_drive_file_24.xml │ │ ├── ic_baseline_output_24.xml │ │ ├── ic_baseline_remove_24.xml │ │ ├── ic_baseline_save_24.xml │ │ ├── ic_baseline_select_all_24.xml │ │ ├── ic_config.xml │ │ ├── ic_microsoft.xml │ │ ├── ic_microsoft_edge.xml │ │ ├── ic_shortcut_plugin.xml │ │ ├── ic_shortcut_replace.xml │ │ ├── ic_shortcut_speech_rule.xml │ │ ├── ic_switch.xml │ │ ├── ic_tts.xml │ │ └── ic_web.xml │ │ ├── layout │ │ ├── big_text_view.xml │ │ ├── material_text_input.xml │ │ ├── seekbar.xml │ │ └── slider.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_app_launcher.xml │ │ └── ic_app_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_app_launcher.webp │ │ └── ic_app_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_app_launcher.webp │ │ └── ic_app_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_app_launcher.webp │ │ └── ic_app_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_app_launcher.webp │ │ ├── ic_app_launcher_round.webp │ │ └── ic_legado_launcher.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_app_launcher.webp │ │ ├── ic_app_launcher_round.webp │ │ ├── ic_app_notification.png │ │ └── ic_legado_launcher.png │ │ ├── values-en │ │ └── strings.xml │ │ ├── values-fa │ │ └── strings.xml │ │ ├── values-ja │ │ └── strings.xml │ │ ├── values-night │ │ ├── colors.xml │ │ └── themes.xml │ │ ├── values-zh-rHK │ │ └── strings.xml │ │ ├── values-zh-rTW │ │ └── strings.xml │ │ ├── values-zh │ │ └── strings.xml │ │ ├── values │ │ ├── arrays.xml │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ic_new_app_background.xml │ │ ├── ic_new_app_launcher_background.xml │ │ ├── keys.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ └── themes.xml │ │ └── xml │ │ ├── file_paths.xml │ │ └── tts_engine.xml │ └── test │ └── java │ └── com │ └── github │ └── jing332 │ └── tts_server_android │ └── HttpTtsUrlUnitTest.kt ├── build.gradle ├── crowdin.yml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── 1.jpg ├── 2.jpg ├── 3.jpg └── 4.jpg ├── settings.gradle └── tts-server-lib ├── .gitignore ├── README.md ├── api_ms_tts.go ├── api_ms_tts_test.go ├── api_scirpt_code_sync_server.go ├── api_sys_tts_server.go ├── api_sys_tts_server_test.go ├── go.mod ├── go.sum ├── ms_tts_server.go ├── net_tools.go ├── sync_server └── script_code_sync_server.go ├── sys_tts_server ├── logic.go ├── logic_test.go ├── public │ └── index.html ├── server.go └── server_test.go └── wrapper └── log.go /.github/workflows/automerge.yml: -------------------------------------------------------------------------------- 1 | name: Auto merge 2 | on: 3 | pull_request: 4 | types: 5 | - labeled 6 | - unlabeled 7 | - synchronize 8 | - opened 9 | - edited 10 | - ready_for_review 11 | - reopened 12 | - unlocked 13 | pull_request_review: 14 | types: 15 | - submitted 16 | check_suite: 17 | types: 18 | - completed 19 | status: {} 20 | jobs: 21 | automerge: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - id: automerge 25 | name: automerge 26 | uses: "pascalgn/automerge-action@v0.16.2" 27 | env: 28 | MERGE_FILTER_AUTHOR: "jing332" 29 | GITHUB_TOKEN: ${{ secrets.TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | .gradle 4 | .DS_Store 5 | /build 6 | /captures 7 | .externalNativeBuild 8 | .cxx 9 | local.properties 10 | 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | - 有好的配置导入提示 2 | - 添加三种主题色 3 | - 新用户自动添加默认配置 4 | - 添加帮助文档,并自动显示 5 | - 朗读规则支持单条导出 6 | - 支持由调用者通过API指定发音配置 7 | - 修复朗读规则导出无拓展名 8 | - 修复本地TTS无法在编辑界面试听 9 | - 修复插件TTS附加数据不更新(解决Azure插件风格和角色变化问题) 10 | - 修复Android8及以下版本的备份问题 -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /release 3 | /libs/tts_server_lib.aar -------------------------------------------------------------------------------- /app/libs/README.md: -------------------------------------------------------------------------------- 1 | # libs 2 | 3 | 将 tts-server-lib 目录下的 tts_server_lib.aar 移动到当前目录,以供Android调用。 -------------------------------------------------------------------------------- /app/libs/rhino-1.7.13-1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgz0227/tts-server-android/045eb75f55a70f5c6fca255e300b600d106cb2c0/app/libs/rhino-1.7.13-1.jar -------------------------------------------------------------------------------- /app/src/androidTest/java/com/github/jing332/tts_server_android/GoLibTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import org.junit.Test 5 | import org.junit.runner.RunWith 6 | 7 | @RunWith(AndroidJUnit4::class) 8 | class GoLibTest { 9 | @Test 10 | fun goLib() { 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/github/jing332/tts_server_android/HttpTtsUrlTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android 2 | 3 | import android.util.Log 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | import com.github.jing332.tts_server_android.constant.AppConst 6 | import com.github.jing332.tts_server_android.model.AnalyzeUrl 7 | import org.junit.Assert.* 8 | import org.junit.Test 9 | import org.junit.runner.RunWith 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class HttpTtsUrlTest { 18 | @Test 19 | fun test() { 20 | // Context of the app under test. 21 | // val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | 23 | // "http://tsn.baidu.com/text2audio,{\"method\": \"POST\", \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4114&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=220&vol=5&aue=6&pit=5&res_tag=audio\"}" 24 | val url = 25 | """ http://192.168.0.109:1233/api/ra ,{"method":"POST","body":"{{String(speakText).replace(/&/g, '&').replace(/\"/g, '"').replace(/'/g, ''').replace(//g, '>')}}"} """ 26 | Log.e("TAG", url) 27 | val a = AnalyzeUrl(url, speakText = "t\\\\est\\测\\\\试") 28 | Log.e("TAG", "baseUrl: " + a.eval().toString()) 29 | 30 | println(AppConst.SCRIPT_ENGINE.eval(""" String("Test\\\\测试") """)) 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/github/jing332/tts_server_android/OkHttpTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import com.drake.net.Net 5 | import okhttp3.MediaType.Companion.toMediaType 6 | import okhttp3.MultipartBody 7 | import okhttp3.RequestBody.Companion.toRequestBody 8 | import okhttp3.Response 9 | import org.junit.Test 10 | import org.junit.runner.RunWith 11 | 12 | @RunWith(AndroidJUnit4::class) 13 | class OkHttpTest { 14 | @Test 15 | fun postMultiPart() { 16 | val json = """ 17 | {"11":"11", "22": "22"} 18 | """.trimIndent() 19 | val url = "http://v2.jt12.de/up-v2.php" 20 | val resp: Response = Net.post(url) { 21 | body = MultipartBody.Builder() 22 | .setType(MultipartBody.FORM) 23 | .addFormDataPart( 24 | "file", 25 | "filename.json", 26 | json.toRequestBody("text/javascript".toMediaType()) 27 | ) 28 | .build() 29 | }.execute() 30 | println("${resp.code} ${resp.message}: ${resp.body?.string()}") 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/github/jing332/tts_server_android/RhinoEngineTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import cn.hutool.crypto.symmetric.SymmetricCrypto 5 | import com.script.javascript.RhinoScriptEngine 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | import org.mozilla.javascript.NativeObject 9 | 10 | @RunWith(AndroidJUnit4::class) 11 | class RhinoEngineTest { 12 | @Test 13 | fun script() { 14 | val jsCode = """ 15 | 16 | """.trimIndent() 17 | 18 | RhinoScriptEngine().apply { 19 | val compiledScript = compile(jsCode) 20 | compiledScript.eval() 21 | println((get("tts") as NativeObject).get("name")) 22 | } 23 | 24 | // PluginEngine().apply { 25 | // println(runScript(jsCode, "测试文本", 1)) 26 | // } 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/debug/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | DB·TTS Server 4 | -------------------------------------------------------------------------------- /app/src/dev/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | D·TTS Server 4 | -------------------------------------------------------------------------------- /app/src/main/assets/defaultData/direct_link_upload.js: -------------------------------------------------------------------------------- 1 | let DirectUploadJS = { 2 | 3 | "喵公子① (有效期7天)": function(config) { 4 | let url = 'https://sy.mgz6.cc/shuyuan' 5 | let zl = 'https://shuyuan.mgz6.cc/shuyuan' 6 | let resp = upload(url, config) 7 | let result = JSON.parse(resp.body().string()) 8 | if (result['msg'] !== 'success') { 9 | throw "error: " + result['msg'] 10 | } 11 | 12 | return zl + '/' + result['data'] 13 | } 14 | , 15 | "喵公子②·备用 (有效期7天)": function(config) { 16 | let url = 'https://sy.miaogongzi.cc/shuyuan' 17 | let zl = 'https://shuyuan.miaogongzi.cc/shuyuan' 18 | let resp = upload(url, config) 19 | let result = JSON.parse(resp.body().string()) 20 | if (result['msg'] !== 'success') { 21 | throw "error: " + result['msg'] 22 | } 23 | 24 | return zl + '/' + result['data'] 25 | } 26 | } 27 | 28 | function upload(url, config, extra) { 29 | let form = { 30 | "file":{ 31 | 'body': config, 32 | 'fileName': 'config.json', 33 | 'contentType': 'application/json', 34 | } 35 | } 36 | 37 | return ttsrv.httpPostMultipart(url, form) 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/assets/defaultData/list.json: -------------------------------------------------------------------------------- 1 | [ 2 | 3 | ] 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/assets/defaultData/speech_rule.js: -------------------------------------------------------------------------------- 1 | let SpeechRuleJS = { 2 | name: "旁白/对话", 3 | id: "ttsrv.multi_voice", 4 | author: "TTS Server", 5 | version: 4, 6 | tags: {narration: "旁白", dialogue: "对话"}, 7 | 8 | handleText(text) { 9 | const list = []; 10 | let tmpStr = ""; 11 | let endTag = "narration"; 12 | 13 | text.split("").forEach((char, index) => { 14 | tmpStr += char; 15 | 16 | if (char === '“') { 17 | endTag = "dialogue"; 18 | list.push({text: tmpStr, tag: "narration"}); 19 | tmpStr = ""; 20 | } else if (char === '”') { 21 | endTag = "narration"; 22 | tmpStr = tmpStr.slice(0, -1) 23 | list.push({text: tmpStr, tag: "dialogue"}); 24 | tmpStr = ""; 25 | } else if (index === text.length - 1) { 26 | list.push({text: tmpStr, tag: endTag}); 27 | } 28 | }); 29 | 30 | return list; 31 | }, 32 | 33 | splitText(text) { 34 | let separatorStr = "。??!!;;" 35 | 36 | let list = [] 37 | let tmpStr = "" 38 | text.split("").forEach((char, index) => { 39 | tmpStr += char 40 | 41 | if (separatorStr.includes(char)) { 42 | list.push(tmpStr) 43 | tmpStr = "" 44 | } else if (index === text.length - 1) { 45 | list.push(tmpStr); 46 | } 47 | }) 48 | 49 | return list.filter(item => item.replace(/[“”]/g, '').trim().length > 0); 50 | } 51 | 52 | }; 53 | -------------------------------------------------------------------------------- /app/src/main/assets/help/app.md: -------------------------------------------------------------------------------- 1 | version-1 2 | 3 | ### 此帮助文档可在左侧滑菜单打开。 4 | 5 | [![Q群](https://img.shields.io/badge/Q%E7%BE%A4-点击加入-blue.svg)](https://h5.qun.qq.com/s/hxc6YglYE8) 6 | [![Issue](https://img.shields.io/badge/Github-Issue-greeb.svg)](https://github.com/mgz0227/tts-server-android/issues) 7 | [![Dev](https://img.shields.io/github/actions/workflow/status/mgz0227/tts-server-android/test.yml?label=%E5%BC%80%E5%8F%91%E7%89%88)](https://github.com/mgz0227/tts-server-android/actions/workflows/test.yml) 8 | 9 | # TTS Server 10 | 本应用有3个独立功能,通过左侧滑菜单进行切换。 11 | 12 | 13 | ## 1️⃣ 系统TTS 14 | 以下4个界面,在右上角更多选项中都有独立的导入、导出功能。您还可在设置中进行全部备份、恢复等操作。 15 | 16 | ### 主界面 17 | 配置列表,用于管理TTS配置,您可使用分组功能进行一键切换多个配置。 18 | - 可在设置中调换 `编辑`与`试听` 按钮的位置 ( 长按编辑按钮进行试听,反之,长按试听按钮进行编辑 ) 19 | 20 | ### 朗读规则 21 | 用于处理朗读文本,根据用户配置的标签进行匹配TTS配置(如:旁白/对话)。 22 | 程序已内置 基于中文的双引号的 `旁白对话` 朗读规则,您可直接进行使用。 23 | 24 | ### 插件 25 | 用于扩展TTS功能,使用JS脚本进行调用互联网的上的TTS接口,如:内置的`Azure插件`。 26 | 27 | ### 替换规则 28 | 用于替换朗读文本进行纠正发音等操作,如:将“你好”替换为“您好” 29 | 30 | 高级示例: 31 | - 将字数5以内的对话的双引号替换为【】,以达到旁白朗读的目的。 32 | ``` 33 | (启用正则表达式) 34 | 替换规则:(“)(.{1,5})(”) 35 | 替换为:【$2】 36 | ``` 37 | 38 | ## 👨‍🏫 系统TTS 常见问题 39 | ### 1. 锁屏后一段时间朗读突然停止? 40 | > 在 `系统设置->应用->电池优化` 中将本APP与阅读APP加入电池优化白名单。 41 | > 42 | > 对于本APP,您可在左侧滑菜单中单击 `电池优化白名单` 进行快捷设置。 43 | > 44 | > PS: 对于国内系统,您可能还需对后台任务上锁,启用后台权限等操作。 45 | 46 | ### 2. 段落间隔时间长? 47 | > 一般是由于网络延迟原因,因为 安卓系统TTS 服务的技术限制,导致无法预缓存音频,故每次只能同步获取。 48 | 49 | ### 3. 启动朗读时提示 `⚠️ 缺少{朗读全部},...` ? 50 | > 添加一个`朗读全部`类型的TTS配置并启用。或尝试开启多语音选项使用 `标签`配置 51 | 52 | ### 4. 启动朗读时提示 `⚠️无标签配置,...!` 53 | > 添加一个 `标签` 类型的TTS配置并启用。或尝试关闭多语音选项使用 `朗读全部` 。 54 | 55 | 56 | ## 2️⃣ 系统TTS转发器 57 | 用于将安卓系统TTS转为HTTP网络接口形式,便于在网页调用。 58 | 59 | ## 3️⃣ 微软TTS转发器 60 | 用于将Edge大声朗读接口简化为HTTP网络接口形式,便于`开源阅读`进行调用。 61 | 62 | 这也是`TTS Server`名称的来源: 63 | 64 | 早期,我将 tts-server-go 移植到安卓,即得名 tts-server-android (Github项目名) -------------------------------------------------------------------------------- /app/src/main/ic_new_app-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgz0227/tts-server-android/045eb75f55a70f5c6fca255e300b600d106cb2c0/app/src/main/ic_new_app-playstore.png -------------------------------------------------------------------------------- /app/src/main/ic_new_app_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgz0227/tts-server-android/045eb75f55a70f5c6fca255e300b600d106cb2c0/app/src/main/ic_new_app_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/App.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.Application 5 | import android.content.Context 6 | import android.content.Intent 7 | import android.os.Process 8 | import com.github.jing332.tts_server_android.model.hanlp.HanlpManager 9 | import kotlinx.coroutines.DelicateCoroutinesApi 10 | import kotlinx.coroutines.GlobalScope 11 | import kotlinx.coroutines.launch 12 | import java.util.* 13 | import kotlin.properties.Delegates 14 | 15 | 16 | val app: App 17 | inline get() = App.instance 18 | 19 | @Suppress("DEPRECATION") 20 | class App : Application() { 21 | companion object { 22 | const val TAG = "App" 23 | var instance: App by Delegates.notNull() 24 | val context: Context by lazy { instance } 25 | } 26 | 27 | override fun attachBaseContext(base: Context) { 28 | super.attachBaseContext(base.apply { AppLocale.setLocale(base) }) 29 | } 30 | 31 | @SuppressLint("SdCardPath") 32 | @OptIn(DelicateCoroutinesApi::class) 33 | override fun onCreate() { 34 | super.onCreate() 35 | instance = this 36 | CrashHandler(this) 37 | 38 | GlobalScope.launch { 39 | HanlpManager.initDir( 40 | context.getExternalFilesDir("hanlp")?.absolutePath 41 | ?: "/data/data/$packageName/files/hanlp" 42 | ) 43 | } 44 | } 45 | 46 | @SuppressLint("UnspecifiedImmutableFlag") 47 | fun restart() { 48 | val intent = packageManager.getLaunchIntentForPackage(packageName)!! 49 | intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 50 | startActivity(intent); 51 | //杀掉以前进程 52 | Process.killProcess(Process.myPid()); 53 | } 54 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/CrashHandler.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android 2 | 3 | import android.content.Context 4 | import com.github.jing332.tts_server_android.constant.AppConst 5 | import com.github.jing332.tts_server_android.utils.ClipboardUtils 6 | import com.github.jing332.tts_server_android.utils.longToast 7 | import com.github.jing332.tts_server_android.utils.runOnUI 8 | import tts_server_lib.Tts_server_lib 9 | import java.time.LocalDateTime 10 | 11 | 12 | class CrashHandler(var context: Context) : Thread.UncaughtExceptionHandler { 13 | private var mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler() 14 | 15 | init { 16 | Thread.setDefaultUncaughtExceptionHandler(this) 17 | } 18 | 19 | override fun uncaughtException(t: Thread, e: Throwable) { 20 | handleException(e) 21 | mDefaultHandler?.uncaughtException(t, e) 22 | } 23 | 24 | private fun handleException(e: Throwable) { 25 | context.longToast("TTS Server已崩溃 上传日志中 稍后将会复制到剪贴板") 26 | val log = "\n${LocalDateTime.now()}" + 27 | "\n版本代码:${AppConst.appInfo.versionCode}, 版本名称:${AppConst.appInfo.versionName}\n" + 28 | "崩溃详情:\n${e.stackTraceToString()}" 29 | val copyContent: String = try { 30 | if (BuildConfig.DEBUG) 31 | log 32 | else 33 | Tts_server_lib.uploadLog(log) 34 | } catch (e: Exception) { 35 | e.printStackTrace() 36 | log 37 | } 38 | 39 | runOnUI { 40 | ClipboardUtils.copyText("TTS-Server崩溃日志", copyContent) 41 | context.longToast("已将日志复制到剪贴板") 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/bean/EdgeVoiceBean.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.bean 2 | 3 | 4 | import kotlinx.serialization.SerialName 5 | import kotlinx.serialization.Serializable 6 | 7 | @Serializable 8 | data class EdgeVoiceBean( 9 | @SerialName("Gender") 10 | val gender: String, 11 | @SerialName("Locale") 12 | val locale: String, 13 | @SerialName("Name") 14 | val name: String, 15 | @SerialName("ShortName") 16 | val shortName: String, 17 | @SerialName("FriendlyName") 18 | val friendlyName: String, 19 | // @SerialName("Status") 20 | // val status: String, 21 | // @SerialName("SuggestedCodec") 22 | // val suggestedCodec: String, 23 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/bean/GithubReleaseApiBean.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.bean 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class GithubReleaseApiBean( 8 | @SerialName("assets") 9 | val assets: List, 10 | @SerialName("body") 11 | val body: String, 12 | @SerialName("tag_name") 13 | val tagName: String, 14 | ) 15 | 16 | @Serializable 17 | data class Asset( 18 | @SerialName("browser_download_url") 19 | val browserDownloadUrl: String, 20 | @SerialName("name") 21 | val name: String, 22 | @SerialName("size") 23 | val size: Int, 24 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/bean/LegadoHttpTts.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.bean 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | 7 | @Serializable 8 | data class LegadoHttpTts( 9 | @SerialName("header") val header: String = "", 10 | @SerialName("id") val id: Long = 0, 11 | @SerialName("name") val name: String = "", 12 | @SerialName("url") val url: String = "" 13 | 14 | // @SerialName("concurrentRate") 15 | // val concurrentRate: String, 16 | // @SerialName("contentType") 17 | // val contentType: String, 18 | // @SerialName("enabledCookieJar") 19 | // val enabledCookieJar: Boolean, 20 | // @SerialName("lastUpdateTime") 21 | // val lastUpdateTime: Long, 22 | // @SerialName("loginCheckJs") 23 | // val loginCheckJs: String, 24 | // @SerialName("loginUi") 25 | // val loginUi: String, 26 | // @SerialName("loginUrl") 27 | // val loginUrl: String, 28 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/ShadowReorderableItem.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose 2 | 3 | import android.view.HapticFeedbackConstants 4 | import androidx.compose.animation.core.animateDpAsState 5 | import androidx.compose.foundation.layout.Box 6 | import androidx.compose.foundation.layout.Column 7 | import androidx.compose.foundation.lazy.LazyItemScope 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.draw.shadow 11 | import androidx.compose.ui.platform.LocalView 12 | import androidx.compose.ui.unit.dp 13 | import org.burnoutcrew.reorderable.ReorderableItem 14 | import org.burnoutcrew.reorderable.ReorderableState 15 | 16 | @Composable 17 | fun LazyItemScope.ShadowReorderableItem( 18 | reorderableState: ReorderableState<*>, 19 | key: Any, 20 | content: @Composable LazyItemScope.(isDragging: Boolean) -> Unit 21 | ) { 22 | val view = LocalView.current 23 | ReorderableItem(reorderableState, key) { isDragging -> 24 | if (isDragging) { 25 | view.isHapticFeedbackEnabled = true 26 | view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) 27 | } 28 | 29 | val elevation = 30 | animateDpAsState(if (isDragging) 24.dp else 0.dp, label = "") 31 | Box( 32 | modifier = Modifier 33 | .shadow(elevation.value) 34 | ) { 35 | content(isDragging) 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/backup/Type.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.backup 2 | 3 | import com.github.jing332.tts_server_android.R 4 | 5 | 6 | sealed class Type(val nameStrId: Int) { 7 | companion object { 8 | val typeList by lazy { 9 | listOf( 10 | Preference, 11 | List, 12 | SpeechRule, 13 | ReplaceRule, 14 | Plugin, 15 | PluginVars 16 | ) 17 | } 18 | } 19 | 20 | data object Preference : Type(R.string.preference_settings) 21 | data object List : Type(R.string.config_list) 22 | data object SpeechRule : Type(R.string.speech_rule) 23 | data object ReplaceRule : Type(R.string.replace_rule) 24 | 25 | abstract class IPlugin(val id: Int, val includeVars: Boolean) : Type(id) 26 | object Plugin : IPlugin(R.string.plugin, false) 27 | object PluginVars : IPlugin(R.string.plugin_vars, true) 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/codeeditor/CodeEditor.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.codeeditor 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.Modifier 5 | import androidx.compose.ui.platform.LocalContext 6 | import androidx.compose.ui.viewinterop.AndroidView 7 | import com.github.jing332.text_searcher.ui.plugin.CodeEditorHelper 8 | import com.github.jing332.tts_server_android.conf.CodeEditorConfig 9 | import io.github.rosemoe.sora.widget.CodeEditor 10 | 11 | @Composable 12 | fun CodeEditor(modifier: Modifier, onUpdate: (CodeEditor) -> Unit) { 13 | val context = LocalContext.current 14 | 15 | AndroidView(modifier = modifier, factory = { 16 | CodeEditor(it).apply { 17 | val helper = CodeEditorHelper(context, this) 18 | helper.initEditor() 19 | tag = helper 20 | } 21 | }, update = { 22 | // val helper = (it.tag as CodeEditorHelper) 23 | onUpdate(it) 24 | }) 25 | } 26 | 27 | fun CodeEditor.helper(): CodeEditorHelper { 28 | return tag as CodeEditorHelper 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/codeeditor/ThemeSettingsDialog.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.codeeditor 2 | 3 | import androidx.compose.material3.Text 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.getValue 6 | import androidx.compose.runtime.mutableStateOf 7 | import androidx.compose.runtime.remember 8 | import androidx.compose.runtime.setValue 9 | import androidx.compose.ui.res.stringResource 10 | import androidx.compose.ui.tooling.preview.Preview 11 | import com.github.jing332.tts_server_android.R 12 | import com.github.jing332.tts_server_android.compose.widgets.AppSelectionDialog 13 | import com.github.jing332.tts_server_android.conf.CodeEditorConfig 14 | import com.github.jing332.tts_server_android.constant.CodeEditorTheme 15 | 16 | @Composable 17 | internal fun ThemeSettingsDialog(onDismissRequest: () -> Unit) { 18 | AppSelectionDialog( 19 | onDismissRequest = onDismissRequest, 20 | title = { Text(stringResource(id = R.string.theme)) }, 21 | value = CodeEditorConfig.theme.value, 22 | values = CodeEditorTheme.values().toList(), 23 | entries = CodeEditorTheme.values().map { it.id.ifBlank { stringResource(id = R.string.theme_default) } }, 24 | onClick = { value, _ -> 25 | CodeEditorConfig.theme.value = value as CodeEditorTheme 26 | onDismissRequest() 27 | } 28 | ) 29 | } 30 | 31 | @Preview 32 | @Composable 33 | fun PreviewEditorThemeDialog() { 34 | var show by remember { mutableStateOf(true) } 35 | if (show) { 36 | ThemeSettingsDialog { 37 | show = false 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/forwarder/ConfigViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.forwarder 2 | 3 | import androidx.compose.foundation.lazy.LazyListState 4 | import androidx.compose.runtime.mutableStateListOf 5 | import androidx.lifecycle.ViewModel 6 | import com.github.jing332.tts_server_android.ui.AppLog 7 | 8 | class ConfigViewModel : ViewModel() { 9 | val logs = mutableStateListOf() 10 | val logState by lazy { LazyListState() } 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/nav/NavRoutes.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.nav 2 | 3 | import androidx.annotation.StringRes 4 | import androidx.compose.foundation.layout.size 5 | import androidx.compose.material.icons.Icons 6 | import androidx.compose.material.icons.filled.Settings 7 | import androidx.compose.material3.Icon 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.res.painterResource 11 | import androidx.compose.ui.unit.dp 12 | import com.github.jing332.tts_server_android.R 13 | 14 | sealed class NavRoutes( 15 | val id: String, 16 | @StringRes val strId: Int, 17 | val icon: @Composable () -> Unit = {}, 18 | ) { 19 | companion object { 20 | val routes by lazy { 21 | listOf( 22 | SystemTTS, 23 | SystemTtsForwarder, 24 | MsTtsForwarder, 25 | Settings 26 | ) 27 | } 28 | } 29 | 30 | data object SystemTTS : NavRoutes("system_tts", R.string.system_tts, icon = { 31 | Icon(modifier = Modifier.size(24.dp), painter = painterResource(id = R.drawable.ic_tts), contentDescription = null) 32 | }) 33 | 34 | data object SystemTtsForwarder : 35 | NavRoutes("system_tts_forwarder", R.string.forwarder_systts, icon = { 36 | Icon(modifier = Modifier.size(24.dp), painter = painterResource(id = R.drawable.ic_tts), contentDescription = null) 37 | }) 38 | 39 | data object MsTtsForwarder : NavRoutes("ms_tts_forwarder", R.string.forwarder_ms, icon = { 40 | Icon(painter = painterResource(id = R.drawable.ic_microsoft), null) 41 | }) 42 | 43 | data object Settings : NavRoutes("settings", R.string.settings, icon = { 44 | Icon(Icons.Default.Settings, null) 45 | }) 46 | 47 | // ============= 48 | data object TtsEdit : NavRoutes("tts_edit", 0) { 49 | const val DATA = "data" 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/systts/ConfigDeleteDialog.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.systts 2 | 3 | import androidx.compose.material.icons.Icons 4 | import androidx.compose.material.icons.filled.DeleteForever 5 | import androidx.compose.material3.AlertDialog 6 | import androidx.compose.material3.Icon 7 | import androidx.compose.material3.MaterialTheme 8 | import androidx.compose.material3.Text 9 | import androidx.compose.material3.TextButton 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.res.stringResource 12 | import androidx.compose.ui.text.font.FontWeight 13 | import com.github.jing332.tts_server_android.R 14 | 15 | @Composable 16 | fun ConfigDeleteDialog(onDismissRequest: () -> Unit, name: String, onConfirm: () -> Unit) { 17 | AlertDialog( 18 | onDismissRequest = onDismissRequest, 19 | title = { Text(stringResource(id = R.string.is_confirm_delete)) }, 20 | text = { 21 | Text( 22 | name, 23 | style = MaterialTheme.typography.titleMedium, 24 | fontWeight = FontWeight.Bold 25 | ) 26 | }, 27 | confirmButton = { 28 | TextButton(onClick = { 29 | onConfirm() 30 | }) { 31 | Text( 32 | stringResource(id = R.string.delete), 33 | color = MaterialTheme.colorScheme.error, 34 | fontWeight = FontWeight.Bold 35 | ) 36 | } 37 | }, 38 | dismissButton = { 39 | TextButton(onClick = onDismissRequest) { 40 | Text(stringResource(id = R.string.cancel)) 41 | } 42 | }, 43 | icon = { 44 | Icon( 45 | Icons.Default.DeleteForever, 46 | contentDescription = null, 47 | tint = MaterialTheme.colorScheme.error 48 | ) 49 | } 50 | ) 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/systts/SystemTtsViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.systts 2 | 3 | import androidx.compose.foundation.lazy.LazyListState 4 | import androidx.lifecycle.ViewModel 5 | 6 | class SystemTtsViewModel : ViewModel() { 7 | 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/systts/TtsLogViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.systts 2 | 3 | import androidx.compose.runtime.mutableStateListOf 4 | import androidx.lifecycle.ViewModel 5 | import com.github.jing332.tts_server_android.ui.AppLog 6 | 7 | class TtsLogViewModel : ViewModel() { 8 | val logs = mutableStateListOf() 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/systts/list/GlobalAudioParamsDialog.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.systts.list 2 | 3 | import androidx.compose.material3.Text 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.getValue 6 | import androidx.compose.runtime.remember 7 | import androidx.compose.ui.res.stringResource 8 | import com.github.jing332.tts_server_android.R 9 | import com.github.jing332.tts_server_android.conf.SystemTtsConfig 10 | 11 | @Composable 12 | fun GlobalAudioParamsDialog(onDismissRequest: () -> Unit) { 13 | var speed by remember { SystemTtsConfig.audioParamsSpeed } 14 | var volume by remember { SystemTtsConfig.audioParamsVolume } 15 | var pitch by remember { SystemTtsConfig.audioParamsPitch } 16 | BasicAudioParamsDialog( 17 | title = { Text(stringResource(id = R.string.audio_params_settings)) }, 18 | onDismissRequest = onDismissRequest, 19 | 20 | speedRange = 0.1f..3f, 21 | speed = speed, 22 | onSpeedChange = { speed = it }, 23 | 24 | volumeRange = 0.1f..3f, 25 | volume = volume, 26 | onVolumeChange = { volume = it }, 27 | 28 | pitchRange = 0.1f..3f, 29 | pitch = pitch, 30 | onPitchChange = { pitch = it }, 31 | 32 | onReset = { 33 | speed = 1f 34 | volume = 1f 35 | pitch = 1f 36 | } 37 | ) 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/systts/list/IntSlider.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.systts.list 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.Modifier 5 | import com.github.jing332.tts_server_android.compose.widgets.LabelSlider 6 | 7 | @Composable 8 | fun IntSlider( 9 | modifier: Modifier = Modifier, 10 | label: String, 11 | value: Float, 12 | onValueChange: (Float) -> Unit, 13 | valueRange: ClosedFloatingPointRange 14 | ) { 15 | LabelSlider( 16 | modifier = modifier, 17 | value = value, 18 | onValueChange = onValueChange, 19 | valueRange = valueRange, 20 | text = label, 21 | buttonSteps = 1f, 22 | buttonLongSteps = 10f 23 | ) 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/systts/list/ListExportBottomSheet.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.systts.list 2 | 3 | import androidx.compose.runtime.Composable 4 | import com.github.jing332.tts_server_android.compose.systts.ConfigExportBottomSheet 5 | import com.github.jing332.tts_server_android.constant.AppConst 6 | import com.github.jing332.tts_server_android.data.entities.systts.GroupWithSystemTts 7 | import kotlinx.serialization.encodeToString 8 | 9 | @Composable 10 | fun ListExportBottomSheet(onDismissRequest: () -> Unit, list: List) { 11 | ConfigExportBottomSheet( 12 | json = AppConst.jsonBuilder.encodeToString(list), 13 | onDismissRequest = onDismissRequest, 14 | fileName = "ttsrv-list.json" 15 | ) 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/systts/list/edit/TagDataClearConfirmDialog.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.systts.list.edit 2 | 3 | import androidx.compose.material3.MaterialTheme 4 | import androidx.compose.material3.Text 5 | import androidx.compose.material3.TextButton 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.res.stringResource 8 | import androidx.compose.ui.text.font.FontWeight 9 | import com.github.jing332.tts_server_android.R 10 | import com.github.jing332.tts_server_android.compose.widgets.AppDialog 11 | 12 | @Composable 13 | fun TagDataClearConfirmDialog( 14 | tagData: String, 15 | onDismissRequest: () -> Unit, 16 | onConfirm: () -> Unit 17 | ) { 18 | AppDialog( 19 | title = { Text(stringResource(id = R.string.tag_data_clear_warn)) }, 20 | content = { Text(tagData, style = MaterialTheme.typography.bodySmall) }, 21 | buttons = { 22 | TextButton(onClick = onDismissRequest) { 23 | Text(stringResource(id = R.string.cancel)) 24 | } 25 | TextButton(onClick = onConfirm) { 26 | Text( 27 | stringResource(id = R.string.delete), 28 | fontWeight = FontWeight.Bold, 29 | color = MaterialTheme.colorScheme.error 30 | ) 31 | } 32 | }, 33 | onDismissRequest = onDismissRequest 34 | ) 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/systts/list/edit/ui/LocalTtsViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.systts.list.edit.ui 2 | 3 | import android.speech.tts.TextToSpeech 4 | import android.speech.tts.Voice 5 | import androidx.compose.runtime.mutableStateListOf 6 | import androidx.lifecycle.ViewModel 7 | import com.github.jing332.tts_server_android.App 8 | import com.github.jing332.tts_server_android.model.LocalTtsEngine 9 | import java.util.Locale 10 | 11 | class LocalTtsViewModel : ViewModel() { 12 | private val engine by lazy { LocalTtsEngine(App.context) } 13 | 14 | val engines = mutableStateListOf() 15 | val locales = mutableStateListOf() 16 | val voices = mutableStateListOf() 17 | 18 | fun init() { 19 | engines.clear() 20 | engines.addAll(LocalTtsEngine.getEngines()) 21 | } 22 | 23 | suspend fun setEngine(engine: String) { 24 | val ok = this.engine.setEngine(engine) 25 | if (!ok) return 26 | 27 | engines.clear() 28 | engines.addAll(LocalTtsEngine.getEngines()) 29 | } 30 | 31 | fun updateLocales() { 32 | locales.clear() 33 | locales.addAll(engine.locales) 34 | } 35 | 36 | fun updateVoices(locale: String) { 37 | voices.clear() 38 | voices.addAll(engine.voices 39 | .filter { it.locale.toLanguageTag() == locale } 40 | .sortedBy { it.name } 41 | ) 42 | } 43 | 44 | override fun onCleared() { 45 | super.onCleared() 46 | 47 | engine.shutdown() 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/systts/list/edit/ui/MsTtsViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.systts.list.edit.ui 2 | 3 | import androidx.compose.runtime.getValue 4 | import androidx.compose.runtime.mutableStateListOf 5 | import androidx.compose.runtime.mutableStateOf 6 | import androidx.compose.runtime.setValue 7 | import androidx.lifecycle.ViewModel 8 | import androidx.lifecycle.viewModelScope 9 | import com.drake.net.utils.withIO 10 | import com.github.jing332.tts_server_android.constant.MsTtsApiType 11 | import com.github.jing332.tts_server_android.model.GeneralVoiceData 12 | import com.github.jing332.tts_server_android.model.MsTtsEditRepository 13 | import kotlinx.coroutines.Dispatchers 14 | import kotlinx.coroutines.launch 15 | 16 | class MsTtsViewModel : ViewModel() { 17 | companion object { 18 | private val repo: MsTtsEditRepository by lazy { MsTtsEditRepository() } 19 | } 20 | 21 | private var mAllList: List = emptyList() 22 | 23 | var isLoading by mutableStateOf(true) 24 | 25 | val locales = mutableStateListOf>() 26 | val voices = mutableStateListOf() 27 | 28 | suspend fun load(){ 29 | isLoading = true 30 | mAllList = withIO { repo.voicesByApi(MsTtsApiType.EDGE) } 31 | isLoading = false 32 | } 33 | 34 | fun updateLocales() { 35 | locales.clear() 36 | locales.addAll( 37 | mAllList.map { it.locale to it.localeName }.distinctBy { it.first } 38 | .sortedBy { it.first } 39 | ) 40 | } 41 | 42 | fun onLocaleChanged(locale: String) { 43 | voices.clear() 44 | // if (locale.isEmpty()) return 45 | voices.addAll(mAllList.filter { it.locale == locale }.sortedBy { it.voiceName }) 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/systts/list/edit/ui/SaveHandler.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.systts.list.edit.ui 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.DisposableEffect 5 | import androidx.compose.runtime.remember 6 | import androidx.compose.runtime.staticCompositionLocalOf 7 | 8 | internal val LocalSaveCallBack = 9 | staticCompositionLocalOf> { mutableListOf() } 10 | 11 | internal fun interface SaveCallBack { 12 | suspend fun onSave(): Boolean 13 | } 14 | 15 | @Composable 16 | internal fun rememberSaveCallBacks() = remember { mutableListOf() } 17 | 18 | @Composable 19 | internal fun SaveActionHandler(cb: SaveCallBack) { 20 | val cbs = LocalSaveCallBack.current 21 | DisposableEffect(Unit) { 22 | cbs.add(cb) 23 | onDispose { 24 | cbs.remove(cb) 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/systts/list/edit/ui/TtsUI.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.systts.list.edit.ui 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.MutableState 5 | import androidx.compose.runtime.mutableStateOf 6 | import androidx.compose.runtime.remember 7 | import androidx.compose.ui.Modifier 8 | import com.github.jing332.tts_server_android.data.entities.systts.SystemTts 9 | import kotlinx.coroutines.CoroutineScope 10 | 11 | typealias CallbackState = MutableState<(suspend () -> Unit)?> 12 | typealias CallbackStateWithRet = MutableState<(suspend CoroutineScope.() -> Boolean)?> 13 | 14 | @Composable 15 | fun rememberCallbackState() = 16 | remember { mutableStateOf<(suspend () -> Unit)?>(null) } 17 | 18 | @Composable 19 | fun rememberCallbackStateWithRet() = 20 | remember { mutableStateOf<(suspend CoroutineScope.() -> Boolean)?>(null) } 21 | 22 | open class TtsUI { 23 | @Composable 24 | open fun ParamsEditScreen( 25 | modifier: Modifier, 26 | systts: SystemTts, 27 | onSysttsChange: (SystemTts) -> Unit 28 | ) { 29 | } 30 | 31 | 32 | @Composable 33 | open fun FullEditScreen( 34 | modifier: Modifier, 35 | systts: SystemTts, 36 | onSysttsChange: (SystemTts) -> Unit, 37 | onSave: () -> Unit, 38 | onCancel: () -> Unit 39 | ) { 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/systts/list/edit/ui/TtsUiFactory.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.systts.list.edit.ui 2 | 3 | import com.github.jing332.tts_server_android.model.speech.tts.BgmTTS 4 | import com.github.jing332.tts_server_android.model.speech.tts.ITextToSpeechEngine 5 | import com.github.jing332.tts_server_android.model.speech.tts.LocalTTS 6 | import com.github.jing332.tts_server_android.model.speech.tts.MsTTS 7 | import com.github.jing332.tts_server_android.model.speech.tts.PluginTTS 8 | 9 | object TtsUiFactory { 10 | fun from(tts: ITextToSpeechEngine): TtsUI? { 11 | return when (tts) { 12 | is PluginTTS -> PluginTtsUI() 13 | is MsTTS -> MsTtsUI() 14 | is LocalTTS -> LocalTtsUI() 15 | is BgmTTS -> BgmTtsUI() 16 | 17 | else -> null 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/systts/list/edit/ui/widgets/AuditionTextField.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.systts.list.edit.ui.widgets 2 | 3 | import androidx.compose.material.icons.Icons 4 | import androidx.compose.material.icons.filled.Headset 5 | import androidx.compose.material3.Icon 6 | import androidx.compose.material3.IconButton 7 | import androidx.compose.material3.OutlinedTextField 8 | import androidx.compose.material3.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.runtime.getValue 11 | import androidx.compose.runtime.remember 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.res.stringResource 14 | import com.github.jing332.tts_server_android.R 15 | import com.github.jing332.tts_server_android.conf.AppConfig 16 | 17 | @Composable 18 | fun AuditionTextField(modifier: Modifier, onAudition: (String) -> Unit) { 19 | var text by remember { AppConfig.testSampleText } 20 | OutlinedTextField( 21 | modifier = modifier, 22 | label = {Text(stringResource(id = R.string.audition_text))}, 23 | value = text, 24 | onValueChange = { text = it }, 25 | trailingIcon = { 26 | IconButton(onClick = { onAudition(text) }) { 27 | Icon(Icons.Default.Headset, stringResource(id = R.string.audition)) 28 | } 29 | } 30 | ) 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/systts/list/edit/ui/widgets/BaseViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.systts.list.edit.ui.widgets 2 | 3 | import androidx.lifecycle.ViewModel 4 | import com.github.jing332.tts_server_android.app 5 | import com.github.jing332.tts_server_android.help.audio.AudioPlayer 6 | 7 | open class BaseViewModel : ViewModel() { 8 | val audioPlayer by lazy { AudioPlayer(app) } 9 | 10 | override fun onCleared() { 11 | super.onCleared() 12 | 13 | audioPlayer.release() 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/systts/list/edit/ui/widgets/TtsTopAppBar.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.systts.list.edit.ui.widgets 2 | 3 | import androidx.compose.material.icons.Icons 4 | import androidx.compose.material.icons.filled.ArrowBack 5 | import androidx.compose.material.icons.filled.MoreVert 6 | import androidx.compose.material.icons.filled.Save 7 | import androidx.compose.material3.ExperimentalMaterial3Api 8 | import androidx.compose.material3.Icon 9 | import androidx.compose.material3.IconButton 10 | import androidx.compose.material3.TopAppBar 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.runtime.getValue 13 | import androidx.compose.runtime.mutableStateOf 14 | import androidx.compose.runtime.remember 15 | import androidx.compose.runtime.setValue 16 | import androidx.compose.ui.res.stringResource 17 | import com.github.jing332.tts_server_android.R 18 | 19 | @OptIn(ExperimentalMaterial3Api::class) 20 | @Composable 21 | fun TtsTopAppBar( 22 | title: @Composable () -> Unit, 23 | onBackAction: () -> Unit, 24 | onSaveAction: () -> Unit, 25 | 26 | moreOptions: (@Composable (dismiss: () -> Unit) -> Unit)? = null 27 | ) { 28 | TopAppBar( 29 | title = title, 30 | navigationIcon = { 31 | IconButton(onClick = onBackAction) { 32 | Icon(Icons.Default.ArrowBack, stringResource(id = R.string.nav_back)) 33 | } 34 | }, 35 | actions = { 36 | IconButton(onClick = onSaveAction) { 37 | Icon(Icons.Default.Save, stringResource(id = R.string.save)) 38 | } 39 | 40 | var showOptions by remember { mutableStateOf(false) } 41 | if (moreOptions != null) 42 | IconButton(onClick = { 43 | 44 | }) { 45 | Icon(Icons.Default.MoreVert, stringResource(id = R.string.more_options)) 46 | moreOptions { showOptions = false } 47 | } 48 | } 49 | ) 50 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/systts/plugin/NavRoutes.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.systts.plugin 2 | 3 | internal sealed class NavRoutes(val id: String) { 4 | data object PluginManager : NavRoutes("plugin_manager") 5 | data object PluginEdit : NavRoutes("plugin_edit") { 6 | const val KEY_DATA = "data" 7 | } 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/systts/plugin/PluginExportBottomSheet.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.systts.plugin 2 | 3 | import androidx.compose.foundation.clickable 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.material3.Checkbox 7 | import androidx.compose.material3.MaterialTheme 8 | import androidx.compose.material3.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.runtime.getValue 11 | import androidx.compose.runtime.mutableStateOf 12 | import androidx.compose.runtime.remember 13 | import androidx.compose.runtime.setValue 14 | import androidx.compose.ui.Alignment 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.draw.clip 17 | import androidx.compose.ui.res.stringResource 18 | import androidx.compose.ui.semantics.Role 19 | import androidx.compose.ui.unit.dp 20 | import com.github.jing332.tts_server_android.R 21 | import com.github.jing332.tts_server_android.compose.systts.ConfigExportBottomSheet 22 | import com.github.jing332.tts_server_android.compose.widgets.TextCheckBox 23 | 24 | @Composable 25 | internal fun PluginExportBottomSheet( 26 | onDismissRequest: () -> Unit, 27 | fileName: String, 28 | onGetJson: (isExportVars: Boolean) -> String, 29 | ) { 30 | var isExportVars by remember { mutableStateOf(false) } 31 | ConfigExportBottomSheet( 32 | fileName = fileName, 33 | json = onGetJson(isExportVars), 34 | onDismissRequest = onDismissRequest, 35 | content = { 36 | TextCheckBox(modifier = Modifier 37 | .align(Alignment.CenterHorizontally) 38 | .padding(vertical = 8.dp), 39 | text = { Text(stringResource(id = R.string.export_vars)) }, 40 | checked = isExportVars, 41 | onCheckedChange = { isExportVars = !isExportVars }) 42 | } 43 | ) 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/systts/plugin/PluginImportBottomSheet.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.systts.plugin 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.getValue 5 | import androidx.compose.runtime.mutableStateOf 6 | import androidx.compose.runtime.remember 7 | import androidx.compose.runtime.setValue 8 | import com.github.jing332.tts_server_android.compose.systts.ConfigImportBottomSheet 9 | import com.github.jing332.tts_server_android.compose.systts.ConfigModel 10 | import com.github.jing332.tts_server_android.compose.systts.SelectImportConfigDialog 11 | import com.github.jing332.tts_server_android.constant.AppConst 12 | import com.github.jing332.tts_server_android.data.appDb 13 | import com.github.jing332.tts_server_android.data.entities.plugin.Plugin 14 | 15 | @Composable 16 | fun PluginImportBottomSheet(onDismissRequest: () -> Unit) { 17 | var list by remember { mutableStateOf?>(null) } 18 | if (list != null) { 19 | SelectImportConfigDialog( 20 | onDismissRequest = { list = null }, 21 | models = list!!.map { 22 | ConfigModel( 23 | isSelected = true, 24 | title = it.name, 25 | subtitle = it.author, 26 | it 27 | ) 28 | }, 29 | onSelectedList = { 30 | appDb.pluginDao.insert(*it.map { plugin -> plugin as Plugin }.toTypedArray()) 31 | 32 | it.size 33 | } 34 | ) 35 | } 36 | 37 | ConfigImportBottomSheet(onDismissRequest = onDismissRequest, onImport = { 38 | list = AppConst.jsonBuilder.decodeFromString>(it) 39 | }) 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/systts/plugin/PluginManagerViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.systts.plugin 2 | 3 | import androidx.lifecycle.ViewModel 4 | 5 | class PluginManagerViewModel : ViewModel() { 6 | 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/systts/replace/NavRoutes.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.systts.replace 2 | 3 | internal sealed class NavRoutes(val id: String) { 4 | data object Manager : NavRoutes("manager") 5 | data object Edit : NavRoutes("edit") { 6 | const val KEY_DATA = "data" 7 | } 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/systts/replace/ReplaceRuleExportBottomSheet.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.systts.replace 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.remember 5 | import com.github.jing332.tts_server_android.compose.systts.ConfigExportBottomSheet 6 | import com.github.jing332.tts_server_android.constant.AppConst 7 | import com.github.jing332.tts_server_android.data.entities.replace.GroupWithReplaceRule 8 | import kotlinx.serialization.encodeToString 9 | 10 | @Composable 11 | fun ReplaceRuleExportBottomSheet(onDismissRequest: () -> Unit, list: List) { 12 | val json = remember(list) { 13 | AppConst.jsonBuilder.encodeToString(list) 14 | } 15 | 16 | ConfigExportBottomSheet(json = json, onDismissRequest = onDismissRequest, fileName = "ttsrv-replaces.json") 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/systts/replace/edit/RuleEditViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.systts.replace.edit 2 | 3 | import androidx.lifecycle.ViewModel 4 | import com.github.jing332.tts_server_android.data.entities.replace.ReplaceRule 5 | 6 | class RuleEditViewModel : ViewModel() { 7 | fun doReplace(rule: ReplaceRule, text: String): String { 8 | return if (rule.isRegex) 9 | Regex(rule.pattern).replace(text, rule.replacement) 10 | else 11 | text.replace(rule.pattern, rule.replacement) 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/systts/speechrule/NavRoutes.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.systts.speechrule 2 | 3 | internal sealed class NavRoutes(val id: String) { 4 | data object SpeechRuleManager : NavRoutes("manager") 5 | data object SpeechRuleEdit : NavRoutes("edit") { 6 | const val KEY_DATA = "data" 7 | } 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/systts/speechrule/SpeechRuleExportBottomSheet.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.systts.speechrule 2 | 3 | import androidx.compose.runtime.Composable 4 | import com.github.jing332.tts_server_android.compose.systts.ConfigExportBottomSheet 5 | import com.github.jing332.tts_server_android.constant.AppConst 6 | import com.github.jing332.tts_server_android.data.entities.SpeechRule 7 | import kotlinx.serialization.encodeToString 8 | 9 | @Composable 10 | fun SpeechRuleExportBottomSheet(onDismissRequest: () -> Unit, list: List) { 11 | ConfigExportBottomSheet( 12 | onDismissRequest = onDismissRequest, 13 | json = AppConst.jsonBuilder.encodeToString(list), 14 | fileName = if (list.size == 1) "ttsrv-speechRule-${list[0].name}.json" else "ttsrv-speechRules.json" 15 | ) 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/systts/speechrule/SpeechRuleImportBottomSheet.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.systts.speechrule 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.getValue 5 | import androidx.compose.runtime.mutableStateOf 6 | import androidx.compose.runtime.remember 7 | import androidx.compose.runtime.setValue 8 | import com.github.jing332.tts_server_android.compose.systts.ConfigImportBottomSheet 9 | import com.github.jing332.tts_server_android.compose.systts.ConfigModel 10 | import com.github.jing332.tts_server_android.compose.systts.SelectImportConfigDialog 11 | import com.github.jing332.tts_server_android.constant.AppConst 12 | import com.github.jing332.tts_server_android.data.appDb 13 | import com.github.jing332.tts_server_android.data.entities.SpeechRule 14 | 15 | @Composable 16 | fun SpeechRuleImportBottomSheet(onDismissRequest: () -> Unit) { 17 | var showSelectDialog by remember { mutableStateOf?>(null) } 18 | if (showSelectDialog != null) { 19 | val list = showSelectDialog!! 20 | SelectImportConfigDialog( 21 | onDismissRequest = { showSelectDialog = null }, 22 | models = list.map { ConfigModel(true, it.name, "${it.author} - v${it.version}", it) }, 23 | onSelectedList = { 24 | appDb.speechRuleDao.insert(*it.map { speechRule -> speechRule as SpeechRule }.toTypedArray()) 25 | 26 | it.size 27 | } 28 | ) 29 | } 30 | 31 | ConfigImportBottomSheet(onDismissRequest = onDismissRequest, onImport = { 32 | showSelectDialog = AppConst.jsonBuilder.decodeFromString>(it) 33 | }) 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/theme/AppTheme.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | import com.github.jing332.tts_server_android.R 5 | 6 | enum class AppTheme(val id: String, val stringResId: Int = -1, val color: Color) { 7 | DEFAULT("", R.string.theme_default, green_seed), 8 | DYNAMIC_COLOR("dynamicColor", R.string.dynamic_color, Color.Unspecified), 9 | GREEN("green", R.string.green, green_seed), 10 | RED("red", R.string.red, red_seed), 11 | PINK("pink", R.string.pink, pink_seed), 12 | BLUE("blue", R.string.blue, blue_seed), 13 | CYAN("cyan", R.string.cyan, cyan_seed), 14 | ORANGE("orange", R.string.orange, orange_seed), 15 | PURPLE("purple", R.string.purple, purple_seed), 16 | BROWN("brown", R.string.brown, brown_seed), 17 | GRAY("gray", R.string.gray, gray_seed), 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val Purple80 = Color(0xFFD0BCFF) 6 | val PurpleGrey80 = Color(0xFFCCC2DC) 7 | val Pink80 = Color(0xFFEFB8C8) 8 | 9 | val Purple40 = Color(0xFF6650a4) 10 | val PurpleGrey40 = Color(0xFF625b71) 11 | val Pink40 = Color(0xFF7D5260) -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.theme 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val Typography = Typography( 11 | bodyLarge = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp, 15 | lineHeight = 24.sp, 16 | letterSpacing = 0.5.sp 17 | ) 18 | /* Other default text styles to override 19 | titleLarge = TextStyle( 20 | fontFamily = FontFamily.Default, 21 | fontWeight = FontWeight.Normal, 22 | fontSize = 22.sp, 23 | lineHeight = 28.sp, 24 | letterSpacing = 0.sp 25 | ), 26 | labelSmall = TextStyle( 27 | fontFamily = FontFamily.Default, 28 | fontWeight = FontWeight.Medium, 29 | fontSize = 11.sp, 30 | lineHeight = 16.sp, 31 | letterSpacing = 0.5.sp 32 | ) 33 | */ 34 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/widgets/AppBottomSheet.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.widgets 2 | 3 | import androidx.compose.foundation.layout.fillMaxSize 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.LaunchedEffect 6 | import androidx.compose.runtime.rememberCoroutineScope 7 | import androidx.compose.ui.Modifier 8 | import com.dokar.sheets.BottomSheetValue 9 | import com.dokar.sheets.m3.BottomSheet 10 | import com.dokar.sheets.rememberBottomSheetState 11 | import kotlinx.coroutines.Dispatchers 12 | import kotlinx.coroutines.delay 13 | import kotlinx.coroutines.launch 14 | 15 | @Composable 16 | fun AppBottomSheet( 17 | value: BottomSheetValue = BottomSheetValue.Peeked, 18 | onDismissRequest: () -> Unit, 19 | content: @Composable () -> Unit 20 | ) { 21 | val scope = rememberCoroutineScope() 22 | val state = rememberBottomSheetState(confirmValueChange = { 23 | if (it == BottomSheetValue.Collapsed) { 24 | scope.launch(Dispatchers.Main) { 25 | delay(200) 26 | onDismissRequest() 27 | } 28 | } 29 | 30 | true 31 | }) 32 | LaunchedEffect(value) { 33 | when (value) { 34 | BottomSheetValue.Collapsed -> state.collapse() 35 | BottomSheetValue.Expanded -> state.expand() 36 | BottomSheetValue.Peeked -> state.peek() 37 | } 38 | } 39 | BottomSheet( 40 | modifier = Modifier.fillMaxSize(), 41 | state = state, 42 | content = content, 43 | ) 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/widgets/AppTooltip.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.widgets 2 | 3 | import androidx.compose.material3.ExperimentalMaterial3Api 4 | import androidx.compose.material3.PlainTooltip 5 | import androidx.compose.material3.Text 6 | import androidx.compose.material3.TooltipBox 7 | import androidx.compose.material3.TooltipDefaults 8 | import androidx.compose.material3.rememberTooltipState 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | 12 | @OptIn(ExperimentalMaterial3Api::class) 13 | @Composable 14 | fun AppTooltip( 15 | modifier: Modifier = Modifier, 16 | tooltip: String, 17 | content: @Composable (tooltip: String) -> Unit 18 | ) { 19 | val state = rememberTooltipState() 20 | TooltipBox( 21 | modifier = modifier, 22 | positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), 23 | tooltip = { 24 | PlainTooltip { 25 | Text(tooltip) 26 | } 27 | }, 28 | state = state, 29 | content = { content(tooltip) }, 30 | ) 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/widgets/CheckMenuItem.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.widgets 2 | 3 | import androidx.compose.foundation.interaction.MutableInteractionSource 4 | import androidx.compose.foundation.layout.PaddingValues 5 | import androidx.compose.material3.Checkbox 6 | import androidx.compose.material3.DropdownMenuItem 7 | import androidx.compose.material3.MenuDefaults 8 | import androidx.compose.material3.MenuItemColors 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.runtime.remember 11 | import androidx.compose.ui.Modifier 12 | 13 | @Composable 14 | fun CheckedMenuItem( 15 | modifier: Modifier = Modifier, 16 | text: @Composable () -> Unit, 17 | checked: Boolean, 18 | onClick: (Boolean) -> Unit, 19 | onClickCheckBox: (Boolean) -> Unit = onClick, 20 | leadingIcon: @Composable (() -> Unit)? = null, 21 | enabled: Boolean = true, 22 | colors: MenuItemColors = MenuDefaults.itemColors(), 23 | contentPadding: PaddingValues = MenuDefaults.DropdownMenuItemContentPadding, 24 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, 25 | ) { 26 | DropdownMenuItem( 27 | onClick = { onClick(!checked) }, 28 | modifier = modifier, 29 | enabled = enabled, 30 | contentPadding = contentPadding, 31 | interactionSource = interactionSource, 32 | colors = colors, 33 | text = text, 34 | leadingIcon = leadingIcon, 35 | trailingIcon = { 36 | Checkbox( 37 | checked = checked, 38 | onCheckedChange = { onClickCheckBox.invoke(it) }, 39 | enabled = enabled, 40 | ) 41 | } 42 | ) 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/widgets/EmptyTextToolbar.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.widgets 2 | 3 | import androidx.compose.ui.geometry.Rect 4 | import androidx.compose.ui.platform.TextToolbar 5 | import androidx.compose.ui.platform.TextToolbarStatus 6 | 7 | object EmptyTextToolbar : TextToolbar { 8 | override val status: TextToolbarStatus = TextToolbarStatus.Hidden 9 | 10 | override fun hide() {} 11 | 12 | override fun showMenu( 13 | rect: Rect, 14 | onCopyRequested: (() -> Unit)?, 15 | onPasteRequested: (() -> Unit)?, 16 | onCutRequested: (() -> Unit)?, 17 | onSelectAllRequested: (() -> Unit)?, 18 | ) { 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/widgets/LazyListIndexStateSaver.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.widgets 2 | 3 | import androidx.compose.foundation.lazy.LazyListState 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.DisposableEffect 6 | import androidx.compose.runtime.LaunchedEffect 7 | import androidx.compose.runtime.getValue 8 | import androidx.compose.runtime.mutableIntStateOf 9 | import androidx.compose.runtime.saveable.rememberSaveable 10 | import androidx.compose.runtime.setValue 11 | 12 | @Composable 13 | fun LazyListIndexStateSaver( 14 | models: Any?, 15 | listState: LazyListState, 16 | 17 | onIndexUpdate: suspend (Int, Int) -> Unit = { index, offset -> 18 | listState.scrollToItem(index, offset) 19 | }, 20 | ) { 21 | var index by rememberSaveable { mutableIntStateOf(0) } 22 | var offset by rememberSaveable { mutableIntStateOf(0) } 23 | 24 | LaunchedEffect(models) { 25 | if (models != null && listState.firstVisibleItemIndex <= 0 && listState.firstVisibleItemScrollOffset <= 0) { 26 | onIndexUpdate(index, offset) 27 | } 28 | } 29 | 30 | DisposableEffect(Unit) { 31 | onDispose { 32 | index = listState.firstVisibleItemIndex 33 | offset = listState.firstVisibleItemScrollOffset 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/widgets/TextCheckBox.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.widgets 2 | 3 | import androidx.compose.foundation.layout.Arrangement 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.foundation.layout.RowScope 6 | import androidx.compose.foundation.layout.height 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.material3.Checkbox 9 | import androidx.compose.material3.MaterialTheme 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Alignment 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.draw.clip 14 | import androidx.compose.ui.semantics.Role 15 | import androidx.compose.ui.semantics.semantics 16 | import androidx.compose.ui.unit.dp 17 | import com.github.jing332.tts_server_android.utils.clickableRipple 18 | 19 | @Composable 20 | fun TextCheckBox( 21 | modifier: Modifier = Modifier, 22 | text: @Composable RowScope.() -> Unit, 23 | checked: Boolean, 24 | onCheckedChange: (Boolean) -> Unit, 25 | 26 | horizontalArrangement: Arrangement.Horizontal = Arrangement.Center, 27 | verticalAlignment: Alignment.Vertical = Alignment.CenterVertically, 28 | ) { 29 | Row( 30 | modifier 31 | .height(48.dp) 32 | .clip(MaterialTheme.shapes.small) 33 | .clickableRipple(role = Role.Checkbox) { onCheckedChange(!checked) } 34 | , 35 | verticalAlignment = verticalAlignment, 36 | horizontalArrangement = horizontalArrangement, 37 | ) { 38 | Row(Modifier.padding(horizontal = 8.dp)) { 39 | Checkbox(checked = checked, onCheckedChange = null) 40 | text() 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/widgets/TextFieldDialog.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.widgets 2 | 3 | import androidx.compose.material3.AlertDialog 4 | import androidx.compose.material3.OutlinedTextField 5 | import androidx.compose.material3.Text 6 | import androidx.compose.material3.TextButton 7 | import androidx.compose.runtime.Composable 8 | 9 | @Composable 10 | fun TextFieldDialog( 11 | title: String, 12 | text: String, 13 | onTextChange: (String) -> Unit, 14 | onDismissRequest: () -> Unit, 15 | onConfirm: () -> Unit 16 | ) { 17 | AlertDialog(onDismissRequest = onDismissRequest, 18 | title = { 19 | Text(title) 20 | }, 21 | text = { 22 | OutlinedTextField(value = text, onValueChange = onTextChange) 23 | }, 24 | confirmButton = { 25 | TextButton(onClick = onConfirm) { 26 | Text("确定") 27 | } 28 | } 29 | ) 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/widgets/htmlcompose/CharacterStyle.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.widgets.htmlcompose 2 | 3 | import android.text.style.ForegroundColorSpan 4 | import android.text.style.StrikethroughSpan 5 | import android.text.style.UnderlineSpan 6 | import androidx.compose.ui.graphics.Color 7 | import androidx.compose.ui.text.SpanStyle 8 | import androidx.compose.ui.text.style.TextDecoration 9 | 10 | internal fun UnderlineSpan.spanStyle(): SpanStyle = 11 | SpanStyle(textDecoration = TextDecoration.Underline) 12 | 13 | internal fun ForegroundColorSpan.spanStyle(): SpanStyle = 14 | SpanStyle(color = Color(foregroundColor)) 15 | 16 | internal fun StrikethroughSpan.spanStyle(): SpanStyle = 17 | SpanStyle(textDecoration = TextDecoration.LineThrough) -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/compose/widgets/htmlcompose/MetricAffectingSpan.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.compose.widgets.htmlcompose 2 | 3 | import android.graphics.Typeface 4 | import android.text.style.* 5 | import androidx.compose.ui.text.SpanStyle 6 | import androidx.compose.ui.text.font.FontFamily 7 | import androidx.compose.ui.text.font.FontStyle 8 | import androidx.compose.ui.text.font.FontWeight 9 | import androidx.compose.ui.text.style.BaselineShift 10 | import androidx.compose.ui.unit.TextUnit 11 | import androidx.compose.ui.unit.sp 12 | import java.io.File 13 | 14 | private const val PATH_SYSTEM_FONTS_FILE = "/system/etc/fonts.xml" 15 | private const val PATH_SYSTEM_FONTS_DIR = "/system/fonts/" 16 | 17 | internal fun RelativeSizeSpan.spanStyle(fontSize: TextUnit): SpanStyle = 18 | SpanStyle(fontSize = (fontSize.value * sizeChange).sp) 19 | 20 | internal fun StyleSpan.spanStyle(): SpanStyle? = when (style) { 21 | Typeface.BOLD -> SpanStyle(fontWeight = FontWeight.Bold) 22 | Typeface.ITALIC -> SpanStyle(fontStyle = FontStyle.Italic) 23 | Typeface.BOLD_ITALIC -> SpanStyle( 24 | fontWeight = FontWeight.Bold, 25 | fontStyle = FontStyle.Italic, 26 | ) 27 | else -> null 28 | } 29 | 30 | internal fun SubscriptSpan.spanStyle(): SpanStyle = 31 | SpanStyle(baselineShift = BaselineShift.Subscript) 32 | 33 | internal fun SuperscriptSpan.spanStyle(): SpanStyle = 34 | SpanStyle(baselineShift = BaselineShift.Superscript) 35 | 36 | internal fun TypefaceSpan.spanStyle(): SpanStyle? { 37 | val xmlContent = File(PATH_SYSTEM_FONTS_FILE).readText() 38 | return if (xmlContent.contains("""""") 41 | val fontName = familyChunkXml.substringAfter("""""") 42 | .substringBefore("") 43 | SpanStyle(fontFamily = FontFamily(Typeface.createFromFile("$PATH_SYSTEM_FONTS_DIR$fontName"))) 44 | } else { 45 | null 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/conf/CodeEditorConfig.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.conf 2 | 3 | import com.funny.data_saver.core.DataSaverConverter.registerTypeConverters 4 | import com.funny.data_saver.core.DataSaverPreferences 5 | import com.funny.data_saver.core.mutableDataSaverStateOf 6 | import com.github.jing332.tts_server_android.app 7 | import com.github.jing332.tts_server_android.constant.CodeEditorTheme 8 | 9 | object CodeEditorConfig { 10 | private val pref = DataSaverPreferences(app.getSharedPreferences("code_editor", 0)) 11 | 12 | init { 13 | registerTypeConverters( 14 | save = { it.id }, 15 | restore = { value -> 16 | CodeEditorTheme.values().find { it.id == value } ?: CodeEditorTheme.AUTO 17 | } 18 | ) 19 | } 20 | 21 | val theme = mutableDataSaverStateOf(pref, "codeEditorTheme", CodeEditorTheme.AUTO) 22 | 23 | val isWordWrapEnabled = mutableDataSaverStateOf(pref, "isWordWrapEnabled", false) 24 | val isRemoteSyncEnabled = mutableDataSaverStateOf(pref, "isRemoteSyncEnabled", false) 25 | val remoteSyncPort = mutableDataSaverStateOf(pref, "remoteSyncPort", 4566) 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/conf/DirectUploadConfig.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.conf 2 | 3 | import com.funny.data_saver.core.DataSaverPreferences 4 | import com.funny.data_saver.core.mutableDataSaverStateOf 5 | import com.github.jing332.tts_server_android.app 6 | import com.github.jing332.tts_server_android.utils.FileUtils.readAllText 7 | 8 | object DirectUploadConfig { 9 | private val pref = DataSaverPreferences(app.getSharedPreferences("direct_link_upload", 0)) 10 | 11 | val code = mutableDataSaverStateOf( 12 | pref, 13 | key = "code", 14 | app.assets.open("defaultData/direct_link_upload.js").readAllText() 15 | ) 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/conf/MsForwarderConfig.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.conf 2 | 3 | import com.funny.data_saver.core.DataSaverPreferences 4 | import com.funny.data_saver.core.mutableDataSaverStateOf 5 | import com.github.jing332.tts_server_android.app 6 | 7 | object MsForwarderConfig { 8 | private val pref = DataSaverPreferences(app.getSharedPreferences("server", 0)) 9 | 10 | val port = mutableDataSaverStateOf( 11 | dataSaverInterface = pref, 12 | key = "port", 13 | initialValue = 1233 14 | ) 15 | 16 | val isWakeLockEnabled = mutableDataSaverStateOf( 17 | dataSaverInterface = pref, 18 | key = "isWakeLockEnabled", 19 | initialValue = false 20 | ) 21 | 22 | val token = mutableDataSaverStateOf( 23 | dataSaverInterface = pref, 24 | key = "token", 25 | initialValue = "", 26 | ) 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/conf/MsTtsForwarderConfig.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.conf 2 | 3 | import com.funny.data_saver.core.DataSaverPreferences 4 | import com.funny.data_saver.core.mutableDataSaverStateOf 5 | import com.github.jing332.tts_server_android.app 6 | 7 | object MsTtsForwarderConfig { 8 | private val pref = DataSaverPreferences(app.getSharedPreferences("server", 0)) 9 | 10 | val port = mutableDataSaverStateOf(pref, key = "port", 1233) 11 | val isWakeLockEnabled = mutableDataSaverStateOf(pref, key = "isWakeLockEnabled", false) 12 | val token = mutableDataSaverStateOf(pref, key = "token", "") 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/conf/PluginConfig.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.conf 2 | 3 | import com.funny.data_saver.core.DataSaverPreferences 4 | import com.funny.data_saver.core.mutableDataSaverStateOf 5 | import com.github.jing332.tts_server_android.app 6 | 7 | object PluginConfig { 8 | private val pref = DataSaverPreferences(app.getSharedPreferences("plugin", 0)) 9 | 10 | val textParam = mutableDataSaverStateOf(pref, key = "sampleText", "示例文本。 Sample text.") 11 | // val isSaveRhinoLog = mutableDataSaverStateOf(pref, key = "isSaveRhinoLog", false) 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/conf/ReplaceRuleConfig.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.conf 2 | 3 | import com.funny.data_saver.core.DataSaverConverter 4 | import com.funny.data_saver.core.DataSaverMutableState 5 | import com.funny.data_saver.core.DataSaverPreferences 6 | import com.funny.data_saver.core.mutableDataSaverStateOf 7 | import com.github.jing332.tts_server_android.app 8 | import com.github.jing332.tts_server_android.constant.AppConst 9 | import kotlinx.serialization.encodeToString 10 | 11 | object ReplaceRuleConfig { 12 | private val pref = DataSaverPreferences(app.getSharedPreferences("replace_rule", 0)) 13 | 14 | val defaultSymbols: LinkedHashMap = listOf( 15 | "(", 16 | ")", 17 | "[", 18 | "]", 19 | "|", 20 | "\\", 21 | "/", 22 | "{", 23 | "}", 24 | "^", 25 | "$", 26 | ".", 27 | "*", 28 | "+", 29 | "?" 30 | ).associateWith { it } as LinkedHashMap 31 | 32 | init { 33 | DataSaverConverter.registerTypeConverters( 34 | save = { AppConst.jsonBuilder.encodeToString(it) }, 35 | restore = { value -> 36 | try { 37 | AppConst.jsonBuilder.decodeFromString>(value) 38 | } catch (_: Exception) { 39 | defaultSymbols 40 | } 41 | } 42 | ) 43 | } 44 | 45 | val symbols: DataSaverMutableState> = mutableDataSaverStateOf( 46 | pref, key = "symbols", 47 | defaultSymbols 48 | ) 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/conf/SpeechRuleConfig.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.conf 2 | 3 | import com.funny.data_saver.core.DataSaverPreferences 4 | import com.funny.data_saver.core.mutableDataSaverStateOf 5 | import com.github.jing332.tts_server_android.app 6 | 7 | object SpeechRuleConfig { 8 | private val pref = DataSaverPreferences(app.getSharedPreferences("speech_rule", 0)) 9 | 10 | var textParam = mutableDataSaverStateOf(pref, key = "textParam", "这是一个Android系统TTS应用,内置微软演示接口,可自定义HTTP请求,可导入其他本地TTS引擎,以及根据中文双引号的简单旁白/对话识别朗读 ,还有自动重试,备用配置,文本替换等更多功能。") 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/conf/SysttsForwarderConfig.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.conf 2 | 3 | import com.funny.data_saver.core.DataSaverPreferences 4 | import com.funny.data_saver.core.mutableDataSaverStateOf 5 | import com.github.jing332.tts_server_android.app 6 | 7 | object SysttsForwarderConfig { 8 | private val pref = DataSaverPreferences(app.getSharedPreferences("systts_forwarder", 0)) 9 | 10 | val port = mutableDataSaverStateOf( 11 | dataSaverInterface = pref, 12 | key = "port", 13 | initialValue = 1221 14 | ) 15 | 16 | val isWakeLockEnabled = mutableDataSaverStateOf( 17 | dataSaverInterface = pref, 18 | key = "isWakeLockEnabled", 19 | initialValue = false 20 | ) 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/constant/AppPattern.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.constant 2 | 3 | object AppPattern { 4 | val notReadAloudRegex = Regex("^(\\s|\\p{C}|\\p{P}|\\p{Z}|\\p{S})+$") 5 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/constant/CodeEditorTheme.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.constant 2 | 3 | enum class CodeEditorTheme(val id: String) { 4 | AUTO(""), 5 | QUIET_LIGHT("quietlight"), 6 | SOLARIZED_DRAK("solarized_drak"), 7 | DARCULA("darcula"), 8 | ABYSS("abyss") 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/constant/ConfigType.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.constant 2 | 3 | import androidx.annotation.StringRes 4 | import com.github.jing332.tts_server_android.R 5 | 6 | enum class ConfigType(@StringRes val strId: Int) { 7 | UNKNOWN(R.string.unknown), 8 | LIST(R.string.config_list), 9 | PLUGIN(R.string.plugin), 10 | REPLACE_RULE(R.string.replace_rule), 11 | SPEECH_RULE(R.string.speech_rule); 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/constant/FilePickerMode.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.constant 2 | 3 | object FilePickerMode { 4 | const val PROMPT = 0 5 | const val SYSTEM = 1 6 | const val BUILTIN = 2 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/constant/KeyConst.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.constant 2 | 3 | object KeyConst { 4 | const val KEY_DATA = "data" 5 | const val KEY_POSITION = "position" 6 | 7 | const val KEY_BUNDLE = "bundle" 8 | const val KEY_LARGE_DATA_BINDER = "bigData" 9 | 10 | const val RESULT_ADD = 111 11 | const val RESULT_EDIT = 222 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/constant/LogLevel.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.constant 2 | 3 | import android.graphics.Color 4 | import androidx.annotation.IntDef 5 | 6 | @IntDef( 7 | LogLevel.PANIC, 8 | LogLevel.FATIL, 9 | LogLevel.ERROR, 10 | LogLevel.WARN, 11 | LogLevel.INFO, 12 | LogLevel.DEBUG, 13 | LogLevel.TRACE 14 | ) 15 | annotation class LogLevel { 16 | companion object { 17 | const val PANIC = 0 18 | const val FATIL = 1 19 | const val ERROR = 2 20 | const val WARN = 3 21 | const val INFO = 4 22 | const val DEBUG = 5 23 | const val TRACE = 6 24 | 25 | fun toColor(level: Int): Int { 26 | return when { 27 | level == WARN -> { 28 | Color.rgb(255, 215, 0) /* 金色 */ 29 | } 30 | 31 | level <= ERROR -> { 32 | Color.RED 33 | } 34 | 35 | else -> { 36 | Color.GRAY 37 | } 38 | } 39 | } 40 | 41 | fun toString(level: Int): String { 42 | when (level) { 43 | PANIC -> return "宕机" 44 | FATIL -> return "致命" 45 | ERROR -> return "错误" 46 | WARN -> return "警告" 47 | INFO -> return "信息" 48 | DEBUG -> return "调试" 49 | TRACE -> return "详细" 50 | } 51 | return level.toString() 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/constant/MsTtsApiType.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.constant 2 | 3 | import androidx.annotation.IntDef 4 | 5 | @IntDef( 6 | MsTtsApiType.EDGE, MsTtsApiType.AZURE, MsTtsApiType.CREATION, MsTtsApiType.EDGE_OKHTTP 7 | ) 8 | @Retention(AnnotationRetention.SOURCE) 9 | annotation class MsTtsApiType { 10 | companion object { 11 | const val EDGE: Int = 0 12 | const val AZURE = 1 13 | const val CREATION = 2 14 | const val EDGE_OKHTTP = 3 15 | 16 | fun toString(@MsTtsApiType apiType: Int): String { 17 | return when (apiType) { 18 | EDGE -> "Edge" 19 | EDGE_OKHTTP -> "Edge-OkHttp" 20 | AZURE -> "Azure" 21 | CREATION -> "Creation" 22 | else -> "" 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/constant/PreferKey.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.constant 2 | 3 | object PreferKey { 4 | const val isSplitEnabled = "isSplitEnabled" 5 | const val isMultiVoiceEnabled = "isMultiVoiceEnabled" 6 | const val isReplaceEnabled = "isReplaceEnabled" 7 | const val requestTimeout = "requestTimeout" 8 | const val minDialogueLength = "minDialogueLength" 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/constant/ReplaceExecution.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.constant 2 | 3 | /** 4 | * 替换规则执行时机 5 | */ 6 | object ReplaceExecution { 7 | const val BEFORE = 0 // 朗读规则前 8 | const val AFTER = 1 // 朗读规则后 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/constant/SpeechTarget.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.constant 2 | 3 | import androidx.annotation.IntDef 4 | import com.github.jing332.tts_server_android.App 5 | import com.github.jing332.tts_server_android.R 6 | import com.github.jing332.tts_server_android.app 7 | 8 | @IntDef( 9 | SpeechTarget.ALL, 10 | // SpeechTarget.ASIDE, 11 | // SpeechTarget.DIALOGUE, 12 | SpeechTarget.BGM, 13 | SpeechTarget.CUSTOM_TAG 14 | ) 15 | @Retention(AnnotationRetention.SOURCE) 16 | annotation class SpeechTarget { 17 | companion object { 18 | const val ALL = 0 19 | 20 | @Deprecated("已有自定TAG") 21 | const val ASIDE = 1 //旁白 22 | 23 | @Deprecated("已有自定TAG") 24 | const val DIALOGUE = 2 //对话 25 | 26 | const val BGM = 3 //背景音乐 27 | const val CUSTOM_TAG = 4 // 自定义Tag 28 | 29 | fun toText(@SpeechTarget target: Int): String { 30 | return when (target) { 31 | BGM -> { 32 | app.getString(R.string.bgm) 33 | } 34 | 35 | else -> "" 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/constant/SystemNotificationConst.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.constant 2 | 3 | object SystemNotificationConst { 4 | const val ID_FORWARDER_MS = 1 5 | const val ID_FORWARDER_SYS = 2 6 | const val ID_SYSTEM_TTS = 3 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/data/DataBaseMigration.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.data 2 | 3 | import androidx.room.migration.Migration 4 | import androidx.sqlite.db.SupportSQLiteDatabase 5 | 6 | object DataBaseMigration { 7 | val migrations: Array by lazy { 8 | arrayOf(migration_15_16) 9 | } 10 | 11 | private val migration_15_16 = object : Migration(15, 16) { 12 | override fun migrate(database: SupportSQLiteDatabase) { 13 | //language=RoomSql 14 | database.apply { 15 | execSQL("ALTER TABLE sysTts ADD COLUMN speechRule_tagRuleId TEXT NOT NULL DEFAULT ''") 16 | execSQL("ALTER TABLE sysTts ADD COLUMN speechRule_tag TEXT NOT NULL DEFAULT ''") 17 | // 移动到嵌套对象 18 | execSQL("ALTER TABLE sysTts RENAME readAloudTarget TO speechRule_target") 19 | execSQL("ALTER TABLE sysTts RENAME isStandby TO speechRule_isStandby") 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/data/dao/PluginDao.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.data.dao 2 | 3 | import androidx.room.* 4 | import com.github.jing332.tts_server_android.data.entities.SpeechRule 5 | import com.github.jing332.tts_server_android.data.entities.plugin.Plugin 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | @Dao 9 | interface PluginDao { 10 | @get:Query("SELECT * FROM plugin ORDER BY `order` ASC") 11 | val all: List 12 | 13 | @get:Query("SELECT * FROM plugin WHERE isEnabled = '1' ORDER BY `order` ASC") 14 | val allEnabled: List 15 | 16 | @Query("SELECT * FROM plugin ORDER BY `order` ASC") 17 | fun flowAll(): Flow> 18 | 19 | @get:Query("SELECT count(*) FROM plugin") 20 | val count: Int 21 | 22 | @Insert(onConflict = OnConflictStrategy.REPLACE) 23 | fun insert(vararg data: Plugin) 24 | 25 | @Delete 26 | fun delete(vararg data: Plugin) 27 | 28 | @Update 29 | fun update(vararg data: Plugin) 30 | 31 | @Query("SELECT * FROM plugin WHERE pluginId = :pluginId ") 32 | fun getByPluginId(pluginId: String): Plugin? 33 | 34 | fun insertOrUpdate(vararg args: Plugin) { 35 | for (v in args) { 36 | val old = getByPluginId(v.pluginId) 37 | if (old == null) { 38 | insert(v) 39 | continue 40 | } 41 | 42 | if (v.pluginId == old.pluginId && v.version > old.version) 43 | update(v.copy(id = old.id)) 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/data/dao/SpeechRuleDao.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.data.dao 2 | 3 | import androidx.room.* 4 | import com.github.jing332.tts_server_android.data.entities.SpeechRule 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | @Dao 8 | interface SpeechRuleDao { 9 | @get:Query("SELECT * FROM speech_rules ORDER BY `order` ASC") 10 | val all: List 11 | 12 | @get:Query("SELECT * FROM speech_rules WHERE isEnabled = '1'") 13 | val allEnabled: List 14 | 15 | @Query("SELECT * FROM speech_rules ORDER BY `order` ASC") 16 | fun flowAll(): Flow> 17 | 18 | @get:Query("SELECT count(*) FROM speech_rules") 19 | val count: Int 20 | 21 | @Insert(onConflict = OnConflictStrategy.REPLACE) 22 | fun insert(vararg data: SpeechRule) 23 | 24 | @Delete 25 | fun delete(vararg data: SpeechRule) 26 | 27 | @Update 28 | fun update(vararg data: SpeechRule) 29 | 30 | @Query("SELECT * FROM speech_rules WHERE ruleId = :ruleId AND isEnabled = :isEnabled LIMIT 1") 31 | fun getByRuleId(ruleId: String, isEnabled: Boolean = true): SpeechRule? 32 | 33 | // @Query("SELECT * FROM speech_rules WHERE ruleId = :ruleId") 34 | // fun getByRuleId(ruleId: String): SpeechRule? 35 | 36 | fun insertOrUpdate(vararg args: SpeechRule) { 37 | for (v in args) { 38 | val old = getByRuleId(v.ruleId) 39 | if (old == null) { 40 | insert(v) 41 | continue 42 | } 43 | 44 | if (v.ruleId == old.ruleId && v.version > old.version) 45 | update(v.copy(id = old.id)) 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/data/dao/SysTtsDao.kt: -------------------------------------------------------------------------------- 1 | /* 2 | package com.github.jing332.tts_server_android.data.dao 3 | 4 | import androidx.room.* 5 | import com.github.jing332.tts_server_android.constant.ReadAloudTarget 6 | import com.github.jing332.tts_server_android.data.entities.SysTts 7 | import kotlinx.coroutines.flow.Flow 8 | 9 | @Dao 10 | interface SysTtsDao { 11 | @get:Query("select * from SysTts ORDER BY readAloudTarget ASC") 12 | val all: List 13 | 14 | @Query("select * from SysTts ORDER BY readAloudTarget ASC") 15 | fun flowAll(): Flow> 16 | 17 | @get:Query("select count(*) from SysTts") 18 | val count: Int 19 | 20 | @Query("select * from SysTts where id = :id") 21 | fun get(id: Long): SysTts? 22 | 23 | @Query("select * from SysTts where readAloudTarget = :target and isEnabled = '1'") 24 | fun getByReadAloudTarget(target: Int = ReadAloudTarget.ALL): SysTts? 25 | 26 | @Query("select * from sysTts where readAloudTarget = :target and isEnabled = '1'") 27 | fun getAllByReadAloudTarget(target: Int = ReadAloudTarget.ALL): List? 28 | 29 | @Insert(onConflict = OnConflictStrategy.REPLACE) 30 | fun insert(vararg tts: SysTts) 31 | 32 | @Delete 33 | fun delete(vararg tts: SysTts) 34 | 35 | @Update 36 | fun update(vararg tts: SysTts) 37 | 38 | @Query("delete from SysTts where id < 0") 39 | fun deleteDefault() 40 | }*/ 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/data/entities/AbstractListGroup.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.data.entities 2 | 3 | interface AbstractListGroup { 4 | val id: Long 5 | var name: String 6 | var order: Int 7 | var isExpanded: Boolean 8 | 9 | companion object { 10 | const val DEFAULT_GROUP_ID = 1L 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/data/entities/MapConverters.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.data.entities 2 | 3 | import androidx.room.TypeConverter 4 | 5 | object MapConverters { 6 | @TypeConverter 7 | fun toMap(json: String): Map { 8 | return TypeConverterUtils.decodeFromString(json) ?: emptyMap() 9 | } 10 | 11 | @TypeConverter 12 | fun fromMap(tags: Map): String { 13 | return TypeConverterUtils.encodeToString(tags) ?: "" 14 | } 15 | 16 | @TypeConverter 17 | fun toNestMap(json: String): Map> { 18 | return TypeConverterUtils.decodeFromString(json) ?: emptyMap() 19 | } 20 | 21 | @TypeConverter 22 | fun fromNestMap(map: Map>): String { 23 | return TypeConverterUtils.encodeToString(map) ?: "" 24 | } 25 | 26 | @TypeConverter 27 | fun toMapList(json: String): Map>> { 28 | return TypeConverterUtils.decodeFromString(json) ?: emptyMap() 29 | } 30 | 31 | @TypeConverter 32 | fun fromMapList(tags: Map>>): String { 33 | return TypeConverterUtils.encodeToString(tags) ?: "" 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/data/entities/SpeechRule.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.data.entities 2 | 3 | import android.os.Parcelable 4 | import androidx.room.ColumnInfo 5 | import androidx.room.Entity 6 | import androidx.room.PrimaryKey 7 | import androidx.room.TypeConverter 8 | import androidx.room.TypeConverters 9 | import kotlinx.parcelize.Parcelize 10 | import kotlinx.parcelize.RawValue 11 | import kotlinx.serialization.Serializable 12 | import kotlinx.serialization.Transient 13 | import kotlinx.serialization.json.JsonElement 14 | 15 | @Entity(tableName = "speech_rules") 16 | @Parcelize 17 | @TypeConverters(SpeechRule.Converters::class, MapConverters::class) 18 | @Serializable 19 | data class SpeechRule( 20 | // @Transient 21 | @PrimaryKey(autoGenerate = true) 22 | val id: Long = System.currentTimeMillis(), 23 | 24 | var isEnabled: Boolean = false, 25 | 26 | var name: String = "", 27 | var version: Int = 0, 28 | var ruleId: String = "", 29 | var author: String = "", 30 | var code: String = "", 31 | 32 | @ColumnInfo(defaultValue = "") 33 | var tags: Map = mutableMapOf(), 34 | 35 | // 声明的tag的附加 36 | // 如:为tag为dialogue的声明role附加数据 37 | // {dialogue: {role: {label: '角色名', "hint": "仅支持前置搜索"}, } } 38 | @ColumnInfo(defaultValue = "") 39 | var tagsData: TagsDataMap = mutableMapOf(), 40 | 41 | // 索引 排序用 42 | @ColumnInfo(name = "order", defaultValue = "0") 43 | var order: Int = 0, 44 | ) : Parcelable { 45 | 46 | class Converters { 47 | @TypeConverter 48 | fun toListMap(json: String): TagsDataMap { 49 | return TypeConverterUtils.decodeFromString(json) ?: emptyMap() 50 | } 51 | 52 | @TypeConverter 53 | fun fromListMap(tags: TagsDataMap): String { 54 | return TypeConverterUtils.encodeToString(tags) ?: "" 55 | } 56 | } 57 | } 58 | 59 | // {dialogue: {role: {label: '角色名', "hint": "仅支持前置搜索"}, } } 60 | typealias TagsDataMap = Map>> -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/data/entities/TypeConverterUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.data.entities 2 | 3 | import kotlinx.serialization.ExperimentalSerializationApi 4 | import kotlinx.serialization.decodeFromString 5 | import kotlinx.serialization.encodeToString 6 | import kotlinx.serialization.json.Json 7 | 8 | internal object TypeConverterUtils { 9 | @OptIn(ExperimentalSerializationApi::class) 10 | val json by lazy { 11 | Json { 12 | ignoreUnknownKeys = true //忽略未知 13 | explicitNulls = false //忽略为null的字段 14 | isLenient = true //忽略不符合json规范的字段 15 | allowStructuredMapKeys = true 16 | } 17 | } 18 | 19 | inline fun decodeFromString(s: String?): T? { 20 | if (s == null) return null 21 | return try { 22 | json.decodeFromString(s.toString()) 23 | } catch (e: Exception) { 24 | e.printStackTrace() 25 | null 26 | } 27 | } 28 | 29 | inline fun encodeToString(value :T?): String? { 30 | if (value == null) return null 31 | return try { 32 | json.encodeToString(value) 33 | } catch (e: Exception) { 34 | e.printStackTrace() 35 | null 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/data/entities/plugin/Plugin.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.data.entities.plugin 2 | 3 | import android.os.Parcelable 4 | import androidx.room.ColumnInfo 5 | import androidx.room.Entity 6 | import androidx.room.PrimaryKey 7 | import androidx.room.TypeConverters 8 | import com.github.jing332.tts_server_android.data.entities.MapConverters 9 | import kotlinx.parcelize.Parcelize 10 | import kotlinx.serialization.Serializable 11 | import kotlinx.serialization.Transient 12 | 13 | @Serializable 14 | @Parcelize 15 | @Entity 16 | @TypeConverters(MapConverters::class) 17 | data class Plugin( 18 | @Transient 19 | @PrimaryKey(autoGenerate = true) 20 | val id: Long = 0, 21 | 22 | var isEnabled: Boolean = false, 23 | 24 | @ColumnInfo(defaultValue = "0") 25 | var version: Int = 0, 26 | var name: String = "", 27 | var pluginId: String = "", 28 | var author: String = "", 29 | var code: String = "", 30 | 31 | // authKey: { label: "验证KEY", hint: "填入用于验证身份的KEY"} 32 | @ColumnInfo(name = "defVars", defaultValue = "{}") 33 | var defVars: Map> = mutableMapOf(), 34 | 35 | 36 | @ColumnInfo(defaultValue = "{}") 37 | var userVars: Map = mutableMapOf(), 38 | 39 | // 索引 排序用 40 | @ColumnInfo(name = "order", defaultValue = "0") 41 | var order: Int = 0, 42 | ) : Parcelable { 43 | val mutableUserVars: MutableMap 44 | get() = userVars as MutableMap 45 | 46 | override fun toString(): String { 47 | return "name: $name, pluginId: $pluginId, author: $author, version: $version, isEnabled: $isEnabled" 48 | } 49 | 50 | override fun equals(other: Any?): Boolean { 51 | return super.equals(other) && other is Plugin && other.userVars == userVars 52 | } 53 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/data/entities/replace/GroupWithReplaceRule.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.data.entities.replace 2 | 3 | import androidx.room.Embedded 4 | import androidx.room.Relation 5 | 6 | @kotlinx.serialization.Serializable 7 | data class GroupWithReplaceRule( 8 | @Embedded 9 | val group: ReplaceRuleGroup, 10 | 11 | @Relation( 12 | parentColumn = "id", 13 | entityColumn = "groupId" 14 | ) 15 | val list: List 16 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/data/entities/replace/ReplaceRule.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.data.entities.replace 2 | 3 | import android.os.Parcelable 4 | import androidx.room.ColumnInfo 5 | import androidx.room.Entity 6 | import androidx.room.PrimaryKey 7 | import com.github.jing332.tts_server_android.data.entities.AbstractListGroup.Companion.DEFAULT_GROUP_ID 8 | import kotlinx.parcelize.Parcelize 9 | import kotlinx.serialization.Serializable 10 | 11 | @Parcelize 12 | @Serializable 13 | @Entity(tableName = "replaceRule") 14 | data class ReplaceRule( 15 | @PrimaryKey(autoGenerate = true) 16 | var id: Long = System.currentTimeMillis(), 17 | 18 | // 所属组的ID 19 | @ColumnInfo(defaultValue = DEFAULT_GROUP_ID.toString()) 20 | var groupId: Long = DEFAULT_GROUP_ID, 21 | 22 | // 显示名称 23 | var name: String = "", 24 | // 是否启用 25 | var isEnabled: Boolean = true, 26 | // 是否正则 27 | var isRegex: Boolean = false, 28 | // 匹配 29 | var pattern: String = "", 30 | // 替换为 31 | var replacement: String = "", 32 | // 索引 排序用 33 | @ColumnInfo(defaultValue = "0") 34 | var order: Int = 0, 35 | 36 | @ColumnInfo(defaultValue = "") 37 | var sampleText: String = "" 38 | ) : Parcelable -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/data/entities/replace/ReplaceRuleGroup.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.data.entities.replace 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | import com.github.jing332.tts_server_android.constant.ReplaceExecution 7 | import com.github.jing332.tts_server_android.data.entities.AbstractListGroup 8 | 9 | @Entity("replaceRuleGroup") 10 | @kotlinx.serialization.Serializable 11 | data class ReplaceRuleGroup( 12 | @PrimaryKey 13 | override val id: Long = System.currentTimeMillis(), 14 | override var name: String, 15 | override var order: Int = 0, 16 | 17 | @kotlinx.serialization.Transient 18 | override var isExpanded: Boolean = false, 19 | 20 | @ColumnInfo(defaultValue = ReplaceExecution.BEFORE.toString()) 21 | var onExecution: Int = ReplaceExecution.BEFORE, 22 | ) : AbstractListGroup -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/data/entities/systts/AudioParams.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.data.entities.systts 2 | 3 | import android.os.Parcelable 4 | import androidx.room.ColumnInfo 5 | import kotlinx.parcelize.Parcelize 6 | import kotlinx.serialization.Serializable 7 | 8 | @Parcelize 9 | @Serializable 10 | data class AudioParams( 11 | @ColumnInfo("speed", defaultValue = "$FOLLOW_GLOBAL_VALUE") 12 | var speed: Float = FOLLOW_GLOBAL_VALUE, 13 | 14 | @ColumnInfo("volume", defaultValue = "$FOLLOW_GLOBAL_VALUE") 15 | var volume: Float = FOLLOW_GLOBAL_VALUE, 16 | 17 | @ColumnInfo("pitch", defaultValue = "$FOLLOW_GLOBAL_VALUE") 18 | var pitch: Float = FOLLOW_GLOBAL_VALUE 19 | ) : Parcelable { 20 | companion object { 21 | const val FOLLOW_GLOBAL_VALUE = 0f 22 | } 23 | 24 | val isDefaultValue: Boolean 25 | get() = speed == 1f && volume == 1f && pitch == 1f 26 | 27 | fun copyIfFollow(followSpeed: Float, followVolume: Float, followPitch: Float): AudioParams { 28 | return AudioParams( 29 | if (speed == FOLLOW_GLOBAL_VALUE) followSpeed else speed, 30 | if (volume == FOLLOW_GLOBAL_VALUE) followVolume else volume, 31 | if (pitch == FOLLOW_GLOBAL_VALUE) followPitch else pitch 32 | ) 33 | } 34 | 35 | fun reset(v: Float) { 36 | speed = v 37 | volume = v 38 | pitch = v 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/data/entities/systts/CompatSystemTts.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.data.entities.systts 2 | 3 | import com.github.jing332.tts_server_android.constant.SpeechTarget 4 | import com.github.jing332.tts_server_android.model.speech.tts.ITextToSpeechEngine 5 | import kotlinx.serialization.Serializable 6 | 7 | 8 | @Serializable 9 | data class CompatSystemTts( 10 | @kotlinx.serialization.Transient 11 | val id: Long = 0, 12 | 13 | // 是否启用 14 | @kotlinx.serialization.Transient 15 | var isEnabled: Boolean = false, 16 | 17 | // UI显示名称 18 | var displayName: String = "", 19 | 20 | // 朗读目标 21 | @SpeechTarget var speechTarget: Int = SpeechTarget.ALL, 22 | 23 | // TTS属性 24 | var tts: ITextToSpeechEngine, 25 | ) { 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/data/entities/systts/GroupWithSystemTts.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.data.entities.systts 2 | 3 | import androidx.room.Embedded 4 | import androidx.room.Relation 5 | 6 | @kotlinx.serialization.Serializable 7 | data class GroupWithSystemTts( 8 | @Embedded 9 | val group: SystemTtsGroup, 10 | 11 | @Relation( 12 | parentColumn = "groupId", 13 | entityColumn = "groupId" 14 | ) 15 | val list: List 16 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/data/entities/systts/SystemTtsGroup.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.data.entities.systts 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | import androidx.room.ColumnInfo 6 | import androidx.room.Embedded 7 | import androidx.room.Entity 8 | import androidx.room.PrimaryKey 9 | import com.github.jing332.tts_server_android.data.entities.AbstractListGroup 10 | import kotlinx.parcelize.Parcelize 11 | import kotlinx.serialization.Serializable 12 | 13 | @Serializable 14 | @Parcelize 15 | @Entity 16 | data class SystemTtsGroup( 17 | @ColumnInfo("groupId") 18 | @PrimaryKey 19 | override val id: Long = System.currentTimeMillis(), 20 | override var name: String, 21 | @ColumnInfo(defaultValue = "0") 22 | override var order: Int = 0, 23 | override var isExpanded: Boolean = false, 24 | 25 | @Embedded(prefix = "audioParams_") 26 | var audioParams: AudioParams = AudioParams() 27 | ) : AbstractListGroup, Parcelable -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/help/ByteArrayBinder.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.help 2 | 3 | import android.os.Binder 4 | 5 | // Binder 用于传递大数据 6 | class ByteArrayBinder(val data: ByteArray) : Binder() -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/help/ConfigImportHelper.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.help 2 | 3 | import com.github.jing332.tts_server_android.constant.ConfigType 4 | 5 | object ConfigImportHelper { 6 | private val fieldMap = mapOf( 7 | listOf("pluginId", "code", "version") to ConfigType.PLUGIN, 8 | "pattern" to ConfigType.REPLACE_RULE, 9 | listOf("ruleId", "tags") to ConfigType.SPEECH_RULE, 10 | listOf("list", "tts", "displayName", "#type") to ConfigType.LIST, 11 | ) 12 | 13 | /** 14 | * 检查配置字符串是否符合配置格式 15 | */ 16 | fun getConfigType(str: String): ConfigType { 17 | fieldMap.forEach { entry -> 18 | when (entry.key) { 19 | is String -> if (str.contains("\"${entry.key}\":")) { 20 | return entry.value 21 | } 22 | 23 | is List<*> -> if (str.contains((entry.key as List<*>).map { "\"${it.toString()}\":" })) { 24 | return entry.value 25 | } 26 | } 27 | } 28 | 29 | return ConfigType.UNKNOWN 30 | } 31 | 32 | private fun String.contains(list: List): Boolean { 33 | var count = 0 34 | list.forEach { 35 | if (this.contains(it)) count++ 36 | } 37 | return count == list.size 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/help/LocalTtsEngineHelper.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.help 2 | 3 | import android.content.Context 4 | import android.speech.tts.TextToSpeech 5 | import android.speech.tts.Voice 6 | import kotlinx.coroutines.delay 7 | import java.util.* 8 | 9 | class LocalTtsEngineHelper(val context: Context) { 10 | companion object { 11 | private const val INIT_STATUS_WAITING = -2 12 | } 13 | 14 | private var tts: TextToSpeech? = null 15 | 16 | private var engineName: String = "" 17 | 18 | /** 19 | * return 是否 初始化成功 20 | */ 21 | suspend fun setEngine(name: String): Boolean { 22 | if (engineName != name) { 23 | engineName = name 24 | shutdown() 25 | 26 | var status = INIT_STATUS_WAITING 27 | tts = TextToSpeech(context, { status = it }, name) 28 | 29 | for (i in 1..50) { // 5s 30 | if (status == TextToSpeech.SUCCESS) break 31 | else if (i == 50) return false 32 | delay(100) 33 | } 34 | 35 | } 36 | return true 37 | } 38 | 39 | fun shutdown() { 40 | tts?.shutdown() 41 | } 42 | 43 | 44 | val voices: List 45 | get() = try { 46 | tts!!.voices?.toList()!! 47 | } catch (e: NullPointerException) { 48 | emptyList() 49 | } 50 | 51 | val locales: List 52 | get() = try { 53 | tts!!.availableLanguages.toList().sortedBy { it.toString() } 54 | } catch (e: NullPointerException) { 55 | emptyList() 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/help/audio/AudioDecoderException.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.help.audio 2 | 3 | class AudioDecoderException( 4 | val errCode: Int = ERROR_CODE_DECODER, override val message: String? = null, 5 | override val cause: Throwable? = null 6 | ) : Exception() { 7 | companion object { 8 | const val ERROR_CODE_DECODER = 0 9 | const val ERROR_CODE_NO_AUDIO_TRACK = 1 10 | const val ERROR_CODE_NOT_SUPPORT_A5 = 2 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/help/audio/AudioPlayer.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.help.audio 2 | 3 | import android.content.Context 4 | import java.io.InputStream 5 | 6 | class AudioPlayer(context: Context) { 7 | private val exoAudioPlayer = ExoAudioPlayer(context) 8 | private val pcmAudioPlayer = PcmAudioPlayer() 9 | 10 | suspend fun play(inputStream: InputStream, sampleRate: Int) { 11 | pcmAudioPlayer.play(inputStream, sampleRate) 12 | } 13 | 14 | fun play(bytes: ByteArray, sampleRate: Int) { 15 | pcmAudioPlayer.play(bytes, sampleRate) 16 | } 17 | 18 | suspend fun play(inputStream: InputStream) { 19 | exoAudioPlayer.play(inputStream) 20 | } 21 | 22 | suspend fun play(bytes: ByteArray) { 23 | exoAudioPlayer.play(bytes) 24 | } 25 | 26 | fun stop() { 27 | exoAudioPlayer.stop() 28 | pcmAudioPlayer.stop() 29 | } 30 | 31 | fun release() { 32 | exoAudioPlayer.release() 33 | pcmAudioPlayer.release() 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/help/audio/ByteArrayMediaDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.help.audio 2 | 3 | import android.media.MediaDataSource 4 | import android.os.Build 5 | import androidx.annotation.RequiresApi 6 | 7 | @RequiresApi(api = Build.VERSION_CODES.M) 8 | class ByteArrayMediaDataSource(var data: ByteArray) : MediaDataSource() { 9 | override fun readAt(position: Long, buffer: ByteArray, offset: Int, size: Int): Int { 10 | if (position >= data.size) return -1 11 | 12 | val endPosition = (position + size).toInt() 13 | val size2 = if (endPosition > data.size) size - (endPosition - data.size) else size 14 | 15 | System.arraycopy(data, position.toInt(), buffer, offset, size2) 16 | return size2 17 | } 18 | 19 | override fun getSize(): Long { 20 | return data.size.toLong() 21 | } 22 | 23 | override fun close() { 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/help/audio/ExoPlayerHelper.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.help.audio 2 | 3 | import android.annotation.SuppressLint 4 | import androidx.media3.common.MediaItem 5 | import androidx.media3.common.util.UnstableApi 6 | import androidx.media3.datasource.ByteArrayDataSource 7 | import androidx.media3.datasource.DataSource 8 | import androidx.media3.exoplayer.source.DefaultMediaSourceFactory 9 | import androidx.media3.exoplayer.source.MediaSource 10 | import com.github.jing332.tts_server_android.App 11 | import com.github.jing332.tts_server_android.help.audio.exo.InputStreamDataSource 12 | import java.io.InputStream 13 | 14 | 15 | object ExoPlayerHelper { 16 | @SuppressLint("UnsafeOptInUsageError") 17 | fun createMediaSourceFromInputStream(inputStream: InputStream): MediaSource { 18 | val factory = DataSource.Factory { 19 | InputStreamDataSource(inputStream) 20 | } 21 | return DefaultMediaSourceFactory(App.context).setDataSourceFactory(factory) 22 | .createMediaSource(MediaItem.fromUri("")) 23 | } 24 | 25 | // 创建音频媒体源 26 | @SuppressLint("UnsafeOptInUsageError") 27 | fun createMediaSourceFromByteArray(data: ByteArray): MediaSource { 28 | val factory = DataSource.Factory { ByteArrayDataSource(data) } 29 | return DefaultMediaSourceFactory(App.context).setDataSourceFactory(factory) 30 | .createMediaSource(MediaItem.fromUri("")) 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/help/audio/InputStreamMediaDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.help.audio 2 | 3 | import android.media.MediaDataSource 4 | import android.os.Build 5 | import android.util.Log 6 | import androidx.annotation.RequiresApi 7 | import okio.buffer 8 | import okio.source 9 | import java.io.InputStream 10 | 11 | @RequiresApi(Build.VERSION_CODES.M) 12 | class InputStreamMediaDataSource(private val inputStream: InputStream) : MediaDataSource() { 13 | companion object { 14 | const val TAG = "InputStreamDataSource" 15 | } 16 | 17 | private val bufferedInputStream = inputStream.source().buffer() 18 | 19 | override fun close() { 20 | Log.d(TAG, "close") 21 | bufferedInputStream.close() 22 | } 23 | 24 | override fun readAt(position: Long, buffer: ByteArray, offset: Int, size: Int): Int { 25 | Log.d(TAG, "readAt: pos=$position, offset=$offset, size=$size") 26 | kotlin.runCatching { 27 | return bufferedInputStream.read(buffer, offset, size).apply { 28 | Log.d(TAG, "readAt: readLen=$this") 29 | } 30 | }.onFailure { 31 | Log.d(TAG, it.stackTraceToString()) 32 | } 33 | return -1 34 | } 35 | 36 | override fun getSize(): Long { 37 | return inputStream.available().toLong().apply { 38 | Log.d(TAG, "getSize: $this") 39 | } 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/model/hanlp/HanlpManager.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.model.hanlp 2 | 3 | import android.util.Log 4 | import com.hankcs.hanlp.HanLP 5 | import java.io.File 6 | 7 | object HanlpManager { 8 | const val TAG = "HanlpManager" 9 | 10 | fun test(): Boolean { 11 | kotlin.runCatching { 12 | HanLP.newSegment().seg("test, 测试") 13 | return true 14 | } 15 | 16 | return false 17 | } 18 | 19 | fun initDir(dir: String) { 20 | val cfgClz = HanLP.Config::class.java 21 | for (field in cfgClz.declaredFields) { 22 | if (field.type == String::class.java) { 23 | field.isAccessible = true 24 | val value = field.get(null) as String 25 | val newValue = dir + File.separator + value.removePrefix("data/") 26 | field.set(null, newValue) 27 | Log.d(TAG, "set config: $newValue") 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/model/rhino/ExceptionExt.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.model.rhino 2 | 3 | import com.github.jing332.tts_server_android.utils.rootCause 4 | import com.script.ScriptException 5 | 6 | object ExceptionExt { 7 | fun ScriptException.lineMessage(): String { 8 | return "第 $lineNumber 行错误:${rootCause?.message ?: this}" 9 | } 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/model/rhino/core/BaseScriptEngine.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.model.rhino.core 2 | 3 | import com.script.javascript.RhinoScriptEngine 4 | import org.mozilla.javascript.NativeObject 5 | 6 | open class BaseScriptEngine( 7 | open val rhino: RhinoScriptEngine = RhinoScriptEngine(), 8 | open val ttsrvObject: BaseScriptEngineContext, 9 | open var code: String = "", 10 | open val logger: Logger = Logger.global, 11 | ) { 12 | companion object { 13 | const val OBJ_TTSRV = "ttsrv" 14 | const val OBJ_LOGGER = "logger" 15 | } 16 | 17 | open fun findObject(name: String): NativeObject { 18 | return rhino.get(name).run { 19 | if (this == null) throw Exception("Not found object: $name") 20 | else this as NativeObject 21 | } 22 | } 23 | 24 | fun putDefaultObjects() { 25 | rhino.put(OBJ_TTSRV, ttsrvObject) 26 | rhino.put(OBJ_LOGGER, logger) 27 | } 28 | 29 | @Synchronized 30 | open fun eval( 31 | prefixCode: String = "" 32 | ): Any? { 33 | putDefaultObjects() 34 | 35 | return rhino.eval("${prefixCode.removePrefix(";").removeSuffix(";")};$code") 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/model/rhino/core/BaseScriptEngineContext.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.model.rhino.core 2 | 3 | import android.content.Context 4 | import com.github.jing332.tts_server_android.model.rhino.core.ext.JsExtensions 5 | 6 | /** 7 | * ttsrv 对象类 8 | */ 9 | open class BaseScriptEngineContext( 10 | override val context: Context, override val engineId: String, 11 | 12 | /*val globalData: Map = BaseScriptEngineContext.globalDataSet.run { 13 | if (!this.containsKey(engineId)) 14 | this[engineId] = mutableMapOf() 15 | 16 | this[engineId]!! 17 | }*/ 18 | ) : 19 | JsExtensions(context, engineId) { 20 | /*companion object { 21 | private val globalDataSet = mutableMapOf>() 22 | }*/ 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/model/rhino/core/ext/JsUserInterface.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.model.rhino.core.ext 2 | 3 | import android.view.View 4 | import android.view.ViewGroup 5 | import com.github.jing332.tts_server_android.app 6 | import com.github.jing332.tts_server_android.utils.dp 7 | import com.github.jing332.tts_server_android.utils.longToast 8 | import com.github.jing332.tts_server_android.utils.toast 9 | 10 | interface JsUserInterface { 11 | fun toast(msg: String) = app.toast(msg) 12 | fun longToast(msg: String) = app.longToast(msg) 13 | 14 | fun setMargins(v: View, left: Int, top: Int, right: Int, bottom: Int) { 15 | (v.layoutParams as ViewGroup.MarginLayoutParams).setMargins( 16 | left.dp, 17 | top.dp, 18 | right.dp, 19 | bottom.dp 20 | ) 21 | } 22 | // 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/model/rhino/core/type/JClass.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.model.rhino.core.type 2 | 3 | abstract class JClass { 4 | var onThrowable: ((t: Throwable) -> Unit)? = null 5 | 6 | fun tryBlock(block: () -> Unit) { 7 | kotlin.runCatching { 8 | block.invoke() 9 | }.onFailure { 10 | it.printStackTrace() 11 | onThrowable?.invoke(it) 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/model/rhino/core/type/ui/Item.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.model.rhino.core.type.ui 2 | 3 | data class Item(val name: CharSequence, val value: Any) -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/model/rhino/core/type/ws/internal/IWebSocketEvent.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.model.rhino.core.type.ws.internal 2 | 3 | import okhttp3.Response 4 | import okio.ByteString 5 | 6 | interface IWebSocketEvent { 7 | fun onOpen(response: Response) {} 8 | fun onMessage(text: String) 9 | fun onMessage(bytes: ByteString) 10 | fun onClosed(code: Int, reason: String) 11 | fun onClosing(code: Int, reason: String) 12 | fun onFailure(t: Throwable) 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/model/rhino/core/type/ws/internal/WebSocketException.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.model.rhino.core.type.ws.internal 2 | 3 | import okhttp3.Response 4 | 5 | data class WebSocketException(val response: Response? = null) : Exception() -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/model/rhino/direct_link_upload/DirectUploadEngine.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.model.rhino.direct_link_upload 2 | 3 | import android.content.Context 4 | import com.github.jing332.tts_server_android.conf.DirectUploadConfig 5 | import com.github.jing332.tts_server_android.model.rhino.core.BaseScriptEngine 6 | import com.github.jing332.tts_server_android.model.rhino.core.BaseScriptEngineContext 7 | import com.github.jing332.tts_server_android.model.rhino.core.Logger 8 | import com.script.javascript.RhinoScriptEngine 9 | import org.mozilla.javascript.NativeObject 10 | 11 | class DirectUploadEngine( 12 | override val rhino: RhinoScriptEngine = RhinoScriptEngine(), 13 | private val context: Context, 14 | override val logger: Logger = Logger(), 15 | override var code: String = DirectUploadConfig.code.value, 16 | ) : BaseScriptEngine(rhino, BaseScriptEngineContext(context, "DirectUpload"), code, logger) { 17 | companion object { 18 | private const val TAG = "DirectUploadEngine" 19 | const val OBJ_DIRECT_UPLOAD = "DirectUploadJS" 20 | } 21 | 22 | private val jsObject: NativeObject 23 | get() = findObject(OBJ_DIRECT_UPLOAD) 24 | 25 | /** 26 | * 获取所有方法 27 | */ 28 | fun obtainFunctionList(): List { 29 | eval() 30 | return jsObject.map { 31 | DirectUploadFunction(rhino, jsObject, it.key as String) 32 | } 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/model/rhino/direct_link_upload/DirectUploadFunction.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.model.rhino.direct_link_upload 2 | 3 | import com.script.javascript.RhinoScriptEngine 4 | import org.mozilla.javascript.NativeObject 5 | 6 | data class DirectUploadFunction( 7 | val scriptEngine: RhinoScriptEngine, 8 | val thisObj: NativeObject, 9 | val funcName: String, 10 | ) { 11 | fun invoke(config: String): String? { 12 | return scriptEngine.invokeMethod(thisObj, funcName, config) 13 | ?.run { this as String } 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/model/rhino/speech_rule/ScriptEngineContext.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.model.rhino.speech_rule 2 | 3 | import android.content.Context 4 | import com.github.jing332.tts_server_android.model.rhino.core.BaseScriptEngineContext 5 | import com.hankcs.hanlp.HanLP 6 | import com.hankcs.hanlp.seg.Segment 7 | 8 | class ScriptEngineContext( 9 | override val context: Context, override val engineId: String 10 | ) : BaseScriptEngineContext(context, engineId) { 11 | 12 | /** 13 | * 创建一个分词器, 这是一个工厂方法 14 | * Params: 15 | * algorithm – 分词算法,传入算法的中英文名都可以,可选列表: 16 | * 维特比 (viterbi):效率和效果的最佳平衡 17 | * 双数组trie树 (dat):极速词典分词,千万字符每秒 18 | * 条件随机场 (crf):分词、词性标注与命名实体识别精度都较高,适合要求较高的NLP任务 19 | * 感知机 (perceptron):分词、词性标注与命名实体识别,支持在线学习 20 | * N最短路 (nshort):命名实体识别稍微好一些,牺牲了速度 21 | * Returns: 22 | * 一个分词器 23 | */ 24 | @JvmOverloads 25 | fun newSegment(algorithm: String = "viterbi"): Segment = HanLP.newSegment(algorithm)!! 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/model/rhino/tts/EngineContext.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.model.rhino.tts 2 | 3 | import android.content.Context 4 | import androidx.annotation.Keep 5 | import com.github.jing332.tts_server_android.model.rhino.core.BaseScriptEngineContext 6 | import com.github.jing332.tts_server_android.model.speech.tts.PluginTTS 7 | 8 | /** 9 | * @param tts 在JS中用 `ttsrv.tts` 访问 10 | */ 11 | @Keep 12 | data class EngineContext( 13 | var tts: PluginTTS, 14 | val userVars: Map = mutableMapOf(), 15 | override val context: Context, 16 | override val engineId: String 17 | ) : 18 | BaseScriptEngineContext(context, engineId) { 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/model/speech/SynthesizerException.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.model.speech 2 | 3 | class SynthesizerException : Exception() { 4 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/model/speech/TtsTextSegment.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.model.speech 2 | 3 | import com.github.jing332.tts_server_android.model.speech.tts.ITextToSpeechEngine 4 | 5 | data class TtsTextSegment(val tts: ITextToSpeechEngine, val text: String) -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/model/speech/tts/BaseAudioFormat.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.model.speech.tts 2 | 3 | import android.media.AudioFormat 4 | import android.os.Parcelable 5 | import com.github.jing332.tts_server_android.R 6 | import com.github.jing332.tts_server_android.app 7 | import kotlinx.parcelize.Parcelize 8 | import kotlinx.serialization.Serializable 9 | 10 | @Parcelize 11 | @Serializable 12 | open class BaseAudioFormat( 13 | var sampleRate: Int = 16000, 14 | var bitRate: Int = AudioFormat.ENCODING_PCM_16BIT, 15 | var isNeedDecode: Boolean = true 16 | ) : Parcelable { 17 | override fun toString(): String { 18 | val str = if (isNeedDecode) " | " + app.getString(R.string.decode) else "" 19 | return "${sampleRate}hz" + str 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/model/speech/tts/BgmTTS.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.model.speech.tts 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import com.github.jing332.tts_server_android.R 6 | import com.github.jing332.tts_server_android.data.entities.systts.AudioParams 7 | import com.github.jing332.tts_server_android.data.entities.systts.SpeechRuleInfo 8 | import com.github.jing332.tts_server_android.utils.toHtmlBold 9 | import kotlinx.parcelize.IgnoredOnParcel 10 | import kotlinx.parcelize.Parcelize 11 | import kotlinx.serialization.SerialName 12 | import kotlinx.serialization.Serializable 13 | import kotlinx.serialization.Transient 14 | import java.io.InputStream 15 | 16 | @Parcelize 17 | @Serializable 18 | @SerialName("bgm") 19 | data class BgmTTS( 20 | var musicList: MutableSet = mutableSetOf(), 21 | 22 | override var pitch: Int = 0, 23 | override var volume: Int = 0, 24 | override var rate: Int = 0, 25 | override var audioFormat: BaseAudioFormat = BaseAudioFormat(), 26 | override var audioPlayer: PlayerParams = PlayerParams(), 27 | 28 | @Transient 29 | @IgnoredOnParcel 30 | override var audioParams: AudioParams = AudioParams(), 31 | @Transient 32 | override var speechRule: SpeechRuleInfo = SpeechRuleInfo(), 33 | 34 | override var locale: String = "" 35 | ) : ITextToSpeechEngine() { 36 | override fun getType() = "BGM" 37 | 38 | override fun getDescription(): String { 39 | val volStr = if (volume == 0) context.getString(R.string.follow) else volume.toString() 40 | return context.getString(R.string.systts_bgm_description, volStr.toHtmlBold()) 41 | } 42 | 43 | override fun getBottomContent(): String = 44 | context.getString(R.string.total_n_folders, musicList.size.toString()) 45 | 46 | override suspend fun getAudio(speakText: String, rate: Int, pitch: Int): InputStream? { 47 | throw Exception("请在编辑界面中点击音乐路径进行测试播放") 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/model/speech/tts/LocalTtsParameter.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.model.speech.tts 2 | 3 | import android.os.Bundle 4 | import android.os.Parcelable 5 | import kotlinx.parcelize.Parcelize 6 | import kotlinx.serialization.Serializable 7 | 8 | @Parcelize 9 | @Serializable 10 | data class LocalTtsParameter(var type: String, var key: String, var value: String) : Parcelable { 11 | companion object { 12 | val typeList = listOf( 13 | "Boolean", 14 | "Int", 15 | "Float", 16 | "String" 17 | ) 18 | } 19 | 20 | fun putValueFromBundle(b: Bundle) { 21 | when (type) { 22 | "Boolean" -> b.putBoolean(key, value.toBoolean()) 23 | "Int" -> b.putInt(key, value.toInt()) 24 | "Float" -> b.putFloat(key, value.toFloat()) 25 | else -> b.putString(key, value) 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/model/speech/tts/MsTtsAudioFormat.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.model.speech.tts 2 | 3 | import androidx.annotation.IntDef 4 | import com.github.jing332.tts_server_android.constant.MsTtsApiType 5 | 6 | data class MsTtsAudioFormat( 7 | val name: String, 8 | val value: String, 9 | @SupportedApi val supportedApi: Int = 0, 10 | ) : BaseAudioFormat() { 11 | companion object { 12 | const val DEFAULT = "audio-24khz-48kbitrate-mono-mp3" 13 | } 14 | 15 | constructor( 16 | value: String, 17 | sampleRate: Int, 18 | bitRate: Int, 19 | @SupportedApi supportedApi: Int, 20 | isNeedDecode: Boolean = true 21 | ) : this(value, value, supportedApi) { 22 | this.bitRate = bitRate 23 | this.sampleRate = sampleRate 24 | this.isNeedDecode = isNeedDecode 25 | } 26 | 27 | override fun toString(): String { 28 | return value 29 | } 30 | 31 | @IntDef(flag = true, value = [SupportedApi.AZURE, SupportedApi.EDGE, SupportedApi.CREATION]) 32 | @Retention(AnnotationRetention.SOURCE) 33 | annotation class SupportedApi { 34 | companion object { 35 | const val EDGE: Int = 1 36 | const val AZURE = 1 shl 1 37 | const val CREATION = 1 shl 2 38 | 39 | fun fromApiType(@MsTtsApiType api: Int): Int { 40 | return when (api) { 41 | MsTtsApiType.EDGE -> EDGE 42 | MsTtsApiType.AZURE -> AZURE 43 | else -> CREATION 44 | } 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/model/speech/tts/PlayerParams.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.model.speech.tts 2 | 3 | import android.os.Parcelable 4 | import kotlinx.parcelize.Parcelize 5 | import kotlinx.serialization.Serializable 6 | 7 | @Serializable 8 | @Parcelize 9 | data class PlayerParams( 10 | var rate: Float = VALUE_FOLLOW_GLOBAL, 11 | var pitch: Float = VALUE_FOLLOW_GLOBAL, 12 | var volume: Float = VALUE_FOLLOW_GLOBAL, 13 | ) : Parcelable { 14 | companion object { 15 | const val VALUE_FOLLOW_GLOBAL = 0f 16 | } 17 | 18 | fun setParamsIfFollow(gRate: Float, gVolume: Float, gPitch: Float): PlayerParams { 19 | if (this.rate == VALUE_FOLLOW_GLOBAL) this.rate = gRate 20 | if (this.volume == VALUE_FOLLOW_GLOBAL) this.volume = gVolume 21 | if (this.pitch == VALUE_FOLLOW_GLOBAL) this.pitch = gPitch 22 | 23 | return this 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/model/speech/tts/TtsInfo.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.model.speech.tts 2 | 3 | import android.os.Parcelable 4 | import com.github.jing332.tts_server_android.constant.SpeechTarget 5 | import kotlinx.parcelize.Parcelize 6 | import kotlinx.serialization.Serializable 7 | 8 | @Serializable 9 | @Parcelize 10 | data class TtsInfo( 11 | @SpeechTarget var target: Int = SpeechTarget.ALL, 12 | var standbyTts: ITextToSpeechEngine? = null, 13 | 14 | var tag: String = "" 15 | ) : 16 | Parcelable { 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/model/updater/UpdateResult.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.model.updater 2 | 3 | data class UpdateResult( 4 | val version: String = "", 5 | val time: String = "", 6 | val content: String = "", 7 | val downloadUrl: String = "", 8 | val size: Long = 0, 9 | ) { 10 | fun hasUpdate() = version.isNotBlank() && downloadUrl.isNotBlank() 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/service/forwarder/ForwarderServiceManager.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.service.forwarder 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import com.github.jing332.tts_server_android.service.forwarder.ms.MsTtsForwarderService 6 | import com.github.jing332.tts_server_android.service.forwarder.system.SysTtsForwarderService 7 | 8 | object ForwarderServiceManager { 9 | fun Context.switchMsTtsForwarder() { 10 | if (MsTtsForwarderService.isRunning) { 11 | closeMsTtsForwarder() 12 | } else { 13 | startMsTtsForwarder() 14 | } 15 | } 16 | 17 | fun Context.switchSysTtsForwarder() { 18 | if (SysTtsForwarderService.isRunning) { 19 | closeSysTtsForwarder() 20 | } else { 21 | startSysTtsForwarder() 22 | } 23 | } 24 | 25 | fun Context.startMsTtsForwarder() { 26 | startService(Intent(this, MsTtsForwarderService::class.java)) 27 | } 28 | 29 | fun closeMsTtsForwarder() { 30 | MsTtsForwarderService.instance?.close() 31 | } 32 | 33 | fun Context.startSysTtsForwarder() { 34 | startService(Intent(this, SysTtsForwarderService::class.java)) 35 | } 36 | 37 | fun closeSysTtsForwarder() { 38 | SysTtsForwarderService.instance?.close() 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/service/forwarder/ms/QSTileService.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.service.forwarder.ms 2 | 3 | import android.content.Intent 4 | import android.os.Build 5 | import android.service.quicksettings.Tile 6 | import android.service.quicksettings.TileService 7 | import androidx.annotation.RequiresApi 8 | import com.github.jing332.tts_server_android.service.forwarder.ForwarderServiceManager.startMsTtsForwarder 9 | 10 | /* 快捷开关(Android 7+) */ 11 | @RequiresApi(Build.VERSION_CODES.N) 12 | class QSTileService : TileService() { 13 | override fun startActivity(intent: Intent?) { 14 | super.startActivity(intent) 15 | } 16 | 17 | override fun onStartListening() { 18 | super.onStartListening() 19 | if (MsTtsForwarderService.isRunning) { 20 | qsTile.state = Tile.STATE_ACTIVE 21 | } else { 22 | qsTile.state = Tile.STATE_INACTIVE 23 | } 24 | qsTile.updateTile() 25 | } 26 | 27 | override fun onClick() { 28 | super.onClick() 29 | if (qsTile.state == Tile.STATE_ACTIVE) { /* 关闭 */ 30 | if (MsTtsForwarderService.isRunning) { 31 | MsTtsForwarderService.instance?.close() 32 | } 33 | qsTile.state = Tile.STATE_INACTIVE 34 | } else {/* 打开 */ 35 | startMsTtsForwarder() 36 | qsTile.state = Tile.STATE_ACTIVE 37 | } 38 | qsTile.updateTile() 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/service/forwarder/system/EngineInfo.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.service.forwarder.system 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class EngineInfo(val name: String, val label: String) -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/service/forwarder/system/QSTileService.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.service.forwarder.system 2 | 3 | import android.content.Intent 4 | import android.os.Build 5 | import android.service.quicksettings.Tile 6 | import android.service.quicksettings.TileService 7 | import androidx.annotation.RequiresApi 8 | import com.github.jing332.tts_server_android.service.forwarder.ForwarderServiceManager 9 | import com.github.jing332.tts_server_android.service.forwarder.ForwarderServiceManager.startSysTtsForwarder 10 | 11 | @RequiresApi(Build.VERSION_CODES.N) 12 | class QSTileService : TileService() { 13 | override fun onStartListening() { 14 | super.onStartListening() 15 | if (SysTtsForwarderService.isRunning) { 16 | qsTile.state = Tile.STATE_ACTIVE 17 | } else { 18 | qsTile.state = Tile.STATE_INACTIVE 19 | } 20 | qsTile.updateTile() 21 | } 22 | 23 | override fun onClick() { 24 | super.onClick() 25 | if (qsTile.state == Tile.STATE_ACTIVE) { /* 关闭 */ 26 | if (SysTtsForwarderService.isRunning) { 27 | ForwarderServiceManager.closeSysTtsForwarder() 28 | } 29 | qsTile.state = Tile.STATE_INACTIVE 30 | } else {/* 打开 */ 31 | startSysTtsForwarder() 32 | qsTile.state = Tile.STATE_ACTIVE 33 | } 34 | qsTile.updateTile() 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/service/forwarder/system/VoiceInfo.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.service.forwarder.system 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class VoiceInfo( 7 | val name: String, 8 | val locale: String, 9 | val localeName: String, 10 | val features: List? = null 11 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/service/systts/CheckVoiceData.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.service.systts 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import android.speech.tts.TextToSpeech 7 | 8 | class CheckVoiceData : Activity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | val result = TextToSpeech.Engine.CHECK_VOICE_DATA_PASS 12 | val returnData = Intent() 13 | 14 | val available: ArrayList = arrayListOf("zho-CHN") 15 | val unavailable: ArrayList = arrayListOf() 16 | 17 | returnData.putStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES, available) 18 | returnData.putStringArrayListExtra( 19 | TextToSpeech.Engine.EXTRA_UNAVAILABLE_VOICES, 20 | unavailable 21 | ) 22 | setResult(result, returnData) 23 | finish() 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/service/systts/help/SpeechRuleHelper.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.service.systts.help 2 | 3 | import android.content.Context 4 | import android.os.SystemClock 5 | import com.github.jing332.tts_server_android.data.entities.SpeechRule 6 | import com.github.jing332.tts_server_android.model.rhino.speech_rule.SpeechRuleEngine 7 | import com.github.jing332.tts_server_android.model.speech.TtsTextSegment 8 | import com.github.jing332.tts_server_android.model.speech.tts.ITextToSpeechEngine 9 | import java.util.Random 10 | 11 | class SpeechRuleHelper { 12 | private val random: Random by lazy { Random(SystemClock.elapsedRealtime()) } 13 | lateinit var engine: SpeechRuleEngine 14 | 15 | fun init(context: Context, rule: SpeechRule) { 16 | engine = SpeechRuleEngine(context, rule) 17 | engine.eval() 18 | random.setSeed(SystemClock.elapsedRealtime()) 19 | } 20 | 21 | fun splitText(text: String): List { 22 | return engine.splitText(text).map { it.toString() } 23 | } 24 | 25 | fun handleText( 26 | text: String, 27 | config: Map>, 28 | defaultConfig: ITextToSpeechEngine, 29 | ): List { 30 | if (!this::engine.isInitialized) return listOf(TtsTextSegment(defaultConfig, text)) 31 | val resultList = mutableListOf() 32 | 33 | val list = config.entries.map { it.value }.flatten().map { it.speechRule } 34 | engine.handleText(text, list).forEach { txtWithTag -> 35 | if (txtWithTag.text.isNotBlank()) { 36 | val sameTagList = config[txtWithTag.tag] ?: listOf(defaultConfig) 37 | val ttsFromId = sameTagList.find { it.speechRule.configId == txtWithTag.id } 38 | 39 | val tts = ttsFromId ?: sameTagList[random.nextInt(sameTagList.size)] 40 | resultList.add(TtsTextSegment(text = txtWithTag.text, tts = tts)) 41 | } 42 | } 43 | 44 | return resultList 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/service/systts/help/TextReplacer.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.service.systts.help 2 | 3 | import com.github.jing332.tts_server_android.data.appDb 4 | import com.github.jing332.tts_server_android.data.entities.replace.ReplaceRule 5 | import com.github.jing332.tts_server_android.service.systts.help.exception.TextReplacerException 6 | 7 | class TextReplacer { 8 | companion object { 9 | const val TAG = "ReplaceHelper" 10 | } 11 | 12 | private var map: MutableMap> = mutableMapOf() 13 | 14 | fun load() { 15 | map.clear() 16 | appDb.replaceRuleDao.allGroupWithReplaceRules().forEach { groupWithRules -> 17 | if (map[groupWithRules.group.onExecution] == null) 18 | map[groupWithRules.group.onExecution] = mutableListOf() 19 | 20 | map[groupWithRules.group.onExecution]?.addAll( 21 | groupWithRules.list.filter { it.isEnabled } 22 | ) 23 | } 24 | } 25 | 26 | /** 27 | * 执行替换 28 | */ 29 | fun replace( 30 | text: String, 31 | onExecution: Int, 32 | onReplaceError: (t: TextReplacerException) -> Unit 33 | ): String { 34 | var s = text 35 | map[onExecution]?.forEach { rule -> 36 | kotlin.runCatching { 37 | s = if (rule.isRegex) 38 | s.replace(Regex(rule.pattern), rule.replacement) 39 | else 40 | s.replace(rule.pattern, rule.replacement) 41 | }.onFailure { 42 | onReplaceError.invoke(TextReplacerException(rule, it)) 43 | } 44 | } 45 | 46 | return s 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/service/systts/help/exception/ConfigLoadException.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.service.systts.help.exception 2 | 3 | import com.github.jing332.tts_server_android.model.speech.tts.ITextToSpeechEngine 4 | 5 | class ConfigLoadException( 6 | override val message: String? = null, 7 | override val cause: Throwable? = null 8 | ) : TtsManagerException() { 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/service/systts/help/exception/PlayException.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.service.systts.help.exception 2 | 3 | import com.github.jing332.tts_server_android.model.speech.tts.ITextToSpeechEngine 4 | 5 | class PlayException( 6 | override val text: String? = null, 7 | override val tts: ITextToSpeechEngine?, 8 | override val cause: Throwable?, 9 | override val message: String? = null 10 | ) : 11 | SynthesisException() { 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/service/systts/help/exception/RequestException.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.service.systts.help.exception 2 | 3 | import com.github.jing332.tts_server_android.model.speech.tts.ITextToSpeechEngine 4 | 5 | class RequestException( 6 | val errorCode: Int = 0, 7 | val times: Int = 1, 8 | override val tts: ITextToSpeechEngine?, 9 | override val text: String?, 10 | override val message: String? = null, 11 | override val cause: Throwable? = null 12 | ) : 13 | SynthesisException() { 14 | companion object { 15 | const val ERROR_CODE_REQUEST = 0 16 | const val ERROR_CODE_AUDIO_NULL = 1 17 | const val ERROR_CODE_TIMEOUT = 2 18 | } 19 | 20 | override fun getLocalizedMessage(): String? { 21 | return when (errorCode) { 22 | ERROR_CODE_REQUEST -> return "Request error" 23 | ERROR_CODE_AUDIO_NULL -> return "Audio is null" 24 | ERROR_CODE_TIMEOUT -> return "Request timeout" 25 | else -> super.getLocalizedMessage() 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/service/systts/help/exception/SpeechRuleException.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.service.systts.help.exception 2 | 3 | import com.github.jing332.tts_server_android.model.speech.tts.ITextToSpeechEngine 4 | 5 | class SpeechRuleException(override val text: String?, override val tts: ITextToSpeechEngine?, 6 | override val message: String? = null, override val cause: Throwable? = null) : 7 | SynthesisException() { 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/service/systts/help/exception/SynthesisException.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.service.systts.help.exception 2 | 3 | import com.github.jing332.tts_server_android.model.speech.tts.ITextToSpeechEngine 4 | 5 | open class SynthesisException( 6 | open val tts: ITextToSpeechEngine? = null, 7 | open val text: String? = null, 8 | override val message: String? = null, 9 | override val cause: Throwable? = null 10 | ) : TtsManagerException() { 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/service/systts/help/exception/TextReplacerException.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.service.systts.help.exception 2 | 3 | import com.github.jing332.tts_server_android.data.entities.replace.ReplaceRule 4 | 5 | data class TextReplacerException( 6 | val replaceRule: ReplaceRule? = null, 7 | override val cause: Throwable?, 8 | override val message: String? = null, 9 | ) : 10 | TtsManagerException() { 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/service/systts/help/exception/TtsManagerException.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.service.systts.help.exception 2 | 3 | import com.github.jing332.tts_server_android.model.speech.tts.ITextToSpeechEngine 4 | 5 | open class TtsManagerException( 6 | override val message: String? = null, 7 | override val cause: Throwable? = null 8 | ) : 9 | Exception() { 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/ui/AppLog.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.ui 2 | 3 | import android.os.Parcelable 4 | import com.github.jing332.tts_server_android.constant.LogLevel 5 | import com.github.jing332.tts_server_android.constant.LogLevel.Companion.toColor 6 | import kotlinx.parcelize.Parcelize 7 | 8 | @Parcelize 9 | data class AppLog(@LogLevel val level: Int, val msg: String) : Parcelable { 10 | fun toColor(): Int { 11 | return toColor(level) 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/ui/forwarder/MsForwarderSwitchActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.ui.forwarder 2 | 3 | import android.app.Activity 4 | import android.os.Bundle 5 | import com.github.jing332.tts_server_android.R 6 | import com.github.jing332.tts_server_android.service.forwarder.ForwarderServiceManager.startMsTtsForwarder 7 | import com.github.jing332.tts_server_android.service.forwarder.ms.MsTtsForwarderService 8 | 9 | /* 桌面长按菜单{开关} */ 10 | class MsForwarderSwitchActivity : Activity() { 11 | override fun onCreate(savedInstanceState: Bundle?) { 12 | super.onCreate(savedInstanceState) 13 | if (MsTtsForwarderService.isRunning) 14 | MsTtsForwarderService.instance?.close() 15 | else 16 | startMsTtsForwarder() 17 | 18 | finish() 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/ui/forwarder/SystemForwarderSwitchActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.ui.forwarder 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import com.github.jing332.tts_server_android.service.forwarder.system.SysTtsForwarderService 7 | 8 | class SystemForwarderSwitchActivity : Activity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | if (SysTtsForwarderService.isRunning) 12 | SysTtsForwarderService.instance?.close() 13 | else 14 | startService(Intent(this, SysTtsForwarderService::class.java)) 15 | 16 | finish() 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/ui/view/AppDialogs.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.ui.view 2 | 3 | import android.content.Context 4 | import com.github.jing332.tts_server_android.utils.runOnUI 5 | 6 | object AppDialogs { 7 | fun Context.displayErrorDialog(t: Throwable, title: String? = null) { 8 | runOnUI { 9 | ErrorDialogActivity.start(this, title ?: "Error", t) 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/ui/view/Attributes.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.ui.view 2 | 3 | import android.content.Context 4 | import android.util.TypedValue 5 | import androidx.annotation.ColorInt 6 | import androidx.annotation.DrawableRes 7 | 8 | object Attributes { 9 | @ColorInt 10 | fun Context.colorAttr(resId: Int): Int { 11 | val typedValue = TypedValue() 12 | this.theme.resolveAttribute( 13 | resId, 14 | typedValue, 15 | true 16 | ) 17 | return typedValue.data 18 | } 19 | 20 | 21 | @DrawableRes 22 | fun Context.drawableAttr(resId: Int): Int { 23 | val typedValue = TypedValue() 24 | this.theme.resolveAttribute( 25 | resId, 26 | typedValue, 27 | true 28 | ) 29 | return typedValue.resourceId 30 | } 31 | 32 | @get:ColorInt 33 | val Context.colorChipStroke: Int 34 | get() = colorAttr(com.google.android.material.R.attr.chipStrokeColor) 35 | 36 | @get:ColorInt 37 | val Context.colorControlHighlight: Int 38 | get() = colorAttr(com.google.android.material.R.attr.colorControlHighlight) 39 | 40 | @get:ColorInt 41 | val Context.colorSurface: Int 42 | get() = colorAttr(com.google.android.material.R.attr.colorSurface) 43 | 44 | @get:ColorInt 45 | val Context.colorOnBackground: Int 46 | get() = colorAttr(com.google.android.material.R.attr.colorOnBackground) 47 | 48 | @get:DrawableRes 49 | val Context.selectableItemBackground: Int 50 | get() = drawableAttr(android.R.attr.selectableItemBackground) 51 | 52 | @get:DrawableRes 53 | val Context.selectableItemBackgroundBorderless: Int 54 | get() = drawableAttr(android.R.attr.selectableItemBackgroundBorderless) 55 | 56 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/ui/view/BigTextView.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.ui.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.widget.FrameLayout 6 | import com.github.jing332.tts_server_android.databinding.BigTextViewBinding 7 | import splitties.systemservices.layoutInflater 8 | 9 | class BigTextView(context: Context, attrs: AttributeSet?, defaultStyle: Int) : 10 | FrameLayout(context, attrs, defaultStyle) { 11 | constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) 12 | constructor(context: Context) : this(context, null, 0) 13 | 14 | private val mBinding by lazy { 15 | BigTextViewBinding.inflate(layoutInflater, this, true).apply { 16 | tvLog.setTextIsSelectable(true) 17 | } 18 | } 19 | 20 | fun setText(text: CharSequence) { 21 | mBinding.tvLog.text = text 22 | } 23 | 24 | fun append(text: CharSequence) { 25 | mBinding.tvLog.append(text) 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/ui/view/ErrorDialogViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.ui.view 2 | 3 | import androidx.lifecycle.ViewModel 4 | 5 | class ErrorDialogViewModel : ViewModel() { 6 | internal val throwableList = mutableMapOf() 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/ui/view/widget/AppTextInputLayout.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.ui.view.widget 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.View 6 | import androidx.core.view.accessibility.AccessibilityNodeInfoCompat 7 | import com.google.android.material.R 8 | import com.google.android.material.textfield.TextInputLayout 9 | 10 | open class AppTextInputLayout(context: Context, attrs: AttributeSet? = null, defaultStyle: Int = R.attr.textInputStyle) : 11 | TextInputLayout(context, attrs, defaultStyle) { 12 | constructor(context: Context, attrs: AttributeSet?) : this( 13 | context, 14 | attrs, 15 | R.attr.textInputStyle 16 | ) 17 | 18 | 19 | override fun onAttachedToWindow() { 20 | super.onAttachedToWindow() 21 | setTextInputAccessibilityDelegate(TextInputLayoutDelegate(this)) 22 | } 23 | 24 | class TextInputLayoutDelegate(private val til: TextInputLayout) : AccessibilityDelegate(til) { 25 | override fun onInitializeAccessibilityNodeInfo( 26 | host: View, 27 | info: AccessibilityNodeInfoCompat 28 | ) { 29 | super.onInitializeAccessibilityNodeInfo(host, info) 30 | info.text = "${til.hint} ${til.editText?.text}" 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/utils/ConfigurationExtensions.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | 3 | package com.github.jing332.tts_server_android.utils 4 | 5 | import android.content.res.Configuration 6 | import android.content.res.Resources 7 | 8 | val sysConfiguration: Configuration = Resources.getSystem().configuration 9 | 10 | val Configuration.isNightMode: Boolean 11 | get() { 12 | val mode = uiMode and Configuration.UI_MODE_NIGHT_MASK 13 | return mode == Configuration.UI_MODE_NIGHT_YES 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/utils/DecimalUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.utils 2 | 3 | import java.math.BigDecimal 4 | import java.math.RoundingMode 5 | 6 | object DecimalUtils { 7 | } 8 | 9 | fun Float.toScale(scale: Int = 2) = 10 | BigDecimal(this.toDouble()).setScale(scale, RoundingMode.HALF_UP).toFloat() -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/utils/EncoderUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.utils 2 | 3 | import android.util.Base64 4 | 5 | /** 6 | * 编码工具 escape base64 7 | */ 8 | @Suppress("unused") 9 | object EncoderUtils { 10 | 11 | fun escape(src: String): String { 12 | val tmp = StringBuilder() 13 | for (char in src) { 14 | val charCode = char.code 15 | if (charCode in 48..57 || charCode in 65..90 || charCode in 97..122) { 16 | tmp.append(char) 17 | continue 18 | } 19 | 20 | val prefix = when { 21 | charCode < 16 -> "%0" 22 | charCode < 256 -> "%" 23 | else -> "%u" 24 | } 25 | tmp.append(prefix).append(charCode.toString(16)) 26 | } 27 | return tmp.toString() 28 | } 29 | 30 | @JvmOverloads 31 | fun base64Decode(str: String, flags: Int = Base64.DEFAULT): String { 32 | val bytes = Base64.decode(str, flags) 33 | return String(bytes) 34 | } 35 | 36 | @JvmOverloads 37 | fun base64Encode(str: String, flags: Int = Base64.NO_WRAP): String? { 38 | return base64Encode(str.toByteArray(), flags) 39 | } 40 | 41 | fun base64Encode(src: ByteArray, flags: Int = Base64.NO_WRAP): String? { 42 | return Base64.encodeToString(src, flags) 43 | } 44 | 45 | @JvmOverloads 46 | fun base64DecodeToByteArray(str: String, flags: Int = Base64.DEFAULT): ByteArray { 47 | return Base64.decode(str, flags) 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/utils/GcManager.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.utils 2 | 3 | import android.os.SystemClock 4 | 5 | 6 | object GcManager { 7 | var last: Long = 0 8 | 9 | /** 10 | * 避免频繁GC 11 | */ 12 | @Synchronized 13 | fun doGC() { 14 | if (SystemClock.elapsedRealtime() - last > 10000) { 15 | Runtime.getRuntime().gc() 16 | last = SystemClock.elapsedRealtime() 17 | } 18 | 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/utils/HandlerUtils.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | /* https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/utils/HandlerUtils.kt */ 3 | package com.github.jing332.tts_server_android.utils 4 | 5 | import android.os.Build.VERSION.SDK_INT 6 | import android.os.Handler 7 | import android.os.Looper 8 | import kotlinx.coroutines.CoroutineScope 9 | import kotlinx.coroutines.Dispatchers.IO 10 | import kotlinx.coroutines.launch 11 | import kotlinx.coroutines.runBlocking 12 | 13 | /** This main looper cache avoids synchronization overhead when accessed repeatedly. */ 14 | private val mainLooper: Looper = Looper.getMainLooper() 15 | 16 | private val mainThread: Thread = mainLooper.thread 17 | 18 | private val isMainThread: Boolean inline get() = mainThread === Thread.currentThread() 19 | 20 | fun buildMainHandler(): Handler { 21 | return if (SDK_INT >= 28) Handler.createAsync(mainLooper) else try { 22 | Handler::class.java.getDeclaredConstructor( 23 | Looper::class.java, 24 | Handler.Callback::class.java, 25 | Boolean::class.javaPrimitiveType // async 26 | ).newInstance(mainLooper, null, true) 27 | } catch (ignored: NoSuchMethodException) { 28 | // Hidden constructor absent. Fall back to non-async constructor. 29 | Handler(mainLooper) 30 | } 31 | } 32 | 33 | private val mainHandler by lazy { buildMainHandler() } 34 | 35 | fun runOnUI(function: () -> Unit) { 36 | if (isMainThread) { 37 | function() 38 | } else { 39 | mainHandler.post(function) 40 | } 41 | } 42 | 43 | fun CoroutineScope.runOnIO(function: suspend () -> Unit) { 44 | if (isMainThread) { 45 | launch(IO) { 46 | function() 47 | } 48 | } else { 49 | runBlocking { function() } 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/utils/MD5Utils.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.utils 2 | 3 | import cn.hutool.crypto.digest.DigestUtil 4 | import java.io.InputStream 5 | 6 | /** 7 | * 将字符串转化为MD5 8 | */ 9 | @Suppress("unused") 10 | object MD5Utils { 11 | fun md5Encode(str: String?): String { 12 | return DigestUtil.digester("MD5").digestHex(str) 13 | } 14 | 15 | fun md5Encode(inputStream: InputStream): String { 16 | return DigestUtil.digester("MD5").digestHex(inputStream) 17 | } 18 | 19 | fun md5Encode16(str: String): String { 20 | var reStr = md5Encode(str) 21 | reStr = reStr.substring(8, 24) 22 | return reStr 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/utils/ParcelUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.utils 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | 6 | @Suppress("UNCHECKED_CAST") 7 | fun Parcelable?.clone(): T? { 8 | val p = Parcel.obtain() 9 | p.writeValue(this) 10 | p.setDataPosition(0) 11 | val c: Class = this!!::class.java 12 | val newObject = p.readValue(c.classLoader) as T? 13 | p.recycle() 14 | return newObject 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/utils/SizeUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.utils 2 | 3 | import android.content.res.Resources 4 | 5 | object SizeUtils { 6 | 7 | /** 8 | * Value of dp to value of px. 9 | * 10 | * @param dpValue The value of dp. 11 | * @return value of px 12 | */ 13 | fun dp2px(dpValue: Float): Int { 14 | val scale: Float = Resources.getSystem().displayMetrics.density 15 | return (dpValue * scale + 0.5f).toInt() 16 | } 17 | 18 | /** 19 | * Value of px to value of dp. 20 | * 21 | * @param pxValue The value of px. 22 | * @return value of dp 23 | */ 24 | fun px2dp(pxValue: Float): Int { 25 | val scale: Float = Resources.getSystem().displayMetrics.density 26 | return (pxValue / scale + 0.5f).toInt() 27 | } 28 | 29 | /** 30 | * Value of sp to value of px. 31 | * 32 | * @param spValue The value of sp. 33 | * @return value of px 34 | */ 35 | @Suppress("DEPRECATION") 36 | fun sp2px(spValue: Float): Int { 37 | val fontScale: Float = Resources.getSystem().displayMetrics.scaledDensity 38 | return (spValue * fontScale + 0.5f).toInt() 39 | } 40 | 41 | /** 42 | * Value of px to value of sp. 43 | * 44 | * @param pxValue The value of px. 45 | * @return value of sp 46 | */ 47 | fun px2sp(pxValue: Float): Int { 48 | val fontScale: Float = Resources.getSystem().getDisplayMetrics().scaledDensity 49 | return (pxValue / fontScale + 0.5f).toInt() 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/utils/SoftKeyboardUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.utils 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.view.View 6 | import android.view.inputmethod.InputMethodManager 7 | 8 | // https://juejin.cn/post/6844903471687172104 9 | object SoftKeyboardUtils { 10 | /** 11 | * 隐藏软键盘(只适用于Activity,不适用于Fragment) 12 | */ 13 | fun hideSoftKeyboard(activity: Activity) { 14 | val view: View? = activity.currentFocus 15 | if (view != null) { 16 | val inputMethodManager: InputMethodManager = 17 | activity.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager 18 | inputMethodManager.hideSoftInputFromWindow( 19 | view.windowToken, 20 | InputMethodManager.HIDE_NOT_ALWAYS 21 | ) 22 | } 23 | } 24 | 25 | /** 26 | * 隐藏软键盘(可用于Activity,Fragment) 27 | */ 28 | fun hideSoftKeyboard(context: Context, viewList: List?) { 29 | if (viewList == null) return 30 | val inputMethodManager: InputMethodManager = 31 | context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager 32 | for (v in viewList) { 33 | inputMethodManager.hideSoftInputFromWindow( 34 | v?.windowToken, 35 | InputMethodManager.HIDE_NOT_ALWAYS 36 | ) 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/utils/ThrottleUtil.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.utils 2 | 3 | import kotlinx.coroutines.* 4 | 5 | @OptIn(DelicateCoroutinesApi::class) 6 | class ThrottleUtil(private val scope: CoroutineScope = GlobalScope, val time: Long = 100L) { 7 | var job: Job? = null 8 | 9 | fun runAction( 10 | dispatcher: CoroutineDispatcher = Dispatchers.Main, 11 | action: suspend () -> Unit, 12 | ) { 13 | job?.cancel() 14 | job = null 15 | job = scope.launch(dispatcher) { 16 | delay(time) 17 | action.invoke() 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/utils/ThrowableUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.utils 2 | 3 | import cn.hutool.core.exceptions.ExceptionUtil.getThrowableList 4 | 5 | 6 | object ThrowableUtils { 7 | fun getRootCause(throwable: Throwable?): Throwable? { 8 | val list = getThrowableList(throwable) 9 | return if (list.size < 2) null else list[list.size - 1] as Throwable 10 | } 11 | } 12 | 13 | val Throwable.rootCause: Throwable? 14 | get() = ThrowableUtils.getRootCause(this) 15 | 16 | val Throwable.readableString: String 17 | get() = "${rootCause}\n⬇ More:\n${stackTraceToString()}" 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/utils/ToastUtils.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | /* https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/utils/ToastUtils.kt */ 3 | package com.github.jing332.tts_server_android.utils 4 | 5 | import android.content.Context 6 | import android.widget.Toast 7 | import androidx.annotation.StringRes 8 | import androidx.fragment.app.Fragment 9 | 10 | fun Context.toast(@StringRes message: Int, vararg args: Any) { 11 | runOnUI { 12 | kotlin.runCatching { 13 | Toast.makeText(this, getString(message, *args), Toast.LENGTH_SHORT).show() 14 | } 15 | } 16 | } 17 | 18 | fun Context.toast(message: CharSequence?) { 19 | runOnUI { 20 | kotlin.runCatching { 21 | Toast.makeText(this, message, Toast.LENGTH_SHORT).show() 22 | } 23 | } 24 | } 25 | 26 | fun Context.longToast(@StringRes message: Int, vararg args: Any) { 27 | runOnUI { 28 | kotlin.runCatching { 29 | Toast.makeText(this, getString(message, *args), Toast.LENGTH_LONG).show() 30 | } 31 | } 32 | } 33 | 34 | fun Context.longToast(message: CharSequence?) { 35 | runOnUI { 36 | kotlin.runCatching { 37 | Toast.makeText(this, message, Toast.LENGTH_LONG).show() 38 | } 39 | } 40 | } 41 | 42 | 43 | fun Fragment.toast(@StringRes message: Int) = requireActivity().toast(message) 44 | 45 | fun Fragment.toast(message: CharSequence) = requireActivity().toast(message) 46 | 47 | fun Fragment.longToast(@StringRes message: Int) = requireContext().longToast(message) 48 | 49 | fun Fragment.longToast(message: CharSequence) = requireContext().longToast(message) -------------------------------------------------------------------------------- /app/src/main/java/com/github/jing332/tts_server_android/utils/VarDelegate.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android.utils 2 | 3 | import kotlin.reflect.KProperty 4 | 5 | class VarDelegate(var value: Any?) { 6 | operator fun getValue(thisRef: Any?, property: KProperty<*>): Any? = value 7 | operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: Any?) { 8 | value = newValue 9 | } 10 | } -------------------------------------------------------------------------------- /app/src/main/res/color/bg_text_btn_color_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/color/tab_text_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_error_24.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_tag_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_spinner_item_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_add_24.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_compare_arrows_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_file_open_24.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_input_24.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_insert_drive_file_24.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_output_24.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_remove_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_save_24.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_select_all_24.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_microsoft.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_microsoft_edge.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_shortcut_plugin.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_shortcut_replace.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_shortcut_speech_rule.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_tts.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_web.xml: -------------------------------------------------------------------------------- 1 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/big_text_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/material_text_input.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_app_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_app_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_app_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgz0227/tts-server-android/045eb75f55a70f5c6fca255e300b600d106cb2c0/app/src/main/res/mipmap-hdpi/ic_app_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_app_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgz0227/tts-server-android/045eb75f55a70f5c6fca255e300b600d106cb2c0/app/src/main/res/mipmap-hdpi/ic_app_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_app_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgz0227/tts-server-android/045eb75f55a70f5c6fca255e300b600d106cb2c0/app/src/main/res/mipmap-mdpi/ic_app_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_app_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgz0227/tts-server-android/045eb75f55a70f5c6fca255e300b600d106cb2c0/app/src/main/res/mipmap-mdpi/ic_app_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_app_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgz0227/tts-server-android/045eb75f55a70f5c6fca255e300b600d106cb2c0/app/src/main/res/mipmap-xhdpi/ic_app_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_app_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgz0227/tts-server-android/045eb75f55a70f5c6fca255e300b600d106cb2c0/app/src/main/res/mipmap-xhdpi/ic_app_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_app_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgz0227/tts-server-android/045eb75f55a70f5c6fca255e300b600d106cb2c0/app/src/main/res/mipmap-xxhdpi/ic_app_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_app_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgz0227/tts-server-android/045eb75f55a70f5c6fca255e300b600d106cb2c0/app/src/main/res/mipmap-xxhdpi/ic_app_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_legado_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgz0227/tts-server-android/045eb75f55a70f5c6fca255e300b600d106cb2c0/app/src/main/res/mipmap-xxhdpi/ic_legado_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_app_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgz0227/tts-server-android/045eb75f55a70f5c6fca255e300b600d106cb2c0/app/src/main/res/mipmap-xxxhdpi/ic_app_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_app_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgz0227/tts-server-android/045eb75f55a70f5c6fca255e300b600d106cb2c0/app/src/main/res/mipmap-xxxhdpi/ic_app_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_app_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgz0227/tts-server-android/045eb75f55a70f5c6fca255e300b600d106cb2c0/app/src/main/res/mipmap-xxxhdpi/ic_app_notification.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_legado_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgz0227/tts-server-android/045eb75f55a70f5c6fca255e300b600d106cb2c0/app/src/main/res/mipmap-xxxhdpi/ic_legado_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @android:color/white 4 | @android:color/white 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_new_app_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_new_app_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFCDB 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/keys.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | backup_restore 4 | key_backup 5 | key_restore 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/xml/file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/xml/tts_engine.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/test/java/com/github/jing332/tts_server_android/HttpTtsUrlUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.github.jing332.tts_server_android 2 | 3 | import com.github.jing332.tts_server_android.model.AnalyzeUrl 4 | import com.github.jing332.tts_server_android.service.systts.help.ResultTextTag 5 | import org.junit.Test 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class HttpTtsUrlUnitTest { 13 | @Test 14 | fun testJs() { 15 | // val url = 16 | // "http://tsn.baidu.com/text2audio,{\"method\": \"POST\", \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4114&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=220&vol=5&aue=6&pit=5&res_tag=audio\"}" 17 | val url = 18 | """ http://192.168.0.109:1233/api/ra,{"method":"POST","body":"{{speakText}}"} """ 19 | val aurl = AnalyzeUrl(url) 20 | println("baseUrl: " + aurl.eval().toString()) 21 | } 22 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | kotlin_version = '1.9.22' 4 | agp_version = '8.2.0' 5 | compose_compiler = "1.5.8" 6 | room_version = '2.6.1' 7 | ksp_version = '1.9.22-1.0.17' 8 | about_lib_version = "10.9.2" 9 | } 10 | } 11 | 12 | plugins { 13 | id 'com.android.application' version "$agp_version" apply false 14 | id 'com.android.library' version "$agp_version" apply false 15 | id 'org.jetbrains.kotlin.android' version "$kotlin_version" apply false 16 | id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlin_version" 17 | id("com.google.devtools.ksp") version "$ksp_version" apply false 18 | id("com.mikepenz.aboutlibraries.plugin") version "$about_lib_version" apply false 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } -------------------------------------------------------------------------------- /crowdin.yml: -------------------------------------------------------------------------------- 1 | files: 2 | - source: /app/src/main/res/values/strings.xml 3 | translation: /app/src/main/res/values-%two_letters_code%/%original_file_name% 4 | type: android 5 | dest: /app/strings.xml 6 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -XX:+UseParallelGC 2 | android.useAndroidX=true 3 | android.enableJetifier = true 4 | android.nonTransitiveRClass=true 5 | android.nonFinalResIds=false 6 | android.enableR8.fullMode=false 7 | 8 | org.gradle.parallel=true 9 | #org.gradle.unsafe.configuration-cache=true 10 | # Use this flag sparingly, in case some of the plugins are not fully compatible 11 | #org.gradle.unsafe.configuration-cache-problems=warn 12 | 13 | kotlin.code.style=official 14 | kotlin.incremental=true 15 | kotlin.incremental.java=true 16 | kotlin.incremental.js=true 17 | kotlin.caching.enabled=true 18 | kotlin.parallel.tasks.in.project=true 19 | 20 | #kotlin.experimental.tryK2=true 21 | #android.lint.useK2Uast=true 22 | #kapt.incremental.apt=true 23 | #kapt.include.compile.classpath=false 24 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgz0227/tts-server-android/045eb75f55a70f5c6fca255e300b600d106cb2c0/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Aug 20 16:02:57 CST 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /images/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgz0227/tts-server-android/045eb75f55a70f5c6fca255e300b600d106cb2c0/images/1.jpg -------------------------------------------------------------------------------- /images/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgz0227/tts-server-android/045eb75f55a70f5c6fca255e300b600d106cb2c0/images/2.jpg -------------------------------------------------------------------------------- /images/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgz0227/tts-server-android/045eb75f55a70f5c6fca255e300b600d106cb2c0/images/3.jpg -------------------------------------------------------------------------------- /images/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgz0227/tts-server-android/045eb75f55a70f5c6fca255e300b600d106cb2c0/images/4.jpg -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | maven { url 'https://jitpack.io' } 14 | } 15 | } 16 | rootProject.name = "TTS Server" 17 | include ':app' 18 | -------------------------------------------------------------------------------- /tts-server-lib/.gitignore: -------------------------------------------------------------------------------- 1 | *.aar 2 | *.jar 3 | .idea 4 | *.cmd 5 | *.mp3 6 | *.wav 7 | -------------------------------------------------------------------------------- /tts-server-lib/README.md: -------------------------------------------------------------------------------- 1 | 这个目录是包装的 tts-server-go, 以方便Android调用。 2 | 3 | # Build 4 | 5 | ``` 6 | go install golang.org/x/mobile/cmd/gomobile@latest 7 | gomobile init 8 | go get golang.org/x/mobile/bind 9 | 10 | 另外,还需设置ANDROID_NDK_HOME变量(如果在Android Studio里安装了NDK就直接设置ANDROID_HOME为SDK目录即可) 11 | 然后构建: 12 | gomobile bind -v -target="android/arm,android/arm64" -androidapi=19 13 | 14 | 最后将 tts_server_lib.aar 移动到 app/libs 目录下 15 | ``` 16 | -------------------------------------------------------------------------------- /tts-server-lib/api_scirpt_code_sync_server.go: -------------------------------------------------------------------------------- 1 | package tts_server_lib 2 | 3 | import ( 4 | log "github.com/sirupsen/logrus" 5 | "io" 6 | "time" 7 | "tts-server-lib/sync_server" 8 | "tts-server-lib/wrapper" 9 | ) 10 | 11 | type ScriptCodeSyncServerCallback interface { 12 | Log(level int32, msg string) 13 | Push(code string) 14 | Pull() (string, error) 15 | 16 | Action(name string, body []byte) 17 | } 18 | 19 | type ScriptSyncServer struct { 20 | server sync_server.ScriptCodeSyncServer 21 | callback ScriptCodeSyncServerCallback 22 | } 23 | 24 | func (p *ScriptSyncServer) Init(cb ScriptCodeSyncServerCallback) { 25 | p.callback = cb 26 | p.server.Log = log.New() 27 | p.server.Log.Out = io.Discard 28 | p.server.Log.Formatter = &wrapper.MyFormatter{ 29 | OnLog: func(level int32, msg string) { 30 | p.callback.Log(level, msg) 31 | }, 32 | } 33 | 34 | p.server.OnPush = cb.Push 35 | p.server.OnPull = cb.Pull 36 | 37 | p.server.OnAction = cb.Action 38 | } 39 | 40 | func (p *ScriptSyncServer) Start(port int64) error { 41 | return p.server.Start(port) 42 | } 43 | 44 | func (p *ScriptSyncServer) Close() error { 45 | return p.server.Close(time.Second * 5) 46 | } 47 | -------------------------------------------------------------------------------- /tts-server-lib/api_sys_tts_server.go: -------------------------------------------------------------------------------- 1 | package tts_server_lib 2 | 3 | import ( 4 | log "github.com/sirupsen/logrus" 5 | "io" 6 | "os" 7 | "time" 8 | "tts-server-lib/sys_tts_server" 9 | "tts-server-lib/wrapper" 10 | ) 11 | 12 | type SysTtsForwarderCallback interface { 13 | Log(level int32, msg string) 14 | 15 | CancelAudio(engine string) 16 | GetAudio(engine string, voice string, text string, rate int32, pitch int32) (file string, err error) 17 | GetEngines() (json string, err error) 18 | GetVoices(engine string) (json string, err error) 19 | } 20 | 21 | type SysTtsForwarder struct { 22 | server conv_server.ConvServer 23 | callback SysTtsForwarderCallback 24 | } 25 | 26 | func (s *SysTtsForwarder) InitCallback(cb SysTtsForwarderCallback) { 27 | s.callback = cb 28 | s.server.Log = log.New() 29 | s.server.Log.Out = io.Discard 30 | s.server.Log.Formatter = &wrapper.MyFormatter{ 31 | OnLog: func(level int32, msg string) { 32 | s.callback.Log(level, msg) 33 | }} 34 | 35 | s.server.OnGetEngines = func() (string, error) { 36 | return s.callback.GetEngines() 37 | } 38 | s.server.OnGetVoices = func(engine string) (string, error) { 39 | return s.callback.GetVoices(engine) 40 | } 41 | s.server.OnCancelAudio = func(engine string) { 42 | s.callback.CancelAudio(engine) 43 | } 44 | s.server.OnGetWavAudio = func(engine string, voice string, text string, rate int32, pitch int32) ([]byte, error) { 45 | filePath, err := s.callback.GetAudio(engine, voice, text, rate, pitch) 46 | if err != nil { 47 | return nil, err 48 | } 49 | audio, err := os.ReadFile(filePath) 50 | if err != nil { 51 | return nil, err 52 | } 53 | return audio, nil 54 | } 55 | } 56 | 57 | func (s *SysTtsForwarder) Start(port int64) error { 58 | err := s.server.Start(port) 59 | if err != nil { 60 | return err 61 | } 62 | return nil 63 | } 64 | 65 | func (s *SysTtsForwarder) Close() error { 66 | return s.server.Close(time.Second * 5) 67 | } 68 | -------------------------------------------------------------------------------- /tts-server-lib/api_sys_tts_server_test.go: -------------------------------------------------------------------------------- 1 | package tts_server_lib 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestServer(t *testing.T) { 9 | s := new(SysTtsForwarder) 10 | s.InitCallback(new(testCallback)) 11 | err := s.Start(1221) 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | } 16 | 17 | type testCallback struct { 18 | } 19 | 20 | func (testCallback) Log(level int32, msg string) { 21 | fmt.Printf("log: %d, %s\n", level, msg) 22 | } 23 | 24 | func (testCallback) GetAudio(engine string, text string, rate int32) (file string, err error) { 25 | return "", nil 26 | } 27 | 28 | func (testCallback) CancelAudio(engine string) { 29 | fmt.Printf("CancelAudio: %s", engine) 30 | } 31 | 32 | func (testCallback) GetEngines() (json string, err error) { 33 | return `{["name":"com.google.tts", "label":"Google语音引擎"]}`, nil 34 | } 35 | 36 | func (testCallback) GetVoices(engine string) (json string, err error) { 37 | return `{["name":"voiceName", "locale":"zh-CN", "localeName":"中文"]}`, nil 38 | } 39 | -------------------------------------------------------------------------------- /tts-server-lib/go.mod: -------------------------------------------------------------------------------- 1 | module tts-server-lib 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/jing332/tts-server-go v0.0.0-20230130020836-aed022548060 7 | github.com/sirupsen/logrus v1.9.0 8 | ) 9 | 10 | // replace github.com/jing332/tts-server-go => G:\Users\jing\GolandProjects\tts-server-go 11 | 12 | require ( 13 | github.com/asters1/tools v0.1.5 // indirect 14 | github.com/gorilla/websocket v1.5.0 // indirect 15 | github.com/kr/pretty v0.3.0 // indirect 16 | github.com/satori/go.uuid v1.2.0 // indirect 17 | golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105 // indirect 18 | golang.org/x/mod v0.4.2 // indirect 19 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect 20 | golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098 // indirect 21 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /tts-server-lib/ms_tts_server.go: -------------------------------------------------------------------------------- 1 | package tts_server_lib 2 | 3 | import ( 4 | "github.com/jing332/tts-server-go/server" 5 | log "github.com/sirupsen/logrus" 6 | "io" 7 | ) 8 | 9 | type LogCallback interface { 10 | Log(level int32, msg string) 11 | } 12 | 13 | var w LogHandler 14 | var s *server.GracefulServer 15 | 16 | func Init(cb LogCallback) { 17 | w.cb = cb //转发log到android 18 | log.SetOutput(w) 19 | log.SetFormatter(new(MyFormatter)) 20 | 21 | s = &server.GracefulServer{} 22 | s.HandleFunc() 23 | } 24 | 25 | func RunServer(port int64, token string, useDnsEdge bool) { 26 | s.Token = token 27 | s.UseDnsEdge = useDnsEdge 28 | err := s.ListenAndServe(port) 29 | if err != nil { 30 | log.Error(err) 31 | } 32 | s = nil 33 | } 34 | 35 | func CloseServer() { 36 | if s != nil { 37 | s.Close() 38 | } 39 | } 40 | 41 | type LogHandler struct { 42 | w io.Writer 43 | cb LogCallback 44 | } 45 | 46 | func (lh LogHandler) Write(p []byte) (n int, err error) { 47 | //lh.cb.Log(string(p)) 48 | return 0, err 49 | } 50 | 51 | func (lh LogHandler) Close() { 52 | w.Close() 53 | } 54 | 55 | type MyFormatter struct { 56 | OnLog func(level int32, msg string) 57 | } 58 | 59 | func (f *MyFormatter) Format(entry *log.Entry) ([]byte, error) { 60 | w.cb.Log(int32(entry.Level), entry.Message) 61 | return nil, nil 62 | } 63 | -------------------------------------------------------------------------------- /tts-server-lib/net_tools.go: -------------------------------------------------------------------------------- 1 | package tts_server_lib 2 | 3 | import ( 4 | tts_server_go "github.com/jing332/tts-server-go" 5 | "net/http" 6 | "strings" 7 | ) 8 | 9 | func UploadLog(log string) (string, error) { 10 | uploadUrl := "https://bin.kv2.dev" 11 | req, err := http.NewRequest(http.MethodPost, uploadUrl, strings.NewReader(log)) 12 | if err != nil { 13 | return "", err 14 | } 15 | 16 | res, err := http.DefaultClient.Do(req) 17 | if err != nil { 18 | return "", err 19 | } 20 | defer res.Body.Close() 21 | 22 | return uploadUrl + res.Request.URL.Path, nil 23 | } 24 | 25 | func GetOutboundIP() string { 26 | return tts_server_go.GetOutboundIPString() 27 | } 28 | 29 | type UploadConfigJson struct { 30 | Data string `json:"data"` 31 | Msg string `json:"msg"` 32 | } 33 | -------------------------------------------------------------------------------- /tts-server-lib/sys_tts_server/logic.go: -------------------------------------------------------------------------------- 1 | package conv_server 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | ) 7 | 8 | type LegadoJson struct { 9 | ContentType string `json:"contentType"` 10 | Header string `json:"header"` 11 | ID int64 `json:"id"` 12 | LastUpdateTime int64 `json:"lastUpdateTime"` 13 | Name string `json:"name"` 14 | URL string `json:"url"` 15 | ConcurrentRate string `json:"concurrentRate"` 16 | //EnabledCookieJar bool `json:"enabledCookieJar"` 17 | //LoginCheckJs string `json:"loginCheckJs"` 18 | //LoginUI string `json:"loginUi"` 19 | //LoginURL string `json:"loginUrl"` 20 | } 21 | 22 | func getLegadoJson(api string, displayName string, engine string, voice string, pitch string) (string, error) { 23 | t := time.Now().UnixNano() / 1e6 24 | url := api + `?engine=` + engine + `&text={{java.encodeURI(speakText)}}&rate={{speakSpeed * 2}}&pitch=` + 25 | pitch + `&voice=` + voice 26 | 27 | data := &LegadoJson{Name: displayName, LastUpdateTime: t, ID: t, URL: url, ContentType: "audio/x-wav", ConcurrentRate: "100"} 28 | jsonStr, err := json.Marshal(data) 29 | if err != nil { 30 | return "", err 31 | } 32 | return string(jsonStr), nil 33 | } 34 | -------------------------------------------------------------------------------- /tts-server-lib/sys_tts_server/logic_test.go: -------------------------------------------------------------------------------- 1 | package conv_server 2 | 3 | import "testing" 4 | 5 | func TestLegado(t *testing.T) { 6 | json, err := getLegadoJson("http://127.0.0.1/api/tts", "ni好啊", "com.gggoel.gtts", "voiceqwq", "50") 7 | if err != nil { 8 | t.Fatal(err) 9 | } 10 | t.Log(json) 11 | } 12 | -------------------------------------------------------------------------------- /tts-server-lib/sys_tts_server/server_test.go: -------------------------------------------------------------------------------- 1 | package conv_server 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestServer(t *testing.T) { 9 | server := ConvServer{} 10 | server.OnGetEngines = func() (string, error) { 11 | return `{["name": "com.google.android.tts", "Google语音服务"]}`, nil 12 | } 13 | server.OnGetWavAudio = func(engine string, voice string, text string, rate int32, pitch int32) ([]byte, error) { 14 | file, err := os.ReadFile("./ms_audio.wav") 15 | if err != nil { 16 | return nil, err 17 | } 18 | return file, nil 19 | } 20 | 21 | err := server.Start(1221) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | } 26 | 27 | type testCancelCallback struct { 28 | } 29 | 30 | func (testCancelCallback) Cancel() { 31 | 32 | } 33 | -------------------------------------------------------------------------------- /tts-server-lib/wrapper/log.go: -------------------------------------------------------------------------------- 1 | package wrapper 2 | 3 | import ( 4 | log "github.com/sirupsen/logrus" 5 | ) 6 | 7 | type MyFormatter struct { 8 | log.Formatter 9 | OnLog func(level int32, msg string) 10 | } 11 | 12 | func (f *MyFormatter) Format(entry *log.Entry) ([]byte, error) { 13 | f.OnLog(int32(entry.Level), entry.Message) 14 | return nil, nil 15 | } 16 | --------------------------------------------------------------------------------