scitex 2.7.0__py3-none-any.whl → 2.7.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scitex/__init__.py +6 -2
- scitex/__version__.py +1 -1
- scitex/audio/README.md +52 -0
- scitex/audio/__init__.py +384 -0
- scitex/audio/__main__.py +129 -0
- scitex/audio/_tts.py +334 -0
- scitex/audio/engines/__init__.py +44 -0
- scitex/audio/engines/base.py +275 -0
- scitex/audio/engines/elevenlabs_engine.py +143 -0
- scitex/audio/engines/gtts_engine.py +162 -0
- scitex/audio/engines/pyttsx3_engine.py +131 -0
- scitex/audio/mcp_server.py +757 -0
- scitex/bridge/_helpers.py +1 -1
- scitex/bridge/_plt_vis.py +1 -1
- scitex/bridge/_stats_vis.py +1 -1
- scitex/dev/plt/__init__.py +272 -0
- scitex/dev/plt/plot_mpl_axhline.py +28 -0
- scitex/dev/plt/plot_mpl_axhspan.py +28 -0
- scitex/dev/plt/plot_mpl_axvline.py +28 -0
- scitex/dev/plt/plot_mpl_axvspan.py +28 -0
- scitex/dev/plt/plot_mpl_bar.py +29 -0
- scitex/dev/plt/plot_mpl_barh.py +29 -0
- scitex/dev/plt/plot_mpl_boxplot.py +28 -0
- scitex/dev/plt/plot_mpl_contour.py +31 -0
- scitex/dev/plt/plot_mpl_contourf.py +31 -0
- scitex/dev/plt/plot_mpl_errorbar.py +30 -0
- scitex/dev/plt/plot_mpl_eventplot.py +28 -0
- scitex/dev/plt/plot_mpl_fill.py +30 -0
- scitex/dev/plt/plot_mpl_fill_between.py +31 -0
- scitex/dev/plt/plot_mpl_hexbin.py +28 -0
- scitex/dev/plt/plot_mpl_hist.py +28 -0
- scitex/dev/plt/plot_mpl_hist2d.py +28 -0
- scitex/dev/plt/plot_mpl_imshow.py +29 -0
- scitex/dev/plt/plot_mpl_pcolormesh.py +31 -0
- scitex/dev/plt/plot_mpl_pie.py +29 -0
- scitex/dev/plt/plot_mpl_plot.py +29 -0
- scitex/dev/plt/plot_mpl_quiver.py +31 -0
- scitex/dev/plt/plot_mpl_scatter.py +28 -0
- scitex/dev/plt/plot_mpl_stackplot.py +31 -0
- scitex/dev/plt/plot_mpl_stem.py +29 -0
- scitex/dev/plt/plot_mpl_step.py +29 -0
- scitex/dev/plt/plot_mpl_violinplot.py +28 -0
- scitex/dev/plt/plot_sns_barplot.py +29 -0
- scitex/dev/plt/plot_sns_boxplot.py +29 -0
- scitex/dev/plt/plot_sns_heatmap.py +28 -0
- scitex/dev/plt/plot_sns_histplot.py +29 -0
- scitex/dev/plt/plot_sns_kdeplot.py +29 -0
- scitex/dev/plt/plot_sns_lineplot.py +31 -0
- scitex/dev/plt/plot_sns_scatterplot.py +29 -0
- scitex/dev/plt/plot_sns_stripplot.py +29 -0
- scitex/dev/plt/plot_sns_swarmplot.py +29 -0
- scitex/dev/plt/plot_sns_violinplot.py +29 -0
- scitex/dev/plt/plot_stx_bar.py +29 -0
- scitex/dev/plt/plot_stx_barh.py +29 -0
- scitex/dev/plt/plot_stx_box.py +28 -0
- scitex/dev/plt/plot_stx_boxplot.py +28 -0
- scitex/dev/plt/plot_stx_conf_mat.py +28 -0
- scitex/dev/plt/plot_stx_contour.py +31 -0
- scitex/dev/plt/plot_stx_ecdf.py +28 -0
- scitex/dev/plt/plot_stx_errorbar.py +30 -0
- scitex/dev/plt/plot_stx_fill_between.py +31 -0
- scitex/dev/plt/plot_stx_fillv.py +28 -0
- scitex/dev/plt/plot_stx_heatmap.py +28 -0
- scitex/dev/plt/plot_stx_image.py +28 -0
- scitex/dev/plt/plot_stx_imshow.py +28 -0
- scitex/dev/plt/plot_stx_joyplot.py +28 -0
- scitex/dev/plt/plot_stx_kde.py +28 -0
- scitex/dev/plt/plot_stx_line.py +28 -0
- scitex/dev/plt/plot_stx_mean_ci.py +28 -0
- scitex/dev/plt/plot_stx_mean_std.py +28 -0
- scitex/dev/plt/plot_stx_median_iqr.py +28 -0
- scitex/dev/plt/plot_stx_raster.py +28 -0
- scitex/dev/plt/plot_stx_rectangle.py +28 -0
- scitex/dev/plt/plot_stx_scatter.py +29 -0
- scitex/dev/plt/plot_stx_shaded_line.py +29 -0
- scitex/dev/plt/plot_stx_violin.py +28 -0
- scitex/dev/plt/plot_stx_violinplot.py +28 -0
- scitex/fig/__init__.py +352 -0
- scitex/{vis → fig}/backend/_parser.py +1 -1
- scitex/{vis → fig}/canvas.py +1 -1
- scitex/{vis → fig}/editor/_defaults.py +70 -5
- scitex/fig/editor/_edit.py +751 -0
- scitex/{vis → fig}/editor/_qt_editor.py +181 -1
- scitex/fig/editor/flask_editor/_bbox.py +1276 -0
- scitex/fig/editor/flask_editor/_core.py +624 -0
- scitex/{vis → fig}/editor/flask_editor/_plotter.py +38 -4
- scitex/fig/editor/flask_editor/_renderer.py +739 -0
- scitex/{vis → fig}/editor/flask_editor/templates/__init__.py +1 -1
- scitex/fig/editor/flask_editor/templates/_html.py +834 -0
- scitex/fig/editor/flask_editor/templates/_scripts.py +3136 -0
- scitex/{vis → fig}/editor/flask_editor/templates/_styles.py +625 -18
- scitex/{vis → fig}/io/__init__.py +13 -1
- scitex/fig/io/_bundle.py +973 -0
- scitex/{vis → fig}/io/_canvas.py +1 -1
- scitex/{vis → fig}/io/_data.py +1 -1
- scitex/{vis → fig}/io/_export.py +1 -1
- scitex/{vis → fig}/io/_load.py +1 -1
- scitex/{vis → fig}/io/_panel.py +1 -1
- scitex/{vis → fig}/io/_save.py +1 -1
- scitex/{vis → fig}/model/__init__.py +1 -1
- scitex/{vis → fig}/model/_annotations.py +1 -1
- scitex/{vis → fig}/model/_axes.py +1 -1
- scitex/{vis → fig}/model/_figure.py +1 -1
- scitex/{vis → fig}/model/_guides.py +1 -1
- scitex/{vis → fig}/model/_plot.py +1 -1
- scitex/{vis → fig}/model/_styles.py +1 -1
- scitex/{vis → fig}/utils/__init__.py +1 -1
- scitex/io/__init__.py +10 -26
- scitex/io/_bundle.py +434 -0
- scitex/io/_flush.py +5 -2
- scitex/io/_load.py +98 -0
- scitex/io/_load_modules/_H5Explorer.py +5 -2
- scitex/io/_load_modules/_canvas.py +2 -2
- scitex/io/_load_modules/_image.py +3 -4
- scitex/io/_load_modules/_txt.py +4 -2
- scitex/io/_metadata.py +34 -324
- scitex/io/_metadata_modules/__init__.py +46 -0
- scitex/io/_metadata_modules/_embed.py +70 -0
- scitex/io/_metadata_modules/_read.py +64 -0
- scitex/io/_metadata_modules/_utils.py +79 -0
- scitex/io/_metadata_modules/embed_metadata_jpeg.py +74 -0
- scitex/io/_metadata_modules/embed_metadata_pdf.py +53 -0
- scitex/io/_metadata_modules/embed_metadata_png.py +26 -0
- scitex/io/_metadata_modules/embed_metadata_svg.py +62 -0
- scitex/io/_metadata_modules/read_metadata_jpeg.py +57 -0
- scitex/io/_metadata_modules/read_metadata_pdf.py +51 -0
- scitex/io/_metadata_modules/read_metadata_png.py +39 -0
- scitex/io/_metadata_modules/read_metadata_svg.py +44 -0
- scitex/io/_qr_utils.py +5 -3
- scitex/io/_save.py +548 -30
- scitex/io/_save_modules/_canvas.py +3 -3
- scitex/io/_save_modules/_image.py +5 -9
- scitex/io/_save_modules/_tex.py +7 -4
- scitex/io/utils/h5_to_zarr.py +11 -9
- scitex/msword/__init__.py +255 -0
- scitex/msword/profiles.py +357 -0
- scitex/msword/reader.py +753 -0
- scitex/msword/utils.py +289 -0
- scitex/msword/writer.py +362 -0
- scitex/plt/__init__.py +5 -2
- scitex/plt/_subplots/_AxesWrapper.py +6 -6
- scitex/plt/_subplots/_AxisWrapper.py +15 -9
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/__init__.py +36 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_labels.py +264 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_metadata.py +213 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_visual.py +128 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/__init__.py +59 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_base.py +34 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_scientific.py +593 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_statistical.py +654 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_stx_aliases.py +527 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_RawMatplotlibMixin.py +321 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/__init__.py +33 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_base.py +152 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +600 -0
- scitex/plt/_subplots/_AxisWrapperMixins/__init__.py +79 -5
- scitex/plt/_subplots/_FigWrapper.py +6 -6
- scitex/plt/_subplots/_SubplotsWrapper.py +28 -18
- scitex/plt/_subplots/_export_as_csv.py +35 -5
- scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +8 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_annotate.py +10 -21
- scitex/plt/_subplots/_export_as_csv_formatters/_format_eventplot.py +18 -7
- scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow2d.py +28 -12
- scitex/plt/_subplots/_export_as_csv_formatters/_format_matshow.py +10 -4
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_imshow.py +13 -1
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_kde.py +12 -2
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_scatter.py +10 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_quiver.py +10 -4
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_jointplot.py +18 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_lineplot.py +44 -36
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_pairplot.py +14 -2
- scitex/plt/_subplots/_export_as_csv_formatters/_format_streamplot.py +11 -5
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_bar.py +84 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_barh.py +85 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_conf_mat.py +14 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_contour.py +54 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_ecdf.py +14 -2
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_errorbar.py +120 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_heatmap.py +16 -6
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_image.py +29 -19
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_imshow.py +63 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_joyplot.py +22 -5
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_ci.py +18 -14
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_std.py +18 -14
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_median_iqr.py +18 -14
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_raster.py +10 -2
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter.py +51 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter_hist.py +18 -9
- scitex/plt/ax/_plot/_stx_ecdf.py +4 -2
- scitex/plt/gallery/_generate.py +421 -14
- scitex/plt/io/__init__.py +53 -0
- scitex/plt/io/_bundle.py +490 -0
- scitex/plt/io/_layered_bundle.py +1343 -0
- scitex/plt/styles/SCITEX_STYLE.yaml +26 -0
- scitex/plt/styles/__init__.py +14 -0
- scitex/plt/styles/presets.py +78 -0
- scitex/plt/utils/__init__.py +13 -1
- scitex/plt/utils/_collect_figure_metadata.py +10 -14
- scitex/plt/utils/_configure_mpl.py +6 -18
- scitex/plt/utils/_crop.py +32 -14
- scitex/plt/utils/_csv_column_naming.py +54 -0
- scitex/plt/utils/_figure_mm.py +116 -1
- scitex/plt/utils/_hitmap.py +1643 -0
- scitex/plt/utils/metadata/__init__.py +25 -0
- scitex/plt/utils/metadata/_core.py +9 -10
- scitex/plt/utils/metadata/_dimensions.py +6 -3
- scitex/plt/utils/metadata/_editable_export.py +405 -0
- scitex/plt/utils/metadata/_geometry_extraction.py +570 -0
- scitex/schema/__init__.py +109 -16
- scitex/schema/_canvas.py +1 -1
- scitex/schema/_plot.py +1015 -0
- scitex/schema/_stats.py +2 -2
- scitex/stats/__init__.py +117 -0
- scitex/stats/io/__init__.py +29 -0
- scitex/stats/io/_bundle.py +156 -0
- scitex/tex/__init__.py +4 -0
- scitex/tex/_export.py +890 -0
- {scitex-2.7.0.dist-info → scitex-2.7.3.dist-info}/METADATA +11 -1
- {scitex-2.7.0.dist-info → scitex-2.7.3.dist-info}/RECORD +238 -170
- scitex/io/memo.md +0 -2827
- scitex/plt/REQUESTS.md +0 -191
- scitex/plt/_subplots/TODO.md +0 -53
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin.py +0 -559
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin.py +0 -1609
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin.py +0 -447
- scitex/plt/templates/research-master/scitex/vis/gallery/area/fill_between.json +0 -110
- scitex/plt/templates/research-master/scitex/vis/gallery/area/fill_betweenx.json +0 -88
- scitex/plt/templates/research-master/scitex/vis/gallery/area/stx_fill_between.json +0 -103
- scitex/plt/templates/research-master/scitex/vis/gallery/area/stx_fillv.json +0 -106
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/bar.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/barh.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/boxplot.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_bar.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_barh.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_box.json +0 -83
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_boxplot.json +0 -93
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_violin.json +0 -91
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_violinplot.json +0 -91
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/violinplot.json +0 -91
- scitex/plt/templates/research-master/scitex/vis/gallery/contour/contour.json +0 -97
- scitex/plt/templates/research-master/scitex/vis/gallery/contour/contourf.json +0 -98
- scitex/plt/templates/research-master/scitex/vis/gallery/contour/stx_contour.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/distribution/hist.json +0 -101
- scitex/plt/templates/research-master/scitex/vis/gallery/distribution/hist2d.json +0 -96
- scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_ecdf.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_joyplot.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_kde.json +0 -93
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/imshow.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/matshow.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_conf_mat.json +0 -83
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_heatmap.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_image.json +0 -121
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_imshow.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/line/plot.json +0 -110
- scitex/plt/templates/research-master/scitex/vis/gallery/line/step.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/line/stx_line.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/line/stx_shaded_line.json +0 -96
- scitex/plt/templates/research-master/scitex/vis/gallery/scatter/hexbin.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/scatter/scatter.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/scatter/stem.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/scatter/stx_scatter.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/special/pie.json +0 -94
- scitex/plt/templates/research-master/scitex/vis/gallery/special/stx_raster.json +0 -109
- scitex/plt/templates/research-master/scitex/vis/gallery/special/stx_rectangle.json +0 -108
- scitex/plt/templates/research-master/scitex/vis/gallery/statistical/errorbar.json +0 -93
- scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_errorbar.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_mean_ci.json +0 -96
- scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_mean_std.json +0 -96
- scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_median_iqr.json +0 -96
- scitex/plt/templates/research-master/scitex/vis/gallery/vector/quiver.json +0 -99
- scitex/plt/templates/research-master/scitex/vis/gallery/vector/streamplot.json +0 -100
- scitex/vis/__init__.py +0 -177
- scitex/vis/editor/_edit.py +0 -390
- scitex/vis/editor/flask_editor/_bbox.py +0 -529
- scitex/vis/editor/flask_editor/_core.py +0 -168
- scitex/vis/editor/flask_editor/_renderer.py +0 -393
- scitex/vis/editor/flask_editor/templates/_html.py +0 -513
- scitex/vis/editor/flask_editor/templates/_scripts.py +0 -1261
- /scitex/{vis → fig}/README.md +0 -0
- /scitex/{vis → fig}/backend/__init__.py +0 -0
- /scitex/{vis → fig}/backend/_export.py +0 -0
- /scitex/{vis → fig}/backend/_render.py +0 -0
- /scitex/{vis → fig}/docs/CANVAS_ARCHITECTURE.md +0 -0
- /scitex/{vis → fig}/editor/__init__.py +0 -0
- /scitex/{vis → fig}/editor/_dearpygui_editor.py +0 -0
- /scitex/{vis → fig}/editor/_flask_editor.py +0 -0
- /scitex/{vis → fig}/editor/_mpl_editor.py +0 -0
- /scitex/{vis → fig}/editor/_tkinter_editor.py +0 -0
- /scitex/{vis → fig}/editor/flask_editor/__init__.py +0 -0
- /scitex/{vis → fig}/editor/flask_editor/_utils.py +0 -0
- /scitex/{vis → fig}/io/_directory.py +0 -0
- /scitex/{vis → fig}/model/_plot_types.py +0 -0
- /scitex/{vis → fig}/utils/_defaults.py +0 -0
- /scitex/{vis → fig}/utils/_validate.py +0 -0
- {scitex-2.7.0.dist-info → scitex-2.7.3.dist-info}/WHEEL +0 -0
- {scitex-2.7.0.dist-info → scitex-2.7.3.dist-info}/entry_points.txt +0 -0
- {scitex-2.7.0.dist-info → scitex-2.7.3.dist-info}/licenses/LICENSE +0 -0
scitex/__init__.py
CHANGED
|
@@ -140,7 +140,7 @@ reproduce = _LazyModule("reproduce")
|
|
|
140
140
|
rng = _LazyModule("rng")
|
|
141
141
|
scholar = _LazyModule("scholar")
|
|
142
142
|
writer = _LazyModule("writer")
|
|
143
|
-
|
|
143
|
+
fig = _LazyModule("fig")
|
|
144
144
|
resource = _LazyModule("resource")
|
|
145
145
|
tex = _LazyModule("tex")
|
|
146
146
|
linalg = _LazyModule("linalg")
|
|
@@ -161,6 +161,8 @@ capture = _LazyModule("capture")
|
|
|
161
161
|
template = _LazyModule("template")
|
|
162
162
|
cloud = _LazyModule("cloud")
|
|
163
163
|
config = _LazyModule("config")
|
|
164
|
+
audio = _LazyModule("audio")
|
|
165
|
+
msword = _LazyModule("msword")
|
|
164
166
|
|
|
165
167
|
# Centralized path configuration - eager loaded for convenience
|
|
166
168
|
# Usage: scitex.PATHS.logs, scitex.PATHS.cache, etc.
|
|
@@ -207,7 +209,7 @@ __all__ = [
|
|
|
207
209
|
"reproduce",
|
|
208
210
|
"scholar",
|
|
209
211
|
"writer",
|
|
210
|
-
"
|
|
212
|
+
"fig",
|
|
211
213
|
"resource",
|
|
212
214
|
"tex",
|
|
213
215
|
"linalg",
|
|
@@ -221,6 +223,8 @@ __all__ = [
|
|
|
221
223
|
"gists",
|
|
222
224
|
"cloud",
|
|
223
225
|
"config",
|
|
226
|
+
"audio",
|
|
227
|
+
"msword",
|
|
224
228
|
"PATHS",
|
|
225
229
|
"INJECTED",
|
|
226
230
|
]
|
scitex/__version__.py
CHANGED
scitex/audio/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# SciTeX Audio
|
|
2
|
+
|
|
3
|
+
Text-to-Speech with automatic fallback: pyttsx3 -> gtts -> elevenlabs
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
import scitex
|
|
9
|
+
|
|
10
|
+
# Basic
|
|
11
|
+
scitex.audio.speak("Hello!")
|
|
12
|
+
|
|
13
|
+
# Faster speech (rate in words per minute)
|
|
14
|
+
scitex.audio.speak("Hello!", rate=200)
|
|
15
|
+
|
|
16
|
+
# Specific backend
|
|
17
|
+
scitex.audio.speak("Hello", backend="pyttsx3")
|
|
18
|
+
|
|
19
|
+
# Stop speech
|
|
20
|
+
scitex.audio.stop_speech()
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## MCP Server
|
|
24
|
+
|
|
25
|
+
Add to `~/.claude.json`:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"mcpServers": {
|
|
30
|
+
"scitex-audio": {
|
|
31
|
+
"command": "/path/to/python",
|
|
32
|
+
"args": ["-m", "scitex.audio", "--mcp"]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Tools
|
|
39
|
+
|
|
40
|
+
| Tool | Description |
|
|
41
|
+
|------|-------------|
|
|
42
|
+
| `speak` | Text to speech (supports `rate` param for speed) |
|
|
43
|
+
| `generate_audio` | Save audio to file |
|
|
44
|
+
| `list_backends` | Show available backends |
|
|
45
|
+
|
|
46
|
+
## Backends
|
|
47
|
+
|
|
48
|
+
| Backend | Cost | Internet | Install |
|
|
49
|
+
|---------|------|----------|---------|
|
|
50
|
+
| pyttsx3 | Free | No | `pip install pyttsx3` + `apt install espeak-ng` |
|
|
51
|
+
| gtts | Free | Yes | `pip install gTTS` |
|
|
52
|
+
| elevenlabs | Paid | Yes | `pip install elevenlabs` + API key |
|
scitex/audio/__init__.py
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Timestamp: "2025-12-11 (ywatanabe)"
|
|
4
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/audio/__init__.py
|
|
5
|
+
# ----------------------------------------
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
SciTeX Audio Module - Text-to-Speech with Multiple Backends
|
|
9
|
+
|
|
10
|
+
Fallback order: pyttsx3 -> gtts -> elevenlabs
|
|
11
|
+
|
|
12
|
+
Backends:
|
|
13
|
+
- pyttsx3: System TTS (offline, free, uses espeak/SAPI5)
|
|
14
|
+
- gtts: Google TTS (free, requires internet)
|
|
15
|
+
- elevenlabs: ElevenLabs (paid, high quality)
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
import scitex
|
|
19
|
+
|
|
20
|
+
# Auto-select with fallback (pyttsx3 -> gtts -> elevenlabs)
|
|
21
|
+
scitex.audio.speak("Hello, world!")
|
|
22
|
+
|
|
23
|
+
# Specify backend
|
|
24
|
+
scitex.audio.speak("Hello", backend="gtts")
|
|
25
|
+
|
|
26
|
+
# Use TTS class directly
|
|
27
|
+
from scitex.audio import GoogleTTS, ElevenLabsTTS, SystemTTS
|
|
28
|
+
tts = SystemTTS()
|
|
29
|
+
tts.speak("Hello!")
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
import subprocess
|
|
33
|
+
from typing import List, Optional
|
|
34
|
+
|
|
35
|
+
# Import from engines subpackage
|
|
36
|
+
from .engines import (
|
|
37
|
+
BaseTTS,
|
|
38
|
+
TTSBackend,
|
|
39
|
+
SystemTTS,
|
|
40
|
+
GoogleTTS,
|
|
41
|
+
ElevenLabsTTS,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def stop_speech() -> None:
|
|
46
|
+
"""Stop any currently playing speech by killing espeak processes."""
|
|
47
|
+
try:
|
|
48
|
+
subprocess.run(["pkill", "-9", "espeak"], capture_output=True)
|
|
49
|
+
except Exception:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def check_wsl_audio() -> dict:
|
|
54
|
+
"""Check WSL audio status and connectivity.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
dict with keys:
|
|
58
|
+
- is_wsl: bool - whether running in WSL
|
|
59
|
+
- wslg_available: bool - whether WSLg is available
|
|
60
|
+
- pulse_server_exists: bool - whether PulseServer socket exists
|
|
61
|
+
- pulse_connected: bool - whether PulseAudio connection works
|
|
62
|
+
- windows_fallback_available: bool - whether Windows fallback is available
|
|
63
|
+
- recommended: str - recommended playback method
|
|
64
|
+
"""
|
|
65
|
+
import os
|
|
66
|
+
import shutil
|
|
67
|
+
|
|
68
|
+
result = {
|
|
69
|
+
"is_wsl": False,
|
|
70
|
+
"wslg_available": False,
|
|
71
|
+
"pulse_server_exists": False,
|
|
72
|
+
"pulse_connected": False,
|
|
73
|
+
"windows_fallback_available": False,
|
|
74
|
+
"recommended": "linux",
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Check if in WSL
|
|
78
|
+
if os.path.exists("/mnt/c/Windows"):
|
|
79
|
+
result["is_wsl"] = True
|
|
80
|
+
|
|
81
|
+
# Check WSLg
|
|
82
|
+
if os.path.exists("/mnt/wslg"):
|
|
83
|
+
result["wslg_available"] = True
|
|
84
|
+
|
|
85
|
+
# Check PulseServer socket
|
|
86
|
+
if os.path.exists("/mnt/wslg/PulseServer"):
|
|
87
|
+
result["pulse_server_exists"] = True
|
|
88
|
+
|
|
89
|
+
# Try to connect to PulseAudio
|
|
90
|
+
try:
|
|
91
|
+
env = os.environ.copy()
|
|
92
|
+
env["PULSE_SERVER"] = "unix:/mnt/wslg/PulseServer"
|
|
93
|
+
proc = subprocess.run(
|
|
94
|
+
["pactl", "info"],
|
|
95
|
+
capture_output=True,
|
|
96
|
+
timeout=5,
|
|
97
|
+
env=env,
|
|
98
|
+
)
|
|
99
|
+
if proc.returncode == 0:
|
|
100
|
+
result["pulse_connected"] = True
|
|
101
|
+
except Exception:
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
# Check Windows fallback
|
|
105
|
+
if shutil.which("powershell.exe"):
|
|
106
|
+
result["windows_fallback_available"] = True
|
|
107
|
+
|
|
108
|
+
# Determine recommendation
|
|
109
|
+
if result["pulse_connected"]:
|
|
110
|
+
result["recommended"] = "linux"
|
|
111
|
+
elif result["windows_fallback_available"]:
|
|
112
|
+
result["recommended"] = "windows"
|
|
113
|
+
else:
|
|
114
|
+
result["recommended"] = "none"
|
|
115
|
+
else:
|
|
116
|
+
# Native Linux
|
|
117
|
+
result["recommended"] = "linux"
|
|
118
|
+
|
|
119
|
+
return result
|
|
120
|
+
|
|
121
|
+
# Keep legacy TTS import for backwards compatibility
|
|
122
|
+
from ._tts import TTS
|
|
123
|
+
|
|
124
|
+
__all__ = [
|
|
125
|
+
"speak",
|
|
126
|
+
"stop_speech",
|
|
127
|
+
"check_wsl_audio",
|
|
128
|
+
"TTS",
|
|
129
|
+
"GoogleTTS",
|
|
130
|
+
"ElevenLabsTTS",
|
|
131
|
+
"SystemTTS",
|
|
132
|
+
"BaseTTS",
|
|
133
|
+
"TTSBackend",
|
|
134
|
+
"get_tts",
|
|
135
|
+
"available_backends",
|
|
136
|
+
"start_mcp_server",
|
|
137
|
+
"FALLBACK_ORDER",
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
# Fallback order: pyttsx3 (offline, free) -> gtts (free) -> elevenlabs (paid)
|
|
141
|
+
# FALLBACK_ORDER = ["pyttsx3", "gtts", "elevenlabs"]
|
|
142
|
+
FALLBACK_ORDER = ["gtts", "pyttsx3", "elevenlabs"]
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def available_backends() -> List[str]:
|
|
146
|
+
"""Return list of available TTS backends in fallback order."""
|
|
147
|
+
backends = []
|
|
148
|
+
|
|
149
|
+
# Check pyttsx3 (offline)
|
|
150
|
+
if SystemTTS:
|
|
151
|
+
try:
|
|
152
|
+
import pyttsx3
|
|
153
|
+
# Try to init to check if espeak is available
|
|
154
|
+
engine = pyttsx3.init()
|
|
155
|
+
engine.stop()
|
|
156
|
+
backends.append("pyttsx3")
|
|
157
|
+
except Exception:
|
|
158
|
+
pass
|
|
159
|
+
|
|
160
|
+
# Check gTTS (requires internet, but no API key)
|
|
161
|
+
if GoogleTTS:
|
|
162
|
+
backends.append("gtts")
|
|
163
|
+
|
|
164
|
+
# Check ElevenLabs (requires API key)
|
|
165
|
+
if ElevenLabsTTS:
|
|
166
|
+
import os
|
|
167
|
+
if os.environ.get("ELEVENLABS_API_KEY"):
|
|
168
|
+
backends.append("elevenlabs")
|
|
169
|
+
|
|
170
|
+
return backends
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def get_tts(backend: Optional[str] = None, **kwargs) -> BaseTTS:
|
|
174
|
+
"""Get a TTS instance for the specified backend.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
backend: Backend name ('pyttsx3', 'gtts', 'elevenlabs').
|
|
178
|
+
Auto-selects with fallback if None.
|
|
179
|
+
**kwargs: Backend-specific options.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
TTS instance.
|
|
183
|
+
|
|
184
|
+
Raises:
|
|
185
|
+
ValueError: If no backends available or backend not found.
|
|
186
|
+
"""
|
|
187
|
+
backends = available_backends()
|
|
188
|
+
|
|
189
|
+
if not backends:
|
|
190
|
+
raise ValueError(
|
|
191
|
+
"No TTS backends available. Install one of:\n"
|
|
192
|
+
" pip install pyttsx3 # System TTS (offline, free)\n"
|
|
193
|
+
" + Linux: sudo apt install espeak-ng libespeak1\n"
|
|
194
|
+
" pip install gTTS # Google TTS (free, needs internet)\n"
|
|
195
|
+
" pip install elevenlabs # ElevenLabs (paid, best quality)"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
if backend is None:
|
|
199
|
+
# Use fallback order: pyttsx3 -> gtts -> elevenlabs
|
|
200
|
+
for b in FALLBACK_ORDER:
|
|
201
|
+
if b in backends:
|
|
202
|
+
backend = b
|
|
203
|
+
break
|
|
204
|
+
|
|
205
|
+
if backend == "pyttsx3" and SystemTTS and "pyttsx3" in backends:
|
|
206
|
+
return SystemTTS(**kwargs)
|
|
207
|
+
elif backend == "gtts" and GoogleTTS:
|
|
208
|
+
return GoogleTTS(**kwargs)
|
|
209
|
+
elif backend == "elevenlabs" and ElevenLabsTTS:
|
|
210
|
+
return ElevenLabsTTS(**kwargs)
|
|
211
|
+
else:
|
|
212
|
+
raise ValueError(
|
|
213
|
+
f"Backend '{backend}' not available. "
|
|
214
|
+
f"Available: {backends}"
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _try_speak_with_fallback(
|
|
219
|
+
text: str,
|
|
220
|
+
voice: Optional[str] = None,
|
|
221
|
+
play: bool = True,
|
|
222
|
+
output_path: Optional[str] = None,
|
|
223
|
+
**kwargs,
|
|
224
|
+
) -> tuple:
|
|
225
|
+
"""Try to speak with fallback through backends.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
(result, backend_used, error_log)
|
|
229
|
+
"""
|
|
230
|
+
backends = available_backends()
|
|
231
|
+
errors = []
|
|
232
|
+
|
|
233
|
+
for backend in FALLBACK_ORDER:
|
|
234
|
+
if backend not in backends:
|
|
235
|
+
continue
|
|
236
|
+
|
|
237
|
+
try:
|
|
238
|
+
tts = get_tts(backend, **kwargs)
|
|
239
|
+
result = tts.speak(
|
|
240
|
+
text=text,
|
|
241
|
+
voice=voice,
|
|
242
|
+
play=play,
|
|
243
|
+
output_path=output_path,
|
|
244
|
+
)
|
|
245
|
+
return (result, backend, errors)
|
|
246
|
+
except Exception as e:
|
|
247
|
+
errors.append(f"{backend}: {str(e)}")
|
|
248
|
+
continue
|
|
249
|
+
|
|
250
|
+
return (None, None, errors)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# Cache for default TTS instance
|
|
254
|
+
_default_tts: Optional[BaseTTS] = None
|
|
255
|
+
_default_backend: Optional[str] = None
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def speak(
|
|
259
|
+
text: str,
|
|
260
|
+
backend: Optional[str] = None,
|
|
261
|
+
voice: Optional[str] = None,
|
|
262
|
+
play: bool = True,
|
|
263
|
+
output_path: Optional[str] = None,
|
|
264
|
+
fallback: bool = True,
|
|
265
|
+
rate: Optional[int] = None,
|
|
266
|
+
speed: Optional[float] = None,
|
|
267
|
+
**kwargs,
|
|
268
|
+
) -> Optional[str]:
|
|
269
|
+
"""Convert text to speech with automatic fallback.
|
|
270
|
+
|
|
271
|
+
Fallback order: pyttsx3 -> gtts -> elevenlabs
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
text: Text to speak.
|
|
275
|
+
backend: TTS backend ('pyttsx3', 'gtts', 'elevenlabs').
|
|
276
|
+
Auto-selects with fallback if None.
|
|
277
|
+
voice: Voice name, ID, or language code.
|
|
278
|
+
play: Whether to play the audio.
|
|
279
|
+
output_path: Path to save audio file.
|
|
280
|
+
fallback: If True, try next backend on failure.
|
|
281
|
+
rate: Speech rate in words per minute (pyttsx3 only, default 150).
|
|
282
|
+
speed: Speed multiplier for gtts (1.0=normal, >1.0=faster, <1.0=slower).
|
|
283
|
+
**kwargs: Additional backend options.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
Path to audio file if output_path specified, else None.
|
|
287
|
+
|
|
288
|
+
Examples:
|
|
289
|
+
import scitex
|
|
290
|
+
|
|
291
|
+
# Simple (auto-selects with fallback)
|
|
292
|
+
scitex.audio.speak("Hello!")
|
|
293
|
+
|
|
294
|
+
# Faster speech (pyttsx3)
|
|
295
|
+
scitex.audio.speak("Hello", rate=200)
|
|
296
|
+
|
|
297
|
+
# Faster speech (gtts with pydub)
|
|
298
|
+
scitex.audio.speak("Hello", backend="gtts", speed=1.5)
|
|
299
|
+
|
|
300
|
+
# Specific backend (no fallback)
|
|
301
|
+
scitex.audio.speak("Hello", backend="pyttsx3", fallback=False)
|
|
302
|
+
|
|
303
|
+
# Different language (gTTS)
|
|
304
|
+
scitex.audio.speak("Bonjour", backend="gtts", voice="fr")
|
|
305
|
+
|
|
306
|
+
# Save to file
|
|
307
|
+
scitex.audio.speak("Test", output_path="/tmp/test.mp3")
|
|
308
|
+
"""
|
|
309
|
+
global _default_tts, _default_backend
|
|
310
|
+
|
|
311
|
+
# Stop any previously running speech first
|
|
312
|
+
stop_speech()
|
|
313
|
+
|
|
314
|
+
# Pass rate to kwargs for pyttsx3
|
|
315
|
+
if rate is not None:
|
|
316
|
+
kwargs["rate"] = rate
|
|
317
|
+
|
|
318
|
+
# Pass speed to kwargs for gtts
|
|
319
|
+
if speed is not None:
|
|
320
|
+
kwargs["speed"] = speed
|
|
321
|
+
|
|
322
|
+
# If specific backend requested without fallback
|
|
323
|
+
if backend and not fallback:
|
|
324
|
+
tts = get_tts(backend, **kwargs)
|
|
325
|
+
result = tts.speak(
|
|
326
|
+
text=text,
|
|
327
|
+
voice=voice,
|
|
328
|
+
play=play,
|
|
329
|
+
output_path=output_path,
|
|
330
|
+
)
|
|
331
|
+
return str(result) if result else None
|
|
332
|
+
|
|
333
|
+
# Use fallback logic
|
|
334
|
+
if fallback and backend is None:
|
|
335
|
+
result, used_backend, errors = _try_speak_with_fallback(
|
|
336
|
+
text=text,
|
|
337
|
+
voice=voice,
|
|
338
|
+
play=play,
|
|
339
|
+
output_path=output_path,
|
|
340
|
+
**kwargs,
|
|
341
|
+
)
|
|
342
|
+
if result is None and errors:
|
|
343
|
+
raise RuntimeError(
|
|
344
|
+
f"All TTS backends failed:\n" + "\n".join(errors)
|
|
345
|
+
)
|
|
346
|
+
return str(result) if result else None
|
|
347
|
+
|
|
348
|
+
# Specific backend with fallback enabled
|
|
349
|
+
try:
|
|
350
|
+
tts = get_tts(backend, **kwargs)
|
|
351
|
+
result = tts.speak(
|
|
352
|
+
text=text,
|
|
353
|
+
voice=voice,
|
|
354
|
+
play=play,
|
|
355
|
+
output_path=output_path,
|
|
356
|
+
)
|
|
357
|
+
return str(result) if result else None
|
|
358
|
+
except Exception as e:
|
|
359
|
+
if fallback:
|
|
360
|
+
# Try other backends
|
|
361
|
+
result, used_backend, errors = _try_speak_with_fallback(
|
|
362
|
+
text=text,
|
|
363
|
+
voice=voice,
|
|
364
|
+
play=play,
|
|
365
|
+
output_path=output_path,
|
|
366
|
+
**kwargs,
|
|
367
|
+
)
|
|
368
|
+
if result is None:
|
|
369
|
+
raise RuntimeError(
|
|
370
|
+
f"Primary backend '{backend}' failed: {e}\n"
|
|
371
|
+
f"Fallback errors:\n" + "\n".join(errors)
|
|
372
|
+
)
|
|
373
|
+
return str(result) if result else None
|
|
374
|
+
raise
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def start_mcp_server():
|
|
378
|
+
"""Start the MCP server for audio."""
|
|
379
|
+
import asyncio
|
|
380
|
+
from .mcp_server import main
|
|
381
|
+
asyncio.run(main())
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
# EOF
|
scitex/audio/__main__.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Timestamp: "2025-12-11 (ywatanabe)"
|
|
4
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/audio/__main__.py
|
|
5
|
+
# ----------------------------------------
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
CLI entry point for SciTeX Audio.
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
python -m scitex.audio --mcp # Start MCP server
|
|
12
|
+
python -m scitex.audio speak "Hello" # Quick TTS with fallback
|
|
13
|
+
python -m scitex.audio --help # Show help
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import asyncio
|
|
18
|
+
import sys
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def main():
|
|
22
|
+
parser = argparse.ArgumentParser(
|
|
23
|
+
description="SciTeX Audio - Text-to-Speech with fallback (pyttsx3 -> gtts -> elevenlabs)"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Global options
|
|
27
|
+
parser.add_argument(
|
|
28
|
+
"--mcp", action="store_true",
|
|
29
|
+
help="Start MCP server (for Claude Code integration)"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
subparsers = parser.add_subparsers(dest="command", help="Commands")
|
|
33
|
+
|
|
34
|
+
# Speak command
|
|
35
|
+
speak_parser = subparsers.add_parser("speak", help="Quick text-to-speech")
|
|
36
|
+
speak_parser.add_argument("text", help="Text to speak")
|
|
37
|
+
speak_parser.add_argument(
|
|
38
|
+
"-b", "--backend",
|
|
39
|
+
choices=["pyttsx3", "gtts", "elevenlabs"],
|
|
40
|
+
help="TTS backend (auto-selects with fallback if not specified)"
|
|
41
|
+
)
|
|
42
|
+
speak_parser.add_argument(
|
|
43
|
+
"-v", "--voice", help="Voice name or language code"
|
|
44
|
+
)
|
|
45
|
+
speak_parser.add_argument(
|
|
46
|
+
"-o", "--output", help="Save to file"
|
|
47
|
+
)
|
|
48
|
+
speak_parser.add_argument(
|
|
49
|
+
"--no-play", action="store_true", help="Don't play audio"
|
|
50
|
+
)
|
|
51
|
+
speak_parser.add_argument(
|
|
52
|
+
"--no-fallback", action="store_true", help="Disable fallback"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# List backends
|
|
56
|
+
backends_parser = subparsers.add_parser("backends", help="List available backends")
|
|
57
|
+
|
|
58
|
+
# List voices
|
|
59
|
+
voices_parser = subparsers.add_parser("voices", help="List available voices")
|
|
60
|
+
voices_parser.add_argument(
|
|
61
|
+
"-b", "--backend",
|
|
62
|
+
choices=["pyttsx3", "gtts", "elevenlabs"],
|
|
63
|
+
help="Backend to list voices for"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
args = parser.parse_args()
|
|
67
|
+
|
|
68
|
+
# MCP server mode
|
|
69
|
+
if args.mcp:
|
|
70
|
+
from .mcp_server import main as server_main
|
|
71
|
+
asyncio.run(server_main())
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
if args.command == "speak":
|
|
75
|
+
from . import speak
|
|
76
|
+
|
|
77
|
+
speak(
|
|
78
|
+
text=args.text,
|
|
79
|
+
backend=args.backend,
|
|
80
|
+
voice=args.voice,
|
|
81
|
+
play=not args.no_play,
|
|
82
|
+
output_path=args.output,
|
|
83
|
+
fallback=not args.no_fallback,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
elif args.command == "backends":
|
|
87
|
+
from . import available_backends, FALLBACK_ORDER
|
|
88
|
+
|
|
89
|
+
backends = available_backends()
|
|
90
|
+
print("Available TTS backends (in fallback order):")
|
|
91
|
+
for b in FALLBACK_ORDER:
|
|
92
|
+
status = "available" if b in backends else "not available"
|
|
93
|
+
desc = {
|
|
94
|
+
"pyttsx3": "System TTS (offline, free)",
|
|
95
|
+
"gtts": "Google TTS (free, needs internet)",
|
|
96
|
+
"elevenlabs": "ElevenLabs (paid, high quality)",
|
|
97
|
+
}
|
|
98
|
+
marker = "[*]" if b in backends else "[ ]"
|
|
99
|
+
print(f" {marker} {b}: {desc.get(b, '')} - {status}")
|
|
100
|
+
|
|
101
|
+
elif args.command == "voices":
|
|
102
|
+
from . import get_tts, available_backends
|
|
103
|
+
|
|
104
|
+
backend = args.backend
|
|
105
|
+
if not backend:
|
|
106
|
+
backends = available_backends()
|
|
107
|
+
backend = backends[0] if backends else None
|
|
108
|
+
|
|
109
|
+
if backend:
|
|
110
|
+
try:
|
|
111
|
+
tts = get_tts(backend)
|
|
112
|
+
voices = tts.get_voices()
|
|
113
|
+
print(f"Voices for {backend}:")
|
|
114
|
+
for v in voices:
|
|
115
|
+
print(f" {v.get('name', 'unknown')}: {v.get('id', '')}")
|
|
116
|
+
except Exception as e:
|
|
117
|
+
print(f"Error: {e}")
|
|
118
|
+
else:
|
|
119
|
+
print("No backends available")
|
|
120
|
+
|
|
121
|
+
else:
|
|
122
|
+
# Default: show help
|
|
123
|
+
parser.print_help()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
if __name__ == "__main__":
|
|
127
|
+
main()
|
|
128
|
+
|
|
129
|
+
# EOF
|