scitex 2.7.3__py3-none-any.whl → 2.10.0__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 +15 -7
- scitex/__version__.py +1 -2
- scitex/_install_guide.py +250 -0
- scitex/_optional_deps.py +206 -39
- scitex/ai/_gen_ai/_Groq.py +2 -4
- scitex/ai/_gen_ai/_OpenAI.py +5 -2
- scitex/ai/_gen_ai/_Perplexity.py +20 -6
- scitex/audio/__init__.py +24 -15
- scitex/audio/_cross_process_lock.py +139 -0
- scitex/audio/_mcp_handlers.py +256 -0
- scitex/audio/_mcp_tool_schemas.py +203 -0
- scitex/audio/engines/elevenlabs_engine.py +5 -2
- scitex/audio/mcp_server.py +98 -457
- scitex/bridge/__init__.py +30 -19
- scitex/bridge/_figrecipe.py +245 -0
- scitex/bridge/_helpers.py +2 -1
- scitex/bridge/_plt_vis.py +23 -10
- scitex/bridge/_stats_plt.py +18 -5
- scitex/bridge/_stats_vis.py +16 -2
- scitex/browser/__init__.py +84 -44
- scitex/browser/automation/__init__.py +5 -1
- scitex/browser/core/BrowserMixin.py +17 -4
- scitex/browser/core/__init__.py +11 -2
- scitex/browser/remote/CaptchaHandler.py +1 -1
- scitex/browser/remote/ZenRowsAPIClient.py +1 -1
- scitex/capture/grid.py +487 -0
- scitex/capture/mcp_handlers.py +401 -0
- scitex/capture/mcp_tool_defs.py +192 -0
- scitex/capture/mcp_tools.py +241 -0
- scitex/capture/mcp_utils.py +30 -0
- scitex/cli/convert.py +421 -0
- scitex/cli/main.py +6 -4
- scitex/datetime/__init__.py +46 -0
- scitex/datetime/_linspace.py +100 -0
- scitex/datetime/_normalize_timestamp.py +306 -0
- scitex/db/_delete_duplicates.py +4 -4
- scitex/db/_sqlite3/_delete_duplicates.py +11 -2
- scitex/dev/plt/__init__.py +61 -62
- scitex/dev/plt/demo_plotters/__init__.py +0 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axhline.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axhspan.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axvline.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axvspan.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_bar.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_barh.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_boxplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_contour.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_contourf.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_errorbar.py +30 -0
- scitex/dev/plt/demo_plotters/plot_mpl_eventplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_fill.py +30 -0
- scitex/dev/plt/demo_plotters/plot_mpl_fill_between.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hexbin.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hist.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hist2d.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_imshow.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_pcolormesh.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_pie.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_plot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_quiver.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_scatter.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_stackplot.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_stem.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_step.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_violinplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_sns_barplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_boxplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_heatmap.py +28 -0
- scitex/dev/plt/demo_plotters/plot_sns_histplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_kdeplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_lineplot.py +31 -0
- scitex/dev/plt/demo_plotters/plot_sns_scatterplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_stripplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_swarmplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_violinplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_bar.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_barh.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_box.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_boxplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_conf_mat.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_contour.py +31 -0
- scitex/dev/plt/demo_plotters/plot_stx_ecdf.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_errorbar.py +30 -0
- scitex/dev/plt/demo_plotters/plot_stx_fill_between.py +31 -0
- scitex/dev/plt/demo_plotters/plot_stx_fillv.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_heatmap.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_image.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_imshow.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_joyplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_kde.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_line.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_mean_ci.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_mean_std.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_median_iqr.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_raster.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_rectangle.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_scatter.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_shaded_line.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_violin.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_violinplot.py +28 -0
- scitex/dev/plt/mpl/get_dir_ax.py +46 -0
- scitex/dev/plt/mpl/get_signatures.py +176 -0
- scitex/dev/plt/mpl/get_signatures_details.py +522 -0
- scitex/dev/plt/plot_mpl_axhline.py +0 -0
- scitex/dev/plt/plot_mpl_axhspan.py +0 -0
- scitex/dev/plt/plot_mpl_axvline.py +0 -0
- scitex/dev/plt/plot_mpl_axvspan.py +0 -0
- scitex/dev/plt/plot_mpl_bar.py +0 -0
- scitex/dev/plt/plot_mpl_barh.py +0 -0
- scitex/dev/plt/plot_mpl_boxplot.py +0 -0
- scitex/dev/plt/plot_mpl_contour.py +0 -0
- scitex/dev/plt/plot_mpl_contourf.py +0 -0
- scitex/dev/plt/plot_mpl_errorbar.py +0 -0
- scitex/dev/plt/plot_mpl_eventplot.py +0 -0
- scitex/dev/plt/plot_mpl_fill.py +0 -0
- scitex/dev/plt/plot_mpl_fill_between.py +0 -0
- scitex/dev/plt/plot_mpl_hexbin.py +0 -0
- scitex/dev/plt/plot_mpl_hist.py +0 -0
- scitex/dev/plt/plot_mpl_hist2d.py +0 -0
- scitex/dev/plt/plot_mpl_imshow.py +0 -0
- scitex/dev/plt/plot_mpl_pcolormesh.py +0 -0
- scitex/dev/plt/plot_mpl_pie.py +0 -0
- scitex/dev/plt/plot_mpl_plot.py +0 -0
- scitex/dev/plt/plot_mpl_quiver.py +0 -0
- scitex/dev/plt/plot_mpl_scatter.py +0 -0
- scitex/dev/plt/plot_mpl_stackplot.py +0 -0
- scitex/dev/plt/plot_mpl_stem.py +0 -0
- scitex/dev/plt/plot_mpl_step.py +0 -0
- scitex/dev/plt/plot_mpl_violinplot.py +0 -0
- scitex/dev/plt/plot_sns_barplot.py +0 -0
- scitex/dev/plt/plot_sns_boxplot.py +0 -0
- scitex/dev/plt/plot_sns_heatmap.py +0 -0
- scitex/dev/plt/plot_sns_histplot.py +0 -0
- scitex/dev/plt/plot_sns_kdeplot.py +0 -0
- scitex/dev/plt/plot_sns_lineplot.py +0 -0
- scitex/dev/plt/plot_sns_scatterplot.py +0 -0
- scitex/dev/plt/plot_sns_stripplot.py +0 -0
- scitex/dev/plt/plot_sns_swarmplot.py +0 -0
- scitex/dev/plt/plot_sns_violinplot.py +0 -0
- scitex/dev/plt/plot_stx_bar.py +0 -0
- scitex/dev/plt/plot_stx_barh.py +0 -0
- scitex/dev/plt/plot_stx_box.py +0 -0
- scitex/dev/plt/plot_stx_boxplot.py +0 -0
- scitex/dev/plt/plot_stx_conf_mat.py +0 -0
- scitex/dev/plt/plot_stx_contour.py +0 -0
- scitex/dev/plt/plot_stx_ecdf.py +0 -0
- scitex/dev/plt/plot_stx_errorbar.py +0 -0
- scitex/dev/plt/plot_stx_fill_between.py +0 -0
- scitex/dev/plt/plot_stx_fillv.py +0 -0
- scitex/dev/plt/plot_stx_heatmap.py +0 -0
- scitex/dev/plt/plot_stx_image.py +0 -0
- scitex/dev/plt/plot_stx_imshow.py +0 -0
- scitex/dev/plt/plot_stx_joyplot.py +0 -0
- scitex/dev/plt/plot_stx_kde.py +0 -0
- scitex/dev/plt/plot_stx_line.py +0 -0
- scitex/dev/plt/plot_stx_mean_ci.py +0 -0
- scitex/dev/plt/plot_stx_mean_std.py +0 -0
- scitex/dev/plt/plot_stx_median_iqr.py +0 -0
- scitex/dev/plt/plot_stx_raster.py +0 -0
- scitex/dev/plt/plot_stx_rectangle.py +0 -0
- scitex/dev/plt/plot_stx_scatter.py +0 -0
- scitex/dev/plt/plot_stx_shaded_line.py +0 -0
- scitex/dev/plt/plot_stx_violin.py +0 -0
- scitex/dev/plt/plot_stx_violinplot.py +0 -0
- scitex/diagram/README.md +197 -0
- scitex/diagram/__init__.py +48 -0
- scitex/diagram/_compile.py +312 -0
- scitex/diagram/_diagram.py +355 -0
- scitex/diagram/_presets.py +173 -0
- scitex/diagram/_schema.py +182 -0
- scitex/diagram/_split.py +278 -0
- scitex/dict/_pop_keys.py +1 -7
- scitex/dsp/__init__.py +15 -10
- scitex/dsp/add_noise.py +5 -2
- scitex/dsp/example.py +35 -22
- scitex/dsp/filt.py +8 -3
- scitex/dsp/reference.py +3 -2
- scitex/dsp/utils/__init__.py +2 -1
- scitex/dsp/utils/_differential_bandpass_filters.py +14 -4
- scitex/dt/__init__.py +39 -2
- scitex/errors.py +82 -521
- scitex/fig/__init__.py +4 -4
- scitex/fig/editor/__init__.py +5 -2
- scitex/fig/editor/_dearpygui_editor.py +1 -1
- scitex/fig/editor/_mpl_editor.py +1 -1
- scitex/fig/editor/_qt_editor.py +1 -1
- scitex/fig/editor/_tkinter_editor.py +1 -1
- scitex/fig/editor/edit/__init__.py +50 -0
- scitex/fig/editor/edit/backend_detector.py +109 -0
- scitex/fig/editor/edit/bundle_resolver.py +240 -0
- scitex/fig/editor/edit/editor_launcher.py +239 -0
- scitex/fig/editor/edit/manual_handler.py +53 -0
- scitex/fig/editor/edit/panel_loader.py +232 -0
- scitex/fig/editor/edit/path_resolver.py +67 -0
- scitex/fig/editor/flask_editor/_bbox.py +23 -0
- scitex/fig/editor/flask_editor/_core.py +908 -103
- scitex/fig/editor/flask_editor/_renderer.py +74 -0
- scitex/fig/editor/flask_editor/static/css/base/reset.css +41 -0
- scitex/fig/editor/flask_editor/static/css/base/typography.css +16 -0
- scitex/fig/editor/flask_editor/static/css/base/variables.css +85 -0
- scitex/fig/editor/flask_editor/static/css/components/buttons.css +217 -0
- scitex/fig/editor/flask_editor/static/css/components/context-menu.css +93 -0
- scitex/fig/editor/flask_editor/static/css/components/dropdown.css +57 -0
- scitex/fig/editor/flask_editor/static/css/components/forms.css +112 -0
- scitex/fig/editor/flask_editor/static/css/components/modal.css +59 -0
- scitex/fig/editor/flask_editor/static/css/components/sections.css +212 -0
- scitex/fig/editor/flask_editor/static/css/features/canvas.css +176 -0
- scitex/fig/editor/flask_editor/static/css/features/element-inspector.css +190 -0
- scitex/fig/editor/flask_editor/static/css/features/loading.css +59 -0
- scitex/fig/editor/flask_editor/static/css/features/overlay.css +45 -0
- scitex/fig/editor/flask_editor/static/css/features/panel-grid.css +95 -0
- scitex/fig/editor/flask_editor/static/css/features/selection.css +101 -0
- scitex/fig/editor/flask_editor/static/css/features/statistics.css +138 -0
- scitex/fig/editor/flask_editor/static/css/index.css +31 -0
- scitex/fig/editor/flask_editor/static/css/layout/container.css +7 -0
- scitex/fig/editor/flask_editor/static/css/layout/controls.css +56 -0
- scitex/fig/editor/flask_editor/static/css/layout/preview.css +78 -0
- scitex/fig/editor/flask_editor/static/js/alignment/axis.js +314 -0
- scitex/fig/editor/flask_editor/static/js/alignment/basic.js +107 -0
- scitex/fig/editor/flask_editor/static/js/alignment/distribute.js +54 -0
- scitex/fig/editor/flask_editor/static/js/canvas/canvas.js +172 -0
- scitex/fig/editor/flask_editor/static/js/canvas/dragging.js +258 -0
- scitex/fig/editor/flask_editor/static/js/canvas/resize.js +48 -0
- scitex/fig/editor/flask_editor/static/js/canvas/selection.js +71 -0
- scitex/fig/editor/flask_editor/static/js/core/api.js +288 -0
- scitex/fig/editor/flask_editor/static/js/core/state.js +143 -0
- scitex/fig/editor/flask_editor/static/js/core/utils.js +245 -0
- scitex/fig/editor/flask_editor/static/js/dev/element-inspector.js +992 -0
- scitex/fig/editor/flask_editor/static/js/editor/bbox.js +339 -0
- scitex/fig/editor/flask_editor/static/js/editor/element-drag.js +286 -0
- scitex/fig/editor/flask_editor/static/js/editor/overlay.js +371 -0
- scitex/fig/editor/flask_editor/static/js/editor/preview.js +293 -0
- scitex/fig/editor/flask_editor/static/js/main.js +426 -0
- scitex/fig/editor/flask_editor/static/js/shortcuts/context-menu.js +152 -0
- scitex/fig/editor/flask_editor/static/js/shortcuts/keyboard.js +265 -0
- scitex/fig/editor/flask_editor/static/js/ui/controls.js +184 -0
- scitex/fig/editor/flask_editor/static/js/ui/download.js +57 -0
- scitex/fig/editor/flask_editor/static/js/ui/help.js +100 -0
- scitex/fig/editor/flask_editor/static/js/ui/theme.js +34 -0
- scitex/fig/editor/flask_editor/templates/__init__.py +95 -5
- scitex/fig/editor/flask_editor/templates/_html.py +27 -9
- scitex/fig/editor/flask_editor/templates/_scripts.py +1928 -131
- scitex/fig/editor/flask_editor/templates/_styles.py +363 -51
- scitex/fig/io/_bundle.py +104 -19
- scitex/fts/README.md +262 -0
- scitex/fts/TODO.md +66 -0
- scitex/fts/__init__.py +90 -0
- scitex/fts/_bundle/README_IN_BUNDLE.md +102 -0
- scitex/fts/_bundle/_FTS.py +657 -0
- scitex/fts/_bundle/__init__.py +38 -0
- scitex/fts/_bundle/_children.py +216 -0
- scitex/fts/_bundle/_conversion/__init__.py +15 -0
- scitex/fts/_bundle/_conversion/_bundle2dict.py +44 -0
- scitex/fts/_bundle/_conversion/_dict2bundle.py +50 -0
- scitex/fts/_bundle/_dataclasses/_Axes.py +57 -0
- scitex/fts/_bundle/_dataclasses/_BBox.py +54 -0
- scitex/fts/_bundle/_dataclasses/_ColumnDef.py +72 -0
- scitex/fts/_bundle/_dataclasses/_DataFormat.py +40 -0
- scitex/fts/_bundle/_dataclasses/_DataInfo.py +135 -0
- scitex/fts/_bundle/_dataclasses/_DataSource.py +44 -0
- scitex/fts/_bundle/_dataclasses/_Node.py +319 -0
- scitex/fts/_bundle/_dataclasses/_NodeRefs.py +45 -0
- scitex/fts/_bundle/_dataclasses/_SizeMM.py +38 -0
- scitex/fts/_bundle/_dataclasses/__init__.py +35 -0
- scitex/fts/_bundle/_extractors/__init__.py +32 -0
- scitex/fts/_bundle/_extractors/_extract_bar.py +131 -0
- scitex/fts/_bundle/_extractors/_extract_line.py +71 -0
- scitex/fts/_bundle/_extractors/_extract_scatter.py +79 -0
- scitex/fts/_bundle/_loader.py +134 -0
- scitex/fts/_bundle/_mpl_helpers.py +389 -0
- scitex/fts/_bundle/_saver.py +269 -0
- scitex/fts/_bundle/_storage.py +200 -0
- scitex/fts/_bundle/_utils/__init__.py +55 -0
- scitex/fts/_bundle/_utils/_const.py +26 -0
- scitex/fts/_bundle/_utils/_errors.py +73 -0
- scitex/fts/_bundle/_utils/_generate.py +21 -0
- scitex/fts/_bundle/_utils/_types.py +76 -0
- scitex/fts/_bundle/_validation.py +434 -0
- scitex/fts/_bundle/_zipbundle.py +165 -0
- scitex/fts/_fig/__init__.py +22 -0
- scitex/fts/_fig/_backend/__init__.py +53 -0
- scitex/fts/_fig/_backend/_export.py +165 -0
- scitex/fts/_fig/_backend/_parser.py +188 -0
- scitex/fts/_fig/_backend/_render.py +538 -0
- scitex/fts/_fig/_composite.py +345 -0
- scitex/fts/_fig/_dataclasses/_ChannelEncoding.py +46 -0
- scitex/fts/_fig/_dataclasses/_Encoding.py +82 -0
- scitex/fts/_fig/_dataclasses/_Theme.py +441 -0
- scitex/fts/_fig/_dataclasses/_TraceEncoding.py +52 -0
- scitex/fts/_fig/_dataclasses/__init__.py +47 -0
- scitex/fts/_fig/_editor/__init__.py +14 -0
- scitex/fts/_fig/_editor/_cui/__init__.py +33 -0
- scitex/fts/_fig/_editor/_cui/_backend_detector.py +39 -0
- scitex/fts/_fig/_editor/_cui/_bundle_resolver.py +366 -0
- scitex/fts/_fig/_editor/_cui/_editor_launcher.py +175 -0
- scitex/fts/_fig/_editor/_cui/_manual_handler.py +52 -0
- scitex/fts/_fig/_editor/_cui/_panel_loader.py +246 -0
- scitex/fts/_fig/_editor/_cui/_path_resolver.py +66 -0
- scitex/fts/_fig/_editor/_defaults.py +300 -0
- scitex/fts/_fig/_editor/_gui/__init__.py +11 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/__init__.py +20 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_bbox.py +1339 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_core.py +1688 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_plotter.py +664 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_renderer.py +853 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_utils.py +79 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/reset.css +41 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/typography.css +16 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/variables.css +85 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/buttons.css +217 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/context-menu.css +93 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/dropdown.css +57 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/forms.css +112 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/modal.css +59 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/sections.css +212 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/canvas.css +176 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/element-inspector.css +190 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/loading.css +59 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/overlay.css +45 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/panel-grid.css +95 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/selection.css +101 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/statistics.css +138 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/index.css +31 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/container.css +7 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/controls.css +56 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/preview.css +78 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/axis.js +314 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/basic.js +107 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/distribute.js +54 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/canvas.js +172 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/dragging.js +258 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/resize.js +48 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/selection.js +71 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/api.js +288 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/state.js +143 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/utils.js +245 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/dev/element-inspector.js +992 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/bbox.js +339 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/element-drag.js +286 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/overlay.js +371 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/preview.js +293 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/main.js +426 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/context-menu.js +152 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/keyboard.js +265 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/controls.js +184 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/download.js +57 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/help.js +100 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/theme.js +34 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/__init__.py +124 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_html.py +851 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_scripts.py +4932 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_styles.py +1657 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor.py +36 -0
- scitex/fts/_fig/_models/_Annotations.py +115 -0
- scitex/fts/_fig/_models/_Axes.py +152 -0
- scitex/fts/_fig/_models/_Figure.py +138 -0
- scitex/fts/_fig/_models/_Guides.py +104 -0
- scitex/fts/_fig/_models/_Plot.py +123 -0
- scitex/fts/_fig/_models/_Styles.py +245 -0
- scitex/fts/_fig/_models/__init__.py +80 -0
- scitex/fts/_fig/_models/_plot_types/__init__.py +156 -0
- scitex/fts/_fig/_models/_plot_types/_bar.py +43 -0
- scitex/fts/_fig/_models/_plot_types/_box.py +38 -0
- scitex/fts/_fig/_models/_plot_types/_distribution.py +36 -0
- scitex/fts/_fig/_models/_plot_types/_errorbar.py +60 -0
- scitex/fts/_fig/_models/_plot_types/_histogram.py +30 -0
- scitex/fts/_fig/_models/_plot_types/_image.py +61 -0
- scitex/fts/_fig/_models/_plot_types/_line.py +57 -0
- scitex/fts/_fig/_models/_plot_types/_scatter.py +30 -0
- scitex/fts/_fig/_models/_plot_types/_seaborn.py +121 -0
- scitex/fts/_fig/_models/_plot_types/_violin.py +36 -0
- scitex/fts/_fig/_utils/__init__.py +129 -0
- scitex/fts/_fig/_utils/_auto_layout.py +127 -0
- scitex/fts/_fig/_utils/_calc_bounds.py +111 -0
- scitex/fts/_fig/_utils/_const_sizes.py +48 -0
- scitex/fts/_fig/_utils/_convert_coords.py +77 -0
- scitex/fts/_fig/_utils/_get_template.py +178 -0
- scitex/fts/_fig/_utils/_normalize.py +73 -0
- scitex/fts/_fig/_utils/_plot_layout.py +397 -0
- scitex/fts/_fig/_utils/_validate.py +197 -0
- scitex/fts/_kinds/__init__.py +45 -0
- scitex/fts/_kinds/_figure/__init__.py +19 -0
- scitex/fts/_kinds/_figure/_composite.py +345 -0
- scitex/fts/_kinds/_plot/__init__.py +25 -0
- scitex/fts/_kinds/_plot/_backend/__init__.py +53 -0
- scitex/fts/_kinds/_plot/_backend/_export.py +165 -0
- scitex/fts/_kinds/_plot/_backend/_parser.py +188 -0
- scitex/fts/_kinds/_plot/_backend/_render.py +538 -0
- scitex/fts/_kinds/_plot/_dataclasses/_ChannelEncoding.py +46 -0
- scitex/fts/_kinds/_plot/_dataclasses/_Encoding.py +82 -0
- scitex/fts/_kinds/_plot/_dataclasses/_Theme.py +441 -0
- scitex/fts/_kinds/_plot/_dataclasses/_TraceEncoding.py +52 -0
- scitex/fts/_kinds/_plot/_dataclasses/__init__.py +47 -0
- scitex/fts/_kinds/_plot/_models/_Annotations.py +115 -0
- scitex/fts/_kinds/_plot/_models/_Axes.py +152 -0
- scitex/fts/_kinds/_plot/_models/_Figure.py +138 -0
- scitex/fts/_kinds/_plot/_models/_Guides.py +104 -0
- scitex/fts/_kinds/_plot/_models/_Plot.py +123 -0
- scitex/fts/_kinds/_plot/_models/_Styles.py +245 -0
- scitex/fts/_kinds/_plot/_models/__init__.py +80 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/__init__.py +156 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_bar.py +43 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_box.py +38 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_distribution.py +36 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_errorbar.py +60 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_histogram.py +30 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_image.py +61 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_line.py +57 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_scatter.py +30 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_seaborn.py +121 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_violin.py +36 -0
- scitex/fts/_kinds/_plot/_utils/__init__.py +129 -0
- scitex/fts/_kinds/_plot/_utils/_auto_layout.py +127 -0
- scitex/fts/_kinds/_plot/_utils/_calc_bounds.py +111 -0
- scitex/fts/_kinds/_plot/_utils/_const_sizes.py +48 -0
- scitex/fts/_kinds/_plot/_utils/_convert_coords.py +77 -0
- scitex/fts/_kinds/_plot/_utils/_get_template.py +178 -0
- scitex/fts/_kinds/_plot/_utils/_normalize.py +73 -0
- scitex/fts/_kinds/_plot/_utils/_plot_layout.py +397 -0
- scitex/fts/_kinds/_plot/_utils/_validate.py +197 -0
- scitex/fts/_kinds/_shape/__init__.py +141 -0
- scitex/fts/_kinds/_stats/__init__.py +56 -0
- scitex/fts/_kinds/_stats/_dataclasses/_Stats.py +423 -0
- scitex/fts/_kinds/_stats/_dataclasses/__init__.py +48 -0
- scitex/fts/_kinds/_table/__init__.py +72 -0
- scitex/fts/_kinds/_table/_latex/__init__.py +93 -0
- scitex/fts/_kinds/_table/_latex/_editor/__init__.py +11 -0
- scitex/fts/_kinds/_table/_latex/_editor/_app.py +725 -0
- scitex/fts/_kinds/_table/_latex/_export.py +279 -0
- scitex/fts/_kinds/_table/_latex/_figure_exporter.py +153 -0
- scitex/fts/_kinds/_table/_latex/_stats_formatter.py +274 -0
- scitex/fts/_kinds/_table/_latex/_table_exporter.py +362 -0
- scitex/fts/_kinds/_table/_latex/_utils.py +369 -0
- scitex/fts/_kinds/_table/_latex/_validator.py +445 -0
- scitex/fts/_kinds/_text/__init__.py +77 -0
- scitex/fts/_schemas/data_info.schema.json +75 -0
- scitex/fts/_schemas/encoding.schema.json +90 -0
- scitex/fts/_schemas/node.schema.json +145 -0
- scitex/fts/_schemas/render_manifest.schema.json +62 -0
- scitex/fts/_schemas/stats.schema.json +132 -0
- scitex/fts/_schemas/theme.schema.json +141 -0
- scitex/fts/_stats/__init__.py +48 -0
- scitex/fts/_stats/_dataclasses/_Stats.py +423 -0
- scitex/fts/_stats/_dataclasses/__init__.py +48 -0
- scitex/fts/_tables/__init__.py +65 -0
- scitex/fts/_tables/_latex/__init__.py +93 -0
- scitex/fts/_tables/_latex/_editor/__init__.py +11 -0
- scitex/fts/_tables/_latex/_editor/_app.py +725 -0
- scitex/fts/_tables/_latex/_export.py +279 -0
- scitex/fts/_tables/_latex/_figure_exporter.py +153 -0
- scitex/fts/_tables/_latex/_stats_formatter.py +274 -0
- scitex/fts/_tables/_latex/_table_exporter.py +362 -0
- scitex/fts/_tables/_latex/_utils.py +369 -0
- scitex/fts/_tables/_latex/_validator.py +445 -0
- scitex/gen/__init__.py +66 -25
- scitex/gen/misc.py +28 -0
- scitex/io/__init__.py +47 -20
- scitex/io/_load.py +87 -36
- scitex/io/_load_modules/__init__.py +10 -7
- scitex/io/_load_modules/_pandas.py +6 -1
- scitex/io/_save.py +299 -1556
- scitex/io/_save_modules/__init__.py +76 -19
- scitex/io/_save_modules/_figure_utils.py +90 -0
- scitex/io/_save_modules/_image_csv.py +497 -0
- scitex/io/_save_modules/_legends.py +91 -0
- scitex/io/_save_modules/_pltz_bundle.py +356 -0
- scitex/io/_save_modules/_pltz_stx.py +536 -0
- scitex/io/_save_modules/_stx_bundle.py +104 -0
- scitex/io/_save_modules/_symlink.py +96 -0
- scitex/io/_save_modules/_yaml.py +1 -1
- scitex/io/_save_modules/_zarr.py +64 -18
- scitex/io/bundle/README.md +212 -0
- scitex/io/bundle/__init__.py +110 -0
- scitex/io/{_bundle.py → bundle/_core.py} +219 -89
- scitex/io/bundle/_nested.py +713 -0
- scitex/io/bundle/_types.py +74 -0
- scitex/io/bundle/_zip.py +487 -0
- scitex/io/utils/h5_to_zarr.py +1 -1
- scitex/logging/__init__.py +108 -13
- scitex/logging/_errors.py +508 -0
- scitex/logging/_formatters.py +30 -6
- scitex/logging/_warnings.py +261 -0
- scitex/plt/__init__.py +4 -1
- scitex/plt/_figrecipe.py +236 -0
- scitex/plt/_subplots/_AxisWrapper.py +6 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/__init__.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_labels.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_metadata.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_visual.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/__init__.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_base.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_scientific.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_statistical.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_stx_aliases.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_RawMatplotlibMixin.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/__init__.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_base.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_UnitAwareMixin.py +112 -1
- scitex/plt/_subplots/_FigWrapper.py +15 -0
- scitex/plt/_subplots/_SubplotsWrapper.py +125 -489
- scitex/plt/_subplots/_export_as_csv.py +11 -0
- scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +2 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_pcolormesh.py +66 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stackplot.py +62 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_bar.py +0 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_barh.py +0 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_errorbar.py +0 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter.py +0 -0
- scitex/plt/_subplots/_export_as_csv_formatters/test_formatters.py +208 -0
- scitex/plt/_subplots/_fonts.py +71 -0
- scitex/plt/_subplots/_mm_layout.py +282 -0
- scitex/plt/gallery/__init__.py +99 -2
- scitex/plt/io/_layered_bundle.py +0 -0
- scitex/plt/styles/_plot_postprocess.py +3 -1
- scitex/plt/utils/_configure_mpl.py +16 -19
- scitex/repro/_RandomStateManager.py +13 -8
- scitex/resource/__init__.py +19 -1
- scitex/resource/_utils/_get_env_info.py +13 -25
- scitex/schema/__init__.py +149 -160
- scitex/schema/_encoding.py +273 -0
- scitex/schema/_figure_elements.py +406 -0
- scitex/schema/_plot.py +0 -0
- scitex/schema/_theme.py +360 -0
- scitex/schema/_validation.py +0 -98
- scitex/scholar/__init__.py +56 -14
- scitex/scholar/auth/ScholarAuthManager.py +1 -1
- scitex/scholar/auth/__init__.py +11 -2
- scitex/scholar/auth/providers/BaseAuthenticator.py +1 -1
- scitex/scholar/auth/providers/EZProxyAuthenticator.py +1 -1
- scitex/scholar/auth/providers/OpenAthensAuthenticator.py +1 -1
- scitex/scholar/auth/providers/ShibbolethAuthenticator.py +1 -1
- scitex/scholar/config/ScholarConfig.py +1 -1
- scitex/scholar/core/Scholar.py +1 -1
- scitex/session/_decorator.py +18 -16
- scitex/session/_lifecycle.py +9 -11
- scitex/session/template.py +9 -8
- scitex/sh/test_sh.py +72 -0
- scitex/sh/test_sh_simple.py +61 -0
- scitex/stats/__init__.py +221 -97
- scitex/stats/_schema.py +21 -22
- scitex/stats/descriptive/_circular.py +212 -351
- scitex/stats/descriptive/_describe.py +81 -132
- scitex/stats/descriptive/_nan.py +205 -433
- scitex/stats/descriptive/_real.py +127 -141
- scitex/str/_format_plot_text.py +5 -5
- scitex/str/_latex.py +26 -84
- scitex/str/_latex_fallback.py +53 -47
- scitex/web/_search_pubmed.py +5 -4
- scitex/writer/tests/test_diff_between.py +451 -0
- scitex/writer/tests/test_document_section.py +311 -0
- scitex/writer/tests/test_document_workflow.py +393 -0
- scitex/writer/tests/test_writer.py +361 -0
- scitex/writer/tests/test_writer_integration.py +303 -0
- {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/METADATA +364 -181
- {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/RECORD +479 -108
- scitex/fig/editor/_edit.py +0 -751
- scitex/scholar/docs/to_claude/guidelines/examples/mgmt/ARCHITECTURE_EXAMPLE.md +0 -905
- scitex/scholar/docs/to_claude/guidelines/examples/mgmt/BULLETIN_BOARD_EXAMPLE.md +0 -99
- scitex/scholar/docs/to_claude/guidelines/examples/mgmt/PROJECT_DESCRIPTION_EXAMPLE.md +0 -96
- {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/WHEEL +0 -0
- {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/entry_points.txt +0 -0
- {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/licenses/LICENSE +0 -0
scitex/ai/_gen_ai/_Perplexity.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
2
|
# Time-stamp: "2024-11-11 04:11:10 (ywatanabe)"
|
|
4
3
|
# File: ./scitex_repo/src/scitex/ai/_gen_ai/_Perplexity.py
|
|
5
4
|
|
|
@@ -45,6 +44,12 @@ class Perplexity(BaseGenAI):
|
|
|
45
44
|
chat_history: Optional[List[Dict[str, str]]] = None,
|
|
46
45
|
max_tokens: Optional[int] = None, # Added parameter
|
|
47
46
|
) -> None:
|
|
47
|
+
# Validate API key
|
|
48
|
+
if not api_key:
|
|
49
|
+
api_key = os.getenv("PERPLEXITY_API_KEY", "")
|
|
50
|
+
if not api_key:
|
|
51
|
+
raise ValueError("PERPLEXITY_API_KEY environment variable not set")
|
|
52
|
+
|
|
48
53
|
# Set max_tokens based on model if not provided
|
|
49
54
|
if max_tokens is None:
|
|
50
55
|
max_tokens = 128_000 if "128k" in model else 32_000
|
|
@@ -54,6 +59,7 @@ class Perplexity(BaseGenAI):
|
|
|
54
59
|
model=model,
|
|
55
60
|
api_key=api_key,
|
|
56
61
|
stream=stream,
|
|
62
|
+
seed=seed,
|
|
57
63
|
n_keep=n_keep,
|
|
58
64
|
temperature=temperature,
|
|
59
65
|
provider="Perplexity",
|
|
@@ -61,6 +67,11 @@ class Perplexity(BaseGenAI):
|
|
|
61
67
|
max_tokens=max_tokens,
|
|
62
68
|
)
|
|
63
69
|
|
|
70
|
+
@property
|
|
71
|
+
def chat_history(self) -> List[Dict[str, str]]:
|
|
72
|
+
"""Alias for history to maintain backward compatibility."""
|
|
73
|
+
return self.history
|
|
74
|
+
|
|
64
75
|
def _init_client(self) -> OpenAI:
|
|
65
76
|
return OpenAI(api_key=self.api_key, base_url="https://api.perplexity.ai")
|
|
66
77
|
# return OpenAI(
|
|
@@ -95,7 +106,11 @@ class Perplexity(BaseGenAI):
|
|
|
95
106
|
)
|
|
96
107
|
|
|
97
108
|
for chunk in stream:
|
|
98
|
-
|
|
109
|
+
# Handle empty chunks or chunks without choices
|
|
110
|
+
if not chunk or not chunk.choices:
|
|
111
|
+
continue
|
|
112
|
+
|
|
113
|
+
if chunk.choices[0].finish_reason == "stop":
|
|
99
114
|
print(chunk.choices)
|
|
100
115
|
try:
|
|
101
116
|
self.input_tokens += chunk.usage.prompt_tokens
|
|
@@ -103,10 +118,9 @@ class Perplexity(BaseGenAI):
|
|
|
103
118
|
except AttributeError:
|
|
104
119
|
pass
|
|
105
120
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
yield current_text
|
|
121
|
+
current_text = chunk.choices[0].delta.content
|
|
122
|
+
if current_text:
|
|
123
|
+
yield current_text
|
|
110
124
|
|
|
111
125
|
def _get_available_models(self) -> List[str]:
|
|
112
126
|
return [
|
scitex/audio/__init__.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
2
|
# Timestamp: "2025-12-11 (ywatanabe)"
|
|
4
3
|
# File: /home/ywatanabe/proj/scitex-code/src/scitex/audio/__init__.py
|
|
5
4
|
# ----------------------------------------
|
|
@@ -27,18 +26,26 @@ Usage:
|
|
|
27
26
|
from scitex.audio import GoogleTTS, ElevenLabsTTS, SystemTTS
|
|
28
27
|
tts = SystemTTS()
|
|
29
28
|
tts.speak("Hello!")
|
|
29
|
+
|
|
30
|
+
Installation:
|
|
31
|
+
pip install scitex[audio]
|
|
30
32
|
"""
|
|
31
33
|
|
|
32
34
|
import subprocess
|
|
33
35
|
from typing import List, Optional
|
|
34
36
|
|
|
37
|
+
# Check for missing dependencies and warn user
|
|
38
|
+
from scitex._install_guide import warn_module_deps
|
|
39
|
+
|
|
40
|
+
_missing = warn_module_deps("audio")
|
|
41
|
+
|
|
35
42
|
# Import from engines subpackage
|
|
36
43
|
from .engines import (
|
|
37
44
|
BaseTTS,
|
|
38
|
-
TTSBackend,
|
|
39
|
-
SystemTTS,
|
|
40
|
-
GoogleTTS,
|
|
41
45
|
ElevenLabsTTS,
|
|
46
|
+
GoogleTTS,
|
|
47
|
+
SystemTTS,
|
|
48
|
+
TTSBackend,
|
|
42
49
|
)
|
|
43
50
|
|
|
44
51
|
|
|
@@ -118,6 +125,7 @@ def check_wsl_audio() -> dict:
|
|
|
118
125
|
|
|
119
126
|
return result
|
|
120
127
|
|
|
128
|
+
|
|
121
129
|
# Keep legacy TTS import for backwards compatibility
|
|
122
130
|
from ._tts import TTS
|
|
123
131
|
|
|
@@ -137,9 +145,8 @@ __all__ = [
|
|
|
137
145
|
"FALLBACK_ORDER",
|
|
138
146
|
]
|
|
139
147
|
|
|
140
|
-
# Fallback order:
|
|
141
|
-
|
|
142
|
-
FALLBACK_ORDER = ["gtts", "pyttsx3", "elevenlabs"]
|
|
148
|
+
# Fallback order: elevenlabs (best quality) -> gtts (free) -> pyttsx3 (offline)
|
|
149
|
+
FALLBACK_ORDER = ["elevenlabs", "gtts", "pyttsx3"]
|
|
143
150
|
|
|
144
151
|
|
|
145
152
|
def available_backends() -> List[str]:
|
|
@@ -150,6 +157,7 @@ def available_backends() -> List[str]:
|
|
|
150
157
|
if SystemTTS:
|
|
151
158
|
try:
|
|
152
159
|
import pyttsx3
|
|
160
|
+
|
|
153
161
|
# Try to init to check if espeak is available
|
|
154
162
|
engine = pyttsx3.init()
|
|
155
163
|
engine.stop()
|
|
@@ -164,7 +172,11 @@ def available_backends() -> List[str]:
|
|
|
164
172
|
# Check ElevenLabs (requires API key)
|
|
165
173
|
if ElevenLabsTTS:
|
|
166
174
|
import os
|
|
167
|
-
|
|
175
|
+
|
|
176
|
+
api_key = os.environ.get("ELEVENLABS_API_KEY") or os.environ.get(
|
|
177
|
+
"ELEVENLABS_API_KEY_SCITEX_AUDIO"
|
|
178
|
+
)
|
|
179
|
+
if api_key:
|
|
168
180
|
backends.append("elevenlabs")
|
|
169
181
|
|
|
170
182
|
return backends
|
|
@@ -209,10 +221,7 @@ def get_tts(backend: Optional[str] = None, **kwargs) -> BaseTTS:
|
|
|
209
221
|
elif backend == "elevenlabs" and ElevenLabsTTS:
|
|
210
222
|
return ElevenLabsTTS(**kwargs)
|
|
211
223
|
else:
|
|
212
|
-
raise ValueError(
|
|
213
|
-
f"Backend '{backend}' not available. "
|
|
214
|
-
f"Available: {backends}"
|
|
215
|
-
)
|
|
224
|
+
raise ValueError(f"Backend '{backend}' not available. Available: {backends}")
|
|
216
225
|
|
|
217
226
|
|
|
218
227
|
def _try_speak_with_fallback(
|
|
@@ -340,9 +349,7 @@ def speak(
|
|
|
340
349
|
**kwargs,
|
|
341
350
|
)
|
|
342
351
|
if result is None and errors:
|
|
343
|
-
raise RuntimeError(
|
|
344
|
-
f"All TTS backends failed:\n" + "\n".join(errors)
|
|
345
|
-
)
|
|
352
|
+
raise RuntimeError("All TTS backends failed:\n" + "\n".join(errors))
|
|
346
353
|
return str(result) if result else None
|
|
347
354
|
|
|
348
355
|
# Specific backend with fallback enabled
|
|
@@ -377,7 +384,9 @@ def speak(
|
|
|
377
384
|
def start_mcp_server():
|
|
378
385
|
"""Start the MCP server for audio."""
|
|
379
386
|
import asyncio
|
|
387
|
+
|
|
380
388
|
from .mcp_server import main
|
|
389
|
+
|
|
381
390
|
asyncio.run(main())
|
|
382
391
|
|
|
383
392
|
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: "2025-12-27 (ywatanabe)"
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/audio/_cross_process_lock.py
|
|
4
|
+
# ----------------------------------------
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
Cross-process FIFO lock for audio playback.
|
|
8
|
+
|
|
9
|
+
Ensures only one MCP server instance can play audio at a time,
|
|
10
|
+
providing true FIFO ordering across all Claude Code sessions.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import fcntl
|
|
16
|
+
import os
|
|
17
|
+
import time
|
|
18
|
+
from contextlib import contextmanager
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
__all__ = ["AudioPlaybackLock", "acquire_audio_lock"]
|
|
22
|
+
|
|
23
|
+
# Lock file location
|
|
24
|
+
SCITEX_BASE_DIR = Path(os.getenv("SCITEX_DIR", Path.home() / ".scitex"))
|
|
25
|
+
LOCK_FILE = SCITEX_BASE_DIR / "audio" / ".audio_playback.lock"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AudioPlaybackLock:
|
|
29
|
+
"""Cross-process lock for sequential audio playback.
|
|
30
|
+
|
|
31
|
+
Uses fcntl.flock() for POSIX file locking to ensure
|
|
32
|
+
only one process can play audio at a time.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, lock_file: Path | None = None):
|
|
36
|
+
self.lock_file = lock_file or LOCK_FILE
|
|
37
|
+
self._fd: int | None = None
|
|
38
|
+
self._acquired = False
|
|
39
|
+
|
|
40
|
+
def _ensure_lock_dir(self):
|
|
41
|
+
"""Ensure the lock file directory exists."""
|
|
42
|
+
self.lock_file.parent.mkdir(parents=True, exist_ok=True)
|
|
43
|
+
|
|
44
|
+
def acquire(self, timeout: float | None = None) -> bool:
|
|
45
|
+
"""Acquire the audio playback lock.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
timeout: Maximum time to wait in seconds.
|
|
49
|
+
None means wait indefinitely.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
True if lock acquired, False if timeout.
|
|
53
|
+
"""
|
|
54
|
+
self._ensure_lock_dir()
|
|
55
|
+
|
|
56
|
+
# Open or create the lock file
|
|
57
|
+
self._fd = os.open(
|
|
58
|
+
str(self.lock_file),
|
|
59
|
+
os.O_RDWR | os.O_CREAT,
|
|
60
|
+
0o644,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
start_time = time.time()
|
|
64
|
+
|
|
65
|
+
while True:
|
|
66
|
+
try:
|
|
67
|
+
# Try to acquire exclusive lock (non-blocking)
|
|
68
|
+
fcntl.flock(self._fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
69
|
+
self._acquired = True
|
|
70
|
+
|
|
71
|
+
# Write PID to lock file for debugging
|
|
72
|
+
os.ftruncate(self._fd, 0)
|
|
73
|
+
os.lseek(self._fd, 0, os.SEEK_SET)
|
|
74
|
+
os.write(self._fd, f"{os.getpid()}\n".encode())
|
|
75
|
+
|
|
76
|
+
return True
|
|
77
|
+
|
|
78
|
+
except OSError:
|
|
79
|
+
# Lock is held by another process
|
|
80
|
+
if timeout is not None:
|
|
81
|
+
elapsed = time.time() - start_time
|
|
82
|
+
if elapsed >= timeout:
|
|
83
|
+
self._cleanup()
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
# Wait a bit before retrying
|
|
87
|
+
time.sleep(0.1)
|
|
88
|
+
|
|
89
|
+
def release(self):
|
|
90
|
+
"""Release the audio playback lock."""
|
|
91
|
+
if self._fd is not None and self._acquired:
|
|
92
|
+
try:
|
|
93
|
+
fcntl.flock(self._fd, fcntl.LOCK_UN)
|
|
94
|
+
except OSError:
|
|
95
|
+
pass
|
|
96
|
+
self._acquired = False
|
|
97
|
+
self._cleanup()
|
|
98
|
+
|
|
99
|
+
def _cleanup(self):
|
|
100
|
+
"""Clean up file descriptor."""
|
|
101
|
+
if self._fd is not None:
|
|
102
|
+
try:
|
|
103
|
+
os.close(self._fd)
|
|
104
|
+
except OSError:
|
|
105
|
+
pass
|
|
106
|
+
self._fd = None
|
|
107
|
+
|
|
108
|
+
def __enter__(self):
|
|
109
|
+
self.acquire()
|
|
110
|
+
return self
|
|
111
|
+
|
|
112
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
113
|
+
self.release()
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@contextmanager
|
|
118
|
+
def acquire_audio_lock(timeout: float | None = 60.0):
|
|
119
|
+
"""Context manager for acquiring the audio playback lock.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
timeout: Maximum time to wait in seconds (default: 60s).
|
|
123
|
+
|
|
124
|
+
Yields:
|
|
125
|
+
True if lock was acquired.
|
|
126
|
+
|
|
127
|
+
Raises:
|
|
128
|
+
TimeoutError: If lock could not be acquired within timeout.
|
|
129
|
+
"""
|
|
130
|
+
lock = AudioPlaybackLock()
|
|
131
|
+
try:
|
|
132
|
+
if not lock.acquire(timeout=timeout):
|
|
133
|
+
raise TimeoutError(f"Could not acquire audio lock within {timeout}s")
|
|
134
|
+
yield True
|
|
135
|
+
finally:
|
|
136
|
+
lock.release()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# EOF
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: "2025-12-27 (ywatanabe)"
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/audio/_mcp_handlers.py
|
|
4
|
+
# ----------------------------------------
|
|
5
|
+
|
|
6
|
+
"""Utility handlers for the scitex-audio MCP server."""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import base64
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"generate_audio_handler",
|
|
17
|
+
"list_backends_handler",
|
|
18
|
+
"list_voices_handler",
|
|
19
|
+
"play_audio_handler",
|
|
20
|
+
"list_audio_files_handler",
|
|
21
|
+
"clear_audio_cache_handler",
|
|
22
|
+
"check_audio_status_handler",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _get_audio_dir() -> Path:
|
|
27
|
+
"""Get the audio output directory."""
|
|
28
|
+
import os
|
|
29
|
+
|
|
30
|
+
base_dir = Path(os.getenv("SCITEX_DIR", Path.home() / ".scitex"))
|
|
31
|
+
audio_dir = base_dir / "audio"
|
|
32
|
+
audio_dir.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
return audio_dir
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def generate_audio_handler(
|
|
37
|
+
text: str,
|
|
38
|
+
backend: str | None = None,
|
|
39
|
+
voice: str | None = None,
|
|
40
|
+
output_path: str | None = None,
|
|
41
|
+
return_base64: bool = False,
|
|
42
|
+
) -> dict:
|
|
43
|
+
"""Generate audio file without playing."""
|
|
44
|
+
try:
|
|
45
|
+
from . import speak as tts_speak
|
|
46
|
+
|
|
47
|
+
loop = asyncio.get_event_loop()
|
|
48
|
+
|
|
49
|
+
if not output_path:
|
|
50
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
51
|
+
output_path = str(_get_audio_dir() / f"tts_{timestamp}.mp3")
|
|
52
|
+
|
|
53
|
+
def do_generate():
|
|
54
|
+
return tts_speak(
|
|
55
|
+
text=text,
|
|
56
|
+
backend=backend,
|
|
57
|
+
voice=voice,
|
|
58
|
+
play=False,
|
|
59
|
+
output_path=output_path,
|
|
60
|
+
fallback=True,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
result_path = await loop.run_in_executor(None, do_generate)
|
|
64
|
+
|
|
65
|
+
result = {
|
|
66
|
+
"success": True,
|
|
67
|
+
"path": str(result_path),
|
|
68
|
+
"text": text,
|
|
69
|
+
"backend": backend,
|
|
70
|
+
"timestamp": datetime.now().isoformat(),
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if result_path.exists():
|
|
74
|
+
result["size_kb"] = round(result_path.stat().st_size / 1024, 2)
|
|
75
|
+
|
|
76
|
+
if return_base64 and result_path.exists():
|
|
77
|
+
with open(result_path, "rb") as f:
|
|
78
|
+
result["base64"] = base64.b64encode(f.read()).decode()
|
|
79
|
+
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
except Exception as e:
|
|
83
|
+
return {"success": False, "error": str(e)}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
async def list_backends_handler() -> dict:
|
|
87
|
+
"""List available TTS backends."""
|
|
88
|
+
try:
|
|
89
|
+
from . import available_backends
|
|
90
|
+
|
|
91
|
+
backends = available_backends()
|
|
92
|
+
|
|
93
|
+
info = []
|
|
94
|
+
for b in ["gtts", "elevenlabs", "pyttsx3"]:
|
|
95
|
+
available = b in backends
|
|
96
|
+
desc = {
|
|
97
|
+
"gtts": "Google TTS - Free, requires internet",
|
|
98
|
+
"elevenlabs": "ElevenLabs - Paid, high quality",
|
|
99
|
+
"pyttsx3": "System TTS - Offline, uses espeak/SAPI5",
|
|
100
|
+
}
|
|
101
|
+
info.append(
|
|
102
|
+
{
|
|
103
|
+
"name": b,
|
|
104
|
+
"available": available,
|
|
105
|
+
"description": desc.get(b, ""),
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
"success": True,
|
|
111
|
+
"backends": info,
|
|
112
|
+
"available": backends,
|
|
113
|
+
"default": backends[0] if backends else None,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
except Exception as e:
|
|
117
|
+
return {"success": False, "error": str(e)}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
async def list_voices_handler(backend: str = "gtts") -> dict:
|
|
121
|
+
"""List available voices for a backend."""
|
|
122
|
+
try:
|
|
123
|
+
from . import get_tts
|
|
124
|
+
|
|
125
|
+
loop = asyncio.get_event_loop()
|
|
126
|
+
|
|
127
|
+
def do_list():
|
|
128
|
+
tts = get_tts(backend)
|
|
129
|
+
return tts.get_voices()
|
|
130
|
+
|
|
131
|
+
voices = await loop.run_in_executor(None, do_list)
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
"success": True,
|
|
135
|
+
"backend": backend,
|
|
136
|
+
"voices": voices,
|
|
137
|
+
"count": len(voices),
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
except Exception as e:
|
|
141
|
+
return {"success": False, "error": str(e)}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
async def play_audio_handler(path: str) -> dict:
|
|
145
|
+
"""Play an audio file."""
|
|
146
|
+
try:
|
|
147
|
+
from .engines.base import BaseTTS
|
|
148
|
+
|
|
149
|
+
path_obj = Path(path)
|
|
150
|
+
if not path_obj.exists():
|
|
151
|
+
return {"success": False, "error": f"File not found: {path}"}
|
|
152
|
+
|
|
153
|
+
loop = asyncio.get_event_loop()
|
|
154
|
+
|
|
155
|
+
def do_play():
|
|
156
|
+
BaseTTS._play_audio(None, path_obj)
|
|
157
|
+
|
|
158
|
+
await loop.run_in_executor(None, do_play)
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
"success": True,
|
|
162
|
+
"played": str(path_obj),
|
|
163
|
+
"timestamp": datetime.now().isoformat(),
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
except Exception as e:
|
|
167
|
+
return {"success": False, "error": str(e)}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
async def list_audio_files_handler(limit: int = 20) -> dict:
|
|
171
|
+
"""List generated audio files."""
|
|
172
|
+
try:
|
|
173
|
+
audio_dir = _get_audio_dir()
|
|
174
|
+
if not audio_dir.exists():
|
|
175
|
+
return {"success": True, "files": [], "count": 0}
|
|
176
|
+
|
|
177
|
+
audio_files = sorted(
|
|
178
|
+
list(audio_dir.glob("*.mp3")) + list(audio_dir.glob("*.wav")),
|
|
179
|
+
key=lambda p: p.stat().st_mtime,
|
|
180
|
+
reverse=True,
|
|
181
|
+
)[:limit]
|
|
182
|
+
|
|
183
|
+
files = []
|
|
184
|
+
for f in audio_files:
|
|
185
|
+
files.append(
|
|
186
|
+
{
|
|
187
|
+
"name": f.name,
|
|
188
|
+
"path": str(f),
|
|
189
|
+
"size_kb": round(f.stat().st_size / 1024, 2),
|
|
190
|
+
"created": datetime.fromtimestamp(f.stat().st_mtime).isoformat(),
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
total_size = sum(f.stat().st_size for f in audio_dir.glob("*.*"))
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
"success": True,
|
|
198
|
+
"files": files,
|
|
199
|
+
"count": len(files),
|
|
200
|
+
"total_size_mb": round(total_size / (1024 * 1024), 2),
|
|
201
|
+
"audio_dir": str(audio_dir),
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
except Exception as e:
|
|
205
|
+
return {"success": False, "error": str(e)}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
async def clear_audio_cache_handler(max_age_hours: float = 24) -> dict:
|
|
209
|
+
"""Clear audio cache."""
|
|
210
|
+
try:
|
|
211
|
+
audio_dir = _get_audio_dir()
|
|
212
|
+
if not audio_dir.exists():
|
|
213
|
+
return {"success": True, "deleted": 0}
|
|
214
|
+
|
|
215
|
+
deleted = 0
|
|
216
|
+
now = datetime.now()
|
|
217
|
+
|
|
218
|
+
for f in list(audio_dir.glob("*.mp3")) + list(audio_dir.glob("*.wav")):
|
|
219
|
+
try:
|
|
220
|
+
if max_age_hours == 0:
|
|
221
|
+
f.unlink()
|
|
222
|
+
deleted += 1
|
|
223
|
+
else:
|
|
224
|
+
mtime = datetime.fromtimestamp(f.stat().st_mtime)
|
|
225
|
+
age_hours = (now - mtime).total_seconds() / 3600
|
|
226
|
+
if age_hours > max_age_hours:
|
|
227
|
+
f.unlink()
|
|
228
|
+
deleted += 1
|
|
229
|
+
except Exception:
|
|
230
|
+
pass
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
"success": True,
|
|
234
|
+
"deleted": deleted,
|
|
235
|
+
"max_age_hours": max_age_hours,
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
except Exception as e:
|
|
239
|
+
return {"success": False, "error": str(e)}
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
async def check_audio_status_handler() -> dict:
|
|
243
|
+
"""Check WSL audio connectivity and available playback methods."""
|
|
244
|
+
try:
|
|
245
|
+
from . import check_wsl_audio
|
|
246
|
+
|
|
247
|
+
status = check_wsl_audio()
|
|
248
|
+
status["success"] = True
|
|
249
|
+
status["timestamp"] = datetime.now().isoformat()
|
|
250
|
+
return status
|
|
251
|
+
|
|
252
|
+
except Exception as e:
|
|
253
|
+
return {"success": False, "error": str(e)}
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
# EOF
|