figrecipe 0.6.0__py3-none-any.whl → 0.9.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.
Files changed (269) hide show
  1. figrecipe/__init__.py +161 -1030
  2. figrecipe/__main__.py +12 -0
  3. figrecipe/_api/__init__.py +48 -0
  4. figrecipe/_api/_extract.py +108 -0
  5. figrecipe/_api/_notebook.py +61 -0
  6. figrecipe/_api/_panel.py +113 -0
  7. figrecipe/_api/_save.py +287 -0
  8. figrecipe/_api/_seaborn_proxy.py +34 -0
  9. figrecipe/_api/_style_manager.py +153 -0
  10. figrecipe/_api/_subplots.py +333 -0
  11. figrecipe/_api/_validate.py +82 -0
  12. figrecipe/_cli/__init__.py +7 -0
  13. figrecipe/_cli/_compose.py +87 -0
  14. figrecipe/_cli/_convert.py +117 -0
  15. figrecipe/_cli/_crop.py +82 -0
  16. figrecipe/_cli/_edit.py +70 -0
  17. figrecipe/_cli/_extract.py +128 -0
  18. figrecipe/_cli/_fonts.py +47 -0
  19. figrecipe/_cli/_info.py +67 -0
  20. figrecipe/_cli/_main.py +58 -0
  21. figrecipe/_cli/_reproduce.py +79 -0
  22. figrecipe/_cli/_style.py +77 -0
  23. figrecipe/_cli/_validate.py +66 -0
  24. figrecipe/_cli/_version.py +50 -0
  25. figrecipe/_composition/__init__.py +32 -0
  26. figrecipe/_composition/_alignment.py +452 -0
  27. figrecipe/_composition/_compose.py +179 -0
  28. figrecipe/_composition/_import_axes.py +127 -0
  29. figrecipe/_composition/_visibility.py +125 -0
  30. figrecipe/_dev/__init__.py +4 -93
  31. figrecipe/_dev/_plotters.py +76 -0
  32. figrecipe/_dev/_run_demos.py +56 -0
  33. figrecipe/_dev/browser/__init__.py +69 -0
  34. figrecipe/_dev/browser/_audio.py +240 -0
  35. figrecipe/_dev/browser/_caption.py +356 -0
  36. figrecipe/_dev/browser/_click_effect.py +146 -0
  37. figrecipe/_dev/browser/_cursor.py +196 -0
  38. figrecipe/_dev/browser/_highlight.py +105 -0
  39. figrecipe/_dev/browser/_narration.py +237 -0
  40. figrecipe/_dev/browser/_recorder.py +446 -0
  41. figrecipe/_dev/browser/_utils.py +178 -0
  42. figrecipe/_dev/browser/_video_trim/__init__.py +152 -0
  43. figrecipe/_dev/browser/_video_trim/_detection.py +223 -0
  44. figrecipe/_dev/browser/_video_trim/_markers.py +140 -0
  45. figrecipe/_dev/demo_plotters/__init__.py +35 -166
  46. figrecipe/_dev/demo_plotters/_categories.py +81 -0
  47. figrecipe/_dev/demo_plotters/_figure_creators.py +119 -0
  48. figrecipe/_dev/demo_plotters/_helpers.py +31 -0
  49. figrecipe/_dev/demo_plotters/_registry.py +50 -0
  50. figrecipe/_dev/demo_plotters/bar_categorical/__init__.py +4 -0
  51. figrecipe/_dev/demo_plotters/contour_surface/__init__.py +4 -0
  52. figrecipe/_dev/demo_plotters/distribution/__init__.py +4 -0
  53. figrecipe/_dev/demo_plotters/image_matrix/__init__.py +4 -0
  54. figrecipe/_dev/demo_plotters/line_curve/__init__.py +4 -0
  55. figrecipe/_dev/demo_plotters/{plot_plot.py → line_curve/plot_plot.py} +3 -2
  56. figrecipe/_dev/demo_plotters/scatter_points/__init__.py +4 -0
  57. figrecipe/_dev/demo_plotters/special/__init__.py +4 -0
  58. figrecipe/_dev/demo_plotters/{plot_pie.py → special/plot_pie.py} +5 -1
  59. figrecipe/_dev/demo_plotters/spectral_signal/__init__.py +4 -0
  60. figrecipe/_dev/demo_plotters/vector_flow/__init__.py +4 -0
  61. figrecipe/_editor/__init__.py +61 -13
  62. figrecipe/_editor/_bbox/__init__.py +43 -0
  63. figrecipe/_editor/_bbox/_collections.py +177 -0
  64. figrecipe/_editor/_bbox/_elements.py +159 -0
  65. figrecipe/_editor/_bbox/_extract.py +402 -0
  66. figrecipe/_editor/_bbox/_extract_axes.py +370 -0
  67. figrecipe/_editor/_bbox/_extract_text.py +466 -0
  68. figrecipe/_editor/_bbox/_lines.py +173 -0
  69. figrecipe/_editor/_bbox/_transforms.py +146 -0
  70. figrecipe/_editor/_call_overrides.py +183 -0
  71. figrecipe/_editor/_datatable_plot_handlers.py +249 -0
  72. figrecipe/_editor/_figure_layout.py +211 -0
  73. figrecipe/_editor/_flask_app.py +200 -1030
  74. figrecipe/_editor/_helpers.py +251 -0
  75. figrecipe/_editor/_hitmap/__init__.py +76 -0
  76. figrecipe/_editor/_hitmap/_artists/__init__.py +21 -0
  77. figrecipe/_editor/_hitmap/_artists/_collections.py +345 -0
  78. figrecipe/_editor/_hitmap/_artists/_images.py +68 -0
  79. figrecipe/_editor/_hitmap/_artists/_lines.py +107 -0
  80. figrecipe/_editor/_hitmap/_artists/_patches.py +163 -0
  81. figrecipe/_editor/_hitmap/_artists/_text.py +190 -0
  82. figrecipe/_editor/_hitmap/_colors.py +181 -0
  83. figrecipe/_editor/_hitmap/_detect.py +194 -0
  84. figrecipe/_editor/_hitmap/_restore.py +154 -0
  85. figrecipe/_editor/_hitmap_main.py +182 -0
  86. figrecipe/_editor/_overrides.py +4 -1
  87. figrecipe/_editor/_plot_types_registry.py +190 -0
  88. figrecipe/_editor/_preferences.py +135 -0
  89. figrecipe/_editor/_render_overrides.py +507 -0
  90. figrecipe/_editor/_renderer.py +81 -186
  91. figrecipe/_editor/_routes_annotation.py +114 -0
  92. figrecipe/_editor/_routes_axis.py +482 -0
  93. figrecipe/_editor/_routes_captions.py +130 -0
  94. figrecipe/_editor/_routes_composition.py +270 -0
  95. figrecipe/_editor/_routes_core.py +126 -0
  96. figrecipe/_editor/_routes_datatable.py +364 -0
  97. figrecipe/_editor/_routes_element.py +335 -0
  98. figrecipe/_editor/_routes_files.py +443 -0
  99. figrecipe/_editor/_routes_image.py +200 -0
  100. figrecipe/_editor/_routes_snapshot.py +94 -0
  101. figrecipe/_editor/_routes_style.py +243 -0
  102. figrecipe/_editor/_templates/__init__.py +116 -1
  103. figrecipe/_editor/_templates/_html.py +154 -64
  104. figrecipe/_editor/_templates/_html_components/__init__.py +13 -0
  105. figrecipe/_editor/_templates/_html_components/_composition_toolbar.py +79 -0
  106. figrecipe/_editor/_templates/_html_components/_file_browser.py +41 -0
  107. figrecipe/_editor/_templates/_html_datatable.py +92 -0
  108. figrecipe/_editor/_templates/_scripts/__init__.py +178 -0
  109. figrecipe/_editor/_templates/_scripts/_accordion.py +328 -0
  110. figrecipe/_editor/_templates/_scripts/_annotation_drag.py +504 -0
  111. figrecipe/_editor/_templates/_scripts/_api.py +228 -0
  112. figrecipe/_editor/_templates/_scripts/_canvas_context_menu.py +182 -0
  113. figrecipe/_editor/_templates/_scripts/_captions.py +231 -0
  114. figrecipe/_editor/_templates/_scripts/_colors.py +485 -0
  115. figrecipe/_editor/_templates/_scripts/_composition.py +283 -0
  116. figrecipe/_editor/_templates/_scripts/_core.py +493 -0
  117. figrecipe/_editor/_templates/_scripts/_datatable/__init__.py +59 -0
  118. figrecipe/_editor/_templates/_scripts/_datatable/_cell_edit.py +97 -0
  119. figrecipe/_editor/_templates/_scripts/_datatable/_clipboard.py +164 -0
  120. figrecipe/_editor/_templates/_scripts/_datatable/_context_menu.py +221 -0
  121. figrecipe/_editor/_templates/_scripts/_datatable/_core.py +150 -0
  122. figrecipe/_editor/_templates/_scripts/_datatable/_editable.py +511 -0
  123. figrecipe/_editor/_templates/_scripts/_datatable/_import.py +161 -0
  124. figrecipe/_editor/_templates/_scripts/_datatable/_plot.py +261 -0
  125. figrecipe/_editor/_templates/_scripts/_datatable/_selection.py +438 -0
  126. figrecipe/_editor/_templates/_scripts/_datatable/_table.py +256 -0
  127. figrecipe/_editor/_templates/_scripts/_datatable/_tabs.py +354 -0
  128. figrecipe/_editor/_templates/_scripts/_debug_snapshot.py +186 -0
  129. figrecipe/_editor/_templates/_scripts/_element_editor.py +325 -0
  130. figrecipe/_editor/_templates/_scripts/_files.py +429 -0
  131. figrecipe/_editor/_templates/_scripts/_files_context_menu.py +240 -0
  132. figrecipe/_editor/_templates/_scripts/_hitmap.py +512 -0
  133. figrecipe/_editor/_templates/_scripts/_image_drop.py +428 -0
  134. figrecipe/_editor/_templates/_scripts/_inspector.py +315 -0
  135. figrecipe/_editor/_templates/_scripts/_labels.py +464 -0
  136. figrecipe/_editor/_templates/_scripts/_legend_drag.py +270 -0
  137. figrecipe/_editor/_templates/_scripts/_modals.py +226 -0
  138. figrecipe/_editor/_templates/_scripts/_multi_select.py +198 -0
  139. figrecipe/_editor/_templates/_scripts/_overlays.py +292 -0
  140. figrecipe/_editor/_templates/_scripts/_panel_drag.py +505 -0
  141. figrecipe/_editor/_templates/_scripts/_panel_drag_snapshot.py +33 -0
  142. figrecipe/_editor/_templates/_scripts/_panel_position.py +463 -0
  143. figrecipe/_editor/_templates/_scripts/_panel_resize.py +230 -0
  144. figrecipe/_editor/_templates/_scripts/_panel_snap.py +307 -0
  145. figrecipe/_editor/_templates/_scripts/_region_select.py +255 -0
  146. figrecipe/_editor/_templates/_scripts/_selection.py +244 -0
  147. figrecipe/_editor/_templates/_scripts/_sync.py +242 -0
  148. figrecipe/_editor/_templates/_scripts/_tabs.py +89 -0
  149. figrecipe/_editor/_templates/_scripts/_undo_redo.py +348 -0
  150. figrecipe/_editor/_templates/_scripts/_view_mode.py +107 -0
  151. figrecipe/_editor/_templates/_scripts/_zoom.py +212 -0
  152. figrecipe/_editor/_templates/_styles/__init__.py +78 -0
  153. figrecipe/_editor/_templates/_styles/_base.py +111 -0
  154. figrecipe/_editor/_templates/_styles/_buttons.py +327 -0
  155. figrecipe/_editor/_templates/_styles/_color_input.py +123 -0
  156. figrecipe/_editor/_templates/_styles/_composition.py +87 -0
  157. figrecipe/_editor/_templates/_styles/_controls.py +430 -0
  158. figrecipe/_editor/_templates/_styles/_datatable/__init__.py +40 -0
  159. figrecipe/_editor/_templates/_styles/_datatable/_editable.py +203 -0
  160. figrecipe/_editor/_templates/_styles/_datatable/_panel.py +268 -0
  161. figrecipe/_editor/_templates/_styles/_datatable/_table.py +479 -0
  162. figrecipe/_editor/_templates/_styles/_datatable/_toolbar.py +384 -0
  163. figrecipe/_editor/_templates/_styles/_datatable/_vars.py +123 -0
  164. figrecipe/_editor/_templates/_styles/_dynamic_props.py +144 -0
  165. figrecipe/_editor/_templates/_styles/_file_browser.py +466 -0
  166. figrecipe/_editor/_templates/_styles/_forms.py +224 -0
  167. figrecipe/_editor/_templates/_styles/_hitmap.py +191 -0
  168. figrecipe/_editor/_templates/_styles/_inspector.py +90 -0
  169. figrecipe/_editor/_templates/_styles/_labels.py +118 -0
  170. figrecipe/_editor/_templates/_styles/_modals.py +127 -0
  171. figrecipe/_editor/_templates/_styles/_overlays.py +130 -0
  172. figrecipe/_editor/_templates/_styles/_preview.py +430 -0
  173. figrecipe/_editor/_templates/_styles/_selection.py +73 -0
  174. figrecipe/_editor/_templates/_styles/_spinner.py +117 -0
  175. figrecipe/_editor/static/audio/click.mp3 +0 -0
  176. figrecipe/_editor/static/click.mp3 +0 -0
  177. figrecipe/_editor/static/icons/favicon.ico +0 -0
  178. figrecipe/_integrations/__init__.py +17 -0
  179. figrecipe/_integrations/_scitex_stats.py +298 -0
  180. figrecipe/_params/_DECORATION_METHODS.py +8 -0
  181. figrecipe/_recorder.py +63 -109
  182. figrecipe/_recorder_utils.py +124 -0
  183. figrecipe/_reproducer/__init__.py +18 -0
  184. figrecipe/_reproducer/_core.py +509 -0
  185. figrecipe/_reproducer/_custom_plots.py +279 -0
  186. figrecipe/_reproducer/_seaborn.py +100 -0
  187. figrecipe/_reproducer/_violin.py +186 -0
  188. figrecipe/_signatures/_kwargs.py +273 -0
  189. figrecipe/_signatures/_loader.py +21 -423
  190. figrecipe/_signatures/_parsing.py +147 -0
  191. figrecipe/_utils/__init__.py +3 -0
  192. figrecipe/_utils/_bundle.py +205 -0
  193. figrecipe/_wrappers/_axes.py +252 -895
  194. figrecipe/_wrappers/_axes_helpers.py +136 -0
  195. figrecipe/_wrappers/_axes_plots.py +418 -0
  196. figrecipe/_wrappers/_axes_seaborn.py +157 -0
  197. figrecipe/_wrappers/_caption_generator.py +218 -0
  198. figrecipe/_wrappers/_figure.py +188 -1
  199. figrecipe/_wrappers/_panel_labels.py +127 -0
  200. figrecipe/_wrappers/_plot_helpers.py +143 -0
  201. figrecipe/_wrappers/_stat_annotation.py +274 -0
  202. figrecipe/_wrappers/_violin_helpers.py +180 -0
  203. figrecipe/styles/__init__.py +8 -6
  204. figrecipe/styles/_dotdict.py +72 -0
  205. figrecipe/styles/_finalize.py +134 -0
  206. figrecipe/styles/_fonts.py +77 -0
  207. figrecipe/styles/_kwargs_converter.py +178 -0
  208. figrecipe/styles/_plot_styles.py +209 -0
  209. figrecipe/styles/_style_applier.py +42 -480
  210. figrecipe/styles/_style_loader.py +16 -192
  211. figrecipe/styles/_themes.py +151 -0
  212. figrecipe/styles/presets/MATPLOTLIB.yaml +2 -1
  213. figrecipe/styles/presets/SCITEX.yaml +40 -28
  214. figrecipe-0.9.0.dist-info/METADATA +427 -0
  215. figrecipe-0.9.0.dist-info/RECORD +277 -0
  216. figrecipe-0.9.0.dist-info/entry_points.txt +2 -0
  217. figrecipe/_editor/_bbox.py +0 -978
  218. figrecipe/_editor/_hitmap.py +0 -937
  219. figrecipe/_editor/_templates/_scripts.py +0 -2778
  220. figrecipe/_editor/_templates/_styles.py +0 -1326
  221. figrecipe/_reproducer.py +0 -975
  222. figrecipe-0.6.0.dist-info/METADATA +0 -394
  223. figrecipe-0.6.0.dist-info/RECORD +0 -90
  224. /figrecipe/_dev/demo_plotters/{plot_bar.py → bar_categorical/plot_bar.py} +0 -0
  225. /figrecipe/_dev/demo_plotters/{plot_barh.py → bar_categorical/plot_barh.py} +0 -0
  226. /figrecipe/_dev/demo_plotters/{plot_contour.py → contour_surface/plot_contour.py} +0 -0
  227. /figrecipe/_dev/demo_plotters/{plot_contourf.py → contour_surface/plot_contourf.py} +0 -0
  228. /figrecipe/_dev/demo_plotters/{plot_tricontour.py → contour_surface/plot_tricontour.py} +0 -0
  229. /figrecipe/_dev/demo_plotters/{plot_tricontourf.py → contour_surface/plot_tricontourf.py} +0 -0
  230. /figrecipe/_dev/demo_plotters/{plot_tripcolor.py → contour_surface/plot_tripcolor.py} +0 -0
  231. /figrecipe/_dev/demo_plotters/{plot_triplot.py → contour_surface/plot_triplot.py} +0 -0
  232. /figrecipe/_dev/demo_plotters/{plot_boxplot.py → distribution/plot_boxplot.py} +0 -0
  233. /figrecipe/_dev/demo_plotters/{plot_ecdf.py → distribution/plot_ecdf.py} +0 -0
  234. /figrecipe/_dev/demo_plotters/{plot_hist.py → distribution/plot_hist.py} +0 -0
  235. /figrecipe/_dev/demo_plotters/{plot_hist2d.py → distribution/plot_hist2d.py} +0 -0
  236. /figrecipe/_dev/demo_plotters/{plot_violinplot.py → distribution/plot_violinplot.py} +0 -0
  237. /figrecipe/_dev/demo_plotters/{plot_hexbin.py → image_matrix/plot_hexbin.py} +0 -0
  238. /figrecipe/_dev/demo_plotters/{plot_imshow.py → image_matrix/plot_imshow.py} +0 -0
  239. /figrecipe/_dev/demo_plotters/{plot_matshow.py → image_matrix/plot_matshow.py} +0 -0
  240. /figrecipe/_dev/demo_plotters/{plot_pcolor.py → image_matrix/plot_pcolor.py} +0 -0
  241. /figrecipe/_dev/demo_plotters/{plot_pcolormesh.py → image_matrix/plot_pcolormesh.py} +0 -0
  242. /figrecipe/_dev/demo_plotters/{plot_spy.py → image_matrix/plot_spy.py} +0 -0
  243. /figrecipe/_dev/demo_plotters/{plot_errorbar.py → line_curve/plot_errorbar.py} +0 -0
  244. /figrecipe/_dev/demo_plotters/{plot_fill.py → line_curve/plot_fill.py} +0 -0
  245. /figrecipe/_dev/demo_plotters/{plot_fill_between.py → line_curve/plot_fill_between.py} +0 -0
  246. /figrecipe/_dev/demo_plotters/{plot_fill_betweenx.py → line_curve/plot_fill_betweenx.py} +0 -0
  247. /figrecipe/_dev/demo_plotters/{plot_stackplot.py → line_curve/plot_stackplot.py} +0 -0
  248. /figrecipe/_dev/demo_plotters/{plot_stairs.py → line_curve/plot_stairs.py} +0 -0
  249. /figrecipe/_dev/demo_plotters/{plot_step.py → line_curve/plot_step.py} +0 -0
  250. /figrecipe/_dev/demo_plotters/{plot_scatter.py → scatter_points/plot_scatter.py} +0 -0
  251. /figrecipe/_dev/demo_plotters/{plot_eventplot.py → special/plot_eventplot.py} +0 -0
  252. /figrecipe/_dev/demo_plotters/{plot_loglog.py → special/plot_loglog.py} +0 -0
  253. /figrecipe/_dev/demo_plotters/{plot_semilogx.py → special/plot_semilogx.py} +0 -0
  254. /figrecipe/_dev/demo_plotters/{plot_semilogy.py → special/plot_semilogy.py} +0 -0
  255. /figrecipe/_dev/demo_plotters/{plot_stem.py → special/plot_stem.py} +0 -0
  256. /figrecipe/_dev/demo_plotters/{plot_acorr.py → spectral_signal/plot_acorr.py} +0 -0
  257. /figrecipe/_dev/demo_plotters/{plot_angle_spectrum.py → spectral_signal/plot_angle_spectrum.py} +0 -0
  258. /figrecipe/_dev/demo_plotters/{plot_cohere.py → spectral_signal/plot_cohere.py} +0 -0
  259. /figrecipe/_dev/demo_plotters/{plot_csd.py → spectral_signal/plot_csd.py} +0 -0
  260. /figrecipe/_dev/demo_plotters/{plot_magnitude_spectrum.py → spectral_signal/plot_magnitude_spectrum.py} +0 -0
  261. /figrecipe/_dev/demo_plotters/{plot_phase_spectrum.py → spectral_signal/plot_phase_spectrum.py} +0 -0
  262. /figrecipe/_dev/demo_plotters/{plot_psd.py → spectral_signal/plot_psd.py} +0 -0
  263. /figrecipe/_dev/demo_plotters/{plot_specgram.py → spectral_signal/plot_specgram.py} +0 -0
  264. /figrecipe/_dev/demo_plotters/{plot_xcorr.py → spectral_signal/plot_xcorr.py} +0 -0
  265. /figrecipe/_dev/demo_plotters/{plot_barbs.py → vector_flow/plot_barbs.py} +0 -0
  266. /figrecipe/_dev/demo_plotters/{plot_quiver.py → vector_flow/plot_quiver.py} +0 -0
  267. /figrecipe/_dev/demo_plotters/{plot_streamplot.py → vector_flow/plot_streamplot.py} +0 -0
  268. {figrecipe-0.6.0.dist-info → figrecipe-0.9.0.dist-info}/WHEEL +0 -0
  269. {figrecipe-0.6.0.dist-info → figrecipe-0.9.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Browser automation utilities for demo generation.
4
+
5
+ This module provides tools for creating visual demos of figrecipe features
6
+ using Playwright for browser automation.
7
+
8
+ Features:
9
+ - Mouse cursor visualization
10
+ - Click effect animations
11
+ - Caption overlays
12
+ - Element highlighting
13
+ - Video/GIF recording
14
+ """
15
+
16
+ from ._audio import generate_tts_segments, mix_narration_with_bgm
17
+ from ._caption import hide_caption, show_caption, show_title_screen
18
+ from ._click_effect import inject_click_effect, remove_click_effect
19
+ from ._cursor import (
20
+ inject_cursor,
21
+ move_cursor_to,
22
+ move_cursor_to_element,
23
+ remove_cursor,
24
+ )
25
+ from ._highlight import highlight_element
26
+ from ._narration import (
27
+ add_narration_to_video,
28
+ estimate_caption_times,
29
+ extract_captions_from_script,
30
+ get_video_duration,
31
+ )
32
+ from ._recorder import DemoRecorder
33
+ from ._utils import concatenate_videos, convert_to_gif
34
+ from ._video_trim import (
35
+ detect_markers,
36
+ inject_end_marker,
37
+ inject_start_marker,
38
+ process_video_with_markers,
39
+ trim_video_by_markers,
40
+ )
41
+
42
+ __all__ = [
43
+ "inject_cursor",
44
+ "remove_cursor",
45
+ "move_cursor_to",
46
+ "move_cursor_to_element",
47
+ "inject_click_effect",
48
+ "remove_click_effect",
49
+ "show_caption",
50
+ "hide_caption",
51
+ "show_title_screen",
52
+ "highlight_element",
53
+ "DemoRecorder",
54
+ "convert_to_gif",
55
+ "concatenate_videos",
56
+ "inject_start_marker",
57
+ "inject_end_marker",
58
+ "detect_markers",
59
+ "trim_video_by_markers",
60
+ "process_video_with_markers",
61
+ "generate_tts_segments",
62
+ "mix_narration_with_bgm",
63
+ "extract_captions_from_script",
64
+ "get_video_duration",
65
+ "estimate_caption_times",
66
+ "add_narration_to_video",
67
+ ]
68
+
69
+ # EOF
@@ -0,0 +1,240 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Audio processing for demo videos.
4
+
5
+ Provides TTS generation and audio mixing for demo narration.
6
+ """
7
+
8
+ import hashlib
9
+ import os
10
+ import re
11
+ import subprocess
12
+ from pathlib import Path
13
+ from typing import List, Tuple
14
+
15
+ # Check for ElevenLabs API key
16
+ ELEVENLABS_API_KEY = os.environ.get("ELEVENLABS_API_KEY")
17
+
18
+
19
+ def _sanitize_filename(text: str, max_length: int = 50) -> str:
20
+ """Convert text to a safe filename prefix.
21
+
22
+ Parameters
23
+ ----------
24
+ text : str
25
+ Text to convert.
26
+ max_length : int
27
+ Maximum length of the result.
28
+
29
+ Returns
30
+ -------
31
+ str
32
+ Sanitized filename-safe string.
33
+ """
34
+ # Remove special characters, keep alphanumeric and spaces
35
+ clean = re.sub(r"[^a-zA-Z0-9\s]", "", text)
36
+ # Replace spaces with underscores
37
+ clean = re.sub(r"\s+", "_", clean.strip())
38
+ # Truncate and lowercase
39
+ return clean[:max_length].lower().rstrip("_")
40
+
41
+
42
+ def _get_cache_path(text: str, cache_dir: Path) -> Path:
43
+ """Get cache file path using transcription-based naming.
44
+
45
+ Format: {sanitized_text}_{hash}.mp3
46
+ Example: enable_dark_mode_demo_a1b2c3d4.mp3
47
+
48
+ Parameters
49
+ ----------
50
+ text : str
51
+ Text content for TTS.
52
+ cache_dir : Path
53
+ Cache directory.
54
+
55
+ Returns
56
+ -------
57
+ Path
58
+ Cache file path.
59
+ """
60
+ # Create short hash for uniqueness
61
+ text_hash = hashlib.md5(text.encode()).hexdigest()[:8]
62
+ # Sanitize text for filename
63
+ sanitized = _sanitize_filename(text)
64
+ # Combine: readable prefix + hash for uniqueness
65
+ filename = f"{sanitized}_{text_hash}.mp3"
66
+ return cache_dir / filename
67
+
68
+
69
+ def generate_tts_segments(
70
+ narrations: List[Tuple[str, str]],
71
+ output_dir: Path,
72
+ ) -> List[Path]:
73
+ """Generate TTS audio files for narrations.
74
+
75
+ Uses ElevenLabs if API key is available, falls back to gTTS.
76
+ Cache files are named using transcription text for easy identification.
77
+
78
+ Parameters
79
+ ----------
80
+ narrations : List[Tuple[str, str]]
81
+ List of (name, text) tuples.
82
+ output_dir : Path
83
+ Output directory for audio files.
84
+
85
+ Returns
86
+ -------
87
+ List[Path]
88
+ List of generated audio file paths.
89
+ """
90
+ output_dir = Path(output_dir)
91
+ output_dir.mkdir(parents=True, exist_ok=True)
92
+
93
+ audio_files = []
94
+
95
+ if ELEVENLABS_API_KEY:
96
+ print("Using ElevenLabs TTS (high quality)")
97
+ try:
98
+ from elevenlabs import ElevenLabs
99
+
100
+ client = ElevenLabs(api_key=ELEVENLABS_API_KEY)
101
+
102
+ for name, text in narrations:
103
+ cache_path = _get_cache_path(text, output_dir)
104
+
105
+ # Check cache
106
+ if cache_path.exists():
107
+ print(f" [cache] {cache_path.name}")
108
+ audio_files.append(cache_path)
109
+ continue
110
+
111
+ # Generate TTS
112
+ audio = client.text_to_speech.convert(
113
+ voice_id="21m00Tcm4TlvDq8ikWAM", # Rachel
114
+ text=text,
115
+ model_id="eleven_monolingual_v1",
116
+ )
117
+
118
+ # Save audio
119
+ with open(cache_path, "wb") as f:
120
+ for chunk in audio:
121
+ f.write(chunk)
122
+
123
+ print(f" ✓ ElevenLabs: {cache_path.name} - '{text[:40]}...'")
124
+ audio_files.append(cache_path)
125
+
126
+ return audio_files
127
+
128
+ except Exception as e:
129
+ print(f" ElevenLabs failed: {e}, falling back to gTTS")
130
+
131
+ # Fallback to gTTS
132
+ print("Using gTTS (fallback)")
133
+ try:
134
+ from gtts import gTTS
135
+
136
+ for name, text in narrations:
137
+ cache_path = _get_cache_path(text, output_dir)
138
+
139
+ # Check cache
140
+ if cache_path.exists():
141
+ print(f" [cache] {cache_path.name}")
142
+ audio_files.append(cache_path)
143
+ continue
144
+
145
+ # Generate TTS
146
+ tts = gTTS(text=text, lang="en")
147
+ tts.save(str(cache_path))
148
+
149
+ print(f" ✓ gTTS: {cache_path.name} - '{text[:40]}...'")
150
+ audio_files.append(cache_path)
151
+
152
+ return audio_files
153
+
154
+ except ImportError:
155
+ raise RuntimeError("Neither ElevenLabs nor gTTS available")
156
+
157
+
158
+ def mix_narration_with_bgm(
159
+ narration_files: List[Path],
160
+ narration_times: List[float],
161
+ bgm_path: Path,
162
+ output_path: Path,
163
+ duration: float,
164
+ bgm_volume: float = 0.10,
165
+ narration_delay: float = 0.3,
166
+ fade_in_duration: float = 0.5,
167
+ fade_out_duration: float = 1.0,
168
+ ) -> Path:
169
+ """Mix narration audio with background music.
170
+
171
+ Parameters
172
+ ----------
173
+ narration_files : List[Path]
174
+ List of narration audio files.
175
+ narration_times : List[float]
176
+ Start times for each narration.
177
+ bgm_path : Path
178
+ Path to background music file.
179
+ output_path : Path
180
+ Output path for mixed audio.
181
+ duration : float
182
+ Total duration in seconds.
183
+ bgm_volume : float
184
+ Background music volume (0.0-1.0).
185
+ narration_delay : float
186
+ Delay before narration starts.
187
+ fade_in_duration : float
188
+ BGM fade-in duration.
189
+ fade_out_duration : float
190
+ BGM fade-out duration.
191
+
192
+ Returns
193
+ -------
194
+ Path
195
+ Path to mixed audio file.
196
+ """
197
+ # Build ffmpeg filter for mixing
198
+ inputs = ["-i", str(bgm_path)]
199
+ for f in narration_files:
200
+ inputs.extend(["-i", str(f)])
201
+
202
+ # BGM filter: loop, trim, volume, fade
203
+ bgm_filter = (
204
+ f"[0:a]aloop=loop=-1:size=2e+09,atrim=0:{duration},"
205
+ f"volume={bgm_volume},"
206
+ f"afade=t=in:st=0:d={fade_in_duration},"
207
+ f"afade=t=out:st={duration - fade_out_duration}:d={fade_out_duration}[bgm]"
208
+ )
209
+
210
+ # Narration filters with delays
211
+ narration_filters = []
212
+ mix_inputs = "[bgm]"
213
+
214
+ for i, (f, t) in enumerate(zip(narration_files, narration_times)):
215
+ delay_ms = int((t + narration_delay) * 1000)
216
+ narration_filters.append(f"[{i + 1}:a]adelay={delay_ms}|{delay_ms}[n{i}]")
217
+ mix_inputs += f"[n{i}]"
218
+
219
+ # Combine all filters
220
+ all_filters = [bgm_filter] + narration_filters
221
+ mix_filter = (
222
+ f"{mix_inputs}amix=inputs={len(narration_files) + 1}:duration=first[out]"
223
+ )
224
+ all_filters.append(mix_filter)
225
+
226
+ filter_complex = ";".join(all_filters)
227
+
228
+ # Run ffmpeg
229
+ cmd = (
230
+ ["ffmpeg", "-y"]
231
+ + inputs
232
+ + ["-filter_complex", filter_complex, "-map", "[out]", str(output_path)]
233
+ )
234
+
235
+ subprocess.run(cmd, check=True, capture_output=True)
236
+
237
+ return output_path
238
+
239
+
240
+ __all__ = ["generate_tts_segments", "mix_narration_with_bgm"]
@@ -0,0 +1,356 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Caption overlay for demo recordings.
4
+
5
+ Shows text captions/banners during demo recordings to explain
6
+ what is happening in the demo.
7
+ """
8
+
9
+ # JavaScript template for showing caption
10
+ SHOW_CAPTION_JS = """
11
+ (text) => {
12
+ // Remove existing caption if any
13
+ const existing = document.getElementById('demo-caption');
14
+ if (existing) existing.remove();
15
+
16
+ // Add CSS if not exists
17
+ if (!document.getElementById('demo-caption-style')) {
18
+ const style = document.createElement('style');
19
+ style.id = 'demo-caption-style';
20
+ style.textContent = `
21
+ @keyframes demo-caption-fade-in {
22
+ 0% { opacity: 0; transform: translateY(20px); }
23
+ 100% { opacity: 1; transform: translateY(0); }
24
+ }
25
+ @keyframes demo-caption-fade-out {
26
+ 0% { opacity: 1; transform: translateY(0); }
27
+ 100% { opacity: 0; transform: translateY(-20px); }
28
+ }
29
+ `;
30
+ document.head.appendChild(style);
31
+ }
32
+
33
+ // Create caption element
34
+ const caption = document.createElement('div');
35
+ caption.id = 'demo-caption';
36
+ caption.textContent = text;
37
+ caption.style.cssText = `
38
+ position: fixed;
39
+ bottom: 50px;
40
+ left: 50%;
41
+ transform: translateX(-50%);
42
+ background: rgba(0, 0, 0, 0.85);
43
+ color: white;
44
+ padding: 18px 40px;
45
+ border-radius: 10px;
46
+ font-size: 24px;
47
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
48
+ font-weight: 500;
49
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
50
+ z-index: 2147483645;
51
+ pointer-events: none;
52
+ animation: demo-caption-fade-in 0.3s ease-out forwards;
53
+ max-width: 80%;
54
+ text-align: center;
55
+ `;
56
+ document.body.appendChild(caption);
57
+
58
+ return true;
59
+ }
60
+ """
61
+
62
+ HIDE_CAPTION_JS = """
63
+ () => {
64
+ const caption = document.getElementById('demo-caption');
65
+ if (caption) {
66
+ caption.style.animation = 'demo-caption-fade-out 0.3s ease-out forwards';
67
+ setTimeout(() => caption.remove(), 300);
68
+ }
69
+ return true;
70
+ }
71
+ """
72
+
73
+ # JavaScript for title screen with blur overlay
74
+ TITLE_SCREEN_JS = """
75
+ async (args) => {
76
+ const { title, subtitle = '', timestamp = '', duration = 2000 } = args;
77
+
78
+ // Add CSS animations if not exists
79
+ if (!document.getElementById('demo-title-style')) {
80
+ const style = document.createElement('style');
81
+ style.id = 'demo-title-style';
82
+ style.textContent = `
83
+ @keyframes demo-title-fade-in {
84
+ 0% { opacity: 0; }
85
+ 100% { opacity: 1; }
86
+ }
87
+ @keyframes demo-title-fade-out {
88
+ 0% { opacity: 1; }
89
+ 100% { opacity: 0; }
90
+ }
91
+ @keyframes demo-title-text-in {
92
+ 0% { opacity: 0; transform: scale(0.9); }
93
+ 100% { opacity: 1; transform: scale(1); }
94
+ }
95
+ `;
96
+ document.head.appendChild(style);
97
+ }
98
+
99
+ // Create blur overlay
100
+ const overlay = document.createElement('div');
101
+ overlay.id = 'demo-title-overlay';
102
+ overlay.style.cssText = `
103
+ position: fixed;
104
+ top: 0;
105
+ left: 0;
106
+ width: 100%;
107
+ height: 100%;
108
+ background: rgba(0, 0, 0, 0.7);
109
+ backdrop-filter: blur(8px);
110
+ -webkit-backdrop-filter: blur(8px);
111
+ z-index: 2147483646;
112
+ display: flex;
113
+ flex-direction: column;
114
+ align-items: center;
115
+ justify-content: center;
116
+ animation: demo-title-fade-in 0.5s ease-out forwards;
117
+ `;
118
+
119
+ // Create title text
120
+ const titleEl = document.createElement('div');
121
+ titleEl.style.cssText = `
122
+ color: white;
123
+ font-size: 48px;
124
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
125
+ font-weight: 700;
126
+ text-align: center;
127
+ animation: demo-title-text-in 0.6s ease-out 0.2s both;
128
+ text-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
129
+ `;
130
+ titleEl.textContent = title;
131
+ overlay.appendChild(titleEl);
132
+
133
+ // Create subtitle if provided
134
+ if (subtitle) {
135
+ const subtitleEl = document.createElement('div');
136
+ subtitleEl.style.cssText = `
137
+ color: rgba(255, 255, 255, 0.8);
138
+ font-size: 24px;
139
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
140
+ font-weight: 400;
141
+ margin-top: 16px;
142
+ text-align: center;
143
+ animation: demo-title-text-in 0.6s ease-out 0.4s both;
144
+ `;
145
+ subtitleEl.textContent = subtitle;
146
+ overlay.appendChild(subtitleEl);
147
+ }
148
+
149
+ // Create timestamp if provided
150
+ if (timestamp) {
151
+ const timestampEl = document.createElement('div');
152
+ timestampEl.style.cssText = `
153
+ color: rgba(255, 255, 255, 0.5);
154
+ font-size: 14px;
155
+ font-family: monospace;
156
+ margin-top: 24px;
157
+ text-align: center;
158
+ animation: demo-title-text-in 0.6s ease-out 0.5s both;
159
+ `;
160
+ timestampEl.textContent = timestamp;
161
+ overlay.appendChild(timestampEl);
162
+ }
163
+
164
+ document.body.appendChild(overlay);
165
+
166
+ // Wait and fade out
167
+ await new Promise(r => setTimeout(r, duration));
168
+
169
+ overlay.style.animation = 'demo-title-fade-out 0.5s ease-out forwards';
170
+ await new Promise(r => setTimeout(r, 500));
171
+ overlay.remove();
172
+
173
+ return true;
174
+ }
175
+ """
176
+
177
+
178
+ async def show_caption(page, text: str) -> bool:
179
+ """Show caption text overlay on page.
180
+
181
+ Parameters
182
+ ----------
183
+ page : playwright.async_api.Page
184
+ Playwright page object.
185
+ text : str
186
+ Caption text to display.
187
+
188
+ Returns
189
+ -------
190
+ bool
191
+ True if successful.
192
+ """
193
+ return await page.evaluate(SHOW_CAPTION_JS, text)
194
+
195
+
196
+ async def hide_caption(page) -> bool:
197
+ """Hide caption overlay from page.
198
+
199
+ Parameters
200
+ ----------
201
+ page : playwright.async_api.Page
202
+ Playwright page object.
203
+
204
+ Returns
205
+ -------
206
+ bool
207
+ True if successful.
208
+ """
209
+ return await page.evaluate(HIDE_CAPTION_JS)
210
+
211
+
212
+ async def show_title_screen(
213
+ page,
214
+ title: str,
215
+ subtitle: str = "",
216
+ timestamp: str = "",
217
+ duration_ms: int = 2000,
218
+ ) -> bool:
219
+ """Show title screen with blur overlay and fade effect.
220
+
221
+ Parameters
222
+ ----------
223
+ page : playwright.async_api.Page
224
+ Playwright page object.
225
+ title : str
226
+ Main title text.
227
+ subtitle : str, optional
228
+ Subtitle text (default: "").
229
+ timestamp : str, optional
230
+ Timestamp to display (default: "").
231
+ duration_ms : int, optional
232
+ Duration to show title in milliseconds (default: 2000).
233
+
234
+ Returns
235
+ -------
236
+ bool
237
+ True if successful.
238
+ """
239
+ args = {
240
+ "title": title,
241
+ "subtitle": subtitle,
242
+ "timestamp": timestamp,
243
+ "duration": duration_ms,
244
+ }
245
+ return await page.evaluate(TITLE_SCREEN_JS, args)
246
+
247
+
248
+ # JavaScript for closing branding screen
249
+ CLOSING_SCREEN_JS = """
250
+ async (args) => {
251
+ const { duration = 2500 } = args;
252
+
253
+ // Add CSS animations
254
+ if (!document.getElementById('demo-closing-style')) {
255
+ const style = document.createElement('style');
256
+ style.id = 'demo-closing-style';
257
+ style.textContent = `
258
+ @keyframes demo-closing-fade-in {
259
+ 0% { opacity: 0; }
260
+ 100% { opacity: 1; }
261
+ }
262
+ @keyframes demo-closing-fade-out {
263
+ 0% { opacity: 1; }
264
+ 100% { opacity: 0; }
265
+ }
266
+ `;
267
+ document.head.appendChild(style);
268
+ }
269
+
270
+ // Create overlay
271
+ const overlay = document.createElement('div');
272
+ overlay.id = 'demo-closing-overlay';
273
+ overlay.style.cssText = `
274
+ position: fixed;
275
+ top: 0;
276
+ left: 0;
277
+ width: 100%;
278
+ height: 100%;
279
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
280
+ z-index: 2147483646;
281
+ display: flex;
282
+ flex-direction: column;
283
+ align-items: center;
284
+ justify-content: center;
285
+ animation: demo-closing-fade-in 0.5s ease-out forwards;
286
+ `;
287
+
288
+ // FigRecipe title
289
+ const title = document.createElement('div');
290
+ title.style.cssText = `
291
+ color: white;
292
+ font-size: 56px;
293
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
294
+ font-weight: 700;
295
+ margin-bottom: 16px;
296
+ `;
297
+ title.textContent = 'FigRecipe';
298
+ overlay.appendChild(title);
299
+
300
+ // Part of SciTeX
301
+ const scitex = document.createElement('div');
302
+ scitex.style.cssText = `
303
+ color: rgba(255, 255, 255, 0.7);
304
+ font-size: 20px;
305
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
306
+ margin-bottom: 24px;
307
+ `;
308
+ scitex.innerHTML = 'Part of SciTeX™';
309
+ overlay.appendChild(scitex);
310
+
311
+ // URL
312
+ const url = document.createElement('div');
313
+ url.style.cssText = `
314
+ color: #4da6ff;
315
+ font-size: 18px;
316
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
317
+ `;
318
+ url.textContent = 'https://scitex.ai';
319
+ overlay.appendChild(url);
320
+
321
+ document.body.appendChild(overlay);
322
+
323
+ // Wait and fade out
324
+ await new Promise(r => setTimeout(r, duration));
325
+
326
+ overlay.style.animation = 'demo-closing-fade-out 0.5s ease-out forwards';
327
+ await new Promise(r => setTimeout(r, 500));
328
+ overlay.remove();
329
+
330
+ return true;
331
+ }
332
+ """
333
+
334
+
335
+ async def show_closing_screen(page, duration_ms: int = 2500) -> bool:
336
+ """Show closing branding screen with FigRecipe and SciTeX.
337
+
338
+ Parameters
339
+ ----------
340
+ page : playwright.async_api.Page
341
+ Playwright page object.
342
+ duration_ms : int, optional
343
+ Duration to show screen in milliseconds (default: 2500).
344
+
345
+ Returns
346
+ -------
347
+ bool
348
+ True if successful.
349
+ """
350
+ args = {"duration": duration_ms}
351
+ return await page.evaluate(CLOSING_SCREEN_JS, args)
352
+
353
+
354
+ __all__ = ["show_caption", "hide_caption", "show_title_screen", "show_closing_screen"]
355
+
356
+ # EOF