pykara 0.0.1__tar.gz

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 (256) hide show
  1. pykara-0.0.1/.editorconfig +4 -0
  2. pykara-0.0.1/.gitattributes +2 -0
  3. pykara-0.0.1/.github/workflows/ci.yml +103 -0
  4. pykara-0.0.1/.github/workflows/package.yml +46 -0
  5. pykara-0.0.1/.github/workflows/publish.yml +70 -0
  6. pykara-0.0.1/.gitignore +36 -0
  7. pykara-0.0.1/.rumdl.toml +7 -0
  8. pykara-0.0.1/PKG-INFO +107 -0
  9. pykara-0.0.1/README.md +76 -0
  10. pykara-0.0.1/bridge/pykara.lua +243 -0
  11. pykara-0.0.1/docs/directives/char-scope.md +41 -0
  12. pykara-0.0.1/docs/directives/interpolation.md +38 -0
  13. pykara-0.0.1/docs/directives/line-scope.md +37 -0
  14. pykara-0.0.1/docs/directives/modifiers.md +172 -0
  15. pykara-0.0.1/docs/directives/scopes.md +16 -0
  16. pykara-0.0.1/docs/directives/setup-scope.md +23 -0
  17. pykara-0.0.1/docs/directives/syllable-scope.md +47 -0
  18. pykara-0.0.1/docs/directives/types.md +55 -0
  19. pykara-0.0.1/docs/directives/word-scope.md +36 -0
  20. pykara-0.0.1/docs/directives.md +55 -0
  21. pykara-0.0.1/docs/helpers/color.md +65 -0
  22. pykara-0.0.1/docs/helpers/memory.md +29 -0
  23. pykara-0.0.1/docs/helpers/motion.md +191 -0
  24. pykara-0.0.1/docs/helpers/palette.md +30 -0
  25. pykara-0.0.1/docs/helpers/relayer.md +17 -0
  26. pykara-0.0.1/docs/helpers/repeat.md +43 -0
  27. pykara-0.0.1/docs/helpers/retime.md +198 -0
  28. pykara-0.0.1/docs/helpers/scale.md +72 -0
  29. pykara-0.0.1/docs/helpers/timeline.md +80 -0
  30. pykara-0.0.1/docs/helpers.md +80 -0
  31. pykara-0.0.1/docs/index.md +27 -0
  32. pykara-0.0.1/pyproject.toml +122 -0
  33. pykara-0.0.1/src/pykara/__init__.py +26 -0
  34. pykara-0.0.1/src/pykara/ass/__init__.py +26 -0
  35. pykara-0.0.1/src/pykara/ass/parser.py +353 -0
  36. pykara-0.0.1/src/pykara/ass/time.py +78 -0
  37. pykara-0.0.1/src/pykara/ass/types.py +141 -0
  38. pykara-0.0.1/src/pykara/ass/writer.py +139 -0
  39. pykara-0.0.1/src/pykara/cli.py +125 -0
  40. pykara-0.0.1/src/pykara/directive/__init__.py +51 -0
  41. pykara-0.0.1/src/pykara/directive/parser.py +393 -0
  42. pykara-0.0.1/src/pykara/directive/types.py +168 -0
  43. pykara-0.0.1/src/pykara/directive/validator.py +231 -0
  44. pykara-0.0.1/src/pykara/engine/__init__.py +20 -0
  45. pykara-0.0.1/src/pykara/engine/collector.py +166 -0
  46. pykara-0.0.1/src/pykara/engine/context_builder.py +68 -0
  47. pykara-0.0.1/src/pykara/engine/evaluator.py +31 -0
  48. pykara-0.0.1/src/pykara/engine/execution_runtime.py +209 -0
  49. pykara-0.0.1/src/pykara/engine/expression_catalog.py +228 -0
  50. pykara-0.0.1/src/pykara/engine/expression_proxies.py +152 -0
  51. pykara-0.0.1/src/pykara/engine/fbf_slot.py +34 -0
  52. pykara-0.0.1/src/pykara/engine/interpolator.py +91 -0
  53. pykara-0.0.1/src/pykara/engine/line_runtime_factory.py +70 -0
  54. pykara-0.0.1/src/pykara/engine/loop.py +190 -0
  55. pykara-0.0.1/src/pykara/engine/output.py +21 -0
  56. pykara-0.0.1/src/pykara/engine/preflight.py +163 -0
  57. pykara-0.0.1/src/pykara/engine/protocols.py +256 -0
  58. pykara-0.0.1/src/pykara/engine/retime_state.py +114 -0
  59. pykara-0.0.1/src/pykara/engine/rng.py +43 -0
  60. pykara-0.0.1/src/pykara/engine/runner.py +296 -0
  61. pykara-0.0.1/src/pykara/engine/runtime_execution.py +37 -0
  62. pykara-0.0.1/src/pykara/engine/scope_registry.py +63 -0
  63. pykara-0.0.1/src/pykara/engine/variable_registry.py +86 -0
  64. pykara-0.0.1/src/pykara/engine/variable_resolver.py +129 -0
  65. pykara-0.0.1/src/pykara/exceptions.py +35 -0
  66. pykara-0.0.1/src/pykara/helpers/__init__.py +44 -0
  67. pykara-0.0.1/src/pykara/helpers/color.py +109 -0
  68. pykara-0.0.1/src/pykara/helpers/memory.py +41 -0
  69. pykara-0.0.1/src/pykara/helpers/motion.py +23 -0
  70. pykara-0.0.1/src/pykara/helpers/motion_base.py +63 -0
  71. pykara-0.0.1/src/pykara/helpers/motion_fbf.py +155 -0
  72. pykara-0.0.1/src/pykara/helpers/motion_shad.py +182 -0
  73. pykara-0.0.1/src/pykara/helpers/palette.py +382 -0
  74. pykara-0.0.1/src/pykara/helpers/relayer.py +23 -0
  75. pykara-0.0.1/src/pykara/helpers/repeat.py +77 -0
  76. pykara-0.0.1/src/pykara/helpers/retime.py +31 -0
  77. pykara-0.0.1/src/pykara/helpers/retime_presets.py +221 -0
  78. pykara-0.0.1/src/pykara/helpers/retime_targets.py +138 -0
  79. pykara-0.0.1/src/pykara/helpers/scale.py +63 -0
  80. pykara-0.0.1/src/pykara/helpers/timeline.py +160 -0
  81. pykara-0.0.1/src/pykara/helpers/timeline_parsing.py +223 -0
  82. pykara-0.0.1/src/pykara/karaoke/__init__.py +20 -0
  83. pykara-0.0.1/src/pykara/karaoke/char.py +112 -0
  84. pykara-0.0.1/src/pykara/karaoke/line.py +62 -0
  85. pykara-0.0.1/src/pykara/karaoke/override_tags.py +73 -0
  86. pykara-0.0.1/src/pykara/karaoke/parser.py +340 -0
  87. pykara-0.0.1/src/pykara/karaoke/syllable.py +77 -0
  88. pykara-0.0.1/src/pykara/karaoke/word.py +148 -0
  89. pykara-0.0.1/src/pykara/layout/__init__.py +26 -0
  90. pykara-0.0.1/src/pykara/layout/_pango_measure.py +90 -0
  91. pykara-0.0.1/src/pykara/layout/_win32_measure.py +204 -0
  92. pykara-0.0.1/src/pykara/layout/positioning.py +399 -0
  93. pykara-0.0.1/src/pykara/layout/style.py +136 -0
  94. pykara-0.0.1/src/pykara/layout/text_measure.py +83 -0
  95. pykara-0.0.1/src/pykara/math/__init__.py +1 -0
  96. pykara-0.0.1/src/pykara/math/curves.py +57 -0
  97. pykara-0.0.1/src/pykara/motion/__init__.py +1 -0
  98. pykara-0.0.1/src/pykara/motion/arc/__init__.py +1 -0
  99. pykara-0.0.1/src/pykara/motion/arc/curve.py +27 -0
  100. pykara-0.0.1/src/pykara/motion/arc/fbf.py +56 -0
  101. pykara-0.0.1/src/pykara/motion/arc/request.py +50 -0
  102. pykara-0.0.1/src/pykara/motion/arc/shad.py +66 -0
  103. pykara-0.0.1/src/pykara/motion/bezier/__init__.py +1 -0
  104. pykara-0.0.1/src/pykara/motion/bezier/curve.py +35 -0
  105. pykara-0.0.1/src/pykara/motion/bezier/fbf.py +38 -0
  106. pykara-0.0.1/src/pykara/motion/bezier/request.py +36 -0
  107. pykara-0.0.1/src/pykara/motion/bezier/shad.py +49 -0
  108. pykara-0.0.1/src/pykara/motion/jitter/__init__.py +1 -0
  109. pykara-0.0.1/src/pykara/motion/jitter/engine.py +90 -0
  110. pykara-0.0.1/src/pykara/motion/jitter/fbf.py +100 -0
  111. pykara-0.0.1/src/pykara/motion/jitter/request.py +44 -0
  112. pykara-0.0.1/src/pykara/motion/jitter/shad.py +69 -0
  113. pykara-0.0.1/src/pykara/motion/jitter/spec.py +46 -0
  114. pykara-0.0.1/src/pykara/motion/runtime/__init__.py +2 -0
  115. pykara-0.0.1/src/pykara/motion/runtime/fbf/__init__.py +1 -0
  116. pykara-0.0.1/src/pykara/motion/runtime/fbf/ass_tags.py +943 -0
  117. pykara-0.0.1/src/pykara/motion/runtime/fbf/expansion.py +157 -0
  118. pykara-0.0.1/src/pykara/motion/runtime/fbf/protocol.py +21 -0
  119. pykara-0.0.1/src/pykara/motion/runtime/fbf/timeline.py +65 -0
  120. pykara-0.0.1/src/pykara/motion/runtime/runtime_geometry.py +26 -0
  121. pykara-0.0.1/src/pykara/motion/runtime/shad/__init__.py +1 -0
  122. pykara-0.0.1/src/pykara/motion/runtime/shad/formatting.py +19 -0
  123. pykara-0.0.1/src/pykara/motion/runtime/shad/transforms.py +9 -0
  124. pykara-0.0.1/src/pykara/motion/runtime/window.py +28 -0
  125. pykara-0.0.1/src/pykara/motion/spring/__init__.py +1 -0
  126. pykara-0.0.1/src/pykara/motion/spring/curve.py +24 -0
  127. pykara-0.0.1/src/pykara/motion/spring/fbf.py +50 -0
  128. pykara-0.0.1/src/pykara/motion/spring/request.py +48 -0
  129. pykara-0.0.1/src/pykara/motion/spring/shad.py +60 -0
  130. pykara-0.0.1/src/pykara/motion/wave/__init__.py +1 -0
  131. pykara-0.0.1/src/pykara/motion/wave/curve.py +23 -0
  132. pykara-0.0.1/src/pykara/motion/wave/fbf.py +51 -0
  133. pykara-0.0.1/src/pykara/motion/wave/request.py +46 -0
  134. pykara-0.0.1/src/pykara/motion/wave/shad.py +61 -0
  135. pykara-0.0.1/src/pykara/py.typed +1 -0
  136. pykara-0.0.1/src/pykara/timing/__init__.py +13 -0
  137. pykara-0.0.1/src/pykara/timing/framerate.py +298 -0
  138. pykara-0.0.1/tests/__init__.py +1 -0
  139. pykara-0.0.1/tests/acceptance/test_fixtures.py +158 -0
  140. pykara-0.0.1/tests/conftest.py +76 -0
  141. pykara-0.0.1/tests/fixtures/acceptance/01_foundations/basic_01_grow_larger.ass +121 -0
  142. pykara-0.0.1/tests/fixtures/acceptance/01_foundations/basic_02_appear_left_to_right_syllable.ass +120 -0
  143. pykara-0.0.1/tests/fixtures/acceptance/01_foundations/basic_03_appear_left_to_right_character.ass +298 -0
  144. pykara-0.0.1/tests/fixtures/acceptance/01_foundations/basic_04_show_one_object_per_line.ass +123 -0
  145. pykara-0.0.1/tests/fixtures/acceptance/01_foundations/basic_05_random_color_memory.ass +121 -0
  146. pykara-0.0.1/tests/fixtures/acceptance/01_foundations/basic_06_random_color_line_state.ass +122 -0
  147. pykara-0.0.1/tests/fixtures/acceptance/01_foundations/basic_07_move_shape_between_syllables.ass +160 -0
  148. pykara-0.0.1/tests/fixtures/acceptance/01_foundations/basic_08_jumping_effect.ass +118 -0
  149. pykara-0.0.1/tests/fixtures/acceptance/01_foundations/basic_25_vertical_karaoke.ass +115 -0
  150. pykara-0.0.1/tests/fixtures/acceptance/01_foundations/basic_26_repeat_single_loop.ass +16 -0
  151. pykara-0.0.1/tests/fixtures/acceptance/01_foundations/basic_27_repeat_named_cartesian.ass +19 -0
  152. pykara-0.0.1/tests/fixtures/acceptance/02_selection_and_color/basic_09_multi_highlight_ruby.ass +125 -0
  153. pykara-0.0.1/tests/fixtures/acceptance/02_selection_and_color/basic_10_select_effects_by_k_tag.ass +125 -0
  154. pykara-0.0.1/tests/fixtures/acceptance/02_selection_and_color/basic_11_select_effects_by_inline_fx.ass +124 -0
  155. pykara-0.0.1/tests/fixtures/acceptance/02_selection_and_color/basic_12_select_line_effects_by_actor.ass +119 -0
  156. pykara-0.0.1/tests/fixtures/acceptance/02_selection_and_color/basic_13_select_line_effects_by_when.ass +123 -0
  157. pykara-0.0.1/tests/fixtures/acceptance/02_selection_and_color/basic_14_alternating_color_syllable.ass +120 -0
  158. pykara-0.0.1/tests/fixtures/acceptance/02_selection_and_color/basic_15_alternating_color_character.ass +260 -0
  159. pykara-0.0.1/tests/fixtures/acceptance/02_selection_and_color/basic_16_color_from_hex.ass +25 -0
  160. pykara-0.0.1/tests/fixtures/acceptance/03_transitions/basic_16_transition_fade_in_center_to_sides.ass +163 -0
  161. pykara-0.0.1/tests/fixtures/acceptance/03_transitions/basic_17_transition_fade_in_left_to_right.ass +163 -0
  162. pykara-0.0.1/tests/fixtures/acceptance/03_transitions/basic_18_transition_fade_in_right_to_left.ass +162 -0
  163. pykara-0.0.1/tests/fixtures/acceptance/03_transitions/basic_19_transition_flash_left_to_right.ass +163 -0
  164. pykara-0.0.1/tests/fixtures/acceptance/03_transitions/basic_20_transition_flash_center_to_sides.ass +163 -0
  165. pykara-0.0.1/tests/fixtures/acceptance/03_transitions/basic_21_transition_flash_sides_to_center.ass +162 -0
  166. pykara-0.0.1/tests/fixtures/acceptance/03_transitions/basic_22_transition_rainbow_flash.ass +162 -0
  167. pykara-0.0.1/tests/fixtures/acceptance/03_transitions/basic_23_auto_translucent_background.ass +145 -0
  168. pykara-0.0.1/tests/fixtures/acceptance/03_transitions/basic_28_timeline_transitions.ass +25 -0
  169. pykara-0.0.1/tests/fixtures/acceptance/03_transitions/basic_29_motion_shadow_trick.ass +25 -0
  170. pykara-0.0.1/tests/fixtures/acceptance/03_transitions/basic_30_motion_jitter_shadow_trick.ass +25 -0
  171. pykara-0.0.1/tests/fixtures/acceptance/03_transitions/basic_31_timeline_pulse.ass +25 -0
  172. pykara-0.0.1/tests/fixtures/acceptance/04_stateful/basic_24_code_line_palette_helper.ass +149 -0
  173. pykara-0.0.1/tests/fixtures/acceptance/04_stateful/basic_25_timeline_sequence_palette_helper.ass +149 -0
  174. pykara-0.0.1/tests/fixtures/acceptance/99_invalid/retime_preset_matrix.ass +959 -0
  175. pykara-0.0.1/tests/fixtures/ass/cli_generated_fx_input.ass +13 -0
  176. pykara-0.0.1/tests/fixtures/ass/cli_timecodes_input.ass +13 -0
  177. pykara-0.0.1/tests/fixtures/ass/comment_event.ass +10 -0
  178. pykara-0.0.1/tests/fixtures/ass/event_before_format.ass +2 -0
  179. pykara-0.0.1/tests/fixtures/ass/event_missing_fields.ass +3 -0
  180. pykara-0.0.1/tests/fixtures/ass/events_with_comment.ass +4 -0
  181. pykara-0.0.1/tests/fixtures/ass/invalid_event_line.ass +3 -0
  182. pykara-0.0.1/tests/fixtures/ass/invalid_events_line.ass +2 -0
  183. pykara-0.0.1/tests/fixtures/ass/invalid_script_info_line.ass +2 -0
  184. pykara-0.0.1/tests/fixtures/ass/invalid_style_line.ass +3 -0
  185. pykara-0.0.1/tests/fixtures/ass/karaoke_inline_fx.ass +10 -0
  186. pykara-0.0.1/tests/fixtures/ass/reordered_format.ass +16 -0
  187. pykara-0.0.1/tests/fixtures/ass/style_before_format.ass +2 -0
  188. pykara-0.0.1/tests/fixtures/ass/style_missing_fields.ass +3 -0
  189. pykara-0.0.1/tests/fixtures/ass/styles_with_extra_column.ass +3 -0
  190. pykara-0.0.1/tests/fixtures/ass/unsupported_event_type.ass +3 -0
  191. pykara-0.0.1/tests/fixtures/ass/unsupported_styles_line.ass +3 -0
  192. pykara-0.0.1/tests/fixtures/reference/karaoke/karaoke.ass +28 -0
  193. pykara-0.0.1/tests/fixtures/reference/karaoke/karaoke_expected.json +47 -0
  194. pykara-0.0.1/tests/fixtures/reference/layout/layout.ass +27 -0
  195. pykara-0.0.1/tests/fixtures/reference/layout/text_measure.json +18 -0
  196. pykara-0.0.1/tests/fixtures/timecodes/crf_timecodes.txt +481 -0
  197. pykara-0.0.1/tests/fixtures/timecodes/vrf_timecodes.txt +116045 -0
  198. pykara-0.0.1/tests/fonts/Arial.ttf +0 -0
  199. pykara-0.0.1/tests/fonts/DroidSans-Bold.ttf +0 -0
  200. pykara-0.0.1/tests/fonts/DroidSans.ttf +0 -0
  201. pykara-0.0.1/tests/integration/test_collector.py +94 -0
  202. pykara-0.0.1/tests/integration/test_execution_runtime.py +702 -0
  203. pykara-0.0.1/tests/integration/test_runner_layout.py +69 -0
  204. pykara-0.0.1/tests/integration/test_runner_simple.py +693 -0
  205. pykara-0.0.1/tests/support/__init__.py +19 -0
  206. pykara-0.0.1/tests/support/builders.py +195 -0
  207. pykara-0.0.1/tests/unit/test_ass_error_paths.py +79 -0
  208. pykara-0.0.1/tests/unit/test_ass_parser.py +111 -0
  209. pykara-0.0.1/tests/unit/test_ass_time.py +16 -0
  210. pykara-0.0.1/tests/unit/test_ass_types.py +16 -0
  211. pykara-0.0.1/tests/unit/test_ass_writer.py +45 -0
  212. pykara-0.0.1/tests/unit/test_char.py +29 -0
  213. pykara-0.0.1/tests/unit/test_cli.py +119 -0
  214. pykara-0.0.1/tests/unit/test_directive_error_paths.py +56 -0
  215. pykara-0.0.1/tests/unit/test_directive_parser.py +222 -0
  216. pykara-0.0.1/tests/unit/test_directive_validator.py +315 -0
  217. pykara-0.0.1/tests/unit/test_evaluator.py +19 -0
  218. pykara-0.0.1/tests/unit/test_expression_catalog.py +207 -0
  219. pykara-0.0.1/tests/unit/test_framerate.py +331 -0
  220. pykara-0.0.1/tests/unit/test_helpers_color.py +86 -0
  221. pykara-0.0.1/tests/unit/test_helpers_memory.py +28 -0
  222. pykara-0.0.1/tests/unit/test_helpers_motion.py +159 -0
  223. pykara-0.0.1/tests/unit/test_helpers_motion_arc.py +96 -0
  224. pykara-0.0.1/tests/unit/test_helpers_motion_bezier.py +130 -0
  225. pykara-0.0.1/tests/unit/test_helpers_motion_spring.py +125 -0
  226. pykara-0.0.1/tests/unit/test_helpers_motion_wave.py +118 -0
  227. pykara-0.0.1/tests/unit/test_helpers_palette.py +73 -0
  228. pykara-0.0.1/tests/unit/test_helpers_relayer.py +81 -0
  229. pykara-0.0.1/tests/unit/test_helpers_retime.py +476 -0
  230. pykara-0.0.1/tests/unit/test_helpers_scale.py +55 -0
  231. pykara-0.0.1/tests/unit/test_helpers_timeline.py +353 -0
  232. pykara-0.0.1/tests/unit/test_interpolator.py +163 -0
  233. pykara-0.0.1/tests/unit/test_karaoke_error_paths.py +82 -0
  234. pykara-0.0.1/tests/unit/test_karaoke_line.py +20 -0
  235. pykara-0.0.1/tests/unit/test_karaoke_multi.py +19 -0
  236. pykara-0.0.1/tests/unit/test_karaoke_parser.py +53 -0
  237. pykara-0.0.1/tests/unit/test_layout_positioning.py +351 -0
  238. pykara-0.0.1/tests/unit/test_layout_text_measure.py +672 -0
  239. pykara-0.0.1/tests/unit/test_loop.py +134 -0
  240. pykara-0.0.1/tests/unit/test_math_curves.py +109 -0
  241. pykara-0.0.1/tests/unit/test_motion_arc_fbf.py +55 -0
  242. pykara-0.0.1/tests/unit/test_motion_arc_shad.py +67 -0
  243. pykara-0.0.1/tests/unit/test_motion_bezier_fbf.py +75 -0
  244. pykara-0.0.1/tests/unit/test_motion_bezier_shad.py +59 -0
  245. pykara-0.0.1/tests/unit/test_motion_constraints.py +71 -0
  246. pykara-0.0.1/tests/unit/test_motion_fbf.py +1225 -0
  247. pykara-0.0.1/tests/unit/test_motion_jitter_fbf.py +104 -0
  248. pykara-0.0.1/tests/unit/test_motion_spring_fbf.py +71 -0
  249. pykara-0.0.1/tests/unit/test_motion_spring_shad.py +53 -0
  250. pykara-0.0.1/tests/unit/test_motion_wave_fbf.py +79 -0
  251. pykara-0.0.1/tests/unit/test_motion_wave_shad.py +64 -0
  252. pykara-0.0.1/tests/unit/test_override_tags.py +23 -0
  253. pykara-0.0.1/tests/unit/test_preflight.py +353 -0
  254. pykara-0.0.1/tests/unit/test_retime_state.py +155 -0
  255. pykara-0.0.1/tests/unit/test_syllable.py +20 -0
  256. pykara-0.0.1/tests/unit/test_word.py +23 -0
