scitex 2.7.0__py3-none-any.whl → 2.8.1__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.
Files changed (355) hide show
  1. scitex/__init__.py +6 -2
  2. scitex/__version__.py +1 -1
  3. scitex/audio/README.md +52 -0
  4. scitex/audio/__init__.py +384 -0
  5. scitex/audio/__main__.py +129 -0
  6. scitex/audio/_tts.py +334 -0
  7. scitex/audio/engines/__init__.py +44 -0
  8. scitex/audio/engines/base.py +275 -0
  9. scitex/audio/engines/elevenlabs_engine.py +143 -0
  10. scitex/audio/engines/gtts_engine.py +162 -0
  11. scitex/audio/engines/pyttsx3_engine.py +131 -0
  12. scitex/audio/mcp_server.py +757 -0
  13. scitex/bridge/_helpers.py +1 -1
  14. scitex/bridge/_plt_vis.py +1 -1
  15. scitex/bridge/_stats_vis.py +1 -1
  16. scitex/dev/plt/__init__.py +272 -0
  17. scitex/dev/plt/plot_mpl_axhline.py +28 -0
  18. scitex/dev/plt/plot_mpl_axhspan.py +28 -0
  19. scitex/dev/plt/plot_mpl_axvline.py +28 -0
  20. scitex/dev/plt/plot_mpl_axvspan.py +28 -0
  21. scitex/dev/plt/plot_mpl_bar.py +29 -0
  22. scitex/dev/plt/plot_mpl_barh.py +29 -0
  23. scitex/dev/plt/plot_mpl_boxplot.py +28 -0
  24. scitex/dev/plt/plot_mpl_contour.py +31 -0
  25. scitex/dev/plt/plot_mpl_contourf.py +31 -0
  26. scitex/dev/plt/plot_mpl_errorbar.py +30 -0
  27. scitex/dev/plt/plot_mpl_eventplot.py +28 -0
  28. scitex/dev/plt/plot_mpl_fill.py +30 -0
  29. scitex/dev/plt/plot_mpl_fill_between.py +31 -0
  30. scitex/dev/plt/plot_mpl_hexbin.py +28 -0
  31. scitex/dev/plt/plot_mpl_hist.py +28 -0
  32. scitex/dev/plt/plot_mpl_hist2d.py +28 -0
  33. scitex/dev/plt/plot_mpl_imshow.py +29 -0
  34. scitex/dev/plt/plot_mpl_pcolormesh.py +31 -0
  35. scitex/dev/plt/plot_mpl_pie.py +29 -0
  36. scitex/dev/plt/plot_mpl_plot.py +29 -0
  37. scitex/dev/plt/plot_mpl_quiver.py +31 -0
  38. scitex/dev/plt/plot_mpl_scatter.py +28 -0
  39. scitex/dev/plt/plot_mpl_stackplot.py +31 -0
  40. scitex/dev/plt/plot_mpl_stem.py +29 -0
  41. scitex/dev/plt/plot_mpl_step.py +29 -0
  42. scitex/dev/plt/plot_mpl_violinplot.py +28 -0
  43. scitex/dev/plt/plot_sns_barplot.py +29 -0
  44. scitex/dev/plt/plot_sns_boxplot.py +29 -0
  45. scitex/dev/plt/plot_sns_heatmap.py +28 -0
  46. scitex/dev/plt/plot_sns_histplot.py +29 -0
  47. scitex/dev/plt/plot_sns_kdeplot.py +29 -0
  48. scitex/dev/plt/plot_sns_lineplot.py +31 -0
  49. scitex/dev/plt/plot_sns_scatterplot.py +29 -0
  50. scitex/dev/plt/plot_sns_stripplot.py +29 -0
  51. scitex/dev/plt/plot_sns_swarmplot.py +29 -0
  52. scitex/dev/plt/plot_sns_violinplot.py +29 -0
  53. scitex/dev/plt/plot_stx_bar.py +29 -0
  54. scitex/dev/plt/plot_stx_barh.py +29 -0
  55. scitex/dev/plt/plot_stx_box.py +28 -0
  56. scitex/dev/plt/plot_stx_boxplot.py +28 -0
  57. scitex/dev/plt/plot_stx_conf_mat.py +28 -0
  58. scitex/dev/plt/plot_stx_contour.py +31 -0
  59. scitex/dev/plt/plot_stx_ecdf.py +28 -0
  60. scitex/dev/plt/plot_stx_errorbar.py +30 -0
  61. scitex/dev/plt/plot_stx_fill_between.py +31 -0
  62. scitex/dev/plt/plot_stx_fillv.py +28 -0
  63. scitex/dev/plt/plot_stx_heatmap.py +28 -0
  64. scitex/dev/plt/plot_stx_image.py +28 -0
  65. scitex/dev/plt/plot_stx_imshow.py +28 -0
  66. scitex/dev/plt/plot_stx_joyplot.py +28 -0
  67. scitex/dev/plt/plot_stx_kde.py +28 -0
  68. scitex/dev/plt/plot_stx_line.py +28 -0
  69. scitex/dev/plt/plot_stx_mean_ci.py +28 -0
  70. scitex/dev/plt/plot_stx_mean_std.py +28 -0
  71. scitex/dev/plt/plot_stx_median_iqr.py +28 -0
  72. scitex/dev/plt/plot_stx_raster.py +28 -0
  73. scitex/dev/plt/plot_stx_rectangle.py +28 -0
  74. scitex/dev/plt/plot_stx_scatter.py +29 -0
  75. scitex/dev/plt/plot_stx_shaded_line.py +29 -0
  76. scitex/dev/plt/plot_stx_violin.py +28 -0
  77. scitex/dev/plt/plot_stx_violinplot.py +28 -0
  78. scitex/diagram/README.md +197 -0
  79. scitex/diagram/__init__.py +48 -0
  80. scitex/diagram/_compile.py +312 -0
  81. scitex/diagram/_diagram.py +355 -0
  82. scitex/diagram/_presets.py +173 -0
  83. scitex/diagram/_schema.py +182 -0
  84. scitex/diagram/_split.py +278 -0
  85. scitex/fig/__init__.py +352 -0
  86. scitex/{vis → fig}/backend/_parser.py +1 -1
  87. scitex/{vis → fig}/canvas.py +1 -1
  88. scitex/{vis → fig}/editor/__init__.py +5 -2
  89. scitex/{vis → fig}/editor/_dearpygui_editor.py +1 -1
  90. scitex/{vis → fig}/editor/_defaults.py +70 -5
  91. scitex/{vis → fig}/editor/_mpl_editor.py +1 -1
  92. scitex/{vis → fig}/editor/_qt_editor.py +182 -2
  93. scitex/{vis → fig}/editor/_tkinter_editor.py +1 -1
  94. scitex/fig/editor/edit/__init__.py +50 -0
  95. scitex/fig/editor/edit/backend_detector.py +109 -0
  96. scitex/fig/editor/edit/bundle_resolver.py +240 -0
  97. scitex/fig/editor/edit/editor_launcher.py +239 -0
  98. scitex/fig/editor/edit/manual_handler.py +53 -0
  99. scitex/fig/editor/edit/panel_loader.py +232 -0
  100. scitex/fig/editor/edit/path_resolver.py +67 -0
  101. scitex/fig/editor/flask_editor/_bbox.py +1299 -0
  102. scitex/fig/editor/flask_editor/_core.py +1429 -0
  103. scitex/{vis → fig}/editor/flask_editor/_plotter.py +38 -4
  104. scitex/fig/editor/flask_editor/_renderer.py +813 -0
  105. scitex/fig/editor/flask_editor/static/css/base/reset.css +41 -0
  106. scitex/fig/editor/flask_editor/static/css/base/typography.css +16 -0
  107. scitex/fig/editor/flask_editor/static/css/base/variables.css +85 -0
  108. scitex/fig/editor/flask_editor/static/css/components/buttons.css +217 -0
  109. scitex/fig/editor/flask_editor/static/css/components/context-menu.css +93 -0
  110. scitex/fig/editor/flask_editor/static/css/components/dropdown.css +57 -0
  111. scitex/fig/editor/flask_editor/static/css/components/forms.css +112 -0
  112. scitex/fig/editor/flask_editor/static/css/components/modal.css +59 -0
  113. scitex/fig/editor/flask_editor/static/css/components/sections.css +212 -0
  114. scitex/fig/editor/flask_editor/static/css/features/canvas.css +176 -0
  115. scitex/fig/editor/flask_editor/static/css/features/element-inspector.css +190 -0
  116. scitex/fig/editor/flask_editor/static/css/features/loading.css +59 -0
  117. scitex/fig/editor/flask_editor/static/css/features/overlay.css +45 -0
  118. scitex/fig/editor/flask_editor/static/css/features/panel-grid.css +95 -0
  119. scitex/fig/editor/flask_editor/static/css/features/selection.css +101 -0
  120. scitex/fig/editor/flask_editor/static/css/features/statistics.css +138 -0
  121. scitex/fig/editor/flask_editor/static/css/index.css +31 -0
  122. scitex/fig/editor/flask_editor/static/css/layout/container.css +7 -0
  123. scitex/fig/editor/flask_editor/static/css/layout/controls.css +56 -0
  124. scitex/fig/editor/flask_editor/static/css/layout/preview.css +78 -0
  125. scitex/fig/editor/flask_editor/static/js/alignment/axis.js +314 -0
  126. scitex/fig/editor/flask_editor/static/js/alignment/basic.js +107 -0
  127. scitex/fig/editor/flask_editor/static/js/alignment/distribute.js +54 -0
  128. scitex/fig/editor/flask_editor/static/js/canvas/canvas.js +172 -0
  129. scitex/fig/editor/flask_editor/static/js/canvas/dragging.js +258 -0
  130. scitex/fig/editor/flask_editor/static/js/canvas/resize.js +48 -0
  131. scitex/fig/editor/flask_editor/static/js/canvas/selection.js +71 -0
  132. scitex/fig/editor/flask_editor/static/js/core/api.js +288 -0
  133. scitex/fig/editor/flask_editor/static/js/core/state.js +143 -0
  134. scitex/fig/editor/flask_editor/static/js/core/utils.js +245 -0
  135. scitex/fig/editor/flask_editor/static/js/dev/element-inspector.js +992 -0
  136. scitex/fig/editor/flask_editor/static/js/editor/bbox.js +339 -0
  137. scitex/fig/editor/flask_editor/static/js/editor/element-drag.js +286 -0
  138. scitex/fig/editor/flask_editor/static/js/editor/overlay.js +371 -0
  139. scitex/fig/editor/flask_editor/static/js/editor/preview.js +293 -0
  140. scitex/fig/editor/flask_editor/static/js/main.js +426 -0
  141. scitex/fig/editor/flask_editor/static/js/shortcuts/context-menu.js +152 -0
  142. scitex/fig/editor/flask_editor/static/js/shortcuts/keyboard.js +265 -0
  143. scitex/fig/editor/flask_editor/static/js/ui/controls.js +184 -0
  144. scitex/fig/editor/flask_editor/static/js/ui/download.js +57 -0
  145. scitex/fig/editor/flask_editor/static/js/ui/help.js +100 -0
  146. scitex/fig/editor/flask_editor/static/js/ui/theme.js +34 -0
  147. scitex/fig/editor/flask_editor/templates/__init__.py +123 -0
  148. scitex/fig/editor/flask_editor/templates/_html.py +852 -0
  149. scitex/fig/editor/flask_editor/templates/_scripts.py +4933 -0
  150. scitex/fig/editor/flask_editor/templates/_styles.py +1658 -0
  151. scitex/{vis → fig}/io/__init__.py +13 -1
  152. scitex/fig/io/_bundle.py +1058 -0
  153. scitex/{vis → fig}/io/_canvas.py +1 -1
  154. scitex/{vis → fig}/io/_data.py +1 -1
  155. scitex/{vis → fig}/io/_export.py +1 -1
  156. scitex/{vis → fig}/io/_load.py +1 -1
  157. scitex/{vis → fig}/io/_panel.py +1 -1
  158. scitex/{vis → fig}/io/_save.py +1 -1
  159. scitex/{vis → fig}/model/__init__.py +1 -1
  160. scitex/{vis → fig}/model/_annotations.py +1 -1
  161. scitex/{vis → fig}/model/_axes.py +1 -1
  162. scitex/{vis → fig}/model/_figure.py +1 -1
  163. scitex/{vis → fig}/model/_guides.py +1 -1
  164. scitex/{vis → fig}/model/_plot.py +1 -1
  165. scitex/{vis → fig}/model/_styles.py +1 -1
  166. scitex/{vis → fig}/utils/__init__.py +1 -1
  167. scitex/io/__init__.py +22 -26
  168. scitex/io/_bundle.py +493 -0
  169. scitex/io/_flush.py +5 -2
  170. scitex/io/_load.py +98 -0
  171. scitex/io/_load_modules/_H5Explorer.py +5 -2
  172. scitex/io/_load_modules/_canvas.py +2 -2
  173. scitex/io/_load_modules/_image.py +3 -4
  174. scitex/io/_load_modules/_txt.py +4 -2
  175. scitex/io/_metadata.py +34 -324
  176. scitex/io/_metadata_modules/__init__.py +46 -0
  177. scitex/io/_metadata_modules/_embed.py +70 -0
  178. scitex/io/_metadata_modules/_read.py +64 -0
  179. scitex/io/_metadata_modules/_utils.py +79 -0
  180. scitex/io/_metadata_modules/embed_metadata_jpeg.py +74 -0
  181. scitex/io/_metadata_modules/embed_metadata_pdf.py +53 -0
  182. scitex/io/_metadata_modules/embed_metadata_png.py +26 -0
  183. scitex/io/_metadata_modules/embed_metadata_svg.py +62 -0
  184. scitex/io/_metadata_modules/read_metadata_jpeg.py +57 -0
  185. scitex/io/_metadata_modules/read_metadata_pdf.py +51 -0
  186. scitex/io/_metadata_modules/read_metadata_png.py +39 -0
  187. scitex/io/_metadata_modules/read_metadata_svg.py +44 -0
  188. scitex/io/_qr_utils.py +5 -3
  189. scitex/io/_save.py +548 -30
  190. scitex/io/_save_modules/_canvas.py +3 -3
  191. scitex/io/_save_modules/_image.py +5 -9
  192. scitex/io/_save_modules/_tex.py +7 -4
  193. scitex/io/_zip_bundle.py +439 -0
  194. scitex/io/utils/h5_to_zarr.py +11 -9
  195. scitex/msword/__init__.py +255 -0
  196. scitex/msword/profiles.py +357 -0
  197. scitex/msword/reader.py +753 -0
  198. scitex/msword/utils.py +289 -0
  199. scitex/msword/writer.py +362 -0
  200. scitex/plt/__init__.py +5 -2
  201. scitex/plt/_subplots/_AxesWrapper.py +6 -6
  202. scitex/plt/_subplots/_AxisWrapper.py +15 -9
  203. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/__init__.py +36 -0
  204. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_labels.py +264 -0
  205. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_metadata.py +213 -0
  206. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_visual.py +128 -0
  207. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/__init__.py +59 -0
  208. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_base.py +34 -0
  209. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_scientific.py +593 -0
  210. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_statistical.py +654 -0
  211. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_stx_aliases.py +527 -0
  212. scitex/plt/_subplots/_AxisWrapperMixins/_RawMatplotlibMixin.py +321 -0
  213. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/__init__.py +33 -0
  214. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_base.py +152 -0
  215. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +600 -0
  216. scitex/plt/_subplots/_AxisWrapperMixins/__init__.py +79 -5
  217. scitex/plt/_subplots/_FigWrapper.py +6 -6
  218. scitex/plt/_subplots/_SubplotsWrapper.py +28 -18
  219. scitex/plt/_subplots/_export_as_csv.py +35 -5
  220. scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +8 -0
  221. scitex/plt/_subplots/_export_as_csv_formatters/_format_annotate.py +10 -21
  222. scitex/plt/_subplots/_export_as_csv_formatters/_format_eventplot.py +18 -7
  223. scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow2d.py +28 -12
  224. scitex/plt/_subplots/_export_as_csv_formatters/_format_matshow.py +10 -4
  225. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_imshow.py +13 -1
  226. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_kde.py +12 -2
  227. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_scatter.py +10 -3
  228. scitex/plt/_subplots/_export_as_csv_formatters/_format_quiver.py +10 -4
  229. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_jointplot.py +18 -3
  230. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_lineplot.py +44 -36
  231. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_pairplot.py +14 -2
  232. scitex/plt/_subplots/_export_as_csv_formatters/_format_streamplot.py +11 -5
  233. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_bar.py +84 -0
  234. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_barh.py +85 -0
  235. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_conf_mat.py +14 -3
  236. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_contour.py +54 -0
  237. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_ecdf.py +14 -2
  238. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_errorbar.py +120 -0
  239. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_heatmap.py +16 -6
  240. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_image.py +29 -19
  241. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_imshow.py +63 -0
  242. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_joyplot.py +22 -5
  243. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_ci.py +18 -14
  244. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_std.py +18 -14
  245. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_median_iqr.py +18 -14
  246. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_raster.py +10 -2
  247. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter.py +51 -0
  248. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter_hist.py +18 -9
  249. scitex/plt/ax/_plot/_stx_ecdf.py +4 -2
  250. scitex/plt/gallery/_generate.py +421 -14
  251. scitex/plt/io/__init__.py +53 -0
  252. scitex/plt/io/_bundle.py +490 -0
  253. scitex/plt/io/_layered_bundle.py +1343 -0
  254. scitex/plt/styles/SCITEX_STYLE.yaml +26 -0
  255. scitex/plt/styles/__init__.py +14 -0
  256. scitex/plt/styles/presets.py +78 -0
  257. scitex/plt/utils/__init__.py +13 -1
  258. scitex/plt/utils/_collect_figure_metadata.py +10 -14
  259. scitex/plt/utils/_configure_mpl.py +6 -18
  260. scitex/plt/utils/_crop.py +32 -14
  261. scitex/plt/utils/_csv_column_naming.py +54 -0
  262. scitex/plt/utils/_figure_mm.py +116 -1
  263. scitex/plt/utils/_hitmap.py +1643 -0
  264. scitex/plt/utils/metadata/__init__.py +25 -0
  265. scitex/plt/utils/metadata/_core.py +9 -10
  266. scitex/plt/utils/metadata/_dimensions.py +6 -3
  267. scitex/plt/utils/metadata/_editable_export.py +405 -0
  268. scitex/plt/utils/metadata/_geometry_extraction.py +570 -0
  269. scitex/schema/__init__.py +109 -16
  270. scitex/schema/_canvas.py +1 -1
  271. scitex/schema/_plot.py +1015 -0
  272. scitex/schema/_stats.py +2 -2
  273. scitex/stats/__init__.py +117 -0
  274. scitex/stats/io/__init__.py +29 -0
  275. scitex/stats/io/_bundle.py +156 -0
  276. scitex/tex/__init__.py +4 -0
  277. scitex/tex/_export.py +890 -0
  278. {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/METADATA +11 -1
  279. {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/RECORD +294 -170
  280. scitex/io/memo.md +0 -2827
  281. scitex/plt/REQUESTS.md +0 -191
  282. scitex/plt/_subplots/TODO.md +0 -53
  283. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin.py +0 -559
  284. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin.py +0 -1609
  285. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin.py +0 -447
  286. scitex/plt/templates/research-master/scitex/vis/gallery/area/fill_between.json +0 -110
  287. scitex/plt/templates/research-master/scitex/vis/gallery/area/fill_betweenx.json +0 -88
  288. scitex/plt/templates/research-master/scitex/vis/gallery/area/stx_fill_between.json +0 -103
  289. scitex/plt/templates/research-master/scitex/vis/gallery/area/stx_fillv.json +0 -106
  290. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/bar.json +0 -92
  291. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/barh.json +0 -92
  292. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/boxplot.json +0 -92
  293. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_bar.json +0 -84
  294. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_barh.json +0 -84
  295. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_box.json +0 -83
  296. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_boxplot.json +0 -93
  297. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_violin.json +0 -91
  298. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_violinplot.json +0 -91
  299. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/violinplot.json +0 -91
  300. scitex/plt/templates/research-master/scitex/vis/gallery/contour/contour.json +0 -97
  301. scitex/plt/templates/research-master/scitex/vis/gallery/contour/contourf.json +0 -98
  302. scitex/plt/templates/research-master/scitex/vis/gallery/contour/stx_contour.json +0 -84
  303. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/hist.json +0 -101
  304. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/hist2d.json +0 -96
  305. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_ecdf.json +0 -95
  306. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_joyplot.json +0 -95
  307. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_kde.json +0 -93
  308. scitex/plt/templates/research-master/scitex/vis/gallery/grid/imshow.json +0 -95
  309. scitex/plt/templates/research-master/scitex/vis/gallery/grid/matshow.json +0 -95
  310. scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_conf_mat.json +0 -83
  311. scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_heatmap.json +0 -92
  312. scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_image.json +0 -121
  313. scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_imshow.json +0 -84
  314. scitex/plt/templates/research-master/scitex/vis/gallery/line/plot.json +0 -110
  315. scitex/plt/templates/research-master/scitex/vis/gallery/line/step.json +0 -92
  316. scitex/plt/templates/research-master/scitex/vis/gallery/line/stx_line.json +0 -95
  317. scitex/plt/templates/research-master/scitex/vis/gallery/line/stx_shaded_line.json +0 -96
  318. scitex/plt/templates/research-master/scitex/vis/gallery/scatter/hexbin.json +0 -95
  319. scitex/plt/templates/research-master/scitex/vis/gallery/scatter/scatter.json +0 -95
  320. scitex/plt/templates/research-master/scitex/vis/gallery/scatter/stem.json +0 -92
  321. scitex/plt/templates/research-master/scitex/vis/gallery/scatter/stx_scatter.json +0 -84
  322. scitex/plt/templates/research-master/scitex/vis/gallery/special/pie.json +0 -94
  323. scitex/plt/templates/research-master/scitex/vis/gallery/special/stx_raster.json +0 -109
  324. scitex/plt/templates/research-master/scitex/vis/gallery/special/stx_rectangle.json +0 -108
  325. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/errorbar.json +0 -93
  326. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_errorbar.json +0 -84
  327. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_mean_ci.json +0 -96
  328. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_mean_std.json +0 -96
  329. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_median_iqr.json +0 -96
  330. scitex/plt/templates/research-master/scitex/vis/gallery/vector/quiver.json +0 -99
  331. scitex/plt/templates/research-master/scitex/vis/gallery/vector/streamplot.json +0 -100
  332. scitex/vis/__init__.py +0 -177
  333. scitex/vis/editor/_edit.py +0 -390
  334. scitex/vis/editor/flask_editor/_bbox.py +0 -529
  335. scitex/vis/editor/flask_editor/_core.py +0 -168
  336. scitex/vis/editor/flask_editor/_renderer.py +0 -393
  337. scitex/vis/editor/flask_editor/templates/__init__.py +0 -33
  338. scitex/vis/editor/flask_editor/templates/_html.py +0 -513
  339. scitex/vis/editor/flask_editor/templates/_scripts.py +0 -1261
  340. scitex/vis/editor/flask_editor/templates/_styles.py +0 -739
  341. /scitex/{vis → fig}/README.md +0 -0
  342. /scitex/{vis → fig}/backend/__init__.py +0 -0
  343. /scitex/{vis → fig}/backend/_export.py +0 -0
  344. /scitex/{vis → fig}/backend/_render.py +0 -0
  345. /scitex/{vis → fig}/docs/CANVAS_ARCHITECTURE.md +0 -0
  346. /scitex/{vis → fig}/editor/_flask_editor.py +0 -0
  347. /scitex/{vis → fig}/editor/flask_editor/__init__.py +0 -0
  348. /scitex/{vis → fig}/editor/flask_editor/_utils.py +0 -0
  349. /scitex/{vis → fig}/io/_directory.py +0 -0
  350. /scitex/{vis → fig}/model/_plot_types.py +0 -0
  351. /scitex/{vis → fig}/utils/_defaults.py +0 -0
  352. /scitex/{vis → fig}/utils/_validate.py +0 -0
  353. {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/WHEEL +0 -0
  354. {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/entry_points.txt +0 -0
  355. {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/licenses/LICENSE +0 -0
scitex/audio/_tts.py ADDED
@@ -0,0 +1,334 @@
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/_tts.py
5
+ # ----------------------------------------
6
+
7
+ """
8
+ Text-to-Speech implementation using ElevenLabs API.
9
+
10
+ This module provides TTS functionality that can be used:
11
+ 1. Directly via the ElevenLabs Python SDK
12
+ 2. Via MCP server integration
13
+
14
+ Environment Variables:
15
+ ELEVENLABS_API_KEY: Your ElevenLabs API key
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import os
21
+ import subprocess
22
+ import tempfile
23
+ from dataclasses import dataclass, field
24
+ from pathlib import Path
25
+ from typing import Optional
26
+
27
+ __all__ = ["TTS", "speak"]
28
+
29
+
30
+ @dataclass
31
+ class TTSConfig:
32
+ """Configuration for TTS."""
33
+
34
+ voice_id: str = "21m00Tcm4TlvDq8ikWAM" # Rachel (default)
35
+ voice_name: Optional[str] = None
36
+ model_id: str = "eleven_multilingual_v2"
37
+ stability: float = 0.5
38
+ similarity_boost: float = 0.75
39
+ style: float = 0.0
40
+ speed: float = 1.0
41
+ output_format: str = "mp3_44100_128"
42
+
43
+
44
+ class TTS:
45
+ """Text-to-Speech using ElevenLabs API.
46
+
47
+ Examples:
48
+ # Basic usage
49
+ tts = TTS()
50
+ tts.speak("Hello, world!")
51
+
52
+ # With custom voice
53
+ tts = TTS(voice_name="Rachel")
54
+ tts.speak("Processing complete")
55
+
56
+ # Save to file without playing
57
+ tts.speak("Test", output_path="/tmp/test.mp3", play=False)
58
+ """
59
+
60
+ # Popular voice presets
61
+ VOICES = {
62
+ "rachel": "21m00Tcm4TlvDq8ikWAM",
63
+ "adam": "pNInz6obpgDQGcFmaJgB",
64
+ "antoni": "ErXwobaYiN019PkySvjV",
65
+ "bella": "EXAVITQu4vr4xnSDxMaL",
66
+ "domi": "AZnzlk1XvdvUeBnXmlld",
67
+ "elli": "MF3mGyEYCl7XYWbV9V6O",
68
+ "josh": "TxGEqnHWrfWFTfGW9XjX",
69
+ "sam": "yoZ06aMxZJJ28mfd3POQ",
70
+ }
71
+
72
+ def __init__(
73
+ self,
74
+ api_key: Optional[str] = None,
75
+ voice_name: Optional[str] = None,
76
+ voice_id: Optional[str] = None,
77
+ **kwargs,
78
+ ):
79
+ """Initialize TTS.
80
+
81
+ Args:
82
+ api_key: ElevenLabs API key. Defaults to ELEVENLABS_API_KEY env var.
83
+ voice_name: Voice name (e.g., "Rachel", "Adam").
84
+ voice_id: Direct voice ID (overrides voice_name).
85
+ **kwargs: Additional config options (stability, speed, etc.)
86
+ """
87
+ self.api_key = api_key or os.environ.get("ELEVENLABS_API_KEY")
88
+ self.config = TTSConfig(**kwargs)
89
+
90
+ if voice_id:
91
+ self.config.voice_id = voice_id
92
+ elif voice_name:
93
+ self.config.voice_name = voice_name
94
+ normalized = voice_name.lower()
95
+ if normalized in self.VOICES:
96
+ self.config.voice_id = self.VOICES[normalized]
97
+
98
+ self._client = None
99
+
100
+ @property
101
+ def client(self):
102
+ """Lazy-load ElevenLabs client."""
103
+ if self._client is None:
104
+ try:
105
+ from elevenlabs.client import ElevenLabs
106
+
107
+ self._client = ElevenLabs(api_key=self.api_key)
108
+ except ImportError:
109
+ raise ImportError(
110
+ "elevenlabs package not installed. "
111
+ "Install with: pip install elevenlabs"
112
+ )
113
+ return self._client
114
+
115
+ def speak(
116
+ self,
117
+ text: str,
118
+ output_path: Optional[str] = None,
119
+ play: bool = True,
120
+ voice_name: Optional[str] = None,
121
+ voice_id: Optional[str] = None,
122
+ ) -> Optional[Path]:
123
+ """Convert text to speech and optionally play it.
124
+
125
+ Args:
126
+ text: Text to convert to speech.
127
+ output_path: Path to save audio file. Auto-generated if None.
128
+ play: Whether to play the audio after generation.
129
+ voice_name: Override voice name for this call.
130
+ voice_id: Override voice ID for this call.
131
+
132
+ Returns:
133
+ Path to the generated audio file, or None if only played.
134
+ """
135
+ # Determine voice
136
+ vid = voice_id or self.config.voice_id
137
+ if voice_name and not voice_id:
138
+ normalized = voice_name.lower()
139
+ vid = self.VOICES.get(normalized, vid)
140
+
141
+ # Generate audio
142
+ audio = self.client.text_to_speech.convert(
143
+ text=text,
144
+ voice_id=vid,
145
+ model_id=self.config.model_id,
146
+ voice_settings={
147
+ "stability": self.config.stability,
148
+ "similarity_boost": self.config.similarity_boost,
149
+ "style": self.config.style,
150
+ "speed": self.config.speed,
151
+ },
152
+ output_format=self.config.output_format,
153
+ )
154
+
155
+ # Determine output path
156
+ if output_path:
157
+ out_path = Path(output_path)
158
+ else:
159
+ suffix = ".mp3" if "mp3" in self.config.output_format else ".wav"
160
+ fd, tmp_path = tempfile.mkstemp(suffix=suffix, prefix="scitex_tts_")
161
+ os.close(fd)
162
+ out_path = Path(tmp_path)
163
+
164
+ # Write audio to file
165
+ with open(out_path, "wb") as f:
166
+ for chunk in audio:
167
+ f.write(chunk)
168
+
169
+ # Play if requested
170
+ if play:
171
+ self._play_audio(out_path)
172
+
173
+ return out_path if output_path else None
174
+
175
+ def _play_audio(self, path: Path) -> None:
176
+ """Play audio file using available system player.
177
+
178
+ Includes Windows fallback for WSL environments.
179
+ """
180
+ # Check if we're in WSL - if so, prefer Windows playback directly
181
+ # to avoid double playback issues with Linux audio hanging
182
+ if os.path.exists("/mnt/c/Windows"):
183
+ if self._play_audio_windows(path):
184
+ return
185
+ # Fall through to Linux players if Windows playback fails
186
+
187
+ players = [
188
+ ["mpv", "--no-video", str(path)],
189
+ ["ffplay", "-nodisp", "-autoexit", str(path)],
190
+ ["aplay", str(path)],
191
+ ["afplay", str(path)], # macOS
192
+ ]
193
+
194
+ for player_cmd in players:
195
+ try:
196
+ subprocess.run(
197
+ player_cmd,
198
+ check=True,
199
+ stdout=subprocess.DEVNULL,
200
+ stderr=subprocess.DEVNULL,
201
+ timeout=30,
202
+ )
203
+ return
204
+ except subprocess.TimeoutExpired:
205
+ # Audio playback hung, don't try more players
206
+ return
207
+ except (subprocess.CalledProcessError, FileNotFoundError):
208
+ continue
209
+
210
+ print(f"Warning: No audio player found. Audio saved to: {path}")
211
+
212
+ def _play_audio_windows(self, path: Path) -> bool:
213
+ """Play audio via Windows PowerShell SoundPlayer (WSL fallback).
214
+
215
+ Uses headless SoundPlayer - no GUI popup.
216
+ """
217
+ import shutil
218
+ import tempfile
219
+
220
+ # Check if we're in WSL
221
+ if not os.path.exists("/mnt/c/Windows"):
222
+ return False
223
+
224
+ powershell = shutil.which("powershell.exe")
225
+ if not powershell:
226
+ return False
227
+
228
+ try:
229
+ # SoundPlayer only supports WAV, so convert if needed
230
+ wav_path = path
231
+ if path.suffix.lower() in ('.mp3', '.ogg', '.m4a'):
232
+ try:
233
+ from pydub import AudioSegment
234
+ fd, tmp_wav = tempfile.mkstemp(suffix='.wav', prefix='scitex_')
235
+ os.close(fd)
236
+ wav_path = Path(tmp_wav)
237
+ audio = AudioSegment.from_file(str(path))
238
+ audio.export(str(wav_path), format='wav')
239
+ except ImportError:
240
+ pass
241
+
242
+ result = subprocess.run(
243
+ ["wslpath", "-w", str(wav_path)],
244
+ capture_output=True,
245
+ text=True,
246
+ timeout=5,
247
+ )
248
+ if result.returncode != 0:
249
+ return False
250
+
251
+ windows_path = result.stdout.strip()
252
+
253
+ ps_command = f'''
254
+ $player = New-Object System.Media.SoundPlayer
255
+ $player.SoundLocation = "{windows_path}"
256
+ $player.PlaySync()
257
+ '''
258
+ subprocess.run(
259
+ [powershell, "-NoProfile", "-Command", ps_command],
260
+ stdout=subprocess.DEVNULL,
261
+ stderr=subprocess.DEVNULL,
262
+ timeout=60,
263
+ )
264
+
265
+ # Clean up temp WAV
266
+ if wav_path != path and wav_path.exists():
267
+ try:
268
+ wav_path.unlink()
269
+ except Exception:
270
+ pass
271
+
272
+ return True
273
+
274
+ except Exception:
275
+ return False
276
+
277
+ def list_voices(self) -> list:
278
+ """List available voices from ElevenLabs."""
279
+ response = self.client.voices.get_all()
280
+ return [
281
+ {"name": v.name, "voice_id": v.voice_id, "labels": v.labels}
282
+ for v in response.voices
283
+ ]
284
+
285
+
286
+ # Module-level convenience function
287
+ _default_tts: Optional[TTS] = None
288
+
289
+
290
+ def speak(
291
+ text: str,
292
+ voice: Optional[str] = None,
293
+ play: bool = True,
294
+ output_path: Optional[str] = None,
295
+ **kwargs,
296
+ ) -> Optional[Path]:
297
+ """Convenience function for quick TTS.
298
+
299
+ Args:
300
+ text: Text to speak.
301
+ voice: Voice name (e.g., "Rachel", "Adam").
302
+ play: Whether to play audio.
303
+ output_path: Optional path to save audio.
304
+ **kwargs: Additional TTS config options.
305
+
306
+ Returns:
307
+ Path to audio file if output_path specified, else None.
308
+
309
+ Examples:
310
+ import scitex
311
+
312
+ # Simple speak
313
+ scitex.audio.speak("Hello!")
314
+
315
+ # With specific voice
316
+ scitex.audio.speak("Processing complete", voice="Adam")
317
+
318
+ # Save without playing
319
+ scitex.audio.speak("Test", play=False, output_path="/tmp/test.mp3")
320
+ """
321
+ global _default_tts
322
+
323
+ if _default_tts is None or kwargs:
324
+ _default_tts = TTS(**kwargs)
325
+
326
+ return _default_tts.speak(
327
+ text=text,
328
+ voice_name=voice,
329
+ play=play,
330
+ output_path=output_path,
331
+ )
332
+
333
+
334
+ # EOF
@@ -0,0 +1,44 @@
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/engines/__init__.py
5
+ # ----------------------------------------
6
+
7
+ """
8
+ TTS Engine Backends
9
+
10
+ Fallback order: pyttsx3 -> gtts -> elevenlabs
11
+
12
+ Engines:
13
+ - SystemTTS (pyttsx3): Offline, free, uses system TTS
14
+ - GoogleTTS (gtts): Free, requires internet
15
+ - ElevenLabsTTS: Paid, high quality
16
+ """
17
+
18
+ from .base import BaseTTS, TTSBackend
19
+
20
+ # Import engines (fail gracefully if dependencies missing)
21
+ try:
22
+ from .pyttsx3_engine import SystemTTS
23
+ except ImportError:
24
+ SystemTTS = None
25
+
26
+ try:
27
+ from .gtts_engine import GoogleTTS
28
+ except ImportError:
29
+ GoogleTTS = None
30
+
31
+ try:
32
+ from .elevenlabs_engine import ElevenLabsTTS
33
+ except ImportError:
34
+ ElevenLabsTTS = None
35
+
36
+ __all__ = [
37
+ "BaseTTS",
38
+ "TTSBackend",
39
+ "SystemTTS",
40
+ "GoogleTTS",
41
+ "ElevenLabsTTS",
42
+ ]
43
+
44
+ # EOF
@@ -0,0 +1,275 @@
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/engines/base.py
5
+ # ----------------------------------------
6
+
7
+ """
8
+ Base TTS class defining the common interface for all TTS backends.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import subprocess
14
+ from abc import ABC, abstractmethod
15
+ from pathlib import Path
16
+ from typing import List, Optional
17
+
18
+ __all__ = ["BaseTTS", "TTSBackend"]
19
+
20
+
21
+ class TTSBackend:
22
+ """Enum-like class for TTS backend types."""
23
+
24
+ ELEVENLABS = "elevenlabs"
25
+ GTTS = "gtts"
26
+ PYTTSX3 = "pyttsx3"
27
+ EDGE = "edge" # Future: edge-tts
28
+
29
+ @classmethod
30
+ def available(cls) -> List[str]:
31
+ """Return list of available backends."""
32
+ backends = []
33
+
34
+ # Check gTTS (always available if installed, needs internet)
35
+ try:
36
+ import gtts
37
+
38
+ backends.append(cls.GTTS)
39
+ except ImportError:
40
+ pass
41
+
42
+ # Check pyttsx3
43
+ try:
44
+ import pyttsx3
45
+
46
+ backends.append(cls.PYTTSX3)
47
+ except ImportError:
48
+ pass
49
+
50
+ # Check ElevenLabs
51
+ try:
52
+ import elevenlabs
53
+ import os
54
+
55
+ if os.environ.get("ELEVENLABS_API_KEY"):
56
+ backends.append(cls.ELEVENLABS)
57
+ except ImportError:
58
+ pass
59
+
60
+ return backends
61
+
62
+
63
+ class BaseTTS(ABC):
64
+ """Abstract base class for TTS implementations."""
65
+
66
+ def __init__(self, **kwargs):
67
+ self.config = kwargs
68
+
69
+ @abstractmethod
70
+ def synthesize(self, text: str, output_path: str) -> Path:
71
+ """Synthesize text to audio file.
72
+
73
+ Args:
74
+ text: Text to convert to speech.
75
+ output_path: Path to save the audio file.
76
+
77
+ Returns:
78
+ Path to the generated audio file.
79
+ """
80
+ pass
81
+
82
+ @abstractmethod
83
+ def get_voices(self) -> List[dict]:
84
+ """Get available voices for this backend.
85
+
86
+ Returns:
87
+ List of voice dictionaries with 'name' and 'id' keys.
88
+ """
89
+ pass
90
+
91
+ @property
92
+ @abstractmethod
93
+ def name(self) -> str:
94
+ """Return the backend name."""
95
+ pass
96
+
97
+ @property
98
+ def requires_api_key(self) -> bool:
99
+ """Whether this backend requires an API key."""
100
+ return False
101
+
102
+ @property
103
+ def requires_internet(self) -> bool:
104
+ """Whether this backend requires internet connection."""
105
+ return False
106
+
107
+ def speak(
108
+ self,
109
+ text: str,
110
+ output_path: Optional[str] = None,
111
+ play: bool = True,
112
+ voice: Optional[str] = None,
113
+ ) -> Optional[Path]:
114
+ """Synthesize and optionally play text.
115
+
116
+ Args:
117
+ text: Text to speak.
118
+ output_path: Optional path to save audio.
119
+ play: Whether to play the audio.
120
+ voice: Optional voice name/id.
121
+
122
+ Returns:
123
+ Path to audio file if output_path specified, else None.
124
+ """
125
+ import tempfile
126
+
127
+ # Determine output path
128
+ if output_path:
129
+ out_path = Path(output_path)
130
+ else:
131
+ suffix = ".mp3"
132
+ fd, tmp_path = tempfile.mkstemp(suffix=suffix, prefix="scitex_tts_")
133
+ import os
134
+
135
+ os.close(fd)
136
+ out_path = Path(tmp_path)
137
+
138
+ # Set voice if provided
139
+ if voice:
140
+ self.config["voice"] = voice
141
+
142
+ # Synthesize
143
+ result_path = self.synthesize(text, str(out_path))
144
+
145
+ # Play if requested
146
+ if play:
147
+ self._play_audio(result_path)
148
+
149
+ # Return path only if explicitly requested
150
+ if output_path:
151
+ return result_path
152
+
153
+ return None
154
+
155
+ def _play_audio(self, path: Path) -> None:
156
+ """Play audio file using available system player.
157
+
158
+ Includes Windows fallback for WSL environments where PulseAudio
159
+ may be unstable.
160
+ """
161
+ import os
162
+
163
+ # Check if we're in WSL - if so, prefer Windows playback directly
164
+ # to avoid double playback issues with Linux audio hanging
165
+ if os.path.exists("/mnt/c/Windows"):
166
+ if self._play_audio_windows(path):
167
+ return
168
+ # Fall through to Linux players if Windows playback fails
169
+
170
+ players = [
171
+ ["ffplay", "-nodisp", "-autoexit", str(path)],
172
+ ["mpv", "--no-video", str(path)],
173
+ ["aplay", str(path)],
174
+ ["afplay", str(path)], # macOS
175
+ ]
176
+
177
+ for player_cmd in players:
178
+ try:
179
+ subprocess.run(
180
+ player_cmd,
181
+ check=True,
182
+ stdout=subprocess.DEVNULL,
183
+ stderr=subprocess.DEVNULL,
184
+ timeout=30,
185
+ )
186
+ return
187
+ except subprocess.TimeoutExpired:
188
+ # Audio playback hung, don't try more players
189
+ return
190
+ except (subprocess.CalledProcessError, FileNotFoundError):
191
+ continue
192
+
193
+ print(f"Warning: No audio player found. Audio saved to: {path}")
194
+
195
+ def _play_audio_windows(self, path: Path) -> bool:
196
+ """Play audio via Windows PowerShell SoundPlayer (WSL fallback).
197
+
198
+ This is useful when WSLg PulseAudio connection is unstable.
199
+ Uses System.Media.SoundPlayer which is headless (no GUI).
200
+
201
+ Args:
202
+ path: Path to audio file (in WSL filesystem)
203
+
204
+ Returns:
205
+ True if playback succeeded, False otherwise
206
+ """
207
+ import os
208
+ import shutil
209
+ import tempfile
210
+
211
+ # Check if we're in WSL
212
+ if not os.path.exists("/mnt/c/Windows"):
213
+ return False
214
+
215
+ # Check if powershell.exe is available
216
+ powershell = shutil.which("powershell.exe")
217
+ if not powershell:
218
+ return False
219
+
220
+ try:
221
+ # SoundPlayer only supports WAV, so convert if needed
222
+ wav_path = path
223
+ if path.suffix.lower() in ('.mp3', '.ogg', '.m4a'):
224
+ try:
225
+ from pydub import AudioSegment
226
+ # Create temp WAV file
227
+ fd, tmp_wav = tempfile.mkstemp(suffix='.wav', prefix='scitex_')
228
+ os.close(fd)
229
+ wav_path = Path(tmp_wav)
230
+
231
+ audio = AudioSegment.from_file(str(path))
232
+ audio.export(str(wav_path), format='wav')
233
+ except ImportError:
234
+ # pydub not available, try direct playback anyway
235
+ pass
236
+
237
+ # Convert WSL path to Windows path
238
+ result = subprocess.run(
239
+ ["wslpath", "-w", str(wav_path)],
240
+ capture_output=True,
241
+ text=True,
242
+ timeout=5,
243
+ )
244
+ if result.returncode != 0:
245
+ return False
246
+
247
+ windows_path = result.stdout.strip()
248
+
249
+ # Play using PowerShell's SoundPlayer (headless, no GUI)
250
+ ps_command = f'''
251
+ $player = New-Object System.Media.SoundPlayer
252
+ $player.SoundLocation = "{windows_path}"
253
+ $player.PlaySync()
254
+ '''
255
+ subprocess.run(
256
+ [powershell, "-NoProfile", "-Command", ps_command],
257
+ stdout=subprocess.DEVNULL,
258
+ stderr=subprocess.DEVNULL,
259
+ timeout=60,
260
+ )
261
+
262
+ # Clean up temp WAV if created
263
+ if wav_path != path and wav_path.exists():
264
+ try:
265
+ wav_path.unlink()
266
+ except Exception:
267
+ pass
268
+
269
+ return True
270
+
271
+ except (subprocess.TimeoutExpired, subprocess.CalledProcessError, Exception):
272
+ return False
273
+
274
+
275
+ # EOF