@@ -0,0 +1,4 @@
1
+ root = true
2
+
3
+ [tests/fixtures/**/*.ass]
4
+ trim_trailing_whitespace = false
@@ -0,0 +1,2 @@
1
+ # ASS fixtures may include significant trailing spaces in dialogue text.
2
+ tests/fixtures/**/*.ass whitespace=-trailing-space
@@ -0,0 +1,103 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ env:
11
+ PYTHON_VERSION: "3.11"
12
+
13
+ jobs:
14
+ quality:
15
+ name: Quality
16
+ runs-on: ubuntu-latest
17
+
18
+ steps:
19
+ - name: Check out repository
20
+ uses: actions/checkout@v4
21
+
22
+ - name: Set up Python
23
+ uses: actions/setup-python@v5
24
+ with:
25
+ python-version: ${{ env.PYTHON_VERSION }}
26
+ cache: "pip"
27
+
28
+ - name: Install system dependencies
29
+ run: |
30
+ sudo apt-get update
31
+ sudo apt-get install -y \
32
+ libcairo2-dev \
33
+ libgirepository-2.0-dev \
34
+ gir1.2-pango-1.0 \
35
+ pkg-config
36
+
37
+ - name: Install Python dependencies
38
+ run: |
39
+ python -m pip install --upgrade pip
40
+ python -m pip install .[dev]
41
+
42
+ - name: Run Ruff
43
+ run: python -m ruff check src tests
44
+
45
+ - name: Generate Pyright CI config
46
+ run: |
47
+ python - <<'PY'
48
+ import json
49
+ import site
50
+
51
+ config = {
52
+ "include": ["src", "tests"],
53
+ "typeCheckingMode": "strict",
54
+ "pythonVersion": "3.11",
55
+ "extraPaths": [".", "src", site.getsitepackages()[0]],
56
+ "executionEnvironments": [
57
+ {
58
+ "root": "tests",
59
+ "reportUnknownMemberType": "none",
60
+ }
61
+ ],
62
+ }
63
+
64
+ with open("pyright-ci.json", "w", encoding="utf-8") as file:
65
+ json.dump(config, file, indent=2)
66
+ PY
67
+
68
+ - name: Run Pyright
69
+ run: python -m pyright -p pyright-ci.json
70
+
71
+ - name: Run Rumdl
72
+ run: rumdl check README.md $(find docs -name '*.md' -type f | sort)
73
+
74
+ tests:
75
+ name: Tests
76
+ runs-on: ubuntu-latest
77
+
78
+ steps:
79
+ - name: Check out repository
80
+ uses: actions/checkout@v4
81
+
82
+ - name: Set up Python
83
+ uses: actions/setup-python@v5
84
+ with:
85
+ python-version: ${{ env.PYTHON_VERSION }}
86
+ cache: "pip"
87
+
88
+ - name: Install system dependencies
89
+ run: |
90
+ sudo apt-get update
91
+ sudo apt-get install -y \
92
+ libcairo2-dev \
93
+ libgirepository-2.0-dev \
94
+ gir1.2-pango-1.0 \
95
+ pkg-config
96
+
97
+ - name: Install Python dependencies
98
+ run: |
99
+ python -m pip install --upgrade pip
100
+ python -m pip install .[dev]
101
+
102
+ - name: Run Pytest
103
+ run: python -m pytest
@@ -0,0 +1,46 @@
1
+ name: Package
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ env:
12
+ PYTHON_VERSION: "3.11"
13
+
14
+ jobs:
15
+ build-distributions:
16
+ name: Build distributions
17
+ runs-on: ubuntu-latest
18
+
19
+ steps:
20
+ - name: Check out repository
21
+ uses: actions/checkout@v6
22
+ with:
23
+ persist-credentials: false
24
+
25
+ - name: Set up Python
26
+ uses: actions/setup-python@v6
27
+ with:
28
+ python-version: ${{ env.PYTHON_VERSION }}
29
+ cache: "pip"
30
+
31
+ - name: Install packaging tools
32
+ run: |
33
+ python -m pip install --upgrade pip
34
+ python -m pip install build twine
35
+
36
+ - name: Build wheel and sdist
37
+ run: python -m build
38
+
39
+ - name: Check distribution metadata
40
+ run: python -m twine check --strict dist/*
41
+
42
+ - name: Store distribution packages
43
+ uses: actions/upload-artifact@v5
44
+ with:
45
+ name: python-package-distributions
46
+ path: dist/
@@ -0,0 +1,70 @@
1
+ name: Publish
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+ - "*.*.*"
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ env:
13
+ PYTHON_VERSION: "3.11"
14
+ PYPI_PACKAGE_NAME: "pykara"
15
+
16
+ jobs:
17
+ build:
18
+ name: Build distributions
19
+ runs-on: ubuntu-latest
20
+
21
+ steps:
22
+ - name: Check out repository
23
+ uses: actions/checkout@v6
24
+ with:
25
+ persist-credentials: false
26
+
27
+ - name: Set up Python
28
+ uses: actions/setup-python@v6
29
+ with:
30
+ python-version: ${{ env.PYTHON_VERSION }}
31
+ cache: "pip"
32
+
33
+ - name: Install packaging tools
34
+ run: |
35
+ python -m pip install --upgrade pip
36
+ python -m pip install build twine
37
+
38
+ - name: Build wheel and sdist
39
+ run: python -m build
40
+
41
+ - name: Check distribution metadata
42
+ run: python -m twine check --strict dist/*
43
+
44
+ - name: Store distribution packages
45
+ uses: actions/upload-artifact@v5
46
+ with:
47
+ name: python-package-distributions
48
+ path: dist/
49
+
50
+ publish-to-pypi:
51
+ name: Publish to PyPI
52
+ if: startsWith(github.ref, 'refs/tags/')
53
+ needs:
54
+ - build
55
+ runs-on: ubuntu-latest
56
+ environment:
57
+ name: pypi
58
+ url: https://pypi.org/p/${{ env.PYPI_PACKAGE_NAME }}
59
+ permissions:
60
+ id-token: write
61
+
62
+ steps:
63
+ - name: Download distribution packages
64
+ uses: actions/download-artifact@v6
65
+ with:
66
+ name: python-package-distributions
67
+ path: dist/
68
+
69
+ - name: Publish distribution
70
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,36 @@
1
+ # Python bytecode and caches
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.pyo
5
+ *.pyd
6
+
7
+ # Virtual environments
8
+ .venv/
9
+ venv/
10
+ impl/.venv/
11
+
12
+ # Packaging artifacts
13
+ build/
14
+ dist/
15
+ *.egg-info/
16
+ .eggs/
17
+ pip-wheel-metadata/
18
+
19
+ # Test and lint caches
20
+ .pytest_cache/
21
+ .ruff_cache/
22
+ .mypy_cache/
23
+ .pyright/
24
+ .pyrightcache/
25
+ .coverage
26
+ .coverage.*
27
+ htmlcov/
28
+ .rumdl_cache/
29
+
30
+ # Local tooling and editor files
31
+ .DS_Store
32
+ .idea/
33
+ .vscode/
34
+ .codex
35
+ .claude
36
+ .agents
@@ -0,0 +1,7 @@
1
+ line-length = 88
2
+
3
+ [MD013]
4
+ line-length = 88
5
+ code-blocks = false
6
+ tables = false
7
+ reflow = true
pykara-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,107 @@
1
+ Metadata-Version: 2.4
2
+ Name: pykara
3
+ Version: 0.0.1
4
+ Summary: Standalone Python CLI karaoke templater.
5
+ Project-URL: Homepage, https://github.com/pandafansub/pykara
6
+ Project-URL: Documentation, https://github.com/pandafansub/pykara/blob/main/docs/index.md
7
+ Project-URL: Repository, https://github.com/pandafansub/pykara
8
+ Project-URL: Issues, https://github.com/pandafansub/pykara/issues
9
+ Author-email: Panda Fansub <contato@pandafansub.com>
10
+ Keywords: aegisub,ass,karaoke,subtitles
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: End Users/Desktop
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Topic :: Multimedia :: Video
18
+ Classifier: Topic :: Text Processing
19
+ Requires-Python: >=3.11
20
+ Requires-Dist: pycairo
21
+ Requires-Dist: pygobject
22
+ Provides-Extra: dev
23
+ Requires-Dist: build; extra == 'dev'
24
+ Requires-Dist: coverage; extra == 'dev'
25
+ Requires-Dist: pyright; extra == 'dev'
26
+ Requires-Dist: pytest; extra == 'dev'
27
+ Requires-Dist: ruff; extra == 'dev'
28
+ Requires-Dist: rumdl; extra == 'dev'
29
+ Requires-Dist: twine; extra == 'dev'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # pykara
33
+
34
+ Standalone Python CLI karaoke templater for ASS subtitles.
35
+
36
+ ## Requirements
37
+
38
+ - Python 3.11+
39
+ - Cairo and Pango bindings available to Python
40
+
41
+ On Ubuntu/Debian, the CI setup installs:
42
+
43
+ ```bash
44
+ sudo apt-get install -y \
45
+ libcairo2-dev \
46
+ libgirepository-2.0-dev \
47
+ gir1.2-pango-1.0 \
48
+ pkg-config
49
+ ```
50
+
51
+ ## Install
52
+
53
+ Install from PyPI:
54
+
55
+ ```bash
56
+ python -m pip install pykara
57
+ ```
58
+
59
+ The package name on PyPI is `pykara`. The CLI command remains
60
+ `pykara`.
61
+
62
+ For a source checkout with development tools:
63
+
64
+ ```bash
65
+ python -m pip install -e .[dev]
66
+ ```
67
+
68
+ ## Documentation
69
+
70
+ Start with the
71
+ [documentation index](https://github.com/pandafansub/pykara/blob/main/docs/index.md)
72
+ for directives, scopes, and helpers.
73
+
74
+ ## CLI
75
+
76
+ Write a new `.ass` output file:
77
+
78
+ ```bash
79
+ pykara input.ass output.ass
80
+ ```
81
+
82
+ Print only the generated ASS event rows:
83
+
84
+ ```bash
85
+ pykara --emit-events input.ass
86
+ ```
87
+
88
+ Use timecodes for frame-baked motion:
89
+
90
+ ```bash
91
+ pykara --timecodes path/to/timecodes.txt input.ass output.ass
92
+ ```
93
+
94
+ Useful flags:
95
+
96
+ - `--seed`: deterministic RNG seed for reproducible output
97
+ - `--debug`: show the full traceback instead of the short CLI error
98
+
99
+ ## Aegisub Bridge
100
+
101
+ The repository includes an Aegisub automation script at
102
+ `bridge/pykara.lua`.
103
+
104
+ The bridge expects the `pykara` command to be available in `PATH`, runs
105
+ `pykara --emit-events` for the currently open subtitle file, removes existing
106
+ `fx` lines, inserts the regenerated ones, and comments the source karaoke
107
+ lines.
pykara-0.0.1/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # pykara
2
+
3
+ Standalone Python CLI karaoke templater for ASS subtitles.
4
+
5
+ ## Requirements
6
+
7
+ - Python 3.11+
8
+ - Cairo and Pango bindings available to Python
9
+
10
+ On Ubuntu/Debian, the CI setup installs:
11
+
12
+ ```bash
13
+ sudo apt-get install -y \
14
+ libcairo2-dev \
15
+ libgirepository-2.0-dev \
16
+ gir1.2-pango-1.0 \
17
+ pkg-config
18
+ ```
19
+
20
+ ## Install
21
+
22
+ Install from PyPI:
23
+
24
+ ```bash
25
+ python -m pip install pykara
26
+ ```
27
+
28
+ The package name on PyPI is `pykara`. The CLI command remains
29
+ `pykara`.
30
+
31
+ For a source checkout with development tools:
32
+
33
+ ```bash
34
+ python -m pip install -e .[dev]
35
+ ```
36
+
37
+ ## Documentation
38
+
39
+ Start with the
40
+ [documentation index](https://github.com/pandafansub/pykara/blob/main/docs/index.md)
41
+ for directives, scopes, and helpers.
42
+
43
+ ## CLI
44
+
45
+ Write a new `.ass` output file:
46
+
47
+ ```bash
48
+ pykara input.ass output.ass
49
+ ```
50
+
51
+ Print only the generated ASS event rows:
52
+
53
+ ```bash
54
+ pykara --emit-events input.ass
55
+ ```
56
+
57
+ Use timecodes for frame-baked motion:
58
+
59
+ ```bash
60
+ pykara --timecodes path/to/timecodes.txt input.ass output.ass
61
+ ```
62
+
63
+ Useful flags:
64
+
65
+ - `--seed`: deterministic RNG seed for reproducible output
66
+ - `--debug`: show the full traceback instead of the short CLI error
67
+
68
+ ## Aegisub Bridge
69
+
70
+ The repository includes an Aegisub automation script at
71
+ `bridge/pykara.lua`.
72
+
73
+ The bridge expects the `pykara` command to be available in `PATH`, runs
74
+ `pykara --emit-events` for the currently open subtitle file, removes existing
75
+ `fx` lines, inserts the regenerated ones, and comments the source karaoke
76
+ lines.
@@ -0,0 +1,243 @@
1
+ script_name = "pykara Bridge"
2
+ script_description = "Run pykara and insert generated FX lines"
3
+ script_author = "pykara"
4
+ script_version = "1.0"
5
+
6
+ local function is_windows()
7
+ return package.config:sub(1, 1) == "\\"
8
+ end
9
+
10
+ local function shell_quote(value)
11
+ if is_windows() then
12
+ return '"' .. tostring(value):gsub('"', '""') .. '"'
13
+ end
14
+ return "'" .. tostring(value):gsub("'", "'\\''") .. "'"
15
+ end
16
+
17
+ local function join_path(...)
18
+ local separator = is_windows() and "\\" or "/"
19
+ local parts = {...}
20
+ return table.concat(parts, separator)
21
+ end
22
+
23
+ local function parse_ass_time(value)
24
+ local hours, minutes, seconds, centiseconds =
25
+ value:match("^(%d+):(%d%d):(%d%d)%.(%d%d)$")
26
+ if not hours then
27
+ error("Invalid ASS time: " .. tostring(value))
28
+ end
29
+ return (
30
+ (((tonumber(hours) * 60) + tonumber(minutes)) * 60 + tonumber(seconds))
31
+ * 1000
32
+ ) + tonumber(centiseconds) * 10
33
+ end
34
+
35
+ local function split_event_fields(value)
36
+ local fields = {}
37
+ local rest = value
38
+ for _ = 1, 9 do
39
+ local current, next_rest = rest:match("^([^,]*),(.*)$")
40
+ if not current then
41
+ break
42
+ end
43
+ table.insert(fields, current)
44
+ rest = next_rest
45
+ end
46
+ table.insert(fields, rest)
47
+ return fields
48
+ end
49
+
50
+ local function parse_generated_line(raw_line)
51
+ local kind, body = raw_line:match("^(%a+):%s*(.*)$")
52
+ if kind ~= "Dialogue" and kind ~= "Comment" then
53
+ error("Unexpected generated event line: " .. tostring(raw_line))
54
+ end
55
+
56
+ local fields = split_event_fields(body)
57
+ return {
58
+ class = "dialogue",
59
+ section = "[Events]",
60
+ comment = kind == "Comment",
61
+ layer = tonumber(fields[1]) or 0,
62
+ start_time = parse_ass_time(fields[2] or "0:00:00.00"),
63
+ end_time = parse_ass_time(fields[3] or "0:00:00.00"),
64
+ style = fields[4] or "",
65
+ actor = fields[5] or "",
66
+ margin_l = tonumber(fields[6]) or 0,
67
+ margin_r = tonumber(fields[7]) or 0,
68
+ margin_t = tonumber(fields[8]) or 0,
69
+ margin_b = tonumber(fields[8]) or 0,
70
+ effect = fields[9] or "",
71
+ text = fields[10] or "",
72
+ raw = raw_line,
73
+ }
74
+ end
75
+
76
+ local function current_ass_path()
77
+ local dir = aegisub.decode_path("?script")
78
+ local name = aegisub.file_name()
79
+
80
+ if not dir or dir == "" or not name or name == "" then
81
+ return nil
82
+ end
83
+
84
+ return join_path(dir, name)
85
+ end
86
+
87
+ local function pykara_command()
88
+ return "pykara --emit-events "
89
+ end
90
+
91
+ local function show_text_dialog(text)
92
+ aegisub.dialog.display({
93
+ {
94
+ class = "textbox",
95
+ x = 0,
96
+ y = 0,
97
+ width = 60,
98
+ height = 10,
99
+ text = text,
100
+ }
101
+ }, {"OK"})
102
+ end
103
+
104
+ local function show_missing_pykara_message(output)
105
+ local message = table.concat({
106
+ "The `pykara` command is not available in PATH.",
107
+ "",
108
+ "Install it in your environment and ensure the command is available.",
109
+ "",
110
+ "Command output:",
111
+ output,
112
+ }, "\n")
113
+ show_text_dialog(message)
114
+ end
115
+
116
+ local function strip_ansi(text)
117
+ return (text or ""):gsub("\27%[[%d;]*m", "")
118
+ end
119
+
120
+ local function normalize_output_lines(output)
121
+ local cleaned = strip_ansi(output)
122
+ local lines = {}
123
+ for raw_line in cleaned:gmatch("[^\r\n]+") do
124
+ local line = raw_line:gsub("%s+$", "")
125
+ if line ~= "" then
126
+ table.insert(lines, line)
127
+ end
128
+ end
129
+ return lines
130
+ end
131
+
132
+ local function try_bridge_command(command)
133
+ local pipe = io.popen(command .. " 2>&1")
134
+
135
+ if not pipe then
136
+ return false, "io.popen failed in this Aegisub build.", false
137
+ end
138
+
139
+ local output = pipe:read("*a") or ""
140
+ local ok = pipe:close()
141
+
142
+ return ok ~= nil, output, true
143
+ end
144
+
145
+ local function remove_existing_fx_lines(subs)
146
+ for i = #subs, 1, -1 do
147
+ local line = subs[i]
148
+ if line.class == "dialogue" and not line.comment and line.effect == "fx" then
149
+ subs.delete(i)
150
+ end
151
+ end
152
+ end
153
+
154
+ local function comment_input_lines(subs)
155
+ for i = 1, #subs do
156
+ local line = subs[i]
157
+ if line.class == "dialogue" and not line.comment then
158
+ local effect = (line.effect or ""):lower()
159
+ if effect == "kara" or effect == "karaoke" then
160
+ line.comment = true
161
+ subs[i] = line
162
+ end
163
+ end
164
+ end
165
+ end
166
+
167
+ local function run_bridge(subs)
168
+ local input_path = current_ass_path()
169
+
170
+ if not input_path then
171
+ aegisub.dialog.display({
172
+ {
173
+ class = "label",
174
+ x = 0,
175
+ y = 0,
176
+ width = 60,
177
+ height = 2,
178
+ label = "Could not resolve the current subtitle path.",
179
+ }
180
+ }, {"OK"})
181
+ return
182
+ end
183
+
184
+ local command = pykara_command() .. shell_quote(input_path)
185
+ local succeeded, output, command_started = try_bridge_command(command)
186
+
187
+ if not command_started then
188
+ aegisub.dialog.display({
189
+ {
190
+ class = "label",
191
+ x = 0,
192
+ y = 0,
193
+ width = 60,
194
+ height = 2,
195
+ label = "io.popen failed in this Aegisub build.",
196
+ }
197
+ }, {"OK"})
198
+ return
199
+ end
200
+
201
+ if not succeeded then
202
+ if output:match("command not found")
203
+ or output:match("not found")
204
+ or output:match("is not recognized as an internal or external command")
205
+ then
206
+ show_missing_pykara_message(output)
207
+ return
208
+ end
209
+
210
+ if output:match("No module named ['\"]pykara['\"]")
211
+ or output:match("ModuleNotFoundError:.-pykara")
212
+ then
213
+ show_missing_pykara_message(output)
214
+ return
215
+ end
216
+
217
+ local lines = normalize_output_lines(output)
218
+ if #lines > 0 then
219
+ show_text_dialog(table.concat(lines, "\n"))
220
+ else
221
+ show_text_dialog(output)
222
+ end
223
+ return
224
+ end
225
+
226
+ remove_existing_fx_lines(subs)
227
+
228
+ for raw_line in output:gmatch("[^\r\n]+") do
229
+ if raw_line:match("^Dialogue: ") or raw_line:match("^Comment: ") then
230
+ subs.append(parse_generated_line(raw_line))
231
+ end
232
+ end
233
+
234
+ comment_input_lines(subs)
235
+
236
+ aegisub.set_undo_point("pykara Apply Templates")
237
+ end
238
+
239
+ aegisub.register_macro(
240
+ "pykara Apply Templates",
241
+ "Generate FX lines through the Python bridge",
242
+ run_bridge
243
+ )